feat: ZeroClaw compliance agent — document analysis + role assignment + email
Add autonomous compliance agent that fetches web documents (cookie banners, privacy policies), classifies them via Qwen/Ollama, assesses DSGVO compliance, assigns to the responsible role, and sends notification emails. Components: - ZeroClaw SOP (6-step workflow: fetch, classify, assess, summarize, assign, notify) - Backend: /api/compliance/agent/analyze (combined endpoint) - Backend: /api/compliance/agent/notify (standalone email) - Frontend: /sdk/agent page (Manager UI with URL input + results) - Helper scripts + E2E test Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
109
admin-compliance/app/sdk/agent/_components/AnalysisResult.tsx
Normal file
109
admin-compliance/app/sdk/agent/_components/AnalysisResult.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import type { AnalysisResult as AnalysisResultType } from '../_hooks/useAgentAnalysis'
|
||||
|
||||
const RISK_COLORS: Record<string, { bg: string; text: string; label: string }> = {
|
||||
low: { bg: 'bg-green-100', text: 'text-green-800', label: 'Niedrig' },
|
||||
medium: { bg: 'bg-yellow-100', text: 'text-yellow-800', label: 'Mittel' },
|
||||
high: { bg: 'bg-orange-100', text: 'text-orange-800', label: 'Hoch' },
|
||||
critical: { bg: 'bg-red-100', text: 'text-red-800', label: 'Kritisch' },
|
||||
unknown: { bg: 'bg-gray-100', text: 'text-gray-800', label: 'Unbekannt' },
|
||||
}
|
||||
|
||||
const DOC_TYPE_LABELS: Record<string, string> = {
|
||||
privacy_policy: 'Datenschutzerklaerung',
|
||||
cookie_banner: 'Cookie-Banner',
|
||||
terms_of_service: 'AGB',
|
||||
imprint: 'Impressum',
|
||||
dpa: 'Auftragsverarbeitung (AVV)',
|
||||
other: 'Sonstiges',
|
||||
}
|
||||
|
||||
interface Props {
|
||||
result: AnalysisResultType
|
||||
}
|
||||
|
||||
export function AnalysisResult({ result }: Props) {
|
||||
const risk = RISK_COLORS[result.risk_level] || RISK_COLORS.unknown
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">
|
||||
{DOC_TYPE_LABELS[result.classification] || result.classification}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-500 truncate max-w-md">{result.url}</p>
|
||||
</div>
|
||||
<span className={`px-3 py-1 rounded-full text-sm font-medium ${risk.bg} ${risk.text}`}>
|
||||
{risk.label} ({result.risk_score}/100)
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Role Assignment */}
|
||||
<div className="bg-purple-50 border border-purple-200 rounded-lg p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<svg className="w-5 h-5 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||
</svg>
|
||||
<span className="text-sm font-medium text-purple-900">
|
||||
Zugewiesen an: <strong>{result.responsible_role}</strong>
|
||||
</span>
|
||||
<span className="text-xs text-purple-600 ml-auto">
|
||||
Eskalationsstufe {result.escalation_level}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Summary */}
|
||||
{result.summary && (
|
||||
<div className="bg-gray-50 rounded-lg p-4">
|
||||
<h4 className="text-sm font-medium text-gray-700 mb-2">Zusammenfassung</h4>
|
||||
<p className="text-sm text-gray-600 whitespace-pre-wrap">{result.summary}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Findings */}
|
||||
{result.findings.length > 0 && (
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-gray-700 mb-2">Findings ({result.findings.length})</h4>
|
||||
<ul className="space-y-1">
|
||||
{result.findings.map((f, i) => (
|
||||
<li key={i} className="flex items-start gap-2 text-sm text-gray-600">
|
||||
<span className="text-orange-500 mt-0.5">!</span>
|
||||
{f}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Required Controls */}
|
||||
{result.required_controls.length > 0 && (
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-gray-700 mb-2">Erforderliche Massnahmen</h4>
|
||||
<ul className="space-y-1">
|
||||
{result.required_controls.map((c, i) => (
|
||||
<li key={i} className="flex items-start gap-2 text-sm text-gray-600">
|
||||
<span className="text-blue-500 mt-0.5">✓</span>
|
||||
{c}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Email Status */}
|
||||
<div className="flex items-center gap-2 text-sm text-gray-500 pt-2 border-t">
|
||||
<span className={result.email_status === 'sent' ? 'text-green-600' : 'text-yellow-600'}>
|
||||
{result.email_status === 'sent' ? '✉ Email gesendet' : '✉ Email ausstehend'}
|
||||
</span>
|
||||
<span className="ml-auto text-xs">
|
||||
{new Date(result.analyzed_at).toLocaleString('de-DE')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user