token-speed-tester 1.8.0 → 1.10.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/README.en.md CHANGED
@@ -24,7 +24,7 @@ A powerful command-line tool for testing token output speed of LLM APIs. Support
24
24
  - **Peak Speed**: Fastest speed over a 10-token window
25
25
  - **Peak TPS**: Highest tokens received within a single second
26
26
  - **TPS Curve**: Tokens received per second throughout the stream
27
- - **Statistical Analysis**: Mean, min, max, and standard deviation across multiple test runs
27
+ - **Statistical Analysis**: Mean, P50/P95/P99, min, and max across multiple test runs
28
28
  - **ASCII Visualization**: Beautiful terminal-based charts and tables
29
29
  - **HTML Report**: Generate interactive HTML reports with SVG charts
30
30
  - **Custom Endpoints**: Test third-party APIs compatible with OpenAI/Anthropic protocols
@@ -85,14 +85,26 @@ token-speed-test \
85
85
  # Generate HTML report (with SVG charts)
86
86
  token-speed-test \
87
87
  --api-key sk-ant-xxx \
88
- --html \
88
+ --output-format html \
89
89
  --output my-report.html
90
90
 
91
+ # Generate JSON report
92
+ token-speed-test \
93
+ --api-key sk-ant-xxx \
94
+ --output-format json \
95
+ --output report.json
96
+
97
+ # Generate CSV report
98
+ token-speed-test \
99
+ --api-key sk-ant-xxx \
100
+ --output-format csv \
101
+ --output report.csv
102
+
91
103
  # Combined: Generate English HTML report
92
104
  token-speed-test \
93
105
  --api-key sk-ant-xxx \
94
106
  --runs 5 \
95
- --html \
107
+ --output-format html \
96
108
  -o performance-report.html \
97
109
  --lang en
98
110
  ```
@@ -115,18 +127,18 @@ node dist/index.js --api-key sk-ant-xxx
115
127
 
116
128
  ## Command Line Options
117
129
 
118
- | Option | Short | Description | Default |
119
- | -------------- | ----- | --------------------------------- | ------------------------- |
120
- | `--api-key` | `-k` | API Key (required) | - |
121
- | `--provider` | `-p` | API type: `anthropic` or `openai` | `anthropic` |
122
- | `--model` | `-m` | Model name | Auto-selected by provider |
123
- | `--url` | `-u` | Custom API endpoint | Official endpoint |
124
- | `--runs` | `-r` | Number of test runs | `3` |
125
- | `--prompt` | | Test prompt | Language-specific |
126
- | `--max-tokens` | | Maximum output tokens | `1024` |
127
- | `--lang` | | Output language: `zh` or `en` | `zh` |
128
- | `--html` | | Generate HTML report | `false` |
129
- | `--output` | `-o` | HTML report output path | `report.html` |
130
+ | Option | Short | Description | Default |
131
+ | ----------------- | ----- | --------------------------------------------- | ------------------------- |
132
+ | `--api-key` | `-k` | API Key (required) | - |
133
+ | `--provider` | `-p` | API type: `anthropic` or `openai` | `anthropic` |
134
+ | `--model` | `-m` | Model name | Auto-selected by provider |
135
+ | `--url` | `-u` | Custom API endpoint | Official endpoint |
136
+ | `--runs` | `-r` | Number of test runs | `3` |
137
+ | `--prompt` | | Test prompt | Language-specific |
138
+ | `--max-tokens` | | Maximum output tokens | `1024` |
139
+ | `--lang` | | Output language: `zh` or `en` | `zh` |
140
+ | `--output-format` | `-f` | Output format: `terminal`/`json`/`csv`/`html` | `html` |
141
+ | `--output` | `-o` | Output file path (default `report.{ext}`) | `report.{ext}` |
130
142
 
131
143
  Note: The default prompt follows the selected language. Use `--lang en` for the English default prompt.
132
144
 
@@ -173,16 +185,16 @@ Model output (streaming):
173
185
  Token Speed Test Report
174
186
  ======================================================================
175
187
  Summary (N=3)
176
- +-----------------+--------+-------+-------+---------+
177
- | Metric | Mean | Min | Max | Std Dev |
178
- +-----------------+--------+-------+-------+---------+
179
- | TTFT (ms) | 503.67 | 487.00| 523.00| 14.57 |
180
- | Total Time (ms) | 3248.67| 3189.00|3312.00|51.92 |
181
- | Total Tokens | 405.00 | 398.00| 412.00| 5.35 |
182
- | Avg Speed | 124.69 | 122.28| 126.96| 1.88 |
183
- | Peak Speed | 156.32 | 154.23| 158.41| 1.82 |
184
- | Peak TPS | 168.33 | 166.00| 171.00| 2.05 |
185
- +-----------------+--------+-------+-------+---------+
188
+ +-----------------------------------------------------------------------------------+
189
+ | Metric | Mean | P50 | P95 | P99 | Min | Max |
190
+ +-----------------------------------------------------------------------------------+
191
+ | TTFT (ms) | 503.67 | 501.00 | 520.00 | 523.00 | 487.00 | 523.00 |
192
+ | Total Time (ms) | 3248.67 | 3245.00 | 3312.00 | 3312.00 | 3189.00 | 3312.00 |
193
+ | Total Tokens | 405.00 | 405.00 | 412.00 | 412.00 | 398.00 | 412.00 |
194
+ | Avg Speed | 124.69 | 124.84 | 126.96 | 126.96 | 122.28 | 126.96 |
195
+ | Peak Speed | 156.32 | 156.32 | 158.41 | 158.41 | 154.23 | 158.41 |
196
+ | Peak TPS | 168.33 | 168.00 | 171.00 | 171.00 | 166.00 | 171.00 |
197
+ +-----------------------------------------------------------------------------------+
186
198
  Token Speed Trend (TPS)
187
199
  [chart omitted]
188
200
  TPS Distribution
@@ -193,7 +205,7 @@ Tests complete!
193
205
 
194
206
  ### HTML Report
195
207
 
196
- Use the `--html` option to generate a beautiful HTML report that includes:
208
+ Use the `--output-format html` option to generate a beautiful HTML report that includes:
197
209
 
198
210
  - **Speed Trend Chart**: Multi-run speed curves with SVG animations
199
211
  - **TPS Distribution**: Histogram of tokens per second
package/README.md CHANGED
@@ -24,7 +24,7 @@
24
24
  - **峰值速度**:10 个 Token 滑动窗口内的最快速度
25
25
  - **峰值 TPS**:单秒内的最高 Token 数
26
26
  - **TPS 曲线**:整个流式响应中每秒接收的 Token 数
27
- - **统计分析**:多次测试运行的均值、最小值、最大值和标准差
27
+ - **统计分析**:多次测试运行的均值、P50/P95/P99、最小值和最大值
28
28
  - **ASCII 可视化**:精美的终端图表和数据表格
29
29
  - **HTML 报告**:生成包含 SVG 图表的交互式 HTML 报告
30
30
  - **自定义端点**:测试兼容 OpenAI/Anthropic 协议的第三方 API
@@ -86,14 +86,26 @@ token-speed-test \
86
86
  # 生成 HTML 报告(包含 SVG 图表)
87
87
  token-speed-test \
88
88
  --api-key sk-ant-xxx \
89
- --html \
89
+ --output-format html \
90
90
  --output my-report.html
91
91
 
92
+ # 生成 JSON 报告
93
+ token-speed-test \
94
+ --api-key sk-ant-xxx \
95
+ --output-format json \
96
+ --output report.json
97
+
98
+ # 生成 CSV 报告
99
+ token-speed-test \
100
+ --api-key sk-ant-xxx \
101
+ --output-format csv \
102
+ --output report.csv
103
+
92
104
  # 组合使用:生成英文 HTML 报告
93
105
  token-speed-test \
94
106
  --api-key sk-ant-xxx \
95
107
  --runs 5 \
96
- --html \
108
+ --output-format html \
97
109
  -o performance-report.html \
98
110
  --lang en
99
111
  ```
@@ -116,18 +128,18 @@ node dist/index.js --api-key sk-ant-xxx
116
128
 
117
129
  ## 命令行选项
118
130
 
119
- | 选项 | 简写 | 说明 | 默认值 |
120
- | -------------- | ---- | --------------------------------- | ---------------------- |
121
- | `--api-key` | `-k` | API Key(必填) | - |
122
- | `--provider` | `-p` | API 类型:`anthropic` 或 `openai` | `anthropic` |
123
- | `--model` | `-m` | 模型名称 | 根据提供商自动选择 |
124
- | `--url` | `-u` | 自定义 API 端点 | 官方端点 |
125
- | `--runs` | `-r` | 测试次数 | `3` |
126
- | `--prompt` | | 测试提示词 | "写一篇关于 AI 的短文" |
127
- | `--max-tokens` | | 最大输出 Token 数 | `1024` |
128
- | `--lang` | | 输出语言: `zh` 或 `en` | `zh` |
129
- | `--html` | | 生成 HTML 报告 | `false` |
130
- | `--output` | `-o` | HTML 报告输出路径 | `report.html` |
131
+ | 选项 | 简写 | 说明 | 默认值 |
132
+ | ----------------- | ---- | ---------------------------------------- | ---------------------- |
133
+ | `--api-key` | `-k` | API Key(必填) | - |
134
+ | `--provider` | `-p` | API 类型:`anthropic` 或 `openai` | `anthropic` |
135
+ | `--model` | `-m` | 模型名称 | 根据提供商自动选择 |
136
+ | `--url` | `-u` | 自定义 API 端点 | 官方端点 |
137
+ | `--runs` | `-r` | 测试次数 | `3` |
138
+ | `--prompt` | | 测试提示词 | "写一篇关于 AI 的短文" |
139
+ | `--max-tokens` | | 最大输出 Token 数 | `1024` |
140
+ | `--lang` | | 输出语言: `zh` 或 `en` | `zh` |
141
+ | `--output-format` | `-f` | 输出格式:`terminal`/`json`/`csv`/`html` | `html` |
142
+ | `--output` | `-o` | 输出文件路径(默认 `report.{ext}`) | `report.{ext}` |
131
143
 
132
144
  ### 默认模型
133
145
 
@@ -180,19 +192,19 @@ Token 速度测试报告
180
192
 
181
193
  统计汇总 (N=3)
182
194
  ┌──────────────────────────────────────────────────────────────────────┐
183
- │ 指标 │ 均值 │ 最小值 │ 最大值 │ 标准差 │
195
+ │ 指标 │ 均值 │ P50 │ P95 │ P99 │ 最小值 │ 最大值 │
184
196
  ├──────────────────────────────────────────────────────────────────────┤
185
- │ TTFT (ms) │ 503.67 │ 487.00 │ 523.00 │ 14.57
197
+ │ TTFT (ms) │ 503.67 │ 501.00 │ 520.00 │ 523.00 │ 487.00 523.00 │
186
198
  ├──────────────────────────────────────────────────────────────────────┤
187
- │ 总耗时 (ms) │ 3248.67 │ 3189.00 │ 3312.00 │ 51.92
199
+ │ 总耗时 (ms) │ 3248.67 │ 3245.00 │ 3312.00 │ 3312.00 3189.00 │ 3312.00 │
188
200
  ├──────────────────────────────────────────────────────────────────────┤
189
- │ 总 Token 数 │ 405.00 │ 398.00 │ 412.00 │ 5.35
201
+ │ 总 Token 数 │ 405.00 │ 405.00 │ 412.00 │ 412.00 398.00 │ 412.00 │
190
202
  ├──────────────────────────────────────────────────────────────────────┤
191
- │ 平均速度 │ 124.69 │ 122.28 126.96 │ 1.88
203
+ │ 平均速度 │ 124.69 │ 124.84 126.96 │ 126.96 122.28 │ 126.96 │
192
204
  ├──────────────────────────────────────────────────────────────────────┤
193
- │ 峰值速度 │ 156.32 │ 154.23 158.41 │ 1.82
205
+ │ 峰值速度 │ 156.32 │ 156.32 158.41 │ 158.41 154.23 │ 158.41 │
194
206
  ├──────────────────────────────────────────────────────────────────────┤
195
- │ 峰值 TPS │ 168.33 │ 166.00 │ 171.00 │ 2.05
207
+ │ 峰值 TPS │ 168.33 │ 168.00 │ 171.00 │ 171.00 166.00 │ 171.00 │
196
208
  └──────────────────────────────────────────────────────────────────────┘
197
209
 
198
210
  Token 速度趋势图 (TPS)
@@ -224,7 +236,7 @@ TPS 分布
224
236
 
225
237
  ### HTML 报告
226
238
 
227
- 使用 `--html` 选项可以生成精美的 HTML 报告,报告包含:
239
+ 使用 `--output-format html` 选项可以生成精美的 HTML 报告,报告包含:
228
240
 
229
241
  - **速度趋势图**:多条运行的速度曲线对比(SVG 动画)
230
242
  - **TPS 分布图**:每秒 Token 数的直方图
package/dist/index.js CHANGED
@@ -185,7 +185,9 @@ function parseConfig(args) {
185
185
  maxTokens = DEFAULT_MAX_TOKENS,
186
186
  runs = DEFAULT_RUNS,
187
187
  prompt,
188
- lang: langInput
188
+ lang: langInput,
189
+ outputFormat = "html",
190
+ outputPath
189
191
  } = args;
190
192
  const lang = resolveLang(langInput);
191
193
  const messages = getMessages(lang);
