- 新增 providers/models 数据库表、迁移和数据访问层 - 新增 15 个后端 API 路由(供应商/模型 CRUD + 连通性测试) - 新增 AI 服务层(registry.ts: buildProviderRegistry + testProviderConnection) - 新增前端模型管理页面(Tabs: 供应商/模型,含表格、表单、工具栏) - 新增前端 hooks(use-providers, use-models) - 新增共享类型和 MODEL_CAPABILITIES 常量 - 新增 10 个测试文件(66 个测试用例,4 个因 bun test ESM 兼容问题待修复) - 更新开发文档(architecture, backend, frontend) - 附带 apply-review 修复:统一错误响应、提取共享常量、清理重复测试 注意:registry.test.ts 中 4 个测试因 bun test 无法解析 createProviderRegistry ESM 导出而失败,详情见 context.md
187 lines
6.6 KiB
TypeScript
187 lines
6.6 KiB
TypeScript
import { Flex, Tabs } from "antd";
|
|
import { useState } from "react";
|
|
|
|
import type { Model, Provider } from "../../../shared/api";
|
|
|
|
import {
|
|
useCreateModel,
|
|
useDeleteModel,
|
|
useDisableModel,
|
|
useEnableModel,
|
|
useModelList,
|
|
useUpdateModel,
|
|
} from "../../hooks/use-models";
|
|
import {
|
|
useCreateProvider,
|
|
useDeleteProvider,
|
|
useDisableProvider,
|
|
useEnableProvider,
|
|
useProviderList,
|
|
useTestProviderConnection,
|
|
useUpdateProvider,
|
|
} from "../../hooks/use-providers";
|
|
import { ModelFormModal } from "./components/ModelFormModal";
|
|
import { ModelTable } from "./components/ModelTable";
|
|
import { ModelToolbar } from "./components/ModelToolbar";
|
|
import { ProviderFormModal } from "./components/ProviderFormModal";
|
|
import { ProviderTable } from "./components/ProviderTable";
|
|
import { ProviderToolbar } from "./components/ProviderToolbar";
|
|
|
|
export function ModelsPage() {
|
|
const [activeTab, setActiveTab] = useState<string>("providers");
|
|
|
|
const [providerPage, setProviderPage] = useState(1);
|
|
const [providerPageSize, setProviderPageSize] = useState(20);
|
|
const [providerKeyword, setProviderKeyword] = useState("");
|
|
const [providerDialogOpen, setProviderDialogOpen] = useState(false);
|
|
const [editingProvider, setEditingProvider] = useState<null | Provider>(null);
|
|
|
|
const [modelPage, setModelPage] = useState(1);
|
|
const [modelPageSize, setModelPageSize] = useState(20);
|
|
const [modelKeyword, setModelKeyword] = useState("");
|
|
const [modelDialogOpen, setModelDialogOpen] = useState(false);
|
|
const [editingModel, setEditingModel] = useState<Model | null>(null);
|
|
|
|
const { data: providerData, isLoading: providerLoading } = useProviderList({
|
|
keyword: providerKeyword || undefined,
|
|
page: providerPage,
|
|
pageSize: providerPageSize,
|
|
});
|
|
|
|
const { data: modelData, isLoading: modelLoading } = useModelList({
|
|
keyword: modelKeyword || undefined,
|
|
page: modelPage,
|
|
pageSize: modelPageSize,
|
|
});
|
|
|
|
const createProviderMutation = useCreateProvider();
|
|
const updateProviderMutation = useUpdateProvider();
|
|
const deleteProviderMutation = useDeleteProvider();
|
|
const enableProviderMutation = useEnableProvider();
|
|
const disableProviderMutation = useDisableProvider();
|
|
const testProviderMutation = useTestProviderConnection();
|
|
|
|
const createModelMutation = useCreateModel();
|
|
const updateModelMutation = useUpdateModel();
|
|
const deleteModelMutation = useDeleteModel();
|
|
const enableModelMutation = useEnableModel();
|
|
const disableModelMutation = useDisableModel();
|
|
|
|
const isProviderSubmitting = createProviderMutation.isPending || updateProviderMutation.isPending;
|
|
const isProviderActionPending =
|
|
deleteProviderMutation.isPending || enableProviderMutation.isPending || disableProviderMutation.isPending;
|
|
|
|
const isModelSubmitting = createModelMutation.isPending || updateModelMutation.isPending;
|
|
const isModelActionPending =
|
|
deleteModelMutation.isPending || enableModelMutation.isPending || disableModelMutation.isPending;
|
|
|
|
return (
|
|
<Flex flex={1} gap="var(--ant-margin-lg)" vertical>
|
|
<Tabs
|
|
activeKey={activeTab}
|
|
items={[
|
|
{ key: "providers", label: "供应商" },
|
|
{ key: "models", label: "模型" },
|
|
]}
|
|
onChange={(key) => setActiveTab(key)}
|
|
/>
|
|
|
|
{activeTab === "providers" && (
|
|
<>
|
|
<ProviderToolbar
|
|
keyword={providerKeyword}
|
|
onSearch={(value) => {
|
|
setProviderKeyword(value);
|
|
setProviderPage(1);
|
|
}}
|
|
onSearchClear={() => {
|
|
setProviderKeyword("");
|
|
setProviderPage(1);
|
|
}}
|
|
openCreateDialog={() => {
|
|
setEditingProvider(null);
|
|
setProviderDialogOpen(true);
|
|
}}
|
|
/>
|
|
<ProviderTable
|
|
data={providerData}
|
|
loading={providerLoading || isProviderActionPending}
|
|
onDelete={(id) => deleteProviderMutation.mutateAsync(id)}
|
|
onDisable={(id) => disableProviderMutation.mutateAsync(id)}
|
|
onEdit={(provider) => {
|
|
setEditingProvider(provider);
|
|
setProviderDialogOpen(true);
|
|
}}
|
|
onEnable={(id) => enableProviderMutation.mutateAsync(id)}
|
|
onPageChange={(p, ps) => {
|
|
setProviderPage(p);
|
|
setProviderPageSize(ps);
|
|
}}
|
|
onTest={(id) => testProviderMutation.mutateAsync(id)}
|
|
page={providerPage}
|
|
pageSize={providerPageSize}
|
|
/>
|
|
<ProviderFormModal
|
|
editingProvider={editingProvider}
|
|
onCancel={() => setProviderDialogOpen(false)}
|
|
onCreate={(data) => createProviderMutation.mutateAsync(data)}
|
|
onOpenChange={setProviderDialogOpen}
|
|
onUpdate={(args) => updateProviderMutation.mutateAsync(args)}
|
|
open={providerDialogOpen}
|
|
submitting={isProviderSubmitting}
|
|
/>
|
|
</>
|
|
)}
|
|
|
|
{activeTab === "models" && (
|
|
<>
|
|
<ModelToolbar
|
|
keyword={modelKeyword}
|
|
onSearch={(value) => {
|
|
setModelKeyword(value);
|
|
setModelPage(1);
|
|
}}
|
|
onSearchClear={() => {
|
|
setModelKeyword("");
|
|
setModelPage(1);
|
|
}}
|
|
openCreateDialog={() => {
|
|
setEditingModel(null);
|
|
setModelDialogOpen(true);
|
|
}}
|
|
/>
|
|
<ModelTable
|
|
data={modelData}
|
|
loading={modelLoading || isModelActionPending}
|
|
onDelete={(id) => deleteModelMutation.mutateAsync(id)}
|
|
onDisable={(id) => disableModelMutation.mutateAsync(id)}
|
|
onEdit={(model) => {
|
|
setEditingModel(model);
|
|
setModelDialogOpen(true);
|
|
}}
|
|
onEnable={(id) => enableModelMutation.mutateAsync(id)}
|
|
onPageChange={(p, ps) => {
|
|
setModelPage(p);
|
|
setModelPageSize(ps);
|
|
}}
|
|
page={modelPage}
|
|
pageSize={modelPageSize}
|
|
providers={providerData?.items ?? []}
|
|
/>
|
|
<ModelFormModal
|
|
editingModel={editingModel}
|
|
onCancel={() => setModelDialogOpen(false)}
|
|
onCreate={(data) => createModelMutation.mutateAsync(data)}
|
|
onOpenChange={setModelDialogOpen}
|
|
onUpdate={(args) => updateModelMutation.mutateAsync(args)}
|
|
open={modelDialogOpen}
|
|
providers={providerData?.items ?? []}
|
|
submitting={isModelSubmitting}
|
|
testConnection={editingModel ? (id: string) => testProviderMutation.mutateAsync(id) : undefined}
|
|
/>
|
|
</>
|
|
)}
|
|
</Flex>
|
|
);
|
|
}
|