UI fixes, cleanups
frdel committed
Jan 28, 2026 at 15:53 UTC
dd717dc8cc73a1bb388467071b952833dc2e002d
5 files changed
+66
-627
webui/components/messages/process-group/process-group.css
+2
-1
@@ -493,6 +493,7 @@
493
.process-step.expanded > .process-step-detail > .process-step-detail-scroll {
494
max-height: 40em;
495
overflow-y: auto;
496
+ border-radius: var(--border-radius-sm);
497
}
498
499
.process-step.expanded > .process-step-detail > .process-step-detail-scroll > .process-step-detail-scroll {
@@ -625,7 +626,7 @@
626
margin: var(--spacing-xs) 0 0 0;
627
padding: var(--spacing-xs);
628
background: rgba(0, 0, 0, 0.8);
628
- border-radius: 8px;
629
+ border-radius: var(--border-radius-sm);
630
color: #c9d1d9;
631
white-space: pre;
632
font-family: monospace;
webui/css/messages.css
+9
-10
@@ -10,18 +10,14 @@
10
overflow-y: scroll;
11
overflow-x: hidden;
12
scroll-behavior: auto !important; /* avoid infinite scrolling! */
13
- /* padding: var(--spacing-md) 10em 0 !important; */
13
+ padding-left: var(--spacing-sm) !important;
14
+ padding-right: var(--spacing-md) !important;
15
+ padding-top: 4rem !important;
16
+ padding-bottom: 4rem !important;
17
-webkit-transition: all 0.3s ease;
18
transition: all 0.3s ease;
19
scrollbar-width: thin;
20
scrollbar-color: #555 transparent;
18
- padding-left: var(--spacing-sm);
19
- padding-right: var(--spacing-sm);
20
- padding-bottom: 5em;
21
-}
22
-
23
-#chat-history > *:first-child {
24
- margin-top: 4.4em;
21
}
22
23
/* Scrollbar styling for Firefox */
@@ -577,8 +573,11 @@
573
574
@media (max-width: 1024px) {
575
#chat-history {
580
- padding: var(--spacing-md) var(--spacing-sm) 0 !important;
581
- }
576
+ padding-left: var(--spacing-sm) !important;
577
+ padding-right: var(--spacing-md) !important;
578
+ padding-top: 4rem !important;
579
+ padding-bottom: 2rem !important;
580
+}
581
}
582
583
@media (max-width: 640px) {
webui/index.css
+1
-24
@@ -81,6 +81,7 @@
81
82
/* Other variables */
83
--border-radius: 1.125rem;
84
+ --border-radius-sm: 0.5rem;
85
--transition-speed: 0.3s;
86
--svg-filter: brightness(0) saturate(100%) var(--color-primary-filter);
87
--color-primary-filter: invert(73%) sepia(17%) saturate(360%)
@@ -739,30 +740,6 @@ h4 {
740
padding: 0 !important;
741
}
742
742
-/* #chat-history .attachment-item {
743
- padding: 0 !important;
744
- background-color: transparent !important;
745
- border: none !important;
746
- box-shadow: none !important;
747
- transform: none !important;
748
-}
749
-
750
-#chat-history .attachment-item:hover {
751
- background-color: transparent !important;
752
- border: none !important;
753
- box-shadow: none !important;
754
- transform: none !important;
755
-}
756
-
757
-#chat-history .attachment-preview,
758
-#chat-history .attachment-item.image-type .attachment-preview {
759
- background: transparent;
760
-}
761
-
762
-.attachment-image .attachment-preview {
763
- margin-right: 0;
764
-} */
765
-
743
.attachment-info,
744
.file-info {
745
display: flex;
webui/index.js
-2
@@ -459,8 +459,6 @@ function setProgressBarShine(progressBarEl, active) {
459
if (!progressBarEl) return;
460
if (!active) {
461
removeClassFromElement(progressBarEl, "shiny-text");
462
- // clear any lingering shines in process steps
463
- msgs.clearActiveStepShine();
462
} else {
463
addClassToElement(progressBarEl, "shiny-text");
464
}
webui/js/messages.js
+54
-590
@@ -9,73 +9,15 @@ import { store as stepDetailStore } from "/components/modals/process-step-detail
9
import { store as preferencesStore } from "/components/sidebar/bottom/preferences/preferences-store.js";
10
import { formatDuration } from "./time-utils.js";
11
12
-// ============================================
13
-// Timing Constants
14
-// ============================================
12
// Delay before collapsing previous steps when a new step is added
13
const STEP_COLLAPSE_DELAY_MS = 3000;
14
// Delay before collapsing the last step when processing completes
15
const FINAL_STEP_COLLAPSE_DELAY_MS = 3000;
16
20
-// // Tool-specific status codes (fallback for tool steps)
21
-// const TOOL_STATUS_CODES = {
22
-// call_subordinate: "SUB",
23
-// search_engine: "WEB",
24
-// a2a_chat: "A2A",
25
-// behaviour_adjustment: "ADJ",
26
-// document_query: "DOC",
27
-// vision_load: "EYE",
28
-// notify_user: "NTF",
29
-// scheduler: "SCH",
30
-// unknown: "UNK",
31
-// memory_save: "MEM",
32
-// memory_load: "MEM",
33
-// memory_forget: "MEM",
34
-// memory_delete: "MEM",
35
-// };
36
-
37
-// Tool-specific status classes (fallback for tool steps)
38
-// const TOOL_STATUS_CLASSES = {
39
-// call_subordinate: "status-sub",
40
-// };
41
-
42
-const TYPE_STATUS_CODES = {
43
- // agent: "GEN",
44
- response: "END",
45
- // tool: "USE",
46
- // code_exe: "EXE",
47
- // browser: "WWW",
48
- // progress: "HLD",
49
- // mcp: "MCP",
50
- // subagent: "SUB",
51
- info: "INF",
52
- hint: "HNT",
53
- // warning: "WRN",
54
- rate_limit: "WRN",
55
- // error: "ERR",
56
- // util: "UTL",
57
- done: "END",
58
-};
59
-
60
-const TYPE_STATUS_CLASSES = {
61
- // agent: "status-gen",
62
- response: "status-end",
63
- // tool: "status-tool",
64
- // code_exe: "status-exe",
65
- // browser: "status-www",
66
- // progress: "status-wait",
67
- // mcp: "status-mcp",
68
- // subagent: "status-sub",
69
- info: "status-inf",
70
- hint: "status-hnt",
71
- // warning: "status-wrn",
72
- rate_limit: "status-wrn",
73
- // error: "status-err",
74
- // util: "status-utl",
75
- done: "status-end",
76
-};
77
-
17
+// dom references
18
let _chatHistory = null;
19
+
20
+// state vars
21
let _massRender = false;
22
23
// handlers for log message rendering
@@ -120,34 +62,25 @@ export function getMessageHandler(type) {
62
* Mark a process group as the active one (via .active class)
63
*/
64
function setActiveProcessGroup(group) {
123
- if (!group) return;
124
-
125
- // Already active? Nothing to do
126
- if (group.classList.contains("active")) return;
127
-
128
- // Clear active + shiny from all other groups
129
- getChatHistoryEl()
130
- .querySelectorAll(".process-group.active")
131
- .forEach((g) => {
132
- if (g !== group) {
133
- g.classList.remove("active");
134
- g.querySelectorAll(".step-title.shiny-text").forEach((el) =>
135
- el.classList.remove("shiny-text"),
136
- );
137
- }
138
- });
139
-
140
- // Mark this group as active
141
- group.classList.add("active");
142
-}
65
+ // if (!group) return;
66
+
67
+ // // Already active? Nothing to do
68
+ // if (group.classList.contains("active")) return;
69
+
70
+ // // Clear active + shiny from all other groups
71
+ // getChatHistoryEl()
72
+ // .querySelectorAll(".process-group.active")
73
+ // .forEach((g) => {
74
+ // if (g !== group) {
75
+ // g.classList.remove("active");
76
+ // g.querySelectorAll(".step-title.shiny-text").forEach((el) =>
77
+ // el.classList.remove("shiny-text"),
78
+ // );
79
+ // }
80
+ // });
81
144
-export function clearActiveStepShine() {
145
- // Clear all shiny step titles in process steps
146
- getChatHistoryEl()
147
- .querySelectorAll(".process-step .step-title.shiny-text")
148
- .forEach((el) => {
149
- el.classList.remove("shiny-text");
150
- });
82
+ // // Mark this group as active
83
+ // group.classList.add("active");
84
}
85
86
function getChatHistoryEl() {
@@ -393,7 +326,7 @@ function drawProcessStep({
326
let step = document.getElementById(stepId);
327
328
const isNewStep = !step;
396
- const isGroupCompleted = group.classList.contains("process-group-completed");
329
+ const isGroupComplete = isProcessGroupComplete(group);
330
331
if (isNewStep) {
332
// create the base DOM element for the step
@@ -409,25 +342,6 @@ function drawProcessStep({
342
// apply step classes
343
if (classes) step.classList.add(...classes);
344
412
- // if (toolName) {
413
- // step.setAttribute("data-tool-name", toolName);
414
- // }
415
- // timestamp data
416
- // if (log.timestamp) {
417
- // step.setAttribute("data-timestamp", log.timestamp);
418
-
419
- // if (!group.getAttribute("data-start-timestamp")) {
420
- // group.setAttribute("data-start-timestamp", log.timestamp);
421
- // const timeMetricEl = group.querySelector(".metric-time .metric-value");
422
- // if (timeMetricEl) {
423
- // const date = new Date(parseFloat(timestamp) * 1000);
424
- // const hours = String(date.getHours()).padStart(2, "0");
425
- // const minutes = String(date.getMinutes()).padStart(2, "0");
426
- // timeMetricEl.textContent = `${hours}:${minutes}`;
427
- // }
428
- // }
429
- // }
430
-
345
let appendTarget = stepsContainer;
346
const parentStep = findParentDelegationStep(group, log.agentno);
347
if (parentStep) {
@@ -450,24 +364,22 @@ function drawProcessStep({
364
365
// expand all or current step based on settings
366
const detailMode = preferencesStore.detailMode;
453
- const isActiveGroup = group.classList.contains("active");
367
+ // const isActiveGroup = group.classList.contains("active");
368
369
//expand all
370
if (detailMode === "expanded") {
371
step.classList.add("expanded");
372
// expand current step and schedule collapse of previous
459
- } else if (detailMode === "current") {
460
- if (isActiveGroup && !isGroupCompleted) {
461
- step.classList.add("expanded");
462
- const allExpandedSteps = stepsContainer.querySelectorAll(
463
- ".process-step.expanded",
464
- );
465
- allExpandedSteps.forEach((expandedStep) => {
466
- if (expandedStep.id !== stepId) {
373
+ } else if (detailMode === "current" && !isMassRender() && !isGroupComplete) {
374
+ step.classList.add("expanded");
375
+ const allExpandedSteps = stepsContainer.querySelectorAll(
376
+ ".process-step.expanded",
377
+ );
378
+ allExpandedSteps.forEach((expandedStep) => {
379
+ if (expandedStep.id !== stepId) {
380
scheduleStepCollapse(expandedStep, STEP_COLLAPSE_DELAY_MS);
381
}
382
});
470
- }
383
}
384
}
385
@@ -496,20 +408,7 @@ function drawProcessStep({
408
"process-step-detail-scroll",
409
);
410
499
- // else {
500
- // if (timestamp && !step.hasAttribute("data-timestamp")) {
501
- // step.setAttribute("data-timestamp", timestamp);
502
- // }
503
- // if (agentno !== undefined) {
504
- // step.setAttribute("data-agent-number", agentno);
505
- // }
506
- // }
507
-
508
- // const toolNameToUse = resolveToolName(type, kvps, step) || toolName;
509
- // if (toolNameToUse) {
510
- // step.setAttribute("data-tool-name", toolNameToUse);
511
- // }
512
-
411
+ // expand/collapse handler for step header
412
if (!stepHeader.hasAttribute("data-expand-handler")) {
413
stepHeader.setAttribute("data-expand-handler", "true");
414
stepHeader.addEventListener("click", (e) => {
@@ -544,39 +443,7 @@ function drawProcessStep({
443
const titleEl = ensureChild(stepHeader, ".step-title", "span", "step-title");
444
titleEl.textContent = title;
445
547
- // const detail = ensureChild(step, ".process-step-detail", "div", "process-step-detail");
548
- // const detailContent = ensureChild(
549
- // detail,
550
- // ".process-step-detail-content",
551
- // "div",
552
- // "process-step-detail-content",
553
- // );
554
-
555
- // let skipFullRender = false;
556
-
557
- // const terminal = stepDetailContent.querySelector(".terminal-output");
558
- // const scroller = terminal ? new Scroller(terminal) : null;
559
-
560
- // if (type === "browser" && kvps?.screenshot) {
561
- // const existingImg = detailContent.querySelector(".screenshot-img");
562
- // const newSrc = kvps.screenshot.replace("img://", "/image_get?path=");
563
- // if (existingImg) {
564
- // if (!existingImg.src.endsWith(newSrc.split("?path=")[1])) {
565
- // existingImg.src = newSrc;
566
- // }
567
- // skipFullRender = true;
568
- // }
569
- // }
570
-
571
- // if (!skipFullRender) {
572
- // renderStepDetailContent(detailContent, content, kvps, type);
573
-
574
- // const newTerminal = detailContent.querySelector(".terminal-output");
575
- // if (newTerminal && scroller?.wasAtBottom) {
576
- // newTerminal.scrollTop = newTerminal.scrollHeight;
577
- // }
578
- // }
579
-
446
+ // auto-scroller of the step detail
447
const detailScroller = new Scroller(stepDetailScroll); // scroller for step detail content
448
449
// update KVPs of the step detail
@@ -592,14 +459,9 @@ function drawProcessStep({
459
);
460
stepDetailContent.textContent = content;
461
595
- // const detailDataToUse =
596
- // detailData ||
597
- // buildDetailPayload({
598
- // ...stepData,
599
- // toolName: toolNameToUse,
600
- // statusCode: resolvedStatusCode,
601
- // statusClass: resolvedStatusClass,
602
- // });
462
+ // reapply scroll position (autoscroll if bottom) - only when expanded already and not mass rendering
463
+ if (isExpanded && !isMassRender()) detailScroller.reApplyScroll();
464
+
465
466
// Render action buttons: get/create container, clear, append
467
const stepActionBtns = ensureChild(
@@ -614,11 +476,15 @@ function drawProcessStep({
476
.filter(Boolean)
477
.forEach((button) => stepActionBtns.appendChild(button));
478
617
- if (isExpanded && !isMassRender()) detailScroller.reApplyScroll(); // reapply scroll position (autoscroll if bottom) - only when expanded already and not
479
480
+ // update the process grop header by this step
481
updateProcessGroupHeader(group);
482
621
- if (isNewStep && !isGroupCompleted) {
483
+ // remove shine from previous steps and add to this one if new and not completed
484
+ if (isNewStep && !isGroupComplete) {
485
+ stepDetailScroll.querySelectorAll(".step-title.shiny-text").forEach((el) => {
486
+ el.classList.remove("shiny-text");
487
+ });
488
titleEl.classList.add("shiny-text");
489
}
490
@@ -1553,137 +1419,6 @@ export function drawMessageError({
1419
});
1420
}
1421
1556
-// export function drawMessageError({
1557
-// id,
1558
-// heading,
1559
-// content,
1560
-// kvps = null,
1561
-// ...additional
1562
-// }) {
1563
-// const messageContainer = getOrCreateMessageContainer(id, "mid", [
1564
-// "ai-container",
1565
-// "center-container",
1566
-// ]);
1567
-
1568
-// // Create or get the message div
1569
-// let messageDiv = messageContainer.querySelector(".message");
1570
-// if (!messageDiv) {
1571
-// messageDiv = document.createElement("div");
1572
-// messageDiv.classList.add("message", "message-error-group");
1573
-// messageContainer.appendChild(messageDiv);
1574
-// }
1575
-
1576
-// // Check if error group already exists
1577
-// let errorGroup = messageDiv.querySelector(".error-group");
1578
-// if (!errorGroup) {
1579
-// errorGroup = document.createElement("div");
1580
-// errorGroup.classList.add("error-group");
1581
-// errorGroup.setAttribute("data-error-id", id);
1582
-
1583
-// // Create header (clickable for expand/collapse)
1584
-// const header = document.createElement("div");
1585
-// header.classList.add("error-group-header");
1586
-
1587
-// // Expand icon (triangle)
1588
-// const expandIcon = document.createElement("span");
1589
-// expandIcon.classList.add("expand-icon");
1590
-// header.appendChild(expandIcon);
1591
-
1592
-// // Status badge (before title)
1593
-// const badge = document.createElement("span");
1594
-// badge.classList.add("step-badge", "status-err");
1595
-// badge.textContent = "ERR";
1596
-// header.appendChild(badge);
1597
-
1598
-// // Title
1599
-// const title = document.createElement("span");
1600
-// title.classList.add("error-title");
1601
-// title.textContent = "Error";
1602
-// header.appendChild(title);
1603
-
1604
-// // Subtitle (short error description)
1605
-// const subtitle = document.createElement("span");
1606
-// subtitle.classList.add("error-subtitle");
1607
-// header.appendChild(subtitle);
1608
-
1609
-// // Click handler for expand/collapse
1610
-// header.addEventListener("click", () => {
1611
-// errorGroup.classList.toggle("expanded");
1612
-// });
1613
-
1614
-// errorGroup.appendChild(header);
1615
-
1616
-// // Create content container (collapsible)
1617
-// const contentWrapper = document.createElement("div");
1618
-// contentWrapper.classList.add("error-group-content");
1619
-
1620
-// const contentInner = document.createElement("div");
1621
-// contentInner.classList.add("error-content-inner");
1622
-// contentWrapper.appendChild(contentInner);
1623
-
1624
-// errorGroup.appendChild(contentWrapper);
1625
-// messageDiv.appendChild(errorGroup);
1626
-
1627
-// // Check detail mode and expand if needed
1628
-// const detailMode = preferencesStore.detailMode || "current";
1629
-// if (detailMode === "current" || detailMode === "expanded") {
1630
-// errorGroup.classList.add("expanded");
1631
-// }
1632
-// }
1633
-
1634
-// // Update subtitle with short error description
1635
-// const subtitle = errorGroup.querySelector(".error-subtitle");
1636
-// if (subtitle) {
1637
-// // Extract short description from heading or content
1638
-// let shortDesc = "";
1639
-// // Skip if heading is just "Error" (redundant with title)
1640
-// if (heading && heading.trim() && heading.trim().toLowerCase() !== "error") {
1641
-// shortDesc = heading.trim();
1642
-// }
1643
-// // If no useful heading, try to extract from content
1644
-// if (!shortDesc && content && content.trim()) {
1645
-// const lines = content.trim().split("\n");
1646
-// // Look for the error line (usually last meaningful line or one matching ErrorType: pattern)
1647
-// for (let i = lines.length - 1; i >= 0; i--) {
1648
-// const line = lines[i].trim();
1649
-// if (line && /^[\w\.]+Error[:\s]/.test(line)) {
1650
-// shortDesc = line;
1651
-// break;
1652
-// }
1653
-// }
1654
-// // Fallback to first non-empty line if no error pattern found
1655
-// if (!shortDesc) {
1656
-// for (const line of lines) {
1657
-// if (line.trim() && !line.startsWith("Traceback")) {
1658
-// shortDesc = line.trim();
1659
-// break;
1660
-// }
1661
-// }
1662
-// }
1663
-// }
1664
-// // Truncate if too long
1665
-// if (shortDesc.length > 100) {
1666
-// shortDesc = shortDesc.substring(0, 97) + "...";
1667
-// }
1668
-// subtitle.textContent = shortDesc;
1669
-// subtitle.title = shortDesc; // Full text on hover
1670
-// }
1671
-
1672
-// // Update content (full callstack)
1673
-// const contentInner = errorGroup.querySelector(".error-content-inner");
1674
-// if (contentInner && content) {
1675
-// contentInner.innerHTML = "";
1676
-
1677
-// // Create pre element for callstack/content
1678
-// const pre = document.createElement("pre");
1679
-// pre.classList.add("error-callstack");
1680
-// pre.textContent = content;
1681
-// contentInner.appendChild(pre);
1682
-
1683
-// }
1684
-
1685
-// messageContainer.classList.add("center-container");
1686
-// }
1422
1423
function drawKvpsIncremental(container, kvps, latex) {
1424
// existing KVPS table
@@ -1809,27 +1544,6 @@ function convertToTitleCase(str) {
1544
});
1545
}
1546
1812
-/**
1813
- * Clean text value by removing standalone bracket lines and trimming
1814
- * Handles both strings and arrays (filters out bracket-only items)
1815
- */
1816
-// function cleanTextValue(value) {
1817
-// if (Array.isArray(value)) {
1818
-// return value
1819
-// .filter(
1820
-// (item) =>
1821
-// item && String(item).trim() && !/^[\[\]]$/.test(String(item).trim()),
1822
-// )
1823
-// .join("\n");
1824
-// }
1825
-// if (typeof value === "object" && value !== null) {
1826
-// return JSON.stringify(value, null, 2);
1827
-// }
1828
-// return String(value)
1829
-// .replace(/^\s*[\[\]]\s*$/gm, "")
1830
-// .trim();
1831
-// }
1832
-
1547
function convertImageTags(content) {
1548
// Regular expression to match <image> tags and extract base64 content
1549
const imageTagRegex = /<image>(.*?)<\/image>/g;
@@ -2173,15 +1887,6 @@ function findParentDelegationStep(group, agentno) {
1887
* Get a concise title for a process step
1888
*/
1889
function getStepTitle(heading, kvps, type) {
2176
- // code_exe: show command when finished
2177
- // const showCommand =
2178
- // type === "code_exe" &&
2179
- // kvps?.code &&
2180
- // /done_all|code_execution_tool/.test(heading || "");
2181
- // if (showCommand) {
2182
- // const s = kvps.session ?? kvps.Session;
2183
- // return `${s != null ? `[${s}] ` : ""}${kvps.runtime || "bash"}> ${kvps.code.trim()}`;
2184
- // }
1890
1891
// Try to get a meaningful title from heading or kvps
1892
if (heading && heading.trim()) {
@@ -2243,222 +1948,6 @@ function cleanStepTitle(text, maxLength = 100) {
1948
return truncateText(cleaned, maxLength);
1949
}
1950
2246
-/**
2247
- * Render content for step detail panel
2248
- */
2249
-// function renderStepDetailContent(container, content, kvps, type = null) {
2250
-// container.innerHTML = "";
2251
-
2252
-// drawKvpsIncremental(container, kvps);
2253
-
2254
-// // Special handling for response type - show content as markdown (for subordinate responses)
2255
-// if (type === "response" && content && content.trim()) {
2256
-// const responseDiv = document.createElement("div");
2257
-// responseDiv.classList.add("step-response-content");
2258
-
2259
-// // Parse markdown
2260
-// let processedContent = content;
2261
-// processedContent = convertImageTags(processedContent);
2262
-// processedContent = convertImgFilePaths(processedContent);
2263
-// processedContent = convertFilePaths(processedContent);
2264
-// processedContent = marked.parse(processedContent, { breaks: true });
2265
-// processedContent = convertPathsToLinks(processedContent);
2266
-// processedContent = addBlankTargetsToLinks(processedContent);
2267
-
2268
-// responseDiv.innerHTML = processedContent;
2269
-// container.appendChild(responseDiv);
2270
-// return;
2271
-// }
2272
-
2273
-// // Special handling for warning/error types - always show content prominently
2274
-// if ((type === "warning" || type === "error") && content && content.trim()) {
2275
-// const warningDiv = document.createElement("div");
2276
-// warningDiv.classList.add("step-warning-content");
2277
-// warningDiv.textContent = content;
2278
-// container.appendChild(warningDiv);
2279
-// // Don't return - also show kvps if present
2280
-// }
2281
-
2282
-// // Special handling for code_exe type - render as terminal-style output
2283
-// if (type === "code_exe" && kvps) {
2284
-// const runtime = kvps.runtime || kvps.Runtime || "bash";
2285
-// const code = kvps.code || kvps.Code || "";
2286
-// const output = content || "";
2287
-
2288
-// if (code || output) {
2289
-// const terminalDiv = document.createElement("div");
2290
-// terminalDiv.classList.add("step-terminal");
2291
-
2292
-// // Show output if present (no truncation - CSS handles max-height)
2293
-// if (output && output.trim()) {
2294
-// const outputPre = document.createElement("pre");
2295
-// outputPre.classList.add("terminal-output");
2296
-// // Escape HTML first, then convert paths to clickable links
2297
-// let processedOutput = escapeHTML(output);
2298
-// processedOutput = convertPathsToLinks(processedOutput);
2299
-// outputPre.innerHTML = processedOutput;
2300
-// terminalDiv.appendChild(outputPre);
2301
-// }
2302
-
2303
-// container.appendChild(terminalDiv);
2304
-// }
2305
-
2306
-// // Still render thoughts if present (but not reasoning - that's native model thinking, not structured output)
2307
-// if (kvps.thoughts || kvps.thinking) {
2308
-// const thoughtKey = kvps.thoughts ? "thoughts" : "thinking";
2309
-// const thoughtValue = kvps[thoughtKey];
2310
-// renderThoughts(container, thoughtValue);
2311
-// }
2312
-
2313
-// return;
2314
-// }
2315
-
2316
-// // Add KVPs if present
2317
-// if (kvps && Object.keys(kvps).length > 0) {
2318
-// const kvpsDiv = document.createElement("div");
2319
-// kvpsDiv.classList.add("step-kvps");
2320
-
2321
-// for (const [key, value] of Object.entries(kvps)) {
2322
-// // Skip internal/display keys
2323
-// if (key === "finished" || key === "attachments") continue;
2324
-
2325
-// // Skip code_exe specific keys that we handle specially above
2326
-// if (
2327
-// type === "code_exe" &&
2328
-// (key.toLowerCase() === "runtime" ||
2329
-// key.toLowerCase() === "session" ||
2330
-// key.toLowerCase() === "code")
2331
-// ) {
2332
-// continue;
2333
-// }
2334
-
2335
-// const lowerKey = key.toLowerCase();
2336
-
2337
-// // Skip headline and tool_name - they're shown elsewhere
2338
-// if (lowerKey === "headline" || lowerKey === "tool_name") continue;
2339
-
2340
-// // Skip query in agent steps - it's shown in the tool call step
2341
-// if (type === "agent" && lowerKey === "query") continue;
2342
-
2343
-// // Special handling for thoughts - render with single lightbulb icon
2344
-// // Skip reasoning
2345
-// if (lowerKey === "reasoning") continue;
2346
-// if (
2347
-// lowerKey === "thoughts" ||
2348
-// lowerKey === "thinking" ||
2349
-// lowerKey === "reflection"
2350
-// ) {
2351
-// renderThoughts(kvpsDiv, value);
2352
-// continue;
2353
-// }
2354
-
2355
-// // Special handling for tool_args - render only for tool/mcp types (skip for agent)
2356
-// if (lowerKey === "tool_args") {
2357
-// // Skip tool_args for agent steps - it's shown in the tool call step
2358
-// if (type === "agent") continue;
2359
-
2360
-// if (typeof value !== "object" || value === null) continue;
2361
-// const argsDiv = document.createElement("div");
2362
-// argsDiv.classList.add("step-tool-args");
2363
-
2364
-// for (const [argKey, argValue] of Object.entries(value)) {
2365
-// const argRow = document.createElement("div");
2366
-// argRow.classList.add("tool-arg-row");
2367
-
2368
-// const argLabel = document.createElement("span");
2369
-// argLabel.classList.add("tool-arg-label");
2370
-
2371
-// const iconName = extractIconFromKey(argKey);
2372
-// if (iconName) {
2373
-// argLabel.innerHTML = `<span class="material-symbols-outlined">${iconName}</span>`;
2374
-// } else {
2375
-// argLabel.textContent = convertToTitleCase(argKey) + ":";
2376
-// }
2377
-
2378
-// const argVal = document.createElement("span");
2379
-// argVal.classList.add("tool-arg-value");
2380
-
2381
-// const argText = cleanTextValue(argValue);
2382
-
2383
-// argVal.textContent = truncateText(argText, 300);
2384
-
2385
-// argRow.appendChild(argLabel);
2386
-// argRow.appendChild(argVal);
2387
-// argsDiv.appendChild(argRow);
2388
-// }
2389
-
2390
-// kvpsDiv.appendChild(argsDiv);
2391
-// continue;
2392
-// }
2393
-
2394
-// const kvpDiv = document.createElement("div");
2395
-// kvpDiv.classList.add("step-kvp");
2396
-
2397
-// const keySpan = document.createElement("span");
2398
-// keySpan.classList.add("step-kvp-key");
2399
-
2400
-// const iconName = extractIconFromKey(key);
2401
-// if (iconName) {
2402
-// keySpan.innerHTML = `<span class="material-symbols-outlined">${iconName}</span>`;
2403
-// } else {
2404
-// keySpan.textContent = convertToTitleCase(key) + ":";
2405
-// }
2406
-
2407
-// const valueSpan = document.createElement("div");
2408
-// valueSpan.classList.add("step-kvp-value");
2409
-
2410
-// if (typeof value === "string" && value.startsWith("img://")) {
2411
-// const imgElement = document.createElement("img");
2412
-// imgElement.classList.add("screenshot-img");
2413
-// imgElement.src = value.replace("img://", "/image_get?path=");
2414
-// imgElement.alt = "Image Attachment";
2415
-// imgElement.style.cursor = "pointer";
2416
-// imgElement.style.maxWidth = "100%";
2417
-// imgElement.style.display = "block";
2418
-// imgElement.style.marginTop = "4px";
2419
-
2420
-// // Add click handler and cursor change
2421
-// imgElement.addEventListener("click", () => {
2422
-// imageViewerStore.open(imgElement.src, { name: "Image Attachment" });
2423
-// });
2424
-
2425
-// valueSpan.appendChild(imgElement);
2426
-// } else {
2427
-// const valueText = cleanTextValue(value);
2428
-// valueSpan.textContent = truncateText(valueText, 1000);
2429
-// }
2430
-
2431
-// kvpDiv.appendChild(keySpan);
2432
-// kvpDiv.appendChild(valueSpan);
2433
-// kvpsDiv.appendChild(kvpDiv);
2434
-// }
2435
-
2436
-// container.appendChild(kvpsDiv);
2437
-// }
2438
-
2439
-// // Add main content if present (JSON content)
2440
-// if (content && content.trim()) {
2441
-// const pre = document.createElement("pre");
2442
-// pre.classList.add("msg-json");
2443
-// pre.textContent = truncateText(content, 1000);
2444
-// container.appendChild(pre);
2445
-// }
2446
-// }
2447
-
2448
-/**
2449
- * Helper to render thoughts/reasoning with lightbulb icon
2450
- */
2451
-// function renderThoughts(container, value) {
2452
-// const thoughtsDiv = document.createElement("div");
2453
-// thoughtsDiv.classList.add("step-thoughts", "msg-thoughts");
2454
-
2455
-// const thoughtText = cleanTextValue(value);
2456
-
2457
-// if (thoughtText) {
2458
-// thoughtsDiv.innerHTML = `<span class="thought-icon material-symbols-outlined">lightbulb</span><span class="thought-text">${escapeHTML(thoughtText)}</span>`;
2459
-// container.appendChild(thoughtsDiv);
2460
-// }
2461
-// }
1951
1952
/**
1953
* Update process group header with step count, status, and metrics
@@ -2469,8 +1958,7 @@ function updateProcessGroupHeader(group) {
1958
const titleEl = header.querySelector(".group-title");
1959
const badgeEl = header.querySelector(".step-badge");
1960
const metricsEl = header.querySelector(".group-metrics");
2472
- const response = group.querySelector(".process-group-response");
2473
- const isCompleted = !!response;
1961
+ const isCompleted = isProcessGroupComplete(group);
1962
const notificationsEl = metricsEl?.querySelector(".metric-notifications");
1963
1964
// Update group title with the latest agent step heading
@@ -2494,7 +1982,12 @@ function updateProcessGroupHeader(group) {
1982
1983
// If completed, set badge to END
1984
if (isCompleted) {
1985
+ // set end badge
1986
badgeEl.outerHTML = `<span class="step-badge END">END</span>`;
1987
+ // remove shine from any steps
1988
+ group.querySelectorAll(".step-title.shiny-text").forEach((el) => {
1989
+ el.classList.remove("shiny-text");
1990
+ });
1991
} else {
1992
// if not complete, clone the last step badge
1993
if (badgeEl && steps.length > 0) {
@@ -2502,6 +1995,7 @@ function updateProcessGroupHeader(group) {
1995
const code = lastStep.getAttribute("data-step-code");
1996
badgeEl.outerHTML = `<span class="step-badge ${code}">${code}</span>`;
1997
}
1998
+
1999
}
2000
2001
// Update step count in metrics - All GEN steps from all agents per process group
@@ -2573,42 +2067,12 @@ function updateProcessGroupHeader(group) {
2067
}
2068
}
2069
2576
- if (steps.length > 0) {
2577
- // Get the last step's type for status
2578
- // const lastStep = steps[steps.length - 1];
2579
- // const lastType = lastStep.getAttribute("data-type");
2580
- // const lastToolName = lastStep.getAttribute("data-tool-name");
2581
- // const lastTitle = lastStep.querySelector(".step-title")?.textContent || "";
2582
- // Update status badge
2583
- // if (statusEl) {
2584
- // const statusCode = getStatusCode(lastType, lastToolName);
2585
- // const statusColorClass = getStatusClass(lastType, lastToolName);
2586
- // statusEl.textContent = statusCode;
2587
- // statusEl.className = `step-badge ${statusColorClass} group-status`;
2588
- // }
2589
- // Update title
2590
- // if (titleEl) {
2591
- // // Prefer agent type steps for the group title as they contain thinking/planning info
2592
- // if (lastType === "agent" && lastTitle) {
2593
- // titleEl.textContent = cleanStepTitle(lastTitle, 50);
2594
- // } else {
2595
- // // Try to find the most recent agent step for a better title
2596
- // const agentSteps = group.querySelectorAll(
2597
- // '.process-step[data-type="agent"]',
2598
- // );
2599
- // if (agentSteps.length > 0) {
2600
- // const lastAgentStep = agentSteps[agentSteps.length - 1];
2601
- // const agentTitle =
2602
- // lastAgentStep.querySelector(".step-title")?.textContent || "";
2603
- // if (agentTitle) {
2604
- // titleEl.textContent = cleanStepTitle(agentTitle, 50);
2605
- // return;
2606
- // }
2607
- // }
2608
- // titleEl.textContent = cleanStepTitle(lastTitle, 50) || `Processing...`;
2609
- // }
2610
- // }
2611
- }
2070
+
2071
+}
2072
+
2073
+function isProcessGroupComplete(group) {
2074
+ const response = group.querySelector(".process-group-response");
2075
+ return !!response;
2076
}
2077
2078
/**