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('键为端点类型,值为路径和方法对象')}
/>
) : (