zen-gitsync 2.16.51 → 2.17.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.
Files changed (119) hide show
  1. package/package.json +12 -2
  2. package/src/cli/ai/agent.js +2 -2
  3. package/src/cli/ai/images.js +5 -3
  4. package/src/cli/ai/modelSetup.js +1 -1
  5. package/src/cli/ai/sessionStore.js +4 -6
  6. package/src/config.js +178 -39
  7. package/src/gitCommit.js +8 -0
  8. package/src/ui/public/assets/AgentView-BsN9y4Kg.css +1 -0
  9. package/src/ui/public/assets/AgentView-YDATCFcA.js +4 -0
  10. package/src/ui/public/assets/AppVersionBadge-Bw4-tHkL.js +2 -0
  11. package/src/ui/public/assets/{AppVersionBadge-CJrDr7GV.css → AppVersionBadge-D5tJzJfa.css} +1 -1
  12. package/src/ui/public/assets/BranchSelector-BZTHIIaF.js +1 -0
  13. package/src/ui/public/assets/BranchSelector-T_rJm-JY.css +1 -0
  14. package/src/ui/public/assets/CommitForm-DKedPnu-.js +6 -0
  15. package/src/ui/public/assets/CommitForm-Oij8R6P-.css +1 -0
  16. package/src/ui/public/assets/CommonDialog-C_RjF9Mp.css +1 -0
  17. package/src/ui/public/assets/CommonDialog-OCekbVQM.js +1 -0
  18. package/src/ui/public/assets/EditorView-BgA-_8WV.css +1 -0
  19. package/src/ui/public/assets/EditorView-C5O3ihKq.js +1 -0
  20. package/src/ui/public/assets/FlowExecutionViewer-B5kh7VxS.css +1 -0
  21. package/src/ui/public/assets/FlowExecutionViewer-vIvDP419.js +1 -0
  22. package/src/ui/public/assets/FlowOrchestrationWorkspace-BAQDU9Jd.css +1 -0
  23. package/src/ui/public/assets/FlowOrchestrationWorkspace-SlVXZt60.js +21 -0
  24. package/src/ui/public/assets/LogList-DlPJsPy_.js +7 -0
  25. package/src/ui/public/assets/LogList-DlWwSZet.css +1 -0
  26. package/src/ui/public/assets/MindmapView-B1Go82pJ.js +1 -0
  27. package/src/ui/public/assets/MindmapView-CS2t30Ko.css +1 -0
  28. package/src/ui/public/assets/MonitorView-CphDOlQu.css +1 -0
  29. package/src/ui/public/assets/MonitorView-GQ34uXPs.js +1 -0
  30. package/src/ui/public/assets/ProjectStartupButton-0UNF9CeP.js +1 -0
  31. package/src/ui/public/assets/ProjectStartupButton-B7zvqRj_.css +1 -0
  32. package/src/ui/public/assets/RemoteManagerDialog-D51q3x7T.js +1 -0
  33. package/src/ui/public/assets/RemoteManagerDialog-bpwaVAvX.css +1 -0
  34. package/src/ui/public/assets/RemoteRepoCard-Bin9d3GZ.js +1 -0
  35. package/src/ui/public/assets/RemoteRepoCard-CDykPlPj.css +1 -0
  36. package/src/ui/public/assets/SourceMapView-DG_a_076.css +1 -0
  37. package/src/ui/public/assets/SourceMapView-OxlKvnEc.js +3 -0
  38. package/src/ui/public/assets/{SvgIcon-CDNsk4-Q.css → SvgIcon-BQ6bR7l0.css} +1 -1
  39. package/src/ui/public/assets/SvgIcon-BpKq_phu.js +1 -0
  40. package/src/ui/public/assets/UserInputNode-D2EDkZ_H.js +1 -0
  41. package/src/ui/public/assets/UserInputNode-DDtvv9Iq.css +1 -0
  42. package/src/ui/public/assets/WorkbenchView-DWMdGe8g.css +1 -0
  43. package/src/ui/public/assets/WorkbenchView-DyhIOjGD.js +13 -0
  44. package/src/ui/public/assets/{_plugin-vue_export-helper-N7dqbp6x.js → _plugin-vue_export-helper-BaSe6lmR.js} +3 -3
  45. package/src/ui/public/assets/configStore-CyAQ0a-U.js +1 -0
  46. package/src/ui/public/assets/element-plus-H7kW-grr.js +19 -0
  47. package/src/ui/public/assets/{flow-mindmap-wqtwJcDW.js → flow-mindmap-DEwSVurC.js} +12 -12
  48. package/src/ui/public/assets/index-BXdqTR9A.css +1 -0
  49. package/src/ui/public/assets/index-SjRTI8Of.js +46 -0
  50. package/src/ui/public/assets/office-docx-C67al1tG.js +31 -0
  51. package/src/ui/public/assets/{office-excel-5lGzd5Yy.js → office-excel-ffLec5k6.js} +1 -1
  52. package/src/ui/public/assets/office-pptx-vRGbnHIO.js +60 -0
  53. package/src/ui/public/assets/toolsStore-BcJP9HmK.js +1 -0
  54. package/src/ui/public/assets/{vendor-C6ntdXkR.js → vendor-Dwln8rZH.js} +39 -46
  55. package/src/ui/public/assets/vue-flow-BfBE1dKN.js +39 -0
  56. package/src/ui/public/index.html +13 -13
  57. package/src/ui/server/index.js +9 -0
  58. package/src/ui/server/routes/config.js +9 -6
  59. package/src/ui/server/routes/git/aiDiffSummary.js +2 -2
  60. package/src/ui/server/routes/gitOps.js +63 -7
  61. package/src/ui/server/routes/workbench/agentSessionStore.js +6 -9
  62. package/src/ui/server/routes/workbench/attachmentUtils.js +93 -0
  63. package/src/ui/server/routes/workbench/attachmentUtils.test.js +100 -0
  64. package/src/ui/server/routes/workbench/envContext.js +137 -0
  65. package/src/ui/server/routes/workbench/envContext.test.js +178 -0
  66. package/src/ui/server/routes/workbench/index.js +210 -8
  67. package/src/ui/server/routes/workbench/orchestratorStore.js +6 -0
  68. package/src/ui/server/routes/workbench/pathParity.test.js +73 -0
  69. package/src/ui/server/routes/workbench/projectRegistry.js +37 -16
  70. package/src/ui/server/routes/workbench/projectRegistry.test.js +62 -17
  71. package/src/ui/server/routes/workbench/shared.js +7 -2
  72. package/src/ui/server/routes/workbench/targetResolver.js +260 -0
  73. package/src/ui/server/routes/workbench/targetResolver.test.js +224 -0
  74. package/src/ui/server/routes/workbench/taskRunner.js +42 -0
  75. package/src/ui/server/utils/gitExitCode.js +116 -0
  76. package/src/ui/server/utils/gitExitCode.test.js +108 -0
  77. package/src/ui/server/utils/instanceRegistry.js +17 -6
  78. package/src/ui/public/assets/AgentView-8-xsOmE6.css +0 -1
  79. package/src/ui/public/assets/AgentView-DcTGw8-j.js +0 -4
  80. package/src/ui/public/assets/AppVersionBadge-CA5-POGA.js +0 -2
  81. package/src/ui/public/assets/BranchSelector-B0eLkAdv.css +0 -1
  82. package/src/ui/public/assets/BranchSelector-qn-iGHOI.js +0 -1
  83. package/src/ui/public/assets/CommitForm-CQ0K8Yy2.css +0 -1
  84. package/src/ui/public/assets/CommitForm-DHfyJ331.js +0 -6
  85. package/src/ui/public/assets/CommonDialog-DJBixJcI.js +0 -1
  86. package/src/ui/public/assets/CommonDialog-jP0E9mco.css +0 -1
  87. package/src/ui/public/assets/EditorView-BRWoa7--.js +0 -1
  88. package/src/ui/public/assets/EditorView-DZMLvQds.css +0 -1
  89. package/src/ui/public/assets/FlowExecutionViewer-YoPgld-R.js +0 -1
  90. package/src/ui/public/assets/FlowExecutionViewer-jNHI5zdm.css +0 -1
  91. package/src/ui/public/assets/FlowOrchestrationWorkspace-CYGp-PqS.css +0 -1
  92. package/src/ui/public/assets/FlowOrchestrationWorkspace-DyEjlCBW.js +0 -21
  93. package/src/ui/public/assets/LogList-BlJplIi1.css +0 -1
  94. package/src/ui/public/assets/LogList-_z4dIgvS.js +0 -7
  95. package/src/ui/public/assets/MindmapView-BQTqx842.css +0 -1
  96. package/src/ui/public/assets/MindmapView-BrkcAcDh.js +0 -2
  97. package/src/ui/public/assets/MonitorView-8TG_K4m3.js +0 -1
  98. package/src/ui/public/assets/MonitorView-DetlFFXq.css +0 -1
  99. package/src/ui/public/assets/ProjectStartupButton-BqLyrik7.js +0 -1
  100. package/src/ui/public/assets/ProjectStartupButton-QLPltCK5.css +0 -1
  101. package/src/ui/public/assets/RemoteManagerDialog-BauAeH2w.css +0 -1
  102. package/src/ui/public/assets/RemoteManagerDialog-LOVV6KrB.js +0 -4
  103. package/src/ui/public/assets/RemoteRepoCard-CG-jARTe.css +0 -1
  104. package/src/ui/public/assets/RemoteRepoCard-SQyKcXDG.js +0 -7
  105. package/src/ui/public/assets/SourceMapView-BNRjvgzH.css +0 -1
  106. package/src/ui/public/assets/SourceMapView-DckLynaL.js +0 -3
  107. package/src/ui/public/assets/SvgIcon-CvIX1KL7.js +0 -1
  108. package/src/ui/public/assets/UserInputNode-BVgQkB2l.css +0 -1
  109. package/src/ui/public/assets/UserInputNode-Bc124-BK.js +0 -1
  110. package/src/ui/public/assets/WorkbenchView-BtQkd9Oy.js +0 -37
  111. package/src/ui/public/assets/WorkbenchView-FdhmMI-b.css +0 -1
  112. package/src/ui/public/assets/configStore-NaVpBxPo.js +0 -1
  113. package/src/ui/public/assets/element-plus-Csu3CJKj.js +0 -39
  114. package/src/ui/public/assets/index-Ci3lbjk2.css +0 -1
  115. package/src/ui/public/assets/index-D9u0mCSi.js +0 -98
  116. package/src/ui/public/assets/office-docx-BKrAsewH.js +0 -32
  117. package/src/ui/public/assets/office-pptx-DKtkU_7r.js +0 -74
  118. package/src/ui/public/assets/toolsStore-CNDLLgUG.js +0 -1
  119. package/src/ui/public/assets/vue-flow-D7IxiTyP.js +0 -39
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zen-gitsync",
3
- "version": "2.16.51",
3
+ "version": "2.17.1",
4
4
  "description": "Auto commit, scheduled sync, and visual GUI for Git. Run `g` in any repo for one-key commit & push, AI commit messages, scheduled background sync, and a drag-and-drop workflow builder.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -24,7 +24,16 @@
24
24
  "test:watch": "node scripts/run-tests.cjs --watch",
25
25
  "verify:i18n": "node scripts/verify-i18n-keys.mjs",
26
26
  "verify:i18n:orphans": "node scripts/verify-i18n-keys.mjs --orphans",
27
+ "verify:config-concurrency": "node scripts/verify-config-concurrency.mjs",
28
+ "verify:data-dir-migration": "node scripts/verify-data-dir-migration.mjs",
29
+ "verify:config-split": "node scripts/verify-config-split.mjs",
27
30
  "verify:wb-dialogs": "node scripts/verify-workbench-dialogs.cjs",
