import {get} from '../request'; // 注意:后端 /api/v1/models 的 category 查询参数实际不可用(传参返回空列表), // 因此客户端采用"全量拉取 + 按 category_name 字段分组"的策略。 // 后端分类 slug 格式为 img2img / txt2img 等,前端通过 CATEGORY_SLUG_MAP 映射。 // ---------- 路由常量 ---------- const ModelsUrlObj = { modelList: '/api/v1/models', getModelInfo: '/api/v1/models/{model_id}', estimateCost: '/api/v1/models/{model_id}/estimate' } as const; // ---------- 类型 ---------- /** 模型类别常量数组(单一数据源,类型由此推导) */ export const MODEL_CATEGORIES = [ 'image_to_image', 'text_to_image', 'image_to_video', 'text_to_audio', ] as const; export type ModelCategoryStringEnum = typeof MODEL_CATEGORIES[number]; /** * 模型参数 Schema 项 * * 后端 /api/v1/models 返回的 param_schema 为对象数组, * 每项描述一个模型入参的元信息(标识、字段映射、规格约束、UI 配置)。 */ export interface ParamSchemaItem { id: string; /** 映射到提交参数中的字段名 */ map_to: string; /** 规格约束(类型、范围、默认值等) */ spec: Record; /** UI 控件配置 */ ui: Record; } /** 模型类别中文映射 */ export const MODEL_CATEGORY_LABELS: Record = { image_to_image: '图生图', text_to_image: '文生图', image_to_video: '图生视频', text_to_audio: '文生音频', }; /** * 前端 ModelCategory → 后端分类 slug 映射 * * 后端分类 API 使用简写 slug(如 img2img),而非前端的长标识符。 * 查询模型列表时需将前端类别转为后端 slug 作为 category 参数。 */ export const CATEGORY_SLUG_MAP: Record = { image_to_image: 'img2img', text_to_image: 'txt2img', image_to_video: 'img2video', text_to_audio: 'txt2audio', }; /** 后端 slug → 前端 ModelCategory 反向映射(由 CATEGORY_SLUG_MAP 自动推导) */ const SLUG_TO_CATEGORY: Record = Object.fromEntries( Object.entries(CATEGORY_SLUG_MAP).map(([cat, slug]) => [slug, cat as ModelCategoryStringEnum]), ) as Record; /** * 根据任意 category_name 值解析出最佳中文标签 * * 解析优先级: * 1. 匹配 MODEL_CATEGORY_LABELS(前端 ModelCategory 字面量) * 2. 匹配 SLUG_TO_CATEGORY 反向映射(后端 slug → 中英文标签) * 3. 兜底返回原始字符串 */ export function resolveCategoryLabel(categoryName: string): string { // 优先按 ModelCategory 字面量查找 if (categoryName in MODEL_CATEGORY_LABELS) { return MODEL_CATEGORY_LABELS[categoryName as ModelCategoryStringEnum]; } // 按后端 slug 反向查找 const cat = SLUG_TO_CATEGORY[categoryName]; if (cat) { return MODEL_CATEGORY_LABELS[cat]; } // 兜底返回原始值 return categoryName; } /** * 将前端 ModelCategory 转为后端 API 用的分类 slug */ export function toCategorySlug(category: ModelCategoryStringEnum): string { return CATEGORY_SLUG_MAP[category]; } /** * 查询可用模型的请求参数 */ export interface ModelsListReqeustParams { provider_name?: string; category?: ModelCategoryStringEnum; page: number; page_size: number; } /** * 查询可用模型的响应体 */ export interface ModelsListResponseBody { id: string; name: string; provider_name: string; model_type: ModelCategoryStringEnum; category_name: ModelCategoryStringEnum | null; status: string; priority: number; response_mode: string; pricing_config: Record; param_schema: ParamSchemaItem[]; quota_config: Record | null; ui_config: Record; meta_config: Record; constraints_config: Record; expires_at: string; created_at: string; updated_at: string; } /** * 模型信息请求参数 */ export interface ModelInfoRequestParams { model_id: string; } /** 模型详情响应体(与列表项结构一致) */ export type ModelInfoResponseBody = ModelsListResponseBody; // ---------- API ---------- export class ModelAPI { /** * 获取可用模型列表 * GET /api/v1/models */ static fetchModels(params: ModelsListReqeustParams): Promise { return get(ModelsUrlObj.modelList, params as unknown as Record); } /** * 获取单个模型详情 * GET /api/v1/models/:id */ static fetchModelInfo(modelId: string): Promise { const url = ModelsUrlObj.getModelInfo.replace('{model_id}', modelId); return get(url); } }