zen-gitsync 2.13.21 → 2.13.23
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/scripts/README_COLOR_CONVERTER.md +196 -196
- package/scripts/README_FONTSIZE_CONVERTER.md +278 -278
- package/scripts/README_SPACING_CONVERTER.md +126 -126
- package/scripts/README_STYLE_VARS.md +180 -180
- package/src/ui/public/assets/{AppVersionBadge-D9w7Usgz.js → AppVersionBadge-BUS8w2f8.js} +2 -2
- package/src/ui/public/assets/{BranchSelector-BJpA20eU.js → BranchSelector-Dm7nENer.js} +1 -1
- package/src/ui/public/assets/{CommandConsole-DUyDJSaq.js → CommandConsole-C1W9ZQkE.js} +2 -2
- package/src/ui/public/assets/{CommitForm-BCWCchcJ.js → CommitForm-C76W1ZZH.js} +1 -1
- package/src/ui/public/assets/{CustomCommandManager-DRzAgnp3.js → CustomCommandManager-JKx8JYqn.js} +1 -1
- package/src/ui/public/assets/{EditorView-DuB3Btcp.js → EditorView-CriUYDvi.js} +0 -0
- package/src/ui/public/assets/{FileDiffViewer-DldATw5k.js → FileDiffViewer-DNWsiIGt.js} +1 -1
- package/src/ui/public/assets/{FlowExecutionViewer-BNuIB5Bi.js → FlowExecutionViewer-CTDh2bCD.js} +1 -1
- package/src/ui/public/assets/{FlowOrchestrationWorkspace-BcA7LT02.js → FlowOrchestrationWorkspace-Chah2aGN.js} +1 -1
- package/src/ui/public/assets/{GitStatus-BFpSu8gV.js → GitStatus-DP-DQsiS.js} +1 -1
- package/src/ui/public/assets/{ImagePreview-C8eIs5yL.js → ImagePreview-D8klNa6F.js} +1 -1
- package/src/ui/public/assets/{LogList-C4mS0mZ4.js → LogList-Z76_5USQ.js} +1 -1
- package/src/ui/public/assets/{ProjectStartupButton-BBRjQvH3.js → ProjectStartupButton-C5oF4unq.js} +1 -1
- package/src/ui/public/assets/{RemoteRepoCard-OUVJTKSC.js → RemoteRepoCard-B-fGjb8Y.js} +1 -1
- package/src/ui/public/assets/ResetToRemoteButton-BTIEz5PB.css +1 -0
- package/src/ui/public/assets/{ResetToRemoteButton-Bpqx8Rhc.js → ResetToRemoteButton-BixSoEVs.js} +1 -1
- package/src/ui/public/assets/{SourceMapView-CDETiU3C.js → SourceMapView-C9HZiJ9N.js} +1 -1
- package/src/ui/public/assets/{TemplateManager-idRwh__b.js → TemplateManager-C7SuKq0f.js} +1 -1
- package/src/ui/public/assets/{UserInputNode-pnY8iicS.js → UserInputNode-CYy0UpzR.js} +1 -1
- package/src/ui/public/assets/WorkbenchView-A9Oik707.js +12 -0
- package/src/ui/public/assets/WorkbenchView-DvML7yIv.css +1 -0
- package/src/ui/public/assets/{_plugin-vue_export-helper-vLPduyLG.js → _plugin-vue_export-helper-BfbhkIv4.js} +2 -2
- package/src/ui/public/assets/index-CDa9xf0V.css +1 -0
- package/src/ui/public/assets/{index-Bi1D0fFa.js → index-DPSGvy34.js} +3 -3
- package/src/ui/public/favicon.svg +75 -75
- package/src/ui/public/index.html +20 -20
- package/src/ui/public/logo.svg +74 -74
- package/src/ui/server/routes/workbench.js +171 -31
- package/src/ui/public/assets/ResetToRemoteButton-DQuq2KfL.css +0 -1
- package/src/ui/public/assets/WorkbenchView-B83HdnYr.js +0 -12
- package/src/ui/public/assets/WorkbenchView-DslIMLgz.css +0 -1
- package/src/ui/public/assets/index-DYjISu_4.css +0 -1
|
@@ -66,11 +66,11 @@ const DEFAULT_INSTRUCTION = `你是一名资深软件架构师。
|
|
|
66
66
|
- 2 层目录树(最多 200 行)
|
|
67
67
|
|
|
68
68
|
【输出要求】
|
|
69
|
-
1.
|
|
69
|
+
1. 给出一段中文「项目架构说明」,长度不限,模型自行决定篇幅与详尽程度,能写多详细就多详细。覆盖:项目整体定位、技术栈、模块划分、核心流程、关键设计决策。
|
|
70
70
|
2. 必须引用子项目里实际存在的文件路径、目录名、依赖名,不要编造。
|
|
71
71
|
3. 多个子项目时:先逐个说明,最后输出一段「整体架构」总结它们之间的关系。
|
|
72
72
|
4. 语气专业、具体、面向接手这个项目的开发者。
|
|
73
|
-
5. 只返回 JSON:{ "name": "
|
|
73
|
+
5. 只返回 JSON:{ "name": "项目名(建议:项目名+架构说明)", "summary": "架构说明正文" }。`;
|
|
74
74
|
|
|
75
75
|
// 单个附件最大 5MB;与 Anthropic Messages API 文档约束一致
|
|
76
76
|
const MAX_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
@@ -388,8 +388,70 @@ async function listDirTree(projectPath, maxDepth = 2, maxEntries = 400) {
|
|
|
388
388
|
return lines.join('\n');
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
+
// 剥离 LLM 输出的 思考块、```json``` 代码块围栏,以及首尾说明文字。
|
|
392
|
+
// 原贪婪正则 /({[\s\S]*})/ 会把 思考块(内含未配对花括号或字符串)一起吞进 JSON.parse,
|
|
393
|
+
// 在 DeepSeek 等会输出 推理链的模型上偶发 "Unterminated string in JSON"。
|
|
394
|
+
function stripThinkingBlocks(content) {
|
|
395
|
+
let s = String(content || '');
|
|
396
|
+
// 1) 显式 思考块(成对标签)
|
|
397
|
+
s = s.replace(/<think(?:ing)?>[\s\S]*?<\/think(?:ing)?>/gi, '');
|
|
398
|
+
// 2) 某些 provider 流式结尾会留下裸 <think>...</think> 之外的残段(无结束标签时被截断)
|
|
399
|
+
s = s.replace(/<think(?:ing)?>[\s\S]*$/gi, '');
|
|
400
|
+
// 3) ```json ... ``` 代码块围栏(保留内部 JSON)
|
|
401
|
+
s = s.replace(/```json\s*/gi, '').replace(/```/g, '');
|
|
402
|
+
return s;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// 在剥离 思考块 的文本里定位第一个「平衡花括号对象」的起点/终点。
|
|
406
|
+
// 跳过字符串内的 {/}(含转义)以及 Jinja {{ }} 模板占位符;
|
|
407
|
+
// 同时只在「JSON 特征显著」的 { 处起算 —— 即该 { 之后不远处出现 " 字符串。
|
|
408
|
+
// 这能避免把 LLM 思考文本里的裸 { ... }(如 "see { views, stores }")误当 JSON 起点。
|
|
409
|
+
function extractFirstJsonObject(content) {
|
|
410
|
+
const s = String(content || '');
|
|
411
|
+
let depth = 0;
|
|
412
|
+
let inStr = false;
|
|
413
|
+
let escape = false;
|
|
414
|
+
let start = -1;
|
|
415
|
+
for (let i = 0; i < s.length; i++) {
|
|
416
|
+
const ch = s[i];
|
|
417
|
+
if (inStr) {
|
|
418
|
+
if (escape) { escape = false; continue; }
|
|
419
|
+
if (ch === '\\') { escape = true; continue; }
|
|
420
|
+
if (ch === '"') { inStr = false; }
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
if (ch === '"') { inStr = true; continue; }
|
|
424
|
+
// 跳过 Jinja {{ }} 占位符(提示词模板常用,勿当 JSON)
|
|
425
|
+
if (ch === '{' && s[i + 1] === '{') { i++; continue; }
|
|
426
|
+
if (ch === '}' && s[i - 1] === '}') { continue; }
|
|
427
|
+
if (ch === '{') {
|
|
428
|
+
if (depth === 0) {
|
|
429
|
+
// 启发式判断「这看起来像 JSON 起点」:
|
|
430
|
+
// 1. { 之前紧邻「真正的语义字符」(字母/数字/中文/下划线) → 不是 JSON 起点(散文里的花括号)
|
|
431
|
+
// 但接受 . , ; : 等句末标点(LLM 经常以「总结:{...}」直接开头 JSON)
|
|
432
|
+
// 2. { 之后跳过空白第一个字符必须是 " 或 }(空对象)
|
|
433
|
+
const before = i > 0 ? s[i - 1] : '';
|
|
434
|
+
if (/[A-Za-z0-9_一-龥]/.test(before)) continue;
|
|
435
|
+
let j = i + 1;
|
|
436
|
+
while (j < s.length && /\s/.test(s[j])) j++;
|
|
437
|
+
const firstNonWs = s[j];
|
|
438
|
+
if (firstNonWs !== '"' && firstNonWs !== '}') continue;
|
|
439
|
+
start = i;
|
|
440
|
+
}
|
|
441
|
+
depth++;
|
|
442
|
+
} else if (ch === '}') {
|
|
443
|
+
if (depth > 0) depth--;
|
|
444
|
+
if (depth === 0 && start !== -1) {
|
|
445
|
+
return s.slice(start, i + 1);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
// 兜底:找不到平衡对象时返回原文(让上层 JSON.parse 报清晰错误)
|
|
450
|
+
return s;
|
|
451
|
+
}
|
|
452
|
+
|
|
391
453
|
async function callLlmJson(model, prompt, opts = {}) {
|
|
392
|
-
const {
|
|
454
|
+
const { timeoutMs = 60000, images = [] } = opts;
|
|
393
455
|
const { default: fetch } = await import('node-fetch').catch(() => ({ default: globalThis.fetch }));
|
|
394
456
|
const url = `${String(model.baseURL || '').replace(/\/$/, '')}/chat/completions`;
|
|
395
457
|
const headers = { 'Content-Type': 'application/json' };
|
|
@@ -410,7 +472,6 @@ async function callLlmJson(model, prompt, opts = {}) {
|
|
|
410
472
|
const body = JSON.stringify({
|
|
411
473
|
model: model.model,
|
|
412
474
|
messages: [{ role: 'user', content: userContent }],
|
|
413
|
-
max_tokens: maxTokens,
|
|
414
475
|
temperature: 0.4,
|
|
415
476
|
response_format: { type: 'json_object' },
|
|
416
477
|
stream: false,
|
|
@@ -422,18 +483,37 @@ async function callLlmJson(model, prompt, opts = {}) {
|
|
|
422
483
|
const resp = await fetch(url, { method: 'POST', headers, body, signal: controller.signal });
|
|
423
484
|
const data = await resp.json().catch(() => ({}));
|
|
424
485
|
if (!resp.ok) throw new Error(data?.error?.message || `HTTP ${resp.status}`);
|
|
425
|
-
const content = data?.choices?.[0]?.message?.content || '
|
|
486
|
+
const content = data?.choices?.[0]?.message?.content || '';
|
|
487
|
+
if (!content.trim()) {
|
|
488
|
+
throw new Error('LLM 返回内容为空');
|
|
489
|
+
}
|
|
426
490
|
try {
|
|
427
|
-
const
|
|
428
|
-
return JSON.parse(
|
|
429
|
-
} catch {
|
|
430
|
-
|
|
491
|
+
const cleaned = stripThinkingBlocks(content);
|
|
492
|
+
return JSON.parse(extractFirstJsonObject(cleaned));
|
|
493
|
+
} catch (err) {
|
|
494
|
+
const snippet = content.length > 500 ? content.slice(0, 500) + '…' : content;
|
|
495
|
+
throw new Error(`LLM 返回非 JSON 或被截断:${err.message};原始内容片段:${snippet}`);
|
|
431
496
|
}
|
|
432
497
|
} finally {
|
|
433
498
|
clearTimeout(timer);
|
|
434
499
|
}
|
|
435
500
|
}
|
|
436
501
|
|
|
502
|
+
async function callLlmJsonWithRetry(model, prompt, opts = {}, retries = 1) {
|
|
503
|
+
let lastErr;
|
|
504
|
+
for (let i = 0; i <= retries; i++) {
|
|
505
|
+
try {
|
|
506
|
+
return await callLlmJson(model, prompt, opts);
|
|
507
|
+
} catch (err) {
|
|
508
|
+
lastErr = err;
|
|
509
|
+
if (i < retries) {
|
|
510
|
+
await new Promise(r => setTimeout(r, 800 * (i + 1)));
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
throw lastErr;
|
|
515
|
+
}
|
|
516
|
+
|
|
437
517
|
/**
|
|
438
518
|
* 流式调用 OpenAI 兼容 LLM。每收到一个 chunk 调 onDelta 回调。
|
|
439
519
|
* onDelta 接收 { thinking?: string, content?: string },二选一。
|
|
@@ -1118,13 +1198,21 @@ async function runTaskQueue(task, repoPath, branch, opts) {
|
|
|
1118
1198
|
const priorOutputs = fromIndex > 0
|
|
1119
1199
|
? await collectPriorOutputsUpTo(task, fromIndex)
|
|
1120
1200
|
: []
|
|
1201
|
+
// 连续模式(默认):任意 sub 终态非 done(cancelled / error)→ 整批停,后续 sub 保持 todo。
|
|
1202
|
+
// AI 拆出来的 sub 一般前后强依赖(前一步产出是后一步输入),出错就停下来让用户决策。
|
|
1203
|
+
// 关闭后回退旧行为:单个 sub 失败不影响后续 sub 继续跑。
|
|
1204
|
+
const sequential = task.sequential !== false
|
|
1121
1205
|
for (let i = fromIndex; i < task.subtasks.length; i++) {
|
|
1122
1206
|
const sub = task.subtasks[i]
|
|
1123
1207
|
if (sub.status === 'done') continue;
|
|
1124
|
-
await runSingleSubtask(task, sub, repoPath, branch, priorOutputs)
|
|
1208
|
+
const outcome = await runSingleSubtask(task, sub, repoPath, branch, priorOutputs)
|
|
1125
1209
|
// 逐 sub 落盘:之前只在队列跑完才 persistTaskAfterRun,中途崩溃会丢已完成
|
|
1126
1210
|
// sub 的状态。runSingleSubtask 已经把 sub.status 改完,这里补一次落盘。
|
|
1127
1211
|
await persistTaskAfterRun(task)
|
|
1212
|
+
if (sequential && outcome !== 'done') {
|
|
1213
|
+
// cancelled / error → 后续 sub 全部保持 todo,不再继续
|
|
1214
|
+
break
|
|
1215
|
+
}
|
|
1128
1216
|
}
|
|
1129
1217
|
}
|
|
1130
1218
|
|
|
@@ -1325,7 +1413,12 @@ ${prompt}`
|
|
|
1325
1413
|
job.exitCode = 130; // 128 + SIGINT(2),约定俗成的"用户取消"退出码
|
|
1326
1414
|
job.status = 'cancelled';
|
|
1327
1415
|
job.error = '用户已停止执行';
|
|
1328
|
-
// sub
|
|
1416
|
+
// 同步把闭包里的 sub 也置 cancelled(cancel 接口的 syncSubToCancelled 改了磁盘 task
|
|
1417
|
+
// 引用,但 runSingleSubtask 形参里的 sub 是另一份内存引用)。否则 finally publish sub:update
|
|
1418
|
+
// 会用 status='running' 覆盖掉前端已渲染的 cancelled 状态,导致 UI 反复跳回 running。
|
|
1419
|
+
// 同时 sequential=true 时 runTaskQueue 会看到 outcome='cancelled',break 不再跑后续 sub。
|
|
1420
|
+
sub.status = 'cancelled';
|
|
1421
|
+
if (!sub.error) sub.error = '用户已停止执行';
|
|
1329
1422
|
} else {
|
|
1330
1423
|
job.exitCode = 0;
|
|
1331
1424
|
job.status = 'done';
|
|
@@ -1359,6 +1452,36 @@ ${prompt}`
|
|
|
1359
1452
|
console.warn('[workbench] flushJobsSaveNow failed (job id=' + job.id + ', status=' + job.status + '):', err && err.message || err)
|
|
1360
1453
|
}
|
|
1361
1454
|
}
|
|
1455
|
+
// 把 sub 的终态返回给 runTaskQueue,用于「连续模式」判断要不要 break 整批队列
|
|
1456
|
+
return job.status // 'done' | 'cancelled' | 'error'
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
/**
|
|
1460
|
+
* 把被取消的 sub 同步置 'cancelled' 并落盘。
|
|
1461
|
+
* cancelJob 路径专用:前端 taskIsRunning/sub.is-running 都看 sub.status,不改就会出现
|
|
1462
|
+
* "主任务黄点 + sub running 动效 + 右侧执行完成"三处不一致。
|
|
1463
|
+
*
|
|
1464
|
+
* 简单任务的虚拟 subId 不在 tasks.json 里(task.subtasks 是 complex 才有),所以这里
|
|
1465
|
+
* 找不到 sub 时静默返回;简单任务的 running 状态由 job 数组单独维护(见 taskIsRunning)。
|
|
1466
|
+
*
|
|
1467
|
+
* @returns {{ taskId: string, sub: object } | null} 找到并更新时返回新 sub,否则 null
|
|
1468
|
+
*/
|
|
1469
|
+
async function syncSubToCancelled(job) {
|
|
1470
|
+
if (!job || !job.taskId || !job.subId) return null
|
|
1471
|
+
const data = await readJson(TASKS_FILE, { tasks: [] })
|
|
1472
|
+
const task = (data.tasks || []).find(x => x.id === job.taskId)
|
|
1473
|
+
if (!task || !Array.isArray(task.subtasks)) return null
|
|
1474
|
+
const sub = task.subtasks.find(s => s && s.id === job.subId)
|
|
1475
|
+
if (!sub) return null
|
|
1476
|
+
if (sub.status === 'cancelled') return { taskId: task.id, sub } // 已置过,幂等返回
|
|
1477
|
+
sub.status = 'cancelled'
|
|
1478
|
+
sub.error = '用户已停止执行'
|
|
1479
|
+
sub.errorAt = nowIso()
|
|
1480
|
+
task.updatedAt = nowIso()
|
|
1481
|
+
await writeJson(TASKS_FILE, data)
|
|
1482
|
+
publish('sub:update', { taskId: task.id, sub })
|
|
1483
|
+
publish('task:update', task)
|
|
1484
|
+
return { taskId: task.id, sub }
|
|
1362
1485
|
}
|
|
1363
1486
|
|
|
1364
1487
|
/** 把 task.subtasks 写回 tasks.json,并广播 task:update。runTaskQueue 和"单 sub 执行"共用。 */
|
|
@@ -1488,7 +1611,7 @@ export function registerWorkbenchRoutes({ app, getCurrentProjectPath, getProject
|
|
|
1488
1611
|
}
|
|
1489
1612
|
|
|
1490
1613
|
const projectName = path.basename(projectPath);
|
|
1491
|
-
const LLM_OPTS = {
|
|
1614
|
+
const LLM_OPTS = { timeoutMs: 1200000 };
|
|
1492
1615
|
|
|
1493
1616
|
// ── 第一阶段:基于可编辑指令 + 根目录概览,生成「可复用的提示词模板」 ──
|
|
1494
1617
|
const overviewBlock = subProjects.map(sp =>
|
|
@@ -1518,12 +1641,12 @@ ${subProjects.map(sp => {
|
|
|
1518
1641
|
|
|
1519
1642
|
只返回 JSON:
|
|
1520
1643
|
{
|
|
1521
|
-
"name": "
|
|
1522
|
-
"template": "
|
|
1644
|
+
"name": "项目名(建议:项目名+架构说明,可根据实际项目特征调整)",
|
|
1645
|
+
"template": "可复用的提示词模板,长度不限,请充分覆盖 {{task.title}} / {{task.desc}} / {{sub.title}} / {{sub.desc}} / {{repo.path}} / {{branch}} 这 6 个变量的用法与上下文"
|
|
1523
1646
|
}`;
|
|
1524
1647
|
|
|
1525
|
-
const first = await
|
|
1526
|
-
const templateName = String(first.name || '').trim() || projectName
|
|
1648
|
+
const first = await callLlmJsonWithRetry(model, firstPrompt, LLM_OPTS);
|
|
1649
|
+
const templateName = String(first.name || '').trim() || `${projectName}架构说明`;
|
|
1527
1650
|
const template = String(first.template || '').trim();
|
|
1528
1651
|
|
|
1529
1652
|
// ── 第二阶段:为每个子项目分别生成总结(单子项目 = 现在的行为) ──
|
|
@@ -1551,13 +1674,17 @@ ${sp.readme || '(无)'}
|
|
|
1551
1674
|
|
|
1552
1675
|
只返回 JSON:
|
|
1553
1676
|
{
|
|
1554
|
-
"summary": "
|
|
1677
|
+
"summary": "该子项目的架构说明,长度不限,模型自行决定篇幅与详尽程度,能写多详细就多详细"
|
|
1555
1678
|
}`;
|
|
1556
|
-
const r = await
|
|
1679
|
+
const r = await callLlmJsonWithRetry(model, subPrompt, LLM_OPTS);
|
|
1557
1680
|
return { name: sp.name, root: sp.root, summary: String(r.summary || '').trim() };
|
|
1558
1681
|
}
|
|
1559
1682
|
|
|
1560
|
-
|
|
1683
|
+
// 串行执行,避免并发触发 provider 限流(429)导致整批失败
|
|
1684
|
+
const subSummaries = [];
|
|
1685
|
+
for (const sp of subProjects) {
|
|
1686
|
+
subSummaries.push(await summarizeOneSub(sp));
|
|
1687
|
+
}
|
|
1561
1688
|
|
|
1562
1689
|
// ── 第三阶段:仅多子项目时合并(单子项目直接拿它的 summary) ──
|
|
1563
1690
|
let finalSummary = '';
|
|
@@ -1566,32 +1693,31 @@ ${sp.readme || '(无)'}
|
|
|
1566
1693
|
if (subSummaries.length === 1) {
|
|
1567
1694
|
finalSummary = subSummaries[0].summary;
|
|
1568
1695
|
} else {
|
|
1569
|
-
const mergePrompt = `你是项目架构师。下列是同一仓库下 N
|
|
1696
|
+
const mergePrompt = `你是项目架构师。下列是同一仓库下 N 个子项目的架构说明,请合并输出**单一**的「项目架构说明」,长度不限,模型自行决定篇幅与详尽程度。覆盖:项目整体定位、技术栈、模块划分、子项目间关系、核心流程、关键设计决策。
|
|
1570
1697
|
子项目之间用清晰的小标题或编号分隔。最后输出一段「整体架构」总结它们如何协同。
|
|
1571
1698
|
只引用实际出现的子项目名 / 文件路径 / 依赖名,不要编造。只返回 JSON:
|
|
1572
1699
|
|
|
1573
1700
|
{
|
|
1574
|
-
"name": "
|
|
1701
|
+
"name": "项目名(建议:项目名+架构说明)",
|
|
1575
1702
|
"summary": "合并后的架构说明"
|
|
1576
1703
|
}
|
|
1577
1704
|
|
|
1578
1705
|
## 子项目说明
|
|
1579
1706
|
${subSummaries.map((s, i) => `\n### [${i + 1}] ${s.name} (${s.root})\n${s.summary || '(空)'}`).join('\n')}`;
|
|
1580
1707
|
|
|
1581
|
-
const merged = await
|
|
1708
|
+
const merged = await callLlmJsonWithRetry(model, mergePrompt, LLM_OPTS);
|
|
1582
1709
|
finalSummary = String(merged.summary || '').trim()
|
|
1583
1710
|
|| subSummaries.map(s => `### ${s.name}\n${s.summary}`).join('\n\n');
|
|
1584
1711
|
finalName = String(merged.name || '').trim() || templateName;
|
|
1585
1712
|
}
|
|
1586
1713
|
|
|
1714
|
+
// 名称始终固定为「<项目名>架构说明」,不信任模型返回的 name 字段
|
|
1715
|
+
finalName = `${projectName}架构说明`;
|
|
1716
|
+
|
|
1587
1717
|
if (!finalSummary) {
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
name: finalName,
|
|
1592
|
-
template,
|
|
1593
|
-
result: '',
|
|
1594
|
-
content: template
|
|
1718
|
+
return res.status(502).json({
|
|
1719
|
+
success: false,
|
|
1720
|
+
error: '模型返回为空,请稍后重试'
|
|
1595
1721
|
});
|
|
1596
1722
|
}
|
|
1597
1723
|
|
|
@@ -2273,11 +2399,14 @@ ${desc ? `描述:${desc}` : '描述:(无)'}${attachmentBlock}${templateB
|
|
|
2273
2399
|
|
|
2274
2400
|
app.post('/api/workbench/tasks', async (req, res) => {
|
|
2275
2401
|
try {
|
|
2276
|
-
const { id, title, desc, promptId, subtasks, type: rawType, simpleOverride } = req.body || {};
|
|
2402
|
+
const { id, title, desc, promptId, subtasks, type: rawType, simpleOverride, sequential: rawSequential } = req.body || {};
|
|
2277
2403
|
// title 非必填:允许空字符串,UI 层会用"未命名任务"占位
|
|
2278
2404
|
const safeTitle = typeof title === 'string' ? title.trim() : '';
|
|
2279
2405
|
// type 归一化:仅接受 'simple' | 'complex',缺省/未知一律按 complex
|
|
2280
2406
|
const taskType = rawType === 'simple' ? 'simple' : 'complex';
|
|
2407
|
+
// sequential 归一化:仅接受 boolean,缺省/非布尔一律 true(连续模式),
|
|
2408
|
+
// 旧任务没字段时也是 true,保留连续默认行为
|
|
2409
|
+
const sequential = rawSequential === false ? false : true;
|
|
2281
2410
|
const safeOverride = typeof simpleOverride === 'string' ? simpleOverride.slice(0, 8000) : '';
|
|
2282
2411
|
const data = await readJson(TASKS_FILE, { tasks: [] });
|
|
2283
2412
|
const tasks = data.tasks || [];
|
|
@@ -2293,6 +2422,8 @@ ${desc ? `描述:${desc}` : '描述:(无)'}${attachmentBlock}${templateB
|
|
|
2293
2422
|
desc: desc || '',
|
|
2294
2423
|
promptId: promptId || null,
|
|
2295
2424
|
type: taskType,
|
|
2425
|
+
// 复杂任务可显式关闭连续模式;简单任务无意义,固定 true
|
|
2426
|
+
sequential: taskType === 'complex' ? sequential : true,
|
|
2296
2427
|
simpleOverride: taskType === 'simple' ? safeOverride : '',
|
|
2297
2428
|
subtasks: Array.isArray(subtasks) ? subtasks.map(s => ({
|
|
2298
2429
|
id: s.id || genId(),
|
|
@@ -2326,6 +2457,8 @@ ${desc ? `描述:${desc}` : '描述:(无)'}${attachmentBlock}${templateB
|
|
|
2326
2457
|
desc: desc || '',
|
|
2327
2458
|
promptId: promptId || null,
|
|
2328
2459
|
type: taskType,
|
|
2460
|
+
// 新建任务:简单任务固定 true,复杂任务用传入值(默认 true)
|
|
2461
|
+
sequential: taskType === 'complex' ? sequential : true,
|
|
2329
2462
|
simpleOverride: taskType === 'simple' ? safeOverride : '',
|
|
2330
2463
|
projectPath: currentProjectPath || '',
|
|
2331
2464
|
subtasks: Array.isArray(subtasks) ? subtasks.map(s => ({
|
|
@@ -2527,7 +2660,11 @@ ${desc ? `描述:${desc}` : '描述:(无)'}${attachmentBlock}${templateB
|
|
|
2527
2660
|
// - 找到正在运行的 job,调 child.kill() 终止 claude 进程
|
|
2528
2661
|
// - Windows 下用 taskkill /T /F 杀进程树(claude 进程可能 fork 出子进程)
|
|
2529
2662
|
// - 加入 cancelledJobs 集合,runTaskQueue 退出循环后会把 job 标为 'cancelled'
|
|
2530
|
-
// -
|
|
2663
|
+
// - 同步把 sub.status 改为 'cancelled' 并 publish sub:update,
|
|
2664
|
+
// 否则前端 sub 列表/任务头黄点会一直显示 running(sub.status 没动)。
|
|
2665
|
+
// 历史这里只改 job.status 导致 UI 误显示「执行中」。
|
|
2666
|
+
// - 只影响这一个 sub;同 task 后续 sub 在 sequential=false 时继续执行,
|
|
2667
|
+
// sequential=true 时由 runTaskQueue 自然 break(见 runSingleSubtask 的 cancelled 分支)
|
|
2531
2668
|
app.post('/api/workbench/jobs/:id/cancel', (req, res) => {
|
|
2532
2669
|
const job = jobs.get(req.params.id)
|
|
2533
2670
|
if (!job) {
|
|
@@ -2542,6 +2679,9 @@ ${desc ? `描述:${desc}` : '描述:(无)'}${attachmentBlock}${templateB
|
|
|
2542
2679
|
job.error = '用户已停止执行'
|
|
2543
2680
|
job.endedAt = nowIso()
|
|
2544
2681
|
publish('job:update', { ...job }) // 用浅拷贝避免序列化 child 引用
|
|
2682
|
+
// 同步把 sub 也置 cancelled,否则 taskIsRunning(sub.status==='running') 一直 true
|
|
2683
|
+
// → 左侧任务头黄点 + 右侧 sub 列表 running 动效都不消失
|
|
2684
|
+
const subInfo = syncSubToCancelled(job)
|
|
2545
2685
|
// 终态:fire-and-forget 同步落盘,cancel 是显式操作,要保证不丢
|
|
2546
2686
|
flushJobsSaveNow().catch(err => console.warn('[workbench] jobs save failed:', err.message))
|
|
2547
2687
|
const child = job.child
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.unstage-all-button[data-v-6fd84bc7] .el-button.from-drawer{padding:6px var(--spacing-md);font-size:var(--font-size-sm);height:32px}.unstage-all-button[data-v-6fd84bc7] .el-button.from-form{padding:10px var(--spacing-lg);height:40px}.git-command-preview[data-v-8150488e]{margin-top:var(--spacing-base)}.preview-container[data-v-8150488e]{background:var(--bg-console);border:1px solid var(--border-console);border-radius:var(--radius-lg);align-items:center;gap:0;transition:border-color .2s;display:flex;overflow:hidden}.preview-container[data-v-8150488e]:hover{border-color:var(--border-console-hover)}.preview-title[data-v-8150488e]{font-size:var(--font-size-xs);font-family:var(--font-mono);color:var(--text-console-muted);white-space:nowrap;background:var(--bg-console-cmd);flex-shrink:0;align-items:center;gap:6px;padding:7px 10px 7px 14px;display:flex}.preview-title[data-v-8150488e]:before{content:"$";color:var(--text-console-prompt);font-size:13px;font-weight:700}.preview-content[data-v-8150488e]{padding:7px var(--spacing-base);font-family:var(--font-mono);font-size:var(--font-size-sm);color:var(--text-console);white-space:nowrap;background:0 0;flex:1;font-weight:500;line-height:1.4;overflow-x:auto}.preview-content[data-v-8150488e]::-webkit-scrollbar{height:3px}.preview-content[data-v-8150488e]::-webkit-scrollbar-track{background:0 0}.preview-content[data-v-8150488e]::-webkit-scrollbar-thumb{background:var(--console-scrollbar);border-radius:2px}[data-v-8150488e]::deep(.copy-command-btn){flex-shrink:0;margin-right:6px;color:var(--text-console-muted)!important}[data-v-8150488e]::deep(.copy-command-btn):hover{color:var(--text-console)!important;background:var(--bg-console-soft)!important}.merge-branch-button[data-v-9dd82c62]{display:inline-block}.merge-dialog-content[data-v-9dd82c62]{gap:var(--spacing-base);flex-direction:column;display:flex}.merge-info-card[data-v-9dd82c62]{align-items:flex-start;gap:var(--spacing-base);padding:var(--spacing-base);border-radius:var(--radius-xl);background:linear-gradient(135deg,#eff6ff 0%,#dbeafe 100%);border:1px solid #93c5fd;display:flex;position:relative;overflow:hidden}.merge-info-card[data-v-9dd82c62]:before{content:"";background:linear-gradient(90deg,#3b82f6 0%,#2563eb 100%);height:3px;position:absolute;top:0;left:0;right:0}.merge-info-card .info-icon[data-v-9dd82c62]{color:#fff;width:40px;height:40px;font-size:var(--font-size-lg);box-shadow:var(--shadow-md);background:linear-gradient(135deg,#3b82f6 0%,#2563eb 100%);border-radius:10px;flex-shrink:0;justify-content:center;align-items:center;display:flex}.merge-info-card .info-content[data-v-9dd82c62]{flex:1}.merge-info-card .info-content h4[data-v-9dd82c62]{font-size:var(--font-size-md);color:#1e3a8a;margin:0 0 6px;font-weight:600;line-height:1.2}.merge-info-card .info-content p[data-v-9dd82c62]{color:#1e40af;margin:0;line-height:1.4}.merge-info-card .info-content code[data-v-9dd82c62]{padding:var(--spacing-xs) 6px;border-radius:var(--radius-base);color:#1e40af;background:#3b82f61a;font-family:Consolas,Monaco,Courier New,monospace;font-weight:600}.merge-form[data-v-9dd82c62]{gap:var(--spacing-base);flex-direction:column;display:flex}.merge-form[data-v-9dd82c62] .el-form-item{margin-bottom:0}.merge-form[data-v-9dd82c62] .el-form-item__label{color:var(--color-text-title);margin-bottom:var(--spacing-base);font-weight:600}.merge-form[data-v-9dd82c62] .el-select .el-input__wrapper,.merge-form[data-v-9dd82c62] .el-select .el-textarea__inner,.merge-form[data-v-9dd82c62] .el-input .el-input__wrapper,.merge-form[data-v-9dd82c62] .el-input .el-textarea__inner,.merge-form[data-v-9dd82c62] .el-textarea .el-input__wrapper,.merge-form[data-v-9dd82c62] .el-textarea .el-textarea__inner{border-radius:var(--radius-lg);border:2px solid var(--color-gray-200);transition:all .3s}.merge-form[data-v-9dd82c62] .el-select .el-input__wrapper:hover,.merge-form[data-v-9dd82c62] .el-select .el-textarea__inner:hover,.merge-form[data-v-9dd82c62] .el-input .el-input__wrapper:hover,.merge-form[data-v-9dd82c62] .el-input .el-textarea__inner:hover,.merge-form[data-v-9dd82c62] .el-textarea .el-input__wrapper:hover,.merge-form[data-v-9dd82c62] .el-textarea .el-textarea__inner:hover{border-color:var(--color-gray-300)}.merge-form[data-v-9dd82c62] .el-select .el-input__wrapper.is-focus,.merge-form[data-v-9dd82c62] .el-select .el-textarea__inner:focus-visible,.merge-form[data-v-9dd82c62] .el-input .el-input__wrapper.is-focus,.merge-form[data-v-9dd82c62] .el-input .el-textarea__inner:focus-visible,.merge-form[data-v-9dd82c62] .el-textarea .el-input__wrapper.is-focus,.merge-form[data-v-9dd82c62] .el-textarea .el-textarea__inner:focus-visible{border-color:#3b82f6;box-shadow:0 0 0 3px #3b82f61a}.merge-form[data-v-9dd82c62] .el-radio-group{gap:var(--spacing-base);display:flex}.merge-form[data-v-9dd82c62] .el-radio-group .el-radio-button{flex:1}.merge-form[data-v-9dd82c62] .el-radio-group .el-radio-button__inner{border-radius:var(--radius-lg);width:100%;transition:all .3s}.merge-options[data-v-9dd82c62]{padding:var(--spacing-base);border-radius:var(--radius-xl)}.options-title[data-v-9dd82c62]{align-items:center;gap:var(--spacing-base);margin:0 0 var(--spacing-md) 0;color:var(--color-text-title);font-weight:600;display:flex}.options-title[data-v-9dd82c62] .el-icon{color:var(--color-gray-500);font-size:var(--font-size-md)}.options-horizontal[data-v-9dd82c62]{align-items:center;gap:var(--spacing-2xl);flex-wrap:wrap;display:flex}.options-horizontal[data-v-9dd82c62] .el-checkbox{white-space:nowrap;margin:0}.options-horizontal[data-v-9dd82c62] .el-checkbox .el-checkbox__label{color:var(--color-text-title);font-weight:500}.default-message-hint[data-v-9dd82c62]{justify-content:space-between;align-items:center;gap:var(--spacing-sm);margin-top:var(--spacing-xs);font-size:var(--font-size-sm);color:var(--text-secondary);display:flex}.default-message-hint .hint-content[data-v-9dd82c62]{align-items:center;gap:var(--spacing-xs);flex:1;min-width:0;display:flex}.default-message-hint .hint-content span[data-v-9dd82c62]{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.default-message-hint .el-icon[data-v-9dd82c62]{font-size:var(--font-size-md);color:var(--color-info);flex-shrink:0}.default-message-hint .copy-btn[data-v-9dd82c62]{flex-shrink:0;height:auto;padding:0}[data-v-9dd82c62] .merge-dialog .common-dialog__footer .el-button{border-radius:var(--radius-lg);font-weight:500;transition:all .3s}[data-v-9dd82c62] .merge-dialog .common-dialog__footer .el-button:hover{transform:scale(1.02)}[data-v-9dd82c62] .merge-dialog .common-dialog__footer .el-button:active{transform:scale(.98)}[data-v-9dd82c62] .merge-dialog .common-dialog__footer .el-button:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}.stash-changes-button[data-v-bcdf10c3]{display:inline-block}.stash-dialog-content[data-v-bcdf10c3]{gap:var(--spacing-base);flex-direction:column;display:flex}.stash-info-card[data-v-bcdf10c3]{align-items:flex-start;gap:var(--spacing-base);padding:var(--spacing-base);border-radius:var(--radius-xl);background:linear-gradient(135deg,#f0f9ff 0%,#e0f2fe 100%);border:1px solid #bae6fd;display:flex;position:relative;overflow:hidden}.stash-info-card[data-v-bcdf10c3]:before{content:"";background:linear-gradient(90deg,#0ea5e9 0%,#0284c7 100%);height:3px;position:absolute;top:0;left:0;right:0}.stash-info-card .info-icon[data-v-bcdf10c3]{color:#fff;width:40px;height:40px;font-size:var(--font-size-lg);box-shadow:var(--shadow-md);background:linear-gradient(135deg,#0ea5e9 0%,#0284c7 100%);border-radius:10px;flex-shrink:0;justify-content:center;align-items:center;display:flex}.stash-info-card .info-content[data-v-bcdf10c3]{flex:1}.stash-info-card .info-content h4[data-v-bcdf10c3]{font-size:var(--font-size-md);color:#0c4a6e;margin:0 0 6px;font-weight:600;line-height:1.2}.stash-info-card .info-content p[data-v-bcdf10c3]{color:#075985;margin:0;line-height:1.4}.stash-form[data-v-bcdf10c3]{gap:var(--spacing-base);flex-direction:column;display:flex}.stash-form[data-v-bcdf10c3] .el-form-item{margin-bottom:0}.stash-form[data-v-bcdf10c3] .el-form-item__label{color:var(--color-text-title);margin-bottom:var(--spacing-base);font-weight:600}.stash-form[data-v-bcdf10c3] .el-textarea .el-textarea__inner{border-radius:var(--radius-lg);border:2px solid var(--color-gray-200);line-height:1.5;transition:all .3s}.stash-form[data-v-bcdf10c3] .el-textarea .el-textarea__inner:hover{border-color:var(--color-gray-300)}.stash-form[data-v-bcdf10c3] .el-textarea .el-textarea__inner:focus-visible{border-color:#3b82f6;box-shadow:0 0 0 3px #3b82f61a}.stash-options[data-v-bcdf10c3]{padding:var(--spacing-base);border-radius:var(--radius-xl)}.options-title[data-v-bcdf10c3]{align-items:center;gap:var(--spacing-base);margin:0 0 var(--spacing-base) 0;color:var(--color-text-title);font-weight:600;display:flex}.options-title[data-v-bcdf10c3] .el-icon{color:var(--color-gray-500);font-size:var(--font-size-md)}.option-item[data-v-bcdf10c3]{margin-bottom:var(--spacing-base)}.option-item[data-v-bcdf10c3]:last-child{margin-bottom:0}.option-item[data-v-bcdf10c3] .el-checkbox{margin-bottom:var(--spacing-base)}.option-label[data-v-bcdf10c3]{color:var(--color-text-title);font-weight:500}.option-desc[data-v-bcdf10c3]{font-size:var(--font-size-sm);color:var(--color-gray-500);padding-left:var(--spacing-2xl);margin:0;line-height:1.4}.option-desc.disabled[data-v-bcdf10c3]{color:var(--color-gray-400)}.stash-preview[data-v-bcdf10c3]{padding:var(--spacing-base);border-radius:var(--radius-xl);background:linear-gradient(135deg,#fefce8 0%,#fef3c7 100%);border:1px solid #fbbf24;position:relative}.stash-preview[data-v-bcdf10c3]:before{content:"";background:linear-gradient(90deg, var(--git-status-modified) 0%, #d97706 100%);border-radius:12px 12px 0 0;height:3px;position:absolute;top:0;left:0;right:0}.preview-title[data-v-bcdf10c3]{align-items:center;gap:var(--spacing-base);color:#92400e;font-weight:600;display:flex}.preview-title[data-v-bcdf10c3] .el-icon{color:#d97706;font-size:var(--font-size-md)}.file-count-info[data-v-bcdf10c3]{gap:var(--spacing-base);flex-wrap:wrap;display:flex}.file-count-info[data-v-bcdf10c3] .el-tag{border-radius:var(--radius-md);padding:var(--spacing-sm) var(--spacing-base)}[data-v-bcdf10c3] .stash-dialog .common-dialog__footer .el-button{border-radius:var(--radius-lg);font-weight:500;transition:all .3s}[data-v-bcdf10c3] .stash-dialog .common-dialog__footer .el-button:hover{transform:scale(1.02)}[data-v-bcdf10c3] .stash-dialog .common-dialog__footer .el-button:active{transform:scale(.98)}[data-v-bcdf10c3] .stash-dialog .common-dialog__footer .el-button:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}.stash-list-button[data-v-392ff82b]{display:inline-block}.stash-list-content[data-v-392ff82b]{min-height:400px;padding:0}[data-v-392ff82b] .stash-list-dialog .common-dialog__header{background:var(--color-primary);color:#fff;padding:var(--spacing-xl) var(--spacing-2xl);border-radius:8px 8px 0 0;margin:0}[data-v-392ff82b] .stash-list-dialog .common-dialog__title{font-size:var(--font-size-lg);color:#fff;font-weight:600}[data-v-392ff82b] .stash-list-dialog .common-dialog__close{color:#fff;font-size:var(--font-size-lg)}[data-v-392ff82b] .stash-list-dialog .common-dialog__close:hover{color:var(--border-card)}.stash-header[data-v-392ff82b]{margin-bottom:var(--spacing-base);padding:var(--spacing-base);background:linear-gradient(135deg, var(--color-white) 0%, #f8f9fa 100%);border-radius:var(--radius-lg);border:1px solid var(--border-component);box-shadow:var(--shadow-md);justify-content:space-between;align-items:center;display:flex}.stash-stats[data-v-392ff82b]{align-items:center;gap:var(--spacing-base);display:flex}.stat-item[data-v-392ff82b]{align-items:center;gap:var(--spacing-base);padding:var(--spacing-xs) var(--spacing-base);background:var(--color-primary);border-radius:var(--radius-lg);color:#fff;display:flex}.stat-icon[data-v-392ff82b]{font-size:var(--font-size-md)}.stat-number[data-v-392ff82b]{font-size:var(--font-size-lg);font-weight:600}.stat-label[data-v-392ff82b]{opacity:.9}.stash-actions-header[data-v-392ff82b]{gap:var(--spacing-base);display:flex}.clear-all-btn[data-v-392ff82b]{border-radius:var(--radius-lg);padding:var(--spacing-xs) var(--spacing-base);font-weight:500;transition:all .3s}.clear-all-btn[data-v-392ff82b]:hover{transform:scale(1.02)}.clear-all-btn[data-v-392ff82b]:active{transform:scale(.98)}.clear-all-btn[data-v-392ff82b]:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}.stash-list-container[data-v-392ff82b]{min-height:300px}.stash-cards[data-v-392ff82b]{gap:var(--spacing-base);flex-direction:column;display:flex}.stash-card[data-v-392ff82b]{background:var(--bg-container);border-radius:var(--radius-lg);border:1px solid var(--border-component);box-shadow:var(--shadow-md);transition:all .3s;overflow:hidden}.stash-card[data-v-392ff82b]:hover{border-color:var(--color-primary);transform:scale(1.02)}.stash-card[data-v-392ff82b]:active{transform:scale(.98)}.stash-card[data-v-392ff82b]:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}.stash-card-latest[data-v-392ff82b]{border:2px solid var(--color-success);box-shadow:var(--shadow-md)}.stash-card-latest[data-v-392ff82b]:hover{border-color:var(--color-success);box-shadow:var(--shadow-lg)}.stash-card-content[data-v-392ff82b]{padding:var(--spacing-base);align-items:center;gap:var(--spacing-base);display:flex}.stash-info[data-v-392ff82b]{gap:var(--spacing-sm);flex-direction:column;flex:1;min-width:0;display:flex}.stash-main-info[data-v-392ff82b]{align-items:center;gap:var(--spacing-base);flex-wrap:wrap;display:flex}.stash-id-badge[data-v-392ff82b]{align-items:center;gap:var(--spacing-base);flex-shrink:0;display:flex}.badge-icon[data-v-392ff82b]{color:var(--text-tertiary)}.stash-id-text[data-v-392ff82b]{font-family:Consolas,Monaco,Courier New,monospace;font-size:var(--font-size-sm);background:var(--bg-panel);color:var(--text-secondary);padding:3px var(--spacing-base);border-radius:var(--radius-base);letter-spacing:.5px;border:1px solid var(--border-card);font-weight:500}.latest-tag[data-v-392ff82b]{margin-left:var(--spacing-sm)}.stash-description[data-v-392ff82b]{flex:1;min-width:0}.description-text[data-v-392ff82b]{word-break:break-word;text-overflow:ellipsis;white-space:nowrap;margin:0;font-weight:500;line-height:1.4;overflow:hidden}.stash-card-actions[data-v-392ff82b]{flex-shrink:0;align-items:center;gap:6px;display:none}.stash-card:hover .stash-card-actions[data-v-392ff82b]{display:flex}.action-btn[data-v-392ff82b]{border-radius:var(--radius-md);min-width:60px;padding:6px var(--spacing-md);font-weight:500;font-size:var(--font-size-sm);transition:all .3s}.action-btn[data-v-392ff82b]:hover{transform:scale(1.02)}.action-btn[data-v-392ff82b]:active{transform:scale(.98)}.action-btn[data-v-392ff82b]:focus-visible{outline:2px solid var(--color-primary);outline-offset:2px}.view-btn[data-v-392ff82b]:hover,.apply-btn[data-v-392ff82b]:hover,.apply-pop-btn[data-v-392ff82b]:hover,.delete-btn[data-v-392ff82b]:hover{box-shadow:var(--shadow-md)}.stash-content[data-v-392ff82b]{gap:var(--spacing-base);flex-direction:column;height:100%;display:flex}.stash-info-row[data-v-392ff82b]{align-items:center;gap:var(--spacing-base);padding:var(--spacing-base);border-radius:var(--radius-md);border:1px solid var(--border-component);flex-shrink:0;display:flex}.stash-id[data-v-392ff82b],.stash-description[data-v-392ff82b]{align-items:center;gap:var(--spacing-base);display:flex}.info-label[data-v-392ff82b]{font-size:var(--font-size-sm);color:var(--text-secondary);white-space:nowrap;font-weight:500}.stash-id-value[data-v-392ff82b]{font-family:ui-monospace,SFMono-Regular,SF Mono,Monaco,Inconsolata,Roboto Mono,Droid Sans Mono,Consolas,Courier New,monospace;font-size:var(--font-size-sm);background-color:var(--border-component);color:var(--color-text);padding:var(--spacing-xs) 6px;border-radius:var(--radius-sm);border:1px solid var(--border-component)}.stash-description[data-v-392ff82b]{flex:1}.stash-description-value[data-v-392ff82b]{word-break:break-all;font-weight:400}.stash-main-content[data-v-392ff82b]{flex-direction:column;flex:1;min-height:0;display:flex}[data-v-9a8027f9] .git-reset-icon{vertical-align:middle;width:1em;height:1em;margin-right:4px}
|