diff --git a/admin-compliance/app/api/sdk/v1/agent/snapshots/[snapshotId]/dse-check/route.ts b/admin-compliance/app/api/sdk/v1/agent/snapshots/[snapshotId]/dse-check/route.ts new file mode 100644 index 00000000..832560a0 --- /dev/null +++ b/admin-compliance/app/api/sdk/v1/agent/snapshots/[snapshotId]/dse-check/route.ts @@ -0,0 +1,34 @@ +/** + * DSE-Analyse-Proxy + * GET /api/sdk/v1/agent/snapshots/{snapshotId}/dse-check + * → backend /api/compliance/agent/snapshots/{snapshotId}/dse-check + * + * Laeuft den kuratierten DSEAgent (Art. 13/14, ART13_CHECKLIST — kein + * Library-Firehose) auf dem gespeicherten DSE-Text (kein Re-Crawl). + */ + +import { NextRequest, NextResponse } from 'next/server' + +const BACKEND_URL = + process.env.BACKEND_API_URL || process.env.BACKEND_URL || + 'http://backend-compliance:8002' + +export async function GET( + _request: NextRequest, + { params }: { params: Promise<{ snapshotId: string }> }, +) { + const { snapshotId } = await params + try { + const response = await fetch( + `${BACKEND_URL}/api/compliance/agent/snapshots/${snapshotId}/dse-check`, + { signal: AbortSignal.timeout(120_000) }, + ) + const data = await response.json() + return NextResponse.json(data, { status: response.status }) + } catch { + return NextResponse.json( + { error: 'DSE-Analyse fehlgeschlagen', findings: [] }, + { status: 503 }, + ) + } +} diff --git a/admin-compliance/app/sdk/agent/_components/AgentModuleTab.tsx b/admin-compliance/app/sdk/agent/_components/AgentModuleTab.tsx new file mode 100644 index 00000000..7bf593a0 --- /dev/null +++ b/admin-compliance/app/sdk/agent/_components/AgentModuleTab.tsx @@ -0,0 +1,44 @@ +'use client' + +/** + * AgentModuleTab — generischer Snapshot-Modul-Tab für einen Doc-Type-Agenten + * (Impressum, DSE, …). Lädt `/snapshots/{id}/{docType}-check` beim Mounten + * (kein Re-Crawl) und rendert den AgentOutput im geteilten AgentResultTab. + * Wird nur gemountet, wenn der Tab aktiv ist → Analyse läuft on-demand. + */ + +import React, { useEffect, useState } from 'react' + +import { AgentResultTab } from './AgentResultTab' + +export function AgentModuleTab( + { snapshotId, docType, label }: + { snapshotId: string; docType: string; label: string }, +) { + const [data, setData] = useState(null) + const [loading, setLoading] = useState(true) + + useEffect(() => { + let cancelled = false + setLoading(true) + fetch(`/api/sdk/v1/agent/snapshots/${snapshotId}/${docType}-check`) + .then(r => r.json()) + .then(d => { if (!cancelled) setData(d) }) + .catch(() => { + if (!cancelled) setData({ error: `${label}-Analyse fehlgeschlagen`, findings: [] }) + }) + .finally(() => { if (!cancelled) setLoading(false) }) + return () => { cancelled = true } + }, [snapshotId, docType, label]) + + if (loading) return
{label}-Analyse läuft…
+ if (data?.error) return
{data.error}
+ if (data && ((data.findings?.length ?? 0) > 0 || (data.mc_coverage?.length ?? 0) > 0)) { + return + } + return ( +
+ {data?.notes || `Keine ${label}-Auswertung verfügbar.`} +
+ ) +} diff --git a/admin-compliance/app/sdk/agent/snapshots/[snapshotId]/page.tsx b/admin-compliance/app/sdk/agent/snapshots/[snapshotId]/page.tsx index 59f1c9e8..97a9c610 100644 --- a/admin-compliance/app/sdk/agent/snapshots/[snapshotId]/page.tsx +++ b/admin-compliance/app/sdk/agent/snapshots/[snapshotId]/page.tsx @@ -3,8 +3,9 @@ /** * Snapshot-Detail — öffnet einen gespeicherten Check aus der Historie und * zeigt die Ergebnis-Views aus den Rohdaten (kein Re-Crawl), als Modul-Tabs: - * Cookies & Tracking + Impressum (DSE/AGB folgen). Impressum wird beim Öffnen - * des Tabs nachgeladen (ImpressumAgent auf dem gespeicherten Text). + * Cookies & Tracking + Impressum + Datenschutzerklärung (AGB folgen). + * Doc-Agenten (Impressum/DSE) laufen beim Öffnen des Tabs auf dem gespeicherten + * Text — generisch via AgentModuleTab. */ import React, { use as useUnwrap, useEffect, useMemo, useState } from 'react' @@ -12,7 +13,7 @@ import Link from 'next/link' import { CookieLibraryPanel } from '../../_components/CookieLibraryPanel' import { CookieResultView } from '../../_components/CookieResultView' -import { AgentResultTab } from '../../_components/AgentResultTab' +import { AgentModuleTab } from '../../_components/AgentModuleTab' export default function SnapshotDetail( { params }: { params: Promise<{ snapshotId: string }> }, @@ -20,8 +21,6 @@ export default function SnapshotDetail( const { snapshotId } = useUnwrap(params) const [snap, setSnap] = useState(null) const [check, setCheck] = useState(null) // cookie-check - const [impressum, setImpressum] = useState(null) // impressum-check (lazy) - const [impLoading, setImpLoading] = useState(false) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [tab, setTab] = useState('') @@ -51,29 +50,20 @@ export default function SnapshotDetail( const docs = snap?.doc_entries || [] const hasCookies = (snap?.cmp_vendors?.length ?? 0) > 0 - const hasImpressum = docs.some( - (e: any) => e.doc_type === 'impressum' && (e.text || e.content || '').length > 100) + const hasDoc = (dt: string) => docs.some( + (e: any) => e.doc_type === dt && (e.text || e.content || '').length > 100) const modules = useMemo(() => [ ...(hasCookies ? [{ key: 'cookie', label: 'Cookies & Tracking' }] : []), - ...(hasImpressum ? [{ key: 'impressum', label: 'Impressum' }] : []), - ], [hasCookies, hasImpressum]) + ...(hasDoc('impressum') ? [{ key: 'impressum', label: 'Impressum' }] : []), + ...(hasDoc('dse') ? [{ key: 'dse', label: 'Datenschutzerklärung' }] : []), + // eslint-disable-next-line react-hooks/exhaustive-deps + ], [snap]) useEffect(() => { if (!tab && modules.length) setTab(modules[0].key) }, [modules, tab]) - // Impressum erst beim Öffnen des Tabs analysieren (ImpressumAgent, ggf. LLM). - useEffect(() => { - if (tab !== 'impressum' || impressum || impLoading) return - setImpLoading(true) - fetch(`/api/sdk/v1/agent/snapshots/${snapshotId}/impressum-check`) - .then(r => r.json()) - .then(setImpressum) - .catch(() => setImpressum({ error: 'Impressum-Analyse fehlgeschlagen', findings: [] })) - .finally(() => setImpLoading(false)) - }, [tab, snapshotId, impressum, impLoading]) - const tabBtn = (key: string, label: string) => (