mac: bundle web chat assets

This commit is contained in:
Peter Steinberger
2025-12-06 05:01:28 +01:00
parent 15cdeeddaf
commit 42d843297d
315 changed files with 16618 additions and 20 deletions

View File

@@ -0,0 +1,9 @@
import { LitElement, type TemplateResult } from "lit";
export declare abstract class ArtifactElement extends LitElement {
filename: string;
protected createRenderRoot(): HTMLElement | DocumentFragment;
abstract get content(): string;
abstract set content(value: string);
abstract getHeaderButtons(): TemplateResult | HTMLElement;
}
//# sourceMappingURL=ArtifactElement.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ArtifactElement.d.ts","sourceRoot":"","sources":["../../../src/tools/artifacts/ArtifactElement.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAEtD,8BAAsB,eAAgB,SAAQ,UAAU;IAChD,QAAQ,SAAM;cAEF,gBAAgB,IAAI,WAAW,GAAG,gBAAgB;IAIrE,aAAoB,OAAO,IAAI,MAAM,CAAC;IACtC,aAAoB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE;IAE3C,QAAQ,CAAC,gBAAgB,IAAI,cAAc,GAAG,WAAW;CACzD"}

View File

@@ -0,0 +1,11 @@
import { LitElement } from "lit";
export class ArtifactElement extends LitElement {
constructor() {
super(...arguments);
this.filename = "";
}
createRenderRoot() {
return this; // light DOM for shared styles
}
}
//# sourceMappingURL=ArtifactElement.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ArtifactElement.js","sourceRoot":"","sources":["../../../src/tools/artifacts/ArtifactElement.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAuB,MAAM,KAAK,CAAC;AAEtD,MAAM,OAAgB,eAAgB,SAAQ,UAAU;IAAxD;;QACQ,aAAQ,GAAG,EAAE,CAAC;IAUtB,CAAC;IARmB,gBAAgB;QAClC,OAAO,IAAI,CAAC,CAAC,8BAA8B;IAC5C,CAAC;CAMD"}

View File

@@ -0,0 +1,4 @@
import { type TemplateResult } from "lit";
import type { ArtifactsPanel } from "./artifacts.js";
export declare function ArtifactPill(filename: string, artifactsPanel?: ArtifactsPanel): TemplateResult;
//# sourceMappingURL=ArtifactPill.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ArtifactPill.d.ts","sourceRoot":"","sources":["../../../src/tools/artifacts/ArtifactPill.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAEhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,cAAc,GAAG,cAAc,CAoB9F"}

View File

@@ -0,0 +1,23 @@
import { icon } from "@mariozechner/mini-lit";
import { html } from "lit";
import { FileCode2 } from "lucide";
export function ArtifactPill(filename, artifactsPanel) {
const handleClick = (e) => {
if (!artifactsPanel)
return;
e.preventDefault();
e.stopPropagation();
// openArtifact will show the artifact and call onOpen() to open the panel if needed
artifactsPanel.openArtifact(filename);
};
return html `
<span
class="inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-muted/50 border border-border rounded ${artifactsPanel ? "cursor-pointer hover:bg-muted transition-colors" : ""}"
@click=${artifactsPanel ? handleClick : null}
>
${icon(FileCode2, "sm")}
<span class="text-foreground">${filename}</span>
</span>
`;
}
//# sourceMappingURL=ArtifactPill.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ArtifactPill.js","sourceRoot":"","sources":["../../../src/tools/artifacts/ArtifactPill.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAuB,MAAM,KAAK,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAGnC,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,cAA+B;IAC7E,MAAM,WAAW,GAAG,CAAC,CAAQ,EAAE,EAAE;QAChC,IAAI,CAAC,cAAc;YAAE,OAAO;QAC5B,CAAC,CAAC,cAAc,EAAE,CAAC;QACnB,CAAC,CAAC,eAAe,EAAE,CAAC;QACpB,oFAAoF;QACpF,cAAc,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IACvC,CAAC,CAAC;IAEF,OAAO,IAAI,CAAA;;wGAGR,cAAc,CAAC,CAAC,CAAC,iDAAiD,CAAC,CAAC,CAAC,EACtE;YACS,cAAc,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI;;KAE1C,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC;mCACS,QAAQ;;EAEzC,CAAC;AACH,CAAC"}

View File

@@ -0,0 +1,18 @@
import "@mariozechner/mini-lit/dist/CopyButton.js";
import { LitElement, type TemplateResult } from "lit";
interface LogEntry {
type: "log" | "error";
text: string;
}
export declare class Console extends LitElement {
logs: LogEntry[];
private expanded;
private autoscroll;
private logsContainerRef;
protected createRenderRoot(): this;
updated(): void;
private getLogsText;
render(): TemplateResult;
}
export {};
//# sourceMappingURL=Console.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"Console.d.ts","sourceRoot":"","sources":["../../../src/tools/artifacts/Console.ts"],"names":[],"mappings":"AACA,OAAO,2CAA2C,CAAC;AACnD,OAAO,EAAQ,UAAU,EAAE,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAO5D,UAAU,QAAQ;IACjB,IAAI,EAAE,KAAK,GAAG,OAAO,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACb;AAED,qBACa,OAAQ,SAAQ,UAAU;IACN,IAAI,EAAE,QAAQ,EAAE,CAAM;IAC7C,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,gBAAgB,CAAoC;IAE5D,SAAS,CAAC,gBAAgB;IAIjB,OAAO;IAOhB,OAAO,CAAC,WAAW;IAIV,MAAM,IAAI,cAAc;CAwDjC"}

View File

