dsh-llm-verifier 0.1.1 → 0.1.3

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/lib/client.js CHANGED
@@ -10,45 +10,108 @@ window.__ModuleLoader__.load({
10
10
  //#region src/client.tsx
11
11
  const NS = "llm-verifier";
12
12
  const shell = {
13
+ width: "100%",
14
+ maxWidth: 720,
13
15
  display: "flex",
14
16
  flexDirection: "column",
15
- gap: 18,
16
- padding: "8px 4px 32px",
17
- color: "var(--dsw-text-primary)"
17
+ gap: 12,
18
+ padding: "0 0 32px",
19
+ color: "var(--dsw-alias-label-primary)"
20
+ };
21
+ const settingsHeading = {
22
+ margin: 0,
23
+ fontSize: 16,
24
+ fontWeight: 500,
25
+ lineHeight: "24px",
26
+ color: "var(--dsw-alias-label-primary)"
27
+ };
28
+ const settingsIntro = {
29
+ margin: 0,
30
+ fontSize: 14,
31
+ lineHeight: "22px",
32
+ color: "var(--dsw-alias-label-tertiary)"
18
33
  };
19
- const card = {
34
+ const group = {
35
+ width: "100%",
20
36
  display: "flex",
21
37
  flexDirection: "column",
22
- gap: 0,
23
- padding: "16px 16px 0",
24
- border: "1px solid var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.16))",
25
- borderRadius: 12,
26
- background: "var(--dsw-alias-bg-module, rgba(20, 31, 57, 0.42))",
27
- overflow: "hidden"
38
+ borderTop: "1px solid var(--dsw-alias-border-l2)"
28
39
  };
29
- const sectionTitle = {
30
- display: "flex",
31
- gap: 10,
32
- alignItems: "center",
33
- padding: "0 0 12px",
34
- borderBottom: "1px solid var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.16))"
40
+ const groupTitle = {
41
+ margin: 0,
42
+ padding: "18px 0 8px",
43
+ fontSize: 14,
44
+ fontWeight: 500,
45
+ lineHeight: "22px",
46
+ color: "var(--dsw-alias-label-primary)"
35
47
  };
36
48
  const row = {
49
+ minHeight: 64,
37
50
  display: "grid",
38
- gridTemplateColumns: "minmax(150px, 1fr) minmax(220px, 1.4fr)",
39
- gap: 18,
51
+ gridTemplateColumns: "minmax(180px, 1fr) minmax(230px, 288px)",
52
+ gap: 24,
40
53
  alignItems: "center",
41
- padding: "14px 0",
42
- borderBottom: "1px solid var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.16))"
54
+ padding: "12px 0",
55
+ borderBottom: "1px solid var(--dsw-alias-border-l2)"
43
56
  };
44
57
  const selectStyle = {
58
+ boxSizing: "border-box",
45
59
  width: "100%",
46
- minHeight: 38,
47
- padding: "0 12px",
48
- borderRadius: 10,
49
- color: "var(--dsw-text-primary)",
50
- background: "var(--dsw-surface-sunken)",
51
- border: "1px solid var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.16))"
60
+ height: 36,
61
+ padding: "0 34px 0 12px",
62
+ borderRadius: 8,
63
+ color: "var(--dsw-alias-label-primary)",
64
+ background: "var(--dsw-alias-bg-input)",
65
+ border: "1px solid var(--dsw-alias-border-l2)",
66
+ font: "inherit",
67
+ fontSize: 14,
68
+ lineHeight: "22px",
69
+ outline: "none"
70
+ };
71
+ const toggleStyle = (enabled) => ({
72
+ position: "relative",
73
+ justifySelf: "end",
74
+ width: 40,
75
+ height: 22,
76
+ padding: 0,
77
+ border: 0,
78
+ borderRadius: 999,
79
+ cursor: "pointer",
80
+ transition: "background .15s ease",
81
+ background: enabled ? "var(--dsw-alias-state-success-primary)" : "var(--dsw-alias-bg-input)"
82
+ });
83
+ const toggleThumbStyle = (enabled) => ({
84
+ position: "absolute",
85
+ top: 3,
86
+ left: enabled ? 21 : 3,
87
+ width: 16,
88
+ height: 16,
89
+ borderRadius: "50%",
90
+ background: "var(--dsw-static-neutral-00, #fff)",
91
+ boxShadow: "0 1px 3px rgba(0,0,0,.28)",
92
+ transition: "left .15s ease"
93
+ });
94
+ const dashboardCard = {
95
+ border: "1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.13))",
96
+ background: "color-mix(in srgb, var(--dsw-alias-bg-module, #171925) 88%, transparent)",
97
+ borderRadius: 16,
98
+ boxShadow: "0 12px 36px rgba(0,0,0,.12)"
99
+ };
100
+ const muted = {
101
+ color: "var(--dsw-text-secondary)",
102
+ fontSize: 12
103
+ };
104
+ const toolLabels = {
105
+ verifier_compare: "两项对比",
106
+ verifier_select: "多项优选",
107
+ verifier_track: "进度跟踪",
108
+ verifier_current_session: "会话验收"
109
+ };
110
+ const toolColors = {
111
+ verifier_compare: "#4f8cff",
112
+ verifier_select: "#8b6df6",
113
+ verifier_track: "#2fc5c9",
114
+ verifier_current_session: "#f5a524"
52
115
  };
53
116
  function record(value) {
54
117
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
@@ -73,17 +136,70 @@ window.__ModuleLoader__.load({
73
136
  return error instanceof Error ? error.message : String(error);
74
137
  }
75
138
  function Label({ title, help }) {
76
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
77
- style: { fontWeight: 600 },
78
- children: title
79
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
80
- style: {
81
- fontSize: 12,
82
- color: "var(--dsw-text-secondary)",
83
- marginTop: 3
84
- },
85
- children: help
86
- })] });
139
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
140
+ style: { minWidth: 0 },
141
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
142
+ style: {
143
+ fontSize: 14,
144
+ fontWeight: 400,
145
+ lineHeight: "22px",
146
+ color: "var(--dsw-alias-label-primary)"
147
+ },
148
+ children: title
149
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
150
+ style: {
151
+ fontSize: 12,
152
+ lineHeight: "18px",
153
+ color: "var(--dsw-alias-label-tertiary)",
154
+ marginTop: 2
155
+ },
156
+ children: help
157
+ })]
158
+ });
159
+ }
160
+ function GroupTitle({ children }) {
161
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
162
+ style: groupTitle,
163
+ children
164
+ });
165
+ }
166
+ function compact(value) {
167
+ return new Intl.NumberFormat("zh-CN", {
168
+ notation: value >= 1e4 ? "compact" : "standard",
169
+ maximumFractionDigits: value >= 1e3 ? 1 : 0
170
+ }).format(value);
171
+ }
172
+ function money(value) {
173
+ return "$" + value.toLocaleString("en-US", {
174
+ minimumFractionDigits: value < .01 ? 4 : 2,
175
+ maximumFractionDigits: value < .01 ? 4 : 2
176
+ });
177
+ }
178
+ function duration(value) {
179
+ if (value < 1e3) return Math.round(value) + " ms";
180
+ if (value < 6e4) return (value / 1e3).toFixed(1) + " s";
181
+ return (value / 6e4).toFixed(1) + " min";
182
+ }
183
+ function dateTime(value) {
184
+ return new Intl.DateTimeFormat("zh-CN", {
185
+ month: "2-digit",
186
+ day: "2-digit",
187
+ hour: "2-digit",
188
+ minute: "2-digit",
189
+ second: "2-digit"
190
+ }).format(value);
191
+ }
192
+ function startOfRange(days) {
193
+ const date = /* @__PURE__ */ new Date();
194
+ date.setHours(0, 0, 0, 0);
195
+ date.setDate(date.getDate() - days + 1);
196
+ return date.getTime();
197
+ }
198
+ function endOfToday() {
199
+ const date = /* @__PURE__ */ new Date();
200
+ date.setHours(0, 0, 0, 0);
201
+ date.setDate(date.getDate() + 1);
202
+ return date.getTime();
87
203
  }
