pi-web-ui 0.96.0 → 0.96.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 CHANGED
@@ -10,6 +10,26 @@
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.96.1] — 2026-09-26
14
+
15
+ ### Fixed
16
+
17
+ - **插件安装审批弹窗层级提升与进度反馈补齐(#382)** —— 将能力与目录访问审批弹窗提升至高于设置弹窗的顶层浮层(`z-index: 350`),无需关闭设置页;修复「记住并允许」未持久化问题,授权写入 `plugin-permissions.json` 并在再次安装时直接放行;点击安装即刻反馈等待确认/执行中状态,已安装列表补挂进度条,关闭弹窗后主界面提供全局进度指示。
18
+ - **顶栏全部居中对齐时偏右修复** —— 修复当顶栏条目全部设为居中时,因右侧缺失占位弹簧(`tb-spacer`)导致条目被左侧单个弹簧推向最右侧的问题,确保中间条目精准水平居中。
19
+ - **设置保存回执时序修复** —— 确保 `set_settings` 在触发 `needsReload` 重启前先行持久化并推送状态回执。
20
+
21
+ ### Changed
22
+
23
+ - **朱批与朱批·夜主题界面精细化** —— 顶栏控件全面素色化(透明底、无边框,悬停现 hairline);消息折叠条内边距清零,折叠指示三角光学对齐左侧墨线;输入框边框微调,发送/停止按钮微调为圆形;输入框底部模型选择、思考强度、预设等按钮去边框透明底,悬停显示细边框。
24
+ - **输入框与顶栏控件排版微调** —— 顶栏 chip 统一幽灵化;输入框底部模型与思考强度 chip 尺寸微调收紧;思考强度 Chip 文案精简为 `{level}`,保留 tooltip 说明;预设选择器图标优化为 `FiSliders`。
25
+
26
+ <!-- auto-i18n:start -->
27
+ ### i18n
28
+
29
+ - 前端中文变更(1):`thinkingChip`
30
+ - 前端英文变更(1):`thinkingChip`
31
+ <!-- auto-i18n:end -->
32
+
13
33
  ## [0.96.0] — 2026-09-26
14
34
 
15
35
  ### Added
@@ -1293,7 +1313,8 @@ when?, children?}`,也收 `topbar` / `settings` 这类简写别名);宿主
1293
1313
  - 0.35.1(2026-08-27):编辑重问保留附件(#18)+ 全窗口拖放(#19)。
1294
1314
  - 0.29.0(2026-08-23):全局搜索弹窗(Ctrl+K)+ 消息列表惰性窗口化。
1295
1315
 
1296
- [Unreleased]: https://github.com/xing-shuyin/pi-web-ui/compare/v0.96.0...main
1316
+ [Unreleased]: https://github.com/xing-shuyin/pi-web-ui/compare/v0.96.1...main
1317
+ [0.96.1]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.96.1
1297
1318
  [0.96.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.96.0
1298
1319
  [0.95.0]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.95.0
1299
1320
  [0.94.1]: https://github.com/xing-shuyin/pi-web-ui/releases/tag/v0.94.1
@@ -48,7 +48,7 @@ import { validateClientId } from "./ws-client-id.js";
48
48
  import { PendingCommandQueue } from "./ws-pending-queue.js";
49
49
  import { installPack, isKnownPack, listPacks, loadServerStrings, readPackFile, removePack, unloadServerStrings, } from "./locales.js";
50
50
  import { PluginManager, resolvePluginClientFile, } from "./plugins.js";
51
- import { buildPluginJobArgs, inspectInstallSpec, PluginInstaller } from "./plugin-installer.js";
51
+ import { buildPluginJobArgs, confirmPluginInstall, inspectInstallSpec, PluginInstaller } from "./plugin-installer.js";
52
52
  import { syncPluginCatalog } from "./plugin-catalog-sync.js";
53
53
  import { McpBridge } from "./mcp-bridge.js";
54
54
  import { createMcpHotReload } from "./mcp-hot-reload.js";
@@ -1129,22 +1129,14 @@ function findDomConsentById(id) {
1129
1129
  }
1130
1130
  // ---------------------------------------------------------------------------
1131
1131
  // 插件安装的用户确认门(P0):plugin_catalog_sync 的 install:true 与 plugin_job
1132
- // 的 install/update 在真正动安装器之前必须拿到用户确认。第三方页面脚本可以直发
1133
- // 这两类消息,没有这道门就能借 WS 静默安装任意插件。复用 permissionRequester
1134
- // 的同一条「plugin_permission_request 弹窗 + 120s 超时视为拒绝」管线 —— 不新增
1135
- // 协议消息,将安装清单放在 reason 里展示;这里直接调 requester(不写授权表,
1136
- // remember 标志被忽略)。拒绝 / 超时 / 未接弹窗设施(无头 DSH)一律 fail-closed。
1132
+ // 的 install/update 在真正动安装器之前必须拿到用户确认。
1137
1133
  // ---------------------------------------------------------------------------
1138
- async function confirmPluginInstall(items) {
1139
- const ask = pluginMgr.permissionRequester;
1140
- if (!ask)
1141
- return false;
1142
- const list = items.map((x) => `${x.id} ← ${x.source}`).join("\n");
1143
- const ans = await ask("plugin-installer", {
1144
- family: "net",
1145
- reason: `安装确认:将安装/更新以下插件(id ← source):\n${list}\n拒绝或 120 秒未确认则不安装。`,
1134
+ async function confirmPluginInstallHelper(items) {
1135
+ return confirmPluginInstall(items, {
1136
+ permGrants: pluginMgr.permGrants,
1137
+ permissionRequester: pluginMgr.permissionRequester,
1138
+ onGrantsChanged: pushPluginPermissions,
1146
1139
  });
1147
- return ans.ok === true;
1148
1140
  }
1149
1141
  // 内置定时任务(issue #184):全局 <dataDir>/scheduler-tasks.json,TTL 与
1150
1142
  // client-state 同级;Agent 工具建的任务优先唤醒发起对话(issue #193:
@@ -2174,15 +2166,35 @@ wss.on("connection", (ws) => {
2174
2166
  // plugin_job;拒绝/超时直接回一条 done,让面板上的作业就地结束(不占
2175
2167
  // 安装锁、不弹「失败」之外的噪音)。卸载不在本门范围内(由面板本身发起)。
2176
2168
  if (jobAction === "install" || jobAction === "update") {
2177
- void confirmPluginInstall([{ id: pluginId, source: String(msg.source ?? "") }])
2169
+ const alreadyGranted = pluginMgr.permGrants.has("plugin-installer", "net", { host: "github.com" });
2170
+ if (!alreadyGranted) {
2171
+ send({
2172
+ type: "plugin_job",
2173
+ jobId,
2174
+ action: jobAction,
2175
+ pluginId,
2176
+ phase: "start",
2177
+ });
2178
+ send({
2179
+ type: "plugin_job",
2180
+ jobId,
2181
+ action: jobAction,
2182
+ pluginId,
2183
+ phase: "log",
2184
+ line: jobLang() === "zh" ? "等待确认安装授权…" : "Waiting for install confirmation…",
2185
+ });
2186
+ }
2187
+ void confirmPluginInstallHelper([{ id: pluginId, source: String(msg.source ?? "") }])
2178
2188
  .then((confirmed) => {
2179
2189
  if (!confirmed) {
2180
- jobDone(false, "用户未确认安装(拒绝或 120 秒超时)");
2190
+ jobDone(false, jobLang() === "zh"
2191
+ ? "用户未确认安装(拒绝或 120 秒超时)"
2192
+ : "Installation not confirmed (rejected or timed out after 120s)");
2181
2193
  return;
2182
2194
  }
2183
2195
  startPluginJob();
2184
2196
  })
2185
- .catch(() => jobDone(false, "安装确认流程异常"));
2197
+ .catch(() => jobDone(false, jobLang() === "zh" ? "安装确认流程异常" : "Installation confirmation error"));
2186
2198
  break;
2187
2199
  }
2188
2200
  startPluginJob();
@@ -2195,6 +2207,19 @@ wss.on("connection", (ws) => {
2195
2207
  done: async (ok, info) => {
2196
2208
  if (ok) {
2197
2209
  await reloadPluginsAndPush(jobLang);
2210
+ const isZh = jobLang() === "zh";
2211
+ const actionLabel = jobAction === "uninstall"
2212
+ ? isZh
2213
+ ? "卸载"
2214
+ : "uninstalled"
2215
+ : jobAction === "update"
2216
+ ? isZh
2217
+ ? "更新"
2218
+ : "updated"
2219
+ : isZh
2220
+ ? "安装"
2221
+ : "installed";
2222
+ cs?.emitNotice("info", `插件「${pluginId}」${actionLabel}完成`, `Plugin "${pluginId}" ${actionLabel} successfully`);
2198
2223
  }
2199
2224
  else if (info.error) {
2200
2225
  cs?.emitNotice("error", `插件操作失败:${info.error}`, `Plugin operation failed: ${info.error}`);
@@ -2354,7 +2379,7 @@ wss.on("connection", (ws) => {
2354
2379
  // 本地文件来源只允许工作区内(防任意路径文件探测 oracle)。
2355
2380
  workspaceRoot: cs?.cwd ?? CWD,
2356
2381
  // 安装确认门(P0):拒绝/超时只写目录不安装。
2357
- confirmInstall: (items) => confirmPluginInstall(items),
2382
+ confirmInstall: (items) => confirmPluginInstallHelper(items),
2358
2383
  }).then((r) => {
2359
2384
  if (r.installRefused) {
2360
2385
  cs?.emitNotice("warning", "目录已同步,但安装未获用户确认(拒绝或超时),未安装任何插件", "Catalog synced, but installation was not confirmed (denied or timed out) — nothing was installed");
@@ -315,3 +315,33 @@ export class PluginInstaller {
315
315
  this.currentJobId = null;
316
316
  }
317
317
  }
318
+ /**
319
+ * 插件安装的用户确认门(P0):plugin_catalog_sync 的 install:true 与 plugin_job
320
+ * 的 install/update 在真正动安装器之前必须拿到用户确认。
321
+ *
322
+ * 用户若选择「记住并允许」,授权存入 plugin-permissions.json(后续同类操作自动放行,
323
+ * 设置面板「能力授权」页可审计与撤销)。拒绝 / 超时 / 未接弹窗设施(无头 DSH)一律 fail-closed。
324
+ */
325
+ export async function confirmPluginInstall(items, deps) {
326
+ if (deps.permGrants?.has("plugin-installer", "net", { host: "github.com" })) {
327
+ return true;
328
+ }
329
+ const ask = deps.permissionRequester;
330
+ if (!ask)
331
+ return false;
332
+ const list = items.map((x) => `${x.id} ← ${x.source}`).join("\n");
333
+ const ans = await ask("plugin-installer", {
334
+ family: "net",
335
+ hosts: ["github.com"],
336
+ reason: `安装确认:将安装/更新以下插件(id ← source):\n${list}\n拒绝或 120 秒未确认则不安装。`,
337
+ });
338
+ if (ans.ok && ans.remember && deps.permGrants) {
339
+ deps.permGrants.grant("plugin-installer", "net", {
340
+ hosts: ["github.com"],
341
+ reason: "插件安装/更新确认",
342
+ remember: true,
343
+ });
344
+ deps.onGrantsChanged?.();
345
+ }
346
+ return ans.ok === true;
347
+ }
@@ -573,11 +573,11 @@ export class SettingsService {
573
573
  // 必须在 this.push() 前生效,保证 promptSnapshot 收集到的是最新的活跃工具集与完整提示词!
574
574
  if (toolGatingChanged)
575
575
  this.host.applyToolGating();
576
- if (needsReload)
577
- await this.applyRuntime();
578
576
  this.host.stateStore.saveSettings(this.host.clientId, this.settings);
579
577
  this.push();
580
578
  this.host.flushSnapshot();
579
+ if (needsReload)
580
+ await this.applyRuntime();
581
581
  }
582
582
  /** Save the CURRENT settings as a named preset (overwrites if exists). */
583
583
  async savePreset(name) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-web-ui",
3
- "version": "0.96.0",
3
+ "version": "0.96.1",
4
4
  "description": "Just open your browser to get all your work done — browser cockpit for AI coding agents (chat, code, files, terminal, Git in one tab)",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -149,11 +149,13 @@ body,
149
149
  letter-spacing: 0.06em;
150
150
  }
151
151
 
152
- /* 顶栏控件全部素色化:无底无框,hover 才现 hairline */
153
- .topbar .chip,
154
- .topbar .tb-tab,
155
- .topbar .panel-toggle,
156
- .topbar .plugin-topbar-item {
152
+ /* 顶栏控件全部素色化:无底无框,hover 才现 hairline。兜底所有 button /
153
+ * [role=button]——不管类名是谁(基线 chip / 插件注入 / capsule 等),顶栏里
154
+ * 一律裸图标,dropdown 触发器不许再带盒子。 */
155
+ .topbar button,
156
+ .topbar [role="button"],
157
+ .topbar a.chip,
158
+ .topbar .chip {
157
159
  background: transparent !important;
158
160
  border: 1px solid transparent !important;
159
161
  border-radius: 3px !important;
@@ -163,14 +165,20 @@ body,
163
165
  color 0.15s ease !important;
164
166
  }
165
167
 
166
- .topbar .chip:hover,
167
- .topbar .tb-tab:hover,
168
- .topbar .panel-toggle:hover,
169
- .topbar .plugin-topbar-item:hover {
168
+ .topbar button:hover,
169
+ .topbar [role="button"]:hover,
170
+ .topbar a.chip:hover,
171
+ .topbar .chip:hover {
170
172
  border-color: var(--border) !important;
171
173
  background: transparent !important;
172
174
  }
173
175
 
176
+ .topbar .dropdown {
177
+ background: transparent !important;
178
+ border: none !important;
179
+ box-shadow: none !important;
180
+ }
181
+
174
182
  /* ---- 2. 消息区:左缘墨线活动脊 + 文档化排版 ----
175
183
  * 列缘这条线让给脊线与输入框边框:脊线(虚线针脚)与输入框左边框同一条
176
184
  * 像素线,历史虚下来、接到输入框变实;消息正文整体右移 23px 挂在轨上。
@@ -469,6 +477,12 @@ div.chead[role="button"]:hover,
469
477
  border-bottom: 1px dotted var(--border-soft) !important;
470
478
  border-radius: 0 !important;
471
479
  box-shadow: none !important;
480
+ padding-left: 0 !important;
481
+ padding-right: 0 !important;
482
+ }
483
+
484
+ .msg-collapsed-action::before {
485
+ margin-left: 0 !important;
472
486
  }
473
487
 
474
488
  .msg-error {
@@ -487,7 +501,7 @@ div.chead[role="button"]:hover,
487
501
 
488
502
  .inputbox {
489
503
  background: var(--inputbox-bg) !important;
490
- border: 1px solid var(--border) !important;
504
+ border: none !important;
491
505
  border-radius: 3px !important;
492
506
  box-shadow: inset 3px 0 0 0 rgba(224, 90, 53, 0.4) !important;
493
507
  transition:
@@ -496,14 +510,14 @@ div.chead[role="button"]:hover,
496
510
  }
497
511
 
498
512
  .inputbox:focus-within {
499
- border-color: var(--accent) !important;
513
+ border: none !important;
500
514
  box-shadow: inset 3px 0 0 0 var(--accent) !important;
501
515
  }
502
516
 
503
517
  .btn.send {
504
518
  background: var(--send-blue) !important;
505
519
  color: #131313 !important;
506
- border-radius: 3px !important;
520
+ border-radius: 50% !important;
507
521
  box-shadow: none !important;
508
522
  font-weight: 600;
509
523
  }
@@ -515,7 +529,54 @@ div.chead[role="button"]:hover,
515
529
  .btn.stop {
516
530
  background: var(--stop-red) !important;
517
531
  color: #131313 !important;
532
+ border-radius: 50% !important;
533
+ }
534
+
535
+ /* 输入框底部模型选择、思考强度、预设等按钮:去边框、透明底,hover 现 hairline */
536
+ .composer-tools {
537
+ border-top: none !important;
538
+ }
539
+
540
+ .composer-tools .chip,
541
+ .composer-tools .capsule-segment {
542
+ font-size: 13px !important;
543
+ padding: 4px 10px !important;
544
+ gap: 6px !important;
545
+ background: transparent !important;
546
+ border: 1px solid transparent !important;
547
+ box-shadow: none !important;
518
548
  border-radius: 3px !important;
549
+ transition:
550
+ border-color 0.15s ease,
551
+ background 0.15s ease !important;
552
+ }
553
+
554
+ .composer-tools .model-thinking-capsule {
555
+ background: transparent !important;
556
+ border: none !important;
557
+ box-shadow: none !important;
558
+ padding: 0 !important;
559
+ }
560
+
561
+ .composer-tools .model-thinking-capsule .capsule-divider {
562
+ display: none !important;
563
+ }
564
+
565
+ .composer-tools .chip:hover,
566
+ .composer-tools .capsule-segment:hover {
567
+ border-color: var(--border) !important;
568
+ background: transparent !important;
569
+ }
570
+
571
+ .composer-tools-left {
572
+ gap: 6px !important;
573
+ }
574
+
575
+ .composer-tools .chip-model,
576
+ .composer-tools .chip-sub,
577
+ .composer-tools .capsule-model-name,
578
+ .composer-tools .capsule-thinking-text {
579
+ font-size: 12.5px !important;
519
580
  }
520
581
 
521
582
  /* ---- 9. 底栏 / 状态栏:hairline 收边 ---- */
@@ -606,8 +667,7 @@ div.chead[role="button"]:hover,
606
667
  0 16px 40px -12px rgba(0, 0, 0, 0.55) !important;
607
668
  }
608
669
 
609
- .dd-menu,
610
- .dropdown {
670
+ .dd-menu {
611
671
  background: var(--menu-bg) !important;
612
672
  border: 1px solid var(--border) !important;
613
673
  border-radius: 4px !important;
package/themes/zhupi.css CHANGED
@@ -154,11 +154,13 @@ body,
154
154
  letter-spacing: 0.06em;
155
155
  }
156
156
 
157
- /* 顶栏控件全部素色化:无底无框,hover 才现 hairline */
158
- .topbar .chip,
159
- .topbar .tb-tab,
160
- .topbar .panel-toggle,
161
- .topbar .plugin-topbar-item {
157
+ /* 顶栏控件全部素色化:无底无框,hover 才现 hairline。兜底所有 button /
158
+ * [role=button]——不管类名是谁(基线 chip / 插件注入 / capsule 等),顶栏里
159
+ * 一律裸图标,dropdown 触发器不许再带盒子。 */
160
+ .topbar button,
161
+ .topbar [role="button"],
162
+ .topbar a.chip,
163
+ .topbar .chip {
162
164
  background: transparent !important;
163
165
  border: 1px solid transparent !important;
164
166
  border-radius: 3px !important;
@@ -168,14 +170,20 @@ body,
168
170
  color 0.15s ease !important;
169
171
  }
170
172
 
171
- .topbar .chip:hover,
172
- .topbar .tb-tab:hover,
173
- .topbar .panel-toggle:hover,
174
- .topbar .plugin-topbar-item:hover {
173
+ .topbar button:hover,
174
+ .topbar [role="button"]:hover,
175
+ .topbar a.chip:hover,
176
+ .topbar .chip:hover {
175
177
  border-color: var(--border) !important;
176
178
  background: transparent !important;
177
179
  }
178
180
 
181
+ .topbar .dropdown {
182
+ background: transparent !important;
183
+ border: none !important;
184
+ box-shadow: none !important;
185
+ }
186
+
179
187
  /* ---- 2. 消息区:左缘墨线活动脊 + 文档化排版 ----
180
188
  * 列缘这条线让给脊线与输入框边框:脊线(虚线针脚)与输入框左边框同一条
181
189
  * 像素线,历史虚下来、接到输入框变实,像「线活了起来」;消息正文整体
@@ -481,6 +489,12 @@ div.chead[role="button"]:hover,
481
489
  border-bottom: 1px dotted var(--border-soft) !important;
482
490
  border-radius: 0 !important;
483
491
  box-shadow: none !important;
492
+ padding-left: 0 !important;
493
+ padding-right: 0 !important;
494
+ }
495
+
496
+ .msg-collapsed-action::before {
497
+ margin-left: 0 !important;
484
498
  }
485
499
 
486
500
  .msg-error {
@@ -499,7 +513,7 @@ div.chead[role="button"]:hover,
499
513
 
500
514
  .inputbox {
501
515
  background: var(--inputbox-bg) !important;
502
- border: 1px solid var(--border) !important;
516
+ border: none !important;
503
517
  border-radius: 3px !important;
504
518
  box-shadow: inset 3px 0 0 0 rgba(191, 59, 31, 0.35) !important;
505
519
  transition:
@@ -508,14 +522,14 @@ div.chead[role="button"]:hover,
508
522
  }
509
523
 
510
524
  .inputbox:focus-within {
511
- border-color: var(--accent) !important;
525
+ border: none !important;
512
526
  box-shadow: inset 3px 0 0 0 var(--accent) !important;
513
527
  }
514
528
 
515
529
  .btn.send {
516
530
  background: var(--send-blue) !important;
517
531
  color: #f6f2e9 !important;
518
- border-radius: 3px !important;
532
+ border-radius: 50% !important;
519
533
  box-shadow: none !important;
520
534
  font-weight: 600;
521
535
  }
@@ -527,7 +541,54 @@ div.chead[role="button"]:hover,
527
541
  .btn.stop {
528
542
  background: var(--stop-red) !important;
529
543
  color: #f6f2e9 !important;
544
+ border-radius: 50% !important;
545
+ }
546
+
547
+ /* 输入框底部模型选择、思考强度、预设等按钮:去边框、透明底,hover 现 hairline */
548
+ .composer-tools {
549
+ border-top: none !important;
550
+ }
551
+
552
+ .composer-tools .chip,
553
+ .composer-tools .capsule-segment {
554
+ font-size: 13px !important;
555
+ padding: 4px 10px !important;
556
+ gap: 6px !important;
557
+ background: transparent !important;
558
+ border: 1px solid transparent !important;
559
+ box-shadow: none !important;
530
560
  border-radius: 3px !important;
561
+ transition:
562
+ border-color 0.15s ease,
563
+ background 0.15s ease !important;
564
+ }
565
+
566
+ .composer-tools .model-thinking-capsule {
567
+ background: transparent !important;
568
+ border: none !important;
569
+ box-shadow: none !important;
570
+ padding: 0 !important;
571
+ }
572
+
573
+ .composer-tools .model-thinking-capsule .capsule-divider {
574
+ display: none !important;
575
+ }
576
+
577
+ .composer-tools .chip:hover,
578
+ .composer-tools .capsule-segment:hover {
579
+ border-color: var(--border) !important;
580
+ background: transparent !important;
581
+ }
582
+
583
+ .composer-tools-left {
584
+ gap: 6px !important;
585
+ }
586
+
587
+ .composer-tools .chip-model,
588
+ .composer-tools .chip-sub,
589
+ .composer-tools .capsule-model-name,
590
+ .composer-tools .capsule-thinking-text {
591
+ font-size: 12.5px !important;
531
592
  }
532
593
 
533
594
  /* ---- 9. 底栏 / 状态栏:hairline 收边 ---- */
@@ -659,8 +720,7 @@ div.chead[role="button"]:hover,
659
720
  0 16px 40px -12px rgba(38, 34, 24, 0.25) !important;
660
721
  }
661
722
 
662
- .dd-menu,
663
- .dropdown {
723
+ .dd-menu {
664
724
  background: var(--menu-bg) !important;
665
725
  border: 1px solid var(--border) !important;
666
726
  border-radius: 4px !important;
@@ -1,3 +1,3 @@
1
- import{r as e}from"./rolldown-runtime-hePW80VL.js";import{i as t,t as n}from"./react-CR5VJ85Q.js";import{a as r,c as i,d as a,f as o,g as s,h as c,i as l,l as u,m as d,o as f,p,r as m,s as h,t as g,u as _}from"./index-Do9RgJC3.js";import{n as v,t as y}from"./xterm-Cf5xxe4g.js";var b=e(t(),1);function x(e,t){return Math.abs(t)>10&&Math.abs(t)>Math.abs(e)*1.2}function S(e,t){return e-t}var C=n();function w(e){let t=null;return{clean:e.replace(/\r?\n?\[pi-term-exit:(-?\d+)\]\r?\n?/g,(e,n)=>(t=Number(n),`\r
1
+ import{r as e}from"./rolldown-runtime-hePW80VL.js";import{i as t,t as n}from"./react-CR5VJ85Q.js";import{a as r,c as i,d as a,f as o,g as s,h as c,i as l,l as u,m as d,o as f,p,r as m,s as h,t as g,u as _}from"./index-ByfoaDwp.js";import{n as v,t as y}from"./xterm-Cf5xxe4g.js";var b=e(t(),1);function x(e,t){return Math.abs(t)>10&&Math.abs(t)>Math.abs(e)*1.2}function S(e,t){return e-t}var C=n();function w(e){let t=null;return{clean:e.replace(/\r?\n?\[pi-term-exit:(-?\d+)\]\r?\n?/g,(e,n)=>(t=Number(n),`\r
2
2
  `)).replace(/\r?\n?\x1b\[90m\[(?:进程已退出,退出码 |Process exited with code )-?\d+\]\x1b\[0m\r?\n?/g,`\r
3
3
  `),exitCode:t}}function T({conversationId:e,terminalId:t,command:n,cwd:i,title:a,agentBash:o,active:s,running:c,exitCode:u,register:d}){let p=(0,b.useRef)(null),h=(0,b.useRef)(null),{locale:g}=f(),_=(0,b.useRef)(g);_.current=g;let T=n?JSON.stringify(n):``,E=(0,b.useRef)(c===!1);(0,b.useEffect)(()=>{let c=p.current;if(!c)return;let u=new v({theme:l(),fontFamily:`"SF Mono", "JetBrains Mono", ui-monospace, Menlo, Consolas, monospace`,fontSize:13,cursorBlink:!0,scrollback:8e3}),f=new y;u.loadAddon(f),u.open(c),h.current={term:u,fit:f},s&&u.focus();let g=()=>{u.options.theme=l()};window.addEventListener(m,g),u.attachCustomKeyEventHandler(e=>{if(e.type!==`keydown`)return!0;let t=e.key?.toLowerCase();if((e.ctrlKey||e.metaKey)&&t===`v`)return!1;if(e.ctrlKey&&!e.shiftKey&&!e.altKey&&t===`c`&&u.hasSelection()){let e=u.textarea;return e&&(e.value=u.getSelection(),e.select()),!1}return!0});let b=d(e,t,{write:e=>u.write(w(e).clean),dispose:()=>u.dispose()}),C=()=>{try{f.fit(),r({type:`terminal_resize`,terminalId:t,conversationId:e,cols:u.cols,rows:u.rows})}catch{}},T=requestAnimationFrame(()=>{try{f.fit()}catch{}E.current||r(n?{type:`run_command`,terminalId:t,conversationId:e,command:n,cols:u.cols,rows:u.rows}:{type:`terminal_create`,terminalId:t,title:a,locale:_.current,agentBash:o,conversationId:e,cwd:i,cols:u.cols,rows:u.rows})}),D=u.onData(n=>{r({type:`terminal_input`,terminalId:t,conversationId:e,data:n})}),O=u.element,k=null,A=0,j=0,M=0,N=!1,P=e=>{if(e.touches.length!==1){k=null,N=!1;return}let t=e.touches[0];k=t.identifier,A=t.clientX,j=t.clientY,M=t.clientY,N=!1},F=e=>{if(k===null||e.touches.length!==1)return;let t=e.touches[0];if(t.identifier!==k)return;if(!N){if(!x(t.clientX-A,t.clientY-j))return;N=!0}e.preventDefault();let n=S(M,t.clientY);M=t.clientY,n!==0&&typeof WheelEvent<`u`&&(c.querySelector(`.xterm-viewport`)??O)?.dispatchEvent(new WheelEvent(`wheel`,{bubbles:!0,cancelable:!0,deltaY:n,deltaMode:0,clientX:t.clientX,clientY:t.clientY}))},I=e=>{if(k!==null){if(e.touches.length>0){k=null,N=!1;return}k=null,N=!1}};O?.addEventListener(`touchstart`,P,{passive:!0}),O?.addEventListener(`touchmove`,F,{passive:!1}),O?.addEventListener(`touchend`,I),O?.addEventListener(`touchcancel`,I);let L=null;return typeof ResizeObserver<`u`&&(L=new ResizeObserver(()=>{c.offsetWidth>0&&c.offsetHeight>0&&C()}),L.observe(c)),()=>{cancelAnimationFrame(T),D.dispose(),O?.removeEventListener(`touchstart`,P),O?.removeEventListener(`touchmove`,F),O?.removeEventListener(`touchend`,I),O?.removeEventListener(`touchcancel`,I),window.removeEventListener(m,g),L?.disconnect(),b(),u.dispose(),h.current=null}},[e,t,T,d]),(0,b.useEffect)(()=>{if(!s)return;let n=requestAnimationFrame(()=>{let n=h.current;if(n){try{n.fit.fit(),r({type:`terminal_resize`,terminalId:t,conversationId:e,cols:n.term.cols,rows:n.term.rows})}catch{}n.term.focus()}});return()=>cancelAnimationFrame(n)},[s]);let{t:D}=f(),O=(0,b.useRef)(void 0);return(0,b.useEffect)(()=>{if(c===O.current||(O.current=c,c!==!1))return;let e=h.current;e&&e.term.write(`\r\n\x1b[90m${D(`exitBanner`,{code:u??``})}\x1b[0m\r\n`)},[c,t,u]),(0,C.jsx)(`div`,{ref:p,className:`term-xterm ${s?``:`hidden`}`})}var E={name:``,command:``,cwd:"${pwd}"};function D({chat:e,terminal:t,uiTerminalToolbar:n,onUiAction:l}){let f=h(),[m,v]=(0,b.useState)(null),[y,x]=(0,b.useState)(!1),[S,w]=(0,b.useState)(!1),[D,O]=(0,b.useState)(null),[k,A]=(0,b.useState)(E),[j,M]=(0,b.useState)(null),N=(0,b.useRef)(null),[P,F]=(0,b.useState)(!0),[I,L]=(0,b.useState)(null),[R,z]=(0,b.useState)(``);(0,b.useEffect)(()=>{e.terminals.length===0?v(null):e.terminals.some(e=>e.id===m)||v(e.terminals[e.terminals.length-1].id)},[e.terminals,m]),(0,b.useEffect)(()=>{e.terminalActiveId&&(v(e.terminalActiveId),x(!1))},[e.terminalActiveId]),(0,b.useEffect)(()=>()=>{N.current&&clearTimeout(N.current)},[]);let B=e.terminals.filter(e=>!e.agentBash),V=e.terminals.filter(e=>e.agentBash),H=n=>{if(!e.ready)return;let r=i(),a=e.activeConversationId||e.state?.conversationId||``;t.create({...n,id:r,conversationId:a,cols:n.cols??80,rows:n.rows??24,running:!0,exitCode:null}),v(r),x(!1)},U=()=>H({title:f(`terminalTitle`,{n:B.length+1}),cwd:e.state?.cwd??``}),W=n=>{let i=n.name||n.command,a=e.terminals.find(e=>e.title===i);if(a){t.restart(a.id),v(a.id),r({type:`run_command`,terminalId:a.id,conversationId:a.conversationId,command:n,cols:80,rows:24});return}H({title:i,cwd:e.state?.cwd??``,command:n})},G=n=>{let i=e.terminals.find(e=>e.id===n);if(i&&r({type:`terminal_kill`,terminalId:n,conversationId:i.conversationId}),t.close(n),m===n){let t=e.terminals.filter(e=>e.id!==n);v(t.length>0?t[t.length-1].id:null)}},K=e=>(0,C.jsxs)(`div`,{className:`term-tab ${e.id===m?`active`:``}`,children:[(0,C.jsxs)(`button`,{type:`button`,className:`term-tab-main`,title:`${e.cwd}${e.command?`\n> ${e.command.command}`:``}`,onClick:()=>{I||(v(e.id),x(!1))},children:[(0,C.jsx)(`span`,{className:`term-tab-dot ${e.running?`run`:`exit`}`}),(0,C.jsxs)(`span`,{className:`term-tab-title`,children:[I===e.id?(0,C.jsx)(`input`,{autoFocus:!0,className:`term-tab-rename-input`,value:R,placeholder:e.title,onClick:e=>e.stopPropagation(),onChange:e=>z(e.target.value),onKeyDown:t=>{if(t.stopPropagation(),t.key===`Enter`&&!t.nativeEvent.isComposing){let t=R.trim();t&&r({type:`rename_terminal`,terminalId:e.id,conversationId:e.conversationId,title:t}),L(null)}else t.key===`Escape`&&L(null)},onBlur:()=>L(null)}):e.title,!e.running&&(0,C.jsx)(`span`,{className:`term-tab-exit`,children:f(`exited`,{code:e.exitCode===null?``:` ${e.exitCode}`})})]})]}),(0,C.jsx)(`button`,{type:`button`,className:`term-tab-close term-tab-rename`,title:f(`renameTerminal`),onClick:t=>{t.stopPropagation(),z(e.title),L(e.id)},children:(0,C.jsx)(u,{})}),(0,C.jsx)(`button`,{type:`button`,className:`term-tab-close`,title:f(`closeTerminal`),onClick:()=>G(e.id),children:(0,C.jsx)(s,{})})]},e.id),q=()=>{w(!0),O(null),A(E)},J=t=>{let n=e.commands[t];n&&(w(!1),O(t),A({name:n.name,command:n.command,cwd:n.cwd??``}))},Y=()=>{w(!1),O(null)},X=()=>{let t=k.name.trim(),n=k.command.trim();if(!t||!n)return;let i={name:t,command:n,cwd:k.cwd.trim()||void 0},a=S?[...e.commands,i]:D===null?e.commands:e.commands.map((e,t)=>t===D?i:e);r({type:`save_commands`,commands:a}),Y()},Z=t=>{if(j===t){let n=e.commands.filter((e,n)=>n!==t);r({type:`save_commands`,commands:n}),M(null),N.current&&clearTimeout(N.current)}else M(t),N.current&&clearTimeout(N.current),N.current=setTimeout(()=>M(null),2500)},ee=S||D!==null,te=n===void 0?[`host:term-cmd-refresh`,`host:term-cmd-new`,`host:term-tab-new`].map(e=>({id:e,source:`host`})):n.filter(e=>!e.hidden),Q=(e,t)=>{let n=new Set(e);return te.filter(e=>n.has(e.id)||t&&e.source!==`host`)},$={"host:term-cmd-refresh":(0,C.jsx)(`button`,{type:`button`,className:`panel-refresh`,title:f(`rerun`),onClick:()=>r({type:`list_commands`}),children:(0,C.jsx)(p,{})}),"host:term-cmd-new":(0,C.jsx)(`button`,{type:`button`,className:`panel-new`,title:f(`newCommand`),onClick:q,children:(0,C.jsx)(o,{})}),"host:term-tab-new":(0,C.jsx)(`button`,{type:`button`,className:`panel-new`,title:f(`newTerminal`),onClick:U,children:(0,C.jsx)(o,{})})};return(0,C.jsxs)(`div`,{className:`terminal-view`,children:[(0,C.jsxs)(`aside`,{className:`term-side term-commands ${y?`open`:``}`,children:[(0,C.jsxs)(`div`,{className:`panel-header`,children:[(0,C.jsx)(`span`,{className:`panel-title`,children:f(`commands`)}),(0,C.jsx)(`div`,{className:`panel-header-actions`,children:g(Q([`host:term-cmd-refresh`,`host:term-cmd-new`],!1),$,l)})]}),(0,C.jsx)(`div`,{className:`panel-body`,children:ee?(0,C.jsxs)(`div`,{className:`cmd-form`,children:[(0,C.jsx)(`label`,{htmlFor:`cmd-name`,children:f(`name`)}),(0,C.jsx)(`input`,{id:`cmd-name`,className:`cmd-input`,value:k.name,placeholder:f(`exampleName`),autoFocus:!0,onChange:e=>A({...k,name:e.target.value})}),(0,C.jsx)(`label`,{htmlFor:`cmd-command`,children:f(`command`)}),(0,C.jsx)(`input`,{id:`cmd-command`,className:`cmd-input`,value:k.command,placeholder:f(`exampleCommand`),onChange:e=>A({...k,command:e.target.value}),onKeyDown:e=>{e.key===`Enter`&&!e.nativeEvent.isComposing&&X()}}),(0,C.jsxs)(`label`,{htmlFor:`cmd-cwd`,children:[f(`directory`),` `,(0,C.jsx)(`span`,{className:`cmd-hint`,children:f(`cwdHint`)})]}),(0,C.jsx)(`input`,{id:`cmd-cwd`,className:`cmd-input`,value:k.cwd,placeholder:"${pwd}",onChange:e=>A({...k,cwd:e.target.value}),onKeyDown:e=>{e.key===`Enter`&&!e.nativeEvent.isComposing&&X()}}),(0,C.jsxs)(`div`,{className:`cmd-form-actions`,children:[(0,C.jsx)(`button`,{type:`button`,className:`btn`,onClick:Y,children:f(`cancel`)}),(0,C.jsx)(`button`,{type:`button`,className:`btn primary`,disabled:!k.name.trim()||!k.command.trim(),onClick:X,children:f(`save`)})]})]}):(0,C.jsxs)(C.Fragment,{children:[e.commands.length===0&&(0,C.jsx)(`div`,{className:`panel-empty`,children:f(`noCommands`)}),e.commands.map((e,t)=>(0,C.jsxs)(`div`,{className:`cmd-item`,children:[(0,C.jsx)(`button`,{type:`button`,className:`cmd-run`,title:f(`clickToRun`),onClick:()=>W(e),children:(0,C.jsx)(a,{})}),(0,C.jsxs)(`button`,{type:`button`,className:`cmd-main`,title:f(`clickToRun`),onClick:()=>W(e),children:[(0,C.jsx)(`span`,{className:`cmd-name`,children:e.name}),(0,C.jsx)(`span`,{className:`cmd-command`,children:e.command}),e.cwd&&(0,C.jsx)(`span`,{className:`cmd-cwd`,children:e.cwd})]}),(0,C.jsx)(`button`,{type:`button`,className:`cmd-act`,title:f(`edit`),onClick:()=>J(t),children:(0,C.jsx)(u,{})}),(0,C.jsx)(`button`,{type:`button`,className:`cmd-act del ${j===t?`confirm`:``}`,title:f(`delete`),onClick:()=>Z(t),children:j===t?f(`confirmQ`):(0,C.jsx)(c,{})})]},t))]})}),(0,C.jsxs)(`div`,{className:`term-tabs-block`,children:[(0,C.jsxs)(`div`,{className:`panel-header`,children:[(0,C.jsx)(`span`,{className:`panel-title`,children:f(`terminal`)}),g(Q([`host:term-tab-new`],!0),$,l)]}),(0,C.jsxs)(`div`,{className:`panel-body`,children:[e.terminals.length===0&&(0,C.jsx)(`div`,{className:`panel-empty`,children:f(`noTerminal`)}),B.map(K),V.length>0&&(0,C.jsxs)(`div`,{className:`term-folder`,children:[(0,C.jsxs)(`button`,{type:`button`,className:`term-folder-header ${P?`open`:``}`,title:f(`aiBashGroup`),onClick:()=>F(e=>!e),children:[(0,C.jsx)(`span`,{className:`term-folder-caret`,children:P?`▾`:`▸`}),(0,C.jsx)(`span`,{className:`term-folder-title`,children:f(`aiBashGroup`)}),(0,C.jsx)(`span`,{className:`term-folder-count`,children:V.length})]}),P&&(0,C.jsx)(`div`,{className:`term-folder-body`,children:V.map(K)})]})]})]})]}),(0,C.jsxs)(`div`,{className:`term-main`,children:[y&&(0,C.jsx)(`div`,{className:`drawer-backdrop`,onClick:()=>x(!1)}),(0,C.jsx)(`button`,{type:`button`,className:`term-side-toggle`,title:f(`commands`),onClick:()=>x(e=>!e),children:(0,C.jsx)(_,{})}),e.terminals.length===0?(0,C.jsxs)(`div`,{className:`term-empty`,children:[(0,C.jsx)(d,{className:`term-empty-icon`}),(0,C.jsx)(`div`,{className:`term-empty-title`,children:f(`builtinTerminal`)}),(0,C.jsx)(`div`,{className:`term-empty-sub`,children:f(`termEmptySub`)})]}):e.terminals.map(e=>(0,C.jsx)(T,{conversationId:e.conversationId,terminalId:e.id,command:e.command,cwd:e.cwd,title:e.title,agentBash:e.agentBash,active:e.id===m,running:e.running,exitCode:e.exitCode,register:t.register},`${e.conversationId}:${e.id}`))]})]})}export{D as TerminalPanel};