diff --git a/web/src/components/common/ui/JSONEditor.js b/web/src/components/common/ui/JSONEditor.js index fd4064dd..bc256970 100644 --- a/web/src/components/common/ui/JSONEditor.js +++ b/web/src/components/common/ui/JSONEditor.js @@ -14,6 +14,7 @@ import { TextArea, Row, Col, + Divider, } from '@douyinfe/semi-ui'; import { IconCode, @@ -31,6 +32,7 @@ const JSONEditor = ({ label, placeholder, extraText, + extraFooter, showClear = true, template, templateLabel, @@ -634,8 +636,13 @@ const JSONEditor = ({ {/* 额外文本显示在卡片底部 */} {extraText && ( -
+ {extraText} + + )} + {extraFooter && ( +
+ {extraFooter}
)} diff --git a/web/src/components/table/models/ModelsColumnDefs.js b/web/src/components/table/models/ModelsColumnDefs.js index 48841e60..ef433553 100644 --- a/web/src/components/table/models/ModelsColumnDefs.js +++ b/web/src/components/table/models/ModelsColumnDefs.js @@ -18,13 +18,7 @@ For commercial licensing, please contact support@quantumnous.com */ import React from 'react'; -import { - Button, - Space, - Tag, - Typography, - Modal -} from '@douyinfe/semi-ui'; +import { Button, Space, Tag, Typography, Modal } from '@douyinfe/semi-ui'; import { timestamp2string, getLobeHubIcon, @@ -81,21 +75,39 @@ const renderTags = (text) => { }); }; -// Render endpoints -const renderEndpoints = (text) => { - let arr; +// Render endpoints (supports object map or legacy array) +const renderEndpoints = (value) => { try { - arr = JSON.parse(text); - } catch (_) { } - if (!Array.isArray(arr)) return text || '-'; - return renderLimitedItems({ - items: arr, - renderItem: (ep, idx) => ( - - {ep} - - ), - }); + const parsed = typeof value === 'string' ? JSON.parse(value) : value; + if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { + const keys = Object.keys(parsed || {}); + if (keys.length === 0) return '-'; + return renderLimitedItems({ + items: keys, + renderItem: (key, idx) => ( + + {key} + + ), + maxDisplay: 3, + }); + } + if (Array.isArray(parsed)) { + if (parsed.length === 0) return '-'; + return renderLimitedItems({ + items: parsed, + renderItem: (ep, idx) => ( + + {ep} + + ), + maxDisplay: 3, + }); + } + return value || '-'; + } catch (_) { + return value || '-'; + } }; // Render quota type diff --git a/web/src/components/table/models/ModelsTable.jsx b/web/src/components/table/models/ModelsTable.jsx index 7ced70c5..7c37079a 100644 --- a/web/src/components/table/models/ModelsTable.jsx +++ b/web/src/components/table/models/ModelsTable.jsx @@ -56,13 +56,7 @@ const ModelsTable = (modelsData) => { refresh, vendorMap, }); - }, [ - t, - manageModel, - setEditingModel, - setShowEdit, - refresh, - ]); + }, [t, manageModel, setEditingModel, setShowEdit, refresh, vendorMap]); // Handle compact mode by removing fixed positioning const tableColumns = useMemo(() => { diff --git a/web/src/components/table/models/modals/EditModelModal.jsx b/web/src/components/table/models/modals/EditModelModal.jsx index 8cfc1306..47173c77 100644 --- a/web/src/components/table/models/modals/EditModelModal.jsx +++ b/web/src/components/table/models/modals/EditModelModal.jsx @@ -32,17 +32,20 @@ import { Col, Row, } from '@douyinfe/semi-ui'; -import { - Save, - X, - FileText, -} from 'lucide-react'; +import { Save, X, FileText } from 'lucide-react'; import { API, showError, showSuccess } from '../../../../helpers'; import { useTranslation } from 'react-i18next'; import { useIsMobile } from '../../../../hooks/common/useIsMobile'; const { Text, Title } = Typography; +// Example endpoint template for quick fill +const ENDPOINT_TEMPLATE = { + openai: { path: '/v1/chat/completions', method: 'POST' }, + anthropic: { path: '/v1/messages', method: 'POST' }, + 'image-generation': { path: '/v1/images/generations', method: 'POST' }, +}; + const nameRuleOptions = [ { label: '精确名称匹配', value: 0 }, { label: '前缀名称匹配', value: 1 }, @@ -385,7 +388,37 @@ const EditModelModal = (props) => { onChange={(val) => formApiRef.current?.setValue('endpoints', val)} formApi={formApiRef.current} editorType='object' - extraText={t('留空则使用默认端点;支持 {path, method}')} + template={ENDPOINT_TEMPLATE} + templateLabel={t('填入模板')} + extraText={({t('留空则使用默认端点;支持 {path, method}')})} + extraFooter={endpointGroups.length > 0 && ( + + {endpointGroups.map(group => ( + + ))} + + )} /> diff --git a/web/src/components/table/models/modals/EditPrefillGroupModal.jsx b/web/src/components/table/models/modals/EditPrefillGroupModal.jsx index 6e3a6f20..72791555 100644 --- a/web/src/components/table/models/modals/EditPrefillGroupModal.jsx +++ b/web/src/components/table/models/modals/EditPrefillGroupModal.jsx @@ -43,6 +43,13 @@ import { useIsMobile } from '../../../../hooks/common/useIsMobile'; const { Text, Title } = Typography; +// Example endpoint template for quick fill +const ENDPOINT_TEMPLATE = { + openai: { path: '/v1/chat/completions', method: 'POST' }, + anthropic: { path: '/v1/messages', method: 'POST' }, + 'image-generation': { path: '/v1/images/generations', method: 'POST' }, +}; + const EditPrefillGroupModal = ({ visible, onClose, editingGroup, onSuccess }) => { const { t } = useTranslation(); const isMobile = useIsMobile(); @@ -240,6 +247,8 @@ const EditPrefillGroupModal = ({ visible, onClose, editingGroup, onSuccess }) => onChange={(val) => formRef.current?.setValue('items', val)} editorType='object' placeholder={'{\n "openai": {"path": "/v1/chat/completions", "method": "POST"}\n}'} + template={ENDPOINT_TEMPLATE} + templateLabel={t('填入模板')} extraText={t('键为端点类型,值为路径和方法对象')} /> ) : (