From fcfc06c640661c335808792eeb487d46d15f3202 Mon Sep 17 00:00:00 2001 From: DBT Date: Thu, 26 Feb 2026 03:16:12 +0000 Subject: [PATCH] webui chat: add role avatars and distinct styling for user/agent/exec/system --- webui/src/pages/Chat.tsx | 80 +++++++++++++++++++++++++++------------- webui/src/types/index.ts | 3 +- 2 files changed, 57 insertions(+), 26 deletions(-) diff --git a/webui/src/pages/Chat.tsx b/webui/src/pages/Chat.tsx index f9a6b20..526cdd0 100644 --- a/webui/src/pages/Chat.tsx +++ b/webui/src/pages/Chat.tsx @@ -24,15 +24,24 @@ const Chat: React.FC = () => { const j = await r.json(); const arr = Array.isArray(j.messages) ? j.messages : []; const mapped: ChatItem[] = arr.map((m: any) => { - const role = (m.role === 'assistant' || m.role === 'user') ? m.role : 'assistant'; + const baseRole = String(m.role || 'assistant'); + let role: ChatItem['role'] = 'assistant'; + if (baseRole === 'user') role = 'user'; + else if (baseRole === 'tool') role = 'tool'; + else if (baseRole === 'system') role = 'system'; + let text = m.content || ''; - if (m.role === 'tool') { - text = `[tool output]\n${text}`; - } + let label = role === 'user' ? 'User' : role === 'tool' ? 'Exec' : role === 'system' ? 'System' : 'Agent'; + if (Array.isArray(m.tool_calls) && m.tool_calls.length > 0) { + role = 'exec'; + label = 'Exec'; text = `${text}\n[tool calls: ${m.tool_calls.map((x: any) => x?.function?.name || x?.name).filter(Boolean).join(', ')}]`; } - return { role, text }; + if (baseRole === 'tool') { + text = `[tool output]\n${text}`; + } + return { role, text, label }; }); setChat(mapped); } catch (e) { @@ -58,7 +67,7 @@ const Chat: React.FC = () => { } const userText = msg + (media ? `\n[Attached File: ${f?.name}]` : ''); - setChat((prev) => [...prev, { role: 'user', text: userText }]); + setChat((prev) => [...prev, { role: 'user', text: userText, label: 'User' }]); const currentMsg = msg; setMsg(''); @@ -78,7 +87,7 @@ const Chat: React.FC = () => { const decoder = new TextDecoder(); let assistantText = ''; - setChat((prev) => [...prev, { role: 'assistant', text: '' }]); + setChat((prev) => [...prev, { role: 'assistant', text: '', label: 'Agent' }]); while (true) { const { value, done } = await reader.read(); @@ -87,7 +96,7 @@ const Chat: React.FC = () => { assistantText += chunk; setChat((prev) => { const next = [...prev]; - next[next.length - 1] = { role: 'assistant', text: assistantText }; + next[next.length - 1] = { role: 'assistant', text: assistantText, label: 'Agent' }; return next; }); } @@ -95,7 +104,7 @@ const Chat: React.FC = () => { // refresh full persisted history (includes tool/internal traces) loadHistory(); } catch (e) { - setChat((prev) => [...prev, { role: 'assistant', text: 'Error: Failed to get response from server.' }]); + setChat((prev) => [...prev, { role: 'system', text: 'Error: Failed to get response from server.', label: 'System' }]); } } @@ -120,22 +129,43 @@ const Chat: React.FC = () => {

{t('startConversation')}

) : ( - chat.map((m, i) => ( - -
-

{m.text}

-
-
- )) + chat.map((m, i) => { + const isUser = m.role === 'user'; + const isExec = m.role === 'tool' || m.role === 'exec'; + const isSystem = m.role === 'system'; + const avatar = isUser ? 'U' : isExec ? 'E' : isSystem ? 'S' : 'A'; + const avatarClass = isUser + ? 'bg-indigo-600/90 text-white' + : isExec + ? 'bg-amber-600/80 text-white' + : isSystem + ? 'bg-zinc-700 text-zinc-100' + : 'bg-emerald-600/80 text-white'; + const bubbleClass = isUser + ? 'bg-indigo-600 text-white rounded-br-sm' + : isExec + ? 'bg-amber-500/10 text-amber-100 rounded-bl-sm border border-amber-500/30' + : isSystem + ? 'bg-zinc-700/40 text-zinc-100 rounded-bl-sm border border-zinc-600/40' + : 'bg-zinc-800/80 text-zinc-200 rounded-bl-sm border border-zinc-700/50'; + + return ( + +
+
{avatar}
+
+
{m.label || (isUser ? 'User' : isExec ? 'Exec' : isSystem ? 'System' : 'Agent')}
+

{m.text}

+
+
+
+ ); + }) )}
diff --git a/webui/src/types/index.ts b/webui/src/types/index.ts index 7ee3d4f..ad25ad2 100644 --- a/webui/src/types/index.ts +++ b/webui/src/types/index.ts @@ -1,4 +1,5 @@ -export type ChatItem = { role: 'user' | 'assistant'; text: string }; +export type ChatRole = 'user' | 'assistant' | 'tool' | 'system' | 'exec'; +export type ChatItem = { role: ChatRole; text: string; label?: string }; export type Session = { key: string; title: string }; export type CronJob = { id: string;