167 lines
5.1 KiB
TypeScript
167 lines
5.1 KiB
TypeScript
// ============================================================
|
||
// use-api.ts — 业务 API Hook 集合
|
||
//
|
||
// 基于 useAsyncData / useAsyncMutation 封装各模块 API,
|
||
// 组件只需调用 Hook 获取 { data, loading, error } 三元组,
|
||
// 不再需要在组件内手写 useEffect + cancelled flag + useState。
|
||
//
|
||
// 每个 Hook 自动处理:
|
||
// - 登录状态门控(enabled: isLoggedIn)
|
||
// - 竞态条件
|
||
// - 错误日志
|
||
// - 用户可读错误信息(errorMessage)
|
||
// ============================================================
|
||
|
||
import { useMemo } from 'react';
|
||
import { useAppContext } from '@/contexts/app-context';
|
||
import {
|
||
TaskAPI,
|
||
ModelAPI,
|
||
fetchBanners,
|
||
MODEL_CATEGORY_LABELS,
|
||
type TaskListRequestParams,
|
||
type TaskInfoItemResponseBody,
|
||
type CreatTaskRequestBody,
|
||
type ModelsListResponseBody,
|
||
type Banner,
|
||
type ModelCategory,
|
||
} from '@/services/modules';
|
||
import { useAsyncData, useAsyncMutation } from './use-async';
|
||
|
||
// ============================================================
|
||
// Banner
|
||
// ============================================================
|
||
|
||
/** 首页轮播图数据 Hook */
|
||
export function useBannerList() {
|
||
const { isLoggedIn } = useAppContext();
|
||
|
||
return useAsyncData<Banner[]>(
|
||
fetchBanners,
|
||
[],
|
||
{ enabled: isLoggedIn, label: 'banner-list' },
|
||
);
|
||
}
|
||
|
||
// ============================================================
|
||
// 模型
|
||
// ============================================================
|
||
|
||
/** 模型列表数据 Hook(按类别分别请求后合并) */
|
||
export function useModelList() {
|
||
const { isLoggedIn } = useAppContext();
|
||
|
||
// 使用单个 fetcher 返回合并后的结果
|
||
const result = useAsyncData<ModelsListResponseBody[]>(
|
||
async () => {
|
||
const categories: ModelCategory[] = ['txt2img', 'img2img', 'img2video', 'txt2audio'];
|
||
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;
|
||
},
|
||
[],
|
||
{ enabled: isLoggedIn, label: 'model-list' },
|
||
);
|
||
|
||
// 按类别分组
|
||
const groupedModels = useMemo(() => {
|
||
const groups = new Map<string, ModelsListResponseBody[]>();
|
||
result.data?.forEach((model) => {
|
||
const cat = model.category_name || 'other';
|
||
if (!groups.has(cat)) groups.set(cat, []);
|
||
groups.get(cat)!.push(model);
|
||
});
|
||
return groups;
|
||
}, [result.data]);
|
||
|
||
return {
|
||
...result,
|
||
/** 按类别分组后的模型 */
|
||
groupedModels,
|
||
};
|
||
}
|
||
|
||
// ============================================================
|
||
// 任务列表
|
||
// ============================================================
|
||
|
||
export interface UseTaskListParams {
|
||
/** 完整的 API 请求参数(由调用方根据 antd Table onChange 构建) */
|
||
requestParams: TaskListRequestParams;
|
||
/** 外部传入的刷新信号(任意变化触发重新请求) */
|
||
refreshSignal?: number;
|
||
}
|
||
|
||
/**
|
||
* 任务列表 Hook(分页 + 筛选 + 排序)
|
||
*
|
||
* 调用方负责构建 TaskListRequestParams(包括 edition-aware 的 user_ids 逻辑),
|
||
* 本 Hook 仅负责数据获取、竞态控制、错误日志。
|
||
*/
|
||
export function useTaskList(params: UseTaskListParams) {
|
||
const { isLoggedIn } = useAppContext();
|
||
const { requestParams, refreshSignal = 0 } = params;
|
||
|
||
return useAsyncData(
|
||
() => TaskAPI.getTaskList(requestParams),
|
||
[requestParams, refreshSignal],
|
||
{ enabled: isLoggedIn, label: 'task-list' },
|
||
);
|
||
}
|
||
|
||
// ============================================================
|
||
// 任务详情
|
||
// ============================================================
|
||
|
||
/** 任务详情 Hook(taskId 为 null 时不请求) */
|
||
export function useTaskDetail(taskId: string | null | undefined) {
|
||
const { isLoggedIn } = useAppContext();
|
||
|
||
return useAsyncData<TaskInfoItemResponseBody>(
|
||
() => TaskAPI.getTaskInfo(taskId!),
|
||
[taskId],
|
||
{ enabled: isLoggedIn && !!taskId, label: 'task-detail' },
|
||
);
|
||
}
|
||
|
||
// ============================================================
|
||
// 提交任务
|
||
// ============================================================
|
||
|
||
/** 提交任务 Mutation Hook */
|
||
export function useSubmitTask(options?: {
|
||
onSuccess?: (data: TaskInfoItemResponseBody) => void;
|
||
onError?: (err: Error) => void;
|
||
}) {
|
||
return useAsyncMutation<TaskInfoItemResponseBody, [CreatTaskRequestBody]>(
|
||
(body) => TaskAPI.submitTask(body),
|
||
{
|
||
label: 'submit-task',
|
||
onSuccess: options?.onSuccess,
|
||
onError: options?.onError,
|
||
},
|
||
);
|
||
}
|
||
|
||
// ---------- 重新导出类型和工具 ----------
|
||
|
||
export { MODEL_CATEGORY_LABELS };
|
||
export type { ModelCategory, ModelsListResponseBody, TaskInfoItemResponseBody, Banner };
|