fix: Agents-Seite in SDK-Layout verschoben — Sidebar + Icons sichtbar
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 36s
CI / test-python-backend-compliance (push) Successful in 35s
CI / test-python-document-crawler (push) Successful in 22s
CI / test-python-dsms-gateway (push) Successful in 18s

Agents lag in app/(sdk)/sdk/agents/ (separater Route-Group) und erbte
daher nicht das SDK-Layout aus app/sdk/layout.tsx. Verschoben nach
app/sdk/agents/ damit SDKSidebar, ComplianceAdvisorWidget und
SDKPipelineSidebar korrekt angezeigt werden.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-05 08:42:40 +01:00
parent 451616b10e
commit 6e0e9cd3cf
5 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,333 @@
'use client'
import React, { useEffect, useState, useCallback } from 'react'
import Link from 'next/link'
import { useParams } from 'next/navigation'
interface AgentDetail {
id: string
name: string
description: string
color: string
icon: string
status: 'active' | 'inactive' | 'error'
version: string
soulContent: string
createdAt: string | null
updatedAt: string | null
fileSize: number
stats: {
sessionsToday: number
avgResponseTime: string
successRate: string
}
}
interface BackupEntry {
filename: string
timestamp: number
size: number
}
function StatusBadge({ status }: { status: string }) {
const colors = {
active: 'bg-green-100 text-green-700',
inactive: 'bg-gray-100 text-gray-600',
error: 'bg-red-100 text-red-700',
}
const labels = { active: 'Aktiv', inactive: 'Inaktiv', error: 'Fehler' }
return (
<span className={`px-2.5 py-1 rounded-full text-xs font-medium ${colors[status as keyof typeof colors] || colors.inactive}`}>
{labels[status as keyof typeof labels] || status}
</span>
)
}
export default function AgentDetailPage() {
const params = useParams()
const agentId = params.agentId as string
const [agent, setAgent] = useState<AgentDetail | null>(null)
const [loading, setLoading] = useState(true)
const [activeTab, setActiveTab] = useState<'soul' | 'stats' | 'history'>('soul')
const [editing, setEditing] = useState(false)
const [editContent, setEditContent] = useState('')
const [saving, setSaving] = useState(false)
const [saveMessage, setSaveMessage] = useState('')
const [backups, setBackups] = useState<BackupEntry[]>([])
const loadAgent = useCallback(async () => {
try {
const res = await fetch(`/api/sdk/agents/${agentId}`)
if (res.ok) {
const data = await res.json()
setAgent(data)
if (!editing) setEditContent(data.soulContent)
}
} catch (err) {
console.error('Failed to load agent:', err)
} finally {
setLoading(false)
}
}, [agentId, editing])
const loadBackups = useCallback(async () => {
try {
const res = await fetch(`/api/sdk/agents/${agentId}/soul?history=true`)
if (res.ok) {
const data = await res.json()
setBackups(data.backups || [])
}
} catch (err) {
console.error('Failed to load backups:', err)
}
}, [agentId])
useEffect(() => {
loadAgent()
}, [loadAgent])
useEffect(() => {
if (activeTab === 'history') loadBackups()
}, [activeTab, loadBackups])
const handleSave = async () => {
setSaving(true)
setSaveMessage('')
try {
const res = await fetch(`/api/sdk/agents/${agentId}/soul`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content: editContent }),
})
if (res.ok) {
setSaveMessage('SOUL-Datei gespeichert')
setEditing(false)
await loadAgent()
} else {
const err = await res.json()
setSaveMessage(`Fehler: ${err.error}`)
}
} catch {
setSaveMessage('Speichern fehlgeschlagen')
} finally {
setSaving(false)
setTimeout(() => setSaveMessage(''), 3000)
}
}
const handleReset = () => {
if (agent) {
setEditContent(agent.soulContent)
setEditing(false)
}
}
if (loading) {
return (
<div className="p-8">
<div className="animate-pulse space-y-4">
<div className="h-8 bg-gray-200 rounded w-48" />
<div className="h-64 bg-gray-200 rounded-xl" />
</div>
</div>
)
}
if (!agent) {
return (
<div className="p-8">
<p className="text-red-600">Agent nicht gefunden.</p>
<Link href="/sdk/agents" className="text-purple-600 hover:underline mt-2 inline-block">
Zurueck zur Uebersicht
</Link>
</div>
)
}
return (
<div className="p-8 max-w-5xl">
{/* Header */}
<div className="flex items-center gap-4 mb-6">
<Link href="/sdk/agents" className="text-gray-400 hover:text-gray-600 transition-colors">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
</Link>
<div
className="w-12 h-12 rounded-xl flex items-center justify-center text-white"
style={{ backgroundColor: agent.color }}
>
{agent.icon === 'shield' ? (
<svg className="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5}
d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
) : (
<svg className="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5}
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
)}
</div>
<div className="flex-1">
<div className="flex items-center gap-3">
<h1 className="text-2xl font-bold text-gray-900">{agent.name}</h1>
<StatusBadge status={agent.status} />
</div>
<p className="text-sm text-gray-500">{agent.description}</p>
</div>
</div>
{/* Stats Bar */}
<div className="grid grid-cols-2 md:grid-cols-5 gap-4 mb-6">
<div className="bg-white border border-gray-200 rounded-xl px-4 py-3">
<div className="text-lg font-semibold text-gray-900">{agent.stats.sessionsToday}</div>
<div className="text-xs text-gray-500">Sessions heute</div>
</div>
<div className="bg-white border border-gray-200 rounded-xl px-4 py-3">
<div className="text-lg font-semibold text-gray-900">{agent.stats.avgResponseTime}</div>
<div className="text-xs text-gray-500">Avg. Antwortzeit</div>
</div>
<div className="bg-white border border-gray-200 rounded-xl px-4 py-3">
<div className="text-lg font-semibold text-gray-900">{agent.stats.successRate}</div>
<div className="text-xs text-gray-500">Erfolgsrate</div>
</div>
<div className="bg-white border border-gray-200 rounded-xl px-4 py-3">
<div className="text-lg font-semibold text-gray-900">v{agent.version}</div>
<div className="text-xs text-gray-500">Version</div>
</div>
<div className="bg-white border border-gray-200 rounded-xl px-4 py-3">
<div className="text-lg font-semibold text-gray-900">{agent.fileSize ? `${(agent.fileSize / 1024).toFixed(1)}k` : '—'}</div>
<div className="text-xs text-gray-500">SOUL-Groesse</div>
</div>
</div>
{/* Tabs */}
<div className="flex border-b border-gray-200 mb-6">
{[
{ id: 'soul' as const, label: 'SOUL-File' },
{ id: 'stats' as const, label: 'Live-Statistiken' },
{ id: 'history' as const, label: 'Aenderungshistorie' },
].map(tab => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`px-4 py-3 text-sm font-medium border-b-2 transition-colors ${
activeTab === tab.id
? 'border-purple-600 text-purple-700'
: 'border-transparent text-gray-500 hover:text-gray-700'
}`}
>
{tab.label}
</button>
))}
</div>
{/* Tab Content */}
{activeTab === 'soul' && (
<div>
{/* Toolbar */}
<div className="flex items-center justify-between mb-3">
<div className="text-sm text-gray-500">
{agent.updatedAt && `Zuletzt geaendert: ${new Date(agent.updatedAt).toLocaleString('de-DE')}`}
</div>
<div className="flex items-center gap-2">
{saveMessage && (
<span className={`text-sm ${saveMessage.startsWith('Fehler') ? 'text-red-600' : 'text-green-600'}`}>
{saveMessage}
</span>
)}
{editing ? (
<>
<button
onClick={handleReset}
className="px-3 py-1.5 text-sm text-gray-600 bg-white border border-gray-300 rounded-lg hover:bg-gray-50"
>
Abbrechen
</button>
<button
onClick={handleSave}
disabled={saving}
className="px-3 py-1.5 text-sm text-white bg-purple-600 rounded-lg hover:bg-purple-700 disabled:opacity-50"
>
{saving ? 'Speichern...' : 'Speichern'}
</button>
</>
) : (
<button
onClick={() => setEditing(true)}
className="px-3 py-1.5 text-sm text-purple-600 bg-purple-50 border border-purple-200 rounded-lg hover:bg-purple-100"
>
Bearbeiten
</button>
)}
</div>
</div>
{/* Editor */}
<textarea
value={editContent}
onChange={(e) => setEditContent(e.target.value)}
readOnly={!editing}
className={`w-full h-[600px] font-mono text-sm p-4 border rounded-xl resize-none focus:outline-none ${
editing
? 'border-purple-300 bg-white focus:ring-2 focus:ring-purple-200'
: 'border-gray-200 bg-gray-50 cursor-default'
}`}
spellCheck={false}
/>
</div>
)}
{activeTab === 'stats' && (
<div className="bg-white border border-gray-200 rounded-xl p-8 text-center">
<svg className="w-16 h-16 text-gray-300 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5}
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
<h3 className="text-lg font-medium text-gray-900 mb-2">Live-Statistiken</h3>
<p className="text-gray-500">
Detaillierte Echtzeit-Statistiken werden in einer zukuenftigen Version implementiert.
</p>
</div>
)}
{activeTab === 'history' && (
<div>
{backups.length === 0 ? (
<div className="bg-white border border-gray-200 rounded-xl p-8 text-center">
<svg className="w-16 h-16 text-gray-300 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5}
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<h3 className="text-lg font-medium text-gray-900 mb-2">Keine Backups vorhanden</h3>
<p className="text-gray-500">
Backups werden automatisch beim Speichern von SOUL-Dateien erstellt.
</p>
</div>
) : (
<div className="space-y-3">
{backups.map((backup) => (
<div key={backup.filename} className="bg-white border border-gray-200 rounded-xl px-5 py-4 flex items-center justify-between">
<div>
<div className="font-medium text-sm text-gray-900">
{new Date(backup.timestamp).toLocaleString('de-DE')}
</div>
<div className="text-xs text-gray-500 mt-0.5">
{(backup.size / 1024).toFixed(1)} KB {backup.filename}
</div>
</div>
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-purple-200" />
<span className="text-xs text-gray-400">Backup</span>
</div>
</div>
))}
</div>
)}
</div>
)}
</div>
)
}

