fix(video): 创作台视频 - 修正同步音频参数与万有引力结果下载地址 (#554)
Original-Commit: e85e78fb898be7ef1fcd2027b62ab43c00dfaa2c fix(video): 创作台视频 - 修正同步音频参数与万有引力结果下载地址
This commit is contained in:
26
web/src/pages/create/creation-generation-config.ts
Normal file
26
web/src/pages/create/creation-generation-config.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { modelCapabilityConfigFor, normalizeVideoValue } from "@/lib/model-capabilities";
|
||||
import { resolveModelVideoBooleanOptions } from "@/lib/model-selection";
|
||||
import type { AiConfig } from "@/stores/use-config-store";
|
||||
import type { CreationSettings } from "./creation-types";
|
||||
|
||||
// 创作台没有音频和水印开关;报价与提交共用所选模型的默认值和能力限制。
|
||||
export function creationVideoConfig(config: AiConfig, model: string, settings: Pick<CreationSettings, "ratio" | "seconds" | "videoQuality">): AiConfig {
|
||||
const profile = modelCapabilityConfigFor(config, model).video!;
|
||||
const normalized = normalizeVideoValue(profile, {
|
||||
ratio: settings.ratio,
|
||||
seconds: settings.seconds,
|
||||
resolution: settings.videoQuality,
|
||||
});
|
||||
return {
|
||||
...config,
|
||||
model,
|
||||
videoModel: model,
|
||||
size: normalized.ratio,
|
||||
videoSeconds: normalized.seconds,
|
||||
vquality: normalized.resolution.replace(/p$/i, ""),
|
||||
...resolveModelVideoBooleanOptions(config, model, {}, {
|
||||
videoGenerateAudio: config.videoGenerateAudio,
|
||||
videoWatermark: config.videoWatermark,
|
||||
}),
|
||||
};
|
||||
}
|
||||
@@ -34,6 +34,7 @@ import { attachCreationTaskContexts, completedCreationGenerationTask, conversati
|
||||
import { CreationComposer, CreationEmptySuggest, CreationFeaturedWorks, CreationHistoryDrawer, CreationMessageView, CreationModeTabs, CreationWorkspaceToolbar, creationAssetCategoryLabels } from "./creation-workspace";
|
||||
import { CreationAgentEntry } from "./creation-agent-entry";
|
||||
import { createCreationSubmitGate } from "./creation-submit-gate";
|
||||
import { creationVideoConfig } from "./creation-generation-config";
|
||||
|
||||
const AssetLibraryPickerModal = lazy(() => import("@/components/assets/asset-library-picker-modal").then((module) => ({ default: module.AssetLibraryPickerModal })));
|
||||
const loadCreationRuntime = () => import("./creation-runtime");
|
||||
@@ -142,10 +143,13 @@ export default function CreatePage() {
|
||||
options: mode === "image"
|
||||
? { size: ratio, quality, count: Number(count), transparentBackground: config.transparentBackground === "true" }
|
||||
: mode === "video"
|
||||
? { size: ratio, videoSeconds: Number(seconds), vquality: videoQuality, videoGenerateAudio: config.videoGenerateAudio === "true", videoWatermark: config.videoWatermark === "true" }
|
||||
? { size: ratio, videoSeconds: Number(seconds), vquality: videoQuality }
|
||||
: {},
|
||||
}), [attachments, config.transparentBackground, config.videoGenerateAudio, config.videoWatermark, count, hasPrompt, mode, quality, ratio, seconds, videoQuality]);
|
||||
}), [attachments, config.transparentBackground, count, hasPrompt, mode, quality, ratio, seconds, videoQuality]);
|
||||
const selectedModel = resolveCompatibleModel(config, preferredModel, modelRequirements) || preferredModel;
|
||||
const generationConfig = useMemo(() => mode === "video"
|
||||
? creationVideoConfig(config, selectedModel, { ratio, seconds, videoQuality })
|
||||
: config, [config, mode, ratio, seconds, selectedModel, videoQuality]);
|
||||
const imageProfile = useMemo(() => modelCapabilityConfigFor(config, selectedModel).image!, [config, selectedModel]);
|
||||
const videoProfile = useMemo(() => modelCapabilityConfigFor(config, selectedModel).video!, [config, selectedModel]);
|
||||
const maxReferences = mode === "video" ? videoProfile.operations.includes("image_to_video") ? videoProfile.references.maxImages : 0 : mode === "image" ? imageProfile.references.maxImages : 6;
|
||||
@@ -628,18 +632,15 @@ export default function CreatePage() {
|
||||
const requestLifecycle = runtime.beginGenerationConsumer(controller.signal);
|
||||
abortRef.current = controller;
|
||||
const normalizedImage = mode === "image" ? normalizeImageValue(imageProfile, { size: ratio, quality, count }) : undefined;
|
||||
const normalizedVideo = mode === "video" ? normalizeVideoValue(videoProfile, { seconds, ratio, resolution: videoQuality }) : undefined;
|
||||
const requestConfig = {
|
||||
...config,
|
||||
...generationConfig,
|
||||
model: selectedModel,
|
||||
imageModel: selectedModel,
|
||||
videoModel: selectedModel,
|
||||
textModel: selectedModel,
|
||||
...(mode === "image"
|
||||
? { size: normalizedImage?.size || ratio, quality: normalizedImage?.quality || quality, count: normalizedImage?.count || count, videoSeconds: config.videoSeconds }
|
||||
: mode === "video"
|
||||
? { size: normalizedVideo?.ratio ?? ratio, videoSeconds: normalizedVideo?.seconds || seconds, vquality: (normalizedVideo?.resolution ?? videoQuality).replace(/p$/i, "") }
|
||||
: {}),
|
||||
: {}),
|
||||
};
|
||||
try {
|
||||
if (mode === "text") {
|
||||
@@ -957,7 +958,7 @@ export default function CreatePage() {
|
||||
modelRequirements,
|
||||
imageProfile,
|
||||
videoProfile,
|
||||
config,
|
||||
config: generationConfig,
|
||||
onModelChange: (value: string) => updateConfig(mode === "text" ? "textModel" : mode === "image" ? "imageModel" : "videoModel", value),
|
||||
ratio,
|
||||
setRatio: setComposerRatio,
|
||||
|
||||
50
web/test/creation-generation-config.test.ts
Normal file
50
web/test/creation-generation-config.test.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { defaultModelCapabilityConfig } from "../src/lib/model-capabilities";
|
||||
import { modelQuoteRequest } from "../src/lib/model-pricing";
|
||||
import { creationVideoConfig } from "../src/pages/create/creation-generation-config";
|
||||
import { prepareBackendGenerationTask } from "../src/services/api/generation-task";
|
||||
import { defaultConfig, type AiConfig } from "../src/stores/use-config-store";
|
||||
|
||||
function videoConfig(generateAudio: { supported: boolean; default: boolean }): AiConfig {
|
||||
const capabilityConfig = defaultModelCapabilityConfig();
|
||||
capabilityConfig.video!.generateAudio = generateAudio;
|
||||
capabilityConfig.video!.watermark = { supported: false, default: false };
|
||||
return {
|
||||
...defaultConfig,
|
||||
model: "gravity::minimax-h3-a",
|
||||
videoModel: "gravity::minimax-h3-a",
|
||||
videoGenerateAudio: "true",
|
||||
videoWatermark: "true",
|
||||
channels: [{
|
||||
id: "gravity", name: "万有引力", baseUrl: "/api", apiKey: "", apiFormat: "openai", scope: "system",
|
||||
models: ["minimax-h3-a"],
|
||||
modelCosts: [{ model: "minimax-h3-a", capability: "video", billingMode: "per_second", unitPriceMicrocredits: 1, capabilityConfig }],
|
||||
}],
|
||||
};
|
||||
}
|
||||
|
||||
test("创作台无声视频报价、提交与重试不携带全局音频和水印默认值", async () => {
|
||||
const config = videoConfig({ supported: false, default: false });
|
||||
const requestConfig = creationVideoConfig(config, config.videoModel, { ratio: "16:9", seconds: "6", videoQuality: "480" });
|
||||
const quote = modelQuoteRequest(requestConfig, requestConfig.model, "video");
|
||||
expect(quote?.intent.options).toMatchObject({ size: "16:9", vquality: "480", videoSeconds: 6, videoGenerateAudio: false, videoWatermark: false });
|
||||
|
||||
for (const retryOf of [undefined, "failed-task"]) {
|
||||
const task = await prepareBackendGenerationTask({ mode: "video", prompt: "小猫在流泪", config: requestConfig, retryOf });
|
||||
expect(task.input).toMatchObject({
|
||||
config: { size: "16:9", vquality: "480", videoSeconds: "6", videoGenerateAudio: "false", videoWatermark: "false" },
|
||||
...(retryOf ? { metadata: { retryOf } } : {}),
|
||||
});
|
||||
expect(task.operation).toBe("text_to_video");
|
||||
}
|
||||
expect(config.videoGenerateAudio).toBe("true");
|
||||
expect(config.videoWatermark).toBe("true");
|
||||
});
|
||||
|
||||
test.each([false, true])("创作台支持同步音频时遵循模型默认值 %s", (audioDefault) => {
|
||||
const config = videoConfig({ supported: true, default: audioDefault });
|
||||
config.videoGenerateAudio = String(!audioDefault);
|
||||
const requestConfig = creationVideoConfig(config, config.videoModel, { ratio: "16:9", seconds: "6", videoQuality: "480" });
|
||||
expect(requestConfig.videoGenerateAudio).toBe(String(audioDefault));
|
||||
expect(modelQuoteRequest(requestConfig, requestConfig.model, "video")?.intent.options?.videoGenerateAudio).toBe(audioDefault);
|
||||
});
|
||||
Reference in New Issue
Block a user