@uwa4d/openapi-mcp 0.2.0-beta.5 → 0.2.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -5
- package/dist/annotate-dashboard.d.ts +34 -0
- package/dist/annotate-dashboard.js +275 -0
- package/dist/composite/name-pattern.d.ts +8 -0
- package/dist/composite/name-pattern.js +25 -0
- package/dist/composite/report-diagnosis.js +42 -2
- package/dist/composite/stack-agg.d.ts +15 -3
- package/dist/composite/stack-agg.js +154 -2
- package/dist/composite/stack-test-mode.d.ts +33 -0
- package/dist/composite/stack-test-mode.js +60 -0
- package/dist/composite/top-functions.d.ts +7 -2
- package/dist/composite/top-functions.js +74 -29
- package/dist/indicator-dashboard-keys.d.ts +12 -0
- package/dist/indicator-dashboard-keys.js +49 -0
- package/dist/indicator-dashboard-keys.json +338 -0
- package/dist/presets.js +5 -0
- package/dist/tools.d.ts +4 -0
- package/dist/tools.js +135 -11
- package/dist/version-guide.d.ts +5 -0
- package/dist/version-guide.js +101 -1
- package/package.json +2 -2
- package/spec/uwa-openapi.json +490 -106
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { StackMetric } from './stack-agg.js';
|
|
2
|
+
/**
|
|
3
|
+
* Overview 堆栈采集模式(来自 overview/stack/overall/tree/presign 的 testMode)。
|
|
4
|
+
* 仅适用于 Overview 报告;Mono 报告走独立堆栈,不使用本枚举。
|
|
5
|
+
*/
|
|
6
|
+
export type OverviewStackTestMode = 'CPU_ONLY' | 'LUA_MEM_ONLY' | 'CPU_AND_LUA_MEM';
|
|
7
|
+
/** @deprecated 使用 OverviewStackTestMode */
|
|
8
|
+
export type StackTestMode = OverviewStackTestMode;
|
|
9
|
+
export interface OverviewStackTestModeProfile {
|
|
10
|
+
stackTestMode: OverviewStackTestMode;
|
|
11
|
+
availableMetrics: StackMetric[];
|
|
12
|
+
availableFields: readonly string[];
|
|
13
|
+
description: string;
|
|
14
|
+
}
|
|
15
|
+
/** @deprecated 使用 OverviewStackTestModeProfile */
|
|
16
|
+
export type StackTestModeProfile = OverviewStackTestModeProfile;
|
|
17
|
+
export declare const OVERVIEW_STACK_TEST_MODE_PROFILES: Record<OverviewStackTestMode, OverviewStackTestModeProfile>;
|
|
18
|
+
export declare const STACK_TEST_MODE_PROFILES: Record<OverviewStackTestMode, OverviewStackTestModeProfile>;
|
|
19
|
+
export declare function normalizeOverviewStackTestMode(raw: unknown): OverviewStackTestMode | null;
|
|
20
|
+
/** @deprecated 使用 normalizeOverviewStackTestMode */
|
|
21
|
+
export declare const normalizeStackTestMode: typeof normalizeOverviewStackTestMode;
|
|
22
|
+
export declare function inferOverviewStackTestMode(flags: {
|
|
23
|
+
hasTime: boolean;
|
|
24
|
+
hasMem: boolean;
|
|
25
|
+
}): OverviewStackTestMode | null;
|
|
26
|
+
/** @deprecated 使用 inferOverviewStackTestMode */
|
|
27
|
+
export declare const inferStackTestMode: typeof inferOverviewStackTestMode;
|
|
28
|
+
export declare function resolveOverviewDefaultMetric(stackTestMode: OverviewStackTestMode): StackMetric;
|
|
29
|
+
/** @deprecated 使用 resolveOverviewDefaultMetric */
|
|
30
|
+
export declare const resolveDefaultMetric: typeof resolveOverviewDefaultMetric;
|
|
31
|
+
export declare function assertOverviewMetricSupported(metric: StackMetric, profile: OverviewStackTestModeProfile): void;
|
|
32
|
+
/** @deprecated 使用 assertOverviewMetricSupported */
|
|
33
|
+
export declare const assertMetricSupported: typeof assertOverviewMetricSupported;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export const OVERVIEW_STACK_TEST_MODE_PROFILES = {
|
|
2
|
+
CPU_ONLY: {
|
|
3
|
+
stackTestMode: 'CPU_ONLY',
|
|
4
|
+
availableMetrics: ['selfTime', 'totalTime', 'callCount'],
|
|
5
|
+
availableFields: ['selfTimeMean', 'totalTimeMean', 'callCountTotal'],
|
|
6
|
+
description: 'CPU_ONLY:仅有耗时(selfTimeMean/totalTimeMean,ms)与调用次数(callCountTotal);内存字段恒为 null。',
|
|
7
|
+
},
|
|
8
|
+
LUA_MEM_ONLY: {
|
|
9
|
+
stackTestMode: 'LUA_MEM_ONLY',
|
|
10
|
+
availableMetrics: ['callCount', 'selfMemory'],
|
|
11
|
+
availableFields: ['callCountTotal', 'selfMemoryMean', 'selfMemoryTotal'],
|
|
12
|
+
description: 'LUA_MEM_ONLY:仅有调用次数(callCountTotal)与内存(selfMemoryMean/selfMemoryTotal);耗时字段恒为 null。',
|
|
13
|
+
},
|
|
14
|
+
CPU_AND_LUA_MEM: {
|
|
15
|
+
stackTestMode: 'CPU_AND_LUA_MEM',
|
|
16
|
+
availableMetrics: ['selfTime', 'totalTime', 'callCount', 'selfMemory'],
|
|
17
|
+
availableFields: [
|
|
18
|
+
'selfTimeMean',
|
|
19
|
+
'totalTimeMean',
|
|
20
|
+
'callCountTotal',
|
|
21
|
+
'selfMemoryMean',
|
|
22
|
+
'selfMemoryTotal',
|
|
23
|
+
],
|
|
24
|
+
description: 'CPU_AND_LUA_MEM:耗时、调用次数、内存均可用。',
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
export const STACK_TEST_MODE_PROFILES = OVERVIEW_STACK_TEST_MODE_PROFILES;
|
|
28
|
+
export function normalizeOverviewStackTestMode(raw) {
|
|
29
|
+
const tm = String(raw ?? '').toUpperCase();
|
|
30
|
+
if (tm === 'CPU_ONLY' || tm === 'LUA_MEM_ONLY' || tm === 'CPU_AND_LUA_MEM') {
|
|
31
|
+
return tm;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
/** @deprecated 使用 normalizeOverviewStackTestMode */
|
|
36
|
+
export const normalizeStackTestMode = normalizeOverviewStackTestMode;
|
|
37
|
+
export function inferOverviewStackTestMode(flags) {
|
|
38
|
+
if (flags.hasTime && flags.hasMem)
|
|
39
|
+
return 'CPU_AND_LUA_MEM';
|
|
40
|
+
if (flags.hasMem && !flags.hasTime)
|
|
41
|
+
return 'LUA_MEM_ONLY';
|
|
42
|
+
if (flags.hasTime)
|
|
43
|
+
return 'CPU_ONLY';
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
/** @deprecated 使用 inferOverviewStackTestMode */
|
|
47
|
+
export const inferStackTestMode = inferOverviewStackTestMode;
|
|
48
|
+
export function resolveOverviewDefaultMetric(stackTestMode) {
|
|
49
|
+
return stackTestMode === 'LUA_MEM_ONLY' ? 'selfMemory' : 'selfTime';
|
|
50
|
+
}
|
|
51
|
+
/** @deprecated 使用 resolveOverviewDefaultMetric */
|
|
52
|
+
export const resolveDefaultMetric = resolveOverviewDefaultMetric;
|
|
53
|
+
export function assertOverviewMetricSupported(metric, profile) {
|
|
54
|
+
if (!profile.availableMetrics.includes(metric)) {
|
|
55
|
+
throw new Error(`当前 Overview stackTestMode=${profile.stackTestMode} 不支持 metric=${metric}。` +
|
|
56
|
+
`可用:${profile.availableMetrics.join('、')}。${profile.description}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** @deprecated 使用 assertOverviewMetricSupported */
|
|
60
|
+
export const assertMetricSupported = assertOverviewMetricSupported;
|
|
@@ -7,8 +7,13 @@ export interface FunctionRow {
|
|
|
7
7
|
source: string;
|
|
8
8
|
label?: string;
|
|
9
9
|
methodId?: string;
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
stackMethodId?: string;
|
|
11
|
+
selfTimeMean?: number | null;
|
|
12
|
+
totalTimeMean?: number | null;
|
|
13
|
+
callCountTotal?: number | null;
|
|
14
|
+
selfMemoryMean?: number | null;
|
|
15
|
+
selfMemoryTotal?: number | null;
|
|
16
|
+
selfMemoryUnit?: string | null;
|
|
12
17
|
callCount?: number;
|
|
13
18
|
nodeCount?: number;
|
|
14
19
|
}
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { z } from './types.js';
|
|
2
2
|
import { errorResult, jsonToolResult, requireReportKey, topNOf } from './helpers.js';
|
|
3
3
|
import { fetchReportIdentity } from './route-overview.js';
|
|
4
|
-
import { aggregateMonoStack,
|
|
5
|
-
|
|
4
|
+
import { aggregateMonoStack, aggregateOverviewStackFromStatistic } from './stack-agg.js';
|
|
5
|
+
import { filterRowsByNamePattern } from './name-pattern.js';
|
|
6
|
+
function resolveOverviewMetricInput(raw) {
|
|
6
7
|
if (raw === 'totalTime' || raw === 'callCount' || raw === 'selfMemory' || raw === 'selfTime') {
|
|
7
8
|
return raw;
|
|
8
9
|
}
|
|
9
|
-
return
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
function resolveMonoMetric(raw) {
|
|
13
|
+
if (raw === 'totalTime' || raw === 'callCount' || raw === 'selfMemory' || raw === 'selfTime') {
|
|
14
|
+
return raw;
|
|
15
|
+
}
|
|
16
|
+
return 'selfMemory';
|
|
10
17
|
}
|
|
11
18
|
export async function runTopFunctions(client, args) {
|
|
12
19
|
const key = requireReportKey(args);
|
|
@@ -17,33 +24,42 @@ export async function runTopFunctions(client, args) {
|
|
|
17
24
|
const identity = await fetchReportIdentity(client, key);
|
|
18
25
|
const apisUsed = ['get_report_detail'];
|
|
19
26
|
const mode = subtype ?? identity.serviceSubtype ?? 'overview';
|
|
20
|
-
const metric = resolveMetric(args['metric'], mode);
|
|
21
27
|
if (mode === 'mono') {
|
|
28
|
+
const metric = resolveMonoMetric(args['metric']);
|
|
22
29
|
if (identity.engine !== 'unity') {
|
|
23
30
|
return {
|
|
24
31
|
engine: identity.engine,
|
|
25
32
|
dataKey: identity.dataKey,
|
|
33
|
+
serviceSubtype: 'mono',
|
|
26
34
|
items: [],
|
|
27
35
|
_apisUsed: apisUsed,
|
|
28
|
-
_note: 'Mono 模式仅支持 Unity
|
|
36
|
+
_note: 'Mono 模式仅支持 Unity 报告(与 Overview stackTestMode 无关)。',
|
|
29
37
|
_limitations: ['engine!=unity'],
|
|
30
38
|
};
|
|
31
39
|
}
|
|
32
40
|
try {
|
|
33
|
-
const
|
|
41
|
+
const namePattern = typeof args['namePattern'] === 'string' && args['namePattern'].trim()
|
|
42
|
+
? String(args['namePattern']).trim()
|
|
43
|
+
: undefined;
|
|
44
|
+
const fetchN = namePattern ? Math.max(topN, 200) : topN;
|
|
45
|
+
const agg = await aggregateMonoStack(client, identity, fetchN, metric);
|
|
46
|
+
const filtered = filterRowsByNamePattern(agg.rows, namePattern);
|
|
47
|
+
const items = filtered.rows.slice(0, topN);
|
|
34
48
|
return {
|
|
35
49
|
engine: identity.engine,
|
|
36
50
|
dataKey: identity.dataKey,
|
|
37
51
|
serviceSubtype: 'mono',
|
|
38
52
|
topN,
|
|
39
53
|
metric: agg.metric,
|
|
40
|
-
|
|
41
|
-
|
|
54
|
+
namePattern: namePattern ?? null,
|
|
55
|
+
matchedFunctions: namePattern ? filtered.matchedTotal : agg.uniqueFunctions,
|
|
56
|
+
items,
|
|
42
57
|
totalNodes: agg.totalNodes,
|
|
43
58
|
uniqueFunctions: agg.uniqueFunctions,
|
|
44
59
|
_apisUsed: [...apisUsed, ...agg.apisUsed],
|
|
45
|
-
_note:
|
|
46
|
-
|
|
60
|
+
_note: `Mono 模式:已下载正向堆栈树合并取 Top ${items.length}(共 ${agg.totalNodes} 节点)。` +
|
|
61
|
+
`Mono 不使用 Overview stackTestMode;默认按 selfMemory 排序。` +
|
|
62
|
+
(namePattern ? ` namePattern「${String(namePattern)}」命中 ${filtered.matchedTotal} 个。` : ''),
|
|
47
63
|
};
|
|
48
64
|
}
|
|
49
65
|
catch (e) {
|
|
@@ -58,25 +74,44 @@ export async function runTopFunctions(client, args) {
|
|
|
58
74
|
};
|
|
59
75
|
}
|
|
60
76
|
}
|
|
61
|
-
|
|
77
|
+
const threadName = typeof args['threadName'] === 'string' && args['threadName'].trim()
|
|
78
|
+
? String(args['threadName']).trim()
|
|
79
|
+
: undefined;
|
|
80
|
+
const metricInput = resolveOverviewMetricInput(args['metric']);
|
|
81
|
+
const namePattern = typeof args['namePattern'] === 'string' && args['namePattern'].trim()
|
|
82
|
+
? String(args['namePattern']).trim()
|
|
83
|
+
: undefined;
|
|
62
84
|
try {
|
|
63
|
-
const agg = await
|
|
85
|
+
const agg = await aggregateOverviewStackFromStatistic(client, identity, topN, metricInput, threadName, namePattern);
|
|
86
|
+
const nameFilterNote = namePattern
|
|
87
|
+
? agg.rows.length === 0
|
|
88
|
+
? ` namePattern「${namePattern}」无匹配函数。`
|
|
89
|
+
: ` namePattern「${namePattern}」已过滤后取 Top ${agg.rows.length}。`
|
|
90
|
+
: '';
|
|
64
91
|
return {
|
|
65
92
|
engine: identity.engine,
|
|
66
93
|
dataKey: identity.dataKey,
|
|
67
94
|
createDate: identity.createDate,
|
|
68
95
|
sdkVersion: identity.sdkVersion,
|
|
69
|
-
serviceSubtype:
|
|
96
|
+
serviceSubtype: 'overview',
|
|
97
|
+
stackTestMode: agg.stackTestMode,
|
|
98
|
+
availableMetrics: agg.availableMetrics,
|
|
99
|
+
availableFields: agg.availableFields,
|
|
70
100
|
topN,
|
|
71
101
|
metric: agg.metric,
|
|
72
|
-
|
|
102
|
+
namePattern: namePattern ?? null,
|
|
73
103
|
items: agg.rows,
|
|
74
104
|
totalNodes: agg.totalNodes,
|
|
75
105
|
uniqueFunctions: agg.uniqueFunctions,
|
|
76
106
|
_apisUsed: [...apisUsed, ...agg.apisUsed],
|
|
77
|
-
_note:
|
|
78
|
-
|
|
79
|
-
|
|
107
|
+
_note: `Overview 模式 stackTestMode=${agg.stackTestMode}。${agg.metricGuide ?? ''} ` +
|
|
108
|
+
`本次按 ${agg.metric} 排序,Top ${agg.rows.length} / 共 ${agg.totalNodes} 函数。` +
|
|
109
|
+
`数据来自 method/idmap/statistic v1.0.2(非堆栈树解析)。` +
|
|
110
|
+
`逐帧曲线:method/curve/presign v1.0.2 + stackMethodId。` +
|
|
111
|
+
(identity.engine === 'unreal'
|
|
112
|
+
? ` UE 默认 threadName=GameThread${threadName ? `(本次 ${threadName})` : ''};UE 固定 CPU_ONLY。`
|
|
113
|
+
: '') +
|
|
114
|
+
nameFilterNote,
|
|
80
115
|
};
|
|
81
116
|
}
|
|
82
117
|
catch (e) {
|
|
@@ -85,12 +120,12 @@ export async function runTopFunctions(client, args) {
|
|
|
85
120
|
dataKey: identity.dataKey,
|
|
86
121
|
createDate: identity.createDate,
|
|
87
122
|
sdkVersion: identity.sdkVersion,
|
|
88
|
-
serviceSubtype:
|
|
123
|
+
serviceSubtype: 'overview',
|
|
89
124
|
items: [],
|
|
90
125
|
_apisUsed: apisUsed,
|
|
91
|
-
_note:
|
|
92
|
-
|
|
93
|
-
_limitations: ['
|
|
126
|
+
_note: `Overview 函数 Top 失败:${e instanceof Error ? e.message : String(e)}。` +
|
|
127
|
+
`通常需 SDK ≥ 2.5.1 且报告开启堆栈。`,
|
|
128
|
+
_limitations: ['overview_idmap_statistic_unavailable'],
|
|
94
129
|
};
|
|
95
130
|
}
|
|
96
131
|
}
|
|
@@ -100,26 +135,36 @@ export const topFunctionsTool = {
|
|
|
100
135
|
engines: ['unity', 'unreal'],
|
|
101
136
|
filterKeys: ['top_functions', 'composite', 'preset.default', 'overview', 'mono'],
|
|
102
137
|
description: [
|
|
103
|
-
'
|
|
138
|
+
'从函数统计聚合指定报告的函数 Top N。',
|
|
104
139
|
'适用引擎:Unity / UE|复合工具',
|
|
105
|
-
'Overview
|
|
106
|
-
'
|
|
107
|
-
'
|
|
108
|
-
'
|
|
109
|
-
'
|
|
140
|
+
'Overview:method/idmap/statistic v1.0.2;stackTestMode 决定可用字段(仅 Overview):',
|
|
141
|
+
' CPU_ONLY → selfTimeMean/totalTimeMean/callCountTotal;',
|
|
142
|
+
' LUA_MEM_ONLY → callCountTotal/selfMemoryMean/selfMemoryTotal;',
|
|
143
|
+
' CPU_AND_LUA_MEM → 全部;UE 固定 CPU_ONLY。',
|
|
144
|
+
'Mono(仅 Unity):独立堆栈树,不使用 stackTestMode,默认 selfMemory。',
|
|
145
|
+
'逐帧曲线:method/curve/presign v1.0.2 + stackMethodId。',
|
|
146
|
+
'namePattern:可选,按函数名子串或 /正则/ 过滤(Overview 在 idmap 结果上过滤;Mono 在合并后的函数列表上过滤)。',
|
|
110
147
|
].join('\n'),
|
|
111
148
|
inputSchema: {
|
|
112
149
|
dataKey: z.string().optional().describe('报告 dataKey,与 recordId 二选一'),
|
|
113
150
|
recordId: z.union([z.string(), z.number()]).optional().describe('报告 recordId,与 dataKey 二选一'),
|
|
114
151
|
topN: z.number().optional().describe('返回条数,默认 20,最大 200'),
|
|
152
|
+
namePattern: z
|
|
153
|
+
.string()
|
|
154
|
+
.optional()
|
|
155
|
+
.describe('函数名过滤:默认子串匹配(忽略大小写);正则写 /pattern/i,如 /Canvas\\./ 或 Update'),
|
|
115
156
|
metric: z
|
|
116
157
|
.enum(['selfTime', 'totalTime', 'callCount', 'selfMemory'])
|
|
117
158
|
.optional()
|
|
118
|
-
.describe('
|
|
159
|
+
.describe('排序指标。Overview 须与 stackTestMode 匹配(CPU_ONLY 无 selfMemory;LUA_MEM_ONLY 无 selfTime/totalTime);未传时 Overview 按模式默认 selfTime 或 selfMemory'),
|
|
160
|
+
threadName: z
|
|
161
|
+
.string()
|
|
162
|
+
.optional()
|
|
163
|
+
.describe('UE Overview 线程名,默认 GameThread;Unity 忽略'),
|
|
119
164
|
serviceSubtype: z
|
|
120
165
|
.enum(['overview', 'mono'])
|
|
121
166
|
.optional()
|
|
122
|
-
.describe('
|
|
167
|
+
.describe('overview(默认)或 mono;stackTestMode 仅 overview 有效'),
|
|
123
168
|
},
|
|
124
169
|
async handler(args, ctx) {
|
|
125
170
|
try {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** indicatorDashboards 合法面板传参名称(与 got-query OverviewIndicatorDashboardCatalogService 同源)。 */
|
|
2
|
+
export declare const INDICATOR_DASHBOARD_KEYS: readonly string[];
|
|
3
|
+
/** 常见误传:统计维度后缀 / 子指标名,不是面板标识符。 */
|
|
4
|
+
export declare const INDICATOR_DASHBOARD_NEGATIVE_EXAMPLES: readonly ["fps_mean", "fps_maximum", "frametime_min", "frametime_gt_40_pct", "temperature_mean", "drawcall_maximum"];
|
|
5
|
+
export declare function splitIndicatorDashboards(raw: unknown): string[];
|
|
6
|
+
export declare function findUnknownIndicatorDashboards(raw: unknown): string[];
|
|
7
|
+
/** 拆成已接受 / 未识别面板名(保持请求顺序,已接受去重)。 */
|
|
8
|
+
export declare function partitionIndicatorDashboards(raw: unknown): {
|
|
9
|
+
accepted: string[];
|
|
10
|
+
unknown: string[];
|
|
11
|
+
all: string[];
|
|
12
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
const keysJson = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), 'indicator-dashboard-keys.json'), 'utf8'));
|
|
5
|
+
/** indicatorDashboards 合法面板传参名称(与 got-query OverviewIndicatorDashboardCatalogService 同源)。 */
|
|
6
|
+
export const INDICATOR_DASHBOARD_KEYS = keysJson.keys;
|
|
7
|
+
/** 常见误传:统计维度后缀 / 子指标名,不是面板标识符。 */
|
|
8
|
+
export const INDICATOR_DASHBOARD_NEGATIVE_EXAMPLES = [
|
|
9
|
+
'fps_mean',
|
|
10
|
+
'fps_maximum',
|
|
11
|
+
'frametime_min',
|
|
12
|
+
'frametime_gt_40_pct',
|
|
13
|
+
'temperature_mean',
|
|
14
|
+
'drawcall_maximum',
|
|
15
|
+
];
|
|
16
|
+
export function splitIndicatorDashboards(raw) {
|
|
17
|
+
if (typeof raw !== 'string' || !raw.trim())
|
|
18
|
+
return [];
|
|
19
|
+
return raw
|
|
20
|
+
.split(',')
|
|
21
|
+
.map((s) => s.trim())
|
|
22
|
+
.filter(Boolean);
|
|
23
|
+
}
|
|
24
|
+
export function findUnknownIndicatorDashboards(raw) {
|
|
25
|
+
const parts = splitIndicatorDashboards(raw);
|
|
26
|
+
if (!parts.length)
|
|
27
|
+
return [];
|
|
28
|
+
const valid = new Set(INDICATOR_DASHBOARD_KEYS);
|
|
29
|
+
return parts.filter((k) => !valid.has(k));
|
|
30
|
+
}
|
|
31
|
+
/** 拆成已接受 / 未识别面板名(保持请求顺序,已接受去重)。 */
|
|
32
|
+
export function partitionIndicatorDashboards(raw) {
|
|
33
|
+
const all = splitIndicatorDashboards(raw);
|
|
34
|
+
const valid = new Set(INDICATOR_DASHBOARD_KEYS);
|
|
35
|
+
const accepted = [];
|
|
36
|
+
const unknown = [];
|
|
37
|
+
const seen = new Set();
|
|
38
|
+
for (const k of all) {
|
|
39
|
+
if (!valid.has(k)) {
|
|
40
|
+
unknown.push(k);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (seen.has(k))
|
|
44
|
+
continue;
|
|
45
|
+
seen.add(k);
|
|
46
|
+
accepted.push(k);
|
|
47
|
+
}
|
|
48
|
+
return { accepted, unknown, all };
|
|
49
|
+
}
|