ai-code-detector / frontend /src /screens /DetailScreen.jsx
Karim Krklec
Obavljene optimizacije
a641ed5
Raw
History Blame Contribute Delete
4.91 kB
import { Button, Card, Badge, Label, Icon, LangBadge } from '../components/primitives'
import CodeEditor from '../components/CodeEditor'
import ResultGauge from '../components/ResultGauge'
import DetailSidebar from '../components/DetailSidebar'
import ExplanationPanel from '../components/ExplanationPanel'
function buildFeatures(allFeatures) {
if (!allFeatures) return []
const styleKeys = ['avg_identifier_length', 'avg_function_name_length', 'single_char_name_ratio', 'naming_consistency', 'comment_ratio', 'num_docstrings', 'lexical_diversity', 'trailing_whitespace_ratio']
const structKeys = ['num_functions', 'avg_function_length', 'max_nesting_depth', 'cyclomatic_complexity_approx', 'if_density', 'for_density', 'ast_depth']
const statKeys = ['char_entropy', 'token_entropy', 'perplexity']
const toFeature = (key, category) => {
const val = allFeatures[key]
if (val === undefined || val === null) return null
const fmtVal = typeof val === 'number'
? (key.includes('ratio') || key.includes('density') ? (val * 100).toFixed(1) + '%' : val.toFixed ? val.toFixed(2) : String(val))
: String(val)
const severity = key.includes('length') && val > 6 ? 'high'
: key === 'naming_consistency' && val > 0.7 ? 'high'
: key === 'comment_ratio' && val > 0.2 ? 'high'
: key.includes('entropy') ? 'medium'
: 'low'
return { name: key.replace(/_/g, ' '), value: fmtVal, severity, category }
}
return [
...styleKeys.map(k => toFeature(k, 'Style')),
...structKeys.map(k => toFeature(k, 'Structure')),
...statKeys.map(k => toFeature(k, 'Statistical')),
].filter(Boolean)
}
// Anotacije sada dolaze direktno s backenda (analyze_lines funkcija)
// Backend analizira svaku liniju i detektira konkretne AI signale:
// "red" = jak signal (docstringovi, dugački identifikatori)
// "amber" = umjeren signal (komentari, type anotacije, exception handling)
function buildAnnotations(row) {
if (row?.line_annotations?.length) {
return row.line_annotations
}
// Fallback: ako nema backendskih anotacija (stariji model), vraćamo prazno
return []
}
export default function DetailScreen({ row, onBack }) {
if (!row) return null
const prob = Math.round((row.ai_probability || 0) * 100)
const tone = prob >= 70 ? 'red' : prob >= 40 ? 'amber' : 'green'
const label = prob >= 70 ? 'LIKELY AI' : prob >= 40 ? 'POSSIBLY AI' : 'LIKELY HUMAN'
const lang = row.detected_language || 'python'
const langDisplay = lang.charAt(0).toUpperCase() + lang.slice(1)
const features = buildFeatures(row.all_features)
const annotations = buildAnnotations(row)
const code = row.code || '// Code not available'
return (
<div style={{ padding: '32px 80px 80px', maxWidth: 1280, margin: '0 auto' }}>
<button onClick={onBack} style={{
background: 'transparent', border: 'none', color: '#94A3B8', cursor: 'pointer',
display: 'inline-flex', alignItems: 'center', gap: 6, padding: 0, marginBottom: 20, fontSize: 13,
}}>
<Icon name="arrowLeft" size={14}/> Back to batch
</button>
{/* Header */}
<Card style={{ marginBottom: 24, padding: 24 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 32 }}>
<div>
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 10 }}>
<span style={{ fontFamily: 'JetBrains Mono, monospace', fontSize: 24, fontWeight: 700, color: '#F8FAFC' }}>{row.id}</span>
<LangBadge lang={langDisplay}/>
<Badge tone={tone}>{label}</Badge>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, color: '#94A3B8', fontSize: 13 }}>
<Icon name="fileCode" size={14}/>
<span style={{ fontFamily: 'JetBrains Mono, monospace' }}>{row.file || row.id}</span>
</div>
</div>
<ResultGauge value={prob} size={140} animate={false}/>
</div>
</Card>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 360px', gap: 24, alignItems: 'start' }}>
<div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
<Label style={{ marginBottom: 0 }}>Annotated source</Label>
{annotations.length > 0 && (
<span style={{ fontSize: 11, color: '#64748B' }}>{annotations.length} suspicious lines flagged</span>
)}
</div>
<CodeEditor code={code} readOnly lang={langDisplay} height={420} annotations={annotations} filename={row.file}/>
<ExplanationPanel
explanations={row.explanations}
verdict={row.verdict}
aiProbability={row.ai_probability}
/>
</div>
<DetailSidebar features={features}/>
</div>
</div>
)
}