phantom-wiki-viewer / trajectory-ui.js
timchen0618's picture
Polish cross-tab trajectory UX
caf41a5 verified
Raw
History Blame Contribute Delete
5.79 kB
(function () {
"use strict";
var sharedQid = null;
function esc(value) {
return String(value === null || value === undefined ? "" : value)
.replace(/&/g, "&amp;").replace(/</g, "&lt;")
.replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
function text(value) {
if (value === null || value === undefined) return "";
if (typeof value === "string") return value;
return JSON.stringify(value, null, 2);
}
function inputText(event) {
var value = event && event.input;
if (value === null || value === undefined) return "";
if (typeof value !== "object" || Array.isArray(value)) return text(value);
var keys = Object.keys(value);
if (keys.length === 1 && typeof value[keys[0]] === "string") return value[keys[0]];
return JSON.stringify(value, null, 2);
}
function humanBytes(length) {
if (length < 1024) return length + " B";
if (length < 1024 * 1024) return (length / 1024).toFixed(1) + " KB";
return (length / (1024 * 1024)).toFixed(2) + " MB";
}
function toolStep(index, event, resultEvent) {
var name = event.name || "tool";
var input = inputText(event);
var output = resultEvent ? text(resultEvent.content) : "";
var collapsed = output.length > 2000;
return '<div class="pretty-step tool tool-' + esc(name) + '">' +
'<div class="pretty-step-head"><span class="pretty-step-index">' + index +
'</span><span class="pretty-kind">tool</span><span class="pretty-tool-pill">' +
esc(name) + "</span></div>" +
(input ? '<div class="pretty-label">Input</div><pre class="pretty-input">' +
esc(input) + "</pre>" : "") +
(resultEvent ? '<div class="pretty-label">Output <span>' +
humanBytes(output.length) + '</span></div><div class="pretty-output-wrap' +
(collapsed ? " collapsed" : "") + '"><pre class="pretty-output">' +
esc(output) + "</pre>" +
(collapsed ? '<button type="button" class="pretty-output-toggle">Expand output ▾</button>' : "") +
"</div>" : "") +
"</div>";
}
function messageStep(index, event) {
var content = text(event.content).trim();
if (!content) return "";
return '<div class="pretty-step model"><div class="pretty-step-head">' +
'<span class="pretty-step-index">' + index +
'</span><span class="pretty-kind">model</span></div>' +
'<div class="pretty-model-text">' + esc(content) + "</div></div>";
}
function otherStep(index, event) {
var content = text(event.content || event.input || event);
return '<div class="pretty-step other"><div class="pretty-step-head">' +
'<span class="pretty-step-index">' + index + '</span><span class="pretty-kind">' +
esc(event.type || "event") + "</span></div>" +
'<pre class="pretty-output">' + esc(content) + "</pre></div>";
}
function render(events, label) {
events = events || [];
if (!events.length) return "";
var parts = [];
var step = 0;
for (var i = 0; i < events.length; i++) {
var event = events[i] || {};
if (event.type === "tool_call") {
step++;
var next = events[i + 1];
parts.push(toolStep(step, event, next && next.type === "tool_result" ? next : null));
if (next && next.type === "tool_result") i++;
} else if (event.type === "tool_result") {
step++;
parts.push(toolStep(step, { name: "tool" }, event));
} else if (["text", "llm_response", "assistant", "model"].indexOf(event.type) >= 0) {
step++;
parts.push(messageStep(step, event));
} else if (event.type !== "done") {
step++;
parts.push(otherStep(step, event));
}
}
return '<details class="pretty-trajectory"><summary>' +
esc(label || "Agent trajectory") + " · " + events.length +
' events</summary><div class="pretty-steps">' + parts.join("") + "</div></details>";
}
function bind(container) {
if (!container) return;
container.querySelectorAll(".pretty-output-toggle").forEach(function (button) {
button.addEventListener("click", function () {
var wrap = button.closest(".pretty-output-wrap");
var collapsed = wrap.classList.toggle("collapsed");
button.textContent = collapsed ? "Expand output ▾" : "Collapse output ▴";
});
});
}
function goldItems(value) {
var values = Array.isArray(value) ? value : [value];
return values.filter(function (item) {
return item !== null && item !== undefined && String(item).trim() !== "";
}).map(function (item) {
if (typeof item === "object") {
return item.answer_text || item.value || item.name || JSON.stringify(item);
}
return String(item);
});
}
function gold(value, label) {
var values = goldItems(value);
return '<div class="compact-gold"><span class="compact-gold-label">' +
esc(label || "Gold") + (values.length > 1 ? " · " + values.length : "") +
'</span><div class="compact-gold-pills">' +
(values.length ? values.map(function (item) {
return '<span class="compact-gold-pill" title="' + esc(item) + '">' +
esc(item) + "</span>";
}).join("") : '<span class="compact-gold-empty">none</span>') +
"</div></div>";
}
function supporting(label, html, count) {
return '<details class="compact-supporting"><summary>' + esc(label) +
(count !== undefined ? " · " + count : "") +
'</summary><div class="compact-supporting-body">' + html + "</div></details>";
}
window.TrajectoryUI = {
render: render,
bind: bind,
gold: gold,
supporting: supporting,
setQid: function (qid) { if (qid !== null && qid !== undefined) sharedQid = String(qid); },
getQid: function () { return sharedQid; },
};
})();