zen-gitsync 2.13.11 → 2.13.12

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.
@@ -10,11 +10,11 @@
10
10
  <link rel="preconnect" href="https://fonts.googleapis.com" />
11
11
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
12
12
  <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,400&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
13
- <script type="module" crossorigin src="/assets/index-DvRkCEGg.js"></script>
13
+ <script type="module" crossorigin src="/assets/index-BKmzezIu.js"></script>
14
14
  <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-CMxvf4Kt.js">
15
- <link rel="modulepreload" crossorigin href="/assets/vendor-CgFFAa1w.js">
16
- <link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-CNd9lWx2.js">
17
- <link rel="stylesheet" crossorigin href="/assets/vendor-irUC_mD1.css">
15
+ <link rel="modulepreload" crossorigin href="/assets/vendor-Cs1V1Sid.js">
16
+ <link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-D0W9RqoT.js">
17
+ <link rel="stylesheet" crossorigin href="/assets/vendor-F3eVqHe1.css">
18
18
  <link rel="stylesheet" crossorigin href="/assets/index-CrcxldZ2.css">
19
19
  </head>
20
20
  <body>
@@ -30,6 +30,10 @@ const TASKS_FILE = path.join(DATA_DIR, 'tasks.json');
30
30
  const IMAGES_DIR = path.join(DATA_DIR, 'workbench-images');
31
31
  const INSTRUCTION_FILE = path.join(DATA_DIR, 'ai-instruction.json');
32
32
  const SUBTASK_INSTRUCTION_FILE = path.join(DATA_DIR, 'ai-subtask-instruction.json');
33
+ // AI 对话拆分会话存档:每个 sessionId 一个文件,重启可恢复多轮上下文
34
+ const SESSIONS_DIR = path.join(DATA_DIR, 'ai-split-sessions');
35
+ const MAX_SESSIONS = 100; // 软上限,触发清理
36
+ const SESSIONS_KEEP = 50; // 超过上限时保留最新 N 个
33
37
  // 执行日志持久化:jobs.json 是历史档案,jobs-config.json 是保留策略。
34
38
  // jobs Map 仍只承载当前进程产出的活跃 job;管理页直接读 jobs.json。
35
39
  const JOBS_FILE = path.join(DATA_DIR, 'jobs.json');
@@ -90,7 +94,7 @@ const DEFAULT_SUBTASK_INSTRUCTION_ZH = `你是一名任务拆分助手。
90
94
  2. 粒度适中:单个子任务应当能在一次会话里完成(既不要"实现整个登录功能"这么大,也不要"打印 hello"这么琐碎)。
91
95
  3. 顺序合理:子任务按依赖关系和执行顺序排列(先准备、后实现、最后验证)。
92
96
  4. 可验证:每个子任务都有明确的完成标志("输出文件 xxx"、"通过测试 yyy"、"控制台打印 zzz")。
93
- 5. 数量:拆成 3-6 个子任务为宜。任务很简单时 2-3 个;复杂时 5-6 个,不要超过 8 个。
97
+ 5. 数量:不设上限。任务很简单时 1-2 个就够,复杂时按需拆成 10 个、20 个甚至更多子任务都行,直到覆盖所有可独立验证的步骤。
94
98
  6. 描述具体:desc 字段要写清楚"要做什么、参考什么、产出什么",不要只是把 title 改写一遍。
95
99
  7. 如果任务里附带了图片,必须基于图片的实际内容拆分(例如指出图片中的哪个区域、哪个元素需要改),而不是泛泛而谈。
96
100
 
@@ -98,7 +102,7 @@ const DEFAULT_SUBTASK_INSTRUCTION_ZH = `你是一名任务拆分助手。
98
102
  最后必须输出 JSON,结构:
99
103
  {
100
104
  "subtasks": [
101
- { "title": "子任务标题(10-20字)", "desc": "子任务的具体描述,包含要做什么、输入是什么、输出/验证标志是什么" }
105
+ { "title": "子任务标题", "desc": "子任务的具体描述,包含要做什么、输入是什么、输出/验证标志是什么" }
102
106
  ]
103
107
  }
104
108
 
