import { useState } from 'react' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { Button, message, Tag, Modal, Form, Input, InputNumber, Switch, Space, Popconfirm, Tabs, Table, Typography } from 'antd' import { PlusOutlined } from '@ant-design/icons' import type { ProColumns } from '@ant-design/pro-components' import { ProTable } from '@ant-design/pro-components' import { providerService } from '@/services/providers' import { modelService } from '@/services/models' import type { Provider, ProviderKey, Model } from '@/types' const { Text } = Typography // ============================================================ // 子组件: 模型表格 // ============================================================ function ProviderModelsTable({ providerId }: { providerId: string }) { const queryClient = useQueryClient() const [form] = Form.useForm() const [modalOpen, setModalOpen] = useState(false) const [editingId, setEditingId] = useState(null) const { data, isLoading } = useQuery({ queryKey: ['provider-models', providerId], queryFn: ({ signal }) => modelService.list({ provider_id: providerId! }, signal), }) const createMutation = useMutation({ mutationFn: (data: Partial>) => modelService.create(data), onSuccess: () => { message.success('模型已创建') queryClient.invalidateQueries({ queryKey: ['provider-models', providerId] }) setModalOpen(false) form.resetFields() }, onError: (err: Error) => message.error(err.message || '创建失败'), }) const updateMutation = useMutation({ mutationFn: ({ id, data }: { id: string; data: Partial> }) => modelService.update(id, data), onSuccess: () => { message.success('模型已更新') queryClient.invalidateQueries({ queryKey: ['provider-models', providerId] }) setModalOpen(false) }, onError: (err: Error) => message.error(err.message || '更新失败'), }) const deleteMutation = useMutation({ mutationFn: (id: string) => modelService.delete(id), onSuccess: () => { message.success('模型已删除') queryClient.invalidateQueries({ queryKey: ['provider-models', providerId] }) }, onError: (err: Error) => message.error(err.message || '删除失败'), }) const handleSave = async () => { const values = await form.validateFields() if (editingId) { updateMutation.mutate({ id: editingId, data: values }) } else { createMutation.mutate({ ...values, provider_id: providerId }) } } const columns: ProColumns[] = [ { title: '模型 ID', dataIndex: 'model_id', width: 180, render: (_, r) => {r.model_id} }, { title: '别名', dataIndex: 'alias', width: 120 }, { title: '类型', dataIndex: 'is_embedding', width: 80, render: (_, r) => r.is_embedding ? Embedding : Chat }, { title: '上下文窗口', dataIndex: 'context_window', width: 100, render: (_, r) => r.context_window?.toLocaleString() }, { title: '最大输出', dataIndex: 'max_output_tokens', width: 90, render: (_, r) => r.max_output_tokens?.toLocaleString() }, { title: '流式', dataIndex: 'supports_streaming', width: 60, render: (_, r) => r.supports_streaming ? : }, { title: '视觉', dataIndex: 'supports_vision', width: 60, render: (_, r) => r.supports_vision ? : }, { title: '状态', dataIndex: 'enabled', width: 60, render: (_, r) => r.enabled ? 启用 : 禁用 }, { title: '操作', width: 120, render: (_, record) => ( deleteMutation.mutate(record.id)}> ), }, ] const models = data?.items ?? [] return (
columns={columns} dataSource={models} loading={isLoading} rowKey="id" size="small" pagination={false} /> { setModalOpen(false); setEditingId(null); form.resetFields() }} confirmLoading={createMutation.isPending || updateMutation.isPending} width={560} >
) } // ============================================================ // 子组件: Key Pool 表格 // ============================================================ function ProviderKeysTable({ providerId }: { providerId: string }) { const queryClient = useQueryClient() const [addKeyForm] = Form.useForm() const [addKeyOpen, setAddKeyOpen] = useState(false) const { data, isLoading } = useQuery({ queryKey: ['provider-keys', providerId], queryFn: ({ signal }) => providerService.listKeys(providerId!, signal), }) const addKeyMutation = useMutation({ mutationFn: (data: { key_label: string; key_value: string; priority?: number; max_rpm?: number; max_tpm?: number }) => providerService.addKey(providerId, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['provider-keys', providerId] }) message.success('密钥已添加') setAddKeyOpen(false) addKeyForm.resetFields() }, onError: () => message.error('添加失败'), }) const toggleKeyMutation = useMutation({ mutationFn: ({ keyId, active }: { keyId: string; active: boolean }) => providerService.toggleKey(providerId, keyId, active), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['provider-keys', providerId] }) message.success('状态已切换') }, onError: () => message.error('切换失败'), }) const deleteKeyMutation = useMutation({ mutationFn: (keyId: string) => providerService.deleteKey(providerId, keyId), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['provider-keys', providerId] }) message.success('密钥已删除') }, onError: () => message.error('删除失败'), }) const keyColumns: ProColumns[] = [ { title: '标签', dataIndex: 'key_label', width: 120 }, { title: '优先级', dataIndex: 'priority', width: 70 }, { title: '请求数', dataIndex: 'total_requests', width: 80 }, { title: 'Token 数', dataIndex: 'total_tokens', width: 90 }, { title: '状态', dataIndex: 'is_active', width: 70, render: (_, r) => r.is_active ? 活跃 : 冷却, }, { title: '操作', width: 120, render: (_, record) => ( toggleKeyMutation.mutate({ keyId: record.id, active: !record.is_active })} > deleteKeyMutation.mutate(record.id)}> ), }, ] const keys = data ?? [] return (
columns={keyColumns} dataSource={keys} loading={isLoading} rowKey="id" size="small" pagination={false} /> { addKeyForm.validateFields().then((v) => addKeyMutation.mutate(v)) }} onCancel={() => setAddKeyOpen(false)} confirmLoading={addKeyMutation.isPending} >
) } // ============================================================ // 主页面: 模型服务 // ============================================================ export default function ModelServices() { const queryClient = useQueryClient() const [form] = Form.useForm() const [modalOpen, setModalOpen] = useState(false) const [editingId, setEditingId] = useState(null) const { data, isLoading } = useQuery({ queryKey: ['providers'], queryFn: ({ signal }) => providerService.list(signal), }) const createMutation = useMutation({ mutationFn: (data: Partial>) => providerService.create(data), onSuccess: () => { message.success('服务商已创建') queryClient.invalidateQueries({ queryKey: ['providers'] }) setModalOpen(false) form.resetFields() }, onError: (err: Error) => message.error(err.message || '创建失败'), }) const updateMutation = useMutation({ mutationFn: ({ id, data }: { id: string; data: Partial> }) => providerService.update(id, data), onSuccess: () => { message.success('服务商已更新') queryClient.invalidateQueries({ queryKey: ['providers'] }) setModalOpen(false) }, onError: (err: Error) => message.error(err.message || '更新失败'), }) const deleteMutation = useMutation({ mutationFn: (id: string) => providerService.delete(id), onSuccess: () => { message.success('服务商已删除') queryClient.invalidateQueries({ queryKey: ['providers'] }) }, onError: (err: Error) => message.error(err.message || '删除失败'), }) const handleSave = async () => { const values = await form.validateFields() if (editingId) { updateMutation.mutate({ id: editingId, data: values }) } else { createMutation.mutate(values) } } const columns: ProColumns[] = [ { title: '名称', dataIndex: 'display_name', width: 150 }, { title: '标识', dataIndex: 'name', width: 120, render: (_, r) => {r.name} }, { title: 'Base URL', dataIndex: 'base_url', width: 260, ellipsis: true }, { title: '协议', dataIndex: 'api_protocol', width: 90, hideInSearch: true }, { title: 'RPM', dataIndex: 'rate_limit_rpm', width: 80, hideInSearch: true, render: (_, r) => r.rate_limit_rpm ?? '-' }, { title: '状态', dataIndex: 'enabled', width: 70, hideInSearch: true, render: (_, r) => r.enabled ? 启用 : 禁用, }, { title: '操作', width: 140, hideInSearch: true, render: (_, record) => ( deleteMutation.mutate(record.id)}> ), }, ] return (
columns={columns} dataSource={data?.items ?? []} loading={isLoading} rowKey="id" search={{}} toolBarRender={() => [ , ]} pagination={{ total: data?.total ?? 0, pageSize: data?.page_size ?? 20, current: data?.page ?? 1, showSizeChanger: false, }} expandable={{ expandedRowRender: (record) => ( , }, { key: 'keys', label: 'Key Pool', children: , }, ]} /> ), }} /> { setModalOpen(false); setEditingId(null); form.resetFields() }} confirmLoading={createMutation.isPending || updateMutation.isPending} width={560} >
) }