From 8ec9805112a6d28ed1eec9a937a2c9b280b08bdb Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Wed, 8 Oct 2025 01:54:50 +0200 Subject: [PATCH] Refactor artifacts renderer and add Console component - Extract ArtifactsToolRenderer from ArtifactsPanel into standalone renderer - Fix ChatPanel to register ArtifactsToolRenderer instead of panel - Implement command-specific rendering logic (create/update/rewrite/get/logs/delete) - Create reusable Console component with copy button and autoscroll toggle - Replace custom console implementation with ExpandableSection and Console - Fix Lit reactivity for HtmlArtifact logs using spread operator - Add Lucide icons (FileCode2, ChevronsDown, Lock) for UI consistency - Follow skill.ts patterns with renderHeader and state handling - Add i18n strings for all artifact actions and console features --- packages/web-ui/src/ChatPanel.ts | 5 +- .../web-ui/src/components/ConsoleBlock.ts | 6 +- .../src/components/ExpandableSection.ts | 46 ++++ packages/web-ui/src/components/Messages.ts | 93 +------- packages/web-ui/src/index.ts | 5 +- .../web-ui/src/tools/artifacts/Console.ts | 93 ++++++++ .../src/tools/artifacts/HtmlArtifact.ts | 78 ++----- .../artifacts/artifacts-tool-renderer.ts | 209 ++++++++++++++++++ .../web-ui/src/tools/artifacts/artifacts.ts | 195 +--------------- packages/web-ui/src/tools/artifacts/index.ts | 1 + packages/web-ui/src/tools/index.ts | 24 +- packages/web-ui/src/tools/javascript-repl.ts | 125 ++++++----- .../web-ui/src/tools/renderer-registry.ts | 38 ++++ .../src/tools/renderers/BashRenderer.ts | 49 ++-- .../src/tools/renderers/CalculateRenderer.ts | 59 +++-- .../src/tools/renderers/DefaultRenderer.ts | 45 ++-- .../tools/renderers/GetCurrentTimeRenderer.ts | 74 +++++-- packages/web-ui/src/tools/types.ts | 7 +- packages/web-ui/src/utils/i18n.ts | 90 +++++++- 19 files changed, 716 insertions(+), 526 deletions(-) create mode 100644 packages/web-ui/src/components/ExpandableSection.ts create mode 100644 packages/web-ui/src/tools/artifacts/Console.ts create mode 100644 packages/web-ui/src/tools/artifacts/artifacts-tool-renderer.ts diff --git a/packages/web-ui/src/ChatPanel.ts b/packages/web-ui/src/ChatPanel.ts index 128e7413..507c4192 100644 --- a/packages/web-ui/src/ChatPanel.ts +++ b/packages/web-ui/src/ChatPanel.ts @@ -4,7 +4,7 @@ import { customElement, property, state } from "lit/decorators.js"; import type { AgentInterface } from "./components/AgentInterface.js"; import "./components/AgentInterface.js"; import type { Agent } from "./agent/agent.js"; -import { ArtifactsPanel } from "./tools/artifacts/index.js"; +import { ArtifactsPanel, ArtifactsToolRenderer } from "./tools/artifacts/index.js"; import { createJavaScriptReplTool } from "./tools/javascript-repl.js"; import { registerToolRenderer } from "./tools/renderer-registry.js"; import { i18n } from "./utils/i18n.js"; @@ -78,7 +78,8 @@ export class ChatPanel extends LitElement { if (this.sandboxUrlProvider) { this.artifactsPanel.sandboxUrlProvider = this.sandboxUrlProvider; } - registerToolRenderer("artifacts", this.artifactsPanel); + // Register the standalone tool renderer (not the panel itself) + registerToolRenderer("artifacts", new ArtifactsToolRenderer()); // Attachments provider const getAttachments = () => { diff --git a/packages/web-ui/src/components/ConsoleBlock.ts b/packages/web-ui/src/components/ConsoleBlock.ts index b4949bee..58a08984 100644 --- a/packages/web-ui/src/components/ConsoleBlock.ts +++ b/packages/web-ui/src/components/ConsoleBlock.ts @@ -6,6 +6,7 @@ import { i18n } from "../utils/i18n.js"; export class ConsoleBlock extends LitElement { @property() content: string = ""; + @property() variant: "default" | "error" = "default"; @state() private copied = false; protected override createRenderRoot(): HTMLElement | DocumentFragment { @@ -38,6 +39,9 @@ export class ConsoleBlock extends LitElement { } override render() { + const isError = this.variant === "error"; + const textClass = isError ? "text-destructive" : "text-foreground"; + return html`
@@ -52,7 +56,7 @@ export class ConsoleBlock extends LitElement {
-
+					
 ${this.content || ""}
diff --git a/packages/web-ui/src/components/ExpandableSection.ts b/packages/web-ui/src/components/ExpandableSection.ts new file mode 100644 index 00000000..b032af9f --- /dev/null +++ b/packages/web-ui/src/components/ExpandableSection.ts @@ -0,0 +1,46 @@ +import { icon } from "@mariozechner/mini-lit"; +import { html, LitElement, type TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; +import { ChevronDown, ChevronRight } from "lucide"; + +/** + * Reusable expandable section component for tool renderers. + * Captures children in connectedCallback and re-renders them in the details area. + */ +@customElement("expandable-section") +export class ExpandableSection extends LitElement { + @property() summary!: string; + @property({ type: Boolean }) defaultExpanded = false; + @state() private expanded = false; + private capturedChildren: Node[] = []; + + protected createRenderRoot() { + return this; // light DOM + } + + override connectedCallback() { + super.connectedCallback(); + // Capture children before first render + this.capturedChildren = Array.from(this.childNodes); + // Clear children (we'll re-insert them in render) + this.innerHTML = ""; + this.expanded = this.defaultExpanded; + } + + override render(): TemplateResult { + return html` +
+ + ${this.expanded ? html`
${this.capturedChildren}
` : ""} +
+ `; + } +} diff --git a/packages/web-ui/src/components/Messages.ts b/packages/web-ui/src/components/Messages.ts index 1b0827b6..68162ba5 100644 --- a/packages/web-ui/src/components/Messages.ts +++ b/packages/web-ui/src/components/Messages.ts @@ -6,11 +6,10 @@ import type { ToolResultMessage as ToolResultMessageType, UserMessage as UserMessageType, } from "@mariozechner/pi-ai"; -import type { AgentToolResult } from "@mariozechner/pi-ai/dist/agent/types.js"; import { LitElement, type TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { Bug, Loader, Wrench } from "lucide"; -import { renderToolParams, renderToolResult } from "../tools/index.js"; +import { renderTool } from "../tools/index.js"; import type { Attachment } from "../utils/attachment-utils.js"; import { formatUsage } from "../utils/format.js"; import { i18n } from "../utils/i18n.js"; @@ -149,7 +148,7 @@ export class AssistantMessage extends LitElement { @customElement("tool-message-debug") export class ToolMessageDebugView extends LitElement { @property({ type: Object }) callArgs: any; - @property({ type: String }) result?: AgentToolResult; + @property({ type: Object }) result?: ToolResultMessageType; @property({ type: Boolean }) hasResult: boolean = false; protected override createRenderRoot(): HTMLElement | DocumentFragment { @@ -205,7 +204,6 @@ export class ToolMessage extends LitElement { @property({ type: Boolean }) pending: boolean = false; @property({ type: Boolean }) aborted: boolean = false; @property({ type: Boolean }) isStreaming: boolean = false; - @state() private _showDebug = false; protected override createRenderRoot(): HTMLElement | DocumentFragment { return this; @@ -216,94 +214,15 @@ export class ToolMessage extends LitElement { this.style.display = "block"; } - private toggleDebug = () => { - this._showDebug = !this._showDebug; - }; - override render() { - const toolLabel = this.tool?.label || this.toolCall.name; const toolName = this.tool?.name || this.toolCall.name; - const isError = this.result?.isError === true; - const hasResult = !!this.result; - let statusIcon: TemplateResult; - if (this.pending || (this.isStreaming && !hasResult)) { - statusIcon = html`${icon(Loader, "sm")}`; - } else if (this.aborted && !hasResult) { - statusIcon = html`${icon(Wrench, "sm")}`; - } else if (hasResult && isError) { - statusIcon = html`${icon(Wrench, "sm")}`; - } else if (hasResult) { - statusIcon = html`${icon(Wrench, "sm")}`; - } else { - statusIcon = html`${icon(Wrench, "sm")}`; - } - - // Normalize error text - let errorMessage = this.result?.output || ""; - if (isError) { - try { - const parsed = JSON.parse(errorMessage); - if ((parsed as any).error) errorMessage = (parsed as any).error; - else if ((parsed as any).message) errorMessage = (parsed as any).message; - } catch {} - errorMessage = errorMessage.replace(/^(Tool )?Error:\s*/i, ""); - errorMessage = errorMessage.replace(/^Error:\s*/i, ""); - } - - const paramsTpl = renderToolParams( - toolName, - this.toolCall.arguments, - this.isStreaming || (this.pending && !hasResult), - ); - const resultTpl = - hasResult && !isError ? renderToolResult(toolName, this.toolCall.arguments, this.result!) : undefined; + // Render tool content (renderer handles errors and styling) + const toolContent = renderTool(toolName, this.toolCall.arguments, this.result, this.isStreaming || this.pending); return html` -
-
-
- ${statusIcon} - ${toolLabel} -
- ${Button({ - variant: this._showDebug ? "default" : "ghost", - size: "sm", - onClick: this.toggleDebug, - children: icon(Bug, "sm"), - className: "text-muted-foreground", - })} -
- - ${ - this._showDebug - ? html`` - : html` -
${paramsTpl}
- ${ - this.pending && !hasResult - ? html`
${i18n("Waiting for tool result…")}
` - : "" - } - ${ - this.aborted && !hasResult - ? html`
${i18n("Call was aborted; no result.")}
` - : "" - } - ${ - hasResult && isError - ? html`
- ${errorMessage} -
` - : "" - } - ${resultTpl ? html`
${resultTpl}
` : ""} - ` - } +
+ ${toolContent}
`; } diff --git a/packages/web-ui/src/index.ts b/packages/web-ui/src/index.ts index f87009bf..f91bd6c4 100644 --- a/packages/web-ui/src/index.ts +++ b/packages/web-ui/src/index.ts @@ -13,6 +13,7 @@ export { ChatPanel } from "./ChatPanel.js"; export { AgentInterface } from "./components/AgentInterface.js"; export { AttachmentTile } from "./components/AttachmentTile.js"; export { ConsoleBlock } from "./components/ConsoleBlock.js"; +export { ExpandableSection } from "./components/ExpandableSection.js"; export { Input } from "./components/Input.js"; export { MessageEditor } from "./components/MessageEditor.js"; export { MessageList } from "./components/MessageList.js"; @@ -60,13 +61,15 @@ export type { // Artifacts export { ArtifactElement } from "./tools/artifacts/ArtifactElement.js"; export { type Artifact, ArtifactsPanel, type ArtifactsParams } from "./tools/artifacts/artifacts.js"; +export { ArtifactsToolRenderer } from "./tools/artifacts/artifacts-tool-renderer.js"; export { HtmlArtifact } from "./tools/artifacts/HtmlArtifact.js"; export { MarkdownArtifact } from "./tools/artifacts/MarkdownArtifact.js"; export { SvgArtifact } from "./tools/artifacts/SvgArtifact.js"; export { TextArtifact } from "./tools/artifacts/TextArtifact.js"; // Tools -export { getToolRenderer, registerToolRenderer, renderToolParams, renderToolResult } from "./tools/index.js"; +export { getToolRenderer, registerToolRenderer, renderTool } from "./tools/index.js"; export { createJavaScriptReplTool, javascriptReplTool } from "./tools/javascript-repl.js"; +export { renderHeader } from "./tools/renderer-registry.js"; export { BashRenderer } from "./tools/renderers/BashRenderer.js"; export { CalculateRenderer } from "./tools/renderers/CalculateRenderer.js"; // Tool renderers diff --git a/packages/web-ui/src/tools/artifacts/Console.ts b/packages/web-ui/src/tools/artifacts/Console.ts new file mode 100644 index 00000000..287ed043 --- /dev/null +++ b/packages/web-ui/src/tools/artifacts/Console.ts @@ -0,0 +1,93 @@ +import { icon } from "@mariozechner/mini-lit"; +import "@mariozechner/mini-lit/dist/CopyButton.js"; +import { html, LitElement, type TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators.js"; +import { createRef, type Ref, ref } from "lit/directives/ref.js"; +import { repeat } from "lit/directives/repeat.js"; +import { ChevronDown, ChevronRight, ChevronsDown, Lock } from "lucide"; +import { i18n } from "../../utils/i18n.js"; + +interface LogEntry { + type: "log" | "error"; + text: string; +} + +@customElement("artifact-console") +export class Console extends LitElement { + @property({ attribute: false }) logs: LogEntry[] = []; + @state() private expanded = false; + @state() private autoscroll = true; + private logsContainerRef: Ref = createRef(); + + protected createRenderRoot() { + return this; // light DOM + } + + override updated() { + // Autoscroll to bottom when new logs arrive + if (this.autoscroll && this.expanded && this.logsContainerRef.value) { + this.logsContainerRef.value.scrollTop = this.logsContainerRef.value.scrollHeight; + } + } + + private getLogsText(): string { + return this.logs.map((l) => `[${l.type}] ${l.text}`).join("\n"); + } + + override render(): TemplateResult { + const errorCount = this.logs.filter((l) => l.type === "error").length; + const summary = + errorCount > 0 + ? `${i18n("console")} (${errorCount} ${errorCount === 1 ? "error" : "errors"})` + : `${i18n("console")} (${this.logs.length})`; + + return html` +
+
+ + ${ + this.expanded + ? html` + + + ` + : "" + } +
+ ${ + this.expanded + ? html` +
+ ${repeat( + this.logs, + (_log, index) => index, + (log) => html` +
+ [${log.type}] ${log.text} +
+ `, + )} +
+ ` + : "" + } +
+ `; + } +} diff --git a/packages/web-ui/src/tools/artifacts/HtmlArtifact.ts b/packages/web-ui/src/tools/artifacts/HtmlArtifact.ts index 4fc175d2..ed670607 100644 --- a/packages/web-ui/src/tools/artifacts/HtmlArtifact.ts +++ b/packages/web-ui/src/tools/artifacts/HtmlArtifact.ts @@ -9,6 +9,8 @@ import type { Attachment } from "../../utils/attachment-utils.js"; import { i18n } from "../../utils/i18n.js"; import "../../components/SandboxedIframe.js"; import { ArtifactElement } from "./ArtifactElement.js"; +import type { Console } from "./Console.js"; +import "./Console.js"; @customElement("html-artifact") export class HtmlArtifact extends ArtifactElement { @@ -22,14 +24,12 @@ export class HtmlArtifact extends ArtifactElement { // Refs for DOM elements private sandboxIframeRef: Ref = createRef(); - private consoleLogsRef: Ref = createRef(); - private consoleButtonRef: Ref = createRef(); + private consoleRef: Ref = createRef(); // Store message handler so we can remove it private messageHandler?: (e: MessageEvent) => void; @state() private viewMode: "preview" | "code" = "preview"; - @state() private consoleOpen = false; private setViewMode(mode: "preview" | "code") { this.viewMode = mode; @@ -62,13 +62,9 @@ export class HtmlArtifact extends ArtifactElement { if (oldValue !== value) { // Reset logs when content changes this.logs = []; - if (this.consoleLogsRef.value) { - this.consoleLogsRef.value.innerHTML = ""; - } this.requestUpdate(); // Execute content in sandbox if it exists if (this.sandboxIframeRef.value && value) { - this.updateConsoleButton(); this.executeContent(value); } } @@ -95,11 +91,15 @@ export class HtmlArtifact extends ArtifactElement { if (e.data.sandboxId !== sandboxId) return; if (e.data.type === "console") { - this.logs.push({ - type: e.data.method === "error" ? "error" : "log", - text: e.data.text, - }); - this.updateConsoleButton(); + // Create new array reference for Lit reactivity + this.logs = [ + ...this.logs, + { + type: e.data.method === "error" ? "error" : "log", + text: e.data.text, + }, + ]; + this.requestUpdate(); // Re-render to show console } }; window.addEventListener("message", this.messageHandler); @@ -137,39 +137,6 @@ export class HtmlArtifact extends ArtifactElement { } } - private updateConsoleButton() { - const button = this.consoleButtonRef.value; - if (!button) return; - - const errorCount = this.logs.filter((l) => l.type === "error").length; - const text = - errorCount > 0 - ? `${i18n("console")} ${errorCount} errors` - : `${i18n("console")} (${this.logs.length})`; - button.innerHTML = `${text}${this.consoleOpen ? "▼" : "▶"}`; - } - - private toggleConsole() { - this.consoleOpen = !this.consoleOpen; - this.requestUpdate(); - - // Populate console logs if opening - if (this.consoleOpen) { - requestAnimationFrame(() => { - if (this.consoleLogsRef.value) { - // Populate with existing logs - this.consoleLogsRef.value.innerHTML = ""; - this.logs.forEach((log) => { - const logEl = document.createElement("div"); - logEl.className = `text-xs font-mono ${log.type === "error" ? "text-destructive" : "text-muted-foreground"}`; - logEl.textContent = `[${log.type}] ${log.text}`; - this.consoleLogsRef.value!.appendChild(logEl); - }); - } - }); - } - } - public getLogs(): string { if (this.logs.length === 0) return i18n("No logs for {filename}").replace("{filename}", this.filename); return this.logs.map((l) => `[${l.type}] ${l.text}`).join("\n"); @@ -184,26 +151,7 @@ export class HtmlArtifact extends ArtifactElement { ${ this.logs.length > 0 - ? html` -
- - ${this.consoleOpen ? html`
` : ""} -
- ` + ? html`` : "" }
diff --git a/packages/web-ui/src/tools/artifacts/artifacts-tool-renderer.ts b/packages/web-ui/src/tools/artifacts/artifacts-tool-renderer.ts new file mode 100644 index 00000000..cd7abbdd --- /dev/null +++ b/packages/web-ui/src/tools/artifacts/artifacts-tool-renderer.ts @@ -0,0 +1,209 @@ +import { Diff, html, type TemplateResult } from "@mariozechner/mini-lit"; +import "@mariozechner/mini-lit/dist/CodeBlock.js"; +import type { ToolResultMessage } from "@mariozechner/pi-ai"; +import { FileCode2 } from "lucide"; +import "../../components/ConsoleBlock.js"; +import { i18n } from "../../utils/i18n.js"; +import { renderHeader } from "../renderer-registry.js"; +import type { ToolRenderer } from "../types.js"; +import type { ArtifactsParams } from "./artifacts.js"; + +// Helper to determine language for syntax highlighting +function getLanguageFromFilename(filename?: string): string { + if (!filename) return "text"; + const ext = filename.split(".").pop()?.toLowerCase(); + const languageMap: Record = { + js: "javascript", + jsx: "javascript", + ts: "typescript", + tsx: "typescript", + html: "html", + css: "css", + scss: "scss", + json: "json", + py: "python", + md: "markdown", + svg: "xml", + xml: "xml", + yaml: "yaml", + yml: "yaml", + sh: "bash", + bash: "bash", + sql: "sql", + java: "java", + c: "c", + cpp: "cpp", + cs: "csharp", + go: "go", + rs: "rust", + php: "php", + rb: "ruby", + swift: "swift", + kt: "kotlin", + r: "r", + }; + return languageMap[ext || ""] || "text"; +} + +export class ArtifactsToolRenderer implements ToolRenderer { + render( + params: ArtifactsParams | undefined, + result: ToolResultMessage | undefined, + isStreaming?: boolean, + ): TemplateResult { + const state = result ? (result.isError ? "error" : "complete") : "inprogress"; + + // Helper to get command labels + const getCommandLabels = (command: string): { streaming: string; complete: string } => { + const labels: Record = { + create: { streaming: i18n("Creating artifact"), complete: i18n("Created artifact") }, + update: { streaming: i18n("Updating artifact"), complete: i18n("Updated artifact") }, + rewrite: { streaming: i18n("Rewriting artifact"), complete: i18n("Rewrote artifact") }, + get: { streaming: i18n("Getting artifact"), complete: i18n("Got artifact") }, + delete: { streaming: i18n("Deleting artifact"), complete: i18n("Deleted artifact") }, + logs: { streaming: i18n("Getting logs"), complete: i18n("Got logs") }, + }; + return labels[command] || { streaming: i18n("Processing artifact"), complete: i18n("Processed artifact") }; + }; + + // Error handling + if (result?.isError) { + const command = params?.command; + const filename = params?.filename; + const labels = command + ? getCommandLabels(command) + : { streaming: i18n("Processing artifact"), complete: i18n("Processed artifact") }; + const headerText = filename ? `${labels.streaming} ${filename}` : labels.streaming; + + // For create/update/rewrite errors, show code block + console/error + if (command === "create" || command === "update" || command === "rewrite") { + const content = command === "update" ? params?.new_str || params?.old_str || "" : params?.content || ""; + + const isHtml = filename?.endsWith(".html"); + + return html` +
+ ${renderHeader(state, FileCode2, headerText)} + ${content ? html`` : ""} + ${ + isHtml + ? html`` + : html`
${result.output || i18n("An error occurred")}
` + } +
+ `; + } + + // For other errors, just show error message + return html` +
+ ${renderHeader(state, FileCode2, headerText)} +
${result.output || i18n("An error occurred")}
+
+ `; + } + + // Full params + result + if (result && params) { + const { command, filename, content } = params; + const labels = command + ? getCommandLabels(command) + : { streaming: i18n("Processing artifact"), complete: i18n("Processed artifact") }; + const headerText = filename ? `${labels.complete} ${filename}` : labels.complete; + + // GET command: show code block with file content + if (command === "get") { + const fileContent = result.output || i18n("(no output)"); + return html` +
+ ${renderHeader(state, FileCode2, headerText)} + +
+ `; + } + + // LOGS command: show console block + if (command === "logs") { + const logs = result.output || i18n("(no output)"); + return html` +
+ ${renderHeader(state, FileCode2, headerText)} + +
+ `; + } + + // CREATE/UPDATE/REWRITE: always show code block, + console block for .html files + if (command === "create" || command === "update" || command === "rewrite") { + const codeContent = content || ""; + const isHtml = filename?.endsWith(".html"); + const logs = result.output || ""; + + return html` +
+ ${renderHeader(state, FileCode2, headerText)} + ${codeContent ? html`` : ""} + ${isHtml && logs ? html`` : ""} +
+ `; + } + + // For DELETE, just show header + return html` +
+ ${renderHeader(state, FileCode2, headerText)} +
+ `; + } + + // Params only (streaming or waiting for result) + if (params) { + const { command, filename, content, old_str, new_str } = params; + + // If no command yet + if (!command) { + return renderHeader(state, FileCode2, i18n("Preparing artifact...")); + } + + const labels = getCommandLabels(command); + const headerText = filename ? `${labels.streaming} ${filename}` : labels.streaming; + + // Render based on command type + switch (command) { + case "create": + case "rewrite": + return html` +
+ ${renderHeader(state, FileCode2, headerText)} + ${ + content + ? html`` + : "" + } +
+ `; + + case "update": + return html` +
+ ${renderHeader(state, FileCode2, headerText)} + ${ + old_str !== undefined && new_str !== undefined + ? Diff({ oldText: old_str, newText: new_str, className: "mt-2" }) + : "" + } +
+ `; + + case "get": + case "delete": + case "logs": + default: + return renderHeader(state, FileCode2, headerText); + } + } + + // No params or result yet + return renderHeader(state, FileCode2, i18n("Preparing artifact...")); + } +} diff --git a/packages/web-ui/src/tools/artifacts/artifacts.ts b/packages/web-ui/src/tools/artifacts/artifacts.ts index cc10d5ac..0b195763 100644 --- a/packages/web-ui/src/tools/artifacts/artifacts.ts +++ b/packages/web-ui/src/tools/artifacts/artifacts.ts @@ -1,4 +1,4 @@ -import { Button, Diff, icon } from "@mariozechner/mini-lit"; +import { Button, icon } from "@mariozechner/mini-lit"; import { type AgentTool, type Message, StringEnum, type ToolCall, type ToolResultMessage } from "@mariozechner/pi-ai"; import { type Static, Type } from "@sinclair/typebox"; import { html, LitElement, type TemplateResult } from "lit"; @@ -7,14 +7,12 @@ import { createRef, type Ref, ref } from "lit/directives/ref.js"; import { X } from "lucide"; import type { Attachment } from "../../utils/attachment-utils.js"; import { i18n } from "../../utils/i18n.js"; -import type { ToolRenderer } from "../types.js"; import type { ArtifactElement } from "./ArtifactElement.js"; import { HtmlArtifact } from "./HtmlArtifact.js"; import { MarkdownArtifact } from "./MarkdownArtifact.js"; import { SvgArtifact } from "./SvgArtifact.js"; import { TextArtifact } from "./TextArtifact.js"; import "@mariozechner/mini-lit/dist/MarkdownBlock.js"; -import "@mariozechner/mini-lit/dist/CodeBlock.js"; // Simple artifact model export interface Artifact { @@ -38,13 +36,8 @@ const artifactsParamsSchema = Type.Object({ }); export type ArtifactsParams = Static; -// Minimal helper to render plain text outputs consistently -function plainOutput(text: string): TemplateResult { - return html`
${text}
`; -} - @customElement("artifacts-panel") -export class ArtifactsPanel extends LitElement implements ToolRenderer { +export class ArtifactsPanel extends LitElement { @state() private _artifacts = new Map(); @state() private _activeFilename: string | null = null; @@ -107,43 +100,6 @@ export class ArtifactsPanel extends LitElement implements ToolRenderer = { - js: "javascript", - jsx: "javascript", - ts: "typescript", - tsx: "typescript", - html: "html", - css: "css", - scss: "scss", - json: "json", - py: "python", - md: "markdown", - svg: "xml", - xml: "xml", - yaml: "yaml", - yml: "yaml", - sh: "bash", - bash: "bash", - sql: "sql", - java: "java", - c: "c", - cpp: "cpp", - cs: "csharp", - go: "go", - rs: "rust", - php: "php", - rb: "ruby", - swift: "swift", - kt: "kotlin", - r: "r", - }; - return languageMap[ext || ""] || "text"; - } - // Get or create artifact element private getOrCreateArtifactElement(filename: string, content: string, title: string): ArtifactElement { let element = this.artifactElements.get(filename); @@ -330,153 +286,6 @@ CRITICAL REMINDER FOR ALL ARTIFACTS: }; } - // ToolRenderer implementation - renderParams(params: ArtifactsParams, isStreaming?: boolean): TemplateResult { - if (isStreaming && !params.command) { - return html`
${i18n("Processing artifact...")}
`; - } - - let commandLabel = i18n("Processing"); - if (params.command) { - switch (params.command) { - case "create": - commandLabel = i18n("Create"); - break; - case "update": - commandLabel = i18n("Update"); - break; - case "rewrite": - commandLabel = i18n("Rewrite"); - break; - case "get": - commandLabel = i18n("Get"); - break; - case "delete": - commandLabel = i18n("Delete"); - break; - case "logs": - commandLabel = i18n("Get logs"); - break; - default: - commandLabel = params.command.charAt(0).toUpperCase() + params.command.slice(1); - } - } - const filename = params.filename || ""; - - switch (params.command) { - case "create": - return html` -
this.openArtifact(params.filename)} - > -
- ${i18n("Create")} - ${filename} -
- ${ - params.content - ? html`` - : "" - } -
- `; - case "update": - return html` -
this.openArtifact(params.filename)} - > -
- ${i18n("Update")} - ${filename} -
- ${ - params.old_str !== undefined && params.new_str !== undefined - ? Diff({ oldText: params.old_str, newText: params.new_str, className: "mt-2" }) - : "" - } -
- `; - case "rewrite": - return html` -
this.openArtifact(params.filename)} - > -
- ${i18n("Rewrite")} - ${filename} -
- ${ - params.content - ? html`` - : "" - } -
- `; - case "get": - return html` -
this.openArtifact(params.filename)} - > - ${i18n("Get")} - ${filename} -
- `; - case "delete": - return html` -
this.openArtifact(params.filename)} - > - ${i18n("Delete")} - ${filename} -
- `; - case "logs": - return html` -
this.openArtifact(params.filename)} - > - ${i18n("Get logs")} - ${filename} -
- `; - default: - // Fallback for any command not yet handled during streaming - return html` -
this.openArtifact(params.filename)} - > - ${commandLabel} - ${filename} -
- `; - } - } - - renderResult(params: ArtifactsParams, result: ToolResultMessage): TemplateResult { - // Make result clickable to focus the referenced file when applicable - const content = result.output || i18n("(no output)"); - return html` -
this.openArtifact(params.filename)}> - ${plainOutput(content)} -
- `; - } - // Re-apply artifacts by scanning a message list (optional utility) public async reconstructFromMessages(messages: Array): Promise { const toolCalls = new Map(); diff --git a/packages/web-ui/src/tools/artifacts/index.ts b/packages/web-ui/src/tools/artifacts/index.ts index 5b3d3010..e45580a1 100644 --- a/packages/web-ui/src/tools/artifacts/index.ts +++ b/packages/web-ui/src/tools/artifacts/index.ts @@ -1,5 +1,6 @@ export { ArtifactElement } from "./ArtifactElement.js"; export { type Artifact, ArtifactsPanel, type ArtifactsParams } from "./artifacts.js"; +export { ArtifactsToolRenderer } from "./artifacts-tool-renderer.js"; export { HtmlArtifact } from "./HtmlArtifact.js"; export { MarkdownArtifact } from "./MarkdownArtifact.js"; export { SvgArtifact } from "./SvgArtifact.js"; diff --git a/packages/web-ui/src/tools/index.ts b/packages/web-ui/src/tools/index.ts index 43945ede..107612c5 100644 --- a/packages/web-ui/src/tools/index.ts +++ b/packages/web-ui/src/tools/index.ts @@ -11,25 +11,19 @@ registerToolRenderer("bash", new BashRenderer()); const defaultRenderer = new DefaultRenderer(); /** - * Render tool call parameters + * Render tool - unified function that handles params, result, and streaming state */ -export function renderToolParams(toolName: string, params: any, isStreaming?: boolean): TemplateResult { +export function renderTool( + toolName: string, + params: any | undefined, + result: ToolResultMessage | undefined, + isStreaming?: boolean, +): TemplateResult { const renderer = getToolRenderer(toolName); if (renderer) { - return renderer.renderParams(params, isStreaming); + return renderer.render(params, result, isStreaming); } - return defaultRenderer.renderParams(params, isStreaming); -} - -/** - * Render tool result - */ -export function renderToolResult(toolName: string, params: any, result: ToolResultMessage): TemplateResult { - const renderer = getToolRenderer(toolName); - if (renderer) { - return renderer.renderResult(params, result); - } - return defaultRenderer.renderResult(params, result); + return defaultRenderer.render(params, result, isStreaming); } export { registerToolRenderer, getToolRenderer }; diff --git a/packages/web-ui/src/tools/javascript-repl.ts b/packages/web-ui/src/tools/javascript-repl.ts index 9525881f..02efc3b5 100644 --- a/packages/web-ui/src/tools/javascript-repl.ts +++ b/packages/web-ui/src/tools/javascript-repl.ts @@ -1,10 +1,11 @@ import { html, i18n, type TemplateResult } from "@mariozechner/mini-lit"; import type { AgentTool, ToolResultMessage } from "@mariozechner/pi-ai"; import { type Static, Type } from "@sinclair/typebox"; +import { Code } from "lucide"; import { type SandboxFile, SandboxIframe, type SandboxResult } from "../components/SandboxedIframe.js"; import type { Attachment } from "../utils/attachment-utils.js"; -import { registerToolRenderer } from "./renderer-registry.js"; +import { registerToolRenderer, renderHeader } from "./renderer-registry.js"; import type { ToolRenderer } from "./types.js"; // Execute JavaScript code with attachments using SandboxedIframe @@ -92,6 +93,7 @@ export type JavaScriptReplToolResult = { }; const javascriptReplSchema = Type.Object({ + title: Type.String({ description: "Brief title describing what the code snippet tries to achieve" }), code: Type.String({ description: "JavaScript code to execute" }), }); @@ -245,63 +247,76 @@ interface JavaScriptReplResult { } export const javascriptReplRenderer: ToolRenderer = { - renderParams(params: JavaScriptReplParams, isStreaming?: boolean): TemplateResult { - if (isStreaming && (!params.code || params.code.length === 0)) { - return html`
Writing JavaScript code...
`; + render( + params: JavaScriptReplParams | undefined, + result: ToolResultMessage | undefined, + isStreaming?: boolean, + ): TemplateResult { + // Determine status + const state = result ? (result.isError ? "error" : "complete") : isStreaming ? "inprogress" : "inprogress"; + + // With result: show params + result + if (result && params) { + const output = result.output || ""; + const files = result.details?.files || []; + + const attachments: Attachment[] = files.map((f, i) => { + // Decode base64 content for text files to show in overlay + let extractedText: string | undefined; + const isTextBased = + f.mimeType?.startsWith("text/") || + f.mimeType === "application/json" || + f.mimeType === "application/javascript" || + f.mimeType?.includes("xml"); + + if (isTextBased && f.contentBase64) { + try { + extractedText = atob(f.contentBase64); + } catch (e) { + console.warn("Failed to decode base64 content for", f.fileName); + } + } + + return { + id: `repl-${Date.now()}-${i}`, + type: f.mimeType?.startsWith("image/") ? "image" : "document", + fileName: f.fileName || `file-${i}`, + mimeType: f.mimeType || "application/octet-stream", + size: f.size ?? 0, + content: f.contentBase64, + preview: f.mimeType?.startsWith("image/") ? f.contentBase64 : undefined, + extractedText, + }; + }); + + return html` +
+ ${renderHeader(state, Code, i18n("Executing JavaScript"))} + + ${output ? html`` : ""} + ${ + attachments.length + ? html`
+ ${attachments.map((att) => html``)} +
` + : "" + } +
+ `; } - return html` -
${i18n("Executing JavaScript")}
- - `; - }, + // Just params (streaming or waiting for result) + if (params) { + return html` +
+ ${renderHeader(state, Code, i18n("Executing JavaScript"))} + ${params.code ? html`` : ""} +
+ `; + } - renderResult(_params: JavaScriptReplParams, result: ToolResultMessage): TemplateResult { - // Console output is in the main output field, files are in details - const output = result.output || ""; - const files = result.details?.files || []; - - const attachments: Attachment[] = files.map((f, i) => { - // Decode base64 content for text files to show in overlay - let extractedText: string | undefined; - const isTextBased = - f.mimeType?.startsWith("text/") || - f.mimeType === "application/json" || - f.mimeType === "application/javascript" || - f.mimeType?.includes("xml"); - - if (isTextBased && f.contentBase64) { - try { - extractedText = atob(f.contentBase64); - } catch (e) { - console.warn("Failed to decode base64 content for", f.fileName); - } - } - - return { - id: `repl-${Date.now()}-${i}`, - type: f.mimeType?.startsWith("image/") ? "image" : "document", - fileName: f.fileName || `file-${i}`, - mimeType: f.mimeType || "application/octet-stream", - size: f.size ?? 0, - content: f.contentBase64, - preview: f.mimeType?.startsWith("image/") ? f.contentBase64 : undefined, - extractedText, - }; - }); - - return html` -
- ${output ? html`` : ""} - ${ - attachments.length - ? html`
- ${attachments.map((att) => html``)} -
` - : "" - } -
- `; + // No params or result yet + return renderHeader(state, Code, i18n("Preparing JavaScript...")); }, }; diff --git a/packages/web-ui/src/tools/renderer-registry.ts b/packages/web-ui/src/tools/renderer-registry.ts index c6e3b71b..df28a2b9 100644 --- a/packages/web-ui/src/tools/renderer-registry.ts +++ b/packages/web-ui/src/tools/renderer-registry.ts @@ -1,3 +1,5 @@ +import { html, icon, type TemplateResult } from "@mariozechner/mini-lit"; +import { Loader } from "lucide"; import type { ToolRenderer } from "./types.js"; // Registry of tool renderers @@ -16,3 +18,39 @@ export function registerToolRenderer(toolName: string, renderer: ToolRenderer): export function getToolRenderer(toolName: string): ToolRenderer | undefined { return toolRenderers.get(toolName); } + +/** + * Helper to render a header for tool renderers + * Shows icon on left when complete/error, spinner on right when in progress + */ +export function renderHeader(state: "inprogress" | "complete" | "error", toolIcon: any, text: string): TemplateResult { + const statusIcon = (iconComponent: any, color: string) => + html`${icon(iconComponent, "sm")}`; + + switch (state) { + case "inprogress": + return html` +
+
+ ${statusIcon(toolIcon, "text-foreground")} + ${text} +
+ ${statusIcon(Loader, "text-foreground animate-spin")} +
+ `; + case "complete": + return html` +
+ ${statusIcon(toolIcon, "text-green-600 dark:text-green-500")} + ${text} +
+ `; + case "error": + return html` +
+ ${statusIcon(toolIcon, "text-destructive")} + ${text} +
+ `; + } +} diff --git a/packages/web-ui/src/tools/renderers/BashRenderer.ts b/packages/web-ui/src/tools/renderers/BashRenderer.ts index ba9ae330..413e23c5 100644 --- a/packages/web-ui/src/tools/renderers/BashRenderer.ts +++ b/packages/web-ui/src/tools/renderers/BashRenderer.ts @@ -1,6 +1,8 @@ import { html, type TemplateResult } from "@mariozechner/mini-lit"; import type { ToolResultMessage } from "@mariozechner/pi-ai"; +import { SquareTerminal } from "lucide"; import { i18n } from "../../utils/i18n.js"; +import { renderHeader } from "../renderer-registry.js"; import type { ToolRenderer } from "../types.js"; interface BashParams { @@ -9,37 +11,32 @@ interface BashParams { // Bash tool has undefined details (only uses output) export class BashRenderer implements ToolRenderer { - renderParams(params: BashParams, isStreaming?: boolean): TemplateResult { - if (isStreaming && (!params.command || params.command.length === 0)) { - return html`
${i18n("Writing command...")}
`; - } + render(params: BashParams | undefined, result: ToolResultMessage | undefined): TemplateResult { + const state = result ? (result.isError ? "error" : "complete") : "inprogress"; - return html` -
- ${i18n("Running command:")} - ${params.command} -
- `; - } - - renderResult(_params: BashParams, result: ToolResultMessage): TemplateResult { - const output = result.output || ""; - const isError = result.isError === true; - - if (isError) { + // With result: show command + output + if (result && params?.command) { + const output = result.output || ""; + const combined = output ? `> ${params.command}\n\n${output}` : `> ${params.command}`; return html` -
-
${i18n("Command failed:")}
-
${output}
+
+ ${renderHeader(state, SquareTerminal, i18n("Running command..."))} +
`; } - // Display the command output - return html` -
-
${output}
-
- `; + // Just params (streaming or waiting) + if (params?.command) { + return html` +
+ ${renderHeader(state, SquareTerminal, i18n("Running command..."))} + ${params.command}`}> +
+ `; + } + + // No params yet + return renderHeader(state, SquareTerminal, i18n("Waiting for command...")); } } diff --git a/packages/web-ui/src/tools/renderers/CalculateRenderer.ts b/packages/web-ui/src/tools/renderers/CalculateRenderer.ts index fb4eec05..cf630862 100644 --- a/packages/web-ui/src/tools/renderers/CalculateRenderer.ts +++ b/packages/web-ui/src/tools/renderers/CalculateRenderer.ts @@ -1,6 +1,8 @@ import { html, type TemplateResult } from "@mariozechner/mini-lit"; import type { ToolResultMessage } from "@mariozechner/pi-ai"; +import { Calculator } from "lucide"; import { i18n } from "../../utils/i18n.js"; +import { renderHeader } from "../renderer-registry.js"; import type { ToolRenderer } from "../types.js"; interface CalculateParams { @@ -9,41 +11,38 @@ interface CalculateParams { // Calculate tool has undefined details (only uses output) export class CalculateRenderer implements ToolRenderer { - renderParams(params: CalculateParams, isStreaming?: boolean): TemplateResult { - if (isStreaming && !params.expression) { - return html`
${i18n("Writing expression...")}
`; + render(params: CalculateParams | undefined, result: ToolResultMessage | undefined): TemplateResult { + const state = result ? (result.isError ? "error" : "complete") : "inprogress"; + + // Full params + full result + if (result && params?.expression) { + const output = result.output || ""; + + // Error: show expression in header, error below + if (result.isError) { + return html` +
+ ${renderHeader(state, Calculator, params.expression)} +
${output}
+
+ `; + } + + // Success: show expression = result in header + return renderHeader(state, Calculator, `${params.expression} = ${output}`); } - return html` -
- ${i18n("Calculating")} - ${params.expression} -
- `; - } - - renderResult(_params: CalculateParams, result: ToolResultMessage): TemplateResult { - // Parse the output to make it look nicer - const output = result.output || ""; - const isError = result.isError === true; - - if (isError) { - return html`
${output}
`; + // Full params, no result: just show header with expression in it + if (params?.expression) { + return renderHeader(state, Calculator, `${i18n("Calculating")} ${params.expression}`); } - // Try to split on = to show expression and result separately - const parts = output.split(" = "); - if (parts.length === 2) { - return html` -
- ${parts[0]} - = - ${parts[1]} -
- `; + // Partial params (empty expression), no result + if (params && !params.expression) { + return renderHeader(state, Calculator, i18n("Writing expression...")); } - // Fallback to showing the whole output - return html`
${output}
`; + // No params, no result + return renderHeader(state, Calculator, i18n("Waiting for expression...")); } } diff --git a/packages/web-ui/src/tools/renderers/DefaultRenderer.ts b/packages/web-ui/src/tools/renderers/DefaultRenderer.ts index dd15e028..8bed4dca 100644 --- a/packages/web-ui/src/tools/renderers/DefaultRenderer.ts +++ b/packages/web-ui/src/tools/renderers/DefaultRenderer.ts @@ -4,33 +4,34 @@ import { i18n } from "../../utils/i18n.js"; import type { ToolRenderer } from "../types.js"; export class DefaultRenderer implements ToolRenderer { - renderParams(params: any, isStreaming?: boolean): TemplateResult { - let text: string; - let isJson = false; + render(params: any | undefined, result: ToolResultMessage | undefined, isStreaming?: boolean): TemplateResult { + // Show result if available + if (result) { + const text = result.output || i18n("(no output)"); + return html`
${text}
`; + } - try { - text = JSON.stringify(JSON.parse(params), null, 2); - isJson = true; - } catch { + // Show params + if (params) { + let text: string; try { - text = JSON.stringify(params, null, 2); - isJson = true; + text = JSON.stringify(JSON.parse(params), null, 2); } catch { - text = String(params); + try { + text = JSON.stringify(params, null, 2); + } catch { + text = String(params); + } } + + if (isStreaming && (!text || text === "{}" || text === "null")) { + return html`
${i18n("Preparing tool parameters...")}
`; + } + + return html``; } - if (isStreaming && (!text || text === "{}" || text === "null")) { - return html`
${i18n("Preparing tool parameters...")}
`; - } - - return html``; - } - - renderResult(_params: any, result: ToolResultMessage): TemplateResult { - // Just show the output field - that's what was sent to the LLM - const text = result.output || i18n("(no output)"); - - return html`
${text}
`; + // No params or result yet + return html`
${i18n("Preparing tool...")}
`; } } diff --git a/packages/web-ui/src/tools/renderers/GetCurrentTimeRenderer.ts b/packages/web-ui/src/tools/renderers/GetCurrentTimeRenderer.ts index db2d629b..20fd4bf1 100644 --- a/packages/web-ui/src/tools/renderers/GetCurrentTimeRenderer.ts +++ b/packages/web-ui/src/tools/renderers/GetCurrentTimeRenderer.ts @@ -1,6 +1,8 @@ import { html, type TemplateResult } from "@mariozechner/mini-lit"; import type { ToolResultMessage } from "@mariozechner/pi-ai"; +import { Clock } from "lucide"; import { i18n } from "../../utils/i18n.js"; +import { renderHeader } from "../renderer-registry.js"; import type { ToolRenderer } from "../types.js"; interface GetCurrentTimeParams { @@ -9,31 +11,59 @@ interface GetCurrentTimeParams { // GetCurrentTime tool has undefined details (only uses output) export class GetCurrentTimeRenderer implements ToolRenderer { - renderParams(params: GetCurrentTimeParams, isStreaming?: boolean): TemplateResult { - if (params.timezone) { - return html` -
- ${i18n("Getting current time in")} - ${params.timezone} -
- `; - } - return html` -
- ${i18n("Getting current date and time")}${isStreaming ? "..." : ""} -
- `; - } + render(params: GetCurrentTimeParams | undefined, result: ToolResultMessage | undefined): TemplateResult { + const state = result ? (result.isError ? "error" : "complete") : "inprogress"; - renderResult(_params: GetCurrentTimeParams, result: ToolResultMessage): TemplateResult { - const output = result.output || ""; - const isError = result.isError === true; + // Full params + full result + if (result && params) { + const output = result.output || ""; + const headerText = params.timezone + ? `${i18n("Getting current time in")} ${params.timezone}` + : i18n("Getting current date and time"); - if (isError) { - return html`
${output}
`; + // Error: show header, error below + if (result.isError) { + return html` +
+ ${renderHeader(state, Clock, headerText)} +
${output}
+
+ `; + } + + // Success: show time in header + return renderHeader(state, Clock, `${headerText}: ${output}`); } - // Display the date/time result - return html`
${output}
`; + // Full result, no params + if (result) { + const output = result.output || ""; + + // Error: show header, error below + if (result.isError) { + return html` +
+ ${renderHeader(state, Clock, i18n("Getting current date and time"))} +
${output}
+
+ `; + } + + // Success: show time in header + return renderHeader(state, Clock, `${i18n("Getting current date and time")}: ${output}`); + } + + // Full params, no result: show timezone info in header + if (params?.timezone) { + return renderHeader(state, Clock, `${i18n("Getting current time in")} ${params.timezone}`); + } + + // Partial params (no timezone) or empty params, no result + if (params) { + return renderHeader(state, Clock, i18n("Getting current date and time")); + } + + // No params, no result + return renderHeader(state, Clock, i18n("Getting time...")); } } diff --git a/packages/web-ui/src/tools/types.ts b/packages/web-ui/src/tools/types.ts index 6db8e8ae..c7d8ad29 100644 --- a/packages/web-ui/src/tools/types.ts +++ b/packages/web-ui/src/tools/types.ts @@ -2,6 +2,9 @@ import type { ToolResultMessage } from "@mariozechner/pi-ai"; import type { TemplateResult } from "lit"; export interface ToolRenderer { - renderParams(params: TParams, isStreaming?: boolean): TemplateResult; - renderResult(params: TParams, result: ToolResultMessage): TemplateResult; + render( + params: TParams | undefined, + result: ToolResultMessage | undefined, + isStreaming?: boolean, + ): TemplateResult; } diff --git a/packages/web-ui/src/utils/i18n.ts b/packages/web-ui/src/utils/i18n.ts index b7588935..ec31839d 100644 --- a/packages/web-ui/src/utils/i18n.ts +++ b/packages/web-ui/src/utils/i18n.ts @@ -59,11 +59,13 @@ declare module "@mariozechner/mini-lit" { "Preparing tool parameters...": string; "(no output)": string; "Writing expression...": string; + "Waiting for expression...": string; Calculating: string; "Getting current time in": string; "Getting current date and time": string; + "Waiting for command...": string; "Writing command...": string; - "Running command:": string; + "Running command...": string; "Command failed:": string; "Enter Auth Token": string; "Please enter your auth token.": string; @@ -78,8 +80,32 @@ declare module "@mariozechner/mini-lit" { "JavaScript code to execute": string; "Writing JavaScript code...": string; "Executing JavaScript": string; + "Preparing JavaScript...": string; + "Preparing command...": string; + "Preparing calculation...": string; + "Preparing tool...": string; + "Getting time...": string; // Artifacts strings "Processing artifact...": string; + "Preparing artifact...": string; + "Processing artifact": string; + "Processed artifact": string; + "Creating artifact": string; + "Created artifact": string; + "Updating artifact": string; + "Updated artifact": string; + "Rewriting artifact": string; + "Rewrote artifact": string; + "Getting artifact": string; + "Got artifact": string; + "Deleting artifact": string; + "Deleted artifact": string; + "Getting logs": string; + "Got logs": string; + "An error occurred": string; + "Copy logs": string; + "Autoscroll enabled": string; + "Autoscroll disabled": string; Processing: string; Create: string; Rewrite: string; @@ -199,13 +225,15 @@ export const translations = { "No session set": "No session set", "Preparing tool parameters...": "Preparing tool parameters...", "(no output)": "(no output)", + "Waiting for expression...": "Waiting for expression...", "Writing expression...": "Writing expression...", Calculating: "Calculating", "Getting current time in": "Getting current time in", "Getting current date and time": "Getting current date and time", + "Waiting for command...": "Waiting for command...", "Writing command...": "Writing command...", - "Running command:": "Running command:", - "Command failed:": "Command failed:", + "Running command...": "Running command...", + "Command failed": "Command failed", "Enter Auth Token": "Enter Auth Token", "Please enter your auth token.": "Please enter your auth token.", "Auth token is required for proxy transport": "Auth token is required for proxy transport", @@ -219,8 +247,32 @@ export const translations = { "JavaScript code to execute": "JavaScript code to execute", "Writing JavaScript code...": "Writing JavaScript code...", "Executing JavaScript": "Executing JavaScript", + "Preparing JavaScript...": "Preparing JavaScript...", + "Preparing command...": "Preparing command...", + "Preparing calculation...": "Preparing calculation...", + "Preparing tool...": "Preparing tool...", + "Getting time...": "Getting time...", // Artifacts strings "Processing artifact...": "Processing artifact...", + "Preparing artifact...": "Preparing artifact...", + "Processing artifact": "Processing artifact", + "Processed artifact": "Processed artifact", + "Creating artifact": "Creating artifact", + "Created artifact": "Created artifact", + "Updating artifact": "Updating artifact", + "Updated artifact": "Updated artifact", + "Rewriting artifact": "Rewriting artifact", + "Rewrote artifact": "Rewrote artifact", + "Getting artifact": "Getting artifact", + "Got artifact": "Got artifact", + "Deleting artifact": "Deleting artifact", + "Deleted artifact": "Deleted artifact", + "Getting logs": "Getting logs", + "Got logs": "Got logs", + "An error occurred": "An error occurred", + "Copy logs": "Copy logs", + "Autoscroll enabled": "Autoscroll enabled", + "Autoscroll disabled": "Autoscroll disabled", Processing: "Processing", Create: "Create", Rewrite: "Rewrite", @@ -281,6 +333,7 @@ export const translations = { tokens: "tokens", Delete: "Delete", "Drop files here": "Drop files here", + "Command failed:": "Command failed:", }, de: { ...defaultGerman, @@ -342,13 +395,15 @@ export const translations = { "No session set": "Keine Sitzung gesetzt", "Preparing tool parameters...": "Bereite Tool-Parameter vor...", "(no output)": "(keine Ausgabe)", + "Waiting for expression...": "Warte auf Ausdruck", "Writing expression...": "Schreibe Ausdruck...", Calculating: "Berechne", "Getting current time in": "Hole aktuelle Zeit in", "Getting current date and time": "Hole aktuelles Datum und Uhrzeit", + "Waiting for command...": "Warte auf Befehl...", "Writing command...": "Schreibe Befehl...", - "Running command:": "Führe Befehl aus:", - "Command failed:": "Befehl fehlgeschlagen:", + "Running command...": "Führe Befehl aus...", + "Command failed": "Befehl fehlgeschlagen", "Enter Auth Token": "Auth-Token eingeben", "Please enter your auth token.": "Bitte geben Sie Ihr Auth-Token ein.", "Auth token is required for proxy transport": "Auth-Token ist für Proxy-Transport erforderlich", @@ -362,8 +417,32 @@ export const translations = { "JavaScript code to execute": "Auszuführender JavaScript-Code", "Writing JavaScript code...": "Schreibe JavaScript-Code...", "Executing JavaScript": "Führe JavaScript aus", + "Preparing JavaScript...": "Bereite JavaScript vor...", + "Preparing command...": "Bereite Befehl vor...", + "Preparing calculation...": "Bereite Berechnung vor...", + "Preparing tool...": "Bereite Tool vor...", + "Getting time...": "Hole Zeit...", // Artifacts strings "Processing artifact...": "Verarbeite Artefakt...", + "Preparing artifact...": "Bereite Artefakt vor...", + "Processing artifact": "Verarbeite Artefakt", + "Processed artifact": "Artefakt verarbeitet", + "Creating artifact": "Erstelle Artefakt", + "Created artifact": "Artefakt erstellt", + "Updating artifact": "Aktualisiere Artefakt", + "Updated artifact": "Artefakt aktualisiert", + "Rewriting artifact": "Überschreibe Artefakt", + "Rewrote artifact": "Artefakt überschrieben", + "Getting artifact": "Hole Artefakt", + "Got artifact": "Artefakt geholt", + "Deleting artifact": "Lösche Artefakt", + "Deleted artifact": "Artefakt gelöscht", + "Getting logs": "Hole Logs", + "Got logs": "Logs geholt", + "An error occurred": "Ein Fehler ist aufgetreten", + "Copy logs": "Logs kopieren", + "Autoscroll enabled": "Automatisches Scrollen aktiviert", + "Autoscroll disabled": "Automatisches Scrollen deaktiviert", Processing: "Verarbeitung", Create: "Erstellen", Rewrite: "Überschreiben", @@ -424,6 +503,7 @@ export const translations = { tokens: "Tokens", Delete: "Löschen", "Drop files here": "Dateien hier ablegen", + "Command failed:": "Befehl fehlgeschlagen:", }, };