ChatPanel:顶层布局与工具装配
ChatPanel 是 pi-web-ui 的顶层 customElement(<pi-chat-panel>)。它本身不跑会话,而是把 AgentInterface、ArtifactsPanel、toolsFactory 几样东西拼到一起:接收外部传进来的 Agent,创建 agent-interface 子元素并赋上 session,根据屏幕宽度在「并排」和「覆盖」两种 artifacts 布局间切换,调用 toolsFactory 让宿主注入额外的 AgentTool。
职责
- 持有 agent 与 agentInterface:
@state agent、@state agentInterface两个字段,agent由 host 传入,agentInterface在setAgent时document.createElement创建,见packages/web-ui/src/ChatPanel.ts:17-25。 - 装配会话宿主:
setAgent创建AgentInterface、设置enableAttachments/onApiKeyRequired/onBeforeSend等回调,把agent作为session挂上去,见packages/web-ui/src/ChatPanel.ts:56-85。 - 装配 artifacts 面板:
new ArtifactsPanel()、注入sandboxUrlProvider、注册ArtifactsToolRenderer到 renderer registry,见packages/web-ui/src/ChatPanel.ts:86-94。 - 构造 runtime providers:
runtimeProvidersFactory把会话历史里的附件聚合成AttachmentsRuntimeProvider,加上可读写的ArtifactsRuntimeProvider,供 REPL 工具消费,见packages/web-ui/src/ChatPanel.ts:96-116。 - 响应式布局:
windowWidth < 800切到 overlay,否则并排 50/50,artifacts 折叠时显示浮动 pill,见packages/web-ui/src/ChatPanel.ts:159-208。
设计动机
为什么把 ArtifactsPanel 的装配放在 ChatPanel 而不是 AgentInterface?因为 artifacts 是「会话产物」维度的概念,与渲染消息列表解耦:AgentInterface 只看消息流,ArtifactsPanel 只看 artifact 消息并维护自己的 artifacts Map。两者都通过 agent.state 共享数据,但生命周期、UI 位置都不同。ChatPanel 作为布局层把它们组装起来,同时把 toolsFactory 暴露给 host,让 host 能注入业务专属工具(比如某个 app 的 query_database),而不需要改 pi-web-ui 源码。
BREAKPOINT = 800 是经验值:窄屏下 artifacts 全屏覆盖聊天区,避免两列都被挤压到不可读;宽屏下并排显示便于边聊边看产物。overlay 与 collapsed 由 ChatPanel 在 render 里根据状态推到 ArtifactsPanel,而不是让 panel 自己监听窗口,这样布局决策集中在顶层。
关键文件
packages/web-ui/src/ChatPanel.ts:17-25—class ChatPanel声明与状态字段。packages/web-ui/src/ChatPanel.ts:36-49—connectedCallback:初始windowWidth、绑resize监听、设 flex 布局。packages/web-ui/src/ChatPanel.ts:56-85—setAgent前半段:创建AgentInterface、挂session、透传回调。packages/web-ui/src/ChatPanel.ts:86-116— artifacts panel 装配、runtimeProvidersFactory闭包。packages/web-ui/src/ChatPanel.ts:118-157— artifacts 变更回调与reconstructFromMessages(从历史消息重建 artifacts)。packages/web-ui/src/ChatPanel.ts:141-144—toolsFactory调用,把[artifactsPanel.tool, ...additionalTools]赋给agent.state.tools。packages/web-ui/src/ChatPanel.ts:159-208—render:按windowWidth/hasArtifacts/showArtifactsPanel切布局,渲染浮动 pill。
setAgent 里 AgentInterface 的创建与回调注入,是 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 让 host 注入额外工具,而 artifacts 工具始终第一个被加入:
// 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;数据流
setAgent 一条主线把 host 传入的 Agent 装配成可用的 UI:
布局切换由 render 里的状态判断驱动:
边界与失败
- agent 未设置:
render直接显示「No agent set」,避免agentInterfaceundefined 时崩,见packages/web-ui/src/ChatPanel.ts:160-164。 - 重建时抑制自动展开:
reconstructFromMessages前临时把onArtifactsChange置空,避免历史 artifacts 触发面板自动弹出,见packages/web-ui/src/ChatPanel.ts:148-151。 - artifacts 计数为 0:
hasArtifacts=false,showArtifactsPanel=false,pill 不渲染,右侧面板隐藏。 - 新 artifact 才自动展开:仅
count > artifactCount时把showArtifactsPanel设 true,避免已有 artifact 重新出现也强制弹窗,见packages/web-ui/src/ChatPanel.ts:118-127。 - resize 监听清理:
disconnectedCallback移除window.resize监听,防止 leak,见packages/web-ui/src/ChatPanel.ts:51-54。
小结
ChatPanel 是布局层 + 装配车间:装配 AgentInterface、ArtifactsPanel、toolsFactory,按窗口宽度切布局。往里看是 AgentInterface 会话宿主,工具渲染的注册机制看 工具渲染器注册表。