diff --git a/backend/main.py b/backend/main.py index 59b4fe8..b86ba20 100644 --- a/backend/main.py +++ b/backend/main.py @@ -603,6 +603,10 @@ async def process_pdf( class ReviewRequest(BaseModel): reviewed_text: str reviewer_name: str + author: Optional[str] = None + book: Optional[str] = None + chapter: Optional[str] = None + page: Optional[str] = None def _job_row_to_dict(row) -> Dict[str, Any]: @@ -811,11 +815,23 @@ async def review_job(job_id: str, body: ReviewRequest): SET status = 'reviewed', reviewed_text = %s, reviewer_name = %s, - reviewed_at = NOW() + reviewed_at = NOW(), + author = %s, + book = %s, + chapter = %s, + page = %s WHERE id = %s RETURNING * """, - (body.reviewed_text, body.reviewer_name, job_id), + ( + body.reviewed_text, + body.reviewer_name, + body.author or None, + body.book or None, + body.chapter or None, + body.page or None, + job_id, + ), ) row = cur.fetchone() except Exception as exc: diff --git a/frontend/src/components/JobsPanel.jsx b/frontend/src/components/JobsPanel.jsx index aa59d08..30999d1 100644 --- a/frontend/src/components/JobsPanel.jsx +++ b/frontend/src/components/JobsPanel.jsx @@ -29,37 +29,39 @@ function StatusBadge({ status }) { ) } -function MetaRow({ icon: Icon, label, value }) { - if (!value) return null - return ( -
- - {label}: - {value} -
- ) -} - function JobDetail({ jobId, onClose, onReviewed }) { const [job, setJob] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) + + // Editable fields const [editedText, setEditedText] = useState('') + const [editAuthor, setEditAuthor] = useState('') + const [editBook, setEditBook] = useState('') + const [editChapter, setEditChapter] = useState('') + const [editPage, setEditPage] = useState('') const [reviewerName, setReviewerName] = useState('') + const [submitting, setSubmitting] = useState(false) - const [reviewResult, setReviewResult] = useState(null) + const [saveResult, setSaveResult] = useState(null) useEffect(() => { let cancelled = false setLoading(true) setError(null) - setReviewResult(null) + setSaveResult(null) axios.get(`${API_BASE}/jobs/${jobId}`) .then(res => { if (!cancelled) { - setJob(res.data) - setEditedText(res.data.reviewed_text ?? res.data.ocr_text ?? '') + const d = res.data + setJob(d) + setEditedText(d.reviewed_text ?? d.ocr_text ?? '') + setEditAuthor(d.author || '') + setEditBook(d.book || '') + setEditChapter(d.chapter || '') + setEditPage(d.page || '') + setReviewerName(d.reviewer_name || '') } }) .catch(err => { @@ -72,23 +74,27 @@ function JobDetail({ jobId, onClose, onReviewed }) { return () => { cancelled = true } }, [jobId]) - const handleMarkReviewed = async () => { + const handleSave = async () => { if (!reviewerName.trim()) { - setReviewResult({ success: false, error: 'Reviewer name is required.' }) + setSaveResult({ success: false, error: 'Reviewer name is required.' }) return } setSubmitting(true) - setReviewResult(null) + setSaveResult(null) try { const res = await axios.put(`${API_BASE}/jobs/${jobId}/review`, { reviewed_text: editedText, reviewer_name: reviewerName.trim(), + author: editAuthor, + book: editBook, + chapter: editChapter, + page: editPage, }) setJob(res.data) - setReviewResult({ success: true }) + setSaveResult({ success: true }) onReviewed(res.data) } catch (err) { - setReviewResult({ success: false, error: err.response?.data?.detail || err.message }) + setSaveResult({ success: false, error: err.response?.data?.detail || err.message }) } finally { setSubmitting(false) } @@ -98,48 +104,38 @@ function JobDetail({ jobId, onClose, onReviewed }) { 'w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-sm text-gray-200 ' + 'placeholder-gray-600 focus:outline-none focus:border-purple-500/50 transition-colors' + const isReviewed = job?.status === 'reviewed' + return ( -
+
{/* Header */}
-

Job Detail

+
+ {job && } +

Job Detail

+
-
- {loading && ( -
- -
- )} + {loading && ( +
+ +
+ )} - {error && ( -
-

{error}

-
- )} + {error && ( +
+

{error}

+
+ )} - {job && !loading && ( - <> - {/* Status + IDs */} -
- - {job.id} -
+ {job && !loading && ( +
- {/* Metadata */} -
- - - - - - {job.mode && } -
- - {/* Image */} + {/* ── Left column: image + read-only info ── */} +

Source Image @@ -152,93 +148,141 @@ function JobDetail({ jobId, onClose, onReviewed }) { />

- {/* OCR / Reviewed text */} -
-

- {job.status === 'reviewed' ? 'Reviewed Text' : 'OCR Text (editable)'} -

- {job.status === 'reviewed' ? ( -
-
-                    {job.reviewed_text}
-                  
-
- ) : ( -