31
+ "verify:wb-dispatch": "node scripts/verify-workbench-dispatch-attach.cjs",
32
+ "verify:wb-env-context": "node scripts/verify-workbench-env-context.mjs",
33
+ "verify:wb-overview": "node scripts/verify-workbench-overview.cjs",
34
+ "verify:wb-proj-branch": "node scripts/verify-workbench-proj-branch.cjs",
35
+ "verify:wb-proj-filter": "node scripts/verify-wb-proj-filter.cjs",
36
+ "verify:wb-responsive": "node scripts/verify-wb-responsive.cjs",
28
37
  "e2e": "cd src/ui/client && npx playwright test",
29
38
  "e2e:headed": "cd src/ui/client && npx playwright test --headed",
30
39
  "e2e:install": "cd src/ui/client && npx playwright install chromium",
@@ -55,7 +64,8 @@
55
64
  "change:nrm:lenovo-nexus": "nrm use lenovo-nexus",
56
65
  "npm:ls:g": "npm ls -g",
57
66
  "npm:2fa:get": "npm profile get",
58
- "npm:2fa:disable": "npm profile disable-2fa"
67
+ "npm:2fa:disable": "npm profile disable-2fa",
68
+ "verify:wb-select-popup": "node scripts/verify-native-select-popup.cjs"
59
69
  },
