fix(platform): 模型能力 - 修复图片时长误判与后台默认参数同步

This commit is contained in:
ddCat committed 2026-08-19 09:57:37 +08:00
1 parent 6a698129bd
commit c7452b701d
6 files changed
+289 -46

No files matched your search

+39 -15
View File
@@ -7,8 +7,8 @@ import { resourceIdFromStorageKey } from "@/services/api/resources";
import { NODE_DEFAULT_SIZE } from "@/constant/canvas";
import { normalizeVideoDuration, normalizeVideoResolution } from "@/lib/video-generation-options";
import { isSeedanceVideoConfig } from "@/lib/seedance-video";
import { modelCapabilityConfigFor, normalizeImageValue } from "@/lib/model-capabilities";
import { modelRequestOptions, resolveCompatibleModel, resolveVideoOperation, type ModelRequirements } from "@/lib/model-selection";
import { modelCapabilityConfigFor } from "@/lib/model-capabilities";
import { modelRequestOptions, resolveCompatibleModel, resolveModelGenerationDefaults, resolveVideoOperation, type ModelRequirements } from "@/lib/model-selection";
import { imageMetadata } from "@/lib/canvas/canvas-generation-task-sync";
import { ensureMediaNodeMinimumSize } from "@/lib/canvas/canvas-node-size";
import type { CanvasNodeGenerationMode } from "@/components/canvas/canvas-node-prompt-panel";
@@ -399,22 +399,46 @@ export function buildGenerationConfig(config: AiConfig, node: CanvasNodeData | u
? { ...requirements, options: { ...liveOptions, ...(requirements.options || {}) } }
: { capability: mode, options: liveOptions };
const model = resolveCompatibleModel(config, preferredModel, imageSize ? { ...baseRequirements, imageSize } : baseRequirements) || preferredModel;
const imageProfile = mode === "image" ? modelCapabilityConfigFor(config, model).image! : undefined;
const normalizedImage = imageProfile
? normalizeImageValue(imageProfile, {
quality: requestedConfig.quality,
size: requestedConfig.size,
transparentBackground: requestedConfig.transparentBackground,
count: requestedConfig.count,
})
: undefined;
const generationDefaults = resolveModelGenerationDefaults(
config,
model,
mode === "image" ? "image" : mode === "video" ? "video" : undefined,
mode === "image"
? {
size: node?.metadata?.size,
quality: node?.metadata?.quality,
transparentBackground: node?.metadata?.transparentBackground,
count: requestedConfig.count,
}
: {
size: node?.metadata?.size,
videoSeconds: node?.metadata?.seconds,
vquality: node?.metadata?.vquality,
videoGenerateAudio: node?.metadata?.generateAudio,
videoWatermark: node?.metadata?.watermark,
},
{
size: requestedConfig.size,
quality: requestedConfig.quality,
transparentBackground: requestedConfig.transparentBackground,
count: requestedConfig.count,
videoSeconds: requestedConfig.videoSeconds,
vquality: requestedConfig.vquality,
videoGenerateAudio: requestedConfig.videoGenerateAudio,
videoWatermark: requestedConfig.videoWatermark,
},
);
return {
...requestedConfig,
model,
quality: normalizedImage?.quality || requestedConfig.quality,
size: normalizedImage?.size || requestedConfig.size,
transparentBackground: normalizedImage?.transparentBackground || (requestedConfig.transparentBackground === "true" ? "true" : "false"),
count: normalizedImage?.count || requestedConfig.count,
quality: generationDefaults.quality || requestedConfig.quality,
size: generationDefaults.size || requestedConfig.size,
transparentBackground: generationDefaults.transparentBackground || (requestedConfig.transparentBackground === "true" ? "true" : "false"),
videoSeconds: generationDefaults.videoSeconds || requestedConfig.videoSeconds,
vquality: generationDefaults.vquality || requestedConfig.vquality,
videoGenerateAudio: generationDefaults.videoGenerateAudio || requestedConfig.videoGenerateAudio,
videoWatermark: generationDefaults.videoWatermark || requestedConfig.videoWatermark,
count: generationDefaults.count || requestedConfig.count,
};
}
+53 -2
View File
@@ -1,4 +1,4 @@
import { defaultImageCapabilityConfig, modelCapabilityConfigFor, STANDARD_IMAGE_SIZE_VALUES, videoDurationAllowed, type ImageCapabilityConfig } from "@/lib/model-capabilities";
import { defaultImageCapabilityConfig, modelCapabilityConfigFor, normalizeImageValue, normalizeVideoValue, STANDARD_IMAGE_SIZE_VALUES, videoDurationAllowed, type ImageCapabilityConfig } from "@/lib/model-capabilities";
import { modelOptionName, resolveModelChannel, selectableModelsByCapability, type AiConfig, type ModelCapability } from "@/stores/use-config-store";
export type ModelInputSummary = {
@@ -129,7 +129,13 @@ function logicalModelCompatibilityError(spec: NonNullable<NonNullable<AiConfig["
}
const operation = requirements.capability === "video" && input ? resolveVideoOperation(input, requirements.videoOperation) : requirements.videoOperation;
if (operation && spec.operations?.length && !spec.operations.includes(operation)) return "不支持当前生成模式";
const options = { ...requirements.options, ...(requirements.videoSeconds ? { videoSeconds: requirements.videoSeconds } : {}), ...(requirements.imageSize ? { size: requirements.imageSize } : {}) };
// 图片创作状态也会携带全局默认视频时长;这个字段只对视频模型有意义,
// 不能把它拼进图片逻辑模型的能力匹配,否则图片模型会被误判为“不支持当前时长”。
const options = {
...requirements.options,
...(requirements.capability === "video" && requirements.videoSeconds ? { videoSeconds: requirements.videoSeconds } : {}),
...(requirements.capability === "image" && requirements.imageSize ? { size: requirements.imageSize } : {}),
};
for (const [name, value] of Object.entries(options)) {
if (value === undefined || value === null || value === "") continue;
const constraint = spec.options?.[name];
@@ -223,6 +229,51 @@ export function defaultImageParamsForModel(config: AiConfig, model: string): Pic
};
}
export type ModelGenerationDefaults = Pick<AiConfig, "size" | "quality" | "transparentBackground" | "count" | "videoSeconds" | "vquality" | "videoGenerateAudio" | "videoWatermark">;
export function resolveModelGenerationDefaults(
config: AiConfig,
model: string,
capability: "image" | "video" | undefined,
explicit: Partial<ModelGenerationDefaults> = {},
fallback: Partial<ModelGenerationDefaults> = {},
): Partial<ModelGenerationDefaults> {
if (!capability) return {};
const channel = resolveModelChannel(config, model);
const cost = channel.modelCosts?.find((item) => item.model === modelOptionName(model));
const isManagedModel = Boolean(cost?.logicalModelId || cost?.logicalCapabilitySpec);
const source = (key: keyof ModelGenerationDefaults) => explicit[key] ?? (isManagedModel ? undefined : fallback[key]);
const profile = modelCapabilityConfigFor(config, model);
if (capability === "image" && profile.image) {
const normalized = normalizeImageValue(profile.image, {
size: source("size"),
quality: source("quality"),
count: source("count"),
transparentBackground: source("transparentBackground"),
});
return normalized;
}
if (capability === "video" && profile.video) {
const normalized = normalizeVideoValue(profile.video, {
seconds: source("videoSeconds"),
ratio: source("size"),
resolution: source("vquality"),
});
return {
videoSeconds: normalized.seconds,
size: normalized.ratio,
vquality: normalized.resolution.replace(/p$/i, ""),
videoGenerateAudio: source("videoGenerateAudio") ?? String(profile.video.generateAudio.default),
videoWatermark: source("videoWatermark") ?? String(profile.video.watermark.default),
};
}
return {};
}
export function maxModelInputCapacity(config: AiConfig, capability: "image" | "video", kind: "image" | "video" | "audio") {
const options = selectableModelsByCapability(config, capability);
if (!options.length) return null;