zen-gitsync 2.16.42 → 2.16.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/ui/public/assets/AgentView-BsN9y4Kg.css +1 -0
- package/src/ui/public/assets/AgentView-Cv9VaSKN.js +4 -0
- package/src/ui/public/assets/AppVersionBadge-BdqGOtBK.js +2 -0
- package/src/ui/public/assets/AppVersionBadge-Dj6H729N.css +1 -0
- package/src/ui/public/assets/BranchSelector-BXJDzZ3f.js +1 -0
- package/src/ui/public/assets/BranchSelector-T_rJm-JY.css +1 -0
- package/src/ui/public/assets/CommitForm-CUvl0VcF.css +1 -0
- package/src/ui/public/assets/CommitForm-DSddCPcu.js +6 -0
- package/src/ui/public/assets/EditorView-BgA-_8WV.css +1 -0
- package/src/ui/public/assets/EditorView-et7aqhjC.js +1 -0
- package/src/ui/public/assets/FlowExecutionViewer-AWaR_biA.js +1 -0
- package/src/ui/public/assets/FlowExecutionViewer-B5kh7VxS.css +1 -0
- package/src/ui/public/assets/FlowOrchestrationWorkspace-BAQDU9Jd.css +1 -0
- package/src/ui/public/assets/FlowOrchestrationWorkspace-RC5DaY3c.js +21 -0
- package/src/ui/public/assets/LogList-puKBzBb8.js +7 -0
- package/src/ui/public/assets/LogList-r8sgFBhZ.css +1 -0
- package/src/ui/public/assets/MindmapView-BcLn10w2.js +1 -0
- package/src/ui/public/assets/MindmapView-CS2t30Ko.css +1 -0
- package/src/ui/public/assets/MonitorView-ChZk7T1l.js +1 -0
- package/src/ui/public/assets/MonitorView-DxFogQIt.css +1 -0
- package/src/ui/public/assets/ProjectStartupButton-B7zvqRj_.css +1 -0
- package/src/ui/public/assets/ProjectStartupButton-BFXo6CRz.js +1 -0
- package/src/ui/public/assets/RemoteRepoCard-DyCD9GBp.js +1 -0
- package/src/ui/public/assets/RemoteRepoCard-d0klonVb.css +1 -0
- package/src/ui/public/assets/SourceMapView-DEgfJ4jb.js +3 -0
- package/src/ui/public/assets/SourceMapView-DG_a_076.css +1 -0
- package/src/ui/public/assets/UserInputNode-D5yO5I4U.js +1 -0
- package/src/ui/public/assets/UserInputNode-DDtvv9Iq.css +1 -0
- package/src/ui/public/assets/WorkbenchView-B1yeYHWg.js +12 -0
- package/src/ui/public/assets/{WorkbenchView-Bp2YaSGS.css → WorkbenchView-CcYljxjT.css} +1 -1
- package/src/ui/public/assets/element-plus-utMlO-GC.js +27 -0
- package/src/ui/public/assets/{flow-mindmap-BqudHiZw.js → flow-mindmap-CatiJi_w.js} +12 -12
- package/src/ui/public/assets/index-B15iIgdD.css +1 -0
- package/src/ui/public/assets/index-C2HMPXsP.js +62 -0
- package/src/ui/public/assets/office-docx-CUC3NjrQ.js +31 -0
- package/src/ui/public/assets/{office-excel-oizyPOWk.js → office-excel-BGq5miA9.js} +1 -1
- package/src/ui/public/assets/office-pptx-CNefnIUm.js +60 -0
- package/src/ui/public/assets/socket-io-ObqgZMoP.js +1 -0
- package/src/ui/public/assets/{vendor-CD2SBxQj.js → vendor-C_cx2RSh.js} +39 -46
- package/src/ui/public/assets/vue-flow-CBbapQM_.js +39 -0
- package/src/ui/public/index.html +7 -7
- package/src/ui/server/index.js +1 -0
- package/src/ui/server/routes/fileOpen.js +248 -20
- package/src/ui/server/routes/fileOpen.test.js +239 -0
- package/src/ui/server/routes/fs.js +4 -0
- package/src/ui/server/routes/gitOps.js +20 -23
- package/src/ui/server/routes/gitRepoFlagSync.test.js +129 -0
- package/src/ui/server/utils/instanceRegistry.js +45 -7
- package/src/ui/server/utils/instanceRegistry.test.js +58 -0
- package/src/utils/index.js +14 -47
- package/src/ui/public/assets/AgentView-8-xsOmE6.css +0 -1
- package/src/ui/public/assets/AgentView-DjZTGHd_.js +0 -4
- package/src/ui/public/assets/AppVersionBadge-CS7Trvp-.css +0 -1
- package/src/ui/public/assets/AppVersionBadge-jSrdjNi3.js +0 -2
- package/src/ui/public/assets/BranchSelector-B0eLkAdv.css +0 -1
- package/src/ui/public/assets/BranchSelector-CNHhFgaH.js +0 -1
- package/src/ui/public/assets/CommitForm-B4rwEBIH.js +0 -6
- package/src/ui/public/assets/CommitForm-DdK8dymc.css +0 -1
- package/src/ui/public/assets/EditorView-D8Zdu0id.js +0 -1
- package/src/ui/public/assets/EditorView-DZMLvQds.css +0 -1
- package/src/ui/public/assets/FlowExecutionViewer-Ci56tI0_.js +0 -1
- package/src/ui/public/assets/FlowExecutionViewer-jNHI5zdm.css +0 -1
- package/src/ui/public/assets/FlowOrchestrationWorkspace-CYGp-PqS.css +0 -1
- package/src/ui/public/assets/FlowOrchestrationWorkspace-WqB1AJl6.js +0 -21
- package/src/ui/public/assets/LogList-BlJplIi1.css +0 -1
- package/src/ui/public/assets/LogList-DkwwTvhf.js +0 -7
- package/src/ui/public/assets/MindmapView-BQTqx842.css +0 -1
- package/src/ui/public/assets/MindmapView-CX1qlHl3.js +0 -2
- package/src/ui/public/assets/MonitorView-TaS4kL0Y.css +0 -1
- package/src/ui/public/assets/MonitorView-fnUCn0oW.js +0 -1
- package/src/ui/public/assets/ProjectStartupButton-DJZiuAuR.js +0 -1
- package/src/ui/public/assets/ProjectStartupButton-QLPltCK5.css +0 -1
- package/src/ui/public/assets/RemoteRepoCard-BRGNlbeO.css +0 -1
- package/src/ui/public/assets/RemoteRepoCard-DFWs9d6t.js +0 -5
- package/src/ui/public/assets/SourceMapView-BNRjvgzH.css +0 -1
- package/src/ui/public/assets/SourceMapView-mCG7TN0y.js +0 -3
- package/src/ui/public/assets/UserInputNode-BVgQkB2l.css +0 -1
- package/src/ui/public/assets/UserInputNode-C6pUnTWe.js +0 -1
- package/src/ui/public/assets/WorkbenchView-DgeUjdHD.js +0 -28
- package/src/ui/public/assets/element-plus-DqggPOBO.js +0 -47
- package/src/ui/public/assets/index-B2a65GAv.css +0 -1
- package/src/ui/public/assets/index-DAgSqrPI.js +0 -104
- package/src/ui/public/assets/office-docx-DOvu6SBd.js +0 -32
- package/src/ui/public/assets/office-pptx-CCaqOF63.js +0 -74
- package/src/ui/public/assets/socket-io-B9UuzwTN.js +0 -1
- package/src/ui/public/assets/vue-flow-BEmfD-aa.js +0 -39
|
@@ -42,6 +42,7 @@ export function registerGitOpsRoutes({
|
|
|
42
42
|
clearCommandHistory,
|
|
43
43
|
checkAndClearGitLock,
|
|
44
44
|
getIsGitRepo,
|
|
45
|
+
setIsGitRepo,
|
|
45
46
|
setRecentPushStatus
|
|
46
47
|
}) {
|
|
47
48
|
// 提交更改
|
|
@@ -948,9 +949,8 @@ export function registerGitOpsRoutes({
|
|
|
948
949
|
// 拼成 origin/<branch> 后进 git 参数位,同样要过 ref 校验
|
|
949
950
|
const safeBranch = assertGitRef(branch, '分支名');
|
|
950
951
|
|
|
951
|
-
//
|
|
952
|
-
//
|
|
953
|
-
// 一个刚崩出来的新孤儿锁挡住)
|
|
952
|
+
// 仅清理能确认持有进程已退出的孤儿锁。原生 Git 的空锁无法确认
|
|
953
|
+
// 持有者状态时必须保留,让 Git 自己返回并发锁错误。
|
|
954
954
|
await checkAndClearGitLock({ force: true });
|
|
955
955
|
|
|
956
956
|
// 执行 git reset --hard origin/branch 命令
|
|
@@ -968,9 +968,8 @@ export function registerGitOpsRoutes({
|
|
|
968
968
|
// 清除本地所有更改,包括未跟踪文件 (git reset --hard && git clean -fd)
|
|
969
969
|
app.post('/api/discard-all-changes', async (req, res) => {
|
|
970
970
|
try {
|
|
971
|
-
//
|
|
972
|
-
//
|
|
973
|
-
// 一个刚崩出来的新孤儿锁挡住)
|
|
971
|
+
// 仅清理能确认持有进程已退出的孤儿锁。原生 Git 的空锁无法确认
|
|
972
|
+
// 持有者状态时必须保留,让 Git 自己返回并发锁错误。
|
|
974
973
|
await checkAndClearGitLock({ force: true });
|
|
975
974
|
|
|
976
975
|
// 1. 执行 git reset --hard 丢弃已跟踪文件的更改
|
|
@@ -1286,23 +1285,15 @@ export function registerGitOpsRoutes({
|
|
|
1286
1285
|
// 添加清理Git锁定文件的接口
|
|
1287
1286
|
app.post('/api/remove-lock', async (req, res) => {
|
|
1288
1287
|
try {
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
} catch (error) {
|
|
1299
|
-
// 如果文件不存在,也返回成功
|
|
1300
|
-
if (error.code === 'ENOENT') {
|
|
1301
|
-
res.json({ success: true, message: '没有发现锁定文件' })
|
|
1302
|
-
} else {
|
|
1303
|
-
throw error
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
1288
|
+
// 统一走带并发保护的锁检查。原生 Git 的 index.lock 通常为空,
|
|
1289
|
+
// 无法确认是否仍被其他工具使用时绝不能直接 unlink。
|
|
1290
|
+
const cleaned = await checkAndClearGitLock({ force: false });
|
|
1291
|
+
res.json({
|
|
1292
|
+
success: cleaned,
|
|
1293
|
+
message: cleaned
|
|
1294
|
+
? '已清理确认失效的锁定文件'
|
|
1295
|
+
: '未自动删除锁定文件:无法确认没有其他 Git 操作正在运行'
|
|
1296
|
+
});
|
|
1306
1297
|
} catch (error) {
|
|
1307
1298
|
logger.error('清理锁定文件失败:', error)
|
|
1308
1299
|
res.status(500).json({
|
|
@@ -1371,6 +1362,12 @@ export function registerGitOpsRoutes({
|
|
|
1371
1362
|
app.post('/api/git-init', async (req, res) => {
|
|
1372
1363
|
try {
|
|
1373
1364
|
const { stdout } = await execGitCommand(['init']);
|
|
1365
|
+
// git init 成功后立即同步服务端 isGitRepo 标志。
|
|
1366
|
+
// 否则该标志仍是启动时检测的 false,后续 /api/add-remote 等以
|
|
1367
|
+
// getIsGitRepo() 做门禁的接口会误报"当前目录不是Git仓库"。
|
|
1368
|
+
if (typeof setIsGitRepo === 'function') {
|
|
1369
|
+
setIsGitRepo(true);
|
|
1370
|
+
}
|
|
1374
1371
|
res.json({ success: true, output: stdout.trim() });
|
|
1375
1372
|
} catch (error) {
|
|
1376
1373
|
logger.error('git init 失败:', error);
|
|
@@ -0,0 +1,129 @@
|
|
|
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
|
+
// src/ui/server/routes/gitOps.js isGitRepo 标志同步回归测试。
|
|
16
|
+
//
|
|
17
|
+
// 背景 bug:服务端 isGitRepo 是启动时一次性检测的变量。用户在非仓库目录
|
|
18
|
+
// 点"初始化Git仓库"(或"初始化并添加远程")后,/api/git-init 真实创建了 .git,
|
|
19
|
+
// 但标志没有同步,/api/add-remote 用 stale 的 getIsGitRepo() 做门禁,
|
|
20
|
+
// 必然报"当前目录不是Git仓库"。
|
|
21
|
+
//
|
|
22
|
+
// 修复:git-init 成功后 setIsGitRepo(true)。
|
|
23
|
+
// 本文件验证:
|
|
24
|
+
// 1. git-init 成功 → setIsGitRepo(true) 被调用
|
|
25
|
+
// 2. 同一实例内 init 后 add-remote 不再被误拦(完整回归场景)
|
|
26
|
+
// 3. 未 init 的目录 add-remote 仍被正确拦截(门禁本身不回退)
|
|
27
|
+
// 4. add-remote 成功路径正确执行 git remote add
|
|
28
|
+
//
|
|
29
|
+
// 测试不真起 Express,直接调 registerGitOpsRoutes 注入 mock app,
|
|
30
|
+
// 触发最后一个注册的 handler(跳过 express.json() 等中间件)。
|
|
31
|
+
import { test } from 'node:test'
|
|
32
|
+
import assert from 'node:assert/strict'
|
|
33
|
+
import { registerGitOpsRoutes } from './gitOps.js'
|
|
34
|
+
|
|
35
|
+
/** 最小 express app mock:post(path, ...fns) 只保留最后一个 handler */
|
|
36
|
+
function makeApp() {
|
|
37
|
+
const handlers = new Map() // key = "METHOD path" → handler
|
|
38
|
+
return {
|
|
39
|
+
get(p, ...fns) { handlers.set(`GET ${p}`, fns[fns.length - 1]) },
|
|
40
|
+
post(p, ...fns) { handlers.set(`POST ${p}`, fns[fns.length - 1]) },
|
|
41
|
+
invoke(method, p, req = { body: {} }, res = makeRes()) {
|
|
42
|
+
const key = `${method} ${p}`
|
|
43
|
+
const handler = handlers.get(key)
|
|
44
|
+
assert.ok(handler, `no handler for ${key}`)
|
|
45
|
+
return handler(req, res, () => {})
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function makeRes() {
|
|
50
|
+
return {
|
|
51
|
+
statusCode: 200,
|
|
52
|
+
payload: null,
|
|
53
|
+
status(c) { this.statusCode = c; return this },
|
|
54
|
+
json(p) { this.payload = p; return this }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 组装依赖:isGitRepo 是可变闭包变量,模拟服务端标志 */
|
|
59
|
+
function setup({ initialRepo = false } = {}) {
|
|
60
|
+
const app = makeApp()
|
|
61
|
+
let isGitRepo = initialRepo
|
|
62
|
+
const gitCalls = []
|
|
63
|
+
registerGitOpsRoutes({
|
|
64
|
+
app,
|
|
65
|
+
execGitCommand: async (args) => {
|
|
66
|
+
gitCalls.push(args)
|
|
67
|
+
if (args[0] === 'init') isGitRepo = true // git init 真实生效
|
|
68
|
+
return { stdout: '' }
|
|
69
|
+
},
|
|
70
|
+
configManager: { getLockedFiles: async () => [] },
|
|
71
|
+
execGitAddWithLockFilter: async () => ({}),
|
|
72
|
+
addCommandToHistory: () => {},
|
|
73
|
+
clearCommandHistory: () => {},
|
|
74
|
+
checkAndClearGitLock: async () => true,
|
|
75
|
+
getIsGitRepo: () => isGitRepo,
|
|
76
|
+
setIsGitRepo: (v) => { isGitRepo = v },
|
|
77
|
+
setRecentPushStatus: () => {}
|
|
78
|
+
})
|
|
79
|
+
return { app, gitCalls, getFlag: () => isGitRepo }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ========== 回归:初始化后立即添加远程 ==========
|
|
83
|
+
|
|
84
|
+
test('gitRepoFlagSync: git-init 成功后同步 setIsGitRepo(true)', async () => {
|
|
85
|
+
const { app, getFlag } = setup({ initialRepo: false })
|
|
86
|
+
const res = makeRes()
|
|
87
|
+
await app.invoke('POST', '/api/git-init', { body: {} }, res)
|
|
88
|
+
assert.equal(res.payload.success, true)
|
|
89
|
+
assert.equal(getFlag(), true, 'git-init 成功后服务端标志应立即翻为 true')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('gitRepoFlagSync: 初始化后立即添加远程不再报"当前目录不是Git仓库"', async () => {
|
|
93
|
+
const { app, gitCalls } = setup({ initialRepo: false })
|
|
94
|
+
// 第一步:初始化(模拟"初始化并添加远程"按钮的第一步)
|
|
95
|
+
const initRes = makeRes()
|
|
96
|
+
await app.invoke('POST', '/api/git-init', { body: {} }, initRes)
|
|
97
|
+
assert.equal(initRes.payload.success, true)
|
|
98
|
+
// 第二步:添加远程(修复前此处被 stale 标志拦下)
|
|
99
|
+
const addRes = makeRes()
|
|
100
|
+
await app.invoke('POST', '/api/add-remote', {
|
|
101
|
+
body: { name: 'origin', url: 'git@gitee.com:flowdash/home2026.git' }
|
|
102
|
+
}, addRes)
|
|
103
|
+
assert.equal(addRes.payload.success, true,
|
|
104
|
+
`add-remote 应成功,实际: ${JSON.stringify(addRes.payload)}`)
|
|
105
|
+
assert.deepEqual(gitCalls[gitCalls.length - 1],
|
|
106
|
+
['remote', 'add', 'origin', 'git@gitee.com:flowdash/home2026.git'])
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('gitRepoFlagSync: 未初始化时 add-remote 仍被门禁拦截', async () => {
|
|
110
|
+
const { app, gitCalls } = setup({ initialRepo: false })
|
|
111
|
+
const res = makeRes()
|
|
112
|
+
await app.invoke('POST', '/api/add-remote', {
|
|
113
|
+
body: { name: 'origin', url: 'git@gitee.com:flowdash/home2026.git' }
|
|
114
|
+
}, res)
|
|
115
|
+
assert.equal(res.payload.success, false)
|
|
116
|
+
assert.equal(res.payload.error, '当前目录不是Git仓库')
|
|
117
|
+
assert.equal(gitCalls.length, 0, '非 git 仓库不应执行任何 git 子命令')
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('gitRepoFlagSync: add-remote 正常路径执行 git remote add', async () => {
|
|
121
|
+
const { app, gitCalls } = setup({ initialRepo: true })
|
|
122
|
+
const res = makeRes()
|
|
123
|
+
await app.invoke('POST', '/api/add-remote', {
|
|
124
|
+
body: { name: 'origin', url: 'https://gitee.com/x/y.git' }
|
|
125
|
+
}, res)
|
|
126
|
+
assert.equal(res.payload.success, true)
|
|
127
|
+
assert.deepEqual(gitCalls[gitCalls.length - 1],
|
|
128
|
+
['remote', 'add', 'origin', 'https://gitee.com/x/y.git'])
|
|
129
|
+
})
|
|
@@ -139,19 +139,46 @@ export function createInstanceRegistry({
|
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
// 读单个 entry
|
|
142
|
+
// 读单个 entry 文件。返回值三种:
|
|
143
|
+
// { entry } → 解析成功且为对象
|
|
144
|
+
// { corrupt: true }→ 文件存在但内容损坏(非合法 JSON / 非对象)
|
|
145
|
+
// null → 文件不存在或读取失败(IO 错误,不动文件)
|
|
146
|
+
//
|
|
147
|
+
// 损坏来源(Windows 实测):进程被强杀 / 断电时文件长度已扩展但数据未落盘,
|
|
148
|
+
// 留下全 \x00 的文件。这类文件永远解析失败,若不清理会永久堆积,
|
|
149
|
+
// 且每次 list()(监控轮询约 5 秒一次)都打一条 warn 刷屏。
|
|
143
150
|
async function readEntry(pid) {
|
|
144
151
|
const fp = entryFilePath(pid);
|
|
152
|
+
let raw;
|
|
145
153
|
try {
|
|
146
|
-
|
|
147
|
-
const parsed = JSON.parse(raw);
|
|
148
|
-
if (parsed && typeof parsed === 'object') return parsed;
|
|
149
|
-
return null;
|
|
154
|
+
raw = await fsMod.readFile(fp, 'utf-8');
|
|
150
155
|
} catch (err) {
|
|
151
156
|
if (err && (err.code === 'ENOENT' || err.code === 'ENOTDIR')) return null;
|
|
152
157
|
logger.warn(`[instanceRegistry] 读 entry pid=${pid} 失败: ${err?.message || err}`);
|
|
153
158
|
return null;
|
|
154
159
|
}
|
|
160
|
+
try {
|
|
161
|
+
const parsed = JSON.parse(raw);
|
|
162
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
163
|
+
return { entry: parsed };
|
|
164
|
+
}
|
|
165
|
+
return { corrupt: true };
|
|
166
|
+
} catch (err) {
|
|
167
|
+
logger.warn(`[instanceRegistry] entry pid=${pid} 内容损坏(非合法 JSON),将被清理: ${err?.message || err}`);
|
|
168
|
+
return { corrupt: true };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// 尽力删除损坏的 entry 文件(失败不抛,只留 warn,下轮再试)
|
|
173
|
+
async function removeCorruptEntry(pid) {
|
|
174
|
+
try {
|
|
175
|
+
await fsMod.unlink(entryFilePath(pid));
|
|
176
|
+
logger.info(`[instanceRegistry] 已清理损坏的 entry 文件 pid=${pid}`);
|
|
177
|
+
} catch (err) {
|
|
178
|
+
if (err && err.code !== 'ENOENT') {
|
|
179
|
+
logger.warn(`[instanceRegistry] 清理损坏 entry pid=${pid} 失败: ${err?.message || err}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
155
182
|
}
|
|
156
183
|
|
|
157
184
|
// 原子写单个 entry 文件:tmp + rename
|
|
@@ -189,7 +216,13 @@ export function createInstanceRegistry({
|
|
|
189
216
|
for (const name of names) {
|
|
190
217
|
if (!isEntryFileName(name)) continue;
|
|
191
218
|
const pid = pidFromFileName(name);
|
|
192
|
-
const
|
|
219
|
+
const res = await readEntry(pid);
|
|
220
|
+
if (res && res.corrupt) {
|
|
221
|
+
// 损坏文件直接删掉,自愈,避免永久堆积 + 每 5 秒刷 warn
|
|
222
|
+
await removeCorruptEntry(pid);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const entry = res?.entry;
|
|
193
226
|
if (entry && Number.isInteger(entry.pid)) {
|
|
194
227
|
instances[String(entry.pid)] = entry;
|
|
195
228
|
}
|
|
@@ -320,7 +353,12 @@ export function createInstanceRegistry({
|
|
|
320
353
|
await ensureDir();
|
|
321
354
|
await migrateLegacy();
|
|
322
355
|
|
|
323
|
-
const
|
|
356
|
+
const res = await readEntry(pid);
|
|
357
|
+
if (res && res.corrupt) {
|
|
358
|
+
// 损坏文件先清掉,按"条目丢失"走自愈重建
|
|
359
|
+
await removeCorruptEntry(pid);
|
|
360
|
+
}
|
|
361
|
+
const existing = res?.entry || null;
|
|
324
362
|
let entry;
|
|
325
363
|
if (!existing) {
|
|
326
364
|
const canRebuild =
|
|
@@ -298,6 +298,64 @@ test('list: prune 后删除对应 entry 文件', async () => {
|
|
|
298
298
|
assert.equal(exists, false, '僵尸 entry 文件应被删');
|
|
299
299
|
})
|
|
300
300
|
|
|
301
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
302
|
+
// 损坏 entry 自愈(Windows 强杀/断电留下全 \x00 文件的真实场景)
|
|
303
|
+
// ──────────────────────────────────────────────────────────────────────────────
|
|
304
|
+
|
|
305
|
+
test('list: 损坏的 entry 文件被自动清理,不再出现在结果里', async () => {
|
|
306
|
+
const { registry, fs, registryPath } = setup({ alivePids: new Set([100]) });
|
|
307
|
+
|
|
308
|
+
// 模拟强杀后留下的全 \x00 损坏文件
|
|
309
|
+
await fs.writeFile(`${registryPath}/14400.json`, '\x00'.repeat(224), 'utf-8');
|
|
310
|
+
// 合法 JSON 但不是对象(比如被写成数组/字符串)也算损坏
|
|
311
|
+
await fs.writeFile(`${registryPath}/38520.json`, JSON.stringify([1, 2, 3]), 'utf-8');
|
|
312
|
+
// 正常条目不受影响
|
|
313
|
+
await fs.writeFile(`${registryPath}/100.json`, JSON.stringify({
|
|
314
|
+
pid: 100, port: 9001, projectName: 'live', projectPath: '/live',
|
|
315
|
+
startedAt: Date.now(), lastHeartbeat: Date.now(), hostname: 'h'
|
|
316
|
+
}), 'utf-8');
|
|
317
|
+
|
|
318
|
+
const arr = await registry.list();
|
|
319
|
+
assert.equal(arr.length, 1, '损坏条目不应出现在 list 结果里');
|
|
320
|
+
assert.equal(arr[0].pid, 100);
|
|
321
|
+
|
|
322
|
+
// 损坏文件应被删除
|
|
323
|
+
const names = await fs.readdir(registryPath);
|
|
324
|
+
assert.ok(!names.includes('14400.json'), '全 \\x00 损坏文件应被清理');
|
|
325
|
+
assert.ok(!names.includes('38520.json'), '非对象 JSON 文件应被清理');
|
|
326
|
+
assert.ok(names.includes('100.json'), '正常文件不受影响');
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
test('list: 损坏文件清理后不再重复打警告(文件已消失)', async () => {
|
|
330
|
+
const { registry, fs, registryPath } = setup();
|
|
331
|
+
await fs.writeFile(`${registryPath}/14400.json`, '\x00'.repeat(224), 'utf-8');
|
|
332
|
+
|
|
333
|
+
await registry.list(); // 第一次:warn 一次 + 清理
|
|
334
|
+
const names1 = await fs.readdir(registryPath);
|
|
335
|
+
assert.ok(!names1.includes('14400.json'));
|
|
336
|
+
|
|
337
|
+
await registry.list(); // 第二次:文件已不在,零警告零报错
|
|
338
|
+
const arr = await registry.list();
|
|
339
|
+
assert.equal(arr.length, 0);
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
test('heartbeat: 条目文件损坏时清理并自愈重建', async () => {
|
|
343
|
+
const { registry, fs, registryPath } = setup();
|
|
344
|
+
await fs.writeFile(`${registryPath}/100.json`, '\x00'.repeat(224), 'utf-8');
|
|
345
|
+
|
|
346
|
+
await registry.heartbeat(100, {
|
|
347
|
+
port: 9876,
|
|
348
|
+
projectPath: '/path/1',
|
|
349
|
+
projectName: 'p1',
|
|
350
|
+
hostname: 'host1'
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
const entry = await readEntry(fs, registryPath, 100);
|
|
354
|
+
assert.ok(entry, '损坏条目应被重建');
|
|
355
|
+
assert.equal(entry.pid, 100);
|
|
356
|
+
assert.equal(entry.port, 9876);
|
|
357
|
+
})
|
|
358
|
+
|
|
301
359
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
302
360
|
// 关键:并发安全
|
|
303
361
|
// ──────────────────────────────────────────────────────────────────────────────
|
package/src/utils/index.js
CHANGED
|
@@ -362,32 +362,17 @@ function execGitCommand(command, options = {}) {
|
|
|
362
362
|
* - ESRCH(无此进程)→ 锁是孤儿 → 进入清理
|
|
363
363
|
* - EPERM(进程存在但无权限)→ 锁被别人持有且我们是其他用户 → 跳过
|
|
364
364
|
* 2. 锁文件内容不是 PID(原生 git 通过 O_CREAT|O_EXCL 创建空文件作锁,
|
|
365
|
-
*
|
|
366
|
-
*
|
|
367
|
-
* - force=true:绕过 mtime 判断直接视为陈旧(用于破坏性入口,
|
|
368
|
-
* 如 reset --hard / discard-all-changes — 用户主动触发,
|
|
369
|
-
* 不该被一个刚崩出来的新孤儿锁挡住)
|
|
365
|
+
* 内容几乎总为空):无法从锁文件本身确认持有者是否仍在运行,因此永不自动删除。
|
|
366
|
+
* mtime 只能用于诊断,不能证明锁已失效;长时间运行的 git 操作同样可能保持旧 mtime。
|
|
370
367
|
*
|
|
371
|
-
* 注:force
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
* 注:阈值取 5min 而非 60s,是为大仓库的 git add -A / stash / gc 等长操作
|
|
375
|
-
* 留出空间 — 60s 阈值下用户在手抖触发清理时可能误删活跃锁,
|
|
376
|
-
* 导致 .git/index 半写损坏。5min 是 GitHub Desktop / Sourcetree 等
|
|
377
|
-
* GUI 客户端常用的陈旧判定阈值。
|
|
368
|
+
* 注:force 参数保留用于兼容现有调用方,但不会绕过并发保护。无法确认锁的
|
|
369
|
+
* 持有者时宁可让 Git 返回 lock 错误,也不删除锁破坏 Git 的互斥机制。
|
|
378
370
|
*
|
|
379
371
|
* @param {Object} [options]
|
|
380
|
-
* @param {boolean} [options.force=false]
|
|
381
|
-
* 注意:force 不影响 PID liveness 校验——活跃进程持有的锁永远不会被删。
|
|
372
|
+
* @param {boolean} [options.force=false] 兼容参数;当前不会绕过并发安全检查。
|
|
382
373
|
* @returns {Promise<boolean>} 是否清理成功
|
|
383
374
|
*/
|
|
384
375
|
async function checkAndClearGitLock({ force = false } = {}) {
|
|
385
|
-
// 锁文件"陈旧"阈值(毫秒):5min 是经验值。
|
|
386
|
-
// - 大仓库 git add -A / stash / gc 单次可能 > 60s,60s 阈值会误删活跃锁;
|
|
387
|
-
// - 5min 与 GitHub Desktop / Sourcetree 等 GUI 客户端的陈旧判定一致;
|
|
388
|
-
// - 只有真正崩溃/被 kill -9 留下的孤儿锁才会存在这么久。
|
|
389
|
-
// - force=true 时此阈值被绕过,但仍受 PID liveness 校验保护。
|
|
390
|
-
const STALE_LOCK_THRESHOLD_MS = 5 * 60_000;
|
|
391
376
|
try {
|
|
392
377
|
const cwd = getCwd();
|
|
393
378
|
let gitRoot;
|
|
@@ -447,33 +432,15 @@ async function checkAndClearGitLock({ force = false } = {}) {
|
|
|
447
432
|
}
|
|
448
433
|
}
|
|
449
434
|
} else {
|
|
450
|
-
//
|
|
451
|
-
//
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
const ageMs = Date.now() - stat.mtimeMs;
|
|
460
|
-
console.log(chalk.gray(`ℹ️ force=true,绕过 mtime 判断清理 Git 锁(年龄 ${Math.round(ageMs / 1000)}s)`));
|
|
461
|
-
} catch (_) {}
|
|
462
|
-
isStale = true;
|
|
463
|
-
} else {
|
|
464
|
-
try {
|
|
465
|
-
const stat = await fs.stat(lockFilePath);
|
|
466
|
-
const ageMs = Date.now() - stat.mtimeMs;
|
|
467
|
-
if (ageMs > STALE_LOCK_THRESHOLD_MS) {
|
|
468
|
-
isStale = true;
|
|
469
|
-
} else {
|
|
470
|
-
console.log(chalk.yellow(`⚠️ Git 锁文件较新(${Math.round(ageMs / 1000)}s),保守跳过清理`));
|
|
471
|
-
return false;
|
|
472
|
-
}
|
|
473
|
-
} catch (_) {
|
|
474
|
-
return false;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
435
|
+
// 原生 Git 的锁通常是空文件,无法从内容判断持有进程是否已退出。
|
|
436
|
+
// mtime 不能证明锁已失效(长时间运行的 git 操作也可能不更新 mtime),
|
|
437
|
+
// 因此包括 force=true 在内都不自动删除,让 Git 自己维持互斥保护。
|
|
438
|
+
try {
|
|
439
|
+
const stat = await fs.stat(lockFilePath);
|
|
440
|
+
const ageSeconds = Math.max(0, Math.round((Date.now() - stat.mtimeMs) / 1000));
|
|
441
|
+
console.log(chalk.yellow(`⚠️ 无法确认 Git 锁持有进程(年龄 ${ageSeconds}s),跳过自动清理`));
|
|
442
|
+
} catch (_) {}
|
|
443
|
+
return false;
|
|
477
444
|
}
|
|
478
445
|
|
|
479
446
|
if (!isStale) return false;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.agent-view[data-v-bd681ddd]{background:var(--bg-container);height:100%;min-height:0;display:flex}.agent-sidebar[data-v-bd681ddd]{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-bd681ddd]{flex-shrink:0;padding:10px 12px}.new-session-btn[data-v-bd681ddd]{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-bd681ddd]:hover{border-color:var(--color-primary);color:var(--color-primary);background:color-mix(in srgb, var(--color-primary) 6%, transparent)}.sidebar-search[data-v-bd681ddd]{flex-shrink:0;align-items:center;gap:6px;padding:0 12px 8px;display:flex}.sidebar-search .search-icon[data-v-bd681ddd]{color:var(--text-tertiary);flex-shrink:0}.sidebar-search .search-input[data-v-bd681ddd]{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-bd681ddd]::placeholder{color:var(--text-tertiary)}.session-list[data-v-bd681ddd]{flex:1;min-height:0;padding:0 6px 8px;overflow-y:auto}.empty-state[data-v-bd681ddd]{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-bd681ddd]{color:var(--text-secondary);margin-top:10px;font-size:14px}.empty-state .empty-hint[data-v-bd681ddd]{margin-top:4px;font-size:12px}.session-item[data-v-bd681ddd]{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-bd681ddd]:hover{background:var(--bg-hover)}.session-item:hover .session-item-actions[data-v-bd681ddd]{opacity:1}.session-item.active[data-v-bd681ddd]{background:color-mix(in srgb, var(--color-primary) 10%, transparent)}.session-item.active .session-item-title[data-v-bd681ddd]{color:var(--color-primary)}.session-item.active[data-v-bd681ddd]: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-bd681ddd]{flex:1;min-width:0}.session-item-title[data-v-bd681ddd]{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-bd681ddd]{color:var(--text-tertiary);align-items:center;gap:4px;font-size:11px;display:flex}.session-item-meta .meta-dot[data-v-bd681ddd]{opacity:.5}.session-item-meta .meta-source[data-v-bd681ddd]{border-radius:3px;padding:0 4px;font-size:10px;font-weight:600}.session-item-meta .meta-source.cli[data-v-bd681ddd]{background:color-mix(in srgb, var(--color-warning) 20%, transparent);color:var(--color-warning)}.session-item-actions[data-v-bd681ddd]{opacity:0;flex-shrink:0;gap:2px;transition:opacity .12s;display:flex}.item-action-btn[data-v-bd681ddd]{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-bd681ddd]:hover{background:var(--bg-hover);color:var(--text-secondary)}.item-action-btn.danger[data-v-bd681ddd]:hover{color:var(--color-danger);background:color-mix(in srgb, var(--color-danger) 10%, transparent)}.sidebar-resizer[data-v-bd681ddd]{cursor:col-resize;background:0 0;flex-shrink:0;width:4px;transition:background .15s}.sidebar-resizer[data-v-bd681ddd]:hover,.sidebar-resizer.active[data-v-bd681ddd]{background:var(--color-primary);opacity:.3}.agent-chat-area[data-v-bd681ddd]{flex-direction:column;flex:1;min-width:0;display:flex;position:relative;overflow:hidden}.chat-loading[data-v-bd681ddd]{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-bd681ddd]{z-index:10;position:absolute;bottom:80px;left:50%;transform:translate(-50%)}.stop-button[data-v-bd681ddd]{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-bd681ddd]:hover{background:var(--color-danger);color:#fff}.fade-enter-active[data-v-bd681ddd],.fade-leave-active[data-v-bd681ddd]{transition:opacity .2s}.fade-enter-from[data-v-bd681ddd],.fade-leave-to[data-v-bd681ddd]{opacity:0}[data-theme=dark]{background:var(--bg-panel)}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import"./rolldown-runtime-BM3Ffeng.js";import{Bt as e,Dn as t,Et as n,G as r,Gn as i,Gr as a,In as o,Pt as s,Qn as c,Sn as l,Un as u,Vr as d,Y as f,a as p,dr as m,er as h,et as g,ht as _,i as v,kn as y,lr as b,lt as ee,mn as x,o as S,pn as C,rn as w,tr as T,un as E,vn as D,xn as O,yn as k,zr as A}from"./element-plus-DqggPOBO.js";import{a as j,r as M,t as N}from"./vendor-CD2SBxQj.js";import{D as P,N as F,j as I}from"./index-DAgSqrPI.js";var L=64*1024;function R(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:V(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>L&&(a=`…(前文已截断)\n${a.slice(-L)}`);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||M(),name:e.function?.name||``,arguments:e.function?.arguments||``,argsPreview:z(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>L&&(e=`…(前文已截断)\n${e.slice(-L)}`),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 z(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 B(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 V(e){let t=/^data:([^;,]+)/.exec(e);return t?t[1]:``}function H(){let e=P(),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){p.error(n.error||F(`@AGENT:加载会话列表失败`));return}t.value=Array.isArray(n.sessions)?n.sessions:[]}catch(e){if(r!==l)return;p.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){p.error(t.error||F(`@AGENT:加载会话失败`));return}i.value=R(t.session)}catch(e){p.error(F(`@AGENT:加载会话失败`)+`: `+(e?.message||e))}finally{o.value=!1}}async function f(e){try{await v.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){p.error(n.error||F(`@AGENT:删除失败`));return}t.value=t.value.filter(t=>t.sessionId!==e),r.value===e&&(r.value=null,i.value=[]),p.success(F(`@AGENT:已删除`))}catch(e){p.error(F(`@AGENT:删除失败`)+`: `+(e?.message||e))}}async function h(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){p.error(r.error||F(`@AGENT:重命名失败`));return}let i=t.value.find(t=>t.sessionId===e);i&&(i.title=n),p.success(F(`@AGENT:已重命名`))}catch(e){p.error(F(`@AGENT:重命名失败`)+`: `+(e?.message||e))}}function g(){r.value=null,i.value=[]}async function _(t,n=[]){let o=n.filter(e=>e?.file?.type?.startsWith(`image/`));if(o.length<n.length&&p.warning(F(`@AGENT:仅支持发送图片,非图片文件已忽略`)),!t.trim()&&o.length===0||a.value)return;let l=(await Promise.allSettled(o.map(e=>B(e.file)))).map(e=>e.status===`fulfilled`?e.value:``).filter(e=>e.startsWith(`data:image/`)),d=++c;s=new AbortController;let f=s,m={id:M(),role:`user`,content:t,status:`done`,createdAt:Date.now()};o.length>0&&(m.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(m);let h=b({id:M(),role:`assistant`,content:``,status:`pending`,createdAt:Date.now()});i.value.push(h),a.value=!0;let g=[];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`:h.reasoning||(h.reasoning=``,h.reasoningStatus=`streaming`),h.reasoning+=String(i.delta||``),h.status=`streaming`;break;case`content`:h.status===`pending`&&(h.status=`streaming`);{let e=String(i.delta||``);h.content+=e;let t=N(h.content);t.reasoning&&(h.reasoning||(h.reasoning=``,h.reasoningStatus=`streaming`),h.reasoning=t.reasoning,h.content=t.content)}break;case`tool_call_start`:{let e=b({id:i.toolCallId||M(),name:i.name||``,argsPreview:i.argsPreview||``,arguments:i.argsPreview||``,status:`running`,result:``});g.push(e),h.toolCalls||=[],h.toolCalls.push(e),h.status===`pending`&&(h.status=`streaming`);break}case`tool_result`:{let e=g.find(e=>e.id===i.toolCallId);e&&(e.result=String(i.result||``),e.status=`done`);break}case`done`:{let e=N(i.content||h.content);e.reasoning&&!h.reasoning&&(h.reasoning=e.reasoning),h.content=e.content,h.status=`done`,h.reasoningStatus===`streaming`&&(h.reasoningStatus=`done`);break}case`error`:h.status=`error`,h.error=String(i.error||F(`@AGENT:未知错误`)),h.reasoningStatus===`streaming`&&(h.reasoningStatus=`done`);break}}i.value=[...i.value]}if(d!==c)return;h.status===`pending`&&(h.status=`done`),p&&p!==r.value&&(r.value=p),u().catch(()=>{})}catch(e){if(d!==c)return;e?.name===`AbortError`||f.signal.aborted?(h.content=(h.content||``)+`
|
|
3
|
-
|
|
4
|
-
[`+F(`@AGENT:已停止`)+`]`,h.status=`done`,h.reasoningStatus===`streaming`&&(h.reasoningStatus=`done`)):(h.status=`error`,h.error=e?.message||String(e),p.error(h.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:h,newSession:g,sendMessage:_,stop:y}}var U=y({__name:`AgentView`,setup(t,{expose:i}){i();let a=P(),{sessions:l,sessionsLoading:u,currentSessionId:d,messages:h,isStreaming:y,sessionLoading:b,loadSessions:x,loadSession:S,deleteSession:C,renameSession:w,newSession:T,sendMessage:E,stop:O}=H(),k=D(()=>{let e=a.theme;return e===`dark`||e===`auto`&&window.matchMedia(`(prefers-color-scheme: dark)`).matches?`dark`:`light`}),A=m(``),M=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))}),N=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(),z()}async function L(e){await E(e.prompt),await o(),z()}let R=m(null);function z(e=!0){R.value?.scrollToBottom(e)}c(()=>h.value.length,()=>{o(()=>z(!1))});function B(e){if(y.value){p.warning(F(`@AGENT:请先停止当前生成`));return}S(e)}function V(){if(y.value){p.warning(F(`@AGENT:请先停止当前生成`));return}T()}function U(e,t){if(t.stopPropagation(),y.value&&d.value===e){p.warning(F(`@AGENT:请先停止当前生成`));return}C(e)}async function W(e,t,n){n.stopPropagation();try{let{value:n}=await v.prompt(F(`@AGENT:请输入新的会话标题`),F(`@AGENT:重命名会话`),{inputValue:t,inputPattern:/.+/,inputErrorMessage:F(`@AGENT:标题不能为空`)});n&&n!==t&&await w(e,n)}catch{}}function G(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 K=m(280),q=m(!1);function J(e){q.value=!0;let t=e.clientX,n=K.value,r=e=>{let r=e.clientX-t;K.value=Math.max(200,Math.min(500,n+r))},i=()=>{q.value=!1,document.removeEventListener(`mousemove`,r),document.removeEventListener(`mouseup`,i)};document.addEventListener(`mousemove`,r),document.addEventListener(`mouseup`,i)}c(()=>[a.currentDirectory,y.value],async([e,t],n)=>{let r=!n||e!==n[0],i=!!n&&n[1]===!0&&!t;(r||i)&&await x(),d.value&&!y.value&&!l.value.some(e=>e.sessionId===d.value)&&T()},{immediate:!0});let Y={configStore:a,sessions:l,sessionsLoading:u,currentSessionId:d,messages:h,isStreaming:y,sessionLoading:b,loadSessions:x,loadSession:S,deleteSession:C,renameSession:w,newSession:T,sendMessage:E,stop:O,chatTheme:k,searchQuery:A,filteredSessions:M,presetQuestions:N,onSend:I,onSelectPreset:L,chatContainerRef:R,scrollToBottom:z,selectSession:B,handleNewSession:V,handleDelete:U,handleRename:W,formatDate:G,sidebarWidth:K,isResizing:q,startResize:J,get $t(){return F},get ElTooltip(){return r},get ElIcon(){return f},get Plus(){return s},get Search(){return e},get Delete(){return ee},get Edit(){return _},get ChatLineRound(){return g},get Loading(){return n},get ChatContainer(){return j}};return Object.defineProperty(Y,`__isScriptSetup`,{enumerable:!1,value:!0}),Y}}),W={class:`agent-view`},G={class:`sidebar-header`},K={class:`sidebar-search`},q=[`placeholder`],J={class:`session-list`},Y={key:0,class:`empty-state`},X={class:`empty-text`},Z={key:0,class:`empty-hint`},Q=[`onClick`],te={class:`session-item-main`},ne={class:`session-item-title`},re={class:`session-item-meta`},ie={class:`meta-time`},ae={class:`meta-count`},$={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 f=S;return u(),l(`div`,W,[O(` ═══ 左侧:会话列表 ═══ `),k(`aside`,{class:`agent-sidebar`,style:d({width:o.sidebarWidth+`px`})},[O(` 顶部操作栏 `),k(`div`,G,[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`,K,[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,q),[[E,o.searchQuery]])]),O(` 会话列表 `),T((u(),l(`div`,J,[o.filteredSessions.length===0&&!o.sessionsLoading?(u(),l(`div`,Y,[t(o.ElIcon,{size:28,color:`var(--text-tertiary)`},{default:h(()=>[t(o.ChatLineRound)]),_:1}),k(`div`,X,a(o.searchQuery?o.$t(`@AGENT:未找到匹配的会话`):o.$t(`@AGENT:暂无会话`)),1),o.searchQuery?O(`v-if`,!0):(u(),l(`div`,Z,a(o.$t(`@AGENT:点击上方按钮开始对话`)),1))])):O(`v-if`,!0),(u(!0),l(x,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`,te,[k(`div`,ne,a(e.title||o.$t(`@AGENT:无标题`)),1),k(`div`,re,[k(`span`,ie,a(o.formatDate(e.updatedAt)),1),n[3]||=k(`span`,{class:`meta-dot`},`·`,-1),k(`span`,ae,a(e.messageCount)+` `+a(o.$t(`@AGENT:条`)),1),e.source===`cli`?(u(),l(`span`,$,`CLI`)):O(`v-if`,!0)])]),k(`div`,{class:`session-item-actions`,onClick:n[1]||=C(()=>{},[`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,Q))),128))])),[[f,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(x,{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(U,[[`render`,de],[`__scopeId`,`data-v-bd681ddd`],[`__file`,`C:/workspace/github_workspace/zen-gitsync/src/ui/client/src/views/AgentView.vue`]]);export{fe as default};
|
|
@@ -1 +0,0 @@
|
|
|
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}@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,2 +0,0 @@
|
|
|
1
|
-
import"./rolldown-runtime-BM3Ffeng.js";import{B as e,D as t,Dn as n,En as r,Et as i,G as a,Gr as o,In as s,Qn as c,Sn as l,Un as u,Vn as d,Y as f,a as p,bn as m,dr as h,er as g,i as _,kn as v,nt as y,rt as b,vn as x,xn as S,yn as C,zn as w,zr as T}from"./element-plus-DqggPOBO.js";import{N as E,j as D}from"./index-DAgSqrPI.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=v({__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:n}){t();let r=e,a=n,o=h(null);c(()=>r.logs,async()=>{await s(),o.value&&(o.value.scrollTop=o.value.scrollHeight)});function l(){r.status!==`running`&&(r.status===`success`&&r.countdown>0&&a(`cancel`),a(`update:modelValue`,!1))}function u(){a(`retry`)}function d(){a(`restart`)}function f(){a(`cancel`),a(`update:modelValue`,!1)}let p={props:r,emit:a,logEl:o,close:l,onRetry:u,onRestart:d,onLater:f,get Loading(){return i},get CircleCheck(){return y},get CircleClose(){return b},get $t(){return E}};return Object.defineProperty(p,`__isScriptSetup`,{enumerable:!1,value:!0}),p}}),N={key:3},P={key:4},F={key:5},I={key:0,class:`upgrade-hint`},L={key:0,class:`upgrade-countdown`};function R(i,a,s,c,d,p){let h=f,_=e,v=t;return u(),m(v,{"model-value":s.modelValue,"onUpdate:modelValue":a[0]||=e=>c.emit(`update:modelValue`,e),title:c.$t(`@F13B4:升级`),width:`680px`,"close-on-click-modal":!1,"close-on-press-escape":!1,"show-close":s.status!==`running`,"append-to-body":``,onClose:c.close},{footer:g(()=>[s.status===`failed`?(u(),m(_,{key:0,type:`primary`,onClick:c.onRetry},{default:g(()=>[r(o(c.$t(`@F13B4:重试`)),1)]),_:1})):S(`v-if`,!0),s.status===`success`?(u(),m(_,{key:1,type:`primary`,onClick:c.onRestart},{default:g(()=>[r(o(c.$t(`@F13B4:立即重启并刷新`)),1)]),_:1})):S(`v-if`,!0),s.status===`success`?(u(),m(_,{key:2,onClick:c.onLater},{default:g(()=>[r(o(c.$t(`@F13B4:稍后手动重启`)),1)]),_:1})):S(`v-if`,!0),s.status===`running`?(u(),m(_,{key:3,disabled:``},{default:g(()=>[r(o(c.$t(`@F13B4:升级中`))+`... `,1)]),_:1})):S(`v-if`,!0)]),default:g(()=>[C(`div`,{class:T([`upgrade-status`,`is-${s.status}`])},[s.status===`running`?(u(),m(h,{key:0,class:`is-loading`},{default:g(()=>[n(c.Loading)]),_:1})):s.status===`success`?(u(),m(h,{key:1,color:`#67c23a`},{default:g(()=>[n(c.CircleCheck)]),_:1})):(u(),m(h,{key:2,color:`#f56c6c`},{default:g(()=>[n(c.CircleClose)]),_:1})),s.status===`running`?(u(),l(`span`,N,o(c.$t(`@F13B4:升级中`)),1)):s.status===`success`?(u(),l(`span`,P,o(c.$t(`@F13B4:升级完成`)),1)):(u(),l(`span`,F,o(c.$t(`@F13B4:升级失败`)),1))],2),s.status===`success`?(u(),l(`p`,I,[r(o(c.$t(`@F13B4:新版本已全局安装,需要重启服务才能生效`))+` `,1),s.countdown>0?(u(),l(`span`,L,o(c.$t(`@F13B4:{seconds} 秒后自动刷新`,{seconds:s.countdown})),1)):S(`v-if`,!0)])):S(`v-if`,!0),C(`pre`,{ref:`logEl`,class:`upgrade-log`},o(s.logs||c.$t(`@F13B4:等待日志输出`)),513)]),_:1},8,[`model-value`,`title`,`show-close`])}var z=D(M,[[`render`,R],[`__scopeId`,`data-v-bc566bba`],[`__file`,`C:/workspace/github_workspace/zen-gitsync/src/ui/client/src/components/UpgradeDialog.vue`]]),B=`https://github.com/xz333221/zen-gitsync/releases`,V=5,H=`app-version-check`,U=3600*1e3,W=v({__name:`AppVersionBadge`,setup(e,{expose:t}){t();let n=`2.16.42`,r=h(n),i=x(()=>r.value||n),a=h(null),o=h(!1),s=h(``),c=h(`running`),l=h(0),u=null,f=h(!1);function m(){try{let e=sessionStorage.getItem(H);if(!e)return null;let{at:t,data:n}=JSON.parse(e);return Date.now()-t>U?null:n}catch{return null}}function g(e){try{sessionStorage.setItem(H,JSON.stringify({at:Date.now(),data:e}))}catch{}}async function v(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&&p({type:`success`,message:E(`@F13B4:发现新版本 {version}`,{version:t.latest??``}),duration:4e3})}catch(t){e||p({type:`warning`,message:E(`@F13B4:检查更新失败 {error}`,{error:t?.message||``})})}}async function y(){if(a.value?.hasUpdate){try{await _.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(H)}catch{}a.value?.latest&&(r.value=a.value.latest),b()}}else e.message&&(s.value+=e.message)})}}function b(){S(),l.value=V,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];p({type:`warning`,message:n?E(n):E(`@F13B4:重启失败 {error}`,{error:t})}),f.value=!1}}}w(()=>{S()}),d(()=>{v(!0)});let D={buildVersion:n,releasesUrl:B,runtimeVersion:r,displayVersion:i,latestInfo:a,upgradeDialogVisible:o,upgradeLogs:s,upgradeStatus:c,AUTO_REFRESH_SECONDS:V,countdown:l,get countdownTimer(){return u},set countdownTimer(e){u=e},restartInFlight:f,CACHE_KEY:H,CACHE_TTL_MS:U,readCache:m,writeCache:g,checkUpdate:v,handleUpgrade:y,startAutoRefreshCountdown:b,cancelAutoRefreshCountdown:S,handleCancelAutoRefresh:C,handleRestart:T,get $t(){return E},UpgradeDialog:z};return Object.defineProperty(D,`__isScriptSetup`,{enumerable:!1,value:!0}),D}}),G={class:`app-version-badge`},K=[`aria-label`],q=[`aria-label`],J=[`title`,`aria-label`],Y={class:`upgrade-btn__label`},X={class:`upgrade-btn__version`};function Z(e,t,r,i,s,c){let d=a;return u(),l(`div`,G,[S(` GitHub 链接 `),n(d,{content:i.$t(`@F13B4:在 GitHub 上查看源码`),placement:`top`,effect:`dark`,"show-after":300},{default:g(()=>[C(`a`,{href:`https://github.com/xz333221/zen-gitsync`,target:`_blank`,rel:`noopener noreferrer`,class:`badge-icon-link`,"aria-label":i.$t(`@F13B4:在 GitHub 上查看源码`)},[...t[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,K)]),_:1},8,[`content`]),S(` NPM 链接 `),n(d,{content:i.$t(`@F13B4:在 npm 上查看包`),placement:`top`,effect:`dark`,"show-after":300},{default:g(()=>[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":i.$t(`@F13B4:在 npm 上查看包`)},[...t[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,q)]),_:1},8,[`content`]),S(` 版本号 `),n(d,{content:i.$t(`@F13B4:查看更新`),placement:`top`,effect:`dark`,"show-after":300},{default:g(()=>[C(`a`,{href:i.releasesUrl,target:`_blank`,rel:`noopener noreferrer`,class:`version-link`},`v`+o(i.displayVersion),1)]),_:1},8,[`content`]),i.latestInfo?.hasUpdate?(u(),l(`button`,{key:0,type:`button`,class:`upgrade-btn`,title:i.$t(`@F13B4:发现新版本 {version},点击升级`,{version:i.latestInfo.latest??``}),"aria-label":i.$t(`@F13B4:发现新版本 {version},点击升级`,{version:i.latestInfo.latest??``}),onClick:i.handleUpgrade},[t[3]||=C(`span`,{class:`upgrade-btn__dot`,"aria-hidden":`true`},null,-1),C(`span`,Y,o(i.$t(`@F13B4:升级`)),1),C(`span`,X,`v`+o(i.latestInfo.latest),1)],8,J)):S(`v-if`,!0),n(i.UpgradeDialog,{modelValue:i.upgradeDialogVisible,"onUpdate:modelValue":t[0]||=e=>i.upgradeDialogVisible=e,logs:i.upgradeLogs,status:i.upgradeStatus,countdown:i.countdown,onRetry:i.handleUpgrade,onRestart:i.handleRestart,onCancel:i.handleCancelAutoRefresh},null,8,[`modelValue`,`logs`,`status`,`countdown`])])}var Q=D(W,[[`render`,Z],[`__scopeId`,`data-v-7e0300ec`],[`__file`,`C:/workspace/github_workspace/zen-gitsync/src/ui/client/src/components/AppVersionBadge.vue`]]);export{Q as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.branch-wrapper[data-v-9548a76f]{align-items:center;gap:2px;display:inline-flex}.branch-btn[data-v-9548a76f]{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-9548a76f]:hover{background:var(--bg-hover,#ffffff14)}.branch-btn[data-v-9548a76f]:active{background:var(--bg-active,#ffffff1f)}[data-v-9548a76f] .branch-btn-icon{flex-shrink:0;width:14px;height:14px}.branch-btn-name[data-v-9548a76f]{text-overflow:ellipsis;max-width:160px;overflow:hidden}.branch-btn-caret[data-v-9548a76f]{opacity:.6;align-items:center;font-size:10px;display:flex}.branch-popover-inner[data-v-9548a76f]{flex-direction:column;gap:0;display:flex}.branch-search[data-v-9548a76f]{padding-bottom:8px}.branch-list[data-v-9548a76f]{max-height:240px;margin:0 -16px;padding:0 4px;overflow-y:auto}.branch-list[data-v-9548a76f]::-webkit-scrollbar{width:4px}.branch-list[data-v-9548a76f]::-webkit-scrollbar-thumb{background:var(--border-color,#8080804d);border-radius:2px}.branch-item[data-v-9548a76f]{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-9548a76f]:hover{background:var(--bg-hover,#ffffff14)}.branch-item.is-active[data-v-9548a76f]{color:var(--color-primary)}.branch-item-check[data-v-9548a76f]{color:var(--color-primary);flex-shrink:0;font-size:12px}.branch-item-check-placeholder[data-v-9548a76f]{flex-shrink:0;width:12px;display:inline-block}[data-v-9548a76f] .branch-item-icon{opacity:.7;flex-shrink:0;width:13px;height:13px}.branch-item-name[data-v-9548a76f]{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.branch-empty[data-v-9548a76f]{text-align:center;color:var(--color-text-secondary,#888);padding:12px;font-size:12px}.branch-popover-footer[data-v-9548a76f]{margin-top:4px;padding-top:6px}.branch-create-btn[data-v-9548a76f]{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-9548a76f]:hover{background:var(--bg-hover,#ffffff14)}.branch-info[data-v-9548a76f]{align-items:center;display:flex}.create-branch-content[data-v-9548a76f]{padding:var(--spacing-base) 0}.is-loading[data-v-9548a76f]{animation:2s linear infinite rotating-9548a76f}@keyframes rotating-9548a76f{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}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import"./rolldown-runtime-BM3Ffeng.js";import{Dn as e,Gn as t,Gr as n,K as r,L as i,Lt as a,Pt as o,R as s,Sn as c,Un as l,W as u,Y as d,bn as f,dr as p,er as m,fn as h,kn as g,kt as _,mn as v,q as y,r as b,tt as x,vn as S,xn as C,yn as w,zr as T}from"./element-plus-DqggPOBO.js";import{A as E,E as D,N as O,f as k,j as A,k as j}from"./index-DAgSqrPI.js";var M=g({__name:`BranchSelector`,emits:[`branchChanged`],setup(e,{expose:t,emit:n}){t();let r=D(),i=n,s=p(!1),c=p(``),l=S(()=>{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)&&(i(`branchChanged`),r.refreshLog())}let d=p(!1),f=p(``),m=p(``);function h(){s.value=!1,m.value=r.currentBranch,d.value=!0}async function g(){await r.createBranch(f.value,m.value)&&(d.value=!1,f.value=``,i(`branchChanged`))}let v=p(!1);async function y(){if(!v.value){v.value=!0;try{await Promise.all([r.getCurrentBranch(!0),r.getBranchStatus(!0),r.getAllBranches()]),i(`branchChanged`),r.refreshLog()}finally{v.value=!1}}}let b={gitStore:r,emit:i,branchPopoverVisible:s,branchFilter:c,filteredBranches:l,selectBranch:u,createBranchDialogVisible:d,newBranchName:f,selectedBaseBranch:m,openCreateBranchDialog:h,createNewBranch:g,isRefreshingBranch:v,refreshCurrentBranch:y,get $t(){return O},get Plus(){return o},get Menu(){return _},get Check(){return x},get Refresh(){return a},CommonDialog:k,IconButton:j,SvgIcon:E};return Object.defineProperty(b,`__isScriptSetup`,{enumerable:!1,value:!0}),b}}),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(a,o,p,g,_,x){let S=d,E=u,D=b,O=y,k=i,A=s,j=r;return g.gitStore.currentBranch?(l(),c(`div`,N,[w(`div`,P,[C(` VSCode 风格分支按钮 + 弹出列表 `),e(D,{visible:g.branchPopoverVisible,"onUpdate:visible":o[1]||=e=>g.branchPopoverVisible=e,placement:`top-start`,width:260,trigger:`click`,"popper-class":`branch-popover`,"show-arrow":!1,onShow:o[2]||=e=>g.branchFilter=``},{reference:m(()=>[w(`button`,F,[e(g.SvgIcon,{"icon-class":`git-branch`,"class-name":`branch-btn-icon`}),w(`span`,I,n(g.gitStore.currentBranch),1),w(`span`,L,[e(S,null,{default:m(()=>[...o[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:m(()=>[w(`div`,R,[C(` 搜索框 `),w(`div`,z,[e(E,{modelValue:g.branchFilter,"onUpdate:modelValue":o[0]||=e=>g.branchFilter=e,placeholder:g.$t(`@F13B4:搜索或签出分支`),size:`small`,clearable:``,autofocus:``},null,8,[`modelValue`,`placeholder`])]),C(` 分支列表 `),w(`div`,B,[(l(!0),c(v,null,t(g.filteredBranches,t=>(l(),c(`div`,{key:t,class:T([`branch-item`,{"is-active":t===g.gitStore.currentBranch}]),onClick:e=>g.selectBranch(t)},[t===g.gitStore.currentBranch?(l(),f(S,{key:0,class:`branch-item-check`},{default:m(()=>[e(g.Check)]),_:1})):(l(),c(`span`,H)),e(g.SvgIcon,{"icon-class":`git-branch`,"class-name":`branch-item-icon`}),w(`span`,U,n(t),1)],10,V))),128)),g.filteredBranches.length===0?(l(),c(`div`,W,n(g.$t(`@F13B4:没有匹配的分支`)),1)):C(`v-if`,!0)]),C(` 底部:创建新分支 `),w(`div`,G,[w(`button`,{class:`branch-create-btn`,onClick:g.openCreateBranchDialog},[e(S,null,{default:m(()=>[e(g.Plus)]),_:1}),w(`span`,null,n(g.$t(`@F13B4:创建新分支`)),1)])])])]),_:1},8,[`visible`]),C(` 刷新按钮 `),e(g.IconButton,{tooltip:g.$t(`@CMDCON:刷新`),onClick:g.refreshCurrentBranch},{default:m(()=>[e(S,{class:T({"is-loading":g.isRefreshingBranch})},{default:m(()=>[e(g.Refresh)]),_:1},8,[`class`])]),_:1},8,[`tooltip`])]),C(` 创建分支对话框 `),e(g.CommonDialog,{modelValue:g.createBranchDialogVisible,"onUpdate:modelValue":o[6]||=e=>g.createBranchDialogVisible=e,title:g.$t(`@F13B4:创建新分支`),size:`small`,"destroy-on-close":!0,"append-to-body":!0,"custom-class":`create-branch-dialog`},{footer:m(()=>[w(`div`,Y,[w(`div`,X,[w(`button`,{type:`button`,class:`dialog-cancel-btn`,onClick:o[5]||=e=>g.createBranchDialogVisible=!1},n(g.$t(`@F13B4:取消`)),1),w(`button`,{type:`button`,class:`dialog-confirm-btn`,onClick:g.createNewBranch,disabled:g.gitStore.isCreatingBranch},[g.gitStore.isCreatingBranch?(l(),f(S,{key:1,class:`is-loading`},{default:m(()=>[...o[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(),f(S,{key:0},{default:m(()=>[e(g.Check)]),_:1})),w(`span`,null,n(g.$t(`@F13B4:创建`)),1)],8,Z)])])]),default:m(()=>[w(`div`,K,[e(j,{model:{newBranchName:g.newBranchName,selectedBaseBranch:g.selectedBaseBranch},"label-position":`top`},{default:m(()=>[e(O,null,{label:m(()=>[w(`div`,q,[e(S,{class:`label-icon`},{default:m(()=>[e(g.Plus)]),_:1}),w(`span`,null,n(g.$t(`@F13B4:新分支名称`)),1)])]),default:m(()=>[e(E,{modelValue:g.newBranchName,"onUpdate:modelValue":o[3]||=e=>g.newBranchName=e,placeholder:g.$t(`@F13B4:请输入新分支名称`),class:`modern-input`,size:`large`,onKeyup:h(g.createNewBranch,[`enter`])},null,8,[`modelValue`,`placeholder`])]),_:1}),e(O,null,{label:m(()=>[w(`div`,J,[e(S,{class:`label-icon`},{default:m(()=>[e(g.Menu)]),_:1}),w(`span`,null,n(g.$t(`@F13B4:基于分支`)),1)])]),default:m(()=>[e(A,{modelValue:g.selectedBaseBranch,"onUpdate:modelValue":o[4]||=e=>g.selectedBaseBranch=e,placeholder:g.$t(`@F13B4:选择基础分支`),class:`modern-select`,size:`large`,style:{width:`100%`}},{default:m(()=>[(l(!0),c(v,null,t(g.gitStore.allBranches,e=>(l(),f(k,{key:e,label:e,value:e},null,8,[`label`,`value`]))),128))]),_:1},8,[`modelValue`,`placeholder`])]),_:1})]),_:1},8,[`model`])])]),_:1},8,[`modelValue`,`title`])])):C(`v-if`,!0)}var $=A(M,[[`render`,Q],[`__scopeId`,`data-v-9548a76f`],[`__file`,`C:/workspace/github_workspace/zen-gitsync/src/ui/client/src/components/BranchSelector.vue`]]);export{$ as default};
|