feat: Mission Control Dashboard – AI Team Network, Chat, Queue
CI - Build & Test / Backend (.NET) (push) Successful in 31s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 16s
CI - Build & Test / Security Check (push) Successful in 3s

- 3-Spalten-Layout: Missions/Feed | Team Network | Chat/Queue
- AI Team Network (Herzstück): Iris + 4 Agenten mit SVG-Linien
- AgentNode: Workload-Ring, Pulse-Animation, Farbcodierung
- AgentModal: Task, Goal, Progress, Working Feed
- MissionCard: Glass-Morphism, Progress-Bar, Status-Badges
- ChatPanel: Iris Chat mit Focus-Banner, Auto-Scroll
- QueuePanel: Drag&Drop, Prioritäten, Force-Execute
- Composables: useTimer, useDashboardData
This commit is contained in:
2026-06-09 20:59:45 +02:00
parent 9fb90f9c05
commit 3c95281119
10 changed files with 2187 additions and 283 deletions
@@ -0,0 +1,297 @@
import { ref, reactive, computed } from 'vue'
export interface AgentNodeData {
id: string
name: string
role: string
description: string
color: string
icon: string
currentTask: string
goal: string
progress: number
workload: number // 0-100
active: boolean
runtimeSeconds: number
workingFeed: string[]
}
export interface MissionData {
id: string
name: string
progress: number
currentTask: string
lastActivity: string
remainingTasks: number
status: 'healthy' | 'attention' | 'blocked' | 'paused'
}
export interface FeedEntry {
time: string
agent: string
action: string
timestamp: number
}
export interface ChatMessage {
id: string
sender: 'user' | 'iris'
text: string
timestamp: number
}
export interface QueueItem {
id: string
text: string
priority: 'high' | 'medium' | 'low'
waitTime: string
}
const now = Date.now()
export function useDashboardData() {
const sessionStart = Date.now()
// Runtime counter
const runtimeSeconds = ref(0)
let runtimeInterval: ReturnType<typeof setInterval> | null = null
function startRuntime() {
const startTs = sessionStart
runtimeSeconds.value = Math.floor((Date.now() - startTs) / 1000)
runtimeInterval = setInterval(() => {
runtimeSeconds.value = Math.floor((Date.now() - startTs) / 1000)
}, 1000)
}
function stopRuntime() {
if (runtimeInterval) {
clearInterval(runtimeInterval)
runtimeInterval = null
}
}
const formatRuntime = (seconds: number): string => {
const m = Math.floor(seconds / 60)
const s = seconds % 60
return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`
}
const irisRuntime = computed(() => formatRuntime(runtimeSeconds.value))
// Agent runtimes (simulated)
const agentStartTimes = reactive<Record<string, number>>({
developer: now - 3600000,
devops: now - 1800000,
researcher: now - 2700000,
reviewer: now - 900000,
})
const getAgentRuntime = (id: string): string => {
const start = agentStartTimes[id]
if (!start) return '00:00'
const secs = Math.floor((now - start) / 1000)
const m = Math.floor(secs / 60)
const s = secs % 60
return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`
}
// Agents
const agents = ref<AgentNodeData[]>([
{
id: 'developer',
name: 'Developer',
role: 'Backend & Frontend',
description: 'Implements features across the stack with TypeScript, C#, and Vue.',
color: '#3b82f6',
icon: 'code',
currentTask: 'Building Dungeon System API endpoints',
goal: 'Complete Dungeon CRUD + room generation',
progress: 62,
workload: 65,
active: true,
runtimeSeconds: 3600,
workingFeed: [
'Created DungeonController',
'Defined dungeon schema',
'Implementing room generation algorithm',
'Writing unit tests for RoomFactory',
],
},
{
id: 'devops',
name: 'DevOps',
role: 'Infrastructure & CI/CD',
description: 'Manages Docker, deployment pipelines, and system reliability.',
color: '#eab308',
icon: 'server',
currentTask: 'Optimizing Docker Compose caching',
goal: 'Reduce build times by 40%',
progress: 45,
workload: 40,
active: false,
runtimeSeconds: 1800,
workingFeed: [
'Analyzed Docker layer cache',
'Optimized COPY order in Dockerfile',
'Added .dockerignore for node_modules',
'Testing incremental builds',
],
},
{
id: 'researcher',
name: 'Researcher',
role: 'Analysis & Documentation',
description: 'Researches APIs, patterns, and best practices. Maintains docs.',
color: '#22c55e',
icon: 'search',
currentTask: 'Analyzing WebSocket alternatives',
goal: 'Recommend real-time communication strategy',
progress: 30,
workload: 25,
active: true,
runtimeSeconds: 2700,
workingFeed: [
'Evaluated WebSocket vs SSE vs WebRTC',
'Documented SignalR limitations',
'Prototyping WebSocket fallback',
],
},
{
id: 'reviewer',
name: 'Reviewer',
role: 'Code Quality & Testing',
description: 'Reviews pull requests, enforces standards, runs test suites.',
color: '#a855f7',
icon: 'shield',
currentTask: 'Reviewing Dungeon System PR',
goal: 'Zero critical findings before merge',
progress: 80,
workload: 50,
active: false,
runtimeSeconds: 900,
workingFeed: [
'Reviewed DungeonController.cs',
'Found 3 minor style issues',
'Approved RoomValidator',
'Running integration tests',
],
},
])
// Missions
const missions = ref<MissionData[]>([
{
id: 'dungeon-system',
name: 'Dungeon System',
progress: 62,
currentTask: 'Implement room generation',
lastActivity: '3 min ago',
remainingTasks: 8,
status: 'healthy',
},
{
id: 'dashboard-redesign',
name: 'Dashboard Redesign',
progress: 45,
currentTask: 'AI Team Network layout',
lastActivity: 'Just now',
remainingTasks: 6,
status: 'healthy',
},
{
id: 'infra-optimization',
name: 'Infra Optimization',
progress: 30,
currentTask: 'Optimize build caching',
lastActivity: '12 min ago',
remainingTasks: 4,
status: 'attention',
},
{
id: 'auth-system',
name: 'Auth System',
progress: 88,
currentTask: 'Finalize refresh token flow',
lastActivity: '45 min ago',
remainingTasks: 2,
status: 'healthy',
},
])
// Feed
const feedEntries = ref<FeedEntry[]>([
{ time: '20:42', agent: 'Developer', action: 'Created DungeonController endpoints', timestamp: now - 60000 },
{ time: '20:38', agent: 'DevOps', action: 'Optimized Docker COPY order', timestamp: now - 300000 },
{ time: '20:35', agent: 'Iris', action: 'Delegated room generation to Developer', timestamp: now - 540000 },
{ time: '20:28', agent: 'Researcher', action: 'Documented WebSocket vs SSE analysis', timestamp: now - 780000 },
{ time: '20:22', agent: 'Reviewer', action: 'Approved RoomValidator PR', timestamp: now - 900000 },
{ time: '20:15', agent: 'DevOps', action: 'Added .dockerignore for node_modules', timestamp: now - 1200000 },
{ time: '20:08', agent: 'Iris', action: 'Broke down Dungeon System tasks', timestamp: now - 1500000 },
{ time: '19:55', agent: 'Developer', action: 'Defined dungeon schema models', timestamp: now - 1800000 },
])
// Chat
const chatMessages = ref<ChatMessage[]>([
{ id: 'm1', sender: 'iris', text: 'Guten Abend, Bao. Ready to continue the Dungeon System?', timestamp: now - 600000 },
{ id: 'm2', sender: 'user', text: "Yes, what's the status?", timestamp: now - 540000 },
{ id: 'm3', sender: 'iris', text: "Developer is at 62% on room generation. Reviewer approved the schema. I'd recommend focusing on the room connection logic next.", timestamp: now - 480000 },
])
const irisBusy = ref(true)
const irisFocus = ref('Breaking down Dungeon System for DevOps and Developer')
// Queue
const queue = ref<QueueItem[]>([
{ id: 'q1', text: 'Deploy latest dashboard build to preview', priority: 'high', waitTime: '2 min' },
{ id: 'q2', text: 'Review infrastructure cost analysis', priority: 'medium', waitTime: '8 min' },
{ id: 'q3', text: 'Schedule B2 German lesson review', priority: 'low', waitTime: '15 min' },
{ id: 'q4', text: 'Update project roadmap document', priority: 'medium', waitTime: '12 min' },
])
function sendChat(text: string): void {
if (!text.trim()) return
chatMessages.value.push({
id: `user-${Date.now()}`,
sender: 'user',
text: text.trim(),
timestamp: Date.now(),
})
}
function removeQueueItem(id: string): void {
const idx = queue.value.findIndex(q => q.id === id)
if (idx !== -1) queue.value.splice(idx, 1)
}
function moveQueueItem(fromIdx: number, toIdx: number): void {
if (toIdx < 0 || toIdx >= queue.value.length) return
const [item] = queue.value.splice(fromIdx, 1)
queue.value.splice(toIdx, 0, item)
}
function changeQueuePriority(id: string, priority: QueueItem['priority']): void {
const item = queue.value.find(q => q.id === id)
if (item) item.priority = priority
}
return {
agents,
missions,
feedEntries,
chatMessages,
irisBusy,
irisFocus,
irisRuntime,
queue,
runtimeSeconds,
getAgentRuntime,
startRuntime,
stopRuntime,
formatRuntime,
sendChat,
removeQueueItem,
moveQueueItem,
changeQueuePriority,
}
}
+37
View File
@@ -0,0 +1,37 @@
import { ref, onMounted, onUnmounted } from 'vue'
export function useTimer() {
const elapsed = ref(0)
let interval: ReturnType<typeof setInterval> | null = null
function start() {
if (interval !== null) return
const startTime = Date.now()
elapsed.value = 0
interval = setInterval(() => {
elapsed.value = Math.floor((Date.now() - startTime) / 1000)
}, 1000)
}
function stop() {
if (interval !== null) {
clearInterval(interval)
interval = null
}
}
function format(minutes: number, seconds: number): string {
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`
}
const formatted = (): string => {
const m = Math.floor(elapsed.value / 60)
const s = elapsed.value % 60
return format(m, s)
}
onMounted(start)
onUnmounted(stop)
return { elapsed, formatted, start, stop }
}