Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions web/classic/src/pages/Setting/Ratio/ModelPricingCombined.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ For commercial licensing, please contact support@quantumnous.com
*/

import React, { useState } from 'react';
import { Radio, RadioGroup } from '@douyinfe/semi-ui';
import { Banner, Radio, RadioGroup } from '@douyinfe/semi-ui';
import { useTranslation } from 'react-i18next';
import ModelPricingEditor from './components/ModelPricingEditor';
import ModelRatioSettings from './ModelRatioSettings';
Expand All @@ -37,13 +37,25 @@ export default function ModelPricingCombined({ options, refresh }) {
onChange={(e) => setEditMode(e.target.value)}
>
<Radio value='visual'>{t('可视化编辑')}</Radio>
<Radio value='manual'>{t('手动编辑')}</Radio>
<Radio value='manual'>{t('倍率(高级)')}</Radio>
</RadioGroup>
</div>
{editMode === 'visual' ? (
<ModelPricingEditor options={options} refresh={refresh} />
) : (
<ModelRatioSettings options={options} refresh={refresh} />
<>
<Banner
type='warning'
bordered
fullMode={false}
closeIcon={null}
style={{ marginBottom: 16 }}
description={t(
'高级模式:此处编辑的是后端原始倍率(无量纲系数)与美元按次价(ModelPrice),与上方「可视化编辑」的人民币口径不同,请勿混用。不熟悉倍率的话建议使用可视化编辑。',
)}
/>
<ModelRatioSettings options={options} refresh={refresh} />
</>
)}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/

import React, { useCallback, useMemo, useState } from 'react';
import React, { useCallback, useContext, useMemo, useState } from 'react';
import {
Banner,
Button,
Expand Down Expand Up @@ -45,15 +45,20 @@ import {
PAGE_SIZE,
PRICE_SUFFIX,
buildSummaryText,
formatDisplayPrice,
hasValue,
normalizeRate,
useModelPricingEditorState,
} from '../hooks/useModelPricingEditorState';
import { useIsMobile } from '../../../../hooks/common/useIsMobile';
import { StatusContext } from '../../../../context/Status';
import TieredPricingEditor from './TieredPricingEditor';

const { Text } = Typography;
const EMPTY_CANDIDATE_MODEL_NAMES = [];

// 显示固定 2 位(¥4.00),编辑时才展开原始精度串;失焦不回写圆整值,
// 故 model state 始终保持高精度,序列化不受 2 位显示影响(见设计 §6.1.1)。
const PriceInput = ({
label,
value,
Expand All @@ -64,26 +69,40 @@ const PriceInput = ({
extraText = '',
headerAction = null,
hidden = false,
}) => (
<div style={{ marginBottom: 16 }}>
<div className='mb-1 font-medium text-gray-700 flex items-center justify-between gap-3'>
<span>{label}</span>
{headerAction}
}) => {
const [focused, setFocused] = useState(false);
const [draft, setDraft] = useState('');
const displayValue = focused ? draft : formatDisplayPrice(value);
return (
<div style={{ marginBottom: 16 }}>
<div className='mb-1 font-medium text-gray-700 flex items-center justify-between gap-3'>
<span>{label}</span>
{headerAction}
</div>
{!hidden ? (
<Input
value={displayValue}
placeholder={placeholder}
onFocus={() => {
// 编辑起点用原始高精度串,避免廉价模型(如 ¥0.0146)被 2 位显示圆整后丢精度。
setDraft(value ?? '');
setFocused(true);
}}
onBlur={() => setFocused(false)}
onChange={(v) => {
setDraft(v);
onChange(v);
}}
suffix={suffix}
disabled={disabled}
/>
) : null}
{extraText ? (
<div className='mt-1 text-xs text-gray-500'>{extraText}</div>
) : null}
</div>
{!hidden ? (
<Input
value={value}
placeholder={placeholder}
onChange={onChange}
suffix={suffix}
disabled={disabled}
/>
) : null}
{extraText ? (
<div className='mt-1 text-xs text-gray-500'>{extraText}</div>
) : null}
</div>
);
);
};

export default function ModelPricingEditor({
options,
Expand All @@ -99,6 +118,12 @@ export default function ModelPricingEditor({
}) {
const { t } = useTranslation();
const isMobile = useIsMobile();
const [statusState] = useContext(StatusContext);
// 优先用页面级、随保存刷新的 options.USDExchangeRate;StatusContext 仅 app 启动加载一次,
// 改汇率后同会话不刷新会折算错(写错计费倍率),故仅作兜底。
const rate = normalizeRate(
options?.USDExchangeRate ?? statusState?.status?.usd_exchange_rate,
);
const [addVisible, setAddVisible] = useState(false);
const [batchVisible, setBatchVisible] = useState(false);
const [newModelName, setNewModelName] = useState('');
Expand Down Expand Up @@ -136,6 +161,7 @@ export default function ModelPricingEditor({
t,
candidateModelNames,
filterMode,
rate,
});

const getExprModeLabel = useCallback((model) => {
Expand Down Expand Up @@ -253,6 +279,17 @@ export default function ModelPricingEditor({
return (
<>
<Space vertical align='start' style={{ width: '100%' }}>
<Banner
type='info'
bordered
fullMode={false}
closeIcon={null}
style={{ width: '100%' }}
description={t(
'价格单位为人民币(¥),按运营设置的「美元兑人民币汇率」{{rate}} 自动折算为后端倍率存储;价格显示保留 2 位小数,存储仍为高精度,计费不受影响。(注:表达式/阶梯计费仍按美元 $ 计价,不受此折算影响。)',
{ rate },
)}
/>
<Space wrap className='mt-2'>
{allowAddModel ? (
<Button
Expand Down Expand Up @@ -445,7 +482,7 @@ export default function ModelPricingEditor({
label={t('固定价格')}
value={selectedModel.fixedPrice}
placeholder={t('输入每次调用价格')}
suffix={t('$/次')}
suffix={t('¥/次')}
onChange={(value) => handleNumericFieldChange('fixedPrice', value)}
extraText={t('适合 MJ / 任务类等按次收费模型。')}
/>
Expand All @@ -470,7 +507,7 @@ export default function ModelPricingEditor({
<PriceInput
label={t('输入价格')}
value={selectedModel.inputPrice}
placeholder={t('输入 $/1M tokens')}
placeholder={t('输入 ¥/1M tokens')}
onChange={(value) => handleNumericFieldChange('inputPrice', value)}
/>
{selectedModel.completionRatioLocked ? (
Expand All @@ -492,7 +529,7 @@ export default function ModelPricingEditor({
<PriceInput
label={t('补全价格')}
value={selectedModel.completionPrice}
placeholder={t('输入 $/1M tokens')}
placeholder={t('输入 ¥/1M tokens')}
onChange={(value) =>
handleNumericFieldChange('completionPrice', value)
}
Expand Down Expand Up @@ -535,7 +572,7 @@ export default function ModelPricingEditor({
<PriceInput
label={t('缓存读取价格')}
value={selectedModel.cachePrice}
placeholder={t('输入 $/1M tokens')}
placeholder={t('输入 ¥/1M tokens')}
onChange={(value) => handleNumericFieldChange('cachePrice', value)}
headerAction={
<Switch
Expand All @@ -557,7 +594,7 @@ export default function ModelPricingEditor({
<PriceInput
label={t('缓存创建价格')}
value={selectedModel.createCachePrice}
placeholder={t('输入 $/1M tokens')}
placeholder={t('输入 ¥/1M tokens')}
onChange={(value) =>
handleNumericFieldChange('createCachePrice', value)
}
Expand Down Expand Up @@ -604,7 +641,7 @@ export default function ModelPricingEditor({
<PriceInput
label={t('图片输入价格')}
value={selectedModel.imagePrice}
placeholder={t('输入 $/1M tokens')}
placeholder={t('输入 ¥/1M tokens')}
onChange={(value) => handleNumericFieldChange('imagePrice', value)}
headerAction={
<Switch
Expand All @@ -626,7 +663,7 @@ export default function ModelPricingEditor({
<PriceInput
label={t('音频输入价格')}
value={selectedModel.audioInputPrice}
placeholder={t('输入 $/1M tokens')}
placeholder={t('输入 ¥/1M tokens')}
onChange={(value) =>
handleNumericFieldChange('audioInputPrice', value)
}
Expand Down Expand Up @@ -656,7 +693,7 @@ export default function ModelPricingEditor({
<PriceInput
label={t('音频补全价格')}
value={selectedModel.audioOutputPrice}
placeholder={t('输入 $/1M tokens')}
placeholder={t('输入 ¥/1M tokens')}
onChange={(value) =>
handleNumericFieldChange('audioOutputPrice', value)
}
Expand Down
Loading