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:
Benjamin Boenisch
2026-02-11 23:47:28 +01:00
commit 4435e7ea0a
734 changed files with 251369 additions and 0 deletions

View File

@@ -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>
);
}