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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user