step-title shiny text
3clyp50 committed
Jan 19, 2026 at 15:31 UTC
f0e4e6d1fe195826e449e0c4858587716784ba4c
2 files changed
+55
-61
webui/components/messages/process-group/process-group.css
+2
-44
@@ -226,33 +226,6 @@
226
color: var(--step-accent);
227
}
228
229
-/* Animated spinner for active status (CSS-only, no Material Icons) */
230
-.status-badge.status-active::after {
231
- content: "";
232
- display: inline-block;
233
- width: 8px;
234
- height: 8px;
235
- border: 1.5px solid currentColor;
236
- border-top-color: transparent;
237
- border-radius: 50%;
238
- animation: spin 0.8s linear infinite;
239
- margin-left: 4px;
240
- flex-shrink: 0;
241
-}
242
-
243
-@keyframes spin {
244
- from { transform: rotate(0deg); }
245
- to { transform: rotate(360deg); }
246
-}
247
-
248
-/* Don't show spinner pseudo-element on END/ERR (they have their own indicators) */
249
-.status-badge.status-end::after,
250
-.status-badge.status-err::after {
251
- display: none;
252
-}
253
-
254
-/* END status with checkmark icon (icon added via JS, no ::before needed) */
255
-
229
/* Completed process group styling */
230
.process-group-completed {
231
opacity: 0.95;
@@ -388,16 +361,6 @@
361
min-height: 18px;
362
}
363
391
-/* Step icon removed - using status badges instead */
392
-.process-step-header .step-icon {
393
- display: none;
394
-}
395
-
396
-/* Step type label removed - using status badges instead */
397
-.process-step-header .step-type {
398
- display: none;
399
-}
400
-
364
/* Step title */
365
.process-step-header .step-title {
366
flex: 1;
@@ -787,9 +750,8 @@
750
50% { opacity: 0.8; }
751
}
752
790
-.process-step.loading .step-icon {
791
- animation: pulse-step 1.2s ease-in-out infinite;
792
-}
753
+.step-title.shiny-text { color: transparent !important; -webkit-background-clip: text; background-clip: text; animation: shine 1s linear infinite; }
754
+ @keyframes shine { to { background-position: -100% center; } }
755
756
/* Responsive adjustments */
757
@media (max-width: 768px) {
@@ -797,10 +759,6 @@
759
padding: var(--spacing-xs) var(--spacing-sm);
760
}
761
800
- .process-step-header .step-type {
801
- display: none;
802
- }
803
-
762
.process-step-header .step-title {
763
font-size: 0.7rem;
764
}
webui/js/messages.js
+53
-17
@@ -13,6 +13,27 @@ const chatHistory = document.getElementById("chat-history");
13
let messageGroup = null;
14
let currentProcessGroup = null; // Track current process group for collapsible UI
15
let currentDelegationSteps = {}; // Track delegation steps by agent number for nesting
16
+let activeProcessGroupId = null; // Only one process group should show "running" indicators at a time
17
+let activeProcessGroupEl = null;
18
+let activeStepTitleEl = null;
19
+
20
+/**
21
+ * Mark current process group as active and clear active badges.
22
+ */
23
+function setActiveProcessGroup(group) {
24
+ if (!group || !group.id) return;
25
+ if (activeProcessGroupId === group.id) return;
26
+
27
+ // Clear shiny effect from the previous active step title if we moved to a new group
28
+ if (activeStepTitleEl && activeProcessGroupEl && activeProcessGroupEl !== group && activeProcessGroupEl.contains(activeStepTitleEl)) {
29
+ activeStepTitleEl.classList.remove("shiny-text");
30
+ activeStepTitleEl = null;
31
+ }
32
+
33
+ activeProcessGroupId = group.id;
34
+ activeProcessGroupEl = group;
35
+
36
+}
37
38
/**
39
* Resolve tool name from kvps, existing attribute, or previous siblings
@@ -82,6 +103,7 @@ export function setMessage(id, type, heading, content, temp, kvps = null, timest
103
if (!currentProcessGroup || !document.getElementById(currentProcessGroup.id)) {
104
currentProcessGroup = createProcessGroup(id);
105
chatHistory.appendChild(currentProcessGroup);
106
+ setActiveProcessGroup(currentProcessGroup);
107
}
108
109
// Add step to current process group
@@ -102,6 +124,7 @@ export function setMessage(id, type, heading, content, temp, kvps = null, timest
124
if (!currentProcessGroup || !document.getElementById(currentProcessGroup.id)) {
125
currentProcessGroup = createProcessGroup(id);
126
chatHistory.appendChild(currentProcessGroup);
127
+ setActiveProcessGroup(currentProcessGroup);
128
}
129
130
// Add subordinate response as a response step (special type to show content)
@@ -1200,7 +1223,7 @@ function createProcessGroup(id) {
1223
header.innerHTML = `
1224
<span class="expand-icon"></span>
1225
<span class="group-title">Processing...</span>
1203
- <span class="status-badge status-gen status-active group-status">GEN</span>
1226
+ <span class="status-badge status-gen group-status">GEN</span>
1227
<span class="group-metrics">
1228
<span class="metric-time" title="Start time"><span class="material-symbols-outlined">schedule</span><span class="metric-value">--:--</span></span>
1229
<span class="metric-steps" title="Steps"><span class="material-symbols-outlined">footprint</span><span class="metric-value">0</span></span>
@@ -1260,6 +1283,9 @@ function getNestedContainer(parentStep) {
1283
* Add a step to a process group
1284
*/
1285
function addProcessStep(group, id, type, heading, content, kvps, timestamp = null, durationMs = null, agentNumber = 0) {
1286
+ // group with newest step becomes the active one
1287
+ setActiveProcessGroup(group);
1288
+
1289
const groupId = group.getAttribute("data-group-id");
1290
let stepsContainer = group.querySelector(".process-steps");
1291
const isGroupCompleted = group.classList.contains("process-group-completed");
@@ -1339,10 +1365,9 @@ function addProcessStep(group, id, type, heading, content, kvps, timestamp = nul
1365
// Add status color class to step for cascading --step-accent to internal icons
1366
step.classList.add(statusColorClass);
1367
1342
- const activeClass = isGroupCompleted ? "" : " status-active";
1368
stepHeader.innerHTML = `
1369
<span class="step-expand-icon"></span>
1345
- <span class="status-badge ${statusColorClass}${activeClass}">${statusCode}</span>
1370
+ <span class="status-badge ${statusColorClass}">${statusCode}</span>
1371
<span class="step-title">${escapeHTML(title)}</span>
1372
`;
1373
@@ -1407,14 +1432,25 @@ function addProcessStep(group, id, type, heading, content, kvps, timestamp = nul
1432
}
1433
}
1434
1410
- // Remove status-active from all previous steps (only the current step is active)
1411
- const prevSteps = stepsContainer.querySelectorAll(".process-step .status-badge.status-active");
1412
- prevSteps.forEach(badge => badge.classList.remove("status-active"));
1435
+ // Remove shiny effect from the previously active step title (O(1))
1436
+ if (activeStepTitleEl) {
1437
+ activeStepTitleEl.classList.remove("shiny-text");
1438
+ activeStepTitleEl = null;
1439
+ }
1440
1441
appendTarget.appendChild(step);
1442
1443
// Update group header
1444
updateProcessGroupHeader(group);
1445
+
1446
+ // Apply shiny effect to the active step title
1447
+ if (!isGroupCompleted && group.id === activeProcessGroupId) {
1448
+ const titleEl = step.querySelector(".process-step-header .step-title");
1449
+ if (titleEl) {
1450
+ titleEl.classList.add("shiny-text");
1451
+ activeStepTitleEl = titleEl;
1452
+ }
1453
+ }
1454
1455
return step;
1456
}
@@ -1810,10 +1846,8 @@ function updateProcessGroupHeader(group) {
1846
const metricsEl = group.querySelector(".group-metrics");
1847
const isCompleted = group.classList.contains("process-group-completed");
1848
1813
- // If completed, only remove active badges and exit early (don't update metrics)
1849
+ // If completed, don't update metrics
1850
if (isCompleted) {
1815
- const activeBadges = group.querySelectorAll(".status-badge.status-active");
1816
- activeBadges.forEach(badge => badge.classList.remove("status-active"));
1851
return;
1852
}
1853
@@ -1881,14 +1915,14 @@ function updateProcessGroupHeader(group) {
1915
const lastToolName = lastStep.getAttribute("data-tool-name");
1916
const lastTitle = lastStep.querySelector(".step-title")?.textContent || "";
1917
1884
- // Update status badge (keep status-active during execution)
1918
+ // Update status badge
1919
if (statusEl) {
1920
// Status code and color class from store (maps backend types)
1921
const statusCode = processGroupStore.getStepCode(lastType, lastToolName);
1922
const statusColorClass = processGroupStore.getStatusColorClass(lastType, lastToolName);
1923
1924
statusEl.textContent = statusCode;
1891
- statusEl.className = `status-badge ${statusColorClass} status-active group-status`;
1925
+ statusEl.className = `status-badge ${statusColorClass} group-status`;
1926
}
1927
1928
// Update title
@@ -1929,17 +1963,13 @@ function truncateText(text, maxLength) {
1963
function markProcessGroupComplete(group, responseTitle) {
1964
if (!group) return;
1965
1932
- // Update status badge to END (remove status-active)
1966
+ // Update status badge to END
1967
const statusEl = group.querySelector(".group-status");
1968
if (statusEl) {
1969
statusEl.innerHTML = '<span class="badge-icon material-symbols-outlined">check</span>END';
1936
- statusEl.className = "status-badge status-end group-status"; // No status-active
1970
+ statusEl.className = "status-badge status-end group-status";
1971
}
1972
1939
- // Remove status-active from all step badges (stop spinners)
1940
- const stepBadges = group.querySelectorAll(".process-step .status-badge.status-active");
1941
- stepBadges.forEach(badge => badge.classList.remove("status-active"));
1942
-
1973
// Update title if response title is available
1974
const titleEl = group.querySelector(".group-title");
1975
if (titleEl && responseTitle) {
@@ -1975,6 +2005,12 @@ export function resetProcessGroups() {
2005
currentProcessGroup = null;
2006
currentDelegationSteps = {};
2007
messageGroup = null;
2008
+ activeProcessGroupId = null;
2009
+ activeProcessGroupEl = null;
2010
+ if (activeStepTitleEl) {
2011
+ activeStepTitleEl.classList.remove("shiny-text");
2012
+ }
2013
+ activeStepTitleEl = null;
2014
}
2015
2016
/**