Update artifacts-tool-renderer to use collapsible headers

- All actions except DELETE now use collapsible headers
- CREATE/UPDATE/REWRITE/GET/LOGS: code/output collapsed by default
- DELETE: keeps simple non-collapsible header
- Fix isStreaming parameter usage for proper spinner state
- Add smooth 300ms animation on expand/collapse
- Full header is clickable to toggle collapse state
This commit is contained in:
Mario Zechner 2025-10-08 13:46:52 +02:00
parent 1405c47b50
commit b3efac4591

View file

@ -1,10 +1,11 @@
import { Diff, html, type TemplateResult } from "@mariozechner/mini-lit"; import { Diff, html, type TemplateResult } from "@mariozechner/mini-lit";
import "@mariozechner/mini-lit/dist/CodeBlock.js"; import "@mariozechner/mini-lit/dist/CodeBlock.js";
import type { ToolResultMessage } from "@mariozechner/pi-ai"; import type { ToolResultMessage } from "@mariozechner/pi-ai";
import { createRef, ref } from "lit/directives/ref.js";
import { FileCode2 } from "lucide"; import { FileCode2 } from "lucide";
import "../../components/ConsoleBlock.js"; import "../../components/ConsoleBlock.js";
import { i18n } from "../../utils/i18n.js"; import { i18n } from "../../utils/i18n.js";
import { renderHeader } from "../renderer-registry.js"; import { renderCollapsibleHeader, renderHeader } from "../renderer-registry.js";
import type { ToolRenderer } from "../types.js"; import type { ToolRenderer } from "../types.js";
import type { ArtifactsParams } from "./artifacts.js"; import type { ArtifactsParams } from "./artifacts.js";
@ -51,7 +52,11 @@ export class ArtifactsToolRenderer implements ToolRenderer<ArtifactsParams, unde
result: ToolResultMessage<undefined> | undefined, result: ToolResultMessage<undefined> | undefined,
isStreaming?: boolean, isStreaming?: boolean,
): TemplateResult { ): TemplateResult {
const state = result ? (result.isError ? "error" : "complete") : "inprogress"; const state = result ? (result.isError ? "error" : "complete") : isStreaming ? "inprogress" : "inprogress";
// Create refs for collapsible sections
const contentRef = createRef<HTMLDivElement>();
const chevronRef = createRef<HTMLSpanElement>();
// Helper to get command labels // Helper to get command labels
const getCommandLabels = (command: string): { streaming: string; complete: string } => { const getCommandLabels = (command: string): { streaming: string; complete: string } => {
@ -82,14 +87,16 @@ export class ArtifactsToolRenderer implements ToolRenderer<ArtifactsParams, unde
const isHtml = filename?.endsWith(".html"); const isHtml = filename?.endsWith(".html");
return html` return html`
<div class="space-y-3"> <div>
${renderHeader(state, FileCode2, headerText)} ${renderCollapsibleHeader(state, FileCode2, headerText, contentRef, chevronRef, false)}
${content ? html`<code-block .code=${content} language=${getLanguageFromFilename(filename)}></code-block>` : ""} <div ${ref(contentRef)} class="max-h-0 overflow-hidden transition-all duration-300 space-y-3">
${ ${content ? html`<code-block .code=${content} language=${getLanguageFromFilename(filename)}></code-block>` : ""}
isHtml ${
? html`<console-block .content=${result.output || i18n("An error occurred")} variant="error"></console-block>` isHtml
: html`<div class="text-sm text-destructive">${result.output || i18n("An error occurred")}</div>` ? html`<console-block .content=${result.output || i18n("An error occurred")} variant="error"></console-block>`
} : html`<div class="text-sm text-destructive">${result.output || i18n("An error occurred")}</div>`
}
</div>
</div> </div>
`; `;
} }
@ -115,9 +122,11 @@ export class ArtifactsToolRenderer implements ToolRenderer<ArtifactsParams, unde
if (command === "get") { if (command === "get") {
const fileContent = result.output || i18n("(no output)"); const fileContent = result.output || i18n("(no output)");
return html` return html`
<div class="space-y-3"> <div>
${renderHeader(state, FileCode2, headerText)} ${renderCollapsibleHeader(state, FileCode2, headerText, contentRef, chevronRef, false)}
<code-block .code=${fileContent} language=${getLanguageFromFilename(filename)}></code-block> <div ${ref(contentRef)} class="max-h-0 overflow-hidden transition-all duration-300">
<code-block .code=${fileContent} language=${getLanguageFromFilename(filename)}></code-block>
</div>
</div> </div>
`; `;
} }
@ -126,9 +135,11 @@ export class ArtifactsToolRenderer implements ToolRenderer<ArtifactsParams, unde
if (command === "logs") { if (command === "logs") {
const logs = result.output || i18n("(no output)"); const logs = result.output || i18n("(no output)");
return html` return html`
<div class="space-y-3"> <div>
${renderHeader(state, FileCode2, headerText)} ${renderCollapsibleHeader(state, FileCode2, headerText, contentRef, chevronRef, false)}
<console-block .content=${logs}></console-block> <div ${ref(contentRef)} class="max-h-0 overflow-hidden transition-all duration-300">
<console-block .content=${logs}></console-block>
</div>
</div> </div>
`; `;
} }
@ -140,10 +151,12 @@ export class ArtifactsToolRenderer implements ToolRenderer<ArtifactsParams, unde
const logs = result.output || ""; const logs = result.output || "";
return html` return html`
<div class="space-y-3"> <div>
${renderHeader(state, FileCode2, headerText)} ${renderCollapsibleHeader(state, FileCode2, headerText, contentRef, chevronRef, false)}
${codeContent ? html`<code-block .code=${codeContent} language=${getLanguageFromFilename(filename)}></code-block>` : ""} <div ${ref(contentRef)} class="max-h-0 overflow-hidden transition-all duration-300 space-y-3">
${isHtml && logs ? html`<console-block .content=${logs}></console-block>` : ""} ${codeContent ? html`<code-block .code=${codeContent} language=${getLanguageFromFilename(filename)}></code-block>` : ""}
${isHtml && logs ? html`<console-block .content=${logs}></console-block>` : ""}
</div>
</div> </div>
`; `;
} }
@ -173,31 +186,42 @@ export class ArtifactsToolRenderer implements ToolRenderer<ArtifactsParams, unde
case "create": case "create":
case "rewrite": case "rewrite":
return html` return html`
<div class="space-y-3"> <div>
${renderHeader(state, FileCode2, headerText)} ${renderCollapsibleHeader(state, FileCode2, headerText, contentRef, chevronRef, false)}
${ <div ${ref(contentRef)} class="max-h-0 overflow-hidden transition-all duration-300">
content ${
? html`<code-block .code=${content} language=${getLanguageFromFilename(filename)} class="mt-2"></code-block>` content
: "" ? html`<code-block .code=${content} language=${getLanguageFromFilename(filename)}></code-block>`
} : ""
}
</div>
</div> </div>
`; `;
case "update": case "update":
return html` return html`
<div class="space-y-3"> <div>
${renderHeader(state, FileCode2, headerText)} ${renderCollapsibleHeader(state, FileCode2, headerText, contentRef, chevronRef, false)}
${ <div ${ref(contentRef)} class="max-h-0 overflow-hidden transition-all duration-300">
old_str !== undefined && new_str !== undefined ${
? Diff({ oldText: old_str, newText: new_str, className: "mt-2" }) old_str !== undefined && new_str !== undefined
: "" ? Diff({ oldText: old_str, newText: new_str })
} : ""
}
</div>
</div> </div>
`; `;
case "get": case "get":
case "delete":
case "logs": case "logs":
return html`
<div>
${renderCollapsibleHeader(state, FileCode2, headerText, contentRef, chevronRef, false)}
<div ${ref(contentRef)} class="max-h-0 overflow-hidden transition-all duration-300"></div>
</div>
`;
case "delete":
default: default:
return renderHeader(state, FileCode2, headerText); return renderHeader(state, FileCode2, headerText);
} }