From 247a5e4b0eb4e90c7a98910ea45a4f2acc5c115d Mon Sep 17 00:00:00 2001 From: Aaron Roberts Date: Tue, 9 Jun 2026 17:57:11 +0100 Subject: [PATCH] Full-screen side-by-side layout for New Job and Browse Jobs New Job (plain_ocr): - After OCR completes, the entire main area becomes a flex-column view pinned to viewport height: image and editable textarea side by side at top (filling available space), metadata fields in a compact row below, Commit Job button at the bottom - "New Analysis" button in the header returns to the upload view - ResultPanel reverted to simple rendered-output only (no commit logic) Browse Jobs: - Selecting a job replaces the search list with a full-screen detail view using the same layout: image | editable textarea on top, all metadata fields + Reviewer name + action button in a single row below - "Back to results" button returns to the search/list grid - Search results now display as a responsive card grid Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/App.jsx | 288 +++++++----- frontend/src/components/JobsPanel.jsx | 593 ++++++++++-------------- frontend/src/components/ResultPanel.jsx | 148 ++---- 3 files changed, 454 insertions(+), 575 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 6d08cc1..3be8e74 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,6 +1,9 @@ import { useState, useCallback } from 'react' import { motion, AnimatePresence } from 'framer-motion' -import { Sparkles, Zap, Loader2, Settings, Image as ImageIcon, FileText, Layers } from 'lucide-react' +import { + Sparkles, Zap, Loader2, Settings, Image as ImageIcon, FileText, + Layers, ChevronLeft, CheckCircle2, Database, +} from 'lucide-react' import ImageUpload from './components/ImageUpload' import ModeSelector from './components/ModeSelector' import ResultPanel from './components/ResultPanel' @@ -12,12 +15,16 @@ import axios from 'axios' const API_BASE = import.meta.env.VITE_API_URL || '/api' +const INPUT_CLASS = + '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' + function App() { - const [view, setView] = useState('new_job') // 'new_job' | 'jobs' + const [view, setView] = useState('new_job') // OCR state const [mode, setMode] = useState('plain_ocr') - const [fileType, setFileType] = useState('image') // 'image' or 'pdf' + const [fileType, setFileType] = useState('image') const [image, setImage] = useState(null) const [imagePreview, setImagePreview] = useState(null) const [result, setResult] = useState(null) @@ -26,26 +33,20 @@ function App() { const [showAdvanced, setShowAdvanced] = useState(false) const [includeCaption, setIncludeCaption] = useState(false) - // Form state const [prompt, setPrompt] = useState('') const [findTerm, setFindTerm] = useState('') const [advancedSettings, setAdvancedSettings] = useState({ - base_size: 1024, - image_size: 640, - crop_mode: true, - test_compress: false + base_size: 1024, image_size: 640, crop_mode: true, test_compress: false, }) - // Job metadata 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 const [commitLoading, setCommitLoading] = useState(false) const [commitResult, setCommitResult] = useState(null) + // Whether to show the full-screen result view + const showResultView = view === 'new_job' && mode === 'plain_ocr' && !!result + const handleFileTypeChange = useCallback((newType) => { setImage(null) if (imagePreview) URL.revokeObjectURL(imagePreview) @@ -62,6 +63,8 @@ function App() { setImagePreview(null) setError(null) setResult(null) + setEditedOcrText('') + setCommitResult(null) } else { setImage(file) setImagePreview(fileType === 'image' ? URL.createObjectURL(file) : file) @@ -73,14 +76,10 @@ function App() { }, [imagePreview, fileType]) const handleSubmit = async () => { - if (!image) { - setError('Please upload an image first') - return - } + if (!image) { setError('Please upload an image first'); return } setLoading(true) setError(null) setCommitResult(null) - try { const formData = new FormData() formData.append('image', image) @@ -108,6 +107,12 @@ function App() { } } + const handleNewAnalysis = () => { + setResult(null) + setEditedOcrText('') + setCommitResult(null) + } + const handleCommitJob = useCallback(async () => { if (!image) return setCommitLoading(true) @@ -149,7 +154,9 @@ function App() { a.download = `deepseek-ocr-result.${ext}` a.click() URL.revokeObjectURL(url) - }, [result, mode]) + }, [editedOcrText, result, mode]) + + const metaField = (key) => (e) => setMetadata(m => ({ ...m, [key]: e.target.value })) return (
@@ -160,12 +167,12 @@ function App() {
@@ -173,11 +180,7 @@ function App() {
- +
@@ -190,30 +193,29 @@ function App() {
- {/* Navigation */}
{/* Main Content */} -
+
- {view === 'jobs' ? ( + + {/* ── Full-screen OCR result view (plain_ocr + result) ── */} + {showResultView ? ( + + {/* Image + Text */} +
+ {imagePreview && typeof imagePreview === 'string' ? ( +
+ Source +
+ ) : ( +
+

No preview

+
+ )} +
+

+ OCR Text (edit before committing) +

+