@evandu/super-pi 0.1.76 → 0.1.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -123,16 +123,25 @@ MIT
123
123
 
124
124
  ## Web tools and terminal
125
125
 
126
- ### Add an HTTP web tool
126
+ ### Add a sidebar tool
127
127
 
128
- 1. Start the tool's HTTP server, for example at `http://127.0.0.1:9000`.
129
- 2. Open the **Tools** tab in Super Pi's right sidebar — it shows only your curated tool icons. Click **工具设置** (Tool settings) at the bottom to open the setup popup.
130
- 3. Inside the popup enter the URL under **添加网址工具**, optionally name it, and add it. The same popup holds the preset tools and the customisation notes.
131
- 4. Select its icon to display the page in the popup, or choose **Open in new tab**.
132
- 5. Use **Copy development prompt** to copy a starter request for an agent building another standalone HTTP tool.
128
+ Open **Tools → 工具设置** and use **复制工具开发 Prompt** in the setup dialog. Ask the Super Pi agent to create the tool; its built-in `tool_register` submits the complete files and mounts the result globally, so the tool is available from every workspace without a separate server.
133
129
 
134
- Only HTTP and HTTPS pages are embedded. Pages that block framing or require browser permissions can be opened in a new tab instead.
130
+ For a tool that needs local computation, include a Python script in the registration and declare its parameter schema. The script receives one JSON object on stdin and must print one JSON object on stdout. Load the built-in browser SDK from the mounted page:
135
131
 
132
+ ```html
133
+ <script src="./super-pi-tool.js"></script>
134
+ <script>
135
+ async function calculate(params) {
136
+ const run = await SuperPiTool.run(params);
137
+ return await SuperPiTool.wait(run.id);
138
+ }
139
+ </script>
140
+ ```
141
+
142
+ `SuperPiTool.run`, `get`, `wait`, and `cancel` are relative, tool-scoped APIs. A page should not call `/api/exec`, guess a tool id, start an HTTP server, or send shell commands. The server enforces the declared schema, timeout, cancellation, and Python process isolation.
143
+
144
+ Legacy URL bookmarks remain available for opening external HTTP/HTTPS pages, but they are not the mechanism for registering or running a Super Pi tool.
136
145
  ### Enable the terminal
137
146
 
138
147
  The terminal is off by default. Set the environment variable before starting Super Pi, then restart it after changing the setting.
@@ -145,7 +145,7 @@ const extension = (pi: ExtensionApi): void => {
145
145
  // cancellation. The text says what was asked and what was chosen, in words.
146
146
  return text(JSON.stringify({ answers: (payload.answers ?? []).map((answer) => ({ question: p.question, selected: answer.selected, ...(answer.note ? { note: answer.note } : {}) })), ...(payload.summary ? { summary: payload.summary } : {}) }));
147
147
  } });
