Initial commit: breakpilot-compliance - Compliance SDK Platform
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>
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user