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:
@@ -24,9 +24,9 @@ import {
|
||||
type CreatTaskRequestBody,
|
||||
type ModelsListResponseBody,
|
||||
type Banner,
|
||||
type ModelCategory,
|
||||
type ModelCategoryStringEnum,
|
||||
} from '@/services/modules';
|
||||
import {useAsyncData, useAsyncMutation} from './use-async';
|
||||
import {useAsyncData, UseAsyncDataReturn, useAsyncMutation} from './use-async';
|
||||
|
||||
// ============================================================
|
||||
// Banner
|
||||
@@ -47,53 +47,38 @@ export function useBannerList() {
|
||||
// 模型
|
||||
// ============================================================
|
||||
|
||||
/** 模型列表数据 Hook(按类别分别请求后合并) */
|
||||
/** 模型列表数据 Hook(单次请求全量模型 + 客户端按 category_name 分组)
|
||||
|
||||
* 因后端 category 查询参数实际不可用(传参返回空列表),
|
||||
* 改为不带分类参数一次拉取全部,再由客户端根据响应中的
|
||||
* category_name 字段手动分组。 */
|
||||
export function useModelList() {
|
||||
const {isLoggedIn} = useAppContext();
|
||||
|
||||
// 使用单个 fetcher 返回合并后的结果
|
||||
const result = useAsyncData<ModelsListResponseBody[]>(
|
||||
async () => {
|
||||
const categories: ModelCategory[] = ["image_to_image", "text_to_image", "image_to_video", "text_to_audio"];
|
||||
const results = await Promise.allSettled(
|
||||
categories.map((category) =>
|
||||
ModelAPI.fetchModels({
|
||||
provider_name: '',
|
||||
category,
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const allModels: ModelsListResponseBody[] = [];
|
||||
results.forEach((r) => {
|
||||
if (r.status === 'fulfilled') {
|
||||
allModels.push(...r.value);
|
||||
}
|
||||
});
|
||||
// 按优先级降序排列
|
||||
allModels.sort((a, b) => b.priority - a.priority);
|
||||
return allModels;
|
||||
},
|
||||
const result: UseAsyncDataReturn<ModelsListResponseBody[]> = useAsyncData<ModelsListResponseBody[]>(
|
||||
() => ModelAPI.fetchModels({page: 1, page_size: 200}),
|
||||
[],
|
||||
{enabled: isLoggedIn, label: 'model-list'},
|
||||
);
|
||||
|
||||
// 按类别分组
|
||||
const groupedModels = useMemo(() => {
|
||||
// 按 category_name 分组(后端返回的 category_name 为 slug 格式如 img2img)
|
||||
const groupedModels: Map<string, ModelsListResponseBody[]> = useMemo(() => {
|
||||
const groups = new Map<string, ModelsListResponseBody[]>();
|
||||
result.data?.forEach((model) => {
|
||||
const cat = model.category_name || 'other';
|
||||
result.data?.forEach((model: ModelsListResponseBody) => {
|
||||
const cat: ModelCategoryStringEnum = model.model_type;
|
||||
if (!groups.has(cat)) groups.set(cat, []);
|
||||
groups.get(cat)!.push(model);
|
||||
});
|
||||
// 每组内按优先级降序排列
|
||||
// groups.forEach((models) => {
|
||||
// models.sort((a, b) => b.priority - a.priority);
|
||||
// });
|
||||
return groups;
|
||||
}, [result.data]);
|
||||
|
||||
return {
|
||||
...result,
|
||||
/** 按类别分组后的模型 */
|
||||
/** 按 model_type 分组后的模型(key 为后端 slug) */
|
||||
groupedModels,
|
||||
};
|
||||
}
|
||||
@@ -163,4 +148,4 @@ export function useSubmitTask(options?: {
|
||||
// ---------- 重新导出类型和工具 ----------
|
||||
|
||||
export {MODEL_CATEGORY_LABELS};
|
||||
export type {ModelCategory, ModelsListResponseBody, TaskInfoItemResponseBody, Banner};
|
||||
export type {ModelCategoryStringEnum, ModelsListResponseBody, TaskInfoItemResponseBody, Banner};
|
||||
|
||||
Reference in New Issue
Block a user