@@ -202,7 +204,22 @@ function parseConfig(args) {
202
204
  if (!Number.isFinite(runs) || !Number.isInteger(runs) || runs <= 0) {
203
205
  throw new Error(`Invalid runs: ${runs}. Must be a positive integer.`);
204
206
  }
207
+ if (outputFormat && !["terminal", "json", "csv", "html"].includes(outputFormat)) {
208
+ throw new Error(
209
+ `Invalid output-format: ${outputFormat}. Must be 'terminal', 'json', 'csv', or 'html'.`
210
+ );
211
+ }
205
212
  const finalModel = model || DEFAULT_MODELS[provider];
213
+ let finalOutputPath = "report";
214
+ if (outputPath) {
215
+ finalOutputPath = outputPath;
216
+ } else if (outputFormat === "html") {
217
+ finalOutputPath = "report.html";
218
+ } else if (outputFormat === "json") {
219
+ finalOutputPath = "report.json";
220
+ } else if (outputFormat === "csv") {
221
+ finalOutputPath = "report.csv";
222
+ }
206
223
  return {
207
224
  provider,
208
225
  apiKey: apiKey.trim(),
@@ -211,7 +228,9 @@ function parseConfig(args) {
211
228
  maxTokens,
212
229
  runCount: runs,
213
230
  prompt: finalPrompt.trim(),
214
- lang
231
+ lang,
232
+ outputFormat,
233
+ outputPath: finalOutputPath
215
234
  };
216
235
  }
217
236
 
@@ -1289,6 +1308,121 @@ function generateHTMLReport(options2) {
1289
1308
  return replaceTemplate(template, data);
1290
1309
  }
1291
1310
 
1311
+ // src/export.ts
1312
+ function generateJSONExport(config, results, stats) {
1313
+ const exportData = {
1314
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1315
+ config: {
1316
+ provider: config.provider,
1317
+ model: config.model,
1318
+ maxTokens: config.maxTokens,
1319
+ runCount: config.runCount,
1320
+ prompt: config.prompt
1321
+ },
1322
+ runs: results.map((r) => ({
1323
+ ttft: Math.round(r.ttft * 100) / 100,
1324
+ totalTime: Math.round(r.totalTime * 100) / 100,
1325
+ totalTokens: r.totalTokens,
1326
+ averageSpeed: Math.round(r.averageSpeed * 100) / 100,
1327
+ peakSpeed: Math.round(r.peakSpeed * 100) / 100,
1328
+ peakTps: Math.round(r.peakTps * 100) / 100,
1329
+ tps: r.tps
1330
+ })),
1331
+ stats: {
1332
+ mean: {
1333
+ ttft: Math.round(stats.mean.ttft * 100) / 100,
1334
+ totalTime: Math.round(stats.mean.totalTime * 100) / 100,
1335
+ totalTokens: Math.round(stats.mean.totalTokens * 100) / 100,
1336
+ averageSpeed: Math.round(stats.mean.averageSpeed * 100) / 100,
1337
+ peakSpeed: Math.round(stats.mean.peakSpeed * 100) / 100,
1338
+ peakTps: Math.round(stats.mean.peakTps * 100) / 100
1339
+ },
1340
+ min: {
1341
+ ttft: Math.round(stats.min.ttft * 100) / 100,
1342
+ totalTime: Math.round(stats.min.totalTime * 100) / 100,
1343
+ totalTokens: stats.min.totalTokens,
1344
+ averageSpeed: Math.round(stats.min.averageSpeed * 100) / 100,
1345
+ peakSpeed: Math.round(stats.min.peakSpeed * 100) / 100,
1346
+ peakTps: Math.round(stats.min.peakTps * 100) / 100
1347
+ },
1348
+ max: {
1349
+ ttft: Math.round(stats.max.ttft * 100) / 100,
1350
+ totalTime: Math.round(stats.max.totalTime * 100) / 100,
1351
+ totalTokens: stats.max.totalTokens,
1352
+ averageSpeed: Math.round(stats.max.averageSpeed * 100) / 100,
1353
+ peakSpeed: Math.round(stats.max.peakSpeed * 100) / 100,
1354
+ peakTps: Math.round(stats.max.peakTps * 100) / 100
1355
+ },
1356
+ p50: {
1357
+ ttft: Math.round(stats.percentiles.ttft.p50 * 100) / 100,
1358
+ totalTime: Math.round(stats.percentiles.totalTime.p50 * 100) / 100,
1359
+ totalTokens: Math.round(stats.percentiles.totalTokens.p50 * 100) / 100,
1360
+ averageSpeed: Math.round(stats.percentiles.averageSpeed.p50 * 100) / 100,
1361
+ peakSpeed: Math.round(stats.percentiles.peakSpeed.p50 * 100) / 100,
1362
+ peakTps: Math.round(stats.percentiles.peakTps.p50 * 100) / 100
1363
+ },
1364
+ p95: {
1365
+ ttft: Math.round(stats.percentiles.ttft.p95 * 100) / 100,
1366
+ totalTime: Math.round(stats.percentiles.totalTime.p95 * 100) / 100,
1367
+ totalTokens: Math.round(stats.percentiles.totalTokens.p95 * 100) / 100,
1368
+ averageSpeed: Math.round(stats.percentiles.averageSpeed.p95 * 100) / 100,
1369
+ peakSpeed: Math.round(stats.percentiles.peakSpeed.p95 * 100) / 100,
1370
+ peakTps: Math.round(stats.percentiles.peakTps.p95 * 100) / 100
1371
+ },
1372
+ p99: {
1373
+ ttft: Math.round(stats.percentiles.ttft.p99 * 100) / 100,
1374
+ totalTime: Math.round(stats.percentiles.totalTime.p99 * 100) / 100,
1375
+ totalTokens: Math.round(stats.percentiles.totalTokens.p99 * 100) / 100,
1376
+ averageSpeed: Math.round(stats.percentiles.averageSpeed.p99 * 100) / 100,
1377
+ peakSpeed: Math.round(stats.percentiles.peakSpeed.p99 * 100) / 100,
1378
+ peakTps: Math.round(stats.percentiles.peakTps.p99 * 100) / 100
1379
+ }
1380
+ }
1381
+ };
1382
+ return JSON.stringify(exportData, null, 2);
1383
+ }
1384
+ function generateCSVExport(config, results, stats) {
1385
+ const lines = [];
1386
+ lines.push("# Token Speed Test Results");
1387
+ lines.push(`# Timestamp: ${(/* @__PURE__ */ new Date()).toISOString()}`);
1388
+ lines.push(`# Provider: ${config.provider}`);
1389
+ lines.push(`# Model: ${config.model}`);
1390
+ lines.push(`# Runs: ${config.runCount}`);
1391
+ lines.push(`# Prompt: ${config.prompt}`);
1392
+ lines.push("");
1393
+ lines.push("# Statistics");
1394
+ lines.push("Metric,Mean,P50,P95,P99,Min,Max");
1395
+ lines.push(
1396
+ `TTFT (ms),${stats.mean.ttft.toFixed(2)},${stats.percentiles.ttft.p50.toFixed(2)},${stats.percentiles.ttft.p95.toFixed(2)},${stats.percentiles.ttft.p99.toFixed(2)},${stats.min.ttft.toFixed(2)},${stats.max.ttft.toFixed(2)}`
1397
+ );
1398
+ lines.push(
1399
+ `Total Time (ms),${stats.mean.totalTime.toFixed(2)},${stats.percentiles.totalTime.p50.toFixed(2)},${stats.percentiles.totalTime.p95.toFixed(2)},${stats.percentiles.totalTime.p99.toFixed(2)},${stats.min.totalTime.toFixed(2)},${stats.max.totalTime.toFixed(2)}`
1400
+ );
1401
+ lines.push(
1402
+ `Total Tokens,${stats.mean.totalTokens.toFixed(2)},${stats.percentiles.totalTokens.p50.toFixed(2)},${stats.percentiles.totalTokens.p95.toFixed(2)},${stats.percentiles.totalTokens.p99.toFixed(2)},${stats.min.totalTokens},${stats.max.totalTokens}`
1403
+ );
1404
+ lines.push(
1405
+ `Average Speed (tokens/s),${stats.mean.averageSpeed.toFixed(2)},${stats.percentiles.averageSpeed.p50.toFixed(2)},${stats.percentiles.averageSpeed.p95.toFixed(2)},${stats.percentiles.averageSpeed.p99.toFixed(2)},${stats.min.averageSpeed.toFixed(2)},${stats.max.averageSpeed.toFixed(2)}`
1406
+ );
1407
+ lines.push(
1408
+ `Peak Speed (tokens/s),${stats.mean.peakSpeed.toFixed(2)},${stats.percentiles.peakSpeed.p50.toFixed(2)},${stats.percentiles.peakSpeed.p95.toFixed(2)},${stats.percentiles.peakSpeed.p99.toFixed(2)},${stats.min.peakSpeed.toFixed(2)},${stats.max.peakSpeed.toFixed(2)}`
1409
+ );
1410
+ lines.push(
1411
+ `Peak TPS,${stats.mean.peakTps.toFixed(2)},${stats.percentiles.peakTps.p50.toFixed(2)},${stats.percentiles.peakTps.p95.toFixed(2)},${stats.percentiles.peakTps.p99.toFixed(2)},${stats.min.peakTps.toFixed(2)},${stats.max.peakTps.toFixed(2)}`
1412
+ );
1413
+ lines.push("");
1414
+ lines.push("# Individual Runs");
1415
+ lines.push(
1416
+ "Run,TTFT (ms),Total Time (ms),Total Tokens,Average Speed (tokens/s),Peak Speed (tokens/s),Peak TPS"
1417
+ );
1418
+ results.forEach((r, i) => {
1419
+ lines.push(
1420
+ `${i + 1},${r.ttft.toFixed(2)},${r.totalTime.toFixed(2)},${r.totalTokens},${r.averageSpeed.toFixed(2)},${r.peakSpeed.toFixed(2)},${r.peakTps.toFixed(2)}`
1421
+ );
1422
+ });
1423
+ return lines.join("\n");
1424
+ }
1425
+
1292
1426
  // src/index.ts
1293
1427
  function getCliVersion() {
1294
1428
  try {
@@ -1302,7 +1436,7 @@ function getCliVersion() {
1302
1436
  }
1303
1437
  var program = new Command();
1304
1438
  program.name("token-speed-test").description("A CLI tool to test LLM API token output speed").version(getCliVersion());
1305
- 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);
1439
+ 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("-f, --output-format <format>", "Output format: terminal, json, csv, html", "html").option("-o, --output <path>", "Output file path (default: report.{ext})").parse(process.argv);
1306
1440
  var options = program.opts();
1307
1441
  async function main() {
1308
1442
  let messages = getMessages(DEFAULT_LANG);
@@ -1315,7 +1449,9 @@ async function main() {
1315
1449
  maxTokens: parseInt(options.maxTokens, 10),
1316
1450
  runs: parseInt(options.runs, 10),
1317
1451
  prompt: options.prompt,
1318
- lang: options.lang
1452
+ lang: options.lang,
1453
+ outputFormat: options.outputFormat,
1454
+ outputPath: options.output
1319
1455
  });
1320
1456
  messages = getMessages(config.lang);
1321
1457
  console.log(chalk.cyan(`
@@ -1338,16 +1474,25 @@ ${messages.runningTests}
1338
1474
  `));
1339
1475
  const results = await runMultipleTests(config);
1340
1476
  const allMetrics = results.map((r) => calculateMetrics(r));
1341
- for (let i = 0; i < allMetrics.length; i++) {
1342
- console.log(chalk.gray(renderSingleResult(allMetrics[i], i, config.lang)));
1343
- }
1344
1477
  const stats = calculateStats(allMetrics);
1345
- console.log(chalk.cyan("\n" + renderReport(stats, config.lang)));
1346
- console.log(chalk.green(`
1347
- ${messages.testComplete}
1478
+ if (config.outputFormat === "terminal") {
1479
+ for (let i = 0; i < allMetrics.length; i++) {
1480
+ console.log(chalk.gray(renderSingleResult(allMetrics[i], i, config.lang)));
1481
+ }
1482
+ console.log(chalk.cyan("\n" + renderReport(stats, config.lang)));
1483
+ } else if (config.outputFormat === "json") {
1484
+ const jsonContent = generateJSONExport(config, allMetrics, stats);
1485
+ await fsWriteFile(config.outputPath, jsonContent, "utf-8");
1486
+ console.log(chalk.cyan(`
1487
+ \u2713 JSON report generated: ${config.outputPath}
1488
+ `));
1489
+ } else if (config.outputFormat === "csv") {
1490
+ const csvContent = generateCSVExport(config, allMetrics, stats);
1491
+ await fsWriteFile(config.outputPath, csvContent, "utf-8");
1492
+ console.log(chalk.cyan(`
1493
+ \u2713 CSV report generated: ${config.outputPath}
1348
1494
  `));
1349
- if (options.html) {
1350
- const htmlPath = options.output;
1495
+ } else if (config.outputFormat === "html") {
1351
1496
  const htmlContent = generateHTMLReport({
1352
1497
  config,
1353
1498
  singleResults: allMetrics,
@@ -1355,22 +1500,18 @@ ${messages.testComplete}
1355
1500
  lang: config.lang,
1356
1501
  messages
1357
1502
  });
1358
- try {
1359
- await fsWriteFile(htmlPath, htmlContent, "utf-8");
1360
- console.log(chalk.cyan(messages.htmlGenerated(htmlPath)));
1361
- await open(htmlPath).catch(() => {
1362
- console.warn(chalk.yellow(messages.htmlOpenError(htmlPath)));
1363
- });
1364
- } catch (err) {
1365
- console.error(
1366
- chalk.red(
1367
- `
1368
- ${messages.errorPrefix}: ${err instanceof Error ? err.message : String(err)}
1369
- `
1370
- )
1503
+ await fsWriteFile(config.outputPath, htmlContent, "utf-8");
1504
+ console.log(chalk.cyan(`
1505
+ \u2713 HTML report generated: ${config.outputPath}
1506
+ `));
1507
+ await open(config.outputPath).catch(() => {
1508
+ console.warn(
1509
+ chalk.yellow(`Could not auto-open report, please open manually: ${config.outputPath}`)
1371
1510
  );
1372
- }
1511
+ });
1373
1512
  }
1513
+ console.log(chalk.green(`${messages.testComplete}
1514
+ `));
1374
1515
  } catch (error) {
1375
1516
  if (error instanceof Error) {
1376
1517
  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","../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 p50: string;\n p95: string;\n p99: 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 p50: \"P50\",\n p95: \"P95\",\n p99: \"P99\",\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 p50: \"P50\",\n p95: \"P95\",\n p99: \"P99\",\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 PercentileMetrics {\n p50: number; // 中位数\n p95: number; // 95th 百分位\n p99: number; // 99th 百分位\n}\n\n/**\n * 多次测试的统计结果\n */\nexport interface StatsResult {\n mean: CalculatedMetrics;\n min: CalculatedMetrics;\n max: CalculatedMetrics;\n stdDev: CalculatedMetrics;\n percentiles: {\n ttft: PercentileMetrics;\n totalTime: PercentileMetrics;\n totalTokens: PercentileMetrics;\n averageSpeed: PercentileMetrics;\n peakSpeed: PercentileMetrics;\n peakTps: PercentileMetrics;\n };\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 * 计算一组数值的百分位数\n * @param values 已排序的数值数组\n * @param p 百分位 (0-100)\n */\nfunction calculatePercentile(values: number[], p: number): number {\n if (values.length === 0) return 0;\n if (values.length === 1) return values[0];\n\n // 使用线性插值方法计算百分位数\n const index = (p / 100) * (values.length - 1);\n const lowerIndex = Math.floor(index);\n const upperIndex = Math.ceil(index);\n const fraction = index - lowerIndex;\n\n if (lowerIndex === upperIndex) {\n return values[lowerIndex];\n }\n\n return values[lowerIndex] + fraction * (values[upperIndex] - values[lowerIndex]);\n}\n\n/**\n * 计算一组数值的多个百分位数\n */\nexport function calculatePercentiles(values: number[]): PercentileMetrics {\n if (values.length === 0) {\n return { p50: 0, p95: 0, p99: 0 };\n }\n\n const sorted = [...values].sort((a, b) => a - b);\n return {\n p50: calculatePercentile(sorted, 50),\n p95: calculatePercentile(sorted, 95),\n p99: calculatePercentile(sorted, 99),\n };\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 percentiles: {\n ttft: calculatePercentiles(ttfts),\n totalTime: calculatePercentiles(totalTimes),\n totalTokens: calculatePercentiles(totalTokens),\n averageSpeed: calculatePercentiles(averageSpeeds),\n peakSpeed: calculatePercentiles(peakSpeeds),\n peakTps: calculatePercentiles(peakTpsValues),\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 = 8;\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.p50, STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(messages.statsHeaders.p95, STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(messages.statsHeaders.p99, STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(messages.statsHeaders.min, STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(messages.statsHeaders.max, 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.percentiles.ttft.p50,\n stats.percentiles.ttft.p95,\n stats.percentiles.ttft.p99,\n stats.min.ttft,\n stats.max.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.percentiles.totalTime.p50,\n stats.percentiles.totalTime.p95,\n stats.percentiles.totalTime.p99,\n stats.min.totalTime,\n stats.max.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.percentiles.totalTokens.p50,\n stats.percentiles.totalTokens.p95,\n stats.percentiles.totalTokens.p99,\n stats.min.totalTokens,\n stats.max.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.percentiles.averageSpeed.p50,\n stats.percentiles.averageSpeed.p95,\n stats.percentiles.averageSpeed.p99,\n stats.min.averageSpeed,\n stats.max.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.percentiles.peakSpeed.p50,\n stats.percentiles.peakSpeed.p95,\n stats.percentiles.peakSpeed.p99,\n stats.min.peakSpeed,\n stats.max.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.percentiles.peakTps.p50,\n stats.percentiles.peakTps.p95,\n stats.percentiles.peakTps.p99,\n stats.min.peakTps,\n stats.max.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 p50: number,\n p95: number,\n p99: number,\n min: number,\n max: 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(p50), STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(fmt(p95), STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(fmt(p99), STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(fmt(min), STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(fmt(max), 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 p50: formatTime(stats.percentiles.ttft.p50),\n p95: formatTime(stats.percentiles.ttft.p95),\n p99: formatTime(stats.percentiles.ttft.p99),\n min: formatTime(stats.min.ttft),\n max: formatTime(stats.max.ttft),\n },\n {\n metric: messages.statsLabels.totalTime,\n mean: formatTime(stats.mean.totalTime),\n p50: formatTime(stats.percentiles.totalTime.p50),\n p95: formatTime(stats.percentiles.totalTime.p95),\n p99: formatTime(stats.percentiles.totalTime.p99),\n min: formatTime(stats.min.totalTime),\n max: formatTime(stats.max.totalTime),\n },\n {\n metric: messages.statsLabels.totalTokens,\n mean: formatNumber(stats.mean.totalTokens, 1),\n p50: formatNumber(stats.percentiles.totalTokens.p50, 0),\n p95: formatNumber(stats.percentiles.totalTokens.p95, 0),\n p99: formatNumber(stats.percentiles.totalTokens.p99, 0),\n min: formatNumber(stats.min.totalTokens, 0),\n max: formatNumber(stats.max.totalTokens, 0),\n },\n {\n metric: messages.statsLabels.averageSpeed,\n mean: formatNumber(stats.mean.averageSpeed),\n p50: formatNumber(stats.percentiles.averageSpeed.p50),\n p95: formatNumber(stats.percentiles.averageSpeed.p95),\n p99: formatNumber(stats.percentiles.averageSpeed.p99),\n min: formatNumber(stats.min.averageSpeed),\n max: formatNumber(stats.max.averageSpeed),\n },\n {\n metric: messages.statsLabels.peakSpeed,\n mean: formatNumber(stats.mean.peakSpeed),\n p50: formatNumber(stats.percentiles.peakSpeed.p50),\n p95: formatNumber(stats.percentiles.peakSpeed.p95),\n p99: formatNumber(stats.percentiles.peakSpeed.p99),\n min: formatNumber(stats.min.peakSpeed),\n max: formatNumber(stats.max.peakSpeed),\n },\n {\n metric: messages.statsLabels.peakTps,\n mean: formatNumber(stats.mean.peakTps),\n p50: formatNumber(stats.percentiles.peakTps.p50),\n p95: formatNumber(stats.percentiles.peakTps.p95),\n p99: formatNumber(stats.percentiles.peakTps.p99),\n min: formatNumber(stats.min.peakTps),\n max: formatNumber(stats.max.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.p50}</td>\n <td>${row.p95}</td>\n <td>${row.p99}</td>\n <td>${row.min}</td>\n <td>${row.max}</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.p50}</th>\n <th>${messages.statsHeaders.p95}</th>\n <th>${messages.statsHeaders.p99}</th>\n <th>${messages.statsHeaders.min}</th>\n <th>${messages.statsHeaders.max}</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"]}
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","../src/export.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, type OutputFormat } 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 { generateCSVExport, generateJSONExport } from \"./export.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(\"-f, --output-format <format>\", \"Output format: terminal, json, csv, html\", \"html\")\n .option(\"-o, --output <path>\", \"Output file path (default: report.{ext})\")\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 outputFormat: options.outputFormat as OutputFormat,\n outputPath: options.output,\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 const stats = calculateStats(allMetrics);\n\n // 根据输出格式处理结果\n if (config.outputFormat === \"terminal\") {\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 console.log(chalk.cyan(\"\\n\" + renderReport(stats, config.lang)));\n } else if (config.outputFormat === \"json\") {\n const jsonContent = generateJSONExport(config, allMetrics, stats);\n await fsWriteFile(config.outputPath, jsonContent, \"utf-8\");\n console.log(chalk.cyan(`\\n✓ JSON report generated: ${config.outputPath}\\n`));\n } else if (config.outputFormat === \"csv\") {\n const csvContent = generateCSVExport(config, allMetrics, stats);\n await fsWriteFile(config.outputPath, csvContent, \"utf-8\");\n console.log(chalk.cyan(`\\n✓ CSV report generated: ${config.outputPath}\\n`));\n } else if (config.outputFormat === \"html\") {\n const htmlContent = generateHTMLReport({\n config,\n singleResults: allMetrics,\n stats,\n lang: config.lang,\n messages,\n });\n\n await fsWriteFile(config.outputPath, htmlContent, \"utf-8\");\n console.log(chalk.cyan(`\\n✓ HTML report generated: ${config.outputPath}\\n`));\n\n // 自动打开浏览器\n await open(config.outputPath).catch(() => {\n console.warn(\n chalk.yellow(`Could not auto-open report, please open manually: ${config.outputPath}`)\n );\n });\n }\n\n console.log(chalk.green(`${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 p50: string;\n p95: string;\n p99: 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 p50: \"P50\",\n p95: \"P95\",\n p99: \"P99\",\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 p50: \"P50\",\n p95: \"P95\",\n p99: \"P99\",\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\";\nexport type OutputFormat = \"terminal\" | \"json\" | \"csv\" | \"html\";\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 outputFormat: OutputFormat;\n outputPath: string;\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 outputFormat?: OutputFormat;\n outputPath?: 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 outputFormat = \"html\",\n outputPath,\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 if (outputFormat && ![\"terminal\", \"json\", \"csv\", \"html\"].includes(outputFormat)) {\n throw new Error(\n `Invalid output-format: ${outputFormat}. Must be 'terminal', 'json', 'csv', or 'html'.`\n );\n }\n\n // 使用默认模型或用户指定的模型\n const finalModel = model || DEFAULT_MODELS[provider];\n\n // 确定输出路径\n let finalOutputPath = \"report\";\n if (outputPath) {\n finalOutputPath = outputPath;\n } else if (outputFormat === \"html\") {\n finalOutputPath = \"report.html\";\n } else if (outputFormat === \"json\") {\n finalOutputPath = \"report.json\";\n } else if (outputFormat === \"csv\") {\n finalOutputPath = \"report.csv\";\n }\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 outputFormat,\n outputPath: finalOutputPath,\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 if (![\"terminal\", \"json\", \"csv\", \"html\"].includes(config.outputFormat)) {\n return { valid: false, error: `Invalid outputFormat: ${config.outputFormat}` };\n }\n\n if (!config.outputPath || config.outputPath.trim() === \"\") {\n return { valid: false, error: \"outputPath cannot be empty\" };\n }\n\n return { valid: true };\n}\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 PercentileMetrics {\n p50: number; // 中位数\n p95: number; // 95th 百分位\n p99: number; // 99th 百分位\n}\n\n/**\n * 多次测试的统计结果\n */\nexport interface StatsResult {\n mean: CalculatedMetrics;\n min: CalculatedMetrics;\n max: CalculatedMetrics;\n stdDev: CalculatedMetrics;\n percentiles: {\n ttft: PercentileMetrics;\n totalTime: PercentileMetrics;\n totalTokens: PercentileMetrics;\n averageSpeed: PercentileMetrics;\n peakSpeed: PercentileMetrics;\n peakTps: PercentileMetrics;\n };\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 * 计算一组数值的百分位数\n * @param values 已排序的数值数组\n * @param p 百分位 (0-100)\n */\nfunction calculatePercentile(values: number[], p: number): number {\n if (values.length === 0) return 0;\n if (values.length === 1) return values[0];\n\n // 使用线性插值方法计算百分位数\n const index = (p / 100) * (values.length - 1);\n const lowerIndex = Math.floor(index);\n const upperIndex = Math.ceil(index);\n const fraction = index - lowerIndex;\n\n if (lowerIndex === upperIndex) {\n return values[lowerIndex];\n }\n\n return values[lowerIndex] + fraction * (values[upperIndex] - values[lowerIndex]);\n}\n\n/**\n * 计算一组数值的多个百分位数\n */\nexport function calculatePercentiles(values: number[]): PercentileMetrics {\n if (values.length === 0) {\n return { p50: 0, p95: 0, p99: 0 };\n }\n\n const sorted = [...values].sort((a, b) => a - b);\n return {\n p50: calculatePercentile(sorted, 50),\n p95: calculatePercentile(sorted, 95),\n p99: calculatePercentile(sorted, 99),\n };\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 percentiles: {\n ttft: calculatePercentiles(ttfts),\n totalTime: calculatePercentiles(totalTimes),\n totalTokens: calculatePercentiles(totalTokens),\n averageSpeed: calculatePercentiles(averageSpeeds),\n peakSpeed: calculatePercentiles(peakSpeeds),\n peakTps: calculatePercentiles(peakTpsValues),\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 = 8;\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.p50, STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(messages.statsHeaders.p95, STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(messages.statsHeaders.p99, STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(messages.statsHeaders.min, STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(messages.statsHeaders.max, 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.percentiles.ttft.p50,\n stats.percentiles.ttft.p95,\n stats.percentiles.ttft.p99,\n stats.min.ttft,\n stats.max.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.percentiles.totalTime.p50,\n stats.percentiles.totalTime.p95,\n stats.percentiles.totalTime.p99,\n stats.min.totalTime,\n stats.max.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.percentiles.totalTokens.p50,\n stats.percentiles.totalTokens.p95,\n stats.percentiles.totalTokens.p99,\n stats.min.totalTokens,\n stats.max.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.percentiles.averageSpeed.p50,\n stats.percentiles.averageSpeed.p95,\n stats.percentiles.averageSpeed.p99,\n stats.min.averageSpeed,\n stats.max.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.percentiles.peakSpeed.p50,\n stats.percentiles.peakSpeed.p95,\n stats.percentiles.peakSpeed.p99,\n stats.min.peakSpeed,\n stats.max.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.percentiles.peakTps.p50,\n stats.percentiles.peakTps.p95,\n stats.percentiles.peakTps.p99,\n stats.min.peakTps,\n stats.max.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 p50: number,\n p95: number,\n p99: number,\n min: number,\n max: 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(p50), STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(fmt(p95), STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(fmt(p99), STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(fmt(min), STAT_VALUE_WIDTH) +\n \" │ \" +\n padStartWidth(fmt(max), 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 p50: formatTime(stats.percentiles.ttft.p50),\n p95: formatTime(stats.percentiles.ttft.p95),\n p99: formatTime(stats.percentiles.ttft.p99),\n min: formatTime(stats.min.ttft),\n max: formatTime(stats.max.ttft),\n },\n {\n metric: messages.statsLabels.totalTime,\n mean: formatTime(stats.mean.totalTime),\n p50: formatTime(stats.percentiles.totalTime.p50),\n p95: formatTime(stats.percentiles.totalTime.p95),\n p99: formatTime(stats.percentiles.totalTime.p99),\n min: formatTime(stats.min.totalTime),\n max: formatTime(stats.max.totalTime),\n },\n {\n metric: messages.statsLabels.totalTokens,\n mean: formatNumber(stats.mean.totalTokens, 1),\n p50: formatNumber(stats.percentiles.totalTokens.p50, 0),\n p95: formatNumber(stats.percentiles.totalTokens.p95, 0),\n p99: formatNumber(stats.percentiles.totalTokens.p99, 0),\n min: formatNumber(stats.min.totalTokens, 0),\n max: formatNumber(stats.max.totalTokens, 0),\n },\n {\n metric: messages.statsLabels.averageSpeed,\n mean: formatNumber(stats.mean.averageSpeed),\n p50: formatNumber(stats.percentiles.averageSpeed.p50),\n p95: formatNumber(stats.percentiles.averageSpeed.p95),\n p99: formatNumber(stats.percentiles.averageSpeed.p99),\n min: formatNumber(stats.min.averageSpeed),\n max: formatNumber(stats.max.averageSpeed),\n },\n {\n metric: messages.statsLabels.peakSpeed,\n mean: formatNumber(stats.mean.peakSpeed),\n p50: formatNumber(stats.percentiles.peakSpeed.p50),\n p95: formatNumber(stats.percentiles.peakSpeed.p95),\n p99: formatNumber(stats.percentiles.peakSpeed.p99),\n min: formatNumber(stats.min.peakSpeed),\n max: formatNumber(stats.max.peakSpeed),\n },\n {\n metric: messages.statsLabels.peakTps,\n mean: formatNumber(stats.mean.peakTps),\n p50: formatNumber(stats.percentiles.peakTps.p50),\n p95: formatNumber(stats.percentiles.peakTps.p95),\n p99: formatNumber(stats.percentiles.peakTps.p99),\n min: formatNumber(stats.min.peakTps),\n max: formatNumber(stats.max.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.p50}</td>\n <td>${row.p95}</td>\n <td>${row.p99}</td>\n <td>${row.min}</td>\n <td>${row.max}</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.p50}</th>\n <th>${messages.statsHeaders.p95}</th>\n <th>${messages.statsHeaders.p99}</th>\n <th>${messages.statsHeaders.min}</th>\n <th>${messages.statsHeaders.max}</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","import type { Config } from \"./config.js\";\nimport type { CalculatedMetrics, StatsResult } from \"./metrics.js\";\n\nexport interface ExportData {\n timestamp: string;\n config: {\n provider: string;\n model: string;\n maxTokens: number;\n runCount: number;\n prompt: string;\n };\n runs: ExportRun[];\n stats: ExportStats;\n}\n\nexport interface ExportRun {\n ttft: number;\n totalTime: number;\n totalTokens: number;\n averageSpeed: number;\n peakSpeed: number;\n peakTps: number;\n tps: number[];\n}\n\nexport interface ExportStats {\n mean: ExportMetrics;\n min: ExportMetrics;\n max: ExportMetrics;\n p50: ExportMetrics;\n p95: ExportMetrics;\n p99: ExportMetrics;\n}\n\nexport interface ExportMetrics {\n ttft: number;\n totalTime: number;\n totalTokens: number;\n averageSpeed: number;\n peakSpeed: number;\n peakTps: number;\n}\n\n/**\n * Generate JSON export data\n */\nexport function generateJSONExport(\n config: Config,\n results: CalculatedMetrics[],\n stats: StatsResult\n): string {\n const exportData: ExportData = {\n timestamp: new Date().toISOString(),\n config: {\n provider: config.provider,\n model: config.model,\n maxTokens: config.maxTokens,\n runCount: config.runCount,\n prompt: config.prompt,\n },\n runs: results.map((r) => ({\n ttft: Math.round(r.ttft * 100) / 100,\n totalTime: Math.round(r.totalTime * 100) / 100,\n totalTokens: r.totalTokens,\n averageSpeed: Math.round(r.averageSpeed * 100) / 100,\n peakSpeed: Math.round(r.peakSpeed * 100) / 100,\n peakTps: Math.round(r.peakTps * 100) / 100,\n tps: r.tps,\n })),\n stats: {\n mean: {\n ttft: Math.round(stats.mean.ttft * 100) / 100,\n totalTime: Math.round(stats.mean.totalTime * 100) / 100,\n totalTokens: Math.round(stats.mean.totalTokens * 100) / 100,\n averageSpeed: Math.round(stats.mean.averageSpeed * 100) / 100,\n peakSpeed: Math.round(stats.mean.peakSpeed * 100) / 100,\n peakTps: Math.round(stats.mean.peakTps * 100) / 100,\n },\n min: {\n ttft: Math.round(stats.min.ttft * 100) / 100,\n totalTime: Math.round(stats.min.totalTime * 100) / 100,\n totalTokens: stats.min.totalTokens,\n averageSpeed: Math.round(stats.min.averageSpeed * 100) / 100,\n peakSpeed: Math.round(stats.min.peakSpeed * 100) / 100,\n peakTps: Math.round(stats.min.peakTps * 100) / 100,\n },\n max: {\n ttft: Math.round(stats.max.ttft * 100) / 100,\n totalTime: Math.round(stats.max.totalTime * 100) / 100,\n totalTokens: stats.max.totalTokens,\n averageSpeed: Math.round(stats.max.averageSpeed * 100) / 100,\n peakSpeed: Math.round(stats.max.peakSpeed * 100) / 100,\n peakTps: Math.round(stats.max.peakTps * 100) / 100,\n },\n p50: {\n ttft: Math.round(stats.percentiles.ttft.p50 * 100) / 100,\n totalTime: Math.round(stats.percentiles.totalTime.p50 * 100) / 100,\n totalTokens: Math.round(stats.percentiles.totalTokens.p50 * 100) / 100,\n averageSpeed: Math.round(stats.percentiles.averageSpeed.p50 * 100) / 100,\n peakSpeed: Math.round(stats.percentiles.peakSpeed.p50 * 100) / 100,\n peakTps: Math.round(stats.percentiles.peakTps.p50 * 100) / 100,\n },\n p95: {\n ttft: Math.round(stats.percentiles.ttft.p95 * 100) / 100,\n totalTime: Math.round(stats.percentiles.totalTime.p95 * 100) / 100,\n totalTokens: Math.round(stats.percentiles.totalTokens.p95 * 100) / 100,\n averageSpeed: Math.round(stats.percentiles.averageSpeed.p95 * 100) / 100,\n peakSpeed: Math.round(stats.percentiles.peakSpeed.p95 * 100) / 100,\n peakTps: Math.round(stats.percentiles.peakTps.p95 * 100) / 100,\n },\n p99: {\n ttft: Math.round(stats.percentiles.ttft.p99 * 100) / 100,\n totalTime: Math.round(stats.percentiles.totalTime.p99 * 100) / 100,\n totalTokens: Math.round(stats.percentiles.totalTokens.p99 * 100) / 100,\n averageSpeed: Math.round(stats.percentiles.averageSpeed.p99 * 100) / 100,\n peakSpeed: Math.round(stats.percentiles.peakSpeed.p99 * 100) / 100,\n peakTps: Math.round(stats.percentiles.peakTps.p99 * 100) / 100,\n },\n },\n };\n\n return JSON.stringify(exportData, null, 2);\n}\n\n/**\n * Generate CSV export data\n */\nexport function generateCSVExport(\n config: Config,\n results: CalculatedMetrics[],\n stats: StatsResult\n): string {\n const lines: string[] = [];\n\n // Header with metadata\n lines.push(\"# Token Speed Test Results\");\n lines.push(`# Timestamp: ${new Date().toISOString()}`);\n lines.push(`# Provider: ${config.provider}`);\n lines.push(`# Model: ${config.model}`);\n lines.push(`# Runs: ${config.runCount}`);\n lines.push(`# Prompt: ${config.prompt}`);\n lines.push(\"\");\n\n // Statistics section\n lines.push(\"# Statistics\");\n lines.push(\"Metric,Mean,P50,P95,P99,Min,Max\");\n lines.push(\n `TTFT (ms),${stats.mean.ttft.toFixed(2)},${stats.percentiles.ttft.p50.toFixed(2)},${stats.percentiles.ttft.p95.toFixed(2)},${stats.percentiles.ttft.p99.toFixed(2)},${stats.min.ttft.toFixed(2)},${stats.max.ttft.toFixed(2)}`\n );\n lines.push(\n `Total Time (ms),${stats.mean.totalTime.toFixed(2)},${stats.percentiles.totalTime.p50.toFixed(2)},${stats.percentiles.totalTime.p95.toFixed(2)},${stats.percentiles.totalTime.p99.toFixed(2)},${stats.min.totalTime.toFixed(2)},${stats.max.totalTime.toFixed(2)}`\n );\n lines.push(\n `Total Tokens,${stats.mean.totalTokens.toFixed(2)},${stats.percentiles.totalTokens.p50.toFixed(2)},${stats.percentiles.totalTokens.p95.toFixed(2)},${stats.percentiles.totalTokens.p99.toFixed(2)},${stats.min.totalTokens},${stats.max.totalTokens}`\n );\n lines.push(\n `Average Speed (tokens/s),${stats.mean.averageSpeed.toFixed(2)},${stats.percentiles.averageSpeed.p50.toFixed(2)},${stats.percentiles.averageSpeed.p95.toFixed(2)},${stats.percentiles.averageSpeed.p99.toFixed(2)},${stats.min.averageSpeed.toFixed(2)},${stats.max.averageSpeed.toFixed(2)}`\n );\n lines.push(\n `Peak Speed (tokens/s),${stats.mean.peakSpeed.toFixed(2)},${stats.percentiles.peakSpeed.p50.toFixed(2)},${stats.percentiles.peakSpeed.p95.toFixed(2)},${stats.percentiles.peakSpeed.p99.toFixed(2)},${stats.min.peakSpeed.toFixed(2)},${stats.max.peakSpeed.toFixed(2)}`\n );\n lines.push(\n `Peak TPS,${stats.mean.peakTps.toFixed(2)},${stats.percentiles.peakTps.p50.toFixed(2)},${stats.percentiles.peakTps.p95.toFixed(2)},${stats.percentiles.peakTps.p99.toFixed(2)},${stats.min.peakTps.toFixed(2)},${stats.max.peakTps.toFixed(2)}`\n );\n lines.push(\"\");\n\n // Individual runs section\n lines.push(\"# Individual Runs\");\n lines.push(\n \"Run,TTFT (ms),Total Time (ms),Total Tokens,Average Speed (tokens/s),Peak Speed (tokens/s),Peak TPS\"\n );\n results.forEach((r, i) => {\n lines.push(\n `${i + 1},${r.ttft.toFixed(2)},${r.totalTime.toFixed(2)},${r.totalTokens},${r.averageSpeed.toFixed(2)},${r.peakSpeed.toFixed(2)},${r.peakTps.toFixed(2)}`\n );\n });\n\n return lines.join(\"\\n\");\n}\n"],"mappings":";;;AACA,SAAS,gBAAAA,qBAAoB;AAC7B,SAAS,aAAa,mBAAmB;AACzC,SAAS,WAAAC,UAAS,YAAY;AAC9B,SAAS,iBAAAC,sBAAqB;AAC9B,SAAS,eAAe;AACxB,OAAO,WAAW;AAClB,OAAO,UAAU;;;ACPV,IAAM,kBAAkB,CAAC,MAAM,IAAI;AAGnC,IAAM,eAAqB;AAyElC,IAAM,aAAuB;AAAA,EAC3B,eAAe;AAAA,EACf,UAAU;AAAA,EACV,cAAc;AAAA,EACd,iBAAiB;AAAA,EACjB,cAAc;AAAA,EACd,aAAa;AAAA,EACb,cAAc;AAAA,EACd,cAAc;AAAA,IACZ,UAAU;AAAA,IACV,OAAO;AAAA,IACP,WAAW;AAAA,IACX,MAAM;AAAA,IACN,QAAQ;AAAA,EACV;AAAA,EACA,UAAU,CAAC,UAAkB,iBAAO,KAAK;AAAA,EACzC,kBAAkB,CAAC,SAAiB,UAAkB,iBAAO,OAAO,IAAI,KAAK;AAAA,EAC7E,aAAa;AAAA,EACb,iBAAiB;AAAA,EACjB,mBAAmB;AAAA,EACnB,aAAa;AAAA,EACb,WAAW;AAAA,EACX,mBAAmB,CAAC,eAAuB,+BAAW,UAAU;AAAA,EAChE,cAAc;AAAA,IACZ,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AAAA,EACA,aAAa;AAAA,IACX,MAAM;AAAA,IACN,WAAW;AAAA,IACX,aAAa;AAAA,IACb,cAAc;AAAA,IACd,WAAW;AAAA,IACX,SAAS;AAAA,EACX;AAAA,EACA,cAAc;AAAA,IACZ,MAAM;AAAA,IACN,WAAW;AAAA,IACX,aAAa;AAAA,IACb,cAAc;AAAA,IACd,WAAW;AAAA,IACX,SAAS;AAAA,EACX;AAAA,EACA,WAAW;AAAA,EACX,iBAAiB;AAAA,EACjB,eAAe,CAAC,SAAiB,+CAAiB,IAAI;AAAA,EACtD,eAAe,CAAC,SAAiB,yFAAmB,IAAI;AAAA,EACxD,mBAAmB;AAAA,EACnB,oBAAoB;AAAA,EACpB,mBAAmB;AAAA,EACnB,oBAAoB;AAAA,EACpB,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,WAAW;AAAA,EACX,SAAS;AAAA,EACT,gBAAgB;AAAA,EAChB,qBAAqB;AAAA,EACrB,cAAc;AAAA,EACd,mBAAmB;AACrB;AAEA,IAAM,aAAuB;AAAA,EAC3B,eAAe;AAAA,EACf,UAAU;AAAA,EACV,cAAc;AAAA,EACd,iBAAiB;AAAA,EACjB,cAAc;AAAA,EACd,aAAa;AAAA,EACb,cAAc;AAAA,EACd,cAAc;AAAA,IACZ,UAAU;AAAA,IACV,OAAO;AAAA,IACP,WAAW;AAAA,IACX,MAAM;AAAA,IACN,QAAQ;AAAA,EACV;AAAA,EACA,UAAU,CAAC,UAAkB,QAAQ,KAAK;AAAA,EAC1C,kBAAkB,CAAC,SAAiB,UAAkB,QAAQ,OAAO,IAAI,KAAK;AAAA,EAC9E,aAAa;AAAA,EACb,iBAAiB;AAAA,EACjB,mBAAmB;AAAA,EACnB,aAAa;AAAA,EACb,WAAW;AAAA,EACX,mBAAmB,CAAC,eAAuB,cAAc,UAAU;AAAA,EACnE,cAAc;AAAA,IACZ,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,KAAK;AAAA,IACL,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AAAA,EACA,aAAa;AAAA,IACX,MAAM;AAAA,IACN,WAAW;AAAA,IACX,aAAa;AAAA,IACb,cAAc;AAAA,IACd,WAAW;AAAA,IACX,SAAS;AAAA,EACX;AAAA,EACA,cAAc;AAAA,IACZ,MAAM;AAAA,IACN,WAAW;AAAA,IACX,aAAa;AAAA,IACb,cAAc;AAAA,IACd,WAAW;AAAA,IACX,SAAS;AAAA,EACX;AAAA,EACA,WAAW;AAAA,EACX,iBAAiB;AAAA,EACjB,eAAe,CAAC,SAAiB,iCAA4B,IAAI;AAAA,EACjE,eAAe,CAAC,SAAiB,qDAAqD,IAAI;AAAA,EAC1F,mBAAmB;AAAA,EACnB,oBAAoB;AAAA,EACpB,mBAAmB;AAAA,EACnB,oBAAoB;AAAA,EACpB,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,WAAW;AAAA,EACX,SAAS;AAAA,EACT,gBAAgB;AAAA,EAChB,qBAAqB;AAAA,EACrB,cAAc;AAAA,EACd,mBAAmB;AACrB;AAEO,SAAS,gBAAgB,OAA8B;AAC5D,SAAO,gBAAgB,SAAS,KAAa;AAC/C;AAEO,SAAS,YAAY,OAAsB;AAChD,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,EACT;AACA,QAAM,aAAa,MAAM,YAAY;AACrC,MAAI,CAAC,gBAAgB,UAAU,GAAG;AAChC,UAAM,IAAI,MAAM,iBAAiB,KAAK,yBAAyB;AAAA,EACjE;AACA,SAAO;AACT;AAEO,SAAS,YAAY,MAAsB;AAChD,SAAO,SAAS,OAAO,aAAa;AACtC;;;AC5MA,IAAM,iBAA2C;AAAA,EAC/C,WAAW;AAAA,EACX,QAAQ;AACV;AAEA,IAAM,qBAAqB;AAC3B,IAAM,eAAe;AAId,SAAS,YAAY,MAA0B;AACpD,QAAM;AAAA,IACJ;AAAA,IACA,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ,OAAO;AAAA,IACP;AAAA,IACA,MAAM;AAAA,IACN,eAAe;AAAA,IACf;AAAA,EACF,IAAI;AACJ,QAAM,OAAO,YAAY,SAAS;AAClC,QAAM,WAAW,YAAY,IAAI;AACjC,QAAM,cAAc,UAAU,SAAS;AAGvC,MAAI,CAAC,UAAU,OAAO,KAAK,MAAM,IAAI;AACnC,UAAM,IAAI,MAAM,yDAAyD;AAAA,EAC3E;AAGA,MAAI,aAAa,eAAe,aAAa,UAAU;AACrD,UAAM,IAAI,MAAM,qBAAqB,QAAQ,oCAAoC;AAAA,EACnF;AAGA,MAAI,CAAC,OAAO,SAAS,SAAS,KAAK,CAAC,OAAO,UAAU,SAAS,KAAK,aAAa,GAAG;AACjF,UAAM,IAAI,MAAM,uBAAuB,SAAS,+BAA+B;AAAA,EACjF;AAEA,MAAI,CAAC,OAAO,SAAS,IAAI,KAAK,CAAC,OAAO,UAAU,IAAI,KAAK,QAAQ,GAAG;AAClE,UAAM,IAAI,MAAM,iBAAiB,IAAI,+BAA+B;AAAA,EACtE;AAGA,MAAI,gBAAgB,CAAC,CAAC,YAAY,QAAQ,OAAO,MAAM,EAAE,SAAS,YAAY,GAAG;AAC/E,UAAM,IAAI;AAAA,MACR,0BAA0B,YAAY;AAAA,IACxC;AAAA,EACF;AAGA,QAAM,aAAa,SAAS,eAAe,QAAQ;AAGnD,MAAI,kBAAkB;AACtB,MAAI,YAAY;AACd,sBAAkB;AAAA,EACpB,WAAW,iBAAiB,QAAQ;AAClC,sBAAkB;AAAA,EACpB,WAAW,iBAAiB,QAAQ;AAClC,sBAAkB;AAAA,EACpB,WAAW,iBAAiB,OAAO;AACjC,sBAAkB;AAAA,EACpB;AAEA,SAAO;AAAA,IACL;AAAA,IACA,QAAQ,OAAO,KAAK;AAAA,IACpB,SAAS,KAAK,KAAK;AAAA,IACnB,OAAO;AAAA,IACP;AAAA,IACA,UAAU;AAAA,IACV,QAAQ,YAAY,KAAK;AAAA,IACzB;AAAA,IACA;AAAA,IACA,YAAY;AAAA,EACd;AACF;;;AC/GA,SAAS,mBAAmB;AAC5B,OAAO,eAAe;AACtB,OAAO,YAAY;;;ACFnB,SAAS,oBAAoB,oBAAoB;AAGjD,IAAM,oBAAoB;AAEnB,SAAS,gBAAgB,OAAe;AAC7C,MAAI;AACF,UAAM,aAAa,MAAM,KAAK;AAC9B,QAAI,CAAC,YAAY;AACf,aAAO,aAAa,iBAAiB;AAAA,IACvC;AACA,WAAO,mBAAmB,UAA2B;AAAA,EACvD,QAAQ;AACN,WAAO,aAAa,iBAAiB;AAAA,EACvC;AACF;;;ADJA,eAAsB,oBAAoB,QAAwC;AAChF,QAAM,YAAY,YAAY,IAAI;AAClC,QAAM,aAAuB,CAAC;AAC9B,MAAI,OAAO;AACX,MAAI,qBAAqB;AACzB,MAAI,aAAa;AACjB,MAAI,cAAc;AAElB,QAAM,WAAW,gBAAgB,OAAO,KAAK;AAC7C,QAAM,SAAS,IAAI,UAAU;AAAA,IAC3B,QAAQ,OAAO;AAAA,IACf,SAAS,OAAO;AAAA,EAClB,CAAC;AAED,MAAI;AACF,UAAM,SAAS,MAAM,OAAO,SAAS,OAAO;AAAA,MAC1C,OAAO,OAAO;AAAA,MACd,YAAY,OAAO;AAAA,MACnB,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,OAAO,OAAO,CAAC;AAAA,MACnD,QAAQ;AAAA,IACV,CAAC;AAED,qBAAiB,SAAS,QAAQ;AAChC,YAAM,cAAc,YAAY,IAAI;AAEpC,UAAI,MAAM,SAAS,yBAAyB,MAAM,MAAM,SAAS,cAAc;AAC7E,cAAM,OAAO,MAAM,MAAM;AAEzB,YAAI,QAAQ,KAAK,SAAS,GAAG;AAC3B,kBAAQ,OAAO,MAAM,IAAI;AACzB,wBAAc;AACd,gBAAM,UAAU,SAAS,OAAO,IAAI;AACpC,gBAAM,YAAY,QAAQ;AAE1B,cAAI,YAAY,GAAG;AACjB,gBAAI,CAAC,oBAAoB;AACvB,qBAAO,cAAc;AACrB,mCAAqB;AAAA,YACvB;AAGA,qBAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,yBAAW,KAAK,cAAc,SAAS;AAAA,YACzC;AAEA,0BAAc;AAAA,UAChB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,QAAI,iBAAiB,OAAO;AAC1B,YAAM,IAAI,MAAM,wBAAwB,MAAM,OAAO,EAAE;AAAA,IACzD;AACA,UAAM;AAAA,EACR,UAAE;AACA,QAAI,aAAa;AACf,cAAQ,OAAO,MAAM,IAAI;AAAA,IAC3B;AACA,aAAS,KAAK;AAAA,EAChB;AAEA,QAAM,UAAU,YAAY,IAAI;AAChC,QAAM,YAAY,UAAU;AAE5B,SAAO;AAAA,IACL;AAAA,IACA,QAAQ;AAAA,IACR,aAAa;AAAA,IACb;AAAA,EACF;AACF;AAKA,eAAsB,iBAAiB,QAAwC;AAC7E,QAAM,YAAY,YAAY,IAAI;AAClC,QAAM,aAAuB,CAAC;AAC9B,MAAI,OAAO;AACX,MAAI,qBAAqB;AACzB,MAAI,aAAa;AACjB,MAAI,cAAc;AAElB,QAAM,WAAW,gBAAgB,OAAO,KAAK;AAC7C,QAAM,SAAS,IAAI,OAAO;AAAA,IACxB,QAAQ,OAAO;AAAA,IACf,SAAS,OAAO;AAAA,EAClB,CAAC;AAED,MAAI;AACF,UAAM,SAAS,MAAM,OAAO,KAAK,YAAY,OAAO;AAAA,MAClD,OAAO,OAAO;AAAA,MACd,YAAY,OAAO;AAAA,MACnB,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,OAAO,OAAO,CAAC;AAAA,MACnD,QAAQ;AAAA,IACV,CAAC;AAED,qBAAiB,SAAS,QAAQ;AAChC,YAAM,cAAc,YAAY,IAAI;AAEpC,YAAM,QAAQ,MAAM,QAAQ,CAAC,GAAG;AAEhC,UAAI,OAAO,SAAS;AAClB,cAAM,UAAU,MAAM;AAEtB,YAAI,QAAQ,SAAS,GAAG;AACtB,kBAAQ,OAAO,MAAM,OAAO;AAC5B,wBAAc;AACd,gBAAM,UAAU,SAAS,OAAO,OAAO;AACvC,gBAAM,YAAY,QAAQ;AAE1B,cAAI,YAAY,GAAG;AACjB,gBAAI,CAAC,oBAAoB;AACvB,qBAAO,cAAc;AACrB,mCAAqB;AAAA,YACvB;AAGA,qBAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,yBAAW,KAAK,cAAc,SAAS;AAAA,YACzC;AAEA,0BAAc;AAAA,UAChB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,QAAI,iBAAiB,OAAO;AAC1B,YAAM,IAAI,MAAM,qBAAqB,MAAM,OAAO,EAAE;AAAA,IACtD;AACA,UAAM;AAAA,EACR,UAAE;AACA,QAAI,aAAa;AACf,cAAQ,OAAO,MAAM,IAAI;AAAA,IAC3B;AACA,aAAS,KAAK;AAAA,EAChB;AAEA,QAAM,UAAU,YAAY,IAAI;AAChC,QAAM,YAAY,UAAU;AAE5B,SAAO;AAAA,IACL;AAAA,IACA,QAAQ;AAAA,IACR,aAAa;AAAA,IACb;AAAA,EACF;AACF;AAKA,eAAsB,WAAW,QAAwC;AACvE,MAAI,OAAO,aAAa,aAAa;AACnC,WAAO,oBAAoB,MAAM;AAAA,EACnC,OAAO;AACL,WAAO,iBAAiB,MAAM;AAAA,EAChC;AACF;AAKA,eAAsB,iBAAiB,QAA0C;AAC/E,QAAM,UAA2B,CAAC;AAClC,QAAM,WAAW,YAAY,OAAO,IAAI;AAExC,WAAS,IAAI,GAAG,IAAI,OAAO,UAAU,KAAK;AACxC,QAAI,OAAO,WAAW,GAAG;AACvB,YAAM,QAAQ;AAAA,EAAK,SAAS,iBAAiB,IAAI,GAAG,OAAO,QAAQ,CAAC;AACpE,cAAQ,IAAI,KAAK;AACjB,cAAQ,IAAI,IAAI,OAAO,MAAM,SAAS,CAAC,CAAC;AAAA,IAC1C;AACA,UAAM,SAAS,MAAM,WAAW,MAAM;AACtC,YAAQ,KAAK,MAAM;AAAA,EACrB;AAEA,SAAO;AACT;;;AEzIO,SAAS,cAAc,SAAgC;AAC5D,SAAO,QAAQ;AACjB;AAKO,SAAS,sBAAsB,SAAgC;AACpE,MAAI,QAAQ,aAAa,GAAG;AAC1B,WAAO;AAAA,EACT;AACA,SAAQ,QAAQ,cAAc,QAAQ,YAAa;AACrD;AAKA,IAAM,qBAAqB;AAEpB,SAAS,mBAAmB,SAAwB,aAAqB,IAAY;AAC1F,MAAI,QAAQ,OAAO,SAAS,YAAY;AAEtC,QAAI,QAAQ,OAAO,SAAS,GAAG;AAC7B,aAAO;AAAA,IACT;AACA,UAAM,YAAY,QAAQ,OAAO,QAAQ,OAAO,SAAS,CAAC,IAAI,QAAQ,OAAO,CAAC;AAC9E,UAAM,aAAa,KAAK,IAAI,WAAW,kBAAkB;AACzD,YAAS,QAAQ,OAAO,SAAS,KAAK,aAAc;AAAA,EACtD;AAEA,MAAI,WAAW;AACf,WAAS,IAAI,GAAG,KAAK,QAAQ,OAAO,SAAS,YAAY,KAAK;AAC5D,UAAM,YAAY,QAAQ,OAAO,CAAC;AAClC,UAAM,UAAU,QAAQ,OAAO,IAAI,aAAa,CAAC;AACjD,UAAM,WAAW,UAAU;AAC3B,UAAM,aAAa,KAAK,IAAI,UAAU,kBAAkB;AACxD,UAAM,SAAU,aAAa,KAAK,aAAc;AAChD,eAAW,KAAK,IAAI,UAAU,KAAK;AAAA,EACrC;AAEA,SAAO;AACT;AAKO,SAAS,aAAa,SAAkC;AAC7D,MAAI,QAAQ,OAAO,WAAW,GAAG;AAC/B,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,gBAAgB,QAAQ,OAAO,QAAQ,OAAO,SAAS,CAAC;AAC9D,QAAM,eAAe,KAAK,KAAK,gBAAgB,GAAI;AAEnD,MAAI,gBAAgB,GAAG;AACrB,WAAO,QAAQ,OAAO,SAAS,IAAI,CAAC,QAAQ,OAAO,MAAM,IAAI,CAAC;AAAA,EAChE;AAEA,QAAM,MAAgB,IAAI,MAAM,YAAY,EAAE,KAAK,CAAC;AAGpD,aAAW,aAAa,QAAQ,QAAQ;AACtC,UAAM,cAAc,KAAK,MAAM,YAAY,GAAI;AAC/C,QAAI,cAAc,IAAI,QAAQ;AAC5B,UAAI,WAAW;AAAA,IACjB;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,iBAAiB,SAA2C;AAC1E,QAAM,MAAM,aAAa,OAAO;AAChC,SAAO;AAAA,IACL,MAAM,cAAc,OAAO;AAAA,IAC3B,WAAW,QAAQ;AAAA,IACnB,aAAa,QAAQ;AAAA,IACrB,cAAc,sBAAsB,OAAO;AAAA,IAC3C,WAAW,mBAAmB,OAAO;AAAA,IACrC,SAAS,IAAI,SAAS,IAAI,KAAK,IAAI,GAAG,GAAG,IAAI;AAAA,IAC7C;AAAA,EACF;AACF;AAKA,SAAS,KAAK,QAA0B;AACtC,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,SAAO,OAAO,OAAO,CAAC,KAAK,MAAM,MAAM,GAAG,CAAC,IAAI,OAAO;AACxD;AAKA,SAAS,kBAAkB,QAA0B;AACnD,MAAI,OAAO,SAAS,EAAG,QAAO;AAC9B,QAAM,MAAM,KAAK,MAAM;AACvB,QAAM,cAAc,OAAO,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC;AAC1D,SAAO,KAAK,KAAK,KAAK,WAAW,CAAC;AACpC;AAOA,SAAS,oBAAoB,QAAkB,GAAmB;AAChE,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,MAAI,OAAO,WAAW,EAAG,QAAO,OAAO,CAAC;AAGxC,QAAM,QAAS,IAAI,OAAQ,OAAO,SAAS;AAC3C,QAAM,aAAa,KAAK,MAAM,KAAK;AACnC,QAAM,aAAa,KAAK,KAAK,KAAK;AAClC,QAAM,WAAW,QAAQ;AAEzB,MAAI,eAAe,YAAY;AAC7B,WAAO,OAAO,UAAU;AAAA,EAC1B;AAEA,SAAO,OAAO,UAAU,IAAI,YAAY,OAAO,UAAU,IAAI,OAAO,UAAU;AAChF;AAKO,SAAS,qBAAqB,QAAqC;AACxE,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,EAAE;AAAA,EAClC;AAEA,QAAM,SAAS,CAAC,GAAG,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAC/C,SAAO;AAAA,IACL,KAAK,oBAAoB,QAAQ,EAAE;AAAA,IACnC,KAAK,oBAAoB,QAAQ,EAAE;AAAA,IACnC,KAAK,oBAAoB,QAAQ,EAAE;AAAA,EACrC;AACF;AAKO,SAAS,eAAe,YAA8C;AAC3E,MAAI,WAAW,WAAW,GAAG;AAC3B,UAAM,IAAI,MAAM,iDAAiD;AAAA,EACnE;AAEA,QAAM,aAAa,WAAW;AAG9B,QAAM,QAAQ,WAAW,IAAI,CAAC,MAAM,EAAE,IAAI;AAC1C,QAAM,aAAa,WAAW,IAAI,CAAC,MAAM,EAAE,SAAS;AACpD,QAAM,cAAc,WAAW,IAAI,CAAC,MAAM,EAAE,WAAW;AACvD,QAAM,gBAAgB,WAAW,IAAI,CAAC,MAAM,EAAE,YAAY;AAC1D,QAAM,aAAa,WAAW,IAAI,CAAC,MAAM,EAAE,SAAS;AACpD,QAAM,gBAAgB,WAAW,IAAI,CAAC,MAAM,EAAE,OAAO;AAGrD,QAAM,eAAe,KAAK,IAAI,GAAG,WAAW,IAAI,CAAC,MAAM,EAAE,IAAI,MAAM,CAAC;AACpE,QAAM,SAAmB,CAAC;AAC1B,WAAS,IAAI,GAAG,IAAI,cAAc,KAAK;AACrC,UAAM,SAAS,WAAW,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC;AAClD,WAAO,KAAK,KAAK,MAAM,CAAC;AAAA,EAC1B;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,MACJ,MAAM,KAAK,KAAK;AAAA,MAChB,WAAW,KAAK,UAAU;AAAA,MAC1B,aAAa,KAAK,WAAW;AAAA,MAC7B,cAAc,KAAK,aAAa;AAAA,MAChC,WAAW,KAAK,UAAU;AAAA,MAC1B,SAAS,KAAK,aAAa;AAAA,MAC3B,KAAK;AAAA,IACP;AAAA,IACA,KAAK;AAAA,MACH,MAAM,KAAK,IAAI,GAAG,KAAK;AAAA,MACvB,WAAW,KAAK,IAAI,GAAG,UAAU;AAAA,MACjC,aAAa,KAAK,IAAI,GAAG,WAAW;AAAA,MACpC,cAAc,KAAK,IAAI,GAAG,aAAa;AAAA,MACvC,WAAW,KAAK,IAAI,GAAG,UAAU;AAAA,MACjC,SAAS,KAAK,IAAI,GAAG,aAAa;AAAA,MAClC,KAAK,CAAC;AAAA,IACR;AAAA,IACA,KAAK;AAAA,MACH,MAAM,KAAK,IAAI,GAAG,KAAK;AAAA,MACvB,WAAW,KAAK,IAAI,GAAG,UAAU;AAAA,MACjC,aAAa,KAAK,IAAI,GAAG,WAAW;AAAA,MACpC,cAAc,KAAK,IAAI,GAAG,aAAa;AAAA,MACvC,WAAW,KAAK,IAAI,GAAG,UAAU;AAAA,MACjC,SAAS,KAAK,IAAI,GAAG,aAAa;AAAA,MAClC,KAAK,CAAC;AAAA,IACR;AAAA,IACA,QAAQ;AAAA,MACN,MAAM,kBAAkB,KAAK;AAAA,MAC7B,WAAW,kBAAkB,UAAU;AAAA,MACvC,aAAa,kBAAkB,WAAW;AAAA,MAC1C,cAAc,kBAAkB,aAAa;AAAA,MAC7C,WAAW,kBAAkB,UAAU;AAAA,MACvC,SAAS,kBAAkB,aAAa;AAAA,MACxC,KAAK,CAAC;AAAA,IACR;AAAA,IACA,aAAa;AAAA,MACX,MAAM,qBAAqB,KAAK;AAAA,MAChC,WAAW,qBAAqB,UAAU;AAAA,MAC1C,aAAa,qBAAqB,WAAW;AAAA,MAC7C,cAAc,qBAAqB,aAAa;AAAA,MAChD,WAAW,qBAAqB,UAAU;AAAA,MAC1C,SAAS,qBAAqB,aAAa;AAAA,IAC7C;AAAA,IACA;AAAA,EACF;AACF;;;AC9QA,OAAO,iBAAiB;AAIxB,IAAM,aAAa;AACnB,IAAM,cAAc;AACpB,IAAM,eAAe;AACrB,IAAM,mBAAmB;AACzB,IAAM,mBAAmB;AACzB,IAAM,gBAAgB;AAEtB,SAAS,YAAY,MAAc,OAAuB;AACxD,QAAM,eAAe,YAAY,IAAI;AACrC,MAAI,gBAAgB,OAAO;AACzB,WAAO;AAAA,EACT;AACA,SAAO,OAAO,IAAI,OAAO,QAAQ,YAAY;AAC/C;AAEA,SAAS,cAAc,MAAc,OAAuB;AAC1D,QAAM,eAAe,YAAY,IAAI;AACrC,MAAI,gBAAgB,OAAO;AACzB,WAAO;AAAA,EACT;AACA,SAAO,IAAI,OAAO,QAAQ,YAAY,IAAI;AAC5C;AAKO,SAAS,iBACd,KACA,UACA,OAAa,cACL;AACR,QAAM,WAAW,YAAY,IAAI;AACjC,MAAI,IAAI,WAAW,GAAG;AACpB,WAAO,SAAS;AAAA,EAClB;AAEA,QAAM,YAAY,YAAY,KAAK,IAAI,GAAG,KAAK,CAAC;AAChD,QAAM,SAAS,KAAK,IAAI,WAAW,CAAC;AAEpC,QAAM,WAAW,CAAC,OAAe,SAC/B,UAAK,cAAc,OAAO,aAAa,CAAC,UAAK,IAAI;AACnD,QAAM,WAAW,SAAS,KAAK,IAAI,OAAO,WAAW,CAAC;AACtD,QAAM,aAAa,YAAY,QAAQ,IAAI;AAC3C,QAAM,aAAa,UAAK,cAAc,IAAI,aAAa,CAAC;AAExD,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,SAAS,eAAe;AAGnC,QAAM,KAAK,WAAM,SAAI,OAAO,UAAU,IAAI,QAAG;AAE7C,WAAS,MAAM,eAAe,GAAG,OAAO,GAAG,OAAO;AAChD,UAAM,QAAS,OAAO,eAAe,KAAM;AAC3C,UAAM,QAAQ,MAAM,QAAQ,CAAC;AAE7B,QAAI,OAAO;AACX,aAAS,MAAM,GAAG,MAAM,aAAa,OAAO;AAC1C,YAAM,QAAQ,KAAK,MAAO,MAAM,cAAe,IAAI,MAAM;AACzD,YAAM,WAAW,IAAI,KAAK,KAAK;AAC/B,YAAM,mBAAoB,WAAW,UAAW,eAAe;AAC/D,cAAQ,oBAAoB,MAAM,aAAa;AAAA,IACjD;AAEA,UAAM,KAAK,SAAS,OAAO,IAAI,CAAC;AAAA,EAClC;AAGA,QAAM,KAAK,GAAG,UAAU,GAAG,SAAI,OAAO,WAAW,CAAC,SAAI;AACtD,QAAM,KAAK,WAAM,SAAI,OAAO,UAAU,IAAI,QAAG;AAG7C,QAAM,UAAU,gBAAgB,IAAI,QAAQ,CAAC;AAC7C,QAAM,YAAY,IAAI,MAAM,WAAW,EAAE,KAAK,GAAG;AACjD,QAAM,WAAW,KAAK,IAAI,IAAI,SAAS,GAAG,CAAC;AAC3C,aAAW,SAAS,SAAS;AAC3B,UAAM,UAAU,SAAS,MAAM,QAAQ,KAAK,EAAE,GAAG,EAAE;AACnD,UAAM,WAAW,KAAK;AAAA,MACpB,cAAc;AAAA,MACd,KAAK,MAAO,UAAU,YAAa,cAAc,EAAE;AAAA,IACrD;AACA,aAAS,IAAI,GAAG,IAAI,MAAM,UAAU,WAAW,IAAI,aAAa,KAAK;AACnE,gBAAU,WAAW,CAAC,IAAI,MAAM,CAAC;AAAA,IACnC;AAAA,EACF;AACA,QAAM,KAAK,IAAI,OAAO,YAAY,UAAU,CAAC,IAAI,UAAU,KAAK,EAAE,CAAC;AAEnE,SAAO,MAAM,KAAK,IAAI;AACxB;AAKA,SAAS,gBAAgB,YAAoB,WAA6B;AACxE,MAAI,cAAc,GAAG;AACnB,WAAO,CAAC,IAAI;AAAA,EACd;AAEA,QAAM,SAAmB,CAAC;AAC1B,QAAM,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,aAAa,SAAS,CAAC;AAE3D,WAAS,IAAI,GAAG,IAAI,YAAY,KAAK,MAAM;AACzC,WAAO,KAAK,GAAG,CAAC,GAAG;AAAA,EACrB;AAGA,MAAI,OAAO,OAAO,SAAS,CAAC,MAAM,GAAG,aAAa,CAAC,KAAK;AACtD,WAAO,KAAK,GAAG,aAAa,CAAC,GAAG;AAAA,EAClC;AAEA,SAAO;AACT;AAKO,SAAS,mBAAmB,KAAe,OAAa,cAAsB;AACnF,QAAM,WAAW,YAAY,IAAI;AACjC,MAAI,IAAI,WAAW,GAAG;AACpB,WAAO,SAAS;AAAA,EAClB;AAEA,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,SAAS,iBAAiB;AAGrC,QAAM,SAAS,KAAK,IAAI,GAAG,KAAK,CAAC;AACjC,QAAM,UAAU;AAChB,QAAM,aAAa,SAAS;AAC5B,QAAM,YAAY,IAAI,MAAM,OAAO,EAAE,KAAK,CAAC;AAE3C,aAAW,KAAK,KAAK;AACnB,UAAM,cAAc,KAAK,IAAI,KAAK,MAAM,IAAI,UAAU,GAAG,UAAU,CAAC;AACpE,cAAU,WAAW;AAAA,EACvB;AAEA,QAAM,WAAW,KAAK,IAAI,GAAG,WAAW,CAAC;AAEzC,QAAM,SAAS,UAAU,IAAI,CAAC,GAAG,MAAM;AACrC,UAAM,eAAe,IAAI,YAAY,QAAQ,CAAC;AAC9C,UAAM,cAAc,IAAI,KAAK,YAAY,QAAQ,CAAC;AAClD,WAAO,GAAG,WAAW,IAAI,SAAS;AAAA,EACpC,CAAC;AACD,QAAM,aAAa,KAAK,IAAI,GAAG,OAAO,IAAI,CAAC,MAAM,YAAY,CAAC,CAAC,CAAC;AAEhE,WAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,UAAM,QAAQ,YAAY,OAAO,CAAC,GAAG,UAAU;AAC/C,UAAM,QAAQ,UAAU,CAAC;AACzB,UAAM,YAAY,KAAK,MAAO,QAAQ,WAAY,WAAW;AAC7D,UAAM,MAAM,WAAW,OAAO,SAAS;AAEvC,UAAM,cAAc,QAAQ,IAAI,IAAI,KAAK,KAAK;AAC9C,UAAM,KAAK,GAAG,KAAK,UAAK,GAAG,GAAG,WAAW,EAAE;AAAA,EAC7C;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAKO,SAAS,iBAAiB,OAAoB,OAAa,cAAsB;AACtF,QAAM,WAAW,YAAY,IAAI;AACjC,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,SAAS,kBAAkB,MAAM,UAAU,CAAC;AAGvD,QAAM,YACJ,YACA,YAAY,SAAS,aAAa,QAAQ,gBAAgB,IAC1D,aACA,cAAc,SAAS,aAAa,MAAM,gBAAgB,IAC1D,aACA,cAAc,SAAS,aAAa,KAAK,gBAAgB,IACzD,aACA,cAAc,SAAS,aAAa,KAAK,gBAAgB,IACzD,aACA,cAAc,SAAS,aAAa,KAAK,gBAAgB,IACzD,aACA,cAAc,SAAS,aAAa,KAAK,gBAAgB,IACzD,aACA,cAAc,SAAS,aAAa,KAAK,gBAAgB,IACzD;AAEF,QAAM,aAAa,YAAY,SAAS,IAAI;AAC5C,QAAM,KAAK,WAAM,SAAI,OAAO,UAAU,IAAI,QAAG;AAC7C,QAAM,KAAK,SAAS;AACpB,QAAM,KAAK,WAAM,SAAI,OAAO,UAAU,IAAI,QAAG;AAG7C,QAAM;AAAA,IACJ;AAAA,MACE,SAAS,YAAY;AAAA,MACrB,MAAM,KAAK;AAAA,MACX,MAAM,YAAY,KAAK;AAAA,MACvB,MAAM,YAAY,KAAK;AAAA,MACvB,MAAM,YAAY,KAAK;AAAA,MACvB,MAAM,IAAI;AAAA,MACV,MAAM,IAAI;AAAA,MACV;AAAA,IACF;AAAA,EACF;AACA,QAAM,KAAK,WAAM,SAAI,OAAO,UAAU,IAAI,QAAG;AAG7C,QAAM;AAAA,IACJ;AAAA,MACE,SAAS,YAAY;AAAA,MACrB,MAAM,KAAK;AAAA,MACX,MAAM,YAAY,UAAU;AAAA,MAC5B,MAAM,YAAY,UAAU;AAAA,MAC5B,MAAM,YAAY,UAAU;AAAA,MAC5B,MAAM,IAAI;AAAA,MACV,MAAM,IAAI;AAAA,MACV;AAAA,IACF;AAAA,EACF;AACA,QAAM,KAAK,WAAM,SAAI,OAAO,UAAU,IAAI,QAAG;AAG7C,QAAM;AAAA,IACJ;AAAA,MACE,SAAS,YAAY;AAAA,MACrB,MAAM,KAAK;AAAA,MACX,MAAM,YAAY,YAAY;AAAA,MAC9B,MAAM,YAAY,YAAY;AAAA,MAC9B,MAAM,YAAY,YAAY;AAAA,MAC9B,MAAM,IAAI;AAAA,MACV,MAAM,IAAI;AAAA,MACV;AAAA,IACF;AAAA,EACF;AACA,QAAM,KAAK,WAAM,SAAI,OAAO,UAAU,IAAI,QAAG;AAG7C,QAAM;AAAA,IACJ;AAAA,MACE,SAAS,YAAY;AAAA,MACrB,MAAM,KAAK;AAAA,MACX,MAAM,YAAY,aAAa;AAAA,MAC/B,MAAM,YAAY,aAAa;AAAA,MAC/B,MAAM,YAAY,aAAa;AAAA,MAC/B,MAAM,IAAI;AAAA,MACV,MAAM,IAAI;AAAA,MACV;AAAA,IACF;AAAA,EACF;AACA,QAAM,KAAK,WAAM,SAAI,OAAO,UAAU,IAAI,QAAG;AAG7C,QAAM;AAAA,IACJ;AAAA,MACE,SAAS,YAAY;AAAA,MACrB,MAAM,KAAK;AAAA,MACX,MAAM,YAAY,UAAU;AAAA,MAC5B,MAAM,YAAY,UAAU;AAAA,MAC5B,MAAM,YAAY,UAAU;AAAA,MAC5B,MAAM,IAAI;AAAA,MACV,MAAM,IAAI;AAAA,MACV;AAAA,IACF;AAAA,EACF;AAEA,QAAM,KAAK,WAAM,SAAI,OAAO,UAAU,IAAI,QAAG;AAG7C,QAAM;AAAA,IACJ;AAAA,MACE,SAAS,YAAY;AAAA,MACrB,MAAM,KAAK;AAAA,MACX,MAAM,YAAY,QAAQ;AAAA,MAC1B,MAAM,YAAY,QAAQ;AAAA,MAC1B,MAAM,YAAY,QAAQ;AAAA,MAC1B,MAAM,IAAI;AAAA,MACV,MAAM,IAAI;AAAA,MACV;AAAA,IACF;AAAA,EACF;AAEA,QAAM,KAAK,WAAM,SAAI,OAAO,UAAU,IAAI,QAAG;AAE7C,SAAO,MAAM,KAAK,IAAI;AACxB;AAKA,SAAS,cACP,OACAC,OACA,KACA,KACA,KACA,KACA,KACA,QACQ;AACR,QAAM,MAAM,CAAC,MAAe,WAAW,MAAM,EAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;AAEvE,SACE,YACA,YAAY,OAAO,gBAAgB,IACnC,aACA,cAAc,IAAIA,KAAI,GAAG,gBAAgB,IACzC,aACA,cAAc,IAAI,GAAG,GAAG,gBAAgB,IACxC,aACA,cAAc,IAAI,GAAG,GAAG,gBAAgB,IACxC,aACA,cAAc,IAAI,GAAG,GAAG,gBAAgB,IACxC,aACA,cAAc,IAAI,GAAG,GAAG,gBAAgB,IACxC,aACA,cAAc,IAAI,GAAG,GAAG,gBAAgB,IACxC;AAEJ;AAKA,SAAS,uBAAuB,IAAoB;AAClD,MAAI,OAAO,KAAK,MAAM,EAAE,GAAG;AACzB,WAAO,GAAG,GAAG,QAAQ,CAAC,CAAC;AAAA,EACzB;AACA,SAAO,GAAG,GAAG,QAAQ,CAAC,CAAC;AACzB;AAKO,SAAS,mBACd,SACA,UACA,OAAa,cACL;AACR,QAAM,WAAW,YAAY,IAAI;AACjC,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK;AAAA,EAAK,SAAS,SAAS,WAAW,CAAC,CAAC,EAAE;AACjD,QAAM,KAAK,KAAK,SAAS,aAAa,IAAI,KAAK,uBAAuB,QAAQ,IAAI,CAAC,EAAE;AACrF,QAAM,KAAK,KAAK,SAAS,aAAa,SAAS,KAAK,uBAAuB,QAAQ,SAAS,CAAC,EAAE;AAC/F,QAAM,KAAK,KAAK,SAAS,aAAa,WAAW,KAAK,QAAQ,WAAW,EAAE;AAC3E,QAAM;AAAA,IACJ,KAAK,SAAS,aAAa,YAAY,KAAK,QAAQ,aAAa,QAAQ,CAAC,CAAC;AAAA,EAC7E;AACA,QAAM,KAAK,KAAK,SAAS,aAAa,SAAS,KAAK,QAAQ,UAAU,QAAQ,CAAC,CAAC,WAAW;AAC3F,QAAM,KAAK,KAAK,SAAS,aAAa,OAAO,KAAK,QAAQ,QAAQ,QAAQ,CAAC,CAAC,WAAW;AACvF,SAAO,MAAM,KAAK,IAAI;AACxB;AAKO,SAAS,aAAa,OAAoB,OAAa,cAAsB;AAClF,QAAM,WAAW,YAAY,IAAI;AACjC,QAAM,QAAkB,CAAC;AAEzB,QAAM,KAAK,OAAO,SAAI,OAAO,EAAE,CAAC;AAChC,QAAM,KAAK,SAAS,WAAW;AAC/B,QAAM,KAAK,SAAI,OAAO,EAAE,CAAC;AAGzB,QAAM,KAAK,iBAAiB,OAAO,IAAI,CAAC;AAGxC,MAAI,MAAM,KAAK,IAAI,SAAS,GAAG;AAC7B,UAAM,KAAK,OAAO,iBAAiB,MAAM,KAAK,KAAK,QAAW,IAAI,CAAC;AAAA,EACrE;AAGA,MAAI,MAAM,KAAK,IAAI,SAAS,GAAG;AAC7B,UAAM,KAAK,OAAO,mBAAmB,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,EAC5D;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;;;AC3XA,SAAS,oBAAoB;AAC7B,SAAS,SAAS,eAAe;AACjC,SAAS,qBAAqB;AAK9B,IAAM,eAAe;AAAA,EACnB;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACF;AAEA,IAAM,UAAU;AAAA,EACd,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,iBAAiB;AAAA,EACjB,gBAAgB;AAClB;AA4BA,SAAS,aAAa,KAAa,WAAmB,GAAW;AAC/D,SAAO,IAAI,QAAQ,QAAQ;AAC7B;AAEA,SAAS,WAAW,IAAoB;AACtC,MAAI,KAAK,KAAM;AACb,WAAO,GAAG,GAAG,QAAQ,CAAC,CAAC;AAAA,EACzB;AACA,SAAO,IAAI,KAAK,KAAM,QAAQ,CAAC,CAAC;AAClC;AAEA,SAAS,WAAW,MAAsB;AACxC,QAAM,MAA8B;AAAA,IAClC,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AACA,SAAO,KAAK,QAAQ,YAAY,CAAC,MAAM,IAAI,CAAC,CAAC;AAC/C;AAEA,SAAS,eAAuB;AAC9B,QAAM,aAAa,cAAc,YAAY,GAAG;AAChD,QAAM,YAAY,QAAQ,UAAU;AACpC,SAAO,aAAa,QAAQ,WAAW,eAAe,GAAG,OAAO;AAClE;AAEA,SAAS,gBAAgB,UAAkB,MAA4B;AACrE,MAAI,SAAS;AACb,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,GAAG;AAC/C,aAAS,OAAO,WAAW,IAAI,OAAO,KAAK,GAAG,MAAM,GAAG,GAAG,KAAK;AAAA,EACjE;AACA,SAAO;AACT;AAEA,SAAS,mBAAmB,SAA8B,UAA4B;AACpF,QAAM,SAAS,QAAQ,QAAQ,CAAC,MAAM,EAAE,GAAG;AAC3C,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,wBAAwB,SAAS,eAAe,mBAAmB;AAAA,EAC5E;AAEA,QAAM,SAAS,KAAK,IAAI,GAAG,QAAQ,CAAC;AACpC,QAAM,cAAc,KAAK,IAAI,GAAG,QAAQ,IAAI,CAAC,MAAM,EAAE,IAAI,MAAM,CAAC;AAChE,QAAM,QAAQ;AACd,QAAM,SAAS;AACf,QAAM,UAAU,EAAE,KAAK,IAAI,OAAO,IAAI,QAAQ,IAAI,MAAM,GAAG;AAC3D,QAAM,aAAa,QAAQ,QAAQ,OAAO,QAAQ;AAClD,QAAM,cAAc,SAAS,QAAQ,MAAM,QAAQ;AAEnD,QAAM,SAAmB,CAAC;AAC1B,WAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AACpC,UAAM,SAAS,QAAQ,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC;AAC/C,WAAO,KAAK,OAAO,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,OAAO,MAAM;AAAA,EAC/D;AAEA,QAAM,YAAY,QACf,IAAI,CAAC,QAAQ,QAAQ;AACpB,UAAM,QAAQ,aAAa,MAAM,aAAa,MAAM;AACpD,QAAI,SAAS;AACb,QAAI,aAAa,GAAG,QAAQ,IAAI,IAAI,SAAS,QAAQ,MAAM;AAE3D,WAAO,IAAI,QAAQ,CAAC,KAAK,MAAM;AAC7B,YAAM,IAAI,QAAQ,OAAQ,IAAI,KAAK,IAAI,cAAc,GAAG,CAAC,IAAK;AAC9D,YAAM,IAAI,QAAQ,MAAM,cAAe,MAAM,SAAU;AACvD,gBAAU,GAAG,CAAC,IAAI,CAAC;AACnB,oBAAc,GAAG,CAAC,IAAI,CAAC;AAAA,IACzB,CAAC;AACD,kBAAc,GAAG,QAAQ,OAAO,UAAU,IAAI,SAAS,QAAQ,MAAM;AAErE,WAAO;AAAA;AAAA,mCAEsB,GAAG;AAAA,gDACU,KAAK;AAAA,kDACH,KAAK;AAAA;AAAA;AAAA;AAAA,kBAIrC,WAAW,KAAK,CAAC;AAAA,0BACT,GAAG;AAAA,sBACP,GAAG;AAAA;AAAA;AAAA;AAAA,kBAIP,KAAK;AAAA;AAAA,kBAEL,OAAO,KAAK,CAAC;AAAA,2BACJ,GAAG;AAAA,oBACV,MAAM,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAYnB,OAAO,IACN,IAAI,CAAC,KAAK,MAAM;AACf,YAAM,IAAI,QAAQ,OAAQ,IAAI,KAAK,IAAI,cAAc,GAAG,CAAC,IAAK;AAC9D,YAAM,IAAI,QAAQ,MAAM,cAAe,MAAM,SAAU;AACvD,aAAO,eAAe,CAAC,SAAS,CAAC,iBAAiB,QAAQ,EAAE,aAAa,KAAK,iCAAiC,GAAG,wBAAwB,SAAS,kBAAkB,SAAS,IAAI,CAAC,MAAM,IAAI,QAAQ,CAAC,CAAC;AAAA,oEAC7I,MAAM,IAAI,IAAI;AAAA;AAAA,IAE1E,CAAC,EACA,KAAK,EAAE,CAAC;AAAA;AAAA,EAEb,CAAC,EACA,KAAK,UAAU;AAElB,MAAI,YAAY;AAChB,SAAO,QAAQ,CAAC,KAAK,MAAM;AACzB,UAAM,IAAI,QAAQ,OAAQ,IAAI,KAAK,IAAI,cAAc,GAAG,CAAC,IAAK;AAC9D,UAAM,IAAI,QAAQ,MAAM,cAAe,MAAM,SAAU;AACvD,iBAAa,GAAG,CAAC,IAAI,CAAC;AAAA,EACxB,CAAC;AAED,QAAM,UAAU,CAAC;AACjB,WAAS,IAAI,GAAG,KAAK,GAAG,KAAK;AAC3B,UAAM,QAAQ,KAAK,MAAO,SAAS,IAAK,CAAC;AACzC,UAAM,IAAI,QAAQ,MAAM,cAAe,IAAI,IAAK;AAChD,YAAQ;AAAA,MACN,YAAY,QAAQ,OAAO,EAAE,QAAQ,IAAI,CAAC,4CAA4C,QAAQ,SAAS,KAAK,KAAK;AAAA,IACnH;AACA,QAAI,IAAI,GAAG;AACT,YAAM,QAAQ,QAAQ,MAAM,cAAe,IAAI,IAAK;AACpD,cAAQ;AAAA,QACN,aAAa,QAAQ,IAAI,SAAS,KAAK,SAAS,QAAQ,QAAQ,KAAK,SAAS,KAAK,aAAa,QAAQ,MAAM;AAAA,MAChH;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAU,CAAC;AACjB,QAAM,SAAS,KAAK,IAAI,aAAa,EAAE;AACvC,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,UAAM,IAAI,QAAQ,OAAQ,IAAI,KAAK,IAAI,SAAS,GAAG,CAAC,IAAK;AACzD,UAAM,QAAQ,EAAE,SAAS;AACzB,YAAQ;AAAA,MACN,YAAY,CAAC,QAAQ,SAAS,QAAQ,SAAS,EAAE,+CAA+C,QAAQ,SAAS,KAAK,KAAK,GAAG,SAAS,YAAY;AAAA,IACrJ;AAAA,EACF;AAEA,SAAO;AAAA,wBACe,KAAK,IAAI,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAOtB,QAAQ,IAAI,QAAQ,QAAQ,GAAG,YAAY,UAAU,aAAa,WAAW,WAAW,QAAQ,MAAM;AAAA,QAC/G,QAAQ,KAAK,UAAU,CAAC;AAAA,QACxB,QAAQ,KAAK,UAAU,CAAC;AAAA,kBACd,QAAQ,IAAI,SAAS,QAAQ,GAAG,SAAS,QAAQ,IAAI,SAAS,SAAS,QAAQ,MAAM,aAAa,QAAQ,MAAM;AAAA,kBAChH,QAAQ,IAAI,SAAS,SAAS,QAAQ,MAAM,SAAS,QAAQ,QAAQ,KAAK,SAAS,SAAS,QAAQ,MAAM,aAAa,QAAQ,MAAM;AAAA,QAC/I,SAAS;AAAA;AAAA;AAAA,kBAGC,QAAQ,IAAI;AAAA;AAAA;AAAA;AAAA,kBAIZ,UAAU,KAAK,CAAC;AAAA;AAAA,iBAEjB,QAAQ,OAAO,aAAa,CAAC,QAAQ,SAAS,CAAC,+CAA+C,QAAQ,SAAS,WAAW,SAAS,YAAY;AAAA,wBACxI,QAAQ,MAAM,cAAc,CAAC,+CAA+C,QAAQ,SAAS,gCAAgC,QAAQ,MAAM,cAAc,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,gBAKlK,SAAS,kBAAkB;AAAA;AAAA,QAEnC,QACC,IAAI,CAAC,GAAG,QAAQ;AACf,UAAM,QAAQ,aAAa,MAAM,aAAa,MAAM;AACpD,WAAO;AAAA;AAAA,yDAEwC,KAAK;AAAA,kBAC5C,SAAS,OAAO,IAAI,MAAM,CAAC;AAAA;AAAA,EAErC,CAAC,EACA,KAAK,EAAE,CAAC;AAAA;AAAA;AAGjB;AAEA,SAAS,qBAAqB,OAAoB,UAA4B;AAC5E,QAAM,SAAS,MAAM,KAAK;AAC1B,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO,wBAAwB,SAAS,aAAa,uBAAuB;AAAA,EAC9E;AAEA,QAAM,SAAS,KAAK,IAAI,GAAG,MAAM;AACjC,QAAM,QAAQ;AACd,QAAM,SAAS;AACf,QAAM,UAAU,EAAE,KAAK,IAAI,OAAO,IAAI,QAAQ,IAAI,MAAM,GAAG;AAC3D,QAAM,aAAa,QAAQ,QAAQ,OAAO,QAAQ;AAClD,QAAM,cAAc,SAAS,QAAQ,MAAM,QAAQ;AAEnD,QAAM,OAAO,OACV,IAAI,CAAC,KAAK,MAAM;AACf,UAAM,WAAW,aAAa,OAAO,SAAS;AAC9C,UAAM,IAAI,QAAQ,OAAQ,IAAI,OAAO,SAAU;AAC/C,UAAM,YAAa,MAAM,SAAU;AACnC,UAAM,IAAI,QAAQ,MAAM,cAAc;AACtC,UAAM,MAAM,MAAO,MAAM,SAAU;AACnC,UAAM,QAAQ,OAAO,GAAG;AAExB,WAAO;AAAA;AAAA,aAEA,CAAC;AAAA,aACD,CAAC;AAAA,iBACG,QAAQ;AAAA,kBACP,SAAS;AAAA,gBACX,KAAK;AAAA;AAAA,uBAEE,CAAC;AAAA,oBACJ,IAAI,QAAQ,CAAC,CAAC;AAAA;AAAA;AAAA,iBAGjB,SAAS,kBAAkB,aAAa,IAAI,CAAC,MAAM,IAAI,QAAQ,CAAC,CAAC;AAAA;AAAA;AAAA;AAAA,gBAIlE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,mBAKN,IAAI,IAAI;AAAA;AAAA;AAAA;AAAA,kBAIT,SAAS,QAAQ,MAAM;AAAA,gBACzB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,mBAKE,IAAI,IAAI;AAAA;AAAA;AAAA;AAAA,EAIvB,CAAC,EACA,KAAK,EAAE;AAEV,QAAM,UAAU,CAAC;AACjB,WAAS,IAAI,GAAG,KAAK,GAAG,KAAK;AAC3B,UAAM,QAAQ,KAAK,MAAO,SAAS,IAAK,CAAC;AACzC,UAAM,IAAI,QAAQ,MAAM,cAAe,IAAI,IAAK;AAChD,YAAQ;AAAA,MACN,YAAY,QAAQ,OAAO,EAAE,QAAQ,IAAI,CAAC,4CAA4C,QAAQ,SAAS,KAAK,KAAK;AAAA,IACnH;AAAA,EACF;AAEA,QAAM,UAAU,CAAC;AACjB,QAAM,SAAS,KAAK,IAAI,OAAO,QAAQ,CAAC;AACxC,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,UAAM,IAAI,QAAQ,OAAQ,IAAI,KAAK,IAAI,SAAS,GAAG,CAAC,IAAK;AACzD,UAAM,QAAQ,EAAE,SAAS;AACzB,YAAQ;AAAA,MACN,YAAY,CAAC,QAAQ,SAAS,QAAQ,SAAS,EAAE,+CAA+C,QAAQ,SAAS,KAAK,KAAK,GAAG,SAAS,YAAY;AAAA,IACrJ;AAAA,EACF;AAEA,SAAO;AAAA,wBACe,KAAK,IAAI,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA,iBAKtB,QAAQ,IAAI,QAAQ,QAAQ,GAAG,YAAY,UAAU,aAAa,WAAW,WAAW,QAAQ,MAAM;AAAA,QAC/G,QAAQ,KAAK,UAAU,CAAC;AAAA,QACxB,QAAQ,KAAK,UAAU,CAAC;AAAA,kBACd,QAAQ,IAAI,SAAS,QAAQ,GAAG,SAAS,QAAQ,IAAI,SAAS,SAAS,QAAQ,MAAM,aAAa,QAAQ,MAAM;AAAA,kBAChH,QAAQ,IAAI,SAAS,SAAS,QAAQ,MAAM,SAAS,QAAQ,QAAQ,KAAK,SAAS,SAAS,QAAQ,MAAM,aAAa,QAAQ,MAAM;AAAA,QAC/I,IAAI;AAAA;AAAA;AAGZ;AAEO,SAAS,mBAAmBC,UAAoC;AACrE,QAAM,EAAE,QAAQ,eAAe,OAAO,MAAM,SAAS,IAAIA;AAEzD,QAAM,OAAO,SAAS;AACtB,QAAM,YAAW,oBAAI,KAAK,GAAE,eAAe,OAAO,UAAU,OAAO;AAEnE,QAAM,eAAe;AAAA,IACnB;AAAA,MACE,OAAO,SAAS,YAAY;AAAA,MAC5B,OAAO,WAAW,MAAM,KAAK,IAAI;AAAA,MACjC,QAAQ,GAAG,SAAS,aAAa,GAAG,KAAK,WAAW,MAAM,IAAI,IAAI,CAAC,SAAM,SAAS,aAAa,GAAG,KAAK,WAAW,MAAM,IAAI,IAAI,CAAC;AAAA,MACjI,QAAQ,QAAQ;AAAA,IAClB;AAAA,IACA;AAAA,MACE,OAAO,SAAS,YAAY;AAAA,MAC5B,OAAO,aAAa,MAAM,KAAK,YAAY;AAAA,MAC3C,QAAQ,GAAG,SAAS,aAAa,GAAG,KAAK,aAAa,MAAM,IAAI,YAAY,CAAC,SAAM,SAAS,aAAa,GAAG,KAAK,aAAa,MAAM,IAAI,YAAY,CAAC;AAAA,MACrJ,QAAQ,QAAQ;AAAA,MAChB,MAAM,SAAS;AAAA,IACjB;AAAA,IACA;AAAA,MACE,OAAO,SAAS,YAAY;AAAA,MAC5B,OAAO,aAAa,MAAM,KAAK,SAAS;AAAA,MACxC,QAAQ,GAAG,SAAS,aAAa,GAAG,KAAK,aAAa,MAAM,IAAI,SAAS,CAAC,SAAM,SAAS,aAAa,GAAG,KAAK,aAAa,MAAM,IAAI,SAAS,CAAC;AAAA,MAC/I,QAAQ,QAAQ;AAAA,MAChB,MAAM,SAAS;AAAA,IACjB;AAAA,IACA;AAAA,MACE,OAAO,SAAS,YAAY;AAAA,MAC5B,OAAO,aAAa,MAAM,KAAK,aAAa,CAAC;AAAA,MAC7C,QAAQ,GAAG,SAAS,aAAa,GAAG,KAAK,aAAa,MAAM,IAAI,aAAa,CAAC,CAAC,SAAM,SAAS,aAAa,GAAG,KAAK,aAAa,MAAM,IAAI,aAAa,CAAC,CAAC;AAAA,MACzJ,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,QAAM,aAAa,cAChB;AAAA,IACC,CAAC,QAAQ,QAAQ;AAAA;AAAA,wCAEiB,MAAM,CAAC;AAAA,gBAC/B,WAAW,OAAO,IAAI,CAAC;AAAA,gBACvB,WAAW,OAAO,SAAS,CAAC;AAAA,gBAC5B,OAAO,WAAW;AAAA,gBAClB,aAAa,OAAO,YAAY,CAAC;AAAA,gBACjC,aAAa,OAAO,SAAS,CAAC;AAAA,gBAC9B,OAAO,OAAO;AAAA;AAAA;AAAA,EAG1B,EACC,KAAK,EAAE;AAEV,QAAM,YAAY;AAAA,IAChB;AAAA,MACE,QAAQ,SAAS,YAAY;AAAA,MAC7B,MAAM,WAAW,MAAM,KAAK,IAAI;AAAA,MAChC,KAAK,WAAW,MAAM,YAAY,KAAK,GAAG;AAAA,MAC1C,KAAK,WAAW,MAAM,YAAY,KAAK,GAAG;AAAA,MAC1C,KAAK,WAAW,MAAM,YAAY,KAAK,GAAG;AAAA,MAC1C,KAAK,WAAW,MAAM,IAAI,IAAI;AAAA,MAC9B,KAAK,WAAW,MAAM,IAAI,IAAI;AAAA,IAChC;AAAA,IACA;AAAA,MACE,QAAQ,SAAS,YAAY;AAAA,MAC7B,MAAM,WAAW,MAAM,KAAK,SAAS;AAAA,MACrC,KAAK,WAAW,MAAM,YAAY,UAAU,GAAG;AAAA,MAC/C,KAAK,WAAW,MAAM,YAAY,UAAU,GAAG;AAAA,MAC/C,KAAK,WAAW,MAAM,YAAY,UAAU,GAAG;AAAA,MAC/C,KAAK,WAAW,MAAM,IAAI,SAAS;AAAA,MACnC,KAAK,WAAW,MAAM,IAAI,SAAS;AAAA,IACrC;AAAA,IACA;AAAA,MACE,QAAQ,SAAS,YAAY;AAAA,MAC7B,MAAM,aAAa,MAAM,KAAK,aAAa,CAAC;AAAA,MAC5C,KAAK,aAAa,MAAM,YAAY,YAAY,KAAK,CAAC;AAAA,MACtD,KAAK,aAAa,MAAM,YAAY,YAAY,KAAK,CAAC;AAAA,MACtD,KAAK,aAAa,MAAM,YAAY,YAAY,KAAK,CAAC;AAAA,MACtD,KAAK,aAAa,MAAM,IAAI,aAAa,CAAC;AAAA,MAC1C,KAAK,aAAa,MAAM,IAAI,aAAa,CAAC;AAAA,IAC5C;AAAA,IACA;AAAA,MACE,QAAQ,SAAS,YAAY;AAAA,MAC7B,MAAM,aAAa,MAAM,KAAK,YAAY;AAAA,MAC1C,KAAK,aAAa,MAAM,YAAY,aAAa,GAAG;AAAA,MACpD,KAAK,aAAa,MAAM,YAAY,aAAa,GAAG;AAAA,MACpD,KAAK,aAAa,MAAM,YAAY,aAAa,GAAG;AAAA,MACpD,KAAK,aAAa,MAAM,IAAI,YAAY;AAAA,MACxC,KAAK,aAAa,MAAM,IAAI,YAAY;AAAA,IAC1C;AAAA,IACA;AAAA,MACE,QAAQ,SAAS,YAAY;AAAA,MAC7B,MAAM,aAAa,MAAM,KAAK,SAAS;AAAA,MACvC,KAAK,aAAa,MAAM,YAAY,UAAU,GAAG;AAAA,MACjD,KAAK,aAAa,MAAM,YAAY,UAAU,GAAG;AAAA,MACjD,KAAK,aAAa,MAAM,YAAY,UAAU,GAAG;AAAA,MACjD,KAAK,aAAa,MAAM,IAAI,SAAS;AAAA,MACrC,KAAK,aAAa,MAAM,IAAI,SAAS;AAAA,IACvC;AAAA,IACA;AAAA,MACE,QAAQ,SAAS,YAAY;AAAA,MAC7B,MAAM,aAAa,MAAM,KAAK,OAAO;AAAA,MACrC,KAAK,aAAa,MAAM,YAAY,QAAQ,GAAG;AAAA,MAC/C,KAAK,aAAa,MAAM,YAAY,QAAQ,GAAG;AAAA,MAC/C,KAAK,aAAa,MAAM,YAAY,QAAQ,GAAG;AAAA,MAC/C,KAAK,aAAa,MAAM,IAAI,OAAO;AAAA,MACnC,KAAK,aAAa,MAAM,IAAI,OAAO;AAAA,IACrC;AAAA,EACF,EACG;AAAA,IACC,CAAC,QAAQ;AAAA;AAAA,oCAEqB,IAAI,MAAM;AAAA,sCACR,IAAI,IAAI;AAAA,gBAC9B,IAAI,GAAG;AAAA,gBACP,IAAI,GAAG;AAAA,gBACP,IAAI,GAAG;AAAA,gBACP,IAAI,GAAG;AAAA,gBACP,IAAI,GAAG;AAAA;AAAA;AAAA,EAGnB,EACC,KAAK,EAAE;AAEV,QAAM,aAAa,mBAAmB,eAAe,QAAQ;AAC7D,QAAM,WAAW,qBAAqB,OAAO,QAAQ;AAErD,QAAM,iBAAiB;AAAA;AAAA;AAAA,uCAGc,SAAS,aAAa,QAAQ;AAAA,uCAC9B,OAAO,SAAS,YAAY,CAAC;AAAA;AAAA;AAAA,uCAG7B,SAAS,aAAa,KAAK;AAAA,uCAC3B,WAAW,OAAO,KAAK,CAAC;AAAA;AAAA;AAAA,uCAGxB,SAAS,aAAa,SAAS;AAAA,uCAC/B,OAAO,SAAS;AAAA;AAAA;AAAA,uCAGhB,SAAS,aAAa,IAAI;AAAA,uCAC1B,OAAO,QAAQ;AAAA;AAAA;AAAA,uCAGf,SAAS,aAAa,MAAM;AAAA,wCAC3B,WAAW,OAAO,MAAM,CAAC;AAAA;AAAA;AAAA;AAK/D,QAAM,mBAAmB,aACtB;AAAA,IACC,CAAC,SAAS;AAAA,kDACkC,KAAK,MAAM;AAAA,oCACzB,KAAK,KAAK;AAAA,oCACV,KAAK,KAAK,2BAA2B,KAAK,QAAQ,EAAE;AAAA,qCACnD,KAAK,MAAM;AAAA;AAAA;AAAA,EAG5C,EACC,KAAK,EAAE;AAEV,QAAM,aAAa;AAAA;AAAA;AAAA,qCAGgB,SAAS,eAAe;AAAA,YACjD,UAAU;AAAA;AAAA;AAAA,qCAGe,SAAS,mBAAmB;AAAA,YACrD,QAAQ;AAAA;AAAA;AAAA;AAKlB,QAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA,oBAKL,SAAS,aAAa,MAAM;AAAA,oBAC5B,SAAS,aAAa,IAAI;AAAA,oBAC1B,SAAS,aAAa,GAAG;AAAA,oBACzB,SAAS,aAAa,GAAG;AAAA,oBACzB,SAAS,aAAa,GAAG;AAAA,oBACzB,SAAS,aAAa,GAAG;AAAA,oBACzB,SAAS,aAAa,GAAG;AAAA;AAAA;AAAA;AAAA,cAI/B,SAAS;AAAA;AAAA;AAAA;AAAA;AAMrB,QAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA,oBAKP,SAAS,OAAO;AAAA,oBAChB,SAAS,aAAa,IAAI;AAAA,oBAC1B,SAAS,aAAa,SAAS;AAAA,oBAC/B,SAAS,aAAa,WAAW;AAAA,oBACjC,SAAS,aAAa,YAAY;AAAA,oBAClC,SAAS,aAAa,SAAS;AAAA,oBAC/B,SAAS,aAAa,OAAO;AAAA;AAAA;AAAA;AAAA,cAInC,UAAU;AAAA;AAAA;AAAA;AAAA;AAMtB,QAAM,OAAqB;AAAA,IACzB;AAAA,IACA,OAAO,SAAS;AAAA,IAChB,aAAa,SAAS;AAAA,IACtB,eAAe,SAAS;AAAA,IACxB;AAAA,IACA,eAAe,SAAS;AAAA,IACxB,gBAAgB,SAAS;AAAA,IACzB,eAAe,SAAS;AAAA,IACxB,YAAY,SAAS,kBAAkB,MAAM,UAAU;AAAA,IACvD,gBAAgB,SAAS;AAAA,IACzB,YAAY;AAAA,IACZ,cAAc;AAAA,IACd,QAAQ;AAAA,IACR,YAAY;AAAA,IACZ,cAAc;AAAA,EAChB;AAEA,QAAM,WAAW,aAAa;AAC9B,SAAO,gBAAgB,UAAU,IAAI;AACvC;;;AChhBO,SAAS,mBACd,QACA,SACA,OACQ;AACR,QAAM,aAAyB;AAAA,IAC7B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC,QAAQ;AAAA,MACN,UAAU,OAAO;AAAA,MACjB,OAAO,OAAO;AAAA,MACd,WAAW,OAAO;AAAA,MAClB,UAAU,OAAO;AAAA,MACjB,QAAQ,OAAO;AAAA,IACjB;AAAA,IACA,MAAM,QAAQ,IAAI,CAAC,OAAO;AAAA,MACxB,MAAM,KAAK,MAAM,EAAE,OAAO,GAAG,IAAI;AAAA,MACjC,WAAW,KAAK,MAAM,EAAE,YAAY,GAAG,IAAI;AAAA,MAC3C,aAAa,EAAE;AAAA,MACf,cAAc,KAAK,MAAM,EAAE,eAAe,GAAG,IAAI;AAAA,MACjD,WAAW,KAAK,MAAM,EAAE,YAAY,GAAG,IAAI;AAAA,MAC3C,SAAS,KAAK,MAAM,EAAE,UAAU,GAAG,IAAI;AAAA,MACvC,KAAK,EAAE;AAAA,IACT,EAAE;AAAA,IACF,OAAO;AAAA,MACL,MAAM;AAAA,QACJ,MAAM,KAAK,MAAM,MAAM,KAAK,OAAO,GAAG,IAAI;AAAA,QAC1C,WAAW,KAAK,MAAM,MAAM,KAAK,YAAY,GAAG,IAAI;AAAA,QACpD,aAAa,KAAK,MAAM,MAAM,KAAK,cAAc,GAAG,IAAI;AAAA,QACxD,cAAc,KAAK,MAAM,MAAM,KAAK,eAAe,GAAG,IAAI;AAAA,QAC1D,WAAW,KAAK,MAAM,MAAM,KAAK,YAAY,GAAG,IAAI;AAAA,QACpD,SAAS,KAAK,MAAM,MAAM,KAAK,UAAU,GAAG,IAAI;AAAA,MAClD;AAAA,MACA,KAAK;AAAA,QACH,MAAM,KAAK,MAAM,MAAM,IAAI,OAAO,GAAG,IAAI;AAAA,QACzC,WAAW,KAAK,MAAM,MAAM,IAAI,YAAY,GAAG,IAAI;AAAA,QACnD,aAAa,MAAM,IAAI;AAAA,QACvB,cAAc,KAAK,MAAM,MAAM,IAAI,eAAe,GAAG,IAAI;AAAA,QACzD,WAAW,KAAK,MAAM,MAAM,IAAI,YAAY,GAAG,IAAI;AAAA,QACnD,SAAS,KAAK,MAAM,MAAM,IAAI,UAAU,GAAG,IAAI;AAAA,MACjD;AAAA,MACA,KAAK;AAAA,QACH,MAAM,KAAK,MAAM,MAAM,IAAI,OAAO,GAAG,IAAI;AAAA,QACzC,WAAW,KAAK,MAAM,MAAM,IAAI,YAAY,GAAG,IAAI;AAAA,QACnD,aAAa,MAAM,IAAI;AAAA,QACvB,cAAc,KAAK,MAAM,MAAM,IAAI,eAAe,GAAG,IAAI;AAAA,QACzD,WAAW,KAAK,MAAM,MAAM,IAAI,YAAY,GAAG,IAAI;AAAA,QACnD,SAAS,KAAK,MAAM,MAAM,IAAI,UAAU,GAAG,IAAI;AAAA,MACjD;AAAA,MACA,KAAK;AAAA,QACH,MAAM,KAAK,MAAM,MAAM,YAAY,KAAK,MAAM,GAAG,IAAI;AAAA,QACrD,WAAW,KAAK,MAAM,MAAM,YAAY,UAAU,MAAM,GAAG,IAAI;AAAA,QAC/D,aAAa,KAAK,MAAM,MAAM,YAAY,YAAY,MAAM,GAAG,IAAI;AAAA,QACnE,cAAc,KAAK,MAAM,MAAM,YAAY,aAAa,MAAM,GAAG,IAAI;AAAA,QACrE,WAAW,KAAK,MAAM,MAAM,YAAY,UAAU,MAAM,GAAG,IAAI;AAAA,QAC/D,SAAS,KAAK,MAAM,MAAM,YAAY,QAAQ,MAAM,GAAG,IAAI;AAAA,MAC7D;AAAA,MACA,KAAK;AAAA,QACH,MAAM,KAAK,MAAM,MAAM,YAAY,KAAK,MAAM,GAAG,IAAI;AAAA,QACrD,WAAW,KAAK,MAAM,MAAM,YAAY,UAAU,MAAM,GAAG,IAAI;AAAA,QAC/D,aAAa,KAAK,MAAM,MAAM,YAAY,YAAY,MAAM,GAAG,IAAI;AAAA,QACnE,cAAc,KAAK,MAAM,MAAM,YAAY,aAAa,MAAM,GAAG,IAAI;AAAA,QACrE,WAAW,KAAK,MAAM,MAAM,YAAY,UAAU,MAAM,GAAG,IAAI;AAAA,QAC/D,SAAS,KAAK,MAAM,MAAM,YAAY,QAAQ,MAAM,GAAG,IAAI;AAAA,MAC7D;AAAA,MACA,KAAK;AAAA,QACH,MAAM,KAAK,MAAM,MAAM,YAAY,KAAK,MAAM,GAAG,IAAI;AAAA,QACrD,WAAW,KAAK,MAAM,MAAM,YAAY,UAAU,MAAM,GAAG,IAAI;AAAA,QAC/D,aAAa,KAAK,MAAM,MAAM,YAAY,YAAY,MAAM,GAAG,IAAI;AAAA,QACnE,cAAc,KAAK,MAAM,MAAM,YAAY,aAAa,MAAM,GAAG,IAAI;AAAA,QACrE,WAAW,KAAK,MAAM,MAAM,YAAY,UAAU,MAAM,GAAG,IAAI;AAAA,QAC/D,SAAS,KAAK,MAAM,MAAM,YAAY,QAAQ,MAAM,GAAG,IAAI;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AAEA,SAAO,KAAK,UAAU,YAAY,MAAM,CAAC;AAC3C;AAKO,SAAS,kBACd,QACA,SACA,OACQ;AACR,QAAM,QAAkB,CAAC;AAGzB,QAAM,KAAK,4BAA4B;AACvC,QAAM,KAAK,iBAAgB,oBAAI,KAAK,GAAE,YAAY,CAAC,EAAE;AACrD,QAAM,KAAK,eAAe,OAAO,QAAQ,EAAE;AAC3C,QAAM,KAAK,YAAY,OAAO,KAAK,EAAE;AACrC,QAAM,KAAK,WAAW,OAAO,QAAQ,EAAE;AACvC,QAAM,KAAK,aAAa,OAAO,MAAM,EAAE;AACvC,QAAM,KAAK,EAAE;AAGb,QAAM,KAAK,cAAc;AACzB,QAAM,KAAK,iCAAiC;AAC5C,QAAM;AAAA,IACJ,aAAa,MAAM,KAAK,KAAK,QAAQ,CAAC,CAAC,IAAI,MAAM,YAAY,KAAK,IAAI,QAAQ,CAAC,CAAC,IAAI,MAAM,YAAY,KAAK,IAAI,QAAQ,CAAC,CAAC,IAAI,MAAM,YAAY,KAAK,IAAI,QAAQ,CAAC,CAAC,IAAI,MAAM,IAAI,KAAK,QAAQ,CAAC,CAAC,IAAI,MAAM,IAAI,KAAK,QAAQ,CAAC,CAAC;AAAA,EAC9N;AACA,QAAM;AAAA,IACJ,mBAAmB,MAAM,KAAK,UAAU,QAAQ,CAAC,CAAC,IAAI,MAAM,YAAY,UAAU,IAAI,QAAQ,CAAC,CAAC,IAAI,MAAM,YAAY,UAAU,IAAI,QAAQ,CAAC,CAAC,IAAI,MAAM,YAAY,UAAU,IAAI,QAAQ,CAAC,CAAC,IAAI,MAAM,IAAI,UAAU,QAAQ,CAAC,CAAC,IAAI,MAAM,IAAI,UAAU,QAAQ,CAAC,CAAC;AAAA,EAClQ;AACA,QAAM;AAAA,IACJ,gBAAgB,MAAM,KAAK,YAAY,QAAQ,CAAC,CAAC,IAAI,MAAM,YAAY,YAAY,IAAI,QAAQ,CAAC,CAAC,IAAI,MAAM,YAAY,YAAY,IAAI,QAAQ,CAAC,CAAC,IAAI,MAAM,YAAY,YAAY,IAAI,QAAQ,CAAC,CAAC,IAAI,MAAM,IAAI,WAAW,IAAI,MAAM,IAAI,WAAW;AAAA,EACrP;AACA,QAAM;AAAA,IACJ,4BAA4B,MAAM,KAAK,aAAa,QAAQ,CAAC,CAAC,IAAI,MAAM,YAAY,aAAa,IAAI,QAAQ,CAAC,CAAC,IAAI,MAAM,YAAY,aAAa,IAAI,QAAQ,CAAC,CAAC,IAAI,MAAM,YAAY,aAAa,IAAI,QAAQ,CAAC,CAAC,IAAI,MAAM,IAAI,aAAa,QAAQ,CAAC,CAAC,IAAI,MAAM,IAAI,aAAa,QAAQ,CAAC,CAAC;AAAA,EAC7R;AACA,QAAM;AAAA,IACJ,yBAAyB,MAAM,KAAK,UAAU,QAAQ,CAAC,CAAC,IAAI,MAAM,YAAY,UAAU,IAAI,QAAQ,CAAC,CAAC,IAAI,MAAM,YAAY,UAAU,IAAI,QAAQ,CAAC,CAAC,IAAI,MAAM,YAAY,UAAU,IAAI,QAAQ,CAAC,CAAC,IAAI,MAAM,IAAI,UAAU,QAAQ,CAAC,CAAC,IAAI,MAAM,IAAI,UAAU,QAAQ,CAAC,CAAC;AAAA,EACxQ;AACA,QAAM;AAAA,IACJ,YAAY,MAAM,KAAK,QAAQ,QAAQ,CAAC,CAAC,IAAI,MAAM,YAAY,QAAQ,IAAI,QAAQ,CAAC,CAAC,IAAI,MAAM,YAAY,QAAQ,IAAI,QAAQ,CAAC,CAAC,IAAI,MAAM,YAAY,QAAQ,IAAI,QAAQ,CAAC,CAAC,IAAI,MAAM,IAAI,QAAQ,QAAQ,CAAC,CAAC,IAAI,MAAM,IAAI,QAAQ,QAAQ,CAAC,CAAC;AAAA,EAC/O;AACA,QAAM,KAAK,EAAE;AAGb,QAAM,KAAK,mBAAmB;AAC9B,QAAM;AAAA,IACJ;AAAA,EACF;AACA,UAAQ,QAAQ,CAAC,GAAG,MAAM;AACxB,UAAM;AAAA,MACJ,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,QAAQ,CAAC,CAAC,IAAI,EAAE,UAAU,QAAQ,CAAC,CAAC,IAAI,EAAE,WAAW,IAAI,EAAE,aAAa,QAAQ,CAAC,CAAC,IAAI,EAAE,UAAU,QAAQ,CAAC,CAAC,IAAI,EAAE,QAAQ,QAAQ,CAAC,CAAC;AAAA,IACzJ;AAAA,EACF,CAAC;AAED,SAAO,MAAM,KAAK,IAAI;AACxB;;;ARlKA,SAAS,gBAAwB;AAC/B,MAAI;AACF,UAAM,aAAaC,SAAQC,eAAc,YAAY,GAAG,CAAC;AACzD,UAAM,cAAc,KAAK,YAAY,MAAM,cAAc;AACzD,UAAM,cAAc,KAAK,MAAMC,cAAa,aAAa,OAAO,CAAC;AACjE,WAAO,YAAY,WAAW;AAAA,EAChC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,kBAAkB,EACvB,YAAY,+CAA+C,EAC3D,QAAQ,cAAc,CAAC;AAE1B,QACG,OAAO,uBAAuB,sBAAsB,EAAE,EACtD,OAAO,6BAA6B,qCAAqC,WAAW,EACpF,OAAO,mBAAmB,yBAAyB,EACnD,OAAO,uBAAuB,YAAY,EAC1C,OAAO,yBAAyB,yBAAyB,MAAM,EAC/D,OAAO,uBAAuB,uBAAuB,GAAG,EACxD,OAAO,mBAAmB,aAAa,EACvC,OAAO,iBAAiB,6BAA6B,IAAI,EACzD,OAAO,gCAAgC,4CAA4C,MAAM,EACzF,OAAO,uBAAuB,0CAA0C,EACxE,MAAM,QAAQ,IAAI;AAErB,IAAM,UAAU,QAAQ,KAAK;AAE7B,eAAe,OAAO;AACpB,MAAI,WAAW,YAAY,YAAY;AACvC,MAAI;AAEF,UAAM,SAAS,YAAY;AAAA,MACzB,QAAQ,QAAQ;AAAA,MAChB,UAAU,QAAQ;AAAA,MAClB,KAAK,QAAQ;AAAA,MACb,OAAO,QAAQ;AAAA,MACf,WAAW,SAAS,QAAQ,WAAW,EAAE;AAAA,MACzC,MAAM,SAAS,QAAQ,MAAM,EAAE;AAAA,MAC/B,QAAQ,QAAQ;AAAA,MAChB,MAAM,QAAQ;AAAA,MACd,cAAc,QAAQ;AAAA,MACtB,YAAY,QAAQ;AAAA,IACtB,CAAC;AACD,eAAW,YAAY,OAAO,IAAI;AAGlC,YAAQ,IAAI,MAAM,KAAK;AAAA,EAAK,SAAS,QAAQ,EAAE,CAAC;AAChD,YAAQ,IAAI,MAAM,KAAK,SAAI,OAAO,EAAE,CAAC,CAAC;AACtC,YAAQ,IAAI,MAAM,KAAK,GAAG,SAAS,aAAa,QAAQ,KAAK,MAAM,MAAM,OAAO,QAAQ,CAAC,EAAE,CAAC;AAC5F,YAAQ,IAAI,MAAM,KAAK,GAAG,SAAS,aAAa,KAAK,KAAK,MAAM,MAAM,OAAO,KAAK,CAAC,EAAE,CAAC;AACtF,YAAQ,IAAI,MAAM,KAAK,GAAG,SAAS,aAAa,SAAS,KAAK,MAAM,MAAM,OAAO,SAAS,CAAC,EAAE,CAAC;AAC9F,YAAQ,IAAI,MAAM,KAAK,GAAG,SAAS,aAAa,IAAI,KAAK,MAAM,MAAM,OAAO,QAAQ,CAAC,EAAE,CAAC;AACxF,YAAQ;AAAA,MACN,MAAM;AAAA,QACJ,GAAG,SAAS,aAAa,MAAM,KAAK,MAAM,MAAM,OAAO,OAAO,UAAU,GAAG,EAAE,CAAC,CAAC,GAC7E,OAAO,OAAO,SAAS,KAAK,QAAQ,EACtC;AAAA,MACF;AAAA,IACF;AACA,YAAQ,IAAI,MAAM,KAAK,SAAI,OAAO,EAAE,CAAC,CAAC;AAGtC,YAAQ,IAAI,MAAM,OAAO;AAAA,EAAK,SAAS,YAAY;AAAA,CAAI,CAAC;AACxD,YAAQ,IAAI,MAAM,KAAK,GAAG,SAAS,eAAe;AAAA,CAAI,CAAC;AAEvD,UAAM,UAAU,MAAM,iBAAiB,MAAM;AAG7C,UAAM,aAAa,QAAQ,IAAI,CAAC,MAAM,iBAAiB,CAAC,CAAC;AAGzD,UAAM,QAAQ,eAAe,UAAU;AAGvC,QAAI,OAAO,iBAAiB,YAAY;AAEtC,eAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;AAC1C,gBAAQ,IAAI,MAAM,KAAK,mBAAmB,WAAW,CAAC,GAAG,GAAG,OAAO,IAAI,CAAC,CAAC;AAAA,MAC3E;AAGA,cAAQ,IAAI,MAAM,KAAK,OAAO,aAAa,OAAO,OAAO,IAAI,CAAC,CAAC;AAAA,IACjE,WAAW,OAAO,iBAAiB,QAAQ;AACzC,YAAM,cAAc,mBAAmB,QAAQ,YAAY,KAAK;AAChE,YAAM,YAAY,OAAO,YAAY,aAAa,OAAO;AACzD,cAAQ,IAAI,MAAM,KAAK;AAAA,gCAA8B,OAAO,UAAU;AAAA,CAAI,CAAC;AAAA,IAC7E,WAAW,OAAO,iBAAiB,OAAO;AACxC,YAAM,aAAa,kBAAkB,QAAQ,YAAY,KAAK;AAC9D,YAAM,YAAY,OAAO,YAAY,YAAY,OAAO;AACxD,cAAQ,IAAI,MAAM,KAAK;AAAA,+BAA6B,OAAO,UAAU;AAAA,CAAI,CAAC;AAAA,IAC5E,WAAW,OAAO,iBAAiB,QAAQ;AACzC,YAAM,cAAc,mBAAmB;AAAA,QACrC;AAAA,QACA,eAAe;AAAA,QACf;AAAA,QACA,MAAM,OAAO;AAAA,QACb;AAAA,MACF,CAAC;AAED,YAAM,YAAY,OAAO,YAAY,aAAa,OAAO;AACzD,cAAQ,IAAI,MAAM,KAAK;AAAA,gCAA8B,OAAO,UAAU;AAAA,CAAI,CAAC;AAG3E,YAAM,KAAK,OAAO,UAAU,EAAE,MAAM,MAAM;AACxC,gBAAQ;AAAA,UACN,MAAM,OAAO,qDAAqD,OAAO,UAAU,EAAE;AAAA,QACvF;AAAA,MACF,CAAC;AAAA,IACH;AAEA,YAAQ,IAAI,MAAM,MAAM,GAAG,SAAS,YAAY;AAAA,CAAI,CAAC;AAAA,EACvD,SAAS,OAAO;AACd,QAAI,iBAAiB,OAAO;AAC1B,cAAQ,MAAM,MAAM,IAAI;AAAA,EAAK,SAAS,WAAW,KAAK,MAAM,OAAO;AAAA,CAAI,CAAC;AAAA,IAC1E,OAAO;AACL,cAAQ,MAAM,MAAM,IAAI;AAAA,EAAK,SAAS,YAAY;AAAA,CAAI,CAAC;AAAA,IACzD;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,KAAK,KAAK;","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.8.0",
3
+ "version": "1.10.0",
4
4
  "description": "A CLI tool to test LLM API token output speed",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",