sync: PDF file upload from private repo

- 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>
This commit is contained in:
“BeeRad”
2026-01-14 20:13:13 +11:00
co-authored by Claude Opus 4.5
parent 2cbc950fd4
commit 419d29dbe8
3 changed files with 427 additions and 53 deletions
@@ -28,6 +28,13 @@ interface ExtractionResult {
url: string;
}
interface BufferExtractionResult {
content: string;
chunk: string;
metadata: PaperMetadata;
filename: string;
}
type PdfJsTextItem = {
str?: string;
};
@@ -341,6 +348,41 @@ export class PaperExtractor {
}
}
/**
* Extract text from a PDF buffer (for file uploads)
* @param buffer - The PDF file contents as a Buffer
* @param filename - Optional original filename for metadata
*/
async extractFromBuffer(buffer: Buffer, filename?: string): Promise<BufferExtractionResult> {
// Validate PDF header
if (!this.isLikelyPdf(buffer)) {
const preview = buffer.slice(0, 64).toString('utf8');
throw new Error(`File does not appear to be a valid PDF (header: ${preview.substring(0, 20)}...)`);
}
// Extract text and metadata using existing private method
const { text, metadata } = await this.extractFromPDF(buffer);
// Clean the text
const cleanedText = this.cleanAcademicContent(text);
// Add filename to metadata
metadata.filename = filename || 'uploaded.pdf';
if (!metadata.extraction_method) {
metadata.extraction_method = 'typescript_pdf-parse';
}
// Format content for display
const content = this.formatContent(metadata, cleanedText);
return {
content,
chunk: cleanedText,
metadata,
filename: metadata.filename,
};
}
private formatErrorMessage(error: unknown): string {
if (error instanceof Error) {
return error.message;