Add copy buttons to code blocks and tool input and output

This commit is contained in:
Ian Gulliver
2026-10-15 16:52:20 -07:00
parent e9039cceb4
commit f9be3c3c9d
10 changed files with 81 additions and 14 deletions
+17
View File
@@ -26,6 +26,23 @@ export function replace(el, ...children) {
el.replaceChildren();
append(el, ...children);
}
export function copyable(pre) {
const button = h("button", { type: "button", class: "copy", title: "Copy to the clipboard" }, "copy");
button.onclick = async () => {
try {
await navigator.clipboard.writeText(pre.textContent ?? "");
}
catch (err) {
void showAlert(errorText(err));
return;
}
button.textContent = "copied";
setTimeout(() => {
button.textContent = "copy";
}, 1500);
};
return h("div", { class: "copyable" }, pre, button);
}
export function ago(iso) {
const seconds = Math.round((Date.now() - Date.parse(iso)) / 1000);
if (seconds < 60) {
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -1,4 +1,4 @@
import { h } from "./dom.js";
import { copyable, h } from "./dom.js";
import { diagram } from "./mermaid.js";
import { taskStyle } from "./taskcolor.js";
const choiceMarker = /^(\([ *]\)|\[[ xX]\])\s+(\S.*?)\s*$/;
@@ -37,7 +37,7 @@ function blocks(lines, opts) {
i++;
}
i++;
out.push(f[2].toLowerCase() === "mermaid" ? diagram(body.join("\n")) : h("pre", { class: "md-code" }, h("code", {}, body.join("\n"))));
out.push(f[2].toLowerCase() === "mermaid" ? diagram(body.join("\n")) : copyable(h("pre", { class: "md-code" }, h("code", {}, body.join("\n")))));
continue;
}
if (detailsClose.test(line)) {
+1 -1
View File
File diff suppressed because one or more lines are too long
+3 -3
View File
@@ -1,5 +1,5 @@
import { get, post } from "./api.js";
import { ago, errorText, h, replace, showAlert, showConfirm } from "./dom.js";
import { ago, copyable, errorText, h, replace, showAlert, showConfirm } from "./dom.js";
import { markdown } from "./markdown.js";
import { previewURL } from "./preview.js";
import { reviewCounts } from "./reviews.js";
@@ -717,7 +717,7 @@ function subagentTag(parent, ctx) {
function renderToolCall(p, ctx) {
const result = h("div", { class: "tool-result pending" }, "running…");
ctx.tools.set(String(p["id"]), result);
return h("details", { class: "tool" }, h("summary", {}, subagentTag(parentOf(p), ctx), h("span", { class: "tool-name" }, String(p["name"])), " ", h("span", { class: "tool-brief" }, toolBrief(p["input"], ctx.root()))), h("pre", { class: "tool-input" }, JSON.stringify(p["input"], null, 2)), result);
return h("details", { class: "tool" }, h("summary", {}, subagentTag(parentOf(p), ctx), h("span", { class: "tool-name" }, String(p["name"])), " ", h("span", { class: "tool-brief" }, toolBrief(p["input"], ctx.root()))), copyable(h("pre", { class: "tool-input" }, JSON.stringify(p["input"], null, 2))), result);
}
function isSubagentCall(p) {
return p["name"] === "Agent" || p["name"] === "Task";
@@ -833,7 +833,7 @@ function renderEvent(ev, ctx) {
return h("details", { class: "thinking" }, h("summary", {}, subagentTag(parentOf(p), ctx), "thinking"), markdown(String(p["text"]), "text md", md));
case "agent.tool_result": {
const target = ctx.tools.get(String(p["tool_use_id"]));
const body = h("pre", {}, resultText(p["content"]));
const body = copyable(h("pre", {}, resultText(p["content"])));
if (!target) {
return h("div", { class: "tool-result orphan" }, body);
}
+1 -1
View File
File diff suppressed because one or more lines are too long