Files
Alfred/src/web/pages/models/components/ProviderFormModal.tsx
lanyuanxiaoyao 48c76e6180 fix: 模型管理审查修复与归档
- 修复 registry 测试 ai mock 缺失 createProviderRegistry 导出
- 新增 POST /api/providers/test 支持未保存供应商配置连通性测试
- 供应商表单新增测试连接按钮,新建默认 openai-compatible
- 连通性测试按 ok 展示成功/失败,不再统一 success 样式
- 模型表单新建时也可测试供应商连接
- 模型页使用独立 provider 列表避免分页/搜索影响
- 移除模型管理组件内联 style
- 新增 ProviderTestResultResponse 共享响应类型
- 新增 bun run format:check 脚本
- 补充关键测试覆盖(删除关联、连通性、默认类型、表单测试)
- 更新 docs/user/usage.md、docs/development/*、design.md、tasks.md
- 归档 change 至 openspec/changes/archive/2026-05-29-add-model-management
2026-05-29 14:05:01 +08:00

155 lines
4.6 KiB
TypeScript

import { App as AntApp, Button, Form, Input, Modal, Select, Space } from "antd";
import { useEffect, useState } from "react";
import type {
CreateProviderRequest,
Provider,
ProviderTestResponse,
ProviderType,
UpdateProviderRequest,
} from "../../../../shared/api";
interface FormValues {
apiKey: string;
baseUrl: string;
name: string;
type: ProviderType;
}
interface ProviderFormModalProps {
editingProvider: null | Provider;
onCancel: () => void;
onCreate: (data: CreateProviderRequest) => Promise<unknown>;
onOpenChange: (open: boolean) => void;
onTest: (data: CreateProviderRequest) => Promise<ProviderTestResponse>;
onUpdate: (args: { data: UpdateProviderRequest; id: string }) => Promise<unknown>;
open: boolean;
submitting: boolean;
}
const TYPE_OPTIONS = [
{ label: "OpenAI 兼容", value: "openai-compatible" },
{ label: "OpenAI", value: "openai" },
{ label: "Anthropic", value: "anthropic" },
];
export function ProviderFormModal({
editingProvider,
onCancel,
onCreate,
onOpenChange,
onTest,
onUpdate,
open,
submitting,
}: ProviderFormModalProps) {
const { message } = AntApp.useApp();
const [form] = Form.useForm<FormValues>();
const [testing, setTesting] = useState(false);
useEffect(() => {
if (!open) return;
if (editingProvider) {
form.setFieldsValue({
apiKey: editingProvider.apiKey,
baseUrl: editingProvider.baseUrl,
name: editingProvider.name,
type: editingProvider.type,
});
} else {
form.resetFields();
form.setFieldsValue({ type: "openai-compatible" });
}
}, [editingProvider, form, open]);
const handleFinish = async (values: FormValues) => {
try {
if (editingProvider) {
const reqData: UpdateProviderRequest = {};
if (values.name !== editingProvider.name) reqData.name = values.name;
if (values.baseUrl !== editingProvider.baseUrl) reqData.baseUrl = values.baseUrl;
if (values.apiKey !== editingProvider.apiKey) reqData.apiKey = values.apiKey;
if (values.type !== editingProvider.type) reqData.type = values.type;
await onUpdate({ data: reqData, id: editingProvider.id });
message.success("供应商已更新");
} else {
const reqData: CreateProviderRequest = {
apiKey: values.apiKey,
baseUrl: values.baseUrl,
name: values.name,
type: values.type,
};
await onCreate(reqData);
message.success("供应商已创建");
}
onOpenChange(false);
} catch (err) {
if (err instanceof Error) {
message.error(err.message);
}
}
};
const handleTest = async () => {
try {
const values = await form.validateFields(["name", "type", "baseUrl", "apiKey"]);
setTesting(true);
const result = await onTest({
apiKey: values.apiKey,
baseUrl: values.baseUrl,
name: values.name,
type: values.type,
});
if (result.ok) {
message.success(result.message);
} else {
message.error(result.message);
}
} catch (err) {
if (err instanceof Error) {
message.error(err.message);
}
} finally {
setTesting(false);
}
};
return (
<Modal
confirmLoading={submitting}
destroyOnHidden
okText="确定"
onCancel={onCancel}
onOk={() => void form.submit()}
open={open}
title={editingProvider ? "编辑供应商" : "新建供应商"}
>
<Form form={form} layout="vertical" onFinish={(values) => void handleFinish(values)}>
<Form.Item
label="供应商名称"
name="name"
rules={[{ message: "请输入供应商名称", required: true, whitespace: true }]}
>
<Input placeholder="请输入供应商名称" />
</Form.Item>
<Form.Item label="供应商类型" name="type" rules={[{ message: "请选择供应商类型", required: true }]}>
<Select options={TYPE_OPTIONS} placeholder="请选择供应商类型" />
</Form.Item>
<Form.Item label="Base URL" name="baseUrl" rules={[{ message: "请输入 Base URL", required: true }]}>
<Input placeholder="https://api.openai.com/v1" />
</Form.Item>
<Form.Item label="API Key" name="apiKey" rules={[{ message: "请输入 API Key", required: true }]}>
<Input.Password placeholder="请输入 API Key" />
</Form.Item>
<Form.Item>
<Space>
<Button loading={testing} onClick={() => void handleTest()}>
</Button>
</Space>
</Form.Item>
</Form>
</Modal>
);
}