token-speed-tester 1.6.0 → 1.7.0

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/dist/index.js CHANGED
@@ -1,11 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { readFileSync } from "fs";
5
- import { dirname, join } from "path";
6
- import { fileURLToPath } from "url";
4
+ import { readFileSync as readFileSync2 } from "fs";
5
+ import { writeFile as fsWriteFile } from "fs/promises";
6
+ import { dirname as dirname2, join } from "path";
7
+ import { fileURLToPath as fileURLToPath2 } from "url";
7
8
  import { Command } from "commander";
8
9
  import chalk from "chalk";
10
+ import open from "open";
9
11
 
10
12
  // src/i18n.ts
11
13
  var SUPPORTED_LANGS = ["zh", "en"];
@@ -55,7 +57,27 @@ var zhMessages = {
55
57
  averageSpeed: "\u5E73\u5747\u901F\u5EA6",
56
58
  peakSpeed: "\u5CF0\u503C\u901F\u5EA6",
57
59
  peakTps: "\u5CF0\u503C TPS"
58
- }
60
+ },
61
+ htmlTitle: "Token \u901F\u5EA6\u6D4B\u8BD5\u62A5\u544A",
62
+ htmlReportTitle: "LLM API Token \u6D41\u5F0F\u6027\u80FD\u6D4B\u8BD5\u62A5\u544A",
63
+ htmlGenerated: (file) => `\u2713 HTML \u62A5\u544A\u5DF2\u751F\u6210: ${file}`,
64
+ htmlOpenError: (file) => `\u65E0\u6CD5\u81EA\u52A8\u6253\u5F00\u62A5\u544A\uFF0C\u8BF7\u624B\u52A8\u6253\u5F00: ${file}`,
65
+ htmlConfigSection: "\u6D4B\u8BD5\u914D\u7F6E",
66
+ htmlSummarySection: "\u7EDF\u8BA1\u6C47\u603B",
67
+ htmlChartsSection: "\u56FE\u8868\u5206\u6790",
68
+ htmlDetailsSection: "\u8BE6\u7EC6\u6570\u636E",
69
+ htmlTestTime: "\u6D4B\u8BD5\u65F6\u95F4",
70
+ htmlMetric: "\u6307\u6807",
71
+ htmlValue: "\u6570\u503C",
72
+ htmlRun: "\u8FD0\u884C",
73
+ htmlTokens: "Token \u6570",
74
+ htmlDuration: "\u8017\u65F6",
75
+ htmlSpeed: "\u901F\u5EA6",
76
+ htmlTps: "TPS",
77
+ htmlAverageTps: "\u5E73\u5747 TPS",
78
+ htmlTpsDistribution: "TPS \u5206\u5E03",
79
+ htmlTimeUnit: "\u79D2",
80
+ htmlTpsChartHover: "\u6B21"
59
81
  };
60
82
  var enMessages = {
61
83
  defaultPrompt: "Write a short essay about AI",
@@ -102,7 +124,27 @@ var enMessages = {
102
124
  averageSpeed: "Avg Speed",
103
125
  peakSpeed: "Peak Speed",
104
126
  peakTps: "Peak TPS"
105
- }
127
+ },
128
+ htmlTitle: "Token Speed Test Report",
129
+ htmlReportTitle: "LLM API Token Streaming Performance Report",
130
+ htmlGenerated: (file) => `\u2713 HTML report generated: ${file}`,
131
+ htmlOpenError: (file) => `Could not auto-open report, please open manually: ${file}`,
132
+ htmlConfigSection: "Test Configuration",
133
+ htmlSummarySection: "Summary Statistics",
134
+ htmlChartsSection: "Chart Analysis",
135
+ htmlDetailsSection: "Detailed Data",
136
+ htmlTestTime: "Test Time",
137
+ htmlMetric: "Metric",
138
+ htmlValue: "Value",
139
+ htmlRun: "Run",
140
+ htmlTokens: "Tokens",
141
+ htmlDuration: "Duration",
142
+ htmlSpeed: "Speed",
143
+ htmlTps: "TPS",
144
+ htmlAverageTps: "Average TPS",
145
+ htmlTpsDistribution: "TPS Distribution",
146
+ htmlTimeUnit: "s",
147
+ htmlTpsChartHover: "count"
106
148
  };
