fix: migrate AI chat system from Firebase/Firestore to Postgres

Firebase was not configured so every chat request crashed with
'Firebase Admin credentials not configured'.

- chat-mode-resolver.ts: read project phase from fs_projects (Postgres)
- chat-context.ts: load project data from fs_projects instead of Firestore
- /api/ai/conversation: store/retrieve conversations in chat_conversations
  Postgres table (created automatically on first use)
- /api/ai/chat: replace all Firestore reads/writes with Postgres queries
- v_ai_chat/page.tsx: replace Firebase client auth with useSession from
  next-auth/react; remove Firestore listeners, use REST API for project data

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-19 12:07:03 -08:00
parent a281d4d373
commit e3a6641e3c
6 changed files with 193 additions and 350 deletions

View File

@@ -9,15 +9,13 @@ import { Input } from "@/components/ui/input";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Send, Loader2, Paperclip, X, FileText, RotateCcw, Upload, CheckCircle2, AlertTriangle, Sparkles } from "lucide-react";
import { cn } from "@/lib/utils";
import { auth } from "@/lib/firebase/config";
import { useSession } from "next-auth/react";
import { toast } from "sonner";
import { GitHubRepoPicker } from "@/components/ai/github-repo-picker";
import { PhaseSidebar } from "@/components/ai/phase-sidebar";
import { CollapsibleSidebar } from "@/components/ui/collapsible-sidebar";
import { ExtractionResultsEditable } from "@/components/ai/extraction-results-editable";
import type { ChatExtractionData } from "@/lib/ai/chat-extraction-types";
import { db } from "@/lib/firebase/config";
import { doc, onSnapshot, getDoc } from "firebase/firestore";
import { VisionForm } from "@/components/ai/vision-form";
interface Message {
@@ -72,6 +70,7 @@ export default function GettingStartedPage() {
const params = useParams();
const projectId = params.projectId as string;
const workspace = params.workspace as string;
const { status: sessionStatus } = useSession();
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState("");
@@ -104,74 +103,45 @@ export default function GettingStartedPage() {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
// Check for vision answers on load
// Load project phase + vision answers from the Postgres-backed API
useEffect(() => {
if (!projectId) return;
const checkVision = async () => {
const loadProject = async () => {
try {
const projectDoc = await getDoc(doc(db, "projects", projectId));
if (projectDoc.exists()) {
const data = projectDoc.data();
const hasAnswers = data?.visionAnswers?.allAnswered === true;
const res = await fetch(`/api/projects/${projectId}`);
if (res.ok) {
const data = await res.json();
const phase = data.project?.currentPhase || 'collector';
setCurrentPhase(phase);
const hasAnswers = data.project?.visionAnswers?.allAnswered === true;
setHasVisionAnswers(hasAnswers);
}
} catch (error) {
console.error('Error checking vision answers:', error);
console.error('Error loading project:', error);
} finally {
setCheckingVision(false);
}
};
checkVision();
}, [projectId]);
// Listen to project phase changes
useEffect(() => {
if (!projectId) return;
const unsubscribe = onSnapshot(
doc(db, "projects", projectId),
(snapshot) => {
if (snapshot.exists()) {
const data = snapshot.data();
const phase = data?.currentPhase || "collector";
setCurrentPhase(phase);
// Update vision answers status
const hasAnswers = data?.visionAnswers?.allAnswered === true;
setHasVisionAnswers(hasAnswers);
}
}
);
return () => unsubscribe();
loadProject();
}, [projectId]);
// Initialize with AI welcome message
useEffect(() => {
if (!isInitialized && projectId) {
const unsubscribe = auth.onAuthStateChanged(async (user) => {
if (!user) {
// Not signed in, trigger AI welcome
if (!isInitialized && projectId && sessionStatus !== 'loading') {
const initialize = async () => {
if (sessionStatus === 'unauthenticated') {
setIsLoading(false);
setIsInitialized(true);
setTimeout(() => {
sendChatMessage("Hello");
}, 500);
setTimeout(() => sendChatMessage("Hello"), 500);
return;
}
// User is signed in, load conversation history first
// Signed in via NextAuth — load conversation history
try {
const token = await user.getIdToken();
// Fetch existing conversation history
const historyResponse = await fetch(`/api/ai/conversation?projectId=${projectId}`, {
headers: {
'Authorization': `Bearer ${token}`,
},
});
const historyResponse = await fetch(`/api/ai/conversation?projectId=${projectId}`);
let existingMessages: Message[] = [];
@@ -231,11 +201,11 @@ export default function GettingStartedPage() {
setIsLoading(false);
setIsInitialized(true);
}
});
};
return () => unsubscribe();
initialize();
}
}, [projectId, isInitialized]);
}, [projectId, isInitialized, sessionStatus]);
const sendChatMessage = async (messageContent: string) => {
const content = messageContent.trim();
@@ -251,24 +221,10 @@ export default function GettingStartedPage() {
setIsSending(true);
try {
const user = auth.currentUser;
if (!user) {
toast.error('Please sign in to continue');
setIsSending(false);
return;
}
const token = await user.getIdToken();
const response = await fetch('/api/ai/chat', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
projectId,
message: content,
}),
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ projectId, message: content }),
});
if (!response.ok) {
@@ -392,21 +348,8 @@ export default function GettingStartedPage() {
}
try {
const user = auth.currentUser;
if (!user) {
toast.error('Please sign in to reset chat');
return;
}
const token = await user.getIdToken();
const response = await fetch('/api/ai/conversation/reset', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ projectId }),
const response = await fetch(`/api/ai/conversation?projectId=${projectId}`, {
method: 'DELETE',
});
if (response.ok) {
@@ -433,20 +376,9 @@ export default function GettingStartedPage() {
setExtractionStatus("importing");
setExtractionError(null);
const user = auth.currentUser;
if (!user) {
toast.error("Please sign in to import chats");
setIsImporting(false);
return;
}
const token = await user.getIdToken();
const importResponse = await fetch(`/api/projects/${projectId}/knowledge/import-ai-chat`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
title: extractForm.title || "Imported AI chat",
provider: extractForm.provider,