@pactor-app/skills 1.34.0 → 1.35.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.
Files changed (43) hide show
  1. package/package.json +1 -1
  2. package/pactor-prd/SKILL.md +18 -21
  3. package/pactor-prd/references/acceptance-checklist.md +1 -1
  4. package/pactor-prd/references/data-api.md +35 -0
  5. package/pactor-prd/references/incremental-wireframe-delta.md +8 -9
  6. package/pactor-prd/references/page-inventory-and-files.md +2 -2
  7. package/pactor-prd/references/page-templates.md +59 -12
  8. package/pactor-prd/references/project-structure.md +1 -1
  9. package/pactor-prd/template/README.md +4 -4
  10. package/pactor-prd/template/app/api/d/[...parts]/route.ts +80 -0
  11. package/pactor-prd/template/app/layout.tsx +1 -3
  12. package/pactor-prd/template/next.config.js +2 -2
  13. package/pactor-prd/template/pnpm-lock.yaml +97 -97
  14. package/pactor-prd/template/src/pactor/App.tsx +3 -2
  15. package/pactor-prd/template/src/pactor/actions.ts +5 -431
  16. package/pactor-prd/template/src/pactor/config.ts +12 -4
  17. package/pactor-prd/template/src/server/yaml/common/dashboard.yaml +1 -1
  18. package/pactor-prd/template/src/server/yaml/common/login.yaml +46 -75
  19. package/pactor-prd/template/src/server/yaml/models/data-list.yaml +4 -4
  20. package/pactor-prd/template/src/server/yaml/provider/provider-detail.yaml +2 -2
  21. package/pactor-prd/template/src/server/yaml/provider/provider-edit.yaml +1 -1
  22. package/pactor-prd/template/src/server/yaml/provider/provider-list.yaml +1 -1
  23. package/pactor-prd/template/src/server/yaml/route/route-detail.yaml +1 -1
  24. package/pactor-prd/template/src/server/yaml/route/route-edit.yaml +1 -1
  25. package/pactor-prd/template/src/server/yaml/route/route-list.yaml +1 -1
  26. package/pactor-prd/template/src/server/yaml/sitemap.yaml +2 -6
  27. package/pactor-prd/references/mock-data-routes.md +0 -66
  28. package/pactor-prd/template/app/api/data/[...parts]/route.ts +0 -160
  29. package/pactor-prd/template/app/login.css +0 -122
  30. package/pactor-prd/template/src/pactor/mocks/audit-mock.ts +0 -403
  31. package/pactor-prd/template/src/pactor/mocks/call-logs-mock.ts +0 -875
  32. package/pactor-prd/template/src/pactor/mocks/client-debug-mock.ts +0 -188
  33. package/pactor-prd/template/src/pactor/mocks/client-templates-mock.ts +0 -497
  34. package/pactor-prd/template/src/pactor/mocks/clients-mock.ts +0 -483
  35. package/pactor-prd/template/src/pactor/mocks/costs-mock.ts +0 -406
  36. package/pactor-prd/template/src/pactor/mocks/dashboard-mock.ts +0 -192
  37. package/pactor-prd/template/src/pactor/mocks/data-list-mock.ts +0 -84
  38. package/pactor-prd/template/src/pactor/mocks/model-debug-mock.ts +0 -144
  39. package/pactor-prd/template/src/pactor/mocks/models-mock.ts +0 -799
  40. package/pactor-prd/template/src/pactor/mocks/ops-usage-mock.ts +0 -806
  41. package/pactor-prd/template/src/pactor/mocks/providers-mock.ts +0 -424
  42. package/pactor-prd/template/src/pactor/mocks/routes-mock.ts +0 -604
  43. package/pactor-prd/template/src/pactor/services.ts +0 -200