88
204
  function VerifierSettings({ api }) {
89
205
  const [loaded, setLoaded] = (0, react.useState)(null);
@@ -152,15 +268,27 @@ window.__ModuleLoader__.load({
152
268
  if (!loaded || !draft) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
153
269
  style: shell,
154
270
  children: [
155
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", { children: "LLM Verifier" }),
156
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: error ?? "正在读取 DSH 模型和设置…" }),
157
- error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
271
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
272
+ style: settingsHeading,
273
+ children: "LLM Verifier"
274
+ }),
275
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
276
+ style: settingsIntro,
277
+ children: error ?? "正在读取 DSH 模型和设置…"
278
+ }),
279
+ error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
280
+ variant: "outline",
158
281
  onClick: () => void load(),
159
282
  children: "重试"
160
- })
283
+ }) })
161
284
  ]
162
285
  });
163
286
  const numeric = (key, min = 0) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
287
+ style: {
288
+ width: "100%",
289
+ height: 36,
290
+ borderRadius: 8
291
+ },
164
292
  type: "number",
165
293
  min,
166
294
  value: String(draft[key]),
@@ -169,66 +297,53 @@ window.__ModuleLoader__.load({
169
297
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
170
298
  style: shell,
171
299
  children: [
172
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
173
- style: { margin: "0 0 6px" },
300
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
301
+ style: settingsHeading,
174
302
  children: "LLM Verifier"
175
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
176
- style: {
177
- margin: 0,
178
- color: "var(--dsw-text-secondary)"
179
- },
180
- children: "选择任意已在 DSH「模型」页配置并启用的模型作为独立裁判。设置实时生效。"
181
- })] }),
182
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
183
- style: card,
303
+ }),
304
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
305
+ style: settingsIntro,
306
+ children: "选择已在 DSH「模型」中配置的模型作为独立裁判。修改后点击页面底部的保存按钮生效。"
307
+ }),
308
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
309
+ style: group,
184
310
  children: [
185
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
186
- style: sectionTitle,
187
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: draft.enabled ? "done" : "error" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "工具开关" })]
188
- }),
311
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupTitle, { children: "工具" }),
189
312
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
190
313
  style: row,
191
314
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
192
315
  title: "启用 Verifier 工具",
193
- help: draft.enabled ? "四个 verifier 工具可被 Agent 调用,每次调用会向裁判模型发起请求。" : "已停用:Agent 调用 verifier 工具会立即返回错误,不产生任何模型请求。"
316
+ help: draft.enabled ? "允许 Agent 调用四个 verifier 工具并向裁判模型发起请求。" : "停用后,所有 verifier 工具都会立即返回错误。"
194
317
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
318
+ type: "button",
319
+ role: "switch",
320
+ "aria-checked": draft.enabled,
321
+ "aria-label": "启用 Verifier 工具",
195
322
  onClick: () => patch("enabled", !draft.enabled),
