@mengzf/skillflow-mcp 1.0.0 → 1.0.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/README.md +48 -7
- package/dist/adapters/claude-code.adapter.d.ts +10 -0
- package/dist/adapters/claude-code.adapter.d.ts.map +1 -0
- package/dist/adapters/claude-code.adapter.js +166 -0
- package/dist/adapters/claude-code.adapter.js.map +1 -0
- package/dist/adapters/codex.adapter.d.ts +10 -0
- package/dist/adapters/codex.adapter.d.ts.map +1 -0
- package/dist/adapters/codex.adapter.js +109 -0
- package/dist/adapters/codex.adapter.js.map +1 -0
- package/dist/adapters/dsh.adapter.d.ts +8 -0
- package/dist/adapters/dsh.adapter.d.ts.map +1 -0
- package/dist/adapters/dsh.adapter.js +75 -0
- package/dist/adapters/dsh.adapter.js.map +1 -0
- package/dist/adapters/qoder.adapter.d.ts +10 -0
- package/dist/adapters/qoder.adapter.d.ts.map +1 -0
- package/dist/adapters/qoder.adapter.js +119 -0
- package/dist/adapters/qoder.adapter.js.map +1 -0
- package/dist/adapters/trae.adapter.d.ts +8 -0
- package/dist/adapters/trae.adapter.d.ts.map +1 -0
- package/dist/adapters/trae.adapter.js +104 -0
- package/dist/adapters/trae.adapter.js.map +1 -0
- package/dist/adapters/types.d.ts +25 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +2 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/adapters/universal.sync.d.ts +22 -0
- package/dist/adapters/universal.sync.d.ts.map +1 -0
- package/dist/adapters/universal.sync.js +140 -0
- package/dist/adapters/universal.sync.js.map +1 -0
- package/dist/adapters/workbuddy.sniffer.d.ts.map +1 -1
- package/dist/adapters/workbuddy.sniffer.js +8 -3
- package/dist/adapters/workbuddy.sniffer.js.map +1 -1
- package/dist/db/invocation-filter.d.ts +29 -0
- package/dist/db/invocation-filter.d.ts.map +1 -0
- package/dist/db/invocation-filter.js +78 -0
- package/dist/db/invocation-filter.js.map +1 -0
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +28 -2
- package/dist/db/schema.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +16 -12
- package/dist/server.js.map +1 -1
- package/dist/services/dashboard.service.d.ts +2 -0
- package/dist/services/dashboard.service.d.ts.map +1 -1
- package/dist/services/dashboard.service.js +145 -40
- package/dist/services/dashboard.service.js.map +1 -1
- package/dist/services/filter.service.d.ts +26 -0
- package/dist/services/filter.service.d.ts.map +1 -0
- package/dist/services/filter.service.js +103 -0
- package/dist/services/filter.service.js.map +1 -0
- package/dist/services/telemetry.service.d.ts +9 -1
- package/dist/services/telemetry.service.d.ts.map +1 -1
- package/dist/services/telemetry.service.js +105 -36
- package/dist/services/telemetry.service.js.map +1 -1
- package/dist/types/models.d.ts +13 -1
- package/dist/types/models.d.ts.map +1 -1
- package/dist/types/tools.d.ts +44 -5
- package/dist/types/tools.d.ts.map +1 -1
- package/dist/types/tools.js +47 -1
- package/dist/types/tools.js.map +1 -1
- package/dist/utils/governance.d.ts +25 -0
- package/dist/utils/governance.d.ts.map +1 -0
- package/dist/utils/governance.js +70 -0
- package/dist/utils/governance.js.map +1 -0
- package/dist/utils/pricing.d.ts.map +1 -1
- package/dist/utils/pricing.js +21 -1
- package/dist/utils/pricing.js.map +1 -1
- package/dist/web/render.d.ts.map +1 -1
- package/dist/web/render.js +1751 -241
- package/dist/web/render.js.map +1 -1
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +66 -31
- package/dist/web/server.js.map +1 -1
- package/package.json +1 -1
package/dist/web/render.js
CHANGED
|
@@ -4,336 +4,1331 @@ export function renderDashboardHtml() {
|
|
|
4
4
|
<head>
|
|
5
5
|
<meta charset="UTF-8">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<meta name="color-scheme" content="light">
|
|
7
8
|
<title>SkillFlow 效能监控看板 - 跨 Agent 技能遥测</title>
|
|
8
9
|
<style>
|
|
9
10
|
:root {
|
|
10
|
-
|
|
11
|
-
--
|
|
12
|
-
--surface
|
|
13
|
-
--
|
|
14
|
-
--
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
--
|
|
18
|
-
--
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
--
|
|
23
|
-
--
|
|
24
|
-
--
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
--apple-
|
|
28
|
-
|
|
29
|
-
--
|
|
30
|
-
--
|
|
31
|
-
--
|
|
11
|
+
/* Surfaces — Apple light canvas */
|
|
12
|
+
--bg: #f5f5f7;
|
|
13
|
+
--surface: #ffffff;
|
|
14
|
+
--surface-sunken: #fafafc;
|
|
15
|
+
--surface-hover: #f5f5f7;
|
|
16
|
+
|
|
17
|
+
/* Hairlines */
|
|
18
|
+
--border: rgba(0, 0, 0, 0.07);
|
|
19
|
+
--border-highlight: rgba(0, 0, 0, 0.14);
|
|
20
|
+
--hairline: rgba(60, 60, 67, 0.12);
|
|
21
|
+
|
|
22
|
+
/* Ink */
|
|
23
|
+
--text-primary: #1d1d1f;
|
|
24
|
+
--text-secondary: #5f5f66;
|
|
25
|
+
--text-tertiary: #6e6e73;
|
|
26
|
+
|
|
27
|
+
/* Accents (text-safe, WCAG AA on white) */
|
|
28
|
+
--apple-blue: #0066cc;
|
|
29
|
+
--blue-fill: #0071e3;
|
|
30
|
+
--apple-blue-glow: rgba(0, 113, 227, 0.16);
|
|
31
|
+
--apple-green: #1a7f37;
|
|
32
|
+
--green-fill: #34c759;
|
|
33
|
+
--apple-green-glow: rgba(52, 199, 89, 0.16);
|
|
34
|
+
--apple-purple: #7d3ca8;
|
|
35
|
+
--apple-orange: #a35200;
|
|
36
|
+
--apple-red: #d70015;
|
|
37
|
+
|
|
38
|
+
--radius-sm: 8px;
|
|
39
|
+
--radius-md: 12px;
|
|
40
|
+
--radius-lg: 20px;
|
|
32
41
|
--radius-full: 9999px;
|
|
33
42
|
|
|
34
|
-
--
|
|
35
|
-
--shadow-
|
|
43
|
+
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
44
|
+
--shadow-md: 0 10px 26px -14px rgba(0, 0, 0, 0.22), 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
45
|
+
--shadow-card: var(--shadow-sm);
|
|
46
|
+
|
|
47
|
+
--blur: saturate(180%) blur(20px);
|
|
48
|
+
--ease: cubic-bezier(0.25, 0.1, 0.25, 1);
|
|
36
49
|
}
|
|
37
50
|
|
|
38
|
-
|
|
51
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
52
|
+
|
|
53
|
+
* { margin: 0; padding: 0; }
|
|
54
|
+
|
|
55
|
+
html { -webkit-text-size-adjust: 100%; }
|
|
39
56
|
|
|
40
57
|
body {
|
|
41
58
|
background-color: var(--bg);
|
|
42
|
-
background-image:
|
|
43
|
-
radial-gradient(1200px circle at 50% -180px, rgba(41, 151, 255, 0.12), transparent 70%),
|
|
44
|
-
radial-gradient(800px circle at 90% 400px, rgba(191, 90, 242, 0.06), transparent 60%);
|
|
45
|
-
background-attachment: fixed;
|
|
46
59
|
color: var(--text-primary);
|
|
47
|
-
font-family: -apple-system, BlinkMacSystemFont, "SF Pro
|
|
60
|
+
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display",
|
|
61
|
+
"Segoe UI Variable Text", "Segoe UI", "PingFang SC", "Hiragino Sans GB",
|
|
62
|
+
"Microsoft YaHei", system-ui, sans-serif;
|
|
63
|
+
font-size: 15px;
|
|
64
|
+
line-height: 1.5;
|
|
65
|
+
letter-spacing: -0.006em;
|
|
48
66
|
min-height: 100vh;
|
|
49
|
-
|
|
50
|
-
|
|
67
|
+
-webkit-font-smoothing: antialiased;
|
|
68
|
+
-moz-osx-font-smoothing: grayscale;
|
|
51
69
|
}
|
|
52
70
|
|
|
53
|
-
|
|
71
|
+
::selection { background: rgba(0, 113, 227, 0.18); }
|
|
54
72
|
|
|
55
|
-
|
|
73
|
+
/* ---------- Navigation bar ---------- */
|
|
74
|
+
.nav {
|
|
75
|
+
position: sticky;
|
|
76
|
+
top: 0;
|
|
77
|
+
z-index: 50;
|
|
78
|
+
background: rgba(255, 255, 255, 0.72);
|
|
79
|
+
backdrop-filter: var(--blur);
|
|
80
|
+
-webkit-backdrop-filter: var(--blur);
|
|
81
|
+
border-bottom: 1px solid var(--border);
|
|
82
|
+
}
|
|
83
|
+
.nav-inner {
|
|
84
|
+
max-width: 1560px;
|
|
85
|
+
margin: 0 auto;
|
|
86
|
+
padding: 0 32px;
|
|
87
|
+
min-height: 60px;
|
|
56
88
|
display: flex;
|
|
89
|
+
align-items: center;
|
|
57
90
|
justify-content: space-between;
|
|
91
|
+
gap: 20px;
|
|
92
|
+
}
|
|
93
|
+
.nav-actions {
|
|
94
|
+
display: flex;
|
|
58
95
|
align-items: center;
|
|
59
|
-
|
|
60
|
-
background: rgba(22, 22, 24, 0.65);
|
|
61
|
-
backdrop-filter: var(--blur);
|
|
62
|
-
-webkit-backdrop-filter: var(--blur);
|
|
63
|
-
border: 1px solid var(--border-highlight);
|
|
64
|
-
border-radius: var(--radius-lg);
|
|
65
|
-
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
|
|
66
|
-
margin-bottom: 32px;
|
|
67
|
-
flex-wrap: wrap;
|
|
68
|
-
gap: 16px;
|
|
96
|
+
gap: 10px;
|
|
69
97
|
}
|
|
70
98
|
|
|
71
|
-
.brand {
|
|
99
|
+
.brand {
|
|
100
|
+
display: inline-flex;
|
|
101
|
+
align-items: center;
|
|
102
|
+
gap: 10px;
|
|
103
|
+
min-width: 0;
|
|
104
|
+
}
|
|
72
105
|
.brand-icon {
|
|
73
|
-
width:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
border:
|
|
77
|
-
display:
|
|
78
|
-
|
|
106
|
+
width: 28px;
|
|
107
|
+
height: 28px;
|
|
108
|
+
flex: none;
|
|
109
|
+
border-radius: 8px;
|
|
110
|
+
display: grid;
|
|
111
|
+
place-items: center;
|
|
112
|
+
background: linear-gradient(180deg, #0a84ff, #0071e3);
|
|
113
|
+
box-shadow: 0 1px 2px rgba(0, 113, 227, 0.32), inset 0 1px 0 rgba(255, 255, 255, 0.35);
|
|
114
|
+
}
|
|
115
|
+
.brand-icon svg { width: 15px; height: 15px; fill: #ffffff; }
|
|
116
|
+
.brand-title {
|
|
117
|
+
font-size: 16px;
|
|
118
|
+
font-weight: 600;
|
|
119
|
+
letter-spacing: -0.015em;
|
|
120
|
+
color: var(--text-primary);
|
|
121
|
+
white-space: nowrap;
|
|
79
122
|
}
|
|
80
|
-
.brand-icon svg { width: 20px; height: 20px; fill: var(--apple-blue); }
|
|
81
|
-
.brand-title { font-size: 19px; font-weight: 600; letter-spacing: -0.025em; color: #fff; }
|
|
82
123
|
|
|
83
124
|
.brand-badge {
|
|
84
|
-
display: inline-flex;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
padding
|
|
125
|
+
display: inline-flex;
|
|
126
|
+
align-items: center;
|
|
127
|
+
gap: 6px;
|
|
128
|
+
margin-left: 4px;
|
|
129
|
+
padding-left: 12px;
|
|
130
|
+
border-left: 1px solid var(--hairline);
|
|
131
|
+
font-size: 12px;
|
|
132
|
+
font-weight: 500;
|
|
133
|
+
color: var(--apple-green);
|
|
134
|
+
white-space: nowrap;
|
|
89
135
|
}
|
|
90
136
|
.pulse-dot {
|
|
91
|
-
width: 6px;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
137
|
+
width: 6px;
|
|
138
|
+
height: 6px;
|
|
139
|
+
border-radius: 50%;
|
|
140
|
+
background: var(--green-fill);
|
|
141
|
+
box-shadow: 0 0 0 3px var(--apple-green-glow);
|
|
142
|
+
animation: pulse 2.4s var(--ease) infinite;
|
|
95
143
|
}
|
|
96
144
|
@keyframes pulse {
|
|
97
145
|
0%, 100% { opacity: 1; transform: scale(1); }
|
|
98
|
-
50% { opacity: 0.
|
|
146
|
+
50% { opacity: 0.55; transform: scale(0.86); }
|
|
99
147
|
}
|
|
100
148
|
|
|
149
|
+
/* ---------- Segmented control ---------- */
|
|
101
150
|
.segmented-control {
|
|
102
151
|
display: inline-flex;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
152
|
+
padding: 3px;
|
|
153
|
+
gap: 2px;
|
|
154
|
+
background: rgba(120, 120, 128, 0.1);
|
|
155
|
+
border-radius: var(--radius-full);
|
|
106
156
|
}
|
|
107
157
|
.segment-btn {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
158
|
+
appearance: none;
|
|
159
|
+
border: none;
|
|
160
|
+
background: transparent;
|
|
161
|
+
color: var(--text-secondary);
|
|
162
|
+
font: inherit;
|
|
163
|
+
font-size: 13px;
|
|
164
|
+
font-weight: 500;
|
|
165
|
+
letter-spacing: -0.01em;
|
|
166
|
+
padding: 6px 14px;
|
|
167
|
+
border-radius: var(--radius-full);
|
|
168
|
+
cursor: pointer;
|
|
169
|
+
white-space: nowrap;
|
|
170
|
+
transition: color 0.2s var(--ease), background-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
|
|
112
171
|
}
|
|
113
172
|
.segment-btn:hover { color: var(--text-primary); }
|
|
114
173
|
.segment-btn.active {
|
|
115
|
-
background:
|
|
116
|
-
color:
|
|
117
|
-
box-shadow: 0
|
|
174
|
+
background: var(--surface);
|
|
175
|
+
color: var(--text-primary);
|
|
176
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 0 0.5px rgba(0, 0, 0, 0.04);
|
|
118
177
|
}
|
|
119
178
|
|
|
120
179
|
.btn-refresh {
|
|
121
|
-
display: inline-flex;
|
|
122
|
-
|
|
180
|
+
display: inline-flex;
|
|
181
|
+
align-items: center;
|
|
182
|
+
gap: 6px;
|
|
183
|
+
appearance: none;
|
|
184
|
+
background: var(--surface);
|
|
123
185
|
border: 1px solid var(--border-highlight);
|
|
124
|
-
color: var(--text-primary);
|
|
125
|
-
|
|
126
|
-
|
|
186
|
+
color: var(--text-primary);
|
|
187
|
+
font: inherit;
|
|
188
|
+
font-size: 13px;
|
|
189
|
+
font-weight: 500;
|
|
190
|
+
letter-spacing: -0.01em;
|
|
191
|
+
padding: 7px 14px;
|
|
192
|
+
border-radius: var(--radius-full);
|
|
193
|
+
cursor: pointer;
|
|
194
|
+
white-space: nowrap;
|
|
195
|
+
box-shadow: var(--shadow-sm);
|
|
196
|
+
transition: background-color 0.2s var(--ease), box-shadow 0.2s var(--ease), transform 0.2s var(--ease);
|
|
197
|
+
}
|
|
198
|
+
.btn-refresh:hover { background: var(--surface-sunken); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); }
|
|
199
|
+
.btn-refresh:active { transform: scale(0.975); }
|
|
200
|
+
.btn-refresh svg { width: 13px; height: 13px; fill: currentColor; opacity: 0.85; }
|
|
201
|
+
/* Outline icons must not inherit the solid fill used by the refresh glyph */
|
|
202
|
+
.btn-help svg {
|
|
203
|
+
fill: none;
|
|
204
|
+
stroke: currentColor;
|
|
205
|
+
stroke-width: 1.7;
|
|
206
|
+
stroke-linecap: round;
|
|
207
|
+
stroke-linejoin: round;
|
|
208
|
+
opacity: 0.9;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
:where(.segment-btn, .btn-refresh):focus-visible {
|
|
212
|
+
outline: 2px solid var(--blue-fill);
|
|
213
|
+
outline-offset: 2px;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/* ---------- Page shell ---------- */
|
|
217
|
+
.container {
|
|
218
|
+
max-width: 1560px;
|
|
219
|
+
margin: 0 auto;
|
|
220
|
+
padding: 26px 32px 72px;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/* Compact header: title and subtitle share one baseline row */
|
|
224
|
+
.page-head {
|
|
225
|
+
display: flex;
|
|
226
|
+
align-items: baseline;
|
|
227
|
+
flex-wrap: wrap;
|
|
228
|
+
gap: 2px 16px;
|
|
229
|
+
margin-bottom: 18px;
|
|
230
|
+
}
|
|
231
|
+
.page-head h1 {
|
|
232
|
+
font-size: 26px;
|
|
233
|
+
font-weight: 600;
|
|
234
|
+
line-height: 1.25;
|
|
235
|
+
letter-spacing: -0.022em;
|
|
236
|
+
color: var(--text-primary);
|
|
237
|
+
white-space: nowrap;
|
|
238
|
+
}
|
|
239
|
+
.page-desc {
|
|
240
|
+
font-size: 13.5px;
|
|
241
|
+
line-height: 1.4;
|
|
242
|
+
letter-spacing: -0.004em;
|
|
243
|
+
color: var(--text-tertiary);
|
|
244
|
+
padding-left: 16px;
|
|
245
|
+
border-left: 1px solid var(--hairline);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/* ---------- Query filters ---------- */
|
|
249
|
+
.filters {
|
|
250
|
+
display: flex;
|
|
251
|
+
flex-wrap: wrap;
|
|
252
|
+
align-items: flex-end;
|
|
253
|
+
gap: 14px 16px;
|
|
254
|
+
background: var(--surface);
|
|
255
|
+
border: 1px solid var(--border);
|
|
256
|
+
border-radius: var(--radius-lg);
|
|
257
|
+
box-shadow: var(--shadow-sm);
|
|
258
|
+
padding: 16px 20px 18px;
|
|
259
|
+
margin-bottom: 20px;
|
|
260
|
+
}
|
|
261
|
+
.filter-field {
|
|
262
|
+
display: flex;
|
|
263
|
+
flex-direction: column;
|
|
264
|
+
gap: 7px;
|
|
265
|
+
min-width: 0;
|
|
266
|
+
}
|
|
267
|
+
.filter-label {
|
|
268
|
+
font-size: 11.5px;
|
|
269
|
+
font-weight: 500;
|
|
270
|
+
letter-spacing: 0.02em;
|
|
271
|
+
color: var(--text-tertiary);
|
|
272
|
+
padding-left: 2px;
|
|
127
273
|
}
|
|
128
|
-
.
|
|
129
|
-
.btn-refresh svg { width: 13px; height: 13px; fill: currentColor; }
|
|
274
|
+
.filter-field--range .segmented-control { align-items: center; height: 36px; }
|
|
130
275
|
|
|
131
|
-
|
|
276
|
+
.select {
|
|
277
|
+
appearance: none;
|
|
278
|
+
-webkit-appearance: none;
|
|
279
|
+
height: 36px;
|
|
280
|
+
min-width: 148px;
|
|
281
|
+
max-width: 210px;
|
|
282
|
+
padding: 0 32px 0 12px;
|
|
283
|
+
font: inherit;
|
|
284
|
+
font-size: 13px;
|
|
285
|
+
font-weight: 500;
|
|
286
|
+
letter-spacing: -0.008em;
|
|
287
|
+
color: var(--text-primary);
|
|
288
|
+
background-color: var(--surface);
|
|
289
|
+
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M3 4.75 6 7.75 9 4.75' fill='none' stroke='%236e6e73' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
|
290
|
+
background-repeat: no-repeat;
|
|
291
|
+
background-position: right 11px center;
|
|
292
|
+
border: 1px solid var(--border-highlight);
|
|
293
|
+
border-radius: 10px;
|
|
294
|
+
cursor: pointer;
|
|
295
|
+
text-overflow: ellipsis;
|
|
296
|
+
transition: border-color 0.2s var(--ease), background-color 0.2s var(--ease), color 0.2s var(--ease);
|
|
297
|
+
}
|
|
298
|
+
.select:hover { background-color: var(--surface-sunken); }
|
|
299
|
+
.select:focus-visible { outline: 2px solid var(--blue-fill); outline-offset: 1px; }
|
|
300
|
+
.select.is-active {
|
|
301
|
+
border-color: rgba(0, 113, 227, 0.45);
|
|
302
|
+
background-color: rgba(0, 113, 227, 0.045);
|
|
303
|
+
color: var(--apple-blue);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.filter-actions {
|
|
307
|
+
margin-left: auto;
|
|
308
|
+
display: flex;
|
|
309
|
+
align-items: center;
|
|
310
|
+
gap: 8px;
|
|
311
|
+
}
|
|
312
|
+
.btn-reset {
|
|
313
|
+
appearance: none;
|
|
314
|
+
height: 36px;
|
|
315
|
+
padding: 0 16px;
|
|
316
|
+
font: inherit;
|
|
317
|
+
font-size: 13px;
|
|
318
|
+
font-weight: 500;
|
|
319
|
+
letter-spacing: -0.008em;
|
|
320
|
+
color: var(--text-primary);
|
|
321
|
+
background: var(--surface);
|
|
322
|
+
border: 1px solid var(--border-highlight);
|
|
323
|
+
border-radius: var(--radius-full);
|
|
324
|
+
cursor: pointer;
|
|
325
|
+
transition: background-color 0.2s var(--ease), opacity 0.2s var(--ease);
|
|
326
|
+
}
|
|
327
|
+
.btn-reset:hover:not(:disabled) { background: var(--surface-sunken); }
|
|
328
|
+
.btn-reset:focus-visible { outline: 2px solid var(--blue-fill); outline-offset: 2px; }
|
|
329
|
+
.btn-reset:disabled { opacity: 0.38; cursor: default; }
|
|
330
|
+
|
|
331
|
+
/* ---------- KPI cards ---------- */
|
|
132
332
|
.kpi-row {
|
|
133
333
|
display: grid;
|
|
134
334
|
grid-template-columns: repeat(4, 1fr);
|
|
135
|
-
gap:
|
|
136
|
-
margin-bottom:
|
|
335
|
+
gap: 16px;
|
|
336
|
+
margin-bottom: 16px;
|
|
137
337
|
}
|
|
138
|
-
@media (max-width: 1024px) { .kpi-row { grid-template-columns: repeat(2, 1fr); } }
|
|
139
|
-
@media (max-width: 600px) { .kpi-row { grid-template-columns: 1fr; } }
|
|
140
338
|
|
|
141
339
|
.card {
|
|
142
340
|
background: var(--surface);
|
|
143
|
-
backdrop-filter: var(--blur);
|
|
144
|
-
-webkit-backdrop-filter: var(--blur);
|
|
145
341
|
border: 1px solid var(--border);
|
|
146
342
|
border-radius: var(--radius-lg);
|
|
147
|
-
padding: 24px;
|
|
148
|
-
box-shadow: var(--shadow-
|
|
149
|
-
|
|
150
|
-
transition: transform 0.25s ease, border-color 0.25s ease;
|
|
343
|
+
padding: 22px 24px 24px;
|
|
344
|
+
box-shadow: var(--shadow-sm);
|
|
345
|
+
transition: box-shadow 0.3s var(--ease), border-color 0.3s var(--ease);
|
|
151
346
|
}
|
|
152
|
-
.card:hover {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
top: 0; left: 0; right: 0; height: 1px;
|
|
156
|
-
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
|
347
|
+
.card:hover {
|
|
348
|
+
box-shadow: var(--shadow-md);
|
|
349
|
+
border-color: var(--border-highlight);
|
|
157
350
|
}
|
|
158
351
|
|
|
159
352
|
.kpi-label {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
353
|
+
display: flex;
|
|
354
|
+
align-items: center;
|
|
355
|
+
justify-content: space-between;
|
|
356
|
+
gap: 12px;
|
|
357
|
+
font-size: 13px;
|
|
358
|
+
font-weight: 500;
|
|
359
|
+
letter-spacing: -0.005em;
|
|
360
|
+
color: var(--text-secondary);
|
|
361
|
+
margin-bottom: 18px;
|
|
362
|
+
}
|
|
363
|
+
.kpi-icon {
|
|
364
|
+
width: 30px;
|
|
365
|
+
height: 30px;
|
|
366
|
+
flex: none;
|
|
367
|
+
border-radius: 9px;
|
|
368
|
+
display: grid;
|
|
369
|
+
place-items: center;
|
|
370
|
+
}
|
|
371
|
+
.kpi-icon svg {
|
|
372
|
+
width: 16px;
|
|
373
|
+
height: 16px;
|
|
374
|
+
fill: none;
|
|
375
|
+
stroke: currentColor;
|
|
376
|
+
stroke-width: 1.7;
|
|
377
|
+
stroke-linecap: round;
|
|
378
|
+
stroke-linejoin: round;
|
|
163
379
|
}
|
|
380
|
+
.kpi-icon svg .fill { fill: currentColor; stroke: none; }
|
|
381
|
+
.tint-blue { background: rgba(0, 113, 227, 0.1); color: var(--apple-blue); }
|
|
382
|
+
.tint-purple { background: rgba(125, 60, 168, 0.1); color: var(--apple-purple); }
|
|
383
|
+
.tint-green { background: rgba(26, 127, 55, 0.1); color: var(--apple-green); }
|
|
384
|
+
.tint-amber { background: rgba(163, 82, 0, 0.1); color: var(--apple-orange); }
|
|
385
|
+
|
|
164
386
|
.kpi-number {
|
|
165
|
-
font-size: 38px;
|
|
166
|
-
|
|
167
|
-
|
|
387
|
+
font-size: 38px;
|
|
388
|
+
font-weight: 600;
|
|
389
|
+
line-height: 1.06;
|
|
390
|
+
letter-spacing: -0.032em;
|
|
391
|
+
color: var(--text-primary);
|
|
392
|
+
font-variant-numeric: tabular-nums;
|
|
393
|
+
margin-bottom: 8px;
|
|
168
394
|
}
|
|
169
395
|
.kpi-meta {
|
|
170
|
-
|
|
171
|
-
|
|
396
|
+
display: flex;
|
|
397
|
+
align-items: center;
|
|
398
|
+
flex-wrap: wrap;
|
|
399
|
+
gap: 4px;
|
|
400
|
+
font-size: 12.5px;
|
|
401
|
+
letter-spacing: -0.004em;
|
|
402
|
+
color: var(--text-tertiary);
|
|
172
403
|
}
|
|
173
|
-
.accent-green
|
|
174
|
-
.accent-blue
|
|
175
|
-
.accent-purple {
|
|
404
|
+
.accent-green,
|
|
405
|
+
.accent-blue,
|
|
406
|
+
.accent-purple { font-weight: 600; }
|
|
407
|
+
|
|
408
|
+
/* The token figure is injected with an inline colour by the renderer;
|
|
409
|
+
keep it in ink so it stays legible on a light surface. */
|
|
410
|
+
#kpiTokens { color: var(--text-primary) !important; }
|
|
176
411
|
|
|
177
|
-
/*
|
|
412
|
+
/* ---------- Panels ---------- */
|
|
178
413
|
.dashboard-grid {
|
|
179
414
|
display: grid;
|
|
180
415
|
grid-template-columns: 1fr 1fr;
|
|
181
|
-
gap:
|
|
182
|
-
margin-bottom:
|
|
416
|
+
gap: 16px;
|
|
417
|
+
margin-bottom: 16px;
|
|
183
418
|
}
|
|
184
|
-
@media (max-width: 860px) { .dashboard-grid { grid-template-columns: 1fr; } }
|
|
185
419
|
|
|
186
420
|
.panel-header {
|
|
187
|
-
display: flex;
|
|
188
|
-
|
|
189
|
-
|
|
421
|
+
display: flex;
|
|
422
|
+
align-items: baseline;
|
|
423
|
+
justify-content: space-between;
|
|
424
|
+
gap: 16px;
|
|
425
|
+
margin-bottom: 22px;
|
|
190
426
|
}
|
|
191
427
|
.panel-title {
|
|
192
|
-
|
|
193
|
-
|
|
428
|
+
display: inline-flex;
|
|
429
|
+
align-items: center;
|
|
430
|
+
gap: 8px;
|
|
431
|
+
font-size: 15px;
|
|
432
|
+
font-weight: 600;
|
|
433
|
+
letter-spacing: -0.014em;
|
|
434
|
+
color: var(--text-primary);
|
|
435
|
+
}
|
|
436
|
+
.panel-title svg {
|
|
437
|
+
width: 16px;
|
|
438
|
+
height: 16px;
|
|
439
|
+
flex: none;
|
|
440
|
+
fill: none;
|
|
441
|
+
stroke: var(--text-tertiary);
|
|
442
|
+
stroke-width: 1.7;
|
|
443
|
+
stroke-linecap: round;
|
|
444
|
+
stroke-linejoin: round;
|
|
445
|
+
}
|
|
446
|
+
.panel-subtitle {
|
|
447
|
+
font-size: 12px;
|
|
448
|
+
color: var(--text-tertiary);
|
|
449
|
+
white-space: nowrap;
|
|
194
450
|
}
|
|
195
|
-
.panel-subtitle { font-size: 12px; color: var(--text-secondary); }
|
|
196
451
|
|
|
197
|
-
|
|
452
|
+
/* ---------- Bars ---------- */
|
|
453
|
+
.bar-row { margin-bottom: 18px; }
|
|
454
|
+
.bar-row:last-child { margin-bottom: 0; }
|
|
198
455
|
.bar-label-group {
|
|
199
|
-
display: flex;
|
|
200
|
-
|
|
456
|
+
display: flex;
|
|
457
|
+
align-items: baseline;
|
|
458
|
+
justify-content: space-between;
|
|
459
|
+
gap: 12px;
|
|
460
|
+
font-size: 13px;
|
|
461
|
+
margin-bottom: 9px;
|
|
201
462
|
}
|
|
202
463
|
.skill-rank-badge {
|
|
203
|
-
display: inline-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
464
|
+
display: inline-grid;
|
|
465
|
+
place-items: center;
|
|
466
|
+
width: 20px;
|
|
467
|
+
height: 20px;
|
|
468
|
+
margin-right: 8px;
|
|
469
|
+
border-radius: 50%;
|
|
470
|
+
background: rgba(120, 120, 128, 0.1);
|
|
471
|
+
color: var(--text-secondary);
|
|
472
|
+
font-size: 11px;
|
|
473
|
+
font-weight: 600;
|
|
474
|
+
vertical-align: 1px;
|
|
475
|
+
}
|
|
476
|
+
.skill-title {
|
|
477
|
+
font-weight: 500;
|
|
478
|
+
letter-spacing: -0.006em;
|
|
479
|
+
color: var(--text-primary);
|
|
480
|
+
}
|
|
481
|
+
.skill-name-cell {
|
|
482
|
+
display: flex;
|
|
483
|
+
align-items: center;
|
|
484
|
+
min-width: 0;
|
|
485
|
+
overflow: hidden;
|
|
486
|
+
white-space: nowrap;
|
|
487
|
+
}
|
|
488
|
+
.skill-id {
|
|
489
|
+
font-size: 11px;
|
|
490
|
+
color: var(--text-tertiary);
|
|
491
|
+
margin-left: 6px;
|
|
492
|
+
overflow: hidden;
|
|
493
|
+
text-overflow: ellipsis;
|
|
494
|
+
}
|
|
495
|
+
.skill-hours {
|
|
496
|
+
font-size: 11px;
|
|
497
|
+
color: var(--text-tertiary);
|
|
498
|
+
margin-left: 8px;
|
|
499
|
+
font-variant-numeric: tabular-nums;
|
|
500
|
+
}
|
|
501
|
+
.skill-stat {
|
|
502
|
+
color: var(--text-secondary);
|
|
503
|
+
font-size: 12.5px;
|
|
504
|
+
font-variant-numeric: tabular-nums;
|
|
505
|
+
white-space: nowrap;
|
|
207
506
|
}
|
|
208
|
-
.skill-title { font-weight: 500; color: #fff; }
|
|
209
|
-
.skill-stat { color: var(--text-secondary); font-variant-numeric: tabular-nums; }
|
|
210
507
|
|
|
211
508
|
.progress-track {
|
|
212
|
-
|
|
213
|
-
height:
|
|
214
|
-
|
|
509
|
+
width: 100%;
|
|
510
|
+
height: 6px;
|
|
511
|
+
border-radius: var(--radius-full);
|
|
512
|
+
background: rgba(120, 120, 128, 0.12);
|
|
513
|
+
overflow: hidden;
|
|
215
514
|
}
|
|
216
515
|
.progress-fill {
|
|
217
|
-
height: 100%;
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
.fill-gradient-mint {
|
|
221
|
-
background: linear-gradient(90deg, #30d158, #34c759);
|
|
222
|
-
box-shadow: 0 0 10px var(--apple-green-glow);
|
|
223
|
-
}
|
|
224
|
-
.fill-gradient-blue {
|
|
225
|
-
background: linear-gradient(90deg, #0071e3, #2997ff);
|
|
226
|
-
box-shadow: 0 0 10px var(--apple-blue-glow);
|
|
516
|
+
height: 100%;
|
|
517
|
+
border-radius: inherit;
|
|
518
|
+
transition: width 0.7s cubic-bezier(0.25, 0.1, 0.25, 1);
|
|
227
519
|
}
|
|
520
|
+
.fill-gradient-mint { background: linear-gradient(90deg, #2fb350, #17803a); }
|
|
521
|
+
.fill-gradient-blue { background: linear-gradient(90deg, #0071e3, #0a84ff); }
|
|
228
522
|
|
|
229
|
-
/* Table */
|
|
523
|
+
/* ---------- Table ---------- */
|
|
230
524
|
.table-card {
|
|
231
525
|
background: var(--surface);
|
|
232
|
-
backdrop-filter: var(--blur);
|
|
233
|
-
-webkit-backdrop-filter: var(--blur);
|
|
234
526
|
border: 1px solid var(--border);
|
|
235
527
|
border-radius: var(--radius-lg);
|
|
236
|
-
|
|
528
|
+
box-shadow: var(--shadow-sm);
|
|
529
|
+
padding: 24px 0 4px;
|
|
237
530
|
overflow: hidden;
|
|
238
531
|
}
|
|
239
|
-
.table-
|
|
240
|
-
|
|
532
|
+
.table-card .panel-header {
|
|
533
|
+
padding: 0 24px;
|
|
534
|
+
margin-bottom: 18px;
|
|
535
|
+
}
|
|
536
|
+
.table-responsive {
|
|
537
|
+
overflow-x: auto;
|
|
538
|
+
scrollbar-width: thin;
|
|
539
|
+
scrollbar-color: rgba(0, 0, 0, 0.12) transparent;
|
|
540
|
+
}
|
|
541
|
+
.table-responsive::-webkit-scrollbar { height: 6px; }
|
|
542
|
+
.table-responsive::-webkit-scrollbar-thumb {
|
|
543
|
+
background: rgba(0, 0, 0, 0.14);
|
|
544
|
+
border-radius: var(--radius-full);
|
|
545
|
+
}
|
|
546
|
+
.table-responsive::-webkit-scrollbar-track { background: transparent; }
|
|
547
|
+
|
|
548
|
+
table {
|
|
549
|
+
width: 100%;
|
|
550
|
+
border-collapse: separate;
|
|
551
|
+
border-spacing: 0;
|
|
552
|
+
font-size: 13px;
|
|
553
|
+
}
|
|
241
554
|
th {
|
|
242
|
-
text-align: left;
|
|
243
|
-
|
|
244
|
-
font-
|
|
555
|
+
text-align: left;
|
|
556
|
+
font-size: 11.5px;
|
|
557
|
+
font-weight: 600;
|
|
558
|
+
letter-spacing: 0.01em;
|
|
559
|
+
color: var(--text-tertiary);
|
|
560
|
+
background: rgba(120, 120, 128, 0.06);
|
|
561
|
+
border-top: 1px solid var(--border);
|
|
562
|
+
border-bottom: 1px solid var(--border);
|
|
563
|
+
padding: 10px 12px;
|
|
564
|
+
white-space: nowrap;
|
|
245
565
|
}
|
|
246
566
|
td {
|
|
247
|
-
padding:
|
|
248
|
-
|
|
567
|
+
padding: 12px 12px;
|
|
568
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
|
569
|
+
color: var(--text-primary);
|
|
570
|
+
vertical-align: middle;
|
|
249
571
|
font-variant-numeric: tabular-nums;
|
|
572
|
+
white-space: nowrap;
|
|
250
573
|
}
|
|
574
|
+
th:first-child, td:first-child { padding-left: 20px; }
|
|
575
|
+
th:last-child, td:last-child { padding-right: 20px; }
|
|
251
576
|
tr:last-child td { border-bottom: none; }
|
|
252
|
-
tr
|
|
577
|
+
tbody tr { transition: background-color 0.15s var(--ease); }
|
|
578
|
+
tbody tr:hover td { background: var(--surface-sunken); }
|
|
579
|
+
|
|
580
|
+
/* Cell typography */
|
|
581
|
+
.cell-muted { color: var(--text-tertiary); font-size: 12px; }
|
|
582
|
+
.cell-session {
|
|
583
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
584
|
+
font-size: 11px;
|
|
585
|
+
color: var(--text-tertiary);
|
|
586
|
+
letter-spacing: -0.01em;
|
|
587
|
+
}
|
|
588
|
+
.cell-hours {
|
|
589
|
+
color: var(--text-primary);
|
|
590
|
+
font-weight: 500;
|
|
591
|
+
}
|
|
592
|
+
.cell-steps {
|
|
593
|
+
font-size: 13px;
|
|
594
|
+
font-weight: 600;
|
|
595
|
+
line-height: 1.2;
|
|
596
|
+
color: var(--apple-green);
|
|
597
|
+
}
|
|
598
|
+
.cell-steps-note {
|
|
599
|
+
font-size: 11px;
|
|
600
|
+
font-weight: 400;
|
|
601
|
+
line-height: 1.2;
|
|
602
|
+
color: var(--text-tertiary);
|
|
603
|
+
margin-top: 3px;
|
|
604
|
+
font-variant-numeric: tabular-nums;
|
|
605
|
+
}
|
|
606
|
+
.cell-gain {
|
|
607
|
+
font-size: 12.5px;
|
|
608
|
+
font-weight: 600;
|
|
609
|
+
color: var(--apple-green);
|
|
610
|
+
font-variant-numeric: tabular-nums;
|
|
611
|
+
}
|
|
253
612
|
|
|
613
|
+
/* ---------- Pagination ---------- */
|
|
614
|
+
.pagination-bar {
|
|
615
|
+
display: flex;
|
|
616
|
+
align-items: center;
|
|
617
|
+
justify-content: space-between;
|
|
618
|
+
padding: 14px 24px;
|
|
619
|
+
border-top: 1px solid var(--border);
|
|
620
|
+
background: var(--surface);
|
|
621
|
+
font-size: 13px;
|
|
622
|
+
color: var(--text-secondary);
|
|
623
|
+
flex-wrap: wrap;
|
|
624
|
+
gap: 14px;
|
|
625
|
+
}
|
|
626
|
+
.pagination-left {
|
|
627
|
+
display: inline-flex;
|
|
628
|
+
align-items: center;
|
|
629
|
+
gap: 16px;
|
|
630
|
+
flex-wrap: wrap;
|
|
631
|
+
}
|
|
632
|
+
.pagination-size {
|
|
633
|
+
display: inline-flex;
|
|
634
|
+
align-items: center;
|
|
635
|
+
gap: 6px;
|
|
636
|
+
font-size: 12.5px;
|
|
637
|
+
color: var(--text-tertiary);
|
|
638
|
+
}
|
|
639
|
+
.pagination-size-label {
|
|
640
|
+
font-size: 12px;
|
|
641
|
+
color: var(--text-tertiary);
|
|
642
|
+
}
|
|
643
|
+
.segmented-control--sm {
|
|
644
|
+
padding: 2px;
|
|
645
|
+
gap: 1px;
|
|
646
|
+
}
|
|
647
|
+
.segmented-control--sm .segment-btn {
|
|
648
|
+
font-size: 11.5px;
|
|
649
|
+
padding: 3px 10px;
|
|
650
|
+
font-variant-numeric: tabular-nums;
|
|
651
|
+
}
|
|
652
|
+
.pagination-info {
|
|
653
|
+
font-size: 12.5px;
|
|
654
|
+
color: var(--text-tertiary);
|
|
655
|
+
font-variant-numeric: tabular-nums;
|
|
656
|
+
}
|
|
657
|
+
.pagination-controls {
|
|
658
|
+
display: inline-flex;
|
|
659
|
+
align-items: center;
|
|
660
|
+
gap: 8px;
|
|
661
|
+
}
|
|
662
|
+
.page-btn {
|
|
663
|
+
display: inline-flex;
|
|
664
|
+
align-items: center;
|
|
665
|
+
gap: 5px;
|
|
666
|
+
appearance: none;
|
|
667
|
+
background: var(--surface);
|
|
668
|
+
border: 1px solid var(--border-highlight);
|
|
669
|
+
color: var(--text-primary);
|
|
670
|
+
font: inherit;
|
|
671
|
+
font-size: 12.5px;
|
|
672
|
+
font-weight: 500;
|
|
673
|
+
padding: 6px 14px;
|
|
674
|
+
border-radius: var(--radius-full);
|
|
675
|
+
cursor: pointer;
|
|
676
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
677
|
+
transition: background-color 0.15s var(--ease), border-color 0.15s var(--ease), opacity 0.15s var(--ease);
|
|
678
|
+
}
|
|
679
|
+
.page-btn:hover:not(:disabled) {
|
|
680
|
+
background: var(--surface-sunken);
|
|
681
|
+
border-color: var(--hairline);
|
|
682
|
+
}
|
|
683
|
+
.page-btn:disabled {
|
|
684
|
+
opacity: 0.35;
|
|
685
|
+
cursor: not-allowed;
|
|
686
|
+
box-shadow: none;
|
|
687
|
+
}
|
|
688
|
+
.page-btn svg {
|
|
689
|
+
width: 13px;
|
|
690
|
+
height: 13px;
|
|
691
|
+
fill: none;
|
|
692
|
+
stroke: currentColor;
|
|
693
|
+
stroke-width: 2;
|
|
694
|
+
stroke-linecap: round;
|
|
695
|
+
stroke-linejoin: round;
|
|
696
|
+
}
|
|
697
|
+
.page-indicator {
|
|
698
|
+
font-size: 12.5px;
|
|
699
|
+
font-weight: 500;
|
|
700
|
+
color: var(--text-secondary);
|
|
701
|
+
font-variant-numeric: tabular-nums;
|
|
702
|
+
padding: 0 6px;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/* ---------- Pills ---------- */
|
|
254
706
|
.pill {
|
|
255
|
-
display: inline-flex;
|
|
256
|
-
|
|
257
|
-
|
|
707
|
+
display: inline-flex;
|
|
708
|
+
align-items: center;
|
|
709
|
+
gap: 5px;
|
|
710
|
+
padding: 2px 8px;
|
|
711
|
+
border-radius: 6px;
|
|
712
|
+
font-size: 11.5px;
|
|
713
|
+
font-weight: 500;
|
|
714
|
+
line-height: 1.6;
|
|
715
|
+
letter-spacing: -0.002em;
|
|
716
|
+
white-space: nowrap;
|
|
258
717
|
}
|
|
259
|
-
.pill-agent {
|
|
260
|
-
|
|
261
|
-
|
|
718
|
+
.pill-agent { background: rgba(0, 113, 227, 0.09); color: var(--apple-blue); }
|
|
719
|
+
.pill-neutral {
|
|
720
|
+
background: rgba(120, 120, 128, 0.1);
|
|
721
|
+
color: #48484a;
|
|
262
722
|
}
|
|
263
723
|
.pill-model {
|
|
264
|
-
background: rgba(
|
|
265
|
-
|
|
266
|
-
font-family: ui-monospace,
|
|
724
|
+
background: rgba(120, 120, 128, 0.12);
|
|
725
|
+
color: #48484a;
|
|
726
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
|
|
727
|
+
font-size: 11px;
|
|
728
|
+
}
|
|
729
|
+
.pill-purple { background: rgba(125, 60, 168, 0.09); color: var(--apple-purple); }
|
|
730
|
+
.pill-blue { background: rgba(0, 113, 227, 0.09); color: var(--apple-blue); }
|
|
731
|
+
.pill-success { background: rgba(26, 127, 55, 0.09); color: var(--apple-green); }
|
|
732
|
+
.pill-partial { background: rgba(163, 82, 0, 0.09); color: var(--apple-orange); }
|
|
733
|
+
.pill-failed { background: rgba(215, 0, 21, 0.09); color: var(--apple-red); }
|
|
734
|
+
|
|
735
|
+
/* ---------- Info Help Indicator ---------- */
|
|
736
|
+
.info-help {
|
|
737
|
+
display: inline-flex;
|
|
738
|
+
align-items: center;
|
|
739
|
+
justify-content: center;
|
|
740
|
+
width: 15px;
|
|
741
|
+
height: 15px;
|
|
742
|
+
border-radius: 50%;
|
|
743
|
+
font-size: 10px;
|
|
744
|
+
font-weight: 600;
|
|
745
|
+
line-height: 1;
|
|
746
|
+
color: var(--text-tertiary);
|
|
747
|
+
background: rgba(120, 120, 128, 0.12);
|
|
748
|
+
border: 1px solid rgba(0, 0, 0, 0.08);
|
|
749
|
+
cursor: pointer;
|
|
750
|
+
margin-left: 5px;
|
|
751
|
+
vertical-align: middle;
|
|
752
|
+
transition: all 0.2s var(--ease);
|
|
753
|
+
user-select: none;
|
|
754
|
+
flex: none;
|
|
755
|
+
}
|
|
756
|
+
.info-help:hover {
|
|
757
|
+
background: var(--blue-fill);
|
|
758
|
+
color: #ffffff;
|
|
759
|
+
border-color: var(--blue-fill);
|
|
760
|
+
transform: scale(1.15);
|
|
761
|
+
box-shadow: 0 2px 6px var(--apple-blue-glow);
|
|
762
|
+
}
|
|
763
|
+
/* Inside dense headers the marker should read as a quiet annotation */
|
|
764
|
+
th .info-help,
|
|
765
|
+
.mini-stat-label .info-help,
|
|
766
|
+
.subhead .info-help,
|
|
767
|
+
.inline-stats .info-help {
|
|
768
|
+
width: 14px;
|
|
769
|
+
height: 14px;
|
|
770
|
+
font-size: 9.5px;
|
|
771
|
+
background: transparent;
|
|
772
|
+
border-color: rgba(0, 0, 0, 0.16);
|
|
773
|
+
color: var(--text-tertiary);
|
|
774
|
+
margin-left: 3px;
|
|
775
|
+
transform: none;
|
|
776
|
+
}
|
|
777
|
+
th .info-help:hover,
|
|
778
|
+
.mini-stat-label .info-help:hover,
|
|
779
|
+
.subhead .info-help:hover,
|
|
780
|
+
.inline-stats .info-help:hover {
|
|
781
|
+
background: var(--blue-fill);
|
|
782
|
+
border-color: var(--blue-fill);
|
|
783
|
+
color: #fff;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/* ---------- Modal & Glossary Whitepaper ---------- */
|
|
787
|
+
.modal-overlay {
|
|
788
|
+
position: fixed;
|
|
789
|
+
inset: 0;
|
|
790
|
+
z-index: 100;
|
|
791
|
+
background: rgba(0, 0, 0, 0.4);
|
|
792
|
+
backdrop-filter: blur(12px);
|
|
793
|
+
-webkit-backdrop-filter: blur(12px);
|
|
794
|
+
display: flex;
|
|
795
|
+
align-items: center;
|
|
796
|
+
justify-content: center;
|
|
797
|
+
padding: 24px;
|
|
798
|
+
opacity: 0;
|
|
799
|
+
pointer-events: none;
|
|
800
|
+
transition: opacity 0.25s var(--ease);
|
|
801
|
+
}
|
|
802
|
+
.modal-overlay.open {
|
|
803
|
+
opacity: 1;
|
|
804
|
+
pointer-events: auto;
|
|
805
|
+
}
|
|
806
|
+
.modal-sheet {
|
|
807
|
+
background: var(--surface);
|
|
808
|
+
border: 1px solid var(--border-highlight);
|
|
809
|
+
border-radius: var(--radius-lg);
|
|
810
|
+
box-shadow: 0 24px 64px -16px rgba(0, 0, 0, 0.35), 0 0 0 1px rgba(0, 0, 0, 0.06);
|
|
811
|
+
width: 100%;
|
|
812
|
+
max-width: 860px;
|
|
813
|
+
max-height: 85vh;
|
|
814
|
+
display: flex;
|
|
815
|
+
flex-direction: column;
|
|
816
|
+
overflow: hidden;
|
|
817
|
+
transform: scale(0.96) translateY(12px);
|
|
818
|
+
transition: transform 0.25s var(--ease);
|
|
819
|
+
}
|
|
820
|
+
.modal-overlay.open .modal-sheet {
|
|
821
|
+
transform: scale(1) translateY(0);
|
|
822
|
+
}
|
|
823
|
+
.modal-header {
|
|
824
|
+
display: flex;
|
|
825
|
+
align-items: center;
|
|
826
|
+
justify-content: space-between;
|
|
827
|
+
padding: 20px 28px;
|
|
828
|
+
border-bottom: 1px solid var(--border);
|
|
829
|
+
background: var(--surface);
|
|
830
|
+
gap: 16px;
|
|
831
|
+
}
|
|
832
|
+
.modal-title-group h2 {
|
|
833
|
+
font-size: 18px;
|
|
834
|
+
font-weight: 600;
|
|
835
|
+
letter-spacing: -0.016em;
|
|
836
|
+
color: var(--text-primary);
|
|
837
|
+
}
|
|
838
|
+
.modal-title-group p {
|
|
839
|
+
font-size: 13px;
|
|
840
|
+
color: var(--text-secondary);
|
|
841
|
+
margin-top: 2px;
|
|
842
|
+
}
|
|
843
|
+
.modal-close {
|
|
844
|
+
appearance: none;
|
|
845
|
+
border: none;
|
|
846
|
+
background: rgba(120, 120, 128, 0.12);
|
|
847
|
+
width: 30px;
|
|
848
|
+
height: 30px;
|
|
849
|
+
border-radius: 50%;
|
|
850
|
+
display: grid;
|
|
851
|
+
place-items: center;
|
|
852
|
+
font-size: 14px;
|
|
853
|
+
color: var(--text-secondary);
|
|
854
|
+
cursor: pointer;
|
|
855
|
+
transition: all 0.15s ease;
|
|
856
|
+
flex: none;
|
|
857
|
+
}
|
|
858
|
+
.modal-close:hover {
|
|
859
|
+
background: rgba(120, 120, 128, 0.22);
|
|
860
|
+
color: var(--text-primary);
|
|
861
|
+
}
|
|
862
|
+
.modal-search {
|
|
863
|
+
padding: 12px 28px;
|
|
864
|
+
background: var(--surface);
|
|
865
|
+
border-bottom: 1px solid var(--border);
|
|
866
|
+
}
|
|
867
|
+
.glossary-search {
|
|
868
|
+
width: 100%;
|
|
869
|
+
height: 36px;
|
|
870
|
+
padding: 0 14px;
|
|
871
|
+
font: inherit;
|
|
872
|
+
font-size: 13px;
|
|
873
|
+
color: var(--text-primary);
|
|
874
|
+
background: var(--surface-sunken);
|
|
875
|
+
border: 1px solid var(--border-highlight);
|
|
876
|
+
border-radius: 10px;
|
|
877
|
+
outline: none;
|
|
878
|
+
transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease), background-color 0.2s var(--ease);
|
|
879
|
+
}
|
|
880
|
+
.glossary-search::placeholder { color: var(--text-tertiary); }
|
|
881
|
+
.glossary-search:focus {
|
|
882
|
+
background: var(--surface);
|
|
883
|
+
border-color: var(--blue-fill);
|
|
884
|
+
box-shadow: 0 0 0 3px var(--apple-blue-glow);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
.modal-body {
|
|
888
|
+
padding: 20px 28px 28px;
|
|
889
|
+
overflow-y: auto;
|
|
890
|
+
display: flex;
|
|
891
|
+
flex-direction: column;
|
|
892
|
+
gap: 12px;
|
|
893
|
+
background: var(--bg);
|
|
894
|
+
}
|
|
895
|
+
.glossary-card {
|
|
896
|
+
background: var(--surface);
|
|
897
|
+
border: 1px solid var(--border);
|
|
898
|
+
border-radius: 14px;
|
|
899
|
+
padding: 18px 20px;
|
|
900
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
901
|
+
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
|
902
|
+
}
|
|
903
|
+
.glossary-card.highlight {
|
|
904
|
+
border-color: var(--blue-fill);
|
|
905
|
+
background: rgba(0, 113, 227, 0.04);
|
|
906
|
+
}
|
|
907
|
+
.glossary-title-row {
|
|
908
|
+
display: flex;
|
|
909
|
+
align-items: center;
|
|
910
|
+
justify-content: space-between;
|
|
911
|
+
margin-bottom: 8px;
|
|
912
|
+
}
|
|
913
|
+
.glossary-term {
|
|
914
|
+
font-size: 15px;
|
|
915
|
+
font-weight: 600;
|
|
916
|
+
color: var(--text-primary);
|
|
917
|
+
display: flex;
|
|
918
|
+
align-items: center;
|
|
919
|
+
gap: 8px;
|
|
920
|
+
}
|
|
921
|
+
.glossary-def {
|
|
922
|
+
font-size: 13.5px;
|
|
923
|
+
line-height: 1.55;
|
|
924
|
+
color: var(--text-secondary);
|
|
925
|
+
margin-bottom: 8px;
|
|
926
|
+
}
|
|
927
|
+
.glossary-formula {
|
|
928
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
929
|
+
font-size: 12px;
|
|
930
|
+
line-height: 1.5;
|
|
931
|
+
background: rgba(0, 113, 227, 0.05);
|
|
932
|
+
border-left: 3px solid var(--blue-fill);
|
|
933
|
+
padding: 7px 12px;
|
|
934
|
+
border-radius: 0 6px 6px 0;
|
|
935
|
+
color: var(--text-primary);
|
|
936
|
+
margin: 9px 0;
|
|
937
|
+
}
|
|
938
|
+
.glossary-scene {
|
|
939
|
+
font-size: 12.5px;
|
|
940
|
+
line-height: 1.5;
|
|
941
|
+
color: var(--text-tertiary);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
/* ---------- Panel body primitives ---------- */
|
|
945
|
+
.dashboard-grid > .card {
|
|
946
|
+
display: flex;
|
|
947
|
+
flex-direction: column;
|
|
948
|
+
}
|
|
949
|
+
#l2QualityList,
|
|
950
|
+
#l3GovernanceList {
|
|
951
|
+
display: flex;
|
|
952
|
+
flex-direction: column;
|
|
953
|
+
flex: 1;
|
|
954
|
+
}
|
|
955
|
+
.panel-body {
|
|
956
|
+
display: flex;
|
|
957
|
+
flex-direction: column;
|
|
958
|
+
gap: 14px;
|
|
959
|
+
flex: 1;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/* Small section heading inside a panel */
|
|
963
|
+
.subhead {
|
|
964
|
+
display: flex;
|
|
965
|
+
align-items: center;
|
|
966
|
+
justify-content: space-between;
|
|
967
|
+
gap: 10px;
|
|
968
|
+
font-size: 12px;
|
|
969
|
+
font-weight: 600;
|
|
970
|
+
letter-spacing: -0.002em;
|
|
971
|
+
color: var(--text-secondary);
|
|
972
|
+
margin-bottom: 8px;
|
|
973
|
+
}
|
|
974
|
+
.subhead > span:first-child { display: inline-flex; align-items: center; }
|
|
975
|
+
.subhead-note {
|
|
976
|
+
font-size: 11.5px;
|
|
977
|
+
font-weight: 400;
|
|
978
|
+
color: var(--text-tertiary);
|
|
979
|
+
white-space: nowrap;
|
|
980
|
+
font-variant-numeric: tabular-nums;
|
|
981
|
+
}
|
|
982
|
+
.subhead-note strong { color: var(--apple-blue); font-weight: 600; }
|
|
983
|
+
|
|
984
|
+
/* Two-segment ratio bar */
|
|
985
|
+
.split-bar {
|
|
986
|
+
display: flex;
|
|
987
|
+
gap: 3px;
|
|
988
|
+
height: 6px;
|
|
989
|
+
border-radius: var(--radius-full);
|
|
990
|
+
overflow: hidden;
|
|
991
|
+
}
|
|
992
|
+
.split-bar-seg { height: 100%; border-radius: var(--radius-full); }
|
|
993
|
+
.split-bar-explicit { background: linear-gradient(90deg, #0071e3, #0a84ff); }
|
|
994
|
+
.split-bar-implicit { background: rgba(120, 120, 128, 0.22); }
|
|
995
|
+
|
|
996
|
+
/* Compact stat tile */
|
|
997
|
+
.stat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
|
|
998
|
+
.mini-stat {
|
|
999
|
+
background: var(--bg);
|
|
1000
|
+
border: 1px solid transparent;
|
|
1001
|
+
border-radius: var(--radius-md);
|
|
1002
|
+
padding: 12px 14px 13px;
|
|
1003
|
+
min-width: 0;
|
|
1004
|
+
}
|
|
1005
|
+
.mini-stat-label {
|
|
1006
|
+
display: flex;
|
|
1007
|
+
align-items: center;
|
|
1008
|
+
gap: 3px;
|
|
1009
|
+
font-size: 12px;
|
|
1010
|
+
letter-spacing: -0.002em;
|
|
1011
|
+
color: var(--text-secondary);
|
|
1012
|
+
margin-bottom: 5px;
|
|
1013
|
+
white-space: nowrap;
|
|
1014
|
+
}
|
|
1015
|
+
.mini-stat-value {
|
|
1016
|
+
font-size: 21px;
|
|
1017
|
+
font-weight: 600;
|
|
1018
|
+
line-height: 1.15;
|
|
1019
|
+
letter-spacing: -0.022em;
|
|
1020
|
+
color: var(--text-primary);
|
|
1021
|
+
font-variant-numeric: tabular-nums;
|
|
1022
|
+
}
|
|
1023
|
+
.mini-stat-note {
|
|
1024
|
+
font-size: 11.5px;
|
|
1025
|
+
line-height: 1.4;
|
|
1026
|
+
color: var(--text-tertiary);
|
|
1027
|
+
margin-top: 3px;
|
|
1028
|
+
}
|
|
1029
|
+
.is-warn { color: var(--apple-orange); }
|
|
1030
|
+
|
|
1031
|
+
/* Bottom inline stats row (divided, no heavy rules) */
|
|
1032
|
+
.inline-stats {
|
|
1033
|
+
display: grid;
|
|
1034
|
+
grid-template-columns: 1fr 1fr;
|
|
1035
|
+
margin-top: auto;
|
|
1036
|
+
padding-top: 12px;
|
|
1037
|
+
border-top: 1px solid var(--border);
|
|
1038
|
+
font-size: 12px;
|
|
1039
|
+
color: var(--text-tertiary);
|
|
1040
|
+
}
|
|
1041
|
+
.inline-stats > div {
|
|
1042
|
+
display: flex;
|
|
1043
|
+
align-items: center;
|
|
1044
|
+
gap: 5px;
|
|
1045
|
+
min-width: 0;
|
|
267
1046
|
}
|
|
268
|
-
.
|
|
269
|
-
|
|
270
|
-
border: 1px solid
|
|
1047
|
+
.inline-stats > div + div {
|
|
1048
|
+
padding-left: 14px;
|
|
1049
|
+
border-left: 1px solid var(--border);
|
|
271
1050
|
}
|
|
272
|
-
.
|
|
273
|
-
|
|
274
|
-
|
|
1051
|
+
.inline-stats strong {
|
|
1052
|
+
color: var(--text-primary);
|
|
1053
|
+
font-weight: 600;
|
|
1054
|
+
font-variant-numeric: tabular-nums;
|
|
275
1055
|
}
|
|
276
|
-
.
|
|
277
|
-
|
|
278
|
-
|
|
1056
|
+
.inline-stats .is-danger { color: var(--apple-red); }
|
|
1057
|
+
|
|
1058
|
+
/* Health score list */
|
|
1059
|
+
.health-list {
|
|
1060
|
+
display: flex;
|
|
1061
|
+
flex-direction: column;
|
|
1062
|
+
gap: 2px;
|
|
1063
|
+
margin: 0 -8px;
|
|
1064
|
+
}
|
|
1065
|
+
.health-row {
|
|
1066
|
+
display: flex;
|
|
1067
|
+
align-items: center;
|
|
1068
|
+
justify-content: space-between;
|
|
1069
|
+
gap: 10px;
|
|
1070
|
+
padding: 6px 8px;
|
|
1071
|
+
border-radius: 8px;
|
|
1072
|
+
font-size: 12.5px;
|
|
1073
|
+
transition: background-color 0.15s var(--ease);
|
|
1074
|
+
}
|
|
1075
|
+
.health-row:hover { background: var(--surface-hover); }
|
|
1076
|
+
.health-name {
|
|
1077
|
+
font-weight: 600;
|
|
1078
|
+
color: var(--text-primary);
|
|
1079
|
+
letter-spacing: -0.004em;
|
|
1080
|
+
min-width: 0;
|
|
1081
|
+
overflow: hidden;
|
|
1082
|
+
text-overflow: ellipsis;
|
|
1083
|
+
white-space: nowrap;
|
|
279
1084
|
}
|
|
280
|
-
.
|
|
281
|
-
|
|
282
|
-
|
|
1085
|
+
.health-id {
|
|
1086
|
+
font-size: 11px;
|
|
1087
|
+
font-weight: 400;
|
|
1088
|
+
color: var(--text-tertiary);
|
|
1089
|
+
margin-left: 6px;
|
|
283
1090
|
}
|
|
284
|
-
.pill
|
|
285
|
-
|
|
286
|
-
|
|
1091
|
+
.grade-pill {
|
|
1092
|
+
font-size: 10.5px;
|
|
1093
|
+
padding: 1px 7px;
|
|
1094
|
+
font-variant-numeric: tabular-nums;
|
|
1095
|
+
flex: none;
|
|
287
1096
|
}
|
|
288
1097
|
|
|
1098
|
+
/* Advisory blocks (dormant skills, follow-up feedback) */
|
|
1099
|
+
.notice {
|
|
1100
|
+
padding-top: 12px;
|
|
1101
|
+
border-top: 1px solid var(--border);
|
|
1102
|
+
}
|
|
1103
|
+
.notice-title {
|
|
1104
|
+
display: flex;
|
|
1105
|
+
align-items: center;
|
|
1106
|
+
gap: 6px;
|
|
1107
|
+
font-size: 12px;
|
|
1108
|
+
font-weight: 600;
|
|
1109
|
+
letter-spacing: -0.002em;
|
|
1110
|
+
color: var(--text-secondary);
|
|
1111
|
+
margin-bottom: 5px;
|
|
1112
|
+
}
|
|
1113
|
+
.notice-badge {
|
|
1114
|
+
display: inline-flex;
|
|
1115
|
+
align-items: center;
|
|
1116
|
+
justify-content: center;
|
|
1117
|
+
min-width: 18px;
|
|
1118
|
+
height: 17px;
|
|
1119
|
+
padding: 0 6px;
|
|
1120
|
+
border-radius: var(--radius-full);
|
|
1121
|
+
background: rgba(163, 82, 0, 0.1);
|
|
1122
|
+
color: var(--apple-orange);
|
|
1123
|
+
font-size: 10.5px;
|
|
1124
|
+
font-weight: 600;
|
|
1125
|
+
font-variant-numeric: tabular-nums;
|
|
1126
|
+
}
|
|
1127
|
+
.notice-body {
|
|
1128
|
+
font-size: 12px;
|
|
1129
|
+
line-height: 1.55;
|
|
1130
|
+
color: var(--text-tertiary);
|
|
1131
|
+
}
|
|
1132
|
+
.notice-item {
|
|
1133
|
+
font-size: 12px;
|
|
1134
|
+
line-height: 1.5;
|
|
1135
|
+
color: var(--text-tertiary);
|
|
1136
|
+
margin-top: 5px;
|
|
1137
|
+
}
|
|
1138
|
+
.notice-item strong { color: var(--text-primary); font-weight: 600; }
|
|
1139
|
+
.notice-count {
|
|
1140
|
+
font-size: 11px;
|
|
1141
|
+
color: var(--text-tertiary);
|
|
1142
|
+
font-variant-numeric: tabular-nums;
|
|
1143
|
+
}
|
|
1144
|
+
.dot {
|
|
1145
|
+
width: 6px;
|
|
1146
|
+
height: 6px;
|
|
1147
|
+
border-radius: 50%;
|
|
1148
|
+
flex: none;
|
|
1149
|
+
}
|
|
1150
|
+
.dot-warn { background: var(--apple-orange); }
|
|
1151
|
+
.dot-info { background: var(--apple-purple); }
|
|
1152
|
+
|
|
1153
|
+
.empty-state {
|
|
1154
|
+
padding: 30px 0;
|
|
1155
|
+
text-align: center;
|
|
1156
|
+
font-size: 13px;
|
|
1157
|
+
color: var(--text-tertiary);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
/* ---------- Footer ---------- */
|
|
289
1161
|
footer {
|
|
290
|
-
|
|
291
|
-
|
|
1162
|
+
margin-top: 44px;
|
|
1163
|
+
padding-top: 26px;
|
|
1164
|
+
border-top: 1px solid var(--border);
|
|
1165
|
+
text-align: center;
|
|
1166
|
+
font-size: 12px;
|
|
1167
|
+
line-height: 1.9;
|
|
1168
|
+
letter-spacing: -0.004em;
|
|
1169
|
+
color: var(--text-tertiary);
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
/* ---------- Responsive ---------- */
|
|
1173
|
+
@media (max-width: 1024px) {
|
|
1174
|
+
.kpi-row { grid-template-columns: repeat(2, 1fr); }
|
|
1175
|
+
}
|
|
1176
|
+
@media (max-width: 860px) {
|
|
1177
|
+
.dashboard-grid { grid-template-columns: 1fr; }
|
|
1178
|
+
}
|
|
1179
|
+
/* Filter bar collapses to a two-column grid before the selects would wrap awkwardly. */
|
|
1180
|
+
@media (max-width: 960px) {
|
|
1181
|
+
.filters {
|
|
1182
|
+
display: grid;
|
|
1183
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
1184
|
+
gap: 12px;
|
|
1185
|
+
}
|
|
1186
|
+
.filter-field--range { grid-column: 1 / -1; }
|
|
1187
|
+
.filter-field--range .segmented-control { width: 100%; }
|
|
1188
|
+
.filters .segment-btn { flex: 1; }
|
|
1189
|
+
.select { width: 100%; max-width: none; min-width: 0; }
|
|
1190
|
+
.filter-actions { grid-column: 1 / -1; margin-left: 0; }
|
|
1191
|
+
.btn-reset { width: 100%; }
|
|
1192
|
+
}
|
|
1193
|
+
@media (max-width: 900px) {
|
|
1194
|
+
/* Subtitle falls to its own line — drop the inline divider */
|
|
1195
|
+
.page-head { flex-direction: column; gap: 3px; }
|
|
1196
|
+
.page-desc { padding-left: 0; border-left: 0; }
|
|
1197
|
+
}
|
|
1198
|
+
@media (max-width: 760px) {
|
|
1199
|
+
.nav-inner {
|
|
1200
|
+
flex-wrap: wrap;
|
|
1201
|
+
gap: 10px;
|
|
1202
|
+
padding: 10px 20px 12px;
|
|
1203
|
+
}
|
|
1204
|
+
.nav-actions { width: 100%; justify-content: flex-end; }
|
|
1205
|
+
.container { padding: 20px 20px 56px; }
|
|
1206
|
+
.page-head h1 { font-size: 21px; }
|
|
1207
|
+
.page-desc { font-size: 13px; }
|
|
1208
|
+
.filters { padding: 14px 16px 16px; }
|
|
1209
|
+
}
|
|
1210
|
+
@media (max-width: 560px) {
|
|
1211
|
+
.kpi-row { grid-template-columns: 1fr; }
|
|
1212
|
+
.brand-badge { display: none; }
|
|
1213
|
+
.kpi-number { font-size: 34px; }
|
|
1214
|
+
.nav-actions { gap: 8px; }
|
|
1215
|
+
.segment-btn { font-size: 12px; padding: 6px 10px; }
|
|
1216
|
+
.btn-refresh { padding: 7px 11px; }
|
|
1217
|
+
.panel-header {
|
|
1218
|
+
flex-direction: column;
|
|
1219
|
+
align-items: flex-start;
|
|
1220
|
+
gap: 4px;
|
|
1221
|
+
}
|
|
1222
|
+
.table-card .panel-header { padding: 0 20px; }
|
|
1223
|
+
th:first-child, td:first-child { padding-left: 20px; }
|
|
1224
|
+
th:last-child, td:last-child { padding-right: 20px; }
|
|
1225
|
+
}
|
|
1226
|
+
@media (max-width: 400px) {
|
|
1227
|
+
.btn-label { display: none; }
|
|
1228
|
+
.btn-refresh { width: 32px; height: 32px; padding: 0; justify-content: center; }
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1232
|
+
*, *::before, *::after {
|
|
1233
|
+
animation-duration: 0.001ms !important;
|
|
1234
|
+
animation-iteration-count: 1 !important;
|
|
1235
|
+
transition-duration: 0.001ms !important;
|
|
1236
|
+
}
|
|
292
1237
|
}
|
|
293
1238
|
</style>
|
|
294
1239
|
</head>
|
|
295
1240
|
<body>
|
|
296
|
-
<div class="
|
|
297
|
-
|
|
298
|
-
<header>
|
|
1241
|
+
<div class="nav">
|
|
1242
|
+
<div class="nav-inner">
|
|
299
1243
|
<div class="brand">
|
|
300
1244
|
<div class="brand-icon">
|
|
301
|
-
<svg viewBox="0 0 24 24">
|
|
1245
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
302
1246
|
<path d="M12 2L14.4 9.6L22 12L14.4 14.4L12 22L9.6 14.4L2 12L9.6 9.6L12 2Z"/>
|
|
303
1247
|
</svg>
|
|
304
1248
|
</div>
|
|
305
|
-
<div>
|
|
306
|
-
<div class="brand-title">SkillFlow</div>
|
|
307
|
-
</div>
|
|
1249
|
+
<div class="brand-title">SkillFlow</div>
|
|
308
1250
|
<div class="brand-badge">
|
|
309
1251
|
<span class="pulse-dot"></span>
|
|
310
1252
|
系统就绪
|
|
311
1253
|
</div>
|
|
312
1254
|
</div>
|
|
313
1255
|
|
|
314
|
-
<div class="
|
|
315
|
-
<button class="
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
<svg viewBox="0 0 24 24">
|
|
1256
|
+
<div class="nav-actions">
|
|
1257
|
+
<button type="button" class="btn-refresh btn-help" onclick="openGlossary()" title="查看指标定义、推导公式与业务白皮书">
|
|
1258
|
+
<svg viewBox="0 0 24 24" aria-hidden="true" style="width:14px;height:14px;">
|
|
1259
|
+
<circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/>
|
|
1260
|
+
</svg>
|
|
1261
|
+
<span class="btn-label">指标术语手册</span>
|
|
1262
|
+
</button>
|
|
1263
|
+
<button type="button" class="btn-refresh" onclick="refreshAll()">
|
|
1264
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
324
1265
|
<path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/>
|
|
325
1266
|
</svg>
|
|
326
|
-
|
|
1267
|
+
<span class="btn-label">刷新</span>
|
|
327
1268
|
</button>
|
|
328
1269
|
</div>
|
|
329
|
-
</
|
|
1270
|
+
</div>
|
|
1271
|
+
</div>
|
|
1272
|
+
|
|
1273
|
+
<main class="container">
|
|
1274
|
+
<div class="page-head">
|
|
1275
|
+
<h1>SkillFlow 效能监控看板</h1>
|
|
1276
|
+
<p class="page-desc">跨 Agent 技能遥测 · 汇总技能命中、节省工时与模型成本的真实收益</p>
|
|
1277
|
+
</div>
|
|
330
1278
|
|
|
331
|
-
<!--
|
|
1279
|
+
<!-- Query filters: applies to KPI, rankings, agent split and the feed -->
|
|
1280
|
+
<section class="filters" aria-label="查询条件">
|
|
1281
|
+
<div class="filter-field filter-field--range">
|
|
1282
|
+
<span class="filter-label">时间范围</span>
|
|
1283
|
+
<div class="segmented-control" role="group" aria-label="时间范围">
|
|
1284
|
+
<button type="button" class="segment-btn active" data-range="all" onclick="switchRange('all', this)">全部历史</button>
|
|
1285
|
+
<button type="button" class="segment-btn" data-range="today" onclick="switchRange('today', this)">今日</button>
|
|
1286
|
+
<button type="button" class="segment-btn" data-range="7days" onclick="switchRange('7days', this)">近 7 天</button>
|
|
1287
|
+
<button type="button" class="segment-btn" data-range="30days" onclick="switchRange('30days', this)">近 30 天</button>
|
|
1288
|
+
</div>
|
|
1289
|
+
</div>
|
|
1290
|
+
|
|
1291
|
+
<label class="filter-field">
|
|
1292
|
+
<span class="filter-label">Agent 宿主</span>
|
|
1293
|
+
<select class="select" id="filterAgent" onchange="onFilterChange('agent', this)">
|
|
1294
|
+
<option value="">全部 Agent</option>
|
|
1295
|
+
</select>
|
|
1296
|
+
</label>
|
|
1297
|
+
|
|
1298
|
+
<label class="filter-field">
|
|
1299
|
+
<span class="filter-label">Skill 技能</span>
|
|
1300
|
+
<select class="select" id="filterSkill" onchange="onFilterChange('skill', this)">
|
|
1301
|
+
<option value="">全部 Skill</option>
|
|
1302
|
+
</select>
|
|
1303
|
+
</label>
|
|
1304
|
+
|
|
1305
|
+
<label class="filter-field">
|
|
1306
|
+
<span class="filter-label">执行模型</span>
|
|
1307
|
+
<select class="select" id="filterModel" onchange="onFilterChange('model', this)">
|
|
1308
|
+
<option value="">全部模型</option>
|
|
1309
|
+
</select>
|
|
1310
|
+
</label>
|
|
1311
|
+
|
|
1312
|
+
<label class="filter-field">
|
|
1313
|
+
<span class="filter-label">执行状态</span>
|
|
1314
|
+
<select class="select" id="filterStatus" onchange="onFilterChange('status', this)">
|
|
1315
|
+
<option value="">全部状态</option>
|
|
1316
|
+
</select>
|
|
1317
|
+
</label>
|
|
1318
|
+
|
|
1319
|
+
<div class="filter-actions">
|
|
1320
|
+
<button type="button" class="btn-reset" id="btnReset" onclick="resetFilters()" disabled>重置</button>
|
|
1321
|
+
</div>
|
|
1322
|
+
</section>
|
|
1323
|
+
|
|
1324
|
+
<!-- KPI Row -->
|
|
332
1325
|
<div class="kpi-row">
|
|
333
1326
|
<div class="card">
|
|
334
1327
|
<div class="kpi-label">
|
|
335
1328
|
<span>技能触发总量</span>
|
|
336
|
-
<span
|
|
1329
|
+
<span class="kpi-icon tint-blue" aria-hidden="true">
|
|
1330
|
+
<svg viewBox="0 0 24 24"><path class="fill" d="M13.2 2.4 4.6 13.3c-.4.5 0 1.3.7 1.3h4.5l-1.1 6.6c-.1.8.9 1.3 1.4.6l8.4-10.9c.4-.5 0-1.3-.7-1.3h-4.5l1.2-6.6c.1-.8-.9-1.3-1.3-.6Z"/></svg>
|
|
1331
|
+
</span>
|
|
337
1332
|
</div>
|
|
338
1333
|
<div class="kpi-number" id="kpiHits">-</div>
|
|
339
1334
|
<div class="kpi-meta">执行成功率 <span class="accent-green" id="kpiSuccessRate">-</span></div>
|
|
@@ -342,7 +1337,9 @@ export function renderDashboardHtml() {
|
|
|
342
1337
|
<div class="card">
|
|
343
1338
|
<div class="kpi-label">
|
|
344
1339
|
<span>累计节省人工工时</span>
|
|
345
|
-
<span
|
|
1340
|
+
<span class="kpi-icon tint-purple" aria-hidden="true">
|
|
1341
|
+
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7.2V12l3.2 2"/></svg>
|
|
1342
|
+
</span>
|
|
346
1343
|
</div>
|
|
347
1344
|
<div class="kpi-number accent-purple" id="kpiHours">-</div>
|
|
348
1345
|
<div class="kpi-meta">替代人工手动编写与排查</div>
|
|
@@ -350,29 +1347,34 @@ export function renderDashboardHtml() {
|
|
|
350
1347
|
|
|
351
1348
|
<div class="card">
|
|
352
1349
|
<div class="kpi-label">
|
|
353
|
-
<span
|
|
354
|
-
<span
|
|
1350
|
+
<span>精简流程步骤 <button type="button" class="info-help" data-glossary="term-steps" title="计算公式: 无 Skill 时基准步骤 - 实际执行步骤 = 节省步骤。点击查看白皮书">?</button></span>
|
|
1351
|
+
<span class="kpi-icon tint-green" aria-hidden="true">
|
|
1352
|
+
<svg viewBox="0 0 24 24"><path d="M4 7.5h16"/><path d="M4 12.5h11"/><path d="M4 17.5h6"/></svg>
|
|
1353
|
+
</span>
|
|
355
1354
|
</div>
|
|
356
1355
|
<div class="kpi-number accent-green" id="kpiSteps">-</div>
|
|
357
|
-
<div class="kpi-meta">消除多轮重复交互</div>
|
|
1356
|
+
<div class="kpi-meta" id="kpiStepsMeta" title="计算公式: 无 Skill 时的人工/自由探索基准步骤 - 实际执行步骤 = 精简步骤">消除多轮重复交互</div>
|
|
358
1357
|
</div>
|
|
359
1358
|
|
|
360
1359
|
<div class="card">
|
|
361
1360
|
<div class="kpi-label">
|
|
362
1361
|
<span>真实模型节约折算</span>
|
|
363
|
-
<span
|
|
1362
|
+
<span class="kpi-icon tint-amber" aria-hidden="true">
|
|
1363
|
+
<svg viewBox="0 0 24 24"><path d="M8.6 6.2 12 10.4l3.4-4.2"/><path d="M12 10.4V18"/><path d="M8.8 12.6h6.4"/><path d="M8.8 15.4h6.4"/></svg>
|
|
1364
|
+
</span>
|
|
364
1365
|
</div>
|
|
365
1366
|
<div class="kpi-number accent-blue" id="kpiCost">-</div>
|
|
366
1367
|
<div class="kpi-meta">节约 Token <span id="kpiTokens" style="color:#fff; font-weight:600;">-</span></div>
|
|
367
1368
|
</div>
|
|
368
1369
|
</div>
|
|
369
1370
|
|
|
370
|
-
<!--
|
|
1371
|
+
<!-- Panels -->
|
|
371
1372
|
<div class="dashboard-grid">
|
|
372
1373
|
<div class="card">
|
|
373
1374
|
<div class="panel-header">
|
|
374
1375
|
<div class="panel-title">
|
|
375
|
-
<
|
|
1376
|
+
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 20V11"/><path d="M10 20V4.5"/><path d="M16 20v-6"/></svg>
|
|
1377
|
+
价值贡献最大 Skill 排行榜
|
|
376
1378
|
</div>
|
|
377
1379
|
<div class="panel-subtitle">按节省步骤与工时</div>
|
|
378
1380
|
</div>
|
|
@@ -382,7 +1384,8 @@ export function renderDashboardHtml() {
|
|
|
382
1384
|
<div class="card">
|
|
383
1385
|
<div class="panel-header">
|
|
384
1386
|
<div class="panel-title">
|
|
385
|
-
<
|
|
1387
|
+
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3.2 20.4 7.6 12 12 3.6 7.6 12 3.2Z"/><path d="M4 12.2 12 16.6l8-4.4"/><path d="M4 16.6 12 21l8-4.4"/></svg>
|
|
1388
|
+
各 Agent 生态调用与节约贡献
|
|
386
1389
|
</div>
|
|
387
1390
|
<div class="panel-subtitle">跨平台分布</div>
|
|
388
1391
|
</div>
|
|
@@ -390,11 +1393,39 @@ export function renderDashboardHtml() {
|
|
|
390
1393
|
</div>
|
|
391
1394
|
</div>
|
|
392
1395
|
|
|
393
|
-
<!--
|
|
1396
|
+
<!-- L2 & L3 Governance Section -->
|
|
1397
|
+
<div class="dashboard-grid">
|
|
1398
|
+
<!-- Engineering Quality -->
|
|
1399
|
+
<div class="card">
|
|
1400
|
+
<div class="panel-header">
|
|
1401
|
+
<div class="panel-title">
|
|
1402
|
+
<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
|
1403
|
+
工程质量与意图纯度
|
|
1404
|
+
</div>
|
|
1405
|
+
<div class="panel-subtitle">上下文与调用指标</div>
|
|
1406
|
+
</div>
|
|
1407
|
+
<div id="l2QualityList">加载中...</div>
|
|
1408
|
+
</div>
|
|
1409
|
+
|
|
1410
|
+
<!-- Asset Evolution -->
|
|
1411
|
+
<div class="card">
|
|
1412
|
+
<div class="panel-header">
|
|
1413
|
+
<div class="panel-title">
|
|
1414
|
+
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
|
|
1415
|
+
资产治理与盲区反哺
|
|
1416
|
+
</div>
|
|
1417
|
+
<div class="panel-subtitle">健康评分与进化建议</div>
|
|
1418
|
+
</div>
|
|
1419
|
+
<div id="l3GovernanceList">加载中...</div>
|
|
1420
|
+
</div>
|
|
1421
|
+
</div>
|
|
1422
|
+
|
|
1423
|
+
<!-- Live invocations table -->
|
|
394
1424
|
<div class="table-card">
|
|
395
|
-
<div class="panel-header"
|
|
1425
|
+
<div class="panel-header">
|
|
396
1426
|
<div class="panel-title">
|
|
397
|
-
<
|
|
1427
|
+
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 12.5h3.6l2.6-7 4 14 2.6-7H21"/></svg>
|
|
1428
|
+
实时 Skill 命中与模型明细
|
|
398
1429
|
</div>
|
|
399
1430
|
<div class="panel-subtitle" id="tableCount">展示最新调用流水</div>
|
|
400
1431
|
</div>
|
|
@@ -407,41 +1438,277 @@ export function renderDashboardHtml() {
|
|
|
407
1438
|
<th>Agent 宿主</th>
|
|
408
1439
|
<th>执行模型</th>
|
|
409
1440
|
<th>命中的 Skill</th>
|
|
410
|
-
<th
|
|
1441
|
+
<th>价值模式 <button type="button" class="info-help" data-glossary="term-profile" title="根据 Token 投入与预期产出划定(品质重载 / 高效精简 / 深度均衡)。点击查看白皮书">?</button></th>
|
|
411
1442
|
<th>执行状态</th>
|
|
412
1443
|
<th>节省工时</th>
|
|
413
|
-
<th
|
|
414
|
-
<th>Token 收益 /
|
|
1444
|
+
<th title="计算公式: 无 Skill 时的人工/自由探索基准步骤 - 本次实际执行步骤 = 节省步骤">节省步骤 <button type="button" class="info-help" data-glossary="term-steps" title="无该技能基准步骤 - 实际执行步骤 = 节省步骤。点击查看白皮书">?</button></th>
|
|
1445
|
+
<th>Token 收益 / 投入 <button type="button" class="info-help" data-glossary="term-tokens" title="基准预估 Token - 实际消耗 Token = 净收益。点击查看白皮书">?</button></th>
|
|
415
1446
|
</tr>
|
|
416
1447
|
</thead>
|
|
417
1448
|
<tbody id="invocationsBody">
|
|
418
|
-
<tr><td colspan="
|
|
1449
|
+
<tr><td colspan="10" style="text-align: center; color: var(--text-secondary); padding: 28px 0;">正在连接本地数据库...</td></tr>
|
|
419
1450
|
</tbody>
|
|
420
1451
|
</table>
|
|
421
1452
|
</div>
|
|
1453
|
+
|
|
1454
|
+
<!-- Pagination bar (page size selector: 20 / 50 / 100) -->
|
|
1455
|
+
<div class="pagination-bar">
|
|
1456
|
+
<div class="pagination-left">
|
|
1457
|
+
<div class="pagination-info" id="paginationInfo">正在计算页码...</div>
|
|
1458
|
+
<div class="pagination-size">
|
|
1459
|
+
<span class="pagination-size-label">每页</span>
|
|
1460
|
+
<div class="segmented-control segmented-control--sm" role="group" aria-label="每页显示条数">
|
|
1461
|
+
<button type="button" class="segment-btn" data-size="20" onclick="onPageSizeChange(20, this)">20</button>
|
|
1462
|
+
<button type="button" class="segment-btn" data-size="50" onclick="onPageSizeChange(50, this)">50</button>
|
|
1463
|
+
<button type="button" class="segment-btn active" data-size="100" onclick="onPageSizeChange(100, this)">100</button>
|
|
1464
|
+
</div>
|
|
1465
|
+
<span class="pagination-size-label">条</span>
|
|
1466
|
+
</div>
|
|
1467
|
+
</div>
|
|
1468
|
+
<div class="pagination-controls">
|
|
1469
|
+
<button type="button" class="page-btn" id="btnPrevPage" onclick="changePage(-1)" disabled>
|
|
1470
|
+
<svg viewBox="0 0 24 24"><path d="M15 18l-6-6 6-6"/></svg>
|
|
1471
|
+
上一页
|
|
1472
|
+
</button>
|
|
1473
|
+
<span class="page-indicator" id="pageIndicator">第 1 / 1 页</span>
|
|
1474
|
+
<button type="button" class="page-btn" id="btnNextPage" onclick="changePage(1)" disabled>
|
|
1475
|
+
下一页
|
|
1476
|
+
<svg viewBox="0 0 24 24"><path d="M9 18l6-6-6-6"/></svg>
|
|
1477
|
+
</button>
|
|
1478
|
+
</div>
|
|
1479
|
+
</div>
|
|
422
1480
|
</div>
|
|
423
1481
|
|
|
424
1482
|
<footer>
|
|
425
|
-
SkillFlow MCP Telemetry Engine • Phase 1: Model-Aware Pricing & Work-Hour ROI<br>
|
|
1483
|
+
SkillFlow MCP Telemetry Engine • Phase 1: Model-Aware Pricing & Work-Hour ROI<br>
|
|
426
1484
|
支持 Claude Code · Trae · Qoder · WorkBuddy · Codex · DSH
|
|
427
1485
|
</footer>
|
|
428
|
-
|
|
1486
|
+
|
|
1487
|
+
<!-- Apple Frosted Glass Glossary Modal -->
|
|
1488
|
+
<div class="modal-overlay" id="glossaryModal" onclick="onModalBackdropClick(event)">
|
|
1489
|
+
<div class="modal-sheet" role="dialog" aria-modal="true" aria-labelledby="modalTitle">
|
|
1490
|
+
<div class="modal-header">
|
|
1491
|
+
<div class="modal-title-group">
|
|
1492
|
+
<h2 id="modalTitle">指标术语与算法度量白皮书</h2>
|
|
1493
|
+
<p>核心概念定义、数学核算推演公式与业务效能解读</p>
|
|
1494
|
+
</div>
|
|
1495
|
+
<button type="button" class="modal-close" onclick="closeGlossary()" aria-label="关闭">✕</button>
|
|
1496
|
+
</div>
|
|
1497
|
+
<div class="modal-search">
|
|
1498
|
+
<input type="text" id="glossarySearch" class="glossary-search" placeholder="搜索名词、公式或关键字..." oninput="filterGlossary(this.value)">
|
|
1499
|
+
</div>
|
|
1500
|
+
<div class="modal-body" id="glossaryCardsList">
|
|
1501
|
+
<!-- 1. 节省步骤 -->
|
|
1502
|
+
<div class="glossary-card" id="term-steps">
|
|
1503
|
+
<div class="glossary-title-row">
|
|
1504
|
+
<div class="glossary-term">节省步骤 (Saved Workflow Steps)</div>
|
|
1505
|
+
<span class="pill pill-success">业务感知</span>
|
|
1506
|
+
</div>
|
|
1507
|
+
<div class="glossary-def">没有该技能辅助时人工或自由探索的多轮交互步骤,减去本次实际交互步骤。</div>
|
|
1508
|
+
<div class="glossary-formula">节省步骤 = 基准预估步骤 (Baseline) - 实际交互步骤 (Actual)</div>
|
|
1509
|
+
<div class="glossary-scene"><strong>典型场景</strong>:例如写长文原本需经“初始提问 ➡️ 纠错去AI味 ➡️ 调整文风 ➡️ 补例子 ➡️ 调排版 ➡️ 定稿”共 6 轮交互。使用技能后 1 轮直接生成符合预期的成稿,净消除 5 轮人机拉扯。</div>
|
|
1510
|
+
</div>
|
|
1511
|
+
|
|
1512
|
+
<!-- 2. Token 收益 / 投入 -->
|
|
1513
|
+
<div class="glossary-card" id="term-tokens">
|
|
1514
|
+
<div class="glossary-title-row">
|
|
1515
|
+
<div class="glossary-term">Token 收益 / 投入 (Net Tokens)</div>
|
|
1516
|
+
<span class="pill pill-success">业务感知</span>
|
|
1517
|
+
</div>
|
|
1518
|
+
<div class="glossary-def">衡量本次技能执行的真实 Token 增减差值(净 ROI 收益)。</div>
|
|
1519
|
+
<div class="glossary-formula">Net Tokens = 基准预估 Token - 本次实际消耗 Token</div>
|
|
1520
|
+
<div class="glossary-scene"><strong>业务意义</strong>:若消耗低于基准则显示为绿色节约;若技能挂载了长篇规约导致消耗超额(如活人感写作),系统不虚标“节省”,而是真实显示为「投入 X 万 Tokens」,实事求是。</div>
|
|
1521
|
+
</div>
|
|
1522
|
+
|
|
1523
|
+
<!-- 3. 价值模式 -->
|
|
1524
|
+
<div class="glossary-card" id="term-profile">
|
|
1525
|
+
<div class="glossary-title-row">
|
|
1526
|
+
<div class="glossary-term">价值模式 (Quality Profile)</div>
|
|
1527
|
+
<span class="pill pill-purple">价值定位</span>
|
|
1528
|
+
</div>
|
|
1529
|
+
<div class="glossary-def">根据 Token 投入与预期产出比,由算法自动给技能打上的价值分类标签。</div>
|
|
1530
|
+
<div class="glossary-formula">高效精简型 (Net > 0) | 品质重载型 (Actual > 2 × Baseline) | 深度均衡型 (Balanced)</div>
|
|
1531
|
+
<div class="glossary-scene"><strong>三类划分</strong>:<br>• <strong>高效精简型</strong>:消耗少、响应极快,以高流程压缩比为主。<br>• <strong>品质重载型</strong>:注入超长参考知识库(如活人感写作),以高 Token 投入换取媲美专家的成稿质感。<br>• <strong>深度均衡型</strong>:标准复杂度的交互任务。</div>
|
|
1532
|
+
</div>
|
|
1533
|
+
|
|
1534
|
+
<!-- 4. 二次返工纠错率 -->
|
|
1535
|
+
<div class="glossary-card" id="term-corrections">
|
|
1536
|
+
<div class="glossary-title-row">
|
|
1537
|
+
<div class="glossary-term">二次返工纠错率 (Correction Rate)</div>
|
|
1538
|
+
<span class="pill pill-partial">工程质量</span>
|
|
1539
|
+
</div>
|
|
1540
|
+
<div class="glossary-def">技能交付后,用户紧接着在后续对话中提出“重写、写错了、重新来、不对”等负向纠错意图的调用比例。</div>
|
|
1541
|
+
<div class="glossary-formula">纠错率 = (存在重试纠错的调用次数 / 总调用次数) × 100%</div>
|
|
1542
|
+
<div class="glossary-scene"><strong>业务意义</strong>:效能的“水分过滤器”。纠错率高的技能说明规则存在盲区或模型理解偏差,并未真正为开发者省心,需要针对性优化提示词。</div>
|
|
1543
|
+
</div>
|
|
1544
|
+
|
|
1545
|
+
<!-- 5. 上下文产出率 -->
|
|
1546
|
+
<div class="glossary-card" id="term-yield">
|
|
1547
|
+
<div class="glossary-title-row">
|
|
1548
|
+
<div class="glossary-term">上下文产出率 (Yield Ratio)</div>
|
|
1549
|
+
<span class="pill pill-blue">上下文效率</span>
|
|
1550
|
+
</div>
|
|
1551
|
+
<div class="glossary-def">最终生成的有效内容 Token,与前置注入的规约参考文档 Token 之间的比值。</div>
|
|
1552
|
+
<div class="glossary-formula">上下文产出率 = (生成内容 Tokens / 挂载参考文档 Tokens) × 100%</div>
|
|
1553
|
+
<div class="glossary-scene"><strong>业务意义</strong>:监控 Prompt 是否过于冗长膨胀。防止某个技能每次挂载 5 万字参考资料却只生成了 50 字回答(过低的 Yield 意味着严重的算力与延迟浪费)。</div>
|
|
1554
|
+
</div>
|
|
1555
|
+
|
|
1556
|
+
<!-- 6. 工具链调用总深度 -->
|
|
1557
|
+
<div class="glossary-card" id="term-chain">
|
|
1558
|
+
<div class="glossary-title-row">
|
|
1559
|
+
<div class="glossary-term">工具链调用总深度 (Tool Chain Depth)</div>
|
|
1560
|
+
<span class="pill pill-blue">自动化编排</span>
|
|
1561
|
+
</div>
|
|
1562
|
+
<div class="glossary-def">技能在单次任务中,自动驱动底层子工具(如读写文件、代码替换、命令行执行、网络抓取)的连续调用次数。</div>
|
|
1563
|
+
<div class="glossary-formula">总深度 = 累加所有关联 Tool Invocations 次数</div>
|
|
1564
|
+
<div class="glossary-scene"><strong>业务意义</strong>:反映技能作为复杂 Agent 智能体的自闭环行动能力。调用链越深,代表承担的系统自动化工作量越重。</div>
|
|
1565
|
+
</div>
|
|
1566
|
+
|
|
1567
|
+
<!-- 7. 阻断异常 -->
|
|
1568
|
+
<div class="glossary-card" id="term-failures">
|
|
1569
|
+
<div class="glossary-title-row">
|
|
1570
|
+
<div class="glossary-term">阻断异常 (Tool Failures & Blockages)</div>
|
|
1571
|
+
<span class="pill pill-failed">稳定性</span>
|
|
1572
|
+
</div>
|
|
1573
|
+
<div class="glossary-def">技能在自动编排调用工具链时,因权限拒绝(Permission Denied)、命令语法报错或超时导致的异常中断次数。</div>
|
|
1574
|
+
<div class="glossary-formula">阻断异常 = 子工具抛错 / 中断失败总次数</div>
|
|
1575
|
+
<div class="glossary-scene"><strong>业务意义</strong>:直接评估技能在沙箱环境中的跨系统健壮性,排查是否存在跨平台环境 Bug。</div>
|
|
1576
|
+
</div>
|
|
1577
|
+
|
|
1578
|
+
<!-- 8. 活跃技能健康评分 -->
|
|
1579
|
+
<div class="glossary-card" id="term-health">
|
|
1580
|
+
<div class="glossary-title-row">
|
|
1581
|
+
<div class="glossary-term">活跃技能健康评分 (Health Score)</div>
|
|
1582
|
+
<span class="pill pill-purple">资产治理</span>
|
|
1583
|
+
</div>
|
|
1584
|
+
<div class="glossary-def">综合成功率 (40%)、低返工率 (30%)、工具链稳定性 (15%) 与产出比 (15%) 动态加权计算出的 0~100 分综合健康度评级。</div>
|
|
1585
|
+
<div class="glossary-formula">综合健康分 = 100 - (低成功率罚分 × 40) - (纠错率罚分 × 30) - (工具阻断罚分 × 15)</div>
|
|
1586
|
+
<div class="glossary-scene"><strong>评级标准</strong>:A+ (95-100分 顶尖) · A (85-94分 优秀) · B (70-84分 良好) · C (50-69分 需优化) · D (<50分 需重构)。</div>
|
|
1587
|
+
</div>
|
|
1588
|
+
</div>
|
|
1589
|
+
</div>
|
|
1590
|
+
</div>
|
|
1591
|
+
</main>
|
|
429
1592
|
|
|
430
1593
|
<script>
|
|
431
1594
|
let currentRange = 'all';
|
|
1595
|
+
let currentPage = 1;
|
|
1596
|
+
let pageSize = 100;
|
|
1597
|
+
const activeFilters = { agent: '', skill: '', model: '', status: '' };
|
|
1598
|
+
const FILTER_QUERY_KEYS = { agent: 'agent_name', skill: 'skill_id', model: 'model_name', status: 'status' };
|
|
1599
|
+
|
|
1600
|
+
function buildQueryString() {
|
|
1601
|
+
const params = new URLSearchParams();
|
|
1602
|
+
params.set('time_range', currentRange);
|
|
1603
|
+
Object.keys(FILTER_QUERY_KEYS).forEach(key => {
|
|
1604
|
+
const value = activeFilters[key];
|
|
1605
|
+
if (value) {
|
|
1606
|
+
params.set(FILTER_QUERY_KEYS[key], value);
|
|
1607
|
+
}
|
|
1608
|
+
});
|
|
1609
|
+
return params.toString();
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
function hasActiveFilter() {
|
|
1613
|
+
const anyFilter = Object.keys(activeFilters).some(key => !!activeFilters[key]);
|
|
1614
|
+
return anyFilter || currentRange !== 'all';
|
|
1615
|
+
}
|
|
432
1616
|
|
|
433
1617
|
function switchRange(range, el) {
|
|
434
1618
|
currentRange = range;
|
|
435
|
-
|
|
1619
|
+
currentPage = 1;
|
|
1620
|
+
document.querySelectorAll('.filter-field--range .segment-btn').forEach(btn => btn.classList.remove('active'));
|
|
436
1621
|
el.classList.add('active');
|
|
1622
|
+
syncFilterState();
|
|
1623
|
+
fetchData();
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
function onFilterChange(key, el) {
|
|
1627
|
+
activeFilters[key] = el.value || '';
|
|
1628
|
+
currentPage = 1;
|
|
1629
|
+
syncFilterState();
|
|
1630
|
+
fetchData();
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
function onPageSizeChange(size, el) {
|
|
1634
|
+
const newSize = Number(size) || 100;
|
|
1635
|
+
if (pageSize === newSize) return;
|
|
1636
|
+
pageSize = newSize;
|
|
1637
|
+
currentPage = 1;
|
|
1638
|
+
document.querySelectorAll('.pagination-size .segment-btn').forEach(btn => {
|
|
1639
|
+
btn.classList.toggle('active', Number(btn.getAttribute('data-size')) === newSize);
|
|
1640
|
+
});
|
|
1641
|
+
fetchData(1);
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
function resetFilters() {
|
|
1645
|
+
currentRange = 'all';
|
|
1646
|
+
currentPage = 1;
|
|
1647
|
+
pageSize = 100;
|
|
1648
|
+
Object.keys(activeFilters).forEach(key => { activeFilters[key] = ''; });
|
|
1649
|
+
document.querySelectorAll('.filter-field--range .segment-btn').forEach(btn => {
|
|
1650
|
+
btn.classList.toggle('active', btn.getAttribute('data-range') === 'all');
|
|
1651
|
+
});
|
|
1652
|
+
document.querySelectorAll('.pagination-size .segment-btn').forEach(btn => {
|
|
1653
|
+
btn.classList.toggle('active', btn.getAttribute('data-size') === '100');
|
|
1654
|
+
});
|
|
1655
|
+
document.querySelectorAll('.select').forEach(sel => { sel.value = ''; });
|
|
1656
|
+
syncFilterState();
|
|
1657
|
+
fetchData();
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
function syncFilterState() {
|
|
1661
|
+
document.querySelectorAll('.select').forEach(sel => {
|
|
1662
|
+
sel.classList.toggle('is-active', !!sel.value);
|
|
1663
|
+
});
|
|
1664
|
+
const resetBtn = document.getElementById('btnReset');
|
|
1665
|
+
if (resetBtn) {
|
|
1666
|
+
resetBtn.disabled = !hasActiveFilter();
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
async function loadFilterOptions() {
|
|
1671
|
+
try {
|
|
1672
|
+
const res = await fetch('/api/filters');
|
|
1673
|
+
const options = await res.json();
|
|
1674
|
+
fillSelect('filterAgent', options.agents || [], '全部 Agent');
|
|
1675
|
+
fillSelect('filterSkill', options.skills || [], '全部 Skill');
|
|
1676
|
+
fillSelect('filterModel', options.models || [], '全部模型');
|
|
1677
|
+
fillSelect('filterStatus', options.statuses || [], '全部状态');
|
|
1678
|
+
syncFilterState();
|
|
1679
|
+
} catch (err) {
|
|
1680
|
+
console.error('Filter options error:', err);
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
function fillSelect(id, options, placeholder) {
|
|
1685
|
+
const el = document.getElementById(id);
|
|
1686
|
+
if (!el) {
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
const selected = el.value;
|
|
1690
|
+
const items = ['<option value="">' + placeholder + '</option>'];
|
|
1691
|
+
options.forEach(option => {
|
|
1692
|
+
items.push('<option value="' + escapeHtml(option.value) + '">' + escapeHtml(option.label) + '</option>');
|
|
1693
|
+
});
|
|
1694
|
+
el.innerHTML = items.join('');
|
|
1695
|
+
el.value = options.some(option => option.value === selected) ? selected : '';
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
function refreshAll() {
|
|
1699
|
+
loadFilterOptions();
|
|
437
1700
|
fetchData();
|
|
438
1701
|
}
|
|
439
1702
|
|
|
440
|
-
async function fetchData() {
|
|
1703
|
+
async function fetchData(page) {
|
|
1704
|
+
if (typeof page === 'number') {
|
|
1705
|
+
currentPage = page;
|
|
1706
|
+
}
|
|
441
1707
|
try {
|
|
1708
|
+
const query = buildQueryString();
|
|
442
1709
|
const [dashRes, invRes] = await Promise.all([
|
|
443
|
-
fetch('/api/dashboard?
|
|
444
|
-
fetch('/api/invocations?limit=
|
|
1710
|
+
fetch('/api/dashboard?' + query),
|
|
1711
|
+
fetch('/api/invocations?' + query + '&limit=' + pageSize + '&page=' + currentPage)
|
|
445
1712
|
]);
|
|
446
1713
|
const dashData = await dashRes.json();
|
|
447
1714
|
const invData = await invRes.json();
|
|
@@ -449,17 +1716,35 @@ export function renderDashboardHtml() {
|
|
|
449
1716
|
renderKpi(dashData.kpi);
|
|
450
1717
|
renderTopSkills(dashData.skillRows || []);
|
|
451
1718
|
renderAgentBreakdown(dashData.agentRows || [], dashData.kpi?.totalInvocations || 1);
|
|
452
|
-
|
|
1719
|
+
renderL2Quality(dashData.kpi);
|
|
1720
|
+
renderL3Governance(dashData.governance);
|
|
1721
|
+
renderInvocations(
|
|
1722
|
+
invData.invocations || [],
|
|
1723
|
+
invData.total || 0,
|
|
1724
|
+
invData.page || currentPage,
|
|
1725
|
+
invData.totalPages || 1,
|
|
1726
|
+
invData.limit || pageSize
|
|
1727
|
+
);
|
|
453
1728
|
} catch (err) {
|
|
454
1729
|
console.error('Fetch error:', err);
|
|
455
1730
|
}
|
|
456
1731
|
}
|
|
457
1732
|
|
|
1733
|
+
function changePage(delta) {
|
|
1734
|
+
const target = currentPage + delta;
|
|
1735
|
+
if (target < 1) return;
|
|
1736
|
+
fetchData(target);
|
|
1737
|
+
}
|
|
1738
|
+
|
|
458
1739
|
function renderKpi(kpi) {
|
|
459
1740
|
if (!kpi) return;
|
|
460
1741
|
document.getElementById('kpiHits').innerText = (kpi.totalInvocations || 0).toLocaleString();
|
|
461
1742
|
document.getElementById('kpiSuccessRate').innerText = ((kpi.successRate || 1) * 100).toFixed(1) + '%';
|
|
462
1743
|
document.getElementById('kpiSteps').innerText = '+' + (kpi.totalSavedSteps || 0).toLocaleString() + ' 步';
|
|
1744
|
+
const stepsMetaEl = document.getElementById('kpiStepsMeta');
|
|
1745
|
+
if (stepsMetaEl) {
|
|
1746
|
+
stepsMetaEl.innerText = '消除 ' + (kpi.totalSavedSteps || 0).toLocaleString() + ' 轮人机校准交互';
|
|
1747
|
+
}
|
|
463
1748
|
document.getElementById('kpiTokens').innerText = '+' + (kpi.totalSavedTokens || 0).toLocaleString();
|
|
464
1749
|
|
|
465
1750
|
const hours = kpi.totalSavedHours || 0;
|
|
@@ -473,34 +1758,40 @@ export function renderDashboardHtml() {
|
|
|
473
1758
|
function renderTopSkills(skills) {
|
|
474
1759
|
const container = document.getElementById('topSkillsList');
|
|
475
1760
|
if (!skills || skills.length === 0) {
|
|
476
|
-
container.innerHTML = '<div
|
|
1761
|
+
container.innerHTML = '<div class="empty-state">暂无技能触发记录</div>';
|
|
477
1762
|
return;
|
|
478
1763
|
}
|
|
479
1764
|
const maxSteps = Math.max(...skills.map(s => s.saved_steps), 1);
|
|
480
|
-
container.innerHTML = skills.slice(0, 5).map((s, idx) =>
|
|
1765
|
+
container.innerHTML = skills.slice(0, 5).map((s, idx) => {
|
|
1766
|
+
const name = s.skill_name || s.skill_id;
|
|
1767
|
+
const idTag = s.skill_id && s.skill_id !== name
|
|
1768
|
+
? '<span class="skill-id">(' + escapeHtml(s.skill_id) + ')</span>'
|
|
1769
|
+
: '';
|
|
1770
|
+
const width = Math.min(100, Math.max(6, Math.round(s.saved_steps / maxSteps * 100)));
|
|
1771
|
+
return \`
|
|
481
1772
|
<div class="bar-row">
|
|
482
1773
|
<div class="bar-label-group">
|
|
483
|
-
<div>
|
|
1774
|
+
<div class="skill-name-cell">
|
|
484
1775
|
<span class="skill-rank-badge">\${idx + 1}</span>
|
|
485
|
-
<span class="skill-title">\${
|
|
486
|
-
<span style="color: var(--text-tertiary); font-size: 11px; margin-left: 4px;">(\${s.skill_id})</span>
|
|
1776
|
+
<span class="skill-title">\${escapeHtml(name)}</span>\${idTag}
|
|
487
1777
|
</div>
|
|
488
1778
|
<div class="skill-stat">
|
|
489
1779
|
<strong>+\${(s.saved_steps || 0).toLocaleString()} 步</strong>
|
|
490
|
-
<span
|
|
1780
|
+
<span class="skill-hours">+\${(s.saved_hours || 0).toFixed(1)}h</span>
|
|
491
1781
|
</div>
|
|
492
1782
|
</div>
|
|
493
1783
|
<div class="progress-track">
|
|
494
|
-
<div class="progress-fill fill-gradient-mint" style="width: \${
|
|
1784
|
+
<div class="progress-fill fill-gradient-mint" style="width: \${width}%;"></div>
|
|
495
1785
|
</div>
|
|
496
1786
|
</div>
|
|
497
|
-
|
|
1787
|
+
\`;
|
|
1788
|
+
}).join('');
|
|
498
1789
|
}
|
|
499
1790
|
|
|
500
1791
|
function renderAgentBreakdown(agents, totalHits) {
|
|
501
1792
|
const container = document.getElementById('agentBreakdownList');
|
|
502
1793
|
if (!agents || agents.length === 0) {
|
|
503
|
-
container.innerHTML = '<div
|
|
1794
|
+
container.innerHTML = '<div class="empty-state">暂无 Agent 接入数据</div>';
|
|
504
1795
|
return;
|
|
505
1796
|
}
|
|
506
1797
|
container.innerHTML = agents.map(a => {
|
|
@@ -508,8 +1799,8 @@ export function renderDashboardHtml() {
|
|
|
508
1799
|
return \`
|
|
509
1800
|
<div class="bar-row">
|
|
510
1801
|
<div class="bar-label-group">
|
|
511
|
-
<span class="pill pill-agent">\${a.agent_name}</span>
|
|
512
|
-
<span class="skill-stat">\${a.hit_cnt} 次
|
|
1802
|
+
<span class="pill pill-agent">\${escapeHtml(a.agent_name)}</span>
|
|
1803
|
+
<span class="skill-stat">\${a.hit_cnt} 次 · \${pct}%<span class="skill-hours" style="margin-left:10px;">+\${(a.saved_tokens || 0).toLocaleString()} Tokens</span></span>
|
|
513
1804
|
</div>
|
|
514
1805
|
<div class="progress-track">
|
|
515
1806
|
<div class="progress-fill fill-gradient-blue" style="width: \${Math.min(100, Math.max(6, pct))}%;"></div>
|
|
@@ -529,10 +1820,168 @@ export function renderDashboardHtml() {
|
|
|
529
1820
|
.replace(/'/g, ''');
|
|
530
1821
|
}
|
|
531
1822
|
|
|
532
|
-
function
|
|
1823
|
+
function renderL2Quality(kpi) {
|
|
1824
|
+
const container = document.getElementById('l2QualityList');
|
|
1825
|
+
if (!container) return;
|
|
1826
|
+
if (!kpi || !kpi.totalInvocations) {
|
|
1827
|
+
container.innerHTML = '<div class="empty-state">暂无工程指标数据</div>';
|
|
1828
|
+
return;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
const explicitPct = Number(((kpi.explicitRate || 1) * 100).toFixed(0));
|
|
1832
|
+
const implicitPct = Math.max(0, 100 - explicitPct);
|
|
1833
|
+
const correctionPct = ((kpi.correctionRate || 0) * 100).toFixed(1);
|
|
1834
|
+
const yieldPct = (kpi.yieldRatio || 100).toFixed(1);
|
|
1835
|
+
const toolDepth = kpi.totalToolDepth || 0;
|
|
1836
|
+
const toolFailures = kpi.totalToolFailures || 0;
|
|
1837
|
+
const correctionWarn = Number(correctionPct) > 10;
|
|
1838
|
+
|
|
1839
|
+
let html = '<div class="panel-body">';
|
|
1840
|
+
|
|
1841
|
+
/* 意图纯度:显式 / 隐式 两段式比例条 */
|
|
1842
|
+
html += '<div>';
|
|
1843
|
+
html += '<div class="subhead"><span>意图触发纯度</span>';
|
|
1844
|
+
html += '<span class="subhead-note">显式 <strong>' + explicitPct + '%</strong> · 隐式 ' + implicitPct + '%</span></div>';
|
|
1845
|
+
html += '<div class="split-bar">';
|
|
1846
|
+
html += '<div class="split-bar-seg split-bar-explicit" style="width: ' + explicitPct + '%;"></div>';
|
|
1847
|
+
html += '<div class="split-bar-seg split-bar-implicit" style="width: ' + implicitPct + '%;"></div>';
|
|
1848
|
+
html += '</div></div>';
|
|
1849
|
+
|
|
1850
|
+
/* 两个核心质量指标 */
|
|
1851
|
+
html += '<div class="stat-grid">';
|
|
1852
|
+
|
|
1853
|
+
html += '<div class="mini-stat">';
|
|
1854
|
+
html += '<div class="mini-stat-label">二次返工纠错率';
|
|
1855
|
+
html += '<button type="button" class="info-help" data-glossary="term-corrections" title="后续轮次出现重写/修改提示的比例。点击查看白皮书">?</button>';
|
|
1856
|
+
html += '</div>';
|
|
1857
|
+
html += '<div class="mini-stat-value' + (correctionWarn ? ' is-warn' : '') + '">' + correctionPct + '%</div>';
|
|
1858
|
+
html += '<div class="mini-stat-note">' + (kpi.totalCorrections || 0) + ' 次重试纠错</div>';
|
|
1859
|
+
html += '</div>';
|
|
1860
|
+
|
|
1861
|
+
html += '<div class="mini-stat">';
|
|
1862
|
+
html += '<div class="mini-stat-label">上下文产出率';
|
|
1863
|
+
html += '<button type="button" class="info-help" data-glossary="term-yield" title="有效输出 Token / 规约文档 Token 比率。点击查看白皮书">?</button>';
|
|
1864
|
+
html += '</div>';
|
|
1865
|
+
html += '<div class="mini-stat-value">' + yieldPct + '%</div>';
|
|
1866
|
+
html += '<div class="mini-stat-note">规约投入 ' + (kpi.totalContextTokens || 0).toLocaleString() + ' Tokens</div>';
|
|
1867
|
+
html += '</div>';
|
|
1868
|
+
|
|
1869
|
+
html += '</div>';
|
|
1870
|
+
|
|
1871
|
+
/* 工具链健康度 */
|
|
1872
|
+
html += '<div class="inline-stats">';
|
|
1873
|
+
html += '<div>工具链深度 <strong>' + toolDepth + ' 次</strong>';
|
|
1874
|
+
html += '<button type="button" class="info-help" data-glossary="term-chain" title="技能驱动底层子工具的连续调用次数。点击查看白皮书">?</button></div>';
|
|
1875
|
+
html += '<div>阻断异常 <strong' + (toolFailures > 0 ? ' class="is-danger"' : '') + '>' + toolFailures + ' 次</strong>';
|
|
1876
|
+
html += '<button type="button" class="info-help" data-glossary="term-failures" title="子工具调用被拒绝或报错阻断的次数。点击查看白皮书">?</button></div>';
|
|
1877
|
+
html += '</div>';
|
|
1878
|
+
|
|
1879
|
+
html += '</div>';
|
|
1880
|
+
container.innerHTML = html;
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
function renderL3Governance(gov) {
|
|
1884
|
+
const container = document.getElementById('l3GovernanceList');
|
|
1885
|
+
if (!container) return;
|
|
1886
|
+
if (!gov) {
|
|
1887
|
+
container.innerHTML = '<div class="empty-state">暂无治理指标数据</div>';
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
const leaderboard = gov.healthLeaderboard || [];
|
|
1892
|
+
const dormants = gov.dormantSkills || [];
|
|
1893
|
+
const modifiers = gov.followUpModifiers || [];
|
|
1894
|
+
|
|
1895
|
+
let html = '<div class="panel-body">';
|
|
1896
|
+
|
|
1897
|
+
if (leaderboard.length > 0) {
|
|
1898
|
+
html += '<div>';
|
|
1899
|
+
html += '<div class="subhead"><span>活跃技能健康评分';
|
|
1900
|
+
html += '<button type="button" class="info-help" data-glossary="term-health" title="综合成功率、低返工率、工具链稳定性打出的 0-100 综合分。点击查看白皮书">?</button>';
|
|
1901
|
+
html += '</span><span class="subhead-note">满分 100</span></div>';
|
|
1902
|
+
html += '<div class="health-list">';
|
|
1903
|
+
leaderboard.slice(0, 3).forEach(item => {
|
|
1904
|
+
const badgeClass = item.grade === 'A+' || item.grade === 'A' ? 'pill-success' : item.grade === 'B' ? 'pill-blue' : 'pill-partial';
|
|
1905
|
+
const idTag = item.skill_id && item.skill_id !== item.skill_name
|
|
1906
|
+
? '<span class="health-id">(' + escapeHtml(item.skill_id) + ')</span>'
|
|
1907
|
+
: '';
|
|
1908
|
+
html += '<div class="health-row">';
|
|
1909
|
+
html += '<span class="health-name">' + escapeHtml(item.skill_name) + idTag + '</span>';
|
|
1910
|
+
html += '<span class="pill grade-pill ' + badgeClass + '">' + escapeHtml(item.grade) + ' · ' + item.score + ' 分</span>';
|
|
1911
|
+
html += '</div>';
|
|
1912
|
+
});
|
|
1913
|
+
html += '</div></div>';
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
if (dormants.length > 0) {
|
|
1917
|
+
const names = dormants.slice(0, 3).map(d => escapeHtml(d.skill_name || d.skill_id)).join('、');
|
|
1918
|
+
html += '<div class="notice">';
|
|
1919
|
+
html += '<div class="notice-title"><span class="dot dot-warn"></span>僵尸技能待治理';
|
|
1920
|
+
html += '<span class="notice-badge">' + dormants.length + '</span></div>';
|
|
1921
|
+
html += '<div class="notice-body">' + names + (dormants.length > 3 ? ' 等' : '') + ',已超 14 天未调起,建议归档或优化触发词。</div>';
|
|
1922
|
+
html += '</div>';
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
if (modifiers.length > 0) {
|
|
1926
|
+
html += '<div class="notice">';
|
|
1927
|
+
html += '<div class="notice-title"><span class="dot dot-info"></span>用户高频追加诉求反哺</div>';
|
|
1928
|
+
modifiers.slice(0, 2).forEach(m => {
|
|
1929
|
+
html += '<div class="notice-item"><strong>' + escapeHtml(m.category) + '</strong>';
|
|
1930
|
+
html += '<span class="notice-count">· ' + m.count + ' 次</span><br>' + escapeHtml(m.recommendation) + '</div>';
|
|
1931
|
+
});
|
|
1932
|
+
html += '</div>';
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
html += '</div>';
|
|
1936
|
+
container.innerHTML = html;
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
function renderInvocations(items, total, page, totalPages, limit) {
|
|
533
1940
|
const tbody = document.getElementById('invocationsBody');
|
|
1941
|
+
const countEl = document.getElementById('tableCount');
|
|
1942
|
+
const filtered = hasActiveFilter();
|
|
1943
|
+
|
|
1944
|
+
const totalCount = typeof total === 'number' ? total : (items ? items.length : 0);
|
|
1945
|
+
const currPage = page || 1;
|
|
1946
|
+
const currLimit = limit || pageSize;
|
|
1947
|
+
const pagesCount = totalPages || Math.max(1, Math.ceil(totalCount / currLimit));
|
|
1948
|
+
const startNum = totalCount === 0 ? 0 : (currPage - 1) * currLimit + 1;
|
|
1949
|
+
const endNum = Math.min(totalCount, (currPage - 1) * currLimit + (items ? items.length : 0));
|
|
1950
|
+
|
|
1951
|
+
if (countEl) {
|
|
1952
|
+
if (!items || items.length === 0) {
|
|
1953
|
+
countEl.innerText = filtered ? '当前筛选条件下无记录' : '暂无调用流水';
|
|
1954
|
+
} else {
|
|
1955
|
+
countEl.innerText = '共 ' + totalCount.toLocaleString() + ' 条 · 显示第 ' + startNum + ' - ' + endNum + ' 条 (每页 ' + currLimit + ' 条)';
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
// Update pagination bar
|
|
1960
|
+
const pagInfo = document.getElementById('paginationInfo');
|
|
1961
|
+
const pageIndicator = document.getElementById('pageIndicator');
|
|
1962
|
+
const btnPrev = document.getElementById('btnPrevPage');
|
|
1963
|
+
const btnNext = document.getElementById('btnNextPage');
|
|
1964
|
+
|
|
1965
|
+
if (pagInfo) {
|
|
1966
|
+
pagInfo.innerText = totalCount === 0 ? '暂无记录' : '共 ' + totalCount.toLocaleString() + ' 条明细 · 第 ' + startNum + ' - ' + endNum + ' 条';
|
|
1967
|
+
}
|
|
1968
|
+
if (pageIndicator) {
|
|
1969
|
+
pageIndicator.innerText = '第 ' + currPage + ' / ' + pagesCount + ' 页';
|
|
1970
|
+
}
|
|
1971
|
+
if (btnPrev) {
|
|
1972
|
+
btnPrev.disabled = currPage <= 1;
|
|
1973
|
+
}
|
|
1974
|
+
if (btnNext) {
|
|
1975
|
+
btnNext.disabled = currPage >= pagesCount;
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
document.querySelectorAll('.pagination-size .segment-btn').forEach(btn => {
|
|
1979
|
+
btn.classList.toggle('active', Number(btn.getAttribute('data-size')) === currLimit);
|
|
1980
|
+
});
|
|
1981
|
+
|
|
534
1982
|
if (!items || items.length === 0) {
|
|
535
|
-
|
|
1983
|
+
const emptyText = filtered ? '当前筛选条件下没有匹配的调用记录' : '暂无近期 Skill 调用流水';
|
|
1984
|
+
tbody.innerHTML = '<tr><td colspan="10" style="text-align: center; color: var(--text-secondary); padding: 32px 0;">' + emptyText + '</td></tr>';
|
|
536
1985
|
return;
|
|
537
1986
|
}
|
|
538
1987
|
tbody.innerHTML = items.map(inv => {
|
|
@@ -555,34 +2004,95 @@ export function renderDashboardHtml() {
|
|
|
555
2004
|
const profileBadge = inv.quality_profile === 'heavy_investment'
|
|
556
2005
|
? '<span class="pill pill-purple">品质重载</span>'
|
|
557
2006
|
: inv.quality_profile === 'efficient'
|
|
558
|
-
? '<span class="pill pill-
|
|
559
|
-
: '<span class="pill pill-
|
|
2007
|
+
? '<span class="pill pill-blue">高效精简</span>'
|
|
2008
|
+
: '<span class="pill pill-neutral">深度均衡</span>';
|
|
560
2009
|
|
|
561
2010
|
const tokenCell = (inv.net_tokens || 0) > 0
|
|
562
|
-
? \`<span
|
|
563
|
-
: \`<span
|
|
2011
|
+
? \`<span class="cell-gain">+\${(inv.net_tokens || 0).toLocaleString()} Tokens</span>\`
|
|
2012
|
+
: \`<span class="cell-muted">投入 \${(inv.actual_tokens || 0).toLocaleString()} Tokens</span>\`;
|
|
564
2013
|
|
|
565
2014
|
const safeSession = escapeHtml(inv.session_id);
|
|
566
2015
|
const safeAgent = escapeHtml(inv.agent_name);
|
|
567
2016
|
const safeSkill = escapeHtml(inv.skill_name || inv.skill_id);
|
|
568
2017
|
|
|
2018
|
+
const bSteps = inv.baseline_steps || (inv.saved_steps + (inv.actual_steps || 1));
|
|
2019
|
+
const aSteps = inv.actual_steps || 1;
|
|
2020
|
+
const formulaTip = '计算推演: 无该技能预估需 ' + bSteps + ' 步交互 - 实际执行 ' + aSteps + ' 步 = 精简 ' + inv.saved_steps + ' 步';
|
|
2021
|
+
|
|
569
2022
|
return \`
|
|
570
2023
|
<tr>
|
|
571
|
-
<td
|
|
572
|
-
<td
|
|
2024
|
+
<td class="cell-muted">\${dateStr}</td>
|
|
2025
|
+
<td class="cell-session">\${safeSession.substring(0, 16)}...</td>
|
|
573
2026
|
<td><span class="pill pill-agent">\${safeAgent}</span></td>
|
|
574
2027
|
<td>\${modelBadge}</td>
|
|
575
2028
|
<td><strong>\${safeSkill}</strong></td>
|
|
576
2029
|
<td>\${profileBadge}</td>
|
|
577
2030
|
<td>\${statusPill}</td>
|
|
578
|
-
<td
|
|
579
|
-
<td
|
|
2031
|
+
<td class="cell-hours">\${hoursText}</td>
|
|
2032
|
+
<td title="\${formulaTip}">
|
|
2033
|
+
<div class="cell-steps">+\${inv.saved_steps} 步</div>
|
|
2034
|
+
<div class="cell-steps-note">基准 \${bSteps} → 实际 \${aSteps}</div>
|
|
2035
|
+
</td>
|
|
580
2036
|
<td>\${tokenCell}</td>
|
|
581
2037
|
</tr>
|
|
582
2038
|
\`;
|
|
583
2039
|
}).join('');
|
|
584
2040
|
}
|
|
585
2041
|
|
|
2042
|
+
function openGlossary(termId) {
|
|
2043
|
+
const modal = document.getElementById('glossaryModal');
|
|
2044
|
+
if (!modal) return;
|
|
2045
|
+
modal.classList.add('open');
|
|
2046
|
+
document.body.style.overflow = 'hidden';
|
|
2047
|
+
|
|
2048
|
+
if (termId) {
|
|
2049
|
+
setTimeout(function() {
|
|
2050
|
+
const card = document.getElementById(termId);
|
|
2051
|
+
if (card) {
|
|
2052
|
+
card.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
2053
|
+
card.classList.add('highlight');
|
|
2054
|
+
setTimeout(function() { card.classList.remove('highlight'); }, 2200);
|
|
2055
|
+
}
|
|
2056
|
+
}, 120);
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
function closeGlossary() {
|
|
2061
|
+
const modal = document.getElementById('glossaryModal');
|
|
2062
|
+
if (!modal) return;
|
|
2063
|
+
modal.classList.remove('open');
|
|
2064
|
+
document.body.style.overflow = '';
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
function onModalBackdropClick(e) {
|
|
2068
|
+
if (e.target && e.target.id === 'glossaryModal') {
|
|
2069
|
+
closeGlossary();
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
function filterGlossary(q) {
|
|
2074
|
+
const query = (q || '').trim().toLowerCase();
|
|
2075
|
+
const cards = document.querySelectorAll('.glossary-card');
|
|
2076
|
+
cards.forEach(function(card) {
|
|
2077
|
+
const text = card.innerText.toLowerCase();
|
|
2078
|
+
card.style.display = (!query || text.includes(query)) ? 'block' : 'none';
|
|
2079
|
+
});
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
window.addEventListener('keydown', function(e) {
|
|
2083
|
+
if (e.key === 'Escape') closeGlossary();
|
|
2084
|
+
});
|
|
2085
|
+
|
|
2086
|
+
document.addEventListener('click', function(e) {
|
|
2087
|
+
const btn = e.target && e.target.closest ? e.target.closest('[data-glossary]') : null;
|
|
2088
|
+
if (btn) {
|
|
2089
|
+
e.preventDefault();
|
|
2090
|
+
const termId = btn.getAttribute('data-glossary');
|
|
2091
|
+
openGlossary(termId);
|
|
2092
|
+
}
|
|
2093
|
+
});
|
|
2094
|
+
|
|
2095
|
+
loadFilterOptions();
|
|
586
2096
|
fetchData();
|
|
587
2097
|
setInterval(fetchData, 5000);
|
|
588
2098
|
</script>
|