3c95281119
- 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
346 lines
7.7 KiB
Vue
346 lines
7.7 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref } from 'vue'
|
|
import { Bot, Sparkles } from '@lucide/vue'
|
|
import AgentNode from './AgentNode.vue'
|
|
import AgentModal from './AgentModal.vue'
|
|
import type { AgentNodeData } from '../../composables/useDashboardData'
|
|
|
|
const props = defineProps<{
|
|
agents: AgentNodeData[]
|
|
irisRuntime: string
|
|
getAgentRuntime: (id: string) => string
|
|
irisFocus: string
|
|
}>()
|
|
|
|
const selectedAgent = ref<AgentNodeData | null>(null)
|
|
|
|
function onAgentSelect(agentId: string): void {
|
|
const agent = props.agents.find(a => a.id === agentId)
|
|
if (agent) selectedAgent.value = agent
|
|
}
|
|
|
|
function closeModal(): void {
|
|
selectedAgent.value = null
|
|
}
|
|
|
|
const agentColorMap: Record<string, string> = {
|
|
developer: '#3b82f6',
|
|
devops: '#eab308',
|
|
researcher: '#22c55e',
|
|
reviewer: '#a855f7',
|
|
}
|
|
|
|
const agentLineActive: Record<string, boolean> = {
|
|
developer: true,
|
|
devops: false,
|
|
researcher: true,
|
|
reviewer: false,
|
|
}
|
|
|
|
const NETWORK_W = 440
|
|
const IRIS_CX = NETWORK_W / 2
|
|
const IRIS_CY = 80
|
|
const AGENT_START_Y = 170
|
|
|
|
const agentPositions = computed(() => [
|
|
{ id: 'developer', x: 60, y: AGENT_START_Y },
|
|
{ id: 'researcher', x: NETWORK_W - 60, y: AGENT_START_Y },
|
|
{ id: 'devops', x: 60, y: AGENT_START_Y + 110 },
|
|
{ id: 'reviewer', x: NETWORK_W - 60, y: AGENT_START_Y + 110 },
|
|
])
|
|
|
|
const activeLines = computed(() =>
|
|
agentPositions.value.filter(p => agentLineActive[p.id])
|
|
)
|
|
</script>
|
|
|
|
<template>
|
|
<div class="team-network">
|
|
<!-- Iris Node -->
|
|
<div class="iris-node">
|
|
<div class="iris-avatar-ring">
|
|
<svg class="ring-svg" viewBox="0 0 60 60" width="60" height="60">
|
|
<circle cx="30" cy="30" r="27" fill="none" stroke="rgba(167,139,250,0.12)" stroke-width="2" />
|
|
<circle
|
|
cx="30" cy="30" r="27"
|
|
fill="none" stroke="#a78bfa" stroke-width="2"
|
|
stroke-dasharray="169.6" stroke-dashoffset="42"
|
|
stroke-linecap="round"
|
|
transform="rotate(-90 30 30)"
|
|
class="ring-arc"
|
|
/>
|
|
</svg>
|
|
<div class="iris-avatar-inner">
|
|
<Bot :size="26" />
|
|
</div>
|
|
</div>
|
|
|
|
<div class="iris-name-block">
|
|
<h2>Iris</h2>
|
|
<span class="iris-role-label">Chief of Staff</span>
|
|
</div>
|
|
|
|
<p class="iris-tagline">Breaking down tasks and coordinating specialists</p>
|
|
|
|
<div class="iris-runtime">
|
|
<span class="rt-label">Session Runtime</span>
|
|
<span class="rt-value">{{ irisRuntime }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- SVG Connections -->
|
|
<svg
|
|
class="network-svg"
|
|
:viewBox="`0 0 ${NETWORK_W} 400`"
|
|
preserveAspectRatio="xMidYMid meet"
|
|
>
|
|
<defs>
|
|
<filter id="lineglow">
|
|
<feGaussianBlur stdDeviation="2" result="blur" />
|
|
<feMerge>
|
|
<feMergeNode in="blur" />
|
|
<feMergeNode in="SourceGraphic" />
|
|
</feMerge>
|
|
</filter>
|
|
</defs>
|
|
|
|
<line
|
|
v-for="pos in agentPositions"
|
|
:key="'conn-' + pos.id"
|
|
:x1="IRIS_CX" :y1="IRIS_CY + 32"
|
|
:x2="pos.x + 22" :y2="pos.y"
|
|
:stroke="agentColorMap[pos.id]"
|
|
:stroke-width="agentLineActive[pos.id] ? 1.5 : 1"
|
|
:opacity="agentLineActive[pos.id] ? 0.4 : 0.1"
|
|
class="conn-line"
|
|
:class="{ 'line-pulse': agentLineActive[pos.id] }"
|
|
filter="url(#lineglow)"
|
|
/>
|
|
|
|
<g v-for="pos in activeLines" :key="'fx-' + pos.id">
|
|
<circle
|
|
:cx="pos.x + 22" :cy="pos.y"
|
|
r="2.5" fill="#ffffff"
|
|
class="pulse-end" :style="{ '--pulse-color': agentColorMap[pos.id] }"
|
|
/>
|
|
<circle
|
|
:cx="IRIS_CX" :cy="IRIS_CY + 32"
|
|
r="2.5" fill="#ffffff"
|
|
class="pulse-origin"
|
|
/>
|
|
</g>
|
|
</svg>
|
|
|
|
<!-- Agent Cards -->
|
|
<div class="agents-grid">
|
|
<AgentNode
|
|
v-for="agent in agents"
|
|
:key="agent.id"
|
|
:agent="agent"
|
|
:runtime="getAgentRuntime(agent.id)"
|
|
@select="onAgentSelect"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Focus Banner -->
|
|
<div v-if="irisFocus" class="focus-banner">
|
|
<Sparkles :size="12" class="focus-icon" />
|
|
<span>{{ irisFocus }}</span>
|
|
</div>
|
|
|
|
<!-- Agent Modal -->
|
|
<AgentModal
|
|
v-if="selectedAgent"
|
|
:agent="selectedAgent"
|
|
:runtime="getAgentRuntime(selectedAgent.id)"
|
|
@close="closeModal"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.team-network {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 24px 20px 20px;
|
|
background: rgba(22, 27, 34, 0.75);
|
|
border: 1px solid rgba(139, 124, 246, 0.12);
|
|
border-radius: 16px;
|
|
backdrop-filter: blur(12px);
|
|
-webkit-backdrop-filter: blur(12px);
|
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
|
|
transition: border-color 0.2s ease;
|
|
position: relative;
|
|
overflow: hidden;
|
|
min-height: 520px;
|
|
}
|
|
.team-network:hover {
|
|
border-color: rgba(139, 124, 246, 0.18);
|
|
}
|
|
|
|
/* Iris Node */
|
|
.iris-node {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 20px 28px;
|
|
background: rgba(167, 139, 250, 0.06);
|
|
border: 1px solid rgba(167, 139, 250, 0.15);
|
|
border-radius: 14px;
|
|
position: relative;
|
|
z-index: 2;
|
|
width: 100%;
|
|
max-width: 320px;
|
|
}
|
|
.iris-avatar-ring {
|
|
position: relative;
|
|
width: 60px;
|
|
height: 60px;
|
|
}
|
|
.ring-svg {
|
|
position: absolute;
|
|
inset: 0;
|
|
}
|
|
.ring-arc {
|
|
animation: iris-spin 3s linear infinite;
|
|
}
|
|
@keyframes iris-spin {
|
|
to { transform: rotate(270deg); }
|
|
}
|
|
.iris-avatar-inner {
|
|
width: 60px;
|
|
height: 60px;
|
|
display: grid;
|
|
place-items: center;
|
|
border-radius: 50%;
|
|
background: rgba(167, 139, 250, 0.15);
|
|
color: #a78bfa;
|
|
}
|
|
.iris-name-block {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 2px;
|
|
}
|
|
.iris-name-block h2 {
|
|
margin: 0;
|
|
font-size: 18px;
|
|
font-weight: 700;
|
|
color: #e8eaf0;
|
|
}
|
|
.iris-role-label {
|
|
font-size: 9px;
|
|
color: #a78bfa;
|
|
font-weight: 600;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
.iris-tagline {
|
|
margin: 2px 0 0;
|
|
font-size: 10.5px;
|
|
color: #6b7385;
|
|
text-align: center;
|
|
line-height: 1.35;
|
|
}
|
|
.iris-runtime {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-top: 6px;
|
|
padding: 5px 14px;
|
|
background: rgba(167, 139, 250, 0.08);
|
|
border: 1px solid rgba(167, 139, 250, 0.1);
|
|
border-radius: 20px;
|
|
}
|
|
.rt-label {
|
|
font-size: 8px;
|
|
color: #7e8799;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
}
|
|
.rt-value {
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
font-variant-numeric: tabular-nums;
|
|
color: #a78bfa;
|
|
}
|
|
|
|
/* SVG Lines */
|
|
.network-svg {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
pointer-events: none;
|
|
z-index: 1;
|
|
}
|
|
.conn-line {
|
|
transition: opacity 0.4s ease, stroke-width 0.4s ease;
|
|
}
|
|
.line-pulse {
|
|
animation: line-glow 2s ease-in-out infinite;
|
|
}
|
|
@keyframes line-glow {
|
|
0%, 100% { opacity: 0.4; }
|
|
50% { opacity: 0.7; }
|
|
}
|
|
.pulse-origin {
|
|
animation: pulse-origin 2s ease-in-out infinite;
|
|
}
|
|
.pulse-end {
|
|
animation: pulse-end 2s ease-in-out infinite;
|
|
}
|
|
@keyframes pulse-origin {
|
|
0%, 100% { opacity: 0; r: 1.5; }
|
|
50% { opacity: 0.8; r: 3.5; }
|
|
}
|
|
@keyframes pulse-end {
|
|
0%, 100% { opacity: 0.2; r: 1.5; }
|
|
50% { opacity: 0.9; r: 3; }
|
|
}
|
|
|
|
/* Agent Cards */
|
|
.agents-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 10px;
|
|
width: 100%;
|
|
position: relative;
|
|
z-index: 2;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
/* Focus Banner */
|
|
.focus-banner {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
width: 100%;
|
|
padding: 10px 16px;
|
|
background: rgba(234, 179, 8, 0.05);
|
|
border: 1px solid rgba(234, 179, 8, 0.1);
|
|
border-radius: 10px;
|
|
margin-top: 4px;
|
|
position: relative;
|
|
z-index: 2;
|
|
}
|
|
.focus-icon {
|
|
color: #eab308;
|
|
flex-shrink: 0;
|
|
}
|
|
.focus-banner span {
|
|
font-size: 10.5px;
|
|
color: #eab308;
|
|
line-height: 1.35;
|
|
}
|
|
|
|
@media (max-width: 700px) {
|
|
.agents-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|