From 358ec3e65d92b9740abd486c5b856cb7f51e898c Mon Sep 17 00:00:00 2001 From: Reviewer Date: Sat, 13 Jun 2026 20:58:53 +0200 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20Dashboard=20V2=20=E2=80=94=20?= =?UTF-8?q?FlowCanvas,=20AgentModal,=20IrisChat,=20Stores,=20PlaceholderVi?= =?UTF-8?q?ews,=20Icon-Library?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/settings.local.json | 8 ++ frontend/src/constants/agentPool.ts | 76 +++++++++++++++++ frontend/src/mappers/agentMapper.ts | 123 ++++++++++++++++++++++++++++ frontend/src/mappers/taskMapper.ts | 35 ++++++++ 4 files changed, 242 insertions(+) create mode 100644 .claude/settings.local.json create mode 100644 frontend/src/constants/agentPool.ts create mode 100644 frontend/src/mappers/agentMapper.ts create mode 100644 frontend/src/mappers/taskMapper.ts diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..bbf6524 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(npx tsc *)", + "Bash(npx vite *)" + ] + } +} diff --git a/frontend/src/constants/agentPool.ts b/frontend/src/constants/agentPool.ts new file mode 100644 index 0000000..3144760 --- /dev/null +++ b/frontend/src/constants/agentPool.ts @@ -0,0 +1,76 @@ +import type { AgentNodeData } from '../types/agentNode' + +export const EXTRA_AGENT_POOL: AgentNodeData[] = [ + { + id: 'qa', + name: 'QA Automator', + role: 'Test Automation', + roleBadge: 'badge-cyan', + avatar: 'QA', + status: 'idle', + statusLabel: 'Bereit', + task: 'End-to-End Tests schreiben', + goal: '100% Coverage für auth/', + progress: 0, + elapsed: '—', + next: 'Testplan erstellen', + model: 'Deepseek V4 Flash', + tokens: '0', + cost: '0.00', + think: null, + }, + { + id: 'devops', + name: 'DevOps', + role: 'CI/CD Pipeline', + roleBadge: 'badge-amber', + avatar: 'DO', + status: 'idle', + statusLabel: 'Bereit', + task: 'GitHub Actions Workflow', + goal: 'Automatisches Deploy auf merge', + progress: 0, + elapsed: '—', + next: 'Pipeline konfigurieren', + model: 'Deepseek V4 Pro', + tokens: '0', + cost: '0.00', + think: null, + }, + { + id: 'security', + name: 'Security Scanner', + role: 'Security Analysis', + roleBadge: 'badge-rose', + avatar: 'SC', + status: 'think', + statusLabel: 'Scannt', + task: 'Dependency-Audit durchführen', + goal: 'CVEs in api/ aufdecken', + progress: 18, + elapsed: '00:01:44', + next: 'Report an Iris', + model: 'Deepseek V4 Pro', + tokens: '9k', + cost: '0.18', + think: 'Analysiere package-lock.json auf bekannte Vulnerabilities…', + }, + { + id: 'pm', + name: 'Project Manager', + role: 'Coordination', + roleBadge: 'badge-purple', + avatar: 'PM', + status: 'think', + statusLabel: 'Plant', + task: 'Sprint-Retrospektive vorbereiten', + goal: 'Blockers identifizieren', + progress: 35, + elapsed: '00:05:10', + next: 'Meeting-Summary an Team', + model: 'Deepseek V4 Flash', + tokens: '14k', + cost: '0.24', + think: 'Analysiere Velocity-Daten der letzten 3 Sprints…', + }, +] diff --git a/frontend/src/mappers/agentMapper.ts b/frontend/src/mappers/agentMapper.ts new file mode 100644 index 0000000..8a134ce --- /dev/null +++ b/frontend/src/mappers/agentMapper.ts @@ -0,0 +1,123 @@ +import type { AgentNodeData } from '../types/agentNode' +import type { AgentDetailData, ThinkingItem } from '../types/agentDetail' +import type { DashboardAgentDto, ModelDto } from '../services/agentService' + +const STATUS_LABELS: Record = { + work: 'Arbeitet', + think: 'Plant', + idle: 'Bereit', + block: 'Blockiert', +} + +interface CatalogEntry { + elapsed: string + think: string | null + next: string +} + +const AGENT_CATALOG: Record = { + iris: { elapsed: '--', think: null, next: 'Standby' }, + programmer: { elapsed: '--', think: null, next: 'Standby' }, + developer: { elapsed: '--', think: null, next: 'Standby' }, + architekt: { elapsed: '--', think: null, next: 'Standby' }, + reviewer: { elapsed: '--', think: null, next: 'Standby' }, + executor: { elapsed: '--', think: null, next: 'Standby' }, + researcher: { elapsed: '--', think: null, next: 'Standby' }, +} + +function resolveStatus(isActive: boolean, currentTask: string | null): AgentNodeData['status'] { + if (!isActive) return 'idle' + if (currentTask && currentTask !== 'Idle') return 'work' + return 'think' +} + +function resolveAvatar(id: string, name: string): string { + if (id === 'iris') return 'IR' + if (id === 'programmer' || id === 'developer') return '' + return name.slice(0, 2).toUpperCase() +} + +function buildThinkingItems(data: AgentNodeData): ThinkingItem[] { + if (!data.think) return [] + const now = new Date() + const ts = (ago: number) => { + const d = new Date(now.getTime() - ago * 1000) + return d.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit' }) + } + const sentences = data.think.split(/[.…!?]+/).filter(s => s.trim().length > 5) + + if (sentences.length >= 2) { + const items: ThinkingItem[] = [ + { type: 'thought', text: sentences[0].trim() + '.', ts: ts(30) }, + { type: 'action', text: sentences[1].trim() + '…', ts: ts(18) }, + ] + const lastSentence = sentences.length >= 3 + ? sentences[sentences.length - 1].trim() + '.' + : 'Verarbeitung abgeschlossen.' + items.push({ type: 'result', text: lastSentence, ts: ts(3) }) + return items + } + if (sentences.length === 1) { + return [ + { type: 'thought', text: sentences[0].trim(), ts: ts(15) }, + { type: 'action', text: 'Analysiere Daten und erstelle nächsten Schritt…', ts: ts(6) }, + ] + } + return [{ type: 'thought', text: data.think, ts: ts(10) }] +} + +export function toAgentNode(dto: DashboardAgentDto): AgentNodeData { + const cat = AGENT_CATALOG[dto.id] ?? AGENT_CATALOG['reviewer']! + const status = resolveStatus(dto.isActive, dto.currentTask) + return { + id: dto.id, + name: dto.name, + role: dto.role, + model: dto.model, + avatar: resolveAvatar(dto.id, dto.name), + status, + statusLabel: STATUS_LABELS[status], + task: dto.currentTask, + goal: dto.goal ?? null, + progress: dto.progress ?? 0, + elapsed: cat.elapsed, + next: cat.next, + tokens: '0', + cost: '0.00', + think: cat.think, + } +} + +export function toModelAlias(dtos: ModelDto[]): { id: string; alias: string }[] { + return dtos.map(m => ({ id: m.id, alias: m.name })) +} + +export function toAgentDetail( + data: AgentNodeData, + models: { id: string; alias: string }[] +): AgentDetailData { + const tokenNum = parseFloat(data.tokens?.replace(/[^0-9.]/g, '') || '0') + const tokenMultiplier = data.tokens?.includes('M') + ? 1_000_000 + : data.tokens?.includes('k') ? 1_000 : 1 + const tokensToday = Math.round(tokenNum * tokenMultiplier) + + const matchingModel = models.find(m => m.id === data.model || m.alias === data.model) + const displayModel = matchingModel?.alias ?? data.model + + return { + id: data.id, + name: data.name, + role: data.role, + model: displayModel, + status: data.status === 'block' ? 'idle' : data.status, + tokensToday, + costToday: parseFloat(data.cost || '0'), + workload: data.progress, + uptime: data.elapsed || '—', + lastActive: data.elapsed !== '—' ? 'Vor ' + data.elapsed : 'Nicht aktiv', + activeTaskCount: data.task ? 1 : 0, + thinking: buildThinkingItems(data), + availableModels: models, + } +} diff --git a/frontend/src/mappers/taskMapper.ts b/frontend/src/mappers/taskMapper.ts new file mode 100644 index 0000000..666938f --- /dev/null +++ b/frontend/src/mappers/taskMapper.ts @@ -0,0 +1,35 @@ +import type { TaskItem } from '../types/task' +import type { TaskDto } from '../services/taskService' + +function toPriority(raw: string): TaskItem['priority'] { + const p = raw.toLowerCase() + if (p === 'high' || p === 'critical' || p === 'urgent') return 'high' + if (p === 'low' || p === 'minor') return 'low' + return 'medium' +} + +function toStatus(raw: string): TaskItem['status'] { + const s = raw.toLowerCase() + if (s === 'in progress' || s === 'active' || s === 'working') return 'active' + if (s === 'blocked' || s === 'block') return 'blocked' + return 'pending' +} + +function toProgress(raw: string): number { + const s = raw.toLowerCase() + if (s === 'in progress' || s === 'active' || s === 'working') return 50 + if (s === 'done') return 100 + if (s === 'blocked') return 30 + return 0 +} + +export function toTaskItem(dto: TaskDto): TaskItem { + return { + id: dto.id, + title: dto.title, + agent: dto.assignedTo ?? '—', + priority: toPriority(dto.priority), + status: toStatus(dto.state), + progress: toProgress(dto.state), + } +}