import { Badge, html } from "@mariozechner/mini-lit"; import { LitElement } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import type { AgentInterface } from "./components/AgentInterface.js"; import "./components/AgentInterface.js"; import type { Agent } from "./agent/agent.js"; import { ArtifactsPanel } from "./tools/artifacts/index.js"; import { createJavaScriptReplTool } from "./tools/javascript-repl.js"; import { registerToolRenderer } from "./tools/renderer-registry.js"; import { i18n } from "./utils/i18n.js"; const BREAKPOINT = 800; // px - switch between overlay and side-by-side @customElement("pi-chat-panel") export class ChatPanel extends LitElement { @state() private agent?: Agent; @state() private agentInterface?: AgentInterface; @state() private artifactsPanel?: ArtifactsPanel; @state() private hasArtifacts = false; @state() private artifactCount = 0; @state() private showArtifactsPanel = false; @state() private windowWidth = window.innerWidth; @property({ attribute: false }) sandboxUrlProvider?: () => string; @property({ attribute: false }) onApiKeyRequired?: (provider: string) => Promise; @property({ attribute: false }) onBeforeSend?: () => void | Promise; @property({ attribute: false }) additionalTools?: any[]; private resizeHandler = () => { this.windowWidth = window.innerWidth; this.requestUpdate(); }; createRenderRoot() { return this; } override connectedCallback() { super.connectedCallback(); window.addEventListener("resize", this.resizeHandler); this.style.display = "flex"; this.style.flexDirection = "column"; this.style.height = "100%"; this.style.minHeight = "0"; } override disconnectedCallback() { super.disconnectedCallback(); window.removeEventListener("resize", this.resizeHandler); } async setAgent(agent: Agent) { this.agent = agent; // Create AgentInterface this.agentInterface = document.createElement("agent-interface") as AgentInterface; this.agentInterface.session = agent; this.agentInterface.enableAttachments = true; this.agentInterface.enableModelSelector = true; this.agentInterface.enableThinkingSelector = true; this.agentInterface.showThemeToggle = false; this.agentInterface.onApiKeyRequired = this.onApiKeyRequired; this.agentInterface.onBeforeSend = this.onBeforeSend; // Create JavaScript REPL tool const javascriptReplTool = createJavaScriptReplTool(); if (this.sandboxUrlProvider) { javascriptReplTool.sandboxUrlProvider = this.sandboxUrlProvider; } // Set up artifacts panel this.artifactsPanel = new ArtifactsPanel(); if (this.sandboxUrlProvider) { this.artifactsPanel.sandboxUrlProvider = this.sandboxUrlProvider; } registerToolRenderer("artifacts", this.artifactsPanel); // Attachments provider const getAttachments = () => { const attachments: any[] = []; for (const message of this.agent!.state.messages) { if (message.role === "user") { const content = Array.isArray(message.content) ? message.content : [message.content]; for (const block of content) { if (typeof block !== "string" && block.type === "image") { attachments.push({ id: `image-${attachments.length}`, fileName: "image.png", mimeType: block.mimeType || "image/png", size: 0, content: block.data, }); } } } } return attachments; }; javascriptReplTool.attachmentsProvider = getAttachments; this.artifactsPanel.attachmentsProvider = getAttachments; this.artifactsPanel.onArtifactsChange = () => { const count = this.artifactsPanel?.artifacts?.size ?? 0; const created = count > this.artifactCount; this.hasArtifacts = count > 0; this.artifactCount = count; if (this.hasArtifacts && created) { this.showArtifactsPanel = true; } this.requestUpdate(); }; this.artifactsPanel.onClose = () => { this.showArtifactsPanel = false; this.requestUpdate(); }; this.artifactsPanel.onOpen = () => { this.showArtifactsPanel = true; this.requestUpdate(); }; // Set tools on the agent const tools = [javascriptReplTool, this.artifactsPanel.tool, ...(this.additionalTools || [])]; this.agent.setTools(tools); // Reconstruct artifacts from existing messages // Temporarily disable the onArtifactsChange callback to prevent auto-opening on load const originalCallback = this.artifactsPanel.onArtifactsChange; this.artifactsPanel.onArtifactsChange = undefined; await this.artifactsPanel.reconstructFromMessages(this.agent.state.messages); this.artifactsPanel.onArtifactsChange = originalCallback; this.hasArtifacts = this.artifactsPanel.artifacts.size > 0; this.artifactCount = this.artifactsPanel.artifacts.size; this.requestUpdate(); } render() { if (!this.agent || !this.agentInterface) { return html`
No agent set
`; } const isMobile = this.windowWidth < BREAKPOINT; // Set panel props if (this.artifactsPanel) { this.artifactsPanel.collapsed = !this.showArtifactsPanel; this.artifactsPanel.overlay = isMobile; } return html`
${this.agentInterface}
${ this.hasArtifacts && !this.showArtifactsPanel ? html` ` : "" }
${this.artifactsPanel}
`; } }