From 6414d67b3b52b5f616519f71733ad6f9a60cd9e8 Mon Sep 17 00:00:00 2001 From: wangwei Date: Wed, 3 Jun 2026 17:58:38 +0800 Subject: [PATCH] feat: implement Regulation Q&A chat page with history pane, streaming, citation rail --- frontend/src/pages/RagChat/RagChatPage.tsx | 211 ++++++++++++++++++++- 1 file changed, 209 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/RagChat/RagChatPage.tsx b/frontend/src/pages/RagChat/RagChatPage.tsx index 7a649d4..80e6cd5 100644 --- a/frontend/src/pages/RagChat/RagChatPage.tsx +++ b/frontend/src/pages/RagChat/RagChatPage.tsx @@ -1,3 +1,210 @@ -export function RagChatPage() { - return

Chat

; +import { useState, useRef, useEffect } from 'react'; +import { Topbar } from '../../components/layout/Topbar'; +import { Send, Download } from 'lucide-react'; + +interface Message { + id: string; + role: 'user' | 'assistant'; + text: string; +} + +interface Citation { + score: number; + name: string; + clause: string; + snippet: string; +} + +const HISTORY = [ + { id: 'h1', title: 'EU AI Act Article 9 scope', date: '2025-11-18' }, + { id: 'h2', title: 'MIIT training data requirements', date: '2025-11-15' }, + { id: 'h3', title: 'ISO 21434 CSMS audit scope', date: '2025-11-10' }, +]; + +const QUICK = [ + 'What does EU AI Act Art. 9 require for risk management?', + 'Which documents need CSMS certification?', + 'Summarize MIIT training data rules', + 'What are high-risk AI categories under Annex III?', +]; + +const MOCK_CITATIONS: Citation[] = [ + { + score: 94, name: 'EU AI Act', clause: 'Art. 9(1)', + snippet: 'Providers of high-risk AI systems shall establish a risk management system consisting of a continuous iterative process run throughout the entire lifecycle.' + }, + { + score: 87, name: 'Vehicle AI Safety Manual', clause: '§4.2.1', + snippet: 'All AI systems classified as high-risk must maintain a documented risk register with quarterly review cadence.' + }, + { + score: 72, name: 'ISO/SAE 21434', clause: 'Clause 9.3', + snippet: 'The cybersecurity management system shall include AI model update governance procedures and audit log retention policy.' + }, +]; + +export function RagChatPage() { + const [messages, setMessages] = useState([ + { + id: 'init', role: 'assistant', + text: 'Hello! I can answer questions about your indexed regulations and compliance documents. Try asking about EU AI Act requirements, MIIT rules, or ISO/SAE 21434 scope.' + } + ]); + const [input, setInput] = useState(''); + const [streaming, setStreaming] = useState(false); + const [citations, setCitations] = useState(MOCK_CITATIONS); + const bottomRef = useRef(null); + const abortRef = useRef(null); + + useEffect(() => { + bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [messages]); + + async function send(text?: string) { + const q = (text ?? input).trim(); + if (!q || streaming) return; + setInput(''); + const userMsg: Message = { id: Date.now().toString(), role: 'user', text: q }; + setMessages(m => [...m, userMsg]); + + const assistantId = (Date.now() + 1).toString(); + setMessages(m => [...m, { id: assistantId, role: 'assistant', text: '' }]); + setStreaming(true); + + const ctrl = new AbortController(); + abortRef.current = ctrl; + + try { + const res = await fetch('/api/v1/rag/chat', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ question: q }), + signal: ctrl.signal, + }); + + if (!res.body) throw new Error('No stream'); + const reader = res.body.getReader(); + const dec = new TextDecoder(); + let buffer = ''; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += dec.decode(value); + const lines = buffer.split('\n'); + buffer = lines.pop() ?? ''; + for (const line of lines) { + if (line.startsWith('data: ')) { + const data = line.slice(6); + if (data === '[DONE]') break; + try { + const j = JSON.parse(data); + if (j.text) setMessages(m => m.map(msg => + msg.id === assistantId ? { ...msg, text: msg.text + j.text } : msg + )); + if (j.citations) setCitations(j.citations); + } catch { + setMessages(m => m.map(msg => + msg.id === assistantId ? { ...msg, text: msg.text + data } : msg + )); + } + } + } + } + } catch (e: unknown) { + if (e instanceof Error && e.name !== 'AbortError') { + setMessages(m => m.map(msg => + msg.id === assistantId + ? { ...msg, text: 'Could not reach the RAG API. Please check the backend.' } + : msg + )); + } + } finally { + setStreaming(false); + } + } + + const lastAssistantId = [...messages].reverse().find(m => m.role === 'assistant')?.id; + + return ( +
+ Export chat} + /> +
+
+
Chat history
+ {HISTORY.map(h => ( +
+
{h.title}
+
{h.date}
+
+ ))} +
Quick prompts
+ {QUICK.map(q => ( + + ))} +
+ +
+
+ {messages.map(msg => ( +
+ {msg.role === 'assistant' &&
AI
} +
+ {msg.text} + {streaming && msg.id === lastAssistantId && ( + + )} +
+ {msg.role === 'user' &&
You
} +
+ ))} +
+
+ +
+
+ {QUICK.slice(0, 3).map(q => ( + + ))} +
+
+