196
- style: {
197
- width: 80,
198
- height: 32,
199
- borderRadius: 16,
200
- border: "1px solid var(--dsw-alias-border-l2, rgba(255,255,255,0.16))",
201
- background: draft.enabled ? "var(--dsw-success, #2f9e5b)" : "var(--dsw-surface-sunken)",
202
- color: "#fff",
203
- cursor: "pointer",
204
- fontSize: 13,
205
- fontWeight: 600
206
- },
207
- children: draft.enabled ? "已启用" : "已停用"
323
+ style: toggleStyle(draft.enabled),
324
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: toggleThumbStyle(draft.enabled) })
208
325
  })]
209
326
  }),
210
- !draft.enabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
327
+ !draft.enabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
211
328
  style: {
212
- padding: "0 0 14px",
329
+ margin: "8px 0 0",
213
330
  fontSize: 12,
214
- color: "var(--dsw-state-warn-primary, #d9a441)"
331
+ lineHeight: "18px",
332
+ color: "var(--dsw-alias-state-warn-label)"
215
333
  },
216
- children: "⚠ 当前停用 verifier_compare / verifier_select / verifier_track / verifier_current_session 四个工具"
334
+ children: "verifier_compareverifier_selectverifier_track verifier_current_session 当前不可用。"
217
335
  })
218
336
  ]
219
337
  }),
220
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
221
- style: card,
338
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
339
+ style: group,
222
340
  children: [
223
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
224
- style: sectionTitle,
225
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.StateDot, { state: "done" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "裁判模型" })]
226
- }),
341
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupTitle, { children: "裁判模型" }),
227
342
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
228
343
  style: row,
229
344
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
230
345
  title: "供应商",
231
- help: "只显示当前 DSH 中可路由的供应商"
346
+ help: "当前 DSH 中可路由的模型供应商。"
232
347
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
233
348
  style: selectStyle,
234
349
  value: draft.provider,
@@ -258,7 +373,7 @@ window.__ModuleLoader__.load({
258
373
  style: row,
259
374
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
260
375
  title: "模型",
261
- help: "模型目录来自 DSH adapter,选择结果会持久化"
376
+ help: "用作裁判的具体模型。"
262
377
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
263
378
  style: selectStyle,
264
379
  value: draft.model,
@@ -285,7 +400,7 @@ window.__ModuleLoader__.load({
285
400
  style: row,
286
401
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
287
402
  title: "推理强度",
288
- help: "由所选模型 adapter 声明;留空使用模型默认值"
403
+ help: "留空时使用所选模型的默认值。"
289
404
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
290
405
  style: selectStyle,
291
406
  value: draft.reasoningEffort ?? "",
@@ -303,106 +418,864 @@ window.__ModuleLoader__.load({
303
418
  style: row,
304
419
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
305
420
  title: "最大输出 Token",
306
- help: "每个裁判请求的输出上限"
421
+ help: "单次裁判请求允许生成的最大 Token 数。"
307
422
  }), numeric("maxTokens", 1)]
308
423
  })
309
424
  ]
310
425
  }),
311
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
312
- style: card,
426
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
427
+ style: group,
313
428
  children: [
314
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
315
- style: sectionTitle,
316
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "执行控制" })
317
- }),
429
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupTitle, { children: "执行" }),
318
430
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
319
431
  style: row,
320
432
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
321
433
  title: "最大并发",
322
- help: "所有 verifier 工具共享的请求并发上限"
434
+ help: "所有 verifier 工具共享的请求并发上限。"
323
435
  }), numeric("maxConcurrency", 1)]
324
436
  }),
325
437
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
326
438
  style: row,
327
439
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
328
440
  title: "最多重试",
329
- help: "短暂网络、限流和服务端错误的重试次数"
441
+ help: "限流、超时和短暂网络错误的重试次数。"
330
442
  }), numeric("maxRetries", 0)]
331
443
  }),
332
444
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
333
445
  style: row,
334
446
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
335
- title: "请求超时(毫秒)",
336
- help: "单个模型请求的超时时间"
447
+ title: "请求超时",
448
+ help: "单次模型请求的超时时间,单位为毫秒。"
337
449
  }), numeric("timeoutMs", 1)]
338
450
  }),
339
451
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
340
452
  style: row,
341
453
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
342
454
  title: "缓存条目上限",
343
- help: "持久评分缓存保留的最大条目数"
455
+ help: "本地持久评分缓存保留的最大条目数。"
344
456
  }), numeric("cacheMaxEntries", 1)]
345
457
  })
346
458
  ]
347
459
  }),
348
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
349
- style: card,
460
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
461
+ style: group,
350
462
  children: [
351
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
352
- style: sectionTitle,
353
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "费用估算(每百万 Token,USD)" })
354
- }),
463
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GroupTitle, { children: "费用估算" }),
355
464
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
356
465
  style: row,
357
466
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
358
467
  title: "输入价格",
359
- help: "仅用于结果中的 estimatedCostUsd"
468
+ help: "每百万输入 Token 的美元价格,仅用于统计估算。"
360
469
  }), numeric("estimatedInputUsdPerMillion", 0)]
361
470
  }),
362
471
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
363
472
  style: row,
364
473
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Label, {
365
474
  title: "输出价格",
366
- help: "仅用于结果中的 estimatedCostUsd"
475
+ help: "每百万输出 Token 的美元价格,仅用于统计估算。"
367
476
  }), numeric("estimatedOutputUsdPerMillion", 0)]
368
477
  })
369
478
  ]
370
479
  }),
371
480
  loaded.failures.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
