dsh-llm-verifier 0.1.1 → 0.1.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/lib/client.js CHANGED
@@ -50,6 +50,28 @@ window.__ModuleLoader__.load({
50
50
  background: "var(--dsw-surface-sunken)",
51
51
  border: "1px solid var(--dsw-alias-border-l2, rgba(255, 255, 255, 0.16))"
52
52
  };
53
+ const dashboardCard = {
54
+ border: "1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.13))",
55
+ background: "color-mix(in srgb, var(--dsw-alias-bg-module, #171925) 88%, transparent)",
56
+ borderRadius: 16,
57
+ boxShadow: "0 12px 36px rgba(0,0,0,.12)"
58
+ };
59
+ const muted = {
60
+ color: "var(--dsw-text-secondary)",
61
+ fontSize: 12
62
+ };
63
+ const toolLabels = {
64
+ verifier_compare: "两项对比",
65
+ verifier_select: "多项优选",
66
+ verifier_track: "进度跟踪",
67
+ verifier_current_session: "会话验收"
68
+ };
69
+ const toolColors = {
70
+ verifier_compare: "#4f8cff",
71
+ verifier_select: "#8b6df6",
72
+ verifier_track: "#2fc5c9",
73
+ verifier_current_session: "#f5a524"
74
+ };
53
75
  function record(value) {
54
76
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
55
77
  }
@@ -85,6 +107,44 @@ window.__ModuleLoader__.load({
85
107
  children: help
86
108
  })] });
87
109
  }
110
+ function compact(value) {
111
+ return new Intl.NumberFormat("zh-CN", {
112
+ notation: value >= 1e4 ? "compact" : "standard",
113
+ maximumFractionDigits: value >= 1e3 ? 1 : 0
114
+ }).format(value);
115
+ }
116
+ function money(value) {
117
+ return "$" + value.toLocaleString("en-US", {
118
+ minimumFractionDigits: value < .01 ? 4 : 2,
119
+ maximumFractionDigits: value < .01 ? 4 : 2
120
+ });
121
+ }
122
+ function duration(value) {
123
+ if (value < 1e3) return Math.round(value) + " ms";
124
+ if (value < 6e4) return (value / 1e3).toFixed(1) + " s";
125
+ return (value / 6e4).toFixed(1) + " min";
126
+ }
127
+ function dateTime(value) {
128
+ return new Intl.DateTimeFormat("zh-CN", {
129
+ month: "2-digit",
130
+ day: "2-digit",
131
+ hour: "2-digit",
132
+ minute: "2-digit",
133
+ second: "2-digit"
134
+ }).format(value);
135
+ }
136
+ function startOfRange(days) {
137
+ const date = /* @__PURE__ */ new Date();
138
+ date.setHours(0, 0, 0, 0);
139
+ date.setDate(date.getDate() - days + 1);
140
+ return date.getTime();
141
+ }
142
+ function endOfToday() {
143
+ const date = /* @__PURE__ */ new Date();
144
+ date.setHours(0, 0, 0, 0);
145
+ date.setDate(date.getDate() + 1);
146
+ return date.getTime();
147
+ }
88
148
  function VerifierSettings({ api }) {
89
149
  const [loaded, setLoaded] = (0, react.useState)(null);
90
150
  const [draft, setDraft] = (0, react.useState)(null);
@@ -403,6 +463,748 @@ window.__ModuleLoader__.load({
403
463
  ]
404
464
  });
405
465
  }
