137 lines
4.1 KiB
TypeScript
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 },
|
|
);
|
|
}
|
|
}
|
|
|