6cedd8410f
- 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
179 lines
5.1 KiB
Vue
179 lines
5.1 KiB
Vue
<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>
|