Store all mode results (OCR, Describe, Freeform) in a single job record
- DB: add describe_text and freeform_text columns (ALTER TABLE IF NOT EXISTS) - Backend: commit and review endpoints accept/persist all three text fields - App: accumulate results per mode in state; tabs appear when >1 mode run; all results sent on Commit Job - JobDetail: tabbed text panel shows whichever fields are populated, all editable Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -36,12 +36,15 @@ function JobDetail({ jobId, onClose, onReviewed, onDeleted, suggestions = {} })
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
|
||||
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 [editedText, setEditedText] = useState('')
|
||||
const [editDescribeText, setEditDescribeText] = useState('')
|
||||
const [editFreeformText, setEditFreeformText] = useState('')
|
||||
const [activeTab, setActiveTab] = useState('ocr')
|
||||
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 [saveResult, setSaveResult] = useState(null)
|
||||
@@ -60,11 +63,17 @@ function JobDetail({ jobId, onClose, onReviewed, onDeleted, suggestions = {} })
|
||||
const d = res.data
|
||||
setJob(d)
|
||||
setEditedText(d.reviewed_text ?? d.ocr_text ?? '')
|
||||
setEditDescribeText(d.describe_text ?? '')
|
||||
setEditFreeformText(d.freeform_text ?? '')
|
||||
setEditAuthor(d.author || '')
|
||||
setEditBook(d.book || '')
|
||||
setEditChapter(d.chapter || '')
|
||||
setEditPage(d.page || '')
|
||||
setReviewerName(d.reviewer_name || '')
|
||||
// Default to first tab that has content
|
||||
if (d.reviewed_text || d.ocr_text) setActiveTab('ocr')
|
||||
else if (d.describe_text) setActiveTab('describe')
|
||||
else if (d.freeform_text) setActiveTab('freeform')
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
@@ -90,6 +99,8 @@ function JobDetail({ jobId, onClose, onReviewed, onDeleted, suggestions = {} })
|
||||
book: editBook,
|
||||
chapter: editChapter,
|
||||
page: editPage,
|
||||
describe_text: editDescribeText || null,
|
||||
freeform_text: editFreeformText || null,
|
||||
})
|
||||
setJob(res.data)
|
||||
setSaveResult({ success: true })
|
||||
@@ -199,26 +210,72 @@ function JobDetail({ jobId, onClose, onReviewed, onDeleted, suggestions = {} })
|
||||
/>
|
||||
</div>
|
||||
<div className="glass rounded-2xl p-4 flex flex-col h-full">
|
||||
{/* Tabs — only show tabs that have content */}
|
||||
{(() => {
|
||||
const tabs = [
|
||||
job.ocr_text || job.reviewed_text ? { id: 'ocr', label: 'OCR Text' } : null,
|
||||
job.describe_text != null ? { id: 'describe', label: 'Description' } : null,
|
||||
job.freeform_text != null ? { id: 'freeform', label: 'Freeform' } : null,
|
||||
].filter(Boolean)
|
||||
return tabs.length > 1 ? (
|
||||
<div className="flex gap-1 mb-3 flex-shrink-0">
|
||||
{tabs.map(t => (
|
||||
<button
|
||||
key={t.id}
|
||||
onClick={() => setActiveTab(t.id)}
|
||||
className={`px-3 py-1 rounded-lg text-xs font-medium transition-colors ${
|
||||
activeTab === t.id
|
||||
? 'bg-purple-600 text-white'
|
||||
: 'bg-white/5 text-gray-400 hover:bg-white/10'
|
||||
}`}
|
||||
>
|
||||
{t.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : null
|
||||
})()}
|
||||
|
||||
<p className="text-xs text-gray-400 mb-2 flex-shrink-0">
|
||||
{isReviewed ? 'Reviewed Text' : 'OCR Text'}
|
||||
{{ ocr: isReviewed ? 'Reviewed Text' : 'OCR Text', describe: 'Description', freeform: 'Freeform' }[activeTab]}
|
||||
<span className="text-purple-400 ml-1">(editable)</span>
|
||||
</p>
|
||||
<textarea
|
||||
value={editedText}
|
||||
onChange={e => setEditedText(e.target.value)}
|
||||
className="flex-1 w-full bg-transparent text-sm text-gray-200 font-mono resize-none focus:outline-none min-h-0"
|
||||
placeholder="Text content..."
|
||||
/>
|
||||
{/* Original OCR text collapsed for reviewed jobs */}
|
||||
{isReviewed && job.ocr_text && (
|
||||
<details className="flex-shrink-0 mt-2 border-t border-white/10 pt-2">
|
||||
<summary className="cursor-pointer text-xs text-gray-500 hover:text-gray-400 transition-colors">
|
||||
Original OCR Text
|
||||
</summary>
|
||||
<pre className="text-xs text-gray-600 whitespace-pre-wrap font-mono mt-1 max-h-28 overflow-y-auto">
|
||||
{job.ocr_text}
|
||||
</pre>
|
||||
</details>
|
||||
|
||||
{activeTab === 'ocr' && (
|
||||
<>
|
||||
<textarea
|
||||
value={editedText}
|
||||
onChange={e => setEditedText(e.target.value)}
|
||||
className="flex-1 w-full bg-transparent text-sm text-gray-200 font-mono resize-none focus:outline-none min-h-0"
|
||||
placeholder="OCR text..."
|
||||
/>
|
||||
{isReviewed && job.ocr_text && (
|
||||
<details className="flex-shrink-0 mt-2 border-t border-white/10 pt-2">
|
||||
<summary className="cursor-pointer text-xs text-gray-500 hover:text-gray-400 transition-colors">
|
||||
Original OCR Text
|
||||
</summary>
|
||||
<pre className="text-xs text-gray-600 whitespace-pre-wrap font-mono mt-1 max-h-28 overflow-y-auto">
|
||||
{job.ocr_text}
|
||||
</pre>
|
||||
</details>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{activeTab === 'describe' && (
|
||||
<textarea
|
||||
value={editDescribeText}
|
||||
onChange={e => setEditDescribeText(e.target.value)}
|
||||
className="flex-1 w-full bg-transparent text-sm text-gray-200 font-mono resize-none focus:outline-none min-h-0"
|
||||
placeholder="Description text..."
|
||||
/>
|
||||
)}
|
||||
{activeTab === 'freeform' && (
|
||||
<textarea
|
||||
value={editFreeformText}
|
||||
onChange={e => setEditFreeformText(e.target.value)}
|
||||
className="flex-1 w-full bg-transparent text-sm text-gray-200 font-mono resize-none focus:outline-none min-h-0"
|
||||
placeholder="Freeform result..."
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user