@lesliechan721/aw 0.1.0-beta.4 → 0.1.0-beta.6
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/dist/authoring/scripts/lib/plugin-version-check.d.mts +14 -0
- package/dist/authoring/scripts/lib/plugin-version-check.mjs +10 -224
- package/dist/authoring/scripts/lib/plugin-version-check.mjs.map +1 -1
- package/dist/authoring/scripts/remove-plugin.d.mts +2 -0
- package/dist/authoring/scripts/remove-plugin.mjs +366 -0
- package/dist/authoring/scripts/remove-plugin.mjs.map +1 -0
- package/dist/authoring/scripts/validate-plugin.mjs +9 -46
- package/dist/authoring/scripts/validate-plugin.mjs.map +1 -1
- package/dist/scripts/bundle-plugins.d.ts +0 -2
- package/dist/scripts/bundle-plugins.js +4 -2
- package/dist/scripts/bundle-plugins.js.map +1 -1
- package/dist/scripts/plugin-staging.d.ts +1 -16
- package/dist/scripts/plugin-staging.js +8 -115
- package/dist/scripts/plugin-staging.js.map +1 -1
- package/dist/src/authoring/workspace-commands.d.ts +9 -1
- package/dist/src/authoring/workspace-commands.js +24 -16
- package/dist/src/authoring/workspace-commands.js.map +1 -1
- package/dist/src/commands/catalog-publication-inspection.d.ts +106 -0
- package/dist/src/commands/catalog-publication-inspection.js +444 -0
- package/dist/src/commands/catalog-publication-inspection.js.map +1 -0
- package/dist/src/commands/catalog-publish.d.ts +18 -2
- package/dist/src/commands/catalog-publish.js +261 -316
- package/dist/src/commands/catalog-publish.js.map +1 -1
- package/dist/src/commands/catalog-status.d.ts +60 -0
- package/dist/src/commands/catalog-status.js +251 -0
- package/dist/src/commands/catalog-status.js.map +1 -0
- package/dist/src/commands/plugin-capabilities.d.ts +22 -0
- package/dist/src/commands/plugin-capabilities.js +143 -0
- package/dist/src/commands/plugin-capabilities.js.map +1 -0
- package/dist/src/commands/rebuild-command.d.ts +2 -0
- package/dist/src/commands/rebuild-command.js +37 -6
- package/dist/src/commands/rebuild-command.js.map +1 -1
- package/dist/src/commands/rebuild-integration.d.ts +1 -0
- package/dist/src/commands/rebuild-integration.js +55 -57
- package/dist/src/commands/rebuild-integration.js.map +1 -1
- package/dist/src/commands/status-v4.d.ts +21 -0
- package/dist/src/commands/status-v4.js +63 -2
- package/dist/src/commands/status-v4.js.map +1 -1
- package/dist/src/index.js +79 -18
- package/dist/src/index.js.map +1 -1
- package/dist/src/tui/components.d.ts +70 -0
- package/dist/src/tui/components.js +149 -6
- package/dist/src/tui/components.js.map +1 -1
- package/dist/src/tui/console-app-v4.js +1660 -628
- package/dist/src/tui/console-app-v4.js.map +1 -1
- package/dist/src/tui/controller-runtime.d.ts +5 -1
- package/dist/src/tui/controller-runtime.js +184 -33
- package/dist/src/tui/controller-runtime.js.map +1 -1
- package/dist/src/tui/controller.d.ts +35 -2
- package/dist/src/tui/controller.js +300 -39
- package/dist/src/tui/controller.js.map +1 -1
- package/dist/src/tui/diagnostics.d.ts +31 -0
- package/dist/src/tui/diagnostics.js +71 -0
- package/dist/src/tui/diagnostics.js.map +1 -0
- package/dist/src/tui/index.d.ts +5 -3
- package/dist/src/tui/index.js +3 -1
- package/dist/src/tui/index.js.map +1 -1
- package/dist/src/tui/model.d.ts +33 -4
- package/dist/src/tui/model.js +252 -17
- package/dist/src/tui/model.js.map +1 -1
- package/dist/src/tui/router.d.ts +15 -9
- package/dist/src/tui/router.js +35 -0
- package/dist/src/tui/router.js.map +1 -1
- package/dist/src/tui/theme.d.ts +18 -10
- package/dist/src/tui/theme.js +18 -10
- package/dist/src/tui/theme.js.map +1 -1
- package/dist/src/tui/viewport.d.ts +20 -0
- package/dist/src/tui/viewport.js +29 -0
- package/dist/src/tui/viewport.js.map +1 -0
- package/dist/src/utils/catalog-runtime-projection.d.ts +1 -7
- package/dist/src/utils/catalog-runtime-projection.js +2 -62
- package/dist/src/utils/catalog-runtime-projection.js.map +1 -1
- package/dist/src/utils/catalog-source-cache.d.ts +12 -11
- package/dist/src/utils/catalog-source-cache.js +20 -0
- package/dist/src/utils/catalog-source-cache.js.map +1 -1
- package/dist/src/utils/dynamic-skill-tree.js +1 -1
- package/dist/src/utils/dynamic-skill-tree.js.map +1 -1
- package/dist/src/utils/git-catalog-provider.js +1 -1
- package/dist/src/utils/git-catalog-provider.js.map +1 -1
- package/dist/src/utils/manifest.d.ts +0 -1
- package/dist/src/utils/manifest.js +0 -1
- package/dist/src/utils/manifest.js.map +1 -1
- package/dist/src/utils/plugin-config.js +28 -30
- package/dist/src/utils/plugin-config.js.map +1 -1
- package/dist/src/utils/skill-validation.d.ts +1 -1
- package/dist/src/utils/skill-validation.js +1 -1
- package/dist/src/utils/skill-validation.js.map +1 -1
- package/dist/src/utils/state-paths.d.ts +1 -0
- package/dist/src/utils/state-paths.js +5 -0
- package/dist/src/utils/state-paths.js.map +1 -1
- package/package.json +3 -4
- package/dist/src/utils/plugin-authoring-capabilities.d.ts +0 -23
- package/dist/src/utils/plugin-authoring-capabilities.js +0 -37
- package/dist/src/utils/plugin-authoring-capabilities.js.map +0 -1
- package/dist/src/utils/plugin-authoring-capabilities.json +0 -92
- package/dist/src/utils/skill-frontmatter-capabilities.d.ts +0 -201
- package/dist/src/utils/skill-frontmatter-capabilities.js +0 -5
- package/dist/src/utils/skill-frontmatter-capabilities.js.map +0 -1
- package/dist/src/utils/skill-frontmatter-capabilities.json +0 -200
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text, useInput, useWindowSize } from 'ink';
|
|
3
3
|
import { useEffect, useRef, useState } from 'react';
|
|
4
4
|
import path from 'node:path';
|
|
@@ -6,11 +6,13 @@ import { CATALOG_SOURCE_ID_PATTERN } from '../utils/catalog-sources.js';
|
|
|
6
6
|
import { canonicalizeCatalogGitUrl } from '../utils/catalog-source-identity.js';
|
|
7
7
|
import { normalizePortableAgents } from '../portable/commands.js';
|
|
8
8
|
import { projectCatalogSourceLocation } from '../utils/catalog-source-presentation.js';
|
|
9
|
-
import { ScreenFrame } from './components.js';
|
|
9
|
+
import { ActionRow, Callout, ChoiceRow, DiagnosticSummary, InputCard, PluginListRow, ProgressRow, ResultCard, ResultRow, ReviewCard, ReviewChangeRow, ScreenFrame, SectionCard, SectionRow, StaticRow, StatusCard, TaskRow, TechnicalDetails, ToggleRow, } from './components.js';
|
|
10
|
+
import { safeDiagnosticCode, sanitizeDiagnosticText, } from './diagnostics.js';
|
|
10
11
|
import { extractPortableError, userIssueKey } from './controller.js';
|
|
11
12
|
import { resolveTuiSizePolicy } from './layout.js';
|
|
12
|
-
import { hasInstalledPlacement } from './model.js';
|
|
13
|
+
import { buildPlatformActions, buildPluginDetailActions, buildPluginPreviewActions, hasInstalledPlacement, } from './model.js';
|
|
13
14
|
import { tuiTheme } from './theme.js';
|
|
15
|
+
import { contentRowBudget, rowViewport } from './viewport.js';
|
|
14
16
|
export const CONSOLE_V4_SCREEN_IDS = [
|
|
15
17
|
'G01', 'G02', 'G03', 'G04', 'G05',
|
|
16
18
|
'P01', 'P02', 'P03', 'P04', 'P05', 'P06', 'P07', 'P08', 'P09', 'P10',
|
|
@@ -25,6 +27,49 @@ export const CONSOLE_V4_SCREEN_IDS = [
|
|
|
25
27
|
'D10', 'D11', 'D12', 'D13', 'D14', 'D15', 'D16', 'D17',
|
|
26
28
|
'U01', 'U02', 'U03', 'U04', 'U05', 'U06', 'U07', 'U08', 'U09', 'U10', 'U11',
|
|
27
29
|
];
|
|
30
|
+
function pluginMenuActionId(action) {
|
|
31
|
+
return action.id === 'platform' || action.id === 'scopeConflict'
|
|
32
|
+
? `${action.id}:${action.platform}`
|
|
33
|
+
: action.id;
|
|
34
|
+
}
|
|
35
|
+
function isCharacterInputMode(route, searchMode) {
|
|
36
|
+
return searchMode
|
|
37
|
+
|| route.name === 'portableAgentInput'
|
|
38
|
+
|| route.name === 'configFieldEditor' && route.editor !== 'enum' && route.editor !== 'boolean'
|
|
39
|
+
|| route.name === 'sourceAdd' && route.step !== 'type' && route.step !== 'refPolicy';
|
|
40
|
+
}
|
|
41
|
+
function helpRowsForRoute(route, actionCount, supportsTechnicalDetails, busy) {
|
|
42
|
+
const rows = [];
|
|
43
|
+
if (!busy && actionCount > 1)
|
|
44
|
+
rows.push(['↑/↓ · j/k', '移动当前唯一焦点']);
|
|
45
|
+
if (!busy && actionCount > 0)
|
|
46
|
+
rows.push(['Enter', '打开、选择或继续']);
|
|
47
|
+
if (!busy && route.name !== 'home')
|
|
48
|
+
rows.push(['Esc / b', '返回上一页']);
|
|
49
|
+
if (route.name === 'pluginList') {
|
|
50
|
+
rows.push(['←/→ · [ / ]', '切换来源']);
|
|
51
|
+
rows.push(['/', '进入搜索']);
|
|
52
|
+
if (route.mode === 'browse')
|
|
53
|
+
rows.push(['f', '切换全部或未安装']);
|
|
54
|
+
}
|
|
55
|
+
if (route.name === 'portableSkillSelect') {
|
|
56
|
+
rows.push(['Space', '切换当前 Skill']);
|
|
57
|
+
rows.push(['a', '全选 Skills']);
|
|
58
|
+
}
|
|
59
|
+
if (route.name === 'configFields' && route.layer.scope !== 'effective') {
|
|
60
|
+
rows.push(['s', '保存设置']);
|
|
61
|
+
rows.push(['u', '恢复当前字段继承']);
|
|
62
|
+
}
|
|
63
|
+
if (route.name === 'sourceList' || route.name === 'sourceDetail') {
|
|
64
|
+
rows.push(['r', '重新检查来源']);
|
|
65
|
+
}
|
|
66
|
+
if (supportsTechnicalDetails) {
|
|
67
|
+
rows.push(['d', route.name === 'progress' ? '打开技术进度' : '打开技术详情']);
|
|
68
|
+
}
|
|
69
|
+
rows.push(['q / Ctrl-C', busy ? '操作完成后退出' : '退出控制台']);
|
|
70
|
+
rows.push(['?', '关闭帮助']);
|
|
71
|
+
return rows;
|
|
72
|
+
}
|
|
28
73
|
function configOwnerKey(route) {
|
|
29
74
|
return `${route.pluginId}\0${route.layer.scope}\0${route.layer.projectLayer}`;
|
|
30
75
|
}
|
|
@@ -67,18 +112,27 @@ function statusText(plugin) {
|
|
|
67
112
|
unavailable: '来源不可用',
|
|
68
113
|
}[plugin.aggregateStatus];
|
|
69
114
|
}
|
|
70
|
-
function
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return
|
|
81
|
-
|
|
115
|
+
function pluginPlacementSummary(plugin) {
|
|
116
|
+
return plugin.supportedPlatforms.map((platform) => {
|
|
117
|
+
const label = platformLabel(platform);
|
|
118
|
+
const placement = plugin.installedPlacements.find((entry) => entry.platform === platform);
|
|
119
|
+
if (!placement || placement.state === 'notInstalled')
|
|
120
|
+
return `${label} · 未添加`;
|
|
121
|
+
if (placement.state === 'scopeConflict')
|
|
122
|
+
return `${label} · 位置冲突`;
|
|
123
|
+
const health = placement.health === 'ready' ? '可用'
|
|
124
|
+
: placement.health === 'incomplete' ? '需要处理' : '不可用';
|
|
125
|
+
return `${label} · ${scopeLabel(placement.scope)} · ${health}`;
|
|
126
|
+
}).join(' | ') || '没有可用平台';
|
|
127
|
+
}
|
|
128
|
+
function pluginRowStatus(plugin) {
|
|
129
|
+
if (plugin.aggregateStatus === 'needsAttention')
|
|
130
|
+
return 'warning';
|
|
131
|
+
if (plugin.aggregateStatus === 'installed')
|
|
132
|
+
return 'success';
|
|
133
|
+
if (plugin.aggregateStatus === 'unavailable')
|
|
134
|
+
return 'error';
|
|
135
|
+
return 'muted';
|
|
82
136
|
}
|
|
83
137
|
function moveFocus(actions, current, direction) {
|
|
84
138
|
if (actions.length === 0)
|
|
@@ -90,67 +144,82 @@ function moveFocus(actions, current, direction) {
|
|
|
90
144
|
}
|
|
91
145
|
return current;
|
|
92
146
|
}
|
|
93
|
-
function
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
147
|
+
function moveIndex(count, current, direction) {
|
|
148
|
+
if (count === 0)
|
|
149
|
+
return 0;
|
|
150
|
+
return (current + direction + count) % count;
|
|
151
|
+
}
|
|
152
|
+
function actionViewport(actions, compact, focus, requestedOffset, lineBudget) {
|
|
153
|
+
return rowViewport({
|
|
154
|
+
rowHeights: actions.map((action) => compact || (!action.description && !action.disabled) ? 1 : 2),
|
|
155
|
+
focus,
|
|
156
|
+
requestedOffset,
|
|
157
|
+
rowBudget: lineBudget,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function portableSkillCandidateViewport(actions, candidates, compact, focus, requestedOffset, rowBudget) {
|
|
161
|
+
const candidateFocus = focus < candidates.length ? focus : requestedOffset;
|
|
162
|
+
return groupedRowViewport({
|
|
163
|
+
rowHeights: actions.map(() => compact ? 1 : 2),
|
|
164
|
+
groupKeys: candidates.map((candidate) => candidate.providerPluginId),
|
|
165
|
+
groupOverhead: 3,
|
|
166
|
+
focus: candidateFocus,
|
|
167
|
+
requestedOffset,
|
|
168
|
+
rowBudget,
|
|
169
|
+
});
|
|
104
170
|
}
|
|
105
|
-
function
|
|
171
|
+
function groupedRowViewport(options) {
|
|
172
|
+
const total = options.rowHeights.length;
|
|
173
|
+
if (total === 0) {
|
|
174
|
+
return { offset: 0, limit: 0, hiddenBefore: 0, hiddenAfter: 0, position: 0, total: 0 };
|
|
175
|
+
}
|
|
176
|
+
const focus = Math.min(Math.max(0, options.focus), total - 1);
|
|
106
177
|
const height = (start, end) => {
|
|
107
|
-
let
|
|
178
|
+
let rows = 0;
|
|
108
179
|
let previousGroup;
|
|
109
180
|
for (let index = start; index < end; index += 1) {
|
|
110
|
-
const group = groupKeys
|
|
111
|
-
if (group && group !== previousGroup)
|
|
112
|
-
|
|
113
|
-
|
|
181
|
+
const group = options.groupKeys[index];
|
|
182
|
+
if (group !== undefined && group !== previousGroup)
|
|
183
|
+
rows += options.groupOverhead;
|
|
184
|
+
rows += options.rowHeights[index] ?? 1;
|
|
114
185
|
previousGroup = group;
|
|
115
186
|
}
|
|
116
|
-
return
|
|
187
|
+
return rows;
|
|
117
188
|
};
|
|
118
|
-
let offset = Math.min(
|
|
119
|
-
|
|
120
|
-
offset = focus;
|
|
121
|
-
while (offset < focus && height(offset, focus + 1) > lineBudget)
|
|
189
|
+
let offset = Math.min(Math.max(0, options.requestedOffset), focus);
|
|
190
|
+
while (offset < focus && height(offset, focus + 1) > options.rowBudget)
|
|
122
191
|
offset += 1;
|
|
123
|
-
let end =
|
|
124
|
-
while (end <
|
|
192
|
+
let end = focus + 1;
|
|
193
|
+
while (end < total && height(offset, end + 1) <= options.rowBudget)
|
|
125
194
|
end += 1;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
: routeName === 'configFields' || routeName === 'review' || routeName === 'issueDetail' ? 7 : undefined;
|
|
137
|
-
if (limit === undefined)
|
|
138
|
-
return { offset: 0 };
|
|
139
|
-
const maximumOffset = Math.max(0, actions.length - limit);
|
|
140
|
-
let offset = Math.min(requestedOffset, maximumOffset);
|
|
141
|
-
if (focus < offset)
|
|
142
|
-
offset = focus;
|
|
143
|
-
else if (focus >= offset + limit)
|
|
144
|
-
offset = focus - limit + 1;
|
|
145
|
-
return { limit, offset: Math.max(0, offset) };
|
|
195
|
+
while (offset > 0 && height(offset - 1, end) <= options.rowBudget)
|
|
196
|
+
offset -= 1;
|
|
197
|
+
return {
|
|
198
|
+
offset,
|
|
199
|
+
limit: end - offset,
|
|
200
|
+
hiddenBefore: offset,
|
|
201
|
+
hiddenAfter: total - end,
|
|
202
|
+
position: focus + 1,
|
|
203
|
+
total,
|
|
204
|
+
};
|
|
146
205
|
}
|
|
147
|
-
function
|
|
148
|
-
|
|
149
|
-
|
|
206
|
+
function prefixRowLimit(rowHeights, rowBudget) {
|
|
207
|
+
let used = 0;
|
|
208
|
+
let visible = 0;
|
|
209
|
+
for (const rowHeight of rowHeights) {
|
|
210
|
+
const hiddenSummary = visible + 1 < rowHeights.length ? 1 : 0;
|
|
211
|
+
if (used + rowHeight + hiddenSummary > rowBudget)
|
|
212
|
+
break;
|
|
213
|
+
used += rowHeight;
|
|
214
|
+
visible += 1;
|
|
215
|
+
}
|
|
216
|
+
return visible;
|
|
150
217
|
}
|
|
151
218
|
function portableScopeVariantsDiffer(candidate) {
|
|
152
219
|
const project = candidate.scopeDetails.project;
|
|
153
220
|
const user = candidate.scopeDetails.user;
|
|
221
|
+
if (Boolean(project) !== Boolean(user))
|
|
222
|
+
return true;
|
|
154
223
|
return Boolean(project && user && (project.description !== user.description
|
|
155
224
|
|| project.providerPluginId !== user.providerPluginId
|
|
156
225
|
|| project.providerDisplayName !== user.providerDisplayName
|
|
@@ -159,7 +228,7 @@ function portableScopeVariantsDiffer(candidate) {
|
|
|
159
228
|
}
|
|
160
229
|
function portableScopeVariantLabel(scope, detail) {
|
|
161
230
|
if (!detail)
|
|
162
|
-
return ''
|
|
231
|
+
return `${scope === 'project' ? '当前项目' : '所有项目'}:不可用`;
|
|
163
232
|
return `${scope === 'project' ? '当前项目' : '所有项目'}:${detail.description} · ${detail.providerDisplayName}${detail.dynamic ? ' · 按设置生成' : ''}`;
|
|
164
233
|
}
|
|
165
234
|
function privatePathInput(value) {
|
|
@@ -168,49 +237,172 @@ function privatePathInput(value) {
|
|
|
168
237
|
const basename = value.split(/[\\/]/u).filter(Boolean).at(-1) ?? '已选择';
|
|
169
238
|
return `已选择 / … / ${basename}`;
|
|
170
239
|
}
|
|
171
|
-
function
|
|
240
|
+
function portableBlockerDiagnostic(route) {
|
|
241
|
+
const cliUnavailable = route.code === 'portableSkillsCliMissing'
|
|
242
|
+
|| route.code === 'portableSkillsCliIncompatible';
|
|
243
|
+
const userMessage = route.code === 'requiredConfigMissing'
|
|
244
|
+
? '插件缺少生成内容所需的设置。'
|
|
245
|
+
: route.code === 'portableScopeUnavailable'
|
|
246
|
+
? '至少一个所选 Skill 不适用于这个范围。'
|
|
247
|
+
: cliUnavailable
|
|
248
|
+
? '没有找到兼容的 Skills CLI。'
|
|
249
|
+
: '候选 Skill 没有通过 Portable 安全校验。';
|
|
250
|
+
const blocked = route.code === 'requiredConfigMissing'
|
|
251
|
+
? '缺少生成 Skill 所需的设置;没有调用 Skills CLI。'
|
|
252
|
+
: cliUnavailable
|
|
253
|
+
? '当前不能开始安装;没有调用 Skills CLI。'
|
|
254
|
+
: route.code === 'portableScopeUnavailable'
|
|
255
|
+
? '所选内容不能在这个范围完整生成;没有开始写入。'
|
|
256
|
+
: '候选内容没有通过安全校验;没有开始写入。';
|
|
257
|
+
const nextStep = route.code === 'requiredConfigMissing'
|
|
258
|
+
? '完成插件设置,然后重新准备。'
|
|
259
|
+
: cliUnavailable
|
|
260
|
+
? '安装或升级兼容的 Skills CLI,然后重新检查。'
|
|
261
|
+
: route.code === 'portableScopeUnavailable'
|
|
262
|
+
? '返回 Skill 选择或更改范围,然后重新检查。'
|
|
263
|
+
: '修复来源内容或修改选择,然后重新检查。';
|
|
264
|
+
return {
|
|
265
|
+
severity: 'error',
|
|
266
|
+
title: route.code === 'portableScopeUnavailable' ? '所选范围不可用'
|
|
267
|
+
: cliUnavailable ? '暂时无法安装通用 Skills' : '无法准备通用 Skills',
|
|
268
|
+
userMessage,
|
|
269
|
+
impact: [
|
|
270
|
+
{ state: 'blocked', message: blocked },
|
|
271
|
+
{ state: 'preserved', message: '现有插件、Skills、设置和 Portable 草稿保持不变。' },
|
|
272
|
+
],
|
|
273
|
+
nextStep,
|
|
274
|
+
technical: {
|
|
275
|
+
code: safeDiagnosticCode(route.diagnosticCode, 'portableValidationFailed'),
|
|
276
|
+
stage: cliUnavailable ? 'check-skills-cli' : 'prepare-portable',
|
|
277
|
+
...(cliUnavailable ? { context: [{
|
|
278
|
+
key: 'compatibility',
|
|
279
|
+
value: 'Skills CLI >=1.5.23 <2.0.0',
|
|
280
|
+
}] } : {}),
|
|
281
|
+
checks: cliUnavailable
|
|
282
|
+
? ['检查 PATH 中的 Skills CLI 版本。', '安装兼容版本后运行“重新检查”。']
|
|
283
|
+
: ['检查插件设置、所选范围和 Portable 来源内容。', '修复后运行“重新检查”。'],
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function configUnavailableDiagnostic(route) {
|
|
288
|
+
return {
|
|
289
|
+
severity: 'error',
|
|
290
|
+
title: 'Manifest 或来源不可用',
|
|
291
|
+
userMessage: '当前无法取得可信的字段定义,因此不能验证新值。',
|
|
292
|
+
impact: [
|
|
293
|
+
{ state: 'blocked', message: '恢复可信字段定义前,不能编辑或重置这个插件的设置。' },
|
|
294
|
+
{ state: 'preserved', message: '已保存的设置保持不变。' },
|
|
295
|
+
],
|
|
296
|
+
nextStep: '重新检查来源,然后再次打开设置。',
|
|
297
|
+
technical: {
|
|
298
|
+
code: safeDiagnosticCode(route.diagnosticCode, 'configUnavailable'),
|
|
299
|
+
stage: safeDiagnosticCode(route.diagnosticStage, 'read-config'),
|
|
300
|
+
context: [
|
|
301
|
+
{ key: 'pluginId', value: route.pluginId },
|
|
302
|
+
...(route.layer ? [{ key: 'scope', value: route.layer.scope }] : []),
|
|
303
|
+
],
|
|
304
|
+
checks: ['恢复插件来源和可信 Manifest。', '重新检查来源后再次打开设置。'],
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
function sourceDiagnostic(sourceId, state, detail) {
|
|
309
|
+
const degraded = state === 'degraded';
|
|
310
|
+
const unavailable = state === 'unavailable' || state === 'error';
|
|
311
|
+
if (!degraded && !unavailable)
|
|
312
|
+
return undefined;
|
|
313
|
+
return {
|
|
314
|
+
severity: degraded ? 'warning' : 'error',
|
|
315
|
+
title: degraded ? '远端 Catalog 检查失败,正在使用缓存' : 'Catalog 来源不可用',
|
|
316
|
+
userMessage: degraded
|
|
317
|
+
? '远端读取没有成功。当前使用匹配且已验证的缓存数据。'
|
|
318
|
+
: '当前无法读取这个来源,并且没有可用缓存数据。',
|
|
319
|
+
impact: degraded
|
|
320
|
+
? [
|
|
321
|
+
{ state: 'degraded', message: '数据可能不是最新;匹配缓存中的插件仍可浏览和安装。' },
|
|
322
|
+
{ state: 'preserved', message: '已安装插件和已保存设置不会被删除。' },
|
|
323
|
+
]
|
|
324
|
+
: [
|
|
325
|
+
{ state: 'blocked', message: '当前不能浏览或安装这个来源中的插件。' },
|
|
326
|
+
{ state: 'preserved', message: '已安装插件和已保存设置不会被删除。' },
|
|
327
|
+
],
|
|
328
|
+
nextStep: degraded
|
|
329
|
+
? '重新检查来源;在修复前也可以继续使用匹配缓存。'
|
|
330
|
+
: '重新检查来源;如仍失败,请编辑来源 URL、路径或 ref。',
|
|
331
|
+
technical: {
|
|
332
|
+
code: safeDiagnosticCode(detail?.errorCode, degraded ? 'catalogSourceDegraded' : 'catalogSourceUnavailable'),
|
|
333
|
+
stage: 'resolve-source',
|
|
334
|
+
sourceId,
|
|
335
|
+
...(detail?.requestedRef ? { requestedRef: detail.requestedRef } : {}),
|
|
336
|
+
...(detail?.resolvedCommit ? { resolvedCommit: detail.resolvedCommit } : {}),
|
|
337
|
+
lastGood: degraded ? 'available' : 'unavailable',
|
|
338
|
+
...(detail?.freshness ? { context: [{ key: 'freshness', value: detail.freshness }] } : {}),
|
|
339
|
+
checks: ['检查 Git URL、ref 或本地目录。', '确认当前终端具备来源授权。', '运行“重新检查来源”验证修复。'],
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
function terminalTextWidth(value) {
|
|
344
|
+
return [...value].reduce((width, character) => width + (character.codePointAt(0) > 0xff ? 2 : 1), 0);
|
|
345
|
+
}
|
|
346
|
+
function SemanticRows(props) {
|
|
172
347
|
const offset = props.offset ?? 0;
|
|
173
348
|
const actions = props.limit === undefined
|
|
174
|
-
? props.actions
|
|
349
|
+
? props.actions.slice(offset)
|
|
175
350
|
: props.actions.slice(offset, offset + props.limit);
|
|
176
351
|
return (_jsx(Box, { flexDirection: "column", children: actions.map((action, visibleIndex) => {
|
|
177
352
|
const index = visibleIndex + offset;
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
353
|
+
const rowProps = {
|
|
354
|
+
title: action.label,
|
|
355
|
+
description: action.description,
|
|
356
|
+
meta: action.meta ?? (props.compact ? action.disabled : undefined),
|
|
357
|
+
status: action.status,
|
|
358
|
+
disabled: action.disabled,
|
|
359
|
+
focused: index === props.focus,
|
|
360
|
+
compact: props.compact,
|
|
361
|
+
};
|
|
362
|
+
if (props.kind === 'section')
|
|
363
|
+
return _jsx(SectionRow, { ...rowProps }, `${action.label}:${index}`);
|
|
364
|
+
if (props.kind === 'choice') {
|
|
365
|
+
return _jsx(ChoiceRow, { ...rowProps, selected: Boolean(action.selected) }, `${action.label}:${index}`);
|
|
366
|
+
}
|
|
367
|
+
if (props.kind === 'toggle') {
|
|
368
|
+
return _jsx(ToggleRow, { ...rowProps, selected: Boolean(action.selected) }, `${action.label}:${index}`);
|
|
369
|
+
}
|
|
370
|
+
if (props.kind === 'action')
|
|
371
|
+
return _jsx(ActionRow, { ...rowProps }, `${action.label}:${index}`);
|
|
372
|
+
return _jsx(TaskRow, { ...rowProps }, `${action.label}:${index}`);
|
|
182
373
|
}) }));
|
|
183
374
|
}
|
|
184
|
-
function
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
.filter((index) => index >= props.offset && index < end);
|
|
188
|
-
return _jsx(Box, { flexDirection: "column", children: indexes.map((index) => {
|
|
189
|
-
const candidate = props.candidates[index];
|
|
190
|
-
const previous = props.candidates[index - 1];
|
|
191
|
-
const showHeading = candidate && (index === props.offset
|
|
192
|
-
|| candidate.providerPluginId !== previous?.providerPluginId);
|
|
193
|
-
const heading = candidate?.relation === 'requested'
|
|
194
|
-
? '当前插件'
|
|
195
|
-
: candidate ? `来自依赖插件 · ${candidate.providerDisplayName}` : undefined;
|
|
196
|
-
return _jsxs(Box, { flexDirection: "column", children: [showHeading && heading ? _jsx(Text, { bold: true, color: tuiTheme.colors.muted, children: heading }) : null, _jsx(ActionRows, { actions: props.actions, focus: props.focus, offset: index, limit: 1 })] }, index);
|
|
197
|
-
}) });
|
|
375
|
+
function PluginRows(props) {
|
|
376
|
+
return (_jsx(Box, { flexDirection: "column", children: props.rows.slice(props.viewport.offset, props.viewport.offset + props.viewport.limit)
|
|
377
|
+
.map((row, visibleIndex) => (_jsx(PluginListRow, { name: row.name, sourceLabel: row.sourceLabel, description: row.description, placementSummary: row.placementSummary, statusLabel: row.statusLabel, status: row.status, focused: props.viewport.offset + visibleIndex === props.focus }, row.pluginId))) }));
|
|
198
378
|
}
|
|
199
|
-
function
|
|
200
|
-
|
|
379
|
+
function IssueTechnicalDetails(props) {
|
|
380
|
+
const visible = props.details.slice(0, props.compact ? 1 : 2);
|
|
381
|
+
if (visible.length === 0)
|
|
382
|
+
return null;
|
|
383
|
+
const hidden = props.details.length - visible.length;
|
|
384
|
+
return _jsx(TechnicalDetails, { children: visible.flatMap((detail, index) => [
|
|
385
|
+
_jsx(StaticRow, { title: detail.code, compact: true }, `${detail.code}:${index}:code`),
|
|
386
|
+
_jsx(StaticRow, { title: detail.message, meta: index === visible.length - 1 && hidden > 0 ? `另有 ${hidden} 项` : undefined, compact: true }, `${detail.code}:${index}:message`),
|
|
387
|
+
]) });
|
|
201
388
|
}
|
|
202
389
|
function SourceTabs(props) {
|
|
203
|
-
const
|
|
390
|
+
const filter = { mode: props.mode, query: props.query, installFilter: props.installFilter };
|
|
391
|
+
const tabs = [{ id: 'all', label: '全部', count: props.controller.listPlugins(filter).length },
|
|
204
392
|
...props.controller.listSources().map((source) => ({
|
|
205
393
|
...source,
|
|
206
|
-
count: props.controller.listPlugins({
|
|
394
|
+
count: props.controller.listPlugins({ ...filter, sourceId: source.id }).length,
|
|
207
395
|
}))];
|
|
208
396
|
const index = Math.max(0, tabs.findIndex((tab) => tab.id === props.current));
|
|
209
|
-
|
|
397
|
+
const fullText = `来源 ${tabs.map((tab) => {
|
|
398
|
+
const state = 'state' in tab ? tab.state : 'ready';
|
|
399
|
+
return `${tab.id === props.current ? '[' : ''}${tab.label}${state === 'error' ? ' !' : ` ${tab.count ?? ''}`}${tab.id === props.current ? ']' : ''} `;
|
|
400
|
+
}).join('')}`;
|
|
401
|
+
if (props.compact || terminalTextWidth(fullText) > props.columns) {
|
|
210
402
|
const tab = tabs[index];
|
|
211
|
-
return _jsxs(Text, { children: ["\u6765\u6E90 ", _jsxs(Text, { color: tuiTheme.colors.accent, children: ["[", tab.label, "]"] }), " ", index + 1, " / ", tabs.length, " \u2190 \u2192"] });
|
|
403
|
+
return _jsxs(Text, { wrap: "truncate-end", children: ["\u6765\u6E90 ", _jsxs(Text, { color: tuiTheme.colors.accent, children: ["[", tab.label, "]"] }), " ", index + 1, " / ", tabs.length, " \u2190 \u2192"] });
|
|
212
404
|
}
|
|
213
|
-
return (_jsxs(Text, { children: ["\u6765\u6E90 ", tabs.map((tab) => {
|
|
405
|
+
return (_jsxs(Text, { wrap: "truncate-end", children: ["\u6765\u6E90 ", tabs.map((tab) => {
|
|
214
406
|
const active = tab.id === props.current;
|
|
215
407
|
const state = 'state' in tab ? tab.state : 'ready';
|
|
216
408
|
return (_jsxs(Text, { bold: active, color: active ? tuiTheme.colors.accent
|
|
@@ -218,6 +410,31 @@ function SourceTabs(props) {
|
|
|
218
410
|
: state === 'cache' ? tuiTheme.statusColors.warning : tuiTheme.colors.muted, children: [active ? '[' : '', tab.label, state === 'error' ? ' !' : ` ${tab.count ?? ''}`, active ? ']' : '', ' '] }, tab.id));
|
|
219
411
|
})] }));
|
|
220
412
|
}
|
|
413
|
+
function configTabs(page) {
|
|
414
|
+
return [
|
|
415
|
+
...page.groups.map((group) => ({
|
|
416
|
+
id: group.id,
|
|
417
|
+
label: group.title,
|
|
418
|
+
count: group.fields.length,
|
|
419
|
+
kind: 'group',
|
|
420
|
+
})),
|
|
421
|
+
...(page.presets.length > 0
|
|
422
|
+
? [{ id: '@presets', label: '快速方案', count: page.presets.length, kind: 'presets' }]
|
|
423
|
+
: []),
|
|
424
|
+
];
|
|
425
|
+
}
|
|
426
|
+
function ConfigGroupTabs(props) {
|
|
427
|
+
const index = Math.max(0, props.tabs.findIndex((tab) => tab.id === props.current));
|
|
428
|
+
const fullText = `分组 ${props.tabs.map((tab) => `${tab.id === props.current ? '[' : ''}${tab.label} ${tab.count}${tab.id === props.current ? ']' : ''} `).join('')}`;
|
|
429
|
+
if (props.compact || terminalTextWidth(fullText) > props.columns) {
|
|
430
|
+
const tab = props.tabs[index];
|
|
431
|
+
return _jsxs(Text, { wrap: "truncate-end", children: ["\u5206\u7EC4 ", _jsxs(Text, { color: tuiTheme.colors.accent, children: ["[", tab.label, " ", tab.count, "]"] }), " ", index + 1, " / ", props.tabs.length, " \u2190 \u2192"] });
|
|
432
|
+
}
|
|
433
|
+
return (_jsxs(Text, { wrap: "truncate-end", children: ["\u5206\u7EC4 ", props.tabs.map((tab) => {
|
|
434
|
+
const active = tab.id === props.current;
|
|
435
|
+
return _jsxs(Text, { bold: active, color: active ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: [active ? '[' : '', tab.label, " ", tab.count, active ? ']' : '', ' '] }, tab.id);
|
|
436
|
+
}), "\u2190 \u2192"] }));
|
|
437
|
+
}
|
|
221
438
|
function placementStatus(placement) {
|
|
222
439
|
if (placement.state === 'notInstalled') {
|
|
223
440
|
return { value: '未添加', status: 'muted', description: '选择使用位置后添加' };
|
|
@@ -241,15 +458,14 @@ function dependencyStatus(item) {
|
|
|
241
458
|
return { value: '不可用', status: 'error' };
|
|
242
459
|
}
|
|
243
460
|
function dependencyPlacementLabel(item) {
|
|
244
|
-
const
|
|
245
|
-
if (
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
return dependencyStatus(item).value;
|
|
461
|
+
const aggregate = dependencyStatus(item);
|
|
462
|
+
if (item.aggregateStatus !== 'ready')
|
|
463
|
+
return aggregate.value;
|
|
464
|
+
const ready = item.placements.filter((entry) => entry.state === 'installed' && entry.health === 'ready');
|
|
465
|
+
const scopes = new Set(ready.map((entry) => entry.state === 'installed' ? entry.scope : undefined));
|
|
466
|
+
return scopes.size === 1 && ready[0]?.state === 'installed'
|
|
467
|
+
? `${scopeLabel(ready[0].scope)} · ${aggregate.value}`
|
|
468
|
+
: aggregate.value;
|
|
253
469
|
}
|
|
254
470
|
function dependentTargetLabel(item) {
|
|
255
471
|
const consumer = item.placementReferences?.[0]?.consumer;
|
|
@@ -330,7 +546,7 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
|
|
|
330
546
|
if (route.name === 'installPlacement') {
|
|
331
547
|
return controller.getPluginOverview(route.pluginId)?.dependencySummary?.totalCount ? 'D03' : 'P05';
|
|
332
548
|
}
|
|
333
|
-
if (route.name === '
|
|
549
|
+
if (route.name === 'installTargets') {
|
|
334
550
|
if (route.view === 'blocked')
|
|
335
551
|
return 'D16';
|
|
336
552
|
return controller.getPluginOverview(route.pluginId)?.dependencySummary?.totalCount ? 'D04' : 'P06';
|
|
@@ -349,6 +565,8 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
|
|
|
349
565
|
return 'P14';
|
|
350
566
|
return 'P16';
|
|
351
567
|
}
|
|
568
|
+
if (route.name === 'reviewPreparing')
|
|
569
|
+
return 'P07';
|
|
352
570
|
const dependencyOperation = snapshot.activeReview?.operationKind === 'pluginWrite'
|
|
353
571
|
&& Boolean(snapshot.activeReview.changes.some((change) => change.group));
|
|
354
572
|
if (route.name === 'review' && dependencyOperation) {
|
|
@@ -357,9 +575,14 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
|
|
|
357
575
|
}
|
|
358
576
|
if (route.name === 'progress' && snapshot.progressTargets.length > 0)
|
|
359
577
|
return 'D06';
|
|
360
|
-
if (route.name === 'result' &&
|
|
361
|
-
|
|
362
|
-
|
|
578
|
+
if (route.name === 'result' && snapshot.result) {
|
|
579
|
+
const dependencyTargets = snapshot.result.state === 'succeeded' || snapshot.result.state === 'partial'
|
|
580
|
+
? snapshot.result.summary.targets
|
|
581
|
+
: snapshot.result.state === 'blocked' && snapshot.result.code === 'operationFailed'
|
|
582
|
+
? snapshot.result.summary?.targets : undefined;
|
|
583
|
+
if (dependencyTargets?.some((target) => target.relation)) {
|
|
584
|
+
return snapshot.result.state === 'succeeded' ? 'D07' : 'D08';
|
|
585
|
+
}
|
|
363
586
|
}
|
|
364
587
|
if (route.name === 'review' && snapshot.activeReview?.originIssue)
|
|
365
588
|
return 'I10';
|
|
@@ -406,14 +629,11 @@ export function resolveConsoleV4ScreenId(snapshot, controller, overlay, tooSmall
|
|
|
406
629
|
if (route.name === 'configLayer')
|
|
407
630
|
return 'C01';
|
|
408
631
|
if (route.name === 'configFields')
|
|
409
|
-
return route.layer.scope === 'effective'
|
|
410
|
-
? 'C02' : route.groupId ? 'C04' : 'C03';
|
|
632
|
+
return route.layer.scope === 'effective' ? 'C02' : 'C03';
|
|
411
633
|
if (route.name === 'configFieldEditor')
|
|
412
634
|
return {
|
|
413
635
|
enum: 'C05', boolean: 'C06', number: 'C07', string: 'C08', path: 'C09', sensitive: 'C10',
|
|
414
636
|
}[route.editor];
|
|
415
|
-
if (route.name === 'configPresets')
|
|
416
|
-
return 'C11';
|
|
417
637
|
if (route.name === 'configUnavailable')
|
|
418
638
|
return 'C17';
|
|
419
639
|
if (route.name === 'sourceList')
|
|
@@ -467,10 +687,14 @@ export function ConsoleAppV4(props) {
|
|
|
467
687
|
const [scrollOffset, setScrollOffset] = useState(0);
|
|
468
688
|
const [searchMode, setSearchMode] = useState(false);
|
|
469
689
|
const [query, setQuery] = useState('');
|
|
690
|
+
const [installFilter, setInstallFilter] = useState(props.controller.snapshot.router.current.context.installFilter ?? 'all');
|
|
470
691
|
const [scope, setScope] = useState('user');
|
|
471
|
-
const [
|
|
692
|
+
const [selectedTargets, setSelectedTargets] = useState([]);
|
|
693
|
+
const [targetSelections, setTargetSelections] = useState({});
|
|
694
|
+
const [preferredTarget, setPreferredTarget] = useState();
|
|
472
695
|
const [technicalRouteKey, setTechnicalRouteKey] = useState();
|
|
473
696
|
const [exitAfterBusy, setExitAfterBusy] = useState(false);
|
|
697
|
+
const [exitAfterPreparation, setExitAfterPreparation] = useState(false);
|
|
474
698
|
const [, setClockTick] = useState(0);
|
|
475
699
|
const [dirty, setDirty] = useState(false);
|
|
476
700
|
const [configPage, setConfigPage] = useState();
|
|
@@ -481,7 +705,6 @@ export function ConsoleAppV4(props) {
|
|
|
481
705
|
const [inputValue, setInputValue] = useState('');
|
|
482
706
|
const [inputError, setInputError] = useState();
|
|
483
707
|
const [configActionError, setConfigActionError] = useState();
|
|
484
|
-
const [configResetAvailability, setConfigResetAvailability] = useState();
|
|
485
708
|
const [actionError, setActionError] = useState();
|
|
486
709
|
const [sourceDraft, setSourceDraft] = useState();
|
|
487
710
|
const [sourceInput, setSourceInput] = useState('');
|
|
@@ -490,6 +713,8 @@ export function ConsoleAppV4(props) {
|
|
|
490
713
|
const [sourceDependenciesExpanded, setSourceDependenciesExpanded] = useState(false);
|
|
491
714
|
const [portableAgentInput, setPortableAgentInput] = useState('');
|
|
492
715
|
const [draftOwner, setDraftOwner] = useState();
|
|
716
|
+
const [pendingPresetId, setPendingPresetId] = useState();
|
|
717
|
+
const dynamicMenuFocus = useRef(undefined);
|
|
493
718
|
const exitSent = useRef(false);
|
|
494
719
|
const operationBusy = useRef(false);
|
|
495
720
|
const route = snapshot.router.current.route;
|
|
@@ -504,6 +729,8 @@ export function ConsoleAppV4(props) {
|
|
|
504
729
|
? props.controller.listUserIssues().find((entry) => entry.issueId === route.issueId)
|
|
505
730
|
: undefined;
|
|
506
731
|
const busy = route.name === 'progress';
|
|
732
|
+
const preparationRunning = route.name === 'reviewPreparing'
|
|
733
|
+
&& snapshot.reviewPreparation?.state === 'running';
|
|
507
734
|
useEffect(() => {
|
|
508
735
|
if (!busy)
|
|
509
736
|
return;
|
|
@@ -540,6 +767,14 @@ export function ConsoleAppV4(props) {
|
|
|
540
767
|
: state === 'stale' ? 'stale' : 'completed',
|
|
541
768
|
});
|
|
542
769
|
}, [exitAfterBusy, snapshot.result]);
|
|
770
|
+
useEffect(() => {
|
|
771
|
+
if (!exitAfterPreparation || preparationRunning)
|
|
772
|
+
return;
|
|
773
|
+
requestExit({
|
|
774
|
+
code: snapshot.reviewPreparation?.state === 'failed' ? 1 : 0,
|
|
775
|
+
reason: snapshot.reviewPreparation?.state === 'failed' ? 'blocked' : 'user',
|
|
776
|
+
});
|
|
777
|
+
}, [exitAfterPreparation, preparationRunning, snapshot.reviewPreparation?.state]);
|
|
543
778
|
useEffect(() => {
|
|
544
779
|
if (route.name !== 'result'
|
|
545
780
|
|| !snapshot.result
|
|
@@ -569,7 +804,6 @@ export function ConsoleAppV4(props) {
|
|
|
569
804
|
}, [route.name]);
|
|
570
805
|
const configRoute = route.name === 'configFields'
|
|
571
806
|
|| route.name === 'configFieldEditor'
|
|
572
|
-
|| route.name === 'configPresets'
|
|
573
807
|
? route
|
|
574
808
|
: undefined;
|
|
575
809
|
const currentConfigPage = configPageMatches(configPage, configRoute) ? configPage : undefined;
|
|
@@ -578,7 +812,6 @@ export function ConsoleAppV4(props) {
|
|
|
578
812
|
return;
|
|
579
813
|
const abort = new AbortController();
|
|
580
814
|
const layers = [
|
|
581
|
-
{ key: 'effective', layer: { scope: 'effective', projectLayer: 'shared' } },
|
|
582
815
|
{ key: 'user', layer: { scope: 'user', projectLayer: 'shared' } },
|
|
583
816
|
{ key: 'project', layer: { scope: 'project', projectLayer: 'shared' } },
|
|
584
817
|
{ key: 'local', layer: { scope: 'project', projectLayer: 'local' } },
|
|
@@ -616,14 +849,19 @@ export function ConsoleAppV4(props) {
|
|
|
616
849
|
return;
|
|
617
850
|
setConfigPage(page);
|
|
618
851
|
})
|
|
619
|
-
.catch(() => {
|
|
852
|
+
.catch((error) => {
|
|
620
853
|
if (!abort.signal.aborted) {
|
|
621
854
|
const layer = configRoute.layer.scope === 'effective'
|
|
622
855
|
? undefined
|
|
623
856
|
: { scope: configRoute.layer.scope, projectLayer: configRoute.layer.projectLayer };
|
|
857
|
+
const code = typeof error === 'object' && error !== null && 'code' in error
|
|
858
|
+
? safeDiagnosticCode(error.code, 'configUnavailable')
|
|
859
|
+
: 'configUnavailable';
|
|
624
860
|
props.controller.replace({
|
|
625
861
|
name: 'configUnavailable',
|
|
626
862
|
pluginId: expectedPluginId,
|
|
863
|
+
diagnosticCode: code,
|
|
864
|
+
diagnosticStage: 'read-config',
|
|
627
865
|
...(layer ? { layer } : {}),
|
|
628
866
|
});
|
|
629
867
|
}
|
|
@@ -631,22 +869,16 @@ export function ConsoleAppV4(props) {
|
|
|
631
869
|
return () => abort.abort();
|
|
632
870
|
}, [configLoadKey, configRoute?.pluginId, configRoute?.layer.scope, configRoute?.layer.projectLayer, props.controller]);
|
|
633
871
|
useEffect(() => {
|
|
634
|
-
if (route.name !== '
|
|
635
|
-
setConfigResetAvailability(undefined);
|
|
872
|
+
if (route.name !== 'configFields' || !currentConfigPage)
|
|
636
873
|
return;
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
})
|
|
644
|
-
|
|
645
|
-
if (!abort.signal.aborted)
|
|
646
|
-
setConfigResetAvailability({ exists: false, sourceIdentityPresent: false });
|
|
647
|
-
});
|
|
648
|
-
return () => abort.abort();
|
|
649
|
-
}, [props.controller, route.name, route.name === 'configUnavailable' ? route.pluginId : '', route.name === 'configUnavailable' ? JSON.stringify(route.layer) : '']);
|
|
874
|
+
const tabs = configTabs(currentConfigPage);
|
|
875
|
+
if (tabs.length === 0)
|
|
876
|
+
return;
|
|
877
|
+
const current = snapshot.router.current.context.configGroupId;
|
|
878
|
+
if (tabs.some((tab) => tab.id === current))
|
|
879
|
+
return;
|
|
880
|
+
props.controller.updatePageContext({ configGroupId: tabs[0].id });
|
|
881
|
+
}, [props.controller, route.name, currentConfigPage, snapshot.router.current.context.configGroupId]);
|
|
650
882
|
useEffect(() => {
|
|
651
883
|
let count;
|
|
652
884
|
if (route.name === 'sourceList')
|
|
@@ -660,18 +892,19 @@ export function ConsoleAppV4(props) {
|
|
|
660
892
|
mode: route.mode,
|
|
661
893
|
...(route.sourceId ? { sourceId: route.sourceId } : {}),
|
|
662
894
|
...(query ? { query } : {}),
|
|
895
|
+
installFilter,
|
|
663
896
|
}).length;
|
|
664
897
|
count = itemCount || 1;
|
|
665
898
|
}
|
|
666
899
|
if (count === undefined)
|
|
667
900
|
return;
|
|
668
901
|
setFocus((value) => Math.min(value, Math.max(0, count - 1)));
|
|
669
|
-
|
|
670
|
-
setScrollOffset((value) => Math.min(value, Math.max(0, count - maximumVisible)));
|
|
902
|
+
setScrollOffset((value) => Math.min(value, Math.max(0, count - 1)));
|
|
671
903
|
}, [
|
|
672
904
|
policy.mode,
|
|
673
905
|
props.controller,
|
|
674
906
|
query,
|
|
907
|
+
installFilter,
|
|
675
908
|
route.name,
|
|
676
909
|
route.name === 'pluginList' ? route.mode : '',
|
|
677
910
|
route.name === 'pluginList' ? route.sourceId : '',
|
|
@@ -713,51 +946,33 @@ export function ConsoleAppV4(props) {
|
|
|
713
946
|
return () => abort.abort();
|
|
714
947
|
}, [props.controller, route.name, route.name === 'sourceRemoveBlocked' ? route.sourceId : '']);
|
|
715
948
|
const screenId = resolveConsoleV4ScreenId(snapshot, props.controller, overlay, tooSmall, searchMode);
|
|
949
|
+
const portableVariantCandidates = route.name === 'portableSkillSelect' && snapshot.portableDraft
|
|
950
|
+
? snapshot.portableDraft.candidates.filter(portableScopeVariantsDiffer)
|
|
951
|
+
: [];
|
|
952
|
+
const supportsTechnicalDetails = Boolean(route.name === 'progress' && snapshot.progress.messages.length > 0)
|
|
953
|
+
|| Boolean(route.name === 'review' && snapshot.activeReview?.technicalDetails?.length)
|
|
954
|
+
|| Boolean(route.name === 'issueDetail' && issue
|
|
955
|
+
&& issue.diagnostic.technical)
|
|
956
|
+
|| Boolean(route.name === 'pluginDependencies' && plugin)
|
|
957
|
+
|| portableVariantCandidates.length > 0
|
|
958
|
+
|| Boolean(route.name === 'portableDependencies' && plugin
|
|
959
|
+
&& props.controller.getPluginDependencyOverview(plugin.pluginId)?.dependencies.length)
|
|
960
|
+
|| Boolean(route.name === 'portableBlocker' && plugin)
|
|
961
|
+
|| Boolean(route.name === 'configUnavailable' && plugin)
|
|
962
|
+
|| Boolean(route.name === 'result' && snapshot.result && 'operationKind' in snapshot.result
|
|
963
|
+
&& snapshot.result.operationKind === 'portableInstall');
|
|
964
|
+
const canOpenHelp = !tooSmall
|
|
965
|
+
&& !isCharacterInputMode(route, searchMode)
|
|
966
|
+
&& !(route.name === 'installTargets' && route.view !== 'blocked');
|
|
716
967
|
useEffect(() => {
|
|
717
968
|
props.onScreenChange?.(screenId);
|
|
718
969
|
}, [props.onScreenChange, screenId]);
|
|
719
|
-
const selectedPlatforms = (candidate, selectedScope) => (candidate?.supportedPlatforms ?? []).map((platform) => {
|
|
720
|
-
const placement = props.controller.getPlatformPlacement(candidate.pluginId, platform);
|
|
721
|
-
if (!placement)
|
|
722
|
-
return { platform, disabled: '当前没有可信的平台能力信息' };
|
|
723
|
-
if (placement.state === 'scopeConflict')
|
|
724
|
-
return { platform, disabled: '安装位置冲突;请先处理问题' };
|
|
725
|
-
if (placement.state === 'installed') {
|
|
726
|
-
return { platform, disabled: `已安装到${scopeLabel(placement.scope)};请先移除现有安装` };
|
|
727
|
-
}
|
|
728
|
-
if (!placement.supportedScopes.includes(selectedScope))
|
|
729
|
-
return { platform, disabled: '不支持所选位置' };
|
|
730
|
-
const overview = props.controller.getPluginDependencyOverview(candidate.pluginId);
|
|
731
|
-
for (const dependency of overview?.dependencies ?? []) {
|
|
732
|
-
const dependentPlugin = props.controller.getPluginOverview(dependency.pluginId);
|
|
733
|
-
if (!dependentPlugin || !dependentPlugin.supportedPlatforms.includes(platform)) {
|
|
734
|
-
return { platform, disabled: `${dependency.displayName} 不支持${platformLabel(platform)}` };
|
|
735
|
-
}
|
|
736
|
-
const dependencyPlacement = props.controller.getPlatformPlacement(dependency.pluginId, platform);
|
|
737
|
-
if (dependencyPlacement?.state === 'scopeConflict') {
|
|
738
|
-
return { platform, disabled: `${dependency.displayName} 的安装位置冲突` };
|
|
739
|
-
}
|
|
740
|
-
if (dependencyPlacement?.state === 'installed') {
|
|
741
|
-
if (dependencyPlacement.health !== 'ready') {
|
|
742
|
-
return { platform, disabled: `${dependency.displayName} 需要先处理` };
|
|
743
|
-
}
|
|
744
|
-
if (selectedScope === 'user' && dependencyPlacement.scope === 'project') {
|
|
745
|
-
return { platform, disabled: `${dependency.displayName} 只在当前项目可用` };
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
if (dependencyPlacement?.state === 'notInstalled'
|
|
749
|
-
&& !dependencyPlacement.supportedScopes.includes(selectedScope)) {
|
|
750
|
-
return { platform, disabled: `${dependency.displayName} 不支持所选位置` };
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
return { platform };
|
|
754
|
-
});
|
|
755
970
|
const dependencyOverview = (candidate) => candidate ? props.controller.getPluginDependencyOverview(candidate.pluginId) : undefined;
|
|
756
|
-
const installIntent = (candidate) => ({
|
|
971
|
+
const installIntent = (candidate, targets = selectedTargets) => ({
|
|
757
972
|
kind: 'pluginWrite',
|
|
758
973
|
action: 'install',
|
|
759
974
|
pluginId: candidate.pluginId,
|
|
760
|
-
platform:
|
|
975
|
+
platform: targets.length === 2 ? 'both' : targets[0],
|
|
761
976
|
scope,
|
|
762
977
|
});
|
|
763
978
|
let title = '管理你的 Agent 工作流插件';
|
|
@@ -767,8 +982,43 @@ export function ConsoleAppV4(props) {
|
|
|
767
982
|
let help = '↑↓ 移动 · Enter 选择 · ? 帮助 · q 退出';
|
|
768
983
|
let content;
|
|
769
984
|
let actions = [];
|
|
985
|
+
let pluginRows = [];
|
|
986
|
+
let visibleConfigTabs = [];
|
|
987
|
+
let activeConfigTabId;
|
|
988
|
+
const dynamicMenu = plugin && route.name === 'pluginPreview'
|
|
989
|
+
? buildPluginPreviewActions(plugin)
|
|
990
|
+
: plugin && route.name === 'pluginDetail'
|
|
991
|
+
? buildPluginDetailActions(plugin)
|
|
992
|
+
: plugin && route.name === 'platform'
|
|
993
|
+
? buildPlatformActions(plugin, route.platform)
|
|
994
|
+
: undefined;
|
|
995
|
+
const dynamicActionIds = dynamicMenu?.map(pluginMenuActionId);
|
|
996
|
+
const dynamicActionSignature = dynamicActionIds?.join('\0');
|
|
997
|
+
const previousDynamicFocus = dynamicMenuFocus.current;
|
|
998
|
+
const retainedDynamicFocus = dynamicActionIds
|
|
999
|
+
&& previousDynamicFocus?.routeKey === routeKey
|
|
1000
|
+
&& previousDynamicFocus.actionIds !== dynamicActionSignature
|
|
1001
|
+
&& previousDynamicFocus.focusedActionId !== undefined
|
|
1002
|
+
? dynamicActionIds.indexOf(previousDynamicFocus.focusedActionId)
|
|
1003
|
+
: -1;
|
|
1004
|
+
const effectiveFocus = dynamicActionIds
|
|
1005
|
+
&& previousDynamicFocus?.routeKey === routeKey
|
|
1006
|
+
&& previousDynamicFocus.actionIds !== dynamicActionSignature
|
|
1007
|
+
? retainedDynamicFocus >= 0 ? retainedDynamicFocus : 0
|
|
1008
|
+
: focus;
|
|
1009
|
+
const pageActionBudget = (staticRows = 0) => contentRowBudget({
|
|
1010
|
+
terminalRows: size.rows,
|
|
1011
|
+
description: Boolean(description),
|
|
1012
|
+
compact: policy.mode === 'compact',
|
|
1013
|
+
staticRows,
|
|
1014
|
+
});
|
|
1015
|
+
const responsiveBudget = (compactRows, standardRows) => {
|
|
1016
|
+
const baseline = policy.mode === 'compact' ? 18 : 24;
|
|
1017
|
+
const base = policy.mode === 'compact' ? compactRows : standardRows;
|
|
1018
|
+
return base + Math.max(0, size.rows - baseline);
|
|
1019
|
+
};
|
|
770
1020
|
const go = (next) => {
|
|
771
|
-
props.controller.updatePageContext({ focusIndex: focus, scrollOffset, query, sourceTabId: route.name === 'pluginList' ? route.sourceId ?? 'all' : undefined });
|
|
1021
|
+
props.controller.updatePageContext({ focusIndex: focus, scrollOffset, query, installFilter, sourceTabId: route.name === 'pluginList' ? route.sourceId ?? 'all' : undefined });
|
|
772
1022
|
props.controller.push(next);
|
|
773
1023
|
setFocus(0);
|
|
774
1024
|
setScrollOffset(0);
|
|
@@ -776,6 +1026,21 @@ export function ConsoleAppV4(props) {
|
|
|
776
1026
|
setShowTechnical(false);
|
|
777
1027
|
setActionError(undefined);
|
|
778
1028
|
};
|
|
1029
|
+
const beginConfigFlow = (pluginId) => {
|
|
1030
|
+
props.controller.updatePageContext({
|
|
1031
|
+
focusIndex: focus,
|
|
1032
|
+
scrollOffset,
|
|
1033
|
+
query,
|
|
1034
|
+
installFilter,
|
|
1035
|
+
sourceTabId: route.name === 'pluginList' ? route.sourceId ?? 'all' : undefined,
|
|
1036
|
+
});
|
|
1037
|
+
props.controller.beginConfigFlow(pluginId);
|
|
1038
|
+
setFocus(0);
|
|
1039
|
+
setScrollOffset(0);
|
|
1040
|
+
setSearchMode(false);
|
|
1041
|
+
setShowTechnical(false);
|
|
1042
|
+
setActionError(undefined);
|
|
1043
|
+
};
|
|
779
1044
|
const back = () => {
|
|
780
1045
|
if (route.name === 'result')
|
|
781
1046
|
props.controller.leaveResult();
|
|
@@ -785,6 +1050,7 @@ export function ConsoleAppV4(props) {
|
|
|
785
1050
|
setFocus(restored.focusIndex ?? 0);
|
|
786
1051
|
setScrollOffset(restored.scrollOffset ?? 0);
|
|
787
1052
|
setQuery(restored.query ?? '');
|
|
1053
|
+
setInstallFilter(restored.installFilter ?? 'all');
|
|
788
1054
|
setSearchMode(Boolean(restored.query));
|
|
789
1055
|
setActionError(undefined);
|
|
790
1056
|
};
|
|
@@ -794,15 +1060,23 @@ export function ConsoleAppV4(props) {
|
|
|
794
1060
|
setFocus(restored.focusIndex ?? 0);
|
|
795
1061
|
setScrollOffset(restored.scrollOffset ?? 0);
|
|
796
1062
|
setQuery(restored.query ?? '');
|
|
1063
|
+
setInstallFilter(restored.installFilter ?? 'all');
|
|
797
1064
|
setSearchMode(Boolean(restored.query));
|
|
798
1065
|
setShowTechnical(false);
|
|
799
1066
|
setActionError(undefined);
|
|
800
1067
|
};
|
|
801
|
-
const beginReview =
|
|
1068
|
+
const beginReview = (intent) => {
|
|
802
1069
|
const underlyingResultRoute = route.name === 'result' && snapshot.result && 'returnRoute' in snapshot.result
|
|
803
1070
|
? snapshot.result.returnRoute
|
|
804
1071
|
: undefined;
|
|
805
|
-
|
|
1072
|
+
props.controller.updatePageContext({
|
|
1073
|
+
focusIndex: focus,
|
|
1074
|
+
scrollOffset,
|
|
1075
|
+
query,
|
|
1076
|
+
installFilter,
|
|
1077
|
+
sourceTabId: route.name === 'pluginList' ? route.sourceId ?? 'all' : undefined,
|
|
1078
|
+
});
|
|
1079
|
+
props.controller.startReviewPreparation(intent, underlyingResultRoute);
|
|
806
1080
|
setFocus(0);
|
|
807
1081
|
setScrollOffset(0);
|
|
808
1082
|
};
|
|
@@ -826,10 +1100,11 @@ export function ConsoleAppV4(props) {
|
|
|
826
1100
|
}
|
|
827
1101
|
}
|
|
828
1102
|
};
|
|
829
|
-
const beginPortableReview =
|
|
1103
|
+
const beginPortableReview = (pluginId, selectedScope) => {
|
|
830
1104
|
props.controller.setPortableScope(selectedScope);
|
|
831
1105
|
try {
|
|
832
|
-
|
|
1106
|
+
props.controller.updatePageContext({ focusIndex: focus, scrollOffset });
|
|
1107
|
+
props.controller.startPortableInstallReviewPreparation();
|
|
833
1108
|
setFocus(0);
|
|
834
1109
|
setActionError(undefined);
|
|
835
1110
|
}
|
|
@@ -895,9 +1170,11 @@ export function ConsoleAppV4(props) {
|
|
|
895
1170
|
const selectedPlatform = action.platform ?? selectedIssue.platform;
|
|
896
1171
|
const writeAction = action.writeAction ?? (action.id === 'reinstall'
|
|
897
1172
|
? 'reinstall'
|
|
898
|
-
: action.id === '
|
|
899
|
-
? '
|
|
900
|
-
:
|
|
1173
|
+
: action.id === 'cleanup'
|
|
1174
|
+
? 'cleanup'
|
|
1175
|
+
: action.id === 'remove' || action.id === 'keepUserScope' || action.id === 'keepProjectScope'
|
|
1176
|
+
? 'uninstall'
|
|
1177
|
+
: undefined);
|
|
901
1178
|
if (!selectedScope || !selectedPlatform || !writeAction)
|
|
902
1179
|
return undefined;
|
|
903
1180
|
return {
|
|
@@ -989,6 +1266,15 @@ export function ConsoleAppV4(props) {
|
|
|
989
1266
|
return field.value ? '开启' : '关闭';
|
|
990
1267
|
return `${field.displayValue}${field.unit && field.displayValue !== '未设置' ? ` ${field.unit}` : ''}`;
|
|
991
1268
|
};
|
|
1269
|
+
const effectiveFieldDisplay = (field) => {
|
|
1270
|
+
if (field.type === 'enum' && field.effectiveValue !== undefined) {
|
|
1271
|
+
return field.valueLabels?.[String(field.effectiveValue)] ?? field.effectiveDisplayValue;
|
|
1272
|
+
}
|
|
1273
|
+
if (field.type === 'boolean' && field.effectiveValue !== undefined) {
|
|
1274
|
+
return field.effectiveValue ? '开启' : '关闭';
|
|
1275
|
+
}
|
|
1276
|
+
return `${field.effectiveDisplayValue}${field.unit && field.effectiveDisplayValue !== '未设置' ? ` ${field.unit}` : ''}`;
|
|
1277
|
+
};
|
|
992
1278
|
const fieldEditor = (field) => {
|
|
993
1279
|
if (field.sensitive)
|
|
994
1280
|
return 'sensitive';
|
|
@@ -1028,6 +1314,88 @@ export function ConsoleAppV4(props) {
|
|
|
1028
1314
|
return next;
|
|
1029
1315
|
});
|
|
1030
1316
|
};
|
|
1317
|
+
const configFieldTarget = (page, key) => {
|
|
1318
|
+
if (!key)
|
|
1319
|
+
return undefined;
|
|
1320
|
+
for (const group of page.groups) {
|
|
1321
|
+
const focusIndex = group.fields.findIndex((field) => field.key === key);
|
|
1322
|
+
if (focusIndex >= 0)
|
|
1323
|
+
return { groupId: group.id, focusIndex };
|
|
1324
|
+
}
|
|
1325
|
+
return undefined;
|
|
1326
|
+
};
|
|
1327
|
+
const applyConfigPreset = (page, presetId) => {
|
|
1328
|
+
const preset = page.presets.find((candidate) => candidate.id === presetId);
|
|
1329
|
+
if (!preset)
|
|
1330
|
+
return;
|
|
1331
|
+
const fields = new Map(page.groups.flatMap((group) => group.fields)
|
|
1332
|
+
.map((field) => [field.key, field]));
|
|
1333
|
+
setDraftUpdates((current) => {
|
|
1334
|
+
const next = { ...current };
|
|
1335
|
+
for (const [key, value] of Object.entries(preset.values)) {
|
|
1336
|
+
const field = fields.get(key);
|
|
1337
|
+
if (field?.configured && field.value === value)
|
|
1338
|
+
delete next[key];
|
|
1339
|
+
else
|
|
1340
|
+
next[key] = value;
|
|
1341
|
+
}
|
|
1342
|
+
const nextRemoves = draftRemoves.filter((key) => !Object.hasOwn(preset.values, key));
|
|
1343
|
+
setDraftRemoves(nextRemoves);
|
|
1344
|
+
setDirty(Object.keys(next).length + nextRemoves.length > 0);
|
|
1345
|
+
return next;
|
|
1346
|
+
});
|
|
1347
|
+
const target = configFieldTarget(page, Object.keys(preset.values)[0])
|
|
1348
|
+
?? (page.groups[0] ? { groupId: page.groups[0].id, focusIndex: 0 } : undefined);
|
|
1349
|
+
if (target) {
|
|
1350
|
+
props.controller.updatePageContext({
|
|
1351
|
+
configGroupId: target.groupId,
|
|
1352
|
+
focusIndex: target.focusIndex,
|
|
1353
|
+
scrollOffset: 0,
|
|
1354
|
+
});
|
|
1355
|
+
setFocus(target.focusIndex);
|
|
1356
|
+
setScrollOffset(0);
|
|
1357
|
+
}
|
|
1358
|
+
setConfigActionError((preset.privateFieldTitles?.length ?? 0) > 0
|
|
1359
|
+
? `${preset.privateFieldTitles.join('、')}需手动输入。`
|
|
1360
|
+
: undefined);
|
|
1361
|
+
};
|
|
1362
|
+
const openConfigLayerTarget = (layer) => {
|
|
1363
|
+
if (route.name !== 'configLayer')
|
|
1364
|
+
return;
|
|
1365
|
+
const field = route.fieldKey && configPage
|
|
1366
|
+
? configPage.groups.flatMap((group) => group.fields).find((candidate) => candidate.key === route.fieldKey)
|
|
1367
|
+
: undefined;
|
|
1368
|
+
const fieldTarget = configPage ? configFieldTarget(configPage, route.fieldKey) : undefined;
|
|
1369
|
+
const preset = route.presetId && configPage
|
|
1370
|
+
? configPage.presets.find((candidate) => candidate.id === route.presetId)
|
|
1371
|
+
: undefined;
|
|
1372
|
+
const presetTarget = preset
|
|
1373
|
+
? configFieldTarget(configPage, Object.keys(preset.values)[0])
|
|
1374
|
+
: undefined;
|
|
1375
|
+
const target = fieldTarget ?? (route.groupId ? { groupId: route.groupId, focusIndex: 0 } : undefined) ?? presetTarget
|
|
1376
|
+
?? (configPage?.groups[0] ? { groupId: configPage.groups[0].id, focusIndex: 0 } : undefined);
|
|
1377
|
+
const context = target
|
|
1378
|
+
? { configGroupId: target.groupId, focusIndex: target.focusIndex, scrollOffset: 0 }
|
|
1379
|
+
: {};
|
|
1380
|
+
props.controller.replace({ name: 'configFields', pluginId: route.pluginId, layer }, context);
|
|
1381
|
+
if (route.presetId) {
|
|
1382
|
+
setPendingPresetId(route.presetId);
|
|
1383
|
+
setFocus(target?.focusIndex ?? 0);
|
|
1384
|
+
setScrollOffset(0);
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
if (field && route.fieldKey) {
|
|
1388
|
+
props.controller.push({
|
|
1389
|
+
name: 'configFieldEditor',
|
|
1390
|
+
pluginId: route.pluginId,
|
|
1391
|
+
layer,
|
|
1392
|
+
fieldKey: route.fieldKey,
|
|
1393
|
+
editor: fieldEditor(field),
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
setFocus(0);
|
|
1397
|
+
setScrollOffset(0);
|
|
1398
|
+
};
|
|
1031
1399
|
const configChanges = () => {
|
|
1032
1400
|
const fields = currentConfigPage?.groups.flatMap((group) => group.fields) ?? [];
|
|
1033
1401
|
return [...Object.keys(draftUpdates), ...draftRemoves.filter((key) => !Object.hasOwn(draftUpdates, key))]
|
|
@@ -1063,19 +1431,34 @@ export function ConsoleAppV4(props) {
|
|
|
1063
1431
|
setConfigActionError('设置值未通过验证。请重新检查修改。');
|
|
1064
1432
|
}
|
|
1065
1433
|
};
|
|
1434
|
+
useEffect(() => {
|
|
1435
|
+
if (!pendingPresetId || route.name !== 'configFields'
|
|
1436
|
+
|| route.layer.scope === 'effective' || !currentConfigPage)
|
|
1437
|
+
return;
|
|
1438
|
+
const presetId = pendingPresetId;
|
|
1439
|
+
setPendingPresetId(undefined);
|
|
1440
|
+
applyConfigPreset(currentConfigPage, presetId);
|
|
1441
|
+
}, [pendingPresetId, route.name, currentConfigPage]);
|
|
1442
|
+
useEffect(() => {
|
|
1443
|
+
if (!pendingPresetId)
|
|
1444
|
+
return;
|
|
1445
|
+
if (route.name === 'configFields' && route.layer.scope !== 'effective')
|
|
1446
|
+
return;
|
|
1447
|
+
setPendingPresetId(undefined);
|
|
1448
|
+
}, [pendingPresetId, route.name, route.name === 'configFields' ? route.layer.scope : '']);
|
|
1066
1449
|
if (snapshot.phase === 'error') {
|
|
1067
1450
|
breadcrumb = '启动失败';
|
|
1068
1451
|
title = '无法读取插件状态';
|
|
1069
1452
|
description = snapshot.loadError?.message ?? '当前不能安全显示插件状态。';
|
|
1070
1453
|
help = 'q 退出 · Ctrl-C 退出';
|
|
1071
|
-
content =
|
|
1454
|
+
content = _jsx(StatusCard, { status: "error", title: snapshot.loadError?.code ?? 'statusLoadFailed', description: "\u6CA1\u6709\u6267\u884C\u4EFB\u4F55\u5199\u64CD\u4F5C\u3002" });
|
|
1072
1455
|
}
|
|
1073
1456
|
else if (snapshot.phase !== 'ready') {
|
|
1074
1457
|
breadcrumb = '启动';
|
|
1075
1458
|
title = '正在检查插件状态';
|
|
1076
1459
|
description = '单个来源失败不会阻止其他来源继续加载。';
|
|
1077
1460
|
help = 'Ctrl-C 完成后退出';
|
|
1078
|
-
content =
|
|
1461
|
+
content = _jsxs(StatusCard, { status: "info", title: "\u51C6\u5907\u63A7\u5236\u53F0", children: [_jsx(ProgressRow, { title: "\u8BFB\u53D6\u63D2\u4EF6\u6765\u6E90", state: "active" }), _jsx(ProgressRow, { title: "\u68C0\u67E5\u5F53\u524D\u9879\u76EE", state: "pending" }), _jsx(ProgressRow, { title: "\u6C47\u603B\u53EF\u7528\u64CD\u4F5C", state: "pending" })] });
|
|
1079
1462
|
}
|
|
1080
1463
|
else if (route.name === 'home') {
|
|
1081
1464
|
const home = snapshot.viewModel.home;
|
|
@@ -1086,7 +1469,7 @@ export function ConsoleAppV4(props) {
|
|
|
1086
1469
|
{ label: '需要处理', description: '解决安装、来源或设置问题', meta: String(home.issueCount), status: 'warning', run: () => go({ name: 'issueList' }) },
|
|
1087
1470
|
{ label: '插件来源', description: '管理 Git 仓库和本地目录', meta: String(home.sourceCount), run: () => go({ name: 'sourceList' }) },
|
|
1088
1471
|
];
|
|
1089
|
-
content =
|
|
1472
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(StatusCard, { status: home.issueCount > 0 || home.unavailableSourceCount > 0 ? 'warning' : 'success', title: "\u5F53\u524D\u72B6\u6001", children: [_jsx(ResultRow, { title: `${home.installedPlugins} 个插件可用`, status: "success" }), _jsx(ResultRow, { title: `${home.issueCount} 项需要处理`, status: home.issueCount > 0 ? 'warning' : 'muted' }), home.cachedSourceCount > 0 ? _jsx(ResultRow, { title: `${home.cachedSourceCount} 个来源使用缓存`, status: "warning" }) : null, home.unavailableSourceCount > 0 ? _jsx(ResultRow, { title: `${home.unavailableSourceCount} 个来源不可用`, status: "error" }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "task", compact: policy.mode === 'compact' })] });
|
|
1090
1473
|
}
|
|
1091
1474
|
else if (route.name === 'pluginList') {
|
|
1092
1475
|
const sourceId = route.sourceId ?? 'all';
|
|
@@ -1094,37 +1477,59 @@ export function ConsoleAppV4(props) {
|
|
|
1094
1477
|
mode: route.mode,
|
|
1095
1478
|
...(sourceId !== 'all' ? { sourceId } : {}),
|
|
1096
1479
|
...(query ? { query } : {}),
|
|
1480
|
+
installFilter,
|
|
1097
1481
|
});
|
|
1098
1482
|
breadcrumb = route.mode === 'browse' ? '添加插件' : '我的插件';
|
|
1099
1483
|
title = breadcrumb;
|
|
1100
1484
|
description = route.mode === 'browse' ? '选择希望 Agent 获得的能力。' : '查看状态,或选择一个插件进行管理。';
|
|
1101
1485
|
context = `${items.length} 个插件`;
|
|
1102
|
-
help =
|
|
1486
|
+
help = `↑↓ 移动 · Enter 查看 · ←→ 来源 · / 搜索${route.mode === 'browse' ? ' · f 未安装' : ''} · Esc 返回`;
|
|
1103
1487
|
const sourceError = sourceId !== 'all' && props.controller.getSourceOverview(sourceId).source?.state === 'error';
|
|
1104
1488
|
actions = sourceError
|
|
1105
1489
|
? [{ label: '查看来源问题', description: '没有可用缓存;当前不能浏览这个来源', run: () => go({ name: 'issueDetail', issueId: `source:${sourceId}:unavailable` }) }]
|
|
1106
|
-
:
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
}));
|
|
1490
|
+
: [];
|
|
1491
|
+
pluginRows = sourceError ? [] : items.map((item) => ({
|
|
1492
|
+
pluginId: item.pluginId,
|
|
1493
|
+
name: item.displayName,
|
|
1494
|
+
sourceLabel: item.sourceLabel,
|
|
1495
|
+
description: item.description,
|
|
1496
|
+
placementSummary: pluginPlacementSummary(item),
|
|
1497
|
+
statusLabel: statusText(item),
|
|
1498
|
+
status: pluginRowStatus(item),
|
|
1499
|
+
}));
|
|
1117
1500
|
if (route.mode === 'installed' && items.length === 0 && !sourceError) {
|
|
1118
1501
|
title = '还没有添加插件';
|
|
1119
1502
|
description = `从已配置的 ${props.controller.listSources().length} 个来源中浏览可用能力。`;
|
|
1120
1503
|
actions = [{ label: '浏览插件', description: '开始添加第一个插件', run: () => go({ name: 'pluginList', mode: 'browse' }) }];
|
|
1121
1504
|
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1505
|
+
else if (route.mode === 'browse' && items.length === 0 && !sourceError) {
|
|
1506
|
+
actions = [{
|
|
1507
|
+
label: '没有匹配插件',
|
|
1508
|
+
description: installFilter === 'uninstalled' ? '当前来源和搜索条件下没有未安装插件' : '请调整来源或搜索条件',
|
|
1509
|
+
disabled: '没有可打开的插件',
|
|
1510
|
+
}];
|
|
1511
|
+
}
|
|
1512
|
+
const pluginListStaticRows = route.mode === 'browse' ? 3 : 2;
|
|
1513
|
+
const viewport = sourceError || pluginRows.length === 0
|
|
1514
|
+
? actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(pluginListStaticRows))
|
|
1515
|
+
: rowViewport({
|
|
1516
|
+
rowHeights: pluginRows.map(() => 3),
|
|
1517
|
+
focus,
|
|
1518
|
+
requestedOffset: scrollOffset,
|
|
1519
|
+
rowBudget: pageActionBudget(pluginListStaticRows),
|
|
1520
|
+
});
|
|
1521
|
+
if (viewport.hiddenBefore > 0 || viewport.hiddenAfter > 0) {
|
|
1522
|
+
context = `${items.length} 个插件 · ${viewport.position}/${viewport.total}`;
|
|
1523
|
+
}
|
|
1524
|
+
content = (_jsxs(Box, { flexDirection: "column", children: [_jsx(SourceTabs, { controller: props.controller, current: sourceId, compact: policy.mode === 'compact', columns: size.columns - 2, mode: route.mode, query: query || undefined, installFilter: installFilter }), _jsxs(Text, { color: searchMode ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["/ ", searchMode ? query || '输入搜索内容' : '搜索名称、用途或来源…'] }), route.mode === 'browse'
|
|
1525
|
+
? _jsxs(Text, { color: tuiTheme.colors.muted, children: ["\u7B5B\u9009 ", _jsxs(Text, { color: installFilter === 'uninstalled' ? tuiTheme.colors.accent : tuiTheme.colors.muted, children: ["[", installFilter === 'uninstalled' ? '未安装' : '全部', "]"] }), " f"] })
|
|
1526
|
+
: null, sourceError
|
|
1527
|
+
? _jsx(Callout, { status: "error", title: `${sourceId} 暂时无法提供插件`, description: "\u6CA1\u6709\u53EF\u7528\u7F13\u5B58\uFF0C\u56E0\u6B64\u5F53\u524D\u4E0D\u80FD\u6D4F\u89C8\u8FD9\u4E2A\u6765\u6E90\u3002" })
|
|
1125
1528
|
: route.mode === 'installed' && items.length === 0
|
|
1126
|
-
?
|
|
1127
|
-
: null,
|
|
1529
|
+
? _jsx(Callout, { title: "\u5F00\u59CB\u6DFB\u52A0\u7B2C\u4E00\u4E2A\u63D2\u4EF6", description: "\u63D2\u4EF6\u53EF\u4EE5\u5B89\u88C5\u5230 Codex\u3001Claude Code \u6216\u4E24\u8005\u3002" })
|
|
1530
|
+
: null, pluginRows.length > 0
|
|
1531
|
+
? _jsx(PluginRows, { rows: pluginRows, focus: focus, viewport: viewport })
|
|
1532
|
+
: _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit })] }));
|
|
1128
1533
|
}
|
|
1129
1534
|
else if (route.name === 'pluginPreview' && plugin) {
|
|
1130
1535
|
breadcrumb = `添加插件 / ${plugin.displayName}`;
|
|
@@ -1134,69 +1539,152 @@ export function ConsoleAppV4(props) {
|
|
|
1134
1539
|
const overview = dependencyOverview(plugin);
|
|
1135
1540
|
const hasDependencies = (plugin.dependencySummary?.totalCount ?? 0) > 0;
|
|
1136
1541
|
const beginInstall = () => {
|
|
1137
|
-
const initialScope = ['user', 'project'].find((candidateScope) =>
|
|
1138
|
-
|
|
1139
|
-
|
|
1542
|
+
const initialScope = ['user', 'project'].find((candidateScope) => props.controller.listInstallTargets(plugin.pluginId, candidateScope)
|
|
1543
|
+
.some((entry) => entry.compatible)) ?? scope;
|
|
1544
|
+
const available = props.controller.listInstallTargets(plugin.pluginId, initialScope)
|
|
1545
|
+
.filter((entry) => entry.compatible).map((entry) => entry.platform);
|
|
1140
1546
|
setScope(initialScope);
|
|
1141
|
-
|
|
1547
|
+
setSelectedTargets([]);
|
|
1548
|
+
setTargetSelections({});
|
|
1549
|
+
setPreferredTarget(undefined);
|
|
1142
1550
|
if (available.length > 0)
|
|
1143
1551
|
go({ name: 'installPlacement', pluginId: plugin.pluginId });
|
|
1144
1552
|
else
|
|
1145
|
-
go({ name: '
|
|
1553
|
+
go({ name: 'installTargets', pluginId: plugin.pluginId, view: 'blocked' });
|
|
1146
1554
|
};
|
|
1147
|
-
const
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
{ label: '
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1555
|
+
const previewAction = (action) => {
|
|
1556
|
+
if (action.id === 'add')
|
|
1557
|
+
return { label: '添加插件', description: '选择使用位置并确认变化', run: beginInstall };
|
|
1558
|
+
if (action.id === 'manage')
|
|
1559
|
+
return { label: '管理插件', description: '查看已安装目标平台', run: () => go({ name: 'pluginDetail', pluginId: plugin.pluginId }) };
|
|
1560
|
+
if (action.id === 'repair')
|
|
1561
|
+
return { label: '管理并修复', description: '查看需要处理的安装位置', status: 'warning', run: () => go({ name: 'pluginDetail', pluginId: plugin.pluginId }) };
|
|
1562
|
+
if (action.id === 'portableSkills')
|
|
1563
|
+
return { label: '安装通用 Skills…', description: '选择 Skill 和 Agent 后交给 Skills CLI 安装', run: () => beginPortable(plugin.pluginId) };
|
|
1564
|
+
if (action.id === 'dependencies')
|
|
1565
|
+
return {
|
|
1566
|
+
label: (overview?.blockers.length ?? 0) > 0 ? '查看依赖问题' : '查看依赖关系',
|
|
1567
|
+
description: overview?.blockers[0]?.explanation ?? '查看直接、间接依赖和当前状态',
|
|
1568
|
+
...((overview?.blockers.length ?? 0) > 0 ? { status: 'warning' } : {}),
|
|
1569
|
+
run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }),
|
|
1570
|
+
};
|
|
1571
|
+
if (action.id === 'settings')
|
|
1572
|
+
return { label: '查看插件设置', description: `${plugin.configFieldCount} 个配置字段及当前来源`, run: () => beginConfigFlow(plugin.pluginId) };
|
|
1573
|
+
if (action.id === 'sourceProblem')
|
|
1574
|
+
return { label: '查看来源问题', status: 'warning', run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }) };
|
|
1575
|
+
return { label: '查看来源', run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }) };
|
|
1576
|
+
};
|
|
1577
|
+
const previewMenu = dynamicMenu;
|
|
1578
|
+
actions = previewMenu.map(previewAction);
|
|
1579
|
+
const previewViewport = actionViewport(actions, true, effectiveFocus, scrollOffset, responsiveBudget(4, 2));
|
|
1580
|
+
const dependencyGroups = ['direct', 'transitive'].map((relation) => ({
|
|
1581
|
+
relation,
|
|
1582
|
+
dependencies: (overview?.dependencies.filter((entry) => entry.relation === relation) ?? [])
|
|
1583
|
+
.sort((left, right) => {
|
|
1584
|
+
const priority = (entry) => {
|
|
1585
|
+
const status = dependencyStatus(entry).status;
|
|
1586
|
+
return status === 'error' ? 0 : status === 'warning' ? 1 : 2;
|
|
1587
|
+
};
|
|
1588
|
+
return priority(left) - priority(right);
|
|
1589
|
+
}),
|
|
1590
|
+
}));
|
|
1591
|
+
const previewContentLines = size.rows - 6;
|
|
1592
|
+
const previewActionLines = previewViewport.limit ?? actions.length;
|
|
1593
|
+
const previewFixedStaticLines = 7 + (plugin.portableSkills && policy.mode !== 'compact' ? 5 : 0);
|
|
1594
|
+
let dependencyLineBudget = Math.max(dependencyGroups.filter((group) => group.dependencies.length > 0).length, previewContentLines - previewActionLines - previewFixedStaticLines);
|
|
1595
|
+
const visibleByRelation = new Map();
|
|
1596
|
+
for (const group of dependencyGroups) {
|
|
1597
|
+
if (group.dependencies.length === 0)
|
|
1598
|
+
continue;
|
|
1599
|
+
const firstItemLines = policy.mode === 'compact' || group.dependencies.length > 1 ? 1 : 2;
|
|
1600
|
+
const hiddenSummaryLines = group.dependencies.length > 1 ? 1 : 0;
|
|
1601
|
+
if (dependencyLineBudget >= firstItemLines + hiddenSummaryLines) {
|
|
1602
|
+
visibleByRelation.set(group.relation, 1);
|
|
1603
|
+
dependencyLineBudget -= firstItemLines + hiddenSummaryLines;
|
|
1604
|
+
}
|
|
1605
|
+
else {
|
|
1606
|
+
visibleByRelation.set(group.relation, 0);
|
|
1607
|
+
dependencyLineBudget -= 1;
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
for (const group of dependencyGroups) {
|
|
1611
|
+
let visibleCount = visibleByRelation.get(group.relation) ?? 0;
|
|
1612
|
+
while (dependencyLineBudget > 0 && visibleCount < group.dependencies.length) {
|
|
1613
|
+
visibleCount += 1;
|
|
1614
|
+
dependencyLineBudget -= 1;
|
|
1615
|
+
}
|
|
1616
|
+
visibleByRelation.set(group.relation, visibleCount);
|
|
1617
|
+
}
|
|
1164
1618
|
const summaryRows = (relation) => {
|
|
1165
|
-
const dependencies =
|
|
1166
|
-
const
|
|
1619
|
+
const dependencies = dependencyGroups.find((group) => group.relation === relation).dependencies;
|
|
1620
|
+
const visibleCount = policy.mode === 'compact' ? Math.min(1, dependencies.length)
|
|
1621
|
+
: visibleByRelation.get(relation) ?? 0;
|
|
1622
|
+
const visible = dependencies.slice(0, visibleCount);
|
|
1623
|
+
const compactRows = policy.mode === 'compact' || dependencies.length > visible.length;
|
|
1167
1624
|
return [
|
|
1168
|
-
...visible.map((entry) =>
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1625
|
+
...visible.map((entry) => {
|
|
1626
|
+
const state = dependencyStatus(entry);
|
|
1627
|
+
return _jsx(StaticRow, { title: entry.displayName, description: dependencyRelationDescription(entry), meta: state.value, status: state.status, compact: compactRows }, entry.pluginId);
|
|
1628
|
+
}),
|
|
1629
|
+
...(visible.length === 0 && dependencies.length > 0
|
|
1630
|
+
? [_jsx(StaticRow, { title: `${dependencies.length} 个${relation === 'direct' ? '直接' : '间接'}依赖`, status: "muted", compact: true }, `${relation}:count`)]
|
|
1631
|
+
: dependencies.length > visible.length
|
|
1632
|
+
? [_jsx(StaticRow, { title: `另有 ${dependencies.length - visible.length} 个${relation === 'direct' ? '直接' : '间接'}依赖`, status: "muted", compact: true }, `${relation}:more`)]
|
|
1633
|
+
: []),
|
|
1172
1634
|
];
|
|
1173
1635
|
};
|
|
1636
|
+
const compactDependencyRows = overview?.dependencies.slice(0, 2) ?? [];
|
|
1174
1637
|
content = hasDependencies
|
|
1175
|
-
? _jsxs(Box, { flexDirection: "column", children: [plugin.portableSkills ? _jsx(
|
|
1176
|
-
|
|
1638
|
+
? _jsxs(Box, { flexDirection: "column", children: [plugin.portableSkills && policy.mode !== 'compact' ? _jsx(SectionCard, { title: "\u53EF\u7528\u65B9\u5F0F", children: _jsx(StaticRow, { title: "\u901A\u7528 Skills", meta: `可安装 · ${plugin.dependencySummary.totalCount} 个插件依赖`, description: "\u9009\u62E9 Skill \u548C Agent \u540E\u4EA4\u7ED9 Skills CLI \u5B89\u88C5" }) }) : null, _jsxs(SectionCard, { title: "\u5B89\u88C5\u5185\u5BB9", children: [policy.mode !== 'compact' ? _jsx(StaticRow, { title: plugin.displayName, description: "\u4F60\u9009\u62E9\u7684\u63D2\u4EF6", status: "info" }) : null, plugin.portableSkills && policy.mode === 'compact' ? _jsx(StaticRow, { title: "\u901A\u7528 Skills", meta: `可安装 · ${plugin.dependencySummary.totalCount} 个插件依赖`, compact: true }) : null, policy.mode === 'compact' ? compactDependencyRows.map((entry) => {
|
|
1639
|
+
const state = dependencyStatus(entry);
|
|
1640
|
+
return _jsx(StaticRow, { title: entry.displayName, meta: `${entry.relation === 'direct' ? '直接' : '间接'} · ${state.value}`, status: state.status, compact: true }, entry.pluginId);
|
|
1641
|
+
}) : _jsxs(_Fragment, { children: [summaryRows('direct'), summaryRows('transitive')] }), policy.mode === 'compact' && (overview?.dependencies.length ?? 0) > compactDependencyRows.length ? _jsx(StaticRow, { title: `另有 ${(overview?.dependencies.length ?? 0) - compactDependencyRows.length} 个依赖`, status: "muted", compact: true }) : null, policy.mode !== 'compact' ? _jsx(StaticRow, { title: `${plugin.dependencySummary.totalCount + 1} 个插件将作为一个安装计划检查`, description: "\u4F9D\u8D56\u4ECE\u540C\u4E00\u4E2A\u6765\u6E90\u5FEB\u7167\u89E3\u6790\u3002" }) : null] }), _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact' || previewViewport.limit !== undefined, offset: previewViewport.offset, limit: previewViewport.limit })] })
|
|
1642
|
+
: _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { title: "\u53EF\u7528\u76EE\u6807\u5E73\u53F0", children: [_jsx(StaticRow, { title: plugin.supportedPlatforms.map(platformLabel).join(' · ') || '暂无可用目标平台' }), plugin.portableSkills ? _jsx(StaticRow, { title: "\u901A\u7528 Skills", meta: "\u53EF\u5B89\u88C5", description: "\u9009\u62E9 Skill \u548C Agent \u540E\u4EA4\u7ED9 Skills CLI \u5B89\u88C5", compact: policy.mode === 'compact' }) : null] }), _jsx(SectionCard, { title: "\u4F7F\u7528\u8BF4\u660E", children: _jsx(StaticRow, { title: "\u5728 Agent \u4F1A\u8BDD\u4E2D\u63D0\u4F9B\u80FD\u529B", status: "success", description: "\u5B89\u88C5\u4E0D\u4F1A\u81EA\u52A8\u4FEE\u6539\u9879\u76EE\u6E90\u7801\u3002" }) }), _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact', offset: previewViewport.offset, limit: previewViewport.limit })] });
|
|
1177
1643
|
}
|
|
1178
1644
|
else if (route.name === 'portableSkillSelect' && plugin && snapshot.portableDraft) {
|
|
1179
1645
|
const draft = snapshot.portableDraft;
|
|
1180
|
-
const focusedCandidate = draft.candidates[focus];
|
|
1181
1646
|
breadcrumb = `${plugin.displayName} / 通用 Skills`;
|
|
1182
1647
|
context = `${draft.selectedCount} / ${draft.totalCount} 已选择`;
|
|
1183
1648
|
title = '选择要安装的 Skills';
|
|
1184
1649
|
description = '已根据插件依赖和当前设置准备候选;默认全部选择。';
|
|
1185
1650
|
const candidateActions = draft.candidates.map((candidate) => ({
|
|
1186
|
-
label:
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
: `${candidate.description} · ${candidate.providerDisplayName} · ${candidate.relation === 'requested' ? '当前插件' : candidate.relation === 'direct' ? '直接依赖' : '间接依赖'}${candidate.dynamic ? ' · 按设置生成' : ''}`,
|
|
1651
|
+
label: candidate.name,
|
|
1652
|
+
selected: candidate.selected,
|
|
1653
|
+
description: `${candidate.description}${candidate.dynamic ? ' · 按设置生成' : ''}`,
|
|
1190
1654
|
run: () => props.controller.togglePortableSkill(candidate.name),
|
|
1191
1655
|
}));
|
|
1192
1656
|
actions = [
|
|
1193
1657
|
...candidateActions,
|
|
1194
|
-
{ label: '继续', disabled: draft.canContinue ? undefined : '至少选择一个 Skill', run: () => go({ name: 'portableAgentMode', pluginId: plugin.pluginId }) },
|
|
1658
|
+
{ label: '继续', meta: draft.canContinue ? undefined : '至少选择一个 Skill', disabled: draft.canContinue ? undefined : '至少选择一个 Skill', run: () => go({ name: 'portableAgentMode', pluginId: plugin.pluginId }) },
|
|
1195
1659
|
...(plugin.dependencySummary?.totalCount ? [{ label: '查看依赖关系', run: () => go({ name: 'portableDependencies', pluginId: plugin.pluginId }) }] : []),
|
|
1196
1660
|
];
|
|
1197
|
-
const
|
|
1661
|
+
const candidateViewport = portableSkillCandidateViewport(candidateActions, draft.candidates, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(actions.length - draft.candidates.length
|
|
1662
|
+
+ (portableVariantCandidates.length > 0 ? 3 : 0)));
|
|
1663
|
+
const viewport = showTechnical
|
|
1664
|
+
? { offset: focus < draft.candidates.length ? focus : candidateViewport.offset, limit: 1 }
|
|
1665
|
+
: candidateViewport;
|
|
1666
|
+
const visibleCandidateEntries = draft.candidates
|
|
1667
|
+
.map((candidate, index) => ({ candidate, action: candidateActions[index], index }))
|
|
1668
|
+
.slice(viewport.offset, viewport.offset + viewport.limit);
|
|
1669
|
+
const providerGroups = [...new Map(visibleCandidateEntries.map((entry) => [
|
|
1670
|
+
entry.candidate.providerPluginId,
|
|
1671
|
+
visibleCandidateEntries.filter((candidate) => candidate.candidate.providerPluginId === entry.candidate.providerPluginId),
|
|
1672
|
+
])).values()];
|
|
1673
|
+
const variantCandidates = portableVariantCandidates;
|
|
1674
|
+
const focusedVariant = variantCandidates.find((candidate) => candidate.name === draft.candidates[focus]?.name);
|
|
1675
|
+
const visibleVariant = visibleCandidateEntries.find((entry) => portableScopeVariantsDiffer(entry.candidate))?.candidate;
|
|
1676
|
+
const technicalVariant = focusedVariant ?? visibleVariant ?? variantCandidates[0];
|
|
1198
1677
|
help = '↑↓ 移动 · Space 切换 · a 全选 · Enter 继续 · Esc 返回';
|
|
1199
|
-
content = _jsxs(Box, { flexDirection: "column", children: [
|
|
1678
|
+
content = _jsxs(Box, { flexDirection: "column", children: [showTechnical ? visibleCandidateEntries.map(({ action, index }) => _jsx(ToggleRow, { title: action.label, selected: Boolean(action.selected), focused: focus === index, compact: true }, String(action.label))) : providerGroups.map((entries) => {
|
|
1679
|
+
const provider = entries[0].candidate;
|
|
1680
|
+
const relation = provider.relation === 'requested' ? '当前插件'
|
|
1681
|
+
: provider.relation === 'direct' ? '直接依赖插件' : '间接依赖插件';
|
|
1682
|
+
return _jsx(SectionCard, { title: provider.providerDisplayName, meta: relation, compact: policy.mode === 'compact', children: entries.map(({ action, index }) => _jsx(ToggleRow, { title: action.label, description: action.description, selected: Boolean(action.selected), focused: focus === index, compact: policy.mode === 'compact' }, String(action.label))) }, provider.providerPluginId);
|
|
1683
|
+
}), variantCandidates.length > 0
|
|
1684
|
+
? showTechnical
|
|
1685
|
+
? _jsx(StaticRow, { title: `! ${variantCandidates.length} 个 Skill 存在范围变体`, status: "warning", compact: true })
|
|
1686
|
+
: _jsx(Callout, { status: "warning", title: `! ${variantCandidates.length} 个 Skill 在不同安装范围会生成不同内容。选择范围后会重新确认最终内容。` })
|
|
1687
|
+
: null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: draft.candidates.length }), showTechnical && technicalVariant ? _jsxs(TechnicalDetails, { title: "\u8303\u56F4\u53D8\u4F53", children: [_jsx(StaticRow, { title: `${technicalVariant.name} · ${portableScopeVariantLabel('project', technicalVariant.scopeDetails.project)}`, compact: true }, `${technicalVariant.name}:project`), _jsx(StaticRow, { title: `${technicalVariant.name} · ${portableScopeVariantLabel('user', technicalVariant.scopeDetails.user)}`, compact: true }, `${technicalVariant.name}:user`), variantCandidates.length > 1 ? _jsx(StaticRow, { title: `另有 ${variantCandidates.length - 1} 个 Skill 的范围变体已折叠`, status: "muted", compact: true }) : null] }) : null] });
|
|
1200
1688
|
}
|
|
1201
1689
|
else if (route.name === 'portableDependencies' && plugin && snapshot.portableDraft) {
|
|
1202
1690
|
const overview = dependencyOverview(plugin);
|
|
@@ -1205,51 +1693,49 @@ export function ConsoleAppV4(props) {
|
|
|
1205
1693
|
title = '生成这些候选 Skills 需要的插件';
|
|
1206
1694
|
description = '插件依赖用于生成,不能在这里取消。';
|
|
1207
1695
|
actions = [
|
|
1208
|
-
...(overview?.dependencies ?? []).map((dependency) => ({
|
|
1209
|
-
label: dependency.displayName,
|
|
1210
|
-
description: `${dependency.relation === 'direct' ? '直接依赖' : '间接依赖'} · 贡献 ${snapshot.portableDraft.candidates.filter((candidate) => candidate.providerPluginId === dependency.pluginId).length} 个候选 Skill`,
|
|
1211
|
-
})),
|
|
1212
1696
|
{ label: '返回 Skill 选择', run: back },
|
|
1213
|
-
{ label: '技术详情', run: () => setShowTechnical((value) => !value) },
|
|
1697
|
+
{ label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => setShowTechnical((value) => !value) },
|
|
1214
1698
|
];
|
|
1215
|
-
const
|
|
1216
|
-
|
|
1699
|
+
const dependencies = overview?.dependencies ?? [];
|
|
1700
|
+
const visibleDependencies = dependencies.slice(0, showTechnical ? 1 : responsiveBudget(2, 3));
|
|
1701
|
+
const technicalDependencies = dependencies.slice(0, 1);
|
|
1702
|
+
const compactTechnicalSummary = policy.mode === 'compact' && showTechnical;
|
|
1703
|
+
content = _jsxs(Box, { flexDirection: "column", children: [compactTechnicalSummary ? _jsxs(SectionCard, { title: "provider \u6458\u8981", meta: `${dependencies.length + 1} 个`, compact: true, children: [_jsx(StaticRow, { title: plugin.displayName, meta: "\u6839 provider", compact: true }), _jsx(StaticRow, { title: `${dependencies.length} 个依赖 providers`, meta: "\u7528\u4E8E\u751F\u6210", compact: true })] }) : _jsx(SectionCard, { title: "\u5F53\u524D\u63D2\u4EF6", meta: "\u6839 provider", children: _jsx(StaticRow, { title: plugin.displayName, description: "\u63D0\u4F9B\u672C\u6B21\u5B89\u88C5\u5165\u53E3", meta: `贡献 ${snapshot.portableDraft.candidates.filter((candidate) => candidate.relation === 'requested').length} 个 Skills`, compact: policy.mode === 'compact' }) }), compactTechnicalSummary ? null : _jsxs(SectionCard, { title: "\u4F9D\u8D56 providers", meta: `${dependencies.length} 个`, children: [visibleDependencies.map((dependency) => _jsx(StaticRow, { title: dependency.displayName, description: `${dependency.relation === 'direct' ? '根 provider 的直接依赖' : '依赖链中的间接 provider'} · ${dependency.requiredBy.map((entry) => entry.displayName).join('、')} 需要它`, meta: `贡献 ${snapshot.portableDraft.candidates.filter((candidate) => candidate.providerPluginId === dependency.pluginId).length} 个 Skills`, compact: policy.mode === 'compact' }, dependency.pluginId)), dependencies.length > visibleDependencies.length ? _jsx(StaticRow, { title: `另有 ${dependencies.length - visibleDependencies.length} 个依赖 provider`, status: "muted", compact: true }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true }), showTechnical ? _jsx(TechnicalDetails, { children: technicalDependencies.map((entry) => _jsx(StaticRow, { title: entry.displayName, meta: `${entry.pluginId}${compactTechnicalSummary && dependencies.length > technicalDependencies.length ? ` · 另有 ${dependencies.length - technicalDependencies.length} 个依赖` : ''}`, compact: true }, entry.pluginId)) }) : null] });
|
|
1217
1704
|
}
|
|
1218
1705
|
else if (route.name === 'portableAgentMode' && plugin && snapshot.portableDraft) {
|
|
1219
1706
|
breadcrumb = `${plugin.displayName} / 通用 Skills / Agent`;
|
|
1220
1707
|
title = '安装到哪些 Agent?';
|
|
1221
1708
|
description = '可以自动选择检测到的 Agent,也可以限制到指定 Agent。';
|
|
1222
1709
|
actions = [
|
|
1223
|
-
{ label: '
|
|
1224
|
-
{ label: '指定 Agent…', description: '输入一个或多个
|
|
1710
|
+
{ label: '使用 Skills CLI 默认选择', description: '继续 Skills CLI 默认选择', meta: '默认', selected: snapshot.portableDraft.agentMode === 'automatic', run: () => { props.controller.setPortableAgentMode('automatic'); go({ name: 'portableScope', pluginId: plugin.pluginId }); } },
|
|
1711
|
+
{ label: '指定 Agent…', description: '输入一个或多个 Agent ID', selected: snapshot.portableDraft.agentMode === 'explicit', run: () => go({ name: 'portableAgentInput', pluginId: plugin.pluginId }) },
|
|
1225
1712
|
];
|
|
1226
|
-
content = _jsx(
|
|
1713
|
+
content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' });
|
|
1227
1714
|
}
|
|
1228
1715
|
else if (route.name === 'portableAgentInput' && plugin) {
|
|
1229
1716
|
breadcrumb = `${plugin.displayName} / 通用 Skills / Agent`;
|
|
1230
1717
|
context = '指定';
|
|
1231
1718
|
title = '输入 Agent ID';
|
|
1232
|
-
description =
|
|
1233
|
-
|
|
1719
|
+
description = '多个 Agent 可使用逗号或空格分隔。';
|
|
1720
|
+
help = '输入 Agent ID · Enter 保存 · Esc 返回';
|
|
1721
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(InputCard, { focused: true, title: portableAgentInput || ' ', description: inputError ?? 'Enter 保存并继续 · Esc 返回;q 和 b 会作为输入。', status: inputError ? 'error' : undefined }), _jsx(StaticRow, { title: "\u793A\u4F8B", meta: "cursor, claude-code, codex", status: "muted", compact: true })] });
|
|
1234
1722
|
}
|
|
1235
1723
|
else if (route.name === 'portableScope' && plugin && snapshot.portableDraft) {
|
|
1236
1724
|
breadcrumb = `${plugin.displayName} / 通用 Skills / 范围`;
|
|
1237
1725
|
title = '在哪里使用这些 Skills?';
|
|
1238
1726
|
description = `已选择 ${snapshot.portableDraft.selectedCount} 个 Skills · Agent:${snapshot.portableDraft.agentMode === 'automatic' ? '自动选择' : snapshot.portableDraft.requestedAgents.join('、')}`;
|
|
1239
1727
|
actions = [
|
|
1240
|
-
{ label: '当前项目', description: '
|
|
1241
|
-
{ label: '所有项目', description: '
|
|
1728
|
+
{ label: '当前项目', description: '仅供当前项目中的 Agent 使用', meta: '推荐', selected: snapshot.portableDraft.scope === 'project', run: () => props.controller.setPortableScope('project') },
|
|
1729
|
+
{ label: '所有项目', description: '安装到用户范围,供所有项目使用', selected: snapshot.portableDraft.scope === 'user', run: () => props.controller.setPortableScope('user') },
|
|
1730
|
+
{ label: '继续', description: '按所选范围重新确认最终内容', run: () => beginPortableReview(plugin.pluginId, snapshot.portableDraft.scope) },
|
|
1242
1731
|
];
|
|
1243
|
-
content = _jsx(
|
|
1732
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions.slice(0, 2), focus: focus, kind: "choice", compact: policy.mode === 'compact' }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: 2 })] });
|
|
1244
1733
|
}
|
|
1245
1734
|
else if (route.name === 'portableBlocker' && plugin) {
|
|
1246
1735
|
breadcrumb = `${plugin.displayName} / 通用 Skills`;
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
description = route.code === 'requiredConfigMissing' ? '插件缺少生成内容所需的设置。'
|
|
1251
|
-
: route.code === 'portableScopeUnavailable' ? '至少一个所选 Skill 不适用于这个范围。请返回 Skill 选择或更改范围。'
|
|
1252
|
-
: route.code === 'portableSkillsCliMissing' || route.code === 'portableSkillsCliIncompatible' ? '没有找到兼容的 Skills CLI。' : '候选 Skill 没有通过 Portable 校验。';
|
|
1736
|
+
const diagnostic = portableBlockerDiagnostic(route);
|
|
1737
|
+
title = diagnostic.title;
|
|
1738
|
+
description = '';
|
|
1253
1739
|
actions = [
|
|
1254
1740
|
...(route.code === 'requiredConfigMissing' ? [{ label: '完成插件设置', run: () => {
|
|
1255
1741
|
props.controller.replace({ name: 'configLayer', pluginId: plugin.pluginId });
|
|
@@ -1257,7 +1743,8 @@ export function ConsoleAppV4(props) {
|
|
|
1257
1743
|
setScrollOffset(0);
|
|
1258
1744
|
} }] : []),
|
|
1259
1745
|
...(route.code === 'portableSkillsCliMissing' || route.code === 'portableSkillsCliIncompatible'
|
|
1260
|
-
? [{ label: '查看安装说明', run: () => setShowTechnical((value) => !value) }]
|
|
1746
|
+
? [{ label: showTechnical ? '返回问题摘要' : '查看安装说明', run: () => setShowTechnical((value) => !value) }]
|
|
1747
|
+
: []),
|
|
1261
1748
|
...(route.code === 'portableScopeUnavailable' ? [{ label: '返回 Skill 选择', run: () => {
|
|
1262
1749
|
props.controller.resetPortableFlowToPreview(plugin.pluginId, { name: 'portableSkillSelect', pluginId: plugin.pluginId }, true);
|
|
1263
1750
|
setFocus(0);
|
|
@@ -1265,45 +1752,37 @@ export function ConsoleAppV4(props) {
|
|
|
1265
1752
|
} }] : []),
|
|
1266
1753
|
{ label: '重新检查', run: () => beginPortable(plugin.pluginId) },
|
|
1267
1754
|
{ label: '返回插件预览', run: () => props.controller.resetPortableFlowToPreview(plugin.pluginId) },
|
|
1755
|
+
...(route.code === 'portableSkillsCliMissing' || route.code === 'portableSkillsCliIncompatible'
|
|
1756
|
+
? []
|
|
1757
|
+
: [{ label: showTechnical ? '返回问题摘要' : '技术详情', run: () => setShowTechnical((value) => !value) }]),
|
|
1268
1758
|
];
|
|
1269
|
-
|
|
1759
|
+
const blockerActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(2, 4));
|
|
1760
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(DiagnosticSummary, { diagnostic: diagnostic, technical: showTechnical, compact: policy.mode === 'compact' }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: blockerActionViewport.offset, limit: blockerActionViewport.limit })] });
|
|
1270
1761
|
}
|
|
1271
1762
|
else if (route.name === 'pluginDependencies' && plugin) {
|
|
1272
1763
|
const overview = dependencyOverview(plugin);
|
|
1273
1764
|
const visibleDependents = route.view === 'removeBlocked' && route.platform && route.scope
|
|
1274
1765
|
? props.controller.getBlockingDependents(plugin.pluginId, route.platform, route.scope)
|
|
1275
1766
|
: overview?.dependents ?? [];
|
|
1276
|
-
const relationActions = [
|
|
1277
|
-
...(overview?.dependencies ?? []).map((entry) => {
|
|
1278
|
-
const state = dependencyStatus(entry);
|
|
1279
|
-
return {
|
|
1280
|
-
label: entry.displayName,
|
|
1281
|
-
description: dependencyRelationDescription(entry),
|
|
1282
|
-
meta: dependencyPlacementLabel(entry),
|
|
1283
|
-
status: state.status,
|
|
1284
|
-
run: () => go({ name: 'pluginDetail', pluginId: entry.pluginId }),
|
|
1285
|
-
};
|
|
1286
|
-
}),
|
|
1287
|
-
...visibleDependents.map((entry) => {
|
|
1288
|
-
const state = dependencyStatus(entry);
|
|
1289
|
-
return {
|
|
1290
|
-
label: entry.displayName,
|
|
1291
|
-
description: dependencyRelationDescription(entry),
|
|
1292
|
-
meta: route.view === 'removeBlocked' ? dependentTargetLabel(entry) : dependencyPlacementLabel(entry),
|
|
1293
|
-
status: state.status,
|
|
1294
|
-
run: () => go({ name: 'pluginDetail', pluginId: entry.pluginId }),
|
|
1295
|
-
};
|
|
1296
|
-
}),
|
|
1297
|
-
];
|
|
1298
1767
|
const dependencyCount = overview?.dependencies.length ?? 0;
|
|
1299
1768
|
const dependentCount = visibleDependents.length;
|
|
1769
|
+
const dependencyLimit = policy.mode === 'compact'
|
|
1770
|
+
? dependentCount > 0 ? 1 : 2
|
|
1771
|
+
: dependentCount > 0 ? 3 : 5;
|
|
1772
|
+
const dependentLimit = responsiveBudget(2, 4);
|
|
1773
|
+
const dependencyRows = (entries, limit, dependent = false) => {
|
|
1774
|
+
const visible = entries.slice(0, limit);
|
|
1775
|
+
return _jsxs(_Fragment, { children: [visible.map((entry) => {
|
|
1776
|
+
const state = dependencyStatus(entry);
|
|
1777
|
+
return _jsx(StaticRow, { title: entry.displayName, description: dependencyRelationDescription(entry), meta: dependent && route.view === 'removeBlocked' ? dependentTargetLabel(entry) : dependencyPlacementLabel(entry), status: state.status, compact: policy.mode === 'compact' }, entry.pluginId);
|
|
1778
|
+
}), entries.length > visible.length ? _jsx(StaticRow, { title: `另有 ${entries.length - visible.length} 项`, status: "muted", compact: true }) : null] });
|
|
1779
|
+
};
|
|
1300
1780
|
if (route.view === 'removeBlocked') {
|
|
1301
1781
|
breadcrumb = `${plugin.displayName} / 移除`;
|
|
1302
1782
|
context = '已阻止';
|
|
1303
1783
|
title = `无法移除 ${plugin.displayName}`;
|
|
1304
1784
|
description = '仍有插件依赖它。请先处理依赖者,再重新尝试。';
|
|
1305
1785
|
actions = [
|
|
1306
|
-
...relationActions.slice(dependencyCount),
|
|
1307
1786
|
...(route.platform && route.scope ? [{
|
|
1308
1787
|
label: '强制清理…',
|
|
1309
1788
|
description: '依赖者会变为不完整;下一页仍需明确确认',
|
|
@@ -1323,10 +1802,13 @@ export function ConsoleAppV4(props) {
|
|
|
1323
1802
|
setScrollOffset(0);
|
|
1324
1803
|
} },
|
|
1325
1804
|
{ label: '返回插件详情', run: back },
|
|
1805
|
+
{ label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => setShowTechnical((value) => !value) },
|
|
1326
1806
|
];
|
|
1327
|
-
const
|
|
1328
|
-
|
|
1329
|
-
|
|
1807
|
+
const blockedRemovalViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(5));
|
|
1808
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(Callout, { status: "error", title: `${dependentCount} 个已安装插件仍在使用`, description: "\u6CA1\u6709\u4FEE\u6539\u4EFB\u4F55\u5B89\u88C5\u3002" }), showTechnical ? null : _jsx(SectionCard, { title: "\u4F7F\u7528\u8FD9\u4E2A\u4F9D\u8D56\u7684\u63D2\u4EF6", meta: "\u53CD\u5411\u5F15\u7528", children: dependencyRows(visibleDependents, dependentLimit, true) }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: blockedRemovalViewport.offset, limit: blockedRemovalViewport.limit }), showTechnical ? _jsx(IssueTechnicalDetails, { details: [{
|
|
1809
|
+
code: 'dependencyStillRequired',
|
|
1810
|
+
message: '已安装插件仍在使用这个依赖;强制清理会使依赖者变为不完整。',
|
|
1811
|
+
}], compact: policy.mode === 'compact' }) : null] });
|
|
1330
1812
|
}
|
|
1331
1813
|
else {
|
|
1332
1814
|
breadcrumb = `${plugin.displayName} / 依赖关系`;
|
|
@@ -1334,19 +1816,51 @@ export function ConsoleAppV4(props) {
|
|
|
1334
1816
|
title = hasInstalledPlacement(plugin) ? '依赖与被使用情况' : '安装所需插件';
|
|
1335
1817
|
description = '依赖从同一个来源快照解析;关系页不会修改安装。';
|
|
1336
1818
|
actions = [
|
|
1337
|
-
...relationActions,
|
|
1338
1819
|
{ label: '技术详情', description: '显示 canonical pluginId 和安全错误代码', run: () => setShowTechnical((value) => !value) },
|
|
1339
1820
|
];
|
|
1340
|
-
const
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1821
|
+
const rootStatus = plugin.aggregateStatus === 'installed' ? 'success'
|
|
1822
|
+
: plugin.aggregateStatus === 'needsAttention' ? 'warning'
|
|
1823
|
+
: plugin.aggregateStatus === 'unavailable' ? 'error' : 'muted';
|
|
1824
|
+
const dependencyActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(6));
|
|
1825
|
+
const blockerCount = overview?.blockers.length ?? 0;
|
|
1826
|
+
const useBudgetedRelations = policy.mode === 'compact'
|
|
1827
|
+
|| showTechnical
|
|
1828
|
+
|| dependencyCount + dependentCount + blockerCount > 7;
|
|
1829
|
+
const relationCategories = [
|
|
1830
|
+
...(blockerCount > 0 ? [{ key: 'blocker', count: blockerCount }] : []),
|
|
1831
|
+
...(dependencyCount > 0 ? [{ key: 'dependency', count: dependencyCount }] : []),
|
|
1832
|
+
...(dependentCount > 0 ? [{ key: 'dependent', count: dependentCount }] : []),
|
|
1833
|
+
];
|
|
1834
|
+
const technicalLineBudget = showTechnical ? 4 : 0;
|
|
1835
|
+
const relationItemBudget = Math.max(relationCategories.length, size.rows - 6 - 1 - technicalLineBudget - 4);
|
|
1836
|
+
const relationBudgets = new Map(relationCategories.map((category) => [category.key, 1]));
|
|
1837
|
+
let remainingRelationLines = Math.max(0, relationItemBudget - relationCategories.length);
|
|
1838
|
+
for (const category of relationCategories) {
|
|
1839
|
+
if (category.key === 'blocker')
|
|
1840
|
+
continue;
|
|
1841
|
+
while (remainingRelationLines > 0 && (relationBudgets.get(category.key) ?? 0) < category.count) {
|
|
1842
|
+
relationBudgets.set(category.key, (relationBudgets.get(category.key) ?? 0) + 1);
|
|
1843
|
+
remainingRelationLines -= 1;
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
const prioritizedRelations = (entries) => [...entries].sort((left, right) => {
|
|
1847
|
+
const priority = (entry) => {
|
|
1848
|
+
const status = dependencyStatus(entry).status;
|
|
1849
|
+
return status === 'error' ? 0 : status === 'warning' ? 1 : 2;
|
|
1850
|
+
};
|
|
1851
|
+
return priority(left) - priority(right);
|
|
1852
|
+
});
|
|
1853
|
+
const budgetedRelationRows = (entries, prefix, budget, dependent = false) => {
|
|
1854
|
+
const prioritized = prioritizedRelations(entries);
|
|
1855
|
+
const visible = prioritized.slice(0, budget);
|
|
1856
|
+
return visible.map((entry, index) => {
|
|
1857
|
+
const state = dependencyStatus(entry);
|
|
1858
|
+
const hidden = index === visible.length - 1 ? entries.length - visible.length : 0;
|
|
1859
|
+
const relationMeta = dependent ? dependentTargetLabel(entry) : dependencyPlacementLabel(entry);
|
|
1860
|
+
return _jsx(StaticRow, { title: `${prefix} · ${entry.displayName}`, description: dependencyRelationDescription(entry), meta: `${relationMeta}${hidden > 0 ? ` · 另有 ${hidden} 项` : ''}`, status: state.status, compact: true }, entry.pluginId);
|
|
1861
|
+
});
|
|
1862
|
+
};
|
|
1863
|
+
content = _jsxs(Box, { flexDirection: "column", children: [useBudgetedRelations ? _jsxs(SectionCard, { title: "\u4F9D\u8D56\u5173\u7CFB", compact: true, children: [_jsx(StaticRow, { title: `根插件 · ${plugin.displayName}`, meta: statusText(plugin), status: rootStatus, compact: true }), overview?.blockers.slice(0, relationBudgets.get('blocker') ?? 0).map((blocker, index, blockers) => _jsx(StaticRow, { title: `阻塞 · ${blocker.title}`, meta: index === blockers.length - 1 && blockerCount > blockers.length ? `另有 ${blockerCount - blockers.length} 项` : undefined, description: blocker.explanation, status: "error", compact: true }, blocker.code)), budgetedRelationRows(overview?.dependencies ?? [], '必需依赖', relationBudgets.get('dependency') ?? 0), budgetedRelationRows(visibleDependents, '被依赖', relationBudgets.get('dependent') ?? 0, true), dependencyCount + dependentCount + blockerCount === 0 ? _jsx(StaticRow, { title: "\u6CA1\u6709\u53EF\u663E\u793A\u7684\u4F9D\u8D56\u5173\u7CFB", status: "muted", compact: true }) : null] }) : _jsxs(_Fragment, { children: [_jsx(SectionCard, { title: "\u4F60\u9009\u62E9\u7684\u63D2\u4EF6", meta: "\u6839\u8282\u70B9", children: _jsx(StaticRow, { title: plugin.displayName, meta: statusText(plugin), status: rootStatus }) }), dependencyCount > 0 ? _jsx(SectionCard, { title: "\u5FC5\u9700\u4F9D\u8D56", meta: "\u4F9D\u8D56\u4F18\u5148", children: dependencyRows(overview?.dependencies ?? [], dependencyLimit) }) : null, dependentCount > 0 ? _jsx(SectionCard, { title: "\u4F7F\u7528\u5B83\u7684\u63D2\u4EF6", meta: "\u53CD\u5411\u5F15\u7528", children: dependencyRows(visibleDependents, dependentLimit, true) }) : null, overview?.blockers.map((blocker) => _jsx(Callout, { status: "error", title: blocker.title, description: blocker.explanation }, blocker.code)), dependencyCount + dependentCount + blockerCount === 0 ? _jsx(Callout, { status: "muted", title: "\u6CA1\u6709\u53EF\u663E\u793A\u7684\u4F9D\u8D56\u5173\u7CFB" }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: useBudgetedRelations, offset: dependencyActionViewport.offset, limit: dependencyActionViewport.limit }), showTechnical ? _jsx(TechnicalDetails, { children: _jsx(StaticRow, { title: `${dependencyCount} 依赖 · ${dependentCount} 被依赖 · 已折叠`, meta: plugin.pluginId, compact: true }) }) : null] });
|
|
1350
1864
|
}
|
|
1351
1865
|
}
|
|
1352
1866
|
else if (route.name === 'installPlacement' && plugin) {
|
|
@@ -1357,67 +1871,97 @@ export function ConsoleAppV4(props) {
|
|
|
1357
1871
|
const hasDependencies = (plugin.dependencySummary?.totalCount ?? 0) > 0;
|
|
1358
1872
|
description = hasDependencies
|
|
1359
1873
|
? '根插件和缺失依赖使用同一个位置;健康的所有项目依赖可在当前项目复用。'
|
|
1360
|
-
: '
|
|
1361
|
-
const availableScopes = ['user', 'project'].filter((candidateScope) =>
|
|
1874
|
+
: '选择位置后,再选择一个或多个目标平台。';
|
|
1875
|
+
const availableScopes = ['user', 'project'].filter((candidateScope) => props.controller.listInstallTargets(plugin.pluginId, candidateScope)
|
|
1876
|
+
.some((entry) => entry.compatible));
|
|
1362
1877
|
const reusableDependencies = overview?.dependencies.filter((dependency) => dependency.placements.some((placement) => placement.state === 'installed' && placement.health === 'ready'
|
|
1363
1878
|
&& (scope === 'project' || placement.scope === 'user'))).length ?? 0;
|
|
1364
1879
|
const missingDependencies = Math.max(0, (overview?.summary.totalCount ?? 0) - reusableDependencies);
|
|
1365
1880
|
actions = [
|
|
1366
1881
|
...availableScopes.map((candidateScope) => ({
|
|
1367
|
-
label:
|
|
1882
|
+
label: scopeLabel(candidateScope),
|
|
1883
|
+
selected: candidateScope === scope,
|
|
1368
1884
|
description: candidateScope === 'user'
|
|
1369
1885
|
? hasDependencies ? '根插件和缺失依赖都可在任意项目中使用' : '在任意项目中可使用,适合个人常用能力'
|
|
1370
1886
|
: hasDependencies ? '缺失依赖添加到当前项目;已有所有项目依赖会复用' : '仅在当前项目中使用,适合团队共享',
|
|
1371
1887
|
meta: candidateScope === 'user' ? '推荐' : undefined,
|
|
1372
1888
|
run: () => {
|
|
1889
|
+
const choices = props.controller.listInstallTargets(plugin.pluginId, candidateScope);
|
|
1890
|
+
const compatible = choices.filter((entry) => entry.compatible).map((entry) => entry.platform);
|
|
1891
|
+
const previous = targetSelections[candidateScope];
|
|
1892
|
+
const next = previous
|
|
1893
|
+
? previous.filter((platform) => compatible.includes(platform))
|
|
1894
|
+
: preferredTarget && compatible.includes(preferredTarget)
|
|
1895
|
+
? [preferredTarget]
|
|
1896
|
+
: compatible.length === 1 ? compatible : [];
|
|
1373
1897
|
setScope(candidateScope);
|
|
1374
|
-
|
|
1898
|
+
setSelectedTargets(next);
|
|
1899
|
+
setTargetSelections((current) => ({ ...current, [candidateScope]: next }));
|
|
1900
|
+
go({
|
|
1901
|
+
name: 'installTargets',
|
|
1902
|
+
pluginId: plugin.pluginId,
|
|
1903
|
+
...(compatible.length === 0 ? { view: 'blocked' } : {}),
|
|
1904
|
+
});
|
|
1375
1905
|
},
|
|
1376
1906
|
})),
|
|
1377
|
-
{ label: '继续', disabled: tools.length === 0 ? '没有可安装的工具' : undefined, run: async () => beginReview(installIntent(plugin)) },
|
|
1378
|
-
{ label: '调整工具…', run: () => {
|
|
1379
|
-
const choices = selectedPlatforms(plugin, scope);
|
|
1380
|
-
go({ name: 'installTools', pluginId: plugin.pluginId,
|
|
1381
|
-
...(choices.every((choice) => choice.disabled) ? { view: 'blocked' } : {}) });
|
|
1382
|
-
} },
|
|
1383
1907
|
];
|
|
1384
|
-
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(
|
|
1908
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' }), hasDependencies ? _jsxs(SectionCard, { title: "\u4F9D\u8D56\u5904\u7406", children: [_jsx(StaticRow, { title: `${reusableDependencies} 个依赖会复用现有安装`, status: reusableDependencies > 0 ? 'success' : 'muted' }), _jsx(StaticRow, { title: `${missingDependencies} 个依赖将安装到所选位置`, status: missingDependencies > 0 ? 'info' : 'muted' })] }) : null] });
|
|
1385
1909
|
}
|
|
1386
|
-
else if (route.name === '
|
|
1387
|
-
breadcrumb = `${plugin.displayName} / 添加 /
|
|
1388
|
-
context = '
|
|
1389
|
-
title = '
|
|
1910
|
+
else if (route.name === 'installTargets' && plugin) {
|
|
1911
|
+
breadcrumb = `${plugin.displayName} / 添加 / 目标平台`;
|
|
1912
|
+
context = '第 2 步,共 2 步';
|
|
1913
|
+
title = '选择目标平台';
|
|
1390
1914
|
const hasDependencies = (plugin.dependencySummary?.totalCount ?? 0) > 0;
|
|
1391
1915
|
description = hasDependencies
|
|
1392
|
-
? '
|
|
1916
|
+
? '只有根插件和所有依赖都支持所选位置的目标平台才可选择。'
|
|
1393
1917
|
: '同一插件、同一平台只能安装到一个位置。';
|
|
1394
|
-
const choices =
|
|
1395
|
-
const
|
|
1918
|
+
const choices = props.controller.listInstallTargets(plugin.pluginId, scope);
|
|
1919
|
+
const validSelectedTargets = selectedTargets.filter((platform) => choices.some((choice) => choice.platform === platform && choice.compatible));
|
|
1920
|
+
const allUnavailable = choices.length > 0 && choices.every((choice) => !choice.compatible);
|
|
1396
1921
|
if (route.view === 'blocked' || allUnavailable) {
|
|
1397
1922
|
context = '依赖限制';
|
|
1398
|
-
title = '
|
|
1399
|
-
description = '
|
|
1923
|
+
title = '无法添加到所选目标平台';
|
|
1924
|
+
description = '根插件或必需依赖不支持当前目标平台组合,因此不会进入确认页。';
|
|
1400
1925
|
actions = [
|
|
1401
1926
|
{ label: '查看依赖关系', run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }) },
|
|
1402
1927
|
{ label: '返回调整位置', run: back },
|
|
1403
1928
|
];
|
|
1404
|
-
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(
|
|
1929
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StatusCard, { status: "warning", title: "\u6240\u9009\u76EE\u6807\u5E73\u53F0\u4E0D\u53EF\u7528", children: choices.map((choice) => _jsx(StaticRow, { title: platformLabel(choice.platform), description: choice.disabled ? `不可选 · ${choice.disabled}` : '不可选', status: "muted", compact: policy.mode === 'compact' }, choice.platform)) }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
|
|
1405
1930
|
}
|
|
1406
1931
|
else {
|
|
1407
|
-
|
|
1408
|
-
label:
|
|
1932
|
+
const targetActions = choices.map((choice) => ({
|
|
1933
|
+
label: platformLabel(choice.platform),
|
|
1934
|
+
selected: validSelectedTargets.includes(choice.platform),
|
|
1409
1935
|
description: choice.disabled ?? '当前未安装,支持所选位置',
|
|
1410
|
-
meta:
|
|
1936
|
+
meta: validSelectedTargets.includes(choice.platform) ? '已选择' : undefined,
|
|
1411
1937
|
disabled: choice.disabled,
|
|
1412
1938
|
run: () => {
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1939
|
+
if (!choice.compatible)
|
|
1940
|
+
return;
|
|
1941
|
+
setSelectedTargets((current) => {
|
|
1942
|
+
const next = current.includes(choice.platform)
|
|
1943
|
+
? current.filter((platform) => platform !== choice.platform)
|
|
1944
|
+
: [...current, choice.platform];
|
|
1945
|
+
setTargetSelections((selections) => ({ ...selections, [scope]: next }));
|
|
1946
|
+
return next;
|
|
1947
|
+
});
|
|
1417
1948
|
},
|
|
1418
1949
|
}));
|
|
1419
|
-
|
|
1420
|
-
|
|
1950
|
+
actions = [
|
|
1951
|
+
...targetActions,
|
|
1952
|
+
{
|
|
1953
|
+
label: '确认目标平台',
|
|
1954
|
+
description: validSelectedTargets.length > 0 ? `已选择 ${validSelectedTargets.length} 个平台` : undefined,
|
|
1955
|
+
disabled: validSelectedTargets.length === 0 ? '至少选择一个目标平台' : undefined,
|
|
1956
|
+
run: () => {
|
|
1957
|
+
setDirty(false);
|
|
1958
|
+
void beginReview(installIntent(plugin, validSelectedTargets));
|
|
1959
|
+
},
|
|
1960
|
+
},
|
|
1961
|
+
{ label: '返回选择位置', run: back },
|
|
1962
|
+
];
|
|
1963
|
+
help = '↑↓ 移动 · Enter 选择/确认 · Esc 返回';
|
|
1964
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SemanticRows, { actions: actions, focus: focus, kind: "toggle", compact: policy.mode === 'compact', limit: targetActions.length }), hasDependencies ? _jsx(Callout, { title: "\u76EE\u6807\u5E73\u53F0\u517C\u5BB9\u6027", description: "\u4E0D\u517C\u5BB9\u7684\u76EE\u6807\u5E73\u53F0\u4E0D\u80FD\u9009\u62E9\uFF1B\u8BF7\u67E5\u770B\u4F9D\u8D56\u5173\u7CFB\u4E86\u89E3\u539F\u56E0\u3002" }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: targetActions.length })] });
|
|
1421
1965
|
}
|
|
1422
1966
|
}
|
|
1423
1967
|
else if (route.name === 'pluginDetail' && plugin) {
|
|
@@ -1426,181 +1970,260 @@ export function ConsoleAppV4(props) {
|
|
|
1426
1970
|
title = plugin.displayName;
|
|
1427
1971
|
description = plugin.description;
|
|
1428
1972
|
const dependencySummary = plugin.dependencySummary;
|
|
1429
|
-
const
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
:
|
|
1439
|
-
|
|
1973
|
+
const detailMenu = dynamicMenu;
|
|
1974
|
+
actions = detailMenu.map((action) => {
|
|
1975
|
+
if (action.id === 'platform' || action.id === 'scopeConflict') {
|
|
1976
|
+
const placement = plugin.installedPlacements.find((entry) => entry.platform === action.platform);
|
|
1977
|
+
const projected = placementStatus(placement);
|
|
1978
|
+
return {
|
|
1979
|
+
label: platformLabel(action.platform),
|
|
1980
|
+
description: projected.description,
|
|
1981
|
+
meta: projected.value,
|
|
1982
|
+
status: projected.status,
|
|
1983
|
+
run: () => action.id === 'scopeConflict'
|
|
1984
|
+
? go({ name: 'issueDetail', issueId: `scope:${plugin.pluginId}:${action.platform}` })
|
|
1985
|
+
: go({ name: 'platform', pluginId: plugin.pluginId, platform: action.platform }),
|
|
1986
|
+
};
|
|
1987
|
+
}
|
|
1988
|
+
if (action.id === 'dependencies')
|
|
1989
|
+
return {
|
|
1990
|
+
label: '必需依赖',
|
|
1991
|
+
description: dependencySummary.issueCount > 0 ? `${dependencySummary.issueCount} 项需要处理` : '查看直接、间接依赖和被使用情况',
|
|
1992
|
+
meta: dependencySummaryText(dependencySummary),
|
|
1993
|
+
status: dependencySummary.issueCount > 0 ? 'warning' : undefined,
|
|
1994
|
+
run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }),
|
|
1995
|
+
};
|
|
1996
|
+
if (action.id === 'portableSkills')
|
|
1997
|
+
return {
|
|
1998
|
+
label: '通用 Skills',
|
|
1999
|
+
description: '选择 Skill 和 Agent 后交给 Skills CLI 安装',
|
|
2000
|
+
meta: dependencySummary ? `可安装 · ${dependencySummary.totalCount} 个插件依赖` : '可安装',
|
|
2001
|
+
run: () => beginPortable(plugin.pluginId),
|
|
2002
|
+
};
|
|
2003
|
+
if (action.id === 'settings')
|
|
2004
|
+
return { label: '设置', description: '查看当前实际生效的配置和值来源', meta: `${plugin.configFieldCount} 项配置`, run: () => beginConfigFlow(plugin.pluginId) };
|
|
2005
|
+
if (action.id === 'sourceProblem')
|
|
2006
|
+
return { label: '查看来源问题', status: 'warning', run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }) };
|
|
2007
|
+
return { label: '关于与技术详情', description: '版本、来源和受管内容', run: () => go({ name: 'pluginAbout', pluginId: plugin.pluginId }) };
|
|
1440
2008
|
});
|
|
1441
|
-
const
|
|
1442
|
-
|
|
1443
|
-
description: dependencySummary.issueCount > 0 ? `${dependencySummary.issueCount} 项需要处理` : '查看直接、间接依赖和被使用情况',
|
|
1444
|
-
meta: dependencySummaryText(dependencySummary),
|
|
1445
|
-
status: dependencySummary.issueCount > 0 ? 'warning' : undefined,
|
|
1446
|
-
run: () => go({ name: 'pluginDependencies', pluginId: plugin.pluginId }),
|
|
1447
|
-
}] : [];
|
|
1448
|
-
const portableActions = plugin.portableSkills ? [{
|
|
1449
|
-
label: '通用 Skills',
|
|
1450
|
-
description: '由插件提供;安装状态未知',
|
|
1451
|
-
meta: '已提供',
|
|
1452
|
-
run: () => beginPortable(plugin.pluginId),
|
|
1453
|
-
}] : [];
|
|
1454
|
-
actions = [
|
|
1455
|
-
...placementActions,
|
|
1456
|
-
...dependencyActions,
|
|
1457
|
-
...portableActions,
|
|
1458
|
-
{ label: '设置', description: '个人默认、当前项目和本机覆盖', meta: `${plugin.configFieldCount} 项配置`, run: () => go({ name: 'configLayer', pluginId: plugin.pluginId }) },
|
|
1459
|
-
{ label: '关于与技术详情', description: '版本、来源和受管内容', run: () => go({ name: 'pluginAbout', pluginId: plugin.pluginId }) },
|
|
1460
|
-
];
|
|
1461
|
-
const dependencyOffset = placementActions.length;
|
|
1462
|
-
const portableOffset = dependencyOffset + dependencyActions.length;
|
|
1463
|
-
const settingOffset = portableOffset + portableActions.length;
|
|
1464
|
-
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(Bordered, { title: "\u4F7F\u7528\u72B6\u6001", children: _jsx(ActionRows, { actions: placementActions, focus: focus }) }), dependencySummary ? _jsx(Bordered, { title: "\u4F9D\u8D56\u5173\u7CFB", children: _jsx(ActionRows, { actions: dependencyActions, focus: focus - dependencyOffset }) }) : null, plugin.portableSkills ? _jsx(Bordered, { title: "\u901A\u7528 Skills", children: _jsx(ActionRows, { actions: portableActions, focus: focus - portableOffset }) }) : null, _jsx(Bordered, { title: "\u63D2\u4EF6\u8BBE\u7F6E", children: _jsx(ActionRows, { actions: actions.slice(settingOffset, -1), focus: focus - settingOffset }) }), _jsx(ActionRows, { actions: actions.slice(-1), focus: focus - actions.length + 1 })] });
|
|
2009
|
+
const detailViewport = actionViewport(actions, policy.mode === 'compact', effectiveFocus, scrollOffset, pageActionBudget(4));
|
|
2010
|
+
content = _jsx(SectionCard, { title: "\u63D2\u4EF6\u64CD\u4F5C", children: _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact', offset: detailViewport.offset, limit: detailViewport.limit }) });
|
|
1465
2011
|
}
|
|
1466
2012
|
else if (route.name === 'platform' && plugin) {
|
|
1467
2013
|
const placement = props.controller.getPlatformPlacement(plugin.pluginId, route.platform);
|
|
2014
|
+
const platformMenu = dynamicMenu;
|
|
2015
|
+
const typedIssue = placement?.state === 'installed' && placement.health === 'error'
|
|
2016
|
+
? props.controller.listUserIssues().find((candidate) => candidate.pluginId === plugin.pluginId
|
|
2017
|
+
&& candidate.platform === route.platform
|
|
2018
|
+
&& candidate.scope === placement.scope)
|
|
2019
|
+
: undefined;
|
|
2020
|
+
const sourceIssue = plugin.aggregateStatus === 'unavailable'
|
|
2021
|
+
? props.controller.listUserIssues().find((candidate) => candidate.entity.kind === 'source' && candidate.sourceId === plugin.sourceId)
|
|
2022
|
+
: undefined;
|
|
2023
|
+
const platformAction = (action) => {
|
|
2024
|
+
if (action.id === 'sourceProblem')
|
|
2025
|
+
return { label: '查看来源问题', description: `${plugin.sourceId} 无可用缓存`, run: () => go({ name: 'issueDetail', issueId: `source:${plugin.sourceId}:unavailable` }) };
|
|
2026
|
+
if (action.id === 'refreshSource')
|
|
2027
|
+
return { label: '重新检查状态', description: '不会修改现场内容', run: () => props.controller.refreshSource(plugin.sourceId) };
|
|
2028
|
+
if (action.id === 'add')
|
|
2029
|
+
return { label: `添加到 ${platformLabel(route.platform)}…`, description: '选择使用位置', run: () => {
|
|
2030
|
+
const initialScope = placement?.state === 'notInstalled'
|
|
2031
|
+
? placement.supportedScopes[0] ?? scope
|
|
2032
|
+
: scope;
|
|
2033
|
+
setScope(initialScope);
|
|
2034
|
+
setSelectedTargets([]);
|
|
2035
|
+
setTargetSelections({});
|
|
2036
|
+
setPreferredTarget(route.platform);
|
|
2037
|
+
go({ name: 'installPlacement', pluginId: plugin.pluginId });
|
|
2038
|
+
} };
|
|
2039
|
+
if (action.id === 'about')
|
|
2040
|
+
return { label: '查看支持信息', description: 'manifest 与平台能力', run: () => go({ name: 'pluginAbout', pluginId: plugin.pluginId }) };
|
|
2041
|
+
if (action.id === 'scopeConflict')
|
|
2042
|
+
return { label: '查看位置冲突问题', run: () => go({ name: 'issueDetail', issueId: `scope:${plugin.pluginId}:${route.platform}` }) };
|
|
2043
|
+
if (action.id === 'upgrade')
|
|
2044
|
+
return { label: `升级到 ${action.availableVersion}…`, description: '使用当前来源中的版本重建', run: () => beginReview({ kind: 'pluginWrite', action: 'upgrade', pluginId: plugin.pluginId, platform: action.platform, scope: action.scope }) };
|
|
2045
|
+
if (action.id === 'reinstall')
|
|
2046
|
+
return { label: placement?.state === 'installed' && placement.health === 'incomplete' ? '重新连接缺失内容…' : '重新安装…', description: '保留现有设置并重新生成受管内容', run: () => beginReview({ kind: 'pluginWrite', action: 'reinstall', pluginId: plugin.pluginId, platform: action.platform, scope: action.scope }) };
|
|
2047
|
+
if (action.id === 'remove')
|
|
2048
|
+
return { label: `从${scopeLabel(action.scope)}移除…`, description: '保留插件设置', status: 'error', run: () => beginPluginRemoval(plugin, action.platform, action.scope) };
|
|
2049
|
+
if (action.id === 'technical')
|
|
2050
|
+
return typedIssue
|
|
2051
|
+
? { label: '查看对应问题', description: typedIssue.explanation, run: () => go({ name: 'issueDetail', issueId: typedIssue.issueId }) }
|
|
2052
|
+
: { label: placement?.state === 'installed' && placement.health === 'error' ? '查看安全技术详情' : placement?.state === 'installed' && placement.health === 'incomplete' ? '查看影响' : '查看安装详情', description: '组件与诊断信息', run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }) };
|
|
2053
|
+
return { label: '返回插件', run: back };
|
|
2054
|
+
};
|
|
1468
2055
|
breadcrumb = `${plugin.displayName} / ${platformLabel(route.platform)}`;
|
|
1469
2056
|
if (plugin.aggregateStatus === 'unavailable') {
|
|
2057
|
+
const sourceOverview = props.controller.getSourceOverview(plugin.sourceId);
|
|
2058
|
+
const diagnostic = sourceIssue?.diagnostic ?? sourceDiagnostic(plugin.sourceId, sourceOverview.health?.state ?? sourceOverview.source?.state, sourceOverview.detail);
|
|
1470
2059
|
title = `暂时无法管理 ${platformLabel(route.platform)}`;
|
|
1471
|
-
description = '
|
|
1472
|
-
actions =
|
|
1473
|
-
content = _jsxs(Box, { flexDirection: "column", children: [
|
|
2060
|
+
description = '';
|
|
2061
|
+
actions = platformMenu.map(platformAction);
|
|
2062
|
+
content = _jsxs(Box, { flexDirection: "column", children: [diagnostic
|
|
2063
|
+
? _jsx(DiagnosticSummary, { diagnostic: diagnostic, compact: policy.mode === 'compact' })
|
|
2064
|
+
: null, _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact' })] });
|
|
1474
2065
|
}
|
|
1475
2066
|
else if (!placement || placement.state === 'notInstalled') {
|
|
1476
2067
|
title = `尚未添加到 ${platformLabel(route.platform)}`;
|
|
1477
2068
|
description = '此插件支持所列使用位置。';
|
|
1478
|
-
actions =
|
|
1479
|
-
|
|
1480
|
-
? placement.supportedScopes[0] ?? scope
|
|
1481
|
-
: scope;
|
|
1482
|
-
setScope(initialScope);
|
|
1483
|
-
setTools([route.platform]);
|
|
1484
|
-
go({ name: 'installPlacement', pluginId: plugin.pluginId });
|
|
1485
|
-
} }, { label: '查看支持信息', description: 'manifest 与平台能力', run: () => go({ name: 'pluginAbout', pluginId: plugin.pluginId }) }];
|
|
1486
|
-
content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Bordered, { children: [_jsx(Text, { color: tuiTheme.colors.muted, children: "\u25CB \u672A\u6DFB\u52A0" }), _jsx(Text, { color: tuiTheme.colors.muted, children: "\u63D2\u4EF6\u8BBE\u7F6E\u4FDD\u6301\u4E0D\u53D8" })] }), _jsx(ActionRows, { actions: actions, focus: focus })] });
|
|
2069
|
+
actions = platformMenu.map(platformAction);
|
|
2070
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StatusCard, { status: "muted", title: "\u672A\u6DFB\u52A0", description: "\u63D2\u4EF6\u8BBE\u7F6E\u4FDD\u6301\u4E0D\u53D8" }), _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact' })] });
|
|
1487
2071
|
}
|
|
1488
2072
|
else if (placement.state === 'scopeConflict') {
|
|
1489
2073
|
title = '安装位置冲突';
|
|
1490
2074
|
description = '正常状态只能保留一个位置。';
|
|
1491
|
-
actions =
|
|
1492
|
-
content = _jsx(
|
|
2075
|
+
actions = platformMenu.map(platformAction);
|
|
2076
|
+
content = _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact' });
|
|
1493
2077
|
}
|
|
1494
2078
|
else if (placement.health === 'error') {
|
|
1495
|
-
const
|
|
1496
|
-
|
|
1497
|
-
|
|
2079
|
+
const diagnostic = typedIssue?.diagnostic ?? {
|
|
2080
|
+
severity: 'error',
|
|
2081
|
+
title: '状态检查失败',
|
|
2082
|
+
userMessage: '平台状态检查失败,当前不能安全选择写操作。',
|
|
2083
|
+
impact: [
|
|
2084
|
+
{ state: 'blocked', message: '当前只提供只读操作。' },
|
|
2085
|
+
{ state: 'preserved', message: '现有安装和已保存设置不会因本页自动改变。' },
|
|
2086
|
+
],
|
|
2087
|
+
nextStep: '查看安全技术详情,然后重新检查当前状态。',
|
|
2088
|
+
technical: {
|
|
2089
|
+
code: 'targetStateError',
|
|
2090
|
+
stage: 'read-platform-state',
|
|
2091
|
+
context: [
|
|
2092
|
+
{ key: 'pluginId', value: plugin.pluginId },
|
|
2093
|
+
{ key: 'platform', value: route.platform },
|
|
2094
|
+
{ key: 'scope', value: placement.scope },
|
|
2095
|
+
],
|
|
2096
|
+
},
|
|
2097
|
+
};
|
|
1498
2098
|
title = `${platformLabel(route.platform)} 暂时不可管理`;
|
|
1499
|
-
description =
|
|
1500
|
-
actions =
|
|
1501
|
-
|
|
1502
|
-
{ label: '查看安全技术详情', description: '只显示类型化状态和错误码', run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }) },
|
|
1503
|
-
{ label: '返回插件', run: back },
|
|
1504
|
-
];
|
|
1505
|
-
content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Bordered, { children: [_jsx(Text, { color: tuiTheme.statusColors.error, children: "\u00D7 \u72B6\u6001\u68C0\u67E5\u5931\u8D25" }), _jsx(Text, { color: tuiTheme.colors.muted, children: "\u5F53\u524D\u53EA\u63D0\u4F9B\u53EA\u8BFB\u64CD\u4F5C" })] }), _jsx(ActionRows, { actions: actions, focus: focus })] });
|
|
2099
|
+
description = '';
|
|
2100
|
+
actions = platformMenu.map(platformAction);
|
|
2101
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(DiagnosticSummary, { diagnostic: diagnostic, compact: policy.mode === 'compact' }), _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact' })] });
|
|
1506
2102
|
}
|
|
1507
2103
|
else if (placement.health === 'incomplete') {
|
|
1508
2104
|
title = `${platformLabel(route.platform)} ${placement.health === 'incomplete' ? '安装不完整' : '暂时不可用'}`;
|
|
1509
2105
|
description = '缺少一项必要内容,插件可能无法稳定启动。';
|
|
1510
|
-
actions =
|
|
1511
|
-
|
|
1512
|
-
{ label: '查看影响', description: '缺少的内容和范围', run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }) },
|
|
1513
|
-
{ label: `从${scopeLabel(placement.scope)}移除…`, description: '保留插件设置', status: 'error', run: () => beginPluginRemoval(plugin, route.platform, placement.scope) },
|
|
1514
|
-
];
|
|
1515
|
-
content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Bordered, { children: [_jsx(Text, { color: tuiTheme.statusColors.warning, children: "! \u9700\u8981\u5904\u7406" }), _jsxs(Text, { color: tuiTheme.colors.muted, children: [scopeLabel(placement.scope), " \u00B7 ", plugin.sourceLabel] })] }), _jsx(ActionRows, { actions: actions, focus: focus })] });
|
|
2106
|
+
actions = platformMenu.map(platformAction);
|
|
2107
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StatusCard, { status: "warning", title: "\u9700\u8981\u5904\u7406", description: `${scopeLabel(placement.scope)} · ${plugin.sourceLabel}` }), _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact' })] });
|
|
1516
2108
|
}
|
|
1517
2109
|
else {
|
|
1518
2110
|
title = `已安装到${scopeLabel(placement.scope)}`;
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
{ label: `从${scopeLabel(placement.scope)}移除…`, description: '保留插件设置', status: 'error', run: () => beginPluginRemoval(plugin, route.platform, placement.scope) },
|
|
1524
|
-
{ label: '查看安装详情', description: '组件与诊断信息', run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }) },
|
|
1525
|
-
];
|
|
1526
|
-
content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(Bordered, { children: [_jsx(Text, { color: tuiTheme.statusColors.success, children: "\u2713 \u72B6\u6001\u6B63\u5E38" }), _jsxs(Text, { color: tuiTheme.colors.muted, children: [scopeLabel(placement.scope), plugin.version ? ` · 当前版本 ${plugin.version}` : ''] })] }), _jsx(ActionRows, { actions: actions, focus: focus })] });
|
|
2111
|
+
const installedVersion = placement.revision.installedVersion;
|
|
2112
|
+
description = `当前检查正常${installedVersion ? `;已安装版本 ${installedVersion}` : ''}。`;
|
|
2113
|
+
actions = platformMenu.map(platformAction);
|
|
2114
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(StatusCard, { status: "success", title: "\u72B6\u6001\u6B63\u5E38", description: `${scopeLabel(placement.scope)}${installedVersion ? ` · 已安装版本 ${installedVersion}` : ''}` }), _jsx(SemanticRows, { actions: actions, focus: effectiveFocus, kind: "action", compact: policy.mode === 'compact' })] });
|
|
1527
2115
|
}
|
|
1528
2116
|
}
|
|
1529
2117
|
else if (route.name === 'configLayer' && plugin) {
|
|
1530
2118
|
breadcrumb = `${plugin.displayName} / 设置`;
|
|
1531
|
-
|
|
1532
|
-
|
|
2119
|
+
const selectedField = route.fieldKey && configPage
|
|
2120
|
+
? configPage.groups.flatMap((group) => group.fields).find((field) => field.key === route.fieldKey)
|
|
2121
|
+
: undefined;
|
|
2122
|
+
const selectedPreset = route.presetId && configPage
|
|
2123
|
+
? configPage.presets.find((preset) => preset.id === route.presetId)
|
|
2124
|
+
: undefined;
|
|
2125
|
+
title = selectedField ? `修改“${selectedField.title}”`
|
|
2126
|
+
: selectedPreset ? `应用“${selectedPreset.title}”` : `修改 ${plugin.displayName} 设置`;
|
|
2127
|
+
description = selectedField
|
|
2128
|
+
? `当前:${effectiveFieldDisplay(selectedField)} · ${selectedField.effectiveSourceLabel}。选择保存位置后直接编辑。`
|
|
2129
|
+
: selectedPreset ? '选择保存位置后,方案会应用到该位置的草稿。' : '选择修改保存到哪里。';
|
|
1533
2130
|
help = '↑↓ 移动 · Enter 打开 · Esc 返回';
|
|
1534
2131
|
actions = [
|
|
1535
|
-
{ label: '
|
|
1536
|
-
{ label: '
|
|
1537
|
-
{ label: '
|
|
1538
|
-
{ label: '当前项目(仅本机)', description: '只保存在本机,不提交到仓库', meta: configLayerCounts.local === undefined ? undefined : configLayerCounts.local === 0 ? '未设置' : `${configLayerCounts.local} 个值`, run: () => go({ name: 'configFields', pluginId: plugin.pluginId, layer: { scope: 'project', projectLayer: 'local' } }) },
|
|
2132
|
+
{ label: '个人默认', description: '在所有项目中作为默认值', meta: configLayerCounts.user === undefined ? undefined : `${configLayerCounts.user} 个值`, run: () => openConfigLayerTarget({ scope: 'user', projectLayer: 'shared' }) },
|
|
2133
|
+
{ label: '当前项目', description: '随仓库共享,适合团队约定', meta: configLayerCounts.project === undefined ? undefined : `${configLayerCounts.project} 个值`, run: () => openConfigLayerTarget({ scope: 'project', projectLayer: 'shared' }) },
|
|
2134
|
+
{ label: '当前项目(仅本机)', description: '只保存在本机,不提交到仓库', meta: configLayerCounts.local === undefined ? undefined : configLayerCounts.local === 0 ? '未设置' : `${configLayerCounts.local} 个值`, run: () => openConfigLayerTarget({ scope: 'project', projectLayer: 'local' }) },
|
|
1539
2135
|
];
|
|
1540
|
-
content = _jsx(
|
|
2136
|
+
content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "section", compact: policy.mode === 'compact' });
|
|
1541
2137
|
}
|
|
1542
2138
|
else if (route.name === 'configFields' && plugin) {
|
|
1543
2139
|
const readonly = route.layer.scope === 'effective';
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
title = readonly ? '当前实际使用的设置' : route.groupId ? selectedGroups[0]?.title ?? '高级设置' : configLayerLabel(route.layer);
|
|
1550
|
-
description = readonly ? '由默认值、个人默认、当前项目和本机覆盖合并。' : route.groupId ? '这些字段来自 manifest 的高级设置分组。' : '字段来自插件 manifest;修改先保存在本地草稿。';
|
|
2140
|
+
breadcrumb = readonly ? `${plugin.displayName} / 设置` : `${plugin.displayName} / 设置 / ${configLayerLabel(route.layer)}`;
|
|
2141
|
+
title = readonly ? '当前插件设置' : '编辑插件设置';
|
|
2142
|
+
description = readonly
|
|
2143
|
+
? '查看当前实际生效的值;← → 切换设置分组。'
|
|
2144
|
+
: `保存位置:${configLayerLabel(route.layer)} · ← → 切换设置分组。`;
|
|
1551
2145
|
if (!readonly && configActionError)
|
|
1552
2146
|
description = configActionError;
|
|
1553
|
-
help = readonly
|
|
2147
|
+
help = readonly
|
|
2148
|
+
? '↑↓ 选择配置 · Enter 修改 · ←→ 分组 · Esc 返回'
|
|
2149
|
+
: '↑↓ 移动 · Enter 修改 · ←→ 分组 · s 保存 · u 恢复继承 · Esc 返回';
|
|
1554
2150
|
if (!currentConfigPage) {
|
|
1555
|
-
content = _jsx(
|
|
1556
|
-
}
|
|
1557
|
-
else if (readonly) {
|
|
1558
|
-
actions = selectedGroups.flatMap((group) => group.fields.map((field) => ({
|
|
1559
|
-
label: field.title,
|
|
1560
|
-
description: `来自${field.sourceLabel}`,
|
|
1561
|
-
meta: currentFieldDisplay(field),
|
|
1562
|
-
})));
|
|
1563
|
-
const limit = policy.mode === 'compact' ? 4 : 7;
|
|
1564
|
-
content = _jsx(Bordered, { title: "\u6709\u6548\u8BBE\u7F6E", children: _jsx(ActionRows, { actions: actions, focus: focus, offset: scrollOffset, limit: limit }) });
|
|
2151
|
+
content = _jsx(StatusCard, { status: "info", title: "\u6B63\u5728\u8BFB\u53D6\u53EF\u4FE1\u8BBE\u7F6E\u5B9A\u4E49", children: _jsx(ProgressRow, { title: "\u8BFB\u53D6 manifest \u8BBE\u7F6E\u5B57\u6BB5", state: "active" }) });
|
|
1565
2152
|
}
|
|
1566
2153
|
else {
|
|
2154
|
+
visibleConfigTabs = configTabs(currentConfigPage);
|
|
2155
|
+
const requestedTab = snapshot.router.current.context.configGroupId;
|
|
2156
|
+
const activeTab = visibleConfigTabs.find((tab) => tab.id === requestedTab) ?? visibleConfigTabs[0];
|
|
2157
|
+
activeConfigTabId = activeTab?.id;
|
|
2158
|
+
const activeGroup = activeTab?.kind === 'group'
|
|
2159
|
+
? currentConfigPage.groups.find((group) => group.id === activeTab.id)
|
|
2160
|
+
: undefined;
|
|
1567
2161
|
const mutableLayer = {
|
|
1568
2162
|
scope: route.layer.scope,
|
|
1569
2163
|
projectLayer: route.layer.projectLayer,
|
|
1570
2164
|
};
|
|
1571
|
-
const
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
2165
|
+
const tabActions = activeTab?.kind === 'presets'
|
|
2166
|
+
? currentConfigPage.presets.map((preset) => ({
|
|
2167
|
+
label: preset.title,
|
|
2168
|
+
description: (preset.privateFieldTitles?.length ?? 0) > 0
|
|
2169
|
+
? `${preset.description ? `${preset.description} · ` : ''}${preset.privateFieldTitles.join('、')}需手动输入`
|
|
2170
|
+
: preset.description,
|
|
2171
|
+
run: readonly
|
|
2172
|
+
? () => go({
|
|
2173
|
+
name: 'configLayer',
|
|
2174
|
+
pluginId: plugin.pluginId,
|
|
2175
|
+
presetId: preset.id,
|
|
2176
|
+
})
|
|
2177
|
+
: () => applyConfigPreset(currentConfigPage, preset.id),
|
|
2178
|
+
}))
|
|
2179
|
+
: (activeGroup?.fields ?? []).map((field) => ({
|
|
2180
|
+
label: field.title,
|
|
2181
|
+
description: readonly
|
|
2182
|
+
? field.sourceLabel === '未设置' ? '没有任何位置提供值' : `来自${field.sourceLabel}`
|
|
2183
|
+
: `${field.description ? `${field.description} · ` : ''}当前有效:${effectiveFieldDisplay(field)} · ${field.effectiveSourceLabel}`,
|
|
2184
|
+
meta: readonly ? effectiveFieldDisplay(field) : currentFieldDisplay(field),
|
|
2185
|
+
configKey: field.key,
|
|
2186
|
+
run: readonly
|
|
2187
|
+
? () => go({
|
|
2188
|
+
name: 'configLayer',
|
|
2189
|
+
pluginId: plugin.pluginId,
|
|
2190
|
+
fieldKey: field.key,
|
|
2191
|
+
groupId: activeGroup.id,
|
|
2192
|
+
})
|
|
2193
|
+
: () => go({
|
|
2194
|
+
name: 'configFieldEditor',
|
|
2195
|
+
pluginId: plugin.pluginId,
|
|
2196
|
+
layer: mutableLayer,
|
|
2197
|
+
fieldKey: field.key,
|
|
2198
|
+
editor: fieldEditor(field),
|
|
2199
|
+
}),
|
|
2200
|
+
}));
|
|
2201
|
+
const operationActions = readonly ? [] : [
|
|
2202
|
+
{ label: '保存修改', description: dirty ? '查看安全变更汇总' : '当前没有未保存修改', disabled: dirty ? undefined : '当前没有未保存修改', run: beginConfigSave },
|
|
2203
|
+
{
|
|
1590
2204
|
label: '重置此设置层…',
|
|
1591
2205
|
description: '删除本层全部值并恢复继承',
|
|
1592
2206
|
status: 'error',
|
|
1593
2207
|
disabled: currentConfigPage.storedCount === 0 ? '本层没有已存值' : undefined,
|
|
1594
2208
|
run: () => beginReview({ kind: 'configReset', pluginId: plugin.pluginId, scope: route.layer.scope, projectLayer: route.layer.projectLayer }),
|
|
1595
|
-
}
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
2209
|
+
},
|
|
2210
|
+
];
|
|
2211
|
+
actions = [...tabActions, ...operationActions];
|
|
2212
|
+
const effectiveConfigFocus = Math.min(focus, Math.max(0, actions.length - 1));
|
|
2213
|
+
const fieldFocus = tabActions.length > 0
|
|
2214
|
+
? effectiveConfigFocus < tabActions.length ? effectiveConfigFocus : Math.min(scrollOffset, tabActions.length - 1)
|
|
2215
|
+
: 0;
|
|
2216
|
+
const operationRows = operationActions.reduce((rows, action) => rows + (policy.mode === 'compact' || !action.description ? 1 : 2), 0);
|
|
2217
|
+
const viewport = actionViewport(tabActions, policy.mode === 'compact', fieldFocus, scrollOffset, pageActionBudget(1 + operationRows + 3));
|
|
2218
|
+
const totalFields = currentConfigPage.groups.reduce((count, group) => count + group.fields.length, 0);
|
|
2219
|
+
const countLabel = `${totalFields} 项${currentConfigPage.presets.length > 0 ? ` · ${currentConfigPage.presets.length} 个方案` : ''}`;
|
|
2220
|
+
context = readonly ? countLabel
|
|
2221
|
+
: dirty ? `${Object.keys(draftUpdates).length + draftRemoves.length} 项未保存` : countLabel;
|
|
2222
|
+
content = _jsxs(Box, { flexDirection: "column", children: [activeTab ? _jsx(ConfigGroupTabs, { tabs: visibleConfigTabs, current: activeTab.id, compact: policy.mode === 'compact', columns: size.columns - 2 }) : null, activeTab && tabActions.length > 0
|
|
2223
|
+
? _jsx(SectionCard, { title: activeTab.label, meta: viewport.hiddenBefore > 0 || viewport.hiddenAfter > 0 ? `${viewport.position}/${viewport.total}` : undefined, children: _jsx(SemanticRows, { actions: tabActions, focus: effectiveConfigFocus, kind: "section", compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit }) })
|
|
2224
|
+
: null, operationActions.length > 0
|
|
2225
|
+
? _jsx(SemanticRows, { actions: actions, focus: effectiveConfigFocus, kind: "action", compact: policy.mode === 'compact', offset: tabActions.length, limit: operationActions.length })
|
|
2226
|
+
: null] });
|
|
1604
2227
|
}
|
|
1605
2228
|
}
|
|
1606
2229
|
else if (route.name === 'configFieldEditor' && plugin && activeField) {
|
|
@@ -1613,16 +2236,16 @@ export function ConsoleAppV4(props) {
|
|
|
1613
2236
|
description = '保存值仍是 manifest 中的原始 enum token。';
|
|
1614
2237
|
actions = (activeField.enumValues ?? []).map((value) => ({
|
|
1615
2238
|
label: `${activeField.valueLabels?.[value] ?? value}${activeField.recommendedValue === value ? '(推荐)' : ''}`,
|
|
1616
|
-
|
|
2239
|
+
selected: draftUpdates[activeField.key] === value || (!Object.hasOwn(draftUpdates, activeField.key) && activeField.value === value),
|
|
1617
2240
|
run: () => { updateDraft(activeField.key, value); back(); },
|
|
1618
2241
|
}));
|
|
1619
|
-
content = _jsx(
|
|
2242
|
+
content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' });
|
|
1620
2243
|
}
|
|
1621
2244
|
else if (route.editor === 'boolean') {
|
|
1622
2245
|
title = `${activeField.title}?`;
|
|
1623
2246
|
description = activeField.description ?? '选择开启或关闭。';
|
|
1624
|
-
actions = [{ label: '开启', run: () => { updateDraft(activeField.key, true); back(); } }, { label: '关闭', run: () => { updateDraft(activeField.key, false); back(); } }];
|
|
1625
|
-
content = _jsx(
|
|
2247
|
+
actions = [{ label: '开启', selected: draftUpdates[activeField.key] === true || (!Object.hasOwn(draftUpdates, activeField.key) && activeField.value === true), run: () => { updateDraft(activeField.key, true); back(); } }, { label: '关闭', selected: draftUpdates[activeField.key] === false || (!Object.hasOwn(draftUpdates, activeField.key) && activeField.value === false), run: () => { updateDraft(activeField.key, false); back(); } }];
|
|
2248
|
+
content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' });
|
|
1626
2249
|
}
|
|
1627
2250
|
else {
|
|
1628
2251
|
title = route.editor === 'number' ? `输入${activeField.title}` : route.editor === 'path' ? `选择${activeField.title}` : route.editor === 'sensitive' ? '输入敏感值' : `输入${activeField.title}`;
|
|
@@ -1633,55 +2256,25 @@ export function ConsoleAppV4(props) {
|
|
|
1633
2256
|
const visibleInput = route.editor === 'sensitive'
|
|
1634
2257
|
? '•'.repeat(inputValue.length)
|
|
1635
2258
|
: route.editor === 'path' ? privatePathInput(inputValue) : inputValue;
|
|
1636
|
-
content =
|
|
1637
|
-
}
|
|
1638
|
-
}
|
|
1639
|
-
else if (route.name === 'configPresets' && plugin) {
|
|
1640
|
-
breadcrumb = `${plugin.displayName} / 设置 / 预设`;
|
|
1641
|
-
title = '选择快速方案';
|
|
1642
|
-
description = '预设由插件 manifest 声明;应用后仍可逐项修改。';
|
|
1643
|
-
actions = (currentConfigPage?.presets ?? []).map((preset) => ({
|
|
1644
|
-
label: preset.title,
|
|
1645
|
-
description: (preset.privateFieldTitles?.length ?? 0) > 0
|
|
1646
|
-
? `${preset.description ? `${preset.description} · ` : ''}${preset.privateFieldTitles.join('、')}需手动输入`
|
|
1647
|
-
: preset.description,
|
|
1648
|
-
run: () => {
|
|
1649
|
-
const fields = new Map(currentConfigPage?.groups.flatMap((group) => group.fields)
|
|
1650
|
-
.map((field) => [field.key, field]));
|
|
1651
|
-
const nextUpdates = { ...draftUpdates };
|
|
1652
|
-
for (const [key, value] of Object.entries(preset.values)) {
|
|
1653
|
-
const field = fields.get(key);
|
|
1654
|
-
if (field?.configured && field.value === value)
|
|
1655
|
-
delete nextUpdates[key];
|
|
1656
|
-
else
|
|
1657
|
-
nextUpdates[key] = value;
|
|
1658
|
-
}
|
|
1659
|
-
const nextRemoves = draftRemoves.filter((key) => !Object.hasOwn(preset.values, key));
|
|
1660
|
-
setDraftUpdates(nextUpdates);
|
|
1661
|
-
setDraftRemoves(nextRemoves);
|
|
1662
|
-
setDirty(Object.keys(nextUpdates).length + nextRemoves.length > 0);
|
|
1663
|
-
back();
|
|
1664
|
-
},
|
|
1665
|
-
}));
|
|
1666
|
-
content = currentConfigPage ? _jsx(ActionRows, { actions: actions, focus: focus }) : _jsx(Text, { color: tuiTheme.colors.accent, children: "\u25CF \u6B63\u5728\u8BFB\u53D6\u9884\u8BBE" });
|
|
2259
|
+
content = _jsx(InputCard, { focused: true, title: visibleInput || activeField.placeholder || ' ', description: inputError ?? (route.editor === 'sensitive' ? '输入已隐藏。' : route.editor === 'path' ? '已隐藏父目录;Backspace 仍会编辑原始输入。' : activeField.placeholder ?? '按 Enter 使用输入值。'), status: inputError ? 'error' : undefined });
|
|
2260
|
+
}
|
|
1667
2261
|
}
|
|
1668
2262
|
else if (route.name === 'configUnavailable' && plugin) {
|
|
1669
2263
|
breadcrumb = `${plugin.displayName} / 设置`;
|
|
1670
2264
|
context = '暂时不可编辑';
|
|
1671
|
-
|
|
1672
|
-
|
|
2265
|
+
const diagnostic = configUnavailableDiagnostic(route);
|
|
2266
|
+
title = diagnostic.title;
|
|
2267
|
+
description = '';
|
|
1673
2268
|
actions = [
|
|
1674
2269
|
{ label: '重新检查来源', description: '刷新当前来源后重新读取可信定义', run: async () => {
|
|
1675
2270
|
await props.controller.refreshSource(plugin.sourceId);
|
|
1676
2271
|
setConfigLoadKey((value) => value + 1);
|
|
1677
2272
|
props.controller.replace({ name: 'configFields', pluginId: plugin.pluginId, layer: route.layer ?? { scope: 'user', projectLayer: 'shared' } });
|
|
1678
2273
|
} },
|
|
1679
|
-
|
|
1680
|
-
? [{ label: '技术详情', description: '查看可信身份和设置层摘要', run: () => setShowTechnical((value) => !value) }]
|
|
1681
|
-
: []),
|
|
2274
|
+
{ label: showTechnical ? '返回问题摘要' : '技术详情', description: '查看脱敏错误代码和阶段', run: () => setShowTechnical((value) => !value) },
|
|
1682
2275
|
{ label: '返回', run: back },
|
|
1683
2276
|
];
|
|
1684
|
-
content = _jsxs(Box, { flexDirection: "column", children: [
|
|
2277
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(DiagnosticSummary, { diagnostic: diagnostic, technical: showTechnical, compact: policy.mode === 'compact' }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
|
|
1685
2278
|
}
|
|
1686
2279
|
else if (route.name === 'sourceList') {
|
|
1687
2280
|
breadcrumb = '插件来源';
|
|
@@ -1709,35 +2302,71 @@ export function ConsoleAppV4(props) {
|
|
|
1709
2302
|
}),
|
|
1710
2303
|
{ label: '+ 添加来源', description: 'Git 仓库或本地目录', run: beginAddSource },
|
|
1711
2304
|
];
|
|
1712
|
-
const viewport = actionViewport(
|
|
1713
|
-
|
|
2305
|
+
const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(8, 12));
|
|
2306
|
+
const sourceCount = Math.max(0, actions.length - 1);
|
|
2307
|
+
content = _jsx(Box, { flexDirection: "column", children: _jsx(SemanticRows, { actions: actions, focus: focus, kind: focus < sourceCount ? 'section' : 'action', compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit }) });
|
|
1714
2308
|
}
|
|
1715
2309
|
else if (route.name === 'sourceDetail') {
|
|
1716
2310
|
const overview = props.controller.getSourceOverview(route.sourceId);
|
|
1717
2311
|
const detail = overview.detail;
|
|
1718
2312
|
const state = overview.health?.state ?? overview.source?.state;
|
|
2313
|
+
const diagnostic = sourceDiagnostic(route.sourceId, state, detail);
|
|
1719
2314
|
breadcrumb = `插件来源 / ${route.sourceId}`;
|
|
1720
2315
|
context = detail?.type === 'path' ? '本地目录' : 'Git';
|
|
1721
2316
|
title = route.sourceId;
|
|
1722
|
-
description =
|
|
1723
|
-
: state === 'unavailable' ? '当前无法读取这个来源。' : '来源状态可用。';
|
|
2317
|
+
description = diagnostic ? '' : '来源状态可用。';
|
|
1724
2318
|
help = '↑↓ 移动 · Enter 选择 · r 重新检查 · Esc 返回';
|
|
1725
|
-
actions =
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
2319
|
+
actions = diagnostic
|
|
2320
|
+
? [
|
|
2321
|
+
{ label: '重新检查来源', description: '读取远端并验证可用数据', run: () => props.controller.refreshSource(route.sourceId) },
|
|
2322
|
+
...(state === 'degraded' ? [{
|
|
2323
|
+
label: '继续使用缓存',
|
|
2324
|
+
description: '浏览匹配且已验证的缓存数据',
|
|
2325
|
+
run: () => go({ name: 'pluginList', mode: 'browse', sourceId: route.sourceId }),
|
|
2326
|
+
}] : []),
|
|
2327
|
+
{ label: '编辑来源…', description: '检查 URL、路径或 ref', run: () => go({ name: 'sourceEdit', sourceId: route.sourceId }) },
|
|
2328
|
+
{ label: '设为主来源…', description: '只改变默认 marketplace', disabled: detail?.primary ? '当前已经是主来源' : undefined, run: () => beginReview({ kind: 'sourceMutation', action: 'setPrimary', sourceId: route.sourceId, changes: [{ title: '主来源', before: snapshot.viewModel?.sourceTabs.find((source) => source.primary)?.id, after: route.sourceId }] }) },
|
|
2329
|
+
{ label: '移除来源…', description: '移除来源配置', status: 'error', disabled: detail?.primary ? '主来源不能直接移除' : undefined, run: () => beginReview({ kind: 'sourceMutation', action: 'remove', sourceId: route.sourceId, changes: [{ title: `移除来源 ${route.sourceId}` }] }) },
|
|
2330
|
+
{ label: '技术详情', description: '查看脱敏错误代码和解析证据', run: () => go({ name: 'sourceTechnical', sourceId: route.sourceId }) },
|
|
2331
|
+
]
|
|
2332
|
+
: [
|
|
2333
|
+
{ label: '重新检查来源', description: '读取远端并更新缓存', run: () => props.controller.refreshSource(route.sourceId) },
|
|
2334
|
+
{ label: '编辑来源…', description: 'ID 与类型创建后不可修改', run: () => go({ name: 'sourceEdit', sourceId: route.sourceId }) },
|
|
2335
|
+
{ label: '设为主来源…', description: '只改变默认 marketplace', disabled: detail?.primary ? '当前已经是主来源' : undefined, run: () => beginReview({ kind: 'sourceMutation', action: 'setPrimary', sourceId: route.sourceId, changes: [{ title: '主来源', before: snapshot.viewModel?.sourceTabs.find((source) => source.primary)?.id, after: route.sourceId }] }) },
|
|
2336
|
+
{ label: '移除来源…', description: '移除来源配置', status: 'error', disabled: detail?.primary ? '主来源不能直接移除' : undefined, run: () => beginReview({ kind: 'sourceMutation', action: 'remove', sourceId: route.sourceId, changes: [{ title: `移除来源 ${route.sourceId}` }] }) },
|
|
2337
|
+
{ label: '技术详情', description: '只读身份与解析证据', run: () => go({ name: 'sourceTechnical', sourceId: route.sourceId }) },
|
|
2338
|
+
];
|
|
2339
|
+
const sourceActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(2, 6));
|
|
2340
|
+
content = _jsxs(Box, { flexDirection: "column", children: [diagnostic
|
|
2341
|
+
? _jsx(DiagnosticSummary, { diagnostic: diagnostic, compact: policy.mode === 'compact' })
|
|
2342
|
+
: _jsxs(_Fragment, { children: [_jsx(StatusCard, { status: state === 'checking' ? 'warning' : 'success', title: state === 'checking' ? '正在检查' : '可用' }), _jsxs(SectionCard, { title: "\u6765\u6E90\u914D\u7F6E", children: [_jsx(StaticRow, { title: "\u4F4D\u7F6E", meta: detail?.locationSummary ?? '不可用' }), _jsx(StaticRow, { title: "\u63D2\u4EF6", meta: `${detail?.pluginCount ?? 0} 个` }), policy.mode === 'compact' ? null : _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "\u7248\u672C\u9009\u62E9", meta: detail?.type === 'path' ? '本地目录' : detail?.refPolicy === 'explicit' ? '固定 Git 引用' : '跟随 AW 版本通道' }), detail?.explicitRef ? _jsx(StaticRow, { title: "\u5F15\u7528", meta: detail.explicitRef }) : null] })] })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: sourceActionViewport.offset, limit: sourceActionViewport.limit })] });
|
|
1733
2343
|
}
|
|
1734
2344
|
else if (route.name === 'sourceTechnical') {
|
|
1735
|
-
const
|
|
2345
|
+
const overview = props.controller.getSourceOverview(route.sourceId);
|
|
2346
|
+
const detail = overview.detail;
|
|
2347
|
+
const state = overview.health?.state ?? overview.source?.state;
|
|
2348
|
+
const diagnostic = sourceDiagnostic(route.sourceId, state, detail) ?? {
|
|
2349
|
+
severity: 'warning',
|
|
2350
|
+
title: '来源解析详情',
|
|
2351
|
+
userMessage: '来源当前可用。',
|
|
2352
|
+
impact: [{ state: 'preserved', message: '当前来源状态没有阻止操作。' }],
|
|
2353
|
+
nextStep: '返回来源详情。',
|
|
2354
|
+
technical: {
|
|
2355
|
+
code: 'catalogSourceReady',
|
|
2356
|
+
stage: 'resolve-source',
|
|
2357
|
+
sourceId: route.sourceId,
|
|
2358
|
+
...(detail?.requestedRef ? { requestedRef: detail.requestedRef } : {}),
|
|
2359
|
+
...(detail?.resolvedCommit ? { resolvedCommit: detail.resolvedCommit } : {}),
|
|
2360
|
+
...(detail?.freshness ? { context: [{ key: 'freshness', value: detail.freshness }] } : {}),
|
|
2361
|
+
},
|
|
2362
|
+
};
|
|
1736
2363
|
breadcrumb = `插件来源 / ${route.sourceId} / 技术详情`;
|
|
1737
2364
|
context = '只读';
|
|
1738
|
-
title = '
|
|
1739
|
-
description = '
|
|
1740
|
-
|
|
2365
|
+
title = '来源技术诊断';
|
|
2366
|
+
description = '';
|
|
2367
|
+
help = 'Enter 返回 · Esc 返回';
|
|
2368
|
+
actions = [{ label: '返回来源详情', run: back }];
|
|
2369
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(DiagnosticSummary, { diagnostic: diagnostic, technical: true, compact: policy.mode === 'compact' }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
|
|
1741
2370
|
}
|
|
1742
2371
|
else if (route.name === 'sourceAdd' && sourceDraft?.owner === route.owner) {
|
|
1743
2372
|
breadcrumb = sourceDraft.mode === 'add' ? '插件来源 / 添加' : `插件来源 / ${sourceDraft.sourceId} / 编辑`;
|
|
@@ -1751,7 +2380,7 @@ export function ConsoleAppV4(props) {
|
|
|
1751
2380
|
{ label: 'Git 仓库', description: '从远端 Git 仓库读取 Catalog', run: () => { setSourceDraft({ ...sourceDraft, type: 'git' }); go({ name: 'sourceAdd', step: sourceDraft.sourceId ? 'location' : 'id', owner: route.owner }); } },
|
|
1752
2381
|
{ label: '本地目录', description: '用于本地开发与测试', run: () => { setSourceDraft({ ...sourceDraft, type: 'path' }); go({ name: 'sourceAdd', step: sourceDraft.sourceId ? 'location' : 'id', owner: route.owner }); } },
|
|
1753
2382
|
];
|
|
1754
|
-
content = _jsx(
|
|
2383
|
+
content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' });
|
|
1755
2384
|
}
|
|
1756
2385
|
else if (route.step === 'refPolicy') {
|
|
1757
2386
|
title = '选择版本引用方式';
|
|
@@ -1763,7 +2392,7 @@ export function ConsoleAppV4(props) {
|
|
|
1763
2392
|
go({ name: 'sourceAdd', step: 'subdir', owner: route.owner }); } },
|
|
1764
2393
|
{ label: '固定 Git 引用', description: 'branch、tag 或 commit', run: () => { setSourceDraft({ ...sourceDraft, refPolicy: 'explicit' }); go({ name: 'sourceAdd', step: 'ref', owner: route.owner }); } },
|
|
1765
2394
|
];
|
|
1766
|
-
content = _jsx(
|
|
2395
|
+
content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "choice", compact: policy.mode === 'compact' });
|
|
1767
2396
|
}
|
|
1768
2397
|
else {
|
|
1769
2398
|
title = route.step === 'id' ? '输入来源 ID' : route.step === 'location' ? `输入 ${sourceDraft.type === 'git' ? 'Git URL' : '绝对路径'}` : route.step === 'ref' ? '输入固定 Git 引用' : '输入 Catalog 子目录';
|
|
@@ -1773,7 +2402,7 @@ export function ConsoleAppV4(props) {
|
|
|
1773
2402
|
: route.step === 'location' && sourceDraft.type === 'path'
|
|
1774
2403
|
? privatePathInput(sourceInput)
|
|
1775
2404
|
: sourceInput;
|
|
1776
|
-
content =
|
|
2405
|
+
content = _jsx(InputCard, { focused: true, title: displayedInput || ' ', description: sourceInputError ?? (route.step === 'location' && sourceDraft.type === 'path' ? '已隐藏父目录;Backspace 仍会编辑原始输入。' : '按 Enter 继续。'), status: sourceInputError ? 'error' : undefined });
|
|
1777
2406
|
}
|
|
1778
2407
|
}
|
|
1779
2408
|
else if (route.name === 'sourceEdit') {
|
|
@@ -1794,7 +2423,7 @@ export function ConsoleAppV4(props) {
|
|
|
1794
2423
|
{ label: '版本引用', description: detail.refPolicy === 'explicit' ? `固定:${detail.explicitRef}` : '跟随 AW 版本通道', run: () => startEdit('refPolicy') },
|
|
1795
2424
|
{ label: 'Catalog 子目录', description: detail.subdir ?? '未设置', run: () => startEdit('subdir') },
|
|
1796
2425
|
] : [{ label: '本地目录', description: '已选择本地目录', run: () => startEdit('location') }];
|
|
1797
|
-
content = _jsx(
|
|
2426
|
+
content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "section", compact: policy.mode === 'compact' });
|
|
1798
2427
|
}
|
|
1799
2428
|
else if (route.name === 'sourceRemoveBlocked') {
|
|
1800
2429
|
breadcrumb = `插件来源 / ${route.sourceId} / 移除`;
|
|
@@ -1803,7 +2432,7 @@ export function ConsoleAppV4(props) {
|
|
|
1803
2432
|
description = sourceReferenceSummary?.inventoryComplete === false ? '引用清单不完整,不能把未知引用当作零。' : '仍有内容依赖这个来源。';
|
|
1804
2433
|
actions = [{ label: sourceDependenciesExpanded ? '收起依赖内容' : '查看依赖内容', description: '只显示插件、来源、平台、位置和类别聚合', run: () => setSourceDependenciesExpanded((value) => !value) }, { label: '返回来源详情', run: back }];
|
|
1805
2434
|
const countLabel = (value, unit) => value === undefined ? '未知' : `${value} ${unit}`;
|
|
1806
|
-
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(
|
|
2435
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(Callout, { status: "error", title: sourceReferenceSummary?.inventoryComplete === false ? '引用清单不完整' : '发现活动引用' }), _jsxs(SectionCard, { title: "\u5F15\u7528\u6C47\u603B", children: [_jsx(StaticRow, { title: "\u5B89\u88C5\u4F4D\u7F6E", meta: countLabel(sourceReferenceSummary?.installationCount, '个') }), _jsx(StaticRow, { title: "\u63D2\u4EF6\u8BBE\u7F6E", meta: countLabel(sourceReferenceSummary?.configCount, '份') }), _jsx(StaticRow, { title: "\u751F\u547D\u5468\u671F\u72B6\u6001", meta: countLabel(sourceReferenceSummary?.lifecycleCount, '个') }), _jsx(StaticRow, { title: "Marketplace", meta: countLabel(sourceReferenceSummary?.marketplaceCount, '个') }), sourceDependenciesExpanded ? sourceReferenceSummary?.dependencies.slice(0, Math.max(1, pageActionBudget(10))).map((dependency, index) => _jsx(StaticRow, { title: `${dependency.category} · ${dependency.pluginId ?? dependency.sourceId ?? '身份未知'}`, meta: `${dependency.platform ? platformLabel(dependency.platform) : ''}${dependency.scope ? ` · ${scopeLabel(dependency.scope)}` : ''}` }, `${dependency.category}:${dependency.pluginId ?? dependency.sourceId ?? index}`)) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
|
|
1807
2436
|
}
|
|
1808
2437
|
else if (route.name === 'issueList') {
|
|
1809
2438
|
const issues = props.controller.listUserIssues();
|
|
@@ -1835,8 +2464,8 @@ export function ConsoleAppV4(props) {
|
|
|
1835
2464
|
: go({ name: 'issueDetail', issueId: entry.issueId }),
|
|
1836
2465
|
}));
|
|
1837
2466
|
}
|
|
1838
|
-
const viewport = actionViewport(
|
|
1839
|
-
content = _jsx(
|
|
2467
|
+
const viewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, responsiveBudget(8, 12));
|
|
2468
|
+
content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: issues.length > 0 ? 'task' : 'action', compact: policy.mode === 'compact', offset: viewport.offset, limit: viewport.limit });
|
|
1840
2469
|
}
|
|
1841
2470
|
else if (route.name === 'issueDetail' && issue) {
|
|
1842
2471
|
breadcrumb = `需要处理 / ${issue.pluginId ?? issue.sourceId ?? issue.entity.id}`;
|
|
@@ -1854,7 +2483,9 @@ export function ConsoleAppV4(props) {
|
|
|
1854
2483
|
else if (entry.id === 'continueWithCache' || entry.id === 'retryLater')
|
|
1855
2484
|
go({ name: 'issueList' });
|
|
1856
2485
|
else if (entry.id === 'editSource' && issue.sourceId)
|
|
1857
|
-
go(
|
|
2486
|
+
go(issue.entity.kind === 'source'
|
|
2487
|
+
? { name: 'sourceEdit', sourceId: issue.sourceId }
|
|
2488
|
+
: { name: 'sourceDetail', sourceId: issue.sourceId });
|
|
1858
2489
|
else if (entry.id === 'reAddSource' && issue.sourceId)
|
|
1859
2490
|
beginIssueSourceAdd(issue.sourceId);
|
|
1860
2491
|
else if (entry.id === 'viewSourceChange' && issue.sourceId)
|
|
@@ -1878,8 +2509,9 @@ export function ConsoleAppV4(props) {
|
|
|
1878
2509
|
else
|
|
1879
2510
|
setShowTechnical((value) => !value);
|
|
1880
2511
|
};
|
|
1881
|
-
|
|
1882
|
-
|
|
2512
|
+
const issueHasTechnicalDetails = Boolean(issue.diagnostic.technical);
|
|
2513
|
+
actions = issue.actions.filter((entry) => entry.id !== 'viewTechnical' || issueHasTechnicalDetails).map((entry) => ({
|
|
2514
|
+
label: entry.id === 'viewTechnical' && showTechnical ? '返回问题摘要' : entry.label,
|
|
1883
2515
|
description: entry.description,
|
|
1884
2516
|
status: entry.id === 'remove'
|
|
1885
2517
|
|| entry.id === 'keepUserScope' || entry.id === 'keepProjectScope' ? 'error' : undefined,
|
|
@@ -1887,7 +2519,14 @@ export function ConsoleAppV4(props) {
|
|
|
1887
2519
|
}));
|
|
1888
2520
|
const issuePlugin = issue.pluginId ? props.controller.getPluginOverview(issue.pluginId) : undefined;
|
|
1889
2521
|
const issueOverview = issue.pluginId ? props.controller.getPluginDependencyOverview(issue.pluginId) : undefined;
|
|
1890
|
-
const
|
|
2522
|
+
const issueDiagnosticSummary = _jsx(DiagnosticSummary, { diagnostic: issue.diagnostic, technical: showTechnical, compact: policy.mode === 'compact' || issue.category.startsWith('dependency') });
|
|
2523
|
+
const issueActionViewport = actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(6));
|
|
2524
|
+
const compactTechnicalActionViewport = policy.mode === 'compact' && showTechnical
|
|
2525
|
+
? {
|
|
2526
|
+
offset: Math.min(Math.max(0, focus - 1), Math.max(0, actions.length - 2)),
|
|
2527
|
+
limit: 2,
|
|
2528
|
+
}
|
|
2529
|
+
: {};
|
|
1891
2530
|
if (issue.category === 'dependencyStillRequired' && issuePlugin) {
|
|
1892
2531
|
const blockingDependents = issue.platform && issue.scope
|
|
1893
2532
|
? props.controller.getBlockingDependents(issuePlugin.pluginId, issue.platform, issue.scope)
|
|
@@ -1897,51 +2536,112 @@ export function ConsoleAppV4(props) {
|
|
|
1897
2536
|
actions = [
|
|
1898
2537
|
{ label: '查看完整依赖关系', run: () => go({ name: 'pluginDependencies', pluginId: issuePlugin.pluginId }) },
|
|
1899
2538
|
{ label: '返回插件详情', run: () => go({ name: 'pluginDetail', pluginId: issuePlugin.pluginId }) },
|
|
2539
|
+
...(issueHasTechnicalDetails ? [{ label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => setShowTechnical((value) => !value) }] : []),
|
|
1900
2540
|
];
|
|
1901
|
-
|
|
2541
|
+
const dependencyBlockedViewport = policy.mode === 'compact' && showTechnical
|
|
2542
|
+
? { offset: Math.min(Math.max(0, focus - 1), Math.max(0, actions.length - 2)), limit: 2 }
|
|
2543
|
+
: actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(4));
|
|
2544
|
+
content = _jsxs(Box, { flexDirection: "column", children: [issueDiagnosticSummary, showTechnical ? null : _jsx(SectionCard, { title: "\u4F7F\u7528\u8FD9\u4E2A\u4F9D\u8D56\u7684\u63D2\u4EF6", children: blockingDependents.map((dependent) => _jsx(StaticRow, { title: dependent.displayName, meta: dependentTargetLabel(dependent) }, dependent.pluginId)) }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: dependencyBlockedViewport.offset, limit: dependencyBlockedViewport.limit })] });
|
|
2545
|
+
}
|
|
2546
|
+
else if ((issue.category === 'dependencyMissing' || issue.category === 'dependencyNotReady') && issuePlugin) {
|
|
2547
|
+
const dependencyName = issue.dependencyPluginId
|
|
2548
|
+
? props.controller.getPluginOverview(issue.dependencyPluginId)?.displayName ?? issue.dependencyPluginId
|
|
2549
|
+
: '必需依赖';
|
|
2550
|
+
const dependencyTarget = [
|
|
2551
|
+
issue.dependencyPlatform ? platformLabel(issue.dependencyPlatform) : undefined,
|
|
2552
|
+
issue.dependencyScope ? scopeLabel(issue.dependencyScope) : undefined,
|
|
2553
|
+
].filter((entry) => Boolean(entry)).join(' · ');
|
|
2554
|
+
content = _jsxs(Box, { flexDirection: "column", children: [issueDiagnosticSummary, !showTechnical && policy.mode !== 'compact' ? _jsxs(StatusCard, { status: "warning", title: dependencyName, description: "\u53D7\u5F71\u54CD\u7684\u4F9D\u8D56\u76EE\u6807", children: [_jsx(StaticRow, { title: issuePlugin.displayName, description: "\u53D7\u5F71\u54CD\u7684\u6839\u63D2\u4EF6", meta: issue.platform && issue.scope ? `${platformLabel(issue.platform)} · ${scopeLabel(issue.scope)}` : undefined }), dependencyTarget ? _jsx(StaticRow, { title: "\u4F9D\u8D56\u76EE\u6807", meta: dependencyTarget, status: "warning" }) : null] }) : null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', ...compactTechnicalActionViewport })] });
|
|
1902
2555
|
}
|
|
1903
2556
|
else if (issue.category === 'dependencyTargetUnsupported' && issuePlugin) {
|
|
1904
|
-
const
|
|
1905
|
-
|
|
2557
|
+
const issueScope = issue.scope ?? scope;
|
|
2558
|
+
const compatible = props.controller.listInstallTargets(issuePlugin.pluginId, issueScope)
|
|
2559
|
+
.filter((entry) => entry.compatible);
|
|
2560
|
+
title = `无法添加到 ${issue.platform ? platformLabel(issue.platform) : '所选目标平台'}`;
|
|
1906
2561
|
description = issue.dependencyPluginId
|
|
1907
|
-
? `${props.controller.getPluginOverview(issue.dependencyPluginId)?.displayName ?? issue.dependencyPluginId}
|
|
1908
|
-
: '
|
|
2562
|
+
? `${props.controller.getPluginOverview(issue.dependencyPluginId)?.displayName ?? issue.dependencyPluginId} 不支持所选目标平台,因此完整插件计划不能运行。`
|
|
2563
|
+
: '完整插件计划不支持所选目标平台。';
|
|
1909
2564
|
actions = [
|
|
1910
|
-
...compatible.map((entry) => ({ label: `只添加到 ${platformLabel(entry.platform)}`, description: '
|
|
1911
|
-
setScope(
|
|
1912
|
-
|
|
1913
|
-
|
|
2565
|
+
...compatible.map((entry) => ({ label: `只添加到 ${platformLabel(entry.platform)}`, description: '返回目标平台页并使用完整依赖检查', run: () => {
|
|
2566
|
+
setScope(issueScope);
|
|
2567
|
+
setSelectedTargets([entry.platform]);
|
|
2568
|
+
setTargetSelections((current) => ({ ...current, [issueScope]: [entry.platform] }));
|
|
2569
|
+
go({ name: 'installTargets', pluginId: issuePlugin.pluginId });
|
|
1914
2570
|
} })),
|
|
1915
|
-
{ label: '
|
|
2571
|
+
{ label: '返回调整目标平台', run: () => {
|
|
2572
|
+
setScope(issueScope);
|
|
2573
|
+
setSelectedTargets([]);
|
|
2574
|
+
setTargetSelections((current) => ({ ...current, [issueScope]: [] }));
|
|
2575
|
+
go({ name: 'installTargets', pluginId: issuePlugin.pluginId });
|
|
2576
|
+
} },
|
|
1916
2577
|
{ label: '查看依赖关系', run: () => go({ name: 'pluginDependencies', pluginId: issuePlugin.pluginId }) },
|
|
2578
|
+
...(issueHasTechnicalDetails ? [{ label: showTechnical ? '隐藏技术详情' : '技术详情', run: () => setShowTechnical((value) => !value) }] : []),
|
|
1917
2579
|
];
|
|
1918
|
-
|
|
2580
|
+
const unsupportedTargetViewport = policy.mode === 'compact' && showTechnical
|
|
2581
|
+
? { offset: Math.min(Math.max(0, focus - 1), Math.max(0, actions.length - 2)), limit: 2 }
|
|
2582
|
+
: actionViewport(actions, policy.mode === 'compact', focus, scrollOffset, pageActionBudget(6));
|
|
2583
|
+
content = _jsxs(Box, { flexDirection: "column", children: [issueDiagnosticSummary, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: unsupportedTargetViewport.offset, limit: unsupportedTargetViewport.limit })] });
|
|
1919
2584
|
}
|
|
1920
2585
|
else if (issue.category === 'dependencyGraphInvalid') {
|
|
1921
2586
|
title = `暂时无法添加 ${issuePlugin?.displayName ?? issue.pluginId ?? '插件'}`;
|
|
1922
2587
|
description = '来源中的依赖关系无效或安装记录不能安全验证。没有修改任何安装。';
|
|
1923
|
-
|
|
2588
|
+
const sourceId = issue.sourceId;
|
|
2589
|
+
if (sourceId) {
|
|
2590
|
+
actions = [
|
|
2591
|
+
{ label: '查看来源问题', description: '进入来源页并重新检查 Catalog', run: () => go({ name: 'sourceDetail', sourceId }) },
|
|
2592
|
+
...actions,
|
|
2593
|
+
];
|
|
2594
|
+
}
|
|
2595
|
+
content = _jsxs(Box, { flexDirection: "column", children: [issueDiagnosticSummary, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
|
|
1924
2596
|
}
|
|
1925
2597
|
else {
|
|
1926
2598
|
const sourceMissingCleanup = issue.category === 'sourceMissing' && issue.pluginId && issue.platform && issue.scope
|
|
1927
2599
|
? `aw cleanup ${issue.pluginId} --platform ${issue.platform} --scope ${issue.scope}`
|
|
1928
2600
|
: undefined;
|
|
1929
|
-
|
|
2601
|
+
const hasRecoveryEvidence = sourceMissingCleanup || issue.category === 'sourceIdentityChanged'
|
|
2602
|
+
|| issue.placements || issue.requiredFields;
|
|
2603
|
+
const recoveryRows = _jsxs(_Fragment, { children: [sourceMissingCleanup ? _jsxs(_Fragment, { children: [_jsx(Text, { color: tuiTheme.statusColors.warning, children: sourceMissingCleanup }), _jsx(Text, { color: tuiTheme.statusColors.warning, children: "\u9700\u8981\u65F6\u53EF\u5728\u547D\u4EE4\u672B\u5C3E\u6DFB\u52A0 --force\u3002" })] }) : null, issue.category === 'sourceIdentityChanged' ? _jsx(StaticRow, { title: "\u6765\u6E90\u8EAB\u4EFD\u5DF2\u53D8\u5316\uFF1B\u8BF7\u786E\u8BA4\u53D8\u66F4\u6216\u6062\u590D\u539F\u6765\u6E90\u3002", status: "warning", compact: true }) : null, issue.placements ? _jsx(StaticRow, { title: "\u4F4D\u7F6E", meta: issue.placements.map(scopeLabel).join('、'), compact: true }) : null, issue.requiredFields ? _jsx(StaticRow, { title: "\u5B57\u6BB5", meta: sanitizeDiagnosticText(issue.requiredFields.map((field) => field.title).join('、')), compact: true }) : null] });
|
|
2604
|
+
content = _jsxs(Box, { flexDirection: "column", children: [issueDiagnosticSummary, !showTechnical && hasRecoveryEvidence
|
|
2605
|
+
? policy.mode === 'compact' ? recoveryRows : _jsx(SectionCard, { title: "\u6062\u590D\u8BC1\u636E", children: recoveryRows })
|
|
2606
|
+
: null, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact', offset: issueActionViewport.offset, limit: issueActionViewport.limit })] });
|
|
1930
2607
|
}
|
|
1931
2608
|
}
|
|
2609
|
+
else if (route.name === 'reviewPreparing' && snapshot.reviewPreparation) {
|
|
2610
|
+
const preparation = snapshot.reviewPreparation;
|
|
2611
|
+
const labels = preparation.intent.kind === 'portableInstall'
|
|
2612
|
+
? ['通用 Skills / 安装', '正在准备通用 Skills 安装确认']
|
|
2613
|
+
: preparation.intent.kind === 'configSave' || preparation.intent.kind === 'configReset'
|
|
2614
|
+
? ['设置', '正在准备设置确认']
|
|
2615
|
+
: preparation.intent.kind === 'sourceMutation'
|
|
2616
|
+
? ['插件来源', '正在准备来源变更确认']
|
|
2617
|
+
: ['插件操作', '正在准备插件操作确认'];
|
|
2618
|
+
breadcrumb = `${labels[0]} / 准备确认`;
|
|
2619
|
+
context = '只读';
|
|
2620
|
+
title = preparation.state === 'failed' ? '无法准备确认信息' : labels[1];
|
|
2621
|
+
description = preparation.state === 'failed'
|
|
2622
|
+
? preparation.message ?? '无法准备确认信息。请返回检查选择,或重试。'
|
|
2623
|
+
: '这个阶段只读取和验证状态,不会修改任何安装或设置。';
|
|
2624
|
+
help = preparation.state === 'running'
|
|
2625
|
+
? 'Esc 取消 · Ctrl-C 完成后退出'
|
|
2626
|
+
: '↑↓ 移动 · Enter 选择 · Esc 返回';
|
|
2627
|
+
actions = preparation.state === 'failed' ? [
|
|
2628
|
+
{ label: '重试', description: '重新读取和验证当前状态', run: () => {
|
|
2629
|
+
props.controller.retryReviewPreparation();
|
|
2630
|
+
setFocus(0);
|
|
2631
|
+
setScrollOffset(0);
|
|
2632
|
+
} },
|
|
2633
|
+
{ label: '返回修改', run: back },
|
|
2634
|
+
] : [];
|
|
2635
|
+
const stageState = (state) => state === 'running' ? 'active' : state === 'completed' ? 'complete' : state === 'failed' ? 'error' : 'pending';
|
|
2636
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsx(SectionCard, { title: "\u51C6\u5907\u6B65\u9AA4", children: preparation.stages.map((stage) => _jsx(ProgressRow, { title: stage.label, state: stageState(stage.state), meta: stage.state === 'running' ? '进行中' : stage.state === 'completed' ? '完成' : stage.state === 'failed' ? '失败' : '未开始', compact: policy.mode === 'compact' }, stage.id)) }), preparation.currentLabel ? _jsx(StaticRow, { title: "\u5F53\u524D", meta: preparation.currentLabel, status: "muted" }) : null, preparation.code && preparation.message
|
|
2637
|
+
? _jsx(Callout, { status: "error", title: preparation.code, description: preparation.message }) : null, actions.length > 0 ? _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' }) : null] });
|
|
2638
|
+
}
|
|
1932
2639
|
else if (route.name === 'review' && snapshot.activeReview) {
|
|
1933
2640
|
const review = snapshot.activeReview;
|
|
1934
2641
|
breadcrumb = `${review.subject.name} / ${review.title}`;
|
|
1935
|
-
title = `${snapshot.result?.state === 'stale' ? '状态已变化 · ' : ''}
|
|
2642
|
+
title = `${snapshot.result?.state === 'stale' ? '状态已变化 · ' : ''}${review.operationKind === 'portableInstall' ? review.title : `确认${review.title}`} ${review.subject.name}`;
|
|
1936
2643
|
description = snapshot.result?.state === 'stale' ? '当前状态已变化。请检查新的变化后再次确认。' : review.description;
|
|
1937
2644
|
actions = [
|
|
1938
|
-
...review.changes.map((change) => ({
|
|
1939
|
-
label: `${reviewSymbol(change)} ${change.title}`,
|
|
1940
|
-
description: change.description,
|
|
1941
|
-
status: change.kind === 'remove' ? 'error'
|
|
1942
|
-
: change.kind === 'warning' ? 'warning'
|
|
1943
|
-
: change.kind === 'keep' || change.kind === 'add' ? 'success' : undefined,
|
|
1944
|
-
})),
|
|
1945
2645
|
{ label: review.primaryLabel, description: '执行前将再次检查当前状态', status: review.destructive ? 'error' : undefined, run: () => {
|
|
1946
2646
|
if (operationBusy.current)
|
|
1947
2647
|
return;
|
|
@@ -1960,11 +2660,6 @@ export function ConsoleAppV4(props) {
|
|
|
1960
2660
|
}
|
|
1961
2661
|
: back },
|
|
1962
2662
|
];
|
|
1963
|
-
const reviewViewport = lineBudgetViewport(actions, focus, scrollOffset, policy.mode === 'compact' ? 8 : 15, [...review.changes.map((change) => change.group), undefined, undefined]);
|
|
1964
|
-
const reviewRows = (indexes) => indexes
|
|
1965
|
-
.filter((index) => index >= reviewViewport.offset
|
|
1966
|
-
&& (reviewViewport.limit === undefined || index < reviewViewport.offset + reviewViewport.limit))
|
|
1967
|
-
.map((index) => _jsx(ActionRows, { actions: actions, focus: focus, offset: index, limit: 1 }, index));
|
|
1968
2663
|
const groupedChanges = review.changes.some((change) => change.group);
|
|
1969
2664
|
const groupTitle = {
|
|
1970
2665
|
requested: review.operationKind === 'portableInstall' ? '已选择 Skills · 当前插件' : '你选择的插件',
|
|
@@ -1974,12 +2669,75 @@ export function ConsoleAppV4(props) {
|
|
|
1974
2669
|
portableWarnings: '覆盖冲突',
|
|
1975
2670
|
preserved: '保持不变',
|
|
1976
2671
|
};
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
2672
|
+
const changeStatus = (change) => change.kind === 'remove' ? 'error'
|
|
2673
|
+
: change.kind === 'warning' ? 'warning'
|
|
2674
|
+
: change.kind === 'keep' || change.kind === 'add' ? 'success' : undefined;
|
|
2675
|
+
const groupOrder = ['portableWarnings', 'portableTarget', 'requested', 'dependencies', 'preserved', 'portableInputs'];
|
|
2676
|
+
const groupedSpecs = groupedChanges
|
|
2677
|
+
? groupOrder.map((group) => ({
|
|
2678
|
+
key: group,
|
|
2679
|
+
label: groupTitle[group],
|
|
2680
|
+
protectedCount: group === 'portableWarnings' || group === 'portableTarget' || group === 'preserved',
|
|
2681
|
+
changes: review.changes.filter((change) => (change.group ?? 'requested') === group),
|
|
2682
|
+
}))
|
|
2683
|
+
: [
|
|
2684
|
+
{ key: 'warnings', label: '警告', protectedCount: true, changes: review.changes.filter((change) => change.kind === 'warning') },
|
|
2685
|
+
{ key: 'kept', label: '保持不变', protectedCount: true, changes: review.changes.filter((change) => change.kind === 'keep') },
|
|
2686
|
+
{ key: 'changes', label: undefined, protectedCount: false, changes: review.changes.filter((change) => change.kind !== 'warning' && change.kind !== 'keep') },
|
|
2687
|
+
];
|
|
2688
|
+
const visibleSpecs = groupedSpecs.filter((group) => group.changes.length > 0);
|
|
2689
|
+
const headingLines = policy.mode === 'compact' ? 0 : visibleSpecs.filter((group) => group.label).length;
|
|
2690
|
+
const staticRowBudget = Math.max(visibleSpecs.length, responsiveBudget(7, 12) - headingLines);
|
|
2691
|
+
const allocations = new Map(visibleSpecs.map((group) => [group.key, 1]));
|
|
2692
|
+
const targetGroup = visibleSpecs.find((group) => group.key === 'portableTarget');
|
|
2693
|
+
if (targetGroup && targetGroup.changes.length > 1 && staticRowBudget > visibleSpecs.length) {
|
|
2694
|
+
allocations.set(targetGroup.key, 2);
|
|
2695
|
+
}
|
|
2696
|
+
let remainingRows = staticRowBudget - [...allocations.values()].reduce((sum, count) => sum + count, 0);
|
|
2697
|
+
for (const group of visibleSpecs) {
|
|
2698
|
+
if (remainingRows <= 0)
|
|
2699
|
+
break;
|
|
2700
|
+
const current = allocations.get(group.key) ?? 1;
|
|
2701
|
+
const added = Math.min(remainingRows, group.changes.length - current);
|
|
2702
|
+
allocations.set(group.key, current + added);
|
|
2703
|
+
remainingRows -= added;
|
|
2704
|
+
}
|
|
2705
|
+
const changeGroups = visibleSpecs.map((group) => {
|
|
2706
|
+
const visibleCount = allocations.get(group.key) ?? 1;
|
|
2707
|
+
const hiddenCount = group.changes.length - visibleCount;
|
|
2708
|
+
return _jsxs(Box, { flexDirection: "column", children: [policy.mode !== 'compact' && group.label ? _jsx(Text, { bold: true, color: tuiTheme.colors.muted, children: group.label }) : null, group.changes.slice(0, visibleCount).map((change, index) => {
|
|
2709
|
+
const retainDescription = change.kind === 'warning' || group.key === 'portableTarget'
|
|
2710
|
+
|| group.key === 'requested' || group.key === 'dependencies';
|
|
2711
|
+
const description = retainDescription ? change.description : undefined;
|
|
2712
|
+
const compactGroupLabel = group.key === 'requested' ? '根插件'
|
|
2713
|
+
: group.key === 'dependencies' ? '依赖'
|
|
2714
|
+
: group.key === 'preserved' ? '保持' : group.label;
|
|
2715
|
+
const category = policy.mode === 'compact' && index === 0 && compactGroupLabel ? `${compactGroupLabel} · ` : '';
|
|
2716
|
+
const count = index === visibleCount - 1 && (hiddenCount > 0 || group.protectedCount)
|
|
2717
|
+
? hiddenCount > 0 ? `另有 ${hiddenCount} 项` : `共 ${group.changes.length} 项`
|
|
2718
|
+
: undefined;
|
|
2719
|
+
return _jsx(ReviewChangeRow, { title: `${category}${reviewSymbol(change)} ${change.title}${description ? ` · ${description}` : ''}`, meta: count, status: changeStatus(change), compact: true }, `${change.title}:${index}`);
|
|
2720
|
+
})] }, group.key);
|
|
2721
|
+
});
|
|
2722
|
+
const technicalDetails = review.technicalDetails ?? [];
|
|
2723
|
+
const technicalDetailLimit = responsiveBudget(1, 3);
|
|
2724
|
+
const visibleTechnicalDetails = technicalDetails.slice(0, technicalDetailLimit);
|
|
2725
|
+
const hiddenTechnicalDetails = technicalDetails.length - visibleTechnicalDetails.length;
|
|
2726
|
+
const technicalCardRows = 3 + visibleTechnicalDetails.length;
|
|
2727
|
+
const technicalChangeBudget = Math.max(1, size.rows - 6 - 3 - (review.equivalentCommand ? 1 : 0) - actions.length - technicalCardRows);
|
|
2728
|
+
const prioritizedTechnicalChanges = [
|
|
2729
|
+
...review.changes.filter((change) => change.kind === 'warning'),
|
|
2730
|
+
...review.changes.filter((change) => change.group === 'portableTarget'),
|
|
2731
|
+
...review.changes.filter((change) => change.kind !== 'warning' && change.group !== 'portableTarget'),
|
|
2732
|
+
];
|
|
2733
|
+
const visibleTechnicalChanges = prioritizedTechnicalChanges.slice(0, technicalChangeBudget);
|
|
2734
|
+
const hiddenTechnicalChanges = review.changes.length - visibleTechnicalChanges.length;
|
|
2735
|
+
const technicalChanges = visibleTechnicalChanges.map((change, index) => {
|
|
2736
|
+
const category = change.group ? groupTitle[change.group] : undefined;
|
|
2737
|
+
return _jsx(ReviewChangeRow, { title: `${category ? `${category} · ` : ''}${reviewSymbol(change)} ${change.title}${change.description ? ` · ${change.description}` : ''}`, meta: index === visibleTechnicalChanges.length - 1 && hiddenTechnicalChanges > 0
|
|
2738
|
+
? `另有 ${hiddenTechnicalChanges} 项` : undefined, status: changeStatus(change), compact: true }, `${change.title}:${index}`);
|
|
2739
|
+
});
|
|
2740
|
+
content = _jsx(Box, { flexDirection: "column", children: _jsxs(ReviewCard, { title: review.subject.name, meta: `${review.subject.sourceLabel} ${review.subject.version ?? ''}`, description: review.equivalentCommand ? review.description : undefined, children: [review.equivalentCommand ? _jsxs(Text, { children: ["\u7B49\u4EF7\u547D\u4EE4\uFF1A", review.equivalentCommand] }) : null, showTechnical ? technicalChanges : changeGroups, _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true }), policy.mode === 'compact' || showTechnical ? null : _jsx(StaticRow, { title: "\u6280\u672F\u8BE6\u60C5\uFF08d\uFF09", status: "muted" }), showTechnical ? _jsx(TechnicalDetails, { children: visibleTechnicalDetails.map((detail, index) => _jsx(StaticRow, { title: detail, meta: index === visibleTechnicalDetails.length - 1 && hiddenTechnicalDetails > 0 ? `另有 ${hiddenTechnicalDetails} 项` : undefined, compact: true }, detail)) }) : null] }) });
|
|
1983
2741
|
}
|
|
1984
2742
|
else if (route.name === 'progress') {
|
|
1985
2743
|
const portableOperation = snapshot.activeReview?.operationKind === 'portableInstall';
|
|
@@ -1990,13 +2748,24 @@ export function ConsoleAppV4(props) {
|
|
|
1990
2748
|
breadcrumb = portableOperation ? '通用 Skills / 进度' : issueOperation ? '需要处理 / 进度' : sourceOperation ? '插件来源 / 保存' : configOperation ? '设置 / 进度' : '插件操作 / 进度';
|
|
1991
2749
|
title = portableOperation ? '正在安装通用 Skills' : issueOperation ? '正在处理问题' : sourceOperation ? '正在保存并刷新来源' : configOperation ? '正在保存并应用设置' : '正在执行插件操作';
|
|
1992
2750
|
description = issueOperation ? '完成后会使用新的状态 generation 重新检查问题。' : sourceOperation ? '先原子保存来源配置,再尽力刷新。刷新失败时配置仍保留并显示警告。' : configOperation ? '先保存所选层,再重新应用当前可见安装位置。' : '请勿关闭当前终端。';
|
|
1993
|
-
help = '
|
|
2751
|
+
help = 'Ctrl-C 完成后退出';
|
|
1994
2752
|
const progress = snapshot.progress;
|
|
1995
2753
|
const elapsedSeconds = Math.max(0, Math.floor(((progress.elapsedMs
|
|
1996
2754
|
?? (progress.startedAtMs === undefined ? 0 : Date.now() - progress.startedAtMs))) / 1_000));
|
|
1997
2755
|
const latestMessage = progress.messages.at(-1);
|
|
1998
|
-
const
|
|
2756
|
+
const technicalMessageLimit = portableOperation ? responsiveBudget(1, 3) : responsiveBudget(4, 4);
|
|
2757
|
+
const technicalMessages = progress.messages.slice(-technicalMessageLimit);
|
|
2758
|
+
const hiddenTechnicalMessages = progress.messages.length - technicalMessages.length;
|
|
1999
2759
|
const dependencyProgress = snapshot.progressTargets.length > 0;
|
|
2760
|
+
const currentPhase = progress.current?.phase ?? '';
|
|
2761
|
+
const failedProgress = snapshot.progressTargets.some((entry) => entry.state === 'failed');
|
|
2762
|
+
const validationActive = currentPhase.includes('检查写入结果')
|
|
2763
|
+
|| currentPhase.includes('读取最终状态')
|
|
2764
|
+
|| (currentPhase.includes('验证') && currentPhase !== '验证当前状态'
|
|
2765
|
+
&& !currentPhase.includes('验证设置值'));
|
|
2766
|
+
const preflightActive = !validationActive && (currentPhase.includes('检查')
|
|
2767
|
+
|| currentPhase === '验证当前状态' || currentPhase.includes('验证设置值'));
|
|
2768
|
+
const executionActive = Boolean(currentPhase) && !preflightActive && !validationActive && !failedProgress;
|
|
2000
2769
|
const progressLabel = (entry) => {
|
|
2001
2770
|
if (entry.state === 'kept')
|
|
2002
2771
|
return { label: '已保留', status: 'success' };
|
|
@@ -2010,22 +2779,93 @@ export function ConsoleAppV4(props) {
|
|
|
2010
2779
|
return { label: '未执行 · 依赖未完成', status: 'warning' };
|
|
2011
2780
|
return { label: entry.relation === 'requested' ? '等待依赖' : '未开始', status: 'muted' };
|
|
2012
2781
|
};
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2782
|
+
const prioritizedProgressTargets = [...snapshot.progressTargets].sort((left, right) => {
|
|
2783
|
+
const priority = (entry) => entry.state === 'failed' ? 0
|
|
2784
|
+
: entry.state === 'running' ? 1
|
|
2785
|
+
: entry.relation === 'requested' ? 2
|
|
2786
|
+
: entry.state === 'skipped' ? 3 : 4;
|
|
2787
|
+
return priority(left) - priority(right);
|
|
2788
|
+
});
|
|
2789
|
+
const progressStepRows = policy.mode === 'compact' ? 4 : 5;
|
|
2790
|
+
const progressTargetAndMessageBudget = pageActionBudget()
|
|
2791
|
+
- 2
|
|
2792
|
+
- progressStepRows
|
|
2793
|
+
- 3
|
|
2794
|
+
- (exitAfterBusy ? 3 : 0);
|
|
2795
|
+
const dependencyMessageLimit = progressTargetAndMessageBudget
|
|
2796
|
+
>= (policy.mode === 'compact' ? 2 : 3) + 1
|
|
2797
|
+
? Math.min(1, showTechnical ? technicalMessages.length : latestMessage ? 1 : 0)
|
|
2798
|
+
: 0;
|
|
2799
|
+
const progressTargetBudget = progressTargetAndMessageBudget - dependencyMessageLimit;
|
|
2800
|
+
const progressTargetLimit = prefixRowLimit(prioritizedProgressTargets.map(() => policy.mode === 'compact' ? 1 : 2), progressTargetBudget);
|
|
2801
|
+
const visibleProgressTargets = prioritizedProgressTargets.slice(0, progressTargetLimit);
|
|
2802
|
+
const hiddenProgressTargets = snapshot.progressTargets.length - visibleProgressTargets.length;
|
|
2803
|
+
const targetStates = snapshot.progressTargets.map((entry) => entry.state);
|
|
2804
|
+
const executionComplete = targetStates.length > 0
|
|
2805
|
+
&& targetStates.every((state) => state === 'completed' || state === 'kept');
|
|
2806
|
+
const executionStarted = targetStates.some((state) => state === 'running' || state === 'completed'
|
|
2807
|
+
|| state === 'failed' || state === 'skipped');
|
|
2808
|
+
const relationStep = currentPhase === '验证当前状态' || preflightActive ? 'active' : 'complete';
|
|
2809
|
+
const planStep = relationStep === 'active' ? 'pending' : 'complete';
|
|
2810
|
+
const closureStep = failedProgress ? 'error'
|
|
2811
|
+
: executionComplete ? 'complete'
|
|
2812
|
+
: executionStarted || executionActive ? 'active' : 'pending';
|
|
2813
|
+
const verificationStep = failedProgress && validationActive ? 'error'
|
|
2814
|
+
: validationActive ? 'active' : 'pending';
|
|
2815
|
+
const portableDraft = snapshot.portableDraft;
|
|
2816
|
+
const portableReviewProviderIds = snapshot.activeReview?.changes
|
|
2817
|
+
.filter((change) => change.group === 'requested' || change.group === 'dependencies')
|
|
2818
|
+
.flatMap((change) => change.pluginId ? [change.pluginId] : []) ?? [];
|
|
2819
|
+
const portableProviderCount = new Set(portableReviewProviderIds.length > 0
|
|
2820
|
+
? portableReviewProviderIds
|
|
2821
|
+
: portableDraft?.candidates.filter((candidate) => candidate.selected)
|
|
2822
|
+
.map((candidate) => candidate.providerPluginId) ?? []).size;
|
|
2823
|
+
const portableSelectedCount = portableDraft?.selectedCount
|
|
2824
|
+
?? snapshot.activeReview?.changes.filter((change) => change.group === 'requested' || change.group === 'dependencies').length
|
|
2825
|
+
?? 0;
|
|
2826
|
+
const portablePreparing = currentPhase.includes('检查插件依赖并生成候选');
|
|
2827
|
+
const portableInstalling = currentPhase.includes('安装已选择的 Skills');
|
|
2828
|
+
const portableVerifying = currentPhase.includes('验证安装结果');
|
|
2829
|
+
const portableStep = portableVerifying ? 3 : portableInstalling ? 2 : portablePreparing ? 1 : 0;
|
|
2830
|
+
const portableProgressState = (step) => {
|
|
2831
|
+
if (failedProgress && step === portableStep)
|
|
2832
|
+
return 'error';
|
|
2833
|
+
if (step < portableStep)
|
|
2834
|
+
return 'complete';
|
|
2835
|
+
if (step === portableStep)
|
|
2836
|
+
return 'active';
|
|
2837
|
+
return 'pending';
|
|
2838
|
+
};
|
|
2839
|
+
const portableProgressMeta = (step) => {
|
|
2840
|
+
const state = portableProgressState(step);
|
|
2841
|
+
if (state === 'complete')
|
|
2842
|
+
return '完成';
|
|
2843
|
+
if (state === 'error')
|
|
2844
|
+
return '失败';
|
|
2845
|
+
if (state === 'pending')
|
|
2846
|
+
return '未开始';
|
|
2847
|
+
return step === 2 ? '正在安装' : step === 3 ? '正在验证' : step === 1 ? '正在生成' : '正在检查';
|
|
2848
|
+
};
|
|
2849
|
+
const compactPortableProgress = policy.mode === 'compact' && showTechnical;
|
|
2850
|
+
content = portableOperation ? _jsxs(StatusCard, { status: failedProgress ? 'error' : 'info', title: "Skills CLI", children: [_jsx(ProgressRow, { title: "\u68C0\u67E5\u63D2\u4EF6\u548C\u4F9D\u8D56", description: `${snapshot.activeReview?.subject.name ?? '当前插件'} · ${portableDraft?.dependencySummary?.directCount ?? 0} 个直接依赖`, meta: portableProgressMeta(0), state: portableProgressState(0), compact: compactPortableProgress }), _jsx(ProgressRow, { title: "\u751F\u6210\u5DF2\u9009\u5019\u9009 Skills", description: `${portableProviderCount} 个 provider 提供 ${portableSelectedCount} 个 Skills`, meta: portableProgressMeta(1), state: portableProgressState(1), compact: compactPortableProgress }), _jsx(ProgressRow, { title: `安装已选择的 ${portableSelectedCount} 个 Skills`, description: `Agent:${portableDraft?.agentMode === 'explicit' ? portableDraft.requestedAgents.join('、') : 'Skills CLI 默认选择'}`, meta: portableProgressMeta(2), state: portableProgressState(2), compact: compactPortableProgress }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u7ED3\u679C", description: "\u9010\u9879\u786E\u8BA4\u6240\u9009 Skill \u7684\u5B89\u88C5\u76EE\u6807", meta: portableProgressMeta(3), state: portableProgressState(3), compact: compactPortableProgress }), showTechnical ? _jsxs(TechnicalDetails, { children: [technicalMessages.map((message, index) => _jsx(StaticRow, { title: message, compact: true }, `${index}:${message}`)), hiddenTechnicalMessages > 0 ? _jsx(StaticRow, { title: `另有 ${hiddenTechnicalMessages} 条技术消息`, status: "muted", compact: true }) : null] }) : null] }) : _jsxs(StatusCard, { status: "info", children: [dependencyProgress ? _jsxs(_Fragment, { children: [_jsx(ProgressRow, { title: "\u68C0\u67E5\u4F9D\u8D56\u5173\u7CFB", state: relationStep }), _jsx(ProgressRow, { title: "\u751F\u6210\u6267\u884C\u8BA1\u5212", state: planStep }), _jsx(ProgressRow, { title: "\u6267\u884C\u4F9D\u8D56\u95ED\u5305", description: progress.current?.target ? `当前:${progress.current.target}` : undefined, meta: `${elapsedSeconds}s`, state: closureStep, compact: policy.mode === 'compact' }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u5B8C\u6574\u7ED3\u679C", state: verificationStep })] }) : _jsxs(_Fragment, { children: [_jsx(ProgressRow, { title: sourceOperation ? '检查来源操作输入' : configOperation ? '验证设置值' : '检查兼容性', state: !currentPhase ? 'pending' : preflightActive ? 'active' : 'complete' }), _jsx(ProgressRow, { title: progress.current?.phase || (sourceOperation ? '保存来源配置并刷新' : configOperation ? '保存设置层并重新应用' : '连接到所选目标平台'), description: progress.current?.target ? `当前:${progress.current.target}` : undefined, meta: `${elapsedSeconds}s`, state: failedProgress ? 'error' : executionActive ? 'active' : validationActive ? 'complete' : 'pending', compact: policy.mode === 'compact' }), _jsx(ProgressRow, { title: "\u9A8C\u8BC1\u7ED3\u679C", state: failedProgress ? 'error' : validationActive ? 'active' : 'pending' })] }), dependencyProgress ? _jsxs(SectionCard, { title: "\u6267\u884C\u76EE\u6807", meta: `${snapshot.progressTargets.length} 个目标`, children: [visibleProgressTargets.map((entry) => {
|
|
2851
|
+
const state = progressLabel(entry);
|
|
2852
|
+
const relation = entry.relation === 'requested' ? '你选择的插件' : entry.relation === 'direct' ? '直接依赖' : '间接依赖';
|
|
2853
|
+
const title = props.controller.getPluginOverview(entry.pluginId)?.displayName
|
|
2854
|
+
?? entry.pluginId.split('/').at(-1);
|
|
2855
|
+
return _jsx(ResultRow, { title: title, description: relation, meta: state.label, status: state.status ?? 'muted', compact: policy.mode === 'compact' }, `${entry.pluginId}:${entry.platform}:${entry.scope}`);
|
|
2856
|
+
}), hiddenProgressTargets > 0 ? _jsx(StaticRow, { title: `另有 ${hiddenProgressTargets} 个目标`, status: "muted", compact: true }) : null] }) : null, !showTechnical && latestMessage && (!dependencyProgress || dependencyMessageLimit > 0) ? _jsx(StaticRow, { title: latestMessage, status: "muted", compact: true }) : null, showTechnical ? (dependencyProgress
|
|
2857
|
+
? dependencyMessageLimit > 0 ? technicalMessages.slice(-dependencyMessageLimit) : []
|
|
2858
|
+
: technicalMessages).map((message, index) => _jsx(StaticRow, { title: message, compact: true }, `${index}:${message}`)) : null, exitAfterBusy ? _jsx(Callout, { status: "warning", title: "\u64CD\u4F5C\u5B8C\u6210\u540E\u9000\u51FA" }) : null] });
|
|
2020
2859
|
}
|
|
2021
2860
|
else if (route.name === 'result' && snapshot.result) {
|
|
2022
2861
|
const result = snapshot.result;
|
|
2023
2862
|
const partial = result.state === 'partial';
|
|
2024
2863
|
const blocked = result.state === 'blocked';
|
|
2025
2864
|
breadcrumb = '插件操作 / 结果';
|
|
2026
|
-
const
|
|
2027
|
-
? result.summary
|
|
2028
|
-
: undefined;
|
|
2865
|
+
const resultSummary = result.state === 'succeeded' || result.state === 'partial'
|
|
2866
|
+
? result.summary
|
|
2867
|
+
: result.state === 'blocked' ? result.summary : undefined;
|
|
2868
|
+
const action = resultSummary?.action;
|
|
2029
2869
|
const operationKind = 'operationKind' in result ? result.operationKind : undefined;
|
|
2030
2870
|
const portableOperation = operationKind === 'portableInstall';
|
|
2031
2871
|
const portableReceipt = result.state === 'succeeded' || result.state === 'partial'
|
|
@@ -2035,9 +2875,10 @@ export function ConsoleAppV4(props) {
|
|
|
2035
2875
|
&& result.returnRoute?.name === 'pluginDetail';
|
|
2036
2876
|
const configOperation = operationKind === 'configSave' || operationKind === 'configReset'
|
|
2037
2877
|
? operationKind : undefined;
|
|
2878
|
+
const successfulConfigOperation = configOperation !== undefined && !blocked && !partial;
|
|
2038
2879
|
const sourceOperation = operationKind === 'sourceMutation';
|
|
2039
2880
|
const sourceRefreshWarning = sourceOperation && 'summary' in result
|
|
2040
|
-
&& result.summary
|
|
2881
|
+
&& result.summary?.messages?.some((message) => message.startsWith('警告:'));
|
|
2041
2882
|
const issueOperation = result.originIssue;
|
|
2042
2883
|
const completedTitle = {
|
|
2043
2884
|
install: '✓ 插件添加完成',
|
|
@@ -2059,7 +2900,9 @@ export function ConsoleAppV4(props) {
|
|
|
2059
2900
|
? blocked
|
|
2060
2901
|
? '× 设置未确认保存'
|
|
2061
2902
|
: partial ? '! 设置已保存,但部分应用失败' : '✓ 设置已保存并应用'
|
|
2062
|
-
: partial ? '! 插件操作只完成了一部分'
|
|
2903
|
+
: partial ? '! 插件操作只完成了一部分'
|
|
2904
|
+
: blocked ? result.code === 'operationFailed' ? '× 插件操作没有完成' : '× 插件操作未完成'
|
|
2905
|
+
: completedTitle;
|
|
2063
2906
|
description = portableOperation
|
|
2064
2907
|
? blocked
|
|
2065
2908
|
? result.code === 'operationReviewExpired' || result.code === 'reviewTicketExpired'
|
|
@@ -2090,16 +2933,29 @@ export function ConsoleAppV4(props) {
|
|
|
2090
2933
|
: '来源配置已保存,刷新已完成。'
|
|
2091
2934
|
: blocked ? result.message
|
|
2092
2935
|
: configOperation ? '设置层已经更新。' : '状态已刷新。';
|
|
2093
|
-
const
|
|
2094
|
-
? result
|
|
2095
|
-
: undefined;
|
|
2096
|
-
const pluginId = completedResult?.summary.pluginId
|
|
2936
|
+
const pluginId = resultSummary?.pluginId
|
|
2097
2937
|
?? ('pluginId' in result ? result.pluginId : undefined);
|
|
2098
|
-
const
|
|
2938
|
+
const configOrigin = successfulConfigOperation && pluginId
|
|
2939
|
+
? props.controller.configOrigin(pluginId)
|
|
2940
|
+
: undefined;
|
|
2941
|
+
const dependencyTargets = resultSummary?.targets?.some((target) => target.relation) ?? false;
|
|
2942
|
+
const blockedDependencyFailure = blocked && result.code === 'operationFailed' && dependencyTargets;
|
|
2943
|
+
const dependencyRecovery = (partial && dependencyTargets) || blockedDependencyFailure;
|
|
2944
|
+
if (blockedDependencyFailure) {
|
|
2945
|
+
title = '× 依赖安装没有完成';
|
|
2946
|
+
description = '依赖目标失败,后续目标未执行。可以查看失败项或重试失败的依赖。';
|
|
2947
|
+
}
|
|
2948
|
+
const failedTarget = resultSummary?.targets
|
|
2099
2949
|
?.filter((target) => target.state === 'failed')
|
|
2100
2950
|
.sort((left, right) => [left.pluginId ?? '', left.platform, left.scope].join('\0')
|
|
2101
2951
|
.localeCompare([right.pluginId ?? '', right.platform, right.scope].join('\0'), 'en'))[0];
|
|
2102
|
-
const
|
|
2952
|
+
const failedTargetPluginId = failedTarget?.pluginId;
|
|
2953
|
+
const failedPluginId = dependencyRecovery
|
|
2954
|
+
? failedTargetPluginId
|
|
2955
|
+
: failedTargetPluginId && props.controller.getPluginOverview(failedTargetPluginId)
|
|
2956
|
+
? failedTargetPluginId : pluginId;
|
|
2957
|
+
const failedPluginPlatforms = failedPluginId
|
|
2958
|
+
? props.controller.getPluginOverview(failedPluginId)?.supportedPlatforms : undefined;
|
|
2103
2959
|
const failedIssue = failedPluginId && failedTarget
|
|
2104
2960
|
? props.controller.listUserIssues().find((candidate) => (candidate.pluginId === failedPluginId || candidate.dependencyPluginId === failedPluginId)
|
|
2105
2961
|
&& candidate.platform === failedTarget.platform
|
|
@@ -2121,7 +2977,7 @@ export function ConsoleAppV4(props) {
|
|
|
2121
2977
|
&& (candidate.scope ?? failedIssue.scope) === failedTarget?.scope)
|
|
2122
2978
|
: undefined;
|
|
2123
2979
|
actions = portableOperation
|
|
2124
|
-
? [
|
|
2980
|
+
? blocked || partial ? [
|
|
2125
2981
|
{ label: blocked || partial ? '返回修改' : portableOriginIsDetail ? '返回插件详情' : '返回插件预览', run: async () => {
|
|
2126
2982
|
if (!pluginId) {
|
|
2127
2983
|
leaveResult();
|
|
@@ -2153,8 +3009,19 @@ export function ConsoleAppV4(props) {
|
|
|
2153
3009
|
} },
|
|
2154
3010
|
{ label: portableOriginIsDetail ? '返回插件详情' : '返回插件预览', run: () => { if (pluginId)
|
|
2155
3011
|
props.controller.resetPortableFlowToPreview(pluginId); } },
|
|
3012
|
+
] : [
|
|
3013
|
+
{ label: portableOriginIsDetail ? '返回插件详情' : '返回插件预览', run: () => {
|
|
3014
|
+
props.controller.discardPortableDraft();
|
|
3015
|
+
leaveResult();
|
|
3016
|
+
} },
|
|
3017
|
+
{ label: '再安装其他 Skills…', run: async () => {
|
|
3018
|
+
if (!pluginId)
|
|
3019
|
+
return;
|
|
3020
|
+
props.controller.resetPortableFlowToPreview(pluginId);
|
|
3021
|
+
await beginPortable(pluginId);
|
|
3022
|
+
} },
|
|
2156
3023
|
]
|
|
2157
|
-
: issueOperation
|
|
3024
|
+
: issueOperation && !dependencyRecovery
|
|
2158
3025
|
? blocked || partial
|
|
2159
3026
|
? [
|
|
2160
3027
|
{ label: '返回问题详情', run: () => blocked
|
|
@@ -2177,19 +3044,32 @@ export function ConsoleAppV4(props) {
|
|
|
2177
3044
|
{ label: '返回设置', run: () => leaveResult() },
|
|
2178
3045
|
{ label: '返回首页', run: () => leaveResult({ name: 'home' }) },
|
|
2179
3046
|
]
|
|
2180
|
-
: [{ label: partial
|
|
2181
|
-
|
|
3047
|
+
: [{ label: partial || blockedDependencyFailure ? '查看失败项' : successfulConfigOperation && configOrigin
|
|
3048
|
+
? configOrigin?.name === 'pluginPreview' ? '返回插件预览' : '返回插件详情'
|
|
3049
|
+
: configOperation ? '查看插件'
|
|
3050
|
+
: action === 'uninstall' || action === 'cleanup' ? '返回插件详情' : '查看插件', run: () => {
|
|
3051
|
+
if (successfulConfigOperation && configOrigin) {
|
|
3052
|
+
leaveResult();
|
|
3053
|
+
return;
|
|
3054
|
+
}
|
|
3055
|
+
if ((partial || blockedDependencyFailure) && failedPluginId && failedTarget) {
|
|
2182
3056
|
if (failedIssue)
|
|
2183
3057
|
leaveResult({ name: 'issueDetail', issueId: failedIssue.issueId });
|
|
3058
|
+
else if (failedPluginPlatforms?.includes(failedTarget.platform)) {
|
|
3059
|
+
leaveResult({ name: 'platform', pluginId: failedPluginId, platform: failedTarget.platform });
|
|
3060
|
+
}
|
|
3061
|
+
else if (snapshot.viewModel?.plugins.some((entry) => entry.pluginId === failedPluginId)) {
|
|
3062
|
+
leaveResult({ name: 'pluginDetail', pluginId: failedPluginId });
|
|
3063
|
+
}
|
|
2184
3064
|
else
|
|
2185
|
-
leaveResult({ name: '
|
|
3065
|
+
leaveResult({ name: 'pluginList', mode: 'installed' });
|
|
2186
3066
|
}
|
|
2187
3067
|
else if (pluginId && snapshot.viewModel?.plugins.some((entry) => entry.pluginId === pluginId)) {
|
|
2188
3068
|
leaveResult({ name: 'pluginDetail', pluginId });
|
|
2189
3069
|
}
|
|
2190
3070
|
else
|
|
2191
3071
|
leaveResult({ name: 'pluginList', mode: 'installed' });
|
|
2192
|
-
} }, ...(partial &&
|
|
3072
|
+
} }, ...((partial || blockedDependencyFailure) && failedPluginId && failedTarget && retryAction ? [{
|
|
2193
3073
|
label: '重试失败部分…',
|
|
2194
3074
|
run: () => {
|
|
2195
3075
|
const issueIntent = failedIssue && retryIssueAction
|
|
@@ -2204,20 +3084,65 @@ export function ConsoleAppV4(props) {
|
|
|
2204
3084
|
});
|
|
2205
3085
|
},
|
|
2206
3086
|
}] : []), { label: '返回首页', run: () => leaveResult({ name: 'home' }) }];
|
|
2207
|
-
const visibleMessages =
|
|
2208
|
-
const dependencyTargets = completedResult?.summary.targets?.some((target) => target.relation) ?? false;
|
|
3087
|
+
const visibleMessages = resultSummary?.messages?.slice(policy.mode === 'compact' ? -2 : -4) ?? [];
|
|
2209
3088
|
const resultTargetName = (target) => target.pluginId
|
|
2210
3089
|
? props.controller.getPluginOverview(target.pluginId)?.displayName
|
|
2211
3090
|
?? target.pluginId.split('/').at(-1)
|
|
2212
3091
|
: `${platformLabel(target.platform)} · ${scopeLabel(target.scope)}`;
|
|
2213
|
-
|
|
3092
|
+
const resultStatus = blocked ? 'error' : partial ? 'warning' : 'success';
|
|
3093
|
+
const resultTargets = resultSummary?.targets ?? [];
|
|
3094
|
+
const hasPreservedTarget = resultTargets.some((target) => target.state === 'completed' || target.state === 'kept');
|
|
3095
|
+
const prioritizedTargets = [...resultTargets].sort((left, right) => {
|
|
3096
|
+
const priority = (target) => target.state === 'failed' ? 0
|
|
3097
|
+
: target.state === 'skipped' ? 1 : target.relation === 'requested' ? 2 : 3;
|
|
3098
|
+
return priority(left) - priority(right);
|
|
3099
|
+
});
|
|
3100
|
+
const resultActionViewport = actionViewport(actions, true, focus, scrollOffset, pageActionBudget(8));
|
|
3101
|
+
const resultExtraRows = (resultSummary?.elapsedMs !== undefined ? 1 : 0)
|
|
3102
|
+
+ visibleMessages.length
|
|
3103
|
+
+ (partial && hasPreservedTarget && resultTargets.some((target) => target.state === 'skipped') ? 1 : 0);
|
|
3104
|
+
const resultTargetLimit = prefixRowLimit(prioritizedTargets.map(() => policy.mode === 'compact' ? 1 : 2), pageActionBudget() - 3 - resultActionViewport.limit - resultExtraRows);
|
|
3105
|
+
const visibleResultTargets = prioritizedTargets.slice(0, resultTargetLimit);
|
|
3106
|
+
const hiddenResultTargets = resultTargets.length - visibleResultTargets.length;
|
|
3107
|
+
const portableMissingSkills = new Set(portableReceipt?.missing.map((entry) => entry.skill) ?? []);
|
|
3108
|
+
const portableSelectedSkills = portableReceipt?.selectedSkills
|
|
3109
|
+
?? snapshot.portableDraft?.candidates.filter((candidate) => candidate.selected).map((candidate) => candidate.name)
|
|
3110
|
+
?? [];
|
|
3111
|
+
const prioritizedPortableSkills = [...portableSelectedSkills].sort((left, right) => Number(portableMissingSkills.has(right)) - Number(portableMissingSkills.has(left)));
|
|
3112
|
+
const portableTechnicalSummary = showTechnical && (partial || blocked);
|
|
3113
|
+
const portableSkillLimit = showTechnical ? 1 : responsiveBudget(2, 6);
|
|
3114
|
+
const visiblePortableSkills = prioritizedPortableSkills.slice(0, portableSkillLimit);
|
|
3115
|
+
const hiddenPortableSkills = prioritizedPortableSkills.length - visiblePortableSkills.length;
|
|
3116
|
+
const portableRows = visiblePortableSkills.map((skill) => {
|
|
3117
|
+
const missing = portableMissingSkills.has(skill);
|
|
3118
|
+
const status = blocked || portableReceipt?.verificationUnknown ? 'warning'
|
|
3119
|
+
: missing ? 'warning' : 'success';
|
|
3120
|
+
const meta = blocked ? result.writeMayHaveCompleted ? '结果未知' : '未执行'
|
|
3121
|
+
: portableReceipt?.verificationUnknown ? '结果未知'
|
|
3122
|
+
: missing ? '未完成 · 缺少目标' : '已安装';
|
|
3123
|
+
return _jsx(ResultRow, { title: skill, meta: meta, status: status, compact: true }, skill);
|
|
3124
|
+
});
|
|
3125
|
+
const compactTechnicalRows = portableReceipt ? [
|
|
3126
|
+
portableReceipt.failureCode
|
|
3127
|
+
? _jsx(StaticRow, { title: "failure code", meta: portableReceipt.failureCode, compact: true }, "failure-code")
|
|
3128
|
+
: _jsx(StaticRow, { title: "materialization digest", meta: portableReceipt.materializationDigest, compact: true }, "materialization-digest"),
|
|
3129
|
+
_jsx(StaticRow, { title: "\u5176\u4F59\u6280\u672F\u5B57\u6BB5\u5DF2\u6298\u53E0", meta: "\u6458\u8981\u5DF2\u4FDD\u7559", status: "muted", compact: true }, "folded-fields"),
|
|
3130
|
+
] : [
|
|
3131
|
+
_jsx(StaticRow, { title: "failure code", meta: 'code' in result ? result.code : 'operationFailed', compact: true }, "failure-code"),
|
|
3132
|
+
_jsx(StaticRow, { title: "\u5176\u4F59\u6280\u672F\u5B57\u6BB5\u5DF2\u6298\u53E0", meta: "\u672C\u6B21\u5199\u5165\u6CA1\u6709\u5B89\u5168 receipt", status: "muted", compact: true }, "folded-fields"),
|
|
3133
|
+
];
|
|
3134
|
+
content = portableOperation ? _jsxs(Box, { flexDirection: "column", children: [portableTechnicalSummary ? _jsxs(_Fragment, { children: [portableRows, hiddenPortableSkills > 0 ? _jsx(StaticRow, { title: `另有 ${hiddenPortableSkills} 个 Skills`, status: "muted", compact: true }) : null, portableReceipt?.recovery ? _jsx(StaticRow, { title: "\u5B89\u5168\u6062\u590D", meta: portableReceipt.recovery, status: "warning", compact: true }) : null, _jsx(TechnicalDetails, { children: compactTechnicalRows })] }) : _jsx(_Fragment, { children: _jsxs(ResultCard, { status: resultStatus, title: blocked ? '安装未开始或结果未确认' : partial ? '部分 Skills 未确认' : '通用 Skills 安装结果', children: [portableRows, hiddenPortableSkills > 0 ? _jsx(StaticRow, { title: `另有 ${hiddenPortableSkills} 个 Skills`, status: "muted", compact: true }) : null, portableReceipt ? _jsx(StaticRow, { title: `Agent:${portableReceipt.agentMode === 'automatic' ? 'Skills CLI 默认选择' : portableReceipt.requestedAgents.join('、')}`, meta: `范围:${scopeLabel(portableReceipt.scope)}`, compact: true }) : null, portableReceipt?.recovery ? policy.mode === 'compact'
|
|
3135
|
+
? _jsx(StaticRow, { title: "\u5B89\u5168\u6062\u590D", meta: portableReceipt.recovery, status: "warning", compact: true })
|
|
3136
|
+
: _jsx(Callout, { status: "warning", title: "\u5B89\u5168\u6062\u590D", description: portableReceipt.recovery }) : null, showTechnical && portableReceipt ? _jsxs(TechnicalDetails, { children: [portableReceipt.failureCode ? _jsx(StaticRow, { title: "failure code", meta: portableReceipt.failureCode, compact: true }) : null, _jsx(StaticRow, { title: "verification missing", meta: String(portableReceipt.missing.length), compact: true }), _jsx(StaticRow, { title: "Skills CLI version", meta: portableReceipt.skillsCliVersion, compact: true }), partial || blocked
|
|
3137
|
+
? _jsx(StaticRow, { title: "\u53E6\u6709\u6280\u672F\u5B57\u6BB5\u5DF2\u6298\u53E0", meta: "materialization digest \u00B7 elapsed", status: "muted", compact: true })
|
|
3138
|
+
: _jsxs(_Fragment, { children: [_jsx(StaticRow, { title: "materialization digest", meta: portableReceipt.materializationDigest, compact: true }), resultSummary?.elapsedMs !== undefined ? _jsx(StaticRow, { title: "elapsed", meta: `${resultSummary.elapsedMs} ms`, compact: true }) : null] })] }) : null, showTechnical && !portableReceipt ? _jsxs(TechnicalDetails, { children: [_jsx(StaticRow, { title: "failure code", meta: 'code' in result ? result.code : 'operationFailed', compact: true }), _jsx(StaticRow, { title: "\u5176\u4F59\u6280\u672F\u5B57\u6BB5", meta: "\u672C\u6B21\u5199\u5165\u6CA1\u6709\u751F\u6210\u5B89\u5168 receipt", status: "muted", compact: true })] }) : null] }) }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: resultActionViewport.offset, limit: resultActionViewport.limit })] }) : _jsxs(Box, { flexDirection: "column", children: [_jsxs(ResultCard, { status: resultStatus, title: blockedDependencyFailure ? '依赖安装没有完成' : blocked ? `${result.writeMayHaveCompleted ? '结果未确认' : '操作未完成'} · ${result.code}` : sourceOperation ? '来源配置写入已完成' : dependencyTargets ? partial ? '依赖安装没有全部完成' : '操作已完成' : resultSummary?.message ?? '状态已变化,请重新确认', children: [visibleResultTargets.map((target) => { const state = operationTargetStateLabel(target.state); const relation = target.relation === 'requested' ? '你选择的插件' : target.relation === 'direct' ? '直接依赖' : target.relation === 'transitive' ? '间接依赖' : undefined; return _jsx(ResultRow, { title: resultTargetName(target), description: relation, meta: state.label, status: state.status ?? 'muted', compact: policy.mode === 'compact' }, `${target.pluginId ?? ''}:${target.platform}:${target.scope}`); }), hiddenResultTargets > 0 ? _jsx(StaticRow, { title: `另有 ${hiddenResultTargets} 个目标`, status: "muted", compact: true }) : null, partial && hasPreservedTarget && resultTargets.some((target) => target.state === 'skipped') ? _jsx(StaticRow, { title: "\u672A\u6267\u884C\u7684\u76EE\u6807\u6CA1\u6709\u5199\u5165\uFF1B\u5DF2\u5B8C\u6210\u6216\u5DF2\u4FDD\u7559\u7684\u76EE\u6807\u4FDD\u6301\u53EF\u7528\u3002", status: "muted", compact: true }) : null, resultSummary?.elapsedMs !== undefined ? _jsx(StaticRow, { title: "\u5DF2\u7528\u65F6", meta: `${Math.floor(resultSummary.elapsedMs / 1_000)}s` }) : null, visibleMessages.map((message, index) => _jsx(StaticRow, { title: message, status: "muted", compact: true }, `${index}:${message}`))] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: true, offset: resultActionViewport.offset, limit: resultActionViewport.limit })] });
|
|
2214
3139
|
}
|
|
2215
3140
|
else if (route.name === 'pluginAbout' && plugin) {
|
|
2216
3141
|
breadcrumb = `${plugin.displayName} / 关于`;
|
|
2217
3142
|
title = plugin.displayName;
|
|
2218
3143
|
description = plugin.description;
|
|
2219
3144
|
actions = [{ label: '查看技术详情', run: () => go({ name: 'pluginTechnical', pluginId: plugin.pluginId }) }, { label: '查看来源', run: () => go({ name: 'sourceDetail', sourceId: plugin.sourceId }) }];
|
|
2220
|
-
content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(
|
|
3145
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(SectionCard, { children: [_jsx(StaticRow, { title: "\u7248\u672C", meta: plugin.version ?? '未知' }), _jsx(StaticRow, { title: "\u6765\u6E90", meta: plugin.sourceLabel }), _jsx(StaticRow, { title: "\u53EF\u7528\u76EE\u6807\u5E73\u53F0", meta: plugin.supportedPlatforms.map(platformLabel).join('、') }), _jsx(StaticRow, { title: "\u914D\u7F6E", meta: `${plugin.configFieldCount} 个字段` })] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
|
|
2221
3146
|
}
|
|
2222
3147
|
else if (route.name === 'pluginTechnical' && plugin) {
|
|
2223
3148
|
breadcrumb = `${plugin.displayName} / 技术详情`;
|
|
@@ -2225,18 +3150,67 @@ export function ConsoleAppV4(props) {
|
|
|
2225
3150
|
title = '安装与身份详情';
|
|
2226
3151
|
description = '默认页面不会展示这些内部信息。';
|
|
2227
3152
|
actions = [{ label: '返回插件', run: back }];
|
|
2228
|
-
|
|
3153
|
+
const prioritizedComponents = [...plugin.technicalComponents].sort((left, right) => {
|
|
3154
|
+
const priority = (state) => state === 'error' ? 0
|
|
3155
|
+
: state === 'partial' ? 1 : state === 'absent' ? 2 : 3;
|
|
3156
|
+
return priority(left.state) - priority(right.state);
|
|
3157
|
+
});
|
|
3158
|
+
const detailRowBudget = responsiveBudget(9, 15);
|
|
3159
|
+
const componentRowBudget = Math.max(1, detailRowBudget - 2 - plugin.installedPlacements.length);
|
|
3160
|
+
const componentLimit = prioritizedComponents.length > componentRowBudget
|
|
3161
|
+
? Math.max(1, componentRowBudget - 1)
|
|
3162
|
+
: componentRowBudget;
|
|
3163
|
+
const visibleComponents = prioritizedComponents.slice(0, componentLimit);
|
|
3164
|
+
const hiddenComponents = prioritizedComponents.length - visibleComponents.length;
|
|
3165
|
+
content = _jsxs(Box, { flexDirection: "column", children: [_jsxs(TechnicalDetails, { children: [_jsx(StaticRow, { title: "canonical pluginId", meta: plugin.pluginId }), _jsx(StaticRow, { title: "\u6765\u6E90", meta: plugin.sourceId }), plugin.installedPlacements.map((placement) => _jsx(StaticRow, { title: `${platformLabel(placement.platform)} target`, meta: placementStatus(placement).value }, placement.platform)), visibleComponents.map((component) => _jsx(StaticRow, { title: `组件 · ${component.state === 'absent' ? '缺失' : component.state}${component.errorCode ? ` · ${component.errorCode}` : ''} · ${component.key} · ${platformLabel(component.platform)} · ${scopeLabel(component.scope)}` }, `${component.platform}:${component.scope}:${component.key}`)), hiddenComponents > 0 ? _jsx(StaticRow, { title: `另有 ${hiddenComponents} 个组件`, status: "muted", compact: true }) : null] }), _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' })] });
|
|
2229
3166
|
}
|
|
2230
3167
|
else {
|
|
2231
3168
|
breadcrumb = '后续阶段';
|
|
2232
3169
|
title = '此业务页将在后续阶段接入';
|
|
2233
3170
|
description = '当前阶段只实现插件发现、安装和维护页面。';
|
|
2234
3171
|
actions = [{ label: '返回上一页', run: back }];
|
|
2235
|
-
content = _jsx(
|
|
3172
|
+
content = _jsx(SemanticRows, { actions: actions, focus: focus, kind: "action", compact: policy.mode === 'compact' });
|
|
3173
|
+
}
|
|
3174
|
+
if (canOpenHelp && supportsTechnicalDetails) {
|
|
3175
|
+
const technicalHelp = route.name === 'progress' ? 'd 技术进度' : 'd 技术详情';
|
|
3176
|
+
const remainingHelp = help.replace(' · ? 帮助', '').replace('? 帮助 · ', '');
|
|
3177
|
+
help = `${technicalHelp} · ? 帮助 · ${remainingHelp}`;
|
|
2236
3178
|
}
|
|
3179
|
+
else if (canOpenHelp && !help.includes('? 帮助')) {
|
|
3180
|
+
help = `? 帮助 · ${help}`;
|
|
3181
|
+
}
|
|
3182
|
+
const targetActionSignature = route.name === 'installTargets' && route.view !== 'blocked'
|
|
3183
|
+
? actions.map((action) => String(Boolean(action.disabled))).join(',')
|
|
3184
|
+
: '';
|
|
3185
|
+
useEffect(() => {
|
|
3186
|
+
if (!targetActionSignature || !actions[focus]?.disabled)
|
|
3187
|
+
return;
|
|
3188
|
+
const firstEnabled = actions.findIndex((action) => !action.disabled);
|
|
3189
|
+
if (firstEnabled < 0)
|
|
3190
|
+
return;
|
|
3191
|
+
setFocus(firstEnabled);
|
|
3192
|
+
setScrollOffset(0);
|
|
3193
|
+
}, [focus, targetActionSignature]);
|
|
3194
|
+
useEffect(() => {
|
|
3195
|
+
if (!dynamicActionIds || dynamicActionSignature === undefined) {
|
|
3196
|
+
dynamicMenuFocus.current = undefined;
|
|
3197
|
+
return;
|
|
3198
|
+
}
|
|
3199
|
+
if (effectiveFocus !== focus) {
|
|
3200
|
+
setFocus(effectiveFocus);
|
|
3201
|
+
setScrollOffset(0);
|
|
3202
|
+
}
|
|
3203
|
+
dynamicMenuFocus.current = {
|
|
3204
|
+
routeKey,
|
|
3205
|
+
actionIds: dynamicActionSignature,
|
|
3206
|
+
focusedActionId: dynamicActionIds[effectiveFocus],
|
|
3207
|
+
};
|
|
3208
|
+
}, [dynamicActionSignature, routeKey]);
|
|
2237
3209
|
const runAction = (action) => {
|
|
3210
|
+
if (!action || action.disabled)
|
|
3211
|
+
return;
|
|
2238
3212
|
try {
|
|
2239
|
-
const pending = action
|
|
3213
|
+
const pending = action.run?.();
|
|
2240
3214
|
if (pending)
|
|
2241
3215
|
void pending.catch(() => {
|
|
2242
3216
|
setActionError('操作未完成。请重新检查后重试。');
|
|
@@ -2247,11 +3221,37 @@ export function ConsoleAppV4(props) {
|
|
|
2247
3221
|
}
|
|
2248
3222
|
};
|
|
2249
3223
|
useInput((input, key) => {
|
|
3224
|
+
if (!overlay && route.name === 'reviewPreparing' && snapshot.reviewPreparation?.state === 'failed'
|
|
3225
|
+
&& (key.escape || input.toLowerCase() === 'b')) {
|
|
3226
|
+
back();
|
|
3227
|
+
return;
|
|
3228
|
+
}
|
|
3229
|
+
if (preparationRunning) {
|
|
3230
|
+
if (key.escape || input.toLowerCase() === 'b') {
|
|
3231
|
+
back();
|
|
3232
|
+
}
|
|
3233
|
+
else if (input.toLowerCase() === 'q' || (key.ctrl && input.toLowerCase() === 'c')) {
|
|
3234
|
+
setExitAfterPreparation(true);
|
|
3235
|
+
}
|
|
3236
|
+
return;
|
|
3237
|
+
}
|
|
2250
3238
|
if (busy) {
|
|
2251
|
-
if (
|
|
3239
|
+
if (overlay === 'help') {
|
|
3240
|
+
if (key.escape || key.return || input.toLowerCase() === 'b' || input === '?') {
|
|
3241
|
+
setOverlay(undefined);
|
|
3242
|
+
}
|
|
3243
|
+
else if (key.ctrl && input.toLowerCase() === 'c') {
|
|
3244
|
+
setExitAfterBusy(true);
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
else if (input.toLowerCase() === 'q' || (key.ctrl && input.toLowerCase() === 'c')) {
|
|
2252
3248
|
setExitAfterBusy(true);
|
|
2253
3249
|
}
|
|
2254
|
-
else if (
|
|
3250
|
+
else if (canOpenHelp && input === '?') {
|
|
3251
|
+
setOverlayFocus(0);
|
|
3252
|
+
setOverlay('help');
|
|
3253
|
+
}
|
|
3254
|
+
else if (!tooSmall && supportsTechnicalDetails && input.toLowerCase() === 'd') {
|
|
2255
3255
|
setShowTechnical((value) => !value);
|
|
2256
3256
|
}
|
|
2257
3257
|
return;
|
|
@@ -2272,7 +3272,8 @@ export function ConsoleAppV4(props) {
|
|
|
2272
3272
|
return;
|
|
2273
3273
|
}
|
|
2274
3274
|
if (key.leftArrow || key.rightArrow || key.upArrow || key.downArrow) {
|
|
2275
|
-
|
|
3275
|
+
if (overlay !== 'help')
|
|
3276
|
+
setOverlayFocus((value) => value === 0 ? 1 : 0);
|
|
2276
3277
|
return;
|
|
2277
3278
|
}
|
|
2278
3279
|
if (key.return) {
|
|
@@ -2314,7 +3315,7 @@ export function ConsoleAppV4(props) {
|
|
|
2314
3315
|
if (key.return) {
|
|
2315
3316
|
try {
|
|
2316
3317
|
const normalizedInput = portableAgentInput.trim();
|
|
2317
|
-
const agents = normalizePortableAgents(normalizedInput ? normalizedInput.split(
|
|
3318
|
+
const agents = normalizePortableAgents(normalizedInput ? normalizedInput.split(/[\s,]+/u) : []);
|
|
2318
3319
|
props.controller.setPortableAgentMode('explicit', agents);
|
|
2319
3320
|
setInputError(undefined);
|
|
2320
3321
|
go({ name: 'portableScope', pluginId: route.pluginId });
|
|
@@ -2475,11 +3476,10 @@ export function ConsoleAppV4(props) {
|
|
|
2475
3476
|
}
|
|
2476
3477
|
if (key.tab && (route.name === 'configLayer'
|
|
2477
3478
|
|| route.name === 'configFields'
|
|
2478
|
-
|| route.name === 'configFieldEditor'
|
|
2479
|
-
|| route.name === 'configPresets')) {
|
|
3479
|
+
|| route.name === 'configFieldEditor')) {
|
|
2480
3480
|
return;
|
|
2481
3481
|
}
|
|
2482
|
-
if (input === '?') {
|
|
3482
|
+
if (canOpenHelp && input === '?') {
|
|
2483
3483
|
setOverlayFocus(0);
|
|
2484
3484
|
setOverlay('help');
|
|
2485
3485
|
}
|
|
@@ -2488,18 +3488,11 @@ export function ConsoleAppV4(props) {
|
|
|
2488
3488
|
setDiscardForExit(dirty);
|
|
2489
3489
|
setOverlay(dirty ? 'discard' : 'exit');
|
|
2490
3490
|
}
|
|
2491
|
-
else if (input.toLowerCase() === 'd' &&
|
|
2492
|
-
|| route.name === 'issueDetail'
|
|
2493
|
-
|| route.name === 'configUnavailable'
|
|
2494
|
-
|| route.name === 'pluginDependencies'
|
|
2495
|
-
|| route.name === 'portableDependencies'
|
|
2496
|
-
|| route.name === 'portableBlocker')) {
|
|
3491
|
+
else if (input.toLowerCase() === 'd' && supportsTechnicalDetails) {
|
|
2497
3492
|
setShowTechnical((value) => !value);
|
|
2498
3493
|
}
|
|
2499
3494
|
else if ((key.escape || input.toLowerCase() === 'b')
|
|
2500
|
-
&&
|
|
2501
|
-
|| route.name === 'configPresets'
|
|
2502
|
-
|| (route.name === 'configFields' && Boolean(route.groupId)))) {
|
|
3495
|
+
&& route.name === 'configFieldEditor') {
|
|
2503
3496
|
back();
|
|
2504
3497
|
}
|
|
2505
3498
|
else if (key.escape || input.toLowerCase() === 'b') {
|
|
@@ -2514,6 +3507,23 @@ export function ConsoleAppV4(props) {
|
|
|
2514
3507
|
else if (route.name === 'pluginList' && input === '/') {
|
|
2515
3508
|
setSearchMode(true);
|
|
2516
3509
|
}
|
|
3510
|
+
else if (route.name === 'pluginList' && route.mode === 'browse' && input.toLowerCase() === 'f') {
|
|
3511
|
+
const selectedPluginId = pluginRows[focus]?.pluginId;
|
|
3512
|
+
const nextFilter = installFilter === 'all' ? 'uninstalled' : 'all';
|
|
3513
|
+
const nextItems = props.controller.listPlugins({
|
|
3514
|
+
mode: route.mode,
|
|
3515
|
+
...(route.sourceId ? { sourceId: route.sourceId } : {}),
|
|
3516
|
+
...(query ? { query } : {}),
|
|
3517
|
+
installFilter: nextFilter,
|
|
3518
|
+
});
|
|
3519
|
+
const retained = selectedPluginId
|
|
3520
|
+
? nextItems.findIndex((item) => item.pluginId === selectedPluginId)
|
|
3521
|
+
: -1;
|
|
3522
|
+
setInstallFilter(nextFilter);
|
|
3523
|
+
props.controller.updatePageContext({ installFilter: nextFilter });
|
|
3524
|
+
setFocus(retained >= 0 ? retained : 0);
|
|
3525
|
+
setScrollOffset(0);
|
|
3526
|
+
}
|
|
2517
3527
|
else if (route.name === 'configFields' && route.layer.scope !== 'effective' && input.toLowerCase() === 's') {
|
|
2518
3528
|
if (dirty)
|
|
2519
3529
|
void beginConfigSave();
|
|
@@ -2523,6 +3533,15 @@ export function ConsoleAppV4(props) {
|
|
|
2523
3533
|
if (key)
|
|
2524
3534
|
removeDraft(key);
|
|
2525
3535
|
}
|
|
3536
|
+
else if (route.name === 'configFields' && visibleConfigTabs.length > 1
|
|
3537
|
+
&& (key.leftArrow || key.rightArrow)) {
|
|
3538
|
+
const current = Math.max(0, visibleConfigTabs.findIndex((tab) => tab.id === activeConfigTabId));
|
|
3539
|
+
const direction = key.leftArrow ? -1 : 1;
|
|
3540
|
+
const next = visibleConfigTabs[(current + direction + visibleConfigTabs.length) % visibleConfigTabs.length];
|
|
3541
|
+
props.controller.updatePageContext({ configGroupId: next.id, focusIndex: 0, scrollOffset: 0 });
|
|
3542
|
+
setFocus(0);
|
|
3543
|
+
setScrollOffset(0);
|
|
3544
|
+
}
|
|
2526
3545
|
else if (input.toLowerCase() === 'r' && route.name === 'sourceDetail') {
|
|
2527
3546
|
void props.controller.refreshSource(route.sourceId);
|
|
2528
3547
|
}
|
|
@@ -2538,13 +3557,14 @@ export function ConsoleAppV4(props) {
|
|
|
2538
3557
|
mode: route.mode,
|
|
2539
3558
|
...(current !== 'all' ? { sourceId: current } : {}),
|
|
2540
3559
|
...(query ? { query } : {}),
|
|
3560
|
+
installFilter,
|
|
2541
3561
|
})[focus]?.pluginId;
|
|
2542
3562
|
const direction = key.leftArrow || input === '[' ? -1 : 1;
|
|
2543
3563
|
const index = tabs.indexOf(current);
|
|
2544
3564
|
const next = tabs[(index + direction + tabs.length) % tabs.length];
|
|
2545
|
-
const nextItems = props.controller.listPlugins({ mode: route.mode, ...(next !== 'all' ? { sourceId: next } : {}), ...(query ? { query } : {}) });
|
|
3565
|
+
const nextItems = props.controller.listPlugins({ mode: route.mode, ...(next !== 'all' ? { sourceId: next } : {}), ...(query ? { query } : {}), installFilter });
|
|
2546
3566
|
const retained = selectedPluginId ? nextItems.findIndex((item) => item.pluginId === selectedPluginId) : -1;
|
|
2547
|
-
props.controller.replace({ name: 'pluginList', mode: route.mode, ...(next !== 'all' ? { sourceId: next } : {}) }, { ...snapshot.router.current.context, sourceTabId: next });
|
|
3567
|
+
props.controller.replace({ name: 'pluginList', mode: route.mode, ...(next !== 'all' ? { sourceId: next } : {}) }, { ...snapshot.router.current.context, query, installFilter, sourceTabId: next });
|
|
2548
3568
|
setFocus(retained >= 0 ? retained : 0);
|
|
2549
3569
|
setScrollOffset(retained >= 4 ? retained - 3 : 0);
|
|
2550
3570
|
}
|
|
@@ -2558,56 +3578,68 @@ export function ConsoleAppV4(props) {
|
|
|
2558
3578
|
}
|
|
2559
3579
|
else if (key.upArrow || input.toLowerCase() === 'k') {
|
|
2560
3580
|
setFocus((value) => {
|
|
2561
|
-
const
|
|
3581
|
+
const current = dynamicActionIds ? effectiveFocus : value;
|
|
3582
|
+
const next = pluginRows.length > 0 ? moveIndex(pluginRows.length, current, -1) : moveFocus(actions, current, -1);
|
|
3583
|
+
if (dynamicActionIds && dynamicActionSignature !== undefined) {
|
|
3584
|
+
dynamicMenuFocus.current = {
|
|
3585
|
+
routeKey,
|
|
3586
|
+
actionIds: dynamicActionSignature,
|
|
3587
|
+
focusedActionId: dynamicActionIds[next],
|
|
3588
|
+
};
|
|
3589
|
+
}
|
|
2562
3590
|
const viewport = route.name === 'portableSkillSelect' && snapshot.portableDraft
|
|
2563
|
-
? portableSkillCandidateViewport(actions.slice(0, snapshot.portableDraft.candidates.length), snapshot.portableDraft.candidates, policy.mode, next, scrollOffset
|
|
2564
|
-
|
|
3591
|
+
? portableSkillCandidateViewport(actions.slice(0, snapshot.portableDraft.candidates.length), snapshot.portableDraft.candidates, policy.mode === 'compact', next, scrollOffset, pageActionBudget(actions.length - snapshot.portableDraft.candidates.length
|
|
3592
|
+
+ (portableVariantCandidates.length > 0 ? 3 : 0)))
|
|
3593
|
+
: actionViewport(actions, policy.mode === 'compact', next, scrollOffset, pageActionBudget());
|
|
2565
3594
|
setScrollOffset(viewport.offset);
|
|
2566
3595
|
return next;
|
|
2567
3596
|
});
|
|
2568
3597
|
}
|
|
2569
3598
|
else if (key.downArrow || input.toLowerCase() === 'j') {
|
|
2570
3599
|
setFocus((value) => {
|
|
2571
|
-
const
|
|
3600
|
+
const current = dynamicActionIds ? effectiveFocus : value;
|
|
3601
|
+
const next = pluginRows.length > 0 ? moveIndex(pluginRows.length, current, 1) : moveFocus(actions, current, 1);
|
|
3602
|
+
if (dynamicActionIds && dynamicActionSignature !== undefined) {
|
|
3603
|
+
dynamicMenuFocus.current = {
|
|
3604
|
+
routeKey,
|
|
3605
|
+
actionIds: dynamicActionSignature,
|
|
3606
|
+
focusedActionId: dynamicActionIds[next],
|
|
3607
|
+
};
|
|
3608
|
+
}
|
|
2572
3609
|
const viewport = route.name === 'portableSkillSelect' && snapshot.portableDraft
|
|
2573
|
-
? portableSkillCandidateViewport(actions.slice(0, snapshot.portableDraft.candidates.length), snapshot.portableDraft.candidates, policy.mode, next, scrollOffset
|
|
2574
|
-
|
|
3610
|
+
? portableSkillCandidateViewport(actions.slice(0, snapshot.portableDraft.candidates.length), snapshot.portableDraft.candidates, policy.mode === 'compact', next, scrollOffset, pageActionBudget(actions.length - snapshot.portableDraft.candidates.length
|
|
3611
|
+
+ (portableVariantCandidates.length > 0 ? 3 : 0)))
|
|
3612
|
+
: actionViewport(actions, policy.mode === 'compact', next, scrollOffset, pageActionBudget());
|
|
2575
3613
|
setScrollOffset(viewport.offset);
|
|
2576
3614
|
return next;
|
|
2577
3615
|
});
|
|
2578
3616
|
}
|
|
2579
|
-
else if (
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
if (tools.length > 0) {
|
|
2584
|
-
setDirty(false);
|
|
2585
|
-
back();
|
|
2586
|
-
}
|
|
3617
|
+
else if (key.return && route.name === 'pluginList' && pluginRows[focus]) {
|
|
3618
|
+
go(route.mode === 'browse'
|
|
3619
|
+
? { name: 'pluginPreview', pluginId: pluginRows[focus].pluginId }
|
|
3620
|
+
: { name: 'pluginDetail', pluginId: pluginRows[focus].pluginId });
|
|
2587
3621
|
}
|
|
2588
3622
|
else if (key.return) {
|
|
2589
|
-
runAction(actions[
|
|
3623
|
+
runAction(actions[effectiveFocus]);
|
|
2590
3624
|
}
|
|
2591
3625
|
});
|
|
2592
3626
|
if (tooSmall) {
|
|
2593
|
-
return (_jsx(ScreenFrame, { title: "\u7EC8\u7AEF\u7A7A\u95F4\u4E0D\u8DB3", breadcrumb: "\u7EC8\u7AEF\u7A7A\u95F4\u4E0D\u8DB3", context: `${size.columns} × ${size.rows}`, help: "q \u9000\u51FA", size: size, children:
|
|
3627
|
+
return (_jsx(ScreenFrame, { title: "\u7EC8\u7AEF\u7A7A\u95F4\u4E0D\u8DB3", breadcrumb: "\u7EC8\u7AEF\u7A7A\u95F4\u4E0D\u8DB3", context: `${size.columns} × ${size.rows}`, help: "q \u9000\u51FA", size: size, children: _jsx(Callout, { status: "warning", title: "\u2194 \u8BF7\u5C06\u7EC8\u7AEF\u8C03\u6574\u5230\u81F3\u5C11 60 \u00D7 18", description: "\u8C03\u6574\u540E\u4F1A\u8FD4\u56DE\u539F\u9875\u9762\uFF0C\u4E0D\u4F1A\u4E22\u5931\u5F53\u524D\u9009\u62E9\u3002" }) }));
|
|
2594
3628
|
}
|
|
2595
3629
|
if (actionError)
|
|
2596
3630
|
description = actionError;
|
|
2597
3631
|
const base = (_jsx(ScreenFrame, { title: title, description: description, breadcrumb: breadcrumb, context: context, help: help, size: size, children: content }));
|
|
2598
3632
|
if (!overlay)
|
|
2599
3633
|
return base;
|
|
2600
|
-
const helpRows =
|
|
2601
|
-
|
|
2602
|
-
['
|
|
2603
|
-
[
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
? _jsx(_Fragment, { children: helpRows.map(([key, value]) => _jsxs(Text, { children: [key.padEnd(9), value] }, key)) })
|
|
2611
|
-
: _jsx(Text, { color: tuiTheme.colors.muted, children: overlay === 'discard' ? '放弃后将返回上一页。' : '进行中的写操作会先完成。' }), _jsxs(Text, { bold: true, color: overlayFocus === 0 ? tuiTheme.colors.accent : undefined, children: ["\u203A ", overlay === 'help' ? '关闭' : overlay === 'discard' ? '继续编辑' : '继续使用'] }), overlay === 'exit' || overlay === 'discard' ? _jsxs(Text, { bold: true, color: overlayFocus === 1 ? tuiTheme.statusColors.error : undefined, children: [overlayFocus === 1 ? '› ' : ' ', overlay === 'discard' ? '放弃修改' : '退出'] }) : null] })] }));
|
|
3634
|
+
const helpRows = helpRowsForRoute(route, actions.length, supportsTechnicalDetails, busy);
|
|
3635
|
+
const overlayActions = overlay === 'help'
|
|
3636
|
+
? [{ label: '关闭' }]
|
|
3637
|
+
: [
|
|
3638
|
+
{ label: overlay === 'discard' ? '继续编辑' : '继续使用' },
|
|
3639
|
+
{ label: overlay === 'discard' ? '放弃修改' : '退出', status: 'error' },
|
|
3640
|
+
];
|
|
3641
|
+
return (_jsxs(Box, { position: "relative", width: size.columns, height: size.rows, children: [_jsx(Box, { children: overlay === 'help' ? null : base }), _jsx(Box, { position: "absolute", marginTop: 5, marginLeft: Math.max(2, Math.floor(size.columns / 5)), width: Math.max(40, Math.floor(size.columns * 0.6)), flexDirection: "column", children: _jsxs(ReviewCard, { title: overlay === 'help' ? '当前页快捷键' : overlay === 'discard' ? '放弃未保存修改?' : '退出控制台?', children: [overlay === 'help'
|
|
3642
|
+
? _jsx(_Fragment, { children: helpRows.map(([key, value]) => _jsx(StaticRow, { title: `${key} ${value}`, compact: true }, key)) })
|
|
3643
|
+
: _jsx(StaticRow, { title: overlay === 'discard' ? '放弃后将返回上一页。' : '进行中的写操作会先完成。', status: "muted" }), _jsx(SemanticRows, { actions: overlayActions, focus: overlayFocus, kind: "action", compact: true })] }) })] }));
|
|
2612
3644
|
}
|
|
2613
3645
|
//# sourceMappingURL=console-app-v4.js.map
|