SettingGlobalModel.jsx 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. /*
  2. Copyright (C) 2025 QuantumNous
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <https://www.gnu.org/licenses/>.
  13. For commercial licensing, please contact support@quantumnous.com
  14. */
  15. import React, { useEffect, useState, useRef } from 'react';
  16. import { Button, Col, Form, Row, Spin, Banner } from '@douyinfe/semi-ui';
  17. import {
  18. compareObjects,
  19. API,
  20. showError,
  21. showSuccess,
  22. showWarning,
  23. verifyJSON,
  24. } from '../../../helpers';
  25. import { useTranslation } from 'react-i18next';
  26. const thinkingExample = JSON.stringify(
  27. ['moonshotai/kimi-k2-thinking', 'kimi-k2-thinking'],
  28. null,
  29. 2,
  30. );
  31. const defaultGlobalSettingInputs = {
  32. 'global.pass_through_request_enabled': false,
  33. 'global.thinking_model_blacklist': '[]',
  34. 'general_setting.ping_interval_enabled': false,
  35. 'general_setting.ping_interval_seconds': 60,
  36. };
  37. export default function SettingGlobalModel(props) {
  38. const { t } = useTranslation();
  39. const [loading, setLoading] = useState(false);
  40. const [inputs, setInputs] = useState(defaultGlobalSettingInputs);
  41. const refForm = useRef();
  42. const [inputsRow, setInputsRow] = useState(defaultGlobalSettingInputs);
  43. const normalizeValueBeforeSave = (key, value) => {
  44. if (key === 'global.thinking_model_blacklist') {
  45. const text = typeof value === 'string' ? value.trim() : '';
  46. return text === '' ? '[]' : value;
  47. }
  48. return value;
  49. };
  50. function onSubmit() {
  51. const updateArray = compareObjects(inputs, inputsRow);
  52. if (!updateArray.length) return showWarning(t('你似乎并没有修改什么'));
  53. const requestQueue = updateArray.map((item) => {
  54. const normalizedValue = normalizeValueBeforeSave(
  55. item.key,
  56. inputs[item.key],
  57. );
  58. let value = String(normalizedValue);
  59. return API.put('/api/option/', {
  60. key: item.key,
  61. value,
  62. });
  63. });
  64. setLoading(true);
  65. Promise.all(requestQueue)
  66. .then((res) => {
  67. if (requestQueue.length === 1) {
  68. if (res.includes(undefined)) return;
  69. } else if (requestQueue.length > 1) {
  70. if (res.includes(undefined))
  71. return showError(t('部分保存失败,请重试'));
  72. }
  73. showSuccess(t('保存成功'));
  74. props.refresh();
  75. })
  76. .catch(() => {
  77. showError(t('保存失败,请重试'));
  78. })
  79. .finally(() => {
  80. setLoading(false);
  81. });
  82. }
  83. useEffect(() => {
  84. const currentInputs = {};
  85. for (const key of Object.keys(defaultGlobalSettingInputs)) {
  86. if (props.options[key] !== undefined) {
  87. let value = props.options[key];
  88. if (key === 'global.thinking_model_blacklist') {
  89. try {
  90. value =
  91. value && String(value).trim() !== ''
  92. ? JSON.stringify(JSON.parse(value), null, 2)
  93. : defaultGlobalSettingInputs[key];
  94. } catch (error) {
  95. value = defaultGlobalSettingInputs[key];
  96. }
  97. }
  98. currentInputs[key] = value;
  99. } else {
  100. currentInputs[key] = defaultGlobalSettingInputs[key];
  101. }
  102. }
  103. setInputs(currentInputs);
  104. setInputsRow(structuredClone(currentInputs));
  105. if (refForm.current) {
  106. refForm.current.setValues(currentInputs);
  107. }
  108. }, [props.options]);
  109. return (
  110. <>
  111. <Spin spinning={loading}>
  112. <Form
  113. values={inputs}
  114. getFormApi={(formAPI) => (refForm.current = formAPI)}
  115. style={{ marginBottom: 15 }}
  116. >
  117. <Form.Section text={t('全局设置')}>
  118. <Row>
  119. <Col xs={24} sm={12} md={8} lg={8} xl={8}>
  120. <Form.Switch
  121. label={t('启用请求透传')}
  122. field={'global.pass_through_request_enabled'}
  123. onChange={(value) =>
  124. setInputs({
  125. ...inputs,
  126. 'global.pass_through_request_enabled': value,
  127. })
  128. }
  129. extraText={
  130. t('开启后,所有请求将直接透传给上游,不会进行任何处理(重定向和渠道适配也将失效),请谨慎开启')
  131. }
  132. />
  133. </Col>
  134. </Row>
  135. <Row>
  136. <Col span={24}>
  137. <Form.TextArea
  138. label={t('禁用思考处理的模型列表')}
  139. field={'global.thinking_model_blacklist'}
  140. placeholder={
  141. t('例如:') +
  142. '\n' +
  143. thinkingExample
  144. }
  145. rows={4}
  146. rules={[
  147. {
  148. validator: (rule, value) => {
  149. if (!value || value.trim() === '') return true;
  150. return verifyJSON(value);
  151. },
  152. message: t('不是合法的 JSON 字符串'),
  153. },
  154. ]}
  155. extraText={t(
  156. '列出的模型将不会自动添加或移除-thinking/-nothinking 后缀',
  157. )}
  158. onChange={(value) =>
  159. setInputs({
  160. ...inputs,
  161. 'global.thinking_model_blacklist': value,
  162. })
  163. }
  164. />
  165. </Col>
  166. </Row>
  167. <Form.Section text={t('连接保活设置')}>
  168. <Row style={{ marginTop: 10 }}>
  169. <Col span={24}>
  170. <Banner
  171. type='warning'
  172. description={t('警告:启用保活后,如果已经写入保活数据后渠道出错,系统无法重试,如果必须开启,推荐设置尽可能大的Ping间隔')}
  173. />
  174. </Col>
  175. </Row>
  176. <Row>
  177. <Col xs={24} sm={12} md={8} lg={8} xl={8}>
  178. <Form.Switch
  179. label={t('启用Ping间隔')}
  180. field={'general_setting.ping_interval_enabled'}
  181. onChange={(value) =>
  182. setInputs({
  183. ...inputs,
  184. 'general_setting.ping_interval_enabled': value,
  185. })
  186. }
  187. extraText={t('开启后,将定期发送ping数据保持连接活跃')}
  188. />
  189. </Col>
  190. <Col xs={24} sm={12} md={8} lg={8} xl={8}>
  191. <Form.InputNumber
  192. label={t('Ping间隔(秒)')}
  193. field={'general_setting.ping_interval_seconds'}
  194. onChange={(value) =>
  195. setInputs({
  196. ...inputs,
  197. 'general_setting.ping_interval_seconds': value,
  198. })
  199. }
  200. min={1}
  201. disabled={!inputs['general_setting.ping_interval_enabled']}
  202. />
  203. </Col>
  204. </Row>
  205. </Form.Section>
  206. <Row>
  207. <Button size='default' onClick={onSubmit}>
  208. {t('保存')}
  209. </Button>
  210. </Row>
  211. </Form.Section>
  212. </Form>
  213. </Spin>
  214. </>
  215. );
  216. }