feat: Mission Control Dashboard – AI Team Network, Chat, Queue
- 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:
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
Reference in New Issue
Block a user