| import { useState, useCallback } from "react"; |
| import { PDFDocument } from "@/types/chat"; |
|
|
| export function usePDF() { |
| const [documents, setDocuments] = useState<PDFDocument[]>([]); |
| const [activeDocument, setActiveDocument] = useState<PDFDocument | null>(null); |
| const [isLoading, setIsLoading] = useState(false); |
| const [error, setError] = useState<string | null>(null); |
|
|
| const uploadPDF = useCallback(async (file: File): Promise<PDFDocument | null> => { |
| setIsLoading(true); |
| setError(null); |
|
|
| try { |
| |
| const arrayBuffer = await file.arrayBuffer(); |
| |
| |
| const textContent = await extractTextFromPDF(arrayBuffer); |
| |
| const doc: PDFDocument = { |
| id: crypto.randomUUID(), |
| name: file.name, |
| content: textContent, |
| pages: textContent.split(/\f/).length || 1, |
| uploadedAt: new Date(), |
| }; |
|
|
| setDocuments((prev) => [...prev, doc]); |
| setActiveDocument(doc); |
| return doc; |
| } catch (err) { |
| setError(err instanceof Error ? err.message : "Failed to process PDF"); |
| return null; |
| } finally { |
| setIsLoading(false); |
| } |
| }, []); |
|
|
| const removeDocument = useCallback((id: string) => { |
| setDocuments((prev) => prev.filter((d) => d.id !== id)); |
| if (activeDocument?.id === id) { |
| setActiveDocument(null); |
| } |
| }, [activeDocument]); |
|
|
| const getContextForRAG = useCallback((query: string, maxChunks: number = 5): string => { |
| if (!activeDocument) return ""; |
|
|
| |
| const chunks = activeDocument.content |
| .split(/\n\n+/) |
| .filter((chunk) => chunk.trim().length > 50); |
|
|
| |
| const queryWords = new Set(query.toLowerCase().split(/\s+/)); |
| |
| const scoredChunks = chunks.map((chunk) => { |
| const chunkWords = chunk.toLowerCase().split(/\s+/); |
| const overlap = chunkWords.filter((w) => queryWords.has(w)).length; |
| return { chunk, score: overlap }; |
| }); |
|
|
| scoredChunks.sort((a, b) => b.score - a.score); |
| |
| return scoredChunks |
| .slice(0, maxChunks) |
| .map((c) => c.chunk) |
| .join("\n\n---\n\n"); |
| }, [activeDocument]); |
|
|
| return { |
| documents, |
| activeDocument, |
| isLoading, |
| error, |
| uploadPDF, |
| removeDocument, |
| setActiveDocument, |
| getContextForRAG, |
| }; |
| } |
|
|
| |
| async function extractTextFromPDF(arrayBuffer: ArrayBuffer): Promise<string> { |
| |
| const uint8Array = new Uint8Array(arrayBuffer); |
| let text = ""; |
| |
| |
| const decoder = new TextDecoder("utf-8", { fatal: false }); |
| const raw = decoder.decode(uint8Array); |
| |
| |
| const textMatches = raw.match(/\(([^)]+)\)/g); |
| if (textMatches) { |
| text = textMatches |
| .map((m) => m.slice(1, -1)) |
| .filter((t) => t.length > 2 && !/^[\x00-\x1f]+$/.test(t)) |
| .join(" "); |
| } |
| |
| |
| if (text.trim().length < 100) { |
| text = `[PDF Document - Text extraction requires advanced parsing. |
| |
| For full PDF text extraction, the content would be processed server-side. |
| This document has been uploaded and is ready for AI-powered analysis. |
| |
| Document size: ${(arrayBuffer.byteLength / 1024).toFixed(1)} KB]`; |
| } |
|
|
| return text; |
| } |
|
|