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))