feat: 重构模型列表UI + 修复后端数据格式适配

模型列表 UI 重构:
- ModelSelector 从 Menu 折叠分组改为 Tabs 标签页 + 平铺列表
- 新增「全部模型」Tab + 各分类由 MODEL_CATEGORIES 数组控制顺序
- ModelItem 新增 hover 高亮效果 + 维护中模型禁用 + toast 提示
- 新增错误态(Result + 重试按钮)

后端数据格式适配:
- param_schema 类型从 string[] 改为 ParamSchemaItem[]({id, map_to, spec, ui})
- 修复 Form.Item 重复 key 报错(item.id 作 React key,item.map_to 作表单字段名)
- ModelsListResponseBody 字段类型收紧(model_type / category_name / quota_config)

数据层重构:
- useModelList 从 4 次并行请求改为 1 次全量拉取(后端 category 参数不可用)
- 分组键从 category_name 改为 model_type
- page_size 提升至 200

类型体系增强:
- type 别名改为 as const 数组推导(MODEL_CATEGORIES 单一数据源)
- 新增 CATEGORY_SLUG_MAP + resolveCategoryLabel + toCategorySlug 映射层
- 类型重命名 ModelCategory → ModelCategoryStringEnum
This commit is contained in:
2026-06-08 19:02:32 +08:00
parent a2741edf01
commit 59059caae7
6 changed files with 343 additions and 163 deletions

View File

@@ -29,7 +29,7 @@ import { useHomeContext } from '@/contexts/home-context';
import { useAppContext } from '@/contexts/app-context';
import { useSubmitTask } from '@/hooks/use-api';
import { emit, EVENTS } from '@/utils/event-bus';
import type { TaskInfoItemResponseBody } from '@/services/modules';
import type { ModelsListResponseBody, TaskInfoItemResponseBody } from '@/services/modules';
const { Text, Title } = Typography;
const { TextArea } = Input;
@@ -39,7 +39,10 @@ const { Dragger } = Upload;
/** 从 ui_config 中解析的参数描述 */
interface ParamDescriptor {
/** React keyitem.id保证唯一 */
key: string;
/** API 字段名item.map_to */
name: string;
label: string;
/** 控件类型 */
controlType: 'text' | 'number' | 'textarea' | 'select' | 'slider' | 'switch' | 'image-upload';
@@ -90,21 +93,26 @@ function inferControlType(config: Record<string, unknown>): ParamDescriptor['con
}
}
/** 从 param_schema + ui_config 构建参数描述符列表 */
/**
* 从 param_schema 构建参数描述符列表
*
* 后端 param_schema 格式为 ParamSchemaItem[](对象数组),
* 每项的 ui 字段内嵌了控件类型、标签、校验规则等 UI 配置。
*/
function buildParamDescriptors(
paramSchema: string[],
uiConfig: Record<string, unknown>,
paramSchema: ModelsListResponseBody['param_schema'],
): ParamDescriptor[] {
return paramSchema.map((key) => {
const config = (uiConfig[key] || {}) as Record<string, unknown>;
return paramSchema.map((item) => {
const config = (item.ui || {}) as Record<string, unknown>;
const controlType = inferControlType(config);
return {
key,
label: (config.label as string) || key,
key: item.id,
name: item.map_to,
label: (config.label as string) || item.map_to,
controlType,
defaultValue: config.default ?? config.defaultValue,
required: (config.required as boolean) || false,
placeholder: (config.placeholder as string) || `请输入${config.label || key}`,
placeholder: (config.placeholder as string) || `请输入${config.label || item.map_to}`,
min: config.min as number | undefined,
max: config.max as number | undefined,
step: config.step as number | undefined,
@@ -145,10 +153,7 @@ export function ModelInputForm() {
// 参数描述符
const paramDescriptors = useMemo(() => {
if (!selectedModel) return [];
return buildParamDescriptors(
selectedModel.param_schema || [],
selectedModel.ui_config || {},
);
return buildParamDescriptors(selectedModel.param_schema || []);
}, [selectedModel]);
// 提交
@@ -419,7 +424,7 @@ export function ModelInputForm() {
{paramDescriptors.map((desc) => (
<Form.Item
key={desc.key}
name={desc.key}
name={desc.name}
label={desc.label}
rules={desc.required ? [{ required: true, message: `请输入${desc.label}` }] : undefined}
initialValue={desc.defaultValue}