diff --git a/frontend/src/pages/Perception/PerceptionPage.tsx b/frontend/src/pages/Perception/PerceptionPage.tsx index 7336eb3..d3239cd 100644 --- a/frontend/src/pages/Perception/PerceptionPage.tsx +++ b/frontend/src/pages/Perception/PerceptionPage.tsx @@ -1,3 +1,265 @@ -export function PerceptionPage() { - return

Signals

; +import { useState, useEffect, useRef } from 'react'; +import { Topbar } from '../../components/layout/Topbar'; +import { RefreshCw, Play, Square, ExternalLink } from 'lucide-react'; + +interface Signal { + id: string; + source: string; + standard: string; + status: 'ok' | 'warn' | 'risk' | 'info'; + title: string; + summary: string; + date: string; + tags: string[]; + impact: 'High' | 'Medium' | 'Low'; +} + +interface Stats { + total: number; + high_impact: number; + medium_impact: number; + last_90_days: number; +} + +interface DocResult { + score: number; + name: string; + clause: string; + snippet: string; +} + +const SOURCES = ['All', 'MIIT', 'UN-ECE', 'ISO', 'GB Comm.', 'EUR-Lex', 'IATF']; +const IMPACTS = ['All', 'High', 'Medium', 'Low']; + +const MOCK_SIGNALS: Signal[] = [ + { + id: '1', source: 'EUR-Lex', standard: 'EU/2024/1689', status: 'risk', + title: 'EU AI Act — High-risk AI in vehicles', + summary: 'Article 9 mandates risk management systems for automotive AI classifying as high-risk under Annex III point 3.', + date: '2025-11-18', tags: ['automotive', 'GDPR', 'certification'], impact: 'High' + }, + { + id: '2', source: 'MIIT', standard: 'Draft-2025-08', status: 'warn', + title: 'MIIT Draft — in-vehicle AI training data', + summary: 'Draft regulation requires OEM data provenance documentation and OTA audit trails for AI systems.', + date: '2025-10-30', tags: ['OTA', 'data-governance', 'China'], impact: 'High' + }, + { + id: '3', source: 'ISO', standard: 'ISO/SAE 21434:2021/Amd1', status: 'info', + title: 'ISO/SAE 21434 Amendment 1', + summary: 'Amendment clarifies CSMS scope for software-only updates and vulnerability disclosure timelines.', + date: '2025-10-05', tags: ['cybersecurity', 'CSMS', 'ISO'], impact: 'Medium' + }, + { + id: '4', source: 'UN-ECE', standard: 'UNECE WP.29 R155', status: 'ok', + title: 'UNECE R155 Corrigendum', + summary: 'Editorial corrections to cybersecurity management system requirements. No substantive changes.', + date: '2025-09-12', tags: ['type-approval', 'UNECE'], impact: 'Low' + }, +]; + +const MOCK_DOCS: DocResult[] = [ + { score: 94, name: 'Vehicle AI Safety Manual v3.2', clause: '§4.2.1', snippet: 'The risk management process shall identify and evaluate risks arising from AI system decisions in safety-critical scenarios...' }, + { score: 87, name: 'ADAS System Requirements', clause: '§7.1', snippet: 'Automated driving functions must document training data lineage and model performance envelopes prior to deployment.' }, + { score: 71, name: 'Type Approval Documentation', clause: 'Annex B', snippet: 'Cybersecurity management system certification requires third-party audit of AI decision audit logs retention policy.' }, +]; + +export function PerceptionPage() { + const [stats, setStats] = useState(null); + const [sourceFilter, setSourceFilter] = useState('All'); + const [impactFilter, setImpactFilter] = useState('All'); + const [selected, setSelected] = useState(null); + const [streaming, setStreaming] = useState(false); + const [aiOutput, setAiOutput] = useState(''); + const abortRef = useRef(null); + + useEffect(() => { + fetch('/api/v1/perception/stats') + .then(r => r.json()) + .then(setStats) + .catch(() => setStats({ total: 47, high_impact: 7, medium_impact: 18, last_90_days: 14 })); + }, []); + + const filtered = MOCK_SIGNALS.filter(s => + (sourceFilter === 'All' || s.source === sourceFilter) && + (impactFilter === 'All' || s.impact === impactFilter) + ); + + function runAnalysis() { + if (!selected) return; + setStreaming(true); + setAiOutput(''); + const ctrl = new AbortController(); + abortRef.current = ctrl; + fetch(`/api/v1/perception/analyze?signal_id=${selected.id}`, { signal: ctrl.signal }) + .then(async res => { + if (!res.body) { setAiOutput('No stream available.'); setStreaming(false); return; } + const reader = res.body.getReader(); + const dec = new TextDecoder(); + while (true) { + const { done, value } = await reader.read(); + if (done) break; + const chunk = dec.decode(value); + for (const line of chunk.split('\n')) { + if (line.startsWith('data: ')) { + const data = line.slice(6); + if (data === '[DONE]') break; + try { const j = JSON.parse(data); setAiOutput(p => p + (j.text || '')); } + catch { setAiOutput(p => p + data); } + } + } + } + setStreaming(false); + }) + .catch(e => { + if (e.name !== 'AbortError') setAiOutput('Analysis failed. Check API connection.'); + setStreaming(false); + }); + } + + function stopAnalysis() { + abortRef.current?.abort(); + setStreaming(false); + } + + function selectSignal(sig: Signal) { + setSelected(sig); + setAiOutput(''); + setStreaming(false); + } + + return ( +
+ +
+ +
+ + + } + /> + +
+
+ {stats?.total ?? '—'} + Total signals +
+
+ {stats?.high_impact ?? '—'} + High impact +
+
+ {stats?.medium_impact ?? '—'} + Medium impact +
+
+ {stats?.last_90_days ?? '—'} + Last 90 days +
+
+ +
+
+ {SOURCES.map(s => ( + + ))} +
+
+
+ {IMPACTS.map(i => ( + + ))} +
+
+ +
+
+ {filtered.map(sig => ( +
selectSignal(sig)} + > +
+ {sig.source} + {sig.standard} + + {sig.status === 'ok' ? 'Final' : sig.status === 'warn' ? 'Draft' : sig.status === 'risk' ? 'Urgent' : 'Published'} + +
+
{sig.title}
+
{sig.summary}
+
+ {sig.date} +
{sig.tags.map(t => {t})}
+ {sig.impact} +
+
+ ))} +
+ +
+ {!selected ? ( +
+
+

Select a signal to run impact analysis

+
+ ) : ( + <> +
+
+ {selected.source} + {selected.standard} + + {selected.status === 'risk' ? 'Urgent' : 'Published'} + +
+
{selected.title}
+

{selected.summary}

+
+ {!streaming + ? + : + } + +
+
+ +
+
Affected documents
+ {MOCK_DOCS.map(d => ( +
+ {d.score}% +
+
{d.name} {d.clause}
+
{d.snippet}
+
+
+ ))} +
+ + {(aiOutput || streaming) && ( +
+
AI Impact Analysis
+
+ {aiOutput} + {streaming && } +
+
+ )} + + )} +
+
+ + +
+ ); }