@@ -118,7 +122,7 @@ Before producing JSON, think carefully (put your thoughts in reasoning if the mo
118
122
  2. Right granularity: a subtask should finish in one Claude session (not as big as "implement the whole login flow", not as trivial as "print hello").
119
123
  3. Sensible order: arrange subtasks by dependency / execution order (prepare first, implement, then verify).
120
124
  4. Verifiable: every subtask has a clear completion signal (e.g. "produce file xxx", "pass test yyy", "log zzz to console").
121
- 5. Quantity: prefer 3-6 subtasks. Very simple tasks 2-3, complex ones 5-6, never exceed 8.
125
+ 5. Quantity: no hard limit. Very simple tasks need only 1-2; complex tasks can be split into 10, 20 or more subtasks until every independently verifiable step is covered.
122
126
  6. Concrete desc: write what to do, what to reference, what to produce — don't just paraphrase the title.
123
127
  7. If the task includes images, the breakdown must reference the actual image content (which region, which element to change), not just generic talk.
124
128
 
@@ -126,7 +130,7 @@ Before producing JSON, think carefully (put your thoughts in reasoning if the mo
126
130
  End with JSON, structure:
127
131
  {
128
132
  "subtasks": [
129
- { "title": "subtask title (10-20 chars)", "desc": "concrete description: what to do, what the input is, what the output / verification signal is" }
133
+ { "title": "subtask title", "desc": "concrete description: what to do, what the input is, what the output / verification signal is" }
130
134
  ]
131
135
  }
132
136
 
@@ -141,6 +145,102 @@ function pickDefaultSubtaskInstruction(req) {
141
145
  if (al.startsWith('en')) return DEFAULT_SUBTASK_INSTRUCTION_EN
142
146
  return DEFAULT_SUBTASK_INSTRUCTION_ZH
143
147
  }
148
+
149
+ // ── AI 对话拆分: session 持久化层 ────────────────────────────
150
+ // 文件命名: {sessionId}.json
151
+ // 内容: { version, sessionId, title, desc, taskId, promptId, createdAt, updatedAt,
152
+ // messages: [{role, content}], latestSubtasks, latestRaw, latestParseStage }
153
+ // 写入用临时文件 + rename 原子替换(与 workbench 现有 writeJson 模式一致),
154
+ // 避免半写状态被读到。并发安全: 单进程内同一 session 串行调用,
155
+ // 跨端点并发写同 session 概率极低,丢失只会是最后一次 user/assistant pair,可重发。
156
+ function genSessionId() {
157
+ // 16 字符: 时间戳后 8 位 + 随机 8 位 base36,既可读也基本不冲突
158
+ return `${Date.now().toString(36).slice(-8)}-${Math.random().toString(36).slice(2, 10)}`
159
+ }
160
+
161
+ async function readSessionFile(sessionId) {
162
+ if (!/^[a-z0-9-]{4,32}$/i.test(sessionId)) {
163
+ const err = new Error('非法 sessionId'); err.statusCode = 400; throw err
164
+ }
165
+ const file = path.join(SESSIONS_DIR, `${sessionId}.json`)
166
+ try {
167
+ return JSON.parse(await fsp.readFile(file, 'utf-8'))
168
+ } catch (err) {
169
+ if (err.code === 'ENOENT') {
170
+ const e = new Error('会话不存在'); e.statusCode = 404; throw e
171
+ }
172
+ throw err
173
+ }
174
+ }
175
+
176
+ async function writeSessionFile(sessionId, data) {
177
+ await fsp.mkdir(SESSIONS_DIR, { recursive: true })
178
+ const file = path.join(SESSIONS_DIR, `${sessionId}.json`)
179
+ const tmp = `${file}.tmp.${process.pid}.${Date.now()}`
180
+ await fsp.writeFile(tmp, JSON.stringify(data, null, 2), 'utf-8')
181
+ await fsp.rename(tmp, file)
182
+ }
183
+
184
+ async function deleteSessionFile(sessionId) {
185
+ if (!/^[a-z0-9-]{4,32}$/i.test(sessionId)) {
186
+ const err = new Error('非法 sessionId'); err.statusCode = 400; throw err
187
+ }
188
+ const file = path.join(SESSIONS_DIR, `${sessionId}.json`)
189
+ try {
190
+ await fsp.unlink(file)
191
+ } catch (err) {
192
+ if (err.code === 'ENOENT') {
193
+ const e = new Error('会话不存在'); e.statusCode = 404; throw e
194
+ }
195
+ throw err
196
+ }
197
+ }
198
+
199
+ // 列表只读 metadata 不读 messages 内容,启动无需预加载
200
+ async function listSessionsMeta() {
201
+ await fsp.mkdir(SESSIONS_DIR, { recursive: true })
202
+ const files = await fsp.readdir(SESSIONS_DIR)
203
+ const list = []
204
+ for (const f of files) {
205
+ if (!f.endsWith('.json')) continue
206
+ const sid = f.slice(0, -5)
207
+ try {
208
+ const file = path.join(SESSIONS_DIR, f)
209
+ const stat = await fsp.stat(file)
210
+ const data = JSON.parse(await fsp.readFile(file, 'utf-8'))
211
+ list.push({
212
+ sessionId: data.sessionId || sid,
213
+ title: data.title || '(无标题)',
214
+ taskId: data.taskId || '',
215
+ createdAt: data.createdAt || '',
216
+ updatedAt: data.updatedAt || '',
217
+ messageCount: Array.isArray(data.messages) ? data.messages.length : 0,
218
+ latestSubtaskCount: Array.isArray(data.latestSubtasks) ? data.latestSubtasks.length : 0,
219
+ size: stat.size
220
+ })
221
+ } catch { /* 跳过坏文件 */ }
222
+ }
223
+ list.sort((a, b) => (b.updatedAt || '').localeCompare(a.updatedAt || ''))
224
+ return list
225
+ }
226
+
227
+ // 容量控制: 超过 MAX_SESSIONS 时按 mtime 删旧的,保留最新 SESSIONS_KEEP 个
228
+ async function enforceSessionsRetention() {
229
+ const files = await fsp.readdir(SESSIONS_DIR).catch(() => [])
230
+ const jsonFiles = files.filter(f => f.endsWith('.json'))
231
+ if (jsonFiles.length < MAX_SESSIONS) return
232
+ const stats = await Promise.all(jsonFiles.map(async f => {
233
+ const full = path.join(SESSIONS_DIR, f)
234
+ const s = await fsp.stat(full)
235
+ return { file: full, mtime: s.mtimeMs }
236
+ }))
237
+ stats.sort((a, b) => b.mtime - a.mtime)
238
+ const toDelete = stats.slice(SESSIONS_KEEP)
239
+ await Promise.all(toDelete.map(s => fsp.unlink(s.file).catch(() => {})))
240
+ if (toDelete.length > 0) {
241
+ console.log(`[workbench] ai-split sessions: cleaned up ${toDelete.length} old files`)
242
+ }
243
+ }
144
244
  // 白名单后缀:图片 + 常见文档(PDF / 纯文本 / Markdown)
145
245
  const IMAGE_EXTS = new Set(['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'svg']);
146
246
  const DOC_EXTS = new Set(['pdf', 'txt', 'md', 'markdown', 'csv', 'json', 'log']);
@@ -325,27 +425,50 @@ async function callLlmJson(model, prompt, opts = {}) {
325
425
  * - reasoning / reasoning_text:openai o1 风格
326
426
  * - content:普通输出
327
427
  * 返回完整 content 字符串。
428
+ *
429
+ * input 支持两种形态(判别 union):
430
+ * - string: 旧模式,拼成单条 user message,支持 opts.images 多模态
431
+ * - ChatMsg[]: 新模式(对话拆分),直接作为 messages 发送;opts.images/systemPrompt 忽略
328
432
  */
329
- async function callLlmStream(model, prompt, onDelta, opts = {}) {
330
- const { maxTokens = 2000, timeoutMs = 600000, signal, images = [] } = opts
433
+ function cloneMsgContent(c) {
434
+ if (typeof c === 'string') return c
435
+ if (Array.isArray(c)) return c.map(p => ({ ...p }))
436
+ return c
437
+ }
438
+
439
+ async function callLlmStream(model, input, onDelta, opts = {}) {
440
+ const { maxTokens = 2000, timeoutMs = 600000, signal, images = [], systemPrompt } = opts
331
441
  const { default: fetch } = await import('node-fetch').catch(() => ({ default: globalThis.fetch }))
332
442
  const url = `${String(model.baseURL || '').replace(/\/$/, '')}/chat/completions`
333
443
  const headers = { 'Content-Type': 'application/json' }
334
444
  if (model.apiKey) headers['Authorization'] = `Bearer ${model.apiKey}`
335
445
 
336
- let userContent
337
- if (Array.isArray(images) && images.length > 0) {
338
- userContent = [
339
- { type: 'text', text: prompt },
340
- ...images.map(img => ({ type: 'image_url', image_url: { url: img } }))
341
- ]
446
+ // 判别 input: string(旧,直接拆分)或 messages 数组(新,对话拆分)
447
+ let messages
448
+ if (Array.isArray(input)) {
449
+ // messages 模式: 深拷贝避免外部 mutation 污染 LLM 请求
450
+ // images / systemPrompt opts messages 模式下不生效(由 caller 自管)
451
+ messages = input.map(m => ({ role: m.role, content: cloneMsgContent(m.content) }))
342
452
  } else {
343
- userContent = prompt
453
+ // 旧模式: string prompt 拼成单条 user message,支持多模态
454
+ const text = String(input || '')
455
+ let userContent
456
+ if (Array.isArray(images) && images.length > 0) {
457
+ userContent = [
458
+ { type: 'text', text },
459
+ ...images.map(img => ({ type: 'image_url', image_url: { url: img } }))
460
+ ]
461
+ } else {
462
+ userContent = text
463
+ }
464
+ messages = []
465
+ if (systemPrompt) messages.push({ role: 'system', content: String(systemPrompt) })
466
+ messages.push({ role: 'user', content: userContent })
344
467
  }
345
468
 
346
469
  const body = JSON.stringify({
347
470
  model: model.model,
348
- messages: [{ role: 'user', content: userContent }],
471
+ messages,
349
472
  max_tokens: maxTokens,
350
473
  temperature: 0.4,
351
474
  // 注意:stream: true 模式下不能同时使用 response_format:{type:'json_object'},
@@ -428,16 +551,22 @@ async function callLlmStream(model, prompt, onDelta, opts = {}) {
428
551
  // 任一步成功就返回 parsed,全部失败时返回最后一次 JSON.parse 的错误,
429
552
  // 用 parseStage 告知前端"模型输出哪一步崩了",并把原始 raw 一并回传。
430
553
  function parseSubtaskJson(content) {
431
- const src = String(content || '');
554
+ let src = String(content || '');
432
555
  if (!src.trim()) {
433
556
  return { parsed: null, parseError: '模型未返回任何内容', parseStage: 'empty' };
434
557
  }
435
558
 
559
+ // 预处理:剥离 deepseek-r1 / QwQ 等模型在 content 前输出的 <think>...</think> 思考块。
560
+ // 思考块通常跨行、可能多段,也可能自闭合 <think/>。剥离后下面的 JSON 扫描
561
+ // 才能命中真正的 subtasks JSON 块,避免贪婪正则把整段 think 包进 candidates。
562
+ src = src.replace(/<think>[\s\S]*?<\/think>/gi, '').replace(/<think\s*\/>/gi, '').trim();
563
+
436
564
  const candidates = [];
437
565
  const fenced = src.match(/```json\s*([\s\S]*?)```/i) || src.match(/```\s*([\s\S]*?)```/);
438
566
  if (fenced) candidates.push(fenced[1]);
439
- const bracePair = src.match(/\{[\s\S]*\}/);
440
- if (bracePair) candidates.push(bracePair[0]);
567
+ // 平衡花括号扫描:避免贪婪正则把多段 JSON / 散落的 { } 全包进去
568
+ const balancedFirst = extractBalancedJson(src);
569
+ if (balancedFirst) candidates.push(balancedFirst);
441
570
  // 兜底:整段当 JSON 试
442
571
  candidates.push(src);
443
572
 
@@ -1392,8 +1521,8 @@ ${subSummaries.map((s, i) => `\n### [${i + 1}] ${s.name} (${s.root})\n${s.summar
1392
1521
  if (!text) {
1393
1522
  return res.status(400).json({ success: false, error: '指令不能为空' });
1394
1523
  }
1395
- if (text.length > 50000) {
1396
- return res.status(413).json({ success: false, error: '指令过长(最多 50000 字符)' });
1524
+ if (text.length > 500000) {
1525
+ return res.status(413).json({ success: false, error: '指令过长(最多 500000 字符)' });
1397
1526
  }
1398
1527
  await writeInstruction(text);
1399
1528
  res.json({ success: true });
@@ -1427,8 +1556,8 @@ ${subSummaries.map((s, i) => `\n### [${i + 1}] ${s.name} (${s.root})\n${s.summar
1427
1556
  if (!text) {
1428
1557
  return res.status(400).json({ success: false, error: '指令不能为空' });
1429
1558
  }
1430
- if (text.length > 50000) {
1431
- return res.status(413).json({ success: false, error: '指令过长(最多 50000 字符)' });
1559
+ if (text.length > 500000) {
1560
+ return res.status(413).json({ success: false, error: '指令过长(最多 500000 字符)' });
1432
1561
  }
1433
1562
  // 如果保存的文本正好等于当前 locale 的默认——不写文件,保持 fallback 行为
1434
1563
  const def = pickDefaultSubtaskInstruction(req);
@@ -1574,7 +1703,7 @@ ${desc ? `描述:${desc}` : '描述:(无)'}${attachmentBlock}${templateB
1574
1703
  请先简要分析(可以放在 reasoning 中或直接写出来),然后给出 JSON。JSON 用 \`\`\`json ... \`\`\` 包裹:
1575
1704
  {
1576
1705
  "subtasks": [
1577
- { "title": "子任务标题(10-20字)", "desc": "具体描述" }
1706
+ { "title": "子任务标题", "desc": "具体描述" }
1578
1707
  ]
1579
1708
  }
1580
1709
 
@@ -1595,7 +1724,7 @@ ${desc ? `描述:${desc}` : '描述:(无)'}${attachmentBlock}${templateB
1595
1724
  if (delta.thinking) send({ type: 'thinking', delta: delta.thinking });
1596
1725
  if (delta.content) send({ type: 'content', delta: delta.content });
1597
1726
  },
1598
- { maxTokens: 4000, timeoutMs: 600000, images: imageDataUrls, signal: abortController.signal }
1727
+ { maxTokens: 16000, timeoutMs: 600000, images: imageDataUrls, signal: abortController.signal }
1599
1728
  );
1600
1729
 
1601
1730
  if (aborted) {
@@ -1609,11 +1738,10 @@ ${desc ? `描述:${desc}` : '描述:(无)'}${attachmentBlock}${templateB
1609
1738
  const list = Array.isArray(parsed?.subtasks) ? parsed.subtasks : [];
1610
1739
  const subtasks = list
1611
1740
  .map(s => ({
1612
- title: String(s?.title || '').trim().slice(0, 80),
1613
- desc: String(s?.desc || '').trim().slice(0, 500)
1741
+ title: String(s?.title || '').trim(),
1742
+ desc: String(s?.desc || '').trim()
1614
1743
  }))
1615
- .filter(s => s.title)
1616
- .slice(0, 8);
1744
+ .filter(s => s.title);
1617
1745
 
1618
1746
  send({ type: 'done', subtasks, raw: content, parseError, parseStage });
1619
1747
  finished = true;
@@ -1638,17 +1766,213 @@ ${desc ? `描述:${desc}` : '描述:(无)'}${attachmentBlock}${templateB
1638
1766
  const list = Array.isArray(parsed?.subtasks) ? parsed.subtasks : [];
1639
1767
  const subtasks = list
1640
1768
  .map(s => ({
1641
- title: String(s?.title || '').trim().slice(0, 80),
1642
- desc: String(s?.desc || '').trim().slice(0, 500)
1769
+ title: String(s?.title || '').trim(),
1770
+ desc: String(s?.desc || '').trim()
1643
1771
  }))
1644
- .filter(s => s.title)
1645
- .slice(0, 8);
1772
+ .filter(s => s.title);
1646
1773
  res.json({ success: true, subtasks, parseError, parseStage });
1647
1774
  } catch (err) {
1648
1775
  res.status(500).json({ success: false, error: err?.message || String(err) });
1649
1776
  }
1650
1777
  });
1651
1778
 
1779
+ // ── AI 对话拆分: 多轮 SSE 端点 ───────────────────────────────────────
1780
+ // POST /api/workbench/tasks/ai-chat-split (SSE)
1781
+ // body: { sessionId?, title, desc?, taskId?, promptId?, userMessage }
1782
+ // → meta / user_echo / thinking / content / done / error
1783
+ // 第一轮可省略 sessionId,后端会新建;后续轮带 sessionId 继续追加 messages。
1784
+ // 历史对话持久化在 SESSIONS_DIR,前端可从 GET 列表/GET 详情恢复。
1785
+ app.post('/api/workbench/tasks/ai-chat-split', async (req, res) => {
1786
+ const userMessage = String(req.body?.userMessage || '').trim();
1787
+ const sessionIdInput = String(req.body?.sessionId || '').trim();
1788
+ const title = String(req.body?.title || '').trim();
1789
+ const desc = String(req.body?.desc || '').trim();
1790
+ const taskId = String(req.body?.taskId || '').trim();
1791
+ const promptId = String(req.body?.promptId || '').trim();
1792
+
1793
+ // SSE 头
1794
+ res.set({
1795
+ 'Content-Type': 'text/event-stream',
1796
+ 'Cache-Control': 'no-cache, no-transform',
1797
+ 'Connection': 'keep-alive',
1798
+ 'X-Accel-Buffering': 'no'
1799
+ });
1800
+ res.flushHeaders?.();
1801
+ const send = (obj) => {
1802
+ try { res.write(`data: ${JSON.stringify(obj)}\n\n`); } catch {}
1803
+ };
1804
+ const abortController = new AbortController();
1805
+ let finished = false;
1806
+ if (req.socket) req.socket.once('close', () => { if (!finished) abortController.abort(); });
1807
+
1808
+ try {
1809
+ // 加载或新建 session
1810
+ let session, isNew = false;
1811
+ if (sessionIdInput) {
1812
+ try {
1813
+ session = await readSessionFile(sessionIdInput);
1814
+ } catch (err) {
1815
+ if (err.statusCode === 404) {
1816
+ send({ type: 'error', error: '会话不存在' });
1817
+ return res.end();
1818
+ }
1819
+ throw err;
1820
+ }
1821
+ } else {
1822
+ if (!title) {
1823
+ send({ type: 'error', error: '新建会话时必须提供 title' });
1824
+ return res.end();
1825
+ }
1826
+ session = {
1827
+ version: 1,
1828
+ sessionId: genSessionId(),
1829
+ title, desc, taskId, promptId,
1830
+ createdAt: nowIso(),
1831
+ updatedAt: nowIso(),
1832
+ messages: [],
1833
+ latestSubtasks: [],
1834
+ latestRaw: '',
1835
+ latestParseStage: ''
1836
+ };
1837
+ isNew = true;
1838
+ }
1839
+
1840
+ if (!userMessage) {
1841
+ send({ type: 'error', error: '消息内容不能为空' });
1842
+ return res.end();
1843
+ }
1844
+
1845
+ // 拼 system message(只在第一轮)
1846
+ if (session.messages.length === 0) {
1847
+ const userInstruction = await readSubtaskInstruction(req);
1848
+ // 第一轮时把任务上下文拼到 system message 末尾,让 LLM 一开始就知道要拆哪个任务。
1849
+ // 直接拆分端点把 title/desc 拼在 user prompt 里同样达到此效果;对话模式因 user 消息
1850
+ // 可能只是「拆一下」这种简短追问,必须把上下文放在 system 里 LLM 才不会反问。
1851
+ // 后续轮次不再追加,避免任务变更(切换 taskId)时影响已建立的多轮上下文。
1852
+ const ctxLines = [];
1853
+ if (title) ctxLines.push(`【任务标题】${title}`);
1854
+ if (desc) ctxLines.push(`【任务描述】${desc}`);
1855
+ const sysContent = ctxLines.length
1856
+ ? `${userInstruction}\n\n${ctxLines.join('\n')}`
1857
+ : userInstruction;
1858
+ session.messages.push({ role: 'system', content: sysContent });
1859
+ }
1860
+
1861
+ // 追加 user message
1862
+ session.messages.push({ role: 'user', content: userMessage });
1863
+
1864
+ // 推 meta + user_echo
1865
+ send({
1866
+ type: 'meta',
1867
+ sessionId: session.sessionId,
1868
+ isNew,
1869
+ prompt: { system: session.messages[0].content, user: userMessage }
1870
+ });
1871
+ send({ type: 'user_echo', userMessage });
1872
+
1873
+ // 加载 model
1874
+ let model;
1875
+ try {
1876
+ if (!configManager) throw new Error('configManager 不可用');
1877
+ const rawConfig = await configManager.readRawConfigFile();
1878
+ const models = Array.isArray(rawConfig.models) ? rawConfig.models : [];
1879
+ model = models.find(m => m.isDefault) || models[0];
1880
+ } catch (err) {
1881
+ send({ type: 'error', error: '读取 AI 配置失败: ' + err.message });
1882
+ finished = true;
1883
+ return res.end();
1884
+ }
1885
+ if (!model) {
1886
+ send({ type: 'error', error: '未配置 AI 模型' });
1887
+ finished = true;
1888
+ return res.end();
1889
+ }
1890
+
1891
+ // 流式调用(messages 模式)
1892
+ const { content, aborted } = await callLlmStream(
1893
+ model,
1894
+ session.messages,
1895
+ (delta) => {
1896
+ if (delta.thinking) send({ type: 'thinking', delta: delta.thinking });
1897
+ if (delta.content) send({ type: 'content', delta: delta.content });
1898
+ },
1899
+ { maxTokens: 16000, timeoutMs: 600000, signal: abortController.signal }
1900
+ );
1901
+
1902
+ if (aborted) {
1903
+ session.updatedAt = nowIso();
1904
+ await writeSessionFile(session.sessionId, session).catch(() => {});
1905
+ enforceSessionsRetention().catch(() => {});
1906
+ send({ type: 'error', error: '已取消' });
1907
+ finished = true;
1908
+ return res.end();
1909
+ }
1910
+
1911
+ // 解析 + 写盘
1912
+ const { parsed, parseError, parseStage } = parseSubtaskJson(content);
1913
+ const subtasks = Array.isArray(parsed?.subtasks)
1914
+ ? parsed.subtasks
1915
+ .map(s => ({
1916
+ title: String(s?.title || '').trim(),
1917
+ desc: String(s?.desc || '').trim()
1918
+ }))
1919
+ .filter(s => s.title)
1920
+ : [];
1921
+
1922
+ session.messages.push({ role: 'assistant', content });
1923
+ session.latestSubtasks = subtasks;
1924
+ session.latestRaw = content;
1925
+ session.latestParseStage = parseStage;
1926
+ session.updatedAt = nowIso();
1927
+ await writeSessionFile(session.sessionId, session);
1928
+ enforceSessionsRetention().catch(() => {});
1929
+
1930
+ send({ type: 'done', subtasks, raw: content, parseError, parseStage });
1931
+ finished = true;
1932
+ res.end();
1933
+ } catch (err) {
1934
+ send({ type: 'error', error: '对话拆分失败: ' + (err?.message || String(err)) });
1935
+ finished = true;
1936
+ res.end();
1937
+ }
1938
+ });
1939
+
1940
+ // GET /api/workbench/tasks/ai-chat-sessions
1941
+ // → { success, sessions: [{ sessionId, title, taskId, createdAt, updatedAt, messageCount, latestSubtaskCount, size }] }
1942
+ // 列表只读 metadata,启动时无需预加载
1943
+ app.get('/api/workbench/tasks/ai-chat-sessions', async (_req, res) => {
1944
+ try {
1945
+ const sessions = await listSessionsMeta();
1946
+ res.json({ success: true, sessions });
1947
+ } catch (err) {
1948
+ res.status(500).json({ success: false, error: err?.message || String(err) });
1949
+ }
1950
+ });
1951
+
1952
+ // GET /api/workbench/tasks/ai-chat-sessions/:sessionId
1953
+ // → { success, session: { ...完整,含 messages } }
1954
+ app.get('/api/workbench/tasks/ai-chat-sessions/:sessionId', async (req, res) => {
1955
+ try {
1956
+ const session = await readSessionFile(req.params.sessionId);
1957
+ res.json({ success: true, session });
1958
+ } catch (err) {
1959
+ const code = err.statusCode || 500;
1960
+ res.status(code).json({ success: false, error: err.message });
1961
+ }
1962
+ });
1963
+
1964
+ // DELETE /api/workbench/tasks/ai-chat-sessions/:sessionId
1965
+ // → { success: true } (硬删文件)
1966
+ app.delete('/api/workbench/tasks/ai-chat-sessions/:sessionId', async (req, res) => {
1967
+ try {
1968
+ await deleteSessionFile(req.params.sessionId);
1969
+ res.json({ success: true });
1970
+ } catch (err) {
1971
+ const code = err.statusCode || 500;
1972
+ res.status(code).json({ success: false, error: err.message });
1973
+ }
1974
+ });
1975
+
1652
1976
  // SSE 事件流
1653
1977
  app.get('/api/workbench/events', (req, res) => {
1654
1978
  res.set({
@@ -1,3 +0,0 @@
1
- import{o as e}from"./rolldown-runtime-CMxvf4Kt.js";import{$n as t,Bn as n,F as r,Gn as i,Hn as a,In as o,Kn as s,Mn as c,Nn as l,Pn as ee,Qn as u,Un as d,Yn as f,_ as te,_r as p,b as ne,c as m,cr as h,er as re,f as ie,g as ae,gr as g,h as oe,hr as _,ir as v,lr as y,m as se,mr as ce,nr as b,p as le,pr as x,qn as S,t as ue,ur as C,v as de,vr as w,vt as fe,y as pe,zn as T}from"./vendor-CgFFAa1w.js";import{n as E,t as D}from"./_plugin-vue_export-helper-CNd9lWx2.js";import{i as me,o as he}from"./index-DvRkCEGg.js";var ge=e(ie(),1),_e={class:`source-map-view`},ve={class:`sm-toolbar`},ye={class:`sm-toolbar-left`},be={class:`sm-title`},xe={class:`sm-toolbar-center`},Se=[`placeholder`,`disabled`],Ce=[`disabled`],we={class:`sm-toolbar-right`},Te={class:`sm-body`},Ee={class:`sm-panel-header sm-panel-header--tabs`},De={key:0,class:`sm-badge`},Oe={class:`sm-panel-body sm-file-tree`},ke=[`onClick`],Ae={key:1,class:`sm-tree-arrow-spacer`},je={key:2,class:`sm-tree-icon mit-icon`,"aria-hidden":`true`},Me=[`xlink:href`],Ne={key:3,class:`sm-tree-icon mit-icon`,"aria-hidden":`true`},Pe=[`xlink:href`],Fe=[`title`],Ie={key:1,class:`sm-tree-empty`},Le={class:`sm-panel-body sm-outline-body`},Re={class:`sm-badge`,style:{"margin-left":`auto`}},ze=[`onClick`],Be=[`title`],Ve={key:0,class:`sm-outline-desc`},He={key:1,class:`sm-tree-empty`},Ue={class:`sm-panel sm-panel-graph`},We={key:0,class:`sm-project-info`},Ge={class:`sm-lang-badge`},Ke={class:`sm-summary-text`},qe={class:`sm-graph-container`},Je={class:`sm-layout-btn-wrap`},Ye=[`disabled`],Xe=[`title`],Ze={class:`sm-fn-label`},Qe={key:0,class:`sm-fn-desc`},$e={key:0,class:`sm-graph-empty`},et={viewBox:`0 0 24 24`,width:`48`,height:`48`,fill:`none`,stroke:`currentColor`,"stroke-width":`1`,style:{opacity:`0.3`}},tt={key:1,class:`sm-graph-loading`},nt={class:`sm-log-header`},rt={key:0,class:`sm-log-indicator`},it={class:`sm-log-body`,ref:`logBodyRef`},at={key:0,class:`sm-empty`},ot={class:`sm-panel-header`},st={key:0,class:`sm-badge sm-badge-amber`},ct={key:0,class:`sm-node-detail`},lt={class:`sm-node-name`},ut={class:`sm-node-file`},dt={key:0},ft={key:0,class:`sm-node-desc`},pt={class:`sm-panel-body sm-source-body`},mt={key:0,class:`sm-source-overlay`},ht={key:1,class:`sm-source-overlay sm-source-placeholder`},O=D(f({__name:`SourceMapView`,setup(e){let f=x(document.documentElement.getAttribute(`data-theme`)===`dark`?`dark`:`light`);function ie(){f.value=document.documentElement.getAttribute(`data-theme`)===`dark`?`dark`:`light`}let D=null,O=he(),k=x(O.currentDirectory||``),A=x(`idle`),j=x([]),gt=0,M=x(null),N=x(null),P=x(`files`),F=x(``),I=x(``),L=x(!1),R=x([]),z=x(new Set),B=x({files:!0,graph:!0,source:!0}),V=x(!1),H=x(220),U=x(360),W=x(140),G=null,K=0,_t=0,q=0;function J(e,t){G=e,K=t.clientX,_t=t.clientY,q=e===`files`?H.value:e===`source`?U.value:W.value,document.addEventListener(`mousemove`,vt),document.addEventListener(`mouseup`,Y),t.preventDefault()}function vt(e){G&&(G===`files`?H.value=Math.max(120,Math.min(480,q+e.clientX-K)):G===`source`?U.value=Math.max(200,Math.min(700,q+K-e.clientX)):W.value=Math.max(60,Math.min(500,q+_t-e.clientY)))}function Y(){G=null,document.removeEventListener(`mousemove`,vt),document.removeEventListener(`mouseup`,Y)}let yt=x(null),X=ce(null),{fitView:bt,setNodes:xt,setEdges:St,getNodes:Ct,getEdges:wt,updateNodeInternals:Tt}=ne(),Z=T(()=>A.value===`scanning`||A.value===`analyzing`),Et=T(()=>f.value===`dark`?`#334155`:`#cbd5e1`),Q=T(()=>M.value?.nodes.find(e=>e.id===N.value)??null),Dt=T(()=>{if(!M.value)return[];let e=new Map;for(let t of M.value.nodes){let n=t.subsystem??`__default__`;if(!e.has(n)){let r=M.value.subsystems?.find(e=>e.name===n);e.set(n,{displayName:r?.displayName||t.subsystem||E(`@SRCMAP:默认`),color:t.subsystemColor||kt[0],nodes:[]})}e.get(n).nodes.push(t)}return[...e.entries()].map(([,e])=>e)});function $(e,t=`info`){j.value.push({id:++gt,message:e,type:t,timestamp:Date.now()}),j.value.length>200&&j.value.splice(0,j.value.length-200)}function Ot(e){let t={name:``,path:``,kind:`dir`,children:[],childMap:new Map};for(let n of e){let e=n.trim().split(`/`).filter(Boolean),r=t,i=``;for(let t=0;t<e.length;t++){let n=e[t];i=i?`${i}/${n}`:n;let a=t===e.length-1,o=r.childMap.get(n);o||(o={name:n,path:i,kind:a?`file`:`dir`,children:[],childMap:new Map},r.childMap.set(n,o),r.children.push(o)),r=o}}let n=e=>{e.sort((e,t)=>e.kind===t.kind?e.name.localeCompare(t.name):e.kind===`dir`?-1:1),e.forEach(e=>{e.kind===`dir`&&n(e.children)})};return n(t.children),t.children}let kt=[`#f59e0b`,`#3b82f6`,`#10b981`,`#8b5cf6`];function At(e){return e.subsystemColor?e.subsystemColor:e.subsystemIndex===void 0?e.importance===`high`?`#f59e0b`:e.importance===`low`?`#94a3b8`:`#3b82f6`:kt[e.subsystemIndex%kt.length]}function jt(e){return{ts:`typescript`,tsx:`typescript`,js:`javascript`,mjs:`javascript`,cjs:`javascript`,jsx:`javascript`,vue:`html`,svelte:`html`,html:`html`,py:`python`,java:`java`,go:`go`,rs:`rust`,cpp:`cpp`,cc:`cpp`,cxx:`cpp`,c:`c`,h:`c`,hpp:`cpp`,cs:`csharp`,rb:`ruby`,php:`php`,swift:`swift`,kt:`kotlin`,sh:`shell`,bash:`shell`,json:`json`,yaml:`yaml`,yml:`yaml`,md:`markdown`,css:`css`,scss:`scss`,less:`less`,sql:`sql`}[e.split(`.`).pop()?.toLowerCase()||``]||`plaintext`}function Mt(){!yt.value||X.value||(X.value=m.create(yt.value,{value:``,language:`plaintext`,theme:f.value===`dark`?`vs-dark`:`vs`,readOnly:!0,fontSize:12,lineHeight:19,fontFamily:`'JetBrains Mono', 'Fira Code', Consolas, monospace`,minimap:{enabled:!1},scrollBeyondLastLine:!1,automaticLayout:!0,wordWrap:`off`,padding:{top:8,bottom:8},scrollbar:{verticalScrollbarSize:6,horizontalScrollbarSize:6}}))}h(f,e=>{X.value&&m.setTheme(e===`dark`?`vs-dark`:`vs`)});function Nt(e,t){let n={};t.forEach(e=>{n[e.source]||(n[e.source]=[]),n[e.source].push(e.target)});let r=new Map;e.forEach(e=>{let t=e.subsystem??`__default`;r.has(t)||r.set(t,[]),r.get(t).push(e)});let i=[],a=0;for(let[,e]of r){let t=new Set(e.map(e=>e.id)),r={},o=e.length?[e[0].id]:[];for(o[0]&&(r[o[0]]=0);o.length;){let e=o.shift();(n[e]||[]).filter(e=>t.has(e)).forEach(t=>{r[t]===void 0&&(r[t]=(r[e]??0)+1,o.push(t))})}let s={};e.forEach(e=>{let t=r[e.id]??0;s[t]=(s[t]||0)+1});let c={};e.forEach(e=>{let t=r[e.id]??0;c[t]=(c[t]??-1)+1;let n=c[t],o=s[t]??1,l=a*600+n*220-(o-1)*220/2,ee=t*110,u=At(e);i.push({id:e.id,type:`default`,position:{x:l,y:ee},label:e.label,class:`sm-fn-node`,data:{...e,_accentColor:u},style:{"--node-accent":u}})}),a++}return{flowNodes:i,flowEdges:t.map((e,t)=>({id:`e_${t}_${e.source}_${e.target}`,source:e.source,target:e.target,type:`smoothstep`,animated:!1,class:`sm-fn-edge`,markerEnd:{type:ae.ArrowClosed}}))}}async function Pt(){let e=Ct.value,t=wt.value;if(e.length!==0){V.value=!0;try{await u(),await new Promise(e=>requestAnimationFrame(()=>e())),Tt(e.map(e=>e.id)),await new Promise(e=>requestAnimationFrame(()=>e()));let n=new Map;e.forEach(e=>{let t=e.data?.subsystem??`__default__`;n.has(t)||n.set(t,[]),n.get(t).push(e)});let r=new Map,i=0;for(let[,e]of n){let n=new ge.default.graphlib.Graph;n.setDefaultEdgeLabel(()=>({})),n.setGraph({rankdir:`TB`,nodesep:55,ranksep:75,marginx:40,marginy:40});let a=new Set(e.map(e=>e.id));e.forEach(e=>{let t=190,r=e.data?.description?68:48,i=document.querySelector(`.vue-flow__node[data-id="${e.id}"]`);i&&i.offsetWidth>0&&(t=i.offsetWidth,r=i.offsetHeight),n.setNode(e.id,{width:t,height:r})}),t.forEach(e=>{a.has(e.source)&&a.has(e.target)&&n.setEdge(e.source,e.target)}),ge.default.layout(n);let o=0;e.forEach(e=>{let t=n.node(e.id);t&&(r.set(e.id,{x:i+t.x-t.width/2,y:t.y-t.height/2}),o=Math.max(o,t.x+t.width/2))}),i+=o+120}xt(e.map(e=>({...e,position:r.get(e.id)??e.position}))),await u(),bt({padding:.18})}finally{V.value=!1}}}async function Ft(){if(!k.value.trim()){r.warning(E(`@SRCMAP:请先输入项目路径`));return}if(!Z.value){j.value=[],M.value=null,N.value=null,F.value=``,I.value=``,xt([]),St([]),A.value=`scanning`,$(E(`@SRCMAP:开始分析项目...`),`info`);try{let e=await fetch(`/api/code-analysis/analyze`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({path:k.value})});if(!e.ok){let t=await e.json().catch(()=>({error:`请求失败`}));throw Error(t.error||`HTTP ${e.status}`)}let t=e.body.getReader(),n=new TextDecoder,r=``;A.value=`analyzing`;let i=``,a=[];for(;;){let{done:e,value:o}=await t.read();if(e)break;r+=n.decode(o,{stream:!0});let s=r.split(`
2
- `);r=s.pop()??``;for(let e of s)if(e.startsWith(`event:`))i=e.slice(6).trim(),a=[];else if(e.startsWith(`data:`))a.push(e.slice(5).trim());else if(e===``){if(i&&a.length){let e=a.join(``);try{let t=JSON.parse(e);It(i,t)}catch{}}i=``,a=[]}}}catch(e){$(`${E(`@SRCMAP:分析失败`)}: ${e.message}`,`error`),A.value=`error`}}}function It(e,t){if(e===`log`)$(t.message,t.type||`info`);else if(e===`files`)R.value=Ot(t.files||[]),R.value.forEach(e=>{e.kind===`dir`&&z.value.add(e.path)});else if(e===`result`){M.value={language:t.language||``,entryFile:t.entryFile||``,entryFunction:t.entryFunction||``,nodes:Array.isArray(t.nodes)?t.nodes:[],edges:Array.isArray(t.edges)?t.edges:[],techStack:Array.isArray(t.techStack)?t.techStack:[],summary:t.summary||``,allFiles:Array.isArray(t.allFiles)?t.allFiles:[],codeFiles:Array.isArray(t.codeFiles)?t.codeFiles:[],subsystems:Array.isArray(t.subsystems)?t.subsystems:void 0};let{flowNodes:e,flowEdges:n}=Nt(M.value.nodes,M.value.edges);xt(e),St(n),u(()=>Pt())}else e===`done`&&(t.error?($(`${E(`@SRCMAP:分析失败`)}: ${t.error}`,`error`),A.value=`error`):A.value=`done`)}async function Lt(e){if(!(!e||L.value)&&I.value!==e){L.value=!0,I.value=e,F.value=``;try{let t=await(await fetch(`/api/code-analysis/file-content?path=${encodeURIComponent(k.value)}&file=${encodeURIComponent(e)}`)).json();if(t.error)throw Error(t.error);F.value=t.content||``}catch(e){F.value=`// ${E(`@SRCMAP:加载失败`)}: ${e.message}`}finally{L.value=!1}}}function Rt(e){let t=e.node.data;N.value=t.id,t.file&&Lt(t.file)}function zt(e){z.value.has(e)?z.value.delete(e):z.value.add(e)}let Bt=T(()=>{let e=[];function t(n,r){for(let i of n){let n=z.value.has(i.path);e.push({name:i.name,path:i.path,kind:i.kind,depth:r,expanded:n}),i.kind===`dir`&&n&&t(i.children,r+1)}}return t(R.value,0),e});return h(()=>O.currentDirectory,e=>{e&&!k.value&&(k.value=e)}),h([F,I],([e,t])=>{let n=X.value;if(!n)return;let r=jt(t||``),i=n.getModel(),a=m.createModel(e||``,r);n.setModel(a),i?.dispose(),n.setScrollPosition({scrollTop:0,scrollLeft:0})}),re(()=>{Mt(),!k.value&&O.currentDirectory&&(k.value=O.currentDirectory),D=new MutationObserver(()=>ie()),D.observe(document.documentElement,{attributes:!0,attributeFilter:[`data-theme`]})}),t(()=>{X.value?.getModel()?.dispose(),X.value?.dispose(),Y(),D?.disconnect(),D=null}),(e,t)=>{let r=fe;return b(),d(`div`,_e,[n(`div`,ve,[n(`div`,ye,[t[9]||=n(`svg`,{viewBox:`0 0 24 24`,width:`18`,height:`18`,fill:`none`,stroke:`currentColor`,"stroke-width":`1.8`,class:`sm-icon-map`},[n(`polygon`,{points:`3 6 9 3 15 6 21 3 21 18 15 21 9 18 3 21`}),n(`line`,{x1:`9`,y1:`3`,x2:`9`,y2:`18`}),n(`line`,{x1:`15`,y1:`6`,x2:`15`,y2:`21`})],-1),n(`span`,be,w(_(E)(`@SRCMAP:源码地图`)),1)]),n(`div`,xe,[C(n(`input`,{"onUpdate:modelValue":t[0]||=e=>k.value=e,class:`sm-path-input`,placeholder:_(E)(`@SRCMAP:输入项目目录路径`),disabled:Z.value,onKeydown:ee(Ft,[`enter`])},null,40,Se),[[c,k.value]]),n(`button`,{class:`sm-btn sm-btn-primary`,disabled:Z.value,onClick:Ft},[Z.value?(b(),d(o,{key:0},[t[10]||=n(`span`,{class:`sm-spinner`},null,-1),s(` `+w(_(E)(`@SRCMAP:分析中...`)),1)],64)):(b(),d(o,{key:1},[s(w(A.value===`done`?_(E)(`@SRCMAP:重新分析`):_(E)(`@SRCMAP:开始分析`)),1)],64))],8,Ce)]),n(`div`,we,[S(r,{content:_(E)(`@SRCMAP:文件列表`),placement:`bottom`},{default:y(()=>[n(`button`,{class:g([`sm-panel-btn`,{active:B.value.files}]),onClick:t[1]||=e=>B.value.files=!B.value.files},[...t[11]||=[n(`svg`,{viewBox:`0 0 24 24`,width:`16`,height:`16`,fill:`none`,stroke:`currentColor`,"stroke-width":`1.8`},[n(`path`,{d:`M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z`})],-1)]],2)]),_:1},8,[`content`]),S(r,{content:_(E)(`@SRCMAP:调用图`),placement:`bottom`},{default:y(()=>[n(`button`,{class:g([`sm-panel-btn`,{active:B.value.graph}]),onClick:t[2]||=e=>B.value.graph=!B.value.graph},[...t[12]||=[n(`svg`,{viewBox:`0 0 24 24`,width:`16`,height:`16`,fill:`none`,stroke:`currentColor`,"stroke-width":`1.8`},[n(`circle`,{cx:`12`,cy:`12`,r:`3`}),n(`circle`,{cx:`3`,cy:`6`,r:`2`}),n(`circle`,{cx:`21`,cy:`6`,r:`2`}),n(`circle`,{cx:`3`,cy:`18`,r:`2`}),n(`circle`,{cx:`21`,cy:`18`,r:`2`}),n(`line`,{x1:`5`,y1:`6`,x2:`9`,y2:`11`}),n(`line`,{x1:`19`,y1:`6`,x2:`15`,y2:`11`}),n(`line`,{x1:`5`,y1:`18`,x2:`9`,y2:`13`}),n(`line`,{x1:`19`,y1:`18`,x2:`15`,y2:`13`})],-1)]],2)]),_:1},8,[`content`]),S(r,{content:_(E)(`@SRCMAP:源码面板`),placement:`bottom`},{default:y(()=>[n(`button`,{class:g([`sm-panel-btn`,{active:B.value.source}]),onClick:t[3]||=e=>B.value.source=!B.value.source},[...t[13]||=[n(`svg`,{viewBox:`0 0 24 24`,width:`16`,height:`16`,fill:`none`,stroke:`currentColor`,"stroke-width":`1.8`},[n(`polyline`,{points:`16 18 22 12 16 6`}),n(`polyline`,{points:`8 6 2 12 8 18`})],-1)]],2)]),_:1},8,[`content`])])]),n(`div`,Te,[C(n(`div`,{class:`sm-panel sm-panel-files`,style:p({width:H.value+`px`})},[n(`div`,Ee,[n(`button`,{class:g([`sm-tab-btn`,{active:P.value===`files`}]),onClick:t[4]||=e=>P.value=`files`},[t[14]||=n(`svg`,{viewBox:`0 0 24 24`,width:`12`,height:`12`,fill:`none`,stroke:`currentColor`,"stroke-width":`1.8`},[n(`path`,{d:`M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z`})],-1),s(` `+w(_(E)(`@SRCMAP:文件列表`))+` `,1),M.value?(b(),d(`span`,De,w(M.value.allFiles.length),1)):a(``,!0)],2),n(`button`,{class:g([`sm-tab-btn`,{active:P.value===`outline`}]),onClick:t[5]||=e=>P.value=`outline`},[t[15]||=i(`<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.8" data-v-71d3569e><line x1="8" y1="6" x2="21" y2="6" data-v-71d3569e></line><line x1="8" y1="12" x2="21" y2="12" data-v-71d3569e></line><line x1="8" y1="18" x2="21" y2="18" data-v-71d3569e></line><line x1="3" y1="6" x2="3.01" y2="6" data-v-71d3569e></line><line x1="3" y1="12" x2="3.01" y2="12" data-v-71d3569e></line><line x1="3" y1="18" x2="3.01" y2="18" data-v-71d3569e></line></svg>`,1),s(` `+w(_(E)(`@SRCMAP:大纲`)),1)],2)]),C(n(`div`,Oe,[Bt.value.length>0?(b(!0),d(o,{key:0},v(Bt.value,e=>(b(),d(`div`,{key:e.path,class:g([`sm-tree-node`,{"sm-tree-node--dir":e.kind===`dir`,"sm-tree-node--active":e.kind===`file`&&I.value===e.path}]),style:p({paddingLeft:10+e.depth*14+`px`}),onClick:t=>e.kind===`dir`?zt(e.path):Lt(e.path)},[e.kind===`dir`?(b(),d(`span`,{key:0,class:g([`sm-tree-arrow`,{expanded:e.expanded}])},[...t[16]||=[n(`svg`,{viewBox:`0 0 24 24`,width:`10`,height:`10`,fill:`none`,stroke:`currentColor`,"stroke-width":`2.5`,"stroke-linecap":`round`,"stroke-linejoin":`round`},[n(`polyline`,{points:`9 18 15 12 9 6`})],-1)]],2)):(b(),d(`span`,Ae)),e.kind===`dir`?(b(),d(`svg`,je,[n(`use`,{"xlink:href":`#${_(me)(e.name)||`icon-folder`}`},null,8,Me)])):(b(),d(`svg`,Ne,[n(`use`,{"xlink:href":`#${_(me)(e.name)}`},null,8,Pe)])),n(`span`,{class:`sm-tree-name`,title:e.path},w(e.name),9,Fe)],14,ke))),128)):(b(),d(`div`,Ie,w(_(E)(`@SRCMAP:暂无文件,请先开始分析`)),1))],512),[[l,P.value===`files`]]),C(n(`div`,Le,[Dt.value.length>0?(b(!0),d(o,{key:0},v(Dt.value,e=>(b(),d(`div`,{key:e.displayName,class:`sm-outline-group`},[n(`div`,{class:`sm-outline-group-header`,style:p({color:e.color})},[t[17]||=n(`svg`,{viewBox:`0 0 24 24`,width:`8`,height:`8`,fill:`currentColor`},[n(`circle`,{cx:`12`,cy:`12`,r:`6`})],-1),s(` `+w(e.displayName)+` `,1),n(`span`,Re,w(e.nodes.length),1)],4),(b(!0),d(o,null,v(e.nodes,t=>(b(),d(`div`,{key:t.id,class:g([`sm-outline-node`,{"sm-outline-node--active":N.value===t.id}]),onClick:e=>{N.value=t.id,t.file&&Lt(t.file)}},[n(`span`,{class:`sm-outline-dot`,style:p({background:e.color})},null,4),n(`span`,{class:`sm-outline-label`,title:t.file||t.label},w(t.label),9,Be),t.description?(b(),d(`span`,Ve,w(t.description.length>18?t.description.slice(0,18)+`…`:t.description),1)):a(``,!0)],10,ze))),128))]))),128)):(b(),d(`div`,He,w(_(E)(`@SRCMAP:暂无分析结果`)),1))],512),[[l,P.value===`outline`]])],4),[[l,B.value.files]]),C(n(`div`,{class:`sm-resizer sm-resizer-v`,onMousedown:t[6]||=e=>J(`files`,e)},null,544),[[l,B.value.files&&B.value.graph]]),C(n(`div`,Ue,[M.value?(b(),d(`div`,We,[n(`span`,Ge,w(M.value.language),1),M.value.subsystems&&M.value.subsystems.length>1?(b(!0),d(o,{key:0},v(M.value.subsystems,e=>(b(),d(`span`,{key:e.name,class:`sm-subsystem-tag`,style:p({borderColor:e.color,color:e.color})},`● `+w(e.displayName||e.name),5))),128)):(b(!0),d(o,{key:1},v(M.value.techStack.slice(0,4),e=>(b(),d(`span`,{key:e,class:`sm-tech-tag`},w(e),1))),128)),n(`span`,Ke,w(M.value.summary),1)])):a(``,!0),n(`div`,qe,[n(`div`,Je,[n(`button`,{class:`sm-layout-btn`,disabled:V.value||!M.value,onClick:Pt},[t[18]||=i(`<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="1.8" data-v-71d3569e><rect x="3" y="3" width="7" height="7" rx="1" data-v-71d3569e></rect><rect x="14" y="3" width="7" height="7" rx="1" data-v-71d3569e></rect><rect x="3" y="14" width="7" height="7" rx="1" data-v-71d3569e></rect><rect x="14" y="14" width="7" height="7" rx="1" data-v-71d3569e></rect></svg>`,1),s(` `+w(V.value?_(E)(`@SRCMAP:布局中...`):_(E)(`@SRCMAP:优化布局`)),1)],8,Ye)]),S(_(de),{class:`sm-vue-flow`,"default-viewport":{zoom:1},"min-zoom":.2,"max-zoom":4,"fit-view-on-init":``,onNodeClick:Rt},{"node-default":y(({data:e,label:t})=>[S(_(pe),{type:`target`,position:_(te).Top},null,8,[`position`]),n(`div`,{class:`sm-fn-inner`,title:`${t}${e?.description?`
3
- `+e.description:``}`},[n(`div`,Ze,w(t),1),e?.description?(b(),d(`div`,Qe,w(e.description.length>22?e.description.slice(0,22)+`…`:e.description),1)):a(``,!0)],8,Xe),S(_(pe),{type:`source`,position:_(te).Bottom},null,8,[`position`])]),default:y(()=>[S(_(oe),{variant:_(se).Dots,gap:20,size:1,"pattern-color":Et.value},null,8,[`variant`,`pattern-color`]),S(_(le)),S(_(ue),{"node-color":`#3b82f6`,"mask-color":f.value===`dark`?`rgba(0,0,0,0.55)`:`rgba(15,23,42,0.06)`},null,8,[`mask-color`])]),_:1}),!M.value&&!Z.value?(b(),d(`div`,$e,[(b(),d(`svg`,et,[...t[19]||=[n(`polygon`,{points:`3 6 9 3 15 6 21 3 21 18 15 21 9 18 3 21`},null,-1),n(`line`,{x1:`9`,y1:`3`,x2:`9`,y2:`18`},null,-1),n(`line`,{x1:`15`,y1:`6`,x2:`15`,y2:`21`},null,-1)]])),n(`p`,null,w(_(E)(`@SRCMAP:输入项目路径后点击开始分析`)),1)])):a(``,!0),Z.value&&!M.value?(b(),d(`div`,tt,[t[20]||=n(`span`,{class:`sm-spinner sm-spinner-lg`},null,-1),n(`p`,null,w(_(E)(`@SRCMAP:AI 正在分析项目结构...`)),1)])):a(``,!0)]),n(`div`,{class:`sm-resizer sm-resizer-h`,onMousedown:t[7]||=e=>J(`log`,e)},null,32),n(`div`,{class:`sm-log-panel`,style:p({height:W.value+`px`})},[n(`div`,nt,[t[21]||=n(`svg`,{viewBox:`0 0 24 24`,width:`12`,height:`12`,fill:`none`,stroke:`currentColor`,"stroke-width":`1.8`},[n(`polyline`,{points:`4 17 10 11 4 5`}),n(`line`,{x1:`12`,y1:`19`,x2:`20`,y2:`19`})],-1),s(` `+w(_(E)(`@SRCMAP:Agent 日志`))+` `,1),Z.value?(b(),d(`span`,rt)):a(``,!0)]),n(`div`,it,[(b(!0),d(o,null,v(j.value.slice(-50),e=>(b(),d(`div`,{key:e.id,class:g([`sm-log-entry`,`sm-log-entry--${e.type}`])},w(e.message),3))),128)),j.value.length===0?(b(),d(`div`,at,w(_(E)(`@SRCMAP:等待开始分析`)),1)):a(``,!0)],512)],4)],512),[[l,B.value.graph]]),C(n(`div`,{class:`sm-resizer sm-resizer-v`,onMousedown:t[8]||=e=>J(`source`,e)},null,544),[[l,B.value.graph&&B.value.source]]),C(n(`div`,{class:`sm-panel sm-panel-source`,style:p({width:U.value+`px`})},[n(`div`,ot,[t[22]||=n(`svg`,{viewBox:`0 0 24 24`,width:`13`,height:`13`,fill:`none`,stroke:`currentColor`,"stroke-width":`1.8`},[n(`polyline`,{points:`16 18 22 12 16 6`}),n(`polyline`,{points:`8 6 2 12 8 18`})],-1),s(` `+w(I.value||_(E)(`@SRCMAP:源码面板`))+` `,1),Q.value?(b(),d(`span`,st,w(Q.value.label),1)):a(``,!0)]),Q.value?(b(),d(`div`,ct,[n(`div`,lt,w(Q.value.label),1),n(`div`,ut,[s(w(Q.value.file),1),Q.value.line?(b(),d(`span`,dt,` :`+w(Q.value.line),1)):a(``,!0)]),Q.value.description?(b(),d(`div`,ft,w(Q.value.description),1)):a(``,!0)])):a(``,!0),n(`div`,pt,[L.value?(b(),d(`div`,mt,[...t[23]||=[n(`span`,{class:`sm-spinner`},null,-1)]])):a(``,!0),!L.value&&!F.value?(b(),d(`div`,ht,w(_(E)(`@SRCMAP:点击调用图中的节点查看源码`)),1)):a(``,!0),n(`div`,{ref_key:`monacoContainerRef`,ref:yt,class:`sm-monaco-container`},null,512)])],4),[[l,B.value.source]])])])}}}),[[`__scopeId`,`data-v-71d3569e`]]);export{O as default};