pi-web-ui 0.48.1 → 0.48.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.
@@ -1332,28 +1332,17 @@ export class ClientSession {
1332
1332
  this.webUi.resolveDialog(id, value);
1333
1333
  }
1334
1334
  /**
1335
- * Whether the pi agent config looks ready: the agent dir exists and
1336
- * auth.json has at least one provider credential. Cached for 2s.
1335
+ * Whether the pi agent has at least one usable model. ModelRuntime's
1336
+ * available snapshot already accounts for models.json, auth.json, env-var
1337
+ * credentials, OAuth, and runtime API-key overrides. Cached for 2s because
1338
+ * this is called while building frequent snapshots.
1337
1339
  */
1338
1340
  isPiConfigured() {
1339
1341
  const now = Date.now();
1340
1342
  const cached = this.piCheckCache;
1341
1343
  if (cached && now - cached.at < 2000)
1342
1344
  return cached.configured;
1343
- let configured = false;
1344
- try {
1345
- const authPath = join(this.agentDir, "auth.json");
1346
- if (existsSync(authPath)) {
1347
- const data = JSON.parse(readFileSync(authPath, "utf8"));
1348
- configured =
1349
- typeof data === "object" &&
1350
- data !== null &&
1351
- Object.keys(data).length > 0;
1352
- }
1353
- }
1354
- catch {
1355
- configured = false;
1356
- }
1345
+ const configured = (this.sharedModelRuntime?.getAvailableSnapshot().length ?? 0) > 0;
1357
1346
  this.piCheckCache = { at: now, configured };
1358
1347
  return configured;
1359
1348
  }
@@ -769,6 +769,7 @@ export class ModelAdminService {
769
769
  // auth.json is optional; models.json can still use its own apiKey.
770
770
  }
771
771
  await this.host.modelRuntime().refresh();
772
+ this.host.invalidatePiConfig();
772
773
  await this.listModelsConfig();
773
774
  await this.host.pushModels();
