diff --git a/web/src/components/settings/OperationSetting.jsx b/web/src/components/settings/OperationSetting.jsx index ef0e427..a836c55 100644 --- a/web/src/components/settings/OperationSetting.jsx +++ b/web/src/components/settings/OperationSetting.jsx @@ -30,6 +30,7 @@ import SettingsCreditLimit from '../../pages/Setting/Operation/SettingsCreditLim import SettingsCheckin from '../../pages/Setting/Operation/SettingsCheckin'; import SettingsRegionSync from '../../pages/Setting/Operation/SettingsRegionSync'; import SettingsPlayground from '../../pages/Setting/Operation/SettingsPlayground'; +import ModelPricing from '../../pages/Setting/Operation/ModelPricing'; import { API, showError, toBoolean } from '../../helpers'; const OperationSetting = () => { @@ -189,6 +190,7 @@ const OperationSetting = () => { + ); diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 28a8fe5..9cbda58 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -3112,14 +3112,26 @@ ",点击更新": ", click Update", ",音频 {{symbol}}{{price}} / 1M tokens": ", Audio {{symbol}}{{price}} / 1M tokens", "???????????????": "Advanced ratios (leave empty to use global defaults)", - "??????": "Audio output ratio", + "??????": "Apply generated rows", "??????1.0?": "Global default (1.0)", "?????????????0.5 = ????? token ???????????????????? 1.0": "Multiplier applied to input pricing when cache is hit. 0.5 means cached tokens are billed at half the input price. Leave empty to use the global default 1.0.", "???????5 ???": "Cache creation ratio (5 minutes)", "??????1.25?": "Global default (1.25)", "????? token ???? ? ????????? 1.25?5 ?? TTL???? 1 ????? 1.6x ????????????": "Tokens written into cache are billed as this ratio ? input price. Default is 1.25 for a 5-minute TTL; cache creation over 1 hour is billed automatically at 1.6x. Leave empty to use the global default.", - "????": "Image ratio", - "?????": "Global default" + "????": "Final price", + "?????": "Preserve manual rows", + "??????????": "Failed to save model multidimensional pricing", + "?????????": "Model multidimensional pricing saved", + "???": "Default value", + "Legacy ??": "Legacy price", + "??????? Legacy ???{{price}}": "Deleting this rule falls back to Legacy price: {{price}}", + "????????": "Add model multidimensional pricing", + "??? tokens": "Per 1M tokens", + "??": "Refresh", + "?? JSON": "Apply JSON", + "??? {{count}} ?????{{sample}}": "Will generate {{count}} rows. Sample: {{sample}}", + "JSON ????": "JSON format error", + "Key": "Key" }, "邮箱后缀": "Email Suffix", "邮箱后缀必须以 @ 开头": "Email suffix must start with @", diff --git a/web/src/i18n/locales/zh-CN.json b/web/src/i18n/locales/zh-CN.json index 5938369..058d04b 100644 --- a/web/src/i18n/locales/zh-CN.json +++ b/web/src/i18n/locales/zh-CN.json @@ -3087,7 +3087,22 @@ ",当前无生效订阅,将自动使用钱包": ",当前无生效订阅,将自动使用钱包", ",时间:": ",时间:", ",点击更新": ",点击更新", - ",音频 {{symbol}}{{price}} / 1M tokens": ",音频 {{symbol}}{{price}} / 1M tokens" + ",音频 {{symbol}}{{price}} / 1M tokens": ",音频 {{symbol}}{{price}} / 1M tokens", + "??????????": "??????????", + "?????????": "?????????", + "??????": "??????", + "???": "???", + "Legacy ??": "Legacy ??", + "????": "????", + "??????? Legacy ???{{price}}": "??????? Legacy ???{{price}}", + "????????": "????????", + "??? tokens": "??? tokens", + "??": "??", + "?????": "?????", + "?? JSON": "?? JSON", + "??? {{count}} ?????{{sample}}": "??? {{count}} ?????{{sample}}", + "JSON ????": "JSON ????", + "Key": "Key" }, "邮箱后缀": "邮箱后缀", "邮箱后缀必须以 @ 开头": "邮箱后缀必须以 @ 开头", diff --git a/web/src/pages/Setting/Operation/ModelPricing/DimensionEditor.jsx b/web/src/pages/Setting/Operation/ModelPricing/DimensionEditor.jsx new file mode 100644 index 0000000..9e863a5 --- /dev/null +++ b/web/src/pages/Setting/Operation/ModelPricing/DimensionEditor.jsx @@ -0,0 +1,91 @@ +import React from 'react'; +import { Button, Checkbox, Input, Select, Space, Table } from '@douyinfe/semi-ui'; +import { Plus, Trash2 } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; +import { parseDimensionValue } from './pricingConfig'; + +const SOURCE_OPTIONS = ['request.resolution', 'request.duration', 'request.options.size', 'derived.input_mode', 'derived.megapixels', 'derived.aspect_ratio']; + +export default function DimensionEditor({ dimensions, onChange }) { + const { t } = useTranslation(); + + const update = (index, patch) => { + const next = [...dimensions]; + next[index] = { ...next[index], ...patch }; + onChange(next); + }; + + const remove = (index) => onChange(dimensions.filter((_, itemIndex) => itemIndex !== index)); + const add = () => onChange([...dimensions, { key: 'duration', source: 'request.duration', type: 'number', default: null }]); + + const columns = [ + { title: t('Key'), dataIndex: 'key', render: (value, _, index) => update(index, { key })} /> }, + { + title: t('来源'), + dataIndex: 'source', + render: (value, _, index) => ( + + ), + }, + { + title: t('类型'), + dataIndex: 'type', + width: 130, + render: (value, _, index) => ( + + ), + }, + { + title: t('可选'), + dataIndex: 'optional', + width: 80, + render: (value, row, index) => ( + update(index, { optional: event.target.checked, default: event.target.checked ? defaultForType(row.type) : null })} + /> + ), + }, + { + title: t('默认值'), + dataIndex: 'default', + render: (value, row, index) => ( + update(index, { default: text === '' ? null : parseDimensionValue(text, row.type) })} + /> + ), + }, + { + title: t('操作'), + width: 80, + render: (_, __, index) =>