'use client' import React, { useState, useCallback } from 'react' import { ChecklistView } from './ChecklistView' import { DocumentRow } from './DocumentRow' const DOCUMENT_TYPES = [ { id: 'dse', label: 'DSI (Datenschutzinformation)', required: true }, { id: 'impressum', label: 'Impressum', required: true }, { id: 'social_media', label: 'Social Media DSE', required: false }, { id: 'cookie', label: 'Cookie-Richtlinie', required: false }, { id: 'agb', label: 'AGB', required: false }, { id: 'nutzungsbedingungen', label: 'Nutzungsbedingungen', required: false }, { id: 'widerruf', label: 'Widerrufsbelehrung', required: false }, { id: 'dsb', label: 'DSB-Kontakt', required: false }, ] as const type DocTypeId = typeof DOCUMENT_TYPES[number]['id'] interface DocState { url: string text: string loading: boolean error: string | null } type DocsState = Record const STORAGE_KEY_STATE = 'compliance-check-state' const STORAGE_KEY_RESULTS = 'compliance-check-results' const STORAGE_KEY_HISTORY = 'compliance-check-history' function emptyDocState(): DocState { return { url: '', text: '', loading: false, error: null } } function initState(): DocsState { if (typeof window === 'undefined') { return Object.fromEntries(DOCUMENT_TYPES.map(d => [d.id, emptyDocState()])) as DocsState } try { const saved = localStorage.getItem(STORAGE_KEY_STATE) if (saved) { const parsed = JSON.parse(saved) as Record return Object.fromEntries( DOCUMENT_TYPES.map(d => [d.id, { url: parsed[d.id]?.url || '', text: parsed[d.id]?.text || '', loading: false, error: null, }]) ) as DocsState } } catch { /* ignore */ } return Object.fromEntries(DOCUMENT_TYPES.map(d => [d.id, emptyDocState()])) as DocsState } function countWords(text: string): number { if (!text.trim()) return 0 return text.trim().split(/\s+/).length } interface HistoryEntry { date: string docCount: number findings: number resultKey: string } export function ComplianceCheckTab() { const [docs, setDocs] = useState(initState) const [useAgent, setUseAgent] = useState(false) const [loading, setLoading] = useState(false) const [progress, setProgress] = useState('') const [results, setResults] = useState(() => { if (typeof window === 'undefined') return null try { const s = localStorage.getItem(STORAGE_KEY_RESULTS); return s ? JSON.parse(s) : null } catch { return null } }) const [error, setError] = useState(null) const [history, setHistory] = useState(() => { if (typeof window === 'undefined') return [] try { return JSON.parse(localStorage.getItem(STORAGE_KEY_HISTORY) || '[]') } catch { return [] } }) // Persist URLs and texts (not loading/error state) React.useEffect(() => { const toSave: Record = {} for (const [key, val] of Object.entries(docs)) { toSave[key] = { url: val.url, text: val.text } } try { localStorage.setItem(STORAGE_KEY_STATE, JSON.stringify(toSave)) } catch { /* quota */ } }, [docs]) const updateDoc = useCallback((docType: DocTypeId, patch: Partial) => { setDocs(prev => ({ ...prev, [docType]: { ...prev[docType], ...patch } })) }, []) const handleFetchText = useCallback(async (docType: DocTypeId) => { const url = docs[docType].url.trim() if (!url) return updateDoc(docType, { loading: true, error: null }) try { const res = await fetch('/api/sdk/v1/agent/extract-text', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url }), }) if (!res.ok) { const msg = res.status === 404 ? 'Seite nicht erreichbar' : `Fehler beim Laden (${res.status})` throw new Error(msg) } const data = await res.json() updateDoc(docType, { text: data.text || '', loading: false }) } catch (e) { updateDoc(docType, { loading: false, error: e instanceof Error ? e.message : 'Text konnte nicht geladen werden', }) } }, [docs, updateDoc]) const handleFileUpload = useCallback(async (docType: DocTypeId, file: File) => { // For now, read as text. PDF/DOCX parsing can be added server-side later. const reader = new FileReader() reader.onload = () => { updateDoc(docType, { text: reader.result as string }) } reader.readAsText(file) }, [updateDoc]) const filledCount = Object.values(docs).filter(d => d.url.trim() || d.text.trim()).length const handleSubmit = async () => { if (filledCount === 0) return setLoading(true) setError(null) setResults(null) setProgress('Compliance-Check wird gestartet...') try { const entries = DOCUMENT_TYPES .filter(dt => docs[dt.id].url.trim() || docs[dt.id].text.trim()) .map(dt => ({ doc_type: dt.id, label: dt.label, url: docs[dt.id].url.trim(), text: docs[dt.id].text.trim() || undefined, })) const startRes = await fetch('/api/sdk/v1/agent/compliance-check', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ entries, use_agent: useAgent, }), }) if (!startRes.ok) throw new Error(`Pruefung konnte nicht gestartet werden: ${startRes.status}`) const { check_id } = await startRes.json() if (!check_id) throw new Error('Keine Check-ID erhalten') // Poll for results let attempts = 0 while (attempts < 120) { await new Promise(r => setTimeout(r, 3000)) const pollRes = await fetch(`/api/sdk/v1/agent/compliance-check?check_id=${check_id}`) if (!pollRes.ok) { attempts++; continue } const pollData = await pollRes.json() if (pollData.progress) setProgress(pollData.progress) if (pollData.status === 'completed' && pollData.result) { setResults(pollData.result) setProgress('') localStorage.setItem(STORAGE_KEY_RESULTS, JSON.stringify(pollData.result)) const resultKey = `compliance-check-result-${Date.now()}` try { localStorage.setItem(resultKey, JSON.stringify(pollData.result)) } catch { /* quota */ } const entry: HistoryEntry = { date: new Date().toISOString(), docCount: entries.length, findings: pollData.result.total_findings || 0, resultKey, } const updated = [entry, ...history].slice(0, 30) setHistory(updated) localStorage.setItem(STORAGE_KEY_HISTORY, JSON.stringify(updated)) break } if (pollData.status === 'failed') { throw new Error(pollData.error || 'Pruefung fehlgeschlagen') } attempts++ } if (attempts >= 120) throw new Error('Zeitlimit ueberschritten') } catch (e) { setError(e instanceof Error ? e.message : 'Unbekannter Fehler') setProgress('') } finally { setLoading(false) } } const loadFromHistory = (entry: HistoryEntry) => { if (entry.resultKey) { try { const saved = localStorage.getItem(entry.resultKey) if (saved) { setResults(JSON.parse(saved)); return } } catch { /* ignore */ } } try { const last = localStorage.getItem(STORAGE_KEY_RESULTS) if (last) setResults(JSON.parse(last)) } catch { /* ignore */ } } return (
{/* Info box */}

Compliance-Check (Alle Dokumente)

Geben Sie die URLs Ihrer Rechtstexte ein oder laden Sie die Dokumente hoch. Das System prueft alle Pflichtangaben nach DSGVO, TDDDG, TMG und UWG. Pflichtdokumente sind mit * markiert.

{/* Document rows */}
{DOCUMENT_TYPES.map(dt => ( updateDoc(dt.id, { url })} onFetchText={() => handleFetchText(dt.id)} onTextChange={text => updateDoc(dt.id, { text })} onFileUpload={file => handleFileUpload(dt.id, file)} /> ))}
{/* Agent toggle + submit */}
{filledCount} von {DOCUMENT_TYPES.length} Dokumenten ausgefuellt
{/* Submit button */} {/* Progress */} {progress && (
{progress}
)} {/* Error */} {error && (
{error}
)} {/* Results */} {results && results.results && (
{/* Email status */} {results.email_status && (
E-Mail: {results.email_status === 'sent' ? 'Gesendet' : results.email_status}
)}
)} {/* History */} {history.length > 0 && (

Letzte Compliance-Checks

{history.map((h, i) => ( ))}
)}
) }