774
775
  this.host.emit({
@@ -801,6 +802,7 @@ export class ModelAdminService {
801
802
  delete providers[providerId];
802
803
  writeFileSync(this.modelsConfigPath(), JSON.stringify({ providers }, null, 2) + "\n");
803
804
  await this.host.modelRuntime().refresh();
805
+ this.host.invalidatePiConfig();
804
806
  await this.listModelsConfig();
805
807
  await this.host.pushModels();
806
808
  this.host.emit({
package/package.json CHANGED
@@ -1,110 +1,110 @@
1
- {
2
- "name": "pi-web-ui",
3
- "version": "0.48.1",
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
- "license": "MIT",
6
- "author": {
7
- "name": "xingshuyin",
8
- "email": "xingshuyin@users.noreply.github.com"
9
- },
10
- "type": "module",
11
- "bin": {
12
- "pi-web-ui": "bin/pi-web-ui.mjs"
13
- },
14
- "main": "electron/main.mjs",
15
- "files": [
16
- "bin/",
17
- "dist/",
18
- "web/dist/",
19
- "web/public/",
20
- "themes/",
21
- "deploy/",
22
- "extensions/",
23
- "README.md",
24
- "LICENSE"
25
- ],
26
- "keywords": [
27
- "pi",
28
- "pi-package",
29
- "coding-agent",
30
- "ai",
31
- "chat",
32
- "web-ui",
33
- "terminal",
34
- "llm"
35
- ],
36
- "repository": {
37
- "type": "git",
38
- "url": "git+https://github.com/xing-shuyin/pi-web-ui.git"
39
- },
40
- "pi": {
41
- "extensions": [
42
- "./extensions"
43
- ]
44
- },
45
- "engines": {
46
- "node": ">=22.19.0"
47
- },
48
- "scripts": {
49
- "prepublishOnly": "npm run build",
50
- "dev": "concurrently -k -n server,web -c blue,green \"npm:dev:server\" \"npm:dev:web\"",
51
- "dev:server": "cross-env PORT=8788 PI_WEB_ALLOW_ORIGINS=http://localhost:5173,http://127.0.0.1:5173 node --watch --import tsx server/index.ts",
52
- "dev:web": "vite --config web/vite.config.ts",
53
- "build": "npm run build:web && npm run build:server",
54
- "build:web": "vite build --config web/vite.config.ts",
55
- "build:server": "tsc -p tsconfig.server.json",
56
- "typecheck": "tsc -p tsconfig.server.json --noEmit && tsc -p web/tsconfig.json --noEmit && tsc -p tsconfig.tests.json --noEmit",
57
- "test": "vitest run",
58
- "test:unit": "vitest run",
59
- "test:smoke": "node tests/run-smoke.mjs",
60
- "check:protocol": "node scripts/check-protocol-sync.mjs",
61
- "test:freeze": "node tests/freeze-test.mjs",
62
- "start": "node dist/server/index.js",
63
- "start:electron": "electron .",
64
- "build:electron": "electron-builder build",
65
- "build:electron:mac": "electron-builder build --mac",
66
- "build:electron:win": "electron-builder build --win",
67
- "build:electron:linux": "electron-builder build --linux",
68
- "publish:electron": "electron-builder build --publish always"
69
- },
70
- "dependencies": {
71
- "@earendil-works/pi-coding-agent": "^0.84.2",
72
- "@xterm/addon-fit": "^0.11.0",
73
- "@xterm/xterm": "^6.0.0",
74
- "compression": "^1.8.1",
75
- "express": "^4.21.2",
76
- "highlight.js": "^11.10.0",
77
- "node-pty": "^1.1.0",
78
- "react": "^18.3.1",
79
- "react-dom": "^18.3.1",
80
- "react-icons": "^5.7.0",
81
- "react-markdown": "^9.0.1",
82
- "rehype-highlight": "^7.0.1",
83
- "remark-gfm": "^4.0.0",
84
- "typebox": "^1.3.14",
85
- "ws": "^8.18.0"
86
- },
87
- "devDependencies": {
88
- "@types/compression": "^1.8.1",
89
- "@types/express": "^4.17.21",
90
- "@types/node": "^22.10.0",
91
- "@types/react": "^18.3.12",
92
- "@types/react-dom": "^18.3.1",
93
- "@types/ws": "^8.5.13",
94
- "@vitejs/plugin-react": "^4.3.4",
95
- "concurrently": "^9.1.0",
96
- "cross-env": "^10.1.0",
97
- "playwright-core": "^1.62.1",
98
- "tsx": "^4.19.2",
99
- "typescript": "^5.7.2",
100
- "vite": "^6.0.3",
101
- "vitest": "^4.1.11",
102
- "electron": "^41.0.0",
103
- "electron-builder": "^25.0.0",
104
- "electron-updater": "^6.0.0"
105
- },
106
- "allowScripts": {
107
- "node-pty@1.1.0": true,
108
- "electron@41.10.7": true
109
- }
110
- }
1
+ {
2
+ "name": "pi-web-ui",
3
+ "version": "0.48.2",
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
+ "license": "MIT",
6
+ "author": {
7
+ "name": "xingshuyin",
8
+ "email": "xingshuyin@users.noreply.github.com"
9
+ },
10
+ "type": "module",
11
+ "bin": {
12
+ "pi-web-ui": "bin/pi-web-ui.mjs"
13
+ },
14
+ "main": "electron/main.mjs",
15
+ "files": [
16
+ "bin/",
17
+ "dist/",
18
+ "web/dist/",
19
+ "web/public/",
20
+ "themes/",
21
+ "deploy/",
22
+ "extensions/",
23
+ "README.md",
24
+ "LICENSE"
25
+ ],
26
+ "keywords": [
27
+ "pi",
28
+ "pi-package",
29
+ "coding-agent",
30
+ "ai",
31
+ "chat",
32
+ "web-ui",
33
+ "terminal",
34
+ "llm"
35
+ ],
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/xing-shuyin/pi-web-ui.git"
39
+ },
40
+ "pi": {
41
+ "extensions": [
42
+ "./extensions"
43
+ ]
44
+ },
45
+ "engines": {
46
+ "node": ">=22.19.0"
47
+ },
48
+ "scripts": {
49
+ "prepublishOnly": "npm run build",
50
+ "dev": "concurrently -k -n server,web -c blue,green \"npm:dev:server\" \"npm:dev:web\"",
51
+ "dev:server": "cross-env PORT=8788 PI_WEB_ALLOW_ORIGINS=http://localhost:5173,http://127.0.0.1:5173 node --watch --import tsx server/index.ts",
52
+ "dev:web": "vite --config web/vite.config.ts",
53
+ "build": "npm run build:web && npm run build:server",
54
+ "build:web": "vite build --config web/vite.config.ts",
55
+ "build:server": "tsc -p tsconfig.server.json",
56
+ "typecheck": "tsc -p tsconfig.server.json --noEmit && tsc -p web/tsconfig.json --noEmit && tsc -p tsconfig.tests.json --noEmit",
57
+ "test": "vitest run",
58
+ "test:unit": "vitest run",
59
+ "test:smoke": "node tests/run-smoke.mjs",
60
+ "check:protocol": "node scripts/check-protocol-sync.mjs",
61
+ "test:freeze": "node tests/freeze-test.mjs",
62
+ "start": "node dist/server/index.js",
63
+ "start:electron": "electron .",
64
+ "build:electron": "electron-builder build",
65
+ "build:electron:mac": "electron-builder build --mac",
66
+ "build:electron:win": "electron-builder build --win",
67
+ "build:electron:linux": "electron-builder build --linux",
68
+ "publish:electron": "electron-builder build --publish always"
69
+ },
70
+ "dependencies": {
71
+ "@earendil-works/pi-coding-agent": "^0.84.2",
72
+ "@xterm/addon-fit": "^0.11.0",
73
+ "@xterm/xterm": "^6.0.0",
74
+ "compression": "^1.8.1",
75
+ "express": "^4.21.2",
76
+ "highlight.js": "^11.10.0",
77
+ "node-pty": "^1.1.0",
78
+ "react": "^18.3.1",
79
+ "react-dom": "^18.3.1",
80
+ "react-icons": "^5.7.0",
81
+ "react-markdown": "^9.0.1",
82
+ "rehype-highlight": "^7.0.1",
83
+ "remark-gfm": "^4.0.0",
84
+ "typebox": "^1.3.14",
85
+ "ws": "^8.18.0"
86
+ },
87
+ "devDependencies": {
88
+ "@types/compression": "^1.8.1",
89
+ "@types/express": "^4.17.21",
90
+ "@types/node": "^22.10.0",
91
+ "@types/react": "^18.3.12",
92
+ "@types/react-dom": "^18.3.1",
93
+ "@types/ws": "^8.5.13",
94
+ "@vitejs/plugin-react": "^4.3.4",
95
+ "concurrently": "^9.1.0",
96
+ "cross-env": "^10.1.0",
97
+ "playwright-core": "^1.62.1",
98
+ "tsx": "^4.19.2",
99
+ "typescript": "^5.7.2",
100
+ "vite": "^6.0.3",
101
+ "vitest": "^4.1.11",
102
+ "electron": "^41.0.0",
103
+ "electron-builder": "^25.0.0",
104
+ "electron-updater": "^6.0.0"
105
+ },
106
+ "allowScripts": {
107
+ "node-pty@1.1.0": true,
108
+ "electron@41.10.7": true
109
+ }
110
+ }
@@ -2903,7 +2903,9 @@ body.panel-resizing {
2903
2903
  color: var(--text-faint);
2904
2904
  }
2905
2905
  flex: none;
2906
- width: 112px;
2906
+ width: max-content; /* 按最宽服务商名自适应(上限 280px),名字完整显示 */
2907
+ min-width: 150px;
2908
+ max-width: 280px;
2907
2909
  overflow-y: auto;
2908
2910
  border-right: 1px solid var(--border-soft);
2909
2911
  margin: 2px 4px 2px 0;
@@ -2959,14 +2961,24 @@ body.panel-resizing {
2959
2961
  display: flex;
2960
2962
  flex-direction: column;
2961
2963
  overflow-y: hidden;
2962
- /* 加宽以容纳左侧服务商栏 + 右侧模型列表 */
2963
- min-width: 330px;
2964
- max-width: 460px;
2964
+ /* 宽度:由 JS 打开时测量内容自然宽度并锁定(自适应最宽需要、不来回变化);
2965
+ min/max 只是兜底。 */
2966
+ min-width: 440px;
2967
+ max-width: 620px;
2968
+ }
2969
+ /* 模型弹窗高度固定(按最高来):不再随筛选/搜索内容变短而回缩。
2970
+ 底部输入栏最高约 300px,预留 320px 保证任何输入框高度都不溢出视口。 */
2971
+ @media (min-width: 769px) {
2972
+ .dropdown.dd-up .dd-menu.dd-menu-model {
2973
+ max-height: none; /* 去掉 dd-up 的 360px 上限,改用固定高度 */
2974
+ height: min(600px, calc(100vh - 320px));
2975
+ }
2965
2976
  }
2966
2977
  .dd-menu.dd-menu-model .dd-model-scroll {
2967
2978
  flex: 1 1 auto;
2968
2979
  min-height: 0;
2969
- max-height: none; /* 由外层 max-height 约束,列表吃掉剩余空间后内部滚动 */
2980
+ min-width: 0; /* 模型列可收缩:宽度由固定服务商栏/内容最宽项决定,不被行内长内容撑开 */
2981
+ max-height: none; /* 由外层固定 height 约束,列表吃掉剩余空间后内部滚动 */
2970
2982
  }
2971
2983
  .dd-menu.dd-menu-model .dd-footer {
2972
2984
  flex-shrink: 0;
@@ -3062,7 +3074,11 @@ body.panel-resizing {
3062
3074
  white-space: nowrap;
3063
3075
  }
3064
3076
  .dd-model-provider {
3065
- flex-shrink: 0;
3077
+ flex-shrink: 1;
3078
+ min-width: 0;
3079
+ overflow: hidden;
3080
+ text-overflow: ellipsis;
3081
+ white-space: nowrap;
3066
3082
  font-size: 11px;
3067
3083
  color: var(--text-faint);
3068
3084
  font-family: var(--mono);
@@ -3083,6 +3099,18 @@ body.panel-resizing {
3083
3099
  white-space: nowrap;
3084
3100
  }
3085
3101
 
3102
+ /* 使用次数徽标(突出显示,区别于推理/识图等普通徽标)。 */
3103
+ .dd-model-usage {
3104
+ font-size: 10.5px;
3105
+ line-height: 1.5;
3106
+ color: var(--accent);
3107
+ background: var(--accent-soft);
3108
+ border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border));
3109
+ border-radius: 4px;
3110
+ padding: 0 6px;
3111
+ white-space: nowrap;
3112
+ }
3113
+
3086
3114
  .dd-refresh {
3087
3115
  width: 100%;
3088
3116
  margin-top: 4px;
@@ -4306,87 +4334,13 @@ span.msg-editor-hint svg {
4306
4334
  flex-direction: column;
4307
4335
  align-items: center;
4308
4336
  text-align: center;
4309
- padding: 70px 20px 30px;
4310
- color: var(--text-dim);
4311
- }
4312
-
4313
- .empty-logo-wrap {
4314
- position: relative;
4315
- margin-bottom: 20px;
4316
- }
4317
-
4318
- .empty-logo {
4319
- width: 68px;
4320
- height: 68px;
4321
- display: flex;
4322
- align-items: center;
4323
- justify-content: center;
4324
- font-size: 40px;
4325
- font-weight: 700;
4326
- color: #fff;
4327
- background: linear-gradient(135deg, #8b5cf6 0%, #6366f1 60%, #4f46e5 100%);
4328
- border-radius: 20px;
4329
- box-shadow:
4330
- 0 16px 44px rgba(139, 92, 246, 0.38),
4331
- inset 0 1px 0 rgba(255, 255, 255, 0.22);
4332
- }
4333
-
4334
- /* soft glow behind the logo */
4335
- .empty-logo-wrap::before {
4336
- content: "";
4337
- position: absolute;
4338
- inset: -18px;
4339
- border-radius: 32px;
4340
- background: radial-gradient(
4341
- ellipse at center,
4342
- rgba(139, 92, 246, 0.28),
4343
- transparent 70%
4344
- );
4345
- z-index: -1;
4346
- }
4347
-
4348
- .empty-title {
4349
- margin: 0 0 6px;
4350
- color: var(--text);
4351
- font-size: 22px;
4352
- font-weight: 650;
4353
- letter-spacing: 0.3px;
4354
- }
4355
-
4356
- .empty-sub {
4357
- margin: 0 0 16px;
4358
- font-size: 13px;
4359
- color: var(--text-faint);
4360
- }
4361
-
4362
- .empty-cwd {
4363
- display: inline-flex;
4364
- align-items: center;
4365
- gap: 7px;
4366
- max-width: min(560px, 80vw);
4367
- padding: 4px 12px;
4368
- border: 1px solid var(--border-soft);
4369
- border-radius: 999px;
4370
- background: var(--bg-elev);
4371
- font-size: 11.5px;
4372
- }
4373
-
4374
- .empty-cwd-label {
4375
- color: var(--text-faint);
4376
- flex-shrink: 0;
4377
- }
4378
-
4379
- .empty-cwd-path {
4337
+ padding: 40px 20px 30px;
4380
4338
  color: var(--text-dim);
4381
- font-family: var(--mono);
4382
- overflow: hidden;
4383
- text-overflow: ellipsis;
4384
- white-space: nowrap;
4385
4339
  }
4386
4340
 
4387
4341
  /* ---- 提示词模板(空态推荐卡片) ---- */
4388
4342
  .empty-templates {
4389
- margin-top: 30px;
4343
+ margin-top: 0;
4390
4344
  width: min(720px, 92vw);
4391
4345
  }
4392
4346
 
@@ -4538,9 +4492,12 @@ span.msg-editor-hint svg {
4538
4492
  max-width: 620px;
4539
4493
  }
4540
4494
 
4541
- /* 模板库(选择器)弹窗:列所有模板卡片 */
4542
- .template-picker {
4543
- max-width: 720px;
4495
+ /* 模板库(选择器)弹窗:列所有模板卡片。用复合选择器保证覆盖更靠后的
4496
+ * `.modal { max-width: 460px }` 基类——同优先级的选择器会被后面的规则覆盖,
4497
+ * 单类选择器 `.template-picker` 之前一直没生效。 */
4498
+ .modal.template-picker {
4499
+ /* 两倍宽度:原设计 720px → 1440px,模板卡片多列铺开。 */
4500
+ max-width: 1440px;
4544
4501
  }
4545
4502
 
4546
4503
  .template-picker-head {
package/themes/white.css CHANGED
@@ -2902,7 +2902,9 @@ body.panel-resizing {
2902
2902
  color: var(--text-faint);
2903
2903
  }
2904
2904
  flex: none;
2905
- width: 112px;
2905
+ width: max-content; /* 按最宽服务商名自适应(上限 280px),名字完整显示 */
2906
+ min-width: 150px;
2907
+ max-width: 280px;
2906
2908
  overflow-y: auto;
2907
2909
  border-right: 1px solid var(--border-soft);
2908
2910
  margin: 2px 4px 2px 0;
@@ -2958,14 +2960,24 @@ body.panel-resizing {
2958
2960
  display: flex;
2959
2961
  flex-direction: column;
2960
2962
  overflow-y: hidden;
2961
- /* 加宽以容纳左侧服务商栏 + 右侧模型列表 */
2962
- min-width: 330px;
2963
- max-width: 460px;
2963
+ /* 宽度:由 JS 打开时测量内容自然宽度并锁定(自适应最宽需要、不来回变化);
2964
+ min/max 只是兜底。 */
2965
+ min-width: 440px;
2966
+ max-width: 620px;
2967
+ }
2968
+ /* 模型弹窗高度固定(按最高来):不再随筛选/搜索内容变短而回缩。
2969
+ 底部输入栏最高约 300px,预留 320px 保证任何输入框高度都不溢出视口。 */
2970
+ @media (min-width: 769px) {
2971
+ .dropdown.dd-up .dd-menu.dd-menu-model {
2972
+ max-height: none; /* 去掉 dd-up 的 360px 上限,改用固定高度 */
2973
+ height: min(600px, calc(100vh - 320px));
2974
+ }
2964
2975
  }
2965
2976
  .dd-menu.dd-menu-model .dd-model-scroll {
2966
2977
  flex: 1 1 auto;
2967
2978
  min-height: 0;
2968
- max-height: none; /* 由外层 max-height 约束,列表吃掉剩余空间后内部滚动 */
2979
+ min-width: 0; /* 模型列可收缩:宽度由固定服务商栏/内容最宽项决定,不被行内长内容撑开 */
2980
+ max-height: none; /* 由外层固定 height 约束,列表吃掉剩余空间后内部滚动 */
2969
2981
  }
2970
2982
  .dd-menu.dd-menu-model .dd-footer {
2971
2983
  flex-shrink: 0;
@@ -3061,7 +3073,11 @@ body.panel-resizing {
3061
3073
  white-space: nowrap;
3062
3074
  }
3063
3075
  .dd-model-provider {
3064
- flex-shrink: 0;
3076
+ flex-shrink: 1;
3077
+ min-width: 0;
3078
+ overflow: hidden;
3079
+ text-overflow: ellipsis;
3080
+ white-space: nowrap;
3065
3081
  font-size: 11px;
3066
3082
  color: var(--text-faint);
3067
3083
  font-family: var(--mono);
@@ -3082,6 +3098,18 @@ body.panel-resizing {
3082
3098
  white-space: nowrap;
3083
3099
  }
3084
3100
 
3101
+ /* 使用次数徽标(突出显示,区别于推理/识图等普通徽标)。 */
3102
+ .dd-model-usage {
3103
+ font-size: 10.5px;
3104
+ line-height: 1.5;
3105
+ color: var(--accent);
3106
+ background: var(--accent-soft);
3107
+ border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border));
3108
+ border-radius: 4px;
3109
+ padding: 0 6px;
3110
+ white-space: nowrap;
3111
+ }
3112
+
3085
3113
  .dd-refresh {
3086
3114
  width: 100%;
3087
3115
  margin-top: 4px;
@@ -4305,87 +4333,13 @@ span.msg-editor-hint svg {
4305
4333
  flex-direction: column;
4306
4334
  align-items: center;
4307
4335
  text-align: center;
4308
- padding: 70px 20px 30px;
4309
- color: var(--text-dim);
4310
- }
4311
-
4312
- .empty-logo-wrap {
4313
- position: relative;
4314
- margin-bottom: 20px;
4315
- }
4316
-
4317
- .empty-logo {
4318
- width: 68px;
4319
- height: 68px;
4320
- display: flex;
4321
- align-items: center;
4322
- justify-content: center;
4323
- font-size: 40px;
4324
- font-weight: 700;
4325
- color: #fff;
4326
- background: linear-gradient(135deg, #8b5cf6 0%, #6366f1 60%, #4f46e5 100%);
4327
- border-radius: 20px;
4328
- box-shadow:
4329
- 0 16px 44px rgba(139, 92, 246, 0.38),
4330
- inset 0 1px 0 rgba(255, 255, 255, 0.22);
4331
- }
4332
-
4333
- /* soft glow behind the logo */
4334
- .empty-logo-wrap::before {
4335
- content: "";
4336
- position: absolute;
4337
- inset: -18px;
4338
- border-radius: 32px;
4339
- background: radial-gradient(
4340
- ellipse at center,
4341
- rgba(139, 92, 246, 0.28),
4342
- transparent 70%
4343
- );
4344
- z-index: -1;
4345
- }
4346
-
4347
- .empty-title {
4348
- margin: 0 0 6px;
4349
- color: var(--text);
4350
- font-size: 22px;
4351
- font-weight: 650;
4352
- letter-spacing: 0.3px;
4353
- }
4354
-
4355
- .empty-sub {
4356
- margin: 0 0 16px;
4357
- font-size: 13px;
4358
- color: var(--text-faint);
4359
- }
4360
-
4361
- .empty-cwd {
4362
- display: inline-flex;
4363
- align-items: center;
4364
- gap: 7px;
4365
- max-width: min(560px, 80vw);
4366
- padding: 4px 12px;
4367
- border: 1px solid var(--border-soft);
4368
- border-radius: 999px;
4369
- background: var(--bg-elev);
4370
- font-size: 11.5px;
4371
- }
4372
-
4373
- .empty-cwd-label {
4374
- color: var(--text-faint);
4375
- flex-shrink: 0;
4376
- }
4377
-
4378
- .empty-cwd-path {
4336
+ padding: 40px 20px 30px;
4379
4337
  color: var(--text-dim);
4380
- font-family: var(--mono);
4381
- overflow: hidden;
4382
- text-overflow: ellipsis;
4383
- white-space: nowrap;
4384
4338
  }
4385
4339
 
4386
4340
  /* ---- 提示词模板(空态推荐卡片) ---- */
4387
4341
  .empty-templates {
4388
- margin-top: 30px;
4342
+ margin-top: 0;
4389
4343
  width: min(720px, 92vw);
4390
4344
  }
4391
4345
 
@@ -4537,9 +4491,12 @@ span.msg-editor-hint svg {
4537
4491
  max-width: 620px;
4538
4492
  }
4539
4493
 
4540
- /* 模板库(选择器)弹窗:列所有模板卡片 */
4541
- .template-picker {
4542
- max-width: 720px;
4494
+ /* 模板库(选择器)弹窗:列所有模板卡片。用复合选择器保证覆盖更靠后的
4495
+ * `.modal { max-width: 460px }` 基类——同优先级的选择器会被后面的规则覆盖,
4496
+ * 单类选择器 `.template-picker` 之前一直没生效。 */
4497
+ .modal.template-picker {
4498
+ /* 两倍宽度:原设计 720px → 1440px,模板卡片多列铺开。 */
4499
+ max-width: 1440px;
4543
4500
  }
4544
4501
 
4545
4502
  .template-picker-head {
@@ -1,2 +1,2 @@
1
- import{a as d,j as n}from"./markdown-DRBrS2Nf.js";import{b as $,T as R,u as O,F as P,a as K,c as q,d as L,e as B,f as J,g as X,h as G,r as Q}from"./index-ChxqCQTs.js";import{D as U,o as V}from"./xterm-D1D2FVe3.js";import"./react-C9ovnpIm.js";function W({conversationId:s,terminalId:r,command:x,cwd:a,active:f,send:m,register:v}){const w=d.useRef(null),N=d.useRef(null),b=x?JSON.stringify(x):"";return d.useEffect(()=>{const p=w.current;if(!p)return;const t=new U({theme:$(),fontFamily:'"SF Mono", "JetBrains Mono", ui-monospace, Menlo, Consolas, monospace',fontSize:13,cursorBlink:!0,scrollback:8e3}),c=new V;t.loadAddon(c),t.open(p),N.current={term:t,fit:c},f&&t.focus();const h=()=>{t.options.theme=$()};window.addEventListener(R,h),t.attachCustomKeyEventHandler(o=>{var F;if(o.type!=="keydown")return!0;const E=(F=o.key)==null?void 0:F.toLowerCase();if((o.ctrlKey||o.metaKey)&&E==="v")return!1;if(o.ctrlKey&&!o.shiftKey&&!o.altKey&&E==="c"&&t.hasSelection()){const T=t.textarea;return T&&(T.value=t.getSelection(),T.select()),!1}return!0});const g=v(s,r,{write:o=>t.write(o),dispose:()=>t.dispose()}),C=()=>{try{c.fit(),m({type:"terminal_resize",terminalId:r,conversationId:s,cols:t.cols,rows:t.rows})}catch{}},j=requestAnimationFrame(()=>{try{c.fit()}catch{}m(x?{type:"run_command",terminalId:r,conversationId:s,command:x,cols:t.cols,rows:t.rows}:{type:"terminal_create",terminalId:r,conversationId:s,cwd:a,cols:t.cols,rows:t.rows})}),k=t.onData(o=>{m({type:"terminal_input",terminalId:r,conversationId:s,data:o})});let y=null;return typeof ResizeObserver<"u"&&(y=new ResizeObserver(()=>{p.offsetWidth>0&&p.offsetHeight>0&&C()}),y.observe(p)),()=>{cancelAnimationFrame(j),k.dispose(),window.removeEventListener(R,h),y==null||y.disconnect(),g(),t.dispose(),N.current=null}},[s,r,b,m,v]),d.useEffect(()=>{if(!f)return;const p=requestAnimationFrame(()=>{const t=N.current;if(t){try{t.fit.fit(),m({type:"terminal_resize",terminalId:r,conversationId:s,cols:t.term.cols,rows:t.term.rows})}catch{}t.term.focus()}});return()=>cancelAnimationFrame(p)},[f]),n.jsx("div",{ref:w,className:`term-xterm ${f?"":"hidden"}`})}const A={name:"",command:"",cwd:"${pwd}"};function se({chat:s,send:r,terminal:x}){const a=O(),[f,m]=d.useState(null),[v,w]=d.useState(!1),[N,b]=d.useState(!1),[p,t]=d.useState(null),[c,h]=d.useState(A),[g,C]=d.useState(null),j=d.useRef(null);d.useEffect(()=>{s.terminals.length===0?m(null):s.terminals.some(e=>e.id===f)||m(s.terminals[s.terminals.length-1].id)},[s.terminals,f]),d.useEffect(()=>()=>{j.current&&clearTimeout(j.current)},[]);const k=e=>{var u;if(!s.ready)return;const i=Q(),l=s.activeConversationId||((u=s.state)==null?void 0:u.conversationId)||"";x.create({...e,id:i,conversationId:l,cols:e.cols??80,rows:e.rows??24,running:!0,exitCode:null}),m(i),w(!1)},y=()=>{var e;return k({title:a("terminalTitle",{n:s.terminals.length+1}),cwd:((e=s.state)==null?void 0:e.cwd)??""})},o=e=>{var u;const i=e.name||e.command,l=s.terminals.find(_=>_.title===i);if(l){x.restart(l.id),m(l.id),r({type:"run_command",terminalId:l.id,conversationId:l.conversationId,command:e,cols:80,rows:24});return}k({title:i,cwd:((u=s.state)==null?void 0:u.cwd)??"",command:e})},E=e=>{const i=s.terminals.find(l=>l.id===e);if(i&&r({type:"terminal_kill",terminalId:e,conversationId:i.conversationId}),x.close(e),f===e){const l=s.terminals.filter(u=>u.id!==e);m(l.length>0?l[l.length-1].id:null)}},F=()=>{b(!0),t(null),h(A)},T=e=>{const i=s.commands[e];i&&(b(!1),t(e),h({name:i.name,command:i.command,cwd:i.cwd??""}))},D=()=>{b(!1),t(null)},S=()=>{const e=c.name.trim(),i=c.command.trim();if(!e||!i)return;const l=c.cwd.trim(),u={name:e,command:i,cwd:l||void 0},_=N?[...s.commands,u]:p!==null?s.commands.map((z,H)=>H===p?u:z):s.commands;r({type:"save_commands",commands:_}),D()},I=e=>{if(g===e){const i=s.commands.filter((l,u)=>u!==e);r({type:"save_commands",commands:i}),C(null),j.current&&clearTimeout(j.current)}else C(e),j.current&&clearTimeout(j.current),j.current=setTimeout(()=>C(null),2500)},M=N||p!==null;return n.jsxs("div",{className:"terminal-view",children:[n.jsxs("aside",{className:`term-side term-commands ${v?"open":""}`,children:[n.jsxs("div",{className:"panel-header",children:[n.jsx("span",{className:"panel-title",children:a("commands")}),n.jsxs("div",{className:"panel-header-actions",children:[n.jsx("button",{type:"button",className:"panel-refresh",title:a("rerun"),onClick:()=>r({type:"list_commands"}),children:n.jsx(P,{})}),n.jsx("button",{type:"button",className:"panel-new",title:a("newCommand"),onClick:F,children:n.jsx(K,{})})]})]}),n.jsx("div",{className:"panel-body",children:M?n.jsxs("div",{className:"cmd-form",children:[n.jsx("label",{htmlFor:"cmd-name",children:a("name")}),n.jsx("input",{id:"cmd-name",className:"cmd-input",value:c.name,placeholder:a("exampleName"),autoFocus:!0,onChange:e=>h({...c,name:e.target.value})}),n.jsx("label",{htmlFor:"cmd-command",children:a("command")}),n.jsx("input",{id:"cmd-command",className:"cmd-input",value:c.command,placeholder:a("exampleCommand"),onChange:e=>h({...c,command:e.target.value}),onKeyDown:e=>{e.key==="Enter"&&!e.nativeEvent.isComposing&&S()}}),n.jsxs("label",{htmlFor:"cmd-cwd",children:[a("directory")," ",n.jsx("span",{className:"cmd-hint",children:a("cwdHint")})]}),n.jsx("input",{id:"cmd-cwd",className:"cmd-input",value:c.cwd,placeholder:"${pwd}",onChange:e=>h({...c,cwd:e.target.value}),onKeyDown:e=>{e.key==="Enter"&&!e.nativeEvent.isComposing&&S()}}),n.jsxs("div",{className:"cmd-form-actions",children:[n.jsx("button",{type:"button",className:"btn",onClick:D,children:a("cancel")}),n.jsx("button",{type:"button",className:"btn primary",disabled:!c.name.trim()||!c.command.trim(),onClick:S,children:a("save")})]})]}):n.jsxs(n.Fragment,{children:[s.commands.length===0&&n.jsx("div",{className:"panel-empty",children:a("noCommands")}),s.commands.map((e,i)=>n.jsxs("div",{className:"cmd-item",children:[n.jsx("button",{type:"button",className:"cmd-run",title:a("clickToRun"),onClick:()=>o(e),children:n.jsx(q,{})}),n.jsxs("button",{type:"button",className:"cmd-main",title:a("clickToRun"),onClick:()=>o(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:a("edit"),onClick:()=>T(i),children:n.jsx(L,{})}),n.jsx("button",{type:"button",className:`cmd-act del ${g===i?"confirm":""}`,title:a("delete"),onClick:()=>I(i),children:g===i?a("confirmQ"):n.jsx(B,{})})]},i))]})}),n.jsxs("div",{className:"term-tabs-block",children:[n.jsxs("div",{className:"panel-header",children:[n.jsx("span",{className:"panel-title",children:a("terminal")}),n.jsx("button",{type:"button",className:"panel-new",title:a("newTerminal"),onClick:y,children:n.jsx(K,{})})]}),n.jsxs("div",{className:"panel-body",children:[s.terminals.length===0&&n.jsx("div",{className:"panel-empty",children:a("noTerminal")}),s.terminals.map(e=>n.jsxs("div",{className:`term-tab ${e.id===f?"active":""}`,children:[n.jsxs("button",{type:"button",className:"term-tab-main",title:`${e.cwd}${e.command?`
1
+ import{a as d,j as n}from"./markdown-DRBrS2Nf.js";import{b as $,T as R,u as O,F as P,a as K,c as q,d as L,e as B,f as J,g as X,h as G,r as Q}from"./index-Dg-bQ98a.js";import{D as U,o as V}from"./xterm-D1D2FVe3.js";import"./react-C9ovnpIm.js";function W({conversationId:s,terminalId:r,command:x,cwd:a,active:f,send:m,register:v}){const w=d.useRef(null),N=d.useRef(null),b=x?JSON.stringify(x):"";return d.useEffect(()=>{const p=w.current;if(!p)return;const t=new U({theme:$(),fontFamily:'"SF Mono", "JetBrains Mono", ui-monospace, Menlo, Consolas, monospace',fontSize:13,cursorBlink:!0,scrollback:8e3}),c=new V;t.loadAddon(c),t.open(p),N.current={term:t,fit:c},f&&t.focus();const h=()=>{t.options.theme=$()};window.addEventListener(R,h),t.attachCustomKeyEventHandler(o=>{var F;if(o.type!=="keydown")return!0;const E=(F=o.key)==null?void 0:F.toLowerCase();if((o.ctrlKey||o.metaKey)&&E==="v")return!1;if(o.ctrlKey&&!o.shiftKey&&!o.altKey&&E==="c"&&t.hasSelection()){const T=t.textarea;return T&&(T.value=t.getSelection(),T.select()),!1}return!0});const g=v(s,r,{write:o=>t.write(o),dispose:()=>t.dispose()}),C=()=>{try{c.fit(),m({type:"terminal_resize",terminalId:r,conversationId:s,cols:t.cols,rows:t.rows})}catch{}},j=requestAnimationFrame(()=>{try{c.fit()}catch{}m(x?{type:"run_command",terminalId:r,conversationId:s,command:x,cols:t.cols,rows:t.rows}:{type:"terminal_create",terminalId:r,conversationId:s,cwd:a,cols:t.cols,rows:t.rows})}),k=t.onData(o=>{m({type:"terminal_input",terminalId:r,conversationId:s,data:o})});let y=null;return typeof ResizeObserver<"u"&&(y=new ResizeObserver(()=>{p.offsetWidth>0&&p.offsetHeight>0&&C()}),y.observe(p)),()=>{cancelAnimationFrame(j),k.dispose(),window.removeEventListener(R,h),y==null||y.disconnect(),g(),t.dispose(),N.current=null}},[s,r,b,m,v]),d.useEffect(()=>{if(!f)return;const p=requestAnimationFrame(()=>{const t=N.current;if(t){try{t.fit.fit(),m({type:"terminal_resize",terminalId:r,conversationId:s,cols:t.term.cols,rows:t.term.rows})}catch{}t.term.focus()}});return()=>cancelAnimationFrame(p)},[f]),n.jsx("div",{ref:w,className:`term-xterm ${f?"":"hidden"}`})}const A={name:"",command:"",cwd:"${pwd}"};function se({chat:s,send:r,terminal:x}){const a=O(),[f,m]=d.useState(null),[v,w]=d.useState(!1),[N,b]=d.useState(!1),[p,t]=d.useState(null),[c,h]=d.useState(A),[g,C]=d.useState(null),j=d.useRef(null);d.useEffect(()=>{s.terminals.length===0?m(null):s.terminals.some(e=>e.id===f)||m(s.terminals[s.terminals.length-1].id)},[s.terminals,f]),d.useEffect(()=>()=>{j.current&&clearTimeout(j.current)},[]);const k=e=>{var u;if(!s.ready)return;const i=Q(),l=s.activeConversationId||((u=s.state)==null?void 0:u.conversationId)||"";x.create({...e,id:i,conversationId:l,cols:e.cols??80,rows:e.rows??24,running:!0,exitCode:null}),m(i),w(!1)},y=()=>{var e;return k({title:a("terminalTitle",{n:s.terminals.length+1}),cwd:((e=s.state)==null?void 0:e.cwd)??""})},o=e=>{var u;const i=e.name||e.command,l=s.terminals.find(_=>_.title===i);if(l){x.restart(l.id),m(l.id),r({type:"run_command",terminalId:l.id,conversationId:l.conversationId,command:e,cols:80,rows:24});return}k({title:i,cwd:((u=s.state)==null?void 0:u.cwd)??"",command:e})},E=e=>{const i=s.terminals.find(l=>l.id===e);if(i&&r({type:"terminal_kill",terminalId:e,conversationId:i.conversationId}),x.close(e),f===e){const l=s.terminals.filter(u=>u.id!==e);m(l.length>0?l[l.length-1].id:null)}},F=()=>{b(!0),t(null),h(A)},T=e=>{const i=s.commands[e];i&&(b(!1),t(e),h({name:i.name,command:i.command,cwd:i.cwd??""}))},D=()=>{b(!1),t(null)},S=()=>{const e=c.name.trim(),i=c.command.trim();if(!e||!i)return;const l=c.cwd.trim(),u={name:e,command:i,cwd:l||void 0},_=N?[...s.commands,u]:p!==null?s.commands.map((z,H)=>H===p?u:z):s.commands;r({type:"save_commands",commands:_}),D()},I=e=>{if(g===e){const i=s.commands.filter((l,u)=>u!==e);r({type:"save_commands",commands:i}),C(null),j.current&&clearTimeout(j.current)}else C(e),j.current&&clearTimeout(j.current),j.current=setTimeout(()=>C(null),2500)},M=N||p!==null;return n.jsxs("div",{className:"terminal-view",children:[n.jsxs("aside",{className:`term-side term-commands ${v?"open":""}`,children:[n.jsxs("div",{className:"panel-header",children:[n.jsx("span",{className:"panel-title",children:a("commands")}),n.jsxs("div",{className:"panel-header-actions",children:[n.jsx("button",{type:"button",className:"panel-refresh",title:a("rerun"),onClick:()=>r({type:"list_commands"}),children:n.jsx(P,{})}),n.jsx("button",{type:"button",className:"panel-new",title:a("newCommand"),onClick:F,children:n.jsx(K,{})})]})]}),n.jsx("div",{className:"panel-body",children:M?n.jsxs("div",{className:"cmd-form",children:[n.jsx("label",{htmlFor:"cmd-name",children:a("name")}),n.jsx("input",{id:"cmd-name",className:"cmd-input",value:c.name,placeholder:a("exampleName"),autoFocus:!0,onChange:e=>h({...c,name:e.target.value})}),n.jsx("label",{htmlFor:"cmd-command",children:a("command")}),n.jsx("input",{id:"cmd-command",className:"cmd-input",value:c.command,placeholder:a("exampleCommand"),onChange:e=>h({...c,command:e.target.value}),onKeyDown:e=>{e.key==="Enter"&&!e.nativeEvent.isComposing&&S()}}),n.jsxs("label",{htmlFor:"cmd-cwd",children:[a("directory")," ",n.jsx("span",{className:"cmd-hint",children:a("cwdHint")})]}),n.jsx("input",{id:"cmd-cwd",className:"cmd-input",value:c.cwd,placeholder:"${pwd}",onChange:e=>h({...c,cwd:e.target.value}),onKeyDown:e=>{e.key==="Enter"&&!e.nativeEvent.isComposing&&S()}}),n.jsxs("div",{className:"cmd-form-actions",children:[n.jsx("button",{type:"button",className:"btn",onClick:D,children:a("cancel")}),n.jsx("button",{type:"button",className:"btn primary",disabled:!c.name.trim()||!c.command.trim(),onClick:S,children:a("save")})]})]}):n.jsxs(n.Fragment,{children:[s.commands.length===0&&n.jsx("div",{className:"panel-empty",children:a("noCommands")}),s.commands.map((e,i)=>n.jsxs("div",{className:"cmd-item",children:[n.jsx("button",{type:"button",className:"cmd-run",title:a("clickToRun"),onClick:()=>o(e),children:n.jsx(q,{})}),n.jsxs("button",{type:"button",className:"cmd-main",title:a("clickToRun"),onClick:()=>o(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:a("edit"),onClick:()=>T(i),children:n.jsx(L,{})}),n.jsx("button",{type:"button",className:`cmd-act del ${g===i?"confirm":""}`,title:a("delete"),onClick:()=>I(i),children:g===i?a("confirmQ"):n.jsx(B,{})})]},i))]})}),n.jsxs("div",{className:"term-tabs-block",children:[n.jsxs("div",{className:"panel-header",children:[n.jsx("span",{className:"panel-title",children:a("terminal")}),n.jsx("button",{type:"button",className:"panel-new",title:a("newTerminal"),onClick:y,children:n.jsx(K,{})})]}),n.jsxs("div",{className:"panel-body",children:[s.terminals.length===0&&n.jsx("div",{className:"panel-empty",children:a("noTerminal")}),s.terminals.map(e=>n.jsxs("div",{className:`term-tab ${e.id===f?"active":""}`,children:[n.jsxs("button",{type:"button",className:"term-tab-main",title:`${e.cwd}${e.command?`
2
2
  > ${e.command.command}`:""}`,onClick:()=>{m(e.id),w(!1)},children:[n.jsx("span",{className:`term-tab-dot ${e.running?"run":"exit"}`}),n.jsxs("span",{className:"term-tab-title",children:[e.title,!e.running&&n.jsx("span",{className:"term-tab-exit",children:a("exited",{code:e.exitCode===null?"":` ${e.exitCode}`})})]})]}),n.jsx("button",{type:"button",className:"term-tab-close",title:a("closeTerminal"),onClick:()=>E(e.id),children:n.jsx(J,{})})]},e.id))]})]})]}),n.jsxs("div",{className:"term-main",children:[v&&n.jsx("div",{className:"drawer-backdrop",onClick:()=>w(!1)}),n.jsx("button",{type:"button",className:"term-side-toggle",title:a("commands"),onClick:()=>w(e=>!e),children:n.jsx(X,{})}),s.terminals.length===0?n.jsxs("div",{className:"term-empty",children:[n.jsx(G,{className:"term-empty-icon"}),n.jsx("div",{className:"term-empty-title",children:a("builtinTerminal")}),n.jsx("div",{className:"term-empty-sub",children:a("termEmptySub")})]}):s.terminals.map(e=>n.jsx(W,{conversationId:e.conversationId,terminalId:e.id,command:e.command,cwd:e.cwd,active:e.id===f,send:r,register:x.register},`${e.conversationId}:${e.id}`))]})]})}export{se as TerminalPanel};