status icons and formatting
3clyp50 committed
Dec 30, 2025 at 01:15 UTC
e275cab0c980dad4b70693734a9bdfba411bf3ae
7 files changed
+285
-207
python/extensions/before_main_llm_call/_10_log_for_stream.py
+2
-2
@@ -19,8 +19,8 @@ class LogForStream(Extension):
19
)
20
)
21
22
-def build_heading(agent, text: str):
23
- return f"icon://network_intelligence {agent.agent_name}: {text}"
22
+def build_heading(agent, text: str, icon: str = "network_intelligence"):
23
+ return f"icon://{icon} {text}"
24
25
def build_default_heading(agent):
26
return build_heading(agent, "Generating...")
\ No newline at end of file
python/extensions/response_stream/_10_log_from_stream.py
+2
-5
@@ -22,12 +22,9 @@ class LogFromStream(Extension):
22
if "headline" in parsed:
23
heading = build_heading(self.agent, parsed['headline'])
24
elif "tool_name" in parsed:
25
- heading = build_heading(self.agent, f"Using tool {parsed['tool_name']}") # if the llm skipped headline
25
+ heading = build_heading(self.agent, f"Using {parsed['tool_name']}") # if the llm skipped headline
26
elif "thoughts" in parsed:
27
- # thought length indicator
28
- thoughts = "\n".join(parsed["thoughts"])
29
- pipes = "|" * math.ceil(math.sqrt(len(thoughts)))
30
- heading = build_heading(self.agent, f"Thinking... {pipes}")
27
+ heading = build_default_heading(self.agent)
28
29
# create log message and store it in loop data temporary params
30
if "log_item_generating" not in loop_data.params_temporary:
python/extensions/response_stream/_20_live_response.py
+1
-1
@@ -30,7 +30,7 @@ class LiveResponse(Extension):
30
loop_data.params_temporary["log_item_response"] = (
31
self.agent.context.log.log(
32
type="response",
33
- heading=f"icon://chat {self.agent.agent_name}: Responding",
33
+ heading=f"icon://chat Responding",
34
)
35
)
36
webui/components/messages/process-group/process-group-store.js
+35
-72
@@ -78,110 +78,73 @@ const model = {
78
this._persist();
79
},
80
81
- // Get icon for step type (Material Symbols)
81
+ // Fallback icon for step type when backend doesn't provide one (Material Symbols)
82
+ // Maps backend log types to appropriate icons
83
getStepIcon(type) {
84
const icons = {
84
- 'agent': 'neurology',
85
- 'tool': 'build',
85
+ 'agent': 'network_intelligence',
86
+ 'response': 'chat',
87
+ 'tool': 'construction',
88
+ 'mcp': 'api',
89
+ 'subagent': 'communication',
90
'code_exe': 'terminal',
87
- 'browser': 'language',
91
+ 'browser': 'captive_portal',
92
+ 'progress': 'timer',
93
'info': 'info',
94
'hint': 'lightbulb',
90
- 'util': 'settings',
95
'warning': 'warning',
96
'error': 'error',
93
- 'mcp': 'api',
94
- 'memory': 'memory',
95
- 'done': 'check_circle',
96
- 'subagent': 'group'
97
+ 'util': 'memory',
98
+ 'user': 'person',
99
+ 'done': 'done_all'
100
};
101
return icons[type] || 'circle';
102
},
103
101
- // Get 3-letter status code for step type
104
+ // Status code (3-4 letter) for backend log types
105
getStepCode(type) {
106
const codes = {
107
'agent': 'GEN',
105
- 'tool': 'MCP',
108
+ 'response': 'END',
109
+ 'tool': 'TOOL',
110
+ 'mcp': 'MCP',
111
+ 'subagent': 'SUB',
112
'code_exe': 'EXE',
107
- 'browser': 'EXE',
113
+ 'browser': 'BRW',
114
+ 'progress': 'WAIT',
115
'info': 'INF',
109
- 'hint': 'INF',
110
- 'util': 'UTL',
116
+ 'hint': 'HNT',
117
'warning': 'WRN',
118
'error': 'ERR',
113
- 'mcp': 'MCP',
114
- 'memory': 'MEM',
115
- 'done': 'END',
116
- 'response': 'END',
117
- 'subagent': 'SUB'
119
+ 'util': 'UTL',
120
+ 'user': 'USR',
121
+ 'done': 'END'
122
};
119
- return codes[type] || 'GEN';
123
+ return codes[type] || type?.toUpperCase()?.slice(0, 4) || 'GEN';
124
},
125
122
- // Get color class for status code
126
+ // CSS color class for backend log types
127
getStatusColorClass(type) {
128
const colors = {
129
'agent': 'status-gen',
126
- 'tool': 'status-mcp',
130
+ 'response': 'status-end',
131
+ 'tool': 'status-tool',
132
+ 'mcp': 'status-mcp',
133
+ 'subagent': 'status-sub',
134
'code_exe': 'status-exe',
128
- 'browser': 'status-exe',
135
+ 'browser': 'status-brw',
136
+ 'progress': 'status-wait',
137
'info': 'status-inf',
130
- 'hint': 'status-inf',
131
- 'util': 'status-utl',
138
+ 'hint': 'status-hnt',
139
'warning': 'status-wrn',
140
'error': 'status-err',
134
- 'mcp': 'status-mcp',
135
- 'memory': 'status-mem',
136
- 'done': 'status-end',
137
- 'response': 'status-end',
138
- 'subagent': 'status-sub'
141
+ 'util': 'status-utl',
142
+ 'user': 'status-usr',
143
+ 'done': 'status-end'
144
};
145
return colors[type] || 'status-gen';
146
},
147
143
- // Get icon for badge display (Material Symbols)
144
- getStepBadgeIcon(type) {
145
- const icons = {
146
- 'agent': 'public', // Globe for GEN (network intelligence)
147
- 'tool': 'build', // Wrench for MCP
148
- 'code_exe': 'terminal', // Terminal for EXE
149
- 'browser': 'language', // Globe for browser
150
- 'info': 'info',
151
- 'hint': 'lightbulb',
152
- 'util': 'settings',
153
- 'warning': 'warning',
154
- 'error': 'error',
155
- 'mcp': 'build', // Wrench for MCP
156
- 'memory': 'memory',
157
- 'done': 'check_circle',
158
- 'response': 'check_circle',
159
- 'subagent': 'group'
160
- };
161
- return icons[type] || 'circle';
162
- },
163
-
164
- // Get label for step type (longer description)
165
- getStepLabel(type) {
166
- const labels = {
167
- 'agent': 'Generating',
168
- 'tool': 'MCP Call',
169
- 'code_exe': 'Executing',
170
- 'browser': 'Browser',
171
- 'info': 'Info',
172
- 'hint': 'Hint',
173
- 'util': 'Utility',
174
- 'warning': 'Warning',
175
- 'error': 'Error',
176
- 'mcp': 'MCP',
177
- 'memory': 'Memory',
178
- 'done': 'Done',
179
- 'response': 'Response',
180
- 'subagent': 'Subagent'
181
- };
182
- return labels[type] || 'Process';
183
- },
184
-
148
// Clear state for a specific context (when chat is reset)
149
clearContext(contextPrefix) {
150
// Clear groups matching the context
webui/components/messages/process-group/process-group.css
+147
-63
@@ -140,76 +140,106 @@
140
opacity: 0.9;
141
}
142
143
-/* GEN - Generating (blue/cyan) */
143
+/* Status colors - mapped from backend types via store */
144
+
145
+/* GEN - agent type (blue/cyan) */
146
.status-gen {
147
background-color: rgba(56, 189, 248, 0.15);
148
color: #38bdf8;
149
border: 1px solid rgba(56, 189, 248, 0.3);
150
}
151
150
-/* EXE - Executing (magenta/purple) */
151
-.status-exe {
152
- background-color: rgba(186, 104, 200, 0.15);
153
- color: #ba68c8;
154
- border: 1px solid rgba(186, 104, 200, 0.3);
152
+/* END - response/done type (green) */
153
+.status-end {
154
+ background-color: rgba(34, 197, 94, 0.15);
155
+ color: #22c55e;
156
+ border: 1px solid rgba(34, 197, 94, 0.3);
157
}
158
157
-/* MCP - MCP Tool Call (amber/yellow) */
158
-.status-mcp {
159
+/* TOOL - tool type (amber/yellow) */
160
+.status-tool {
161
background-color: rgba(251, 191, 36, 0.15);
162
color: #fbbf24;
163
border: 1px solid rgba(251, 191, 36, 0.3);
164
}
165
164
-/* MEM - Memory (blue) */
165
-.status-mem {
166
- background-color: rgba(96, 165, 250, 0.15);
167
- color: #60a5fa;
168
- border: 1px solid rgba(96, 165, 250, 0.3);
166
+/* MCP - mcp type (amber/yellow) */
167
+.status-mcp {
168
+ background-color: rgba(251, 191, 36, 0.15);
169
+ color: #fbbf24;
170
+ border: 1px solid rgba(251, 191, 36, 0.3);
171
}
172
171
-/* END - Done (green) */
172
-.status-end {
173
- background-color: rgba(34, 197, 94, 0.15);
174
- color: #22c55e;
175
- border: 1px solid rgba(34, 197, 94, 0.3);
173
+/* SUB - subagent type (teal) */
174
+.status-sub {
175
+ background-color: rgba(20, 184, 166, 0.15);
176
+ color: #14b8a6;
177
+ border: 1px solid rgba(20, 184, 166, 0.3);
178
}
179
178
-/* ERR - Error (red) */
179
-.status-err {
180
- background-color: rgba(239, 68, 68, 0.15);
181
- color: #ef4444;
182
- border: 1px solid rgba(239, 68, 68, 0.3);
180
+/* EXE - code_exe type (magenta/purple) */
181
+.status-exe {
182
+ background-color: rgba(186, 104, 200, 0.15);
183
+ color: #ba68c8;
184
+ border: 1px solid rgba(186, 104, 200, 0.3);
185
}
186
185
-/* WRN - Warning (orange) */
186
-.status-wrn {
187
- background-color: rgba(249, 115, 22, 0.15);
188
- color: #f97316;
189
- border: 1px solid rgba(249, 115, 22, 0.3);
187
+/* BRW - browser type (indigo) */
188
+.status-brw {
189
+ background-color: rgba(129, 140, 248, 0.15);
190
+ color: #818cf8;
191
+ border: 1px solid rgba(129, 140, 248, 0.3);
192
}
193
192
-/* SUB - Subagent (teal) */
193
-.status-sub {
194
- background-color: rgba(20, 184, 166, 0.15);
195
- color: #14b8a6;
196
- border: 1px solid rgba(20, 184, 166, 0.3);
194
+/* WAIT - progress type (slate) */
195
+.status-wait {
196
+ background-color: rgba(148, 163, 184, 0.15);
197
+ color: #94a3b8;
198
+ border: 1px solid rgba(148, 163, 184, 0.3);
199
}
200
199
-/* INF - Info (gray) */
201
+/* INF - info type (gray) */
202
.status-inf {
203
background-color: rgba(148, 163, 184, 0.15);
204
color: #94a3b8;
205
border: 1px solid rgba(148, 163, 184, 0.3);
206
}
207
206
-/* UTL - Utility (gray-blue) */
208
+/* HNT - hint type (yellow-green) */
209
+.status-hnt {
210
+ background-color: rgba(163, 230, 53, 0.15);
211
+ color: #a3e635;
212
+ border: 1px solid rgba(163, 230, 53, 0.3);
213
+}
214
+
215
+/* WRN - warning type (orange) */
216
+.status-wrn {
217
+ background-color: rgba(249, 115, 22, 0.15);
218
+ color: #f97316;
219
+ border: 1px solid rgba(249, 115, 22, 0.3);
220
+}
221
+
222
+/* ERR - error type (red) */
223
+.status-err {
224
+ background-color: rgba(239, 68, 68, 0.15);
225
+ color: #ef4444;
226
+ border: 1px solid rgba(239, 68, 68, 0.3);
227
+}
228
+
229
+/* UTL - util type (gray-blue) */
230
.status-utl {
231
background-color: rgba(100, 116, 139, 0.15);
232
color: #64748b;
233
border: 1px solid rgba(100, 116, 139, 0.3);
234
}
235
236
+/* USR - user type (sky) */
237
+.status-usr {
238
+ background-color: rgba(56, 189, 248, 0.15);
239
+ color: #38bdf8;
240
+ border: 1px solid rgba(56, 189, 248, 0.3);
241
+}
242
+
243
/* Animated spinner for active status (CSS-only, no Material Icons) */
244
.status-badge.status-active::after {
245
content: "";
@@ -235,7 +265,7 @@
265
display: none;
266
}
267
238
-/* END status with checkmark icon (like sketch) - use ::before only */
268
+/* END status with checkmark icon - use ::before only */
269
.status-end::before {
270
content: "✓";
271
margin-right: 2px;
@@ -477,8 +507,13 @@
507
.process-step-detail-content .step-kvp-key {
508
color: var(--color-primary);
509
font-weight: 500;
480
- min-width: 80px;
510
opacity: 0.8;
511
+ display: flex;
512
+ align-items: center;
513
+}
514
+
515
+.process-step-detail-content .step-kvp-key .material-symbols-outlined {
516
+ font-size: 0.9rem;
517
}
518
519
.process-step-detail-content .step-kvp-value {
@@ -540,8 +575,14 @@
575
font-size: 0.68rem;
576
font-weight: 600;
577
color: var(--color-primary);
543
- min-width: 70px;
578
flex-shrink: 0;
579
+ display: flex;
580
+ align-items: center;
581
+}
582
+
583
+.process-step-detail-content .tool-arg-label .material-symbols-outlined {
584
+ font-size: 0.9rem;
585
+ opacity: 0.85;
586
}
587
588
.process-step-detail-content .tool-arg-value {
@@ -558,6 +599,25 @@
599
border-left-color: transparent;
600
}
601
602
+.process-step-detail-content .step-tool-header .tool-icon {
603
+ font-size: 0.85rem;
604
+ color: var(--color-warning);
605
+ opacity: 0.85;
606
+}
607
+
608
+.process-step-detail-content .step-tool-header .tool-label {
609
+ font-size: 0.68rem;
610
+ font-weight: 600;
611
+ color: var(--color-warning);
612
+ opacity: 0.85;
613
+}
614
+
615
+.process-step-detail-content .step-tool-header .tool-name {
616
+ font-size: 0.72rem;
617
+ color: var(--color-text);
618
+ opacity: 0.85;
619
+}
620
+
621
/* Terminal-style output for code_exe */
622
.process-step-detail-content .step-terminal {
623
margin: var(--spacing-xs) 0;
@@ -658,46 +718,46 @@
718
border-color: rgba(14, 165, 233, 0.25);
719
}
720
661
-.light-mode .status-exe {
662
- background-color: rgba(147, 51, 234, 0.12);
663
- color: #7c3aed;
664
- border-color: rgba(147, 51, 234, 0.25);
721
+.light-mode .status-end {
722
+ background-color: rgba(22, 163, 74, 0.12);
723
+ color: #15803d;
724
+ border-color: rgba(22, 163, 74, 0.25);
725
}
726
667
-.light-mode .status-mcp {
727
+.light-mode .status-tool {
728
background-color: rgba(217, 119, 6, 0.12);
729
color: #b45309;
730
border-color: rgba(217, 119, 6, 0.25);
731
}
732
673
-.light-mode .status-mem {
674
- background-color: rgba(37, 99, 235, 0.12);
675
- color: #1d4ed8;
676
- border-color: rgba(37, 99, 235, 0.25);
733
+.light-mode .status-mcp {
734
+ background-color: rgba(217, 119, 6, 0.12);
735
+ color: #b45309;
736
+ border-color: rgba(217, 119, 6, 0.25);
737
}
738
679
-.light-mode .status-end {
680
- background-color: rgba(22, 163, 74, 0.12);
681
- color: #15803d;
682
- border-color: rgba(22, 163, 74, 0.25);
739
+.light-mode .status-sub {
740
+ background-color: rgba(13, 148, 136, 0.12);
741
+ color: #0f766e;
742
+ border-color: rgba(13, 148, 136, 0.25);
743
}
744
685
-.light-mode .status-err {
686
- background-color: rgba(220, 38, 38, 0.12);
687
- color: #b91c1c;
688
- border-color: rgba(220, 38, 38, 0.25);
745
+.light-mode .status-exe {
746
+ background-color: rgba(147, 51, 234, 0.12);
747
+ color: #7c3aed;
748
+ border-color: rgba(147, 51, 234, 0.25);
749
}
750
691
-.light-mode .status-wrn {
692
- background-color: rgba(234, 88, 12, 0.12);
693
- color: #c2410c;
694
- border-color: rgba(234, 88, 12, 0.25);
751
+.light-mode .status-brw {
752
+ background-color: rgba(99, 102, 241, 0.12);
753
+ color: #4f46e5;
754
+ border-color: rgba(99, 102, 241, 0.25);
755
}
756
697
-.light-mode .status-sub {
698
- background-color: rgba(13, 148, 136, 0.12);
699
- color: #0f766e;
700
- border-color: rgba(13, 148, 136, 0.25);
757
+.light-mode .status-wait {
758
+ background-color: rgba(100, 116, 139, 0.12);
759
+ color: #475569;
760
+ border-color: rgba(100, 116, 139, 0.25);
761
}
762
763
.light-mode .status-inf {
@@ -706,12 +766,36 @@
766
border-color: rgba(100, 116, 139, 0.25);
767
}
768
769
+.light-mode .status-hnt {
770
+ background-color: rgba(132, 204, 22, 0.12);
771
+ color: #65a30d;
772
+ border-color: rgba(132, 204, 22, 0.25);
773
+}
774
+
775
+.light-mode .status-wrn {
776
+ background-color: rgba(234, 88, 12, 0.12);
777
+ color: #c2410c;
778
+ border-color: rgba(234, 88, 12, 0.25);
779
+}
780
+
781
+.light-mode .status-err {
782
+ background-color: rgba(220, 38, 38, 0.12);
783
+ color: #b91c1c;
784
+ border-color: rgba(220, 38, 38, 0.25);
785
+}
786
+
787
.light-mode .status-utl {
788
background-color: rgba(71, 85, 105, 0.12);
789
color: #334155;
790
border-color: rgba(71, 85, 105, 0.25);
791
}
792
793
+.light-mode .status-usr {
794
+ background-color: rgba(14, 165, 233, 0.12);
795
+ color: #0284c7;
796
+ border-color: rgba(14, 165, 233, 0.25);
797
+}
798
+
799
/* Animation for loading state */
800
@keyframes pulse-step {
801
0%, 100% { opacity: 0.5; }
webui/css/messages.css
-6
@@ -78,13 +78,7 @@
78
}
79
80
.message-user {
81
- background-color: #4a4a4a;
82
- border-radius: var(--border-radius) !important;
83
- /* border-bottom-right-radius: var(--spacing-xxs); */
84
- /* min-width: 195px; */
81
text-align: end;
86
- box-shadow: inset 0 2rem 2rem -2rem rgba(0, 0, 0, 0.3),
87
- inset 0 -2rem 2rem -2rem rgba(0, 0, 0, 0.1);
82
}
83
84
.message-user > div {
webui/js/messages.js
+98
-58
@@ -218,42 +218,6 @@ export function _drawMessage(
218
// Update message classes
219
messageDiv.className = `message ${mainClass} ${messageClasses.join(" ")}`;
220
221
- // Handle heading
222
- if (heading) {
223
- let headingElement = messageDiv.querySelector(".msg-heading");
224
- if (!headingElement) {
225
- headingElement = document.createElement("div");
226
- headingElement.classList.add("msg-heading");
227
- messageDiv.insertBefore(headingElement, messageDiv.firstChild);
228
- }
229
-
230
- let headingH4 = headingElement.querySelector("h4");
231
- if (!headingH4) {
232
- headingH4 = document.createElement("h4");
233
- headingElement.appendChild(headingH4);
234
- }
235
- headingH4.innerHTML = convertIcons(escapeHTML(heading));
236
-
237
- if (resizeBtns) {
238
- let minMaxBtn = headingElement.querySelector(".msg-min-max-btns");
239
- if (!minMaxBtn) {
240
- minMaxBtn = document.createElement("div");
241
- minMaxBtn.classList.add("msg-min-max-btns");
242
- minMaxBtn.innerHTML = `
243
- <a href="#" class="msg-min-max-btn" @click.prevent="$store.messageResize.minimizeMessageClass('${mainClass}', $event)"><span class="material-symbols-outlined" x-text="$store.messageResize.getSetting('${mainClass}').minimized ? 'expand_content' : 'minimize'"></span></a>
244
- <a href="#" class="msg-min-max-btn" x-show="!$store.messageResize.getSetting('${mainClass}').minimized" @click.prevent="$store.messageResize.maximizeMessageClass('${mainClass}', $event)"><span class="material-symbols-outlined" x-text="$store.messageResize.getSetting('${mainClass}').maximized ? 'expand' : 'expand_all'"></span></a>
245
- `;
246
- headingElement.appendChild(minMaxBtn);
247
- }
248
- }
249
- } else {
250
- // Remove heading if it exists but heading is null
251
- const existingHeading = messageDiv.querySelector(".msg-heading");
252
- if (existingHeading) {
253
- existingHeading.remove();
254
- }
255
- }
256
-
221
// Find existing body div or create new one
222
let bodyDiv = messageDiv.querySelector(".message-body");
223
if (!bodyDiv) {
@@ -1162,8 +1126,8 @@ function createProcessGroup(id) {
1126
header.classList.add("process-group-header");
1127
header.innerHTML = `
1128
<span class="expand-icon"></span>
1165
- <span class="status-badge status-gen status-active group-status"><span class="badge-icon material-symbols-outlined">public</span>GEN</span>
1129
<span class="group-title">Processing...</span>
1130
+ <span class="status-badge status-gen status-active group-status"><span class="badge-icon material-symbols-outlined">public</span>GEN</span>
1131
<span class="group-metrics">
1132
<span class="metric-time" title="Start time"><span class="metric-value">--:--</span></span>
1133
<span class="metric-steps" title="Steps"><span class="metric-value">0</span></span>
@@ -1201,6 +1165,7 @@ function createProcessGroup(id) {
1165
function addProcessStep(group, id, type, heading, content, kvps, timestamp = null, durationMs = null, tokensIn = 0, tokensOut = 0) {
1166
const groupId = group.getAttribute("data-group-id");
1167
const stepsContainer = group.querySelector(".process-steps");
1168
+ const isGroupCompleted = group.classList.contains("process-group-completed");
1169
1170
// Create step element
1171
const step = document.createElement("div");
@@ -1235,9 +1200,7 @@ function addProcessStep(group, id, type, heading, content, kvps, timestamp = nul
1200
}
1201
}
1202
1238
- // Get step info
1239
- const icon = processGroupStore.getStepIcon(type);
1240
- const label = processGroupStore.getStepLabel(type);
1203
+ // Get step info from heading (single source of truth: backend)
1204
const title = getStepTitle(heading, kvps, type);
1205
1206
// Check if step should be expanded
@@ -1250,16 +1213,17 @@ function addProcessStep(group, id, type, heading, content, kvps, timestamp = nul
1213
const stepHeader = document.createElement("div");
1214
stepHeader.classList.add("process-step-header");
1215
1253
- // Get 3-letter status code and color class
1216
+ // Status code and color class from store (maps backend types)
1217
const statusCode = processGroupStore.getStepCode(type);
1218
const statusColorClass = processGroupStore.getStatusColorClass(type);
1219
1257
- // Get icon for step type
1258
- const stepIcon = processGroupStore.getStepBadgeIcon(type);
1220
+ // Icon extracted from heading (backend sends icon://xxx), fallback to type-based
1221
+ const stepIcon = extractIconFromHeading(heading) || processGroupStore.getStepIcon(type);
1222
1223
+ const activeClass = isGroupCompleted ? "" : " status-active";
1224
stepHeader.innerHTML = `
1225
<span class="step-expand-icon"></span>
1262
- <span class="status-badge ${statusColorClass} status-active"><span class="badge-icon material-symbols-outlined">${stepIcon}</span>${statusCode}</span>
1226
+ <span class="status-badge ${statusColorClass}${activeClass}"><span class="badge-icon material-symbols-outlined">${stepIcon}</span>${statusCode}</span>
1227
<span class="step-title">${escapeHTML(title)}</span>
1228
`;
1229
@@ -1355,12 +1319,23 @@ function getStepTitle(heading, kvps, type) {
1319
}
1320
}
1321
1358
- return processGroupStore.getStepLabel(type);
1322
+ // Fallback: capitalize type (backend is source of truth)
1323
+ return type ? type.charAt(0).toUpperCase() + type.slice(1).replace(/_/g, ' ') : 'Process';
1324
+}
1325
+
1326
+/**
1327
+ * Extract icon name from heading with icon:// prefix
1328
+ * Returns the icon name (e.g., "terminal") or null if no prefix found
1329
+ */
1330
+function extractIconFromHeading(heading) {
1331
+ if (!heading) return null;
1332
+ const match = String(heading).match(/^icon:\/\/([a-zA-Z0-9_]+)/);
1333
+ return match ? match[1] : null;
1334
}
1335
1336
/**
1362
- * Clean step title by removing icon:// prefixes
1363
- * Preserves agent markers (A0:, A1:, etc.) so users can see which agent is executing
1337
+ * Clean step title by removing icon:// prefixes and status phrases
1338
+ * Preserves agent markers (A1:, A2:, etc.) so users can see which subordinate agent is executing
1339
*/
1340
function cleanStepTitle(text, maxLength) {
1341
if (!text) return "";
@@ -1369,10 +1344,6 @@ function cleanStepTitle(text, maxLength) {
1344
// Remove icon:// patterns (e.g., "icon://network_intelligence")
1345
cleaned = cleaned.replace(/icon:\/\/[a-zA-Z0-9_]+\s*/g, "");
1346
1372
- // Keep agent markers (A0:, A1:, etc.) - users need to see which agent is executing
1373
- // Only remove "A0:" as it's the main agent (implied)
1374
- cleaned = cleaned.replace(/^A0:\s*/i, "");
1375
-
1347
// Trim whitespace
1348
cleaned = cleaned.trim();
1349
@@ -1440,6 +1411,12 @@ function renderStepDetailContent(container, content, kvps, type = null) {
1411
1412
const lowerKey = key.toLowerCase();
1413
1414
+ // Skip headline and tool_name - they're shown elsewhere
1415
+ if (lowerKey === "headline" || lowerKey === "tool_name") continue;
1416
+
1417
+ // Skip query in agent steps - it's shown in the tool call step
1418
+ if (type === "agent" && lowerKey === "query") continue;
1419
+
1420
// Special handling for thoughts/reasoning - render with single lightbulb icon
1421
if (lowerKey === "thoughts" || lowerKey === "thinking" || lowerKey === "reflection" || lowerKey === "reasoning") {
1422
const thoughtsDiv = document.createElement("div");
@@ -1472,18 +1449,45 @@ function renderStepDetailContent(container, content, kvps, type = null) {
1449
continue;
1450
}
1451
1475
- // Special handling for tool_args - render as structured labeled parameters
1476
- if (lowerKey === "tool_args" && typeof value === "object" && value !== null) {
1452
+ // Special handling for tool_args - render only for tool/mcp types (skip for agent)
1453
+ if (lowerKey === "tool_args") {
1454
+ // Skip tool_args for agent steps - it's shown in the tool call step
1455
+ if (type === "agent") continue;
1456
+
1457
+ if (typeof value !== "object" || value === null) continue;
1458
const argsDiv = document.createElement("div");
1459
argsDiv.classList.add("step-tool-args");
1460
1461
+ // Icon mapping for common tool arguments
1462
+ const argIcons = {
1463
+ 'query': 'search',
1464
+ 'url': 'link',
1465
+ 'path': 'folder',
1466
+ 'file': 'description',
1467
+ 'code': 'code',
1468
+ 'command': 'terminal',
1469
+ 'message': 'chat',
1470
+ 'text': 'notes',
1471
+ 'content': 'article',
1472
+ 'name': 'label',
1473
+ 'id': 'tag',
1474
+ 'type': 'category'
1475
+ };
1476
+
1477
for (const [argKey, argValue] of Object.entries(value)) {
1478
const argRow = document.createElement("div");
1479
argRow.classList.add("tool-arg-row");
1480
1481
const argLabel = document.createElement("span");
1482
argLabel.classList.add("tool-arg-label");
1486
- argLabel.textContent = convertToTitleCase(argKey) + ":";
1483
+
1484
+ // Use icon if available, otherwise use text label
1485
+ const lowerArgKey = argKey.toLowerCase();
1486
+ if (argIcons[lowerArgKey]) {
1487
+ argLabel.innerHTML = `<span class="material-symbols-outlined">${argIcons[lowerArgKey]}</span>`;
1488
+ } else {
1489
+ argLabel.textContent = convertToTitleCase(argKey) + ":";
1490
+ }
1491
1492
const argVal = document.createElement("span");
1493
argVal.classList.add("tool-arg-value");
@@ -1508,7 +1512,31 @@ function renderStepDetailContent(container, content, kvps, type = null) {
1512
1513
const keySpan = document.createElement("span");
1514
keySpan.classList.add("step-kvp-key");
1511
- keySpan.textContent = convertToTitleCase(key) + ":";
1515
+
1516
+ // Icon mapping for common kvp keys
1517
+ const kvpIcons = {
1518
+ 'query': 'search',
1519
+ 'url': 'link',
1520
+ 'path': 'folder',
1521
+ 'file': 'description',
1522
+ 'code': 'code',
1523
+ 'command': 'terminal',
1524
+ 'message': 'chat',
1525
+ 'text': 'notes',
1526
+ 'content': 'article',
1527
+ 'name': 'label',
1528
+ 'id': 'tag',
1529
+ 'type': 'category',
1530
+ 'runtime': 'memory',
1531
+ 'result': 'output'
1532
+ };
1533
+
1534
+ // lowerKey already defined above
1535
+ if (kvpIcons[lowerKey]) {
1536
+ keySpan.innerHTML = `<span class="material-symbols-outlined">${kvpIcons[lowerKey]}</span>`;
1537
+ } else {
1538
+ keySpan.textContent = convertToTitleCase(key) + ":";
1539
+ }
1540
1541
const valueSpan = document.createElement("span");
1542
valueSpan.classList.add("step-kvp-value");
@@ -1578,6 +1606,7 @@ function updateProcessGroupHeader(group) {
1606
const titleEl = group.querySelector(".group-title");
1607
const statusEl = group.querySelector(".group-status");
1608
const metricsEl = group.querySelector(".group-metrics");
1609
+ const isCompleted = group.classList.contains("process-group-completed");
1610
1611
// Update step count in metrics
1612
const stepsMetricEl = metricsEl?.querySelector(".metric-steps .metric-value");
@@ -1626,6 +1655,14 @@ function updateProcessGroupHeader(group) {
1655
tokensMetricEl.textContent = "--";
1656
}
1657
}
1658
+
1659
+ // Once a group is completed, never re-enable any loading spinners (status-active).
1660
+ // This prevents late util/tool messages from making a completed group look "running".
1661
+ if (isCompleted) {
1662
+ const activeBadges = group.querySelectorAll(".status-badge.status-active");
1663
+ activeBadges.forEach(badge => badge.classList.remove("status-active"));
1664
+ return;
1665
+ }
1666
1667
if (steps.length > 0) {
1668
// Get the last step's type for status
@@ -1635,9 +1672,12 @@ function updateProcessGroupHeader(group) {
1672
1673
// Update status badge with icon (keep status-active during execution)
1674
if (statusEl) {
1675
+ // Status code and color class from store (maps backend types)
1676
const statusCode = processGroupStore.getStepCode(lastType);
1677
const statusColorClass = processGroupStore.getStatusColorClass(lastType);
1640
- const badgeIcon = processGroupStore.getStepBadgeIcon(lastType);
1678
+ // Get icon from the last step's badge, fallback to type-based
1679
+ const lastStepBadge = lastStep.querySelector(".badge-icon");
1680
+ const badgeIcon = lastStepBadge?.textContent || processGroupStore.getStepIcon(lastType);
1681
statusEl.innerHTML = `<span class="badge-icon material-symbols-outlined">${badgeIcon}</span>${statusCode}`;
1682
statusEl.className = `status-badge ${statusColorClass} status-active group-status`;
1683
}
@@ -1683,8 +1723,8 @@ function markProcessGroupComplete(group, responseTitle) {
1723
// Update status badge to END (remove status-active)
1724
const statusEl = group.querySelector(".group-status");
1725
if (statusEl) {
1686
- statusEl.textContent = "END";
1687
- statusEl.className = "status-badge status-end group-status"; // No status-active
1726
+ statusEl.innerHTML = '<span class="badge-icon material-symbols-outlined">check_circle</span>END';
1727
+ statusEl.className = "status-badge status-response group-status"; // No status-active
1728
}
1729
1730
// Remove status-active from all step badges (stop spinners)