fix(canvas): 分镜视频参数 - 按模型能力约束同步音频与水印
This commit is contained in:
1 parent
58272e683f
commit
54c29b3fb4
3 files changed
+79
-3
No files matched your search
@@ -248,6 +248,8 @@ export function defaultImageParamsForModel(config: AiConfig, model: string): Pic
|
|||||||
|
|
||||||
export type ModelGenerationDefaults = Pick<AiConfig, "size" | "quality" | "transparentBackground" | "count" | "videoSeconds" | "vquality" | "videoGenerateAudio" | "videoWatermark">;
|
export type ModelGenerationDefaults = Pick<AiConfig, "size" | "quality" | "transparentBackground" | "count" | "videoSeconds" | "vquality" | "videoGenerateAudio" | "videoWatermark">;
|
||||||
|
|
||||||
|
type ModelVideoBooleanOptions = Pick<AiConfig, "videoGenerateAudio" | "videoWatermark">;
|
||||||
|
|
||||||
export function resolveModelGenerationDefaults(
|
export function resolveModelGenerationDefaults(
|
||||||
config: AiConfig,
|
config: AiConfig,
|
||||||
model: string,
|
model: string,
|
||||||
@@ -294,6 +296,20 @@ export function resolveModelGenerationDefaults(
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveModelVideoBooleanOptions(
|
||||||
|
config: AiConfig,
|
||||||
|
model: string,
|
||||||
|
explicit: Partial<ModelVideoBooleanOptions> = {},
|
||||||
|
fallback: Partial<ModelVideoBooleanOptions> = {},
|
||||||
|
): ModelVideoBooleanOptions {
|
||||||
|
const profile = modelCapabilityConfigFor(config, model).video!;
|
||||||
|
const defaults = resolveModelGenerationDefaults(config, model, "video", explicit, fallback);
|
||||||
|
return {
|
||||||
|
videoGenerateAudio: profile.generateAudio.supported ? defaults.videoGenerateAudio ?? String(profile.generateAudio.default) : "false",
|
||||||
|
videoWatermark: profile.watermark.supported ? defaults.videoWatermark ?? String(profile.watermark.default) : "false",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function maxModelInputCapacity(config: AiConfig, capability: "image" | "video", kind: "image" | "video" | "audio") {
|
export function maxModelInputCapacity(config: AiConfig, capability: "image" | "video", kind: "image" | "video" | "audio") {
|
||||||
const options = selectableModelsByCapability(config, capability);
|
const options = selectableModelsByCapability(config, capability);
|
||||||
if (!options.length) return null;
|
if (!options.length) return null;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { CreditSymbol, requestCreditCost } from "@/constant/credits";
|
|||||||
import { modelCapabilityConfigFor, normalizeImageValue, normalizeVideoValue, videoDurationOptions } from "@/lib/model-capabilities";
|
import { modelCapabilityConfigFor, normalizeImageValue, normalizeVideoValue, videoDurationOptions } from "@/lib/model-capabilities";
|
||||||
import { modelQuoteRequest } from "@/lib/model-pricing";
|
import { modelQuoteRequest } from "@/lib/model-pricing";
|
||||||
import { customShotTitle, formatShotOrdinal, normalizeDefaultShotTitle } from "@/lib/shot-label";
|
import { customShotTitle, formatShotOrdinal, normalizeDefaultShotTitle } from "@/lib/shot-label";
|
||||||
import { modelCompatibilityError, resolveCompatibleModel, type ModelRequirements } from "@/lib/model-selection";
|
import { modelCompatibilityError, resolveCompatibleModel, resolveModelVideoBooleanOptions, type ModelRequirements } from "@/lib/model-selection";
|
||||||
import { formatVideoResolutionLabel } from "@/lib/video-generation-options";
|
import { formatVideoResolutionLabel } from "@/lib/video-generation-options";
|
||||||
import { submitBackendGenerationTask } from "@/services/api/generation-task";
|
import { submitBackendGenerationTask } from "@/services/api/generation-task";
|
||||||
import { quoteLogicalModel } from "@/services/api/logical-models";
|
import { quoteLogicalModel } from "@/services/api/logical-models";
|
||||||
@@ -145,6 +145,12 @@ export default function WorkflowProductionWorkbench(props: Props) {
|
|||||||
const activeProfile = useMemo(() => modelCapabilityConfigFor(effectiveConfig, routedModel), [effectiveConfig, routedModel]);
|
const activeProfile = useMemo(() => modelCapabilityConfigFor(effectiveConfig, routedModel), [effectiveConfig, routedModel]);
|
||||||
const videoProfile = generationCapability === "video" ? activeProfile.video : undefined;
|
const videoProfile = generationCapability === "video" ? activeProfile.video : undefined;
|
||||||
const imageProfile = generationCapability === "image" ? activeProfile.image : undefined;
|
const imageProfile = generationCapability === "image" ? activeProfile.image : undefined;
|
||||||
|
const videoBooleanOptions = useMemo(() => generationCapability === "video"
|
||||||
|
? resolveModelVideoBooleanOptions(effectiveConfig, routedModel, {}, {
|
||||||
|
videoGenerateAudio: effectiveConfig.videoGenerateAudio,
|
||||||
|
videoWatermark: effectiveConfig.videoWatermark,
|
||||||
|
})
|
||||||
|
: undefined, [effectiveConfig, generationCapability, routedModel]);
|
||||||
const generationConfig = useMemo(() => ({
|
const generationConfig = useMemo(() => ({
|
||||||
...effectiveConfig,
|
...effectiveConfig,
|
||||||
model: routedModel,
|
model: routedModel,
|
||||||
@@ -154,7 +160,8 @@ export default function WorkflowProductionWorkbench(props: Props) {
|
|||||||
quality: imageQuality,
|
quality: imageQuality,
|
||||||
vquality: resolution,
|
vquality: resolution,
|
||||||
videoSeconds: generationSeconds,
|
videoSeconds: generationSeconds,
|
||||||
}), [aspectRatio, effectiveConfig, generationCapability, generationSeconds, imageQuality, resolution, routedModel]);
|
...(videoBooleanOptions || {}),
|
||||||
|
}), [aspectRatio, effectiveConfig, generationCapability, generationSeconds, imageQuality, resolution, routedModel, videoBooleanOptions]);
|
||||||
const priceChannel = resolveModelChannel(generationConfig, routedModel);
|
const priceChannel = resolveModelChannel(generationConfig, routedModel);
|
||||||
const configuredCredits = requestCreditCost({
|
const configuredCredits = requestCreditCost({
|
||||||
channelMode: priceChannel.scope === "system" ? "remote" : "local",
|
channelMode: priceChannel.scope === "system" ? "remote" : "local",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { describe, expect, test } from "bun:test";
|
|||||||
import { canvasConnectionError } from "../src/lib/canvas/canvas-connection-policy";
|
import { canvasConnectionError } from "../src/lib/canvas/canvas-connection-policy";
|
||||||
import { assertCanvasImageReferenceLimit, buildGenerationConfig, canvasImageReferenceLimitError, resolveCanvasGenerationModel } from "../src/lib/canvas/canvas-project-generation";
|
import { assertCanvasImageReferenceLimit, buildGenerationConfig, canvasImageReferenceLimitError, resolveCanvasGenerationModel } from "../src/lib/canvas/canvas-project-generation";
|
||||||
import { defaultModelCapabilityConfig } from "../src/lib/model-capabilities";
|
import { defaultModelCapabilityConfig } from "../src/lib/model-capabilities";
|
||||||
import { groupModelsByDisplayName, inferVideoOperation, modelCompatibilityError, modelGroupReferenceLimits, modelPromptLengthError, resolveCompatibleModel, resolveModelGenerationDefaults } from "../src/lib/model-selection";
|
import { groupModelsByDisplayName, inferVideoOperation, modelCompatibilityError, modelGroupReferenceLimits, modelPromptLengthError, resolveCompatibleModel, resolveModelGenerationDefaults, resolveModelVideoBooleanOptions } from "../src/lib/model-selection";
|
||||||
import { defaultConfig, normalizeModelOptionValue, type AiConfig, type ModelChannel } from "../src/stores/use-config-store";
|
import { defaultConfig, normalizeModelOptionValue, type AiConfig, type ModelChannel } from "../src/stores/use-config-store";
|
||||||
import { CanvasNodeType, type CanvasConnection, type CanvasNodeData } from "../src/types/canvas";
|
import { CanvasNodeType, type CanvasConnection, type CanvasNodeData } from "../src/types/canvas";
|
||||||
|
|
||||||
@@ -141,6 +141,59 @@ describe("逻辑模型选择", () => {
|
|||||||
expect(resolveModelGenerationDefaults(config, model, "video", {}, { videoSeconds: "6" }).videoSeconds).toBe("15");
|
expect(resolveModelGenerationDefaults(config, model, "video", {}, { videoSeconds: "6" }).videoSeconds).toBe("15");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("镜头视频不会向不支持同步音频的模型提交全局 true 默认值", () => {
|
||||||
|
const model = "platform::silent-video";
|
||||||
|
const profile = defaultModelCapabilityConfig(undefined, "silent-video");
|
||||||
|
profile.video!.generateAudio = { supported: false, default: false };
|
||||||
|
profile.video!.watermark = { supported: false, default: false };
|
||||||
|
const config: AiConfig = {
|
||||||
|
...defaultConfig,
|
||||||
|
videoGenerateAudio: "true",
|
||||||
|
videoWatermark: "true",
|
||||||
|
channels: [{
|
||||||
|
id: "platform",
|
||||||
|
name: "平台模型",
|
||||||
|
baseUrl: "/api",
|
||||||
|
apiKey: "system",
|
||||||
|
apiFormat: "openai",
|
||||||
|
scope: "system",
|
||||||
|
models: ["silent-video"],
|
||||||
|
modelCosts: [{
|
||||||
|
model: "silent-video",
|
||||||
|
capability: "video",
|
||||||
|
billingMode: "per_second",
|
||||||
|
unitPriceMicrocredits: 1,
|
||||||
|
logicalModelId: "silent-video",
|
||||||
|
logicalCapabilitySpec: {
|
||||||
|
version: 1,
|
||||||
|
capability: "video",
|
||||||
|
options: {
|
||||||
|
videoGenerateAudio: { values: [false] },
|
||||||
|
videoWatermark: { values: [false] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
capabilityConfig: profile,
|
||||||
|
}],
|
||||||
|
}],
|
||||||
|
models: [model],
|
||||||
|
videoModels: [model],
|
||||||
|
videoModel: model,
|
||||||
|
model,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(resolveModelVideoBooleanOptions(config, model, {}, {
|
||||||
|
videoGenerateAudio: config.videoGenerateAudio,
|
||||||
|
videoWatermark: config.videoWatermark,
|
||||||
|
})).toEqual({ videoGenerateAudio: "false", videoWatermark: "false" });
|
||||||
|
|
||||||
|
profile.video!.generateAudio = { supported: true, default: true };
|
||||||
|
config.videoGenerateAudio = "false";
|
||||||
|
expect(resolveModelVideoBooleanOptions(config, model, {}, {
|
||||||
|
videoGenerateAudio: config.videoGenerateAudio,
|
||||||
|
videoWatermark: config.videoWatermark,
|
||||||
|
})).toEqual({ videoGenerateAudio: "true", videoWatermark: "false" });
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
test("渠道视频能力配置优先于旧的全局 6 秒和 1:1 配置", () => {
|
test("渠道视频能力配置优先于旧的全局 6 秒和 1:1 配置", () => {
|
||||||
const model = "autodl-channel::MiniMax H3";
|
const model = "autodl-channel::MiniMax H3";
|
||||||
|
|||||||
Reference in new issue
Block a user