+ handleInputChange('original_password', value)
+ }
+ />
+
{
ModelRequestRateLimitCount: 0,
ModelRequestRateLimitSuccessCount: 1000,
ModelRequestRateLimitDurationMinutes: 1,
+ ModelRequestRateLimitGroup: '',
});
let [loading, setLoading] = useState(false);
@@ -23,10 +24,14 @@ const RateLimitSetting = () => {
if (success) {
let newInputs = {};
data.forEach((item) => {
- if (item.key.endsWith('Enabled')) {
- newInputs[item.key] = item.value === 'true' ? true : false;
- } else {
- newInputs[item.key] = item.value;
+ if (item.key === 'ModelRequestRateLimitGroup') {
+ item.value = JSON.stringify(JSON.parse(item.value), null, 2);
+ }
+
+ if (item.key.endsWith('Enabled')) {
+ newInputs[item.key] = item.value === 'true' ? true : false;
+ } else {
+ newInputs[item.key] = item.value;
}
});
diff --git a/web/src/components/SystemSetting.js b/web/src/components/SystemSetting.js
index 8364657b..53ba675d 100644
--- a/web/src/components/SystemSetting.js
+++ b/web/src/components/SystemSetting.js
@@ -19,7 +19,7 @@ import {
verifyJSON,
} from '../helpers/utils';
import { API } from '../helpers/api';
-import axios from "axios";
+import axios from 'axios';
const SystemSetting = () => {
let [inputs, setInputs] = useState({
@@ -45,6 +45,7 @@ const SystemSetting = () => {
ServerAddress: '',
WorkerUrl: '',
WorkerValidKey: '',
+ WorkerAllowHttpImageRequestEnabled: '',
EpayId: '',
EpayKey: '',
Price: 7.3,
@@ -111,6 +112,7 @@ const SystemSetting = () => {
case 'SMTPSSLEnabled':
case 'LinuxDOOAuthEnabled':
case 'oidc.enabled':
+ case 'WorkerAllowHttpImageRequestEnabled':
item.value = item.value === 'true';
break;
case 'Price':
@@ -206,7 +208,11 @@ const SystemSetting = () => {
let WorkerUrl = removeTrailingSlash(inputs.WorkerUrl);
const options = [
{ key: 'WorkerUrl', value: WorkerUrl },
- ]
+ {
+ key: 'WorkerAllowHttpImageRequestEnabled',
+ value: inputs.WorkerAllowHttpImageRequestEnabled ? 'true' : 'false',
+ },
+ ];
if (inputs.WorkerValidKey !== '' || WorkerUrl === '') {
options.push({ key: 'WorkerValidKey', value: inputs.WorkerValidKey });
}
@@ -302,7 +308,8 @@ const SystemSetting = () => {
const domain = emailToAdd.trim();
// 验证域名格式
- const domainRegex = /^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
+ const domainRegex =
+ /^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
if (!domainRegex.test(domain)) {
showError('邮箱域名格式不正确,请输入有效的域名,如 gmail.com');
return;
@@ -577,6 +584,12 @@ const SystemSetting = () => {
/>
+
+ 允许 HTTP 协议图片请求(适用于自部署代理)
+
@@ -799,7 +812,13 @@ const SystemSetting = () => {
onChange={(value) => setEmailToAdd(value)}
style={{ marginTop: 16 }}
suffix={
-
+
}
onEnterPress={handleAddEmail}
/>
diff --git a/web/src/constants/channel.constants.js b/web/src/constants/channel.constants.js
index fa59bcce..054da535 100644
--- a/web/src/constants/channel.constants.js
+++ b/web/src/constants/channel.constants.js
@@ -118,6 +118,11 @@ export const CHANNEL_OPTIONS = [
{
value: 48,
color: 'blue',
- label: 'xAI'
- }
+ label: 'xAI',
+ },
+ {
+ value: 49,
+ color: 'blue',
+ label: 'Coze',
+ },
];
diff --git a/web/src/helpers/render.js b/web/src/helpers/render.js
index 7b80da6f..5a59356b 100644
--- a/web/src/helpers/render.js
+++ b/web/src/helpers/render.js
@@ -317,6 +317,12 @@ export function renderModelPrice(
image = false,
imageRatio = 1.0,
imageOutputTokens = 0,
+ webSearch = false,
+ webSearchCallCount = 0,
+ webSearchPrice = 0,
+ fileSearch = false,
+ fileSearchCallCount = 0,
+ fileSearchPrice = 0,
) {
if (modelPrice !== -1) {
return i18next.t(
@@ -339,14 +345,17 @@ export function renderModelPrice(
// Calculate effective input tokens (non-cached + cached with ratio applied)
let effectiveInputTokens =
inputTokens - cacheTokens + cacheTokens * cacheRatio;
-// Handle image tokens if present
+ // Handle image tokens if present
if (image && imageOutputTokens > 0) {
- effectiveInputTokens = inputTokens - imageOutputTokens + imageOutputTokens * imageRatio;
+ effectiveInputTokens =
+ inputTokens - imageOutputTokens + imageOutputTokens * imageRatio;
}
let price =
(effectiveInputTokens / 1000000) * inputRatioPrice * groupRatio +
- (completionTokens / 1000000) * completionRatioPrice * groupRatio;
+ (completionTokens / 1000000) * completionRatioPrice * groupRatio +
+ (webSearchCallCount / 1000) * webSearchPrice * groupRatio +
+ (fileSearchCallCount / 1000) * fileSearchPrice * groupRatio;
return (
<>
@@ -391,9 +400,23 @@ export function renderModelPrice(
)}
)}
+ {webSearch && webSearchCallCount > 0 && (
+
+ {i18next.t('Web搜索价格:${{price}} / 1K 次', {
+ price: webSearchPrice,
+ })}
+
+ )}
+ {fileSearch && fileSearchCallCount > 0 && (
+
+ {i18next.t('文件搜索价格:${{price}} / 1K 次', {
+ price: fileSearchPrice,
+ })}
+
+ )}
- {cacheTokens > 0 && !image
+ {cacheTokens > 0 && !image && !webSearch && !fileSearch
? i18next.t(
'输入 {{nonCacheInput}} tokens / 1M tokens * ${{price}} + 缓存 {{cacheInput}} tokens / 1M tokens * ${{cachePrice}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
{
@@ -407,31 +430,82 @@ export function renderModelPrice(
total: price.toFixed(6),
},
)
- : image && imageOutputTokens > 0
- ? i18next.t(
- '输入 {{nonImageInput}} tokens + 图片输入 {{imageInput}} tokens * {{imageRatio}} / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
- {
- nonImageInput: inputTokens - imageOutputTokens,
- imageInput: imageOutputTokens,
- imageRatio: imageRatio,
- price: inputRatioPrice,
- completion: completionTokens,
- compPrice: completionRatioPrice,
- ratio: groupRatio,
- total: price.toFixed(6),
- },
- )
- : i18next.t(
- '输入 {{input}} tokens / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
- {
- input: inputTokens,
- price: inputRatioPrice,
- completion: completionTokens,
- compPrice: completionRatioPrice,
- ratio: groupRatio,
- total: price.toFixed(6),
- },
- )}
+ : image && imageOutputTokens > 0 && !webSearch && !fileSearch
+ ? i18next.t(
+ '输入 {{nonImageInput}} tokens + 图片输入 {{imageInput}} tokens * {{imageRatio}} / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
+ {
+ nonImageInput: inputTokens - imageOutputTokens,
+ imageInput: imageOutputTokens,
+ imageRatio: imageRatio,
+ price: inputRatioPrice,
+ completion: completionTokens,
+ compPrice: completionRatioPrice,
+ ratio: groupRatio,
+ total: price.toFixed(6),
+ },
+ )
+ : webSearch && webSearchCallCount > 0 && !image && !fileSearch
+ ? i18next.t(
+ '输入 {{input}} tokens / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} + Web搜索 {{webSearchCallCount}}次 / 1K 次 * ${{webSearchPrice}} * {{ratio}} = ${{total}}',
+ {
+ input: inputTokens,
+ price: inputRatioPrice,
+ completion: completionTokens,
+ compPrice: completionRatioPrice,
+ ratio: groupRatio,
+ webSearchCallCount,
+ webSearchPrice,
+ total: price.toFixed(6),
+ },
+ )
+ : fileSearch &&
+ fileSearchCallCount > 0 &&
+ !image &&
+ !webSearch
+ ? i18next.t(
+ '输入 {{input}} tokens / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} + 文件搜索 {{fileSearchCallCount}}次 / 1K 次 * ${{fileSearchPrice}} * {{ratio}}= ${{total}}',
+ {
+ input: inputTokens,
+ price: inputRatioPrice,
+ completion: completionTokens,
+ compPrice: completionRatioPrice,
+ ratio: groupRatio,
+ fileSearchCallCount,
+ fileSearchPrice,
+ total: price.toFixed(6),
+ },
+ )
+ : webSearch &&
+ webSearchCallCount > 0 &&
+ fileSearch &&
+ fileSearchCallCount > 0 &&
+ !image
+ ? i18next.t(
+ '输入 {{input}} tokens / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} + Web搜索 {{webSearchCallCount}}次 / 1K 次 * ${{webSearchPrice}} * {{ratio}}+ 文件搜索 {{fileSearchCallCount}}次 / 1K 次 * ${{fileSearchPrice}} * {{ratio}}= ${{total}}',
+ {
+ input: inputTokens,
+ price: inputRatioPrice,
+ completion: completionTokens,
+ compPrice: completionRatioPrice,
+ ratio: groupRatio,
+ webSearchCallCount,
+ webSearchPrice,
+ fileSearchCallCount,
+ fileSearchPrice,
+ total: price.toFixed(6),
+ },
+ )
+ : i18next.t(
+ '输入 {{input}} tokens / 1M tokens * ${{price}} + 输出 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}',
+ {
+ input: inputTokens,
+ price: inputRatioPrice,
+ completion: completionTokens,
+ compPrice: completionRatioPrice,
+ ratio: groupRatio,
+ total: price.toFixed(6),
+ },
+ )}
{i18next.t('仅供参考,以实际扣费为准')}
@@ -448,33 +522,56 @@ export function renderLogContent(
user_group_ratio,
image = false,
imageRatio = 1.0,
- useUserGroupRatio = undefined
+ useUserGroupRatio = undefined,
+ webSearch = false,
+ webSearchCallCount = 0,
+ fileSearch = false,
+ fileSearchCallCount = 0,
) {
- const ratioLabel = useUserGroupRatio ? i18next.t('专属倍率') : i18next.t('分组倍率');
+ const ratioLabel = useUserGroupRatio
+ ? i18next.t('专属倍率')
+ : i18next.t('分组倍率');
const ratio = useUserGroupRatio ? user_group_ratio : groupRatio;
if (modelPrice !== -1) {
return i18next.t('模型价格 ${{price}},{{ratioType}} {{ratio}}', {
price: modelPrice,
ratioType: ratioLabel,
- ratio
+ ratio,
});
} else {
if (image) {
- return i18next.t('模型倍率 {{modelRatio}},输出倍率 {{completionRatio}},图片输入倍率 {{imageRatio}},{{ratioType}} {{ratio}}', {
- modelRatio: modelRatio,
- completionRatio: completionRatio,
- imageRatio: imageRatio,
- ratioType: ratioLabel,
- ratio
- });
+ return i18next.t(
+ '模型倍率 {{modelRatio}},输出倍率 {{completionRatio}},图片输入倍率 {{imageRatio}},{{ratioType}} {{ratio}}',
+ {
+ modelRatio: modelRatio,
+ completionRatio: completionRatio,
+ imageRatio: imageRatio,
+ ratioType: ratioLabel,
+ ratio,
+ },
+ );
+ } else if (webSearch) {
+ return i18next.t(
+ '模型倍率 {{modelRatio}},输出倍率 {{completionRatio}},{{ratioType}} {{ratio}},Web 搜索调用 {{webSearchCallCount}} 次',
+ {
+ modelRatio: modelRatio,
+ completionRatio: completionRatio,
+ ratioType: ratioLabel,
+ ratio,
+ webSearchCallCount,
+ },
+ );
} else {
- return i18next.t('模型倍率 {{modelRatio}},输出倍率 {{completionRatio}},{{ratioType}} {{ratio}}', {
- modelRatio: modelRatio,
- completionRatio: completionRatio,
- ratioType: ratioLabel,
- ratio
- });
+ return i18next.t(
+ '模型倍率 {{modelRatio}},输出倍率 {{completionRatio}},{{ratioType}} {{ratio}}',
+ {
+ modelRatio: modelRatio,
+ completionRatio: completionRatio,
+ ratioType: ratioLabel,
+ ratio,
+ },
+ );
}
}
}
diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json
index e9975f61..916329e7 100644
--- a/web/src/i18n/locales/en.json
+++ b/web/src/i18n/locales/en.json
@@ -493,6 +493,7 @@
"默认": "default",
"图片演示": "Image demo",
"注意,系统请求的时模型名称中的点会被剔除,例如:gpt-4.1会请求为gpt-41,所以在Azure部署的时候,部署模型名称需要手动改为gpt-41": "Note that the dot in the model name requested by the system will be removed, for example: gpt-4.1 will be requested as gpt-41, so when deploying on Azure, the deployment model name needs to be manually changed to gpt-41",
+ "2025年5月10日后添加的渠道,不需要再在部署的时候移除模型名称中的\".\"": "After May 10, 2025, channels added do not need to remove the dot in the model name during deployment",
"模型映射必须是合法的 JSON 格式!": "Model mapping must be in valid JSON format!",
"取消无限额度": "Cancel unlimited quota",
"取消": "Cancel",
@@ -1085,7 +1086,7 @@
"没有账户?": "No account? ",
"请输入 AZURE_OPENAI_ENDPOINT,例如:https://docs-test-001.openai.azure.com": "Please enter AZURE_OPENAI_ENDPOINT, e.g.: https://docs-test-001.openai.azure.com",
"默认 API 版本": "Default API Version",
- "请输入默认 API 版本,例如:2024-12-01-preview": "Please enter default API version, e.g.: 2024-12-01-preview.",
+ "请输入默认 API 版本,例如:2025-04-01-preview": "Please enter default API version, e.g.: 2025-04-01-preview.",
"请为渠道命名": "Please name the channel",
"请选择可以使用该渠道的分组": "Please select groups that can use this channel",
"请在系统设置页面编辑分组倍率以添加新的分组:": "Please edit Group ratios in system settings to add new groups:",
@@ -1373,4 +1374,4 @@
"适用于展示系统功能的场景。": "Suitable for scenarios where the system functions are displayed.",
"可在初始化后修改": "Can be modified after initialization",
"初始化系统": "Initialize system"
-}
+}
\ No newline at end of file
diff --git a/web/src/pages/Channel/EditChannel.js b/web/src/pages/Channel/EditChannel.js
index a793e149..f7fab057 100644
--- a/web/src/pages/Channel/EditChannel.js
+++ b/web/src/pages/Channel/EditChannel.js
@@ -24,7 +24,8 @@ import {
TextArea,
Checkbox,
Banner,
- Modal, ImagePreview
+ Modal,
+ ImagePreview,
} from '@douyinfe/semi-ui';
import { getChannelModels, loadChannelModels } from '../../components/utils.js';
import { IconHelpCircle } from '@douyinfe/semi-icons';
@@ -306,7 +307,7 @@ const EditChannel = (props) => {
fetchModels().then();
fetchGroups().then();
if (isEdit) {
- loadChannel().then(() => { });
+ loadChannel().then(() => {});
} else {
setInputs(originInputs);
let localModels = getChannelModels(inputs.type);
@@ -477,24 +478,26 @@ const EditChannel = (props) => {
type={'warning'}
description={
<>
- {t('注意,系统请求的时模型名称中的点会被剔除,例如:gpt-4.1会请求为gpt-41,所以在Azure部署的时候,部署模型名称需要手动改为gpt-41')}
-
-
{
- setModalImageUrl(
- '/azure_model_name.png',
- );
- setIsModalOpenurl(true)
+ {t(
+ '2025年5月10日后添加的渠道,不需要再在部署的时候移除模型名称中的"."',
+ )}
+ {/*
*/}
+ {/* {*/}
+ {/* setModalImageUrl(*/}
+ {/* '/azure_model_name.png',*/}
+ {/* );*/}
+ {/* setIsModalOpenurl(true)*/}
- }}
- >
- {t('查看示例')}
-
+ {/* }}*/}
+ {/*>*/}
+ {/* {t('查看示例')}*/}
+ {/**/}
>
}
>
@@ -522,7 +525,7 @@ const EditChannel = (props) => {
{
handleInputChange('other', value);
}}
@@ -584,25 +587,35 @@ const EditChannel = (props) => {
value={inputs.name}
autoComplete='new-password'
/>
- {inputs.type !== 3 && inputs.type !== 8 && inputs.type !== 22 && inputs.type !== 36 && inputs.type !== 45 && (
- <>
-
- {t('API地址')}:
-
-
- {
- handleInputChange('base_url', value);
- }}
- value={inputs.base_url}
- autoComplete="new-password"
- />
-
- >
- )}
+ {inputs.type !== 3 &&
+ inputs.type !== 8 &&
+ inputs.type !== 22 &&
+ inputs.type !== 36 &&
+ inputs.type !== 45 && (
+ <>
+
+ {t('API地址')}:
+
+
+ {
+ handleInputChange('base_url', value);
+ }}
+ value={inputs.base_url}
+ autoComplete='new-password'
+ />
+
+ >
+ )}
{t('密钥')}:
@@ -761,10 +774,10 @@ const EditChannel = (props) => {
name='other'
placeholder={t(
'请输入部署地区,例如:us-central1\n支持使用模型映射格式\n' +
- '{\n' +
- ' "default": "us-central1",\n' +
- ' "claude-3-5-sonnet-20240620": "europe-west1"\n' +
- '}',
+ '{\n' +
+ ' "default": "us-central1",\n' +
+ ' "claude-3-5-sonnet-20240620": "europe-west1"\n' +
+ '}',
)}
autosize={{ minRows: 2 }}
onChange={(value) => {
@@ -825,6 +838,22 @@ const EditChannel = (props) => {
/>
>
)}
+ {inputs.type === 49 && (
+ <>
+
+ 智能体ID:
+
+
{
+ handleInputChange('other', value);
+ }}
+ value={inputs.other}
+ autoComplete='new-password'
+ />
+ >
+ )}
{t('模型')}:
diff --git a/web/src/pages/Home/index.js b/web/src/pages/Home/index.js
index 599c7930..84fabf6f 100644
--- a/web/src/pages/Home/index.js
+++ b/web/src/pages/Home/index.js
@@ -158,7 +158,7 @@ const Home = () => {
{t('OIDC 身份验证')}:
- {statusState?.status?.oidc === true
+ {statusState?.status?.oidc_enabled === true
? t('已启用')
: t('未启用')}
diff --git a/web/src/pages/Playground/Playground.js b/web/src/pages/Playground/Playground.js
index e8138c01..08eada17 100644
--- a/web/src/pages/Playground/Playground.js
+++ b/web/src/pages/Playground/Playground.js
@@ -64,8 +64,9 @@ const Playground = () => {
},
];
+ const defaultModel = 'gpt-4o-mini';
const [inputs, setInputs] = useState({
- model: 'gpt-4o-mini',
+ model: defaultModel,
group: '',
max_tokens: 0,
temperature: 0,
@@ -108,6 +109,11 @@ const Playground = () => {
value: model,
}));
setModels(localModelOptions);
+ // if default model is not in the list, set the first one as default
+ const hasDefault = localModelOptions.some(option => option.value === defaultModel);
+ if (!hasDefault && localModelOptions.length > 0) {
+ setInputs((inputs) => ({ ...inputs, model: localModelOptions[0].value }));
+ }
} else {
showError(t(message));
}
diff --git a/web/src/pages/Setting/Model/SettingGeminiModel.js b/web/src/pages/Setting/Model/SettingGeminiModel.js
index 6f6da279..b802af1a 100644
--- a/web/src/pages/Setting/Model/SettingGeminiModel.js
+++ b/web/src/pages/Setting/Model/SettingGeminiModel.js
@@ -27,40 +27,48 @@ export default function SettingGeminiModel(props) {
const [inputs, setInputs] = useState({
'gemini.safety_settings': '',
'gemini.version_settings': '',
- 'gemini.supported_imagine_models': [],
+ 'gemini.supported_imagine_models': '',
'gemini.thinking_adapter_enabled': false,
'gemini.thinking_adapter_budget_tokens_percentage': 0.6,
});
const refForm = useRef();
const [inputsRow, setInputsRow] = useState(inputs);
- function onSubmit() {
- const updateArray = compareObjects(inputs, inputsRow);
- if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
- const requestQueue = updateArray.map((item) => {
- let value = String(inputs[item.key]);
- return API.put('/api/option/', {
- key: item.key,
- value,
- });
- });
- setLoading(true);
- Promise.all(requestQueue)
- .then((res) => {
- if (requestQueue.length === 1) {
- if (res.includes(undefined)) return;
- } else if (requestQueue.length > 1) {
- if (res.includes(undefined))
- return showError(t('部分保存失败,请重试'));
- }
- showSuccess(t('保存成功'));
- props.refresh();
+ async function onSubmit() {
+ await refForm.current
+ .validate()
+ .then(() => {
+ const updateArray = compareObjects(inputs, inputsRow);
+ if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
+ const requestQueue = updateArray.map((item) => {
+ let value = String(inputs[item.key]);
+ return API.put('/api/option/', {
+ key: item.key,
+ value,
+ });
+ });
+ setLoading(true);
+ Promise.all(requestQueue)
+ .then((res) => {
+ if (requestQueue.length === 1) {
+ if (res.includes(undefined)) return;
+ } else if (requestQueue.length > 1) {
+ if (res.includes(undefined))
+ return showError(t('部分保存失败,请重试'));
+ }
+ showSuccess(t('保存成功'));
+ props.refresh();
+ })
+ .catch(() => {
+ showError(t('保存失败,请重试'));
+ })
+ .finally(() => {
+ setLoading(false);
+ });
})
- .catch(() => {
- showError(t('保存失败,请重试'));
- })
- .finally(() => {
- setLoading(false);
+ .catch((error) => {
+ console.error('Validation failed:', error);
+ showError(t('请检查输入'));
});
}
@@ -146,6 +154,14 @@ export default function SettingGeminiModel(props) {
label={t('支持的图像模型')}
placeholder={t('例如:') + '\n' + JSON.stringify(['gemini-2.0-flash-exp-image-generation'], null, 2)}
onChange={(value) => setInputs({ ...inputs, 'gemini.supported_imagine_models': value })}
+ trigger='blur'
+ stopValidateWithError
+ rules={[
+ {
+ validator: (rule, value) => verifyJSON(value),
+ message: t('不是合法的 JSON 字符串'),
+ },
+ ]}
/>
diff --git a/web/src/pages/Setting/RateLimit/SettingsRequestRateLimit.js b/web/src/pages/Setting/RateLimit/SettingsRequestRateLimit.js
index 800e9636..73626351 100644
--- a/web/src/pages/Setting/RateLimit/SettingsRequestRateLimit.js
+++ b/web/src/pages/Setting/RateLimit/SettingsRequestRateLimit.js
@@ -6,6 +6,7 @@ import {
showError,
showSuccess,
showWarning,
+ verifyJSON,
} from '../../../helpers';
import { useTranslation } from 'react-i18next';
@@ -18,6 +19,7 @@ export default function RequestRateLimit(props) {
ModelRequestRateLimitCount: -1,
ModelRequestRateLimitSuccessCount: 1000,
ModelRequestRateLimitDurationMinutes: 1,
+ ModelRequestRateLimitGroup: '',
});
const refForm = useRef();
const [inputsRow, setInputsRow] = useState(inputs);
@@ -46,6 +48,13 @@ export default function RequestRateLimit(props) {
if (res.includes(undefined))
return showError(t('部分保存失败,请重试'));
}
+
+ for (let i = 0; i < res.length; i++) {
+ if (!res[i].data.success) {
+ return showError(res[i].data.message);
+ }
+ }
+
showSuccess(t('保存成功'));
props.refresh();
})
@@ -147,6 +156,41 @@ export default function RequestRateLimit(props) {
/>
+
+
+ verifyJSON(value),
+ message: t('不是合法的 JSON 字符串'),
+ },
+ ]}
+ extraText={
+
+
{t('说明:')}
+
+ - {t('使用 JSON 对象格式,格式为:{"组名": [最多请求次数, 最多请求完成次数]}')}
+ - {t('示例:{"default": [200, 100], "vip": [0, 1000]}。')}
+ - {t('[最多请求次数]必须大于等于0,[最多请求完成次数]必须大于等于1。')}
+ - {t('分组速率配置优先级高于全局速率限制。')}
+ - {t('限制周期统一使用上方配置的“限制周期”值。')}
+
+
+ }
+ onChange={(value) => {
+ setInputs({ ...inputs, ModelRequestRateLimitGroup: value });
+ }}
+ />
+
+