diff --git a/web/package.json b/web/package.json
index 4f9eced..e814d65 100644
--- a/web/package.json
+++ b/web/package.json
@@ -5,11 +5,12 @@
"type": "module",
"dependencies": {
"@douyinfe/semi-icons": "^2.46.1",
- "@douyinfe/semi-ui": "^2.46.1",
+ "@douyinfe/semi-ui": "^2.55.3",
"@visactor/react-vchart": "~1.8.8",
"@visactor/vchart": "~1.8.8",
"@visactor/vchart-semi-theme": "~1.8.8",
"axios": "^0.27.2",
+ "dayjs": "^1.11.11",
"history": "^5.3.0",
"marked": "^4.1.1",
"react": "^18.2.0",
diff --git a/web/src/components/OperationSetting.js b/web/src/components/OperationSetting.js
index 41573af..3273f24 100644
--- a/web/src/components/OperationSetting.js
+++ b/web/src/components/OperationSetting.js
@@ -1,17 +1,17 @@
import React, { useEffect, useState } from 'react';
-import { Divider, Form, Grid, Header } from 'semantic-ui-react';
-import {
- API,
- showError,
- showSuccess,
- timestamp2string,
- verifyJSON,
-} from '../helpers';
+import { Card, Spin } 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';
+import SettingsLog from '../pages/Setting/Operation/SettingsLog.js';
+import SettingsDataDashboard from '../pages/Setting/Operation/SettingsDataDashboard.js';
+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 { useTheme } from '../context/Theme';
+import { API, showError, showSuccess } from '../helpers';
const OperationSetting = () => {
- let now = new Date();
let [inputs, setInputs] = useState({
QuotaForNewUser: 0,
QuotaForInviter: 0,
@@ -27,39 +27,31 @@ const OperationSetting = () => {
ChatLink: '',
ChatLink2: '', // 添加的新状态变量
QuotaPerUnit: 0,
- AutomaticDisableChannelEnabled: '',
- AutomaticEnableChannelEnabled: '',
+ AutomaticDisableChannelEnabled: false,
+ AutomaticEnableChannelEnabled: false,
ChannelDisableThreshold: 0,
- LogConsumeEnabled: '',
- DisplayInCurrencyEnabled: '',
- DisplayTokenStatEnabled: '',
- CheckSensitiveEnabled: '',
- CheckSensitiveOnPromptEnabled: '',
+ LogConsumeEnabled: false,
+ DisplayInCurrencyEnabled: false,
+ DisplayTokenStatEnabled: false,
+ CheckSensitiveEnabled: false,
+ CheckSensitiveOnPromptEnabled: false,
CheckSensitiveOnCompletionEnabled: '',
StopOnSensitiveEnabled: '',
SensitiveWords: '',
- MjNotifyEnabled: '',
- MjAccountFilterEnabled: '',
- MjModeClearEnabled: '',
- MjForwardUrlEnabled: '',
- DrawingEnabled: '',
- DataExportEnabled: '',
+ MjNotifyEnabled: false,
+ MjAccountFilterEnabled: false,
+ MjModeClearEnabled: false,
+ MjForwardUrlEnabled: false,
+ DrawingEnabled: false,
+ DataExportEnabled: false,
DataExportDefaultTime: 'hour',
DataExportInterval: 5,
- DefaultCollapseSidebar: '', // 默认折叠侧边栏
+ DefaultCollapseSidebar: false, // 默认折叠侧边栏
RetryTimes: 0,
});
- const [originInputs, setOriginInputs] = useState({});
+
let [loading, setLoading] = useState(false);
- let [historyTimestamp, setHistoryTimestamp] = useState(
- timestamp2string(now.getTime() / 1000 - 30 * 24 * 3600),
- ); // a month ago
- // 精确时间选项(小时,天,周)
- const timeOptions = [
- { key: 'hour', text: '小时', value: 'hour' },
- { key: 'day', text: '天', value: 'day' },
- { key: 'week', text: '周', value: 'week' },
- ];
+
const getOptions = async () => {
const res = await API.get('/api/option/');
const { success, message, data } = res.data;
@@ -74,566 +66,74 @@ const OperationSetting = () => {
) {
item.value = JSON.stringify(JSON.parse(item.value), null, 2);
}
- newInputs[item.key] = item.value;
+ if (
+ item.key.endsWith('Enabled') ||
+ ['DefaultCollapseSidebar'].includes(item.key)
+ ) {
+ newInputs[item.key] = item.value === 'true' ? true : false;
+ } else {
+ newInputs[item.key] = item.value;
+ }
});
+
setInputs(newInputs);
- setOriginInputs(newInputs);
} else {
showError(message);
}
};
-
- const theme = useTheme();
- const isDark = theme === 'dark';
+ async function onRefresh() {
+ try {
+ setLoading(true);
+ await getOptions();
+ showSuccess('刷新成功');
+ } catch (error) {
+ showError('刷新失败');
+ } finally {
+ setLoading(false);
+ }
+ }
useEffect(() => {
- getOptions().then();
+ onRefresh();
}, []);
- const updateOption = async (key, value) => {
- setLoading(true);
- if (key.endsWith('Enabled')) {
- value = inputs[key] === 'true' ? 'false' : 'true';
- }
- if (key === 'DefaultCollapseSidebar') {
- value = inputs[key] === 'true' ? 'false' : 'true';
- }
- console.log(key, value);
- const res = await API.put('/api/option/', {
- key,
- value,
- });
- const { success, message } = res.data;
- if (success) {
- setInputs((inputs) => ({ ...inputs, [key]: value }));
- } else {
- showError(message);
- }
- setLoading(false);
- };
-
- const handleInputChange = async (e, { name, value }) => {
- if (
- name.endsWith('Enabled') ||
- name === 'DataExportInterval' ||
- name === 'DataExportDefaultTime' ||
- name === 'DefaultCollapseSidebar'
- ) {
- if (name === 'DataExportDefaultTime') {
- localStorage.setItem('data_export_default_time', value);
- } else if (name === 'MjNotifyEnabled') {
- localStorage.setItem('mj_notify_enabled', value);
- }
- await updateOption(name, value);
- } else {
- setInputs((inputs) => ({ ...inputs, [name]: value }));
- }
- };
-
- const submitConfig = async (group) => {
- switch (group) {
- case 'monitor':
- if (
- originInputs['ChannelDisableThreshold'] !==
- inputs.ChannelDisableThreshold
- ) {
- await updateOption(
- 'ChannelDisableThreshold',
- inputs.ChannelDisableThreshold,
- );
- }
- if (
- originInputs['QuotaRemindThreshold'] !== inputs.QuotaRemindThreshold
- ) {
- await updateOption(
- 'QuotaRemindThreshold',
- inputs.QuotaRemindThreshold,
- );
- }
- break;
- case 'ratio':
- if (originInputs['ModelRatio'] !== inputs.ModelRatio) {
- if (!verifyJSON(inputs.ModelRatio)) {
- showError('模型倍率不是合法的 JSON 字符串');
- return;
- }
- await updateOption('ModelRatio', inputs.ModelRatio);
- }
- if (originInputs['CompletionRatio'] !== inputs.CompletionRatio) {
- if (!verifyJSON(inputs.CompletionRatio)) {
- showError('模型补全倍率不是合法的 JSON 字符串');
- return;
- }
- await updateOption('CompletionRatio', inputs.CompletionRatio);
- }
- if (originInputs['GroupRatio'] !== inputs.GroupRatio) {
- if (!verifyJSON(inputs.GroupRatio)) {
- showError('分组倍率不是合法的 JSON 字符串');
- return;
- }
- await updateOption('GroupRatio', inputs.GroupRatio);
- }
- if (originInputs['ModelPrice'] !== inputs.ModelPrice) {
- if (!verifyJSON(inputs.ModelPrice)) {
- showError('模型固定价格不是合法的 JSON 字符串');
- return;
- }
- await updateOption('ModelPrice', inputs.ModelPrice);
- }
- break;
- case 'words':
- if (originInputs['SensitiveWords'] !== inputs.SensitiveWords) {
- await updateOption('SensitiveWords', inputs.SensitiveWords);
- }
- break;
- case 'quota':
- if (originInputs['QuotaForNewUser'] !== inputs.QuotaForNewUser) {
- await updateOption('QuotaForNewUser', inputs.QuotaForNewUser);
- }
- if (originInputs['QuotaForInvitee'] !== inputs.QuotaForInvitee) {
- await updateOption('QuotaForInvitee', inputs.QuotaForInvitee);
- }
- if (originInputs['QuotaForInviter'] !== inputs.QuotaForInviter) {
- await updateOption('QuotaForInviter', inputs.QuotaForInviter);
- }
- if (originInputs['PreConsumedQuota'] !== inputs.PreConsumedQuota) {
- await updateOption('PreConsumedQuota', inputs.PreConsumedQuota);
- }
- break;
- case 'general':
- if (originInputs['TopUpLink'] !== inputs.TopUpLink) {
- await updateOption('TopUpLink', inputs.TopUpLink);
- }
- if (originInputs['ChatLink'] !== inputs.ChatLink) {
- await updateOption('ChatLink', inputs.ChatLink);
- }
- if (originInputs['ChatLink2'] !== inputs.ChatLink2) {
- await updateOption('ChatLink2', inputs.ChatLink2);
- }
- if (originInputs['QuotaPerUnit'] !== inputs.QuotaPerUnit) {
- await updateOption('QuotaPerUnit', inputs.QuotaPerUnit);
- }
- if (originInputs['RetryTimes'] !== inputs.RetryTimes) {
- await updateOption('RetryTimes', inputs.RetryTimes);
- }
- break;
- }
- };
-
- const deleteHistoryLogs = async () => {
- console.log(inputs);
- const res = await API.delete(
- `/api/log/?target_timestamp=${Date.parse(historyTimestamp) / 1000}`,
- );
- const { success, message, data } = res.data;
- if (success) {
- showSuccess(`${data} 条日志已清理!`);
- return;
- }
- showError('日志清理失败:' + message);
- };
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- submitConfig('general').then();
- }}
- >
- 保存通用设置
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/**/}
-
- {/**/}
- {/* */}
- {/**/}
- {/**/}
- {/* */}
- {/**/}
-
-
-
- {
- submitConfig('words').then();
- }}
- >
- 保存屏蔽词设置
-
-
-
-
-
-
-
- {
- setHistoryTimestamp(value);
- }}
- />
-
- {
- deleteHistoryLogs().then();
- }}
- >
- 清理历史日志
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- submitConfig('monitor').then();
- }}
- >
- 保存监控设置
-
-
-
-
-
-
-
-
-
- {
- submitConfig('quota').then();
- }}
- >
- 保存额度设置
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- submitConfig('ratio').then();
- }}
- >
- 保存倍率设置
-
-
-
-
+ <>
+
+ {/* 通用设置 */}
+
+
+
+ {/* 绘图设置 */}
+
+
+
+ {/* 屏蔽词过滤设置 */}
+
+
+
+ {/* 日志设置 */}
+
+
+
+ {/* 数据看板 */}
+
+
+
+ {/* 监控设置 */}
+
+
+
+ {/* 额度设置 */}
+
+
+
+ {/* 倍率设置 */}
+
+
+
+
+ >
);
};
diff --git a/web/src/helpers/utils.js b/web/src/helpers/utils.js
index 78fa3d6..0ca3476 100644
--- a/web/src/helpers/utils.js
+++ b/web/src/helpers/utils.js
@@ -220,3 +220,28 @@ export function shouldShowPrompt(id) {
export function setPromptShown(id) {
localStorage.setItem(`prompt-${id}`, 'true');
}
+
+/**
+ * 比较两个对象的属性,找出有变化的属性,并返回包含变化属性信息的数组
+ * @param {Object} oldObject - 旧对象
+ * @param {Object} newObject - 新对象
+ * @return {Array} 包含变化属性信息的数组,每个元素是一个对象,包含 key, oldValue 和 newValue
+ */
+export function compareObjects(oldObject, newObject) {
+ const changedProperties = [];
+
+ // 比较两个对象的属性
+ for (const key in oldObject) {
+ if (oldObject.hasOwnProperty(key) && newObject.hasOwnProperty(key)) {
+ if (oldObject[key] !== newObject[key]) {
+ changedProperties.push({
+ key: key,
+ oldValue: oldObject[key],
+ newValue: newObject[key],
+ });
+ }
+ }
+ }
+
+ return changedProperties;
+}
diff --git a/web/src/pages/Setting/Operation/SettingsCreditLimit.js b/web/src/pages/Setting/Operation/SettingsCreditLimit.js
new file mode 100644
index 0000000..ed5862a
--- /dev/null
+++ b/web/src/pages/Setting/Operation/SettingsCreditLimit.js
@@ -0,0 +1,156 @@
+import React, { useEffect, useState, useRef } from 'react';
+import { Button, Col, Form, Row, Spin } from '@douyinfe/semi-ui';
+import {
+ compareObjects,
+ API,
+ showError,
+ showSuccess,
+ showWarning,
+} from '../../../helpers';
+
+export default function SettingsCreditLimit(props) {
+ const [loading, setLoading] = useState(false);
+ const [inputs, setInputs] = useState({
+ QuotaForNewUser: '',
+ PreConsumedQuota: '',
+ QuotaForInviter: '',
+ QuotaForInvitee: '',
+ });
+ const refForm = useRef();
+ const [inputsRow, setInputsRow] = useState(inputs);
+
+ function onSubmit() {
+ const updateArray = compareObjects(inputs, inputsRow);
+ if (!updateArray.length) return showWarning('你似乎并没有修改什么');
+ const requestQueue = updateArray.map((item) => {
+ let value = '';
+ if (typeof inputs[item.key] === 'boolean') {
+ value = String(inputs[item.key]);
+ } else {
+ value = 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('部分保存失败,请重试');
+ }
+ showSuccess('保存成功');
+ props.refresh();
+ })
+ .catch(() => {
+ showError('保存失败,请重试');
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ }
+
+ 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 (
+ <>
+
+
+
+
+
+ setInputs({
+ ...inputs,
+ QuotaForNewUser: String(value),
+ })
+ }
+ />
+
+
+
+ setInputs({
+ ...inputs,
+ PreConsumedQuota: String(value),
+ })
+ }
+ />
+
+
+
+ setInputs({
+ ...inputs,
+ QuotaForInviter: String(value),
+ })
+ }
+ />
+
+
+
+ setInputs({
+ ...inputs,
+ QuotaForInvitee: String(value),
+ })
+ }
+ />
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/web/src/pages/Setting/Operation/SettingsDataDashboard.js b/web/src/pages/Setting/Operation/SettingsDataDashboard.js
new file mode 100644
index 0000000..de0911e
--- /dev/null
+++ b/web/src/pages/Setting/Operation/SettingsDataDashboard.js
@@ -0,0 +1,147 @@
+import React, { useEffect, useState, useRef } from 'react';
+import { Button, Col, Form, Row, Spin, Tag } from '@douyinfe/semi-ui';
+import {
+ compareObjects,
+ API,
+ showError,
+ showSuccess,
+ showWarning,
+} from '../../../helpers';
+
+export default function DataDashboard(props) {
+ const optionsDataExportDefaultTime = [
+ { key: 'hour', label: '小时', value: 'hour' },
+ { key: 'day', label: '天', value: 'day' },
+ { key: 'week', label: '周', value: 'week' },
+ ];
+ const [loading, setLoading] = useState(false);
+ const [inputs, setInputs] = useState({
+ DataExportEnabled: false,
+ DataExportInterval: '',
+ DataExportDefaultTime: '',
+ });
+ const refForm = useRef();
+ const [inputsRow, setInputsRow] = useState(inputs);
+
+ function onSubmit() {
+ const updateArray = compareObjects(inputs, inputsRow);
+ if (!updateArray.length) return showWarning('你似乎并没有修改什么');
+ const requestQueue = updateArray.map((item) => {
+ let value = '';
+ if (typeof inputs[item.key] === 'boolean') {
+ value = String(inputs[item.key]);
+ } else {
+ value = 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('部分保存失败,请重试');
+ }
+ showSuccess('保存成功');
+ props.refresh();
+ })
+ .catch(() => {
+ showError('保存失败,请重试');
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ }
+
+ 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);
+ localStorage.setItem(
+ 'data_export_default_time',
+ String(inputs.DataExportDefaultTime),
+ );
+ }, [props.options]);
+
+ return (
+ <>
+
+
+
+
+ {
+ setInputs({
+ ...inputs,
+ DataExportEnabled: value,
+ });
+ }}
+ />
+
+
+
+
+
+ setInputs({
+ ...inputs,
+ DataExportInterval: String(value),
+ })
+ }
+ />
+
+
+
+ setInputs({
+ ...inputs,
+ DataExportDefaultTime: String(value),
+ })
+ }
+ />
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/web/src/pages/Setting/Operation/SettingsDrawing.js b/web/src/pages/Setting/Operation/SettingsDrawing.js
new file mode 100644
index 0000000..bb1c047
--- /dev/null
+++ b/web/src/pages/Setting/Operation/SettingsDrawing.js
@@ -0,0 +1,170 @@
+import React, { useEffect, useState, useRef } from 'react';
+import { Button, Col, Form, Row, Spin, Tag } from '@douyinfe/semi-ui';
+import {
+ compareObjects,
+ API,
+ showError,
+ showSuccess,
+ showWarning,
+} from '../../../helpers';
+
+export default function SettingsDrawing(props) {
+ const [loading, setLoading] = useState(false);
+ const [inputs, setInputs] = useState({
+ DrawingEnabled: false,
+ MjNotifyEnabled: false,
+ MjAccountFilterEnabled: false,
+ MjForwardUrlEnabled: false,
+ MjModeClearEnabled: false,
+ });
+ const refForm = useRef();
+ const [inputsRow, setInputsRow] = useState(inputs);
+
+ function onSubmit() {
+ const updateArray = compareObjects(inputs, inputsRow);
+ if (!updateArray.length) return showWarning('你似乎并没有修改什么');
+ const requestQueue = updateArray.map((item) => {
+ let value = '';
+ if (typeof inputs[item.key] === 'boolean') {
+ value = String(inputs[item.key]);
+ } else {
+ value = 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('部分保存失败,请重试');
+ }
+ showSuccess('保存成功');
+ props.refresh();
+ })
+ .catch(() => {
+ showError('保存失败,请重试');
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ }
+
+ 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);
+ localStorage.setItem('mj_notify_enabled', String(inputs.MjNotifyEnabled));
+ }, [props.options]);
+ return (
+ <>
+
+
+
+
+ {
+ setInputs({
+ ...inputs,
+ DrawingEnabled: value,
+ });
+ }}
+ />
+
+
+
+ setInputs({
+ ...inputs,
+ MjNotifyEnabled: value,
+ })
+ }
+ />
+
+
+
+ setInputs({
+ ...inputs,
+ MjAccountFilterEnabled: value,
+ })
+ }
+ />
+
+
+
+ setInputs({
+ ...inputs,
+ MjForwardUrlEnabled: value,
+ })
+ }
+ />
+
+
+
+ 开启之后会清除用户提示词中的 --fast 、
+ --relax 以及 --turbo 参数
+ >
+ }
+ size='large'
+ checkedText='|'
+ uncheckedText='〇'
+ onChange={(value) =>
+ setInputs({
+ ...inputs,
+ MjModeClearEnabled: value,
+ })
+ }
+ />
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/web/src/pages/Setting/Operation/SettingsGeneral.js b/web/src/pages/Setting/Operation/SettingsGeneral.js
new file mode 100644
index 0000000..ef52371
--- /dev/null
+++ b/web/src/pages/Setting/Operation/SettingsGeneral.js
@@ -0,0 +1,192 @@
+import React, { useEffect, useState, useRef } from 'react';
+import { Button, Col, Form, Row, Spin } from '@douyinfe/semi-ui';
+import {
+ compareObjects,
+ API,
+ showError,
+ showSuccess,
+ showWarning,
+} from '../../../helpers';
+
+export default function GeneralSettings(props) {
+ const [loading, setLoading] = useState(false);
+ const [inputs, setInputs] = useState({
+ TopUpLink: '',
+ ChatLink: '',
+ ChatLink2: '',
+ QuotaPerUnit: '',
+ RetryTimes: '',
+ DisplayInCurrencyEnabled: false,
+ DisplayTokenStatEnabled: false,
+ DefaultCollapseSidebar: false,
+ });
+ const refForm = useRef();
+ const [inputsRow, setInputsRow] = useState(inputs);
+ function onChange(value, e) {
+ const name = e.target.id;
+ setInputs((inputs) => ({ ...inputs, [name]: value }));
+ }
+ function onSubmit() {
+ const updateArray = compareObjects(inputs, inputsRow);
+ if (!updateArray.length) return showWarning('你似乎并没有修改什么');
+ const requestQueue = updateArray.map((item) => {
+ let value = '';
+ if (typeof inputs[item.key] === 'boolean') {
+ value = String(inputs[item.key]);
+ } else {
+ value = 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('部分保存失败,请重试');
+ }
+ showSuccess('保存成功');
+ props.refresh();
+ })
+ .catch(() => {
+ showError('保存失败,请重试');
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ }
+
+ 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 (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ setInputs({
+ ...inputs,
+ DisplayInCurrencyEnabled: value,
+ });
+ }}
+ />
+
+
+
+ setInputs({
+ ...inputs,
+ DisplayTokenStatEnabled: value,
+ })
+ }
+ />
+
+
+
+ setInputs({
+ ...inputs,
+ DefaultCollapseSidebar: value,
+ })
+ }
+ />
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/web/src/pages/Setting/Operation/SettingsLog.js b/web/src/pages/Setting/Operation/SettingsLog.js
new file mode 100644
index 0000000..6977dca
--- /dev/null
+++ b/web/src/pages/Setting/Operation/SettingsLog.js
@@ -0,0 +1,147 @@
+import React, { useEffect, useState, useRef } from 'react';
+import { Button, Col, Form, Row, Spin, DatePicker } from '@douyinfe/semi-ui';
+import dayjs from 'dayjs';
+import {
+ compareObjects,
+ API,
+ showError,
+ showSuccess,
+ showWarning,
+} from '../../../helpers';
+
+export default function SettingsLog(props) {
+ const [loading, setLoading] = useState(false);
+ const [loadingCleanHistoryLog, setLoadingCleanHistoryLog] = useState(false);
+ const [inputs, setInputs] = useState({
+ LogConsumeEnabled: false,
+ historyTimestamp: dayjs().subtract(1, 'month').toDate(),
+ });
+ const refForm = useRef();
+ const [inputsRow, setInputsRow] = useState(inputs);
+
+ function onSubmit() {
+ const updateArray = compareObjects(inputs, inputsRow).filter(
+ (item) => item.key !== 'historyTimestamp',
+ );
+
+ if (!updateArray.length) return showWarning('你似乎并没有修改什么');
+ const requestQueue = updateArray.map((item) => {
+ let value = '';
+ if (typeof inputs[item.key] === 'boolean') {
+ value = String(inputs[item.key]);
+ } else {
+ value = 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('部分保存失败,请重试');
+ }
+ showSuccess('保存成功');
+ props.refresh();
+ })
+ .catch(() => {
+ showError('保存失败,请重试');
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ }
+ async function onCleanHistoryLog() {
+ try {
+ setLoadingCleanHistoryLog(true);
+ if (!inputs.historyTimestamp) throw new Error('请选择日志记录时间');
+ const res = await API.delete(
+ `/api/log/?target_timestamp=${Date.parse(inputs.historyTimestamp) / 1000}`,
+ );
+ const { success, message, data } = res.data;
+ if (success) {
+ showSuccess(`${data} 条日志已清理!`);
+ return;
+ } else {
+ throw new Error('日志清理失败:' + message);
+ }
+ } catch (error) {
+ showError(error.message);
+ } finally {
+ setLoadingCleanHistoryLog(false);
+ }
+ }
+
+ useEffect(() => {
+ const currentInputs = {};
+ for (let key in props.options) {
+ if (Object.keys(inputs).includes(key)) {
+ currentInputs[key] = props.options[key];
+ }
+ }
+ currentInputs['historyTimestamp'] = inputs.historyTimestamp;
+ setInputs(Object.assign(inputs, currentInputs));
+ setInputsRow(structuredClone(currentInputs));
+ refForm.current.setValues(currentInputs);
+ }, [props.options]);
+ return (
+ <>
+
+
+
+
+ {
+ setInputs({
+ ...inputs,
+ LogConsumeEnabled: value,
+ });
+ }}
+ />
+
+
+
+ {
+ setInputs({
+ ...inputs,
+ historyTimestamp: value,
+ });
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/web/src/pages/Setting/Operation/SettingsMagnification.js b/web/src/pages/Setting/Operation/SettingsMagnification.js
new file mode 100644
index 0000000..9c64793
--- /dev/null
+++ b/web/src/pages/Setting/Operation/SettingsMagnification.js
@@ -0,0 +1,194 @@
+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';
+
+export default function SettingsMagnification(props) {
+ const [loading, setLoading] = useState(false);
+ const [inputs, setInputs] = useState({
+ ModelPrice: '',
+ ModelRatio: '',
+ CompletionRatio: '',
+ GroupRatio: '',
+ });
+ const refForm = useRef();
+ const [inputsRow, setInputsRow] = useState(inputs);
+
+ async function onSubmit() {
+ try {
+ await refForm.current.validate();
+ const updateArray = compareObjects(inputs, inputsRow);
+ if (!updateArray.length) return showWarning('你似乎并没有修改什么');
+ const requestQueue = updateArray.map((item) => {
+ let value = '';
+ if (typeof inputs[item.key] === 'boolean') {
+ value = String(inputs[item.key]);
+ } else {
+ value = 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('部分保存失败,请重试');
+ }
+ showSuccess('保存成功');
+ props.refresh();
+ })
+ .catch(() => {
+ showError('保存失败,请重试');
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ } catch (error) {
+ showError('请检查输入');
+ console.error(error);
+ } finally {
+ }
+ }
+
+ 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,
+ })
+ }
+ />
+
+
+
+
+ verifyJSON(value),
+ message: '不是合法的 JSON 字符串',
+ },
+ ]}
+ onChange={(value) =>
+ setInputs({
+ ...inputs,
+ GroupRatio: value,
+ })
+ }
+ />
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/web/src/pages/Setting/Operation/SettingsMonitoring.js b/web/src/pages/Setting/Operation/SettingsMonitoring.js
new file mode 100644
index 0000000..fc86b97
--- /dev/null
+++ b/web/src/pages/Setting/Operation/SettingsMonitoring.js
@@ -0,0 +1,154 @@
+import React, { useEffect, useState, useRef } from 'react';
+import { Button, Col, Form, Row, Spin } from '@douyinfe/semi-ui';
+import {
+ compareObjects,
+ API,
+ showError,
+ showSuccess,
+ showWarning,
+} from '../../../helpers';
+
+export default function SettingsMonitoring(props) {
+ const [loading, setLoading] = useState(false);
+ const [inputs, setInputs] = useState({
+ ChannelDisableThreshold: '',
+ QuotaRemindThreshold: '',
+ AutomaticDisableChannelEnabled: false,
+ AutomaticEnableChannelEnabled: false,
+ });
+ const refForm = useRef();
+ const [inputsRow, setInputsRow] = useState(inputs);
+
+ function onSubmit() {
+ const updateArray = compareObjects(inputs, inputsRow);
+ if (!updateArray.length) return showWarning('你似乎并没有修改什么');
+ const requestQueue = updateArray.map((item) => {
+ let value = '';
+ if (typeof inputs[item.key] === 'boolean') {
+ value = String(inputs[item.key]);
+ } else {
+ value = 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('部分保存失败,请重试');
+ }
+ showSuccess('保存成功');
+ props.refresh();
+ })
+ .catch(() => {
+ showError('保存失败,请重试');
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ }
+
+ 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 (
+ <>
+
+
+
+
+
+ setInputs({
+ ...inputs,
+ ChannelDisableThreshold: String(value),
+ })
+ }
+ />
+
+
+
+ setInputs({
+ ...inputs,
+ QuotaRemindThreshold: String(value),
+ })
+ }
+ />
+
+
+
+
+ {
+ setInputs({
+ ...inputs,
+ AutomaticDisableChannelEnabled: value,
+ });
+ }}
+ />
+
+
+
+ setInputs({
+ ...inputs,
+ AutomaticEnableChannelEnabled: value,
+ })
+ }
+ />
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/web/src/pages/Setting/Operation/SettingsSensitiveWords.js b/web/src/pages/Setting/Operation/SettingsSensitiveWords.js
new file mode 100644
index 0000000..21583b6
--- /dev/null
+++ b/web/src/pages/Setting/Operation/SettingsSensitiveWords.js
@@ -0,0 +1,135 @@
+import React, { useEffect, useState, useRef } from 'react';
+import { Button, Col, Form, Row, Spin, Tag } from '@douyinfe/semi-ui';
+import {
+ compareObjects,
+ API,
+ showError,
+ showSuccess,
+ showWarning,
+} from '../../../helpers';
+
+export default function SettingsSensitiveWords(props) {
+ const [loading, setLoading] = useState(false);
+ const [inputs, setInputs] = useState({
+ CheckSensitiveEnabled: false,
+ CheckSensitiveOnPromptEnabled: false,
+ SensitiveWords: '',
+ });
+ const refForm = useRef();
+ const [inputsRow, setInputsRow] = useState(inputs);
+
+ function onSubmit() {
+ const updateArray = compareObjects(inputs, inputsRow);
+ if (!updateArray.length) return showWarning('你似乎并没有修改什么');
+ const requestQueue = updateArray.map((item) => {
+ let value = '';
+ if (typeof inputs[item.key] === 'boolean') {
+ value = String(inputs[item.key]);
+ } else {
+ value = 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('部分保存失败,请重试');
+ }
+ showSuccess('保存成功');
+ props.refresh();
+ })
+ .catch(() => {
+ showError('保存失败,请重试');
+ })
+ .finally(() => {
+ setLoading(false);
+ });
+ }
+
+ 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 (
+ <>
+
+
+
+
+ {
+ setInputs({
+ ...inputs,
+ CheckSensitiveEnabled: value,
+ });
+ }}
+ />
+
+
+
+ setInputs({
+ ...inputs,
+ CheckSensitiveOnPromptEnabled: value,
+ })
+ }
+ />
+
+
+
+
+
+ setInputs({
+ ...inputs,
+ SensitiveWords: value,
+ })
+ }
+ style={{ fontFamily: 'JetBrains Mono, Consolas' }}
+ autosize={{ minRows: 6, maxRows: 12 }}
+ />
+
+
+
+
+
+
+
+
+ >
+ );
+}