Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d8b8a44c9c | |||
| c2a37d83a7 | |||
| a1f6781c0e | |||
| 88f7c0670f | |||
| c08edc315d | |||
| 683accf05b | |||
| d8c10dcb51 |
@@ -117,7 +117,6 @@ New API提供了丰富的功能,详细特性请参考[特性说明](https://do
|
||||
|
||||
> [!TIP]
|
||||
> 最新版Docker镜像:`calciumion/new-api:latest`
|
||||
> 默认账号root 密码123456
|
||||
|
||||
### 多机部署注意事项
|
||||
- 必须设置环境变量 `SESSION_SECRET`,否则会导致多机部署时登录状态不一致
|
||||
|
||||
+10
-4
@@ -6,6 +6,7 @@ import (
|
||||
"one-api/constant"
|
||||
"one-api/model"
|
||||
"one-api/setting/operation_setting"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Setup struct {
|
||||
@@ -124,7 +125,7 @@ func PostSetup(c *gin.Context) {
|
||||
operation_setting.DemoSiteEnabled = req.DemoSiteEnabled
|
||||
|
||||
// Save operation modes to database for persistence
|
||||
err = model.UpdateOption("self_use_mode", boolToString(req.SelfUseModeEnabled))
|
||||
err = model.UpdateOption("SelfUseModeEnabled", boolToString(req.SelfUseModeEnabled))
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{
|
||||
"success": false,
|
||||
@@ -133,7 +134,7 @@ func PostSetup(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
err = model.UpdateOption("demo_site_mode", boolToString(req.DemoSiteEnabled))
|
||||
err = model.UpdateOption("DemoSiteEnabled", boolToString(req.DemoSiteEnabled))
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{
|
||||
"success": false,
|
||||
@@ -144,11 +145,16 @@ func PostSetup(c *gin.Context) {
|
||||
|
||||
// Update setup status
|
||||
constant.Setup = true
|
||||
err = model.UpdateOption("setup", "true")
|
||||
|
||||
setup := model.Setup{
|
||||
Version: common.Version,
|
||||
InitializedAt: time.Now().Unix(),
|
||||
}
|
||||
err = model.DB.Create(&setup).Error
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{
|
||||
"success": false,
|
||||
"message": "设置初始化状态失败: " + err.Error(),
|
||||
"message": "系统初始化失败: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1346,5 +1346,26 @@
|
||||
"提示缓存倍率": "Prompt cache ratio",
|
||||
"缓存:${{price}} * {{ratio}} = ${{total}} / 1M tokens (缓存倍率: {{cacheRatio}})": "Cache: ${{price}} * {{ratio}} = ${{total}} / 1M tokens (cache ratio: {{cacheRatio}})",
|
||||
"提示 {{nonCacheInput}} tokens + 缓存 {{cacheInput}} tokens * {{cacheRatio}} / 1M tokens * ${{price}} + 补全 {{completion}} tokens / 1M tokens * ${{compPrice}} * 分组 {{ratio}} = ${{total}}": "Prompt {{nonCacheInput}} tokens + cache {{cacheInput}} tokens * {{cacheRatio}} / 1M tokens * ${{price}} + completion {{completion}} tokens / 1M tokens * ${{compPrice}} * group {{ratio}} = ${{total}}",
|
||||
"缓存 Tokens": "Cache Tokens"
|
||||
"缓存 Tokens": "Cache Tokens",
|
||||
"系统初始化": "System initialization",
|
||||
"管理员账号已经初始化过,请继续设置系统参数": "The admin account has already been initialized, please continue to set the system parameters",
|
||||
"管理员账号": "Admin account",
|
||||
"请输入管理员用户名": "Please enter the admin username",
|
||||
"请输入管理员密码": "Please enter the admin password",
|
||||
"请确认管理员密码": "Please confirm the admin password",
|
||||
"请选择使用模式": "Please select the usage mode",
|
||||
"数据库警告": "Database warning",
|
||||
"您正在使用 SQLite 数据库。如果您在容器环境中运行,请确保已正确设置数据库文件的持久化映射,否则容器重启后所有数据将丢失!": "You are using the SQLite database. If you are running in a container environment, please ensure that the database file persistence mapping is correctly set, otherwise all data will be lost after container restart!",
|
||||
"建议在生产环境中使用 MySQL 或 PostgreSQL 数据库,或确保 SQLite 数据库文件已映射到宿主机的持久化存储。": "It is recommended to use MySQL or PostgreSQL databases in production environments, or ensure that the SQLite database file is mapped to the persistent storage of the host machine.",
|
||||
"使用模式": "Usage mode",
|
||||
"对外运营模式": "Default mode",
|
||||
"密码长度至少为8个字符": "Password must be at least 8 characters long",
|
||||
"表单引用错误,请刷新页面重试": "Form reference error, please refresh the page and try again",
|
||||
"默认模式,适用于为多个用户提供服务的场景。": "Default mode, suitable for scenarios where multiple users are provided.",
|
||||
"此模式下,系统将计算每次调用的用量,您需要对每个模型都设置价格,如果没有设置价格,用户将无法使用该模型。": "In this mode, the system will calculate the usage of each call, you need to set the price for each model, if the price is not set, the user will not be able to use the model.",
|
||||
"适用于个人使用的场景。": "Suitable for personal use.",
|
||||
"不需要设置模型价格,系统将弱化用量计算,您可专注于使用模型。": "No need to set the model price, the system will weaken the usage calculation, you can focus on using the model.",
|
||||
"适用于展示系统功能的场景。": "Suitable for scenarios where the system functions are displayed.",
|
||||
"可在初始化后修改": "Can be modified after initialization",
|
||||
"初始化系统": "Initialize system"
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
// ModelSettingsVisualEditor.js
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Table, Button, Input, Modal, Form, Space } from '@douyinfe/semi-ui';
|
||||
import { IconDelete, IconPlus, IconSearch, IconSave } from '@douyinfe/semi-icons';
|
||||
import React, { useContext, useEffect, useState, useRef } from 'react';
|
||||
import { Table, Button, Input, Modal, Form, Space, RadioGroup, Radio, Tabs, TabPane } from '@douyinfe/semi-ui';
|
||||
import { IconDelete, IconPlus, IconSearch, IconSave, IconEdit } from '@douyinfe/semi-icons';
|
||||
import { showError, showSuccess } from '../../../helpers';
|
||||
import { API } from '../../../helpers';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { StatusContext } from '../../../context/Status/index.js';
|
||||
import { getQuotaPerUnit } from '../../../helpers/render.js';
|
||||
|
||||
export default function ModelSettingsVisualEditor(props) {
|
||||
const { t } = useTranslation();
|
||||
@@ -14,7 +16,11 @@ export default function ModelSettingsVisualEditor(props) {
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [pricingMode, setPricingMode] = useState('per-token'); // 'per-token' or 'per-request'
|
||||
const [pricingSubMode, setPricingSubMode] = useState('ratio'); // 'ratio' or 'token-price'
|
||||
const formRef = useRef(null);
|
||||
const pageSize = 10;
|
||||
const quotaPerUnit = getQuotaPerUnit()
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
@@ -171,11 +177,19 @@ export default function ModelSettingsVisualEditor(props) {
|
||||
title: t('操作'),
|
||||
key: 'action',
|
||||
render: (_, record) => (
|
||||
<Button
|
||||
icon={<IconDelete />}
|
||||
type="danger"
|
||||
onClick={() => deleteModel(record.name)}
|
||||
/>
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<IconEdit />}
|
||||
onClick={() => editModel(record)}
|
||||
>
|
||||
</Button>
|
||||
<Button
|
||||
icon={<IconDelete />}
|
||||
type="danger"
|
||||
onClick={() => deleteModel(record.name)}
|
||||
/>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
];
|
||||
@@ -197,28 +211,171 @@ export default function ModelSettingsVisualEditor(props) {
|
||||
const deleteModel = (name) => {
|
||||
setModels(prev => prev.filter(model => model.name !== name));
|
||||
};
|
||||
const addModel = (values) => {
|
||||
// 检查模型名称是否存在, 如果存在则拒绝添加
|
||||
if (models.some(model => model.name === values.name)) {
|
||||
showError('模型名称已存在');
|
||||
return;
|
||||
|
||||
const calculateRatioFromTokenPrice = (tokenPrice) => {
|
||||
return tokenPrice / 2;
|
||||
};
|
||||
|
||||
const calculateCompletionRatioFromPrices = (modelTokenPrice, completionTokenPrice) => {
|
||||
if (!modelTokenPrice || modelTokenPrice === '0') {
|
||||
showError('模型价格不能为0');
|
||||
return '';
|
||||
}
|
||||
setModels(prev => [{
|
||||
name: values.name,
|
||||
price: values.price || '',
|
||||
ratio: values.ratio || '',
|
||||
completionRatio: values.completionRatio || ''
|
||||
}, ...prev]);
|
||||
setVisible(false);
|
||||
showSuccess('添加成功');
|
||||
return completionTokenPrice / modelTokenPrice;
|
||||
};
|
||||
|
||||
const handleTokenPriceChange = (value) => {
|
||||
|
||||
// Use a temporary variable to hold the new state
|
||||
let newState = {
|
||||
...(currentModel || {}),
|
||||
tokenPrice: value,
|
||||
ratio: 0
|
||||
};
|
||||
|
||||
if (!isNaN(value) && value !== '') {
|
||||
const tokenPrice = parseFloat(value);
|
||||
const ratio = calculateRatioFromTokenPrice(tokenPrice);
|
||||
newState.ratio = ratio;
|
||||
}
|
||||
|
||||
// Set the state with the complete updated object
|
||||
setCurrentModel(newState);
|
||||
};
|
||||
|
||||
const handleCompletionTokenPriceChange = (value) => {
|
||||
|
||||
// Use a temporary variable to hold the new state
|
||||
let newState = {
|
||||
...(currentModel || {}),
|
||||
completionTokenPrice: value,
|
||||
completionRatio: 0
|
||||
};
|
||||
|
||||
if (!isNaN(value) && value !== '' && currentModel?.tokenPrice) {
|
||||
const completionTokenPrice = parseFloat(value);
|
||||
const modelTokenPrice = parseFloat(currentModel.tokenPrice);
|
||||
|
||||
if (modelTokenPrice > 0) {
|
||||
const completionRatio = calculateCompletionRatioFromPrices(modelTokenPrice, completionTokenPrice);
|
||||
newState.completionRatio = completionRatio;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the state with the complete updated object
|
||||
setCurrentModel(newState);
|
||||
};
|
||||
|
||||
const addOrUpdateModel = (values) => {
|
||||
// Check if we're editing an existing model or adding a new one
|
||||
const existingModelIndex = models.findIndex(model => model.name === values.name);
|
||||
|
||||
if (existingModelIndex >= 0) {
|
||||
// Update existing model
|
||||
setModels(prev => prev.map((model, index) =>
|
||||
index === existingModelIndex ? {
|
||||
name: values.name,
|
||||
price: values.price || '',
|
||||
ratio: values.ratio || '',
|
||||
completionRatio: values.completionRatio || ''
|
||||
} : model
|
||||
));
|
||||
setVisible(false);
|
||||
showSuccess(t('更新成功'));
|
||||
} else {
|
||||
// Add new model
|
||||
// Check if model name already exists
|
||||
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 calculateTokenPriceFromRatio = (ratio) => {
|
||||
return ratio * 2;
|
||||
};
|
||||
|
||||
const resetModalState = () => {
|
||||
setCurrentModel(null);
|
||||
setPricingMode('per-token');
|
||||
setPricingSubMode('ratio');
|
||||
};
|
||||
|
||||
const editModel = (record) => {
|
||||
|
||||
// Determine which pricing mode to use based on the model's current configuration
|
||||
let initialPricingMode = 'per-token';
|
||||
let initialPricingSubMode = 'ratio';
|
||||
|
||||
if (record.price !== '') {
|
||||
initialPricingMode = 'per-request';
|
||||
} else {
|
||||
initialPricingMode = 'per-token';
|
||||
// We default to ratio mode, but could set to token-price if needed
|
||||
}
|
||||
|
||||
// Set the pricing modes for the form
|
||||
setPricingMode(initialPricingMode);
|
||||
setPricingSubMode(initialPricingSubMode);
|
||||
|
||||
// Create a copy of the model data to avoid modifying the original
|
||||
const modelCopy = { ...record };
|
||||
|
||||
// If the model has ratio data and we want to populate token price fields
|
||||
if (record.ratio) {
|
||||
modelCopy.tokenPrice = calculateTokenPriceFromRatio(parseFloat(record.ratio)).toString();
|
||||
|
||||
if (record.completionRatio) {
|
||||
modelCopy.completionTokenPrice = (parseFloat(modelCopy.tokenPrice) * parseFloat(record.completionRatio)).toString();
|
||||
}
|
||||
}
|
||||
|
||||
// Set the current model
|
||||
setCurrentModel(modelCopy);
|
||||
|
||||
// Open the modal
|
||||
setVisible(true);
|
||||
|
||||
// Use setTimeout to ensure the form is rendered before setting values
|
||||
setTimeout(() => {
|
||||
if (formRef.current) {
|
||||
// Update the form fields based on pricing mode
|
||||
const formValues = {
|
||||
name: modelCopy.name,
|
||||
};
|
||||
|
||||
if (initialPricingMode === 'per-request') {
|
||||
formValues.priceInput = modelCopy.price;
|
||||
} else if (initialPricingMode === 'per-token') {
|
||||
formValues.ratioInput = modelCopy.ratio;
|
||||
formValues.completionRatioInput = modelCopy.completionRatio;
|
||||
formValues.modelTokenPrice = modelCopy.tokenPrice;
|
||||
formValues.completionTokenPrice = modelCopy.completionTokenPrice;
|
||||
}
|
||||
|
||||
formRef.current.setValues(formValues);
|
||||
}
|
||||
}, 0);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Space vertical align="start" style={{ width: '100%' }}>
|
||||
<Space>
|
||||
<Button icon={<IconPlus />} onClick={() => setVisible(true)}>
|
||||
<Button icon={<IconPlus />} onClick={() => {
|
||||
resetModalState();
|
||||
setVisible(true);
|
||||
}}>
|
||||
{t('添加模型')}
|
||||
</Button>
|
||||
<Button type="primary" icon={<IconSave />} onClick={SubmitData}>
|
||||
@@ -256,56 +413,205 @@ export default function ModelSettingsVisualEditor(props) {
|
||||
</Space>
|
||||
|
||||
<Modal
|
||||
title={t('添加模型')}
|
||||
title={currentModel && currentModel.name && models.some(model => model.name === currentModel.name) ? t('编辑模型') : t('添加模型')}
|
||||
visible={visible}
|
||||
onCancel={() => setVisible(false)}
|
||||
onCancel={() => {
|
||||
resetModalState();
|
||||
setVisible(false);
|
||||
}}
|
||||
onOk={() => {
|
||||
currentModel && addModel(currentModel);
|
||||
if (currentModel) {
|
||||
// If we're in token price mode, make sure ratio values are properly set
|
||||
const valuesToSave = { ...currentModel };
|
||||
|
||||
if (pricingMode === 'per-token' && pricingSubMode === 'token-price' && currentModel.tokenPrice) {
|
||||
// Calculate and set ratio from token price
|
||||
const tokenPrice = parseFloat(currentModel.tokenPrice);
|
||||
valuesToSave.ratio = (tokenPrice / 2).toString();
|
||||
|
||||
// Calculate and set completion ratio if both token prices are available
|
||||
if (currentModel.completionTokenPrice && currentModel.tokenPrice) {
|
||||
const completionPrice = parseFloat(currentModel.completionTokenPrice);
|
||||
const modelPrice = parseFloat(currentModel.tokenPrice);
|
||||
if (modelPrice > 0) {
|
||||
valuesToSave.completionRatio = (completionPrice / modelPrice).toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear price if we're in per-token mode
|
||||
if (pricingMode === 'per-token') {
|
||||
valuesToSave.price = '';
|
||||
} else {
|
||||
// Clear ratios if we're in per-request mode
|
||||
valuesToSave.ratio = '';
|
||||
valuesToSave.completionRatio = '';
|
||||
}
|
||||
|
||||
addOrUpdateModel(valuesToSave);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Form>
|
||||
<Form getFormApi={api => formRef.current = api}>
|
||||
<Form.Input
|
||||
field="name"
|
||||
label={t('模型名称')}
|
||||
placeholder="strawberry"
|
||||
required
|
||||
disabled={currentModel && currentModel.name && models.some(model => model.name === currentModel.name)}
|
||||
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.Section text={t('定价模式')}>
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<RadioGroup type="button" value={pricingMode} onChange={(e) => {
|
||||
const newMode = e.target.value;
|
||||
const oldMode = pricingMode;
|
||||
setPricingMode(newMode);
|
||||
|
||||
// Instead of resetting all values, convert between modes
|
||||
if (currentModel) {
|
||||
const updatedModel = { ...currentModel };
|
||||
|
||||
// Update formRef with converted values
|
||||
if (formRef.current) {
|
||||
const formValues = {
|
||||
name: updatedModel.name
|
||||
};
|
||||
|
||||
if (newMode === 'per-request') {
|
||||
formValues.priceInput = updatedModel.price || '';
|
||||
} else if (newMode === 'per-token') {
|
||||
formValues.ratioInput = updatedModel.ratio || '';
|
||||
formValues.completionRatioInput = updatedModel.completionRatio || '';
|
||||
formValues.modelTokenPrice = updatedModel.tokenPrice || '';
|
||||
formValues.completionTokenPrice = updatedModel.completionTokenPrice || '';
|
||||
}
|
||||
|
||||
formRef.current.setValues(formValues);
|
||||
}
|
||||
|
||||
// Update the model state
|
||||
setCurrentModel(updatedModel);
|
||||
}
|
||||
}}>
|
||||
<Radio value="per-token">{t('按量计费')}</Radio>
|
||||
<Radio value="per-request">{t('按次计费')}</Radio>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</Form.Section>
|
||||
|
||||
{pricingMode === 'per-token' && (
|
||||
<>
|
||||
<Form.Section text={t('价格设置方式')}>
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<RadioGroup type="button" value={pricingSubMode} onChange={(e) => {
|
||||
const newSubMode = e.target.value;
|
||||
const oldSubMode = pricingSubMode;
|
||||
setPricingSubMode(newSubMode);
|
||||
|
||||
// Handle conversion between submodes
|
||||
if (currentModel) {
|
||||
const updatedModel = { ...currentModel };
|
||||
|
||||
// Convert between ratio and token price
|
||||
if (oldSubMode === 'ratio' && newSubMode === 'token-price') {
|
||||
if (updatedModel.ratio) {
|
||||
updatedModel.tokenPrice = calculateTokenPriceFromRatio(parseFloat(updatedModel.ratio)).toString();
|
||||
|
||||
if (updatedModel.completionRatio) {
|
||||
updatedModel.completionTokenPrice = (parseFloat(updatedModel.tokenPrice) * parseFloat(updatedModel.completionRatio)).toString();
|
||||
}
|
||||
}
|
||||
} else if (oldSubMode === 'token-price' && newSubMode === 'ratio') {
|
||||
// Ratio values should already be calculated by the handlers
|
||||
}
|
||||
|
||||
// Update the form values
|
||||
if (formRef.current) {
|
||||
const formValues = {};
|
||||
|
||||
if (newSubMode === 'ratio') {
|
||||
formValues.ratioInput = updatedModel.ratio || '';
|
||||
formValues.completionRatioInput = updatedModel.completionRatio || '';
|
||||
} else if (newSubMode === 'token-price') {
|
||||
formValues.modelTokenPrice = updatedModel.tokenPrice || '';
|
||||
formValues.completionTokenPrice = updatedModel.completionTokenPrice || '';
|
||||
}
|
||||
|
||||
formRef.current.setValues(formValues);
|
||||
}
|
||||
|
||||
setCurrentModel(updatedModel);
|
||||
}
|
||||
}}>
|
||||
<Radio value="ratio">{t('按倍率设置')}</Radio>
|
||||
<Radio value="token-price">{t('按价格设置')}</Radio>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</Form.Section>
|
||||
|
||||
{pricingSubMode === 'ratio' && (
|
||||
<>
|
||||
<Form.Input
|
||||
field="ratioInput"
|
||||
label={t('模型倍率')}
|
||||
placeholder={t('输入模型倍率')}
|
||||
onChange={value => setCurrentModel(prev => ({
|
||||
...prev || {},
|
||||
ratio: value
|
||||
}))}
|
||||
initValue={currentModel?.ratio || ''}
|
||||
/>
|
||||
<Form.Input
|
||||
field="completionRatioInput"
|
||||
label={t('补全倍率')}
|
||||
placeholder={t('输入补全倍率')}
|
||||
onChange={value => setCurrentModel(prev => ({
|
||||
...prev || {},
|
||||
completionRatio: value
|
||||
}))}
|
||||
initValue={currentModel?.completionRatio || ''}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{pricingSubMode === 'token-price' && (
|
||||
<>
|
||||
<Form.Input
|
||||
field="modelTokenPrice"
|
||||
label={t('输入价格')}
|
||||
onChange={(value) => {
|
||||
handleTokenPriceChange(value);
|
||||
}}
|
||||
initValue={currentModel?.tokenPrice || ''}
|
||||
suffix={t('$/1M tokens')}
|
||||
/>
|
||||
<Form.Input
|
||||
field="completionTokenPrice"
|
||||
label={t('输出价格')}
|
||||
onChange={(value) => {
|
||||
handleCompletionTokenPriceChange(value);
|
||||
}}
|
||||
initValue={currentModel?.completionTokenPrice || ''}
|
||||
suffix={t('$/1M tokens')}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{pricingMode === 'per-request' && (
|
||||
<Form.Input
|
||||
field="price"
|
||||
field="priceInput"
|
||||
label={t('固定价格(每次)')}
|
||||
placeholder={t('输入每次价格')}
|
||||
onChange={value => setCurrentModel(prev => ({ ...prev, price: value }))}
|
||||
onChange={value => setCurrentModel(prev => ({
|
||||
...prev || {},
|
||||
price: value
|
||||
}))}
|
||||
initValue={currentModel?.price || ''}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user