zen-gitsync 2.16.50 → 2.16.51

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 (62) hide show
  1. package/package.json +5 -2
  2. package/src/ui/public/assets/AgentView-DcTGw8-j.js +4 -0
  3. package/src/ui/public/assets/AppVersionBadge-CA5-POGA.js +2 -0
  4. package/src/ui/public/assets/BranchSelector-qn-iGHOI.js +1 -0
  5. package/src/ui/public/assets/CommitForm-DHfyJ331.js +6 -0
  6. package/src/ui/public/assets/CommonDialog-DJBixJcI.js +1 -0
  7. package/src/ui/public/assets/EditorView-BRWoa7--.js +1 -0
  8. package/src/ui/public/assets/{FlowExecutionViewer-DlsUDtqp.js → FlowExecutionViewer-YoPgld-R.js} +1 -1
  9. package/src/ui/public/assets/FlowOrchestrationWorkspace-DyEjlCBW.js +21 -0
  10. package/src/ui/public/assets/LogList-_z4dIgvS.js +7 -0
  11. package/src/ui/public/assets/MindmapView-BrkcAcDh.js +2 -0
  12. package/src/ui/public/assets/MonitorView-8TG_K4m3.js +1 -0
  13. package/src/ui/public/assets/ProjectStartupButton-BqLyrik7.js +1 -0
  14. package/src/ui/public/assets/RemoteManagerDialog-LOVV6KrB.js +4 -0
  15. package/src/ui/public/assets/{RemoteRepoCard-5kcQFW__.js → RemoteRepoCard-SQyKcXDG.js} +3 -3
  16. package/src/ui/public/assets/SourceMapView-DckLynaL.js +3 -0
  17. package/src/ui/public/assets/SvgIcon-CvIX1KL7.js +1 -0
  18. package/src/ui/public/assets/UserInputNode-Bc124-BK.js +1 -0
  19. package/src/ui/public/assets/WorkbenchView-BtQkd9Oy.js +37 -0
  20. package/src/ui/public/assets/WorkbenchView-FdhmMI-b.css +1 -0
  21. package/src/ui/public/assets/{_plugin-vue_export-helper-SBtt0pJv.js → _plugin-vue_export-helper-N7dqbp6x.js} +3 -3
  22. package/src/ui/public/assets/configStore-NaVpBxPo.js +1 -0
  23. package/src/ui/public/assets/{element-plus-DhvzaBj9.js → element-plus-Csu3CJKj.js} +1 -1
  24. package/src/ui/public/assets/{flow-mindmap-iFPJqeGK.js → flow-mindmap-wqtwJcDW.js} +12 -12
  25. package/src/ui/public/assets/index-D9u0mCSi.js +98 -0
  26. package/src/ui/public/assets/{office-docx-lTQk2xZI.js → office-docx-BKrAsewH.js} +6 -6
  27. package/src/ui/public/assets/{office-excel-DLpZO4xP.js → office-excel-5lGzd5Yy.js} +1 -1
  28. package/src/ui/public/assets/{office-pptx-COhadcNP.js → office-pptx-DKtkU_7r.js} +1 -1
  29. package/src/ui/public/assets/toolsStore-CNDLLgUG.js +1 -0
  30. package/src/ui/public/assets/{vendor-CjAdbnlA.js → vendor-C6ntdXkR.js} +43 -43
  31. package/src/ui/public/assets/{vendor-BkgN6wPK.css → vendor-_6pgW9Z2.css} +1 -1
  32. package/src/ui/public/assets/vue-flow-D7IxiTyP.js +39 -0
  33. package/src/ui/public/index.html +11 -11
  34. package/src/ui/server/routes/workbench/index.js +206 -5
  35. package/src/ui/server/routes/workbench/orchestratorStore.js +260 -0
  36. package/src/ui/server/routes/workbench/projectRegistry.js +385 -0
  37. package/src/ui/server/routes/workbench/projectRegistry.test.js +339 -0
  38. package/src/ui/server/routes/workbench/promptParts.js +51 -0
  39. package/src/ui/server/routes/workbench/promptParts.test.js +82 -0
  40. package/src/ui/server/routes/workbench/shared.js +5 -0
  41. package/src/ui/server/routes/workbench/taskRunner.js +3 -2
  42. package/src/ui/public/assets/AgentView-BgWYXDtl.js +0 -4
  43. package/src/ui/public/assets/AppVersionBadge-BCIsBONZ.js +0 -2
  44. package/src/ui/public/assets/BranchSelector-BobZDQ3H.js +0 -1
  45. package/src/ui/public/assets/CommitForm-B5gIj9Ib.js +0 -6
  46. package/src/ui/public/assets/CommonDialog-CpMIq-Hr.js +0 -1
  47. package/src/ui/public/assets/EditorView-CH98t3V5.js +0 -1
  48. package/src/ui/public/assets/FlowOrchestrationWorkspace-18v5NKYl.js +0 -21
  49. package/src/ui/public/assets/LogList-BYiEpIUO.js +0 -7
  50. package/src/ui/public/assets/MindmapView-CUC7icIE.js +0 -2
  51. package/src/ui/public/assets/MonitorView-ggIzE5af.js +0 -1
  52. package/src/ui/public/assets/ProjectStartupButton-BatNYldf.js +0 -1
  53. package/src/ui/public/assets/RemoteManagerDialog-Be7FyEZR.js +0 -4
  54. package/src/ui/public/assets/SourceMapView-C5bto8pg.js +0 -3
  55. package/src/ui/public/assets/SvgIcon-uPiOGYUG.js +0 -1
  56. package/src/ui/public/assets/UserInputNode-BaWZXnvN.js +0 -1
  57. package/src/ui/public/assets/WorkbenchView-CR9FKLxj.css +0 -1
  58. package/src/ui/public/assets/WorkbenchView-DYS6reGa.js +0 -31
  59. package/src/ui/public/assets/configStore-B93BSbxD.js +0 -1
  60. package/src/ui/public/assets/index-CfUzUz7b.js +0 -98
  61. package/src/ui/public/assets/toolsStore-vZowzNpd.js +0 -1
  62. package/src/ui/public/assets/vue-flow-Cn2Hs-io.js +0 -39
