From 2d6e3537e81d430f9191e608109e6c531f239037 Mon Sep 17 00:00:00 2001 From: Developer Date: Fri, 12 Jun 2026 00:24:56 +0200 Subject: [PATCH] feat(v2): FlowCanvas, AgentNode, AlertBar, useFlowLayout composable --- .../src/components/dashboard/v2/AgentNode.vue | 284 ++++++++++ .../src/components/dashboard/v2/AlertBar.vue | 171 ++++++ .../components/dashboard/v2/FlowCanvas.vue | 485 ++++++++++++++++++ frontend/src/composables/useFlowLayout.ts | 322 ++++++++++++ 4 files changed, 1262 insertions(+) create mode 100644 frontend/src/components/dashboard/v2/AgentNode.vue create mode 100644 frontend/src/components/dashboard/v2/AlertBar.vue create mode 100644 frontend/src/components/dashboard/v2/FlowCanvas.vue create mode 100644 frontend/src/composables/useFlowLayout.ts diff --git a/frontend/src/components/dashboard/v2/AgentNode.vue b/frontend/src/components/dashboard/v2/AgentNode.vue new file mode 100644 index 0000000..86afe6d --- /dev/null +++ b/frontend/src/components/dashboard/v2/AgentNode.vue @@ -0,0 +1,284 @@ + + + + + diff --git a/frontend/src/components/dashboard/v2/AlertBar.vue b/frontend/src/components/dashboard/v2/AlertBar.vue new file mode 100644 index 0000000..301d9cd --- /dev/null +++ b/frontend/src/components/dashboard/v2/AlertBar.vue @@ -0,0 +1,171 @@ + + + + + diff --git a/frontend/src/components/dashboard/v2/FlowCanvas.vue b/frontend/src/components/dashboard/v2/FlowCanvas.vue new file mode 100644 index 0000000..9e7e18b --- /dev/null +++ b/frontend/src/components/dashboard/v2/FlowCanvas.vue @@ -0,0 +1,485 @@ + + + + + diff --git a/frontend/src/composables/useFlowLayout.ts b/frontend/src/composables/useFlowLayout.ts new file mode 100644 index 0000000..109f9a3 --- /dev/null +++ b/frontend/src/composables/useFlowLayout.ts @@ -0,0 +1,322 @@ +/** + * useFlowLayout — Auto-Layout und Edge-Formeln für das V2 FlowCanvas + * + * Portiert von agents.js (design_handoff_nexus_v2). + * Enthält alle Positionslogik, Edge-Erzeugung und den Typ AgentNodeData. + */ + +export interface Point { + x: number + y: number +} + +export interface AgentNodeData { + id: string + name: string + role: string + roleBadge?: string + model: string + avatar: string + status: 'work' | 'think' | 'idle' | 'block' + statusLabel: string + task: string | null + goal: string | null + progress: number + elapsed: string + next: string + tokens: string + cost: string + think: string | null + handoff?: string + from?: string + links?: string[] + md?: string +} + +export interface EdgeData { + a: string + b: string + kind: 'orch' | 'flow' +} + +/** + * Auto-Layout Algorithmus + * Iris immer top-center (x:50%, y:14%). + * Andere Agenten in Reihen (maxPerRow variiert nach Gesamtzahl). + */ +export function autoLayout(agents: AgentNodeData[]): Record { + const positions: Record = { iris: { x: 50, y: 14 } } + const others = agents.filter(a => a.id !== 'iris') + const n = others.length + if (n === 0) return positions + + const maxPerRow = n <= 2 ? 2 : n <= 6 ? 3 : n <= 9 ? 3 : 4 + const numRows = Math.ceil(n / maxPerRow) + const yStart = n <= 3 ? 58 : 30 + const yEnd = 86 + const yVals = numRows === 1 + ? [yStart] + : Array.from({ length: numRows }, (_, i) => yStart + (yEnd - yStart) * i / (numRows - 1)) + + let idx = 0 + yVals.forEach(y => { + const rowN = Math.min(maxPerRow, n - idx) + const xSpan = Math.min(72, 24 * (rowN - 1) + 18) + const xOff = 50 - xSpan / 2 + const xStep = rowN > 1 ? xSpan / (rowN - 1) : 0 + for (let ci = 0; ci < rowN; ci++, idx++) { + positions[others[idx].id] = { x: xOff + ci * xStep, y } + } + }) + + return positions +} + +/** + * Erzeugt die Kantenliste basierend auf Agenten-Daten + * (gleiche Logik wie buildEdges in agents.js). + */ +export function buildEdges(agents: AgentNodeData[]): EdgeData[] { + const edges: EdgeData[] = [] + + // Orchestrierung: Iris → jeder Agent + agents.filter(a => a.id !== 'iris').forEach(a => { + edges.push({ a: 'iris', b: a.id, kind: 'orch' }) + }) + + // Spezifische Flows (wenn Agent existiert) + const hasId = (id: string) => agents.some(a => a.id === id) + if (hasId('dev') && hasId('rev')) edges.push({ a: 'dev', b: 'rev', kind: 'flow' }) + if (hasId('arch') && hasId('exec')) edges.push({ a: 'arch', b: 'exec', kind: 'flow' }) + if (hasId('res')) edges.push({ a: 'res', b: 'iris', kind: 'flow' }) + if (hasId('qa') && hasId('rev')) edges.push({ a: 'qa', b: 'rev', kind: 'flow' }) + if (hasId('security')) edges.push({ a: 'security', b: 'iris', kind: 'flow' }) + if (hasId('pm')) edges.push({ a: 'pm', b: 'iris', kind: 'flow' }) + if (hasId('devops') && hasId('exec')) edges.push({ a: 'exec', b: 'devops', kind: 'flow' }) + + return edges +} + +/** + * Bézier-Kurve zwischen zwei Punkten + * Verwendet die README.p1 und p2)) Formel: + * M p1 Q Kontrollpunkt p2 + * mx,my = Mittelpunkt; off = min(50, hypot*0.14) + * len = hypot(-dy, dx) (Normale) + * cp = (mx + (-dy/len)*off, my + (dx/len)*off) + */ +export function curve(p1: Point, p2: Point): string { + const mx = (p1.x + p2.x) / 2 + const my = (p1.y + p2.y) / 2 + const dx = p2.x - p1.x + const dy = p2.y - p1.y + const off = Math.min(50, Math.hypot(dx, dy) * 0.14) + const len = Math.hypot(-dy, dx) || 1 + const cx = mx + (-dy / len) * off + const cy = my + (dx / len) * off + return `M${p1.x},${p1.y} Q${cx},${cy} ${p2.x},${p2.y}` +} + +/** + * Mock-Daten (identisch zu NEXUS.agents aus agents.js) + */ +export const mockAgents: AgentNodeData[] = [ + { + id: 'iris', + name: 'Iris', + role: 'Chief of Staff', + roleBadge: 'badge-purple', + model: 'Deepseek V4 Pro', + avatar: 'IR', + status: 'think', + statusLabel: 'Orchestriert', + task: 'Sprint-Planung koordinieren', + goal: 'Tagesziele auf das Team verteilen', + progress: 60, + elapsed: '00:14:22', + next: 'Standup-Report 17:00', + tokens: '48.2k', + cost: '1.84', + md: 'mission/sprint-12.md', + think: 'Priorisiere Deploy-Blocker vor Feature-Tasks; weise Healthcheck-Fehler dem Executor zu…', + links: ['dev', 'rev', 'arch', 'exec', 'res'], + }, + { + id: 'dev', + name: 'Full-Stack Developer', + role: 'Implementation', + roleBadge: 'badge-blue', + model: 'Deepseek V4 Flash', + avatar: '', + status: 'work', + statusLabel: 'Arbeitet', + task: 'Auth-Refactor — JWT-Rotation', + goal: 'Sichere Token-Rotation in api/auth', + progress: 72, + elapsed: '00:41:09', + next: 'Unit-Tests schreiben', + tokens: '121k', + cost: '2.40', + md: 'projects/auth/refactor.md', + think: 'Erzeuge Refresh-Token-Middleware, prüfe Edge-Case bei abgelaufenem Token…', + handoff: 'rev', + }, + { + id: 'rev', + name: 'Reviewer', + role: 'Code Quality', + roleBadge: 'badge-cyan', + model: 'Deepseek V4 Pro', + avatar: 'RV', + status: 'work', + statusLabel: 'Arbeitet', + task: 'Review PR #142 — Auth-Refactor', + goal: 'Diff auf Regressionen prüfen', + progress: 35, + elapsed: '00:08:51', + next: 'Feedback an Developer', + tokens: '64k', + cost: '1.10', + md: 'reviews/pr-142.md', + think: 'Analysiere Änderungen in auth/middleware.ts — fehlende Rate-Limit-Prüfung gefunden…', + from: 'dev', + }, + { + id: 'arch', + name: 'Architekt', + role: 'Infrastructure', + roleBadge: 'badge-amber', + model: 'Deepseek V4 Pro', + avatar: 'AR', + status: 'think', + statusLabel: 'Plant', + task: 'VPS-Skalierung planen', + goal: 'Lastspitzen abfedern, Kosten senken', + progress: 20, + elapsed: '00:03:30', + next: 'Terraform-Plan an Executor', + tokens: '31k', + cost: '0.74', + md: 'infra/scaling-q3.md', + think: 'Vergleiche vertikale vs. horizontale Skalierung…', + handoff: 'exec', + }, + { + id: 'exec', + name: 'Executor', + role: 'Host Executor', + roleBadge: 'badge-green', + model: 'Deepseek V4 Flash', + avatar: 'EX', + status: 'work', + statusLabel: 'Deployt', + task: 'Deploy nexus-api v2.3', + goal: 'Zero-Downtime Rollout auf VPS', + progress: 88, + elapsed: '00:02:12', + next: 'Healthcheck verifizieren', + tokens: '18k', + cost: '0.32', + md: 'ops/deploy-v2.3.md', + think: '$ docker compose up -d --no-deps api…', + from: 'arch', + }, + { + id: 'res', + name: 'Researcher', + role: 'Research & Analysis', + roleBadge: 'badge-slate', + model: 'Deepseek V4 Flash', + avatar: 'RS', + status: 'idle', + statusLabel: 'Bereit', + task: null, + goal: null, + progress: 0, + elapsed: '—', + next: 'LLM-Cost-Benchmark (Queue)', + tokens: '0', + cost: '0.00', + md: 'research/vector-db.md', + think: null, + handoff: 'iris', + }, +] + +/** + * Padding-ähnliche Extra-Agenten zum Hinzufügen + */ +export const extraAgentPool: 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…', + }, +]