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.
- package/CHANGELOG.md +124 -1
- package/bin/pi-web-ui.mjs +98 -8
- package/dist/server/agent-service.js +542 -16
- package/dist/server/client-state.js +99 -1
- package/dist/server/dsh/dsh-agent-service.js +239 -7
- package/dist/server/index.js +205 -3
- package/dist/server/managed.js +4 -0
- package/dist/server/plugin-catalog-sync.js +117 -0
- package/dist/server/plugin-catalog.js +67 -0
- package/dist/server/plugin-grants.js +281 -0
- package/dist/server/plugin-installer.js +238 -0
- package/dist/server/plugin-project.js +406 -0
- package/dist/server/plugins.js +390 -4
- package/dist/server/settings-service.js +34 -1
- package/dist/server/terminals.js +7 -2
- package/dist/server/update-check.js +100 -6
- package/dist/server/wait-subscription-scan.js +1 -1
- package/package.json +1 -1
- package/themes/dark-teal.css +209 -0
- package/web/dist/assets/TerminalPanel-BytY8dx7.js +6 -0
- package/web/dist/assets/index-CKoVyDkP.css +10 -0
- package/web/dist/assets/index-Dmwji4Cr.js +361 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/TerminalPanel-CNz4vxHW.js +0 -6
- package/web/dist/assets/index-BjODtvwn.css +0 -10
- package/web/dist/assets/index-DM-A59lk.js +0 -348
|
@@ -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
|
-
|
|
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
|
-
/**
|
|
260
|
-
|
|
261
|
-
|
|
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.
|
|
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};
|