feat(admin): 计价配置(按模型·分输入/输出单价)—— 计费比率配置落地

计费需 token↔真钱比率,配置归管理端。本次落地"按模型·分输入/输出"粒度:

后端(gateway):
- store.Pricing 模型(BaseModel + model_id 唯一 + input_per_1k/output_per_1k + currency),
  AutoMigrate 建 sundynix_pricing;ListPricing/UpsertPricing(OnConflict model_id 覆盖)。
- admin handler:GET /admin/pricing 列表、PUT /admin/pricing 设置(校验非负,币种默认 CNY),
  挂在 RequireAdmin 组下。

前端(admin):
- api:listPricing/savePricing(带 Bearer)。
- PricingPage:列出所有已登记模型(chat+embedding),每行可编辑 输入/输出每1K单价 + 币种,逐行保存。
- routes 新增「计价」页(配置组)。

实测:PUT→ok;GET 返回正确行;重复 PUT 同 model_id 仍 1 行且值更新(upsert 生效);表自动迁移。
前端 tsc 干净。下一步可做用量计量 × 单价折算(真正计费)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-06-19 11:25:24 +08:00
parent 030dcda9b4
commit 597665f3c8
8 changed files with 254 additions and 2 deletions
+8
View File
@@ -5,6 +5,7 @@ import { Soon } from "./components/Soon";
// 新增页面 = 在此加一条;real 页面用 lazy 懒加载(代码分割)。
const ModelsPage = lazy(() => import("./pages/ModelsPage").then((m) => ({ default: m.ModelsPage })));
const DatasourcesPage = lazy(() => import("./pages/DatasourcesPage").then((m) => ({ default: m.DatasourcesPage })));
const PricingPage = lazy(() => import("./pages/PricingPage").then((m) => ({ default: m.PricingPage })));
export interface RouteDef {
path: string;
@@ -29,6 +30,13 @@ export const routes: RouteDef[] = [
ready: true,
element: <DatasourcesPage />,
},
{
path: "/pricing",
label: "计价",
group: "配置",
ready: true,
element: <PricingPage />,
},
{
path: "/tenants",
label: "租户",