View File

@@ -0,0 +1,221 @@
'use client'
import React from 'react'
import Link from 'next/link'
export default function AgentArchitecturePage() {
return (
<div className="p-8 max-w-5xl">
{/* Header */}
<div className="flex items-center gap-4 mb-8">
<Link href="/sdk/agents" className="text-gray-400 hover:text-gray-600 transition-colors">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
</Link>
<div>
<h1 className="text-2xl font-bold text-gray-900">Agent-Architektur</h1>
<p className="text-gray-500 mt-1">2-Agenten-System mit RAG, SOUL-Files und LLM-Backend</p>
</div>
</div>
{/* Architecture Overview */}
<section className="mb-10">
<h2 className="text-lg font-semibold text-gray-900 mb-4">System-Uebersicht</h2>
<div className="bg-gray-900 text-green-400 font-mono text-sm rounded-xl p-6 overflow-x-auto">
<pre>{`
+---------------------+ +---------------------+
| Compliance Advisor | | Drafting Agent |
| (RAG-Chat) | | (4-Modi) |
| - DSGVO | | - Explain |
| - AI Act | | - Ask |
| - 6 Sammlungen | | - Draft |
+--------+------------+ | - Validate |
| +--------+------------+
| |
+----------+ +--------------+
| |
v v
+--------+--+--------+
| SOUL-File System |
| agent-core/soul/ |
| - .soul.md Dateien |
| - .backups/ |
| - 30s TTL Cache |
+--------+-----------+
|
v
+--------+-----------+
| RAG-Service :8097 |
| 6 Sammlungen: |
| - gesetze |
| - ce |
| - datenschutz |
| - dsfa_corpus |
| - recht |
| - legal_templates |
+--------+-----------+
|
v
+--------+-----------+
| Ollama LLM |
| qwen2.5vl:32b |
| Temp: 0.2-0.3 |
+--------------------+
`.trim()}</pre>
</div>
</section>
{/* Agent Cards */}
<section className="mb-10">
<h2 className="text-lg font-semibold text-gray-900 mb-4">Agenten im Detail</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Compliance Advisor */}
<div className="bg-white border border-gray-200 rounded-2xl p-6">
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 rounded-xl bg-purple-600 flex items-center justify-center text-white">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5}
d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
</div>
<h3 className="font-semibold text-gray-900">Compliance Advisor</h3>
</div>
<ul className="space-y-2 text-sm text-gray-600">
<li className="flex items-start gap-2">
<span className="text-purple-600 mt-0.5">&#8226;</span>
Multi-Collection RAG (6 Sammlungen parallel)
</li>
<li className="flex items-start gap-2">
<span className="text-purple-600 mt-0.5">&#8226;</span>
Laender-Filter: DE, AT, CH, EU
</li>
<li className="flex items-start gap-2">
<span className="text-purple-600 mt-0.5">&#8226;</span>
Streaming-Antworten (Ollama)
</li>
<li className="flex items-start gap-2">
<span className="text-purple-600 mt-0.5">&#8226;</span>
Quellenschutz: Keine Collection-Namen preisgeben
</li>
<li className="flex items-start gap-2">
<span className="text-purple-600 mt-0.5">&#8226;</span>
IFRS-Besonderheit: Nur EU-endorsed Standards
</li>
</ul>
<div className="mt-4 pt-3 border-t border-gray-100">
<code className="text-xs text-gray-500">API: POST /api/sdk/compliance-advisor/chat</code>
</div>
</div>
{/* Drafting Agent */}
<div className="bg-white border border-gray-200 rounded-2xl p-6">
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 rounded-xl bg-blue-600 flex items-center justify-center text-white">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5}
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
</div>
<h3 className="font-semibold text-gray-900">Drafting Agent</h3>
</div>
<ul className="space-y-2 text-sm text-gray-600">
<li className="flex items-start gap-2">
<span className="text-blue-600 mt-0.5">&#8226;</span>
<strong>Explain</strong>: Fragen verstaendlich beantworten
</li>
<li className="flex items-start gap-2">
<span className="text-blue-600 mt-0.5">&#8226;</span>
<strong>Ask</strong>: Luecken analysieren, gezielte Fragen
</li>
<li className="flex items-start gap-2">
<span className="text-blue-600 mt-0.5">&#8226;</span>
<strong>Draft</strong>: Dokument-Sections entwerfen (JSON)
</li>
<li className="flex items-start gap-2">
<span className="text-blue-600 mt-0.5">&#8226;</span>
<strong>Validate</strong>: Cross-Dokument-Konsistenz pruefen
</li>
<li className="flex items-start gap-2">
<span className="text-blue-600 mt-0.5">&#8226;</span>
SDK-State-Projection fuer token-effizienten Kontext
</li>
</ul>
<div className="mt-4 pt-3 border-t border-gray-100">
<code className="text-xs text-gray-500">API: POST /api/sdk/drafting-engine/chat</code>
</div>
</div>
</div>
</section>
{/* SOUL System */}
<section className="mb-10">
<h2 className="text-lg font-semibold text-gray-900 mb-4">SOUL-File System</h2>
<div className="bg-white border border-gray-200 rounded-2xl p-6">
<p className="text-sm text-gray-600 mb-4">
Jeder Agent hat eine <code className="bg-gray-100 px-1.5 py-0.5 rounded text-purple-700">.soul.md</code> Datei,
die seinen System-Prompt definiert. Diese Datei kann ueber die Agent-Detail-Seite live bearbeitet werden.
Aenderungen werden nach 30 Sekunden (TTL-Cache) wirksam.
</p>
<div className="bg-gray-50 rounded-xl p-4 font-mono text-sm text-gray-700">
<div>agent-core/</div>
<div className="ml-4">soul/</div>
<div className="ml-8 text-purple-700">compliance-advisor.soul.md</div>
<div className="ml-8 text-blue-700">drafting-agent.soul.md</div>
<div className="ml-8 text-gray-400">.backups/</div>
<div className="ml-12 text-gray-400">compliance-advisor-1709567890123.soul.md</div>
<div className="ml-12 text-gray-400">...</div>
</div>
</div>
</section>
{/* RAG Collections */}
<section className="mb-10">
<h2 className="text-lg font-semibold text-gray-900 mb-4">RAG-Sammlungen</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{[
{ name: 'bp_compliance_gesetze', desc: 'DSGVO, BDSG, AI Act, TTDSG, nationale Gesetze', icon: '&#9878;' },
{ name: 'bp_compliance_ce', desc: 'EU Maschinenverordnung, Blue Guide, CE-Kennzeichnung', icon: '&#9881;' },
{ name: 'bp_compliance_datenschutz', desc: 'DSK-Kurzpapiere, SDM, EDPB Guidelines', icon: '&#128274;' },
{ name: 'bp_dsfa_corpus', desc: 'DSFA-Listen, Muss-Listen der Aufsichtsbehoerden', icon: '&#128203;' },
{ name: 'bp_compliance_recht', desc: 'WP248, EU-Verordnungen (DORA, MiCA, etc.)', icon: '&#9878;' },
{ name: 'bp_legal_templates', desc: 'Vorlagen fuer Datenschutz-Dokumente', icon: '&#128196;' },
].map(col => (
<div key={col.name} className="bg-white border border-gray-200 rounded-xl p-4">
<div className="flex items-center gap-2 mb-2">
<span dangerouslySetInnerHTML={{ __html: col.icon }} />
<code className="text-xs font-medium text-gray-700">{col.name}</code>
</div>
<p className="text-xs text-gray-500">{col.desc}</p>
</div>
))}
</div>
</section>
{/* LLM Config */}
<section>
<h2 className="text-lg font-semibold text-gray-900 mb-4">LLM-Konfiguration</h2>
<div className="bg-white border border-gray-200 rounded-2xl p-6">
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div>
<div className="text-xs text-gray-500 mb-1">Modell</div>
<div className="font-medium text-gray-900">qwen2.5vl:32b</div>
</div>
<div>
<div className="text-xs text-gray-500 mb-1">Backend</div>
<div className="font-medium text-gray-900">Ollama</div>
</div>
<div>
<div className="text-xs text-gray-500 mb-1">Temperatur</div>
<div className="font-medium text-gray-900">0.2 - 0.3</div>
</div>
<div>
<div className="text-xs text-gray-500 mb-1">Max Tokens</div>
<div className="font-medium text-gray-900">8.192 - 16.384</div>
</div>
</div>
</div>
</section>
</div>
)
}

