Merge branch 'main' into ui
This commit is contained in:
@@ -618,7 +618,6 @@ const LogsTable = () => {
|
||||
</Paragraph>
|
||||
);
|
||||
}
|
||||
|
||||
let content = other?.claude
|
||||
? renderClaudeModelPriceSimple(
|
||||
other.model_ratio,
|
||||
@@ -935,6 +934,13 @@ const LogsTable = () => {
|
||||
other.model_price,
|
||||
other.group_ratio,
|
||||
other?.user_group_ratio,
|
||||
false,
|
||||
1.0,
|
||||
undefined,
|
||||
other.web_search || false,
|
||||
other.web_search_call_count || 0,
|
||||
other.file_search || false,
|
||||
other.file_search_call_count || 0,
|
||||
),
|
||||
});
|
||||
}
|
||||
@@ -995,6 +1001,12 @@ const LogsTable = () => {
|
||||
other?.image || false,
|
||||
other?.image_ratio || 0,
|
||||
other?.image_output || 0,
|
||||
other?.web_search || false,
|
||||
other?.web_search_call_count || 0,
|
||||
other?.web_search_price || 0,
|
||||
other?.file_search || false,
|
||||
other?.file_search_call_count || 0,
|
||||
other?.file_search_price || 0,
|
||||
);
|
||||
}
|
||||
expandDataLocal.push({
|
||||
|
||||
@@ -57,6 +57,7 @@ const PersonalSetting = () => {
|
||||
email_verification_code: '',
|
||||
email: '',
|
||||
self_account_deletion_confirmation: '',
|
||||
original_password: '',
|
||||
set_new_password: '',
|
||||
set_new_password_confirmation: '',
|
||||
});
|
||||
@@ -239,11 +240,24 @@ const PersonalSetting = () => {
|
||||
};
|
||||
|
||||
const changePassword = async () => {
|
||||
if (inputs.original_password === '') {
|
||||
showError(t('请输入原密码!'));
|
||||
return;
|
||||
}
|
||||
if (inputs.set_new_password === '') {
|
||||
showError(t('请输入新密码!'));
|
||||
return;
|
||||
}
|
||||
if (inputs.original_password === inputs.set_new_password) {
|
||||
showError(t('新密码需要和原密码不一致!'));
|
||||
return;
|
||||
}
|
||||
if (inputs.set_new_password !== inputs.set_new_password_confirmation) {
|
||||
showError(t('两次输入的密码不一致!'));
|
||||
return;
|
||||
}
|
||||
const res = await API.put(`/api/user/self`, {
|
||||
original_password: inputs.original_password,
|
||||
password: inputs.set_new_password,
|
||||
});
|
||||
const { success, message } = res.data;
|
||||
@@ -816,8 +830,8 @@ const PersonalSetting = () => {
|
||||
</div>
|
||||
</Card>
|
||||
<Card style={{ marginTop: 10 }}>
|
||||
<Tabs type="line" defaultActiveKey="notification">
|
||||
<TabPane tab={t('通知设置')} itemKey="notification">
|
||||
<Tabs type='line' defaultActiveKey='notification'>
|
||||
<TabPane tab={t('通知设置')} itemKey='notification'>
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<Typography.Text strong>{t('通知方式')}</Typography.Text>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
@@ -993,23 +1007,36 @@ const PersonalSetting = () => {
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</TabPane>
|
||||
<TabPane tab={t('价格设置')} itemKey="price">
|
||||
<TabPane tab={t('价格设置')} itemKey='price'>
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<Typography.Text strong>{t('接受未设置价格模型')}</Typography.Text>
|
||||
<Typography.Text strong>
|
||||
{t('接受未设置价格模型')}
|
||||
</Typography.Text>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Checkbox
|
||||
checked={notificationSettings.acceptUnsetModelRatioModel}
|
||||
onChange={e => handleNotificationSettingChange('acceptUnsetModelRatioModel', e.target.checked)}
|
||||
checked={
|
||||
notificationSettings.acceptUnsetModelRatioModel
|
||||
}
|
||||
onChange={(e) =>
|
||||
handleNotificationSettingChange(
|
||||
'acceptUnsetModelRatioModel',
|
||||
e.target.checked,
|
||||
)
|
||||
}
|
||||
>
|
||||
{t('接受未设置价格模型')}
|
||||
</Checkbox>
|
||||
<Typography.Text type="secondary" style={{ marginTop: 8, display: 'block' }}>
|
||||
{t('当模型没有设置价格时仍接受调用,仅当您信任该网站时使用,可能会产生高额费用')}
|
||||
<Typography.Text
|
||||
type='secondary'
|
||||
style={{ marginTop: 8, display: 'block' }}
|
||||
>
|
||||
{t(
|
||||
'当模型没有设置价格时仍接受调用,仅当您信任该网站时使用,可能会产生高额费用',
|
||||
)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
</TabPane>
|
||||
|
||||
</Tabs>
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<Button type='primary' onClick={saveNotificationSettings}>
|
||||
@@ -1118,6 +1145,16 @@ const PersonalSetting = () => {
|
||||
>
|
||||
<div style={{ marginTop: 20 }}>
|
||||
<Input
|
||||
name='original_password'
|
||||
placeholder={t('原密码')}
|
||||
type='password'
|
||||
value={inputs.original_password}
|
||||
onChange={(value) =>
|
||||
handleInputChange('original_password', value)
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
style={{ marginTop: 20 }}
|
||||
name='set_new_password'
|
||||
placeholder={t('新密码')}
|
||||
value={inputs.set_new_password}
|
||||
|
||||
@@ -13,6 +13,7 @@ const RateLimitSetting = () => {
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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 = () => {
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Form.Checkbox
|
||||
field='WorkerAllowHttpImageRequestEnabled'
|
||||
noLabel
|
||||
>
|
||||
允许 HTTP 协议图片请求(适用于自部署代理)
|
||||
</Form.Checkbox>
|
||||
<Button onClick={submitWorker}>更新Worker设置</Button>
|
||||
</Form.Section>
|
||||
</Card>
|
||||
@@ -799,7 +812,13 @@ const SystemSetting = () => {
|
||||
onChange={(value) => setEmailToAdd(value)}
|
||||
style={{ marginTop: 16 }}
|
||||
suffix={
|
||||
<Button theme="solid" type="primary" onClick={handleAddEmail}>添加</Button>
|
||||
<Button
|
||||
theme='solid'
|
||||
type='primary'
|
||||
onClick={handleAddEmail}
|
||||
>
|
||||
添加
|
||||
</Button>
|
||||
}
|
||||
onEnterPress={handleAddEmail}
|
||||
/>
|
||||
|
||||
@@ -118,6 +118,11 @@ export const CHANNEL_OPTIONS = [
|
||||
{
|
||||
value: 48,
|
||||
color: 'blue',
|
||||
label: 'xAI'
|
||||
}
|
||||
label: 'xAI',
|
||||
},
|
||||
{
|
||||
value: 49,
|
||||
color: 'blue',
|
||||
label: 'Coze',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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(
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
{webSearch && webSearchCallCount > 0 && (
|
||||
<p>
|
||||
{i18next.t('Web搜索价格:${{price}} / 1K 次', {
|
||||
price: webSearchPrice,
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
{fileSearch && fileSearchCallCount > 0 && (
|
||||
<p>
|
||||
{i18next.t('文件搜索价格:${{price}} / 1K 次', {
|
||||
price: fileSearchPrice,
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
<p></p>
|
||||
<p>
|
||||
{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),
|
||||
},
|
||||
)}
|
||||
</p>
|
||||
<p>{i18next.t('仅供参考,以实际扣费为准')}</p>
|
||||
</article>
|
||||
@@ -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,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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')}
|
||||
<br />
|
||||
<Typography.Text
|
||||
style={{
|
||||
color: 'rgba(var(--semi-blue-5), 1)',
|
||||
userSelect: 'none',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => {
|
||||
setModalImageUrl(
|
||||
'/azure_model_name.png',
|
||||
);
|
||||
setIsModalOpenurl(true)
|
||||
{t(
|
||||
'2025年5月10日后添加的渠道,不需要再在部署的时候移除模型名称中的"."',
|
||||
)}
|
||||
{/*<br />*/}
|
||||
{/*<Typography.Text*/}
|
||||
{/* style={{*/}
|
||||
{/* color: 'rgba(var(--semi-blue-5), 1)',*/}
|
||||
{/* userSelect: 'none',*/}
|
||||
{/* cursor: 'pointer',*/}
|
||||
{/* }}*/}
|
||||
{/* onClick={() => {*/}
|
||||
{/* setModalImageUrl(*/}
|
||||
{/* '/azure_model_name.png',*/}
|
||||
{/* );*/}
|
||||
{/* setIsModalOpenurl(true)*/}
|
||||
|
||||
}}
|
||||
>
|
||||
{t('查看示例')}
|
||||
</Typography.Text>
|
||||
{/* }}*/}
|
||||
{/*>*/}
|
||||
{/* {t('查看示例')}*/}
|
||||
{/*</Typography.Text>*/}
|
||||
</>
|
||||
}
|
||||
></Banner>
|
||||
@@ -522,7 +525,7 @@ const EditChannel = (props) => {
|
||||
<Input
|
||||
label={t('默认 API 版本')}
|
||||
name='azure_other'
|
||||
placeholder={t('请输入默认 API 版本,例如:2024-12-01-preview')}
|
||||
placeholder={t('请输入默认 API 版本,例如:2025-04-01-preview')}
|
||||
onChange={(value) => {
|
||||
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 && (
|
||||
<>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>{t('API地址')}:</Typography.Text>
|
||||
</div>
|
||||
<Tooltip content={t('对于官方渠道,new-api已经内置地址,除非是第三方代理站点或者Azure的特殊接入地址,否则不需要填写')}>
|
||||
<Input
|
||||
label={t('API地址')}
|
||||
name="base_url"
|
||||
placeholder={t('此项可选,用于通过自定义API地址来进行 API 调用,末尾不要带/v1和/')}
|
||||
onChange={(value) => {
|
||||
handleInputChange('base_url', value);
|
||||
}}
|
||||
value={inputs.base_url}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
{inputs.type !== 3 &&
|
||||
inputs.type !== 8 &&
|
||||
inputs.type !== 22 &&
|
||||
inputs.type !== 36 &&
|
||||
inputs.type !== 45 && (
|
||||
<>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>{t('API地址')}:</Typography.Text>
|
||||
</div>
|
||||
<Tooltip
|
||||
content={t(
|
||||
'对于官方渠道,new-api已经内置地址,除非是第三方代理站点或者Azure的特殊接入地址,否则不需要填写',
|
||||
)}
|
||||
>
|
||||
<Input
|
||||
label={t('API地址')}
|
||||
name='base_url'
|
||||
placeholder={t(
|
||||
'此项可选,用于通过自定义API地址来进行 API 调用,末尾不要带/v1和/',
|
||||
)}
|
||||
onChange={(value) => {
|
||||
handleInputChange('base_url', value);
|
||||
}}
|
||||
value={inputs.base_url}
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>{t('密钥')}:</Typography.Text>
|
||||
</div>
|
||||
@@ -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 && (
|
||||
<>
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>智能体ID:</Typography.Text>
|
||||
</div>
|
||||
<Input
|
||||
name='other'
|
||||
placeholder={'请输入智能体ID,例如:7342866812345'}
|
||||
onChange={(value) => {
|
||||
handleInputChange('other', value);
|
||||
}}
|
||||
value={inputs.other}
|
||||
autoComplete='new-password'
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<Typography.Text strong>{t('模型')}:</Typography.Text>
|
||||
</div>
|
||||
|
||||
@@ -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) {
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col xs={24} sm={16}>
|
||||
<Form.TextArea
|
||||
label={t('分组速率限制')}
|
||||
placeholder={t(
|
||||
'{\n "default": [200, 100],\n "vip": [0, 1000]\n}',
|
||||
)}
|
||||
field={'ModelRequestRateLimitGroup'}
|
||||
autosize={{ minRows: 5, maxRows: 15 }}
|
||||
trigger='blur'
|
||||
stopValidateWithError
|
||||
rules={[
|
||||
{
|
||||
validator: (rule, value) => verifyJSON(value),
|
||||
message: t('不是合法的 JSON 字符串'),
|
||||
},
|
||||
]}
|
||||
extraText={
|
||||
<div>
|
||||
<p style={{ marginBottom: -15 }}>{t('说明:')}</p>
|
||||
<ul>
|
||||
<li>{t('使用 JSON 对象格式,格式为:{"组名": [最多请求次数, 最多请求完成次数]}')}</li>
|
||||
<li>{t('示例:{"default": [200, 100], "vip": [0, 1000]}。')}</li>
|
||||
<li>{t('[最多请求次数]必须大于等于0,[最多请求完成次数]必须大于等于1。')}</li>
|
||||
<li>{t('分组速率配置优先级高于全局速率限制。')}</li>
|
||||
<li>{t('限制周期统一使用上方配置的“限制周期”值。')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
onChange={(value) => {
|
||||
setInputs({ ...inputs, ModelRequestRateLimitGroup: value });
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Button size='default' onClick={onSubmit}>
|
||||
{t('保存模型速率限制')}
|
||||
|
||||
Reference in New Issue
Block a user