From 49458ba59c4083919e763fb4c0188a904e07b597 Mon Sep 17 00:00:00 2001 From: mohiit1502 Date: Thu, 9 Apr 2026 09:39:34 +0530 Subject: [PATCH] =?UTF-8?q?refactor:=20remove=20agent=20selector=20?= =?UTF-8?q?=E2=80=94=20auto-route=20to=20best-fit=20agent=20by=20keyword?= =?UTF-8?q?=20score?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pickAgent(goal, agents): scores agents by name/description keyword overlap, falls back to first agent - Removed AgentSelector component from Agent mode header - Header now shows passive agent count (N agents / agent name) — informational only - Bubble label shows actual resolved agent name instead of 'Agent' - Empty state and placeholder copy updated to reinforce auto-routing UX - Fixed TypeScript: role literal types in history, removed stale selectedAgentId deps --- .../src/components/layout/ChatWorkspace.tsx | 187 +++++------------- 1 file changed, 51 insertions(+), 136 deletions(-) diff --git a/webapp/src/components/layout/ChatWorkspace.tsx b/webapp/src/components/layout/ChatWorkspace.tsx index 9b831bf9..2b62b5cb 100644 --- a/webapp/src/components/layout/ChatWorkspace.tsx +++ b/webapp/src/components/layout/ChatWorkspace.tsx @@ -26,6 +26,7 @@ interface ChatMessage { tokenCost?: number createdAt: string agentId?: string + agentName?: string mode?: ChatMode chunksUsed?: number latencyMs?: number @@ -63,6 +64,22 @@ function parseSlash(input: string): { cmd: string; args: string } | null { const SKILL_INTENT_RE = /\b(create|build|make|set up|setup)\s+(an?\s+)?(agent|bot|skill|assistant)\s+(that|to|for|which)/i const CONNECT_INTENT_RE = /\b(connect|integrate|set up|setup|link)\s+(my\s+)?(slack|telegram|gmail|whatsapp|discord|notion|github|linear|jira|google)/i +// ── Agent auto-routing ──────────────────────────────────────────────────── + +function pickAgent(goal: string, agents: ApiAgent[]): ApiAgent | null { + if (agents.length === 0) return null + if (agents.length === 1) return agents[0] + const words = goal.toLowerCase().split(/\W+/).filter(w => w.length > 2) + let best = agents[0] + let bestScore = -1 + for (const agent of agents) { + const hay = `${agent.name} ${agent.description ?? ''}`.toLowerCase() + const score = words.reduce((acc, w) => acc + (hay.includes(w) ? 1 : 0), 0) + if (score > bestScore) { bestScore = score; best = agent } + } + return best +} + // ── Suggestion chips ─────────────────────────────────────────────────────── interface SuggestionChip { @@ -144,109 +161,6 @@ function inlineFormat(text: string): React.ReactNode { })} } -// ── Agent Selector ───────────────────────────────────────────────────────── - -function AgentSelector({ agents, selectedId, onSelect }: { - agents: ApiAgent[] - selectedId: string - onSelect: (id: string) => void -}) { - const [open, setOpen] = useState(false) - const selected = agents.find(a => a.id === selectedId) - const ref = useRef(null) - - useEffect(() => { - if (!open) return - const handler = (e: MouseEvent) => { - if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false) - } - document.addEventListener('mousedown', handler) - return () => document.removeEventListener('mousedown', handler) - }, [open]) - - return ( -
- - - {open && ( -
- {agents.length === 0 && ( -
No agents yet
- )} - {agents.map(a => ( - - ))} -
- )} -
- ) -} - // ── Message bubble ───────────────────────────────────────────────────────── function MessageBubble({ msg, navigate }: { msg: ChatMessage; navigate: ReturnType }) { @@ -269,7 +183,7 @@ function MessageBubble({ msg, navigate }: { msg: ChatMessage; navigate: ReturnTy ? : } - {isNebula ? 'Nebula' : 'Agent'} + {isNebula ? 'Nebula' : (msg.agentName ?? 'Agent')} )} @@ -283,12 +197,12 @@ function MessageBubble({ msg, navigate }: { msg: ChatMessage; navigate: ReturnTy ? 'color-mix(in srgb, var(--nbl-nebula) 6%, var(--nbl-bg-surface))' : 'var(--nbl-bg-surface)', border: `1px solid ${isUser - ? 'color-mix(in srgb, var(--nbl-nebula) 28%, var(--nbl-border))' - : failed - ? 'color-mix(in srgb, var(--nbl-red) 30%, var(--nbl-border))' - : isNebula - ? 'color-mix(in srgb, var(--nbl-nebula) 18%, var(--nbl-border))' - : 'var(--nbl-border)'}`, + ? 'color-mix(in srgb, var(--nbl-nebula) 28%, var(--nbl-border))' + : failed + ? 'color-mix(in srgb, var(--nbl-red) 30%, var(--nbl-border))' + : isNebula + ? 'color-mix(in srgb, var(--nbl-nebula) 18%, var(--nbl-border))' + : 'var(--nbl-border)'}`, fontSize: 13, lineHeight: 1.6, color: 'var(--nbl-text-primary)', @@ -481,7 +395,6 @@ export function ChatWorkspace() { const qc = useQueryClient() const [input, setInput] = useState('') const [chatMode, setChatMode] = useState('nebula') - const [selectedAgentId, setSelectedAgentId] = useState('') const [messages, setMessages] = useState([]) const [pendingTaskIds, setPendingTaskIds] = useState>(new Set()) const [history, setHistory] = useState([]) @@ -505,11 +418,6 @@ export function ChatWorkspace() { const agents = agentList?.items ?? [] const integrationCount = instanceData?.length ?? 0 - // Auto-select first agent - useEffect(() => { - if (!selectedAgentId && agents.length > 0) setSelectedAgentId(agents[0].id) - }, [agents, selectedAgentId]) - // ── WS — real-time task updates ───────────────────────────────────────── const handleWsMessage = useCallback((msg: { type: string; entity_id?: string; payload?: Record }) => { @@ -555,8 +463,8 @@ export function ChatWorkspace() { setMessages(prev => [...prev, userMsg, aiMsg]) setHistory(prev => [ ...prev, - { role: 'user', content: message }, - { role: 'assistant', content: resp.reply }, + { role: 'user' as const, content: message }, + { role: 'assistant' as const, content: resp.reply }, ].slice(-20)) }, onError: (err: Error) => { @@ -574,8 +482,9 @@ export function ChatWorkspace() { mutationFn: ({ agentId, goal }: { agentId: string; goal: string }) => agentsApi.run(agentId, { goal }), onSuccess: (task, { goal }) => { + const agentName = agents.find(a => a.id === task.agent_id)?.name const userMsg: ChatMessage = { id: `u-${Date.now()}`, role: 'user', content: goal, agentId: task.agent_id, createdAt: new Date().toISOString(), mode: 'agent' } - const agentMsg: ChatMessage = { id: `a-${task.id}`, role: 'agent', content: '', taskId: task.id, taskStatus: task.status, agentId: task.agent_id, createdAt: new Date().toISOString(), mode: 'agent' } + const agentMsg: ChatMessage = { id: `a-${task.id}`, role: 'agent', content: '', taskId: task.id, taskStatus: task.status, agentId: task.agent_id, agentName, createdAt: new Date().toISOString(), mode: 'agent' } setMessages(prev => [...prev, userMsg, agentMsg]) setPendingTaskIds(prev => new Set([...prev, task.id])) qc.invalidateQueries({ queryKey: ['viz-agents'] }) @@ -614,12 +523,14 @@ export function ChatWorkspace() { case '/kb': if (args) nebulaMut.mutate(args) return !!args - case '/run': - if (args && selectedAgentId) { - runMut.mutate({ agentId: selectedAgentId, goal: args }) + case '/run': { + const agent = pickAgent(args, agents) + if (args && agent) { + runMut.mutate({ agentId: agent.id, goal: args }) return true } return false + } case '/agent': navigate(`/agents/new${args ? `?name=${encodeURIComponent(args)}` : ''}`) return true @@ -636,7 +547,7 @@ export function ChatWorkspace() { default: return false } - }, [navigate, nebulaMut, runMut, selectedAgentId]) + }, [navigate, nebulaMut, runMut, agents]) // ── Send ───────────────────────────────────────────────────────────────── @@ -680,13 +591,14 @@ export function ChatWorkspace() { if (mode === 'nebula') { nebulaMut.mutate(text) } else { - if (!selectedAgentId) { - setMessages(prev => [...prev, { id: `e-${Date.now()}`, role: 'agent', content: 'Select an agent first.', taskStatus: 'failed', mode: 'agent', createdAt: new Date().toISOString() }]) + const agent = pickAgent(text, agents) + if (!agent) { + setMessages(prev => [...prev, { id: `e-${Date.now()}`, role: 'agent', content: 'No agents available. Create one first.', taskStatus: 'failed', mode: 'agent', createdAt: new Date().toISOString() }]) return } - runMut.mutate({ agentId: selectedAgentId, goal: text }) + runMut.mutate({ agentId: agent.id, goal: text }) } - }, [input, isPending, chatMode, handleSlash, navigate, nebulaMut, runMut, selectedAgentId]) + }, [input, isPending, chatMode, handleSlash, navigate, nebulaMut, runMut, agents]) // ── Auto-scroll + resize ───────────────────────────────────────────────── @@ -699,8 +611,6 @@ export function ChatWorkspace() { el.style.height = `${Math.min(el.scrollHeight, 120)}px` }, [input]) - const selectedAgent = agents.find(a => a.id === selectedAgentId) - return (
{/* Header */} @@ -712,8 +622,11 @@ export function ChatWorkspace() { workspace
{ setChatMode(m); setInput('') }} /> - {chatMode === 'agent' && ( - + {chatMode === 'agent' && agents.length > 0 && ( + + + {agents.length === 1 ? agents[0].name : `${agents.length} agents`} + )}