import { useEffect, useState } from 'react'; import { Button, Form, Input, InputNumber, Modal, Popconfirm, Select, Space, Switch, Table, Tag, Typography, message, } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import { LLM_PROVIDERS, LLM_PROVIDER_LABELS, LLM_PROVIDER_PRESETS, type LlmApiConfigDto, type LlmProvider, } from '@dukang/shared-types'; import { request } from '../lib/api'; import { fmtTime } from '../lib/constants'; import { useAdminList } from '../lib/useAdminList'; type FormValues = { name: string; provider: LlmProvider; baseUrl?: string; apiKey?: string; modelName?: string; temperature?: number | null; maxTokens?: number | null; systemPrompt?: string; enabled: boolean; }; export default function LlmConfigsPage() { const [filterForm] = Form.useForm(); const [form] = Form.useForm(); const [filters, setFilters] = useState>({}); const [modalOpen, setModalOpen] = useState(false); const [editing, setEditing] = useState(null); const [saving, setSaving] = useState(false); const providerWatch = Form.useWatch('provider', form); const { data, loading, page, pageSize, setPage, setPageSize, reload } = useAdminList( '/admin/llm-configs', () => { const qs = new URLSearchParams(); if (filters.name) qs.set('name', filters.name); if (filters.enabled) qs.set('enabled', filters.enabled); return qs; }, [filters], ); useEffect(() => { if (!providerWatch || editing) return; const preset = LLM_PROVIDER_PRESETS[providerWatch as LlmProvider]; if (!preset) return; form.setFieldsValue({ baseUrl: preset.defaultBaseUrl || undefined, modelName: preset.defaultModel || undefined, }); }, [providerWatch, editing, form]); const openCreate = () => { setEditing(null); form.setFieldsValue({ name: '', provider: 'DEEPSEEK', baseUrl: LLM_PROVIDER_PRESETS.DEEPSEEK.defaultBaseUrl, apiKey: '', modelName: LLM_PROVIDER_PRESETS.DEEPSEEK.defaultModel, temperature: 0.3, maxTokens: 1024, systemPrompt: '', enabled: true, }); setModalOpen(true); }; const openEdit = (row: LlmApiConfigDto) => { setEditing(row); form.setFieldsValue({ name: row.name, provider: row.provider, baseUrl: row.baseUrl, apiKey: '', modelName: row.modelName, temperature: row.temperature, maxTokens: row.maxTokens, systemPrompt: row.systemPrompt || '', enabled: row.enabled, }); setModalOpen(true); }; const onSave = async () => { const values = await form.validateFields(); setSaving(true); try { if (editing) { if (!editing.canEditFull) { await request(`/admin/llm-configs/${editing.id}`, { method: 'PUT', body: JSON.stringify({ enabled: values.enabled }), }); } else { await request(`/admin/llm-configs/${editing.id}`, { method: 'PUT', body: JSON.stringify({ name: values.name, provider: values.provider, baseUrl: values.baseUrl, apiKey: values.apiKey || undefined, modelName: values.modelName, temperature: values.temperature ?? null, maxTokens: values.maxTokens ?? null, systemPrompt: values.systemPrompt || null, enabled: values.enabled, }), }); } message.success('已保存'); } else { await request('/admin/llm-configs', { method: 'POST', body: JSON.stringify({ name: values.name, provider: values.provider, baseUrl: values.baseUrl, apiKey: values.apiKey, modelName: values.modelName, temperature: values.temperature ?? null, maxTokens: values.maxTokens ?? null, systemPrompt: values.systemPrompt || null, enabled: values.enabled, }), }); message.success('已创建'); } setModalOpen(false); reload(); } catch (e) { message.error(e instanceof Error ? e.message : String(e)); } finally { setSaving(false); } }; const columns: ColumnsType = [ { title: '名称', dataIndex: 'name' }, { title: '提供商', dataIndex: 'provider', width: 120, render: (p: LlmProvider) => LLM_PROVIDER_LABELS[p] || p, }, { title: '模型', dataIndex: 'modelName', ellipsis: true }, { title: 'Key', dataIndex: 'apiKeyConfigured', width: 80, render: (v: boolean) => (v ? 已配 : ), }, { title: '生效', dataIndex: 'enabled', width: 90, render: (v: boolean, row) => ( { try { await request(`/admin/llm-configs/${row.id}`, { method: 'PUT', body: JSON.stringify({ enabled: checked }), }); message.success(checked ? '已启用' : '已停用'); reload(); } catch (e) { message.error(e instanceof Error ? e.message : String(e)); } }} /> ), }, { title: '创建人', dataIndex: 'createdByName', width: 100, render: (v: string | null, row) => v || row.createdByHqAccountId, }, { title: '更新时间', dataIndex: 'updatedAt', width: 170, render: (t: string) => fmtTime(t), }, { title: '操作', width: 220, render: (_, row) => ( {row.canEditFull ? ( { try { await request(`/admin/llm-configs/${row.id}`, { method: 'DELETE' }); message.success('已删除'); reload(); } catch (e) { message.error(e instanceof Error ? e.message : String(e)); } }} > ) : null} ), }, ]; const fullEdit = !editing || editing.canEditFull; return (
语言模型配置 非超管仅可见自己创建的配置,且创建后只能改是否生效
setFilters({ name: v.name || '', enabled: v.enabled === undefined || v.enabled === null ? '' : String(v.enabled), }) } >
); }