View File

@@ -0,0 +1,224 @@
'use client'
import React, { useEffect, useState, useCallback } from 'react'
import Link from 'next/link'
interface AgentData {
id: string
name: string
description: string
color: string
icon: string
status: 'active' | 'inactive' | 'error'
version: string
stats: {
sessionsToday: number
avgResponseTime: string
successRate: string
}
}
interface AgentsResponse {
agents: AgentData[]
stats: {
total: number
active: number
inactive: number
error: number
totalSessions: number
avgResponseTime: string
}
timestamp: string
}
const ShieldIcon = () => (
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5}
d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
)
const PencilIcon = () => (
<svg className="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5}
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
)
function getAgentIcon(icon: string) {
switch (icon) {
case 'shield': return <ShieldIcon />
case 'pencil': return <PencilIcon />
default: return <ShieldIcon />
}
}
function StatusBadge({ status }: { status: string }) {
const colors = {
active: 'bg-green-100 text-green-700',
inactive: 'bg-gray-100 text-gray-600',
error: 'bg-red-100 text-red-700',
}
const labels = { active: 'Aktiv', inactive: 'Inaktiv', error: 'Fehler' }
return (
<span className={`px-2 py-0.5 rounded-full text-xs font-medium ${colors[status as keyof typeof colors] || colors.inactive}`}>
{labels[status as keyof typeof labels] || status}
</span>
)
}
export default function AgentsDashboardPage() {
const [data, setData] = useState<AgentsResponse | null>(null)
const [loading, setLoading] = useState(true)
const loadAgents = useCallback(async () => {
try {
const res = await fetch('/api/sdk/agents')
if (res.ok) {
setData(await res.json())
}
} catch (err) {
console.error('Failed to load agents:', err)
} finally {
setLoading(false)
}
}, [])
useEffect(() => {
loadAgents()
const interval = setInterval(loadAgents, 30_000)
return () => clearInterval(interval)
}, [loadAgents])
if (loading) {
return (
<div className="p-8">
<div className="animate-pulse space-y-4">
<div className="h-8 bg-gray-200 rounded w-64" />
<div className="grid grid-cols-3 gap-4">
{[1, 2, 3].map(i => <div key={i} className="h-24 bg-gray-200 rounded-xl" />)}
</div>
</div>
</div>
)
}
const stats = data?.stats
const agents = data?.agents || []
return (
<div className="p-8 max-w-6xl">
{/* Header */}
<div className="mb-8">
<h1 className="text-2xl font-bold text-gray-900">Compliance-Agenten</h1>
<p className="text-gray-500 mt-1">
Verwaltung und Konfiguration der KI-Agenten im Compliance SDK
</p>
</div>
{/* Quick Links */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 mb-8">
<Link href="/sdk/agents/architecture" className="flex items-center gap-2 px-4 py-3 bg-white border border-gray-200 rounded-xl hover:border-purple-300 hover:bg-purple-50 transition-colors text-sm">
<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="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2" />
</svg>
Architektur
</Link>
<Link href="/sdk/agents/sessions" className="flex items-center gap-2 px-4 py-3 bg-white border border-gray-200 rounded-xl hover:border-purple-300 hover:bg-purple-50 transition-colors text-sm">
<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="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
Sessions
</Link>
<Link href="/sdk/agents/statistics" className="flex items-center gap-2 px-4 py-3 bg-white border border-gray-200 rounded-xl hover:border-purple-300 hover:bg-purple-50 transition-colors text-sm">
<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="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
Statistiken
</Link>
<Link href="/sdk/document-generator" className="flex items-center gap-2 px-4 py-3 bg-white border border-gray-200 rounded-xl hover:border-purple-300 hover:bg-purple-50 transition-colors text-sm">
<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="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
Dokument-Generator
</Link>
</div>
{/* Stats Bar */}
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4 mb-8">
<div className="bg-white border border-gray-200 rounded-xl px-4 py-3">
<div className="text-2xl font-bold text-gray-900">{stats?.total || 0}</div>
<div className="text-xs text-gray-500">Agenten gesamt</div>
</div>
<div className="bg-white border border-gray-200 rounded-xl px-4 py-3">
<div className="text-2xl font-bold text-green-600">{stats?.active || 0}</div>
<div className="text-xs text-gray-500">Aktiv</div>
</div>
<div className="bg-white border border-gray-200 rounded-xl px-4 py-3">
<div className="text-2xl font-bold text-red-600">{stats?.error || 0}</div>
<div className="text-xs text-gray-500">Fehler</div>
</div>
<div className="bg-white border border-gray-200 rounded-xl px-4 py-3">
<div className="text-2xl font-bold text-gray-900">{stats?.totalSessions || 0}</div>
<div className="text-xs text-gray-500">Sessions heute</div>
</div>
<div className="bg-white border border-gray-200 rounded-xl px-4 py-3">
<div className="text-2xl font-bold text-gray-900">{stats?.avgResponseTime || '—'}</div>
<div className="text-xs text-gray-500">Avg. Antwortzeit</div>
</div>
<div className="bg-white border border-gray-200 rounded-xl px-4 py-3">
<div className="text-2xl font-bold text-gray-900">6</div>
<div className="text-xs text-gray-500">RAG-Sammlungen</div>
</div>
</div>
{/* Agent Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{agents.map((agent) => (
<Link
key={agent.id}
href={`/sdk/agents/${agent.id}`}
className="bg-white border border-gray-200 rounded-2xl p-6 hover:border-purple-300 hover:shadow-lg transition-all group"
>
<div className="flex items-start justify-between mb-4">
<div className="flex items-center gap-3">
<div
className="w-12 h-12 rounded-xl flex items-center justify-center text-white"
style={{ backgroundColor: agent.color }}
>
{getAgentIcon(agent.icon)}
</div>
<div>
<h2 className="font-semibold text-gray-900 group-hover:text-purple-700 transition-colors">
{agent.name}
</h2>
<div className="text-xs text-gray-400">v{agent.version}</div>
</div>
</div>
<StatusBadge status={agent.status} />
</div>
<p className="text-sm text-gray-600 mb-4 line-clamp-2">
{agent.description}
</p>
<div className="grid grid-cols-3 gap-3 pt-3 border-t border-gray-100">
<div>
<div className="text-lg font-semibold text-gray-900">{agent.stats.sessionsToday}</div>
<div className="text-xs text-gray-500">Sessions</div>
</div>
<div>
<div className="text-lg font-semibold text-gray-900">{agent.stats.avgResponseTime}</div>
<div className="text-xs text-gray-500">Avg. Zeit</div>
</div>
<div>
<div className="text-lg font-semibold text-gray-900">{agent.stats.successRate}</div>
<div className="text-xs text-gray-500">Erfolgsrate</div>
</div>
</div>
</Link>
))}
</div>
</div>
)
}

View File

@@ -0,0 +1,34 @@
'use client'
import React from 'react'
import Link from 'next/link'
export default function AgentSessionsPage() {
return (
<div className="p-8 max-w-5xl">
<div className="flex items-center gap-4 mb-8">
<Link href="/sdk/agents" className="text-gray-400 hover:text-gray-600 transition-colors">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
</Link>
<div>
<h1 className="text-2xl font-bold text-gray-900">Agent-Sessions</h1>
<p className="text-gray-500 mt-1">Chat-Verlaeufe und Session-Management</p>
</div>
</div>
<div className="bg-white border border-gray-200 rounded-xl p-12 text-center">
<svg className="w-20 h-20 text-gray-300 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5}
d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
<h2 className="text-xl font-medium text-gray-900 mb-2">Sessions-Tracking</h2>
<p className="text-gray-500 max-w-md mx-auto">
Das Session-Tracking fuer Compliance-Agenten wird in einer zukuenftigen Version implementiert.
Hier werden Chat-Verlaeufe, Antwortqualitaet und Nutzer-Feedback angezeigt.
</p>
</div>
</div>
)
}

View File

@@ -0,0 +1,34 @@
'use client'
import React from 'react'
import Link from 'next/link'
export default function AgentStatisticsPage() {
return (
<div className="p-8 max-w-5xl">
<div className="flex items-center gap-4 mb-8">
<Link href="/sdk/agents" className="text-gray-400 hover:text-gray-600 transition-colors">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
</Link>
<div>
<h1 className="text-2xl font-bold text-gray-900">Agent-Statistiken</h1>
<p className="text-gray-500 mt-1">Performance-Metriken und Nutzungsanalysen</p>
</div>
</div>
<div className="bg-white border border-gray-200 rounded-xl p-12 text-center">
<svg className="w-20 h-20 text-gray-300 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5}
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
<h2 className="text-xl font-medium text-gray-900 mb-2">Agent-Statistiken</h2>
<p className="text-gray-500 max-w-md mx-auto">
Detaillierte Statistiken wie Antwortzeiten, Erfolgsraten, haeufigste Themen und
RAG-Trefferquoten werden in einer zukuenftigen Version implementiert.
</p>
</div>
</div>
)
}