@@ -0,0 +1,339 @@
1
+ // Copyright 2026 xz333221
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ //
15
+ // 多项目编排台的纯函数单测:任务列推导、项目合并去重、执行目录解析、活动流拼装。
16
+ // 这些规则前后端只实现一次(后端),所以口径漂移的代价全压在这里的断言上。
17
+
18
+ import { test } from 'node:test';
19
+ import assert from 'node:assert/strict';
20
+
21
+ import {
22
+ canonicalProjectPath,
23
+ projectName,
24
+ resolveTaskRepoPath,
25
+ deriveTaskColumn,
26
+ latestJob,
27
+ summarizeProjectTasks,
28
+ buildProjectEntries,
29
+ decorateTaskForBoard,
30
+ OUTPUT_TAIL_CHARS,
31
+ trimJobForDetail,
32
+ buildTaskDetail,
33
+ } from './projectRegistry.js';
34
+ import { normalizeOrchestrator, buildActivityFeed, buildRunningAgents } from './orchestratorStore.js';
35
+
36
+ const sub = (id, status) => ({ id, title: id, desc: '', status, promptOverride: '' });
37
+ const job = (id, taskId, status, startedAt, endedAt = null) => ({
38
+ id, taskId, subId: `${taskId}__${id}`, title: '', status, startedAt, endedAt,
39
+ });
40
+
41
+ // ── 路径归一 ────────────────────────────────────────────────────────
42
+
43
+ test('canonicalProjectPath: 盘符转大写 + Windows 斜杠归一(与前端同口径)', () => {
44
+ assert.equal(canonicalProjectPath('e:\\workspace\\x'), 'E:\\workspace\\x');
45
+ assert.equal(canonicalProjectPath(' D:/ws/y '), 'D:\\ws\\y');
46
+ // 同一目录的两种斜杠写法必须落到同一个 key,否则项目列表里出现重复项
47
+ // (实测 article-generator 就同时存在于 D:/... 与 D:\... 两种常用目录里)
48
+ assert.equal(canonicalProjectPath('D:/ws/y'), canonicalProjectPath('D:\\ws\\y'));
49
+ // POSIX 路径原样返回,不能被改写成反斜杠
50
+ assert.equal(canonicalProjectPath('/home/me/proj'), '/home/me/proj');
51
+ assert.equal(canonicalProjectPath('/home/me/proj/nested'), '/home/me/proj/nested');
52
+ assert.equal(canonicalProjectPath(''), '');
53
+ assert.equal(canonicalProjectPath(null), '');
54
+ });
55
+
56
+ test('projectName 取末级目录名,空路径返回空串', () => {
57
+ assert.equal(projectName('D:\\ws\\zen-gitsync'), 'zen-gitsync');
58
+ assert.equal(projectName('/home/me/proj/'), 'proj');
59
+ assert.equal(projectName(''), '');
60
+ });
61
+
62
+ // ── 执行目录 ────────────────────────────────────────────────────────
63
+
64
+ test('resolveTaskRepoPath 优先用任务自己的项目,空则回退当前项目', () => {
65
+ assert.equal(resolveTaskRepoPath({ projectPath: 'D:\\proj-a' }, 'D:\\proj-b'), 'D:\\proj-a');
66
+ assert.equal(resolveTaskRepoPath({ projectPath: ' ' }, 'D:\\proj-b'), 'D:\\proj-b');
67
+ assert.equal(resolveTaskRepoPath({}, 'D:\\proj-b'), 'D:\\proj-b');
68
+ // 目录不存在也不回退:宁可执行报错,也不能把改动悄悄落到另一个仓库
69
+ assert.equal(resolveTaskRepoPath({ projectPath: 'Z:\\gone' }, 'D:\\proj-b'), 'Z:\\gone');
70
+ });
71
+
72
+ // ── 看板列推导 ──────────────────────────────────────────────────────
73
+
74
+ test('deriveTaskColumn: 从没动过的任务在待处理', () => {
75
+ assert.equal(deriveTaskColumn({ subtasks: [] }, []), 'todo');
76
+ assert.equal(deriveTaskColumn({ subtasks: [sub('s1', 'todo')] }, []), 'todo');
77
+ });
78
+
79
+ test('deriveTaskColumn: 有 job 在跑或子任务 running 就是进行中', () => {
80
+ assert.equal(deriveTaskColumn({ subtasks: [] }, [job('j1', 't1', 'running', '2026-01-01T00:00:00Z')]), 'doing');
81
+ assert.equal(deriveTaskColumn({ subtasks: [] }, [job('j1', 't1', 'pending', '2026-01-01T00:00:00Z')]), 'doing');
82
+ assert.equal(deriveTaskColumn({ subtasks: [sub('s1', 'running')] }, []), 'doing');
83
+ });
84
+
85
+ test('deriveTaskColumn: 子任务全部完成才算已完成', () => {
86
+ assert.equal(deriveTaskColumn({ subtasks: [sub('s1', 'done'), sub('s2', 'done')] }, []), 'done');
87
+ // 一半完成 → 跑过但没收尾,落在评审中
88
+ assert.equal(deriveTaskColumn({ subtasks: [sub('s1', 'done'), sub('s2', 'todo')] }, []), 'review');
89
+ // 有子任务报错也算"需要人看一眼"
90
+ assert.equal(deriveTaskColumn({ subtasks: [sub('s1', 'error'), sub('s2', 'todo')] }, []), 'review');
91
+ });
92
+
93
+ test('deriveTaskColumn: 无子任务的任务只能看执行记录', () => {
94
+ assert.equal(deriveTaskColumn({ subtasks: [], type: 'simple' }, [job('j1', 't1', 'done', '2026-01-01T00:00:00Z')]), 'done');
95
+ assert.equal(deriveTaskColumn({ subtasks: [], type: 'simple' }, [job('j1', 't1', 'cancelled', '2026-01-01T00:00:00Z')]), 'review');
96
+ assert.equal(deriveTaskColumn({ subtasks: [], type: 'simple' }, [job('j1', 't1', 'error', '2026-01-01T00:00:00Z')]), 'review');
97
+ });
98
+
99
+ test('latestJob 按 startedAt 取最新的一条', () => {
100
+ const list = [
101
+ job('j1', 't1', 'error', '2026-01-01T00:00:00Z', '2026-01-01T00:01:00Z'),
102
+ job('j2', 't1', 'done', '2026-01-02T00:00:00Z', '2026-01-02T00:01:00Z'),
103
+ ];
104
+ assert.equal(latestJob(list).id, 'j2');
105
+ assert.equal(latestJob([]), null);
106
+ });
107
+
108
+ // ── 项目合并去重 ────────────────────────────────────────────────────
109
+
110
+ test('buildProjectEntries: 最近目录与任务路径取并集去重,盘符大小写归一', () => {
111
+ const entries = buildProjectEntries({
112
+ recentDirs: ['e:\\ws\\proj-a', 'D:\\ws\\proj-b'],
113
+ tasks: [
114
+ { id: 't1', projectPath: 'E:\\ws\\proj-a' }, // 与最近目录同一项目(盘符大小写不同)
115
+ { id: 't2', projectPath: 'D:\\ws\\proj-c' }, // 只在任务里出现
116
+ { id: 't3', projectPath: '' }, // 未关联项目,不进清单
117
+ ],
118
+ });
119
+ const byKey = new Map(entries.map(e => [e.key, e]));
120
+ assert.equal(entries.length, 3);
121
+ assert.equal(byKey.get('E:\\ws\\proj-a').source, 'both');
122
+ assert.equal(byKey.get('D:\\ws\\proj-b').source, 'recent');
123
+ assert.equal(byKey.get('D:\\ws\\proj-c').source, 'task');
124
+ assert.equal(byKey.get('D:\\ws\\proj-c').name, 'proj-c');
125
+ });
126
+
127
+ test('summarizeProjectTasks: 按项目汇总列数量、进度、活跃执行与最后活跃时间', () => {
128
+ const tasks = [
129
+ // A 项目:1 个已完成
130
+ { id: 'a1', projectPath: 'D:\\a', subtasks: [sub('a1s', 'done')], updatedAt: '2026-01-01T00:00:00Z' },
131
+ // A 项目:1 个在跑(job running)
132
+ { id: 'a2', projectPath: 'D:\\a', subtasks: [sub('a2s', 'running')], updatedAt: '2026-01-02T00:00:00Z' },
133
+ // B 项目:1 个待处理
134
+ { id: 'b1', projectPath: 'D:\\b', subtasks: [sub('b1s', 'todo')], updatedAt: '2026-01-03T00:00:00Z' },
135
+ ];
136
+ const jobs = [
137
+ job('j1', 'a2', 'running', '2026-01-04T00:00:00Z'),
138
+ job('j2', 'a1', 'done', '2026-01-01T01:00:00Z', '2026-01-01T01:30:00Z'),
139
+ ];
140
+ const stats = summarizeProjectTasks(tasks, jobs);
141
+
142
+ const a = stats.get('D:\\a');
143
+ assert.equal(a.total, 2);
144
+ assert.equal(a.done, 1);
145
+ assert.equal(a.doing, 1);
146
+ assert.equal(a.progress, 50);
147
+ assert.equal(a.runningJobs, 1);
148
+ // 最后活跃时间要把 job 的起止也算进来(任务体本身可能很久没改)
149
+ assert.equal(a.lastActiveAt, '2026-01-04T00:00:00Z');
150
+
151
+ const b = stats.get('D:\\b');
152
+ assert.equal(b.total, 1);
153
+ assert.equal(b.todo, 1);
154
+ assert.equal(b.progress, 0);
155
+ assert.equal(b.runningJobs, 0);
156
+ assert.equal(b.lastActiveAt, '2026-01-03T00:00:00Z');
157
+ });
158
+
159
+ test('decorateTaskForBoard: 只回卡片需要的字段', () => {
160
+ const card = decorateTaskForBoard({
161
+ id: 't1', title: '标题', desc: '描述', projectPath: 'D:\\a',
162
+ subtasks: [sub('s1', 'done'), sub('s2', 'error')],
163
+ attachments: [{ id: 'at1' }],
164
+ createdAt: '2026-01-01T00:00:00Z', updatedAt: '2026-01-02T00:00:00Z',
165
+ }, []);
166
+ assert.equal(card.column, 'review');
167
+ assert.equal(card.type, 'complex');
168
+ assert.equal(card.subtaskCount, 2);
169
+ assert.equal(card.subtaskDoneCount, 1);
170
+ assert.equal(card.subtaskErrorCount, 1);
171
+ assert.equal(card.attachmentCount, 1);
172
+ assert.equal(card.runningJobs, 0);
173
+ });
174
+
175
+ // ── 编排状态与活动流 ────────────────────────────────────────────────
176
+
177
+ test('normalizeOrchestrator: 缺字段/脏数据都要能起来', () => {
178
+ const fresh = normalizeOrchestrator(null);
179
+ assert.equal(fresh.active, true); // 缺省是"调度中"
180
+ assert.deepEqual(fresh.instructions, []);
181
+
182
+ assert.equal(normalizeOrchestrator({ active: false }).active, false);
183
+ assert.equal(normalizeOrchestrator({ active: 'no' }).active, true); // 只有显式 false 才算暂停
184
+
185
+ const withJunk = normalizeOrchestrator({ instructions: [null, 5, { text: 'hi' }] });
186
+ assert.equal(withJunk.instructions.length, 1);
187
+ assert.equal(withJunk.instructions[0].text, 'hi');
188
+ assert.equal(withJunk.instructions[0].status, 'created');
189
+ });
190
+
191
+ test('buildActivityFeed: job 起止合成两条记录,与人类指令一起按时间倒序', () => {
192
+ const jobs = [{
193
+ id: 'j1', taskId: 't1', subId: 's1', status: 'done', pid: 123,
194
+ startedAt: '2026-01-01T00:00:00Z', endedAt: '2026-01-01T00:05:00Z', exitCode: 0,
195
+ }];
196
+ const tasks = [{
197
+ id: 't1', projectPath: 'D:\\ws\\proj-a',
198
+ subtasks: [{ id: 's1', title: '子任务一', status: 'done' }],
199
+ title: '任务一',
200
+ }];
201
+ const instructions = [{
202
+ id: 'i1', text: '把这些重构一下', projectPath: 'D:\\ws\\proj-a',
203
+ at: '2026-01-01T00:10:00Z', status: 'accepted',
204
+ }];
205
+
206
+ const feed = buildActivityFeed({ jobs, tasks, instructions });
207
+ assert.equal(feed.length, 3);
208
+ assert.deepEqual(feed.map(r => r.kind), ['user', 'done', 'dispatch']);
209
+ assert.equal(feed[1].taskTitle, '任务一');
210
+ assert.equal(feed[1].subTitle, '子任务一');
211
+ assert.equal(feed[1].projectName, 'proj-a');
212
+ assert.equal(feed[2].pid, 123);
213
+ assert.equal(feed[0].instructionStatus, 'accepted');
214
+ });
215
+
216
+ test('buildActivityFeed: 失败与取消分别落成 error / cancelled', () => {
217
+ const jobs = [
218
+ { id: 'j1', taskId: 't1', subId: 's1', status: 'error', startedAt: '2026-01-01T00:00:00Z', endedAt: '2026-01-01T00:01:00Z', error: 'boom' },
219
+ { id: 'j2', taskId: 't1', subId: 's2', status: 'cancelled', startedAt: '2026-01-01T00:02:00Z', endedAt: '2026-01-01T00:03:00Z' },
220
+ ];
221
+ const feed = buildActivityFeed({ jobs, tasks: [{ id: 't1', projectPath: 'D:\\a', subtasks: [] }] });
222
+ const kinds = feed.map(r => r.kind);
223
+ assert.ok(kinds.includes('error'));
224
+ assert.ok(kinds.includes('cancelled'));
225
+ assert.equal(feed.find(r => r.kind === 'error').error, 'boom');
226
+ });
227
+
228
+ test('buildActivityFeed: 任务已被删除时仍能出记录(不留孤儿空白)', () => {
229
+ const jobs = [{ id: 'j1', taskId: 'gone', subId: 's1', status: 'done', startedAt: '2026-01-01T00:00:00Z', endedAt: '2026-01-01T00:01:00Z' }];
230
+ const feed = buildActivityFeed({ jobs, tasks: [] });
231
+ assert.equal(feed.length, 2);
232
+ assert.equal(feed[0].taskTitle, '');
233
+ assert.equal(feed[0].projectName, '');
234
+ });
235
+
236
+ test('buildRunningAgents: 只列活跃 job,到 job 粒度而不是任务粒度', () => {
237
+ const tasks = [{
238
+ id: 't1', projectPath: 'D:\\ws\\proj-a', subtasks: [
239
+ { id: 's1', title: '子任务一', status: 'running' },
240
+ { id: 's2', title: '子任务二', status: 'running' },
241
+ ],
242
+ }];
243
+ const jobs = [
244
+ { id: 'j1', taskId: 't1', subId: 's1', status: 'running', pid: 111, startedAt: '2026-01-01T00:02:00Z' },
245
+ { id: 'j2', taskId: 't1', subId: 's2', status: 'pending', pid: 222, startedAt: '2026-01-01T00:01:00Z' },
246
+ { id: 'j3', taskId: 't1', subId: 's3', status: 'done', pid: 333, startedAt: '2026-01-01T00:00:00Z' },
247
+ ];
248
+ const running = buildRunningAgents({ jobs, tasks });
249
+ assert.equal(running.length, 2);
250
+ // 最新的排在前面
251
+ assert.equal(running[0].jobId, 'j1');
252
+ assert.equal(running[0].subTitle, '子任务一');
253
+ assert.equal(running[0].projectName, 'proj-a');
254
+ assert.equal(running[1].status, 'pending');
255
+ });
256
+
257
+ // ── 任务详情(点卡片弹窗用) ────────────────────────────────────────
258
+
259
+ test('trimJobForDetail: 丢 prompt/thinking,output 只留尾部并标记被截断', () => {
260
+ const big = 'x'.repeat(OUTPUT_TAIL_CHARS + 500) + 'END';
261
+ const j = {
262
+ id: 'j1', taskId: 't1', subId: 's1', title: '跑一下', status: 'done',
263
+ prompt: 'P'.repeat(10000), thinking: 'T'.repeat(10000),
264
+ output: big, pid: 42, startedAt: '2026-01-01T00:00:00Z', endedAt: '2026-01-01T00:01:00Z',
265
+ exitCode: 0, error: '',
266
+ };
267
+ const t = trimJobForDetail(j);
268
+ assert.equal(t.outputTail.length, OUTPUT_TAIL_CHARS);
269
+ assert.equal(t.outputTruncated, true);
270
+ assert.equal(t.hasOutput, true);
271
+ // 保留的是尾部,不是头部
272
+ assert.equal(t.outputTail.endsWith('END'), true);
273
+ assert.equal(t.outputTruncated === true && t.outputTail.startsWith('x'), true);
274
+ // 大字段必须被丢掉,否则弹窗会把整份日志搬进 DOM
275
+ assert.equal('prompt' in t, false);
276
+ assert.equal('thinking' in t, false);
277
+ assert.equal(t.pid, 42);
278
+ assert.equal(t.exitCode, 0);
279
+ });
280
+
281
+ test('trimJobForDetail: 短输出原样保留且不谎报截断', () => {
282
+ const t = trimJobForDetail({ id: 'j1', status: 'error', output: 'boom', error: '炸了' });
283
+ assert.equal(t.outputTail, 'boom');
284
+ assert.equal(t.outputTruncated, false);
285
+ assert.equal(t.error, '炸了');
286
+ assert.equal(t.pid, null);
287
+ assert.equal(t.exitCode, null);
288
+ });
289
+
290
+ test('trimJobForDetail: null / 无 output 不炸', () => {
291
+ assert.equal(trimJobForDetail(null), null);
292
+ const t = trimJobForDetail({ id: 'j2', status: 'pending' });
293
+ assert.equal(t.outputTail, '');
294
+ assert.equal(t.outputTruncated, false);
295
+ assert.equal(t.hasOutput, false);
296
+ });
297
+
298
+ test('buildTaskDetail: 任务缺失返回 null(交路由转 404)', () => {
299
+ assert.equal(buildTaskDetail(null, []), null);
300
+ assert.equal(buildTaskDetail({}, []), null);
301
+ });
302
+
303
+ test('buildTaskDetail: lastJob 取最新一条,recentJobs 倒序且被限制条数', () => {
304
+ const task = {
305
+ id: 't1', title: '任务', desc: '', type: 'complex',
306
+ subtasks: [sub('s1', 'done'), sub('s2', 'running')],
307
+ attachments: [{ id: 'a1' }],
308
+ };
309
+ const jobs = [
310
+ job('j1', 't1', 'done', '2026-01-01T00:00:00Z', '2026-01-01T00:01:00Z'),
311
+ job('j3', 't1', 'running', '2026-01-01T00:02:00Z'),
312
+ job('j2', 't1', 'done', '2026-01-01T00:01:00Z', '2026-01-01T00:02:00Z'),
313
+ ];
314
+ const d = buildTaskDetail(task, jobs);
315
+ assert.equal(d.task.id, 't1');
316
+ assert.equal(d.jobCount, 3);
317
+ // 最新的是 j3(有 startedAt 无 endedAt,时间戳取 startedAt)
318
+ assert.equal(d.lastJob.id, 'j3');
319
+ assert.equal(d.recentJobs[0].id, 'j3');
320
+ assert.equal(d.recentJobs[1].id, 'j2');
321
+ assert.equal(d.recentJobs[2].id, 'j1');
322
+ // 有 job 在跑 → 进行中
323
+ assert.equal(d.column, 'doing');
324
+ });
325
+
326
+ test('buildTaskDetail: recentJobLimit 生效;无 job 时 lastJob 为 null 而不是抛错', () => {
327
+ const task = { id: 't2', title: '', desc: '', type: 'simple', subtasks: [] };
328
+ const jobs = [1, 2, 3, 4, 5, 6].map(i => job('j' + i, 't2', 'done', `2026-01-0${i}T00:00:00Z`));
329
+ const d = buildTaskDetail(task, jobs, { recentJobLimit: 2 });
330
+ assert.equal(d.recentJobs.length, 2);
331
+ assert.equal(d.jobCount, 6);
332
+ assert.equal(d.lastJob.id, 'j6');
333
+
334
+ const empty = buildTaskDetail(task, []);
335
+ assert.equal(empty.lastJob, null);
336
+ assert.deepEqual(empty.recentJobs, []);
337
+ assert.equal(empty.jobCount, 0);
338
+ assert.equal(empty.column, 'todo');
339
+ });
@@ -0,0 +1,51 @@
1
+ // Copyright 2026 xz333221
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ //
15
+ // 任务 prompt 正文的拼装规则(纯函数,无副作用)。
16
+ //
17
+ // 为什么单独成一个文件:taskRunner.js 顶层 import 了 jobStore.js,而后者在**模块加载时**
18
+ // 就调用 hydrateJobs() 并回写 ~/.zen-gitsync/jobs.json(还会把陈旧的 running/pending 降级为
19
+ // error 后落盘)。单测若直接 import taskRunner 就会碰到真实用户数据 ——
20
+ // 所以把"纯拼装"这一小块剥出来,测试只碰它。
21
+
22
+ /**
23
+ * 拼任务 prompt 的正文 = 提示词模板 + 子任务标题 + 子任务描述。
24
+ *
25
+ * 去重规则只作用于「标题 / 描述」这一对:
26
+ * 主 Agent 派发(POST /api/workbench/orchestrator/dispatch)建的简单任务里
27
+ * `title = text.split('\n')[0].slice(0, 120)`、`desc = text` —— 单行指令时两者逐字相同,
28
+ * 直接拼会得到「帮我拉取一下代码\n\n帮我拉取一下代码」:白烧 token,而且让 prompt
29
+ * 看起来像谁把话说了两遍。首行超过 120 字时 title 是首行的**前缀**,所以用
30
+ * startsWith 兜住这个截断情形(判定重复只要求"标题已被描述完整包含",不丢信息)。
31
+ *
32
+ * 模板(interpolated)刻意不参与去重:那是用户显式配置的提示词,
33
+ * 与标题撞车与否该由用户决定,代码不擅自删。
34
+ *
35
+ * @param {string} template 已插值的提示词模板,可为空
36
+ * @param {string} title 子任务标题
37
+ * @param {string} desc 子任务描述
38
+ * @returns {string} 用空行分隔的非空段落
39
+ */
40
+ export function composePromptBody(template, title, desc) {
41
+ const t = (title || '').trim();
42
+ const d = (desc || '').trim();
43
+ // 描述首行 —— title 由它截断而来,这是判定"重复"的依据
44
+ const firstLine = d.split('\n')[0];
45
+ const titleRedundant = !!t && (t === d || firstLine.startsWith(t));
46
+
47
+ // 判定用 trim 后的值,但拼进 prompt 的仍是**原值**:
48
+ // 只删掉重复的那一段,其余一个字符都不动(缩进、结尾换行都保持原样)。
49
+ const body = titleRedundant ? [desc] : [title, desc];
50
+ return [template, ...body].filter(s => s && s.trim()).join('\n\n');
51
+ }
@@ -0,0 +1,82 @@
1
+ // Copyright 2026 xz333221
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ //
15
+ // prompt 正文拼装的单测。
16
+ //
17
+ // 回归目标:主 Agent 派发建的简单任务(title = desc 首行)曾把同一句话拼两遍进 prompt ——
18
+ // 实测 job.prompt 是「帮我拉取一下代码\n\n帮我拉取一下代码」。
19
+ // 这里把"什么算重复、什么不算"钉死,避免以后又把用户手填的标题误删、或漏掉截断情形。
20
+
21
+ import { test } from 'node:test';
22
+ import assert from 'node:assert/strict';
23
+
24
+ import { composePromptBody } from './promptParts.js';
25
+
26
+ test('单行指令:title 与 desc 逐字相同时只出现一次', () => {
27
+ const out = composePromptBody('', '帮我拉取一下代码', '帮我拉取一下代码');
28
+ assert.equal(out, '帮我拉取一下代码');
29
+ // 反例锚点:这正是修复前的产物
30
+ assert.notEqual(out, '帮我拉取一下代码\n\n帮我拉取一下代码');
31
+ });
32
+
33
+ test('多行描述:title 就是首行时只留描述全文(首行不重复)', () => {
34
+ const out = composePromptBody('', '重构登录', '重构登录\n把错误处理抽成 composable');
35
+ assert.equal(out, '重构登录\n把错误处理抽成 composable');
36
+ });
37
+
38
+ test('派发路径的截断:title 是超长首行的前 120 字时仍判为重复', () => {
39
+ const firstLine = 'A'.repeat(200);
40
+ const title = firstLine.slice(0, 120); // 与 dispatch 路由的 slice(0,120) 同口径
41
+ const desc = `${firstLine}\n第二行`;
42
+ assert.equal(composePromptBody('', title, desc), desc);
43
+ });
44
+
45
+ test('标题被描述完整包含(后接其它文字)也算重复 —— 描述已含标题的全部信息', () => {
46
+ const out = composePromptBody('', '拉取代码', '拉取代码(顺便更新依赖)');
47
+ assert.equal(out, '拉取代码(顺便更新依赖)');
48
+ });
49
+
50
+ test('用户手填的标题与描述不同:两段都要保留', () => {
51
+ const out = composePromptBody('', '修复登录白屏', '用户反馈登录页点确定后白屏,帮我定位');
52
+ assert.equal(out, '修复登录白屏\n\n用户反馈登录页点确定后白屏,帮我定位');
53
+ });
54
+
55
+ test('标题只是描述中间出现过的词,不算重复(不做子串匹配)', () => {
56
+ const out = composePromptBody('', '登录', '修复登录白屏');
57
+ // '修复登录白屏' 的首行不以 '登录' 开头 → 两段都在
58
+ assert.equal(out, '登录\n\n修复登录白屏');
59
+ });
60
+
61
+ test('模板 + 不重复的标题/描述:三段按序拼接', () => {
62
+ const out = composePromptBody('请用中文回答', '重构登录', '把错误处理抽成 composable');
63
+ assert.equal(out, '请用中文回答\n\n重构登录\n\n把错误处理抽成 composable');
64
+ });
65
+
66
+ test('模板不参与去重:与标题相同也不删', () => {
67
+ const out = composePromptBody('重构登录', '重构登录', '把错误处理抽成 composable');
68
+ assert.equal(out, '重构登录\n\n重构登录\n\n把错误处理抽成 composable');
69
+ });
70
+
71
+ test('空段一律不占位,不产生多余空行', () => {
72
+ assert.equal(composePromptBody('', '', '仅描述'), '仅描述');
73
+ assert.equal(composePromptBody('', '仅标题', ''), '仅标题');
74
+ assert.equal(composePromptBody('模板', '', ''), '模板');
75
+ assert.equal(composePromptBody('', '', ''), '');
76
+ assert.equal(composePromptBody(' ', ' ', '\n'), '');
77
+ });
78
+
79
+ test('判定用 trim 值,但拼进 prompt 的是原值(缩进与结尾换行保持原样)', () => {
80
+ const desc = ' 缩进的描述\n';
81
+ assert.equal(composePromptBody('', ' 缩进的描述\n', desc), desc);
82
+ });
@@ -44,6 +44,11 @@ export const JOBS_CONFIG_FILE = path.join(DATA_DIR, 'jobs-config.json');
44
44
  export const JOBS_SAVE_DEBOUNCE_MS = 1500;