@@ -1,875 +0,0 @@
1
- /**
2
- * 调用日志(运行管理)演示数据服务(MVP mock,静态页面)。
3
- * callLog.list / callLog.detail 注册为 Pactor service;接入真实后端后替换为
4
- * http / service 数据源,DSL 绑定路径保持不变。
5
- * 数据形态对齐 PRD《运行管理/01-调用日志列表与详情》:列表(状态·时间 / 用户·系统 /
6
- * 路由 / 标签 / 用量 / 成本 / 毛利)+ 详情 11 个 Tab(基本信息 / 调用主体 / Ingress /
7
- * 路由结果 / Upstream / 性能 / 用量 / 成本 / 错误 / 路由追踪 / Prompt/Response)。
8
- * 模型 / 供应商 / 客户端 / 路由命名与 models-mock / providers-mock / clients-mock /
9
- * routes-mock 保持一致。复合文案与紧凑格式(K/M、千分位)全部在 mock 层预计算
10
- * (表达式引擎不支持数组方法与函数调用)。
11
- */
12
-
13
- export type CallLogStatus =
14
- | "success"
15
- | "error"
16
- | "unfinished"
17
- | "cancelled"
18
- | "other";
19
-
20
- /** 路由追踪决策链单步:输入 / 判断 / 输出 / 耗时 / 原因 */
21
- export interface TraceStep {
22
- name: string;
23
- input: string;
24
- decision: string;
25
- output: string;
26
- duration: string;
27
- reason: string;
28
- }
29
-
30
- /** 错误详情(仅错误状态记录有值) */
31
- export interface CallLogError {
32
- code: string;
33
- type: string;
34
- /** 上游 message */
35
- message: string;
36
- }
37
-
38
- /** Prompt/Response 留存(按平台策略:不保存 / 脱敏 / 抽样 / 完整) */
39
- export interface PromptResponse {
40
- policy: string;
41
- policyNote: string;
42
- /** null = 按策略未留存 */
43
- prompt: string | null;
44
- response: string | null;
45
- }
46
-
47
- export interface CallLogRecord {
48
- id: string;
49
- status: CallLogStatus;
50
- statusLabel: string;
51
- /** 2026-09-18 14:32:18 UTC */
52
- timeUtc: string;
53
- /** 09-18 14:32:18 UTC */
54
- timeShort: string;
55
- latencyMs: number;
56
- /** 320ms */
57
- latencyText: string;
58
- userEmail: string;
59
- clientCode: string;
60
- clientName: string;
61
- appKeyId: string;
62
- channel: string;
63
- routeTag: string;
64
- application: string;
65
- /** 上下文标签摘要(紧凑展示),如 web · agent */
66
- tagsText: string;
67
- routeCode: string;
68
- routeName: string;
69
- routeGroup: string;
70
- protocol: string;
71
- modelCode: string;
72
- modelName: string;
73
- providerCode: string;
74
- providerName: string;
75
- inputTokens: number | null;
76
- outputTokens: number | null;
77
- cacheReadTokens: number;
78
- cacheWriteTokens: number;
79
- /** 用量紧凑格式,如 2.1K/1.8K;无输出为 520/— */
80
- usageText: string;
81
- inputTokensText: string;
82
- outputTokensText: string;
83
- cacheReadText: string;
84
- cacheWriteText: string;
85
- totalTokensText: string;
86
- costUsd: number | null;
87
- billingUsd: number | null;
88
- marginUsd: number | null;
89
- costText: string;
90
- billingText: string;
91
- /** 失败或未计费显示 — */
92
- marginText: string;
93
- standardPriceText: string;
94
- pricingSnapshot: string;
95
- httpStatusText: string;
96
- // ── Ingress ──
97
- ingressMethod: string;
98
- ingressPath: string;
99
- authResult: string;
100
- clientIp: string;
101
- // ── 路由结果 ──
102
- alias: string;
103
- strategyText: string;
104
- // ── Upstream / 性能 ──
105
- upstreamEndpoint: string;
106
- upstreamModel: string;
107
- upstreamStatusText: string;
108
- upstreamLatencyText: string;
109
- ttftText: string;
110
- retries: number;
111
- retriesText: string;
112
- traceId: string;
113
- // ── 错误 / 路由追踪 / Prompt ──
114
- error: CallLogError | null;
115
- trace: TraceStep[];
116
- pr: PromptResponse;
117
- }
118
-
119
- export interface CallLogListParams {
120
- /** 时间范围(ISO 日期对,含端点;缺省不过滤) */
121
- start?: string;
122
- end?: string;
123
- /** all | success | error | unfinished | cancelled | other */
124
- status?: string;
125
- model?: string;
126
- provider?: string;
127
- protocol?: string;
128
- routeGroup?: string;
129
- /** 用户 Email 模糊 */
130
- email?: string;
131
- /** AppKey ID 模糊 */
132
- appKey?: string;
133
- }
134
-
135
- export interface CallLogListResult {
136
- items: CallLogRecord[];
137
- /** 全量条数(不随筛选变化) */
138
- total: number;
139
- /** 当前筛选命中数(千分位预计算,供「总计: N 个请求」展示) */
140
- filteredText: string;
141
- }
142
-
143
- export const CALL_LOG_STATUS_LABELS: Record<CallLogStatus, string> = {
144
- success: "成功",
145
- error: "错误",
146
- unfinished: "未完成",
147
- cancelled: "已取消",
148
- other: "其他",
149
- };
150
-
151
- // ── 一致性维度元数据(名称取自 models / providers / clients / routes mock)──
152
-
153
- const CLIENT_META: Record<
154
- string,
155
- { appKey: string; channel: string; application: string; routeTag: string }
156
- > = {
157
- "agent-app": {
158
- appKey: "gw-ak-7f3a2b1c",
159
- channel: "web",
160
- application: "agent-app",
161
- routeTag: "prod",
162
- },
163
- "web-portal": {
164
- appKey: "gw-ak-9c1d4e5f",
165
- channel: "web",
166
- application: "web-portal",
167
- routeTag: "prod",
168
- },
169
- "internal-batch": {
170
- appKey: "gw-ak-2b8e6a0d",
171
- channel: "batch",
172
- application: "internal-batch",
173
- routeTag: "internal",
174
- },
175
- "legacy-mobile": {
176
- appKey: "gw-ak-5d7c9b2e",
177
- channel: "mobile",
178
- application: "legacy-mobile",
179
- routeTag: "legacy",
180
- },
181
- "expired-trial": {
182
- appKey: "gw-ak-0a4f8c6b",
183
- channel: "trial",
184
- application: "expired-trial",
185
- routeTag: "trial",
186
- },
187
- };
188
-
189
- const MODEL_META: Record<
190
- string,
191
- { name: string; provider: string; upstreamId: string }
192
- > = {
193
- "qwen3-vl-plus": {
194
- name: "Qwen3-VL Plus",
195
- provider: "dashscope",
196
- upstreamId: "qwen-vl-plus-latest",
197
- },
198
- "qwen3.5-flash": {
199
- name: "Qwen3.5 Flash",
200
- provider: "dashscope",
201
- upstreamId: "qwen3.5-flash",
202
- },
203
- "gemini-3-flash": {
204
- name: "Gemini 3 Flash",
205
- provider: "gemini",
206
- upstreamId: "gemini-3-flash",
207
- },
208
- "gpt-5": { name: "GPT-5", provider: "openai", upstreamId: "gpt-5" },
209
- "text-embedding-3-large": {
210
- name: "Text Embedding 3 Large",
211
- provider: "openai",
212
- upstreamId: "text-embedding-3-large",
213
- },
214
- };
215
-
216
- const PROVIDER_META: Record<string, { name: string; endpoint: string }> = {
217
- dashscope: {
218
- name: "阿里云百炼",
219
- endpoint: "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions",
220
- },
221
- openai: {
222
- name: "OpenAI",
223
- endpoint: "https://api.openai.com/v1/chat/completions",
224
- },
225
- gemini: {
226
- name: "Google Gemini",
227
- endpoint: "https://generativelanguage.googleapis.com/v1beta/models",
228
- },
229
- };
230
-
231
- const ROUTE_META: Record<
232
- string,
233
- {
234
- name: string;
235
- alias: string;
236
- strategyLabel: string;
237
- group: string;
238
- path: string;
239
- protocol: string;
240
- }
241
- > = {
242
- "reasoning-default": {
243
- name: "推理模型默认路由",
244
- alias: "reasoning",
245
- strategyLabel: "WEIGHTED 权重",
246
- group: "premium",
247
- path: "/v1/chat/completions",
248
- protocol: "OpenAI / Chat Completions",
249
- },
250
- "general-chat": {
251
- name: "通用对话路由",
252
- alias: "general",
253
- strategyLabel: "FIXED 固定",
254
- group: "default",
255
- path: "/v1/chat/completions",
256
- protocol: "OpenAI / Chat Completions",
257
- },
258
- "coding-assistant": {
259
- name: "代码助手路由",
260
- alias: "coding",
261
- strategyLabel: "FIXED 固定",
262
- group: "default",
263
- path: "/v1/chat/completions",
264
- protocol: "OpenAI / Chat Completions",
265
- },
266
- "mobile-lite": {
267
- name: "移动端轻量路由",
268
- alias: "chat-lite",
269
- strategyLabel: "ROUND_ROBIN 轮询",
270
- group: "edge",
271
- path: "/v1/chat/completions",
272
- protocol: "OpenAI / Chat Completions",
273
- },
274
- "embeddings-std": {
275
- name: "标准向量路由",
276
- alias: "embedding",
277
- strategyLabel: "FIXED 固定",
278
- group: "batch",
279
- path: "/v1/embeddings",
280
- protocol: "OpenAI / Embeddings",
281
- },
282
- "long-context": {
283
- name: "长上下文路由",
284
- alias: "—",
285
- strategyLabel: "PRIORITY 优先级",
286
- group: "premium",
287
- path: "/v1/chat/completions",
288
- protocol: "OpenAI / Responses",
289
- },
290
- "failover-chat": {
291
- name: "通用兜底路由",
292
- alias: "—",
293
- strategyLabel: "WEIGHTED 权重",
294
- group: "default",
295
- path: "/v1/chat/completions",
296
- protocol: "OpenAI / Chat Completions",
297
- },
298
- };
299
-
300
- // ── 预计算辅助 ──
301
-
302
- const pad2 = (n: number) => String(n).padStart(2, "0");
303
-
304
- /**
305
- * 相对当前时间的 "YYYY-MM-DD HH:mm:ss" 时间戳:mock 时间随运行日漂移,
306
- * 保证时间范围默认区间(近 7 天)始终命中数据
307
- */
308
- function timeAgo(minutes: number): string {
309
- const d = new Date(Date.now() - minutes * 60000);
310
- return (
311
- `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ` +
312
- `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`
313
- );
314
- }
315
-
316
- /** Token 紧凑格式:2100 → 2.1K;1,200,000 → 1.2M */
317
- function compactTokens(n: number): string {
318
- if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
319
- if (n >= 1000) return `${(n / 1000).toFixed(1)}K`;
320
- return String(n);
321
- }
322
-
323
- /** 千分位,如 2,100 */
324
- function thousands(n: number): string {
325
- return n.toLocaleString("en-US");
326
- }
327
-
328
- /** 决策链短路步(前序失败,后续未执行) */
329
- function skippedStep(name: string): TraceStep {
330
- return {
331
- name,
332
- input: "—",
333
- decision: "—",
334
- output: "未执行",
335
- duration: "—",
336
- reason: "前序步骤失败,决策链短路",
337
- };
338
- }
339
-
340
- interface LogSeed {
341
- id: string;
342
- status: CallLogStatus;
343
- /** 距当前时间的分钟数(deriveLog 生成时间戳,随运行日漂移) */
344
- minutesAgo: number;
345
- latencyMs: number;
346
- userEmail: string;
347
- client: keyof typeof CLIENT_META;
348
- route: keyof typeof ROUTE_META;
349
- model: keyof typeof MODEL_META;
350
- input: number | null;
351
- output: number | null;
352
- cacheRead?: number;
353
- cacheWrite?: number;
354
- /** 供应侧成本(含 C 倍率);未计费为 null */
355
- cost: number | null;
356
- /** 计费金额;未计费为 null */
357
- billing: number | null;
358
- httpStatus: string;
359
- upstreamStatus: string;
360
- ttftMs: number | null;
361
- retries?: number;
362
- clientIp: string;
363
- error?: CallLogError;
364
- /** 鉴权失败(决策链在 Request Context 短路) */
365
- authFailed?: boolean;
366
- pr?: PromptResponse;
367
- }
368
-
369
- const PR_MASKED: PromptResponse = {
370
- policy: "脱敏 + 抽样(10%)",
371
- policyNote:
372
- "平台策略:命中抽样的请求留存脱敏后的 Prompt/Response;Secret 与凭证绝不入库。",
373
- prompt:
374
- "System: 你是企业财报分析助手。\nUser: 帮我总结一下 <DOC_2026Q2.pdf> 的核心数据,联系人邮箱 <EMAIL>,电话 <PHONE>。",
375
- response:
376
- "好的。该财报核心数据摘要如下:营收 12.4 亿元(同比 +8.3%),净利润 1.9 亿元,毛利率 42.1%。\n[内容已按平台策略脱敏]",
377
- };
378
-
379
- const PR_NONE: PromptResponse = {
380
- policy: "不保存",
381
- policyNote: "平台策略:该路由不保存 Prompt/Response 内容。",
382
- prompt: null,
383
- response: null,
384
- };
385
-
386
- /** 路由追踪决策链:Request Context → Alias → Route → 路由组 → 策略 → Target → Actual Model */
387
- function buildTrace(seed: LogSeed): TraceStep[] {
388
- const route = ROUTE_META[seed.route];
389
- const model = MODEL_META[seed.model];
390
- const provider = PROVIDER_META[model.provider];
391
- const client = CLIENT_META[seed.client];
392
- if (seed.authFailed) {
393
- return [
394
- {
395
- name: "Request Context",
396
- input: `POST ${route.path} · model=${seed.model}`,
397
- decision: "提取请求上下文并鉴权",
398
- output: `AppKey ${client.appKey} 校验失败`,
399
- duration: "0.4ms",
400
- reason: seed.error?.message ?? "鉴权失败,终止路由",
401
- },
402
- ...["Alias", "Route", "路由组", "策略", "Target", "Actual Model"].map(
403
- skippedStep,
404
- ),
405
- ];
406
- }
407
- const done = seed.status === "success";
408
- return [
409
- {
410
- name: "Request Context",
411
- input: `POST ${route.path} · model=${seed.model}`,
412
- decision: "提取请求上下文(model / channel / tags)",
413
- output: `alias=${route.alias} · channel=${client.channel} · app=${client.application}`,
414
- duration: "0.2ms",
415
- reason: "—",
416
- },
417
- {
418
- name: "Alias",
419
- input: route.alias,
420
- decision: "Alias 表匹配",
421
- output:
422
- route.alias === "—"
423
- ? `未声明别名,按路径直配 ${seed.route}`
424
- : `${route.alias} → ${seed.route}`,
425
- duration: "0.1ms",
426
- reason: route.alias === "—" ? "无别名声明,走路径直配" : "命中静态别名",
427
- },
428
- {
429
- name: "Route",
430
- input: seed.route,
431
- decision: "Route 状态检查",
432
- output: `${route.name}(启用)`,
433
- duration: "0.1ms",
434
- reason: "—",
435
- },
436
- {
437
- name: "路由组",
438
- input: seed.route,
439
- decision: "路由组归属解析",
440
- output: route.group,
441
- duration: "0.1ms",
442
- reason: "—",
443
- },
444
- {
445
- name: "策略",
446
- input: "启用模型池",
447
- decision: `${route.strategyLabel}调度`,
448
- output: `${seed.model}(${model.name})`,
449
- duration: "0.3ms",
450
- reason: "健康检查通过,按策略命中",
451
- },
452
- {
453
- name: "Target",
454
- input: seed.model,
455
- decision: "供应商模型解析",
456
- output: `${provider.name} · ${model.upstreamId}`,
457
- duration: "0.2ms",
458
- reason: "应用路由行 C / M 倍率",
459
- },
460
- {
461
- name: "Actual Model",
462
- input: model.upstreamId,
463
- decision: "上游调用",
464
- output: done
465
- ? `${seed.upstreamStatus} · ${seed.latencyMs}ms`
466
- : `${seed.upstreamStatus}`,
467
- duration: done ? `${seed.latencyMs}ms` : `${seed.latencyMs}ms`,
468
- reason: done ? "—" : (seed.error?.message ?? "请求未完成"),
469
- },
470
- ];
471
- }
472
-
473
- function deriveLog(seed: LogSeed): CallLogRecord {
474
- const route = ROUTE_META[seed.route];
475
- const model = MODEL_META[seed.model];
476
- const provider = PROVIDER_META[model.provider];
477
- const client = CLIENT_META[seed.client];
478
- const input = seed.input;
479
- const output = seed.output;
480
- const total = (input ?? 0) + (output ?? 0);
481
- const margin =
482
- seed.billing != null && seed.cost != null ? seed.billing - seed.cost : null;
483
- const success = seed.status === "success";
484
- const time = timeAgo(seed.minutesAgo);
485
- return {
486
- id: seed.id,
487
- status: seed.status,
488
- statusLabel: CALL_LOG_STATUS_LABELS[seed.status],
489
- timeUtc: `${time} UTC`,
490
- timeShort: `${time.slice(5)} UTC`,
491
- latencyMs: seed.latencyMs,
492
- latencyText: `${seed.latencyMs}ms`,
493
- userEmail: seed.userEmail,
494
- clientCode: seed.client,
495
- clientName: seed.client,
496
- appKeyId: client.appKey,
497
- channel: client.channel,
498
- routeTag: client.routeTag,
499
- application: client.application,
500
- tagsText: `${client.channel} · ${client.routeTag}`,
501
- routeCode: seed.route,
502
- routeName: route.name,
503
- routeGroup: route.group,
504
- protocol: route.protocol,
505
- modelCode: seed.model,
506
- modelName: model.name,
507
- providerCode: model.provider,
508
- providerName: provider.name,
509
- inputTokens: input,
510
- outputTokens: output,
511
- cacheReadTokens: seed.cacheRead ?? 0,
512
- cacheWriteTokens: seed.cacheWrite ?? 0,
513
- usageText:
514
- input != null
515
- ? `${compactTokens(input)}/${output != null ? compactTokens(output) : "—"}`
516
- : "—",
517
- inputTokensText: input != null ? thousands(input) : "—",
518
- outputTokensText: output != null ? thousands(output) : "—",
519
- cacheReadText: thousands(seed.cacheRead ?? 0),
520
- cacheWriteText: thousands(seed.cacheWrite ?? 0),
521
- totalTokensText: input != null ? thousands(total) : "—",
522
- costUsd: seed.cost,
523
- billingUsd: seed.billing,
524
- marginUsd: margin,
525
- costText: seed.cost != null ? seed.cost.toFixed(4) : "—",
526
- billingText: seed.billing != null ? seed.billing.toFixed(4) : "—",
527
- marginText: margin != null && margin > 0 ? margin.toFixed(4) : "—",
528
- standardPriceText:
529
- seed.billing != null ? `${(seed.billing / 1.2).toFixed(4)}` : "—",
530
- pricingSnapshot:
531
- "pricing@v7(2026-09-01 生效)· C=1.0 · M=1.2 · 无时段倍率",
532
- httpStatusText: seed.httpStatus,
533
- ingressMethod: "POST",
534
- ingressPath: route.path,
535
- authResult: seed.authFailed
536
- ? `失败:AppKey ${client.appKey} 已过期`
537
- : "通过(AppKey 有效)",
538
- clientIp: seed.clientIp,
539
- alias: route.alias,
540
- strategyText: route.strategyLabel,
541
- upstreamEndpoint: provider.endpoint,
542
- upstreamModel: model.upstreamId,
543
- upstreamStatusText: seed.upstreamStatus,
544
- upstreamLatencyText: success ? `${Math.max(seed.latencyMs - 25, 10)}ms` : "—",
545
- ttftText: seed.ttftMs != null ? `${seed.ttftMs}ms` : "—",
546
- retries: seed.retries ?? 0,
547
- retriesText: String(seed.retries ?? 0),
548
- traceId: `trace-${seed.id.slice(4)}${seed.id.slice(0, 4)}`,
549
- error: seed.error ?? null,
550
- trace: buildTrace(seed),
551
- pr: seed.pr ?? PR_MASKED,
552
- };
553
- }
554
-
555
- const CALL_LOGS: CallLogRecord[] = [
556
- deriveLog({
557
- id: "req-9f2a1c4d",
558
- status: "success",
559
- minutesAgo: 20,
560
- latencyMs: 320,
561
- userEmail: "user@corp.com",
562
- client: "agent-app",
563
- route: "reasoning-default",
564
- model: "qwen3-vl-plus",
565
- input: 2100,
566
- output: 1800,
567
- cacheRead: 400,
568
- cost: 0.0126,
569
- billing: 0.0151,
570
- httpStatus: "200 OK",
571
- upstreamStatus: "200 OK",
572
- ttftMs: 96,
573
- clientIp: "10.20.3.14",
574
- }),
575
- deriveLog({
576
- id: "req-7b3e5f08",
577
- status: "success",
578
- minutesAgo: 105,
579
- latencyMs: 182,
580
- userEmail: "ops@corp.com",
581
- client: "internal-batch",
582
- route: "embeddings-std",
583
- model: "text-embedding-3-large",
584
- input: 520,
585
- output: 0,
586
- cost: 0.0001,
587
- billing: 0.0001,
588
- httpStatus: "200 OK",
589
- upstreamStatus: "200 OK",
590
- ttftMs: 60,
591
- clientIp: "10.20.8.22",
592
- }),
593
- deriveLog({
594
- id: "req-4c8d2a91",
595
- status: "error",
596
- minutesAgo: 125,
597
- latencyMs: 96,
598
- userEmail: "ops@corp.com",
599
- client: "internal-batch",
600
- route: "reasoning-default",
601
- model: "gpt-5",
602
- input: 520,
603
- output: null,
604
- cost: 0,
605
- billing: 0,
606
- httpStatus: "429 Too Many Requests",
607
- upstreamStatus: "429 Too Many Requests",
608
- ttftMs: null,
609
- retries: 2,
610
- clientIp: "10.20.8.22",
611
- error: {
612
- code: "UPSTREAM_429",
613
- type: "RateLimit",
614
- message: "上游限流:OpenAI RPM 超限,重试 2 次后仍返回 429",
615
- },
616
- }),
617
- deriveLog({
618
- id: "req-1e6f0b35",
619
- status: "success",
620
- minutesAgo: 720,
621
- latencyMs: 1240,
622
- userEmail: "dev@corp.com",
623
- client: "web-portal",
624
- route: "long-context",
625
- model: "qwen3.5-flash",
626
- input: 45200,
627
- output: 3100,
628
- cacheRead: 12800,
629
- cacheWrite: 3200,
630
- cost: 0.0904,
631
- billing: 0.1085,
632
- httpStatus: "200 OK",
633
- upstreamStatus: "200 OK",
634
- ttftMs: 410,
635
- clientIp: "10.20.1.7",
636
- }),
637
- deriveLog({
638
- id: "req-8a5c7d62",
639
- status: "unfinished",
640
- minutesAgo: 960,
641
- latencyMs: 30000,
642
- userEmail: "user@corp.com",
643
- client: "agent-app",
644
- route: "coding-assistant",
645
- model: "gpt-5",
646
- input: 3400,
647
- output: 512,
648
- cost: null,
649
- billing: null,
650
- httpStatus: "504 Gateway Timeout",
651
- upstreamStatus: "—(超时无响应)",
652
- ttftMs: null,
653
- clientIp: "10.20.3.14",
654
- }),
655
- deriveLog({
656
- id: "req-2d9b4e77",
657
- status: "success",
658
- minutesAgo: 1100,
659
- latencyMs: 256,
660
- userEmail: "mobile@corp.com",
661
- client: "legacy-mobile",
662
- route: "mobile-lite",
663
- model: "gemini-3-flash",
664
- input: 890,
665
- output: 640,
666
- cost: 0.0004,
667
- billing: 0.0005,
668
- httpStatus: "200 OK",
669
- upstreamStatus: "200 OK",
670
- ttftMs: 88,
671
- clientIp: "10.20.5.31",
672
- }),
673
- deriveLog({
674
- id: "req-6f1a8c20",
675
- status: "cancelled",
676
- minutesAgo: 1350,
677
- latencyMs: 840,
678
- userEmail: "user@corp.com",
679
- client: "agent-app",
680
- route: "general-chat",
681
- model: "qwen3.5-flash",
682
- input: 1200,
683
- output: null,
684
- cost: null,
685
- billing: null,
686
- httpStatus: "499 Client Closed Request",
687
- upstreamStatus: "—(已取消)",
688
- ttftMs: 120,
689
- clientIp: "10.20.3.14",
690
- }),
691
- deriveLog({
692
- id: "req-3e7c9a54",
693
- status: "success",
694
- minutesAgo: 2300,
695
- latencyMs: 410,
696
- userEmail: "coder@corp.com",
697
- client: "web-portal",
698
- route: "coding-assistant",
699
- model: "gpt-5",
700
- input: 5600,
701
- output: 2200,
702
- cacheRead: 800,
703
- cost: 0.0195,
704
- billing: 0.0234,
705
- httpStatus: "200 OK",
706
- upstreamStatus: "200 OK",
707
- ttftMs: 150,
708
- clientIp: "10.20.1.9",
709
- }),
710
- deriveLog({
711
- id: "req-5b0d6f18",
712
- status: "error",
713
- minutesAgo: 3000,
714
- latencyMs: 45,
715
- userEmail: "trial@corp.com",
716
- client: "expired-trial",
717
- route: "general-chat",
718
- model: "qwen3.5-flash",
719
- input: 300,
720
- output: null,
721
- cost: 0,
722
- billing: 0,
723
- httpStatus: "401 Unauthorized",
724
- upstreamStatus: "—(未发起上游调用)",
725
- ttftMs: null,
726
- clientIp: "10.20.9.40",
727
- authFailed: true,
728
- error: {
729
- code: "AUTH_EXPIRED",
730
- type: "Auth",
731
- message: "鉴权失败:AppKey gw-ak-0a4f8c6b 已于 2026-08-31 到期",
732
- },
733
- }),
734
- deriveLog({
735
- id: "req-0c4e2b89",
736
- status: "success",
737
- minutesAgo: 3900,
738
- latencyMs: 198,
739
- userEmail: "batch@corp.com",
740
- client: "internal-batch",
741
- route: "general-chat",
742
- model: "qwen3.5-flash",
743
- input: 1800,
744
- output: 950,
745
- cost: 0.0007,
746
- billing: 0.0008,
747
- httpStatus: "200 OK",
748
- upstreamStatus: "200 OK",
749
- ttftMs: 72,
750
- clientIp: "10.20.8.25",
751
- pr: PR_NONE,
752
- }),
753
- deriveLog({
754
- id: "req-a3f5d906",
755
- status: "other",
756
- minutesAgo: 5300,
757
- latencyMs: 12,
758
- userEmail: "ops@corp.com",
759
- client: "web-portal",
760
- route: "failover-chat",
761
- model: "qwen3.5-flash",
762
- input: 240,
763
- output: null,
764
- cost: null,
765
- billing: null,
766
- httpStatus: "520 Unknown",
767
- upstreamStatus: "—",
768
- ttftMs: null,
769
- clientIp: "10.20.1.12",
770
- }),
771
- deriveLog({
772
- id: "req-d8b1e473",
773
- status: "success",
774
- minutesAgo: 7200,
775
- latencyMs: 365,
776
- userEmail: "dev@corp.com",
777
- client: "agent-app",
778
- route: "reasoning-default",
779
- model: "gpt-5",
780
- input: 2900,
781
- output: 1600,
782
- cost: 0.0113,
783
- billing: 0.0136,
784
- httpStatus: "200 OK",
785
- upstreamStatus: "200 OK",
786
- ttftMs: 132,
787
- clientIp: "10.20.3.18",
788
- }),
789
- ];
790
-
791
- // ── 筛选项(从 mock 记录归集)──
792
-
793
- export const LOG_STATUS_OPTIONS: { value: string; label: string }[] = [
794
- { value: "all", label: "全部" },
795
- { value: "success", label: "成功" },
796
- { value: "error", label: "错误" },
797
- { value: "unfinished", label: "未完成" },
798
- { value: "cancelled", label: "已取消" },
799
- { value: "other", label: "其他" },
800
- ];
801
-
802
- function uniqueOptions(
803
- records: CallLogRecord[],
804
- pick: (r: CallLogRecord) => { value: string; label: string },
805
- ): { value: string; label: string }[] {
806
- const seen = new Map<string, string>();
807
- for (const r of records) {
808
- const { value, label } = pick(r);
809
- if (!seen.has(value)) seen.set(value, label);
810
- }
811
- return [
812
- { value: "all", label: "全部" },
813
- ...[...seen.entries()].map(([value, label]) => ({ value, label })),
814
- ];
815
- }
816
-
817
- export const LOG_MODEL_OPTIONS = uniqueOptions(CALL_LOGS, (r) => ({
818
- value: r.modelCode,
819
- label: r.modelName,
820
- }));
821
-
822
- export const LOG_PROVIDER_OPTIONS = uniqueOptions(CALL_LOGS, (r) => ({
823
- value: r.providerCode,
824
- label: r.providerName,
825
- }));
826
-
827
- export const LOG_PROTOCOL_OPTIONS = uniqueOptions(CALL_LOGS, (r) => ({
828
- value: r.protocol,
829
- label: r.protocol,
830
- }));
831
-
832
- export const LOG_ROUTE_GROUP_OPTIONS = uniqueOptions(CALL_LOGS, (r) => ({
833
- value: r.routeGroup,
834
- label: r.routeGroup,
835
- }));
836
-
837
- /** callLog.list:时间区间(按 UTC 日期含端点)+ 状态/模型/供应商/协议/路由组
838
- * 精确 + Email/AppKey 模糊,按时间降序 */
839
- export function queryCallLogs(
840
- params: CallLogListParams = {},
841
- ): CallLogListResult {
842
- const email = (params.email ?? "").trim().toLowerCase();
843
- const appKey = (params.appKey ?? "").trim().toLowerCase();
844
- const items = CALL_LOGS.filter(
845
- (r) =>
846
- (!params.start || r.timeUtc.slice(0, 10) >= params.start) &&
847
- (!params.end || r.timeUtc.slice(0, 10) <= params.end) &&
848
- (!params.status || params.status === "all" || r.status === params.status) &&
849
- (!params.model || params.model === "all" || r.modelCode === params.model) &&
850
- (!params.provider ||
851
- params.provider === "all" ||
852
- r.providerCode === params.provider) &&
853
- (!params.protocol ||
854
- params.protocol === "all" ||
855
- r.protocol === params.protocol) &&
856
- (!params.routeGroup ||
857
- params.routeGroup === "all" ||
858
- r.routeGroup === params.routeGroup) &&
859
- (email === "" || r.userEmail.toLowerCase().includes(email)) &&
860
- (appKey === "" || r.appKeyId.toLowerCase().includes(appKey)),
861
- ).sort((a, b) => b.timeUtc.localeCompare(a.timeUtc));
862
- return {
863
- items,
864
- total: CALL_LOGS.length,
865
- filteredText: items.length.toLocaleString("en-US"),
866
- };
867
- }
868
-
869
- /** callLog.detail:按请求 ID 取详情;未命中返回 undefined(页面做空态) */
870
- export function getCallLog(id: string): CallLogRecord | undefined {
871
- return CALL_LOGS.find((r) => r.id === id);
872
- }
873
-
874
- /** 全量请求 ID(静态导出 generateStaticParams 用) */
875
- export const CALL_LOG_IDS: string[] = CALL_LOGS.map((r) => r.id);