import { useEffect, useState } from 'react' import { Button, Drawer, Form, Input, message, Popconfirm, Select, Space, Switch, Table, Tag, Tooltip, } from 'antd' import { ApiOutlined, CheckCircleOutlined, DeleteOutlined, EditOutlined, PlusOutlined, ReloadOutlined, } from '@ant-design/icons' import { modelConfigsApi, type ModelCapability, type ModelConfig, type ModelConfigPayload, } from '../api' import PageWrapper from '../components/PageWrapper' import { formatDateTime } from '../utils/date' import { notifyModelConfigsChanged } from '../utils/modelConfigEvents' const capabilityLabels: Record = { chat: '对话', embedding: '向量', moderation: '内容审核', } const capabilityColors: Record = { chat: 'blue', embedding: 'green', moderation: 'orange', } export default function ModelConfigsPage() { const [configs, setConfigs] = useState([]) const [loading, setLoading] = useState(false) const [drawerOpen, setDrawerOpen] = useState(false) const [editing, setEditing] = useState(null) const [testingId, setTestingId] = useState(null) const [capability, setCapability] = useState() const [enabled, setEnabled] = useState() const [form] = Form.useForm() const load = async () => { setLoading(true) try { const response = await modelConfigsApi.list({ capability, enabled }) setConfigs(response.data) } finally { setLoading(false) } } useEffect(() => { load() }, [capability, enabled]) const openCreate = () => { setEditing(null) form.resetFields() form.setFieldsValue({ provider: 'openai_compatible', capability: 'chat', enabled: true, is_default: false, description: '', }) setDrawerOpen(true) } const openEdit = (config: ModelConfig) => { setEditing(config) form.setFieldsValue({ name: config.name, provider: config.provider, capability: config.capability, endpoint_url: config.endpoint_url, model_name: config.model_name, api_key: null, clear_api_key: false, enabled: config.enabled, is_default: config.is_default, description: config.description, }) setDrawerOpen(true) } const submit = async () => { const values = await form.validateFields() const payload: ModelConfigPayload = { ...values, api_key: values.api_key || null, model_name: values.model_name || null, description: values.description || '', } if (editing) { await modelConfigsApi.update(editing.id, payload) message.success('模型配置已更新') } else { await modelConfigsApi.create(payload) message.success('模型配置已创建') } notifyModelConfigsChanged() setDrawerOpen(false) load() } const testConnection = async (config: ModelConfig) => { setTestingId(config.id) try { const response = await modelConfigsApi.test(config.id) if (response.data.ok) message.success(`${config.name} 连接成功`) else message.warning(response.data.message) } finally { setTestingId(null) } } const remove = async (config: ModelConfig) => { await modelConfigsApi.delete(config.id) message.success('模型配置已删除') notifyModelConfigsChanged() load() } const columns = [ { title: '配置名称', dataIndex: 'name', key: 'name', width: 190, render: (value: string, record: ModelConfig) => ( {value} {record.is_default && 默认} ), }, { title: '能力', dataIndex: 'capability', key: 'capability', width: 110, render: (value: ModelCapability) => {capabilityLabels[value]}, }, { title: '模型', dataIndex: 'model_name', key: 'model_name', width: 180, render: (value: string | null) => value || '-' }, { title: 'Endpoint', dataIndex: 'endpoint_url', key: 'endpoint_url', ellipsis: true }, { title: '凭据', dataIndex: 'has_api_key', key: 'has_api_key', width: 90, render: (value: boolean) => value ? 已配置 : , }, { title: '状态', dataIndex: 'enabled', key: 'enabled', width: 90, render: (value: boolean) => value ? 启用 : 停用, }, { title: '更新时间', dataIndex: 'updated_at', key: 'updated_at', width: 170, render: (value: string | null) => formatDateTime(value), }, { title: '操作', key: 'actions', width: 145, fixed: 'right' as const, render: (_: unknown, record: ModelConfig) => ( } >
`共 ${total} 个` }} scroll={{ y: 'calc(100vh - 200px)' }} style={{ height: '100%' }} /> setDrawerOpen(false)} extra={} >
({ value, label }))} />
{() => form.getFieldValue('capability') !== 'moderation' && ( )} {editing?.has_api_key && ( )}
) }