generic modals with kvps for step detail
3clyp50 committed
Jan 27, 2026 at 12:09 UTC
2463b857490c15a84de2ca105ddfc9210afc74f3
3 files changed
+47
-358
webui/components/modals/process-step-detail/process-step-detail.html
+21
-349
@@ -19,14 +19,10 @@
19
:class="$store.stepDetail.selectedStepForDetail?.statusClass || ''"
20
x-text="$store.stepDetail.selectedStepForDetail?.statusCode || ''"></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>
22
+ <template x-if="$store.stepDetail.selectedStepForDetail?.headerLabels">
23
+ <template x-for="(label, index) in $store.stepDetail.selectedStepForDetail?.headerLabels || []" :key="index">
24
+ <span :class="label.class || 'header-label'" x-text="label.label"></span>
25
+ </template>
26
</template>
27
</div>
28
<div class="header-actions">
@@ -66,147 +62,26 @@
62
</div>
63
</template>
64
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
- <!-- Raw LLM Response (content field) -->
109
- <template x-if="$store.stepDetail.selectedStepForDetail?.content">
110
- <div class="content-block raw-response-block">
111
- <h4><span class="material-symbols-outlined">smart_toy</span> Raw Response</h4>
112
- <pre class="raw-response-content" x-text="$store.stepDetail.selectedStepForDetail?.content"></pre>
113
- </div>
114
- </template>
115
- </div>
116
- </template>
117
-
118
- <!-- EXE (code_exe) Type: Terminal Output -->
119
- <template x-if="$store.stepDetail.selectedStepForDetail?.type === 'code_exe'">
120
- <div class="type-specific-content">
121
- <!-- Command Info -->
122
- <template x-if="$store.stepDetail.selectedStepForDetail?.kvps?.code">
123
- <div class="content-block command-block">
124
- <h4><span class="material-symbols-outlined">terminal</span> Command</h4>
125
- <div class="command-info">
126
- <template x-if="$store.stepDetail.selectedStepForDetail?.kvps?.runtime">
127
- <span class="runtime-badge" x-text="$store.stepDetail.selectedStepForDetail?.kvps?.runtime"></span>
128
- </template>
129
- <template x-if="$store.stepDetail.selectedStepForDetail?.kvps?.session != null">
130
- <span class="session-badge" x-text="'Session ' + $store.stepDetail.selectedStepForDetail?.kvps?.session"></span>
131
- </template>
132
- </div>
133
- <pre class="command-text" x-text="$store.stepDetail.selectedStepForDetail?.kvps?.code"></pre>
134
- </div>
135
- </template>
136
- <!-- Terminal Output -->
137
- <template x-if="$store.stepDetail.selectedStepForDetail?.content">
138
- <div class="content-block terminal-block">
139
- <h4><span class="material-symbols-outlined">output</span> Output</h4>
140
- <pre class="terminal-output terminal-output-full" x-text="$store.stepDetail.selectedStepForDetail?.content"></pre>
141
- </div>
142
- </template>
143
- </div>
144
- </template>
145
-
146
- <!-- Tool/MCP Type: Arguments and Result -->
147
- <template x-if="$store.stepDetail.selectedStepForDetail?.type === 'tool' || $store.stepDetail.selectedStepForDetail?.type === 'mcp'">
148
- <div class="type-specific-content">
149
- <!-- Tool Arguments -->
150
- <template x-if="$store.stepDetail.selectedStepForDetail?.kvps?.tool_args">
151
- <div class="content-block">
152
- <h4><span class="material-symbols-outlined">build</span> Arguments</h4>
153
- <div class="tool-args-grid">
154
- <template x-for="[key, value] in Object.entries($store.stepDetail.selectedStepForDetail?.kvps?.tool_args || {})" :key="key">
155
- <div class="tool-arg-item result-content">
156
- <span class="arg-key" x-text="key"></span>
157
- <span class="arg-value" x-text="$store.stepDetail.formatValue(value)"></span>
158
- </div>
159
- </template>
65
+ <!-- Generic KVPs for all types -->
66
+ <template x-if="$store.stepDetail.selectedStepForDetail?.kvps">
67
+ <div class="content-block">
68
+ <h4>Details</h4>
69
+ <div class="kvps-display">
70
+ <template x-for="[key, value] in Object.entries($store.stepDetail.selectedStepForDetail?.kvps || {})" :key="key">
71
+ <div class="kvp-item">
72
+ <span class="kvp-key" x-text="$store.stepDetail.formatKey(key)"></span>
73
+ <span class="kvp-value" x-text="$store.stepDetail.formatValue(value)"></span>
74
</div>
161
- </div>
162
- </template>
163
- <!-- Result -->
164
- <template x-if="$store.stepDetail.selectedStepForDetail?.kvps?.result || $store.stepDetail.selectedStepForDetail?.content">
165
- <div class="content-block">
166
- <h4><span class="material-symbols-outlined">output</span> Result</h4>
167
- <pre class="result-content" x-text="$store.stepDetail.selectedStepForDetail?.kvps?.result || $store.stepDetail.selectedStepForDetail?.content"></pre>
168
- </div>
169
- </template>
75
+ </template>
76
+ </div>
77
</div>
78
</template>
79
173
- <!-- Browser Type: Screenshot -->
174
- <template x-if="$store.stepDetail.selectedStepForDetail?.type === 'browser'">
175
- <div class="type-specific-content">
176
- <template x-if="$store.stepDetail.selectedStepForDetail?.kvps?.screenshot">
177
- <div class="content-block">
178
- <h4><span class="material-symbols-outlined">screenshot_monitor</span> Screenshot</h4>
179
- <img class="screenshot-full"
180
- :src="$store.stepDetail.selectedStepForDetail?.kvps?.screenshot?.replace('img://', '/image_get?path=')"
181
- alt="Browser Screenshot" />
182
- </div>
183
- </template>
184
- </div>
185
- </template>
186
-
187
- <!-- Generic KVPs for other types -->
188
- <template x-if="!['agent', 'code_exe', 'tool', 'mcp', 'browser'].includes($store.stepDetail.selectedStepForDetail?.type)">
189
- <div class="type-specific-content">
190
- <template x-if="$store.stepDetail.selectedStepForDetail?.kvps">
191
- <div class="content-block">
192
- <h4>Details</h4>
193
- <div class="kvps-display">
194
- <template x-for="[key, value] in Object.entries($store.stepDetail.selectedStepForDetail?.kvps || {}).filter(([k]) => k !== 'reasoning')" :key="key">
195
- <div class="kvp-item">
196
- <span class="kvp-key" x-text="$store.stepDetail.formatKey(key)"></span>
197
- <span class="kvp-value" x-text="$store.stepDetail.formatValue(value)"></span>
198
- </div>
199
- </template>
200
- </div>
201
- </div>
202
- </template>
203
- <!-- Content -->
204
- <template x-if="$store.stepDetail.selectedStepForDetail?.content">
205
- <div class="content-block">
206
- <h4>Content</h4>
207
- <pre class="tool-arg-item" x-text="$store.stepDetail.selectedStepForDetail?.content"></pre>
208
- </div>
209
- </template>
80
+ <!-- Content -->
81
+ <template x-if="$store.stepDetail.selectedStepForDetail?.content">
82
+ <div class="content-block">
83
+ <h4>Content</h4>
84
+ <pre class="result-content" x-text="$store.stepDetail.selectedStepForDetail?.content"></pre>
85
</div>
86
</template>
87
</div>
@@ -245,19 +120,12 @@
120
flex-wrap: wrap;
121
}
122
248
- .step-badge {
249
- padding: 0.25rem 0.5rem;
250
- border-radius: 8px;
251
- font-size: 0.75rem;
252
- font-weight: 600;
253
- font-family: var(--font-mono);
254
- }
255
-
123
.step-type-label {
124
font-weight: 500;
125
color: var(--color-text);
126
}
127
128
+ .header-label,
129
.tool-name-badge {
130
background: var(--color-message-bg);
131
color: var(--color-text);
@@ -268,16 +136,6 @@
136
border: 1px solid var(--color-border);
137
}
138
271
- .timestamp-text,
272
- .duration-badge {
273
- color: var(--color-text);
274
- opacity: 0.7;
275
- padding: 0.2rem 0.5rem;
276
- border-radius: var(--border-radius);
277
- font-size: 0.75rem;
278
- border: 1px solid var(--color-border);
279
- }
280
-
139
.header-actions {
140
display: flex;
141
gap: 0.5rem;
@@ -318,10 +176,6 @@
176
color: white;
177
}
178
321
- .btn-action-header.copied .material-symbols-outlined::before {
322
- content: 'check';
323
- }
324
-
179
.modal-body {
180
flex: 1;
181
overflow-y: auto;
@@ -368,21 +222,9 @@
222
color: var(--color-text);
223
font-size: 0.9rem;
224
font-weight: 600;
371
- display: flex;
372
- align-items: center;
373
- gap: 0.5rem;
225
opacity: 0.8;
226
}
227
377
- .content-block h4 .material-symbols-outlined {
378
- font-size: 18px;
379
- opacity: 0.7;
380
- }
381
-
382
- .content-block:last-child {
383
- margin-top: 1rem;
384
- }
385
-
228
.content-text {
229
color: var(--color-text);
230
line-height: 1.6;
@@ -390,154 +232,6 @@
232
word-break: break-word;
233
}
234
393
- .thoughts-block {
394
- margin-bottom: 1rem;
395
- }
396
-
397
- .thoughts-content {
398
- opacity: 0.8;
399
- }
400
-
401
- .reasoning-block {
402
- margin-bottom: 1rem;
403
- }
404
-
405
- .reasoning-content {
406
- opacity: 0.75;
407
- font-style: italic;
408
- }
409
-
410
- .raw-response-block {
411
- margin-top: 1rem;
412
- }
413
-
414
- .raw-response-content {
415
- background: var(--color-background);
416
- padding: 1rem;
417
- border-radius: 8px;
418
- font-family: var(--font-mono);
419
- font-size: 0.85rem;
420
- margin: 0;
421
- max-height: 50vh;
422
- overflow: auto;
423
- white-space: pre-wrap;
424
- word-break: break-word;
425
- color: var(--color-text);
426
- }
427
-
428
- .tool-call-block {
429
- margin-bottom: 1rem;
430
- }
431
-
432
- .tool-name-inline {
433
- background: var(--color-primary);
434
- color: white;
435
- padding: 0.15rem 0.5rem;
436
- border-radius: 4px;
437
- font-size: 0.8rem;
438
- font-family: var(--font-mono);
439
- font-weight: 500;
440
- margin-left: 0.5rem;
441
- }
442
-
443
- .command-block {
444
- margin-bottom: 1rem;
445
- }
446
-
447
- .command-info {
448
- display: flex;
449
- gap: 0.5rem;
450
- margin-bottom: 0.5rem;
451
- }
452
-
453
- .runtime-badge,
454
- .session-badge {
455
- background: var(--color-message-bg);
456
- padding: 0.2rem 0.5rem;
457
- border-radius: 8px;
458
- font-size: 0.75rem;
459
- font-family: var(--font-mono);
460
- border: 1px solid var(--color-border);
461
- }
462
-
463
- .command-text {
464
- background: var(--color-background);
465
- padding: 0.75rem;
466
- border-radius: 8px;
467
- font-size: 0.85rem;
468
- margin: 0;
469
- overflow-x: auto;
470
- }
471
-
472
- .terminal-output-full {
473
- background: rgba(0, 0, 0, 0.8);
474
- color: var(--color-text);
475
- padding: 1rem;
476
- border-radius: 8px;
477
- font-size: 0.85rem;
478
- margin: 0;
479
- max-height: 40vh;
480
- overflow: auto;
481
- white-space: pre-wrap;
482
- word-break: break-word;
483
- }
484
-
485
- .light-mode .terminal-output-full {
486
- background: rgba(0, 0, 0, 0.05);
487
- scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
488
- }
489
-
490
- .tool-args-grid {
491
- display: flex;
492
- flex-direction: column;
493
- gap: 0.5rem;
494
- }
495
-
496
- .tool-arg-item {
497
- display: flex;
498
- gap: 0.75rem;
499
- padding: 0.5rem;
500
- background: var(--color-background);
501
- border-radius: 8px;
502
- border: 1px solid var(--color-border);
503
- }
504
-
505
- /* GEN step tool call - single container with KV rows */
506
- .tool-args-container {
507
- background: var(--color-background);
508
- border-radius: 8px;
509
- border: 1px solid var(--color-border);
510
- padding: 0.5rem;
511
- display: grid;
512
- grid-template-columns: auto 1fr;
513
- gap: 0;
514
- }
515
-
516
- .tool-arg-row {
517
- display: contents;
518
- }
519
-
520
- .tool-arg-row .arg-key,
521
- .tool-arg-row .arg-value {
522
- padding: 0.5rem;
523
- }
524
-
525
- .arg-key {
526
- font-weight: 600;
527
- color: var(--color-primary);
528
- font-size: 0.85rem;
529
- flex-shrink: 0;
530
- }
531
-
532
- .arg-value {
533
- color: var(--color-text);
534
- font-family: var(--font-mono);
535
- font-size: 0.85rem;
536
- white-space: pre-wrap;
537
- word-break: break-word;
538
- flex: 1;
539
- }
540
-
235
.result-content {
236
background: var(--color-background);
237
padding: 1rem;
@@ -551,13 +245,6 @@
245
word-break: break-word;
246
}
247
554
- .screenshot-full {
555
- max-width: 100%;
556
- border-radius: 8px;
557
- border: 1px solid var(--color-border);
558
- cursor: pointer;
559
- }
560
-
248
.kvps-display {
249
display: flex;
250
flex-direction: column;
@@ -589,7 +276,6 @@
276
word-break: break-word;
277
}
278
592
- /* Scrollbar styling */
279
.modal-body::-webkit-scrollbar,
280
.result-content::-webkit-scrollbar {
281
width: 6px;
@@ -607,12 +293,6 @@
293
border-radius: 6px;
294
}
295
610
- .modal-body::-webkit-scrollbar-thumb:hover,
611
- .result-content::-webkit-scrollbar-thumb:hover {
612
- background-color: rgba(155, 155, 155, 0.7);
613
- }
614
-
615
- /* Responsive */
296
@media (max-width: 600px) {
297
.modal-header-compact {
298
flex-direction: column;
@@ -623,14 +303,6 @@
303
width: 100%;
304
justify-content: flex-end;
305
}
626
-
627
- .tool-arg-item {
628
- flex-direction: column;
629
- }
630
-
631
- .arg-key {
632
- min-width: unset;
633
- }
306
}
307
</style>
308
</body>
webui/components/modals/process-step-detail/step-detail-store.js
-1
@@ -49,7 +49,6 @@ const model = {
49
lines.push("");
50
lines.push("--- Data ---");
51
for (const [key, value] of Object.entries(step.kvps)) {
52
- if (key === "reasoning") continue;
52
const formattedValue = typeof value === "object" ? JSON.stringify(value, null, 2) : String(value);
53
lines.push(`${key}: ${formattedValue}`);
54
}
webui/js/messages.js
+26
-8
@@ -339,7 +339,7 @@ function getOrCreateProcessGroup(id, allowCompleted = true) {
339
return group;
340
}
341
342
-function buildDetailPayload(stepData) {
342
+function buildDetailPayload(stepData, extras = {}) {
343
if (!stepData) return null;
344
return {
345
type: stepData.type,
@@ -351,6 +351,7 @@ function buildDetailPayload(stepData) {
351
toolName: stepData.toolName,
352
statusCode: stepData.statusCode,
353
statusClass: stepData.statusClass,
354
+ ...extras,
355
};
356
}
357
@@ -899,10 +900,13 @@ export function drawMessageAgent({
900
if (kvps?.thoughts) displayKvps["icon://lightbulb"] = kvps.thoughts;
901
if (kvps?.step) displayKvps["icon://step"] = kvps.step;
902
const thoughtsText = String(kvps?.thoughts ?? "");
903
+ const headerLabels = [
904
+ kvps?.tool_name && { label: kvps.tool_name, class: "tool-name-badge" },
905
+ ].filter(Boolean);
906
const actionButtons = thoughtsText.trim()
907
? [
908
createActionButton("detail", "", () =>
905
- stepDetailStore.showStepDetail(buildDetailPayload(arguments[0])),
909
+ stepDetailStore.showStepDetail(buildDetailPayload(arguments[0], { headerLabels })),
910
),
911
createActionButton("speak", "", () => speechStore.speak(thoughtsText)),
912
createActionButton("copy", "", () => copyToClipboard(thoughtsText)),
@@ -1171,11 +1175,14 @@ export function drawMessageTool({
1175
}) {
1176
const title = cleanStepTitle(heading);
1177
let displayKvps = { ...kvps };
1178
+ const headerLabels = [
1179
+ kvps?.tool_name && { label: kvps.tool_name, class: "tool-name-badge" },
1180
+ ].filter(Boolean);
1181
const contentText = String(content ?? "");
1182
const actionButtons = contentText.trim()
1183
? [
1184
createActionButton("detail", "", () =>
1178
- stepDetailStore.showStepDetail(buildDetailPayload(arguments[0])),
1185
+ stepDetailStore.showStepDetail(buildDetailPayload(arguments[0], { headerLabels })),
1186
),
1187
createActionButton("speak", "", () => speechStore.speak(contentText)),
1188
createActionButton("copy", "", () => copyToClipboard(contentText)),
@@ -1223,12 +1230,17 @@ export function drawMessageCodeExe({
1230
if (kvps?.runtime) displayKvps.runtime = kvps.runtime;
1231
if (kvps?.session) displayKvps.session = kvps.session;
1232
1233
+ const headerLabels = [
1234
+ kvps?.runtime && { label: kvps.runtime, class: "tool-name-badge" },
1235
+ kvps?.session != null && { label: `Session ${kvps.session}`, class: "header-label" },
1236
+ ].filter(Boolean);
1237
+
1238
// render the standard step
1239
const commandText = String(kvps?.code ?? "");
1240
const outputText = String(content ?? "");
1241
const actionButtons = [
1242
createActionButton("detail", "", () =>
1231
- stepDetailStore.showStepDetail(buildDetailPayload(arguments[0])),
1243
+ stepDetailStore.showStepDetail(buildDetailPayload(arguments[0], { headerLabels })),
1244
),
1245
commandText.trim()
1246
? createActionButton("copy", "Command", () =>
@@ -1268,7 +1280,7 @@ export function drawMessageBrowser({
1280
const actionButtons = answerText.trim()
1281
? [
1282
createActionButton("detail", "", () =>
1271
- stepDetailStore.showStepDetail(buildDetailPayload(arguments[0])),
1283
+ stepDetailStore.showStepDetail(buildDetailPayload(arguments[0], { headerLabels: [] })),
1284
),
1285
createActionButton("speak", "", () => speechStore.speak(answerText)),
1286
createActionButton("copy", "", () => copyToClipboard(answerText)),
@@ -1300,11 +1312,14 @@ export function drawMessageMcp({
1312
}) {
1313
const title = cleanStepTitle(heading);
1314
let displayKvps = { ...kvps };
1315
+ const headerLabels = [
1316
+ kvps?.tool_name && { label: kvps.tool_name, class: "tool-name-badge" },
1317
+ ].filter(Boolean);
1318
const contentText = String(content ?? "");
1319
const actionButtons = contentText.trim()
1320
? [
1321
createActionButton("detail", "", () =>
1307
- stepDetailStore.showStepDetail(buildDetailPayload(arguments[0])),
1322
+ stepDetailStore.showStepDetail(buildDetailPayload(arguments[0], { headerLabels })),
1323
),
1324
createActionButton("speak", "", () => speechStore.speak(contentText)),
1325
createActionButton("copy", "", () => copyToClipboard(contentText)),
@@ -1336,11 +1351,14 @@ export function drawMessageSubagent({
1351
}) {
1352
const title = cleanStepTitle(heading);
1353
let displayKvps = { ...kvps };
1354
+ const headerLabels = [
1355
+ kvps?.tool_name && { label: kvps.tool_name, class: "tool-name-badge" },
1356
+ ].filter(Boolean);
1357
const contentText = String(content ?? "");
1358
const actionButtons = contentText.trim()
1359
? [
1360
createActionButton("detail", "", () =>
1343
- stepDetailStore.showStepDetail(buildDetailPayload(arguments[0])),
1361
+ stepDetailStore.showStepDetail(buildDetailPayload(arguments[0], { headerLabels })),
1362
),
1363
createActionButton("speak", "", () => speechStore.speak(contentText)),
1364
createActionButton("copy", "", () => copyToClipboard(contentText)),
@@ -1534,7 +1552,7 @@ export function drawMessageError({
1552
const contentText = String(content ?? "");
1553
const actionButtons = [
1554
createActionButton("detail", "", () =>
1537
- stepDetailStore.showStepDetail(buildDetailPayload(arguments[0])),
1555
+ stepDetailStore.showStepDetail(buildDetailPayload(arguments[0], { headerLabels: [] })),
1556
),
1557
contentText.trim()
1558
? createActionButton("copy", "", () => copyToClipboard(contentText))