import { useState, useCallback } from 'react' import { useSuggestions } from './hooks/useSuggestions' import { motion, AnimatePresence } from 'framer-motion' 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' import AdvancedSettings from './components/AdvancedSettings' import PDFProcessor from './components/PDFProcessor' import MetadataForm from './components/MetadataForm' import JobsPanel from './components/JobsPanel' 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') // OCR state const [mode, setMode] = useState('plain_ocr') const [fileType, setFileType] = useState('image') const [image, setImage] = useState(null) const [imagePreview, setImagePreview] = useState(null) const [result, setResult] = useState(null) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [showAdvanced, setShowAdvanced] = useState(false) const [includeCaption, setIncludeCaption] = useState(false) const [prompt, setPrompt] = useState('') const [findTerm, setFindTerm] = useState('') const [advancedSettings, setAdvancedSettings] = useState({ base_size: 1024, image_size: 640, crop_mode: true, test_compress: false, }) const suggestions = useSuggestions() const [metadata, setMetadata] = useState({ author: '', book: '', chapter: '', page: '' }) // 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' } // Show the full-screen result view once at least one committable mode has a result const showResultView = view === 'new_job' && Object.keys(modeResults).length > 0 const handleFileTypeChange = useCallback((newType) => { setImage(null) if (imagePreview) URL.revokeObjectURL(imagePreview) setImagePreview(null) setError(null) setResult(null) setFileType(newType) }, [imagePreview]) const handleImageSelect = useCallback((file) => { if (file === null) { setImage(null) if (imagePreview && fileType === 'image') URL.revokeObjectURL(imagePreview) setImagePreview(null) setError(null) setResult(null) setModeResults({}) setEditedResults({}) setActiveResultMode(null) setCommitResult(null) } else { setImage(file) setImagePreview(fileType === 'image' ? URL.createObjectURL(file) : file) setError(null) setResult(null) setModeResults({}) setEditedResults({}) setActiveResultMode(null) setCommitResult(null) } }, [imagePreview, fileType]) const handleSubmit = async () => { if (!image) { setError('Please upload an image first'); return } setLoading(true) setError(null) setCommitResult(null) try { const formData = new FormData() formData.append('image', image) formData.append('mode', mode) formData.append('prompt', prompt) formData.append('grounding', mode === 'find_ref') formData.append('include_caption', includeCaption) formData.append('find_term', findTerm) formData.append('schema', '') formData.append('base_size', advancedSettings.base_size) formData.append('image_size', advancedSettings.image_size) formData.append('crop_mode', advancedSettings.crop_mode) formData.append('test_compress', advancedSettings.test_compress) const response = await axios.post(`${API_BASE}/ocr`, formData, { headers: { 'Content-Type': 'multipart/form-data' }, }) setResult(response.data) 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') } finally { setLoading(false) } } const handleNewAnalysis = () => { setResult(null) setModeResults({}) setEditedResults({}) setActiveResultMode(null) setCommitResult(null) } const handleCommitJob = useCallback(async () => { if (!image) return setCommitLoading(true) setCommitResult(null) try { const formData = new FormData() formData.append('image', image) formData.append('author', metadata.author) formData.append('book', metadata.book) formData.append('chapter', metadata.chapter) formData.append('page', metadata.page) 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, { headers: { 'Content-Type': 'multipart/form-data' }, }) setCommitResult({ success: true, job: response.data }) } catch (err) { setCommitResult({ success: false, error: err.response?.data?.detail || err.message }) } finally { setCommitLoading(false) } }, [image, editedResults, metadata, mode]) const handleCopy = useCallback(() => { const text = (activeResultMode && editedResults[activeResultMode]) || result?.text if (text) navigator.clipboard.writeText(text) }, [activeResultMode, editedResults, result]) const handleDownload = useCallback(() => { 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' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `deepseek-ocr-result.${ext}` a.click() URL.revokeObjectURL(url) }, [activeResultMode, editedResults, result, mode]) const metaField = (key) => (e) => setMetadata(m => ({ ...m, [key]: e.target.value })) return (
{/* Animated background */}
{/* Header */}

DeepSeek OCR

Next-Gen Vision AI

{/* Main Content */}
{/* ── Full-screen OCR result view ── */} {showResultView ? ( {/* Run additional modes */}
{loading ? <> Processing... : <> Analyze} {error &&

{error}

}
{/* Image + Text */}
{imagePreview && typeof imagePreview === 'string' ? (
Source
) : (

No preview

)}
{/* Mode tabs — only shown when multiple modes have results */} {Object.keys(modeResults).length > 1 && (
{Object.keys(modeResults).map(m => ( ))}
)}

{MODE_LABELS[activeResultMode] || 'Result'} (edit before committing)

{loading && COMMITTABLE_MODES.has(mode) ? (
) : (