372
481
  style: {
373
- ...card,
374
- borderColor: "var(--dsw-alias-state-warn-primary, #d9a441)",
375
- paddingBottom: 16
482
+ padding: "10px 12px",
483
+ borderRadius: 8,
484
+ background: "var(--dsw-alias-state-warn-bg)",
485
+ color: "var(--dsw-alias-state-warn-label)",
486
+ fontSize: 12,
487
+ lineHeight: "18px"
376
488
  },
377
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "部分模型目录读取失败" }), loaded.failures.map((x) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: x }, x))]
489
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
490
+ style: {
491
+ fontWeight: 500,
492
+ marginBottom: 3
493
+ },
494
+ children: "部分模型目录读取失败"
495
+ }), loaded.failures.map((x) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: x }, x))]
378
496
  }),
379
- error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
380
- style: { color: "var(--dsw-danger)" },
497
+ error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
498
+ style: {
499
+ margin: 0,
500
+ fontSize: 12,
501
+ lineHeight: "18px",
502
+ color: "var(--dsw-alias-state-error-primary)"
503
+ },
381
504
  children: error
382
505
  }),
383
- saved && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
384
- style: { color: "var(--dsw-success)" },
385
- children: "已保存,后续 verifier 调用将使用新设置。"
506
+ saved && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
507
+ style: {
508
+ margin: 0,
509
+ fontSize: 12,
510
+ lineHeight: "18px",
511
+ color: "var(--dsw-alias-state-success-primary)"
512
+ },
513
+ children: "设置已保存。"
386
514
  }),
387
515
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
388
516
  style: {
389
517
  display: "flex",
390
- gap: 10
518
+ justifyContent: "flex-end",
519
+ gap: 8,
520
+ paddingTop: 4
391
521
  },
392
522
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
393
- disabled: busy || !loaded.writable,
394
- onClick: () => void save(),
395
- children: busy ? "保存中…" : "保存设置"
396
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
397
523
  variant: "outline",
398
524
  disabled: busy,
399
525
  onClick: () => void load(),
400
526
  children: "重新载入"
527
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
528
+ variant: "primary",
529
+ disabled: busy || !loaded.writable,
530
+ onClick: () => void save(),
531
+ children: busy ? "保存中…" : "保存设置"
401
532
  })]
402
533
  })
403
534
  ]
404
535
  });
405
536
  }
