diff --git a/web/src/components/OperationSetting.js b/web/src/components/OperationSetting.js
index d7d69f71..eebff04f 100644
--- a/web/src/components/OperationSetting.js
+++ b/web/src/components/OperationSetting.js
@@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react';
-import { Card, Spin } from '@douyinfe/semi-ui';
+import { Card, Spin, Tabs } from '@douyinfe/semi-ui';
import SettingsGeneral from '../pages/Setting/Operation/SettingsGeneral.js';
import SettingsDrawing from '../pages/Setting/Operation/SettingsDrawing.js';
import SettingsSensitiveWords from '../pages/Setting/Operation/SettingsSensitiveWords.js';
@@ -9,11 +9,16 @@ import SettingsMonitoring from '../pages/Setting/Operation/SettingsMonitoring.js
import SettingsCreditLimit from '../pages/Setting/Operation/SettingsCreditLimit.js';
import SettingsMagnification from '../pages/Setting/Operation/SettingsMagnification.js';
import ModelSettingsVisualEditor from '../pages/Setting/Operation/ModelSettingsVisualEditor.js';
+import GroupRatioSettings from '../pages/Setting/Operation/GroupRatioSettings.js';
+import ModelRatioSettings from '../pages/Setting/Operation/ModelRatioSettings.js';
+
import { API, showError, showSuccess } from '../helpers';
import SettingsChats from '../pages/Setting/Operation/SettingsChats.js';
+import { useTranslation } from 'react-i18next';
const OperationSetting = () => {
+ const { t } = useTranslation();
let [inputs, setInputs] = useState({
QuotaForNewUser: 0,
QuotaForInviter: 0,
@@ -138,13 +143,20 @@ const OperationSetting = () => {
- {/* 倍率设置 */}
+ {/* 分组倍率设置 */}
-
+
- {/*可视化倍率设置*/}
+ {/* 合并模型倍率设置和可视化倍率设置 */}
-
+
+
+
+
+
+
+
+
>
diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json
index 32da3670..51b2c510 100644
--- a/web/src/i18n/locales/en.json
+++ b/web/src/i18n/locales/en.json
@@ -1161,7 +1161,7 @@
"默认折叠侧边栏": "Default collapse sidebar",
"聊天链接功能已经弃用,请使用下方聊天设置功能": "Chat link function has been deprecated, please use the chat settings below",
"你似乎并没有修改什么": "You seem to have not modified anything",
- "令牌聊天设置": "Token chat settings",
+ "令牌聊天设置": "Chat settings",
"必须将上方聊天链接全部设置为空,才能使用下方聊天设置功能": "Must set all chat links above to empty to use the chat settings below",
"链接中的{key}将自动替换为sk-xxxx,{address}将自动替换为系统设置的服务器地址,末尾不带/和/v1": "The {key} in the link will be automatically replaced with sk-xxxx, the {address} will be automatically replaced with the server address in system settings, and the end will not have / and /v1",
"聊天配置": "Chat configuration",
@@ -1217,5 +1217,19 @@
"确定要修改所有子渠道权重为 ": "Confirm to modify all sub-channel weights to ",
" 吗?": "?",
"修改子渠道优先级": "Modify sub-channel priority",
- "确定要修改所有子渠道优先级为 ": "Confirm to modify all sub-channel priorities to "
+ "确定要修改所有子渠道优先级为 ": "Confirm to modify all sub-channel priorities to ",
+ "分组设置": "Group settings",
+ "用户可选分组": "User selectable groups",
+ "保存分组倍率设置": "Save group ratio settings",
+ "模型倍率设置": "Model ratio settings",
+ "可视化倍率设置": "Visual model ratio settings",
+ "确定重置模型倍率吗?": "Confirm to reset model ratio?",
+ "模型固定价格": "Model price per call",
+ "模型补全倍率(仅对自定义模型有效)": "Model completion ratio (only effective for custom models)",
+ "保存模型倍率设置": "Save model ratio settings",
+ "重置模型倍率": "Reset model ratio",
+ "一次调用消耗多少刀,优先级大于模型倍率": "How much USD one call costs, priority over model ratio",
+ "仅对自定义模型有效": "Only effective for custom models",
+ "添加模型": "Add model",
+ "应用更改": "Apply changes"
}
\ No newline at end of file
diff --git a/web/src/pages/Setting/Operation/GroupRatioSettings.js b/web/src/pages/Setting/Operation/GroupRatioSettings.js
new file mode 100644
index 00000000..b3258b28
--- /dev/null
+++ b/web/src/pages/Setting/Operation/GroupRatioSettings.js
@@ -0,0 +1,131 @@
+import React, { useEffect, useState, useRef } from 'react';
+import { Button, Col, Form, Row, Spin } from '@douyinfe/semi-ui';
+import {
+ compareObjects,
+ API,
+ showError,
+ showSuccess,
+ showWarning,
+ verifyJSON,
+} from '../../../helpers';
+import { useTranslation } from 'react-i18next';
+
+export default function GroupRatioSettings(props) {
+ const { t } = useTranslation();
+ const [loading, setLoading] = useState(false);
+ const [inputs, setInputs] = useState({
+ GroupRatio: '',
+ UserUsableGroups: ''
+ });
+ const refForm = useRef();
+ const [inputsRow, setInputsRow] = useState(inputs);
+
+ async function onSubmit() {
+ try {
+ await refForm.current.validate().then(() => {
+ const updateArray = compareObjects(inputs, inputsRow);
+ if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
+
+ const requestQueue = updateArray.map((item) => {
+ const value = typeof inputs[item.key] === 'boolean'
+ ? String(inputs[item.key])
+ : inputs[item.key];
+ return API.put('/api/option/', { key: item.key, value });
+ });
+
+ setLoading(true);
+ Promise.all(requestQueue)
+ .then((res) => {
+ if (res.includes(undefined)) {
+ return showError(requestQueue.length > 1 ? t('部分保存失败,请重试') : t('保存失败'));
+ }
+
+ for (let i = 0; i < res.length; i++) {
+ if (!res[i].data.success) {
+ return showError(res[i].data.message);
+ }
+ }
+
+ showSuccess(t('保存成功'));
+ props.refresh();
+ })
+ .catch(error => {
+ console.error('Unexpected error:', error);
+ showError(t('保存失败,请重试'));
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ }).catch(() => {
+ showError(t('请检查输入'));
+ });
+ } catch (error) {
+ showError(t('请检查输入'));
+ console.error(error);
+ }
+ }
+
+ useEffect(() => {
+ const currentInputs = {};
+ for (let key in props.options) {
+ if (Object.keys(inputs).includes(key)) {
+ currentInputs[key] = props.options[key];
+ }
+ }
+ setInputs(currentInputs);
+ setInputsRow(structuredClone(currentInputs));
+ refForm.current.setValues(currentInputs);
+ }, [props.options]);
+
+ return (
+
+
+
+
+ verifyJSON(value),
+ message: t('不是合法的 JSON 字符串')
+ }
+ ]}
+ onChange={(value) => setInputs({ ...inputs, GroupRatio: value })}
+ />
+
+
+
+
+ verifyJSON(value),
+ message: t('不是合法的 JSON 字符串')
+ }
+ ]}
+ onChange={(value) => setInputs({ ...inputs, UserUsableGroups: value })}
+ />
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/web/src/pages/Setting/Operation/ModelRatioSettings.js b/web/src/pages/Setting/Operation/ModelRatioSettings.js
new file mode 100644
index 00000000..48981d6a
--- /dev/null
+++ b/web/src/pages/Setting/Operation/ModelRatioSettings.js
@@ -0,0 +1,178 @@
+import React, { useEffect, useState, useRef } from 'react';
+import { Button, Col, Form, Popconfirm, Row, Space, Spin } from '@douyinfe/semi-ui';
+import {
+ compareObjects,
+ API,
+ showError,
+ showSuccess,
+ showWarning,
+ verifyJSON,
+} from '../../../helpers';
+import { useTranslation } from 'react-i18next';
+
+export default function ModelRatioSettings(props) {
+ const [loading, setLoading] = useState(false);
+ const [inputs, setInputs] = useState({
+ ModelPrice: '',
+ ModelRatio: '',
+ CompletionRatio: '',
+ });
+ const refForm = useRef();
+ const [inputsRow, setInputsRow] = useState(inputs);
+ const { t } = useTranslation();
+
+ async function onSubmit() {
+ try {
+ await refForm.current.validate().then(() => {
+ const updateArray = compareObjects(inputs, inputsRow);
+ if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
+
+ const requestQueue = updateArray.map((item) => {
+ const value = typeof inputs[item.key] === 'boolean'
+ ? String(inputs[item.key])
+ : inputs[item.key];
+ return API.put('/api/option/', { key: item.key, value });
+ });
+
+ setLoading(true);
+ Promise.all(requestQueue)
+ .then((res) => {
+ if (res.includes(undefined)) {
+ return showError(requestQueue.length > 1 ? t('部分保存失败,请重试') : t('保存失败'));
+ }
+
+ for (let i = 0; i < res.length; i++) {
+ if (!res[i].data.success) {
+ return showError(res[i].data.message);
+ }
+ }
+
+ showSuccess(t('保存成功'));
+ props.refresh();
+ })
+ .catch(error => {
+ console.error('Unexpected error:', error);
+ showError(t('保存失败,请重试'));
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ }).catch(() => {
+ showError(t('请检查输入'));
+ });
+ } catch (error) {
+ showError(t('请检查输入'));
+ console.error(error);
+ }
+ }
+
+ async function resetModelRatio() {
+ try {
+ let res = await API.post(`/api/option/rest_model_ratio`);
+ if (res.data.success) {
+ showSuccess(res.data.message);
+ props.refresh();
+ } else {
+ showError(res.data.message);
+ }
+ } catch (error) {
+ showError(error);
+ }
+ }
+
+ useEffect(() => {
+ const currentInputs = {};
+ for (let key in props.options) {
+ if (Object.keys(inputs).includes(key)) {
+ currentInputs[key] = props.options[key];
+ }
+ }
+ setInputs(currentInputs);
+ setInputsRow(structuredClone(currentInputs));
+ refForm.current.setValues(currentInputs);
+ }, [props.options]);
+
+ return (
+
+
+
+
+ verifyJSON(value),
+ message: '不是合法的 JSON 字符串'
+ }
+ ]}
+ onChange={(value) => setInputs({ ...inputs, ModelPrice: value })}
+ />
+
+
+
+
+ verifyJSON(value),
+ message: '不是合法的 JSON 字符串'
+ }
+ ]}
+ onChange={(value) => setInputs({ ...inputs, ModelRatio: value })}
+ />
+
+
+
+
+ verifyJSON(value),
+ message: '不是合法的 JSON 字符串'
+ }
+ ]}
+ onChange={(value) => setInputs({ ...inputs, CompletionRatio: value })}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/web/src/pages/Setting/Operation/ModelSettingsVisualEditor.js b/web/src/pages/Setting/Operation/ModelSettingsVisualEditor.js
index 6158b17e..c5ad9737 100644
--- a/web/src/pages/Setting/Operation/ModelSettingsVisualEditor.js
+++ b/web/src/pages/Setting/Operation/ModelSettingsVisualEditor.js
@@ -4,7 +4,10 @@ import { Table, Button, Input, Modal, Form, Space } from '@douyinfe/semi-ui';
import { IconDelete, IconPlus, IconSearch, IconSave } from '@douyinfe/semi-icons';
import { showError, showSuccess } from '../../../helpers';
import { API } from '../../../helpers';
+import { useTranslation } from 'react-i18next';
+
export default function ModelSettingsVisualEditor(props) {
+ const { t } = useTranslation();
const [models, setModels] = useState([]);
const [visible, setVisible] = useState(false);
const [currentModel, setCurrentModel] = useState(null);
@@ -122,51 +125,50 @@ export default function ModelSettingsVisualEditor(props) {
const columns = [
{
- title: '模型名称',
+ title: t('模型名称'),
dataIndex: 'name',
key: 'name',
},
{
- title: '固定价格',
+ title: t('模型固定价格'),
dataIndex: 'price',
key: 'price',
render: (text, record) => (
updateModel(record.name, 'price', value)}
/>
)
},
{
- title: '模型倍率',
+ title: t('模型倍率'),
dataIndex: 'ratio',
key: 'ratio',
render: (text, record) => (
updateModel(record.name, 'ratio', value)}
/>
)
},
{
- title: '补全倍率',
+ title: t('补全倍率'),
dataIndex: 'completionRatio',
key: 'completionRatio',
render: (text, record) => (
updateModel(record.name, 'completionRatio', value)}
/>
)
},
{
- title: '操作',
+ title: t('操作'),
key: 'action',
render: (_, record) => (
} onClick={SubmitData}>
- 应用更改
+ {t('应用更改')}
}
- placeholder="搜索模型名称"
+ placeholder={t('搜索模型名称')}
value={searchText}
onChange={value => {
setSearchText(value)
- // 搜索时重置页码
setCurrentPage(1);
}}
style={{ width: 200 }}
@@ -242,12 +242,18 @@ export default function ModelSettingsVisualEditor(props) {
setCurrentPage(page),
+ formatPageText: (page) =>
+ t('第 {{start}} - {{end}} 条,共 {{total}} 条', {
+ start: page.currentStart,
+ end: page.currentEnd,
+ total: filteredModels.length
+ }),
showTotal: true,
showSizeChanger: false
}}
@@ -255,7 +261,7 @@ export default function ModelSettingsVisualEditor(props) {
setVisible(false)}
onOk={() => {
@@ -263,32 +269,49 @@ export default function ModelSettingsVisualEditor(props) {
}}
>
setCurrentModel(prev => ({ ...prev, name: value }))}
/>
- setCurrentModel(prev => ({ ...prev, price: value }))}
- />
- setCurrentModel(prev => ({ ...prev, ratio: value }))}
- />
- setCurrentModel(prev => ({ ...prev, completionRatio: value }))}
+ {t('定价模式')}:{currentModel?.priceMode ? t("固定价格") : t("倍率模式")}>}
+ onChange={checked => {
+ setCurrentModel(prev => ({
+ ...prev,
+ price: '',
+ ratio: '',
+ completionRatio: '',
+ priceMode: checked
+ }));
+ }}
/>
+ {currentModel?.priceMode ? (
+ setCurrentModel(prev => ({ ...prev, price: value }))}
+ />
+ ) : (
+ <>
+ setCurrentModel(prev => ({ ...prev, ratio: value }))}
+ />
+ setCurrentModel(prev => ({ ...prev, completionRatio: value }))}
+ />
+ >
+ )}
>