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
*/