|
|
@@ -0,0 +1,511 @@ |
|
|
|
|
|
# 扩展可视化倍率设置设计文档 |
|
|
|
|
|
|
|
|
|
|
|
> **创建日期**:2026-06-04 |
|
|
|
|
|
> **状态**:待实施 |
|
|
|
|
|
> **相关组件**:ModelSettingsVisualEditor.jsx |
|
|
|
|
|
|
|
|
|
|
|
## 需求概述 |
|
|
|
|
|
|
|
|
|
|
|
在现有的 `ModelSettingsVisualEditor` 组件中添加高级倍率字段支持,允许用户为每个模型单独设置缓存、图片、音频相关的倍率。 |
|
|
|
|
|
|
|
|
|
|
|
### 当前限制 |
|
|
|
|
|
|
|
|
|
|
|
现有的可视化倍率设置只能设置: |
|
|
|
|
|
- 模型倍率 |
|
|
|
|
|
- 补全倍率 |
|
|
|
|
|
|
|
|
|
|
|
### 目标 |
|
|
|
|
|
|
|
|
|
|
|
新增以下5个高级倍率字段: |
|
|
|
|
|
- 缓存读取倍率 |
|
|
|
|
|
- 缓存创建倍率 |
|
|
|
|
|
- 图片倍率 |
|
|
|
|
|
- 音频输入倍率 |
|
|
|
|
|
- 音频输出倍率 |
|
|
|
|
|
|
|
|
|
|
|
## 设计方案 |
|
|
|
|
|
|
|
|
|
|
|
### 界面设计 |
|
|
|
|
|
|
|
|
|
|
|
#### 主表格(保持不变) |
|
|
|
|
|
|
|
|
|
|
|
表格列结构保持现有的简洁布局: |
|
|
|
|
|
|
|
|
|
|
|
| 列名 | 说明 | |
|
|
|
|
|
|------|------| |
|
|
|
|
|
| 模型名称 | 显示模型名称,如有矛盾显示红色标签 | |
|
|
|
|
|
| 模型固定价格 | 按次计费的价格输入框 | |
|
|
|
|
|
| 模型倍率 | 按量计费的模型倍率 | |
|
|
|
|
|
| 补全倍率 | 按量计费的补全倍率 | |
|
|
|
|
|
| 操作 | 编辑、删除按钮 | |
|
|
|
|
|
|
|
|
|
|
|
#### 编辑弹窗(新增"高级比例"区域) |
|
|
|
|
|
|
|
|
|
|
|
在现有的表单结构中,在"定价模式"和"价格设置方式"区域之后,增加一个新的表单区域: |
|
|
|
|
|
|
|
|
|
|
|
```jsx |
|
|
|
|
|
{pricingMode === 'per-token' && ( |
|
|
|
|
|
<> |
|
|
|
|
|
{/* 现有的价格设置方式区域 */} |
|
|
|
|
|
<Form.Section text={t('价格设置方式')}> |
|
|
|
|
|
... |
|
|
|
|
|
</Form.Section> |
|
|
|
|
|
|
|
|
|
|
|
{/* 新增:高级比例区域 */} |
|
|
|
|
|
<Form.Section text={t('高级比例(留空使用默认值)')}> |
|
|
|
|
|
<Form.InputNumber |
|
|
|
|
|
field="cacheRatio" |
|
|
|
|
|
label={t('缓存读取倍率')} |
|
|
|
|
|
min={0} |
|
|
|
|
|
step={0.01} |
|
|
|
|
|
placeholder={t('默认值:1.0')} |
|
|
|
|
|
/> |
|
|
|
|
|
<Form.InputNumber |
|
|
|
|
|
field="cacheCreationRatio" |
|
|
|
|
|
label={t('缓存创建倍率(5分钟)')} |
|
|
|
|
|
min={0} |
|
|
|
|
|
step={0.01} |
|
|
|
|
|
placeholder={t('默认值:1.25')} |
|
|
|
|
|
/> |
|
|
|
|
|
<Form.InputNumber |
|
|
|
|
|
field="imageRatio" |
|
|
|
|
|
label={t('图片倍率')} |
|
|
|
|
|
min={0} |
|
|
|
|
|
step={0.01} |
|
|
|
|
|
placeholder={t('默认值:1.0')} |
|
|
|
|
|
/> |
|
|
|
|
|
<Form.InputNumber |
|
|
|
|
|
field="audioRatio" |
|
|
|
|
|
label={t('音频输入倍率')} |
|
|
|
|
|
min={0} |
|
|
|
|
|
step={0.01} |
|
|
|
|
|
placeholder={t('默认值:1.0')} |
|
|
|
|
|
/> |
|
|
|
|
|
<Form.InputNumber |
|
|
|
|
|
field="audioCompletionRatio" |
|
|
|
|
|
label={t('音频输出倍率')} |
|
|
|
|
|
min={0} |
|
|
|
|
|
step={0.01} |
|
|
|
|
|
placeholder={t('默认值:1.0')} |
|
|
|
|
|
/> |
|
|
|
|
|
</Form.Section> |
|
|
|
|
|
</> |
|
|
|
|
|
)} |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
**设计决策**: |
|
|
|
|
|
- 只在"按量计费"模式下显示高级比例字段 |
|
|
|
|
|
- 按次计费模式不需要这些倍率 |
|
|
|
|
|
- 使用 `Form.InputNumber` 确保输入的是有效数字 |
|
|
|
|
|
- `placeholder` 显示默认值提示 |
|
|
|
|
|
- `step={0.01}` 允许小数点后两位精度 |
|
|
|
|
|
|
|
|
|
|
|
### 数据结构 |
|
|
|
|
|
|
|
|
|
|
|
#### 内部状态扩展 |
|
|
|
|
|
|
|
|
|
|
|
在 `models` 状态数组中,每个模型对象增加5个字段: |
|
|
|
|
|
|
|
|
|
|
|
```javascript |
|
|
|
|
|
{ |
|
|
|
|
|
name: 'gpt-4', |
|
|
|
|
|
price: '', |
|
|
|
|
|
ratio: '', |
|
|
|
|
|
completionRatio: '', |
|
|
|
|
|
cacheRatio: '', // 新增 |
|
|
|
|
|
cacheCreationRatio: '', // 新增 |
|
|
|
|
|
imageRatio: '', // 新增 |
|
|
|
|
|
audioRatio: '', // 新增 |
|
|
|
|
|
audioCompletionRatio: '', // 新增 |
|
|
|
|
|
hasConflict: false |
|
|
|
|
|
} |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
#### 数据初始化 |
|
|
|
|
|
|
|
|
|
|
|
```javascript |
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
try { |
|
|
|
|
|
const modelPrice = JSON.parse(props.options.ModelPrice || '{}'); |
|
|
|
|
|
const modelRatio = JSON.parse(props.options.ModelRatio || '{}'); |
|
|
|
|
|
const completionRatio = JSON.parse(props.options.CompletionRatio || '{}'); |
|
|
|
|
|
// 新增:解析高级倍率字段 |
|
|
|
|
|
const cacheRatio = JSON.parse(props.options.CacheRatio || '{}'); |
|
|
|
|
|
const createCacheRatio = JSON.parse(props.options.CreateCacheRatio || '{}'); |
|
|
|
|
|
const imageRatio = JSON.parse(props.options.ImageRatio || '{}'); |
|
|
|
|
|
const audioRatio = JSON.parse(props.options.AudioRatio || '{}'); |
|
|
|
|
|
const audioCompletionRatio = JSON.parse(props.options.AudioCompletionRatio || '{}'); |
|
|
|
|
|
|
|
|
|
|
|
// 合并所有模型名称 |
|
|
|
|
|
const modelNames = new Set([ |
|
|
|
|
|
...Object.keys(modelPrice), |
|
|
|
|
|
...Object.keys(modelRatio), |
|
|
|
|
|
...Object.keys(completionRatio), |
|
|
|
|
|
...Object.keys(cacheRatio), |
|
|
|
|
|
...Object.keys(createCacheRatio), |
|
|
|
|
|
...Object.keys(imageRatio), |
|
|
|
|
|
...Object.keys(audioRatio), |
|
|
|
|
|
...Object.keys(audioCompletionRatio), |
|
|
|
|
|
]); |
|
|
|
|
|
|
|
|
|
|
|
const modelData = Array.from(modelNames).map((name) => { |
|
|
|
|
|
return { |
|
|
|
|
|
name, |
|
|
|
|
|
price: modelPrice[name] === undefined ? '' : modelPrice[name], |
|
|
|
|
|
ratio: modelRatio[name] === undefined ? '' : modelRatio[name], |
|
|
|
|
|
completionRatio: completionRatio[name] === undefined ? '' : completionRatio[name], |
|
|
|
|
|
cacheRatio: cacheRatio[name] === undefined ? '' : cacheRatio[name], |
|
|
|
|
|
cacheCreationRatio: createCacheRatio[name] === undefined ? '' : createCacheRatio[name], |
|
|
|
|
|
imageRatio: imageRatio[name] === undefined ? '' : imageRatio[name], |
|
|
|
|
|
audioRatio: audioRatio[name] === undefined ? '' : audioRatio[name], |
|
|
|
|
|
audioCompletionRatio: audioCompletionRatio[name] === undefined ? '' : audioCompletionRatio[name], |
|
|
|
|
|
hasConflict: modelPrice[name] !== '' && (modelRatio[name] !== '' || completionRatio[name] !== ''), |
|
|
|
|
|
}; |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
setModels(modelData); |
|
|
|
|
|
} catch (error) { |
|
|
|
|
|
console.error('JSON解析错误:', error); |
|
|
|
|
|
} |
|
|
|
|
|
}, [props.options]); |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
#### API 请求结构 |
|
|
|
|
|
|
|
|
|
|
|
保存时将5个新增字段序列化到对应的 JSON 字段: |
|
|
|
|
|
|
|
|
|
|
|
```javascript |
|
|
|
|
|
const SubmitData = async () => { |
|
|
|
|
|
setLoading(true); |
|
|
|
|
|
const output = { |
|
|
|
|
|
ModelPrice: {}, |
|
|
|
|
|
ModelRatio: {}, |
|
|
|
|
|
CompletionRatio: {}, |
|
|
|
|
|
CacheRatio: {}, // 新增 |
|
|
|
|
|
CreateCacheRatio: {}, // 新增 |
|
|
|
|
|
ImageRatio: {}, // 新增 |
|
|
|
|
|
AudioRatio: {}, // 新增 |
|
|
|
|
|
AudioCompletionRatio: {} // 新增 |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 新增:处理高级倍率字段 |
|
|
|
|
|
if (model.cacheRatio !== '') |
|
|
|
|
|
output.CacheRatio[model.name] = parseFloat(model.cacheRatio); |
|
|
|
|
|
if (model.cacheCreationRatio !== '') |
|
|
|
|
|
output.CreateCacheRatio[model.name] = parseFloat(model.cacheCreationRatio); |
|
|
|
|
|
if (model.imageRatio !== '') |
|
|
|
|
|
output.ImageRatio[model.name] = parseFloat(model.imageRatio); |
|
|
|
|
|
if (model.audioRatio !== '') |
|
|
|
|
|
output.AudioRatio[model.name] = parseFloat(model.audioRatio); |
|
|
|
|
|
if (model.audioCompletionRatio !== '') |
|
|
|
|
|
output.AudioCompletionRatio[model.name] = parseFloat(model.audioCompletionRatio); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 准备API请求数组 |
|
|
|
|
|
const finalOutput = { |
|
|
|
|
|
ModelPrice: JSON.stringify(output.ModelPrice, null, 2), |
|
|
|
|
|
ModelRatio: JSON.stringify(output.ModelRatio, null, 2), |
|
|
|
|
|
CompletionRatio: JSON.stringify(output.CompletionRatio, null, 2), |
|
|
|
|
|
CacheRatio: JSON.stringify(output.CacheRatio, null, 2), |
|
|
|
|
|
CreateCacheRatio: JSON.stringify(output.CreateCacheRatio, null, 2), |
|
|
|
|
|
ImageRatio: JSON.stringify(output.ImageRatio, null, 2), |
|
|
|
|
|
AudioRatio: JSON.stringify(output.AudioRatio, null, 2), |
|
|
|
|
|
AudioCompletionRatio: JSON.stringify(output.AudioCompletionRatio, null, 2), |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const requestQueue = Object.entries(finalOutput).map(([key, value]) => { |
|
|
|
|
|
return API.put('/api/option/', { key, value }); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 批量处理请求 |
|
|
|
|
|
const results = await Promise.all(requestQueue); |
|
|
|
|
|
|
|
|
|
|
|
// 验证结果(保持现有逻辑) |
|
|
|
|
|
// ... |
|
|
|
|
|
} catch (error) { |
|
|
|
|
|
// 错误处理 |
|
|
|
|
|
} finally { |
|
|
|
|
|
setLoading(false); |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
### 交互逻辑 |
|
|
|
|
|
|
|
|
|
|
|
#### 1. 编辑模型 |
|
|
|
|
|
|
|
|
|
|
|
编辑现有模型时,需要在弹窗中正确回显高级倍率字段的值: |
|
|
|
|
|
|
|
|
|
|
|
```javascript |
|
|
|
|
|
const editModel = (record) => { |
|
|
|
|
|
setIsEditMode(true); |
|
|
|
|
|
|
|
|
|
|
|
// 现有的定价模式设置逻辑 |
|
|
|
|
|
// ... |
|
|
|
|
|
|
|
|
|
|
|
// 创建模型副本 |
|
|
|
|
|
const modelCopy = { ...record }; |
|
|
|
|
|
|
|
|
|
|
|
// 如果有倍率数据,转换为 token price(现有逻辑) |
|
|
|
|
|
if (record.ratio) { |
|
|
|
|
|
modelCopy.tokenPrice = calculateTokenPriceFromRatio(parseFloat(record.ratio)).toString(); |
|
|
|
|
|
if (record.completionRatio) { |
|
|
|
|
|
modelCopy.completionTokenPrice = ( |
|
|
|
|
|
parseFloat(modelCopy.tokenPrice) * parseFloat(record.completionRatio) |
|
|
|
|
|
).toString(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 设置当前模型 |
|
|
|
|
|
setCurrentModel(modelCopy); |
|
|
|
|
|
setVisible(true); |
|
|
|
|
|
|
|
|
|
|
|
// 设置表单值 |
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
if (formRef.current) { |
|
|
|
|
|
formRef.current.setValues({ |
|
|
|
|
|
name: modelCopy.name, |
|
|
|
|
|
// 现有字段 |
|
|
|
|
|
ratioInput: modelCopy.ratio, |
|
|
|
|
|
completionRatioInput: modelCopy.completionRatio, |
|
|
|
|
|
modelTokenPrice: modelCopy.tokenPrice, |
|
|
|
|
|
completionTokenPrice: modelCopy.completionTokenPrice, |
|
|
|
|
|
// 新增:高级倍率字段 |
|
|
|
|
|
cacheRatio: modelCopy.cacheRatio, |
|
|
|
|
|
cacheCreationRatio: modelCopy.cacheCreationRatio, |
|
|
|
|
|
imageRatio: modelCopy.imageRatio, |
|
|
|
|
|
audioRatio: modelCopy.audioRatio, |
|
|
|
|
|
audioCompletionRatio: modelCopy.audioCompletionRatio, |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
}, 0); |
|
|
|
|
|
}; |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
#### 2. 添加/更新模型 |
|
|
|
|
|
|
|
|
|
|
|
在 `addOrUpdateModel` 函数中处理高级倍率字段: |
|
|
|
|
|
|
|
|
|
|
|
```javascript |
|
|
|
|
|
const addOrUpdateModel = (values) => { |
|
|
|
|
|
const existingModelIndex = models.findIndex( |
|
|
|
|
|
(model) => model.name === values.name, |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
const updatedModel = { |
|
|
|
|
|
name: values.name, |
|
|
|
|
|
price: values.price || '', |
|
|
|
|
|
ratio: values.ratio || '', |
|
|
|
|
|
completionRatio: values.completionRatio || '', |
|
|
|
|
|
// 新增:高级倍率字段 |
|
|
|
|
|
cacheRatio: values.cacheRatio || '', |
|
|
|
|
|
cacheCreationRatio: values.cacheCreationRatio || '', |
|
|
|
|
|
imageRatio: values.imageRatio || '', |
|
|
|
|
|
audioRatio: values.audioRatio || '', |
|
|
|
|
|
audioCompletionRatio: values.audioCompletionRatio || '', |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
updatedModel.hasConflict = |
|
|
|
|
|
updatedModel.price !== '' && |
|
|
|
|
|
(updatedModel.ratio !== '' || updatedModel.completionRatio !== ''); |
|
|
|
|
|
|
|
|
|
|
|
if (existingModelIndex >= 0) { |
|
|
|
|
|
// 更新现有模型 |
|
|
|
|
|
setModels((prev) => |
|
|
|
|
|
prev.map((model, index) => |
|
|
|
|
|
index !== existingModelIndex ? model : updatedModel |
|
|
|
|
|
) |
|
|
|
|
|
); |
|
|
|
|
|
setVisible(false); |
|
|
|
|
|
showSuccess(t('更新成功')); |
|
|
|
|
|
} else { |
|
|
|
|
|
// 添加新模型 |
|
|
|
|
|
if (models.some((model) => model.name === values.name)) { |
|
|
|
|
|
showError(t('模型名称已存在')); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
setModels((prev) => [updatedModel, ...prev]); |
|
|
|
|
|
setVisible(false); |
|
|
|
|
|
showSuccess(t('添加成功')); |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
#### 3. 输入验证 |
|
|
|
|
|
|
|
|
|
|
|
在 `updateModel` 函数中增加对高级倍率字段的验证: |
|
|
|
|
|
|
|
|
|
|
|
```javascript |
|
|
|
|
|
const updateModel = (name, field, value) => { |
|
|
|
|
|
if (isNaN(value) && value !== '') { // 允许空字符串 |
|
|
|
|
|
showError(t('请输入数字')); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
setModels((prev) => |
|
|
|
|
|
prev.map((model) => { |
|
|
|
|
|
if (model.name !== name) return model; |
|
|
|
|
|
const updated = { ...model, [field]: value }; |
|
|
|
|
|
updated.hasConflict = |
|
|
|
|
|
updated.price !== '' && |
|
|
|
|
|
(updated.ratio !== '' || updated.completionRatio !== ''); |
|
|
|
|
|
return updated; |
|
|
|
|
|
}), |
|
|
|
|
|
); |
|
|
|
|
|
}; |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
### 默认值处理策略 |
|
|
|
|
|
|
|
|
|
|
|
**前端处理默认值**:用户输入框留空时,保存为空字符串,后端使用全局默认值。 |
|
|
|
|
|
|
|
|
|
|
|
**默认值映射**: |
|
|
|
|
|
- 缓存读取倍率:1.0 |
|
|
|
|
|
- 缓存创建倍率:1.25 |
|
|
|
|
|
- 图片倍率:1.0 |
|
|
|
|
|
- 音频输入倍率:1.0 |
|
|
|
|
|
- 音频输出倍率:1.0 |
|
|
|
|
|
|
|
|
|
|
|
**实现方式**: |
|
|
|
|
|
- 输入框使用 `placeholder` 提示默认值 |
|
|
|
|
|
- 提交时空字符串保持为空,不转换 |
|
|
|
|
|
- 后端处理空值时返回对应的默认值 |
|
|
|
|
|
|
|
|
|
|
|
### 模型能力处理 |
|
|
|
|
|
|
|
|
|
|
|
**策略**:全部显示,用户自行判断 |
|
|
|
|
|
|
|
|
|
|
|
- 所有5个高级倍率字段始终显示在编辑弹窗中 |
|
|
|
|
|
- 用户根据自己使用的模型判断是否填写相应字段 |
|
|
|
|
|
- 不在前端进行模型能力验证或字段隐藏 |
|
|
|
|
|
|
|
|
|
|
|
**设计理由**: |
|
|
|
|
|
- 简化实现,避免维护复杂的模型能力映射 |
|
|
|
|
|
- 给用户更多控制权,允许为任意模型设置任意倍率 |
|
|
|
|
|
- 与渠道价格设置的实现方式保持一致 |
|
|
|
|
|
|
|
|
|
|
|
## 实现要点 |
|
|
|
|
|
|
|
|
|
|
|
### 1. 字段映射 |
|
|
|
|
|
|
|
|
|
|
|
确保前端字段名与后端 `options` 表中的 key 一致: |
|
|
|
|
|
|
|
|
|
|
|
| 前端字段名 | 后端 Option Key | 默认值 | |
|
|
|
|
|
|-----------|----------------|--------| |
|
|
|
|
|
| cacheRatio | CacheRatio | 1.0 | |
|
|
|
|
|
| cacheCreationRatio | CreateCacheRatio | 1.25 | |
|
|
|
|
|
| imageRatio | ImageRatio | 1.0 | |
|
|
|
|
|
| audioRatio | AudioRatio | 1.0 | |
|
|
|
|
|
| audioCompletionRatio | AudioCompletionRatio | 1.0 | |
|
|
|
|
|
|
|
|
|
|
|
### 2. 数值验证 |
|
|
|
|
|
|
|
|
|
|
|
- 输入时验证为有效数字:`if (isNaN(value) && value !== '')` |
|
|
|
|
|
- 范围验证:`min={0}` 确保非负数 |
|
|
|
|
|
- 小数精度:`step={0.01}` 允许两位小数 |
|
|
|
|
|
|
|
|
|
|
|
### 3. 空值处理 |
|
|
|
|
|
|
|
|
|
|
|
- 空字符串视为未设置,保存时保持为空字符串 |
|
|
|
|
|
- 后端处理时如果值为空,返回对应的全局默认值 |
|
|
|
|
|
- 前端解析 JSON 时,如果字段不存在,默认为空字符串:`cacheRatio[name] === undefined ? '' : cacheRatio[name]` |
|
|
|
|
|
|
|
|
|
|
|
### 4. 样式统一 |
|
|
|
|
|
|
|
|
|
|
|
- 使用与现有字段一致的 `Form.InputNumber` 组件 |
|
|
|
|
|
- 保持相同的 `label`、`placeholder` 样式 |
|
|
|
|
|
- 表单区域使用 `Form.Section` 包裹,标题为"高级比例(留空使用默认值)" |
|
|
|
|
|
|
|
|
|
|
|
### 5. 国际化支持 |
|
|
|
|
|
|
|
|
|
|
|
需要为新增的文本添加翻译: |
|
|
|
|
|
|
|
|
|
|
|
**中文 (zh-CN.json)**: |
|
|
|
|
|
```json |
|
|
|
|
|
{ |
|
|
|
|
|
"高级比例(留空使用默认值)": "高级比例(留空使用默认值)", |
|
|
|
|
|
"缓存读取倍率": "缓存读取倍率", |
|
|
|
|
|
"缓存创建倍率(5分钟)": "缓存创建倍率(5分钟)", |
|
|
|
|
|
"图片倍率": "图片倍率", |
|
|
|
|
|
"音频输入倍率": "音频输入倍率", |
|
|
|
|
|
"音频输出倍率": "音频输出倍率", |
|
|
|
|
|
"默认值:1.0": "默认值:1.0", |
|
|
|
|
|
"默认值:1.25": "默认值:1.25" |
|
|
|
|
|
} |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
**英文 (en.json)**: |
|
|
|
|
|
```json |
|
|
|
|
|
{ |
|
|
|
|
|
"高级比例(留空使用默认值)": "Advanced Ratios (Leave empty for defaults)", |
|
|
|
|
|
"缓存读取倍率": "Cache Read Ratio", |
|
|
|
|
|
"缓存创建倍率(5分钟)": "Cache Creation Ratio (5min)", |
|
|
|
|
|
"图片倍率": "Image Ratio", |
|
|
|
|
|
"音频输入倍率": "Audio Input Ratio", |
|
|
|
|
|
"音频输出倍率": "Audio Output Ratio", |
|
|
|
|
|
"默认值:1.0": "Default: 1.0", |
|
|
|
|
|
"默认值:1.25": "Default: 1.25" |
|
|
|
|
|
} |
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
## 测试要点 |
|
|
|
|
|
|
|
|
|
|
|
### 1. 数据初始化测试 |
|
|
|
|
|
|
|
|
|
|
|
- 测试从空 options 初始化,确保所有字段为空字符串 |
|
|
|
|
|
- 测试从已有数据初始化,确保正确解析所有倍率值 |
|
|
|
|
|
|
|
|
|
|
|
### 2. 编辑功能测试 |
|
|
|
|
|
|
|
|
|
|
|
- 测试编辑现有模型,确保高级倍率字段正确回显 |
|
|
|
|
|
- 测试添加新模型,确保所有字段可以正常输入 |
|
|
|
|
|
- 测试删除模型,确保相关数据正确清理 |
|
|
|
|
|
|
|
|
|
|
|
### 3. 保存功能测试 |
|
|
|
|
|
|
|
|
|
|
|
- 测试只填写部分高级倍率字段,确保正确保存 |
|
|
|
|
|
- 测试所有高级倍率字段都留空,确保保存为空字符串 |
|
|
|
|
|
- 测试填写数值超出范围,确保验证生效 |
|
|
|
|
|
|
|
|
|
|
|
### 4. 默认值测试 |
|
|
|
|
|
|
|
|
|
|
|
- 测试留空字段,确保后端使用默认值 |
|
|
|
|
|
- 测试输入 0,确保保存为数值 0 而不是默认值 |
|
|
|
|
|
|
|
|
|
|
|
### 5. 边界情况测试 |
|
|
|
|
|
|
|
|
|
|
|
- 测试输入负数,确保被拒绝(min={0}) |
|
|
|
|
|
- 测试输入非数字字符,确保显示错误提示 |
|
|
|
|
|
- 测试输入非常多小数位,确保 step={0.01} 生效 |
|
|
|
|
|
|
|
|
|
|
|
## 参考实现 |
|
|
|
|
|
|
|
|
|
|
|
本设计参考了 `feat/channel-pricing-extended` 分支中的 `ChannelPricingView.jsx` 实现,特别是: |
|
|
|
|
|
|
|
|
|
|
|
1. **表单结构**:使用 `Form.Section` 包裹高级比例字段 |
|
|
|
|
|
2. **字段命名**:使用与后端 Option Key 一致的字段名 |
|
|
|
|
|
3. **默认值处理**:使用 placeholder 提示默认值,空字符串表示使用默认值 |
|
|
|
|
|
4. **输入验证**:使用 `Form.InputNumber` 配合 `min` 和 `step` 属性 |
|
|
|
|
|
|
|
|
|
|
|
## 实施检查清单 |
|
|
|
|
|
|
|
|
|
|
|
- [ ] 修改 `ModelSettingsVisualEditor.jsx`,增加高级比例表单区域 |
|
|
|
|
|
- [ ] 更新数据初始化逻辑,解析新增的5个 JSON 字段 |
|
|
|
|
|
- [ ] 更新保存逻辑,序列化并提交新增的5个字段 |
|
|
|
|
|
- [ ] 更新编辑模型逻辑,正确回显高级倍率字段 |
|
|
|
|
|
- [ ] 更新添加/更新模型逻辑,处理高级倍率字段 |
|
|
|
|
|
- [ ] 添加输入验证,确保输入有效数字 |
|
|
|
|
|
- [ ] 添加国际化文本(中英文) |
|
|
|
|
|
- [ ] 测试数据初始化、编辑、保存功能 |
|
|
|
|
|
- [ ] 测试边界情况和默认值处理 |