Compare commits

..

6 Commits

Author SHA1 Message Date
devops f037aa2eeb chore: bump version to v0.2.28 [skip ci] 2026-06-09 21:30:00 +00:00
developer e6520fc26d fix: Queue → Chat Queue umbenannt
CI - Build & Test / Backend (.NET) (push) Successful in 23s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 16s
CI - Build & Test / Security Check (push) Successful in 3s
2026-06-09 23:29:11 +02:00
devops c9d8852609 chore: bump version to v0.2.27 [skip ci] 2026-06-09 21:22:19 +00:00
developer 11e9a257a1 fix: AgentNodeData um tags, task, runtime erweitert – Cards wieder sichtbar
CI - Build & Test / Backend (.NET) (push) Successful in 22s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 19s
CI - Build & Test / Security Check (push) Successful in 3s
- Interface: tags:string[], task:string, runtime:string, hero?:boolean
- Alle 5 Agenten mit Tags, Task, Runtime, Hero befüllt
2026-06-09 23:21:31 +02:00
devops ead202ad8b chore: bump version to v0.2.26 [skip ci] 2026-06-09 21:16:23 +00:00
developer effc86e15b feat: LLM Model + Glassmorphism + Doppel-Pulse + Task-Board
CI - Build & Test / Backend (.NET) (push) Successful in 26s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 16s
CI - Build & Test / Security Check (push) Successful in 3s
- 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
2026-06-09 23:15:33 +02:00
6 changed files with 111 additions and 14 deletions
+1 -1
View File
@@ -1 +1 @@
0.2.25 0.2.28
@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import { Plus, Circle } from '@lucide/vue' import { Plus, Circle, ChevronRight } from '@lucide/vue'
import type { OpenTask } from '../../composables/useDashboardData' import type { OpenTask } from '../../composables/useDashboardData'
defineProps<{ defineProps<{
@@ -9,6 +9,7 @@ defineProps<{
const emit = defineEmits<{ const emit = defineEmits<{
newTask: [] newTask: []
'go-board': []
}>() }>()
const expandedId = ref<string | null>(null) const expandedId = ref<string | null>(null)
@@ -67,6 +68,11 @@ function toggleExpand(id: string) {
</div> </div>
</TransitionGroup> </TransitionGroup>
</div> </div>
<button class="task-board-btn" @click="emit('go-board')">
<span>Zum Task Board</span>
<ChevronRight :size="14" />
</button>
</div> </div>
</template> </template>
@@ -223,6 +229,20 @@ function toggleExpand(id: string) {
line-height: 1.5; line-height: 1.5;
} }
.task-board-btn {
width: 100%; margin-top: 12px; padding: 10px;
display: flex; align-items: center; justify-content: center; gap: 6px;
background: rgba(139, 124, 246, 0.08);
border: 1px solid rgba(139, 124, 246, 0.15);
border-radius: 10px; color: #a78bfa;
font-size: 10px; font-weight: 600; cursor: pointer;
transition: all 0.2s;
}
.task-board-btn:hover {
background: rgba(139, 124, 246, 0.15);
border-color: rgba(139, 124, 246, 0.3);
}
/* TransitionGroup */ /* TransitionGroup */
.task-enter-active { .task-enter-active {
transition: all 0.3s ease; transition: all 0.3s ease;
@@ -76,7 +76,7 @@ function onDragEnd(): void {
<div class="queue-header" @click="expanded = !expanded"> <div class="queue-header" @click="expanded = !expanded">
<div class="queue-header-left"> <div class="queue-header-left">
<ListTodo :size="14" class="queue-icon" /> <ListTodo :size="14" class="queue-icon" />
<h2>Queue</h2> <h2>Chat Queue</h2>
<span class="queue-count">{{ items.length }}</span> <span class="queue-count">{{ items.length }}</span>
</div> </div>
<button class="queue-toggle" aria-label="Toggle"> <button class="queue-toggle" aria-label="Toggle">
@@ -13,6 +13,7 @@ interface AgentData {
hero?: boolean hero?: boolean
task?: string task?: string
runtime?: string runtime?: string
model?: string
} }
const props = defineProps<{ const props = defineProps<{
@@ -172,9 +173,17 @@ function refreshPathLengths() {
if (pulseOffsets.value[id] === undefined) { if (pulseOffsets.value[id] === undefined) {
pulseOffsets.value[id] = 0 pulseOffsets.value[id] = 0
} }
pulseEl.setAttribute('stroke-dasharray', `10 ${p.length}`) pulseEl.setAttribute('stroke-dasharray', `40 ${p.length}`)
pulseEl.setAttribute('stroke-dashoffset', String(-pulseOffsets.value[id])) pulseEl.setAttribute('stroke-dashoffset', String(-pulseOffsets.value[id]))
} }
const pulseEl2 = pulseElements2.value[id]
if (pulseEl2 && p && p.length > 0) {
if (pulseOffsets2.value[id] === undefined) {
pulseOffsets2.value[id] = 0
}
pulseEl2.setAttribute('stroke-dasharray', `40 ${p.length}`)
pulseEl2.setAttribute('stroke-dashoffset', String(-pulseOffsets2.value[id]))
}
} }
} }
@@ -190,6 +199,9 @@ function startPulseAnimation() {
if (pulseOffsets.value[id] === undefined) { if (pulseOffsets.value[id] === undefined) {
pulseOffsets.value[id] = 0 pulseOffsets.value[id] = 0
} }
if (pulseOffsets2.value[id] === undefined) {
pulseOffsets2.value[id] = 0
}
} }
} }
@@ -204,18 +216,28 @@ function startPulseAnimation() {
const id = children[i].id const id = children[i].id
const pathEl = pathElements.value[id] const pathEl = pathElements.value[id]
const pulseEl = pulseElements.value[id] const pulseEl = pulseElements.value[id]
const pulseEl2 = pulseElements2.value[id]
const p = connectionPaths.value[id] const p = connectionPaths.value[id]
if (!pathEl || !pulseEl || !p) continue if (!pathEl || !pulseEl || !p) continue
const len = p.length const len = p.length
if (len <= 0) continue if (len <= 0) continue
const currentOffset = pulseOffsets.value[id] ?? 0 const speed = speeds[id] ?? len / 3000
const newOffset = currentOffset + (speeds[id] ?? len / 3000) * dt const cycleLen = len + 40
const cycleLen = len + 10
pulseOffsets.value[id] = newOffset > cycleLen ? newOffset % cycleLen : newOffset
// Pulse 1
const currentOffset = pulseOffsets.value[id] ?? 0
const newOffset = currentOffset + speed * dt
pulseOffsets.value[id] = newOffset > cycleLen ? newOffset % cycleLen : newOffset
pulseEl.setAttribute('stroke-dashoffset', String(-pulseOffsets.value[id])) pulseEl.setAttribute('stroke-dashoffset', String(-pulseOffsets.value[id]))
// Pulse 2 (offset by half cycle)
if (pulseEl2) {
const offset2 = (pulseOffsets.value[id] + cycleLen / 2) % cycleLen
pulseOffsets2.value[id] = offset2
pulseEl2.setAttribute('stroke-dashoffset', String(-offset2))
}
} }
animFrameId = requestAnimationFrame(tick) animFrameId = requestAnimationFrame(tick)
@@ -316,7 +338,7 @@ onUnmounted(() => {
opacity="0.5" opacity="0.5"
/> />
<!-- Pulse line (white dashed segment moving along) --> <!-- Pulse line 1 (white dashed segment moving along) -->
<path <path
v-if="connectionPaths[agent.id]" v-if="connectionPaths[agent.id]"
:ref="storePulseRef(agent.id)" :ref="storePulseRef(agent.id)"
@@ -328,6 +350,19 @@ onUnmounted(() => {
stroke-linejoin="round" stroke-linejoin="round"
:opacity="isActive(agent.id) ? 1 : 0.4" :opacity="isActive(agent.id) ? 1 : 0.4"
/> />
<!-- Pulse line 2 (offset by half cycle) -->
<path
v-if="connectionPaths[agent.id]"
:ref="storePulseRef2(agent.id)"
:d="connectionPaths[agent.id]!.d"
stroke="white"
stroke-width="3"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
:opacity="isActive(agent.id) ? 0.8 : 0.3"
/>
</template> </template>
</svg> </svg>
@@ -362,6 +397,7 @@ onUnmounted(() => {
{{ hero.task }} {{ hero.task }}
</span> </span>
<span v-if="hero.runtime" class="node-runtime">{{ hero.runtime }}</span> <span v-if="hero.runtime" class="node-runtime">{{ hero.runtime }}</span>
<span v-if="hero.model" class="node-model">{{ hero.model }}</span>
</div> </div>
<div class="card-tags"> <div class="card-tags">
<span v-for="tag in hero.tags" :key="tag" class="card-tag" :style="{ background: `${hero.color}18`, color: hero.color }">{{ tag }}</span> <span v-for="tag in hero.tags" :key="tag" class="card-tag" :style="{ background: `${hero.color}18`, color: hero.color }">{{ tag }}</span>
@@ -409,6 +445,7 @@ onUnmounted(() => {
{{ agent.task }} {{ agent.task }}
</span> </span>
<span v-if="agent.runtime" class="node-runtime">{{ agent.runtime }}</span> <span v-if="agent.runtime" class="node-runtime">{{ agent.runtime }}</span>
<span v-if="agent.model" class="node-model">{{ agent.model }}</span>
</div> </div>
<div class="card-tags"> <div class="card-tags">
<span v-for="tag in agent.tags" :key="tag" class="card-tag" :style="{ background: `${agent.color}18`, color: agent.color }">{{ tag }}</span> <span v-for="tag in agent.tags" :key="tag" class="card-tag" :style="{ background: `${agent.color}18`, color: agent.color }">{{ tag }}</span>
@@ -471,24 +508,31 @@ onUnmounted(() => {
/* ── Agent Card (inlined from old AgentCard.vue) ── */ /* ── Agent Card (inlined from old AgentCard.vue) ── */
.agent-card { .agent-card {
background: var(--panel, #11141b); background: rgba(18, 22, 30, 0.45);
border: 1px solid var(--line, #1f2330); backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 12px; border-radius: 12px;
padding: 18px; padding: 18px;
cursor: pointer; cursor: pointer;
transition: border-color 0.2s, box-shadow 0.2s; transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
} }
.agent-card:hover { .agent-card:hover {
background: rgba(18, 22, 30, 0.65);
border-color: var(--card-color, #8b7cf6); border-color: var(--card-color, #8b7cf6);
box-shadow: 0 0 16px color-mix(in srgb, var(--card-color, #8b7cf6) 10%, transparent); box-shadow: 0 0 16px color-mix(in srgb, var(--card-color, #8b7cf6) 10%, transparent);
} }
.hero-card { .hero-card {
border-color: rgba(139, 124, 246, 0.2); background: rgba(18, 22, 30, 0.45);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.06);
box-shadow: 0 0 20px rgba(139, 124, 246, 0.06); box-shadow: 0 0 20px rgba(139, 124, 246, 0.06);
} }
.hero-card:hover { .hero-card:hover {
background: rgba(18, 22, 30, 0.65);
border-color: #8b7cf6; border-color: #8b7cf6;
box-shadow: 0 0 24px rgba(139, 124, 246, 0.12); box-shadow: 0 0 24px rgba(139, 124, 246, 0.12);
} }
@@ -567,6 +611,13 @@ onUnmounted(() => {
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
flex-shrink: 0; flex-shrink: 0;
} }
.node-model {
font-size: 8.5px;
color: #6b7385;
font-weight: 500;
flex-shrink: 0;
margin-left: 6px;
}
/* ── Tags ── */ /* ── Tags ── */
.card-tags { .card-tags {
@@ -5,8 +5,13 @@ export interface AgentNodeData {
name: string name: string
role: string role: string
description: string description: string
tags: string[]
color: string color: string
icon: string icon: string
task: string
runtime: string
model?: string
hero?: boolean
currentTask: string currentTask: string
goal: string goal: string
progress: number progress: number
@@ -103,14 +108,19 @@ export function useDashboardData() {
name: 'Iris', name: 'Iris',
role: 'Chief of Staff', role: 'Chief of Staff',
description: 'Koordiniert, delegiert, hält das Team tight. Die erste Anlaufstelle zwischen Boss und Maschine.', description: 'Koordiniert, delegiert, hält das Team tight. Die erste Anlaufstelle zwischen Boss und Maschine.',
tags: ['Orchestration', 'Delegation', 'Approval'],
color: '#8b7cf6', color: '#8b7cf6',
icon: 'bot', icon: 'bot',
hero: true,
task: 'Orchestrating Nexus Dashboard redesign',
runtime: '14:23',
currentTask: 'Orchestrating Nexus Dashboard redesign', currentTask: 'Orchestrating Nexus Dashboard redesign',
goal: 'Complete Mission Control v3', goal: 'Complete Mission Control v3',
progress: 85, progress: 85,
workload: 55, workload: 55,
active: true, active: true,
runtimeSeconds: 28800, runtimeSeconds: 28800,
model: 'GPT-5.4',
workingFeed: [ workingFeed: [
{ time: '22:38', text: 'Analyzed user feedback on Dashboard' }, { time: '22:38', text: 'Analyzed user feedback on Dashboard' },
{ time: '22:36', text: 'Delegated card redesign to Developer' }, { time: '22:36', text: 'Delegated card redesign to Developer' },
@@ -128,8 +138,11 @@ export function useDashboardData() {
name: 'Developer', name: 'Developer',
role: 'Backend & Frontend', role: 'Backend & Frontend',
description: 'Implements features across the stack with TypeScript, C#, and Vue.', description: 'Implements features across the stack with TypeScript, C#, and Vue.',
tags: ['Coding', 'Development', 'Builds'],
color: '#3b82f6', color: '#3b82f6',
icon: 'code', icon: 'code',
task: 'Building Dungeon System API endpoints',
runtime: '01:00',
currentTask: 'Building Dungeon System API endpoints', currentTask: 'Building Dungeon System API endpoints',
goal: 'Complete Dungeon CRUD + room generation', goal: 'Complete Dungeon CRUD + room generation',
progress: 62, progress: 62,
@@ -147,14 +160,18 @@ export function useDashboardData() {
{ time: '22:23', text: 'Designing RoomFactory interface' }, { time: '22:23', text: 'Designing RoomFactory interface' },
{ time: '22:24', text: 'Implementing corridor connection logic' }, { time: '22:24', text: 'Implementing corridor connection logic' },
], ],
model: 'DeepSeek V4 Flash',
}, },
{ {
id: 'devops', id: 'devops',
name: 'DevOps', name: 'DevOps',
role: 'Infrastructure & CI/CD', role: 'Infrastructure & CI/CD',
description: 'Manages Docker, deployment pipelines, and system reliability.', description: 'Manages Docker, deployment pipelines, and system reliability.',
tags: ['Deployment', 'Docker', 'CI/CD'],
color: '#eab308', color: '#eab308',
icon: 'server', icon: 'server',
task: 'Optimizing Docker Compose caching',
runtime: '00:30',
currentTask: 'Optimizing Docker Compose caching', currentTask: 'Optimizing Docker Compose caching',
goal: 'Reduce build times by 40%', goal: 'Reduce build times by 40%',
progress: 45, progress: 45,
@@ -172,14 +189,18 @@ export function useDashboardData() {
{ time: '22:21', text: 'Benchmarking multi-stage vs single-stage' }, { time: '22:21', text: 'Benchmarking multi-stage vs single-stage' },
{ time: '22:22', text: 'Calculating potential speedup from caching' }, { time: '22:22', text: 'Calculating potential speedup from caching' },
], ],
model: 'DeepSeek V4 Pro',
}, },
{ {
id: 'researcher', id: 'researcher',
name: 'Researcher', name: 'Researcher',
role: 'Analysis & Documentation', role: 'Analysis & Documentation',
description: 'Researches APIs, patterns, and best practices. Maintains docs.', description: 'Researches APIs, patterns, and best practices. Maintains docs.',
tags: ['Research', 'Analysis', 'Docs'],
color: '#22c55e', color: '#22c55e',
icon: 'search', icon: 'search',
task: 'Analyzing WebSocket alternatives',
runtime: '00:45',
currentTask: 'Analyzing WebSocket alternatives', currentTask: 'Analyzing WebSocket alternatives',
goal: 'Recommend real-time communication strategy', goal: 'Recommend real-time communication strategy',
progress: 30, progress: 30,
@@ -196,14 +217,18 @@ export function useDashboardData() {
{ time: '22:19', text: 'Checking SSE browser support matrix' }, { time: '22:19', text: 'Checking SSE browser support matrix' },
{ time: '22:20', text: 'Drafting recommendation summary' }, { time: '22:20', text: 'Drafting recommendation summary' },
], ],
model: 'DeepSeek V4 Pro',
}, },
{ {
id: 'reviewer', id: 'reviewer',
name: 'Reviewer', name: 'Reviewer',
role: 'Code Quality & Testing', role: 'Code Quality & Testing',
description: 'Reviews pull requests, enforces standards, runs test suites.', description: 'Reviews pull requests, enforces standards, runs test suites.',
tags: ['Code Review', 'Testing', 'Quality'],
color: '#a855f7', color: '#a855f7',
icon: 'shield', icon: 'shield',
task: 'Reviewing Dungeon System PR',
runtime: '00:15',
currentTask: 'Reviewing Dungeon System PR', currentTask: 'Reviewing Dungeon System PR',
goal: 'Zero critical findings before merge', goal: 'Zero critical findings before merge',
progress: 80, progress: 80,
@@ -221,6 +246,7 @@ export function useDashboardData() {
{ time: '22:16', text: 'Checking RoomValidator edge cases' }, { time: '22:16', text: 'Checking RoomValidator edge cases' },
{ time: '22:17', text: 'Verifying integration test coverage' }, { time: '22:17', text: 'Verifying integration test coverage' },
], ],
model: 'DeepSeek V4 Pro',
}, },
]) ])
+1 -1
View File
@@ -48,7 +48,7 @@ function onQueueExecuteNow(id: string): void {
<div class="dashboard"> <div class="dashboard">
<div class="col-left"> <div class="col-left">
<section class="missions-section"> <section class="missions-section">
<TaskCard :tasks="openTasks" @new-task="console.log('New task requested')" /> <TaskCard :tasks="openTasks" @new-task="console.log('New task requested')" @go-board="console.log('Go to Task Board')" />
</section> </section>
<OperationsFeed :entries="feedEntries" /> <OperationsFeed :entries="feedEntries" />
</div> </div>