@pactor-app/skills 1.34.0 → 1.40.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 (49) hide show
  1. package/package.json +1 -1
  2. package/pactor-dsl/SKILL.md +28 -1
  3. package/pactor-dsl/references/dsl-authoring.md +174 -36
  4. package/pactor-prd/SKILL.md +20 -21
  5. package/pactor-prd/references/acceptance-checklist.md +1 -1
  6. package/pactor-prd/references/component-mapping.md +1 -1
  7. package/pactor-prd/references/data-api.md +37 -0
  8. package/pactor-prd/references/incremental-wireframe-delta.md +8 -9
  9. package/pactor-prd/references/page-inventory-and-files.md +2 -2
  10. package/pactor-prd/references/page-templates.md +59 -12
  11. package/pactor-prd/references/project-structure.md +1 -1
  12. package/pactor-prd/template/README.md +4 -4
  13. package/pactor-prd/template/app/api/d/[...parts]/route.ts +80 -0
  14. package/pactor-prd/template/app/layout.tsx +1 -3
  15. package/pactor-prd/template/next.config.js +2 -2
  16. package/pactor-prd/template/pnpm-lock.yaml +97 -97
  17. package/pactor-prd/template/src/pactor/App.tsx +3 -2
  18. package/pactor-prd/template/src/pactor/actions.ts +5 -431
  19. package/pactor-prd/template/src/pactor/components/index.ts +1 -4
  20. package/pactor-prd/template/src/pactor/config.ts +12 -4
  21. package/pactor-prd/template/src/server/yaml/common/dashboard.yaml +1 -1
  22. package/pactor-prd/template/src/server/yaml/common/login.yaml +46 -75
  23. package/pactor-prd/template/src/server/yaml/models/data-list.yaml +4 -4
  24. package/pactor-prd/template/src/server/yaml/provider/provider-detail.yaml +2 -2
  25. package/pactor-prd/template/src/server/yaml/provider/provider-edit.yaml +1 -1
  26. package/pactor-prd/template/src/server/yaml/provider/provider-list.yaml +1 -1
  27. package/pactor-prd/template/src/server/yaml/route/route-detail.yaml +1 -1
  28. package/pactor-prd/template/src/server/yaml/route/route-edit.yaml +1 -1
  29. package/pactor-prd/template/src/server/yaml/route/route-list.yaml +1 -1
  30. package/pactor-prd/template/src/server/yaml/sitemap.yaml +2 -6
  31. package/pactor-prd/references/mock-data-routes.md +0 -66
  32. package/pactor-prd/template/app/api/data/[...parts]/route.ts +0 -160
  33. package/pactor-prd/template/app/login.css +0 -122
  34. package/pactor-prd/template/src/pactor/components/chip-filter.tsx +0 -89
  35. package/pactor-prd/template/src/pactor/components/chip-node.tsx +0 -46
  36. package/pactor-prd/template/src/pactor/mocks/audit-mock.ts +0 -403
  37. package/pactor-prd/template/src/pactor/mocks/call-logs-mock.ts +0 -875
  38. package/pactor-prd/template/src/pactor/mocks/client-debug-mock.ts +0 -188
  39. package/pactor-prd/template/src/pactor/mocks/client-templates-mock.ts +0 -497
  40. package/pactor-prd/template/src/pactor/mocks/clients-mock.ts +0 -483
  41. package/pactor-prd/template/src/pactor/mocks/costs-mock.ts +0 -406
  42. package/pactor-prd/template/src/pactor/mocks/dashboard-mock.ts +0 -192
  43. package/pactor-prd/template/src/pactor/mocks/data-list-mock.ts +0 -84
  44. package/pactor-prd/template/src/pactor/mocks/model-debug-mock.ts +0 -144
  45. package/pactor-prd/template/src/pactor/mocks/models-mock.ts +0 -799
  46. package/pactor-prd/template/src/pactor/mocks/ops-usage-mock.ts +0 -806
  47. package/pactor-prd/template/src/pactor/mocks/providers-mock.ts +0 -424
  48. package/pactor-prd/template/src/pactor/mocks/routes-mock.ts +0 -604
  49. package/pactor-prd/template/src/pactor/services.ts +0 -200
