pi-web-ui 0.85.0 → 0.86.2

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.
@@ -11,7 +11,8 @@ import { readdirSync, readFileSync, realpathSync, existsSync } from "node:fs";
11
11
  import { delimiter, dirname, join } from "node:path";
12
12
  import { pick } from "./i18n.js";
13
13
  const PI_CORE_PACKAGE = "@earendil-works/pi-coding-agent";
14
- const REGISTRY = "https://registry.npmjs.org";
14
+ /** npm 官方源;用户在 <agentDir>/npm/.npmrc 里配了镜像/私有源时会被覆盖(issue #151)。 */
15
+ export const NPM_DEFAULT_REGISTRY = "https://registry.npmjs.org";
15
16
  const FETCH_TIMEOUT_MS = 8_000;
16
17
  /** Parallel registry lookups per batch. */
17
18
  const CONCURRENCY = 5;
@@ -256,10 +257,99 @@ export function collectTargets(agentDir, webuiVersion, probePiCore = defaultProb
256
257
  }
257
258
  /** Default fetcher (real network). Tests inject a fake. */
258
259
  export const defaultFetcher = (url, init) => fetch(url, init);
259
- /** Look up one package's latest version + publish time in the npm registry. */
260
- export async function fetchLatest(fetcher, name) {
261
- const res = await fetcher(`${REGISTRY}/${encodeURIComponent(name)}`, {
260
+ /**
261
+ * 解析 .npmrc 文本里的全局 registry(`registry=<url>`,后出现的覆盖先出现的)。
262
+ * 找不到返回 null(调用方回落 NPM_DEFAULT_REGISTRY)。引号与行尾 `/` 会被清理。
263
+ */
264
+ export function parseNpmrcRegistry(text) {
265
+ let registry = null;
266
+ for (const raw of text.split(/\r?\n/)) {
267
+ const line = raw.trim();
268
+ if (!line || line.startsWith("#") || line.startsWith(";"))
269
+ continue;
270
+ const m = line.match(/^registry\s*=\s*(.+?)\s*$/i);
271
+ if (!m)
272
+ continue;
273
+ let url = m[1]
274
+ .trim()
275
+ .replace(/^["']|["']$/g, "")
276
+ .trim();
277
+ if (!url)
278
+ continue;
279
+ url = url.replace(/\/+$/, "");
280
+ if (/^https?:\/\//i.test(url))
281
+ registry = url;
282
+ }
283
+ return registry;
284
+ }
285
+ /**
286
+ * 从 .npmrc 文本里找出与 registry 同源的认证头(`//host/path:_authToken=` 优先,
287
+ * 其次 `//host/path:_auth=`)。私有源检查更新时没有它会直接 401(issue #151)。
288
+ */
289
+ export function parseNpmrcAuth(text, registry) {
290
+ let host;
291
+ try {
292
+ host = new URL(registry).host.toLowerCase();
293
+ }
294
+ catch {
295
+ return null;
296
+ }
297
+ let token = null;
298
+ let basic = null;
299
+ for (const raw of text.split(/\r?\n/)) {
300
+ const line = raw.trim();
301
+ if (!line || line.startsWith("#") || line.startsWith(";") || !line.startsWith("//"))
302
+ continue;
303
+ const eq = line.indexOf("=");
304
+ if (eq < 0)
305
+ continue;
306
+ const key = line.slice(0, eq).trim();
307
+ const value = line
308
+ .slice(eq + 1)
309
+ .trim()
310
+ .replace(/^["']|["']$/g, "")
311
+ .trim();
312
+ if (!value)
313
+ continue;
314
+ // key 形如 //registry.example.com/:_authToken —— 取 // 与 : 之间的 host 比对
315
+ const keyHost = key.slice(2).split("/")[0].split(":")[0].toLowerCase();
316
+ if (keyHost !== host)
317
+ continue;
318
+ if (/.:_authToken$/i.test(key))
319
+ token = value;
320
+ else if (/.:_auth$/i.test(key))
321
+ basic = value;
322
+ }
323
+ if (token)
324
+ return `Bearer ${token}`;
325
+ if (basic)
326
+ return `Basic ${basic}`;
327
+ return null;
328
+ }
329
+ /**
330
+ * 读取 <agentDir>/npm/.npmrc(`pi update --extensions` 经 npm 自动遵守的同一份),
331
+ * 解析出检查更新该用的 registry + 认证头。文件不存在/不可读/无 registry 行时
332
+ * 回落官方源(issue #151:镜像/私有源用户不再被卡在官方源上)。
333
+ */
334
+ export function resolveNpmRegistry(agentDir) {
335
+ try {
336
+ const text = readFileSync(join(agentDir, "npm", ".npmrc"), "utf8");
337
+ const registry = parseNpmrcRegistry(text) ?? NPM_DEFAULT_REGISTRY;
338
+ return { registry, authHeader: parseNpmrcAuth(text, registry) };
339
+ }
340
+ catch {
341
+ return { registry: NPM_DEFAULT_REGISTRY, authHeader: null };
342
+ }
343
+ }
344
+ /**
345
+ * Look up one package's latest version + publish time in the npm registry.
346
+ * registry/authHeader 默认官方源;镜像/私有源用户经 resolveNpmRegistry 传入
347
+ * <agentDir>/npm/.npmrc 的配置(issue #151)。
348
+ */
349
+ export async function fetchLatest(fetcher, name, registry = NPM_DEFAULT_REGISTRY, authHeader = null) {
350
+ const res = await fetcher(`${registry}/${encodeURIComponent(name)}`, {
262
351
  signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
352
+ ...(authHeader ? { headers: { authorization: authHeader } } : {}),
263
353
  });
264
354
  if (!res.ok)
265
355
  throw new Error(`HTTP ${res.status}`);
@@ -277,8 +367,12 @@ export async function fetchLatest(fetcher, name) {
277
367
  */
278
368
  export async function checkAll(targets, fetcher = defaultFetcher,
279
369
  /** 单项 registry 查询失败时的 error 文案语言(默认英文)。 */
280
- lang) {
370
+ lang,
371
+ /** 镜像/私有源配置(默认官方源;调用方经 resolveNpmRegistry 传入 .npmrc,issue #151)。 */
372
+ registryConfig) {
281
373
  const l = lang?.() ?? "en";
374
+ const registry = registryConfig?.registry ?? NPM_DEFAULT_REGISTRY;
375
+ const authHeader = registryConfig?.authHeader ?? null;
282
376
  const results = Array.from({ length: targets.length });
283
377
  let cursor = 0;
284
378
  async function worker() {
@@ -286,7 +380,7 @@ lang) {
286
380
  const i = cursor++;
287
381
  const t = targets[i];
288
382
  try {
289
- const { latest, latestPublishedAt } = await fetchLatest(fetcher, t.name);
383
+ const { latest, latestPublishedAt } = await fetchLatest(fetcher, t.name, registry, authHeader);
290
384
  results[i] = {
291
385
  name: t.name,
292
386
  kind: t.kind,
@@ -248,7 +248,7 @@ export function hasPendingWaitSubscription(options) {
248
248
  export function shouldRetainActive(input) {
249
249
  if (input.reviewing || input.wizardRunning)
250
250
  return true;
251
- if (input.streaming)
251
+ if (input.streaming || input.compacting)
252
252
  return true;
253
253
  if (input.openTerminals > 0)
254
254
  return true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-web-ui",
3
- "version": "0.85.0",
3
+ "version": "0.86.2",
4
4
  "description": "Web chat interface for the pi coding agent, powered by the pi SDK (@earendil-works/pi-coding-agent) — one-command run, Docker/systemd/launchd deployable",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -0,0 +1,209 @@
1
+ /* theme-name: 暗青 */
2
+ /* theme-name-en: Dark Teal */
3
+ :root {
4
+ color-scheme: dark;
5
+ --bg: #0a0a0a;
6
+ --bg-elev: #17171c;
7
+ --bg-elev2: #1c1c22;
8
+ --border: #26262e;
9
+ --border-soft: #1f1f26;
10
+ --text: #dadada;
11
+ --text-dim: #a8a8b3;
12
+ --text-faint: #6e6e7a;
13
+ --accent: #31ac9d;
14
+ --accent-soft: rgb(58 140 138 / 15%);
15
+ --green: #2dd4bf; /* 成功色:刻意比 accent 的青更亮一档,非笔误 */
16
+ --green-soft: rgba(74, 222, 128, 0.12);
17
+ --amber: #eab380;
18
+ --term-bg: #0b0b0e;
19
+ --term-fg: #ededf0;
20
+ --term-cursor: #2dd4bf;
21
+ --term-cursor-accent: #0b0b0e;
22
+ --term-selection: rgba(45, 212, 191, 0.28);
23
+ --term-black: #1c1c22;
24
+ --term-green: #4ade80;
25
+ --term-cyan: #2dd4bf;
26
+ --term-white: #ededf0;
27
+ --term-bright-black: #6e6e7a;
28
+ --term-bright-red: #fca5a5;
29
+ --term-bright-green: #86efac;
30
+ --term-bright-yellow: #fde68a;
31
+ --term-bright-blue: #93c5fd;
32
+ --term-bright-magenta: #d8b4fe;
33
+ --term-bright-cyan: #5eead4;
34
+ --tooltip-bg: #22222a;
35
+ --code-bg: #0b0b0e;
36
+ --code-text: #d8d8e0;
37
+ --err-text: #fca5a5;
38
+ --info-blue: #38bdf8;
39
+ --link: #2dd4bf;
40
+ --link-hover: #5eead4;
41
+ --link-soft: #99f6e4;
42
+ --md-strong: #ffffff;
43
+ --skill-blue: #38bdf8;
44
+ --plugin-purple: #c084fc;
45
+ --auth-green: #86efac;
46
+ --scroll-thumb: #2b2b34;
47
+ --scroll-thumb-hover: #3a3a46;
48
+ --notice-err-bg: #3a1f22;
49
+ --notice-warn-bg: #362a16;
50
+ --notice-info-bg: #10302f;
51
+ --notice-err-border: #f87171;
52
+ --notice-warn-border: #fbbf24;
53
+ --notice-info-border: #2dd4bf;
54
+ /* 发送/停止按钮内置写死 color:#fff,白字压在亮青上只有 1.9:1 → 改用深青做底 */
55
+ --send-blue: #0f766e;
56
+ --send-blue-hover: #0b5a54;
57
+ --brand-grad-a: #28a696;
58
+ --brand-grad-b: #38e6f8;
59
+ --on-amber: #12121a;
60
+ --search-active-fg: #0f0f12;
61
+ --switch-knob: #f5f5f7;
62
+ --bg-elev3: rgba(255, 255, 255, 0.05);
63
+ --control-fg: #a8a8b3;
64
+ --control-bg: #1c1c22;
65
+ --control-border: #33333d;
66
+ }
67
+
68
+ /* ---- syntax highlighting (overrides static github-dark import) ---- */
69
+ .hljs {
70
+ color: #d8d8e0;
71
+ background: transparent;
72
+ }
73
+ .hljs-doctag,
74
+ .hljs-keyword,
75
+ .hljs-meta .hljs-keyword,
76
+ .hljs-template-tag,
77
+ .hljs-template-variable,
78
+ .hljs-type,
79
+ .hljs-variable.language_ {
80
+ color: #5eead4;
81
+ }
82
+ .hljs-title,
83
+ .hljs-title.class_,
84
+ .hljs-title.class_.inherited__,
85
+ .hljs-title.function_ {
86
+ color: #38bdf8;
87
+ }
88
+ .hljs-attr,
89
+ .hljs-attribute,
90
+ .hljs-literal,
91
+ .hljs-meta,
92
+ .hljs-number,
93
+ .hljs-operator,
94
+ .hljs-variable,
95
+ .hljs-selector-attr,
96
+ .hljs-selector-class,
97
+ .hljs-selector-id {
98
+ color: #fbbf24;
99
+ }
100
+ .hljs-regexp,
101
+ .hljs-string,
102
+ .hljs-meta .hljs-string {
103
+ color: #86efac;
104
+ }
105
+ .hljs-built_in,
106
+ .hljs-symbol {
107
+ color: #c084fc;
108
+ }
109
+ .hljs-comment,
110
+ .hljs-code,
111
+ .hljs-formula {
112
+ color: #8a8a96;
113
+ }
114
+ .hljs-name,
115
+ .hljs-quote,
116
+ .hljs-selector-tag,
117
+ .hljs-selector-pseudo {
118
+ color: #2dd4bf;
119
+ }
120
+ .hljs-subst {
121
+ color: #d8d8e0;
122
+ }
123
+ .hljs-section {
124
+ color: #38bdf8;
125
+ font-weight: 700;
126
+ }
127
+ .hljs-bullet {
128
+ color: #2dd4bf;
129
+ }
130
+ .hljs-emphasis {
131
+ color: #d8d8e0;
132
+ font-style: italic;
133
+ }
134
+ .hljs-strong {
135
+ color: #ffffff;
136
+ font-weight: 700;
137
+ }
138
+ .hljs-addition {
139
+ color: #86efac;
140
+ background: rgba(74, 222, 128, 0.14);
141
+ }
142
+ .hljs-deletion {
143
+ color: #fca5a5;
144
+ background: rgba(248, 113, 113, 0.14);
145
+ }
146
+
147
+ /* 亮底上的按钮/徽标:内置写死 color:#fff,压亮青只有 1.9:1 → 统一换近黑墨色(只改颜色) */
148
+ .btn.primary,
149
+ .dialog-submit,
150
+ .goalbar-btn,
151
+ .template-btn.send,
152
+ .brand-logo,
153
+ .goalbar-chip.reviewing,
154
+ .goalbar-chip.pass,
155
+ .goalbar-chip.fail,
156
+ .update-badge,
157
+ .bg-task-badge,
158
+ .qn-list-item.active .qn-list-idx,
159
+ .bg-task-stop:hover:not(:disabled),
160
+ .bg-task-stopall:hover:not(:disabled),
161
+ .toolcall-kill:hover,
162
+ .lp-del-opt.danger:hover,
163
+ .lp-row .lp-del.confirm,
164
+ .lp-row .lp-del.confirm:hover,
165
+ .file-attach.inline:hover,
166
+ .file-attach.download:hover,
167
+ .file-attach.copy.copied,
168
+ .cmd-run:hover,
169
+ .fp-attach.inline:hover {
170
+ color: #0f0f12;
171
+ }
172
+
173
+ /* 内置这几处写死紫描边 rgba(139,92,246,.4) → 换成符合主题的青(只改 border-color) */
174
+ .msg-user .msg-body,
175
+ .session-item.active,
176
+ .project-item.active,
177
+ .term-tab.active,
178
+ .set-save-btn {
179
+ border-color: rgb(93 199 176 / 50%);
180
+ }
181
+ .chip.newchat {
182
+ border-color: var(--accent);
183
+ }
184
+
185
+ /* 模板卡悬停紫描边+阴影 → 换成主题描边青 */
186
+ .empty-template:hover {
187
+ border-color: #5dc7b030;
188
+ box-shadow: 0 8px 22px #5dc7b040;
189
+ }
190
+ .empty-template.add:hover {
191
+ border-color: #5dc7b099;
192
+ }
193
+
194
+ /* 工具视图(终端侧栏 / SCM)内置写死 var(--bg-elev) → 并入面板层 #121215(= 62% #17171c 叠 #0a0a0a)*/
195
+ .term-side,
196
+ .scm-view,
197
+ .scm-diff-header {
198
+ background: #121215;
199
+ }
200
+
201
+ /* 让消息正文行内代码更显眼:内置 --bg-elev2 与面板只差 4 级 → 单独抬到 #303038 */
202
+ .msg-text code {
203
+ background: #303038;
204
+ }
205
+
206
+ /* 附件卡虚线框内置是紫 → 与待发图片 chip 同一支梅红(只改 border-color) */
207
+ .attachcard {
208
+ border-color: #ec489973;
209
+ }
@@ -0,0 +1,6 @@
1
+ import{a as i,j as n}from"./markdown-D3PKeHAZ.js";import{u as P,b as M,T as z,a as f,c as W,F as Y,d as H,e as Z,f as q,g as ee,h as ne,i as te,j as se,r as ae}from"./index-Dmwji4Cr.js";import{D as re,o as ie}from"./xterm-B96xOxS9.js";import"./react-w24rH0km.js";function le(t){let c=null;return{clean:t.replace(/\r?\n?\[pi-term-exit:(-?\d+)\]\r?\n?/g,(h,u)=>(c=Number(u),`\r
2
+ `)).replace(/\r?\n?\x1b\[90m\[(?:进程已退出,退出码 |Process exited with code )-?\d+\]\x1b\[0m\r?\n?/g,`\r
3
+ `),exitCode:c}}function ce({conversationId:t,terminalId:c,command:s,cwd:h,title:u,agentBash:_,active:p,running:j,exitCode:v,register:b}){const g=i.useRef(null),o=i.useRef(null),{locale:y}=P(),C=i.useRef(y);C.current=y;const k=s?JSON.stringify(s):"",x=i.useRef(j===!1);i.useEffect(()=>{const m=g.current;if(!m)return;const r=new re({theme:M(),fontFamily:'"SF Mono", "JetBrains Mono", ui-monospace, Menlo, Consolas, monospace',fontSize:13,cursorBlink:!0,scrollback:8e3}),w=new ie;r.loadAddon(w),r.open(m),o.current={term:r,fit:w},p&&r.focus();const F=()=>{r.options.theme=M()};window.addEventListener(z,F),r.attachCustomKeyEventHandler(d=>{if(d.type!=="keydown")return!0;const S=d.key?.toLowerCase();if((d.ctrlKey||d.metaKey)&&S==="v")return!1;if(d.ctrlKey&&!d.shiftKey&&!d.altKey&&S==="c"&&r.hasSelection()){const D=r.textarea;return D&&(D.value=r.getSelection(),D.select()),!1}return!0});const $=b(t,c,{write:d=>r.write(le(d).clean),dispose:()=>r.dispose()}),R=()=>{try{w.fit(),f({type:"terminal_resize",terminalId:c,conversationId:t,cols:r.cols,rows:r.rows})}catch{}},A=requestAnimationFrame(()=>{try{w.fit()}catch{}x.current||(s?f({type:"run_command",terminalId:c,conversationId:t,command:s,cols:r.cols,rows:r.rows}):f({type:"terminal_create",terminalId:c,title:u,locale:C.current,agentBash:_,conversationId:t,cwd:h,cols:r.cols,rows:r.rows}))}),I=r.onData(d=>{f({type:"terminal_input",terminalId:c,conversationId:t,data:d})});let T=null;return typeof ResizeObserver<"u"&&(T=new ResizeObserver(()=>{m.offsetWidth>0&&m.offsetHeight>0&&R()}),T.observe(m)),()=>{cancelAnimationFrame(A),I.dispose(),window.removeEventListener(z,F),T?.disconnect(),$(),r.dispose(),o.current=null}},[t,c,k,b]),i.useEffect(()=>{if(!p)return;const m=requestAnimationFrame(()=>{const r=o.current;if(r){try{r.fit.fit(),f({type:"terminal_resize",terminalId:c,conversationId:t,cols:r.term.cols,rows:r.term.rows})}catch{}r.term.focus()}});return()=>cancelAnimationFrame(m)},[p]);const{t:E}=P(),B=i.useRef(void 0);return i.useEffect(()=>{if(j===B.current||(B.current=j,j!==!1))return;const m=o.current;m&&m.term.write(`\r
4
+ \x1B[90m${E("exitBanner",{code:v??""})}\x1B[0m\r
5
+ `)},[j,c,v]),n.jsx("div",{ref:g,className:`term-xterm ${p?"":"hidden"}`})}const G={name:"",command:"",cwd:"${pwd}"};function fe({chat:t,terminal:c}){const s=W(),[h,u]=i.useState(null),[_,p]=i.useState(!1),[j,v]=i.useState(!1),[b,g]=i.useState(null),[o,y]=i.useState(G),[C,k]=i.useState(null),x=i.useRef(null),[E,B]=i.useState(!0),[m,r]=i.useState(null),[w,F]=i.useState("");i.useEffect(()=>{t.terminals.length===0?u(null):t.terminals.some(e=>e.id===h)||u(t.terminals[t.terminals.length-1].id)},[t.terminals,h]),i.useEffect(()=>{t.terminalActiveId&&(u(t.terminalActiveId),p(!1))},[t.terminalActiveId]),i.useEffect(()=>()=>{x.current&&clearTimeout(x.current)},[]);const $=t.terminals.filter(e=>!e.agentBash),R=t.terminals.filter(e=>e.agentBash),A=e=>{if(!t.ready)return;const a=ae(),l=t.activeConversationId||t.state?.conversationId||"";c.create({...e,id:a,conversationId:l,cols:e.cols??80,rows:e.rows??24,running:!0,exitCode:null}),u(a),p(!1)},I=()=>A({title:s("terminalTitle",{n:$.length+1}),cwd:t.state?.cwd??""}),T=e=>{const a=e.name||e.command,l=t.terminals.find(N=>N.title===a);if(l){c.restart(l.id),u(l.id),f({type:"run_command",terminalId:l.id,conversationId:l.conversationId,command:e,cols:80,rows:24});return}A({title:a,cwd:t.state?.cwd??"",command:e})},d=e=>{const a=t.terminals.find(l=>l.id===e);if(a&&f({type:"terminal_kill",terminalId:e,conversationId:a.conversationId}),c.close(e),h===e){const l=t.terminals.filter(N=>N.id!==e);u(l.length>0?l[l.length-1].id:null)}},S=e=>n.jsxs("div",{className:`term-tab ${e.id===h?"active":""}`,children:[n.jsxs("button",{type:"button",className:"term-tab-main",title:`${e.cwd}${e.command?`
6
+ > ${e.command.command}`:""}`,onClick:()=>{m||(u(e.id),p(!1))},children:[n.jsx("span",{className:`term-tab-dot ${e.running?"run":"exit"}`}),n.jsxs("span",{className:"term-tab-title",children:[m===e.id?n.jsx("input",{autoFocus:!0,className:"term-tab-rename-input",value:w,placeholder:e.title,onClick:a=>a.stopPropagation(),onChange:a=>F(a.target.value),onKeyDown:a=>{if(a.stopPropagation(),a.key==="Enter"&&!a.nativeEvent.isComposing){const l=w.trim();l&&f({type:"rename_terminal",terminalId:e.id,conversationId:e.conversationId,title:l}),r(null)}else a.key==="Escape"&&r(null)},onBlur:()=>r(null)}):e.title,!e.running&&n.jsx("span",{className:"term-tab-exit",children:s("exited",{code:e.exitCode===null?"":` ${e.exitCode}`})})]})]}),n.jsx("button",{type:"button",className:"term-tab-close term-tab-rename",title:s("renameTerminal"),onClick:a=>{a.stopPropagation(),F(e.title),r(e.id)},children:n.jsx(q,{})}),n.jsx("button",{type:"button",className:"term-tab-close",title:s("closeTerminal"),onClick:()=>d(e.id),children:n.jsx(se,{})})]},e.id),D=()=>{v(!0),g(null),y(G)},L=e=>{const a=t.commands[e];a&&(v(!1),g(e),y({name:a.name,command:a.command,cwd:a.cwd??""}))},O=()=>{v(!1),g(null)},K=()=>{const e=o.name.trim(),a=o.command.trim();if(!e||!a)return;const l=o.cwd.trim(),N={name:e,command:a,cwd:l||void 0},Q=j?[...t.commands,N]:b!==null?t.commands.map((U,V)=>V===b?N:U):t.commands;f({type:"save_commands",commands:Q}),O()},J=e=>{if(C===e){const a=t.commands.filter((l,N)=>N!==e);f({type:"save_commands",commands:a}),k(null),x.current&&clearTimeout(x.current)}else k(e),x.current&&clearTimeout(x.current),x.current=setTimeout(()=>k(null),2500)},X=j||b!==null;return n.jsxs("div",{className:"terminal-view",children:[n.jsxs("aside",{className:`term-side term-commands ${_?"open":""}`,children:[n.jsxs("div",{className:"panel-header",children:[n.jsx("span",{className:"panel-title",children:s("commands")}),n.jsxs("div",{className:"panel-header-actions",children:[n.jsx("button",{type:"button",className:"panel-refresh",title:s("rerun"),onClick:()=>f({type:"list_commands"}),children:n.jsx(Y,{})}),n.jsx("button",{type:"button",className:"panel-new",title:s("newCommand"),onClick:D,children:n.jsx(H,{})})]})]}),n.jsx("div",{className:"panel-body",children:X?n.jsxs("div",{className:"cmd-form",children:[n.jsx("label",{htmlFor:"cmd-name",children:s("name")}),n.jsx("input",{id:"cmd-name",className:"cmd-input",value:o.name,placeholder:s("exampleName"),autoFocus:!0,onChange:e=>y({...o,name:e.target.value})}),n.jsx("label",{htmlFor:"cmd-command",children:s("command")}),n.jsx("input",{id:"cmd-command",className:"cmd-input",value:o.command,placeholder:s("exampleCommand"),onChange:e=>y({...o,command:e.target.value}),onKeyDown:e=>{e.key==="Enter"&&!e.nativeEvent.isComposing&&K()}}),n.jsxs("label",{htmlFor:"cmd-cwd",children:[s("directory")," ",n.jsx("span",{className:"cmd-hint",children:s("cwdHint")})]}),n.jsx("input",{id:"cmd-cwd",className:"cmd-input",value:o.cwd,placeholder:"${pwd}",onChange:e=>y({...o,cwd:e.target.value}),onKeyDown:e=>{e.key==="Enter"&&!e.nativeEvent.isComposing&&K()}}),n.jsxs("div",{className:"cmd-form-actions",children:[n.jsx("button",{type:"button",className:"btn",onClick:O,children:s("cancel")}),n.jsx("button",{type:"button",className:"btn primary",disabled:!o.name.trim()||!o.command.trim(),onClick:K,children:s("save")})]})]}):n.jsxs(n.Fragment,{children:[t.commands.length===0&&n.jsx("div",{className:"panel-empty",children:s("noCommands")}),t.commands.map((e,a)=>n.jsxs("div",{className:"cmd-item",children:[n.jsx("button",{type:"button",className:"cmd-run",title:s("clickToRun"),onClick:()=>T(e),children:n.jsx(Z,{})}),n.jsxs("button",{type:"button",className:"cmd-main",title:s("clickToRun"),onClick:()=>T(e),children:[n.jsx("span",{className:"cmd-name",children:e.name}),n.jsx("span",{className:"cmd-command",children:e.command}),e.cwd&&n.jsx("span",{className:"cmd-cwd",children:e.cwd})]}),n.jsx("button",{type:"button",className:"cmd-act",title:s("edit"),onClick:()=>L(a),children:n.jsx(q,{})}),n.jsx("button",{type:"button",className:`cmd-act del ${C===a?"confirm":""}`,title:s("delete"),onClick:()=>J(a),children:C===a?s("confirmQ"):n.jsx(ee,{})})]},a))]})}),n.jsxs("div",{className:"term-tabs-block",children:[n.jsxs("div",{className:"panel-header",children:[n.jsx("span",{className:"panel-title",children:s("terminal")}),n.jsx("button",{type:"button",className:"panel-new",title:s("newTerminal"),onClick:I,children:n.jsx(H,{})})]}),n.jsxs("div",{className:"panel-body",children:[t.terminals.length===0&&n.jsx("div",{className:"panel-empty",children:s("noTerminal")}),$.map(S),R.length>0&&n.jsxs("div",{className:"term-folder",children:[n.jsxs("button",{type:"button",className:`term-folder-header ${E?"open":""}`,title:s("aiBashGroup"),onClick:()=>B(e=>!e),children:[n.jsx("span",{className:"term-folder-caret",children:E?"▾":"▸"}),n.jsx("span",{className:"term-folder-title",children:s("aiBashGroup")}),n.jsx("span",{className:"term-folder-count",children:R.length})]}),E&&n.jsx("div",{className:"term-folder-body",children:R.map(S)})]})]})]})]}),n.jsxs("div",{className:"term-main",children:[_&&n.jsx("div",{className:"drawer-backdrop",onClick:()=>p(!1)}),n.jsx("button",{type:"button",className:"term-side-toggle",title:s("commands"),onClick:()=>p(e=>!e),children:n.jsx(ne,{})}),t.terminals.length===0?n.jsxs("div",{className:"term-empty",children:[n.jsx(te,{className:"term-empty-icon"}),n.jsx("div",{className:"term-empty-title",children:s("builtinTerminal")}),n.jsx("div",{className:"term-empty-sub",children:s("termEmptySub")})]}):t.terminals.map(e=>n.jsx(ce,{conversationId:e.conversationId,terminalId:e.id,command:e.command,cwd:e.cwd,title:e.title,agentBash:e.agentBash,active:e.id===h,running:e.running,exitCode:e.exitCode,register:c.register},`${e.conversationId}:${e.id}`))]})]})}export{fe as TerminalPanel};