diff --git a/backend/database.py b/backend/database.py index 6720b5c..416b7c0 100644 --- a/backend/database.py +++ b/backend/database.py @@ -45,6 +45,15 @@ def init_db(): cur.execute(""" CREATE INDEX IF NOT EXISTS ocr_jobs_submitted_at_idx ON ocr_jobs(submitted_at DESC) """) + # Add columns introduced after initial schema (safe to run repeatedly) + cur.execute(""" + ALTER TABLE ocr_jobs + ADD COLUMN IF NOT EXISTS describe_text TEXT + """) + cur.execute(""" + ALTER TABLE ocr_jobs + ADD COLUMN IF NOT EXISTS freeform_text TEXT + """) # Unique constraint: prevent duplicate (author, chapter, page) submissions. # Applies only when all three fields are non-null. cur.execute(""" diff --git a/backend/main.py b/backend/main.py index d7397ad..11e7960 100644 --- a/backend/main.py +++ b/backend/main.py @@ -607,6 +607,8 @@ class ReviewRequest(BaseModel): book: Optional[str] = None chapter: Optional[str] = None page: Optional[str] = None + describe_text: Optional[str] = None + freeform_text: Optional[str] = None def _job_row_to_dict(row) -> Dict[str, Any]: @@ -628,6 +630,8 @@ async def commit_job( chapter: str = Form(""), page: str = Form(""), ocr_text: str = Form(""), + describe_text: str = Form(""), + freeform_text: str = Form(""), mode: str = Form("plain_ocr"), ): """Commit an OCR job: save the image and insert a DB record.""" @@ -660,13 +664,13 @@ async def commit_job( """ INSERT INTO ocr_jobs (id, author, book, chapter, page, image_path, original_filename, - ocr_text, mode, status) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, 'unreviewed') + ocr_text, describe_text, freeform_text, mode, status) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 'unreviewed') RETURNING * """, (job_id, author or None, book or None, chapter or None, page or None, image_path, original_filename, - ocr_text or None, mode), + ocr_text or None, describe_text or None, freeform_text or None, mode), ) row = cur.fetchone() except Exception as exc: @@ -852,7 +856,9 @@ async def review_job(job_id: str, body: ReviewRequest): author = %s, book = %s, chapter = %s, - page = %s + page = %s, + describe_text = %s, + freeform_text = %s WHERE id = %s RETURNING * """, @@ -863,6 +869,8 @@ async def review_job(job_id: str, body: ReviewRequest): body.book or None, body.chapter or None, body.page or None, + body.describe_text, + body.freeform_text, job_id, ), ) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 29ef466..001c48e 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -43,15 +43,21 @@ function App() { const suggestions = useSuggestions() const [metadata, setMetadata] = useState({ author: '', book: '', chapter: '', page: '' }) - const [editedOcrText, setEditedOcrText] = useState('') + // Results accumulated per mode: { plain_ocr: 'text', describe: 'text', freeform: 'text' } + const [modeResults, setModeResults] = useState({}) + const [editedResults, setEditedResults] = useState({}) + const [activeResultMode, setActiveResultMode] = useState(null) const [commitLoading, setCommitLoading] = useState(false) const [commitResult, setCommitResult] = useState(null) // Modes that produce editable text output and can be committed to the DB const COMMITTABLE_MODES = new Set(['plain_ocr', 'describe', 'freeform']) + const MODE_LABELS = { plain_ocr: 'OCR Text', describe: 'Description', freeform: 'Freeform' } - // Whether to show the full-screen result view - const showResultView = view === 'new_job' && COMMITTABLE_MODES.has(mode) && !!result + // Show the full-screen result view when any committable mode has a result (or is loading) + const showResultView = view === 'new_job' && ( + Object.keys(modeResults).length > 0 || (loading && COMMITTABLE_MODES.has(mode)) + ) const handleFileTypeChange = useCallback((newType) => { setImage(null) @@ -69,14 +75,18 @@ function App() { setImagePreview(null) setError(null) setResult(null) - setEditedOcrText('') + setModeResults({}) + setEditedResults({}) + setActiveResultMode(null) setCommitResult(null) } else { setImage(file) setImagePreview(fileType === 'image' ? URL.createObjectURL(file) : file) setError(null) setResult(null) - setEditedOcrText('') + setModeResults({}) + setEditedResults({}) + setActiveResultMode(null) setCommitResult(null) } }, [imagePreview, fileType]) @@ -104,7 +114,12 @@ function App() { headers: { 'Content-Type': 'multipart/form-data' }, }) setResult(response.data) - setEditedOcrText(response.data.text || '') + if (COMMITTABLE_MODES.has(mode)) { + const text = response.data.text || '' + setModeResults(prev => ({ ...prev, [mode]: text })) + setEditedResults(prev => ({ ...prev, [mode]: text })) + setActiveResultMode(mode) + } setCommitResult(null) } catch (err) { setError(err.response?.data?.detail || err.message || 'An error occurred') @@ -115,7 +130,9 @@ function App() { const handleNewAnalysis = () => { setResult(null) - setEditedOcrText('') + setModeResults({}) + setEditedResults({}) + setActiveResultMode(null) setCommitResult(null) } @@ -130,7 +147,9 @@ function App() { formData.append('book', metadata.book) formData.append('chapter', metadata.chapter) formData.append('page', metadata.page) - formData.append('ocr_text', editedOcrText) + formData.append('ocr_text', editedResults.plain_ocr || '') + formData.append('describe_text', editedResults.describe || '') + formData.append('freeform_text', editedResults.freeform || '') formData.append('mode', mode) const response = await axios.post(`${API_BASE}/jobs`, formData, { @@ -142,15 +161,15 @@ function App() { } finally { setCommitLoading(false) } - }, [image, editedOcrText, metadata, mode]) + }, [image, editedResults, metadata, mode]) const handleCopy = useCallback(() => { - const text = editedOcrText || result?.text + const text = (activeResultMode && editedResults[activeResultMode]) || result?.text if (text) navigator.clipboard.writeText(text) - }, [editedOcrText, result]) + }, [activeResultMode, editedResults, result]) const handleDownload = useCallback(() => { - const text = editedOcrText || result?.text + const text = (activeResultMode && editedResults[activeResultMode]) || result?.text if (!text) return const ext = { plain_ocr: 'txt', describe: 'txt', find_ref: 'txt', freeform: 'txt' }[mode] || 'txt' const blob = new Blob([text], { type: 'text/plain' }) @@ -260,16 +279,40 @@ function App() { )}
- {{ plain_ocr: 'OCR Text', describe: 'Description', freeform: 'Result' }[mode] || 'Result'} + {MODE_LABELS[activeResultMode] || 'Result'} (edit before committing)
-- {isReviewed ? 'Reviewed Text' : 'OCR Text'} + {{ ocr: isReviewed ? 'Reviewed Text' : 'OCR Text', describe: 'Description', freeform: 'Freeform' }[activeTab]} (editable)
-
- {job.ocr_text}
-
-
+ {job.ocr_text}
+
+