@@ -1,406 +0,0 @@
1
- /**
2
- * 成本明细(运行管理)演示数据服务(MVP mock,静态页面)。
3
- * cost.list / cost.detail 注册为 Pactor service;接入真实后端后替换为
4
- * http / service 数据源,DSL 绑定路径保持不变。
5
- * 数据形态对齐 PRD《运行管理/06-成本明细》:按单次请求展示实际成本与
6
- * pricing_snapshot(命中档位 + 时段倍率);计费公式
7
- * actual_price = tier_price × multiplier
8
- * Cost = Σ(usage × actual_price / 1M)
9
- * 金额、档位文案、计算过程说明一律在此预计算为字符串(表达式引擎不支持
10
- * 数组方法 / 函数调用)。模型 / 供应商 / 客户端 / AppKey / Alias 名称与
11
- * models-mock / providers-mock / clients-mock / routes-mock 保持一致。
12
- */
13
-
14
- export interface CostRecord {
15
- /** 与调用日志成功(已计费)记录的 request id 一致,如 req-9f2a1c4d */
16
- id: string;
17
- /** 距当前时间的分钟数(time/shortTime 由此派生,随运行日漂移,
18
- * 保证默认时间范围(近 7 天)始终命中数据) */
19
- minutesAgo: number;
20
- /** 完整时间,如 2026-09-19 08:05:12(由 minutesAgo 派生) */
21
- time: string;
22
- /** 列表短时间(MM-DD HH:mm,由 minutesAgo 派生) */
23
- shortTime: string;
24
- application: string;
25
- clientName: string;
26
- appKeyId: string;
27
- alias: string;
28
- modelCode: string;
29
- modelName: string;
30
- providerCode: string;
31
- providerName: string;
32
- currency: string;
33
- // ── 用量 ──
34
- inputTokens: number;
35
- outputTokens: number;
36
- /** 0 表示无缓存用量(列表 Cache 列展示 —) */
37
- cacheReadTokens: number;
38
- // ── 定价快照(pricing_snapshot,只读) ──
39
- priceVersion: string;
40
- priceEffective: string;
41
- /** 命中档位 UPTO 上限;null = 无上限档(∞) */
42
- tierUpto: number | null;
43
- /** 档位单价($/1M tokens) */
44
- tierInputPrice: number;
45
- tierOutputPrice: number;
46
- tierCacheReadPrice: number | null;
47
- /** 命中的时段倍率(未命中为 1.0) */
48
- multiplier: number;
49
- /** 时段倍率规则文案,如「每天 00:00–08:00 ×0.8」;未命中为说明文字 */
50
- multiplierRule: string;
51
- // ── 预计算展示字段 ──
52
- /** 档位短标签,如 ≤32K / ≤128K / ∞ */
53
- tierLabel: string;
54
- /** 倍率短标签,如 0.8x */
55
- multiplierText: string;
56
- inputCost: string;
57
- outputCost: string;
58
- /** 无缓存用量时为 — */
59
- cacheCost: string;
60
- totalCost: string;
61
- /** Header 摘要:时间 · 客户端(AppKey)· 模型 · 供应商 */
62
- contextSummary: string;
63
- /** 档位完整文案,如 UPTO ≤ 32,000 / UPTO ∞(无上限档) */
64
- tierDetail: string;
65
- /** 档位单价摘要:输入 $0.2/M · 输出 $1.6/M · 缓存读 — */
66
- tierPriceText: string;
67
- inputTokensText: string;
68
- outputTokensText: string;
69
- cacheReadTokensText: string;
70
- /** 费用计算过程,如 2,400 × $0.2 × 0.8 ÷ 1M = $0.0004 */
71
- inputCalcText: string;
72
- outputCalcText: string;
73
- cacheCalcText: string;
74
- }
75
-
76
- export interface CostListParams {
77
- /** Application 模糊 */
78
- application?: string;
79
- /** AppKey ID 模糊 */
80
- appKey?: string;
81
- /** Alias 模糊 */
82
- alias?: string;
83
- /** all | Model Code */
84
- model?: string;
85
- /** all | Provider Code */
86
- provider?: string;
87
- /** 时间范围(ISO 日期对,含端点;缺省不过滤) */
88
- start?: string;
89
- end?: string;
90
- }
91
-
92
- export interface CostListResult {
93
- items: CostRecord[];
94
- total: number;
95
- }
96
-
97
- const pad2 = (n: number) => String(n).padStart(2, "0");
98
-
99
- /** 相对当前时间的 "YYYY-MM-DD HH:mm:ss" 时间戳(mock 时间随运行日漂移) */
100
- function timeAgo(minutes: number): string {
101
- const d = new Date(Date.now() - minutes * 60000);
102
- return (
103
- `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ` +
104
- `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`
105
- );
106
- }
107
-
108
- /** Token 档位的短写法:32000 → ≤32K,128000 → ≤128K,null → ∞ */
109
- function tierShortLabel(upto: number | null): string {
110
- if (upto == null) return "∞";
111
- if (upto >= 1000 && upto % 1000 === 0) return `≤${upto / 1000}K`;
112
- return `≤${upto.toLocaleString("en-US")}`;
113
- }
114
-
115
- function money(v: number): string {
116
- return `$${v.toFixed(4)}`;
117
- }
118
-
119
- function num(n: number): string {
120
- return n.toLocaleString("en-US");
121
- }
122
-
123
- /** 单价展示:$0.2(/M tokens 单价,保留原始精度) */
124
- function price(p: number): string {
125
- return `$${p}`;
126
- }
127
-
128
- /** 列表/详情展示字段派生(费用按 PRD 公式计算并格式化为字符串) */
129
- function derive(
130
- record: Omit<
131
- CostRecord,
132
- | "time"
133
- | "shortTime"
134
- | "tierLabel"
135
- | "multiplierText"
136
- | "inputCost"
137
- | "outputCost"
138
- | "cacheCost"
139
- | "totalCost"
140
- | "contextSummary"
141
- | "tierDetail"
142
- | "tierPriceText"
143
- | "inputTokensText"
144
- | "outputTokensText"
145
- | "cacheReadTokensText"
146
- | "inputCalcText"
147
- | "outputCalcText"
148
- | "cacheCalcText"
149
- >,
150
- ): CostRecord {
151
- // actual_price = tier_price × multiplier;Cost = usage × actual_price / 1M
152
- const inputCost =
153
- (record.inputTokens * record.tierInputPrice * record.multiplier) / 1e6;
154
- const outputCost =
155
- (record.outputTokens * record.tierOutputPrice * record.multiplier) / 1e6;
156
- const cacheCost =
157
- record.cacheReadTokens > 0 && record.tierCacheReadPrice != null
158
- ? (record.cacheReadTokens * record.tierCacheReadPrice * record.multiplier) /
159
- 1e6
160
- : 0;
161
- const hasCache = record.cacheReadTokens > 0 && record.tierCacheReadPrice != null;
162
- const calc = (tokens: number, unitPrice: number, cost: number) =>
163
- `${num(tokens)} × ${price(unitPrice)} × ${record.multiplier} ÷ 1M = ${money(cost)}`;
164
- const time = timeAgo(record.minutesAgo);
165
- return {
166
- ...record,
167
- time,
168
- shortTime: time.slice(5, 16),
169
- tierLabel: tierShortLabel(record.tierUpto),
170
- multiplierText: `${record.multiplier}x`,
171
- inputCost: money(inputCost),
172
- outputCost: money(outputCost),
173
- cacheCost: hasCache ? money(cacheCost) : "—",
174
- totalCost: money(inputCost + outputCost + cacheCost),
175
- contextSummary: `${time} · ${record.clientName}(${record.appKeyId})· ${record.modelName} · ${record.providerName}`,
176
- tierDetail:
177
- record.tierUpto == null
178
- ? "UPTO ∞(无上限档)"
179
- : `UPTO ≤ ${num(record.tierUpto)}`,
180
- tierPriceText: `输入 ${price(record.tierInputPrice)}/M · 输出 ${price(record.tierOutputPrice)}/M · 缓存读 ${record.tierCacheReadPrice != null ? `${price(record.tierCacheReadPrice)}/M` : "—"}`,
181
- inputTokensText: num(record.inputTokens),
182
- outputTokensText: num(record.outputTokens),
183
- cacheReadTokensText: hasCache ? num(record.cacheReadTokens) : "—",
184
- inputCalcText: calc(record.inputTokens, record.tierInputPrice, inputCost),
185
- outputCalcText: calc(record.outputTokens, record.tierOutputPrice, outputCost),
186
- cacheCalcText: hasCache
187
- ? calc(record.cacheReadTokens, record.tierCacheReadPrice!, cacheCost)
188
- : "—(无缓存用量)",
189
- };
190
- }
191
-
192
- const COSTS: CostRecord[] = [
193
- derive({
194
- id: "req-9f2a1c4d",
195
- minutesAgo: 25,
196
- application: "agent-platform",
197
- clientName: "agent-app",
198
- appKeyId: "gw-ak-7f3a2b1c",
199
- alias: "reasoning",
200
- modelCode: "qwen3-vl-plus",
201
- modelName: "Qwen3-VL Plus",
202
- providerCode: "dashscope",
203
- providerName: "阿里云百炼",
204
- currency: "USD",
205
- inputTokens: 2400,
206
- outputTokens: 1800,
207
- cacheReadTokens: 0,
208
- priceVersion: "v3",
209
- priceEffective: "2026-09-01",
210
- tierUpto: 32000,
211
- tierInputPrice: 0.2,
212
- tierOutputPrice: 1.6,
213
- tierCacheReadPrice: null,
214
- multiplier: 0.8,
215
- multiplierRule: "每天 00:00–08:00 ×0.8",
216
- }),
217
- derive({
218
- id: "req-7b3e5f08",
219
- minutesAgo: 180,
220
- application: "web-portal",
221
- clientName: "web-portal",
222
- appKeyId: "gw-ak-9c1d4e5f",
223
- alias: "general",
224
- modelCode: "qwen3.5-flash",
225
- modelName: "Qwen3.5 Flash",
226
- providerCode: "dashscope",
227
- providerName: "阿里云百炼",
228
- currency: "USD",
229
- inputTokens: 1200,
230
- outputTokens: 640,
231
- cacheReadTokens: 0,
232
- priceVersion: "v2",
233
- priceEffective: "2026-08-15",
234
- tierUpto: 128000,
235
- tierInputPrice: 0.1,
236
- tierOutputPrice: 0.4,
237
- tierCacheReadPrice: null,
238
- multiplier: 1.0,
239
- multiplierRule: "—(未命中时段倍率,按 1.0 计)",
240
- }),
241
- derive({
242
- id: "req-1e6f0b35",
243
- minutesAgo: 900,
244
- application: "batch-jobs",
245
- clientName: "internal-batch",
246
- appKeyId: "gw-ak-2b8e6a0d",
247
- alias: "embedding",
248
- modelCode: "text-embedding-3-large",
249
- modelName: "Text Embedding 3 Large",
250
- providerCode: "openai",
251
- providerName: "OpenAI",
252
- currency: "USD",
253
- inputTokens: 56000,
254
- outputTokens: 0,
255
- cacheReadTokens: 0,
256
- priceVersion: "v1",
257
- priceEffective: "2026-07-01",
258
- tierUpto: null,
259
- tierInputPrice: 0.1,
260
- tierOutputPrice: 0,
261
- tierCacheReadPrice: null,
262
- multiplier: 1.0,
263
- multiplierRule: "—(未命中时段倍率,按 1.0 计)",
264
- }),
265
- derive({
266
- id: "req-2d9b4e77",
267
- minutesAgo: 3000,
268
- application: "agent-platform",
269
- clientName: "agent-app",
270
- appKeyId: "gw-ak-7f3a2b1c",
271
- alias: "coding",
272
- modelCode: "gpt-5",
273
- modelName: "GPT-5",
274
- providerCode: "openai",
275
- providerName: "OpenAI",
276
- currency: "USD",
277
- inputTokens: 3200,
278
- outputTokens: 900,
279
- cacheReadTokens: 12000,
280
- priceVersion: "v1",
281
- priceEffective: "2026-09-10",
282
- tierUpto: null,
283
- tierInputPrice: 0.4,
284
- tierOutputPrice: 1.8,
285
- tierCacheReadPrice: 0.1,
286
- multiplier: 1.0,
287
- multiplierRule: "—(未命中时段倍率,按 1.0 计)",
288
- }),
289
- derive({
290
- id: "req-3e7c9a54",
291
- minutesAgo: 5000,
292
- application: "web-portal",
293
- clientName: "web-portal",
294
- appKeyId: "gw-ak-9c1d4e5f",
295
- alias: "general",
296
- modelCode: "gemini-3-flash",
297
- modelName: "Gemini 3 Flash",
298
- providerCode: "gemini",
299
- providerName: "Google Gemini",
300
- currency: "USD",
301
- inputTokens: 8000,
302
- outputTokens: 2200,
303
- cacheReadTokens: 0,
304
- priceVersion: "v1",
305
- priceEffective: "2026-08-30",
306
- tierUpto: 200000,
307
- tierInputPrice: 0.15,
308
- tierOutputPrice: 0.6,
309
- tierCacheReadPrice: null,
310
- multiplier: 1.0,
311
- multiplierRule: "—(未命中时段倍率,按 1.0 计)",
312
- }),
313
- derive({
314
- id: "req-0c4e2b89",
315
- minutesAgo: 8200,
316
- application: "mobile-app",
317
- clientName: "legacy-mobile",
318
- appKeyId: "gw-ak-5d7c9b2e",
319
- alias: "chat-lite",
320
- modelCode: "qwen3.5-flash",
321
- modelName: "Qwen3.5 Flash",
322
- providerCode: "dashscope",
323
- providerName: "阿里云百炼",
324
- currency: "USD",
325
- inputTokens: 40000,
326
- outputTokens: 3000,
327
- cacheReadTokens: 0,
328
- priceVersion: "v2",
329
- priceEffective: "2026-08-15",
330
- tierUpto: 128000,
331
- tierInputPrice: 0.1,
332
- tierOutputPrice: 0.4,
333
- tierCacheReadPrice: null,
334
- multiplier: 0.9,
335
- multiplierRule: "每天 00:00–08:00 ×0.9",
336
- }),
337
- derive({
338
- id: "req-d8b1e473",
339
- minutesAgo: 13000,
340
- application: "agent-platform",
341
- clientName: "agent-app",
342
- appKeyId: "gw-ak-7f3a2b1c",
343
- alias: "reasoning",
344
- modelCode: "qwen3-vl-plus",
345
- modelName: "Qwen3-VL Plus",
346
- providerCode: "dashscope",
347
- providerName: "阿里云百炼",
348
- currency: "USD",
349
- inputTokens: 45000,
350
- outputTokens: 6000,
351
- cacheReadTokens: 0,
352
- priceVersion: "v3",
353
- priceEffective: "2026-09-01",
354
- tierUpto: 128000,
355
- tierInputPrice: 0.4,
356
- tierOutputPrice: 3.2,
357
- tierCacheReadPrice: null,
358
- multiplier: 1.2,
359
- multiplierRule: "工作日 09:00–18:00 ×1.2",
360
- }),
361
- ];
362
-
363
- /** Model 筛选选项:全部 + 数据中出现过的模型(按名称去重) */
364
- export const COST_MODEL_OPTIONS: { value: string; label: string }[] = [
365
- { value: "all", label: "全部模型" },
366
- ...[...new Map(COSTS.map((c) => [c.modelCode, c.modelName])).entries()].map(
367
- ([value, label]) => ({ value, label }),
368
- ),
369
- ];
370
-
371
- /** Provider 筛选选项:全部 + 数据中出现过的供应商 */
372
- export const COST_PROVIDER_OPTIONS: { value: string; label: string }[] = [
373
- { value: "all", label: "全部供应商" },
374
- ...[
375
- ...new Map(COSTS.map((c) => [c.providerCode, c.providerName])).entries(),
376
- ].map(([value, label]) => ({ value, label })),
377
- ];
378
-
379
- /** cost.list:时间区间(按日期含端点)+ Application/AppKey/Alias 模糊 +
380
- * Model/Provider 单选,按时间倒序 */
381
- export function queryCosts(params: CostListParams = {}): CostListResult {
382
- const application = (params.application ?? "").trim().toLowerCase();
383
- const appKey = (params.appKey ?? "").trim().toLowerCase();
384
- const alias = (params.alias ?? "").trim().toLowerCase();
385
- const model = params.model ?? "all";
386
- const provider = params.provider ?? "all";
387
- const items = COSTS.filter(
388
- (c) =>
389
- (!params.start || c.time.slice(0, 10) >= params.start) &&
390
- (!params.end || c.time.slice(0, 10) <= params.end) &&
391
- (application === "" || c.application.toLowerCase().includes(application)) &&
392
- (appKey === "" || c.appKeyId.toLowerCase().includes(appKey)) &&
393
- (alias === "" || c.alias.toLowerCase().includes(alias)) &&
394
- (model === "all" || c.modelCode === model) &&
395
- (provider === "all" || c.providerCode === provider),
396
- ).sort((a, b) => b.time.localeCompare(a.time));
397
- return { items, total: COSTS.length };
398
- }
399
-
400
- /** cost.detail:按请求 id 取详情;未命中返回 undefined(页面做空态) */
401
- export function getCost(id: string): CostRecord | undefined {
402
- return COSTS.find((c) => c.id === id);
403
- }
404
-
405
- /** 全量请求 id(静态导出 generateStaticParams 用) */
406
- export const COST_IDS: string[] = COSTS.map((c) => c.id);
@@ -1,192 +0,0 @@
1
- /**
2
- * Dashboard 演示数据服务(MVP mock)。
3
- * queryDashboard(start, end) 按任意日期区间确定性生成看板数据,
4
- * 注册为 Pactor service(dashboard.query);接入真实后端后替换为
5
- * http / service 数据源,DSL 绑定路径保持不变。
6
- *
7
- * 时间桶规则(对齐 PRD §2):≤2 天 → 2h 桶;3 天 → 6h 桶;
8
- * ≤16 天 → 天桶;更长 → 约 15 个多天桶。
9
- */
10
-
11
- export interface TrendPoint {
12
- t: string;
13
- requests: number;
14
- tokens: number;
15
- cost: number;
16
- latency: number;
17
- }
18
-
19
- export interface DistributionItem {
20
- name: string;
21
- percent: number;
22
- }
23
-
24
- export interface DashboardStats {
25
- requests: string;
26
- activeClients: number;
27
- tokens: string;
28
- successRate: string;
29
- failures: string;
30
- errorRate: string;
31
- avgLatency: number;
32
- cost: string;
33
- avgCost: string;
34
- }
35
-
36
- export interface DashboardData {
37
- realtime: { rpm: number; tpm: string };
38
- /** Chart yKeys 需要数组,而表达式不支持数组字面量 → 预置映射按指标取 */
39
- metricKeys: Record<string, string[]>;
40
- stats: DashboardStats;
41
- trend: TrendPoint[];
42
- models: DistributionItem[];
43
- clients: DistributionItem[];
44
- }
45
-
46
- const DAY_MS = 86400000;
47
- const pad = (n: number) => String(n).padStart(2, "0");
48
-
49
- /** 本地日期 → ISO yyyy-MM-dd(与 DatePicker 的 format 约定一致) */
50
- export function isoDate(d: Date): string {
51
- return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
52
- }
53
-
54
- /** 默认分析范围:最近 7 天(含今天) */
55
- export function defaultRange(): { start: string; end: string } {
56
- const end = new Date();
57
- const start = new Date(end.getTime() - 6 * DAY_MS);
58
- return { start: isoDate(start), end: isoDate(end) };
59
- }
60
-
61
- function monthDay(d: Date): string {
62
- return `${pad(d.getMonth() + 1)}/${pad(d.getDate())}`;
63
- }
64
-
65
- /** 确定性伪随机(同一区间多次调用结果一致) */
66
- function seededRandom(seedText: string): () => number {
67
- let a = 0;
68
- for (const ch of seedText) a = (a * 31 + ch.charCodeAt(0)) | 0;
69
- return () => {
70
- a |= 0;
71
- a = (a + 0x6d2b79f5) | 0;
72
- let t = Math.imul(a ^ (a >>> 15), 1 | a);
73
- t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
74
- return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
75
- };
76
- }
77
-
78
- function fmt(n: number): string {
79
- return n.toLocaleString("en-US");
80
- }
81
-
82
- const MODEL_SHARES: Array<[string, number]> = [
83
- ["gpt-4o", 38],
84
- ["reasoning-v2", 28],
85
- ["claude-3.5", 18],
86
- ];
87
-
88
- const CLIENT_SHARES: Array<[string, number]> = [
89
- ["app-mobile", 42],
90
- ["app-web", 31],
91
- ["internal-api", 16],
92
- ];
93
-
94
- function buildDistribution(
95
- base: Array<[string, number]>,
96
- rand: () => number,
97
- ): DistributionItem[] {
98
- const items = base.map(([name, share]) => ({
99
- name,
100
- percent: Math.max(1, Math.round(share * (0.85 + rand() * 0.3))),
101
- }));
102
- items.sort((a, b) => b.percent - a.percent);
103
- const top = items.reduce((sum, it) => sum + it.percent, 0);
104
- items.push({ name: "其他", percent: Math.max(0, 100 - top) });
105
- return items;
106
- }
107
-
108
- /** 时间桶标签序列:按区间长度选择粒度(PRD:1~3 天小时桶,7~14 天 6h/天,30 天/周/月 天) */
109
- function bucketLabels(start: Date, days: number): string[] {
110
- if (days <= 2) {
111
- return Array.from({ length: 12 }, (_, i) => `${pad(i * 2)}:00`);
112
- }
113
- if (days === 3) {
114
- return Array.from({ length: 12 }, (_, i) => {
115
- const d = new Date(start.getTime() + Math.floor(i / 4) * DAY_MS);
116
- return `${monthDay(d)} ${(i % 4) * 6}:00`;
117
- });
118
- }
119
- if (days <= 16) {
120
- return Array.from({ length: days }, (_, i) =>
121
- monthDay(new Date(start.getTime() + i * DAY_MS)),
122
- );
123
- }
124
- const step = Math.ceil(days / 15);
125
- const labels: string[] = [];
126
- for (let i = 0; i < days; i += step) {
127
- labels.push(monthDay(new Date(start.getTime() + i * DAY_MS)));
128
- }
129
- return labels;
130
- }
131
-
132
- export function queryDashboard(start: string, end: string): DashboardData {
133
- const rand = seededRandom(`${start}|${end}`);
134
- const startDate = new Date(`${start}T00:00:00`);
135
- const endDate = new Date(`${end}T00:00:00`);
136
- const valid =
137
- !Number.isNaN(startDate.getTime()) && !Number.isNaN(endDate.getTime());
138
- const days = valid
139
- ? Math.min(
140
- 62,
141
- Math.max(1, Math.round((endDate.getTime() - startDate.getTime()) / DAY_MS) + 1),
142
- )
143
- : 7;
144
- const base = valid ? startDate : new Date(Date.now() - 6 * DAY_MS);
145
-
146
- const labels = bucketLabels(base, days);
147
- const bucketBase = (days * 18000) / labels.length;
148
-
149
- const trend: TrendPoint[] = labels.map((t) => {
150
- const requests = Math.round(bucketBase * (0.6 + rand() * 0.8));
151
- return {
152
- t,
153
- requests,
154
- tokens: Number(((requests * (320 + rand() * 60)) / 1_000_000).toFixed(2)),
155
- cost: Number((requests * 0.0069 * (0.9 + rand() * 0.2)).toFixed(2)),
156
- latency: Math.round(280 + rand() * 140),
157
- };
158
- });
159
-
160
- const totalRequests = trend.reduce((s, p) => s + p.requests, 0);
161
- const totalTokens = trend.reduce((s, p) => s + p.tokens, 0);
162
- const totalCost = trend.reduce((s, p) => s + p.cost, 0);
163
- const avgLatency = Math.round(
164
- trend.reduce((s, p) => s + p.latency, 0) / trend.length,
165
- );
166
- const successRate = 97 + rand() * 2.8;
167
- const failures = Math.round(totalRequests * (1 - successRate / 100));
168
-
169
- return {
170
- realtime: { rpm: 128, tpm: "2.1K" },
171
- metricKeys: {
172
- requests: ["requests"],
173
- tokens: ["tokens"],
174
- cost: ["cost"],
175
- latency: ["latency"],
176
- },
177
- stats: {
178
- requests: fmt(totalRequests),
179
- activeClients: 8 + Math.floor(rand() * 10),
180
- tokens: `${totalTokens.toFixed(1)}M`,
181
- successRate: successRate.toFixed(1),
182
- failures: fmt(failures),
183
- errorRate: (100 - successRate).toFixed(2),
184
- avgLatency,
185
- cost: `$${totalCost.toFixed(4)}`,
186
- avgCost: `$${(totalCost / totalRequests).toFixed(4)}`,
187
- },
188
- trend,
189
- models: buildDistribution(MODEL_SHARES, rand),
190
- clients: buildDistribution(CLIENT_SHARES, rand),
191
- };
192
- }
@@ -1,84 +0,0 @@
1
- /**
2
- * 数据列表骨架示例 mock(对应 yaml/models/data-list.yaml)。
3
- * query / get / save 注册为 dataList.* 服务;生成业务列表页时复制本文件
4
- * 改名并替换字段;接入真实后端后删除。
5
- */
6
-
7
- export interface DataListItem {
8
- id: string;
9
- name: string;
10
- code: string;
11
- /** 基础 | 高级(高级显示扩展说明字段,演示条件显隐) */
12
- type: string;
13
- extra: string;
14
- sort: number;
15
- /** 启用 | 禁用 */
16
- status: string;
17
- }
18
-
19
- const ITEMS: DataListItem[] = [
20
- { id: "1", name: "示例记录甲", code: "DEMO_ALPHA", type: "基础", extra: "", sort: 1, status: "启用" },
21
- { id: "2", name: "示例记录乙", code: "DEMO_BETA", type: "高级", extra: "高级类型的扩展配置", sort: 2, status: "启用" },
22
- { id: "3", name: "示例记录丙", code: "DEMO_GAMMA", type: "基础", extra: "", sort: 3, status: "禁用" },
23
- { id: "4", name: "示例记录丁", code: "DEMO_DELTA", type: "高级", extra: "", sort: 4, status: "启用" },
24
- ];
25
-
26
- let seq = ITEMS.length;
27
-
28
- export interface DataListQuery {
29
- keyword?: string;
30
- status?: string;
31
- }
32
-
33
- /** 列表查询:items + total + 筛选选项(filters 由接口下发,搜索栏/表单的 Select 直接消费) */
34
- export function queryDataList(params: DataListQuery) {
35
- const keyword = (params.keyword ?? "").trim().toLowerCase();
36
- const status = params.status ?? "all";
37
- const items = ITEMS.filter(
38
- (it) =>
39
- (status === "all" || it.status === status) &&
40
- (keyword === "" ||
41
- it.name.toLowerCase().includes(keyword) ||
42
- it.code.toLowerCase().includes(keyword)),
43
- );
44
- return {
45
- items,
46
- total: items.length,
47
- filters: {
48
- status: [
49
- { label: "全部", value: "all" },
50
- { label: "启用", value: "启用" },
51
- { label: "禁用", value: "禁用" },
52
- ],
53
- type: [
54
- { label: "基础", value: "基础" },
55
- { label: "高级", value: "高级" },
56
- ],
57
- },
58
- };
59
- }
60
-
61
- /** 编辑预填;空 id(新建)返回空对象,Form initialValues 各字段即空 */
62
- export function getDataListItem(id: string): Partial<DataListItem> {
63
- return ITEMS.find((it) => it.id === id) ?? {};
64
- }
65
-
66
- /** 新建 / 编辑保存(upsert,按 id 区分) */
67
- export function saveDataListItem(body: Partial<DataListItem>) {
68
- if (body.id) {
69
- const idx = ITEMS.findIndex((it) => it.id === body.id);
70
- if (idx >= 0) ITEMS[idx] = { ...ITEMS[idx], ...body, id: body.id } as DataListItem;
71
- return { ok: true, id: body.id };
72
- }
73
- const id = String(++seq);
74
- ITEMS.push({
75
- id,
76
- name: body.name ?? "",
77
- code: body.code ?? "",
78
- type: body.type ?? "基础",
79
- extra: body.extra ?? "",
80
- sort: body.sort ?? 0,
81
- status: body.status ?? "启用",
82
- });
83
- return { ok: true, id };
84
- }