dsh-github-flow 0.6.0 → 0.7.0
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/README.en.md +98 -15
- package/README.md +101 -15
- package/docs/UPGRADE-NOTES.md +12 -11
- package/docs/architecture.md +56 -13
- package/lib/api/route-helpers.d.ts +19 -0
- package/lib/api/route-helpers.js +54 -0
- package/lib/api/routes-actions.js +75 -1
- package/lib/api/routes-lifecycle.js +8 -1
- package/lib/api/routes-overview.d.ts +26 -0
- package/lib/api/routes-overview.js +276 -34
- package/lib/api/routes-proxy.js +8 -1
- package/lib/api/routes.d.ts +11 -1
- package/lib/api/routes.js +15 -2
- package/lib/cache.d.ts +0 -21
- package/lib/cache.js +0 -37
- package/lib/client.d.ts +2 -1
- package/lib/client.js +1585 -429
- package/lib/destructive.d.ts +59 -0
- package/lib/destructive.js +285 -0
- package/lib/executor/args-guard.d.ts +17 -0
- package/lib/executor/args-guard.js +23 -0
- package/lib/executor/failure-anchors.d.ts +52 -0
- package/lib/executor/failure-anchors.js +116 -0
- package/lib/executor/pr-review.d.ts +95 -0
- package/lib/executor/pr-review.js +175 -0
- package/lib/executor/run-log.d.ts +36 -0
- package/lib/executor/run-log.js +78 -0
- package/lib/executor.d.ts +156 -3
- package/lib/executor.js +594 -28
- package/lib/github-service.d.ts +1 -1
- package/lib/main.d.ts +92 -18
- package/lib/main.js +186 -95
- package/lib/renderers/common.d.ts +0 -3
- package/lib/renderers/common.js +8 -44
- package/lib/renderers/markdown.d.ts +1 -0
- package/lib/renderers/markdown.js +1 -0
- package/lib/renderers/pr.js +26 -1
- package/lib/renderers/task.d.ts +9 -0
- package/lib/renderers/task.js +120 -0
- package/lib/review-rules.d.ts +60 -0
- package/lib/review-rules.js +204 -0
- package/lib/shared/state-badges.d.ts +42 -0
- package/lib/shared/state-badges.js +102 -0
- package/lib/task-primitives.d.ts +109 -0
- package/lib/task-primitives.js +194 -0
- package/lib/tool-tiers.d.ts +57 -0
- package/lib/tool-tiers.js +168 -0
- package/lib/tools/agent-task.js +4 -4
- package/lib/tools/api.js +18 -6
- package/lib/tools/cache.js +42 -40
- package/lib/tools/codespace.js +4 -12
- package/lib/tools/discussion.js +4 -4
- package/lib/tools/gist.d.ts +1 -39
- package/lib/tools/gist.js +4 -14
- package/lib/tools/help.js +52 -13
- package/lib/tools/issue.js +16 -28
- package/lib/tools/label.js +4 -15
- package/lib/tools/pr.d.ts +47 -0
- package/lib/tools/pr.js +170 -33
- package/lib/tools/project.js +4 -13
- package/lib/tools/release.js +11 -22
- package/lib/tools/repo.js +4 -4
- package/lib/tools/ruleset.js +9 -6
- package/lib/tools/run.js +76 -24
- package/lib/tools/secret.js +11 -17
- package/lib/tools/skill.js +18 -22
- package/lib/tools/status.js +4 -4
- package/lib/tools/task.d.ts +219 -0
- package/lib/tools/task.js +457 -0
- package/lib/types.d.ts +183 -1
- package/lib/ui/help-modal.d.ts +1 -1
- package/lib/ui/help-modal.js +20 -10
- package/lib/ui/issue-modal.js +7 -3
- package/lib/ui/pr-modal.js +50 -11
- package/lib/ui/run-modal.d.ts +26 -0
- package/lib/ui/run-modal.js +486 -0
- package/lib/ui/styles.d.ts +1 -1
- package/lib/ui/styles.js +370 -44
- package/lib/ui/sync-settings-modal.js +1 -1
- package/lib/ui/workspace-panel.js +78 -23
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +9 -2
package/lib/client.js
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
// 状态→徽章映射与服务端 CLI 输出共用同一份实现(构建期被内联进本 bundle 的作用域)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
1
21
|
window.__ModuleLoader__.load({
|
|
2
22
|
id: "dsh-github-flow",
|
|
3
23
|
factory: (require) => {
|
|
@@ -6,54 +26,116 @@ window.__ModuleLoader__.load({
|
|
|
6
26
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
27
|
var React = require("react");
|
|
8
28
|
|
|
29
|
+
// ---- [Bundled: state-badges.js] ----
|
|
30
|
+
/**
|
|
31
|
+
* 状态 → 徽章的唯一映射(服务端 Markdown 与客户端 UI 共用)。
|
|
32
|
+
*
|
|
33
|
+
* 为什么必须共用:同一个状态(OPEN / MERGED / CI failure …)在 CLI 输出与右侧栏
|
|
34
|
+
* 面板上必须是同一套颜色与同一套文案。此前这条映射在 9 个文件里各写了一遍
|
|
35
|
+
* (服务端 5 个 `format*Badge`、客户端 6 处内联三元式),新增一个状态要改 12 处,
|
|
36
|
+
* 漏一处就出现「同一个 PR 在两处显示不同颜色」。
|
|
37
|
+
*
|
|
38
|
+
* 约束(构建期强依赖):本文件必须**零依赖**。scripts/build.mjs 会把它的 import
|
|
39
|
+
* 行剥掉、整体内联进客户端 bundle;一旦这里 import 了别的东西,那些符号在 bundle
|
|
40
|
+
* 作用域里就是 undefined,表现是整个驾驶舱白屏。
|
|
41
|
+
*
|
|
42
|
+
* `tone` 直接就是 CSS class 后缀(`dsh-badge-${tone}`),客户端不再需要映射表。
|
|
43
|
+
*/
|
|
44
|
+
/** 服务端 Markdown 渲染用:`🟢 OPEN` / `OPEN` / `-` */
|
|
45
|
+
function badgeText(badge) {
|
|
46
|
+
if (!badge)
|
|
47
|
+
return '-';
|
|
48
|
+
return badge.icon ? `${badge.icon} ${badge.label}` : badge.label;
|
|
49
|
+
}
|
|
50
|
+
/** PR / Issue 状态(空 state 优先于 draft,与旧实现判定顺序一致) */
|
|
51
|
+
function prStateBadge(state, isDraft) {
|
|
52
|
+
const s = String(state || '').toUpperCase();
|
|
53
|
+
// 旧实现把 `if (!state) return '-'` 放在最前、draft 判定在其后。
|
|
54
|
+
// 顺序反了会让 formatStateBadge(undefined, true) 从 '-' 变成 '⚪ DRAFT'——那是行为变化。
|
|
55
|
+
if (!s)
|
|
56
|
+
return null;
|
|
57
|
+
if (isDraft)
|
|
58
|
+
return { icon: '⚪', label: 'DRAFT', tone: 'yellow' };
|
|
59
|
+
if (s === 'OPEN')
|
|
60
|
+
return { icon: '🟢', label: 'OPEN', tone: 'green' };
|
|
61
|
+
if (s === 'MERGED')
|
|
62
|
+
return { icon: '🟣', label: 'MERGED', tone: 'purple' };
|
|
63
|
+
if (s === 'CLOSED')
|
|
64
|
+
return { icon: '🔴', label: 'CLOSED', tone: 'red' };
|
|
65
|
+
return genericStateBadge(s);
|
|
66
|
+
}
|
|
67
|
+
/** Issue 状态(没有 MERGED 这一档) */
|
|
68
|
+
function issueStateBadge(state) {
|
|
69
|
+
const s = String(state || '').toUpperCase();
|
|
70
|
+
if (s === 'OPEN')
|
|
71
|
+
return { icon: '🟢', label: 'OPEN', tone: 'green' };
|
|
72
|
+
if (s === 'CLOSED')
|
|
73
|
+
return { icon: '🔴', label: 'CLOSED', tone: 'red' };
|
|
74
|
+
return genericStateBadge(s);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* CI 状态。
|
|
78
|
+
* 第二参数按调用域语义不同:run 域传 conclusion(success/failure/…),
|
|
79
|
+
* pr checks 域传 bucket(pass/fail/pending/…)——取值族不同但映射到同一组徽章;
|
|
80
|
+
* 第一参数兜底(in_progress/queued 等进行中态)。
|
|
81
|
+
*/
|
|
82
|
+
function ciBadge(status, conclusion) {
|
|
83
|
+
const conc = String(conclusion || '').toLowerCase();
|
|
84
|
+
const st = String(status || '').toLowerCase();
|
|
85
|
+
if (conc === 'success' || conc === 'pass')
|
|
86
|
+
return { icon: '✅', label: conc, tone: 'green' };
|
|
87
|
+
if (conc === 'failure' || conc === 'fail')
|
|
88
|
+
return { icon: '❌', label: conc, tone: 'red' };
|
|
89
|
+
if (conc === 'cancelled' || conc === 'cancel')
|
|
90
|
+
return { icon: '🚫', label: conc, tone: 'red' };
|
|
91
|
+
if (conc === 'skipped' || conc === 'skipping')
|
|
92
|
+
return { icon: '⏭️', label: conc, tone: 'neutral' };
|
|
93
|
+
if (conc === 'pending')
|
|
94
|
+
return { icon: '⏳', label: 'pending', tone: 'yellow' };
|
|
95
|
+
if (st === 'in_progress')
|
|
96
|
+
return { icon: '⏳', label: 'running', tone: 'yellow' };
|
|
97
|
+
if (st === 'queued')
|
|
98
|
+
return { icon: '⏱️', label: 'queued', tone: 'yellow' };
|
|
99
|
+
const fallback = conc || st;
|
|
100
|
+
if (!fallback)
|
|
101
|
+
return null;
|
|
102
|
+
return { icon: '', label: fallback, tone: 'neutral' };
|
|
103
|
+
}
|
|
104
|
+
/** 合并冲突状态 */
|
|
105
|
+
function conflictBadge(status) {
|
|
106
|
+
const s = String(status || '').toUpperCase();
|
|
107
|
+
if (s === 'CLEAN')
|
|
108
|
+
return { icon: '✅', label: 'CLEAN', tone: 'green' };
|
|
109
|
+
if (s === 'DIRTY' || s === 'CONFLICT')
|
|
110
|
+
return { icon: '⚠️', label: 'CONFLICT', tone: 'yellow' };
|
|
111
|
+
if (s === 'BLOCKED')
|
|
112
|
+
return { icon: '⛔', label: 'BLOCKED', tone: 'red' };
|
|
113
|
+
if (!s)
|
|
114
|
+
return null;
|
|
115
|
+
return { icon: '', label: s, tone: 'neutral' };
|
|
116
|
+
}
|
|
117
|
+
/** Release 状态(Draft > Prerelease > Stable) */
|
|
118
|
+
function releaseBadge(isDraft, isPrerelease) {
|
|
119
|
+
if (isDraft)
|
|
120
|
+
return { icon: '⚪', label: 'DRAFT', tone: 'yellow' };
|
|
121
|
+
if (isPrerelease)
|
|
122
|
+
return { icon: '🍼', label: 'PRE', tone: 'yellow' };
|
|
123
|
+
return { icon: '✅', label: 'STABLE', tone: 'green' };
|
|
124
|
+
}
|
|
125
|
+
/** 兜底:任意状态字符串转徽章(未知状态给 neutral,不猜颜色) */
|
|
126
|
+
function genericStateBadge(state) {
|
|
127
|
+
const s = String(state || '').toUpperCase();
|
|
128
|
+
if (!s)
|
|
129
|
+
return null;
|
|
130
|
+
return { icon: '', label: s, tone: 'neutral' };
|
|
131
|
+
}
|
|
132
|
+
|
|
9
133
|
// ---- [Bundled: styles.js] ----
|
|
10
134
|
/**
|
|
11
135
|
* DSH GitHub Flow - 主题自适应样式系统 (Light / Dark 动态联动)
|
|
12
136
|
* 遵循 DSH 原生 DSW (DeepSeek Web) 设计语言规范
|
|
13
137
|
*/
|
|
14
138
|
const GITHUB_PANEL_CSS = `
|
|
15
|
-
.dsh-github-icon {
|
|
16
|
-
display: flex;
|
|
17
|
-
align-items: center;
|
|
18
|
-
justify-content: center;
|
|
19
|
-
width: 100%;
|
|
20
|
-
height: 100%;
|
|
21
|
-
cursor: pointer;
|
|
22
|
-
color: var(--dsw-alias-label-secondary, #656d76);
|
|
23
|
-
transition: color 0.15s ease;
|
|
24
|
-
}
|
|
25
|
-
.dsh-github-icon:hover {
|
|
26
|
-
color: var(--dsw-alias-label-primary, #1f2328);
|
|
27
|
-
}
|
|
28
|
-
.dsh-github-icon-inner {
|
|
29
|
-
display: flex;
|
|
30
|
-
align-items: center;
|
|
31
|
-
justify-content: center;
|
|
32
|
-
width: 28px;
|
|
33
|
-
height: 28px;
|
|
34
|
-
border-radius: 7px;
|
|
35
|
-
background: transparent;
|
|
36
|
-
color: inherit;
|
|
37
|
-
border: 1px solid transparent;
|
|
38
|
-
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
|
39
|
-
}
|
|
40
|
-
.dsh-github-icon-inner.active {
|
|
41
|
-
background: var(--dsw-alias-bg-module-platform, rgba(9, 105, 218, 0.08));
|
|
42
|
-
color: var(--dsw-alias-link, #0969da);
|
|
43
|
-
border-color: var(--dsw-alias-border-l2, rgba(9, 105, 218, 0.2));
|
|
44
|
-
}
|
|
45
|
-
body[data-ds-dark-theme] .dsh-github-icon {
|
|
46
|
-
color: var(--dsw-alias-label-secondary, #9ca3af);
|
|
47
|
-
}
|
|
48
|
-
body[data-ds-dark-theme] .dsh-github-icon:hover {
|
|
49
|
-
color: var(--dsw-alias-label-primary, #ffffff);
|
|
50
|
-
}
|
|
51
|
-
body[data-ds-dark-theme] .dsh-github-icon-inner.active {
|
|
52
|
-
background: linear-gradient(135deg, rgba(46, 160, 67, 0.3) 0%, rgba(31, 111, 235, 0.45) 100%);
|
|
53
|
-
color: #ffffff;
|
|
54
|
-
box-shadow: 0 0 12px rgba(31, 111, 235, 0.4);
|
|
55
|
-
border-color: rgba(88, 166, 255, 0.45);
|
|
56
|
-
}
|
|
57
139
|
.dsh-octocat-badge {
|
|
58
140
|
display: flex;
|
|
59
141
|
align-items: center;
|
|
@@ -2291,178 +2373,495 @@ font-size: 12px;
|
|
|
2291
2373
|
opacity: 0.6;
|
|
2292
2374
|
flex: 0 0 auto;
|
|
2293
2375
|
}
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
style.id = styleId;
|
|
2304
|
-
style.textContent = GITHUB_PANEL_CSS;
|
|
2305
|
-
document.head.appendChild(style);
|
|
2306
|
-
return () => style.remove();
|
|
2376
|
+
.dsh-github-run-modal-dialog {
|
|
2377
|
+
width: 92% !important;
|
|
2378
|
+
max-width: 960px !important;
|
|
2379
|
+
height: 84vh !important;
|
|
2380
|
+
max-height: 84vh !important;
|
|
2381
|
+
display: flex !important;
|
|
2382
|
+
flex-direction: column !important;
|
|
2383
|
+
overflow: hidden !important;
|
|
2384
|
+
padding: 0 !important;
|
|
2307
2385
|
}
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
function formatRelativeTime(iso) {
|
|
2314
|
-
if (!iso || typeof iso !== "string")
|
|
2315
|
-
return "";
|
|
2316
|
-
var t = Date.parse(iso);
|
|
2317
|
-
if (isNaN(t))
|
|
2318
|
-
return "";
|
|
2319
|
-
var diff = Date.now() - t;
|
|
2320
|
-
if (diff < 0)
|
|
2321
|
-
return "刚刚";
|
|
2322
|
-
var MIN = 60 * 1000, HOUR = 60 * MIN, DAY = 24 * HOUR;
|
|
2323
|
-
if (diff < MIN)
|
|
2324
|
-
return "刚刚";
|
|
2325
|
-
if (diff < HOUR)
|
|
2326
|
-
return Math.floor(diff / MIN) + "分钟前";
|
|
2327
|
-
if (diff < DAY)
|
|
2328
|
-
return Math.floor(diff / HOUR) + "小时前";
|
|
2329
|
-
if (diff < 7 * DAY)
|
|
2330
|
-
return Math.floor(diff / DAY) + "天前";
|
|
2331
|
-
var d = new Date(t);
|
|
2332
|
-
return d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0");
|
|
2386
|
+
.dsh-github-run-modal-header {
|
|
2387
|
+
padding: 12px 16px !important;
|
|
2388
|
+
border-bottom: 1px solid var(--dsw-alias-border-l2, #e1e4e8);
|
|
2389
|
+
background: var(--dsw-alias-bg-layer-1, #f6f8fa);
|
|
2390
|
+
flex-shrink: 0;
|
|
2333
2391
|
}
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
var hasFail = false, hasPending = false;
|
|
2338
|
-
for (var i = 0; i < checks.length; i++) {
|
|
2339
|
-
var c = checks[i] || {};
|
|
2340
|
-
var status = String(c.status || "").toLowerCase();
|
|
2341
|
-
var conclusion = String(c.conclusion || "").toLowerCase();
|
|
2342
|
-
if (conclusion === "failure" || conclusion === "timed_out" || conclusion === "cancelled")
|
|
2343
|
-
hasFail = true;
|
|
2344
|
-
else if (conclusion === "success" || conclusion === "skipped" || conclusion === "neutral") { }
|
|
2345
|
-
else if (status === "in_progress" || status === "queued" || conclusion === "pending" || (!conclusion && status))
|
|
2346
|
-
hasPending = true;
|
|
2347
|
-
}
|
|
2348
|
-
if (hasFail)
|
|
2349
|
-
return "fail";
|
|
2350
|
-
if (hasPending)
|
|
2351
|
-
return "pending";
|
|
2352
|
-
return "pass";
|
|
2392
|
+
body[data-ds-dark-theme] .dsh-github-run-modal-header {
|
|
2393
|
+
border-bottom-color: var(--dsw-alias-border-l1, #30363d);
|
|
2394
|
+
background: var(--dsw-alias-bg-layer-1, #16161a);
|
|
2353
2395
|
}
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2396
|
+
.dsh-github-run-body {
|
|
2397
|
+
padding: 14px 16px 20px;
|
|
2398
|
+
overflow-y: auto;
|
|
2399
|
+
flex: 1;
|
|
2400
|
+
min-height: 0;
|
|
2401
|
+
display: flex;
|
|
2402
|
+
flex-direction: column;
|
|
2403
|
+
gap: 10px;
|
|
2404
|
+
overscroll-behavior: contain;
|
|
2363
2405
|
}
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2406
|
+
.dsh-github-run-meta {
|
|
2407
|
+
display: flex;
|
|
2408
|
+
flex-wrap: wrap;
|
|
2409
|
+
align-items: center;
|
|
2410
|
+
gap: 6px;
|
|
2411
|
+
padding: 8px 16px;
|
|
2412
|
+
font-size: 11px;
|
|
2413
|
+
line-height: 18px;
|
|
2414
|
+
color: var(--dsw-alias-label-secondary, #57606a);
|
|
2415
|
+
background: var(--dsw-alias-bg-module-platform, #f6f8fa);
|
|
2416
|
+
border-bottom: 1px solid var(--dsw-alias-border-l2, #e1e4e8);
|
|
2417
|
+
flex: 0 0 auto;
|
|
2375
2418
|
}
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
var
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2419
|
+
.dsh-github-run-meta > span:not(:first-child)::before {
|
|
2420
|
+
content: "·";
|
|
2421
|
+
margin-right: 6px;
|
|
2422
|
+
opacity: 0.5;
|
|
2423
|
+
}
|
|
2424
|
+
body[data-ds-dark-theme] .dsh-github-run-meta {
|
|
2425
|
+
color: var(--dsw-alias-label-secondary, #8b949e);
|
|
2426
|
+
background: var(--dsw-alias-bg-layer-1, #16161a);
|
|
2427
|
+
border-bottom-color: var(--dsw-alias-border-l1, #30363d);
|
|
2428
|
+
}
|
|
2429
|
+
.dsh-github-run-mono {
|
|
2430
|
+
font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, monospace;
|
|
2431
|
+
opacity: 0.9;
|
|
2432
|
+
}
|
|
2433
|
+
.dsh-github-run-degraded {
|
|
2434
|
+
color: var(--dsw-alias-state-warning-primary, #9a6700);
|
|
2435
|
+
}
|
|
2436
|
+
body[data-ds-dark-theme] .dsh-github-run-degraded {
|
|
2437
|
+
color: #d29922;
|
|
2438
|
+
}
|
|
2439
|
+
.dsh-github-run-job {
|
|
2440
|
+
border: 1px solid var(--dsw-alias-border-l2, #e1e4e8);
|
|
2441
|
+
border-radius: 6px;
|
|
2442
|
+
background: var(--dsw-alias-bg-base, #ffffff);
|
|
2443
|
+
overflow: hidden;
|
|
2444
|
+
flex: 0 0 auto;
|
|
2445
|
+
}
|
|
2446
|
+
body[data-ds-dark-theme] .dsh-github-run-job {
|
|
2447
|
+
border-color: var(--dsw-alias-border-l1, #30363d);
|
|
2448
|
+
background: var(--dsw-alias-bg-layer-2, #1e1e22);
|
|
2449
|
+
}
|
|
2450
|
+
.dsh-github-run-job-header {
|
|
2451
|
+
display: flex;
|
|
2452
|
+
align-items: center;
|
|
2453
|
+
gap: 8px;
|
|
2454
|
+
width: 100%;
|
|
2455
|
+
padding: 7px 10px;
|
|
2456
|
+
border: none;
|
|
2457
|
+
background: transparent;
|
|
2458
|
+
cursor: pointer;
|
|
2459
|
+
text-align: left;
|
|
2460
|
+
font-family: inherit;
|
|
2461
|
+
font-size: 12px;
|
|
2462
|
+
line-height: 18px;
|
|
2463
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
2464
|
+
border-radius: 6px;
|
|
2465
|
+
transition: background 0.15s ease;
|
|
2466
|
+
}
|
|
2467
|
+
.dsh-github-run-job-header:hover {
|
|
2468
|
+
background: var(--dsw-alias-interactive-bg-hover, #eaeef2);
|
|
2469
|
+
}
|
|
2470
|
+
body[data-ds-dark-theme] .dsh-github-run-job-header {
|
|
2471
|
+
color: var(--dsw-alias-label-primary, #f0f6fc);
|
|
2472
|
+
}
|
|
2473
|
+
body[data-ds-dark-theme] .dsh-github-run-job-header:hover {
|
|
2474
|
+
background: rgba(56, 139, 253, 0.12);
|
|
2475
|
+
}
|
|
2476
|
+
.dsh-github-run-caret {
|
|
2477
|
+
flex-shrink: 0;
|
|
2478
|
+
width: 10px;
|
|
2479
|
+
font-size: 10px;
|
|
2480
|
+
opacity: 0.65;
|
|
2481
|
+
}
|
|
2482
|
+
.dsh-github-run-job-name {
|
|
2483
|
+
flex: 1 1 auto;
|
|
2484
|
+
min-width: 0;
|
|
2485
|
+
font-weight: 500;
|
|
2486
|
+
overflow: hidden;
|
|
2487
|
+
text-overflow: ellipsis;
|
|
2488
|
+
white-space: nowrap;
|
|
2489
|
+
}
|
|
2490
|
+
.dsh-github-run-failed-count {
|
|
2491
|
+
flex-shrink: 0;
|
|
2492
|
+
font-size: 11px;
|
|
2493
|
+
opacity: 0.6;
|
|
2494
|
+
white-space: nowrap;
|
|
2495
|
+
}
|
|
2496
|
+
.dsh-github-run-failed-count.is-hit {
|
|
2497
|
+
opacity: 1;
|
|
2498
|
+
font-weight: 600;
|
|
2499
|
+
color: var(--dsw-alias-state-error-primary, #cf222e);
|
|
2500
|
+
}
|
|
2501
|
+
body[data-ds-dark-theme] .dsh-github-run-failed-count.is-hit {
|
|
2502
|
+
color: #f85149;
|
|
2503
|
+
}
|
|
2504
|
+
.dsh-github-run-steps {
|
|
2505
|
+
border-top: 1px solid var(--dsw-alias-border-l2, #e1e4e8);
|
|
2506
|
+
display: flex;
|
|
2507
|
+
flex-direction: column;
|
|
2508
|
+
}
|
|
2509
|
+
body[data-ds-dark-theme] .dsh-github-run-steps {
|
|
2510
|
+
border-top-color: var(--dsw-alias-border-l1, #30363d);
|
|
2511
|
+
}
|
|
2512
|
+
.dsh-github-run-step {
|
|
2513
|
+
display: grid;
|
|
2514
|
+
grid-template-columns: 32px minmax(0, 1fr) auto 62px;
|
|
2515
|
+
align-items: center;
|
|
2516
|
+
gap: 8px;
|
|
2517
|
+
padding: 5px 10px 5px 18px;
|
|
2518
|
+
font-size: 11px;
|
|
2519
|
+
line-height: 17px;
|
|
2520
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
2521
|
+
border-bottom: 1px solid var(--dsw-alias-border-l2, #eaeef2);
|
|
2522
|
+
}
|
|
2523
|
+
.dsh-github-run-step:last-child {
|
|
2524
|
+
border-bottom: none;
|
|
2525
|
+
}
|
|
2526
|
+
.dsh-github-run-step > span:first-child {
|
|
2527
|
+
font-variant-numeric: tabular-nums;
|
|
2528
|
+
opacity: 0.55;
|
|
2529
|
+
}
|
|
2530
|
+
.dsh-github-run-step > span:nth-child(2) {
|
|
2531
|
+
overflow: hidden;
|
|
2532
|
+
text-overflow: ellipsis;
|
|
2533
|
+
white-space: nowrap;
|
|
2534
|
+
}
|
|
2535
|
+
.dsh-github-run-step > span:nth-child(3) {
|
|
2536
|
+
justify-self: start;
|
|
2537
|
+
}
|
|
2538
|
+
body[data-ds-dark-theme] .dsh-github-run-step {
|
|
2539
|
+
color: var(--dsw-alias-label-primary, #f0f6fc);
|
|
2540
|
+
border-bottom-color: var(--dsw-alias-border-l1, #30363d);
|
|
2541
|
+
}
|
|
2542
|
+
.dsh-github-run-step-failed {
|
|
2543
|
+
background: rgba(207, 34, 46, 0.07);
|
|
2544
|
+
box-shadow: inset 3px 0 0 rgba(207, 34, 46, 0.55);
|
|
2545
|
+
}
|
|
2546
|
+
body[data-ds-dark-theme] .dsh-github-run-step-failed {
|
|
2547
|
+
background: rgba(248, 81, 73, 0.12);
|
|
2548
|
+
box-shadow: inset 3px 0 0 rgba(248, 81, 73, 0.7);
|
|
2549
|
+
}
|
|
2550
|
+
.dsh-github-run-duration {
|
|
2551
|
+
font-size: 11px;
|
|
2552
|
+
font-variant-numeric: tabular-nums;
|
|
2553
|
+
white-space: nowrap;
|
|
2554
|
+
text-align: right;
|
|
2555
|
+
justify-self: end;
|
|
2556
|
+
opacity: 0.65;
|
|
2557
|
+
}
|
|
2558
|
+
.dsh-github-run-log-btn {
|
|
2559
|
+
align-self: flex-start;
|
|
2560
|
+
flex-shrink: 0;
|
|
2561
|
+
font-family: inherit;
|
|
2562
|
+
font-size: 11px;
|
|
2563
|
+
line-height: 18px;
|
|
2564
|
+
padding: 5px 12px;
|
|
2565
|
+
border-radius: 6px;
|
|
2566
|
+
border: 1px solid var(--dsw-alias-border-l2, #d0d7de);
|
|
2567
|
+
background: var(--dsw-alias-bg-module-platform, #f6f8fa);
|
|
2568
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
2569
|
+
cursor: pointer;
|
|
2570
|
+
transition: border-color 0.15s ease, color 0.15s ease;
|
|
2571
|
+
}
|
|
2572
|
+
.dsh-github-run-log-btn:hover:not(:disabled) {
|
|
2573
|
+
color: var(--dsw-alias-link, #0969da);
|
|
2574
|
+
border-color: var(--dsw-alias-link, #0969da);
|
|
2575
|
+
}
|
|
2576
|
+
.dsh-github-run-log-btn:disabled {
|
|
2577
|
+
cursor: progress;
|
|
2578
|
+
opacity: 0.6;
|
|
2579
|
+
}
|
|
2580
|
+
body[data-ds-dark-theme] .dsh-github-run-log-btn {
|
|
2581
|
+
border-color: var(--dsw-alias-border-l1, #30363d);
|
|
2582
|
+
background: var(--dsw-alias-bg-layer-2, #1e1e22);
|
|
2583
|
+
color: var(--dsw-alias-label-primary, #f0f6fc);
|
|
2584
|
+
}
|
|
2585
|
+
body[data-ds-dark-theme] .dsh-github-run-log-btn:hover:not(:disabled) {
|
|
2586
|
+
color: #58a6ff;
|
|
2587
|
+
border-color: #58a6ff;
|
|
2588
|
+
}
|
|
2589
|
+
.dsh-github-run-log-section {
|
|
2590
|
+
display: flex;
|
|
2591
|
+
flex-direction: column;
|
|
2592
|
+
gap: 8px;
|
|
2593
|
+
flex: 0 0 auto;
|
|
2594
|
+
}
|
|
2595
|
+
.dsh-github-run-log-block {
|
|
2596
|
+
display: flex;
|
|
2597
|
+
flex-direction: column;
|
|
2598
|
+
gap: 6px;
|
|
2599
|
+
}
|
|
2600
|
+
.dsh-github-run-log-title {
|
|
2601
|
+
font-size: 12px;
|
|
2602
|
+
font-weight: 600;
|
|
2603
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
2604
|
+
overflow: hidden;
|
|
2605
|
+
text-overflow: ellipsis;
|
|
2606
|
+
white-space: nowrap;
|
|
2607
|
+
}
|
|
2608
|
+
body[data-ds-dark-theme] .dsh-github-run-log-title {
|
|
2609
|
+
color: var(--dsw-alias-label-primary, #f0f6fc);
|
|
2610
|
+
}
|
|
2611
|
+
.dsh-github-run-log-truncated {
|
|
2612
|
+
font-size: 11px;
|
|
2613
|
+
color: var(--dsw-alias-state-warning-primary, #9a6700);
|
|
2614
|
+
}
|
|
2615
|
+
body[data-ds-dark-theme] .dsh-github-run-log-truncated {
|
|
2616
|
+
color: #d29922;
|
|
2617
|
+
}
|
|
2618
|
+
.dsh-github-run-log-hint {
|
|
2619
|
+
font-size: 11px;
|
|
2620
|
+
line-height: 1.6;
|
|
2621
|
+
opacity: 0.7;
|
|
2622
|
+
}
|
|
2623
|
+
.dsh-github-run-log {
|
|
2624
|
+
margin: 0;
|
|
2625
|
+
padding: 8px 10px;
|
|
2626
|
+
max-height: 320px;
|
|
2627
|
+
overflow: auto;
|
|
2628
|
+
font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, monospace;
|
|
2629
|
+
font-size: 11px;
|
|
2630
|
+
line-height: 1.5;
|
|
2631
|
+
white-space: pre-wrap;
|
|
2632
|
+
word-break: break-word;
|
|
2633
|
+
border: 1px solid var(--dsw-alias-border-l2, #e1e4e8);
|
|
2634
|
+
border-radius: 6px;
|
|
2635
|
+
background: var(--dsw-alias-bg-layer-1, #f6f8fa);
|
|
2636
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
2637
|
+
overscroll-behavior: contain;
|
|
2638
|
+
}
|
|
2639
|
+
body[data-ds-dark-theme] .dsh-github-run-log {
|
|
2640
|
+
border-color: var(--dsw-alias-border-l1, #30363d);
|
|
2641
|
+
background: var(--dsw-alias-bg-layer-1, #16161a);
|
|
2642
|
+
color: var(--dsw-alias-label-primary, #f0f6fc);
|
|
2643
|
+
}
|
|
2644
|
+
.dsh-github-run-error {
|
|
2645
|
+
display: flex;
|
|
2646
|
+
flex-direction: column;
|
|
2647
|
+
gap: 6px;
|
|
2648
|
+
padding: 10px 12px;
|
|
2649
|
+
border-radius: 6px;
|
|
2650
|
+
border: 1px solid rgba(248, 81, 73, 0.35);
|
|
2651
|
+
background: rgba(248, 81, 73, 0.08);
|
|
2652
|
+
color: var(--dsw-alias-state-error-primary, #cf222e);
|
|
2653
|
+
font-size: 12px;
|
|
2654
|
+
flex: 0 0 auto;
|
|
2655
|
+
}
|
|
2656
|
+
body[data-ds-dark-theme] .dsh-github-run-error {
|
|
2657
|
+
border-color: rgba(248, 81, 73, 0.45);
|
|
2658
|
+
background: rgba(248, 81, 73, 0.12);
|
|
2659
|
+
color: #f85149;
|
|
2660
|
+
}
|
|
2661
|
+
.dsh-github-run-error-title {
|
|
2662
|
+
font-weight: 600;
|
|
2663
|
+
}
|
|
2664
|
+
.dsh-github-run-error-text {
|
|
2665
|
+
max-height: 180px;
|
|
2666
|
+
overflow: auto;
|
|
2667
|
+
opacity: 0.9;
|
|
2668
|
+
line-height: 1.6;
|
|
2669
|
+
word-break: break-word;
|
|
2670
|
+
white-space: pre-wrap;
|
|
2671
|
+
overscroll-behavior: contain;
|
|
2672
|
+
}
|
|
2673
|
+
.dsh-github-run-empty {
|
|
2674
|
+
padding: 18px 0;
|
|
2675
|
+
text-align: center;
|
|
2676
|
+
font-size: 12px;
|
|
2677
|
+
opacity: 0.6;
|
|
2678
|
+
flex: 0 0 auto;
|
|
2679
|
+
}
|
|
2680
|
+
body[data-ds-dark-theme] .dsh-github-run-empty {
|
|
2681
|
+
color: var(--dsw-alias-label-secondary, #8b949e);
|
|
2682
|
+
}
|
|
2683
|
+
.dsh-github-run-modal-dialog .dsh-badge-neutral {
|
|
2684
|
+
background: rgba(110, 118, 129, 0.14);
|
|
2685
|
+
border: 1px solid rgba(110, 118, 129, 0.28);
|
|
2686
|
+
color: var(--dsw-alias-label-secondary, #57606a);
|
|
2687
|
+
}
|
|
2688
|
+
body[data-ds-dark-theme] .dsh-github-run-modal-dialog .dsh-badge-neutral {
|
|
2689
|
+
background: rgba(139, 148, 158, 0.18);
|
|
2690
|
+
border-color: rgba(139, 148, 158, 0.35);
|
|
2691
|
+
color: var(--dsw-alias-label-secondary, #8b949e);
|
|
2692
|
+
}
|
|
2693
|
+
`;
|
|
2694
|
+
function injectStyles() {
|
|
2695
|
+
var styleId = "dsh-plugin-github-styles";
|
|
2696
|
+
var existing = document.getElementById(styleId);
|
|
2697
|
+
if (existing) {
|
|
2698
|
+
existing.textContent = GITHUB_PANEL_CSS;
|
|
2699
|
+
return () => { };
|
|
2700
|
+
}
|
|
2701
|
+
var style = document.createElement("style");
|
|
2702
|
+
style.id = styleId;
|
|
2703
|
+
style.textContent = GITHUB_PANEL_CSS;
|
|
2704
|
+
document.head.appendChild(style);
|
|
2705
|
+
return () => style.remove();
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
// ---- [Bundled: utils.js] ----
|
|
2709
|
+
/**
|
|
2710
|
+
* 面板公共工具函数(纯函数,可被单元测试直接覆盖)
|
|
2711
|
+
*/
|
|
2712
|
+
function formatRelativeTime(iso) {
|
|
2713
|
+
if (!iso || typeof iso !== "string")
|
|
2714
|
+
return "";
|
|
2715
|
+
var t = Date.parse(iso);
|
|
2716
|
+
if (isNaN(t))
|
|
2717
|
+
return "";
|
|
2718
|
+
var diff = Date.now() - t;
|
|
2719
|
+
if (diff < 0)
|
|
2720
|
+
return "刚刚";
|
|
2721
|
+
var MIN = 60 * 1000, HOUR = 60 * MIN, DAY = 24 * HOUR;
|
|
2722
|
+
if (diff < MIN)
|
|
2723
|
+
return "刚刚";
|
|
2724
|
+
if (diff < HOUR)
|
|
2725
|
+
return Math.floor(diff / MIN) + "分钟前";
|
|
2726
|
+
if (diff < DAY)
|
|
2727
|
+
return Math.floor(diff / HOUR) + "小时前";
|
|
2728
|
+
if (diff < 7 * DAY)
|
|
2729
|
+
return Math.floor(diff / DAY) + "天前";
|
|
2730
|
+
var d = new Date(t);
|
|
2731
|
+
return d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0");
|
|
2732
|
+
}
|
|
2733
|
+
function rollupCiStatus(checks) {
|
|
2734
|
+
if (!Array.isArray(checks) || checks.length === 0)
|
|
2735
|
+
return null;
|
|
2736
|
+
var hasFail = false, hasPending = false;
|
|
2737
|
+
for (var i = 0; i < checks.length; i++) {
|
|
2738
|
+
var c = checks[i] || {};
|
|
2739
|
+
var status = String(c.status || "").toLowerCase();
|
|
2740
|
+
var conclusion = String(c.conclusion || "").toLowerCase();
|
|
2741
|
+
if (conclusion === "failure" || conclusion === "timed_out" || conclusion === "cancelled")
|
|
2742
|
+
hasFail = true;
|
|
2743
|
+
else if (conclusion === "success" || conclusion === "skipped" || conclusion === "neutral") { }
|
|
2744
|
+
else if (status === "in_progress" || status === "queued" || conclusion === "pending" || (!conclusion && status))
|
|
2745
|
+
hasPending = true;
|
|
2746
|
+
}
|
|
2747
|
+
if (hasFail)
|
|
2748
|
+
return "fail";
|
|
2749
|
+
if (hasPending)
|
|
2750
|
+
return "pending";
|
|
2751
|
+
return "pass";
|
|
2752
|
+
}
|
|
2753
|
+
function reviewDecisionBadge(decision) {
|
|
2754
|
+
var d = String(decision || "").toUpperCase();
|
|
2755
|
+
if (d === "APPROVED")
|
|
2756
|
+
return { text: "✔ 已批准", kind: "green" };
|
|
2757
|
+
if (d === "CHANGES_REQUESTED")
|
|
2758
|
+
return { text: "✋ 需修改", kind: "red" };
|
|
2759
|
+
if (d === "REVIEW_REQUIRED")
|
|
2760
|
+
return { text: "👀 待审查", kind: "yellow" };
|
|
2761
|
+
return null;
|
|
2762
|
+
}
|
|
2763
|
+
function eventLabel(event) {
|
|
2764
|
+
var e = String(event || "");
|
|
2765
|
+
if (e === "push")
|
|
2766
|
+
return "⇡ push";
|
|
2767
|
+
if (e === "schedule")
|
|
2768
|
+
return "⏰ 定时";
|
|
2769
|
+
if (e === "workflow_dispatch")
|
|
2770
|
+
return "▶ 手动";
|
|
2771
|
+
if (e === "pull_request")
|
|
2772
|
+
return "🔀 PR";
|
|
2773
|
+
return e || "";
|
|
2774
|
+
}
|
|
2775
|
+
/**
|
|
2776
|
+
* 确定性失败的特征(客户端侧兜底判定)。
|
|
2777
|
+
*
|
|
2778
|
+
* 为什么这里还要再写一份:后端 GhExecutor 已统一分类并通过 errorKind 分流,
|
|
2779
|
+
* 但浏览器端拿不到 errorKind,只能看到拼接后的错误文本。这类错误重试一万次
|
|
2780
|
+
* 也不会变好(仓库就是关着 Issues),必须阻止阶梯退避把 7 秒白白烧掉。
|
|
2781
|
+
* 与服务端共用同一组特征串,改动时两边同步。
|
|
2782
|
+
*/
|
|
2783
|
+
var DETERMINISTIC_HINT_RE = /repository has disabled|Could not resolve to a Repository|Branch not found|HTTP 403|Not Found \(HTTP 404\)|rate limit/i;
|
|
2784
|
+
/**
|
|
2785
|
+
* 带有阶梯退避重试(Backoff Retry)的通用异步拉取函数
|
|
2786
|
+
*
|
|
2787
|
+
* 核心设计理由与约束:
|
|
2788
|
+
* 1. 应对代理节点不稳定、TLS 握手卡顿、短时网络抖动(半秒至两秒微断流);
|
|
2789
|
+
* 2. 阶梯退避时序:第 1 次失败等 1s (1000ms),第 2 次等 2s (2000ms),第 3 次等 4s (4000ms),附加 0~200ms Jitter;
|
|
2790
|
+
* 3. 智能区分软硬错误:401/403/404/400 确定性错误坚决不重试,仅在网络超时、不可达、或带有 networkError 时重试;
|
|
2791
|
+
* 4. 严格响应外部 AbortSignal,组件卸载或用户手动打断时立即退出等待,绝无内存泄漏。
|
|
2792
|
+
*/
|
|
2793
|
+
async function fetchWithRetry(url, options = {}, config = {}) {
|
|
2794
|
+
var maxRetries = config.maxRetries !== undefined ? config.maxRetries : 3;
|
|
2795
|
+
var baseDelayMs = config.baseDelayMs !== undefined ? config.baseDelayMs : 1000;
|
|
2796
|
+
var signal = config.signal || options.signal;
|
|
2797
|
+
var attempt = 0;
|
|
2798
|
+
while (true) {
|
|
2799
|
+
attempt++;
|
|
2800
|
+
var currentRes = null;
|
|
2801
|
+
var currentJson = null;
|
|
2802
|
+
var failureReason = "";
|
|
2803
|
+
var shouldRetry = false;
|
|
2804
|
+
try {
|
|
2805
|
+
if (signal && signal.aborted) {
|
|
2806
|
+
throw new Error("Aborted");
|
|
2807
|
+
}
|
|
2808
|
+
currentRes = await fetch(url, { ...options, signal });
|
|
2809
|
+
// 硬错误守卫:客户端确定性错误(如未授权、权限拒绝、不存在、参数错误)坚决不重试
|
|
2810
|
+
if (currentRes.status === 401 || currentRes.status === 403 || currentRes.status === 404 || currentRes.status === 400) {
|
|
2811
|
+
currentJson = await currentRes.json().catch(() => ({}));
|
|
2812
|
+
return { res: currentRes, json: currentJson };
|
|
2813
|
+
}
|
|
2814
|
+
if (!currentRes.ok) {
|
|
2815
|
+
failureReason = "HTTP " + currentRes.status + " " + currentRes.statusText;
|
|
2816
|
+
shouldRetry = true;
|
|
2817
|
+
}
|
|
2818
|
+
else {
|
|
2819
|
+
currentJson = await currentRes.json().catch(() => null);
|
|
2820
|
+
// 业务层软错误检测(后端返回了 200,但标记了 networkError 或 partialFailure)
|
|
2821
|
+
var isNetworkError = Boolean(currentJson && (currentJson.partialFailure ||
|
|
2822
|
+
currentJson.data?.partialFailure ||
|
|
2823
|
+
currentJson.data?.networkError ||
|
|
2824
|
+
currentJson.data?.auth?.networkError));
|
|
2825
|
+
if (isNetworkError) {
|
|
2826
|
+
failureReason = currentJson.error || currentJson.data?.networkErrorReason || "云端接口网络抖动";
|
|
2827
|
+
// 后端已把「确定性失败」(仓库关了 Issues / 仓库不存在 / 无权限)从
|
|
2828
|
+
// networkError 里剥离,只留真正的传输层故障。但历史版本或第三方实现
|
|
2829
|
+
// 仍可能把确定性文本塞进同一条链路,这里再兜一道:命中即放弃重试。
|
|
2830
|
+
shouldRetry = !DETERMINISTIC_HINT_RE.test(String(failureReason));
|
|
2831
|
+
}
|
|
2832
|
+
else {
|
|
2833
|
+
// 成功获取完整且无网络错误的数据
|
|
2834
|
+
return { res: currentRes, json: currentJson };
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
catch (err) {
|
|
2839
|
+
if ((signal && signal.aborted) || err.name === "AbortError") {
|
|
2840
|
+
throw err;
|
|
2841
|
+
}
|
|
2842
|
+
failureReason = err.message || "网络连接中断或超时";
|
|
2843
|
+
shouldRetry = true;
|
|
2844
|
+
}
|
|
2845
|
+
// 判断是否已耗尽重试次数
|
|
2846
|
+
if (!shouldRetry || attempt > maxRetries) {
|
|
2847
|
+
if (currentRes) {
|
|
2848
|
+
return { res: currentRes, json: currentJson || {} };
|
|
2849
|
+
}
|
|
2850
|
+
throw new Error(failureReason || "网络请求失败");
|
|
2851
|
+
}
|
|
2852
|
+
// 计算阶梯退避等待时间:baseDelayMs * 2^(attempt - 1) + Jitter 自适应微扰
|
|
2853
|
+
var exponentialDelay = baseDelayMs * Math.pow(2, attempt - 1);
|
|
2854
|
+
var maxJitter = Math.min(200, Math.floor(exponentialDelay * 0.2));
|
|
2855
|
+
var jitter = Math.floor(Math.random() * (maxJitter + 1));
|
|
2856
|
+
var totalDelay = exponentialDelay + jitter;
|
|
2857
|
+
if (config.onRetry) {
|
|
2858
|
+
config.onRetry(attempt, totalDelay, failureReason);
|
|
2859
|
+
}
|
|
2860
|
+
// 可中断的睡眠等待
|
|
2861
|
+
await new Promise((resolve, reject) => {
|
|
2862
|
+
var timer = setTimeout(resolve, totalDelay);
|
|
2863
|
+
if (signal) {
|
|
2864
|
+
signal.addEventListener("abort", () => {
|
|
2466
2865
|
clearTimeout(timer);
|
|
2467
2866
|
reject(new Error("Aborted"));
|
|
2468
2867
|
}, { once: true });
|
|
@@ -4681,18 +5080,36 @@ function PrDetailModal(props) {
|
|
|
4681
5080
|
},
|
|
4682
5081
|
}, data ? `#${data.number} ${data.title}` : `PR #${prNumber}`), data
|
|
4683
5082
|
? h("span", {
|
|
5083
|
+
// 色调来自 shared/state-badges(与右侧栏 / CLI 输出同一份映射);
|
|
5084
|
+
// 文案保持原样(Draft 用大驼峰、其余直接展示 GitHub 原始 state)。
|
|
5085
|
+
// 未知状态沿用旧实现的绿色兜底(旧代码的 else 分支就是 green)。
|
|
4684
5086
|
className: "dsh-github-badge " +
|
|
4685
|
-
(
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
: data.state === "CLOSED"
|
|
4690
|
-
? "dsh-badge-red"
|
|
4691
|
-
: "dsh-badge-green"),
|
|
5087
|
+
(function () {
|
|
5088
|
+
var b = prStateBadge(data.state, data.isDraft);
|
|
5089
|
+
return "dsh-badge-" + (b && b.tone !== "neutral" ? b.tone : "green");
|
|
5090
|
+
})(),
|
|
4692
5091
|
style: { fontSize: "10px", padding: "1px 6px" },
|
|
4693
5092
|
}, data.isDraft ? "Draft" : data.state)
|
|
4694
5093
|
: null), data
|
|
4695
|
-
? h("div", { style: { fontSize: "11px", color: "var(--dsw-alias-label-secondary, #656d76)", display: "flex", gap: "6px", alignItems: "center", flexWrap: "wrap" } }, h("span", null, `👤 @${data.author.login}`), data.createdAt ? h("span", null, `· 提单于 ${formatRelativeTime(data.createdAt)}`) : null, h("span", { style: { fontFamily: "monospace", opacity: 0.85 } }, `(${data.headRefName} → ${data.baseRefName})`)
|
|
5094
|
+
? h("div", { style: { fontSize: "11px", color: "var(--dsw-alias-label-secondary, #656d76)", display: "flex", gap: "6px", alignItems: "center", flexWrap: "wrap" } }, h("span", null, `👤 @${data.author.login}`), data.createdAt ? h("span", null, `· 提单于 ${formatRelativeTime(data.createdAt)}`) : null, h("span", { style: { fontFamily: "monospace", opacity: 0.85 } }, `(${data.headRefName} → ${data.baseRefName})`),
|
|
5095
|
+
/*
|
|
5096
|
+
* 审查锚点:分支名会移动,SHA 不会。
|
|
5097
|
+
* 点开这个弹窗时先看这一枚 chip,就能确认「我看到的是不是最新一次推送」;
|
|
5098
|
+
* title 里放完整 SHA,方便复制去精确引用。老服务端没有该字段时不渲染。
|
|
5099
|
+
*/
|
|
5100
|
+
typeof data.headRefOid === "string" && data.headRefOid.length >= 7
|
|
5101
|
+
? h("span", {
|
|
5102
|
+
title: `headRefOid: ${data.headRefOid}`,
|
|
5103
|
+
style: {
|
|
5104
|
+
fontFamily: "monospace",
|
|
5105
|
+
fontSize: "10px",
|
|
5106
|
+
padding: "1px 6px",
|
|
5107
|
+
borderRadius: "10px",
|
|
5108
|
+
border: "1px solid var(--dsw-alias-border-l2, rgba(31,35,40,0.15))",
|
|
5109
|
+
opacity: 0.9,
|
|
5110
|
+
},
|
|
5111
|
+
}, `🔖 ${data.headRefOid.slice(0, 7)}`)
|
|
5112
|
+
: null)
|
|
4696
5113
|
: null)), h("div", { style: { display: "flex", alignItems: "center", gap: "6px", flexShrink: 0 } }, data?.url
|
|
4697
5114
|
? h("button", {
|
|
4698
5115
|
className: "dsh-github-btn-sm",
|
|
@@ -4735,7 +5152,10 @@ function PrDetailModal(props) {
|
|
|
4735
5152
|
}, `💬 描述与讨论 (${data.comments ? data.comments.length : 0})`), h("button", {
|
|
4736
5153
|
className: `dsh-github-pr-tab-btn ${activeTab === "files" ? "active" : ""}`,
|
|
4737
5154
|
onClick: () => setActiveTab("files"),
|
|
4738
|
-
},
|
|
5155
|
+
},
|
|
5156
|
+
// 截断时显示 N/M:M 来自 changedFiles(权威总数),N 是 gh 实际返回的条数。
|
|
5157
|
+
// 只显示 N 会让「被截断」看起来像「就这么多」——这是 cli/cli#12522 的静默截断。
|
|
5158
|
+
`📁 变动文件 (${data.files ? data.files.length : 0}${data.filesTruncated && data.changedFiles ? '/' + data.changedFiles : ''} 文件 · +${data.additions || 0} / -${data.deletions || 0})${data.filesTruncated ? ' ⚠️' : ''}`)),
|
|
4739
5159
|
// Tab 1: 描述与讨论
|
|
4740
5160
|
activeTab === "conversation"
|
|
4741
5161
|
? h("div", { className: "dsh-github-modal-body dsh-github-pr-conversation-pane" },
|
|
@@ -4806,7 +5226,9 @@ function PrDetailModal(props) {
|
|
|
4806
5226
|
onClick: toggleFilePane,
|
|
4807
5227
|
style: { padding: "2px 8px", fontSize: "10px", flexShrink: 0 },
|
|
4808
5228
|
title: "展开左侧文件列表",
|
|
4809
|
-
},
|
|
5229
|
+
},
|
|
5230
|
+
// 折叠态同样要透出截断事实(N/M),否则展开前的第一眼就被骗了
|
|
5231
|
+
`📁 文件列表 (${(data.files && data.files.length) || 0}${data.filesTruncated && data.changedFiles ? '/' + data.changedFiles : ''})`)
|
|
4810
5232
|
: null, h("span", { style: { fontFamily: "monospace", fontWeight: 600, fontSize: "11px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, selectedFilePath), data.fileDiffs && data.fileDiffs[selectedFilePath]
|
|
4811
5233
|
? h("button", {
|
|
4812
5234
|
className: "dsh-github-btn-sm",
|
|
@@ -4814,7 +5236,9 @@ function PrDetailModal(props) {
|
|
|
4814
5236
|
style: { padding: "2px 8px", fontSize: "10px" },
|
|
4815
5237
|
}, copied ? "✓ 已复制" : "📋 复制文件 Diff")
|
|
4816
5238
|
: null), h("div", { style: { flex: 1, overflowY: "auto", minHeight: 0 } },
|
|
4817
|
-
//
|
|
5239
|
+
// 两种「看不到 diff」的原因必须分开说,否则用户不知道该做什么:
|
|
5240
|
+
// ① diffTruncated:整段 diff 被输出阈值裁掉(去 GitHub 看完整内容)
|
|
5241
|
+
// ② diffIncomplete:这个文件在清单里但没取到切片(换一个文件/重试)
|
|
4818
5242
|
data.diffTruncated
|
|
4819
5243
|
? h("div", {
|
|
4820
5244
|
style: {
|
|
@@ -4827,7 +5251,20 @@ function PrDetailModal(props) {
|
|
|
4827
5251
|
border: "1px solid rgba(210, 153, 34, 0.35)",
|
|
4828
5252
|
color: "var(--dsw-alias-state-attention-primary, #9a6700)",
|
|
4829
5253
|
},
|
|
4830
|
-
}, "⚠️ 该 PR 的 diff 体量过大已触达输出上限,部分文件可能无法展示差异。可点击右上角「↗ 浏览器打开」查看完整内容。")
|
|
5254
|
+
}, "⚠️ 该 PR 的 diff 体量过大已触达输出上限,部分文件可能无法展示差异。可点击右上角「↗ 浏览器打开」查看完整内容。")
|
|
5255
|
+
: null, data.diffIncomplete
|
|
5256
|
+
? h("div", {
|
|
5257
|
+
style: {
|
|
5258
|
+
margin: "8px",
|
|
5259
|
+
padding: "8px 12px",
|
|
5260
|
+
borderRadius: "6px",
|
|
5261
|
+
fontSize: "11px",
|
|
5262
|
+
lineHeight: 1.5,
|
|
5263
|
+
background: "rgba(210, 153, 34, 0.12)",
|
|
5264
|
+
border: "1px solid rgba(210, 153, 34, 0.35)",
|
|
5265
|
+
color: "var(--dsw-alias-state-attention-primary, #9a6700)",
|
|
5266
|
+
},
|
|
5267
|
+
}, "⚠️ 有文件未能取到 diff 正文(二进制、重命名或超出切片范围)。这些文件显示为空不代表没有改动,请到 GitHub 核对。")
|
|
4831
5268
|
: null, data.fileDiffs && data.fileDiffs[selectedFilePath]
|
|
4832
5269
|
? renderDiffContent(data.fileDiffs[selectedFilePath])
|
|
4833
5270
|
: renderDiffContent(""),
|
|
@@ -5064,11 +5501,14 @@ function IssueDetailModal(props) {
|
|
|
5064
5501
|
var stateBadge = function (d) {
|
|
5065
5502
|
if (!d)
|
|
5066
5503
|
return null;
|
|
5067
|
-
|
|
5504
|
+
// 状态→色调来自 shared/state-badges(与右侧栏 / CLI 输出同一份映射)
|
|
5505
|
+
var b = issueStateBadge(d.state);
|
|
5506
|
+
if (!b)
|
|
5507
|
+
return null;
|
|
5068
5508
|
return h("span", {
|
|
5069
|
-
className: "dsh-github-badge
|
|
5509
|
+
className: "dsh-github-badge dsh-badge-" + b.tone,
|
|
5070
5510
|
style: { fontSize: "10px", padding: "1px 6px", flexShrink: 0 },
|
|
5071
|
-
},
|
|
5511
|
+
}, b.label);
|
|
5072
5512
|
};
|
|
5073
5513
|
/**
|
|
5074
5514
|
* 元数据条:只渲染真实存在的字段。
|
|
@@ -5084,67 +5524,552 @@ function IssueDetailModal(props) {
|
|
|
5084
5524
|
title: "关闭原因 (stateReason): " + d.stateReason,
|
|
5085
5525
|
}, "✓ " + reason));
|
|
5086
5526
|
}
|
|
5087
|
-
if (d.isPinned) {
|
|
5088
|
-
chips.push(h("span", { key: "pin", className: "dsh-github-issue-chip" }, "📌 置顶"));
|
|
5527
|
+
if (d.isPinned) {
|
|
5528
|
+
chips.push(h("span", { key: "pin", className: "dsh-github-issue-chip" }, "📌 置顶"));
|
|
5529
|
+
}
|
|
5530
|
+
if (d.issueType) {
|
|
5531
|
+
chips.push(h("span", { key: "type", className: "dsh-github-issue-chip" }, "🏷️ " + d.issueType));
|
|
5532
|
+
}
|
|
5533
|
+
(Array.isArray(d.labels) ? d.labels : []).forEach(function (l, i) {
|
|
5534
|
+
chips.push(h("span", {
|
|
5535
|
+
key: "label-" + i + "-" + l.name,
|
|
5536
|
+
className: "dsh-github-issue-chip dsh-github-issue-label-chip",
|
|
5537
|
+
style: {
|
|
5538
|
+
background: labelRgba(l.color, 0.14),
|
|
5539
|
+
borderColor: labelRgba(l.color, 0.45),
|
|
5540
|
+
},
|
|
5541
|
+
title: l.description || l.name,
|
|
5542
|
+
}, h("span", {
|
|
5543
|
+
className: "dsh-github-issue-label-dot",
|
|
5544
|
+
style: { background: labelRgba(l.color, 0.9) },
|
|
5545
|
+
}), l.name));
|
|
5546
|
+
});
|
|
5547
|
+
(Array.isArray(d.assignees) ? d.assignees : []).forEach(function (a, i) {
|
|
5548
|
+
chips.push(h("span", { key: "asg-" + i, className: "dsh-github-issue-chip" }, "👤 " + a.login));
|
|
5549
|
+
});
|
|
5550
|
+
if (d.milestone && d.milestone.title) {
|
|
5551
|
+
var dueText = d.milestone.dueOn ? formatDueDate(d.milestone.dueOn) : '';
|
|
5552
|
+
chips.push(h("span", {
|
|
5553
|
+
key: "milestone",
|
|
5554
|
+
className: "dsh-github-issue-chip" + (dueText.indexOf('已逾期') >= 0 ? " dsh-github-issue-chip-overdue" : ""),
|
|
5555
|
+
title: d.milestone.dueOn ? "截止 " + d.milestone.dueOn : "未设置截止日期",
|
|
5556
|
+
}, "🏁 " + d.milestone.title + (dueText ? " · " + dueText : "")));
|
|
5557
|
+
}
|
|
5558
|
+
if (d.subIssues && Number(d.subIssues.total) > 0) {
|
|
5559
|
+
chips.push(h("span", { key: "sub", className: "dsh-github-issue-chip" }, "🧩 子议题 " + d.subIssues.completed + "/" + d.subIssues.total));
|
|
5560
|
+
}
|
|
5561
|
+
(Array.isArray(d.linkedPrs) ? d.linkedPrs : []).forEach(function (p, i) {
|
|
5562
|
+
chips.push(h("a", {
|
|
5563
|
+
key: "pr-" + i,
|
|
5564
|
+
className: "dsh-github-issue-chip dsh-github-issue-chip-link",
|
|
5565
|
+
href: p.url,
|
|
5566
|
+
target: "_blank",
|
|
5567
|
+
rel: "noopener noreferrer",
|
|
5568
|
+
title: "该 Issue 被此 Pull Request 关联/关闭",
|
|
5569
|
+
}, "🔗 PR #" + p.number));
|
|
5570
|
+
});
|
|
5571
|
+
chips.push(h(IssueImageList, { key: "images", urls: d.images || [] }));
|
|
5572
|
+
return chips.length > 0 ? h("div", { className: "dsh-github-issue-meta" }, ...chips) : null;
|
|
5573
|
+
};
|
|
5574
|
+
var bodyCard = function (d) {
|
|
5575
|
+
return h("div", { className: "dsh-github-issue-body-card" }, h("div", { className: "dsh-github-issue-comment-header" }, h("strong", null, "@" + d.author.login), h("span", { style: { fontSize: "11px", opacity: 0.6 } }, "提单说明 (Issue Body)")), h("div", { className: "dsh-github-issue-body-content" }, d.body
|
|
5576
|
+
? h(CollapsibleBody, { text: d.body, defaultExpanded: true, idPrefix: "issue-body" })
|
|
5577
|
+
: h("span", { style: { opacity: 0.5, fontStyle: "italic" } }, "(该 Issue 未填写正文)")));
|
|
5578
|
+
};
|
|
5579
|
+
return h("div", { className: "dsh-github-modal-overlay", onClick: onClose, style: { zIndex: 100000 } }, h("div", {
|
|
5580
|
+
className: "dsh-github-modal-dialog dsh-github-issue-modal-dialog",
|
|
5581
|
+
role: "dialog",
|
|
5582
|
+
"aria-modal": "true",
|
|
5583
|
+
"aria-label": "Issue #" + issueNumber + " 详情",
|
|
5584
|
+
onClick: function (e) { e.stopPropagation(); },
|
|
5585
|
+
},
|
|
5586
|
+
// 头部
|
|
5587
|
+
h("div", { className: "dsh-github-modal-header dsh-github-issue-modal-header" }, h("div", { style: { display: "flex", alignItems: "center", gap: "8px", minWidth: 0, flex: 1 } }, renderOctocat(18, "#1f883d"), h("div", { style: { display: "flex", flexDirection: "column", gap: "2px", minWidth: 0 } }, h("div", { style: { display: "flex", alignItems: "center", gap: "6px", flexWrap: "wrap" } }, h("h3", {
|
|
5588
|
+
style: {
|
|
5589
|
+
margin: 0,
|
|
5590
|
+
fontSize: "14px",
|
|
5591
|
+
fontWeight: 600,
|
|
5592
|
+
overflow: "hidden",
|
|
5593
|
+
textOverflow: "ellipsis",
|
|
5594
|
+
whiteSpace: "nowrap",
|
|
5595
|
+
},
|
|
5596
|
+
title: data ? "#" + data.number + " " + data.title : "Issue #" + issueNumber,
|
|
5597
|
+
}, data ? "#" + data.number + " " + data.title : "Issue #" + issueNumber), stateBadge(data)), data
|
|
5598
|
+
? h("div", {
|
|
5599
|
+
style: {
|
|
5600
|
+
fontSize: "11px",
|
|
5601
|
+
color: "var(--dsw-alias-label-secondary, #656d76)",
|
|
5602
|
+
display: "flex",
|
|
5603
|
+
gap: "6px",
|
|
5604
|
+
alignItems: "center",
|
|
5605
|
+
flexWrap: "wrap",
|
|
5606
|
+
},
|
|
5607
|
+
}, h("span", null, "👤 @" + data.author.login), data.createdAt ? h("span", null, "· 提单于 " + formatRelativeTime(data.createdAt)) : null, data.closedAt ? h("span", null, "· 关闭于 " + formatRelativeTime(data.closedAt)) : null, repoNameWithOwner
|
|
5608
|
+
? h("span", { style: { fontFamily: "monospace", opacity: 0.85 } }, repoNameWithOwner)
|
|
5609
|
+
: null)
|
|
5610
|
+
: null)), h("div", { style: { display: "flex", alignItems: "center", gap: "6px", flexShrink: 0 } }, data && data.url
|
|
5611
|
+
? h("button", {
|
|
5612
|
+
type: "button",
|
|
5613
|
+
className: "dsh-github-btn-sm",
|
|
5614
|
+
style: { padding: "4px 8px", fontSize: "11px" },
|
|
5615
|
+
onClick: function () { copyLink(data.url); },
|
|
5616
|
+
title: "复制该 Issue 网页链接",
|
|
5617
|
+
}, copied ? "✓ 已复制" : "📋 复制链接")
|
|
5618
|
+
: null, data && data.url
|
|
5619
|
+
? h("a", {
|
|
5620
|
+
href: data.url,
|
|
5621
|
+
target: "_blank",
|
|
5622
|
+
rel: "noopener noreferrer",
|
|
5623
|
+
className: "dsh-github-btn-sm",
|
|
5624
|
+
style: { padding: "4px 8px", fontSize: "11px", textDecoration: "none", color: "inherit" },
|
|
5625
|
+
title: "在系统默认浏览器中打开该 Issue",
|
|
5626
|
+
}, "↗ 浏览器打开")
|
|
5627
|
+
: null, h("button", {
|
|
5628
|
+
type: "button",
|
|
5629
|
+
"aria-label": "关闭 (Esc)",
|
|
5630
|
+
style: {
|
|
5631
|
+
background: "none",
|
|
5632
|
+
border: "none",
|
|
5633
|
+
cursor: "pointer",
|
|
5634
|
+
fontSize: "18px",
|
|
5635
|
+
color: "var(--dsw-alias-label-secondary, #656d76)",
|
|
5636
|
+
padding: "0 6px",
|
|
5637
|
+
},
|
|
5638
|
+
onClick: onClose,
|
|
5639
|
+
title: "关闭 (Esc)",
|
|
5640
|
+
}, "✕"))),
|
|
5641
|
+
// 主体
|
|
5642
|
+
loading
|
|
5643
|
+
? h("div", { style: { padding: "64px 0", textAlign: "center", fontSize: "13px", opacity: 0.7 } }, "⏳ 正在加载 Issue 详情与讨论…")
|
|
5644
|
+
: error
|
|
5645
|
+
? h("div", {
|
|
5646
|
+
style: {
|
|
5647
|
+
padding: "24px",
|
|
5648
|
+
margin: "16px",
|
|
5649
|
+
borderRadius: "8px",
|
|
5650
|
+
background: "rgba(248,81,73,0.08)",
|
|
5651
|
+
border: "1px solid rgba(248,81,73,0.3)",
|
|
5652
|
+
},
|
|
5653
|
+
}, h("div", { style: { fontWeight: 600, color: "#f85149", marginBottom: "6px" } }, "⚠️ 读取 Issue 详情失败"), h("div", { style: { fontSize: "12px", opacity: 0.85, lineHeight: 1.6 } }, error), repoNameWithOwner
|
|
5654
|
+
? h("a", {
|
|
5655
|
+
href: "https://github.com/" + repoNameWithOwner + "/issues/" + issueNumber,
|
|
5656
|
+
target: "_blank",
|
|
5657
|
+
rel: "noopener noreferrer",
|
|
5658
|
+
className: "dsh-github-btn-sm",
|
|
5659
|
+
style: { display: "inline-block", marginTop: "12px", padding: "4px 10px", fontSize: "11px", textDecoration: "none" },
|
|
5660
|
+
}, "↗ 在浏览器中查看")
|
|
5661
|
+
: null)
|
|
5662
|
+
: data
|
|
5663
|
+
? h("div", { className: "dsh-github-issue-conversation-pane" }, metaChips(data), bodyCard(data), h("div", { className: "dsh-github-issue-timeline-title" }, "💬 讨论与评论时间线 (" + (data.comments ? data.comments.length : 0) + ")"), data.comments && data.comments.length > 0
|
|
5664
|
+
? data.comments.map(function (c) {
|
|
5665
|
+
return h(IssueCommentCard, { key: c.id || c.createdAt, comment: c });
|
|
5666
|
+
})
|
|
5667
|
+
: h("div", { className: "dsh-github-issue-empty-timeline" }, "💬 暂无讨论与评论——这是一个还没有人回复的议题。"))
|
|
5668
|
+
: null));
|
|
5669
|
+
}
|
|
5670
|
+
|
|
5671
|
+
// ---- [Bundled: run-modal.js] ----
|
|
5672
|
+
/**
|
|
5673
|
+
* 日志请求的客户端超时。
|
|
5674
|
+
*
|
|
5675
|
+
* 服务端总预算 90s,这里放 100s:让服务端的「超预算」显式错误先到;
|
|
5676
|
+
* 而连接卡在代理/DNS 层永远不返回时,客户端的 AbortController 是最后一道闸——
|
|
5677
|
+
* 否则按钮会永远停在「正在下载日志…」,用户只能刷新页面。
|
|
5678
|
+
*/
|
|
5679
|
+
var LOG_FETCH_TIMEOUT_MS = 100_000;
|
|
5680
|
+
/**
|
|
5681
|
+
* 结论 → 中文文案。
|
|
5682
|
+
*
|
|
5683
|
+
* ciBadge 只统一了 tone 与英文 label(服务端 Markdown 与右侧栏共用同一份映射),
|
|
5684
|
+
* 中文必须在本组件单独补一层;改 ciBadge 会连带改掉 CLI 与服务端输出。
|
|
5685
|
+
*/
|
|
5686
|
+
var RUN_CONCLUSION_LABEL = {
|
|
5687
|
+
success: "成功",
|
|
5688
|
+
failure: "失败",
|
|
5689
|
+
cancelled: "已取消",
|
|
5690
|
+
skipped: "已跳过",
|
|
5691
|
+
};
|
|
5692
|
+
/**
|
|
5693
|
+
* 未命中中文表时保留 GitHub 原始英文(running / queued / timed_out 等):
|
|
5694
|
+
* 这些取值没有稳定的中文对应,硬翻反而让用户对不上 GitHub 页面。
|
|
5695
|
+
*/
|
|
5696
|
+
function zhConclusion(conclusion, fallback) {
|
|
5697
|
+
var zh = RUN_CONCLUSION_LABEL[String(conclusion || "").toLowerCase()];
|
|
5698
|
+
return zh || fallback;
|
|
5699
|
+
}
|
|
5700
|
+
function pad2(n) {
|
|
5701
|
+
return n < 10 ? "0" + n : String(n);
|
|
5702
|
+
}
|
|
5703
|
+
/**
|
|
5704
|
+
* Actions run / job / step 的耗时格式化(导出以便单测直接覆盖)。
|
|
5705
|
+
*
|
|
5706
|
+
* 为什么不用 formatRelativeTime:那个函数答的是「发生在多久前」,这里是「跑了多久」;
|
|
5707
|
+
* 而且 run → job → step 三层共用同一套精度规则(<60s → `12s`、<60m → `3m20s`、
|
|
5708
|
+
* 其余 `1h05m`),分钟/秒补零才能让三层的列宽稳定对齐。
|
|
5709
|
+
* 任一端缺失或不可解析一律返回空串:排队中/已跳过的 job 没有 startedAt/completedAt,
|
|
5710
|
+
* 硬算只会渲染出「0s」这种假精确值,比不显示更误导。
|
|
5711
|
+
*/
|
|
5712
|
+
function formatRunDuration(startISO, endISO) {
|
|
5713
|
+
var startT = typeof startISO === "string" ? Date.parse(startISO) : NaN;
|
|
5714
|
+
var endT = typeof endISO === "string" ? Date.parse(endISO) : NaN;
|
|
5715
|
+
// endT < startT 视为非法区间(时钟漂移 / 字段错配),同样不显示
|
|
5716
|
+
if (isNaN(startT) || isNaN(endT) || endT < startT)
|
|
5717
|
+
return "";
|
|
5718
|
+
var totalSec = Math.round((endT - startT) / 1000);
|
|
5719
|
+
if (totalSec < 60)
|
|
5720
|
+
return totalSec + "s";
|
|
5721
|
+
var totalMin = Math.floor(totalSec / 60);
|
|
5722
|
+
if (totalMin < 60)
|
|
5723
|
+
return totalMin + "m" + pad2(totalSec % 60) + "s";
|
|
5724
|
+
return Math.floor(totalMin / 60) + "h" + pad2(totalMin % 60) + "m";
|
|
5725
|
+
}
|
|
5726
|
+
/**
|
|
5727
|
+
* 初始展开哪一个 job:优先第一个失败的(排障入口就在那里),全绿时展开第一个。
|
|
5728
|
+
* 必须在 setData 之后用真实数据计算——写死 {} 会让「默认展开」永远不生效。
|
|
5729
|
+
*/
|
|
5730
|
+
function computeInitialExpanded(d) {
|
|
5731
|
+
var init = {};
|
|
5732
|
+
var jobs = d && Array.isArray(d.jobs) ? d.jobs : [];
|
|
5733
|
+
if (jobs.length === 0)
|
|
5734
|
+
return init;
|
|
5735
|
+
var target = jobs[0];
|
|
5736
|
+
for (var i = 0; i < jobs.length; i++) {
|
|
5737
|
+
var job = jobs[i];
|
|
5738
|
+
if (String(job && job.conclusion ? job.conclusion : "").toLowerCase() === "failure") {
|
|
5739
|
+
target = job;
|
|
5740
|
+
break;
|
|
5741
|
+
}
|
|
5742
|
+
}
|
|
5743
|
+
if (target && target.databaseId !== undefined && target.databaseId !== null) {
|
|
5744
|
+
init[target.databaseId] = true;
|
|
5745
|
+
}
|
|
5746
|
+
return init;
|
|
5747
|
+
}
|
|
5748
|
+
/**
|
|
5749
|
+
* Actions Run 详情弹窗(只读:run 元信息 + job/step 树 + 失败日志片段)。
|
|
5750
|
+
*
|
|
5751
|
+
* 单列纵向:run 的信息天然是「job 列表 + 一条日志流」的阅读顺序,没有需要并排
|
|
5752
|
+
* 对照的第二个信息源(PR 才有 diff 双栏)。
|
|
5753
|
+
* 日志刻意渲染成 <pre> 原文而不是 markdown-lite:原始日志里的 `#`、`|`、`---`
|
|
5754
|
+
* 是 shell 注释、表格与 diff 分隔符,交给 Markdown 解析会把堆栈与 diff 撕碎。
|
|
5755
|
+
*/
|
|
5756
|
+
function RunDetailModal(props) {
|
|
5757
|
+
var runId = props.runId;
|
|
5758
|
+
var repoNameWithOwner = props.repoNameWithOwner || "";
|
|
5759
|
+
var cwd = props.cwd;
|
|
5760
|
+
var onClose = props.onClose;
|
|
5761
|
+
var showToast = props.showToast || (function () { });
|
|
5762
|
+
// Hooks 必须先于任何 early return 无条件执行(见 issue-modal 的同类注释):
|
|
5763
|
+
// runId 由空变有值时两次渲染的 Hook 数量必须一致,否则 React 直接抛错。
|
|
5764
|
+
// 因此 useState 与两个 useEffect 全部集中在这里,`if (!runId) return null`
|
|
5765
|
+
// 只能出现在它们之后。
|
|
5766
|
+
var [data, setData] = React.useState(null);
|
|
5767
|
+
var [loading, setLoading] = React.useState(true);
|
|
5768
|
+
var [error, setError] = React.useState(null);
|
|
5769
|
+
var [expanded, setExpanded] = React.useState({});
|
|
5770
|
+
var [logState, setLogState] = React.useState({ status: "idle" });
|
|
5771
|
+
React.useEffect(function () {
|
|
5772
|
+
var onKeyDown = function (e) {
|
|
5773
|
+
if (e.key === "Escape" || e.keyCode === 27)
|
|
5774
|
+
onClose();
|
|
5775
|
+
};
|
|
5776
|
+
window.addEventListener("keydown", onKeyDown);
|
|
5777
|
+
// 必须成对移除:弹窗在面板里会被反复开关,漏掉会累积监听器(Esc 一次关多个)
|
|
5778
|
+
return function () { window.removeEventListener("keydown", onKeyDown); };
|
|
5779
|
+
}, [onClose]);
|
|
5780
|
+
React.useEffect(function () {
|
|
5781
|
+
if (!runId) {
|
|
5782
|
+
setLoading(false);
|
|
5783
|
+
return;
|
|
5784
|
+
}
|
|
5785
|
+
setLoading(true);
|
|
5786
|
+
setError(null);
|
|
5787
|
+
// 换了 run 就是全新上下文:展开态与日志结果都不能沿用上一个 run 的
|
|
5788
|
+
setExpanded({});
|
|
5789
|
+
setLogState({ status: "idle" });
|
|
5790
|
+
var url = "/api/github/run-detail?run_id=" +
|
|
5791
|
+
encodeURIComponent(String(runId)) +
|
|
5792
|
+
(repoNameWithOwner ? "&repo=" + encodeURIComponent(repoNameWithOwner) : "") +
|
|
5793
|
+
(cwd ? "&cwd=" + encodeURIComponent(cwd) : "");
|
|
5794
|
+
fetch(url)
|
|
5795
|
+
.then(async function (res) {
|
|
5796
|
+
if (!res.ok) {
|
|
5797
|
+
// 非 2xx 时 body 里可能带一段很长的 gh 原始报错,优先原样透出给用户
|
|
5798
|
+
var errText = await res.text().catch(function () { return ""; });
|
|
5799
|
+
var j = null;
|
|
5800
|
+
try {
|
|
5801
|
+
j = JSON.parse(errText);
|
|
5802
|
+
}
|
|
5803
|
+
catch (e) { }
|
|
5804
|
+
throw new Error((j && j.error) || errText || ("服务响应异常 (" + res.status + ")"));
|
|
5805
|
+
}
|
|
5806
|
+
return res.json();
|
|
5807
|
+
})
|
|
5808
|
+
.then(function (json) {
|
|
5809
|
+
setLoading(false);
|
|
5810
|
+
if (json && json.ok && json.data) {
|
|
5811
|
+
setData(json.data);
|
|
5812
|
+
// 默认展开必须基于刚拿到的数据算,不能预设 {}
|
|
5813
|
+
setExpanded(computeInitialExpanded(json.data));
|
|
5814
|
+
}
|
|
5815
|
+
else {
|
|
5816
|
+
setError((json && json.error) || "获取 Actions Run 详情失败");
|
|
5817
|
+
}
|
|
5818
|
+
})
|
|
5819
|
+
.catch(function (err) {
|
|
5820
|
+
setLoading(false);
|
|
5821
|
+
setError((err && err.message) || "网络请求异常");
|
|
5822
|
+
});
|
|
5823
|
+
}, [runId, repoNameWithOwner, cwd]);
|
|
5824
|
+
if (!runId)
|
|
5825
|
+
return null;
|
|
5826
|
+
var h = React.createElement;
|
|
5827
|
+
var toggleJob = function (jobId) {
|
|
5828
|
+
setExpanded(function (prev) {
|
|
5829
|
+
// 必须新建对象:原地改 prev 引用不变,React 判定状态未变,不会重渲染
|
|
5830
|
+
var next = {};
|
|
5831
|
+
for (var k in prev) {
|
|
5832
|
+
if (Object.prototype.hasOwnProperty.call(prev, k))
|
|
5833
|
+
next[k] = prev[k];
|
|
5834
|
+
}
|
|
5835
|
+
next[jobId] = !next[jobId];
|
|
5836
|
+
return next;
|
|
5837
|
+
});
|
|
5838
|
+
};
|
|
5839
|
+
/** 结论徽章:tone 来自 shared/state-badges,文案走中文表 */
|
|
5840
|
+
var badgeEl = function (conclusion, status) {
|
|
5841
|
+
var b = ciBadge(status, conclusion);
|
|
5842
|
+
if (!b)
|
|
5843
|
+
return null;
|
|
5844
|
+
return h("span", {
|
|
5845
|
+
className: "dsh-github-badge dsh-badge-" + b.tone,
|
|
5846
|
+
style: { fontSize: "10px", padding: "1px 6px", flexShrink: 0 },
|
|
5847
|
+
}, zhConclusion(conclusion, b.label));
|
|
5848
|
+
};
|
|
5849
|
+
var renderMeta = function (d) {
|
|
5850
|
+
var parts = [];
|
|
5851
|
+
var ev = eventLabel(d.event);
|
|
5852
|
+
if (ev)
|
|
5853
|
+
parts.push(h("span", { key: "ev" }, ev));
|
|
5854
|
+
if (d.headBranch) {
|
|
5855
|
+
parts.push(h("span", { key: "br", className: "dsh-github-run-mono", title: "分支 " + d.headBranch }, d.headBranch));
|
|
5856
|
+
}
|
|
5857
|
+
if (d.headSha) {
|
|
5858
|
+
// 短 SHA 只留 7 位(GitHub 约定长度),完整值放 title,方便核对而不是复制错
|
|
5859
|
+
parts.push(h("span", { key: "sha", className: "dsh-github-run-mono", title: "完整 SHA: " + d.headSha }, String(d.headSha).slice(0, 7)));
|
|
5860
|
+
}
|
|
5861
|
+
if (d.createdAt)
|
|
5862
|
+
parts.push(h("span", { key: "created" }, formatRelativeTime(d.createdAt)));
|
|
5863
|
+
// run 没有 completedAt 字段,用 updatedAt 作终点:运行结束后它就是完成时间
|
|
5864
|
+
var dur = formatRunDuration(d.startedAt, d.updatedAt);
|
|
5865
|
+
if (dur)
|
|
5866
|
+
parts.push(h("span", { key: "dur" }, "耗时 " + dur));
|
|
5867
|
+
if (d.fieldsDegraded) {
|
|
5868
|
+
parts.push(h("span", { key: "degraded", className: "dsh-github-run-degraded", title: "部分字段未取到(job 列表或结论缺失),服务端已降级返回" }, "⚠️ 部分字段降级"));
|
|
5869
|
+
}
|
|
5870
|
+
if (parts.length === 0)
|
|
5871
|
+
return null;
|
|
5872
|
+
return h("div", { className: "dsh-github-run-meta" }, ...parts);
|
|
5873
|
+
};
|
|
5874
|
+
var renderSteps = function (job) {
|
|
5875
|
+
var steps = job && Array.isArray(job.steps) ? job.steps : [];
|
|
5876
|
+
if (steps.length === 0) {
|
|
5877
|
+
return h("div", { className: "dsh-github-run-steps" }, h("div", { className: "dsh-github-run-empty" }, "该 job 没有可读步骤(可能仍在排队)"));
|
|
5878
|
+
}
|
|
5879
|
+
return h("div", { className: "dsh-github-run-steps" }, steps.map(function (s, i) {
|
|
5880
|
+
var failed = String((s && s.conclusion) || "").toLowerCase() === "failure";
|
|
5881
|
+
return h("div", {
|
|
5882
|
+
key: "step-" + (s && s.number !== undefined ? s.number : i),
|
|
5883
|
+
className: "dsh-github-run-step" + (failed ? " dsh-github-run-step-failed" : ""),
|
|
5884
|
+
}, h("span", null, String(s && s.number !== undefined ? s.number : i + 1)), h("span", { title: (s && s.name) || "" }, (s && s.name) || "未命名步骤"),
|
|
5885
|
+
// 徽章外层固定包一个 span:徽章为空时若不占位,grid 第 3 列消失,
|
|
5886
|
+
// 后面的耗时列会左移到徽章列,四列对不齐
|
|
5887
|
+
h("span", null, badgeEl(s && s.conclusion, s && s.status)), h("span", { className: "dsh-github-run-duration" }, formatRunDuration(s && s.startedAt, s && s.completedAt)));
|
|
5888
|
+
}));
|
|
5889
|
+
};
|
|
5890
|
+
var renderJob = function (job, idx) {
|
|
5891
|
+
var steps = job && Array.isArray(job.steps) ? job.steps : [];
|
|
5892
|
+
var failedSteps = 0;
|
|
5893
|
+
steps.forEach(function (s) {
|
|
5894
|
+
if (String((s && s.conclusion) || "").toLowerCase() === "failure")
|
|
5895
|
+
failedSteps++;
|
|
5896
|
+
});
|
|
5897
|
+
var jobId = job && job.databaseId;
|
|
5898
|
+
var isOpen = Boolean(expanded[jobId]);
|
|
5899
|
+
var jobDur = formatRunDuration(job && job.startedAt, job && job.completedAt);
|
|
5900
|
+
return h("div", { key: "job-" + (jobId !== undefined && jobId !== null ? jobId : idx), className: "dsh-github-run-job" }, h("button", {
|
|
5901
|
+
type: "button",
|
|
5902
|
+
className: "dsh-github-run-job-header",
|
|
5903
|
+
"aria-expanded": isOpen ? "true" : "false",
|
|
5904
|
+
onClick: function () { toggleJob(jobId); },
|
|
5905
|
+
title: isOpen ? "收起该 job 的步骤" : "展开该 job 的步骤",
|
|
5906
|
+
}, h("span", { className: "dsh-github-run-caret", "aria-hidden": "true" }, isOpen ? "▾" : "▸"), h("span", { className: "dsh-github-run-job-name", title: (job && job.name) || "" }, (job && job.name) || "未命名 job"), badgeEl(job && job.conclusion, job && job.status), jobDur ? h("span", { className: "dsh-github-run-duration" }, jobDur) : null, steps.length > 0
|
|
5907
|
+
? h("span", { className: "dsh-github-run-failed-count" + (failedSteps > 0 ? " is-hit" : "") }, failedSteps + "/" + steps.length + " 步失败")
|
|
5908
|
+
: null), isOpen ? renderSteps(job) : null);
|
|
5909
|
+
};
|
|
5910
|
+
/**
|
|
5911
|
+
* 选出该拉日志的 job。
|
|
5912
|
+
*
|
|
5913
|
+
* 为什么不能只看 `job.conclusion === 'failure'`:按钮的出现条件是「有失败 step
|
|
5914
|
+
* 或失败 job」,而 `timed_out` / `cancelled` 的 job 完全可能带着失败 step
|
|
5915
|
+
* (`gh run view --help` 对 `--log-failed` 的定义是 "any failed steps")。
|
|
5916
|
+
* 两处口径不一致,用户就会看到「按钮在、点下去说没有失败的 job」。
|
|
5917
|
+
*/
|
|
5918
|
+
var pickLogJobIds = function (jobs) {
|
|
5919
|
+
var ids = [];
|
|
5920
|
+
for (var i = 0; i < jobs.length; i++) {
|
|
5921
|
+
var job = jobs[i];
|
|
5922
|
+
var jobId = job && job.databaseId;
|
|
5923
|
+
// databaseId 缺失或为 0 的 job 无法定向拉取(服务端 requirement 是正整数)
|
|
5924
|
+
if (typeof jobId !== "number" || jobId <= 0)
|
|
5925
|
+
continue;
|
|
5926
|
+
var jobFailed = String((job && job.conclusion) || "").toLowerCase() === "failure";
|
|
5927
|
+
var stepFailed = Array.isArray(job && job.steps) &&
|
|
5928
|
+
job.steps.some(function (s) {
|
|
5929
|
+
return String((s && s.conclusion) || "").toLowerCase() === "failure";
|
|
5930
|
+
});
|
|
5931
|
+
if (jobFailed || stepFailed)
|
|
5932
|
+
ids.push(jobId);
|
|
5933
|
+
}
|
|
5934
|
+
return ids;
|
|
5935
|
+
};
|
|
5936
|
+
var loadLogs = function () {
|
|
5937
|
+
if (!data || logState.status === "loading")
|
|
5938
|
+
return;
|
|
5939
|
+
var jobs = Array.isArray(data.jobs) ? data.jobs : [];
|
|
5940
|
+
var allIds = pickLogJobIds(jobs);
|
|
5941
|
+
if (allIds.length === 0) {
|
|
5942
|
+
showToast("⚠️ 没有失败的 job 可提取日志");
|
|
5943
|
+
return;
|
|
5089
5944
|
}
|
|
5090
|
-
|
|
5091
|
-
|
|
5945
|
+
// 服务端上限 3 个(超标直接 400)。这里先按上限请求,并在界面上明示「只取了前 3 个」,
|
|
5946
|
+
// 不让人以为这次把全部失败 job 都拉完了。
|
|
5947
|
+
var jobIds = allIds.slice(0, 3);
|
|
5948
|
+
setLogState({ status: "loading" });
|
|
5949
|
+
showToast("⏳ 正在下载失败日志(最长 90 秒)");
|
|
5950
|
+
var url = "/api/github/run-logs?run_id=" +
|
|
5951
|
+
encodeURIComponent(String(runId)) +
|
|
5952
|
+
// job_id 是逗号分隔的数字列表:数字与逗号都是 URL 安全字符,不再编码,
|
|
5953
|
+
// 免得某些服务端实现按未解码的原始串切分
|
|
5954
|
+
"&job_id=" + jobIds.join(",") +
|
|
5955
|
+
(repoNameWithOwner ? "&repo=" + encodeURIComponent(repoNameWithOwner) : "") +
|
|
5956
|
+
(cwd ? "&cwd=" + encodeURIComponent(cwd) : "");
|
|
5957
|
+
/*
|
|
5958
|
+
* 客户端超时不是可选项:服务端总预算 90s,但连接可能停在 DNS/代理层永远不返回。
|
|
5959
|
+
* 不设闸门的话按钮会永远停在「正在下载日志…」,用户只能刷新页面。
|
|
5960
|
+
* 100s 比服务端预算略长,让服务端的显式错误优先到达。
|
|
5961
|
+
*/
|
|
5962
|
+
var controller = typeof AbortController === "function" ? new AbortController() : null;
|
|
5963
|
+
// 闭包里不能再依赖可空收窄(TS 对捕获变量不做 narrow),在 if 内绑一个非空引用
|
|
5964
|
+
var timer = null;
|
|
5965
|
+
if (controller) {
|
|
5966
|
+
var ctrl = controller;
|
|
5967
|
+
timer = setTimeout(function () { ctrl.abort(); }, LOG_FETCH_TIMEOUT_MS);
|
|
5092
5968
|
}
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5969
|
+
var clearTimer = function () {
|
|
5970
|
+
if (timer) {
|
|
5971
|
+
clearTimeout(timer);
|
|
5972
|
+
timer = null;
|
|
5973
|
+
}
|
|
5974
|
+
};
|
|
5975
|
+
fetch(url, controller ? { signal: controller.signal } : undefined)
|
|
5976
|
+
.then(async function (res) {
|
|
5977
|
+
if (!res.ok) {
|
|
5978
|
+
var errText = await res.text().catch(function () { return ""; });
|
|
5979
|
+
var j = null;
|
|
5980
|
+
try {
|
|
5981
|
+
j = JSON.parse(errText);
|
|
5982
|
+
}
|
|
5983
|
+
catch (e) { }
|
|
5984
|
+
throw new Error((j && j.error) || errText || ("日志接口响应异常 (" + res.status + ")"));
|
|
5985
|
+
}
|
|
5986
|
+
return res.json();
|
|
5987
|
+
})
|
|
5988
|
+
.then(function (json) {
|
|
5989
|
+
clearTimer();
|
|
5990
|
+
if (json && json.ok && json.data) {
|
|
5991
|
+
setLogState({ status: "ok", data: json.data });
|
|
5992
|
+
}
|
|
5993
|
+
else {
|
|
5994
|
+
setLogState({ status: "error", error: (json && json.error) || "获取运行日志失败" });
|
|
5995
|
+
}
|
|
5996
|
+
})
|
|
5997
|
+
.catch(function (err) {
|
|
5998
|
+
clearTimer();
|
|
5999
|
+
var aborted = err && (err.name === "AbortError" || err.code === 20);
|
|
6000
|
+
setLogState({
|
|
6001
|
+
status: "error",
|
|
6002
|
+
error: aborted
|
|
6003
|
+
? "日志拉取超时(100 秒)已中止。GitHub 日志接口在代理下常常长时间无响应,请稍后重试或直接点右上角「GitHub ↗」查看。"
|
|
6004
|
+
: (err && err.message) || "网络请求异常(日志链路较慢,可直接重试)",
|
|
6005
|
+
});
|
|
5109
6006
|
});
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
6007
|
+
};
|
|
6008
|
+
var renderLogSection = function () {
|
|
6009
|
+
if (!data)
|
|
6010
|
+
return null;
|
|
6011
|
+
// failedSteps 只覆盖「step 级失败」;job 级失败但零失败 step 是真实形态
|
|
6012
|
+
// (setup 阶段失败、被 cancellation 打断),此时日志按钮同样必须出现。
|
|
6013
|
+
var hasFailed = Number(data.failedSteps) > 0 || Number(data.failedJobs) > 0;
|
|
6014
|
+
if (!hasFailed && logState.status === "idle")
|
|
6015
|
+
return null;
|
|
6016
|
+
var loadingLog = logState.status === "loading";
|
|
6017
|
+
var children = [];
|
|
6018
|
+
if (hasFailed) {
|
|
6019
|
+
children.push(h("button", {
|
|
6020
|
+
key: "log-btn",
|
|
6021
|
+
type: "button",
|
|
6022
|
+
className: "dsh-github-run-log-btn",
|
|
6023
|
+
onClick: loadLogs,
|
|
6024
|
+
disabled: loadingLog,
|
|
6025
|
+
title: "从 GitHub 拉取失败 job 的原始日志片段(较慢,最长 90 秒)",
|
|
6026
|
+
}, loadingLog ? "正在下载日志…" : logState.status === "ok" ? "📄 重新提取失败日志" : "📄 提取失败日志"));
|
|
6027
|
+
// 服务端一次最多 3 个 job:超出的必须说出来,否则用户以为全拉完了
|
|
6028
|
+
var failedJobTotal = pickLogJobIds(Array.isArray(data.jobs) ? data.jobs : []).length;
|
|
6029
|
+
if (failedJobTotal > 3) {
|
|
6030
|
+
children.push(h("div", { key: "log-cap", className: "dsh-github-run-log-hint" }, "本次仅提取前 3 个失败 job 的日志(共 " + failedJobTotal + " 个),其余请到 GitHub 查看。"));
|
|
6031
|
+
}
|
|
5117
6032
|
}
|
|
5118
|
-
if (
|
|
5119
|
-
|
|
6033
|
+
if (loadingLog) {
|
|
6034
|
+
children.push(h("div", { key: "log-hint", className: "dsh-github-run-log-hint" }, "⏳ 正在拉取失败日志——GitHub 日志接口较慢(最长 90 秒),请不要关闭弹窗。"));
|
|
5120
6035
|
}
|
|
5121
|
-
(
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
6036
|
+
if (logState.status === "error") {
|
|
6037
|
+
children.push(h("div", { key: "log-error", className: "dsh-github-run-error" }, h("div", { className: "dsh-github-run-error-title" }, "⚠️ 提取失败日志失败"), h("div", { className: "dsh-github-run-error-text" }, logState.error || "未知错误"),
|
|
6038
|
+
// 只有显式点击才重试:慢链路不能自动重试(见 RunLogState 注释)
|
|
6039
|
+
h("button", { key: "retry", type: "button", className: "dsh-github-run-log-btn", onClick: loadLogs }, "🔁 重试")));
|
|
6040
|
+
}
|
|
6041
|
+
if (logState.status === "ok" && logState.data) {
|
|
6042
|
+
var logData = logState.data;
|
|
6043
|
+
var logJobs = Array.isArray(logData.jobs) ? logData.jobs : [];
|
|
6044
|
+
var logErrors = Array.isArray(logData.errors) ? logData.errors : [];
|
|
6045
|
+
if (logJobs.length === 0 && logErrors.length === 0) {
|
|
6046
|
+
children.push(h("div", { key: "log-none", className: "dsh-github-run-empty" }, "GitHub 未返回可读日志(可能日志已过期或权限不足)"));
|
|
6047
|
+
}
|
|
6048
|
+
logJobs.forEach(function (block, i) {
|
|
6049
|
+
children.push(h("div", { key: "log-block-" + i, className: "dsh-github-run-log-block" }, h("div", { className: "dsh-github-run-log-title" }, "📄 " + (block.jobName || ("job " + block.jobId))), block.truncated
|
|
6050
|
+
? h("div", { className: "dsh-github-run-log-truncated" }, "⚠️ 原始日志超出上限,仅显示已获取部分")
|
|
6051
|
+
: null,
|
|
6052
|
+
// <pre> 原文直出:日志里的 #、|、--- 不能进 markdown-lite
|
|
6053
|
+
h("pre", { className: "dsh-github-run-log" }, block.excerpt || "(该 job 无日志输出)")));
|
|
6054
|
+
});
|
|
6055
|
+
logErrors.forEach(function (e, i) {
|
|
6056
|
+
children.push(h("div", { key: "log-err-" + i, className: "dsh-github-run-error" }, h("div", { className: "dsh-github-run-error-title" }, "⚠️ " + (e.jobName || "job") + " 日志获取失败"), h("div", { className: "dsh-github-run-error-text" }, e.error || "未知错误")));
|
|
6057
|
+
});
|
|
6058
|
+
if (logData.fetchedAt) {
|
|
6059
|
+
children.push(h("div", { key: "log-at", className: "dsh-github-run-log-hint" }, "日志获取于 " + formatRelativeTime(logData.fetchedAt)));
|
|
6060
|
+
}
|
|
6061
|
+
// 名称降级必须可见(服务端 details 降级时 jobName 会退化成 job #<id>)
|
|
6062
|
+
if (logData.namesUnavailable) {
|
|
6063
|
+
children.push(h("div", { key: "log-names", className: "dsh-github-run-log-hint" }, "⚠️ job 名称未取到(详情字段降级),以 job #编号 显示。"));
|
|
6064
|
+
}
|
|
6065
|
+
}
|
|
6066
|
+
return h("div", { key: "logs", className: "dsh-github-run-log-section" }, ...children);
|
|
5138
6067
|
};
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
onClick: function (e) { e.stopPropagation(); },
|
|
5145
|
-
},
|
|
5146
|
-
// 头部
|
|
5147
|
-
h("div", { className: "dsh-github-modal-header dsh-github-issue-modal-header" }, h("div", { style: { display: "flex", alignItems: "center", gap: "8px", minWidth: 0, flex: 1 } }, renderOctocat(18, "#1f883d"), h("div", { style: { display: "flex", flexDirection: "column", gap: "2px", minWidth: 0 } }, h("div", { style: { display: "flex", alignItems: "center", gap: "6px", flexWrap: "wrap" } }, h("h3", {
|
|
6068
|
+
var headerTitle = data
|
|
6069
|
+
? data.workflowName || data.name || "Actions Run"
|
|
6070
|
+
: "Actions Run";
|
|
6071
|
+
var ghHref = (data && data.url) || (repoNameWithOwner ? "https://github.com/" + repoNameWithOwner + "/actions/runs/" + runId : "");
|
|
6072
|
+
var header = h("div", { className: "dsh-github-modal-header dsh-github-run-modal-header" }, h("div", { style: { display: "flex", alignItems: "center", gap: "8px", minWidth: 0, flex: 1 } }, h("span", { style: { fontSize: "16px", lineHeight: 1, flexShrink: 0 }, "aria-hidden": "true" }, "⚡"), h("div", { style: { display: "flex", flexDirection: "column", gap: "2px", minWidth: 0 } }, h("div", { style: { display: "flex", alignItems: "center", gap: "6px", flexWrap: "wrap" } }, h("h3", {
|
|
5148
6073
|
style: {
|
|
5149
6074
|
margin: 0,
|
|
5150
6075
|
fontSize: "14px",
|
|
@@ -5152,9 +6077,12 @@ function IssueDetailModal(props) {
|
|
|
5152
6077
|
overflow: "hidden",
|
|
5153
6078
|
textOverflow: "ellipsis",
|
|
5154
6079
|
whiteSpace: "nowrap",
|
|
6080
|
+
maxWidth: "520px",
|
|
5155
6081
|
},
|
|
5156
|
-
title:
|
|
5157
|
-
},
|
|
6082
|
+
title: headerTitle + " #" + runId,
|
|
6083
|
+
}, headerTitle + " #" + runId), badgeEl(data ? data.conclusion : undefined, data ? data.status : undefined), data && Number(data.attempt) > 1
|
|
6084
|
+
? h("span", { className: "dsh-github-badge dsh-badge-yellow", style: { fontSize: "10px", padding: "1px 6px", flexShrink: 0 } }, "第 " + data.attempt + " 次尝试")
|
|
6085
|
+
: null), data && (data.displayTitle || repoNameWithOwner)
|
|
5158
6086
|
? h("div", {
|
|
5159
6087
|
style: {
|
|
5160
6088
|
fontSize: "11px",
|
|
@@ -5163,30 +6091,25 @@ function IssueDetailModal(props) {
|
|
|
5163
6091
|
gap: "6px",
|
|
5164
6092
|
alignItems: "center",
|
|
5165
6093
|
flexWrap: "wrap",
|
|
6094
|
+
minWidth: 0,
|
|
5166
6095
|
},
|
|
5167
|
-
},
|
|
5168
|
-
? h("span", { style: {
|
|
6096
|
+
}, data.displayTitle
|
|
6097
|
+
? h("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, title: data.displayTitle }, data.displayTitle)
|
|
6098
|
+
: null, repoNameWithOwner
|
|
6099
|
+
? h("span", { style: { fontFamily: "monospace", opacity: 0.85, flexShrink: 0 } }, repoNameWithOwner)
|
|
5169
6100
|
: null)
|
|
5170
|
-
: null)), h("div", { style: { display: "flex", alignItems: "center", gap: "6px", flexShrink: 0 } },
|
|
5171
|
-
? h("button", {
|
|
5172
|
-
type: "button",
|
|
5173
|
-
className: "dsh-github-btn-sm",
|
|
5174
|
-
style: { padding: "4px 8px", fontSize: "11px" },
|
|
5175
|
-
onClick: function () { copyLink(data.url); },
|
|
5176
|
-
title: "复制该 Issue 网页链接",
|
|
5177
|
-
}, copied ? "✓ 已复制" : "📋 复制链接")
|
|
5178
|
-
: null, data && data.url
|
|
6101
|
+
: null)), h("div", { style: { display: "flex", alignItems: "center", gap: "6px", flexShrink: 0 } }, ghHref
|
|
5179
6102
|
? h("a", {
|
|
5180
|
-
href:
|
|
6103
|
+
href: ghHref,
|
|
5181
6104
|
target: "_blank",
|
|
5182
6105
|
rel: "noopener noreferrer",
|
|
5183
|
-
className: "dsh-github-btn
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
}, "↗ 浏览器打开")
|
|
6106
|
+
className: "dsh-github-pr-gh-btn",
|
|
6107
|
+
title: "在 GitHub 中打开该次运行",
|
|
6108
|
+
}, "GitHub ↗")
|
|
5187
6109
|
: null, h("button", {
|
|
5188
6110
|
type: "button",
|
|
5189
|
-
"aria-label": "关闭
|
|
6111
|
+
"aria-label": "关闭",
|
|
6112
|
+
title: "关闭 (Esc)",
|
|
5190
6113
|
style: {
|
|
5191
6114
|
background: "none",
|
|
5192
6115
|
border: "none",
|
|
@@ -5196,36 +6119,40 @@ function IssueDetailModal(props) {
|
|
|
5196
6119
|
padding: "0 6px",
|
|
5197
6120
|
},
|
|
5198
6121
|
onClick: onClose,
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
6122
|
+
}, "✕")));
|
|
6123
|
+
var jobs = data && Array.isArray(data.jobs) ? data.jobs : [];
|
|
6124
|
+
var bodyChildren = [];
|
|
6125
|
+
if (data) {
|
|
6126
|
+
if (jobs.length === 0) {
|
|
6127
|
+
bodyChildren.push(h("div", { key: "empty", className: "dsh-github-run-empty" }, "暂无可读 job(可能仍在排队或已跳过)"));
|
|
6128
|
+
}
|
|
6129
|
+
else {
|
|
6130
|
+
jobs.forEach(function (job, i) {
|
|
6131
|
+
bodyChildren.push(renderJob(job, i));
|
|
6132
|
+
});
|
|
6133
|
+
}
|
|
6134
|
+
var logSection = renderLogSection();
|
|
6135
|
+
if (logSection)
|
|
6136
|
+
bodyChildren.push(logSection);
|
|
6137
|
+
}
|
|
6138
|
+
var bodyNode = loading
|
|
6139
|
+
? h("div", { style: { padding: "64px 0", textAlign: "center", fontSize: "13px", opacity: 0.7 } }, "⏳ 正在加载 Actions Run 详情…")
|
|
5204
6140
|
: error
|
|
5205
|
-
? h("div", {
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
}, "↗ 在浏览器中查看")
|
|
5221
|
-
: null)
|
|
5222
|
-
: data
|
|
5223
|
-
? h("div", { className: "dsh-github-issue-conversation-pane" }, metaChips(data), bodyCard(data), h("div", { className: "dsh-github-issue-timeline-title" }, "💬 讨论与评论时间线 (" + (data.comments ? data.comments.length : 0) + ")"), data.comments && data.comments.length > 0
|
|
5224
|
-
? data.comments.map(function (c) {
|
|
5225
|
-
return h(IssueCommentCard, { key: c.id || c.createdAt, comment: c });
|
|
5226
|
-
})
|
|
5227
|
-
: h("div", { className: "dsh-github-issue-empty-timeline" }, "💬 暂无讨论与评论——这是一个还没有人回复的议题。"))
|
|
5228
|
-
: null));
|
|
6141
|
+
? h("div", { className: "dsh-github-run-error" }, h("div", { className: "dsh-github-run-error-title" }, "⚠️ 读取 Actions Run 详情失败"),
|
|
6142
|
+
// 服务端会把 gh 的原始报错透传上来,可能很长,交给 .dsh-github-run-error-text 滚动
|
|
6143
|
+
h("div", { className: "dsh-github-run-error-text" }, error),
|
|
6144
|
+
// 不给「重试」按钮:详情 effect 只依赖 [runId, repoNameWithOwner, cwd],
|
|
6145
|
+
// 三者不变时重建同一份 effect 不会重新发起 fetch,按钮只会空转。
|
|
6146
|
+
// 需要重试的用户可直接用头部的 GitHub 外链核对。
|
|
6147
|
+
h("div", { className: "dsh-github-run-log-hint" }, "可关闭本弹窗后重新打开,或点击右上角「GitHub ↗」直接查看。"))
|
|
6148
|
+
: bodyChildren;
|
|
6149
|
+
return h("div", { className: "dsh-github-modal-overlay", onClick: onClose, style: { zIndex: 100000 } }, h("div", {
|
|
6150
|
+
className: "dsh-github-modal-dialog dsh-github-run-modal-dialog",
|
|
6151
|
+
role: "dialog",
|
|
6152
|
+
"aria-modal": "true",
|
|
6153
|
+
"aria-label": "Actions Run #" + runId + " 详情",
|
|
6154
|
+
onClick: function (e) { e.stopPropagation(); },
|
|
6155
|
+
}, header, data ? renderMeta(data) : null, h("div", { className: "dsh-github-run-body" }, bodyNode)));
|
|
5229
6156
|
}
|
|
5230
6157
|
|
|
5231
6158
|
// ---- [Bundled: sync-settings-modal.js] ----
|
|
@@ -5496,7 +6423,7 @@ function GlobalSyncSettingsModal(props) {
|
|
|
5496
6423
|
? "var(--dsw-alias-state-error-primary, #cf222e)"
|
|
5497
6424
|
: "var(--dsw-alias-label-secondary, #656d76)",
|
|
5498
6425
|
},
|
|
5499
|
-
}, updateMsg.text) : null)) : activeSection === "about" ? h("div", null, h("div", { style: { display: "flex", alignItems: "center", gap: "10px", marginBottom: "14px" } }, renderOctocatBadge(40), h("div", null, h("div", { style: { fontSize: "15px", fontWeight: 600 } }, "DSH GitHub Flow"), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginTop: "2px" } }, "当前版本: v" + (aboutVersion || "…")))), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginBottom: "14px", lineHeight: "1.6" } }, "让 DeepSeek Harness Agent 拥有操作 GitHub 的原生手脚。原生复用宿主机 gh CLI 安全凭据,深度打通本地 Git 工作区与云端协作,提供
|
|
6426
|
+
}, updateMsg.text) : null)) : activeSection === "about" ? h("div", null, h("div", { style: { display: "flex", alignItems: "center", gap: "10px", marginBottom: "14px" } }, renderOctocatBadge(40), h("div", null, h("div", { style: { fontSize: "15px", fontWeight: 600 } }, "DSH GitHub Flow"), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginTop: "2px" } }, "当前版本: v" + (aboutVersion || "…")))), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginBottom: "14px", lineHeight: "1.6" } }, "让 DeepSeek Harness Agent 拥有操作 GitHub 的原生手脚。原生复用宿主机 gh CLI 安全凭据,深度打通本地 Git 工作区与云端协作,提供 19 大领域 Agent 工具、人类 Slash 指令、Web 全景驾驶舱与一键跨平台升级。"), h("div", { style: { display: "flex", flexDirection: "column", gap: "8px", marginBottom: "14px" } }, h("div", { style: { display: "flex", alignItems: "center", gap: "8px", fontSize: "12px" } }, h("span", { style: { minWidth: "64px", color: "var(--dsw-alias-label-secondary, #656d76)" } }, "仓库地址"), h("a", { href: "https://github.com/ItQianChen/dsh-github-flow", target: "_blank", rel: "noopener noreferrer", style: { color: "var(--dsw-alias-link, #0969da)" } }, "github.com/ItQianChen/dsh-github-flow")), h("div", { style: { display: "flex", alignItems: "center", gap: "8px", fontSize: "12px" } }, h("span", { style: { minWidth: "64px", color: "var(--dsw-alias-label-secondary, #656d76)" } }, "npm 包"), h("a", { href: "https://www.npmjs.com/package/dsh-github-flow", target: "_blank", rel: "noopener noreferrer", style: { color: "var(--dsw-alias-link, #0969da)" } }, "npmjs.com/package/dsh-github-flow")), h("div", { style: { display: "flex", alignItems: "center", gap: "8px", fontSize: "12px" } }, h("span", { style: { minWidth: "64px", color: "var(--dsw-alias-label-secondary, #656d76)" } }, "开源协议"), h("a", { href: "https://github.com/ItQianChen/dsh-github-flow/blob/master/LICENSE", target: "_blank", rel: "noopener noreferrer", style: { color: "var(--dsw-alias-link, #0969da)" } }, "MIT License"))), h("div", {
|
|
5500
6427
|
className: "dsh-github-about-star-box",
|
|
5501
6428
|
style: {
|
|
5502
6429
|
display: "flex",
|
|
@@ -5593,7 +6520,7 @@ function GlobalSyncSettingsModal(props) {
|
|
|
5593
6520
|
*
|
|
5594
6521
|
* 【内容与代码同步的纪律】
|
|
5595
6522
|
* 本文件里出现的工具数量、action 数量、Tab 名称、按钮文案,全部以当前代码为准:
|
|
5596
|
-
* 工具矩阵 = src/main.ts 的
|
|
6523
|
+
* 工具矩阵 = src/main.ts 的 19 处 ctx.tools.register;
|
|
5597
6524
|
* Tab 布局 = src/client.ts 的 TAB_DEFS 与 src/ui/workspace-panel.ts 的四个标签;
|
|
5598
6525
|
* 若后续增删工具或 Tab,必须同步改这里,否则用户会照着过时说明去找不存在的入口。
|
|
5599
6526
|
*/
|
|
@@ -5632,7 +6559,7 @@ function GithubHelpModal(props) {
|
|
|
5632
6559
|
{ id: "quickstart", label: "🚀 3分钟极速上手" },
|
|
5633
6560
|
{ id: "prompts", label: "🤖 Agent 提示词实战" },
|
|
5634
6561
|
{ id: "panels", label: "🖥️ 两大界面操作图解" },
|
|
5635
|
-
{ id: "tools", label: "🧰
|
|
6562
|
+
{ id: "tools", label: "🧰 19 大工具速查" },
|
|
5636
6563
|
{ id: "faq", label: "🔑 常见自愈排错" },
|
|
5637
6564
|
];
|
|
5638
6565
|
// 2. 渲染各 Tab 独立视图
|
|
@@ -5661,7 +6588,7 @@ function GithubHelpModal(props) {
|
|
|
5661
6588
|
return h("div", { key: i, className: "dsh-github-help-card", style: { display: "flex", gap: "14px", alignItems: "flex-start" } }, h("div", { className: "dsh-github-help-step-badge" }, s.step), h("div", { style: { flex: 1 } }, h("div", { className: "dsh-github-help-title", style: { marginBottom: "4px" } }, s.title), h("div", { className: "dsh-github-help-desc", style: { marginBottom: "6px" } }, s.desc), h("div", { style: { fontSize: "12px", lineHeight: "1.6", whiteSpace: "pre-line", color: "inherit" } }, s.actionText)));
|
|
5662
6589
|
}),
|
|
5663
6590
|
// 能力总览:让用户三秒钟知道这插件到底能干什么,而不是去翻 README
|
|
5664
|
-
h("div", { className: "dsh-github-help-card", style: { marginTop: "4px" } }, h("div", { className: "dsh-github-help-title", style: { marginBottom: "8px" } }, "📦 能力总览:
|
|
6591
|
+
h("div", { className: "dsh-github-help-card", style: { marginTop: "4px" } }, h("div", { className: "dsh-github-help-title", style: { marginBottom: "8px" } }, "📦 能力总览:19 大工具 · 106 个业务动作 · 2 套可视化工作台"), h("div", { style: { fontSize: "12px", lineHeight: "1.8", color: "var(--dsw-alias-label-secondary, #8b949e)" } }, "Agent 侧可直接调用 19 个 github_* 工具(PR 审查合并、Issue 任务流转、Actions 排障、免 clone 读文件、发版交付、Secrets 管理、Skills 生态、标签与规则集、构建缓存、Gist、跨仓待办大盘、Copilot 智能体、Discussions、Projects v2、Codespaces、运行时遥测与元指南)。", h("br"), "人类侧在每个会话右侧栏拥有 5 个 Tab(Git / 未提交 / PR / Issues / Actions),在最左侧 Octocat 入口拥有 4 个标签的全景驾驶舱。PR / Issue / Actions 的卡片点击都能在线查看详情——Actions 看到的是 job / step 树与失败步骤,失败时还能按需拉一段日志,不用切浏览器。")));
|
|
5665
6592
|
}
|
|
5666
6593
|
if (activeTab === "prompts") {
|
|
5667
6594
|
var promptCards = [
|
|
@@ -5675,7 +6602,7 @@ function GithubHelpModal(props) {
|
|
|
5675
6602
|
title: "🚨 场景二:CI / Actions 门禁报错秒级排查与自动修复",
|
|
5676
6603
|
desc: "构建挂了不用在网页反复翻几十页日志,直接吩咐 Agent 定位报错步骤并就地改代码。",
|
|
5677
6604
|
prompt: "检查当前仓库最近一次 CI 失败的原因,精确定位报错步骤并帮我尝试修复代码",
|
|
5678
|
-
aiAction: "Agent
|
|
6605
|
+
aiAction: "Agent 会调 github_task(action:'diagnose_ci') 或 github_run(action:'log_failed'):先定位失败 job/step,再按框架锚点萃取日志(pytest / node / Go / tsc / npm),最后汇总疑似相关文件。日志被截断时它会明确告诉你「总数未知」,不要当成「就这些失败」。",
|
|
5679
6606
|
},
|
|
5680
6607
|
{
|
|
5681
6608
|
title: "📌 场景三:早会与每日任务待办盘点",
|
|
@@ -5747,17 +6674,19 @@ function GithubHelpModal(props) {
|
|
|
5747
6674
|
"未提交 Tab:实时监听本地未提交文件与状态码(?? 新增 / M 修改 / D 删除 / R 重命名),点击文件可就地下钻查看该文件的 Unified Diff;",
|
|
5748
6675
|
"PR Tab:展示全部 PR 并按状态过滤;带三态 CI 门禁、冲突检测、审核判定与 Draft 标示;点击卡片在线唤起 PR 审阅工作台(描述 / 评论 / 文件 Diff 双栏比对);",
|
|
5749
6676
|
"Issues Tab:展示全部 Issue 并按状态过滤;点击卡片在线查看详情(Markdown 正文 / 评论时间线 / 标签色块 / 里程碑 / 关联 PR / 隐藏评论标注);",
|
|
5750
|
-
"Actions Tab
|
|
6677
|
+
"Actions Tab:最近工作流运行记录与触发方式;点击卡片在线查看本次运行的 job / step 树(每步结论与耗时,失败步骤高亮,默认展开第一个失败 job),失败时可按需提取日志锚点片段(一次最多 3 个失败 job、服务端批次预算 90 秒);",
|
|
5751
6678
|
"动静解耦架构:修改本地代码只定向查询本地 .git 核心元数据(耗时仅 5ms),绝对不发起远端网络请求,杜绝网络风暴。",
|
|
5752
6679
|
],
|
|
5753
6680
|
},
|
|
5754
6681
|
{
|
|
5755
|
-
title: "3.
|
|
6682
|
+
title: "3. 三个详情弹窗的通用操作习惯",
|
|
5756
6683
|
items: [
|
|
5757
|
-
"PR / Issue
|
|
6684
|
+
"PR / Issue / Actions 三个弹窗都支持:按 Esc 或点击遮罩关闭、右上角「GitHub ↗」直达官方页面;",
|
|
5758
6685
|
"列表卡片上的「GitHub ↗」按钮是独立的浏览器外跳入口,点它不会触发弹窗(已做事件隔离);",
|
|
5759
6686
|
"PR 弹窗的文件区可拖拽分隔条调宽、双击或点 ◀ 折叠;布局偏好会记在本地,下次打开保持原样;",
|
|
5760
|
-
"长评论默认折叠(显示「⌄ 展开全部」),PR/Issue
|
|
6687
|
+
"长评论默认折叠(显示「⌄ 展开全部」),PR/Issue 正文默认展开——避免一屏被一段超长文字占满;",
|
|
6688
|
+
"Actions 弹窗按 job 折叠/展开步骤,默认展开第一个失败 job;「📄 提取失败日志」只在存在失败时出现,点一次拉一次(慢链路不做自动重试,失败可手动重试);",
|
|
6689
|
+
"日志片段是按失败锚点萃取过的——看到的是「失败块 + 上下文」或尾部 200 行,不是完整日志;要全文请点「GitHub ↗」。",
|
|
5761
6690
|
],
|
|
5762
6691
|
},
|
|
5763
6692
|
].map(function (sec, idx) {
|
|
@@ -5771,7 +6700,7 @@ function GithubHelpModal(props) {
|
|
|
5771
6700
|
items: [
|
|
5772
6701
|
{ name: "github_pr", desc: "PR 全生命周期:列表/详情/diff/checks/create/merge/review/ready/update_branch/revert/preview/checkout,含行级评论与附件上传" },
|
|
5773
6702
|
{ name: "github_issue", desc: "Issue 任务流转:list/view/create/comment/edit/close/reopen/develop/transfer/pin/lock/delete/status,含截图查看 view_images" },
|
|
5774
|
-
{ name: "github_run", desc: "Actions 排障:list
|
|
6703
|
+
{ name: "github_run", desc: "Actions 排障:list、view(run + job/step 树)、log_failed(失败步骤日志秒级提取)/rerun/cancel/trigger/download 与 workflow 启停" },
|
|
5775
6704
|
{ name: "github_repo", desc: "仓库元数据、免 clone 读文件(read_file/read_dir)、跨仓检索(code/issues/prs/commits)、Fork 同步" },
|
|
5776
6705
|
],
|
|
5777
6706
|
},
|
|
@@ -5805,7 +6734,7 @@ function GithubHelpModal(props) {
|
|
|
5805
6734
|
],
|
|
5806
6735
|
},
|
|
5807
6736
|
];
|
|
5808
|
-
return h("div", null, h("div", { className: "dsh-github-help-desc", style: { fontSize: "13px", marginBottom: "14px" } }, "
|
|
6737
|
+
return h("div", null, h("div", { className: "dsh-github-help-desc", style: { fontSize: "13px", marginBottom: "14px" } }, "19 个 Agent 工具、合计 106 个业务动作(不含元指南的 22 个查询主题与 api 逃生门的 4 个 HTTP method)。你不必记住工具名——直接用自然语言描述目标,Agent 会自己挑工具。下表用于你了解能力边界,以及排查「为什么 Agent 说做不到」:"), toolGroups.map(function (g, gi) {
|
|
5809
6738
|
return h("div", { key: gi, className: "dsh-github-help-card" }, h("div", { className: "dsh-github-help-title", style: { marginBottom: "8px" } }, g.group), g.items.map(function (t, ti) {
|
|
5810
6739
|
return h("div", { key: ti, style: { display: "flex", gap: "10px", alignItems: "flex-start", padding: "5px 0", borderTop: ti === 0 ? "none" : "1px solid var(--dsw-alias-border-l1, rgba(255,255,255,0.06))" } }, h("code", {
|
|
5811
6740
|
style: {
|
|
@@ -5845,6 +6774,14 @@ function GithubHelpModal(props) {
|
|
|
5845
6774
|
q: "Q6: 为什么改了代码,插件界面要重启 DSH 才生效?",
|
|
5846
6775
|
a: "如果你是在开发这个插件本身:后端 HTTP 路由是在插件 `apply()` 时一次性注册到宿主的 WebServer 上的,`pnpm run build` 只更新磁盘产物,不会给已运行的进程热注册新路由。改完代码需重启 DSH 才能挂上新端点。\n若只是普通使用,请忽略此条——正常升级走一键升级即可。",
|
|
5847
6776
|
},
|
|
6777
|
+
{
|
|
6778
|
+
q: "Q7: Actions 弹窗里点「提取失败日志」很久没反应,或报超时?",
|
|
6779
|
+
a: "GitHub 的日志接口是「先打包再下载」,实测单次 4–40 秒,走代理时还可能被直接重置——这是上游链路特性,不是插件卡死。当前实现有三道闸:\n- 服务端单 job 上限 60 秒、整批总预算 90 秒;超预算的 job 会明确列出「未拉取,可单独重试」,不会静默丢掉;\n- 客户端 100 秒自动中止,避免按钮永远停在「正在下载日志…」;\n- 失败后弹窗内给「🔁 重试」,不做自动重试(重试会把等待时间叠加)。\n急的话点右上角「GitHub ↗」看完整日志,或让 Agent 调 `github_task(action:'diagnose_ci', run_id:'…')` 直接拿结论包。",
|
|
6780
|
+
},
|
|
6781
|
+
{
|
|
6782
|
+
q: "Q8: 为什么 Actions 弹窗只能看 3 个失败 job 的日志?",
|
|
6783
|
+
a: "一次点击串行拉 3 个 job 已经是 60–90 秒量级;矩阵构建下失败 job 可能有十几个,全拉会把一次点击变成几分钟。当前策略:只取前 3 个失败 job,并在按钮下方明示「共 N 个,其余请到 GitHub 查看」。需要逐个深挖时,让 Agent 用 `github_run(action:'log_failed', run_id:'…')` 或 `github_task(action:'diagnose_ci')` 分批处理更合适。",
|
|
6784
|
+
},
|
|
5848
6785
|
];
|
|
5849
6786
|
return h("div", null, h("div", { className: "dsh-github-help-desc", style: { fontSize: "13px", marginBottom: "14px" } }, "常见环境、权限与网络问题的排查与自愈指引:"), faqs.map(function (f, idx) {
|
|
5850
6787
|
return h("div", { key: idx, className: "dsh-github-help-card" }, h("div", { className: "dsh-github-help-title", style: { marginBottom: "6px", fontSize: "13px" } }, f.q), h("div", { className: "dsh-github-help-desc", style: { lineHeight: "1.6", whiteSpace: "pre-line" } }, f.a));
|
|
@@ -6134,6 +7071,8 @@ function GithubHelpModal(props) {
|
|
|
6134
7071
|
return h("div", { key: idx, style: lineStyle }, line || " ");
|
|
6135
7072
|
})));
|
|
6136
7073
|
}
|
|
7074
|
+
/** 列表分页步长:与服务端 parseListLimit 的默认页大小保持一致(15) */
|
|
7075
|
+
var OVERVIEW_PAGE_SIZE = 15;
|
|
6137
7076
|
function renderGithubRepoView(h, data, loading, error, onRefresh, isRightbar = false, updateInfo = null, syncStatus = "", lastSyncedAt = 0, onOpenSettings = null, cwd = null, onSelectCommit = null, gitLogPagination = null, onShowToast = null, onOpenHelp = null, onOpenFile = null, dirtyPagination = null, activeDiffFile = null, onSelectDiffFile = null, onCloseDiff = null, onOpenInDsh = null, headerCardsVisible = true, onToggleHeaderCards = null, onSelectPr = null,
|
|
6138
7077
|
// 必须追加在形参末尾:该函数有 17 个位置参数,插在中间会静默错位
|
|
6139
7078
|
onSelectIssue = null,
|
|
@@ -6143,12 +7082,80 @@ function GithubHelpModal(props) {
|
|
|
6143
7082
|
* 普通函数,内部调用 useState 会被 React 当成新的 Hook 序列直接抛错。
|
|
6144
7083
|
* 状态必须住在 GithubRightbarBody 组件体内,这里只接收值与 setter。
|
|
6145
7084
|
*/
|
|
6146
|
-
prStateFilter = "all", issueStateFilter = "all", onChangePrFilter = null, onChangeIssueFilter = null
|
|
7085
|
+
prStateFilter = "all", issueStateFilter = "all", onChangePrFilter = null, onChangeIssueFilter = null,
|
|
7086
|
+
/**
|
|
7087
|
+
* PR / Issue / Actions 三个列表的「加载更多」回调(按 tab 独立触发)。
|
|
7088
|
+
*/
|
|
7089
|
+
onLoadMoreLists = null,
|
|
7090
|
+
/**
|
|
7091
|
+
* 各 Tab 的「正在加载更多」标记。
|
|
7092
|
+
*
|
|
7093
|
+
* 为什么需要它:请求往返几百毫秒到数秒,页脚不变化的话用户会以为点击没生效而连点,
|
|
7094
|
+
* 触发重复请求(服务端虽然有 loadingMoreListsRef 兜底,但界面必须有反馈)。
|
|
7095
|
+
*/
|
|
7096
|
+
listLoading = null,
|
|
7097
|
+
/**
|
|
7098
|
+
* Actions 卡片点击 → Run 详情弹窗。
|
|
7099
|
+
* 同样必须追加在形参末尾(理由见上一条:位置参数错位不会报错,只会静默串值)。
|
|
7100
|
+
*/
|
|
7101
|
+
onSelectRun = null) {
|
|
6147
7102
|
var auth = data ? data.auth : null;
|
|
6148
7103
|
var repo = data ? data.repo : null;
|
|
6149
7104
|
var prs = (data && data.pullRequests) || [];
|
|
6150
7105
|
var issues = (data && data.issues) || [];
|
|
6151
7106
|
var runs = (data && data.runs) || [];
|
|
7107
|
+
/**
|
|
7108
|
+
* 列表「加载更多」页脚:与「未提交变更 / Git 提交记录」两个已有分页同视觉同交互。
|
|
7109
|
+
* 触发条件是服务端下发的 `*Truncated`(= 本次拉满,可能还有更早记录),不是本地条数比较——
|
|
7110
|
+
* 本地比不出「还有没有」。
|
|
7111
|
+
*/
|
|
7112
|
+
/**
|
|
7113
|
+
* 列表「加载更多」页脚:与「未提交变更 / Git 提交记录」两个已有分页同视觉同交互。
|
|
7114
|
+
* 触发条件是服务端下发的 `*Truncated`(= 本次拉满,可能还有更早记录),不是本地条数比较——
|
|
7115
|
+
* 本地比不出「还有没有」。
|
|
7116
|
+
*
|
|
7117
|
+
* `loading` 为真时切换成「正在加载中」并禁用点击:请求往返可能几百毫秒到数秒,
|
|
7118
|
+
* 不反馈的话用户会以为点击没生效而连点,触发重复请求。
|
|
7119
|
+
*/
|
|
7120
|
+
var renderListMore = function (kind, hasMore, count, loading) {
|
|
7121
|
+
if (!hasMore || typeof onLoadMoreLists !== "function")
|
|
7122
|
+
return null;
|
|
7123
|
+
return h("div", {
|
|
7124
|
+
key: "load-more-" + kind,
|
|
7125
|
+
style: {
|
|
7126
|
+
fontSize: "11px",
|
|
7127
|
+
opacity: loading ? 1 : 0.8,
|
|
7128
|
+
padding: "8px 0",
|
|
7129
|
+
textAlign: "center",
|
|
7130
|
+
cursor: loading ? "default" : "pointer",
|
|
7131
|
+
},
|
|
7132
|
+
onClick: function () { if (!loading)
|
|
7133
|
+
onLoadMoreLists(kind); },
|
|
7134
|
+
title: loading ? "正在加载更多…" : "点击加载更多(滚动到底部也会自动加载)",
|
|
7135
|
+
"aria-busy": loading ? "true" : "false",
|
|
7136
|
+
}, loading
|
|
7137
|
+
? "⏳ 正在加载中…"
|
|
7138
|
+
: "⬇️ 下滑或点击加载更多(已显示 " + count + " 条)");
|
|
7139
|
+
};
|
|
7140
|
+
/**
|
|
7141
|
+
* 列表「到底了」终态提示。
|
|
7142
|
+
*
|
|
7143
|
+
* 只在「服务端说不能再加载了,但仓库里确实还有更早记录」时出现——
|
|
7144
|
+
* 触发条件是 `parseListLimit` 的 100 条硬上限被顶满(或搜索索引与列表口径不一致)。
|
|
7145
|
+
* 此时若什么都不说,用户看到「已加载 100 / 共 323」却没有入口,会以为界面坏了;
|
|
7146
|
+
* 说清楚「更早记录请到 GitHub 查看」才是诚实的收尾。
|
|
7147
|
+
*/
|
|
7148
|
+
var renderListEnd = function (kind, hasMore, count, total) {
|
|
7149
|
+
if (hasMore)
|
|
7150
|
+
return null;
|
|
7151
|
+
if (typeof total !== "number" || count >= total)
|
|
7152
|
+
return null;
|
|
7153
|
+
return h("div", {
|
|
7154
|
+
key: "list-end-" + kind,
|
|
7155
|
+
style: { fontSize: "11px", opacity: 0.6, padding: "8px 0", textAlign: "center" },
|
|
7156
|
+
title: "单次最多加载 100 条,更早的记录请在 GitHub 查看",
|
|
7157
|
+
}, "已加载 " + count + " / 共 " + total + " 条,更早记录请到 GitHub 查看");
|
|
7158
|
+
};
|
|
6152
7159
|
var effectiveUpdateInfo = updateInfo || data?.updateInfo;
|
|
6153
7160
|
var upgradeBannerEl = effectiveUpdateInfo && effectiveUpdateInfo.hasUpdate
|
|
6154
7161
|
? h(UpgradeNotificationCard, { updateInfo: effectiveUpdateInfo, isRightbar })
|
|
@@ -6591,27 +7598,25 @@ function GithubHelpModal(props) {
|
|
|
6591
7598
|
// 空态文案要给人看,不能把内部档位键(merged)直接抛给用户
|
|
6592
7599
|
var PR_STATE_LABEL = { all: "全部", open: "Open", merged: "Merged", closed: "Closed" };
|
|
6593
7600
|
var prFilterLabel = PR_STATE_LABEL[prStateFilter] || "全部";
|
|
6594
|
-
|
|
7601
|
+
/*
|
|
7602
|
+
* 计数口径:**优先用仓库真实总数**(服务端 GraphQL `search.issueCount` 下发),
|
|
7603
|
+
* 只有拿不到总数时才回落到「已加载条数」。
|
|
7604
|
+
*
|
|
7605
|
+
* 为什么不能直接用 prs.length:列表按页拉,prs.length 是「已加载」,
|
|
7606
|
+
* 用户每下滑一次它就从 15 → 30 → 45,看起来像仓库总数在变——这是 bug。
|
|
7607
|
+
*/
|
|
7608
|
+
var prTotalCount = typeof data?.prTotal === "number" ? data.prTotal : prs.length;
|
|
7609
|
+
// Actions 总数来自 REST actions/runs.total_count;拿不到才回落已加载条数
|
|
7610
|
+
var runTotalCount = typeof data?.runTotal === "number" ? data.runTotal : runs.length;
|
|
7611
|
+
var prOpenCount = typeof data?.prOpenTotal === "number" ? data.prOpenTotal : prByState.open;
|
|
7612
|
+
var prMergedCount = typeof data?.prMergedTotal === "number" ? data.prMergedTotal : prByState.merged;
|
|
7613
|
+
var prClosedCount = typeof data?.prClosedTotal === "number" ? data.prClosedTotal : prByState.closed;
|
|
7614
|
+
var prPillsEl = h("div", { className: "dsh-github-pills" }, h("button", { className: `dsh-github-pill-btn ${prStateFilter === "all" ? "active" : ""}`, "aria-pressed": prStateFilter === "all" ? "true" : "false", onClick: () => onChangePrFilter && onChangePrFilter("all") }, `全部 (${prTotalCount})`), h("button", { className: `dsh-github-pill-btn ${prStateFilter === "open" ? "active" : ""}`, "aria-pressed": prStateFilter === "open" ? "true" : "false", onClick: () => onChangePrFilter && onChangePrFilter("open") }, `🟢 Open (${prOpenCount})`), h("button", { className: `dsh-github-pill-btn ${prStateFilter === "merged" ? "active" : ""}`, "aria-pressed": prStateFilter === "merged" ? "true" : "false", onClick: () => onChangePrFilter && onChangePrFilter("merged") }, `🟣 Merged (${prMergedCount})`), h("button", { className: `dsh-github-pill-btn ${prStateFilter === "closed" ? "active" : ""}`, "aria-pressed": prStateFilter === "closed" ? "true" : "false", onClick: () => onChangePrFilter && onChangePrFilter("closed") }, `⚪ Closed (${prClosedCount})`));
|
|
6595
7615
|
// 空态必须区分「仓库确实没有」与「当前筛选下没有」——否则用户会把筛空当成数据丢失
|
|
6596
7616
|
var prEmptyEl = prs.length === 0
|
|
6597
7617
|
? h("div", { className: "dsh-github-empty" }, "该仓库暂无 Pull Request 记录")
|
|
6598
7618
|
: h("div", { className: "dsh-github-empty" }, `当前筛选「${prFilterLabel}」下无结果,共 ${prs.length} 条记录(切换到「全部」查看)`);
|
|
6599
|
-
var prSectionEl = h("div", { className: "dsh-github-section" }, h("div", { className: "dsh-github-section-header" }, h("h3", null, `🔀 Pull Requests
|
|
6600
|
-
// 触顶提示:让用户知道列表被截断了,而不是以为历史 PR 凭空消失
|
|
6601
|
-
data && data.prsTruncated
|
|
6602
|
-
? h("div", {
|
|
6603
|
-
style: {
|
|
6604
|
-
margin: "0 0 10px 0",
|
|
6605
|
-
padding: "6px 10px",
|
|
6606
|
-
borderRadius: "6px",
|
|
6607
|
-
fontSize: "11px",
|
|
6608
|
-
lineHeight: 1.5,
|
|
6609
|
-
background: "rgba(210, 153, 34, 0.12)",
|
|
6610
|
-
border: "1px solid rgba(210, 153, 34, 0.35)",
|
|
6611
|
-
color: "var(--dsw-alias-state-attention-primary, #9a6700)",
|
|
6612
|
-
},
|
|
6613
|
-
}, `⚠️ 仅显示最近 ${prs.length} 条 PR(已触及检索上限)。更早的记录请到 GitHub 查看。`)
|
|
6614
|
-
: null, filteredPrs.length === 0
|
|
7619
|
+
var prSectionEl = h("div", { className: "dsh-github-section" }, h("div", { className: "dsh-github-section-header" }, h("h3", null, `🔀 Pull Requests(${prs.length} / 共 ${prTotalCount})`)), prPillsEl, filteredPrs.length === 0
|
|
6615
7620
|
? prEmptyEl
|
|
6616
7621
|
: h("div", { className: "dsh-github-list" }, filteredPrs.map((pr) => {
|
|
6617
7622
|
var ci = rollupCiStatus(pr.statusCheckRollup);
|
|
@@ -6644,25 +7649,28 @@ function GithubHelpModal(props) {
|
|
|
6644
7649
|
onClick: (e) => e.stopPropagation(),
|
|
6645
7650
|
title: "在浏览器打开该 PR(不进入在线审阅)",
|
|
6646
7651
|
}, "GitHub ↗")), h("div", { className: "dsh-github-item-meta" }, h("span", null, `${pr.headRefName} → ${pr.baseRefName}`),
|
|
6647
|
-
// 状态徽章:与主面板保持一致,否则同一 PR
|
|
7652
|
+
// 状态徽章:与主面板保持一致,否则同一 PR 在两处显示不同信息。
|
|
7653
|
+
// 状态→色调的映射来自 src/shared/state-badges.ts(服务端 CLI 输出用同一份),
|
|
7654
|
+
// tone 直接就是 CSS 后缀,这里不再自己判 state。
|
|
6648
7655
|
(() => {
|
|
6649
|
-
var
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
if (s === "OPEN")
|
|
6655
|
-
return h("span", { className: "dsh-github-badge dsh-badge-green" }, "OPEN");
|
|
6656
|
-
return null;
|
|
7656
|
+
var b = prStateBadge(pr.state);
|
|
7657
|
+
// neutral = 未知状态:旧实现对这些值不渲染徽章,保持一致
|
|
7658
|
+
if (!b || b.tone === "neutral")
|
|
7659
|
+
return null;
|
|
7660
|
+
return h("span", { className: "dsh-github-badge dsh-badge-" + b.tone }, b.label);
|
|
6657
7661
|
})(), pr.mergeable === "CONFLICTING"
|
|
6658
7662
|
? h("span", { className: "dsh-github-badge dsh-badge-red" }, "代码冲突")
|
|
6659
|
-
: h("span", { className: "dsh-github-badge dsh-badge-green" }, "可合并"),
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
|
|
7663
|
+
: h("span", { className: "dsh-github-badge dsh-badge-green" }, "可合并"), (() => {
|
|
7664
|
+
// 命中判定保持旧白名单(只有 fail/pending/pass 渲染),
|
|
7665
|
+
// 色调与文案则复用共享映射,避免同一状态两处颜色不一致
|
|
7666
|
+
if (ci !== "fail" && ci !== "pending" && ci !== "pass")
|
|
7667
|
+
return null;
|
|
7668
|
+
var cb = ciBadge(undefined, ci);
|
|
7669
|
+
if (!cb)
|
|
7670
|
+
return null;
|
|
7671
|
+
var text = ci === "fail" ? "❌ CI 失败" : ci === "pending" ? "⏳ CI 运行中" : "✅ CI 通过";
|
|
7672
|
+
return h("span", { className: "dsh-github-badge dsh-badge-" + cb.tone }, text);
|
|
7673
|
+
})(), rv
|
|
6666
7674
|
? h("span", { className: `dsh-github-badge dsh-badge-${rv.kind}` }, rv.text)
|
|
6667
7675
|
: null, relTime ? h("span", null, relTime) : null));
|
|
6668
7676
|
})));
|
|
@@ -6680,30 +7688,21 @@ function GithubHelpModal(props) {
|
|
|
6680
7688
|
});
|
|
6681
7689
|
var ISSUE_STATE_LABEL = { all: "全部", open: "Open", closed: "Closed" };
|
|
6682
7690
|
var issueFilterLabel = ISSUE_STATE_LABEL[issueStateFilter] || "全部";
|
|
6683
|
-
|
|
7691
|
+
// Issue 计数同 PR 口径:优先仓库真实总数,拿不到才回落已加载条数
|
|
7692
|
+
var issueTotalCount = typeof data?.issueTotal === "number" ? data.issueTotal : issues.length;
|
|
7693
|
+
var issueOpenCount = typeof data?.issueOpenTotal === "number" ? data.issueOpenTotal : issueByState.open;
|
|
7694
|
+
var issueClosedCount = typeof data?.issueClosedTotal === "number" ? data.issueClosedTotal : issueByState.closed;
|
|
7695
|
+
var issuePillsEl = h("div", { className: "dsh-github-pills" }, h("button", { className: `dsh-github-pill-btn ${issueStateFilter === "all" ? "active" : ""}`, "aria-pressed": issueStateFilter === "all" ? "true" : "false", onClick: () => onChangeIssueFilter && onChangeIssueFilter("all") }, `全部 (${issueTotalCount})`), h("button", { className: `dsh-github-pill-btn ${issueStateFilter === "open" ? "active" : ""}`, "aria-pressed": issueStateFilter === "open" ? "true" : "false", onClick: () => onChangeIssueFilter && onChangeIssueFilter("open") }, `🟢 Open (${issueOpenCount})`), h("button", { className: `dsh-github-pill-btn ${issueStateFilter === "closed" ? "active" : ""}`, "aria-pressed": issueStateFilter === "closed" ? "true" : "false", onClick: () => onChangeIssueFilter && onChangeIssueFilter("closed") }, `⚪ Closed (${issueClosedCount})`));
|
|
6684
7696
|
var issueEmptyEl = issues.length === 0
|
|
6685
7697
|
? h("div", { className: "dsh-github-empty" }, "该仓库暂无 Issue 记录")
|
|
6686
7698
|
: h("div", { className: "dsh-github-empty" }, `当前筛选「${issueFilterLabel}」下无结果,共 ${issues.length} 条记录(切换到「全部」查看)`);
|
|
6687
|
-
var issueSectionEl = h("div", { className: "dsh-github-section" }, h("div", { className: "dsh-github-section-header" }, h("h3", null, `📋 仓库 Issues
|
|
7699
|
+
var issueSectionEl = h("div", { className: "dsh-github-section" }, h("div", { className: "dsh-github-section-header" }, h("h3", null, `📋 仓库 Issues(${issues.length} / 共 ${issueTotalCount})`), repo?.url
|
|
6688
7700
|
? h("a", {
|
|
6689
7701
|
href: `${repo.url}/issues`,
|
|
6690
7702
|
target: "_blank",
|
|
6691
7703
|
style: { fontSize: "11px", color: "var(--dsw-alias-link, #0969da)", textDecoration: "none" },
|
|
6692
7704
|
}, "全部 ↗")
|
|
6693
|
-
: null), issuePillsEl,
|
|
6694
|
-
? h("div", {
|
|
6695
|
-
style: {
|
|
6696
|
-
margin: "0 0 10px 0",
|
|
6697
|
-
padding: "6px 10px",
|
|
6698
|
-
borderRadius: "6px",
|
|
6699
|
-
fontSize: "11px",
|
|
6700
|
-
lineHeight: 1.5,
|
|
6701
|
-
background: "rgba(210, 153, 34, 0.12)",
|
|
6702
|
-
border: "1px solid rgba(210, 153, 34, 0.35)",
|
|
6703
|
-
color: "var(--dsw-alias-state-attention-primary, #9a6700)",
|
|
6704
|
-
},
|
|
6705
|
-
}, `⚠️ 仅显示最近 ${issues.length} 条 Issue(已触及检索上限)。更早的记录请到 GitHub 查看。`)
|
|
6706
|
-
: null, filteredIssues.length === 0
|
|
7705
|
+
: null), issuePillsEl, filteredIssues.length === 0
|
|
6707
7706
|
? issueEmptyEl
|
|
6708
7707
|
: h("div", { className: "dsh-github-list" }, filteredIssues.map((issue) => {
|
|
6709
7708
|
var cats = issue.categories || [];
|
|
@@ -6753,14 +7752,34 @@ function GithubHelpModal(props) {
|
|
|
6753
7752
|
? issue.labels.map((lbl) => h("span", { key: lbl.name, className: "dsh-github-badge dsh-badge-yellow" }, lbl.name))
|
|
6754
7753
|
: null));
|
|
6755
7754
|
})));
|
|
6756
|
-
var runSectionEl = h("div", { className: "dsh-github-section" }, h("div", { className: "dsh-github-section-header" }, h("h3", null, `⚡ 最近 Actions
|
|
7755
|
+
var runSectionEl = h("div", { className: "dsh-github-section" }, h("div", { className: "dsh-github-section-header" }, h("h3", null, `⚡ 最近 Actions 流水线(${runs.length} / 共 ${runTotalCount})`)), runs.length === 0
|
|
6757
7756
|
? h("div", { className: "dsh-github-empty" }, "暂无工作流记录")
|
|
6758
|
-
: h("div", { className: "dsh-github-list" }, runs.map((r) => h("div", {
|
|
7757
|
+
: h("div", { className: "dsh-github-list" }, runs.map((r) => h("div", {
|
|
7758
|
+
key: r.databaseId,
|
|
7759
|
+
className: "dsh-github-item",
|
|
7760
|
+
// 只加 cursor:内联 display:flex 会顶掉 .dsh-github-item 的 column 布局
|
|
7761
|
+
style: onSelectRun ? { cursor: "pointer" } : undefined,
|
|
7762
|
+
title: onSelectRun ? "点击在线查看该运行(job / step / 失败日志)" : undefined,
|
|
7763
|
+
onClick: onSelectRun
|
|
7764
|
+
? function () {
|
|
7765
|
+
onSelectRun({ runId: Number(r.databaseId), repo: repo?.nameWithOwner || "" });
|
|
7766
|
+
}
|
|
7767
|
+
: undefined,
|
|
7768
|
+
},
|
|
7769
|
+
// 标题与外跳必须同行拆开:Actions 卡片过去整行就是一个 <a>,
|
|
7770
|
+
// 点击即跳浏览器;现在卡片本身要进弹窗,外跳必须独立成按钮并
|
|
7771
|
+
// stopPropagation,否则点外跳会连带把弹窗也打开。
|
|
7772
|
+
h("div", { className: "dsh-github-pr-title-row" }, h("span", {
|
|
6759
7773
|
className: "dsh-github-item-title",
|
|
7774
|
+
title: r.createdAt ? `始于 ${r.createdAt}(点击在线查看)` : "点击在线查看该运行",
|
|
7775
|
+
}, r.name), h("a", {
|
|
7776
|
+
className: "dsh-github-pr-gh-btn",
|
|
6760
7777
|
href: r.url,
|
|
6761
7778
|
target: "_blank",
|
|
6762
|
-
|
|
6763
|
-
|
|
7779
|
+
rel: "noopener noreferrer",
|
|
7780
|
+
onClick: (e) => e.stopPropagation(),
|
|
7781
|
+
title: "在浏览器打开该运行(不进入在线详情)",
|
|
7782
|
+
}, "GitHub ↗")), h("div", { className: "dsh-github-item-meta" }, h("span", null, `分支: ${r.headBranch}`), r.event ? h("span", null, eventLabel(r.event)) : null, formatRelativeTime(r.createdAt) ? h("span", null, formatRelativeTime(r.createdAt)) : null, r.conclusion === "success"
|
|
6764
7783
|
? h("span", { className: "dsh-github-badge dsh-badge-green" }, "✓ 成功")
|
|
6765
7784
|
: r.conclusion === "failure"
|
|
6766
7785
|
? h("span", { className: "dsh-github-badge dsh-badge-red" }, "✕ 失败")
|
|
@@ -6768,7 +7787,7 @@ function GithubHelpModal(props) {
|
|
|
6768
7787
|
? h("div", {
|
|
6769
7788
|
className: "dsh-github-card-subtext",
|
|
6770
7789
|
style: { fontSize: "11px", marginTop: "2px" },
|
|
6771
|
-
}, `💡
|
|
7790
|
+
}, `💡 点击卡片查看失败步骤与日志;或让 Agent 调用 github_task(action:'diagnose_ci', run_id:'${r.databaseId}')`)
|
|
6772
7791
|
: null))));
|
|
6773
7792
|
var authGuideEl = (!auth?.loggedIn && !auth?.networkError && syncStatus !== "failed")
|
|
6774
7793
|
? h(AuthGuideCard, { auth, onRefresh, isRightbar })
|
|
@@ -6776,9 +7795,11 @@ function GithubHelpModal(props) {
|
|
|
6776
7795
|
var TAB_DEFS = [
|
|
6777
7796
|
{ key: "git", label: "Git", count: null, icon: renderGitIcon(13, "#1296db") },
|
|
6778
7797
|
{ key: "dirty", label: "📁 未提交", count: gitSt ? gitSt.dirtyCount : null },
|
|
6779
|
-
|
|
6780
|
-
{ key: "
|
|
6781
|
-
{ key: "
|
|
7798
|
+
// Tab 上的数字是**仓库总数**(不是已加载条数):下滑加载更多时它必须纹丝不动
|
|
7799
|
+
{ key: "prs", label: "🔀 PR", count: prTotalCount },
|
|
7800
|
+
{ key: "issues", label: "📋 Issues", count: issueTotalCount },
|
|
7801
|
+
// Actions 没有可用的总数接口(gh/GraphQL 都不提供),保持已加载条数
|
|
7802
|
+
{ key: "runs", label: "⚡ Actions", count: runTotalCount },
|
|
6782
7803
|
].filter(function (t) {
|
|
6783
7804
|
if ((t.key === "git" || t.key === "dirty") && !gitSt)
|
|
6784
7805
|
return false;
|
|
@@ -6807,7 +7828,7 @@ function GithubHelpModal(props) {
|
|
|
6807
7828
|
},
|
|
6808
7829
|
}, t.icon ? [t.icon, " ", labelText] : labelText);
|
|
6809
7830
|
}));
|
|
6810
|
-
var tabbedContentEl = h("div", { "data-active-tab": defaultTab }, tabsBarEl, h("div", { "data-tab-page": "prs", hidden: defaultTab !== "prs" }, prSectionEl), h("div", { "data-tab-page": "issues", hidden: defaultTab !== "issues" }, issueSectionEl), h("div", { "data-tab-page": "runs", hidden: defaultTab !== "runs" }, runSectionEl), gitSt ? h("div", { "data-tab-page": "git", hidden: defaultTab !== "git" }, gitSectionEl) : null, gitSt ? h("div", { "data-tab-page": "dirty", hidden: defaultTab !== "dirty" }, dirtySectionEl) : null);
|
|
7831
|
+
var tabbedContentEl = h("div", { "data-active-tab": defaultTab }, tabsBarEl, h("div", { "data-tab-page": "prs", hidden: defaultTab !== "prs" }, prSectionEl, renderListMore("prs", data && data.prsTruncated, prs.length, Boolean(listLoading && listLoading.prs)), renderListEnd("prs", data && data.prsTruncated, prs.length, data && data.prTotal)), h("div", { "data-tab-page": "issues", hidden: defaultTab !== "issues" }, issueSectionEl, renderListMore("issues", data && data.issuesTruncated, issues.length, Boolean(listLoading && listLoading.issues)), renderListEnd("issues", data && data.issuesTruncated, issues.length, data && data.issueTotal)), h("div", { "data-tab-page": "runs", hidden: defaultTab !== "runs" }, runSectionEl, renderListMore("runs", data && data.runsTruncated, runs.length, Boolean(listLoading && listLoading.runs)), renderListEnd("runs", data && data.runsTruncated, runs.length, data && data.runTotal)), gitSt ? h("div", { "data-tab-page": "git", hidden: defaultTab !== "git" }, gitSectionEl) : null, gitSt ? h("div", { "data-tab-page": "dirty", hidden: defaultTab !== "dirty" }, dirtySectionEl) : null);
|
|
6811
7832
|
return [
|
|
6812
7833
|
headerEl,
|
|
6813
7834
|
networkAlertBannerEl,
|
|
@@ -6841,6 +7862,15 @@ function GithubHelpModal(props) {
|
|
|
6841
7862
|
var [toastMsg, setToastMsg] = React.useState(null);
|
|
6842
7863
|
var cloudTimerRef = React.useRef(null);
|
|
6843
7864
|
var dataRef = React.useRef(data);
|
|
7865
|
+
// 列表分页游标按 Tab 独立维护:防止加载更多 PR 顺带把 Issue 和 Actions 也拉满膨胀
|
|
7866
|
+
var tabLimitsRef = React.useRef({
|
|
7867
|
+
prs: OVERVIEW_PAGE_SIZE,
|
|
7868
|
+
issues: OVERVIEW_PAGE_SIZE,
|
|
7869
|
+
runs: OVERVIEW_PAGE_SIZE,
|
|
7870
|
+
});
|
|
7871
|
+
var loadingMoreListsRef = React.useRef(false);
|
|
7872
|
+
// 各 Tab 的「正在加载更多」UI 标记(ref 只防重入,界面反馈必须走 state 才能重渲染)
|
|
7873
|
+
var [listLoading, setListLoading] = React.useState({ prs: false, issues: false, runs: false });
|
|
6844
7874
|
dataRef.current = data;
|
|
6845
7875
|
var lastCwdRef = React.useRef(cwd);
|
|
6846
7876
|
var containerRef = React.useRef(null);
|
|
@@ -6852,6 +7882,11 @@ function GithubHelpModal(props) {
|
|
|
6852
7882
|
if (lastCwdRef.current === cwd)
|
|
6853
7883
|
return;
|
|
6854
7884
|
lastCwdRef.current = cwd;
|
|
7885
|
+
tabLimitsRef.current = {
|
|
7886
|
+
prs: OVERVIEW_PAGE_SIZE,
|
|
7887
|
+
issues: OVERVIEW_PAGE_SIZE,
|
|
7888
|
+
runs: OVERVIEW_PAGE_SIZE,
|
|
7889
|
+
};
|
|
6855
7890
|
var snap = getStoredRightbarSnapshot(cwd);
|
|
6856
7891
|
setData(snap?.data || null);
|
|
6857
7892
|
setSyncStatus(snap ? "cached" : "loading");
|
|
@@ -6916,6 +7951,10 @@ function GithubHelpModal(props) {
|
|
|
6916
7951
|
if (cwd) {
|
|
6917
7952
|
params.push("cwd=" + encodeURIComponent(cwd));
|
|
6918
7953
|
}
|
|
7954
|
+
// 分页:PR / Issue / Actions 各自独立游标,避免相互膨胀
|
|
7955
|
+
params.push("prLimit=" + tabLimitsRef.current.prs);
|
|
7956
|
+
params.push("issueLimit=" + tabLimitsRef.current.issues);
|
|
7957
|
+
params.push("runLimit=" + tabLimitsRef.current.runs);
|
|
6919
7958
|
if (params.length > 0) {
|
|
6920
7959
|
url += "?" + params.join("&");
|
|
6921
7960
|
}
|
|
@@ -7191,6 +8230,8 @@ function GithubHelpModal(props) {
|
|
|
7191
8230
|
var [activePr, setActivePr] = React.useState(null);
|
|
7192
8231
|
// Issue 在线查看弹窗状态:与 PR 同构
|
|
7193
8232
|
var [activeIssue, setActiveIssue] = React.useState(null);
|
|
8233
|
+
// Run 在线详情弹窗状态:{ runId, repo };为 null 时不挂载弹窗(零常驻开销)
|
|
8234
|
+
var [activeRun, setActiveRun] = React.useState(null);
|
|
7194
8235
|
var [extraCommits, setExtraCommits] = React.useState([]);
|
|
7195
8236
|
var [loadingMore, setLoadingMore] = React.useState(false);
|
|
7196
8237
|
var [hasMoreCommits, setHasMoreCommits] = React.useState(true);
|
|
@@ -7246,6 +8287,35 @@ function GithubHelpModal(props) {
|
|
|
7246
8287
|
setPrStateFilter(stored.pr);
|
|
7247
8288
|
setIssueStateFilter(stored.issue);
|
|
7248
8289
|
}, [cwd]);
|
|
8290
|
+
/**
|
|
8291
|
+
* PR / Issue / Actions 列表的「加载更多」(独立游标维护)。
|
|
8292
|
+
*
|
|
8293
|
+
* 针对对应 Tab 单独累加步长,未处于当前焦点的 Tab 不增加 limit,
|
|
8294
|
+
* 从源头杜绝并发膨胀触发底层 125KB 截断。
|
|
8295
|
+
*/
|
|
8296
|
+
var loadMoreTabList = function (kind) {
|
|
8297
|
+
if (loadingMoreListsRef.current)
|
|
8298
|
+
return;
|
|
8299
|
+
var validKey = kind === "prs" ? "prs" : kind === "issues" ? "issues" : kind === "runs" ? "runs" : null;
|
|
8300
|
+
if (!validKey)
|
|
8301
|
+
return;
|
|
8302
|
+
tabLimitsRef.current[validKey] = tabLimitsRef.current[validKey] + OVERVIEW_PAGE_SIZE;
|
|
8303
|
+
loadingMoreListsRef.current = true;
|
|
8304
|
+
// 立刻把该 Tab 的页脚切成「正在加载中」,请求结束后再切回来(成功失败都要复位)
|
|
8305
|
+
setListLoading(function (prev) {
|
|
8306
|
+
var next = Object.assign({}, prev);
|
|
8307
|
+
next[validKey] = true;
|
|
8308
|
+
return next;
|
|
8309
|
+
});
|
|
8310
|
+
loadData(true).finally(function () {
|
|
8311
|
+
loadingMoreListsRef.current = false;
|
|
8312
|
+
setListLoading(function (prev) {
|
|
8313
|
+
var next = Object.assign({}, prev);
|
|
8314
|
+
next[validKey] = false;
|
|
8315
|
+
return next;
|
|
8316
|
+
});
|
|
8317
|
+
});
|
|
8318
|
+
};
|
|
7249
8319
|
var loadMoreCommits = function () {
|
|
7250
8320
|
if (loadingMore || !hasMoreCommits)
|
|
7251
8321
|
return;
|
|
@@ -7367,6 +8437,15 @@ function GithubHelpModal(props) {
|
|
|
7367
8437
|
else if (activeTab === "dirty") {
|
|
7368
8438
|
loadMoreDirtyFiles();
|
|
7369
8439
|
}
|
|
8440
|
+
else if (activeTab === "prs" && data?.prsTruncated) {
|
|
8441
|
+
loadMoreTabList("prs");
|
|
8442
|
+
}
|
|
8443
|
+
else if (activeTab === "issues" && data?.issuesTruncated) {
|
|
8444
|
+
loadMoreTabList("issues");
|
|
8445
|
+
}
|
|
8446
|
+
else if (activeTab === "runs" && data?.runsTruncated) {
|
|
8447
|
+
loadMoreTabList("runs");
|
|
8448
|
+
}
|
|
7370
8449
|
}
|
|
7371
8450
|
};
|
|
7372
8451
|
// 监听鼠标滚轮向下滚事件(即使用户收起卡片导致内容暂未撑出滚动条,只要向下滚轮也能瞬间响应!)
|
|
@@ -7384,6 +8463,15 @@ function GithubHelpModal(props) {
|
|
|
7384
8463
|
else if (activeTab === "dirty") {
|
|
7385
8464
|
loadMoreDirtyFiles();
|
|
7386
8465
|
}
|
|
8466
|
+
else if (activeTab === "prs" && data?.prsTruncated) {
|
|
8467
|
+
loadMoreTabList("prs");
|
|
8468
|
+
}
|
|
8469
|
+
else if (activeTab === "issues" && data?.issuesTruncated) {
|
|
8470
|
+
loadMoreTabList("issues");
|
|
8471
|
+
}
|
|
8472
|
+
else if (activeTab === "runs" && data?.runsTruncated) {
|
|
8473
|
+
loadMoreTabList("runs");
|
|
8474
|
+
}
|
|
7387
8475
|
}
|
|
7388
8476
|
};
|
|
7389
8477
|
el.addEventListener("scroll", onScroll, { passive: true });
|
|
@@ -7493,7 +8581,9 @@ function GithubHelpModal(props) {
|
|
|
7493
8581
|
loadingMore: loadingMoreDirty,
|
|
7494
8582
|
hasMore: hasMoreDirty,
|
|
7495
8583
|
onLoadMore: loadMoreDirtyFiles,
|
|
7496
|
-
}, activeDiffFile, (f) => setActiveDiffFile(f), () => setActiveDiffFile(null), openFileInDsh, headerCardsVisible, toggleHeaderCards, (target) => setActivePr(target), (target) => setActiveIssue(target), prStateFilter, issueStateFilter, changePrFilter, changeIssueFilter
|
|
8584
|
+
}, activeDiffFile, (f) => setActiveDiffFile(f), () => setActiveDiffFile(null), openFileInDsh, headerCardsVisible, toggleHeaderCards, (target) => setActivePr(target), (target) => setActiveIssue(target), prStateFilter, issueStateFilter, changePrFilter, changeIssueFilter,
|
|
8585
|
+
// 必须追加在形参末尾(该函数 30+ 个位置参数,插中间会静默错位)
|
|
8586
|
+
loadMoreTabList, listLoading, (target) => setActiveRun(target)), h(GlobalSyncSettingsModal, {
|
|
7497
8587
|
isOpen: aboutModalOpen,
|
|
7498
8588
|
initialSection: "about",
|
|
7499
8589
|
onClose: () => setAboutModalOpen(false),
|
|
@@ -7536,6 +8626,14 @@ function GithubHelpModal(props) {
|
|
|
7536
8626
|
onClose: function () { setActiveIssue(null); },
|
|
7537
8627
|
showToast: showToast,
|
|
7538
8628
|
})
|
|
8629
|
+
: null, activeRun
|
|
8630
|
+
? h(RunDetailModal, {
|
|
8631
|
+
runId: activeRun.runId,
|
|
8632
|
+
repoNameWithOwner: activeRun.repo,
|
|
8633
|
+
cwd: cwd,
|
|
8634
|
+
onClose: function () { setActiveRun(null); },
|
|
8635
|
+
showToast: showToast,
|
|
8636
|
+
})
|
|
7539
8637
|
: null);
|
|
7540
8638
|
}
|
|
7541
8639
|
// 2. 右侧栏 Tab 标题组件
|
|
@@ -7547,15 +8645,19 @@ function GithubHelpModal(props) {
|
|
|
7547
8645
|
function GithubArtwork() {
|
|
7548
8646
|
return renderOctocatBadge(32);
|
|
7549
8647
|
}
|
|
7550
|
-
// 4.
|
|
8648
|
+
// 4. 左侧栏全局入口
|
|
8649
|
+
//
|
|
8650
|
+
// 宿主的 sidebar.panellist 行渲染器会向本组件注入 { size, active }:
|
|
8651
|
+
// renderSlot("sidebar.panellist", { size: wide ? 16 : 18, active }, { only: id })
|
|
8652
|
+
// 且图标格 .panelGlyph 是 flex:none、不设宽高——宽度完全由内容决定。
|
|
8653
|
+
// 因此这里必须:① 用宿主给的 size;② 不再套任何固定宽高的盒子。
|
|
8654
|
+
// 早期版本套了 28px 内层盒子 + 深色渐变发光,结果是图标格被撑到 28px
|
|
8655
|
+
// (同级 16px)、标签右移 12px、选中态多出一块发光底(issue #9)。
|
|
7551
8656
|
function GithubSidebarIcon(props) {
|
|
7552
|
-
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
}, React.createElement("div", {
|
|
7557
|
-
className: "dsh-github-icon-inner" + (active ? " active" : "")
|
|
7558
|
-
}, renderOctocat(18, "currentColor")));
|
|
8657
|
+
var size = props && typeof props.size === "number" ? props.size : 16;
|
|
8658
|
+
// 选中态由宿主 .panelRow.panelActive 负责(行背景 + label-primary),这里不重复绘制;
|
|
8659
|
+
// 也不设 title:宿主已提供 aria-label 与 Tooltip,叠加会出现双提示。
|
|
8660
|
+
return renderOctocat(size, "currentColor");
|
|
7559
8661
|
}
|
|
7560
8662
|
var inject = ["slots", "sidebarRightTabs", "sessions", "layout", "theme", "sidebarRight"];
|
|
7561
8663
|
|
|
@@ -7590,6 +8692,12 @@ function GithubWorkspacePanel() {
|
|
|
7590
8692
|
var [activeIssueModal, setActiveIssueModal] = React.useState(null);
|
|
7591
8693
|
var cloudTimerRef = React.useRef(null);
|
|
7592
8694
|
var dataRef = React.useRef(data);
|
|
8695
|
+
// 全局面板分页游标:PR 与 Issues 独立维护,防止点击加载更多相互膨胀
|
|
8696
|
+
var prLimitRef = React.useRef(15);
|
|
8697
|
+
var issueLimitRef = React.useRef(15);
|
|
8698
|
+
// 「正在加载更多」UI 标记:请求往返期间页脚必须给反馈,否则用户会连点触发重复请求
|
|
8699
|
+
var [loadingPrs, setLoadingPrs] = React.useState(false);
|
|
8700
|
+
var [loadingIssues, setLoadingIssues] = React.useState(false);
|
|
7593
8701
|
dataRef.current = data;
|
|
7594
8702
|
var showToast = (msg) => {
|
|
7595
8703
|
setToastMsg(msg);
|
|
@@ -7672,8 +8780,14 @@ function GithubWorkspacePanel() {
|
|
|
7672
8780
|
if (forceRefresh)
|
|
7673
8781
|
await fetch("/api/github/refresh", { method: "POST", signal: controller.signal }).catch(() => { });
|
|
7674
8782
|
var url = "/api/github/global-overview";
|
|
8783
|
+
var params = [];
|
|
7675
8784
|
if (forceRefresh) {
|
|
7676
|
-
|
|
8785
|
+
params.push("force=true");
|
|
8786
|
+
}
|
|
8787
|
+
params.push("prLimit=" + prLimitRef.current);
|
|
8788
|
+
params.push("issueLimit=" + issueLimitRef.current);
|
|
8789
|
+
if (params.length > 0) {
|
|
8790
|
+
url += "?" + params.join("&");
|
|
7677
8791
|
}
|
|
7678
8792
|
// 接入 1s/2s/4s 阶梯退避重试,平滑吸收代理节点与网络的偶发抖动断流
|
|
7679
8793
|
var { res, json } = await fetchWithRetry(url, {}, {
|
|
@@ -7909,6 +9023,21 @@ function GithubWorkspacePanel() {
|
|
|
7909
9023
|
var workspaceMatrix = (data && data.workspaceMatrix) || [];
|
|
7910
9024
|
var myPrs = (data && data.myPrs) || [];
|
|
7911
9025
|
var myIssues = (data && data.myIssues) || [];
|
|
9026
|
+
// 主面板列表分页:PR 与 Issue 各自独立游标,每页步长 15,避免点击加载更多相互膨胀
|
|
9027
|
+
var loadMorePrs = function () {
|
|
9028
|
+
if (loadingPrs)
|
|
9029
|
+
return;
|
|
9030
|
+
prLimitRef.current = prLimitRef.current + 15;
|
|
9031
|
+
setLoadingPrs(true);
|
|
9032
|
+
loadData(true).finally(function () { setLoadingPrs(false); });
|
|
9033
|
+
};
|
|
9034
|
+
var loadMoreIssues = function () {
|
|
9035
|
+
if (loadingIssues)
|
|
9036
|
+
return;
|
|
9037
|
+
issueLimitRef.current = issueLimitRef.current + 15;
|
|
9038
|
+
setLoadingIssues(true);
|
|
9039
|
+
loadData(true).finally(function () { setLoadingIssues(false); });
|
|
9040
|
+
};
|
|
7912
9041
|
var errorEl = error ? h("div", { style: { color: "#f85149", background: "rgba(248,81,73,0.1)", border: "1px solid rgba(248,81,73,0.3)", padding: "8px 12px", borderRadius: "6px", marginBottom: "16px", fontSize: "12px" } }, `⚠️ 加载失败: ${error}`) : null;
|
|
7913
9042
|
var statsCardsEl = h("div", { className: "dsh-github-overview-cards" }, h("div", { className: "dsh-github-card" }, h("div", { className: "dsh-github-card-title" }, "CLI 认证账号"), h("div", { className: "dsh-github-card-value" }, auth?.networkError
|
|
7914
9043
|
? (auth.user ? `👤 ${auth.user} (离线)` : "🌐 网络连接受限")
|
|
@@ -7984,27 +9113,38 @@ function GithubWorkspacePanel() {
|
|
|
7984
9113
|
return true;
|
|
7985
9114
|
return String(pr.state || '').toLowerCase() === prStateFilter;
|
|
7986
9115
|
});
|
|
7987
|
-
var prPillsEl = h("div", { className: "dsh-github-pills" },
|
|
9116
|
+
var prPillsEl = h("div", { className: "dsh-github-pills" },
|
|
9117
|
+
// 计数优先用账号级真实总数(服务端 GraphQL search.issueCount),拿不到才回落已加载条数
|
|
9118
|
+
h("button", { className: `dsh-github-pill-btn ${prStateFilter === 'all' ? 'active' : ''}`, onClick: () => setPrStateFilter('all') }, `全部 (${typeof data?.prTotal === 'number' ? data.prTotal : myPrs.length})`), h("button", { className: `dsh-github-pill-btn ${prStateFilter === 'open' ? 'active' : ''}`, onClick: () => setPrStateFilter('open') }, `🟢 Open (${typeof data?.prOpenTotal === 'number' ? data.prOpenTotal : prByState.open})`), h("button", { className: `dsh-github-pill-btn ${prStateFilter === 'merged' ? 'active' : ''}`, onClick: () => setPrStateFilter('merged') }, `🟣 Merged (${typeof data?.prMergedTotal === 'number' ? data.prMergedTotal : prByState.merged})`), h("button", { className: `dsh-github-pill-btn ${prStateFilter === 'closed' ? 'active' : ''}`, onClick: () => setPrStateFilter('closed') }, `⚪ Closed (${typeof data?.prClosedTotal === 'number' ? data.prClosedTotal : prByState.closed})`));
|
|
7988
9119
|
// 空态必须区分「账号确实没有」与「当前筛选下没有」——否则用户会把筛空当成数据丢失
|
|
7989
9120
|
var prEmptyEl = myPrs.length === 0
|
|
7990
9121
|
? h("div", { className: "dsh-github-empty" }, "该账号下暂无任何 Pull Request 记录")
|
|
7991
9122
|
: h("div", { className: "dsh-github-empty" }, `当前筛选「${prStateFilter}」下无结果,共 ${myPrs.length} 条记录(切换到「全部」查看)`);
|
|
7992
|
-
contentEl = h("div", null, h("h3", { className: "dsh-github-subheading" }, `🔀 我发起与参与的 Pull Requests
|
|
7993
|
-
//
|
|
9123
|
+
contentEl = h("div", null, h("h3", { className: "dsh-github-subheading" }, `🔀 我发起与参与的 Pull Requests(${filteredPrs.length} 条符合筛选 / 共 ${typeof data?.prTotal === 'number' ? data.prTotal : myPrs.length})`), prPillsEl,
|
|
9124
|
+
// 「加载更多」页脚:服务端说还有更多(本次拉满)时渲染,点击即 +15 重取。
|
|
9125
|
+
// 旧文案是「已触及检索上限,请到 GitHub 查看」——那是在没有分页能力时的兜底,
|
|
9126
|
+
// 现在数据可以继续拉,改成可点击的入口。
|
|
7994
9127
|
data && data.prsTruncated
|
|
7995
9128
|
? h("div", {
|
|
9129
|
+
onClick: loadingPrs ? undefined : loadMorePrs,
|
|
9130
|
+
title: loadingPrs ? "正在加载更多 PR…" : "点击加载更多(滚动到底部也会自动加载)", "aria-busy": loadingPrs ? "true" : "false",
|
|
7996
9131
|
style: {
|
|
7997
9132
|
margin: "8px 0",
|
|
7998
|
-
padding: "
|
|
9133
|
+
padding: "8px 12px",
|
|
7999
9134
|
borderRadius: "6px",
|
|
8000
9135
|
fontSize: "11px",
|
|
8001
9136
|
lineHeight: 1.5,
|
|
9137
|
+
textAlign: "center",
|
|
9138
|
+
cursor: loadingPrs ? "default" : "pointer",
|
|
8002
9139
|
background: "rgba(210, 153, 34, 0.12)",
|
|
8003
9140
|
border: "1px solid rgba(210, 153, 34, 0.35)",
|
|
8004
9141
|
color: "var(--dsw-alias-state-attention-primary, #9a6700)",
|
|
8005
9142
|
},
|
|
8006
|
-
},
|
|
8007
|
-
:
|
|
9143
|
+
}, loadingPrs ? "⏳ 正在加载中…" : `⬇️ 下滑或点击加载更多 PR(已显示 ${myPrs.length} 条)`)
|
|
9144
|
+
: (typeof data?.prTotal === 'number' && myPrs.length < data.prTotal
|
|
9145
|
+
// 顶到单次 100 条上限后不再有加载入口,必须说明「更早记录在哪」,否则用户以为界面坏了
|
|
9146
|
+
? h("div", { style: { margin: "8px 0", fontSize: "11px", opacity: 0.6, textAlign: "center" } }, `已加载 ${myPrs.length} / 共 ${data.prTotal} 条,更早记录请到 GitHub 查看`)
|
|
9147
|
+
: null), filteredPrs.length === 0 ? prEmptyEl : h("div", { className: "dsh-github-list" }, filteredPrs.map((pr) => {
|
|
8008
9148
|
// 点击卡片主体 → 在线审阅弹窗
|
|
8009
9149
|
return h("div", {
|
|
8010
9150
|
key: pr.number,
|
|
@@ -8029,14 +9169,11 @@ function GithubWorkspacePanel() {
|
|
|
8029
9169
|
h("div", { className: "dsh-github-pr-title-row" }, h("span", { className: "dsh-github-item-title", style: { cursor: "pointer" } }, `${pr.isDraft ? "⚪ " : ""}[${pr.repository?.nameWithOwner || 'Repo'}] #${pr.number} ${pr.title}`), pr.isDraft ? h("span", { className: "dsh-github-badge dsh-badge-yellow", style: { flexShrink: 0 } }, "DRAFT") : null,
|
|
8030
9170
|
// 状态徽章:列表含 merged/closed,必须标明才能一眼区分
|
|
8031
9171
|
(() => {
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
|
|
8035
|
-
|
|
8036
|
-
|
|
8037
|
-
if (s === 'OPEN')
|
|
8038
|
-
return h("span", { className: "dsh-github-badge dsh-badge-green", style: { flexShrink: 0 } }, "OPEN");
|
|
8039
|
-
return null;
|
|
9172
|
+
// 状态→色调来自 shared/state-badges(与右侧栏、CLI 输出同一份)
|
|
9173
|
+
var b = prStateBadge(pr.state);
|
|
9174
|
+
if (!b || b.tone === 'neutral')
|
|
9175
|
+
return null;
|
|
9176
|
+
return h("span", { className: "dsh-github-badge dsh-badge-" + b.tone, style: { flexShrink: 0 } }, b.label);
|
|
8040
9177
|
})(), h("a", {
|
|
8041
9178
|
className: "dsh-github-pr-gh-btn",
|
|
8042
9179
|
href: pr.url,
|
|
@@ -8072,7 +9209,7 @@ function GithubWorkspacePanel() {
|
|
|
8072
9209
|
var issuePillsEl = h("div", { className: "dsh-github-pills" }, h("button", {
|
|
8073
9210
|
className: `dsh-github-pill-btn ${issueFilter === 'all' ? 'active' : ''}`,
|
|
8074
9211
|
onClick: () => setIssueFilter('all'),
|
|
8075
|
-
}, `全部 (${
|
|
9212
|
+
}, `全部 (${myIssues.length})`), h("button", {
|
|
8076
9213
|
className: `dsh-github-pill-btn ${issueFilter === 'reported' ? 'active' : ''}`,
|
|
8077
9214
|
onClick: () => setIssueFilter('reported'),
|
|
8078
9215
|
}, `📥 用户提起的 (${issueStats.reported || 0})`), h("button", {
|
|
@@ -8082,7 +9219,28 @@ function GithubWorkspacePanel() {
|
|
|
8082
9219
|
className: `dsh-github-pill-btn ${issueFilter === 'created' ? 'active' : ''}`,
|
|
8083
9220
|
onClick: () => setIssueFilter('created'),
|
|
8084
9221
|
}, `✍️ 我创建的 (${issueStats.created || 0})`));
|
|
8085
|
-
contentEl = h("div", null,
|
|
9222
|
+
contentEl = h("div", null,
|
|
9223
|
+
// Issue 侧没有可信总数(三路并集无法用单条查询计数),老实标「已加载」,不假装是总数
|
|
9224
|
+
h("h3", { className: "dsh-github-subheading" }, `📝 我发起与参与的 Issues(${filteredIssues.length} 条符合筛选 / 已加载 ${myIssues.length})`), issuePillsEl,
|
|
9225
|
+
// 「加载更多」页脚:与 PR 列表同一口径(服务端 issuesTruncated = 还能继续拉)
|
|
9226
|
+
data && data.issuesTruncated
|
|
9227
|
+
? h("div", {
|
|
9228
|
+
onClick: loadingIssues ? undefined : loadMoreIssues,
|
|
9229
|
+
title: loadingIssues ? "正在加载更多 Issue…" : "点击加载更多(滚动到底部也会自动加载)", "aria-busy": loadingIssues ? "true" : "false",
|
|
9230
|
+
style: {
|
|
9231
|
+
margin: "8px 0",
|
|
9232
|
+
padding: "8px 12px",
|
|
9233
|
+
borderRadius: "6px",
|
|
9234
|
+
fontSize: "11px",
|
|
9235
|
+
lineHeight: 1.5,
|
|
9236
|
+
textAlign: "center",
|
|
9237
|
+
cursor: loadingIssues ? "default" : "pointer",
|
|
9238
|
+
background: "rgba(210, 153, 34, 0.12)",
|
|
9239
|
+
border: "1px solid rgba(210, 153, 34, 0.35)",
|
|
9240
|
+
color: "var(--dsw-alias-state-attention-primary, #9a6700)",
|
|
9241
|
+
},
|
|
9242
|
+
}, loadingIssues ? "⏳ 正在加载中…" : `⬇️ 下滑或点击加载更多 Issue(已显示 ${myIssues.length} 条)`)
|
|
9243
|
+
: null, filteredIssues.length === 0
|
|
8086
9244
|
? (myIssues.length === 0
|
|
8087
9245
|
? h("div", { className: "dsh-github-empty" }, "该账号下暂无任何 Issue 记录")
|
|
8088
9246
|
: h("div", { className: "dsh-github-empty" }, `当前分类「${issueFilter}」下无结果,共 ${myIssues.length} 条记录(切换到「全部」查看)`))
|
|
@@ -8118,12 +9276,10 @@ function GithubWorkspacePanel() {
|
|
|
8118
9276
|
// 标题行:标题 → 分类徽章 → 状态徽章 → GitHub 外跳,全部同一行
|
|
8119
9277
|
h("div", { className: "dsh-github-pr-title-row" }, h("span", { className: "dsh-github-item-title", style: { cursor: "pointer" } }, `[${is.repository?.nameWithOwner || 'Repo'}] #${is.number} ${is.title}`), ...badges, (() => {
|
|
8120
9278
|
// 状态徽章:全局检索含 closed,不标注会让用户把已关闭议题当成待办
|
|
8121
|
-
var
|
|
8122
|
-
if (
|
|
8123
|
-
return
|
|
8124
|
-
|
|
8125
|
-
return h("span", { className: "dsh-github-badge dsh-badge-green", style: { flexShrink: 0 } }, "OPEN");
|
|
8126
|
-
return null;
|
|
9279
|
+
var b = issueStateBadge(is.state);
|
|
9280
|
+
if (!b || b.tone === 'neutral')
|
|
9281
|
+
return null;
|
|
9282
|
+
return h("span", { className: "dsh-github-badge dsh-badge-" + b.tone, style: { flexShrink: 0 } }, b.label);
|
|
8127
9283
|
})(), h("a", {
|
|
8128
9284
|
className: "dsh-github-pr-gh-btn",
|
|
8129
9285
|
href: is.url,
|