/** * Chat Mode Resolution Logic * * Determines which chat mode (collector, extraction_review, vision, mvp, marketing, general) * should be active based on project state. */ import { getAdminDb } from '@/lib/firebase/admin'; import type { ChatMode } from '@/lib/ai/chat-modes'; /** * Resolve the appropriate chat mode for a project * * Logic: * 1. No knowledge_items → collector_mode * 2. Has knowledge but no extractions → collector_mode (needs to run extraction) * 3. Has extractions but no canonicalProductModel → extraction_review_mode * 4. Has canonicalProductModel but no mvpPlan → vision_mode * 5. Has mvpPlan but no marketingPlan → mvp_mode * 6. Has marketingPlan → marketing_mode * 7. Otherwise → general_chat_mode * * @param projectId - Firestore project ID * @returns The appropriate chat mode */ export async function resolveChatMode(projectId: string): Promise { try { const adminDb = getAdminDb(); // Load project data const projectSnapshot = await adminDb.collection('projects').doc(projectId).get(); if (!projectSnapshot.exists) { throw new Error(`Project ${projectId} not found`); } const projectData = projectSnapshot.data() ?? {}; const phaseData = (projectData.phaseData ?? {}) as Record; // Check for knowledge_items (top-level collection) const knowledgeSnapshot = await adminDb .collection('knowledge_items') .where('projectId', '==', projectId) .limit(1) .get(); const hasKnowledge = !knowledgeSnapshot.empty; // Check for chat_extractions (top-level collection) const extractionsSnapshot = await adminDb .collection('chat_extractions') .where('projectId', '==', projectId) .limit(1) .get(); const hasExtractions = !extractionsSnapshot.empty; // Apply resolution logic // PRIORITY: Check explicit phase transitions FIRST (overrides knowledge checks) if (projectData.currentPhase === 'extraction_review' || projectData.currentPhase === 'analyzed') { return 'extraction_review_mode'; } if (projectData.currentPhase === 'vision') { return 'vision_mode'; } if (projectData.currentPhase === 'mvp') { return 'mvp_mode'; } if (projectData.currentPhase === 'marketing') { return 'marketing_mode'; } if (!hasKnowledge) { return 'collector_mode'; } if (hasKnowledge && !hasExtractions) { return 'collector_mode'; // Has knowledge but needs extraction } // Fallback: Has extractions but no canonicalProductModel if (hasExtractions && !phaseData.canonicalProductModel) { return 'extraction_review_mode'; } if (phaseData.canonicalProductModel && !phaseData.mvpPlan) { return 'vision_mode'; } if (phaseData.mvpPlan && !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); // Default to collector on error return 'collector_mode'; } } /** * Get a summary of knowledge_items for context building */ export async function summarizeKnowledgeItems( projectId: string ): Promise<{ totalCount: number; bySourceType: Record; recentTitles: string[]; }> { try { const adminDb = getAdminDb(); const snapshot = await adminDb .collection('knowledge_items') .where('projectId', '==', projectId) .orderBy('createdAt', 'desc') .limit(20) .get(); const totalCount = snapshot.size; const bySourceType: Record = {}; const recentTitles: string[] = []; snapshot.docs.forEach((doc) => { const data = doc.data(); const sourceType = data.sourceType ?? 'unknown'; bySourceType[sourceType] = (bySourceType[sourceType] ?? 0) + 1; if (data.title && recentTitles.length < 5) { recentTitles.push(data.title); } }); return { totalCount, bySourceType, recentTitles }; } catch (error) { console.error('[Chat Mode Resolver] Failed to summarize knowledge:', error); return { totalCount: 0, bySourceType: {}, recentTitles: [] }; } } /** * Get a summary of chat_extractions for context building */ export async function summarizeExtractions( projectId: string ): Promise<{ totalCount: number; avgConfidence: number; avgCompletion: number; }> { try { const adminDb = getAdminDb(); const snapshot = await adminDb .collection('chat_extractions') .where('projectId', '==', projectId) .get(); if (snapshot.empty) { return { totalCount: 0, avgConfidence: 0, avgCompletion: 0 }; } let sumConfidence = 0; let sumCompletion = 0; let count = 0; snapshot.docs.forEach((doc) => { const data = doc.data(); sumConfidence += data.overallConfidence ?? 0; sumCompletion += data.overallCompletion ?? 0; count++; }); return { totalCount: count, avgConfidence: count > 0 ? sumConfidence / count : 0, avgCompletion: count > 0 ? sumCompletion / count : 0, }; } catch (error) { console.error('[Chat Mode Resolver] Failed to summarize extractions:', error); return { totalCount: 0, avgConfidence: 0, avgCompletion: 0 }; } }