nex_docus/frontend/src/pages/System/ModelConfigs.jsx

832 lines
25 KiB
React
Raw Normal View History

2026-04-08 17:03:57 +00:00
import { useEffect, useState } from 'react'
import {
Button,
Card,
Form,
Input,
InputNumber,
Modal,
Popconfirm,
2026-07-24 07:37:22 +00:00
Radio,
2026-04-08 17:03:57 +00:00
Select,
2026-07-24 07:37:22 +00:00
Slider,
2026-04-08 17:03:57 +00:00
Space,
Switch,
2026-07-24 07:37:22 +00:00
Tabs,
2026-04-08 17:03:57 +00:00
Tag,
} from 'antd'
import {
CloudServerOutlined,
2026-07-24 07:37:22 +00:00
CommentOutlined,
DeploymentUnitOutlined,
2026-04-08 17:03:57 +00:00
ExperimentOutlined,
PlusOutlined,
ReloadOutlined,
DeleteOutlined,
EditOutlined,
} from '@ant-design/icons'
import {
createLLMModelConfig,
deleteLLMModelConfig,
getLLMModelConfigDetail,
getLLMModelConfigs,
getLLMProviderCatalog,
testLLMModelConfig,
updateLLMModelConfig,
updateLLMModelConfigStatus,
} from '@/api/llmModelConfigs'
import ListTable from '@/components/ListTable/ListTable'
import PageHeader from '@/components/PageHeader/PageHeader'
import Toast from '@/components/Toast/Toast'
import './ModelConfigs.css'
import '@/pages/System/AdminPages.css'
const { Search, TextArea } = Input
2026-07-24 07:37:22 +00:00
const MAX_TOKENS_OPTIONS = [
{ label: '4K', value: 4096 },
{ label: '8K', value: 8192 },
{ label: '16K', value: 16384 },
{ label: '32K', value: 32768 },
]
const DEFAULT_MAX_TOKENS = 8192
const MODEL_TYPE_META = {
chat: {
label: '对话模型',
icon: <CommentOutlined />,
description: '用于知识库对话、问答生成的大语言模型。',
addText: '新增对话模型',
},
embedding: {
label: '向量模型',
icon: <DeploymentUnitOutlined />,
description: '用于文档向量化ZVec与语义检索的 Embedding 模型。',
addText: '新增向量模型',
},
}
function buildModelCode(provider, llmModelName, modelType) {
2026-04-08 17:03:57 +00:00
const providerPart = (provider || 'custom').trim().toLowerCase()
const modelPart = (llmModelName || '').trim().toLowerCase()
const sanitized = []
let previousSeparator = false
for (const char of modelPart) {
if (/[a-z0-9]/.test(char)) {
sanitized.push(char)
previousSeparator = false
} else if (!previousSeparator) {
sanitized.push('_')
previousSeparator = true
}
}
const suffix = sanitized.join('').replace(/^_+|_+$/g, '') || 'model'
2026-07-24 07:37:22 +00:00
const base = `llm_${providerPart}_${suffix}`
return modelType === 'embedding' ? `emb_${base}` : base
2026-04-08 17:03:57 +00:00
}
function buildModelName(providerMeta, provider, llmModelName) {
const label = providerMeta?.label || provider || '自定义模型'
const modelPart = (llmModelName || '').trim()
if (!modelPart) {
return label
}
return `${label} ${modelPart}`
}
function formatDateTime(value) {
if (!value) return '-'
return new Date(value).toLocaleString('zh-CN')
}
function ModelConfigs() {
const [form] = Form.useForm()
2026-07-24 07:37:22 +00:00
const [activeType, setActiveType] = useState('chat')
const [editingType, setEditingType] = useState('chat')
2026-04-08 17:03:57 +00:00
const [loading, setLoading] = useState(false)
const [submitting, setSubmitting] = useState(false)
const [testing, setTesting] = useState(false)
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(10)
const [total, setTotal] = useState(0)
const [keyword, setKeyword] = useState('')
const [providerFilter, setProviderFilter] = useState(undefined)
const [statusFilter, setStatusFilter] = useState(undefined)
const [configs, setConfigs] = useState([])
const [providerCatalog, setProviderCatalog] = useState([])
const [modalVisible, setModalVisible] = useState(false)
const [editingConfigId, setEditingConfigId] = useState(null)
const [autoFillFlags, setAutoFillFlags] = useState({
endpointUrl: true,
modelName: true,
modelCode: true,
})
const providerValue = Form.useWatch('provider', form)
const llmModelNameValue = Form.useWatch('llm_model_name', form)
2026-07-24 07:37:22 +00:00
const isEmbedding = editingType === 'embedding'
2026-04-08 17:03:57 +00:00
useEffect(() => {
loadProviderCatalog()
}, [])
useEffect(() => {
loadConfigs()
2026-07-24 07:37:22 +00:00
}, [activeType, page, pageSize, keyword, providerFilter, statusFilter])
2026-04-08 17:03:57 +00:00
useEffect(() => {
if (!modalVisible || !providerValue) {
return
}
const providerMeta = providerCatalog.find((item) => item.value === providerValue)
if (!providerMeta) {
return
}
const currentValues = form.getFieldsValue()
const nextValues = {}
if (autoFillFlags.endpointUrl) {
const nextEndpointUrl = providerMeta.default_endpoint_url || ''
if (nextEndpointUrl !== currentValues.endpoint_url) {
nextValues.endpoint_url = nextEndpointUrl
}
}
if (llmModelNameValue) {
if (autoFillFlags.modelName) {
const nextModelName = buildModelName(providerMeta, providerValue, llmModelNameValue)
if (nextModelName !== currentValues.model_name) {
nextValues.model_name = nextModelName
}
}
if (autoFillFlags.modelCode) {
2026-07-24 07:37:22 +00:00
const nextModelCode = buildModelCode(providerValue, llmModelNameValue, editingType)
2026-04-08 17:03:57 +00:00
if (nextModelCode !== currentValues.model_code) {
nextValues.model_code = nextModelCode
}
}
}
if (Object.keys(nextValues).length > 0) {
form.setFieldsValue(nextValues)
}
2026-07-24 07:37:22 +00:00
}, [modalVisible, providerValue, llmModelNameValue, providerCatalog, autoFillFlags, editingType, form])
2026-04-08 17:03:57 +00:00
const loadProviderCatalog = async () => {
try {
const res = await getLLMProviderCatalog()
setProviderCatalog(res.data || [])
} catch (error) {
console.error('Load LLM provider catalog error:', error)
Toast.error('加载提供方列表失败')
}
}
const loadConfigs = async () => {
try {
setLoading(true)
const params = {
page,
page_size: pageSize,
2026-07-24 07:37:22 +00:00
model_type: activeType,
2026-04-08 17:03:57 +00:00
}
if (keyword) params.keyword = keyword
if (providerFilter) params.provider = providerFilter
if (statusFilter !== undefined) params.is_active = statusFilter
const res = await getLLMModelConfigs(params)
setConfigs(res.data || [])
setTotal(res.total || 0)
} catch (error) {
console.error('Load llm model configs error:', error)
Toast.error('加载模型配置失败')
} finally {
setLoading(false)
}
}
const getProviderMeta = (provider) => providerCatalog.find((item) => item.value === provider)
2026-07-24 07:37:22 +00:00
const handleTabChange = (key) => {
setActiveType(key)
setPage(1)
setKeyword('')
setProviderFilter(undefined)
setStatusFilter(undefined)
}
2026-04-08 17:03:57 +00:00
const openCreateModal = () => {
const defaultProvider = providerCatalog[0]?.value || 'openai'
const defaultEndpointUrl = getProviderMeta(defaultProvider)?.default_endpoint_url || ''
setEditingConfigId(null)
2026-07-24 07:37:22 +00:00
setEditingType(activeType)
2026-04-08 17:03:57 +00:00
setAutoFillFlags({
endpointUrl: true,
modelName: true,
modelCode: true,
})
form.setFieldsValue({
2026-07-24 07:37:22 +00:00
model_type: activeType,
2026-04-08 17:03:57 +00:00
provider: defaultProvider,
endpoint_url: defaultEndpointUrl,
2026-07-24 07:37:22 +00:00
llm_timeout: activeType === 'embedding' ? 60 : 120,
2026-04-08 17:03:57 +00:00
llm_temperature: 0.7,
llm_top_p: 0.9,
2026-07-24 07:37:22 +00:00
llm_max_tokens: DEFAULT_MAX_TOKENS,
embedding_dimension: undefined,
2026-04-08 17:03:57 +00:00
is_active: true,
description: '',
llm_system_prompt: '',
model_name: '',
model_code: '',
llm_model_name: '',
api_key: '',
})
setModalVisible(true)
}
const openEditModal = async (record) => {
try {
const res = await getLLMModelConfigDetail(record.config_id)
const detail = res.data
2026-07-24 07:37:22 +00:00
const detailType = detail.model_type || 'chat'
2026-04-08 17:03:57 +00:00
const providerMeta = getProviderMeta(detail.provider)
setEditingConfigId(record.config_id)
2026-07-24 07:37:22 +00:00
setEditingType(detailType)
2026-04-08 17:03:57 +00:00
setAutoFillFlags({
endpointUrl: !detail.endpoint_url || detail.endpoint_url === (providerMeta?.default_endpoint_url || ''),
modelName: !detail.model_name || detail.model_name === buildModelName(providerMeta, detail.provider, detail.llm_model_name),
2026-07-24 07:37:22 +00:00
modelCode: !detail.model_code || detail.model_code === buildModelCode(detail.provider, detail.llm_model_name, detailType),
2026-04-08 17:03:57 +00:00
})
form.setFieldsValue({
...detail,
api_key: detail.api_key || '',
})
setModalVisible(true)
} catch (error) {
console.error('Load llm model config detail error:', error)
Toast.error('加载模型配置详情失败')
}
}
const closeModal = () => {
setModalVisible(false)
setEditingConfigId(null)
form.resetFields()
}
const handleSubmit = async (values) => {
try {
setSubmitting(true)
2026-07-24 07:37:22 +00:00
const payload = { ...values, model_type: editingType }
2026-04-08 17:03:57 +00:00
if (editingConfigId) {
2026-07-24 07:37:22 +00:00
await updateLLMModelConfig(editingConfigId, payload)
2026-04-08 17:03:57 +00:00
Toast.success('模型配置更新成功')
} else {
2026-07-24 07:37:22 +00:00
await createLLMModelConfig(payload)
2026-04-08 17:03:57 +00:00
Toast.success('模型配置创建成功')
}
closeModal()
loadConfigs()
} catch (error) {
Toast.error(error.response?.data?.detail || '保存模型配置失败')
} finally {
setSubmitting(false)
}
}
const handleDelete = async (record) => {
try {
await deleteLLMModelConfig(record.config_id)
Toast.success('模型配置删除成功')
loadConfigs()
} catch (error) {
Toast.error(error.response?.data?.detail || '删除模型配置失败')
}
}
const handleStatusChange = async (record, checked) => {
try {
await updateLLMModelConfigStatus(record.config_id, checked)
Toast.success(checked ? '模型已启用' : '模型已停用')
loadConfigs()
} catch (error) {
Toast.error(error.response?.data?.detail || '更新状态失败')
}
}
const showTestResult = (result) => {
2026-07-24 07:37:22 +00:00
const providerLabel = getProviderMeta(result.provider)?.label || result.provider
const lines = [
`提供方:${providerLabel}`,
`模型:${result.llm_model_name}`,
`延迟:${result.latency_ms} ms`,
]
if (result.dimension) {
lines.push(`向量维度:${result.dimension}`)
}
if (result.preview) {
lines.push(`返回预览:${result.preview}`)
}
const description = (
<div style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
{lines.map((line, index) => (
<div key={index}>{line}</div>
))}
</div>
)
Toast.success('模型测试成功', description, 5)
2026-04-08 17:03:57 +00:00
}
const handleFormTest = async () => {
try {
const values = await form.validateFields()
setTesting(true)
2026-07-24 07:37:22 +00:00
const res = await testLLMModelConfig({ ...values, model_type: editingType })
2026-04-08 17:03:57 +00:00
showTestResult(res.data)
} catch (error) {
2026-07-24 07:37:22 +00:00
// 表单校验未通过:不弹提示,仅高亮表单项
// 接口失败:已由全局请求拦截器统一弹出错误 Toast这里不重复提示
2026-04-08 17:03:57 +00:00
} finally {
setTesting(false)
}
}
2026-07-24 07:37:22 +00:00
const getColumns = (type) => {
const baseColumns = [
{
title: '模型名称',
dataIndex: 'model_name',
key: 'model_name',
width: 240,
render: (_, record) => (
<Space direction="vertical" size={2}>
<span>{record.model_name}</span>
<span className="model-config-code">{record.model_code}</span>
</Space>
),
},
{
title: '提供方',
dataIndex: 'provider',
key: 'provider',
width: 140,
render: (provider) => (
<Tag color="blue">{getProviderMeta(provider)?.label || provider || '-'}</Tag>
),
},
{
title: '模型标识',
dataIndex: 'llm_model_name',
key: 'llm_model_name',
width: 180,
},
]
if (type === 'embedding') {
baseColumns.push({
title: '向量维度',
dataIndex: 'embedding_dimension',
key: 'embedding_dimension',
width: 110,
render: (value) => (value ? <Tag color="purple">{value}</Tag> : <Tag>自动</Tag>),
})
}
baseColumns.push(
{
title: 'Base URL',
dataIndex: 'endpoint_url',
key: 'endpoint_url',
ellipsis: true,
render: (value) => value || '-',
},
{
title: '状态',
dataIndex: 'is_active',
key: 'is_active',
width: 100,
render: (value, record) => (
<Switch
checked={value}
checkedChildren="启用"
unCheckedChildren="停用"
onChange={(checked) => handleStatusChange(record, checked)}
/>
),
},
{
title: '更新时间',
dataIndex: 'updated_at',
key: 'updated_at',
width: 180,
render: (value) => formatDateTime(value),
},
{
title: '操作',
key: 'action',
fixed: 'right',
width: 170,
render: (_, record) => (
<Space size="small" className="model-config-actions">
2026-04-08 17:03:57 +00:00
<Button
type="link"
size="small"
2026-07-24 07:37:22 +00:00
icon={<EditOutlined />}
onClick={() => openEditModal(record)}
2026-04-08 17:03:57 +00:00
>
2026-07-24 07:37:22 +00:00
编辑
2026-04-08 17:03:57 +00:00
</Button>
2026-07-24 07:37:22 +00:00
<Popconfirm
title="确认删除该模型配置?"
description="删除后将无法恢复。"
okText="删除"
cancelText="取消"
onConfirm={() => handleDelete(record)}
>
<Button
type="link"
size="small"
danger
icon={<DeleteOutlined />}
>
删除
</Button>
</Popconfirm>
</Space>
),
},
)
2026-04-08 17:03:57 +00:00
2026-07-24 07:37:22 +00:00
return baseColumns
}
const renderListPanel = (type) => (
<>
<div className="admin-toolbar">
<div className="admin-toolbar-left">
<Search
allowClear
placeholder="搜索模型名称、编码或模型标识"
value={keyword}
style={{ width: 320 }}
onChange={(event) => {
setPage(1)
setKeyword(event.target.value)
}}
onSearch={(value) => {
setPage(1)
setKeyword(value)
}}
/>
<Select
allowClear
placeholder="筛选提供方"
style={{ width: 180 }}
value={providerFilter}
options={providerCatalog.map((item) => ({
label: item.label,
value: item.value,
}))}
onChange={(value) => {
setPage(1)
setProviderFilter(value)
}}
/>
<Select
allowClear
placeholder="筛选状态"
style={{ width: 140 }}
value={statusFilter}
options={[
{ label: '启用', value: true },
{ label: '停用', value: false },
]}
onChange={(value) => {
setPage(1)
setStatusFilter(value)
}}
/>
</div>
<div className="admin-toolbar-right">
2026-04-08 17:03:57 +00:00
<Button
type="primary"
icon={<PlusOutlined />}
onClick={openCreateModal}
>
2026-07-24 07:37:22 +00:00
{MODEL_TYPE_META[type].addText}
2026-04-08 17:03:57 +00:00
</Button>
2026-07-24 07:37:22 +00:00
<Button icon={<ReloadOutlined />} onClick={loadConfigs}>
刷新
</Button>
</div>
</div>
<ListTable
rowKey="config_id"
className="model-config-table"
loading={loading}
columns={getColumns(type)}
dataSource={configs}
scroll={{ x: 1280 }}
pagination={{
current: page,
pageSize,
total,
showSizeChanger: true,
showQuickJumper: true,
onChange: (nextPage, nextPageSize) => {
setPage(nextPage)
setPageSize(nextPageSize)
},
showTotal: (value) => `${value}`,
}}
2026-04-08 17:03:57 +00:00
/>
2026-07-24 07:37:22 +00:00
</>
)
2026-04-08 17:03:57 +00:00
2026-07-24 07:37:22 +00:00
const tabItems = Object.entries(MODEL_TYPE_META).map(([type, meta]) => ({
key: type,
label: (
<span>
{meta.icon}
{meta.label}
</span>
),
children: (
<div className="model-config-tab-panel">
<p className="model-config-tab-desc">{meta.description}</p>
{activeType === type ? renderListPanel(type) : null}
</div>
),
}))
2026-04-08 17:03:57 +00:00
2026-07-24 07:37:22 +00:00
return (
<div className="admin-page">
<PageHeader
title="模型配置"
description="分类管理对话模型与向量模型的提供方、接口地址、参数模板与连通性测试。"
icon={<CloudServerOutlined />}
/>
2026-04-08 17:03:57 +00:00
2026-07-24 07:37:22 +00:00
<Card className="admin-card model-config-card">
<Tabs
items={tabItems}
activeKey={activeType}
onChange={handleTabChange}
tabPosition="left"
className="model-config-tabs"
2026-04-08 17:03:57 +00:00
/>
</Card>
<Modal
2026-07-24 07:37:22 +00:00
title={`${editingConfigId ? '编辑' : '新增'}${MODEL_TYPE_META[editingType].label}`}
2026-04-08 17:03:57 +00:00
open={modalVisible}
width={860}
className="model-config-modal"
style={{ maxWidth: 'calc(100vw - 32px)' }}
destroyOnClose
onCancel={closeModal}
onOk={() => form.submit()}
confirmLoading={submitting}
okText={editingConfigId ? '保存修改' : '创建'}
cancelText="取消"
styles={{
body: {
maxHeight: 'calc(80vh - 180px)',
overflowY: 'auto',
paddingRight: 8,
},
}}
footer={(_, { OkBtn, CancelBtn }) => (
<>
<CancelBtn />
<Button
icon={<ExperimentOutlined />}
loading={testing}
onClick={handleFormTest}
>
测试模型
</Button>
<OkBtn />
</>
)}
>
<div className="model-config-modal-hint">
<strong>自动生成规则</strong> 选择提供方后会自动带出默认 `base_url`填写模型标识后会自动生成模型名称模型编码
如果你手动改过这些字段后续就不会再被自动覆盖
2026-07-24 07:37:22 +00:00
{isEmbedding && (
<>
<br />
<strong>向量维度</strong> 留空时按模型实际返回维度自动建立向量库如填写需与模型输出维度一致更换不同维度的模型会重建该项目的向量库
</>
)}
2026-04-08 17:03:57 +00:00
</div>
<Form
form={form}
layout="vertical"
onFinish={handleSubmit}
initialValues={{
llm_timeout: 120,
llm_temperature: 0.7,
llm_top_p: 0.9,
2026-07-24 07:37:22 +00:00
llm_max_tokens: DEFAULT_MAX_TOKENS,
2026-04-08 17:03:57 +00:00
is_active: true,
}}
>
2026-07-24 07:37:22 +00:00
<Form.Item name="model_type" hidden>
<Input />
</Form.Item>
2026-04-08 17:03:57 +00:00
<Space direction="vertical" style={{ width: '100%' }} size={4}>
<Form.Item
label="提供方"
name="provider"
rules={[{ required: true, message: '请选择模型提供方' }]}
>
<Select
showSearch
placeholder="请选择模型提供方"
optionFilterProp="label"
options={providerCatalog.map((item) => ({
label: item.label,
value: item.value,
}))}
/>
</Form.Item>
<Form.Item
label="Base URL"
name="endpoint_url"
rules={[{ required: true, message: '请输入 base_url' }]}
>
<Input
placeholder="选择提供方后自动带出,也可以手动覆盖"
onChange={() => {
setAutoFillFlags((current) => ({ ...current, endpointUrl: false }))
}}
/>
</Form.Item>
</Space>
<Space style={{ width: '100%' }} size={16} align="start">
<Form.Item
label="模型标识"
name="llm_model_name"
style={{ flex: 1 }}
rules={[{ required: true, message: '请输入模型标识或部署名' }]}
>
2026-07-24 07:37:22 +00:00
<Input
placeholder={
isEmbedding
? '如 text-embedding-3-small / text-embedding-v4 / bge-large-zh'
: '如 gpt-4.1-mini / qwen3.6-plus / claude-3-5-sonnet-latest'
}
/>
2026-04-08 17:03:57 +00:00
</Form.Item>
<Form.Item
label="请求超时(秒)"
name="llm_timeout"
style={{ width: 180 }}
rules={[{ required: true, message: '请输入超时时间' }]}
>
<InputNumber min={5} max={600} style={{ width: '100%' }} />
</Form.Item>
</Space>
<Space style={{ width: '100%' }} size={16} align="start">
<Form.Item
label="模型名称"
name="model_name"
style={{ flex: 1 }}
rules={[{ required: true, message: '请输入模型名称' }]}
>
<Input
placeholder="会根据提供方和模型标识自动生成"
onChange={() => {
setAutoFillFlags((current) => ({ ...current, modelName: false }))
}}
/>
</Form.Item>
<Form.Item
label="模型编码"
name="model_code"
style={{ flex: 1 }}
rules={[{ required: true, message: '请输入模型编码' }]}
>
<Input
placeholder="会自动生成,支持手动调整"
onChange={() => {
setAutoFillFlags((current) => ({ ...current, modelCode: false }))
}}
/>
</Form.Item>
</Space>
<Form.Item label="API Key" name="api_key">
<Input.Password
placeholder="支持留空后稍后补齐;测试非 Ollama 模型时建议填写"
autoComplete="new-password"
/>
</Form.Item>
2026-07-24 07:37:22 +00:00
{isEmbedding ? (
2026-04-08 17:03:57 +00:00
<Form.Item
2026-07-24 07:37:22 +00:00
label="向量维度"
name="embedding_dimension"
extra="留空则按模型实际返回维度自动建立;填写需与模型输出一致。"
2026-04-08 17:03:57 +00:00
>
2026-07-24 07:37:22 +00:00
<InputNumber
min={1}
max={8192}
step={1}
style={{ width: '100%' }}
placeholder="如 1536 / 1024 / 768可留空自动识别"
/>
2026-04-08 17:03:57 +00:00
</Form.Item>
2026-07-24 07:37:22 +00:00
) : (
<>
<Space style={{ width: '100%' }} size={24} align="start">
<Form.Item
label="Temperature"
name="llm_temperature"
style={{ flex: 1 }}
tooltip="数值越高回答越发散,越低越稳定保守"
rules={[{ required: true, message: '请设置 temperature' }]}
>
<Slider
min={0}
max={2}
step={0.05}
marks={{ 0: '0', 0.7: '0.7', 1: '1', 2: '2' }}
tooltip={{ open: undefined }}
/>
</Form.Item>
<Form.Item
label="Top P"
name="llm_top_p"
style={{ flex: 1 }}
tooltip="核采样阈值,控制候选词的累计概率范围"
rules={[{ required: true, message: '请设置 top_p' }]}
>
<Slider
min={0}
max={1}
step={0.05}
marks={{ 0: '0', 0.5: '0.5', 0.9: '0.9', 1: '1' }}
tooltip={{ open: undefined }}
/>
</Form.Item>
</Space>
<Form.Item
label="Max Tokens"
name="llm_max_tokens"
tooltip="单次回复的最大输出长度"
rules={[{ required: true, message: '请选择 max_tokens' }]}
>
<Radio.Group
className="max-tokens-group"
optionType="button"
buttonStyle="solid"
options={MAX_TOKENS_OPTIONS}
/>
</Form.Item>
<Form.Item label="系统提示词" name="llm_system_prompt">
<TextArea
rows={4}
placeholder="可选。对话与测试时会作为 system prompt 一并发送。"
/>
</Form.Item>
</>
)}
2026-04-08 17:03:57 +00:00
<Form.Item label="描述" name="description">
<TextArea rows={2} placeholder="可填写用途、场景、适用业务等说明" />
</Form.Item>
2026-07-24 07:37:22 +00:00
<Form.Item label="启用状态" name="is_active" valuePropName="checked">
<Switch checkedChildren="启用" unCheckedChildren="停用" />
</Form.Item>
2026-04-08 17:03:57 +00:00
</Form>
</Modal>
</div>
)
}
export default ModelConfigs