import { NextResponse } from 'next/server'; import { getAdminDb } from '@/lib/firebase/admin'; import { createKnowledgeItem } from '@/lib/server/knowledge'; import type { KnowledgeSourceMeta } from '@/lib/types/knowledge'; import { chunkDocument } from '@/lib/utils/document-chunker'; interface ImportDocumentRequest { filename?: string; content?: string; mimeType?: string; } export const maxDuration = 30; export async function POST( request: Request, { params }: { params: Promise<{ projectId: string }> }, ) { try { const { projectId } = await params; if (!projectId) { return NextResponse.json({ error: 'Missing projectId' }, { status: 400 }); } const body = (await request.json()) as ImportDocumentRequest; const content = body.content?.trim(); const filename = body.filename?.trim(); if (!content || !filename) { return NextResponse.json({ error: 'filename and content are required' }, { status: 400 }); } const adminDb = getAdminDb(); const projectSnap = await adminDb.collection('projects').doc(projectId).get(); if (!projectSnap.exists) { return NextResponse.json({ error: 'Project not found' }, { status: 404 }); } console.log(`[import-document] Processing ${filename}, length=${content.length}`); // Chunk the document const chunks = chunkDocument(content, { maxChunkSize: 2000, chunkOverlap: 200, preserveParagraphs: true, preserveCodeBlocks: true, }); console.log(`[import-document] Created ${chunks.length} chunks for ${filename}`); // Store each chunk as a separate knowledge_item const knowledgeItemIds: string[] = []; for (const chunk of chunks) { const sourceMeta: KnowledgeSourceMeta = { origin: 'other', url: null, filename, createdAtOriginal: new Date().toISOString(), importance: 'primary', tags: ['document', 'chunked'], }; const chunkTitle = chunks.length > 1 ? `${filename} (chunk ${chunk.metadata.chunkIndex + 1}/${chunk.metadata.totalChunks})` : filename; const knowledgeItem = await createKnowledgeItem({ projectId, sourceType: 'imported_document', title: chunkTitle, content: chunk.content, sourceMeta: { ...sourceMeta, chunkMetadata: { chunkIndex: chunk.metadata.chunkIndex, totalChunks: chunk.metadata.totalChunks, startChar: chunk.metadata.startChar, endChar: chunk.metadata.endChar, tokenCount: chunk.metadata.tokenCount, }, }, }); knowledgeItemIds.push(knowledgeItem.id); // Chunk and embed in AlloyDB (fire-and-forget) (async () => { try { const { writeKnowledgeChunksForItem } = await import('@/lib/server/vector-memory'); await writeKnowledgeChunksForItem({ id: knowledgeItem.id, projectId: knowledgeItem.projectId, content: knowledgeItem.content, sourceMeta: knowledgeItem.sourceMeta, }); } catch (error) { console.error(`[import-document] Failed to chunk item ${knowledgeItem.id}:`, error); } })(); } // Also create a summary record in contextSources for UI display const contextSourcesRef = adminDb.collection('projects').doc(projectId).collection('contextSources'); await contextSourcesRef.add({ type: 'document', name: filename, summary: `Document with ${chunks.length} chunks (${content.length} characters)`, connectedAt: new Date(), metadata: { chunkCount: chunks.length, totalChars: content.length, mimeType: body.mimeType, knowledgeItemIds, }, }); return NextResponse.json({ success: true, filename, chunkCount: chunks.length, knowledgeItemIds, }); } catch (error) { console.error('[import-document] Failed to import document', error); return NextResponse.json( { error: 'Failed to import document', details: error instanceof Error ? error.message : String(error), }, { status: 500 }, ); } }