537
+ function Metric({ label, value, note, accent }) {
538
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
539
+ style: {
540
+ ...dashboardCard,
541
+ padding: "16px 18px",
542
+ minWidth: 0
543
+ },
544
+ children: [
545
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
546
+ style: muted,
547
+ children: label
548
+ }),
549
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
550
+ style: {
551
+ fontSize: 25,
552
+ fontWeight: 750,
553
+ lineHeight: 1.2,
554
+ margin: "7px 0 5px",
555
+ color: accent
556
+ },
557
+ children: value
558
+ }),
559
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
560
+ style: {
561
+ ...muted,
562
+ overflow: "hidden",
563
+ textOverflow: "ellipsis",
564
+ whiteSpace: "nowrap"
565
+ },
566
+ children: note
567
+ })
568
+ ]
569
+ });
570
+ }
571
+ function TrendChart({ daily, days }) {
572
+ const rows = (0, react.useMemo)(() => {
573
+ const map = new Map(daily.map((row) => [row.date, row]));
574
+ const values = [];
575
+ const start = new Date(startOfRange(days));
576
+ for (let i = 0; i < days; i += 1) {
577
+ const date = new Date(start);
578
+ date.setDate(start.getDate() + i);
579
+ const key = [
580
+ date.getFullYear(),
581
+ String(date.getMonth() + 1).padStart(2, "0"),
582
+ String(date.getDate()).padStart(2, "0")
583
+ ].join("-");
584
+ values.push(map.get(key) ?? {
585
+ date: key,
586
+ invocations: 0,
587
+ successes: 0,
588
+ failures: 0,
589
+ calls: 0,
590
+ tokens: 0,
591
+ estimatedCostUsd: 0,
592
+ byTool: {}
593
+ });
594
+ }
595
+ return values;
596
+ }, [daily, days]);
597
+ const width = 760, height = 230, pad = {
598
+ l: 44,
599
+ r: 24,
600
+ t: 20,
601
+ b: 38
602
+ };
603
+ const innerW = width - pad.l - pad.r, innerH = height - pad.t - pad.b;
604
+ const max = Math.max(1, ...rows.flatMap((row) => [row.invocations, row.calls]));
605
+ const x = (index) => pad.l + (rows.length <= 1 ? innerW / 2 : index * innerW / (rows.length - 1));
606
+ const y = (value) => pad.t + innerH - value / max * innerH;
607
+ const points = rows.map((row, index) => `${x(index)},${y(row.calls)}`).join(" ");
608
+ const step = rows.length > 16 ? Math.ceil(rows.length / 7) : Math.max(1, Math.ceil(rows.length / 7));
609
+ const barWidth = Math.max(3, Math.min(18, innerW / Math.max(rows.length, 1) * .55));
610
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
611
+ style: {
612
+ width: "100%",
613
+ overflowX: "auto"
614
+ },
615
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
616
+ viewBox: `0 0 ${width} ${height}`,
617
+ style: {
618
+ display: "block",
619
+ width: "100%",
620
+ minWidth: 620,
621
+ height: "auto"
622
+ },
623
+ "aria-label": "每日工具调用与模型请求趋势",
624
+ children: [
625
+ [
626
+ 0,
627
+ .25,
628
+ .5,
629
+ .75,
630
+ 1
631
+ ].map((ratio) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
632
+ x1: pad.l,
633
+ x2: width - pad.r,
634
+ y1: pad.t + innerH * ratio,
635
+ y2: pad.t + innerH * ratio,
636
+ stroke: "rgba(148,163,184,.16)"
637
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
638
+ x: pad.l - 8,
639
+ y: pad.t + innerH * ratio + 4,
640
+ textAnchor: "end",
641
+ fontSize: "10",
642
+ fill: "var(--dsw-text-secondary)",
643
+ children: Math.round(max * (1 - ratio))
644
+ })] }, ratio)),
645
+ rows.map((row, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
646
+ x: x(index) - barWidth / 2,
647
+ y: y(row.invocations),
648
+ width: barWidth,
649
+ height: pad.t + innerH - y(row.invocations),
650
+ rx: "2",
651
+ fill: "#4f8cff",
652
+ opacity: ".82"
653
+ }, row.date)),
654
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("polyline", {
655
+ points,
656
+ fill: "none",
657
+ stroke: "#5ed7e8",
658
+ strokeWidth: "2.4",
659
+ strokeLinejoin: "round",
660
+ strokeLinecap: "round"
661
+ }),
662
+ rows.map((row, index) => index % step === 0 || index === rows.length - 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("text", {
663
+ x: x(index),
664
+ y: height - 14,
665
+ textAnchor: "middle",
666
+ fontSize: "10",
667
+ fill: "var(--dsw-text-secondary)",
668
+ children: [
669
+ Number(row.date.slice(5, 7)),
670
+ "/",
671
+ Number(row.date.slice(8, 10))
672
+ ]
673
+ }, row.date) : null)
674
+ ]
675
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
676
+ style: {
677
+ display: "flex",
678
+ justifyContent: "center",
679
+ gap: 18,
680
+ ...muted
681
+ },
682
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { style: {
683
+ display: "inline-block",
684
+ width: 8,
685
+ height: 8,
686
+ borderRadius: 2,
687
+ background: "#4f8cff",
688
+ marginRight: 6
689
+ } }), "工具调用"] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { style: {
690
+ display: "inline-block",
691
+ width: 14,
692
+ height: 2,
693
+ background: "#5ed7e8",
694
+ marginRight: 6,
695
+ verticalAlign: "middle"
696
+ } }), "模型请求"] })]
697
+ })]
698
+ });
699
+ }
700
+ function StatisticsPage({ sessionId, rpc }) {
701
+ const [days, setDays] = (0, react.useState)(30);
702
+ const [sessionOnly, setSessionOnly] = (0, react.useState)(false);
703
+ const [data, setData] = (0, react.useState)(null);
704
+ const [loading, setLoading] = (0, react.useState)(true);
705
+ const [error, setError] = (0, react.useState)(null);
706
+ const [refresh, setRefresh] = (0, react.useState)(0);
707
+ (0, react.useEffect)(() => {
708
+ const controller = new AbortController();
709
+ setLoading(true);
710
+ setError(null);
711
+ rpc.call("/llm-verifier", "statistics", {
712
+ fromMs: startOfRange(days),
713
+ toMs: endOfToday(),
714
+ timezoneOffsetMinutes: (/* @__PURE__ */ new Date()).getTimezoneOffset(),
715
+ recentLimit: 50,
716
+ ...sessionOnly ? { sessionId: String(sessionId) } : {}
717
+ }, controller.signal).then((result) => {
718
+ if (!result.ok) throw new Error(result.error?.message ?? "统计接口请求失败");
719
+ setData(result.value);
720
+ }, (cause) => {
721
+ if (!controller.signal.aborted) throw cause;
722
+ }).catch((cause) => {
723
+ if (!controller.signal.aborted) setError(message(cause));
724
+ }).finally(() => {
725
+ if (!controller.signal.aborted) setLoading(false);
726
+ });
727
+ return () => controller.abort();
728
+ }, [
729
+ days,
730
+ sessionOnly,
731
+ sessionId,
732
+ refresh,
733
+ rpc
734
+ ]);
735
+ const totals = data?.totals;
736
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("main", {
737
+ style: {
738
+ height: "100%",
739
+ overflow: "auto",
740
+ boxSizing: "border-box",
741
+ padding: "22px clamp(16px, 3vw, 38px) 48px",
742
+ color: "var(--dsw-text-primary)",
743
+ background: "radial-gradient(circle at 10% 0%, rgba(115,77,255,.09), transparent 32%), radial-gradient(circle at 100% 8%, rgba(47,197,201,.07), transparent 28%)"
744
+ },
745
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
746
+ style: {
747
+ maxWidth: 1180,
748
+ margin: "0 auto",
749
+ display: "flex",
750
+ flexDirection: "column",
751
+ gap: 16
752
+ },
753
+ children: [
754
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
755
+ style: {
756
+ display: "flex",
757
+ justifyContent: "space-between",
758
+ alignItems: "flex-start",
759
+ gap: 16,
760
+ flexWrap: "wrap"
761
+ },
762
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
763
+ style: {
764
+ display: "flex",
765
+ alignItems: "center",
766
+ gap: 10
767
+ },
768
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
769
+ style: {
770
+ display: "grid",
771
+ placeItems: "center",
772
+ width: 34,
773
+ height: 34,
774
+ borderRadius: 10,
775
+ background: "rgba(79,140,255,.14)",
776
+ color: "#6da0ff"
777
+ },
778
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDataOutline16, { size: 18 })
779
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
780
+ style: {
781
+ margin: 0,
782
+ fontSize: 23
783
+ },
784
+ children: "Verifier 工具统计"
785
+ })]
786
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
787
+ style: {
788
+ margin: "7px 0 0 44px",
789
+ ...muted
790
+ },
791
+ children: [
792
+ "数据更新于 ",
793
+ data ? dateTime(data.generatedAt) : "--",
794
+ " · 自动记录四个验证工具的实际执行结果"
795
+ ]
796
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
797
+ style: {
798
+ display: "flex",
799
+ gap: 8,
800
+ alignItems: "center",
801
+ flexWrap: "wrap"
802
+ },
803
+ children: [
804
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
805
+ style: {
806
+ display: "flex",
807
+ padding: 3,
808
+ borderRadius: 10,
809
+ background: "var(--dsw-surface-sunken)",
810
+ border: "1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.12))"
811
+ },
812
+ children: [
813
+ 7,
814
+ 30,
815
+ 90
816
+ ].map((value) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
817
+ onClick: () => setDays(value),
818
+ style: {
819
+ border: 0,
820
+ borderRadius: 7,
821
+ padding: "6px 10px",
822
+ cursor: "pointer",
823
+ color: days === value ? "#fff" : "var(--dsw-text-secondary)",
824
+ background: days === value ? "#3f68d8" : "transparent"
825
+ },
826
+ children: [value, " 天"]
827
+ }, value))
828
+ }),
829
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
830
+ onClick: () => setSessionOnly((value) => !value),
831
+ style: {
832
+ border: "1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.15))",
833
+ borderRadius: 9,
834
+ padding: "7px 11px",
835
+ cursor: "pointer",
836
+ color: "var(--dsw-text-primary)",
837
+ background: sessionOnly ? "rgba(79,140,255,.18)" : "var(--dsw-surface-sunken)"
838
+ },
839
+ children: sessionOnly ? "当前会话" : "全部会话"
840
+ }),
841
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
842
+ title: "刷新",
843
+ onClick: () => setRefresh((value) => value + 1),
844
+ style: {
845
+ display: "grid",
846
+ placeItems: "center",
847
+ width: 34,
848
+ height: 34,
849
+ borderRadius: 9,
850
+ border: "1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.15))",
851
+ color: "var(--dsw-text-primary)",
852
+ background: "var(--dsw-surface-sunken)",
853
+ cursor: "pointer"
854
+ },
855
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16, { size: 16 })
856
+ })
857
+ ]
858
+ })]
859
+ }),
860
+ error && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
861
+ style: {
862
+ ...dashboardCard,
863
+ padding: 18,
864
+ borderColor: "var(--dsw-danger, #e85858)",
865
+ color: "var(--dsw-danger, #e85858)"
866
+ },
867
+ children: [error, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
868
+ style: {
869
+ ...muted,
870
+ marginTop: 6
871
+ },
872
+ children: "请确认宿主已重启并加载最新版本的 dsh-llm-verifier。"
873
+ })]
874
+ }),
875
+ loading && !data ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
876
+ style: {
877
+ ...dashboardCard,
878
+ padding: 32,
879
+ textAlign: "center",
880
+ ...muted
881
+ },
882
+ children: "正在读取工具运行统计…"
883
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
884
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
885
+ style: {
886
+ ...dashboardCard,
887
+ padding: "22px 24px",
888
+ display: "grid",
889
+ gridTemplateColumns: "minmax(220px,1.4fr) minmax(240px,1fr)",
890
+ gap: 24,
891
+ alignItems: "center"
892
+ },
893
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
894
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
895
+ style: muted,
896
+ children: [days, " 天估算费用"]
897
+ }),
898
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
899
+ style: {
900
+ fontSize: 38,
901
+ fontWeight: 780,
902
+ letterSpacing: "-.03em",
903
+ margin: "5px 0"
904
+ },
905
+ children: money(totals?.estimatedCostUsd ?? 0)
906
+ }),
907
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
908
+ style: muted,
909
+ children: [
910
+ compact(totals?.invocations ?? 0),
911
+ " 次工具调用 · ",
912
+ compact(totals?.calls ?? 0),
913
+ " 次裁判模型请求"
914
+ ]
915
+ })
916
+ ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
917
+ style: {
918
+ display: "grid",
919
+ gridTemplateColumns: "1fr auto",
920
+ gap: "9px 16px",
921
+ fontSize: 13
922
+ },
923
+ children: [
924
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
925
+ style: muted,
926
+ children: "成功调用"
927
+ }),
928
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("strong", { children: [
929
+ compact(totals?.successes ?? 0),
930
+ " ",
931
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("small", {
932
+ style: { color: "#77d49b" },
933
+ children: [
934
+ "▲ ",
935
+ ((totals?.successRate ?? 0) * 100).toFixed(1),
936
+ "%"
937
+ ]
938
+ })
939
+ ] }),
940
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
941
+ style: muted,
942
+ children: "失败调用"
943
+ }),
944
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: compact(totals?.failures ?? 0) }),
945
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
946
+ style: muted,
947
+ children: "平均耗时"
948
+ }),
949
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: duration(totals?.averageDurationMs ?? 0) })
950
+ ]
951
+ })]
952
+ }),
953
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
954
+ style: {
955
+ display: "grid",
956
+ gridTemplateColumns: "repeat(auto-fit,minmax(180px,1fr))",
957
+ gap: 12
958
+ },
959
+ children: [
960
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
961
+ label: "缓存命中率",
962
+ value: ((totals?.cacheHitRate ?? 0) * 100).toFixed(1) + "%",
963
+ note: `${compact(totals?.cacheHits ?? 0)} 命中 / ${compact((totals?.cacheHits ?? 0) + (totals?.cacheMisses ?? 0))} 评分`,
964
+ accent: "#b7dd64"
965
+ }),
966
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
967
+ label: "Token",
968
+ value: compact(totals?.tokens ?? 0),
969
+ note: `输入 ${compact((totals?.inputTokens ?? 0) + (totals?.cachedInputTokens ?? 0))} · 输出 ${compact(totals?.outputTokens ?? 0)}`
970
+ }),
971
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
972
+ label: "平均模型请求",
973
+ value: (totals?.invocations ?? 0) > 0 ? ((totals?.calls ?? 0) / (totals?.invocations ?? 1)).toFixed(1) : "0",
974
+ note: `${compact(totals?.attempts ?? 0)} 次尝试 · ${compact(totals?.retries ?? 0)} 次重试`
975
+ }),
976
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
977
+ label: "评分模式",
978
+ value: compact(totals?.topLogprobScores ?? 0),
979
+ note: `Top-logprobs · 显式标签 ${compact(totals?.explicitTagScores ?? 0)}`
980
+ })
981
+ ]
982
+ }),
983
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
984
+ style: {
985
+ ...dashboardCard,
986
+ padding: "18px 20px 16px"
987
+ },
988
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
989
+ style: {
990
+ display: "flex",
991
+ justifyContent: "space-between",
992
+ alignItems: "center",
993
+ marginBottom: 4
994
+ },
995
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "每日工具调用与模型请求趋势" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
996
+ style: muted,
997
+ children: sessionOnly ? "当前会话" : "全部会话"
998
+ })]
999
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrendChart, {
1000
+ daily: data?.daily ?? [],
1001
+ days
1002
+ })]
1003
+ }),
1004
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1005
+ style: {
1006
+ ...dashboardCard,
1007
+ padding: "18px 18px 8px",
1008
+ overflow: "hidden"
1009
+ },
1010
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1011
+ style: {
1012
+ display: "flex",
1013
+ justifyContent: "space-between",
1014
+ margin: "0 2px 12px"
1015
+ },
1016
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "工具运行明细" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1017
+ style: muted,
1018
+ children: [data?.tools.length ?? 0, " 类工具"]
1019
+ })]
1020
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1021
+ style: { overflowX: "auto" },
1022
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
1023
+ style: {
1024
+ width: "100%",
1025
+ borderCollapse: "collapse",
1026
+ fontSize: 13,
1027
+ minWidth: 760
1028
+ },
1029
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", {
1030
+ style: {
1031
+ textAlign: "left",
1032
+ color: "var(--dsw-text-secondary)",
1033
+ background: "var(--dsw-surface-sunken)"
1034
+ },
1035
+ children: [
1036
+ "工具",
1037
+ "调用",
1038
+ "成功率",
1039
+ "平均耗时",
1040
+ "模型请求",
1041
+ "Token",
1042
+ "缓存命中",
1043
+ "估算费用"
1044
+ ].map((value) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
1045
+ style: {
1046
+ padding: "10px 12px",
1047
+ fontWeight: 500
1048
+ },
1049
+ children: value
1050
+ }, value))
1051
+ }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tbody", { children: [(data?.tools ?? []).map((tool) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", {
1052
+ style: { borderTop: "1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.1))" },
1053
+ children: [
1054
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", {
1055
+ style: { padding: "13px 12px" },
1056
+ children: [
1057
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
1058
+ display: "inline-block",
1059
+ width: 8,
1060
+ height: 8,
1061
+ borderRadius: "50%",
1062
+ background: toolColors[tool.toolName] ?? "#8691a8",
1063
+ marginRight: 8
1064
+ } }),
1065
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: toolLabels[tool.toolName] ?? tool.toolName }),
1066
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1067
+ style: {
1068
+ ...muted,
1069
+ margin: "3px 0 0 16px"
1070
+ },
1071
+ children: tool.toolName
1072
+ })
1073
+ ]
1074
+ }),
1075
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1076
+ style: { padding: "13px 12px" },
1077
+ children: compact(tool.invocations)
1078
+ }),
1079
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", {
1080
+ style: {
1081
+ padding: "13px 12px",
1082
+ color: tool.successRate >= .9 ? "#77d49b" : tool.successRate >= .7 ? "#e3bd63" : "#ed7777"
1083
+ },
1084
+ children: [(tool.successRate * 100).toFixed(1), "%"]
1085
+ }),
1086
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1087
+ style: { padding: "13px 12px" },
1088
+ children: duration(tool.averageDurationMs)
1089
+ }),
1090
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1091
+ style: { padding: "13px 12px" },
1092
+ children: compact(tool.calls)
1093
+ }),
1094
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1095
+ style: { padding: "13px 12px" },
1096
+ children: compact(tool.tokens)
1097
+ }),
1098
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", {
1099
+ style: { padding: "13px 12px" },
1100
+ children: [
1101
+ tool.cacheHits,
1102
+ "/",
1103
+ tool.cacheHits + tool.cacheMisses
1104
+ ]
1105
+ }),
1106
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1107
+ style: { padding: "13px 12px" },
1108
+ children: money(tool.estimatedCostUsd)
1109
+ })
1110
+ ]
1111
+ }, tool.toolName)), (data?.tools.length ?? 0) === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1112
+ colSpan: 8,
1113
+ style: {
1114
+ padding: 28,
1115
+ textAlign: "center",
1116
+ ...muted
1117
+ },
1118
+ children: "所选范围内还没有 Verifier 工具调用。后续执行会自动出现在这里。"
1119
+ }) })] })]
1120
+ })
1121
+ })]
1122
+ }),
1123
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1124
+ style: {
1125
+ display: "grid",
1126
+ gridTemplateColumns: "minmax(0,1fr) minmax(290px,.45fr)",
1127
+ gap: 16,
1128
+ alignItems: "start"
1129
+ },
1130
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1131
+ style: {
1132
+ ...dashboardCard,
1133
+ padding: "18px 18px 8px",
1134
+ overflow: "hidden"
1135
+ },
1136
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1137
+ style: {
1138
+ display: "flex",
1139
+ justifyContent: "space-between",
1140
+ margin: "0 2px 12px"
1141
+ },
1142
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "最近调用" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1143
+ style: muted,
1144
+ children: "最多 50 条"
1145
+ })]
1146
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1147
+ style: {
1148
+ maxHeight: 360,
1149
+ overflow: "auto"
1150
+ },
1151
+ children: [(data?.recent ?? []).map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1152
+ style: {
1153
+ display: "grid",
1154
+ gridTemplateColumns: "minmax(170px,1fr) auto",
1155
+ gap: 12,
1156
+ padding: "11px 8px",
1157
+ borderTop: "1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.1))"
1158
+ },
1159
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1160
+ style: {
1161
+ display: "flex",
1162
+ alignItems: "center",
1163
+ gap: 8
1164
+ },
1165
+ children: [
1166
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
1167
+ width: 7,
1168
+ height: 7,
1169
+ borderRadius: "50%",
1170
+ background: item.success ? "#59c985" : "#e76565"
1171
+ } }),
1172
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
1173
+ style: { fontSize: 13 },
1174
+ children: toolLabels[item.toolName] ?? item.toolName
1175
+ }),
1176
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1177
+ style: muted,
1178
+ children: [
1179
+ item.provider,
1180
+ "/",
1181
+ item.model
1182
+ ]
1183
+ })
1184
+ ]
1185
+ }), !item.success && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1186
+ title: item.errorMessage,
1187
+ style: {
1188
+ margin: "5px 0 0 15px",
1189
+ fontSize: 11,
1190
+ color: "#e76565",
1191
+ overflow: "hidden",
1192
+ textOverflow: "ellipsis",
1193
+ whiteSpace: "nowrap"
1194
+ },
1195
+ children: item.errorMessage ?? item.errorName
1196
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1197
+ style: { textAlign: "right" },
1198
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1199
+ style: { fontSize: 12 },
1200
+ children: duration(item.durationMs)
1201
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1202
+ style: {
1203
+ ...muted,
1204
+ marginTop: 3
1205
+ },
1206
+ children: dateTime(item.startedAt)
1207
+ })]
1208
+ })]
1209
+ }, item.id)), (data?.recent.length ?? 0) === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1210
+ style: {
1211
+ padding: 24,
1212
+ textAlign: "center",
1213
+ ...muted
1214
+ },
1215
+ children: "暂无记录"
1216
+ })]
1217
+ })]
1218
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1219
+ style: {
1220
+ ...dashboardCard,
1221
+ padding: "18px"
1222
+ },
1223
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "模型汇总" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1224
+ style: {
1225
+ marginTop: 10,
1226
+ display: "flex",
1227
+ flexDirection: "column",
1228
+ gap: 10
1229
+ },
1230
+ children: [(data?.models ?? []).map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1231
+ style: {
1232
+ padding: "11px 12px",
1233
+ borderRadius: 10,
1234
+ background: "var(--dsw-surface-sunken)"
1235
+ },
1236
+ children: [
1237
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1238
+ style: {
1239
+ fontWeight: 650,
1240
+ fontSize: 13,
1241
+ overflow: "hidden",
1242
+ textOverflow: "ellipsis"
1243
+ },
1244
+ children: model.model
1245
+ }),
1246
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1247
+ style: {
1248
+ ...muted,
1249
+ marginTop: 3
1250
+ },
1251
+ children: model.provider
1252
+ }),
1253
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1254
+ style: {
1255
+ display: "flex",
1256
+ justifyContent: "space-between",
1257
+ marginTop: 9,
1258
+ fontSize: 12
1259
+ },
1260
+ children: [
1261
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [compact(model.calls), " 请求"] }),
1262
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [compact(model.tokens), " Token"] }),
1263
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: money(model.estimatedCostUsd) })
1264
+ ]
1265
+ })
1266
+ ]
1267
+ }, model.provider + "\0" + model.model)), (data?.models.length ?? 0) === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1268
+ style: muted,
1269
+ children: "暂无模型调用"
1270
+ })]
1271
+ })]
1272
+ })]
1273
+ })
1274
+ ] })
1275
+ ]
1276
+ })
1277
+ });
1278
+ }
406
1279
  const inject = ["slots", "connection"];
407
1280
  function apply(ctx) {
408
1281
  const connection = ctx.get("connection");
@@ -413,6 +1286,13 @@ window.__ModuleLoader__.load({
413
1286
  label: "LLM Verifier",
414
1287
  inject: () => ({ api: connection.api })
415
1288
  }, VerifierSettings));
1289
+ ctx.slots.inject("conversation.view", () => ctx.slots.register({
1290
+ name: "conversation.view",
1291
+ id: "llm-verifier-statistics",
1292
+ order: 30,
1293
+ label: "工具统计",
1294
+ inject: () => ({ rpc: connection.rpc })
1295
+ }, StatisticsPage));
416
1296
  }
417
1297
  //#endregion
418
1298
  exports.apply = apply;