ModelSettingsVisualEditor.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. // ModelSettingsVisualEditor.js
  2. import React, { useEffect, useState } from 'react';
  3. import { Table, Button, Input, Modal, Form, Space } from '@douyinfe/semi-ui';
  4. import { IconDelete, IconPlus, IconSearch, IconSave } from '@douyinfe/semi-icons';
  5. import { showError, showSuccess } from '../../../helpers';
  6. import { API } from '../../../helpers';
  7. import { useTranslation } from 'react-i18next';
  8. export default function ModelSettingsVisualEditor(props) {
  9. const { t } = useTranslation();
  10. const [models, setModels] = useState([]);
  11. const [visible, setVisible] = useState(false);
  12. const [currentModel, setCurrentModel] = useState(null);
  13. const [searchText, setSearchText] = useState('');
  14. const [currentPage, setCurrentPage] = useState(1);
  15. const [loading, setLoading] = useState(false);
  16. const pageSize = 10;
  17. useEffect(() => {
  18. try {
  19. const modelPrice = JSON.parse(props.options.ModelPrice || '{}');
  20. const modelRatio = JSON.parse(props.options.ModelRatio || '{}');
  21. const completionRatio = JSON.parse(props.options.CompletionRatio || '{}');
  22. // 合并所有模型名称
  23. const modelNames = new Set([
  24. ...Object.keys(modelPrice),
  25. ...Object.keys(modelRatio),
  26. ...Object.keys(completionRatio)
  27. ]);
  28. const modelData = Array.from(modelNames).map(name => ({
  29. name,
  30. price: modelPrice[name] === undefined ? '' : modelPrice[name],
  31. ratio: modelRatio[name] === undefined ? '' : modelRatio[name],
  32. completionRatio: completionRatio[name] === undefined ? '' : completionRatio[name]
  33. }));
  34. setModels(modelData);
  35. } catch (error) {
  36. console.error('JSON解析错误:', error);
  37. }
  38. }, [props.options]);
  39. // 首先声明分页相关的工具函数
  40. const getPagedData = (data, currentPage, pageSize) => {
  41. const start = (currentPage - 1) * pageSize;
  42. const end = start + pageSize;
  43. return data.slice(start, end);
  44. };
  45. // 在 return 语句之前,先处理过滤和分页逻辑
  46. const filteredModels = models.filter(model =>
  47. searchText ? model.name.toLowerCase().includes(searchText.toLowerCase()) : true
  48. );
  49. // 然后基于过滤后的数据计算分页数据
  50. const pagedData = getPagedData(filteredModels, currentPage, pageSize);
  51. const SubmitData = async () => {
  52. setLoading(true);
  53. const output = {
  54. ModelPrice: {},
  55. ModelRatio: {},
  56. CompletionRatio: {}
  57. };
  58. let currentConvertModelName = '';
  59. try {
  60. // 数据转换
  61. models.forEach(model => {
  62. currentConvertModelName = model.name;
  63. if (model.price !== '') {
  64. // 如果价格不为空,则转换为浮点数,忽略倍率参数
  65. output.ModelPrice[model.name] = parseFloat(model.price)
  66. } else {
  67. if (model.ratio !== '') output.ModelRatio[model.name] = parseFloat(model.ratio);
  68. if (model.completionRatio !== '') output.CompletionRatio[model.name] = parseFloat(model.completionRatio);
  69. }
  70. });
  71. // 准备API请求数组
  72. const finalOutput = {
  73. ModelPrice: JSON.stringify(output.ModelPrice, null, 2),
  74. ModelRatio: JSON.stringify(output.ModelRatio, null, 2),
  75. CompletionRatio: JSON.stringify(output.CompletionRatio, null, 2)
  76. };
  77. const requestQueue = Object.entries(finalOutput).map(([key, value]) => {
  78. return API.put('/api/option/', {
  79. key,
  80. value
  81. });
  82. });
  83. // 批量处理请求
  84. const results = await Promise.all(requestQueue);
  85. // 验证结果
  86. if (requestQueue.length === 1) {
  87. if (results.includes(undefined)) return;
  88. } else if (requestQueue.length > 1) {
  89. if (results.includes(undefined)) {
  90. return showError('部分保存失败,请重试');
  91. }
  92. }
  93. // 检查每个请求的结果
  94. for (const res of results) {
  95. if (!res.data.success) {
  96. return showError(res.data.message);
  97. }
  98. }
  99. showSuccess('保存成功');
  100. props.refresh();
  101. } catch (error) {
  102. console.error('保存失败:', error);
  103. showError('保存失败,请重试');
  104. } finally {
  105. setLoading(false);
  106. }
  107. };
  108. const columns = [
  109. {
  110. title: t('模型名称'),
  111. dataIndex: 'name',
  112. key: 'name',
  113. },
  114. {
  115. title: t('模型固定价格'),
  116. dataIndex: 'price',
  117. key: 'price',
  118. render: (text, record) => (
  119. <Input
  120. value={text}
  121. placeholder={t('按量计费')}
  122. onChange={value => updateModel(record.name, 'price', value)}
  123. />
  124. )
  125. },
  126. {
  127. title: t('模型倍率'),
  128. dataIndex: 'ratio',
  129. key: 'ratio',
  130. render: (text, record) => (
  131. <Input
  132. value={text}
  133. placeholder={record.price !== '' ? t('模型倍率') : t('默认补全倍率')}
  134. disabled={record.price !== ''}
  135. onChange={value => updateModel(record.name, 'ratio', value)}
  136. />
  137. )
  138. },
  139. {
  140. title: t('补全倍率'),
  141. dataIndex: 'completionRatio',
  142. key: 'completionRatio',
  143. render: (text, record) => (
  144. <Input
  145. value={text}
  146. placeholder={record.price !== '' ? t('补全倍率') : t('默认补全倍率')}
  147. disabled={record.price !== ''}
  148. onChange={value => updateModel(record.name, 'completionRatio', value)}
  149. />
  150. )
  151. },
  152. {
  153. title: t('操作'),
  154. key: 'action',
  155. render: (_, record) => (
  156. <Button
  157. icon={<IconDelete />}
  158. type="danger"
  159. onClick={() => deleteModel(record.name)}
  160. />
  161. )
  162. }
  163. ];
  164. const updateModel = (name, field, value) => {
  165. if (isNaN(value)) {
  166. showError('请输入数字');
  167. return;
  168. }
  169. setModels(prev =>
  170. prev.map(model =>
  171. model.name === name
  172. ? { ...model, [field]: value }
  173. : model
  174. )
  175. );
  176. };
  177. const deleteModel = (name) => {
  178. setModels(prev => prev.filter(model => model.name !== name));
  179. };
  180. const addModel = (values) => {
  181. // 检查模型名称是否存在, 如果存在则拒绝添加
  182. if (models.some(model => model.name === values.name)) {
  183. showError('模型名称已存在');
  184. return;
  185. }
  186. setModels(prev => [{
  187. name: values.name,
  188. price: values.price || '',
  189. ratio: values.ratio || '',
  190. completionRatio: values.completionRatio || ''
  191. }, ...prev]);
  192. setVisible(false);
  193. showSuccess('添加成功');
  194. };
  195. return (
  196. <>
  197. <Space vertical align="start" style={{ width: '100%' }}>
  198. <Space>
  199. <Button icon={<IconPlus />} onClick={() => setVisible(true)}>
  200. {t('添加模型')}
  201. </Button>
  202. <Button type="primary" icon={<IconSave />} onClick={SubmitData}>
  203. {t('应用更改')}
  204. </Button>
  205. <Input
  206. prefix={<IconSearch />}
  207. placeholder={t('搜索模型名称')}
  208. value={searchText}
  209. onChange={value => {
  210. setSearchText(value)
  211. setCurrentPage(1);
  212. }}
  213. style={{ width: 200 }}
  214. />
  215. </Space>
  216. <Table
  217. columns={columns}
  218. dataSource={pagedData}
  219. pagination={{
  220. currentPage: currentPage,
  221. pageSize: pageSize,
  222. total: filteredModels.length,
  223. onPageChange: page => setCurrentPage(page),
  224. formatPageText: (page) =>
  225. t('第 {{start}} - {{end}} 条,共 {{total}} 条', {
  226. start: page.currentStart,
  227. end: page.currentEnd,
  228. total: filteredModels.length
  229. }),
  230. showTotal: true,
  231. showSizeChanger: false
  232. }}
  233. />
  234. </Space>
  235. <Modal
  236. title={t('添加模型')}
  237. visible={visible}
  238. onCancel={() => setVisible(false)}
  239. onOk={() => {
  240. currentModel && addModel(currentModel);
  241. }}
  242. >
  243. <Form>
  244. <Form.Input
  245. field="name"
  246. label={t('模型名称')}
  247. placeholder="strawberry"
  248. required
  249. onChange={value => setCurrentModel(prev => ({ ...prev, name: value }))}
  250. />
  251. <Form.Switch
  252. field="priceMode"
  253. label={<>{t('定价模式')}:{currentModel?.priceMode ? t("固定价格") : t("倍率模式")}</>}
  254. onChange={checked => {
  255. setCurrentModel(prev => ({
  256. ...prev,
  257. price: '',
  258. ratio: '',
  259. completionRatio: '',
  260. priceMode: checked
  261. }));
  262. }}
  263. />
  264. {currentModel?.priceMode ? (
  265. <Form.Input
  266. field="price"
  267. label={t('固定价格(每次)')}
  268. placeholder={t('输入每次价格')}
  269. onChange={value => setCurrentModel(prev => ({ ...prev, price: value }))}
  270. />
  271. ) : (
  272. <>
  273. <Form.Input
  274. field="ratio"
  275. label={t('模型倍率')}
  276. placeholder={t('输入模型倍率')}
  277. onChange={value => setCurrentModel(prev => ({ ...prev, ratio: value }))}
  278. />
  279. <Form.Input
  280. field="completionRatio"
  281. label={t('补全倍率')}
  282. placeholder={t('输入补全价格')}
  283. onChange={value => setCurrentModel(prev => ({ ...prev, completionRatio: value }))}
  284. />
  285. </>
  286. )}
  287. </Form>
  288. </Modal>
  289. </>
  290. );
  291. }