Files
nexus/frontend/src/views/Dashboard/FlowBoard.vue
T
reviewer 6cedd8410f
CI - Build & Test / Backend (.NET) (push) Failing after 23s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 16s
CI - Build & Test / Security Check (push) Successful in 3s
refactor(frontend): deduplicate CSS keyframes, unify types, extract format utils, add UI states, trim mock data
- Remove duplicate @keyframes pulse-* from 3 component files (already in nexus-tokens.css)
- Rename AgentDetail → AgentDetailData in dashboard types to avoid collision with types/agent.ts
- Extract shared formatNumber/initials/formatTime to utils/format.ts
- Simplify FlowBoard: use agentStore modal/selection getters instead of duplicating local state
- Add error banner + empty state to IrisChat; add loading skeleton + error/empty states to TaskStrip
- Remove 105-line unused mockAgents array from useFlowLayout
- Reduce operations store fallbacks from hardcoded preview data to minimal safe defaults
- Update operations store tests to match lean fallback structure
- Net: -73 lines, cleaner imports, fewer magic strings
2026-06-12 17:02:50 +02:00

179 lines
5.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
/**
* FlowBoard Das neue V2 Dashboard
*
* Layout:
* Stage (AlertBar + FlowCanvas) + Rail (IrisChat) + TaskStrip (unten)
*
* Datenquellen:
* - AgentStore: agents, models, AlertBar-Metriken, Modal-Status
* - ChatStore: messages, isThinking, sendMessage()
* - TaskStore: tasks
*
* Polling startet bei Mount, stoppt bei Unmount.
*/
import { ref, onMounted, onUnmounted } from 'vue'
import { useAgentStore } from '../../stores/agents'
import { useChatStore } from '../../stores/chat'
import { useTaskStore } from '../../stores/tasks'
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 TaskStrip from '../../components/dashboard/v2/TaskStrip.vue'
import AgentDetailModal from '../../components/dashboard/v2/AgentDetailModal.vue'
import type { AgentNodeData } from '../../composables/useFlowLayout'
import { extraAgentPool } from '../../composables/useFlowLayout'
/* ── Stores ──────────────────────────────────────── */
const agentStore = useAgentStore()
const chatStore = useChatStore()
const taskStore = useTaskStore()
/* ── Agent Layout State ───────────────────────────── */
const agentPositions = ref<Record<string, { x: number; y: number }>>({})
const enteringIds = ref<string[]>([])
const localAgentPool = ref<AgentNodeData[]>([...extraAgentPool])
/* ── Event Handlers ───────────────────────────────── */
function handleSelect(id: string) {
agentStore.selectAgent(id)
}
function handleCloseModal() {
agentStore.selectAgent(null)
}
function handleChangeModel(agentId: string, modelAlias: string) {
// Modal emits the alias (display name); resolve to model ID for the API
const model = agentStore.models.find(m => m.alias === modelAlias)
const modelId = model?.id ?? modelAlias
agentStore.changeModel(agentId, modelId)
}
function handleAdd() {
const pool = localAgentPool.value
if (pool.length === 0) return
const next = pool.shift()!
enteringIds.value.push(next.id)
agentStore.agents.push(next)
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 }
}
function handleBlockerClick() {
console.log('[FlowBoard] blocker clicked')
}
function handleChatSend(text: string) {
chatStore.sendMessage(text)
}
/* ── Lifecycle ────────────────────────────────────── */
onMounted(() => {
agentStore.startPolling()
chatStore.startPolling()
taskStore.startPolling()
})
onUnmounted(() => {
agentStore.stopPolling()
chatStore.stopPolling()
taskStore.stopPolling()
})
</script>
<template>
<div class="flow-board">
<!-- Stage + Rail row -->
<div class="board-body">
<!-- Stage: AlertBar + FlowCanvas + TaskStrip -->
<div class="stage">
<AlertBar
:active-count="agentStore.activeCount"
:think-count="agentStore.thinkCount"
:idle-count="agentStore.idleCount"
:blocker-count="agentStore.blockerCount"
:today-cost="agentStore.todayCost"
:today-tokens="agentStore.todayTokens"
@blocker-click="handleBlockerClick"
/>
<FlowCanvas
:agents="agentStore.agentList"
:positions="agentPositions"
:entering-ids="enteringIds"
@select="handleSelect"
@add="handleAdd"
@reset-layout="handleResetLayout"
@update-positions="handleUpdatePositions"
/>
<TaskStrip :tasks="taskStore.taskList" :loading="taskStore.loading" :error="taskStore.error" />
</div>
<!-- Rail: IrisChat -->
<IrisChat
:messages="chatStore.messageList"
:is-thinking="chatStore.isThinking"
:error="chatStore.error"
@send="handleChatSend"
/>
</div>
<!-- Agent Detail Modal -->
<AgentDetailModal
v-if="agentStore.modalOpen && agentStore.selectedAgent"
:agent="agentStore.selectedAgent"
:agent-order="agentStore.agentOrder"
@close="handleCloseModal"
@select="handleSelect"
@change-model="handleChangeModel"
/>
</div>
</template>
<style scoped>
.flow-board {
height: 100%;
display: flex;
flex-direction: column;
min-height: 0;
animation: fade-in 0.35s ease-out;
}
@keyframes fade-in {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.board-body {
flex: 1;
display: flex;
flex-direction: row;
gap: 0;
min-height: 0;
}
.stage {
flex: 1;
display: flex;
flex-direction: column;
gap: 14px;
padding: 0 18px 0 0;
min-height: 0;
min-width: 0;
}
</style>