feat(pricing): 重构定价管理页面,支持卡片视图和多选筛选功能

- 更新定价模型,支持可选输入和输出字段
- 增加多选自动完成输入组件,允许按渠道、令牌和类型筛选
- 重新设计定价行的构建逻辑,优化数据处理
- 提升用户体验,支持 JSON 配置与卡片视图之间的切换
- 增强 API 端点,确保定价配置的有效性和一致性
This commit is contained in:
Jeff Gu committed 2026-04-13 17:54:30 +08:00
1 parent f226f47545
commit 1c9bd91aed
5 files changed
+983 -442

No files matched your search

+65 -15
View File
@@ -5,18 +5,72 @@ import { getJsonSetting, saveSetting } from "../utils";
import { CONSTANTS } from "../constants";
import { CommonErrorResponse, CommonSuccessfulResponse } from "../model";
const pricingModelSchema = z.object({
input: z.number().optional(),
output: z.number().optional(),
cache: z.number().optional(),
request: z.number().optional(),
});
const toPositiveNumber = (value: unknown): number | undefined => {
if (typeof value !== "number" || !Number.isFinite(value)) {
return undefined;
}
return value > 0 ? value : undefined;
};
const sanitizePricingConfig = (
config: Record<string, Partial<ModelPricing>> | null | undefined
): Record<string, Partial<ModelPricing>> => {
if (!config || typeof config !== "object") {
return {};
}
const normalizedEntries: Array<[string, Partial<ModelPricing>]> = [];
Object.entries(config).forEach(([model, pricing]) => {
const normalizedModel = model.trim();
if (!normalizedModel || !pricing || typeof pricing !== "object") {
return;
}
const normalizedPricing: Partial<ModelPricing> = {};
const input = toPositiveNumber(pricing.input);
const output = toPositiveNumber(pricing.output);
const cache = toPositiveNumber(pricing.cache);
const request = toPositiveNumber(pricing.request);
if (input !== undefined) {
normalizedPricing.input = input;
}
if (output !== undefined) {
normalizedPricing.output = output;
}
if (cache !== undefined) {
normalizedPricing.cache = cache;
}
if (request !== undefined) {
normalizedPricing.request = request;
}
if (Object.keys(normalizedPricing).length === 0) {
return;
}
normalizedEntries.push([normalizedModel, normalizedPricing]);
});
return Object.fromEntries(normalizedEntries);
};
// Pricing 获取配置 API
export class PricingGetEndpoint extends OpenAPIRoute {
schema = {
tags: ['Admin API'],
summary: 'Get global pricing configuration',
responses: {
...CommonSuccessfulResponse(z.record(z.string(), z.object({
input: z.number(),
output: z.number(),
cache: z.number().optional(),
request: z.number().optional(),
}))),
...CommonSuccessfulResponse(z.record(z.string(), pricingModelSchema)),
...CommonErrorResponse,
},
};
@@ -29,7 +83,7 @@ export class PricingGetEndpoint extends OpenAPIRoute {
return {
success: true,
data: pricingConfig || {}
data: sanitizePricingConfig(pricingConfig)
} as CommonResponse;
}
}
@@ -43,12 +97,7 @@ export class PricingUpdateEndpoint extends OpenAPIRoute {
body: {
content: {
'application/json': {
schema: z.record(z.string(), z.object({
input: z.number(),
output: z.number(),
cache: z.number().optional(),
request: z.number().optional(),
})),
schema: z.record(z.string(), pricingModelSchema),
}
}
}
@@ -60,12 +109,13 @@ export class PricingUpdateEndpoint extends OpenAPIRoute {
};
async handle(c: Context<HonoCustomType>) {
const body = await c.req.json<Record<string, ModelPricing>>();
const body = await c.req.json<Record<string, Partial<ModelPricing>>>();
const normalizedConfig = sanitizePricingConfig(body);
await saveSetting(
c,
CONSTANTS.MODEL_PRICING_KEY,
JSON.stringify(body)
JSON.stringify(normalizedConfig)
);
return {