diff --git a/README.md b/README.md index c88ae5c..7d703c3 100644 --- a/README.md +++ b/README.md @@ -96,36 +96,29 @@ Export your OCR results in the format you need: 5. View results with bounding boxes (if enabled) 6. Copy or download the extracted text -### Processing PDFs (Multi-Page Documents) - NEW! +### Processing PDFs (Multi-Page Documents) -1. Select **"PDF Processing"** mode in the toggle +Processing a PDF is just another way to create an OCR Job — it produces plain, +editable text exactly like single-image OCR, so it flows into the same review and +commit workflow. + +1. Select **"PDF Processing"** in the file-type toggle 2. Upload a PDF file (up to 100MB) -3. Choose your OCR mode (same as above) -4. Select **output format**: - - 📝 **Markdown** - For documentation, wikis, GitHub - - 🌐 **HTML** - For web publishing, styled viewing - - 📄 **DOCX** - For Word editing, professional documents - - 📊 **JSON** - For programmatic access, data extraction -5. Click **"Process PDF"** -6. Watch the progress bar as pages are processed -7. Your file downloads automatically when complete! +3. Pick a **Model** and **Mode** (same as image OCR) +4. Click **"Process PDF"** +5. All pages are OCR'd and concatenated into one plain-text result +6. Edit the text, fill in metadata, and **Commit Job** — the PDF is stored as the + job's source document (shown alongside the text in Browse Jobs) + +> The `/api/process-pdf` endpoint still supports Markdown / HTML / DOCX / JSON +> exports for programmatic use (see the API reference below); the UI uses the +> plain-text (JSON) path to drive the job workflow. ### Tips for Best Results - **For scanned documents**: Use higher DPI (144-300) in advanced settings - **For tables**: The model excels at extracting structured data - **For formulas**: Mathematical notation is preserved in output -- **For images in PDFs**: Enable "Extract Images" to include them in output -- **For large PDFs**: JSON format is fastest, DOCX takes longer due to formatting - -### Output Format Comparison - -| Format | Best For | Features | File Size | -|--------|----------|----------|-----------| -| **Markdown** | Documentation, GitHub, wikis | Clean text, tables, code blocks | Smallest | -| **HTML** | Web viewing, sharing | Styled output, embedded images, tables | Medium | -| **DOCX** | Editing, professional docs | Full formatting, images, tables | Largest | -| **JSON** | Data processing, APIs | Structured data, metadata, page info | Small | ## Features diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 7144e10..09bc893 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,4 +1,4 @@ -import { useState, useCallback, useEffect } from 'react' +import { useState, useCallback, useEffect, useMemo } from 'react' import { useSuggestions } from './hooks/useSuggestions' import { useModels } from './hooks/useModels' import { motion, AnimatePresence } from 'framer-motion' @@ -11,7 +11,6 @@ import ModeSelector from './components/ModeSelector' import ModelSelector from './components/ModelSelector' import ResultPanel from './components/ResultPanel' import AdvancedSettings from './components/AdvancedSettings' -import PDFProcessor from './components/PDFProcessor' import MetadataForm from './components/MetadataForm' import JobsPanel from './components/JobsPanel' import axios from 'axios' @@ -65,6 +64,13 @@ function App() { } }, [models, model]) + // Blob URL for previewing an uploaded PDF in the result/review view + const pdfUrl = useMemo( + () => (fileType === 'pdf' && image ? URL.createObjectURL(image) : null), + [fileType, image], + ) + useEffect(() => () => { if (pdfUrl) URL.revokeObjectURL(pdfUrl) }, [pdfUrl]) + // Show the full-screen result view once at least one committable mode has a result const showResultView = view === 'new_job' && Object.keys(modeResults).length > 0 @@ -101,31 +107,60 @@ function App() { }, [imagePreview, fileType]) const handleSubmit = async () => { - if (!image) { setError('Please upload an image first'); return } + if (!image) { setError(`Please upload ${fileType === 'pdf' ? 'a PDF' : 'an image'} first`); return } setLoading(true) setError(null) setCommitResult(null) try { - const formData = new FormData() - formData.append('image', image) - if (model) formData.append('model', model) - formData.append('mode', mode) - formData.append('prompt', prompt) - formData.append('grounding', mode === 'find_ref') - formData.append('include_caption', includeCaption) - formData.append('find_term', findTerm) - formData.append('schema', '') - formData.append('base_size', advancedSettings.base_size) - formData.append('image_size', advancedSettings.image_size) - formData.append('crop_mode', advancedSettings.crop_mode) - formData.append('test_compress', advancedSettings.test_compress) + let text = '' + let responseData = null - const response = await axios.post(`${API_BASE}/ocr`, formData, { - headers: { 'Content-Type': 'multipart/form-data' }, - }) - setResult(response.data) + if (fileType === 'pdf') { + // Process the whole PDF into one plain-text blob (all pages), then feed it + // into the same edit/review/commit flow as image OCR. + const formData = new FormData() + formData.append('pdf_file', image) + if (model) formData.append('model', model) + formData.append('mode', mode) + formData.append('output_format', 'json') + formData.append('extract_images', false) + formData.append('grounding', false) + formData.append('include_caption', includeCaption) + formData.append('dpi', 144) + formData.append('base_size', advancedSettings.base_size) + formData.append('image_size', advancedSettings.image_size) + formData.append('crop_mode', advancedSettings.crop_mode) + + const response = await axios.post(`${API_BASE}/process-pdf`, formData, { + headers: { 'Content-Type': 'multipart/form-data' }, + }) + const pages = response.data?.pages || [] + text = pages.map(p => (p.text || '').trim()).filter(Boolean).join('\n\n') + responseData = { text, metadata: response.data?.metadata, total_pages: response.data?.total_pages } + } else { + const formData = new FormData() + formData.append('image', image) + if (model) formData.append('model', model) + formData.append('mode', mode) + formData.append('prompt', prompt) + formData.append('grounding', mode === 'find_ref') + formData.append('include_caption', includeCaption) + formData.append('find_term', findTerm) + formData.append('schema', '') + formData.append('base_size', advancedSettings.base_size) + formData.append('image_size', advancedSettings.image_size) + formData.append('crop_mode', advancedSettings.crop_mode) + formData.append('test_compress', advancedSettings.test_compress) + + const response = await axios.post(`${API_BASE}/ocr`, formData, { + headers: { 'Content-Type': 'multipart/form-data' }, + }) + responseData = response.data + text = response.data.text || '' + } + + setResult(responseData) if (COMMITTABLE_MODES.has(mode)) { - const text = response.data.text || '' setModeResults(prev => ({ ...prev, [mode]: text })) setEditedResults(prev => ({ ...prev, [mode]: text })) setActiveResultMode(mode) @@ -300,7 +335,15 @@ function App() { {/* Image + Text */}
{error}
-{error}
+Processing Failed
-{error}
-- {result.message || 'PDF processed successfully!'} -
- {outputFormat === 'json' && result.pages && ( -- Processed {result.total_pages} page{result.total_pages > 1 ? 's' : ''} -
-