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
273 lines
5.9 KiB
Vue
273 lines
5.9 KiB
Vue
<script setup lang="ts">
|
||
/**
|
||
* AgentNode — Einzelner Agenten-Knoten im FlowCanvas
|
||
*
|
||
* Props:
|
||
* agent – AgentNodeData
|
||
* left – x-Position in % (0–100)
|
||
* top – y-Position in % (0–100)
|
||
* entering – true wenn Node gerade frisch ins DOM kam (Enter-Animation)
|
||
*
|
||
* Emits:
|
||
* select – Agent ausgewählt (id)
|
||
*/
|
||
import type { AgentNodeData } from '../../../composables/useFlowLayout'
|
||
|
||
const props = defineProps<{
|
||
agent: AgentNodeData
|
||
left: number
|
||
top: number
|
||
entering?: boolean
|
||
}>()
|
||
|
||
defineEmits<{
|
||
select: [id: string]
|
||
}>()
|
||
</script>
|
||
|
||
<template>
|
||
<div
|
||
:class="[
|
||
'node',
|
||
agent.id === 'iris' ? 'is-iris' : `is-${agent.status}`,
|
||
{ entering }
|
||
]"
|
||
:style="{ left: left + '%', top: top + '%' }"
|
||
@click="$emit('select', agent.id)"
|
||
>
|
||
<div class="ncard">
|
||
<!-- Header: Avatar + Name + Role + Status-Dot -->
|
||
<div class="nc-top">
|
||
<div :class="['nc-av', { 'iris-av': agent.id === 'iris' }]">
|
||
<span v-html="agent.avatar === '</>' ? '</>' : agent.avatar"></span>
|
||
</div>
|
||
<div class="nc-info">
|
||
<div class="nc-name">{{ agent.name }}</div>
|
||
<div class="nc-role">{{ agent.role }}</div>
|
||
</div>
|
||
<span :class="['nc-stat', 'dot', agent.status]"></span>
|
||
</div>
|
||
|
||
<!-- Task (2-line clamp) -->
|
||
<div class="nc-task">{{ agent.task || 'Bereit · ' + agent.next }}</div>
|
||
|
||
<!-- Progress Bar -->
|
||
<div class="nc-bar">
|
||
<i :style="{ width: (agent.progress || 3) + '%' }"></i>
|
||
</div>
|
||
|
||
<!-- Meta-Zeile -->
|
||
<div class="nc-meta">
|
||
<span
|
||
class="st"
|
||
:style="{ color: `var(--st-${agent.status})` }"
|
||
>
|
||
{{ agent.statusLabel }}
|
||
</span>
|
||
<span>{{ agent.task ? (agent.progress + '% · ' + agent.elapsed) : agent.model }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.node {
|
||
position: absolute;
|
||
transform: translate(-50%, -50%);
|
||
z-index: 3;
|
||
width: 188px;
|
||
transition:
|
||
left 0.55s cubic-bezier(.4, 0, .2, 1),
|
||
top 0.55s cubic-bezier(.4, 0, .2, 1),
|
||
opacity 0.35s,
|
||
scale 0.35s;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
|
||
.node.entering {
|
||
opacity: 0;
|
||
scale: 0.7;
|
||
}
|
||
|
||
.ncard {
|
||
padding: 11px 12px;
|
||
border-radius: 13px;
|
||
background: var(--glass-2);
|
||
border: 1px solid var(--line-2);
|
||
backdrop-filter: blur(10px);
|
||
transition: transform 0.18s;
|
||
}
|
||
|
||
.ncard:hover {
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
/* Status-glow border */
|
||
.node.is-work .ncard {
|
||
border-color: rgba(61, 220, 151, 0.45);
|
||
box-shadow: 0 0 0 1px rgba(61, 220, 151, 0.2), 0 0 26px -6px rgba(61, 220, 151, 0.6);
|
||
}
|
||
|
||
.node.is-think .ncard {
|
||
border-color: rgba(52, 214, 245, 0.45);
|
||
box-shadow: 0 0 0 1px rgba(52, 214, 245, 0.2), 0 0 26px -6px rgba(52, 214, 245, 0.55);
|
||
}
|
||
|
||
.node.is-block .ncard {
|
||
border-color: rgba(251, 113, 133, 0.45);
|
||
box-shadow: 0 0 0 1px rgba(251, 113, 133, 0.2), 0 0 26px -6px rgba(251, 113, 133, 0.55);
|
||
}
|
||
|
||
.node.is-iris .ncard {
|
||
border-color: rgba(124, 108, 255, 0.55);
|
||
box-shadow: var(--glow);
|
||
background: linear-gradient(160deg, rgba(124, 108, 255, 0.2), rgba(28, 24, 64, 0.6));
|
||
}
|
||
|
||
.node.is-idle .ncard {
|
||
opacity: 0.7;
|
||
}
|
||
|
||
/* ── Card Content ────────────────────────────── */
|
||
.nc-top {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 9px;
|
||
}
|
||
|
||
.nc-av {
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 9px;
|
||
flex: 0 0 auto;
|
||
display: grid;
|
||
place-items: center;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-weight: 600;
|
||
font-size: 11px;
|
||
background: var(--grad-soft);
|
||
border: 1px solid var(--line-2);
|
||
color: var(--tx);
|
||
}
|
||
|
||
.nc-av.iris-av {
|
||
background: var(--grad);
|
||
color: #fff;
|
||
box-shadow: var(--glow-purple);
|
||
}
|
||
|
||
.nc-av :deep(svg) {
|
||
display: block;
|
||
}
|
||
|
||
.nc-info {
|
||
min-width: 0;
|
||
flex: 1;
|
||
}
|
||
|
||
.nc-name {
|
||
font-family: 'Space Grotesk', sans-serif;
|
||
font-weight: 600;
|
||
font-size: 13px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
color: var(--tx);
|
||
}
|
||
|
||
.nc-role {
|
||
font-size: 10px;
|
||
color: var(--tx-3);
|
||
margin-top: 2px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.nc-stat {
|
||
margin-left: auto;
|
||
flex: 0 0 auto;
|
||
}
|
||
|
||
.dot {
|
||
width: 9px;
|
||
height: 9px;
|
||
border-radius: 50%;
|
||
flex: 0 0 auto;
|
||
display: block;
|
||
}
|
||
|
||
.dot.work {
|
||
background: var(--st-work);
|
||
box-shadow: 0 0 0 0 rgba(61, 220, 151, 0.55);
|
||
animation: pulse-work 1.8s infinite;
|
||
}
|
||
|
||
.dot.think {
|
||
background: var(--st-think);
|
||
box-shadow: 0 0 0 0 rgba(52, 214, 245, 0.55);
|
||
animation: pulse-think 1.8s infinite;
|
||
}
|
||
|
||
.dot.idle {
|
||
background: var(--st-idle);
|
||
}
|
||
|
||
.dot.block {
|
||
background: var(--st-block);
|
||
box-shadow: 0 0 0 0 rgba(251, 113, 133, 0.55);
|
||
animation: pulse-block 1.8s infinite;
|
||
}
|
||
|
||
/* ── Task ────────────────────────────────────── */
|
||
.nc-task {
|
||
font-size: 11px;
|
||
color: var(--tx-2);
|
||
margin-top: 8px;
|
||
line-height: 1.4;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
min-height: 28px;
|
||
}
|
||
|
||
/* ── Progress Bar ────────────────────────────── */
|
||
.nc-bar {
|
||
height: 4px;
|
||
border-radius: 4px;
|
||
background: rgba(124, 108, 255, 0.12);
|
||
overflow: hidden;
|
||
margin-top: 7px;
|
||
}
|
||
|
||
.nc-bar i {
|
||
display: block;
|
||
height: 100%;
|
||
border-radius: 4px;
|
||
background: var(--grad);
|
||
transition: width 0.4s ease;
|
||
}
|
||
|
||
.node.is-work .nc-bar i {
|
||
background: linear-gradient(90deg, #2bb87f, #3ddc97);
|
||
}
|
||
|
||
/* ── Meta ────────────────────────────────────── */
|
||
.nc-meta {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
font-family: 'JetBrains Mono', monospace;
|
||
font-size: 10px;
|
||
color: var(--tx-3);
|
||
margin-top: 5px;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
.nc-meta .st {
|
||
font-weight: 600;
|
||
}
|
||
|
||
</style>
|