EditToken.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. import React, { useEffect, useState } from 'react';
  2. import { useNavigate } from 'react-router-dom';
  3. import {
  4. API,
  5. isMobile,
  6. showError,
  7. showSuccess,
  8. timestamp2string,
  9. } from '../../helpers';
  10. import { renderQuotaWithPrompt } from '../../helpers/render';
  11. import {
  12. AutoComplete,
  13. Banner,
  14. Button,
  15. Checkbox,
  16. DatePicker,
  17. Input,
  18. Select,
  19. SideSheet,
  20. Space,
  21. Spin,
  22. Typography,
  23. } from '@douyinfe/semi-ui';
  24. import Title from '@douyinfe/semi-ui/lib/es/typography/title';
  25. import { Divider } from 'semantic-ui-react';
  26. const EditToken = (props) => {
  27. const [isEdit, setIsEdit] = useState(false);
  28. const [loading, setLoading] = useState(isEdit);
  29. const originInputs = {
  30. name: '',
  31. remain_quota: isEdit ? 0 : 500000,
  32. expired_time: -1,
  33. unlimited_quota: false,
  34. model_limits_enabled: false,
  35. model_limits: [],
  36. };
  37. const [inputs, setInputs] = useState(originInputs);
  38. const {
  39. name,
  40. remain_quota,
  41. expired_time,
  42. unlimited_quota,
  43. model_limits_enabled,
  44. model_limits,
  45. } = inputs;
  46. // const [visible, setVisible] = useState(false);
  47. const [models, setModels] = useState({});
  48. const navigate = useNavigate();
  49. const handleInputChange = (name, value) => {
  50. setInputs((inputs) => ({ ...inputs, [name]: value }));
  51. };
  52. const handleCancel = () => {
  53. props.handleClose();
  54. };
  55. const setExpiredTime = (month, day, hour, minute) => {
  56. let now = new Date();
  57. let timestamp = now.getTime() / 1000;
  58. let seconds = month * 30 * 24 * 60 * 60;
  59. seconds += day * 24 * 60 * 60;
  60. seconds += hour * 60 * 60;
  61. seconds += minute * 60;
  62. if (seconds !== 0) {
  63. timestamp += seconds;
  64. setInputs({ ...inputs, expired_time: timestamp2string(timestamp) });
  65. } else {
  66. setInputs({ ...inputs, expired_time: -1 });
  67. }
  68. };
  69. const setUnlimitedQuota = () => {
  70. setInputs({ ...inputs, unlimited_quota: !unlimited_quota });
  71. };
  72. const loadModels = async () => {
  73. let res = await API.get(`/api/user/models`);
  74. const { success, message, data } = res.data;
  75. if (success) {
  76. let localModelOptions = data.map((model) => ({
  77. label: model,
  78. value: model,
  79. }));
  80. setModels(localModelOptions);
  81. } else {
  82. showError(message);
  83. }
  84. };
  85. const loadToken = async () => {
  86. setLoading(true);
  87. let res = await API.get(`/api/token/${props.editingToken.id}`);
  88. const { success, message, data } = res.data;
  89. if (success) {
  90. if (data.expired_time !== -1) {
  91. data.expired_time = timestamp2string(data.expired_time);
  92. }
  93. if (data.model_limits !== '') {
  94. data.model_limits = data.model_limits.split(',');
  95. } else {
  96. data.model_limits = [];
  97. }
  98. setInputs(data);
  99. } else {
  100. showError(message);
  101. }
  102. setLoading(false);
  103. };
  104. useEffect(() => {
  105. setIsEdit(props.editingToken.id !== undefined);
  106. }, [props.editingToken.id]);
  107. useEffect(() => {
  108. if (!isEdit) {
  109. setInputs(originInputs);
  110. } else {
  111. loadToken().then(() => {
  112. // console.log(inputs);
  113. });
  114. }
  115. loadModels();
  116. }, [isEdit]);
  117. // 新增 state 变量 tokenCount 来记录用户想要创建的令牌数量,默认为 1
  118. const [tokenCount, setTokenCount] = useState(1);
  119. // 新增处理 tokenCount 变化的函数
  120. const handleTokenCountChange = (value) => {
  121. // 确保用户输入的是正整数
  122. const count = parseInt(value, 10);
  123. if (!isNaN(count) && count > 0) {
  124. setTokenCount(count);
  125. }
  126. };
  127. // 生成一个随机的四位字母数字字符串
  128. const generateRandomSuffix = () => {
  129. const characters =
  130. 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  131. let result = '';
  132. for (let i = 0; i < 6; i++) {
  133. result += characters.charAt(
  134. Math.floor(Math.random() * characters.length),
  135. );
  136. }
  137. return result;
  138. };
  139. const submit = async () => {
  140. setLoading(true);
  141. if (isEdit) {
  142. // 编辑令牌的逻辑保持不变
  143. let localInputs = { ...inputs };
  144. localInputs.remain_quota = parseInt(localInputs.remain_quota);
  145. if (localInputs.expired_time !== -1) {
  146. let time = Date.parse(localInputs.expired_time);
  147. if (isNaN(time)) {
  148. showError('过期时间格式错误!');
  149. setLoading(false);
  150. return;
  151. }
  152. localInputs.expired_time = Math.ceil(time / 1000);
  153. }
  154. localInputs.model_limits = localInputs.model_limits.join(',');
  155. let res = await API.put(`/api/token/`, {
  156. ...localInputs,
  157. id: parseInt(props.editingToken.id),
  158. });
  159. const { success, message } = res.data;
  160. if (success) {
  161. showSuccess('令牌更新成功!');
  162. props.refresh();
  163. props.handleClose();
  164. } else {
  165. showError(message);
  166. }
  167. } else {
  168. // 处理新增多个令牌的情况
  169. let successCount = 0; // 记录成功创建的令牌数量
  170. for (let i = 0; i < tokenCount; i++) {
  171. let localInputs = { ...inputs };
  172. if (i !== 0) {
  173. // 如果用户想要创建多个令牌,则给每个令牌一个序号后缀
  174. localInputs.name = `${inputs.name}-${generateRandomSuffix()}`;
  175. }
  176. localInputs.remain_quota = parseInt(localInputs.remain_quota);
  177. if (localInputs.expired_time !== -1) {
  178. let time = Date.parse(localInputs.expired_time);
  179. if (isNaN(time)) {
  180. showError('过期时间格式错误!');
  181. setLoading(false);
  182. break;
  183. }
  184. localInputs.expired_time = Math.ceil(time / 1000);
  185. }
  186. localInputs.model_limits = localInputs.model_limits.join(',');
  187. let res = await API.post(`/api/token/`, localInputs);
  188. const { success, message } = res.data;
  189. if (success) {
  190. successCount++;
  191. } else {
  192. showError(message);
  193. break; // 如果创建失败,终止循环
  194. }
  195. }
  196. if (successCount > 0) {
  197. showSuccess(
  198. `${successCount}个令牌创建成功,请在列表页面点击复制获取令牌!`,
  199. );
  200. props.refresh();
  201. props.handleClose();
  202. }
  203. }
  204. setLoading(false);
  205. setInputs(originInputs); // 重置表单
  206. setTokenCount(1); // 重置数量为默认值
  207. };
  208. return (
  209. <>
  210. <SideSheet
  211. placement={isEdit ? 'right' : 'left'}
  212. title={
  213. <Title level={3}>{isEdit ? '更新令牌信息' : '创建新的令牌'}</Title>
  214. }
  215. headerStyle={{ borderBottom: '1px solid var(--semi-color-border)' }}
  216. bodyStyle={{ borderBottom: '1px solid var(--semi-color-border)' }}
  217. visible={props.visiable}
  218. footer={
  219. <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
  220. <Space>
  221. <Button theme='solid' size={'large'} onClick={submit}>
  222. 提交
  223. </Button>
  224. <Button
  225. theme='solid'
  226. size={'large'}
  227. type={'tertiary'}
  228. onClick={handleCancel}
  229. >
  230. 取消
  231. </Button>
  232. </Space>
  233. </div>
  234. }
  235. closeIcon={null}
  236. onCancel={() => handleCancel()}
  237. width={isMobile() ? '100%' : 600}
  238. >
  239. <Spin spinning={loading}>
  240. <Input
  241. style={{ marginTop: 20 }}
  242. label='名称'
  243. name='name'
  244. placeholder={'请输入名称'}
  245. onChange={(value) => handleInputChange('name', value)}
  246. value={name}
  247. autoComplete='new-password'
  248. required={!isEdit}
  249. />
  250. <Divider />
  251. <DatePicker
  252. label='过期时间'
  253. name='expired_time'
  254. placeholder={'请选择过期时间'}
  255. onChange={(value) => handleInputChange('expired_time', value)}
  256. value={expired_time}
  257. autoComplete='new-password'
  258. type='dateTime'
  259. />
  260. <div style={{ marginTop: 20 }}>
  261. <Space>
  262. <Button
  263. type={'tertiary'}
  264. onClick={() => {
  265. setExpiredTime(0, 0, 0, 0);
  266. }}
  267. >
  268. 永不过期
  269. </Button>
  270. <Button
  271. type={'tertiary'}
  272. onClick={() => {
  273. setExpiredTime(0, 0, 1, 0);
  274. }}
  275. >
  276. 一小时
  277. </Button>
  278. <Button
  279. type={'tertiary'}
  280. onClick={() => {
  281. setExpiredTime(1, 0, 0, 0);
  282. }}
  283. >
  284. 一个月
  285. </Button>
  286. <Button
  287. type={'tertiary'}
  288. onClick={() => {
  289. setExpiredTime(0, 1, 0, 0);
  290. }}
  291. >
  292. 一天
  293. </Button>
  294. </Space>
  295. </div>
  296. <Divider />
  297. <Banner
  298. type={'warning'}
  299. description={
  300. '注意,令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制。'
  301. }
  302. ></Banner>
  303. <div style={{ marginTop: 20 }}>
  304. <Typography.Text>{`额度${renderQuotaWithPrompt(remain_quota)}`}</Typography.Text>
  305. </div>
  306. <AutoComplete
  307. style={{ marginTop: 8 }}
  308. name='remain_quota'
  309. placeholder={'请输入额度'}
  310. onChange={(value) => handleInputChange('remain_quota', value)}
  311. value={remain_quota}
  312. autoComplete='new-password'
  313. type='number'
  314. // position={'top'}
  315. data={[
  316. { value: 500000, label: '1$' },
  317. { value: 5000000, label: '10$' },
  318. { value: 25000000, label: '50$' },
  319. { value: 50000000, label: '100$' },
  320. { value: 250000000, label: '500$' },
  321. { value: 500000000, label: '1000$' },
  322. ]}
  323. disabled={unlimited_quota}
  324. />
  325. {!isEdit && (
  326. <>
  327. <div style={{ marginTop: 20 }}>
  328. <Typography.Text>新建数量</Typography.Text>
  329. </div>
  330. <AutoComplete
  331. style={{ marginTop: 8 }}
  332. label='数量'
  333. placeholder={'请选择或输入创建令牌的数量'}
  334. onChange={(value) => handleTokenCountChange(value)}
  335. onSelect={(value) => handleTokenCountChange(value)}
  336. value={tokenCount.toString()}
  337. autoComplete='off'
  338. type='number'
  339. data={[
  340. { value: 10, label: '10个' },
  341. { value: 20, label: '20个' },
  342. { value: 30, label: '30个' },
  343. { value: 100, label: '100个' },
  344. ]}
  345. disabled={unlimited_quota}
  346. />
  347. </>
  348. )}
  349. <div>
  350. <Button
  351. style={{ marginTop: 8 }}
  352. type={'warning'}
  353. onClick={() => {
  354. setUnlimitedQuota();
  355. }}
  356. >
  357. {unlimited_quota ? '取消无限额度' : '设为无限额度'}
  358. </Button>
  359. </div>
  360. <Divider />
  361. <div style={{ marginTop: 10, display: 'flex' }}>
  362. <Space>
  363. <Checkbox
  364. name='model_limits_enabled'
  365. checked={model_limits_enabled}
  366. onChange={(e) =>
  367. handleInputChange('model_limits_enabled', e.target.checked)
  368. }
  369. ></Checkbox>
  370. <Typography.Text>
  371. 启用模型限制(非必要,不建议启用)
  372. </Typography.Text>
  373. </Space>
  374. </div>
  375. <Select
  376. style={{ marginTop: 8 }}
  377. placeholder={'请选择该渠道所支持的模型'}
  378. name='models'
  379. required
  380. multiple
  381. selection
  382. onChange={(value) => {
  383. handleInputChange('model_limits', value);
  384. }}
  385. value={inputs.model_limits}
  386. autoComplete='new-password'
  387. optionList={models}
  388. disabled={!model_limits_enabled}
  389. />
  390. </Spin>
  391. </SideSheet>
  392. </>
  393. );
  394. };
  395. export default EditToken;