'use client' import React, { useState, useEffect } from 'react' // ============================================================================= // TYPES // ============================================================================= interface Assertion { id: string tenant_id: string | null entity_type: string entity_id: string sentence_text: string sentence_index: number assertion_type: string // 'assertion' | 'fact' | 'rationale' evidence_ids: string[] confidence: number normative_tier: string | null // 'pflicht' | 'empfehlung' | 'kann' verified_by: string | null verified_at: string | null created_at: string | null updated_at: string | null } interface AssertionSummary { total_assertions: number total_facts: number total_rationale: number unverified_count: number } // ============================================================================= // CONSTANTS // ============================================================================= const TIER_COLORS: Record = { pflicht: 'bg-red-100 text-red-700', empfehlung: 'bg-yellow-100 text-yellow-700', kann: 'bg-blue-100 text-blue-700', } const TIER_LABELS: Record = { pflicht: 'Pflicht', empfehlung: 'Empfehlung', kann: 'Kann', } const TYPE_COLORS: Record = { assertion: 'bg-orange-100 text-orange-700', fact: 'bg-green-100 text-green-700', rationale: 'bg-purple-100 text-purple-700', } const TYPE_LABELS: Record = { assertion: 'Behauptung', fact: 'Fakt', rationale: 'Begruendung', } const API_BASE = '/api/sdk/v1/compliance' type TabKey = 'overview' | 'list' | 'extract' // ============================================================================= // ASSERTION CARD // ============================================================================= function AssertionCard({ assertion, onVerify, }: { assertion: Assertion onVerify: (id: string) => void }) { const tierColor = assertion.normative_tier ? TIER_COLORS[assertion.normative_tier] || 'bg-gray-100 text-gray-600' : 'bg-gray-100 text-gray-600' const tierLabel = assertion.normative_tier ? TIER_LABELS[assertion.normative_tier] || assertion.normative_tier : '—' const typeColor = TYPE_COLORS[assertion.assertion_type] || 'bg-gray-100 text-gray-600' const typeLabel = TYPE_LABELS[assertion.assertion_type] || assertion.assertion_type return (
{tierLabel} {typeLabel} {assertion.entity_type && ( {assertion.entity_type}: {assertion.entity_id?.slice(0, 8) || '—'} )} {assertion.confidence > 0 && ( Konfidenz: {(assertion.confidence * 100).toFixed(0)}% )}

“{assertion.sentence_text}”

{assertion.verified_by && ( Verifiziert von {assertion.verified_by} am {assertion.verified_at ? new Date(assertion.verified_at).toLocaleDateString('de-DE') : '—'} )} {assertion.evidence_ids.length > 0 && ( {assertion.evidence_ids.length} Evidence verknuepft )}
{assertion.assertion_type !== 'fact' && ( )}
) } // ============================================================================= // MAIN PAGE // ============================================================================= export default function AssertionsPage() { const [activeTab, setActiveTab] = useState('overview') const [summary, setSummary] = useState(null) const [assertions, setAssertions] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) // Filters const [filterEntityType, setFilterEntityType] = useState('') const [filterAssertionType, setFilterAssertionType] = useState('') // Extract tab const [extractText, setExtractText] = useState('') const [extractEntityType, setExtractEntityType] = useState('control') const [extractEntityId, setExtractEntityId] = useState('') const [extracting, setExtracting] = useState(false) const [extractedAssertions, setExtractedAssertions] = useState([]) // Verify dialog const [verifyingId, setVerifyingId] = useState(null) const [verifyEmail, setVerifyEmail] = useState('') useEffect(() => { loadSummary() }, []) useEffect(() => { if (activeTab === 'list') loadAssertions() }, [activeTab, filterEntityType, filterAssertionType]) // eslint-disable-line react-hooks/exhaustive-deps const loadSummary = async () => { try { const res = await fetch(`${API_BASE}/assertions/summary`) if (res.ok) setSummary(await res.json()) } catch { /* silent */ } finally { setLoading(false) } } const loadAssertions = async () => { setLoading(true) try { const params = new URLSearchParams() if (filterEntityType) params.set('entity_type', filterEntityType) if (filterAssertionType) params.set('assertion_type', filterAssertionType) params.set('limit', '200') const res = await fetch(`${API_BASE}/assertions?${params}`) if (res.ok) { const data = await res.json() setAssertions(data.assertions || []) } } catch { setError('Assertions konnten nicht geladen werden') } finally { setLoading(false) } } const handleExtract = async () => { if (!extractText.trim()) { setError('Bitte Text eingeben'); return } setExtracting(true) setError(null) setExtractedAssertions([]) try { const res = await fetch(`${API_BASE}/assertions/extract`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: extractText, entity_type: extractEntityType || 'control', entity_id: extractEntityId || undefined, }), }) if (!res.ok) { const err = await res.json().catch(() => ({ detail: 'Extraktion fehlgeschlagen' })) throw new Error(typeof err.detail === 'string' ? err.detail : JSON.stringify(err.detail)) } const data = await res.json() setExtractedAssertions(data.assertions || []) // Refresh summary loadSummary() } catch (err) { setError(err instanceof Error ? err.message : 'Extraktion fehlgeschlagen') } finally { setExtracting(false) } } const handleVerify = async (assertionId: string) => { setVerifyingId(assertionId) } const submitVerify = async () => { if (!verifyingId || !verifyEmail.trim()) return try { const res = await fetch(`${API_BASE}/assertions/${verifyingId}/verify?verified_by=${encodeURIComponent(verifyEmail)}`, { method: 'POST', }) if (res.ok) { setVerifyingId(null) setVerifyEmail('') loadAssertions() loadSummary() } else { const err = await res.json().catch(() => ({ detail: 'Verifizierung fehlgeschlagen' })) setError(typeof err.detail === 'string' ? err.detail : 'Verifizierung fehlgeschlagen') } } catch { setError('Netzwerkfehler') } } const tabs: { key: TabKey; label: string }[] = [ { key: 'overview', label: 'Uebersicht' }, { key: 'list', label: 'Assertion-Liste' }, { key: 'extract', label: 'Extraktion' }, ] return (
{/* Header */}

Assertions

Behauptungen vs. Fakten in Compliance-Texten trennen und verifizieren.

{/* Tabs */}
{tabs.map(tab => ( ))}
{/* Error */} {error && (
{error}
)} {/* ============================================================ */} {/* TAB: Uebersicht */} {/* ============================================================ */} {activeTab === 'overview' && ( <> {loading ? (
) : summary ? (
Gesamt Assertions
{summary.total_assertions}
Verifizierte Fakten
{summary.total_facts}
Begruendungen
{summary.total_rationale}
Unverifizizt
{summary.unverified_count}
) : (

Keine Assertions vorhanden. Nutzen Sie die Extraktion, um Behauptungen aus Texten zu identifizieren.

)} )} {/* ============================================================ */} {/* TAB: Assertion-Liste */} {/* ============================================================ */} {activeTab === 'list' && ( <> {/* Filters */}
{loading ? (
) : assertions.length === 0 ? (

Keine Assertions gefunden.

) : (

{assertions.length} Assertions

{assertions.map(a => ( ))}
)} )} {/* ============================================================ */} {/* TAB: Extraktion */} {/* ============================================================ */} {activeTab === 'extract' && (

Assertions aus Text extrahieren

Geben Sie einen Compliance-Text ein. Das System identifiziert automatisch Behauptungen, Fakten und Begruendungen.

setExtractEntityId(e.target.value)} placeholder="z.B. GOV-001 oder UUID" className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent" />