107
149
  function isSupportedLang(value) {
108
150
  return SUPPORTED_LANGS.includes(value);
@@ -694,12 +736,500 @@ function renderReport(stats, lang = DEFAULT_LANG) {
694
736
  return lines.join("\n");
695
737
  }
696
738
 
739
+ // src/html-report.ts
740
+ import { readFileSync } from "fs";
741
+ import { dirname, resolve } from "path";
742
+ import { fileURLToPath } from "url";
743
+ var CHART_COLORS = [
744
+ "#00f5ff",
745
+ // cyan
746
+ "#ff00aa",
747
+ // magenta
748
+ "#ffcc00",
749
+ // yellow
750
+ "#00ff88",
751
+ // green
752
+ "#ff6600",
753
+ // orange
754
+ "#aa00ff"
755
+ // purple
756
+ ];
757
+ var PALETTE = {
758
+ bg: "#0a0a0f",
759
+ bgSecondary: "#12121a",
760
+ bgCard: "#1a1a24",
761
+ border: "#2a2a3a",
762
+ text: "#e4e4eb",
763
+ textMuted: "#6a6a7a",
764
+ accent: "#00f5ff",
765
+ accentSecondary: "#ff00aa",
766
+ accentTertiary: "#ffcc00"
767
+ };
768
+ function formatNumber(num, decimals = 2) {
769
+ return num.toFixed(decimals);
770
+ }
771
+ function formatTime(ms) {
772
+ if (ms < 1e3) {
773
+ return `${ms.toFixed(0)}ms`;
774
+ }
775
+ return `${(ms / 1e3).toFixed(2)}s`;
776
+ }
777
+ function escapeHtml(text) {
778
+ const map = {
779
+ "&": "&amp;",
780
+ "<": "&lt;",
781
+ ">": "&gt;",
782
+ '"': "&quot;",
783
+ "'": "&#039;"
784
+ };
785
+ return text.replace(/[&<>"']/g, (m) => map[m]);
786
+ }
787
+ function loadTemplate() {
788
+ const __filename = fileURLToPath(import.meta.url);
789
+ const __dirname = dirname(__filename);
790
+ return readFileSync(resolve(__dirname, "template.html"), "utf-8");
791
+ }
792
+ function replaceTemplate(template, data) {
793
+ let result = template;
794
+ for (const [key, value] of Object.entries(data)) {
795
+ result = result.replaceAll(new RegExp(`{{${key}}}`, "g"), value);
796
+ }
797
+ return result;
798
+ }
799
+ function generateSpeedChart(results, messages) {
800
+ const allTps = results.flatMap((r) => r.tps);
801
+ if (allTps.length === 0) {
802
+ return `<div class="no-data">${messages.noChartData || "No data available"}</div>`;
803
+ }
804
+ const maxTps = Math.max(...allTps, 1);
805
+ const maxDuration = Math.max(...results.map((r) => r.tps.length));
806
+ const width = 800;
807
+ const height = 320;
808
+ const padding = { top: 30, right: 30, bottom: 45, left: 55 };
809
+ const chartWidth = width - padding.left - padding.right;
810
+ const chartHeight = height - padding.top - padding.bottom;
811
+ const avgTps = [];
812
+ for (let i = 0; i < maxDuration; i++) {
813
+ const values = results.map((r) => r.tps[i] ?? 0);
814
+ avgTps.push(values.reduce((a, b) => a + b, 0) / values.length);
815
+ }
816
+ const polylines = results.map((result, idx) => {
817
+ const color = CHART_COLORS[idx % CHART_COLORS.length];
818
+ let points = "";
819
+ let areaPoints = `${padding.left},${height - padding.bottom} `;
820
+ result.tps.forEach((tps, i) => {
821
+ const x = padding.left + i / Math.max(maxDuration - 1, 1) * chartWidth;
822
+ const y = padding.top + chartHeight - tps / maxTps * chartHeight;
823
+ points += `${x},${y} `;
824
+ areaPoints += `${x},${y} `;
825
+ });
826
+ areaPoints += `${padding.left + chartWidth},${height - padding.bottom}`;
827
+ return `
828
+ <defs>
829
+ <linearGradient id="grad-${idx}" x1="0%" y1="0%" x2="0%" y2="100%">
830
+ <stop offset="0%" style="stop-color:${color};stop-opacity:0.3"/>
831
+ <stop offset="100%" style="stop-color:${color};stop-opacity:0"/>
832
+ </linearGradient>
833
+ </defs>
834
+ <polygon
835
+ points="${areaPoints.trim()}"
836
+ fill="url(#grad-${idx})"
837
+ class="area-${idx}"
838
+ />
839
+ <polyline
840
+ fill="none"
841
+ stroke="${color}"
842
+ stroke-width="2.5"
843
+ points="${points.trim()}"
844
+ class="line line-${idx}"
845
+ data-run="${idx + 1}"
846
+ >
847
+ <animate
848
+ attributeName="stroke-dasharray"
849
+ from="0,2000"
850
+ to="2000,0"
851
+ dur="1.5s"
852
+ fill="freeze"
853
+ calcMode="spline"
854
+ keySplines="0.4 0 0.2 1"
855
+ />
856
+ </polyline>
857
+ ${result.tps.map((tps, i) => {
858
+ const x = padding.left + i / Math.max(maxDuration - 1, 1) * chartWidth;
859
+ const y = padding.top + chartHeight - tps / maxTps * chartHeight;
860
+ return `<circle cx="${x}" cy="${y}" r="4" fill="${PALETTE.bg}" stroke="${color}" stroke-width="2" class="dot-${idx}" opacity="0"><title>${messages.htmlAverageTps || "Avg TPS"} ${i}s: ${tps.toFixed(1)}</title>
861
+ <animate attributeName="opacity" from="0" to="1" begin="${0.5 + i * 0.05}s" dur="0.3s" fill="freeze"/>
862
+ </circle>`;
863
+ }).join("")}
864
+ `;
865
+ }).join("\n ");
866
+ let avgPoints = "";
867
+ avgTps.forEach((tps, i) => {
868
+ const x = padding.left + i / Math.max(maxDuration - 1, 1) * chartWidth;
869
+ const y = padding.top + chartHeight - tps / maxTps * chartHeight;
870
+ avgPoints += `${x},${y} `;
871
+ });
872
+ const yLabels = [];
873
+ for (let i = 0; i <= 5; i++) {
874
+ const value = Math.round(maxTps * i / 5);
875
+ const y = padding.top + chartHeight - i / 5 * chartHeight;
876
+ yLabels.push(
877
+ `<text x="${padding.left - 12}" y="${y + 4}" text-anchor="end" font-size="11" fill="${PALETTE.textMuted}">${value}</text>`
878
+ );
879
+ if (i > 0) {
880
+ const yLine = padding.top + chartHeight - i / 5 * chartHeight;
881
+ yLabels.push(
882
+ `<line x1="${padding.left}" y1="${yLine}" x2="${width - padding.right}" y2="${yLine}" stroke="${PALETTE.border}" stroke-width="1" opacity="0.5"/>`
883
+ );
884
+ }
885
+ }
886
+ const xLabels = [];
887
+ const xSteps = Math.min(maxDuration, 10);
888
+ for (let i = 0; i < xSteps; i++) {
889
+ const x = padding.left + i / Math.max(xSteps - 1, 1) * chartWidth;
890
+ const label = i.toString();
891
+ xLabels.push(
892
+ `<text x="${x}" y="${height - padding.bottom + 20}" text-anchor="middle" font-size="11" fill="${PALETTE.textMuted}">${label}${messages.htmlTimeUnit}</text>`
893
+ );
894
+ }
895
+ return `
896
+ <svg viewBox="0 0 ${width} ${height}" class="chart" id="speedChart">
897
+ <style>
898
+ #speedChart .line { stroke-dasharray: 2000; stroke-dashoffset: 0; }
899
+ #speedChart .line:hover { stroke-width: 4; filter: drop-shadow(0 0 8px currentColor); }
900
+ #speedChart circle { transition: all 0.2s ease; cursor: pointer; }
901
+ #speedChart circle:hover { r: 6; stroke-width: 3; }
902
+ </style>
903
+ <rect x="${padding.left}" y="${padding.top}" width="${chartWidth}" height="${chartHeight}" fill="${PALETTE.bgCard}" rx="4"/>
904
+ ${yLabels.join("\n ")}
905
+ ${xLabels.join("\n ")}
906
+ <line x1="${padding.left}" y1="${padding.top}" x2="${padding.left}" y2="${height - padding.bottom}" stroke="${PALETTE.border}" stroke-width="2"/>
907
+ <line x1="${padding.left}" y1="${height - padding.bottom}" x2="${width - padding.right}" y2="${height - padding.bottom}" stroke="${PALETTE.border}" stroke-width="2"/>
908
+ ${polylines}
909
+ <polyline
910
+ fill="none"
911
+ stroke="${PALETTE.text}"
912
+ stroke-width="2"
913
+ stroke-dasharray="6,4"
914
+ opacity="0.7"
915
+ points="${avgPoints.trim()}"
916
+ />
917
+ <text x="${padding.left + chartWidth / 2}" y="${height - 8}" text-anchor="middle" font-size="11" fill="${PALETTE.textMuted}">TIME (${messages.htmlTimeUnit})</text>
918
+ <text x="12" y="${padding.top + chartHeight / 2}" text-anchor="middle" font-size="11" fill="${PALETTE.textMuted}" transform="rotate(-90, 12, ${padding.top + chartHeight / 2})">TPS</text>
919
+ </svg>
920
+ <div class="chart-legend">
921
+ <div class="legend-item">
922
+ <span class="legend-line avg"></span>
923
+ <span>${messages.htmlSummarySection}</span>
924
+ </div>
925
+ ${results.map((_, idx) => {
926
+ const color = CHART_COLORS[idx % CHART_COLORS.length];
927
+ return `
928
+ <div class="legend-item">
929
+ <span class="legend-line" style="background: ${color};"></span>
930
+ <span>${messages.htmlRun} ${idx + 1}</span>
931
+ </div>`;
932
+ }).join("")}
933
+ </div>
934
+ `;
935
+ }
936
+ function generateTPSHistogram(stats, messages) {
937
+ const allTps = stats.mean.tps;
938
+ if (allTps.length === 0) {
939
+ return `<div class="no-data">${messages.noTpsData || "No TPS data available"}</div>`;
940
+ }
941
+ const maxTps = Math.max(...allTps);
942
+ const width = 400;
943
+ const height = 280;
944
+ const padding = { top: 25, right: 20, bottom: 40, left: 50 };
945
+ const chartWidth = width - padding.left - padding.right;
946
+ const chartHeight = height - padding.top - padding.bottom;
947
+ const bars = allTps.map((tps, i) => {
948
+ const barWidth = chartWidth / allTps.length - 2;
949
+ const x = padding.left + i / allTps.length * chartWidth;
950
+ const barHeight = tps / maxTps * chartHeight;
951
+ const y = padding.top + chartHeight - barHeight;
952
+ const hue = 180 + tps / maxTps * 60;
953
+ const color = `hsl(${hue}, 100%, 60%)`;
954
+ return `
955
+ <rect
956
+ x="${x}"
957
+ y="${y}"
958
+ width="${barWidth}"
959
+ height="${barHeight}"
960
+ fill="${color}"
961
+ class="bar"
962
+ data-second="${i}"
963
+ data-tps="${tps.toFixed(2)}"
964
+ rx="2"
965
+ >
966
+ <title>${messages.htmlAverageTps || "Average TPS"} ${i}s: ${tps.toFixed(1)}</title>
967
+ <animate
968
+ attributeName="height"
969
+ from="0"
970
+ to="${barHeight}"
971
+ dur="0.8s"
972
+ fill="freeze"
973
+ calcMode="spline"
974
+ keySplines="0.4 0 0.2 1"
975
+ begin="${i * 0.05}s"
976
+ />
977
+ <animate
978
+ attributeName="y"
979
+ from="${height - padding.bottom}"
980
+ to="${y}"
981
+ dur="0.8s"
982
+ fill="freeze"
983
+ calcMode="spline"
984
+ keySplines="0.4 0 0.2 1"
985
+ begin="${i * 0.05}s"
986
+ />
987
+ </rect>
988
+ `;
989
+ }).join("");
990
+ const yLabels = [];
991
+ for (let i = 0; i <= 5; i++) {
992
+ const value = Math.round(maxTps * i / 5);
993
+ const y = padding.top + chartHeight - i / 5 * chartHeight;
994
+ yLabels.push(
995
+ `<text x="${padding.left - 10}" y="${y + 4}" text-anchor="end" font-size="11" fill="${PALETTE.textMuted}">${value}</text>`
996
+ );
997
+ }
998
+ const xLabels = [];
999
+ const xSteps = Math.min(allTps.length, 8);
1000
+ for (let i = 0; i < xSteps; i++) {
1001
+ const x = padding.left + i / Math.max(xSteps - 1, 1) * chartWidth;
1002
+ const label = i.toString();
1003
+ xLabels.push(
1004
+ `<text x="${x}" y="${height - padding.bottom + 18}" text-anchor="middle" font-size="11" fill="${PALETTE.textMuted}">${label}${messages.htmlTimeUnit}</text>`
1005
+ );
1006
+ }
1007
+ return `
1008
+ <svg viewBox="0 0 ${width} ${height}" class="chart" id="tpsChart">
1009
+ <style>
1010
+ #tpsChart .bar { transition: all 0.2s ease; cursor: pointer; opacity: 0.9; }
1011
+ #tpsChart .bar:hover { opacity: 1; filter: brightness(1.2); }
1012
+ </style>
1013
+ <rect x="${padding.left}" y="${padding.top}" width="${chartWidth}" height="${chartHeight}" fill="${PALETTE.bgCard}" rx="4"/>
1014
+ ${yLabels.join("\n ")}
1015
+ ${xLabels.join("\n ")}
1016
+ <line x1="${padding.left}" y1="${padding.top}" x2="${padding.left}" y2="${height - padding.bottom}" stroke="${PALETTE.border}" stroke-width="2"/>
1017
+ <line x1="${padding.left}" y1="${height - padding.bottom}" x2="${width - padding.right}" y2="${height - padding.bottom}" stroke="${PALETTE.border}" stroke-width="2"/>
1018
+ ${bars}
1019
+ </svg>
1020
+ `;
1021
+ }
1022
+ function generateHTMLReport(options2) {
1023
+ const { config, singleResults, stats, lang, messages } = options2;
1024
+ const isZh = lang === "zh";
1025
+ const testTime = (/* @__PURE__ */ new Date()).toLocaleString(isZh ? "zh-CN" : "en-US");
1026
+ const summaryCards = [
1027
+ {
1028
+ label: messages.statsLabels.ttft,
1029
+ value: formatTime(stats.mean.ttft),
1030
+ detail: `${messages.statsHeaders.min}: ${formatTime(stats.min.ttft)} \xB7 ${messages.statsHeaders.max}: ${formatTime(stats.max.ttft)}`,
1031
+ accent: PALETTE.accent
1032
+ },
1033
+ {
1034
+ label: messages.statsLabels.averageSpeed,
1035
+ value: formatNumber(stats.mean.averageSpeed),
1036
+ detail: `${messages.statsHeaders.min}: ${formatNumber(stats.min.averageSpeed)} \xB7 ${messages.statsHeaders.max}: ${formatNumber(stats.max.averageSpeed)}`,
1037
+ accent: PALETTE.accentSecondary,
1038
+ unit: messages.htmlSpeed
1039
+ },
1040
+ {
1041
+ label: messages.statsLabels.peakSpeed,
1042
+ value: formatNumber(stats.mean.peakSpeed),
1043
+ detail: `${messages.statsHeaders.min}: ${formatNumber(stats.min.peakSpeed)} \xB7 ${messages.statsHeaders.max}: ${formatNumber(stats.max.peakSpeed)}`,
1044
+ accent: PALETTE.accentTertiary,
1045
+ unit: messages.htmlSpeed
1046
+ },
1047
+ {
1048
+ label: messages.statsLabels.totalTokens,
1049
+ value: formatNumber(stats.mean.totalTokens, 0),
1050
+ detail: `${messages.statsHeaders.min}: ${formatNumber(stats.min.totalTokens, 0)} \xB7 ${messages.statsHeaders.max}: ${formatNumber(stats.max.totalTokens, 0)}`,
1051
+ accent: "#00ff88"
1052
+ }
1053
+ ];
1054
+ const detailRows = singleResults.map(
1055
+ (result, idx) => `
1056
+ <tr>
1057
+ <td><span class="run-badge">${idx + 1}</span></td>
1058
+ <td>${formatTime(result.ttft)}</td>
1059
+ <td>${formatTime(result.totalTime)}</td>
1060
+ <td>${result.totalTokens}</td>
1061
+ <td>${formatNumber(result.averageSpeed)}</td>
1062
+ <td>${formatNumber(result.peakSpeed)}</td>
1063
+ <td>${result.peakTps}</td>
1064
+ </tr>
1065
+ `
1066
+ ).join("");
1067
+ const statsRows = [
1068
+ {
1069
+ metric: messages.statsLabels.ttft,
1070
+ mean: formatTime(stats.mean.ttft),
1071
+ min: formatTime(stats.min.ttft),
1072
+ max: formatTime(stats.max.ttft),
1073
+ stdDev: formatTime(stats.stdDev.ttft)
1074
+ },
1075
+ {
1076
+ metric: messages.statsLabels.totalTime,
1077
+ mean: formatTime(stats.mean.totalTime),
1078
+ min: formatTime(stats.min.totalTime),
1079
+ max: formatTime(stats.max.totalTime),
1080
+ stdDev: formatTime(stats.stdDev.totalTime)
1081
+ },
1082
+ {
1083
+ metric: messages.statsLabels.totalTokens,
1084
+ mean: formatNumber(stats.mean.totalTokens, 1),
1085
+ min: formatNumber(stats.min.totalTokens, 0),
1086
+ max: formatNumber(stats.max.totalTokens, 0),
1087
+ stdDev: formatNumber(stats.stdDev.totalTokens, 1)
1088
+ },
1089
+ {
1090
+ metric: messages.statsLabels.averageSpeed,
1091
+ mean: formatNumber(stats.mean.averageSpeed),
1092
+ min: formatNumber(stats.min.averageSpeed),
1093
+ max: formatNumber(stats.max.averageSpeed),
1094
+ stdDev: formatNumber(stats.stdDev.averageSpeed)
1095
+ },
1096
+ {
1097
+ metric: messages.statsLabels.peakSpeed,
1098
+ mean: formatNumber(stats.mean.peakSpeed),
1099
+ min: formatNumber(stats.min.peakSpeed),
1100
+ max: formatNumber(stats.max.peakSpeed),
1101
+ stdDev: formatNumber(stats.stdDev.peakSpeed)
1102
+ },
1103
+ {
1104
+ metric: messages.statsLabels.peakTps,
1105
+ mean: formatNumber(stats.mean.peakTps),
1106
+ min: formatNumber(stats.min.peakTps),
1107
+ max: formatNumber(stats.max.peakTps),
1108
+ stdDev: formatNumber(stats.stdDev.peakTps)
1109
+ }
1110
+ ].map(
1111
+ (row) => `
1112
+ <tr>
1113
+ <td class="metric-name">${row.metric}</td>
1114
+ <td class="value-primary">${row.mean}</td>
1115
+ <td>${row.min}</td>
1116
+ <td>${row.max}</td>
1117
+ <td>${row.stdDev}</td>
1118
+ </tr>
1119
+ `
1120
+ ).join("");
1121
+ const speedChart = generateSpeedChart(singleResults, messages);
1122
+ const tpsChart = generateTPSHistogram(stats, messages);
1123
+ const configGridHtml = `
1124
+ <div class="config-grid">
1125
+ <div class="config-item">
1126
+ <span class="config-label">${messages.configLabels.provider}</span>
1127
+ <span class="config-value">${config.provider.toUpperCase()}</span>
1128
+ </div>
1129
+ <div class="config-item">
1130
+ <span class="config-label">${messages.configLabels.model}</span>
1131
+ <span class="config-value">${escapeHtml(config.model)}</span>
1132
+ </div>
1133
+ <div class="config-item">
1134
+ <span class="config-label">${messages.configLabels.maxTokens}</span>
1135
+ <span class="config-value">${config.maxTokens}</span>
1136
+ </div>
1137
+ <div class="config-item">
1138
+ <span class="config-label">${messages.configLabels.runs}</span>
1139
+ <span class="config-value">${config.runCount}</span>
1140
+ </div>
1141
+ <div class="config-item wide">
1142
+ <span class="config-label">${messages.configLabels.prompt}</span>
1143
+ <span class="config-value">"${escapeHtml(config.prompt)}"</span>
1144
+ </div>
1145
+ </div>
1146
+ `;
1147
+ const summaryCardsHtml = summaryCards.map(
1148
+ (card) => `
1149
+ <div class="card" style="--card-accent: ${card.accent}">
1150
+ <div class="card-label">${card.label}</div>
1151
+ <div class="card-value">${card.value}<span class="card-unit">${card.unit || ""}</span></div>
1152
+ <div class="card-detail">${card.detail}</div>
1153
+ </div>
1154
+ `
1155
+ ).join("");
1156
+ const chartsHtml = `
1157
+ <div class="charts-container">
1158
+ <div class="chart-wrapper">
1159
+ <div class="chart-title">${messages.speedChartTitle}</div>
1160
+ ${speedChart}
1161
+ </div>
1162
+ <div class="chart-wrapper">
1163
+ <div class="chart-title">${messages.htmlTpsDistribution}</div>
1164
+ ${tpsChart}
1165
+ </div>
1166
+ </div>
1167
+ `;
1168
+ const statsTableHtml = `
1169
+ <div class="table-wrapper">
1170
+ <table>
1171
+ <thead>
1172
+ <tr>
1173
+ <th>${messages.statsHeaders.metric}</th>
1174
+ <th>${messages.statsHeaders.mean}</th>
1175
+ <th>${messages.statsHeaders.min}</th>
1176
+ <th>${messages.statsHeaders.max}</th>
1177
+ <th>${messages.statsHeaders.stdDev}</th>
1178
+ </tr>
1179
+ </thead>
1180
+ <tbody>
1181
+ ${statsRows}
1182
+ </tbody>
1183
+ </table>
1184
+ </div>
1185
+ `;
1186
+ const detailsTableHtml = `
1187
+ <div class="table-wrapper">
1188
+ <table>
1189
+ <thead>
1190
+ <tr>
1191
+ <th>${messages.htmlRun}</th>
1192
+ <th>${messages.resultLabels.ttft}</th>
1193
+ <th>${messages.resultLabels.totalTime}</th>
1194
+ <th>${messages.resultLabels.totalTokens}</th>
1195
+ <th>${messages.resultLabels.averageSpeed}</th>
1196
+ <th>${messages.resultLabels.peakSpeed}</th>
1197
+ <th>${messages.resultLabels.peakTps}</th>
1198
+ </tr>
1199
+ </thead>
1200
+ <tbody>
1201
+ ${detailRows}
1202
+ </tbody>
1203
+ </table>
1204
+ </div>
1205
+ `;
1206
+ const data = {
1207
+ lang,
1208
+ title: messages.htmlTitle,
1209
+ reportTitle: messages.htmlReportTitle,
1210
+ testTimeLabel: messages.htmlTestTime,
1211
+ testTime,
1212
+ configSection: messages.htmlConfigSection,
1213
+ summarySection: messages.htmlSummarySection,
1214
+ chartsSection: messages.htmlChartsSection,
1215
+ statsTitle: messages.statsSummaryTitle(stats.sampleSize),
1216
+ detailsSection: messages.htmlDetailsSection,
1217
+ configGrid: configGridHtml,
1218
+ summaryCards: summaryCardsHtml,
1219
+ charts: chartsHtml,
1220
+ statsTable: statsTableHtml,
1221
+ detailsTable: detailsTableHtml
1222
+ };
1223
+ const template = loadTemplate();
1224
+ return replaceTemplate(template, data);
1225
+ }
1226
+
697
1227
  // src/index.ts
698
1228
  function getCliVersion() {
699
1229
  try {
700
- const currentDir = dirname(fileURLToPath(import.meta.url));
1230
+ const currentDir = dirname2(fileURLToPath2(import.meta.url));
701
1231
  const packagePath = join(currentDir, "..", "package.json");
702
- const packageJson = JSON.parse(readFileSync(packagePath, "utf-8"));
1232
+ const packageJson = JSON.parse(readFileSync2(packagePath, "utf-8"));
703
1233
  return packageJson.version ?? "unknown";
704
1234
  } catch {
705
1235
  return "unknown";
@@ -707,7 +1237,7 @@ function getCliVersion() {
707
1237
  }
708
1238
  var program = new Command();
709
1239
  program.name("token-speed-test").description("A CLI tool to test LLM API token output speed").version(getCliVersion());
710
- program.option("-k, --api-key <key>", "API Key (required)", "").option("-p, --provider <provider>", "API provider: anthropic or openai", "anthropic").option("-u, --url <url>", "Custom API endpoint URL").option("-m, --model <model>", "Model name").option("--max-tokens <number>", "Maximum output tokens", "1024").option("-r, --runs <number>", "Number of test runs", "3").option("--prompt <text>", "Test prompt").option("--lang <lang>", "Output language: zh or en", "zh").parse(process.argv);
1240
+ program.option("-k, --api-key <key>", "API Key (required)", "").option("-p, --provider <provider>", "API provider: anthropic or openai", "anthropic").option("-u, --url <url>", "Custom API endpoint URL").option("-m, --model <model>", "Model name").option("--max-tokens <number>", "Maximum output tokens", "1024").option("-r, --runs <number>", "Number of test runs", "3").option("--prompt <text>", "Test prompt").option("--lang <lang>", "Output language: zh or en", "zh").option("--html", "Generate HTML report").option("-o, --output <path>", "HTML report output path", "report.html").parse(process.argv);
711
1241
  var options = program.opts();
712
1242
  async function main() {
713
1243
  let messages = getMessages(DEFAULT_LANG);
@@ -751,6 +1281,31 @@ ${messages.runningTests}
751
1281
  console.log(chalk.green(`
752
1282
  ${messages.testComplete}
753
1283
  `));
1284
+ if (options.html) {
1285
+ const htmlPath = options.output;
1286
+ const htmlContent = generateHTMLReport({
1287
+ config,
1288
+ singleResults: allMetrics,
1289
+ stats,
1290
+ lang: config.lang,
1291
+ messages
1292
+ });
1293
+ try {
1294
+ await fsWriteFile(htmlPath, htmlContent, "utf-8");
1295
+ console.log(chalk.cyan(messages.htmlGenerated(htmlPath)));
1296
+ await open(htmlPath).catch(() => {
1297
+ console.warn(chalk.yellow(messages.htmlOpenError(htmlPath)));
1298
+ });
1299
+ } catch (err) {
1300
+ console.error(
1301
+ chalk.red(
1302
+ `
1303
+ ${messages.errorPrefix}: ${err instanceof Error ? err.message : String(err)}
1304
+ `
1305
+ )
1306
+ );
1307
+ }
1308
+ }
754
1309
  } catch (error) {
755
1310
  if (error instanceof Error) {
756
1311
  console.error(chalk.red(`
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/i18n.ts","../src/config.ts","../src/client.ts","../src/tokenizer.ts","../src/metrics.ts","../src/chart.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport type { Provider } from \"./config.js\";\nimport { parseConfig } from \"./config.js\";\nimport { runMultipleTests } from \"./client.js\";\nimport { calculateMetrics, calculateStats } from \"./metrics.js\";\nimport { renderReport, renderSingleResult } from \"./chart.js\";\nimport { DEFAULT_LANG, getMessages } from \"./i18n.js\";\n\nfunction getCliVersion(): string {\n try {\n const currentDir = dirname(fileURLToPath(import.meta.url));\n const packagePath = join(currentDir, \"..\", \"package.json\");\n const packageJson = JSON.parse(readFileSync(packagePath, \"utf-8\")) as { version?: string };\n return packageJson.version ?? \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\nconst program = new Command();\n\nprogram\n .name(\"token-speed-test\")\n .description(\"A CLI tool to test LLM API token output speed\")\n .version(getCliVersion());\n\nprogram\n .option(\"-k, --api-key <key>\", \"API Key (required)\", \"\")\n .option(\"-p, --provider <provider>\", \"API provider: anthropic or openai\", \"anthropic\")\n .option(\"-u, --url <url>\", \"Custom API endpoint URL\")\n .option(\"-m, --model <model>\", \"Model name\")\n .option(\"--max-tokens <number>\", \"Maximum output tokens\", \"1024\")\n .option(\"-r, --runs <number>\", \"Number of test runs\", \"3\")\n .option(\"--prompt <text>\", \"Test prompt\")\n .option(\"--lang <lang>\", \"Output language: zh or en\", \"zh\")\n .parse(process.argv);\n\nconst options = program.opts();\n\nasync function main() {\n let messages = getMessages(DEFAULT_LANG);\n try {\n // 解析配置\n const config = parseConfig({\n apiKey: options.apiKey,\n provider: options.provider as Provider,\n url: options.url,\n model: options.model,\n maxTokens: parseInt(options.maxTokens, 10),\n runs: parseInt(options.runs, 10),\n prompt: options.prompt,\n lang: options.lang,\n });\n messages = getMessages(config.lang);\n\n // 显示配置信息\n console.log(chalk.cyan(`\\n${messages.appTitle}`));\n console.log(chalk.gray(\"─\".repeat(50)));\n console.log(chalk.gray(`${messages.configLabels.provider}: ${chalk.white(config.provider)}`));\n console.log(chalk.gray(`${messages.configLabels.model}: ${chalk.white(config.model)}`));\n console.log(chalk.gray(`${messages.configLabels.maxTokens}: ${chalk.white(config.maxTokens)}`));\n console.log(chalk.gray(`${messages.configLabels.runs}: ${chalk.white(config.runCount)}`));\n console.log(\n chalk.gray(\n `${messages.configLabels.prompt}: ${chalk.white(config.prompt.substring(0, 50))}${\n config.prompt.length > 50 ? \"...\" : \"\"\n }`\n )\n );\n console.log(chalk.gray(\"─\".repeat(50)));\n\n // 执行测试\n console.log(chalk.yellow(`\\n${messages.runningTests}\\n`));\n console.log(chalk.gray(`${messages.streamingOutput}\\n`));\n\n const results = await runMultipleTests(config);\n\n // 计算指标\n const allMetrics = results.map((r) => calculateMetrics(r));\n\n // 显示每次运行的结果\n for (let i = 0; i < allMetrics.length; i++) {\n console.log(chalk.gray(renderSingleResult(allMetrics[i], i, config.lang)));\n }\n\n // 计算统计\n const stats = calculateStats(allMetrics);\n\n // 显示报告\n console.log(chalk.cyan(\"\\n\" + renderReport(stats, config.lang)));\n\n console.log(chalk.green(`\\n${messages.testComplete}\\n`));\n } catch (error) {\n if (error instanceof Error) {\n console.error(chalk.red(`\\n${messages.errorPrefix}: ${error.message}\\n`));\n } else {\n console.error(chalk.red(`\\n${messages.unknownError}\\n`));\n }\n process.exit(1);\n }\n}\n\nvoid main();\n","export const SUPPORTED_LANGS = [\"zh\", \"en\"] as const;\nexport type Lang = (typeof SUPPORTED_LANGS)[number];\n\nexport const DEFAULT_LANG: Lang = \"zh\";\n\nexport interface Messages {\n defaultPrompt: string;\n appTitle: string;\n runningTests: string;\n streamingOutput: string;\n testComplete: string;\n errorPrefix: string;\n unknownError: string;\n configLabels: {\n provider: string;\n model: string;\n maxTokens: string;\n runs: string;\n prompt: string;\n };\n runLabel: (index: number) => string;\n runProgressLabel: (current: number, total: number) => string;\n reportTitle: string;\n speedChartTitle: string;\n tpsHistogramTitle: string;\n noChartData: string;\n noTpsData: string;\n statsSummaryTitle: (sampleSize: number) => string;\n statsHeaders: {\n metric: string;\n mean: string;\n min: string;\n max: string;\n stdDev: string;\n };\n statsLabels: {\n ttft: string;\n totalTime: string;\n totalTokens: string;\n averageSpeed: string;\n peakSpeed: string;\n peakTps: string;\n };\n resultLabels: {\n ttft: string;\n totalTime: string;\n totalTokens: string;\n averageSpeed: string;\n peakSpeed: string;\n peakTps: string;\n };\n}\n\nconst zhMessages: Messages = {\n defaultPrompt: \"写一篇关于 AI 的短文\",\n appTitle: \"🚀 Token 速度测试工具\",\n runningTests: \"⏳ 正在运行测试...\",\n streamingOutput: \"模型输出 (流式):\",\n testComplete: \"✅ 测试完成!\",\n errorPrefix: \"❌ 错误\",\n unknownError: \"❌ 发生未知错误\",\n configLabels: {\n provider: \"Provider\",\n model: \"Model\",\n maxTokens: \"Max Tokens\",\n runs: \"Runs\",\n prompt: \"Prompt\",\n },\n runLabel: (index: number) => `[运行 ${index}]`,\n runProgressLabel: (current: number, total: number) => `[运行 ${current}/${total}]`,\n reportTitle: \"Token 速度测试报告\",\n speedChartTitle: \"Token 速度趋势图 (TPS)\",\n tpsHistogramTitle: \"TPS 分布\",\n noChartData: \"没有可用于图表的数据\",\n noTpsData: \"没有 TPS 数据可用\",\n statsSummaryTitle: (sampleSize: number) => `统计汇总 (N=${sampleSize})`,\n statsHeaders: {\n metric: \"指标\",\n mean: \"均值\",\n min: \"最小值\",\n max: \"最大值\",\n stdDev: \"标准差\",\n },\n statsLabels: {\n ttft: \"TTFT (ms)\",\n totalTime: \"总耗时 (ms)\",\n totalTokens: \"总 Token 数\",\n averageSpeed: \"平均速度\",\n peakSpeed: \"峰值速度\",\n peakTps: \"峰值 TPS\",\n },\n resultLabels: {\n ttft: \"TTFT\",\n totalTime: \"总耗时\",\n totalTokens: \"总 Token 数\",\n averageSpeed: \"平均速度\",\n peakSpeed: \"峰值速度\",\n peakTps: \"峰值 TPS\",\n },\n};\n\nconst enMessages: Messages = {\n defaultPrompt: \"Write a short essay about AI\",\n appTitle: \"🚀 Token Speed Test\",\n runningTests: \"⏳ Running tests...\",\n streamingOutput: \"Model output (streaming):\",\n testComplete: \"✅ Tests complete!\",\n errorPrefix: \"❌ Error\",\n unknownError: \"❌ An unknown error occurred\",\n configLabels: {\n provider: \"Provider\",\n model: \"Model\",\n maxTokens: \"Max Tokens\",\n runs: \"Runs\",\n prompt: \"Prompt\",\n },\n runLabel: (index: number) => `[Run ${index}]`,\n runProgressLabel: (current: number, total: number) => `[Run ${current}/${total}]`,\n reportTitle: \"Token Speed Test Report\",\n speedChartTitle: \"Token Speed Trend (TPS)\",\n tpsHistogramTitle: \"TPS Distribution\",\n noChartData: \"No data available for chart\",\n noTpsData: \"No TPS data available\",\n statsSummaryTitle: (sampleSize: number) => `Summary (N=${sampleSize})`,\n statsHeaders: {\n metric: \"Metric\",\n mean: \"Mean\",\n min: \"Min\",\n max: \"Max\",\n stdDev: \"Std Dev\",\n },\n statsLabels: {\n ttft: \"TTFT (ms)\",\n totalTime: \"Total Time (ms)\",\n totalTokens: \"Total Tokens\",\n averageSpeed: \"Avg Speed\",\n peakSpeed: \"Peak Speed\",\n peakTps: \"Peak TPS\",\n },\n resultLabels: {\n ttft: \"TTFT\",\n totalTime: \"Total Time\",\n totalTokens: \"Total Tokens\",\n averageSpeed: \"Avg Speed\",\n peakSpeed: \"Peak Speed\",\n peakTps: \"Peak TPS\",\n },\n};\n\nexport function isSupportedLang(value: string): value is Lang {\n return SUPPORTED_LANGS.includes(value as Lang);\n}\n\nexport function resolveLang(value?: string): Lang {\n if (!value) {\n return DEFAULT_LANG;\n }\n const normalized = value.toLowerCase();\n if (!isSupportedLang(normalized)) {\n throw new Error(`Invalid lang: ${value}. Must be 'zh' or 'en'.`);\n }\n return normalized;\n}\n\nexport function getMessages(lang: Lang): Messages {\n return lang === \"en\" ? enMessages : zhMessages;\n}\n","import { getMessages, resolveLang, type Lang } from \"./i18n.js\";\n\nexport type Provider = \"anthropic\" | \"openai\";\n\nexport interface Config {\n provider: Provider;\n apiKey: string;\n baseURL?: string;\n model: string;\n maxTokens: number;\n runCount: number;\n prompt: string;\n lang: Lang;\n}\n\nexport interface ParsedArgs {\n apiKey: string;\n provider?: Provider;\n url?: string;\n model?: string;\n maxTokens?: number;\n runs?: number;\n prompt?: string;\n lang?: string;\n}\n\nconst DEFAULT_MODELS: Record<Provider, string> = {\n anthropic: \"claude-opus-4-5-20251101\",\n openai: \"gpt-5.2\",\n};\n\nconst DEFAULT_MAX_TOKENS = 1024;\nconst DEFAULT_RUNS = 3;\n/**\n * 解析命令行参数并生成配置\n */\nexport function parseConfig(args: ParsedArgs): Config {\n const {\n apiKey,\n provider = \"anthropic\",\n url,\n model,\n maxTokens = DEFAULT_MAX_TOKENS,\n runs = DEFAULT_RUNS,\n prompt,\n lang: langInput,\n } = args;\n const lang = resolveLang(langInput);\n const messages = getMessages(lang);\n const finalPrompt = prompt ?? messages.defaultPrompt;\n\n // 验证必填参数\n if (!apiKey || apiKey.trim() === \"\") {\n throw new Error(\"API Key is required. Use --api-key or -k to provide it.\");\n }\n\n // 验证 provider\n if (provider !== \"anthropic\" && provider !== \"openai\") {\n throw new Error(`Invalid provider: ${provider}. Must be 'anthropic' or 'openai'.`);\n }\n\n // 验证数值参数\n if (!Number.isFinite(maxTokens) || !Number.isInteger(maxTokens) || maxTokens <= 0) {\n throw new Error(`Invalid max-tokens: ${maxTokens}. Must be a positive integer.`);\n }\n\n if (!Number.isFinite(runs) || !Number.isInteger(runs) || runs <= 0) {\n throw new Error(`Invalid runs: ${runs}. Must be a positive integer.`);\n }\n\n // 使用默认模型或用户指定的模型\n const finalModel = model || DEFAULT_MODELS[provider];\n\n return {\n provider,\n apiKey: apiKey.trim(),\n baseURL: url?.trim(),\n model: finalModel,\n maxTokens,\n runCount: runs,\n prompt: finalPrompt.trim(),\n lang,\n };\n}\n\n/**\n * 获取默认模型名称\n */\nexport function getDefaultModel(provider: Provider): string {\n return DEFAULT_MODELS[provider];\n}\n\n/**\n * 验证配置有效性\n */\nexport function validateConfig(config: Config): { valid: boolean; error?: string } {\n if (!config.apiKey) {\n return { valid: false, error: \"API Key is required\" };\n }\n\n if (config.provider !== \"anthropic\" && config.provider !== \"openai\") {\n return { valid: false, error: `Invalid provider: ${config.provider}` };\n }\n\n if (\n !Number.isFinite(config.maxTokens) ||\n !Number.isInteger(config.maxTokens) ||\n config.maxTokens <= 0\n ) {\n return { valid: false, error: \"maxTokens must be a positive integer\" };\n }\n\n if (\n !Number.isFinite(config.runCount) ||\n !Number.isInteger(config.runCount) ||\n config.runCount <= 0\n ) {\n return { valid: false, error: \"runCount must be a positive integer\" };\n }\n\n if (!config.prompt || config.prompt.trim() === \"\") {\n return { valid: false, error: \"prompt cannot be empty\" };\n }\n\n if (config.lang !== \"zh\" && config.lang !== \"en\") {\n return { valid: false, error: `Invalid lang: ${config.lang}` };\n }\n\n return { valid: true };\n}\n// test\n","import { performance } from \"node:perf_hooks\";\nimport Anthropic from \"@anthropic-ai/sdk\";\nimport OpenAI from \"openai\";\nimport type { Config } from \"./config.js\";\nimport { getMessages } from \"./i18n.js\";\nimport type { StreamMetrics } from \"./metrics.js\";\nimport { createTokenizer } from \"./tokenizer.js\";\n\n/**\n * 执行 Anthropic API 流式测试\n */\nexport async function anthropicStreamTest(config: Config): Promise<StreamMetrics> {\n const startTime = performance.now();\n const tokenTimes: number[] = [];\n let ttft = 0;\n let firstTokenRecorded = false;\n let tokenCount = 0;\n let wroteOutput = false;\n\n const encoding = createTokenizer(config.model);\n const client = new Anthropic({\n apiKey: config.apiKey,\n baseURL: config.baseURL,\n });\n\n try {\n const stream = await client.messages.create({\n model: config.model,\n max_tokens: config.maxTokens,\n messages: [{ role: \"user\", content: config.prompt }],\n stream: true,\n });\n\n for await (const event of stream) {\n const currentTime = performance.now();\n\n if (event.type === \"content_block_delta\" && event.delta.type === \"text_delta\") {\n const text = event.delta.text;\n\n if (text && text.length > 0) {\n process.stdout.write(text);\n wroteOutput = true;\n const encoded = encoding.encode(text);\n const newTokens = encoded.length;\n\n if (newTokens > 0) {\n if (!firstTokenRecorded) {\n ttft = currentTime - startTime;\n firstTokenRecorded = true;\n }\n\n // 为当前批次的新增 token 记录到达时间\n for (let i = 0; i < newTokens; i++) {\n tokenTimes.push(currentTime - startTime);\n }\n\n tokenCount += newTokens;\n }\n }\n }\n }\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Anthropic API error: ${error.message}`);\n }\n throw error;\n } finally {\n if (wroteOutput) {\n process.stdout.write(\"\\n\");\n }\n encoding.free();\n }\n\n const endTime = performance.now();\n const totalTime = endTime - startTime;\n\n return {\n ttft,\n tokens: tokenTimes,\n totalTokens: tokenCount,\n totalTime,\n };\n}\n\n/**\n * 执行 OpenAI API 流式测试\n */\nexport async function openaiStreamTest(config: Config): Promise<StreamMetrics> {\n const startTime = performance.now();\n const tokenTimes: number[] = [];\n let ttft = 0;\n let firstTokenRecorded = false;\n let tokenCount = 0;\n let wroteOutput = false;\n\n const encoding = createTokenizer(config.model);\n const client = new OpenAI({\n apiKey: config.apiKey,\n baseURL: config.baseURL,\n });\n\n try {\n const stream = await client.chat.completions.create({\n model: config.model,\n max_tokens: config.maxTokens,\n messages: [{ role: \"user\", content: config.prompt }],\n stream: true,\n });\n\n for await (const chunk of stream) {\n const currentTime = performance.now();\n\n const delta = chunk.choices[0]?.delta;\n\n if (delta?.content) {\n const content = delta.content;\n\n if (content.length > 0) {\n process.stdout.write(content);\n wroteOutput = true;\n const encoded = encoding.encode(content);\n const newTokens = encoded.length;\n\n if (newTokens > 0) {\n if (!firstTokenRecorded) {\n ttft = currentTime - startTime;\n firstTokenRecorded = true;\n }\n\n // 为当前批次的新增 token 记录到达时间\n for (let i = 0; i < newTokens; i++) {\n tokenTimes.push(currentTime - startTime);\n }\n\n tokenCount += newTokens;\n }\n }\n }\n }\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`OpenAI API error: ${error.message}`);\n }\n throw error;\n } finally {\n if (wroteOutput) {\n process.stdout.write(\"\\n\");\n }\n encoding.free();\n }\n\n const endTime = performance.now();\n const totalTime = endTime - startTime;\n\n return {\n ttft,\n tokens: tokenTimes,\n totalTokens: tokenCount,\n totalTime,\n };\n}\n\n/**\n * 根据配置执行流式测试\n */\nexport async function streamTest(config: Config): Promise<StreamMetrics> {\n if (config.provider === \"anthropic\") {\n return anthropicStreamTest(config);\n } else {\n return openaiStreamTest(config);\n }\n}\n\n/**\n * 执行多次测试\n */\nexport async function runMultipleTests(config: Config): Promise<StreamMetrics[]> {\n const results: StreamMetrics[] = [];\n const messages = getMessages(config.lang);\n\n for (let i = 0; i < config.runCount; i++) {\n if (config.runCount > 1) {\n const label = `\\n${messages.runProgressLabel(i + 1, config.runCount)}`;\n console.log(label);\n console.log(\"-\".repeat(label.length - 1));\n }\n const result = await streamTest(config);\n results.push(result);\n }\n\n return results;\n}\n","import { encoding_for_model, get_encoding } from \"tiktoken\";\nimport type { TiktokenModel } from \"tiktoken\";\n\nconst FALLBACK_ENCODING = \"cl100k_base\";\n\nexport function createTokenizer(model: string) {\n try {\n const normalized = model.trim();\n if (!normalized) {\n return get_encoding(FALLBACK_ENCODING);\n }\n return encoding_for_model(normalized as TiktokenModel);\n } catch {\n return get_encoding(FALLBACK_ENCODING);\n }\n}\n","/**\n * 单次流式测试的原始计时数据\n */\nexport interface StreamMetrics {\n ttft: number; // Time to First Token (ms)\n tokens: number[]; // 每个 token 的到达时间(相对开始时间,单位:ms)\n totalTokens: number;\n totalTime: number;\n}\n\n/**\n * 计算后的统计指标\n */\nexport interface CalculatedMetrics {\n ttft: number; // Time to First Token (ms)\n totalTime: number; // 总耗时 (ms)\n totalTokens: number; // 总 token 数\n averageSpeed: number; // 平均速度 (tokens/s)\n peakSpeed: number; // 峰值速度 (tokens/s)\n peakTps: number; // 峰值 TPS (tokens/s)\n tps: number[]; // 每秒 token 数 (TPS curve)\n}\n\n/**\n * 多次测试的统计结果\n */\nexport interface StatsResult {\n mean: CalculatedMetrics;\n min: CalculatedMetrics;\n max: CalculatedMetrics;\n stdDev: CalculatedMetrics;\n sampleSize: number;\n}\n\n/**\n * 计算 TTFT (Time to First Token)\n */\nexport function calculateTTFT(metrics: StreamMetrics): number {\n return metrics.ttft;\n}\n\n/**\n * 计算平均速度 (tokens/s)\n */\nexport function calculateAverageSpeed(metrics: StreamMetrics): number {\n if (metrics.totalTime <= 0) {\n return 0;\n }\n return (metrics.totalTokens / metrics.totalTime) * 1000;\n}\n\n/**\n * 计算峰值速度 - 最快连续 N 个 token 的平均速度\n */\nconst MIN_PEAK_WINDOW_MS = 50;\n\nexport function calculatePeakSpeed(metrics: StreamMetrics, windowSize: number = 10): number {\n if (metrics.tokens.length < windowSize) {\n // 如果 token 数少于窗口大小,使用全部 token 计算\n if (metrics.tokens.length < 2) {\n return 0;\n }\n const totalTime = metrics.tokens[metrics.tokens.length - 1] - metrics.tokens[0];\n const durationMs = Math.max(totalTime, MIN_PEAK_WINDOW_MS);\n return ((metrics.tokens.length - 1) / durationMs) * 1000;\n }\n\n let maxSpeed = 0;\n for (let i = 0; i <= metrics.tokens.length - windowSize; i++) {\n const startTime = metrics.tokens[i];\n const endTime = metrics.tokens[i + windowSize - 1];\n const duration = endTime - startTime;\n const durationMs = Math.max(duration, MIN_PEAK_WINDOW_MS);\n const speed = ((windowSize - 1) / durationMs) * 1000;\n maxSpeed = Math.max(maxSpeed, speed);\n }\n\n return maxSpeed;\n}\n\n/**\n * 计算 TPS (Tokens Per Second) 曲线\n */\nexport function calculateTPS(metrics: StreamMetrics): number[] {\n if (metrics.tokens.length === 0) {\n return [];\n }\n\n const totalDuration = metrics.tokens[metrics.tokens.length - 1];\n const totalSeconds = Math.ceil(totalDuration / 1000);\n\n if (totalSeconds <= 0) {\n return metrics.tokens.length > 0 ? [metrics.tokens.length] : [];\n }\n\n const tps: number[] = new Array(totalSeconds).fill(0);\n\n // 计算每秒内的 token 数\n for (const tokenTime of metrics.tokens) {\n const secondIndex = Math.floor(tokenTime / 1000);\n if (secondIndex < tps.length) {\n tps[secondIndex]++;\n }\n }\n\n return tps;\n}\n\n/**\n * 从 StreamMetrics 计算完整的指标\n */\nexport function calculateMetrics(metrics: StreamMetrics): CalculatedMetrics {\n const tps = calculateTPS(metrics);\n return {\n ttft: calculateTTFT(metrics),\n totalTime: metrics.totalTime,\n totalTokens: metrics.totalTokens,\n averageSpeed: calculateAverageSpeed(metrics),\n peakSpeed: calculatePeakSpeed(metrics),\n peakTps: tps.length > 0 ? Math.max(...tps) : 0,\n tps,\n };\n}\n\n/**\n * 计算一组数值的平均值\n */\nfunction mean(values: number[]): number {\n if (values.length === 0) return 0;\n return values.reduce((sum, v) => sum + v, 0) / values.length;\n}\n\n/**\n * 计算一组数值的标准差\n */\nfunction standardDeviation(values: number[]): number {\n if (values.length < 2) return 0;\n const avg = mean(values);\n const squareDiffs = values.map((v) => Math.pow(v - avg, 2));\n return Math.sqrt(mean(squareDiffs));\n}\n\n/**\n * 从多个 CalculatedMetrics 计算统计结果\n */\nexport function calculateStats(allMetrics: CalculatedMetrics[]): StatsResult {\n if (allMetrics.length === 0) {\n throw new Error(\"Cannot calculate stats from empty metrics array\");\n }\n\n const sampleSize = allMetrics.length;\n\n // 提取各项指标的数组\n const ttfts = allMetrics.map((m) => m.ttft);\n const totalTimes = allMetrics.map((m) => m.totalTime);\n const totalTokens = allMetrics.map((m) => m.totalTokens);\n const averageSpeeds = allMetrics.map((m) => m.averageSpeed);\n const peakSpeeds = allMetrics.map((m) => m.peakSpeed);\n const peakTpsValues = allMetrics.map((m) => m.peakTps);\n\n // 找到最长的 TPS 数组\n const maxTpsLength = Math.max(...allMetrics.map((m) => m.tps.length));\n const avgTps: number[] = [];\n for (let i = 0; i < maxTpsLength; i++) {\n const values = allMetrics.map((m) => m.tps[i] ?? 0);\n avgTps.push(mean(values));\n }\n\n return {\n mean: {\n ttft: mean(ttfts),\n totalTime: mean(totalTimes),\n totalTokens: mean(totalTokens),\n averageSpeed: mean(averageSpeeds),\n peakSpeed: mean(peakSpeeds),\n peakTps: mean(peakTpsValues),\n tps: avgTps,\n },\n min: {\n ttft: Math.min(...ttfts),\n totalTime: Math.min(...totalTimes),\n totalTokens: Math.min(...totalTokens),\n averageSpeed: Math.min(...averageSpeeds),\n peakSpeed: Math.min(...peakSpeeds),\n peakTps: Math.min(...peakTpsValues),\n tps: [],\n },\n max: {\n ttft: Math.max(...ttfts),\n totalTime: Math.max(...totalTimes),\n totalTokens: Math.max(...totalTokens),\n averageSpeed: Math.max(...averageSpeeds),\n peakSpeed: Math.max(...peakSpeeds),\n peakTps: Math.max(...peakTpsValues),\n tps: [],\n },\n stdDev: {\n ttft: standardDeviation(ttfts),\n totalTime: standardDeviation(totalTimes),\n totalTokens: standardDeviation(totalTokens),\n averageSpeed: standardDeviation(averageSpeeds),\n peakSpeed: standardDeviation(peakSpeeds),\n peakTps: standardDeviation(peakTpsValues),\n tps: [],\n },\n sampleSize,\n };\n}\n\n/**\n * 格式化速度显示\n */\nexport function formatSpeed(tokensPerSecond: number): string {\n return tokensPerSecond.toFixed(2);\n}\n\n/**\n * 格式化时间显示\n */\nexport function formatTime(ms: number): string {\n if (ms < 1000) {\n return `${ms.toFixed(0)}ms`;\n }\n return `${(ms / 1000).toFixed(2)}s`;\n}\n","import stringWidth from \"string-width\";\nimport type { CalculatedMetrics, StatsResult } from \"./metrics.js\";\nimport { DEFAULT_LANG, getMessages, type Lang } from \"./i18n.js\";\n\nconst BLOCK_CHAR = \"█\";\nconst CHART_WIDTH = 50;\nconst CHART_HEIGHT = 10;\nconst STAT_LABEL_WIDTH = 15;\nconst STAT_VALUE_WIDTH = 10;\nconst Y_LABEL_WIDTH = 4;\n\nfunction padEndWidth(text: string, width: number): string {\n const currentWidth = stringWidth(text);\n if (currentWidth >= width) {\n return text;\n }\n return text + \" \".repeat(width - currentWidth);\n}\n\nfunction padStartWidth(text: string, width: number): string {\n const currentWidth = stringWidth(text);\n if (currentWidth >= width) {\n return text;\n }\n return \" \".repeat(width - currentWidth) + text;\n}\n\n/**\n * 渲染速度趋势图\n */\nexport function renderSpeedChart(\n tps: number[],\n maxSpeed?: number,\n lang: Lang = DEFAULT_LANG\n): string {\n const messages = getMessages(lang);\n if (tps.length === 0) {\n return messages.noChartData;\n }\n\n const actualMax = maxSpeed ?? Math.max(...tps, 1);\n const maxVal = Math.max(actualMax, 1);\n\n const buildRow = (label: string, bars: string) =>\n `│ ${padStartWidth(label, Y_LABEL_WIDTH)} ┤${bars} │`;\n const emptyRow = buildRow(\"0\", \" \".repeat(CHART_WIDTH));\n const chartWidth = stringWidth(emptyRow) - 2;\n const axisPrefix = `│ ${padStartWidth(\"\", Y_LABEL_WIDTH)} ┼`;\n\n const lines: string[] = [];\n lines.push(messages.speedChartTitle);\n\n // Y 轴标签和边框\n lines.push(\"┌\" + \"─\".repeat(chartWidth) + \"┐\");\n\n for (let row = CHART_HEIGHT - 1; row >= 0; row--) {\n const value = (row / (CHART_HEIGHT - 1)) * maxVal;\n const label = value.toFixed(0);\n\n let bars = \"\";\n for (let col = 0; col < CHART_WIDTH; col++) {\n const index = Math.floor((col / CHART_WIDTH) * tps.length);\n const tpsValue = tps[index] ?? 0;\n const normalizedHeight = (tpsValue / maxVal) * (CHART_HEIGHT - 1);\n bars += normalizedHeight >= row ? BLOCK_CHAR : \" \";\n }\n\n lines.push(buildRow(label, bars));\n }\n\n // X 轴\n lines.push(`${axisPrefix}${\"─\".repeat(CHART_WIDTH)} │`);\n lines.push(\"└\" + \"─\".repeat(chartWidth) + \"┘\");\n\n // X 轴标签 (时间标记)\n const xLabels = generateXLabels(tps.length, 6);\n const labelLine = new Array(CHART_WIDTH).fill(\" \");\n const maxIndex = Math.max(tps.length - 1, 1);\n for (const label of xLabels) {\n const seconds = parseInt(label.replace(\"s\", \"\"), 10);\n const position = Math.min(\n CHART_WIDTH - 1,\n Math.round((seconds / maxIndex) * (CHART_WIDTH - 1))\n );\n for (let i = 0; i < label.length && position + i < CHART_WIDTH; i++) {\n labelLine[position + i] = label[i];\n }\n }\n lines.push(\" \".repeat(stringWidth(axisPrefix)) + labelLine.join(\"\"));\n\n return lines.join(\"\\n\");\n}\n\n/**\n * 生成 X 轴时间标签\n */\nfunction generateXLabels(dataPoints: number, maxLabels: number): string[] {\n if (dataPoints <= 1) {\n return [\"0s\"];\n }\n\n const labels: string[] = [];\n const step = Math.max(1, Math.floor(dataPoints / maxLabels));\n\n for (let i = 0; i < dataPoints; i += step) {\n labels.push(`${i}s`);\n }\n\n // 确保最后一个标签是结束时间\n if (labels[labels.length - 1] !== `${dataPoints - 1}s`) {\n labels.push(`${dataPoints - 1}s`);\n }\n\n return labels;\n}\n\n/**\n * 渲染 TPS 直方图\n */\nexport function renderTPSHistogram(tps: number[], lang: Lang = DEFAULT_LANG): string {\n const messages = getMessages(lang);\n if (tps.length === 0) {\n return messages.noTpsData;\n }\n\n const lines: string[] = [];\n lines.push(messages.tpsHistogramTitle);\n\n // 计算分布区间\n const maxTps = Math.max(...tps, 1);\n const buckets = 10;\n const bucketSize = maxTps / buckets;\n const histogram = new Array(buckets).fill(0);\n\n for (const t of tps) {\n const bucketIndex = Math.min(Math.floor(t / bucketSize), buckets - 1);\n histogram[bucketIndex]++;\n }\n\n const maxCount = Math.max(...histogram, 1);\n\n const labels = histogram.map((_, i) => {\n const bucketStart = (i * bucketSize).toFixed(1);\n const bucketEnd = ((i + 1) * bucketSize).toFixed(1);\n return `${bucketStart}-${bucketEnd}`;\n });\n const labelWidth = Math.max(...labels.map((l) => stringWidth(l)));\n\n for (let i = 0; i < buckets; i++) {\n const label = padEndWidth(labels[i], labelWidth);\n const count = histogram[i];\n const barLength = Math.round((count / maxCount) * CHART_WIDTH);\n const bar = BLOCK_CHAR.repeat(barLength);\n\n const countSuffix = count > 0 ? ` ${count}` : \"\";\n lines.push(`${label} │${bar}${countSuffix}`);\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * 渲染统计汇总表\n */\nexport function renderStatsTable(stats: StatsResult, lang: Lang = DEFAULT_LANG): string {\n const messages = getMessages(lang);\n const lines: string[] = [];\n lines.push(\"\");\n lines.push(messages.statsSummaryTitle(stats.sampleSize));\n\n // 表头\n const headerRow =\n \"│ \" +\n padEndWidth(messages.statsHeaders.metric, STAT_LABEL_WIDTH) +\n \" │ \" +\n padStartWidth(messages.statsHeaders.mean, STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(messages.statsHeaders.min, STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(messages.statsHeaders.max, STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(messages.statsHeaders.stdDev, STAT_VALUE_WIDTH) +\n \" │\";\n\n const tableWidth = stringWidth(headerRow) - 2;\n lines.push(\"┌\" + \"─\".repeat(tableWidth) + \"┐\");\n lines.push(headerRow);\n lines.push(\"├\" + \"─\".repeat(tableWidth) + \"┤\");\n\n // TTFT\n lines.push(\n formatStatRow(\n messages.statsLabels.ttft,\n stats.mean.ttft,\n stats.min.ttft,\n stats.max.ttft,\n stats.stdDev.ttft,\n \"f\"\n )\n );\n lines.push(\"├\" + \"─\".repeat(tableWidth) + \"┤\");\n\n // 总耗时\n lines.push(\n formatStatRow(\n messages.statsLabels.totalTime,\n stats.mean.totalTime,\n stats.min.totalTime,\n stats.max.totalTime,\n stats.stdDev.totalTime,\n \"f\"\n )\n );\n lines.push(\"├\" + \"─\".repeat(tableWidth) + \"┤\");\n\n // 总 token 数\n lines.push(\n formatStatRow(\n messages.statsLabels.totalTokens,\n stats.mean.totalTokens,\n stats.min.totalTokens,\n stats.max.totalTokens,\n stats.stdDev.totalTokens,\n \"f\"\n )\n );\n lines.push(\"├\" + \"─\".repeat(tableWidth) + \"┤\");\n\n // 平均速度\n lines.push(\n formatStatRow(\n messages.statsLabels.averageSpeed,\n stats.mean.averageSpeed,\n stats.min.averageSpeed,\n stats.max.averageSpeed,\n stats.stdDev.averageSpeed,\n \"f\"\n )\n );\n lines.push(\"├\" + \"─\".repeat(tableWidth) + \"┤\");\n\n // 峰值速度\n lines.push(\n formatStatRow(\n messages.statsLabels.peakSpeed,\n stats.mean.peakSpeed,\n stats.min.peakSpeed,\n stats.max.peakSpeed,\n stats.stdDev.peakSpeed,\n \"f\"\n )\n );\n\n lines.push(\"├\" + \"─\".repeat(tableWidth) + \"┤\");\n\n // 峰值 TPS\n lines.push(\n formatStatRow(\n messages.statsLabels.peakTps,\n stats.mean.peakTps,\n stats.min.peakTps,\n stats.max.peakTps,\n stats.stdDev.peakTps,\n \"f\"\n )\n );\n\n lines.push(\"└\" + \"─\".repeat(tableWidth) + \"┘\");\n\n return lines.join(\"\\n\");\n}\n\n/**\n * 格式化统计表格的一行\n */\nfunction formatStatRow(\n label: string,\n mean: number,\n min: number,\n max: number,\n stdDev: number,\n format: \"f\" | \"d\"\n): string {\n const fmt = (n: number) => (format === \"f\" ? n.toFixed(2) : n.toFixed(0));\n\n return (\n \"│ \" +\n padEndWidth(label, STAT_LABEL_WIDTH) +\n \" │ \" +\n padStartWidth(fmt(mean), STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(fmt(min), STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(fmt(max), STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(fmt(stdDev), STAT_VALUE_WIDTH) +\n \" │\"\n );\n}\n\n/**\n * 格式化时间显示(带小数)\n */\nfunction formatTimeWithDecimals(ms: number): string {\n if (ms === Math.floor(ms)) {\n return `${ms.toFixed(0)}ms`;\n }\n return `${ms.toFixed(2)}ms`;\n}\n\n/**\n * 渲染单次测试结果\n */\nexport function renderSingleResult(\n metrics: CalculatedMetrics,\n runIndex: number,\n lang: Lang = DEFAULT_LANG\n): string {\n const messages = getMessages(lang);\n const lines: string[] = [];\n lines.push(`\\n${messages.runLabel(runIndex + 1)}`);\n lines.push(` ${messages.resultLabels.ttft}: ${formatTimeWithDecimals(metrics.ttft)}`);\n lines.push(` ${messages.resultLabels.totalTime}: ${formatTimeWithDecimals(metrics.totalTime)}`);\n lines.push(` ${messages.resultLabels.totalTokens}: ${metrics.totalTokens}`);\n lines.push(\n ` ${messages.resultLabels.averageSpeed}: ${metrics.averageSpeed.toFixed(2)} tokens/s`\n );\n lines.push(` ${messages.resultLabels.peakSpeed}: ${metrics.peakSpeed.toFixed(2)} tokens/s`);\n lines.push(` ${messages.resultLabels.peakTps}: ${metrics.peakTps.toFixed(2)} tokens/s`);\n return lines.join(\"\\n\");\n}\n\n/**\n * 渲染完整的测试报告\n */\nexport function renderReport(stats: StatsResult, lang: Lang = DEFAULT_LANG): string {\n const messages = getMessages(lang);\n const lines: string[] = [];\n\n lines.push(\"\\n\" + \"═\".repeat(72));\n lines.push(messages.reportTitle);\n lines.push(\"═\".repeat(72));\n\n // 汇总统计\n lines.push(renderStatsTable(stats, lang));\n\n // 速度趋势图\n if (stats.mean.tps.length > 0) {\n lines.push(\"\\n\" + renderSpeedChart(stats.mean.tps, undefined, lang));\n }\n\n // TPS 直方图\n if (stats.mean.tps.length > 0) {\n lines.push(\"\\n\" + renderTPSHistogram(stats.mean.tps, lang));\n }\n\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":["mean"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/i18n.ts","../src/config.ts","../src/client.ts","../src/tokenizer.ts","../src/metrics.ts","../src/chart.ts","../src/html-report.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { readFileSync } from \"node:fs\";\nimport { writeFile as fsWriteFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { Command } from \"commander\";\nimport chalk from \"chalk\";\nimport open from \"open\";\nimport type { Provider } from \"./config.js\";\nimport { parseConfig } from \"./config.js\";\nimport { runMultipleTests } from \"./client.js\";\nimport { calculateMetrics, calculateStats } from \"./metrics.js\";\nimport { renderReport, renderSingleResult } from \"./chart.js\";\nimport { generateHTMLReport } from \"./html-report.js\";\nimport { DEFAULT_LANG, getMessages } from \"./i18n.js\";\n\nfunction getCliVersion(): string {\n try {\n const currentDir = dirname(fileURLToPath(import.meta.url));\n const packagePath = join(currentDir, \"..\", \"package.json\");\n const packageJson = JSON.parse(readFileSync(packagePath, \"utf-8\")) as { version?: string };\n return packageJson.version ?? \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\nconst program = new Command();\n\nprogram\n .name(\"token-speed-test\")\n .description(\"A CLI tool to test LLM API token output speed\")\n .version(getCliVersion());\n\nprogram\n .option(\"-k, --api-key <key>\", \"API Key (required)\", \"\")\n .option(\"-p, --provider <provider>\", \"API provider: anthropic or openai\", \"anthropic\")\n .option(\"-u, --url <url>\", \"Custom API endpoint URL\")\n .option(\"-m, --model <model>\", \"Model name\")\n .option(\"--max-tokens <number>\", \"Maximum output tokens\", \"1024\")\n .option(\"-r, --runs <number>\", \"Number of test runs\", \"3\")\n .option(\"--prompt <text>\", \"Test prompt\")\n .option(\"--lang <lang>\", \"Output language: zh or en\", \"zh\")\n .option(\"--html\", \"Generate HTML report\")\n .option(\"-o, --output <path>\", \"HTML report output path\", \"report.html\")\n .parse(process.argv);\n\nconst options = program.opts();\n\nasync function main() {\n let messages = getMessages(DEFAULT_LANG);\n try {\n // 解析配置\n const config = parseConfig({\n apiKey: options.apiKey,\n provider: options.provider as Provider,\n url: options.url,\n model: options.model,\n maxTokens: parseInt(options.maxTokens, 10),\n runs: parseInt(options.runs, 10),\n prompt: options.prompt,\n lang: options.lang,\n });\n messages = getMessages(config.lang);\n\n // 显示配置信息\n console.log(chalk.cyan(`\\n${messages.appTitle}`));\n console.log(chalk.gray(\"─\".repeat(50)));\n console.log(chalk.gray(`${messages.configLabels.provider}: ${chalk.white(config.provider)}`));\n console.log(chalk.gray(`${messages.configLabels.model}: ${chalk.white(config.model)}`));\n console.log(chalk.gray(`${messages.configLabels.maxTokens}: ${chalk.white(config.maxTokens)}`));\n console.log(chalk.gray(`${messages.configLabels.runs}: ${chalk.white(config.runCount)}`));\n console.log(\n chalk.gray(\n `${messages.configLabels.prompt}: ${chalk.white(config.prompt.substring(0, 50))}${\n config.prompt.length > 50 ? \"...\" : \"\"\n }`\n )\n );\n console.log(chalk.gray(\"─\".repeat(50)));\n\n // 执行测试\n console.log(chalk.yellow(`\\n${messages.runningTests}\\n`));\n console.log(chalk.gray(`${messages.streamingOutput}\\n`));\n\n const results = await runMultipleTests(config);\n\n // 计算指标\n const allMetrics = results.map((r) => calculateMetrics(r));\n\n // 显示每次运行的结果\n for (let i = 0; i < allMetrics.length; i++) {\n console.log(chalk.gray(renderSingleResult(allMetrics[i], i, config.lang)));\n }\n\n // 计算统计\n const stats = calculateStats(allMetrics);\n\n // 显示报告\n console.log(chalk.cyan(\"\\n\" + renderReport(stats, config.lang)));\n\n console.log(chalk.green(`\\n${messages.testComplete}\\n`));\n\n // 生成 HTML 报告\n if (options.html) {\n const htmlPath = options.output;\n const htmlContent = generateHTMLReport({\n config,\n singleResults: allMetrics,\n stats,\n lang: config.lang,\n messages,\n });\n\n try {\n await fsWriteFile(htmlPath, htmlContent, \"utf-8\");\n console.log(chalk.cyan(messages.htmlGenerated(htmlPath)));\n\n // 自动打开浏览器\n await open(htmlPath).catch(() => {\n console.warn(chalk.yellow(messages.htmlOpenError(htmlPath)));\n });\n } catch (err) {\n console.error(\n chalk.red(\n `\\n${messages.errorPrefix}: ${err instanceof Error ? err.message : String(err)}\\n`\n )\n );\n }\n }\n } catch (error) {\n if (error instanceof Error) {\n console.error(chalk.red(`\\n${messages.errorPrefix}: ${error.message}\\n`));\n } else {\n console.error(chalk.red(`\\n${messages.unknownError}\\n`));\n }\n process.exit(1);\n }\n}\n\nvoid main();\n","export const SUPPORTED_LANGS = [\"zh\", \"en\"] as const;\nexport type Lang = (typeof SUPPORTED_LANGS)[number];\n\nexport const DEFAULT_LANG: Lang = \"zh\";\n\nexport interface Messages {\n defaultPrompt: string;\n appTitle: string;\n runningTests: string;\n streamingOutput: string;\n testComplete: string;\n errorPrefix: string;\n unknownError: string;\n configLabels: {\n provider: string;\n model: string;\n maxTokens: string;\n runs: string;\n prompt: string;\n };\n runLabel: (index: number) => string;\n runProgressLabel: (current: number, total: number) => string;\n reportTitle: string;\n speedChartTitle: string;\n tpsHistogramTitle: string;\n noChartData: string;\n noTpsData: string;\n statsSummaryTitle: (sampleSize: number) => string;\n statsHeaders: {\n metric: string;\n mean: string;\n min: string;\n max: string;\n stdDev: string;\n };\n statsLabels: {\n ttft: string;\n totalTime: string;\n totalTokens: string;\n averageSpeed: string;\n peakSpeed: string;\n peakTps: string;\n };\n resultLabels: {\n ttft: string;\n totalTime: string;\n totalTokens: string;\n averageSpeed: string;\n peakSpeed: string;\n peakTps: string;\n };\n htmlTitle: string;\n htmlReportTitle: string;\n htmlGenerated: (file: string) => string;\n htmlOpenError: (file: string) => string;\n htmlConfigSection: string;\n htmlSummarySection: string;\n htmlChartsSection: string;\n htmlDetailsSection: string;\n htmlTestTime: string;\n htmlMetric: string;\n htmlValue: string;\n htmlRun: string;\n htmlTokens: string;\n htmlDuration: string;\n htmlSpeed: string;\n htmlTps: string;\n htmlAverageTps: string;\n htmlTpsDistribution: string;\n htmlTimeUnit: string;\n htmlTpsChartHover: string;\n}\n\nconst zhMessages: Messages = {\n defaultPrompt: \"写一篇关于 AI 的短文\",\n appTitle: \"🚀 Token 速度测试工具\",\n runningTests: \"⏳ 正在运行测试...\",\n streamingOutput: \"模型输出 (流式):\",\n testComplete: \"✅ 测试完成!\",\n errorPrefix: \"❌ 错误\",\n unknownError: \"❌ 发生未知错误\",\n configLabels: {\n provider: \"Provider\",\n model: \"Model\",\n maxTokens: \"Max Tokens\",\n runs: \"Runs\",\n prompt: \"Prompt\",\n },\n runLabel: (index: number) => `[运行 ${index}]`,\n runProgressLabel: (current: number, total: number) => `[运行 ${current}/${total}]`,\n reportTitle: \"Token 速度测试报告\",\n speedChartTitle: \"Token 速度趋势图 (TPS)\",\n tpsHistogramTitle: \"TPS 分布\",\n noChartData: \"没有可用于图表的数据\",\n noTpsData: \"没有 TPS 数据可用\",\n statsSummaryTitle: (sampleSize: number) => `统计汇总 (N=${sampleSize})`,\n statsHeaders: {\n metric: \"指标\",\n mean: \"均值\",\n min: \"最小值\",\n max: \"最大值\",\n stdDev: \"标准差\",\n },\n statsLabels: {\n ttft: \"TTFT (ms)\",\n totalTime: \"总耗时 (ms)\",\n totalTokens: \"总 Token 数\",\n averageSpeed: \"平均速度\",\n peakSpeed: \"峰值速度\",\n peakTps: \"峰值 TPS\",\n },\n resultLabels: {\n ttft: \"TTFT\",\n totalTime: \"总耗时\",\n totalTokens: \"总 Token 数\",\n averageSpeed: \"平均速度\",\n peakSpeed: \"峰值速度\",\n peakTps: \"峰值 TPS\",\n },\n htmlTitle: \"Token 速度测试报告\",\n htmlReportTitle: \"LLM API Token 流式性能测试报告\",\n htmlGenerated: (file: string) => `✓ HTML 报告已生成: ${file}`,\n htmlOpenError: (file: string) => `无法自动打开报告,请手动打开: ${file}`,\n htmlConfigSection: \"测试配置\",\n htmlSummarySection: \"统计汇总\",\n htmlChartsSection: \"图表分析\",\n htmlDetailsSection: \"详细数据\",\n htmlTestTime: \"测试时间\",\n htmlMetric: \"指标\",\n htmlValue: \"数值\",\n htmlRun: \"运行\",\n htmlTokens: \"Token 数\",\n htmlDuration: \"耗时\",\n htmlSpeed: \"速度\",\n htmlTps: \"TPS\",\n htmlAverageTps: \"平均 TPS\",\n htmlTpsDistribution: \"TPS 分布\",\n htmlTimeUnit: \"秒\",\n htmlTpsChartHover: \"次\",\n};\n\nconst enMessages: Messages = {\n defaultPrompt: \"Write a short essay about AI\",\n appTitle: \"🚀 Token Speed Test\",\n runningTests: \"⏳ Running tests...\",\n streamingOutput: \"Model output (streaming):\",\n testComplete: \"✅ Tests complete!\",\n errorPrefix: \"❌ Error\",\n unknownError: \"❌ An unknown error occurred\",\n configLabels: {\n provider: \"Provider\",\n model: \"Model\",\n maxTokens: \"Max Tokens\",\n runs: \"Runs\",\n prompt: \"Prompt\",\n },\n runLabel: (index: number) => `[Run ${index}]`,\n runProgressLabel: (current: number, total: number) => `[Run ${current}/${total}]`,\n reportTitle: \"Token Speed Test Report\",\n speedChartTitle: \"Token Speed Trend (TPS)\",\n tpsHistogramTitle: \"TPS Distribution\",\n noChartData: \"No data available for chart\",\n noTpsData: \"No TPS data available\",\n statsSummaryTitle: (sampleSize: number) => `Summary (N=${sampleSize})`,\n statsHeaders: {\n metric: \"Metric\",\n mean: \"Mean\",\n min: \"Min\",\n max: \"Max\",\n stdDev: \"Std Dev\",\n },\n statsLabels: {\n ttft: \"TTFT (ms)\",\n totalTime: \"Total Time (ms)\",\n totalTokens: \"Total Tokens\",\n averageSpeed: \"Avg Speed\",\n peakSpeed: \"Peak Speed\",\n peakTps: \"Peak TPS\",\n },\n resultLabels: {\n ttft: \"TTFT\",\n totalTime: \"Total Time\",\n totalTokens: \"Total Tokens\",\n averageSpeed: \"Avg Speed\",\n peakSpeed: \"Peak Speed\",\n peakTps: \"Peak TPS\",\n },\n htmlTitle: \"Token Speed Test Report\",\n htmlReportTitle: \"LLM API Token Streaming Performance Report\",\n htmlGenerated: (file: string) => `✓ HTML report generated: ${file}`,\n htmlOpenError: (file: string) => `Could not auto-open report, please open manually: ${file}`,\n htmlConfigSection: \"Test Configuration\",\n htmlSummarySection: \"Summary Statistics\",\n htmlChartsSection: \"Chart Analysis\",\n htmlDetailsSection: \"Detailed Data\",\n htmlTestTime: \"Test Time\",\n htmlMetric: \"Metric\",\n htmlValue: \"Value\",\n htmlRun: \"Run\",\n htmlTokens: \"Tokens\",\n htmlDuration: \"Duration\",\n htmlSpeed: \"Speed\",\n htmlTps: \"TPS\",\n htmlAverageTps: \"Average TPS\",\n htmlTpsDistribution: \"TPS Distribution\",\n htmlTimeUnit: \"s\",\n htmlTpsChartHover: \"count\",\n};\n\nexport function isSupportedLang(value: string): value is Lang {\n return SUPPORTED_LANGS.includes(value as Lang);\n}\n\nexport function resolveLang(value?: string): Lang {\n if (!value) {\n return DEFAULT_LANG;\n }\n const normalized = value.toLowerCase();\n if (!isSupportedLang(normalized)) {\n throw new Error(`Invalid lang: ${value}. Must be 'zh' or 'en'.`);\n }\n return normalized;\n}\n\nexport function getMessages(lang: Lang): Messages {\n return lang === \"en\" ? enMessages : zhMessages;\n}\n","import { getMessages, resolveLang, type Lang } from \"./i18n.js\";\n\nexport type Provider = \"anthropic\" | \"openai\";\n\nexport interface Config {\n provider: Provider;\n apiKey: string;\n baseURL?: string;\n model: string;\n maxTokens: number;\n runCount: number;\n prompt: string;\n lang: Lang;\n}\n\nexport interface ParsedArgs {\n apiKey: string;\n provider?: Provider;\n url?: string;\n model?: string;\n maxTokens?: number;\n runs?: number;\n prompt?: string;\n lang?: string;\n}\n\nconst DEFAULT_MODELS: Record<Provider, string> = {\n anthropic: \"claude-opus-4-5-20251101\",\n openai: \"gpt-5.2\",\n};\n\nconst DEFAULT_MAX_TOKENS = 1024;\nconst DEFAULT_RUNS = 3;\n/**\n * 解析命令行参数并生成配置\n */\nexport function parseConfig(args: ParsedArgs): Config {\n const {\n apiKey,\n provider = \"anthropic\",\n url,\n model,\n maxTokens = DEFAULT_MAX_TOKENS,\n runs = DEFAULT_RUNS,\n prompt,\n lang: langInput,\n } = args;\n const lang = resolveLang(langInput);\n const messages = getMessages(lang);\n const finalPrompt = prompt ?? messages.defaultPrompt;\n\n // 验证必填参数\n if (!apiKey || apiKey.trim() === \"\") {\n throw new Error(\"API Key is required. Use --api-key or -k to provide it.\");\n }\n\n // 验证 provider\n if (provider !== \"anthropic\" && provider !== \"openai\") {\n throw new Error(`Invalid provider: ${provider}. Must be 'anthropic' or 'openai'.`);\n }\n\n // 验证数值参数\n if (!Number.isFinite(maxTokens) || !Number.isInteger(maxTokens) || maxTokens <= 0) {\n throw new Error(`Invalid max-tokens: ${maxTokens}. Must be a positive integer.`);\n }\n\n if (!Number.isFinite(runs) || !Number.isInteger(runs) || runs <= 0) {\n throw new Error(`Invalid runs: ${runs}. Must be a positive integer.`);\n }\n\n // 使用默认模型或用户指定的模型\n const finalModel = model || DEFAULT_MODELS[provider];\n\n return {\n provider,\n apiKey: apiKey.trim(),\n baseURL: url?.trim(),\n model: finalModel,\n maxTokens,\n runCount: runs,\n prompt: finalPrompt.trim(),\n lang,\n };\n}\n\n/**\n * 获取默认模型名称\n */\nexport function getDefaultModel(provider: Provider): string {\n return DEFAULT_MODELS[provider];\n}\n\n/**\n * 验证配置有效性\n */\nexport function validateConfig(config: Config): { valid: boolean; error?: string } {\n if (!config.apiKey) {\n return { valid: false, error: \"API Key is required\" };\n }\n\n if (config.provider !== \"anthropic\" && config.provider !== \"openai\") {\n return { valid: false, error: `Invalid provider: ${config.provider}` };\n }\n\n if (\n !Number.isFinite(config.maxTokens) ||\n !Number.isInteger(config.maxTokens) ||\n config.maxTokens <= 0\n ) {\n return { valid: false, error: \"maxTokens must be a positive integer\" };\n }\n\n if (\n !Number.isFinite(config.runCount) ||\n !Number.isInteger(config.runCount) ||\n config.runCount <= 0\n ) {\n return { valid: false, error: \"runCount must be a positive integer\" };\n }\n\n if (!config.prompt || config.prompt.trim() === \"\") {\n return { valid: false, error: \"prompt cannot be empty\" };\n }\n\n if (config.lang !== \"zh\" && config.lang !== \"en\") {\n return { valid: false, error: `Invalid lang: ${config.lang}` };\n }\n\n return { valid: true };\n}\n// test\n","import { performance } from \"node:perf_hooks\";\nimport Anthropic from \"@anthropic-ai/sdk\";\nimport OpenAI from \"openai\";\nimport type { Config } from \"./config.js\";\nimport { getMessages } from \"./i18n.js\";\nimport type { StreamMetrics } from \"./metrics.js\";\nimport { createTokenizer } from \"./tokenizer.js\";\n\n/**\n * 执行 Anthropic API 流式测试\n */\nexport async function anthropicStreamTest(config: Config): Promise<StreamMetrics> {\n const startTime = performance.now();\n const tokenTimes: number[] = [];\n let ttft = 0;\n let firstTokenRecorded = false;\n let tokenCount = 0;\n let wroteOutput = false;\n\n const encoding = createTokenizer(config.model);\n const client = new Anthropic({\n apiKey: config.apiKey,\n baseURL: config.baseURL,\n });\n\n try {\n const stream = await client.messages.create({\n model: config.model,\n max_tokens: config.maxTokens,\n messages: [{ role: \"user\", content: config.prompt }],\n stream: true,\n });\n\n for await (const event of stream) {\n const currentTime = performance.now();\n\n if (event.type === \"content_block_delta\" && event.delta.type === \"text_delta\") {\n const text = event.delta.text;\n\n if (text && text.length > 0) {\n process.stdout.write(text);\n wroteOutput = true;\n const encoded = encoding.encode(text);\n const newTokens = encoded.length;\n\n if (newTokens > 0) {\n if (!firstTokenRecorded) {\n ttft = currentTime - startTime;\n firstTokenRecorded = true;\n }\n\n // 为当前批次的新增 token 记录到达时间\n for (let i = 0; i < newTokens; i++) {\n tokenTimes.push(currentTime - startTime);\n }\n\n tokenCount += newTokens;\n }\n }\n }\n }\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Anthropic API error: ${error.message}`);\n }\n throw error;\n } finally {\n if (wroteOutput) {\n process.stdout.write(\"\\n\");\n }\n encoding.free();\n }\n\n const endTime = performance.now();\n const totalTime = endTime - startTime;\n\n return {\n ttft,\n tokens: tokenTimes,\n totalTokens: tokenCount,\n totalTime,\n };\n}\n\n/**\n * 执行 OpenAI API 流式测试\n */\nexport async function openaiStreamTest(config: Config): Promise<StreamMetrics> {\n const startTime = performance.now();\n const tokenTimes: number[] = [];\n let ttft = 0;\n let firstTokenRecorded = false;\n let tokenCount = 0;\n let wroteOutput = false;\n\n const encoding = createTokenizer(config.model);\n const client = new OpenAI({\n apiKey: config.apiKey,\n baseURL: config.baseURL,\n });\n\n try {\n const stream = await client.chat.completions.create({\n model: config.model,\n max_tokens: config.maxTokens,\n messages: [{ role: \"user\", content: config.prompt }],\n stream: true,\n });\n\n for await (const chunk of stream) {\n const currentTime = performance.now();\n\n const delta = chunk.choices[0]?.delta;\n\n if (delta?.content) {\n const content = delta.content;\n\n if (content.length > 0) {\n process.stdout.write(content);\n wroteOutput = true;\n const encoded = encoding.encode(content);\n const newTokens = encoded.length;\n\n if (newTokens > 0) {\n if (!firstTokenRecorded) {\n ttft = currentTime - startTime;\n firstTokenRecorded = true;\n }\n\n // 为当前批次的新增 token 记录到达时间\n for (let i = 0; i < newTokens; i++) {\n tokenTimes.push(currentTime - startTime);\n }\n\n tokenCount += newTokens;\n }\n }\n }\n }\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`OpenAI API error: ${error.message}`);\n }\n throw error;\n } finally {\n if (wroteOutput) {\n process.stdout.write(\"\\n\");\n }\n encoding.free();\n }\n\n const endTime = performance.now();\n const totalTime = endTime - startTime;\n\n return {\n ttft,\n tokens: tokenTimes,\n totalTokens: tokenCount,\n totalTime,\n };\n}\n\n/**\n * 根据配置执行流式测试\n */\nexport async function streamTest(config: Config): Promise<StreamMetrics> {\n if (config.provider === \"anthropic\") {\n return anthropicStreamTest(config);\n } else {\n return openaiStreamTest(config);\n }\n}\n\n/**\n * 执行多次测试\n */\nexport async function runMultipleTests(config: Config): Promise<StreamMetrics[]> {\n const results: StreamMetrics[] = [];\n const messages = getMessages(config.lang);\n\n for (let i = 0; i < config.runCount; i++) {\n if (config.runCount > 1) {\n const label = `\\n${messages.runProgressLabel(i + 1, config.runCount)}`;\n console.log(label);\n console.log(\"-\".repeat(label.length - 1));\n }\n const result = await streamTest(config);\n results.push(result);\n }\n\n return results;\n}\n","import { encoding_for_model, get_encoding } from \"tiktoken\";\nimport type { TiktokenModel } from \"tiktoken\";\n\nconst FALLBACK_ENCODING = \"cl100k_base\";\n\nexport function createTokenizer(model: string) {\n try {\n const normalized = model.trim();\n if (!normalized) {\n return get_encoding(FALLBACK_ENCODING);\n }\n return encoding_for_model(normalized as TiktokenModel);\n } catch {\n return get_encoding(FALLBACK_ENCODING);\n }\n}\n","/**\n * 单次流式测试的原始计时数据\n */\nexport interface StreamMetrics {\n ttft: number; // Time to First Token (ms)\n tokens: number[]; // 每个 token 的到达时间(相对开始时间,单位:ms)\n totalTokens: number;\n totalTime: number;\n}\n\n/**\n * 计算后的统计指标\n */\nexport interface CalculatedMetrics {\n ttft: number; // Time to First Token (ms)\n totalTime: number; // 总耗时 (ms)\n totalTokens: number; // 总 token 数\n averageSpeed: number; // 平均速度 (tokens/s)\n peakSpeed: number; // 峰值速度 (tokens/s)\n peakTps: number; // 峰值 TPS (tokens/s)\n tps: number[]; // 每秒 token 数 (TPS curve)\n}\n\n/**\n * 多次测试的统计结果\n */\nexport interface StatsResult {\n mean: CalculatedMetrics;\n min: CalculatedMetrics;\n max: CalculatedMetrics;\n stdDev: CalculatedMetrics;\n sampleSize: number;\n}\n\n/**\n * 计算 TTFT (Time to First Token)\n */\nexport function calculateTTFT(metrics: StreamMetrics): number {\n return metrics.ttft;\n}\n\n/**\n * 计算平均速度 (tokens/s)\n */\nexport function calculateAverageSpeed(metrics: StreamMetrics): number {\n if (metrics.totalTime <= 0) {\n return 0;\n }\n return (metrics.totalTokens / metrics.totalTime) * 1000;\n}\n\n/**\n * 计算峰值速度 - 最快连续 N 个 token 的平均速度\n */\nconst MIN_PEAK_WINDOW_MS = 50;\n\nexport function calculatePeakSpeed(metrics: StreamMetrics, windowSize: number = 10): number {\n if (metrics.tokens.length < windowSize) {\n // 如果 token 数少于窗口大小,使用全部 token 计算\n if (metrics.tokens.length < 2) {\n return 0;\n }\n const totalTime = metrics.tokens[metrics.tokens.length - 1] - metrics.tokens[0];\n const durationMs = Math.max(totalTime, MIN_PEAK_WINDOW_MS);\n return ((metrics.tokens.length - 1) / durationMs) * 1000;\n }\n\n let maxSpeed = 0;\n for (let i = 0; i <= metrics.tokens.length - windowSize; i++) {\n const startTime = metrics.tokens[i];\n const endTime = metrics.tokens[i + windowSize - 1];\n const duration = endTime - startTime;\n const durationMs = Math.max(duration, MIN_PEAK_WINDOW_MS);\n const speed = ((windowSize - 1) / durationMs) * 1000;\n maxSpeed = Math.max(maxSpeed, speed);\n }\n\n return maxSpeed;\n}\n\n/**\n * 计算 TPS (Tokens Per Second) 曲线\n */\nexport function calculateTPS(metrics: StreamMetrics): number[] {\n if (metrics.tokens.length === 0) {\n return [];\n }\n\n const totalDuration = metrics.tokens[metrics.tokens.length - 1];\n const totalSeconds = Math.ceil(totalDuration / 1000);\n\n if (totalSeconds <= 0) {\n return metrics.tokens.length > 0 ? [metrics.tokens.length] : [];\n }\n\n const tps: number[] = new Array(totalSeconds).fill(0);\n\n // 计算每秒内的 token 数\n for (const tokenTime of metrics.tokens) {\n const secondIndex = Math.floor(tokenTime / 1000);\n if (secondIndex < tps.length) {\n tps[secondIndex]++;\n }\n }\n\n return tps;\n}\n\n/**\n * 从 StreamMetrics 计算完整的指标\n */\nexport function calculateMetrics(metrics: StreamMetrics): CalculatedMetrics {\n const tps = calculateTPS(metrics);\n return {\n ttft: calculateTTFT(metrics),\n totalTime: metrics.totalTime,\n totalTokens: metrics.totalTokens,\n averageSpeed: calculateAverageSpeed(metrics),\n peakSpeed: calculatePeakSpeed(metrics),\n peakTps: tps.length > 0 ? Math.max(...tps) : 0,\n tps,\n };\n}\n\n/**\n * 计算一组数值的平均值\n */\nfunction mean(values: number[]): number {\n if (values.length === 0) return 0;\n return values.reduce((sum, v) => sum + v, 0) / values.length;\n}\n\n/**\n * 计算一组数值的标准差\n */\nfunction standardDeviation(values: number[]): number {\n if (values.length < 2) return 0;\n const avg = mean(values);\n const squareDiffs = values.map((v) => Math.pow(v - avg, 2));\n return Math.sqrt(mean(squareDiffs));\n}\n\n/**\n * 从多个 CalculatedMetrics 计算统计结果\n */\nexport function calculateStats(allMetrics: CalculatedMetrics[]): StatsResult {\n if (allMetrics.length === 0) {\n throw new Error(\"Cannot calculate stats from empty metrics array\");\n }\n\n const sampleSize = allMetrics.length;\n\n // 提取各项指标的数组\n const ttfts = allMetrics.map((m) => m.ttft);\n const totalTimes = allMetrics.map((m) => m.totalTime);\n const totalTokens = allMetrics.map((m) => m.totalTokens);\n const averageSpeeds = allMetrics.map((m) => m.averageSpeed);\n const peakSpeeds = allMetrics.map((m) => m.peakSpeed);\n const peakTpsValues = allMetrics.map((m) => m.peakTps);\n\n // 找到最长的 TPS 数组\n const maxTpsLength = Math.max(...allMetrics.map((m) => m.tps.length));\n const avgTps: number[] = [];\n for (let i = 0; i < maxTpsLength; i++) {\n const values = allMetrics.map((m) => m.tps[i] ?? 0);\n avgTps.push(mean(values));\n }\n\n return {\n mean: {\n ttft: mean(ttfts),\n totalTime: mean(totalTimes),\n totalTokens: mean(totalTokens),\n averageSpeed: mean(averageSpeeds),\n peakSpeed: mean(peakSpeeds),\n peakTps: mean(peakTpsValues),\n tps: avgTps,\n },\n min: {\n ttft: Math.min(...ttfts),\n totalTime: Math.min(...totalTimes),\n totalTokens: Math.min(...totalTokens),\n averageSpeed: Math.min(...averageSpeeds),\n peakSpeed: Math.min(...peakSpeeds),\n peakTps: Math.min(...peakTpsValues),\n tps: [],\n },\n max: {\n ttft: Math.max(...ttfts),\n totalTime: Math.max(...totalTimes),\n totalTokens: Math.max(...totalTokens),\n averageSpeed: Math.max(...averageSpeeds),\n peakSpeed: Math.max(...peakSpeeds),\n peakTps: Math.max(...peakTpsValues),\n tps: [],\n },\n stdDev: {\n ttft: standardDeviation(ttfts),\n totalTime: standardDeviation(totalTimes),\n totalTokens: standardDeviation(totalTokens),\n averageSpeed: standardDeviation(averageSpeeds),\n peakSpeed: standardDeviation(peakSpeeds),\n peakTps: standardDeviation(peakTpsValues),\n tps: [],\n },\n sampleSize,\n };\n}\n\n/**\n * 格式化速度显示\n */\nexport function formatSpeed(tokensPerSecond: number): string {\n return tokensPerSecond.toFixed(2);\n}\n\n/**\n * 格式化时间显示\n */\nexport function formatTime(ms: number): string {\n if (ms < 1000) {\n return `${ms.toFixed(0)}ms`;\n }\n return `${(ms / 1000).toFixed(2)}s`;\n}\n","import stringWidth from \"string-width\";\nimport type { CalculatedMetrics, StatsResult } from \"./metrics.js\";\nimport { DEFAULT_LANG, getMessages, type Lang } from \"./i18n.js\";\n\nconst BLOCK_CHAR = \"█\";\nconst CHART_WIDTH = 50;\nconst CHART_HEIGHT = 10;\nconst STAT_LABEL_WIDTH = 15;\nconst STAT_VALUE_WIDTH = 10;\nconst Y_LABEL_WIDTH = 4;\n\nfunction padEndWidth(text: string, width: number): string {\n const currentWidth = stringWidth(text);\n if (currentWidth >= width) {\n return text;\n }\n return text + \" \".repeat(width - currentWidth);\n}\n\nfunction padStartWidth(text: string, width: number): string {\n const currentWidth = stringWidth(text);\n if (currentWidth >= width) {\n return text;\n }\n return \" \".repeat(width - currentWidth) + text;\n}\n\n/**\n * 渲染速度趋势图\n */\nexport function renderSpeedChart(\n tps: number[],\n maxSpeed?: number,\n lang: Lang = DEFAULT_LANG\n): string {\n const messages = getMessages(lang);\n if (tps.length === 0) {\n return messages.noChartData;\n }\n\n const actualMax = maxSpeed ?? Math.max(...tps, 1);\n const maxVal = Math.max(actualMax, 1);\n\n const buildRow = (label: string, bars: string) =>\n `│ ${padStartWidth(label, Y_LABEL_WIDTH)} ┤${bars} │`;\n const emptyRow = buildRow(\"0\", \" \".repeat(CHART_WIDTH));\n const chartWidth = stringWidth(emptyRow) - 2;\n const axisPrefix = `│ ${padStartWidth(\"\", Y_LABEL_WIDTH)} ┼`;\n\n const lines: string[] = [];\n lines.push(messages.speedChartTitle);\n\n // Y 轴标签和边框\n lines.push(\"┌\" + \"─\".repeat(chartWidth) + \"┐\");\n\n for (let row = CHART_HEIGHT - 1; row >= 0; row--) {\n const value = (row / (CHART_HEIGHT - 1)) * maxVal;\n const label = value.toFixed(0);\n\n let bars = \"\";\n for (let col = 0; col < CHART_WIDTH; col++) {\n const index = Math.floor((col / CHART_WIDTH) * tps.length);\n const tpsValue = tps[index] ?? 0;\n const normalizedHeight = (tpsValue / maxVal) * (CHART_HEIGHT - 1);\n bars += normalizedHeight >= row ? BLOCK_CHAR : \" \";\n }\n\n lines.push(buildRow(label, bars));\n }\n\n // X 轴\n lines.push(`${axisPrefix}${\"─\".repeat(CHART_WIDTH)} │`);\n lines.push(\"└\" + \"─\".repeat(chartWidth) + \"┘\");\n\n // X 轴标签 (时间标记)\n const xLabels = generateXLabels(tps.length, 6);\n const labelLine = new Array(CHART_WIDTH).fill(\" \");\n const maxIndex = Math.max(tps.length - 1, 1);\n for (const label of xLabels) {\n const seconds = parseInt(label.replace(\"s\", \"\"), 10);\n const position = Math.min(\n CHART_WIDTH - 1,\n Math.round((seconds / maxIndex) * (CHART_WIDTH - 1))\n );\n for (let i = 0; i < label.length && position + i < CHART_WIDTH; i++) {\n labelLine[position + i] = label[i];\n }\n }\n lines.push(\" \".repeat(stringWidth(axisPrefix)) + labelLine.join(\"\"));\n\n return lines.join(\"\\n\");\n}\n\n/**\n * 生成 X 轴时间标签\n */\nfunction generateXLabels(dataPoints: number, maxLabels: number): string[] {\n if (dataPoints <= 1) {\n return [\"0s\"];\n }\n\n const labels: string[] = [];\n const step = Math.max(1, Math.floor(dataPoints / maxLabels));\n\n for (let i = 0; i < dataPoints; i += step) {\n labels.push(`${i}s`);\n }\n\n // 确保最后一个标签是结束时间\n if (labels[labels.length - 1] !== `${dataPoints - 1}s`) {\n labels.push(`${dataPoints - 1}s`);\n }\n\n return labels;\n}\n\n/**\n * 渲染 TPS 直方图\n */\nexport function renderTPSHistogram(tps: number[], lang: Lang = DEFAULT_LANG): string {\n const messages = getMessages(lang);\n if (tps.length === 0) {\n return messages.noTpsData;\n }\n\n const lines: string[] = [];\n lines.push(messages.tpsHistogramTitle);\n\n // 计算分布区间\n const maxTps = Math.max(...tps, 1);\n const buckets = 10;\n const bucketSize = maxTps / buckets;\n const histogram = new Array(buckets).fill(0);\n\n for (const t of tps) {\n const bucketIndex = Math.min(Math.floor(t / bucketSize), buckets - 1);\n histogram[bucketIndex]++;\n }\n\n const maxCount = Math.max(...histogram, 1);\n\n const labels = histogram.map((_, i) => {\n const bucketStart = (i * bucketSize).toFixed(1);\n const bucketEnd = ((i + 1) * bucketSize).toFixed(1);\n return `${bucketStart}-${bucketEnd}`;\n });\n const labelWidth = Math.max(...labels.map((l) => stringWidth(l)));\n\n for (let i = 0; i < buckets; i++) {\n const label = padEndWidth(labels[i], labelWidth);\n const count = histogram[i];\n const barLength = Math.round((count / maxCount) * CHART_WIDTH);\n const bar = BLOCK_CHAR.repeat(barLength);\n\n const countSuffix = count > 0 ? ` ${count}` : \"\";\n lines.push(`${label} │${bar}${countSuffix}`);\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * 渲染统计汇总表\n */\nexport function renderStatsTable(stats: StatsResult, lang: Lang = DEFAULT_LANG): string {\n const messages = getMessages(lang);\n const lines: string[] = [];\n lines.push(\"\");\n lines.push(messages.statsSummaryTitle(stats.sampleSize));\n\n // 表头\n const headerRow =\n \"│ \" +\n padEndWidth(messages.statsHeaders.metric, STAT_LABEL_WIDTH) +\n \" │ \" +\n padStartWidth(messages.statsHeaders.mean, STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(messages.statsHeaders.min, STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(messages.statsHeaders.max, STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(messages.statsHeaders.stdDev, STAT_VALUE_WIDTH) +\n \" │\";\n\n const tableWidth = stringWidth(headerRow) - 2;\n lines.push(\"┌\" + \"─\".repeat(tableWidth) + \"┐\");\n lines.push(headerRow);\n lines.push(\"├\" + \"─\".repeat(tableWidth) + \"┤\");\n\n // TTFT\n lines.push(\n formatStatRow(\n messages.statsLabels.ttft,\n stats.mean.ttft,\n stats.min.ttft,\n stats.max.ttft,\n stats.stdDev.ttft,\n \"f\"\n )\n );\n lines.push(\"├\" + \"─\".repeat(tableWidth) + \"┤\");\n\n // 总耗时\n lines.push(\n formatStatRow(\n messages.statsLabels.totalTime,\n stats.mean.totalTime,\n stats.min.totalTime,\n stats.max.totalTime,\n stats.stdDev.totalTime,\n \"f\"\n )\n );\n lines.push(\"├\" + \"─\".repeat(tableWidth) + \"┤\");\n\n // 总 token 数\n lines.push(\n formatStatRow(\n messages.statsLabels.totalTokens,\n stats.mean.totalTokens,\n stats.min.totalTokens,\n stats.max.totalTokens,\n stats.stdDev.totalTokens,\n \"f\"\n )\n );\n lines.push(\"├\" + \"─\".repeat(tableWidth) + \"┤\");\n\n // 平均速度\n lines.push(\n formatStatRow(\n messages.statsLabels.averageSpeed,\n stats.mean.averageSpeed,\n stats.min.averageSpeed,\n stats.max.averageSpeed,\n stats.stdDev.averageSpeed,\n \"f\"\n )\n );\n lines.push(\"├\" + \"─\".repeat(tableWidth) + \"┤\");\n\n // 峰值速度\n lines.push(\n formatStatRow(\n messages.statsLabels.peakSpeed,\n stats.mean.peakSpeed,\n stats.min.peakSpeed,\n stats.max.peakSpeed,\n stats.stdDev.peakSpeed,\n \"f\"\n )\n );\n\n lines.push(\"├\" + \"─\".repeat(tableWidth) + \"┤\");\n\n // 峰值 TPS\n lines.push(\n formatStatRow(\n messages.statsLabels.peakTps,\n stats.mean.peakTps,\n stats.min.peakTps,\n stats.max.peakTps,\n stats.stdDev.peakTps,\n \"f\"\n )\n );\n\n lines.push(\"└\" + \"─\".repeat(tableWidth) + \"┘\");\n\n return lines.join(\"\\n\");\n}\n\n/**\n * 格式化统计表格的一行\n */\nfunction formatStatRow(\n label: string,\n mean: number,\n min: number,\n max: number,\n stdDev: number,\n format: \"f\" | \"d\"\n): string {\n const fmt = (n: number) => (format === \"f\" ? n.toFixed(2) : n.toFixed(0));\n\n return (\n \"│ \" +\n padEndWidth(label, STAT_LABEL_WIDTH) +\n \" │ \" +\n padStartWidth(fmt(mean), STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(fmt(min), STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(fmt(max), STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(fmt(stdDev), STAT_VALUE_WIDTH) +\n \" │\"\n );\n}\n\n/**\n * 格式化时间显示(带小数)\n */\nfunction formatTimeWithDecimals(ms: number): string {\n if (ms === Math.floor(ms)) {\n return `${ms.toFixed(0)}ms`;\n }\n return `${ms.toFixed(2)}ms`;\n}\n\n/**\n * 渲染单次测试结果\n */\nexport function renderSingleResult(\n metrics: CalculatedMetrics,\n runIndex: number,\n lang: Lang = DEFAULT_LANG\n): string {\n const messages = getMessages(lang);\n const lines: string[] = [];\n lines.push(`\\n${messages.runLabel(runIndex + 1)}`);\n lines.push(` ${messages.resultLabels.ttft}: ${formatTimeWithDecimals(metrics.ttft)}`);\n lines.push(` ${messages.resultLabels.totalTime}: ${formatTimeWithDecimals(metrics.totalTime)}`);\n lines.push(` ${messages.resultLabels.totalTokens}: ${metrics.totalTokens}`);\n lines.push(\n ` ${messages.resultLabels.averageSpeed}: ${metrics.averageSpeed.toFixed(2)} tokens/s`\n );\n lines.push(` ${messages.resultLabels.peakSpeed}: ${metrics.peakSpeed.toFixed(2)} tokens/s`);\n lines.push(` ${messages.resultLabels.peakTps}: ${metrics.peakTps.toFixed(2)} tokens/s`);\n return lines.join(\"\\n\");\n}\n\n/**\n * 渲染完整的测试报告\n */\nexport function renderReport(stats: StatsResult, lang: Lang = DEFAULT_LANG): string {\n const messages = getMessages(lang);\n const lines: string[] = [];\n\n lines.push(\"\\n\" + \"═\".repeat(72));\n lines.push(messages.reportTitle);\n lines.push(\"═\".repeat(72));\n\n // 汇总统计\n lines.push(renderStatsTable(stats, lang));\n\n // 速度趋势图\n if (stats.mean.tps.length > 0) {\n lines.push(\"\\n\" + renderSpeedChart(stats.mean.tps, undefined, lang));\n }\n\n // TPS 直方图\n if (stats.mean.tps.length > 0) {\n lines.push(\"\\n\" + renderTPSHistogram(stats.mean.tps, lang));\n }\n\n return lines.join(\"\\n\");\n}\n","import { readFileSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { Config } from \"./config.js\";\nimport type { CalculatedMetrics, StatsResult } from \"./metrics.js\";\nimport type { Lang, Messages } from \"./i18n.js\";\n\nconst CHART_COLORS = [\n \"#00f5ff\", // cyan\n \"#ff00aa\", // magenta\n \"#ffcc00\", // yellow\n \"#00ff88\", // green\n \"#ff6600\", // orange\n \"#aa00ff\", // purple\n];\n\nconst PALETTE = {\n bg: \"#0a0a0f\",\n bgSecondary: \"#12121a\",\n bgCard: \"#1a1a24\",\n border: \"#2a2a3a\",\n text: \"#e4e4eb\",\n textMuted: \"#6a6a7a\",\n accent: \"#00f5ff\",\n accentSecondary: \"#ff00aa\",\n accentTertiary: \"#ffcc00\",\n};\n\ninterface HTMLReportOptions {\n config: Config;\n singleResults: CalculatedMetrics[];\n stats: StatsResult;\n lang: Lang;\n messages: Messages;\n}\n\ninterface TemplateData {\n lang: Lang;\n title: string;\n reportTitle: string;\n testTimeLabel: string;\n testTime: string;\n configSection: string;\n summarySection: string;\n chartsSection: string;\n statsTitle: string;\n detailsSection: string;\n configGrid: string;\n summaryCards: string;\n charts: string;\n statsTable: string;\n detailsTable: string;\n}\n\nfunction formatNumber(num: number, decimals: number = 2): string {\n return num.toFixed(decimals);\n}\n\nfunction formatTime(ms: number): string {\n if (ms < 1000) {\n return `${ms.toFixed(0)}ms`;\n }\n return `${(ms / 1000).toFixed(2)}s`;\n}\n\nfunction escapeHtml(text: string): string {\n const map: Record<string, string> = {\n \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n '\"': \"&quot;\",\n \"'\": \"&#039;\",\n };\n return text.replace(/[&<>\"']/g, (m) => map[m]);\n}\n\nfunction loadTemplate(): string {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = dirname(__filename);\n return readFileSync(resolve(__dirname, \"template.html\"), \"utf-8\");\n}\n\nfunction replaceTemplate(template: string, data: TemplateData): string {\n let result = template;\n for (const [key, value] of Object.entries(data)) {\n result = result.replaceAll(new RegExp(`{{${key}}}`, \"g\"), value);\n }\n return result;\n}\n\nfunction generateSpeedChart(results: CalculatedMetrics[], messages: Messages): string {\n const allTps = results.flatMap((r) => r.tps);\n if (allTps.length === 0) {\n return `<div class=\"no-data\">${messages.noChartData || \"No data available\"}</div>`;\n }\n\n const maxTps = Math.max(...allTps, 1);\n const maxDuration = Math.max(...results.map((r) => r.tps.length));\n const width = 800;\n const height = 320;\n const padding = { top: 30, right: 30, bottom: 45, left: 55 };\n const chartWidth = width - padding.left - padding.right;\n const chartHeight = height - padding.top - padding.bottom;\n\n const avgTps: number[] = [];\n for (let i = 0; i < maxDuration; i++) {\n const values = results.map((r) => r.tps[i] ?? 0);\n avgTps.push(values.reduce((a, b) => a + b, 0) / values.length);\n }\n\n const polylines = results\n .map((result, idx) => {\n const color = CHART_COLORS[idx % CHART_COLORS.length];\n let points = \"\";\n let areaPoints = `${padding.left},${height - padding.bottom} `;\n\n result.tps.forEach((tps, i) => {\n const x = padding.left + (i / Math.max(maxDuration - 1, 1)) * chartWidth;\n const y = padding.top + chartHeight - (tps / maxTps) * chartHeight;\n points += `${x},${y} `;\n areaPoints += `${x},${y} `;\n });\n areaPoints += `${padding.left + chartWidth},${height - padding.bottom}`;\n\n return `\n <defs>\n <linearGradient id=\"grad-${idx}\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\">\n <stop offset=\"0%\" style=\"stop-color:${color};stop-opacity:0.3\"/>\n <stop offset=\"100%\" style=\"stop-color:${color};stop-opacity:0\"/>\n </linearGradient>\n </defs>\n <polygon\n points=\"${areaPoints.trim()}\"\n fill=\"url(#grad-${idx})\"\n class=\"area-${idx}\"\n />\n <polyline\n fill=\"none\"\n stroke=\"${color}\"\n stroke-width=\"2.5\"\n points=\"${points.trim()}\"\n class=\"line line-${idx}\"\n data-run=\"${idx + 1}\"\n >\n <animate\n attributeName=\"stroke-dasharray\"\n from=\"0,2000\"\n to=\"2000,0\"\n dur=\"1.5s\"\n fill=\"freeze\"\n calcMode=\"spline\"\n keySplines=\"0.4 0 0.2 1\"\n />\n </polyline>\n ${result.tps\n .map((tps, i) => {\n const x = padding.left + (i / Math.max(maxDuration - 1, 1)) * chartWidth;\n const y = padding.top + chartHeight - (tps / maxTps) * chartHeight;\n return `<circle cx=\"${x}\" cy=\"${y}\" r=\"4\" fill=\"${PALETTE.bg}\" stroke=\"${color}\" stroke-width=\"2\" class=\"dot-${idx}\" opacity=\"0\"><title>${messages.htmlAverageTps || \"Avg TPS\"} ${i}s: ${tps.toFixed(1)}</title>\n <animate attributeName=\"opacity\" from=\"0\" to=\"1\" begin=\"${0.5 + i * 0.05}s\" dur=\"0.3s\" fill=\"freeze\"/>\n </circle>`;\n })\n .join(\"\")}\n `;\n })\n .join(\"\\n \");\n\n let avgPoints = \"\";\n avgTps.forEach((tps, i) => {\n const x = padding.left + (i / Math.max(maxDuration - 1, 1)) * chartWidth;\n const y = padding.top + chartHeight - (tps / maxTps) * chartHeight;\n avgPoints += `${x},${y} `;\n });\n\n const yLabels = [];\n for (let i = 0; i <= 5; i++) {\n const value = Math.round((maxTps * i) / 5);\n const y = padding.top + chartHeight - (i / 5) * chartHeight;\n yLabels.push(\n `<text x=\"${padding.left - 12}\" y=\"${y + 4}\" text-anchor=\"end\" font-size=\"11\" fill=\"${PALETTE.textMuted}\">${value}</text>`\n );\n if (i > 0) {\n const yLine = padding.top + chartHeight - (i / 5) * chartHeight;\n yLabels.push(\n `<line x1=\"${padding.left}\" y1=\"${yLine}\" x2=\"${width - padding.right}\" y2=\"${yLine}\" stroke=\"${PALETTE.border}\" stroke-width=\"1\" opacity=\"0.5\"/>`\n );\n }\n }\n\n const xLabels = [];\n const xSteps = Math.min(maxDuration, 10);\n for (let i = 0; i < xSteps; i++) {\n const x = padding.left + (i / Math.max(xSteps - 1, 1)) * chartWidth;\n const label = i.toString();\n xLabels.push(\n `<text x=\"${x}\" y=\"${height - padding.bottom + 20}\" text-anchor=\"middle\" font-size=\"11\" fill=\"${PALETTE.textMuted}\">${label}${messages.htmlTimeUnit}</text>`\n );\n }\n\n return `\n <svg viewBox=\"0 0 ${width} ${height}\" class=\"chart\" id=\"speedChart\">\n <style>\n #speedChart .line { stroke-dasharray: 2000; stroke-dashoffset: 0; }\n #speedChart .line:hover { stroke-width: 4; filter: drop-shadow(0 0 8px currentColor); }\n #speedChart circle { transition: all 0.2s ease; cursor: pointer; }\n #speedChart circle:hover { r: 6; stroke-width: 3; }\n </style>\n <rect x=\"${padding.left}\" y=\"${padding.top}\" width=\"${chartWidth}\" height=\"${chartHeight}\" fill=\"${PALETTE.bgCard}\" rx=\"4\"/>\n ${yLabels.join(\"\\n \")}\n ${xLabels.join(\"\\n \")}\n <line x1=\"${padding.left}\" y1=\"${padding.top}\" x2=\"${padding.left}\" y2=\"${height - padding.bottom}\" stroke=\"${PALETTE.border}\" stroke-width=\"2\"/>\n <line x1=\"${padding.left}\" y1=\"${height - padding.bottom}\" x2=\"${width - padding.right}\" y2=\"${height - padding.bottom}\" stroke=\"${PALETTE.border}\" stroke-width=\"2\"/>\n ${polylines}\n <polyline\n fill=\"none\"\n stroke=\"${PALETTE.text}\"\n stroke-width=\"2\"\n stroke-dasharray=\"6,4\"\n opacity=\"0.7\"\n points=\"${avgPoints.trim()}\"\n />\n <text x=\"${padding.left + chartWidth / 2}\" y=\"${height - 8}\" text-anchor=\"middle\" font-size=\"11\" fill=\"${PALETTE.textMuted}\">TIME (${messages.htmlTimeUnit})</text>\n <text x=\"12\" y=\"${padding.top + chartHeight / 2}\" text-anchor=\"middle\" font-size=\"11\" fill=\"${PALETTE.textMuted}\" transform=\"rotate(-90, 12, ${padding.top + chartHeight / 2})\">TPS</text>\n </svg>\n <div class=\"chart-legend\">\n <div class=\"legend-item\">\n <span class=\"legend-line avg\"></span>\n <span>${messages.htmlSummarySection}</span>\n </div>\n ${results\n .map((_, idx) => {\n const color = CHART_COLORS[idx % CHART_COLORS.length];\n return `\n <div class=\"legend-item\">\n <span class=\"legend-line\" style=\"background: ${color};\"></span>\n <span>${messages.htmlRun} ${idx + 1}</span>\n </div>`;\n })\n .join(\"\")}\n </div>\n `;\n}\n\nfunction generateTPSHistogram(stats: StatsResult, messages: Messages): string {\n const allTps = stats.mean.tps;\n if (allTps.length === 0) {\n return `<div class=\"no-data\">${messages.noTpsData || \"No TPS data available\"}</div>`;\n }\n\n const maxTps = Math.max(...allTps);\n const width = 400;\n const height = 280;\n const padding = { top: 25, right: 20, bottom: 40, left: 50 };\n const chartWidth = width - padding.left - padding.right;\n const chartHeight = height - padding.top - padding.bottom;\n\n const bars = allTps\n .map((tps, i) => {\n const barWidth = chartWidth / allTps.length - 2;\n const x = padding.left + (i / allTps.length) * chartWidth;\n const barHeight = (tps / maxTps) * chartHeight;\n const y = padding.top + chartHeight - barHeight;\n const hue = 180 + (tps / maxTps) * 60;\n const color = `hsl(${hue}, 100%, 60%)`;\n\n return `\n <rect\n x=\"${x}\"\n y=\"${y}\"\n width=\"${barWidth}\"\n height=\"${barHeight}\"\n fill=\"${color}\"\n class=\"bar\"\n data-second=\"${i}\"\n data-tps=\"${tps.toFixed(2)}\"\n rx=\"2\"\n >\n <title>${messages.htmlAverageTps || \"Average TPS\"} ${i}s: ${tps.toFixed(1)}</title>\n <animate\n attributeName=\"height\"\n from=\"0\"\n to=\"${barHeight}\"\n dur=\"0.8s\"\n fill=\"freeze\"\n calcMode=\"spline\"\n keySplines=\"0.4 0 0.2 1\"\n begin=\"${i * 0.05}s\"\n />\n <animate\n attributeName=\"y\"\n from=\"${height - padding.bottom}\"\n to=\"${y}\"\n dur=\"0.8s\"\n fill=\"freeze\"\n calcMode=\"spline\"\n keySplines=\"0.4 0 0.2 1\"\n begin=\"${i * 0.05}s\"\n />\n </rect>\n `;\n })\n .join(\"\");\n\n const yLabels = [];\n for (let i = 0; i <= 5; i++) {\n const value = Math.round((maxTps * i) / 5);\n const y = padding.top + chartHeight - (i / 5) * chartHeight;\n yLabels.push(\n `<text x=\"${padding.left - 10}\" y=\"${y + 4}\" text-anchor=\"end\" font-size=\"11\" fill=\"${PALETTE.textMuted}\">${value}</text>`\n );\n }\n\n const xLabels = [];\n const xSteps = Math.min(allTps.length, 8);\n for (let i = 0; i < xSteps; i++) {\n const x = padding.left + (i / Math.max(xSteps - 1, 1)) * chartWidth;\n const label = i.toString();\n xLabels.push(\n `<text x=\"${x}\" y=\"${height - padding.bottom + 18}\" text-anchor=\"middle\" font-size=\"11\" fill=\"${PALETTE.textMuted}\">${label}${messages.htmlTimeUnit}</text>`\n );\n }\n\n return `\n <svg viewBox=\"0 0 ${width} ${height}\" class=\"chart\" id=\"tpsChart\">\n <style>\n #tpsChart .bar { transition: all 0.2s ease; cursor: pointer; opacity: 0.9; }\n #tpsChart .bar:hover { opacity: 1; filter: brightness(1.2); }\n </style>\n <rect x=\"${padding.left}\" y=\"${padding.top}\" width=\"${chartWidth}\" height=\"${chartHeight}\" fill=\"${PALETTE.bgCard}\" rx=\"4\"/>\n ${yLabels.join(\"\\n \")}\n ${xLabels.join(\"\\n \")}\n <line x1=\"${padding.left}\" y1=\"${padding.top}\" x2=\"${padding.left}\" y2=\"${height - padding.bottom}\" stroke=\"${PALETTE.border}\" stroke-width=\"2\"/>\n <line x1=\"${padding.left}\" y1=\"${height - padding.bottom}\" x2=\"${width - padding.right}\" y2=\"${height - padding.bottom}\" stroke=\"${PALETTE.border}\" stroke-width=\"2\"/>\n ${bars}\n </svg>\n `;\n}\n\nexport function generateHTMLReport(options: HTMLReportOptions): string {\n const { config, singleResults, stats, lang, messages } = options;\n\n const isZh = lang === \"zh\";\n const testTime = new Date().toLocaleString(isZh ? \"zh-CN\" : \"en-US\");\n\n const summaryCards = [\n {\n label: messages.statsLabels.ttft,\n value: formatTime(stats.mean.ttft),\n detail: `${messages.statsHeaders.min}: ${formatTime(stats.min.ttft)} · ${messages.statsHeaders.max}: ${formatTime(stats.max.ttft)}`,\n accent: PALETTE.accent,\n },\n {\n label: messages.statsLabels.averageSpeed,\n value: formatNumber(stats.mean.averageSpeed),\n detail: `${messages.statsHeaders.min}: ${formatNumber(stats.min.averageSpeed)} · ${messages.statsHeaders.max}: ${formatNumber(stats.max.averageSpeed)}`,\n accent: PALETTE.accentSecondary,\n unit: messages.htmlSpeed,\n },\n {\n label: messages.statsLabels.peakSpeed,\n value: formatNumber(stats.mean.peakSpeed),\n detail: `${messages.statsHeaders.min}: ${formatNumber(stats.min.peakSpeed)} · ${messages.statsHeaders.max}: ${formatNumber(stats.max.peakSpeed)}`,\n accent: PALETTE.accentTertiary,\n unit: messages.htmlSpeed,\n },\n {\n label: messages.statsLabels.totalTokens,\n value: formatNumber(stats.mean.totalTokens, 0),\n detail: `${messages.statsHeaders.min}: ${formatNumber(stats.min.totalTokens, 0)} · ${messages.statsHeaders.max}: ${formatNumber(stats.max.totalTokens, 0)}`,\n accent: \"#00ff88\",\n },\n ];\n\n const detailRows = singleResults\n .map(\n (result, idx) => `\n <tr>\n <td><span class=\"run-badge\">${idx + 1}</span></td>\n <td>${formatTime(result.ttft)}</td>\n <td>${formatTime(result.totalTime)}</td>\n <td>${result.totalTokens}</td>\n <td>${formatNumber(result.averageSpeed)}</td>\n <td>${formatNumber(result.peakSpeed)}</td>\n <td>${result.peakTps}</td>\n </tr>\n `\n )\n .join(\"\");\n\n const statsRows = [\n {\n metric: messages.statsLabels.ttft,\n mean: formatTime(stats.mean.ttft),\n min: formatTime(stats.min.ttft),\n max: formatTime(stats.max.ttft),\n stdDev: formatTime(stats.stdDev.ttft),\n },\n {\n metric: messages.statsLabels.totalTime,\n mean: formatTime(stats.mean.totalTime),\n min: formatTime(stats.min.totalTime),\n max: formatTime(stats.max.totalTime),\n stdDev: formatTime(stats.stdDev.totalTime),\n },\n {\n metric: messages.statsLabels.totalTokens,\n mean: formatNumber(stats.mean.totalTokens, 1),\n min: formatNumber(stats.min.totalTokens, 0),\n max: formatNumber(stats.max.totalTokens, 0),\n stdDev: formatNumber(stats.stdDev.totalTokens, 1),\n },\n {\n metric: messages.statsLabels.averageSpeed,\n mean: formatNumber(stats.mean.averageSpeed),\n min: formatNumber(stats.min.averageSpeed),\n max: formatNumber(stats.max.averageSpeed),\n stdDev: formatNumber(stats.stdDev.averageSpeed),\n },\n {\n metric: messages.statsLabels.peakSpeed,\n mean: formatNumber(stats.mean.peakSpeed),\n min: formatNumber(stats.min.peakSpeed),\n max: formatNumber(stats.max.peakSpeed),\n stdDev: formatNumber(stats.stdDev.peakSpeed),\n },\n {\n metric: messages.statsLabels.peakTps,\n mean: formatNumber(stats.mean.peakTps),\n min: formatNumber(stats.min.peakTps),\n max: formatNumber(stats.max.peakTps),\n stdDev: formatNumber(stats.stdDev.peakTps),\n },\n ]\n .map(\n (row) => `\n <tr>\n <td class=\"metric-name\">${row.metric}</td>\n <td class=\"value-primary\">${row.mean}</td>\n <td>${row.min}</td>\n <td>${row.max}</td>\n <td>${row.stdDev}</td>\n </tr>\n `\n )\n .join(\"\");\n\n const speedChart = generateSpeedChart(singleResults, messages);\n const tpsChart = generateTPSHistogram(stats, messages);\n\n const configGridHtml = `\n <div class=\"config-grid\">\n <div class=\"config-item\">\n <span class=\"config-label\">${messages.configLabels.provider}</span>\n <span class=\"config-value\">${config.provider.toUpperCase()}</span>\n </div>\n <div class=\"config-item\">\n <span class=\"config-label\">${messages.configLabels.model}</span>\n <span class=\"config-value\">${escapeHtml(config.model)}</span>\n </div>\n <div class=\"config-item\">\n <span class=\"config-label\">${messages.configLabels.maxTokens}</span>\n <span class=\"config-value\">${config.maxTokens}</span>\n </div>\n <div class=\"config-item\">\n <span class=\"config-label\">${messages.configLabels.runs}</span>\n <span class=\"config-value\">${config.runCount}</span>\n </div>\n <div class=\"config-item wide\">\n <span class=\"config-label\">${messages.configLabels.prompt}</span>\n <span class=\"config-value\">\"${escapeHtml(config.prompt)}\"</span>\n </div>\n </div>\n `;\n\n const summaryCardsHtml = summaryCards\n .map(\n (card) => `\n <div class=\"card\" style=\"--card-accent: ${card.accent}\">\n <div class=\"card-label\">${card.label}</div>\n <div class=\"card-value\">${card.value}<span class=\"card-unit\">${card.unit || \"\"}</span></div>\n <div class=\"card-detail\">${card.detail}</div>\n </div>\n `\n )\n .join(\"\");\n\n const chartsHtml = `\n <div class=\"charts-container\">\n <div class=\"chart-wrapper\">\n <div class=\"chart-title\">${messages.speedChartTitle}</div>\n ${speedChart}\n </div>\n <div class=\"chart-wrapper\">\n <div class=\"chart-title\">${messages.htmlTpsDistribution}</div>\n ${tpsChart}\n </div>\n </div>\n `;\n\n const statsTableHtml = `\n <div class=\"table-wrapper\">\n <table>\n <thead>\n <tr>\n <th>${messages.statsHeaders.metric}</th>\n <th>${messages.statsHeaders.mean}</th>\n <th>${messages.statsHeaders.min}</th>\n <th>${messages.statsHeaders.max}</th>\n <th>${messages.statsHeaders.stdDev}</th>\n </tr>\n </thead>\n <tbody>\n ${statsRows}\n </tbody>\n </table>\n </div>\n `;\n\n const detailsTableHtml = `\n <div class=\"table-wrapper\">\n <table>\n <thead>\n <tr>\n <th>${messages.htmlRun}</th>\n <th>${messages.resultLabels.ttft}</th>\n <th>${messages.resultLabels.totalTime}</th>\n <th>${messages.resultLabels.totalTokens}</th>\n <th>${messages.resultLabels.averageSpeed}</th>\n <th>${messages.resultLabels.peakSpeed}</th>\n <th>${messages.resultLabels.peakTps}</th>\n </tr>\n </thead>\n <tbody>\n ${detailRows}\n </tbody>\n </table>\n </div>\n `;\n\n const data: TemplateData = {\n lang,\n title: messages.htmlTitle,\n reportTitle: messages.htmlReportTitle,\n testTimeLabel: messages.htmlTestTime,\n testTime,\n configSection: messages.htmlConfigSection,\n summarySection: messages.htmlSummarySection,\n chartsSection: messages.htmlChartsSection,\n statsTitle: messages.statsSummaryTitle(stats.sampleSize),\n detailsSection: messages.htmlDetailsSection,\n configGrid: configGridHtml,\n summaryCards: summaryCardsHtml,\n charts: chartsHtml,\n statsTable: statsTableHtml,\n detailsTable: detailsTableHtml,\n };\n\n const template = loadTemplate();\n return replaceTemplate(template, data);\n}\n"],"mappings":"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","names":["readFileSync","dirname","fileURLToPath","mean","options","dirname","fileURLToPath","readFileSync"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "token-speed-tester",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "A CLI tool to test LLM API token output speed",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -52,6 +52,7 @@
52
52
  "chalk": "^5.4.1",
53
53
  "cli-table3": "^0.6.5",
54
54
  "commander": "^12.1.0",
55
+ "open": "^11.0.0",
55
56
  "openai": "^4.77.3",
56
57
  "string-width": "^7.2.0",
57
58
  "tiktoken": "^1.0.22"
@@ -62,6 +63,7 @@
62
63
  "@semantic-release/git": "^10.0.1",
63
64
  "@semantic-release/npm": "^13.1.3",
64
65
  "@types/node": "^22.10.6",
66
+ "@types/open": "^6.1.0",
65
67
  "@vitest/coverage-v8": "^2.1.8",
66
68
  "@vitest/ui": "^2.1.8",
67
69
  "eslint": "^9.39.2",