fix(admin-v2): Restore complete admin-v2 application
The admin-v2 application was incomplete in the repository. This commit restores all missing components: - Admin pages (76 pages): dashboard, ai, compliance, dsgvo, education, infrastructure, communication, development, onboarding, rbac - SDK pages (45 pages): tom, dsfa, vvt, loeschfristen, einwilligungen, vendor-compliance, tom-generator, dsr, and more - Developer portal (25 pages): API docs, SDK guides, frameworks - All components, lib files, hooks, and types - Updated package.json with all dependencies The issue was caused by incomplete initial repository state - the full admin-v2 codebase existed in backend/admin-v2 and docs-src/admin-v2 but was never fully synced to the main admin-v2 directory. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
275
admin-v2/app/api/admin/agents/[agentId]/route.ts
Normal file
275
admin-v2/app/api/admin/agents/[agentId]/route.ts
Normal file
@@ -0,0 +1,275 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import * as fs from 'fs/promises'
|
||||
import * as path from 'path'
|
||||
|
||||
/**
|
||||
* Individual Agent API
|
||||
*
|
||||
* GET - Get agent details including SOUL content
|
||||
* PUT - Update agent configuration
|
||||
* DELETE - Delete agent
|
||||
*/
|
||||
|
||||
const AGENT_CORE_PATH = process.env.AGENT_CORE_PATH || '/app/agent-core'
|
||||
const SOUL_PATH = path.join(AGENT_CORE_PATH, 'soul')
|
||||
|
||||
interface AgentDetails {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
soulFile: string
|
||||
soulContent: string
|
||||
color: string
|
||||
icon: string
|
||||
status: 'running' | 'paused' | 'stopped' | 'error'
|
||||
activeSessions: number
|
||||
totalProcessed: number
|
||||
avgResponseTime: number
|
||||
errorRate: number
|
||||
lastActivity: string
|
||||
version: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
// Agent metadata (in production, store in database)
|
||||
const agentMetadata: Record<string, Partial<AgentDetails>> = {
|
||||
'tutor-agent': {
|
||||
name: 'TutorAgent',
|
||||
description: 'Lernbegleitung und Fragen beantworten',
|
||||
color: '#3b82f6',
|
||||
icon: 'brain'
|
||||
},
|
||||
'grader-agent': {
|
||||
name: 'GraderAgent',
|
||||
description: 'Klausur-Korrektur und Bewertung',
|
||||
color: '#10b981',
|
||||
icon: 'bot'
|
||||
},
|
||||
'quality-judge': {
|
||||
name: 'QualityJudge',
|
||||
description: 'BQAS Qualitaetspruefung',
|
||||
color: '#f59e0b',
|
||||
icon: 'settings'
|
||||
},
|
||||
'alert-agent': {
|
||||
name: 'AlertAgent',
|
||||
description: 'Monitoring und Benachrichtigungen',
|
||||
color: '#ef4444',
|
||||
icon: 'alert'
|
||||
},
|
||||
'orchestrator': {
|
||||
name: 'Orchestrator',
|
||||
description: 'Task-Koordination und Routing',
|
||||
color: '#8b5cf6',
|
||||
icon: 'message'
|
||||
}
|
||||
}
|
||||
|
||||
// Read SOUL file content
|
||||
async function readSoulFile(agentId: string): Promise<string | null> {
|
||||
const soulFile = `${agentId}.soul.md`
|
||||
const soulFilePath = path.join(SOUL_PATH, soulFile)
|
||||
|
||||
try {
|
||||
const content = await fs.readFile(soulFilePath, 'utf-8')
|
||||
return content
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// Get file stats
|
||||
async function getFileStats(agentId: string): Promise<{ createdAt: string; updatedAt: string } | null> {
|
||||
const soulFile = `${agentId}.soul.md`
|
||||
const soulFilePath = path.join(SOUL_PATH, soulFile)
|
||||
|
||||
try {
|
||||
const stats = await fs.stat(soulFilePath)
|
||||
return {
|
||||
createdAt: stats.birthtime.toISOString(),
|
||||
updatedAt: stats.mtime.toISOString()
|
||||
}
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// Mock agent stats (in production, query from Redis/PostgreSQL)
|
||||
function getMockStats(agentId: string) {
|
||||
const mockStats: Record<string, { activeSessions: number; totalProcessed: number; avgResponseTime: number; errorRate: number }> = {
|
||||
'tutor-agent': { activeSessions: 12, totalProcessed: 1847, avgResponseTime: 234, errorRate: 0.3 },
|
||||
'grader-agent': { activeSessions: 3, totalProcessed: 456, avgResponseTime: 1205, errorRate: 0.5 },
|
||||
'quality-judge': { activeSessions: 8, totalProcessed: 3291, avgResponseTime: 89, errorRate: 0.1 },
|
||||
'alert-agent': { activeSessions: 1, totalProcessed: 892, avgResponseTime: 45, errorRate: 0.0 },
|
||||
'orchestrator': { activeSessions: 24, totalProcessed: 8934, avgResponseTime: 12, errorRate: 0.2 }
|
||||
}
|
||||
|
||||
return mockStats[agentId] || { activeSessions: 0, totalProcessed: 0, avgResponseTime: 0, errorRate: 0 }
|
||||
}
|
||||
|
||||
// GET - Get agent details
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ agentId: string }> }
|
||||
) {
|
||||
try {
|
||||
const { agentId } = await params
|
||||
|
||||
const soulContent = await readSoulFile(agentId)
|
||||
if (!soulContent) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Agent not found' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
const fileStats = await getFileStats(agentId)
|
||||
const metadata = agentMetadata[agentId] || {}
|
||||
const stats = getMockStats(agentId)
|
||||
|
||||
const agent: AgentDetails = {
|
||||
id: agentId,
|
||||
name: metadata.name || agentId.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(''),
|
||||
description: metadata.description || 'Custom agent',
|
||||
soulFile: `${agentId}.soul.md`,
|
||||
soulContent,
|
||||
color: metadata.color || '#6b7280',
|
||||
icon: metadata.icon || 'bot',
|
||||
status: 'running',
|
||||
...stats,
|
||||
lastActivity: 'just now',
|
||||
version: '1.0.0',
|
||||
createdAt: fileStats?.createdAt || new Date().toISOString(),
|
||||
updatedAt: fileStats?.updatedAt || new Date().toISOString()
|
||||
}
|
||||
|
||||
return NextResponse.json(agent)
|
||||
} catch (error) {
|
||||
console.error('Error fetching agent:', error)
|
||||
return NextResponse.json(
|
||||
{ error: error instanceof Error ? error.message : 'Failed to fetch agent' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// PUT - Update agent
|
||||
export async function PUT(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ agentId: string }> }
|
||||
) {
|
||||
try {
|
||||
const { agentId } = await params
|
||||
const body = await request.json()
|
||||
const { soulContent, name, description, color, icon } = body
|
||||
|
||||
const soulFile = `${agentId}.soul.md`
|
||||
const soulFilePath = path.join(SOUL_PATH, soulFile)
|
||||
|
||||
// Check if agent exists
|
||||
try {
|
||||
await fs.access(soulFilePath)
|
||||
} catch {
|
||||
return NextResponse.json(
|
||||
{ error: 'Agent not found' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
// Update SOUL file if content provided
|
||||
if (soulContent) {
|
||||
// Create backup before updating
|
||||
const backupPath = path.join(SOUL_PATH, '.backups', `${agentId}-${Date.now()}.soul.md`)
|
||||
try {
|
||||
await fs.mkdir(path.dirname(backupPath), { recursive: true })
|
||||
const currentContent = await fs.readFile(soulFilePath, 'utf-8')
|
||||
await fs.writeFile(backupPath, currentContent, 'utf-8')
|
||||
} catch {
|
||||
// Backup failed, continue anyway
|
||||
}
|
||||
|
||||
// Write new content
|
||||
await fs.writeFile(soulFilePath, soulContent, 'utf-8')
|
||||
}
|
||||
|
||||
// In production, update metadata in database
|
||||
// For now, just return success
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: `Agent ${agentId} updated successfully`,
|
||||
agent: {
|
||||
id: agentId,
|
||||
name: name || agentMetadata[agentId]?.name || agentId,
|
||||
description: description || agentMetadata[agentId]?.description || '',
|
||||
soulFile,
|
||||
color: color || agentMetadata[agentId]?.color || '#6b7280',
|
||||
icon: icon || agentMetadata[agentId]?.icon || 'bot',
|
||||
updatedAt: new Date().toISOString()
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error updating agent:', error)
|
||||
return NextResponse.json(
|
||||
{ error: error instanceof Error ? error.message : 'Failed to update agent' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE - Delete agent
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ agentId: string }> }
|
||||
) {
|
||||
try {
|
||||
const { agentId } = await params
|
||||
|
||||
// Don't allow deleting core agents
|
||||
const coreAgents = ['tutor-agent', 'grader-agent', 'quality-judge', 'alert-agent', 'orchestrator']
|
||||
if (coreAgents.includes(agentId)) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Cannot delete core system agent' },
|
||||
{ status: 403 }
|
||||
)
|
||||
}
|
||||
|
||||
const soulFile = `${agentId}.soul.md`
|
||||
const soulFilePath = path.join(SOUL_PATH, soulFile)
|
||||
|
||||
// Check if agent exists
|
||||
try {
|
||||
await fs.access(soulFilePath)
|
||||
} catch {
|
||||
return NextResponse.json(
|
||||
{ error: 'Agent not found' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
// Create backup before deleting
|
||||
const backupPath = path.join(SOUL_PATH, '.deleted', `${agentId}-${Date.now()}.soul.md`)
|
||||
try {
|
||||
await fs.mkdir(path.dirname(backupPath), { recursive: true })
|
||||
const content = await fs.readFile(soulFilePath, 'utf-8')
|
||||
await fs.writeFile(backupPath, content, 'utf-8')
|
||||
} catch {
|
||||
// Backup failed, continue anyway
|
||||
}
|
||||
|
||||
// Delete the file
|
||||
await fs.unlink(soulFilePath)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: `Agent ${agentId} deleted successfully`
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error deleting agent:', error)
|
||||
return NextResponse.json(
|
||||
{ error: error instanceof Error ? error.message : 'Failed to delete agent' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
187
admin-v2/app/api/admin/agents/[agentId]/soul/route.ts
Normal file
187
admin-v2/app/api/admin/agents/[agentId]/soul/route.ts
Normal file
@@ -0,0 +1,187 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import * as fs from 'fs/promises'
|
||||
import * as path from 'path'
|
||||
|
||||
/**
|
||||
* Agent SOUL File API
|
||||
*
|
||||
* GET - Get SOUL file content
|
||||
* PUT - Update SOUL file content
|
||||
* GET /history - Get version history
|
||||
*/
|
||||
|
||||
const AGENT_CORE_PATH = process.env.AGENT_CORE_PATH || '/app/agent-core'
|
||||
const SOUL_PATH = path.join(AGENT_CORE_PATH, 'soul')
|
||||
|
||||
interface SoulVersion {
|
||||
version: string
|
||||
timestamp: string
|
||||
content: string
|
||||
author: string
|
||||
changes: string
|
||||
}
|
||||
|
||||
// Read SOUL file content
|
||||
async function readSoulFile(agentId: string): Promise<string | null> {
|
||||
const soulFile = `${agentId}.soul.md`
|
||||
const soulFilePath = path.join(SOUL_PATH, soulFile)
|
||||
|
||||
try {
|
||||
const content = await fs.readFile(soulFilePath, 'utf-8')
|
||||
return content
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// Get version history from backup directory
|
||||
async function getVersionHistory(agentId: string): Promise<SoulVersion[]> {
|
||||
const backupDir = path.join(SOUL_PATH, '.backups')
|
||||
const versions: SoulVersion[] = []
|
||||
|
||||
try {
|
||||
const files = await fs.readdir(backupDir)
|
||||
const agentBackups = files.filter(f => f.startsWith(`${agentId}-`) && f.endsWith('.soul.md'))
|
||||
|
||||
for (const file of agentBackups.slice(-10)) { // Last 10 versions
|
||||
const filePath = path.join(backupDir, file)
|
||||
const stats = await fs.stat(filePath)
|
||||
const content = await fs.readFile(filePath, 'utf-8')
|
||||
|
||||
// Extract timestamp from filename
|
||||
const match = file.match(/-(\d+)\.soul\.md$/)
|
||||
const timestamp = match ? new Date(parseInt(match[1])).toISOString() : stats.mtime.toISOString()
|
||||
|
||||
versions.push({
|
||||
version: file.replace('.soul.md', ''),
|
||||
timestamp,
|
||||
content,
|
||||
author: 'Admin',
|
||||
changes: 'Manual update'
|
||||
})
|
||||
}
|
||||
|
||||
// Sort by timestamp descending
|
||||
versions.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
|
||||
} catch {
|
||||
// No backup directory or can't read
|
||||
}
|
||||
|
||||
return versions
|
||||
}
|
||||
|
||||
// GET - Get SOUL file content
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ agentId: string }> }
|
||||
) {
|
||||
try {
|
||||
const { agentId } = await params
|
||||
const url = new URL(request.url)
|
||||
const includeHistory = url.searchParams.get('history') === 'true'
|
||||
|
||||
const content = await readSoulFile(agentId)
|
||||
if (!content) {
|
||||
return NextResponse.json(
|
||||
{ error: 'SOUL file not found' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
const soulFile = `${agentId}.soul.md`
|
||||
const soulFilePath = path.join(SOUL_PATH, soulFile)
|
||||
const stats = await fs.stat(soulFilePath)
|
||||
|
||||
const response: {
|
||||
agentId: string
|
||||
soulFile: string
|
||||
content: string
|
||||
updatedAt: string
|
||||
size: number
|
||||
history?: SoulVersion[]
|
||||
} = {
|
||||
agentId,
|
||||
soulFile,
|
||||
content,
|
||||
updatedAt: stats.mtime.toISOString(),
|
||||
size: stats.size
|
||||
}
|
||||
|
||||
if (includeHistory) {
|
||||
response.history = await getVersionHistory(agentId)
|
||||
}
|
||||
|
||||
return NextResponse.json(response)
|
||||
} catch (error) {
|
||||
console.error('Error fetching SOUL file:', error)
|
||||
return NextResponse.json(
|
||||
{ error: error instanceof Error ? error.message : 'Failed to fetch SOUL file' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// PUT - Update SOUL file content
|
||||
export async function PUT(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ agentId: string }> }
|
||||
) {
|
||||
try {
|
||||
const { agentId } = await params
|
||||
const body = await request.json()
|
||||
const { content, author, changeDescription } = body
|
||||
|
||||
if (!content) {
|
||||
return NextResponse.json(
|
||||
{ error: 'content is required' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const soulFile = `${agentId}.soul.md`
|
||||
const soulFilePath = path.join(SOUL_PATH, soulFile)
|
||||
|
||||
// Check if file exists
|
||||
try {
|
||||
await fs.access(soulFilePath)
|
||||
} catch {
|
||||
return NextResponse.json(
|
||||
{ error: 'SOUL file not found' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
// Create backup before updating
|
||||
const backupDir = path.join(SOUL_PATH, '.backups')
|
||||
const backupPath = path.join(backupDir, `${agentId}-${Date.now()}.soul.md`)
|
||||
|
||||
try {
|
||||
await fs.mkdir(backupDir, { recursive: true })
|
||||
const currentContent = await fs.readFile(soulFilePath, 'utf-8')
|
||||
await fs.writeFile(backupPath, currentContent, 'utf-8')
|
||||
} catch (backupError) {
|
||||
console.warn('Failed to create backup:', backupError)
|
||||
}
|
||||
|
||||
// Write new content
|
||||
await fs.writeFile(soulFilePath, content, 'utf-8')
|
||||
const stats = await fs.stat(soulFilePath)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: `SOUL file for ${agentId} updated successfully`,
|
||||
agentId,
|
||||
soulFile,
|
||||
updatedAt: stats.mtime.toISOString(),
|
||||
size: stats.size,
|
||||
author: author || 'Admin',
|
||||
changeDescription: changeDescription || 'Manual update'
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error updating SOUL file:', error)
|
||||
return NextResponse.json(
|
||||
{ error: error instanceof Error ? error.message : 'Failed to update SOUL file' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
282
admin-v2/app/api/admin/agents/route.ts
Normal file
282
admin-v2/app/api/admin/agents/route.ts
Normal file
@@ -0,0 +1,282 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import * as fs from 'fs/promises'
|
||||
import * as path from 'path'
|
||||
|
||||
/**
|
||||
* Agent Management API
|
||||
*
|
||||
* GET - List all agents with their status and configuration
|
||||
* POST - Create a new agent (with SOUL file)
|
||||
*/
|
||||
|
||||
const AGENT_CORE_PATH = process.env.AGENT_CORE_PATH || '/app/agent-core'
|
||||
const SOUL_PATH = path.join(AGENT_CORE_PATH, 'soul')
|
||||
|
||||
interface AgentConfig {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
soulFile: string
|
||||
color: string
|
||||
icon: string
|
||||
status: 'running' | 'paused' | 'stopped' | 'error'
|
||||
activeSessions: number
|
||||
totalProcessed: number
|
||||
avgResponseTime: number
|
||||
lastActivity: string
|
||||
version: string
|
||||
}
|
||||
|
||||
interface AgentStats {
|
||||
totalSessions: number
|
||||
activeSessions: number
|
||||
totalMessages: number
|
||||
avgLatency: number
|
||||
errorRate: number
|
||||
memoryUsage: number
|
||||
}
|
||||
|
||||
// Default agent configurations
|
||||
const defaultAgents: AgentConfig[] = [
|
||||
{
|
||||
id: 'tutor-agent',
|
||||
name: 'TutorAgent',
|
||||
description: 'Lernbegleitung und Fragen beantworten',
|
||||
soulFile: 'tutor-agent.soul.md',
|
||||
color: '#3b82f6',
|
||||
icon: 'brain',
|
||||
status: 'running',
|
||||
activeSessions: 0,
|
||||
totalProcessed: 0,
|
||||
avgResponseTime: 0,
|
||||
lastActivity: new Date().toISOString(),
|
||||
version: '1.0.0'
|
||||
},
|
||||
{
|
||||
id: 'grader-agent',
|
||||
name: 'GraderAgent',
|
||||
description: 'Klausur-Korrektur und Bewertung',
|
||||
soulFile: 'grader-agent.soul.md',
|
||||
color: '#10b981',
|
||||
icon: 'bot',
|
||||
status: 'running',
|
||||
activeSessions: 0,
|
||||
totalProcessed: 0,
|
||||
avgResponseTime: 0,
|
||||
lastActivity: new Date().toISOString(),
|
||||
version: '1.0.0'
|
||||
},
|
||||
{
|
||||
id: 'quality-judge',
|
||||
name: 'QualityJudge',
|
||||
description: 'BQAS Qualitaetspruefung',
|
||||
soulFile: 'quality-judge.soul.md',
|
||||
color: '#f59e0b',
|
||||
icon: 'settings',
|
||||
status: 'running',
|
||||
activeSessions: 0,
|
||||
totalProcessed: 0,
|
||||
avgResponseTime: 0,
|
||||
lastActivity: new Date().toISOString(),
|
||||
version: '1.0.0'
|
||||
},
|
||||
{
|
||||
id: 'alert-agent',
|
||||
name: 'AlertAgent',
|
||||
description: 'Monitoring und Benachrichtigungen',
|
||||
soulFile: 'alert-agent.soul.md',
|
||||
color: '#ef4444',
|
||||
icon: 'alert',
|
||||
status: 'running',
|
||||
activeSessions: 0,
|
||||
totalProcessed: 0,
|
||||
avgResponseTime: 0,
|
||||
lastActivity: new Date().toISOString(),
|
||||
version: '1.0.0'
|
||||
},
|
||||
{
|
||||
id: 'orchestrator',
|
||||
name: 'Orchestrator',
|
||||
description: 'Task-Koordination und Routing',
|
||||
soulFile: 'orchestrator.soul.md',
|
||||
color: '#8b5cf6',
|
||||
icon: 'message',
|
||||
status: 'running',
|
||||
activeSessions: 0,
|
||||
totalProcessed: 0,
|
||||
avgResponseTime: 0,
|
||||
lastActivity: new Date().toISOString(),
|
||||
version: '1.0.0'
|
||||
}
|
||||
]
|
||||
|
||||
// Check if SOUL file exists
|
||||
async function checkSoulFile(filename: string): Promise<boolean> {
|
||||
try {
|
||||
await fs.access(path.join(SOUL_PATH, filename))
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Get agent status from Redis/API (mock for now)
|
||||
async function getAgentStatus(agentId: string): Promise<{
|
||||
status: 'running' | 'paused' | 'stopped' | 'error'
|
||||
activeSessions: number
|
||||
totalProcessed: number
|
||||
avgResponseTime: number
|
||||
lastActivity: string
|
||||
}> {
|
||||
// In production, query Redis/PostgreSQL for real stats
|
||||
// For now, return mock data with some variation
|
||||
const mockStats = {
|
||||
'tutor-agent': { activeSessions: 12, totalProcessed: 1847, avgResponseTime: 234, lastActivity: '2 min ago' },
|
||||
'grader-agent': { activeSessions: 3, totalProcessed: 456, avgResponseTime: 1205, lastActivity: '5 min ago' },
|
||||
'quality-judge': { activeSessions: 8, totalProcessed: 3291, avgResponseTime: 89, lastActivity: '1 min ago' },
|
||||
'alert-agent': { activeSessions: 1, totalProcessed: 892, avgResponseTime: 45, lastActivity: '30 sec ago' },
|
||||
'orchestrator': { activeSessions: 24, totalProcessed: 8934, avgResponseTime: 12, lastActivity: 'just now' }
|
||||
}
|
||||
|
||||
const stats = mockStats[agentId as keyof typeof mockStats] || {
|
||||
activeSessions: 0,
|
||||
totalProcessed: 0,
|
||||
avgResponseTime: 0,
|
||||
lastActivity: 'never'
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'running',
|
||||
...stats
|
||||
}
|
||||
}
|
||||
|
||||
// GET - List all agents
|
||||
export async function GET() {
|
||||
try {
|
||||
const agents: AgentConfig[] = []
|
||||
|
||||
for (const defaultAgent of defaultAgents) {
|
||||
const soulExists = await checkSoulFile(defaultAgent.soulFile)
|
||||
const status = await getAgentStatus(defaultAgent.id)
|
||||
|
||||
agents.push({
|
||||
...defaultAgent,
|
||||
...status,
|
||||
status: soulExists ? status.status : 'error'
|
||||
})
|
||||
}
|
||||
|
||||
// Also check for any additional SOUL files
|
||||
try {
|
||||
const soulFiles = await fs.readdir(SOUL_PATH)
|
||||
for (const file of soulFiles) {
|
||||
if (file.endsWith('.soul.md')) {
|
||||
const agentId = file.replace('.soul.md', '')
|
||||
if (!agents.find(a => a.id === agentId)) {
|
||||
const status = await getAgentStatus(agentId)
|
||||
agents.push({
|
||||
id: agentId,
|
||||
name: agentId.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(''),
|
||||
description: 'Custom agent',
|
||||
soulFile: file,
|
||||
color: '#6b7280',
|
||||
icon: 'bot',
|
||||
version: '1.0.0',
|
||||
...status
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// SOUL directory doesn't exist or isn't accessible
|
||||
}
|
||||
|
||||
// Calculate aggregate stats
|
||||
const stats: AgentStats = {
|
||||
totalSessions: agents.reduce((sum, a) => sum + a.totalProcessed, 0),
|
||||
activeSessions: agents.reduce((sum, a) => sum + a.activeSessions, 0),
|
||||
totalMessages: agents.reduce((sum, a) => sum + a.totalProcessed, 0) * 3, // estimate
|
||||
avgLatency: Math.round(agents.reduce((sum, a) => sum + a.avgResponseTime, 0) / agents.length),
|
||||
errorRate: 0.8, // mock
|
||||
memoryUsage: 67 // mock
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
agents,
|
||||
stats,
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error fetching agents:', error)
|
||||
return NextResponse.json(
|
||||
{ error: error instanceof Error ? error.message : 'Failed to fetch agents' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// POST - Create new agent
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { id, name, description, soulContent, color, icon } = body
|
||||
|
||||
if (!id || !name || !soulContent) {
|
||||
return NextResponse.json(
|
||||
{ error: 'id, name, and soulContent are required' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Validate agent ID format
|
||||
if (!/^[a-z0-9-]+$/.test(id)) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Agent ID must contain only lowercase letters, numbers, and hyphens' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const soulFile = `${id}.soul.md`
|
||||
const soulFilePath = path.join(SOUL_PATH, soulFile)
|
||||
|
||||
// Check if agent already exists
|
||||
const exists = await checkSoulFile(soulFile)
|
||||
if (exists) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Agent with this ID already exists' },
|
||||
{ status: 409 }
|
||||
)
|
||||
}
|
||||
|
||||
// Create SOUL file
|
||||
await fs.writeFile(soulFilePath, soulContent, 'utf-8')
|
||||
|
||||
const newAgent: AgentConfig = {
|
||||
id,
|
||||
name,
|
||||
description: description || '',
|
||||
soulFile,
|
||||
color: color || '#6b7280',
|
||||
icon: icon || 'bot',
|
||||
status: 'stopped',
|
||||
activeSessions: 0,
|
||||
totalProcessed: 0,
|
||||
avgResponseTime: 0,
|
||||
lastActivity: new Date().toISOString(),
|
||||
version: '1.0.0'
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
agent: newAgent,
|
||||
message: `Agent ${name} created successfully`
|
||||
}, { status: 201 })
|
||||
} catch (error) {
|
||||
console.error('Error creating agent:', error)
|
||||
return NextResponse.json(
|
||||
{ error: error instanceof Error ? error.message : 'Failed to create agent' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
240
admin-v2/app/api/admin/agents/sessions/route.ts
Normal file
240
admin-v2/app/api/admin/agents/sessions/route.ts
Normal file
@@ -0,0 +1,240 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
|
||||
/**
|
||||
* Agent Sessions API
|
||||
*
|
||||
* GET - List all active agent sessions
|
||||
* POST - Create/start a new session (for testing)
|
||||
*/
|
||||
|
||||
interface AgentSession {
|
||||
id: string
|
||||
agentType: string
|
||||
agentId: string
|
||||
userId: string
|
||||
userName: string
|
||||
state: 'active' | 'paused' | 'completed' | 'failed'
|
||||
createdAt: string
|
||||
lastActivity: string
|
||||
checkpointCount: number
|
||||
messagesProcessed: number
|
||||
currentTask: string | null
|
||||
avgResponseTime: number
|
||||
}
|
||||
|
||||
// Mock sessions data (in production, query from PostgreSQL/Redis)
|
||||
function getMockSessions(): AgentSession[] {
|
||||
const now = new Date()
|
||||
return [
|
||||
{
|
||||
id: `session-${Date.now()}-001`,
|
||||
agentType: 'tutor-agent',
|
||||
agentId: 'tutor-1',
|
||||
userId: 'user-123',
|
||||
userName: 'Max Mustermann',
|
||||
state: 'active',
|
||||
createdAt: new Date(now.getTime() - 75 * 60000).toISOString(),
|
||||
lastActivity: new Date(now.getTime() - 37000).toISOString(),
|
||||
checkpointCount: 5,
|
||||
messagesProcessed: 23,
|
||||
currentTask: 'Erklaere Quadratische Funktionen',
|
||||
avgResponseTime: 245
|
||||
},
|
||||
{
|
||||
id: `session-${Date.now()}-002`,
|
||||
agentType: 'tutor-agent',
|
||||
agentId: 'tutor-2',
|
||||
userId: 'user-456',
|
||||
userName: 'Anna Schmidt',
|
||||
state: 'active',
|
||||
createdAt: new Date(now.getTime() - 45 * 60000).toISOString(),
|
||||
lastActivity: new Date(now.getTime() - 108000).toISOString(),
|
||||
checkpointCount: 3,
|
||||
messagesProcessed: 12,
|
||||
currentTask: 'Hilfe bei Gedichtanalyse',
|
||||
avgResponseTime: 312
|
||||
},
|
||||
{
|
||||
id: `session-${Date.now()}-003`,
|
||||
agentType: 'grader-agent',
|
||||
agentId: 'grader-1',
|
||||
userId: 'user-789',
|
||||
userName: 'Frau Mueller (Lehrerin)',
|
||||
state: 'active',
|
||||
createdAt: new Date(now.getTime() - 105 * 60000).toISOString(),
|
||||
lastActivity: new Date(now.getTime() - 180000).toISOString(),
|
||||
checkpointCount: 12,
|
||||
messagesProcessed: 45,
|
||||
currentTask: 'Korrektur Klausur 10b - Arbeit 7/24',
|
||||
avgResponseTime: 1205
|
||||
},
|
||||
{
|
||||
id: `session-${Date.now()}-004`,
|
||||
agentType: 'quality-judge',
|
||||
agentId: 'judge-1',
|
||||
userId: 'system',
|
||||
userName: 'System (BQAS)',
|
||||
state: 'active',
|
||||
createdAt: new Date(now.getTime() - 465 * 60000).toISOString(),
|
||||
lastActivity: new Date(now.getTime() - 1000).toISOString(),
|
||||
checkpointCount: 156,
|
||||
messagesProcessed: 892,
|
||||
currentTask: 'Quality Check Queue Processing',
|
||||
avgResponseTime: 89
|
||||
},
|
||||
{
|
||||
id: `session-${Date.now()}-005`,
|
||||
agentType: 'orchestrator',
|
||||
agentId: 'orchestrator-main',
|
||||
userId: 'system',
|
||||
userName: 'System',
|
||||
state: 'active',
|
||||
createdAt: new Date(now.getTime() - 945 * 60000).toISOString(),
|
||||
lastActivity: now.toISOString(),
|
||||
checkpointCount: 2341,
|
||||
messagesProcessed: 8934,
|
||||
currentTask: 'Routing incoming requests',
|
||||
avgResponseTime: 12
|
||||
},
|
||||
{
|
||||
id: `session-${Date.now()}-006`,
|
||||
agentType: 'tutor-agent',
|
||||
agentId: 'tutor-3',
|
||||
userId: 'user-101',
|
||||
userName: 'Tim Berger',
|
||||
state: 'paused',
|
||||
createdAt: new Date(now.getTime() - 150 * 60000).toISOString(),
|
||||
lastActivity: new Date(now.getTime() - 90 * 60000).toISOString(),
|
||||
checkpointCount: 8,
|
||||
messagesProcessed: 34,
|
||||
currentTask: null,
|
||||
avgResponseTime: 278
|
||||
},
|
||||
{
|
||||
id: `session-${Date.now()}-007`,
|
||||
agentType: 'grader-agent',
|
||||
agentId: 'grader-2',
|
||||
userId: 'user-202',
|
||||
userName: 'Herr Weber (Lehrer)',
|
||||
state: 'completed',
|
||||
createdAt: new Date(now.getTime() - 345 * 60000).toISOString(),
|
||||
lastActivity: new Date(now.getTime() - 225 * 60000).toISOString(),
|
||||
checkpointCount: 24,
|
||||
messagesProcessed: 120,
|
||||
currentTask: null,
|
||||
avgResponseTime: 1102
|
||||
},
|
||||
{
|
||||
id: `session-${Date.now()}-008`,
|
||||
agentType: 'alert-agent',
|
||||
agentId: 'alert-1',
|
||||
userId: 'system',
|
||||
userName: 'System (Monitoring)',
|
||||
state: 'active',
|
||||
createdAt: new Date(now.getTime() - 945 * 60000).toISOString(),
|
||||
lastActivity: new Date(now.getTime() - 2000).toISOString(),
|
||||
checkpointCount: 48,
|
||||
messagesProcessed: 256,
|
||||
currentTask: 'Monitoring System Health',
|
||||
avgResponseTime: 45
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// GET - List all sessions
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const url = new URL(request.url)
|
||||
const state = url.searchParams.get('state')
|
||||
const agentType = url.searchParams.get('agentType')
|
||||
const limit = parseInt(url.searchParams.get('limit') || '100')
|
||||
const offset = parseInt(url.searchParams.get('offset') || '0')
|
||||
|
||||
let sessions = getMockSessions()
|
||||
|
||||
// Apply filters
|
||||
if (state) {
|
||||
sessions = sessions.filter(s => s.state === state)
|
||||
}
|
||||
if (agentType) {
|
||||
sessions = sessions.filter(s => s.agentType === agentType)
|
||||
}
|
||||
|
||||
// Calculate stats
|
||||
const stats = {
|
||||
total: sessions.length,
|
||||
active: sessions.filter(s => s.state === 'active').length,
|
||||
paused: sessions.filter(s => s.state === 'paused').length,
|
||||
completed: sessions.filter(s => s.state === 'completed').length,
|
||||
failed: sessions.filter(s => s.state === 'failed').length,
|
||||
totalMessages: sessions.reduce((sum, s) => sum + s.messagesProcessed, 0),
|
||||
avgResponseTime: Math.round(
|
||||
sessions.reduce((sum, s) => sum + s.avgResponseTime, 0) / sessions.length
|
||||
)
|
||||
}
|
||||
|
||||
// Apply pagination
|
||||
const paginatedSessions = sessions.slice(offset, offset + limit)
|
||||
|
||||
return NextResponse.json({
|
||||
sessions: paginatedSessions,
|
||||
stats,
|
||||
pagination: {
|
||||
total: sessions.length,
|
||||
limit,
|
||||
offset,
|
||||
hasMore: offset + limit < sessions.length
|
||||
},
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error fetching sessions:', error)
|
||||
return NextResponse.json(
|
||||
{ error: error instanceof Error ? error.message : 'Failed to fetch sessions' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// POST - Create new session (for testing)
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { agentType, userId, userName, context } = body
|
||||
|
||||
if (!agentType || !userId) {
|
||||
return NextResponse.json(
|
||||
{ error: 'agentType and userId are required' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// In production, create session via agent-core SessionManager
|
||||
const newSession: AgentSession = {
|
||||
id: `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||
agentType,
|
||||
agentId: `${agentType.replace('-agent', '')}-${Math.floor(Math.random() * 100)}`,
|
||||
userId,
|
||||
userName: userName || userId,
|
||||
state: 'active',
|
||||
createdAt: new Date().toISOString(),
|
||||
lastActivity: new Date().toISOString(),
|
||||
checkpointCount: 0,
|
||||
messagesProcessed: 0,
|
||||
currentTask: context?.task || null,
|
||||
avgResponseTime: 0
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
session: newSession,
|
||||
message: 'Session created successfully'
|
||||
}, { status: 201 })
|
||||
} catch (error) {
|
||||
console.error('Error creating session:', error)
|
||||
return NextResponse.json(
|
||||
{ error: error instanceof Error ? error.message : 'Failed to create session' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
208
admin-v2/app/api/admin/agents/statistics/route.ts
Normal file
208
admin-v2/app/api/admin/agents/statistics/route.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
|
||||
/**
|
||||
* Agent Statistics API
|
||||
*
|
||||
* GET - Get aggregated statistics for all agents
|
||||
*/
|
||||
|
||||
interface AgentMetric {
|
||||
agentType: string
|
||||
name: string
|
||||
color: string
|
||||
sessions: number
|
||||
messagesProcessed: number
|
||||
avgResponseTime: number
|
||||
errorRate: number
|
||||
successRate: number
|
||||
trend: 'up' | 'down' | 'stable'
|
||||
trendValue: number
|
||||
}
|
||||
|
||||
interface DailyStats {
|
||||
date: string
|
||||
sessions: number
|
||||
messages: number
|
||||
errors: number
|
||||
avgLatency: number
|
||||
}
|
||||
|
||||
interface HourlyLatency {
|
||||
timestamp: string
|
||||
value: number
|
||||
}
|
||||
|
||||
// Mock agent metrics
|
||||
function getAgentMetrics(): AgentMetric[] {
|
||||
return [
|
||||
{
|
||||
agentType: 'tutor-agent',
|
||||
name: 'TutorAgent',
|
||||
color: '#3b82f6',
|
||||
sessions: 156,
|
||||
messagesProcessed: 4521,
|
||||
avgResponseTime: 234,
|
||||
errorRate: 0.3,
|
||||
successRate: 99.7,
|
||||
trend: 'up',
|
||||
trendValue: 12
|
||||
},
|
||||
{
|
||||
agentType: 'grader-agent',
|
||||
name: 'GraderAgent',
|
||||
color: '#10b981',
|
||||
sessions: 45,
|
||||
messagesProcessed: 1205,
|
||||
avgResponseTime: 1102,
|
||||
errorRate: 0.5,
|
||||
successRate: 99.5,
|
||||
trend: 'stable',
|
||||
trendValue: 2
|
||||
},
|
||||
{
|
||||
agentType: 'quality-judge',
|
||||
name: 'QualityJudge',
|
||||
color: '#f59e0b',
|
||||
sessions: 89,
|
||||
messagesProcessed: 8934,
|
||||
avgResponseTime: 89,
|
||||
errorRate: 0.1,
|
||||
successRate: 99.9,
|
||||
trend: 'up',
|
||||
trendValue: 8
|
||||
},
|
||||
{
|
||||
agentType: 'alert-agent',
|
||||
name: 'AlertAgent',
|
||||
color: '#ef4444',
|
||||
sessions: 12,
|
||||
messagesProcessed: 892,
|
||||
avgResponseTime: 45,
|
||||
errorRate: 0.0,
|
||||
successRate: 100,
|
||||
trend: 'stable',
|
||||
trendValue: 0
|
||||
},
|
||||
{
|
||||
agentType: 'orchestrator',
|
||||
name: 'Orchestrator',
|
||||
color: '#8b5cf6',
|
||||
sessions: 234,
|
||||
messagesProcessed: 15420,
|
||||
avgResponseTime: 12,
|
||||
errorRate: 0.2,
|
||||
successRate: 99.8,
|
||||
trend: 'up',
|
||||
trendValue: 15
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// Generate mock daily stats for the last N days
|
||||
function getDailyStats(days: number): DailyStats[] {
|
||||
const stats: DailyStats[] = []
|
||||
const now = new Date()
|
||||
|
||||
for (let i = days - 1; i >= 0; i--) {
|
||||
const date = new Date(now)
|
||||
date.setDate(date.getDate() - i)
|
||||
|
||||
stats.push({
|
||||
date: date.toISOString().split('T')[0],
|
||||
sessions: 400 + Math.floor(Math.random() * 150),
|
||||
messages: 11000 + Math.floor(Math.random() * 5000),
|
||||
errors: 8 + Math.floor(Math.random() * 12),
|
||||
avgLatency: 140 + Math.floor(Math.random() * 25)
|
||||
})
|
||||
}
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
// Generate hourly latency data for the last 24 hours
|
||||
function getHourlyLatency(): HourlyLatency[] {
|
||||
const data: HourlyLatency[] = []
|
||||
|
||||
for (let i = 0; i < 24; i++) {
|
||||
data.push({
|
||||
timestamp: `${i.toString().padStart(2, '0')}:00`,
|
||||
value: 100 + Math.floor(Math.random() * 100)
|
||||
})
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// GET - Get statistics
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const url = new URL(request.url)
|
||||
const timeRange = url.searchParams.get('range') || '7d'
|
||||
|
||||
// Determine days based on time range
|
||||
const days = timeRange === '24h' ? 1 : timeRange === '7d' ? 7 : 30
|
||||
|
||||
const agentMetrics = getAgentMetrics()
|
||||
const dailyStats = getDailyStats(days)
|
||||
const hourlyLatency = getHourlyLatency()
|
||||
|
||||
// Calculate totals
|
||||
const totals = {
|
||||
sessions: agentMetrics.reduce((sum, m) => sum + m.sessions, 0),
|
||||
messages: agentMetrics.reduce((sum, m) => sum + m.messagesProcessed, 0),
|
||||
avgLatency: Math.round(
|
||||
agentMetrics.reduce((sum, m) => sum + m.avgResponseTime, 0) / agentMetrics.length
|
||||
),
|
||||
avgErrorRate: parseFloat(
|
||||
(agentMetrics.reduce((sum, m) => sum + m.errorRate, 0) / agentMetrics.length).toFixed(2)
|
||||
)
|
||||
}
|
||||
|
||||
// Calculate week totals from daily stats
|
||||
const weekTotals = {
|
||||
sessions: dailyStats.reduce((sum, d) => sum + d.sessions, 0),
|
||||
messages: dailyStats.reduce((sum, d) => sum + d.messages, 0),
|
||||
errors: dailyStats.reduce((sum, d) => sum + d.errors, 0),
|
||||
avgLatency: Math.round(
|
||||
dailyStats.reduce((sum, d) => sum + d.avgLatency, 0) / dailyStats.length
|
||||
)
|
||||
}
|
||||
|
||||
// Calculate trends
|
||||
const trends = {
|
||||
sessions: {
|
||||
value: 12,
|
||||
direction: 'up' as const
|
||||
},
|
||||
messages: {
|
||||
value: 8,
|
||||
direction: 'up' as const
|
||||
},
|
||||
latency: {
|
||||
value: 5,
|
||||
direction: 'down' as const // down is good for latency
|
||||
},
|
||||
errors: {
|
||||
value: 3,
|
||||
direction: 'up' as const
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
agentMetrics,
|
||||
dailyStats,
|
||||
hourlyLatency,
|
||||
totals,
|
||||
weekTotals,
|
||||
trends,
|
||||
timeRange,
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error fetching statistics:', error)
|
||||
return NextResponse.json(
|
||||
{ error: error instanceof Error ? error.message : 'Failed to fetch statistics' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user