Services: Admin-Compliance, Backend-Compliance, AI-Compliance-SDK, Consent-SDK, Developer-Portal, PCA-Platform, DSMS Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
246 lines
9.0 KiB
TypeScript
246 lines
9.0 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { useRAG } from '@breakpilot/compliance-sdk-react';
|
|
import Link from 'next/link';
|
|
import {
|
|
ArrowLeft,
|
|
Search,
|
|
MessageSquare,
|
|
FileText,
|
|
Upload,
|
|
Send,
|
|
Loader2,
|
|
BookOpen,
|
|
Scale,
|
|
} from 'lucide-react';
|
|
|
|
export default function RAGPage() {
|
|
const { search, ask, isSearching, isAsking, searchResults, chatHistory } =
|
|
useRAG();
|
|
const [query, setQuery] = useState('');
|
|
const [mode, setMode] = useState<'search' | 'chat'>('chat');
|
|
const [results, setResults] = useState<any[]>([]);
|
|
const [answer, setAnswer] = useState<string>('');
|
|
|
|
const regulations = [
|
|
{ id: 'dsgvo', name: 'DSGVO', chunks: 99 },
|
|
{ id: 'ai-act', name: 'AI Act', chunks: 85 },
|
|
{ id: 'nis2', name: 'NIS2', chunks: 46 },
|
|
{ id: 'eprivacy', name: 'ePrivacy', chunks: 32 },
|
|
{ id: 'tdddg', name: 'TDDDG', chunks: 28 },
|
|
{ id: 'cra', name: 'CRA', chunks: 41 },
|
|
];
|
|
|
|
const handleSearch = async () => {
|
|
if (!query.trim()) return;
|
|
|
|
if (mode === 'search') {
|
|
const res = await search(query);
|
|
setResults(res || []);
|
|
} else {
|
|
const res = await ask(query);
|
|
setAnswer(res?.answer || '');
|
|
setResults(res?.sources || []);
|
|
}
|
|
};
|
|
|
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
e.preventDefault();
|
|
handleSearch();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-background flex flex-col">
|
|
{/* Header */}
|
|
<header className="border-b bg-card">
|
|
<div className="container mx-auto px-6 py-4">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-4">
|
|
<Link
|
|
href="/"
|
|
className="p-2 hover:bg-muted rounded-lg transition-colors"
|
|
>
|
|
<ArrowLeft className="h-5 w-5" />
|
|
</Link>
|
|
<div className="flex items-center gap-3">
|
|
<div className="p-2 rounded-lg bg-purple-500/10">
|
|
<Scale className="h-6 w-6 text-purple-500" />
|
|
</div>
|
|
<div>
|
|
<h1 className="text-xl font-semibold">Legal Assistant</h1>
|
|
<p className="text-sm text-muted-foreground">
|
|
AI-powered regulatory knowledge base
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2 bg-muted rounded-lg p-1">
|
|
<button
|
|
onClick={() => setMode('chat')}
|
|
className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${
|
|
mode === 'chat'
|
|
? 'bg-background shadow text-foreground'
|
|
: 'text-muted-foreground hover:text-foreground'
|
|
}`}
|
|
>
|
|
<MessageSquare className="h-4 w-4 inline mr-2" />
|
|
Chat
|
|
</button>
|
|
<button
|
|
onClick={() => setMode('search')}
|
|
className={`px-4 py-2 rounded-md text-sm font-medium transition-colors ${
|
|
mode === 'search'
|
|
? 'bg-background shadow text-foreground'
|
|
: 'text-muted-foreground hover:text-foreground'
|
|
}`}
|
|
>
|
|
<Search className="h-4 w-4 inline mr-2" />
|
|
Search
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
{/* Main Content */}
|
|
<div className="flex-1 flex">
|
|
{/* Sidebar - Regulations */}
|
|
<aside className="w-64 border-r bg-card p-4">
|
|
<h3 className="text-sm font-medium mb-4 flex items-center gap-2">
|
|
<BookOpen className="h-4 w-4" />
|
|
Indexed Regulations
|
|
</h3>
|
|
<div className="space-y-2">
|
|
{regulations.map((reg) => (
|
|
<div
|
|
key={reg.id}
|
|
className="flex items-center justify-between p-3 rounded-lg hover:bg-muted cursor-pointer"
|
|
>
|
|
<span className="text-sm font-medium">{reg.name}</span>
|
|
<span className="text-xs text-muted-foreground">
|
|
{reg.chunks} chunks
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div className="mt-6 pt-6 border-t">
|
|
<button className="w-full flex items-center justify-center gap-2 px-4 py-2 border rounded-lg text-sm hover:bg-muted transition-colors">
|
|
<Upload className="h-4 w-4" />
|
|
Upload Document
|
|
</button>
|
|
</div>
|
|
</aside>
|
|
|
|
{/* Chat/Search Area */}
|
|
<main className="flex-1 flex flex-col">
|
|
{/* Results Area */}
|
|
<div className="flex-1 overflow-auto p-6">
|
|
{mode === 'chat' && answer && (
|
|
<div className="max-w-3xl mx-auto">
|
|
<div className="bg-card border rounded-xl p-6 mb-4">
|
|
<div className="prose prose-sm max-w-none dark:prose-invert">
|
|
{answer}
|
|
</div>
|
|
</div>
|
|
{results.length > 0 && (
|
|
<div>
|
|
<h4 className="text-sm font-medium mb-3">Sources</h4>
|
|
<div className="space-y-2">
|
|
{results.map((source, i) => (
|
|
<div
|
|
key={i}
|
|
className="bg-muted/50 rounded-lg p-3 text-sm"
|
|
>
|
|
<div className="font-medium">{source.regulation}</div>
|
|
<div className="text-muted-foreground text-xs mt-1">
|
|
{source.text?.substring(0, 200)}...
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{mode === 'search' && results.length > 0 && (
|
|
<div className="max-w-3xl mx-auto space-y-4">
|
|
{results.map((result, i) => (
|
|
<div
|
|
key={i}
|
|
className="bg-card border rounded-xl p-4 hover:border-primary/50 transition-colors"
|
|
>
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<FileText className="h-4 w-4 text-muted-foreground" />
|
|
<span className="font-medium">{result.regulation}</span>
|
|
<span className="text-xs bg-muted px-2 py-0.5 rounded">
|
|
Score: {(result.score * 100).toFixed(0)}%
|
|
</span>
|
|
</div>
|
|
<p className="text-sm text-muted-foreground">
|
|
{result.text}
|
|
</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{!answer && results.length === 0 && (
|
|
<div className="h-full flex items-center justify-center text-center">
|
|
<div>
|
|
<Scale className="h-16 w-16 text-muted-foreground/30 mx-auto mb-4" />
|
|
<h3 className="text-lg font-medium mb-2">
|
|
Ask a Legal Question
|
|
</h3>
|
|
<p className="text-sm text-muted-foreground max-w-md">
|
|
Search through 21 indexed regulations including DSGVO, AI
|
|
Act, NIS2, and more. Get AI-powered answers with source
|
|
references.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Input Area */}
|
|
<div className="border-t bg-card p-4">
|
|
<div className="max-w-3xl mx-auto">
|
|
<div className="relative">
|
|
<textarea
|
|
value={query}
|
|
onChange={(e) => setQuery(e.target.value)}
|
|
onKeyDown={handleKeyDown}
|
|
placeholder={
|
|
mode === 'chat'
|
|
? 'Ask a question about compliance regulations...'
|
|
: 'Search for specific terms or articles...'
|
|
}
|
|
rows={2}
|
|
className="w-full px-4 py-3 pr-12 border rounded-xl bg-background resize-none focus:outline-none focus:ring-2 focus:ring-primary"
|
|
/>
|
|
<button
|
|
onClick={handleSearch}
|
|
disabled={isSearching || isAsking || !query.trim()}
|
|
className="absolute right-3 bottom-3 p-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
{isSearching || isAsking ? (
|
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
) : (
|
|
<Send className="h-4 w-4" />
|
|
)}
|
|
</button>
|
|
</div>
|
|
<p className="text-xs text-muted-foreground mt-2 text-center">
|
|
Powered by local LLM (Qwen 2.5) with RAG over 21 legal documents
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|