lumencode 1.3.1 → 1.3.2
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/index.js +348 -343
- package/lib/aggregate.js +58 -3
- package/lib/config.js +21 -8
- package/lib/path-utils.js +18 -0
- package/lib/report.js +969 -53
- package/lib/scenario.js +29 -4
- package/lib/server.js +331 -316
- package/package.json +1 -1
- package/public/app.js +232 -17
- package/public/config.js +1 -0
- package/public/export.js +11 -7
- package/public/index.html +77 -16
- package/public/style.css +248 -1
- package/public/utils.js +218 -0
package/public/style.css
CHANGED
|
@@ -759,7 +759,7 @@ input, textarea { font-family: inherit; color: inherit; }
|
|
|
759
759
|
/* ── Tool Calls ── */
|
|
760
760
|
.tool-rank {
|
|
761
761
|
display: grid;
|
|
762
|
-
grid-template-columns: 24px minmax(0, 1fr) 80px
|
|
762
|
+
grid-template-columns: 24px minmax(0, 1fr) 80px 64px;
|
|
763
763
|
align-items: center;
|
|
764
764
|
gap: 8px;
|
|
765
765
|
margin-bottom: 12px;
|
|
@@ -770,6 +770,19 @@ input, textarea { font-family: inherit; color: inherit; }
|
|
|
770
770
|
white-space: nowrap;
|
|
771
771
|
}
|
|
772
772
|
.tool-rank:last-child { margin-bottom: 0; }
|
|
773
|
+
.tool-rank.has-note {
|
|
774
|
+
grid-template-columns: 24px minmax(0, 1fr) 80px 64px 90px;
|
|
775
|
+
gap: 8px 12px;
|
|
776
|
+
}
|
|
777
|
+
.tool-rank-group {
|
|
778
|
+
display: flex;
|
|
779
|
+
align-items: center;
|
|
780
|
+
justify-content: center;
|
|
781
|
+
padding: 8px 0;
|
|
782
|
+
font-size: 11px;
|
|
783
|
+
opacity: 0.5;
|
|
784
|
+
letter-spacing: 0.08em;
|
|
785
|
+
}
|
|
773
786
|
|
|
774
787
|
/* ── Timeline chart ── */
|
|
775
788
|
.timeline-chart { position: relative; height: 280px; }
|
|
@@ -894,6 +907,240 @@ input, textarea { font-family: inherit; color: inherit; }
|
|
|
894
907
|
.form-input:focus, .form-textarea:focus { border-color: var(--foreground); }
|
|
895
908
|
.form-hint { font-size: 12px; opacity: 0.55; margin-top: 4px; }
|
|
896
909
|
|
|
910
|
+
/* ── Config Sections ── */
|
|
911
|
+
.cfg-section-title {
|
|
912
|
+
font-size: 11px;
|
|
913
|
+
font-weight: 600;
|
|
914
|
+
text-transform: uppercase;
|
|
915
|
+
letter-spacing: 0.5px;
|
|
916
|
+
color: var(--muted-foreground);
|
|
917
|
+
margin-bottom: 12px;
|
|
918
|
+
padding-bottom: 6px;
|
|
919
|
+
border-bottom: 1px solid var(--border);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
/* ── Path Tags ── */
|
|
923
|
+
.path-tags {
|
|
924
|
+
display: flex;
|
|
925
|
+
flex-direction: column;
|
|
926
|
+
gap: 8px;
|
|
927
|
+
margin-bottom: 10px;
|
|
928
|
+
min-height: 0;
|
|
929
|
+
}
|
|
930
|
+
.path-tag {
|
|
931
|
+
display: flex;
|
|
932
|
+
align-items: center;
|
|
933
|
+
gap: 10px;
|
|
934
|
+
padding: 10px 14px;
|
|
935
|
+
background: var(--input-background);
|
|
936
|
+
border: 1px solid var(--border);
|
|
937
|
+
border-radius: 8px;
|
|
938
|
+
font-size: 13px;
|
|
939
|
+
color: var(--foreground);
|
|
940
|
+
transition: background 0.15s ease;
|
|
941
|
+
}
|
|
942
|
+
.path-tag:hover {
|
|
943
|
+
background: var(--muted);
|
|
944
|
+
}
|
|
945
|
+
.path-tag-icon {
|
|
946
|
+
flex-shrink: 0;
|
|
947
|
+
color: var(--accent);
|
|
948
|
+
opacity: 0.85;
|
|
949
|
+
}
|
|
950
|
+
.path-tag-icon svg {
|
|
951
|
+
display: block;
|
|
952
|
+
}
|
|
953
|
+
.path-tag-text {
|
|
954
|
+
flex: 1;
|
|
955
|
+
min-width: 0;
|
|
956
|
+
overflow: hidden;
|
|
957
|
+
text-overflow: ellipsis;
|
|
958
|
+
white-space: nowrap;
|
|
959
|
+
font-family: var(--font-mono);
|
|
960
|
+
font-size: 12px;
|
|
961
|
+
}
|
|
962
|
+
.path-tag-remove {
|
|
963
|
+
flex-shrink: 0;
|
|
964
|
+
width: 22px;
|
|
965
|
+
height: 22px;
|
|
966
|
+
display: flex;
|
|
967
|
+
align-items: center;
|
|
968
|
+
justify-content: center;
|
|
969
|
+
border-radius: 5px;
|
|
970
|
+
cursor: pointer;
|
|
971
|
+
color: var(--muted-foreground);
|
|
972
|
+
background: transparent;
|
|
973
|
+
border: none;
|
|
974
|
+
padding: 0;
|
|
975
|
+
transition: all 0.15s ease;
|
|
976
|
+
}
|
|
977
|
+
.path-tag-remove:hover {
|
|
978
|
+
color: var(--destructive);
|
|
979
|
+
background: var(--destructive-foreground);
|
|
980
|
+
}
|
|
981
|
+
.path-tag-remove svg {
|
|
982
|
+
display: block;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
/* ── Path Add Box ── */
|
|
986
|
+
.path-add-box {
|
|
987
|
+
display: flex;
|
|
988
|
+
align-items: center;
|
|
989
|
+
gap: 8px;
|
|
990
|
+
padding: 8px 12px;
|
|
991
|
+
border: 1px dashed var(--border);
|
|
992
|
+
border-radius: 8px;
|
|
993
|
+
background: transparent;
|
|
994
|
+
transition: border-color 0.15s ease, background 0.15s ease;
|
|
995
|
+
}
|
|
996
|
+
.path-add-box:focus-within {
|
|
997
|
+
border-color: var(--ring);
|
|
998
|
+
background: var(--input-background);
|
|
999
|
+
border-style: solid;
|
|
1000
|
+
}
|
|
1001
|
+
.path-add-icon {
|
|
1002
|
+
flex-shrink: 0;
|
|
1003
|
+
color: var(--muted-foreground);
|
|
1004
|
+
opacity: 0.6;
|
|
1005
|
+
display: flex;
|
|
1006
|
+
align-items: center;
|
|
1007
|
+
}
|
|
1008
|
+
.path-add-input {
|
|
1009
|
+
flex: 1;
|
|
1010
|
+
min-width: 0;
|
|
1011
|
+
border: none;
|
|
1012
|
+
outline: none;
|
|
1013
|
+
background: transparent;
|
|
1014
|
+
font-size: 13px;
|
|
1015
|
+
color: var(--foreground);
|
|
1016
|
+
padding: 0;
|
|
1017
|
+
}
|
|
1018
|
+
.path-add-input::placeholder {
|
|
1019
|
+
color: var(--muted-foreground);
|
|
1020
|
+
opacity: 0.55;
|
|
1021
|
+
}
|
|
1022
|
+
.path-add-btn {
|
|
1023
|
+
flex-shrink: 0;
|
|
1024
|
+
padding: 5px 14px;
|
|
1025
|
+
font-size: 12px;
|
|
1026
|
+
font-weight: 500;
|
|
1027
|
+
border-radius: 6px;
|
|
1028
|
+
border: 1px solid var(--border);
|
|
1029
|
+
background: var(--input-background);
|
|
1030
|
+
color: var(--foreground);
|
|
1031
|
+
cursor: pointer;
|
|
1032
|
+
transition: all 0.15s ease;
|
|
1033
|
+
}
|
|
1034
|
+
.path-add-btn:hover {
|
|
1035
|
+
background: var(--foreground);
|
|
1036
|
+
color: var(--background);
|
|
1037
|
+
border-color: var(--foreground);
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
/* ── Advanced Toggle ── */
|
|
1041
|
+
.cfg-advanced-toggle {
|
|
1042
|
+
display: flex;
|
|
1043
|
+
align-items: center;
|
|
1044
|
+
justify-content: space-between;
|
|
1045
|
+
width: 100%;
|
|
1046
|
+
padding: 10px 0;
|
|
1047
|
+
font-size: 11px;
|
|
1048
|
+
font-weight: 600;
|
|
1049
|
+
text-transform: uppercase;
|
|
1050
|
+
letter-spacing: 0.5px;
|
|
1051
|
+
color: var(--muted-foreground);
|
|
1052
|
+
background: none;
|
|
1053
|
+
border: none;
|
|
1054
|
+
border-bottom: 1px solid var(--border);
|
|
1055
|
+
cursor: pointer;
|
|
1056
|
+
transition: color 0.15s ease;
|
|
1057
|
+
}
|
|
1058
|
+
.cfg-advanced-toggle:hover {
|
|
1059
|
+
color: var(--foreground);
|
|
1060
|
+
}
|
|
1061
|
+
.cfg-advanced-toggle svg {
|
|
1062
|
+
transition: transform 0.2s ease;
|
|
1063
|
+
}
|
|
1064
|
+
.cfg-advanced-toggle.expanded svg {
|
|
1065
|
+
transform: rotate(180deg);
|
|
1066
|
+
}
|
|
1067
|
+
.cfg-advanced-body {
|
|
1068
|
+
padding-top: 16px;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
/* ── Keywords Editor ── */
|
|
1072
|
+
.kw-row {
|
|
1073
|
+
display: flex;
|
|
1074
|
+
align-items: flex-start;
|
|
1075
|
+
gap: 10px;
|
|
1076
|
+
margin-bottom: 10px;
|
|
1077
|
+
}
|
|
1078
|
+
.kw-label {
|
|
1079
|
+
width: 48px;
|
|
1080
|
+
flex-shrink: 0;
|
|
1081
|
+
font-size: 12px;
|
|
1082
|
+
font-weight: 500;
|
|
1083
|
+
padding-top: 4px;
|
|
1084
|
+
text-align: right;
|
|
1085
|
+
opacity: 0.7;
|
|
1086
|
+
}
|
|
1087
|
+
.kw-tags {
|
|
1088
|
+
flex: 1;
|
|
1089
|
+
display: flex;
|
|
1090
|
+
flex-wrap: wrap;
|
|
1091
|
+
gap: 4px;
|
|
1092
|
+
align-items: center;
|
|
1093
|
+
}
|
|
1094
|
+
.kw-tag {
|
|
1095
|
+
display: inline-flex;
|
|
1096
|
+
align-items: center;
|
|
1097
|
+
gap: 3px;
|
|
1098
|
+
padding: 2px 8px;
|
|
1099
|
+
border-radius: 4px;
|
|
1100
|
+
background: var(--secondary);
|
|
1101
|
+
font-size: 12px;
|
|
1102
|
+
line-height: 1.5;
|
|
1103
|
+
cursor: default;
|
|
1104
|
+
transition: background 0.15s;
|
|
1105
|
+
}
|
|
1106
|
+
.kw-tag:hover { background: var(--border); }
|
|
1107
|
+
.kw-tag-remove {
|
|
1108
|
+
cursor: pointer;
|
|
1109
|
+
opacity: 0.35;
|
|
1110
|
+
font-size: 14px;
|
|
1111
|
+
line-height: 1;
|
|
1112
|
+
margin-left: 1px;
|
|
1113
|
+
}
|
|
1114
|
+
.kw-tag-remove:hover { opacity: 0.8; }
|
|
1115
|
+
.kw-add-row {
|
|
1116
|
+
display: flex;
|
|
1117
|
+
gap: 4px;
|
|
1118
|
+
margin-top: 2px;
|
|
1119
|
+
}
|
|
1120
|
+
.kw-add-input {
|
|
1121
|
+
padding: 2px 8px;
|
|
1122
|
+
font-size: 12px;
|
|
1123
|
+
border: 1px solid var(--border);
|
|
1124
|
+
border-radius: 4px;
|
|
1125
|
+
background: var(--input-background);
|
|
1126
|
+
color: var(--foreground);
|
|
1127
|
+
outline: none;
|
|
1128
|
+
width: 100px;
|
|
1129
|
+
}
|
|
1130
|
+
.kw-add-input:focus { border-color: var(--foreground); }
|
|
1131
|
+
.kw-add-btn {
|
|
1132
|
+
font-size: 12px;
|
|
1133
|
+
padding: 2px 8px;
|
|
1134
|
+
border: 1px dashed var(--border);
|
|
1135
|
+
border-radius: 4px;
|
|
1136
|
+
background: none;
|
|
1137
|
+
color: var(--muted-foreground);
|
|
1138
|
+
cursor: pointer;
|
|
1139
|
+
}
|
|
1140
|
+
.kw-add-btn:hover { border-color: var(--foreground); color: var(--foreground); }
|
|
1141
|
+
.cfg-save-ok { color: #22c55e; }
|
|
1142
|
+
.cfg-save-err { color: var(--dest); }
|
|
1143
|
+
|
|
897
1144
|
/* ── Work Report Content ── */
|
|
898
1145
|
.work-report-content {
|
|
899
1146
|
background: var(--card);
|
package/public/utils.js
CHANGED
|
@@ -47,6 +47,224 @@ export function destroyAllCharts(keys) {
|
|
|
47
47
|
keys.forEach(destroyChart);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
// MCP server 前缀 → 友好名映射
|
|
51
|
+
const MCP_SERVER_NAMES = {
|
|
52
|
+
'mcp__Playwright': 'Playwright',
|
|
53
|
+
'mcp__context7': 'Context7',
|
|
54
|
+
'mcp__serena': 'Serena',
|
|
55
|
+
'mcp__open-websearch': 'WebSearch',
|
|
56
|
+
'mcp__mcp-deepwiki': 'DeepWiki',
|
|
57
|
+
'mcp__web_reader': 'WebReader',
|
|
58
|
+
'mcp__exa': 'Exa',
|
|
59
|
+
'mcp__codegraph': 'CodeGraph',
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// 工具通俗备注映射表(全部工具 Tab 用)
|
|
63
|
+
export const TOOL_DISPLAY_NAMES = {
|
|
64
|
+
'Bash': '执行命令',
|
|
65
|
+
'Read': '读取文件',
|
|
66
|
+
'Edit': '修改代码',
|
|
67
|
+
'Write': '创建文件',
|
|
68
|
+
'Grep': '搜索内容',
|
|
69
|
+
'Glob': '查找文件',
|
|
70
|
+
'shell_command': '执行命令(Codex)',
|
|
71
|
+
'TaskUpdate': '更新任务',
|
|
72
|
+
'TaskCreate': '创建任务',
|
|
73
|
+
'Agent': '子代理',
|
|
74
|
+
'TaskList': '查看任务',
|
|
75
|
+
'TaskOutput': '获取结果',
|
|
76
|
+
'TaskStop': '停止任务',
|
|
77
|
+
'AskUserQuestion': '询问用户',
|
|
78
|
+
'EnterPlanMode': '进入规划',
|
|
79
|
+
'ExitPlanMode': '退出规划',
|
|
80
|
+
'WebSearch': '网络搜索',
|
|
81
|
+
'WebFetch': '抓取网页',
|
|
82
|
+
'Skill': '技能调用',
|
|
83
|
+
'CronCreate': '创建定时',
|
|
84
|
+
'CronDelete': '删除定时',
|
|
85
|
+
'update_plan': '更新计划',
|
|
86
|
+
'write': '写入文件(OpenCode)',
|
|
87
|
+
'edit': '编辑文件(OpenCode)',
|
|
88
|
+
'bash': '执行命令(OpenCode)',
|
|
89
|
+
'glob': '查找文件(OpenCode)',
|
|
90
|
+
'question': '提问(OpenCode)',
|
|
91
|
+
'todowrite': '待办(OpenCode)',
|
|
92
|
+
// MCP 聚合条目
|
|
93
|
+
'Playwright': '浏览器自动化',
|
|
94
|
+
'Serena': '代码分析',
|
|
95
|
+
'Context7': '文档检索',
|
|
96
|
+
'CodeGraph': '代码图谱',
|
|
97
|
+
'DeepWiki': '知识库',
|
|
98
|
+
'WebReader': '网页阅读',
|
|
99
|
+
'Exa': '搜索引擎',
|
|
100
|
+
// OpenCode
|
|
101
|
+
'write': '写入文件(OpenCode)',
|
|
102
|
+
'edit': '编辑文件(OpenCode)',
|
|
103
|
+
'bash': '执行命令(OpenCode)',
|
|
104
|
+
'glob': '查找文件(OpenCode)',
|
|
105
|
+
'question': '提问(OpenCode)',
|
|
106
|
+
'todowrite': '待办(OpenCode)',
|
|
107
|
+
'search': '搜索(OpenCode)',
|
|
108
|
+
'fetch': '获取(OpenCode)',
|
|
109
|
+
'run': '运行(OpenCode)',
|
|
110
|
+
// MCP 聚合条目
|
|
111
|
+
'Playwright': '浏览器自动化',
|
|
112
|
+
'Serena': '代码分析',
|
|
113
|
+
'Context7': '文档检索',
|
|
114
|
+
'CodeGraph': '代码图谱',
|
|
115
|
+
'DeepWiki': '知识库',
|
|
116
|
+
'WebReader': '网页阅读',
|
|
117
|
+
'Exa': '搜索引擎',
|
|
118
|
+
'WebSearch': '网络搜索',
|
|
119
|
+
// 其他工具
|
|
120
|
+
'PowerShell': '执行命令(PS)',
|
|
121
|
+
'NotebookEdit': '编辑笔记',
|
|
122
|
+
'view_image': '查看图片',
|
|
123
|
+
'initial_instructions': '初始化指南',
|
|
124
|
+
'query_docs': '查询文档',
|
|
125
|
+
// Serena 方法兜底(实际在 all tab 中会被聚合为 Serena,但万一聚合逻辑失效时兜底)
|
|
126
|
+
'replace_symbol_body': '替换代码体',
|
|
127
|
+
'replace_content': '替换内容',
|
|
128
|
+
'insert_before_symbol': '前置插入',
|
|
129
|
+
'insert_after_symbol': '后置插入',
|
|
130
|
+
'find_symbol': '查找符号',
|
|
131
|
+
'find_declaration': '查找声明',
|
|
132
|
+
'find_referencing_symbols': '查找引用',
|
|
133
|
+
'find_implementations': '查找实现',
|
|
134
|
+
'get_symbols_overview': '符号概览',
|
|
135
|
+
'get_diagnostics_for_file': '诊断检查',
|
|
136
|
+
'get_current_config': '获取配置',
|
|
137
|
+
'activate_project': '激活项目',
|
|
138
|
+
'delete_memory': '删除记忆',
|
|
139
|
+
// Playwright 方法兜底
|
|
140
|
+
'browser_evaluate': '浏览器执行',
|
|
141
|
+
'browser_click': '浏览器点击',
|
|
142
|
+
'browser_navigate': '浏览器导航',
|
|
143
|
+
'browser_snapshot': '浏览器快照',
|
|
144
|
+
'browser_take_screenshot': '浏览器截图',
|
|
145
|
+
'browser_close': '浏览器关闭',
|
|
146
|
+
'browser_wait_for': '浏览器等待',
|
|
147
|
+
'browser_console_messages': '浏览器控制台',
|
|
148
|
+
// 其他兜底
|
|
149
|
+
'search': '搜索',
|
|
150
|
+
'fetch': '获取',
|
|
151
|
+
'run': '运行',
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// 工具名友好化(非 MCP 工具直接返回原名)
|
|
155
|
+
export function friendlyToolName(raw) {
|
|
156
|
+
for (const prefix of _sortedPrefixes) {
|
|
157
|
+
if (raw.startsWith(prefix + '__')) {
|
|
158
|
+
const method = raw.slice(prefix.length + 2);
|
|
159
|
+
return `${MCP_SERVER_NAMES[prefix]}.${method}`;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return raw;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 按 MCP server 分组聚合工具调用:MCP 工具合并为一条,非 MCP 保持独立
|
|
166
|
+
// toolsMap 格式: { name: number } 或 { name: { calls, uses } }
|
|
167
|
+
// mode: 'calls' | 'uses'
|
|
168
|
+
const _sortedPrefixes = Object.keys(MCP_SERVER_NAMES).sort((a, b) => b.length - a.length);
|
|
169
|
+
export function aggregateToolsByServer(toolsMap, mode = 'calls') {
|
|
170
|
+
const result = {};
|
|
171
|
+
for (const [name, val] of Object.entries(toolsMap)) {
|
|
172
|
+
const count = typeof val === 'number' ? val : (val[mode] || 0);
|
|
173
|
+
let matched = false;
|
|
174
|
+
for (const prefix of _sortedPrefixes) {
|
|
175
|
+
if (name.startsWith(prefix + '__')) {
|
|
176
|
+
const displayName = MCP_SERVER_NAMES[prefix];
|
|
177
|
+
result[displayName] = (result[displayName] || 0) + count;
|
|
178
|
+
matched = true;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (!matched) {
|
|
183
|
+
result[name] = (result[name] || 0) + count;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 去掉 mcp__ServerName__ 前缀,只保留方法名
|
|
190
|
+
export function stripMcpPrefix(raw) {
|
|
191
|
+
for (const prefix of _sortedPrefixes) {
|
|
192
|
+
if (raw.startsWith(prefix + '__')) {
|
|
193
|
+
return raw.slice(prefix.length + 2);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return raw;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 同时聚合 calls 和 uses,返回 { name: { calls, uses } }
|
|
200
|
+
export function aggregateToolsWithDualCounts(toolsMap) {
|
|
201
|
+
const result = {};
|
|
202
|
+
for (const [name, val] of Object.entries(toolsMap)) {
|
|
203
|
+
const calls = typeof val === 'number' ? val : (val.calls || 0);
|
|
204
|
+
const uses = typeof val === 'number' ? val : (val.uses || 0);
|
|
205
|
+
let matched = false;
|
|
206
|
+
for (const prefix of _sortedPrefixes) {
|
|
207
|
+
if (name.startsWith(prefix + '__')) {
|
|
208
|
+
const displayName = MCP_SERVER_NAMES[prefix] || prefix;
|
|
209
|
+
if (!result[displayName]) result[displayName] = { calls: 0, uses: 0 };
|
|
210
|
+
result[displayName].calls += calls;
|
|
211
|
+
result[displayName].uses += uses;
|
|
212
|
+
matched = true;
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (!matched) {
|
|
217
|
+
if (!result[name]) result[name] = { calls: 0, uses: 0 };
|
|
218
|
+
result[name].calls += calls;
|
|
219
|
+
result[name].uses += uses;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// MCP 工具按 server 分组,返回带 group header 的条目数组
|
|
226
|
+
// mcpToolsMap 格式: { name: { calls, uses } }
|
|
227
|
+
// 返回: [{ name, calls, uses, value, pct, usePct, isGroup?, groupTotal? }, ...]
|
|
228
|
+
export function groupMcpByServer(mcpToolsMap) {
|
|
229
|
+
const groups = {};
|
|
230
|
+
for (const [fullName, val] of Object.entries(mcpToolsMap)) {
|
|
231
|
+
const calls = typeof val === 'number' ? val : (val.calls || 0);
|
|
232
|
+
const uses = typeof val === 'number' ? val : (val.uses || 0);
|
|
233
|
+
let server = 'Other';
|
|
234
|
+
let matched = false;
|
|
235
|
+
for (const prefix of _sortedPrefixes) {
|
|
236
|
+
if (fullName.startsWith(prefix + '__')) {
|
|
237
|
+
server = MCP_SERVER_NAMES[prefix] || prefix;
|
|
238
|
+
matched = true;
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (!matched) continue;
|
|
243
|
+
if (!groups[server]) groups[server] = { items: [], totalUses: 0 };
|
|
244
|
+
groups[server].items.push({ name: stripMcpPrefix(fullName), calls, uses });
|
|
245
|
+
groups[server].totalUses += uses;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const sortedGroups = Object.entries(groups).sort((a, b) => b[1].totalUses - a[1].totalUses);
|
|
249
|
+
const allUses = sortedGroups.flatMap(([, g]) => g.items.map(i => i.uses));
|
|
250
|
+
const maxUses = Math.max(...allUses, 1);
|
|
251
|
+
|
|
252
|
+
const result = [];
|
|
253
|
+
for (const [server, data] of sortedGroups) {
|
|
254
|
+
result.push({ name: `--- ${server} ---`, isGroup: true, groupTotal: data.totalUses });
|
|
255
|
+
for (const item of data.items.sort((a, b) => b.uses - a.uses)) {
|
|
256
|
+
result.push({
|
|
257
|
+
name: item.name,
|
|
258
|
+
calls: item.calls,
|
|
259
|
+
uses: item.uses,
|
|
260
|
+
value: item.calls,
|
|
261
|
+
pct: Math.round((item.uses / maxUses) * 100),
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
267
|
+
|
|
50
268
|
// 趋势箭头
|
|
51
269
|
export function renderTrendArrow(elId, current, previous) {
|
|
52
270
|
const el = document.getElementById(elId);
|