pi-web-ui 0.84.0 → 0.86.1
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 +143 -1
- package/bin/pi-web-ui.mjs +98 -8
- package/dist/server/agent-service.js +563 -16
- package/dist/server/client-state.js +99 -1
- package/dist/server/dsh/dsh-agent-service.js +239 -7
- package/dist/server/index.js +207 -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-facilities.js +13 -3
- 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 +406 -4
- package/dist/server/settings-service.js +34 -1
- package/dist/server/terminals.js +49 -4
- package/dist/server/tool-manager.js +3 -3
- package/dist/server/update-check.js +100 -6
- package/dist/server/wait-subscription-scan.js +1 -1
- package/package.json +1 -1
- package/plugins/catalog.json +9 -0
- package/themes/dark-teal.css +209 -0
- package/web/dist/assets/TerminalPanel-fC8DLbDz.js +6 -0
- package/web/dist/assets/index-CCrF953V.js +361 -0
- package/web/dist/assets/index-CKoVyDkP.css +10 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/TerminalPanel-B7W10RgC.js +0 -6
- package/web/dist/assets/index-B1_0cm7B.js +0 -348
- package/web/dist/assets/index-D6KtyrNW.css +0 -10
|
@@ -68,9 +68,9 @@ export const AGENT_TOOL_CATALOG = [
|
|
|
68
68
|
{ name: DELEGATE_TASK_TOOL_NAME, group: "other", defaultOn: true, dshVisible: false },
|
|
69
69
|
{ name: ASK_USER_QUESTION_TOOL_NAME, group: "other", defaultOn: true, dshVisible: true },
|
|
70
70
|
{ name: MARKERS_LIST_TOOL_NAME, group: "other", defaultOn: true, dshVisible: true },
|
|
71
|
-
//
|
|
72
|
-
// customTool 发出),列在那里只会让用户关一个不存在的工具。
|
|
73
|
-
{ name: BROWSER_PAGE_TOOL_NAME, group: "other", defaultOn:
|
|
71
|
+
// 默认关(AI 动用户浏览器,opt-in 才开)且 dshVisible=false:DSH 引擎没有页面桥
|
|
72
|
+
// (page_request 由 pi 引擎的 customTool 发出),列在那里只会让用户关一个不存在的工具。
|
|
73
|
+
{ name: BROWSER_PAGE_TOOL_NAME, group: "other", defaultOn: false, dshVisible: false },
|
|
74
74
|
];
|
|
75
75
|
const KNOWN_NAMES = new Set(AGENT_TOOL_CATALOG.map((t) => t.name));
|
|
76
76
|
/** 是否为本表登记的可开关工具(未知名一律 false,不抛错)。 */
|
|
@@ -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.1",
|
|
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": {
|
package/plugins/catalog.json
CHANGED
|
@@ -53,6 +53,15 @@
|
|
|
53
53
|
"source": "xing-shuyin/pi-web-ui/plugins/legado-web",
|
|
54
54
|
"homepage": "https://github.com/xing-shuyin/pi-web-ui/tree/main/plugins/legado-web"
|
|
55
55
|
},
|
|
56
|
+
{
|
|
57
|
+
"id": "wechat-ilink",
|
|
58
|
+
"name": "微信通道",
|
|
59
|
+
"icon": "💬",
|
|
60
|
+
"description": "微信扫码登录(与腾讯 openclaw-weixin 同源的 ilink 协议):出站长轮询收消息,微信里直接指挥 agent,无需公网 IP。",
|
|
61
|
+
"descriptionEn": "WeChat channel (ilink protocol, same origin as Tencent openclaw-weixin): outbound long-polling, drive the agent from WeChat, no public IP needed.",
|
|
62
|
+
"source": "xing-shuyin/pi-web-ui/plugins/wechat-ilink",
|
|
63
|
+
"homepage": "https://github.com/xing-shuyin/pi-web-ui/tree/main/plugins/wechat-ilink"
|
|
64
|
+
},
|
|
56
65
|
{
|
|
57
66
|
"id": "image-toolkit",
|
|
58
67
|
"name": "image-toolkit",
|
|
@@ -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-CCrF953V.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};
|