Files
nexus/frontend/src/views/Dashboard/FlowBoard.vue
T
AzuTear 64459ccdb3
CI - Build & Test / Backend (.NET) (push) Successful in 25s
CI - Build & Test / Frontend (Vue/TS) (push) Has been cancelled
CI - Build & Test / Security Check (push) Has been cancelled
feat: wire dashboard v2 to backend data
2026-06-14 15:44:05 +02:00

172 lines
4.5 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 { onMounted, onUnmounted } from 'vue'
import { useAgentStore } from '../../stores/agents'
import { useChatStore } from '../../stores/chat'
import { useDashboardStore } from '../../stores/dashboard'
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 { useFlowBoardState } from '../../composables/useFlowBoardState'
/* ── Stores ──────────────────────────────────────── */
const agentStore = useAgentStore()
const chatStore = useChatStore()
const dashboardStore = useDashboardStore()
const taskStore = useTaskStore()
const {
addAgent,
agentPositions,
changeModel,
closeAgent,
enteringIds,
resetLayout,
selectAgent,
sendChatMessage,
updatePositions,
} = useFlowBoardState(agentStore, chatStore)
function handleBlockerClick() {
console.log('[FlowBoard] blocker clicked')
}
function blockerLabel() {
const blockedTask = taskStore.taskList.find(task => task.status === 'blocked')
if (!blockedTask) return undefined
return `${taskStore.taskList.filter(task => task.status === 'blocked').length} Blocker — ${blockedTask.title}`
}
function blockerCount() {
return taskStore.taskList.filter(task => task.status === 'blocked').length
}
/* ── Lifecycle ────────────────────────────────────── */
onMounted(() => {
agentStore.startPolling()
chatStore.startPolling()
dashboardStore.startPolling()
taskStore.startPolling()
})
onUnmounted(() => {
agentStore.stopPolling()
chatStore.stopPolling()
dashboardStore.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="blockerCount()"
:today-cost="agentStore.todayCost"
:today-tokens="agentStore.todayTokens"
:blocker-label="blockerLabel()"
@blocker-click="handleBlockerClick"
/>
<FlowCanvas
:agents="agentStore.agentList"
:positions="agentPositions"
:entering-ids="enteringIds"
@select="selectAgent"
@add="addAgent"
@reset-layout="resetLayout"
@update-positions="updatePositions"
/>
<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="sendChatMessage"
/>
</div>
<!-- Agent Detail Modal -->
<AgentDetailModal
v-if="agentStore.modalOpen && agentStore.selectedAgent"
:agent="agentStore.selectedAgent"
:agent-order="agentStore.agentOrder"
@close="closeAgent"
@select="selectAgent"
@change-model="changeModel"
/>
</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: 18px;
padding: 18px 20px;
overflow: hidden;
min-height: 0;
}
.stage {
flex: 1;
display: flex;
flex-direction: column;
gap: 14px;
min-height: 0;
min-width: 0;
overflow: hidden;
}
@media (max-width: 767px) {
.board-body {
flex-direction: column;
padding: 8px;
gap: 10px;
}
.stage {
flex: 1;
}
}
</style>