import { NextResponse } from 'next/server' import { auth } from '@/auth' import prisma from '@/lib/prisma' import { agentSuggestionService } from '@/lib/ai/services/agent-suggestion.service' import { bridgeNotesService } from '@/lib/ai/services/bridge-notes.service' import { buildDashboardPaths, buildOpenLoops } from '@/lib/dashboard/paths.service' import { hasUserAiConsent } from '@/lib/consent/server-consent' import { getSystemConfig } from '@/lib/config' import { getChatProvider } from '@/lib/ai/factory' function excerptNoteContent(content: string | null | undefined, max = 200): string { if (!content) return '' const plain = content.replace(/<[^>]+>/g, ' ').replace(/ /g, ' ').replace(/\s+/g, ' ').trim() if (plain.length <= max) return plain return `${plain.slice(0, max)}…` } interface PathsBriefingSnapshot { recentNote?: { id: string title: string | null content: string notebookId: string | null } | null inboxCount?: number dueFlashcards?: number insights?: Array<{ id: string insight: string score: number viewed: boolean note1: { id: string; title: string | null } note2: { id: string; title: string | null } note1Excerpt?: string note2Excerpt?: string }> bridgeSuggestions?: Array<{ clusterAId: number clusterBId: number clusterAName: string clusterBName: string suggestedTitle: string suggestedContent: string justification: string }> agentSuggestions?: Array<{ id: string topic: string reason: string clusterId: number | null }> includeOpenLoops?: boolean } async function resolvePathsPayload(userId: string, snapshot?: PathsBriefingSnapshot | null) { const now = new Date() const hasSnapshot = !!snapshot?.recentNote const needsAgents = !snapshot?.agentSuggestions const needsBridges = !snapshot?.bridgeSuggestions const needsInsights = !snapshot?.insights const needsCounts = snapshot?.inboxCount === undefined || snapshot?.dueFlashcards === undefined const needsFocusNote = !snapshot?.recentNote const [ aiConsent, aiSettings, systemConfig, recentNotes, inboxCount, dueFlashcards, unviewedInsights, agentSuggestions, bridgeSuggestions, ] = await Promise.all([ hasUserAiConsent(), prisma.userAISettings.findUnique({ where: { userId }, select: { memoryEcho: true }, }), getSystemConfig(), needsFocusNote ? prisma.note.findMany({ where: { userId, trashedAt: null, isArchived: false }, select: { id: true, title: true, content: true, notebookId: true, updatedAt: true }, orderBy: { updatedAt: 'desc' }, take: 1, }) : Promise.resolve([]), needsCounts ? prisma.note.count({ where: { userId, notebookId: null, isArchived: false, trashedAt: null }, }) : Promise.resolve(snapshot!.inboxCount!), needsCounts ? prisma.flashcard.count({ where: { deck: { userId }, nextReviewAt: { lte: now } }, }) : Promise.resolve(snapshot!.dueFlashcards!), needsInsights ? prisma.memoryEchoInsight.findMany({ where: { userId, dismissed: false }, select: { id: true, insight: true, similarityScore: true, viewed: true, note1: { select: { id: true, title: true, content: true } }, note2: { select: { id: true, title: true, content: true } }, }, orderBy: { insightDate: 'desc' }, take: 5, }).catch(() => []) : Promise.resolve([]), needsAgents ? agentSuggestionService.getPending(userId, 3).catch(() => []) : Promise.resolve([]), needsBridges ? bridgeNotesService.getBridgeSuggestions(userId).catch(() => []) : Promise.resolve([]), ]) const aiProviderReady = !!getChatProvider(systemConfig) const memoryEchoEnabled = aiSettings?.memoryEcho ?? true const aiActive = aiConsent && aiProviderReady && memoryEchoEnabled const focusNotes = hasSnapshot && snapshot?.recentNote ? [{ ...snapshot.recentNote, updatedAt: now, }] : recentNotes const insights = snapshot?.insights ?? unviewedInsights.map(i => ({ id: i.id, insight: i.insight, score: i.similarityScore, viewed: i.viewed, note1: { id: i.note1.id, title: i.note1.title }, note2: { id: i.note2.id, title: i.note2.title }, note1Excerpt: excerptNoteContent(i.note1.content), note2Excerpt: excerptNoteContent(i.note2.content), })) const resolvedBridges = snapshot?.bridgeSuggestions ?? bridgeSuggestions.slice(0, 3).map(s => ({ clusterAId: s.clusterAId, clusterBId: s.clusterBId, clusterAName: s.clusterAName, clusterBName: s.clusterBName, suggestedTitle: s.suggestedTitle, suggestedContent: s.suggestedContent, justification: s.justification, })) const resolvedAgents = snapshot?.agentSuggestions ?? agentSuggestions.map(s => ({ id: s.id, topic: s.topic, reason: s.reason, clusterId: s.clusterId, })) const paths = await buildDashboardPaths({ userId, aiActive, recentNotes: focusNotes, inboxCount: needsCounts ? inboxCount : snapshot!.inboxCount!, dueFlashcards: needsCounts ? dueFlashcards : snapshot!.dueFlashcards!, insights, bridgeSuggestions: resolvedBridges, agentSuggestions: resolvedAgents, }).catch(() => []) const openLoops = snapshot?.includeOpenLoops === false ? [] : await buildOpenLoops(userId).catch(() => []) return { paths, openLoops } } /** * GET /api/briefing/paths — pistes enrichies (fallback sans snapshot client). */ export async function GET() { const session = await auth() if (!session?.user?.id) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const payload = await resolvePathsPayload(session.user.id) return NextResponse.json(payload) } /** * POST /api/briefing/paths — enrichissement avec snapshot briefing (évite re-fetch agents/ponts). */ export async function POST(request: Request) { const session = await auth() if (!session?.user?.id) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const snapshot = await request.json().catch(() => null) as PathsBriefingSnapshot | null const payload = await resolvePathsPayload(session.user.id, snapshot) return NextResponse.json(payload) }