Fix commit job and OCR text editing
- OCR text is now shown in an editable textarea (plain_ocr mode) so users can correct it before committing - editedOcrText state tracks edits; commit job sends the edited value instead of the original result.text - Remove silent early-return guard that blocked commit when text was empty - Copy and download also use the edited text Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,9 @@ function App() {
|
|||||||
// Job metadata
|
// Job metadata
|
||||||
const [metadata, setMetadata] = useState({ author: '', book: '', chapter: '', page: '' })
|
const [metadata, setMetadata] = useState({ author: '', book: '', chapter: '', page: '' })
|
||||||
|
|
||||||
|
// Editable OCR text (for plain_ocr mode, editable before commit)
|
||||||
|
const [editedOcrText, setEditedOcrText] = useState('')
|
||||||
|
|
||||||
// Job commit state
|
// Job commit state
|
||||||
const [commitLoading, setCommitLoading] = useState(false)
|
const [commitLoading, setCommitLoading] = useState(false)
|
||||||
const [commitResult, setCommitResult] = useState(null)
|
const [commitResult, setCommitResult] = useState(null)
|
||||||
@@ -64,6 +67,7 @@ function App() {
|
|||||||
setImagePreview(fileType === 'image' ? URL.createObjectURL(file) : file)
|
setImagePreview(fileType === 'image' ? URL.createObjectURL(file) : file)
|
||||||
setError(null)
|
setError(null)
|
||||||
setResult(null)
|
setResult(null)
|
||||||
|
setEditedOcrText('')
|
||||||
setCommitResult(null)
|
setCommitResult(null)
|
||||||
}
|
}
|
||||||
}, [imagePreview, fileType])
|
}, [imagePreview, fileType])
|
||||||
@@ -95,6 +99,8 @@ function App() {
|
|||||||
headers: { 'Content-Type': 'multipart/form-data' },
|
headers: { 'Content-Type': 'multipart/form-data' },
|
||||||
})
|
})
|
||||||
setResult(response.data)
|
setResult(response.data)
|
||||||
|
setEditedOcrText(response.data.text || '')
|
||||||
|
setCommitResult(null)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err.response?.data?.detail || err.message || 'An error occurred')
|
setError(err.response?.data?.detail || err.message || 'An error occurred')
|
||||||
} finally {
|
} finally {
|
||||||
@@ -103,7 +109,7 @@ function App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleCommitJob = useCallback(async () => {
|
const handleCommitJob = useCallback(async () => {
|
||||||
if (!image || !result?.text) return
|
if (!image) return
|
||||||
setCommitLoading(true)
|
setCommitLoading(true)
|
||||||
setCommitResult(null)
|
setCommitResult(null)
|
||||||
try {
|
try {
|
||||||
@@ -113,7 +119,7 @@ function App() {
|
|||||||
formData.append('book', metadata.book)
|
formData.append('book', metadata.book)
|
||||||
formData.append('chapter', metadata.chapter)
|
formData.append('chapter', metadata.chapter)
|
||||||
formData.append('page', metadata.page)
|
formData.append('page', metadata.page)
|
||||||
formData.append('ocr_text', result.text)
|
formData.append('ocr_text', editedOcrText)
|
||||||
formData.append('mode', mode)
|
formData.append('mode', mode)
|
||||||
|
|
||||||
const response = await axios.post(`${API_BASE}/jobs`, formData, {
|
const response = await axios.post(`${API_BASE}/jobs`, formData, {
|
||||||
@@ -125,16 +131,18 @@ function App() {
|
|||||||
} finally {
|
} finally {
|
||||||
setCommitLoading(false)
|
setCommitLoading(false)
|
||||||
}
|
}
|
||||||
}, [image, result, metadata, mode])
|
}, [image, editedOcrText, metadata, mode])
|
||||||
|
|
||||||
const handleCopy = useCallback(() => {
|
const handleCopy = useCallback(() => {
|
||||||
if (result?.text) navigator.clipboard.writeText(result.text)
|
const text = editedOcrText || result?.text
|
||||||
}, [result])
|
if (text) navigator.clipboard.writeText(text)
|
||||||
|
}, [editedOcrText, result])
|
||||||
|
|
||||||
const handleDownload = useCallback(() => {
|
const handleDownload = useCallback(() => {
|
||||||
if (!result?.text) return
|
const text = editedOcrText || result?.text
|
||||||
|
if (!text) return
|
||||||
const ext = { plain_ocr: 'txt', describe: 'txt', find_ref: 'txt', freeform: 'txt' }[mode] || 'txt'
|
const ext = { plain_ocr: 'txt', describe: 'txt', find_ref: 'txt', freeform: 'txt' }[mode] || 'txt'
|
||||||
const blob = new Blob([result.text], { type: 'text/plain' })
|
const blob = new Blob([text], { type: 'text/plain' })
|
||||||
const url = URL.createObjectURL(blob)
|
const url = URL.createObjectURL(blob)
|
||||||
const a = document.createElement('a')
|
const a = document.createElement('a')
|
||||||
a.href = url
|
a.href = url
|
||||||
@@ -391,6 +399,8 @@ function App() {
|
|||||||
onCommitJob={mode === 'plain_ocr' && result ? handleCommitJob : null}
|
onCommitJob={mode === 'plain_ocr' && result ? handleCommitJob : null}
|
||||||
commitLoading={commitLoading}
|
commitLoading={commitLoading}
|
||||||
commitResult={commitResult}
|
commitResult={commitResult}
|
||||||
|
editedOcrText={editedOcrText}
|
||||||
|
onOcrTextChange={setEditedOcrText}
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Copy, Download, Sparkles, Loader2, CheckCircle2, ChevronDown, Database
|
|||||||
import ReactMarkdown from 'react-markdown'
|
import ReactMarkdown from 'react-markdown'
|
||||||
import DOMPurify from 'dompurify'
|
import DOMPurify from 'dompurify'
|
||||||
|
|
||||||
export default function ResultPanel({ result, loading, imagePreview, onCopy, onDownload, onCommitJob, commitLoading, commitResult }) {
|
export default function ResultPanel({ result, loading, imagePreview, onCopy, onDownload, onCommitJob, commitLoading, commitResult, editedOcrText, onOcrTextChange }) {
|
||||||
const canvasRef = useRef(null)
|
const canvasRef = useRef(null)
|
||||||
const imgRef = useRef(null)
|
const imgRef = useRef(null)
|
||||||
const [showAdvanced, setShowAdvanced] = useState(false)
|
const [showAdvanced, setShowAdvanced] = useState(false)
|
||||||
@@ -226,26 +226,37 @@ export default function ResultPanel({ result, loading, imagePreview, onCopy, onD
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Text result */}
|
{/* Text result — editable textarea in plain_ocr/commit mode, rendered otherwise */}
|
||||||
<div className="bg-white/5 border border-white/10 rounded-xl p-4 max-h-96 overflow-y-auto">
|
{onCommitJob ? (
|
||||||
{isHTML ? (
|
<div className="space-y-1">
|
||||||
<div
|
<p className="text-xs text-gray-400">OCR Text <span className="text-purple-400">(editable — correct before committing)</span></p>
|
||||||
className="prose prose-invert prose-sm max-w-none"
|
<textarea
|
||||||
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(result.text) }}
|
value={editedOcrText}
|
||||||
style={{
|
onChange={e => onOcrTextChange(e.target.value)}
|
||||||
color: '#e5e7eb',
|
rows={10}
|
||||||
}}
|
className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-sm text-gray-200 font-mono resize-y focus:outline-none focus:border-purple-500/50 transition-colors"
|
||||||
|
placeholder="OCR text will appear here..."
|
||||||
/>
|
/>
|
||||||
) : isMarkdown ? (
|
</div>
|
||||||
<div className="prose prose-invert prose-sm max-w-none">
|
) : (
|
||||||
<ReactMarkdown>{result.text}</ReactMarkdown>
|
<div className="bg-white/5 border border-white/10 rounded-xl p-4 max-h-96 overflow-y-auto">
|
||||||
</div>
|
{isHTML ? (
|
||||||
) : (
|
<div
|
||||||
<pre className="text-sm text-gray-200 whitespace-pre-wrap font-mono">
|
className="prose prose-invert prose-sm max-w-none"
|
||||||
{result.text}
|
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(result.text) }}
|
||||||
</pre>
|
style={{ color: '#e5e7eb' }}
|
||||||
)}
|
/>
|
||||||
</div>
|
) : isMarkdown ? (
|
||||||
|
<div className="prose prose-invert prose-sm max-w-none">
|
||||||
|
<ReactMarkdown>{result.text}</ReactMarkdown>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<pre className="text-sm text-gray-200 whitespace-pre-wrap font-mono">
|
||||||
|
{result.text}
|
||||||
|
</pre>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Raw Response Viewer */}
|
{/* Raw Response Viewer */}
|
||||||
{result.raw_text && (
|
{result.raw_text && (
|
||||||
|
|||||||
Reference in New Issue
Block a user