+
+ {t('视频分辨率倍率')}
+
+ handleOptionalFieldToggle(
+ 'videoResolutionRatio',
+ checked,
+ )
+ }
+ />
+
+ {isOptionalFieldEnabled(
+ selectedModel,
+ 'videoResolutionRatio',
+ ) ? (
+
+ handleJsonFieldChange('videoResolutionRatio', value)
+ }
+ />
+ ) : null}
+
+ {!isOptionalFieldEnabled(
+ selectedModel,
+ 'videoResolutionRatio',
+ )
+ ? t('当前未启用,需要时再打开即可。')
+ : t(
+ '不同视频分辨率的倍率设置。格式如 {"480P": 1, "720P": 2}。',
+ )}
+
+
>
)}
diff --git a/web/classic/src/pages/Setting/Ratio/hooks/useModelPricingEditorState.js b/web/classic/src/pages/Setting/Ratio/hooks/useModelPricingEditorState.js
index ba9efdfcebc0..1814704f5aa6 100644
--- a/web/classic/src/pages/Setting/Ratio/hooks/useModelPricingEditorState.js
+++ b/web/classic/src/pages/Setting/Ratio/hooks/useModelPricingEditorState.js
@@ -40,6 +40,8 @@ const EMPTY_MODEL = {
imagePrice: '',
audioInputPrice: '',
audioOutputPrice: '',
+ voiceCloneUnlockPrice: '',
+ videoResolutionRatio: '',
billingExpr: '',
requestRuleExpr: '',
rawRatios: {
@@ -50,6 +52,8 @@ const EMPTY_MODEL = {
imageRatio: '',
audioRatio: '',
audioCompletionRatio: '',
+ voiceCloneUnlockRatio: '',
+ videoResolutionRatio: '',
},
hasConflict: false,
};
@@ -150,6 +154,15 @@ const buildModelState = (name, sourceMaps) => {
const audioCompletionRatio = toNumericString(
sourceMaps.AudioCompletionRatio[name],
);
+ const voiceCloneUnlockRatio = toNumericString(
+ sourceMaps.VoiceCloneUnlockRatio[name],
+ );
+ const videoResolutionRatioRaw = sourceMaps.VideoResolutionRatio[name];
+ const videoResolutionRatio = videoResolutionRatioRaw !== undefined
+ ? (typeof videoResolutionRatioRaw === 'object'
+ ? JSON.stringify(videoResolutionRatioRaw, null, 2)
+ : String(videoResolutionRatioRaw))
+ : '';
const fixedPrice = toNumericString(sourceMaps.ModelPrice[name]);
const inputPrice = ratioToBasePrice(modelRatio);
const inputPriceNumber = toNumberOrNull(inputPrice);
@@ -199,6 +212,8 @@ const buildModelState = (name, sourceMaps) => {
toNumberOrNull(audioInputPrice) !== null && hasValue(audioCompletionRatio)
? formatNumber(Number(audioInputPrice) * Number(audioCompletionRatio))
: '',
+ voiceCloneUnlockPrice: voiceCloneUnlockRatio,
+ videoResolutionRatio,
requestRuleExpr: '',
rawRatios: {
modelRatio,
@@ -208,6 +223,8 @@ const buildModelState = (name, sourceMaps) => {
imageRatio,
audioRatio,
audioCompletionRatio,
+ voiceCloneUnlockRatio,
+ videoResolutionRatio,
},
hasConflict:
hasValue(fixedPrice) &&
@@ -219,6 +236,8 @@ const buildModelState = (name, sourceMaps) => {
imageRatio,
audioRatio,
audioCompletionRatio,
+ voiceCloneUnlockRatio,
+ videoResolutionRatio,
].some(hasValue),
};
};
@@ -243,6 +262,8 @@ export const getModelWarnings = (model, t) => {
model.imagePrice,
model.audioInputPrice,
model.audioOutputPrice,
+ model.voiceCloneUnlockPrice,
+ model.videoResolutionRatio,
].some(hasValue);
if (model.hasConflict) {
@@ -260,6 +281,8 @@ export const getModelWarnings = (model, t) => {
model.rawRatios.imageRatio,
model.rawRatios.audioRatio,
model.rawRatios.audioCompletionRatio,
+ model.rawRatios.voiceCloneUnlockRatio,
+ model.rawRatios.videoResolutionRatio,
].some(hasValue)
) {
warnings.push(
@@ -315,6 +338,8 @@ export const buildSummaryText = (model, t) => {
model.imagePrice,
model.audioInputPrice,
model.audioOutputPrice,
+ model.voiceCloneUnlockPrice,
+ model.videoResolutionRatio,
].filter(hasValue).length;
const extraLabel =
extraCount > 0 ? `,${t('额外价格项')} ${extraCount}` : '';
@@ -332,6 +357,8 @@ export const buildOptionalFieldToggles = (model) => ({
imagePrice: hasValue(model.imagePrice),
audioInputPrice: hasValue(model.audioInputPrice),
audioOutputPrice: hasValue(model.audioOutputPrice),
+ voiceCloneUnlockPrice: hasValue(model.voiceCloneUnlockPrice),
+ videoResolutionRatio: hasValue(model.videoResolutionRatio),
});
const serializeModel = (model, t) => {
@@ -344,6 +371,8 @@ const serializeModel = (model, t) => {
ImageRatio: null,
AudioRatio: null,
AudioCompletionRatio: null,
+ VoiceCloneUnlockRatio: null,
+ VideoResolutionRatio: null,
};
if (model.billingMode === 'per-request') {
@@ -360,6 +389,7 @@ const serializeModel = (model, t) => {
const imagePrice = toNumberOrNull(model.imagePrice);
const audioInputPrice = toNumberOrNull(model.audioInputPrice);
const audioOutputPrice = toNumberOrNull(model.audioOutputPrice);
+ const voiceCloneUnlockPrice = toNumberOrNull(model.voiceCloneUnlockPrice);
const hasDependentPrice = [
completionPrice,
@@ -409,6 +439,28 @@ const serializeModel = (model, t) => {
model.rawRatios.audioCompletionRatio,
);
}
+ if (hasValue(model.rawRatios.voiceCloneUnlockRatio)) {
+ result.VoiceCloneUnlockRatio = toNormalizedNumber(
+ model.rawRatios.voiceCloneUnlockRatio,
+ );
+ } else if (voiceCloneUnlockPrice !== null) {
+ result.VoiceCloneUnlockRatio = toNormalizedNumber(voiceCloneUnlockPrice);
+ }
+ if (hasValue(model.rawRatios.videoResolutionRatio)) {
+ try {
+ result.VideoResolutionRatio = JSON.parse(
+ model.rawRatios.videoResolutionRatio,
+ );
+ } catch (e) {
+ // skip invalid JSON for video resolution ratio
+ }
+ } else if (hasValue(model.videoResolutionRatio)) {
+ try {
+ result.VideoResolutionRatio = JSON.parse(model.videoResolutionRatio);
+ } catch (e) {
+ // skip invalid JSON for video resolution ratio
+ }
+ }
return result;
}
@@ -448,6 +500,16 @@ const serializeModel = (model, t) => {
audioOutputPrice / audioInputPrice,
);
}
+ if (voiceCloneUnlockPrice !== null) {
+ result.VoiceCloneUnlockRatio = toNormalizedNumber(voiceCloneUnlockPrice);
+ }
+ if (hasValue(model.videoResolutionRatio)) {
+ try {
+ result.VideoResolutionRatio = JSON.parse(model.videoResolutionRatio);
+ } catch (e) {
+ // skip invalid JSON for video resolution ratio
+ }
+ }
return result;
};
@@ -550,6 +612,20 @@ export const buildPreviewRows = (model, t) => {
? model.rawRatios.audioCompletionRatio
: t('空'),
},
+ {
+ key: 'VoiceCloneUnlockRatio',
+ label: 'VoiceCloneUnlockRatio',
+ value: hasValue(model.rawRatios.voiceCloneUnlockRatio)
+ ? model.rawRatios.voiceCloneUnlockRatio
+ : t('空'),
+ },
+ {
+ key: 'VideoResolutionRatio',
+ label: 'VideoResolutionRatio',
+ value: hasValue(model.rawRatios.videoResolutionRatio)
+ ? model.rawRatios.videoResolutionRatio
+ : t('空'),
+ },
];
return rows;
}
@@ -560,6 +636,7 @@ export const buildPreviewRows = (model, t) => {
const imagePrice = toNumberOrNull(model.imagePrice);
const audioInputPrice = toNumberOrNull(model.audioInputPrice);
const audioOutputPrice = toNumberOrNull(model.audioOutputPrice);
+ const voiceCloneUnlockPrice = toNumberOrNull(model.voiceCloneUnlockPrice);
const rows = [
{
@@ -614,6 +691,21 @@ export const buildPreviewRows = (model, t) => {
? formatNumber(audioOutputPrice / audioInputPrice)
: t('空'),
},
+ {
+ key: 'VoiceCloneUnlockRatio',
+ label: 'VoiceCloneUnlockRatio',
+ value:
+ voiceCloneUnlockPrice !== null
+ ? formatNumber(voiceCloneUnlockPrice)
+ : t('空'),
+ },
+ {
+ key: 'VideoResolutionRatio',
+ label: 'VideoResolutionRatio',
+ value: hasValue(model.videoResolutionRatio)
+ ? model.videoResolutionRatio
+ : t('空'),
+ },
];
return rows;
};
@@ -646,6 +738,8 @@ export function useModelPricingEditorState({
ImageRatio: parseOptionJSON(options.ImageRatio),
AudioRatio: parseOptionJSON(options.AudioRatio),
AudioCompletionRatio: parseOptionJSON(options.AudioCompletionRatio),
+ VoiceCloneUnlockRatio: parseOptionJSON(options.VoiceCloneUnlockRatio),
+ VideoResolutionRatio: parseOptionJSON(options.VideoResolutionRatio),
ModelBillingMode: parseOptionJSON(options['billing_setting.billing_mode']),
ModelBillingExpr: parseOptionJSON(options['billing_setting.billing_expr']),
};
@@ -661,6 +755,8 @@ export function useModelPricingEditorState({
...Object.keys(sourceMaps.ImageRatio),
...Object.keys(sourceMaps.AudioRatio),
...Object.keys(sourceMaps.AudioCompletionRatio),
+ ...Object.keys(sourceMaps.VoiceCloneUnlockRatio),
+ ...Object.keys(sourceMaps.VideoResolutionRatio),
...Object.keys(sourceMaps.ModelBillingMode),
...Object.keys(sourceMaps.ModelBillingExpr),
]);
@@ -872,6 +968,14 @@ export function useModelPricingEditorState({
});
};
+ const handleJsonFieldChange = (field, value) => {
+ if (!selectedModel) return;
+ upsertModel(selectedModel.name, (model) => ({
+ ...model,
+ [field]: value,
+ }));
+ };
+
const handleBillingModeChange = (value) => {
if (!selectedModel) return;
upsertModel(selectedModel.name, (model) => {
@@ -971,6 +1075,8 @@ export function useModelPricingEditorState({
imagePrice: selectedModel.imagePrice,
audioInputPrice: selectedModel.audioInputPrice,
audioOutputPrice: selectedModel.audioOutputPrice,
+ voiceCloneUnlockPrice: selectedModel.voiceCloneUnlockPrice,
+ videoResolutionRatio: selectedModel.videoResolutionRatio,
billingExpr: selectedModel.billingExpr || '',
requestRuleExpr: selectedModel.requestRuleExpr || '',
};
@@ -1006,6 +1112,8 @@ export function useModelPricingEditorState({
audioOutputPrice:
Boolean(sourceToggles.audioInputPrice) &&
Boolean(sourceToggles.audioOutputPrice),
+ voiceCloneUnlockPrice: Boolean(sourceToggles.voiceCloneUnlockPrice),
+ videoResolutionRatio: Boolean(sourceToggles.videoResolutionRatio),
};
});
return next;
@@ -1032,6 +1140,8 @@ export function useModelPricingEditorState({
ImageRatio: {},
AudioRatio: {},
AudioCompletionRatio: {},
+ VoiceCloneUnlockRatio: {},
+ VideoResolutionRatio: {},
};
const tieredOutput = {
@@ -1122,6 +1232,7 @@ export function useModelPricingEditorState({
isOptionalFieldEnabled,
handleOptionalFieldToggle,
handleNumericFieldChange,
+ handleJsonFieldChange,
handleBillingModeChange,
handleBillingExprChange,
handleRequestRuleExprChange,
diff --git a/web/classic/vite.config.js b/web/classic/vite.config.js
index 73e46212a587..ed8ac649ef65 100644
--- a/web/classic/vite.config.js
+++ b/web/classic/vite.config.js
@@ -19,10 +19,8 @@ For commercial licensing, please contact support@quantumnous.com
import react from '@vitejs/plugin-react';
import { defineConfig, transformWithEsbuild } from 'vite';
-import pkg from '@douyinfe/vite-plugin-semi';
import path from 'path';
import { codeInspectorPlugin } from 'code-inspector-plugin';
-const { vitePluginSemi } = pkg;
// https://vitejs.dev/config/
export default defineConfig({
@@ -51,9 +49,6 @@ export default defineConfig({
},
},
react(),
- vitePluginSemi({
- cssLayer: true,
- }),
],
optimizeDeps: {
force: true,
diff --git a/web/default/src/features/models/components/drawers/model-mutate-drawer.tsx b/web/default/src/features/models/components/drawers/model-mutate-drawer.tsx
index 5e9cee02b800..99f48b9b85b6 100644
--- a/web/default/src/features/models/components/drawers/model-mutate-drawer.tsx
+++ b/web/default/src/features/models/components/drawers/model-mutate-drawer.tsx
@@ -180,6 +180,8 @@ export function ModelMutateDrawer({
ImageRatio: '',
AudioRatio: '',
AudioCompletionRatio: '',
+ VoiceCloneUnlockRatio: '',
+ VideoResolutionRatio: '',
ExposeRatioEnabled: false,
'billing_setting.billing_mode': '{}',
'billing_setting.billing_expr': '{}',
diff --git a/web/default/src/features/system-settings/billing/index.tsx b/web/default/src/features/system-settings/billing/index.tsx
index daad50668a92..2c0788b12142 100644
--- a/web/default/src/features/system-settings/billing/index.tsx
+++ b/web/default/src/features/system-settings/billing/index.tsx
@@ -47,6 +47,8 @@ const defaultBillingSettings: BillingSettings = {
ImageRatio: '',
AudioRatio: '',
AudioCompletionRatio: '',
+ VoiceCloneUnlockRatio: '',
+ VideoResolutionRatio: '',
ExposeRatioEnabled: false,
'billing_setting.billing_mode': '{}',
'billing_setting.billing_expr': '{}',
diff --git a/web/default/src/features/system-settings/billing/section-registry.tsx b/web/default/src/features/system-settings/billing/section-registry.tsx
index 1a1dc8a2f6c7..77c29587ad87 100644
--- a/web/default/src/features/system-settings/billing/section-registry.tsx
+++ b/web/default/src/features/system-settings/billing/section-registry.tsx
@@ -34,6 +34,8 @@ const getModelDefaults = (settings: BillingSettings) => ({
ImageRatio: settings.ImageRatio,
AudioRatio: settings.AudioRatio,
AudioCompletionRatio: settings.AudioCompletionRatio,
+ VoiceCloneUnlockRatio: settings.VoiceCloneUnlockRatio,
+ VideoResolutionRatio: settings.VideoResolutionRatio,
ExposeRatioEnabled: settings.ExposeRatioEnabled,
BillingMode: settings['billing_setting.billing_mode'],
BillingExpr: settings['billing_setting.billing_expr'],
diff --git a/web/default/src/features/system-settings/models/constants.ts b/web/default/src/features/system-settings/models/constants.ts
index 54dae556b2f7..e14e24a14e2c 100644
--- a/web/default/src/features/system-settings/models/constants.ts
+++ b/web/default/src/features/system-settings/models/constants.ts
@@ -66,6 +66,8 @@ export const RATIO_TYPE_OPTIONS = [
{ label: 'Image ratio', value: 'image_ratio' },
{ label: 'Audio ratio', value: 'audio_ratio' },
{ label: 'Audio completion ratio', value: 'audio_completion_ratio' },
+ { label: 'Voice clone unlock ratio', value: 'voice_clone_unlock_ratio' },
+ { label: 'Video resolution ratio', value: 'video_resolution_ratio' },
{ label: 'Fixed price', value: 'model_price' },
{ label: 'Expression billing', value: 'billing_expr' },
] as const
diff --git a/web/default/src/features/system-settings/models/index.tsx b/web/default/src/features/system-settings/models/index.tsx
index 050235ac7333..0cbcefbcd8e8 100644
--- a/web/default/src/features/system-settings/models/index.tsx
+++ b/web/default/src/features/system-settings/models/index.tsx
@@ -51,6 +51,8 @@ const defaultModelSettings: ModelSettings = {
ImageRatio: '',
AudioRatio: '',
AudioCompletionRatio: '',
+ VoiceCloneUnlockRatio: '',
+ VideoResolutionRatio: '',
ExposeRatioEnabled: false,
'billing_setting.billing_mode': '{}',
'billing_setting.billing_expr': '{}',
diff --git a/web/default/src/features/system-settings/models/model-pricing-sheet.tsx b/web/default/src/features/system-settings/models/model-pricing-sheet.tsx
index b9e5c548e67e..b31cc1255e90 100644
--- a/web/default/src/features/system-settings/models/model-pricing-sheet.tsx
+++ b/web/default/src/features/system-settings/models/model-pricing-sheet.tsx
@@ -84,6 +84,8 @@ const createModelPricingSchema = (t: (key: string) => string) =>
imageRatio: z.string().optional(),
audioRatio: z.string().optional(),
audioCompletionRatio: z.string().optional(),
+ voiceCloneUnlockRatio: z.string().optional(),
+ videoResolutionRatio: z.string().optional(),
})
type ModelPricingFormValues = z.infer<
@@ -98,6 +100,8 @@ type LaneKey =
| 'image'
| 'audioInput'
| 'audioOutput'
+ | 'voiceCloneUnlock'
+ | 'videoResolution'
export type ModelRatioData = {
name: string
@@ -109,6 +113,8 @@ export type ModelRatioData = {
imageRatio?: string
audioRatio?: string
audioCompletionRatio?: string
+ voiceCloneUnlockRatio?: string
+ videoResolutionRatio?: string
billingMode?: PricingMode
billingExpr?: string
requestRuleExpr?: string
@@ -146,6 +152,8 @@ const EMPTY_LANE_PRICES: Record