import { useState } from 'react' import { Button, Card, Label, Icon, Input } from '../components/primitives' import Dropzone from '../components/Dropzone' import ResultsTable from '../components/ResultsTable' import { StatCard } from '../components/FeatureCard' import { analyzeBatch } from '../api/client' export default function BatchScreen({ onOpenRow }) { const [files, setFiles] = useState([]) const [results, setResults] = useState([]) const [summary, setSummary] = useState(null) const [loading, setLoading] = useState(false) const [progress, setProgress] = useState(0) // 0-100 const [done, setDone] = useState(0) // koliko fajlova završilo const [error, setError] = useState(null) const [search, setSearch] = useState('') const handleFiles = (fileList) => { const arr = Array.from(fileList) setFiles(arr); setResults([]); setSummary(null); setError(null); setProgress(0); setDone(0) } const runBatch = async () => { if (!files.length) return setLoading(true); setError(null); setResults([]); setSummary(null); setProgress(0); setDone(0) // Učitamo sve fajlove unaprijed da znamo kod za svaki const submissions = await Promise.all(files.map(async (f) => ({ id: f.name.replace(/\.[^.]+$/, ''), file: f.name, code: await f.text(), }))) try { // Streaming — onResult se zove za svaki fajl čim stigne s backenda const finalSummary = await analyzeBatch( submissions, // Callback za svaki gotov rezultat — odmah ga dodajemo u tablicu (result, index, total) => { const sub = submissions[index - 1] const enriched = { ...result, file: sub?.file || result.id, code: sub?.code || '', } setResults(prev => [...prev, enriched]) setDone(index) }, // Callback za napredak (0-100%) (pct) => setProgress(pct), ) setSummary(finalSummary) } catch (e) { setError(e.message) } finally { setLoading(false) } } const exportCSV = () => { if (!results.length) return const headers = ['ID', 'Datoteka', 'Jezik', 'AI vjerojatnost (%)', 'Zakljucak'] const keys = ['id', 'file', 'detected_language', 'ai_probability', 'verdict'] const escapeCell = (val) => { const s = String(val ?? '') if (s.includes(';') || s.includes('"') || s.includes('\n')) { return '"' + s.replace(/"/g, '""') + '"' } return s } const rows = results.map(r => keys.map(h => { if (h === 'ai_probability') return escapeCell(Math.round((r[h] || 0) * 100) + '%') return escapeCell(r[h] || '') })) const sep = ';' const csvContent = [ headers.map(escapeCell).join(sep), ...rows.map(r => r.join(sep)), ].join('\r\n') const BOM = '\uFEFF' const blob = new Blob([BOM + csvContent], { type: 'text/csv;charset=utf-8;' }) const a = Object.assign(document.createElement('a'), { href: URL.createObjectURL(blob), download: 'codesentinel_rezultati.csv', }) a.click() } const filtered = results.filter(r => !search || r.id?.toLowerCase().includes(search.toLowerCase()) || r.file?.toLowerCase().includes(search.toLowerCase()) ) const total = files.length return (

Class overview

Rezultati se prikazuju u realnom vremenu čim svaki fajl bude analiziran.

{/* Upload zona — prikazuje se samo na početku */} {!loading && !results.length && !summary && (
{files.length > 0 && (
)}
)} {/* Progress bar — prikazuje se za vrijeme analize */} {loading && (
Analyzing… {done} / {total} files
{progress}%
{/* Progress bar */}
{/* Tablica se popunjava ispod progress bara u realnom vremenu */} {results.length > 0 && (
{results.length} result{results.length !== 1 ? 's' : ''} so far — scroll down to see them
)} )} {error && (
{error}
)} {/* Rezultati — prikazuju se čim prvi stignu, i rastu za vrijeme analize */} {results.length > 0 && ( <> {/* Stat kartice — vidljive tek kad je sve gotovo */} {summary && (
)} {/* Action bar */}
{summary ? <>{summary.total} files analyzed — avg. AI probability: {Math.round((summary.avg_ai_probability || 0) * 100)}% : <>{results.length} of {total} analyzed… }
{summary && ( <> )}
{/* Search */}
)}
) }