60
70
  "files": [
61
71
  "src/ui/public/**",
@@ -17,7 +17,7 @@
17
17
  // 入口: runAiAgent(argv)(由 gitCommit.js 在 argv[2]==='ai' 时动态 import)。
18
18
  //
19
19
  // 能力:
20
- // - 默认使用 g ui 里配置的模型(~/.git-commit-tool.json 顶层 models,
20
+ // - 默认使用 g ui 里配置的模型(~/.zen-gitsync/config.json 顶层 models,
21
21
  // isDefault 优先,否则取第一个);--model=<序号|名称> 或会话内 /model 切换
22
22
  // - OpenAI 兼容流式 function calling:模型可以调用 run_command /
23
23
  // read_file / write_file / edit_file / list_files / search_text
@@ -791,7 +791,7 @@ async function handleSlashCommand(state, input, t) {
791
791
  const { baseURL, model, apiKey, displayName } = collected
792
792
  // 构建模型配置(isDefault=false:/addmodel 只是追加,不改默认模型)
793
793
  const newModel = buildModelConfig({ baseURL, model, apiKey, name: displayName, isDefault: false })
794
- // 持久化到 ~/.git-commit-tool.json 顶层 models 数组
794
+ // 持久化到 ~/.zen-gitsync/config.json 顶层 models 数组
795
795
  const cfg = await config.loadConfig()
796
796
  const models = Array.isArray(cfg.models) ? cfg.models : []
797
797
  models.push(newModel)
@@ -15,7 +15,7 @@
15
15
  // g ai 图片附件 — 剪贴板图片读取 + 本地图片转 data URL。
16
16
  //
17
17
  // 触发方式:REPL 里按 Alt+V(或 /image <路径> 附加本地文件)。
18
- // 图片保存到 ~/.git-commit-tool/ai-images/,发送时编码为 base64 data URL,
18
+ // 图片保存到 ~/.zen-gitsync/ai-images/,发送时编码为 base64 data URL,
19
19
  // 以 OpenAI 多模态格式(image_url)拼进 user 消息。
20
20
  //
21
21
  // 平台实现:
@@ -29,10 +29,12 @@
29
29
 
30
30
  import { execFile } from 'node:child_process'
31
31
  import { promises as fsp } from 'node:fs'
32
- import os from 'node:os'
33
32
  import path from 'node:path'
33
+ import { AI_IMAGES_DIR } from '../../paths.js'
34
34
 
35
- const IMAGE_DIR = path.join(os.homedir(), '.git-commit-tool', 'ai-images')
35
+ // 数据目录统一在 ~/.zen-gitsync/ (见 src/paths.js);旧路径 ~/.git-commit-tool/ai-images
36
+ // 由 dataDirMigration 在首次启动时搬过来。
37
+ const IMAGE_DIR = AI_IMAGES_DIR
36
38
  const MAX_KEEP = 20 // 目录里最多保留的历史图片数(超出按时间最旧先删)
37
39
  const CLIP_TIMEOUT_MS = 15000 // 剪贴板读取超时(PowerShell 冷启动可能要几秒)
38
40
 
@@ -14,7 +14,7 @@
14
14
  //
15
15
  // g ai 交互式模型配置向导。
16
16
  //
17
- // 当 ~/.git-commit-tool.json 顶层 models 为空时,由 agent.js 调用本模块
17
+ // 当 ~/.zen-gitsync/config.json 顶层 models 为空时,由 agent.js 调用本模块
18
18
  // 引导用户在终端中完成首个 AI 模型的配置,参照 g ui 设置里的"添加模型"
19
19
  // 功能(ai-model-form 包的 AddModelForm 组件)。
20
20
  //
@@ -19,13 +19,11 @@
19
19
  // - CLI 对话在 Web UI 的智能体 tab 里可见(带 CLI 标记)
20
20
  // - Web UI 可以读取/继续 CLI 创建的会话
21
21
  //
22
- // 本模块刻意不依赖服务器代码,仅用 node 内置 fs/path/os,保持 CLI 自包含。
22
+ // 本模块刻意不依赖服务器代码,仅用 node 内置 fs/path + 共享的路径常量,保持 CLI 自包含。
23
23
 
24
- import fsp from 'fs/promises';
25
- import path from 'path';
26
- import os from 'os';
27
-
28
- const SESSIONS_DIR = path.join(os.homedir(), '.zen-gitsync', 'agent-sessions');
24
+ import { promises as fsp } from 'node:fs';
25
+ import path from 'node:path';
26
+ import { AGENT_SESSIONS_DIR as SESSIONS_DIR } from '../../paths.js';
29
27
  const MAX_SESSIONS = 200;
30
28
  const KEEP_SESSIONS = 100;
31
29
 
package/src/config.js CHANGED
@@ -14,11 +14,41 @@
14
14
  //
15
15
  import { promises as fs } from 'fs';
16
16
  import path from 'path';
17
- import os from 'os';
18
17
  import chalk from 'chalk';
19
18
  import { execSync } from 'child_process';
19
+ import { CONFIG_FILE } from './paths.js';
20
+ import { migrateDataDir } from './dataDirMigration.js';
21
+ import { atomicWriteText } from './fsAtomic.js';
22
+ import {
23
+ ensureSplitStore,
24
+ readSplitProjects,
25
+ writeSplitStore,
26
+ deleteProjectConfig,
27
+ isSplitActive,
28
+ splitMarkerExists,
29
+ } from './configSplit.js';
30
+
31
+ // 当前生效的配置文件路径。默认是统一数据目录下的 ~/.zen-gitsync/config.json;
32
+ // 只有历史文件搬迁失败(被占用/无权限)时才回退到旧的 ~/.git-commit-tool.json,
33
+ // 让应用至少还能以只读方式跑起来,下次启动再试迁移。
34
+ let configPath = CONFIG_FILE;
35
+ let _configPathReady = null;
20
36
 
21
- const configPath = path.join(os.homedir(), '.git-commit-tool.json');
37
+ /**
38
+ * 确保配置文件已就位(必要时执行一次性数据目录迁移),返回实际使用的路径。
39
+ * 惰性执行且只跑一次;迁移内部不抛错,这里的 catch 只是最后一道保险。
40
+ */
41
+ function resolveConfigPath() {
42
+ if (!_configPathReady) {
43
+ _configPathReady = migrateDataDir()
44
+ .then((report) => {
45
+ if (report?.configPath) configPath = report.configPath;
46
+ return configPath;
47
+ })
48
+ .catch(() => configPath);
49
+ }
50
+ return _configPathReady;
51
+ }
22
52
 
23
53
  // 默认配置
24
54
  const defaultConfig = {
@@ -150,8 +180,16 @@ async function statConfigSignature() {
150
180
  }
151
181
  }
152
182
 
153
- // 判断缓存是否仍新鲜。仅在 _rawConfigCache 非空时调用。
154
- async function isRawConfigCacheFresh() {
183
+ // 判断缓存是否仍新鲜。
184
+ //
185
+ // ⚠️ 必须通过参数接收快照,不能在函数内直接闭包引用模块级 _rawConfigCache:
186
+ // 本函数第一个 await(fs.stat)会让出事件循环,并发路径(writeRawConfigFile 尾部、
187
+ // fs.js 的 chdir)可能在这个窗口内调用 invalidateRawConfigCache() 把它置 null,
188
+ // 恢复执行后再解引用就抛 "Cannot read properties of null (reading 'existed')"。
189
+ // 该 await 在 safeLoadRaw 的 try 之外,错误会一路冒到 loadConfig 并被包成
190
+ // "系统配置文件JSON格式错误" —— 报错信息完全误导(磁盘上的文件其实是合法 JSON)。
191
+ async function isRawConfigCacheFresh(cache) {
192
+ if (!cache) return false;
155
193
  const sig = await statConfigSignature();
156
194
  if (sig === undefined) {
157
195
  // stat 异常(权限等):保守沿用缓存,避免把本来可读的状态打成错误
@@ -159,10 +197,10 @@ async function isRawConfigCacheFresh() {
159
197
  }
160
198
  if (sig === null) {
161
199
  // 文件当前不存在:缓存也说"不存在"才算新鲜;缓存有值但文件被外部删了 → 需重走读取路径
162
- return !_rawConfigCache.existed;
200
+ return !cache.existed;
163
201
  }
164
- if (!_rawConfigCache.existed) return false; // 外部进程新建了配置文件 → 需重读
165
- return sig.mtimeMs === _rawConfigCache.mtimeMs && sig.size === _rawConfigCache.size;
202
+ if (!cache.existed) return false; // 外部进程新建了配置文件 → 需重读
203
+ return sig.mtimeMs === cache.mtimeMs && sig.size === cache.size;
166
204
  }
167
205
 
168
206
  async function readRawConfigFile() {
@@ -183,37 +221,59 @@ function invalidateRawConfigCache() {
183
221
  }
184
222
 
185
223
  // 将原始配置对象写回磁盘
224
+ //
225
+ // 串行化(2026-09-18):同一进程内多个请求(锁定文件、布局比例、主题、最近目录、
226
+ // 模型配置…)都可能并发触发写盘。旧实现的 tmpPath 只带 `pid.毫秒时间戳` ——
227
+ // 同一毫秒内的两次写会**共用同一个 tmp 文件**:
228
+ // ① 先完成的 rename 把 tmp 搬走,后者的 rename 拿到 ENOENT/EPERM;
229
+ // ② 于是降级成"直接覆盖写",原子性丢失;
230
+ // ③ 两次 writeFile 交错写同一个 tmp 时,读者能 parse 到半截 JSON
231
+ // (实测报 "Unexpected non-whitespace character after JSON at position N")。
232
+ // 现在:进程内写队列串行 + tmp 名加自增序号(序号在 fsAtomic 里,多进程间 pid 天然不同)。
233
+ let _writeQueue = Promise.resolve();
234
+
235
+ /**
236
+ * 串行写入口。返回的 Promise 与本次写入的真实结果一致(失败也如实抛出),
237
+ * 但队列本身会吞掉失败继续跑 —— 一次写失败不能把后续写全部卡死。
238
+ */
239
+ function writeRawConfigFile(obj) {
240
+ const run = () => writeRawConfigFileInner(obj);
241
+ // then(run, run):前一个写 reject 时也要继续执行本次写
242
+ const p = _writeQueue.then(run, run);
243
+ _writeQueue = p.then(() => {}, () => {});
244
+ return p;
245
+ }
246
+
247
+ // 当前该按哪种模式落盘。原子写与"占用类错误重试"都收敛在 src/fsAtomic.js ——
248
+ // 分文件后写入点变成 1 + N + M 个,重试逻辑必须有唯一实现,否则迟早有一边漏掉
249
+ // EPERM/EACCES/EBUSY(那正是"并发读写偶发 500"的成因)。
250
+ //
251
+ // 模式判定优先用读路径已经定好的结论;_splitActive 还是 null(本进程还没读过
252
+ // 任何配置)时退化成看标记文件 —— 标记存在 = 拆分成功过 = 按分文件写。
253
+ async function resolveWriteMode() {
254
+ const active = isSplitActive();
255
+ if (active !== null) return active;
256
+ return splitMarkerExists();
257
+ }
258
+
186
259
  // 用 tmp + rename 原子写:避免拖拽高频触发 + 防抖期间 beforeunload 同时写入时
187
260
  // 两次 fs.writeFile 直接覆盖产生的"先 truncate 再写"的中间态空文件,
188
261
  // 触发 readRawConfigFile 在 race 时 JSON.parse 失败 → 500。
189
- async function writeRawConfigFile(obj) {
190
- const tmpPath = `${configPath}.${process.pid}.${Date.now()}.tmp`;
191
- const data = JSON.stringify(obj, null, 2);
192
- try {
193
- await fs.writeFile(tmpPath, data, 'utf-8');
194
- try {
195
- await fs.rename(tmpPath, configPath);
196
- } catch (err) {
197
- // Windows 上 rename 到已存在文件会抛 EPERM / EEXIST —— 杀毒软件扫描、
198
- // 文件索引器占用、多个实例并发写同一个配置都会命中。此时降级为直接覆盖写:
199
- // 数据完整性由 writeFile 保证,只是丢了 rename 的原子性(极端情况下
200
- // 可能留下 truncate 到一半的中间态,概率远低于整体写入失败)。
201
- //
202
- // 关键:降级写成功就算成功,**不能把 rename 的 err 再抛出去**。
203
- // 原实现在这里 throw err,导致数据其实已经落盘、调用方却收到异常,
204
- // 前端表现为"保存失败 500"。只有降级写本身失败时,下面的 await 才会抛,
205
- // 交给外层 catch 清理 tmp 并向上传递。
262
+ async function writeRawConfigFileInner(obj) {
263
+ await resolveConfigPath();
264
+ if (await resolveWriteMode()) {
265
+ // 分文件模式:项目/画布各自成文件,只有变更过的才重写;config.json 只留全局
266
+ // 设置(但每次都会写,它是别的实例判断"磁盘变了没"的唯一签名来源)
267
+ const res = await writeSplitStore(obj);
268
+ if (res.errors.length) {
206
269
  console.warn(chalk.yellow(
207
- `[config] 原子写降级为覆盖写(rename 失败: ${err?.code || err?.message || err})`
270
+ `[config] 分文件写盘有 ${res.errors.length} 个错误: ${res.errors.slice(0, 3).join('; ')}`
208
271
  ));
209
- try { await fs.unlink(tmpPath); } catch (_) {}
210
- await fs.writeFile(configPath, data, 'utf-8');
211
272
  }
212
- } catch (err) {
213
- // 写入失败时清理孤儿 tmp 文件,避免 ~/.git-commit-tool.json.*.tmp 堆积
214
- try { await fs.unlink(tmpPath); } catch (_) { /* ignore */ }
215
- throw err;
273
+ invalidateRawConfigCache();
274
+ return;
216
275
  }
276
+ await atomicWriteText(configPath, JSON.stringify(obj, null, 2));
217
277
  // 写盘成功后让缓存失效 — 下次 readRawConfigFile 走实盘,保证拿到最新值
218
278
  invalidateRawConfigCache();
219
279
  }
@@ -232,15 +292,53 @@ async function backupConfigFileIfExists() {
232
292
  }
233
293
  }
234
294
 
295
+ // 把 config.json 读出来的"内联对象"转成对外使用的 raw。
296
+ //
297
+ // 分文件模式(configSplit)下 config.json 只留全局设置,projects 是这里从
298
+ // projects/ + orchestration/ 组装回来的 —— 于是 loadConfig / readRawConfigFile /
299
+ // saveConfig 看到的形状与拆分前**完全一致**,调用点一行都不用改。
300
+ //
301
+ // ⚠️ 返回值的 `projects` 必须是对象,**哪怕是空的**。loadConfig 靠 `raw.projects`
302
+ // 是否存在来区分新旧两种结构(见下面的兼容分支),给出 undefined 会让它掉进
303
+ // "旧扁平格式"分支、把项目配置当顶层字段读 —— 表现为所有项目设置静默失效。
304
+ async function materializeRaw(inline) {
305
+ try {
306
+ const mode = await ensureSplitStore(inline, { configPath });
307
+ if (!mode.active) return mode.raw ?? inline;
308
+ const read = await readSplitProjects();
309
+ if (read.errors.length) {
310
+ // 单个项目/画布文件坏掉不阻断:其余照常可用,如实报告让用户知道少了什么
311
+ console.warn(chalk.yellow(
312
+ `[config] 读取分文件配置有 ${read.errors.length} 个问题(已跳过): ${read.errors.slice(0, 3).join('; ')}`
313
+ ));
314
+ }
315
+ return { ...inline, projects: read.ok ? read.projects : {} };
316
+ } catch (err) {
317
+ console.warn(chalk.yellow(
318
+ `[config] 分文件存储不可用,按内联模式处理: ${err?.code || err?.message}`
319
+ ));
320
+ return inline;
321
+ }
322
+ }
323
+
235
324
  // 更安全的读取,区分“文件不存在”和“解析失败”
236
325
  async function safeLoadRaw() {
326
+ await resolveConfigPath();
237
327
  // 命中缓存且签名未变(无外部进程写入)才直接返回,跳过 readFile + JSON.parse
238
- if (_rawConfigCache && await isRawConfigCacheFresh()) {
239
- return { ok: true, obj: _rawConfigCache.value, existed: _rawConfigCache.existed };
328
+ //
329
+ // 先取快照再判新鲜:判新鲜期间会让出事件循环,缓存可能被并发路径失效。
330
+ // 收尾再比对一次引用 —— 引用变了说明期间确实发生了写盘,此时不能返回旧快照
331
+ // (会读到"写之前的旧值"),直接落到下面重读实盘。
332
+ const cached = _rawConfigCache;
333
+ if (cached && await isRawConfigCacheFresh(cached) && _rawConfigCache === cached) {
334
+ return { ok: true, obj: cached.value, existed: cached.existed };
240
335
  }
241
336
  try {
242
337
  const data = await fs.readFile(configPath, 'utf-8');
243
- const obj = JSON.parse(data);
338
+ const inline = JSON.parse(data);
339
+ // 组装(必要时含一次性拆分)必须在取签名之前 —— 拆分本身会重写 config.json,
340
+ // 先取签名会缓存到一个已经失效的 mtime,导致下一次读误判为"外部没改"。
341
+ const obj = await materializeRaw(inline);
244
342
  // 读完后立刻取签名。取不到(stat 异常)则签名字段置 undefined ——
245
343
  // 之后若 stat 恢复可用,签名比对必然不命中 → 自动重读兜底;
246
344
  // 若 stat 持续异常,isRawConfigCacheFresh 会保守沿用缓存。
@@ -257,7 +355,11 @@ async function safeLoadRaw() {
257
355
  // 文件不存在:当作空对象,但可继续写入。
258
356
  // 缓存签名 mtimeMs:null —— 命中缓存时仍会 stat 一次以感知外部新建,
259
357
  // 比旧的"每次都 readFile 抛 ENOENT"便宜,比"永久信任缓存"及时。
260
- const empty = {};
358
+ //
359
+ // 仍然走一次 materializeRaw:config.json 没了不代表数据没了 ——
360
+ // 分文件模式下项目配置在 projects/ 里,直接返回 `{}` 会让用户看到
361
+ // "项目全消失了",而磁盘上其实一份不少。
362
+ const empty = await materializeRaw({});
261
363
  _rawConfigCache = { value: empty, existed: false, mtimeMs: null, size: null };
262
364
  return { ok: true, obj: empty, existed: false };
263
365
  }
@@ -267,6 +369,18 @@ async function safeLoadRaw() {
267
369
  }
268
370
  }
269
371
 
372
+ /**
373
+ * 判断错误是否**真的**是 JSON 解析失败。
374
+ *
375
+ * 非解析类错误(IO 异常、内部竞态)如果也套上"JSON 格式错误"的帽子,会把排查
376
+ * 方向彻底带偏 —— 2026-09-18 那次缓存竞态就是这样伪装成
377
+ * "系统配置文件JSON格式错误…原因: Cannot read properties of null",而磁盘上的
378
+ * 文件其实是合法 JSON,用户照着提示去修文件只能白忙。
379
+ */
380
+ function isJsonParseError(err) {
381
+ return err instanceof SyntaxError || err?.name === 'SyntaxError';
382
+ }
383
+
270
384
  // 异步读取配置文件
271
385
  async function loadConfig() {
272
386
  const key = getCurrentProjectKey();
@@ -275,7 +389,10 @@ async function loadConfig() {
275
389
  raw = await readRawConfigFile();
276
390
  } catch (err) {
277
391
  const msg = err?.message ? String(err.message) : String(err);
278
- throw new Error(`系统配置文件JSON格式错误,请修复后重试。\n文件: ${configPath}\n原因: ${msg}`);
392
+ const head = isJsonParseError(err)
393
+ ? '系统配置文件JSON格式错误,请修复后重试。'
394
+ : '读取系统配置文件失败。';
395
+ throw new Error(`${head}\n文件: ${configPath}\n原因: ${msg}`);
279
396
  }
280
397
  // 兼容旧版(全局扁平结构)
281
398
  if (raw && !raw.projects) {
@@ -322,8 +439,11 @@ async function saveConfig(config) {
322
439
  if (!state.ok) {
323
440
  const cause = state.error;
324
441
  const detail = cause?.message ? String(cause.message) : String(cause);
442
+ const head = isJsonParseError(cause)
443
+ ? '解析配置文件失败,已取消写入以避免覆盖。'
444
+ : '读取配置文件失败,已取消写入以避免覆盖。';
325
445
  throw new ConfigWriteError(
326
- `解析配置文件失败,已取消写入以避免覆盖。\n文件: ${configPath}\n原因: ${detail}`,
446
+ `${head}\n文件: ${configPath}\n原因: ${detail}`,
327
447
  cause
328
448
  );
329
449
  }
@@ -448,8 +568,11 @@ async function saveRecentDirectory(dirPath) {
448
568
  if (!state.ok) {
449
569
  const cause = state.error;
450
570
  const detail = cause?.message ? String(cause.message) : String(cause);
571
+ const head = isJsonParseError(cause)
572
+ ? '解析配置文件失败,已取消写入最近目录以避免覆盖。'
573
+ : '读取配置文件失败,已取消写入最近目录以避免覆盖。';
451
574
  throw new ConfigWriteError(
452
- `解析配置文件失败,已取消写入最近目录以避免覆盖。\n文件: ${configPath}\n原因: ${detail}`,
575
+ `${head}\n文件: ${configPath}\n原因: ${detail}`,
453
576
  cause
454
577
  );
455
578
  }
@@ -509,6 +632,19 @@ async function handleConfigCommands() {
509
632
  process.exit();
510
633
  }
511
634
  }
635
+ /**
636
+ * 显式删除某个项目的全部分文件。
637
+ *
638
+ * ⚠️ 必须顺带失效读缓存 —— 删的是 projects/ 下的文件,config.json 本身没动,
639
+ * 而缓存新鲜度只看 config.json 的 mtime+size。不失效的话删完再读会把刚删掉的项目
640
+ * 从旧快照里"读回来",用户看到"删了没反应"。
641
+ */
642
+ async function deleteProjectConfigAndInvalidate(key) {
643
+ const res = await deleteProjectConfig(key);
644
+ invalidateRawConfigCache();
645
+ return res;
646
+ }
647
+
512
648
  export default {
513
649
  loadConfig,
514
650
  saveConfig,
@@ -522,7 +658,10 @@ export default {
522
658
  saveRecentDirectory,
523
659
  removeRecentDirectory,
524
660
  readRawConfigFile,
525
- writeRawConfigFile
661
+ writeRawConfigFile,
662
+ // 显式删除某个项目的全部分文件。写路径刻意不做删除(防"只加载了一个项目就写回"
663
+ // 连带清空其它项目),清理失效项目只能走这里。
664
+ deleteProjectConfig: deleteProjectConfigAndInvalidate,
526
665
  };
527
666
 
528
667
  // 命名导出 — 用于测试与外部复用
package/src/gitCommit.js CHANGED
@@ -236,6 +236,14 @@ async function handleFileLockCommands() {
236
236
  async function main() {
237
237
  judgePlatform()
238
238
 
239
+ // 数据目录布局迁移(幂等、永不抛错):把散在主目录的历史文件收进 ~/.zen-gitsync/。
240
+ // 放在最前面是为了让所有子命令(ui / ai / 定时提交)都跑在同一种目录布局上;
241
+ // config.js 首次读写时也会惰性兜底调一次,这里只是把时机提前到"最确定"的位置。
242
+ // 动态 import:迁移模块只依赖 paths + 零依赖 logger,但仍不该拖慢 CLI 冷启动路径。
243
+ try {
244
+ await (await import('./dataDirMigration.js')).migrateDataDir()
245
+ } catch (_) { /* 迁移失败不影响主流程 */ }
246
+
239
247
  // 一次性注册 SIGINT 处理器 — 所有长跑流程(定时 commit / 自定义命令循环 /
240
248
  // 倒计时)都通过 registerCleanup 上报,SIGINT 时由统一处理器 drain。
241
249
  setupSigintHandler({
@@ -0,0 +1 @@
1
+ .agent-view[data-v-773baa74]{background:var(--bg-container);height:100%;min-height:0;display:flex}.agent-sidebar[data-v-773baa74]{background:var(--bg-panel);border-right:1px solid var(--border-color);flex-direction:column;flex-shrink:0;display:flex;overflow:hidden}.sidebar-header[data-v-773baa74]{flex-shrink:0;padding:10px 12px}.new-session-btn[data-v-773baa74]{border:1px dashed var(--border-color);border-radius:var(--radius-md);width:100%;color:var(--text-secondary);cursor:pointer;background:0 0;justify-content:center;align-items:center;gap:6px;padding:8px 12px;font-family:inherit;font-size:13px;transition:all .15s;display:flex}.new-session-btn[data-v-773baa74]:hover{border-color:var(--color-primary);color:var(--color-primary);background:color-mix(in srgb, var(--color-primary) 6%, transparent)}.sidebar-search[data-v-773baa74]{flex-shrink:0;align-items:center;gap:6px;padding:0 12px 8px;display:flex}.sidebar-search .search-icon[data-v-773baa74]{color:var(--text-tertiary);flex-shrink:0}.sidebar-search .search-input[data-v-773baa74]{color:var(--text-primary);background:0 0;border:none;outline:none;flex:1;padding:4px 0;font-family:inherit;font-size:13px}.sidebar-search .search-input[data-v-773baa74]::placeholder{color:var(--text-tertiary)}.session-list[data-v-773baa74]{flex:1;min-height:0;padding:0 6px 8px;overflow-y:auto}.empty-state[data-v-773baa74]{text-align:center;color:var(--text-tertiary);flex-direction:column;justify-content:center;align-items:center;padding:48px 16px;display:flex}.empty-state .empty-text[data-v-773baa74]{color:var(--text-secondary);margin-top:10px;font-size:14px}.empty-state .empty-hint[data-v-773baa74]{margin-top:4px;font-size:12px}.session-item[data-v-773baa74]{border-radius:var(--radius-sm);cursor:pointer;align-items:flex-start;gap:4px;padding:8px 10px;transition:background .12s;display:flex;position:relative}.session-item[data-v-773baa74]:hover{background:var(--bg-hover)}.session-item:hover .session-item-actions[data-v-773baa74]{opacity:1}.session-item.active[data-v-773baa74]{background:color-mix(in srgb, var(--color-primary) 10%, transparent)}.session-item.active .session-item-title[data-v-773baa74]{color:var(--color-primary)}.session-item.active[data-v-773baa74]:before{content:"";background:var(--color-primary);border-radius:0 2px 2px 0;width:3px;height:20px;position:absolute;top:50%;left:0;transform:translateY(-50%)}.session-item-main[data-v-773baa74]{flex:1;min-width:0}.session-item-title[data-v-773baa74]{color:var(--text-primary);white-space:nowrap;text-overflow:ellipsis;margin-bottom:2px;font-size:13px;font-weight:500;overflow:hidden}.session-item-meta[data-v-773baa74]{color:var(--text-tertiary);align-items:center;gap:4px;font-size:11px;display:flex}.session-item-meta .meta-dot[data-v-773baa74]{opacity:.5}.session-item-meta .meta-source[data-v-773baa74]{border-radius:3px;padding:0 4px;font-size:10px;font-weight:600}.session-item-meta .meta-source.cli[data-v-773baa74]{background:color-mix(in srgb, var(--color-warning) 20%, transparent);color:var(--color-warning)}.session-item-actions[data-v-773baa74]{opacity:0;flex-shrink:0;gap:2px;transition:opacity .12s;display:flex}.item-action-btn[data-v-773baa74]{width:24px;height:24px;color:var(--text-tertiary);cursor:pointer;border-radius:var(--radius-xs);background:0 0;border:none;justify-content:center;align-items:center;padding:0;transition:all .12s;display:flex}.item-action-btn[data-v-773baa74]:hover{background:var(--bg-hover);color:var(--text-secondary)}.item-action-btn.danger[data-v-773baa74]:hover{color:var(--color-danger);background:color-mix(in srgb, var(--color-danger) 10%, transparent)}.sidebar-resizer[data-v-773baa74]{cursor:col-resize;background:0 0;flex-shrink:0;width:4px;transition:background .15s}.sidebar-resizer[data-v-773baa74]:hover,.sidebar-resizer.active[data-v-773baa74]{background:var(--color-primary);opacity:.3}.agent-chat-area[data-v-773baa74]{flex-direction:column;flex:1;min-width:0;display:flex;position:relative;overflow:hidden}.chat-loading[data-v-773baa74]{color:var(--text-tertiary);flex-direction:column;flex:1;justify-content:center;align-items:center;gap:12px;font-size:14px;display:flex}.stop-button-bar[data-v-773baa74]{z-index:10;position:absolute;bottom:80px;left:50%;transform:translate(-50%)}.stop-button[data-v-773baa74]{border:1px solid var(--color-danger);background:var(--bg-container);color:var(--color-danger);cursor:pointer;border-radius:20px;align-items:center;gap:6px;padding:6px 16px;font-family:inherit;font-size:13px;transition:all .15s;display:flex;box-shadow:0 2px 8px #0000001f}.stop-button[data-v-773baa74]:hover{background:var(--color-danger);color:#fff}.fade-enter-active[data-v-773baa74],.fade-leave-active[data-v-773baa74]{transition:opacity .2s}.fade-enter-from[data-v-773baa74],.fade-leave-to[data-v-773baa74]{opacity:0}[data-theme=dark]{background:var(--bg-panel)}
@@ -0,0 +1,4 @@
1
+ import"./rolldown-runtime-BM3Ffeng.js";import{Cr as e,Dn as t,En as n,Fn as r,G as i,Hn as a,Kr as o,Lt as s,Nn as c,On as l,Tn as u,Ut as d,Wr as f,Yn as p,Yr as m,Zn as h,_r as g,a as _,bn as ee,et as te,gn as ne,hr as v,ht as re,i as y,ir as b,kt as x,lt as ie,on as S,or as C,r as w,sn as ae,sr as T,wn as E,yn as oe}from"./element-plus-H7kW-grr.js";import{a as se,r as D,t as O}from"./vendor-Dwln8rZH.js";import{n as k,t as A}from"./_plugin-vue_export-helper-BaSe6lmR.js";import{t as j}from"./configStore-CyAQ0a-U.js";var M=64*1024;function N(e){if(!e||!Array.isArray(e.messages))return[];let t=[],n=e.messages.length;for(let r=0;r<n;r++){let i=e.messages[r];if(i.role!==`system`){if(i.role===`user`){let e=``,n;if(typeof i.content==`string`)e=i.content;else if(Array.isArray(i.content)){e=i.content.filter(e=>e?.type===`text`).map(e=>e.text||``).join(` `);let t=i.content.filter(e=>e?.type===`image_url`&&typeof e.image_url?.url==`string`).map((e,t)=>({id:`att-${r}-${t}`,name:`image`,size:0,type:I(e.image_url?.url||``)||`image/*`,preview:e.image_url?.url}));t.length>0&&(n=t)}(e||n)&&t.push({id:`msg-${r}`,role:`user`,content:e,attachments:n,status:`done`,createdAt:Date.now()})}else if(i.role===`assistant`){let a=``;typeof i.content==`string`&&(a=i.content),a.length>M&&(a=`…(前文已截断)\n${a.slice(-M)}`);let o=[];if(i.tool_calls&&i.tool_calls.length>0)for(let e of i.tool_calls)o.push({id:e.id||e.function?.name||D(),name:e.function?.name||``,arguments:e.function?.arguments||``,argsPreview:P(e.function?.name||``,e.function?.arguments||``),status:`done`,result:``});let s=r+1;for(;s<n&&e.messages[s].role===`tool`;){let t=e.messages[s],n=t.tool_call_id||t.name||``,i=o.find(e=>e.id===n);if(i){let e=t.content;typeof e!=`string`&&(e=`(non-text result)`),e.length>M&&(e=`…(前文已截断)\n${e.slice(-M)}`),i.result=e}r=s,s++}let c=!!a,l=o.length>0;(c||l)&&t.push({id:`msg-${r}`,role:`assistant`,content:a||``,toolCalls:l?o:void 0,status:`done`,createdAt:Date.now()})}}}return t}function P(e,t){try{let n=JSON.parse(t);switch(e){case`run_command`:return String(n.command||``).slice(0,200);case`read_file`:case`write_file`:case`edit_file`:return String(n.path||``);case`list_files`:return String(n.path||`.`);case`search_text`:return String(n.pattern||``);default:return JSON.stringify(n).slice(0,200)}}catch{return t.slice(0,200)}}function F(e){return new Promise((t,n)=>{let r=new FileReader;r.onload=()=>t(String(r.result||``)),r.onerror=()=>n(r.error||Error(`FileReader failed`)),r.readAsDataURL(e)})}function I(e){let t=/^data:([^;,]+)/.exec(e);return t?t[1]:``}function ce(){let e=j(),t=g([]),n=g(!1),r=g(null),i=g([]),a=g(!1),o=g(!1),s=null,c=0,l=0;async function u(){let r=++l,i=e.currentDirectory||``;if(!i){t.value=[],n.value=!1;return}n.value=!0;try{let e=`/api/agent/sessions?cwd=${encodeURIComponent(i)}`,n=await fetch(e).then(e=>e.json());if(r!==l)return;if(!n.success){y.error(n.error||k(`@AGENT:加载会话列表失败`));return}t.value=Array.isArray(n.sessions)?n.sessions:[]}catch(e){if(r!==l)return;y.error(k(`@AGENT:加载会话列表失败`)+`: `+(e?.message||e))}finally{r===l&&(n.value=!1)}}async function d(e){o.value=!0,r.value=e;try{let t=await fetch(`/api/agent/sessions/${encodeURIComponent(e)}`).then(e=>e.json());if(!t.success){y.error(t.error||k(`@AGENT:加载会话失败`));return}i.value=N(t.session)}catch(e){y.error(k(`@AGENT:加载会话失败`)+`: `+(e?.message||e))}finally{o.value=!1}}async function f(e){try{await w.confirm(k(`@AGENT:确认删除该会话`),k(`@AGENT:删除后无法恢复`),{type:`warning`})}catch{return}try{let n=await fetch(`/api/agent/sessions/${encodeURIComponent(e)}`,{method:`DELETE`}).then(e=>e.json());if(!n.success){y.error(n.error||k(`@AGENT:删除失败`));return}t.value=t.value.filter(t=>t.sessionId!==e),r.value===e&&(r.value=null,i.value=[]),y.success(k(`@AGENT:已删除`))}catch(e){y.error(k(`@AGENT:删除失败`)+`: `+(e?.message||e))}}async function p(e,n){try{let r=await fetch(`/api/agent/sessions/${encodeURIComponent(e)}`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({title:n})}).then(e=>e.json());if(!r.success){y.error(r.error||k(`@AGENT:重命名失败`));return}let i=t.value.find(t=>t.sessionId===e);i&&(i.title=n),y.success(k(`@AGENT:已重命名`))}catch(e){y.error(k(`@AGENT:重命名失败`)+`: `+(e?.message||e))}}function m(){r.value=null,i.value=[]}async function h(t,n=[]){let o=n.filter(e=>e?.file?.type?.startsWith(`image/`));if(o.length<n.length&&y.warning(k(`@AGENT:仅支持发送图片,非图片文件已忽略`)),!t.trim()&&o.length===0||a.value)return;let l=(await Promise.allSettled(o.map(e=>F(e.file)))).map(e=>e.status===`fulfilled`?e.value:``).filter(e=>e.startsWith(`data:image/`)),d=++c;s=new AbortController;let f=s,p={id:D(),role:`user`,content:t,status:`done`,createdAt:Date.now()};o.length>0&&(p.attachments=o.map(e=>({id:e.id,name:e.file.name||`image`,size:e.file.size||0,type:e.file.type||``,preview:e.preview}))),i.value.push(p);let m=v({id:D(),role:`assistant`,content:``,status:`pending`,createdAt:Date.now()});i.value.push(m),a.value=!0;let h=[];try{let n=await fetch(`/api/agent/chat`,{method:`POST`,headers:{"Content-Type":`application/json`,Accept:`text/event-stream`},body:JSON.stringify({sessionId:r.value||``,userMessage:t,cwd:e.currentDirectory||``,...l.length>0?{images:l}:{}}),signal:f.signal});if(d!==c)return;if(!n.ok||!n.body){let e=await n.text().catch(()=>``);throw Error(e||`HTTP ${n.status}`)}let a=n.body.getReader(),o=new TextDecoder(`utf-8`),s=``,p=r.value;for(;;){let{value:e,done:t}=await a.read();if(d!==c)return;if(t)break;s+=o.decode(e,{stream:!0});let n=s.split(`
2
+ `);s=n.pop()??``;for(let e of n){let t=e.trim();if(!t.startsWith(`data:`))continue;let n=t.slice(5).trim();if(!n)continue;let i;try{i=JSON.parse(n)}catch{continue}switch(i.type){case`meta`:i.sessionId&&(p=i.sessionId,r.value=i.sessionId);break;case`thinking`:m.reasoning||(m.reasoning=``,m.reasoningStatus=`streaming`),m.reasoning+=String(i.delta||``),m.status=`streaming`;break;case`content`:m.status===`pending`&&(m.status=`streaming`);{let e=String(i.delta||``);m.content+=e;let t=O(m.content);t.reasoning&&(m.reasoning||(m.reasoning=``,m.reasoningStatus=`streaming`),m.reasoning=t.reasoning,m.content=t.content)}break;case`tool_call_start`:{let e=v({id:i.toolCallId||D(),name:i.name||``,argsPreview:i.argsPreview||``,arguments:i.argsPreview||``,status:`running`,result:``});h.push(e),m.toolCalls||=[],m.toolCalls.push(e),m.status===`pending`&&(m.status=`streaming`);break}case`tool_result`:{let e=h.find(e=>e.id===i.toolCallId);e&&(e.result=String(i.result||``),e.status=`done`);break}case`done`:{let e=O(i.content||m.content);e.reasoning&&!m.reasoning&&(m.reasoning=e.reasoning),m.content=e.content,m.status=`done`,m.reasoningStatus===`streaming`&&(m.reasoningStatus=`done`);break}case`error`:m.status=`error`,m.error=String(i.error||k(`@AGENT:未知错误`)),m.reasoningStatus===`streaming`&&(m.reasoningStatus=`done`);break}}i.value=[...i.value]}if(d!==c)return;m.status===`pending`&&(m.status=`done`),p&&p!==r.value&&(r.value=p),u().catch(()=>{})}catch(e){if(d!==c)return;e?.name===`AbortError`||f.signal.aborted?(m.content=(m.content||``)+`
3
+
4
+ [`+k(`@AGENT:已停止`)+`]`,m.status=`done`,m.reasoningStatus===`streaming`&&(m.reasoningStatus=`done`)):(m.status=`error`,m.error=e?.message||String(e),y.error(m.error||k(`@AGENT:对话失败`)))}finally{d===c&&(a.value=!1,s=null)}}function _(){s&&=(s.abort(),null)}return{sessions:t,sessionsLoading:n,currentSessionId:r,messages:i,isStreaming:a,sessionLoading:o,loadSessions:u,loadSession:d,deleteSession:f,renameSession:p,newSession:m,sendMessage:h,stop:_}}var le={class:`agent-view`},ue={class:`sidebar-header`},de={class:`sidebar-search`},L=[`placeholder`],R={class:`session-list`},z={key:0,class:`empty-state`},B={class:`empty-text`},V={key:0,class:`empty-hint`},H=[`onClick`],fe={class:`session-item-main`},pe={class:`session-item-title`},U={class:`session-item-meta`},me={class:`meta-time`},he={class:`meta-count`},ge={key:0,class:`meta-source cli`},_e=[`onClick`],ve=[`onClick`],ye={class:`agent-chat-area`},be={key:0,class:`chat-loading`},xe={key:0,class:`stop-button-bar`},W=A(r({__name:`AgentView`,setup(r){let v=j(),{sessions:D,sessionsLoading:O,currentSessionId:A,messages:M,isStreaming:N,sessionLoading:P,loadSessions:F,loadSession:I,deleteSession:W,renameSession:Se,newSession:G,sendMessage:K,stop:q}=ce(),Ce=E(()=>{let e=v.theme;return e===`dark`||e===`auto`&&window.matchMedia(`(prefers-color-scheme: dark)`).matches?`dark`:`light`}),J=g(``),Y=E(()=>{if(!J.value.trim())return D.value;let e=J.value.toLowerCase();return D.value.filter(t=>(t.title||``).toLowerCase().includes(e)||(t.model||``).toLowerCase().includes(e))}),we=E(()=>[{id:`p1`,label:k(`@AGENT:查看项目结构`),prompt:k(`@AGENT:prompt_p1`)},{id:`p2`,label:k(`@AGENT:分析代码质量`),prompt:k(`@AGENT:prompt_p2`)},{id:`p3`,label:k(`@AGENT:帮我写测试`),prompt:k(`@AGENT:prompt_p3`)},{id:`p4`,label:k(`@AGENT:Git 状态检查`),prompt:k(`@AGENT:prompt_p4`)}]);async function Te(e){await K(e.text,e.files),await a(),Z()}async function Ee(e){await K(e.prompt),await a(),Z()}let X=g(null);function Z(e=!0){X.value?.scrollToBottom(e)}b(()=>M.value.length,()=>{a(()=>Z(!1))});function De(e){if(N.value){y.warning(k(`@AGENT:请先停止当前生成`));return}I(e)}function Oe(){if(N.value){y.warning(k(`@AGENT:请先停止当前生成`));return}G()}function ke(e,t){if(t.stopPropagation(),N.value&&A.value===e){y.warning(k(`@AGENT:请先停止当前生成`));return}W(e)}async function Ae(e,t,n){n.stopPropagation();try{let{value:n}=await w.prompt(k(`@AGENT:请输入新的会话标题`),k(`@AGENT:重命名会话`),{inputValue:t,inputPattern:/.+/,inputErrorMessage:k(`@AGENT:标题不能为空`)});n&&n!==t&&await Se(e,n)}catch{}}function je(e){if(!e)return``;try{let t=new Date(e);if(Number.isNaN(t.getTime()))return e;let n=new Date,r=n.getTime()-t.getTime(),i=e=>String(e).padStart(2,`0`);if(t.toDateString()===n.toDateString())return`${i(t.getHours())}:${i(t.getMinutes())}`;if(r<10080*60*1e3){let e=Math.floor(r/(1440*60*1e3));return e===0?k(`@AGENT:今天`):`${e}${k(`@AGENT:天前`)}`}return`${t.getFullYear()}-${i(t.getMonth()+1)}-${i(t.getDate())}`}catch{return e}}let Q=g(280),$=g(!1);function Me(e){$.value=!0;let t=e.clientX,n=Q.value,r=e=>{let r=e.clientX-t;Q.value=Math.max(200,Math.min(500,n+r))},i=()=>{$.value=!1,document.removeEventListener(`mousemove`,r),document.removeEventListener(`mouseup`,i)};document.addEventListener(`mousemove`,r),document.addEventListener(`mouseup`,i)}return b(()=>[v.currentDirectory,N.value],async([e,t],n)=>{let r=!n||e!==n[0],i=!!n&&n[1]===!0&&!t;(r||i)&&await F(),A.value&&!N.value&&!D.value.some(e=>e.sessionId===A.value)&&G()},{immediate:!0}),(r,a)=>{let g=_;return p(),l(`div`,le,[u(`aside`,{class:`agent-sidebar`,style:o({width:Q.value+`px`})},[u(`div`,ue,[u(`button`,{class:`new-session-btn`,onClick:Oe},[c(e(S),null,{default:C(()=>[c(e(s))]),_:1}),u(`span`,null,m(e(k)(`@AGENT:新建会话`)),1)])]),u(`div`,de,[c(e(S),{class:`search-icon`},{default:C(()=>[c(e(d))]),_:1}),T(u(`input`,{"onUpdate:modelValue":a[0]||=e=>J.value=e,placeholder:e(k)(`@AGENT:搜索会话...`),class:`search-input`,type:`text`},null,8,L),[[ne,J.value]])]),T((p(),l(`div`,R,[Y.value.length===0&&!e(O)?(p(),l(`div`,z,[c(e(S),{size:28,color:`var(--text-tertiary)`},{default:C(()=>[c(e(te))]),_:1}),u(`div`,B,m(J.value?e(k)(`@AGENT:未找到匹配的会话`):e(k)(`@AGENT:暂无会话`)),1),J.value?t(``,!0):(p(),l(`div`,V,m(e(k)(`@AGENT:点击上方按钮开始对话`)),1))])):t(``,!0),(p(!0),l(ee,null,h(Y.value,n=>(p(),l(`div`,{key:n.sessionId,class:f([`session-item`,{active:e(A)===n.sessionId}]),onClick:e=>De(n.sessionId)},[u(`div`,fe,[u(`div`,pe,m(n.title||e(k)(`@AGENT:无标题`)),1),u(`div`,U,[u(`span`,me,m(je(n.updatedAt)),1),a[3]||=u(`span`,{class:`meta-dot`},`·`,-1),u(`span`,he,m(n.messageCount)+` `+m(e(k)(`@AGENT:条`)),1),n.source===`cli`?(p(),l(`span`,ge,`CLI`)):t(``,!0)])]),u(`div`,{class:`session-item-actions`,onClick:a[1]||=oe(()=>{},[`stop`])},[c(e(i),{content:e(k)(`@AGENT:重命名`),placement:`top`,"show-after":500},{default:C(()=>[u(`button`,{class:`item-action-btn`,onClick:e=>Ae(n.sessionId,n.title,e)},[c(e(S),null,{default:C(()=>[c(e(re))]),_:1})],8,_e)]),_:2},1032,[`content`]),c(e(i),{content:e(k)(`@AGENT:删除`),placement:`top`,"show-after":500},{default:C(()=>[u(`button`,{class:`item-action-btn danger`,onClick:e=>ke(n.sessionId,e)},[c(e(S),null,{default:C(()=>[c(e(ie))]),_:1})],8,ve)]),_:2},1032,[`content`])])],10,H))),128))])),[[g,e(O)]])],4),u(`div`,{class:f([`sidebar-resizer`,{active:$.value}]),onMousedown:Me},null,34),u(`main`,ye,[e(P)?(p(),l(`div`,be,[c(e(S),{class:`is-loading`,size:32},{default:C(()=>[c(e(x))]),_:1}),u(`span`,null,m(e(k)(`@AGENT:加载会话中...`)),1)])):(p(),n(e(se),{key:1,ref_key:`chatContainerRef`,ref:X,messages:e(M),"preset-questions":we.value,"welcome-title":e(k)(`@AGENT:智能体助手`),"welcome-description":e(k)(`@AGENT:我可以帮你阅读代码、执行命令、修改文件。选择下方话题或直接输入你的问题。`),"assistant-name":`g ai`,theme:Ce.value,disabled:e(N),"upload-config":{accept:`image/*`},placeholder:e(N)?e(k)(`@AGENT:正在生成中...`):e(k)(`@AGENT:输入消息,Enter 发送`),onSend:Te,onSelect:Ee},null,8,[`messages`,`preset-questions`,`welcome-title`,`welcome-description`,`theme`,`disabled`,`placeholder`])),c(ae,{name:`fade`},{default:C(()=>[e(N)?(p(),l(`div`,xe,[u(`button`,{class:`stop-button`,onClick:a[2]||=(...t)=>e(q)&&e(q)(...t)},[c(e(S),null,{default:C(()=>[c(e(x),{class:`is-loading`})]),_:1}),u(`span`,null,m(e(k)(`@AGENT:停止生成`)),1)])])):t(``,!0)]),_:1})])])}}}),[[`__scopeId`,`data-v-773baa74`]]);export{W as default};
@@ -0,0 +1,2 @@
1
+ import"./rolldown-runtime-BM3Ffeng.js";import{Cr as e,Dn as t,En as n,Fn as r,G as i,Gn as a,Hn as o,M as s,Mn as c,Nn as l,On as u,Tn as d,U as f,Wr as p,Yn as m,Yr as h,_r as g,i as _,ir as v,kt as y,nt as b,on as x,or as S,qn as C,r as w,rt as T,wn as E}from"./element-plus-H7kW-grr.js";import{n as D,t as O}from"./_plugin-vue_export-helper-BaSe6lmR.js";async function k(e,t){let n=e.body.getReader(),r=new TextDecoder(`utf-8`),i=``;for(;;){let{done:e,value:a}=await n.read();if(e)break;i+=r.decode(a,{stream:!0});let o=i.split(`
2
+ `);i=o.pop()??``;for(let e of o)e.trim()&&t(e)}i.trim()&&t(i)}async function A(){let e=await fetch(`/api/app-version`);return e.ok?e.json():{success:!1,current:``,latest:null,hasUpdate:!1,error:`HTTP ${e.status}`}}async function j(e){let t=await fetch(`/api/app-upgrade`,{method:`POST`});if(!t.ok){let n=`HTTP ${t.status}`;try{let e=await t.json();e?.error&&(n=e.error)}catch{}e({type:`error`,message:n}),e({type:`done`,code:t.status});return}if(!t.body){e({type:`error`,message:`响应无 body`}),e({type:`done`,code:-1});return}await k(t,t=>{try{e(JSON.parse(t))}catch{}})}async function M(){let e=await fetch(`/api/app-restart`,{method:`POST`});if(!e.ok){let t=`HTTP ${e.status}`;try{let n=await e.json();n?.error&&(t=n.error)}catch{}throw Error(t)}if(!e.body)throw Error(`响应无 body`);return new Promise((t,n)=>{let r=!1,i=e=>{r||(r=!0,e())};k(e,e=>{try{let r=JSON.parse(e);r.type===`ready`&&typeof r.port==`number`&&r.port>0?i(()=>t(r.port)):r.type===`timeout`?i(()=>n(Error(`restart.timeout`))):r.type===`error`&&i(()=>n(Error(r.message||`restart.failed`)))}catch{}}).then(()=>{i(()=>n(Error(`restart.stream-closed`)))}).catch(()=>{})})}var N={key:3},P={key:4},F={key:5},I={key:0,class:`upgrade-hint`},L={key:0,class:`upgrade-countdown`},R={key:1,class:`upgrade-manual`},z={class:`upgrade-manual__label`},B=`npm install -g zen-gitsync --registry https://registry.npmjs.org/`,V=O(r({__name:`UpgradeDialog`,props:{modelValue:{type:Boolean},logs:{},status:{},countdown:{default:0}},emits:[`update:modelValue`,`retry`,`restart`,`cancel`],setup(r,{emit:i}){let a=r,C=i,w=g(null);v(()=>a.logs,async()=>{await o(),w.value&&(w.value.scrollTop=w.value.scrollHeight)});function E(){a.status!==`running`&&(a.status===`success`&&a.countdown>0&&C(`cancel`),C(`update:modelValue`,!1))}function O(){C(`retry`)}function k(){C(`restart`)}function A(){C(`cancel`),C(`update:modelValue`,!1)}async function j(){try{await navigator.clipboard.writeText(B),_.success(D(`@F13B4:已复制升级命令`))}catch{_.warning(D(`@F13B4:复制失败,请手动复制`))}}return(r,i)=>{let a=x,o=f,g=s;return m(),n(g,{"model-value":r.modelValue,"onUpdate:modelValue":i[0]||=e=>C(`update:modelValue`,e),title:e(D)(`@F13B4:升级`),width:`680px`,"close-on-click-modal":!1,"close-on-press-escape":!1,"show-close":r.status!==`running`,"append-to-body":``,onClose:E},{footer:S(()=>[r.status===`failed`?(m(),n(o,{key:0,type:`primary`,onClick:O},{default:S(()=>[c(h(e(D)(`@F13B4:重试`)),1)]),_:1})):t(``,!0),r.status===`failed`?(m(),n(o,{key:1,onClick:j},{default:S(()=>[c(h(e(D)(`@F13B4:复制升级命令`)),1)]),_:1})):t(``,!0),r.status===`success`?(m(),n(o,{key:2,type:`primary`,onClick:k},{default:S(()=>[c(h(e(D)(`@F13B4:立即重启并刷新`)),1)]),_:1})):t(``,!0),r.status===`success`?(m(),n(o,{key:3,onClick:A},{default:S(()=>[c(h(e(D)(`@F13B4:稍后手动重启`)),1)]),_:1})):t(``,!0),r.status===`running`?(m(),n(o,{key:4,disabled:``},{default:S(()=>[c(h(e(D)(`@F13B4:升级中`))+`... `,1)]),_:1})):t(``,!0)]),default:S(()=>[d(`div`,{class:p([`upgrade-status`,`is-${r.status}`])},[r.status===`running`?(m(),n(a,{key:0,class:`is-loading`},{default:S(()=>[l(e(y))]),_:1})):r.status===`success`?(m(),n(a,{key:1,color:`#67c23a`},{default:S(()=>[l(e(b))]),_:1})):(m(),n(a,{key:2,color:`#f56c6c`},{default:S(()=>[l(e(T))]),_:1})),r.status===`running`?(m(),u(`span`,N,h(e(D)(`@F13B4:升级中`)),1)):r.status===`success`?(m(),u(`span`,P,h(e(D)(`@F13B4:升级完成`)),1)):(m(),u(`span`,F,h(e(D)(`@F13B4:升级失败`)),1))],2),r.status===`success`?(m(),u(`p`,I,[c(h(e(D)(`@F13B4:新版本已全局安装,需要重启服务才能生效`))+` `,1),r.countdown>0?(m(),u(`span`,L,h(e(D)(`@F13B4:{seconds} 秒后自动刷新`,{seconds:r.countdown})),1)):t(``,!0)])):t(``,!0),r.status===`failed`?(m(),u(`p`,R,[d(`span`,z,h(e(D)(`@F13B4:手动升级命令`)),1),d(`code`,{class:`upgrade-manual__cmd`},h(B))])):t(``,!0),d(`pre`,{ref_key:`logEl`,ref:w,class:`upgrade-log`},h(r.logs||e(D)(`@F13B4:等待日志输出`)),513)]),_:1},8,[`model-value`,`title`,`show-close`])}}}),[[`__scopeId`,`data-v-40c190d7`]]),H={class:`app-version-badge`},U=[`aria-label`],W=[`aria-label`],G=[`title`,`aria-label`],K={class:`upgrade-btn__label`},q={class:`upgrade-btn__version`},J=`https://github.com/xz333221/zen-gitsync/releases`,Y=5,X=`app-version-check`,Z=3600*1e3,Q=O(r({__name:`AppVersionBadge`,setup(n){let r=`2.17.1`,o=g(r),s=E(()=>o.value||r),c=g(null),f=g(!1),p=g(``),v=g(`running`),y=g(0),b=null,x=g(!1);function T(){try{let e=sessionStorage.getItem(X);if(!e)return null;let{at:t,data:n}=JSON.parse(e);return Date.now()-t>Z?null:n}catch{return null}}function O(e){try{sessionStorage.setItem(X,JSON.stringify({at:Date.now(),data:e}))}catch{}}async function k(e=!1){let t=T();t&&(c.value=t);try{let t=await A();c.value=t,O(t),t.current&&(o.value=t.current),!e&&t.success&&t.hasUpdate&&_({type:`success`,message:D(`@F13B4:发现新版本 {version}`,{version:t.latest??``}),duration:4e3})}catch(t){e||_({type:`warning`,message:D(`@F13B4:检查更新失败 {error}`,{error:t?.message||``})})}}async function N(){if(c.value?.hasUpdate){try{await w.confirm(D(`@F13B4:确定要从 {current} 升级到 {latest} 吗?`,{current:c.value.current,latest:c.value.latest}),D(`@F13B4:升级`),{confirmButtonText:D(`@F13B4:确定升级`),cancelButtonText:D(`@F13B4:取消`),type:`info`})}catch{return}p.value=``,v.value=`running`,f.value=!0,await j(e=>{if(e.type===`done`){if(v.value=e.code===0?`success`:`failed`,e.code===0){try{sessionStorage.removeItem(X)}catch{}c.value?.latest&&(o.value=c.value.latest),P()}}else e.message&&(p.value+=e.message)})}}function P(){F(),y.value=Y,b=setInterval(()=>{--y.value,y.value<=0&&(b&&=(clearInterval(b),null),L())},1e3)}function F(){b&&=(clearInterval(b),null),y.value=0}function I(){F()}async function L(){if(F(),!x.value){x.value=!0;try{let e=await M(),{pathname:t,search:n,hash:r}=window.location;window.location.href=`http://localhost:${e}${t}${n}${r}`}catch(e){let t=e?.message||``,n={"restart.timeout":`@F13B4:重启超时`,"restart.stream-closed":`@F13B4:重启失败,请刷新页面重试`,"restart.failed":`@F13B4:重启失败`}[t];_({type:`warning`,message:n?D(n):D(`@F13B4:重启失败 {error}`,{error:t})}),x.value=!1}}}return a(()=>{F()}),C(()=>{k(!0)}),(n,r)=>{let a=i;return m(),u(`div`,H,[l(a,{content:e(D)(`@F13B4:在 GitHub 上查看源码`),placement:`top`,effect:`dark`,"show-after":300},{default:S(()=>[d(`a`,{href:`https://github.com/xz333221/zen-gitsync`,target:`_blank`,rel:`noopener noreferrer`,class:`badge-icon-link`,"aria-label":e(D)(`@F13B4:在 GitHub 上查看源码`)},r[1]||=[d(`svg`,{viewBox:`0 0 24 24`,width:`15`,height:`15`,"aria-hidden":`true`,fill:`currentColor`,xmlns:`http://www.w3.org/2000/svg`},[d(`path`,{d:`M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2z`})],-1)],8,U)]),_:1},8,[`content`]),l(a,{content:e(D)(`@F13B4:在 npm 上查看包`),placement:`top`,effect:`dark`,"show-after":300},{default:S(()=>[d(`a`,{href:`https://www.npmjs.com/package/zen-gitsync`,target:`_blank`,rel:`noopener noreferrer`,class:`badge-icon-link badge-icon-link--npm`,"aria-label":e(D)(`@F13B4:在 npm 上查看包`)},r[2]||=[d(`svg`,{viewBox:`0 0 24 24`,width:`15`,height:`15`,"aria-hidden":`true`,fill:`currentColor`,xmlns:`http://www.w3.org/2000/svg`},[d(`path`,{d:`M2 2v20h20V2H2zm17.167 17.167H12.5v-10h-3.333v10H4.833V4.833h14.334v14.334z`})],-1)],8,W)]),_:1},8,[`content`]),l(a,{content:e(D)(`@F13B4:查看更新`),placement:`top`,effect:`dark`,"show-after":300},{default:S(()=>[d(`a`,{href:J,target:`_blank`,rel:`noopener noreferrer`,class:`version-link`},`v`+h(s.value),1)]),_:1},8,[`content`]),c.value?.hasUpdate?(m(),u(`button`,{key:0,type:`button`,class:`upgrade-btn`,title:e(D)(`@F13B4:发现新版本 {version},点击升级`,{version:c.value.latest??``}),"aria-label":e(D)(`@F13B4:发现新版本 {version},点击升级`,{version:c.value.latest??``}),onClick:N},[r[3]||=d(`span`,{class:`upgrade-btn__dot`,"aria-hidden":`true`},null,-1),d(`span`,K,h(e(D)(`@F13B4:升级`)),1),d(`span`,q,`v`+h(c.value.latest),1)],8,G)):t(``,!0),l(V,{modelValue:f.value,"onUpdate:modelValue":r[0]||=e=>f.value=e,logs:p.value,status:v.value,countdown:y.value,onRetry:N,onRestart:L,onCancel:I},null,8,[`modelValue`,`logs`,`status`,`countdown`])])}}}),[[`__scopeId`,`data-v-bc7c0d5c`]]);export{Q as default};
@@ -1 +1 @@
1
- .upgrade-status[data-v-bc566bba]{align-items:center;gap:var(--spacing-base);margin-bottom:var(--spacing-md);font-size:14px;font-weight:500;display:flex}.upgrade-status.is-running[data-v-bc566bba]{color:var(--el-color-primary)}.upgrade-status.is-success[data-v-bc566bba]{color:var(--el-color-success)}.upgrade-status.is-failed[data-v-bc566bba]{color:var(--el-color-danger)}.upgrade-status .is-loading[data-v-bc566bba]{animation:2s linear infinite rotating-bc566bba}.upgrade-log[data-v-bc566bba]{color:#d4d4d4;white-space:pre-wrap;word-break:break-all;background:#1e1e1e;border-radius:4px;height:320px;margin:0;padding:12px;font-family:JetBrains Mono,Cascadia Code,Consolas,monospace;font-size:12px;line-height:1.6;overflow:auto}.upgrade-hint[data-v-bc566bba]{margin:0 0 var(--spacing-md) 0;border-left:3px solid var(--el-color-success);color:var(--el-color-success);background:#67c23a14;border-radius:4px;padding:8px 12px;font-size:13px}.upgrade-countdown[data-v-bc566bba]{background:var(--el-color-success);color:#fff;border-radius:10px;margin-left:8px;padding:1px 8px;font-size:12px;font-weight:600;animation:1s ease-in-out infinite pulse-bc566bba;display:inline-block}.upgrade-manual[data-v-bc566bba]{margin:0 0 var(--spacing-md) 0;border-left:3px solid var(--el-color-danger);background:#f56c6c14;border-radius:4px;padding:8px 12px;font-size:13px}.upgrade-manual__label[data-v-bc566bba]{color:var(--el-color-danger);margin-bottom:4px;font-weight:500;display:block}.upgrade-manual__cmd[data-v-bc566bba]{color:var(--text-primary,#303133);word-break:break-all;-webkit-user-select:all;user-select:all;background:#0000000a;border-radius:3px;padding:6px 8px;font-family:JetBrains Mono,Cascadia Code,Consolas,monospace;font-size:12px;display:block}@keyframes pulse-bc566bba{0%,to{opacity:1}50%{opacity:.55}}@keyframes rotating-bc566bba{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.app-version-badge[data-v-7e0300ec]{align-items:center;gap:var(--spacing-base);margin-left:auto;display:inline-flex}.badge-icon-link[data-v-7e0300ec]{width:22px;height:22px;color:var(--text-tertiary);opacity:.7;border-radius:4px;justify-content:center;align-items:center;text-decoration:none;transition:color .18s,opacity .18s,background .18s;display:inline-flex}.badge-icon-link[data-v-7e0300ec]:hover{opacity:1;color:var(--text-secondary);background:var(--bg-hover,#0000000f)}.badge-icon-link--npm[data-v-7e0300ec]:hover{color:var(--color-npm)}.badge-icon-link[data-v-7e0300ec]:first-child:hover{color:var(--text-primary)}.version-link[data-v-7e0300ec]{color:var(--text-secondary);border-radius:3px;padding:2px 6px;font-family:monospace;font-size:11px;text-decoration:none;transition:color .2s,background-color .2s}.version-link[data-v-7e0300ec]:hover{color:var(--color-primary);background-color:#0000000a}.upgrade-btn[data-v-7e0300ec]{appearance:none;cursor:pointer;color:#fff;background:linear-gradient(135deg, var(--color-primary) 0%, #3a8ee6 100%);height:22px;box-shadow:var(--shadow-sm);transition:transform var(--transition-fast) var(--ease-custom), box-shadow var(--transition-fast) var(--ease-custom), filter var(--transition-fast) var(--ease-custom);white-space:nowrap;-webkit-user-select:none;user-select:none;border:0;border-radius:11px;outline:none;align-items:center;gap:5px;margin:0;padding:0 10px 0 8px;font-family:inherit;font-size:11px;font-weight:600;line-height:1;display:inline-flex}.upgrade-btn[data-v-7e0300ec]:hover{filter:brightness(1.08);box-shadow:var(--shadow-md);transform:translateY(-1px)}.upgrade-btn[data-v-7e0300ec]:active{filter:brightness(.96);box-shadow:var(--shadow-sm);transform:translateY(0)}.upgrade-btn[data-v-7e0300ec]:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}.upgrade-btn__dot[data-v-7e0300ec]{background:#fff;border-radius:50%;flex-shrink:0;width:6px;height:6px;animation:1.6s ease-in-out infinite upgrade-pulse-7e0300ec;box-shadow:0 0 0 2px #ffffff59}.upgrade-btn__label[data-v-7e0300ec]{letter-spacing:.5px}.upgrade-btn__version[data-v-7e0300ec]{background:#ffffff38;border-radius:6px;padding:1px 5px;font-family:monospace;font-size:10px;font-weight:500;line-height:1.2}@keyframes upgrade-pulse-7e0300ec{0%,to{opacity:1;transform:scale(1)}50%{opacity:.55;transform:scale(.8)}}
1
+ .upgrade-status[data-v-40c190d7]{align-items:center;gap:var(--spacing-base);margin-bottom:var(--spacing-md);font-size:14px;font-weight:500;display:flex}.upgrade-status.is-running[data-v-40c190d7]{color:var(--el-color-primary)}.upgrade-status.is-success[data-v-40c190d7]{color:var(--el-color-success)}.upgrade-status.is-failed[data-v-40c190d7]{color:var(--el-color-danger)}.upgrade-status .is-loading[data-v-40c190d7]{animation:2s linear infinite rotating-40c190d7}.upgrade-log[data-v-40c190d7]{color:#d4d4d4;white-space:pre-wrap;word-break:break-all;background:#1e1e1e;border-radius:4px;height:320px;margin:0;padding:12px;font-family:JetBrains Mono,Cascadia Code,Consolas,monospace;font-size:12px;line-height:1.6;overflow:auto}.upgrade-hint[data-v-40c190d7]{margin:0 0 var(--spacing-md) 0;border-left:3px solid var(--el-color-success);color:var(--el-color-success);background:#67c23a14;border-radius:4px;padding:8px 12px;font-size:13px}.upgrade-countdown[data-v-40c190d7]{background:var(--el-color-success);color:#fff;border-radius:10px;margin-left:8px;padding:1px 8px;font-size:12px;font-weight:600;animation:1s ease-in-out infinite pulse-40c190d7;display:inline-block}.upgrade-manual[data-v-40c190d7]{margin:0 0 var(--spacing-md) 0;border-left:3px solid var(--el-color-danger);background:#f56c6c14;border-radius:4px;padding:8px 12px;font-size:13px}.upgrade-manual__label[data-v-40c190d7]{color:var(--el-color-danger);margin-bottom:4px;font-weight:500;display:block}.upgrade-manual__cmd[data-v-40c190d7]{color:var(--text-primary,#303133);word-break:break-all;-webkit-user-select:all;user-select:all;background:#0000000a;border-radius:3px;padding:6px 8px;font-family:JetBrains Mono,Cascadia Code,Consolas,monospace;font-size:12px;display:block}@keyframes pulse-40c190d7{0%,to{opacity:1}50%{opacity:.55}}@keyframes rotating-40c190d7{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.app-version-badge[data-v-bc7c0d5c]{align-items:center;gap:var(--spacing-base);margin-left:auto;display:inline-flex}.badge-icon-link[data-v-bc7c0d5c]{width:22px;height:22px;color:var(--text-tertiary);opacity:.7;border-radius:4px;justify-content:center;align-items:center;text-decoration:none;transition:color .18s,opacity .18s,background .18s;display:inline-flex}.badge-icon-link[data-v-bc7c0d5c]:hover{opacity:1;color:var(--text-secondary);background:var(--bg-hover,#0000000f)}.badge-icon-link--npm[data-v-bc7c0d5c]:hover{color:var(--color-npm)}.badge-icon-link[data-v-bc7c0d5c]:first-child:hover{color:var(--text-primary)}.version-link[data-v-bc7c0d5c]{color:var(--text-secondary);border-radius:3px;padding:2px 6px;font-family:monospace;font-size:11px;text-decoration:none;transition:color .2s,background-color .2s}.version-link[data-v-bc7c0d5c]:hover{color:var(--color-primary);background-color:#0000000a}.upgrade-btn[data-v-bc7c0d5c]{appearance:none;cursor:pointer;color:#fff;background:linear-gradient(135deg, var(--color-primary) 0%, #3a8ee6 100%);height:22px;box-shadow:var(--shadow-sm);transition:transform var(--transition-fast) var(--ease-custom), box-shadow var(--transition-fast) var(--ease-custom), filter var(--transition-fast) var(--ease-custom);white-space:nowrap;-webkit-user-select:none;user-select:none;border:0;border-radius:11px;outline:none;align-items:center;gap:5px;margin:0;padding:0 10px 0 8px;font-family:inherit;font-size:11px;font-weight:600;line-height:1;display:inline-flex}.upgrade-btn[data-v-bc7c0d5c]:hover{filter:brightness(1.08);box-shadow:var(--shadow-md);transform:translateY(-1px)}.upgrade-btn[data-v-bc7c0d5c]:active{filter:brightness(.96);box-shadow:var(--shadow-sm);transform:translateY(0)}.upgrade-btn[data-v-bc7c0d5c]:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}.upgrade-btn__dot[data-v-bc7c0d5c]{background:#fff;border-radius:50%;flex-shrink:0;width:6px;height:6px;animation:1.6s ease-in-out infinite upgrade-pulse-bc7c0d5c;box-shadow:0 0 0 2px #ffffff59}.upgrade-btn__label[data-v-bc7c0d5c]{letter-spacing:.5px}.upgrade-btn__version[data-v-bc7c0d5c]{background:#ffffff38;border-radius:6px;padding:1px 5px;font-family:monospace;font-size:10px;font-weight:500;line-height:1.2}@keyframes upgrade-pulse-bc7c0d5c{0%,to{opacity:1;transform:scale(1)}50%{opacity:.55;transform:scale(.8)}}
@@ -0,0 +1 @@
1
+ import"./rolldown-runtime-BM3Ffeng.js";import{Bt as e,Cr as t,Dn as n,E as r,En as i,Fn as a,Lt as o,Mt as ee,Nn as s,On as c,S as te,T as ne,Tn as l,Wr as u,Yn as d,Yr as f,Zn as p,_r as m,bn as h,on as g,or as _,q as re,tt as v,vn as ie,wn as y,x as b,y as x}from"./element-plus-H7kW-grr.js";import{n as S,t as C}from"./_plugin-vue_export-helper-BaSe6lmR.js";import{t as w}from"./SvgIcon-BpKq_phu.js";import{t as ae}from"./CommonDialog-OCekbVQM.js";import{E as oe,w as T}from"./index-SjRTI8Of.js";var E={key:0,class:`branch-info`},D={class:`branch-wrapper`},O={class:`branch-btn`},k={class:`branch-btn-name`},A={class:`branch-btn-caret`},j={class:`branch-popover-inner`},M={class:`branch-search`},N={class:`branch-list`},P=[`onClick`],F={key:1,class:`branch-item-check-placeholder`},I={class:`branch-item-name`},L={key:0,class:`branch-empty`},R={class:`branch-popover-footer`},z={class:`create-branch-content`},B={class:`form-label`},se={class:`form-label`},V={class:`dialog-footer`},H={class:`footer-actions`},U=[`disabled`],W=C(a({__name:`BranchSelector`,emits:[`branchChanged`],setup(a,{emit:C}){let W=T(),G=C,K=m(!1),q=m(``),J=y(()=>{let e=q.value.trim().toLowerCase();return e?W.allBranches.filter(t=>t.toLowerCase().includes(e)):W.allBranches});async function ce(e){if(e===W.currentBranch){K.value=!1;return}K.value=!1,await W.changeBranch(e)&&(G(`branchChanged`),W.refreshLog())}let Y=m(!1),X=m(``),Z=m(``);function le(){K.value=!1,Z.value=W.currentBranch,Y.value=!0}async function Q(){await W.createBranch(X.value,Z.value)&&(Y.value=!1,X.value=``,G(`branchChanged`))}let $=m(!1);async function ue(){if(!$.value){$.value=!0;try{await Promise.all([W.getCurrentBranch(!0),W.getBranchStatus(!0),W.getAllBranches()]),G(`branchChanged`),W.refreshLog()}finally{$.value=!1}}}return(a,m)=>{let y=g,C=re,T=x,G=r,de=b,fe=te,pe=ne;return t(W).currentBranch?(d(),c(`div`,E,[l(`div`,D,[s(T,{visible:K.value,"onUpdate:visible":m[1]||=e=>K.value=e,placement:`top-start`,width:260,trigger:`click`,"popper-class":`branch-popover`,"show-arrow":!1,onShow:m[2]||=e=>q.value=``},{reference:_(()=>[l(`button`,O,[s(w,{"icon-class":`git-branch`,"class-name":`branch-btn-icon`}),l(`span`,k,f(t(W).currentBranch),1),l(`span`,A,[s(y,null,{default:_(()=>m[7]||=[l(`svg`,{viewBox:`0 0 1024 1024`,xmlns:`http://www.w3.org/2000/svg`},[l(`path`,{fill:`currentColor`,d:`M831.872 340.864 512 652.672 192.128 340.864a30.592 30.592 0 0 0-42.752 0 29.12 29.12 0 0 0 0 41.6L489.664 714.24a32 32 0 0 0 44.672 0l340.288-331.776a29.12 29.12 0 0 0 0-41.6 30.592 30.592 0 0 0-42.752 0z`})],-1)]),_:1})])])]),default:_(()=>[l(`div`,j,[l(`div`,M,[s(C,{modelValue:q.value,"onUpdate:modelValue":m[0]||=e=>q.value=e,placeholder:t(S)(`@F13B4:搜索或签出分支`),size:`small`,clearable:``,autofocus:``},null,8,[`modelValue`,`placeholder`])]),l(`div`,N,[(d(!0),c(h,null,p(J.value,e=>(d(),c(`div`,{key:e,class:u([`branch-item`,{"is-active":e===t(W).currentBranch}]),onClick:t=>ce(e)},[e===t(W).currentBranch?(d(),i(y,{key:0,class:`branch-item-check`},{default:_(()=>[s(t(v))]),_:1})):(d(),c(`span`,F)),s(w,{"icon-class":`git-branch`,"class-name":`branch-item-icon`}),l(`span`,I,f(e),1)],10,P))),128)),J.value.length===0?(d(),c(`div`,L,f(t(S)(`@F13B4:没有匹配的分支`)),1)):n(``,!0)]),l(`div`,R,[l(`button`,{class:`branch-create-btn`,onClick:le},[s(y,null,{default:_(()=>[s(t(o))]),_:1}),l(`span`,null,f(t(S)(`@F13B4:创建新分支`)),1)])])])]),_:1},8,[`visible`]),s(oe,{tooltip:t(S)(`@CMDCON:刷新`),onClick:ue},{default:_(()=>[s(y,{class:u({"is-loading":$.value})},{default:_(()=>[s(t(e))]),_:1},8,[`class`])]),_:1},8,[`tooltip`])]),s(ae,{modelValue:Y.value,"onUpdate:modelValue":m[6]||=e=>Y.value=e,title:t(S)(`@F13B4:创建新分支`),size:`small`,"destroy-on-close":!0,"append-to-body":!0,"custom-class":`create-branch-dialog`},{footer:_(()=>[l(`div`,V,[l(`div`,H,[l(`button`,{type:`button`,class:`dialog-cancel-btn`,onClick:m[5]||=e=>Y.value=!1},f(t(S)(`@F13B4:取消`)),1),l(`button`,{type:`button`,class:`dialog-confirm-btn`,onClick:Q,disabled:t(W).isCreatingBranch},[t(W).isCreatingBranch?(d(),i(y,{key:1,class:`is-loading`},{default:_(()=>m[8]||=[l(`svg`,{viewBox:`0 0 1024 1024`,xmlns:`http://www.w3.org/2000/svg`},[l(`path`,{fill:`currentColor`,d:`M512 64a32 32 0 0 1 32 32v192a32 32 0 0 1-64 0V96a32 32 0 0 1 32-32zm0 640a32 32 0 0 1 32 32v192a32 32 0 1 1-64 0V736a32 32 0 0 1 32-32zm448-192a32 32 0 0 1-32 32H736a32 32 0 1 1 0-64h192a32 32 0 0 1 32 32zm-640 0a32 32 0 0 1-32 32H96a32 32 0 0 1 0-64h192a32 32 0 0 1 32 32z`})],-1)]),_:1})):(d(),i(y,{key:0},{default:_(()=>[s(t(v))]),_:1})),l(`span`,null,f(t(S)(`@F13B4:创建`)),1)],8,U)])])]),default:_(()=>[l(`div`,z,[s(pe,{model:{newBranchName:X.value,selectedBaseBranch:Z.value},"label-position":`top`},{default:_(()=>[s(G,null,{label:_(()=>[l(`div`,B,[s(y,{class:`label-icon`},{default:_(()=>[s(t(o))]),_:1}),l(`span`,null,f(t(S)(`@F13B4:新分支名称`)),1)])]),default:_(()=>[s(C,{modelValue:X.value,"onUpdate:modelValue":m[3]||=e=>X.value=e,placeholder:t(S)(`@F13B4:请输入新分支名称`),class:`modern-input`,size:`large`,onKeyup:ie(Q,[`enter`])},null,8,[`modelValue`,`placeholder`])]),_:1}),s(G,null,{label:_(()=>[l(`div`,se,[s(y,{class:`label-icon`},{default:_(()=>[s(t(ee))]),_:1}),l(`span`,null,f(t(S)(`@F13B4:基于分支`)),1)])]),default:_(()=>[s(fe,{modelValue:Z.value,"onUpdate:modelValue":m[4]||=e=>Z.value=e,placeholder:t(S)(`@F13B4:选择基础分支`),class:`modern-select`,size:`large`,style:{width:`100%`}},{default:_(()=>[(d(!0),c(h,null,p(t(W).allBranches,e=>(d(),i(de,{key:e,label:e,value:e},null,8,[`label`,`value`]))),128))]),_:1},8,[`modelValue`,`placeholder`])]),_:1})]),_:1},8,[`model`])])]),_:1},8,[`modelValue`,`title`])])):n(``,!0)}}}),[[`__scopeId`,`data-v-665171bc`]]);export{W as default};
@@ -0,0 +1 @@
1
+ .branch-wrapper[data-v-665171bc]{align-items:center;gap:2px;display:inline-flex}.branch-btn[data-v-665171bc]{cursor:pointer;height:22px;color:var(--color-text);font-family:var(--font-mono,monospace);white-space:nowrap;background:0 0;border:none;border-radius:3px;align-items:center;gap:4px;padding:0 6px;font-size:12px;transition:background .15s;display:inline-flex}.branch-btn[data-v-665171bc]:hover{background:var(--bg-hover,#ffffff14)}.branch-btn[data-v-665171bc]:active{background:var(--bg-active,#ffffff1f)}[data-v-665171bc] .branch-btn-icon{flex-shrink:0;width:14px;height:14px}.branch-btn-name[data-v-665171bc]{text-overflow:ellipsis;max-width:160px;overflow:hidden}.branch-btn-caret[data-v-665171bc]{opacity:.6;align-items:center;font-size:10px;display:flex}.branch-popover-inner[data-v-665171bc]{flex-direction:column;gap:0;display:flex}.branch-search[data-v-665171bc]{padding-bottom:8px}.branch-list[data-v-665171bc]{max-height:240px;margin:0 -16px;padding:0 4px;overflow-y:auto}.branch-list[data-v-665171bc]::-webkit-scrollbar{width:4px}.branch-list[data-v-665171bc]::-webkit-scrollbar-thumb{background:var(--border-color,#8080804d);border-radius:2px}.branch-item[data-v-665171bc]{cursor:pointer;height:28px;color:var(--color-text);border-radius:3px;align-items:center;gap:6px;padding:0 12px;font-size:13px;transition:background .1s;display:flex}.branch-item[data-v-665171bc]:hover{background:var(--bg-hover,#ffffff14)}.branch-item.is-active[data-v-665171bc]{color:var(--color-primary)}.branch-item-check[data-v-665171bc]{color:var(--color-primary);flex-shrink:0;font-size:12px}.branch-item-check-placeholder[data-v-665171bc]{flex-shrink:0;width:12px;display:inline-block}[data-v-665171bc] .branch-item-icon{opacity:.7;flex-shrink:0;width:13px;height:13px}.branch-item-name[data-v-665171bc]{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.branch-empty[data-v-665171bc]{text-align:center;color:var(--color-text-secondary,#888);padding:12px;font-size:12px}.branch-popover-footer[data-v-665171bc]{margin-top:4px;padding-top:6px}.branch-create-btn[data-v-665171bc]{cursor:pointer;width:100%;height:28px;color:var(--color-text);text-align:left;background:0 0;border:none;border-radius:3px;align-items:center;gap:6px;padding:0 12px;font-size:13px;transition:background .1s;display:flex}.branch-create-btn[data-v-665171bc]:hover{background:var(--bg-hover,#ffffff14)}.branch-info[data-v-665171bc]{align-items:center;display:flex}.create-branch-content[data-v-665171bc]{padding:var(--spacing-base) 0}.is-loading[data-v-665171bc]{animation:2s linear infinite rotating-665171bc}@keyframes rotating-665171bc{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.branch-popover{border-radius:4px!important;padding:12px 16px!important;box-shadow:0 4px 16px #0000002e!important}.create-branch-dialog .modern-select .el-select__wrapper{border-radius:var(--radius-lg);border:1px solid var(--border-input);box-shadow:var(--shadow-sm);padding:10px var(--spacing-md);background:var(--bg-container);transition:all .2s}.create-branch-dialog .modern-select .el-select__wrapper:hover{border-color:var(--color-gray-300);box-shadow:var(--shadow-md)}.create-branch-dialog .modern-select.is-focus .el-select__wrapper{border-color:#3498db;box-shadow:0 0 0 2px #3498db1a,0 2px 6px #00000014}