diff --git a/admin-compliance/app/api/sdk/v1/agent/scan/route.ts b/admin-compliance/app/api/sdk/v1/agent/scan/route.ts new file mode 100644 index 0000000..41bbd58 --- /dev/null +++ b/admin-compliance/app/api/sdk/v1/agent/scan/route.ts @@ -0,0 +1,38 @@ +/** + * Agent Scan API Proxy + * POST /api/sdk/v1/agent/scan → backend-compliance /api/compliance/agent/scan + */ + +import { NextRequest, NextResponse } from 'next/server' + +const BACKEND_URL = process.env.BACKEND_API_URL || 'http://backend-compliance:8002' + +export async function POST(request: NextRequest) { + try { + const body = await request.text() + + const response = await fetch(`${BACKEND_URL}/api/compliance/agent/scan`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body, + signal: AbortSignal.timeout(180000), // 3 min — multi-page scan + LLM + }) + + if (!response.ok) { + const errorText = await response.text() + return NextResponse.json( + { error: `Backend: ${response.status}`, detail: errorText }, + { status: response.status } + ) + } + + const data = await response.json() + return NextResponse.json(data) + } catch (error) { + console.error('Agent scan proxy error:', error) + return NextResponse.json( + { error: 'Scan fehlgeschlagen oder Timeout' }, + { status: 503 } + ) + } +} diff --git a/admin-compliance/app/sdk/agent/_components/ScanResult.tsx b/admin-compliance/app/sdk/agent/_components/ScanResult.tsx new file mode 100644 index 0000000..415d94a --- /dev/null +++ b/admin-compliance/app/sdk/agent/_components/ScanResult.tsx @@ -0,0 +1,170 @@ +'use client' + +import React, { useState } from 'react' + +interface ServiceInfo { + name: string + category: string + provider: string + country: string + eu_adequate: boolean + requires_consent: boolean + legal_ref: string + in_dse: boolean + status: string +} + +interface ScanFinding { + code: string + severity: string + text: string + correction: string +} + +interface ScanData { + pages_scanned: number + services: ServiceInfo[] + findings: ScanFinding[] + ai_detected: boolean + chatbot_detected: boolean + chatbot_provider: string + missing_pages: Record + email_status: string +} + +const STATUS_ICON: Record = { + ok: { icon: '✓', color: 'text-green-600' }, + undocumented: { icon: '✗', color: 'text-red-600' }, + outdated: { icon: '~', color: 'text-yellow-600' }, +} + +const SEV_STYLE: Record = { + HIGH: { bg: 'bg-red-50 border-red-200', text: 'text-red-800' }, + MEDIUM: { bg: 'bg-yellow-50 border-yellow-200', text: 'text-yellow-800' }, + LOW: { bg: 'bg-blue-50 border-blue-200', text: 'text-blue-800' }, +} + +export function ScanResult({ data }: { data: ScanData }) { + const [expandedCorrection, setExpandedCorrection] = useState(null) + + const undocCount = data.services.filter(s => s.status === 'undocumented').length + const okCount = data.services.filter(s => s.status === 'ok').length + const outdatedCount = data.services.filter(s => s.status === 'outdated').length + const highCount = data.findings.filter(f => f.severity === 'HIGH').length + + return ( +
+ {/* Summary Bar */} +
+
+

{data.pages_scanned}

+

Seiten gescannt

+
+
+

{okCount}

+

Dokumentiert

+
+
+

{undocCount}

+

Nicht in DSE

+
+
+

{outdatedCount}

+

Veraltet

+
+
+ + {/* AI / Chatbot Detection */} +
+ + {data.ai_detected ? 'KI erkannt' : 'Keine KI erkannt'} + + + {data.chatbot_detected ? `Chatbot: ${data.chatbot_provider}` : 'Kein Chatbot'} + +
+ + {/* Services Table */} +
+

Dienstleister-Abgleich (SOLL/IST)

+
+ + + + + + + + + + + + {data.services.map((s, i) => { + const st = STATUS_ICON[s.status] || STATUS_ICON.ok + return ( + + + + + + + + ) + })} + +
StatusDienstLandEUIn DSE
{st.icon} + {s.name} + {s.category} + {s.country}{s.eu_adequate ? '✓' : '✗'}{s.in_dse ? 'Ja' : Nein}
+
+
+ + {/* Findings */} + {data.findings.length > 0 && ( +
+

+ Findings ({data.findings.length}, davon {highCount} kritisch) +

+
+ {data.findings.map((f, i) => { + const sev = SEV_STYLE[f.severity] || SEV_STYLE.MEDIUM + const isExpanded = expandedCorrection === f.code + return ( +
+
+ + {f.severity} + +

{f.text}

+
+ {f.correction && ( +
+ + {isExpanded && ( +
+
{f.correction}
+ +
+ )} +
+ )} +
+ ) + })} +
+
+ )} +
+ ) +} diff --git a/admin-compliance/app/sdk/agent/page.tsx b/admin-compliance/app/sdk/agent/page.tsx index 20cf5ab..cb36083 100644 --- a/admin-compliance/app/sdk/agent/page.tsx +++ b/admin-compliance/app/sdk/agent/page.tsx @@ -5,135 +5,141 @@ import { useAgentAnalysis } from './_hooks/useAgentAnalysis' import { AnalysisResult } from './_components/AnalysisResult' import { AnalysisHistory } from './_components/AnalysisHistory' import { FollowUpQuestions } from './_components/FollowUpQuestions' +import { ScanResult } from './_components/ScanResult' type AnalysisMode = 'pre_launch' | 'post_launch' +type AnalysisTab = 'quick' | 'scan' const MODES: { id: AnalysisMode; label: string; desc: string; icon: string }[] = [ - { - id: 'pre_launch', - label: 'Internes Dokument pruefen', - desc: 'Dokument oder Website VOR Veroeffentlichung pruefen', - icon: '📋', - }, - { - id: 'post_launch', - label: 'Live-Website pruefen', - desc: 'Bereits veroeffentlichte Website oder Dokument analysieren', - icon: '🌐', - }, + { id: 'pre_launch', label: 'Internes Dokument', desc: 'Vor Veroeffentlichung pruefen', icon: '📋' }, + { id: 'post_launch', label: 'Live-Website', desc: 'Bereits online analysieren', icon: '🌐' }, +] + +const TABS: { id: AnalysisTab; label: string; desc: string }[] = [ + { id: 'quick', label: 'Schnellanalyse', desc: 'Einzelne Seite klassifizieren + bewerten' }, + { id: 'scan', label: 'Website-Scan', desc: 'Mehrere Seiten scannen + Dienstleister abgleichen' }, ] export default function AgentPage() { const [url, setUrl] = useState('') const [mode, setMode] = useState('post_launch') + const [tab, setTab] = useState('quick') + const [scanLoading, setScanLoading] = useState(false) + const [scanError, setScanError] = useState(null) + const [scanData, setScanData] = useState(null) const { analyze, answerFollowUp, loading, error, result, history } = useAgentAnalysis() - const handleSubmit = (e: React.FormEvent) => { + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!url.trim()) return - analyze(url.trim(), mode) + + if (tab === 'quick') { + analyze(url.trim(), mode) + } else { + setScanLoading(true) + setScanError(null) + setScanData(null) + try { + const res = await fetch('/api/sdk/v1/agent/scan', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ url: url.trim(), mode }), + }) + if (!res.ok) throw new Error(`Scan fehlgeschlagen: ${res.status}`) + setScanData(await res.json()) + } catch (e) { + setScanError(e instanceof Error ? e.message : 'Unbekannter Fehler') + } finally { + setScanLoading(false) + } + } } + const isLoading = tab === 'quick' ? loading : scanLoading + const currentError = tab === 'quick' ? error : scanError + return (
- {/* Header */}

Compliance Agent

-

- Analysiere Dokumente und Webseiten auf DSGVO-Konformitaet. -

+

Analysiere Dokumente und Webseiten auf DSGVO-Konformitaet.

{/* Mode Selection */}
{MODES.map(m => ( - ))}
+ {/* Tab Selection */} +
+ {TABS.map(t => ( + + ))} +
+ {/* URL Input */}
- setUrl(e.target.value)} - placeholder={mode === 'pre_launch' - ? 'https://staging.example.com/datenschutz' - : 'https://www.example.com/datenschutz'} + setUrl(e.target.value)} + placeholder={tab === 'scan' ? 'https://www.example.com/' : 'https://example.com/datenschutz'} className="flex-1 px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent text-sm" - disabled={loading} - required - /> -
{/* Error */} - {error && ( -
- {error} -
+ {currentError && ( +
{currentError}
)} - {/* Result */} - {result && ( + {/* Quick Analysis Result */} + {tab === 'quick' && result && (
- - {/* Follow-Up Questions */} {result.follow_up_questions.length > 0 && (
- +
)}
)} - {/* History */} - { - setUrl(r.url) - analyze(r.url, mode) - }} - /> + {/* Scan Result */} + {tab === 'scan' && scanData && ( +
+ +
+ )} + + {/* History (quick only) */} + {tab === 'quick' && ( + { setUrl(r.url); analyze(r.url, mode) }} /> + )}
) }