45
45
  export const DEFAULT_JOBS_CONFIG = { maxCount: 500, maxSizeMB: 256 };
46
46
 
47
+ // 主 Agent 编排台:调度开关(active)+ 人类干预指令存档。
48
+ // 指令存档是「我说过什么」的流水,不参与执行逻辑,只用于控制台日志流回放。
49
+ export const ORCHESTRATOR_FILE = path.join(DATA_DIR, 'orchestrator.json');
50
+ export const MAX_ORCHESTRATOR_INSTRUCTIONS = 200;
51
+
47
52
  // 子项目识别 / 文件扫描时需要跳过的目录
48
53
  export const SKIP_DIRS = new Set([
49
54
  'node_modules', 'dist', 'build', '.next', '.nuxt', '__pycache__',
@@ -38,6 +38,7 @@ import {
38
38
  interpolate,
39
39
  } from './shared.js';
40
40
  import { buildAttachmentBlock } from './pdfText.js';
41
+ import { composePromptBody } from './promptParts.js';
41
42
  import {
42
43
  jobs,
43
44
  cancelledJobs,
@@ -211,8 +212,8 @@ export async function runSingleSubtask(task, sub, repoPath, branch, priorOutputs
211
212
  branch: branch || ''
212
213
  };
213
214
  const interpolated = interpolate(promptTemplate, ctx);
214
- const parts = [interpolated, sub.title, sub.desc].filter(s => s && s.trim());
215
- let prompt = parts.join('\n\n');
215
+ // 标题与描述可能逐字相同(派发建的简单任务就是),去重规则见 promptParts.js
216
+ let prompt = composePromptBody(interpolated, sub.title, sub.desc);
216
217
 
217
218
  // ── 前序上下文:把前几个 done 子任务的输出完整拼到 prompt 头部 ──
218
219
  if (priorOutputs && priorOutputs.length > 0) {
@@ -1,4 +0,0 @@
1
- import"./rolldown-runtime-BM3Ffeng.js";import{$ as e,Dn as t,Et as n,G as r,Gn as i,Gr as a,In as o,Nt as s,Qn as c,Sn as l,Un as u,Vr as d,a as f,ct as p,dr as m,er as h,i as g,kn as _,lr as v,mn as y,mt as b,nn as x,pn as S,r as C,rn as w,tr as T,un as E,vn as D,xn as O,yn as k,zr as A,zt as j}from"./element-plus-DhvzaBj9.js";import{a as M,r as N,t as P}from"./vendor-CjAdbnlA.js";import{n as F,t as I}from"./_plugin-vue_export-helper-SBtt0pJv.js";import{t as L}from"./configStore-B93BSbxD.js";var R=64*1024;function z(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:H(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>R&&(a=`…(前文已截断)\n${a.slice(-R)}`);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||N(),name:e.function?.name||``,arguments:e.function?.arguments||``,argsPreview:B(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>R&&(e=`…(前文已截断)\n${e.slice(-R)}`),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 B(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 V(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 H(e){let t=/^data:([^;,]+)/.exec(e);return t?t[1]:``}function U(){let e=L(),t=m([]),n=m(!1),r=m(null),i=m([]),a=m(!1),o=m(!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){g.error(n.error||F(`@AGENT:加载会话列表失败`));return}t.value=Array.isArray(n.sessions)?n.sessions:[]}catch(e){if(r!==l)return;g.error(F(`@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){g.error(t.error||F(`@AGENT:加载会话失败`));return}i.value=z(t.session)}catch(e){g.error(F(`@AGENT:加载会话失败`)+`: `+(e?.message||e))}finally{o.value=!1}}async function f(e){try{await C.confirm(F(`@AGENT:确认删除该会话`),F(`@AGENT:删除后无法恢复`),{type:`warning`})}catch{return}try{let n=await fetch(`/api/agent/sessions/${encodeURIComponent(e)}`,{method:`DELETE`}).then(e=>e.json());if(!n.success){g.error(n.error||F(`@AGENT:删除失败`));return}t.value=t.value.filter(t=>t.sessionId!==e),r.value===e&&(r.value=null,i.value=[]),g.success(F(`@AGENT:已删除`))}catch(e){g.error(F(`@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){g.error(r.error||F(`@AGENT:重命名失败`));return}let i=t.value.find(t=>t.sessionId===e);i&&(i.title=n),g.success(F(`@AGENT:已重命名`))}catch(e){g.error(F(`@AGENT:重命名失败`)+`: `+(e?.message||e))}}function h(){r.value=null,i.value=[]}async function _(t,n=[]){let o=n.filter(e=>e?.file?.type?.startsWith(`image/`));if(o.length<n.length&&g.warning(F(`@AGENT:仅支持发送图片,非图片文件已忽略`)),!t.trim()&&o.length===0||a.value)return;let l=(await Promise.allSettled(o.map(e=>V(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:N(),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:N(),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=P(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||N(),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=P(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||F(`@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
- [`+F(`@AGENT:已停止`)+`]`,m.status=`done`,m.reasoningStatus===`streaming`&&(m.reasoningStatus=`done`)):(m.status=`error`,m.error=e?.message||String(e),g.error(m.error||F(`@AGENT:对话失败`)))}finally{d===c&&(a.value=!1,s=null)}}function y(){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:h,sendMessage:_,stop:y}}var W=_({__name:`AgentView`,setup(t,{expose:i}){i();let a=L(),{sessions:l,sessionsLoading:u,currentSessionId:d,messages:f,isStreaming:h,sessionLoading:_,loadSessions:v,loadSession:y,deleteSession:S,renameSession:w,newSession:T,sendMessage:E,stop:O}=U(),k=D(()=>{let e=a.theme;return e===`dark`||e===`auto`&&window.matchMedia(`(prefers-color-scheme: dark)`).matches?`dark`:`light`}),A=m(``),N=D(()=>{if(!A.value.trim())return l.value;let e=A.value.toLowerCase();return l.value.filter(t=>(t.title||``).toLowerCase().includes(e)||(t.model||``).toLowerCase().includes(e))}),P=D(()=>[{id:`p1`,label:F(`@AGENT:查看项目结构`),prompt:F(`@AGENT:prompt_p1`)},{id:`p2`,label:F(`@AGENT:分析代码质量`),prompt:F(`@AGENT:prompt_p2`)},{id:`p3`,label:F(`@AGENT:帮我写测试`),prompt:F(`@AGENT:prompt_p3`)},{id:`p4`,label:F(`@AGENT:Git 状态检查`),prompt:F(`@AGENT:prompt_p4`)}]);async function I(e){await E(e.text,e.files),await o(),B()}async function R(e){await E(e.prompt),await o(),B()}let z=m(null);function B(e=!0){z.value?.scrollToBottom(e)}c(()=>f.value.length,()=>{o(()=>B(!1))});function V(e){if(h.value){g.warning(F(`@AGENT:请先停止当前生成`));return}y(e)}function H(){if(h.value){g.warning(F(`@AGENT:请先停止当前生成`));return}T()}function W(e,t){if(t.stopPropagation(),h.value&&d.value===e){g.warning(F(`@AGENT:请先停止当前生成`));return}S(e)}async function G(e,t,n){n.stopPropagation();try{let{value:n}=await C.prompt(F(`@AGENT:请输入新的会话标题`),F(`@AGENT:重命名会话`),{inputValue:t,inputPattern:/.+/,inputErrorMessage:F(`@AGENT:标题不能为空`)});n&&n!==t&&await w(e,n)}catch{}}function K(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?F(`@AGENT:今天`):`${e}${F(`@AGENT:天前`)}`}return`${t.getFullYear()}-${i(t.getMonth()+1)}-${i(t.getDate())}`}catch{return e}}let q=m(280),J=m(!1);function Y(e){J.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=()=>{J.value=!1,document.removeEventListener(`mousemove`,r),document.removeEventListener(`mouseup`,i)};document.addEventListener(`mousemove`,r),document.addEventListener(`mouseup`,i)}c(()=>[a.currentDirectory,h.value],async([e,t],n)=>{let r=!n||e!==n[0],i=!!n&&n[1]===!0&&!t;(r||i)&&await v(),d.value&&!h.value&&!l.value.some(e=>e.sessionId===d.value)&&T()},{immediate:!0});let X={configStore:a,sessions:l,sessionsLoading:u,currentSessionId:d,messages:f,isStreaming:h,sessionLoading:_,loadSessions:v,loadSession:y,deleteSession:S,renameSession:w,newSession:T,sendMessage:E,stop:O,chatTheme:k,searchQuery:A,filteredSessions:N,presetQuestions:P,onSend:I,onSelectPreset:R,chatContainerRef:z,scrollToBottom:B,selectSession:V,handleNewSession:H,handleDelete:W,handleRename:G,formatDate:K,sidebarWidth:q,isResizing:J,startResize:Y,get $t(){return F},get ElTooltip(){return r},get ElIcon(){return x},get Plus(){return s},get Search(){return j},get Delete(){return p},get Edit(){return b},get ChatLineRound(){return e},get Loading(){return n},get ChatContainer(){return M}};return Object.defineProperty(X,`__isScriptSetup`,{enumerable:!1,value:!0}),X}}),G={class:`agent-view`},K={class:`sidebar-header`},q={class:`sidebar-search`},J=[`placeholder`],Y={class:`session-list`},X={key:0,class:`empty-state`},Z={class:`empty-text`},Q={key:0,class:`empty-hint`},$=[`onClick`],ee={class:`session-item-main`},te={class:`session-item-title`},ne={class:`session-item-meta`},re={class:`meta-time`},ie={class:`meta-count`},ae={key:0,class:`meta-source cli`},oe=[`onClick`],se=[`onClick`],ce={class:`agent-chat-area`},le={key:0,class:`chat-loading`},ue={key:0,class:`stop-button-bar`};function de(e,n,r,o,s,c){let p=f;return u(),l(`div`,G,[O(` ═══ 左侧:会话列表 ═══ `),k(`aside`,{class:`agent-sidebar`,style:d({width:o.sidebarWidth+`px`})},[O(` 顶部操作栏 `),k(`div`,K,[k(`button`,{class:`new-session-btn`,onClick:o.handleNewSession},[t(o.ElIcon,null,{default:h(()=>[t(o.Plus)]),_:1}),k(`span`,null,a(o.$t(`@AGENT:新建会话`)),1)])]),O(` 搜索框 `),k(`div`,q,[t(o.ElIcon,{class:`search-icon`},{default:h(()=>[t(o.Search)]),_:1}),T(k(`input`,{"onUpdate:modelValue":n[0]||=e=>o.searchQuery=e,placeholder:o.$t(`@AGENT:搜索会话...`),class:`search-input`,type:`text`},null,8,J),[[E,o.searchQuery]])]),O(` 会话列表 `),T((u(),l(`div`,Y,[o.filteredSessions.length===0&&!o.sessionsLoading?(u(),l(`div`,X,[t(o.ElIcon,{size:28,color:`var(--text-tertiary)`},{default:h(()=>[t(o.ChatLineRound)]),_:1}),k(`div`,Z,a(o.searchQuery?o.$t(`@AGENT:未找到匹配的会话`):o.$t(`@AGENT:暂无会话`)),1),o.searchQuery?O(`v-if`,!0):(u(),l(`div`,Q,a(o.$t(`@AGENT:点击上方按钮开始对话`)),1))])):O(`v-if`,!0),(u(!0),l(y,null,i(o.filteredSessions,e=>(u(),l(`div`,{key:e.sessionId,class:A([`session-item`,{active:o.currentSessionId===e.sessionId}]),onClick:t=>o.selectSession(e.sessionId)},[k(`div`,ee,[k(`div`,te,a(e.title||o.$t(`@AGENT:无标题`)),1),k(`div`,ne,[k(`span`,re,a(o.formatDate(e.updatedAt)),1),n[3]||=k(`span`,{class:`meta-dot`},`·`,-1),k(`span`,ie,a(e.messageCount)+` `+a(o.$t(`@AGENT:条`)),1),e.source===`cli`?(u(),l(`span`,ae,`CLI`)):O(`v-if`,!0)])]),k(`div`,{class:`session-item-actions`,onClick:n[1]||=S(()=>{},[`stop`])},[t(o.ElTooltip,{content:o.$t(`@AGENT:重命名`),placement:`top`,"show-after":500},{default:h(()=>[k(`button`,{class:`item-action-btn`,onClick:t=>o.handleRename(e.sessionId,e.title,t)},[t(o.ElIcon,null,{default:h(()=>[t(o.Edit)]),_:1})],8,oe)]),_:2},1032,[`content`]),t(o.ElTooltip,{content:o.$t(`@AGENT:删除`),placement:`top`,"show-after":500},{default:h(()=>[k(`button`,{class:`item-action-btn danger`,onClick:t=>o.handleDelete(e.sessionId,t)},[t(o.ElIcon,null,{default:h(()=>[t(o.Delete)]),_:1})],8,se)]),_:2},1032,[`content`])])],10,$))),128))])),[[p,o.sessionsLoading]])],4),O(` 拖拽分隔条 `),k(`div`,{class:A([`sidebar-resizer`,{active:o.isResizing}]),onMousedown:o.startResize},null,34),O(` ═══ 右侧:对话区域 ═══ `),k(`main`,ce,[O(` 加载中 `),o.sessionLoading?(u(),l(`div`,le,[t(o.ElIcon,{class:`is-loading`,size:32},{default:h(()=>[t(o.Loading)]),_:1}),k(`span`,null,a(o.$t(`@AGENT:加载会话中...`)),1)])):(u(),l(y,{key:1},[O(` ChatContainer `),t(o.ChatContainer,{ref:`chatContainerRef`,messages:o.messages,"preset-questions":o.presetQuestions,"welcome-title":o.$t(`@AGENT:智能体助手`),"welcome-description":o.$t(`@AGENT:我可以帮你阅读代码、执行命令、修改文件。选择下方话题或直接输入你的问题。`),"assistant-name":`g ai`,theme:o.chatTheme,disabled:o.isStreaming,"upload-config":{accept:`image/*`},placeholder:o.isStreaming?o.$t(`@AGENT:正在生成中...`):o.$t(`@AGENT:输入消息,Enter 发送`),onSend:o.onSend,onSelect:o.onSelectPreset},null,8,[`messages`,`preset-questions`,`welcome-title`,`welcome-description`,`theme`,`disabled`,`placeholder`])],2112)),O(` 停止按钮浮层 `),t(w,{name:`fade`},{default:h(()=>[o.isStreaming?(u(),l(`div`,ue,[k(`button`,{class:`stop-button`,onClick:n[2]||=(...e)=>o.stop&&o.stop(...e)},[t(o.ElIcon,null,{default:h(()=>[t(o.Loading,{class:`is-loading`})]),_:1}),k(`span`,null,a(o.$t(`@AGENT:停止生成`)),1)])])):O(`v-if`,!0)]),_:1})])])}var fe=I(W,[[`render`,de],[`__scopeId`,`data-v-bd681ddd`],[`__file`,`D:/xz_workspace/github_workspace/zen-gitsync/src/ui/client/src/views/AgentView.vue`]]);export{fe as default};
@@ -1,2 +0,0 @@
1
- import"./rolldown-runtime-BM3Ffeng.js";import{Dn as e,En as t,Et as n,G as r,Gr as i,In as a,M as o,Qn as s,Sn as c,U as l,Un as u,Vn as d,bn as f,dr as p,er as m,i as h,kn as g,nn as _,nt as v,r as y,tt as b,vn as x,xn as S,yn as C,zn as w,zr as T}from"./element-plus-DhvzaBj9.js";import{n as E,t as D}from"./_plugin-vue_export-helper-SBtt0pJv.js";async function O(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 k(){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 A(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 O(t,t=>{try{e(JSON.parse(t))}catch{}})}async function j(){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())};O(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 M=`npm install -g zen-gitsync --registry https://registry.npmjs.org/`,N=g({__name:`UpgradeDialog`,props:{modelValue:{type:Boolean,required:!0},logs:{type:String,required:!0},status:{type:String,required:!0},countdown:{type:Number,required:!1,default:0}},emits:[`update:modelValue`,`retry`,`restart`,`cancel`],setup(e,{expose:t,emit:r}){t();let i=e,o=r,c=p(null);s(()=>i.logs,async()=>{await a(),c.value&&(c.value.scrollTop=c.value.scrollHeight)});function l(){i.status!==`running`&&(i.status===`success`&&i.countdown>0&&o(`cancel`),o(`update:modelValue`,!1))}function u(){o(`retry`)}function d(){o(`restart`)}function f(){o(`cancel`),o(`update:modelValue`,!1)}async function m(){try{await navigator.clipboard.writeText(M),h.success(E(`@F13B4:已复制升级命令`))}catch{h.warning(E(`@F13B4:复制失败,请手动复制`))}}let g={MANUAL_COMMAND:M,props:i,emit:o,logEl:c,close:l,onRetry:u,onRestart:d,onLater:f,onCopyCommand:m,get Loading(){return n},get CircleCheck(){return b},get CircleClose(){return v},get $t(){return E}};return Object.defineProperty(g,`__isScriptSetup`,{enumerable:!1,value:!0}),g}}),P={key:3},F={key:4},I={key:5},L={key:0,class:`upgrade-hint`},R={key:0,class:`upgrade-countdown`},z={key:1,class:`upgrade-manual`},B={class:`upgrade-manual__label`};function V(n,r,a,s,d,p){let h=_,g=l,v=o;return u(),f(v,{"model-value":a.modelValue,"onUpdate:modelValue":r[0]||=e=>s.emit(`update:modelValue`,e),title:s.$t(`@F13B4:升级`),width:`680px`,"close-on-click-modal":!1,"close-on-press-escape":!1,"show-close":a.status!==`running`,"append-to-body":``,onClose:s.close},{footer:m(()=>[a.status===`failed`?(u(),f(g,{key:0,type:`primary`,onClick:s.onRetry},{default:m(()=>[t(i(s.$t(`@F13B4:重试`)),1)]),_:1})):S(`v-if`,!0),a.status===`failed`?(u(),f(g,{key:1,onClick:s.onCopyCommand},{default:m(()=>[t(i(s.$t(`@F13B4:复制升级命令`)),1)]),_:1})):S(`v-if`,!0),a.status===`success`?(u(),f(g,{key:2,type:`primary`,onClick:s.onRestart},{default:m(()=>[t(i(s.$t(`@F13B4:立即重启并刷新`)),1)]),_:1})):S(`v-if`,!0),a.status===`success`?(u(),f(g,{key:3,onClick:s.onLater},{default:m(()=>[t(i(s.$t(`@F13B4:稍后手动重启`)),1)]),_:1})):S(`v-if`,!0),a.status===`running`?(u(),f(g,{key:4,disabled:``},{default:m(()=>[t(i(s.$t(`@F13B4:升级中`))+`... `,1)]),_:1})):S(`v-if`,!0)]),default:m(()=>[C(`div`,{class:T([`upgrade-status`,`is-${a.status}`])},[a.status===`running`?(u(),f(h,{key:0,class:`is-loading`},{default:m(()=>[e(s.Loading)]),_:1})):a.status===`success`?(u(),f(h,{key:1,color:`#67c23a`},{default:m(()=>[e(s.CircleCheck)]),_:1})):(u(),f(h,{key:2,color:`#f56c6c`},{default:m(()=>[e(s.CircleClose)]),_:1})),a.status===`running`?(u(),c(`span`,P,i(s.$t(`@F13B4:升级中`)),1)):a.status===`success`?(u(),c(`span`,F,i(s.$t(`@F13B4:升级完成`)),1)):(u(),c(`span`,I,i(s.$t(`@F13B4:升级失败`)),1))],2),a.status===`success`?(u(),c(`p`,L,[t(i(s.$t(`@F13B4:新版本已全局安装,需要重启服务才能生效`))+` `,1),a.countdown>0?(u(),c(`span`,R,i(s.$t(`@F13B4:{seconds} 秒后自动刷新`,{seconds:a.countdown})),1)):S(`v-if`,!0)])):S(`v-if`,!0),a.status===`failed`?(u(),c(`p`,z,[C(`span`,B,i(s.$t(`@F13B4:手动升级命令`)),1),C(`code`,{class:`upgrade-manual__cmd`},i(s.MANUAL_COMMAND))])):S(`v-if`,!0),C(`pre`,{ref:`logEl`,class:`upgrade-log`},i(a.logs||s.$t(`@F13B4:等待日志输出`)),513)]),_:1},8,[`model-value`,`title`,`show-close`])}var H=D(N,[[`render`,V],[`__scopeId`,`data-v-bc566bba`],[`__file`,`D:/xz_workspace/github_workspace/zen-gitsync/src/ui/client/src/components/UpgradeDialog.vue`]]),U=`https://github.com/xz333221/zen-gitsync/releases`,W=5,G=`app-version-check`,K=3600*1e3,q=g({__name:`AppVersionBadge`,setup(e,{expose:t}){t();let n=`2.16.50`,r=p(n),i=x(()=>r.value||n),a=p(null),o=p(!1),s=p(``),c=p(`running`),l=p(0),u=null,f=p(!1);function m(){try{let e=sessionStorage.getItem(G);if(!e)return null;let{at:t,data:n}=JSON.parse(e);return Date.now()-t>K?null:n}catch{return null}}function g(e){try{sessionStorage.setItem(G,JSON.stringify({at:Date.now(),data:e}))}catch{}}async function _(e=!1){let t=m();t&&(a.value=t);try{let t=await k();a.value=t,g(t),t.current&&(r.value=t.current),!e&&t.success&&t.hasUpdate&&h({type:`success`,message:E(`@F13B4:发现新版本 {version}`,{version:t.latest??``}),duration:4e3})}catch(t){e||h({type:`warning`,message:E(`@F13B4:检查更新失败 {error}`,{error:t?.message||``})})}}async function v(){if(a.value?.hasUpdate){try{await y.confirm(E(`@F13B4:确定要从 {current} 升级到 {latest} 吗?`,{current:a.value.current,latest:a.value.latest}),E(`@F13B4:升级`),{confirmButtonText:E(`@F13B4:确定升级`),cancelButtonText:E(`@F13B4:取消`),type:`info`})}catch{return}s.value=``,c.value=`running`,o.value=!0,await A(e=>{if(e.type===`done`){if(c.value=e.code===0?`success`:`failed`,e.code===0){try{sessionStorage.removeItem(G)}catch{}a.value?.latest&&(r.value=a.value.latest),b()}}else e.message&&(s.value+=e.message)})}}function b(){S(),l.value=W,u=setInterval(()=>{--l.value,l.value<=0&&(u&&=(clearInterval(u),null),T())},1e3)}function S(){u&&=(clearInterval(u),null),l.value=0}function C(){S()}async function T(){if(S(),!f.value){f.value=!0;try{let e=await j(),{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];h({type:`warning`,message:n?E(n):E(`@F13B4:重启失败 {error}`,{error:t})}),f.value=!1}}}w(()=>{S()}),d(()=>{_(!0)});let D={buildVersion:n,releasesUrl:U,runtimeVersion:r,displayVersion:i,latestInfo:a,upgradeDialogVisible:o,upgradeLogs:s,upgradeStatus:c,AUTO_REFRESH_SECONDS:W,countdown:l,get countdownTimer(){return u},set countdownTimer(e){u=e},restartInFlight:f,CACHE_KEY:G,CACHE_TTL_MS:K,readCache:m,writeCache:g,checkUpdate:_,handleUpgrade:v,startAutoRefreshCountdown:b,cancelAutoRefreshCountdown:S,handleCancelAutoRefresh:C,handleRestart:T,get $t(){return E},UpgradeDialog:H};return Object.defineProperty(D,`__isScriptSetup`,{enumerable:!1,value:!0}),D}}),J={class:`app-version-badge`},Y=[`aria-label`],X=[`aria-label`],Z=[`title`,`aria-label`],Q={class:`upgrade-btn__label`},$={class:`upgrade-btn__version`};function ee(t,n,a,o,s,l){let d=r;return u(),c(`div`,J,[S(` GitHub 链接 `),e(d,{content:o.$t(`@F13B4:在 GitHub 上查看源码`),placement:`top`,effect:`dark`,"show-after":300},{default:m(()=>[C(`a`,{href:`https://github.com/xz333221/zen-gitsync`,target:`_blank`,rel:`noopener noreferrer`,class:`badge-icon-link`,"aria-label":o.$t(`@F13B4:在 GitHub 上查看源码`)},n[1]||=[C(`svg`,{viewBox:`0 0 24 24`,width:`15`,height:`15`,"aria-hidden":`true`,fill:`currentColor`,xmlns:`http://www.w3.org/2000/svg`},[C(`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,Y)]),_:1},8,[`content`]),S(` NPM 链接 `),e(d,{content:o.$t(`@F13B4:在 npm 上查看包`),placement:`top`,effect:`dark`,"show-after":300},{default:m(()=>[C(`a`,{href:`https://www.npmjs.com/package/zen-gitsync`,target:`_blank`,rel:`noopener noreferrer`,class:`badge-icon-link badge-icon-link--npm`,"aria-label":o.$t(`@F13B4:在 npm 上查看包`)},n[2]||=[C(`svg`,{viewBox:`0 0 24 24`,width:`15`,height:`15`,"aria-hidden":`true`,fill:`currentColor`,xmlns:`http://www.w3.org/2000/svg`},[C(`path`,{d:`M2 2v20h20V2H2zm17.167 17.167H12.5v-10h-3.333v10H4.833V4.833h14.334v14.334z`})],-1)],8,X)]),_:1},8,[`content`]),S(` 版本号 `),e(d,{content:o.$t(`@F13B4:查看更新`),placement:`top`,effect:`dark`,"show-after":300},{default:m(()=>[C(`a`,{href:o.releasesUrl,target:`_blank`,rel:`noopener noreferrer`,class:`version-link`},`v`+i(o.displayVersion),1)]),_:1},8,[`content`]),o.latestInfo?.hasUpdate?(u(),c(`button`,{key:0,type:`button`,class:`upgrade-btn`,title:o.$t(`@F13B4:发现新版本 {version},点击升级`,{version:o.latestInfo.latest??``}),"aria-label":o.$t(`@F13B4:发现新版本 {version},点击升级`,{version:o.latestInfo.latest??``}),onClick:o.handleUpgrade},[n[3]||=C(`span`,{class:`upgrade-btn__dot`,"aria-hidden":`true`},null,-1),C(`span`,Q,i(o.$t(`@F13B4:升级`)),1),C(`span`,$,`v`+i(o.latestInfo.latest),1)],8,Z)):S(`v-if`,!0),e(o.UpgradeDialog,{modelValue:o.upgradeDialogVisible,"onUpdate:modelValue":n[0]||=e=>o.upgradeDialogVisible=e,logs:o.upgradeLogs,status:o.upgradeStatus,countdown:o.countdown,onRetry:o.handleUpgrade,onRestart:o.handleRestart,onCancel:o.handleCancelAutoRefresh},null,8,[`modelValue`,`logs`,`status`,`countdown`])])}var te=D(q,[[`render`,ee],[`__scopeId`,`data-v-7e0300ec`],[`__file`,`D:/xz_workspace/github_workspace/zen-gitsync/src/ui/client/src/components/AppVersionBadge.vue`]]);export{te as default};
@@ -1 +0,0 @@
1
- import"./rolldown-runtime-BM3Ffeng.js";import{Dn as e,E as t,Gn as n,Gr as r,It as i,Nt as a,S as o,Sn as s,T as c,Un as l,bn as u,dr as d,er as f,et as p,fn as m,kn as h,kt as g,mn as _,nn as v,q as y,vn as b,x,xn as S,y as C,yn as w,zr as T}from"./element-plus-DhvzaBj9.js";import{n as E,t as D}from"./_plugin-vue_export-helper-SBtt0pJv.js";import{t as O}from"./SvgIcon-uPiOGYUG.js";import{t as k}from"./CommonDialog-CpMIq-Hr.js";import{T as A,w as j}from"./index-CfUzUz7b.js";var M=h({__name:`BranchSelector`,emits:[`branchChanged`],setup(e,{expose:t,emit:n}){t();let r=j(),o=n,s=d(!1),c=d(``),l=b(()=>{let e=c.value.trim().toLowerCase();return e?r.allBranches.filter(t=>t.toLowerCase().includes(e)):r.allBranches});async function u(e){if(e===r.currentBranch){s.value=!1;return}s.value=!1,await r.changeBranch(e)&&(o(`branchChanged`),r.refreshLog())}let f=d(!1),m=d(``),h=d(``);function _(){s.value=!1,h.value=r.currentBranch,f.value=!0}async function v(){await r.createBranch(m.value,h.value)&&(f.value=!1,m.value=``,o(`branchChanged`))}let y=d(!1);async function x(){if(!y.value){y.value=!0;try{await Promise.all([r.getCurrentBranch(!0),r.getBranchStatus(!0),r.getAllBranches()]),o(`branchChanged`),r.refreshLog()}finally{y.value=!1}}}let S={gitStore:r,emit:o,branchPopoverVisible:s,branchFilter:c,filteredBranches:l,selectBranch:u,createBranchDialogVisible:f,newBranchName:m,selectedBaseBranch:h,openCreateBranchDialog:_,createNewBranch:v,isRefreshingBranch:y,refreshCurrentBranch:x,get $t(){return E},get Plus(){return a},get Menu(){return g},get Check(){return p},get Refresh(){return i},CommonDialog:k,IconButton:A,SvgIcon:O};return Object.defineProperty(S,`__isScriptSetup`,{enumerable:!1,value:!0}),S}}),N={key:0,class:`branch-info`},P={class:`branch-wrapper`},F={class:`branch-btn`},I={class:`branch-btn-name`},L={class:`branch-btn-caret`},R={class:`branch-popover-inner`},z={class:`branch-search`},B={class:`branch-list`},V=[`onClick`],H={key:1,class:`branch-item-check-placeholder`},U={class:`branch-item-name`},W={key:0,class:`branch-empty`},G={class:`branch-popover-footer`},K={class:`create-branch-content`},q={class:`form-label`},J={class:`form-label`},Y={class:`dialog-footer`},X={class:`footer-actions`},Z=[`disabled`];function Q(i,a,d,p,h,g){let b=v,E=y,D=C,O=t,k=x,A=o,j=c;return p.gitStore.currentBranch?(l(),s(`div`,N,[w(`div`,P,[S(` VSCode 风格分支按钮 + 弹出列表 `),e(D,{visible:p.branchPopoverVisible,"onUpdate:visible":a[1]||=e=>p.branchPopoverVisible=e,placement:`top-start`,width:260,trigger:`click`,"popper-class":`branch-popover`,"show-arrow":!1,onShow:a[2]||=e=>p.branchFilter=``},{reference:f(()=>[w(`button`,F,[e(p.SvgIcon,{"icon-class":`git-branch`,"class-name":`branch-btn-icon`}),w(`span`,I,r(p.gitStore.currentBranch),1),w(`span`,L,[e(b,null,{default:f(()=>a[7]||=[w(`svg`,{viewBox:`0 0 1024 1024`,xmlns:`http://www.w3.org/2000/svg`},[w(`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:f(()=>[w(`div`,R,[S(` 搜索框 `),w(`div`,z,[e(E,{modelValue:p.branchFilter,"onUpdate:modelValue":a[0]||=e=>p.branchFilter=e,placeholder:p.$t(`@F13B4:搜索或签出分支`),size:`small`,clearable:``,autofocus:``},null,8,[`modelValue`,`placeholder`])]),S(` 分支列表 `),w(`div`,B,[(l(!0),s(_,null,n(p.filteredBranches,t=>(l(),s(`div`,{key:t,class:T([`branch-item`,{"is-active":t===p.gitStore.currentBranch}]),onClick:e=>p.selectBranch(t)},[t===p.gitStore.currentBranch?(l(),u(b,{key:0,class:`branch-item-check`},{default:f(()=>[e(p.Check)]),_:1})):(l(),s(`span`,H)),e(p.SvgIcon,{"icon-class":`git-branch`,"class-name":`branch-item-icon`}),w(`span`,U,r(t),1)],10,V))),128)),p.filteredBranches.length===0?(l(),s(`div`,W,r(p.$t(`@F13B4:没有匹配的分支`)),1)):S(`v-if`,!0)]),S(` 底部:创建新分支 `),w(`div`,G,[w(`button`,{class:`branch-create-btn`,onClick:p.openCreateBranchDialog},[e(b,null,{default:f(()=>[e(p.Plus)]),_:1}),w(`span`,null,r(p.$t(`@F13B4:创建新分支`)),1)])])])]),_:1},8,[`visible`]),S(` 刷新按钮 `),e(p.IconButton,{tooltip:p.$t(`@CMDCON:刷新`),onClick:p.refreshCurrentBranch},{default:f(()=>[e(b,{class:T({"is-loading":p.isRefreshingBranch})},{default:f(()=>[e(p.Refresh)]),_:1},8,[`class`])]),_:1},8,[`tooltip`])]),S(` 创建分支对话框 `),e(p.CommonDialog,{modelValue:p.createBranchDialogVisible,"onUpdate:modelValue":a[6]||=e=>p.createBranchDialogVisible=e,title:p.$t(`@F13B4:创建新分支`),size:`small`,"destroy-on-close":!0,"append-to-body":!0,"custom-class":`create-branch-dialog`},{footer:f(()=>[w(`div`,Y,[w(`div`,X,[w(`button`,{type:`button`,class:`dialog-cancel-btn`,onClick:a[5]||=e=>p.createBranchDialogVisible=!1},r(p.$t(`@F13B4:取消`)),1),w(`button`,{type:`button`,class:`dialog-confirm-btn`,onClick:p.createNewBranch,disabled:p.gitStore.isCreatingBranch},[p.gitStore.isCreatingBranch?(l(),u(b,{key:1,class:`is-loading`},{default:f(()=>a[8]||=[w(`svg`,{viewBox:`0 0 1024 1024`,xmlns:`http://www.w3.org/2000/svg`},[w(`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})):(l(),u(b,{key:0},{default:f(()=>[e(p.Check)]),_:1})),w(`span`,null,r(p.$t(`@F13B4:创建`)),1)],8,Z)])])]),default:f(()=>[w(`div`,K,[e(j,{model:{newBranchName:p.newBranchName,selectedBaseBranch:p.selectedBaseBranch},"label-position":`top`},{default:f(()=>[e(O,null,{label:f(()=>[w(`div`,q,[e(b,{class:`label-icon`},{default:f(()=>[e(p.Plus)]),_:1}),w(`span`,null,r(p.$t(`@F13B4:新分支名称`)),1)])]),default:f(()=>[e(E,{modelValue:p.newBranchName,"onUpdate:modelValue":a[3]||=e=>p.newBranchName=e,placeholder:p.$t(`@F13B4:请输入新分支名称`),class:`modern-input`,size:`large`,onKeyup:m(p.createNewBranch,[`enter`])},null,8,[`modelValue`,`placeholder`])]),_:1}),e(O,null,{label:f(()=>[w(`div`,J,[e(b,{class:`label-icon`},{default:f(()=>[e(p.Menu)]),_:1}),w(`span`,null,r(p.$t(`@F13B4:基于分支`)),1)])]),default:f(()=>[e(A,{modelValue:p.selectedBaseBranch,"onUpdate:modelValue":a[4]||=e=>p.selectedBaseBranch=e,placeholder:p.$t(`@F13B4:选择基础分支`),class:`modern-select`,size:`large`,style:{width:`100%`}},{default:f(()=>[(l(!0),s(_,null,n(p.gitStore.allBranches,e=>(l(),u(k,{key:e,label:e,value:e},null,8,[`label`,`value`]))),128))]),_:1},8,[`modelValue`,`placeholder`])]),_:1})]),_:1},8,[`model`])])]),_:1},8,[`modelValue`,`title`])])):S(`v-if`,!0)}var $=D(M,[[`render`,Q],[`__scopeId`,`data-v-9548a76f`],[`__file`,`D:/xz_workspace/github_workspace/zen-gitsync/src/ui/client/src/components/BranchSelector.vue`]]);export{$ as default};