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

137 lines
4.1 KiB
TypeScript

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 },
);
}
}