| | import React, { useEffect, useState } from 'react'; |
| | import { |
| | Table, |
| | Button, |
| | Input, |
| | Modal, |
| | Form, |
| | Space, |
| | Typography, |
| | Radio, |
| | Notification, |
| | } from '@douyinfe/semi-ui'; |
| | import { |
| | IconDelete, |
| | IconPlus, |
| | IconSearch, |
| | IconSave, |
| | IconBolt, |
| | } from '@douyinfe/semi-icons'; |
| | import { API, showError, showSuccess } from '../../../helpers'; |
| | import { useTranslation } from 'react-i18next'; |
| |
|
| | export default function ModelRatioNotSetEditor(props) { |
| | const { t } = useTranslation(); |
| | const [models, setModels] = useState([]); |
| | const [visible, setVisible] = useState(false); |
| | const [batchVisible, setBatchVisible] = useState(false); |
| | const [currentModel, setCurrentModel] = useState(null); |
| | const [searchText, setSearchText] = useState(''); |
| | const [currentPage, setCurrentPage] = useState(1); |
| | const [pageSize, setPageSize] = useState(10); |
| | const [loading, setLoading] = useState(false); |
| | const [enabledModels, setEnabledModels] = useState([]); |
| | const [selectedRowKeys, setSelectedRowKeys] = useState([]); |
| | const [batchFillType, setBatchFillType] = useState('ratio'); |
| | const [batchFillValue, setBatchFillValue] = useState(''); |
| | const [batchRatioValue, setBatchRatioValue] = useState(''); |
| | const [batchCompletionRatioValue, setBatchCompletionRatioValue] = |
| | useState(''); |
| | const { Text } = Typography; |
| | |
| | const pageSizeOptions = [10, 20, 50, 100]; |
| |
|
| | const getAllEnabledModels = async () => { |
| | try { |
| | const res = await API.get('/api/channel/models_enabled'); |
| | const { success, message, data } = res.data; |
| | if (success) { |
| | setEnabledModels(data); |
| | } else { |
| | showError(message); |
| | } |
| | } catch (error) { |
| | console.error(t('获取启用模型失败:'), error); |
| | showError(t('获取启用模型失败')); |
| | } |
| | }; |
| |
|
| | useEffect(() => { |
| | |
| | getAllEnabledModels(); |
| | }, []); |
| |
|
| | useEffect(() => { |
| | try { |
| | const modelPrice = JSON.parse(props.options.ModelPrice || '{}'); |
| | const modelRatio = JSON.parse(props.options.ModelRatio || '{}'); |
| | const completionRatio = JSON.parse(props.options.CompletionRatio || '{}'); |
| |
|
| | |
| | const unsetModels = enabledModels.filter((modelName) => { |
| | const hasPrice = modelPrice[modelName] !== undefined; |
| | const hasRatio = modelRatio[modelName] !== undefined; |
| |
|
| | |
| | return !hasPrice && !hasRatio; |
| | }); |
| |
|
| | |
| | const modelData = unsetModels.map((name) => ({ |
| | name, |
| | price: modelPrice[name] || '', |
| | ratio: modelRatio[name] || '', |
| | completionRatio: completionRatio[name] || '', |
| | })); |
| |
|
| | setModels(modelData); |
| | |
| | setSelectedRowKeys([]); |
| | } catch (error) { |
| | console.error(t('JSON解析错误:'), error); |
| | } |
| | }, [props.options, enabledModels]); |
| |
|
| | |
| | const getPagedData = (data, currentPage, pageSize) => { |
| | const start = (currentPage - 1) * pageSize; |
| | const end = start + pageSize; |
| | return data.slice(start, end); |
| | }; |
| |
|
| | |
| | const handlePageSizeChange = (size) => { |
| | setPageSize(size); |
| | |
| | const totalPages = Math.ceil(filteredModels.length / size); |
| | if (currentPage > totalPages) { |
| | setCurrentPage(totalPages || 1); |
| | } |
| | }; |
| |
|
| | |
| | const filteredModels = models.filter((model) => |
| | searchText |
| | ? model.name.toLowerCase().includes(searchText.toLowerCase()) |
| | : true, |
| | ); |
| |
|
| | |
| | const pagedData = getPagedData(filteredModels, currentPage, pageSize); |
| |
|
| | const SubmitData = async () => { |
| | setLoading(true); |
| | const output = { |
| | ModelPrice: JSON.parse(props.options.ModelPrice || '{}'), |
| | ModelRatio: JSON.parse(props.options.ModelRatio || '{}'), |
| | CompletionRatio: JSON.parse(props.options.CompletionRatio || '{}'), |
| | }; |
| |
|
| | try { |
| | |
| | models.forEach((model) => { |
| | |
| | if (model.price !== '') { |
| | |
| | output.ModelPrice[model.name] = parseFloat(model.price); |
| | } else { |
| | if (model.ratio !== '') |
| | output.ModelRatio[model.name] = parseFloat(model.ratio); |
| | if (model.completionRatio !== '') |
| | output.CompletionRatio[model.name] = parseFloat( |
| | model.completionRatio, |
| | ); |
| | } |
| | }); |
| |
|
| | |
| | const finalOutput = { |
| | ModelPrice: JSON.stringify(output.ModelPrice, null, 2), |
| | ModelRatio: JSON.stringify(output.ModelRatio, null, 2), |
| | CompletionRatio: JSON.stringify(output.CompletionRatio, null, 2), |
| | }; |
| |
|
| | const requestQueue = Object.entries(finalOutput).map(([key, value]) => { |
| | return API.put('/api/option/', { |
| | key, |
| | value, |
| | }); |
| | }); |
| |
|
| | |
| | const results = await Promise.all(requestQueue); |
| |
|
| | |
| | if (requestQueue.length === 1) { |
| | if (results.includes(undefined)) return; |
| | } else if (requestQueue.length > 1) { |
| | if (results.includes(undefined)) { |
| | return showError(t('部分保存失败,请重试')); |
| | } |
| | } |
| |
|
| | |
| | for (const res of results) { |
| | if (!res.data.success) { |
| | return showError(res.data.message); |
| | } |
| | } |
| |
|
| | showSuccess(t('保存成功')); |
| | props.refresh(); |
| | |
| | getAllEnabledModels(); |
| | } catch (error) { |
| | console.error(t('保存失败:'), error); |
| | showError(t('保存失败,请重试')); |
| | } finally { |
| | setLoading(false); |
| | } |
| | }; |
| |
|
| | const columns = [ |
| | { |
| | title: t('模型名称'), |
| | dataIndex: 'name', |
| | key: 'name', |
| | }, |
| | { |
| | title: t('模型固定价格'), |
| | dataIndex: 'price', |
| | key: 'price', |
| | render: (text, record) => ( |
| | <Input |
| | value={text} |
| | placeholder={t('按量计费')} |
| | onChange={(value) => updateModel(record.name, 'price', value)} |
| | /> |
| | ), |
| | }, |
| | { |
| | title: t('模型倍率'), |
| | dataIndex: 'ratio', |
| | key: 'ratio', |
| | render: (text, record) => ( |
| | <Input |
| | value={text} |
| | placeholder={record.price !== '' ? t('模型倍率') : t('输入模型倍率')} |
| | disabled={record.price !== ''} |
| | onChange={(value) => updateModel(record.name, 'ratio', value)} |
| | /> |
| | ), |
| | }, |
| | { |
| | title: t('补全倍率'), |
| | dataIndex: 'completionRatio', |
| | key: 'completionRatio', |
| | render: (text, record) => ( |
| | <Input |
| | value={text} |
| | placeholder={record.price !== '' ? t('补全倍率') : t('输入补全倍率')} |
| | disabled={record.price !== ''} |
| | onChange={(value) => |
| | updateModel(record.name, 'completionRatio', value) |
| | } |
| | /> |
| | ), |
| | }, |
| | ]; |
| |
|
| | const updateModel = (name, field, value) => { |
| | if (value !== '' && isNaN(value)) { |
| | showError(t('请输入数字')); |
| | return; |
| | } |
| | setModels((prev) => |
| | prev.map((model) => |
| | model.name === name ? { ...model, [field]: value } : model, |
| | ), |
| | ); |
| | }; |
| |
|
| | const addModel = (values) => { |
| | |
| | if (models.some((model) => model.name === values.name)) { |
| | showError(t('模型名称已存在')); |
| | return; |
| | } |
| | setModels((prev) => [ |
| | { |
| | name: values.name, |
| | price: values.price || '', |
| | ratio: values.ratio || '', |
| | completionRatio: values.completionRatio || '', |
| | }, |
| | ...prev, |
| | ]); |
| | setVisible(false); |
| | showSuccess(t('添加成功')); |
| | }; |
| |
|
| | |
| | const handleBatchFill = () => { |
| | if (selectedRowKeys.length === 0) { |
| | showError(t('请先选择需要批量设置的模型')); |
| | return; |
| | } |
| |
|
| | if (batchFillType === 'bothRatio') { |
| | if (batchRatioValue === '' || batchCompletionRatioValue === '') { |
| | showError(t('请输入模型倍率和补全倍率')); |
| | return; |
| | } |
| | if (isNaN(batchRatioValue) || isNaN(batchCompletionRatioValue)) { |
| | showError(t('请输入有效的数字')); |
| | return; |
| | } |
| | } else { |
| | if (batchFillValue === '') { |
| | showError(t('请输入填充值')); |
| | return; |
| | } |
| | if (isNaN(batchFillValue)) { |
| | showError(t('请输入有效的数字')); |
| | return; |
| | } |
| | } |
| |
|
| | |
| | setModels((prev) => |
| | prev.map((model) => { |
| | if (selectedRowKeys.includes(model.name)) { |
| | if (batchFillType === 'price') { |
| | return { |
| | ...model, |
| | price: batchFillValue, |
| | ratio: '', |
| | completionRatio: '', |
| | }; |
| | } else if (batchFillType === 'ratio') { |
| | return { |
| | ...model, |
| | price: '', |
| | ratio: batchFillValue, |
| | }; |
| | } else if (batchFillType === 'completionRatio') { |
| | return { |
| | ...model, |
| | price: '', |
| | completionRatio: batchFillValue, |
| | }; |
| | } else if (batchFillType === 'bothRatio') { |
| | return { |
| | ...model, |
| | price: '', |
| | ratio: batchRatioValue, |
| | completionRatio: batchCompletionRatioValue, |
| | }; |
| | } |
| | } |
| | return model; |
| | }), |
| | ); |
| |
|
| | setBatchVisible(false); |
| | Notification.success({ |
| | title: t('批量设置成功'), |
| | content: t('已为 {{count}} 个模型设置{{type}}', { |
| | count: selectedRowKeys.length, |
| | type: |
| | batchFillType === 'price' |
| | ? t('固定价格') |
| | : batchFillType === 'ratio' |
| | ? t('模型倍率') |
| | : batchFillType === 'completionRatio' |
| | ? t('补全倍率') |
| | : t('模型倍率和补全倍率'), |
| | }), |
| | duration: 3, |
| | }); |
| | }; |
| |
|
| | const handleBatchTypeChange = (value) => { |
| | console.log(t('Changing batch type to:'), value); |
| | setBatchFillType(value); |
| |
|
| | |
| | if (value !== 'bothRatio') { |
| | setBatchFillValue(''); |
| | } else { |
| | setBatchRatioValue(''); |
| | setBatchCompletionRatioValue(''); |
| | } |
| | }; |
| |
|
| | const rowSelection = { |
| | selectedRowKeys, |
| | onChange: (selectedKeys) => { |
| | setSelectedRowKeys(selectedKeys); |
| | }, |
| | }; |
| |
|
| | return ( |
| | <> |
| | <Space vertical align='start' style={{ width: '100%' }}> |
| | <Space> |
| | <Button icon={<IconPlus />} onClick={() => setVisible(true)}> |
| | {t('添加模型')} |
| | </Button> |
| | <Button |
| | icon={<IconBolt />} |
| | type='secondary' |
| | onClick={() => setBatchVisible(true)} |
| | disabled={selectedRowKeys.length === 0} |
| | > |
| | {t('批量设置')} ({selectedRowKeys.length}) |
| | </Button> |
| | <Button |
| | type='primary' |
| | icon={<IconSave />} |
| | onClick={SubmitData} |
| | loading={loading} |
| | > |
| | {t('应用更改')} |
| | </Button> |
| | <Input |
| | prefix={<IconSearch />} |
| | placeholder={t('搜索模型名称')} |
| | value={searchText} |
| | onChange={(value) => { |
| | setSearchText(value); |
| | setCurrentPage(1); |
| | }} |
| | style={{ width: 200 }} |
| | /> |
| | </Space> |
| | |
| | <Text> |
| | {t('此页面仅显示未设置价格或倍率的模型,设置后将自动从列表中移除')} |
| | </Text> |
| | |
| | <Table |
| | columns={columns} |
| | dataSource={pagedData} |
| | rowSelection={rowSelection} |
| | rowKey='name' |
| | pagination={{ |
| | currentPage: currentPage, |
| | pageSize: pageSize, |
| | total: filteredModels.length, |
| | onPageChange: (page) => setCurrentPage(page), |
| | onPageSizeChange: handlePageSizeChange, |
| | pageSizeOptions: pageSizeOptions, |
| | formatPageText: (page) => |
| | t('第 {{start}} - {{end}} 条,共 {{total}} 条', { |
| | start: page.currentStart, |
| | end: page.currentEnd, |
| | total: filteredModels.length, |
| | }), |
| | showTotal: true, |
| | showSizeChanger: true, |
| | }} |
| | empty={ |
| | <div style={{ textAlign: 'center', padding: '20px' }}> |
| | {t('没有未设置的模型')} |
| | </div> |
| | } |
| | /> |
| | </Space> |
| | |
| | {/* 添加模型弹窗 */} |
| | <Modal |
| | title={t('添加模型')} |
| | visible={visible} |
| | onCancel={() => setVisible(false)} |
| | onOk={() => { |
| | currentModel && addModel(currentModel); |
| | }} |
| | > |
| | <Form> |
| | <Form.Input |
| | field='name' |
| | label={t('模型名称')} |
| | placeholder='strawberry' |
| | required |
| | onChange={(value) => |
| | setCurrentModel((prev) => ({ ...prev, name: value })) |
| | } |
| | /> |
| | <Form.Switch |
| | field='priceMode' |
| | label={ |
| | <> |
| | {t('定价模式')}: |
| | {currentModel?.priceMode ? t('固定价格') : t('倍率模式')} |
| | </> |
| | } |
| | onChange={(checked) => { |
| | setCurrentModel((prev) => ({ |
| | ...prev, |
| | price: '', |
| | ratio: '', |
| | completionRatio: '', |
| | priceMode: checked, |
| | })); |
| | }} |
| | /> |
| | {currentModel?.priceMode ? ( |
| | <Form.Input |
| | field='price' |
| | label={t('固定价格(每次)')} |
| | placeholder={t('输入每次价格')} |
| | onChange={(value) => |
| | setCurrentModel((prev) => ({ ...prev, price: value })) |
| | } |
| | /> |
| | ) : ( |
| | <> |
| | <Form.Input |
| | field='ratio' |
| | label={t('模型倍率')} |
| | placeholder={t('输入模型倍率')} |
| | onChange={(value) => |
| | setCurrentModel((prev) => ({ ...prev, ratio: value })) |
| | } |
| | /> |
| | <Form.Input |
| | field='completionRatio' |
| | label={t('补全倍率')} |
| | placeholder={t('输入补全价格')} |
| | onChange={(value) => |
| | setCurrentModel((prev) => ({ |
| | ...prev, |
| | completionRatio: value, |
| | })) |
| | } |
| | /> |
| | </> |
| | )} |
| | </Form> |
| | </Modal> |
| | |
| | {/* 批量设置弹窗 */} |
| | <Modal |
| | title={t('批量设置模型参数')} |
| | visible={batchVisible} |
| | onCancel={() => setBatchVisible(false)} |
| | onOk={handleBatchFill} |
| | width={500} |
| | > |
| | <Form> |
| | <Form.Section text={t('设置类型')}> |
| | <div style={{ marginBottom: '16px' }}> |
| | <Space> |
| | <Radio |
| | checked={batchFillType === 'price'} |
| | onChange={() => handleBatchTypeChange('price')} |
| | > |
| | {t('固定价格')} |
| | </Radio> |
| | <Radio |
| | checked={batchFillType === 'ratio'} |
| | onChange={() => handleBatchTypeChange('ratio')} |
| | > |
| | {t('模型倍率')} |
| | </Radio> |
| | <Radio |
| | checked={batchFillType === 'completionRatio'} |
| | onChange={() => handleBatchTypeChange('completionRatio')} |
| | > |
| | {t('补全倍率')} |
| | </Radio> |
| | <Radio |
| | checked={batchFillType === 'bothRatio'} |
| | onChange={() => handleBatchTypeChange('bothRatio')} |
| | > |
| | {t('模型倍率和补全倍率同时设置')} |
| | </Radio> |
| | </Space> |
| | </div> |
| | </Form.Section> |
| | |
| | {batchFillType === 'bothRatio' ? ( |
| | <> |
| | <Form.Input |
| | field='batchRatioValue' |
| | label={t('模型倍率值')} |
| | placeholder={t('请输入模型倍率')} |
| | value={batchRatioValue} |
| | onChange={(value) => setBatchRatioValue(value)} |
| | /> |
| | <Form.Input |
| | field='batchCompletionRatioValue' |
| | label={t('补全倍率值')} |
| | placeholder={t('请输入补全倍率')} |
| | value={batchCompletionRatioValue} |
| | onChange={(value) => setBatchCompletionRatioValue(value)} |
| | /> |
| | </> |
| | ) : ( |
| | <Form.Input |
| | field='batchFillValue' |
| | label={ |
| | batchFillType === 'price' |
| | ? t('固定价格值') |
| | : batchFillType === 'ratio' |
| | ? t('模型倍率值') |
| | : t('补全倍率值') |
| | } |
| | placeholder={t('请输入数值')} |
| | value={batchFillValue} |
| | onChange={(value) => setBatchFillValue(value)} |
| | /> |
| | )} |
| | |
| | <Text type='tertiary'> |
| | {t('将为选中的 ')} <Text strong>{selectedRowKeys.length}</Text>{' '} |
| | {t(' 个模型设置相同的值')} |
| | </Text> |
| | <div style={{ marginTop: '8px' }}> |
| | <Text type='tertiary'> |
| | {t('当前设置类型: ')}{' '} |
| | <Text strong> |
| | {batchFillType === 'price' |
| | ? t('固定价格') |
| | : batchFillType === 'ratio' |
| | ? t('模型倍率') |
| | : batchFillType === 'completionRatio' |
| | ? t('补全倍率') |
| | : t('模型倍率和补全倍率')} |
| | </Text> |
| | </Text> |
| | </div> |
| | </Form> |
| | </Modal> |
| | </> |
| | ); |
| | } |
| |
|