import { NextResponse } from 'next/server'; import { getServerSession } from 'next-auth'; import { authOptions } from '@/lib/auth/authOptions'; import { query } from '@/lib/db-postgres'; export const maxDuration = 60; const GEMINI_API_KEY = process.env.GOOGLE_API_KEY || ''; const GEMINI_MODEL = process.env.GEMINI_MODEL || 'gemini-2.0-flash-exp'; const GEMINI_BASE_URL = 'https://generativelanguage.googleapis.com/v1beta/models'; async function callGemini(prompt: string): Promise { const res = await fetch(`${GEMINI_BASE_URL}/${GEMINI_MODEL}:generateContent?key=${GEMINI_API_KEY}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }], generationConfig: { temperature: 0.2, maxOutputTokens: 4096 }, }), }); const data = await res.json(); const text = data?.candidates?.[0]?.content?.parts?.[0]?.text ?? ''; return text; } function parseJsonBlock(raw: string): unknown { const trimmed = raw.trim(); const cleaned = trimmed.startsWith('```') ? trimmed.replace(/^```(?:json)?/i, '').replace(/```$/, '').trim() : trimmed; return JSON.parse(cleaned); } export async function POST( req: Request, { params }: { params: Promise<{ projectId: string }> } ) { try { const { projectId } = await params; const session = await getServerSession(authOptions); if (!session?.user?.email) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const body = await req.json() as { chatText?: string }; const chatText = body.chatText?.trim() || ''; if (!chatText) { return NextResponse.json({ error: 'chatText is required' }, { status: 400 }); } // Verify project ownership const rows = await query<{ data: Record }>( `SELECT p.data FROM fs_projects p JOIN fs_users u ON u.id = p.user_id WHERE p.id = $1::text AND u.data->>'email' = $2::text LIMIT 1`, [projectId, session.user.email] ); if (rows.length === 0) { return NextResponse.json({ error: 'Project not found' }, { status: 404 }); } const extractionPrompt = `You are a product analyst. A founder has pasted AI chat conversation history below. Extract and categorise the following from those conversations. Return ONLY valid JSON — no markdown, no explanation. JSON schema: { "decisions": ["string — concrete decisions already made"], "ideas": ["string — product ideas and features mentioned"], "openQuestions": ["string — unresolved questions that still need answers"], "architecture": ["string — technical architecture notes, stack choices, infra decisions"], "targetUsers": ["string — user segments, personas, or target audiences mentioned"] } Each array can be empty if nothing was found for that category. Extract real content — be specific and concise. Max 10 items per bucket. --- CHAT HISTORY START --- ${chatText.slice(0, 12000)} --- CHAT HISTORY END --- Return only the JSON object:`; const raw = await callGemini(extractionPrompt); let analysisResult: { decisions: string[]; ideas: string[]; openQuestions: string[]; architecture: string[]; targetUsers: string[]; }; try { analysisResult = parseJsonBlock(raw) as typeof analysisResult; } catch { // Fallback: return empty buckets with a note analysisResult = { decisions: [], ideas: [], openQuestions: ["Could not parse extracted insights — try pasting more structured conversation"], architecture: [], targetUsers: [], }; } // Save analysis result to project data const current = rows[0].data ?? {}; const updated = { ...current, analysisResult, creationStage: 'review', updatedAt: new Date().toISOString(), }; await query( `UPDATE fs_projects SET data = $2::jsonb WHERE id = $1::text`, [projectId, JSON.stringify(updated)] ); return NextResponse.json({ analysisResult }); } catch (err) { console.error('[analyze-chats]', err); return NextResponse.json({ error: 'Internal error' }, { status: 500 }); } }