step detail modal for all events

3clyp50 committed Jan 20, 2026 at 15:48 UTC cc276a0f5b67e8f9ff908444e603811e3859b37f
4 files changed +864
webui/components/messages/process-group/process-group.css
+9
@@ -893,3 +893,12 @@
893 .light-mode .process-step-detail-content .screenshot-img:hover {
894 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
895 }
896 +
897 +/* View Details button in step detail */
898 +.step-detail-actions {
899 + display: flex;
900 + padding-left: 18px;
901 + margin-bottom: 1rem;
902 +}
903 +
904 +
webui/components/modals/process-step-detail/process-step-detail.html new
+603
@@ -0,0 +1,603 @@
1 +<html>
2 +
3 +<head>
4 + <title>Step Details</title>
5 + <script type="module">
6 + import { store } from "/components/modals/process-step-detail/step-detail-store.js";
7 + </script>
8 +</head>
9 +
10 +<body>
11 + <div x-data="{ showRaw: false }"
12 + x-effect="if (showRaw) { $nextTick(() => $store.stepDetail.initRawEditor()) } else { $store.stepDetail.destroyRawEditor() }">
13 + <template x-if="$store.stepDetail && $store.stepDetail.selectedStepForDetail">
14 + <div class="step-detail-container">
15 + <!-- Compact Modal Header -->
16 + <div class="modal-header modal-header-compact">
17 + <div class="header-info">
18 + <span class="status-badge"
19 + :class="$store.processGroup.getStatusColorClass($store.stepDetail.selectedStepForDetail?.type, $store.stepDetail.selectedStepForDetail?.toolName || $store.stepDetail.selectedStepForDetail?.kvps?.tool_name)"
20 + x-text="$store.processGroup.getStepCode($store.stepDetail.selectedStepForDetail?.type, $store.stepDetail.selectedStepForDetail?.toolName || $store.stepDetail.selectedStepForDetail?.kvps?.tool_name)"></span>
21 + <span class="step-type-label" x-text="$store.stepDetail.formatStepType($store.stepDetail.selectedStepForDetail?.type)"></span>
22 + <template x-if="$store.stepDetail.selectedStepForDetail?.toolName || $store.stepDetail.selectedStepForDetail?.kvps?.tool_name">
23 + <span class="tool-name-badge" x-text="$store.stepDetail.selectedStepForDetail?.toolName || $store.stepDetail.selectedStepForDetail?.kvps?.tool_name"></span>
24 + </template>
25 + <template x-if="$store.stepDetail.selectedStepForDetail?.timestamp">
26 + <span class="timestamp-text" x-text="$store.stepDetail.formatTimestamp($store.stepDetail.selectedStepForDetail?.timestamp)"></span>
27 + </template>
28 + <template x-if="$store.stepDetail.selectedStepForDetail?.durationMs">
29 + <span class="duration-badge" x-text="$store.stepDetail.formatDuration($store.stepDetail.selectedStepForDetail?.durationMs)"></span>
30 + </template>
31 + </div>
32 + <div class="header-actions">
33 + <button class="btn btn-action-header copy-all"
34 + @click="$store.stepDetail.copyToClipboard($store.stepDetail.formatStepForCopy($store.stepDetail.selectedStepForDetail))"
35 + title="Copy Complete Step with Metadata">
36 + <span class="material-symbols-outlined">copy_all</span> All
37 + </button>
38 + <button class="btn btn-action-header copy-content"
39 + @click="$store.stepDetail.copyToClipboard($store.stepDetail.getStepPrimaryContent($store.stepDetail.selectedStepForDetail))"
40 + title="Copy Primary Content Only">
41 + <span class="material-symbols-outlined">content_copy</span> Content
42 + </button>
43 + <button class="btn btn-action-header toggle-raw"
44 + @click="showRaw = !showRaw"
45 + :class="{ 'active': showRaw }"
46 + title="Toggle Raw JSON View">
47 + <span class="material-symbols-outlined">data_object</span>
48 + </button>
49 + </div>
50 + </div>
51 +
52 + <!-- Modal Body -->
53 + <div class="modal-body">
54 + <!-- Raw JSON View with ACE Editor -->
55 + <div class="raw-json-section" x-show="showRaw">
56 + <div id="step-detail-raw-editor"></div>
57 + </div>
58 +
59 + <!-- Formatted View -->
60 + <div class="formatted-content" x-show="!showRaw">
61 + <!-- Heading/Title Section -->
62 + <template x-if="$store.stepDetail.selectedStepForDetail?.heading">
63 + <div class="content-block">
64 + <h4>Heading</h4>
65 + <div class="content-text" x-text="$store.stepDetail.cleanHeading($store.stepDetail.selectedStepForDetail?.heading)"></div>
66 + </div>
67 + </template>
68 +
69 + <!-- GEN (agent) Type: Thoughts and Reasoning -->
70 + <template x-if="$store.stepDetail.selectedStepForDetail?.type === 'agent'">
71 + <div class="type-specific-content">
72 + <!-- Reasoning (native model thinking) -->
73 + <template x-if="$store.stepDetail.selectedStepForDetail?.kvps?.reasoning">
74 + <div class="content-block reasoning-block">
75 + <h4><span class="material-symbols-outlined">psychology</span> Reasoning</h4>
76 + <div class="content-text reasoning-content" x-text="$store.stepDetail.cleanTextValue($store.stepDetail.selectedStepForDetail?.kvps?.reasoning)"></div>
77 + </div>
78 + </template>
79 + <!-- Thoughts -->
80 + <template x-if="$store.stepDetail.selectedStepForDetail?.kvps?.thoughts">
81 + <div class="content-block thoughts-block">
82 + <h4><span class="material-symbols-outlined">lightbulb</span> Thoughts</h4>
83 + <div class="content-text thoughts-content" x-text="$store.stepDetail.cleanTextValue($store.stepDetail.selectedStepForDetail?.kvps?.thoughts)"></div>
84 + </div>
85 + </template>
86 + <!-- Tool Call Preview -->
87 + <template x-if="$store.stepDetail.selectedStepForDetail?.kvps?.tool_name || $store.stepDetail.selectedStepForDetail?.kvps?.tool_args">
88 + <div class="content-block tool-call-block">
89 + <h4>
90 + <span class="material-symbols-outlined">build</span>
91 + <span>Tool Call</span>
92 + <template x-if="$store.stepDetail.selectedStepForDetail?.toolName || $store.stepDetail.selectedStepForDetail?.kvps?.tool_name">
93 + <span class="tool-name-badge" x-text="$store.stepDetail.selectedStepForDetail?.toolName || $store.stepDetail.selectedStepForDetail?.kvps?.tool_name"></span>
94 + </template>
95 + </h4>
96 + <template x-if="$store.stepDetail.selectedStepForDetail?.kvps?.tool_args">
97 + <div class="tool-args-container">
98 + <template x-for="[key, value] in Object.entries($store.stepDetail.selectedStepForDetail?.kvps?.tool_args || {})" :key="key">
99 + <div class="tool-arg-row">
100 + <span class="arg-key" x-text="key"></span>
101 + <span class="arg-value" x-text="$store.stepDetail.formatValue(value)"></span>
102 + </div>
103 + </template>
104 + </div>
105 + </template>
106 + </div>
107 + </template>
108 + </div>
109 + </template>
110 +
111 + <!-- EXE (code_exe) Type: Terminal Output -->
112 + <template x-if="$store.stepDetail.selectedStepForDetail?.type === 'code_exe'">
113 + <div class="type-specific-content">
114 + <!-- Command Info -->
115 + <template x-if="$store.stepDetail.selectedStepForDetail?.kvps?.code">
116 + <div class="content-block command-block">
117 + <h4><span class="material-symbols-outlined">terminal</span> Command</h4>
118 + <div class="command-info">
119 + <template x-if="$store.stepDetail.selectedStepForDetail?.kvps?.runtime">
120 + <span class="runtime-badge" x-text="$store.stepDetail.selectedStepForDetail?.kvps?.runtime"></span>
121 + </template>
122 + <template x-if="$store.stepDetail.selectedStepForDetail?.kvps?.session != null">
123 + <span class="session-badge" x-text="'Session ' + $store.stepDetail.selectedStepForDetail?.kvps?.session"></span>
124 + </template>
125 + </div>
126 + <pre class="command-text" x-text="$store.stepDetail.selectedStepForDetail?.kvps?.code"></pre>
127 + </div>
128 + </template>
129 + <!-- Terminal Output -->
130 + <template x-if="$store.stepDetail.selectedStepForDetail?.content">
131 + <div class="content-block terminal-block">
132 + <h4><span class="material-symbols-outlined">output</span> Output</h4>
133 + <pre class="terminal-output terminal-output-full" x-text="$store.stepDetail.selectedStepForDetail?.content"></pre>
134 + </div>
135 + </template>
136 + </div>
137 + </template>
138 +
139 + <!-- Tool/MCP Type: Arguments and Result -->
140 + <template x-if="$store.stepDetail.selectedStepForDetail?.type === 'tool' || $store.stepDetail.selectedStepForDetail?.type === 'mcp'">
141 + <div class="type-specific-content">
142 + <!-- Tool Arguments -->
143 + <template x-if="$store.stepDetail.selectedStepForDetail?.kvps?.tool_args">
144 + <div class="content-block">
145 + <h4><span class="material-symbols-outlined">build</span> Arguments</h4>
146 + <div class="tool-args-grid">
147 + <template x-for="[key, value] in Object.entries($store.stepDetail.selectedStepForDetail?.kvps?.tool_args || {})" :key="key">
148 + <div class="tool-arg-item result-content">
149 + <span class="arg-key" x-text="key"></span>
150 + <span class="arg-value" x-text="$store.stepDetail.formatValue(value)"></span>
151 + </div>
152 + </template>
153 + </div>
154 + </div>
155 + </template>
156 + <!-- Result -->
157 + <template x-if="$store.stepDetail.selectedStepForDetail?.kvps?.result || $store.stepDetail.selectedStepForDetail?.content">
158 + <div class="content-block">
159 + <h4><span class="material-symbols-outlined">output</span> Result</h4>
160 + <pre class="result-content" x-text="$store.stepDetail.selectedStepForDetail?.kvps?.result || $store.stepDetail.selectedStepForDetail?.content"></pre>
161 + </div>
162 + </template>
163 + </div>
164 + </template>
165 +
166 + <!-- Browser Type: Screenshot -->
167 + <template x-if="$store.stepDetail.selectedStepForDetail?.type === 'browser'">
168 + <div class="type-specific-content">
169 + <template x-if="$store.stepDetail.selectedStepForDetail?.kvps?.screenshot">
170 + <div class="content-block">
171 + <h4><span class="material-symbols-outlined">screenshot_monitor</span> Screenshot</h4>
172 + <img class="screenshot-full"
173 + :src="$store.stepDetail.selectedStepForDetail?.kvps?.screenshot?.replace('img://', '/image_get?path=')"
174 + alt="Browser Screenshot" />
175 + </div>
176 + </template>
177 + </div>
178 + </template>
179 +
180 + <!-- Generic KVPs for other types -->
181 + <template x-if="!['agent', 'code_exe', 'tool', 'mcp', 'browser'].includes($store.stepDetail.selectedStepForDetail?.type)">
182 + <div class="type-specific-content">
183 + <template x-if="$store.stepDetail.selectedStepForDetail?.kvps">
184 + <div class="content-block">
185 + <h4>Details</h4>
186 + <div class="kvps-display">
187 + <template x-for="[key, value] in Object.entries($store.stepDetail.selectedStepForDetail?.kvps || {}).filter(([k]) => k !== 'reasoning')" :key="key">
188 + <div class="kvp-item">
189 + <span class="kvp-key" x-text="$store.stepDetail.formatKey(key)"></span>
190 + <span class="kvp-value" x-text="$store.stepDetail.formatValue(value)"></span>
191 + </div>
192 + </template>
193 + </div>
194 + </div>
195 + </template>
196 + <!-- Content -->
197 + <template x-if="$store.stepDetail.selectedStepForDetail?.content">
198 + <div class="content-block">
199 + <h4>Content</h4>
200 + <pre class="tool-arg-item" x-text="$store.stepDetail.selectedStepForDetail?.content"></pre>
201 + </div>
202 + </template>
203 + </div>
204 + </template>
205 + </div>
206 + </div>
207 + </div>
208 + </template>
209 + </div>
210 +
211 + <style>
212 + .step-detail-container {
213 + display: flex;
214 + flex-direction: column;
215 + height: 100%;
216 + max-height: 80vh;
217 + }
218 +
219 + .modal-header-compact {
220 + background: linear-gradient(135deg, var(--color-panel) 0%, var(--color-background) 100%);
221 + position: sticky;
222 + padding: 0.75rem 1rem;
223 + border-radius: 6px 6px 0 0;
224 + top: 0;
225 + z-index: 100;
226 + display: flex;
227 + justify-content: space-between;
228 + align-items: center;
229 + flex-wrap: wrap;
230 + gap: 0.5rem;
231 + border-bottom: 1px solid var(--color-border);
232 + }
233 +
234 + .header-info {
235 + display: flex;
236 + align-items: center;
237 + gap: 0.5rem;
238 + flex-wrap: wrap;
239 + }
240 +
241 + .status-badge {
242 + padding: 0.25rem 0.5rem;
243 + border-radius: 8px;
244 + font-size: 0.75rem;
245 + font-weight: 600;
246 + font-family: var(--font-mono);
247 + }
248 +
249 + .step-type-label {
250 + font-weight: 500;
251 + color: var(--color-text);
252 + }
253 +
254 + .tool-name-badge {
255 + background: var(--color-message-bg);
256 + color: var(--color-text);
257 + padding: 0.2rem 0.5rem;
258 + border-radius: 8px;
259 + font-size: 0.75rem;
260 + font-family: var(--font-mono);
261 + border: 1px solid var(--color-border);
262 + }
263 +
264 + .timestamp-text,
265 + .duration-badge {
266 + color: var(--color-text);
267 + opacity: 0.7;
268 + padding: 0.2rem 0.5rem;
269 + border-radius: var(--border-radius);
270 + font-size: 0.75rem;
271 + border: 1px solid var(--color-border);
272 + }
273 +
274 + .header-actions {
275 + display: flex;
276 + gap: 0.5rem;
277 + }
278 +
279 + .btn-action-header {
280 + padding: 0.4rem 0.6rem;
281 + display: flex;
282 + align-items: center;
283 + gap: 0.25rem;
284 + background: var(--color-message-bg);
285 + color: var(--color-text);
286 + border: 1px solid var(--color-border);
287 + border-radius: 4px;
288 + cursor: pointer;
289 + font-size: 0.8rem;
290 + transition: all 0.15s ease;
291 + }
292 +
293 + .btn-action-header .material-symbols-outlined {
294 + font-size: 18px;
295 + }
296 +
297 + .btn-action-header:hover {
298 + border-color: var(--color-primary);
299 + color: var(--color-primary);
300 + }
301 +
302 + .btn-action-header.active {
303 + background: var(--color-primary);
304 + color: white;
305 + border-color: var(--color-primary);
306 + }
307 +
308 + .modal-body {
309 + flex: 1;
310 + overflow-y: auto;
311 + padding: 1rem;
312 + }
313 +
314 + .raw-json-section {
315 + height: 100%;
316 + display: flex;
317 + flex-direction: column;
318 + }
319 +
320 + #step-detail-raw-editor {
321 + width: 100%;
322 + height: 60vh;
323 + border-radius: 6px;
324 + overflow: hidden;
325 + border: 1px solid var(--color-border);
326 + }
327 +
328 + #step-detail-raw-editor .ace_scrollbar-v {
329 + overflow-y: auto;
330 + }
331 +
332 + #step-detail-raw-editor::-webkit-scrollbar {
333 + width: 0;
334 + }
335 +
336 + .formatted-content {
337 + display: flex;
338 + flex-direction: column;
339 + gap: 1rem;
340 + }
341 +
342 + .content-block {
343 + background: var(--color-panel);
344 + border: 1px solid var(--color-border);
345 + border-radius: 6px;
346 + padding: 1rem;
347 + }
348 +
349 + .content-block h4 {
350 + margin: 0 0 0.75rem 0;
351 + color: var(--color-text);
352 + font-size: 0.9rem;
353 + font-weight: 600;
354 + display: flex;
355 + align-items: center;
356 + gap: 0.5rem;
357 + opacity: 0.8;
358 + }
359 +
360 + .content-block h4 .material-symbols-outlined {
361 + font-size: 18px;
362 + opacity: 0.7;
363 + }
364 +
365 + .content-block:last-child {
366 + margin-top: 1rem;
367 + }
368 +
369 + .content-text {
370 + color: var(--color-text);
371 + line-height: 1.6;
372 + white-space: pre-wrap;
373 + word-break: break-word;
374 + }
375 +
376 + .thoughts-block {
377 + margin-bottom: 1rem;
378 + }
379 +
380 + .thoughts-content {
381 + opacity: 0.8;
382 + }
383 +
384 + .reasoning-block {
385 + margin-bottom: 1rem;
386 + }
387 +
388 + .reasoning-content {
389 + opacity: 0.75;
390 + font-style: italic;
391 + }
392 +
393 + .tool-call-block {
394 + margin-bottom: 1rem;
395 + }
396 +
397 + .tool-name-inline {
398 + background: var(--color-primary);
399 + color: white;
400 + padding: 0.15rem 0.5rem;
401 + border-radius: 4px;
402 + font-size: 0.8rem;
403 + font-family: var(--font-mono);
404 + font-weight: 500;
405 + margin-left: 0.5rem;
406 + }
407 +
408 + .command-block {
409 + margin-bottom: 1rem;
410 + }
411 +
412 + .command-info {
413 + display: flex;
414 + gap: 0.5rem;
415 + margin-bottom: 0.5rem;
416 + }
417 +
418 + .runtime-badge,
419 + .session-badge {
420 + background: var(--color-message-bg);
421 + padding: 0.2rem 0.5rem;
422 + border-radius: 8px;
423 + font-size: 0.75rem;
424 + font-family: var(--font-mono);
425 + border: 1px solid var(--color-border);
426 + }
427 +
428 + .command-text {
429 + background: var(--color-background);
430 + padding: 0.75rem;
431 + border-radius: 8px;
432 + font-size: 0.85rem;
433 + margin: 0;
434 + overflow-x: auto;
435 + }
436 +
437 + .terminal-output-full {
438 + background: rgba(0, 0, 0, 0.8);
439 + color: var(--color-text);
440 + padding: 1rem;
441 + border-radius: 8px;
442 + font-size: 0.85rem;
443 + margin: 0;
444 + max-height: 40vh;
445 + overflow: auto;
446 + white-space: pre-wrap;
447 + word-break: break-word;
448 + }
449 +
450 + .light-mode .terminal-output-full {
451 + background: rgba(0, 0, 0, 0.05);
452 + scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
453 + }
454 +
455 + .tool-args-grid {
456 + display: flex;
457 + flex-direction: column;
458 + gap: 0.5rem;
459 + }
460 +
461 + .tool-arg-item {
462 + display: flex;
463 + gap: 0.75rem;
464 + padding: 0.5rem;
465 + background: var(--color-background);
466 + border-radius: 8px;
467 + border: 1px solid var(--color-border);
468 + }
469 +
470 + /* GEN step tool call - single container with KV rows */
471 + .tool-args-container {
472 + background: var(--color-background);
473 + border-radius: 8px;
474 + border: 1px solid var(--color-border);
475 + padding: 0.5rem;
476 + display: grid;
477 + grid-template-columns: auto 1fr;
478 + gap: 0;
479 + }
480 +
481 + .tool-arg-row {
482 + display: contents;
483 + }
484 +
485 + .tool-arg-row .arg-key,
486 + .tool-arg-row .arg-value {
487 + padding: 0.5rem;
488 + }
489 +
490 + .arg-key {
491 + font-weight: 600;
492 + color: var(--color-primary);
493 + font-size: 0.85rem;
494 + flex-shrink: 0;
495 + }
496 +
497 + .arg-value {
498 + color: var(--color-text);
499 + font-family: var(--font-mono);
500 + font-size: 0.85rem;
501 + white-space: pre-wrap;
502 + word-break: break-word;
503 + flex: 1;
504 + }
505 +
506 + .result-content {
507 + background: var(--color-background);
508 + padding: 1rem;
509 + border-radius: 8px;
510 + font-family: var(--font-mono);
511 + font-size: 0.85rem;
512 + margin: 0;
513 + max-height: 40vh;
514 + overflow: auto;
515 + white-space: pre-wrap;
516 + word-break: break-word;
517 + }
518 +
519 + .screenshot-full {
520 + max-width: 100%;
521 + border-radius: 8px;
522 + border: 1px solid var(--color-border);
523 + cursor: pointer;
524 + }
525 +
526 + .kvps-display {
527 + display: flex;
528 + flex-direction: column;
529 + gap: 0.5rem;
530 + }
531 +
532 + .kvp-item {
533 + display: flex;
534 + flex-direction: column;
535 + gap: 0.25rem;
536 + padding: 0.5rem;
537 + background: var(--color-background);
538 + border-radius: 8px;
539 + border: 1px solid var(--color-border);
540 + }
541 +
542 + .kvp-key {
543 + font-weight: 600;
544 + color: var(--color-text);
545 + opacity: 0.7;
546 + font-size: 0.8rem;
547 + text-transform: uppercase;
548 + }
549 +
550 + .kvp-value {
551 + color: var(--color-text);
552 + font-size: 0.9rem;
553 + white-space: pre-wrap;
554 + word-break: break-word;
555 + }
556 +
557 + /* Scrollbar styling */
558 + .modal-body::-webkit-scrollbar,
559 + .result-content::-webkit-scrollbar {
560 + width: 6px;
561 + height: 6px;
562 + }
563 +
564 + .modal-body::-webkit-scrollbar-track,
565 + .result-content::-webkit-scrollbar-track {
566 + background: transparent;
567 + }
568 +
569 + .modal-body::-webkit-scrollbar-thumb,
570 + .result-content::-webkit-scrollbar-thumb {
571 + background-color: rgba(155, 155, 155, 0.5);
572 + border-radius: 6px;
573 + }
574 +
575 + .modal-body::-webkit-scrollbar-thumb:hover,
576 + .result-content::-webkit-scrollbar-thumb:hover {
577 + background-color: rgba(155, 155, 155, 0.7);
578 + }
579 +
580 + /* Responsive */
581 + @media (max-width: 600px) {
582 + .modal-header-compact {
583 + flex-direction: column;
584 + align-items: flex-start;
585 + }
586 +
587 + .header-actions {
588 + width: 100%;
589 + justify-content: flex-end;
590 + }
591 +
592 + .tool-arg-item {
593 + flex-direction: column;
594 + }
595 +
596 + .arg-key {
597 + min-width: unset;
598 + }
599 + }
600 + </style>
601 +</body>
602 +
603 +</html>
webui/components/modals/process-step-detail/step-detail-store.js new
+197
@@ -0,0 +1,197 @@
1 +import { createStore } from "/js/AlpineStore.js";
2 +
3 +// Step Detail Store - manages the step detail modal
4 +
5 +const model = {
6 + // Selected step for detail modal view
7 + selectedStepForDetail: null,
8 +
9 + // ACE editor instance for raw JSON view
10 + _rawEditor: null,
11 +
12 + // Show step detail modal
13 + showStepDetail(stepData) {
14 + if (!stepData) return;
15 + this.selectedStepForDetail = stepData;
16 + window.openModal("modals/process-step-detail/process-step-detail.html");
17 + },
18 +
19 + // Close step detail modal
20 + closeStepDetail() {
21 + this.selectedStepForDetail = null;
22 + window.closeModal();
23 + },
24 +
25 + // Copy text to clipboard
26 + async copyToClipboard(text) {
27 + if (!text) return false;
28 + try {
29 + await navigator.clipboard.writeText(text);
30 + return true;
31 + } catch (err) {
32 + console.error("Failed to copy to clipboard:", err);
33 + return false;
34 + }
35 + },
36 +
37 + // Format step data for full copy (all metadata + content)
38 + formatStepForCopy(step) {
39 + if (!step) return "";
40 + const lines = [];
41 + lines.push(`Type: ${step.type || "unknown"}`);
42 + if (step.heading) lines.push(`Heading: ${step.heading}`);
43 + if (step.timestamp) {
44 + const date = new Date(parseFloat(step.timestamp) * 1000);
45 + lines.push(`Timestamp: ${date.toISOString()}`);
46 + }
47 + if (step.durationMs) lines.push(`Duration: ${step.durationMs}ms`);
48 + if (step.kvps) {
49 + lines.push("");
50 + lines.push("--- Data ---");
51 + for (const [key, value] of Object.entries(step.kvps)) {
52 + if (key === "reasoning") continue;
53 + const formattedValue = typeof value === "object" ? JSON.stringify(value, null, 2) : String(value);
54 + lines.push(`${key}: ${formattedValue}`);
55 + }
56 + }
57 + if (step.content) {
58 + lines.push("");
59 + lines.push("--- Content ---");
60 + lines.push(step.content);
61 + }
62 + return lines.join("\n");
63 + },
64 +
65 + // Get primary content for a step (type-aware)
66 + getStepPrimaryContent(step) {
67 + if (!step) return "";
68 + if (step.type === "code_exe") {
69 + return step.content || "";
70 + }
71 + if (step.type === "agent" && step.kvps) {
72 + return step.kvps.thoughts || step.kvps.headline || step.content || "";
73 + }
74 + if ((step.type === "tool" || step.type === "mcp") && step.kvps) {
75 + return step.kvps.result || step.content || "";
76 + }
77 + return step.content || "";
78 + },
79 +
80 + // Initialize ACE editor for raw JSON view
81 + initRawEditor() {
82 + const container = document.getElementById("step-detail-raw-editor");
83 + if (!container) return;
84 +
85 + this.destroyRawEditor();
86 +
87 + if (!window.ace?.edit) {
88 + console.warn("ACE editor not available");
89 + return;
90 + }
91 +
92 + const stepData = this.selectedStepForDetail;
93 + if (!stepData) return;
94 +
95 + const editorInstance = window.ace.edit("step-detail-raw-editor");
96 + if (!editorInstance) return;
97 +
98 + this._rawEditor = editorInstance;
99 +
100 + const darkMode = window.localStorage?.getItem("darkMode");
101 + const theme = darkMode !== "false" ? "ace/theme/github_dark" : "ace/theme/tomorrow";
102 +
103 + this._rawEditor.setTheme(theme);
104 + this._rawEditor.session.setMode("ace/mode/json");
105 + this._rawEditor.setValue(JSON.stringify(stepData, null, 2), -1);
106 + this._rawEditor.setReadOnly(true);
107 + this._rawEditor.clearSelection();
108 + this._rawEditor.setOptions({
109 + showPrintMargin: false,
110 + highlightActiveLine: false,
111 + highlightGutterLine: false
112 + });
113 + },
114 +
115 + // Destroy ACE editor instance
116 + destroyRawEditor() {
117 + if (this._rawEditor?.destroy) {
118 + this._rawEditor.destroy();
119 + this._rawEditor = null;
120 + }
121 + },
122 +
123 + // Format step type for display
124 + formatStepType(type) {
125 + const typeMap = {
126 + 'agent': 'Generation',
127 + 'code_exe': 'Code Execution',
128 + 'tool': 'Tool Call',
129 + 'mcp': 'MCP Tool',
130 + 'browser': 'Browser',
131 + 'response': 'Response',
132 + 'info': 'Info',
133 + 'hint': 'Hint',
134 + 'warning': 'Warning',
135 + 'error': 'Error',
136 + 'util': 'Utility',
137 + 'progress': 'Progress'
138 + };
139 + return typeMap[type] || (type ? type.charAt(0).toUpperCase() + type.slice(1) : 'Unknown');
140 + },
141 +
142 + // Format timestamp for display
143 + formatTimestamp(timestamp) {
144 + if (!timestamp) return '';
145 + const date = new Date(parseFloat(timestamp) * 1000);
146 + const hours = String(date.getHours()).padStart(2, '0');
147 + const minutes = String(date.getMinutes()).padStart(2, '0');
148 + const seconds = String(date.getSeconds()).padStart(2, '0');
149 + return `${hours}:${minutes}:${seconds}`;
150 + },
151 +
152 + // Format duration for display
153 + formatDuration(ms) {
154 + if (!ms) return '';
155 + if (ms < 1000) return `${ms}ms`;
156 + const seconds = Math.floor(ms / 1000);
157 + if (seconds < 60) return `${seconds}s`;
158 + const minutes = Math.floor(seconds / 60);
159 + const remainingSeconds = seconds % 60;
160 + return `${minutes}m ${remainingSeconds}s`;
161 + },
162 +
163 + // Format value for display (handles objects)
164 + formatValue(value) {
165 + if (value === null || value === undefined) return '';
166 + if (typeof value === 'object') return JSON.stringify(value, null, 2);
167 + return String(value);
168 + },
169 +
170 + // Format key for display (title case)
171 + formatKey(key) {
172 + return key.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
173 + },
174 +
175 + // Clean text value (handle arrays, remove brackets)
176 + cleanTextValue(value) {
177 + if (Array.isArray(value)) {
178 + return value
179 + .filter(item => item && String(item).trim() && !/^[\[\]]$/.test(String(item).trim()))
180 + .join('\n');
181 + }
182 + if (typeof value === 'object' && value !== null) {
183 + return JSON.stringify(value, null, 2);
184 + }
185 + return String(value).replace(/^\s*[\[\]]\s*$/gm, '').trim();
186 + },
187 +
188 + // Clean heading by removing icon:// prefixes
189 + cleanHeading(text) {
190 + if (!text) return "";
191 + return String(text)
192 + .replace(/icon:\/\/[a-zA-Z0-9_]+\s*/g, "")
193 + .trim();
194 + }
195 +};
196 +
197 +export const store = createStore("stepDetail", model);
webui/js/messages.js
+55
@@ -5,6 +5,7 @@ import { store as _messageResizeStore } from "/components/messages/resize/messag
5 import { store as attachmentsStore } from "/components/chat/attachments/attachmentsStore.js";
6 import { addActionButtonsToElement } from "/components/messages/action-buttons/simple-action-buttons.js";
7 import { store as processGroupStore } from "/components/messages/process-group/process-group-store.js";
8 +import { store as stepDetailStore } from "/components/modals/process-step-detail/step-detail-store.js";
9 import { store as preferencesStore } from "/components/sidebar/bottom/preferences/preferences-store.js";
10 import { formatDuration } from "./time-utils.js";
11
@@ -1353,6 +1354,23 @@ function addProcessStep(group, id, type, heading, content, kvps, timestamp = nul
1354 renderStepDetailContent(detailContent, content, kvps, type);
1355
1356 detail.appendChild(detailContent);
1357 +
1358 + // Store step data on the element for fresh access on modal open
1359 + step._stepData = {
1360 + type,
1361 + heading,
1362 + content,
1363 + kvps,
1364 + timestamp,
1365 + durationMs,
1366 + agentNumber,
1367 + toolName: toolNameToUse
1368 + };
1369 +
1370 + // Add "View Details" button for full modal view (reads fresh data from step._stepData)
1371 + const viewDetailsBtn = createViewDetailsButton(step);
1372 + detail.appendChild(viewDetailsBtn);
1373 +
1374 step.appendChild(detail);
1375
1376 // Track delegation steps for nesting
@@ -1462,6 +1480,19 @@ function updateProcessStep(stepElement, id, type, heading, content, kvps, durati
1480 }
1481 }
1482
1483 + // Update stored step data for fresh access by modal
1484 + const timestamp = stepElement._stepData?.timestamp; // preserve original timestamp
1485 + stepElement._stepData = {
1486 + type,
1487 + heading,
1488 + content,
1489 + kvps,
1490 + timestamp,
1491 + durationMs,
1492 + agentNumber,
1493 + toolName: toolNameToUse
1494 + };
1495 +
1496 // Update parent group header
1497 const group = stepElement.closest(".process-group");
1498 if (group) {
@@ -1788,6 +1819,30 @@ function renderThoughts(container, value) {
1819 }
1820 }
1821
1822 +/**
1823 + * Create "View Details" button for opening step detail modal
1824 + * @param {HTMLElement} stepElement - The step DOM element containing _stepData property
1825 + */
1826 +function createViewDetailsButton(stepElement) {
1827 + const btnContainer = document.createElement("div");
1828 + btnContainer.classList.add("step-detail-actions");
1829 +
1830 + const btn = document.createElement("button");
1831 + btn.classList.add("btn", "text-button");
1832 + btn.innerHTML = '<span class="material-symbols-outlined">open_in_full</span> View Details';
1833 + btn.title = "Open full step details in modal";
1834 +
1835 + btn.addEventListener("click", (e) => {
1836 + e.stopPropagation();
1837 + // Read fresh data from the step element at click time
1838 + const freshData = stepElement._stepData || {};
1839 + stepDetailStore.showStepDetail(freshData);
1840 + });
1841 +
1842 + btnContainer.appendChild(btn);
1843 + return btnContainer;
1844 +}
1845 +
1846 /**
1847 * Update process group header with step count, status, and metrics
1848 */