@@ -0,0 +1,95 @@
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { icon } from "@mariozechner/mini-lit";
import "@mariozechner/mini-lit/dist/CopyButton.js";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { createRef, 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";
let Console = class Console extends LitElement {
constructor() {
super(...arguments);
this.logs = [];
this.expanded = false;
this.autoscroll = true;
this.logsContainerRef = createRef();
}
createRenderRoot() {
return this; // light DOM
}
updated() {
// Autoscroll to bottom when new logs arrive
if (this.autoscroll && this.expanded && this.logsContainerRef.value) {
this.logsContainerRef.value.scrollTop = this.logsContainerRef.value.scrollHeight;
}
}
getLogsText() {
return this.logs.map((l) => `[${l.type}] ${l.text}`).join("\n");
}
render() {
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 `
<div class="border-t border-border p-2">
<div class="flex items-center gap-2 w-full">
<button
@click=${() => {
this.expanded = !this.expanded;
}}
class="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors flex-1 text-left"
>
${icon(this.expanded ? ChevronDown : ChevronRight, "sm")}
<span>${summary}</span>
</button>
${this.expanded
? html `
<button
@click=${() => {
this.autoscroll = !this.autoscroll;
}}
class="p-1 rounded transition-colors ${this.autoscroll ? "bg-accent text-accent-foreground" : "hover:bg-muted"}"
title=${this.autoscroll ? i18n("Autoscroll enabled") : i18n("Autoscroll disabled")}
>
${icon(this.autoscroll ? ChevronsDown : Lock, "sm")}
</button>
<copy-button .text=${this.getLogsText()} title=${i18n("Copy logs")} .showText=${false} class="!bg-transparent hover:!bg-accent"></copy-button>
`
: ""}
</div>
${this.expanded
? html `
<div class="max-h-48 overflow-y-auto space-y-1 mt-2" ${ref(this.logsContainerRef)}>
${repeat(this.logs, (_log, index) => index, (log) => html `
<div class="text-xs font-mono ${log.type === "error" ? "text-destructive" : "text-muted-foreground"}">
[${log.type}] ${log.text}
</div>
`)}
</div>
`
: ""}
</div>
`;
}
};
__decorate([
property({ attribute: false })
], Console.prototype, "logs", void 0);
__decorate([
state()
], Console.prototype, "expanded", void 0);
__decorate([
state()
], Console.prototype, "autoscroll", void 0);
Console = __decorate([
customElement("artifact-console")
], Console);
export { Console };
//# sourceMappingURL=Console.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"Console.js","sourceRoot":"","sources":["../../../src/tools/artifacts/Console.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,22 @@
import { type TemplateResult } from "lit";
import { ArtifactElement } from "./ArtifactElement.js";
export declare class DocxArtifact extends ArtifactElement {
private _content;
private error;
get content(): string;
set content(value: string);
protected createRenderRoot(): HTMLElement | DocumentFragment;
connectedCallback(): void;
private base64ToArrayBuffer;
private decodeBase64;
getHeaderButtons(): TemplateResult<1>;
updated(changedProperties: Map<string, any>): Promise<void>;
private renderDocx;
render(): TemplateResult;
}
declare global {
interface HTMLElementTagNameMap {
"docx-artifact": DocxArtifact;
}
}
//# sourceMappingURL=DocxArtifact.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DocxArtifact.d.ts","sourceRoot":"","sources":["../../../src/tools/artifacts/DocxArtifact.ts"],"names":[],"mappings":"AAEA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAGhD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,qBACa,YAAa,SAAQ,eAAe;IACpB,OAAO,CAAC,QAAQ,CAAM;IACzC,OAAO,CAAC,KAAK,CAAuB;IAE7C,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,MAAM,EAIxB;cAEkB,gBAAgB,IAAI,WAAW,GAAG,gBAAgB;IAI5D,iBAAiB,IAAI,IAAI;IAMlC,OAAO,CAAC,mBAAmB;IAkB3B,OAAO,CAAC,YAAY;IAiBb,gBAAgB;IAaR,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC;YAQ5C,UAAU;IAoGf,MAAM,IAAI,cAAc;CAkBjC;AAED,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,eAAe,EAAE,YAAY,CAAC;KAC9B;CACD"}

View File

@@ -0,0 +1,208 @@
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { DownloadButton } from "@mariozechner/mini-lit/dist/DownloadButton.js";
import { renderAsync } from "docx-preview";
import { html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { i18n } from "../../utils/i18n.js";
import { ArtifactElement } from "./ArtifactElement.js";
let DocxArtifact = class DocxArtifact extends ArtifactElement {
constructor() {
super(...arguments);
this._content = "";
this.error = null;
}
get content() {
return this._content;
}
set content(value) {
this._content = value;
this.error = null;
this.requestUpdate();
}
createRenderRoot() {
return this;
}
connectedCallback() {
super.connectedCallback();
this.style.display = "block";
this.style.height = "100%";
}
base64ToArrayBuffer(base64) {
// Remove data URL prefix if present
let base64Data = base64;
if (base64.startsWith("data:")) {
const base64Match = base64.match(/base64,(.+)/);
if (base64Match) {
base64Data = base64Match[1];
}
}
const binaryString = atob(base64Data);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}
decodeBase64() {
let base64Data = this._content;
if (this._content.startsWith("data:")) {
const base64Match = this._content.match(/base64,(.+)/);
if (base64Match) {
base64Data = base64Match[1];
}
}
const binaryString = atob(base64Data);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
}
getHeaderButtons() {
return html `
<div class="flex items-center gap-1">
${DownloadButton({
content: this.decodeBase64(),
filename: this.filename,
mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
title: i18n("Download"),
})}
</div>
`;
}
async updated(changedProperties) {
super.updated(changedProperties);
if (changedProperties.has("_content") && this._content && !this.error) {
await this.renderDocx();
}
}
async renderDocx() {
const container = this.querySelector("#docx-container");
if (!container || !this._content)
return;
try {
const arrayBuffer = this.base64ToArrayBuffer(this._content);
// Clear container first
container.innerHTML = "";
// Create a wrapper div for the document
const wrapper = document.createElement("div");
wrapper.className = "docx-wrapper-custom";
container.appendChild(wrapper);
// Render the DOCX file into the wrapper
await renderAsync(arrayBuffer, wrapper, undefined, {
className: "docx",
inWrapper: true,
ignoreWidth: true,
ignoreHeight: false,
ignoreFonts: false,
breakPages: true,
ignoreLastRenderedPageBreak: true,
experimental: false,
trimXmlDeclaration: true,
useBase64URL: false,
renderHeaders: true,
renderFooters: true,
renderFootnotes: true,
renderEndnotes: true,
});
// Apply custom styles to match theme and fix sizing
const style = document.createElement("style");
style.textContent = `
#docx-container {
padding: 0;
}
#docx-container .docx-wrapper-custom {
max-width: 100%;
overflow-x: auto;
}
#docx-container .docx-wrapper {
max-width: 100% !important;
margin: 0 !important;
background: transparent !important;
padding: 0em !important;
}
#docx-container .docx-wrapper > section.docx {
box-shadow: none !important;
border: none !important;
border-radius: 0 !important;
margin: 0 !important;
padding: 2em !important;
background: white !important;
color: black !important;
max-width: 100% !important;
width: 100% !important;
min-width: 0 !important;
overflow-x: auto !important;
}
/* Fix tables and wide content */
#docx-container table {
max-width: 100% !important;
width: auto !important;
overflow-x: auto !important;
display: block !important;
}
#docx-container img {
max-width: 100% !important;
height: auto !important;
}
/* Fix paragraphs and text */
#docx-container p,
#docx-container span,
#docx-container div {
max-width: 100% !important;
word-wrap: break-word !important;
overflow-wrap: break-word !important;
}
/* Hide page breaks in web view */
#docx-container .docx-page-break {
display: none !important;
}
`;
container.appendChild(style);
}
catch (error) {
console.error("Error rendering DOCX:", error);
this.error = error?.message || i18n("Failed to load document");
}
}
render() {
if (this.error) {
return html `
<div class="h-full flex items-center justify-center bg-background p-4">
<div class="bg-destructive/10 border border-destructive text-destructive p-4 rounded-lg max-w-2xl">
<div class="font-medium mb-1">${i18n("Error loading document")}</div>
<div class="text-sm opacity-90">${this.error}</div>
</div>
</div>
`;
}
return html `
<div class="h-full flex flex-col bg-background overflow-auto">
<div id="docx-container" class="flex-1 overflow-auto"></div>
</div>
`;
}
};
__decorate([
property({ type: String })
], DocxArtifact.prototype, "_content", void 0);
__decorate([
state()
], DocxArtifact.prototype, "error", void 0);
DocxArtifact = __decorate([
customElement("docx-artifact")
], DocxArtifact);
export { DocxArtifact };
//# sourceMappingURL=DocxArtifact.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DocxArtifact.js","sourceRoot":"","sources":["../../../src/tools/artifacts/DocxArtifact.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,24 @@
import { type TemplateResult } from "lit";
import { ArtifactElement } from "./ArtifactElement.js";
export declare class ExcelArtifact extends ArtifactElement {
private _content;
private error;
get content(): string;
set content(value: string);
protected createRenderRoot(): HTMLElement | DocumentFragment;
connectedCallback(): void;
private base64ToArrayBuffer;
private decodeBase64;
private getMimeType;
getHeaderButtons(): TemplateResult<1>;
updated(changedProperties: Map<string, any>): Promise<void>;
private renderExcel;
private renderExcelSheet;
render(): TemplateResult;
}
declare global {
interface HTMLElementTagNameMap {
"excel-artifact": ExcelArtifact;
}
}
//# sourceMappingURL=ExcelArtifact.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExcelArtifact.d.ts","sourceRoot":"","sources":["../../../src/tools/artifacts/ExcelArtifact.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAIhD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,qBACa,aAAc,SAAQ,eAAe;IACrB,OAAO,CAAC,QAAQ,CAAM;IACzC,OAAO,CAAC,KAAK,CAAuB;IAE7C,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,MAAM,EAIxB;cAEkB,gBAAgB,IAAI,WAAW,GAAG,gBAAgB;IAI5D,iBAAiB,IAAI,IAAI;IAMlC,OAAO,CAAC,mBAAmB;IAkB3B,OAAO,CAAC,YAAY;IAiBpB,OAAO,CAAC,WAAW;IAMZ,gBAAgB;IAaR,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC;YAQ5C,WAAW;IAuEzB,OAAO,CAAC,gBAAgB;IAyCf,MAAM,IAAI,cAAc;CAkBjC;AAED,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,gBAAgB,EAAE,aAAa,CAAC;KAChC;CACD"}

View File

@@ -0,0 +1,216 @@
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { DownloadButton } from "@mariozechner/mini-lit/dist/DownloadButton.js";
import { html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import * as XLSX from "xlsx";
import { i18n } from "../../utils/i18n.js";
import { ArtifactElement } from "./ArtifactElement.js";
let ExcelArtifact = class ExcelArtifact extends ArtifactElement {
constructor() {
super(...arguments);
this._content = "";
this.error = null;
}
get content() {
return this._content;
}
set content(value) {
this._content = value;
this.error = null;
this.requestUpdate();
}
createRenderRoot() {
return this;
}
connectedCallback() {
super.connectedCallback();
this.style.display = "block";
this.style.height = "100%";
}
base64ToArrayBuffer(base64) {
// Remove data URL prefix if present
let base64Data = base64;
if (base64.startsWith("data:")) {
const base64Match = base64.match(/base64,(.+)/);
if (base64Match) {
base64Data = base64Match[1];
}
}
const binaryString = atob(base64Data);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}
decodeBase64() {
let base64Data = this._content;
if (this._content.startsWith("data:")) {
const base64Match = this._content.match(/base64,(.+)/);
if (base64Match) {
base64Data = base64Match[1];
}
}
const binaryString = atob(base64Data);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
}
getMimeType() {
const ext = this.filename.split(".").pop()?.toLowerCase();
if (ext === "xls")
return "application/vnd.ms-excel";
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
}
getHeaderButtons() {
return html `
<div class="flex items-center gap-1">
${DownloadButton({
content: this.decodeBase64(),
filename: this.filename,
mimeType: this.getMimeType(),
title: i18n("Download"),
})}
</div>
`;
}
async updated(changedProperties) {
super.updated(changedProperties);
if (changedProperties.has("_content") && this._content && !this.error) {
await this.renderExcel();
}
}
async renderExcel() {
const container = this.querySelector("#excel-container");
if (!container || !this._content)
return;
try {
const arrayBuffer = this.base64ToArrayBuffer(this._content);
const workbook = XLSX.read(arrayBuffer, { type: "array" });
container.innerHTML = "";
const wrapper = document.createElement("div");
wrapper.className = "overflow-auto h-full flex flex-col";
container.appendChild(wrapper);
// Create tabs for multiple sheets
if (workbook.SheetNames.length > 1) {
const tabContainer = document.createElement("div");
tabContainer.className = "flex gap-2 mb-4 border-b border-border sticky top-0 bg-background z-10";
const sheetContents = [];
workbook.SheetNames.forEach((sheetName, index) => {
// Create tab button
const tab = document.createElement("button");
tab.textContent = sheetName;
tab.className =
index === 0
? "px-4 py-2 text-sm font-medium border-b-2 border-primary text-primary"
: "px-4 py-2 text-sm font-medium text-muted-foreground hover:text-foreground hover:border-b-2 hover:border-border transition-colors";
// Create sheet content
const sheetDiv = document.createElement("div");
sheetDiv.style.display = index === 0 ? "flex" : "none";
sheetDiv.className = "flex-1 overflow-auto";
sheetDiv.appendChild(this.renderExcelSheet(workbook.Sheets[sheetName], sheetName));
sheetContents.push(sheetDiv);
// Tab click handler
tab.onclick = () => {
// Update tab styles
tabContainer.querySelectorAll("button").forEach((btn, btnIndex) => {
if (btnIndex === index) {
btn.className = "px-4 py-2 text-sm font-medium border-b-2 border-primary text-primary";
}
else {
btn.className =
"px-4 py-2 text-sm font-medium text-muted-foreground hover:text-foreground hover:border-b-2 hover:border-border transition-colors";
}
});
// Show/hide sheets
sheetContents.forEach((content, contentIndex) => {
content.style.display = contentIndex === index ? "flex" : "none";
});
};
tabContainer.appendChild(tab);
});
wrapper.appendChild(tabContainer);
sheetContents.forEach((content) => {
wrapper.appendChild(content);
});
}
else {
// Single sheet
const sheetName = workbook.SheetNames[0];
wrapper.appendChild(this.renderExcelSheet(workbook.Sheets[sheetName], sheetName));
}
}
catch (error) {
console.error("Error rendering Excel:", error);
this.error = error?.message || i18n("Failed to load spreadsheet");
}
}
renderExcelSheet(worksheet, sheetName) {
const sheetDiv = document.createElement("div");
// Generate HTML table
const htmlTable = XLSX.utils.sheet_to_html(worksheet, { id: `sheet-${sheetName}` });
const tempDiv = document.createElement("div");
tempDiv.innerHTML = htmlTable;
// Find and style the table
const table = tempDiv.querySelector("table");
if (table) {
table.className = "w-full border-collapse text-foreground";
// Style all cells
table.querySelectorAll("td, th").forEach((cell) => {
const cellEl = cell;
cellEl.className = "border border-border px-3 py-2 text-sm text-left";
});
// Style header row
const headerCells = table.querySelectorAll("thead th, tr:first-child td");
if (headerCells.length > 0) {
headerCells.forEach((th) => {
const thEl = th;
thEl.className =
"border border-border px-3 py-2 text-sm font-semibold bg-muted text-foreground sticky top-0";
});
}
// Alternate row colors
table.querySelectorAll("tbody tr:nth-child(even)").forEach((row) => {
const rowEl = row;
rowEl.className = "bg-muted/30";
});
sheetDiv.appendChild(table);
}
return sheetDiv;
}
render() {
if (this.error) {
return html `
<div class="h-full flex items-center justify-center bg-background p-4">
<div class="bg-destructive/10 border border-destructive text-destructive p-4 rounded-lg max-w-2xl">
<div class="font-medium mb-1">${i18n("Error loading spreadsheet")}</div>
<div class="text-sm opacity-90">${this.error}</div>
</div>
</div>
`;
}
return html `
<div class="h-full flex flex-col bg-background overflow-auto">
<div id="excel-container" class="flex-1 overflow-auto"></div>
</div>
`;
}
};
__decorate([
property({ type: String })
], ExcelArtifact.prototype, "_content", void 0);
__decorate([
state()
], ExcelArtifact.prototype, "error", void 0);
ExcelArtifact = __decorate([
customElement("excel-artifact")
], ExcelArtifact);
export { ExcelArtifact };
//# sourceMappingURL=ExcelArtifact.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,19 @@
import { type TemplateResult } from "lit";
import { ArtifactElement } from "./ArtifactElement.js";
export declare class GenericArtifact extends ArtifactElement {
private _content;
get content(): string;
set content(value: string);
protected createRenderRoot(): HTMLElement | DocumentFragment;
connectedCallback(): void;
private decodeBase64;
private getMimeType;
getHeaderButtons(): TemplateResult<1>;
render(): TemplateResult;
}
declare global {
interface HTMLElementTagNameMap {
"generic-artifact": GenericArtifact;
}
}
//# sourceMappingURL=GenericArtifact.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"GenericArtifact.d.ts","sourceRoot":"","sources":["../../../src/tools/artifacts/GenericArtifact.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAGhD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,qBACa,eAAgB,SAAQ,eAAe;IACvB,OAAO,CAAC,QAAQ,CAAM;IAElD,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,MAAM,EAGxB;cAEkB,gBAAgB,IAAI,WAAW,GAAG,gBAAgB;IAI5D,iBAAiB,IAAI,IAAI;IAMlC,OAAO,CAAC,YAAY;IAiBpB,OAAO,CAAC,WAAW;IAuBZ,gBAAgB;IAad,MAAM,IAAI,cAAc;CA4BjC;AAED,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,kBAAkB,EAAE,eAAe,CAAC;KACpC;CACD"}

View File

@@ -0,0 +1,117 @@
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { DownloadButton } from "@mariozechner/mini-lit/dist/DownloadButton.js";
import { html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { i18n } from "../../utils/i18n.js";
import { ArtifactElement } from "./ArtifactElement.js";
let GenericArtifact = class GenericArtifact extends ArtifactElement {
constructor() {
super(...arguments);
this._content = "";
}
get content() {
return this._content;
}
set content(value) {
this._content = value;
this.requestUpdate();
}
createRenderRoot() {
return this;
}
connectedCallback() {
super.connectedCallback();
this.style.display = "block";
this.style.height = "100%";
}
decodeBase64() {
let base64Data = this._content;
if (this._content.startsWith("data:")) {
const base64Match = this._content.match(/base64,(.+)/);
if (base64Match) {
base64Data = base64Match[1];
}
}
const binaryString = atob(base64Data);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
}
getMimeType() {
const ext = this.filename.split(".").pop()?.toLowerCase();
// Add common MIME types
const mimeTypes = {
pdf: "application/pdf",
zip: "application/zip",
tar: "application/x-tar",
gz: "application/gzip",
rar: "application/vnd.rar",
"7z": "application/x-7z-compressed",
mp3: "audio/mpeg",
mp4: "video/mp4",
avi: "video/x-msvideo",
mov: "video/quicktime",
wav: "audio/wav",
ogg: "audio/ogg",
json: "application/json",
xml: "application/xml",
bin: "application/octet-stream",
};
return mimeTypes[ext || ""] || "application/octet-stream";
}
getHeaderButtons() {
return html `
<div class="flex items-center gap-1">
${DownloadButton({
content: this.decodeBase64(),
filename: this.filename,
mimeType: this.getMimeType(),
title: i18n("Download"),
})}
</div>
`;
}
render() {
return html `
<div class="h-full flex items-center justify-center bg-background p-8">
<div class="text-center max-w-md">
<div class="text-muted-foreground text-lg mb-4">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-16 w-16 mx-auto mb-4 text-muted-foreground/50"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"
/>
</svg>
<div class="font-medium text-foreground mb-2">${this.filename}</div>
<p class="text-sm">
${i18n("Preview not available for this file type.")} ${i18n("Click the download button above to view it on your computer.")}
</p>
</div>
</div>
</div>
`;
}
};
__decorate([
property({ type: String })
], GenericArtifact.prototype, "_content", void 0);
GenericArtifact = __decorate([
customElement("generic-artifact")
], GenericArtifact);
export { GenericArtifact };
//# sourceMappingURL=GenericArtifact.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"GenericArtifact.js","sourceRoot":"","sources":["../../../src/tools/artifacts/GenericArtifact.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,27 @@
import { type Ref } from "lit/directives/ref.js";
import type { SandboxIframe } from "../../components/SandboxedIframe.js";
import type { SandboxRuntimeProvider } from "../../components/sandbox/SandboxRuntimeProvider.js";
import "../../components/SandboxedIframe.js";
import { ArtifactElement } from "./ArtifactElement.js";
import "./Console.js";
export declare class HtmlArtifact extends ArtifactElement {
filename: string;
runtimeProviders: SandboxRuntimeProvider[];
sandboxUrlProvider?: () => string;
private _content;
private logs;
sandboxIframeRef: Ref<SandboxIframe>;
private consoleRef;
private viewMode;
private setViewMode;
getHeaderButtons(): import("lit-html").TemplateResult<1>;
set content(value: string);
executeContent(html: string): void;
get content(): string;
disconnectedCallback(): void;
firstUpdated(): void;
updated(changedProperties: Map<string | number | symbol, unknown>): void;
getLogs(): string;
render(): import("lit-html").TemplateResult<1>;
}
//# sourceMappingURL=HtmlArtifact.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"HtmlArtifact.d.ts","sourceRoot":"","sources":["../../../src/tools/artifacts/HtmlArtifact.ts"],"names":[],"mappings":"AAGA,OAAO,EAAa,KAAK,GAAG,EAAO,MAAM,uBAAuB,CAAC;AAGjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AAEzE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oDAAoD,CAAC;AAEjG,OAAO,qCAAqC,CAAC;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,cAAc,CAAC;AAOtB,qBACa,YAAa,SAAQ,eAAe;IAC3B,QAAQ,SAAM;IACH,gBAAgB,EAAE,sBAAsB,EAAE,CAAM;IAChD,kBAAkB,CAAC,EAAE,MAAM,MAAM,CAAC;IAElE,OAAO,CAAC,QAAQ,CAAM;IACtB,OAAO,CAAC,IAAI,CAAsD;IAG3D,gBAAgB,EAAE,GAAG,CAAC,aAAa,CAAC,CAAe;IAC1D,OAAO,CAAC,UAAU,CAA6B;IAEtC,OAAO,CAAC,QAAQ,CAAiC;IAE1D,OAAO,CAAC,WAAW;IAIZ,gBAAgB;IAwCvB,IAAa,OAAO,CAAC,KAAK,EAAE,MAAM,EAYjC;IAEM,cAAc,CAAC,IAAI,EAAE,MAAM;IA6ClC,IAAa,OAAO,IAAI,MAAM,CAE7B;IAEQ,oBAAoB;IAOpB,YAAY;IAOZ,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC;IASnE,OAAO,IAAI,MAAM;IAKf,MAAM;CAwBf"}

View File

@@ -0,0 +1,189 @@
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import hljs from "highlight.js";
import { html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { createRef, ref } from "lit/directives/ref.js";
import { unsafeHTML } from "lit/directives/unsafe-html.js";
import { RefreshCw } from "lucide";
import { RUNTIME_MESSAGE_ROUTER } from "../../components/sandbox/RuntimeMessageRouter.js";
import { i18n } from "../../utils/i18n.js";
import "../../components/SandboxedIframe.js";
import { ArtifactElement } from "./ArtifactElement.js";
import "./Console.js";
import { icon } from "@mariozechner/mini-lit";
import { Button } from "@mariozechner/mini-lit/dist/Button.js";
import { CopyButton } from "@mariozechner/mini-lit/dist/CopyButton.js";
import { DownloadButton } from "@mariozechner/mini-lit/dist/DownloadButton.js";
import { PreviewCodeToggle } from "@mariozechner/mini-lit/dist/PreviewCodeToggle.js";
let HtmlArtifact = class HtmlArtifact extends ArtifactElement {
constructor() {
super(...arguments);
this.filename = "";
this.runtimeProviders = [];
this._content = "";
this.logs = [];
// Refs for DOM elements
this.sandboxIframeRef = createRef();
this.consoleRef = createRef();
this.viewMode = "preview";
}
setViewMode(mode) {
this.viewMode = mode;
}
getHeaderButtons() {
const toggle = new PreviewCodeToggle();
toggle.mode = this.viewMode;
toggle.addEventListener("mode-change", (e) => {
this.setViewMode(e.detail);
});
const copyButton = new CopyButton();
copyButton.text = this._content;
copyButton.title = i18n("Copy HTML");
copyButton.showText = false;
// Generate standalone HTML with all runtime code injected for download
const sandbox = this.sandboxIframeRef.value;
const sandboxId = `artifact-${this.filename}`;
const downloadContent = sandbox?.prepareHtmlDocument(sandboxId, this._content, this.runtimeProviders || [], {
isHtmlArtifact: true,
isStandalone: true, // Skip runtime bridge and navigation interceptor for standalone downloads
}) || this._content;
return html `
<div class="flex items-center gap-2">
${toggle}
${Button({
variant: "ghost",
size: "sm",
onClick: () => {
this.logs = [];
this.executeContent(this._content);
},
title: i18n("Reload HTML"),
children: icon(RefreshCw, "sm"),
})}
${copyButton}
${DownloadButton({ content: downloadContent, filename: this.filename, mimeType: "text/html", title: i18n("Download HTML") })}
</div>
`;
}
set content(value) {
const oldValue = this._content;
this._content = value;
if (oldValue !== value) {
// Reset logs when content changes
this.logs = [];
this.requestUpdate();
// Execute content in sandbox if it exists
if (this.sandboxIframeRef.value && value) {
this.executeContent(value);
}
}
}
executeContent(html) {
const sandbox = this.sandboxIframeRef.value;
if (!sandbox)
return;
// Configure sandbox URL provider if provided (for browser extensions)
if (this.sandboxUrlProvider) {
sandbox.sandboxUrlProvider = this.sandboxUrlProvider;
}
const sandboxId = `artifact-${this.filename}`;
// Create consumer for console messages
const consumer = {
handleMessage: async (message) => {
if (message.type === "console") {
// Create new array reference for Lit reactivity
this.logs = [
...this.logs,
{
type: message.method === "error" ? "error" : "log",
text: message.text,
},
];
this.requestUpdate(); // Re-render to show console
}
},
};
// Inject window.complete() call at the end of the HTML to signal when page is loaded
// HTML artifacts don't time out - they call complete() when ready
let modifiedHtml = html;
if (modifiedHtml.includes("</html>")) {
modifiedHtml = modifiedHtml.replace("</html>", "<script>if (window.complete) window.complete();</script></html>");
}
else {
// If no closing </html> tag, append the script
modifiedHtml += "<script>if (window.complete) window.complete();</script>";
}
// Load content - this handles sandbox registration, consumer registration, and iframe creation
sandbox.loadContent(sandboxId, modifiedHtml, this.runtimeProviders, [consumer]);
}
get content() {
return this._content;
}
disconnectedCallback() {
super.disconnectedCallback();
// Unregister sandbox when element is removed from DOM
const sandboxId = `artifact-${this.filename}`;
RUNTIME_MESSAGE_ROUTER.unregisterSandbox(sandboxId);
}
firstUpdated() {
// Execute initial content
if (this._content && this.sandboxIframeRef.value) {
this.executeContent(this._content);
}
}
updated(changedProperties) {
super.updated(changedProperties);
// If we have content but haven't executed yet (e.g., during reconstruction),
// execute when the iframe ref becomes available
if (this._content && this.sandboxIframeRef.value && this.logs.length === 0) {
this.executeContent(this._content);
}
}
getLogs() {
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");
}
render() {
return html `
<div class="h-full flex flex-col">
<div class="flex-1 overflow-hidden relative">
<!-- Preview container - always in DOM, just hidden when not active -->
<div class="absolute inset-0 flex flex-col" style="display: ${this.viewMode === "preview" ? "flex" : "none"}">
<sandbox-iframe class="flex-1" ${ref(this.sandboxIframeRef)}></sandbox-iframe>
${this.logs.length > 0
? html `<artifact-console .logs=${this.logs} ${ref(this.consoleRef)}></artifact-console>`
: ""}
</div>
<!-- Code view - always in DOM, just hidden when not active -->
<div class="absolute inset-0 overflow-auto bg-background" style="display: ${this.viewMode === "code" ? "block" : "none"}">
<pre class="m-0 p-4 text-xs"><code class="hljs language-html">${unsafeHTML(hljs.highlight(this._content, { language: "html" }).value)}</code></pre>
</div>
</div>
</div>
`;
}
};
__decorate([
property()
], HtmlArtifact.prototype, "filename", void 0);
__decorate([
property({ attribute: false })
], HtmlArtifact.prototype, "runtimeProviders", void 0);
__decorate([
property({ attribute: false })
], HtmlArtifact.prototype, "sandboxUrlProvider", void 0);
__decorate([
state()
], HtmlArtifact.prototype, "viewMode", void 0);
HtmlArtifact = __decorate([
customElement("html-artifact")
], HtmlArtifact);
export { HtmlArtifact };
//# sourceMappingURL=HtmlArtifact.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,20 @@
import { type TemplateResult } from "lit";
import { ArtifactElement } from "./ArtifactElement.js";
export declare class ImageArtifact extends ArtifactElement {
private _content;
get content(): string;
set content(value: string);
protected createRenderRoot(): HTMLElement | DocumentFragment;
connectedCallback(): void;
private getMimeType;
private getImageUrl;
private decodeBase64;
getHeaderButtons(): TemplateResult<1>;
render(): TemplateResult;
}
declare global {
interface HTMLElementTagNameMap {
"image-artifact": ImageArtifact;
}
}
//# sourceMappingURL=ImageArtifact.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ImageArtifact.d.ts","sourceRoot":"","sources":["../../../src/tools/artifacts/ImageArtifact.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAGhD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,qBACa,aAAc,SAAQ,eAAe;IACrB,OAAO,CAAC,QAAQ,CAAM;IAElD,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,MAAM,EAGxB;cAEkB,gBAAgB,IAAI,WAAW,GAAG,gBAAgB;IAI5D,iBAAiB,IAAI,IAAI;IAMlC,OAAO,CAAC,WAAW;IAWnB,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,YAAY;IA6Bb,gBAAgB;IAad,MAAM,IAAI,cAAc;CAkBjC;AAED,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,gBAAgB,EAAE,aAAa,CAAC;KAChC;CACD"}

View File

@@ -0,0 +1,120 @@
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { DownloadButton } from "@mariozechner/mini-lit/dist/DownloadButton.js";
import { html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { i18n } from "../../utils/i18n.js";
import { ArtifactElement } from "./ArtifactElement.js";
let ImageArtifact = class ImageArtifact extends ArtifactElement {
constructor() {
super(...arguments);
this._content = "";
}
get content() {
return this._content;
}
set content(value) {
this._content = value;
this.requestUpdate();
}
createRenderRoot() {
return this;
}
connectedCallback() {
super.connectedCallback();
this.style.display = "block";
this.style.height = "100%";
}
getMimeType() {
const ext = this.filename.split(".").pop()?.toLowerCase();
if (ext === "jpg" || ext === "jpeg")
return "image/jpeg";
if (ext === "gif")
return "image/gif";
if (ext === "webp")
return "image/webp";
if (ext === "svg")
return "image/svg+xml";
if (ext === "bmp")
return "image/bmp";
if (ext === "ico")
return "image/x-icon";
return "image/png";
}
getImageUrl() {
// If content is already a data URL, use it directly
if (this._content.startsWith("data:")) {
return this._content;
}
// Otherwise assume it's base64 and construct data URL
return `data:${this.getMimeType()};base64,${this._content}`;
}
decodeBase64() {
let base64Data;
// If content is a data URL, extract the base64 part
if (this._content.startsWith("data:")) {
const base64Match = this._content.match(/base64,(.+)/);
if (base64Match) {
base64Data = base64Match[1];
}
else {
// Not a base64 data URL, return empty
return new Uint8Array(0);
}
}
else {
// Otherwise use content as-is
base64Data = this._content;
}
// Decode base64 to binary string
const binaryString = atob(base64Data);
// Convert binary string to Uint8Array
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
}
getHeaderButtons() {
return html `
<div class="flex items-center gap-1">
${DownloadButton({
content: this.decodeBase64(),
filename: this.filename,
mimeType: this.getMimeType(),
title: i18n("Download"),
})}
</div>
`;
}
render() {
return html `
<div class="h-full flex flex-col bg-background overflow-auto">
<div class="flex-1 flex items-center justify-center p-4">
<img
src="${this.getImageUrl()}"
alt="${this.filename}"
class="max-w-full max-h-full object-contain"
@error=${(e) => {
const target = e.target;
target.src =
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext x='50' y='50' text-anchor='middle' dominant-baseline='middle' fill='%23999'%3EImage Error%3C/text%3E%3C/svg%3E";
}}
/>
</div>
</div>
`;
}
};
__decorate([
property({ type: String })
], ImageArtifact.prototype, "_content", void 0);
ImageArtifact = __decorate([
customElement("image-artifact")
], ImageArtifact);
export { ImageArtifact };
//# sourceMappingURL=ImageArtifact.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ImageArtifact.js","sourceRoot":"","sources":["../../../src/tools/artifacts/ImageArtifact.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,19 @@
import "@mariozechner/mini-lit/dist/MarkdownBlock.js";
import { ArtifactElement } from "./ArtifactElement.js";
export declare class MarkdownArtifact extends ArtifactElement {
filename: string;
private _content;
get content(): string;
set content(value: string);
private viewMode;
protected createRenderRoot(): HTMLElement | DocumentFragment;
private setViewMode;
getHeaderButtons(): import("lit-html").TemplateResult<1>;
render(): import("lit-html").TemplateResult<1>;
}
declare global {
interface HTMLElementTagNameMap {
"markdown-artifact": MarkdownArtifact;
}
}
//# sourceMappingURL=MarkdownArtifact.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"MarkdownArtifact.d.ts","sourceRoot":"","sources":["../../../src/tools/artifacts/MarkdownArtifact.ts"],"names":[],"mappings":"AAKA,OAAO,8CAA8C,CAAC;AAItD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,qBACa,gBAAiB,SAAQ,eAAe;IAC/B,QAAQ,SAAM;IAEnC,OAAO,CAAC,QAAQ,CAAM;IACtB,IAAa,OAAO,IAAI,MAAM,CAE7B;IACD,IAAa,OAAO,CAAC,KAAK,EAAE,MAAM,EAGjC;IAEQ,OAAO,CAAC,QAAQ,CAAiC;cAEvC,gBAAgB,IAAI,WAAW,GAAG,gBAAgB;IAIrE,OAAO,CAAC,WAAW;IAIZ,gBAAgB;IA0Bd,MAAM;CAef;AAED,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,mBAAmB,EAAE,gBAAgB,CAAC;KACtC;CACD"}

View File

@@ -0,0 +1,82 @@
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import hljs from "highlight.js";
import { html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { unsafeHTML } from "lit/directives/unsafe-html.js";
import { i18n } from "../../utils/i18n.js";
import "@mariozechner/mini-lit/dist/MarkdownBlock.js";
import { CopyButton } from "@mariozechner/mini-lit/dist/CopyButton.js";
import { DownloadButton } from "@mariozechner/mini-lit/dist/DownloadButton.js";
import { PreviewCodeToggle } from "@mariozechner/mini-lit/dist/PreviewCodeToggle.js";
import { ArtifactElement } from "./ArtifactElement.js";
let MarkdownArtifact = class MarkdownArtifact extends ArtifactElement {
constructor() {
super(...arguments);
this.filename = "";
this._content = "";
this.viewMode = "preview";
}
get content() {
return this._content;
}
set content(value) {
this._content = value;
this.requestUpdate();
}
createRenderRoot() {
return this; // light DOM
}
setViewMode(mode) {
this.viewMode = mode;
}
getHeaderButtons() {
const toggle = new PreviewCodeToggle();
toggle.mode = this.viewMode;
toggle.addEventListener("mode-change", (e) => {
this.setViewMode(e.detail);
});
const copyButton = new CopyButton();
copyButton.text = this._content;
copyButton.title = i18n("Copy Markdown");
copyButton.showText = false;
return html `
<div class="flex items-center gap-2">
${toggle}
${copyButton}
${DownloadButton({
content: this._content,
filename: this.filename,
mimeType: "text/markdown",
title: i18n("Download Markdown"),
})}
</div>
`;
}
render() {
return html `
<div class="h-full flex flex-col">
<div class="flex-1 overflow-auto">
${this.viewMode === "preview"
? html `<div class="p-4"><markdown-block .content=${this.content}></markdown-block></div>`
: html `<pre class="m-0 p-4 text-xs whitespace-pre-wrap break-words"><code class="hljs language-markdown">${unsafeHTML(hljs.highlight(this.content, { language: "markdown", ignoreIllegals: true }).value)}</code></pre>`}
</div>
</div>
`;
}
};
__decorate([
property()
], MarkdownArtifact.prototype, "filename", void 0);
__decorate([
state()
], MarkdownArtifact.prototype, "viewMode", void 0);
MarkdownArtifact = __decorate([
customElement("markdown-artifact")
], MarkdownArtifact);
export { MarkdownArtifact };
//# sourceMappingURL=MarkdownArtifact.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"MarkdownArtifact.js","sourceRoot":"","sources":["../../../src/tools/artifacts/MarkdownArtifact.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,IAAI,MAAM,cAAc,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAC3C,OAAO,8CAA8C,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,2CAA2C,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,+CAA+C,CAAC;AAC/E,OAAO,EAAE,iBAAiB,EAAE,MAAM,kDAAkD,CAAC;AACrF,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGhD,IAAM,gBAAgB,GAAtB,MAAM,gBAAiB,SAAQ,eAAe;IAA9C;;QACe,aAAQ,GAAG,EAAE,CAAC;QAE3B,aAAQ,GAAG,EAAE,CAAC;QASL,aAAQ,GAAuB,SAAS,CAAC;IAmD3D,CAAC;IA3DA,IAAa,OAAO;QACnB,OAAO,IAAI,CAAC,QAAQ,CAAC;IACtB,CAAC;IACD,IAAa,OAAO,CAAC,KAAa;QACjC,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACtB,IAAI,CAAC,aAAa,EAAE,CAAC;IACtB,CAAC;IAIkB,gBAAgB;QAClC,OAAO,IAAI,CAAC,CAAC,YAAY;IAC1B,CAAC;IAEO,WAAW,CAAC,IAAwB;QAC3C,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;IACtB,CAAC;IAEM,gBAAgB;QACtB,MAAM,MAAM,GAAG,IAAI,iBAAiB,EAAE,CAAC;QACvC,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC5B,MAAM,CAAC,gBAAgB,CAAC,aAAa,EAAE,CAAC,CAAQ,EAAE,EAAE;YACnD,IAAI,CAAC,WAAW,CAAE,CAAiB,CAAC,MAAM,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;QAEH,MAAM,UAAU,GAAG,IAAI,UAAU,EAAE,CAAC;QACpC,UAAU,CAAC,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC;QAChC,UAAU,CAAC,KAAK,GAAG,IAAI,CAAC,eAAe,CAAC,CAAC;QACzC,UAAU,CAAC,QAAQ,GAAG,KAAK,CAAC;QAE5B,OAAO,IAAI,CAAA;;MAEP,MAAM;MACN,UAAU;MACV,cAAc,CAAC;YAChB,OAAO,EAAE,IAAI,CAAC,QAAQ;YACtB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,QAAQ,EAAE,eAAe;YACzB,KAAK,EAAE,IAAI,CAAC,mBAAmB,CAAC;SAChC,CAAC;;GAEH,CAAC;IACH,CAAC;IAEQ,MAAM;QACd,OAAO,IAAI,CAAA;;;OAIP,IAAI,CAAC,QAAQ,KAAK,SAAS;YAC1B,CAAC,CAAC,IAAI,CAAA,6CAA6C,IAAI,CAAC,OAAO,0BAA0B;YACzF,CAAC,CAAC,IAAI,CAAA,qGAAqG,UAAU,CACnH,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAClF,eACJ;;;GAGF,CAAC;IACH,CAAC;CACD,CAAA;AA9DqB;IAApB,QAAQ,EAAE;kDAAwB;AAWlB;IAAhB,KAAK,EAAE;kDAAkD;AAZ9C,gBAAgB;IAD5B,aAAa,CAAC,mBAAmB,CAAC;GACtB,gBAAgB,CA+D5B"}

View File

@@ -0,0 +1,25 @@
import { type TemplateResult } from "lit";
import { ArtifactElement } from "./ArtifactElement.js";
export declare class PdfArtifact extends ArtifactElement {
private _content;
private error;
private currentLoadingTask;
get content(): string;
set content(value: string);
protected createRenderRoot(): HTMLElement | DocumentFragment;
connectedCallback(): void;
disconnectedCallback(): void;
private cleanup;
private base64ToArrayBuffer;
private decodeBase64;
getHeaderButtons(): TemplateResult<1>;
updated(changedProperties: Map<string, any>): Promise<void>;
private renderPdf;
render(): TemplateResult;
}
declare global {
interface HTMLElementTagNameMap {
"pdf-artifact": PdfArtifact;
}
}
//# sourceMappingURL=PdfArtifact.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"PdfArtifact.d.ts","sourceRoot":"","sources":["../../../src/tools/artifacts/PdfArtifact.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAIhD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAKvD,qBACa,WAAY,SAAQ,eAAe;IACnB,OAAO,CAAC,QAAQ,CAAM;IACzC,OAAO,CAAC,KAAK,CAAuB;IAC7C,OAAO,CAAC,kBAAkB,CAAa;IAEvC,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,MAAM,EAIxB;cAEkB,gBAAgB,IAAI,WAAW,GAAG,gBAAgB;IAI5D,iBAAiB,IAAI,IAAI;IAMzB,oBAAoB,IAAI,IAAI;IAKrC,OAAO,CAAC,OAAO;IAOf,OAAO,CAAC,mBAAmB;IAkB3B,OAAO,CAAC,YAAY;IAiBb,gBAAgB;IAaR,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC;YAQ5C,SAAS;IAwEd,MAAM,IAAI,cAAc;CAkBjC;AAED,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,cAAc,EAAE,WAAW,CAAC;KAC5B;CACD"}

View File

@@ -0,0 +1,184 @@
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { DownloadButton } from "@mariozechner/mini-lit/dist/DownloadButton.js";
import { html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import * as pdfjsLib from "pdfjs-dist";
import { i18n } from "../../utils/i18n.js";
import { ArtifactElement } from "./ArtifactElement.js";
// Configure PDF.js worker
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL("pdfjs-dist/build/pdf.worker.min.mjs", import.meta.url).toString();
let PdfArtifact = class PdfArtifact extends ArtifactElement {
constructor() {
super(...arguments);
this._content = "";
this.error = null;
this.currentLoadingTask = null;
}
get content() {
return this._content;
}
set content(value) {
this._content = value;
this.error = null;
this.requestUpdate();
}
createRenderRoot() {
return this;
}
connectedCallback() {
super.connectedCallback();
this.style.display = "block";
this.style.height = "100%";
}
disconnectedCallback() {
super.disconnectedCallback();
this.cleanup();
}
cleanup() {
if (this.currentLoadingTask) {
this.currentLoadingTask.destroy();
this.currentLoadingTask = null;
}
}
base64ToArrayBuffer(base64) {
// Remove data URL prefix if present
let base64Data = base64;
if (base64.startsWith("data:")) {
const base64Match = base64.match(/base64,(.+)/);
if (base64Match) {
base64Data = base64Match[1];
}
}
const binaryString = atob(base64Data);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}
decodeBase64() {
let base64Data = this._content;
if (this._content.startsWith("data:")) {
const base64Match = this._content.match(/base64,(.+)/);
if (base64Match) {
base64Data = base64Match[1];
}
}
const binaryString = atob(base64Data);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
}
getHeaderButtons() {
return html `
<div class="flex items-center gap-1">
${DownloadButton({
content: this.decodeBase64(),
filename: this.filename,
mimeType: "application/pdf",
title: i18n("Download"),
})}
</div>
`;
}
async updated(changedProperties) {
super.updated(changedProperties);
if (changedProperties.has("_content") && this._content && !this.error) {
await this.renderPdf();
}
}
async renderPdf() {
const container = this.querySelector("#pdf-container");
if (!container || !this._content)
return;
let pdf = null;
try {
const arrayBuffer = this.base64ToArrayBuffer(this._content);
// Cancel any existing loading task
if (this.currentLoadingTask) {
this.currentLoadingTask.destroy();
}
// Load the PDF
this.currentLoadingTask = pdfjsLib.getDocument({ data: arrayBuffer });
pdf = await this.currentLoadingTask.promise;
this.currentLoadingTask = null;
// Clear container
container.innerHTML = "";
const wrapper = document.createElement("div");
wrapper.className = "p-4";
container.appendChild(wrapper);
// Render all pages
for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
const page = await pdf.getPage(pageNum);
const pageContainer = document.createElement("div");
pageContainer.className = "mb-4 last:mb-0";
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
const viewport = page.getViewport({ scale: 1.5 });
canvas.height = viewport.height;
canvas.width = viewport.width;
canvas.className = "w-full max-w-full h-auto block mx-auto bg-white rounded shadow-sm border border-border";
if (context) {
context.fillStyle = "white";
context.fillRect(0, 0, canvas.width, canvas.height);
}
await page.render({
canvasContext: context,
viewport: viewport,
canvas: canvas,
}).promise;
pageContainer.appendChild(canvas);
if (pageNum < pdf.numPages) {
const separator = document.createElement("div");
separator.className = "h-px bg-border my-4";
pageContainer.appendChild(separator);
}
wrapper.appendChild(pageContainer);
}
}
catch (error) {
console.error("Error rendering PDF:", error);
this.error = error?.message || i18n("Failed to load PDF");
}
finally {
if (pdf) {
pdf.destroy();
}
}
}
render() {
if (this.error) {
return html `
<div class="h-full flex items-center justify-center bg-background p-4">
<div class="bg-destructive/10 border border-destructive text-destructive p-4 rounded-lg max-w-2xl">
<div class="font-medium mb-1">${i18n("Error loading PDF")}</div>
<div class="text-sm opacity-90">${this.error}</div>
</div>
</div>
`;
}
return html `
<div class="h-full flex flex-col bg-background overflow-auto">
<div id="pdf-container" class="flex-1 overflow-auto"></div>
</div>
`;
}
};
__decorate([
property({ type: String })
], PdfArtifact.prototype, "_content", void 0);
__decorate([
state()
], PdfArtifact.prototype, "error", void 0);
PdfArtifact = __decorate([
customElement("pdf-artifact")
], PdfArtifact);
export { PdfArtifact };
//# sourceMappingURL=PdfArtifact.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,18 @@
import { ArtifactElement } from "./ArtifactElement.js";
export declare class SvgArtifact extends ArtifactElement {
filename: string;
private _content;
get content(): string;
set content(value: string);
private viewMode;
protected createRenderRoot(): HTMLElement | DocumentFragment;
private setViewMode;
getHeaderButtons(): import("lit-html").TemplateResult<1>;
render(): import("lit-html").TemplateResult<1>;
}
declare global {
interface HTMLElementTagNameMap {
"svg-artifact": SvgArtifact;
}
}
//# sourceMappingURL=SvgArtifact.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"SvgArtifact.d.ts","sourceRoot":"","sources":["../../../src/tools/artifacts/SvgArtifact.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,qBACa,WAAY,SAAQ,eAAe;IAC1B,QAAQ,SAAM;IAEnC,OAAO,CAAC,QAAQ,CAAM;IACtB,IAAa,OAAO,IAAI,MAAM,CAE7B;IACD,IAAa,OAAO,CAAC,KAAK,EAAE,MAAM,EAGjC;IAEQ,OAAO,CAAC,QAAQ,CAAiC;cAEvC,gBAAgB,IAAI,WAAW,GAAG,gBAAgB;IAIrE,OAAO,CAAC,WAAW;IAIZ,gBAAgB;IAqBd,MAAM;CAiBf;AAED,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,cAAc,EAAE,WAAW,CAAC;KAC5B;CACD"}

View File

@@ -0,0 +1,78 @@
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { CopyButton } from "@mariozechner/mini-lit/dist/CopyButton.js";
import { DownloadButton } from "@mariozechner/mini-lit/dist/DownloadButton.js";
import { PreviewCodeToggle } from "@mariozechner/mini-lit/dist/PreviewCodeToggle.js";
import hljs from "highlight.js";
import { html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { unsafeHTML } from "lit/directives/unsafe-html.js";
import { i18n } from "../../utils/i18n.js";
import { ArtifactElement } from "./ArtifactElement.js";
let SvgArtifact = class SvgArtifact extends ArtifactElement {
constructor() {
super(...arguments);
this.filename = "";
this._content = "";
this.viewMode = "preview";
}
get content() {
return this._content;
}
set content(value) {
this._content = value;
this.requestUpdate();
}
createRenderRoot() {
return this; // light DOM
}
setViewMode(mode) {
this.viewMode = mode;
}
getHeaderButtons() {
const toggle = new PreviewCodeToggle();
toggle.mode = this.viewMode;
toggle.addEventListener("mode-change", (e) => {
this.setViewMode(e.detail);
});
const copyButton = new CopyButton();
copyButton.text = this._content;
copyButton.title = i18n("Copy SVG");
copyButton.showText = false;
return html `
<div class="flex items-center gap-2">
${toggle}
${copyButton}
${DownloadButton({ content: this._content, filename: this.filename, mimeType: "image/svg+xml", title: i18n("Download SVG") })}
</div>
`;
}
render() {
return html `
<div class="h-full flex flex-col">
<div class="flex-1 overflow-auto">
${this.viewMode === "preview"
? html `<div class="h-full flex items-center justify-center">
${unsafeHTML(this.content.replace(/<svg(\s|>)/i, (_m, p1) => `<svg class="w-full h-full"${p1}`))}
</div>`
: html `<pre class="m-0 p-4 text-xs"><code class="hljs language-xml">${unsafeHTML(hljs.highlight(this.content, { language: "xml", ignoreIllegals: true }).value)}</code></pre>`}
</div>
</div>
`;
}
};
__decorate([
property()
], SvgArtifact.prototype, "filename", void 0);
__decorate([
state()
], SvgArtifact.prototype, "viewMode", void 0);
SvgArtifact = __decorate([
customElement("svg-artifact")
], SvgArtifact);
export { SvgArtifact };
//# sourceMappingURL=SvgArtifact.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"SvgArtifact.js","sourceRoot":"","sources":["../../../src/tools/artifacts/SvgArtifact.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,2CAA2C,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,+CAA+C,CAAC;AAC/E,OAAO,EAAE,iBAAiB,EAAE,MAAM,kDAAkD,CAAC;AACrF,OAAO,IAAI,MAAM,cAAc,CAAC;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGhD,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,eAAe;IAAzC;;QACe,aAAQ,GAAG,EAAE,CAAC;QAE3B,aAAQ,GAAG,EAAE,CAAC;QASL,aAAQ,GAAuB,SAAS,CAAC;IAgD3D,CAAC;IAxDA,IAAa,OAAO;QACnB,OAAO,IAAI,CAAC,QAAQ,CAAC;IACtB,CAAC;IACD,IAAa,OAAO,CAAC,KAAa;QACjC,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACtB,IAAI,CAAC,aAAa,EAAE,CAAC;IACtB,CAAC;IAIkB,gBAAgB;QAClC,OAAO,IAAI,CAAC,CAAC,YAAY;IAC1B,CAAC;IAEO,WAAW,CAAC,IAAwB;QAC3C,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;IACtB,CAAC;IAEM,gBAAgB;QACtB,MAAM,MAAM,GAAG,IAAI,iBAAiB,EAAE,CAAC;QACvC,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC5B,MAAM,CAAC,gBAAgB,CAAC,aAAa,EAAE,CAAC,CAAQ,EAAE,EAAE;YACnD,IAAI,CAAC,WAAW,CAAE,CAAiB,CAAC,MAAM,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;QAEH,MAAM,UAAU,GAAG,IAAI,UAAU,EAAE,CAAC;QACpC,UAAU,CAAC,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC;QAChC,UAAU,CAAC,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC;QACpC,UAAU,CAAC,QAAQ,GAAG,KAAK,CAAC;QAE5B,OAAO,IAAI,CAAA;;MAEP,MAAM;MACN,UAAU;MACV,cAAc,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,eAAe,EAAE,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC;;GAE9H,CAAC;IACH,CAAC;IAEQ,MAAM;QACd,OAAO,IAAI,CAAA;;;OAIP,IAAI,CAAC,QAAQ,KAAK,SAAS;YAC1B,CAAC,CAAC,IAAI,CAAA;UACH,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,6BAA6B,EAAE,EAAE,CAAC,CAAC;cAC1F;YACP,CAAC,CAAC,IAAI,CAAA,gEAAgE,UAAU,CAC9E,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAC7E,eACJ;;;GAGF,CAAC;IACH,CAAC;CACD,CAAA;AA3DqB;IAApB,QAAQ,EAAE;6CAAwB;AAWlB;IAAhB,KAAK,EAAE;6CAAkD;AAZ9C,WAAW;IADvB,aAAa,CAAC,cAAc,CAAC;GACjB,WAAW,CA4DvB"}

View File

@@ -0,0 +1,19 @@
import { ArtifactElement } from "./ArtifactElement.js";
export declare class TextArtifact extends ArtifactElement {
filename: string;
private _content;
get content(): string;
set content(value: string);
protected createRenderRoot(): HTMLElement | DocumentFragment;
private isCode;
private getLanguageFromExtension;
private getMimeType;
getHeaderButtons(): import("lit-html").TemplateResult<1>;
render(): import("lit-html").TemplateResult<1>;
}
declare global {
interface HTMLElementTagNameMap {
"text-artifact": TextArtifact;
}
}
//# sourceMappingURL=TextArtifact.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"TextArtifact.d.ts","sourceRoot":"","sources":["../../../src/tools/artifacts/TextArtifact.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAiDvD,qBACa,YAAa,SAAQ,eAAe;IAC3B,QAAQ,SAAM;IAEnC,OAAO,CAAC,QAAQ,CAAM;IACtB,IAAa,OAAO,IAAI,MAAM,CAE7B;IACD,IAAa,OAAO,CAAC,KAAK,EAAE,MAAM,EAGjC;cAEkB,gBAAgB,IAAI,WAAW,GAAG,gBAAgB;IAIrE,OAAO,CAAC,MAAM;IAKd,OAAO,CAAC,wBAAwB;IAahC,OAAO,CAAC,WAAW;IAOZ,gBAAgB;IAmBd,MAAM;CAwBf;AAED,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,eAAe,EAAE,YAAY,CAAC;KAC9B;CACD"}

View File

@@ -0,0 +1,144 @@
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { CopyButton } from "@mariozechner/mini-lit/dist/CopyButton.js";
import { DownloadButton } from "@mariozechner/mini-lit/dist/DownloadButton.js";
import hljs from "highlight.js";
import { html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { unsafeHTML } from "lit/directives/unsafe-html.js";
import { i18n } from "../../utils/i18n.js";
import { ArtifactElement } from "./ArtifactElement.js";
// Known code file extensions for highlighting
const CODE_EXTENSIONS = [
"js",
"javascript",
"ts",
"typescript",
"jsx",
"tsx",
"py",
"python",
"java",
"c",
"cpp",
"cs",
"php",
"rb",
"ruby",
"go",
"rust",
"swift",
"kotlin",
"scala",
"dart",
"html",
"css",
"scss",
"sass",
"less",
"json",
"xml",
"yaml",
"yml",
"toml",
"sql",
"sh",
"bash",
"ps1",
"bat",
"r",
"matlab",
"julia",
"lua",
"perl",
"vue",
"svelte",
];
let TextArtifact = class TextArtifact extends ArtifactElement {
constructor() {
super(...arguments);
this.filename = "";
this._content = "";
}
get content() {
return this._content;
}
set content(value) {
this._content = value;
this.requestUpdate();
}
createRenderRoot() {
return this; // light DOM
}
isCode() {
const ext = this.filename.split(".").pop()?.toLowerCase() || "";
return CODE_EXTENSIONS.includes(ext);
}
getLanguageFromExtension(ext) {
const languageMap = {
js: "javascript",
ts: "typescript",
py: "python",
rb: "ruby",
yml: "yaml",
ps1: "powershell",
bat: "batch",
};
return languageMap[ext] || ext;
}
getMimeType() {
const ext = this.filename.split(".").pop()?.toLowerCase() || "";
if (ext === "svg")
return "image/svg+xml";
if (ext === "md" || ext === "markdown")
return "text/markdown";
return "text/plain";
}
getHeaderButtons() {
const copyButton = new CopyButton();
copyButton.text = this.content;
copyButton.title = i18n("Copy");
copyButton.showText = false;
return html `
<div class="flex items-center gap-1">
${copyButton}
${DownloadButton({
content: this.content,
filename: this.filename,
mimeType: this.getMimeType(),
title: i18n("Download"),
})}
</div>
`;
}
render() {
const isCode = this.isCode();
const ext = this.filename.split(".").pop() || "";
return html `
<div class="h-full flex flex-col">
<div class="flex-1 overflow-auto">
${isCode
? html `
<pre class="m-0 p-4 text-xs"><code class="hljs language-${this.getLanguageFromExtension(ext.toLowerCase())}">${unsafeHTML(hljs.highlight(this.content, {
language: this.getLanguageFromExtension(ext.toLowerCase()),
ignoreIllegals: true,
}).value)}</code></pre>
`
: html ` <pre class="m-0 p-4 text-xs font-mono">${this.content}</pre> `}
</div>
</div>
`;
}
};
__decorate([
property()
], TextArtifact.prototype, "filename", void 0);
TextArtifact = __decorate([
customElement("text-artifact")
], TextArtifact);
export { TextArtifact };
//# sourceMappingURL=TextArtifact.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"TextArtifact.js","sourceRoot":"","sources":["../../../src/tools/artifacts/TextArtifact.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,11 @@
import "@mariozechner/mini-lit/dist/CodeBlock.js";
import type { ToolResultMessage } from "@mariozechner/pi-ai";
import "../../components/ConsoleBlock.js";
import type { ToolRenderer, ToolRenderResult } from "../types.js";
import type { ArtifactsPanel, ArtifactsParams } from "./artifacts.js";
export declare class ArtifactsToolRenderer implements ToolRenderer<ArtifactsParams, undefined> {
artifactsPanel?: ArtifactsPanel | undefined;
constructor(artifactsPanel?: ArtifactsPanel | undefined);
render(params: ArtifactsParams | undefined, result: ToolResultMessage<undefined> | undefined, isStreaming?: boolean): ToolRenderResult;
}
//# sourceMappingURL=artifacts-tool-renderer.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"artifacts-tool-renderer.d.ts","sourceRoot":"","sources":["../../../src/tools/artifacts/artifacts-tool-renderer.ts"],"names":[],"mappings":"AAAA,OAAO,0CAA0C,CAAC;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAG7D,OAAO,kCAAkC,CAAC;AAK1C,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAElE,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAkDtE,qBAAa,qBAAsB,YAAW,YAAY,CAAC,eAAe,EAAE,SAAS,CAAC;IAClE,cAAc,CAAC,EAAE,cAAc;gBAA/B,cAAc,CAAC,EAAE,cAAc,YAAA;IAElD,MAAM,CACL,MAAM,EAAE,eAAe,GAAG,SAAS,EACnC,MAAM,EAAE,iBAAiB,CAAC,SAAS,CAAC,GAAG,SAAS,EAChD,WAAW,CAAC,EAAE,OAAO,GACnB,gBAAgB;CAiPnB"}

View File

@@ -0,0 +1,272 @@
import "@mariozechner/mini-lit/dist/CodeBlock.js";
import { createRef, ref } from "lit/directives/ref.js";
import { FileCode2 } from "lucide";
import "../../components/ConsoleBlock.js";
import { Diff } from "@mariozechner/mini-lit/dist/Diff.js";
import { html } from "lit";
import { i18n } from "../../utils/i18n.js";
import { renderCollapsibleHeader, renderHeader } from "../renderer-registry.js";
import { ArtifactPill } from "./ArtifactPill.js";
// Helper to extract text from content blocks
function getTextOutput(result) {
if (!result)
return "";
return (result.content
?.filter((c) => c.type === "text")
.map((c) => c.text)
.join("\n") || "");
}
// Helper to determine language for syntax highlighting
function getLanguageFromFilename(filename) {
if (!filename)
return "text";
const ext = filename.split(".").pop()?.toLowerCase();
const languageMap = {
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 {
constructor(artifactsPanel) {
this.artifactsPanel = artifactsPanel;
}
render(params, result, isStreaming) {
const state = result ? (result.isError ? "error" : "complete") : isStreaming ? "inprogress" : "complete";
// Create refs for collapsible sections
const contentRef = createRef();
const chevronRef = createRef();
// Helper to get command labels
const getCommandLabels = (command) => {
const labels = {
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") };
};
// Helper to render header text with inline artifact pill
const renderHeaderWithPill = (labelText, filename) => {
if (filename) {
return html `<span>${labelText} ${ArtifactPill(filename, this.artifactsPanel)}</span>`;
}
return html `<span>${labelText}</span>`;
};
// 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 = labels.streaming;
// For create/update/rewrite errors, show code block + console/error
if (command === "create" || command === "update" || command === "rewrite") {
const content = params?.content || "";
const { old_str, new_str } = params || {};
const isDiff = command === "update";
const diffContent = old_str !== undefined && new_str !== undefined ? Diff({ oldText: old_str, newText: new_str }) : "";
const isHtml = filename?.endsWith(".html");
return {
content: html `
<div>
${renderCollapsibleHeader(state, FileCode2, renderHeaderWithPill(headerText, filename), contentRef, chevronRef, false)}
<div ${ref(contentRef)} class="max-h-0 overflow-hidden transition-all duration-300 space-y-3">
${isDiff ? diffContent : content ? html `<code-block .code=${content} language=${getLanguageFromFilename(filename)}></code-block>` : ""}
${isHtml
? html `<console-block .content=${getTextOutput(result) || i18n("An error occurred")} variant="error"></console-block>`
: html `<div class="text-sm text-destructive">${getTextOutput(result) || i18n("An error occurred")}</div>`}
</div>
</div>
`,
isCustom: false,
};
}
// For other errors, just show error message
return {
content: html `
<div class="space-y-3">
${renderHeader(state, FileCode2, headerText)}
<div class="text-sm text-destructive">${getTextOutput(result) || i18n("An error occurred")}</div>
</div>
`,
isCustom: false,
};
}
// 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 = labels.complete;
// GET command: show code block with file content
if (command === "get") {
const fileContent = getTextOutput(result) || i18n("(no output)");
return {
content: html `
<div>
${renderCollapsibleHeader(state, FileCode2, renderHeaderWithPill(headerText, filename), contentRef, chevronRef, false)}
<div ${ref(contentRef)} class="max-h-0 overflow-hidden transition-all duration-300">
<code-block .code=${fileContent} language=${getLanguageFromFilename(filename)}></code-block>
</div>
</div>
`,
isCustom: false,
};
}
// LOGS command: show console block
if (command === "logs") {
const logs = getTextOutput(result) || i18n("(no output)");
return {
content: html `
<div>
${renderCollapsibleHeader(state, FileCode2, renderHeaderWithPill(headerText, filename), contentRef, chevronRef, false)}
<div ${ref(contentRef)} class="max-h-0 overflow-hidden transition-all duration-300">
<console-block .content=${logs}></console-block>
</div>
</div>
`,
isCustom: false,
};
}
// CREATE/UPDATE/REWRITE: always show code block, + console block for .html files
if (command === "create" || command === "rewrite") {
const codeContent = content || "";
const isHtml = filename?.endsWith(".html");
const logs = getTextOutput(result) || "";
return {
content: html `
<div>
${renderCollapsibleHeader(state, FileCode2, renderHeaderWithPill(headerText, filename), contentRef, chevronRef, false)}
<div ${ref(contentRef)} class="max-h-0 overflow-hidden transition-all duration-300 space-y-3">
${codeContent ? html `<code-block .code=${codeContent} language=${getLanguageFromFilename(filename)}></code-block>` : ""}
${isHtml && logs ? html `<console-block .content=${logs}></console-block>` : ""}
</div>
</div>
`,
isCustom: false,
};
}
if (command === "update") {
const isHtml = filename?.endsWith(".html");
const logs = getTextOutput(result) || "";
return {
content: html `
<div>
${renderCollapsibleHeader(state, FileCode2, renderHeaderWithPill(headerText, filename), contentRef, chevronRef, false)}
<div ${ref(contentRef)} class="max-h-0 overflow-hidden transition-all duration-300 space-y-3">
${Diff({ oldText: params.old_str || "", newText: params.new_str || "" })}
${isHtml && logs ? html `<console-block .content=${logs}></console-block>` : ""}
</div>
</div>
`,
isCustom: false,
};
}
// For DELETE, just show header
return {
content: html `
<div class="space-y-3">
${renderHeader(state, FileCode2, renderHeaderWithPill(headerText, filename))}
</div>
`,
isCustom: false,
};
}
// 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 { content: renderHeader(state, FileCode2, i18n("Preparing artifact...")), isCustom: false };
}
const labels = getCommandLabels(command);
const headerText = labels.streaming;
// Render based on command type
switch (command) {
case "create":
case "rewrite":
return {
content: html `
<div>
${renderCollapsibleHeader(state, FileCode2, renderHeaderWithPill(headerText, filename), 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)}></code-block>`
: ""}
</div>
</div>
`,
isCustom: false,
};
case "update":
return {
content: html `
<div>
${renderCollapsibleHeader(state, FileCode2, renderHeaderWithPill(headerText, filename), 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 })
: ""}
</div>
</div>
`,
isCustom: false,
};
case "get":
case "logs":
return {
content: html `
<div>
${renderCollapsibleHeader(state, FileCode2, renderHeaderWithPill(headerText, filename), contentRef, chevronRef, false)}
<div ${ref(contentRef)} class="max-h-0 overflow-hidden transition-all duration-300"></div>
</div>
`,
isCustom: false,
};
default:
return {
content: html `
<div>
${renderHeader(state, FileCode2, renderHeaderWithPill(headerText, filename))}
</div>
`,
isCustom: false,
};
}
}
// No params or result yet
return { content: renderHeader(state, FileCode2, i18n("Preparing artifact...")), isCustom: false };
}
}
//# sourceMappingURL=artifacts-tool-renderer.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,64 @@
import "@mariozechner/mini-lit/dist/MarkdownBlock.js";
import { type AgentTool, type Message } from "@mariozechner/pi-ai";
import { type Static } from "@sinclair/typebox";
import { LitElement, type TemplateResult } from "lit";
import type { Agent } from "../../agent/agent.js";
export interface Artifact {
filename: string;
content: string;
createdAt: Date;
updatedAt: Date;
}
declare const artifactsParamsSchema: import("@sinclair/typebox").TObject<{
command: import("@sinclair/typebox").TUnsafe<string>;
filename: import("@sinclair/typebox").TString;
content: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
old_str: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
new_str: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
}>;
export type ArtifactsParams = Static<typeof artifactsParamsSchema>;
export declare class ArtifactsPanel extends LitElement {
private _artifacts;
private _activeFilename;
private artifactElements;
private contentRef;
agent?: Agent;
sandboxUrlProvider?: () => string;
onArtifactsChange?: () => void;
onClose?: () => void;
onOpen?: () => void;
collapsed: boolean;
overlay: boolean;
get artifacts(): Map<string, Artifact>;
private getHtmlArtifactRuntimeProviders;
protected createRenderRoot(): HTMLElement | DocumentFragment;
connectedCallback(): void;
disconnectedCallback(): void;
private getFileType;
private getOrCreateArtifactElement;
private showArtifact;
openArtifact(filename: string): void;
get tool(): AgentTool<typeof artifactsParamsSchema, undefined>;
reconstructFromMessages(messages: Array<Message | {
role: "aborted";
} | {
role: "artifact";
}>): Promise<void>;
private executeCommand;
private waitForHtmlExecution;
private reloadAllHtmlArtifacts;
private createArtifact;
private updateArtifact;
private rewriteArtifact;
private getArtifact;
private deleteArtifact;
private getLogs;
render(): TemplateResult;
}
declare global {
interface HTMLElementTagNameMap {
"artifacts-panel": ArtifactsPanel;
}
}
export {};
//# sourceMappingURL=artifacts.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"artifacts.d.ts","sourceRoot":"","sources":["../../../src/tools/artifacts/artifacts.ts"],"names":[],"mappings":"AACA,OAAO,8CAA8C,CAAC;AAEtD,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,OAAO,EAA6B,MAAM,qBAAqB,CAAC;AAC9F,OAAO,EAAE,KAAK,MAAM,EAAQ,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAQ,UAAU,EAAE,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAI5D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAwBlD,MAAM,WAAW,QAAQ;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CAChB;AAGD,QAAA,MAAM,qBAAqB;;;;;;EAQzB,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEnE,qBACa,cAAe,SAAQ,UAAU;IACpC,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,eAAe,CAAuB;IAGvD,OAAO,CAAC,gBAAgB,CAAsC;IAC9D,OAAO,CAAC,UAAU,CAAoC;IAGtB,KAAK,CAAC,EAAE,KAAK,CAAC;IAEd,kBAAkB,CAAC,EAAE,MAAM,MAAM,CAAC;IAElC,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IAEvB,SAAS,UAAS;IAElB,OAAO,UAAS;IAG7C,IAAI,SAAS,0BAEZ;IAGD,OAAO,CAAC,+BAA+B;cAsBpB,gBAAgB,IAAI,WAAW,GAAG,gBAAgB;IAI5D,iBAAiB,IAAI,IAAI;IAmBzB,oBAAoB;IAM7B,OAAO,CAAC,WAAW;IAiDnB,OAAO,CAAC,0BAA0B;IA2DlC,OAAO,CAAC,YAAY;IAuBb,YAAY,CAAC,QAAQ,EAAE,MAAM;IASpC,IAAW,IAAI,IAAI,SAAS,CAAC,OAAO,qBAAqB,EAAE,SAAS,CAAC,CAmBpE;IAGY,uBAAuB,CACnC,QAAQ,EAAE,KAAK,CAAC,OAAO,GAAG;QAAE,IAAI,EAAE,SAAS,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,UAAU,CAAA;KAAE,CAAC,GACnE,OAAO,CAAC,IAAI,CAAC;YAuHF,cAAc;YAwBd,oBAAoB;IAiBlC,OAAO,CAAC,sBAAsB;YAWhB,cAAc;YAyCd,cAAc;YA4Cd,eAAe;IAwC7B,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,cAAc;IAqCtB,OAAO,CAAC,OAAO;IAeN,MAAM,IAAI,cAAc;CAqDjC;AAED,OAAO,CAAC,MAAM,CAAC;IACd,UAAU,qBAAqB;QAC9B,iBAAiB,EAAE,cAAc,CAAC;KAClC;CACD"}

View File

@@ -0,0 +1,659 @@
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { icon } from "@mariozechner/mini-lit";
import "@mariozechner/mini-lit/dist/MarkdownBlock.js";
import { Button } from "@mariozechner/mini-lit/dist/Button.js";
import { StringEnum } from "@mariozechner/pi-ai";
import { Type } from "@sinclair/typebox";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { createRef, ref } from "lit/directives/ref.js";
import { X } from "lucide";
import { ArtifactsRuntimeProvider } from "../../components/sandbox/ArtifactsRuntimeProvider.js";
import { AttachmentsRuntimeProvider } from "../../components/sandbox/AttachmentsRuntimeProvider.js";
import { ARTIFACTS_RUNTIME_PROVIDER_DESCRIPTION_RO, ARTIFACTS_TOOL_DESCRIPTION, ATTACHMENTS_RUNTIME_DESCRIPTION, } from "../../prompts/prompts.js";
import { i18n } from "../../utils/i18n.js";
import { DocxArtifact } from "./DocxArtifact.js";
import { ExcelArtifact } from "./ExcelArtifact.js";
import { GenericArtifact } from "./GenericArtifact.js";
import { HtmlArtifact } from "./HtmlArtifact.js";
import { ImageArtifact } from "./ImageArtifact.js";
import { MarkdownArtifact } from "./MarkdownArtifact.js";
import { PdfArtifact } from "./PdfArtifact.js";
import { SvgArtifact } from "./SvgArtifact.js";
import { TextArtifact } from "./TextArtifact.js";
// JSON-schema friendly parameters object (LLM-facing)
const artifactsParamsSchema = Type.Object({
command: StringEnum(["create", "update", "rewrite", "get", "delete", "logs"], {
description: "The operation to perform",
}),
filename: Type.String({ description: "Filename including extension (e.g., 'index.html', 'script.js')" }),
content: Type.Optional(Type.String({ description: "File content" })),
old_str: Type.Optional(Type.String({ description: "String to replace (for update command)" })),
new_str: Type.Optional(Type.String({ description: "Replacement string (for update command)" })),
});
let ArtifactsPanel = class ArtifactsPanel extends LitElement {
constructor() {
super(...arguments);
this._artifacts = new Map();
this._activeFilename = null;
// Programmatically managed artifact elements
this.artifactElements = new Map();
this.contentRef = createRef();
// Collapsed mode: hides panel content but can show a floating reopen pill
this.collapsed = false;
// Overlay mode: when true, panel renders full-screen overlay (mobile)
this.overlay = false;
}
// Public getter for artifacts
get artifacts() {
return this._artifacts;
}
// Get runtime providers for HTML artifacts (read-only: attachments + artifacts)
getHtmlArtifactRuntimeProviders() {
const providers = [];
// Get attachments from agent messages
if (this.agent) {
const attachments = [];
for (const message of this.agent.state.messages) {
if (message.role === "user" && message.attachments) {
attachments.push(...message.attachments);
}
}
if (attachments.length > 0) {
providers.push(new AttachmentsRuntimeProvider(attachments));
}
}
// Add read-only artifacts provider
providers.push(new ArtifactsRuntimeProvider(this, this.agent, false));
return providers;
}
createRenderRoot() {
return this; // light DOM for shared styles
}
connectedCallback() {
super.connectedCallback();
this.style.display = "block";
this.style.height = "100%";
// Reattach existing artifact elements when panel is re-inserted into the DOM
requestAnimationFrame(() => {
const container = this.contentRef.value;
if (!container)
return;
// Ensure we have an active filename
if (!this._activeFilename && this._artifacts.size > 0) {
this._activeFilename = Array.from(this._artifacts.keys())[0];
}
this.artifactElements.forEach((element, name) => {
if (!element.parentElement)
container.appendChild(element);
element.style.display = name === this._activeFilename ? "block" : "none";
});
});
}
disconnectedCallback() {
super.disconnectedCallback();
// Do not tear down artifact elements; keep them to restore on next mount
}
// Helper to determine file type from extension
getFileType(filename) {
const ext = filename.split(".").pop()?.toLowerCase();
if (ext === "html")
return "html";
if (ext === "svg")
return "svg";
if (ext === "md" || ext === "markdown")
return "markdown";
if (ext === "pdf")
return "pdf";
if (ext === "xlsx" || ext === "xls")
return "excel";
if (ext === "docx")
return "docx";
if (ext === "png" ||
ext === "jpg" ||
ext === "jpeg" ||
ext === "gif" ||
ext === "webp" ||
ext === "bmp" ||
ext === "ico")
return "image";
// Text files
if (ext === "txt" ||
ext === "json" ||
ext === "xml" ||
ext === "yaml" ||
ext === "yml" ||
ext === "csv" ||
ext === "js" ||
ext === "ts" ||
ext === "jsx" ||
ext === "tsx" ||
ext === "py" ||
ext === "java" ||
ext === "c" ||
ext === "cpp" ||
ext === "h" ||
ext === "css" ||
ext === "scss" ||
ext === "sass" ||
ext === "less" ||
ext === "sh")
return "text";
// Everything else gets generic fallback
return "generic";
}
// Get or create artifact element
getOrCreateArtifactElement(filename, content) {
let element = this.artifactElements.get(filename);
if (!element) {
const type = this.getFileType(filename);
if (type === "html") {
element = new HtmlArtifact();
element.runtimeProviders = this.getHtmlArtifactRuntimeProviders();
if (this.sandboxUrlProvider) {
element.sandboxUrlProvider = this.sandboxUrlProvider;
}
}
else if (type === "svg") {
element = new SvgArtifact();
}
else if (type === "markdown") {
element = new MarkdownArtifact();
}
else if (type === "image") {
element = new ImageArtifact();
}
else if (type === "pdf") {
element = new PdfArtifact();
}
else if (type === "excel") {
element = new ExcelArtifact();
}
else if (type === "docx") {
element = new DocxArtifact();
}
else if (type === "text") {
element = new TextArtifact();
}
else {
element = new GenericArtifact();
}
element.filename = filename;
element.content = content;
element.style.display = "none";
element.style.height = "100%";
// Store element
this.artifactElements.set(filename, element);
// Add to DOM - try immediately if container exists, otherwise schedule
const newElement = element;
if (this.contentRef.value) {
this.contentRef.value.appendChild(newElement);
}
else {
requestAnimationFrame(() => {
if (this.contentRef.value && !newElement.parentElement) {
this.contentRef.value.appendChild(newElement);
}
});
}
}
else {
// Just update content
element.content = content;
if (element instanceof HtmlArtifact) {
element.runtimeProviders = this.getHtmlArtifactRuntimeProviders();
}
}
return element;
}
// Show/hide artifact elements
showArtifact(filename) {
// Ensure the active element is in the DOM
requestAnimationFrame(() => {
this.artifactElements.forEach((element, name) => {
if (this.contentRef.value && !element.parentElement) {
this.contentRef.value.appendChild(element);
}
element.style.display = name === filename ? "block" : "none";
});
});
this._activeFilename = filename;
this.requestUpdate(); // Only for tab bar update
// Scroll the active tab into view after render
requestAnimationFrame(() => {
const activeButton = this.querySelector(`button[data-filename="${filename}"]`);
if (activeButton) {
activeButton.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center" });
}
});
}
// Open panel and focus an artifact tab by filename
openArtifact(filename) {
if (this._artifacts.has(filename)) {
this.showArtifact(filename);
// Ask host to open panel (AgentInterface demo listens to onOpen)
this.onOpen?.();
}
}
// Build the AgentTool (no details payload; return only output strings)
get tool() {
return {
label: "Artifacts",
name: "artifacts",
get description() {
// HTML artifacts have read-only access to attachments and artifacts
const runtimeProviderDescriptions = [
ATTACHMENTS_RUNTIME_DESCRIPTION,
ARTIFACTS_RUNTIME_PROVIDER_DESCRIPTION_RO,
];
return ARTIFACTS_TOOL_DESCRIPTION(runtimeProviderDescriptions);
},
parameters: artifactsParamsSchema,
// Execute mutates our local store and returns a plain output
execute: async (_toolCallId, args, _signal) => {
const output = await this.executeCommand(args);
return { content: [{ type: "text", text: output }], details: undefined };
},
};
}
// Re-apply artifacts by scanning a message list (optional utility)
async reconstructFromMessages(messages) {
const toolCalls = new Map();
const artifactToolName = "artifacts";
// 1) Collect tool calls from assistant messages
for (const message of messages) {
if (message.role === "assistant") {
for (const block of message.content) {
if (block.type === "toolCall" && block.name === artifactToolName) {
toolCalls.set(block.id, block);
}
}
}
}
// 2) Build an ordered list of successful artifact operations
const operations = [];
for (const m of messages) {
if (m.role === "artifact") {
const artifactMsg = m;
switch (artifactMsg.action) {
case "create":
operations.push({
command: "create",
filename: artifactMsg.filename,
content: artifactMsg.content,
});
break;
case "update":
operations.push({
command: "rewrite",
filename: artifactMsg.filename,
content: artifactMsg.content,
});
break;
case "delete":
operations.push({
command: "delete",
filename: artifactMsg.filename,
});
break;
}
}
// Handle tool result messages (from artifacts tool calls)
else if (m.role === "toolResult" && m.toolName === artifactToolName && !m.isError) {
const toolCallId = m.toolCallId;
const call = toolCalls.get(toolCallId);
if (!call)
continue;
const params = call.arguments;
if (params.command === "get" || params.command === "logs")
continue; // no state change
operations.push(params);
}
}
// 3) Compute final state per filename by simulating operations in-memory
const finalArtifacts = new Map();
for (const op of operations) {
const filename = op.filename;
switch (op.command) {
case "create": {
if (op.content) {
finalArtifacts.set(filename, op.content);
}
break;
}
case "rewrite": {
if (op.content) {
finalArtifacts.set(filename, op.content);
}
break;
}
case "update": {
let existing = finalArtifacts.get(filename);
if (!existing)
break; // skip invalid update (shouldn't happen for successful results)
if (op.old_str !== undefined && op.new_str !== undefined) {
existing = existing.replace(op.old_str, op.new_str);
finalArtifacts.set(filename, existing);
}
break;
}
case "delete": {
finalArtifacts.delete(filename);
break;
}
case "get":
case "logs":
// Ignored above, just for completeness
break;
}
}
// 4) Reset current UI state before bulk create
this._artifacts.clear();
this.artifactElements.forEach((el) => {
el.remove();
});
this.artifactElements.clear();
this._activeFilename = null;
this._artifacts = new Map(this._artifacts);
// 5) Create artifacts in a single pass without waiting for iframe execution or tab switching
for (const [filename, content] of finalArtifacts.entries()) {
const createParams = { command: "create", filename, content };
try {
await this.createArtifact(createParams, { skipWait: true, silent: true });
}
catch {
// Ignore failures during reconstruction
}
}
// 6) Show first artifact if any exist, and notify listeners once
if (!this._activeFilename && this._artifacts.size > 0) {
this.showArtifact(Array.from(this._artifacts.keys())[0]);
}
this.onArtifactsChange?.();
this.requestUpdate();
}
// Core command executor
async executeCommand(params, options = {}) {
switch (params.command) {
case "create":
return await this.createArtifact(params, options);
case "update":
return await this.updateArtifact(params, options);
case "rewrite":
return await this.rewriteArtifact(params, options);
case "get":
return this.getArtifact(params);
case "delete":
return this.deleteArtifact(params);
case "logs":
return this.getLogs(params);
default:
// Should never happen with TypeBox validation
return `Error: Unknown command ${params.command}`;
}
}
// Wait for HTML artifact execution and get logs
async waitForHtmlExecution(filename) {
const element = this.artifactElements.get(filename);
if (!(element instanceof HtmlArtifact)) {
return "";
}
return new Promise((resolve) => {
// Fallback timeout - just get logs after execution should complete
setTimeout(() => {
// Get whatever logs we have
const logs = element.getLogs();
resolve(logs);
}, 1500);
});
}
// Reload all HTML artifacts (called when any artifact changes)
reloadAllHtmlArtifacts() {
this.artifactElements.forEach((element) => {
if (element instanceof HtmlArtifact && element.sandboxIframeRef.value) {
// Update runtime providers with latest artifact state
element.runtimeProviders = this.getHtmlArtifactRuntimeProviders();
// Re-execute the HTML content
element.executeContent(element.content);
}
});
}
async createArtifact(params, options = {}) {
if (!params.filename || !params.content) {
return "Error: create command requires filename and content";
}
if (this._artifacts.has(params.filename)) {
return `Error: File ${params.filename} already exists`;
}
const artifact = {
filename: params.filename,
content: params.content,
createdAt: new Date(),
updatedAt: new Date(),
};
this._artifacts.set(params.filename, artifact);
this._artifacts = new Map(this._artifacts);
// Create or update element
this.getOrCreateArtifactElement(params.filename, params.content);
if (!options.silent) {
this.showArtifact(params.filename);
this.onArtifactsChange?.();
this.requestUpdate();
}
// Reload all HTML artifacts since they might depend on this new artifact
this.reloadAllHtmlArtifacts();
// For HTML files, wait for execution
let result = `Created file ${params.filename}`;
if (this.getFileType(params.filename) === "html" && !options.skipWait) {
const logs = await this.waitForHtmlExecution(params.filename);
result += `\n${logs}`;
}
return result;
}
async updateArtifact(params, options = {}) {
const artifact = this._artifacts.get(params.filename);
if (!artifact) {
const files = Array.from(this._artifacts.keys());
if (files.length === 0)
return `Error: File ${params.filename} not found. No files have been created yet.`;
return `Error: File ${params.filename} not found. Available files: ${files.join(", ")}`;
}
if (!params.old_str || params.new_str === undefined) {
return "Error: update command requires old_str and new_str";
}
if (!artifact.content.includes(params.old_str)) {
return `Error: String not found in file. Here is the full content:\n\n${artifact.content}`;
}
artifact.content = artifact.content.replace(params.old_str, params.new_str);
artifact.updatedAt = new Date();
this._artifacts.set(params.filename, artifact);
// Update element
this.getOrCreateArtifactElement(params.filename, artifact.content);
if (!options.silent) {
this.onArtifactsChange?.();
this.requestUpdate();
}
// Show the artifact
this.showArtifact(params.filename);
// Reload all HTML artifacts since they might depend on this updated artifact
this.reloadAllHtmlArtifacts();
// For HTML files, wait for execution
let result = `Updated file ${params.filename}`;
if (this.getFileType(params.filename) === "html" && !options.skipWait) {
const logs = await this.waitForHtmlExecution(params.filename);
result += `\n${logs}`;
}
return result;
}
async rewriteArtifact(params, options = {}) {
const artifact = this._artifacts.get(params.filename);
if (!artifact) {
const files = Array.from(this._artifacts.keys());
if (files.length === 0)
return `Error: File ${params.filename} not found. No files have been created yet.`;
return `Error: File ${params.filename} not found. Available files: ${files.join(", ")}`;
}
if (!params.content) {
return "Error: rewrite command requires content";
}
artifact.content = params.content;
artifact.updatedAt = new Date();
this._artifacts.set(params.filename, artifact);
// Update element
this.getOrCreateArtifactElement(params.filename, artifact.content);
if (!options.silent) {
this.onArtifactsChange?.();
}
// Show the artifact
this.showArtifact(params.filename);
// Reload all HTML artifacts since they might depend on this rewritten artifact
this.reloadAllHtmlArtifacts();
// For HTML files, wait for execution
let result = "";
if (this.getFileType(params.filename) === "html" && !options.skipWait) {
const logs = await this.waitForHtmlExecution(params.filename);
result += `\n${logs}`;
}
return result;
}
getArtifact(params) {
const artifact = this._artifacts.get(params.filename);
if (!artifact) {
const files = Array.from(this._artifacts.keys());
if (files.length === 0)
return `Error: File ${params.filename} not found. No files have been created yet.`;
return `Error: File ${params.filename} not found. Available files: ${files.join(", ")}`;
}
return artifact.content;
}
deleteArtifact(params) {
const artifact = this._artifacts.get(params.filename);
if (!artifact) {
const files = Array.from(this._artifacts.keys());
if (files.length === 0)
return `Error: File ${params.filename} not found. No files have been created yet.`;
return `Error: File ${params.filename} not found. Available files: ${files.join(", ")}`;
}
this._artifacts.delete(params.filename);
this._artifacts = new Map(this._artifacts);
// Remove element
const element = this.artifactElements.get(params.filename);
if (element) {
element.remove();
this.artifactElements.delete(params.filename);
}
// Show another artifact if this was active
if (this._activeFilename === params.filename) {
const remaining = Array.from(this._artifacts.keys());
if (remaining.length > 0) {
this.showArtifact(remaining[0]);
}
else {
this._activeFilename = null;
this.requestUpdate();
}
}
this.onArtifactsChange?.();
this.requestUpdate();
// Reload all HTML artifacts since they might have depended on this deleted artifact
this.reloadAllHtmlArtifacts();
return `Deleted file ${params.filename}`;
}
getLogs(params) {
const element = this.artifactElements.get(params.filename);
if (!element) {
const files = Array.from(this._artifacts.keys());
if (files.length === 0)
return `Error: File ${params.filename} not found. No files have been created yet.`;
return `Error: File ${params.filename} not found. Available files: ${files.join(", ")}`;
}
if (!(element instanceof HtmlArtifact)) {
return `Error: File ${params.filename} is not an HTML file. Logs are only available for HTML files.`;
}
return element.getLogs();
}
render() {
const artifacts = Array.from(this._artifacts.values());
// Panel is hidden when collapsed OR when there are no artifacts
const showPanel = artifacts.length > 0 && !this.collapsed;
return html `
<div
class="${showPanel ? "" : "hidden"} ${this.overlay ? "fixed inset-0 z-40 pointer-events-auto backdrop-blur-sm bg-background/95" : "relative"} h-full flex flex-col bg-background text-card-foreground ${!this.overlay ? "border-l border-border" : ""} overflow-hidden shadow-xl"
>
<!-- Tab bar (always shown when there are artifacts) -->
<div class="flex items-center justify-between border-b border-border bg-background">
<div class="flex overflow-x-auto">
${artifacts.map((a) => {
const isActive = a.filename === this._activeFilename;
const activeClass = isActive
? "border-primary text-primary"
: "border-transparent text-muted-foreground hover:text-foreground";
return html `
<button
class="px-3 py-2 whitespace-nowrap border-b-2 ${activeClass}"
data-filename="${a.filename}"
@click=${() => this.showArtifact(a.filename)}
>
<span class="font-mono text-xs">${a.filename}</span>
</button>
`;
})}
</div>
<div class="flex items-center gap-1 px-2">
${(() => {
const active = this._activeFilename ? this.artifactElements.get(this._activeFilename) : undefined;
return active ? active.getHeaderButtons() : "";
})()}
${Button({
variant: "ghost",
size: "sm",
onClick: () => this.onClose?.(),
title: i18n("Close artifacts"),
children: icon(X, "sm"),
})}
</div>
</div>
<!-- Content area where artifact elements are added programmatically -->
<div class="flex-1 overflow-hidden" ${ref(this.contentRef)}></div>
</div>
`;
}
};
__decorate([
state()
], ArtifactsPanel.prototype, "_artifacts", void 0);
__decorate([
state()
], ArtifactsPanel.prototype, "_activeFilename", void 0);
__decorate([
property({ attribute: false })
], ArtifactsPanel.prototype, "agent", void 0);
__decorate([
property({ attribute: false })
], ArtifactsPanel.prototype, "sandboxUrlProvider", void 0);
__decorate([
property({ attribute: false })
], ArtifactsPanel.prototype, "onArtifactsChange", void 0);
__decorate([
property({ attribute: false })
], ArtifactsPanel.prototype, "onClose", void 0);
__decorate([
property({ attribute: false })
], ArtifactsPanel.prototype, "onOpen", void 0);
__decorate([
property({ type: Boolean })
], ArtifactsPanel.prototype, "collapsed", void 0);
__decorate([
property({ type: Boolean })
], ArtifactsPanel.prototype, "overlay", void 0);
ArtifactsPanel = __decorate([
customElement("artifacts-panel")
], ArtifactsPanel);
export { ArtifactsPanel };
//# sourceMappingURL=artifacts.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,8 @@
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";
export { TextArtifact } from "./TextArtifact.js";
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/artifacts/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,KAAK,QAAQ,EAAE,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACrF,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}

View File

@@ -0,0 +1,8 @@
export { ArtifactElement } from "./ArtifactElement.js";
export { ArtifactsPanel } 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";
export { TextArtifact } from "./TextArtifact.js";
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tools/artifacts/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAiB,cAAc,EAAwB,MAAM,gBAAgB,CAAC;AACrF,OAAO,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}

View File

@@ -0,0 +1,24 @@
import type { AgentTool } from "@mariozechner/pi-ai";
import { type Static } from "@sinclair/typebox";
import type { ToolRenderer } from "./types.js";
declare const extractDocumentSchema: import("@sinclair/typebox").TObject<{
url: import("@sinclair/typebox").TString;
}>;
export type ExtractDocumentParams = Static<typeof extractDocumentSchema>;
export interface ExtractDocumentResult {
extractedText: string;
format: string;
fileName: string;
size: number;
}
export declare function createExtractDocumentTool(): AgentTool<typeof extractDocumentSchema, ExtractDocumentResult> & {
corsProxyUrl?: string;
};
export declare const extractDocumentTool: AgentTool<import("@sinclair/typebox").TObject<{
url: import("@sinclair/typebox").TString;
}>, ExtractDocumentResult> & {
corsProxyUrl?: string;
};
export declare const extractDocumentRenderer: ToolRenderer<ExtractDocumentParams, ExtractDocumentResult>;
export {};
//# sourceMappingURL=extract-document.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"extract-document.d.ts","sourceRoot":"","sources":["../../src/tools/extract-document.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAqB,MAAM,qBAAqB,CAAC;AACxE,OAAO,EAAE,KAAK,MAAM,EAAQ,MAAM,mBAAmB,CAAC;AAQtD,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,YAAY,CAAC;AAMjE,QAAA,MAAM,qBAAqB;;EAIzB,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEzE,MAAM,WAAW,qBAAqB;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACb;AAMD,wBAAgB,yBAAyB,IAAI,SAAS,CAAC,OAAO,qBAAqB,EAAE,qBAAqB,CAAC,GAAG;IAC7G,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB,CA+IA;AAGD,eAAO,MAAM,mBAAmB;;;mBAnJhB,MAAM;CAmJwC,CAAC;AAM/D,eAAO,MAAM,uBAAuB,EAAE,YAAY,CAAC,qBAAqB,EAAE,qBAAqB,CAkF9F,CAAC"}

View File

@@ -0,0 +1,216 @@
import { Type } from "@sinclair/typebox";
import { html } from "lit";
import { createRef, ref } from "lit/directives/ref.js";
import { FileText } from "lucide";
import { EXTRACT_DOCUMENT_DESCRIPTION } from "../prompts/prompts.js";
import { loadAttachment } from "../utils/attachment-utils.js";
import { isCorsError } from "../utils/proxy-utils.js";
import { registerToolRenderer, renderCollapsibleHeader, renderHeader } from "./renderer-registry.js";
// ============================================================================
// TYPES
// ============================================================================
const extractDocumentSchema = Type.Object({
url: Type.String({
description: "URL of the document to extract text from (PDF, DOCX, XLSX, or PPTX)",
}),
});
// ============================================================================
// TOOL
// ============================================================================
export function createExtractDocumentTool() {
const tool = {
label: "Extract Document",
name: "extract_document",
corsProxyUrl: undefined, // Can be set by consumer (e.g., from user settings)
description: EXTRACT_DOCUMENT_DESCRIPTION,
parameters: extractDocumentSchema,
execute: async (_toolCallId, args, signal) => {
if (signal?.aborted) {
throw new Error("Extract document aborted");
}
const url = args.url.trim();
if (!url) {
throw new Error("URL is required");
}
// Validate URL format
try {
new URL(url);
}
catch {
throw new Error(`Invalid URL: ${url}`);
}
// Size limit: 50MB
const MAX_SIZE = 50 * 1024 * 1024;
// Helper function to fetch and process document
const fetchAndProcess = async (fetchUrl) => {
const response = await fetch(fetchUrl, { signal });
if (!response.ok) {
throw new Error(`TELL USER: Unable to download the document (${response.status} ${response.statusText}). The site likely blocks automated downloads.\n\n` +
`INSTRUCT USER: Please download the file manually and attach it to your message using the attachment button (paperclip icon) in the message input area. I can then extract the text from the attached file.`);
}
// Check size before downloading
const contentLength = response.headers.get("content-length");
if (contentLength) {
const size = Number.parseInt(contentLength, 10);
if (size > MAX_SIZE) {
throw new Error(`Document is too large (${(size / 1024 / 1024).toFixed(1)}MB). Maximum supported size is 50MB.`);
}
}
// Download the document
const arrayBuffer = await response.arrayBuffer();
const size = arrayBuffer.byteLength;
if (size > MAX_SIZE) {
throw new Error(`Document is too large (${(size / 1024 / 1024).toFixed(1)}MB). Maximum supported size is 50MB.`);
}
return arrayBuffer;
};
// Try without proxy first, fallback to proxy on CORS error
let arrayBuffer;
try {
// Attempt direct fetch first
arrayBuffer = await fetchAndProcess(url);
}
catch (directError) {
// If CORS error and proxy is available, retry with proxy
if (isCorsError(directError) && tool.corsProxyUrl) {
try {
const proxiedUrl = tool.corsProxyUrl + encodeURIComponent(url);
arrayBuffer = await fetchAndProcess(proxiedUrl);
}
catch (proxyError) {
// Proxy fetch also failed - throw helpful message
throw new Error(`TELL USER: Unable to fetch the document due to CORS restrictions.\n\n` +
`Tried with proxy but it also failed: ${proxyError.message}\n\n` +
`INSTRUCT USER: Please download the file manually and attach it to your message using the attachment button (paperclip icon) in the message input area. I can then extract the text from the attached file.`);
}
}
else if (isCorsError(directError) && !tool.corsProxyUrl) {
// CORS error but no proxy configured
throw new Error(`TELL USER: Unable to fetch the document due to CORS restrictions (the server blocks requests from browser extensions).\n\n` +
`To fix this, you need to configure a CORS proxy in Sitegeist settings:\n` +
`1. Open Sitegeist settings\n` +
`2. Find "CORS Proxy URL" setting\n` +
`3. Enter a proxy URL like: https://corsproxy.io/?\n` +
`4. Save and try again\n\n` +
`Alternatively, download the file manually and attach it to your message using the attachment button (paperclip icon).`);
}
else {
// Not a CORS error - re-throw
throw directError;
}
}
// Extract filename from URL
const urlParts = url.split("/");
let fileName = urlParts[urlParts.length - 1]?.split("?")[0] || "document";
if (url.startsWith("https://arxiv.org/")) {
fileName = fileName + ".pdf";
}
// Use loadAttachment to process the document
const attachment = await loadAttachment(arrayBuffer, fileName);
if (!attachment.extractedText) {
throw new Error(`Document format not supported. Supported formats:\n` +
`- PDF (.pdf)\n` +
`- Word (.docx)\n` +
`- Excel (.xlsx, .xls)\n` +
`- PowerPoint (.pptx)`);
}
// Determine format from attachment
let format = "unknown";
if (attachment.mimeType.includes("pdf")) {
format = "pdf";
}
else if (attachment.mimeType.includes("wordprocessingml")) {
format = "docx";
}
else if (attachment.mimeType.includes("spreadsheetml") || attachment.mimeType.includes("ms-excel")) {
format = "xlsx";
}
else if (attachment.mimeType.includes("presentationml")) {
format = "pptx";
}
return {
content: [{ type: "text", text: attachment.extractedText }],
details: {
extractedText: attachment.extractedText,
format,
fileName: attachment.fileName,
size: attachment.size,
},
};
},
};
return tool;
}
// Export a default instance
export const extractDocumentTool = createExtractDocumentTool();
// ============================================================================
// RENDERER
// ============================================================================
export const extractDocumentRenderer = {
render(params, result, isStreaming) {
// Determine status
const state = result ? (result.isError ? "error" : "complete") : isStreaming ? "inprogress" : "complete";
// Create refs for collapsible sections
const contentRef = createRef();
const chevronRef = createRef();
// With result: show params + result
if (result && params) {
const details = result.details;
const title = details
? result.isError
? `Failed to extract ${details.fileName || "document"}`
: `Extracted text from ${details.fileName} (${details.format.toUpperCase()}, ${(details.size / 1024).toFixed(1)}KB)`
: result.isError
? "Failed to extract document"
: "Extracted text from document";
const output = result.content
?.filter((c) => c.type === "text")
.map((c) => c.text)
.join("\n") || "";
return {
content: html `
<div>
${renderCollapsibleHeader(state, FileText, title, contentRef, chevronRef, false)}
<div ${ref(contentRef)} class="max-h-0 overflow-hidden transition-all duration-300 space-y-3">
${params.url
? html `<div class="text-sm text-gray-600 dark:text-gray-400">
<strong>URL:</strong> ${params.url}
</div>`
: ""}
${output && !result.isError
? html `<code-block .code=${output} language="plaintext"></code-block>`
: ""}
${result.isError && output
? html `<console-block .content=${output} .variant=${"error"}></console-block>`
: ""}
</div>
</div>
`,
isCustom: false,
};
}
// Just params (streaming or waiting for result)
if (params) {
const title = "Extracting document...";
return {
content: html `
<div>
${renderCollapsibleHeader(state, FileText, title, contentRef, chevronRef, false)}
<div ${ref(contentRef)} class="max-h-0 overflow-hidden transition-all duration-300">
<div class="text-sm text-gray-600 dark:text-gray-400"><strong>URL:</strong> ${params.url}</div>
</div>
</div>
`,
isCustom: false,
};
}
// No params or result yet
return {
content: renderHeader(state, FileText, "Preparing extraction..."),
isCustom: false,
};
},
};
// Auto-register the renderer
registerToolRenderer("extract_document", extractDocumentRenderer);
//# sourceMappingURL=extract-document.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,16 @@
import type { ToolResultMessage } from "@mariozechner/pi-ai";
import "./javascript-repl.js";
import "./extract-document.js";
import { getToolRenderer, registerToolRenderer } from "./renderer-registry.js";
import type { ToolRenderResult } from "./types.js";
/**
* Enable or disable show JSON mode
* When enabled, all tool renderers will use the default JSON renderer
*/
export declare function setShowJsonMode(enabled: boolean): void;
/**
* Render tool - unified function that handles params, result, and streaming state
*/
export declare function renderTool(toolName: string, params: any | undefined, result: ToolResultMessage | undefined, isStreaming?: boolean): ToolRenderResult;
export { getToolRenderer, registerToolRenderer };
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,sBAAsB,CAAC;AAC9B,OAAO,uBAAuB,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAG/E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAUnD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAEtD;AAED;;GAEG;AACH,wBAAgB,UAAU,CACzB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,GAAG,GAAG,SAAS,EACvB,MAAM,EAAE,iBAAiB,GAAG,SAAS,EACrC,WAAW,CAAC,EAAE,OAAO,GACnB,gBAAgB,CAWlB;AAED,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,CAAC"}

View File

@@ -0,0 +1,33 @@
import "./javascript-repl.js"; // Auto-registers the renderer
import "./extract-document.js"; // Auto-registers the renderer
import { getToolRenderer, registerToolRenderer } from "./renderer-registry.js";
import { BashRenderer } from "./renderers/BashRenderer.js";
import { DefaultRenderer } from "./renderers/DefaultRenderer.js";
// Register all built-in tool renderers
registerToolRenderer("bash", new BashRenderer());
const defaultRenderer = new DefaultRenderer();
// Global flag to force default JSON rendering for all tools
let showJsonMode = false;
/**
* Enable or disable show JSON mode
* When enabled, all tool renderers will use the default JSON renderer
*/
export function setShowJsonMode(enabled) {
showJsonMode = enabled;
}
/**
* Render tool - unified function that handles params, result, and streaming state
*/
export function renderTool(toolName, params, result, isStreaming) {
// If showJsonMode is enabled, always use the default renderer
if (showJsonMode) {
return defaultRenderer.render(params, result, isStreaming);
}
const renderer = getToolRenderer(toolName);
if (renderer) {
return renderer.render(params, result, isStreaming);
}
return defaultRenderer.render(params, result, isStreaming);
}
export { getToolRenderer, registerToolRenderer };
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AACA,OAAO,sBAAsB,CAAC,CAAC,8BAA8B;AAC7D,OAAO,uBAAuB,CAAC,CAAC,8BAA8B;AAC9D,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC/E,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAGjE,uCAAuC;AACvC,oBAAoB,CAAC,MAAM,EAAE,IAAI,YAAY,EAAE,CAAC,CAAC;AAEjD,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;AAE9C,4DAA4D;AAC5D,IAAI,YAAY,GAAG,KAAK,CAAC;AAEzB;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,OAAgB;IAC/C,YAAY,GAAG,OAAO,CAAC;AACxB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CACzB,QAAgB,EAChB,MAAuB,EACvB,MAAqC,EACrC,WAAqB;IAErB,8DAA8D;IAC9D,IAAI,YAAY,EAAE,CAAC;QAClB,OAAO,eAAe,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,QAAQ,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IAC3C,IAAI,QAAQ,EAAE,CAAC;QACd,OAAO,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;IACrD,CAAC;IACD,OAAO,eAAe,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;AAC5D,CAAC;AAED,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,CAAC"}

View File

@@ -0,0 +1,44 @@
import type { AgentTool } from "@mariozechner/pi-ai";
import { type Static } from "@sinclair/typebox";
import { type SandboxFile } from "../components/SandboxedIframe.js";
import type { SandboxRuntimeProvider } from "../components/sandbox/SandboxRuntimeProvider.js";
import type { ToolRenderer } from "./types.js";
export declare function executeJavaScript(code: string, runtimeProviders: SandboxRuntimeProvider[], signal?: AbortSignal, sandboxUrlProvider?: () => string): Promise<{
output: string;
files?: SandboxFile[];
}>;
export type JavaScriptReplToolResult = {
files?: {
fileName: string;
contentBase64: string;
mimeType: string;
}[] | undefined;
};
declare const javascriptReplSchema: import("@sinclair/typebox").TObject<{
title: import("@sinclair/typebox").TString;
code: import("@sinclair/typebox").TString;
}>;
export type JavaScriptReplParams = Static<typeof javascriptReplSchema>;
interface JavaScriptReplResult {
output?: string;
files?: Array<{
fileName: string;
mimeType: string;
size: number;
contentBase64: string;
}>;
}
export declare function createJavaScriptReplTool(): AgentTool<typeof javascriptReplSchema, JavaScriptReplToolResult> & {
runtimeProvidersFactory?: () => SandboxRuntimeProvider[];
sandboxUrlProvider?: () => string;
};
export declare const javascriptReplTool: AgentTool<import("@sinclair/typebox").TObject<{
title: import("@sinclair/typebox").TString;
code: import("@sinclair/typebox").TString;
}>, JavaScriptReplToolResult> & {
runtimeProvidersFactory?: () => SandboxRuntimeProvider[];
sandboxUrlProvider?: () => string;
};
export declare const javascriptReplRenderer: ToolRenderer<JavaScriptReplParams, JavaScriptReplResult>;
export {};
//# sourceMappingURL=javascript-repl.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"javascript-repl.d.ts","sourceRoot":"","sources":["../../src/tools/javascript-repl.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAqB,MAAM,qBAAqB,CAAC;AACxE,OAAO,EAAE,KAAK,MAAM,EAAQ,MAAM,mBAAmB,CAAC;AAItD,OAAO,EAAE,KAAK,WAAW,EAAqC,MAAM,kCAAkC,CAAC;AACvG,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,iDAAiD,CAAC;AAI9F,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,YAAY,CAAC;AAGjE,wBAAsB,iBAAiB,CACtC,IAAI,EAAE,MAAM,EACZ,gBAAgB,EAAE,sBAAsB,EAAE,EAC1C,MAAM,CAAC,EAAE,WAAW,EACpB,kBAAkB,CAAC,EAAE,MAAM,MAAM,GAC/B,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,WAAW,EAAE,CAAA;CAAE,CAAC,CA2EpD;AAED,MAAM,MAAM,wBAAwB,GAAG;IACtC,KAAK,CAAC,EACH;QACA,QAAQ,EAAE,MAAM,CAAC;QACjB,aAAa,EAAE,MAAM,CAAC;QACtB,QAAQ,EAAE,MAAM,CAAC;KAChB,EAAE,GACH,SAAS,CAAC;CACb,CAAC;AAEF,QAAA,MAAM,oBAAoB;;;EAMxB,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,MAAM,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAEvE,UAAU,oBAAoB;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,KAAK,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,aAAa,EAAE,MAAM,CAAC;KACtB,CAAC,CAAC;CACH;AAED,wBAAgB,wBAAwB,IAAI,SAAS,CAAC,OAAO,oBAAoB,EAAE,wBAAwB,CAAC,GAAG;IAC9G,uBAAuB,CAAC,EAAE,MAAM,sBAAsB,EAAE,CAAC;IACzD,kBAAkB,CAAC,EAAE,MAAM,MAAM,CAAC;CAClC,CAgEA;AAGD,eAAO,MAAM,kBAAkB;;;;8BArEJ,MAAM,sBAAsB,EAAE;yBACnC,MAAM,MAAM;CAoE0B,CAAC;AAE7D,eAAO,MAAM,sBAAsB,EAAE,YAAY,CAAC,oBAAoB,EAAE,oBAAoB,CA0F3F,CAAC"}

View File

@@ -0,0 +1,224 @@
import { i18n } from "@mariozechner/mini-lit";
import { Type } from "@sinclair/typebox";
import { html } from "lit";
import { createRef, ref } from "lit/directives/ref.js";
import { Code } from "lucide";
import { SandboxIframe } from "../components/SandboxedIframe.js";
import { JAVASCRIPT_REPL_TOOL_DESCRIPTION } from "../prompts/prompts.js";
import { registerToolRenderer, renderCollapsibleHeader, renderHeader } from "./renderer-registry.js";
// Execute JavaScript code with attachments using SandboxedIframe
export async function executeJavaScript(code, runtimeProviders, signal, sandboxUrlProvider) {
if (!code) {
throw new Error("Code parameter is required");
}
// Check for abort before starting
if (signal?.aborted) {
throw new Error("Execution aborted");
}
// Create a SandboxedIframe instance for execution
const sandbox = new SandboxIframe();
if (sandboxUrlProvider) {
sandbox.sandboxUrlProvider = sandboxUrlProvider;
}
sandbox.style.display = "none";
document.body.appendChild(sandbox);
try {
const sandboxId = `repl-${Date.now()}-${Math.random().toString(36).substring(7)}`;
// Pass providers to execute (router handles all message routing)
// No additional consumers needed - execute() has its own internal consumer
const result = await sandbox.execute(sandboxId, code, runtimeProviders, [], signal);
// Remove the sandbox iframe after execution
sandbox.remove();
// Build plain text response
let output = "";
// Add console output - result.console contains { type: string, text: string } from sandbox.js
if (result.console && result.console.length > 0) {
for (const entry of result.console) {
output += entry.text + "\n";
}
}
// Add error if execution failed
if (!result.success) {
if (output)
output += "\n";
output += `Error: ${result.error?.message || "Unknown error"}\n${result.error?.stack || ""}`;
// Throw error so tool call is marked as failed
throw new Error(output.trim());
}
// Add return value if present
if (result.returnValue !== undefined) {
if (output)
output += "\n";
output += `=> ${typeof result.returnValue === "object" ? JSON.stringify(result.returnValue, null, 2) : result.returnValue}`;
}
// Add file notifications
if (result.files && result.files.length > 0) {
output += `\n[Files returned: ${result.files.length}]\n`;
for (const file of result.files) {
output += ` - ${file.fileName} (${file.mimeType})\n`;
}
}
else {
// Explicitly note when no files were returned (helpful for debugging)
if (code.includes("returnFile")) {
output += "\n[No files returned - check async operations]";
}
}
return {
output: output.trim() || "Code executed successfully (no output)",
files: result.files,
};
}
catch (error) {
// Clean up on error
sandbox.remove();
throw new Error(error.message || "Execution failed");
}
}
const javascriptReplSchema = Type.Object({
title: Type.String({
description: "Brief title describing what the code snippet tries to achieve in active form, e.g. 'Calculating sum'",
}),
code: Type.String({ description: "JavaScript code to execute" }),
});
export function createJavaScriptReplTool() {
return {
label: "JavaScript REPL",
name: "javascript_repl",
runtimeProvidersFactory: () => [], // default to empty array
sandboxUrlProvider: undefined, // optional, for browser extensions
get description() {
const runtimeProviderDescriptions = this.runtimeProvidersFactory?.()
.map((d) => d.getDescription())
.filter((d) => d.trim().length > 0) || [];
return JAVASCRIPT_REPL_TOOL_DESCRIPTION(runtimeProviderDescriptions);
},
parameters: javascriptReplSchema,
execute: async function (_toolCallId, args, signal) {
const result = await executeJavaScript(args.code, this.runtimeProvidersFactory?.() ?? [], signal, this.sandboxUrlProvider);
// Convert files to JSON-serializable with base64 payloads
const files = (result.files || []).map((f) => {
const toBase64 = (input) => {
if (input instanceof Uint8Array) {
let binary = "";
const chunk = 0x8000;
for (let i = 0; i < input.length; i += chunk) {
binary += String.fromCharCode(...input.subarray(i, i + chunk));
}
return { base64: btoa(binary), size: input.length };
}
else if (typeof input === "string") {
const enc = new TextEncoder();
const bytes = enc.encode(input);
let binary = "";
const chunk = 0x8000;
for (let i = 0; i < bytes.length; i += chunk) {
binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
}
return { base64: btoa(binary), size: bytes.length };
}
else {
const s = String(input);
const enc = new TextEncoder();
const bytes = enc.encode(s);
let binary = "";
const chunk = 0x8000;
for (let i = 0; i < bytes.length; i += chunk) {
binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
}
return { base64: btoa(binary), size: bytes.length };
}
};
const { base64, size } = toBase64(f.content);
return {
fileName: f.fileName || "file",
mimeType: f.mimeType || "application/octet-stream",
size,
contentBase64: base64,
};
});
return { content: [{ type: "text", text: result.output }], details: { files } };
},
};
}
// Export a default instance for backward compatibility
export const javascriptReplTool = createJavaScriptReplTool();
export const javascriptReplRenderer = {
render(params, result, isStreaming) {
// Determine status
const state = result ? (result.isError ? "error" : "complete") : isStreaming ? "inprogress" : "complete";
// Create refs for collapsible code section
const codeContentRef = createRef();
const codeChevronRef = createRef();
// With result: show params + result
if (result && params) {
const output = result.content
?.filter((c) => c.type === "text")
.map((c) => c.text)
.join("\n") || "";
const files = result.details?.files || [];
const attachments = files.map((f, i) => {
// Decode base64 content for text files to show in overlay
let extractedText;
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 {
content: html `
<div>
${renderCollapsibleHeader(state, Code, params.title ? params.title : i18n("Executing JavaScript"), codeContentRef, codeChevronRef, false)}
<div ${ref(codeContentRef)} class="max-h-0 overflow-hidden transition-all duration-300 space-y-3">
<code-block .code=${params.code || ""} language="javascript"></code-block>
${output ? html `<console-block .content=${output} .variant=${result.isError ? "error" : "default"}></console-block>` : ""}
</div>
${attachments.length
? html `<div class="flex flex-wrap gap-2 mt-3">
${attachments.map((att) => html `<attachment-tile .attachment=${att}></attachment-tile>`)}
</div>`
: ""}
</div>
`,
isCustom: false,
};
}
// Just params (streaming or waiting for result)
if (params) {
return {
content: html `
<div>
${renderCollapsibleHeader(state, Code, params.title ? params.title : i18n("Executing JavaScript"), codeContentRef, codeChevronRef, false)}
<div ${ref(codeContentRef)} class="max-h-0 overflow-hidden transition-all duration-300">
${params.code ? html `<code-block .code=${params.code} language="javascript"></code-block>` : ""}
</div>
</div>
`,
isCustom: false,
};
}
// No params or result yet
return { content: renderHeader(state, Code, i18n("Preparing JavaScript...")), isCustom: false };
},
};
// Auto-register the renderer
registerToolRenderer(javascriptReplTool.name, javascriptReplRenderer);
//# sourceMappingURL=javascript-repl.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,23 @@
import { type TemplateResult } from "lit";
import type { Ref } from "lit/directives/ref.js";
import type { ToolRenderer } from "./types.js";
export declare const toolRenderers: Map<string, ToolRenderer<any, any>>;
/**
* Register a custom tool renderer
*/
export declare function registerToolRenderer(toolName: string, renderer: ToolRenderer): void;
/**
* Get a tool renderer by name
*/
export declare function getToolRenderer(toolName: string): ToolRenderer | undefined;
/**
* Helper to render a header for tool renderers
* Shows icon on left when complete/error, spinner on right when in progress
*/
export declare function renderHeader(state: "inprogress" | "complete" | "error", toolIcon: any, text: string | TemplateResult): TemplateResult;
/**
* Helper to render a collapsible header for tool renderers
* Same as renderHeader but with a chevron button that toggles visibility of content
*/
export declare function renderCollapsibleHeader(state: "inprogress" | "complete" | "error", toolIcon: any, text: string | TemplateResult, contentRef: Ref<HTMLElement>, chevronRef: Ref<HTMLElement>, defaultExpanded?: boolean): TemplateResult;
//# sourceMappingURL=renderer-registry.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"renderer-registry.d.ts","sourceRoot":"","sources":["../../src/tools/renderer-registry.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAChD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAC;AAGjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,eAAO,MAAM,aAAa,qCAAkC,CAAC;AAE7D;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,IAAI,CAEnF;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAE1E;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC3B,KAAK,EAAE,YAAY,GAAG,UAAU,GAAG,OAAO,EAC1C,QAAQ,EAAE,GAAG,EACb,IAAI,EAAE,MAAM,GAAG,cAAc,GAC3B,cAAc,CA8BhB;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACtC,KAAK,EAAE,YAAY,GAAG,UAAU,GAAG,OAAO,EAC1C,QAAQ,EAAE,GAAG,EACb,IAAI,EAAE,MAAM,GAAG,cAAc,EAC7B,UAAU,EAAE,GAAG,CAAC,WAAW,CAAC,EAC5B,UAAU,EAAE,GAAG,CAAC,WAAW,CAAC,EAC5B,eAAe,UAAQ,GACrB,cAAc,CAsDhB"}

View File

@@ -0,0 +1,107 @@
import { icon } from "@mariozechner/mini-lit";
import { html } from "lit";
import { ref } from "lit/directives/ref.js";
import { ChevronsUpDown, ChevronUp, Loader } from "lucide";
// Registry of tool renderers
export const toolRenderers = new Map();
/**
* Register a custom tool renderer
*/
export function registerToolRenderer(toolName, renderer) {
toolRenderers.set(toolName, renderer);
}
/**
* Get a tool renderer by name
*/
export function getToolRenderer(toolName) {
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, toolIcon, text) {
const statusIcon = (iconComponent, color) => html `<span class="inline-block ${color}">${icon(iconComponent, "sm")}</span>`;
switch (state) {
case "inprogress":
return html `
<div class="flex items-center justify-between gap-2 text-sm text-muted-foreground">
<div class="flex items-center gap-2">
${statusIcon(toolIcon, "text-foreground")}
${text}
</div>
${statusIcon(Loader, "text-foreground animate-spin")}
</div>
`;
case "complete":
return html `
<div class="flex items-center gap-2 text-sm text-muted-foreground">
${statusIcon(toolIcon, "text-green-600 dark:text-green-500")}
${text}
</div>
`;
case "error":
return html `
<div class="flex items-center gap-2 text-sm text-muted-foreground">
${statusIcon(toolIcon, "text-destructive")}
${text}
</div>
`;
}
}
/**
* Helper to render a collapsible header for tool renderers
* Same as renderHeader but with a chevron button that toggles visibility of content
*/
export function renderCollapsibleHeader(state, toolIcon, text, contentRef, chevronRef, defaultExpanded = false) {
const statusIcon = (iconComponent, color) => html `<span class="inline-block ${color}">${icon(iconComponent, "sm")}</span>`;
const toggleContent = (e) => {
e.preventDefault();
const content = contentRef.value;
const chevron = chevronRef.value;
if (content && chevron) {
const isCollapsed = content.classList.contains("max-h-0");
if (isCollapsed) {
content.classList.remove("max-h-0");
content.classList.add("max-h-[2000px]", "mt-3");
// Show ChevronUp, hide ChevronsUpDown
const upIcon = chevron.querySelector(".chevron-up");
const downIcon = chevron.querySelector(".chevrons-up-down");
if (upIcon && downIcon) {
upIcon.classList.remove("hidden");
downIcon.classList.add("hidden");
}
}
else {
content.classList.remove("max-h-[2000px]", "mt-3");
content.classList.add("max-h-0");
// Show ChevronsUpDown, hide ChevronUp
const upIcon = chevron.querySelector(".chevron-up");
const downIcon = chevron.querySelector(".chevrons-up-down");
if (upIcon && downIcon) {
upIcon.classList.add("hidden");
downIcon.classList.remove("hidden");
}
}
}
};
const toolIconColor = state === "complete"
? "text-green-600 dark:text-green-500"
: state === "error"
? "text-destructive"
: "text-foreground";
return html `
<button @click=${toggleContent} class="flex items-center justify-between gap-2 text-sm text-muted-foreground w-full text-left hover:text-foreground transition-colors cursor-pointer">
<div class="flex items-center gap-2">
${state === "inprogress" ? statusIcon(Loader, "text-foreground animate-spin") : ""}
${statusIcon(toolIcon, toolIconColor)}
${text}
</div>
<span class="inline-block text-muted-foreground" ${ref(chevronRef)}>
<span class="chevron-up ${defaultExpanded ? "" : "hidden"}">${icon(ChevronUp, "sm")}</span>
<span class="chevrons-up-down ${defaultExpanded ? "hidden" : ""}">${icon(ChevronsUpDown, "sm")}</span>
</span>
</button>
`;
}
//# sourceMappingURL=renderer-registry.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"renderer-registry.js","sourceRoot":"","sources":["../../src/tools/renderer-registry.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,10 @@
import type { ToolResultMessage } from "@mariozechner/pi-ai";
import type { ToolRenderer, ToolRenderResult } from "../types.js";
interface BashParams {
command: string;
}
export declare class BashRenderer implements ToolRenderer<BashParams, undefined> {
render(params: BashParams | undefined, result: ToolResultMessage<undefined> | undefined): ToolRenderResult;
}
export {};
//# sourceMappingURL=BashRenderer.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"BashRenderer.d.ts","sourceRoot":"","sources":["../../../src/tools/renderers/BashRenderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAK7D,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAElE,UAAU,UAAU;IACnB,OAAO,EAAE,MAAM,CAAC;CAChB;AAGD,qBAAa,YAAa,YAAW,YAAY,CAAC,UAAU,EAAE,SAAS,CAAC;IACvE,MAAM,CAAC,MAAM,EAAE,UAAU,GAAG,SAAS,EAAE,MAAM,EAAE,iBAAiB,CAAC,SAAS,CAAC,GAAG,SAAS,GAAG,gBAAgB;CAsC1G"}

View File

@@ -0,0 +1,42 @@
import { html } from "lit";
import { SquareTerminal } from "lucide";
import { i18n } from "../../utils/i18n.js";
import { renderHeader } from "../renderer-registry.js";
// Bash tool has undefined details (only uses output)
export class BashRenderer {
render(params, result) {
const state = result ? (result.isError ? "error" : "complete") : "inprogress";
// With result: show command + output
if (result && params?.command) {
const output = result.content
?.filter((c) => c.type === "text")
.map((c) => c.text)
.join("\n") || "";
const combined = output ? `> ${params.command}\n\n${output}` : `> ${params.command}`;
return {
content: html `
<div class="space-y-3">
${renderHeader(state, SquareTerminal, i18n("Running command..."))}
<console-block .content=${combined} .variant=${result.isError ? "error" : "default"}></console-block>
</div>
`,
isCustom: false,
};
}
// Just params (streaming or waiting)
if (params?.command) {
return {
content: html `
<div class="space-y-3">
${renderHeader(state, SquareTerminal, i18n("Running command..."))}
<console-block .content=${`> ${params.command}`}></console-block>
</div>
`,
isCustom: false,
};
}
// No params yet
return { content: renderHeader(state, SquareTerminal, i18n("Waiting for command...")), isCustom: false };
}
}
//# sourceMappingURL=BashRenderer.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"BashRenderer.js","sourceRoot":"","sources":["../../../src/tools/renderers/BashRenderer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAOvD,qDAAqD;AACrD,MAAM,OAAO,YAAY;IACxB,MAAM,CAAC,MAA8B,EAAE,MAAgD;QACtF,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;QAE9E,qCAAqC;QACrC,IAAI,MAAM,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;YAC/B,MAAM,MAAM,GACX,MAAM,CAAC,OAAO;gBACb,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;iBACjC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;iBACvB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACpB,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,OAAO,OAAO,MAAM,EAAE,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,OAAO,EAAE,CAAC;YACrF,OAAO;gBACN,OAAO,EAAE,IAAI,CAAA;;QAET,YAAY,CAAC,KAAK,EAAE,cAAc,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;gCACvC,QAAQ,aAAa,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;;KAEpF;gBACD,QAAQ,EAAE,KAAK;aACf,CAAC;QACH,CAAC;QAED,qCAAqC;QACrC,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;YACrB,OAAO;gBACN,OAAO,EAAE,IAAI,CAAA;;QAET,YAAY,CAAC,KAAK,EAAE,cAAc,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC;gCACvC,KAAK,MAAM,CAAC,OAAO,EAAE;;KAEhD;gBACD,QAAQ,EAAE,KAAK;aACf,CAAC;QACH,CAAC;QAED,gBAAgB;QAChB,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,KAAK,EAAE,cAAc,EAAE,IAAI,CAAC,wBAAwB,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC1G,CAAC;CACD"}

View File

@@ -0,0 +1,10 @@
import type { ToolResultMessage } from "@mariozechner/pi-ai";
import type { ToolRenderer, ToolRenderResult } from "../types.js";
interface CalculateParams {
expression: string;
}
export declare class CalculateRenderer implements ToolRenderer<CalculateParams, undefined> {
render(params: CalculateParams | undefined, result: ToolResultMessage<undefined> | undefined): ToolRenderResult;
}
export {};
//# sourceMappingURL=CalculateRenderer.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"CalculateRenderer.d.ts","sourceRoot":"","sources":["../../../src/tools/renderers/CalculateRenderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAK7D,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAElE,UAAU,eAAe;IACxB,UAAU,EAAE,MAAM,CAAC;CACnB;AAGD,qBAAa,iBAAkB,YAAW,YAAY,CAAC,eAAe,EAAE,SAAS,CAAC;IACjF,MAAM,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,EAAE,MAAM,EAAE,iBAAiB,CAAC,SAAS,CAAC,GAAG,SAAS,GAAG,gBAAgB;CA4C/G"}

View File

@@ -0,0 +1,45 @@
import { html } from "lit";
import { Calculator } from "lucide";
import { i18n } from "../../utils/i18n.js";
import { renderHeader } from "../renderer-registry.js";
// Calculate tool has undefined details (only uses output)
export class CalculateRenderer {
render(params, result) {
const state = result ? (result.isError ? "error" : "complete") : "inprogress";
// Full params + full result
if (result && params?.expression) {
const output = result.content
?.filter((c) => c.type === "text")
.map((c) => c.text)
.join("\n") || "";
// Error: show expression in header, error below
if (result.isError) {
return {
content: html `
<div class="space-y-3">
${renderHeader(state, Calculator, params.expression)}
<div class="text-sm text-destructive">${output}</div>
</div>
`,
isCustom: false,
};
}
// Success: show expression = result in header
return { content: renderHeader(state, Calculator, `${params.expression} = ${output}`), isCustom: false };
}
// Full params, no result: just show header with expression in it
if (params?.expression) {
return {
content: renderHeader(state, Calculator, `${i18n("Calculating")} ${params.expression}`),
isCustom: false,
};
}
// Partial params (empty expression), no result
if (params && !params.expression) {
return { content: renderHeader(state, Calculator, i18n("Writing expression...")), isCustom: false };
}
// No params, no result
return { content: renderHeader(state, Calculator, i18n("Waiting for expression...")), isCustom: false };
}
}
//# sourceMappingURL=CalculateRenderer.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"CalculateRenderer.js","sourceRoot":"","sources":["../../../src/tools/renderers/CalculateRenderer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC3B,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAOvD,0DAA0D;AAC1D,MAAM,OAAO,iBAAiB;IAC7B,MAAM,CAAC,MAAmC,EAAE,MAAgD;QAC3F,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;QAE9E,4BAA4B;QAC5B,IAAI,MAAM,IAAI,MAAM,EAAE,UAAU,EAAE,CAAC;YAClC,MAAM,MAAM,GACX,MAAM,CAAC,OAAO;gBACb,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;iBACjC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;iBACvB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YAEpB,gDAAgD;YAChD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,OAAO;oBACN,OAAO,EAAE,IAAI,CAAA;;SAET,YAAY,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC;+CACZ,MAAM;;MAE/C;oBACD,QAAQ,EAAE,KAAK;iBACf,CAAC;YACH,CAAC;YAED,8CAA8C;YAC9C,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC,UAAU,MAAM,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;QAC1G,CAAC;QAED,iEAAiE;QACjE,IAAI,MAAM,EAAE,UAAU,EAAE,CAAC;YACxB,OAAO;gBACN,OAAO,EAAE,YAAY,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;gBACvF,QAAQ,EAAE,KAAK;aACf,CAAC;QACH,CAAC;QAED,+CAA+C;QAC/C,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;YAClC,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,uBAAuB,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;QACrG,CAAC;QAED,uBAAuB;QACvB,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,CAAC,2BAA2B,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IACzG,CAAC;CACD"}

View File

@@ -0,0 +1,6 @@
import type { ToolResultMessage } from "@mariozechner/pi-ai";
import type { ToolRenderer, ToolRenderResult } from "../types.js";
export declare class DefaultRenderer implements ToolRenderer {
render(params: any | undefined, result: ToolResultMessage | undefined, isStreaming?: boolean): ToolRenderResult;
}
//# sourceMappingURL=DefaultRenderer.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DefaultRenderer.d.ts","sourceRoot":"","sources":["../../../src/tools/renderers/DefaultRenderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAK7D,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAElE,qBAAa,eAAgB,YAAW,YAAY;IACnD,MAAM,CAAC,MAAM,EAAE,GAAG,GAAG,SAAS,EAAE,MAAM,EAAE,iBAAiB,GAAG,SAAS,EAAE,WAAW,CAAC,EAAE,OAAO,GAAG,gBAAgB;CA8F/G"}

View File

@@ -0,0 +1,94 @@
import { html } from "lit";
import { Code } from "lucide";
import { i18n } from "../../utils/i18n.js";
import { renderHeader } from "../renderer-registry.js";
export class DefaultRenderer {
render(params, result, isStreaming) {
const state = result ? (result.isError ? "error" : "complete") : isStreaming ? "inprogress" : "complete";
// Format params as JSON
let paramsJson = "";
if (params) {
try {
paramsJson = JSON.stringify(JSON.parse(params), null, 2);
}
catch {
try {
paramsJson = JSON.stringify(params, null, 2);
}
catch {
paramsJson = String(params);
}
}
}
// With result: show header + params + result
if (result) {
let outputJson = result.content
?.filter((c) => c.type === "text")
.map((c) => c.text)
.join("\n") || i18n("(no output)");
let outputLanguage = "text";
// Try to parse and pretty-print if it's valid JSON
try {
const parsed = JSON.parse(outputJson);
outputJson = JSON.stringify(parsed, null, 2);
outputLanguage = "json";
}
catch {
// Not valid JSON, leave as-is and use text highlighting
}
return {
content: html `
<div class="space-y-3">
${renderHeader(state, Code, "Tool Call")}
${paramsJson
? html `<div>
<div class="text-xs font-medium mb-1 text-muted-foreground">${i18n("Input")}</div>
<code-block .code=${paramsJson} language="json"></code-block>
</div>`
: ""}
<div>
<div class="text-xs font-medium mb-1 text-muted-foreground">${i18n("Output")}</div>
<code-block .code=${outputJson} language="${outputLanguage}"></code-block>
</div>
</div>
`,
isCustom: false,
};
}
// Just params (streaming or waiting for result)
if (params) {
if (isStreaming && (!paramsJson || paramsJson === "{}" || paramsJson === "null")) {
return {
content: html `
<div>
${renderHeader(state, Code, "Preparing tool parameters...")}
</div>
`,
isCustom: false,
};
}
return {
content: html `
<div class="space-y-3">
${renderHeader(state, Code, "Tool Call")}
<div>
<div class="text-xs font-medium mb-1 text-muted-foreground">${i18n("Input")}</div>
<code-block .code=${paramsJson} language="json"></code-block>
</div>
</div>
`,
isCustom: false,
};
}
// No params or result yet
return {
content: html `
<div>
${renderHeader(state, Code, "Preparing tool...")}
</div>
`,
isCustom: false,
};
}
}
//# sourceMappingURL=DefaultRenderer.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DefaultRenderer.js","sourceRoot":"","sources":["../../../src/tools/renderers/DefaultRenderer.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,10 @@
import type { ToolResultMessage } from "@mariozechner/pi-ai";
import type { ToolRenderer, ToolRenderResult } from "../types.js";
interface GetCurrentTimeParams {
timezone?: string;
}
export declare class GetCurrentTimeRenderer implements ToolRenderer<GetCurrentTimeParams, undefined> {
render(params: GetCurrentTimeParams | undefined, result: ToolResultMessage<undefined> | undefined): ToolRenderResult;
}
export {};
//# sourceMappingURL=GetCurrentTimeRenderer.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"GetCurrentTimeRenderer.d.ts","sourceRoot":"","sources":["../../../src/tools/renderers/GetCurrentTimeRenderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAK7D,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAElE,UAAU,oBAAoB;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAGD,qBAAa,sBAAuB,YAAW,YAAY,CAAC,oBAAoB,EAAE,SAAS,CAAC;IAC3F,MAAM,CACL,MAAM,EAAE,oBAAoB,GAAG,SAAS,EACxC,MAAM,EAAE,iBAAiB,CAAC,SAAS,CAAC,GAAG,SAAS,GAC9C,gBAAgB;CA2EnB"}

View File

@@ -0,0 +1,72 @@
import { html } from "lit";
import { Clock } from "lucide";
import { i18n } from "../../utils/i18n.js";
import { renderHeader } from "../renderer-registry.js";
// GetCurrentTime tool has undefined details (only uses output)
export class GetCurrentTimeRenderer {
render(params, result) {
const state = result ? (result.isError ? "error" : "complete") : "inprogress";
// Full params + full result
if (result && params) {
const output = result.content
?.filter((c) => c.type === "text")
.map((c) => c.text)
.join("\n") || "";
const headerText = params.timezone
? `${i18n("Getting current time in")} ${params.timezone}`
: i18n("Getting current date and time");
// Error: show header, error below
if (result.isError) {
return {
content: html `
<div class="space-y-3">
${renderHeader(state, Clock, headerText)}
<div class="text-sm text-destructive">${output}</div>
</div>
`,
isCustom: false,
};
}
// Success: show time in header
return { content: renderHeader(state, Clock, `${headerText}: ${output}`), isCustom: false };
}
// Full result, no params
if (result) {
const output = result.content
?.filter((c) => c.type === "text")
.map((c) => c.text)
.join("\n") || "";
// Error: show header, error below
if (result.isError) {
return {
content: html `
<div class="space-y-3">
${renderHeader(state, Clock, i18n("Getting current date and time"))}
<div class="text-sm text-destructive">${output}</div>
</div>
`,
isCustom: false,
};
}
// Success: show time in header
return {
content: renderHeader(state, Clock, `${i18n("Getting current date and time")}: ${output}`),
isCustom: false,
};
}
// Full params, no result: show timezone info in header
if (params?.timezone) {
return {
content: renderHeader(state, Clock, `${i18n("Getting current time in")} ${params.timezone}`),
isCustom: false,
};
}
// Partial params (no timezone) or empty params, no result
if (params) {
return { content: renderHeader(state, Clock, i18n("Getting current date and time")), isCustom: false };
}
// No params, no result
return { content: renderHeader(state, Clock, i18n("Getting time...")), isCustom: false };
}
}
//# sourceMappingURL=GetCurrentTimeRenderer.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"GetCurrentTimeRenderer.js","sourceRoot":"","sources":["../../../src/tools/renderers/GetCurrentTimeRenderer.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,10 @@
import type { ToolResultMessage } from "@mariozechner/pi-ai";
import type { TemplateResult } from "lit";
export interface ToolRenderResult {
content: TemplateResult;
isCustom: boolean;
}
export interface ToolRenderer<TParams = any, TDetails = any> {
render(params: TParams | undefined, result: ToolResultMessage<TDetails> | undefined, isStreaming?: boolean): ToolRenderResult;
}
//# sourceMappingURL=types.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/tools/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,KAAK,CAAC;AAE1C,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,cAAc,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,YAAY,CAAC,OAAO,GAAG,GAAG,EAAE,QAAQ,GAAG,GAAG;IAC1D,MAAM,CACL,MAAM,EAAE,OAAO,GAAG,SAAS,EAC3B,MAAM,EAAE,iBAAiB,CAAC,QAAQ,CAAC,GAAG,SAAS,EAC/C,WAAW,CAAC,EAAE,OAAO,GACnB,gBAAgB,CAAC;CACpB"}

View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=types.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/tools/types.ts"],"names":[],"mappings":""}