Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 745e202e21 | |||
| 5244e9fd3d |
@@ -20,7 +20,7 @@ const activeView = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const routePaths: Record<string, string> = {
|
const routePaths: Record<string, string> = {
|
||||||
Dashboard: '/dashboard', Memory: '/memory', Docs: '/docs', Team: '/team', Security: '/security',
|
Dashboard: '/dashboard', Memory: '/memory', Docs: '/docs', Security: '/security',
|
||||||
Projects: '/projects', 'Task Board': '/tasks', Incidents: '/incidents', Calendar: '/calendar',
|
Projects: '/projects', 'Task Board': '/tasks', Incidents: '/incidents', Calendar: '/calendar',
|
||||||
Agents: '/agents', Models: '/models', Activity: '/activity', 'Mobile Chat': '/chat', Settings: '/settings',
|
Agents: '/agents', Models: '/models', Activity: '/activity', 'Mobile Chat': '/chat', Settings: '/settings',
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ const navigate = (label: string) => {
|
|||||||
}
|
}
|
||||||
const mobileNavOpen = ref(false)
|
const mobileNavOpen = ref(false)
|
||||||
|
|
||||||
const standaloneViews = computed(() => ['Dashboard', 'Settings', 'ProjectDetail', 'Memory', 'Docs', 'Team', 'Security', 'Incidents', 'Calendar', 'AgentDetail', 'Agents'].includes(activeView.value))
|
const standaloneViews = computed(() => ['Dashboard', 'Settings', 'ProjectDetail', 'Memory', 'Docs', 'Security', 'Incidents', 'Calendar', 'AgentDetail', 'Agents'].includes(activeView.value))
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (auth.isAuthenticated) store.refresh()
|
if (auth.isAuthenticated) store.refresh()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue'
|
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
|
||||||
import { Bot, Sparkles } from '@lucide/vue'
|
import { Bot, Sparkles } from '@lucide/vue'
|
||||||
import AgentNode from './AgentNode.vue'
|
import AgentNode from './AgentNode.vue'
|
||||||
import AgentModal from './AgentModal.vue'
|
import AgentModal from './AgentModal.vue'
|
||||||
@@ -23,41 +23,217 @@ function closeModal(): void {
|
|||||||
selectedAgent.value = null
|
selectedAgent.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
const agentColorMap: Record<string, string> = {
|
// ── Layout measurement ──
|
||||||
developer: '#3b82f6',
|
const networkRef = ref<HTMLDivElement | null>(null)
|
||||||
devops: '#eab308',
|
|
||||||
researcher: '#22c55e',
|
interface CardBox {
|
||||||
reviewer: '#a855f7',
|
left: number
|
||||||
|
right: number
|
||||||
|
top: number
|
||||||
|
bottom: number
|
||||||
|
cx: number
|
||||||
|
cy: number
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
}
|
||||||
|
const cardPositions = ref<Record<string, CardBox>>({})
|
||||||
|
const svgWidth = ref(0)
|
||||||
|
const svgHeight = ref(0)
|
||||||
|
|
||||||
|
function updatePositions() {
|
||||||
|
if (!networkRef.value) return
|
||||||
|
const rect = networkRef.value.getBoundingClientRect()
|
||||||
|
svgWidth.value = rect.width
|
||||||
|
svgHeight.value = rect.height
|
||||||
|
|
||||||
|
const positions: Record<string, CardBox> = {}
|
||||||
|
|
||||||
|
const irisEl = networkRef.value.querySelector('[data-agent-id="iris"]')
|
||||||
|
if (irisEl) {
|
||||||
|
const r = irisEl.getBoundingClientRect()
|
||||||
|
positions['iris'] = {
|
||||||
|
left: r.left - rect.left,
|
||||||
|
right: r.left + r.width - rect.left,
|
||||||
|
top: r.top - rect.top,
|
||||||
|
bottom: r.top + r.height - rect.top,
|
||||||
|
cx: r.left + r.width / 2 - rect.left,
|
||||||
|
cy: r.top + r.height / 2 - rect.top,
|
||||||
|
width: r.width,
|
||||||
|
height: r.height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cards = networkRef.value.querySelectorAll('[data-agent-id]')
|
||||||
|
cards.forEach(el => {
|
||||||
|
const id = el.getAttribute('data-agent-id')
|
||||||
|
if (!id || id === 'iris') return
|
||||||
|
const r = el.getBoundingClientRect()
|
||||||
|
positions[id] = {
|
||||||
|
left: r.left - rect.left,
|
||||||
|
right: r.left + r.width - rect.left,
|
||||||
|
top: r.top - rect.top,
|
||||||
|
bottom: r.top + r.height - rect.top,
|
||||||
|
cx: r.left + r.width / 2 - rect.left,
|
||||||
|
cy: r.top + r.height / 2 - rect.top,
|
||||||
|
width: r.width,
|
||||||
|
height: r.height,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
cardPositions.value = positions
|
||||||
}
|
}
|
||||||
|
|
||||||
const agentLineActive: Record<string, boolean> = {
|
// ── SVG connection paths ──
|
||||||
developer: true,
|
interface ConnectionPath {
|
||||||
devops: false,
|
d: string
|
||||||
researcher: true,
|
length: number
|
||||||
reviewer: false,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const NETWORK_W = 440
|
const connectionPaths = computed<Record<string, ConnectionPath | null>>(() => {
|
||||||
const IRIS_CX = NETWORK_W / 2
|
const result: Record<string, ConnectionPath | null> = {}
|
||||||
const IRIS_CY = 80
|
const pos = cardPositions.value
|
||||||
const AGENT_START_Y = 170
|
const iris = pos['iris']
|
||||||
|
if (!iris) return result
|
||||||
|
|
||||||
const agentPositions = computed(() => [
|
for (const agent of props.agents) {
|
||||||
{ id: 'developer', x: 60, y: AGENT_START_Y },
|
const agentPos = pos[agent.id]
|
||||||
{ id: 'researcher', x: NETWORK_W - 60, y: AGENT_START_Y },
|
if (!agentPos) {
|
||||||
{ id: 'devops', x: 60, y: AGENT_START_Y + 110 },
|
result[agent.id] = null
|
||||||
{ id: 'reviewer', x: NETWORK_W - 60, y: AGENT_START_Y + 110 },
|
continue
|
||||||
])
|
}
|
||||||
|
|
||||||
const activeLines = computed(() =>
|
const startX = iris.cx
|
||||||
agentPositions.value.filter(p => agentLineActive[p.id])
|
const startY = iris.bottom - 1
|
||||||
)
|
const endX = agentPos.cx
|
||||||
|
const endY = agentPos.top + 4
|
||||||
|
|
||||||
|
// Bézier control points
|
||||||
|
const dy = endY - startY
|
||||||
|
const cy1 = startY + dy * 0.4
|
||||||
|
const cy2 = startY + dy * 0.7
|
||||||
|
|
||||||
|
const d = `M ${startX} ${startY} C ${startX} ${cy1}, ${endX} ${cy2}, ${endX} ${endY}`
|
||||||
|
result[agent.id] = { d, length: 0 }
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
|
||||||
|
// ── Pulse animation (JS-driven via requestAnimationFrame) ──
|
||||||
|
let animFrameId: number | null = null
|
||||||
|
let lastAnimTime = 0
|
||||||
|
|
||||||
|
const pathElements = ref<Record<string, SVGPathElement | null>>({})
|
||||||
|
const pulseElements = ref<Record<string, SVGPathElement | null>>({})
|
||||||
|
const pulseOffsets = ref<Record<string, number>>({})
|
||||||
|
|
||||||
|
function storePathRef(id: string) {
|
||||||
|
return (el: SVGPathElement | null) => {
|
||||||
|
pathElements.value[id] = el
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function storePulseRef(id: string) {
|
||||||
|
return (el: SVGPathElement | null) => {
|
||||||
|
pulseElements.value[id] = el
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshPathLengths() {
|
||||||
|
for (const agent of props.agents) {
|
||||||
|
const pathEl = pathElements.value[agent.id]
|
||||||
|
const pulseEl = pulseElements.value[agent.id]
|
||||||
|
const p = connectionPaths.value[agent.id]
|
||||||
|
if (pathEl && p) {
|
||||||
|
p.length = pathEl.getTotalLength()
|
||||||
|
}
|
||||||
|
if (pulseEl && p && p.length > 0) {
|
||||||
|
if (pulseOffsets.value[agent.id] === undefined) {
|
||||||
|
pulseOffsets.value[agent.id] = 0
|
||||||
|
}
|
||||||
|
pulseEl.setAttribute('stroke-dasharray', `12 ${p.length}`)
|
||||||
|
pulseEl.setAttribute('stroke-dashoffset', String(-pulseOffsets.value[agent.id]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startPulseAnimation() {
|
||||||
|
refreshPathLengths()
|
||||||
|
|
||||||
|
const speeds: Record<string, number> = {}
|
||||||
|
for (const agent of props.agents) {
|
||||||
|
const p = connectionPaths.value[agent.id]
|
||||||
|
if (p && p.length > 0) {
|
||||||
|
speeds[agent.id] = p.length / 3000 // full traversal in ~3s
|
||||||
|
if (pulseOffsets.value[agent.id] === undefined) {
|
||||||
|
pulseOffsets.value[agent.id] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastAnimTime = performance.now()
|
||||||
|
|
||||||
|
function tick(now: number) {
|
||||||
|
const dt = now - lastAnimTime
|
||||||
|
lastAnimTime = now
|
||||||
|
|
||||||
|
for (const agent of props.agents) {
|
||||||
|
const pulseEl = pulseElements.value[agent.id]
|
||||||
|
const p = connectionPaths.value[agent.id]
|
||||||
|
if (!pulseEl || !p || p.length <= 0) continue
|
||||||
|
|
||||||
|
const currentOffset = pulseOffsets.value[agent.id] ?? 0
|
||||||
|
const speed = speeds[agent.id] ?? p.length / 3000
|
||||||
|
const newOffset = currentOffset + speed * dt
|
||||||
|
const cycleLen = p.length + 12
|
||||||
|
pulseOffsets.value[agent.id] = newOffset > cycleLen ? newOffset % cycleLen : newOffset
|
||||||
|
pulseEl.setAttribute('stroke-dashoffset', String(-pulseOffsets.value[agent.id]))
|
||||||
|
}
|
||||||
|
|
||||||
|
animFrameId = requestAnimationFrame(tick)
|
||||||
|
}
|
||||||
|
|
||||||
|
animFrameId = requestAnimationFrame(tick)
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopPulseAnimation() {
|
||||||
|
if (animFrameId !== null) {
|
||||||
|
cancelAnimationFrame(animFrameId)
|
||||||
|
animFrameId = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Lifecycle ──
|
||||||
|
let resizeObserver: ResizeObserver | null = null
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await nextTick()
|
||||||
|
updatePositions()
|
||||||
|
await nextTick()
|
||||||
|
updatePositions()
|
||||||
|
refreshPathLengths()
|
||||||
|
startPulseAnimation()
|
||||||
|
|
||||||
|
resizeObserver = new ResizeObserver(() => {
|
||||||
|
updatePositions()
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
refreshPathLengths()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if (networkRef.value) {
|
||||||
|
resizeObserver.observe(networkRef.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
stopPulseAnimation()
|
||||||
|
resizeObserver?.disconnect()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="team-network">
|
<div ref="networkRef" class="team-network">
|
||||||
<!-- Iris Node -->
|
<!-- Iris Node -->
|
||||||
<div class="iris-node">
|
<div class="iris-node" data-agent-id="iris">
|
||||||
<div class="iris-avatar-ring">
|
<div class="iris-avatar-ring">
|
||||||
<svg class="ring-svg" viewBox="0 0 60 60" width="60" height="60">
|
<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="rgba(167,139,250,0.12)" stroke-width="2" />
|
||||||
@@ -88,58 +264,85 @@ const activeLines = computed(() =>
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- SVG Connections -->
|
<!-- SVG Connection Layer -->
|
||||||
<svg
|
<svg
|
||||||
|
v-if="svgWidth > 0 && svgHeight > 0"
|
||||||
class="network-svg"
|
class="network-svg"
|
||||||
:viewBox="`0 0 ${NETWORK_W} 400`"
|
:width="svgWidth"
|
||||||
preserveAspectRatio="xMidYMid meet"
|
:height="svgHeight"
|
||||||
|
:viewBox="`0 0 ${svgWidth} ${svgHeight}`"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<defs>
|
<defs>
|
||||||
<filter id="lineglow">
|
<filter
|
||||||
<feGaussianBlur stdDeviation="2" result="blur" />
|
v-for="agent in agents"
|
||||||
|
:key="`glow-${agent.id}`"
|
||||||
|
:id="`glow-${agent.id}`"
|
||||||
|
x="-30%" y="-30%" width="160%" height="160%"
|
||||||
|
>
|
||||||
|
<feGaussianBlur stdDeviation="4" result="blur" />
|
||||||
<feMerge>
|
<feMerge>
|
||||||
|
<feMergeNode in="blur" />
|
||||||
<feMergeNode in="blur" />
|
<feMergeNode in="blur" />
|
||||||
<feMergeNode in="SourceGraphic" />
|
<feMergeNode in="SourceGraphic" />
|
||||||
</feMerge>
|
</feMerge>
|
||||||
</filter>
|
</filter>
|
||||||
</defs>
|
</defs>
|
||||||
|
|
||||||
<line
|
<template v-for="agent in agents" :key="agent.id">
|
||||||
v-for="pos in agentPositions"
|
<!-- Base connection line -->
|
||||||
:key="'conn-' + pos.id"
|
<path
|
||||||
:x1="IRIS_CX" :y1="IRIS_CY + 32"
|
v-if="connectionPaths[agent.id]"
|
||||||
:x2="pos.x + 22" :y2="pos.y"
|
:ref="storePathRef(agent.id)"
|
||||||
:stroke="agentColorMap[pos.id]"
|
:d="connectionPaths[agent.id]!.d"
|
||||||
:stroke-width="agentLineActive[pos.id] ? 1.5 : 1"
|
:stroke="agent.color"
|
||||||
:opacity="agentLineActive[pos.id] ? 0.4 : 0.1"
|
:stroke-width="agent.active ? 2.5 : 1.5"
|
||||||
class="conn-line"
|
fill="none"
|
||||||
:class="{ 'line-pulse': agentLineActive[pos.id] }"
|
:opacity="agent.active ? 0.7 : 0.25"
|
||||||
filter="url(#lineglow)"
|
stroke-linecap="round"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<g v-for="pos in activeLines" :key="'fx-' + pos.id">
|
<!-- Glow for active agents -->
|
||||||
<circle
|
<path
|
||||||
:cx="pos.x + 22" :cy="pos.y"
|
v-if="agent.active && connectionPaths[agent.id]"
|
||||||
r="2.5" fill="#ffffff"
|
:d="connectionPaths[agent.id]!.d"
|
||||||
class="pulse-end" :style="{ '--pulse-color': agentColorMap[pos.id] }"
|
:stroke="agent.color"
|
||||||
|
stroke-width="4"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
:filter="`url(#glow-${agent.id})`"
|
||||||
|
opacity="0.5"
|
||||||
/>
|
/>
|
||||||
<circle
|
|
||||||
:cx="IRIS_CX" :cy="IRIS_CY + 32"
|
<!-- Moving pulse dot -->
|
||||||
r="2.5" fill="#ffffff"
|
<path
|
||||||
class="pulse-origin"
|
v-if="connectionPaths[agent.id]"
|
||||||
|
:ref="storePulseRef(agent.id)"
|
||||||
|
:d="connectionPaths[agent.id]!.d"
|
||||||
|
stroke="white"
|
||||||
|
stroke-width="3"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
:opacity="agent.active ? 1 : 0.4"
|
||||||
/>
|
/>
|
||||||
</g>
|
</template>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<!-- Agent Cards -->
|
<!-- Agent Grid (dynamic: 2 columns, extends downward) -->
|
||||||
<div class="agents-grid">
|
<div class="agents-grid">
|
||||||
<AgentNode
|
<div
|
||||||
v-for="agent in agents"
|
v-for="agent in agents"
|
||||||
:key="agent.id"
|
:key="agent.id"
|
||||||
:agent="agent"
|
:data-agent-id="agent.id"
|
||||||
:runtime="getAgentRuntime(agent.id)"
|
class="agent-slot"
|
||||||
@select="onAgentSelect"
|
>
|
||||||
/>
|
<AgentNode
|
||||||
|
:agent="agent"
|
||||||
|
:runtime="getAgentRuntime(agent.id)"
|
||||||
|
@select="onAgentSelect"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Focus Banner -->
|
<!-- Focus Banner -->
|
||||||
@@ -160,24 +363,21 @@ const activeLines = computed(() =>
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.team-network {
|
.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;
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
padding: 24px 20px 20px;
|
||||||
|
background: rgba(22, 27, 34, 0.5);
|
||||||
|
border: 1px solid rgba(139, 124, 246, 0.08);
|
||||||
|
border-radius: 16px;
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
-webkit-backdrop-filter: blur(8px);
|
||||||
|
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2);
|
||||||
|
min-height: 480px;
|
||||||
|
transition: border-color 0.2s ease;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-height: 520px;
|
|
||||||
}
|
}
|
||||||
.team-network:hover {
|
.team-network:hover {
|
||||||
border-color: rgba(139, 124, 246, 0.18);
|
border-color: rgba(139, 124, 246, 0.14);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Iris Node */
|
/* Iris Node */
|
||||||
@@ -187,6 +387,7 @@ const activeLines = computed(() =>
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
padding: 20px 28px;
|
padding: 20px 28px;
|
||||||
|
margin-bottom: 8px;
|
||||||
background: rgba(167, 139, 250, 0.06);
|
background: rgba(167, 139, 250, 0.06);
|
||||||
border: 1px solid rgba(167, 139, 250, 0.15);
|
border: 1px solid rgba(167, 139, 250, 0.15);
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
@@ -194,6 +395,8 @@ const activeLines = computed(() =>
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 320px;
|
max-width: 320px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
.iris-avatar-ring {
|
.iris-avatar-ring {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -267,50 +470,28 @@ const activeLines = computed(() =>
|
|||||||
color: #a78bfa;
|
color: #a78bfa;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SVG Lines */
|
/* SVG Connection Lines */
|
||||||
.network-svg {
|
.network-svg {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
pointer-events: none;
|
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
pointer-events: none;
|
||||||
.conn-line {
|
overflow: visible;
|
||||||
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 */
|
/* Agent Grid — dynamic, 2 columns, extends downward as agents grow */
|
||||||
.agents-grid {
|
.agents-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 10px;
|
gap: 12px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
margin-top: 4px;
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
.agent-slot {
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Focus Banner */
|
/* Focus Banner */
|
||||||
@@ -320,10 +501,10 @@ const activeLines = computed(() =>
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px 16px;
|
padding: 10px 16px;
|
||||||
|
margin-top: 12px;
|
||||||
background: rgba(234, 179, 8, 0.05);
|
background: rgba(234, 179, 8, 0.05);
|
||||||
border: 1px solid rgba(234, 179, 8, 0.1);
|
border: 1px solid rgba(234, 179, 8, 0.1);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
margin-top: 4px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { computed } from 'vue'
|
|||||||
import {
|
import {
|
||||||
Activity, Bot, Boxes, Command, FileText,
|
Activity, Bot, Boxes, Command, FileText,
|
||||||
LayoutDashboard, ListTodo, LogOut, MessageSquareText, Settings,
|
LayoutDashboard, ListTodo, LogOut, MessageSquareText, Settings,
|
||||||
Shield, SlidersHorizontal, Sparkles, Users, BookOpen,
|
Shield, SlidersHorizontal, Sparkles, BookOpen,
|
||||||
AlertTriangle, Calendar,
|
AlertTriangle, Calendar,
|
||||||
} from '@lucide/vue'
|
} from '@lucide/vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
@@ -29,7 +29,6 @@ const navigation = [
|
|||||||
{ label: 'Dashboard', icon: LayoutDashboard },
|
{ label: 'Dashboard', icon: LayoutDashboard },
|
||||||
{ label: 'Memory', icon: FileText },
|
{ label: 'Memory', icon: FileText },
|
||||||
{ label: 'Docs', icon: BookOpen },
|
{ label: 'Docs', icon: BookOpen },
|
||||||
{ label: 'Team', icon: Users },
|
|
||||||
{ label: 'Security', icon: Shield },
|
{ label: 'Security', icon: Shield },
|
||||||
{ label: 'Projects', icon: Boxes },
|
{ label: 'Projects', icon: Boxes },
|
||||||
{ label: 'Task Board', icon: ListTodo },
|
{ label: 'Task Board', icon: ListTodo },
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import ProjectDetailView from './views/ProjectDetailView.vue'
|
|||||||
import SettingsView from './views/SettingsView.vue'
|
import SettingsView from './views/SettingsView.vue'
|
||||||
import MemoryView from './views/MemoryView.vue'
|
import MemoryView from './views/MemoryView.vue'
|
||||||
import DocsView from './views/DocsView.vue'
|
import DocsView from './views/DocsView.vue'
|
||||||
import TeamView from './views/TeamView.vue'
|
|
||||||
import AgentDetailView from './views/AgentDetailView.vue'
|
import AgentDetailView from './views/AgentDetailView.vue'
|
||||||
import AgentsIndexView from './views/AgentsIndexView.vue'
|
import AgentsIndexView from './views/AgentsIndexView.vue'
|
||||||
import SecurityView from './views/SecurityView.vue'
|
import SecurityView from './views/SecurityView.vue'
|
||||||
@@ -18,7 +17,6 @@ const routes = [
|
|||||||
{ path: '/dashboard', name: 'Dashboard', component: DashboardView },
|
{ path: '/dashboard', name: 'Dashboard', component: DashboardView },
|
||||||
{ path: '/memory', name: 'Memory', component: MemoryView },
|
{ path: '/memory', name: 'Memory', component: MemoryView },
|
||||||
{ path: '/docs', name: 'Docs', component: DocsView },
|
{ path: '/docs', name: 'Docs', component: DocsView },
|
||||||
{ path: '/team', name: 'Team', component: TeamView },
|
|
||||||
{ path: '/agents/:id', name: 'AgentDetail', component: AgentDetailView },
|
{ path: '/agents/:id', name: 'AgentDetail', component: AgentDetailView },
|
||||||
{ path: '/security', name: 'Security', component: SecurityView },
|
{ path: '/security', name: 'Security', component: SecurityView },
|
||||||
{ path: '/incidents', name: 'Incidents', component: IncidentsView },
|
{ path: '/incidents', name: 'Incidents', component: IncidentsView },
|
||||||
|
|||||||
Reference in New Issue
Block a user