69 lines
2.9 KiB
TypeScript
69 lines
2.9 KiB
TypeScript
|
|
import { useEffect, useRef } from 'react'
|
|||
|
|
import { MessageSquare } from 'lucide-react'
|
|||
|
|
import type { Agent, ChatMessage } from '../../types'
|
|||
|
|
import { getAgentMeta } from '../../lib/agentMeta'
|
|||
|
|
import { cn } from '../../lib/utils'
|
|||
|
|
|
|||
|
|
type Props = {
|
|||
|
|
messages: ChatMessage[]
|
|||
|
|
agents: Agent[]
|
|||
|
|
selectedAgent: Agent | null
|
|||
|
|
busy: boolean
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function CommsPanel({ messages, agents, selectedAgent, busy }: Props) {
|
|||
|
|
const bottomRef = useRef<HTMLDivElement>(null)
|
|||
|
|
const meta = selectedAgent ? getAgentMeta(selectedAgent.id) : null
|
|||
|
|
|
|||
|
|
useEffect(() => {
|
|||
|
|
bottomRef.current?.scrollIntoView({ behavior: 'smooth' })
|
|||
|
|
}, [messages, busy])
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<div className="flex h-full min-h-0 flex-col">
|
|||
|
|
<div className="flex shrink-0 items-center justify-between border-b border-border px-3 py-2">
|
|||
|
|
<h3 className="flex items-center gap-1.5 text-xs font-semibold text-foreground">
|
|||
|
|
<MessageSquare className="h-3.5 w-3.5 text-docker" /> Comms
|
|||
|
|
</h3>
|
|||
|
|
{selectedAgent && meta && (
|
|||
|
|
<span className="truncate font-mono text-[9px]" style={{ color: meta.accent }}>
|
|||
|
|
{selectedAgent.name.split(' ·')[0]}
|
|||
|
|
</span>
|
|||
|
|
)}
|
|||
|
|
</div>
|
|||
|
|
<div className="scrollbar-thin flex-1 space-y-2 overflow-y-auto p-2">
|
|||
|
|
{!messages.length && (
|
|||
|
|
<div className="flex h-full flex-col items-center justify-center py-6 text-center">
|
|||
|
|
<MessageSquare className="mb-2 h-6 w-6 text-foreground-faint" />
|
|||
|
|
<p className="max-w-[200px] text-[10px] text-foreground-muted">
|
|||
|
|
Send a command below — routing selects the right specialist automatically.
|
|||
|
|
</p>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
{messages.map((m, i) => {
|
|||
|
|
const ag = m.role === 'agent' ? agents.find((a) => a.id === m.agent) : null
|
|||
|
|
const agMeta = ag ? getAgentMeta(ag.id) : null
|
|||
|
|
return (
|
|||
|
|
<div key={i} className={cn('flex gap-2', m.role === 'user' && 'flex-row-reverse')}>
|
|||
|
|
<div className={cn('max-w-[85%] rounded-lg border px-2 py-1.5', m.role === 'user' ? 'border-docker/25 bg-docker-light' : 'border-border bg-surface-overlay')}>
|
|||
|
|
<p className="mb-0.5 text-[8px] text-foreground-muted">
|
|||
|
|
{m.role === 'user' ? 'You' : ag?.name || m.agent}
|
|||
|
|
{m.ts && ` · ${new Date(m.ts).toLocaleTimeString('en-US', { hour12: false })}`}
|
|||
|
|
</p>
|
|||
|
|
<p className="whitespace-pre-wrap text-[11px] text-foreground">{m.text}</p>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
})}
|
|||
|
|
{busy && (
|
|||
|
|
<div className="flex items-center gap-2 px-2 py-2 text-[10px] text-foreground-muted">
|
|||
|
|
<span className="h-1.5 w-1.5 animate-bounce rounded-full bg-accent" />
|
|||
|
|
<span>Agent verzamelt cluster-data en vraagt Llama 70B… verwacht ~30–90 sec</span>
|
|||
|
|
</div>
|
|||
|
|
)}
|
|||
|
|
<div ref={bottomRef} />
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
}
|