148
- pi.registerTool({ name: "tool_register", label: "Register Tool", description: "Register a global Super Pi sidebar tool. Use this when the user asks to create or add a right-sidebar tool. Send complete HTML/JS/CSS files directly; they are stored by Super Pi and work in every workspace. An optional registered Python script receives validated JSON on stdin and must print one JSON object to stdout. Do not start another HTTP server.", promptSnippet: "register a newly created global right-sidebar tool with Super Pi", parameters: ToolRegisterParameters, execute: async (_id, raw, _signal, _update, _context): Promise<TaskToolResult> => {
148
+ pi.registerTool({ name: "tool_register", label: "Register Tool", description: "Register a global Super Pi sidebar tool. Use this when the user asks to create or add a right-sidebar tool. Send complete HTML/JS/CSS files directly; they are stored by Super Pi and work in every workspace. An optional registered Python script receives validated JSON on stdin and must print one JSON object to stdout. In the HTML, load ./super-pi-tool.js and use SuperPiTool.run/get/wait/cancel for the tool's own Python jobs; do not call /api/exec, guess a tool id, or start another HTTP server.", promptSnippet: "register a newly created global right-sidebar tool with Super Pi", parameters: ToolRegisterParameters, execute: async (_id, raw, _signal, _update, _context): Promise<TaskToolResult> => {
149
149
  const endpoint = process.env.SUPER_PI_TOOLS_ENDPOINT;
150
150
  const text = (body: string, isError?: boolean): TaskToolResult => ({ content: [{ type: "text", text: body }], details: undefined, ...(isError ? { isError: true } : {}) });
151
151
  if (!endpoint) return text("The Super Pi tool registry is not available in this run.", true);
@@ -156,7 +156,7 @@ const extension = (pi: ExtensionApi): void => {
156
156
  const response = await fetch(endpoint, { method: "POST", headers: { "content-type": "application/json", ...(capability ? { "x-super-pi-capability": capability } : {}) }, body: JSON.stringify({ ...p, conversationId, capability }) });
157
157
  const payload = await response.json() as { id?: string; error?: string };
158
158
  if (!response.ok || payload.error) return text(payload.error ?? `Tool registration failed (HTTP ${response.status}).`, true);
159
- return text(JSON.stringify({ registered: true, id: payload.id, url: `/tools/${payload.id}/` }));
159
+ return text(JSON.stringify({ registered: true, id: payload.id, url: `/tools/${payload.id}/`, runtime: "Load ./super-pi-tool.js; use SuperPiTool.run(params), SuperPiTool.get(runId), SuperPiTool.wait(runId), and SuperPiTool.cancel(runId)." }));
160
160
  } catch (error) { return text(`Tool registration could not reach Super Pi: ${error instanceof Error ? error.message : "unknown error"}`, true); }
161
161
  } });
162
162
  // Read ONCE at load, not per call: the environment cannot change under a running Pi, and `prepareLoadout`
@@ -53,7 +53,7 @@ npx @evandu/super-pi`})})]}),(0,m.jsxs)(`div`,{className:`help-card`,children:[(
53
53
 
54
54
  工具不属于当前 workspace;通过内置 tool_register 一次性提交完整文件(index.html、JS/CSS,必要时 .py),Super Pi 会保存为全局工具并自动出现在每个 workspace 的工具 Tab。
55
55
 
56
- 如需本地计算,声明 script: { path, timeoutMs, parameters }。脚本必须是 Python,参数以 JSON 从 stdin 传入,stdout 必须只输出一个 JSON 对象;通过 POST /api/tools/<toolId>/run 创建任务,再轮询 GET /api/tools/runs/<runId> 获取状态、日志和结果,DELETE 同一路径取消。不要让网页传 shell 命令、任意文件路径或未声明参数。最后返回注册 id、访问地址、文件清单和验证结果。`);u(e),window.setTimeout(()=>u(!1),1800)};return(0,p.useEffect)(()=>{let e=e=>{e.key===`Escape`&&n()};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[n]),(0,d.createPortal)((0,m.jsx)(`div`,{className:`tool-overlay`,"data-tool-settings-overlay":`1`,onClick:e=>{e.target===e.currentTarget&&n()},children:(0,m.jsxs)(`div`,{className:`tool-dialog`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":`tool-settings-title`,"data-tool-settings":`1`,children:[(0,m.jsxs)(`div`,{className:`tool-dialog-head`,children:[(0,m.jsx)(`h2`,{className:`tool-dialog-title`,id:`tool-settings-title`,children:`⚙ 工具设置`}),(0,m.jsx)(`button`,{type:`button`,className:`tool-dialog-close`,"data-tool-settings-close":`1`,title:`关闭(Esc)`,"aria-label":`关闭工具设置`,onClick:n,children:`✕`})]}),(0,m.jsxs)(`div`,{className:`tool-dialog-body`,children:[(0,m.jsxs)(`div`,{className:`tool-prompt-card`,children:[(0,m.jsx)(`strong`,{children:`给 Agent 开发新工具`}),(0,m.jsx)(`p`,{className:`tool-hint`,children:`工具会挂载到 Super Pi 的统一 HTTP 服务,不需要再启动独立服务器。`}),(0,m.jsx)(`button`,{type:`button`,className:`tool-link-copy-prompt`,"data-tool-development-prompt":`1`,onClick:()=>void f(),children:l?`已复制`:`复制工具开发 Prompt`})]}),(0,m.jsx)(`div`,{className:`rail-section-head`,children:(0,m.jsx)(`h2`,{className:`rail-section-title`,children:`从预置列表添加`})}),(0,m.jsx)(`div`,{className:`tool-presets`,"data-tool-presets":Yt.length,children:Yt.map(n=>{let r=e.some(e=>e.kind===n.kind);return(0,m.jsxs)(`div`,{className:`tool-preset`,"data-tool-preset":n.kind,children:[(0,m.jsx)(`span`,{className:`tool-preset-glyph`,"aria-hidden":`true`,children:n.glyph}),(0,m.jsxs)(`span`,{className:`tool-preset-text`,children:[(0,m.jsx)(`strong`,{children:n.name}),(0,m.jsx)(`small`,{children:n.blurb})]}),(0,m.jsx)(`button`,{type:`button`,className:`tool-preset-add`,"data-tool-add-preset":n.kind,disabled:r,title:r?`${n.name}已经在工具栏里了`:`添加${n.name}`,"aria-label":r?`${n.name}已添加`:`添加${n.name}`,onClick:()=>t(an(e,n.kind)),children:r?`已添加`:`添加`})]},n.kind)})}),(0,m.jsx)(`div`,{className:`rail-section-head`,children:(0,m.jsx)(`h2`,{className:`rail-section-title`,children:`添加网址工具`})}),(0,m.jsx)(`p`,{className:`tool-hint`,children:`填一个网址,它会变成一个图标;点开后在弹窗里用按钮在新标签页打开。网址只接受 http / https / mailto。`}),(0,m.jsx)(`input`,{className:`tool-input`,"data-tool-url-input":`1`,value:r,maxLength:2048,placeholder:`https://example.com/工具`,"aria-label":`工具网址`,onChange:e=>{i(e.target.value),c(void 0)}}),(0,m.jsx)(`input`,{className:`tool-input`,"data-tool-url-name":`1`,value:a,maxLength:40,placeholder:`名称(可留空,默认用域名)`,"aria-label":`工具名称(可选)`,onChange:e=>o(e.target.value)}),(0,m.jsx)(`button`,{type:`button`,className:`tool-url-add`,"data-tool-url-add":`1`,disabled:!r.trim(),onClick:()=>{let n=$t(r);n.ok?e.length>=32?c(`工具最多 32 个,先删掉一个再加。`):(t([...e,{id:rn(`link`,e.map(e=>e.id)),kind:`link`,name:a.trim()||si(n.url),glyph:`↗`,url:n.url}]),i(``),o(``),c(void 0)):c(n.reason)},children:`添加网址工具`}),s&&(0,m.jsx)(`p`,{className:`tool-error`,"data-tool-error":`1`,role:`alert`,children:s}),(0,m.jsxs)(`p`,{className:`tool-hint`,"data-tool-scope-note":`1`,children:[`能自定义到哪:从上面挑预置工具,或自己填一个网址。`,(0,m.jsx)(`b`,{children:`没有开放「写一段脚本来执行」这一档`}),`——它在浏览器里等同于任意代码执行,会拿着本应用的权限跑。计算器是手写的算术解析器,不含 eval,也认不出变量名。`]})]})]})}),document.body)}function si(e){try{return new URL(e,typeof window>`u`?`http://localhost`:window.location.href).hostname||e.slice(0,30)}catch{return e.slice(0,30)}}function ci({tool:e,onClose:t}){return(0,p.useEffect)(()=>{let e=e=>{e.key===`Escape`&&t()};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[t]),(0,d.createPortal)((0,m.jsx)(`div`,{className:`tool-overlay`,"data-tool-overlay":e.id,onClick:e=>{e.target===e.currentTarget&&t()},children:(0,m.jsxs)(`div`,{className:`tool-dialog`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":`tool-dialog-title`,"data-tool-dialog":e.id,children:[(0,m.jsxs)(`div`,{className:`tool-dialog-head`,children:[(0,m.jsxs)(`h2`,{className:`tool-dialog-title`,id:`tool-dialog-title`,children:[(0,m.jsx)(`span`,{className:`tool-dialog-glyph`,"aria-hidden":`true`,children:e.glyph}),e.name]}),(0,m.jsx)(`button`,{type:`button`,className:`tool-dialog-close`,"data-tool-close":`1`,title:`关闭(Esc)`,"aria-label":`关闭${e.name}`,onClick:t,children:`✕`})]}),(0,m.jsxs)(`div`,{className:`tool-dialog-body`,children:[e.kind===`calculator`&&(0,m.jsx)(ui,{}),e.kind===`timestamp`&&(0,m.jsx)(di,{}),e.kind===`link`&&(0,m.jsx)(fi,{tool:e})]})]})}),document.body)}function li({image:e,onClose:t}){return(0,p.useEffect)(()=>{let e=e=>{e.key===`Escape`&&t()};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[t]),(0,d.createPortal)((0,m.jsx)(`div`,{className:`lightbox-overlay`,"data-lightbox-overlay":`1`,onClick:e=>{e.target===e.currentTarget&&t()},children:(0,m.jsxs)(`div`,{className:`lightbox-dialog`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":`lightbox-title`,"data-lightbox-dialog":`1`,children:[(0,m.jsxs)(`div`,{className:`lightbox-head`,children:[(0,m.jsx)(`h2`,{className:`lightbox-title`,id:`lightbox-title`,children:e.name||`图片大图`}),(0,m.jsx)(`button`,{type:`button`,className:`lightbox-close`,"data-lightbox-close":`1`,title:`关闭(Esc)`,"aria-label":`关闭大图`,onClick:t,children:`✕`})]}),(0,m.jsx)(`div`,{className:`lightbox-body`,children:(0,m.jsx)(`img`,{src:e.url,alt:e.name||`图片大图`,className:`lightbox-image`})})]})}),document.body)}function ui(){let[e,t]=(0,p.useState)(`12 * (3 + 4)`),[n,r]=(0,p.useState)(!1),i=vn(e),a=e=>{r(!1),t(t=>n?e:t+e)};return(0,m.jsxs)(`div`,{className:`tool-calc`,"data-tool-calc":`1`,children:[(0,m.jsx)(`input`,{className:`tool-input tool-calc-input`,"data-calc-input":`1`,value:e,maxLength:240,"aria-label":`算式`,spellCheck:!1,autoComplete:`off`,placeholder:`例如 (12 + 8) / 4`,onChange:e=>{r(!1),t(e.target.value)},onKeyDown:e=>{e.key===`Enter`&&e.preventDefault()}}),(0,m.jsx)(`div`,{className:`tool-calc-keys`,"data-calc-keys":`1`,children:[[`7`,`7`],[`8`,`8`],[`9`,`9`],[`÷`,`/`],[`(`,`(`],[`)`,`)`],[`4`,`4`],[`5`,`5`],[`6`,`6`],[`×`,`*`],[`^`,`^`],[`%`,`%`],[`1`,`1`],[`2`,`2`],[`3`,`3`],[`-`,`-`],[`←`,`back`],[`C`,`clear`],[`0`,`0`],[`.`,`.`],[`+`,`+`]].map(([e,n])=>(0,m.jsx)(`button`,{type:`button`,className:`tool-calc-key`,"data-calc-key":n,"aria-label":e===`C`?`清空`:e===`←`?`退格`:`输入 ${e}`,onClick:()=>{n===`clear`?(t(``),r(!1)):n===`back`?(t(e=>e.slice(0,-1)),r(!1)):a(n)},children:e},e))}),i.ok?(0,m.jsx)(`p`,{className:`tool-calc-result`,"data-calc-result":`1`,"data-calc-value":String(i.value),children:yn(i.value)}):(0,m.jsx)(`p`,{className:`tool-calc-error`,"data-calc-error":`1`,role:`alert`,children:i.reason})]})}function di(){let[e,t]=(0,p.useState)(``),n=bn(e),r=n.ok?xn(n.ms):null;return(0,m.jsxs)(`div`,{className:`tool-stamp`,"data-tool-stamp":`1`,children:[(0,m.jsx)(`input`,{className:`tool-input`,"data-stamp-input":`1`,value:e,maxLength:64,spellCheck:!1,"aria-label":`时间戳或日期`,placeholder:`1757000000 或 2026-10-08T09:00:00`,onChange:e=>t(e.target.value)}),r?(0,m.jsxs)(`dl`,{className:`tool-stamp-out`,"data-stamp-result":`1`,children:[(0,m.jsxs)(`div`,{children:[(0,m.jsx)(`dt`,{children:`本地时间`}),(0,m.jsx)(`dd`,{"data-stamp-local":`1`,children:r.local})]}),(0,m.jsxs)(`div`,{children:[(0,m.jsx)(`dt`,{children:`ISO(UTC)`}),(0,m.jsx)(`dd`,{"data-stamp-iso":`1`,children:r.iso})]}),(0,m.jsxs)(`div`,{children:[(0,m.jsx)(`dt`,{children:`秒`}),(0,m.jsx)(`dd`,{"data-stamp-seconds":`1`,children:r.seconds})]}),(0,m.jsxs)(`div`,{children:[(0,m.jsx)(`dt`,{children:`毫秒`}),(0,m.jsx)(`dd`,{"data-stamp-ms":`1`,children:r.milliseconds})]})]}):(0,m.jsx)(`p`,{className:`tool-stamp-error`,"data-stamp-error":`1`,role:`alert`,children:n.ok?`这个日期无法显示。`:n.reason})]})}function fi({tool:e}){let[t,n]=(0,p.useState)(!1),r=async()=>{let e=await gr(`请为 Super Pi 开发一个全局右侧栏工具并自动挂载。不要启动独立 HTTP 服务器。通过内置 tool_register 提交完整的 HTML/JS/CSS 文件;工具将被 Super Pi 保存并在所有 workspace 可用。如需计算,注册一个 Python 脚本(不接受 shell 命令),参数经 JSON stdin 传入,stdout 只输出 JSON 结果。页面用 /api/tools/<toolId>/run 创建任务、轮询 /api/tools/runs/<runId> 读取状态/日志/结果,DELETE 取消。`);n(e),window.setTimeout(()=>n(!1),1800)};return(0,m.jsxs)(`div`,{className:`tool-link`,"data-tool-link":e.id,children:[(0,m.jsx)(`p`,{className:`tool-hint`,children:`这是旧版外部网页链接;新工具由 Super Pi 统一托管,并可选绑定受控 Python 脚本。`}),(0,m.jsx)(`code`,{className:`tool-link-url`,"data-tool-link-url":e.id,children:e.url}),(0,m.jsxs)(`div`,{className:`tool-link-actions`,children:[(0,m.jsx)(`a`,{className:`tool-link-open`,"data-tool-link-open":e.id,href:e.url,target:`_blank`,rel:`noopener noreferrer`,title:e.url,children:`在新标签页打开 ?`}),(0,m.jsx)(`button`,{type:`button`,className:`tool-link-copy-prompt`,"data-tool-copy-prompt":e.id,onClick:()=>void r(),children:t?`已复制`:`复制开发 Prompt`})]}),/^https?:\/\//i.test(e.url??``)&&(0,m.jsx)(`iframe`,{className:`tool-link-frame`,"data-tool-frame":e.id,src:e.url,title:e.name,sandbox:`allow-forms allow-modals allow-pointer-lock allow-popups allow-same-origin allow-scripts`,referrerPolicy:`no-referrer`})]})}var pi=`(max-width: 1100px)`,mi=(e,t,n)=>Math.min(Math.max(e,t),Math.max(t,n));function hi({tool:e,onClose:t}){return(0,d.createPortal)((0,m.jsx)(`div`,{className:`tool-overlay`,onClick:e=>{e.target===e.currentTarget&&t()},children:(0,m.jsxs)(`div`,{className:`tool-dialog`,role:`dialog`,"aria-modal":`true`,children:[(0,m.jsxs)(`div`,{className:`tool-dialog-head`,children:[(0,m.jsxs)(`h2`,{className:`tool-dialog-title`,children:[`🧩 `,e.name]}),(0,m.jsx)(`button`,{type:`button`,className:`tool-dialog-close`,onClick:t,children:`✕`})]}),(0,m.jsx)(`div`,{className:`tool-dialog-body`,children:(0,m.jsx)(`iframe`,{className:`tool-link-frame`,src:`/tools/${encodeURIComponent(e.id)}/`,title:e.name,sandbox:`allow-forms allow-modals allow-popups allow-scripts`,referrerPolicy:`no-referrer`})})]})}),document.body)}function gi(e,t,n){let r=Math.max(0,Math.round(n)-480),i=mi(520,180,Math.max(180,r-240)),a=mi(Math.round(e),180,i),o=mi(640,240,Math.max(240,r-a));return{sidebar:a,rail:mi(Math.round(t),240,o),sidebarMin:180,sidebarMax:i,railMin:240,railMax:o,sumCap:r}}var _i=`super-pi.rail-widths.v1`,vi=`super-pi.reader-state.v1`,yi=48,bi=160,xi=e=>{e.dataset.programmaticScroll=`true`,window.setTimeout(()=>{delete e.dataset.programmaticScroll},0)};function Si(){try{if(typeof window>`u`)return{conversations:{}};let e=JSON.parse(window.localStorage.getItem(`super-pi.reader-state.v1`)??`null`);if(!e||typeof e!=`object`)return{conversations:{}};let t={};if(e.conversations&&typeof e.conversations==`object`)for(let[n,r]of Object.entries(e.conversations)){let e=r;typeof e.followTail==`boolean`&&(t[n]={followTail:e.followTail,scrollTop:Number.isFinite(e.scrollTop)?Math.max(0,Number(e.scrollTop)):0,...typeof e.anchorKey==`string`?{anchorKey:e.anchorKey}:{},...Number.isFinite(e.anchorOffset)?{anchorOffset:Number(e.anchorOffset)}:{}})}let n={};if(e.readAt&&typeof e.readAt==`object`)for(let[t,r]of Object.entries(e.readAt))typeof r==`string`&&(n[t]=r);return{activeId:typeof e.activeId==`string`?e.activeId:void 0,workspaceId:typeof e.workspaceId==`string`?e.workspaceId:void 0,conversations:t,readAt:n}}catch{return{conversations:{}}}}function Ci(e){try{typeof window<`u`&&window.localStorage.setItem(vi,JSON.stringify(e))}catch{}}function wi(){try{if(typeof window>`u`)return{};let e=window.localStorage.getItem(_i);if(e===null)return{};let t=JSON.parse(e),n=e=>typeof e==`number`&&Number.isFinite(e)&&e>0?Math.round(e):void 0;return{sidebar:n(t.sidebar),rail:n(t.rail)}}catch{return{}}}function Ti(e){try{if(typeof window>`u`)return;window.localStorage.setItem(_i,JSON.stringify(e))}catch{}}function Ei({side:e,width:t,min:n,max:r,label:i,onWidth:a}){let[o,s]=(0,p.useState)(!1),c=e=>e.shiftKey?64:16;return(0,m.jsx)(`div`,{className:`rail-resizer rail-resizer-${e}${o?` rail-resizer-dragging`:``}`,"data-rail-resizer":e,"data-rail-resizer-dragging":o?`true`:`false`,"data-rail-resizer-width":t,role:`separator`,"aria-orientation":`vertical`,"aria-label":i,"aria-valuenow":t,"aria-valuemin":n,"aria-valuemax":r,tabIndex:0,onMouseDown:i=>{i.preventDefault(),i.currentTarget.focus();let o=i.clientX,c=t;s(!0);let l=document.body,u=l.style.cursor,d=l.style.userSelect;l.style.cursor=`col-resize`,l.style.userSelect=`none`;let f=t=>{let i=e===`left`?t.clientX-o:o-t.clientX;a(mi(c+i,n,r))},p=()=>{l.style.cursor=u,l.style.userSelect=d,s(!1),window.removeEventListener(`mousemove`,f),window.removeEventListener(`mouseup`,p)};window.addEventListener(`mousemove`,f),window.addEventListener(`mouseup`,p)},onKeyDown:i=>{let o=c(i);i.key===`ArrowLeft`?(i.preventDefault(),a(mi(e===`left`?t-o:t+o,n,r))):i.key===`ArrowRight`?(i.preventDefault(),a(mi(e===`left`?t+o:t-o,n,r))):i.key===`Home`?(i.preventDefault(),a(n)):i.key===`End`&&(i.preventDefault(),a(r))}})}function Di(){let[e,t]=(0,p.useState)(tr),[n,r]=(0,p.useState)([]),i=(0,p.useRef)([]),a=(0,p.useRef)(null);a.current||=Si();let o=n.reduce((e,t)=>t.apply(e),e),[s,c]=(0,p.useState)(a.current.activeId),[l,u]=(0,p.useState)(!1),[d,f]=(0,p.useState)(),[h,g]=(0,p.useState)({}),[_,v]=(0,p.useState)(a.current.workspaceId),[y,b]=(0,p.useState)(!1),x=(0,p.useRef)(void 0),S=(0,p.useRef)(a.current.conversations),C=(0,p.useRef)(a.current.readAt??{}),[w,T]=(0,p.useState)(0),ee=(0,p.useRef)(null),E=(0,p.useRef)(null),te=(0,p.useRef)(void 0),ne=(0,p.useRef)(a.current.activeId),[re,ie]=(0,p.useState)({followTail:!0,scrollTop:0}),ae=(0,p.useRef)(void 0),oe=(0,p.useRef)(new Map),se=(0,p.useRef)(void 0),D=(0,p.useCallback)(()=>{if(se.current!==void 0&&(cancelAnimationFrame(se.current),se.current=void 0),!oe.current.size)return;let e=[...oe.current.values()];oe.current.clear();let n=new Map,r=new Map;for(let t of e){let e=`${t.conversationId}\u0000${t.messageId}`;if(t.type===`message.thinking`){let n=r.get(e);r.set(e,{text:`${n?.text??``}${t.delta}`,at:n?.at??t.at})}else n.set(e,`${n.get(e)??``}${t.delta}`)}t(e=>({...e,conversations:e.conversations.map(e=>{let t=e.messages.map(t=>{let i=`${e.id}\u0000${t.id}`,a=n.get(i),o=r.get(i);return a===void 0&&o===void 0?t:{...t,...a===void 0?{}:{content:t.content+a},...o===void 0?{}:{thinking:(t.thinking??``)+o.text,thinkingBlocks:[...t.thinkingBlocks??[],{at:o.at??t.content.length,text:o.text}]}}});return t.every((t,n)=>t===e.messages[n])?e:{...e,messages:t}})}))},[]),ce=(0,p.useCallback)(e=>{let t=`${e.type}\u0000${e.conversationId}\u0000${e.messageId}`,n=oe.current.get(t);oe.current.set(t,n?{...n,delta:n.delta+e.delta}:e),se.current===void 0&&(se.current=requestAnimationFrame(D))},[D]),le=(0,p.useCallback)(e=>{ae.current=e},[]),[ue,de]=(0,p.useState)({}),[fe,pe]=(0,p.useState)(()=>{if(typeof window>`u`)return``;try{return window.localStorage.getItem(`super-pi.user-nickname`)??``}catch{return``}}),me=e=>{let t=e.slice(0,24);pe(t);try{window.localStorage.setItem(`super-pi.user-nickname`,t)}catch{}},he=fe.trim()||`你`,[ge,_e]=(0,p.useState)([]),[ve,ye]=(0,p.useState)([]),[be,xe]=(0,p.useState)(new Map),[Se,O]=(0,p.useState)(new Map),[k,Ce]=(0,p.useState)(),[we,Te]=(0,p.useState)(),[Ee,De]=(0,p.useState)(),[A,Oe]=(0,p.useState)(),[ke,Ae]=(0,p.useState)(),[je,Me]=(0,p.useState)(`idle`),[Ne,Pe]=(0,p.useState)(()=>typeof window<`u`&&typeof window.matchMedia==`function`&&window.matchMedia(`(max-width: 1100px)`).matches),[Fe,Ie]=(0,p.useState)(`chat`),[Le,Re]=(0,p.useState)(()=>typeof window>`u`?1440:window.innerWidth);(0,p.useEffect)(()=>{let e=()=>Re(window.innerWidth);return window.addEventListener(`resize`,e),e(),()=>window.removeEventListener(`resize`,e)},[]);let[ze,Be]=(0,p.useState)(wi),j=gi(ze.sidebar??270,ze.rail??320,Le);(0,p.useEffect)(()=>{Ti({sidebar:j.sidebar,rail:j.rail})},[j.sidebar,j.rail]);let Ve=e=>Be(t=>({...t,sidebar:e})),He=e=>Be(t=>({...t,rail:e}));(0,p.useEffect)(()=>{if(typeof window>`u`||typeof window.matchMedia!=`function`)return;let e=window.matchMedia(pi),t=e=>{e.matches&&Pe(!0)};return e.addEventListener(`change`,t),e.matches&&Pe(!0),()=>e.removeEventListener(`change`,t)},[]);let[Ue,We]=(0,p.useState)(new Map),[Ge,qe]=(0,p.useState)({}),[Je,Ye]=(0,p.useState)({}),[Xe,Ze]=(0,p.useState)({}),[Qe,$e]=(0,p.useState)(),[et,tt]=(0,p.useState)(),[nt,it]=(0,p.useState)(),ot=(0,p.useRef)(new Map),[st,ct]=(0,p.useState)({}),lt=(0,p.useRef)(new Set),[ut,dt]=(0,p.useState)([]),[ft,pt]=(0,p.useState)([]),[mt,ht]=(0,p.useState)(),[gt,_t]=(0,p.useState)(!1),vt=(0,p.useRef)(null),yt=(0,p.useRef)(!1),bt=(0,p.useRef)(0),xt=(0,p.useRef)([]),St=(0,p.useRef)(void 0),Ct=(0,p.useRef)(!1),[Tt,Dt]=(0,p.useState)(()=>Date.now());(0,p.useEffect)(()=>{let e=window.setInterval(()=>Dt(Date.now()),3e4);return()=>window.clearInterval(e)},[]);let Ot=(0,p.useMemo)(()=>o.conversations.filter(e=>!e.archivedAt),[o.conversations]),M=(0,p.useMemo)(()=>Ot.find(e=>e.id===s)??Ot.find(e=>e.workspaceId===_)??(_&&o.workspaces.some(e=>e.id===_)?void 0:Ot[0]),[s,Ot,_,o.workspaces]),kt=(0,p.useMemo)(()=>o.workspaces.find(e=>e.id===_)??o.workspaces[0],[o.workspaces,_]),jt=(0,p.useRef)(_);jt.current=_,(0,p.useEffect)(()=>{M&&v(M.workspaceId)},[M?.id]),(0,p.useEffect)(()=>{St.current=s},[s]),(0,p.useEffect)(()=>{te.current=M?.id},[M?.id]),(0,p.useEffect)(()=>{let e=M?.id;if(!e)return;let t=o.conversations.find(t=>t.id===e);if(!t)return;let n=C.current[e];C.current[e]=t.updatedAt,n!==t.updatedAt&&T(e=>e+1),Ci({activeId:St.current,workspaceId:M?.workspaceId??_,conversations:S.current,readAt:C.current})},[M?.id,o.conversations]),(0,p.useEffect)(()=>{l&&s&&Ci({activeId:s,workspaceId:M?.workspaceId??_,conversations:S.current})},[s,M?.workspaceId,l,_]),(0,p.useEffect)(()=>{let e=ee.current;if(!e||!M?.id)return;let t=M.id,n=S.current[t]??{followTail:!0,scrollTop:0};ie(n),requestAnimationFrame(()=>{if(ee.current===e){if(n.followTail)xi(e),e.scrollTop=e.scrollHeight;else{let t=n.anchorKey?e.querySelector(`[data-transcript-key="${CSS.escape(n.anchorKey)}"]`):null;e.scrollTop=t?Math.max(0,e.scrollTop+t.getBoundingClientRect().top-(n.anchorOffset??0)-e.getBoundingClientRect().top):n.scrollTop}}}),ne.current=void 0},[M?.id]);let Mt=(0,p.useCallback)((e,t=!1,n=!1)=>{let r=te.current;if(!r)return;let i=e.scrollHeight-e.clientHeight-e.scrollTop,a=S.current[r]??{followTail:!0,scrollTop:0},o=t?!1:n?!0:a.followTail?i<=bi:i<=yi,s,c;if(!o){let t=e.getBoundingClientRect().top,n=[...e.querySelectorAll(`[data-transcript-key]`)].find(e=>e.getBoundingClientRect().bottom>t);s=n?.dataset.transcriptKey,c=n?n.getBoundingClientRect().top-t:void 0}let l={followTail:o,scrollTop:e.scrollTop,...s?{anchorKey:s,anchorOffset:c}:{}};S.current[r]=l,ie(l),Ci({activeId:r,workspaceId:jt.current,conversations:S.current})},[]),Nt=st[M?.id??``]??{text:``,images:[],attachments:[],uploading:0},Pt=Nt.text,Ft=Nt.images,It=Nt.attachments,Lt=Nt.uploading,zt=(e,t)=>ct(n=>({...n,[e]:t(n[e]??{text:``,images:[],attachments:[],uploading:0})})),Vt=e=>zt(M?.id??``,t=>({...t,text:e})),Ht=e=>zt(M?.id??``,t=>({...t,attachments:t.attachments.some(t=>t.kind===e.kind&&t.id===e.id)?t.attachments:[...t.attachments,e]})),Ut=(e,t)=>zt(M?.id??``,n=>({...n,attachments:n.attachments.filter(n=>n.kind!==e||n.id!==t)})),[Wt,Gt]=(0,p.useState)({visible:!1,query:``,rect:null}),Kt=e=>zt(M?.id??``,t=>({...t,images:typeof e==`function`?e(t.images):e})),[qt,Jt]=(0,p.useState)([]),Yt=qt.length>0?qt:M?[M.id]:[],Xt=(0,p.useMemo)(()=>Yt.map(e=>Ot.find(t=>t.id===e)).filter(e=>e!==void 0),[Yt,Ot]),Zt=(0,p.useCallback)(e=>{c(e);let t=Ot.find(t=>t.id===e)?.workspaceId;t&&v(t),an(`session`),Jt(t=>t.includes(e)?t:t.length>0?[...t,e]:[...M?.id&&M.id!==e?[M.id]:[],e])},[M]),N=(0,p.useCallback)(e=>{let t=qt.indexOf(e);if(t<0)return;let n=qt.filter(t=>t!==e);if(Jt(n),M?.id!==e)return;let r=n[Math.min(t,n.length-1)];r&&c(r)},[qt,M]),P=Et(_),[Qt,$t]=(0,p.useState)(Gr[0].id),[en,tn]=(0,p.useState)(!1);(0,p.useEffect)(()=>{let e=!1;return fetch(`/api/terminal`).then(e=>e.json()).then(t=>{e||tn(!!t?.enabled)}).catch(()=>{e||tn(!1)}),()=>{e=!0}},[]);let[rn,an]=(0,p.useState)(`session`);(0,p.useEffect)(()=>{rn===`file`&&!P.active&&an(`session`)},[rn,P.active]);let on=(0,p.useCallback)(e=>{P.setActivePath(e),an(`file`),$t(`files`)},[P.setActivePath]),sn=(0,p.useCallback)(async e=>{await P.openFile(e),an(`file`)},[P.openFile]),cn=(0,p.useMemo)(()=>Xt.map(e=>({id:`session:${e.id}`,rawId:e.id,kind:`session`,label:e.title,title:e.title,closable:Xt.length>1||P.open.length>0,badge:(0,m.jsx)(Yr,{state:Jr(e,ge.some(t=>t.conversationId===e.id),C.current[e.id])})})),[Xt,P.open.length,ge,o.conversations,w]),ln=(0,p.useMemo)(()=>P.open.map(e=>({id:`file:${e.path}`,rawId:e.path,kind:`file`,label:e.name,title:e.path,closable:!0,badge:e.dirty?(0,m.jsx)(`span`,{className:`file-tab-badge`,"data-file-tab-dirty":e.path,"aria-label":`未保存`,children:`●`}):void 0})),[P.open]),un=(0,p.useMemo)(()=>[...cn,...ln],[cn,ln]),dn=rn===`file`&&P.activePath?`file:${P.activePath}`:M?.id?`session:${M.id}`:void 0,fn=(0,p.useCallback)(e=>{if(e.startsWith(`file:`)){let t=e.slice(5);on(t)}else if(e.startsWith(`session:`)){let t=e.slice(8);Zt(t)}},[on,Zt]),pn=(0,p.useCallback)(e=>{if(e.startsWith(`file:`)){let t=e.slice(5);P.closeFile(t),P.activePath===t&&P.open.length<=1&&an(`session`)}else if(e.startsWith(`session:`)){let t=e.slice(8);N(t)}},[P,N]),mn=P.active,hn=M?.id;(0,p.useEffect)(()=>{hn&&!hn.startsWith(`pending:`)&&bn({type:`conversation.select`,conversationId:hn})},[hn]);let F=(e,t=`info`)=>{if(!e)return;let n=++bt.current;ye(r=>[...r,{key:n,text:e,kind:t}]),xt.current.push(window.setTimeout(()=>ye(e=>e.filter(e=>e.key!==n)),7e3))},gn=(e,t)=>{let n=e.findIndex(e=>e.id===t.id);if(n<0)return[t,...e];let r=[...e];return r[n]=t,r},_n=(e,t)=>{let n=e.findIndex(e=>e.id===t.id);if(n<0)return[t,...e];let r=[...e];return r[n]=t,r},vn=(e,t)=>{if(t.blocking)_e(n=>[...n.filter(n=>n.conversationId!==e||n.request.requestId!==t.requestId),{conversationId:e,request:t}]);else if(t.method===`notify`)F(t.message||t.title,t.notifyType??`info`);else if(t.method===`setStatus`){let e=t.statusKey??``;xe(n=>{let r=new Map(n);return t.statusText===void 0?r.delete(e):r.set(e,t.statusText),r})}else if(t.method===`setWidget`){let e=t.widgetKey??``;O(n=>{let r=new Map(n);return t.widgetLines===void 0?r.delete(e):r.set(e,{lines:t.widgetLines,placement:t.widgetPlacement??`aboveEditor`}),r})}else t.method===`setTitle`?Ce(t.title):t.method===`set_editor_text`&&e===St.current&&Vt(t.text??``)},yn=(e,t,n)=>{_e(n=>n.filter(n=>n.conversationId!==e||n.request.requestId!==t));let r=Dr[n];r&&F(r,`warning`)};(0,p.useEffect)(()=>{let e=!1,n=0,o,s=()=>{if(e)return;let l=location.protocol===`https:`?`wss`:`ws`,d=new WebSocket(`${l}://${location.host}/ws`);x.current=d,d.onopen=()=>{e||(n=0,u(!0),d.send(JSON.stringify({type:`stream.configure`,coalesce:!0})),d.send(JSON.stringify({type:`catalog.fetch`,conversationId:St.current})),d.send(JSON.stringify({type:`agent.catalog.fetch`})),d.send(JSON.stringify({type:`subagent.catalog.fetch`})))},d.onclose=()=>{if(!e){Ct.current=!1;for(let e of i.current)e.onFailure?.();i.current=[],r([]),u(!1),o=window.setTimeout(s,Math.min(hr,mr*2**n++))}},d.onmessage=({data:n})=>{if(e)return;let o=JSON.parse(n);if(o.type!==`message.delta`&&o.type!==`message.thinking`&&D(),o.type.startsWith(`terminal.`))ae.current?.(o);else if(o.type===`conversation.autoRename.result`){let e=ot.current.get(o.conversationId);e&&(ot.current.delete(o.conversationId),o.ok?e.resolve(o.title):e.reject(Error(o.error)))}else if(o.type===`message.thinking`)ce(o);else if(o.type===`state`)D(),Ct.current=!1,i.current=i.current.filter(e=>!e.accepted?.(o.state)),r(i.current),t(o.state),u(!0),c(e=>{let t=o.state.conversations.filter(e=>!e.archivedAt),n=a.current?.activeId,r=!!(e&&t.some(t=>t.id===e)),s=!!(e&&i.current.some(t=>t.temporaryId===e));return r||s?e:n&&t.some(e=>e.id===n)?n:t.find(e=>e.workspaceId===jt.current)?.id}),v(e=>{let t=a.current?.workspaceId;return t&&o.state.workspaces.some(e=>e.id===t)?t:e&&(o.state.workspaces.some(t=>t.id===e)||i.current.some(t=>t.temporaryId===e))?e:o.state.workspaces[0]?.id});else if(o.type===`message.delta`)ce(o);else if(o.type===`message.revert.result`)vi(void 0),$e(void 0),o.ok||f(o.message);else if(o.type===`operation.result`){let e=i.current.find(e=>e.id===o.requestId);if(i.current=i.current.filter(e=>e.id!==o.requestId),r(i.current),e?.temporaryId){let t=e.temporaryId;c(n=>n===t?o.ok?o.entityId:e.previousActiveId:n),Jt(e=>e.flatMap(e=>e===t?o.ok&&o.entityId?[o.entityId]:[]:[e])),v(e=>e===t?o.ok?o.entityId:void 0:e)}o.ok||(e?.onFailure?.(),f(o.message??`操作失败`))}else o.type===`error`?(f(o.message),Ct.current&&(Ct.current=!1,F(`重命名失败:${o.message}`,`error`))):o.type===`extension.ui`?vn(o.conversationId,o.request):o.type===`extension.ui.closed`?yn(o.conversationId,o.requestId,o.reason):o.type===`model.catalog`?(!o.conversationId||o.conversationId===St.current)&&Te(o.catalog):o.type===`agent.catalog`?De(o.catalog):o.type===`subagent.catalog`?Oe(o.catalog):o.type===`server.version`?(Ae(o.version),o.version.needsRestart&&F(`检测到 Super Pi 代码已更新(v${o.version.runningVersion} → v${o.version.diskVersion}),请执行 /super_pi restart 重启服务生效`,`warning`)):o.type===`subagent.event`?t(e=>({...e,subagentRuns:gn(e.subagentRuns??[],o.run)})):o.type===`workflow.event`?t(e=>({...e,workflows:_n(e.workflows??[],o.workflow)})):o.type===`message.tools`?t(e=>({...e,conversations:e.conversations.map(e=>e.id===o.conversationId?{...e,messages:e.messages.map(e=>e.id===o.messageId?{...e,toolCalls:o.toolCalls}:e)}:e)})):o.type===`pi.queue`?We(e=>{let t=new Map(e);return t.set(o.conversationId,{steering:o.steering,followUp:o.followUp}),t}):o.type===`conversation.fork.result`&&(Ni(e=>e===o.conversationId?void 0:e),F(o.ok?`已从「${o.title}」分叉出新会话,放在侧边栏最上面;没有切换过去。${o.messagesCopied?`复制了 ${o.messagesCopied} 条消息。`:`原会话还没有消息,只复制了会话本身。`}`:`分叉失败:${o.message}`,o.ok?`info`:`error`))}};return s(),()=>{e=!0,D(),window.clearTimeout(o);for(let e of xt.current)window.clearTimeout(e);x.current?.close()}},[ce,D]);let bn=(0,p.useCallback)(e=>x.current?.readyState===WebSocket.OPEN&&(x.current.send(JSON.stringify(e)),!0),[]),I=(e,t,n,a,o,s)=>{if(x.current?.readyState!==WebSocket.OPEN||i.current.some(e=>e.key===t))return!1;let c=nn(),l={id:c,key:t,apply:n,temporaryId:a,previousActiveId:St.current,onFailure:o,accepted:s};return i.current=[...i.current,l],r(i.current),x.current.send(JSON.stringify({...e,operationRequestId:c})),!0},xn=(e,t)=>n=>({...n,conversations:n.conversations.map(n=>n.id===e?{...n,...t}:n)}),Sn=(e,t)=>n=>({...n,workspaces:n.workspaces.map(n=>n.id===e?{...n,...t}:n)}),Cn=n.some(e=>e.key===`send:${M?.id}`||e.key===`configuration:${M?.id}`||e.temporaryId===M?.id||e.key===`archive:${M?.id}`),wn=(e,t,n,r,i)=>{e&&x.current?.send(JSON.stringify({type:`question.answer`,conversationId:e,messageId:t,callIndex:n,answers:r,cancelled:i}))},Tn=(e,t,n)=>I({type:`extension.ui.answer`,conversationId:e,requestId:t,...n},`answer:${e}:${t}`,e=>e),En=M?.model??we?.defaultModel,Dn=M?.thinkingLevel??we?.defaultThinkingLevel,On=we?.source===`pi`,An=!!we?.models.find(e=>e.provider===En?.provider&&e.modelId===En?.modelId)?.imageInput,jn=(e,t)=>!e||!t?e:{...e,models:e.models.filter(e=>(o.modelFilterRules??[]).every(n=>{try{return!new RegExp(n.workspacePattern,`i`).test(t.name)||new RegExp(n.modelPattern,`i`).test(e.label)||new RegExp(n.modelPattern,`i`).test(`${e.provider}/${e.modelId}`)}catch{return!0}}))},Mn=(jn(we,o.workspaces.find(e=>e.id===M?.workspaceId))?.models??[]).map(e=>({key:`${e.provider}/${e.modelId}`,label:e.label,detail:e.detail,badge:Cr(e),selected:En?.provider===e.provider&&En.modelId===e.modelId})),Nn=(we?.levels??[]).map(e=>({key:e,label:e,selected:e===Dn})),Pn=e=>{if(!M)return;let[t,n]=e.split(`/`);t&&n&&I({type:`conversation.model`,conversationId:M.id,provider:t,modelId:n},`configuration:${M.id}`,xn(M.id,{model:we?.models.find(e=>e.provider===t&&e.modelId===n)??{provider:t,modelId:n,label:n}}))},Fn=e=>{M&&I({type:`conversation.thinking`,conversationId:M.id,level:e},`configuration:${M.id}`,xn(M.id,{thinkingLevel:e}))},In=Ee?.agents.find(e=>e.id===M?.agentId),Rn=[{key:L,label:`跟随 Pi 默认`,detail:`不使用任何 agent 角色`,selected:!M?.agentId},...(Ee?.agents??[]).map(e=>({key:e.id,label:e.name,detail:e.toolSummary,color:e.color,selected:M?.agentId===e.id}))],zn=In?.model,Bn=!!(zn&&M?.model&&M.model.provider===zn.provider&&M.model.modelId===zn.modelId);In?.thinkingLevel&&(M?.thinkingLevel,In.thinkingLevel);let Vn=e=>{if(M){let t=Ee?.agents.find(t=>t.id===e);I({type:`conversation.agent`,conversationId:M.id,agentId:e===L?null:e},`configuration:${M.id}`,xn(M.id,{agentId:t?.id,...t?.model?{model:{...t.model,label:t.model.modelId}}:{},...t?.thinkingLevel?{thinkingLevel:t.thinkingLevel}:{}}))}},Hn=ge.filter(e=>e.conversationId===M?.id),Un=(0,p.useMemo)(()=>(o.subagentRuns??[]).filter(e=>e.conversationId===M?.id),[o.subagentRuns,M?.id]),Wn=(0,p.useMemo)(()=>(o.workflows??[]).filter(e=>e.conversationId===M?.id),[o.workflows,M?.id]),Gn=(0,p.useMemo)(()=>new Map(Wn.map(e=>[e.id,e])),[Wn]),Kn=(0,p.useMemo)(()=>new Map((A?.agents??[]).map(e=>[e.id,e.name])),[A]),qn=(0,p.useMemo)(()=>new Map((M?.messages??[]).map(e=>[e.id,e])),[M?.messages]),Jn=(0,p.useMemo)(()=>new Map(Un.map(e=>[e.id,e])),[Un]),Yn=(0,p.useMemo)(()=>{if(!M)return[];let e=Qe?.conversationId===M.id?(()=>{let e=M.messages.find(e=>e.id===Qe.messageId);if(e)return{index:M.messages.indexOf(e),at:Date.parse(e.createdAt)||0,...Qe.kind===`withdraw`?{inclusive:!0}:{inclusive:!1}}})():void 0,t=[];return M.messages.forEach((n,r)=>{e&&(r>e.index||e.inclusive&&r===e.index)||t.push({at:Date.parse(n.createdAt)||0,seq:r,key:`m:${n.id}`})}),Un.forEach((n,r)=>{e&&(Date.parse(n.startedAt)||0)>e.at||t.push({at:Date.parse(n.startedAt)||0,seq:t.length+r,key:`r:${n.id}`})}),Wn.forEach((n,r)=>{e&&(Date.parse(n.startedAt)||0)>e.at||t.push({at:Date.parse(n.startedAt)||0,seq:t.length+r,key:`w:${n.id}`})}),t.sort((e,t)=>e.at-t.at||e.seq-t.seq).map(e=>e.key)},[M,Un,Wn,Qe]);(0,p.useEffect)(()=>{let e=ee.current;if(!e||!M?.id)return;let t=()=>{S.current[M.id]?.followTail&&(xi(e),e.scrollTop=e.scrollHeight)};if(t(),typeof ResizeObserver>`u`)return;let n=new ResizeObserver(t);n.observe(e);for(let t of e.children)n.observe(t);return()=>n.disconnect()},[M?.id,M?.messages,Un,Wn,Yn]);let Xn=e=>qe(t=>({...t,[e]:!t[e]})),Zn=(e,t)=>Ye(n=>({...n,[`${e}:${t}`]:!n[`${e}:${t}`]})),Qn=(e,t)=>Ze(n=>({...n,[`${e}:${t}`]:n[`${e}:${t}`]!==!1})),[$n,er]=(0,p.useState)({}),ir=e=>er(t=>({...t,[e]:t[e]===!1})),ar=e=>{I({type:`subagent.stop`,runId:e},`stop:${e}`,e=>e)||F(`连接断开,无法停止子智能体,请重试`,`error`)},or=e=>{I({type:`workflow.stop`,workflowId:e},`stop:${e}`,e=>e)||F(`连接断开,无法停止编排,请重试`,`error`)},sr=e=>[...Se].filter(([,t])=>t.placement===e).map(([e,t])=>{let n=t.lines;return(0,m.jsxs)(`div`,{className:`extui-widget`,"data-widget-key":e,"data-widget-lines":n.length,children:[n.slice(0,Or).map((e,t)=>(0,m.jsx)(`div`,{children:e},`${t}:${e}`)),n.length>Or&&(0,m.jsx)(`div`,{children:`… (widget truncated)`})]},e)}),cr=(e,t)=>{let n=`pending:${nn()}`,r={id:n,name:e,path:t,updatedAt:new Date().toISOString()};I({type:`workspace.create`,name:e,path:t},`create-workspace`,e=>({...e,workspaces:[...e.workspaces,r]}),n)&&(v(n),c(void 0),b(!1))},[lr,yr]=(0,p.useState)(),br=(e,t)=>yr(n=>n?.kind===e&&n.id===t?n:{kind:e,id:t}),xr=()=>yr(void 0),Sr=(e,t)=>lr?.kind===e&&lr.id===t,wr=(e,t,n)=>t.startsWith(`pending:`)?!1:I(e===`workspace`?{type:`workspace.rename`,workspaceId:t,name:n}:{type:`conversation.rename`,conversationId:t,name:n},`rename:${t}`,e===`workspace`?Sn(t,{name:n}):xn(t,{title:n}))?(Ct.current=!0,xr(),!0):(F(`连接断开,改名未保存,请重试`,`error`),!1),Tr=async e=>{if(e.startsWith(`pending:`)){F(`这个会话还在创建中,稍后再试`,`warning`);return}if(ot.current.has(e))return;F(`正在根据对话内容生成标题…`,`info`);let t=new Promise((t,n)=>{ot.current.set(e,{resolve:t,reject:n}),window.setTimeout(()=>{ot.current.delete(e)&&n(Error(`模型在 30 秒内没有返回标题`))},32e3)});if(!bn({type:`conversation.autoRename`,conversationId:e}))ot.current.delete(e),F(`连接断开,无法生成标题`,`error`);else try{let n=await t;wr(`conversation`,e,n),F(`已重命名为「${n}」`,`info`)}catch(e){F(`重命名失败:${e instanceof Error?e.message:String(e)}`,`error`)}},jr=e=>{if(e.id.startsWith(`pending:`))return;let t=o.conversations.filter(t=>t.workspaceId===e.id).length;window.confirm(`删除工作区「${e.name}」?${t?`其中 ${t} 条会话将一并删除,`:``}此操作不可撤销。`)&&I({type:`workspace.delete`,workspaceId:e.id},`delete:${e.id}`,t=>({...t,workspaces:t.workspaces.filter(t=>t.id!==e.id),conversations:t.conversations.filter(t=>t.workspaceId!==e.id)}))},[Mr,Nr]=(0,p.useState)(),Fr=Mr?o.workspaces.find(e=>e.id===Mr):void 0,{theme:Br,setTheme:Kr}=kn(),[qr,Xr]=(0,p.useState)(!1),[Zr,Qr]=(0,p.useState)(!1),$r=(e,t,n,r)=>{e.startsWith(`pending:`)||(I({type:`workspace.settings`,workspaceId:e,model:n?{provider:n.provider,modelId:n.modelId}:null,thinkingLevel:r??null,agentId:t??null},`defaults:${e}`,Sn(e,{defaultModel:n,defaultThinkingLevel:r,defaultAgentId:t}))?Nr(void 0):F(`连接断开,设置未保存,请重试`,`error`))},ei=e=>{if(e.startsWith(`pending:`)||i.current.some(t=>t.key===`defaults:${e}`))return;let t=`pending:${nn()}`,n={id:t,workspaceId:e,title:`新会话`,status:`idle`,updatedAt:new Date().toISOString(),messages:[]};I({type:`conversation.create`,workspaceId:e},`create-session:${e}`,e=>({...e,conversations:[n,...e.conversations]}),t)&&(v(e),c(t),an(`session`),Jt(e=>[...e.filter(e=>e!==t),t]))},ti=e=>{e.startsWith(`pending:`)||I({type:`conversation.archive`,conversationId:e},`archive:${e}`,xn(e,{archivedAt:new Date().toISOString()}))},ri=e=>I({type:`conversation.unarchive`,conversationId:e},`archive:${e}`,xn(e,{archivedAt:void 0})),ii=async e=>{if(!M){F(`请先选择或创建会话`,`error`);return}if(!An){F(`当前模型不支持图片输入`,`error`);return}let t=[`image/png`,`image/jpeg`,`image/gif`,`image/webp`],n=e.filter(e=>t.includes(e.type)?e.size>5242880?(F(`图片「${e.name}」超过 5MB 上限`,`error`),!1):!0:(F(`不支持的文件格式「${e.name}」,仅支持 PNG/JPEG/GIF/WebP`,`error`),!1));if(n.length===0)return;let r=M.id;zt(r,e=>({...e,uploading:e.uploading+n.length}));let i=n.map(e=>({id:nn(),conversationId:r,name:e.name,url:URL.createObjectURL(e)}));pt(e=>[...e,...i]);for(let[e,t]of n.entries()){let n=i[e];try{let e=await new Promise((e,n)=>{let r=new FileReader;r.onload=()=>{let t=r.result,n=t.indexOf(`,`);e(n>=0?t.slice(n+1):t)},r.onerror=n,r.readAsDataURL(t)}),i=await fetch(`/api/upload`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({conversationId:r,name:t.name,mimeType:t.type,data:e})});if(!(i.headers.get(`content-type`)??``).includes(`application/json`)){F(`服务器未返回 JSON 响应,请刷新或重启服务`,`error`);continue}if(i.ok){let e=await i.json();if(lt.current.has(n.id))continue;pt(e=>e.filter(e=>e.id!==n.id)),URL.revokeObjectURL(n.url),zt(r,t=>({...t,images:[...t.images,{id:e.id,name:e.name,mimeType:e.mimeType,bytes:e.bytes}]}))}else{let e=await i.json().catch(()=>({error:`上传失败`}));F(e.error||`上传失败: ${i.status}`,`error`)}}catch(e){F(`上传错误: ${e instanceof Error?e.message:`网络错误`}`,`error`)}finally{pt(e=>e.map(e=>e.id===n.id?{...e,failed:!0}:e)),zt(r,e=>({...e,uploading:Math.max(0,e.uploading-1)}))}}},ai=e=>{let t=e.clipboardData?.items;if(!t)return;let n=[];for(let e=0;e<t.length;e++){let r=t[e];if(r.type.startsWith(`image/`)){let e=r.getAsFile();e&&n.push(e)}}if(n.length>0){if(!An){F(`当前模型不支持图片输入`,`error`);return}e.preventDefault(),ii(n)}},oi=e=>{e.dataTransfer?.types?.includes(`Files`)&&(e.preventDefault(),_t(!0))},si=e=>{e.currentTarget.contains(e.relatedTarget)||_t(!1)},ci=e=>{_t(!1);let t=Array.from(e.dataTransfer?.files??[]).filter(e=>e.type.startsWith(`image/`));if(t.length>0){if(e.preventDefault(),!An){F(`当前模型不支持图片输入`,`error`);return}ii(t)}},ui=t=>{if(t.preventDefault(),!M||Cn||!Pt.trim()&&Ft.length===0)return;if(Ft.length>0&&!An){F(`当前模型不支持图片输入,请移除图片或切换至支持图文的模型`,`error`);return}if(Lt>0){F(`图片正在上传中,请稍候…`,`info`);return}let n=M.status===`running`,r=`pending:${nn()}`,i=M.id,a=Pt,o=Ft,s=new Set([...e.conversations.find(e=>e.id===i)?.messages.map(e=>e.id)??[],...e.queuedMessages?.map(e=>e.id)??[]]);I({type:`message.send`,conversationId:M.id,content:Pt,...Ft.length>0?{images:Ft}:{},...It.length>0?{attachments:It}:{}},`send:${i}`,e=>e.conversations.find(e=>e.id===i)?.messages.some(e=>!s.has(e.id)&&e.role===`user`&&e.content===a.trim())||e.queuedMessages?.some(e=>!s.has(e.id)&&e.conversationId===i&&e.content===a.trim())||n?e:{...e,conversations:e.conversations.map(e=>e.id===i?{...e,messages:[...e.messages,{id:r,role:`user`,content:a,images:o,createdAt:new Date().toISOString()}]}:e)},void 0,()=>{dt(e=>[...e,{id:r,conversationId:i,content:a,images:o}]),zt(i,e=>e.text||e.images.length?e:{...e,text:a,images:o})},e=>!!(e.conversations.find(e=>e.id===i)?.messages.some(e=>!s.has(e.id)&&e.role===`user`&&e.content===a.trim())||e.queuedMessages?.some(e=>!s.has(e.id)&&e.conversationId===i&&e.content===a.trim())))?zt(M.id,e=>({...e,text:``,images:[]})):F(`连接断开,消息未发送,草稿已保留,请重试`,`error`)},di=(0,p.useMemo)(()=>(o.queuedMessages??[]).filter(e=>e.conversationId===M?.id),[o.queuedMessages,M?.id]),fi=e=>{I({type:`message.cancel`,conversationId:M?.id??``,queueId:e},`queue:${e}`,t=>({...t,queuedMessages:t.queuedMessages?.filter(t=>t.id!==e)}))||F(`连接断开,取消没有生效,请重试`,`error`)},mi=e=>{I({type:`message.steer`,conversationId:M?.id??``,queueId:e},`queue:${e}`,t=>({...t,queuedMessages:t.queuedMessages?.map(t=>t.id===e?{...t,priority:!0}:t)}))||F(`连接断开,优先发送没有生效,请重试`,`error`)},hi=e=>{I({type:`message.sendQueued`,conversationId:M?.id??``,queueId:e},`send-queued:${e}`,t=>({...t,queuedMessages:t.queuedMessages?.filter(t=>t.id!==e)}),void 0,void 0,t=>!t.queuedMessages?.some(t=>t.id===e))||F(`连接断开,发送没有生效,请重试`,`error`)},[_i,vi]=(0,p.useState)(),Di=(e,t=!1)=>{if(!M||_i)return;let n=M.messages.find(t=>t.id===e);n&&(n&&ct(e=>{let t=e[M.id]??{text:``,images:[],uploading:0};return t.text.trim()||t.images.length?e:{...e,[M.id]:{...t,text:n.content,images:n.images??[]}}}),$e({conversationId:M.id,messageId:e,kind:`withdraw`})),bn({type:`message.revert`,conversationId:M.id,messageId:e,withdraw:!0})?vi(e):($e(void 0),F(`连接断开,回退未执行,请重试`,`error`))},[Oi,ki]=(0,p.useState)(),Ai=(0,p.useRef)(void 0),ji=async(e,t)=>{Ai.current&&clearTimeout(Ai.current);let n=await gr(t);ki({id:e,kind:n?`copied`:`error`}),n||F(`复制失败:浏览器拒绝了剪贴板写入,可以手动选中这条消息的文字复制`,`error`),Ai.current=setTimeout(()=>ki(void 0),2e3)};(0,p.useEffect)(()=>()=>{Ai.current&&clearTimeout(Ai.current)},[]);let[Mi,Ni]=(0,p.useState)(),Pi=e=>{Mi||e.startsWith(`pending:`)||(bn({type:`conversation.fork`,conversationId:e})?Ni(e):F(`连接断开,分叉没有执行,请重试`,`error`))};return(0,m.jsxs)(`main`,{className:`shell${Ne?` shell-rail-collapsed`:``} mobile-panel-${Fe}`,"data-rail-collapsed":Ne,"data-selected-workspace":_??``,style:{"--sidebar-width":`${j.sidebar}px`,"--rail-col":Ne?void 0:`${j.rail}px`},children:[(0,m.jsxs)(`aside`,{children:[(0,m.jsxs)(`div`,{className:`brand`,children:[(0,m.jsx)(`span`,{children:`π`}),(0,m.jsxs)(`div`,{children:[(0,m.jsx)(`strong`,{children:`SUPER PI`}),(0,m.jsx)(`small`,{children:`agent workspace`})]})]}),(0,m.jsx)(`div`,{className:`sidebar-actions`,children:(0,m.jsx)(`button`,{className:`workspace-new`,onClick:()=>b(!0),children:`+ 工作区`})}),(0,m.jsx)(`div`,{className:`label`,children:`工作区`}),(0,m.jsx)(`nav`,{children:o.workspaces.map(e=>{let t=o.conversations.filter(t=>t.workspaceId===e.id),n=t.filter(e=>!e.archivedAt).sort(dr),r=t.filter(e=>e.archivedAt).sort(fr),i=h[e.id]??!1,a=ue[e.id]??!0;return(0,m.jsxs)(`section`,{className:`workspace-group`,children:[(0,m.jsx)(`div`,{className:`workspace-header`,children:Sr(`workspace`,e.id)?(0,m.jsx)(Hr,{initial:e.name,label:`重命名工作区 ${e.name}`,onCommit:t=>wr(`workspace`,e.id,t),onClose:xr}):(0,m.jsxs)(m.Fragment,{children:[(0,m.jsxs)(`button`,{className:`workspace-toggle`,"aria-expanded":!i,title:e.path,onContextMenu:t=>{t.preventDefault();let n=t.currentTarget.getBoundingClientRect();tt({workspace:e,x:t.clientX||n.left+24,y:t.clientY||n.top+n.height})},onClick:()=>{v(e.id),c(Ot.find(t=>t.workspaceId===e.id)?.id),g(t=>({...t,[e.id]:!i}))},children:[(0,m.jsx)(`span`,{className:`chevron chevron-folder`,"aria-hidden":`true`,children:i?`📁`:`📂`}),(0,m.jsxs)(`span`,{children:[(0,m.jsx)(`strong`,{onDoubleClick:()=>br(`workspace`,e.id),children:e.name}),(0,m.jsx)(`small`,{children:e.path})]})]}),(0,m.jsx)(`button`,{className:`workspace-add`,title:`在此工作区新建会话`,onClick:()=>ei(e.id),children:`+`}),(0,m.jsx)(`button`,{className:`workspace-settings`,title:`工作区设置(默认智能体 / 模型 / 思考强度)`,"aria-label":`工作区设置 ${e.name}`,onClick:()=>Nr(e.id),children:`⚙`}),(0,m.jsx)(`button`,{className:`workspace-delete`,title:`删除工作区`,onClick:()=>jr(e),children:`×`})]})}),!i&&(n.length?n.map(t=>(0,m.jsx)(`div`,{className:`conversation-row`,children:Sr(`conversation`,t.id)?(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(Hr,{initial:t.title,label:`重命名会话 ${t.title}`,onCommit:e=>wr(`conversation`,t.id,e),onClose:xr}),(0,m.jsx)(`small`,{className:`conversation-time`,children:ur(t.updatedAt,Tt)})]}):(0,m.jsxs)(m.Fragment,{children:[(0,m.jsxs)(`button`,{className:`conversation ${M?.id===t.id?`selected`:``}`,"data-conversation":t.id,title:t.title,"aria-label":`打开会话 ${t.title}`,onContextMenu:e=>{e.preventDefault();let n=e.currentTarget.getBoundingClientRect();it({conversation:t,x:e.clientX||n.left+24,y:e.clientY||n.top+n.height})},onClick:()=>{Zt(t.id),v(e.id)},children:[(0,m.jsx)(`i`,{className:`conversation-mark`,children:(0,m.jsx)(Yr,{state:Jr(t,ge.some(e=>e.conversationId===t.id),C.current[t.id])})}),(0,m.jsx)(`span`,{"data-conversation-title":t.id,onDoubleClick:()=>br(`conversation`,t.id),children:t.title}),(0,m.jsx)(`small`,{children:ur(t.updatedAt,Tt)})]}),(0,m.jsx)(`button`,{type:`button`,className:`conversation-fork`,"data-fork-conversation":t.id,"data-fork-state":Mi===t.id?`pending`:`idle`,disabled:!!Mi,title:`从此会话分叉出一个新会话`,"aria-label":`从会话 ${t.title} 分叉出一个新会话`,onClick:()=>Pi(t.id),children:`⑂`}),(0,m.jsx)(`button`,{className:`conversation-archive`,title:`归档会话`,"aria-label":`归档会话 ${t.title}`,onClick:()=>ti(t.id),children:`×`})]})},t.id)):(0,m.jsx)(`div`,{className:`conversation-empty`,children:`暂无会话`})),!i&&r.length>0&&(0,m.jsxs)(`div`,{className:`archived-group`,"data-archived-count":r.length,children:[(0,m.jsxs)(`button`,{className:`archived-toggle`,title:a?`展开已归档会话`:`收起已归档会话`,"aria-expanded":!a,onClick:()=>de(t=>({...t,[e.id]:!a})),children:[(0,m.jsx)(`span`,{className:`chevron`,"aria-hidden":`true`,children:a?`▸`:`▾`}),(0,m.jsxs)(`span`,{className:`archived-label`,children:[`Archived (`,r.length,`)`]})]}),!a&&r.map(e=>(0,m.jsx)(`div`,{className:`conversation-archived`,"data-archived-at":e.archivedAt,children:Sr(`conversation`,e.id)?(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(Hr,{initial:e.title,label:`重命名已归档会话 ${e.title}`,onCommit:t=>wr(`conversation`,e.id,t),onClose:xr}),(0,m.jsx)(`small`,{children:pr(e.archivedAt)})]}):(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`i`,{className:`conversation-mark`,"aria-hidden":`true`}),(0,m.jsx)(`span`,{"data-archived-title":e.id,onDoubleClick:()=>br(`conversation`,e.id),children:e.title}),(0,m.jsx)(`small`,{children:pr(e.archivedAt)}),(0,m.jsx)(`button`,{type:`button`,className:`conversation-fork`,"data-fork-conversation":e.id,"data-fork-state":Mi===e.id?`pending`:`idle`,disabled:!!Mi,title:`从此会话分叉出一个新会话`,"aria-label":`从已归档会话 ${e.title} 分叉出一个新会话`,onClick:()=>Pi(e.id),children:`⑂`}),(0,m.jsx)(`button`,{className:`conversation-restore`,title:`恢复为正常会话`,"aria-label":`恢复会话 ${e.title}`,onClick:()=>ri(e.id),children:`↑`})]})},e.id))]})]},e.id)})}),(0,m.jsxs)(`footer`,{children:[(0,m.jsxs)(`div`,{className:`footer-status`,children:[(0,m.jsx)(`i`,{className:l?`online`:`offline`}),l?`已连接`:`正在连接`,(0,m.jsx)(`span`,{children:`本地持久化`})]}),(0,m.jsxs)(`button`,{type:`button`,className:`theme-settings-trigger`,"data-theme-open":`1`,"aria-haspopup":`dialog`,"aria-expanded":qr,onClick:()=>Xr(!0),children:[(0,m.jsx)(`span`,{"aria-hidden":`true`,children:`⚙`}),`设置`]}),(0,m.jsxs)(`button`,{type:`button`,className:`theme-settings-trigger`,"data-help-open":`1`,"aria-haspopup":`dialog`,"aria-expanded":Zr,onClick:()=>Qr(!0),children:[(0,m.jsx)(`span`,{"aria-hidden":`true`,children:`📖`}),`使用指南`]}),(0,m.jsxs)(`button`,{type:`button`,className:`theme-settings-trigger restart-needed-trigger`,title:`检查 Super Pi 更新`,onClick:async()=>{Me(`updating`);try{let e=await fetch(`/api/update-check`);if(!e.ok)throw Error(`check failed`);let t=await e.json();if(t.needsRestart)F(`当前代码已更新,正在重启服务…`,`info`);else if(t.updateAvailable){if(!(await fetch(`/api/update`,{method:`POST`})).ok)throw Error(`update failed`);F(`发现新版本 v${t.latestVersion},正在更新并重启…`,`info`)}else F(`当前已是最新版本`,`info`)}catch{F(`检查更新失败,请检查网络或服务日志`,`error`)}finally{Me(`idle`)}},children:[(0,m.jsx)(`span`,{"aria-hidden":`true`,children:je===`updating`?`⟳`:`↻`}),je===`updating`?`正在检查更新…`:`检查更新`]}),ke?.needsRestart&&(0,m.jsxs)(`button`,{type:`button`,className:`theme-settings-trigger restart-needed-trigger`,title:`代码已更新:当前运行 v${ke.runningVersion},磁盘版本 v${ke.diskVersion}。点击复制重启命令 /super_pi restart`,onClick:async()=>{Me(`updating`);try{if(!(await fetch(`/api/update`,{method:`POST`})).ok)throw Error(`update failed`);F(`正在更新 Super Pi,服务即将重启…`,`info`)}catch{Me(`idle`),F(`更新启动失败,请检查服务日志`,`error`)}},children:[(0,m.jsx)(`span`,{"aria-hidden":`true`,children:je===`updating`?`⟳`:`⚠️`}),je===`updating`?`正在更新并重启…`:`更新并重启服务`]})]})]}),(0,m.jsx)(Ei,{side:`left`,width:j.sidebar,min:j.sidebarMin,max:j.sidebarMax,label:`调整左侧栏宽度`,onWidth:Ve}),(0,m.jsxs)(`section`,{className:`chat`,"data-main-view":rn,"data-main-view-kind":rn,children:[un.length>0&&(0,m.jsx)(`div`,{className:`chat-tab-strip`,"data-chat-tab-strip":`1`,"data-session-count":Xt.length,"data-file-count":P.open.length,children:(0,m.jsx)(At,{tabs:un,prefix:`workspace-tab`,ariaLabel:`工作区标签页`,activeId:dn,onSelect:fn,onClose:pn,minWidth:120,maxWidth:320})}),(0,m.jsxs)(`header`,{children:[(0,m.jsxs)(`div`,{className:`chat-head`,children:[(0,m.jsx)(`h1`,{children:M?.title??`准备开始`}),be.size>0?[...be].map(([e,t])=>(0,m.jsx)(`p`,{"data-role":`run-status`,"data-status-key":e,children:`扩展状态:${e?`${e} · `:``}${t}`},e)):(0,m.jsx)(`p`,{"data-role":`run-status`,children:M?.status===`running`?`Agent 正在工作`:`Pi runtime · Web 控制台`})]}),(0,m.jsxs)(`span`,{className:`chat-view chat-view-${rn}`,"data-main-view-indicator":rn,"data-main-view-kind":rn,title:rn===`file`?`中间主画面正在显示文件 ${P.active?.path??``}`:`中间主画面正在显示这个会话的消息流`,children:[(0,m.jsx)(`span`,{className:`chat-view-label`,children:`主区显示`}),(0,m.jsx)(`span`,{className:`chat-view-kind`,children:rn===`file`?`文件`:`会话`}),rn===`file`&&P.active&&(0,m.jsx)(`span`,{className:`chat-view-name`,"data-main-view-name":P.active.name,children:P.active.name})]}),(0,m.jsx)(`div`,{className:`badge`,children:M?.status??`idle`})]}),d&&(0,m.jsx)(`div`,{className:`error`,children:d}),M?.error&&(0,m.jsx)(`div`,{className:`error`,"data-role":`conversation-error`,children:M.error}),k&&(0,m.jsxs)(`div`,{className:`extui-title-note`,children:[`扩展建议标题:`,k]}),ve.length>0&&(0,m.jsx)(`div`,{className:`toasts`,role:`status`,"aria-live":`polite`,"data-toast-count":ve.length,children:ve.map(e=>(0,m.jsx)(`div`,{className:`toast toast-${e.kind}`,"data-toast-kind":e.kind,children:e.text},e.key))}),rn===`file`?(0,m.jsx)(`div`,{className:`chat-file-view`,"data-main-file-view":mn?.path,"data-file-view-kind":`editor`,children:mn?(0,m.jsx)(wt,{file:mn,state:P.saves[mn.path]?.state??`idle`,message:P.saves[mn.path]?.message??``,external:P.external[mn.path]===!0,onChange:e=>P.updateBuffer(mn.path,e),onSave:()=>void P.save(mn.path,!1),onReload:()=>void P.reload(mn.path),onOverwrite:()=>void P.save(mn.path,!0)}):(0,m.jsx)(`p`,{className:`rail-empty`,"data-main-file-empty":`1`,children:`没有选中的文件。`})}):(0,m.jsxs)(m.Fragment,{children:[(0,m.jsxs)(`div`,{className:`messages`,ref:ee,"data-message-scroll-container":M?.id??``,onScroll:e=>{let t=e.currentTarget;t.dataset.programmaticScroll===`true`?delete t.dataset.programmaticScroll:Mt(t)},children:[!M&&(0,m.jsxs)(`div`,{className:`empty`,children:[(0,m.jsx)(`div`,{className:`orb`,children:`π`}),(0,m.jsx)(`h2`,{children:`开始一个新会话`}),o.workspaces.length===0?(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`p`,{children:`先创建一个工作区,再开始会话。`}),(0,m.jsx)(`button`,{onClick:()=>b(!0),children:`+ 新建工作区`})]}):(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`p`,{children:o.workspaces.length===1&&kt?`在「${kt.name}」中创建会话,即可向 Super Pi 发送任务。`:`选择工作区即可开始会话,向 Super Pi 发送任务。`}),o.workspaces.length>1&&(0,m.jsxs)(`div`,{className:`empty-workspace-select`,children:[(0,m.jsx)(`label`,{htmlFor:`empty-workspace-choice`,children:`工作区:`}),(0,m.jsxs)(`select`,{id:`empty-workspace-choice`,"aria-label":`选择工作区`,value:kt?.id,onChange:e=>{e.target.value===`__new__`?b(!0):v(e.target.value)},children:[o.workspaces.map(e=>(0,m.jsx)(`option`,{value:e.id,children:e.name},e.id)),(0,m.jsx)(`option`,{value:`__new__`,children:`+ 新建工作区…`})]})]}),kt&&(0,m.jsx)(`button`,{onClick:()=>ei(kt.id),children:`新建会话`}),(0,m.jsx)(`span`,{className:`empty-secondary-action`,role:`button`,tabIndex:0,onClick:()=>b(!0),onKeyDown:e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),b(!0))},children:`+ 新建工作区`})]})]}),Yn.map(e=>{if(e.startsWith(`m:`)){let t=qn.get(e.slice(2));if(!t)return null;let n=!M?.messages.some(n=>n.id!==t.id&&n.role===`user`&&Yn.indexOf(`m:${n.id}`)>Yn.indexOf(e)),r=t.role===`user`&&M?.status!==`running`&&_i!==t.id,i=r&&n&&!t.id.startsWith(`pending:`),a=r&&!n&&!!t.piEntryId,o=t.content?(0,m.jsx)(Ke,{source:t.content}):(0,m.jsx)(`span`,{className:`typing`,children:`正在思考`}),s=t.toolCalls??[];if(t.role===`user`){let n=Oi?.id===t.id?Oi.kind:void 0,r=t.content||(t.images?.length?`[图片]`:``);return(0,m.jsxs)(`article`,{className:`message user`,"data-transcript-key":e,children:[(0,m.jsxs)(`div`,{className:`message-body`,children:[(0,m.jsxs)(`div`,{className:`bubble`,children:[(0,m.jsxs)(`div`,{className:`meta`,children:[he,(0,m.jsx)(`time`,{children:rr(t.createdAt)})]}),t.images&&t.images.length>0&&(0,m.jsx)(`div`,{className:`message-images`,"data-message-images":`1`,children:t.images.map(e=>(0,m.jsx)(`button`,{type:`button`,className:`message-image-btn`,"data-message-image-id":e.id,onClick:()=>ht({url:`/api/upload/${encodeURIComponent(M?.id??``)}/${encodeURIComponent(e.id)}`,name:e.name,bytes:e.bytes}),title:e.name?`查看大图:${e.name}`:`查看大图`,children:(0,m.jsx)(`img`,{src:`/api/upload/${encodeURIComponent(M?.id??``)}/${encodeURIComponent(e.id)}`,alt:e.name||`图片`,className:`message-image-thumb`,loading:`lazy`})},e.id))}),(0,m.jsx)(`div`,{className:`content`,children:o})]}),t.attachments&&t.attachments.length>0&&(0,m.jsx)(`div`,{className:`message-attachments`,"data-message-attachments":`1`,children:t.attachments.map(e=>{let n=`att:${t.id}:${e.kind}:${e.id}`,r=Je[n]??!1;return(0,m.jsxs)(`div`,{className:`message-attachment`,"data-message-attachment":e.kind,children:[(0,m.jsxs)(`button`,{type:`button`,className:`message-attachment-head`,"data-message-attachment-toggle":e.id,"aria-expanded":r,onClick:()=>Ye(e=>({...e,[n]:!r})),children:[(0,m.jsx)(`span`,{className:`message-attachment-glyph`,"aria-hidden":`true`,children:`▤`}),(0,m.jsx)(`span`,{className:`message-attachment-label`,children:`技能`}),(0,m.jsx)(`span`,{className:`message-attachment-name`,title:e.name,children:e.name}),(0,m.jsx)(`span`,{className:`message-attachment-chevron`,"aria-hidden":`true`,children:r?`▾`:`▸`})]}),r&&(0,m.jsx)(`pre`,{className:`message-attachment-body`,"data-message-attachment-body":e.id,children:e.content})]},n)})}),(0,m.jsxs)(`div`,{className:`message-actions`,children:[(0,m.jsx)(`button`,{type:`button`,className:`message-copy`,"data-copy-message":t.id,"data-copy-state":n,"aria-label":`复制这条消息:${r}`,onClick:()=>ji(t.id,r),children:n===`copied`?`已复制`:n===`error`?`复制失败`:`复制`}),i&&(0,m.jsx)(`button`,{type:`button`,className:`message-revert`,"data-withdraw-message":t.id,title:`撤回并编辑:删除这条消息并将文字恢复到输入框继续编辑`,onClick:()=>Di(t.id,!0),children:`撤回`}),a&&(0,m.jsx)(`button`,{type:`button`,className:`message-revert`,"data-revert-message":t.id,title:`回退到这里:删除后续全部内容(Pi 的记录和这里的历史一起清掉,不会新建会话,也不动任何代码文件)`,onClick:()=>Di(t.id),children:`回退`})]})]}),(0,m.jsx)(`div`,{className:`avatar`,title:he,children:he})]},e)}if(t.role===`tool`){let n=(()=>{let e=/<summary>([\s\S]*?)<\/summary>/.exec(t.content);return e?e[1].trim():`后台回传`})(),r=(()=>{let e=/<task id="[^"]+" state="([^"]+)"/.exec(t.content);return e?e[1]:`completed`})(),i=(()=>{let e=/<task_(?:result|error)>\n?([\s\S]*?)\n?<\/(?:task_result|task_error)>/.exec(t.content);return e?e[1]:t.content})(),a=`tool:${t.id}`,o=Je[a]??!1;return(0,m.jsx)(`article`,{className:`message tool`,"data-transcript-key":e,"data-tool-state":r,children:(0,m.jsxs)(`div`,{className:`message-body`,children:[(0,m.jsx)(`div`,{className:`bg-result-head`,"data-bg-result-head":t.id,children:(0,m.jsxs)(`button`,{type:`button`,className:`bg-result-toggle`,"data-bg-result-toggle":t.id,"aria-expanded":o,title:o?`折叠后台回传`:`展开后台回传`,onClick:()=>Ye(e=>({...e,[a]:!o})),children:[(0,m.jsx)(`span`,{className:`bg-result-glyph`,"aria-hidden":`true`,children:r===`error`?`✕`:`↩`}),(0,m.jsx)(`span`,{className:`bg-result-label`,children:`后台回传`}),(0,m.jsx)(`span`,{className:`bg-result-summary`,title:n,children:n}),!o&&(0,m.jsx)(`span`,{className:`bg-result-preview`,"data-bg-result-preview":t.id,title:`展开查看完整输出`,children:i.split(`
56
+ 如需本地计算,声明 script: { path, timeoutMs, parameters }。脚本必须是 Python,参数以 JSON 从 stdin 传入,stdout 必须只输出一个 JSON 对象。在 index.html 中加载 <script src="./super-pi-tool.js"><\/script>,然后调用 SuperPiTool.run(params)、SuperPiTool.get(runId)、SuperPiTool.wait(runId) 或 SuperPiTool.cancel(runId)。这些都是已绑定本工具身份的相对接口;不要调用 /api/exec,不要猜测 toolId,也不要传 shell 命令、任意文件路径或未声明参数。页面必须实际执行一次示例任务并显示运行状态、日志和结构化结果。最后返回注册 id、访问地址、文件清单和验证结果。`);u(e),window.setTimeout(()=>u(!1),1800)};return(0,p.useEffect)(()=>{let e=e=>{e.key===`Escape`&&n()};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[n]),(0,d.createPortal)((0,m.jsx)(`div`,{className:`tool-overlay`,"data-tool-settings-overlay":`1`,onClick:e=>{e.target===e.currentTarget&&n()},children:(0,m.jsxs)(`div`,{className:`tool-dialog`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":`tool-settings-title`,"data-tool-settings":`1`,children:[(0,m.jsxs)(`div`,{className:`tool-dialog-head`,children:[(0,m.jsx)(`h2`,{className:`tool-dialog-title`,id:`tool-settings-title`,children:`⚙ 工具设置`}),(0,m.jsx)(`button`,{type:`button`,className:`tool-dialog-close`,"data-tool-settings-close":`1`,title:`关闭(Esc)`,"aria-label":`关闭工具设置`,onClick:n,children:`✕`})]}),(0,m.jsxs)(`div`,{className:`tool-dialog-body`,children:[(0,m.jsxs)(`div`,{className:`tool-prompt-card`,children:[(0,m.jsx)(`strong`,{children:`给 Agent 开发新工具`}),(0,m.jsx)(`p`,{className:`tool-hint`,children:`工具会挂载到 Super Pi 的统一 HTTP 服务,不需要再启动独立服务器。`}),(0,m.jsx)(`button`,{type:`button`,className:`tool-link-copy-prompt`,"data-tool-development-prompt":`1`,onClick:()=>void f(),children:l?`已复制`:`复制工具开发 Prompt`})]}),(0,m.jsx)(`div`,{className:`rail-section-head`,children:(0,m.jsx)(`h2`,{className:`rail-section-title`,children:`从预置列表添加`})}),(0,m.jsx)(`div`,{className:`tool-presets`,"data-tool-presets":Yt.length,children:Yt.map(n=>{let r=e.some(e=>e.kind===n.kind);return(0,m.jsxs)(`div`,{className:`tool-preset`,"data-tool-preset":n.kind,children:[(0,m.jsx)(`span`,{className:`tool-preset-glyph`,"aria-hidden":`true`,children:n.glyph}),(0,m.jsxs)(`span`,{className:`tool-preset-text`,children:[(0,m.jsx)(`strong`,{children:n.name}),(0,m.jsx)(`small`,{children:n.blurb})]}),(0,m.jsx)(`button`,{type:`button`,className:`tool-preset-add`,"data-tool-add-preset":n.kind,disabled:r,title:r?`${n.name}已经在工具栏里了`:`添加${n.name}`,"aria-label":r?`${n.name}已添加`:`添加${n.name}`,onClick:()=>t(an(e,n.kind)),children:r?`已添加`:`添加`})]},n.kind)})}),(0,m.jsx)(`div`,{className:`rail-section-head`,children:(0,m.jsx)(`h2`,{className:`rail-section-title`,children:`添加网址工具`})}),(0,m.jsx)(`p`,{className:`tool-hint`,children:`填一个网址,它会变成一个图标;点开后在弹窗里用按钮在新标签页打开。网址只接受 http / https / mailto。`}),(0,m.jsx)(`input`,{className:`tool-input`,"data-tool-url-input":`1`,value:r,maxLength:2048,placeholder:`https://example.com/工具`,"aria-label":`工具网址`,onChange:e=>{i(e.target.value),c(void 0)}}),(0,m.jsx)(`input`,{className:`tool-input`,"data-tool-url-name":`1`,value:a,maxLength:40,placeholder:`名称(可留空,默认用域名)`,"aria-label":`工具名称(可选)`,onChange:e=>o(e.target.value)}),(0,m.jsx)(`button`,{type:`button`,className:`tool-url-add`,"data-tool-url-add":`1`,disabled:!r.trim(),onClick:()=>{let n=$t(r);n.ok?e.length>=32?c(`工具最多 32 个,先删掉一个再加。`):(t([...e,{id:rn(`link`,e.map(e=>e.id)),kind:`link`,name:a.trim()||si(n.url),glyph:`↗`,url:n.url}]),i(``),o(``),c(void 0)):c(n.reason)},children:`添加网址工具`}),s&&(0,m.jsx)(`p`,{className:`tool-error`,"data-tool-error":`1`,role:`alert`,children:s}),(0,m.jsxs)(`p`,{className:`tool-hint`,"data-tool-scope-note":`1`,children:[`能自定义到哪:从上面挑预置工具,或自己填一个网址。`,(0,m.jsx)(`b`,{children:`没有开放「写一段脚本来执行」这一档`}),`——它在浏览器里等同于任意代码执行,会拿着本应用的权限跑。计算器是手写的算术解析器,不含 eval,也认不出变量名。`]})]})]})}),document.body)}function si(e){try{return new URL(e,typeof window>`u`?`http://localhost`:window.location.href).hostname||e.slice(0,30)}catch{return e.slice(0,30)}}function ci({tool:e,onClose:t}){return(0,p.useEffect)(()=>{let e=e=>{e.key===`Escape`&&t()};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[t]),(0,d.createPortal)((0,m.jsx)(`div`,{className:`tool-overlay`,"data-tool-overlay":e.id,onClick:e=>{e.target===e.currentTarget&&t()},children:(0,m.jsxs)(`div`,{className:`tool-dialog`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":`tool-dialog-title`,"data-tool-dialog":e.id,children:[(0,m.jsxs)(`div`,{className:`tool-dialog-head`,children:[(0,m.jsxs)(`h2`,{className:`tool-dialog-title`,id:`tool-dialog-title`,children:[(0,m.jsx)(`span`,{className:`tool-dialog-glyph`,"aria-hidden":`true`,children:e.glyph}),e.name]}),(0,m.jsx)(`button`,{type:`button`,className:`tool-dialog-close`,"data-tool-close":`1`,title:`关闭(Esc)`,"aria-label":`关闭${e.name}`,onClick:t,children:`✕`})]}),(0,m.jsxs)(`div`,{className:`tool-dialog-body`,children:[e.kind===`calculator`&&(0,m.jsx)(ui,{}),e.kind===`timestamp`&&(0,m.jsx)(di,{}),e.kind===`link`&&(0,m.jsx)(fi,{tool:e})]})]})}),document.body)}function li({image:e,onClose:t}){return(0,p.useEffect)(()=>{let e=e=>{e.key===`Escape`&&t()};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[t]),(0,d.createPortal)((0,m.jsx)(`div`,{className:`lightbox-overlay`,"data-lightbox-overlay":`1`,onClick:e=>{e.target===e.currentTarget&&t()},children:(0,m.jsxs)(`div`,{className:`lightbox-dialog`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":`lightbox-title`,"data-lightbox-dialog":`1`,children:[(0,m.jsxs)(`div`,{className:`lightbox-head`,children:[(0,m.jsx)(`h2`,{className:`lightbox-title`,id:`lightbox-title`,children:e.name||`图片大图`}),(0,m.jsx)(`button`,{type:`button`,className:`lightbox-close`,"data-lightbox-close":`1`,title:`关闭(Esc)`,"aria-label":`关闭大图`,onClick:t,children:`✕`})]}),(0,m.jsx)(`div`,{className:`lightbox-body`,children:(0,m.jsx)(`img`,{src:e.url,alt:e.name||`图片大图`,className:`lightbox-image`})})]})}),document.body)}function ui(){let[e,t]=(0,p.useState)(`12 * (3 + 4)`),[n,r]=(0,p.useState)(!1),i=vn(e),a=e=>{r(!1),t(t=>n?e:t+e)};return(0,m.jsxs)(`div`,{className:`tool-calc`,"data-tool-calc":`1`,children:[(0,m.jsx)(`input`,{className:`tool-input tool-calc-input`,"data-calc-input":`1`,value:e,maxLength:240,"aria-label":`算式`,spellCheck:!1,autoComplete:`off`,placeholder:`例如 (12 + 8) / 4`,onChange:e=>{r(!1),t(e.target.value)},onKeyDown:e=>{e.key===`Enter`&&e.preventDefault()}}),(0,m.jsx)(`div`,{className:`tool-calc-keys`,"data-calc-keys":`1`,children:[[`7`,`7`],[`8`,`8`],[`9`,`9`],[`÷`,`/`],[`(`,`(`],[`)`,`)`],[`4`,`4`],[`5`,`5`],[`6`,`6`],[`×`,`*`],[`^`,`^`],[`%`,`%`],[`1`,`1`],[`2`,`2`],[`3`,`3`],[`-`,`-`],[`←`,`back`],[`C`,`clear`],[`0`,`0`],[`.`,`.`],[`+`,`+`]].map(([e,n])=>(0,m.jsx)(`button`,{type:`button`,className:`tool-calc-key`,"data-calc-key":n,"aria-label":e===`C`?`清空`:e===`←`?`退格`:`输入 ${e}`,onClick:()=>{n===`clear`?(t(``),r(!1)):n===`back`?(t(e=>e.slice(0,-1)),r(!1)):a(n)},children:e},e))}),i.ok?(0,m.jsx)(`p`,{className:`tool-calc-result`,"data-calc-result":`1`,"data-calc-value":String(i.value),children:yn(i.value)}):(0,m.jsx)(`p`,{className:`tool-calc-error`,"data-calc-error":`1`,role:`alert`,children:i.reason})]})}function di(){let[e,t]=(0,p.useState)(``),n=bn(e),r=n.ok?xn(n.ms):null;return(0,m.jsxs)(`div`,{className:`tool-stamp`,"data-tool-stamp":`1`,children:[(0,m.jsx)(`input`,{className:`tool-input`,"data-stamp-input":`1`,value:e,maxLength:64,spellCheck:!1,"aria-label":`时间戳或日期`,placeholder:`1757000000 或 2026-10-08T09:00:00`,onChange:e=>t(e.target.value)}),r?(0,m.jsxs)(`dl`,{className:`tool-stamp-out`,"data-stamp-result":`1`,children:[(0,m.jsxs)(`div`,{children:[(0,m.jsx)(`dt`,{children:`本地时间`}),(0,m.jsx)(`dd`,{"data-stamp-local":`1`,children:r.local})]}),(0,m.jsxs)(`div`,{children:[(0,m.jsx)(`dt`,{children:`ISO(UTC)`}),(0,m.jsx)(`dd`,{"data-stamp-iso":`1`,children:r.iso})]}),(0,m.jsxs)(`div`,{children:[(0,m.jsx)(`dt`,{children:`秒`}),(0,m.jsx)(`dd`,{"data-stamp-seconds":`1`,children:r.seconds})]}),(0,m.jsxs)(`div`,{children:[(0,m.jsx)(`dt`,{children:`毫秒`}),(0,m.jsx)(`dd`,{"data-stamp-ms":`1`,children:r.milliseconds})]})]}):(0,m.jsx)(`p`,{className:`tool-stamp-error`,"data-stamp-error":`1`,role:`alert`,children:n.ok?`这个日期无法显示。`:n.reason})]})}function fi({tool:e}){let[t,n]=(0,p.useState)(!1),r=async()=>{let e=await gr(`请为 Super Pi 开发一个全局右侧栏工具并自动挂载。不要启动独立 HTTP 服务器。通过内置 tool_register 提交完整的 HTML/JS/CSS 文件;工具将被 Super Pi 保存并在所有 workspace 可用。如需计算,注册一个 Python 脚本(不接受 shell 命令),参数经 JSON stdin 传入,stdout 只输出 JSON 结果。在页面里加载 ./super-pi-tool.js,使用 SuperPiTool.run(params)、wait(runId)、get(runId) 和 cancel(runId) 执行、等待、查看和取消任务。它们会自动绑定当前工具;不要调用 /api/exec,也不要猜测 toolId。`);n(e),window.setTimeout(()=>n(!1),1800)};return(0,m.jsxs)(`div`,{className:`tool-link`,"data-tool-link":e.id,children:[(0,m.jsx)(`p`,{className:`tool-hint`,children:`这是旧版外部网页链接;新工具由 Super Pi 统一托管,并可选绑定受控 Python 脚本。`}),(0,m.jsx)(`code`,{className:`tool-link-url`,"data-tool-link-url":e.id,children:e.url}),(0,m.jsxs)(`div`,{className:`tool-link-actions`,children:[(0,m.jsx)(`a`,{className:`tool-link-open`,"data-tool-link-open":e.id,href:e.url,target:`_blank`,rel:`noopener noreferrer`,title:e.url,children:`在新标签页打开 ?`}),(0,m.jsx)(`button`,{type:`button`,className:`tool-link-copy-prompt`,"data-tool-copy-prompt":e.id,onClick:()=>void r(),children:t?`已复制`:`复制开发 Prompt`})]}),/^https?:\/\//i.test(e.url??``)&&(0,m.jsx)(`iframe`,{className:`tool-link-frame`,"data-tool-frame":e.id,src:e.url,title:e.name,sandbox:`allow-forms allow-modals allow-pointer-lock allow-popups allow-same-origin allow-scripts`,referrerPolicy:`no-referrer`})]})}var pi=`(max-width: 1100px)`,mi=(e,t,n)=>Math.min(Math.max(e,t),Math.max(t,n));function hi({tool:e,onClose:t}){return(0,d.createPortal)((0,m.jsx)(`div`,{className:`tool-overlay`,onClick:e=>{e.target===e.currentTarget&&t()},children:(0,m.jsxs)(`div`,{className:`tool-dialog`,role:`dialog`,"aria-modal":`true`,children:[(0,m.jsxs)(`div`,{className:`tool-dialog-head`,children:[(0,m.jsxs)(`h2`,{className:`tool-dialog-title`,children:[`🧩 `,e.name]}),(0,m.jsx)(`button`,{type:`button`,className:`tool-dialog-close`,onClick:t,children:`✕`})]}),(0,m.jsx)(`div`,{className:`tool-dialog-body`,children:(0,m.jsx)(`iframe`,{className:`tool-link-frame`,src:`/tools/${encodeURIComponent(e.id)}/`,title:e.name,sandbox:`allow-forms allow-modals allow-popups allow-scripts`,referrerPolicy:`no-referrer`})})]})}),document.body)}function gi(e,t,n){let r=Math.max(0,Math.round(n)-480),i=mi(520,180,Math.max(180,r-240)),a=mi(Math.round(e),180,i),o=mi(640,240,Math.max(240,r-a));return{sidebar:a,rail:mi(Math.round(t),240,o),sidebarMin:180,sidebarMax:i,railMin:240,railMax:o,sumCap:r}}var _i=`super-pi.rail-widths.v1`,vi=`super-pi.reader-state.v1`,yi=48,bi=160,xi=e=>{e.dataset.programmaticScroll=`true`,window.setTimeout(()=>{delete e.dataset.programmaticScroll},0)};function Si(){try{if(typeof window>`u`)return{conversations:{}};let e=JSON.parse(window.localStorage.getItem(`super-pi.reader-state.v1`)??`null`);if(!e||typeof e!=`object`)return{conversations:{}};let t={};if(e.conversations&&typeof e.conversations==`object`)for(let[n,r]of Object.entries(e.conversations)){let e=r;typeof e.followTail==`boolean`&&(t[n]={followTail:e.followTail,scrollTop:Number.isFinite(e.scrollTop)?Math.max(0,Number(e.scrollTop)):0,...typeof e.anchorKey==`string`?{anchorKey:e.anchorKey}:{},...Number.isFinite(e.anchorOffset)?{anchorOffset:Number(e.anchorOffset)}:{}})}let n={};if(e.readAt&&typeof e.readAt==`object`)for(let[t,r]of Object.entries(e.readAt))typeof r==`string`&&(n[t]=r);return{activeId:typeof e.activeId==`string`?e.activeId:void 0,workspaceId:typeof e.workspaceId==`string`?e.workspaceId:void 0,conversations:t,readAt:n}}catch{return{conversations:{}}}}function Ci(e){try{typeof window<`u`&&window.localStorage.setItem(vi,JSON.stringify(e))}catch{}}function wi(){try{if(typeof window>`u`)return{};let e=window.localStorage.getItem(_i);if(e===null)return{};let t=JSON.parse(e),n=e=>typeof e==`number`&&Number.isFinite(e)&&e>0?Math.round(e):void 0;return{sidebar:n(t.sidebar),rail:n(t.rail)}}catch{return{}}}function Ti(e){try{if(typeof window>`u`)return;window.localStorage.setItem(_i,JSON.stringify(e))}catch{}}function Ei({side:e,width:t,min:n,max:r,label:i,onWidth:a}){let[o,s]=(0,p.useState)(!1),c=e=>e.shiftKey?64:16;return(0,m.jsx)(`div`,{className:`rail-resizer rail-resizer-${e}${o?` rail-resizer-dragging`:``}`,"data-rail-resizer":e,"data-rail-resizer-dragging":o?`true`:`false`,"data-rail-resizer-width":t,role:`separator`,"aria-orientation":`vertical`,"aria-label":i,"aria-valuenow":t,"aria-valuemin":n,"aria-valuemax":r,tabIndex:0,onMouseDown:i=>{i.preventDefault(),i.currentTarget.focus();let o=i.clientX,c=t;s(!0);let l=document.body,u=l.style.cursor,d=l.style.userSelect;l.style.cursor=`col-resize`,l.style.userSelect=`none`;let f=t=>{let i=e===`left`?t.clientX-o:o-t.clientX;a(mi(c+i,n,r))},p=()=>{l.style.cursor=u,l.style.userSelect=d,s(!1),window.removeEventListener(`mousemove`,f),window.removeEventListener(`mouseup`,p)};window.addEventListener(`mousemove`,f),window.addEventListener(`mouseup`,p)},onKeyDown:i=>{let o=c(i);i.key===`ArrowLeft`?(i.preventDefault(),a(mi(e===`left`?t-o:t+o,n,r))):i.key===`ArrowRight`?(i.preventDefault(),a(mi(e===`left`?t+o:t-o,n,r))):i.key===`Home`?(i.preventDefault(),a(n)):i.key===`End`&&(i.preventDefault(),a(r))}})}function Di(){let[e,t]=(0,p.useState)(tr),[n,r]=(0,p.useState)([]),i=(0,p.useRef)([]),a=(0,p.useRef)(null);a.current||=Si();let o=n.reduce((e,t)=>t.apply(e),e),[s,c]=(0,p.useState)(a.current.activeId),[l,u]=(0,p.useState)(!1),[d,f]=(0,p.useState)(),[h,g]=(0,p.useState)({}),[_,v]=(0,p.useState)(a.current.workspaceId),[y,b]=(0,p.useState)(!1),x=(0,p.useRef)(void 0),S=(0,p.useRef)(a.current.conversations),C=(0,p.useRef)(a.current.readAt??{}),[w,T]=(0,p.useState)(0),ee=(0,p.useRef)(null),E=(0,p.useRef)(null),te=(0,p.useRef)(void 0),ne=(0,p.useRef)(a.current.activeId),[re,ie]=(0,p.useState)({followTail:!0,scrollTop:0}),ae=(0,p.useRef)(void 0),oe=(0,p.useRef)(new Map),se=(0,p.useRef)(void 0),D=(0,p.useCallback)(()=>{if(se.current!==void 0&&(cancelAnimationFrame(se.current),se.current=void 0),!oe.current.size)return;let e=[...oe.current.values()];oe.current.clear();let n=new Map,r=new Map;for(let t of e){let e=`${t.conversationId}\u0000${t.messageId}`;if(t.type===`message.thinking`){let n=r.get(e);r.set(e,{text:`${n?.text??``}${t.delta}`,at:n?.at??t.at})}else n.set(e,`${n.get(e)??``}${t.delta}`)}t(e=>({...e,conversations:e.conversations.map(e=>{let t=e.messages.map(t=>{let i=`${e.id}\u0000${t.id}`,a=n.get(i),o=r.get(i);return a===void 0&&o===void 0?t:{...t,...a===void 0?{}:{content:t.content+a},...o===void 0?{}:{thinking:(t.thinking??``)+o.text,thinkingBlocks:[...t.thinkingBlocks??[],{at:o.at??t.content.length,text:o.text}]}}});return t.every((t,n)=>t===e.messages[n])?e:{...e,messages:t}})}))},[]),ce=(0,p.useCallback)(e=>{let t=`${e.type}\u0000${e.conversationId}\u0000${e.messageId}`,n=oe.current.get(t);oe.current.set(t,n?{...n,delta:n.delta+e.delta}:e),se.current===void 0&&(se.current=requestAnimationFrame(D))},[D]),le=(0,p.useCallback)(e=>{ae.current=e},[]),[ue,de]=(0,p.useState)({}),[fe,pe]=(0,p.useState)(()=>{if(typeof window>`u`)return``;try{return window.localStorage.getItem(`super-pi.user-nickname`)??``}catch{return``}}),me=e=>{let t=e.slice(0,24);pe(t);try{window.localStorage.setItem(`super-pi.user-nickname`,t)}catch{}},he=fe.trim()||`你`,[ge,_e]=(0,p.useState)([]),[ve,ye]=(0,p.useState)([]),[be,xe]=(0,p.useState)(new Map),[Se,O]=(0,p.useState)(new Map),[k,Ce]=(0,p.useState)(),[we,Te]=(0,p.useState)(),[Ee,De]=(0,p.useState)(),[A,Oe]=(0,p.useState)(),[ke,Ae]=(0,p.useState)(),[je,Me]=(0,p.useState)(`idle`),[Ne,Pe]=(0,p.useState)(()=>typeof window<`u`&&typeof window.matchMedia==`function`&&window.matchMedia(`(max-width: 1100px)`).matches),[Fe,Ie]=(0,p.useState)(`chat`),[Le,Re]=(0,p.useState)(()=>typeof window>`u`?1440:window.innerWidth);(0,p.useEffect)(()=>{let e=()=>Re(window.innerWidth);return window.addEventListener(`resize`,e),e(),()=>window.removeEventListener(`resize`,e)},[]);let[ze,Be]=(0,p.useState)(wi),j=gi(ze.sidebar??270,ze.rail??320,Le);(0,p.useEffect)(()=>{Ti({sidebar:j.sidebar,rail:j.rail})},[j.sidebar,j.rail]);let Ve=e=>Be(t=>({...t,sidebar:e})),He=e=>Be(t=>({...t,rail:e}));(0,p.useEffect)(()=>{if(typeof window>`u`||typeof window.matchMedia!=`function`)return;let e=window.matchMedia(pi),t=e=>{e.matches&&Pe(!0)};return e.addEventListener(`change`,t),e.matches&&Pe(!0),()=>e.removeEventListener(`change`,t)},[]);let[Ue,We]=(0,p.useState)(new Map),[Ge,qe]=(0,p.useState)({}),[Je,Ye]=(0,p.useState)({}),[Xe,Ze]=(0,p.useState)({}),[Qe,$e]=(0,p.useState)(),[et,tt]=(0,p.useState)(),[nt,it]=(0,p.useState)(),ot=(0,p.useRef)(new Map),[st,ct]=(0,p.useState)({}),lt=(0,p.useRef)(new Set),[ut,dt]=(0,p.useState)([]),[ft,pt]=(0,p.useState)([]),[mt,ht]=(0,p.useState)(),[gt,_t]=(0,p.useState)(!1),vt=(0,p.useRef)(null),yt=(0,p.useRef)(!1),bt=(0,p.useRef)(0),xt=(0,p.useRef)([]),St=(0,p.useRef)(void 0),Ct=(0,p.useRef)(!1),[Tt,Dt]=(0,p.useState)(()=>Date.now());(0,p.useEffect)(()=>{let e=window.setInterval(()=>Dt(Date.now()),3e4);return()=>window.clearInterval(e)},[]);let Ot=(0,p.useMemo)(()=>o.conversations.filter(e=>!e.archivedAt),[o.conversations]),M=(0,p.useMemo)(()=>Ot.find(e=>e.id===s)??Ot.find(e=>e.workspaceId===_)??(_&&o.workspaces.some(e=>e.id===_)?void 0:Ot[0]),[s,Ot,_,o.workspaces]),kt=(0,p.useMemo)(()=>o.workspaces.find(e=>e.id===_)??o.workspaces[0],[o.workspaces,_]),jt=(0,p.useRef)(_);jt.current=_,(0,p.useEffect)(()=>{M&&v(M.workspaceId)},[M?.id]),(0,p.useEffect)(()=>{St.current=s},[s]),(0,p.useEffect)(()=>{te.current=M?.id},[M?.id]),(0,p.useEffect)(()=>{let e=M?.id;if(!e)return;let t=o.conversations.find(t=>t.id===e);if(!t)return;let n=C.current[e];C.current[e]=t.updatedAt,n!==t.updatedAt&&T(e=>e+1),Ci({activeId:St.current,workspaceId:M?.workspaceId??_,conversations:S.current,readAt:C.current})},[M?.id,o.conversations]),(0,p.useEffect)(()=>{l&&s&&Ci({activeId:s,workspaceId:M?.workspaceId??_,conversations:S.current})},[s,M?.workspaceId,l,_]),(0,p.useEffect)(()=>{let e=ee.current;if(!e||!M?.id)return;let t=M.id,n=S.current[t]??{followTail:!0,scrollTop:0};ie(n),requestAnimationFrame(()=>{if(ee.current===e){if(n.followTail)xi(e),e.scrollTop=e.scrollHeight;else{let t=n.anchorKey?e.querySelector(`[data-transcript-key="${CSS.escape(n.anchorKey)}"]`):null;e.scrollTop=t?Math.max(0,e.scrollTop+t.getBoundingClientRect().top-(n.anchorOffset??0)-e.getBoundingClientRect().top):n.scrollTop}}}),ne.current=void 0},[M?.id]);let Mt=(0,p.useCallback)((e,t=!1,n=!1)=>{let r=te.current;if(!r)return;let i=e.scrollHeight-e.clientHeight-e.scrollTop,a=S.current[r]??{followTail:!0,scrollTop:0},o=t?!1:n?!0:a.followTail?i<=bi:i<=yi,s,c;if(!o){let t=e.getBoundingClientRect().top,n=[...e.querySelectorAll(`[data-transcript-key]`)].find(e=>e.getBoundingClientRect().bottom>t);s=n?.dataset.transcriptKey,c=n?n.getBoundingClientRect().top-t:void 0}let l={followTail:o,scrollTop:e.scrollTop,...s?{anchorKey:s,anchorOffset:c}:{}};S.current[r]=l,ie(l),Ci({activeId:r,workspaceId:jt.current,conversations:S.current})},[]),Nt=st[M?.id??``]??{text:``,images:[],attachments:[],uploading:0},Pt=Nt.text,Ft=Nt.images,It=Nt.attachments,Lt=Nt.uploading,zt=(e,t)=>ct(n=>({...n,[e]:t(n[e]??{text:``,images:[],attachments:[],uploading:0})})),Vt=e=>zt(M?.id??``,t=>({...t,text:e})),Ht=e=>zt(M?.id??``,t=>({...t,attachments:t.attachments.some(t=>t.kind===e.kind&&t.id===e.id)?t.attachments:[...t.attachments,e]})),Ut=(e,t)=>zt(M?.id??``,n=>({...n,attachments:n.attachments.filter(n=>n.kind!==e||n.id!==t)})),[Wt,Gt]=(0,p.useState)({visible:!1,query:``,rect:null}),Kt=e=>zt(M?.id??``,t=>({...t,images:typeof e==`function`?e(t.images):e})),[qt,Jt]=(0,p.useState)([]),Yt=qt.length>0?qt:M?[M.id]:[],Xt=(0,p.useMemo)(()=>Yt.map(e=>Ot.find(t=>t.id===e)).filter(e=>e!==void 0),[Yt,Ot]),Zt=(0,p.useCallback)(e=>{c(e);let t=Ot.find(t=>t.id===e)?.workspaceId;t&&v(t),an(`session`),Jt(t=>t.includes(e)?t:t.length>0?[...t,e]:[...M?.id&&M.id!==e?[M.id]:[],e])},[M]),N=(0,p.useCallback)(e=>{let t=qt.indexOf(e);if(t<0)return;let n=qt.filter(t=>t!==e);if(Jt(n),M?.id!==e)return;let r=n[Math.min(t,n.length-1)];r&&c(r)},[qt,M]),P=Et(_),[Qt,$t]=(0,p.useState)(Gr[0].id),[en,tn]=(0,p.useState)(!1);(0,p.useEffect)(()=>{let e=!1;return fetch(`/api/terminal`).then(e=>e.json()).then(t=>{e||tn(!!t?.enabled)}).catch(()=>{e||tn(!1)}),()=>{e=!0}},[]);let[rn,an]=(0,p.useState)(`session`);(0,p.useEffect)(()=>{rn===`file`&&!P.active&&an(`session`)},[rn,P.active]);let on=(0,p.useCallback)(e=>{P.setActivePath(e),an(`file`),$t(`files`)},[P.setActivePath]),sn=(0,p.useCallback)(async e=>{await P.openFile(e),an(`file`)},[P.openFile]),cn=(0,p.useMemo)(()=>Xt.map(e=>({id:`session:${e.id}`,rawId:e.id,kind:`session`,label:e.title,title:e.title,closable:Xt.length>1||P.open.length>0,badge:(0,m.jsx)(Yr,{state:Jr(e,ge.some(t=>t.conversationId===e.id),C.current[e.id])})})),[Xt,P.open.length,ge,o.conversations,w]),ln=(0,p.useMemo)(()=>P.open.map(e=>({id:`file:${e.path}`,rawId:e.path,kind:`file`,label:e.name,title:e.path,closable:!0,badge:e.dirty?(0,m.jsx)(`span`,{className:`file-tab-badge`,"data-file-tab-dirty":e.path,"aria-label":`未保存`,children:`●`}):void 0})),[P.open]),un=(0,p.useMemo)(()=>[...cn,...ln],[cn,ln]),dn=rn===`file`&&P.activePath?`file:${P.activePath}`:M?.id?`session:${M.id}`:void 0,fn=(0,p.useCallback)(e=>{if(e.startsWith(`file:`)){let t=e.slice(5);on(t)}else if(e.startsWith(`session:`)){let t=e.slice(8);Zt(t)}},[on,Zt]),pn=(0,p.useCallback)(e=>{if(e.startsWith(`file:`)){let t=e.slice(5);P.closeFile(t),P.activePath===t&&P.open.length<=1&&an(`session`)}else if(e.startsWith(`session:`)){let t=e.slice(8);N(t)}},[P,N]),mn=P.active,hn=M?.id;(0,p.useEffect)(()=>{hn&&!hn.startsWith(`pending:`)&&bn({type:`conversation.select`,conversationId:hn})},[hn]);let F=(e,t=`info`)=>{if(!e)return;let n=++bt.current;ye(r=>[...r,{key:n,text:e,kind:t}]),xt.current.push(window.setTimeout(()=>ye(e=>e.filter(e=>e.key!==n)),7e3))},gn=(e,t)=>{let n=e.findIndex(e=>e.id===t.id);if(n<0)return[t,...e];let r=[...e];return r[n]=t,r},_n=(e,t)=>{let n=e.findIndex(e=>e.id===t.id);if(n<0)return[t,...e];let r=[...e];return r[n]=t,r},vn=(e,t)=>{if(t.blocking)_e(n=>[...n.filter(n=>n.conversationId!==e||n.request.requestId!==t.requestId),{conversationId:e,request:t}]);else if(t.method===`notify`)F(t.message||t.title,t.notifyType??`info`);else if(t.method===`setStatus`){let e=t.statusKey??``;xe(n=>{let r=new Map(n);return t.statusText===void 0?r.delete(e):r.set(e,t.statusText),r})}else if(t.method===`setWidget`){let e=t.widgetKey??``;O(n=>{let r=new Map(n);return t.widgetLines===void 0?r.delete(e):r.set(e,{lines:t.widgetLines,placement:t.widgetPlacement??`aboveEditor`}),r})}else t.method===`setTitle`?Ce(t.title):t.method===`set_editor_text`&&e===St.current&&Vt(t.text??``)},yn=(e,t,n)=>{_e(n=>n.filter(n=>n.conversationId!==e||n.request.requestId!==t));let r=Dr[n];r&&F(r,`warning`)};(0,p.useEffect)(()=>{let e=!1,n=0,o,s=()=>{if(e)return;let l=location.protocol===`https:`?`wss`:`ws`,d=new WebSocket(`${l}://${location.host}/ws`);x.current=d,d.onopen=()=>{e||(n=0,u(!0),d.send(JSON.stringify({type:`stream.configure`,coalesce:!0})),d.send(JSON.stringify({type:`catalog.fetch`,conversationId:St.current})),d.send(JSON.stringify({type:`agent.catalog.fetch`})),d.send(JSON.stringify({type:`subagent.catalog.fetch`})))},d.onclose=()=>{if(!e){Ct.current=!1;for(let e of i.current)e.onFailure?.();i.current=[],r([]),u(!1),o=window.setTimeout(s,Math.min(hr,mr*2**n++))}},d.onmessage=({data:n})=>{if(e)return;let o=JSON.parse(n);if(o.type!==`message.delta`&&o.type!==`message.thinking`&&D(),o.type.startsWith(`terminal.`))ae.current?.(o);else if(o.type===`conversation.autoRename.result`){let e=ot.current.get(o.conversationId);e&&(ot.current.delete(o.conversationId),o.ok?e.resolve(o.title):e.reject(Error(o.error)))}else if(o.type===`message.thinking`)ce(o);else if(o.type===`state`)D(),Ct.current=!1,i.current=i.current.filter(e=>!e.accepted?.(o.state)),r(i.current),t(o.state),u(!0),c(e=>{let t=o.state.conversations.filter(e=>!e.archivedAt),n=a.current?.activeId,r=!!(e&&t.some(t=>t.id===e)),s=!!(e&&i.current.some(t=>t.temporaryId===e));return r||s?e:n&&t.some(e=>e.id===n)?n:t.find(e=>e.workspaceId===jt.current)?.id}),v(e=>{let t=a.current?.workspaceId;return t&&o.state.workspaces.some(e=>e.id===t)?t:e&&(o.state.workspaces.some(t=>t.id===e)||i.current.some(t=>t.temporaryId===e))?e:o.state.workspaces[0]?.id});else if(o.type===`message.delta`)ce(o);else if(o.type===`message.revert.result`)vi(void 0),$e(void 0),o.ok||f(o.message);else if(o.type===`operation.result`){let e=i.current.find(e=>e.id===o.requestId);if(i.current=i.current.filter(e=>e.id!==o.requestId),r(i.current),e?.temporaryId){let t=e.temporaryId;c(n=>n===t?o.ok?o.entityId:e.previousActiveId:n),Jt(e=>e.flatMap(e=>e===t?o.ok&&o.entityId?[o.entityId]:[]:[e])),v(e=>e===t?o.ok?o.entityId:void 0:e)}o.ok||(e?.onFailure?.(),f(o.message??`操作失败`))}else o.type===`error`?(f(o.message),Ct.current&&(Ct.current=!1,F(`重命名失败:${o.message}`,`error`))):o.type===`extension.ui`?vn(o.conversationId,o.request):o.type===`extension.ui.closed`?yn(o.conversationId,o.requestId,o.reason):o.type===`model.catalog`?(!o.conversationId||o.conversationId===St.current)&&Te(o.catalog):o.type===`agent.catalog`?De(o.catalog):o.type===`subagent.catalog`?Oe(o.catalog):o.type===`server.version`?(Ae(o.version),o.version.needsRestart&&F(`检测到 Super Pi 代码已更新(v${o.version.runningVersion} → v${o.version.diskVersion}),请执行 /super_pi restart 重启服务生效`,`warning`)):o.type===`subagent.event`?t(e=>({...e,subagentRuns:gn(e.subagentRuns??[],o.run)})):o.type===`workflow.event`?t(e=>({...e,workflows:_n(e.workflows??[],o.workflow)})):o.type===`message.tools`?t(e=>({...e,conversations:e.conversations.map(e=>e.id===o.conversationId?{...e,messages:e.messages.map(e=>e.id===o.messageId?{...e,toolCalls:o.toolCalls}:e)}:e)})):o.type===`pi.queue`?We(e=>{let t=new Map(e);return t.set(o.conversationId,{steering:o.steering,followUp:o.followUp}),t}):o.type===`conversation.fork.result`&&(Ni(e=>e===o.conversationId?void 0:e),F(o.ok?`已从「${o.title}」分叉出新会话,放在侧边栏最上面;没有切换过去。${o.messagesCopied?`复制了 ${o.messagesCopied} 条消息。`:`原会话还没有消息,只复制了会话本身。`}`:`分叉失败:${o.message}`,o.ok?`info`:`error`))}};return s(),()=>{e=!0,D(),window.clearTimeout(o);for(let e of xt.current)window.clearTimeout(e);x.current?.close()}},[ce,D]);let bn=(0,p.useCallback)(e=>x.current?.readyState===WebSocket.OPEN&&(x.current.send(JSON.stringify(e)),!0),[]),I=(e,t,n,a,o,s)=>{if(x.current?.readyState!==WebSocket.OPEN||i.current.some(e=>e.key===t))return!1;let c=nn(),l={id:c,key:t,apply:n,temporaryId:a,previousActiveId:St.current,onFailure:o,accepted:s};return i.current=[...i.current,l],r(i.current),x.current.send(JSON.stringify({...e,operationRequestId:c})),!0},xn=(e,t)=>n=>({...n,conversations:n.conversations.map(n=>n.id===e?{...n,...t}:n)}),Sn=(e,t)=>n=>({...n,workspaces:n.workspaces.map(n=>n.id===e?{...n,...t}:n)}),Cn=n.some(e=>e.key===`send:${M?.id}`||e.key===`configuration:${M?.id}`||e.temporaryId===M?.id||e.key===`archive:${M?.id}`),wn=(e,t,n,r,i)=>{e&&x.current?.send(JSON.stringify({type:`question.answer`,conversationId:e,messageId:t,callIndex:n,answers:r,cancelled:i}))},Tn=(e,t,n)=>I({type:`extension.ui.answer`,conversationId:e,requestId:t,...n},`answer:${e}:${t}`,e=>e),En=M?.model??we?.defaultModel,Dn=M?.thinkingLevel??we?.defaultThinkingLevel,On=we?.source===`pi`,An=!!we?.models.find(e=>e.provider===En?.provider&&e.modelId===En?.modelId)?.imageInput,jn=(e,t)=>!e||!t?e:{...e,models:e.models.filter(e=>(o.modelFilterRules??[]).every(n=>{try{return!new RegExp(n.workspacePattern,`i`).test(t.name)||new RegExp(n.modelPattern,`i`).test(e.label)||new RegExp(n.modelPattern,`i`).test(`${e.provider}/${e.modelId}`)}catch{return!0}}))},Mn=(jn(we,o.workspaces.find(e=>e.id===M?.workspaceId))?.models??[]).map(e=>({key:`${e.provider}/${e.modelId}`,label:e.label,detail:e.detail,badge:Cr(e),selected:En?.provider===e.provider&&En.modelId===e.modelId})),Nn=(we?.levels??[]).map(e=>({key:e,label:e,selected:e===Dn})),Pn=e=>{if(!M)return;let[t,n]=e.split(`/`);t&&n&&I({type:`conversation.model`,conversationId:M.id,provider:t,modelId:n},`configuration:${M.id}`,xn(M.id,{model:we?.models.find(e=>e.provider===t&&e.modelId===n)??{provider:t,modelId:n,label:n}}))},Fn=e=>{M&&I({type:`conversation.thinking`,conversationId:M.id,level:e},`configuration:${M.id}`,xn(M.id,{thinkingLevel:e}))},In=Ee?.agents.find(e=>e.id===M?.agentId),Rn=[{key:L,label:`跟随 Pi 默认`,detail:`不使用任何 agent 角色`,selected:!M?.agentId},...(Ee?.agents??[]).map(e=>({key:e.id,label:e.name,detail:e.toolSummary,color:e.color,selected:M?.agentId===e.id}))],zn=In?.model,Bn=!!(zn&&M?.model&&M.model.provider===zn.provider&&M.model.modelId===zn.modelId);In?.thinkingLevel&&(M?.thinkingLevel,In.thinkingLevel);let Vn=e=>{if(M){let t=Ee?.agents.find(t=>t.id===e);I({type:`conversation.agent`,conversationId:M.id,agentId:e===L?null:e},`configuration:${M.id}`,xn(M.id,{agentId:t?.id,...t?.model?{model:{...t.model,label:t.model.modelId}}:{},...t?.thinkingLevel?{thinkingLevel:t.thinkingLevel}:{}}))}},Hn=ge.filter(e=>e.conversationId===M?.id),Un=(0,p.useMemo)(()=>(o.subagentRuns??[]).filter(e=>e.conversationId===M?.id),[o.subagentRuns,M?.id]),Wn=(0,p.useMemo)(()=>(o.workflows??[]).filter(e=>e.conversationId===M?.id),[o.workflows,M?.id]),Gn=(0,p.useMemo)(()=>new Map(Wn.map(e=>[e.id,e])),[Wn]),Kn=(0,p.useMemo)(()=>new Map((A?.agents??[]).map(e=>[e.id,e.name])),[A]),qn=(0,p.useMemo)(()=>new Map((M?.messages??[]).map(e=>[e.id,e])),[M?.messages]),Jn=(0,p.useMemo)(()=>new Map(Un.map(e=>[e.id,e])),[Un]),Yn=(0,p.useMemo)(()=>{if(!M)return[];let e=Qe?.conversationId===M.id?(()=>{let e=M.messages.find(e=>e.id===Qe.messageId);if(e)return{index:M.messages.indexOf(e),at:Date.parse(e.createdAt)||0,...Qe.kind===`withdraw`?{inclusive:!0}:{inclusive:!1}}})():void 0,t=[];return M.messages.forEach((n,r)=>{e&&(r>e.index||e.inclusive&&r===e.index)||t.push({at:Date.parse(n.createdAt)||0,seq:r,key:`m:${n.id}`})}),Un.forEach((n,r)=>{e&&(Date.parse(n.startedAt)||0)>e.at||t.push({at:Date.parse(n.startedAt)||0,seq:t.length+r,key:`r:${n.id}`})}),Wn.forEach((n,r)=>{e&&(Date.parse(n.startedAt)||0)>e.at||t.push({at:Date.parse(n.startedAt)||0,seq:t.length+r,key:`w:${n.id}`})}),t.sort((e,t)=>e.at-t.at||e.seq-t.seq).map(e=>e.key)},[M,Un,Wn,Qe]);(0,p.useEffect)(()=>{let e=ee.current;if(!e||!M?.id)return;let t=()=>{S.current[M.id]?.followTail&&(xi(e),e.scrollTop=e.scrollHeight)};if(t(),typeof ResizeObserver>`u`)return;let n=new ResizeObserver(t);n.observe(e);for(let t of e.children)n.observe(t);return()=>n.disconnect()},[M?.id,M?.messages,Un,Wn,Yn]);let Xn=e=>qe(t=>({...t,[e]:!t[e]})),Zn=(e,t)=>Ye(n=>({...n,[`${e}:${t}`]:!n[`${e}:${t}`]})),Qn=(e,t)=>Ze(n=>({...n,[`${e}:${t}`]:n[`${e}:${t}`]!==!1})),[$n,er]=(0,p.useState)({}),ir=e=>er(t=>({...t,[e]:t[e]===!1})),ar=e=>{I({type:`subagent.stop`,runId:e},`stop:${e}`,e=>e)||F(`连接断开,无法停止子智能体,请重试`,`error`)},or=e=>{I({type:`workflow.stop`,workflowId:e},`stop:${e}`,e=>e)||F(`连接断开,无法停止编排,请重试`,`error`)},sr=e=>[...Se].filter(([,t])=>t.placement===e).map(([e,t])=>{let n=t.lines;return(0,m.jsxs)(`div`,{className:`extui-widget`,"data-widget-key":e,"data-widget-lines":n.length,children:[n.slice(0,Or).map((e,t)=>(0,m.jsx)(`div`,{children:e},`${t}:${e}`)),n.length>Or&&(0,m.jsx)(`div`,{children:`… (widget truncated)`})]},e)}),cr=(e,t)=>{let n=`pending:${nn()}`,r={id:n,name:e,path:t,updatedAt:new Date().toISOString()};I({type:`workspace.create`,name:e,path:t},`create-workspace`,e=>({...e,workspaces:[...e.workspaces,r]}),n)&&(v(n),c(void 0),b(!1))},[lr,yr]=(0,p.useState)(),br=(e,t)=>yr(n=>n?.kind===e&&n.id===t?n:{kind:e,id:t}),xr=()=>yr(void 0),Sr=(e,t)=>lr?.kind===e&&lr.id===t,wr=(e,t,n)=>t.startsWith(`pending:`)?!1:I(e===`workspace`?{type:`workspace.rename`,workspaceId:t,name:n}:{type:`conversation.rename`,conversationId:t,name:n},`rename:${t}`,e===`workspace`?Sn(t,{name:n}):xn(t,{title:n}))?(Ct.current=!0,xr(),!0):(F(`连接断开,改名未保存,请重试`,`error`),!1),Tr=async e=>{if(e.startsWith(`pending:`)){F(`这个会话还在创建中,稍后再试`,`warning`);return}if(ot.current.has(e))return;F(`正在根据对话内容生成标题…`,`info`);let t=new Promise((t,n)=>{ot.current.set(e,{resolve:t,reject:n}),window.setTimeout(()=>{ot.current.delete(e)&&n(Error(`模型在 30 秒内没有返回标题`))},32e3)});if(!bn({type:`conversation.autoRename`,conversationId:e}))ot.current.delete(e),F(`连接断开,无法生成标题`,`error`);else try{let n=await t;wr(`conversation`,e,n),F(`已重命名为「${n}」`,`info`)}catch(e){F(`重命名失败:${e instanceof Error?e.message:String(e)}`,`error`)}},jr=e=>{if(e.id.startsWith(`pending:`))return;let t=o.conversations.filter(t=>t.workspaceId===e.id).length;window.confirm(`删除工作区「${e.name}」?${t?`其中 ${t} 条会话将一并删除,`:``}此操作不可撤销。`)&&I({type:`workspace.delete`,workspaceId:e.id},`delete:${e.id}`,t=>({...t,workspaces:t.workspaces.filter(t=>t.id!==e.id),conversations:t.conversations.filter(t=>t.workspaceId!==e.id)}))},[Mr,Nr]=(0,p.useState)(),Fr=Mr?o.workspaces.find(e=>e.id===Mr):void 0,{theme:Br,setTheme:Kr}=kn(),[qr,Xr]=(0,p.useState)(!1),[Zr,Qr]=(0,p.useState)(!1),$r=(e,t,n,r)=>{e.startsWith(`pending:`)||(I({type:`workspace.settings`,workspaceId:e,model:n?{provider:n.provider,modelId:n.modelId}:null,thinkingLevel:r??null,agentId:t??null},`defaults:${e}`,Sn(e,{defaultModel:n,defaultThinkingLevel:r,defaultAgentId:t}))?Nr(void 0):F(`连接断开,设置未保存,请重试`,`error`))},ei=e=>{if(e.startsWith(`pending:`)||i.current.some(t=>t.key===`defaults:${e}`))return;let t=`pending:${nn()}`,n={id:t,workspaceId:e,title:`新会话`,status:`idle`,updatedAt:new Date().toISOString(),messages:[]};I({type:`conversation.create`,workspaceId:e},`create-session:${e}`,e=>({...e,conversations:[n,...e.conversations]}),t)&&(v(e),c(t),an(`session`),Jt(e=>[...e.filter(e=>e!==t),t]))},ti=e=>{e.startsWith(`pending:`)||I({type:`conversation.archive`,conversationId:e},`archive:${e}`,xn(e,{archivedAt:new Date().toISOString()}))},ri=e=>I({type:`conversation.unarchive`,conversationId:e},`archive:${e}`,xn(e,{archivedAt:void 0})),ii=async e=>{if(!M){F(`请先选择或创建会话`,`error`);return}if(!An){F(`当前模型不支持图片输入`,`error`);return}let t=[`image/png`,`image/jpeg`,`image/gif`,`image/webp`],n=e.filter(e=>t.includes(e.type)?e.size>5242880?(F(`图片「${e.name}」超过 5MB 上限`,`error`),!1):!0:(F(`不支持的文件格式「${e.name}」,仅支持 PNG/JPEG/GIF/WebP`,`error`),!1));if(n.length===0)return;let r=M.id;zt(r,e=>({...e,uploading:e.uploading+n.length}));let i=n.map(e=>({id:nn(),conversationId:r,name:e.name,url:URL.createObjectURL(e)}));pt(e=>[...e,...i]);for(let[e,t]of n.entries()){let n=i[e];try{let e=await new Promise((e,n)=>{let r=new FileReader;r.onload=()=>{let t=r.result,n=t.indexOf(`,`);e(n>=0?t.slice(n+1):t)},r.onerror=n,r.readAsDataURL(t)}),i=await fetch(`/api/upload`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({conversationId:r,name:t.name,mimeType:t.type,data:e})});if(!(i.headers.get(`content-type`)??``).includes(`application/json`)){F(`服务器未返回 JSON 响应,请刷新或重启服务`,`error`);continue}if(i.ok){let e=await i.json();if(lt.current.has(n.id))continue;pt(e=>e.filter(e=>e.id!==n.id)),URL.revokeObjectURL(n.url),zt(r,t=>({...t,images:[...t.images,{id:e.id,name:e.name,mimeType:e.mimeType,bytes:e.bytes}]}))}else{let e=await i.json().catch(()=>({error:`上传失败`}));F(e.error||`上传失败: ${i.status}`,`error`)}}catch(e){F(`上传错误: ${e instanceof Error?e.message:`网络错误`}`,`error`)}finally{pt(e=>e.map(e=>e.id===n.id?{...e,failed:!0}:e)),zt(r,e=>({...e,uploading:Math.max(0,e.uploading-1)}))}}},ai=e=>{let t=e.clipboardData?.items;if(!t)return;let n=[];for(let e=0;e<t.length;e++){let r=t[e];if(r.type.startsWith(`image/`)){let e=r.getAsFile();e&&n.push(e)}}if(n.length>0){if(!An){F(`当前模型不支持图片输入`,`error`);return}e.preventDefault(),ii(n)}},oi=e=>{e.dataTransfer?.types?.includes(`Files`)&&(e.preventDefault(),_t(!0))},si=e=>{e.currentTarget.contains(e.relatedTarget)||_t(!1)},ci=e=>{_t(!1);let t=Array.from(e.dataTransfer?.files??[]).filter(e=>e.type.startsWith(`image/`));if(t.length>0){if(e.preventDefault(),!An){F(`当前模型不支持图片输入`,`error`);return}ii(t)}},ui=t=>{if(t.preventDefault(),!M||Cn||!Pt.trim()&&Ft.length===0)return;if(Ft.length>0&&!An){F(`当前模型不支持图片输入,请移除图片或切换至支持图文的模型`,`error`);return}if(Lt>0){F(`图片正在上传中,请稍候…`,`info`);return}let n=M.status===`running`,r=`pending:${nn()}`,i=M.id,a=Pt,o=Ft,s=new Set([...e.conversations.find(e=>e.id===i)?.messages.map(e=>e.id)??[],...e.queuedMessages?.map(e=>e.id)??[]]);I({type:`message.send`,conversationId:M.id,content:Pt,...Ft.length>0?{images:Ft}:{},...It.length>0?{attachments:It}:{}},`send:${i}`,e=>e.conversations.find(e=>e.id===i)?.messages.some(e=>!s.has(e.id)&&e.role===`user`&&e.content===a.trim())||e.queuedMessages?.some(e=>!s.has(e.id)&&e.conversationId===i&&e.content===a.trim())||n?e:{...e,conversations:e.conversations.map(e=>e.id===i?{...e,messages:[...e.messages,{id:r,role:`user`,content:a,images:o,createdAt:new Date().toISOString()}]}:e)},void 0,()=>{dt(e=>[...e,{id:r,conversationId:i,content:a,images:o}]),zt(i,e=>e.text||e.images.length?e:{...e,text:a,images:o})},e=>!!(e.conversations.find(e=>e.id===i)?.messages.some(e=>!s.has(e.id)&&e.role===`user`&&e.content===a.trim())||e.queuedMessages?.some(e=>!s.has(e.id)&&e.conversationId===i&&e.content===a.trim())))?zt(M.id,e=>({...e,text:``,images:[]})):F(`连接断开,消息未发送,草稿已保留,请重试`,`error`)},di=(0,p.useMemo)(()=>(o.queuedMessages??[]).filter(e=>e.conversationId===M?.id),[o.queuedMessages,M?.id]),fi=e=>{I({type:`message.cancel`,conversationId:M?.id??``,queueId:e},`queue:${e}`,t=>({...t,queuedMessages:t.queuedMessages?.filter(t=>t.id!==e)}))||F(`连接断开,取消没有生效,请重试`,`error`)},mi=e=>{I({type:`message.steer`,conversationId:M?.id??``,queueId:e},`queue:${e}`,t=>({...t,queuedMessages:t.queuedMessages?.map(t=>t.id===e?{...t,priority:!0}:t)}))||F(`连接断开,优先发送没有生效,请重试`,`error`)},hi=e=>{I({type:`message.sendQueued`,conversationId:M?.id??``,queueId:e},`send-queued:${e}`,t=>({...t,queuedMessages:t.queuedMessages?.filter(t=>t.id!==e)}),void 0,void 0,t=>!t.queuedMessages?.some(t=>t.id===e))||F(`连接断开,发送没有生效,请重试`,`error`)},[_i,vi]=(0,p.useState)(),Di=(e,t=!1)=>{if(!M||_i)return;let n=M.messages.find(t=>t.id===e);n&&(n&&ct(e=>{let t=e[M.id]??{text:``,images:[],uploading:0};return t.text.trim()||t.images.length?e:{...e,[M.id]:{...t,text:n.content,images:n.images??[]}}}),$e({conversationId:M.id,messageId:e,kind:`withdraw`})),bn({type:`message.revert`,conversationId:M.id,messageId:e,withdraw:!0})?vi(e):($e(void 0),F(`连接断开,回退未执行,请重试`,`error`))},[Oi,ki]=(0,p.useState)(),Ai=(0,p.useRef)(void 0),ji=async(e,t)=>{Ai.current&&clearTimeout(Ai.current);let n=await gr(t);ki({id:e,kind:n?`copied`:`error`}),n||F(`复制失败:浏览器拒绝了剪贴板写入,可以手动选中这条消息的文字复制`,`error`),Ai.current=setTimeout(()=>ki(void 0),2e3)};(0,p.useEffect)(()=>()=>{Ai.current&&clearTimeout(Ai.current)},[]);let[Mi,Ni]=(0,p.useState)(),Pi=e=>{Mi||e.startsWith(`pending:`)||(bn({type:`conversation.fork`,conversationId:e})?Ni(e):F(`连接断开,分叉没有执行,请重试`,`error`))};return(0,m.jsxs)(`main`,{className:`shell${Ne?` shell-rail-collapsed`:``} mobile-panel-${Fe}`,"data-rail-collapsed":Ne,"data-selected-workspace":_??``,style:{"--sidebar-width":`${j.sidebar}px`,"--rail-col":Ne?void 0:`${j.rail}px`},children:[(0,m.jsxs)(`aside`,{children:[(0,m.jsxs)(`div`,{className:`brand`,children:[(0,m.jsx)(`span`,{children:`π`}),(0,m.jsxs)(`div`,{children:[(0,m.jsx)(`strong`,{children:`SUPER PI`}),(0,m.jsx)(`small`,{children:`agent workspace`})]})]}),(0,m.jsx)(`div`,{className:`sidebar-actions`,children:(0,m.jsx)(`button`,{className:`workspace-new`,onClick:()=>b(!0),children:`+ 工作区`})}),(0,m.jsx)(`div`,{className:`label`,children:`工作区`}),(0,m.jsx)(`nav`,{children:o.workspaces.map(e=>{let t=o.conversations.filter(t=>t.workspaceId===e.id),n=t.filter(e=>!e.archivedAt).sort(dr),r=t.filter(e=>e.archivedAt).sort(fr),i=h[e.id]??!1,a=ue[e.id]??!0;return(0,m.jsxs)(`section`,{className:`workspace-group`,children:[(0,m.jsx)(`div`,{className:`workspace-header`,children:Sr(`workspace`,e.id)?(0,m.jsx)(Hr,{initial:e.name,label:`重命名工作区 ${e.name}`,onCommit:t=>wr(`workspace`,e.id,t),onClose:xr}):(0,m.jsxs)(m.Fragment,{children:[(0,m.jsxs)(`button`,{className:`workspace-toggle`,"aria-expanded":!i,title:e.path,onContextMenu:t=>{t.preventDefault();let n=t.currentTarget.getBoundingClientRect();tt({workspace:e,x:t.clientX||n.left+24,y:t.clientY||n.top+n.height})},onClick:()=>{v(e.id),c(Ot.find(t=>t.workspaceId===e.id)?.id),g(t=>({...t,[e.id]:!i}))},children:[(0,m.jsx)(`span`,{className:`chevron chevron-folder`,"aria-hidden":`true`,children:i?`📁`:`📂`}),(0,m.jsxs)(`span`,{children:[(0,m.jsx)(`strong`,{onDoubleClick:()=>br(`workspace`,e.id),children:e.name}),(0,m.jsx)(`small`,{children:e.path})]})]}),(0,m.jsx)(`button`,{className:`workspace-add`,title:`在此工作区新建会话`,onClick:()=>ei(e.id),children:`+`}),(0,m.jsx)(`button`,{className:`workspace-settings`,title:`工作区设置(默认智能体 / 模型 / 思考强度)`,"aria-label":`工作区设置 ${e.name}`,onClick:()=>Nr(e.id),children:`⚙`}),(0,m.jsx)(`button`,{className:`workspace-delete`,title:`删除工作区`,onClick:()=>jr(e),children:`×`})]})}),!i&&(n.length?n.map(t=>(0,m.jsx)(`div`,{className:`conversation-row`,children:Sr(`conversation`,t.id)?(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(Hr,{initial:t.title,label:`重命名会话 ${t.title}`,onCommit:e=>wr(`conversation`,t.id,e),onClose:xr}),(0,m.jsx)(`small`,{className:`conversation-time`,children:ur(t.updatedAt,Tt)})]}):(0,m.jsxs)(m.Fragment,{children:[(0,m.jsxs)(`button`,{className:`conversation ${M?.id===t.id?`selected`:``}`,"data-conversation":t.id,title:t.title,"aria-label":`打开会话 ${t.title}`,onContextMenu:e=>{e.preventDefault();let n=e.currentTarget.getBoundingClientRect();it({conversation:t,x:e.clientX||n.left+24,y:e.clientY||n.top+n.height})},onClick:()=>{Zt(t.id),v(e.id)},children:[(0,m.jsx)(`i`,{className:`conversation-mark`,children:(0,m.jsx)(Yr,{state:Jr(t,ge.some(e=>e.conversationId===t.id),C.current[t.id])})}),(0,m.jsx)(`span`,{"data-conversation-title":t.id,onDoubleClick:()=>br(`conversation`,t.id),children:t.title}),(0,m.jsx)(`small`,{children:ur(t.updatedAt,Tt)})]}),(0,m.jsx)(`button`,{type:`button`,className:`conversation-fork`,"data-fork-conversation":t.id,"data-fork-state":Mi===t.id?`pending`:`idle`,disabled:!!Mi,title:`从此会话分叉出一个新会话`,"aria-label":`从会话 ${t.title} 分叉出一个新会话`,onClick:()=>Pi(t.id),children:`⑂`}),(0,m.jsx)(`button`,{className:`conversation-archive`,title:`归档会话`,"aria-label":`归档会话 ${t.title}`,onClick:()=>ti(t.id),children:`×`})]})},t.id)):(0,m.jsx)(`div`,{className:`conversation-empty`,children:`暂无会话`})),!i&&r.length>0&&(0,m.jsxs)(`div`,{className:`archived-group`,"data-archived-count":r.length,children:[(0,m.jsxs)(`button`,{className:`archived-toggle`,title:a?`展开已归档会话`:`收起已归档会话`,"aria-expanded":!a,onClick:()=>de(t=>({...t,[e.id]:!a})),children:[(0,m.jsx)(`span`,{className:`chevron`,"aria-hidden":`true`,children:a?`▸`:`▾`}),(0,m.jsxs)(`span`,{className:`archived-label`,children:[`Archived (`,r.length,`)`]})]}),!a&&r.map(e=>(0,m.jsx)(`div`,{className:`conversation-archived`,"data-archived-at":e.archivedAt,children:Sr(`conversation`,e.id)?(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(Hr,{initial:e.title,label:`重命名已归档会话 ${e.title}`,onCommit:t=>wr(`conversation`,e.id,t),onClose:xr}),(0,m.jsx)(`small`,{children:pr(e.archivedAt)})]}):(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`i`,{className:`conversation-mark`,"aria-hidden":`true`}),(0,m.jsx)(`span`,{"data-archived-title":e.id,onDoubleClick:()=>br(`conversation`,e.id),children:e.title}),(0,m.jsx)(`small`,{children:pr(e.archivedAt)}),(0,m.jsx)(`button`,{type:`button`,className:`conversation-fork`,"data-fork-conversation":e.id,"data-fork-state":Mi===e.id?`pending`:`idle`,disabled:!!Mi,title:`从此会话分叉出一个新会话`,"aria-label":`从已归档会话 ${e.title} 分叉出一个新会话`,onClick:()=>Pi(e.id),children:`⑂`}),(0,m.jsx)(`button`,{className:`conversation-restore`,title:`恢复为正常会话`,"aria-label":`恢复会话 ${e.title}`,onClick:()=>ri(e.id),children:`↑`})]})},e.id))]})]},e.id)})}),(0,m.jsxs)(`footer`,{children:[(0,m.jsxs)(`div`,{className:`footer-status`,children:[(0,m.jsx)(`i`,{className:l?`online`:`offline`}),l?`已连接`:`正在连接`,(0,m.jsx)(`span`,{children:`本地持久化`})]}),(0,m.jsxs)(`button`,{type:`button`,className:`theme-settings-trigger`,"data-theme-open":`1`,"aria-haspopup":`dialog`,"aria-expanded":qr,onClick:()=>Xr(!0),children:[(0,m.jsx)(`span`,{"aria-hidden":`true`,children:`⚙`}),`设置`]}),(0,m.jsxs)(`button`,{type:`button`,className:`theme-settings-trigger`,"data-help-open":`1`,"aria-haspopup":`dialog`,"aria-expanded":Zr,onClick:()=>Qr(!0),children:[(0,m.jsx)(`span`,{"aria-hidden":`true`,children:`📖`}),`使用指南`]}),(0,m.jsxs)(`button`,{type:`button`,className:`theme-settings-trigger restart-needed-trigger`,title:`检查 Super Pi 更新`,onClick:async()=>{Me(`updating`);try{let e=await fetch(`/api/update-check`);if(!e.ok)throw Error(`check failed`);let t=await e.json();if(t.needsRestart)F(`当前代码已更新,正在重启服务…`,`info`);else if(t.updateAvailable){if(!(await fetch(`/api/update`,{method:`POST`})).ok)throw Error(`update failed`);F(`发现新版本 v${t.latestVersion},正在更新并重启…`,`info`)}else F(`当前已是最新版本`,`info`)}catch{F(`检查更新失败,请检查网络或服务日志`,`error`)}finally{Me(`idle`)}},children:[(0,m.jsx)(`span`,{"aria-hidden":`true`,children:je===`updating`?`⟳`:`↻`}),je===`updating`?`正在检查更新…`:`检查更新`]}),ke?.needsRestart&&(0,m.jsxs)(`button`,{type:`button`,className:`theme-settings-trigger restart-needed-trigger`,title:`代码已更新:当前运行 v${ke.runningVersion},磁盘版本 v${ke.diskVersion}。点击复制重启命令 /super_pi restart`,onClick:async()=>{Me(`updating`);try{if(!(await fetch(`/api/update`,{method:`POST`})).ok)throw Error(`update failed`);F(`正在更新 Super Pi,服务即将重启…`,`info`)}catch{Me(`idle`),F(`更新启动失败,请检查服务日志`,`error`)}},children:[(0,m.jsx)(`span`,{"aria-hidden":`true`,children:je===`updating`?`⟳`:`⚠️`}),je===`updating`?`正在更新并重启…`:`更新并重启服务`]})]})]}),(0,m.jsx)(Ei,{side:`left`,width:j.sidebar,min:j.sidebarMin,max:j.sidebarMax,label:`调整左侧栏宽度`,onWidth:Ve}),(0,m.jsxs)(`section`,{className:`chat`,"data-main-view":rn,"data-main-view-kind":rn,children:[un.length>0&&(0,m.jsx)(`div`,{className:`chat-tab-strip`,"data-chat-tab-strip":`1`,"data-session-count":Xt.length,"data-file-count":P.open.length,children:(0,m.jsx)(At,{tabs:un,prefix:`workspace-tab`,ariaLabel:`工作区标签页`,activeId:dn,onSelect:fn,onClose:pn,minWidth:120,maxWidth:320})}),(0,m.jsxs)(`header`,{children:[(0,m.jsxs)(`div`,{className:`chat-head`,children:[(0,m.jsx)(`h1`,{children:M?.title??`准备开始`}),be.size>0?[...be].map(([e,t])=>(0,m.jsx)(`p`,{"data-role":`run-status`,"data-status-key":e,children:`扩展状态:${e?`${e} · `:``}${t}`},e)):(0,m.jsx)(`p`,{"data-role":`run-status`,children:M?.status===`running`?`Agent 正在工作`:`Pi runtime · Web 控制台`})]}),(0,m.jsxs)(`span`,{className:`chat-view chat-view-${rn}`,"data-main-view-indicator":rn,"data-main-view-kind":rn,title:rn===`file`?`中间主画面正在显示文件 ${P.active?.path??``}`:`中间主画面正在显示这个会话的消息流`,children:[(0,m.jsx)(`span`,{className:`chat-view-label`,children:`主区显示`}),(0,m.jsx)(`span`,{className:`chat-view-kind`,children:rn===`file`?`文件`:`会话`}),rn===`file`&&P.active&&(0,m.jsx)(`span`,{className:`chat-view-name`,"data-main-view-name":P.active.name,children:P.active.name})]}),(0,m.jsx)(`div`,{className:`badge`,children:M?.status??`idle`})]}),d&&(0,m.jsx)(`div`,{className:`error`,children:d}),M?.error&&(0,m.jsx)(`div`,{className:`error`,"data-role":`conversation-error`,children:M.error}),k&&(0,m.jsxs)(`div`,{className:`extui-title-note`,children:[`扩展建议标题:`,k]}),ve.length>0&&(0,m.jsx)(`div`,{className:`toasts`,role:`status`,"aria-live":`polite`,"data-toast-count":ve.length,children:ve.map(e=>(0,m.jsx)(`div`,{className:`toast toast-${e.kind}`,"data-toast-kind":e.kind,children:e.text},e.key))}),rn===`file`?(0,m.jsx)(`div`,{className:`chat-file-view`,"data-main-file-view":mn?.path,"data-file-view-kind":`editor`,children:mn?(0,m.jsx)(wt,{file:mn,state:P.saves[mn.path]?.state??`idle`,message:P.saves[mn.path]?.message??``,external:P.external[mn.path]===!0,onChange:e=>P.updateBuffer(mn.path,e),onSave:()=>void P.save(mn.path,!1),onReload:()=>void P.reload(mn.path),onOverwrite:()=>void P.save(mn.path,!0)}):(0,m.jsx)(`p`,{className:`rail-empty`,"data-main-file-empty":`1`,children:`没有选中的文件。`})}):(0,m.jsxs)(m.Fragment,{children:[(0,m.jsxs)(`div`,{className:`messages`,ref:ee,"data-message-scroll-container":M?.id??``,onScroll:e=>{let t=e.currentTarget;t.dataset.programmaticScroll===`true`?delete t.dataset.programmaticScroll:Mt(t)},children:[!M&&(0,m.jsxs)(`div`,{className:`empty`,children:[(0,m.jsx)(`div`,{className:`orb`,children:`π`}),(0,m.jsx)(`h2`,{children:`开始一个新会话`}),o.workspaces.length===0?(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`p`,{children:`先创建一个工作区,再开始会话。`}),(0,m.jsx)(`button`,{onClick:()=>b(!0),children:`+ 新建工作区`})]}):(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(`p`,{children:o.workspaces.length===1&&kt?`在「${kt.name}」中创建会话,即可向 Super Pi 发送任务。`:`选择工作区即可开始会话,向 Super Pi 发送任务。`}),o.workspaces.length>1&&(0,m.jsxs)(`div`,{className:`empty-workspace-select`,children:[(0,m.jsx)(`label`,{htmlFor:`empty-workspace-choice`,children:`工作区:`}),(0,m.jsxs)(`select`,{id:`empty-workspace-choice`,"aria-label":`选择工作区`,value:kt?.id,onChange:e=>{e.target.value===`__new__`?b(!0):v(e.target.value)},children:[o.workspaces.map(e=>(0,m.jsx)(`option`,{value:e.id,children:e.name},e.id)),(0,m.jsx)(`option`,{value:`__new__`,children:`+ 新建工作区…`})]})]}),kt&&(0,m.jsx)(`button`,{onClick:()=>ei(kt.id),children:`新建会话`}),(0,m.jsx)(`span`,{className:`empty-secondary-action`,role:`button`,tabIndex:0,onClick:()=>b(!0),onKeyDown:e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),b(!0))},children:`+ 新建工作区`})]})]}),Yn.map(e=>{if(e.startsWith(`m:`)){let t=qn.get(e.slice(2));if(!t)return null;let n=!M?.messages.some(n=>n.id!==t.id&&n.role===`user`&&Yn.indexOf(`m:${n.id}`)>Yn.indexOf(e)),r=t.role===`user`&&M?.status!==`running`&&_i!==t.id,i=r&&n&&!t.id.startsWith(`pending:`),a=r&&!n&&!!t.piEntryId,o=t.content?(0,m.jsx)(Ke,{source:t.content}):(0,m.jsx)(`span`,{className:`typing`,children:`正在思考`}),s=t.toolCalls??[];if(t.role===`user`){let n=Oi?.id===t.id?Oi.kind:void 0,r=t.content||(t.images?.length?`[图片]`:``);return(0,m.jsxs)(`article`,{className:`message user`,"data-transcript-key":e,children:[(0,m.jsxs)(`div`,{className:`message-body`,children:[(0,m.jsxs)(`div`,{className:`bubble`,children:[(0,m.jsxs)(`div`,{className:`meta`,children:[he,(0,m.jsx)(`time`,{children:rr(t.createdAt)})]}),t.images&&t.images.length>0&&(0,m.jsx)(`div`,{className:`message-images`,"data-message-images":`1`,children:t.images.map(e=>(0,m.jsx)(`button`,{type:`button`,className:`message-image-btn`,"data-message-image-id":e.id,onClick:()=>ht({url:`/api/upload/${encodeURIComponent(M?.id??``)}/${encodeURIComponent(e.id)}`,name:e.name,bytes:e.bytes}),title:e.name?`查看大图:${e.name}`:`查看大图`,children:(0,m.jsx)(`img`,{src:`/api/upload/${encodeURIComponent(M?.id??``)}/${encodeURIComponent(e.id)}`,alt:e.name||`图片`,className:`message-image-thumb`,loading:`lazy`})},e.id))}),(0,m.jsx)(`div`,{className:`content`,children:o})]}),t.attachments&&t.attachments.length>0&&(0,m.jsx)(`div`,{className:`message-attachments`,"data-message-attachments":`1`,children:t.attachments.map(e=>{let n=`att:${t.id}:${e.kind}:${e.id}`,r=Je[n]??!1;return(0,m.jsxs)(`div`,{className:`message-attachment`,"data-message-attachment":e.kind,children:[(0,m.jsxs)(`button`,{type:`button`,className:`message-attachment-head`,"data-message-attachment-toggle":e.id,"aria-expanded":r,onClick:()=>Ye(e=>({...e,[n]:!r})),children:[(0,m.jsx)(`span`,{className:`message-attachment-glyph`,"aria-hidden":`true`,children:`▤`}),(0,m.jsx)(`span`,{className:`message-attachment-label`,children:`技能`}),(0,m.jsx)(`span`,{className:`message-attachment-name`,title:e.name,children:e.name}),(0,m.jsx)(`span`,{className:`message-attachment-chevron`,"aria-hidden":`true`,children:r?`▾`:`▸`})]}),r&&(0,m.jsx)(`pre`,{className:`message-attachment-body`,"data-message-attachment-body":e.id,children:e.content})]},n)})}),(0,m.jsxs)(`div`,{className:`message-actions`,children:[(0,m.jsx)(`button`,{type:`button`,className:`message-copy`,"data-copy-message":t.id,"data-copy-state":n,"aria-label":`复制这条消息:${r}`,onClick:()=>ji(t.id,r),children:n===`copied`?`已复制`:n===`error`?`复制失败`:`复制`}),i&&(0,m.jsx)(`button`,{type:`button`,className:`message-revert`,"data-withdraw-message":t.id,title:`撤回并编辑:删除这条消息并将文字恢复到输入框继续编辑`,onClick:()=>Di(t.id,!0),children:`撤回`}),a&&(0,m.jsx)(`button`,{type:`button`,className:`message-revert`,"data-revert-message":t.id,title:`回退到这里:删除后续全部内容(Pi 的记录和这里的历史一起清掉,不会新建会话,也不动任何代码文件)`,onClick:()=>Di(t.id),children:`回退`})]})]}),(0,m.jsx)(`div`,{className:`avatar`,title:he,children:he})]},e)}if(t.role===`tool`){let n=(()=>{let e=/<summary>([\s\S]*?)<\/summary>/.exec(t.content);return e?e[1].trim():`后台回传`})(),r=(()=>{let e=/<task id="[^"]+" state="([^"]+)"/.exec(t.content);return e?e[1]:`completed`})(),i=(()=>{let e=/<task_(?:result|error)>\n?([\s\S]*?)\n?<\/(?:task_result|task_error)>/.exec(t.content);return e?e[1]:t.content})(),a=`tool:${t.id}`,o=Je[a]??!1;return(0,m.jsx)(`article`,{className:`message tool`,"data-transcript-key":e,"data-tool-state":r,children:(0,m.jsxs)(`div`,{className:`message-body`,children:[(0,m.jsx)(`div`,{className:`bg-result-head`,"data-bg-result-head":t.id,children:(0,m.jsxs)(`button`,{type:`button`,className:`bg-result-toggle`,"data-bg-result-toggle":t.id,"aria-expanded":o,title:o?`折叠后台回传`:`展开后台回传`,onClick:()=>Ye(e=>({...e,[a]:!o})),children:[(0,m.jsx)(`span`,{className:`bg-result-glyph`,"aria-hidden":`true`,children:r===`error`?`✕`:`↩`}),(0,m.jsx)(`span`,{className:`bg-result-label`,children:`后台回传`}),(0,m.jsx)(`span`,{className:`bg-result-summary`,title:n,children:n}),!o&&(0,m.jsx)(`span`,{className:`bg-result-preview`,"data-bg-result-preview":t.id,title:`展开查看完整输出`,children:i.split(`
57
57
  `).slice(-1)[0]}),(0,m.jsx)(`span`,{className:`bg-result-chevron`,"aria-hidden":`true`,children:o?`▾`:`▸`})]})}),o&&(0,m.jsx)(`div`,{className:`bg-result-body`,"data-bg-result-body":t.id,children:(0,m.jsx)(`pre`,{className:`bg-result-pre`,"data-bg-result-pre":t.id,children:i})})]})},e)}let c=t.thinking?.trim()?t.thinking:``,l=t.thinkingBlocks?.length?t.thinkingBlocks:c?[{at:0,text:c}]:[],u=(e,n)=>{let r=typeof e.at==`number`&&Number.isFinite(e.at)?e.at:n;return Math.max(0,Math.min(r,t.content.length))},d=new Set(s.map((e,t)=>c&&typeof e.thinkingAt==`number`&&e.thinkingAt<c.length?t:-1).filter(e=>e>=0)),f=[],p=0,h=s.map((e,t)=>({call:e,index:t,at:u(e,t)})).filter(({index:e})=>!d.has(e)).sort((e,t)=>e.at-t.at||e.index-t.index);for(let{call:e,index:n,at:r}of h){let i=Math.max(p,r);i>p&&f.push({text:t.content.slice(p,i)}),p=i,f.push({call:e,index:n})}f.push({text:t.content.slice(p)});let g=[];if(c){let e=0,t=s.map((e,t)=>({call:e,index:t,at:Math.max(0,Math.min(e.thinkingAt??0,c.length))})).filter(({index:e})=>d.has(e)).sort((e,t)=>e.at-t.at||e.index-t.index);for(let{call:n,index:r,at:i}of t){let t=Math.max(e,i);t>e&&g.push({text:c.slice(e,t)}),e=t,g.push({call:n,index:r})}g.push({text:c.slice(e)})}c.split(`
58
58
  `).map(e=>e.trim()).find(e=>e.length>0);let _=e=>e.call.question?(0,m.jsx)(`div`,{className:`call-blocks`,"data-call-block-index":e.index,children:(0,m.jsx)(zr,{question:e.call.question,index:e.index,expanded:`${t.id}:${e.index}`in Xe?!!Xe[`${t.id}:${e.index}`]:!0,onToggle:()=>Qn(t.id,e.index),onAnswer:(n,r)=>wn(M?.id??``,t.id,e.index,n,r)})}):(0,m.jsx)(`div`,{className:`call-blocks`,"data-call-block-index":e.index,children:(0,m.jsx)(Pr,{call:e.call,index:e.index,expanded:`${t.id}:${e.index}`in Je&&!!Je[`${t.id}:${e.index}`],onToggle:()=>Zn(t.id,e.index)})});return(0,m.jsxs)(`article`,{className:`message assistant`,"data-transcript-key":e,children:[(0,m.jsx)(`div`,{className:`avatar`,children:`π`}),(0,m.jsxs)(`div`,{className:`message-body`,children:[(0,m.jsxs)(`div`,{className:`meta`,children:[t.agentIdentity?.name??`Super Pi`,(0,m.jsx)(`time`,{children:rr(t.createdAt)})]}),l.map((e,n)=>(0,m.jsxs)(Ir,{messageId:`${t.id}:${n}`,nestedCount:s.filter(t=>typeof t.thinkingAt==`number`&&t.thinkingAt>=e.at&&(l[n+1]?.at===void 0||t.thinkingAt<l[n+1].at)).length,preview:e.text.split(`
59
59
  `).map(e=>e.trim()).find(Boolean)??``,expanded:$n[`${t.id}:${n}`]!==!1,onToggle:()=>ir(`${t.id}:${n}`),children:[(0,m.jsx)(`div`,{className:`content thinking-text`,children:(0,m.jsx)(Ke,{source:e.text})}),s.filter(t=>typeof t.thinkingAt==`number`&&t.thinkingAt>=e.at&&(l[n+1]?.at===void 0||t.thinkingAt<l[n+1].at)).map((e,t)=>_({call:e,index:s.indexOf(e)}))]},`${t.id}:thinking:${n}`)),f.map((e,t)=>`text`in e?e.text?(0,m.jsx)(`div`,{className:`content`,children:(0,m.jsx)(Ke,{source:e.text})},`t${t}`):null:_(e))]})]},e)}if(e.startsWith(`w:`)){let t=Gn.get(e.slice(2));return t?(0,m.jsxs)(`div`,{"data-transcript-key":e,children:[n.some(e=>e.key===`stop:${t.id}`)&&(0,m.jsx)(`span`,{children:`停止中…`}),(0,m.jsx)(Rt,{workflow:t,names:Kn,stopping:n.some(e=>e.key===`stop:${t.id}`),onStop:()=>or(t.id)},e)]},e):null}let t=Jn.get(e.slice(2));return t?(0,m.jsxs)(`div`,{"data-transcript-key":e,children:[n.some(e=>e.key===`stop:${t.id}`)&&(0,m.jsx)(`span`,{children:`停止中…`}),(0,m.jsx)(Lr,{run:t,stopping:n.some(e=>e.key===`stop:${t.id}`),name:Kn.get(t.agentId)??``,expanded:!!Ge[t.id],onToggle:()=>Xn(t.id),onStop:()=>ar(t.id)},e)]},e):null})]}),(0,m.jsxs)(`div`,{className:`stream-tail`,"data-stream-tail":`1`,"data-follow-tail":re.followTail?`true`:`false`,children:[(0,m.jsx)(`span`,{className:`stream-tail-status`,"data-run-status":M?.status??`idle`,"data-stream-state":l?M?.status===`running`?`streaming`:di.length?`waiting`:M?.status===`failed`?`failed`:`ready`:`waiting`,children:l?M?.status===`running`?`Streaming · 正在生成`:di.length?`Waiting · 消息排队中`:M?.status===`failed`?`运行失败`:M?.status===`stopped`?`已停止`:`就绪`:`连接中 / 等待连接`}),!re.followTail&&(0,m.jsx)(`button`,{type:`button`,className:`stream-tail-jump`,"data-jump-to-bottom":`1`,onClick:()=>{let e=ee.current;e&&(xi(e),e.scrollTop=e.scrollHeight,Mt(e,!1,!0))},children:`↓ 跳到底部`}),(0,m.jsx)(`span`,{className:`stream-tail-mode`,"data-follow-tail":re.followTail?`true`:`false`,children:re.followTail?`贴底`:`自由浏览`})]}),(0,m.jsx)(Rr,{items:di,pi:Ue.get(M?.id??``),running:M?.status===`running`,onCancel:fi,onSteer:mi,onSendQueued:hi}),(Hn.length>0||[...Se.values()].some(e=>e.placement===`aboveEditor`))&&(0,m.jsxs)(`div`,{className:`extui`,"data-extui-count":Hn.length,children:[Hn.map(e=>(0,m.jsx)(Vr,{request:e.request,pending:n.some(t=>t.key===`answer:${e.conversationId}:${e.request.requestId}`),onAnswer:t=>Tn(e.conversationId,e.request.requestId,t)},e.request.requestId)),sr(`aboveEditor`)]}),(0,m.jsxs)(`form`,{onSubmit:ui,onPaste:ai,onDragOver:oi,onDragLeave:si,onDrop:ci,className:gt?`composer-dropzone-active`:void 0,children:[(0,m.jsx)(`textarea`,{ref:E,value:Pt,onCompositionStart:()=>{yt.current=!0},onCompositionEnd:()=>{yt.current=!1},onChange:e=>{Vt(e.target.value);let t=e.target.value,n=e.target.selectionStart??t.length,r=t.slice(0,n),i=r.lastIndexOf(`/`),a=r.slice(i+1),o=i>0?r[i-1]:``;i>=0&&(i===0||/\s/.test(o))&&/^[\w:-]*$/u.test(a)?Gt({visible:!0,query:a,rect:e.currentTarget.getBoundingClientRect()}):Gt({visible:!1,query:``,rect:null})},placeholder:M?An?`描述你要完成的任务(支持粘贴/拖放图片)…`:`描述你要完成的任务…`:`先创建会话`,disabled:!M,onKeyDown:e=>{if(!(yt.current||nr(e))){if(Wt.visible){if(e.key===`Escape`){e.preventDefault(),Gt({visible:!1,query:``,rect:null});return}if([`ArrowDown`,`ArrowUp`,`Enter`].includes(e.key)){e.preventDefault();return}}e.key===`Enter`&&!e.shiftKey&&(e.preventDefault(),e.currentTarget.form?.requestSubmit())}}}),Wt.visible&&Wt.rect&&(0,m.jsx)(at,{query:Wt.query,anchorRect:Wt.rect,workspaceId:M?.workspaceId,command:bn,onSelect:e=>{if(e.id.startsWith(`skill:`)){let t=e.id.slice(6),n=document.querySelector(`[data-selected-workspace]`)?.dataset.selectedWorkspace;n&&fetch(`/api/memory/tool`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({workspaceId:n,conversationId:M?.id,kind:`skill.read`,skillId:t})}).then(async n=>{if(!n.ok){F(`读取技能失败`,`error`);return}let r=await n.json();typeof r.content==`string`&&Ht({kind:`skill`,id:r.id??t,name:r.name??e.label,content:r.content})}).catch(()=>F(`读取技能失败`,`error`))}let t=E.current?.selectionStart??Pt.length,n=Pt.slice(0,t),r=n.lastIndexOf(`/`),i=rt(e,Wt.query),a=`${n.slice(0,r)}/${i} ${Pt.slice(t)}`;Vt(a),Gt({visible:!1,query:``,rect:null})},onClose:()=>Gt({visible:!1,query:``,rect:null})}),ut.filter(e=>e.conversationId===M?.id).map(e=>(0,m.jsxs)(`div`,{"data-failed-send":e.id,children:[(0,m.jsxs)(`span`,{children:[`发送失败:`,e.content]}),(0,m.jsx)(`button`,{type:`button`,disabled:!!(Pt||Ft.length),onClick:()=>{zt(e.conversationId,t=>({...t,text:e.content,images:e.images})),dt(t=>t.filter(t=>t.id!==e.id))},children:`恢复草稿`}),(0,m.jsx)(`button`,{type:`button`,onClick:()=>dt(t=>t.filter(t=>t.id!==e.id)),children:`移除`})]},e.id)),ft.filter(e=>e.conversationId===M?.id).map(e=>(0,m.jsxs)(`div`,{"data-upload-preview":e.id,children:[(0,m.jsx)(`img`,{src:e.url,alt:e.name,width:48,height:48}),(0,m.jsx)(`span`,{children:e.failed?`上传失败`:`上传中…`}),(0,m.jsx)(`button`,{type:`button`,onClick:()=>{lt.current.add(e.id),URL.revokeObjectURL(e.url),pt(t=>t.filter(t=>t.id!==e.id))},children:`移除`})]},e.id)),(Ft.length>0||Lt>0)&&(0,m.jsxs)(`div`,{className:`composer-attachments`,"data-composer-attachments":`1`,children:[Ft.map((e,t)=>(0,m.jsxs)(`div`,{className:`composer-attachment-item`,"data-composer-attachment-id":e.id,children:[(0,m.jsx)(`img`,{src:`/api/upload/${encodeURIComponent(M?.id??``)}/${encodeURIComponent(e.id)}`,alt:e.name||`已附图片`,className:`composer-attachment-thumb`,onClick:()=>ht({url:`/api/upload/${encodeURIComponent(M?.id??``)}/${encodeURIComponent(e.id)}`,name:e.name,bytes:e.bytes}),title:`点击查看大图`}),(0,m.jsx)(`span`,{className:`composer-attachment-name`,title:e.name,children:e.name||`图片`}),(0,m.jsx)(`button`,{type:`button`,className:`composer-attachment-remove`,"data-composer-attachment-remove":e.id,onClick:()=>Kt(e=>e.filter((e,n)=>n!==t)),"aria-label":`移除 ${e.name||`图片`}`,title:`移除图片`,children:`✕`})]},e.id)),Lt>0&&(0,m.jsx)(`div`,{className:`composer-attachment-item composer-attachment-uploading`,children:(0,m.jsxs)(`span`,{children:[`图片上传中 (`,Lt,`)…`]})}),It.map(e=>(0,m.jsxs)(`div`,{className:`composer-attachment-item composer-attachment-skill`,"data-composer-skill-id":e.id,children:[(0,m.jsx)(`span`,{className:`composer-attachment-glyph`,"aria-hidden":`true`,children:`▤`}),(0,m.jsx)(`span`,{className:`composer-attachment-name`,title:e.name,children:e.name}),(0,m.jsx)(`button`,{type:`button`,className:`composer-attachment-remove`,"data-composer-skill-remove":e.id,onClick:()=>Ut(e.kind,e.id),"aria-label":`移除 ${e.name}`,title:`移除技能`,children:`✕`})]},`${e.kind}:${e.id}`))]}),(0,m.jsxs)(`div`,{className:`composer`,children:[(0,m.jsxs)(`div`,{className:`session-picker`,"data-session-picker":`1`,children:[(0,m.jsx)(Ur,{kind:`agent`,label:In?.name??`跟随 Pi 默认`,value:M?.agentId??L,options:Rn,disabled:!Ee||Ee.source===`unavailable`||Cn,note:Ar(Ee),onChoose:Vn}),(0,m.jsx)(Ur,{kind:`model`,label:En?.label??(we?`Pi 默认`:`读取中…`),value:En?`${En.provider}/${En.modelId}`:void 0,detail:Bn?`来自 ${In?.name}`:En&&we?.source===`pi`&&!M?.model?`Pi 默认模型`:En?`${En.provider}/${En.modelId}`:void 0,options:Mn,disabled:!On||Cn,note:kr(`模型`,we),onChoose:Pn}),(0,m.jsx)(Ur,{kind:`thinking`,label:Dn??(we?`Pi 默认`:`读取中…`),value:Dn,options:Nn,disabled:!On||Cn,note:kr(`思考等级`,we),onChoose:Fn})]}),(0,m.jsxs)(`div`,{className:`composer-hints`,children:[(0,m.jsxs)(`button`,{type:`button`,className:`composer-attach-btn`,"data-composer-attach":`1`,onClick:()=>vt.current?.click(),disabled:!M||!An||Lt>0,title:M?An?`添加图片(支持剪贴板粘贴与拖拽)`:`当前模型不支持图片输入`:`请先创建或选择会话`,"aria-label":`添加图片`,children:[(0,m.jsx)(`span`,{"aria-hidden":`true`,children:`+`}),` 图片`]}),(0,m.jsx)(`input`,{ref:vt,type:`file`,accept:`image/png,image/jpeg,image/gif,image/webp`,multiple:!0,style:{display:`none`},onChange:e=>{e.target.files?.length&&(ii(Array.from(e.target.files)),e.target.value=``)}}),(0,m.jsx)(`span`,{children:`Enter 发送 · Shift + Enter 换行`})]}),(0,m.jsxs)(`div`,{className:`composer-actions`,children:[M?.status===`running`&&(0,m.jsx)(`button`,{type:`button`,className:`stop`,"data-stop-run":M.id,disabled:n.some(e=>e.key===`stop:${M.id}`),onClick:()=>I({type:`run.stop`,conversationId:M.id},`stop:${M.id}`,e=>e),children:n.some(e=>e.key===`stop:${M.id}`)?`停止中…`:`停止`}),(0,m.jsx)(`button`,{className:`send ${M?.status===`running`?`send-queued`:``}`,"data-send-mode":M?.status===`running`?`queued`:`direct`,disabled:!Pt.trim()&&Ft.length===0||!M||Lt>0||!!_i||Cn,title:M?_i?`正在撤回:Pi 的历史还没截断完成,撤回结束后即可发送`:!Pt.trim()&&Ft.length===0?`请输入消息或添加图片后发送`:Lt>0?`图片上传中…`:M.status===`running`?`这一轮结束后发送(发送后可在上面的排队区取消或插队)`:void 0:`请先创建或选择会话`,children:_i?`撤回中…`:M?.status===`running`?`排队发送 ↑`:`发送 ↑`})]})]})]}),sr(`belowEditor`)]})]}),(0,m.jsx)(Ei,{side:`right`,width:Ne?30:j.rail,min:j.railMin,max:j.railMax,label:`调整右侧栏宽度`,onWidth:He}),(0,m.jsxs)(`nav`,{className:`mobile-nav`,"aria-label":`移动端主导航`,children:[(0,m.jsxs)(`button`,{type:`button`,"data-mobile-nav":`workspaces`,className:Fe===`workspaces`?`active`:``,onClick:()=>Ie(`workspaces`),children:[`☰`,(0,m.jsx)(`span`,{children:`工作区`})]}),(0,m.jsxs)(`button`,{type:`button`,"data-mobile-nav":`chat`,className:Fe===`chat`?`active`:``,onClick:()=>Ie(`chat`),children:[`◉`,(0,m.jsx)(`span`,{children:`对话`})]}),(0,m.jsxs)(`button`,{type:`button`,"data-mobile-nav":`rail`,className:Fe===`rail`?`active`:``,onClick:()=>{Ie(`rail`),Pe(!1)},children:[`▣`,(0,m.jsx)(`span`,{children:`工具`})]})]}),` `,et&&(0,m.jsx)(_r,{workspace:et.workspace,x:et.x,y:et.y,notify:F,onClose:()=>tt(void 0)}),nt&&(0,m.jsx)(vr,{conversation:nt.conversation,x:nt.x,y:nt.y,notify:F,onRename:()=>void Tr(nt.conversation.id),onClose:()=>it(void 0)}),y&&(0,m.jsx)(Er,{onClose:()=>b(!1),onCreate:cr}),qr&&(0,m.jsx)(Ln,{workspaceId:_,theme:Br,onChooseTheme:e=>Kr(e),onClose:()=>Xr(!1),agentCatalog:Ee,subagentCatalog:A,modelFilterRules:o.modelFilterRules??[],pendingSettings:n.some(e=>e.key===`model-rules`||e.key===`agent-directories`),onSetModelRules:e=>I({type:`model.rules.set`,rules:e},`model-rules`,t=>({...t,modelFilterRules:e})),customAgentDirs:o.customAgentDirs??[],onSetCustomAgentDirs:e=>I({type:`agents.setCustomDirs`,dirs:e},`agent-directories`,t=>({...t,customAgentDirs:e})),onRescanAgents:()=>bn({type:`agents.rescan`}),userNickname:fe,onSetUserNickname:me}),Zr&&(0,m.jsx)(Bt,{onClose:()=>Qr(!1)}),Fr&&(0,m.jsx)(Wr,{workspace:Fr,catalog:jn(we,Fr),agents:Ee,onClose:()=>Nr(void 0),onRename:()=>{let e=Fr.id;Nr(void 0),br(`workspace`,e)},onSave:(e,t,n)=>$r(Fr.id,e,t,n)}),mt&&(0,m.jsx)(li,{image:mt,onClose:()=>ht(void 0)}),(0,m.jsx)(ni,{todos:o.todosByConversation?.[M?.id??``]??[],tasks:o.backgroundTasks??[],collapsed:Ne,onToggle:()=>Pe(e=>!e),workspaceId:_,workspaceLabel:o.workspaces.find(e=>e.id===_)?.path,connected:l,files:P,onOpenFile:e=>void sn(e),activeTab:Qt,onSelectTab:$t,terminalAvailable:en,sendCommand:bn,registerTerminalSink:le})]})}On(Dn()),(0,f.createRoot)(document.getElementById(`root`)).render((0,m.jsx)(p.StrictMode,{children:(0,m.jsx)(Di,{})}));
@@ -8,7 +8,7 @@
8
8
  <link rel="alternate icon" href="/favicon.ico" />
9
9
  <link rel="apple-touch-icon" href="/favicon.svg" />
10
10
  <title>Super Pi</title>
11
- <script type="module" crossorigin src="/assets/index-CHRkbLTs.js"></script>
11
+ <script type="module" crossorigin src="/assets/index-DiG980gH.js"></script>
12
12
  <link rel="stylesheet" crossorigin href="/assets/index-CKdlMXJt.css">
13
13
  </head>
14
14
  <body>
@@ -18,6 +18,41 @@ const MIME_TYPES = {
18
18
  ".ico": "image/x-icon",
19
19
  ".png": "image/png",
20
20
  };
21
+ /**
22
+ * The only browser API a mounted tool needs. It is served beneath the tool's own
23
+ * mount, so every request stays relative to that mount and the server can derive
24
+ * the tool identity without exposing a registry id, a workspace, or a credential.
25
+ */
26
+ function toolBrowserSdk() {
27
+ return String.raw `(() => {
28
+ const request = async (path, init) => {
29
+ const response = await fetch(path, init);
30
+ let body;
31
+ try { body = await response.json(); } catch { body = { error: "Super Pi returned an unreadable response" }; }
32
+ if (!response.ok) throw new Error(body && body.error ? body.error : "Tool request failed (HTTP " + response.status + ")");
33
+ return body;
34
+ };
35
+ const api = {
36
+ run: (params = {}) => request("./api/run", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ params }) }),
37
+ get: (runId) => request("./api/runs/" + encodeURIComponent(runId), { method: "GET" }),
38
+ cancel: (runId) => request("./api/runs/" + encodeURIComponent(runId), { method: "DELETE" }),
39
+ wait: async (runId, { intervalMs = 250, signal } = {}) => {
40
+ for (;;) {
41
+ if (signal && signal.aborted) throw new DOMException("Aborted", "AbortError");
42
+ const run = await api.get(runId);
43
+ if (run.status !== "running") return run;
44
+ await new Promise((resolve, reject) => {
45
+ const timer = setTimeout(resolve, Math.max(50, intervalMs));
46
+ if (signal) signal.addEventListener("abort", () => { clearTimeout(timer); reject(new DOMException("Aborted", "AbortError")); }, { once: true });
47
+ });
48
+ }
49
+ },
50
+ runAndWait: async (params = {}, options) => { const run = await api.run(params); return api.wait(run.id, options); },
51
+ };
52
+ Object.freeze(api);
53
+ Object.defineProperty(window, "SuperPiTool", { value: api, writable: false, configurable: false });
54
+ })();`;
55
+ }
21
56
  /**
22
57
  * Checks if the remote address is a local loopback interface.
23
58
  */
@@ -138,17 +173,15 @@ export function createHttpServer(options) {
138
173
  return createServer(async (request, response) => {
139
174
  // Internal tools use a narrow per-run capability, never the global management token.
140
175
  const requestPath = request.url?.split("?")[0] ?? "";
141
- const opaqueToolApi = requestPath.startsWith("/api/tools/") && request.headers.origin === "null";
142
176
  // Tool registration is checked after its JSON body is parsed: an agent proves its turn capability
143
177
  // there, while an interactive browser uses the ordinary server access token.
144
- if (requestPath !== "/api/memory/tool" && requestPath !== "/api/tools" && !opaqueToolApi && !isAuthorized(request, accessToken)) {
178
+ if (requestPath !== "/api/memory/tool" && requestPath !== "/api/tools" && !requestPath.startsWith("/tools/") && !isAuthorized(request, accessToken)) {
145
179
  response.writeHead(401, { "www-authenticate": "Bearer" }).end("Authentication required");
146
180
  return;
147
181
  }
148
182
  const url = new URL(request.url ?? "/", `http://${request.headers.host ?? "localhost"}`);
149
183
  const remote = request.socket.remoteAddress ?? "";
150
184
  const json = (status, body) => { response.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" }); response.end(JSON.stringify(body)); };
151
- const toolJson = (status, body) => { response.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store", "access-control-allow-origin": "null", "vary": "Origin" }); response.end(JSON.stringify(body)); };
152
185
  if (url.pathname === "/api/tools") {
153
186
  try {
154
187
  if (request.method === "GET") {
@@ -211,89 +244,101 @@ export function createHttpServer(options) {
211
244
  response.writeHead(404).end("Tool not found");
212
245
  return;
213
246
  }
214
- try {
215
- const resolved = await toolRegistry.resolve(id, resource);
216
- if (!resolved) {
217
- response.writeHead(404).end("Tool not found");
247
+ // A mounted tool never learns its random registry id. Its API is instead addressed relative
248
+ // to its own mount (`./api/run`), and this route supplies that identity from the URL. Besides
249
+ // making a generated tool usable on its first render, this binds status and cancellation to the
250
+ // same tool that created a run: an opaque frame for tool A cannot inspect tool B's run ids.
251
+ const toolOrigin = request.headers.origin === "null";
252
+ const toolApi = (status, body) => {
253
+ response.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store", "access-control-allow-origin": "null", "vary": "Origin" });
254
+ response.end(JSON.stringify(body));
255
+ };
256
+ if (resource === "api/run") {
257
+ if (!toolOrigin) {
258
+ toolApi(403, { error: "tool runtime API is only available to the sandboxed tool frame" });
218
259
  return;
219
260
  }
220
- response.writeHead(200, { "content-type": MIME_TYPES[extname(resolved.file)] ?? "application/octet-stream", "cache-control": "no-store", "content-security-policy": "default-src 'self'; connect-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'" });
221
- createReadStream(resolved.file).pipe(response);
222
- return;
223
- }
224
- catch {
225
- response.writeHead(404).end("Tool file not found");
226
- return;
227
- }
228
- }
229
- if (url.pathname.startsWith("/api/tools/") && url.pathname.endsWith("/run")) {
230
- const id = decodeURIComponent(url.pathname.slice("/api/tools/".length, -"/run".length));
231
- if (!opaqueToolApi) {
232
- toolJson(403, { error: "tool runtime API is only available to the sandboxed tool frame" });
233
- return;
234
- }
235
- if (request.method === "OPTIONS") {
236
- response.writeHead(204, { "access-control-allow-origin": "null", "access-control-allow-methods": "POST", "access-control-allow-headers": "content-type", "vary": "Origin" }).end();
237
- return;
238
- }
239
- if (request.method !== "POST") {
240
- toolJson(405, { error: "method" });
261
+ if (request.method === "OPTIONS") {
262
+ response.writeHead(204, { "access-control-allow-origin": "null", "access-control-allow-methods": "POST", "access-control-allow-headers": "content-type", "vary": "Origin" }).end();
263
+ return;
264
+ }
265
+ if (request.method !== "POST") {
266
+ toolApi(405, { error: "method" });
267
+ return;
268
+ }
269
+ let raw = "";
270
+ for await (const chunk of request) {
271
+ raw += chunk;
272
+ if (raw.length > 64_000) {
273
+ toolApi(413, { error: "request too large" });
274
+ return;
275
+ }
276
+ }
277
+ try {
278
+ const body = JSON.parse(raw);
279
+ const resolved = await toolRegistry.resolve(id);
280
+ if (!resolved?.tool.script) {
281
+ toolApi(404, { error: "python script is not registered" });
282
+ return;
283
+ }
284
+ const run = toolRunner.start({ toolId: id, script: resolve(resolved.root, resolved.tool.script.path), cwd: resolved.root, definition: resolved.tool.script, params: body.params ?? {} });
285
+ toolApi(202, run);
286
+ }
287
+ catch (error) {
288
+ toolApi(400, { error: error instanceof Error ? error.message : "tool run failed" });
289
+ }
241
290
  return;
242
291
  }
243
- let raw = "";
244
- for await (const chunk of request) {
245
- raw += chunk;
246
- if (raw.length > 64_000) {
247
- json(413, { error: "request too large" });
292
+ const runMatch = /^api\/runs\/([^/]+)$/.exec(resource);
293
+ if (runMatch) {
294
+ if (!toolOrigin) {
295
+ toolApi(403, { error: "tool runtime API is only available to the sandboxed tool frame" });
248
296
  return;
249
297
  }
250
- }
251
- try {
252
- const body = JSON.parse(raw);
253
- const resolved = await toolRegistry.resolve(id);
254
- if (!resolved?.tool.script) {
255
- toolJson(404, { error: "python script is not registered" });
298
+ if (request.method === "OPTIONS") {
299
+ response.writeHead(204, { "access-control-allow-origin": "null", "access-control-allow-methods": "GET, DELETE", "vary": "Origin" }).end();
256
300
  return;
257
301
  }
258
- const run = toolRunner.start({ toolId: id, script: resolve(resolved.root, resolved.tool.script.path), cwd: resolved.root, definition: resolved.tool.script, params: body.params ?? {} });
259
- toolJson(202, run);
260
- }
261
- catch (error) {
262
- toolJson(400, { error: error instanceof Error ? error.message : "tool run failed" });
263
- }
264
- return;
265
- }
266
- if (url.pathname.startsWith("/api/tools/runs/")) {
267
- const parts = url.pathname.slice("/api/tools/runs/".length).split("/");
268
- const runId = decodeURIComponent(parts[0] ?? "");
269
- if (!opaqueToolApi) {
270
- toolJson(403, { error: "tool runtime API is only available to the sandboxed tool frame" });
302
+ const runId = decodeURIComponent(runMatch[1]);
303
+ if (request.method === "GET") {
304
+ const run = toolRunner.get(runId, id);
305
+ if (!run) {
306
+ toolApi(404, { error: "run not found" });
307
+ return;
308
+ }
309
+ toolApi(200, run);
310
+ return;
311
+ }
312
+ if (request.method === "DELETE") {
313
+ const run = toolRunner.cancel(runId, id);
314
+ if (!run) {
315
+ toolApi(404, { error: "run not found" });
316
+ return;
317
+ }
318
+ toolApi(200, run);
319
+ return;
320
+ }
321
+ toolApi(405, { error: "method" });
271
322
  return;
272
323
  }
273
- if (request.method === "OPTIONS") {
274
- response.writeHead(204, { "access-control-allow-origin": "null", "access-control-allow-methods": "GET, DELETE", "vary": "Origin" }).end();
324
+ if (resource === "super-pi-tool.js") {
325
+ response.writeHead(200, { "content-type": "text/javascript; charset=utf-8", "cache-control": "no-store" }).end(toolBrowserSdk());
275
326
  return;
276
327
  }
277
- if (request.method === "GET") {
278
- const run = [...(await toolRegistry.list())].map((tool) => toolRunner.get(runId, tool.id)).find(Boolean);
279
- if (!run) {
280
- toolJson(404, { error: "run not found" });
328
+ try {
329
+ const resolved = await toolRegistry.resolve(id, resource);
330
+ if (!resolved) {
331
+ response.writeHead(404).end("Tool not found");
281
332
  return;
282
333
  }
283
- toolJson(200, run);
334
+ response.writeHead(200, { "content-type": MIME_TYPES[extname(resolved.file)] ?? "application/octet-stream", "cache-control": "no-store", "content-security-policy": "default-src 'self'; connect-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'" });
335
+ createReadStream(resolved.file).pipe(response);
284
336
  return;
285
337
  }
286
- if (request.method === "DELETE") {
287
- const run = [...(await toolRegistry.list())].map((tool) => toolRunner.cancel(runId, tool.id)).find(Boolean);
288
- if (!run) {
289
- toolJson(404, { error: "run not found" });
290
- return;
291
- }
292
- toolJson(200, run);
338
+ catch {
339
+ response.writeHead(404).end("Tool file not found");
293
340
  return;
294
341
  }
295
- toolJson(405, { error: "method" });
296
- return;
297
342
  }
298
343
  /**
299
344
  * THE TERMINAL IS NOT AN API ENDPOINT, and the routing above deliberately cannot reach one.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evandu/super-pi",
3
- "version": "0.1.76",
3
+ "version": "0.1.77",
4
4
  "description": "Web control plane and companion for Pi",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -85,6 +85,7 @@
85
85
  "scripts/model-filter.test.mjs",
86
86
  "scripts/active-memory.test.mjs",
87
87
  "scripts/tool-runner.test.mjs",
88
+ "scripts/tool-runtime-api.test.mjs",
88
89
  "scripts/extension-load.test.mjs"
89
90
  ],
90
91
  "check:browser": [