dsh-plugin-teamflow 0.1.7 → 0.1.9
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/CHANGELOG.md +58 -0
- package/README.en.md +19 -7
- package/README.md +30 -18
- package/lib/client.js +2502 -280
- package/lib/descriptors.mjs +63 -0
- package/lib/host.mjs +3317 -796
- package/lib/store.mjs +55 -6
- package/package.json +19 -4
package/lib/client.js
CHANGED
|
@@ -51,6 +51,15 @@ window.__ModuleLoader__.load({
|
|
|
51
51
|
parameters: [],
|
|
52
52
|
result: strict
|
|
53
53
|
},
|
|
54
|
+
{
|
|
55
|
+
id: "dsh-plugin-teamflow#teamflow/setLocale",
|
|
56
|
+
service: "teamflow",
|
|
57
|
+
namespace: "teamflow",
|
|
58
|
+
method: "setLocale",
|
|
59
|
+
invocation: { kind: "direct" },
|
|
60
|
+
parameters: [p("locale")],
|
|
61
|
+
result: strict
|
|
62
|
+
},
|
|
54
63
|
{
|
|
55
64
|
id: "dsh-plugin-teamflow#teamflow/list",
|
|
56
65
|
service: "teamflow",
|
|
@@ -218,6 +227,60 @@ window.__ModuleLoader__.load({
|
|
|
218
227
|
p("sessionId")
|
|
219
228
|
],
|
|
220
229
|
result: strict
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
id: "dsh-plugin-teamflow#teamflow/products",
|
|
233
|
+
service: "teamflow",
|
|
234
|
+
namespace: "teamflow",
|
|
235
|
+
method: "products",
|
|
236
|
+
invocation: { kind: "direct" },
|
|
237
|
+
parameters: [p("sessionId")],
|
|
238
|
+
result: strict
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: "dsh-plugin-teamflow#teamflow/productView",
|
|
242
|
+
service: "teamflow",
|
|
243
|
+
namespace: "teamflow",
|
|
244
|
+
method: "productView",
|
|
245
|
+
invocation: { kind: "direct" },
|
|
246
|
+
parameters: [p("product")],
|
|
247
|
+
result: strict
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
id: "dsh-plugin-teamflow#teamflow/productRunDetail",
|
|
251
|
+
service: "teamflow",
|
|
252
|
+
namespace: "teamflow",
|
|
253
|
+
method: "productRunDetail",
|
|
254
|
+
invocation: { kind: "direct" },
|
|
255
|
+
parameters: [p("product"), p("runId")],
|
|
256
|
+
result: strict
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
id: "dsh-plugin-teamflow#teamflow/productStageDetail",
|
|
260
|
+
service: "teamflow",
|
|
261
|
+
namespace: "teamflow",
|
|
262
|
+
method: "productStageDetail",
|
|
263
|
+
invocation: { kind: "direct" },
|
|
264
|
+
parameters: [
|
|
265
|
+
p("product"),
|
|
266
|
+
p("runId"),
|
|
267
|
+
p("seq")
|
|
268
|
+
],
|
|
269
|
+
result: strict
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
id: "dsh-plugin-teamflow#teamflow/productItemDetail",
|
|
273
|
+
service: "teamflow",
|
|
274
|
+
namespace: "teamflow",
|
|
275
|
+
method: "productItemDetail",
|
|
276
|
+
invocation: { kind: "direct" },
|
|
277
|
+
parameters: [
|
|
278
|
+
p("product"),
|
|
279
|
+
p("kind"),
|
|
280
|
+
p("id"),
|
|
281
|
+
p("sessionId")
|
|
282
|
+
],
|
|
283
|
+
result: strict
|
|
221
284
|
}
|
|
222
285
|
]);
|
|
223
286
|
/** client 端 $mount 使用的贡献对象。 */
|
|
@@ -226,27 +289,59 @@ window.__ModuleLoader__.load({
|
|
|
226
289
|
descriptors: TEAMFLOW_DESCRIPTORS
|
|
227
290
|
});
|
|
228
291
|
//#endregion
|
|
229
|
-
//#region client/
|
|
292
|
+
//#region client/shared.tsx
|
|
230
293
|
/**
|
|
231
|
-
* dsh-plugin-teamflow — browser half
|
|
294
|
+
* dsh-plugin-teamflow — browser half 共享展示层(主题 token / 状态词表 / 格式化)。
|
|
232
295
|
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
* - 成本中心:每阶段 token + 总计 + 运行时长
|
|
237
|
-
* - 人工介入中心:needs-human 项聚合 + 一键终态
|
|
238
|
-
* - 历史 run 切换
|
|
296
|
+
* 会话内工作台(index.tsx)与全局面板(panel.tsx)共用:**只放无状态纯展示件**,
|
|
297
|
+
* 不放任何 Remote 调用或会话上下文逻辑——两边数据来源不同(sessionId / productKey),
|
|
298
|
+
* 展示语言必须一致。
|
|
239
299
|
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
300
|
+
* 双语(v0.1.9):文案统一走宿主 locale 服务(机制说明见 client/locales.ts)。
|
|
301
|
+
* 组件里能拿到注入的 `t`,但**词表/格式化/折叠件是纯函数**,拿不到 prop,
|
|
302
|
+
* 故由 `apply()` 调 `setTranslator()` 注入模块级翻译函数:`bind()` 每次调用都读当前语言,
|
|
303
|
+
* 且宿主在切语言时会重渲染每个 slot outlet(ui-renderer `useLocaleRevision`),
|
|
304
|
+
* 因此模块级函数不会持有过期语言。
|
|
243
305
|
*/
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
306
|
+
/** 翻译函数(默认恒等:未注入时显示 key 本身,便于发现漏注册)。 */
|
|
307
|
+
let translate = (key) => key;
|
|
308
|
+
/** 当前语言 id 的读取器(默认 en)。 */
|
|
309
|
+
let localeIdOf = () => "en";
|
|
310
|
+
/**
|
|
311
|
+
* 注入翻译函数与语言读取器(apply 时调用一次)。
|
|
312
|
+
* @param fn - `ctx.locale.bind(NS)` 的返回值(调用时读当前语言)。
|
|
313
|
+
* @param idOf - 返回当前语言 id 的函数(如 `() => ctx.locale.getSnapshot().active`)。
|
|
314
|
+
*/
|
|
315
|
+
function setTranslator(fn, idOf) {
|
|
316
|
+
if (typeof fn === "function") translate = fn;
|
|
317
|
+
if (typeof idOf === "function") localeIdOf = idOf;
|
|
318
|
+
}
|
|
319
|
+
/** 翻译(`{name}` 占位符由宿主替换)。 */
|
|
320
|
+
const t = (key, params) => translate(key, params);
|
|
321
|
+
/** 当前语言的 BCP 47 标签(时间格式化用;未知语言回退 en)。 */
|
|
322
|
+
function localeTag() {
|
|
323
|
+
let id = "en";
|
|
324
|
+
try {
|
|
325
|
+
id = String(localeIdOf() || "en");
|
|
326
|
+
} catch (e) {}
|
|
327
|
+
return id === "zh" ? "zh-CN" : id;
|
|
328
|
+
}
|
|
329
|
+
/** 词表查表:命中返回译文,未命中回退原始值(未知状态/角色等)。 */
|
|
330
|
+
function vocab(prefix, raw) {
|
|
331
|
+
const key = `${prefix}.${raw}`;
|
|
332
|
+
const hit = t(key);
|
|
333
|
+
return hit === key ? String(raw === null || raw === void 0 ? "" : raw) : hit;
|
|
334
|
+
}
|
|
335
|
+
const stText = (s) => vocab("status", s);
|
|
336
|
+
const runStatusText = (s) => vocab("runStatus", s);
|
|
337
|
+
const kindTitle = (k) => vocab("kind", k);
|
|
338
|
+
const roleName = (r) => vocab("role", r);
|
|
339
|
+
/** 角色 chip(带图标;未知角色回退「⚙️ <raw>」)。 */
|
|
340
|
+
function roleChip(r) {
|
|
341
|
+
const key = `roleChip.${r}`;
|
|
342
|
+
const hit = t(key);
|
|
343
|
+
return hit === key ? `⚙️ ${String(r)}` : hit;
|
|
344
|
+
}
|
|
250
345
|
const T = {
|
|
251
346
|
bg: "var(--dsw-alias-bg-base)",
|
|
252
347
|
layer1: "var(--dsw-alias-bg-layer-1)",
|
|
@@ -260,30 +355,7 @@ window.__ModuleLoader__.load({
|
|
|
260
355
|
success: "var(--dsw-alias-state-success-primary)",
|
|
261
356
|
warn: "var(--dsw-alias-state-warn-primary)"
|
|
262
357
|
};
|
|
263
|
-
|
|
264
|
-
created: "立项",
|
|
265
|
-
"in-progress": "进行中",
|
|
266
|
-
"pending-acceptance": "待验收",
|
|
267
|
-
accepted: "已验收",
|
|
268
|
-
closed: "已关闭",
|
|
269
|
-
pending: "待办",
|
|
270
|
-
running: "开发中",
|
|
271
|
-
testable: "待测试",
|
|
272
|
-
testing: "测试中",
|
|
273
|
-
rework: "打回",
|
|
274
|
-
"needs-human": "需人工",
|
|
275
|
-
cancelled: "已关闭",
|
|
276
|
-
open: "待认领",
|
|
277
|
-
claimed: "处理中",
|
|
278
|
-
fixed: "已修复待验",
|
|
279
|
-
verified: "已关闭",
|
|
280
|
-
reopened: "重开",
|
|
281
|
-
done: "已完成",
|
|
282
|
-
completed: "已完成",
|
|
283
|
-
failed: "失败",
|
|
284
|
-
interrupted: "已中断",
|
|
285
|
-
superseded: "已取代"
|
|
286
|
-
};
|
|
358
|
+
/** 状态色表(与语言无关的纯视觉映射)。 */
|
|
287
359
|
const STATUS_COLOR = {
|
|
288
360
|
created: T.text2,
|
|
289
361
|
pending: T.text2,
|
|
@@ -308,7 +380,7 @@ window.__ModuleLoader__.load({
|
|
|
308
380
|
interrupted: T.warn,
|
|
309
381
|
superseded: T.text2
|
|
310
382
|
};
|
|
311
|
-
/** 阶段英文键 →
|
|
383
|
+
/** 阶段英文键 → 图标(2026-09-06 英文化:journal.phase 为英文键,展示名统一走词表——换语言即换表)。 */
|
|
312
384
|
const PHASE_ICON = {
|
|
313
385
|
prd: "📋",
|
|
314
386
|
design: "🎨",
|
|
@@ -318,17 +390,6 @@ window.__ModuleLoader__.load({
|
|
|
318
390
|
qa: "🧪",
|
|
319
391
|
acceptance: "✅"
|
|
320
392
|
};
|
|
321
|
-
const PHASE_NAME = {
|
|
322
|
-
prd: "PRD 产品需求",
|
|
323
|
-
design: "UI/UX 设计",
|
|
324
|
-
scaffold: "架构规划",
|
|
325
|
-
tech: "技术方案",
|
|
326
|
-
dev: "开发",
|
|
327
|
-
qa: "QA 测试",
|
|
328
|
-
acceptance: "产品验收"
|
|
329
|
-
};
|
|
330
|
-
const phaseNameOf = (p) => PHASE_NAME[p] || p || "—";
|
|
331
|
-
const phaseIconOf = (p) => PHASE_ICON[p] || "⚙️";
|
|
332
393
|
/** phase 归一:英文键直通;存量中文映射(防御性——新数据全英文)。 */
|
|
333
394
|
const phaseKeyOf = (p) => ({
|
|
334
395
|
"PRD 产品需求": "prd",
|
|
@@ -339,15 +400,21 @@ window.__ModuleLoader__.load({
|
|
|
339
400
|
"QA 测试": "qa",
|
|
340
401
|
"产品验收": "acceptance"
|
|
341
402
|
})[p] || String(p || "");
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
403
|
+
const phaseNameOf = (p) => vocab("phase", phaseKeyOf(p));
|
|
404
|
+
const phaseIconOf = (p) => PHASE_ICON[phaseKeyOf(p)] || "⚙️";
|
|
405
|
+
/**
|
|
406
|
+
* 阶段的展示名(双语安全的取法)。
|
|
407
|
+
*
|
|
408
|
+
* journal 里 `stage.label` 是**持久化中文**(teams.json 配置 + 历史数据),而每个阶段都带
|
|
409
|
+
* 英文 `phase` 键——所以展示时:任务级阶段(dev 子卡,`taskKey` 有值)保留任务名(LLM 数据,
|
|
410
|
+
* 不该翻译),其余阶段用 `phase` 键查当前语言词表。**只影响展示,不动数据**:
|
|
411
|
+
* `__taskKey`/`taskKeyOf` 的任务聚合身份仍走 label 清理(聚合语义不能随语言变)。
|
|
412
|
+
*/
|
|
413
|
+
function stageLabelOf(s) {
|
|
414
|
+
const raw = String(s && s.label || "");
|
|
415
|
+
if (s && s.taskKey) return raw || String(s.taskKey);
|
|
416
|
+
return (s && s.phase ? phaseNameOf(s.phase) : "") || raw;
|
|
417
|
+
}
|
|
351
418
|
const COLUMNS = {
|
|
352
419
|
req: [
|
|
353
420
|
"created",
|
|
@@ -377,11 +444,6 @@ window.__ModuleLoader__.load({
|
|
|
377
444
|
"needs-human"
|
|
378
445
|
]
|
|
379
446
|
};
|
|
380
|
-
const KIND_TITLE = {
|
|
381
|
-
req: "需求",
|
|
382
|
-
task: "任务",
|
|
383
|
-
bug: "缺陷"
|
|
384
|
-
};
|
|
385
447
|
const h = react.default.createElement;
|
|
386
448
|
const MONO = "ui-monospace, SFMono-Regular, Consolas, \"Cascadia Mono\", monospace";
|
|
387
449
|
const SANS = "-apple-system, BlinkMacSystemFont, \"Segoe UI\", \"PingFang SC\", \"Microsoft YaHei\", sans-serif";
|
|
@@ -391,7 +453,7 @@ window.__ModuleLoader__.load({
|
|
|
391
453
|
gap: 6,
|
|
392
454
|
flexWrap: "wrap"
|
|
393
455
|
};
|
|
394
|
-
/** 状态徽章:半透明底 + 状态色文字 + 圆角 pill
|
|
456
|
+
/** 状态徽章:半透明底 + 状态色文字 + 圆角 pill(超宽可省略:窄列/窄卡里不顶破外层)。 */
|
|
395
457
|
const chip = (text, color, opts = {}) => h("span", { style: {
|
|
396
458
|
display: "inline-flex",
|
|
397
459
|
alignItems: "center",
|
|
@@ -404,21 +466,25 @@ window.__ModuleLoader__.load({
|
|
|
404
466
|
background: `color-mix(in srgb, ${color} 14%, transparent)`,
|
|
405
467
|
color,
|
|
406
468
|
whiteSpace: "nowrap",
|
|
469
|
+
maxWidth: "100%",
|
|
470
|
+
overflow: "hidden",
|
|
471
|
+
textOverflow: "ellipsis",
|
|
407
472
|
...opts.style || {}
|
|
408
473
|
} }, opts.dot ? h("span", { style: {
|
|
409
474
|
width: 5,
|
|
410
475
|
height: 5,
|
|
411
476
|
borderRadius: 999,
|
|
412
477
|
background: color,
|
|
413
|
-
display: "inline-block"
|
|
478
|
+
display: "inline-block",
|
|
479
|
+
flex: "0 0 auto"
|
|
414
480
|
} }) : null, text);
|
|
415
481
|
/** 可折叠长文本:默认只显示前几行预览,「展开全文」/「收起」双向切换(数据不动,纯展示层——summary/需求原文等富文本不再铺满抽屉)。 */
|
|
416
482
|
function FoldableText({ text, charLimit = 280, lineLimit = 5, style }) {
|
|
417
483
|
const [open, setOpen] = react.default.useState(false);
|
|
418
484
|
if (!text) return null;
|
|
419
|
-
const
|
|
420
|
-
const lines =
|
|
421
|
-
const compact = lines.length <= lineLimit &&
|
|
485
|
+
const s = String(text);
|
|
486
|
+
const lines = s.split("\n");
|
|
487
|
+
const compact = lines.length <= lineLimit && s.length <= charLimit;
|
|
422
488
|
const body = (txt) => h("div", { style: {
|
|
423
489
|
fontSize: 11.5,
|
|
424
490
|
color: T.text,
|
|
@@ -427,10 +493,10 @@ window.__ModuleLoader__.load({
|
|
|
427
493
|
wordBreak: "break-word",
|
|
428
494
|
...style || {}
|
|
429
495
|
} }, txt);
|
|
430
|
-
if (compact) return body(
|
|
431
|
-
if (open) return h("div", null, body(
|
|
496
|
+
if (compact) return body(s);
|
|
497
|
+
if (open) return h("div", null, body(s), h("button", {
|
|
432
498
|
onClick: () => setOpen(false),
|
|
433
|
-
title: "
|
|
499
|
+
title: t("common.collapseFull"),
|
|
434
500
|
style: {
|
|
435
501
|
marginTop: 3,
|
|
436
502
|
font: "inherit",
|
|
@@ -442,12 +508,12 @@ window.__ModuleLoader__.load({
|
|
|
442
508
|
padding: 0,
|
|
443
509
|
cursor: "pointer"
|
|
444
510
|
}
|
|
445
|
-
}, "
|
|
446
|
-
const pre = lines.length > lineLimit ? lines.slice(0, lineLimit).join("\n") :
|
|
447
|
-
const more = lines.length > lineLimit ?
|
|
511
|
+
}, t("common.collapse")));
|
|
512
|
+
const pre = lines.length > lineLimit ? lines.slice(0, lineLimit).join("\n") : s.slice(0, charLimit);
|
|
513
|
+
const more = lines.length > lineLimit ? t("common.moreLines", { n: lines.length - lineLimit }) : "…";
|
|
448
514
|
return h("div", null, body(pre), h("button", {
|
|
449
515
|
onClick: () => setOpen(true),
|
|
450
|
-
title: "
|
|
516
|
+
title: t("common.clickToExpand"),
|
|
451
517
|
style: {
|
|
452
518
|
marginTop: 3,
|
|
453
519
|
font: "inherit",
|
|
@@ -459,97 +525,2076 @@ window.__ModuleLoader__.load({
|
|
|
459
525
|
padding: 0,
|
|
460
526
|
cursor: "pointer"
|
|
461
527
|
}
|
|
462
|
-
},
|
|
463
|
-
}
|
|
464
|
-
function
|
|
465
|
-
if (!
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
if (
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
528
|
+
}, `${t("common.expandFull")}${more}`));
|
|
529
|
+
}
|
|
530
|
+
function fmtTime(tm) {
|
|
531
|
+
if (!tm) return "—";
|
|
532
|
+
const d = new Date(tm);
|
|
533
|
+
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}:${String(d.getSeconds()).padStart(2, "0")}`;
|
|
534
|
+
}
|
|
535
|
+
function fmtDur(a, b) {
|
|
536
|
+
if (!a) return "";
|
|
537
|
+
const ms = (b || Date.now()) - a;
|
|
538
|
+
if (ms < 0) return "";
|
|
539
|
+
const s = Math.floor(ms / 1e3);
|
|
540
|
+
if (s < 60) return `${s}s`;
|
|
541
|
+
return `${Math.floor(s / 60)}m${s % 60}s`;
|
|
542
|
+
}
|
|
543
|
+
function fmtTokens(n) {
|
|
544
|
+
if (n === null || n === void 0) return "";
|
|
545
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(2)}M`;
|
|
546
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}k`;
|
|
547
|
+
return String(n);
|
|
548
|
+
}
|
|
549
|
+
/** 官方口径计费合计(billed input + output)。 */
|
|
550
|
+
function totalTokens(u) {
|
|
551
|
+
return (u.input || 0) + (u.cacheRead || 0) + (u.cacheWrite || 0) + (u.output || 0);
|
|
552
|
+
}
|
|
553
|
+
/** 官方口径工具函数(与 DeepSeek usage 账单对齐,零额外概念):
|
|
554
|
+
* - u.input : 输入(缓存未命中)
|
|
555
|
+
* - u.cacheRead : 输入(缓存命中)
|
|
556
|
+
* - u.cacheWrite : 输入写入缓存
|
|
557
|
+
* - u.output : 输出
|
|
558
|
+
* billed input = input + cacheRead + cacheWrite;命中率 = cacheRead / (input + cacheRead)。
|
|
559
|
+
*/
|
|
560
|
+
function hitRate(u) {
|
|
561
|
+
const total = (u.input || 0) + (u.cacheRead || 0);
|
|
562
|
+
return total > 0 ? Math.round((u.cacheRead || 0) / total * 100) : null;
|
|
563
|
+
}
|
|
564
|
+
function usageDetail(s) {
|
|
565
|
+
const k = (n) => n === null || n === void 0 ? "—" : fmtTokens(n);
|
|
566
|
+
if (s.usage) {
|
|
567
|
+
const u = s.usage;
|
|
568
|
+
const hit = hitRate(u);
|
|
569
|
+
return t("token.usageLine", {
|
|
570
|
+
input: k(u.input),
|
|
571
|
+
cacheRead: k(u.cacheRead),
|
|
572
|
+
cacheWrite: k(u.cacheWrite),
|
|
573
|
+
output: k(u.output),
|
|
574
|
+
calls: u.calls
|
|
575
|
+
}) + (hit !== null ? t("token.usageHit", { hit }) : "");
|
|
576
|
+
}
|
|
577
|
+
return t("token.usageMissing");
|
|
578
|
+
}
|
|
579
|
+
/** 节点卡主 token 行:官方口径 —— 输入(未命中)/输入(命中)/输出 + 缓存命中率。 */
|
|
580
|
+
function stageUsageLine(s) {
|
|
581
|
+
const u = s && s.usage;
|
|
582
|
+
if (u && (u.input || u.cacheRead || u.cacheWrite || u.output)) {
|
|
583
|
+
const hit = hitRate(u);
|
|
584
|
+
return `⇅${fmtTokens(u.input)} ⇅${fmtTokens(u.cacheRead)} ⬆${fmtTokens(u.output)}${hit !== null ? ` ·${hit}%` : ""}`;
|
|
585
|
+
}
|
|
586
|
+
return null;
|
|
587
|
+
}
|
|
588
|
+
const roleUsage = (u) => {
|
|
589
|
+
if (!u) return "";
|
|
590
|
+
const hit = hitRate(u);
|
|
591
|
+
return `⇅${fmtTokens(u.input || 0)}/${fmtTokens(u.cacheRead || 0)}·⬆${fmtTokens(u.output || 0)}${hit !== null ? `·${hit}%` : ""}`;
|
|
592
|
+
};
|
|
593
|
+
/** 任务卡按角色累计的真实 token 摘要(官方口径:未命中/命中输入 + 输出 + 命中率)。 */
|
|
594
|
+
function byRoleLine(task) {
|
|
595
|
+
const roles = task && task.byRole || {};
|
|
596
|
+
return Object.keys(roles).filter((k) => roles[k] && roles[k].input + roles[k].output + roles[k].cacheRead + roles[k].cacheWrite > 0).map((k) => `${roleName(k)} ${roleUsage(roles[k])}`).join(" · ");
|
|
597
|
+
}
|
|
598
|
+
/** 多阶段 usage 汇总(官方口径)。 */
|
|
599
|
+
function totalUsage(stages) {
|
|
600
|
+
const sum = {
|
|
601
|
+
input: 0,
|
|
602
|
+
cacheRead: 0,
|
|
603
|
+
cacheWrite: 0,
|
|
604
|
+
output: 0,
|
|
605
|
+
calls: 0
|
|
606
|
+
};
|
|
607
|
+
for (const s of stages || []) {
|
|
608
|
+
const u = s && s.usage;
|
|
609
|
+
if (!u) continue;
|
|
610
|
+
sum.input += u.input || 0;
|
|
611
|
+
sum.cacheRead += u.cacheRead || 0;
|
|
612
|
+
sum.cacheWrite += u.cacheWrite || 0;
|
|
613
|
+
sum.output += u.output || 0;
|
|
614
|
+
sum.calls += u.calls || 0;
|
|
615
|
+
}
|
|
616
|
+
return sum;
|
|
617
|
+
}
|
|
618
|
+
const stColor = (s) => STATUS_COLOR[s] || T.text2;
|
|
619
|
+
//#endregion
|
|
620
|
+
//#region client/locales.ts
|
|
621
|
+
/**
|
|
622
|
+
* dsh-plugin-teamflow — 客户端文案词典(zh / en)。
|
|
623
|
+
*
|
|
624
|
+
* 机制:走宿主 `ctx.locale`(@deepseek-ai/dsh-client-locale),不自建 i18n:
|
|
625
|
+
* - `apply()` 里 `ctx.locale.register(NS, { zh, en })` 注册两个词典;
|
|
626
|
+
* - `ctx.locale.bind(NS)` 取翻译函数(调用时读当前语言,故可常驻模块变量);
|
|
627
|
+
* - slot 注册项声明 `locale: NS`,切语言时宿主重渲染每个 outlet
|
|
628
|
+
* (ui-renderer `useLocaleRevision`),面板/tab 名称用 thunk `() => t(...)`。
|
|
629
|
+
*
|
|
630
|
+
* 约定:
|
|
631
|
+
* - key 扁平点号命名,按区域前缀分组(common/status/phase/kind/role/stage/…);
|
|
632
|
+
* - 占位符用 `{name}`(宿主 `translate` 的替换语法);
|
|
633
|
+
* - **zh 与 en 的 key 集合必须完全一致**(en 是兜底语言,缺 key 会直接显示 key 本身;
|
|
634
|
+
* 由 test/smoke.js 断言守门);
|
|
635
|
+
* - 数据面文案(LLM 产出的标题/摘要、teams.json 的团队名、journal 里既有中文 label)不在此表,
|
|
636
|
+
* 属 host/产物语言层,见 AGENTS §5「客户端面」锚点。
|
|
637
|
+
*/
|
|
638
|
+
/** 词典命名空间(宿主 LocaleRuntime 的单占位命名空间)。 */
|
|
639
|
+
const NS = "teamflow";
|
|
640
|
+
/** 中文词典(key 为单一事实来源,en 必须与之同形)。 */
|
|
641
|
+
const zh = {
|
|
642
|
+
"common.close": "关闭",
|
|
643
|
+
"common.loading": "加载中…",
|
|
644
|
+
"common.retry": "重试",
|
|
645
|
+
"common.collapse": "收起",
|
|
646
|
+
"common.collapseFull": "收起全文",
|
|
647
|
+
"common.clickToExpand": "点击查看全文",
|
|
648
|
+
"common.expandFull": "展开全文",
|
|
649
|
+
"common.moreLines": "… +{n} 行",
|
|
650
|
+
"common.none": "无",
|
|
651
|
+
"common.noneDash": "(暂无)",
|
|
652
|
+
"common.noSummary": "(无摘要)",
|
|
653
|
+
"common.failed": "失败",
|
|
654
|
+
"common.running": "进行中",
|
|
655
|
+
"common.needsHuman": "需人工介入",
|
|
656
|
+
"common.unknownError": "未知错误",
|
|
657
|
+
"common.gotIt": "知道了",
|
|
658
|
+
"common.remoteNotReady": "remote 未就绪",
|
|
659
|
+
"common.remoteCallFailed": "{what} 调用失败:{detail}",
|
|
660
|
+
"common.remoteEmptyResult": "{what} 返回空结果(ok=true 但无 value;原始信封={raw})",
|
|
661
|
+
"common.calls": "{n} 次",
|
|
662
|
+
"common.callsCount": "{n} 次调用",
|
|
663
|
+
"common.noUsage": "无 token 数据",
|
|
664
|
+
"status.created": "立项",
|
|
665
|
+
"status.in-progress": "进行中",
|
|
666
|
+
"status.pending-acceptance": "待验收",
|
|
667
|
+
"status.accepted": "已验收",
|
|
668
|
+
"status.closed": "已关闭",
|
|
669
|
+
"status.pending": "待办",
|
|
670
|
+
"status.running": "开发中",
|
|
671
|
+
"status.testable": "待测试",
|
|
672
|
+
"status.testing": "测试中",
|
|
673
|
+
"status.rework": "打回",
|
|
674
|
+
"status.needs-human": "需人工",
|
|
675
|
+
"status.cancelled": "已关闭",
|
|
676
|
+
"status.open": "待认领",
|
|
677
|
+
"status.claimed": "处理中",
|
|
678
|
+
"status.fixed": "已修复待验",
|
|
679
|
+
"status.verified": "已关闭",
|
|
680
|
+
"status.reopened": "重开",
|
|
681
|
+
"status.done": "已完成",
|
|
682
|
+
"status.completed": "已完成",
|
|
683
|
+
"status.failed": "失败",
|
|
684
|
+
"status.interrupted": "已中断",
|
|
685
|
+
"status.superseded": "已取代",
|
|
686
|
+
"runStatus.pending": "等待中",
|
|
687
|
+
"runStatus.running": "进行中",
|
|
688
|
+
"runStatus.completed": "已完成",
|
|
689
|
+
"runStatus.failed": "失败",
|
|
690
|
+
"runStatus.cancelled": "已取消",
|
|
691
|
+
"runStatus.interrupted": "已中断",
|
|
692
|
+
"runStatus.superseded": "已取代",
|
|
693
|
+
"phase.prd": "PRD 产品需求",
|
|
694
|
+
"phase.design": "UI/UX 设计",
|
|
695
|
+
"phase.scaffold": "架构规划",
|
|
696
|
+
"phase.tech": "技术方案",
|
|
697
|
+
"phase.dev": "开发",
|
|
698
|
+
"phase.qa": "QA 测试",
|
|
699
|
+
"phase.acceptance": "产品验收",
|
|
700
|
+
"kind.req": "需求",
|
|
701
|
+
"kind.task": "任务",
|
|
702
|
+
"kind.bug": "缺陷",
|
|
703
|
+
"role.pm": "产品",
|
|
704
|
+
"role.design": "设计",
|
|
705
|
+
"role.arch": "架构",
|
|
706
|
+
"role.tech": "方案",
|
|
707
|
+
"role.dev": "开发",
|
|
708
|
+
"role.qa": "测试",
|
|
709
|
+
"role.acceptance": "验收",
|
|
710
|
+
"role.other": "其他",
|
|
711
|
+
"roleChip.pm": "📌 产品",
|
|
712
|
+
"roleChip.design": "🎨 设计",
|
|
713
|
+
"roleChip.arch": "🏗 架构",
|
|
714
|
+
"roleChip.tech": "📐 方案",
|
|
715
|
+
"roleChip.dev": "👨💻 开发",
|
|
716
|
+
"roleChip.qa": "🧪 QA",
|
|
717
|
+
"roleChip.acceptance": "✅ 验收",
|
|
718
|
+
"token.officialTitle": "TOKEN · 官方口径",
|
|
719
|
+
"token.usageLine": "输入(未命中) {input} / 输入(命中) {cacheRead} / 写缓存 {cacheWrite} / 输出 {output} · {calls} 次调用",
|
|
720
|
+
"token.usageHit": " · 缓存命中 {hit}%",
|
|
721
|
+
"token.usageMissing": "无 usage 明细",
|
|
722
|
+
"token.inputMiss": "输入(未命中) {v}",
|
|
723
|
+
"token.inputHit": "输入(命中) {v}",
|
|
724
|
+
"token.cacheWrite": "写缓存 {v}",
|
|
725
|
+
"token.output": "输出 {v}",
|
|
726
|
+
"token.total": "合计 {v}",
|
|
727
|
+
"token.allStagesTip": "输入(未命中)/输入(命中)/输出 全部阶段合计",
|
|
728
|
+
"token.stageLine": "阶段 token:{v}",
|
|
729
|
+
"stage.cardTip": "{label} —— 点击查看阶段详情",
|
|
730
|
+
"stage.retryTip": "重试 {n} 次(共 {m} 次尝试)",
|
|
731
|
+
"stage.detailTitle": "阶段详情",
|
|
732
|
+
"stage.summaryFallback": "(该 run 未保存完整正文,展示摘要)\n\n{summary}",
|
|
733
|
+
"stage.loadFailed": "⚠ 加载失败:{err}",
|
|
734
|
+
"stage.noOutput": "(无产物正文)",
|
|
735
|
+
"stage.attemptHistory": "↻ 尝试历史({n} 次 · 点击查看该次详情)",
|
|
736
|
+
"stage.attemptDone": "✅ 成功",
|
|
737
|
+
"stage.attemptFailed": "❌ {outcome}",
|
|
738
|
+
"stage.attemptRunning": "⏳ 进行中",
|
|
739
|
+
"stage.childJumpBtn": "🎬 跳转子代理会话",
|
|
740
|
+
"stage.childJumpTip": "跳转到该阶段子代理会话(完整推理与工具调用轨迹);跳转后请切换「对话」tab 查看",
|
|
741
|
+
"stage.childNoneTip": "该阶段无可用子代理会话",
|
|
742
|
+
"stage.childCrossSessionTip": "该子代理由会话 {sid} 发起,跨会话跳转暂不支持——请打开其发起会话的团队工作台查看",
|
|
743
|
+
"stage.childCrossSessionNote": "跨会话暂不支持:该子代理由会话 {sid} 发起。如需查看轨迹,请打开其发起会话的团队工作台。",
|
|
744
|
+
"stage.childJumpNote": "跳转成功后,请切「对话」tab 查看该子代理的完整会话轨迹",
|
|
745
|
+
"stage.evidenceTitle": "🔬 验证证据",
|
|
746
|
+
"stage.evidenceMissing": "(缺失——契约未兑现,host 已记警告;可与该 run 的命令日志对照:run 期间在 logs/teamflow/<runId>/,结束后由 host 归档到 $DSH_HOME/teamflow/<workspace>/logs/<runId>/)",
|
|
747
|
+
"stage.evidenceMissingShort": "(缺失——契约未兑现,host 已记警告)",
|
|
748
|
+
"stage.artifactsTitle": "📄 阶段性产物",
|
|
749
|
+
"pipeline.empty": "暂无运行中的流水线——让模型调用 teamflow_start,或在上方输入需求",
|
|
750
|
+
"pipeline.noNodes": "流水线还没有开始执行节点",
|
|
751
|
+
"pipeline.zoomOut": "缩小",
|
|
752
|
+
"pipeline.zoomIn": "放大",
|
|
753
|
+
"pipeline.fitCanvas": "适应画布",
|
|
754
|
+
"pipeline.canvasHint": "✥ 拖动画布 · 滚轮缩放",
|
|
755
|
+
"workbench.title": "团队工作台",
|
|
756
|
+
"workbench.running": "流水线运行中",
|
|
757
|
+
"workbench.idle": "空闲",
|
|
758
|
+
"workbench.refresh": "🔄 刷新",
|
|
759
|
+
"workbench.resumeTip": "断点续跑 {id}\n当前状态:{status};跳过已完成阶段,从第一个未完成阶段重跑",
|
|
760
|
+
"workbench.resuming": "续跑中…",
|
|
761
|
+
"workbench.resumeBtn": "↻ 从断点重跑 #{id}",
|
|
762
|
+
"workbench.loadFailed": "⚠ {err}(确认已安装 dsh-plugin-teamflow 且 web 已重启)",
|
|
763
|
+
"workbench.needsHumanBanner": "⚠ {n} 项需人工介入",
|
|
764
|
+
"workbench.handle": "处理 {id}",
|
|
765
|
+
"workbench.manualReason": "人工处理",
|
|
766
|
+
"workbench.tabPipeline": "🔄 流水线",
|
|
767
|
+
"workbench.tabBoard": "📋 Backlog 看板",
|
|
768
|
+
"workbench.workspaceTip": "当前工作区(workspace 级隔离):{path}",
|
|
769
|
+
"workbench.noWorkspace": "未连接工作区",
|
|
770
|
+
"workbench.history": "历史",
|
|
771
|
+
"workbench.openRightBarTip": "在右侧栏打开该 run 详情(与任务夹产物并排看)",
|
|
772
|
+
"workbench.openRightBarBtn": "⇥ 右栏打开",
|
|
773
|
+
"board.empty": "backlog 为空(还没有流水线运行过)",
|
|
774
|
+
"board.dragReason": "看板拖拽流转",
|
|
775
|
+
"board.cardTip": "{id} · {status}{summary}(点击查看详情)",
|
|
776
|
+
"board.assignDevTip": "dev 分配\n{who}",
|
|
777
|
+
"board.assignQaTip": "qa 分配\n{who}",
|
|
778
|
+
"board.acceptTip": "验收/汇报人\n{who}",
|
|
779
|
+
"board.subtaskCount": "📦 {n} 子卡",
|
|
780
|
+
"board.retries": "重试 {n}",
|
|
781
|
+
"item.overview": "概览",
|
|
782
|
+
"item.acceptRow": "✅ 验收",
|
|
783
|
+
"item.retryRow": "↻ 重试",
|
|
784
|
+
"item.humanRow": "⚠ 人工介入",
|
|
785
|
+
"item.updatedAt": "更新于",
|
|
786
|
+
"item.runSection": "运行",
|
|
787
|
+
"item.runBtn": "▶ 流水线",
|
|
788
|
+
"item.jumpRunTip": "跳转到该需求的流水线视图 #{id}",
|
|
789
|
+
"item.requirement": "需求原文",
|
|
790
|
+
"item.noRequirement": "(无原文)",
|
|
791
|
+
"item.runDocs": "任务夹",
|
|
792
|
+
"item.previewTip": "在右侧栏预览 {path}",
|
|
793
|
+
"item.subtasks": "关联子卡({n})",
|
|
794
|
+
"item.bugs": "关联缺陷({n})",
|
|
795
|
+
"item.timeline": "流转时间线({n})",
|
|
796
|
+
"team.currentTip": "当前团队:{name}(点击切换)",
|
|
797
|
+
"team.pick": "选择团队",
|
|
798
|
+
"team.label": "团队",
|
|
799
|
+
"team.none": "无团队(直接对话)",
|
|
800
|
+
"team.noneNote": "不走 teamflow,模型直接工作",
|
|
801
|
+
"panel.title": "🏭 团队工作台",
|
|
802
|
+
"panel.subtitle": "全局面板 · 按产品线",
|
|
803
|
+
"panel.currentSession": "当前会话 {sid}",
|
|
804
|
+
"panel.noSession": "无当前会话",
|
|
805
|
+
"panel.reload": "刷新数据",
|
|
806
|
+
"panel.backToChat": "回到对话",
|
|
807
|
+
"panel.backToChatTip": "回到对话(再点侧边栏图标即可切回本面板)",
|
|
808
|
+
"panel.railTitle": "产品线 · {n}",
|
|
809
|
+
"panel.rescanTip": "重新扫描 $DSH_HOME/teamflow",
|
|
810
|
+
"panel.refreshing": "刷新中",
|
|
811
|
+
"panel.refresh": "刷新",
|
|
812
|
+
"panel.noProducts": "还没有产品线。在某个工作区跑过一次流水线后,这里会出现对应产品线($DSH_HOME/teamflow/<key>)。",
|
|
813
|
+
"panel.activeRuns": "运行 {n}",
|
|
814
|
+
"panel.updated": "更新 {time}",
|
|
815
|
+
"panel.reading": "读取中…",
|
|
816
|
+
"panel.verdict": "验收 {v}",
|
|
817
|
+
"panel.pickProduct": "选择左侧产品线查看 backlog 与 run(首次进入默认选最近更新的产品线)。",
|
|
818
|
+
"panel.loadingView": "读取产品线数据中…",
|
|
819
|
+
"panel.runChip": "run {n}",
|
|
820
|
+
"panel.activeChip": "活跃 {n}",
|
|
821
|
+
"panel.verdictChip": "验收 {v}",
|
|
822
|
+
"panel.tabRuns": "🚀 流水线 run · {n}",
|
|
823
|
+
"panel.tabBacklog": "📋 Backlog · {n}",
|
|
824
|
+
"panel.pinnedActive": "已置顶进行中 {n}",
|
|
825
|
+
"panel.showRecent": "只看最近 {n} 条",
|
|
826
|
+
"panel.showAll": "展开全部 {n} 条",
|
|
827
|
+
"panel.clearRunFilterTip": "清除 run 状态筛选",
|
|
828
|
+
"panel.filtered": "筛选中 {sel} 项 · 显示 {shown}/{total} × 清除",
|
|
829
|
+
"panel.emptyRunFilter": "该筛选下没有 run。",
|
|
830
|
+
"panel.runHint": "点一行看详情浮层;「去会话右栏」= 跳到该 run 的发起会话并在其右侧栏打开(与任务夹产物并排)",
|
|
831
|
+
"panel.boardHint": "点状态徽章可筛选(多选);终态卡片默认收起,筛选时自动显示",
|
|
832
|
+
"panel.runDetail": "run 详情",
|
|
833
|
+
"panel.goOwnerSession": "去发起会话",
|
|
834
|
+
"panel.goOwnerSessionTip": "跳到该 run 的发起会话,并在那个会话的右侧栏打开(与任务夹产物并排看)",
|
|
835
|
+
"panel.remoteProductsUnavailable": "remote.products 不可用(插件未挂载或版本过旧)",
|
|
836
|
+
"panel.hintNoAddress": "{label}:host 未生成可打开的地址(可能缺少会话上下文)",
|
|
837
|
+
"panel.hintRightbarFailed": "右侧栏打开失败(宿主只在对话视图挂载它):{label}——已在本面板内联显示",
|
|
838
|
+
"panel.hintSessionGone": "发起会话 {sid}… 不在会话列表里(可能已被清理)——已在本面板展示详情",
|
|
839
|
+
"panel.hintSwitchedNoRightbar": "已切到发起会话,但右栏没打开({label})——可在该会话里用「⇥ 右栏打开」重试",
|
|
840
|
+
"panel.hintInlineSuffix": "——已在本面板内联显示",
|
|
841
|
+
"runList.empty": "该产品线还没有 run 记录。",
|
|
842
|
+
"runList.running": "进行中",
|
|
843
|
+
"runList.noRequirement": "(无需求描述)",
|
|
844
|
+
"runList.stageProgress": "阶段 {done}/{total}",
|
|
845
|
+
"runList.openRightBar": "去会话右栏",
|
|
846
|
+
"runList.openRightBarTip": "跳到该 run 的发起会话,并在那个会话的右侧栏打开详情(右侧栏是会话级的:挂到无关会话上没有意义)",
|
|
847
|
+
"runList.callsSuffix": " · {n} 次",
|
|
848
|
+
"panelBoard.empty": "backlog 为空(该产品线还没有立项卡片)。",
|
|
849
|
+
"panelBoard.groupCount": "{kind} · {n}",
|
|
850
|
+
"panelBoard.activeCount": "活动 {n}",
|
|
851
|
+
"panelBoard.clearFilterTip": "清除本组筛选",
|
|
852
|
+
"panelBoard.collapseDone": "收起已完成 {n}",
|
|
853
|
+
"panelBoard.expandDone": "已完成 {n} ▸",
|
|
854
|
+
"panelBoard.collapseDoneTip": "收起已完成/已关闭卡片",
|
|
855
|
+
"panelBoard.expandDoneTip": "展开已完成/已关闭卡片",
|
|
856
|
+
"panelBoard.emptyFiltered": "该筛选下没有卡片。",
|
|
857
|
+
"panelBoard.filterChipOn": "点击取消该状态筛选",
|
|
858
|
+
"panelBoard.filterChipOff": "点击只看该状态(可多选)",
|
|
859
|
+
"panelItem.row.req": "需求",
|
|
860
|
+
"panelItem.row.owner": "负责人",
|
|
861
|
+
"panelItem.row.dev": "开发",
|
|
862
|
+
"panelItem.row.qa": "测试",
|
|
863
|
+
"panelItem.row.accept": "验收",
|
|
864
|
+
"panelItem.row.retries": "重试",
|
|
865
|
+
"panelItem.row.runDocs": "任务夹",
|
|
866
|
+
"panelItem.defectSection": "缺陷详情(QA 报告导入)",
|
|
867
|
+
"panelItem.row.defectId": "缺陷编号",
|
|
868
|
+
"panelItem.row.severity": "严重级",
|
|
869
|
+
"panelItem.row.module": "功能模块",
|
|
870
|
+
"panelItem.row.reproduce": "复现步骤",
|
|
871
|
+
"panelItem.row.expected": "期望行为",
|
|
872
|
+
"panelItem.row.actual": "实际行为",
|
|
873
|
+
"panelItem.row.defectAc": "关联验收项",
|
|
874
|
+
"panelItem.row.defectCheck": "检测命令",
|
|
875
|
+
"panelItem.row.defectCriterion": "通过判据",
|
|
876
|
+
"panelItem.noDefectDetail": "(该卡无缺陷描述:登记时 QA 报告只给了三要素,或为旧版本登记的卡;细节见关联 run 的 QA-REPORT.md)",
|
|
877
|
+
"panelItem.spec": "规格",
|
|
878
|
+
"panelItem.summary": "结论摘要",
|
|
879
|
+
"panelItem.artifacts": "任务夹产物 · {n}",
|
|
880
|
+
"panelItem.artifactTip": "{address}\n(跳到产物所属会话后在该会话右侧栏打开)",
|
|
881
|
+
"panelItem.noArtifacts": "该条目没有可预览的任务夹产物(或缺少会话上下文,无法生成文件地址)。",
|
|
882
|
+
"panelItem.subtasks": "子卡 · {n}",
|
|
883
|
+
"panelItem.bugs": "关联缺陷 · {n}",
|
|
884
|
+
"panelItem.events": "流转时间线 · 最近 {n} 条",
|
|
885
|
+
"detail.runMissing": "未找到该 run(可能已被清理,或地址已过期)。",
|
|
886
|
+
"detail.subagents": "子代理 {n}",
|
|
887
|
+
"detail.stages": "阶段 · {n}",
|
|
888
|
+
"detail.stageFailed": "阶段详情读取失败:{err}",
|
|
889
|
+
"detail.stageTitle": "阶段 #{seq} 详情",
|
|
890
|
+
"detail.output": "阶段产出",
|
|
891
|
+
"detail.attempts": "同任务尝试 · {n}",
|
|
892
|
+
"detail.logs": "日志 · 最近 {n} 条",
|
|
893
|
+
"detail.endedRunning": "进行中",
|
|
894
|
+
"detail.subagentChip": "子代理 {id}",
|
|
895
|
+
"tab.resolveFailed": "无法解析 run 地址:{address}",
|
|
896
|
+
"tab.readingAddress": "读取 tab 地址中…",
|
|
897
|
+
"tab.noHook": "宿主 tab 信息钩子不可用(useTabInfo 缺失)",
|
|
898
|
+
"tab.remoteUnavailable": "remote 不可用(插件未挂载或版本过旧)",
|
|
899
|
+
"tab.readingRun": "读取 run 详情中…"
|
|
900
|
+
};
|
|
901
|
+
/** 英文词典(key 集合必须与 zh 完全一致)。 */
|
|
902
|
+
const en = {
|
|
903
|
+
"common.close": "Close",
|
|
904
|
+
"common.loading": "Loading…",
|
|
905
|
+
"common.retry": "Retry",
|
|
906
|
+
"common.collapse": "Collapse",
|
|
907
|
+
"common.collapseFull": "Collapse",
|
|
908
|
+
"common.clickToExpand": "Click to expand",
|
|
909
|
+
"common.expandFull": "Show more",
|
|
910
|
+
"common.moreLines": "… +{n} lines",
|
|
911
|
+
"common.none": "none",
|
|
912
|
+
"common.noneDash": "(none)",
|
|
913
|
+
"common.noSummary": "(no summary)",
|
|
914
|
+
"common.failed": "Failed",
|
|
915
|
+
"common.running": "Running",
|
|
916
|
+
"common.needsHuman": "needs human intervention",
|
|
917
|
+
"common.unknownError": "unknown error",
|
|
918
|
+
"common.gotIt": "Got it",
|
|
919
|
+
"common.remoteNotReady": "remote not ready",
|
|
920
|
+
"common.remoteCallFailed": "{what} call failed: {detail}",
|
|
921
|
+
"common.remoteEmptyResult": "{what} returned an empty result (ok=true but no value; raw envelope={raw})",
|
|
922
|
+
"common.calls": "{n} calls",
|
|
923
|
+
"common.callsCount": "{n} calls",
|
|
924
|
+
"common.noUsage": "no token data",
|
|
925
|
+
"status.created": "Created",
|
|
926
|
+
"status.in-progress": "In progress",
|
|
927
|
+
"status.pending-acceptance": "Pending acceptance",
|
|
928
|
+
"status.accepted": "Accepted",
|
|
929
|
+
"status.closed": "Closed",
|
|
930
|
+
"status.pending": "Todo",
|
|
931
|
+
"status.running": "Developing",
|
|
932
|
+
"status.testable": "Ready for test",
|
|
933
|
+
"status.testing": "Testing",
|
|
934
|
+
"status.rework": "Rework",
|
|
935
|
+
"status.needs-human": "Needs human",
|
|
936
|
+
"status.cancelled": "Closed",
|
|
937
|
+
"status.open": "Unclaimed",
|
|
938
|
+
"status.claimed": "In progress",
|
|
939
|
+
"status.fixed": "Fixed (pending verify)",
|
|
940
|
+
"status.verified": "Closed",
|
|
941
|
+
"status.reopened": "Reopened",
|
|
942
|
+
"status.done": "Done",
|
|
943
|
+
"status.completed": "Completed",
|
|
944
|
+
"status.failed": "Failed",
|
|
945
|
+
"status.interrupted": "Interrupted",
|
|
946
|
+
"status.superseded": "Superseded",
|
|
947
|
+
"runStatus.pending": "Queued",
|
|
948
|
+
"runStatus.running": "Running",
|
|
949
|
+
"runStatus.completed": "Completed",
|
|
950
|
+
"runStatus.failed": "Failed",
|
|
951
|
+
"runStatus.cancelled": "Cancelled",
|
|
952
|
+
"runStatus.interrupted": "Interrupted",
|
|
953
|
+
"runStatus.superseded": "Superseded",
|
|
954
|
+
"phase.prd": "PRD",
|
|
955
|
+
"phase.design": "UI/UX design",
|
|
956
|
+
"phase.scaffold": "Architecture",
|
|
957
|
+
"phase.tech": "Technical design",
|
|
958
|
+
"phase.dev": "Development",
|
|
959
|
+
"phase.qa": "QA testing",
|
|
960
|
+
"phase.acceptance": "Acceptance",
|
|
961
|
+
"kind.req": "Requirements",
|
|
962
|
+
"kind.task": "Tasks",
|
|
963
|
+
"kind.bug": "Defects",
|
|
964
|
+
"role.pm": "Product",
|
|
965
|
+
"role.design": "Design",
|
|
966
|
+
"role.arch": "Architecture",
|
|
967
|
+
"role.tech": "Tech design",
|
|
968
|
+
"role.dev": "Dev",
|
|
969
|
+
"role.qa": "QA",
|
|
970
|
+
"role.acceptance": "Acceptance",
|
|
971
|
+
"role.other": "Other",
|
|
972
|
+
"roleChip.pm": "📌 Product",
|
|
973
|
+
"roleChip.design": "🎨 Design",
|
|
974
|
+
"roleChip.arch": "🏗 Architecture",
|
|
975
|
+
"roleChip.tech": "📐 Tech design",
|
|
976
|
+
"roleChip.dev": "👨💻 Dev",
|
|
977
|
+
"roleChip.qa": "🧪 QA",
|
|
978
|
+
"roleChip.acceptance": "✅ Acceptance",
|
|
979
|
+
"token.officialTitle": "TOKEN · official metering",
|
|
980
|
+
"token.usageLine": "Input (miss) {input} / Input (hit) {cacheRead} / Cache write {cacheWrite} / Output {output} · {calls} calls",
|
|
981
|
+
"token.usageHit": " · cache hit {hit}%",
|
|
982
|
+
"token.usageMissing": "no usage detail",
|
|
983
|
+
"token.inputMiss": "Input (miss) {v}",
|
|
984
|
+
"token.inputHit": "Input (hit) {v}",
|
|
985
|
+
"token.cacheWrite": "Cache write {v}",
|
|
986
|
+
"token.output": "Output {v}",
|
|
987
|
+
"token.total": "Total {v}",
|
|
988
|
+
"token.allStagesTip": "Input (miss) / Input (hit) / Output totals across all stages",
|
|
989
|
+
"token.stageLine": "Stage tokens: {v}",
|
|
990
|
+
"stage.cardTip": "{label} — click for stage detail",
|
|
991
|
+
"stage.retryTip": "Retried {n}× ({m} attempts in total)",
|
|
992
|
+
"stage.detailTitle": "Stage detail",
|
|
993
|
+
"stage.summaryFallback": "(Full text was not saved for this run — showing the summary)\n\n{summary}",
|
|
994
|
+
"stage.loadFailed": "⚠ Load failed: {err}",
|
|
995
|
+
"stage.noOutput": "(no output text)",
|
|
996
|
+
"stage.attemptHistory": "↻ Attempt history ({n} · click to inspect one)",
|
|
997
|
+
"stage.attemptDone": "✅ OK",
|
|
998
|
+
"stage.attemptFailed": "❌ {outcome}",
|
|
999
|
+
"stage.attemptRunning": "⏳ Running",
|
|
1000
|
+
"stage.childJumpBtn": "🎬 Open subagent session",
|
|
1001
|
+
"stage.childJumpTip": "Open this stage's subagent session (full reasoning and tool-call trail); then switch to the \"Chat\" tab",
|
|
1002
|
+
"stage.childNoneTip": "No subagent session is available for this stage",
|
|
1003
|
+
"stage.childCrossSessionTip": "This subagent was started by session {sid}; cross-session navigation is not supported yet — open the Team Workbench in its originating session",
|
|
1004
|
+
"stage.childCrossSessionNote": "Cross-session navigation is not supported yet: this subagent was started by session {sid}. Open the Team Workbench in its originating session to see the trail.",
|
|
1005
|
+
"stage.childJumpNote": "After jumping, switch to the \"Chat\" tab to see this subagent's full session trail",
|
|
1006
|
+
"stage.evidenceTitle": "🔬 Verification evidence",
|
|
1007
|
+
"stage.evidenceMissing": "(missing — the contract was not honored and the host logged a warning; cross-check with this run's command logs: logs/teamflow/<runId>/ while the run is live, archived by the host to $DSH_HOME/teamflow/<workspace>/logs/<runId>/ afterwards)",
|
|
1008
|
+
"stage.evidenceMissingShort": "(missing — the contract was not honored and the host logged a warning)",
|
|
1009
|
+
"stage.artifactsTitle": "📄 Stage artifacts",
|
|
1010
|
+
"pipeline.empty": "No active pipeline — ask the model to call teamflow_start, or type a requirement above",
|
|
1011
|
+
"pipeline.noNodes": "The pipeline has not started any stage node yet",
|
|
1012
|
+
"pipeline.zoomOut": "Zoom out",
|
|
1013
|
+
"pipeline.zoomIn": "Zoom in",
|
|
1014
|
+
"pipeline.fitCanvas": "Fit canvas",
|
|
1015
|
+
"pipeline.canvasHint": "✥ Drag to pan · scroll to zoom",
|
|
1016
|
+
"workbench.title": "Team Workbench",
|
|
1017
|
+
"workbench.running": "Pipeline running",
|
|
1018
|
+
"workbench.idle": "Idle",
|
|
1019
|
+
"workbench.refresh": "🔄 Refresh",
|
|
1020
|
+
"workbench.resumeTip": "Resume {id}\nCurrent status: {status}; completed stages are skipped and the run restarts from the first unfinished stage",
|
|
1021
|
+
"workbench.resuming": "Resuming…",
|
|
1022
|
+
"workbench.resumeBtn": "↻ Resume from #{id}",
|
|
1023
|
+
"workbench.loadFailed": "⚠ {err} (make sure dsh-plugin-teamflow is installed and the web host was restarted)",
|
|
1024
|
+
"workbench.needsHumanBanner": "⚠ {n} item(s) need human intervention",
|
|
1025
|
+
"workbench.handle": "Handle {id}",
|
|
1026
|
+
"workbench.manualReason": "manual handling",
|
|
1027
|
+
"workbench.tabPipeline": "🔄 Pipeline",
|
|
1028
|
+
"workbench.tabBoard": "📋 Backlog board",
|
|
1029
|
+
"workbench.workspaceTip": "Current workspace (workspace-level isolation): {path}",
|
|
1030
|
+
"workbench.noWorkspace": "no workspace connected",
|
|
1031
|
+
"workbench.history": "History",
|
|
1032
|
+
"workbench.openRightBarTip": "Open this run's detail in the right sidebar (side by side with task-folder artifacts)",
|
|
1033
|
+
"workbench.openRightBarBtn": "⇥ Open in right bar",
|
|
1034
|
+
"board.empty": "Backlog is empty (no pipeline has run yet)",
|
|
1035
|
+
"board.dragReason": "kanban drag transition",
|
|
1036
|
+
"board.cardTip": "{id} · {status}{summary} (click for details)",
|
|
1037
|
+
"board.assignDevTip": "dev assignee\n{who}",
|
|
1038
|
+
"board.assignQaTip": "qa assignee\n{who}",
|
|
1039
|
+
"board.acceptTip": "acceptance/report owner\n{who}",
|
|
1040
|
+
"board.subtaskCount": "📦 {n} subtasks",
|
|
1041
|
+
"board.retries": "retries {n}",
|
|
1042
|
+
"item.overview": "Overview",
|
|
1043
|
+
"item.acceptRow": "✅ Acceptance",
|
|
1044
|
+
"item.retryRow": "↻ Retries",
|
|
1045
|
+
"item.humanRow": "⚠ Human intervention",
|
|
1046
|
+
"item.updatedAt": "Updated",
|
|
1047
|
+
"item.runSection": "Run",
|
|
1048
|
+
"item.runBtn": "▶ Pipeline",
|
|
1049
|
+
"item.jumpRunTip": "Open this requirement's pipeline view #{id}",
|
|
1050
|
+
"item.requirement": "Raw requirement",
|
|
1051
|
+
"item.noRequirement": "(no raw text)",
|
|
1052
|
+
"item.runDocs": "Task folder",
|
|
1053
|
+
"item.previewTip": "Preview {path} in the right sidebar",
|
|
1054
|
+
"item.subtasks": "Linked subtasks ({n})",
|
|
1055
|
+
"item.bugs": "Linked defects ({n})",
|
|
1056
|
+
"item.timeline": "Transition timeline ({n})",
|
|
1057
|
+
"team.currentTip": "Current team: {name} (click to switch)",
|
|
1058
|
+
"team.pick": "Select team",
|
|
1059
|
+
"team.label": "Team",
|
|
1060
|
+
"team.none": "No team (chat directly)",
|
|
1061
|
+
"team.noneNote": "teamflow is not used; the model works directly",
|
|
1062
|
+
"panel.title": "🏭 Team Workbench",
|
|
1063
|
+
"panel.subtitle": "Global panel · by product line",
|
|
1064
|
+
"panel.currentSession": "Session {sid}",
|
|
1065
|
+
"panel.noSession": "No active session",
|
|
1066
|
+
"panel.reload": "Reload data",
|
|
1067
|
+
"panel.backToChat": "Back to chat",
|
|
1068
|
+
"panel.backToChatTip": "Back to chat (click the sidebar icon again to return to this panel)",
|
|
1069
|
+
"panel.railTitle": "Product lines · {n}",
|
|
1070
|
+
"panel.rescanTip": "Rescan $DSH_HOME/teamflow",
|
|
1071
|
+
"panel.refreshing": "Refreshing",
|
|
1072
|
+
"panel.refresh": "Refresh",
|
|
1073
|
+
"panel.noProducts": "No product lines yet. Once a pipeline has run in a workspace, its product line shows up here ($DSH_HOME/teamflow/<key>).",
|
|
1074
|
+
"panel.activeRuns": "{n} running",
|
|
1075
|
+
"panel.updated": "updated {time}",
|
|
1076
|
+
"panel.reading": "Loading…",
|
|
1077
|
+
"panel.verdict": "acceptance {v}",
|
|
1078
|
+
"panel.pickProduct": "Pick a product line on the left to see its backlog and runs (the most recently updated one is selected on first entry).",
|
|
1079
|
+
"panel.loadingView": "Loading product-line data…",
|
|
1080
|
+
"panel.runChip": "runs {n}",
|
|
1081
|
+
"panel.activeChip": "active {n}",
|
|
1082
|
+
"panel.verdictChip": "acceptance {v}",
|
|
1083
|
+
"panel.tabRuns": "🚀 Pipeline runs · {n}",
|
|
1084
|
+
"panel.tabBacklog": "📋 Backlog · {n}",
|
|
1085
|
+
"panel.pinnedActive": "{n} running pinned",
|
|
1086
|
+
"panel.showRecent": "Show latest {n}",
|
|
1087
|
+
"panel.showAll": "Show all {n}",
|
|
1088
|
+
"panel.clearRunFilterTip": "Clear the run status filter",
|
|
1089
|
+
"panel.filtered": "Filtered {sel} · showing {shown}/{total} × clear",
|
|
1090
|
+
"panel.emptyRunFilter": "No runs match this filter.",
|
|
1091
|
+
"panel.runHint": "Click a row for the detail overlay; \"Open in session right bar\" jumps to the run's originating session and opens the right bar there (side by side with task-folder artifacts)",
|
|
1092
|
+
"panel.boardHint": "Click a status badge to filter (multi-select); terminal cards are collapsed by default and appear automatically while filtering",
|
|
1093
|
+
"panel.runDetail": "Run detail",
|
|
1094
|
+
"panel.goOwnerSession": "Go to originating session",
|
|
1095
|
+
"panel.goOwnerSessionTip": "Jump to the run's originating session and open its right bar there (side by side with task-folder artifacts)",
|
|
1096
|
+
"panel.remoteProductsUnavailable": "remote.products unavailable (plugin not mounted or outdated)",
|
|
1097
|
+
"panel.hintNoAddress": "{label}: the host produced no openable address (session context may be missing)",
|
|
1098
|
+
"panel.hintRightbarFailed": "Failed to open the right sidebar (the host mounts it only in the chat view): {label} — shown inline in this panel instead",
|
|
1099
|
+
"panel.hintSessionGone": "Originating session {sid}… is not in the session list (it may have been cleaned up) — showing the detail in this panel",
|
|
1100
|
+
"panel.hintSwitchedNoRightbar": "Switched to the originating session but the right bar did not open ({label}) — retry with \"⇥ Open in right bar\" in that session",
|
|
1101
|
+
"panel.hintInlineSuffix": " — shown inline in this panel instead",
|
|
1102
|
+
"runList.empty": "No runs recorded for this product line yet.",
|
|
1103
|
+
"runList.running": "Running",
|
|
1104
|
+
"runList.noRequirement": "(no requirement text)",
|
|
1105
|
+
"runList.stageProgress": "Stages {done}/{total}",
|
|
1106
|
+
"runList.openRightBar": "Open in session right bar",
|
|
1107
|
+
"runList.openRightBarTip": "Jump to the run's originating session and open the detail in that session's right bar (the right bar is session-scoped: attaching it to an unrelated session is meaningless)",
|
|
1108
|
+
"runList.callsSuffix": " · {n} calls",
|
|
1109
|
+
"panelBoard.empty": "Backlog is empty (this product line has no cards yet).",
|
|
1110
|
+
"panelBoard.groupCount": "{kind} · {n}",
|
|
1111
|
+
"panelBoard.activeCount": "Active {n}",
|
|
1112
|
+
"panelBoard.clearFilterTip": "Clear this group's filter",
|
|
1113
|
+
"panelBoard.collapseDone": "Hide {n} done",
|
|
1114
|
+
"panelBoard.expandDone": "{n} done ▸",
|
|
1115
|
+
"panelBoard.collapseDoneTip": "Hide done/closed cards",
|
|
1116
|
+
"panelBoard.expandDoneTip": "Show done/closed cards",
|
|
1117
|
+
"panelBoard.emptyFiltered": "No cards match this filter.",
|
|
1118
|
+
"panelBoard.filterChipOn": "Click to clear this status filter",
|
|
1119
|
+
"panelBoard.filterChipOff": "Click to filter by this status only (multi-select)",
|
|
1120
|
+
"panelItem.row.req": "Requirement",
|
|
1121
|
+
"panelItem.row.owner": "Owner",
|
|
1122
|
+
"panelItem.row.dev": "Dev",
|
|
1123
|
+
"panelItem.row.qa": "QA",
|
|
1124
|
+
"panelItem.row.accept": "Acceptance",
|
|
1125
|
+
"panelItem.row.retries": "Retries",
|
|
1126
|
+
"panelItem.row.runDocs": "Task folder",
|
|
1127
|
+
"panelItem.defectSection": "Defect detail (imported from the QA report)",
|
|
1128
|
+
"panelItem.row.defectId": "Defect id",
|
|
1129
|
+
"panelItem.row.severity": "Severity",
|
|
1130
|
+
"panelItem.row.module": "Module",
|
|
1131
|
+
"panelItem.row.reproduce": "Steps to reproduce",
|
|
1132
|
+
"panelItem.row.expected": "Expected",
|
|
1133
|
+
"panelItem.row.actual": "Actual",
|
|
1134
|
+
"panelItem.row.defectAc": "Related AC",
|
|
1135
|
+
"panelItem.row.defectCheck": "Check command",
|
|
1136
|
+
"panelItem.row.defectCriterion": "Pass criterion",
|
|
1137
|
+
"panelItem.noDefectDetail": "(No defect description on this card: the QA report only carried the three key fields when it was registered, or the card predates this version; see QA-REPORT.md in the linked run)",
|
|
1138
|
+
"panelItem.spec": "Spec",
|
|
1139
|
+
"panelItem.summary": "Summary",
|
|
1140
|
+
"panelItem.artifacts": "Task-folder artifacts · {n}",
|
|
1141
|
+
"panelItem.artifactTip": "{address}\n(opens in the right sidebar of the session that owns this artifact)",
|
|
1142
|
+
"panelItem.noArtifacts": "This item has no previewable task-folder artifacts (or no session context to build a file address).",
|
|
1143
|
+
"panelItem.subtasks": "Subtasks · {n}",
|
|
1144
|
+
"panelItem.bugs": "Linked defects · {n}",
|
|
1145
|
+
"panelItem.events": "Transition timeline · last {n}",
|
|
1146
|
+
"detail.runMissing": "Run not found (it may have been cleaned up, or the address is stale).",
|
|
1147
|
+
"detail.subagents": "Subagents {n}",
|
|
1148
|
+
"detail.stages": "Stages · {n}",
|
|
1149
|
+
"detail.stageFailed": "Failed to load stage detail: {err}",
|
|
1150
|
+
"detail.stageTitle": "Stage #{seq} detail",
|
|
1151
|
+
"detail.output": "Stage output",
|
|
1152
|
+
"detail.attempts": "Attempts for this task · {n}",
|
|
1153
|
+
"detail.logs": "Logs · last {n}",
|
|
1154
|
+
"detail.endedRunning": "running",
|
|
1155
|
+
"detail.subagentChip": "Subagent {id}",
|
|
1156
|
+
"tab.resolveFailed": "Cannot parse the run address: {address}",
|
|
1157
|
+
"tab.readingAddress": "Reading the tab address…",
|
|
1158
|
+
"tab.noHook": "Host tab-info hook unavailable (useTabInfo missing)",
|
|
1159
|
+
"tab.remoteUnavailable": "remote unavailable (plugin not mounted or outdated)",
|
|
1160
|
+
"tab.readingRun": "Loading run detail…"
|
|
1161
|
+
};
|
|
1162
|
+
//#endregion
|
|
1163
|
+
//#region client/panel.tsx
|
|
1164
|
+
/**
|
|
1165
|
+
* dsh-plugin-teamflow — 全局面板 + 右栏 run 详情 tab(v0.1.8 ①)。
|
|
1166
|
+
*
|
|
1167
|
+
* 与 conversation.view tab(会话内工作台)的分工:
|
|
1168
|
+
* - 全局面板挂在**应用外壳**的 root slot 上:`sidebar.panellist`(侧边栏图标)+
|
|
1169
|
+
* `main`(中央主区,keyed,**必须与 panellist 同 id**——宿主 `layout.selectPanel`
|
|
1170
|
+
* 对未注册的 main key 直接抛错)。它没有会话上下文,因此数据面按**产品线 key**
|
|
1171
|
+
* 寻址(`remote.products/productView/...`),跨会话可用。
|
|
1172
|
+
* - 右栏 run 详情 tab:tab 类型进 `ctx.sidebarRightTabs`,正文进 keyed seat
|
|
1173
|
+
* `sidebar.right.pane.tab`;地址由 host 生成(`dsh-resource://teamflow/run/<产品线>/<runId>`),
|
|
1174
|
+
* 与产物预览同一条原则——client 不拼地址。右侧栏是 AppFrame 级 root slot,
|
|
1175
|
+
* 全局面板占着中央区时它依然挂载(唯一失败态是"没有挂载会话",此时降级为面板内联详情)。
|
|
1176
|
+
*/
|
|
1177
|
+
const RUN_TAB_ID = "dsh-plugin-teamflow/run";
|
|
1178
|
+
const RUN_TAB_KIND = "teamflow-run";
|
|
1179
|
+
const RUN_TAB_PATTERN = "dsh-resource://teamflow/run/**";
|
|
1180
|
+
/** 解析 host 生成的 run 地址 → { product, runId }(不合法 → null)。 */
|
|
1181
|
+
function parseRunAddress(address) {
|
|
1182
|
+
const m = /^dsh-resource:\/\/teamflow\/run\/([^/]+)\/([^/?#]+)$/.exec(String(address || ""));
|
|
1183
|
+
if (!m) return null;
|
|
1184
|
+
try {
|
|
1185
|
+
return {
|
|
1186
|
+
product: decodeURIComponent(m[1]),
|
|
1187
|
+
runId: decodeURIComponent(m[2])
|
|
1188
|
+
};
|
|
1189
|
+
} catch (e) {
|
|
1190
|
+
return null;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
/** 右栏 tab 类型定义(类型本身;正文在 sidebar.right.pane.tab 的 keyed seat)。 */
|
|
1194
|
+
function runTabDefinition() {
|
|
1195
|
+
return {
|
|
1196
|
+
id: RUN_TAB_ID,
|
|
1197
|
+
kind: RUN_TAB_KIND,
|
|
1198
|
+
priority: "extension",
|
|
1199
|
+
patterns: [RUN_TAB_PATTERN],
|
|
1200
|
+
title: (address) => {
|
|
1201
|
+
const p = parseRunAddress(address);
|
|
1202
|
+
return p ? `TeamFlow · ${p.runId.slice(0, 14)}` : "TeamFlow Run";
|
|
1203
|
+
}
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
/** Typert remote 信封解包(与会话内工作台同一约定)。
|
|
1207
|
+
* 空信封(`{ok:true}` 但没有 `value`)是**真实故障态**:host 方法返回了 `undefined` / 结果被丢弃——
|
|
1208
|
+
* 此时不能静默返回 undefined(调用方会把它再包成「未知错误」,丢掉定位信息),故显式报出原始信封。 */
|
|
1209
|
+
function unwrap$1(res, what) {
|
|
1210
|
+
if (!res || !res.ok) {
|
|
1211
|
+
let detail = t("common.unknownError");
|
|
1212
|
+
try {
|
|
1213
|
+
detail = res && res.error && (res.error.message || res.error.code) || JSON.stringify(res) || t("common.unknownError");
|
|
1214
|
+
} catch (e) {}
|
|
1215
|
+
try {
|
|
1216
|
+
console.warn("[teamflow] remote 调用失败", what, res);
|
|
1217
|
+
} catch (e) {}
|
|
1218
|
+
throw new Error(t("common.remoteCallFailed", {
|
|
1219
|
+
what: what || "remote",
|
|
1220
|
+
detail
|
|
1221
|
+
}));
|
|
1222
|
+
}
|
|
1223
|
+
if (res.value === void 0) {
|
|
1224
|
+
try {
|
|
1225
|
+
console.warn("[teamflow] remote 返回空信封(ok 但无 value)", what, res);
|
|
1226
|
+
} catch (e) {}
|
|
1227
|
+
let raw = "";
|
|
1228
|
+
try {
|
|
1229
|
+
raw = JSON.stringify(res);
|
|
1230
|
+
} catch (e) {
|
|
1231
|
+
raw = String(res);
|
|
1232
|
+
}
|
|
1233
|
+
throw new Error(t("common.remoteEmptyResult", {
|
|
1234
|
+
what: what || "remote",
|
|
1235
|
+
raw
|
|
1236
|
+
}));
|
|
1237
|
+
}
|
|
1238
|
+
return res.value;
|
|
1239
|
+
}
|
|
1240
|
+
/** 全局面板图标:三条泳道 + 节点(与工作台「流水线」语义一致),跟随选中态配色。 */
|
|
1241
|
+
function TeamflowPanelIcon({ size = 16, active = false }) {
|
|
1242
|
+
return h("svg", {
|
|
1243
|
+
width: size,
|
|
1244
|
+
height: size,
|
|
1245
|
+
viewBox: "0 0 24 24",
|
|
1246
|
+
fill: "none",
|
|
1247
|
+
stroke: active ? "currentColor" : "currentColor",
|
|
1248
|
+
strokeWidth: 1.6,
|
|
1249
|
+
strokeLinecap: "round",
|
|
1250
|
+
strokeLinejoin: "round",
|
|
1251
|
+
"aria-hidden": "true",
|
|
1252
|
+
focusable: "false"
|
|
1253
|
+
}, h("rect", {
|
|
1254
|
+
x: 3,
|
|
1255
|
+
y: 4,
|
|
1256
|
+
width: 5.2,
|
|
1257
|
+
height: 4,
|
|
1258
|
+
rx: 1.2,
|
|
1259
|
+
opacity: active ? 1 : .85
|
|
1260
|
+
}), h("rect", {
|
|
1261
|
+
x: 9.4,
|
|
1262
|
+
y: 10,
|
|
1263
|
+
width: 5.2,
|
|
1264
|
+
height: 4,
|
|
1265
|
+
rx: 1.2,
|
|
1266
|
+
opacity: active ? 1 : .85
|
|
1267
|
+
}), h("rect", {
|
|
1268
|
+
x: 3,
|
|
1269
|
+
y: 16,
|
|
1270
|
+
width: 5.2,
|
|
1271
|
+
height: 4,
|
|
1272
|
+
rx: 1.2,
|
|
1273
|
+
opacity: active ? 1 : .85
|
|
1274
|
+
}), h("path", { d: "M8.4 6h3.3a1 1 0 0 1 1 1v3.4" }), h("path", { d: "M8.4 18h3.3a1 1 0 0 0 1-1v-3.4" }), h("path", { d: "M14.6 12h4.4" }));
|
|
1275
|
+
}
|
|
1276
|
+
const panelBtn = {
|
|
1277
|
+
font: "inherit",
|
|
1278
|
+
fontSize: 11,
|
|
1279
|
+
fontWeight: 600,
|
|
1280
|
+
padding: "3px 9px",
|
|
1281
|
+
borderRadius: 7,
|
|
1282
|
+
cursor: "pointer",
|
|
1283
|
+
border: `1px solid ${T.border}`,
|
|
1284
|
+
background: T.layer1,
|
|
1285
|
+
color: T.text,
|
|
1286
|
+
whiteSpace: "nowrap",
|
|
1287
|
+
lineHeight: "16px"
|
|
1288
|
+
};
|
|
1289
|
+
const brandBtn = {
|
|
1290
|
+
...panelBtn,
|
|
1291
|
+
border: `1px solid color-mix(in srgb, ${T.brand} 40%, transparent)`,
|
|
1292
|
+
background: `color-mix(in srgb, ${T.brand} 12%, transparent)`,
|
|
1293
|
+
color: T.brand
|
|
1294
|
+
};
|
|
1295
|
+
const sectionTitle = (text, extra) => h("div", { style: {
|
|
1296
|
+
...flexRow,
|
|
1297
|
+
justifyContent: "space-between",
|
|
1298
|
+
margin: "14px 0 8px"
|
|
1299
|
+
} }, h("div", { style: {
|
|
1300
|
+
fontSize: 12,
|
|
1301
|
+
fontWeight: 700,
|
|
1302
|
+
color: T.text,
|
|
1303
|
+
letterSpacing: ".02em"
|
|
1304
|
+
} }, text), extra || null);
|
|
1305
|
+
const muted = (text, style = {}) => h("div", { style: {
|
|
1306
|
+
fontSize: 11,
|
|
1307
|
+
color: T.text2,
|
|
1308
|
+
lineHeight: 1.6,
|
|
1309
|
+
...style
|
|
1310
|
+
} }, text);
|
|
1311
|
+
const runUsageText = (u) => {
|
|
1312
|
+
if (!u || !(u.input || u.cacheRead || u.cacheWrite || u.output)) return t("common.noUsage");
|
|
1313
|
+
const hit = hitRate(u);
|
|
1314
|
+
return `⇅${fmtTokens(u.input)} ⇅${fmtTokens(u.cacheRead)} ⬆${fmtTokens(u.output)}${hit !== null ? ` ·${hit}%` : ""} · ${t("common.calls", { n: u.calls })}`;
|
|
1315
|
+
};
|
|
1316
|
+
const RUN_PREVIEW = 8;
|
|
1317
|
+
const TERMINAL_STATUSES = [
|
|
1318
|
+
"accepted",
|
|
1319
|
+
"closed",
|
|
1320
|
+
"verified",
|
|
1321
|
+
"cancelled"
|
|
1322
|
+
];
|
|
1323
|
+
function ProductRail({ products, current, loadingKey, onSelect, onRefresh, busy }) {
|
|
1324
|
+
return h("div", { style: {
|
|
1325
|
+
width: 236,
|
|
1326
|
+
flex: "0 0 236px",
|
|
1327
|
+
minHeight: 0,
|
|
1328
|
+
display: "flex",
|
|
1329
|
+
flexDirection: "column",
|
|
1330
|
+
borderRight: `1px solid ${T.border}`,
|
|
1331
|
+
background: `color-mix(in srgb, ${T.layer1} 40%, transparent)`
|
|
1332
|
+
} }, h("div", { style: {
|
|
1333
|
+
...flexRow,
|
|
1334
|
+
justifyContent: "space-between",
|
|
1335
|
+
padding: "10px 12px 8px"
|
|
1336
|
+
} }, h("span", { style: {
|
|
1337
|
+
fontSize: 11.5,
|
|
1338
|
+
fontWeight: 700,
|
|
1339
|
+
color: T.text2
|
|
1340
|
+
} }, t("panel.railTitle", { n: products.length })), h("button", {
|
|
1341
|
+
style: panelBtn,
|
|
1342
|
+
onClick: onRefresh,
|
|
1343
|
+
disabled: busy,
|
|
1344
|
+
title: t("panel.rescanTip")
|
|
1345
|
+
}, busy ? t("panel.refreshing") : t("panel.refresh"))), h("div", { style: {
|
|
1346
|
+
flex: 1,
|
|
1347
|
+
minHeight: 0,
|
|
1348
|
+
overflowY: "auto",
|
|
1349
|
+
padding: "0 8px 12px"
|
|
1350
|
+
} }, products.length === 0 ? muted(t("panel.noProducts"), { padding: "10px 4px" }) : products.map((p) => {
|
|
1351
|
+
const on = p.key === current;
|
|
1352
|
+
return h("div", {
|
|
1353
|
+
key: p.key,
|
|
1354
|
+
onClick: () => onSelect(p.key),
|
|
1355
|
+
title: p.path || p.key,
|
|
1356
|
+
style: {
|
|
1357
|
+
padding: "8px 10px",
|
|
1358
|
+
marginBottom: 6,
|
|
1359
|
+
borderRadius: 9,
|
|
1360
|
+
cursor: "pointer",
|
|
1361
|
+
border: `1px solid ${on ? `color-mix(in srgb, ${T.brand} 45%, transparent)` : T.border}`,
|
|
1362
|
+
background: on ? `color-mix(in srgb, ${T.brand} 10%, ${T.layer1})` : "transparent"
|
|
1363
|
+
}
|
|
1364
|
+
}, h("div", { style: {
|
|
1365
|
+
...flexRow,
|
|
1366
|
+
justifyContent: "space-between"
|
|
1367
|
+
} }, h("span", { style: {
|
|
1368
|
+
fontSize: 12,
|
|
1369
|
+
fontWeight: 600,
|
|
1370
|
+
color: T.text,
|
|
1371
|
+
overflow: "hidden",
|
|
1372
|
+
textOverflow: "ellipsis",
|
|
1373
|
+
whiteSpace: "nowrap"
|
|
1374
|
+
} }, p.title || p.key), p.activeRuns > 0 ? chip(t("panel.activeRuns", { n: p.activeRuns }), T.brand, { dot: true }) : null), h("div", { style: {
|
|
1375
|
+
fontSize: 10,
|
|
1376
|
+
color: T.text2,
|
|
1377
|
+
fontFamily: MONO,
|
|
1378
|
+
marginTop: 2,
|
|
1379
|
+
overflow: "hidden",
|
|
1380
|
+
textOverflow: "ellipsis",
|
|
1381
|
+
whiteSpace: "nowrap"
|
|
1382
|
+
} }, p.key), h("div", { style: {
|
|
1383
|
+
...flexRow,
|
|
1384
|
+
gap: 8,
|
|
1385
|
+
marginTop: 3,
|
|
1386
|
+
fontSize: 10,
|
|
1387
|
+
color: T.text2
|
|
1388
|
+
} }, h("span", null, `run ${p.totalRuns}`), p.updatedAt ? h("span", null, t("panel.updated", { time: fmtTime(p.updatedAt) })) : null, p.key === loadingKey ? h("span", { style: {
|
|
1389
|
+
color: T.brand,
|
|
1390
|
+
fontWeight: 600
|
|
1391
|
+
} }, t("panel.reading")) : null), p.lastRequirement ? h("div", { style: {
|
|
1392
|
+
fontSize: 10.5,
|
|
1393
|
+
color: T.text2,
|
|
1394
|
+
marginTop: 3,
|
|
1395
|
+
lineHeight: 1.45,
|
|
1396
|
+
display: "-webkit-box",
|
|
1397
|
+
WebkitLineClamp: 2,
|
|
1398
|
+
WebkitBoxOrient: "vertical",
|
|
1399
|
+
overflow: "hidden"
|
|
1400
|
+
} }, p.lastRequirement) : null, p.lastVerdict ? h("div", { style: { marginTop: 4 } }, chip(t("panel.verdict", { v: p.lastVerdict }), stColor(p.lastVerdict === "accepted" ? "accepted" : p.lastVerdict))) : null);
|
|
1401
|
+
})));
|
|
1402
|
+
}
|
|
1403
|
+
function RunList({ runs, activeRunId, onOpenRun, onInlineRun }) {
|
|
1404
|
+
if (!runs.length) return muted(t("runList.empty"), { padding: "2px 2px 8px" });
|
|
1405
|
+
return h("div", { style: {
|
|
1406
|
+
display: "flex",
|
|
1407
|
+
flexDirection: "column",
|
|
1408
|
+
gap: 6
|
|
1409
|
+
} }, runs.map((r) => {
|
|
1410
|
+
const on = r.id === activeRunId;
|
|
1411
|
+
const active = r.status === "running" || r.status === "pending";
|
|
1412
|
+
return h("div", {
|
|
1413
|
+
key: r.id,
|
|
1414
|
+
style: {
|
|
1415
|
+
padding: "8px 11px",
|
|
1416
|
+
borderRadius: 9,
|
|
1417
|
+
border: `1px solid ${on ? `color-mix(in srgb, ${T.brand} 45%, transparent)` : T.border}`,
|
|
1418
|
+
borderLeft: `3px solid ${stColor(r.status)}`,
|
|
1419
|
+
background: on ? `color-mix(in srgb, ${T.brand} 8%, ${T.layer1})` : T.layer1,
|
|
1420
|
+
display: "flex",
|
|
1421
|
+
alignItems: "center",
|
|
1422
|
+
gap: 10,
|
|
1423
|
+
cursor: "pointer"
|
|
1424
|
+
},
|
|
1425
|
+
onClick: () => onInlineRun(r)
|
|
1426
|
+
}, h("div", { style: {
|
|
1427
|
+
flex: 1,
|
|
1428
|
+
minWidth: 0
|
|
1429
|
+
} }, h("div", { style: {
|
|
1430
|
+
...flexRow,
|
|
1431
|
+
gap: 6
|
|
1432
|
+
} }, chip(runStatusText(r.status), stColor(r.status), { dot: true }), r.mode ? chip(String(r.mode), T.text2) : null, h("span", { style: {
|
|
1433
|
+
fontFamily: MONO,
|
|
1434
|
+
fontSize: 10,
|
|
1435
|
+
color: T.text2
|
|
1436
|
+
} }, r.id), active ? h("span", { style: {
|
|
1437
|
+
fontSize: 10,
|
|
1438
|
+
color: T.brand
|
|
1439
|
+
} }, t("runList.running")) : null), h("div", { style: {
|
|
1440
|
+
fontSize: 11.5,
|
|
1441
|
+
color: T.text,
|
|
1442
|
+
marginTop: 3,
|
|
1443
|
+
overflow: "hidden",
|
|
1444
|
+
textOverflow: "ellipsis",
|
|
1445
|
+
whiteSpace: "nowrap"
|
|
1446
|
+
} }, r.requirement || t("runList.noRequirement")), h("div", { style: {
|
|
1447
|
+
...flexRow,
|
|
1448
|
+
gap: 10,
|
|
1449
|
+
marginTop: 3,
|
|
1450
|
+
fontSize: 10,
|
|
1451
|
+
color: T.text2,
|
|
1452
|
+
fontFamily: MONO
|
|
1453
|
+
} }, h("span", null, t("runList.stageProgress", {
|
|
1454
|
+
done: r.doneStages,
|
|
1455
|
+
total: r.stageCount
|
|
1456
|
+
})), h("span", null, runUsageText(r.usage)), h("span", null, `${fmtTime(r.startedAt)}${r.endedAt ? ` → ${fmtTime(r.endedAt)}` : ""} ${fmtDur(r.startedAt, r.endedAt)}`))), h("button", {
|
|
1457
|
+
style: brandBtn,
|
|
1458
|
+
title: t("runList.openRightBarTip"),
|
|
1459
|
+
onClick: (e) => {
|
|
1460
|
+
e.stopPropagation();
|
|
1461
|
+
onOpenRun(r);
|
|
1462
|
+
}
|
|
1463
|
+
}, t("runList.openRightBar")));
|
|
1464
|
+
}));
|
|
1465
|
+
}
|
|
1466
|
+
function BacklogCard({ kind, item, onOpen }) {
|
|
1467
|
+
const color = stColor(item.status);
|
|
1468
|
+
return h("div", {
|
|
1469
|
+
onClick: () => onOpen(kind, item.id),
|
|
1470
|
+
style: {
|
|
1471
|
+
padding: "6px 9px",
|
|
1472
|
+
borderRadius: 8,
|
|
1473
|
+
border: `1px solid ${T.border}`,
|
|
1474
|
+
borderLeft: `3px solid ${color}`,
|
|
1475
|
+
background: T.layer1,
|
|
1476
|
+
cursor: "pointer",
|
|
1477
|
+
minWidth: 0,
|
|
1478
|
+
boxSizing: "border-box",
|
|
1479
|
+
maxWidth: "100%",
|
|
1480
|
+
overflow: "hidden"
|
|
1481
|
+
}
|
|
1482
|
+
}, h("div", { style: {
|
|
1483
|
+
...flexRow,
|
|
1484
|
+
gap: 5,
|
|
1485
|
+
minWidth: 0
|
|
1486
|
+
} }, h("span", { style: {
|
|
1487
|
+
fontFamily: MONO,
|
|
1488
|
+
fontSize: 10,
|
|
1489
|
+
color: T.text2,
|
|
1490
|
+
minWidth: 0,
|
|
1491
|
+
overflow: "hidden",
|
|
1492
|
+
textOverflow: "ellipsis",
|
|
1493
|
+
whiteSpace: "nowrap"
|
|
1494
|
+
} }, item.id), chip(stText(item.status), color, { dot: true }), kind === "bug" && item.severity ? chip(String(item.severity), stColor("rework")) : null), h("div", { style: {
|
|
1495
|
+
fontSize: 11.5,
|
|
1496
|
+
color: T.text,
|
|
1497
|
+
marginTop: 3,
|
|
1498
|
+
overflow: "hidden",
|
|
1499
|
+
textOverflow: "ellipsis",
|
|
1500
|
+
whiteSpace: "nowrap"
|
|
1501
|
+
} }, item.title || item.id), (() => {
|
|
1502
|
+
const roles = kind === "task" ? byRoleLine(item) : "";
|
|
1503
|
+
return roles ? h("div", {
|
|
1504
|
+
title: roles,
|
|
1505
|
+
style: {
|
|
1506
|
+
fontSize: 10,
|
|
1507
|
+
color: T.text2,
|
|
1508
|
+
fontFamily: MONO,
|
|
1509
|
+
marginTop: 3,
|
|
1510
|
+
minWidth: 0,
|
|
1511
|
+
overflowWrap: "anywhere",
|
|
1512
|
+
wordBreak: "break-word"
|
|
1513
|
+
}
|
|
1514
|
+
}, roles) : null;
|
|
1515
|
+
})(), h("div", { style: {
|
|
1516
|
+
...flexRow,
|
|
1517
|
+
gap: 6,
|
|
1518
|
+
marginTop: 3,
|
|
1519
|
+
fontSize: 10,
|
|
1520
|
+
color: T.text2
|
|
1521
|
+
} }, item.devAssign ? h("span", null, `dev ${item.devAssign}`) : null, item.qaAssign ? h("span", null, `qa ${item.qaAssign}`) : null, item.acceptBy ? h("span", null, t("panel.verdict", { v: item.acceptBy })) : null, item.retries ? h("span", { style: { color: T.warn } }, t("board.retries", { n: item.retries })) : null, item.humanIntervention ? h("span", { style: { color: T.error } }, t("status.needs-human")) : null));
|
|
1522
|
+
}
|
|
1523
|
+
/** 可点筛选徽章(多选):单击切换该状态,选中态用状态色实心;再点取消。 */
|
|
1524
|
+
const filterChip = (text, color, on, onToggle) => h("button", {
|
|
1525
|
+
onClick: onToggle,
|
|
1526
|
+
title: on ? t("panelBoard.filterChipOn") : t("panelBoard.filterChipOff"),
|
|
1527
|
+
style: {
|
|
1528
|
+
font: "inherit",
|
|
1529
|
+
fontSize: 11,
|
|
1530
|
+
fontWeight: 600,
|
|
1531
|
+
padding: "1px 8px",
|
|
1532
|
+
borderRadius: 999,
|
|
1533
|
+
cursor: "pointer",
|
|
1534
|
+
lineHeight: "16px",
|
|
1535
|
+
whiteSpace: "nowrap",
|
|
1536
|
+
border: `1px solid ${on ? `color-mix(in srgb, ${color} 55%, transparent)` : T.border}`,
|
|
1537
|
+
background: on ? `color-mix(in srgb, ${color} 26%, transparent)` : "transparent",
|
|
1538
|
+
color: on ? color : T.text2
|
|
1539
|
+
}
|
|
1540
|
+
}, text);
|
|
1541
|
+
/** run 状态展示顺序(可行动的在前)。 */
|
|
1542
|
+
const RUN_STATUS_ORDER = [
|
|
1543
|
+
"running",
|
|
1544
|
+
"pending",
|
|
1545
|
+
"interrupted",
|
|
1546
|
+
"failed",
|
|
1547
|
+
"cancelled",
|
|
1548
|
+
"completed",
|
|
1549
|
+
"superseded"
|
|
1550
|
+
];
|
|
1551
|
+
function BacklogGroups({ backlog, onOpen, productKey }) {
|
|
1552
|
+
const [showDone, setShowDone] = react.default.useState({});
|
|
1553
|
+
const [filters, setFilters] = react.default.useState({});
|
|
1554
|
+
react.default.useEffect(() => {
|
|
1555
|
+
setShowDone({});
|
|
1556
|
+
setFilters({});
|
|
1557
|
+
}, [productKey]);
|
|
1558
|
+
if (!backlog) return null;
|
|
1559
|
+
const groups = [
|
|
1560
|
+
["req", backlog.requirements || []],
|
|
1561
|
+
["task", (backlog.tasks || []).filter((t) => t.type !== "subtask")],
|
|
1562
|
+
["bug", backlog.bugs || []]
|
|
1563
|
+
];
|
|
1564
|
+
if (!groups.reduce((a, [, arr]) => a + arr.length, 0)) return muted(t("panelBoard.empty"), { padding: "2px 2px 8px" });
|
|
1565
|
+
return h("div", null, groups.map(([kind, list]) => {
|
|
1566
|
+
if (!list.length) return null;
|
|
1567
|
+
const done = list.filter((it) => TERMINAL_STATUSES.indexOf(it.status) !== -1 && !it.humanIntervention);
|
|
1568
|
+
const active = list.filter((it) => done.indexOf(it) === -1);
|
|
1569
|
+
const open = showDone[kind] === true;
|
|
1570
|
+
const sel = filters[kind] || [];
|
|
1571
|
+
const selSet = new Set(sel);
|
|
1572
|
+
const filtering = sel.length > 0;
|
|
1573
|
+
const matched = filtering ? list.filter((it) => selSet.has(it.status)) : list;
|
|
1574
|
+
const byStatus = {};
|
|
1575
|
+
for (const it of list) byStatus[it.status] = (byStatus[it.status] || 0) + 1;
|
|
1576
|
+
const toggle = (st) => setFilters((m) => {
|
|
1577
|
+
const cur = new Set(m[kind] || []);
|
|
1578
|
+
if (cur.has(st)) cur.delete(st);
|
|
1579
|
+
else cur.add(st);
|
|
1580
|
+
return {
|
|
1581
|
+
...m,
|
|
1582
|
+
[kind]: [...cur]
|
|
1583
|
+
};
|
|
1584
|
+
});
|
|
1585
|
+
const visible = filtering ? matched : open ? list : active;
|
|
1586
|
+
return h("div", {
|
|
1587
|
+
key: kind,
|
|
1588
|
+
style: { marginBottom: 12 }
|
|
1589
|
+
}, h("div", { style: {
|
|
1590
|
+
...flexRow,
|
|
1591
|
+
gap: 6,
|
|
1592
|
+
marginBottom: 6,
|
|
1593
|
+
minWidth: 0
|
|
1594
|
+
} }, h("span", { style: {
|
|
1595
|
+
fontSize: 11.5,
|
|
1596
|
+
fontWeight: 700,
|
|
1597
|
+
color: T.text,
|
|
1598
|
+
flex: "0 0 auto"
|
|
1599
|
+
} }, t("panelBoard.groupCount", {
|
|
1600
|
+
kind: kindTitle(kind),
|
|
1601
|
+
n: list.length
|
|
1602
|
+
})), h("span", { style: {
|
|
1603
|
+
fontSize: 10,
|
|
1604
|
+
color: T.text2,
|
|
1605
|
+
flex: "0 0 auto"
|
|
1606
|
+
} }, t("panelBoard.activeCount", { n: active.length })), ...Object.keys(byStatus).map((s) => filterChip(`${stText(s)} ${byStatus[s]}`, stColor(s), selSet.has(s), () => toggle(s))), filtering ? h("button", {
|
|
1607
|
+
style: {
|
|
1608
|
+
...panelBtn,
|
|
1609
|
+
marginLeft: "auto",
|
|
1610
|
+
flex: "0 0 auto"
|
|
1611
|
+
},
|
|
1612
|
+
title: t("panelBoard.clearFilterTip"),
|
|
1613
|
+
onClick: () => setFilters((m) => ({
|
|
1614
|
+
...m,
|
|
1615
|
+
[kind]: []
|
|
1616
|
+
}))
|
|
1617
|
+
}, t("panel.filtered", {
|
|
1618
|
+
sel: sel.length,
|
|
1619
|
+
shown: matched.length,
|
|
1620
|
+
total: list.length
|
|
1621
|
+
})) : done.length ? h("button", {
|
|
1622
|
+
style: {
|
|
1623
|
+
...panelBtn,
|
|
1624
|
+
marginLeft: "auto",
|
|
1625
|
+
flex: "0 0 auto"
|
|
1626
|
+
},
|
|
1627
|
+
title: open ? t("panelBoard.collapseDoneTip") : t("panelBoard.expandDoneTip"),
|
|
1628
|
+
onClick: () => setShowDone((m) => ({
|
|
1629
|
+
...m,
|
|
1630
|
+
[kind]: !open
|
|
1631
|
+
}))
|
|
1632
|
+
}, open ? t("panelBoard.collapseDone", { n: done.length }) : t("panelBoard.expandDone", { n: done.length })) : null), visible.length ? h("div", { style: {
|
|
1633
|
+
display: "grid",
|
|
1634
|
+
gridTemplateColumns: "repeat(auto-fill, minmax(228px, 1fr))",
|
|
1635
|
+
gap: 6
|
|
1636
|
+
} }, visible.map((it) => h(BacklogCard, {
|
|
1637
|
+
key: it.id,
|
|
1638
|
+
kind,
|
|
1639
|
+
item: it,
|
|
1640
|
+
onOpen
|
|
1641
|
+
}))) : muted(t("panelBoard.emptyFiltered"), { fontSize: 10.5 }));
|
|
1642
|
+
}));
|
|
1643
|
+
}
|
|
1644
|
+
function ItemDetailPane({ det, openArtifact, onClose }) {
|
|
1645
|
+
if (!det) return null;
|
|
1646
|
+
const row = (k, v) => v === null || v === void 0 || v === "" ? null : h("div", { style: {
|
|
1647
|
+
...flexRow,
|
|
1648
|
+
gap: 6,
|
|
1649
|
+
fontSize: 11
|
|
1650
|
+
} }, h("span", { style: {
|
|
1651
|
+
color: T.text2,
|
|
1652
|
+
flex: "0 0 auto"
|
|
1653
|
+
} }, k), h("span", { style: {
|
|
1654
|
+
color: T.text,
|
|
1655
|
+
minWidth: 0,
|
|
1656
|
+
wordBreak: "break-word"
|
|
1657
|
+
} }, String(v)));
|
|
1658
|
+
return h("div", { style: {
|
|
1659
|
+
padding: "10px 12px",
|
|
1660
|
+
display: "flex",
|
|
1661
|
+
flexDirection: "column",
|
|
1662
|
+
gap: 8,
|
|
1663
|
+
minHeight: 0
|
|
1664
|
+
} }, h("div", { style: {
|
|
1665
|
+
...flexRow,
|
|
1666
|
+
justifyContent: "space-between"
|
|
1667
|
+
} }, h("div", { style: {
|
|
1668
|
+
...flexRow,
|
|
1669
|
+
gap: 6
|
|
1670
|
+
} }, h("span", { style: {
|
|
1671
|
+
fontFamily: MONO,
|
|
1672
|
+
fontSize: 11,
|
|
1673
|
+
color: T.text2
|
|
1674
|
+
} }, det.id), chip(stText(det.status), stColor(det.status), { dot: true }), h("span", { style: {
|
|
1675
|
+
fontSize: 10.5,
|
|
1676
|
+
color: T.text2
|
|
1677
|
+
} }, kindTitle(det.kind))), h("button", {
|
|
1678
|
+
style: panelBtn,
|
|
1679
|
+
onClick: onClose
|
|
1680
|
+
}, t("common.close"))), h("div", { style: {
|
|
1681
|
+
fontSize: 13,
|
|
1682
|
+
fontWeight: 600,
|
|
1683
|
+
color: T.text,
|
|
1684
|
+
lineHeight: 1.45
|
|
1685
|
+
} }, det.title), det.kind === "bug" ? h("div", { style: {
|
|
1686
|
+
display: "flex",
|
|
1687
|
+
flexDirection: "column",
|
|
1688
|
+
gap: 4,
|
|
1689
|
+
padding: "8px 10px",
|
|
1690
|
+
borderRadius: 8,
|
|
1691
|
+
background: T.layer2,
|
|
1692
|
+
border: `1px solid ${T.border}`
|
|
1693
|
+
} }, h("div", { style: {
|
|
1694
|
+
...flexRow,
|
|
1695
|
+
gap: 6
|
|
1696
|
+
} }, h("span", { style: {
|
|
1697
|
+
fontSize: 11,
|
|
1698
|
+
fontWeight: 700,
|
|
1699
|
+
color: T.text
|
|
1700
|
+
} }, t("panelItem.defectSection")), det.severity ? chip(String(det.severity), String(det.severity) === "P0" ? T.error : String(det.severity) === "P1" ? T.warn : T.text2) : null), row(t("panelItem.row.defectId"), det.defectId), row(t("panelItem.row.module"), det.module), row(t("panelItem.row.reproduce"), det.reproduce), row(t("panelItem.row.expected"), det.expected), row(t("panelItem.row.actual"), det.actual), row(t("panelItem.row.defectCheck"), det.defectCheck), row(t("panelItem.row.defectCriterion"), det.defectCriterion), row(t("panelItem.row.defectAc"), det.defectAc), det.reproduce || det.expected || det.actual ? null : h("div", { style: {
|
|
1701
|
+
fontSize: 10.5,
|
|
1702
|
+
color: T.warn,
|
|
1703
|
+
lineHeight: 1.5
|
|
1704
|
+
} }, t("panelItem.noDefectDetail"))) : null, h("div", { style: {
|
|
1705
|
+
display: "flex",
|
|
1706
|
+
flexDirection: "column",
|
|
1707
|
+
gap: 3
|
|
1708
|
+
} }, row(t("panelItem.row.req"), det.reqId), row(t("panelItem.row.owner"), det.owner), row(t("panelItem.row.dev"), det.devAssign), row(t("panelItem.row.qa"), det.qaAssign), row(t("panelItem.row.accept"), det.assignBy), row(t("panelItem.row.retries"), det.retries), row(t("panelItem.row.runDocs"), det.runDocs)), det.spec ? h("div", null, h("div", { style: {
|
|
1709
|
+
fontSize: 11,
|
|
1710
|
+
color: T.text2,
|
|
1711
|
+
marginBottom: 3
|
|
1712
|
+
} }, t("panelItem.spec")), h(FoldableText, { text: det.spec })) : null, det.summary ? h("div", null, h("div", { style: {
|
|
1713
|
+
fontSize: 11,
|
|
1714
|
+
color: T.text2,
|
|
1715
|
+
marginBottom: 3
|
|
1716
|
+
} }, t("panelItem.summary")), h(FoldableText, { text: det.summary })) : null, det.artifacts && det.artifacts.length ? h("div", null, h("div", { style: {
|
|
1717
|
+
fontSize: 11,
|
|
1718
|
+
color: T.text2,
|
|
1719
|
+
marginBottom: 4
|
|
1720
|
+
} }, t("panelItem.artifacts", { n: det.artifacts.length })), h("div", { style: {
|
|
1721
|
+
...flexRow,
|
|
1722
|
+
gap: 5
|
|
1723
|
+
} }, det.artifacts.map((a) => h("button", {
|
|
1724
|
+
key: a.name,
|
|
1725
|
+
style: brandBtn,
|
|
1726
|
+
title: t("panelItem.artifactTip", { address: a.address }),
|
|
1727
|
+
onClick: () => openArtifact && openArtifact(a.address, a.name, det.runInfo && det.runInfo.ownerSession || null)
|
|
1728
|
+
}, a.name)))) : muted(t("panelItem.noArtifacts")), det.subtasks && det.subtasks.length ? h("div", null, h("div", { style: {
|
|
1729
|
+
fontSize: 11,
|
|
1730
|
+
color: T.text2,
|
|
1731
|
+
marginBottom: 4
|
|
1732
|
+
} }, t("panelItem.subtasks", { n: det.subtasks.length })), h("div", { style: {
|
|
1733
|
+
display: "flex",
|
|
1734
|
+
flexDirection: "column",
|
|
1735
|
+
gap: 4
|
|
1736
|
+
} }, det.subtasks.map((s) => h("div", {
|
|
1737
|
+
key: s.id,
|
|
1738
|
+
style: {
|
|
1739
|
+
...flexRow,
|
|
1740
|
+
gap: 6,
|
|
1741
|
+
fontSize: 11,
|
|
1742
|
+
padding: "4px 7px",
|
|
1743
|
+
borderRadius: 7,
|
|
1744
|
+
background: T.layer1,
|
|
1745
|
+
border: `1px solid ${T.border}`
|
|
1746
|
+
}
|
|
1747
|
+
}, h("span", { style: {
|
|
1748
|
+
fontFamily: MONO,
|
|
1749
|
+
fontSize: 10,
|
|
1750
|
+
color: T.text2
|
|
1751
|
+
} }, s.id), chip(stText(s.status), stColor(s.status)), h("span", { style: {
|
|
1752
|
+
color: T.text,
|
|
1753
|
+
minWidth: 0,
|
|
1754
|
+
overflow: "hidden",
|
|
1755
|
+
textOverflow: "ellipsis",
|
|
1756
|
+
whiteSpace: "nowrap"
|
|
1757
|
+
} }, s.title), s.failed ? chip(t("common.failed"), T.error) : null)))) : null, det.bugs && det.bugs.length ? h("div", null, h("div", { style: {
|
|
1758
|
+
fontSize: 11,
|
|
1759
|
+
color: T.text2,
|
|
1760
|
+
marginBottom: 4
|
|
1761
|
+
} }, t("panelItem.bugs", { n: det.bugs.length })), h("div", { style: {
|
|
1762
|
+
display: "flex",
|
|
1763
|
+
flexDirection: "column",
|
|
1764
|
+
gap: 4
|
|
1765
|
+
} }, det.bugs.map((b) => h("div", {
|
|
1766
|
+
key: b.id,
|
|
1767
|
+
style: {
|
|
1768
|
+
...flexRow,
|
|
1769
|
+
gap: 6,
|
|
1770
|
+
fontSize: 11,
|
|
1771
|
+
padding: "4px 7px",
|
|
1772
|
+
borderRadius: 7,
|
|
1773
|
+
background: T.layer1,
|
|
1774
|
+
border: `1px solid ${T.border}`
|
|
1775
|
+
}
|
|
1776
|
+
}, h("span", { style: {
|
|
1777
|
+
fontFamily: MONO,
|
|
1778
|
+
fontSize: 10,
|
|
1779
|
+
color: T.text2
|
|
1780
|
+
} }, b.id), b.severity ? chip(String(b.severity), T.warn) : null, chip(stText(b.status), stColor(b.status)), h("span", { style: {
|
|
1781
|
+
color: T.text,
|
|
1782
|
+
minWidth: 0,
|
|
1783
|
+
overflow: "hidden",
|
|
1784
|
+
textOverflow: "ellipsis",
|
|
1785
|
+
whiteSpace: "nowrap"
|
|
1786
|
+
} }, b.title))))) : null, det.events && det.events.length ? h("div", null, h("div", { style: {
|
|
1787
|
+
fontSize: 11,
|
|
1788
|
+
color: T.text2,
|
|
1789
|
+
marginBottom: 4
|
|
1790
|
+
} }, t("panelItem.events", { n: Math.min(det.events.length, 30) })), h("div", { style: {
|
|
1791
|
+
display: "flex",
|
|
1792
|
+
flexDirection: "column",
|
|
1793
|
+
gap: 3,
|
|
1794
|
+
fontFamily: MONO,
|
|
1795
|
+
fontSize: 10,
|
|
1796
|
+
color: T.text2
|
|
1797
|
+
} }, det.events.slice(-30).map((e, i) => h("div", { key: i }, `${fmtTime(e.at)} ${e.from || "—"} → ${e.to || "—"}${e.by ? ` · ${e.by}` : ""}${e.reason ? ` · ${e.reason}` : ""}`)))) : null);
|
|
1798
|
+
}
|
|
1799
|
+
function RunDetailPane({ snap, product, api }) {
|
|
1800
|
+
const [sel, setSel] = react.default.useState(null);
|
|
1801
|
+
const [err, setErr] = react.default.useState(null);
|
|
1802
|
+
react.default.useEffect(() => {
|
|
1803
|
+
setSel(null);
|
|
1804
|
+
setErr(null);
|
|
1805
|
+
}, [snap && snap.id]);
|
|
1806
|
+
if (!snap) return muted(t("detail.runMissing"), { padding: 12 });
|
|
1807
|
+
const stages = snap.stages || [];
|
|
1808
|
+
const totals = stages.reduce((a, s) => {
|
|
1809
|
+
const u = s.usage;
|
|
1810
|
+
if (u) {
|
|
1811
|
+
a.input += u.input || 0;
|
|
1812
|
+
a.cacheRead += u.cacheRead || 0;
|
|
1813
|
+
a.cacheWrite += u.cacheWrite || 0;
|
|
1814
|
+
a.output += u.output || 0;
|
|
1815
|
+
a.calls += u.calls || 0;
|
|
1816
|
+
}
|
|
1817
|
+
return a;
|
|
1818
|
+
}, {
|
|
1819
|
+
input: 0,
|
|
1820
|
+
cacheRead: 0,
|
|
1821
|
+
cacheWrite: 0,
|
|
1822
|
+
output: 0,
|
|
1823
|
+
calls: 0
|
|
1824
|
+
});
|
|
1825
|
+
const openStage = async (s) => {
|
|
1826
|
+
if (!api) return;
|
|
1827
|
+
try {
|
|
1828
|
+
setSel(await api.stageDetail(snap.id, s.seq));
|
|
1829
|
+
setErr(null);
|
|
1830
|
+
} catch (e) {
|
|
1831
|
+
setErr(String(e && e.message || e));
|
|
1832
|
+
}
|
|
1833
|
+
};
|
|
1834
|
+
const logs = (snap.logs || []).slice(-60);
|
|
1835
|
+
return h("div", { style: {
|
|
1836
|
+
padding: "10px 12px",
|
|
1837
|
+
display: "flex",
|
|
1838
|
+
flexDirection: "column",
|
|
1839
|
+
gap: 10,
|
|
1840
|
+
minHeight: 0,
|
|
1841
|
+
overflowY: "auto"
|
|
1842
|
+
} }, h("div", { style: {
|
|
1843
|
+
...flexRow,
|
|
1844
|
+
gap: 6
|
|
1845
|
+
} }, chip(runStatusText(snap.status), stColor(snap.status), { dot: true }), snap.options && snap.options.mode ? chip(String(snap.options.mode), T.text2) : null, h("span", { style: {
|
|
1846
|
+
fontFamily: MONO,
|
|
1847
|
+
fontSize: 10.5,
|
|
1848
|
+
color: T.text2
|
|
1849
|
+
} }, snap.id), product ? h("span", { style: {
|
|
1850
|
+
fontSize: 10,
|
|
1851
|
+
color: T.text2,
|
|
1852
|
+
fontFamily: MONO
|
|
1853
|
+
} }, product) : null), h("div", { style: {
|
|
1854
|
+
fontSize: 12,
|
|
1855
|
+
color: T.text,
|
|
1856
|
+
lineHeight: 1.5
|
|
1857
|
+
} }, snap.requirement || t("runList.noRequirement")), h("div", { style: {
|
|
1858
|
+
...flexRow,
|
|
1859
|
+
gap: 12,
|
|
1860
|
+
fontSize: 10.5,
|
|
1861
|
+
color: T.text2,
|
|
1862
|
+
fontFamily: MONO
|
|
1863
|
+
} }, h("span", null, `${fmtTime(snap.startedAt)}${snap.endedAt ? ` → ${fmtTime(snap.endedAt)}` : " → " + t("detail.endedRunning")} · ${fmtDur(snap.startedAt, snap.endedAt)}`), h("span", null, t("runList.stageProgress", {
|
|
1864
|
+
done: stages.filter((s) => s.status === "done").length,
|
|
1865
|
+
total: stages.length
|
|
1866
|
+
})), h("span", null, t("detail.subagents", { n: snap.agentsStarted || 0 }))), h("div", { style: {
|
|
1867
|
+
...flexRow,
|
|
1868
|
+
gap: 10,
|
|
1869
|
+
fontSize: 10.5,
|
|
1870
|
+
color: T.text2,
|
|
1871
|
+
fontFamily: MONO,
|
|
1872
|
+
padding: "6px 8px",
|
|
1873
|
+
borderRadius: 8,
|
|
1874
|
+
background: `color-mix(in srgb, ${T.layer2} 60%, transparent)`,
|
|
1875
|
+
border: `1px solid ${T.border}`
|
|
1876
|
+
} }, h("span", null, t("token.inputMiss", { v: fmtTokens(totals.input) || "0" })), h("span", null, t("token.inputHit", { v: fmtTokens(totals.cacheRead) || "0" })), h("span", null, t("token.cacheWrite", { v: fmtTokens(totals.cacheWrite) || "0" })), h("span", null, t("token.output", { v: fmtTokens(totals.output) || "0" })), h("span", null, t("common.callsCount", { n: totals.calls })), h("span", null, t("token.total", { v: fmtTokens(totalTokens(totals)) || "0" }))), sectionTitle(t("detail.stages", { n: stages.length })), h("div", { style: {
|
|
1877
|
+
display: "flex",
|
|
1878
|
+
flexDirection: "column",
|
|
1879
|
+
gap: 4
|
|
1880
|
+
} }, stages.map((s) => {
|
|
1881
|
+
const on = sel && Number(sel.seq) === Number(s.seq);
|
|
1882
|
+
const color = stColor(s.status);
|
|
1883
|
+
return h("div", {
|
|
1884
|
+
key: s.seq,
|
|
1885
|
+
onClick: () => openStage(s),
|
|
1886
|
+
style: {
|
|
1887
|
+
padding: "6px 9px",
|
|
1888
|
+
borderRadius: 8,
|
|
1889
|
+
cursor: "pointer",
|
|
1890
|
+
border: `1px solid ${on ? `color-mix(in srgb, ${T.brand} 45%, transparent)` : T.border}`,
|
|
1891
|
+
borderLeft: `3px solid ${color}`,
|
|
1892
|
+
background: on ? `color-mix(in srgb, ${T.brand} 7%, ${T.layer1})` : T.layer1
|
|
1893
|
+
}
|
|
1894
|
+
}, h("div", { style: {
|
|
1895
|
+
...flexRow,
|
|
1896
|
+
gap: 6
|
|
1897
|
+
} }, h("span", { style: {
|
|
1898
|
+
fontFamily: MONO,
|
|
1899
|
+
fontSize: 10,
|
|
1900
|
+
color: T.text2
|
|
1901
|
+
} }, `#${s.seq}`), h("span", null, phaseIconOf(s.phase)), h("span", { style: {
|
|
1902
|
+
fontSize: 11.5,
|
|
1903
|
+
color: T.text,
|
|
1904
|
+
fontWeight: 500
|
|
1905
|
+
} }, stageLabelOf(s)), chip(stText(s.status), color, { dot: true }), s.outcome && s.outcome !== "completed" ? chip(String(s.outcome), stColor(s.outcome)) : null, h("span", { style: {
|
|
1906
|
+
marginLeft: "auto",
|
|
1907
|
+
fontFamily: MONO,
|
|
1908
|
+
fontSize: 10,
|
|
1909
|
+
color: T.text2
|
|
1910
|
+
} }, `${fmtTime(s.startedAt)}${s.endedAt ? ` · ${fmtDur(s.startedAt, s.endedAt)}` : ""}`)), h("div", { style: {
|
|
1911
|
+
...flexRow,
|
|
1912
|
+
gap: 10,
|
|
1913
|
+
marginTop: 3,
|
|
1914
|
+
fontFamily: MONO,
|
|
1915
|
+
fontSize: 10,
|
|
1916
|
+
color: T.text2
|
|
1917
|
+
} }, h("span", null, stageUsageLine(s) || t("common.noUsage")), s.childId ? h("span", { title: s.childId }, t("detail.subagentChip", { id: String(s.childId).slice(0, 14) })) : null), s.summary ? h("div", { style: {
|
|
1918
|
+
fontSize: 10.5,
|
|
1919
|
+
color: T.text2,
|
|
1920
|
+
marginTop: 3,
|
|
1921
|
+
lineHeight: 1.45
|
|
1922
|
+
} }, String(s.summary).slice(0, 200)) : null);
|
|
1923
|
+
})), err ? muted(t("detail.stageFailed", { err }), { color: T.error }) : null, sel ? h("div", { style: {
|
|
1924
|
+
padding: "9px 10px",
|
|
1925
|
+
borderRadius: 9,
|
|
1926
|
+
border: `1px solid ${T.border}`,
|
|
1927
|
+
background: T.layer2,
|
|
1928
|
+
display: "flex",
|
|
1929
|
+
flexDirection: "column",
|
|
1930
|
+
gap: 8
|
|
1931
|
+
} }, h("div", { style: {
|
|
1932
|
+
...flexRow,
|
|
1933
|
+
justifyContent: "space-between"
|
|
1934
|
+
} }, h("div", { style: {
|
|
1935
|
+
...flexRow,
|
|
1936
|
+
gap: 6
|
|
1937
|
+
} }, h("span", { style: {
|
|
1938
|
+
fontSize: 11.5,
|
|
1939
|
+
fontWeight: 700,
|
|
1940
|
+
color: T.text
|
|
1941
|
+
} }, t("detail.stageTitle", { seq: sel.seq })), chip(stText(sel.status), stColor(sel.status))), h("button", {
|
|
1942
|
+
style: panelBtn,
|
|
1943
|
+
onClick: () => setSel(null)
|
|
1944
|
+
}, t("common.collapse"))), h("div", { style: {
|
|
1945
|
+
fontSize: 10.5,
|
|
1946
|
+
color: T.text2,
|
|
1947
|
+
fontFamily: MONO
|
|
1948
|
+
} }, t("token.stageLine", { v: stageUsageLine(sel) || t("common.none") })), sel.verifyEvidence ? h("div", null, h("div", { style: {
|
|
1949
|
+
fontSize: 11,
|
|
1950
|
+
color: T.success,
|
|
1951
|
+
marginBottom: 3
|
|
1952
|
+
} }, t("stage.evidenceTitle")), h("div", { style: {
|
|
1953
|
+
whiteSpace: "pre-wrap",
|
|
1954
|
+
wordBreak: "break-word",
|
|
1955
|
+
fontSize: 11,
|
|
1956
|
+
lineHeight: 1.6,
|
|
1957
|
+
color: T.text,
|
|
1958
|
+
background: `color-mix(in srgb, ${T.layer1} 70%, transparent)`,
|
|
1959
|
+
border: `1px solid ${T.border}`,
|
|
1960
|
+
borderRadius: 8,
|
|
1961
|
+
padding: "7px 9px",
|
|
1962
|
+
maxHeight: 200,
|
|
1963
|
+
overflowY: "auto",
|
|
1964
|
+
fontFamily: MONO
|
|
1965
|
+
} }, sel.verifyEvidence)) : muted(t("stage.evidenceMissingShort"), { color: T.warn }), sel.output ? h("div", null, h("div", { style: {
|
|
1966
|
+
fontSize: 11,
|
|
1967
|
+
color: T.text2,
|
|
1968
|
+
marginBottom: 3
|
|
1969
|
+
} }, t("detail.output")), h(FoldableText, {
|
|
1970
|
+
text: sel.output,
|
|
1971
|
+
charLimit: 400,
|
|
1972
|
+
lineLimit: 8,
|
|
1973
|
+
style: {
|
|
1974
|
+
fontFamily: MONO,
|
|
1975
|
+
fontSize: 11
|
|
1976
|
+
}
|
|
1977
|
+
})) : null, sel.attempts && sel.attempts.length > 1 ? h("div", null, h("div", { style: {
|
|
1978
|
+
fontSize: 11,
|
|
1979
|
+
color: T.text2,
|
|
1980
|
+
marginBottom: 3
|
|
1981
|
+
} }, t("detail.attempts", { n: sel.attempts.length })), h("div", { style: {
|
|
1982
|
+
display: "flex",
|
|
1983
|
+
flexDirection: "column",
|
|
1984
|
+
gap: 3
|
|
1985
|
+
} }, sel.attempts.map((a) => h("div", {
|
|
1986
|
+
key: a.seq,
|
|
1987
|
+
style: {
|
|
1988
|
+
...flexRow,
|
|
1989
|
+
gap: 6,
|
|
1990
|
+
fontSize: 10.5,
|
|
1991
|
+
fontFamily: MONO,
|
|
1992
|
+
color: T.text2
|
|
1993
|
+
}
|
|
1994
|
+
}, h("span", null, `#${a.seq}`), chip(stText(a.status), stColor(a.status)), a.outcome ? h("span", null, a.outcome) : null, h("span", { style: { marginLeft: "auto" } }, `${fmtTime(a.startedAt)} · ${fmtDur(a.startedAt, a.endedAt)}`))))) : null) : null, logs.length ? h("div", null, sectionTitle(t("detail.logs", { n: logs.length })), h("div", { style: {
|
|
1995
|
+
display: "flex",
|
|
1996
|
+
flexDirection: "column",
|
|
1997
|
+
gap: 2,
|
|
1998
|
+
fontFamily: MONO,
|
|
1999
|
+
fontSize: 10,
|
|
2000
|
+
color: T.text2,
|
|
2001
|
+
maxHeight: 220,
|
|
2002
|
+
overflowY: "auto"
|
|
2003
|
+
} }, logs.map((l, i) => h("div", {
|
|
2004
|
+
key: i,
|
|
2005
|
+
style: { color: l.level === "error" ? T.error : l.level === "warn" ? T.warn : T.text2 }
|
|
2006
|
+
}, `${fmtTime(l.t)} [${l.level}] ${l.message}`)))) : null);
|
|
2007
|
+
}
|
|
2008
|
+
/** 右栏 run 详情 tab 正文(地址来自宿主导航记录;host 只认自己的地址格式)。
|
|
2009
|
+
*
|
|
2010
|
+
* ⚠️ prop 名:宿主渲染器把 slot 的 `hooks: { tabInfo }` 绑成 **`useTabInfo`**(约定 `hooks.<name>` → `use<Name>`;
|
|
2011
|
+
* 官方 `ui-sidebar-documentpreview/TextPreview.tsx` 即用 `useTabInfo`)。2026-09-11 实锤:写成 `tabInfo`
|
|
2012
|
+
* 会恒 undefined → 地址取不到、错误也没设 → 永远停在「读取 run 详情中…」。
|
|
2013
|
+
*/
|
|
2014
|
+
function RunDetailTab(props) {
|
|
2015
|
+
const readTab = props && (typeof props.useTabInfo === "function" ? props.useTabInfo : typeof props.tabInfo === "function" ? props.tabInfo : null);
|
|
2016
|
+
let info = null;
|
|
2017
|
+
try {
|
|
2018
|
+
info = readTab ? readTab() : null;
|
|
2019
|
+
} catch (e) {
|
|
2020
|
+
info = null;
|
|
2021
|
+
}
|
|
2022
|
+
const tab = info && info.tab ? info.tab : null;
|
|
2023
|
+
const address = tab && tab.navigation && tab.navigation.address || tab && tab.contentId || null;
|
|
2024
|
+
const parsed = parseRunAddress(address);
|
|
2025
|
+
const remote = props && props.remote;
|
|
2026
|
+
const [snap, setSnap] = react.default.useState(null);
|
|
2027
|
+
const [err, setErr] = react.default.useState(null);
|
|
2028
|
+
const [nonce, setNonce] = react.default.useState(0);
|
|
2029
|
+
const product = parsed ? parsed.product : null;
|
|
2030
|
+
const runId = parsed ? parsed.runId : null;
|
|
2031
|
+
const api = react.default.useMemo(() => remote && product ? productApi(remote, product) : null, [remote, product]);
|
|
2032
|
+
const [, bump] = react.default.useReducer((n) => n + 1, 0);
|
|
2033
|
+
react.default.useEffect(() => {
|
|
2034
|
+
if (address) return void 0;
|
|
2035
|
+
let tries = 0;
|
|
2036
|
+
const timer = setInterval(() => {
|
|
2037
|
+
tries += 1;
|
|
2038
|
+
bump();
|
|
2039
|
+
if (tries >= 12) clearInterval(timer);
|
|
2040
|
+
}, 150);
|
|
2041
|
+
return () => clearInterval(timer);
|
|
2042
|
+
}, [address]);
|
|
2043
|
+
react.default.useEffect(() => {
|
|
2044
|
+
let alive = true;
|
|
2045
|
+
if (!runId) {
|
|
2046
|
+
setSnap(null);
|
|
2047
|
+
setErr(address ? t("tab.resolveFailed", { address }) : readTab ? t("tab.readingAddress") : t("tab.noHook"));
|
|
2048
|
+
return;
|
|
2049
|
+
}
|
|
2050
|
+
if (!api) {
|
|
2051
|
+
setSnap(null);
|
|
2052
|
+
setErr(t("tab.remoteUnavailable"));
|
|
2053
|
+
return;
|
|
2054
|
+
}
|
|
2055
|
+
api.runDetail(runId).then((v) => {
|
|
2056
|
+
if (alive) {
|
|
2057
|
+
setSnap(v);
|
|
2058
|
+
setErr(null);
|
|
2059
|
+
}
|
|
2060
|
+
}, (e) => {
|
|
2061
|
+
if (alive) setErr(String(e && e.message || e));
|
|
2062
|
+
});
|
|
2063
|
+
const timer = setInterval(() => {
|
|
2064
|
+
api.runDetail(runId).then((v) => {
|
|
2065
|
+
if (!alive) return;
|
|
2066
|
+
setSnap(v);
|
|
2067
|
+
if (!v || !(v.status === "running" || v.status === "pending")) clearInterval(timer);
|
|
2068
|
+
}, () => {});
|
|
2069
|
+
}, 5e3);
|
|
2070
|
+
return () => {
|
|
2071
|
+
alive = false;
|
|
2072
|
+
clearInterval(timer);
|
|
2073
|
+
};
|
|
2074
|
+
}, [
|
|
2075
|
+
api,
|
|
2076
|
+
runId,
|
|
2077
|
+
address,
|
|
2078
|
+
nonce,
|
|
2079
|
+
readTab
|
|
2080
|
+
]);
|
|
2081
|
+
if (err && !snap) {
|
|
2082
|
+
const pending = !address && !!readTab;
|
|
2083
|
+
return h("div", { style: {
|
|
2084
|
+
padding: 12,
|
|
2085
|
+
display: "flex",
|
|
2086
|
+
flexDirection: "column",
|
|
2087
|
+
gap: 8
|
|
2088
|
+
} }, muted(pending ? t("tab.readingAddress") : err, { color: pending ? T.text2 : T.error }), pending ? null : h("button", {
|
|
2089
|
+
style: panelBtn,
|
|
2090
|
+
onClick: () => {
|
|
2091
|
+
setErr(null);
|
|
2092
|
+
setNonce((n) => n + 1);
|
|
2093
|
+
}
|
|
2094
|
+
}, t("common.retry")));
|
|
2095
|
+
}
|
|
2096
|
+
if (!snap) return muted(t("tab.readingRun"), { padding: 12 });
|
|
2097
|
+
return h(RunDetailPane, {
|
|
2098
|
+
snap,
|
|
2099
|
+
product,
|
|
2100
|
+
api
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
function productApi(remote, product) {
|
|
2104
|
+
return {
|
|
2105
|
+
view: async () => unwrap$1(await remote.productView(product), "productView"),
|
|
2106
|
+
runDetail: async (runId) => unwrap$1(await remote.productRunDetail(product, runId), "productRunDetail"),
|
|
2107
|
+
stageDetail: async (runId, seq) => unwrap$1(await remote.productStageDetail(product, runId, seq), "productStageDetail"),
|
|
2108
|
+
itemDetail: async (kind, id, sessionId) => unwrap$1(await remote.productItemDetail(product, kind, id, sessionId), "productItemDetail")
|
|
2109
|
+
};
|
|
2110
|
+
}
|
|
2111
|
+
/** 全局面板 props:root scope 标准 props(useSessions 取当前会话)+ 插件注入(remote/打开回调)。 */
|
|
2112
|
+
function GlobalPanel(props) {
|
|
2113
|
+
const remote = props.remote;
|
|
2114
|
+
const useSessions = props.useSessions;
|
|
2115
|
+
const currentSessionId = (() => {
|
|
2116
|
+
try {
|
|
2117
|
+
return useSessions ? useSessions((s) => s.current) : void 0;
|
|
2118
|
+
} catch (e) {
|
|
2119
|
+
return;
|
|
2120
|
+
}
|
|
2121
|
+
})();
|
|
2122
|
+
const [state, setState] = react.default.useState({
|
|
2123
|
+
products: [],
|
|
2124
|
+
current: null,
|
|
2125
|
+
view: null,
|
|
2126
|
+
err: null,
|
|
2127
|
+
busy: false
|
|
2128
|
+
});
|
|
2129
|
+
const [detail, setDetail] = react.default.useState(null);
|
|
2130
|
+
const [hint, setHint] = react.default.useState(null);
|
|
2131
|
+
const [runsExpanded, setRunsExpanded] = react.default.useState(false);
|
|
2132
|
+
const [panelTab, setPanelTab] = react.default.useState("run");
|
|
2133
|
+
const [viewTick, setViewTick] = react.default.useState(0);
|
|
2134
|
+
const [runFilter, setRunFilter] = react.default.useState([]);
|
|
2135
|
+
/**
|
|
2136
|
+
* 右侧栏的**会话内容**宿主只在「对话被选中」时渲染(`RightbarRoot` 门控 `activePanelId === null`),
|
|
2137
|
+
* 而全局面板占着 `main` → seat 不存在 → `ctx.sidebarRight` 没有 binding(抛 `no session surface
|
|
2138
|
+
* is mounted`,2026-09-11 用户实测 tf-mtvrsakj-l2vj5u)。所以:先切回对话,再小步重试
|
|
2139
|
+
* (seat 在切换后的 effect 里才 `bindService`);彻底失败才降级,并把原因显示在面板里。
|
|
2140
|
+
*/
|
|
2141
|
+
const openInConversationRightbar = (address, label, fallback) => {
|
|
2142
|
+
const attempt = (quiet) => !!(props.openResource && address && props.openResource(address, label, quiet));
|
|
2143
|
+
if (attempt(false)) return;
|
|
2144
|
+
if (!address) {
|
|
2145
|
+
setHint(t("panel.hintNoAddress", { label }));
|
|
2146
|
+
if (fallback) fallback();
|
|
2147
|
+
return;
|
|
2148
|
+
}
|
|
2149
|
+
try {
|
|
2150
|
+
if (props.layout && typeof props.layout.selectPanel === "function") props.layout.selectPanel(null);
|
|
2151
|
+
} catch (e) {}
|
|
2152
|
+
let tries = 0;
|
|
2153
|
+
const tick = () => {
|
|
2154
|
+
tries += 1;
|
|
2155
|
+
if (attempt(true)) return;
|
|
2156
|
+
if (tries < 12) {
|
|
2157
|
+
setTimeout(tick, 120);
|
|
2158
|
+
return;
|
|
2159
|
+
}
|
|
2160
|
+
setHint(t("panel.hintRightbarFailed", { label }) + (fallback ? t("panel.hintInlineSuffix") : ""));
|
|
2161
|
+
if (fallback) fallback();
|
|
2162
|
+
};
|
|
2163
|
+
setTimeout(tick, 140);
|
|
2164
|
+
};
|
|
2165
|
+
/**
|
|
2166
|
+
* **跳到资源所属的会话,再在那个会话的右栏打开**(全局面板的正确语义)。
|
|
2167
|
+
* 右侧栏是会话级的:从全局面板看 tetris 的 run 却把 tab 挂到"用户当前所在会话"上没有意义
|
|
2168
|
+
* (用户 2026-09-11 提出)。所以先 `sessions.open(ownerSession)`,等当前会话真的切过去、
|
|
2169
|
+
* 且对话 seat 挂载 bind 之后再 openResource(两者都要等,故小步重试 + 就绪判据)。
|
|
2170
|
+
* @param target.ownerSession - 资源所属会话(run 的发起会话 / 产物地址里的会话)
|
|
2171
|
+
* @param target.address - host 生成的 dsh-resource 地址
|
|
2172
|
+
* @param target.label - 提示用的名字
|
|
2173
|
+
* @param target.fallback - 没有 ownerSession 或跳转失败时的降级(通常是面板内联详情)
|
|
2174
|
+
*/
|
|
2175
|
+
const goOwnerSessionAndOpen = (target) => {
|
|
2176
|
+
const { ownerSession, address, label, fallback } = target || {};
|
|
2177
|
+
const sessions = props.sessions;
|
|
2178
|
+
if (!ownerSession || !sessions || typeof sessions.open !== "function") {
|
|
2179
|
+
openInConversationRightbar(address, label, fallback);
|
|
2180
|
+
return;
|
|
2181
|
+
}
|
|
2182
|
+
try {
|
|
2183
|
+
sessions.open(ownerSession);
|
|
2184
|
+
} catch (e) {
|
|
2185
|
+
setHint(t("panel.hintSessionGone", { sid: String(ownerSession).slice(0, 8) }));
|
|
2186
|
+
if (fallback) fallback();
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
try {
|
|
2190
|
+
if (props.layout && typeof props.layout.selectPanel === "function") props.layout.selectPanel(null);
|
|
2191
|
+
} catch (e) {}
|
|
2192
|
+
let tries = 0;
|
|
2193
|
+
const tick = () => {
|
|
2194
|
+
tries += 1;
|
|
2195
|
+
let nowCurrent = null;
|
|
2196
|
+
try {
|
|
2197
|
+
nowCurrent = sessions.list && sessions.list.getSnapshot ? sessions.list.getSnapshot().current : null;
|
|
2198
|
+
} catch (e) {
|
|
2199
|
+
nowCurrent = null;
|
|
2200
|
+
}
|
|
2201
|
+
if ((nowCurrent === ownerSession || tries >= 6) && props.openResource && address && props.openResource(address, label, tries < 6)) return;
|
|
2202
|
+
if (tries < 14) {
|
|
2203
|
+
setTimeout(tick, 130);
|
|
2204
|
+
return;
|
|
2205
|
+
}
|
|
2206
|
+
setHint(t("panel.hintSwitchedNoRightbar", { label }));
|
|
2207
|
+
};
|
|
2208
|
+
setTimeout(tick, 140);
|
|
2209
|
+
};
|
|
2210
|
+
const loadProducts = react.default.useCallback(async (preferKey) => {
|
|
2211
|
+
if (!remote || typeof remote.products !== "function") {
|
|
2212
|
+
setState((s) => ({
|
|
2213
|
+
...s,
|
|
2214
|
+
err: t("panel.remoteProductsUnavailable")
|
|
2215
|
+
}));
|
|
2216
|
+
return;
|
|
2217
|
+
}
|
|
2218
|
+
setState((s) => ({
|
|
2219
|
+
...s,
|
|
2220
|
+
busy: true
|
|
2221
|
+
}));
|
|
2222
|
+
try {
|
|
2223
|
+
const v = unwrap$1(await remote.products(currentSessionId || null), "products") || {};
|
|
2224
|
+
const list = v.products || [];
|
|
2225
|
+
const key = preferKey || state.current || (v.current && list.some((p) => p.key === v.current) ? v.current : list[0] && list[0].key || null);
|
|
2226
|
+
setState({
|
|
2227
|
+
products: list,
|
|
2228
|
+
current: key,
|
|
2229
|
+
view: key === state.current ? state.view : null,
|
|
2230
|
+
err: null,
|
|
2231
|
+
busy: false
|
|
2232
|
+
});
|
|
2233
|
+
setViewTick((t) => t + 1);
|
|
2234
|
+
} catch (e) {
|
|
2235
|
+
setState((s) => ({
|
|
2236
|
+
...s,
|
|
2237
|
+
busy: false,
|
|
2238
|
+
err: String(e && e.message || e)
|
|
2239
|
+
}));
|
|
2240
|
+
}
|
|
2241
|
+
}, [
|
|
2242
|
+
remote,
|
|
2243
|
+
currentSessionId,
|
|
2244
|
+
state.current,
|
|
2245
|
+
state.view
|
|
2246
|
+
]);
|
|
2247
|
+
const loadView = react.default.useCallback(async (key, silent) => {
|
|
2248
|
+
if (!remote || !key) return;
|
|
2249
|
+
const api = productApi(remote, key);
|
|
2250
|
+
try {
|
|
2251
|
+
const v = await api.view();
|
|
2252
|
+
setState((s) => ({
|
|
2253
|
+
...s,
|
|
2254
|
+
view: v,
|
|
2255
|
+
err: null
|
|
2256
|
+
}));
|
|
2257
|
+
} catch (e) {
|
|
2258
|
+
if (!silent) setState((s) => ({
|
|
2259
|
+
...s,
|
|
2260
|
+
err: String(e && e.message || e)
|
|
2261
|
+
}));
|
|
2262
|
+
}
|
|
2263
|
+
}, [remote]);
|
|
2264
|
+
/** 选中产品线:**同一个**产品线 = 刷新(保留现有视图,不清空 → 不会卡"读取中"且不闪);不同产品线 = 清空 + 重新拉。 */
|
|
2265
|
+
const selectProduct = (k) => {
|
|
2266
|
+
closeDetail();
|
|
2267
|
+
setRunsExpanded(false);
|
|
2268
|
+
setRunFilter([]);
|
|
2269
|
+
setState((s) => ({
|
|
2270
|
+
...s,
|
|
2271
|
+
current: k,
|
|
2272
|
+
view: s.current === k ? s.view : null
|
|
2273
|
+
}));
|
|
2274
|
+
setViewTick((t) => t + 1);
|
|
2275
|
+
};
|
|
2276
|
+
/** run 状态筛选开关(多选)。 */
|
|
2277
|
+
const toggleRunStatus = (st) => setRunFilter((cur) => cur.indexOf(st) === -1 ? cur.concat([st]) : cur.filter((x) => x !== st));
|
|
2278
|
+
react.default.useEffect(() => {
|
|
2279
|
+
loadProducts();
|
|
2280
|
+
}, [currentSessionId]);
|
|
2281
|
+
react.default.useEffect(() => {
|
|
2282
|
+
if (state.current) loadView(state.current);
|
|
2283
|
+
}, [
|
|
2284
|
+
state.current,
|
|
2285
|
+
viewTick,
|
|
2286
|
+
loadView
|
|
2287
|
+
]);
|
|
2288
|
+
react.default.useEffect(() => {
|
|
2289
|
+
if (!(state.view && state.view.runs || []).some((r) => r.status === "running" || r.status === "pending") || !state.current) return void 0;
|
|
2290
|
+
const timer = setInterval(() => {
|
|
2291
|
+
loadView(state.current, true);
|
|
2292
|
+
}, 8e3);
|
|
2293
|
+
return () => clearInterval(timer);
|
|
2294
|
+
}, [
|
|
2295
|
+
state.current,
|
|
2296
|
+
state.view,
|
|
2297
|
+
loadView
|
|
2298
|
+
]);
|
|
2299
|
+
const api = state.current && remote ? productApi(remote, state.current) : null;
|
|
2300
|
+
const openItem = async (kind, id) => {
|
|
2301
|
+
if (!api) return;
|
|
2302
|
+
try {
|
|
2303
|
+
setDetail({
|
|
2304
|
+
kind: "item",
|
|
2305
|
+
data: await api.itemDetail(kind, id, currentSessionId || null)
|
|
2306
|
+
});
|
|
2307
|
+
} catch (e) {
|
|
2308
|
+
setState((s) => ({
|
|
2309
|
+
...s,
|
|
2310
|
+
err: String(e && e.message || e)
|
|
2311
|
+
}));
|
|
2312
|
+
}
|
|
2313
|
+
};
|
|
2314
|
+
const showInline = async (r) => {
|
|
2315
|
+
if (!api) return;
|
|
2316
|
+
try {
|
|
2317
|
+
const snap = await api.runDetail(r.id);
|
|
2318
|
+
setDetail({
|
|
2319
|
+
kind: "run",
|
|
2320
|
+
data: snap,
|
|
2321
|
+
run: r
|
|
2322
|
+
});
|
|
2323
|
+
} catch (e) {
|
|
2324
|
+
setState((s) => ({
|
|
2325
|
+
...s,
|
|
2326
|
+
err: String(e && e.message || e)
|
|
2327
|
+
}));
|
|
2328
|
+
}
|
|
2329
|
+
};
|
|
2330
|
+
const closeDetail = () => setDetail(null);
|
|
2331
|
+
const openRun = (r) => {
|
|
2332
|
+
goOwnerSessionAndOpen({
|
|
2333
|
+
ownerSession: r.ownerSession,
|
|
2334
|
+
address: r.address,
|
|
2335
|
+
label: r.id,
|
|
2336
|
+
fallback: () => {
|
|
2337
|
+
showInline(r);
|
|
2338
|
+
}
|
|
2339
|
+
});
|
|
2340
|
+
};
|
|
2341
|
+
const openArtifactInPanel = (address, name, ownerSession) => {
|
|
2342
|
+
goOwnerSessionAndOpen({
|
|
2343
|
+
ownerSession,
|
|
2344
|
+
address,
|
|
2345
|
+
label: name
|
|
2346
|
+
});
|
|
2347
|
+
};
|
|
2348
|
+
const view = state.view;
|
|
2349
|
+
const product = view && view.product;
|
|
2350
|
+
const runs = view && view.runs || [];
|
|
2351
|
+
const detailOpen = !!detail;
|
|
2352
|
+
const runSel = runFilter || [];
|
|
2353
|
+
const runSelSet = new Set(runSel);
|
|
2354
|
+
const runFiltering = runSel.length > 0;
|
|
2355
|
+
const runsMatched = runFiltering ? runs.filter((r) => runSelSet.has(r.status)) : runs;
|
|
2356
|
+
const runStatusCounts = {};
|
|
2357
|
+
for (const r of runs) runStatusCounts[r.status] = (runStatusCounts[r.status] || 0) + 1;
|
|
2358
|
+
const headRuns = runsMatched.slice(0, RUN_PREVIEW);
|
|
2359
|
+
const headIds = new Set(headRuns.map((r) => r.id));
|
|
2360
|
+
const pinnedActive = runsExpanded ? [] : runsMatched.filter((r) => (r.status === "running" || r.status === "pending") && !headIds.has(r.id));
|
|
2361
|
+
const visibleRuns = runsExpanded || runsMatched.length <= RUN_PREVIEW ? runsMatched : pinnedActive.concat(headRuns);
|
|
2362
|
+
const bl = view && view.backlog;
|
|
2363
|
+
const backlogCount = bl ? (bl.requirements || []).length + (bl.tasks || []).filter((t) => t.type !== "subtask").length + (bl.bugs || []).length : 0;
|
|
2364
|
+
/** 主区标签按钮(选中态用品牌色下划线)。 */
|
|
2365
|
+
const panelTabBtn = (key, label) => h("button", {
|
|
2366
|
+
key,
|
|
2367
|
+
onClick: () => setPanelTab(key),
|
|
2368
|
+
style: {
|
|
2369
|
+
font: "inherit",
|
|
2370
|
+
fontSize: 12,
|
|
2371
|
+
fontWeight: panelTab === key ? 700 : 500,
|
|
2372
|
+
padding: "6px 12px",
|
|
2373
|
+
marginBottom: -1,
|
|
2374
|
+
cursor: "pointer",
|
|
2375
|
+
whiteSpace: "nowrap",
|
|
2376
|
+
background: "transparent",
|
|
2377
|
+
border: "none",
|
|
2378
|
+
borderBottom: `2px solid ${panelTab === key ? T.brand : "transparent"}`,
|
|
2379
|
+
color: panelTab === key ? T.text : T.text2
|
|
2380
|
+
}
|
|
2381
|
+
}, label);
|
|
2382
|
+
return h("div", { style: {
|
|
2383
|
+
height: "100%",
|
|
2384
|
+
minHeight: 0,
|
|
2385
|
+
display: "flex",
|
|
2386
|
+
flexDirection: "column",
|
|
2387
|
+
background: T.bg,
|
|
2388
|
+
color: T.text,
|
|
2389
|
+
fontFamily: SANS,
|
|
2390
|
+
fontSize: 12
|
|
2391
|
+
} }, h("div", { style: {
|
|
2392
|
+
...flexRow,
|
|
2393
|
+
justifyContent: "space-between",
|
|
2394
|
+
padding: "10px 14px",
|
|
2395
|
+
borderBottom: `1px solid ${T.border}`
|
|
2396
|
+
} }, h("div", { style: {
|
|
2397
|
+
...flexRow,
|
|
2398
|
+
gap: 8
|
|
2399
|
+
} }, h("span", { style: {
|
|
2400
|
+
fontSize: 13,
|
|
2401
|
+
fontWeight: 700
|
|
2402
|
+
} }, t("panel.title")), h("span", { style: {
|
|
2403
|
+
fontSize: 11,
|
|
2404
|
+
color: T.text2
|
|
2405
|
+
} }, t("panel.subtitle")), currentSessionId ? h("span", { style: {
|
|
2406
|
+
fontSize: 10,
|
|
2407
|
+
color: T.text2,
|
|
2408
|
+
fontFamily: MONO
|
|
2409
|
+
} }, t("panel.currentSession", { sid: String(currentSessionId).slice(0, 8) })) : h("span", { style: {
|
|
2410
|
+
fontSize: 10,
|
|
2411
|
+
color: T.text2
|
|
2412
|
+
} }, t("panel.noSession"))), h("div", { style: {
|
|
2413
|
+
...flexRow,
|
|
2414
|
+
gap: 6
|
|
2415
|
+
} }, product ? h("button", {
|
|
2416
|
+
style: panelBtn,
|
|
2417
|
+
onClick: () => loadView(state.current)
|
|
2418
|
+
}, t("panel.reload")) : null, h("button", {
|
|
2419
|
+
style: panelBtn,
|
|
2420
|
+
title: t("panel.backToChatTip"),
|
|
2421
|
+
onClick: () => {
|
|
2422
|
+
try {
|
|
2423
|
+
const layout = props.layout;
|
|
2424
|
+
if (layout && layout.selectPanel) layout.selectPanel(null);
|
|
2425
|
+
} catch (e) {}
|
|
2426
|
+
}
|
|
2427
|
+
}, t("panel.backToChat")))), state.err ? h("div", { style: {
|
|
2428
|
+
padding: "6px 14px",
|
|
2429
|
+
fontSize: 11,
|
|
2430
|
+
color: T.error,
|
|
2431
|
+
borderBottom: `1px solid ${T.border}`
|
|
2432
|
+
} }, state.err) : null, hint ? h("div", { style: {
|
|
2433
|
+
...flexRow,
|
|
2434
|
+
justifyContent: "space-between",
|
|
2435
|
+
gap: 8,
|
|
2436
|
+
padding: "6px 14px",
|
|
2437
|
+
fontSize: 11,
|
|
2438
|
+
color: T.warn,
|
|
2439
|
+
borderBottom: `1px solid ${T.border}`,
|
|
2440
|
+
background: `color-mix(in srgb, ${T.warn} 8%, transparent)`
|
|
2441
|
+
} }, h("span", null, hint), h("button", {
|
|
2442
|
+
style: panelBtn,
|
|
2443
|
+
onClick: () => setHint(null)
|
|
2444
|
+
}, t("common.gotIt"))) : null, h("div", { style: {
|
|
2445
|
+
flex: 1,
|
|
2446
|
+
minHeight: 0,
|
|
2447
|
+
display: "flex",
|
|
2448
|
+
position: "relative"
|
|
2449
|
+
} }, h(ProductRail, {
|
|
2450
|
+
products: state.products,
|
|
2451
|
+
current: state.current,
|
|
2452
|
+
loadingKey: state.view ? null : state.current,
|
|
2453
|
+
busy: state.busy,
|
|
2454
|
+
onRefresh: () => loadProducts(state.current),
|
|
2455
|
+
onSelect: selectProduct
|
|
2456
|
+
}), h("div", { style: {
|
|
2457
|
+
flex: 1,
|
|
2458
|
+
minWidth: 0,
|
|
2459
|
+
minHeight: 0,
|
|
2460
|
+
display: "flex",
|
|
2461
|
+
flexDirection: "column"
|
|
2462
|
+
} }, !state.current ? h("div", { style: { padding: "12px 14px" } }, muted(t("panel.pickProduct"))) : !view ? h("div", { style: { padding: "12px 14px" } }, muted(t("panel.loadingView"))) : h(react.default.Fragment, null, h("div", { style: {
|
|
2463
|
+
...flexRow,
|
|
2464
|
+
justifyContent: "space-between",
|
|
2465
|
+
gap: 10,
|
|
2466
|
+
padding: "12px 14px 6px"
|
|
2467
|
+
} }, h("div", { style: { minWidth: 0 } }, h("div", { style: {
|
|
2468
|
+
fontSize: 15,
|
|
2469
|
+
fontWeight: 700,
|
|
2470
|
+
color: T.text
|
|
2471
|
+
} }, product.title || product.key), h("div", { style: {
|
|
2472
|
+
fontSize: 10.5,
|
|
2473
|
+
color: T.text2,
|
|
2474
|
+
fontFamily: MONO,
|
|
2475
|
+
marginTop: 2,
|
|
2476
|
+
overflow: "hidden",
|
|
2477
|
+
textOverflow: "ellipsis",
|
|
2478
|
+
whiteSpace: "nowrap"
|
|
2479
|
+
} }, product.path || product.key)), h("div", { style: {
|
|
2480
|
+
...flexRow,
|
|
2481
|
+
gap: 6,
|
|
2482
|
+
flex: "0 0 auto"
|
|
2483
|
+
} }, chip(t("panel.runChip", { n: product.totalRuns }), T.text2), product.activeRuns > 0 ? chip(t("panel.activeChip", { n: product.activeRuns }), T.brand, { dot: true }) : null, product.lastVerdict ? chip(t("panel.verdictChip", { v: product.lastVerdict }), stColor("accepted")) : null)), h("div", { style: {
|
|
2484
|
+
display: "flex",
|
|
2485
|
+
alignItems: "flex-end",
|
|
2486
|
+
gap: 2,
|
|
2487
|
+
padding: "0 14px",
|
|
2488
|
+
borderBottom: `1px solid ${T.border}`
|
|
2489
|
+
} }, panelTabBtn("run", t("panel.tabRuns", { n: runs.length })), panelTabBtn("backlog", t("panel.tabBacklog", { n: backlogCount })), h("div", { style: {
|
|
2490
|
+
marginLeft: "auto",
|
|
2491
|
+
...flexRow,
|
|
2492
|
+
gap: 6,
|
|
2493
|
+
paddingBottom: 7
|
|
2494
|
+
} }, panelTab === "run" && !runsExpanded && pinnedActive.length > 0 ? chip(t("panel.pinnedActive", { n: pinnedActive.length }), T.brand, { dot: true }) : null, panelTab === "run" && runsMatched.length > RUN_PREVIEW ? h("button", {
|
|
2495
|
+
style: panelBtn,
|
|
2496
|
+
onClick: () => setRunsExpanded((v) => !v)
|
|
2497
|
+
}, runsExpanded ? t("panel.showRecent", { n: RUN_PREVIEW }) : t("panel.showAll", { n: runsMatched.length })) : null)), h("div", { style: {
|
|
2498
|
+
flex: 1,
|
|
2499
|
+
minHeight: 0,
|
|
2500
|
+
overflowY: "auto",
|
|
2501
|
+
overflowX: "hidden",
|
|
2502
|
+
padding: "12px 14px 18px"
|
|
2503
|
+
} }, panelTab === "run" ? h(react.default.Fragment, null, h("div", { style: {
|
|
2504
|
+
...flexRow,
|
|
2505
|
+
gap: 6,
|
|
2506
|
+
marginBottom: 8
|
|
2507
|
+
} }, ...RUN_STATUS_ORDER.filter((st) => runStatusCounts[st]).map((st) => filterChip(`${runStatusText(st)} ${runStatusCounts[st]}`, stColor(st), runSelSet.has(st), () => toggleRunStatus(st))), runFiltering ? h("button", {
|
|
2508
|
+
style: panelBtn,
|
|
2509
|
+
title: t("panel.clearRunFilterTip"),
|
|
2510
|
+
onClick: () => setRunFilter([])
|
|
2511
|
+
}, t("panel.filtered", {
|
|
2512
|
+
sel: runSel.length,
|
|
2513
|
+
shown: runsMatched.length,
|
|
2514
|
+
total: runs.length
|
|
2515
|
+
})) : null), visibleRuns.length ? h(RunList, {
|
|
2516
|
+
runs: visibleRuns,
|
|
2517
|
+
activeRunId: detail && detail.kind === "run" && detail.run ? detail.run.id : null,
|
|
2518
|
+
onOpenRun: openRun,
|
|
2519
|
+
onInlineRun: showInline
|
|
2520
|
+
}) : muted(t("panel.emptyRunFilter"), { fontSize: 10.5 }), muted(t("panel.runHint"), {
|
|
2521
|
+
fontSize: 10,
|
|
2522
|
+
marginTop: 8
|
|
2523
|
+
})) : h(react.default.Fragment, null, muted(t("panel.boardHint"), {
|
|
2524
|
+
fontSize: 10,
|
|
2525
|
+
marginBottom: 8
|
|
2526
|
+
}), h(BacklogGroups, {
|
|
2527
|
+
backlog: view.backlog,
|
|
2528
|
+
onOpen: openItem,
|
|
2529
|
+
productKey: state.current
|
|
2530
|
+
}))))), detailOpen ? h("div", { style: {
|
|
2531
|
+
position: "absolute",
|
|
2532
|
+
top: 8,
|
|
2533
|
+
right: 12,
|
|
2534
|
+
bottom: 8,
|
|
2535
|
+
width: 440,
|
|
2536
|
+
zIndex: 9,
|
|
2537
|
+
overflowY: "auto",
|
|
2538
|
+
overflowX: "hidden",
|
|
2539
|
+
borderRadius: 12,
|
|
2540
|
+
border: `1px solid ${T.border}`,
|
|
2541
|
+
background: `color-mix(in srgb, ${T.layer1} 96%, transparent)`,
|
|
2542
|
+
backdropFilter: "blur(12px)",
|
|
2543
|
+
boxShadow: "0 14px 44px rgba(0,0,0,.28)"
|
|
2544
|
+
} }, detail && detail.kind === "item" ? h(ItemDetailPane, {
|
|
2545
|
+
det: detail.data,
|
|
2546
|
+
openArtifact: openArtifactInPanel,
|
|
2547
|
+
onClose: closeDetail
|
|
2548
|
+
}) : h("div", null, h("div", { style: {
|
|
2549
|
+
...flexRow,
|
|
2550
|
+
justifyContent: "space-between",
|
|
2551
|
+
padding: "10px 12px 0"
|
|
2552
|
+
} }, h("span", { style: {
|
|
2553
|
+
fontSize: 11.5,
|
|
2554
|
+
fontWeight: 700,
|
|
2555
|
+
color: T.text
|
|
2556
|
+
} }, t("panel.runDetail")), h("div", { style: {
|
|
2557
|
+
...flexRow,
|
|
2558
|
+
gap: 6
|
|
2559
|
+
} }, detail && detail.data && detail.data.address ? h("button", {
|
|
2560
|
+
style: brandBtn,
|
|
2561
|
+
title: t("panel.goOwnerSessionTip"),
|
|
2562
|
+
onClick: () => goOwnerSessionAndOpen({
|
|
2563
|
+
ownerSession: detail.data.ownerSession,
|
|
2564
|
+
address: detail.data.address,
|
|
2565
|
+
label: detail.data.id
|
|
2566
|
+
})
|
|
2567
|
+
}, t("panel.goOwnerSession")) : null, h("button", {
|
|
2568
|
+
style: panelBtn,
|
|
2569
|
+
onClick: closeDetail
|
|
2570
|
+
}, t("common.close")))), h(RunDetailPane, {
|
|
2571
|
+
snap: detail && detail.data,
|
|
2572
|
+
product: state.current,
|
|
2573
|
+
api
|
|
2574
|
+
}))) : null));
|
|
481
2575
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
*
|
|
486
|
-
*
|
|
487
|
-
*
|
|
2576
|
+
//#endregion
|
|
2577
|
+
//#region client/index.tsx
|
|
2578
|
+
/**
|
|
2579
|
+
* dsh-plugin-teamflow — browser half(v0.7 视觉重构:「精密工程控制台」)。
|
|
2580
|
+
*
|
|
2581
|
+
* 注册 conversation.view tab「🏭 团队工作台」(与 chat / 轨迹并列):
|
|
2582
|
+
* - 流水线图形工作流:阶段泳道 + 节点卡片(状态/耗时/token/子代理会话)+ 实时轮询
|
|
2583
|
+
* - Backlog 拖拽看板:需求/任务/缺陷三组状态泳道,卡片拖拽流转(原生 HTML5 DnD,零依赖)
|
|
2584
|
+
* - 成本中心:每阶段 token + 总计 + 运行时长
|
|
2585
|
+
* - 人工介入中心:needs-human 项聚合 + 一键终态
|
|
2586
|
+
* - 历史 run 切换
|
|
2587
|
+
*
|
|
2588
|
+
* 视觉:全部使用 DSH 主题 token(--dsw-alias-*,自动适配深浅色)。
|
|
2589
|
+
* Remote 接入:$mount 后 ctx.get('remote.teamflow') 取全局实例传给组件
|
|
2590
|
+
* (不能 inject 'remote.teamflow'——自挂载自注入会让 fiber 死锁)。
|
|
488
2591
|
*/
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
if (s.usage) {
|
|
496
|
-
const u = s.usage;
|
|
497
|
-
const hit = hitRate(u);
|
|
498
|
-
return `输入(未命中) ${k(u.input)} / 输入(命中) ${k(u.cacheRead)} / 写缓存 ${k(u.cacheWrite)} / 输出 ${k(u.output)} · ${u.calls} 次调用${hit !== null ? ` · 缓存命中 ${hit}%` : ""}`;
|
|
499
|
-
}
|
|
500
|
-
return "无 usage 明细";
|
|
501
|
-
}
|
|
502
|
-
/** 节点卡主 token 行:官方口径 —— 输入(未命中)/输入(命中)/输出 + 缓存命中率。 */
|
|
503
|
-
function stageUsageLine(s) {
|
|
504
|
-
const u = s && s.usage;
|
|
505
|
-
if (u && (u.input || u.cacheRead || u.cacheWrite || u.output)) {
|
|
506
|
-
const hit = hitRate(u);
|
|
507
|
-
return `⇅${fmtTokens(u.input)} ⇅${fmtTokens(u.cacheRead)} ⬆${fmtTokens(u.output)}${hit !== null ? ` ·${hit}%` : ""}`;
|
|
508
|
-
}
|
|
509
|
-
return null;
|
|
510
|
-
}
|
|
511
|
-
const ROLE_NAME = {
|
|
512
|
-
pm: "产品",
|
|
513
|
-
design: "设计",
|
|
514
|
-
arch: "架构",
|
|
515
|
-
tech: "方案",
|
|
516
|
-
dev: "开发",
|
|
517
|
-
qa: "测试",
|
|
518
|
-
acceptance: "验收",
|
|
519
|
-
other: "其他"
|
|
520
|
-
};
|
|
521
|
-
const roleUsage = (u) => {
|
|
522
|
-
if (!u) return "";
|
|
523
|
-
const hit = hitRate(u);
|
|
524
|
-
return `⇅${fmtTokens(u.input || 0)}/${fmtTokens(u.cacheRead || 0)}·⬆${fmtTokens(u.output || 0)}${hit !== null ? `·${hit}%` : ""}`;
|
|
525
|
-
};
|
|
526
|
-
/** 任务卡按角色累计的真实 token 摘要(官方口径:未命中/命中输入 + 输出 + 命中率)。 */
|
|
527
|
-
function byRoleLine(task) {
|
|
528
|
-
const roles = task && task.byRole || {};
|
|
529
|
-
return Object.keys(roles).filter((k) => roles[k] && roles[k].input + roles[k].output + roles[k].cacheRead + roles[k].cacheWrite > 0).map((k) => `${ROLE_NAME[k] || k} ${roleUsage(roles[k])}`).join(" · ");
|
|
530
|
-
}
|
|
531
|
-
/** 多阶段 usage 汇总(官方口径)。 */
|
|
532
|
-
function totalUsage(stages) {
|
|
533
|
-
const t = {
|
|
534
|
-
input: 0,
|
|
535
|
-
cacheRead: 0,
|
|
536
|
-
cacheWrite: 0,
|
|
537
|
-
output: 0,
|
|
538
|
-
calls: 0
|
|
539
|
-
};
|
|
540
|
-
for (const s of stages || []) {
|
|
541
|
-
const u = s && s.usage;
|
|
542
|
-
if (!u) continue;
|
|
543
|
-
t.input += u.input || 0;
|
|
544
|
-
t.cacheRead += u.cacheRead || 0;
|
|
545
|
-
t.cacheWrite += u.cacheWrite || 0;
|
|
546
|
-
t.output += u.output || 0;
|
|
547
|
-
t.calls += u.calls || 0;
|
|
548
|
-
}
|
|
549
|
-
return t;
|
|
550
|
-
}
|
|
551
|
-
const stText = (s) => STATUS_TEXT[s] || s;
|
|
552
|
-
const stColor = (s) => STATUS_COLOR[s] || T.text2;
|
|
2592
|
+
const inject = [
|
|
2593
|
+
"remote",
|
|
2594
|
+
"slots",
|
|
2595
|
+
"sessions",
|
|
2596
|
+
"locale"
|
|
2597
|
+
];
|
|
553
2598
|
const NODE_W = 300;
|
|
554
2599
|
const PHASE_GAP = 88;
|
|
555
2600
|
const WAVE = 56;
|
|
@@ -615,7 +2660,7 @@ window.__ModuleLoader__.load({
|
|
|
615
2660
|
const usage = stageUsageLine(s);
|
|
616
2661
|
return h("div", {
|
|
617
2662
|
key,
|
|
618
|
-
title:
|
|
2663
|
+
title: t("stage.cardTip", { label: stageLabelOf(s) }),
|
|
619
2664
|
onMouseDown: (e) => e.stopPropagation(),
|
|
620
2665
|
onClick: () => onOpen && onOpen(s),
|
|
621
2666
|
style: {
|
|
@@ -664,8 +2709,11 @@ window.__ModuleLoader__.load({
|
|
|
664
2709
|
textOverflow: "ellipsis",
|
|
665
2710
|
whiteSpace: "nowrap"
|
|
666
2711
|
}
|
|
667
|
-
}, s
|
|
668
|
-
title:
|
|
2712
|
+
}, stageLabelOf(s)), s.attempts && s.attempts.length > 1 ? h("span", {
|
|
2713
|
+
title: t("stage.retryTip", {
|
|
2714
|
+
n: s.attempts.length - 1,
|
|
2715
|
+
m: s.attempts.length
|
|
2716
|
+
}),
|
|
669
2717
|
style: {
|
|
670
2718
|
fontFamily: MONO,
|
|
671
2719
|
fontSize: 10,
|
|
@@ -812,7 +2860,7 @@ window.__ModuleLoader__.load({
|
|
|
812
2860
|
});
|
|
813
2861
|
} catch (e) {}
|
|
814
2862
|
};
|
|
815
|
-
const outText = cur && cur.output ? cur.output : cur && cur.summary ?
|
|
2863
|
+
const outText = cur && cur.output ? cur.output : cur && cur.summary ? t("stage.summaryFallback", { summary: cur.summary }) : det.err ? t("stage.loadFailed", { err: det.err }) : det.loading ? t("common.loading") : t("stage.noOutput");
|
|
816
2864
|
const closeBtn = {
|
|
817
2865
|
font: "inherit",
|
|
818
2866
|
width: 26,
|
|
@@ -874,7 +2922,7 @@ window.__ModuleLoader__.load({
|
|
|
874
2922
|
textOverflow: "ellipsis",
|
|
875
2923
|
whiteSpace: "nowrap"
|
|
876
2924
|
}
|
|
877
|
-
}, st ? st
|
|
2925
|
+
}, st ? stageLabelOf(st) : t("stage.detailTitle")), h("div", { style: {
|
|
878
2926
|
fontSize: 10.5,
|
|
879
2927
|
color: T.text2,
|
|
880
2928
|
marginTop: 1,
|
|
@@ -883,7 +2931,7 @@ window.__ModuleLoader__.load({
|
|
|
883
2931
|
} }, `${st ? `#${st.seq} · ${st.phase}` : ""}${st && (st.startedAt || st.endedAt) ? ` · ${fmtDur(st.startedAt, st.endedAt)}` : ""}`)), st ? chip(stText(st.status), color, { dot: true }) : null, h("button", {
|
|
884
2932
|
onClick: onClose,
|
|
885
2933
|
style: closeBtn,
|
|
886
|
-
title: "
|
|
2934
|
+
title: t("common.close")
|
|
887
2935
|
}, "✕")), h("div", { style: {
|
|
888
2936
|
flex: 1,
|
|
889
2937
|
overflowY: "auto",
|
|
@@ -900,7 +2948,7 @@ window.__ModuleLoader__.load({
|
|
|
900
2948
|
fontWeight: 700,
|
|
901
2949
|
color: T.text2,
|
|
902
2950
|
letterSpacing: .3
|
|
903
|
-
} }, "
|
|
2951
|
+
} }, t("token.officialTitle")), h("span", { style: {
|
|
904
2952
|
fontSize: 11.5,
|
|
905
2953
|
fontFamily: MONO,
|
|
906
2954
|
color: T.text,
|
|
@@ -914,7 +2962,7 @@ window.__ModuleLoader__.load({
|
|
|
914
2962
|
fontWeight: 700,
|
|
915
2963
|
color: T.text2,
|
|
916
2964
|
letterSpacing: .3
|
|
917
|
-
} },
|
|
2965
|
+
} }, t("stage.attemptHistory", { n: attempts.length })), h("div", { style: {
|
|
918
2966
|
display: "flex",
|
|
919
2967
|
flexDirection: "column",
|
|
920
2968
|
gap: 4
|
|
@@ -945,7 +2993,7 @@ window.__ModuleLoader__.load({
|
|
|
945
2993
|
color: aColor,
|
|
946
2994
|
flex: "0 0 64px",
|
|
947
2995
|
fontWeight: 700
|
|
948
|
-
} }, a.status === "done" ? "
|
|
2996
|
+
} }, a.status === "done" ? t("stage.attemptDone") : a.status === "failed" ? t("stage.attemptFailed", { outcome: a.outcome || t("common.failed") }) : t("stage.attemptRunning")), h("span", { style: {
|
|
949
2997
|
flex: 1,
|
|
950
2998
|
minWidth: 0,
|
|
951
2999
|
fontSize: 10.5,
|
|
@@ -953,7 +3001,7 @@ window.__ModuleLoader__.load({
|
|
|
953
3001
|
overflow: "hidden",
|
|
954
3002
|
textOverflow: "ellipsis",
|
|
955
3003
|
whiteSpace: "nowrap"
|
|
956
|
-
} }, (a.summary || a.outcome || "
|
|
3004
|
+
} }, (a.summary || a.outcome || t("common.noSummary")).slice(0, 80)), h("span", { style: {
|
|
957
3005
|
fontFamily: MONO,
|
|
958
3006
|
fontSize: 10,
|
|
959
3007
|
color: T.text2,
|
|
@@ -971,7 +3019,7 @@ window.__ModuleLoader__.load({
|
|
|
971
3019
|
} }, h("button", {
|
|
972
3020
|
onClick: openChild,
|
|
973
3021
|
disabled: !hasChild,
|
|
974
|
-
title: crossSession ?
|
|
3022
|
+
title: crossSession ? t("stage.childCrossSessionTip", { sid: ownerSession.slice(-6) }) : hasChild ? t("stage.childJumpTip") : t("stage.childNoneTip"),
|
|
975
3023
|
style: {
|
|
976
3024
|
font: "inherit",
|
|
977
3025
|
fontSize: 12,
|
|
@@ -988,17 +3036,17 @@ window.__ModuleLoader__.load({
|
|
|
988
3036
|
color: T.brand,
|
|
989
3037
|
opacity: hasChild ? 1 : .45
|
|
990
3038
|
}
|
|
991
|
-
}, "
|
|
3039
|
+
}, t("stage.childJumpBtn")), crossSession ? h("div", { style: {
|
|
992
3040
|
fontSize: 10.5,
|
|
993
3041
|
color: T.text2,
|
|
994
3042
|
textAlign: "center",
|
|
995
3043
|
lineHeight: 1.55
|
|
996
|
-
} },
|
|
3044
|
+
} }, t("stage.childCrossSessionNote", { sid: ownerSession ? ownerSession.slice(-6) : "" })) : hasChild ? h("div", { style: {
|
|
997
3045
|
fontSize: 10.5,
|
|
998
3046
|
color: T.text2,
|
|
999
3047
|
textAlign: "center",
|
|
1000
3048
|
lineHeight: 1.55
|
|
1001
|
-
} }, "
|
|
3049
|
+
} }, t("stage.childJumpNote")) : null), st && phaseKeyOf(st.phase) === "dev" ? h("div", { style: {
|
|
1002
3050
|
display: "flex",
|
|
1003
3051
|
flexDirection: "column",
|
|
1004
3052
|
gap: 5
|
|
@@ -1007,7 +3055,7 @@ window.__ModuleLoader__.load({
|
|
|
1007
3055
|
fontWeight: 700,
|
|
1008
3056
|
color: T.text2,
|
|
1009
3057
|
letterSpacing: .3
|
|
1010
|
-
} }, "
|
|
3058
|
+
} }, t("stage.evidenceTitle")), cur && cur.verifyEvidence ? h("div", { style: {
|
|
1011
3059
|
whiteSpace: "pre-wrap",
|
|
1012
3060
|
wordBreak: "break-word",
|
|
1013
3061
|
fontSize: 11.5,
|
|
@@ -1028,7 +3076,7 @@ window.__ModuleLoader__.load({
|
|
|
1028
3076
|
borderRadius: 10,
|
|
1029
3077
|
padding: "8px 12px",
|
|
1030
3078
|
lineHeight: 1.55
|
|
1031
|
-
} }, "
|
|
3079
|
+
} }, t("stage.evidenceMissing"))) : null, h("div", { style: {
|
|
1032
3080
|
display: "flex",
|
|
1033
3081
|
flexDirection: "column",
|
|
1034
3082
|
gap: 5
|
|
@@ -1037,7 +3085,7 @@ window.__ModuleLoader__.load({
|
|
|
1037
3085
|
fontWeight: 700,
|
|
1038
3086
|
color: T.text2,
|
|
1039
3087
|
letterSpacing: .3
|
|
1040
|
-
} }, "
|
|
3088
|
+
} }, t("stage.artifactsTitle")), h("div", { style: {
|
|
1041
3089
|
whiteSpace: "pre-wrap",
|
|
1042
3090
|
wordBreak: "break-word",
|
|
1043
3091
|
fontSize: 12,
|
|
@@ -1060,7 +3108,7 @@ window.__ModuleLoader__.load({
|
|
|
1060
3108
|
} }, h("div", { style: {
|
|
1061
3109
|
fontSize: 28,
|
|
1062
3110
|
marginBottom: 8
|
|
1063
|
-
} }, "🏭"), "
|
|
3111
|
+
} }, "🏭"), t("pipeline.empty"));
|
|
1064
3112
|
const groups = [];
|
|
1065
3113
|
const taskKeyOf = (s) => String(s.taskKey || String(s.label || "").replace(/^开发 · /, "").replace(/((?:第 \d+ 次重试|补跑))$/, "").trim());
|
|
1066
3114
|
for (const st of active.stages || []) {
|
|
@@ -1261,7 +3309,8 @@ window.__ModuleLoader__.load({
|
|
|
1261
3309
|
onMouseLeave: onUp,
|
|
1262
3310
|
style: {
|
|
1263
3311
|
position: "relative",
|
|
1264
|
-
|
|
3312
|
+
flex: 1,
|
|
3313
|
+
minHeight: 320,
|
|
1265
3314
|
borderRadius: 12,
|
|
1266
3315
|
overflow: "hidden",
|
|
1267
3316
|
touchAction: "none",
|
|
@@ -1334,7 +3383,7 @@ window.__ModuleLoader__.load({
|
|
|
1334
3383
|
justifyContent: "center",
|
|
1335
3384
|
color: T.text2,
|
|
1336
3385
|
fontSize: 13
|
|
1337
|
-
} }, "
|
|
3386
|
+
} }, t("pipeline.noNodes")) : null, h("div", {
|
|
1338
3387
|
onMouseDown: (e) => e.stopPropagation(),
|
|
1339
3388
|
style: {
|
|
1340
3389
|
position: "absolute",
|
|
@@ -1352,7 +3401,7 @@ window.__ModuleLoader__.load({
|
|
|
1352
3401
|
boxShadow: "0 6px 20px rgba(0,0,0,.16)"
|
|
1353
3402
|
}
|
|
1354
3403
|
}, h("button", {
|
|
1355
|
-
title: "
|
|
3404
|
+
title: t("pipeline.zoomOut"),
|
|
1356
3405
|
onClick: () => zoomBy(.86),
|
|
1357
3406
|
style: zoomStyle
|
|
1358
3407
|
}, "−"), h("span", { style: {
|
|
@@ -1362,7 +3411,7 @@ window.__ModuleLoader__.load({
|
|
|
1362
3411
|
minWidth: 34,
|
|
1363
3412
|
textAlign: "center"
|
|
1364
3413
|
} }, `${Math.round(view.s * 100)}%`), h("button", {
|
|
1365
|
-
title: "
|
|
3414
|
+
title: t("pipeline.zoomIn"),
|
|
1366
3415
|
onClick: () => zoomBy(1.16),
|
|
1367
3416
|
style: zoomStyle
|
|
1368
3417
|
}, "+"), h("span", { style: {
|
|
@@ -1370,7 +3419,7 @@ window.__ModuleLoader__.load({
|
|
|
1370
3419
|
height: 14,
|
|
1371
3420
|
background: T.border
|
|
1372
3421
|
} }), h("button", {
|
|
1373
|
-
title: "
|
|
3422
|
+
title: t("pipeline.fitCanvas"),
|
|
1374
3423
|
onClick: fitNow,
|
|
1375
3424
|
style: {
|
|
1376
3425
|
...zoomStyle,
|
|
@@ -1385,7 +3434,7 @@ window.__ModuleLoader__.load({
|
|
|
1385
3434
|
color: T.text2,
|
|
1386
3435
|
paddingRight: 4,
|
|
1387
3436
|
opacity: .85
|
|
1388
|
-
} }, "
|
|
3437
|
+
} }, t("pipeline.canvasHint"))), det ? h(StageDetailDrawer, {
|
|
1389
3438
|
det,
|
|
1390
3439
|
onClose: closeDet,
|
|
1391
3440
|
sessionId,
|
|
@@ -1431,12 +3480,12 @@ window.__ModuleLoader__.load({
|
|
|
1431
3480
|
} }, h("div", { style: {
|
|
1432
3481
|
fontSize: 28,
|
|
1433
3482
|
marginBottom: 8
|
|
1434
|
-
} }, "📋"), "
|
|
3483
|
+
} }, "📋"), t("board.empty"));
|
|
1435
3484
|
const subtaskMap = {};
|
|
1436
3485
|
for (const t of backlog.tasks || []) if (t.type === "subtask" && t.id) subtaskMap[t.id] = t;
|
|
1437
3486
|
const move = async (kind, id, to) => {
|
|
1438
3487
|
try {
|
|
1439
|
-
await api.backlogUpdate(kind, id, to, sessionId, "
|
|
3488
|
+
await api.backlogUpdate(kind, id, to, sessionId, t("board.dragReason"));
|
|
1440
3489
|
} catch (e) {}
|
|
1441
3490
|
onRefresh();
|
|
1442
3491
|
};
|
|
@@ -1458,33 +3507,52 @@ window.__ModuleLoader__.load({
|
|
|
1458
3507
|
padding: "7px 9px",
|
|
1459
3508
|
cursor: "grab",
|
|
1460
3509
|
fontSize: 12,
|
|
3510
|
+
boxSizing: "border-box",
|
|
3511
|
+
minWidth: 0,
|
|
3512
|
+
maxWidth: "100%",
|
|
3513
|
+
overflow: "hidden",
|
|
1461
3514
|
opacity: drag && drag.id === item.id ? .35 : 1,
|
|
1462
3515
|
transition: "opacity .1s ease, transform .12s ease",
|
|
1463
3516
|
boxShadow: "0 1px 2px rgba(0,0,0,.05)"
|
|
1464
3517
|
},
|
|
1465
|
-
title:
|
|
3518
|
+
title: t("board.cardTip", {
|
|
3519
|
+
id: item.id,
|
|
3520
|
+
status: item.status,
|
|
3521
|
+
summary: item.summary ? "\n" + item.summary : ""
|
|
3522
|
+
})
|
|
1466
3523
|
}, h("div", { style: {
|
|
1467
3524
|
display: "flex",
|
|
1468
3525
|
alignItems: "center",
|
|
1469
|
-
gap: 4
|
|
3526
|
+
gap: 4,
|
|
3527
|
+
minWidth: 0
|
|
1470
3528
|
} }, h("span", { style: {
|
|
1471
3529
|
fontFamily: MONO,
|
|
1472
3530
|
color: T.text2,
|
|
1473
|
-
fontSize: 10.5
|
|
3531
|
+
fontSize: 10.5,
|
|
3532
|
+
minWidth: 0,
|
|
3533
|
+
overflow: "hidden",
|
|
3534
|
+
textOverflow: "ellipsis",
|
|
3535
|
+
whiteSpace: "nowrap"
|
|
1474
3536
|
} }, item.id), item.severity ? chip(item.severity, item.severity === "P0" ? T.error : item.severity === "P1" ? T.warn : T.text2) : null, item.owner ? h("span", { style: {
|
|
1475
3537
|
marginLeft: "auto",
|
|
1476
3538
|
fontSize: 10.5,
|
|
1477
|
-
color: T.text2
|
|
3539
|
+
color: T.text2,
|
|
3540
|
+
minWidth: 0,
|
|
3541
|
+
overflow: "hidden",
|
|
3542
|
+
textOverflow: "ellipsis",
|
|
3543
|
+
whiteSpace: "nowrap"
|
|
1478
3544
|
} }, `👤 ${item.owner}`) : null), h("div", {
|
|
1479
3545
|
title: item.title || void 0,
|
|
1480
3546
|
style: {
|
|
1481
3547
|
fontWeight: 500,
|
|
1482
3548
|
margin: "3px 0 4px",
|
|
1483
|
-
lineHeight: 1.4
|
|
3549
|
+
lineHeight: 1.4,
|
|
3550
|
+
overflowWrap: "anywhere"
|
|
1484
3551
|
}
|
|
1485
3552
|
}, (item.title || "").slice(0, 30)), h("div", { style: {
|
|
1486
3553
|
...flexRow,
|
|
1487
|
-
marginTop: 2
|
|
3554
|
+
marginTop: 2,
|
|
3555
|
+
minWidth: 0
|
|
1488
3556
|
} }, chip(stText(item.status), stColor(item.status)), typeof item.retries === "number" && item.retries > 0 ? h("span", { style: {
|
|
1489
3557
|
fontSize: 10.5,
|
|
1490
3558
|
color: T.warn,
|
|
@@ -1498,38 +3566,42 @@ window.__ModuleLoader__.load({
|
|
|
1498
3566
|
marginTop: 3,
|
|
1499
3567
|
fontSize: 10.5,
|
|
1500
3568
|
color: T.text2,
|
|
1501
|
-
fontFamily: MONO
|
|
3569
|
+
fontFamily: MONO,
|
|
3570
|
+
minWidth: 0
|
|
1502
3571
|
} }, item.devAssign ? h("span", {
|
|
1503
|
-
title:
|
|
3572
|
+
title: t("board.assignDevTip", { who: item.devAssign }),
|
|
1504
3573
|
style: {
|
|
1505
3574
|
display: "inline-flex",
|
|
1506
3575
|
alignItems: "center",
|
|
1507
3576
|
gap: 3,
|
|
1508
3577
|
overflow: "hidden",
|
|
1509
3578
|
textOverflow: "ellipsis",
|
|
1510
|
-
maxWidth:
|
|
3579
|
+
maxWidth: "100%",
|
|
3580
|
+
minWidth: 0,
|
|
1511
3581
|
whiteSpace: "nowrap"
|
|
1512
3582
|
}
|
|
1513
3583
|
}, `👨💻${item.devAssign}`) : null, item.qaAssign ? h("span", {
|
|
1514
|
-
title:
|
|
3584
|
+
title: t("board.assignQaTip", { who: item.qaAssign }),
|
|
1515
3585
|
style: {
|
|
1516
3586
|
display: "inline-flex",
|
|
1517
3587
|
alignItems: "center",
|
|
1518
3588
|
gap: 3,
|
|
1519
3589
|
overflow: "hidden",
|
|
1520
3590
|
textOverflow: "ellipsis",
|
|
1521
|
-
maxWidth:
|
|
3591
|
+
maxWidth: "100%",
|
|
3592
|
+
minWidth: 0,
|
|
1522
3593
|
whiteSpace: "nowrap"
|
|
1523
3594
|
}
|
|
1524
3595
|
}, `🧪${item.qaAssign}`) : null, item.acceptBy ? h("span", {
|
|
1525
|
-
title:
|
|
3596
|
+
title: t("board.acceptTip", { who: item.acceptBy }),
|
|
1526
3597
|
style: {
|
|
1527
3598
|
display: "inline-flex",
|
|
1528
3599
|
alignItems: "center",
|
|
1529
3600
|
gap: 3,
|
|
1530
3601
|
overflow: "hidden",
|
|
1531
3602
|
textOverflow: "ellipsis",
|
|
1532
|
-
maxWidth:
|
|
3603
|
+
maxWidth: "100%",
|
|
3604
|
+
minWidth: 0,
|
|
1533
3605
|
whiteSpace: "nowrap"
|
|
1534
3606
|
}
|
|
1535
3607
|
}, `✅${item.acceptBy}`) : null) : null, kind === "task" && byRoleLine(item) ? h("div", { style: {
|
|
@@ -1537,8 +3609,21 @@ window.__ModuleLoader__.load({
|
|
|
1537
3609
|
marginTop: 3,
|
|
1538
3610
|
fontSize: 10.5,
|
|
1539
3611
|
color: T.warn,
|
|
1540
|
-
fontFamily: MONO
|
|
1541
|
-
|
|
3612
|
+
fontFamily: MONO,
|
|
3613
|
+
minWidth: 0,
|
|
3614
|
+
maxWidth: "100%"
|
|
3615
|
+
} }, h("span", { style: {
|
|
3616
|
+
color: T.text2,
|
|
3617
|
+
flex: "0 0 auto"
|
|
3618
|
+
} }, "⛽"), h("span", {
|
|
3619
|
+
title: byRoleLine(item),
|
|
3620
|
+
style: {
|
|
3621
|
+
minWidth: 0,
|
|
3622
|
+
flex: "1 1 auto",
|
|
3623
|
+
overflowWrap: "anywhere",
|
|
3624
|
+
wordBreak: "break-word"
|
|
3625
|
+
}
|
|
3626
|
+
}, byRoleLine(item))) : null, kind === "task" && (item.subtaskIds || []).length > 0 ? (() => {
|
|
1542
3627
|
const subs = item.subtaskIds.map((id) => subtaskMap[id]).filter(Boolean);
|
|
1543
3628
|
if (subs.length === 0) return null;
|
|
1544
3629
|
const done = subs.filter((s) => s.status === "done").length;
|
|
@@ -1550,7 +3635,7 @@ window.__ModuleLoader__.load({
|
|
|
1550
3635
|
color: T.text2,
|
|
1551
3636
|
fontFamily: MONO,
|
|
1552
3637
|
marginBottom: 3
|
|
1553
|
-
} }, h("span", null,
|
|
3638
|
+
} }, h("span", null, t("board.subtaskCount", { n: subs.length })), done > 0 ? h("span", { style: { color: T.success } }, `${done}✓`) : null, running > 0 ? h("span", { style: { color: T.brand } }, `${running}⟳`) : null, failed > 0 ? h("span", { style: { color: T.error } }, `${failed}✗`) : null), subs.map((sub) => h("div", {
|
|
1554
3639
|
key: sub.id,
|
|
1555
3640
|
style: {
|
|
1556
3641
|
display: "flex",
|
|
@@ -1561,29 +3646,33 @@ window.__ModuleLoader__.load({
|
|
|
1561
3646
|
padding: "2px 6px",
|
|
1562
3647
|
borderRadius: 4,
|
|
1563
3648
|
marginBottom: 2,
|
|
3649
|
+
minWidth: 0,
|
|
1564
3650
|
background: sub.status === "failed" ? `color-mix(in srgb, ${T.error} 7%, transparent)` : T.layer2,
|
|
1565
3651
|
border: `1px solid ${stColor(sub.status)}30`
|
|
1566
3652
|
}
|
|
1567
3653
|
}, h("span", { style: {
|
|
1568
3654
|
color: stColor(sub.status),
|
|
1569
3655
|
fontWeight: 600,
|
|
1570
|
-
minWidth: 12
|
|
3656
|
+
minWidth: 12,
|
|
3657
|
+
flex: "0 0 auto"
|
|
1571
3658
|
} }, sub.status === "done" ? "✓" : sub.status === "failed" ? "✗" : sub.status === "running" ? "⟳" : "…"), h("span", {
|
|
1572
3659
|
title: sub.title || void 0,
|
|
1573
3660
|
style: {
|
|
3661
|
+
flex: "1 1 auto",
|
|
3662
|
+
minWidth: 0,
|
|
1574
3663
|
overflow: "hidden",
|
|
1575
3664
|
textOverflow: "ellipsis",
|
|
1576
|
-
whiteSpace: "nowrap"
|
|
1577
|
-
maxWidth: 130
|
|
3665
|
+
whiteSpace: "nowrap"
|
|
1578
3666
|
}
|
|
1579
3667
|
}, (sub.title || "").replace(/^开发 · /, "")), sub.devAssign ? h("span", {
|
|
1580
|
-
title:
|
|
3668
|
+
title: t("board.assignDevTip", { who: sub.devAssign }),
|
|
1581
3669
|
style: {
|
|
1582
|
-
|
|
3670
|
+
flex: "0 1 auto",
|
|
1583
3671
|
color: T.text2,
|
|
1584
3672
|
overflow: "hidden",
|
|
1585
3673
|
textOverflow: "ellipsis",
|
|
1586
3674
|
maxWidth: 70,
|
|
3675
|
+
minWidth: 0,
|
|
1587
3676
|
whiteSpace: "nowrap"
|
|
1588
3677
|
}
|
|
1589
3678
|
}, sub.devAssign) : null)));
|
|
@@ -1603,24 +3692,35 @@ window.__ModuleLoader__.load({
|
|
|
1603
3692
|
}
|
|
1604
3693
|
];
|
|
1605
3694
|
return h("div", { style: {
|
|
3695
|
+
position: "relative",
|
|
3696
|
+
flex: 1,
|
|
3697
|
+
minHeight: 0,
|
|
3698
|
+
display: "flex",
|
|
3699
|
+
flexDirection: "column"
|
|
3700
|
+
} }, h("div", { style: {
|
|
3701
|
+
flex: 1,
|
|
3702
|
+
minHeight: 0,
|
|
3703
|
+
overflowY: "auto",
|
|
1606
3704
|
display: "flex",
|
|
1607
3705
|
flexDirection: "column",
|
|
1608
3706
|
gap: 14,
|
|
1609
|
-
maxHeight: "72vh",
|
|
1610
|
-
overflowY: "auto",
|
|
1611
3707
|
paddingRight: 4
|
|
1612
3708
|
} }, groups.map(({ kind, list }) => {
|
|
1613
3709
|
const counts = {};
|
|
1614
3710
|
for (const s of COLUMNS[kind]) counts[s] = 0;
|
|
1615
3711
|
for (const item of list) counts[item.status] = (counts[item.status] || 0) + 1;
|
|
1616
3712
|
return h("div", { key: kind }, h("div", { style: {
|
|
3713
|
+
position: "sticky",
|
|
3714
|
+
top: 0,
|
|
3715
|
+
zIndex: 3,
|
|
3716
|
+
background: T.bg,
|
|
1617
3717
|
display: "flex",
|
|
1618
3718
|
alignItems: "center",
|
|
1619
3719
|
gap: 8,
|
|
1620
3720
|
fontWeight: 700,
|
|
1621
3721
|
fontSize: 13,
|
|
1622
|
-
|
|
1623
|
-
} }, h("span", { style: { fontSize: 14 } }, kind === "req" ? "📌" : kind === "task" ? "🔧" : "🐞"),
|
|
3722
|
+
padding: "6px 0 8px"
|
|
3723
|
+
} }, h("span", { style: { fontSize: 14 } }, kind === "req" ? "📌" : kind === "task" ? "🔧" : "🐞"), kindTitle(kind), h("span", { style: {
|
|
1624
3724
|
fontSize: 11,
|
|
1625
3725
|
fontWeight: 600,
|
|
1626
3726
|
color: T.text2,
|
|
@@ -1636,6 +3736,7 @@ window.__ModuleLoader__.load({
|
|
|
1636
3736
|
} }, COLUMNS[kind].map((s) => {
|
|
1637
3737
|
const isOver = over === `${kind}:${s}`;
|
|
1638
3738
|
const color = stColor(s);
|
|
3739
|
+
const colBg = isOver ? `color-mix(in srgb, ${color} 8%, ${T.layer1})` : T.layer2;
|
|
1639
3740
|
return h("div", {
|
|
1640
3741
|
key: s,
|
|
1641
3742
|
onDragOver: (e) => {
|
|
@@ -1653,11 +3754,12 @@ window.__ModuleLoader__.load({
|
|
|
1653
3754
|
maxWidth: 172,
|
|
1654
3755
|
flex: "0 0 auto",
|
|
1655
3756
|
borderRadius: 10,
|
|
1656
|
-
padding:
|
|
3757
|
+
padding: 0,
|
|
1657
3758
|
minHeight: 84,
|
|
1658
3759
|
maxHeight: 340,
|
|
1659
3760
|
overflowY: "auto",
|
|
1660
|
-
|
|
3761
|
+
overflowX: "hidden",
|
|
3762
|
+
background: colBg,
|
|
1661
3763
|
border: `1px dashed ${isOver ? color : T.border}`,
|
|
1662
3764
|
transition: "background .12s ease, border-color .12s ease"
|
|
1663
3765
|
}
|
|
@@ -1666,8 +3768,13 @@ window.__ModuleLoader__.load({
|
|
|
1666
3768
|
fontSize: 11,
|
|
1667
3769
|
fontWeight: 600,
|
|
1668
3770
|
color,
|
|
1669
|
-
|
|
1670
|
-
|
|
3771
|
+
position: "sticky",
|
|
3772
|
+
top: 0,
|
|
3773
|
+
zIndex: 2,
|
|
3774
|
+
background: colBg,
|
|
3775
|
+
padding: "7px 9px 5px",
|
|
3776
|
+
borderTopLeftRadius: 10,
|
|
3777
|
+
borderTopRightRadius: 10
|
|
1671
3778
|
} }, h("span", null, stText(s)), h("span", { style: {
|
|
1672
3779
|
marginLeft: "auto",
|
|
1673
3780
|
fontFamily: MONO,
|
|
@@ -1676,10 +3783,12 @@ window.__ModuleLoader__.load({
|
|
|
1676
3783
|
} }, counts[s] || 0)), h("div", { style: {
|
|
1677
3784
|
display: "flex",
|
|
1678
3785
|
flexDirection: "column",
|
|
1679
|
-
gap: 6
|
|
3786
|
+
gap: 6,
|
|
3787
|
+
padding: "0 7px 7px",
|
|
3788
|
+
minWidth: 0
|
|
1680
3789
|
} }, list.filter((item) => item.status === s).map((item) => card(item, kind))));
|
|
1681
3790
|
})));
|
|
1682
|
-
}), det ? h(ItemDetailDrawer, {
|
|
3791
|
+
})), det ? h(ItemDetailDrawer, {
|
|
1683
3792
|
det,
|
|
1684
3793
|
onClose: () => setDet(null),
|
|
1685
3794
|
onShowRun,
|
|
@@ -1687,7 +3796,7 @@ window.__ModuleLoader__.load({
|
|
|
1687
3796
|
}) : null);
|
|
1688
3797
|
}
|
|
1689
3798
|
function fmtAt(ts) {
|
|
1690
|
-
return ts ? new Date(ts).toLocaleTimeString(
|
|
3799
|
+
return ts ? new Date(ts).toLocaleTimeString(localeTag(), {
|
|
1691
3800
|
hour: "2-digit",
|
|
1692
3801
|
minute: "2-digit"
|
|
1693
3802
|
}) : "—";
|
|
@@ -1765,7 +3874,7 @@ window.__ModuleLoader__.load({
|
|
|
1765
3874
|
color: T.warn,
|
|
1766
3875
|
flex: "0 0 auto"
|
|
1767
3876
|
} }, `⛽${fmtTokens((extra.usage.input || 0) + (extra.usage.cacheRead || 0) + (extra.usage.cacheWrite || 0) + (extra.usage.output || 0))}`) : null, extra && extra.assignee ? h("span", {
|
|
1768
|
-
title:
|
|
3877
|
+
title: t("board.assignDevTip", { who: extra.assignee }),
|
|
1769
3878
|
style: {
|
|
1770
3879
|
display: "inline-flex",
|
|
1771
3880
|
alignItems: "center",
|
|
@@ -1844,7 +3953,7 @@ window.__ModuleLoader__.load({
|
|
|
1844
3953
|
} }, `${d.id} · ${d.kind}${d.severity ? " · " + d.severity : ""}`) : null), d ? chip(stText(d.status), color, { dot: true }) : null, h("button", {
|
|
1845
3954
|
onClick: onClose,
|
|
1846
3955
|
style: closeBtn,
|
|
1847
|
-
title: "
|
|
3956
|
+
title: t("common.close")
|
|
1848
3957
|
}, "✕")), h("div", { style: {
|
|
1849
3958
|
flex: 1,
|
|
1850
3959
|
overflowY: "auto",
|
|
@@ -1856,7 +3965,7 @@ window.__ModuleLoader__.load({
|
|
|
1856
3965
|
color: T.text2,
|
|
1857
3966
|
fontSize: 12,
|
|
1858
3967
|
padding: 12
|
|
1859
|
-
} }, "
|
|
3968
|
+
} }, t("common.loading")) : err ? h("div", { style: {
|
|
1860
3969
|
color: T.error,
|
|
1861
3970
|
fontSize: 12,
|
|
1862
3971
|
padding: 12
|
|
@@ -1865,7 +3974,7 @@ window.__ModuleLoader__.load({
|
|
|
1865
3974
|
display: "flex",
|
|
1866
3975
|
flexDirection: "column",
|
|
1867
3976
|
gap: 4
|
|
1868
|
-
} }, secTitle("
|
|
3977
|
+
} }, secTitle(t("item.overview")), d.spec ? h(FoldableText, {
|
|
1869
3978
|
text: d.spec,
|
|
1870
3979
|
charLimit: 300,
|
|
1871
3980
|
lineLimit: 4,
|
|
@@ -1883,7 +3992,28 @@ window.__ModuleLoader__.load({
|
|
|
1883
3992
|
flexDirection: "column",
|
|
1884
3993
|
gap: 2,
|
|
1885
3994
|
marginTop: 2
|
|
1886
|
-
} }, d.devAssign ? kv("👨💻 dev", d.devAssign.slice(0, 26), true) : null, d.qaAssign ? kv("🧪 qa", d.qaAssign.slice(0, 26), true) : null, d.assignBy ? kv("
|
|
3995
|
+
} }, d.devAssign ? kv("👨💻 dev", d.devAssign.slice(0, 26), true) : null, d.qaAssign ? kv("🧪 qa", d.qaAssign.slice(0, 26), true) : null, d.assignBy ? kv(t("item.acceptRow"), d.assignBy.slice(0, 26), true) : null, d.owner ? kv("👤 owner", d.owner.slice(0, 26), true) : null, typeof d.retries === "number" && d.retries > 0 ? kv(t("item.retryRow"), String(d.retries)) : null, d.humanIntervention ? kv(t("item.humanRow"), t("common.needsHuman")) : null, kv(t("item.updatedAt"), fmtAt(d.updatedAt) || "—"))),
|
|
3996
|
+
kind === "bug" ? h("div", { style: {
|
|
3997
|
+
display: "flex",
|
|
3998
|
+
flexDirection: "column",
|
|
3999
|
+
gap: 4,
|
|
4000
|
+
padding: "8px 10px",
|
|
4001
|
+
borderRadius: 8,
|
|
4002
|
+
background: T.layer2,
|
|
4003
|
+
border: `1px solid ${T.border}`
|
|
4004
|
+
} }, secTitle(t("panelItem.defectSection")), d.spec ? h(FoldableText, {
|
|
4005
|
+
text: d.spec,
|
|
4006
|
+
charLimit: 300,
|
|
4007
|
+
lineLimit: 4
|
|
4008
|
+
}) : null, h("div", { style: {
|
|
4009
|
+
display: "flex",
|
|
4010
|
+
flexDirection: "column",
|
|
4011
|
+
gap: 2
|
|
4012
|
+
} }, d.defectId ? kv(t("panelItem.row.defectId"), String(d.defectId)) : null, d.severity ? kv(t("panelItem.row.severity"), String(d.severity)) : null, d.module ? kv(t("panelItem.row.module"), String(d.module)) : null, d.reproduce ? kv(t("panelItem.row.reproduce"), String(d.reproduce)) : null, d.expected ? kv(t("panelItem.row.expected"), String(d.expected)) : null, d.actual ? kv(t("panelItem.row.actual"), String(d.actual)) : null, d.defectCheck ? kv(t("panelItem.row.defectCheck"), String(d.defectCheck)) : null, d.defectCriterion ? kv(t("panelItem.row.defectCriterion"), String(d.defectCriterion)) : null, d.defectAc ? kv(t("panelItem.row.defectAc"), String(d.defectAc)) : null), d.reproduce || d.expected || d.actual ? null : h("div", { style: {
|
|
4013
|
+
fontSize: 10.5,
|
|
4014
|
+
color: T.warn,
|
|
4015
|
+
lineHeight: 1.5
|
|
4016
|
+
} }, t("panelItem.noDefectDetail"))) : null,
|
|
1887
4017
|
d.runInfo ? (() => {
|
|
1888
4018
|
const ri = d.runInfo;
|
|
1889
4019
|
return h("div", { style: {
|
|
@@ -1898,14 +4028,14 @@ window.__ModuleLoader__.load({
|
|
|
1898
4028
|
display: "flex",
|
|
1899
4029
|
alignItems: "center",
|
|
1900
4030
|
gap: 8
|
|
1901
|
-
} }, secTitle("
|
|
4031
|
+
} }, secTitle(t("item.runSection")), h("span", { style: {
|
|
1902
4032
|
fontFamily: MONO,
|
|
1903
4033
|
fontSize: 10.5,
|
|
1904
4034
|
color: T.text2,
|
|
1905
4035
|
whiteSpace: "nowrap"
|
|
1906
4036
|
} }, ri.runId), chip(stText(ri.status), stColor(ri.status)), onShowRun ? h("button", {
|
|
1907
4037
|
onClick: () => onShowRun(ri.runId),
|
|
1908
|
-
title:
|
|
4038
|
+
title: t("item.jumpRunTip", { id: String(ri.runId).slice(-6) }),
|
|
1909
4039
|
style: {
|
|
1910
4040
|
marginLeft: "auto",
|
|
1911
4041
|
flex: "0 0 auto",
|
|
@@ -1920,7 +4050,7 @@ window.__ModuleLoader__.load({
|
|
|
1920
4050
|
color: T.brand,
|
|
1921
4051
|
lineHeight: "16px"
|
|
1922
4052
|
}
|
|
1923
|
-
}, "
|
|
4053
|
+
}, t("item.runBtn")) : null), ri.startedAt || ri.endedAt ? h("span", { style: {
|
|
1924
4054
|
fontSize: 10.5,
|
|
1925
4055
|
color: T.text2,
|
|
1926
4056
|
fontFamily: MONO
|
|
@@ -1928,8 +4058,8 @@ window.__ModuleLoader__.load({
|
|
|
1928
4058
|
display: "flex",
|
|
1929
4059
|
flexDirection: "column",
|
|
1930
4060
|
gap: 4
|
|
1931
|
-
} }, secTitle("
|
|
1932
|
-
text: ri.requirement || "
|
|
4061
|
+
} }, secTitle(t("item.requirement")), h(FoldableText, {
|
|
4062
|
+
text: ri.requirement || t("item.noRequirement"),
|
|
1933
4063
|
charLimit: 300,
|
|
1934
4064
|
lineLimit: 4,
|
|
1935
4065
|
style: {
|
|
@@ -1943,7 +4073,7 @@ window.__ModuleLoader__.load({
|
|
|
1943
4073
|
display: "flex",
|
|
1944
4074
|
flexDirection: "column",
|
|
1945
4075
|
gap: 4
|
|
1946
|
-
} }, secTitle("
|
|
4076
|
+
} }, secTitle(t("item.runDocs")), h("div", { style: {
|
|
1947
4077
|
fontSize: 11.5,
|
|
1948
4078
|
fontFamily: MONO,
|
|
1949
4079
|
color: T.brand,
|
|
@@ -1961,7 +4091,7 @@ window.__ModuleLoader__.load({
|
|
|
1961
4091
|
onClick: () => {
|
|
1962
4092
|
if (openArtifact) openArtifact(a.address, a.name);
|
|
1963
4093
|
},
|
|
1964
|
-
title:
|
|
4094
|
+
title: t("item.previewTip", { path: `${d.runDocs}/${a.name}` }),
|
|
1965
4095
|
style: {
|
|
1966
4096
|
fontSize: 10.5,
|
|
1967
4097
|
fontWeight: 600,
|
|
@@ -1979,7 +4109,7 @@ window.__ModuleLoader__.load({
|
|
|
1979
4109
|
display: "flex",
|
|
1980
4110
|
flexDirection: "column",
|
|
1981
4111
|
gap: 4
|
|
1982
|
-
} }, secTitle("
|
|
4112
|
+
} }, secTitle(t("token.officialTitle")), h("span", { style: {
|
|
1983
4113
|
fontSize: 11.5,
|
|
1984
4114
|
fontFamily: MONO,
|
|
1985
4115
|
color: T.text,
|
|
@@ -1991,7 +4121,7 @@ window.__ModuleLoader__.load({
|
|
|
1991
4121
|
marginTop: 2
|
|
1992
4122
|
} }, Object.entries(d.byRole).sort((a, b) => totalTokens(b[1]) - totalTokens(a[1])).map(([role, uRaw]) => {
|
|
1993
4123
|
const u = uRaw;
|
|
1994
|
-
const label = role
|
|
4124
|
+
const label = roleChip(role);
|
|
1995
4125
|
return h("div", {
|
|
1996
4126
|
key: role,
|
|
1997
4127
|
style: {
|
|
@@ -2002,13 +4132,13 @@ window.__ModuleLoader__.load({
|
|
|
2002
4132
|
fontFamily: MONO,
|
|
2003
4133
|
color: T.text2
|
|
2004
4134
|
}
|
|
2005
|
-
}, h("span", { style: { flex: "0 0 64px" } }, label), h("span", { style: { color: T.text } }, `⛽${fmtTokens(totalTokens(u))}`), h("span", null, `${fmtTokens(u.input || 0)}i / ${fmtTokens(u.cacheRead || 0)}c / ${fmtTokens(u.output || 0)}o · ${u.calls || 0}
|
|
4135
|
+
}, h("span", { style: { flex: "0 0 64px" } }, label), h("span", { style: { color: T.text } }, `⛽${fmtTokens(totalTokens(u))}`), h("span", null, `${fmtTokens(u.input || 0)}i / ${fmtTokens(u.cacheRead || 0)}c / ${fmtTokens(u.output || 0)}o · ${t("common.calls", { n: u.calls || 0 })}`));
|
|
2006
4136
|
})) : null) : null,
|
|
2007
4137
|
d.subtasks && d.subtasks.length > 0 ? h("div", { style: {
|
|
2008
4138
|
display: "flex",
|
|
2009
4139
|
flexDirection: "column",
|
|
2010
4140
|
gap: 4
|
|
2011
|
-
} }, secTitle(
|
|
4141
|
+
} }, secTitle(t("item.subtasks", { n: d.subtasks.length })), h("div", { style: {
|
|
2012
4142
|
display: "flex",
|
|
2013
4143
|
flexDirection: "column",
|
|
2014
4144
|
gap: 5
|
|
@@ -2020,7 +4150,7 @@ window.__ModuleLoader__.load({
|
|
|
2020
4150
|
display: "flex",
|
|
2021
4151
|
flexDirection: "column",
|
|
2022
4152
|
gap: 4
|
|
2023
|
-
} }, secTitle(
|
|
4153
|
+
} }, secTitle(t("item.bugs", { n: d.bugs.length })), h("div", { style: {
|
|
2024
4154
|
display: "flex",
|
|
2025
4155
|
flexDirection: "column",
|
|
2026
4156
|
gap: 5
|
|
@@ -2029,7 +4159,7 @@ window.__ModuleLoader__.load({
|
|
|
2029
4159
|
display: "flex",
|
|
2030
4160
|
flexDirection: "column",
|
|
2031
4161
|
gap: 4
|
|
2032
|
-
} }, secTitle(
|
|
4162
|
+
} }, secTitle(t("item.timeline", { n: d.events.length })), h("div", { style: {
|
|
2033
4163
|
display: "flex",
|
|
2034
4164
|
flexDirection: "column",
|
|
2035
4165
|
gap: 0
|
|
@@ -2065,7 +4195,7 @@ window.__ModuleLoader__.load({
|
|
|
2065
4195
|
}, ev.reason || ""))))) : null
|
|
2066
4196
|
]));
|
|
2067
4197
|
}
|
|
2068
|
-
function TeamSelector({ sessionId, remote }) {
|
|
4198
|
+
function TeamSelector({ sessionId, remote, locale }) {
|
|
2069
4199
|
const [teams, setTeams] = react.default.useState([]);
|
|
2070
4200
|
const [active, setActive] = react.default.useState(null);
|
|
2071
4201
|
const [open, setOpen] = react.default.useState(false);
|
|
@@ -2078,7 +4208,11 @@ window.__ModuleLoader__.load({
|
|
|
2078
4208
|
const at = unwrap(await remote.getActiveTeam(sessionId), "getActiveTeam");
|
|
2079
4209
|
setActive(at && at.team ? at.team : null);
|
|
2080
4210
|
} catch (e) {}
|
|
2081
|
-
}, [
|
|
4211
|
+
}, [
|
|
4212
|
+
remote,
|
|
4213
|
+
sessionId,
|
|
4214
|
+
locale
|
|
4215
|
+
]);
|
|
2082
4216
|
react.default.useEffect(() => {
|
|
2083
4217
|
load();
|
|
2084
4218
|
}, [load]);
|
|
@@ -2109,7 +4243,7 @@ window.__ModuleLoader__.load({
|
|
|
2109
4243
|
style: { position: "relative" }
|
|
2110
4244
|
}, h("button", {
|
|
2111
4245
|
onClick: () => setOpen(!open),
|
|
2112
|
-
title: active ?
|
|
4246
|
+
title: active ? t("team.currentTip", { name: active.name }) : t("team.pick"),
|
|
2113
4247
|
style: {
|
|
2114
4248
|
display: "inline-flex",
|
|
2115
4249
|
alignItems: "center",
|
|
@@ -2126,14 +4260,14 @@ window.__ModuleLoader__.load({
|
|
|
2126
4260
|
transition: "all .12s ease"
|
|
2127
4261
|
}
|
|
2128
4262
|
}, h("span", { style: { fontSize: 12 } }, active ? active.icon : "🏭"), h("span", {
|
|
2129
|
-
title: active && active.name ? String(active.name) : "
|
|
4263
|
+
title: active && active.name ? String(active.name) : t("team.label"),
|
|
2130
4264
|
style: {
|
|
2131
4265
|
maxWidth: 80,
|
|
2132
4266
|
overflow: "hidden",
|
|
2133
4267
|
textOverflow: "ellipsis",
|
|
2134
4268
|
whiteSpace: "nowrap"
|
|
2135
4269
|
}
|
|
2136
|
-
}, active ? active.name : "
|
|
4270
|
+
}, active ? active.name : t("team.label")), h("span", { style: {
|
|
2137
4271
|
fontSize: 8,
|
|
2138
4272
|
opacity: .6
|
|
2139
4273
|
} }, open ? "▲" : "▼")), open ? h("div", { style: {
|
|
@@ -2153,7 +4287,7 @@ window.__ModuleLoader__.load({
|
|
|
2153
4287
|
fontSize: 10,
|
|
2154
4288
|
color: T.text2,
|
|
2155
4289
|
borderBottom: `1px solid ${T.border}`
|
|
2156
|
-
} }, "
|
|
4290
|
+
} }, t("team.pick")), h("button", {
|
|
2157
4291
|
onClick: () => select(null),
|
|
2158
4292
|
style: {
|
|
2159
4293
|
display: "flex",
|
|
@@ -2182,14 +4316,14 @@ window.__ModuleLoader__.load({
|
|
|
2182
4316
|
} }, h("div", { style: {
|
|
2183
4317
|
fontWeight: 600,
|
|
2184
4318
|
lineHeight: 1.35
|
|
2185
|
-
} }, "
|
|
4319
|
+
} }, t("team.none")), h("div", { style: {
|
|
2186
4320
|
fontSize: 10.5,
|
|
2187
4321
|
color: T.text2,
|
|
2188
4322
|
marginTop: 3,
|
|
2189
4323
|
lineHeight: 1.45,
|
|
2190
4324
|
whiteSpace: "normal",
|
|
2191
4325
|
wordBreak: "break-word"
|
|
2192
|
-
} }, "
|
|
4326
|
+
} }, t("team.noneNote"))), !active ? h("span", { style: {
|
|
2193
4327
|
marginLeft: "auto",
|
|
2194
4328
|
color: T.text2,
|
|
2195
4329
|
fontSize: 12,
|
|
@@ -2238,7 +4372,10 @@ window.__ModuleLoader__.load({
|
|
|
2238
4372
|
}
|
|
2239
4373
|
/** 解包 remote 信封:失败抛错;成功返回 value。 */
|
|
2240
4374
|
function unwrap(res, what) {
|
|
2241
|
-
if (!res || !res.ok) throw new Error(
|
|
4375
|
+
if (!res || !res.ok) throw new Error(t("common.remoteCallFailed", {
|
|
4376
|
+
what: what || "remote",
|
|
4377
|
+
detail: res && res.error && (res.error.message || res.error.code) || t("common.unknownError")
|
|
4378
|
+
}));
|
|
2242
4379
|
return res.value;
|
|
2243
4380
|
}
|
|
2244
4381
|
function TeamFlowView(props) {
|
|
@@ -2257,7 +4394,7 @@ window.__ModuleLoader__.load({
|
|
|
2257
4394
|
if (!api) {
|
|
2258
4395
|
setState((s) => ({
|
|
2259
4396
|
...s,
|
|
2260
|
-
err: "
|
|
4397
|
+
err: t("common.remoteNotReady")
|
|
2261
4398
|
}));
|
|
2262
4399
|
return;
|
|
2263
4400
|
}
|
|
@@ -2360,7 +4497,10 @@ window.__ModuleLoader__.load({
|
|
|
2360
4497
|
display: "flex",
|
|
2361
4498
|
flexDirection: "column",
|
|
2362
4499
|
gap: 10,
|
|
2363
|
-
padding: "4px 16px"
|
|
4500
|
+
padding: "4px 16px 10px",
|
|
4501
|
+
height: "100%",
|
|
4502
|
+
minHeight: 0,
|
|
4503
|
+
overflow: "hidden"
|
|
2364
4504
|
} }, h("div", { style: {
|
|
2365
4505
|
display: "flex",
|
|
2366
4506
|
alignItems: "center",
|
|
@@ -2383,7 +4523,7 @@ window.__ModuleLoader__.load({
|
|
|
2383
4523
|
fontWeight: 700,
|
|
2384
4524
|
fontSize: 14,
|
|
2385
4525
|
lineHeight: "18px"
|
|
2386
|
-
} }, "
|
|
4526
|
+
} }, t("workbench.title")), h("span", { style: {
|
|
2387
4527
|
fontSize: 11,
|
|
2388
4528
|
color: T.text2,
|
|
2389
4529
|
display: "flex",
|
|
@@ -2396,7 +4536,7 @@ window.__ModuleLoader__.load({
|
|
|
2396
4536
|
display: "inline-block",
|
|
2397
4537
|
background: anyRunning ? T.success : T.text2,
|
|
2398
4538
|
animation: anyRunning ? "tf-pulse 1.6s ease-in-out infinite" : "none"
|
|
2399
|
-
} }), anyRunning ? "
|
|
4539
|
+
} }), anyRunning ? t("workbench.running") : t("workbench.idle"))), h("button", {
|
|
2400
4540
|
onClick: refresh,
|
|
2401
4541
|
style: {
|
|
2402
4542
|
...btn,
|
|
@@ -2405,10 +4545,13 @@ window.__ModuleLoader__.load({
|
|
|
2405
4545
|
alignItems: "center",
|
|
2406
4546
|
gap: 5
|
|
2407
4547
|
}
|
|
2408
|
-
}, "
|
|
4548
|
+
}, t("workbench.refresh")), canResume ? h("button", {
|
|
2409
4549
|
onClick: onResume,
|
|
2410
4550
|
disabled: busy,
|
|
2411
|
-
title:
|
|
4551
|
+
title: t("workbench.resumeTip", {
|
|
4552
|
+
id: activeRun.id,
|
|
4553
|
+
status: runStatusText(activeRun.status)
|
|
4554
|
+
}),
|
|
2412
4555
|
style: {
|
|
2413
4556
|
...btn,
|
|
2414
4557
|
background: T.error,
|
|
@@ -2416,14 +4559,14 @@ window.__ModuleLoader__.load({
|
|
|
2416
4559
|
border: "none",
|
|
2417
4560
|
fontWeight: 600
|
|
2418
4561
|
}
|
|
2419
|
-
}, busy ? "
|
|
4562
|
+
}, busy ? t("workbench.resuming") : t("workbench.resumeBtn", { id: String(activeRun.id).slice(-6) })) : null), err ? h("div", { style: {
|
|
2420
4563
|
color: T.error,
|
|
2421
4564
|
fontSize: 12,
|
|
2422
4565
|
background: `color-mix(in srgb, ${T.error} 8%, transparent)`,
|
|
2423
4566
|
border: `1px solid color-mix(in srgb, ${T.error} 30%, transparent)`,
|
|
2424
4567
|
borderRadius: 8,
|
|
2425
4568
|
padding: "7px 11px"
|
|
2426
|
-
} },
|
|
4569
|
+
} }, t("workbench.loadFailed", { err })) : null, needHuman.length > 0 ? h("div", { style: {
|
|
2427
4570
|
display: "flex",
|
|
2428
4571
|
alignItems: "center",
|
|
2429
4572
|
gap: 10,
|
|
@@ -2436,13 +4579,13 @@ window.__ModuleLoader__.load({
|
|
|
2436
4579
|
fontWeight: 700,
|
|
2437
4580
|
color: T.warn,
|
|
2438
4581
|
fontSize: 12.5
|
|
2439
|
-
} },
|
|
4582
|
+
} }, t("workbench.needsHumanBanner", { n: needHuman.length })), needHuman.slice(0, 5).map((item) => {
|
|
2440
4583
|
const kind = (backlog.requirements || []).some((r) => r.id === item.id) ? "req" : (backlog.tasks || []).some((t) => t.id === item.id) ? "task" : "bug";
|
|
2441
4584
|
const fin = kind === "bug" ? "verified" : "accepted";
|
|
2442
4585
|
return h("button", {
|
|
2443
4586
|
key: item.id,
|
|
2444
4587
|
onClick: async () => {
|
|
2445
|
-
await api.backlogUpdate(kind, item.id, fin, props.sessionId, "
|
|
4588
|
+
await api.backlogUpdate(kind, item.id, fin, props.sessionId, t("workbench.manualReason"));
|
|
2446
4589
|
refresh();
|
|
2447
4590
|
},
|
|
2448
4591
|
style: {
|
|
@@ -2452,7 +4595,7 @@ window.__ModuleLoader__.load({
|
|
|
2452
4595
|
border: "none",
|
|
2453
4596
|
fontWeight: 600
|
|
2454
4597
|
}
|
|
2455
|
-
},
|
|
4598
|
+
}, t("workbench.handle", { id: item.id }));
|
|
2456
4599
|
})) : null, h("div", { style: {
|
|
2457
4600
|
display: "flex",
|
|
2458
4601
|
alignItems: "center",
|
|
@@ -2461,10 +4604,10 @@ window.__ModuleLoader__.load({
|
|
|
2461
4604
|
} }, h("button", {
|
|
2462
4605
|
onClick: () => setTab("pipeline"),
|
|
2463
4606
|
style: tabBtn(tab === "pipeline")
|
|
2464
|
-
}, "
|
|
4607
|
+
}, t("workbench.tabPipeline")), h("button", {
|
|
2465
4608
|
onClick: () => setTab("board"),
|
|
2466
4609
|
style: tabBtn(tab === "board")
|
|
2467
|
-
}, "
|
|
4610
|
+
}, t("workbench.tabBoard")), h("div", { style: {
|
|
2468
4611
|
marginLeft: "auto",
|
|
2469
4612
|
display: "flex",
|
|
2470
4613
|
alignItems: "center",
|
|
@@ -2480,14 +4623,14 @@ window.__ModuleLoader__.load({
|
|
|
2480
4623
|
color: activeRun.status === "interrupted" ? T.warn : T.text2,
|
|
2481
4624
|
border: `1px solid ${T.border}`
|
|
2482
4625
|
}
|
|
2483
|
-
}, `#${String(activeRun.id).slice(-8)} · ${
|
|
4626
|
+
}, `#${String(activeRun.id).slice(-8)} · ${runStatusText(activeRun.status)}`) : null, total && total.input + total.cacheRead + total.cacheWrite + total.output > 0 ? h("span", {
|
|
2484
4627
|
style: {
|
|
2485
4628
|
fontSize: 11.5,
|
|
2486
4629
|
fontFamily: MONO,
|
|
2487
4630
|
color: T.text2,
|
|
2488
4631
|
cursor: "help"
|
|
2489
4632
|
},
|
|
2490
|
-
title: "
|
|
4633
|
+
title: t("token.allStagesTip")
|
|
2491
4634
|
}, `∑ ⇅${fmtTokens(total.input)}/⇅${fmtTokens(total.cacheRead)}·⬆${fmtTokens(total.output)}`) : null)), h("div", { style: {
|
|
2492
4635
|
display: "flex",
|
|
2493
4636
|
alignItems: "center",
|
|
@@ -2495,7 +4638,7 @@ window.__ModuleLoader__.load({
|
|
|
2495
4638
|
fontSize: 12,
|
|
2496
4639
|
flexWrap: "wrap"
|
|
2497
4640
|
} }, h("span", {
|
|
2498
|
-
title:
|
|
4641
|
+
title: t("workbench.workspaceTip", { path: workspace && workspace.path || t("workbench.noWorkspace") }),
|
|
2499
4642
|
style: {
|
|
2500
4643
|
display: "inline-flex",
|
|
2501
4644
|
alignItems: "center",
|
|
@@ -2520,7 +4663,7 @@ window.__ModuleLoader__.load({
|
|
|
2520
4663
|
display: "flex",
|
|
2521
4664
|
alignItems: "center",
|
|
2522
4665
|
gap: 5
|
|
2523
|
-
} }, h("span", { style: { color: T.text2 } }, "
|
|
4666
|
+
} }, h("span", { style: { color: T.text2 } }, t("workbench.history")), runs.length ? runs.map((r) => {
|
|
2524
4667
|
const sel = r.id === (runId || runs[0] && runs[0].id);
|
|
2525
4668
|
return h("button", {
|
|
2526
4669
|
key: r.id,
|
|
@@ -2531,7 +4674,18 @@ window.__ModuleLoader__.load({
|
|
|
2531
4674
|
}) : h("span", { style: {
|
|
2532
4675
|
color: T.text2,
|
|
2533
4676
|
fontSize: 11.5
|
|
2534
|
-
} }, "
|
|
4677
|
+
} }, t("common.noneDash"))) : null, tab === "pipeline" && activeRun && activeRun.address ? h("button", {
|
|
4678
|
+
style: chipBtn(false),
|
|
4679
|
+
title: t("workbench.openRightBarTip"),
|
|
4680
|
+
onClick: () => {
|
|
4681
|
+
if (!(props.openResource && props.openResource(activeRun.address, activeRun.id))) console.warn("[teamflow] 右侧栏不可用,run 详情请在画布节点里查看");
|
|
4682
|
+
}
|
|
4683
|
+
}, t("workbench.openRightBarBtn")) : null), h("div", { style: {
|
|
4684
|
+
flex: 1,
|
|
4685
|
+
minHeight: 0,
|
|
4686
|
+
display: "flex",
|
|
4687
|
+
flexDirection: "column"
|
|
4688
|
+
} }, tab === "pipeline" ? h(PipelinePanel, {
|
|
2535
4689
|
active,
|
|
2536
4690
|
api,
|
|
2537
4691
|
runId: activeRun ? activeRun.id : null,
|
|
@@ -2547,7 +4701,7 @@ window.__ModuleLoader__.load({
|
|
|
2547
4701
|
setTab("pipeline");
|
|
2548
4702
|
setRunId(rid);
|
|
2549
4703
|
}
|
|
2550
|
-
}));
|
|
4704
|
+
})));
|
|
2551
4705
|
}
|
|
2552
4706
|
if (typeof document !== "undefined") {
|
|
2553
4707
|
const styleEl = document.createElement("style");
|
|
@@ -2557,41 +4711,109 @@ window.__ModuleLoader__.load({
|
|
|
2557
4711
|
@keyframes tf-flow { from { stroke-dashoffset: 0 } to { stroke-dashoffset: -28 } }`;
|
|
2558
4712
|
document.head.appendChild(styleEl);
|
|
2559
4713
|
}
|
|
2560
|
-
/**
|
|
4714
|
+
/**
|
|
4715
|
+
* 注册三处:
|
|
4716
|
+
* 1. `sidebar.panellist` + `main`(key = 'teamflow'):**全局面板**——侧边栏图标 + 中央主区。
|
|
4717
|
+
* 两者 id 必须一致:宿主 `layout.selectPanel(id)` 对未注册的 main key 直接抛错。
|
|
4718
|
+
* 2. `conversation.view` tab(会话内工作台)+ `conversation.input.right`(团队选择按钮)。
|
|
4719
|
+
* 3. 右栏 run 详情 tab 类型 + body(`sidebarRightTabs` + `sidebar.right.pane.tab`)。
|
|
4720
|
+
*/
|
|
2561
4721
|
async function apply(ctx) {
|
|
2562
4722
|
await ctx.remote.$mount(TEAMFLOW_REMOTE_CONTRIBUTION);
|
|
2563
4723
|
const teamflow = ctx.get("remote.teamflow");
|
|
2564
|
-
const
|
|
4724
|
+
const t = ctx.locale.bind(NS);
|
|
4725
|
+
ctx.effect(() => ctx.locale.register(NS, {
|
|
4726
|
+
zh,
|
|
4727
|
+
en
|
|
4728
|
+
}), "teamflow: dictionaries");
|
|
4729
|
+
ctx.effect(() => ctx.locale.subscribe(() => setTranslator(t, () => ctx.locale.getSnapshot().active)), "teamflow: translator sync");
|
|
4730
|
+
setTranslator(t, () => ctx.locale.getSnapshot().active);
|
|
4731
|
+
const pushLocaleToHost = (active) => {
|
|
4732
|
+
try {
|
|
4733
|
+
if (typeof active !== "string" || !active) return;
|
|
4734
|
+
const p = teamflow.setLocale(active);
|
|
4735
|
+
if (p && typeof p.catch === "function") p.catch(() => {});
|
|
4736
|
+
} catch (e) {}
|
|
4737
|
+
};
|
|
4738
|
+
ctx.effect(() => {
|
|
4739
|
+
pushLocaleToHost(ctx.locale.getSnapshot().active);
|
|
4740
|
+
return ctx.locale.subscribe(() => pushLocaleToHost(ctx.locale.getSnapshot().active));
|
|
4741
|
+
}, "teamflow: host locale push");
|
|
4742
|
+
const openResourceSafe = (address, label, quiet) => {
|
|
2565
4743
|
try {
|
|
2566
4744
|
const sidebarRight = ctx.get("sidebarRight");
|
|
2567
4745
|
if (!sidebarRight || typeof sidebarRight.openResource !== "function") {
|
|
2568
|
-
console.warn("[teamflow]
|
|
2569
|
-
return;
|
|
4746
|
+
if (!quiet) console.warn("[teamflow] 右侧栏服务不可用", label);
|
|
4747
|
+
return false;
|
|
2570
4748
|
}
|
|
2571
4749
|
sidebarRight.openResource(address);
|
|
4750
|
+
return true;
|
|
2572
4751
|
} catch (e) {
|
|
2573
|
-
console.warn("[teamflow]
|
|
4752
|
+
if (!quiet) console.warn("[teamflow] 打开右侧栏失败", label, e && e.message);
|
|
4753
|
+
return false;
|
|
2574
4754
|
}
|
|
2575
4755
|
};
|
|
4756
|
+
const openArtifact = (address, name) => {
|
|
4757
|
+
openResourceSafe(address, name);
|
|
4758
|
+
};
|
|
4759
|
+
ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({
|
|
4760
|
+
name: "sidebar.panellist",
|
|
4761
|
+
id: "teamflow",
|
|
4762
|
+
order: 60,
|
|
4763
|
+
locale: NS,
|
|
4764
|
+
label: () => t("workbench.title")
|
|
4765
|
+
}, TeamflowPanelIcon));
|
|
4766
|
+
ctx.slots.inject("main", () => ctx.slots.register({
|
|
4767
|
+
name: "main",
|
|
4768
|
+
key: "teamflow",
|
|
4769
|
+
locale: NS,
|
|
4770
|
+
inject: () => ({
|
|
4771
|
+
remote: teamflow,
|
|
4772
|
+
sessions: ctx.get("sessions"),
|
|
4773
|
+
layout: ctx.get("layout"),
|
|
4774
|
+
openResource: openResourceSafe,
|
|
4775
|
+
openArtifact
|
|
4776
|
+
})
|
|
4777
|
+
}, GlobalPanel));
|
|
4778
|
+
try {
|
|
4779
|
+
const tabs = ctx.get("sidebarRightTabs");
|
|
4780
|
+
if (tabs && typeof tabs.register === "function") ctx.effect(() => tabs.register(runTabDefinition()), "teamflow: run tab type");
|
|
4781
|
+
else console.warn("[teamflow] sidebarRightTabs 不可用,run 详情右栏 tab 未注册(面板内联详情仍可用)");
|
|
4782
|
+
} catch (e) {
|
|
4783
|
+
console.warn("[teamflow] 注册 run tab 类型失败", e && e.message);
|
|
4784
|
+
}
|
|
4785
|
+
ctx.slots.inject("sidebar.right.pane.tab", () => ctx.slots.register({
|
|
4786
|
+
name: "sidebar.right.pane.tab",
|
|
4787
|
+
key: RUN_TAB_ID,
|
|
4788
|
+
locale: NS,
|
|
4789
|
+
inject: () => ({
|
|
4790
|
+
remote: teamflow,
|
|
4791
|
+
openArtifact
|
|
4792
|
+
})
|
|
4793
|
+
}, RunDetailTab));
|
|
2576
4794
|
ctx.slots.inject("conversation.view", () => ctx.slots.register({
|
|
2577
4795
|
name: "conversation.view",
|
|
2578
4796
|
id: "teamflow",
|
|
2579
4797
|
order: 20,
|
|
2580
|
-
|
|
4798
|
+
locale: NS,
|
|
4799
|
+
label: () => `🏭 ${t("workbench.title")}`,
|
|
2581
4800
|
inject: (sessionId) => ({
|
|
2582
4801
|
sessionId,
|
|
2583
4802
|
remote: teamflow,
|
|
2584
4803
|
sessions: ctx.get("sessions"),
|
|
2585
|
-
openArtifact
|
|
4804
|
+
openArtifact,
|
|
4805
|
+
openResource: openResourceSafe
|
|
2586
4806
|
})
|
|
2587
4807
|
}, TeamFlowView));
|
|
2588
4808
|
ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
|
|
2589
4809
|
name: "conversation.input.right",
|
|
2590
4810
|
id: "teamflow-team-select",
|
|
2591
4811
|
order: 5,
|
|
4812
|
+
locale: NS,
|
|
2592
4813
|
inject: (sessionId) => ({
|
|
2593
4814
|
sessionId,
|
|
2594
|
-
remote: teamflow
|
|
4815
|
+
remote: teamflow,
|
|
4816
|
+
locale: ctx.locale.getSnapshot().active
|
|
2595
4817
|
})
|
|
2596
4818
|
}, TeamSelector));
|
|
2597
4819
|
}
|