OperationSetting.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. import React, { useEffect, useState } from 'react';
  2. import { Divider, Form, Grid, Header } from 'semantic-ui-react';
  3. import { API, showError, verifyJSON } from '../helpers';
  4. const OperationSetting = () => {
  5. let [inputs, setInputs] = useState({
  6. QuotaForNewUser: 0,
  7. QuotaForInviter: 0,
  8. QuotaForInvitee: 0,
  9. QuotaRemindThreshold: 0,
  10. PreConsumedQuota: 0,
  11. ModelRatio: '',
  12. GroupRatio: '',
  13. TopUpLink: '',
  14. ChatLink: '',
  15. QuotaPerUnit: 0,
  16. AutomaticDisableChannelEnabled: '',
  17. ChannelDisableThreshold: 0,
  18. LogConsumeEnabled: '',
  19. DisplayInCurrencyEnabled: ''
  20. });
  21. const [originInputs, setOriginInputs] = useState({});
  22. let [loading, setLoading] = useState(false);
  23. const getOptions = async () => {
  24. const res = await API.get('/api/option/');
  25. const { success, message, data } = res.data;
  26. if (success) {
  27. let newInputs = {};
  28. data.forEach((item) => {
  29. if (item.key === 'ModelRatio' || item.key === 'GroupRatio') {
  30. item.value = JSON.stringify(JSON.parse(item.value), null, 2);
  31. }
  32. newInputs[item.key] = item.value;
  33. });
  34. setInputs(newInputs);
  35. setOriginInputs(newInputs);
  36. } else {
  37. showError(message);
  38. }
  39. };
  40. useEffect(() => {
  41. getOptions().then();
  42. }, []);
  43. const updateOption = async (key, value) => {
  44. setLoading(true);
  45. if (key.endsWith('Enabled')) {
  46. value = inputs[key] === 'true' ? 'false' : 'true';
  47. }
  48. const res = await API.put('/api/option/', {
  49. key,
  50. value
  51. });
  52. const { success, message } = res.data;
  53. if (success) {
  54. setInputs((inputs) => ({ ...inputs, [key]: value }));
  55. } else {
  56. showError(message);
  57. }
  58. setLoading(false);
  59. };
  60. const handleInputChange = async (e, { name, value }) => {
  61. if (name.endsWith('Enabled')) {
  62. await updateOption(name, value);
  63. } else {
  64. setInputs((inputs) => ({ ...inputs, [name]: value }));
  65. }
  66. };
  67. const submitConfig = async (group) => {
  68. switch (group) {
  69. case 'monitor':
  70. if (originInputs['AutomaticDisableChannelEnabled'] !== inputs.AutomaticDisableChannelEnabled) {
  71. await updateOption('AutomaticDisableChannelEnabled', inputs.AutomaticDisableChannelEnabled);
  72. }
  73. if (originInputs['ChannelDisableThreshold'] !== inputs.ChannelDisableThreshold) {
  74. await updateOption('ChannelDisableThreshold', inputs.ChannelDisableThreshold);
  75. }
  76. if (originInputs['QuotaRemindThreshold'] !== inputs.QuotaRemindThreshold) {
  77. await updateOption('QuotaRemindThreshold', inputs.QuotaRemindThreshold);
  78. }
  79. break;
  80. case 'ratio':
  81. if (originInputs['ModelRatio'] !== inputs.ModelRatio) {
  82. if (!verifyJSON(inputs.ModelRatio)) {
  83. showError('模型倍率不是合法的 JSON 字符串');
  84. return;
  85. }
  86. await updateOption('ModelRatio', inputs.ModelRatio);
  87. }
  88. if (originInputs['GroupRatio'] !== inputs.GroupRatio) {
  89. if (!verifyJSON(inputs.GroupRatio)) {
  90. showError('分组倍率不是合法的 JSON 字符串');
  91. return;
  92. }
  93. await updateOption('GroupRatio', inputs.GroupRatio);
  94. }
  95. break;
  96. case 'quota':
  97. if (originInputs['QuotaForNewUser'] !== inputs.QuotaForNewUser) {
  98. await updateOption('QuotaForNewUser', inputs.QuotaForNewUser);
  99. }
  100. if (originInputs['QuotaForInvitee'] !== inputs.QuotaForInvitee) {
  101. await updateOption('QuotaForInvitee', inputs.QuotaForInvitee);
  102. }
  103. if (originInputs['QuotaForInviter'] !== inputs.QuotaForInviter) {
  104. await updateOption('QuotaForInviter', inputs.QuotaForInviter);
  105. }
  106. if (originInputs['PreConsumedQuota'] !== inputs.PreConsumedQuota) {
  107. await updateOption('PreConsumedQuota', inputs.PreConsumedQuota);
  108. }
  109. break;
  110. case 'general':
  111. if (originInputs['TopUpLink'] !== inputs.TopUpLink) {
  112. await updateOption('TopUpLink', inputs.TopUpLink);
  113. }
  114. if (originInputs['ChatLink'] !== inputs.ChatLink) {
  115. await updateOption('ChatLink', inputs.ChatLink);
  116. }
  117. if (originInputs['QuotaPerUnit'] !== inputs.QuotaPerUnit) {
  118. await updateOption('QuotaPerUnit', inputs.QuotaPerUnit);
  119. }
  120. break;
  121. }
  122. };
  123. return (
  124. <Grid columns={1}>
  125. <Grid.Column>
  126. <Form loading={loading}>
  127. <Header as='h3'>
  128. 通用设置
  129. </Header>
  130. <Form.Group widths={3}>
  131. <Form.Input
  132. label='充值链接'
  133. name='TopUpLink'
  134. onChange={handleInputChange}
  135. autoComplete='new-password'
  136. value={inputs.TopUpLink}
  137. type='link'
  138. placeholder='例如发卡网站的购买链接'
  139. />
  140. <Form.Input
  141. label='聊天页面链接'
  142. name='ChatLink'
  143. onChange={handleInputChange}
  144. autoComplete='new-password'
  145. value={inputs.ChatLink}
  146. type='link'
  147. placeholder='例如 ChatGPT Next Web 的部署地址'
  148. />
  149. <Form.Input
  150. label='额度汇率'
  151. name='QuotaPerUnit'
  152. onChange={handleInputChange}
  153. autoComplete='new-password'
  154. value={inputs.QuotaPerUnit}
  155. type='number'
  156. step='0.01'
  157. placeholder='一单位货币能兑换的额度'
  158. />
  159. </Form.Group>
  160. <Form.Group inline>
  161. <Form.Checkbox
  162. checked={inputs.LogConsumeEnabled === 'true'}
  163. label='启用额度消费日志记录'
  164. name='LogConsumeEnabled'
  165. onChange={handleInputChange}
  166. />
  167. <Form.Checkbox
  168. checked={inputs.DisplayInCurrencyEnabled === 'true'}
  169. label='以货币形式显示额度'
  170. name='DisplayInCurrencyEnabled'
  171. onChange={handleInputChange}
  172. />
  173. </Form.Group>
  174. <Form.Button onClick={() => {
  175. submitConfig('general').then();
  176. }}>保存通用设置</Form.Button>
  177. <Divider />
  178. <Header as='h3'>
  179. 监控设置
  180. </Header>
  181. <Form.Group widths={3}>
  182. <Form.Input
  183. label='最长响应时间'
  184. name='ChannelDisableThreshold'
  185. onChange={handleInputChange}
  186. autoComplete='new-password'
  187. value={inputs.ChannelDisableThreshold}
  188. type='number'
  189. min='0'
  190. placeholder='单位秒,当运行通道全部测试时,超过此时间将自动禁用通道'
  191. />
  192. <Form.Input
  193. label='额度提醒阈值'
  194. name='QuotaRemindThreshold'
  195. onChange={handleInputChange}
  196. autoComplete='new-password'
  197. value={inputs.QuotaRemindThreshold}
  198. type='number'
  199. min='0'
  200. placeholder='低于此额度时将发送邮件提醒用户'
  201. />
  202. </Form.Group>
  203. <Form.Group inline>
  204. <Form.Checkbox
  205. checked={inputs.AutomaticDisableChannelEnabled === 'true'}
  206. label='失败时自动禁用通道'
  207. name='AutomaticDisableChannelEnabled'
  208. onChange={handleInputChange}
  209. />
  210. </Form.Group>
  211. <Form.Button onClick={() => {
  212. submitConfig('monitor').then();
  213. }}>保存监控设置</Form.Button>
  214. <Divider />
  215. <Header as='h3'>
  216. 额度设置
  217. </Header>
  218. <Form.Group widths={4}>
  219. <Form.Input
  220. label='新用户初始额度'
  221. name='QuotaForNewUser'
  222. onChange={handleInputChange}
  223. autoComplete='new-password'
  224. value={inputs.QuotaForNewUser}
  225. type='number'
  226. min='0'
  227. placeholder='例如:100'
  228. />
  229. <Form.Input
  230. label='请求预扣费额度'
  231. name='PreConsumedQuota'
  232. onChange={handleInputChange}
  233. autoComplete='new-password'
  234. value={inputs.PreConsumedQuota}
  235. type='number'
  236. min='0'
  237. placeholder='请求结束后多退少补'
  238. />
  239. <Form.Input
  240. label='邀请新用户奖励额度'
  241. name='QuotaForInviter'
  242. onChange={handleInputChange}
  243. autoComplete='new-password'
  244. value={inputs.QuotaForInviter}
  245. type='number'
  246. min='0'
  247. placeholder='例如:2000'
  248. />
  249. <Form.Input
  250. label='新用户使用邀请码奖励额度'
  251. name='QuotaForInvitee'
  252. onChange={handleInputChange}
  253. autoComplete='new-password'
  254. value={inputs.QuotaForInvitee}
  255. type='number'
  256. min='0'
  257. placeholder='例如:1000'
  258. />
  259. </Form.Group>
  260. <Form.Button onClick={() => {
  261. submitConfig('quota').then();
  262. }}>保存额度设置</Form.Button>
  263. <Divider />
  264. <Header as='h3'>
  265. 倍率设置
  266. </Header>
  267. <Form.Group widths='equal'>
  268. <Form.TextArea
  269. label='模型倍率'
  270. name='ModelRatio'
  271. onChange={handleInputChange}
  272. style={{ minHeight: 250, fontFamily: 'JetBrains Mono, Consolas' }}
  273. autoComplete='new-password'
  274. value={inputs.ModelRatio}
  275. placeholder='为一个 JSON 文本,键为模型名称,值为倍率'
  276. />
  277. </Form.Group>
  278. <Form.Group widths='equal'>
  279. <Form.TextArea
  280. label='分组倍率'
  281. name='GroupRatio'
  282. onChange={handleInputChange}
  283. style={{ minHeight: 250, fontFamily: 'JetBrains Mono, Consolas' }}
  284. autoComplete='new-password'
  285. value={inputs.GroupRatio}
  286. placeholder='为一个 JSON 文本,键为分组名称,值为倍率'
  287. />
  288. </Form.Group>
  289. <Form.Button onClick={() => {
  290. submitConfig('ratio').then();
  291. }}>保存倍率设置</Form.Button>
  292. </Form>
  293. </Grid.Column>
  294. </Grid>
  295. );
  296. };
  297. export default OperationSetting;