From 83dd62982ec4000bc8881cc6f718e7c0a0ffb41e Mon Sep 17 00:00:00 2001 From: QuentinHsu Date: Wed, 8 May 2024 14:57:36 +0800 Subject: [PATCH 01/32] =?UTF-8?q?refactor:=20=E8=BF=90=E8=90=A5=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE-=E9=80=9A=E7=94=A8=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/OperationSetting.js | 728 ++++++++---------- web/src/helpers/utils.js | 25 + .../Setting/Operation/GeneralSettings.js | 198 +++++ 3 files changed, 561 insertions(+), 390 deletions(-) create mode 100644 web/src/pages/Setting/Operation/GeneralSettings.js diff --git a/web/src/components/OperationSetting.js b/web/src/components/OperationSetting.js index 7566faa..ecf3d77 100644 --- a/web/src/components/OperationSetting.js +++ b/web/src/components/OperationSetting.js @@ -1,5 +1,7 @@ import React, { useEffect, useState } from 'react'; import { Divider, Form, Grid, Header } from 'semantic-ui-react'; +import { Card } from '@douyinfe/semi-ui'; +import GeneralSettings from '../pages/Setting/Operation/GeneralSettings.js'; import { API, showError, @@ -30,8 +32,8 @@ const OperationSetting = () => { AutomaticEnableChannelEnabled: '', ChannelDisableThreshold: 0, LogConsumeEnabled: '', - DisplayInCurrencyEnabled: '', - DisplayTokenStatEnabled: '', + DisplayInCurrencyEnabled: false, + DisplayTokenStatEnabled: false, CheckSensitiveEnabled: '', CheckSensitiveOnPromptEnabled: '', CheckSensitiveOnCompletionEnabled: '', @@ -45,7 +47,7 @@ const OperationSetting = () => { DataExportEnabled: '', DataExportDefaultTime: 'hour', DataExportInterval: 5, - DefaultCollapseSidebar: '', // 默认折叠侧边栏 + DefaultCollapseSidebar: false, // 默认折叠侧边栏 RetryTimes: 0, }); const [originInputs, setOriginInputs] = useState({}); @@ -72,8 +74,16 @@ 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 { @@ -224,396 +234,334 @@ const OperationSetting = () => { showError('日志清理失败:' + message); }; return ( - - -
-
- 通用设置 -
- - - - - - - - - - - - - { - submitConfig('general').then(); - }} - > - 保存通用设置 - - -
- 绘图设置 -
- - - - - - - - -
- 屏蔽词过滤设置 -
- - - - - - {/**/} - - {/**/} - {/* */} - {/**/} - {/**/} - {/* */} - {/**/} - - - - { - submitConfig('words').then(); - }} - > - 保存屏蔽词设置 - - -
- 日志设置 -
- - - - - { - setHistoryTimestamp(value); + <> + + {/* 通用设置 */} + + + + + + +
+ 绘图设置 +
+ + + + + + + + +
+ 屏蔽词过滤设置 +
+ + + + + + {/**/} + + {/**/} + {/* */} + {/**/} + {/**/} + {/* */} + {/**/} + + + + { + submitConfig('words').then(); }} - /> -
- { - deleteHistoryLogs().then(); - }} - > - 清理历史日志 - - -
- 数据看板 -
- - - - - - -
- 监控设置 -
- - - - - + > + 保存屏蔽词设置 + + +
+ 日志设置 +
+ + + + + { + setHistoryTimestamp(value); + }} + /> + + { + deleteHistoryLogs().then(); + }} + > + 清理历史日志 + + +
+ 数据看板 +
- -
- { - submitConfig('monitor').then(); - }} - > - 保存监控设置 - - -
- 额度设置 -
- - - - - - - { - submitConfig('quota').then(); - }} - > - 保存额度设置 - - -
- 倍率设置 -
- - - - - - - - - - { - submitConfig('ratio').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/GeneralSettings.js b/web/src/pages/Setting/Operation/GeneralSettings.js new file mode 100644 index 0000000..6805a3b --- /dev/null +++ b/web/src/pages/Setting/Operation/GeneralSettings.js @@ -0,0 +1,198 @@ +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('更新成功'); + }) + .catch(() => { + showError('更新失败'); + }) + .finally(() => { + setLoading(false); + setInputsRow(structuredClone(inputs)); + }); + } + + 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, + }) + } + /> + + + + + + +
+
+ + ); +} From 9886cdd52741922b555d92d51d6b7df133a9827d Mon Sep 17 00:00:00 2001 From: QuentinHsu Date: Thu, 9 May 2024 17:20:51 +0800 Subject: [PATCH 02/32] =?UTF-8?q?refactor:=20=E8=BF=90=E8=90=A5=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE-=E7=BB=98=E5=9B=BE=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/OperationSetting.js | 44 +---- .../Setting/Operation/DrawingSettings.js | 179 ++++++++++++++++++ 2 files changed, 185 insertions(+), 38 deletions(-) create mode 100644 web/src/pages/Setting/Operation/DrawingSettings.js diff --git a/web/src/components/OperationSetting.js b/web/src/components/OperationSetting.js index ecf3d77..829e981 100644 --- a/web/src/components/OperationSetting.js +++ b/web/src/components/OperationSetting.js @@ -2,6 +2,7 @@ import React, { useEffect, useState } from 'react'; import { Divider, Form, Grid, Header } from 'semantic-ui-react'; import { Card } from '@douyinfe/semi-ui'; import GeneralSettings from '../pages/Setting/Operation/GeneralSettings.js'; +import DrawingSettings from '../pages/Setting/Operation/DrawingSettings.js'; import { API, showError, @@ -235,50 +236,17 @@ const OperationSetting = () => { }; return ( <> + {/* 通用设置 */} - {/* 通用设置 */} + {/* 绘图设置 */} + + +
- -
- 绘图设置 -
- - - - - - - -
屏蔽词过滤设置
diff --git a/web/src/pages/Setting/Operation/DrawingSettings.js b/web/src/pages/Setting/Operation/DrawingSettings.js new file mode 100644 index 0000000..46187f0 --- /dev/null +++ b/web/src/pages/Setting/Operation/DrawingSettings.js @@ -0,0 +1,179 @@ +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 GeneralSettings(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('更新成功'); + }) + .catch(() => { + showError('更新失败'); + }) + .finally(() => { + setLoading(false); + setInputsRow(structuredClone(inputs)); + }); + } + + 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, + DrawingEnabled: value, + }); + }} + /> + + + + setInputs({ + ...inputs, + MjNotifyEnabled: value, + }) + } + /> + + + + setInputs({ + ...inputs, + MjAccountFilterEnabled: value, + }) + } + /> + + + + setInputs({ + ...inputs, + MjForwardUrlEnabled: value, + }) + } + /> + + + + 开启之后会清除用户提示词中的 --fast 、 + --relax 以及 --turbo 参数 + + } + size='large' + checkedText='|' + uncheckedText='〇' + defaultChecked={false} + checked={false} + onChange={(value) => + setInputs({ + ...inputs, + MjModeClearEnabled: value, + }) + } + /> + + + + + + + + + + ); +} From 96468ce64f299e53f9f84057f63125ced9f3843f Mon Sep 17 00:00:00 2001 From: QuentinHsu Date: Fri, 10 May 2024 16:17:48 +0800 Subject: [PATCH 03/32] =?UTF-8?q?refactor:=20=E8=BF=90=E8=90=A5=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE-=E5=B1=8F=E8=94=BD=E8=AF=8D=E8=BF=87=E6=BB=A4?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/OperationSetting.js | 61 ++------ ...{DrawingSettings.js => SettingsDrawing.js} | 2 +- ...{GeneralSettings.js => SettingsGeneral.js} | 0 .../Operation/SettingsSensitiveWords.js | 139 ++++++++++++++++++ 4 files changed, 151 insertions(+), 51 deletions(-) rename web/src/pages/Setting/Operation/{DrawingSettings.js => SettingsDrawing.js} (99%) rename web/src/pages/Setting/Operation/{GeneralSettings.js => SettingsGeneral.js} (100%) create mode 100644 web/src/pages/Setting/Operation/SettingsSensitiveWords.js diff --git a/web/src/components/OperationSetting.js b/web/src/components/OperationSetting.js index 829e981..3da0dcc 100644 --- a/web/src/components/OperationSetting.js +++ b/web/src/components/OperationSetting.js @@ -1,8 +1,10 @@ import React, { useEffect, useState } from 'react'; import { Divider, Form, Grid, Header } from 'semantic-ui-react'; import { Card } from '@douyinfe/semi-ui'; -import GeneralSettings from '../pages/Setting/Operation/GeneralSettings.js'; -import DrawingSettings from '../pages/Setting/Operation/DrawingSettings.js'; +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 { API, showError, @@ -238,40 +240,19 @@ const OperationSetting = () => { <> {/* 通用设置 */} - + {/* 绘图设置 */} - + + + {/* 屏蔽词过滤设置 */} + +
-
- 屏蔽词过滤设置 -
- - - - - - {/**/} - {/**/} {/* { {/* placeholder="例如:10"*/} {/* />*/} {/**/} - - - - { - submitConfig('words').then(); - }} - > - 保存屏蔽词设置 - - +
日志设置
diff --git a/web/src/pages/Setting/Operation/DrawingSettings.js b/web/src/pages/Setting/Operation/SettingsDrawing.js similarity index 99% rename from web/src/pages/Setting/Operation/DrawingSettings.js rename to web/src/pages/Setting/Operation/SettingsDrawing.js index 46187f0..4815d2f 100644 --- a/web/src/pages/Setting/Operation/DrawingSettings.js +++ b/web/src/pages/Setting/Operation/SettingsDrawing.js @@ -8,7 +8,7 @@ import { showWarning, } from '../../../helpers'; -export default function GeneralSettings(props) { +export default function SettingsDrawing(props) { const [loading, setLoading] = useState(false); const [inputs, setInputs] = useState({ DrawingEnabled: false, diff --git a/web/src/pages/Setting/Operation/GeneralSettings.js b/web/src/pages/Setting/Operation/SettingsGeneral.js similarity index 100% rename from web/src/pages/Setting/Operation/GeneralSettings.js rename to web/src/pages/Setting/Operation/SettingsGeneral.js diff --git a/web/src/pages/Setting/Operation/SettingsSensitiveWords.js b/web/src/pages/Setting/Operation/SettingsSensitiveWords.js new file mode 100644 index 0000000..3a2a4cd --- /dev/null +++ b/web/src/pages/Setting/Operation/SettingsSensitiveWords.js @@ -0,0 +1,139 @@ +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('更新成功'); + }) + .catch(() => { + showError('更新失败'); + }) + .finally(() => { + setLoading(false); + setInputsRow(structuredClone(inputs)); + }); + } + + 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 }} + /> + + + + + + + + + + ); +} From 003745abcbb901704f50c6a7c553ef5d5ce7e79e Mon Sep 17 00:00:00 2001 From: QuentinHsu Date: Sat, 11 May 2024 14:06:32 +0800 Subject: [PATCH 04/32] =?UTF-8?q?refactor:=20=E8=BF=90=E8=90=A5=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE-=E6=97=A5=E5=BF=97=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/package.json | 3 +- web/src/components/OperationSetting.js | 64 ++------ .../pages/Setting/Operation/SettingsLog.js | 150 ++++++++++++++++++ 3 files changed, 165 insertions(+), 52 deletions(-) create mode 100644 web/src/pages/Setting/Operation/SettingsLog.js 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 3da0dcc..6835e1a 100644 --- a/web/src/components/OperationSetting.js +++ b/web/src/components/OperationSetting.js @@ -4,6 +4,7 @@ import { Card } 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 { API, @@ -34,19 +35,19 @@ const OperationSetting = () => { AutomaticDisableChannelEnabled: '', AutomaticEnableChannelEnabled: '', ChannelDisableThreshold: 0, - LogConsumeEnabled: '', + LogConsumeEnabled: false, DisplayInCurrencyEnabled: false, DisplayTokenStatEnabled: false, - CheckSensitiveEnabled: '', - CheckSensitiveOnPromptEnabled: '', + CheckSensitiveEnabled: false, + CheckSensitiveOnPromptEnabled: false, CheckSensitiveOnCompletionEnabled: '', StopOnSensitiveEnabled: '', SensitiveWords: '', - MjNotifyEnabled: '', - MjAccountFilterEnabled: '', - MjModeClearEnabled: '', - MjForwardUrlEnabled: '', - DrawingEnabled: '', + MjNotifyEnabled: false, + MjAccountFilterEnabled: false, + MjModeClearEnabled: false, + MjForwardUrlEnabled: false, + DrawingEnabled: false, DataExportEnabled: '', DataExportDefaultTime: 'hour', DataExportInterval: 5, @@ -223,19 +224,6 @@ const OperationSetting = () => { 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 ( <> {/* 通用设置 */} @@ -250,6 +238,10 @@ const OperationSetting = () => { + {/* 日志设置 */} + + +
@@ -273,36 +265,6 @@ const OperationSetting = () => { {/* />*/} {/**/} -
- 日志设置 -
- - - - - { - setHistoryTimestamp(value); - }} - /> - - { - deleteHistoryLogs().then(); - }} - > - 清理历史日志 - -
数据看板
diff --git a/web/src/pages/Setting/Operation/SettingsLog.js b/web/src/pages/Setting/Operation/SettingsLog.js new file mode 100644 index 0000000..77120ea --- /dev/null +++ b/web/src/pages/Setting/Operation/SettingsLog.js @@ -0,0 +1,150 @@ +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 [historyTimestamp, setHistoryTimestamp] = useState( + // 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('更新成功'); + }) + .catch(() => { + showError('更新失败'); + }) + .finally(() => { + setLoading(false); + setInputsRow(structuredClone(inputs)); + }); + } + 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, + }); + }} + /> + + + + + + + + + + + + + ); +} From a9d9877bce607e68166202319f155c088052e962 Mon Sep 17 00:00:00 2001 From: QuentinHsu Date: Sat, 11 May 2024 16:13:28 +0800 Subject: [PATCH 05/32] =?UTF-8?q?perf:=20=E7=A7=BB=E9=99=A4=E4=B8=8D?= =?UTF-8?q?=E7=94=9F=E6=95=88=E7=9A=84=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/pages/Setting/Operation/SettingsDrawing.js | 10 ---------- web/src/pages/Setting/Operation/SettingsGeneral.js | 6 ------ .../pages/Setting/Operation/SettingsSensitiveWords.js | 4 ---- 3 files changed, 20 deletions(-) diff --git a/web/src/pages/Setting/Operation/SettingsDrawing.js b/web/src/pages/Setting/Operation/SettingsDrawing.js index 4815d2f..085cfd1 100644 --- a/web/src/pages/Setting/Operation/SettingsDrawing.js +++ b/web/src/pages/Setting/Operation/SettingsDrawing.js @@ -82,8 +82,6 @@ export default function SettingsDrawing(props) { size='large' checkedText='|' uncheckedText='〇' - defaultChecked={false} - checked={false} onChange={(value) => { setInputs({ ...inputs, @@ -99,8 +97,6 @@ export default function SettingsDrawing(props) { size='large' checkedText='|' uncheckedText='〇' - defaultChecked={false} - checked={false} onChange={(value) => setInputs({ ...inputs, @@ -116,8 +112,6 @@ export default function SettingsDrawing(props) { size='large' checkedText='|' uncheckedText='〇' - defaultChecked={false} - checked={false} onChange={(value) => setInputs({ ...inputs, @@ -133,8 +127,6 @@ export default function SettingsDrawing(props) { size='large' checkedText='|' uncheckedText='〇' - defaultChecked={false} - checked={false} onChange={(value) => setInputs({ ...inputs, @@ -155,8 +147,6 @@ export default function SettingsDrawing(props) { size='large' checkedText='|' uncheckedText='〇' - defaultChecked={false} - checked={false} onChange={(value) => setInputs({ ...inputs, diff --git a/web/src/pages/Setting/Operation/SettingsGeneral.js b/web/src/pages/Setting/Operation/SettingsGeneral.js index 6805a3b..83fb7c8 100644 --- a/web/src/pages/Setting/Operation/SettingsGeneral.js +++ b/web/src/pages/Setting/Operation/SettingsGeneral.js @@ -140,8 +140,6 @@ export default function GeneralSettings(props) { size='large' checkedText='|' uncheckedText='〇' - defaultChecked={false} - checked={false} onChange={(value) => { setInputs({ ...inputs, @@ -157,8 +155,6 @@ export default function GeneralSettings(props) { size='large' checkedText='|' uncheckedText='〇' - defaultChecked={false} - checked={false} onChange={(value) => setInputs({ ...inputs, @@ -174,8 +170,6 @@ export default function GeneralSettings(props) { size='large' checkedText='|' uncheckedText='〇' - defaultChecked={false} - checked={false} onChange={(value) => setInputs({ ...inputs, diff --git a/web/src/pages/Setting/Operation/SettingsSensitiveWords.js b/web/src/pages/Setting/Operation/SettingsSensitiveWords.js index 3a2a4cd..f251013 100644 --- a/web/src/pages/Setting/Operation/SettingsSensitiveWords.js +++ b/web/src/pages/Setting/Operation/SettingsSensitiveWords.js @@ -80,8 +80,6 @@ export default function SettingsSensitiveWords(props) { size='large' checkedText='|' uncheckedText='〇' - defaultChecked={false} - checked={false} onChange={(value) => { setInputs({ ...inputs, @@ -97,8 +95,6 @@ export default function SettingsSensitiveWords(props) { size='large' checkedText='|' uncheckedText='〇' - defaultChecked={false} - checked={false} onChange={(value) => setInputs({ ...inputs, From 76f6b41bb2f112e5df258140ba5316fa74539286 Mon Sep 17 00:00:00 2001 From: QuentinHsu Date: Sat, 11 May 2024 16:19:35 +0800 Subject: [PATCH 06/32] =?UTF-8?q?refactor:=20=E8=BF=90=E8=90=A5=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE-=E6=95=B0=E6=8D=AE=E7=9C=8B=E6=9D=BF=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/OperationSetting.js | 48 +----- .../Operation/SettingsDataDashboard.js | 147 ++++++++++++++++++ .../Setting/Operation/SettingsDrawing.js | 1 + 3 files changed, 155 insertions(+), 41 deletions(-) create mode 100644 web/src/pages/Setting/Operation/SettingsDataDashboard.js diff --git a/web/src/components/OperationSetting.js b/web/src/components/OperationSetting.js index 6835e1a..09acab0 100644 --- a/web/src/components/OperationSetting.js +++ b/web/src/components/OperationSetting.js @@ -5,6 +5,7 @@ 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 { API, @@ -48,7 +49,7 @@ const OperationSetting = () => { MjModeClearEnabled: false, MjForwardUrlEnabled: false, DrawingEnabled: false, - DataExportEnabled: '', + DataExportEnabled: false, DataExportDefaultTime: 'hour', DataExportInterval: 5, DefaultCollapseSidebar: false, // 默认折叠侧边栏 @@ -56,15 +57,7 @@ const OperationSetting = () => { }); 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; @@ -242,6 +235,10 @@ const OperationSetting = () => { + {/* 数据看板 */} + + +
@@ -265,37 +262,6 @@ const OperationSetting = () => { {/* />*/} {/**/} -
- 数据看板 -
- - - - -
监控设置 diff --git a/web/src/pages/Setting/Operation/SettingsDataDashboard.js b/web/src/pages/Setting/Operation/SettingsDataDashboard.js new file mode 100644 index 0000000..85fd1dc --- /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('更新成功'); + }) + .catch(() => { + showError('更新失败'); + }) + .finally(() => { + setLoading(false); + setInputsRow(structuredClone(inputs)); + }); + } + + 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 index 085cfd1..560bbc7 100644 --- a/web/src/pages/Setting/Operation/SettingsDrawing.js +++ b/web/src/pages/Setting/Operation/SettingsDrawing.js @@ -64,6 +64,7 @@ export default function SettingsDrawing(props) { setInputs(currentInputs); setInputsRow(structuredClone(currentInputs)); refForm.current.setValues(currentInputs); + localStorage.setItem('mj_notify_enabled', String(inputs.MjNotifyEnabled)); }, [props.options]); return ( <> From 88bc2958558f1e8baffd354aec106da24893e631 Mon Sep 17 00:00:00 2001 From: QuentinHsu Date: Sat, 11 May 2024 17:20:18 +0800 Subject: [PATCH 07/32] =?UTF-8?q?refactor:=20=E8=BF=90=E8=90=A5=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE-=E7=9B=91=E6=8E=A7=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/OperationSetting.js | 58 +------ .../Setting/Operation/SettingsMonitoring.js | 154 ++++++++++++++++++ 2 files changed, 162 insertions(+), 50 deletions(-) create mode 100644 web/src/pages/Setting/Operation/SettingsMonitoring.js diff --git a/web/src/components/OperationSetting.js b/web/src/components/OperationSetting.js index 09acab0..b9e8e9e 100644 --- a/web/src/components/OperationSetting.js +++ b/web/src/components/OperationSetting.js @@ -6,6 +6,7 @@ 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 { API, @@ -33,8 +34,8 @@ const OperationSetting = () => { ChatLink: '', ChatLink2: '', // 添加的新状态变量 QuotaPerUnit: 0, - AutomaticDisableChannelEnabled: '', - AutomaticEnableChannelEnabled: '', + AutomaticDisableChannelEnabled: false, + AutomaticEnableChannelEnabled: false, ChannelDisableThreshold: 0, LogConsumeEnabled: false, DisplayInCurrencyEnabled: false, @@ -220,7 +221,7 @@ const OperationSetting = () => { return ( <> {/* 通用设置 */} - + {/* 绘图设置 */} @@ -239,6 +240,10 @@ const OperationSetting = () => { + {/* 监控设置 */} + + +
@@ -262,53 +267,6 @@ const OperationSetting = () => { {/* />*/} {/**/} - -
- 监控设置 -
- - - - - - - - - { - submitConfig('monitor').then(); - }} - > - 保存监控设置 -
额度设置 diff --git a/web/src/pages/Setting/Operation/SettingsMonitoring.js b/web/src/pages/Setting/Operation/SettingsMonitoring.js new file mode 100644 index 0000000..6fed48f --- /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('更新成功'); + }) + .catch(() => { + showError('更新失败'); + }) + .finally(() => { + setLoading(false); + setInputsRow(structuredClone(inputs)); + }); + } + + 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, + }) + } + /> + + + + + + + + + + ); +} From 968ef1e5fa425db1d5681af6463f8014b0df742e Mon Sep 17 00:00:00 2001 From: QuentinHsu Date: Sat, 11 May 2024 17:48:05 +0800 Subject: [PATCH 08/32] =?UTF-8?q?refactor:=20=E8=BF=90=E8=90=A5=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE-=E9=A2=9D=E5=BA=A6=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/OperationSetting.js | 58 +------ .../Setting/Operation/SettingsCreditLimit.js | 156 ++++++++++++++++++ 2 files changed, 161 insertions(+), 53 deletions(-) create mode 100644 web/src/pages/Setting/Operation/SettingsCreditLimit.js diff --git a/web/src/components/OperationSetting.js b/web/src/components/OperationSetting.js index b9e8e9e..2dd5489 100644 --- a/web/src/components/OperationSetting.js +++ b/web/src/components/OperationSetting.js @@ -7,6 +7,7 @@ import SettingsSensitiveWords from '../pages/Setting/Operation/SettingsSensitive 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 { API, @@ -244,6 +245,10 @@ const OperationSetting = () => { + {/* 额度设置 */} + + +
@@ -267,59 +272,6 @@ const OperationSetting = () => { {/* />*/} {/**/} - -
- 额度设置 -
- - - - - - - { - submitConfig('quota').then(); - }} - > - 保存额度设置 -
倍率设置 diff --git a/web/src/pages/Setting/Operation/SettingsCreditLimit.js b/web/src/pages/Setting/Operation/SettingsCreditLimit.js new file mode 100644 index 0000000..4fcbb4e --- /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('更新成功'); + }) + .catch(() => { + showError('更新失败'); + }) + .finally(() => { + setLoading(false); + setInputsRow(structuredClone(inputs)); + }); + } + + 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), + }) + } + /> + + + + + + + + + + + ); +} From 4641d446155377665f5abb46b294107c41542447 Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Sat, 11 May 2024 21:18:30 +0800 Subject: [PATCH 09/32] =?UTF-8?q?feat:=20=E9=99=90=E5=88=B6=E9=82=AE?= =?UTF-8?q?=E7=AE=B1=E5=88=AB=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/misc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller/misc.go b/controller/misc.go index 273a618..8c59952 100644 --- a/controller/misc.go +++ b/controller/misc.go @@ -147,7 +147,7 @@ func SendEmailVerification(c *gin.Context) { } } if common.EmailAliasRestrictionEnabled { - containsSpecialSymbols := strings.Contains(localPart, "+") || strings.Count(localPart, ".") > 1 + containsSpecialSymbols := strings.Contains(localPart, "+") || strings.Contains(localPart, ".") if containsSpecialSymbols { c.JSON(http.StatusOK, gin.H{ "success": false, From 6fb1fbfe96962e72341e6f59657de6369667f74e Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Sun, 12 May 2024 15:35:57 +0800 Subject: [PATCH 10/32] =?UTF-8?q?feat:=20=E6=97=A5=E5=BF=97=E8=AF=A6?= =?UTF-8?q?=E6=83=85=E5=B1=95=E7=A4=BA=E6=A8=A1=E5=9E=8B=E4=BB=B7=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/model-ratio.go | 1 + common/utils.go | 9 ++++++ model/log.go | 5 ++- relay/relay-audio.go | 5 ++- relay/relay-image.go | 5 ++- relay/relay-mj.go | 10 ++++-- relay/relay-text.go | 7 ++++- web/src/components/LogsTable.js | 54 +++++++++++++++++++++++++++------ web/src/helpers/render.js | 26 ++++++++++++++++ 9 files changed, 106 insertions(+), 16 deletions(-) diff --git a/common/model-ratio.go b/common/model-ratio.go index b0a97c0..9ddbe12 100644 --- a/common/model-ratio.go +++ b/common/model-ratio.go @@ -28,6 +28,7 @@ var DefaultModelRatio = map[string]float64{ "gpt-4-vision-preview": 5, // $0.01 / 1K tokens "gpt-4-1106-vision-preview": 5, // $0.01 / 1K tokens "gpt-4-turbo": 5, // $0.01 / 1K tokens + "gpt-4-turbo-2024-04-09": 5, // $0.01 / 1K tokens "gpt-3.5-turbo": 0.25, // $0.0015 / 1K tokens //"gpt-3.5-turbo-0301": 0.75, //deprecated "gpt-3.5-turbo-0613": 0.75, diff --git a/common/utils.go b/common/utils.go index d540c2e..657ffd4 100644 --- a/common/utils.go +++ b/common/utils.go @@ -1,6 +1,7 @@ package common import ( + "encoding/json" "fmt" "github.com/google/uuid" "html/template" @@ -241,3 +242,11 @@ func RandomSleep() { // Sleep for 0-3000 ms time.Sleep(time.Duration(rand.Intn(3000)) * time.Millisecond) } + +func MapToJsonStr(m map[string]interface{}) string { + bytes, err := json.Marshal(m) + if err != nil { + return "" + } + return string(bytes) +} diff --git a/model/log.go b/model/log.go index 2740c5a..57c64d0 100644 --- a/model/log.go +++ b/model/log.go @@ -24,6 +24,7 @@ type Log struct { IsStream bool `json:"is_stream" gorm:"default:false"` ChannelId int `json:"channel" gorm:"index"` TokenId int `json:"token_id" gorm:"default:0;index"` + Other string `json:"other"` } const ( @@ -57,12 +58,13 @@ func RecordLog(userId int, logType int, content string) { } } -func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptTokens int, completionTokens int, modelName string, tokenName string, quota int, content string, tokenId int, userQuota int, useTimeSeconds int, isStream bool) { +func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptTokens int, completionTokens int, modelName string, tokenName string, quota int, content string, tokenId int, userQuota int, useTimeSeconds int, isStream bool, other map[string]interface{}) { common.LogInfo(ctx, fmt.Sprintf("record consume log: userId=%d, 用户调用前余额=%d, channelId=%d, promptTokens=%d, completionTokens=%d, modelName=%s, tokenName=%s, quota=%d, content=%s", userId, userQuota, channelId, promptTokens, completionTokens, modelName, tokenName, quota, content)) if !common.LogConsumeEnabled { return } username, _ := CacheGetUsername(userId) + otherStr := common.MapToJsonStr(other) log := &Log{ UserId: userId, Username: username, @@ -78,6 +80,7 @@ func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptToke TokenId: tokenId, UseTime: useTimeSeconds, IsStream: isStream, + Other: otherStr, } err := DB.Create(log).Error if err != nil { diff --git a/relay/relay-audio.go b/relay/relay-audio.go index 09ac2a0..ef89597 100644 --- a/relay/relay-audio.go +++ b/relay/relay-audio.go @@ -196,7 +196,10 @@ func AudioHelper(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusCode { if quota != 0 { tokenName := c.GetString("token_name") logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f", modelRatio, groupRatio) - model.RecordConsumeLog(ctx, userId, channelId, promptTokens, 0, audioRequest.Model, tokenName, quota, logContent, tokenId, userQuota, int(useTimeSeconds), false) + other := make(map[string]interface{}) + other["model_ratio"] = modelRatio + other["group_ratio"] = groupRatio + model.RecordConsumeLog(ctx, userId, channelId, promptTokens, 0, audioRequest.Model, tokenName, quota, logContent, tokenId, userQuota, int(useTimeSeconds), false, other) model.UpdateUserUsedQuotaAndRequestCount(userId, quota) channelId := c.GetInt("channel_id") model.UpdateChannelUsedQuota(channelId, quota) diff --git a/relay/relay-image.go b/relay/relay-image.go index ce072d1..7f8cd9e 100644 --- a/relay/relay-image.go +++ b/relay/relay-image.go @@ -191,7 +191,10 @@ func RelayImageHelper(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusC quality = "hd" } logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f, 大小 %s, 品质 %s", modelRatio, groupRatio, imageRequest.Size, quality) - model.RecordConsumeLog(ctx, userId, channelId, 0, 0, imageRequest.Model, tokenName, quota, logContent, tokenId, userQuota, int(useTimeSeconds), false) + other := make(map[string]interface{}) + other["model_ratio"] = modelRatio + other["group_ratio"] = groupRatio + model.RecordConsumeLog(ctx, userId, channelId, 0, 0, imageRequest.Model, tokenName, quota, logContent, tokenId, userQuota, int(useTimeSeconds), false, other) model.UpdateUserUsedQuotaAndRequestCount(userId, quota) channelId := c.GetInt("channel_id") model.UpdateChannelUsedQuota(channelId, quota) diff --git a/relay/relay-mj.go b/relay/relay-mj.go index 27b4c6d..16ad412 100644 --- a/relay/relay-mj.go +++ b/relay/relay-mj.go @@ -202,7 +202,10 @@ func RelaySwapFace(c *gin.Context) *dto.MidjourneyResponse { if quota != 0 { tokenName := c.GetString("token_name") logContent := fmt.Sprintf("模型固定价格 %.2f,分组倍率 %.2f,操作 %s", modelPrice, groupRatio, constant.MjActionSwapFace) - model.RecordConsumeLog(ctx, userId, channelId, 0, 0, modelName, tokenName, quota, logContent, tokenId, userQuota, 0, false) + other := make(map[string]interface{}) + other["model_price"] = modelPrice + other["group_ratio"] = groupRatio + model.RecordConsumeLog(ctx, userId, channelId, 0, 0, modelName, tokenName, quota, logContent, tokenId, userQuota, 0, false, other) model.UpdateUserUsedQuotaAndRequestCount(userId, quota) channelId := c.GetInt("channel_id") model.UpdateChannelUsedQuota(channelId, quota) @@ -498,7 +501,10 @@ func RelayMidjourneySubmit(c *gin.Context, relayMode int) *dto.MidjourneyRespons if quota != 0 { tokenName := c.GetString("token_name") logContent := fmt.Sprintf("模型固定价格 %.2f,分组倍率 %.2f,操作 %s", modelPrice, groupRatio, midjRequest.Action) - model.RecordConsumeLog(ctx, userId, channelId, 0, 0, modelName, tokenName, quota, logContent, tokenId, userQuota, 0, false) + other := make(map[string]interface{}) + other["model_price"] = modelPrice + other["group_ratio"] = groupRatio + model.RecordConsumeLog(ctx, userId, channelId, 0, 0, modelName, tokenName, quota, logContent, tokenId, userQuota, 0, false, other) model.UpdateUserUsedQuotaAndRequestCount(userId, quota) channelId := c.GetInt("channel_id") model.UpdateChannelUsedQuota(channelId, quota) diff --git a/relay/relay-text.go b/relay/relay-text.go index 7c2720e..9010381 100644 --- a/relay/relay-text.go +++ b/relay/relay-text.go @@ -315,7 +315,12 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, textRe logModel = "gpt-4-gizmo-*" logContent += fmt.Sprintf(",模型 %s", textRequest.Model) } - model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, promptTokens, completionTokens, logModel, tokenName, quota, logContent, relayInfo.TokenId, userQuota, int(useTimeSeconds), relayInfo.IsStream) + other := make(map[string]interface{}) + other["model_ratio"] = modelRatio + other["group_ratio"] = groupRatio + other["completion_ratio"] = completionRatio + other["model_price"] = modelPrice + model.RecordConsumeLog(ctx, relayInfo.UserId, relayInfo.ChannelId, promptTokens, completionTokens, logModel, tokenName, quota, logContent, relayInfo.TokenId, userQuota, int(useTimeSeconds), relayInfo.IsStream, other) //if quota != 0 { // diff --git a/web/src/components/LogsTable.js b/web/src/components/LogsTable.js index 9331fa7..0de3632 100644 --- a/web/src/components/LogsTable.js +++ b/web/src/components/LogsTable.js @@ -19,9 +19,15 @@ import { Spin, Table, Tag, + Tooltip, } from '@douyinfe/semi-ui'; import { ITEMS_PER_PAGE } from '../constants'; -import { renderNumber, renderQuota, stringToColor } from '../helpers/render'; +import { + renderModelPrice, + renderNumber, + renderQuota, + stringToColor, +} from '../helpers/render'; import Paragraph from '@douyinfe/semi-ui/lib/es/typography/paragraph'; const { Header } = Layout; @@ -292,16 +298,44 @@ const LogsTable = () => { title: '详情', dataIndex: 'content', render: (text, record, index) => { + if (record.other === '') { + return ( + + {text} + + ); + } + let other = JSON.parse(record.other); + let content = renderModelPrice( + other.model_ratio, + other.model_price, + other.completion_ratio, + other.group_ratio, + ); return ( - - {text} - + + + {text} + + ); }, }, diff --git a/web/src/helpers/render.js b/web/src/helpers/render.js index 8cea432..9d36682 100644 --- a/web/src/helpers/render.js +++ b/web/src/helpers/render.js @@ -135,6 +135,32 @@ export function renderQuota(quota, digits = 2) { return renderNumber(quota); } +export function renderModelPrice( + modelRatio, + modelPrice = -1, + completionRatio, + groupRatio, +) { + // 1 ratio = $0.002 / 1K tokens + if (modelPrice !== -1) { + return '模型价格:$' + modelPrice * groupRatio; + } else { + if (completionRatio === undefined) { + completionRatio = 0; + } + let inputRatioPrice = modelRatio * 0.002 * groupRatio; + let completionRatioPrice = + modelRatio * completionRatio * 0.002 * groupRatio; + return ( + '输入:$' + + inputRatioPrice.toFixed(3) + + '/1K tokens,补全:$' + + completionRatioPrice.toFixed(3) + + '/1K tokens' + ); + } +} + export function renderQuotaWithPrompt(quota, digits) { let displayInCurrency = localStorage.getItem('display_in_currency'); displayInCurrency = displayInCurrency === 'true'; From b427f0278f1047558f0a676dfbb9d697322b6ac0 Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Sun, 12 May 2024 16:06:19 +0800 Subject: [PATCH 11/32] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B9=E4=BE=BF?= =?UTF-8?q?=E5=9C=B0=E7=BC=96=E8=BE=91=E7=94=A8=E6=88=B7=E9=A2=9D=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/pages/User/EditUser.js | 56 ++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/web/src/pages/User/EditUser.js b/web/src/pages/User/EditUser.js index 596edcc..90bbc4b 100644 --- a/web/src/pages/User/EditUser.js +++ b/web/src/pages/User/EditUser.js @@ -1,12 +1,13 @@ import React, { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { API, isMobile, showError, showSuccess } from '../../helpers'; -import { renderQuotaWithPrompt } from '../../helpers/render'; +import { renderQuota, renderQuotaWithPrompt } from '../../helpers/render'; import Title from '@douyinfe/semi-ui/lib/es/typography/title'; import { Button, Divider, Input, + Modal, Select, SideSheet, Space, @@ -17,6 +18,8 @@ import { const EditUser = (props) => { const userId = props.editingUser.id; const [loading, setLoading] = useState(true); + const [addQuotaModalOpen, setIsModalOpen] = useState(false); + const [addQuotaLocal, setAddQuotaLocal] = useState(0); const [inputs, setInputs] = useState({ username: '', display_name: '', @@ -107,6 +110,16 @@ const EditUser = (props) => { setLoading(false); }; + const addLocalQuota = () => { + let newQuota = parseInt(quota) + addQuotaLocal; + setInputs((inputs) => ({ ...inputs, quota: newQuota })); + }; + + const openAddQuotaModal = () => { + setAddQuotaLocal(0); + setIsModalOpen(true); + }; + return ( <> {
{`剩余额度${renderQuotaWithPrompt(quota)}`}
- handleInputChange('quota', value)} - value={quota} - type={'number'} - autoComplete='new-password' - /> + + handleInputChange('quota', value)} + value={quota} + type={'number'} + autoComplete='new-password' + /> + + )} 以下信息不可修改 @@ -245,6 +261,28 @@ const EditUser = (props) => { />
+ { + addLocalQuota(); + setIsModalOpen(false); + }} + onCancel={() => setIsModalOpen(false)} + closable={null} + > +
+ {`新额度${renderQuota(quota)} + ${renderQuota(addQuotaLocal)} = ${renderQuota(quota + addQuotaLocal)}`} +
+ setAddQuotaLocal(parseInt(value))} + value={addQuotaLocal} + type={'number'} + autoComplete='new-password' + /> +
); }; From d8c006046f5874aea5d193deef2b4e5c7866b737 Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Sun, 12 May 2024 16:12:31 +0800 Subject: [PATCH 12/32] =?UTF-8?q?feat:=20=E7=BC=96=E8=BE=91=E9=A2=9D?= =?UTF-8?q?=E5=BA=A6=E6=94=AF=E6=8C=81=E8=B4=9F=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/pages/User/EditUser.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/web/src/pages/User/EditUser.js b/web/src/pages/User/EditUser.js index 90bbc4b..fa5f890 100644 --- a/web/src/pages/User/EditUser.js +++ b/web/src/pages/User/EditUser.js @@ -19,7 +19,7 @@ const EditUser = (props) => { const userId = props.editingUser.id; const [loading, setLoading] = useState(true); const [addQuotaModalOpen, setIsModalOpen] = useState(false); - const [addQuotaLocal, setAddQuotaLocal] = useState(0); + const [addQuotaLocal, setAddQuotaLocal] = useState(''); const [inputs, setInputs] = useState({ username: '', display_name: '', @@ -111,12 +111,12 @@ const EditUser = (props) => { }; const addLocalQuota = () => { - let newQuota = parseInt(quota) + addQuotaLocal; + let newQuota = parseInt(quota) + parseInt(addQuotaLocal); setInputs((inputs) => ({ ...inputs, quota: newQuota })); }; const openAddQuotaModal = () => { - setAddQuotaLocal(0); + setAddQuotaLocal('0'); setIsModalOpen(true); }; @@ -272,12 +272,14 @@ const EditUser = (props) => { closable={null} >
- {`新额度${renderQuota(quota)} + ${renderQuota(addQuotaLocal)} = ${renderQuota(quota + addQuotaLocal)}`} + {`新额度${renderQuota(quota)} + ${renderQuota(addQuotaLocal)} = ${renderQuota(quota + parseInt(addQuotaLocal))}`}
setAddQuotaLocal(parseInt(value))} + placeholder={'需要添加的额度(支持负数)'} + onChange={(value) => { + setAddQuotaLocal(value); + }} value={addQuotaLocal} type={'number'} autoComplete='new-password' From 2dbf50dc0776e2de605357b541e6a1ea329864f6 Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Sun, 12 May 2024 19:07:33 +0800 Subject: [PATCH 13/32] =?UTF-8?q?feat:=20=E5=A1=AB=E5=85=A5=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/constants.go | 2 + controller/model.go | 35 +++++++-- relay/channel/ai360/constants.go | 2 + relay/channel/ollama/constants.go | 4 +- relay/channel/openai/constant.go | 2 +- relay/constant/api_type.go | 12 ++- router/api-router.go | 1 + web/src/components/ChannelsTable.js | 30 ++++--- web/src/components/utils.js | 29 +++++++ web/src/constants/channel.constants.js | 4 +- web/src/pages/Channel/EditChannel.js | 105 ++++--------------------- 11 files changed, 111 insertions(+), 115 deletions(-) diff --git a/common/constants.go b/common/constants.go index 99c78ac..cb58391 100644 --- a/common/constants.go +++ b/common/constants.go @@ -208,6 +208,8 @@ const ( ChannelTypeLingYiWanWu = 31 ChannelTypeAws = 33 ChannelTypeCohere = 34 + + ChannelTypeDummy // this one is only for count, do not add any channel after this ) var ChannelBaseURLs = []string{ diff --git a/controller/model.go b/controller/model.go index 1e78b4c..4d8e5e8 100644 --- a/controller/model.go +++ b/controller/model.go @@ -3,14 +3,17 @@ package controller import ( "fmt" "github.com/gin-gonic/gin" + "log" "net/http" + "one-api/common" "one-api/constant" "one-api/dto" "one-api/model" "one-api/relay" "one-api/relay/channel/ai360" - "one-api/relay/channel/moonshot" "one-api/relay/channel/lingyiwanwu" + "one-api/relay/channel/moonshot" + relaycommon "one-api/relay/common" relayconstant "one-api/relay/constant" ) @@ -43,6 +46,7 @@ type OpenAIModels struct { var openAIModels []OpenAIModels var openAIModelsMap map[string]OpenAIModels +var channelId2Models map[int][]string func init() { var permission []OpenAIModelPermission @@ -85,7 +89,7 @@ func init() { Id: modelName, Object: "model", Created: 1626777600, - OwnedBy: "360", + OwnedBy: ai360.ChannelName, Permission: permission, Root: modelName, Parent: nil, @@ -128,6 +132,18 @@ func init() { for _, model := range openAIModels { openAIModelsMap[model.Id] = model } + channelId2Models = make(map[int][]string) + for i := 1; i <= common.ChannelTypeDummy; i++ { + apiType := relayconstant.ChannelType2APIType(i) + if apiType == -1 || apiType == relayconstant.APITypeAIProxyLibrary { + continue + } + log.Println(apiType) + meta := &relaycommon.RelayInfo{ChannelType: i} + adaptor := relay.GetAdaptor(apiType) + adaptor.Init(meta, dto.GeneralOpenAIRequest{}) + channelId2Models[i] = adaptor.GetModelList() + } } func ListModels(c *gin.Context) { @@ -148,15 +164,22 @@ func ListModels(c *gin.Context) { } } c.JSON(200, gin.H{ - "object": "list", - "data": userOpenAiModels, + "success": true, + "data": userOpenAiModels, }) } func ChannelListModels(c *gin.Context) { c.JSON(200, gin.H{ - "object": "list", - "data": openAIModels, + "success": true, + "data": openAIModels, + }) +} + +func DashboardListModels(c *gin.Context) { + c.JSON(200, gin.H{ + "success": true, + "data": channelId2Models, }) } diff --git a/relay/channel/ai360/constants.go b/relay/channel/ai360/constants.go index cfc3cb2..82698fa 100644 --- a/relay/channel/ai360/constants.go +++ b/relay/channel/ai360/constants.go @@ -6,3 +6,5 @@ var ModelList = []string{ "embedding_s1_v1", "semantic_similarity_s1_v1", } + +var ChannelName = "ai360" diff --git a/relay/channel/ollama/constants.go b/relay/channel/ollama/constants.go index 970e977..682626a 100644 --- a/relay/channel/ollama/constants.go +++ b/relay/channel/ollama/constants.go @@ -1,5 +1,7 @@ package ollama -var ModelList []string +var ModelList = []string{ + "llama3-7b", +} var ChannelName = "ollama" diff --git a/relay/channel/openai/constant.go b/relay/channel/openai/constant.go index 91f4e51..8c560c7 100644 --- a/relay/channel/openai/constant.go +++ b/relay/channel/openai/constant.go @@ -6,7 +6,7 @@ var ModelList = []string{ "gpt-3.5-turbo-instruct", "gpt-4", "gpt-4-0314", "gpt-4-0613", "gpt-4-1106-preview", "gpt-4-0125-preview", "gpt-4-32k", "gpt-4-32k-0314", "gpt-4-32k-0613", - "gpt-4-turbo-preview", + "gpt-4-turbo-preview", "gpt-4-turbo", "gpt-4-turbo-2024-04-09", "gpt-4-vision-preview", "text-embedding-ada-002", "text-embedding-3-small", "text-embedding-3-large", "text-curie-001", "text-babbage-001", "text-ada-001", "text-davinci-002", "text-davinci-003", diff --git a/relay/constant/api_type.go b/relay/constant/api_type.go index 7f11ae2..1bc8b47 100644 --- a/relay/constant/api_type.go +++ b/relay/constant/api_type.go @@ -25,8 +25,18 @@ const ( ) func ChannelType2APIType(channelType int) int { - apiType := APITypeOpenAI + apiType := -1 switch channelType { + case common.ChannelTypeOpenAI: + apiType = APITypeOpenAI + case common.ChannelTypeAzure: + apiType = APITypeOpenAI + case common.ChannelTypeMoonshot: + apiType = APITypeOpenAI + case common.ChannelTypeLingYiWanWu: + apiType = APITypeOpenAI + case common.ChannelType360: + apiType = APITypeOpenAI case common.ChannelTypeAnthropic: apiType = APITypeAnthropic case common.ChannelTypeBaidu: diff --git a/router/api-router.go b/router/api-router.go index 8547454..8c0ae30 100644 --- a/router/api-router.go +++ b/router/api-router.go @@ -14,6 +14,7 @@ func SetApiRouter(router *gin.Engine) { apiRouter.Use(middleware.GlobalAPIRateLimit()) { apiRouter.GET("/status", controller.GetStatus) + apiRouter.GET("/models", middleware.UserAuth(), controller.DashboardListModels) apiRouter.GET("/status/test", middleware.AdminAuth(), controller.TestStatus) apiRouter.GET("/notice", controller.GetNotice) apiRouter.GET("/about", controller.GetAbout) diff --git a/web/src/components/ChannelsTable.js b/web/src/components/ChannelsTable.js index ebfcf4d..452309c 100644 --- a/web/src/components/ChannelsTable.js +++ b/web/src/components/ChannelsTable.js @@ -31,6 +31,7 @@ import { } from '@douyinfe/semi-ui'; import EditChannel from '../pages/Channel/EditChannel'; import { IconTreeTriangleDown } from '@douyinfe/semi-icons'; +import { loadChannelModels } from './utils.js'; function renderTimestamp(timestamp) { return <>{timestamp2string(timestamp)}; @@ -354,27 +355,29 @@ const ChannelsTable = () => { }; const copySelectedChannel = async (id) => { - const channelToCopy = channels.find(channel => String(channel.id) === String(id)); - console.log(channelToCopy) + const channelToCopy = channels.find( + (channel) => String(channel.id) === String(id), + ); + console.log(channelToCopy); channelToCopy.name += '_复制'; channelToCopy.created_time = null; channelToCopy.balance = 0; channelToCopy.used_quota = 0; if (!channelToCopy) { - showError("渠道未找到,请刷新页面后重试。"); - return; + showError('渠道未找到,请刷新页面后重试。'); + return; } try { - const newChannel = {...channelToCopy, id: undefined}; - const response = await API.post('/api/channel/', newChannel); - if (response.data.success) { - showSuccess("渠道复制成功"); - await refresh(); - } else { - showError(response.data.message); - } + const newChannel = { ...channelToCopy, id: undefined }; + const response = await API.post('/api/channel/', newChannel); + if (response.data.success) { + showSuccess('渠道复制成功'); + await refresh(); + } else { + showError(response.data.message); + } } catch (error) { - showError("渠道复制失败: " + error.message); + showError('渠道复制失败: ' + error.message); } }; @@ -395,6 +398,7 @@ const ChannelsTable = () => { showError(reason); }); fetchGroups().then(); + loadChannelModels().then(); }, []); const manageChannel = async (id, action, record, value) => { diff --git a/web/src/components/utils.js b/web/src/components/utils.js index 59e3a01..1f0ee30 100644 --- a/web/src/components/utils.js +++ b/web/src/components/utils.js @@ -18,3 +18,32 @@ export async function onGitHubOAuthClicked(github_client_id) { `https://github.com/login/oauth/authorize?client_id=${github_client_id}&state=${state}&scope=user:email`, ); } + +let channelModels = undefined; +export async function loadChannelModels() { + const res = await API.get('/api/models'); + const { success, data } = res.data; + if (!success) { + return; + } + channelModels = data; + localStorage.setItem('channel_models', JSON.stringify(data)); +} + +export function getChannelModels(type) { + if (channelModels !== undefined && type in channelModels) { + if (!channelModels[type]) { + return []; + } + return channelModels[type]; + } + let models = localStorage.getItem('channel_models'); + if (!models) { + return []; + } + channelModels = JSON.parse(models); + if (type in channelModels) { + return channelModels[type]; + } + return []; +} diff --git a/web/src/constants/channel.constants.js b/web/src/constants/channel.constants.js index c6f0899..94b2fc6 100644 --- a/web/src/constants/channel.constants.js +++ b/web/src/constants/channel.constants.js @@ -86,13 +86,13 @@ export const CHANNEL_OPTIONS = [ label: '智谱 ChatGLM', }, { - key: 16, + key: 26, text: '智谱 GLM-4V', value: 26, color: 'purple', label: '智谱 GLM-4V', }, - { key: 16, text: 'Moonshot', value: 25, color: 'green', label: 'Moonshot' }, + { key: 25, text: 'Moonshot', value: 25, color: 'green', label: 'Moonshot' }, { key: 19, text: '360 智脑', value: 19, color: 'blue', label: '360 智脑' }, { key: 23, text: '腾讯混元', value: 23, color: 'teal', label: '腾讯混元' }, { key: 31, text: '零一万物', value: 31, color: 'green', label: '零一万物' }, diff --git a/web/src/pages/Channel/EditChannel.js b/web/src/pages/Channel/EditChannel.js index cc8707d..79e54b4 100644 --- a/web/src/pages/Channel/EditChannel.js +++ b/web/src/pages/Channel/EditChannel.js @@ -23,6 +23,7 @@ import { Banner, } from '@douyinfe/semi-ui'; import { Divider } from 'semantic-ui-react'; +import { getChannelModels, loadChannelModels } from '../../components/utils.js'; const MODEL_MAPPING_EXAMPLE = { 'gpt-3.5-turbo-0301': 'gpt-3.5-turbo', @@ -87,97 +88,9 @@ const EditChannel = (props) => { const [customModel, setCustomModel] = useState(''); const handleInputChange = (name, value) => { setInputs((inputs) => ({ ...inputs, [name]: value })); - if (name === 'type' && inputs.models.length === 0) { + if (name === 'type') { let localModels = []; switch (value) { - case 33: - case 14: - localModels = [ - 'claude-instant-1.2', - 'claude-2', - 'claude-2.0', - 'claude-2.1', - 'claude-3-opus-20240229', - 'claude-3-sonnet-20240229', - 'claude-3-haiku-20240307', - ]; - break; - case 11: - localModels = ['PaLM-2']; - break; - case 15: - localModels = [ - 'ERNIE-Bot', - 'ERNIE-Bot-turbo', - 'ERNIE-Bot-4', - 'Embedding-V1', - ]; - break; - case 17: - localModels = [ - 'qwen-turbo', - 'qwen-plus', - 'qwen-max', - 'qwen-max-longcontext', - 'text-embedding-v1', - ]; - break; - case 16: - localModels = ['chatglm_pro', 'chatglm_std', 'chatglm_lite']; - break; - case 18: - localModels = [ - 'SparkDesk', - 'SparkDesk-v1.1', - 'SparkDesk-v2.1', - 'SparkDesk-v3.1', - 'SparkDesk-v3.5', - ]; - break; - case 19: - localModels = [ - '360GPT_S2_V9', - 'embedding-bert-512-v1', - 'embedding_s1_v1', - 'semantic_similarity_s1_v1', - ]; - break; - case 23: - localModels = ['hunyuan']; - break; - case 24: - localModels = [ - 'gemini-1.0-pro-001', - 'gemini-1.0-pro-vision-001', - 'gemini-1.5-pro', - 'gemini-1.5-pro-latest', - 'gemini-pro', - 'gemini-pro-vision', - ]; - break; - case 34: - localModels = [ - 'command-r', - 'command-r-plus', - 'command-light', - 'command-light-nightly', - 'command', - 'command-nightly', - ]; - break; - case 25: - localModels = [ - 'moonshot-v1-8k', - 'moonshot-v1-32k', - 'moonshot-v1-128k', - ]; - break; - case 26: - localModels = ['glm-4', 'glm-4v', 'glm-3-turbo']; - break; - case 31: - localModels = ['yi-34b-chat-0205', 'yi-34b-chat-200k', 'yi-vl-plus']; - break; case 2: localModels = [ 'mj_imagine', @@ -207,8 +120,14 @@ const EditChannel = (props) => { 'mj_pan', ]; break; + default: + localModels = getChannelModels(value); + break; } - setInputs((inputs) => ({ ...inputs, models: localModels })); + if (inputs.models.length === 0) { + setInputs((inputs) => ({ ...inputs, models: localModels })); + } + setBasicModels(localModels); } //setAutoBan }; @@ -244,6 +163,7 @@ const EditChannel = (props) => { } else { setAutoBan(true); } + setBasicModels(getChannelModels(data.type)); // console.log(data); } else { showError(message); @@ -312,6 +232,9 @@ const EditChannel = (props) => { loadChannel().then(() => {}); } else { setInputs(originInputs); + let localModels = getChannelModels(inputs.type); + setBasicModels(localModels); + setInputs((inputs) => ({ ...inputs, models: localModels })); } }, [props.editingChannel.id]); @@ -596,7 +519,7 @@ const EditChannel = (props) => { handleInputChange('models', basicModels); }} > - 填入基础模型 + 填入相关模型 } From fd19798c92efc17b926062d4f1c3ff3890ae7b08 Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Mon, 13 May 2024 14:32:32 +0800 Subject: [PATCH 18/32] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E6=B8=A0=E9=81=93=E5=87=BA=E9=94=99=20#243?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/channel-test.go | 2 +- controller/model.go | 4 ++-- relay/common/relay_info.go | 2 +- relay/constant/api_type.go | 15 +++++---------- web/src/helpers/render.js | 5 ++--- 5 files changed, 11 insertions(+), 17 deletions(-) diff --git a/controller/channel-test.go b/controller/channel-test.go index f37f309..7474cb4 100644 --- a/controller/channel-test.go +++ b/controller/channel-test.go @@ -53,7 +53,7 @@ func testChannel(channel *model.Channel, testModel string) (err error, openaiErr } meta := relaycommon.GenRelayInfo(c) - apiType := constant.ChannelType2APIType(channel.Type) + apiType, _ := constant.ChannelType2APIType(channel.Type) adaptor := relay.GetAdaptor(apiType) if adaptor == nil { return fmt.Errorf("invalid api type: %d, adaptor is nil", apiType), nil diff --git a/controller/model.go b/controller/model.go index 890b260..c9c50db 100644 --- a/controller/model.go +++ b/controller/model.go @@ -138,8 +138,8 @@ func init() { } channelId2Models = make(map[int][]string) for i := 1; i <= common.ChannelTypeDummy; i++ { - apiType := relayconstant.ChannelType2APIType(i) - if apiType == -1 || apiType == relayconstant.APITypeAIProxyLibrary { + apiType, success := relayconstant.ChannelType2APIType(i) + if !success || apiType == relayconstant.APITypeAIProxyLibrary { continue } meta := &relaycommon.RelayInfo{ChannelType: i} diff --git a/relay/common/relay_info.go b/relay/common/relay_info.go index 7ae9dd4..b40352e 100644 --- a/relay/common/relay_info.go +++ b/relay/common/relay_info.go @@ -38,7 +38,7 @@ func GenRelayInfo(c *gin.Context) *RelayInfo { tokenUnlimited := c.GetBool("token_unlimited_quota") startTime := time.Now() - apiType := constant.ChannelType2APIType(channelType) + apiType, _ := constant.ChannelType2APIType(channelType) info := &RelayInfo{ RelayMode: constant.Path2RelayMode(c.Request.URL.Path), diff --git a/relay/constant/api_type.go b/relay/constant/api_type.go index 1bc8b47..8a1dbd6 100644 --- a/relay/constant/api_type.go +++ b/relay/constant/api_type.go @@ -24,19 +24,11 @@ const ( APITypeDummy // this one is only for count, do not add any channel after this ) -func ChannelType2APIType(channelType int) int { +func ChannelType2APIType(channelType int) (int, bool) { apiType := -1 switch channelType { case common.ChannelTypeOpenAI: apiType = APITypeOpenAI - case common.ChannelTypeAzure: - apiType = APITypeOpenAI - case common.ChannelTypeMoonshot: - apiType = APITypeOpenAI - case common.ChannelTypeLingYiWanWu: - apiType = APITypeOpenAI - case common.ChannelType360: - apiType = APITypeOpenAI case common.ChannelTypeAnthropic: apiType = APITypeAnthropic case common.ChannelTypeBaidu: @@ -66,5 +58,8 @@ func ChannelType2APIType(channelType int) int { case common.ChannelTypeCohere: apiType = APITypeCohere } - return apiType + if apiType == -1 { + return APITypeOpenAI, false + } + return apiType, true } diff --git a/web/src/helpers/render.js b/web/src/helpers/render.js index 9d36682..44bc004 100644 --- a/web/src/helpers/render.js +++ b/web/src/helpers/render.js @@ -1,4 +1,3 @@ -import { Label } from 'semantic-ui-react'; import { Tag } from '@douyinfe/semi-ui'; export function renderText(text, limit) { @@ -152,9 +151,9 @@ export function renderModelPrice( let completionRatioPrice = modelRatio * completionRatio * 0.002 * groupRatio; return ( - '输入:$' + + '输入 $' + inputRatioPrice.toFixed(3) + - '/1K tokens,补全:$' + + '/1K tokens,补全 $' + completionRatioPrice.toFixed(3) + '/1K tokens' ); From 39f6812a2baf2e405a4757bf24698dbfbbe12a19 Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Mon, 13 May 2024 15:08:01 +0800 Subject: [PATCH 19/32] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- relay/relay-text.go | 4 ++-- web/src/components/LogsTable.js | 6 ++---- web/src/helpers/render.js | 27 +++++++++++++++++++-------- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/relay/relay-text.go b/relay/relay-text.go index 9010381..bf3cba0 100644 --- a/relay/relay-text.go +++ b/relay/relay-text.go @@ -268,8 +268,8 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, textRe quota := 0 if modelPrice == -1 { - quota = promptTokens + int(float64(completionTokens)*completionRatio) - quota = int(float64(quota) * ratio) + quota = promptTokens + int(math.Round(float64(completionTokens)*completionRatio)) + quota = int(math.Round(float64(quota) * ratio)) if ratio != 0 && quota <= 0 { quota = 1 } diff --git a/web/src/components/LogsTable.js b/web/src/components/LogsTable.js index 0de3632..8efb9db 100644 --- a/web/src/components/LogsTable.js +++ b/web/src/components/LogsTable.js @@ -316,6 +316,8 @@ const LogsTable = () => { } let other = JSON.parse(record.other); let content = renderModelPrice( + record.prompt_tokens, + record.completion_tokens, other.model_ratio, other.model_price, other.completion_ratio, @@ -326,10 +328,6 @@ const LogsTable = () => { diff --git a/web/src/helpers/render.js b/web/src/helpers/render.js index 44bc004..f0cbd81 100644 --- a/web/src/helpers/render.js +++ b/web/src/helpers/render.js @@ -135,6 +135,8 @@ export function renderQuota(quota, digits = 2) { } export function renderModelPrice( + inputTokens, + completionTokens, modelRatio, modelPrice = -1, completionRatio, @@ -147,15 +149,24 @@ export function renderModelPrice( if (completionRatio === undefined) { completionRatio = 0; } - let inputRatioPrice = modelRatio * 0.002 * groupRatio; - let completionRatioPrice = - modelRatio * completionRatio * 0.002 * groupRatio; + let inputRatioPrice = modelRatio * 2.0 * groupRatio; + let completionRatioPrice = modelRatio * completionRatio * 2.0 * groupRatio; + let price = + (inputTokens / 1000000) * inputRatioPrice + + (completionTokens / 1000000) * completionRatioPrice; return ( - '输入 $' + - inputRatioPrice.toFixed(3) + - '/1K tokens,补全 $' + - completionRatioPrice.toFixed(3) + - '/1K tokens' + <> +
+

提示 ${inputRatioPrice} / 1M tokens

+

补全 ${completionRatioPrice} / 1M tokens

+

计算过程:

+

+ 提示 {inputTokens} tokens / 1M tokens * ${inputRatioPrice} + 补全{' '} + {completionTokens} tokens / 1M tokens * ${completionRatioPrice} = $ + {price.toFixed(6)} +

+
+ ); } } From 71547849bc4f2eb374294bfb79c529a39c01b55f Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Mon, 13 May 2024 16:04:02 +0800 Subject: [PATCH 20/32] =?UTF-8?q?feat:=20dalle=E7=B3=BB=E5=88=97=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E4=BD=BF=E7=94=A8=E6=A8=A1=E5=9E=8B=E5=9B=BA=E5=AE=9A?= =?UTF-8?q?=E4=BB=B7=E6=A0=BC=E8=AE=A1=E8=B4=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/model-ratio.go | 11 ++++++----- relay/relay-image.go | 25 ++++++++++++++++--------- relay/relay-mj.go | 8 ++++---- relay/relay-text.go | 10 +++++----- web/src/helpers/render.js | 2 +- 5 files changed, 32 insertions(+), 24 deletions(-) diff --git a/common/model-ratio.go b/common/model-ratio.go index f470317..a8db3b3 100644 --- a/common/model-ratio.go +++ b/common/model-ratio.go @@ -61,8 +61,6 @@ var DefaultModelRatio = map[string]float64{ "text-search-ada-doc-001": 10, "text-moderation-stable": 0.1, "text-moderation-latest": 0.1, - "dall-e-2": 8, - "dall-e-3": 16, "claude-instant-1": 0.4, // $0.8 / 1M tokens "claude-2.0": 4, // $8 / 1M tokens "claude-2.1": 4, // $8 / 1M tokens @@ -117,6 +115,8 @@ var DefaultModelRatio = map[string]float64{ } var DefaultModelPrice = map[string]float64{ + "dall-e-2": 0.02, + "dall-e-3": 0.04, "gpt-4-gizmo-*": 0.1, "mj_imagine": 0.1, "mj_variation": 0.1, @@ -160,7 +160,8 @@ func UpdateModelPriceByJSONString(jsonStr string) error { return json.Unmarshal([]byte(jsonStr), &modelPrice) } -func GetModelPrice(name string, printErr bool) float64 { +// GetModelPrice 返回模型的价格,如果模型不存在则返回-1,false +func GetModelPrice(name string, printErr bool) (float64, bool) { if modelPrice == nil { modelPrice = DefaultModelPrice } @@ -172,9 +173,9 @@ func GetModelPrice(name string, printErr bool) float64 { if printErr { SysError("model price not found: " + name) } - return -1 + return -1, false } - return price + return price, true } func ModelRatio2JSONString() string { diff --git a/relay/relay-image.go b/relay/relay-image.go index 7f8cd9e..346d72d 100644 --- a/relay/relay-image.go +++ b/relay/relay-image.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/gin-gonic/gin" "io" + "log" "net/http" "one-api/common" "one-api/dto" @@ -106,21 +107,27 @@ func RelayImageHelper(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusC requestBody = c.Request.Body } - modelRatio := common.GetModelRatio(imageRequest.Model) + modelPrice, success := common.GetModelPrice(imageRequest.Model, true) + if !success { + modelRatio := common.GetModelRatio(imageRequest.Model) + // modelRatio 16 = modelPrice $0.04 + // per 1 modelRatio = $0.04 / 16 + modelPrice = 0.0025 * modelRatio + } + log.Printf("modelPrice: %f", modelPrice) groupRatio := common.GetGroupRatio(group) - ratio := modelRatio * groupRatio userQuota, err := model.CacheGetUserQuota(userId) sizeRatio := 1.0 // Size if imageRequest.Size == "256x256" { - sizeRatio = 1 + sizeRatio = 0.4 } else if imageRequest.Size == "512x512" { - sizeRatio = 1.125 + sizeRatio = 0.45 } else if imageRequest.Size == "1024x1024" { - sizeRatio = 1.25 + sizeRatio = 1 } else if imageRequest.Size == "1024x1792" || imageRequest.Size == "1792x1024" { - sizeRatio = 2.5 + sizeRatio = 2 } qualityRatio := 1.0 @@ -131,7 +138,7 @@ func RelayImageHelper(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusC } } - quota := int(ratio*sizeRatio*qualityRatio*1000) * imageRequest.N + quota := int(modelPrice*groupRatio*common.QuotaPerUnit*sizeRatio*qualityRatio) * imageRequest.N if userQuota-quota < 0 { return service.OpenAIErrorWrapper(errors.New("user quota is not enough"), "insufficient_user_quota", http.StatusForbidden) @@ -190,9 +197,9 @@ func RelayImageHelper(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusC if imageRequest.Quality == "hd" { quality = "hd" } - logContent := fmt.Sprintf("模型倍率 %.2f,分组倍率 %.2f, 大小 %s, 品质 %s", modelRatio, groupRatio, imageRequest.Size, quality) + logContent := fmt.Sprintf("模型价格 %.2f,分组倍率 %.2f, 大小 %s, 品质 %s", modelPrice, groupRatio, imageRequest.Size, quality) other := make(map[string]interface{}) - other["model_ratio"] = modelRatio + other["model_price"] = modelPrice other["group_ratio"] = groupRatio model.RecordConsumeLog(ctx, userId, channelId, 0, 0, imageRequest.Model, tokenName, quota, logContent, tokenId, userQuota, int(useTimeSeconds), false, other) model.UpdateUserUsedQuotaAndRequestCount(userId, quota) diff --git a/relay/relay-mj.go b/relay/relay-mj.go index 16ad412..b28f026 100644 --- a/relay/relay-mj.go +++ b/relay/relay-mj.go @@ -155,9 +155,9 @@ func RelaySwapFace(c *gin.Context) *dto.MidjourneyResponse { return service.MidjourneyErrorWrapper(constant.MjRequestError, "sour_base64_and_target_base64_is_required") } modelName := service.CoverActionToModelName(constant.MjActionSwapFace) - modelPrice := common.GetModelPrice(modelName, true) + modelPrice, success := common.GetModelPrice(modelName, true) // 如果没有配置价格,则使用默认价格 - if modelPrice == -1 { + if !success { defaultPrice, ok := common.DefaultModelPrice[modelName] if !ok { modelPrice = 0.1 @@ -454,9 +454,9 @@ func RelayMidjourneySubmit(c *gin.Context, relayMode int) *dto.MidjourneyRespons fullRequestURL := fmt.Sprintf("%s%s", baseURL, requestURL) modelName := service.CoverActionToModelName(midjRequest.Action) - modelPrice := common.GetModelPrice(modelName, true) + modelPrice, success := common.GetModelPrice(modelName, true) // 如果没有配置价格,则使用默认价格 - if modelPrice == -1 { + if !success { defaultPrice, ok := common.DefaultModelPrice[modelName] if !ok { modelPrice = 0.1 diff --git a/relay/relay-text.go b/relay/relay-text.go index bf3cba0..d5ee728 100644 --- a/relay/relay-text.go +++ b/relay/relay-text.go @@ -91,7 +91,7 @@ func TextHelper(c *gin.Context) *dto.OpenAIErrorWithStatusCode { } } relayInfo.UpstreamModelName = textRequest.Model - modelPrice := common.GetModelPrice(textRequest.Model, false) + modelPrice, success := common.GetModelPrice(textRequest.Model, false) groupRatio := common.GetGroupRatio(relayInfo.Group) var preConsumedQuota int @@ -108,7 +108,7 @@ func TextHelper(c *gin.Context) *dto.OpenAIErrorWithStatusCode { return service.OpenAIErrorWrapper(err, "count_token_messages_failed", http.StatusInternalServerError) } - if modelPrice == -1 { + if !success { preConsumedTokens := common.PreConsumedQuota if textRequest.MaxTokens != 0 { preConsumedTokens = promptTokens + int(textRequest.MaxTokens) @@ -178,7 +178,7 @@ func TextHelper(c *gin.Context) *dto.OpenAIErrorWithStatusCode { service.ResetStatusCode(openaiErr, statusCodeMappingStr) return openaiErr } - postConsumeQuota(c, relayInfo, *textRequest, usage, ratio, preConsumedQuota, userQuota, modelRatio, groupRatio, modelPrice) + postConsumeQuota(c, relayInfo, *textRequest, usage, ratio, preConsumedQuota, userQuota, modelRatio, groupRatio, modelPrice, success) return nil } @@ -257,7 +257,7 @@ func returnPreConsumedQuota(c *gin.Context, tokenId int, userQuota int, preConsu func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, textRequest dto.GeneralOpenAIRequest, usage *dto.Usage, ratio float64, preConsumedQuota int, userQuota int, modelRatio float64, groupRatio float64, - modelPrice float64) { + modelPrice float64, usePrice bool) { useTimeSeconds := time.Now().Unix() - relayInfo.StartTime.Unix() promptTokens := usage.PromptTokens @@ -267,7 +267,7 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, textRe completionRatio := common.GetCompletionRatio(textRequest.Model) quota := 0 - if modelPrice == -1 { + if !usePrice { quota = promptTokens + int(math.Round(float64(completionTokens)*completionRatio)) quota = int(math.Round(float64(quota) * ratio)) if ratio != 0 && quota <= 0 { diff --git a/web/src/helpers/render.js b/web/src/helpers/render.js index f0cbd81..3113fed 100644 --- a/web/src/helpers/render.js +++ b/web/src/helpers/render.js @@ -159,7 +159,7 @@ export function renderModelPrice(

提示 ${inputRatioPrice} / 1M tokens

补全 ${completionRatioPrice} / 1M tokens

-

计算过程:

+

提示 {inputTokens} tokens / 1M tokens * ${inputRatioPrice} + 补全{' '} {completionTokens} tokens / 1M tokens * ${completionRatioPrice} = $ From 21839ed13b072326c39365e8925481af53205747 Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Mon, 13 May 2024 16:04:28 +0800 Subject: [PATCH 21/32] =?UTF-8?q?chore:=20=E5=88=A0=E9=99=A4=E6=97=A0?= =?UTF-8?q?=E7=94=A8=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- relay/relay-image.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/relay/relay-image.go b/relay/relay-image.go index 346d72d..ed090f5 100644 --- a/relay/relay-image.go +++ b/relay/relay-image.go @@ -8,7 +8,6 @@ import ( "fmt" "github.com/gin-gonic/gin" "io" - "log" "net/http" "one-api/common" "one-api/dto" @@ -114,7 +113,6 @@ func RelayImageHelper(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusC // per 1 modelRatio = $0.04 / 16 modelPrice = 0.0025 * modelRatio } - log.Printf("modelPrice: %f", modelPrice) groupRatio := common.GetGroupRatio(group) userQuota, err := model.CacheGetUserQuota(userId) From b283365ebcf3c6c94e1c03898294e21fb6f65b49 Mon Sep 17 00:00:00 2001 From: QuentinHsu Date: Mon, 13 May 2024 17:55:15 +0800 Subject: [PATCH 22/32] =?UTF-8?q?refactor:=20=E8=BF=90=E8=90=A5=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE-=E5=80=8D=E7=8E=87=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/OperationSetting.js | 309 +++--------------- .../Operation/SettingsMagnification.js | 193 +++++++++++ 2 files changed, 242 insertions(+), 260 deletions(-) create mode 100644 web/src/pages/Setting/Operation/SettingsMagnification.js diff --git a/web/src/components/OperationSetting.js b/web/src/components/OperationSetting.js index 31345a5..2529ca2 100644 --- a/web/src/components/OperationSetting.js +++ b/web/src/components/OperationSetting.js @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { Divider, Form, Grid, Header } from 'semantic-ui-react'; -import { Card } from '@douyinfe/semi-ui'; +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'; @@ -8,6 +8,7 @@ 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 { API, @@ -17,10 +18,7 @@ import { verifyJSON, } from '../helpers'; -import { useTheme } from '../context/Theme'; - const OperationSetting = () => { - let now = new Date(); let [inputs, setInputs] = useState({ QuotaForNewUser: 0, QuotaForInviter: 0, @@ -58,7 +56,7 @@ const OperationSetting = () => { DefaultCollapseSidebar: false, // 默认折叠侧边栏 RetryTimes: 0, }); - const [originInputs, setOriginInputs] = useState({}); + let [loading, setLoading] = useState(false); const getOptions = async () => { @@ -86,271 +84,62 @@ const OperationSetting = () => { }); 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(); + getOptions(); }, []); - 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; - } - }; return ( <> - {/* 通用设置 */} - - - - {/* 绘图设置 */} - - - - {/* 屏蔽词过滤设置 */} - - - - {/* 日志设置 */} - - - - {/* 数据看板 */} - - - - {/* 监控设置 */} - - - - {/* 额度设置 */} - - - - - -

- {/**/} - {/* */} - {/**/} - {/**/} - {/* */} - {/**/} - - -
- 倍率设置 -
- - - - - - - - - - - - - { - submitConfig('ratio').then(); - }} - > - 保存倍率设置 - - - - + + {/* 通用设置 */} + + + + {/* 绘图设置 */} + + + + {/* 屏蔽词过滤设置 */} + + + + {/* 日志设置 */} + + + + {/* 数据看板 */} + + + + {/* 监控设置 */} + + + + {/* 额度设置 */} + + + + {/* 倍率设置 */} + + + + ); }; diff --git a/web/src/pages/Setting/Operation/SettingsMagnification.js b/web/src/pages/Setting/Operation/SettingsMagnification.js new file mode 100644 index 0000000..3f38b37 --- /dev/null +++ b/web/src/pages/Setting/Operation/SettingsMagnification.js @@ -0,0 +1,193 @@ +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('更新成功'); + }) + .catch(() => { + showError('更新失败'); + }) + .finally(() => { + setLoading(false); + setInputsRow(structuredClone(inputs)); + }); + } 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, + }) + } + /> + + + + + + + +
+
+ + ); +} From 98c347e048ca6977d15a895c69e509404b2bdbdc Mon Sep 17 00:00:00 2001 From: QuentinHsu Date: Mon, 13 May 2024 18:14:57 +0800 Subject: [PATCH 23/32] =?UTF-8?q?refactor:=20=E8=BF=90=E8=90=A5=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE-=E6=95=B0=E6=8D=AE=E5=88=B7=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/OperationSetting.js | 27 +++++++------------ .../Setting/Operation/SettingsCreditLimit.js | 2 +- .../Operation/SettingsDataDashboard.js | 2 +- .../Setting/Operation/SettingsDrawing.js | 2 +- .../Setting/Operation/SettingsGeneral.js | 2 +- .../pages/Setting/Operation/SettingsLog.js | 5 +--- .../Operation/SettingsMagnification.js | 2 +- .../Setting/Operation/SettingsMonitoring.js | 2 +- .../Operation/SettingsSensitiveWords.js | 2 +- 9 files changed, 18 insertions(+), 28 deletions(-) diff --git a/web/src/components/OperationSetting.js b/web/src/components/OperationSetting.js index 2529ca2..3273f24 100644 --- a/web/src/components/OperationSetting.js +++ b/web/src/components/OperationSetting.js @@ -1,5 +1,4 @@ import React, { useEffect, useState } from 'react'; -import { Divider, Form, Grid, Header } from 'semantic-ui-react'; import { Card, Spin } from '@douyinfe/semi-ui'; import SettingsGeneral from '../pages/Setting/Operation/SettingsGeneral.js'; import SettingsDrawing from '../pages/Setting/Operation/SettingsDrawing.js'; @@ -10,13 +9,7 @@ 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 { - API, - showError, - showSuccess, - timestamp2string, - verifyJSON, -} from '../helpers'; +import { API, showError, showSuccess } from '../helpers'; const OperationSetting = () => { let [inputs, setInputs] = useState({ @@ -101,7 +94,7 @@ const OperationSetting = () => { } useEffect(() => { - getOptions(); + onRefresh(); }, []); return ( @@ -109,35 +102,35 @@ const OperationSetting = () => { {/* 通用设置 */} - + {/* 绘图设置 */} - + {/* 屏蔽词过滤设置 */} - + {/* 日志设置 */} - + {/* 数据看板 */} - + {/* 监控设置 */} - + {/* 额度设置 */} - + {/* 倍率设置 */} - + diff --git a/web/src/pages/Setting/Operation/SettingsCreditLimit.js b/web/src/pages/Setting/Operation/SettingsCreditLimit.js index 4fcbb4e..90dfddf 100644 --- a/web/src/pages/Setting/Operation/SettingsCreditLimit.js +++ b/web/src/pages/Setting/Operation/SettingsCreditLimit.js @@ -49,7 +49,7 @@ export default function SettingsCreditLimit(props) { }) .finally(() => { setLoading(false); - setInputsRow(structuredClone(inputs)); + props.refresh(); }); } diff --git a/web/src/pages/Setting/Operation/SettingsDataDashboard.js b/web/src/pages/Setting/Operation/SettingsDataDashboard.js index 85fd1dc..8710639 100644 --- a/web/src/pages/Setting/Operation/SettingsDataDashboard.js +++ b/web/src/pages/Setting/Operation/SettingsDataDashboard.js @@ -53,7 +53,7 @@ export default function DataDashboard(props) { }) .finally(() => { setLoading(false); - setInputsRow(structuredClone(inputs)); + props.refresh(); }); } diff --git a/web/src/pages/Setting/Operation/SettingsDrawing.js b/web/src/pages/Setting/Operation/SettingsDrawing.js index 560bbc7..d0c0ef7 100644 --- a/web/src/pages/Setting/Operation/SettingsDrawing.js +++ b/web/src/pages/Setting/Operation/SettingsDrawing.js @@ -50,7 +50,7 @@ export default function SettingsDrawing(props) { }) .finally(() => { setLoading(false); - setInputsRow(structuredClone(inputs)); + props.refresh(); }); } diff --git a/web/src/pages/Setting/Operation/SettingsGeneral.js b/web/src/pages/Setting/Operation/SettingsGeneral.js index 83fb7c8..a988ce8 100644 --- a/web/src/pages/Setting/Operation/SettingsGeneral.js +++ b/web/src/pages/Setting/Operation/SettingsGeneral.js @@ -56,7 +56,7 @@ export default function GeneralSettings(props) { }) .finally(() => { setLoading(false); - setInputsRow(structuredClone(inputs)); + props.refresh(); }); } diff --git a/web/src/pages/Setting/Operation/SettingsLog.js b/web/src/pages/Setting/Operation/SettingsLog.js index 77120ea..4377e73 100644 --- a/web/src/pages/Setting/Operation/SettingsLog.js +++ b/web/src/pages/Setting/Operation/SettingsLog.js @@ -16,9 +16,6 @@ export default function SettingsLog(props) { LogConsumeEnabled: false, historyTimestamp: dayjs().subtract(1, 'month').toDate(), }); - // const [historyTimestamp, setHistoryTimestamp] = useState( - // dayjs().subtract(1, 'month').toDate(), - // ); const refForm = useRef(); const [inputsRow, setInputsRow] = useState(inputs); @@ -55,7 +52,7 @@ export default function SettingsLog(props) { }) .finally(() => { setLoading(false); - setInputsRow(structuredClone(inputs)); + props.refresh(); }); } async function onCleanHistoryLog() { diff --git a/web/src/pages/Setting/Operation/SettingsMagnification.js b/web/src/pages/Setting/Operation/SettingsMagnification.js index 3f38b37..f92dae9 100644 --- a/web/src/pages/Setting/Operation/SettingsMagnification.js +++ b/web/src/pages/Setting/Operation/SettingsMagnification.js @@ -52,7 +52,7 @@ export default function SettingsMagnification(props) { }) .finally(() => { setLoading(false); - setInputsRow(structuredClone(inputs)); + props.refresh(); }); } catch (error) { showError('请检查输入'); diff --git a/web/src/pages/Setting/Operation/SettingsMonitoring.js b/web/src/pages/Setting/Operation/SettingsMonitoring.js index 6fed48f..39ee002 100644 --- a/web/src/pages/Setting/Operation/SettingsMonitoring.js +++ b/web/src/pages/Setting/Operation/SettingsMonitoring.js @@ -49,7 +49,7 @@ export default function SettingsMonitoring(props) { }) .finally(() => { setLoading(false); - setInputsRow(structuredClone(inputs)); + props.refresh(); }); } diff --git a/web/src/pages/Setting/Operation/SettingsSensitiveWords.js b/web/src/pages/Setting/Operation/SettingsSensitiveWords.js index f251013..f13daff 100644 --- a/web/src/pages/Setting/Operation/SettingsSensitiveWords.js +++ b/web/src/pages/Setting/Operation/SettingsSensitiveWords.js @@ -48,7 +48,7 @@ export default function SettingsSensitiveWords(props) { }) .finally(() => { setLoading(false); - setInputsRow(structuredClone(inputs)); + props.refresh(); }); } From 5715fcf8fb24ba7b18fa0ad3a3a6d971cb269102 Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Mon, 13 May 2024 23:02:35 +0800 Subject: [PATCH 24/32] feat: add pricing page --- common/model-ratio.go | 21 +++ common/utils.go | 8 + controller/model.go | 68 ++++----- dto/pricing.go | 37 +++++ middleware/auth.go | 11 ++ model/ability.go | 7 + model/pricing.go | 72 +++++++++ model/usedata.go | 1 + router/api-router.go | 1 + web/src/App.js | 9 ++ web/src/components/LoginForm.js | 3 +- web/src/components/ModelPricing.js | 229 +++++++++++++++++++++++++++++ web/src/components/SiderBar.js | 37 ++--- web/src/helpers/data.js | 33 +++++ web/src/helpers/render.js | 2 +- web/src/pages/Pricing/index.js | 10 ++ 16 files changed, 481 insertions(+), 68 deletions(-) create mode 100644 dto/pricing.go create mode 100644 model/pricing.go create mode 100644 web/src/components/ModelPricing.js create mode 100644 web/src/helpers/data.js create mode 100644 web/src/pages/Pricing/index.js diff --git a/common/model-ratio.go b/common/model-ratio.go index a8db3b3..4510551 100644 --- a/common/model-ratio.go +++ b/common/model-ratio.go @@ -178,6 +178,13 @@ func GetModelPrice(name string, printErr bool) (float64, bool) { return price, true } +func GetModelPrices() map[string]float64 { + if modelPrice == nil { + modelPrice = DefaultModelPrice + } + return modelPrice +} + func ModelRatio2JSONString() string { if modelRatio == nil { modelRatio = DefaultModelRatio @@ -209,6 +216,13 @@ func GetModelRatio(name string) float64 { return ratio } +func GetModelRatios() map[string]float64 { + if modelRatio == nil { + modelRatio = DefaultModelRatio + } + return modelRatio +} + func CompletionRatio2JSONString() string { if CompletionRatio == nil { CompletionRatio = DefaultCompletionRatio @@ -282,3 +296,10 @@ func GetCompletionRatio(name string) float64 { } return 1 } + +func GetCompletionRatios() map[string]float64 { + if CompletionRatio == nil { + CompletionRatio = DefaultCompletionRatio + } + return CompletionRatio +} diff --git a/common/utils.go b/common/utils.go index 657ffd4..3130020 100644 --- a/common/utils.go +++ b/common/utils.go @@ -250,3 +250,11 @@ func MapToJsonStr(m map[string]interface{}) string { } return string(bytes) } + +func MapToJsonStrFloat(m map[string]float64) string { + bytes, err := json.Marshal(m) + if err != nil { + return "" + } + return string(bytes) +} diff --git a/controller/model.go b/controller/model.go index c9c50db..de86ca3 100644 --- a/controller/model.go +++ b/controller/model.go @@ -18,38 +18,13 @@ import ( // https://platform.openai.com/docs/api-reference/models/list -type OpenAIModelPermission struct { - Id string `json:"id"` - Object string `json:"object"` - Created int `json:"created"` - AllowCreateEngine bool `json:"allow_create_engine"` - AllowSampling bool `json:"allow_sampling"` - AllowLogprobs bool `json:"allow_logprobs"` - AllowSearchIndices bool `json:"allow_search_indices"` - AllowView bool `json:"allow_view"` - AllowFineTuning bool `json:"allow_fine_tuning"` - Organization string `json:"organization"` - Group *string `json:"group"` - IsBlocking bool `json:"is_blocking"` -} - -type OpenAIModels struct { - Id string `json:"id"` - Object string `json:"object"` - Created int `json:"created"` - OwnedBy string `json:"owned_by"` - Permission []OpenAIModelPermission `json:"permission"` - Root string `json:"root"` - Parent *string `json:"parent"` -} - -var openAIModels []OpenAIModels -var openAIModelsMap map[string]OpenAIModels +var openAIModels []dto.OpenAIModels +var openAIModelsMap map[string]dto.OpenAIModels var channelId2Models map[int][]string -func getPermission() []OpenAIModelPermission { - var permission []OpenAIModelPermission - permission = append(permission, OpenAIModelPermission{ +func getPermission() []dto.OpenAIModelPermission { + var permission []dto.OpenAIModelPermission + permission = append(permission, dto.OpenAIModelPermission{ Id: "modelperm-LwHkVFn8AcMItP432fKKDIKJ", Object: "model_permission", Created: 1626777600, @@ -77,7 +52,7 @@ func init() { channelName := adaptor.GetChannelName() modelNames := adaptor.GetModelList() for _, modelName := range modelNames { - openAIModels = append(openAIModels, OpenAIModels{ + openAIModels = append(openAIModels, dto.OpenAIModels{ Id: modelName, Object: "model", Created: 1626777600, @@ -89,7 +64,7 @@ func init() { } } for _, modelName := range ai360.ModelList { - openAIModels = append(openAIModels, OpenAIModels{ + openAIModels = append(openAIModels, dto.OpenAIModels{ Id: modelName, Object: "model", Created: 1626777600, @@ -100,7 +75,7 @@ func init() { }) } for _, modelName := range moonshot.ModelList { - openAIModels = append(openAIModels, OpenAIModels{ + openAIModels = append(openAIModels, dto.OpenAIModels{ Id: modelName, Object: "model", Created: 1626777600, @@ -111,7 +86,7 @@ func init() { }) } for _, modelName := range lingyiwanwu.ModelList { - openAIModels = append(openAIModels, OpenAIModels{ + openAIModels = append(openAIModels, dto.OpenAIModels{ Id: modelName, Object: "model", Created: 1626777600, @@ -122,7 +97,7 @@ func init() { }) } for modelName, _ := range constant.MidjourneyModel2Action { - openAIModels = append(openAIModels, OpenAIModels{ + openAIModels = append(openAIModels, dto.OpenAIModels{ Id: modelName, Object: "model", Created: 1626777600, @@ -132,7 +107,7 @@ func init() { Parent: nil, }) } - openAIModelsMap = make(map[string]OpenAIModels) + openAIModelsMap = make(map[string]dto.OpenAIModels) for _, model := range openAIModels { openAIModelsMap[model.Id] = model } @@ -160,17 +135,17 @@ func ListModels(c *gin.Context) { return } models := model.GetGroupModels(user.Group) - userOpenAiModels := make([]OpenAIModels, 0) + userOpenAiModels := make([]dto.OpenAIModels, 0) permission := getPermission() for _, s := range models { if _, ok := openAIModelsMap[s]; ok { userOpenAiModels = append(userOpenAiModels, openAIModelsMap[s]) } else { - userOpenAiModels = append(userOpenAiModels, OpenAIModels{ + userOpenAiModels = append(userOpenAiModels, dto.OpenAIModels{ Id: s, Object: "model", Created: 1626777600, - OwnedBy: "openai", + OwnedBy: "custom", Permission: permission, Root: s, Parent: nil, @@ -213,3 +188,18 @@ func RetrieveModel(c *gin.Context) { }) } } + +func GetPricing(c *gin.Context) { + userId := c.GetInt("id") + user, _ := model.GetUserById(userId, true) + groupRatio := common.GetGroupRatio("default") + if user != nil { + groupRatio = common.GetGroupRatio(user.Group) + } + pricing := model.GetPricing(user, openAIModels) + c.JSON(200, gin.H{ + "success": true, + "data": pricing, + "group_ratio": groupRatio, + }) +} diff --git a/dto/pricing.go b/dto/pricing.go new file mode 100644 index 0000000..b049749 --- /dev/null +++ b/dto/pricing.go @@ -0,0 +1,37 @@ +package dto + +type OpenAIModelPermission struct { + Id string `json:"id"` + Object string `json:"object"` + Created int `json:"created"` + AllowCreateEngine bool `json:"allow_create_engine"` + AllowSampling bool `json:"allow_sampling"` + AllowLogprobs bool `json:"allow_logprobs"` + AllowSearchIndices bool `json:"allow_search_indices"` + AllowView bool `json:"allow_view"` + AllowFineTuning bool `json:"allow_fine_tuning"` + Organization string `json:"organization"` + Group *string `json:"group"` + IsBlocking bool `json:"is_blocking"` +} + +type OpenAIModels struct { + Id string `json:"id"` + Object string `json:"object"` + Created int `json:"created"` + OwnedBy string `json:"owned_by"` + Permission []OpenAIModelPermission `json:"permission"` + Root string `json:"root"` + Parent *string `json:"parent"` +} + +type ModelPricing struct { + Available bool `json:"available"` + ModelName string `json:"model_name"` + QuotaType int `json:"quota_type"` + ModelRatio float64 `json:"model_ratio"` + ModelPrice float64 `json:"model_price"` + OwnerBy string `json:"owner_by"` + CompletionRatio float64 `json:"completion_ratio"` + EnableGroup []string `json:"enable_group,omitempty"` +} diff --git a/middleware/auth.go b/middleware/auth.go index 686f2d9..d9df9c8 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -64,6 +64,17 @@ func authHelper(c *gin.Context, minRole int) { c.Next() } +func TryUserAuth() func(c *gin.Context) { + return func(c *gin.Context) { + session := sessions.Default(c) + id := session.Get("id") + if id != nil { + c.Set("id", id) + } + c.Next() + } +} + func UserAuth() func(c *gin.Context) { return func(c *gin.Context) { authHelper(c, common.RoleCommonUser) diff --git a/model/ability.go b/model/ability.go index 7fd52bc..8d2d4f8 100644 --- a/model/ability.go +++ b/model/ability.go @@ -29,6 +29,13 @@ func GetGroupModels(group string) []string { return models } +func GetEnabledModels() []string { + var models []string + // Find distinct models + DB.Table("abilities").Where("enabled = ?", true).Distinct("model").Pluck("model", &models) + return models +} + func getPriority(group string, model string, retry int) (int, error) { groupCol := "`group`" trueVal := "1" diff --git a/model/pricing.go b/model/pricing.go new file mode 100644 index 0000000..c9685f3 --- /dev/null +++ b/model/pricing.go @@ -0,0 +1,72 @@ +package model + +import ( + "one-api/common" + "one-api/dto" + "sync" + "time" +) + +var ( + pricingMap []dto.ModelPricing + lastGetPricingTime time.Time + updatePricingLock sync.Mutex +) + +func GetPricing(user *User, openAIModels []dto.OpenAIModels) []dto.ModelPricing { + updatePricingLock.Lock() + defer updatePricingLock.Unlock() + + if time.Since(lastGetPricingTime) > time.Minute*1 || len(pricingMap) == 0 { + updatePricing(openAIModels) + } + if user != nil { + userPricingMap := make([]dto.ModelPricing, 0) + models := GetGroupModels(user.Group) + for _, pricing := range pricingMap { + if !common.StringsContains(models, pricing.ModelName) { + pricing.Available = false + } + userPricingMap = append(userPricingMap, pricing) + } + return userPricingMap + } + return pricingMap +} + +func updatePricing(openAIModels []dto.OpenAIModels) { + modelRatios := common.GetModelRatios() + enabledModels := GetEnabledModels() + allModels := make(map[string]string) + for _, openAIModel := range openAIModels { + if common.StringsContains(enabledModels, openAIModel.Id) { + allModels[openAIModel.Id] = openAIModel.OwnedBy + } + } + for model, _ := range modelRatios { + if common.StringsContains(enabledModels, model) { + if _, ok := allModels[model]; !ok { + allModels[model] = "custom" + } + } + } + pricingMap = make([]dto.ModelPricing, 0) + for model, ownerBy := range allModels { + pricing := dto.ModelPricing{ + Available: true, + ModelName: model, + OwnerBy: ownerBy, + } + modelPrice, findPrice := common.GetModelPrice(model, false) + if findPrice { + pricing.ModelPrice = modelPrice + pricing.QuotaType = 1 + } else { + pricing.ModelRatio = common.GetModelRatio(model) + pricing.CompletionRatio = common.GetCompletionRatio(model) + pricing.QuotaType = 0 + } + pricingMap = append(pricingMap, pricing) + } + lastGetPricingTime = time.Now() +} diff --git a/model/usedata.go b/model/usedata.go index b2f3025..4735333 100644 --- a/model/usedata.go +++ b/model/usedata.go @@ -45,6 +45,7 @@ func logQuotaDataCache(userId int, username string, modelName string, quota int, if ok { quotaData.Count += 1 quotaData.Quota += quota + quotaData.TokenUsed += tokenUsed } else { quotaData = &QuotaData{ UserID: userId, diff --git a/router/api-router.go b/router/api-router.go index 8c0ae30..add5c5f 100644 --- a/router/api-router.go +++ b/router/api-router.go @@ -20,6 +20,7 @@ func SetApiRouter(router *gin.Engine) { apiRouter.GET("/about", controller.GetAbout) //apiRouter.GET("/midjourney", controller.GetMidjourney) apiRouter.GET("/home_page_content", controller.GetHomePageContent) + apiRouter.GET("/pricing", middleware.CriticalRateLimit(), middleware.TryUserAuth(), controller.GetPricing) apiRouter.GET("/verification", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendEmailVerification) apiRouter.GET("/reset_password", middleware.CriticalRateLimit(), middleware.TurnstileCheck(), controller.SendPasswordResetEmail) apiRouter.POST("/user/reset", middleware.CriticalRateLimit(), controller.ResetPassword) diff --git a/web/src/App.js b/web/src/App.js index a3b0660..1b63def 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -22,6 +22,7 @@ import Log from './pages/Log'; import Chat from './pages/Chat'; import { Layout } from '@douyinfe/semi-ui'; import Midjourney from './pages/Midjourney'; +import Pricing from './pages/Pricing/index.js'; // import Detail from './pages/Detail'; const Home = lazy(() => import('./pages/Home')); @@ -219,6 +220,14 @@ function App() { } /> + }> + + + } + /> { const [inputs, setInputs] = useState({ @@ -99,7 +100,7 @@ const LoginForm = () => { const { success, message, data } = res.data; if (success) { userDispatch({ type: 'login', payload: data }); - localStorage.setItem('user', JSON.stringify(data)); + setUserData(data); showSuccess('登录成功!'); if (username === 'root' && password === '123456') { Modal.error({ diff --git a/web/src/components/ModelPricing.js b/web/src/components/ModelPricing.js new file mode 100644 index 0000000..708d79e --- /dev/null +++ b/web/src/components/ModelPricing.js @@ -0,0 +1,229 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { API, copy, showError, showSuccess } from '../helpers'; + +import { Banner, Layout, Modal, Table, Tag, Tooltip } from '@douyinfe/semi-ui'; +import { stringToColor } from '../helpers/render.js'; +import { UserContext } from '../context/User/index.js'; +import Text from '@douyinfe/semi-ui/lib/es/typography/text'; + +function renderQuotaType(type) { + // Ensure all cases are string literals by adding quotes. + switch (type) { + case 1: + return ( + + 按次计费 + + ); + case 0: + return ( + + 按量计费 + + ); + default: + return ( + + 未知 + + ); + } +} + +function renderAvailable(available) { + return available ? ( + + 可用 + + ) : ( + + + 不可用 + + + ); +} + +const ModelPricing = () => { + const columns = [ + { + title: '可用性', + dataIndex: 'available', + render: (text, record, index) => { + return renderAvailable(text); + }, + }, + { + title: '提供者', + dataIndex: 'owner_by', + render: (text, record, index) => { + return ( + <> + + {text} + + + ); + }, + }, + { + title: '模型名称', + dataIndex: 'model_name', // 以finish_time作为dataIndex + render: (text, record, index) => { + return ( + <> + { + copyText(text); + }} + > + {text} + + + ); + }, + }, + { + title: '计费类型', + dataIndex: 'quota_type', + render: (text, record, index) => { + return renderQuotaType(parseInt(text)); + }, + }, + { + title: '模型倍率', + dataIndex: 'model_ratio', + render: (text, record, index) => { + return
{record.quota_type === 0 ? text : 'N/A'}
; + }, + }, + { + title: '补全倍率', + dataIndex: 'completion_ratio', + render: (text, record, index) => { + let ratio = parseFloat(text.toFixed(3)); + return
{record.quota_type === 0 ? ratio : 'N/A'}
; + }, + }, + { + title: '模型价格', + dataIndex: 'model_price', + render: (text, record, index) => { + let content = text; + if (record.quota_type === 0) { + let inputRatioPrice = record.model_ratio * 2.0 * record.group_ratio; + let completionRatioPrice = + record.model_ratio * + record.completion_ratio * + 2.0 * + record.group_ratio; + content = ( + <> + 提示 ${inputRatioPrice} / 1M tokens +
+ 补全 ${completionRatioPrice} / 1M tokens + + ); + } else { + let price = parseFloat(text) * record.group_ratio; + content = <>模型价格:${price}; + } + return
{content}
; + }, + }, + ]; + + const [models, setModels] = useState([]); + const [loading, setLoading] = useState(true); + const [userState, userDispatch] = useContext(UserContext); + const [groupRatio, setGroupRatio] = useState(1); + + const setModelsFormat = (models, groupRatio) => { + for (let i = 0; i < models.length; i++) { + models[i].key = i; + models[i].group_ratio = groupRatio; + } + // sort by quota_type + models.sort((a, b) => { + return a.quota_type - b.quota_type; + }); + + // sort by owner_by, openai is max, other use localeCompare + models.sort((a, b) => { + if (a.owner_by === 'openai') { + return -1; + } else if (b.owner_by === 'openai') { + return 1; + } else { + return a.owner_by.localeCompare(b.owner_by); + } + }); + + setModels(models); + }; + + const loadPricing = async () => { + setLoading(true); + + let url = ''; + url = `/api/pricing`; + const res = await API.get(url); + const { success, message, data, group_ratio } = res.data; + if (success) { + setGroupRatio(group_ratio); + setModelsFormat(data, group_ratio); + } else { + showError(message); + } + setLoading(false); + }; + + const refresh = async () => { + await loadPricing(); + }; + + const copyText = async (text) => { + if (await copy(text)) { + showSuccess('已复制:' + text); + } else { + // setSearchKeyword(text); + Modal.error({ title: '无法复制到剪贴板,请手动复制', content: text }); + } + }; + + useEffect(() => { + refresh().then(); + }, []); + + return ( + <> + + {userState.user ? ( + + ) : ( + + )} + + + + ); +}; + +export default ModelPricing; diff --git a/web/src/components/SiderBar.js b/web/src/components/SiderBar.js index 96d7e7e..d542a43 100644 --- a/web/src/components/SiderBar.js +++ b/web/src/components/SiderBar.js @@ -23,10 +23,12 @@ import { IconImage, IconKey, IconLayers, + IconPriceTag, IconSetting, IconUser, } from '@douyinfe/semi-icons'; import { Layout, Nav } from '@douyinfe/semi-ui'; +import { setStatusData } from '../helpers/data.js'; // HeaderBar Buttons @@ -55,6 +57,7 @@ const SiderBar = () => { about: '/about', chat: '/chat', detail: '/detail', + pricing: '/pricing', }; const headerButtons = useMemo( @@ -100,6 +103,12 @@ const SiderBar = () => { to: '/topup', icon: , }, + { + text: '模型价格', + itemKey: 'pricing', + to: '/pricing', + icon: , + }, { text: '用户管理', itemKey: 'user', @@ -161,34 +170,8 @@ const SiderBar = () => { } const { success, data } = res.data; if (success) { - localStorage.setItem('status', JSON.stringify(data)); statusDispatch({ type: 'set', payload: data }); - localStorage.setItem('system_name', data.system_name); - localStorage.setItem('logo', data.logo); - localStorage.setItem('footer_html', data.footer_html); - localStorage.setItem('quota_per_unit', data.quota_per_unit); - localStorage.setItem('display_in_currency', data.display_in_currency); - localStorage.setItem('enable_drawing', data.enable_drawing); - localStorage.setItem('enable_data_export', data.enable_data_export); - localStorage.setItem( - 'data_export_default_time', - data.data_export_default_time, - ); - localStorage.setItem( - 'default_collapse_sidebar', - data.default_collapse_sidebar, - ); - localStorage.setItem('mj_notify_enabled', data.mj_notify_enabled); - if (data.chat_link) { - localStorage.setItem('chat_link', data.chat_link); - } else { - localStorage.removeItem('chat_link'); - } - if (data.chat_link2) { - localStorage.setItem('chat_link2', data.chat_link2); - } else { - localStorage.removeItem('chat_link2'); - } + setStatusData(data); } else { showError('无法正常连接至服务器!'); } diff --git a/web/src/helpers/data.js b/web/src/helpers/data.js new file mode 100644 index 0000000..750b670 --- /dev/null +++ b/web/src/helpers/data.js @@ -0,0 +1,33 @@ +export function setStatusData(data) { + localStorage.setItem('status', JSON.stringify(data)); + localStorage.setItem('system_name', data.system_name); + localStorage.setItem('logo', data.logo); + localStorage.setItem('footer_html', data.footer_html); + localStorage.setItem('quota_per_unit', data.quota_per_unit); + localStorage.setItem('display_in_currency', data.display_in_currency); + localStorage.setItem('enable_drawing', data.enable_drawing); + localStorage.setItem('enable_data_export', data.enable_data_export); + localStorage.setItem( + 'data_export_default_time', + data.data_export_default_time, + ); + localStorage.setItem( + 'default_collapse_sidebar', + data.default_collapse_sidebar, + ); + localStorage.setItem('mj_notify_enabled', data.mj_notify_enabled); + if (data.chat_link) { + localStorage.setItem('chat_link', data.chat_link); + } else { + localStorage.removeItem('chat_link'); + } + if (data.chat_link2) { + localStorage.setItem('chat_link2', data.chat_link2); + } else { + localStorage.removeItem('chat_link2'); + } +} + +export function setUserData(data) { + localStorage.setItem('user', JSON.stringify(data)); +} diff --git a/web/src/helpers/render.js b/web/src/helpers/render.js index 3113fed..d84b2eb 100644 --- a/web/src/helpers/render.js +++ b/web/src/helpers/render.js @@ -159,7 +159,7 @@ export function renderModelPrice(

提示 ${inputRatioPrice} / 1M tokens

补全 ${completionRatioPrice} / 1M tokens

-

+

提示 {inputTokens} tokens / 1M tokens * ${inputRatioPrice} + 补全{' '} {completionTokens} tokens / 1M tokens * ${completionRatioPrice} = $ diff --git a/web/src/pages/Pricing/index.js b/web/src/pages/Pricing/index.js new file mode 100644 index 0000000..cb56a47 --- /dev/null +++ b/web/src/pages/Pricing/index.js @@ -0,0 +1,10 @@ +import React from 'react'; +import ModelPricing from '../../components/ModelPricing.js'; + +const Pricing = () => ( + <> + + +); + +export default Pricing; From 6c059d5bf21d61afd8712df9a3df290f5ee7b611 Mon Sep 17 00:00:00 2001 From: Maple Gao Date: Tue, 14 May 2024 09:46:39 +0800 Subject: [PATCH 25/32] =?UTF-8?q?=E2=9C=A8=20add:=20Support=20GPT4o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- relay/channel/openai/constant.go | 1 + 1 file changed, 1 insertion(+) diff --git a/relay/channel/openai/constant.go b/relay/channel/openai/constant.go index 8c560c7..fd0a602 100644 --- a/relay/channel/openai/constant.go +++ b/relay/channel/openai/constant.go @@ -1,6 +1,7 @@ package openai var ModelList = []string{ + "gpt-4o", "gpt-3.5-turbo", "gpt-3.5-turbo-0301", "gpt-3.5-turbo-0613", "gpt-3.5-turbo-1106", "gpt-3.5-turbo-0125", "gpt-3.5-turbo-16k", "gpt-3.5-turbo-16k-0613", "gpt-3.5-turbo-instruct", From 256ccfa989b87eb5da1fa798fbdcac08e11e4a9a Mon Sep 17 00:00:00 2001 From: Maple Gao Date: Tue, 14 May 2024 09:48:04 +0800 Subject: [PATCH 26/32] =?UTF-8?q?=E2=9C=A8=20add:=20Support=20GPT4o=20rati?= =?UTF-8?q?o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/model-ratio.go | 1 + 1 file changed, 1 insertion(+) diff --git a/common/model-ratio.go b/common/model-ratio.go index 4510551..cd6126c 100644 --- a/common/model-ratio.go +++ b/common/model-ratio.go @@ -28,6 +28,7 @@ var DefaultModelRatio = map[string]float64{ "gpt-4-vision-preview": 5, // $0.01 / 1K tokens "gpt-4-1106-vision-preview": 5, // $0.01 / 1K tokens "gpt-4-turbo": 5, // $0.01 / 1K tokens + "gpt-4o": 0.25, // $0.0015 / 1K tokens "gpt-4-turbo-2024-04-09": 5, // $0.01 / 1K tokens "gpt-3.5-turbo": 0.25, // $0.0015 / 1K tokens //"gpt-3.5-turbo-0301": 0.75, //deprecated From 65ae70919b43c74f1dfe3c8e49ecdad4fdd35243 Mon Sep 17 00:00:00 2001 From: QuentinHsu Date: Tue, 14 May 2024 10:17:20 +0800 Subject: [PATCH 27/32] =?UTF-8?q?perf:=20=E8=BF=90=E8=90=A5=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE-=E6=95=B0=E6=8D=AE=E5=88=B7=E6=96=B0=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/pages/Setting/Operation/SettingsCreditLimit.js | 4 ++-- web/src/pages/Setting/Operation/SettingsDataDashboard.js | 2 +- web/src/pages/Setting/Operation/SettingsDrawing.js | 2 +- web/src/pages/Setting/Operation/SettingsGeneral.js | 2 +- web/src/pages/Setting/Operation/SettingsLog.js | 2 +- web/src/pages/Setting/Operation/SettingsMagnification.js | 2 +- web/src/pages/Setting/Operation/SettingsMonitoring.js | 2 +- web/src/pages/Setting/Operation/SettingsSensitiveWords.js | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/web/src/pages/Setting/Operation/SettingsCreditLimit.js b/web/src/pages/Setting/Operation/SettingsCreditLimit.js index 90dfddf..80e6191 100644 --- a/web/src/pages/Setting/Operation/SettingsCreditLimit.js +++ b/web/src/pages/Setting/Operation/SettingsCreditLimit.js @@ -40,16 +40,16 @@ export default function SettingsCreditLimit(props) { if (requestQueue.length === 1) { if (res.includes(undefined)) return; } else if (requestQueue.length > 1) { - if (res.includes(undefined)) return showError('部分更新失败'); + if (res.includes(undefined)) return showError('部分更新失败,请重试'); } showSuccess('更新成功'); + props.refresh(); }) .catch(() => { showError('更新失败'); }) .finally(() => { setLoading(false); - props.refresh(); }); } diff --git a/web/src/pages/Setting/Operation/SettingsDataDashboard.js b/web/src/pages/Setting/Operation/SettingsDataDashboard.js index 8710639..263f7cb 100644 --- a/web/src/pages/Setting/Operation/SettingsDataDashboard.js +++ b/web/src/pages/Setting/Operation/SettingsDataDashboard.js @@ -47,13 +47,13 @@ export default function DataDashboard(props) { if (res.includes(undefined)) return showError('部分更新失败'); } showSuccess('更新成功'); + props.refresh(); }) .catch(() => { showError('更新失败'); }) .finally(() => { setLoading(false); - props.refresh(); }); } diff --git a/web/src/pages/Setting/Operation/SettingsDrawing.js b/web/src/pages/Setting/Operation/SettingsDrawing.js index d0c0ef7..c9f63af 100644 --- a/web/src/pages/Setting/Operation/SettingsDrawing.js +++ b/web/src/pages/Setting/Operation/SettingsDrawing.js @@ -44,13 +44,13 @@ export default function SettingsDrawing(props) { if (res.includes(undefined)) return showError('部分更新失败'); } showSuccess('更新成功'); + props.refresh(); }) .catch(() => { showError('更新失败'); }) .finally(() => { setLoading(false); - props.refresh(); }); } diff --git a/web/src/pages/Setting/Operation/SettingsGeneral.js b/web/src/pages/Setting/Operation/SettingsGeneral.js index a988ce8..314068a 100644 --- a/web/src/pages/Setting/Operation/SettingsGeneral.js +++ b/web/src/pages/Setting/Operation/SettingsGeneral.js @@ -50,13 +50,13 @@ export default function GeneralSettings(props) { if (res.includes(undefined)) return showError('部分更新失败'); } showSuccess('更新成功'); + props.refresh(); }) .catch(() => { showError('更新失败'); }) .finally(() => { setLoading(false); - props.refresh(); }); } diff --git a/web/src/pages/Setting/Operation/SettingsLog.js b/web/src/pages/Setting/Operation/SettingsLog.js index 4377e73..1a9b509 100644 --- a/web/src/pages/Setting/Operation/SettingsLog.js +++ b/web/src/pages/Setting/Operation/SettingsLog.js @@ -46,13 +46,13 @@ export default function SettingsLog(props) { if (res.includes(undefined)) return showError('部分更新失败'); } showSuccess('更新成功'); + props.refresh(); }) .catch(() => { showError('更新失败'); }) .finally(() => { setLoading(false); - props.refresh(); }); } async function onCleanHistoryLog() { diff --git a/web/src/pages/Setting/Operation/SettingsMagnification.js b/web/src/pages/Setting/Operation/SettingsMagnification.js index f92dae9..849a9ad 100644 --- a/web/src/pages/Setting/Operation/SettingsMagnification.js +++ b/web/src/pages/Setting/Operation/SettingsMagnification.js @@ -46,13 +46,13 @@ export default function SettingsMagnification(props) { if (res.includes(undefined)) return showError('部分更新失败'); } showSuccess('更新成功'); + props.refresh(); }) .catch(() => { showError('更新失败'); }) .finally(() => { setLoading(false); - props.refresh(); }); } catch (error) { showError('请检查输入'); diff --git a/web/src/pages/Setting/Operation/SettingsMonitoring.js b/web/src/pages/Setting/Operation/SettingsMonitoring.js index 39ee002..b8c891c 100644 --- a/web/src/pages/Setting/Operation/SettingsMonitoring.js +++ b/web/src/pages/Setting/Operation/SettingsMonitoring.js @@ -43,13 +43,13 @@ export default function SettingsMonitoring(props) { if (res.includes(undefined)) return showError('部分更新失败'); } showSuccess('更新成功'); + props.refresh(); }) .catch(() => { showError('更新失败'); }) .finally(() => { setLoading(false); - props.refresh(); }); } diff --git a/web/src/pages/Setting/Operation/SettingsSensitiveWords.js b/web/src/pages/Setting/Operation/SettingsSensitiveWords.js index f13daff..279e304 100644 --- a/web/src/pages/Setting/Operation/SettingsSensitiveWords.js +++ b/web/src/pages/Setting/Operation/SettingsSensitiveWords.js @@ -42,13 +42,13 @@ export default function SettingsSensitiveWords(props) { if (res.includes(undefined)) return showError('部分更新失败'); } showSuccess('更新成功'); + props.refresh(); }) .catch(() => { showError('更新失败'); }) .finally(() => { setLoading(false); - props.refresh(); }); } From 470f3a1d51a96311cc89d9b236402a065c061122 Mon Sep 17 00:00:00 2001 From: QuentinHsu Date: Tue, 14 May 2024 10:18:24 +0800 Subject: [PATCH 28/32] =?UTF-8?q?perf:=20=E8=BF=90=E8=90=A5=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE-=E6=8F=90=E7=A4=BA=E6=96=87=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/pages/Setting/Operation/SettingsCreditLimit.js | 6 +++--- web/src/pages/Setting/Operation/SettingsDataDashboard.js | 6 +++--- web/src/pages/Setting/Operation/SettingsDrawing.js | 6 +++--- web/src/pages/Setting/Operation/SettingsGeneral.js | 6 +++--- web/src/pages/Setting/Operation/SettingsLog.js | 6 +++--- web/src/pages/Setting/Operation/SettingsMagnification.js | 7 ++++--- web/src/pages/Setting/Operation/SettingsMonitoring.js | 6 +++--- web/src/pages/Setting/Operation/SettingsSensitiveWords.js | 6 +++--- 8 files changed, 25 insertions(+), 24 deletions(-) diff --git a/web/src/pages/Setting/Operation/SettingsCreditLimit.js b/web/src/pages/Setting/Operation/SettingsCreditLimit.js index 80e6191..ed5862a 100644 --- a/web/src/pages/Setting/Operation/SettingsCreditLimit.js +++ b/web/src/pages/Setting/Operation/SettingsCreditLimit.js @@ -40,13 +40,13 @@ export default function SettingsCreditLimit(props) { if (requestQueue.length === 1) { if (res.includes(undefined)) return; } else if (requestQueue.length > 1) { - if (res.includes(undefined)) return showError('部分更新失败,请重试'); + if (res.includes(undefined)) return showError('部分保存失败,请重试'); } - showSuccess('更新成功'); + showSuccess('保存成功'); props.refresh(); }) .catch(() => { - showError('更新失败'); + showError('保存失败,请重试'); }) .finally(() => { setLoading(false); diff --git a/web/src/pages/Setting/Operation/SettingsDataDashboard.js b/web/src/pages/Setting/Operation/SettingsDataDashboard.js index 263f7cb..de0911e 100644 --- a/web/src/pages/Setting/Operation/SettingsDataDashboard.js +++ b/web/src/pages/Setting/Operation/SettingsDataDashboard.js @@ -44,13 +44,13 @@ export default function DataDashboard(props) { if (requestQueue.length === 1) { if (res.includes(undefined)) return; } else if (requestQueue.length > 1) { - if (res.includes(undefined)) return showError('部分更新失败'); + if (res.includes(undefined)) return showError('部分保存失败,请重试'); } - showSuccess('更新成功'); + showSuccess('保存成功'); props.refresh(); }) .catch(() => { - showError('更新失败'); + showError('保存失败,请重试'); }) .finally(() => { setLoading(false); diff --git a/web/src/pages/Setting/Operation/SettingsDrawing.js b/web/src/pages/Setting/Operation/SettingsDrawing.js index c9f63af..bb1c047 100644 --- a/web/src/pages/Setting/Operation/SettingsDrawing.js +++ b/web/src/pages/Setting/Operation/SettingsDrawing.js @@ -41,13 +41,13 @@ export default function SettingsDrawing(props) { if (requestQueue.length === 1) { if (res.includes(undefined)) return; } else if (requestQueue.length > 1) { - if (res.includes(undefined)) return showError('部分更新失败'); + if (res.includes(undefined)) return showError('部分保存失败,请重试'); } - showSuccess('更新成功'); + showSuccess('保存成功'); props.refresh(); }) .catch(() => { - showError('更新失败'); + showError('保存失败,请重试'); }) .finally(() => { setLoading(false); diff --git a/web/src/pages/Setting/Operation/SettingsGeneral.js b/web/src/pages/Setting/Operation/SettingsGeneral.js index 314068a..ef52371 100644 --- a/web/src/pages/Setting/Operation/SettingsGeneral.js +++ b/web/src/pages/Setting/Operation/SettingsGeneral.js @@ -47,13 +47,13 @@ export default function GeneralSettings(props) { if (requestQueue.length === 1) { if (res.includes(undefined)) return; } else if (requestQueue.length > 1) { - if (res.includes(undefined)) return showError('部分更新失败'); + if (res.includes(undefined)) return showError('部分保存失败,请重试'); } - showSuccess('更新成功'); + showSuccess('保存成功'); props.refresh(); }) .catch(() => { - showError('更新失败'); + showError('保存失败,请重试'); }) .finally(() => { setLoading(false); diff --git a/web/src/pages/Setting/Operation/SettingsLog.js b/web/src/pages/Setting/Operation/SettingsLog.js index 1a9b509..6977dca 100644 --- a/web/src/pages/Setting/Operation/SettingsLog.js +++ b/web/src/pages/Setting/Operation/SettingsLog.js @@ -43,13 +43,13 @@ export default function SettingsLog(props) { if (requestQueue.length === 1) { if (res.includes(undefined)) return; } else if (requestQueue.length > 1) { - if (res.includes(undefined)) return showError('部分更新失败'); + if (res.includes(undefined)) return showError('部分保存失败,请重试'); } - showSuccess('更新成功'); + showSuccess('保存成功'); props.refresh(); }) .catch(() => { - showError('更新失败'); + showError('保存失败,请重试'); }) .finally(() => { setLoading(false); diff --git a/web/src/pages/Setting/Operation/SettingsMagnification.js b/web/src/pages/Setting/Operation/SettingsMagnification.js index 849a9ad..9c64793 100644 --- a/web/src/pages/Setting/Operation/SettingsMagnification.js +++ b/web/src/pages/Setting/Operation/SettingsMagnification.js @@ -43,13 +43,14 @@ export default function SettingsMagnification(props) { if (requestQueue.length === 1) { if (res.includes(undefined)) return; } else if (requestQueue.length > 1) { - if (res.includes(undefined)) return showError('部分更新失败'); + if (res.includes(undefined)) + return showError('部分保存失败,请重试'); } - showSuccess('更新成功'); + showSuccess('保存成功'); props.refresh(); }) .catch(() => { - showError('更新失败'); + showError('保存失败,请重试'); }) .finally(() => { setLoading(false); diff --git a/web/src/pages/Setting/Operation/SettingsMonitoring.js b/web/src/pages/Setting/Operation/SettingsMonitoring.js index b8c891c..fc86b97 100644 --- a/web/src/pages/Setting/Operation/SettingsMonitoring.js +++ b/web/src/pages/Setting/Operation/SettingsMonitoring.js @@ -40,13 +40,13 @@ export default function SettingsMonitoring(props) { if (requestQueue.length === 1) { if (res.includes(undefined)) return; } else if (requestQueue.length > 1) { - if (res.includes(undefined)) return showError('部分更新失败'); + if (res.includes(undefined)) return showError('部分保存失败,请重试'); } - showSuccess('更新成功'); + showSuccess('保存成功'); props.refresh(); }) .catch(() => { - showError('更新失败'); + showError('保存失败,请重试'); }) .finally(() => { setLoading(false); diff --git a/web/src/pages/Setting/Operation/SettingsSensitiveWords.js b/web/src/pages/Setting/Operation/SettingsSensitiveWords.js index 279e304..21583b6 100644 --- a/web/src/pages/Setting/Operation/SettingsSensitiveWords.js +++ b/web/src/pages/Setting/Operation/SettingsSensitiveWords.js @@ -39,13 +39,13 @@ export default function SettingsSensitiveWords(props) { if (requestQueue.length === 1) { if (res.includes(undefined)) return; } else if (requestQueue.length > 1) { - if (res.includes(undefined)) return showError('部分更新失败'); + if (res.includes(undefined)) return showError('部分保存失败,请重试'); } - showSuccess('更新成功'); + showSuccess('保存成功'); props.refresh(); }) .catch(() => { - showError('更新失败'); + showError('保存失败,请重试'); }) .finally(() => { setLoading(false); From 475dea96d29204da7baf172148d533e6d3a948c7 Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Tue, 14 May 2024 11:07:57 +0800 Subject: [PATCH 29/32] feat: update model ratio --- common/model-ratio.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/model-ratio.go b/common/model-ratio.go index 4510551..ff2e83c 100644 --- a/common/model-ratio.go +++ b/common/model-ratio.go @@ -27,6 +27,8 @@ var DefaultModelRatio = map[string]float64{ "gpt-4-turbo-preview": 5, // $0.01 / 1K tokens "gpt-4-vision-preview": 5, // $0.01 / 1K tokens "gpt-4-1106-vision-preview": 5, // $0.01 / 1K tokens + "gpt-4o": 2.5, // $0.01 / 1K tokens + "gpt-4o-2024-05-13": 2.5, // $0.01 / 1K tokens "gpt-4-turbo": 5, // $0.01 / 1K tokens "gpt-4-turbo-2024-04-09": 5, // $0.01 / 1K tokens "gpt-3.5-turbo": 0.25, // $0.0015 / 1K tokens @@ -252,7 +254,7 @@ func GetCompletionRatio(name string) float64 { return 4.0 / 3.0 } if strings.HasPrefix(name, "gpt-4") && name != "gpt-4-all" && !strings.HasPrefix(name, "gpt-4-gizmo") { - if strings.HasPrefix(name, "gpt-4-turbo") || strings.HasSuffix(name, "preview") { + if strings.HasPrefix(name, "gpt-4-turbo") || strings.HasSuffix(name, "preview") || strings.HasPrefix(name, "gpt-4o") { return 3 } return 2 From eda3bd1c9de8a322850e57537dafeb8e165fdb9e Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Tue, 14 May 2024 11:37:24 +0800 Subject: [PATCH 30/32] feat: update model ratio --- common/model-ratio.go | 1 - 1 file changed, 1 deletion(-) diff --git a/common/model-ratio.go b/common/model-ratio.go index 36f0b45..ff2e83c 100644 --- a/common/model-ratio.go +++ b/common/model-ratio.go @@ -30,7 +30,6 @@ var DefaultModelRatio = map[string]float64{ "gpt-4o": 2.5, // $0.01 / 1K tokens "gpt-4o-2024-05-13": 2.5, // $0.01 / 1K tokens "gpt-4-turbo": 5, // $0.01 / 1K tokens - "gpt-4o": 0.25, // $0.0015 / 1K tokens "gpt-4-turbo-2024-04-09": 5, // $0.01 / 1K tokens "gpt-3.5-turbo": 0.25, // $0.0015 / 1K tokens //"gpt-3.5-turbo-0301": 0.75, //deprecated From 9f18641d7ebf2cf3ab9a5676466ebb7dfb8a3e2b Mon Sep 17 00:00:00 2001 From: CaIon <1808837298@qq.com> Date: Tue, 14 May 2024 16:35:43 +0800 Subject: [PATCH 31/32] fix: gpt-4-gizmo-* model ratio --- common/model-ratio.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/common/model-ratio.go b/common/model-ratio.go index ff2e83c..7302731 100644 --- a/common/model-ratio.go +++ b/common/model-ratio.go @@ -242,6 +242,9 @@ func UpdateCompletionRatioByJSONString(jsonStr string) error { } func GetCompletionRatio(name string) float64 { + if strings.HasPrefix(name, "gpt-4-gizmo") { + name = "gpt-4-gizmo-*" + } if strings.HasPrefix(name, "gpt-3.5") { if name == "gpt-3.5-turbo" || strings.HasSuffix(name, "0125") { // https://openai.com/blog/new-embedding-models-and-api-updates @@ -253,7 +256,7 @@ func GetCompletionRatio(name string) float64 { } return 4.0 / 3.0 } - if strings.HasPrefix(name, "gpt-4") && name != "gpt-4-all" && !strings.HasPrefix(name, "gpt-4-gizmo") { + if strings.HasPrefix(name, "gpt-4") && name != "gpt-4-all" && name != "gpt-4-gizmo-*" { if strings.HasPrefix(name, "gpt-4-turbo") || strings.HasSuffix(name, "preview") || strings.HasPrefix(name, "gpt-4o") { return 3 } From 3dd2a5bfc5eed58abfe0e3f5ee0c895aaf4720be Mon Sep 17 00:00:00 2001 From: congyijiu <113180624+congyijiu@users.noreply.github.com> Date: Tue, 14 May 2024 20:47:06 +0800 Subject: [PATCH 32/32] Update constant.go To prevent the default testing of GPT-4o --- relay/channel/openai/constant.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/relay/channel/openai/constant.go b/relay/channel/openai/constant.go index fd0a602..3a62b89 100644 --- a/relay/channel/openai/constant.go +++ b/relay/channel/openai/constant.go @@ -1,14 +1,13 @@ package openai var ModelList = []string{ - "gpt-4o", "gpt-3.5-turbo", "gpt-3.5-turbo-0301", "gpt-3.5-turbo-0613", "gpt-3.5-turbo-1106", "gpt-3.5-turbo-0125", "gpt-3.5-turbo-16k", "gpt-3.5-turbo-16k-0613", "gpt-3.5-turbo-instruct", "gpt-4", "gpt-4-0314", "gpt-4-0613", "gpt-4-1106-preview", "gpt-4-0125-preview", "gpt-4-32k", "gpt-4-32k-0314", "gpt-4-32k-0613", "gpt-4-turbo-preview", "gpt-4-turbo", "gpt-4-turbo-2024-04-09", - "gpt-4-vision-preview", + "gpt-4-vision-preview", "gpt-4o", "text-embedding-ada-002", "text-embedding-3-small", "text-embedding-3-large", "text-curie-001", "text-babbage-001", "text-ada-001", "text-davinci-002", "text-davinci-003", "text-moderation-latest", "text-moderation-stable",