62 lines
2.3 KiB
TypeScript
62 lines
2.3 KiB
TypeScript
|
|
import type { Agent, FeedEntry } from '../../types'
|
||
|
|
import { getAgentMeta } from '../../lib/agentMeta'
|
||
|
|
import { cn } from '../../lib/utils'
|
||
|
|
|
||
|
|
type Props = {
|
||
|
|
feed: FeedEntry[]
|
||
|
|
agents: Agent[]
|
||
|
|
filterAgentId?: string | null
|
||
|
|
opsOnly?: boolean
|
||
|
|
}
|
||
|
|
|
||
|
|
const LEVEL: Record<string, string> = {
|
||
|
|
info: 'text-foreground-muted',
|
||
|
|
ok: 'text-success',
|
||
|
|
warn: 'text-warning',
|
||
|
|
err: 'text-danger',
|
||
|
|
}
|
||
|
|
|
||
|
|
function isOpsEvent(message: string): boolean {
|
||
|
|
const lower = message.toLowerCase()
|
||
|
|
if (message.includes(' answered:')) return false
|
||
|
|
if (message.startsWith('Prompt received:')) return false
|
||
|
|
if (lower.includes('completed a response')) return false
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
|
||
|
|
export function ActivityStream({ feed, agents, filterAgentId, opsOnly }: Props) {
|
||
|
|
let items = filterAgentId ? feed.filter((e) => e.agent_id === filterAgentId) : feed
|
||
|
|
if (opsOnly) items = items.filter((e) => isOpsEvent(e.message))
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="scrollbar-thin flex-1 overflow-y-auto px-2 pb-2">
|
||
|
|
{items.length === 0 && (
|
||
|
|
<p className="py-8 text-center text-[11px] text-foreground-faint">
|
||
|
|
{opsOnly ? 'Geen operationele events — antwords staan in Chat.' : 'No activity yet — agents are on standby.'}
|
||
|
|
</p>
|
||
|
|
)}
|
||
|
|
{items.map((e) => {
|
||
|
|
const ag = agents.find((a) => a.id === e.agent_id)
|
||
|
|
const meta = ag ? getAgentMeta(ag.id) : null
|
||
|
|
const Icon = meta?.icon
|
||
|
|
return (
|
||
|
|
<div key={e.id} className="flex gap-2 border-b border-border/50 py-1.5 last:border-0">
|
||
|
|
{Icon && (
|
||
|
|
<span className="mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded bg-surface-overlay" style={{ color: meta?.accent }}>
|
||
|
|
<Icon className="h-3 w-3" />
|
||
|
|
</span>
|
||
|
|
)}
|
||
|
|
<div className="min-w-0 flex-1">
|
||
|
|
<div className="flex items-center gap-2">
|
||
|
|
<span className="text-[10px] font-medium text-foreground-muted">{ag?.name.split(' ·')[0] || e.agent_id}</span>
|
||
|
|
<span className="font-mono text-[9px] text-foreground-faint">{new Date(e.ts).toLocaleTimeString('en-US', { hour12: false })}</span>
|
||
|
|
</div>
|
||
|
|
<p className={cn('text-[10px] leading-relaxed', LEVEL[e.level] || 'text-foreground-muted')}>{e.message}</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
})}
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|