dsh-gitnexus-plus 0.1.4 → 0.1.5
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-CN.md +14 -5
- package/README.md +40 -8
- package/lib/client.js +386 -66
- package/lib/gitnexus/analyze.d.ts +6 -0
- package/lib/gitnexus/analyze.d.ts.map +1 -1
- package/lib/gitnexus/analyze.js +10 -0
- package/lib/gitnexus/analyze.js.map +1 -1
- package/lib/gitnexus/commands.d.ts +11 -1
- package/lib/gitnexus/commands.d.ts.map +1 -1
- package/lib/gitnexus/commands.js +27 -8
- package/lib/gitnexus/commands.js.map +1 -1
- package/lib/gitnexus/coverage.d.ts +8 -0
- package/lib/gitnexus/coverage.d.ts.map +1 -1
- package/lib/gitnexus/coverage.js +14 -2
- package/lib/gitnexus/coverage.js.map +1 -1
- package/lib/gitnexus/discover.d.ts +91 -0
- package/lib/gitnexus/discover.d.ts.map +1 -0
- package/lib/gitnexus/discover.js +214 -0
- package/lib/gitnexus/discover.js.map +1 -0
- package/lib/gitnexus/graph.d.ts +9 -0
- package/lib/gitnexus/graph.d.ts.map +1 -1
- package/lib/gitnexus/graph.js +2 -0
- package/lib/gitnexus/graph.js.map +1 -1
- package/lib/gitnexus/index-meta.d.ts +34 -6
- package/lib/gitnexus/index-meta.d.ts.map +1 -1
- package/lib/gitnexus/index-meta.js +47 -7
- package/lib/gitnexus/index-meta.js.map +1 -1
- package/lib/gitnexus/install.d.ts +11 -0
- package/lib/gitnexus/install.d.ts.map +1 -1
- package/lib/gitnexus/install.js +65 -9
- package/lib/gitnexus/install.js.map +1 -1
- package/lib/gitnexus/search.d.ts +13 -0
- package/lib/gitnexus/search.d.ts.map +1 -1
- package/lib/gitnexus/search.js +2 -0
- package/lib/gitnexus/search.js.map +1 -1
- package/lib/index.d.ts +7 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +27 -7
- package/lib/index.js.map +1 -1
- package/lib/routes/analyze.d.ts +5 -4
- package/lib/routes/analyze.d.ts.map +1 -1
- package/lib/routes/analyze.js +17 -4
- package/lib/routes/analyze.js.map +1 -1
- package/lib/routes/coverage.d.ts +16 -10
- package/lib/routes/coverage.d.ts.map +1 -1
- package/lib/routes/coverage.js +31 -22
- package/lib/routes/coverage.js.map +1 -1
- package/lib/routes/delta.js +8 -8
- package/lib/routes/delta.js.map +1 -1
- package/lib/routes/guard.d.ts +3 -6
- package/lib/routes/guard.d.ts.map +1 -1
- package/lib/routes/guard.js +3 -6
- package/lib/routes/guard.js.map +1 -1
- package/lib/routes/projects.d.ts +21 -0
- package/lib/routes/projects.d.ts.map +1 -0
- package/lib/routes/projects.js +61 -0
- package/lib/routes/projects.js.map +1 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -2444,6 +2444,10 @@ define("sections/maintenance", ["require", "exports", "react", "@deepseek-ai/dsh
|
|
|
2444
2444
|
// install or the registry write. Refuse to start it and say why, rather than
|
|
2445
2445
|
// leaving a button that is known to be wrong on this host.
|
|
2446
2446
|
const hostCurrent = maintenance.supportsForce === true;
|
|
2447
|
+
// A folder with no `.git` is indexable, but only with `--skip-git`, and GitNexus
|
|
2448
|
+
// disables commit tracking and incremental updates for it. Calling that action an
|
|
2449
|
+
// "incremental refresh" would describe a mode the index cannot have.
|
|
2450
|
+
const skipGit = maintenance.skipGit === true;
|
|
2447
2451
|
// Normalised rather than trusted. The declared type says `ClientAnalyzeRun | null`,
|
|
2448
2452
|
// but a React element built without the prop arrives as `undefined` — which is
|
|
2449
2453
|
// exactly what happened the first time this component was rendered by a caller that
|
|
@@ -2599,11 +2603,17 @@ define("sections/maintenance", ["require", "exports", "react", "@deepseek-ai/dsh
|
|
|
2599
2603
|
// an index that already existed. Only the label and the "when" line are stateful;
|
|
2600
2604
|
// the command itself is identical.
|
|
2601
2605
|
const shown = command.mode === 'init' && indexed
|
|
2602
|
-
?
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2606
|
+
? skipGit
|
|
2607
|
+
? {
|
|
2608
|
+
...command,
|
|
2609
|
+
label: '重新分析',
|
|
2610
|
+
when: '该目录不是 git 仓库:没有增量刷新,每次都会完整重分析;未变更文件的解析缓存仍会复用。',
|
|
2611
|
+
}
|
|
2612
|
+
: {
|
|
2613
|
+
...command,
|
|
2614
|
+
label: '增量刷新',
|
|
2615
|
+
when: '索引落后或有文件改动时使用;未变更文件的解析产物会复用。',
|
|
2616
|
+
}
|
|
2607
2617
|
: command;
|
|
2608
2618
|
return (0, primitives_7.h)('div', {
|
|
2609
2619
|
key: command.mode,
|
|
@@ -2632,9 +2642,17 @@ define("sections/maintenance", ["require", "exports", "react", "@deepseek-ai/dsh
|
|
|
2632
2642
|
? '运行中'
|
|
2633
2643
|
: gateClean
|
|
2634
2644
|
? '无需刷新'
|
|
2635
|
-
:
|
|
2636
|
-
?
|
|
2637
|
-
|
|
2645
|
+
: !indexed
|
|
2646
|
+
? hostCurrent
|
|
2647
|
+
? '初始化索引'
|
|
2648
|
+
: '初始化索引(需重启)'
|
|
2649
|
+
: skipGit
|
|
2650
|
+
? hostCurrent
|
|
2651
|
+
? '执行重新分析'
|
|
2652
|
+
: '执行重新分析(需重启)'
|
|
2653
|
+
: hostCurrent
|
|
2654
|
+
? '执行增量刷新'
|
|
2655
|
+
: '执行增量刷新(需重启)')
|
|
2638
2656
|
: command.mode === 'repair-fts'
|
|
2639
2657
|
? (0, primitives_7.h)(primitives_7.Button, { variant: 'ghost', disabled: running || !hostCurrent, onClick: () => setConfirming('repair-fts') }, running ? '运行中' : '修复搜索索引')
|
|
2640
2658
|
: hostCurrent
|
|
@@ -2670,12 +2688,21 @@ define("sections/maintenance", ["require", "exports", "react", "@deepseek-ai/dsh
|
|
|
2670
2688
|
// and can take minutes, while the incremental path reuses unchanged files.
|
|
2671
2689
|
(0, primitives_7.h)(primitives.RiskConfirmation, {
|
|
2672
2690
|
open: confirming !== null,
|
|
2673
|
-
title: repairConfirm
|
|
2691
|
+
title: repairConfirm
|
|
2692
|
+
? '修复搜索索引(FTS)?'
|
|
2693
|
+
: fullConfirm
|
|
2694
|
+
? '执行全量重建?'
|
|
2695
|
+
: !indexed
|
|
2696
|
+
? '初始化索引?'
|
|
2697
|
+
: skipGit
|
|
2698
|
+
? '重新分析这个目录?'
|
|
2699
|
+
: '执行增量刷新?',
|
|
2674
2700
|
description: repairConfirm
|
|
2675
2701
|
? '将执行:\n\n' +
|
|
2676
2702
|
(confirmCommand?.shell ?? 'gitnexus analyze --index-only --repair-fts') +
|
|
2677
2703
|
'\n\n' +
|
|
2678
2704
|
'只重建搜索索引(FTS),不重跑分析、不改图,耗时通常在秒级。' +
|
|
2705
|
+
(skipGit ? '该目录没有 .git,命令带 --skip-git。' : '') +
|
|
2679
2706
|
'只写入 .gitnexus/,不改动源文件。' +
|
|
2680
2707
|
'如果 FTS 扩展文件本身缺失(Windows 上常见),修复也会失败 —— 那是上游安装问题,不是索引坏了。' +
|
|
2681
2708
|
'命令由 DSH 的 shell 执行器运行,沙箱与其它命令一致 —— 本插件不申请扩权。'
|
|
@@ -2686,20 +2713,32 @@ define("sections/maintenance", ["require", "exports", "react", "@deepseek-ai/dsh
|
|
|
2686
2713
|
'这会重建图与 FTS;未变更文件的解析产物仍会复用。' +
|
|
2687
2714
|
'但耗时以分钟计,索引很大时更久(上限 10 分钟,可随时取消)。' +
|
|
2688
2715
|
'重建期间索引可能短暂不可用。' +
|
|
2716
|
+
(skipGit ? '该目录没有 .git,命令带 --skip-git;重建后仍没有提交基线与增量刷新。' : '') +
|
|
2689
2717
|
'只写入 .gitnexus/,不改动源文件,也不会重写 AGENTS.md / CLAUDE.md。' +
|
|
2690
2718
|
'命令由 DSH 的 shell 执行器运行,沙箱与其它命令一致 —— 本插件不申请扩权。'
|
|
2691
2719
|
: '将在项目目录下运行:\n\n' +
|
|
2692
2720
|
(confirmCommand?.shell ?? 'gitnexus analyze --index-only') +
|
|
2693
2721
|
'\n\n' +
|
|
2694
2722
|
'只写入 .gitnexus/,不改动任何源文件,也不会重写 AGENTS.md / CLAUDE.md。' +
|
|
2695
|
-
|
|
2696
|
-
|
|
2723
|
+
(skipGit
|
|
2724
|
+
? '该目录没有 .git,命令带 --skip-git:GitNexus 会直接索引这个目录,' +
|
|
2725
|
+
'但没有提交基线,也没有增量刷新 —— 每次都会完整重分析,也不会有 Git 活动。'
|
|
2726
|
+
: '耗时取决于改动量:无改动时约 7 秒,改动几个文件时实测可到 3 分钟,' +
|
|
2727
|
+
'大仓库的初始化或刷新可能更久(上限 10 分钟,可随时取消)。') +
|
|
2697
2728
|
'命令由 DSH 的 shell 执行器运行,沙箱与其它命令一致 —— 本插件不申请扩权。',
|
|
2698
2729
|
acknowledgeLabel: fullConfirm
|
|
2699
2730
|
? '我了解这会重建图与 FTS、耗时数分钟,并可能让索引短暂不可用'
|
|
2700
2731
|
: '我了解这会运行一个进程并写入 .gitnexus/',
|
|
2701
2732
|
cancelLabel: '取消',
|
|
2702
|
-
confirmLabel: repairConfirm
|
|
2733
|
+
confirmLabel: repairConfirm
|
|
2734
|
+
? '开始修复'
|
|
2735
|
+
: fullConfirm
|
|
2736
|
+
? '开始重建'
|
|
2737
|
+
: !indexed
|
|
2738
|
+
? '开始初始化'
|
|
2739
|
+
: skipGit
|
|
2740
|
+
? '开始重新分析'
|
|
2741
|
+
: '执行',
|
|
2703
2742
|
closeLabel: '关闭',
|
|
2704
2743
|
// The checkbox is controlled, so it needs state of its own, and it is reset on
|
|
2705
2744
|
// close: the acknowledgement is per-action rather than remembered.
|
|
@@ -2993,10 +3032,57 @@ define("sections/search", ["require", "exports", "primitives"], function (requir
|
|
|
2993
3032
|
define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-ui-primitives", "format", "navigation", "primitives", "sections/overview", "sections/activity", "sections/capabilities", "sections/install", "sections/languages", "sections/large-files", "sections/maintenance", "sections/search", "sections/graph"], function (require, exports, React, primitives, format_10, navigation_3, primitives_11, overview_1, activity_1, capabilities_2, install_1, languages_1, large_files_1, maintenance_1, search_1, graph_1) {
|
|
2994
3033
|
"use strict";
|
|
2995
3034
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3035
|
+
exports.chooseProject = chooseProject;
|
|
2996
3036
|
exports.LoadingBar = LoadingBar;
|
|
2997
3037
|
exports.CoverageView = CoverageView;
|
|
2998
3038
|
exports.CoveragePanel = CoveragePanel;
|
|
2999
3039
|
exports.CoverageTitle = CoverageTitle;
|
|
3040
|
+
/**
|
|
3041
|
+
* Pick the project a workspace opens on.
|
|
3042
|
+
*
|
|
3043
|
+
* An explicit remembered choice wins while it still exists. Otherwise an indexed
|
|
3044
|
+
* project is preferred over an unindexed git repository, and the workspace's own index
|
|
3045
|
+
* wins over a descendant. A workspace with no indexes opens on the workspace itself —
|
|
3046
|
+
* the scan-only state, with the unindexed git projects available in the switcher.
|
|
3047
|
+
*/
|
|
3048
|
+
function chooseProject(projects, workspace, remembered) {
|
|
3049
|
+
if (remembered !== null) {
|
|
3050
|
+
const hit = projects.find((project) => project.path === remembered);
|
|
3051
|
+
if (hit !== undefined)
|
|
3052
|
+
return hit.path;
|
|
3053
|
+
}
|
|
3054
|
+
const selfIndex = projects.find((project) => project.direction === 'self' && project.kind === 'index');
|
|
3055
|
+
if (selfIndex !== undefined)
|
|
3056
|
+
return selfIndex.path;
|
|
3057
|
+
const indexed = projects.find((project) => project.kind === 'index');
|
|
3058
|
+
if (indexed !== undefined)
|
|
3059
|
+
return indexed.path;
|
|
3060
|
+
const self = projects.find((project) => project.direction === 'self');
|
|
3061
|
+
if (self !== undefined)
|
|
3062
|
+
return self.path;
|
|
3063
|
+
return projects[0]?.path ?? workspace;
|
|
3064
|
+
}
|
|
3065
|
+
const ACTIVE_PROJECT_KEY = 'dsh-gitnexus-plus:active-project:';
|
|
3066
|
+
function readRememberedProject(workspace) {
|
|
3067
|
+
try {
|
|
3068
|
+
if (typeof localStorage === 'undefined')
|
|
3069
|
+
return null;
|
|
3070
|
+
return localStorage.getItem(`${ACTIVE_PROJECT_KEY}${workspace}`);
|
|
3071
|
+
}
|
|
3072
|
+
catch {
|
|
3073
|
+
return null;
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
function rememberProject(workspace, project) {
|
|
3077
|
+
try {
|
|
3078
|
+
if (typeof localStorage === 'undefined')
|
|
3079
|
+
return;
|
|
3080
|
+
localStorage.setItem(`${ACTIVE_PROJECT_KEY}${workspace}`, project);
|
|
3081
|
+
}
|
|
3082
|
+
catch {
|
|
3083
|
+
/* storage may be unavailable in a hardened webview; selection stays in memory */
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3000
3086
|
/**
|
|
3001
3087
|
* Whether two polled run snapshots say the same thing.
|
|
3002
3088
|
*
|
|
@@ -3055,17 +3141,62 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3055
3141
|
},
|
|
3056
3142
|
}), (0, primitives_11.h)('div', { style: { ...primitives_11.MUTED, fontSize: '10px' } }, text));
|
|
3057
3143
|
}
|
|
3058
|
-
function CoverageView({ report, maintenance, projectPath, context, busy, progress, error, readAt, run, runError, delta, deltaError, deltaRoute, onRefresh, onStartRun, onCancelRun, installRun, installError, onInstall, onCancelInstall, search, graph, }) {
|
|
3144
|
+
function CoverageView({ report, maintenance, projectPath, workspacePath, projects, projectsVisited, projectsTruncated, projectsError, onSelectProject, context, busy, progress, error, readAt, run, runError, delta, deltaError, deltaRoute, onRefresh, onStartRun, onCancelRun, installRun, installError, onInstall, onCancelInstall, search, graph, }) {
|
|
3059
3145
|
// Whether an index exists decides which maintenance command is applicable. When
|
|
3060
3146
|
// the project could not even be identified there is nothing to claim, so it is
|
|
3061
3147
|
// false rather than assumed.
|
|
3062
3148
|
const indexed = report?.repo?.indexed === true;
|
|
3063
|
-
|
|
3064
|
-
//
|
|
3065
|
-
|
|
3149
|
+
// A maintenance payload with no commands is not a section: rendering its title
|
|
3150
|
+
// produced an empty card on failed reads and on hosts that predate the route.
|
|
3151
|
+
const maintenanceSection = maintenance !== null &&
|
|
3152
|
+
maintenance !== undefined &&
|
|
3153
|
+
Array.isArray(maintenance.commands) &&
|
|
3154
|
+
maintenance.commands.length > 0
|
|
3155
|
+
? maintenance
|
|
3156
|
+
: null;
|
|
3157
|
+
// The workspace and the active project are different facts when the index lives in a
|
|
3158
|
+
// subdirectory. The path position becomes a switcher when there is more than one.
|
|
3159
|
+
const projectChoices = projects ?? [];
|
|
3160
|
+
const indexedProjectCount = projectChoices.filter((project) => project.kind === 'index').length;
|
|
3161
|
+
const gitProjectCount = projectChoices.filter((project) => project.kind === 'git').length;
|
|
3162
|
+
const projectLabel = (project) => {
|
|
3163
|
+
const base = project.relPath === ''
|
|
3164
|
+
? `${project.name}(本工作区)`
|
|
3165
|
+
: project.relPath === project.name
|
|
3166
|
+
? project.name
|
|
3167
|
+
: `${project.name} — ${project.relPath}`;
|
|
3168
|
+
return project.kind === 'git' ? `${base}(未索引)` : base;
|
|
3169
|
+
};
|
|
3170
|
+
const activeInChoices = projectChoices.some((project) => project.path === projectPath);
|
|
3171
|
+
const showSwitcher = projectChoices.length > 1 &&
|
|
3172
|
+
activeInChoices &&
|
|
3173
|
+
onSelectProject !== undefined &&
|
|
3174
|
+
projectPath !== null;
|
|
3175
|
+
const activeDiffers = projectPath !== null && workspacePath !== null && projectPath !== workspacePath;
|
|
3176
|
+
const header = (0, primitives_11.h)('div', { style: { display: 'flex', flexDirection: 'column', gap: '3px', minWidth: 0 } }, (0, primitives_11.h)(primitives_11.Row, null, report !== null && report.repo?.indexed ? (0, primitives_11.h)(primitives_11.StateDot, { state: (0, format_10.healthState)(report) }) : null, (0, primitives_11.h)('span', { style: { fontWeight: 600 } }, 'GitNexus 索引'),
|
|
3177
|
+
// The project, not a global picker. A workspace-scoped switcher is the one
|
|
3178
|
+
// selection this panel offers; it changes what the cards describe, nothing else.
|
|
3066
3179
|
projectPath === null
|
|
3067
3180
|
? (0, primitives_11.h)('span', { style: primitives_11.MUTED }, '未解析到工作区')
|
|
3068
|
-
:
|
|
3181
|
+
: showSwitcher
|
|
3182
|
+
? (0, primitives_11.h)('span', { style: { display: 'flex', alignItems: 'center', gap: '2px', minWidth: 0, flex: '1 1 auto' } }, (0, primitives_11.h)('select', {
|
|
3183
|
+
value: projectPath,
|
|
3184
|
+
onChange: (event) => onSelectProject?.(event.target.value),
|
|
3185
|
+
'aria-label': '切换工作区内的 GitNexus 索引',
|
|
3186
|
+
title: `工作区:${workspacePath ?? ''} · ${projectChoices.length} 个索引`,
|
|
3187
|
+
style: {
|
|
3188
|
+
appearance: 'none',
|
|
3189
|
+
border: 0,
|
|
3190
|
+
background: 'transparent',
|
|
3191
|
+
color: 'inherit',
|
|
3192
|
+
font: 'inherit',
|
|
3193
|
+
minWidth: 0,
|
|
3194
|
+
maxWidth: '100%',
|
|
3195
|
+
padding: 0,
|
|
3196
|
+
cursor: 'pointer',
|
|
3197
|
+
},
|
|
3198
|
+
}, projectChoices.map((project) => (0, primitives_11.h)('option', { key: project.path, value: project.path }, projectLabel(project)))), (0, primitives_11.h)('span', { style: { ...primitives_11.MUTED, fontSize: '10px' } }, '▾'))
|
|
3199
|
+
: (0, primitives_11.h)('span', { style: { minWidth: 0, flex: '1 1 auto' } }, (0, primitives_11.h)(primitives_11.PathLabel, { path: projectPath })), (0, primitives_11.h)('span', { style: { flex: 1 } }),
|
|
3069
3200
|
// The label states the scope. "刷新" alone next to a staleness warning reads as
|
|
3070
3201
|
// "rebuild the index", which this control has never done and — by design, since
|
|
3071
3202
|
// the plugin spawns nothing — cannot do.
|
|
@@ -3075,7 +3206,30 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3075
3206
|
disabled: busy,
|
|
3076
3207
|
title: '重新读取报告(不会重建索引;重建见下方「维护命令」)',
|
|
3077
3208
|
'aria-label': '重新读取报告',
|
|
3078
|
-
}, (0, primitives_11.h)(primitives_11.IconRefreshOutlineRegular, { size: 14 })))
|
|
3209
|
+
}, (0, primitives_11.h)(primitives_11.IconRefreshOutlineRegular, { size: 14 }))), showSwitcher ||
|
|
3210
|
+
activeDiffers ||
|
|
3211
|
+
projectsTruncated === true ||
|
|
3212
|
+
(projectsError !== null && projectsError !== undefined)
|
|
3213
|
+
? (0, primitives_11.h)('div', { style: { ...primitives_11.MUTED, fontSize: '11px', minWidth: 0, overflowWrap: 'anywhere' } }, [
|
|
3214
|
+
showSwitcher
|
|
3215
|
+
? `工作区:${workspacePath ?? ''} · ${projectChoices.length} 个项目` +
|
|
3216
|
+
(indexedProjectCount > 0
|
|
3217
|
+
? `(${indexedProjectCount} 个已索引)`
|
|
3218
|
+
: gitProjectCount > 0
|
|
3219
|
+
? `(${gitProjectCount} 个 git 项目未索引)`
|
|
3220
|
+
: '') +
|
|
3221
|
+
` · 当前 ${projectPath ?? ''}`
|
|
3222
|
+
: activeDiffers
|
|
3223
|
+
? `工作区:${workspacePath ?? ''} · 当前 ${projectPath ?? ''}`
|
|
3224
|
+
: null,
|
|
3225
|
+
projectsTruncated === true ? '项目搜索达到上限,可能还有未列出的项目。' : null,
|
|
3226
|
+
projectsError !== null && projectsError !== undefined
|
|
3227
|
+
? `项目列表读取失败:${projectsError}`
|
|
3228
|
+
: null,
|
|
3229
|
+
]
|
|
3230
|
+
.filter((line) => line !== null)
|
|
3231
|
+
.join(' '))
|
|
3232
|
+
: null);
|
|
3079
3233
|
// A report already on screen stays readable while a manual refresh runs; the bar
|
|
3080
3234
|
// rides above it. Before the first report exists, the bar is the whole body.
|
|
3081
3235
|
// `percent` is null when the host does not stream (old host, or a cached read):
|
|
@@ -3097,14 +3251,14 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3097
3251
|
: (0, primitives_11.h)(primitives_11.Section, { wide: true, title: '面板收到的上下文' }, (0, primitives_11.h)('div', { style: { display: 'flex', flexDirection: 'column', gap: '3px' } }, (0, primitives_11.h)('div', null, `存在:${context.present.length > 0 ? context.present.join('、') : '(无)'}`), (0, primitives_11.h)('div', null, `缺失:${context.absent.length > 0 ? context.absent.join('、') : '(无)'}`))));
|
|
3098
3252
|
}
|
|
3099
3253
|
if (error !== null) {
|
|
3100
|
-
return (0, primitives_11.h)('div', { style: primitives_11.styles.root }, header, (0, primitives_11.h)(primitives_11.Section, { wide: true, style: { display: 'flex', gap: '8px', alignItems: 'flex-start' } }, (0, primitives_11.h)(primitives_11.StateDot, { state: 'error' }), (0, primitives_11.h)('span', null, error)),
|
|
3101
|
-
?
|
|
3102
|
-
: (0, primitives_11.h)(primitives_11.Section, {
|
|
3254
|
+
return (0, primitives_11.h)('div', { style: primitives_11.styles.root }, header, (0, primitives_11.h)(primitives_11.Section, { wide: true, style: { display: 'flex', gap: '8px', alignItems: 'flex-start' } }, (0, primitives_11.h)(primitives_11.StateDot, { state: 'error' }), (0, primitives_11.h)('span', null, error)), maintenanceSection !== null
|
|
3255
|
+
? (0, primitives_11.h)(primitives_11.Section, {
|
|
3103
3256
|
wide: true,
|
|
3104
3257
|
title: '维护命令',
|
|
3105
3258
|
meta: (0, primitives_11.h)('span', { style: { ...primitives_11.MUTED, fontSize: '12px' } }, '复制后到会话执行(沙箱与提权由 DSH 处理)'),
|
|
3106
3259
|
scroll: 320,
|
|
3107
|
-
}, (0, primitives_11.h)(maintenance_1.MaintenanceSection, { maintenance, projectPath, indexed: false, run, runError, delta, deltaError, deltaRoute, onStartRun, onCancelRun }))
|
|
3260
|
+
}, (0, primitives_11.h)(maintenance_1.MaintenanceSection, { maintenance: maintenanceSection, projectPath, indexed: false, run, runError, delta, deltaError, deltaRoute, onStartRun, onCancelRun }))
|
|
3261
|
+
: null);
|
|
3108
3262
|
}
|
|
3109
3263
|
if (report === null) {
|
|
3110
3264
|
return (0, primitives_11.h)('div', { style: primitives_11.styles.root }, header, progressBar);
|
|
@@ -3134,22 +3288,32 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3134
3288
|
runError: installError,
|
|
3135
3289
|
onInstall,
|
|
3136
3290
|
onCancelInstall,
|
|
3137
|
-
}), (0, primitives_11.h)('div', { style: { gridColumn: '1 / -1', display: 'flex', gap: '8px', alignItems: 'flex-start' } }, (0, primitives_11.h)(primitives_11.StateDot, { state: 'idle' }), (0, primitives_11.h)('
|
|
3291
|
+
}), (0, primitives_11.h)('div', { style: { gridColumn: '1 / -1', display: 'flex', gap: '8px', alignItems: 'flex-start' } }, (0, primitives_11.h)(primitives_11.StateDot, { state: 'idle' }), (0, primitives_11.h)('div', { style: { display: 'flex', flexDirection: 'column', gap: '2px', minWidth: 0 } }, (0, primitives_11.h)('span', null, report.repo?.hasGit === false
|
|
3292
|
+
? '该目录不是 git 仓库,也没有 GitNexus 索引。下面是工作区扫描结果;仍可建立索引(--skip-git),但没有提交基线与增量刷新,也不会有 Git 活动。'
|
|
3293
|
+
: '该项目还没有 GitNexus 索引。下面是工作区扫描结果;初始化后可看到图谱、查询能力与 Git 活动对账。'), Array.isArray(projects)
|
|
3294
|
+
? (0, primitives_11.h)('span', { style: { ...primitives_11.MUTED, fontSize: '11px' } }, projectsError !== null && projectsError !== undefined
|
|
3295
|
+
? `项目列表读取失败:${projectsError};已按工作区描述。`
|
|
3296
|
+
: indexedProjectCount === 0 && gitProjectCount > 0
|
|
3297
|
+
? `已在 ${projectsVisited ?? 0} 个目录中查找;工作区内有 ${gitProjectCount} 个 git 项目尚未建索引,可在标题栏切换后初始化。`
|
|
3298
|
+
: indexedProjectCount === 0
|
|
3299
|
+
? `已在 ${projectsVisited ?? 0} 个目录中查找,没有找到 GitNexus 索引或 git 仓库。`
|
|
3300
|
+
: null)
|
|
3301
|
+
: null)), (0, primitives_11.h)(primitives_11.Section, { wide: true, title: '扫描概览 · 尚未索引' }, (0, primitives_11.h)(primitives_11.MetricGrid, {
|
|
3138
3302
|
items: [
|
|
3139
3303
|
{ label: '扫描文件', value: (0, format_10.compactNumber)(totals.scanned) },
|
|
3140
3304
|
{ label: '选中文件', value: (0, format_10.compactNumber)(totals.selected) },
|
|
3141
3305
|
{ label: '扫描字节', value: (0, format_10.fileSize)(totals.scannedBytes ?? 0) },
|
|
3142
3306
|
{ label: '大小上限', value: cap.text, hint: (0, format_10.capSourceLabel)(cap.source) },
|
|
3143
3307
|
],
|
|
3144
|
-
}), (0, primitives_11.h)('div', { style: { ...primitives_11.MUTED, marginTop: '6px' } }, '这里的数字来自工作区,不是索引;索引里有什么,只有建立后才能回答。')), languages !== null && languages.rows.length > 0 ? (0, primitives_11.h)(languages_1.MemoizedLanguageSection, { languages }) : null,
|
|
3145
|
-
?
|
|
3146
|
-
: (0, primitives_11.h)(primitives_11.Section, {
|
|
3308
|
+
}), (0, primitives_11.h)('div', { style: { ...primitives_11.MUTED, marginTop: '6px' } }, '这里的数字来自工作区,不是索引;索引里有什么,只有建立后才能回答。')), languages !== null && languages.rows.length > 0 ? (0, primitives_11.h)(languages_1.MemoizedLanguageSection, { languages }) : null, maintenanceSection !== null
|
|
3309
|
+
? (0, primitives_11.h)(primitives_11.Section, {
|
|
3147
3310
|
wide: true,
|
|
3148
3311
|
title: '初始化索引',
|
|
3149
|
-
meta: (0, primitives_11.h)('span', { style: { ...primitives_11.MUTED, fontSize: '11px' } },
|
|
3312
|
+
meta: (0, primitives_11.h)('span', { style: { ...primitives_11.MUTED, fontSize: '11px' } }, maintenanceSection.supportsForce === true
|
|
3150
3313
|
? '均可直接执行,也可复制到会话(沙箱与提权由 DSH 处理)'
|
|
3151
3314
|
: '重启 DSH 后可执行;当前仅可复制(沙箱与提权由 DSH 处理)'),
|
|
3152
|
-
}, (0, primitives_11.h)(maintenance_1.MaintenanceSection, { maintenance, projectPath, indexed, run, runError, delta, deltaError, deltaRoute, onStartRun, onCancelRun }))
|
|
3315
|
+
}, (0, primitives_11.h)(maintenance_1.MaintenanceSection, { maintenance: maintenanceSection, projectPath, indexed, run, runError, delta, deltaError, deltaRoute, onStartRun, onCancelRun }))
|
|
3316
|
+
: null);
|
|
3153
3317
|
}
|
|
3154
3318
|
return (0, primitives_11.h)('div', { style: primitives_11.styles.root },
|
|
3155
3319
|
// The header and the two status lines span everything: they describe the whole
|
|
@@ -3333,18 +3497,18 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3333
3497
|
onDaysChange: setActivityDays,
|
|
3334
3498
|
})),
|
|
3335
3499
|
// Wide, and always open: the commands are the action this panel offers.
|
|
3336
|
-
|
|
3337
|
-
?
|
|
3338
|
-
: (0, primitives_11.h)(primitives_11.Section, {
|
|
3500
|
+
maintenanceSection !== null
|
|
3501
|
+
? (0, primitives_11.h)(primitives_11.Section, {
|
|
3339
3502
|
wide: true,
|
|
3340
3503
|
title: '维护命令',
|
|
3341
|
-
meta: (0, primitives_11.h)('span', { style: { ...primitives_11.MUTED, fontSize: '12px' } }, `${
|
|
3342
|
-
(
|
|
3504
|
+
meta: (0, primitives_11.h)('span', { style: { ...primitives_11.MUTED, fontSize: '12px' } }, `${maintenanceSection.commands?.length ?? 0} 条 · ` +
|
|
3505
|
+
(maintenanceSection.supportsForce === true
|
|
3343
3506
|
? '均可直接执行,也可复制到会话'
|
|
3344
3507
|
: '重启 DSH 后可执行;当前仅可复制') +
|
|
3345
3508
|
'(沙箱与提权由 DSH 处理)'),
|
|
3346
3509
|
scroll: 360,
|
|
3347
|
-
}, (0, primitives_11.h)(maintenance_1.MaintenanceSection, { maintenance, projectPath, indexed, run, runError, delta, deltaError, deltaRoute, onStartRun, onCancelRun }))
|
|
3510
|
+
}, (0, primitives_11.h)(maintenance_1.MaintenanceSection, { maintenance: maintenanceSection, projectPath, indexed, run, runError, delta, deltaError, deltaRoute, onStartRun, onCancelRun }))
|
|
3511
|
+
: null);
|
|
3348
3512
|
}
|
|
3349
3513
|
/**
|
|
3350
3514
|
* Stateful wrapper: resolves the current project, loads its coverage.
|
|
@@ -3356,11 +3520,36 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3356
3520
|
* @param {{params?: {repo?: string}, sessionId?: string, useWorkspaces?: Function}} props
|
|
3357
3521
|
*/
|
|
3358
3522
|
function CoveragePanel(props) {
|
|
3359
|
-
|
|
3523
|
+
// The path the operator opened. It is the search root for the project list; the
|
|
3524
|
+
// active project may be the workspace itself or one of its descendants.
|
|
3525
|
+
const workspacePath = (0, navigation_3.useProjectPath)(props);
|
|
3360
3526
|
const sessionId = typeof props.sessionId === 'string' ? props.sessionId : null;
|
|
3361
|
-
const [
|
|
3362
|
-
const [
|
|
3363
|
-
const [
|
|
3527
|
+
const [rawReport, setRawReport] = React.useState(null);
|
|
3528
|
+
const [rawMaintenance, setRawMaintenance] = React.useState(null);
|
|
3529
|
+
const [rawError, setRawError] = React.useState(null);
|
|
3530
|
+
/** The project the current report belongs to. */
|
|
3531
|
+
const [reportProject, setReportProject] = React.useState(null);
|
|
3532
|
+
const [projectList, setProjectList] = React.useState(null);
|
|
3533
|
+
/** The workspace `projectList` was resolved for. Guards against a workspace switch. */
|
|
3534
|
+
const [projectListFor, setProjectListFor] = React.useState(null);
|
|
3535
|
+
const [activeProject, setActiveProject] = React.useState(null);
|
|
3536
|
+
const [projectsError, setProjectsError] = React.useState(null);
|
|
3537
|
+
const [resolving, setResolving] = React.useState(false);
|
|
3538
|
+
/**
|
|
3539
|
+
* Bumped after the project list resolves, so the coverage effect waits for it.
|
|
3540
|
+
* Without this, a manual refresh would read coverage for the old active project
|
|
3541
|
+
* while the resolve was still deciding whether that project still exists.
|
|
3542
|
+
*/
|
|
3543
|
+
const [loadToken, setLoadToken] = React.useState(0);
|
|
3544
|
+
const projectsReady = projectListFor === workspacePath;
|
|
3545
|
+
const visibleProjects = projectsReady ? projectList?.projects ?? [] : undefined;
|
|
3546
|
+
const projectPath = activeProject ?? workspacePath;
|
|
3547
|
+
const reportVisible = reportProject === projectPath;
|
|
3548
|
+
const report = reportVisible ? rawReport : null;
|
|
3549
|
+
const maintenance = reportVisible ? rawMaintenance : null;
|
|
3550
|
+
const error = reportVisible ? rawError : null;
|
|
3551
|
+
/** The graph is only meaningful for an index; a scan-only project has no projection. */
|
|
3552
|
+
const graphAvailable = report?.repo?.indexed === true;
|
|
3364
3553
|
const [busy, setBusy] = React.useState(false);
|
|
3365
3554
|
const [progress, setProgress] = React.useState(null);
|
|
3366
3555
|
const [reloadToken, setReloadToken] = React.useState(0);
|
|
@@ -3388,6 +3577,8 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3388
3577
|
// "not run yet" rather than as a failure.
|
|
3389
3578
|
const [run, setRun] = React.useState(null);
|
|
3390
3579
|
const [runError, setRunError] = React.useState(null);
|
|
3580
|
+
/** Which project the run status belongs to; a switch must not show it elsewhere. */
|
|
3581
|
+
const [runProject, setRunProject] = React.useState(null);
|
|
3391
3582
|
/** The Tier 0 verdict, or null while unknown (no route on this host, or not read yet). */
|
|
3392
3583
|
const [delta, setDelta] = React.useState(null);
|
|
3393
3584
|
const [deltaError, setDeltaError] = React.useState(null);
|
|
@@ -3398,6 +3589,8 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3398
3589
|
* gate and no explanation. Same lesson as the graph card: absent is a state to name.
|
|
3399
3590
|
*/
|
|
3400
3591
|
const [deltaRoute, setDeltaRoute] = React.useState('unknown');
|
|
3592
|
+
/** Which project the current gate reading belongs to, so a switch can clear it. */
|
|
3593
|
+
const deltaRepoRef = React.useRef(null);
|
|
3401
3594
|
/** The bounded graph projection, its scope, and the load state around it. */
|
|
3402
3595
|
const [graphProjection, setGraphProjection] = React.useState(null);
|
|
3403
3596
|
const [graphLoading, setGraphLoading] = React.useState(false);
|
|
@@ -3412,7 +3605,9 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3412
3605
|
const [graphRoute, setGraphRoute] = React.useState('unknown');
|
|
3413
3606
|
const [graphScope, setGraphScope] = React.useState(null);
|
|
3414
3607
|
const [graphReload, setGraphReload] = React.useState(0);
|
|
3415
|
-
|
|
3608
|
+
/** Which project the current projection belongs to, so a switch can clear it. */
|
|
3609
|
+
const graphRepoRef = React.useRef(null);
|
|
3610
|
+
const running = run?.state === 'running' && runProject === projectPath;
|
|
3416
3611
|
/**
|
|
3417
3612
|
* Poll while a run is in flight.
|
|
3418
3613
|
*
|
|
@@ -3433,6 +3628,7 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3433
3628
|
const next = body.run ?? null;
|
|
3434
3629
|
// Polls fire every second; only a changed verdict should re-render the report.
|
|
3435
3630
|
setRun((previous) => (sameRun(previous, next) ? previous : next));
|
|
3631
|
+
setRunProject(projectPath);
|
|
3436
3632
|
// A finished run changed the index, so the report on screen is now a reading of
|
|
3437
3633
|
// the previous one. This is the whole reason the run was started.
|
|
3438
3634
|
if (next !== null && next.state !== 'running')
|
|
@@ -3454,6 +3650,7 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3454
3650
|
if (projectPath === null)
|
|
3455
3651
|
return;
|
|
3456
3652
|
setRunError(null);
|
|
3653
|
+
setRunProject(projectPath);
|
|
3457
3654
|
try {
|
|
3458
3655
|
// One request, three modes. Sending them as flags rather than a body keeps the
|
|
3459
3656
|
// operation visible in the URL the host logs alongside the command it ran.
|
|
@@ -3463,6 +3660,7 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3463
3660
|
if (!body?.ok)
|
|
3464
3661
|
throw new Error(body?.message ?? `请求失败 (${res.status})`);
|
|
3465
3662
|
setRun(body.run ?? null);
|
|
3663
|
+
setRunProject(projectPath);
|
|
3466
3664
|
}
|
|
3467
3665
|
catch (cause) {
|
|
3468
3666
|
setRunError(String(cause?.message ?? cause));
|
|
@@ -3471,6 +3669,7 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3471
3669
|
const cancelRun = React.useCallback(async () => {
|
|
3472
3670
|
if (projectPath === null)
|
|
3473
3671
|
return;
|
|
3672
|
+
setRunProject(projectPath);
|
|
3474
3673
|
try {
|
|
3475
3674
|
const res = await fetch((0, navigation_3.routeUrl)(`/api/gitnexus/analyze?repo=${encodeURIComponent(projectPath)}`), {
|
|
3476
3675
|
method: 'DELETE',
|
|
@@ -3717,13 +3916,93 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3717
3916
|
void startRun('refresh');
|
|
3718
3917
|
}, [installRun, projectPath, report, startRun]);
|
|
3719
3918
|
React.useEffect(() => {
|
|
3720
|
-
if (
|
|
3919
|
+
if (workspacePath === null)
|
|
3721
3920
|
return undefined;
|
|
3722
3921
|
const controller = new AbortController();
|
|
3922
|
+
setResolving(true);
|
|
3923
|
+
setProjectsError(null);
|
|
3924
|
+
// A workspace switch must not leave the previous project's report on screen. A
|
|
3925
|
+
// refresh of the same workspace keeps it readable while the list is re-read.
|
|
3926
|
+
if (projectListFor !== workspacePath) {
|
|
3927
|
+
setRawReport(null);
|
|
3928
|
+
setRawMaintenance(null);
|
|
3929
|
+
setRawError(null);
|
|
3930
|
+
setReadAt(null);
|
|
3931
|
+
setReportProject(null);
|
|
3932
|
+
setActiveProject(null);
|
|
3933
|
+
}
|
|
3934
|
+
void (async () => {
|
|
3935
|
+
try {
|
|
3936
|
+
const res = await fetch((0, navigation_3.routeUrl)(`/api/gitnexus/projects?root=${encodeURIComponent(workspacePath)}`), { signal: controller.signal });
|
|
3937
|
+
const body = (await res.json().catch(() => null));
|
|
3938
|
+
if (!res.ok || body?.ok !== true || !Array.isArray(body.projects)) {
|
|
3939
|
+
throw new Error(body?.message ?? `读取项目列表失败 (${res.status})`);
|
|
3940
|
+
}
|
|
3941
|
+
if (controller.signal.aborted)
|
|
3942
|
+
return;
|
|
3943
|
+
const list = {
|
|
3944
|
+
root: workspacePath,
|
|
3945
|
+
projects: body.projects,
|
|
3946
|
+
visited: typeof body.visited === 'number' ? body.visited : 0,
|
|
3947
|
+
truncated: body.truncated === true,
|
|
3948
|
+
};
|
|
3949
|
+
setProjectList(list);
|
|
3950
|
+
setProjectListFor(workspacePath);
|
|
3951
|
+
const remembered = readRememberedProject(workspacePath);
|
|
3952
|
+
const next = chooseProject(list.projects, workspacePath, remembered);
|
|
3953
|
+
setActiveProject(next);
|
|
3954
|
+
if (remembered !== null && next !== remembered && list.truncated !== true) {
|
|
3955
|
+
// The remembered project disappeared; persist the fallback so the next open
|
|
3956
|
+
// does not look for it again. A truncated walk never proves it disappeared:
|
|
3957
|
+
// it may simply not have reached it before the bound, so keep the preference
|
|
3958
|
+
// and let a later complete resolve restore it.
|
|
3959
|
+
rememberProject(workspacePath, next);
|
|
3960
|
+
}
|
|
3961
|
+
setLoadToken((n) => n + 1);
|
|
3962
|
+
}
|
|
3963
|
+
catch (cause) {
|
|
3964
|
+
if (controller.signal.aborted)
|
|
3965
|
+
return;
|
|
3966
|
+
setProjectsError(String(cause?.message ?? cause));
|
|
3967
|
+
// First load: fall back to scanning the workspace. A refresh keeps the list
|
|
3968
|
+
// already on screen; a transient resolve failure should not remove it.
|
|
3969
|
+
if (projectListFor !== workspacePath) {
|
|
3970
|
+
setProjectList({ root: workspacePath, projects: [], visited: 0, truncated: false });
|
|
3971
|
+
setProjectListFor(workspacePath);
|
|
3972
|
+
setActiveProject(workspacePath);
|
|
3973
|
+
}
|
|
3974
|
+
setLoadToken((n) => n + 1);
|
|
3975
|
+
}
|
|
3976
|
+
finally {
|
|
3977
|
+
if (!controller.signal.aborted)
|
|
3978
|
+
setResolving(false);
|
|
3979
|
+
}
|
|
3980
|
+
})();
|
|
3981
|
+
return () => controller.abort();
|
|
3982
|
+
}, [workspacePath, reloadToken]);
|
|
3983
|
+
React.useEffect(() => {
|
|
3984
|
+
if (activeProject === null || workspacePath === null)
|
|
3985
|
+
return undefined;
|
|
3986
|
+
// Wait until the resolve has produced the list for this workspace. A manual
|
|
3987
|
+
// refresh bumps loadToken after the resolve, so coverage never reads the old
|
|
3988
|
+
// active project while the list is still being decided.
|
|
3989
|
+
if (projectListFor !== workspacePath || resolving)
|
|
3990
|
+
return undefined;
|
|
3991
|
+
const controller = new AbortController();
|
|
3992
|
+
// A project switch must not leave the previous report, maintenance payload or
|
|
3993
|
+
// read time under the new path. A refresh of the same project keeps them readable
|
|
3994
|
+
// while the new reading is fetched.
|
|
3995
|
+
if (reportProject !== activeProject) {
|
|
3996
|
+
setRawReport(null);
|
|
3997
|
+
setRawMaintenance(null);
|
|
3998
|
+
setRawError(null);
|
|
3999
|
+
setReadAt(null);
|
|
4000
|
+
}
|
|
3723
4001
|
setBusy(true);
|
|
3724
|
-
|
|
4002
|
+
setRawError(null);
|
|
4003
|
+
setReportProject(activeProject);
|
|
3725
4004
|
setProgress({ percent: 0, label: '连接索引服务…' });
|
|
3726
|
-
const query = `/api/gitnexus/coverage?repo=${encodeURIComponent(
|
|
4005
|
+
const query = `/api/gitnexus/coverage?repo=${encodeURIComponent(activeProject)}` +
|
|
3727
4006
|
(wantsFresh ? '&refresh=1' : '') +
|
|
3728
4007
|
'&stream=1';
|
|
3729
4008
|
const load = async () => {
|
|
@@ -3781,8 +4060,8 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3781
4060
|
throw new Error('读取中断:没有收到完整报告。');
|
|
3782
4061
|
if (controller.signal.aborted)
|
|
3783
4062
|
return;
|
|
3784
|
-
|
|
3785
|
-
|
|
4063
|
+
setRawReport(payload.report);
|
|
4064
|
+
setRawMaintenance(payload.maintenance ?? null);
|
|
3786
4065
|
setReadAt(typeof payload.readAt === 'string' ? payload.readAt : new Date().toISOString());
|
|
3787
4066
|
return;
|
|
3788
4067
|
}
|
|
@@ -3795,16 +4074,16 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3795
4074
|
// point of leading with the initialize command is that reaching it is
|
|
3796
4075
|
// expected on a project that has never been analysed.
|
|
3797
4076
|
if (res.status === 404 && body?.code === 'not-indexed') {
|
|
3798
|
-
|
|
3799
|
-
|
|
4077
|
+
setRawReport({ repo: { indexed: false, path: activeProject } });
|
|
4078
|
+
setRawMaintenance(null);
|
|
3800
4079
|
setReadAt(body?.readAt ?? new Date().toISOString());
|
|
3801
4080
|
return;
|
|
3802
4081
|
}
|
|
3803
4082
|
if (!body?.ok || body.report === undefined) {
|
|
3804
4083
|
throw new Error(body?.message ?? `请求失败 (${res.status})`);
|
|
3805
4084
|
}
|
|
3806
|
-
|
|
3807
|
-
|
|
4085
|
+
setRawReport(body.report);
|
|
4086
|
+
setRawMaintenance(body.maintenance ?? null);
|
|
3808
4087
|
setReadAt(typeof body.readAt === 'string' ? body.readAt : new Date().toISOString());
|
|
3809
4088
|
}
|
|
3810
4089
|
catch (cause) {
|
|
@@ -3813,7 +4092,7 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3813
4092
|
const error = cause;
|
|
3814
4093
|
if (error?.name === 'AbortError')
|
|
3815
4094
|
return;
|
|
3816
|
-
|
|
4095
|
+
setRawError(String(error?.message ?? cause));
|
|
3817
4096
|
}
|
|
3818
4097
|
finally {
|
|
3819
4098
|
if (!controller.signal.aborted) {
|
|
@@ -3824,7 +4103,7 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3824
4103
|
};
|
|
3825
4104
|
void load();
|
|
3826
4105
|
return () => controller.abort();
|
|
3827
|
-
}, [
|
|
4106
|
+
}, [activeProject, projectListFor, workspacePath, loadToken, resolving]);
|
|
3828
4107
|
/**
|
|
3829
4108
|
* Read the Tier 0 verdict whenever the project or the index changes.
|
|
3830
4109
|
*
|
|
@@ -3837,10 +4116,15 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3837
4116
|
* changed; there is no polling here for the same reason the run poll is conditional.
|
|
3838
4117
|
*/
|
|
3839
4118
|
React.useEffect(() => {
|
|
3840
|
-
|
|
4119
|
+
// A gate read for the previous project must not be shown under the new one. A
|
|
4120
|
+
// refresh of the same project keeps the readable verdict while it is re-read.
|
|
4121
|
+
if (deltaRepoRef.current !== projectPath) {
|
|
4122
|
+
deltaRepoRef.current = projectPath;
|
|
3841
4123
|
setDelta(null);
|
|
3842
4124
|
setDeltaError(null);
|
|
3843
4125
|
setDeltaRoute('unknown');
|
|
4126
|
+
}
|
|
4127
|
+
if (projectPath === null) {
|
|
3844
4128
|
return undefined;
|
|
3845
4129
|
}
|
|
3846
4130
|
const controller = new AbortController();
|
|
@@ -3878,11 +4162,6 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3878
4162
|
})();
|
|
3879
4163
|
return () => controller.abort();
|
|
3880
4164
|
}, [projectPath, reloadToken]);
|
|
3881
|
-
// A different project means the previous drill-down is another repository's
|
|
3882
|
-
// community: keeping the scope would ask the new project for an id that cannot exist.
|
|
3883
|
-
React.useEffect(() => {
|
|
3884
|
-
setGraphScope(null);
|
|
3885
|
-
}, [projectPath]);
|
|
3886
4165
|
/**
|
|
3887
4166
|
* Read the graph projection for the current scope.
|
|
3888
4167
|
*
|
|
@@ -3895,14 +4174,40 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3895
4174
|
*/
|
|
3896
4175
|
React.useEffect(() => {
|
|
3897
4176
|
if (projectPath === null) {
|
|
4177
|
+
graphRepoRef.current = null;
|
|
4178
|
+
setGraphProjection(null);
|
|
4179
|
+
setGraphError(null);
|
|
4180
|
+
setGraphLoading(false);
|
|
4181
|
+
setGraphScope(null);
|
|
4182
|
+
return undefined;
|
|
4183
|
+
}
|
|
4184
|
+
const projectChanged = graphRepoRef.current !== projectPath;
|
|
4185
|
+
if (projectChanged) {
|
|
4186
|
+
graphRepoRef.current = projectPath;
|
|
4187
|
+
setGraphProjection(null);
|
|
4188
|
+
setGraphError(null);
|
|
4189
|
+
// The previous scope is another repository's community. Clearing it makes the
|
|
4190
|
+
// next render fetch the overview instead of asking the new project for an id it
|
|
4191
|
+
// cannot have.
|
|
4192
|
+
setGraphScope(null);
|
|
4193
|
+
}
|
|
4194
|
+
if (!graphAvailable) {
|
|
3898
4195
|
setGraphProjection(null);
|
|
3899
4196
|
setGraphError(null);
|
|
4197
|
+
setGraphLoading(false);
|
|
4198
|
+
return undefined;
|
|
4199
|
+
}
|
|
4200
|
+
if (projectChanged && graphScope !== null) {
|
|
4201
|
+
// The setGraphScope(null) above re-runs this effect; do not also fire a request
|
|
4202
|
+
// for the old scope against the new project.
|
|
3900
4203
|
return undefined;
|
|
3901
4204
|
}
|
|
3902
4205
|
const controller = new AbortController();
|
|
3903
4206
|
setGraphLoading(true);
|
|
3904
|
-
|
|
3905
|
-
const
|
|
4207
|
+
setGraphError(null);
|
|
4208
|
+
const effectiveScope = projectChanged ? null : graphScope;
|
|
4209
|
+
const view = effectiveScope === null ? 'communities' : 'community';
|
|
4210
|
+
const suffix = effectiveScope === null ? '' : `&id=${encodeURIComponent(effectiveScope)}`;
|
|
3906
4211
|
void (async () => {
|
|
3907
4212
|
try {
|
|
3908
4213
|
const res = await fetch((0, navigation_3.routeUrl)(`/api/gitnexus/graph?repo=${encodeURIComponent(projectPath)}&view=${view}${suffix}`), { signal: controller.signal });
|
|
@@ -3940,7 +4245,7 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3940
4245
|
}
|
|
3941
4246
|
})();
|
|
3942
4247
|
return () => controller.abort();
|
|
3943
|
-
}, [projectPath, reloadToken, graphScope, graphReload]);
|
|
4248
|
+
}, [projectPath, reloadToken, graphScope, graphReload, graphAvailable]);
|
|
3944
4249
|
/**
|
|
3945
4250
|
* Open a symbol node, with the same fallbacks the search hits use.
|
|
3946
4251
|
*
|
|
@@ -3979,18 +4284,32 @@ define("views/panel", ["require", "exports", "react", "@deepseek-ai/dsh-client-u
|
|
|
3979
4284
|
setGraphError(`当前宿主没有可用的文件打开能力;位置:${text}`);
|
|
3980
4285
|
}
|
|
3981
4286
|
}, [sessionId]);
|
|
3982
|
-
const
|
|
4287
|
+
const selectProject = React.useCallback((path) => {
|
|
4288
|
+
if (workspacePath === null)
|
|
4289
|
+
return;
|
|
4290
|
+
setActiveProject(path);
|
|
4291
|
+
setProjectsError(null);
|
|
4292
|
+
rememberProject(workspacePath, path);
|
|
4293
|
+
}, [workspacePath]);
|
|
4294
|
+
const visibleRun = runProject === projectPath ? run : null;
|
|
4295
|
+
const visibleRunError = runProject === projectPath ? runError : null;
|
|
3983
4296
|
return (0, primitives_11.h)(CoverageView, {
|
|
3984
4297
|
report,
|
|
3985
|
-
maintenance
|
|
4298
|
+
maintenance,
|
|
3986
4299
|
projectPath,
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
4300
|
+
workspacePath,
|
|
4301
|
+
projects: visibleProjects,
|
|
4302
|
+
projectsVisited: projectsReady ? projectList?.visited ?? 0 : undefined,
|
|
4303
|
+
projectsTruncated: projectsReady ? projectList?.truncated === true : undefined,
|
|
4304
|
+
projectsError: projectsReady ? projectsError : null,
|
|
4305
|
+
onSelectProject: selectProject,
|
|
4306
|
+
context: workspacePath === null ? (0, navigation_3.contextDiagnostic)(props) : null,
|
|
4307
|
+
busy: busy || resolving,
|
|
4308
|
+
progress: resolving ? { percent: 0, label: '正在查找项目索引…' } : progress,
|
|
3990
4309
|
error,
|
|
3991
4310
|
readAt,
|
|
3992
|
-
run,
|
|
3993
|
-
runError,
|
|
4311
|
+
run: visibleRun,
|
|
4312
|
+
runError: visibleRunError,
|
|
3994
4313
|
delta,
|
|
3995
4314
|
deltaError,
|
|
3996
4315
|
deltaRoute,
|
|
@@ -4184,6 +4503,7 @@ define("index", ["require", "exports", "format", "navigation", "primitives", "se
|
|
|
4184
4503
|
compactNumber: format_11.compactNumber,
|
|
4185
4504
|
useProjectPath: navigation_4.useProjectPath,
|
|
4186
4505
|
contextDiagnostic: navigation_4.contextDiagnostic,
|
|
4506
|
+
chooseProject: panel_1.chooseProject,
|
|
4187
4507
|
CoveragePanel: panel_1.CoveragePanel,
|
|
4188
4508
|
CoverageView: panel_1.CoverageView,
|
|
4189
4509
|
CoverageTitle: panel_1.CoverageTitle,
|