effc86e15b
- Agent Cards: aktives LLM Model unter Runtime - Glassmorphism: rgba-BG + backdrop-filter:blur - Bézier-Linien: 2 Pulse pro Verbindung (Offset 50%) - TaskCard: 'Zum Task Board' Button mit Pfeil
213 lines
6.4 KiB
Vue
213 lines
6.4 KiB
Vue
<script setup lang="ts">
|
|
import { onMounted, onUnmounted, ref } from 'vue'
|
|
import TaskCard from '../components/dashboard/MissionCard.vue'
|
|
import OperationsFeed from '../components/dashboard/OperationsFeed.vue'
|
|
import TeamNetwork from '../components/dashboard/TeamNetwork.vue'
|
|
import ChatPanel from '../components/dashboard/ChatPanel.vue'
|
|
import QueuePanel from '../components/dashboard/QueuePanel.vue'
|
|
import AgentModal from '../components/dashboard/AgentModal.vue'
|
|
import { useDashboardData } from '../composables/useDashboardData'
|
|
import type { AgentNodeData } from '../../composables/useDashboardData'
|
|
|
|
const {
|
|
agents, openTasks, feedEntries, chatMessages,
|
|
irisBusy, irisFocus, irisRuntime, queue,
|
|
getAgentRuntime, startRuntime, stopRuntime,
|
|
sendChat, removeQueueItem, moveQueueItem, changeQueuePriority,
|
|
} = useDashboardData()
|
|
|
|
const selectedAgent = ref<AgentNodeData | null>(null)
|
|
|
|
function onAgentSelect(id: string) {
|
|
const agent = agents.value.find(a => a.id === id)
|
|
if (agent) selectedAgent.value = agent
|
|
}
|
|
|
|
onMounted(startRuntime)
|
|
onUnmounted(stopRuntime)
|
|
|
|
function onChatSend(text: string): void { sendChat(text) }
|
|
|
|
function onQueueMoveUp(id: string): void {
|
|
const idx = queue.value.findIndex(q => q.id === id)
|
|
if (idx > 0) moveQueueItem(idx, idx - 1)
|
|
}
|
|
|
|
function onQueueMoveDown(id: string): void {
|
|
const idx = queue.value.findIndex(q => q.id === id)
|
|
if (idx < queue.value.length - 1) moveQueueItem(idx, idx + 1)
|
|
}
|
|
|
|
function onQueueExecuteNow(id: string): void {
|
|
const item = queue.value.find(q => q.id === id)
|
|
if (item) console.log('[Dashboard] Execute now:', item.text)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="dashboard">
|
|
<div class="col-left">
|
|
<section class="missions-section">
|
|
<TaskCard :tasks="openTasks" @new-task="console.log('New task requested')" @go-board="console.log('Go to Task Board')" />
|
|
</section>
|
|
<OperationsFeed :entries="feedEntries" />
|
|
</div>
|
|
<div class="col-center">
|
|
<!-- Quote Pill -->
|
|
<div class="quote-pill">
|
|
<span class="quote-text">"An autonomous organization of AI agents that does work for me and produces value 24/7"</span>
|
|
</div>
|
|
|
|
<!-- Header -->
|
|
<div class="team-header">
|
|
<h1 class="team-title">AI Team Network</h1>
|
|
<p class="team-subtitle">{{ agents.length }} AI agents, connected in real-time.</p>
|
|
<p class="team-description">Mission Control orchestriert ein Team spezialisierter Agenten — jeder mit eigener Identität, eigenem Workspace und klaren Verantwortlichkeiten. Die Pulse zeigen aktive Kommunikationsflüsse.</p>
|
|
</div>
|
|
|
|
<TeamNetwork
|
|
hero-id="iris"
|
|
:agents="agents"
|
|
:iris-runtime="irisRuntime"
|
|
:get-agent-runtime="getAgentRuntime"
|
|
:iris-focus="irisFocus"
|
|
@select="onAgentSelect"
|
|
/>
|
|
|
|
<!-- Legend -->
|
|
<div class="legend-row">
|
|
<div class="legend-item">
|
|
<span class="legend-dot active-pulse"></span>
|
|
<span>Aktive Verbindung</span>
|
|
</div>
|
|
<div class="legend-item">
|
|
<span class="legend-dot idle-pulse"></span>
|
|
<span>Idle</span>
|
|
</div>
|
|
<div class="legend-item">
|
|
<span class="legend-dot pulse-dot"></span>
|
|
<span>Datenfluss (Pulse)</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-right">
|
|
<ChatPanel :messages="chatMessages" :iris-busy="irisBusy" :iris-focus="irisFocus" @send="onChatSend" />
|
|
<QueuePanel :items="queue" @remove="removeQueueItem" @move-up="onQueueMoveUp" @move-down="onQueueMoveDown" @change-priority="changeQueuePriority" @execute-now="onQueueExecuteNow" />
|
|
</div>
|
|
|
|
<AgentModal
|
|
v-if="selectedAgent"
|
|
:agent="selectedAgent"
|
|
:runtime="getAgentRuntime(selectedAgent.id)"
|
|
@close="selectedAgent = null"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.dashboard {
|
|
display: grid; grid-template-columns: 280px 1fr 320px; gap: 14px;
|
|
height: 100%; 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); }
|
|
}
|
|
.dashboard ::-webkit-scrollbar { width: 5px; height: 5px; }
|
|
.dashboard ::-webkit-scrollbar-track { background: transparent; }
|
|
.dashboard ::-webkit-scrollbar-thumb { background: rgba(139,124,246,0.2); border-radius: 3px; }
|
|
.dashboard ::-webkit-scrollbar-thumb:hover { background: rgba(139,124,246,0.35); }
|
|
.col-left { display: flex; flex-direction: column; gap: 12px; overflow-y: auto; padding-right: 4px; }
|
|
.col-center { overflow-y: auto; padding: 0 4px; min-height: 0; display: flex; flex-direction: column; gap: 12px; }
|
|
|
|
/* Quote Pill */
|
|
.quote-pill {
|
|
background: var(--panel);
|
|
border: 1px solid rgba(139, 124, 246, 0.25);
|
|
border-radius: 14px;
|
|
padding: 14px 22px;
|
|
box-shadow: 0 0 18px rgba(139, 124, 246, 0.06), inset 0 0 18px rgba(139, 124, 246, 0.03);
|
|
text-align: center;
|
|
}
|
|
.quote-text {
|
|
font-style: italic;
|
|
font-size: 12px;
|
|
color: #9ea5b3;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
/* Team Header */
|
|
.team-header {
|
|
text-align: center;
|
|
}
|
|
.team-title {
|
|
font-size: 26px;
|
|
font-weight: 600;
|
|
color: #e8eaf0;
|
|
margin: 0 0 6px;
|
|
}
|
|
.team-subtitle {
|
|
font-size: 12px;
|
|
color: #7e8799;
|
|
margin: 0 0 4px;
|
|
}
|
|
.team-description {
|
|
font-size: 10.5px;
|
|
color: #6b7385;
|
|
margin: 0;
|
|
max-width: 560px;
|
|
margin-left: auto;
|
|
margin-right: auto;
|
|
}
|
|
|
|
/* Legend */
|
|
.legend-row {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 24px;
|
|
padding: 12px 20px;
|
|
background: var(--panel);
|
|
border: 1px solid var(--line);
|
|
border-radius: 10px;
|
|
}
|
|
.legend-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 10px;
|
|
color: #7e8799;
|
|
}
|
|
.legend-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
flex-shrink: 0;
|
|
}
|
|
.active-pulse {
|
|
background: #51d49a;
|
|
box-shadow: 0 0 6px rgba(81, 212, 154, 0.6);
|
|
}
|
|
.idle-pulse {
|
|
background: #3a3f4b;
|
|
}
|
|
.pulse-dot {
|
|
background: white;
|
|
width: 6px;
|
|
height: 6px;
|
|
animation: legend-pulse 1.5s ease-in-out infinite;
|
|
}
|
|
@keyframes legend-pulse {
|
|
0%, 100% { opacity: 0.3; transform: scale(0.8); }
|
|
50% { opacity: 1; transform: scale(1.2); }
|
|
}
|
|
.col-right { display: flex; flex-direction: column; gap: 12px; overflow-y: auto; padding-left: 4px; }
|
|
.missions-section { display: flex; flex-direction: column; gap: 8px; }
|
|
.column-title { margin: 0; font-size: 13px; font-weight: 600; color: #e8eaf0; letter-spacing: 0.01em; }
|
|
@media (max-width: 1100px) {
|
|
.dashboard { grid-template-columns: 1fr; }
|
|
.col-left, .col-center, .col-right { overflow: visible; padding: 0; }
|
|
}
|
|
</style>
|