466
+ function Metric({ label, value, note, accent }) {
467
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
468
+ style: {
469
+ ...dashboardCard,
470
+ padding: "16px 18px",
471
+ minWidth: 0
472
+ },
473
+ children: [
474
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
475
+ style: muted,
476
+ children: label
477
+ }),
478
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
479
+ style: {
480
+ fontSize: 25,
481
+ fontWeight: 750,
482
+ lineHeight: 1.2,
483
+ margin: "7px 0 5px",
484
+ color: accent
485
+ },
486
+ children: value
487
+ }),
488
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
489
+ style: {
490
+ ...muted,
491
+ overflow: "hidden",
492
+ textOverflow: "ellipsis",
493
+ whiteSpace: "nowrap"
494
+ },
495
+ children: note
496
+ })
497
+ ]
498
+ });
499
+ }
500
+ function TrendChart({ daily, days }) {
501
+ const rows = (0, react.useMemo)(() => {
502
+ const map = new Map(daily.map((row) => [row.date, row]));
503
+ const values = [];
504
+ const start = new Date(startOfRange(days));
505
+ for (let i = 0; i < days; i += 1) {
506
+ const date = new Date(start);
507
+ date.setDate(start.getDate() + i);
508
+ const key = [
509
+ date.getFullYear(),
510
+ String(date.getMonth() + 1).padStart(2, "0"),
511
+ String(date.getDate()).padStart(2, "0")
512
+ ].join("-");
513
+ values.push(map.get(key) ?? {
514
+ date: key,
515
+ invocations: 0,
516
+ successes: 0,
517
+ failures: 0,
518
+ calls: 0,
519
+ tokens: 0,
520
+ estimatedCostUsd: 0,
521
+ byTool: {}
522
+ });
523
+ }
524
+ return values;
525
+ }, [daily, days]);
526
+ const width = 760, height = 230, pad = {
527
+ l: 44,
528
+ r: 24,
529
+ t: 20,
530
+ b: 38
531
+ };
532
+ const innerW = width - pad.l - pad.r, innerH = height - pad.t - pad.b;
533
+ const max = Math.max(1, ...rows.flatMap((row) => [row.invocations, row.calls]));
534
+ const x = (index) => pad.l + (rows.length <= 1 ? innerW / 2 : index * innerW / (rows.length - 1));
535
+ const y = (value) => pad.t + innerH - value / max * innerH;
536
+ const points = rows.map((row, index) => `${x(index)},${y(row.calls)}`).join(" ");
537
+ const step = rows.length > 16 ? Math.ceil(rows.length / 7) : Math.max(1, Math.ceil(rows.length / 7));
538
+ const barWidth = Math.max(3, Math.min(18, innerW / Math.max(rows.length, 1) * .55));
539
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
540
+ style: {
541
+ width: "100%",
542
+ overflowX: "auto"
543
+ },
544
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
545
+ viewBox: `0 0 ${width} ${height}`,
546
+ style: {
547
+ display: "block",
548
+ width: "100%",
549
+ minWidth: 620,
550
+ height: "auto"
551
+ },
552
+ "aria-label": "每日工具调用与模型请求趋势",
553
+ children: [
554
+ [
555
+ 0,
556
+ .25,
557
+ .5,
558
+ .75,
559
+ 1
560
+ ].map((ratio) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("line", {
561
+ x1: pad.l,
562
+ x2: width - pad.r,
563
+ y1: pad.t + innerH * ratio,
564
+ y2: pad.t + innerH * ratio,
565
+ stroke: "rgba(148,163,184,.16)"
566
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
567
+ x: pad.l - 8,
568
+ y: pad.t + innerH * ratio + 4,
569
+ textAnchor: "end",
570
+ fontSize: "10",
571
+ fill: "var(--dsw-text-secondary)",
572
+ children: Math.round(max * (1 - ratio))
573
+ })] }, ratio)),
574
+ rows.map((row, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
575
+ x: x(index) - barWidth / 2,
576
+ y: y(row.invocations),
577
+ width: barWidth,
578
+ height: pad.t + innerH - y(row.invocations),
579
+ rx: "2",
580
+ fill: "#4f8cff",
581
+ opacity: ".82"
582
+ }, row.date)),
583
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("polyline", {
584
+ points,
585
+ fill: "none",
586
+ stroke: "#5ed7e8",
587
+ strokeWidth: "2.4",
588
+ strokeLinejoin: "round",
589
+ strokeLinecap: "round"
590
+ }),
591
+ rows.map((row, index) => index % step === 0 || index === rows.length - 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("text", {
592
+ x: x(index),
593
+ y: height - 14,
594
+ textAnchor: "middle",
595
+ fontSize: "10",
596
+ fill: "var(--dsw-text-secondary)",
597
+ children: [
598
+ Number(row.date.slice(5, 7)),
599
+ "/",
600
+ Number(row.date.slice(8, 10))
601
+ ]
602
+ }, row.date) : null)
603
+ ]
604
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
605
+ style: {
606
+ display: "flex",
607
+ justifyContent: "center",
608
+ gap: 18,
609
+ ...muted
610
+ },
611
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { style: {
612
+ display: "inline-block",
613
+ width: 8,
614
+ height: 8,
615
+ borderRadius: 2,
616
+ background: "#4f8cff",
617
+ marginRight: 6
618
+ } }), "工具调用"] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { style: {
619
+ display: "inline-block",
620
+ width: 14,
621
+ height: 2,
622
+ background: "#5ed7e8",
623
+ marginRight: 6,
624
+ verticalAlign: "middle"
625
+ } }), "模型请求"] })]
626
+ })]
627
+ });
628
+ }
629
+ function StatisticsPage({ sessionId, rpc }) {
630
+ const [days, setDays] = (0, react.useState)(30);
631
+ const [sessionOnly, setSessionOnly] = (0, react.useState)(false);
632
+ const [data, setData] = (0, react.useState)(null);
633
+ const [loading, setLoading] = (0, react.useState)(true);
634
+ const [error, setError] = (0, react.useState)(null);
635
+ const [refresh, setRefresh] = (0, react.useState)(0);
636
+ (0, react.useEffect)(() => {
637
+ const controller = new AbortController();
638
+ setLoading(true);
639
+ setError(null);
640
+ rpc.call("/llm-verifier", "statistics", {
641
+ fromMs: startOfRange(days),
642
+ toMs: endOfToday(),
643
+ timezoneOffsetMinutes: (/* @__PURE__ */ new Date()).getTimezoneOffset(),
644
+ recentLimit: 50,
645
+ ...sessionOnly ? { sessionId: String(sessionId) } : {}
646
+ }, controller.signal).then((result) => {
647
+ if (!result.ok) throw new Error(result.error?.message ?? "统计接口请求失败");
648
+ setData(result.value);
649
+ }, (cause) => {
650
+ if (!controller.signal.aborted) throw cause;
651
+ }).catch((cause) => {
652
+ if (!controller.signal.aborted) setError(message(cause));
653
+ }).finally(() => {
654
+ if (!controller.signal.aborted) setLoading(false);
655
+ });
656
+ return () => controller.abort();
657
+ }, [
658
+ days,
659
+ sessionOnly,
660
+ sessionId,
661
+ refresh,
662
+ rpc
663
+ ]);
664
+ const totals = data?.totals;
665
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("main", {
666
+ style: {
667
+ height: "100%",
668
+ overflow: "auto",
669
+ boxSizing: "border-box",
670
+ padding: "22px clamp(16px, 3vw, 38px) 48px",
671
+ color: "var(--dsw-text-primary)",
672
+ 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%)"
673
+ },
674
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
675
+ style: {
676
+ maxWidth: 1180,
677
+ margin: "0 auto",
678
+ display: "flex",
679
+ flexDirection: "column",
680
+ gap: 16
681
+ },
682
+ children: [
683
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
684
+ style: {
685
+ display: "flex",
686
+ justifyContent: "space-between",
687
+ alignItems: "flex-start",
688
+ gap: 16,
689
+ flexWrap: "wrap"
690
+ },
691
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
692
+ style: {
693
+ display: "flex",
694
+ alignItems: "center",
695
+ gap: 10
696
+ },
697
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
698
+ style: {
699
+ display: "grid",
700
+ placeItems: "center",
701
+ width: 34,
702
+ height: 34,
703
+ borderRadius: 10,
704
+ background: "rgba(79,140,255,.14)",
705
+ color: "#6da0ff"
706
+ },
707
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDataOutline16, { size: 18 })
708
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
709
+ style: {
710
+ margin: 0,
711
+ fontSize: 23
712
+ },
713
+ children: "Verifier 工具统计"
714
+ })]
715
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
716
+ style: {
717
+ margin: "7px 0 0 44px",
718
+ ...muted
719
+ },
720
+ children: [
721
+ "数据更新于 ",
722
+ data ? dateTime(data.generatedAt) : "--",
723
+ " · 自动记录四个验证工具的实际执行结果"
724
+ ]
725
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
726
+ style: {
727
+ display: "flex",
728
+ gap: 8,
729
+ alignItems: "center",
730
+ flexWrap: "wrap"
731
+ },
732
+ children: [
733
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
734
+ style: {
735
+ display: "flex",
736
+ padding: 3,
737
+ borderRadius: 10,
738
+ background: "var(--dsw-surface-sunken)",
739
+ border: "1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.12))"
740
+ },
741
+ children: [
742
+ 7,
743
+ 30,
744
+ 90
745
+ ].map((value) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
746
+ onClick: () => setDays(value),
747
+ style: {
748
+ border: 0,
749
+ borderRadius: 7,
750
+ padding: "6px 10px",
751
+ cursor: "pointer",
752
+ color: days === value ? "#fff" : "var(--dsw-text-secondary)",
753
+ background: days === value ? "#3f68d8" : "transparent"
754
+ },
755
+ children: [value, " 天"]
756
+ }, value))
757
+ }),
758
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
759
+ onClick: () => setSessionOnly((value) => !value),
760
+ style: {
761
+ border: "1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.15))",
762
+ borderRadius: 9,
763
+ padding: "7px 11px",
764
+ cursor: "pointer",
765
+ color: "var(--dsw-text-primary)",
766
+ background: sessionOnly ? "rgba(79,140,255,.18)" : "var(--dsw-surface-sunken)"
767
+ },
768
+ children: sessionOnly ? "当前会话" : "全部会话"
769
+ }),
770
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
771
+ title: "刷新",
772
+ onClick: () => setRefresh((value) => value + 1),
773
+ style: {
774
+ display: "grid",
775
+ placeItems: "center",
776
+ width: 34,
777
+ height: 34,
778
+ borderRadius: 9,
779
+ border: "1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.15))",
780
+ color: "var(--dsw-text-primary)",
781
+ background: "var(--dsw-surface-sunken)",
782
+ cursor: "pointer"
783
+ },
784
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16, { size: 16 })
785
+ })
786
+ ]
787
+ })]
788
+ }),
789
+ error && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
790
+ style: {
791
+ ...dashboardCard,
792
+ padding: 18,
793
+ borderColor: "var(--dsw-danger, #e85858)",
794
+ color: "var(--dsw-danger, #e85858)"
795
+ },
796
+ children: [error, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
797
+ style: {
798
+ ...muted,
799
+ marginTop: 6
800
+ },
801
+ children: "请确认宿主已重启并加载最新版本的 dsh-llm-verifier。"
802
+ })]
803
+ }),
804
+ loading && !data ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
805
+ style: {
806
+ ...dashboardCard,
807
+ padding: 32,
808
+ textAlign: "center",
809
+ ...muted
810
+ },
811
+ children: "正在读取工具运行统计…"
812
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
813
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
814
+ style: {
815
+ ...dashboardCard,
816
+ padding: "22px 24px",
817
+ display: "grid",
818
+ gridTemplateColumns: "minmax(220px,1.4fr) minmax(240px,1fr)",
819
+ gap: 24,
820
+ alignItems: "center"
821
+ },
822
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
823
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
824
+ style: muted,
825
+ children: [days, " 天估算费用"]
826
+ }),
827
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
828
+ style: {
829
+ fontSize: 38,
830
+ fontWeight: 780,
831
+ letterSpacing: "-.03em",
832
+ margin: "5px 0"
833
+ },
834
+ children: money(totals?.estimatedCostUsd ?? 0)
835
+ }),
836
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
837
+ style: muted,
838
+ children: [
839
+ compact(totals?.invocations ?? 0),
840
+ " 次工具调用 · ",
841
+ compact(totals?.calls ?? 0),
842
+ " 次裁判模型请求"
843
+ ]
844
+ })
845
+ ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
846
+ style: {
847
+ display: "grid",
848
+ gridTemplateColumns: "1fr auto",
849
+ gap: "9px 16px",
850
+ fontSize: 13
851
+ },
852
+ children: [
853
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
854
+ style: muted,
855
+ children: "成功调用"
856
+ }),
857
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("strong", { children: [
858
+ compact(totals?.successes ?? 0),
859
+ " ",
860
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("small", {
861
+ style: { color: "#77d49b" },
862
+ children: [
863
+ "▲ ",
864
+ ((totals?.successRate ?? 0) * 100).toFixed(1),
865
+ "%"
866
+ ]
867
+ })
868
+ ] }),
869
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
870
+ style: muted,
871
+ children: "失败调用"
872
+ }),
873
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: compact(totals?.failures ?? 0) }),
874
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
875
+ style: muted,
876
+ children: "平均耗时"
877
+ }),
878
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: duration(totals?.averageDurationMs ?? 0) })
879
+ ]
880
+ })]
881
+ }),
882
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
883
+ style: {
884
+ display: "grid",
885
+ gridTemplateColumns: "repeat(auto-fit,minmax(180px,1fr))",
886
+ gap: 12
887
+ },
888
+ children: [
889
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
890
+ label: "缓存命中率",
891
+ value: ((totals?.cacheHitRate ?? 0) * 100).toFixed(1) + "%",
892
+ note: `${compact(totals?.cacheHits ?? 0)} 命中 / ${compact((totals?.cacheHits ?? 0) + (totals?.cacheMisses ?? 0))} 评分`,
893
+ accent: "#b7dd64"
894
+ }),
895
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
896
+ label: "Token",
897
+ value: compact(totals?.tokens ?? 0),
898
+ note: `输入 ${compact((totals?.inputTokens ?? 0) + (totals?.cachedInputTokens ?? 0))} · 输出 ${compact(totals?.outputTokens ?? 0)}`
899
+ }),
900
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
901
+ label: "平均模型请求",
902
+ value: (totals?.invocations ?? 0) > 0 ? ((totals?.calls ?? 0) / (totals?.invocations ?? 1)).toFixed(1) : "0",
903
+ note: `${compact(totals?.attempts ?? 0)} 次尝试 · ${compact(totals?.retries ?? 0)} 次重试`
904
+ }),
905
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Metric, {
906
+ label: "评分模式",
907
+ value: compact(totals?.topLogprobScores ?? 0),
908
+ note: `Top-logprobs · 显式标签 ${compact(totals?.explicitTagScores ?? 0)}`
909
+ })
910
+ ]
911
+ }),
912
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
913
+ style: {
914
+ ...dashboardCard,
915
+ padding: "18px 20px 16px"
916
+ },
917
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
918
+ style: {
919
+ display: "flex",
920
+ justifyContent: "space-between",
921
+ alignItems: "center",
922
+ marginBottom: 4
923
+ },
924
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "每日工具调用与模型请求趋势" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
925
+ style: muted,
926
+ children: sessionOnly ? "当前会话" : "全部会话"
927
+ })]
928
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrendChart, {
929
+ daily: data?.daily ?? [],
930
+ days
931
+ })]
932
+ }),
933
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
934
+ style: {
935
+ ...dashboardCard,
936
+ padding: "18px 18px 8px",
937
+ overflow: "hidden"
938
+ },
939
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
940
+ style: {
941
+ display: "flex",
942
+ justifyContent: "space-between",
943
+ margin: "0 2px 12px"
944
+ },
945
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "工具运行明细" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
946
+ style: muted,
947
+ children: [data?.tools.length ?? 0, " 类工具"]
948
+ })]
949
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
950
+ style: { overflowX: "auto" },
951
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
952
+ style: {
953
+ width: "100%",
954
+ borderCollapse: "collapse",
955
+ fontSize: 13,
956
+ minWidth: 760
957
+ },
958
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", {
959
+ style: {
960
+ textAlign: "left",
961
+ color: "var(--dsw-text-secondary)",
962
+ background: "var(--dsw-surface-sunken)"
963
+ },
964
+ children: [
965
+ "工具",
966
+ "调用",
967
+ "成功率",
968
+ "平均耗时",
969
+ "模型请求",
970
+ "Token",
971
+ "缓存命中",
972
+ "估算费用"
973
+ ].map((value) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
974
+ style: {
975
+ padding: "10px 12px",
976
+ fontWeight: 500
977
+ },
978
+ children: value
979
+ }, value))
980
+ }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tbody", { children: [(data?.tools ?? []).map((tool) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", {
981
+ style: { borderTop: "1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.1))" },
982
+ children: [
983
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", {
984
+ style: { padding: "13px 12px" },
985
+ children: [
986
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
987
+ display: "inline-block",
988
+ width: 8,
989
+ height: 8,
990
+ borderRadius: "50%",
991
+ background: toolColors[tool.toolName] ?? "#8691a8",
992
+ marginRight: 8
993
+ } }),
994
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: toolLabels[tool.toolName] ?? tool.toolName }),
995
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
996
+ style: {
997
+ ...muted,
998
+ margin: "3px 0 0 16px"
999
+ },
1000
+ children: tool.toolName
1001
+ })
1002
+ ]
1003
+ }),
1004
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1005
+ style: { padding: "13px 12px" },
1006
+ children: compact(tool.invocations)
1007
+ }),
1008
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", {
1009
+ style: {
1010
+ padding: "13px 12px",
1011
+ color: tool.successRate >= .9 ? "#77d49b" : tool.successRate >= .7 ? "#e3bd63" : "#ed7777"
1012
+ },
1013
+ children: [(tool.successRate * 100).toFixed(1), "%"]
1014
+ }),
1015
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1016
+ style: { padding: "13px 12px" },
1017
+ children: duration(tool.averageDurationMs)
1018
+ }),
1019
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1020
+ style: { padding: "13px 12px" },
1021
+ children: compact(tool.calls)
1022
+ }),
1023
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1024
+ style: { padding: "13px 12px" },
1025
+ children: compact(tool.tokens)
1026
+ }),
1027
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", {
1028
+ style: { padding: "13px 12px" },
1029
+ children: [
1030
+ tool.cacheHits,
1031
+ "/",
1032
+ tool.cacheHits + tool.cacheMisses
1033
+ ]
1034
+ }),
1035
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1036
+ style: { padding: "13px 12px" },
1037
+ children: money(tool.estimatedCostUsd)
1038
+ })
1039
+ ]
1040
+ }, tool.toolName)), (data?.tools.length ?? 0) === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1041
+ colSpan: 8,
1042
+ style: {
1043
+ padding: 28,
1044
+ textAlign: "center",
1045
+ ...muted
1046
+ },
1047
+ children: "所选范围内还没有 Verifier 工具调用。后续执行会自动出现在这里。"
1048
+ }) })] })]
1049
+ })
1050
+ })]
1051
+ }),
1052
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
1053
+ style: {
1054
+ display: "grid",
1055
+ gridTemplateColumns: "minmax(0,1fr) minmax(290px,.45fr)",
1056
+ gap: 16,
1057
+ alignItems: "start"
1058
+ },
1059
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1060
+ style: {
1061
+ ...dashboardCard,
1062
+ padding: "18px 18px 8px",
1063
+ overflow: "hidden"
1064
+ },
1065
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1066
+ style: {
1067
+ display: "flex",
1068
+ justifyContent: "space-between",
1069
+ margin: "0 2px 12px"
1070
+ },
1071
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "最近调用" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1072
+ style: muted,
1073
+ children: "最多 50 条"
1074
+ })]
1075
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1076
+ style: {
1077
+ maxHeight: 360,
1078
+ overflow: "auto"
1079
+ },
1080
+ children: [(data?.recent ?? []).map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1081
+ style: {
1082
+ display: "grid",
1083
+ gridTemplateColumns: "minmax(170px,1fr) auto",
1084
+ gap: 12,
1085
+ padding: "11px 8px",
1086
+ borderTop: "1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.1))"
1087
+ },
1088
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1089
+ style: {
1090
+ display: "flex",
1091
+ alignItems: "center",
1092
+ gap: 8
1093
+ },
1094
+ children: [
1095
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
1096
+ width: 7,
1097
+ height: 7,
1098
+ borderRadius: "50%",
1099
+ background: item.success ? "#59c985" : "#e76565"
1100
+ } }),
1101
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", {
1102
+ style: { fontSize: 13 },
1103
+ children: toolLabels[item.toolName] ?? item.toolName
1104
+ }),
1105
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1106
+ style: muted,
1107
+ children: [
1108
+ item.provider,
1109
+ "/",
1110
+ item.model
1111
+ ]
1112
+ })
1113
+ ]
1114
+ }), !item.success && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1115
+ title: item.errorMessage,
1116
+ style: {
1117
+ margin: "5px 0 0 15px",
1118
+ fontSize: 11,
1119
+ color: "#e76565",
1120
+ overflow: "hidden",
1121
+ textOverflow: "ellipsis",
1122
+ whiteSpace: "nowrap"
1123
+ },
1124
+ children: item.errorMessage ?? item.errorName
1125
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1126
+ style: { textAlign: "right" },
1127
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1128
+ style: { fontSize: 12 },
1129
+ children: duration(item.durationMs)
1130
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1131
+ style: {
1132
+ ...muted,
1133
+ marginTop: 3
1134
+ },
1135
+ children: dateTime(item.startedAt)
1136
+ })]
1137
+ })]
1138
+ }, item.id)), (data?.recent.length ?? 0) === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1139
+ style: {
1140
+ padding: 24,
1141
+ textAlign: "center",
1142
+ ...muted
1143
+ },
1144
+ children: "暂无记录"
1145
+ })]
1146
+ })]
1147
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1148
+ style: {
1149
+ ...dashboardCard,
1150
+ padding: "18px"
1151
+ },
1152
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "模型汇总" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1153
+ style: {
1154
+ marginTop: 10,
1155
+ display: "flex",
1156
+ flexDirection: "column",
1157
+ gap: 10
1158
+ },
1159
+ children: [(data?.models ?? []).map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1160
+ style: {
1161
+ padding: "11px 12px",
1162
+ borderRadius: 10,
1163
+ background: "var(--dsw-surface-sunken)"
1164
+ },
1165
+ children: [
1166
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1167
+ style: {
1168
+ fontWeight: 650,
1169
+ fontSize: 13,
1170
+ overflow: "hidden",
1171
+ textOverflow: "ellipsis"
1172
+ },
1173
+ children: model.model
1174
+ }),
1175
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1176
+ style: {
1177
+ ...muted,
1178
+ marginTop: 3
1179
+ },
1180
+ children: model.provider
1181
+ }),
1182
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1183
+ style: {
1184
+ display: "flex",
1185
+ justifyContent: "space-between",
1186
+ marginTop: 9,
1187
+ fontSize: 12
1188
+ },
1189
+ children: [
1190
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [compact(model.calls), " 请求"] }),
1191
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [compact(model.tokens), " Token"] }),
1192
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: money(model.estimatedCostUsd) })
1193
+ ]
1194
+ })
1195
+ ]
1196
+ }, model.provider + "\0" + model.model)), (data?.models.length ?? 0) === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1197
+ style: muted,
1198
+ children: "暂无模型调用"
1199
+ })]
1200
+ })]
1201
+ })]
1202
+ })
1203
+ ] })
1204
+ ]
1205
+ })
1206
+ });
1207
+ }
406
1208
  const inject = ["slots", "connection"];
407
1209
  function apply(ctx) {
408
1210
  const connection = ctx.get("connection");
@@ -413,6 +1215,13 @@ window.__ModuleLoader__.load({
413
1215
  label: "LLM Verifier",
414
1216
  inject: () => ({ api: connection.api })
415
1217
  }, VerifierSettings));
1218
+ ctx.slots.inject("conversation.view", () => ctx.slots.register({
1219
+ name: "conversation.view",
1220
+ id: "llm-verifier-statistics",
1221
+ order: 30,
1222
+ label: "工具统计",
1223
+ inject: () => ({ rpc: connection.rpc })
1224
+ }, StatisticsPage));
416
1225
  }
417
1226
  //#endregion
418
1227
  exports.apply = apply;