|
|
|
|
@@ -1,16 +1,17 @@
|
|
|
|
|
import { type GenerationTask } from "@/services/api/task-center";
|
|
|
|
|
import { backendProviderConfig, logicalModelIDForConfig, runBackendGenerationTask, type GenerationTaskDependencies } from "@/services/api/generation-task";
|
|
|
|
|
import { configuredModelMatchesCapability, defaultConfig, normalizeModelOptionValue, resolveModelRequestConfig, type AiConfig } from "@/stores/use-config-store";
|
|
|
|
|
import { configuredModelMatchesCapability, defaultConfig, normalizeModelOptionValue, normalizeRunningHubCapability, resolveModelRequestConfig, type AiConfig, type WorkflowFieldMapping } from "@/stores/use-config-store";
|
|
|
|
|
import { resolveImageUrl, uploadImage } from "@/services/image-storage";
|
|
|
|
|
import { resolveMediaUrl } from "@/services/file-storage";
|
|
|
|
|
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 } from "@/lib/model-capabilities";
|
|
|
|
|
import { modelRequestOptions, resolveCompatibleModel, resolveModelGenerationDefaults, resolveVideoOperation, type ModelRequirements } from "@/lib/model-selection";
|
|
|
|
|
import { modelCapabilityConfigFor, workflowFieldCurrentValue, workflowFieldHasStoredValue, workflowFieldKey, workflowFieldRandomKey, workflowFieldSubmissionValue, workflowOutputSizeValue, workflowVideoFieldsFromJson } from "@/lib/model-capabilities";
|
|
|
|
|
import { modelRequestOptions, resolveCompatibleModel, resolveModelGenerationDefaults, resolveVideoOperation, type ModelGenerationDefaults, type ModelRequirements } from "@/lib/model-selection";
|
|
|
|
|
import { imageMetadata } from "@/lib/canvas/canvas-generation-task-sync";
|
|
|
|
|
import { ensureMediaNodeMinimumSize } from "@/lib/canvas/canvas-node-size";
|
|
|
|
|
import { isCanvasWorkflowProvider, resolveCanvasWorkflowProvider } from "@/lib/canvas/canvas-workflow";
|
|
|
|
|
import type { CanvasNodeGenerationMode } from "@/components/canvas/canvas-node-prompt-panel";
|
|
|
|
|
import { CanvasNodeType, type CanvasAssistantSession, type CanvasConnection, type CanvasImageGenerationType, type CanvasNodeData, type CanvasNodeMetadata, type CanvasVideoEditOperation } from "@/types/canvas";
|
|
|
|
|
import type { ReferenceImage } from "@/types/image";
|
|
|
|
|
@@ -75,6 +76,8 @@ export async function runBackendCanvasGenerationTask(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function canvasImageReferenceLimitError(config: AiConfig, referenceImages: ReferenceImage[]) {
|
|
|
|
|
// 工作流的参考素材上限由字段映射槽位决定,不能再套用当前普通图片模型的能力表。
|
|
|
|
|
if ((config.taskWorkflowProvider || "model") !== "model") return "";
|
|
|
|
|
const maxImages = modelCapabilityConfigFor(config, config.model).image?.references.maxImages;
|
|
|
|
|
if (maxImages === undefined || referenceImages.length <= maxImages) return "";
|
|
|
|
|
return `当前图片模型最多支持 ${maxImages} 张参考图,当前已连接 ${referenceImages.length} 张。请移除多余连线后重试`;
|
|
|
|
|
@@ -85,7 +88,9 @@ export function assertCanvasImageReferenceLimit(config: AiConfig, referenceImage
|
|
|
|
|
if (error) throw new Error(error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export { backendProviderConfig, logicalModelIDForConfig };
|
|
|
|
|
export { backendProviderConfig };
|
|
|
|
|
// 兼容故事板执行器的旧导入路径。
|
|
|
|
|
export { logicalModelIDForConfig };
|
|
|
|
|
|
|
|
|
|
const generationOperationLocks = new Map<string, Promise<unknown>>();
|
|
|
|
|
|
|
|
|
|
@@ -214,6 +219,7 @@ export function audioExtension(mimeType?: string) {
|
|
|
|
|
|
|
|
|
|
export function buildImageGenerationMetadata(type: CanvasImageGenerationType, config: AiConfig, count: number, references: ReferenceImage[]): CanvasNodeMetadata {
|
|
|
|
|
return {
|
|
|
|
|
...generationWorkflowMetadata(config),
|
|
|
|
|
generationType: type,
|
|
|
|
|
model: config.model,
|
|
|
|
|
size: config.size,
|
|
|
|
|
@@ -225,18 +231,19 @@ export function buildImageGenerationMetadata(type: CanvasImageGenerationType, co
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function nodeReferenceImage(node: CanvasNodeData): ReferenceImage | null {
|
|
|
|
|
if (node.type !== CanvasNodeType.Image || !node.metadata?.content) return null;
|
|
|
|
|
if (node.type !== CanvasNodeType.Image || (!node.metadata?.content && !node.metadata?.storageKey)) return null;
|
|
|
|
|
return {
|
|
|
|
|
id: node.id,
|
|
|
|
|
name: `reference-${node.id}.png`,
|
|
|
|
|
type: node.metadata.mimeType || "image/png",
|
|
|
|
|
dataUrl: node.metadata.content,
|
|
|
|
|
dataUrl: node.metadata.content || "",
|
|
|
|
|
storageKey: node.metadata.storageKey,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function buildAudioGenerationMetadata(config: AiConfig): CanvasNodeMetadata {
|
|
|
|
|
return {
|
|
|
|
|
...generationWorkflowMetadata(config),
|
|
|
|
|
model: config.model,
|
|
|
|
|
audioVoice: config.audioVoice,
|
|
|
|
|
audioFormat: config.audioFormat,
|
|
|
|
|
@@ -312,12 +319,17 @@ export function buildVideoGenerationMetadata(
|
|
|
|
|
referenceVideos: ReferenceVideo[];
|
|
|
|
|
referenceAudios: ReferenceAudio[];
|
|
|
|
|
},
|
|
|
|
|
config?: AiConfig,
|
|
|
|
|
): CanvasNodeMetadata {
|
|
|
|
|
const metadata = node?.metadata;
|
|
|
|
|
const referenceImageIds = new Set((context?.referenceImages || []).map((image) => image.id));
|
|
|
|
|
const startFrame = requireConnectedVideoFrame(metadata?.videoStartFrameNodeId, "首帧", referenceImageIds);
|
|
|
|
|
const endFrame = requireConnectedVideoFrame(metadata?.videoEndFrameNodeId, "尾帧", referenceImageIds);
|
|
|
|
|
// 工作流视频把已连接媒体交给字段映射处理,不再把历史首尾帧选择当成硬约束。
|
|
|
|
|
// 这样旧节点切换到 RunningHub/ComfyUI 后,不会因为残留的首尾帧 ID 阻断生成。
|
|
|
|
|
const workflowVideo = node?.type === CanvasNodeType.Config && ((config?.taskWorkflowProvider === "runninghub" || config?.taskWorkflowProvider === "comfyui") || isCanvasWorkflowProvider(metadata));
|
|
|
|
|
const startFrame = workflowVideo ? undefined : requireConnectedVideoFrame(metadata?.videoStartFrameNodeId, "首帧", referenceImageIds);
|
|
|
|
|
const endFrame = workflowVideo ? undefined : requireConnectedVideoFrame(metadata?.videoEndFrameNodeId, "尾帧", referenceImageIds);
|
|
|
|
|
return {
|
|
|
|
|
...(config ? generationWorkflowMetadata(config) : {}),
|
|
|
|
|
videoEditOperation: resolveVideoEditOperation(node, context),
|
|
|
|
|
videoCameraMoveId: metadata?.videoCameraMoveId,
|
|
|
|
|
videoCameraMovePrompt: metadata?.videoCameraMovePrompt,
|
|
|
|
|
@@ -343,8 +355,8 @@ export async function hydrateCanvasImages(nodes: CanvasNodeData[]) {
|
|
|
|
|
nodes.map(async (node) => {
|
|
|
|
|
const content = node.metadata?.content;
|
|
|
|
|
if ((node.type === CanvasNodeType.Video || node.type === CanvasNodeType.Audio) && node.metadata?.storageKey) return { ...node, metadata: { ...node.metadata, content: await resolveMediaUrl(node.metadata.storageKey, content) } };
|
|
|
|
|
if (node.type === CanvasNodeType.Image && node.metadata?.storageKey) return { ...node, metadata: { ...node.metadata, content: await resolveImageUrl(node.metadata.storageKey, content, { cacheMiss: true }) } };
|
|
|
|
|
if (node.type !== CanvasNodeType.Image || !content) return node;
|
|
|
|
|
if (node.metadata?.storageKey) return { ...node, metadata: { ...node.metadata, content: await resolveImageUrl(node.metadata.storageKey, content, { cacheMiss: true }) } };
|
|
|
|
|
if (!content.startsWith("data:image/")) return node;
|
|
|
|
|
return { ...node, metadata: { ...node.metadata, ...imageMetadata(await uploadImage(content)) } };
|
|
|
|
|
}),
|
|
|
|
|
@@ -377,19 +389,52 @@ export function getGenerationCount(count: string) {
|
|
|
|
|
return Math.max(1, Math.min(15, Math.floor(Math.abs(Number(count)) || 1)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function generationWorkflowMetadata(config: AiConfig): Pick<CanvasNodeMetadata, "workflowProvider" | "runningHubWorkflowId" | "runningHubWorkflowKind" | "comfyBridgeWorkflowId"> {
|
|
|
|
|
const provider = config.taskWorkflowProvider || "model";
|
|
|
|
|
return {
|
|
|
|
|
workflowProvider: provider,
|
|
|
|
|
runningHubWorkflowId: provider === "runninghub" ? config.runningHub.workflowId : undefined,
|
|
|
|
|
runningHubWorkflowKind: provider === "runninghub" ? config.runningHub.selectedKind : undefined,
|
|
|
|
|
comfyBridgeWorkflowId: provider === "comfyui" ? config.comfyBridge.workflowId : undefined,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function buildGenerationConfig(config: AiConfig, node: CanvasNodeData | undefined, mode: CanvasNodeGenerationMode, requirements?: ModelRequirements): AiConfig {
|
|
|
|
|
// 只有独立 Config 节点读取工作流元数据;普通图片/视频/音频节点始终按基础模型生成。
|
|
|
|
|
const workflowProvider = mode !== "text" && node?.type === CanvasNodeType.Config ? resolveCanvasWorkflowProvider(node.metadata) : "model";
|
|
|
|
|
const defaultModel = mode === "image" ? config.imageModel : mode === "video" ? config.videoModel : mode === "audio" ? config.audioModel : config.textModel;
|
|
|
|
|
const fallbackModel = mode === "image" ? defaultConfig.imageModel : mode === "video" ? defaultConfig.videoModel : mode === "audio" ? defaultConfig.audioModel : defaultConfig.textModel;
|
|
|
|
|
const storedModel = resolveCanvasGenerationModel(config, node?.metadata?.model, mode);
|
|
|
|
|
const preferredModel = storedModel || resolveCanvasGenerationModel(config, defaultModel, mode) || fallbackModel;
|
|
|
|
|
// 先合并节点上的实时选择,再做兼容性匹配。否则路由只看到全局默认值,节点改过的时长、分辨率或布尔能力无法参与分流。
|
|
|
|
|
const workflowParameters = node?.metadata?.workflowParameters || {};
|
|
|
|
|
const runningHubWorkflowId = node?.metadata?.runningHubWorkflowId?.trim() || config.runningHub.workflowId.trim();
|
|
|
|
|
const selectedRunningHubWorkflow = workflowProvider === "runninghub"
|
|
|
|
|
? config.runningHub.workflows.find((item) => item.workflowId.trim() === runningHubWorkflowId && (!node?.metadata?.runningHubWorkflowKind || (item.kind === "app" ? "app" : "workflow") === node.metadata.runningHubWorkflowKind))
|
|
|
|
|
: undefined;
|
|
|
|
|
const comfyBridgeWorkflowId = node?.metadata?.comfyBridgeWorkflowId?.trim() || config.comfyBridge.workflowId.trim();
|
|
|
|
|
const selectedComfyBridgeWorkflow = workflowProvider === "comfyui"
|
|
|
|
|
? config.comfyBridge.workflows.find((item) => item.workflowId.trim() === comfyBridgeWorkflowId)
|
|
|
|
|
: undefined;
|
|
|
|
|
const selectedWorkflowFields = workflowProvider === "runninghub"
|
|
|
|
|
? selectedRunningHubWorkflow?.fields?.length ? selectedRunningHubWorkflow.fields : workflowVideoFieldsFromJson(selectedRunningHubWorkflow?.workflowJson)
|
|
|
|
|
: workflowProvider === "comfyui"
|
|
|
|
|
? selectedComfyBridgeWorkflow?.fields?.length ? selectedComfyBridgeWorkflow.fields : workflowVideoFieldsFromJson(selectedComfyBridgeWorkflow?.workflowJson)
|
|
|
|
|
: [];
|
|
|
|
|
const workflowOutputSize = workflowProvider === "model" ? "" : workflowOutputSizeValue(selectedWorkflowFields, workflowParameters);
|
|
|
|
|
const workflowParameterValue = (source: string) => {
|
|
|
|
|
const value = workflowParameters[`source:${source}`];
|
|
|
|
|
return value === undefined || value === null ? undefined : String(value);
|
|
|
|
|
};
|
|
|
|
|
const requestedConfig: AiConfig = {
|
|
|
|
|
...config,
|
|
|
|
|
quality: node?.metadata?.quality ?? config.quality ?? defaultConfig.quality,
|
|
|
|
|
size: node?.metadata?.size ?? config.size ?? defaultConfig.size,
|
|
|
|
|
quality: node?.metadata?.quality ?? workflowParameterValue("quality") ?? config.quality ?? defaultConfig.quality,
|
|
|
|
|
size: workflowProvider === "model"
|
|
|
|
|
? node?.metadata?.size ?? config.size ?? defaultConfig.size
|
|
|
|
|
: workflowOutputSize || node?.metadata?.size || config.size || defaultConfig.size,
|
|
|
|
|
transparentBackground: node?.metadata?.transparentBackground ?? config.transparentBackground ?? defaultConfig.transparentBackground,
|
|
|
|
|
videoSeconds: normalizeVideoDuration(node?.metadata?.seconds ?? config.videoSeconds ?? defaultConfig.videoSeconds),
|
|
|
|
|
vquality: normalizeVideoResolution(node?.metadata?.vquality ?? config.vquality ?? defaultConfig.vquality),
|
|
|
|
|
videoSeconds: workflowProvider === "model" ? normalizeVideoDuration(node?.metadata?.seconds ?? workflowParameterValue("videoSeconds") ?? config.videoSeconds ?? defaultConfig.videoSeconds) : String(node?.metadata?.seconds ?? workflowParameterValue("videoSeconds") ?? config.videoSeconds ?? defaultConfig.videoSeconds),
|
|
|
|
|
vquality: workflowProvider === "model" ? normalizeVideoResolution(node?.metadata?.vquality ?? workflowParameterValue("vquality") ?? config.vquality ?? defaultConfig.vquality) : String(node?.metadata?.vquality ?? workflowParameterValue("vquality") ?? config.vquality ?? defaultConfig.vquality),
|
|
|
|
|
videoGenerateAudio: node?.metadata?.generateAudio ?? config.videoGenerateAudio ?? defaultConfig.videoGenerateAudio,
|
|
|
|
|
videoWatermark: node?.metadata?.watermark ?? config.videoWatermark ?? defaultConfig.videoWatermark,
|
|
|
|
|
videoArkPrivateAssetUpload: node?.metadata?.arkPrivateAssetUpload ?? config.videoArkPrivateAssetUpload ?? defaultConfig.videoArkPrivateAssetUpload,
|
|
|
|
|
@@ -402,42 +447,53 @@ export function buildGenerationConfig(config: AiConfig, node: CanvasNodeData | u
|
|
|
|
|
const imageSize = mode === "image" ? requestedConfig.size : undefined;
|
|
|
|
|
// 无 requirements 的调用(重试、媒体工具等)也按当前能力与尺寸路由到组内最低价兼容模型,
|
|
|
|
|
// 避免旧 metadata.model 不支持当前尺寸导致生成时被 normalize 回退。
|
|
|
|
|
const liveOptions = modelRequestOptions(requestedConfig, mode);
|
|
|
|
|
const baseRequirements = requirements?.capability
|
|
|
|
|
? { ...requirements, options: { ...liveOptions, ...(requirements.options || {}) } }
|
|
|
|
|
: { capability: mode, options: liveOptions };
|
|
|
|
|
const model = resolveCompatibleModel(config, preferredModel, imageSize ? { ...baseRequirements, imageSize } : baseRequirements) || preferredModel;
|
|
|
|
|
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,
|
|
|
|
|
const liveOptions = modelRequestOptions(requestedConfig, mode);
|
|
|
|
|
const baseRequirements = requirements?.capability
|
|
|
|
|
? { ...requirements, options: { ...liveOptions, ...(requirements.options || {}) } }
|
|
|
|
|
: { capability: mode, options: liveOptions };
|
|
|
|
|
const model = workflowProvider === "model"
|
|
|
|
|
? resolveCompatibleModel(config, preferredModel, imageSize ? { ...baseRequirements, imageSize } : baseRequirements) || preferredModel
|
|
|
|
|
: preferredModel;
|
|
|
|
|
const generationDefaults: Partial<ModelGenerationDefaults> = workflowProvider === "model"
|
|
|
|
|
? 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,
|
|
|
|
|
}
|
|
|
|
|
: {
|
|
|
|
|
size: node?.metadata?.size,
|
|
|
|
|
videoSeconds: node?.metadata?.seconds,
|
|
|
|
|
vquality: node?.metadata?.vquality,
|
|
|
|
|
videoGenerateAudio: node?.metadata?.generateAudio,
|
|
|
|
|
videoWatermark: node?.metadata?.watermark,
|
|
|
|
|
videoSeconds: requestedConfig.videoSeconds,
|
|
|
|
|
vquality: requestedConfig.vquality,
|
|
|
|
|
videoGenerateAudio: requestedConfig.videoGenerateAudio,
|
|
|
|
|
videoWatermark: requestedConfig.videoWatermark,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
size: requestedConfig.size,
|
|
|
|
|
quality: requestedConfig.quality,
|
|
|
|
|
transparentBackground: requestedConfig.transparentBackground,
|
|
|
|
|
count: requestedConfig.count,
|
|
|
|
|
videoSeconds: requestedConfig.videoSeconds,
|
|
|
|
|
vquality: requestedConfig.vquality,
|
|
|
|
|
videoGenerateAudio: requestedConfig.videoGenerateAudio,
|
|
|
|
|
videoWatermark: requestedConfig.videoWatermark,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
)
|
|
|
|
|
: {};
|
|
|
|
|
const modeCapability = mode === "video" || mode === "audio" ? mode : "image";
|
|
|
|
|
const runningHubCapability = normalizeRunningHubCapability(selectedRunningHubWorkflow?.capability, normalizeRunningHubCapability(config.runningHub.capability));
|
|
|
|
|
const runningHub = { ...config.runningHub, enabled: workflowProvider === "runninghub" && config.runningHub.enabled, selectedKind: selectedRunningHubWorkflow?.kind === "app" ? "app" as const : "workflow" as const, workflowId: runningHubWorkflowId, capability: runningHubCapability, workflows: workflowProvider === "runninghub" ? config.runningHub.workflows.map((item) => item.workflowId.trim() === runningHubWorkflowId && (!node?.metadata?.runningHubWorkflowKind || (item.kind === "app" ? "app" : "workflow") === node.metadata.runningHubWorkflowKind) ? { ...item, fields: applyWorkflowParameterValues(item.fields?.length ? item.fields : workflowVideoFieldsFromJson(item.workflowJson) as WorkflowFieldMapping[], workflowParameters) } : item) : config.runningHub.workflows };
|
|
|
|
|
const comfyBridge = { ...config.comfyBridge, enabled: workflowProvider === "comfyui" && config.comfyBridge.enabled, workflowId: comfyBridgeWorkflowId, capability: selectedComfyBridgeWorkflow?.capability || modeCapability, workflows: workflowProvider === "comfyui" ? config.comfyBridge.workflows.map((item) => item.workflowId.trim() === comfyBridgeWorkflowId ? { ...item, fields: applyWorkflowParameterValues(item.fields?.length ? item.fields : workflowVideoFieldsFromJson(item.workflowJson) as WorkflowFieldMapping[], workflowParameters) } : item) : config.comfyBridge.workflows };
|
|
|
|
|
return {
|
|
|
|
|
...requestedConfig,
|
|
|
|
|
taskWorkflowProvider: workflowProvider,
|
|
|
|
|
runningHub,
|
|
|
|
|
comfyBridge,
|
|
|
|
|
model,
|
|
|
|
|
quality: generationDefaults.quality || requestedConfig.quality,
|
|
|
|
|
size: generationDefaults.size || requestedConfig.size,
|
|
|
|
|
@@ -446,6 +502,7 @@ export function buildGenerationConfig(config: AiConfig, node: CanvasNodeData | u
|
|
|
|
|
vquality: generationDefaults.vquality || requestedConfig.vquality,
|
|
|
|
|
videoGenerateAudio: generationDefaults.videoGenerateAudio || requestedConfig.videoGenerateAudio,
|
|
|
|
|
videoWatermark: generationDefaults.videoWatermark || requestedConfig.videoWatermark,
|
|
|
|
|
videoArkPrivateAssetUpload: requestedConfig.videoArkPrivateAssetUpload,
|
|
|
|
|
count: generationDefaults.count || requestedConfig.count,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
@@ -457,6 +514,28 @@ export function resolveCanvasGenerationModel(config: AiConfig, model: string | u
|
|
|
|
|
return configuredModelMatchesCapability(config, normalized, mode) ? normalized : "";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function applyWorkflowParameterValues(fields: WorkflowFieldMapping[] | undefined, values: Record<string, unknown>) {
|
|
|
|
|
if (!fields?.length || !Object.keys(values).length) return fields;
|
|
|
|
|
return fields.map((field) => {
|
|
|
|
|
const key = workflowFieldKey(field);
|
|
|
|
|
const randomKey = workflowFieldRandomKey(field);
|
|
|
|
|
const hasValue = workflowFieldHasStoredValue(field, values);
|
|
|
|
|
const hasRandom = Object.prototype.hasOwnProperty.call(values, randomKey);
|
|
|
|
|
if (!hasValue && !hasRandom) return field;
|
|
|
|
|
const value = workflowFieldSubmissionValue(field, workflowFieldCurrentValue(field, values));
|
|
|
|
|
// 画布动态参数按具体字段覆盖,不能继续走 aspectRatio/quality 等全局来源,
|
|
|
|
|
// 否则同一工作流中的两个同类参数会被折叠成同一个值。
|
|
|
|
|
return {
|
|
|
|
|
...field,
|
|
|
|
|
...(hasValue ? { fieldValue: value, value } : {}),
|
|
|
|
|
source: "",
|
|
|
|
|
sourceAutomatic: false,
|
|
|
|
|
sourceFromUpstream: false,
|
|
|
|
|
randomEnabled: hasRandom ? values[randomKey] === true : hasValue ? false : field.randomEnabled,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function supportsVideoReferenceAudio(config: AiConfig) {
|
|
|
|
|
const interfaceType = resolveModelRequestConfig(config, config.model).interfaceType;
|
|
|
|
|
return interfaceType === "newapi-channel-1" || interfaceType === "newapi-channel-2" || isSeedanceVideoConfig(config);
|
|
|
|
|
|