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>
92 lines
3.0 KiB
TypeScript
92 lines
3.0 KiB
TypeScript
/**
|
|
* Chat Mode Resolution Logic
|
|
*
|
|
* Determines which chat mode (collector, extraction_review, vision, mvp, marketing, general)
|
|
* should be active based on project state stored in Postgres.
|
|
*/
|
|
|
|
import { query } from '@/lib/db-postgres';
|
|
import type { ChatMode } from '@/lib/ai/chat-modes';
|
|
|
|
/**
|
|
* Resolve the appropriate chat mode for a project using Postgres (fs_projects).
|
|
*/
|
|
export async function resolveChatMode(projectId: string): Promise<ChatMode> {
|
|
try {
|
|
const rows = await query<{ data: any }>(
|
|
`SELECT data FROM fs_projects WHERE id = $1 LIMIT 1`,
|
|
[projectId]
|
|
);
|
|
|
|
if (rows.length === 0) {
|
|
console.warn(`[Chat Mode Resolver] Project ${projectId} not found`);
|
|
return 'collector_mode';
|
|
}
|
|
|
|
const projectData = rows[0].data ?? {};
|
|
const phaseData = (projectData.phaseData ?? {}) as Record<string, any>;
|
|
const currentPhase: string = projectData.currentPhase ?? 'collector';
|
|
|
|
// Explicit phase overrides
|
|
if (currentPhase === 'extraction_review' || currentPhase === 'analyzed') return 'extraction_review_mode';
|
|
if (currentPhase === 'vision') return 'vision_mode';
|
|
if (currentPhase === 'mvp') return 'mvp_mode';
|
|
if (currentPhase === 'marketing') return 'marketing_mode';
|
|
|
|
// Derive from phase artifacts
|
|
if (!phaseData.canonicalProductModel) return 'collector_mode';
|
|
if (!phaseData.mvpPlan) return 'vision_mode';
|
|
if (!phaseData.marketingPlan) return 'mvp_mode';
|
|
if (phaseData.marketingPlan) return 'marketing_mode';
|
|
|
|
return 'general_chat_mode';
|
|
} catch (error) {
|
|
console.error('[Chat Mode Resolver] Failed to resolve mode:', error);
|
|
return 'collector_mode';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Summarise knowledge items for context building.
|
|
* Uses Postgres fs_knowledge_items if available, otherwise returns empty.
|
|
*/
|
|
export async function summarizeKnowledgeItems(projectId: string): Promise<{
|
|
totalCount: number;
|
|
bySourceType: Record<string, number>;
|
|
recentTitles: string[];
|
|
}> {
|
|
try {
|
|
const rows = await query<{ data: any }>(
|
|
`SELECT data FROM fs_knowledge_items WHERE project_id = $1 ORDER BY created_at DESC LIMIT 20`,
|
|
[projectId]
|
|
);
|
|
|
|
const bySourceType: Record<string, number> = {};
|
|
const recentTitles: string[] = [];
|
|
|
|
for (const row of rows) {
|
|
const d = row.data ?? {};
|
|
const sourceType = d.sourceType ?? 'unknown';
|
|
bySourceType[sourceType] = (bySourceType[sourceType] ?? 0) + 1;
|
|
if (d.title && recentTitles.length < 5) recentTitles.push(d.title);
|
|
}
|
|
|
|
return { totalCount: rows.length, bySourceType, recentTitles };
|
|
} catch {
|
|
// Table may not exist for older deployments — return empty
|
|
return { totalCount: 0, bySourceType: {}, recentTitles: [] };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Summarise extractions for context building.
|
|
* Returns empty defaults — extractions not yet migrated to Postgres.
|
|
*/
|
|
export async function summarizeExtractions(projectId: string): Promise<{
|
|
totalCount: number;
|
|
avgConfidence: number;
|
|
avgCompletion: number;
|
|
}> {
|
|
return { totalCount: 0, avgConfidence: 0, avgCompletion: 0 };
|
|
}
|