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 ( + <> + +
(refForm.current = formAPI)} + style={{ marginBottom: 15 }} + > + + + + + 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 ( + <> + +
(refForm.current = formAPI)} + style={{ marginBottom: 15 }} + > + + + + { + 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 ( + <> + +
(refForm.current = formAPI)} + style={{ marginBottom: 15 }} + > + + + + { + 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 ( + <> + +
(refForm.current = formAPI)} + style={{ marginBottom: 15 }} + > + + + + + + + + + + + + + + + + + + + + + { + 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 ( + <> + +
(refForm.current = formAPI)} + style={{ marginBottom: 15 }} + > + + + + { + 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 ( + <> + +
(refForm.current = formAPI)} + style={{ marginBottom: 15 }} + > + + + + 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 ( + <> + +
(refForm.current = formAPI)} + style={{ marginBottom: 15 }} + > + + + + + 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 ( + <> + +
(refForm.current = formAPI)} + style={{ marginBottom: 15 }} + > + + + + { + setInputs({ + ...inputs, + CheckSensitiveEnabled: value, + }); + }} + /> + + + + setInputs({ + ...inputs, + CheckSensitiveOnPromptEnabled: value, + }) + } + /> + + + + + + setInputs({ + ...inputs, + SensitiveWords: value, + }) + } + style={{ fontFamily: 'JetBrains Mono, Consolas' }} + autosize={{ minRows: 6, maxRows: 12 }} + /> + + + + + + +
+
+ + ); +}