ChatPanel: Top-level Layout and Wiring
ChatPanel is the top-level customElement of pi-web-ui (<pi-chat-panel>). It does not run the session itself — it assembles AgentInterface, ArtifactsPanel, and toolsFactory: it takes the Agent passed in from outside, creates an agent-interface child element and assigns session to it, switches between the "side-by-side" and "overlay" artifacts layouts based on screen width, and calls toolsFactory so the host can inject additional AgentTools.
Responsibilities
- Hold agent and agentInterface:
@state agentand@state agentInterfacefields;agentis passed in by the host,agentInterfaceis created viadocument.createElementinsetAgent, seepackages/web-ui/src/ChatPanel.ts:17-25. - Wire the session host:
setAgentcreatesAgentInterface, sets up callbacks likeenableAttachments/onApiKeyRequired/onBeforeSend, and attachesagentassession, seepackages/web-ui/src/ChatPanel.ts:56-85. - Wire the artifacts panel:
new ArtifactsPanel(), injectssandboxUrlProvider, registersArtifactsToolRendererinto the renderer registry, seepackages/web-ui/src/ChatPanel.ts:86-94. - Build runtime providers:
runtimeProvidersFactoryaggregates attachments from session history into anAttachmentsRuntimeProvider, plus a readable/writableArtifactsRuntimeProvider, for REPL tools to consume, seepackages/web-ui/src/ChatPanel.ts:96-116. - Responsive layout:
windowWidth < 800switches to overlay; otherwise side-by-side 50/50; when artifacts are collapsed a floating pill is shown, seepackages/web-ui/src/ChatPanel.ts:159-208.
Design motivation
Why put the ArtifactsPanel wiring in ChatPanel instead of AgentInterface? Because artifacts are a "session output" dimension, decoupled from message rendering: AgentInterface only watches the message stream, ArtifactsPanel only watches artifact messages and maintains its own artifacts Map. Both share data through agent.state, but their lifecycles and UI positions are different. ChatPanel as the layout layer assembles them, and at the same time exposes toolsFactory to the host so the host can inject business-specific tools (e.g. a query_database for some app) without modifying pi-web-ui source.
BREAKPOINT = 800 is an empirical value: on narrow screens artifacts cover the chat area full-screen, avoiding both columns being squeezed to unreadable widths; on wide screens they sit side by side so you can chat and inspect outputs at the same time. overlay and collapsed are pushed from ChatPanel to ArtifactsPanel inside render based on state, rather than letting the panel listen to window itself — that keeps layout decisions at the top level.
Key files
packages/web-ui/src/ChatPanel.ts:17-25—class ChatPaneldeclaration and state fields.packages/web-ui/src/ChatPanel.ts:36-49—connectedCallback: initialwindowWidth, bindresizelistener, set flex layout.packages/web-ui/src/ChatPanel.ts:56-85— first half ofsetAgent: createAgentInterface, attachsession, pass through callbacks.packages/web-ui/src/ChatPanel.ts:86-116— artifacts panel wiring,runtimeProvidersFactoryclosure.packages/web-ui/src/ChatPanel.ts:118-157— artifacts change callbacks andreconstructFromMessages(rebuild artifacts from historical messages).packages/web-ui/src/ChatPanel.ts:141-144—toolsFactorycall, assigning[artifactsPanel.tool, ...additionalTools]toagent.state.tools.packages/web-ui/src/ChatPanel.ts:159-208—render: switch layout bywindowWidth/hasArtifacts/showArtifactsPanel, render the floating pill.
Inside setAgent, the creation of AgentInterface and injection of callbacks is the seam between host and session host:
// packages/web-ui/src/ChatPanel.ts:74-84
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 = config?.onApiKeyRequired;
this.agentInterface.onModelSelect = config?.onModelSelect;
this.agentInterface.onBeforeSend = config?.onBeforeSend;
this.agentInterface.onCostClick = config?.onCostClick;toolsFactory lets the host inject extra tools, while the artifacts tool is always added first:
// packages/web-ui/src/ChatPanel.ts:141-144
const additionalTools =
config?.toolsFactory?.(agent, this.agentInterface, this.artifactsPanel, runtimeProvidersFactory) || [];
const tools = [this.artifactsPanel.tool, ...additionalTools];
this.agent.state.tools = tools;Data flow
setAgent is the single thread that turns the Agent passed by the host into a usable UI:
Layout switching is driven by state checks inside render:
Edges and failures
- Agent unset:
rendershows "No agent set" to avoid crashing whenagentInterfaceis undefined, seepackages/web-ui/src/ChatPanel.ts:160-164. - Suppress auto-expand during rebuild:
reconstructFromMessagestemporarily nullsonArtifactsChangeso historical artifacts do not auto-pop the panel, seepackages/web-ui/src/ChatPanel.ts:148-151. - Artifacts count is 0:
hasArtifacts=false,showArtifactsPanel=false, the pill is not rendered, the right panel is hidden. - Only new artifacts auto-expand:
showArtifactsPanelis set to true only whencount > artifactCount, so an existing artifact reappearing does not force a pop, seepackages/web-ui/src/ChatPanel.ts:118-127. - Resize listener cleanup:
disconnectedCallbackremoves thewindow.resizelistener to prevent leaks, seepackages/web-ui/src/ChatPanel.ts:51-54.
Summary
ChatPanel is the layout layer plus the assembly workshop: it wires AgentInterface, ArtifactsPanel, and toolsFactory, and switches layout by window width. Look inside to see AgentInterface session host; for the registration mechanism of tool renderers see Tool renderer registry.