Files
vibn-frontend/app/api/projects/[projectId]/knowledge/themes/route.ts

106 lines
3.6 KiB
TypeScript

import { NextResponse } from 'next/server';
import { getAdminAuth, getAdminDb } from '@/lib/firebase/admin';
import { getAlloyDbClient } from '@/lib/db/alloydb';
import { GeminiLlmClient } from '@/lib/ai/gemini-client';
import { z } from 'zod';
const ThemeGroupingSchema = z.object({
themes: z.array(z.object({
theme: z.string().describe('A short, descriptive theme name (2-4 words)'),
description: z.string().describe('A brief description of what this theme represents'),
insightIds: z.array(z.string()).describe('Array of insight IDs that belong to this theme'),
})),
});
export async function POST(
request: Request,
{ params }: { params: Promise<{ projectId: string }> }
) {
try {
const { projectId } = await params;
// Authentication (skip in development if no auth header)
const authHeader = request.headers.get('Authorization');
const isDevelopment = process.env.NODE_ENV === 'development';
if (!isDevelopment || authHeader?.startsWith('Bearer ')) {
if (!authHeader?.startsWith('Bearer ')) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const token = authHeader.substring(7);
const auth = getAdminAuth();
const decoded = await auth.verifyIdToken(token);
if (!decoded?.uid) {
return NextResponse.json({ error: 'Invalid token' }, { status: 401 });
}
}
// Get insights from request body
const { insights } = await request.json();
if (!insights || insights.length === 0) {
return NextResponse.json({
success: true,
themes: [],
});
}
console.log('[API /knowledge/themes] Grouping', insights.length, 'insights into themes');
// Prepare insights for AI analysis
const insightsContext = insights.map((insight: any, index: number) =>
`[${insight.id}] ${insight.content?.substring(0, 200) || insight.title}`
).join('\n\n');
// Use AI to group insights into themes
const llm = new GeminiLlmClient();
const systemPrompt = `You are an expert at analyzing and categorizing information. Given a list of insights/knowledge chunks, group them into meaningful themes. Each theme should represent a coherent topic or concept. Aim for 3-7 themes depending on the diversity of content.`;
const userPrompt = `Analyze these insights and group them into themes:
${insightsContext}
Group these insights into themes. Each insight ID is in brackets at the start of each line. Return the themes with their associated insight IDs.`;
try {
const result = await llm.structuredCall({
model: 'gemini',
systemPrompt,
messages: [{ role: 'user', content: userPrompt }],
schema: ThemeGroupingSchema,
temperature: 0.3,
});
console.log('[API /knowledge/themes] Generated', result.themes.length, 'themes');
return NextResponse.json({
success: true,
themes: result.themes,
});
} catch (aiError) {
console.error('[API /knowledge/themes] AI grouping failed:', aiError);
// Fallback: create a single "Ungrouped" theme with all insights
return NextResponse.json({
success: true,
themes: [{
theme: 'All Insights',
description: 'Ungrouped insights',
insightIds: insights.map((i: any) => i.id),
}],
});
}
} catch (error) {
console.error('[API /knowledge/themes] Error:', error);
return NextResponse.json(
{
error: 'Failed to group insights into themes',
details: error instanceof Error ? error.message : String(error)
},
{ status: 500 }
);
}
}