feat(v2): IrisChat + TaskStrip components, mock data integration
CI - Build & Test / Backend (.NET) (push) Failing after 20s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 16s
CI - Build & Test / Security Check (push) Successful in 3s

This commit is contained in:
2026-06-12 00:48:13 +02:00
parent 2d6e3537e8
commit 166c9f9051
4 changed files with 848 additions and 68 deletions
+218 -68
View File
@@ -1,46 +1,228 @@
<script setup lang="ts">
/**
* FlowBoard Das neue V2 Dashboard
* Main content area rendered inside NexusLayout.
*
* Layout:
* Stage (AlertBar + FlowCanvas) + Rail (IrisChat) + TaskStrip (unten)
*
* Integriert V2-Komponenten: AlertBar, FlowCanvas, IrisChat, TaskStrip
* mit Mock-Daten aus useFlowLayout / agents.js.
*/
import { useOperationsStore } from '../../stores/operations'
import { ref, computed } from 'vue'
import AlertBar from '../../components/dashboard/v2/AlertBar.vue'
import FlowCanvas from '../../components/dashboard/v2/FlowCanvas.vue'
import IrisChat from '../../components/dashboard/v2/IrisChat.vue'
import type { ChatMessage } from '../../components/dashboard/v2/types'
import TaskStrip from '../../components/dashboard/v2/TaskStrip.vue'
import type { TaskItem } from '../../components/dashboard/v2/types'
import { mockAgents, extraAgentPool } from '../../composables/useFlowLayout'
import type { AgentNodeData } from '../../composables/useFlowLayout'
const store = useOperationsStore()
/* ── Agent State ───────────────────────────────────── */
const agents = ref<AgentNodeData[]>(mockAgents)
const agentPositions = ref<Record<string, { x: number; y: number }>>({})
const enteringIds = ref<string[]>([])
const agentPool = ref<AgentNodeData[]>(extraAgentPool)
function handleSelect(id: string) {
console.log('[FlowBoard] selected agent:', id)
}
function handleAdd() {
const pool = agentPool.value
if (pool.length === 0) return
const next = pool.shift()!
enteringIds.value.push(next.id)
agents.value.push(next)
// Remove "entering" after animation settles
setTimeout(() => {
const idx = enteringIds.value.indexOf(next.id)
if (idx !== -1) enteringIds.value.splice(idx, 1)
}, 600)
}
function handleResetLayout() {
agentPositions.value = {}
}
function handleUpdatePositions(pos: Record<string, { x: number; y: number }>) {
agentPositions.value = { ...pos }
}
/* ── AlertBar computed props ──────────────────────── */
const activeCount = computed(() => agents.value.filter(a => a.status === 'work').length)
const thinkCount = computed(() => agents.value.filter(a => a.status === 'think').length)
const idleCount = computed(() => agents.value.filter(a => a.status === 'idle').length)
const blockerCount = computed(() => agents.value.filter(a => a.status === 'block').length)
const todayCost = computed(() => {
const total = agents.value.reduce((s, a) => s + parseFloat(a.cost || '0'), 0)
return '$' + total.toFixed(2)
})
const todayTokens = computed(() => {
const total = agents.value.reduce((s, a) => {
const v = a.tokens?.replace(/[^0-9.]/g, '')
return v ? s + parseFloat(v) : s
}, 0)
return total >= 1000 ? Math.round(total / 1000) + 'k' : Math.round(total) + ''
})
function handleBlockerClick() {
console.log('[FlowBoard] blocker clicked')
}
/* ── IrisChat Mock Data ────────────────────────────── */
const chatMessages = ref<ChatMessage[]>([
{
sender: 'iris',
text: 'Guten Morgen. Status: 4 Agents aktiv, 1 Blocker. Der Healthcheck auf staging schlägt seit dem letzten Deploy fehl — ich habe das dem Executor mit P0 zugewiesen.',
ts: '12:48',
},
{
sender: 'user',
text: 'Was ist die Ursache?',
ts: '12:49',
},
{
sender: 'iris',
text: 'Reviewer hat in PR #142 eine fehlende Rate-Limit-Prüfung gefunden, die den /health Endpoint blockiert. Developer arbeitet bereits am Fix (72%).',
ts: '12:49',
tool: 'gelesen: reviews/pr-142.md',
},
{
sender: 'user',
text: 'Gut. Halte mich beim Deploy auf dem Laufenden.',
ts: '12:51',
},
{
sender: 'iris',
text: 'Verstanden. Ich melde mich, sobald der Executor den Healthcheck verifiziert hat. Geschätzte Fertigstellung: ~6 Min.',
ts: '12:51',
},
])
const isThinking = ref(false)
function handleChatSend(text: string) {
// Add user message
chatMessages.value.push({
sender: 'user',
text,
ts: new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }),
})
// Simulate thinking + Iris response
isThinking.value = true
setTimeout(() => {
isThinking.value = false
chatMessages.value.push({
sender: 'iris',
text: `👍 Ich habe Deine Anfrage erhalten: "${text}". Ich arbeite daran und melde mich mit Ergebnissen.`,
ts: new Date().toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }),
})
}, 1200)
}
/* ── TaskStrip Mock Data ──────────────────────────── */
const tasks = ref<TaskItem[]>([
{
id: 't1',
title: 'Healthcheck schlägt auf staging fehl',
agent: 'Executor',
priority: 'high',
status: 'active',
progress: 88,
},
{
id: 't2',
title: 'Review PR #142 — Auth-Refactor',
agent: 'Reviewer',
priority: 'high',
status: 'active',
progress: 35,
},
{
id: 't3',
title: 'JWT-Rotation: Unit-Tests',
agent: 'Developer',
priority: 'high',
status: 'pending',
progress: 0,
},
{
id: 't4',
title: 'Terraform-Plan VPS-Skalierung',
agent: 'Architekt',
priority: 'medium',
status: 'pending',
progress: 0,
},
{
id: 't5',
title: 'Standup-Report generieren',
agent: 'Iris',
priority: 'medium',
status: 'pending',
progress: 0,
},
{
id: 't6',
title: 'LLM-Cost-Benchmark',
agent: 'Researcher',
priority: 'low',
status: 'pending',
progress: 0,
},
])
</script>
<template>
<div class="flow-board">
<!-- Placeholder: V2 Dashboard Content -->
<div class="board-hero">
<h2 class="board-title">Flow Board</h2>
<p class="board-subtitle">Real-time Agent Operations Dashboard</p>
</div>
<!-- Stage + Rail row -->
<div class="board-body">
<!-- Stage: AlertBar + FlowCanvas + TaskStrip -->
<div class="stage">
<AlertBar
:active-count="activeCount"
:think-count="thinkCount"
:idle-count="idleCount"
:blocker-count="blockerCount"
:today-cost="todayCost"
:today-tokens="todayTokens"
@blocker-click="handleBlockerClick"
/>
<!-- Metrics Row -->
<div class="metrics-row">
<div class="metric-card">
<span class="metric-label">Connected Agents</span>
<strong class="metric-value">{{ store.snapshot.metrics.runningAgents ?? 0 }}</strong>
</div>
<div class="metric-card">
<span class="metric-label">Queued Tasks</span>
<strong class="metric-value">{{ store.snapshot.metrics.queuedTasks ?? 0 }}</strong>
</div>
<div class="metric-card">
<span class="metric-label">Incidents</span>
<strong class="metric-value">{{ store.snapshot.metrics.incidents ?? 0 }}</strong>
<FlowCanvas
:agents="agents"
:positions="agentPositions"
:entering-ids="enteringIds"
@select="handleSelect"
@add="handleAdd"
@reset-layout="handleResetLayout"
@update-positions="handleUpdatePositions"
/>
<TaskStrip :tasks="tasks" />
</div>
<!-- Rail: IrisChat -->
<IrisChat
:messages="chatMessages"
:is-thinking="isThinking"
@send="handleChatSend"
/>
</div>
</div>
</template>
<style scoped>
.flow-board {
animation: fade-in 0.35s ease-out;
height: 100%;
display: flex;
flex-direction: column;
gap: 18px;
min-height: 0;
animation: fade-in 0.35s ease-out;
}
@keyframes fade-in {
@@ -48,53 +230,21 @@ const store = useOperationsStore()
to { opacity: 1; transform: translateY(0); }
}
.board-hero {
padding-bottom: 8px;
}
.board-title {
font-family: 'Space Grotesk', sans-serif;
font-weight: 600;
font-size: 24px;
margin: 0;
color: var(--tx);
}
.board-subtitle {
font-size: 13px;
color: var(--tx-3);
margin: 4px 0 0;
}
.metrics-row {
display: flex;
gap: 14px;
}
.metric-card {
.board-body {
flex: 1;
background: var(--glass);
border: 1px solid var(--line);
border-radius: var(--r);
backdrop-filter: blur(12px);
padding: 16px 18px;
display: flex;
flex-direction: row;
gap: 0;
min-height: 0;
}
.metric-label {
display: block;
font-size: 10px;
font-weight: 600;
letter-spacing: .08em;
color: var(--tx-3);
text-transform: uppercase;
margin-bottom: 6px;
}
.metric-value {
font-family: 'JetBrains Mono', monospace;
font-size: 28px;
font-weight: 600;
color: var(--tx);
font-variant-numeric: tabular-nums;
.stage {
flex: 1;
display: flex;
flex-direction: column;
gap: 14px;
padding: 0 18px 0 0;
min-height: 0;
min-width: 0;
}
</style>