- Add drag-and-drop PDF upload to Quick Capture - New upload endpoint with size limits (warn 10MB, reject 50MB) - Add extractFromBuffer method to PaperExtractor - Visual feedback: drag-over highlight, file preview card Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
105 lines
3.5 KiB
TypeScript
105 lines
3.5 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { PaperExtractor } from '@/services/typescript/extractors/paper';
|
|
|
|
export const runtime = 'nodejs';
|
|
|
|
// Size limits in bytes
|
|
const WARN_SIZE = 10 * 1024 * 1024; // 10MB
|
|
const MAX_SIZE = 50 * 1024 * 1024; // 50MB
|
|
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
const formData = await request.formData();
|
|
const file = formData.get('file');
|
|
|
|
// Validate file presence
|
|
if (!file || !(file instanceof File)) {
|
|
return NextResponse.json(
|
|
{ success: false, error: 'PDF file is required' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
// Validate MIME type
|
|
if (file.type !== 'application/pdf') {
|
|
return NextResponse.json(
|
|
{ success: false, error: `Invalid file type: ${file.type}. Only PDF files are accepted.` },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
// Check file size
|
|
if (file.size > MAX_SIZE) {
|
|
return NextResponse.json(
|
|
{ success: false, error: `File too large (${Math.round(file.size / 1024 / 1024)}MB). Maximum size is 50MB.` },
|
|
{ status: 413 }
|
|
);
|
|
}
|
|
|
|
const isLargeFile = file.size > WARN_SIZE;
|
|
|
|
// Get buffer from file
|
|
const buffer = Buffer.from(await file.arrayBuffer());
|
|
|
|
// Extract PDF content using PaperExtractor
|
|
const extractor = new PaperExtractor();
|
|
const extraction = await extractor.extractFromBuffer(buffer, file.name);
|
|
|
|
// Derive title from metadata or filename
|
|
const title = extraction.metadata.title || file.name.replace(/\.pdf$/i, '');
|
|
|
|
// Create node via internal API call
|
|
// IMPORTANT: Use request.url origin for packaged Tauri app compatibility
|
|
const nodeApiUrl = new URL('/api/nodes', request.url);
|
|
|
|
const createResponse = await fetch(nodeApiUrl.toString(), {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
title,
|
|
type: 'pdf',
|
|
// Keep content readable in UI (not the full extracted text)
|
|
content: `Imported PDF: ${file.name} (${extraction.metadata.pages} pages, ${Math.round(extraction.metadata.text_length / 1000)}k characters)`,
|
|
// Full extracted text goes in chunk for universal chunking/embedding
|
|
chunk: extraction.chunk,
|
|
metadata: {
|
|
source: 'pdf_upload',
|
|
original_filename: file.name,
|
|
pages: extraction.metadata.pages,
|
|
text_length: extraction.metadata.text_length,
|
|
extraction_method: extraction.metadata.extraction_method,
|
|
imported_at: new Date().toISOString(),
|
|
},
|
|
}),
|
|
});
|
|
|
|
if (!createResponse.ok) {
|
|
const errorData = await createResponse.json().catch(() => ({}));
|
|
throw new Error(errorData.error || `Failed to create node: ${createResponse.status}`);
|
|
}
|
|
|
|
const nodeResult = await createResponse.json();
|
|
|
|
if (!nodeResult.success || !nodeResult.data?.id) {
|
|
throw new Error(nodeResult.error || 'Failed to create node');
|
|
}
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
nodeId: nodeResult.data.id,
|
|
title,
|
|
pages: extraction.metadata.pages,
|
|
textLength: extraction.metadata.text_length,
|
|
warning: isLargeFile ? `Large file (${Math.round(file.size / 1024 / 1024)}MB) - processing may take longer` : undefined,
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('[PDF Upload API] Error:', error);
|
|
const message = error instanceof Error ? error.message : 'Failed to process PDF upload';
|
|
return NextResponse.json(
|
|
{ success: false, error: message },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|