- 全表新增 deleted_at 列,统一软删除替代硬删除+archived_at - models.model_id 重命名为 external_id,消除语义混淆 - conversations.model_id 改为可空(模型为建议而非绑定) - messages 新增 updated_at,移除 CASCADE 改为 DAO 层级联 - 移除 DB 层 UNIQUE 约束,改为应用层检查(配合软删除) - 新增 helpers.ts(baseColumns + 构造层防御)、ESLint 规则、契约测试 - 迁移 0004 补全 CHECK 约束(providers.type/materials.status/messages.role) - DAO 层全面重写:级联软删除、应用层唯一、provider 删除保护 - 路由/前端/测试全量适配 externalId 重命名及类型变更
150 lines
4.7 KiB
TypeScript
150 lines
4.7 KiB
TypeScript
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
|
|
import type {
|
|
CreateModelRequest,
|
|
Model,
|
|
ModelListResponse,
|
|
ModelResponse,
|
|
ModelTestResponse,
|
|
ModelTestResultResponse,
|
|
TestModelRequest,
|
|
UpdateModelRequest,
|
|
} from "../../../shared/api";
|
|
|
|
import { handleResponse, handleVoidResponse } from "../utils/api";
|
|
import { createConsoleLogger } from "../utils/logger";
|
|
|
|
const MODELS_KEY = ["models"] as const;
|
|
const logger = createConsoleLogger();
|
|
|
|
export async function createModel(data: CreateModelRequest): Promise<Model> {
|
|
const response = await fetch("/api/models", {
|
|
body: JSON.stringify(data),
|
|
headers: { "Content-Type": "application/json" },
|
|
method: "POST",
|
|
});
|
|
return handleResponse(response, (data) => (data as ModelResponse).model);
|
|
}
|
|
|
|
export async function deleteModel(id: string): Promise<void> {
|
|
const response = await fetch(`/api/models/${id}`, { method: "DELETE" });
|
|
return handleVoidResponse(response);
|
|
}
|
|
|
|
export async function fetchModel(id: string): Promise<Model> {
|
|
const response = await fetch(`/api/models/${id}`);
|
|
return handleResponse(response, (data) => (data as ModelResponse).model);
|
|
}
|
|
|
|
export async function fetchModelList(params: {
|
|
capabilities?: string;
|
|
keyword?: string;
|
|
page?: number;
|
|
pageSize?: number;
|
|
providerId?: string;
|
|
sortBy?: string;
|
|
sortOrder?: string;
|
|
}): Promise<ModelListResponse> {
|
|
const searchParams = new URLSearchParams();
|
|
if (params.page) searchParams.set("page", String(params.page));
|
|
if (params.pageSize) searchParams.set("pageSize", String(params.pageSize));
|
|
if (params.keyword) searchParams.set("keyword", params.keyword);
|
|
if (params.providerId) searchParams.set("providerId", params.providerId);
|
|
if (params.sortBy) searchParams.set("sortBy", params.sortBy);
|
|
if (params.sortOrder) searchParams.set("sortOrder", params.sortOrder);
|
|
if (params.capabilities) searchParams.set("capabilities", params.capabilities);
|
|
const qs = searchParams.toString();
|
|
const url = `/api/models${qs ? `?${qs}` : ""}`;
|
|
const response = await fetch(url);
|
|
if (!response.ok) {
|
|
const body = (await response.json().catch(() => null)) as null | { error?: string };
|
|
throw new Error(body?.error ?? `HTTP ${response.status}`);
|
|
}
|
|
return response.json() as Promise<ModelListResponse>;
|
|
}
|
|
|
|
export async function testModelConnection(data: TestModelRequest): Promise<ModelTestResponse> {
|
|
const response = await fetch("/api/models/test", {
|
|
body: JSON.stringify(data),
|
|
headers: { "Content-Type": "application/json" },
|
|
method: "POST",
|
|
});
|
|
if (!response.ok) {
|
|
const body = (await response.json().catch(() => null)) as null | { error?: string };
|
|
throw new Error(body?.error ?? `HTTP ${response.status}`);
|
|
}
|
|
const result = (await response.json()) as ModelTestResultResponse;
|
|
return result.modelTestResponse;
|
|
}
|
|
|
|
export async function updateModel(id: string, data: UpdateModelRequest): Promise<Model> {
|
|
const response = await fetch(`/api/models/${id}`, {
|
|
body: JSON.stringify(data),
|
|
headers: { "Content-Type": "application/json" },
|
|
method: "PATCH",
|
|
});
|
|
return handleResponse(response, (data) => (data as ModelResponse).model);
|
|
}
|
|
|
|
export function useCreateModel() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: createModel,
|
|
onSuccess: (data) => {
|
|
logger.info("模型创建成功", { externalId: data.externalId, providerId: data.providerId });
|
|
void queryClient.invalidateQueries({ queryKey: MODELS_KEY });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useDeleteModel() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: deleteModel,
|
|
onSuccess: (_data, variables) => {
|
|
logger.info("模型删除成功", { modelId: variables });
|
|
void queryClient.invalidateQueries({ queryKey: MODELS_KEY });
|
|
},
|
|
});
|
|
}
|
|
|
|
export function useModel(id: string) {
|
|
return useQuery({
|
|
enabled: !!id,
|
|
queryFn: () => fetchModel(id),
|
|
queryKey: [...MODELS_KEY, "detail", id],
|
|
});
|
|
}
|
|
|
|
export function useModelList(params: {
|
|
capabilities?: string;
|
|
keyword?: string;
|
|
page?: number;
|
|
pageSize?: number;
|
|
providerId?: string;
|
|
sortBy?: string;
|
|
sortOrder?: string;
|
|
}) {
|
|
return useQuery({
|
|
queryFn: () => fetchModelList(params),
|
|
queryKey: [...MODELS_KEY, "list", params],
|
|
});
|
|
}
|
|
|
|
export function useTestModelConnection() {
|
|
return useMutation({
|
|
mutationFn: testModelConnection,
|
|
});
|
|
}
|
|
|
|
export function useUpdateModel() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: (args: { data: UpdateModelRequest; id: string }) => updateModel(args.id, args.data),
|
|
onSuccess: (data) => {
|
|
logger.info("模型更新成功", { externalId: data.externalId, providerId: data.providerId });
|
|
void queryClient.invalidateQueries({ queryKey: MODELS_KEY });
|
|
},
|
|
});
|
|
}
|