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 會話宿主,工具渲染的註冊機制看 工具渲染器註冊表。