vn6295337's picture
Add parsing evaluation UI to frontend
2eda359
import { useState } from 'react';
import { evalParsing } from '../api/client';
export default function ParsingEval({ file, accessToken, onClose }) {
const [loading, setLoading] = useState(false);
const [result, setResult] = useState(null);
const [error, setError] = useState(null);
const runEval = async () => {
setLoading(true);
setError(null);
setResult(null);
try {
const data = await evalParsing(file.path_lower, accessToken);
if (data.error) {
setError(data.error);
} else {
setResult(data);
}
} catch (err) {
setError(err.message);
}
setLoading(false);
};
// Format number with commas
const formatNumber = (num) => {
return num?.toLocaleString() || '0';
};
return (
<div className="fixed inset-0 bg-black/70 flex items-center justify-center z-50 p-4">
<div className="bg-slate-800 border border-slate-700 rounded-xl w-full max-w-2xl max-h-[85vh] flex flex-col shadow-xl">
{/* Header */}
<div className="p-4 border-b border-slate-700 flex items-center justify-between">
<div>
<h3 className="font-medium text-slate-100">Docling Parsing Evaluation</h3>
<p className="text-sm text-slate-400 mt-0.5 truncate max-w-md">{file.name}</p>
</div>
<button
type="button"
onClick={onClose}
className="p-1.5 hover:bg-slate-700 rounded-lg transition-colors"
aria-label="Close"
>
<svg className="w-5 h-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{/* Content */}
<div className="flex-1 overflow-auto p-4">
{!result && !loading && !error && (
<div className="text-center py-8">
<svg className="w-16 h-16 text-slate-600 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" />
</svg>
<p className="text-slate-300 mb-2">Test Docling's document parsing</p>
<p className="text-sm text-slate-500 mb-6">
This will download the file and analyze how Docling extracts structure
</p>
<button
type="button"
onClick={runEval}
className="px-6 py-2.5 bg-blue-600 text-white rounded-lg font-medium hover:bg-blue-700 active:scale-[0.98] transition-all"
>
Run Parsing Evaluation
</button>
</div>
)}
{loading && (
<div className="text-center py-12">
<div className="w-10 h-10 border-3 border-blue-400 border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
<p className="text-slate-300">Parsing document with Docling...</p>
<p className="text-sm text-slate-500 mt-2">This may take a moment for large files</p>
</div>
)}
{error && (
<div className="bg-red-900/30 border border-red-700 rounded-lg p-4">
<div className="flex items-start gap-3">
<svg className="w-5 h-5 text-red-400 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<div>
<p className="text-red-400 font-medium">Parsing failed</p>
<p className="text-sm text-red-300 mt-1">{error}</p>
</div>
</div>
<button
type="button"
onClick={runEval}
className="mt-4 px-4 py-2 bg-slate-700 text-slate-200 rounded-lg text-sm hover:bg-slate-600 transition-colors"
>
Try Again
</button>
</div>
)}
{result && (
<div className="space-y-6">
{/* Status Badge */}
<div className="flex items-center gap-2">
{result.status === 'OK' ? (
<span className="flex items-center gap-1.5 bg-green-900/40 border border-green-700 text-green-400 px-3 py-1 rounded-full text-sm font-medium">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
Parsing Successful
</span>
) : (
<span className="flex items-center gap-1.5 bg-red-900/40 border border-red-700 text-red-400 px-3 py-1 rounded-full text-sm font-medium">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
{result.status}
</span>
)}
<span className="text-slate-500 text-sm">{result.format?.toUpperCase()}</span>
</div>
{/* Stats Grid */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
<div className="bg-slate-900 border border-slate-700 rounded-lg p-3 text-center">
<p className="text-2xl font-bold text-blue-400">{formatNumber(result.total_elements)}</p>
<p className="text-xs text-slate-500 mt-1">Elements</p>
</div>
<div className="bg-slate-900 border border-slate-700 rounded-lg p-3 text-center">
<p className="text-2xl font-bold text-green-400">{formatNumber(result.total_chars)}</p>
<p className="text-xs text-slate-500 mt-1">Characters</p>
</div>
<div className="bg-slate-900 border border-slate-700 rounded-lg p-3 text-center">
<p className="text-2xl font-bold text-purple-400">{formatNumber(result.total_words)}</p>
<p className="text-xs text-slate-500 mt-1">Words</p>
</div>
<div className="bg-slate-900 border border-slate-700 rounded-lg p-3 text-center">
<p className="text-2xl font-bold text-orange-400">{result.page_count || '-'}</p>
<p className="text-xs text-slate-500 mt-1">Pages</p>
</div>
</div>
{/* Element Types */}
{result.element_types && Object.keys(result.element_types).length > 0 && (
<div>
<h4 className="text-sm font-medium text-slate-300 mb-3">Element Types</h4>
<div className="bg-slate-900 border border-slate-700 rounded-lg divide-y divide-slate-700">
{Object.entries(result.element_types)
.sort((a, b) => b[1] - a[1])
.map(([type, count]) => (
<div key={type} className="flex items-center justify-between px-4 py-2.5">
<span className="text-slate-300 capitalize">{type.replace('_', ' ')}</span>
<span className="text-slate-400 font-mono">{count}</span>
</div>
))}
</div>
</div>
)}
{/* Sample Elements */}
{result.sample_elements && result.sample_elements.length > 0 && (
<div>
<h4 className="text-sm font-medium text-slate-300 mb-3">Sample Elements (first 10)</h4>
<div className="space-y-2">
{result.sample_elements.map((el, idx) => (
<div key={idx} className="bg-slate-900 border border-slate-700 rounded-lg p-3">
<div className="flex items-center gap-2 mb-2">
<span className={`text-xs font-medium px-2 py-0.5 rounded ${
el.type === 'heading' ? 'bg-blue-900/50 text-blue-300' :
el.type === 'table' ? 'bg-purple-900/50 text-purple-300' :
el.type === 'list_item' ? 'bg-orange-900/50 text-orange-300' :
'bg-slate-700 text-slate-300'
}`}>
{el.type}
</span>
{el.level && (
<span className="text-xs text-slate-500">Level {el.level}</span>
)}
</div>
<p className="text-sm text-slate-400 break-words">{el.text || '(empty)'}</p>
</div>
))}
</div>
</div>
)}
</div>
)}
</div>
{/* Footer */}
{result && (
<div className="p-4 border-t border-slate-700 flex justify-end gap-2">
<button
type="button"
onClick={runEval}
className="px-4 py-2 text-sm font-medium text-slate-300 hover:bg-slate-700 rounded-lg transition-colors"
>
Re-run
</button>
<button
type="button"
onClick={onClose}
className="px-4 py-2 text-sm font-medium bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
Done
</button>
</div>
)}
</div>
</div>
);
}