From 2dddbd51b68d50ba50602a9367ad032256f23940 Mon Sep 17 00:00:00 2001 From: TianYun Date: Fri, 12 Jun 2026 17:14:59 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=E4=B8=8D=E5=BF=85=E8=A6=81?= =?UTF-8?q?=E7=9A=84=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/generate/providers/newapiwg.ts | 5 +- src/app/api/generate/providers/popiserver.ts | 112 ++---------------- src/components/WorkflowCanvas.tsx | 20 ---- src/components/nodes/Generate3DNode.tsx | 17 +-- src/components/nodes/GenerateAudioNode.tsx | 87 +------------- src/components/nodes/GenerateImageNode.tsx | 11 +- src/components/nodes/GenerateVideoNode.tsx | 43 ++----- src/components/nodes/ModelParameters.tsx | 3 +- src/hooks/useDynamicModelInputEdges.ts | 79 ------------ src/store/execution/generate3dExecutor.ts | 6 - src/store/execution/generateAudioExecutor.ts | 13 +- src/store/execution/generateVideoExecutor.ts | 87 ++------------ src/store/execution/nanoBananaExecutor.ts | 17 +-- .../utils/__tests__/connectedInputs.test.ts | 9 +- src/store/utils/connectedInputs.ts | 54 --------- src/store/workflowStore.ts | 56 --------- src/types/nodes.ts | 8 +- src/utils/generationConfig.ts | 5 - src/utils/generationInputSupport.ts | 12 +- src/utils/nodeHandles.ts | 36 +----- 20 files changed, 53 insertions(+), 627 deletions(-) delete mode 100644 src/hooks/useDynamicModelInputEdges.ts diff --git a/src/app/api/generate/providers/newapiwg.ts b/src/app/api/generate/providers/newapiwg.ts index f38bf28c..c6433359 100644 --- a/src/app/api/generate/providers/newapiwg.ts +++ b/src/app/api/generate/providers/newapiwg.ts @@ -883,8 +883,8 @@ function normalizeNewApiWGVideoInputs( const voices: string[] = []; const shouldUseViduReferenceVideoField = isViduVideoModel(input.model.id); const selectedModel = toSelectedModel(input.model); - const supportsVideoReference = modelSupportsReferenceMedia(selectedModel, "video", undefined, input.parameters); - const supportsAudioReference = modelSupportsReferenceMedia(selectedModel, "audio", undefined, input.parameters); + const supportsVideoReference = modelSupportsReferenceMedia(selectedModel, "video", input.parameters); + const supportsAudioReference = modelSupportsReferenceMedia(selectedModel, "audio", input.parameters); for (const [key, value] of Object.entries(dynamicInputs)) { if (NEWAPIWG_VIDEO_IMAGE_INPUTS.has(key)) { @@ -941,7 +941,6 @@ function sanitizeNewApiWGVideoParameters( normalized.referenceSubjectList = filterReferenceSubjectList( normalized.referenceSubjectList, toSelectedModel(model), - undefined, parameters ); if (Array.isArray(normalized.referenceSubjectList) && normalized.referenceSubjectList.length === 0) { diff --git a/src/app/api/generate/providers/popiserver.ts b/src/app/api/generate/providers/popiserver.ts index 559e928a..94a1b17b 100644 --- a/src/app/api/generate/providers/popiserver.ts +++ b/src/app/api/generate/providers/popiserver.ts @@ -14,31 +14,6 @@ const POPISERVER_TASK_CREATE_PATH = "/api_client/anime/task/create"; const POPISERVER_TASK_LIST_PATH = "/api_client/anime/task/list"; const POPISERVER_MEDIA_UPLOAD_PATH = "/api_client/media/upload"; -const POPISERVER_IMAGE_DYNAMIC_KEYS = [ - "image", - "images", - "image_url", - "image_urls", - "first_frame", - "first_frame_url", - "last_frame", - "last_frame_url", - "reference_image", - "reference_image_url", - "reference_image_urls", -]; - -const POPISERVER_VIDEO_DYNAMIC_KEYS = [ - "video", - "videos", - "video_url", - "video_urls", -]; - -const POPISERVER_VOICE_DYNAMIC_KEYS = [ - "voices", -]; - const POPISERVER_LEGACY_AUDIO_KEYS = [ "audio", "audios", @@ -49,17 +24,6 @@ const POPISERVER_LEGACY_AUDIO_KEYS = [ "reference_audio_urls", ]; -const POPISERVER_VOICE_ID_DYNAMIC_KEYS = [ - "voiceId", - "voice_id", -]; - -const POPISERVER_MEDIA_DYNAMIC_KEYS = [ - ...POPISERVER_IMAGE_DYNAMIC_KEYS, - ...POPISERVER_VIDEO_DYNAMIC_KEYS, - ...POPISERVER_VOICE_DYNAMIC_KEYS, -]; - type PopiserverResponse = { status?: string; message?: string; @@ -162,37 +126,6 @@ function collectStrings(values: unknown[]): string[] { return items; } -function stringArray(value: string | string[] | undefined): string[] { - if (typeof value === "string") return value.length > 0 ? [value] : []; - if (Array.isArray(value)) return value.filter((item) => typeof item === "string" && item.length > 0); - return []; -} - -function firstDynamicString( - dynamicInputs: Record | undefined, - keys: string[] -): string | null { - if (!dynamicInputs) return null; - for (const key of keys) { - const value = dynamicInputs[key]; - const found = firstString(Array.isArray(value) ? value : [value]); - if (found) return found; - } - return null; -} - -function collectDynamicMedia( - dynamicInputs: Record | undefined, - keys: string[] -): string[] { - if (!dynamicInputs) return []; - const items: string[] = []; - for (const key of keys) { - items.push(...stringArray(dynamicInputs[key])); - } - return items; -} - function mediaResourceUrl(media: PopiUploadedMedia): string | null { return media.url || media.originUrl || media.path || media.originPath || null; } @@ -327,9 +260,6 @@ async function preparePopiTaskMedia( token: string, input: GenerationInput ): Promise { - const dynamicInputs = input.dynamicInputs - ? { ...input.dynamicInputs } - : undefined; const mediaValues: string[] = []; const assignments: Array<(uploaded: string) => void> = []; @@ -357,31 +287,12 @@ async function preparePopiTaskMedia( }); }); - if (dynamicInputs) { - for (const key of POPISERVER_MEDIA_DYNAMIC_KEYS) { - const value = dynamicInputs[key]; - if (typeof value === "string") { - collect(value, (uploaded) => { - dynamicInputs[key] = uploaded; - }); - } else if (Array.isArray(value)) { - const values = [...value]; - values.forEach((item, index) => { - collect(item, (uploaded) => { - values[index] = uploaded; - dynamicInputs[key] = values; - }); - }); - } - } - } - const uploadedValues = mediaValues.length > 0 ? await uploadPopiMediaBatch(request, token, mediaValues) : []; uploadedValues.forEach((uploaded, index) => assignments[index]?.(uploaded)); - return { ...input, images, videos, voices, dynamicInputs }; + return { ...input, images, videos, voices }; } function getCapabilityMediaType(capabilities: ModelCapability[]): "image" | "video" | "audio" { @@ -571,28 +482,19 @@ function buildPopiTaskBody(input: GenerationInput, modelDetail: PopiAIModel | nu asNumber(input.model.metadata?.aiModelId) ?? asNumber(input.model.id) ?? modelDetail?.id; - const images = [ - ...(input.images || []), - ...collectDynamicMedia(input.dynamicInputs, POPISERVER_IMAGE_DYNAMIC_KEYS), - ]; - const videos = modelSupportsReferenceMedia(selectedModel, "video", undefined, rawParameters) - ? [ - ...(input.videos || []), - ...collectDynamicMedia(input.dynamicInputs, POPISERVER_VIDEO_DYNAMIC_KEYS), - ] + const images = input.images || []; + const videos = modelSupportsReferenceMedia(selectedModel, "video", rawParameters) + ? input.videos || [] : []; - const voices = modelSupportsReferenceMedia(selectedModel, "audio", undefined, rawParameters) - ? [ - ...(input.voices || []), - ...collectDynamicMedia(input.dynamicInputs, POPISERVER_VOICE_DYNAMIC_KEYS), - ] + const voices = modelSupportsReferenceMedia(selectedModel, "audio", rawParameters) + ? input.voices || [] : []; const referenceSubjectList = type === 1 || type === 2 ? buildReferenceSubjectList([...new Set(images)], [...new Set(videos)], [...new Set(voices)], rawParameters) : []; const voiceId = - firstDynamicString(input.dynamicInputs, POPISERVER_VOICE_ID_DYNAMIC_KEYS) ?? asString(parameters.voiceId) ?? + asString(parameters.voice_id) ?? ""; const commonBody = { diff --git a/src/components/WorkflowCanvas.tsx b/src/components/WorkflowCanvas.tsx index 2b5db88e..e6f0649e 100644 --- a/src/components/WorkflowCanvas.tsx +++ b/src/components/WorkflowCanvas.tsx @@ -365,7 +365,6 @@ function buildMultiAngleNodeData(sourceImage: string, settings: MultiAngleSettin model: getLegacyImageModel(DEFAULT_MULTI_ANGLE_IMAGE_MODEL), selectedModel: DEFAULT_MULTI_ANGLE_IMAGE_MODEL, parameters: {}, - inputSchema: undefined, useGoogleSearch: false, useImageSearch: false, status: "idle", @@ -1220,24 +1219,6 @@ export function WorkflowCanvas() { } return capability.outputs.includes(SINGLE_OUTPUT_HANDLE_ID) ? SINGLE_OUTPUT_HANDLE_ID : null; - // Check for dynamic inputSchema first - const nodeData = node.data as { inputSchema?: Array<{ name: string; type: string }> }; - const inputSchema = nodeData.inputSchema ?? []; - if (inputSchema.length > 0) { - if (needInput) { - // Find input handles matching the type - const matchingInputs = inputSchema.filter(i => i.type === handleType); - const numHandles = matchingInputs.length; - if (numHandles > 0) { - return handleType; - } - } - // Output handle - check for video, 3d, or image type - if (handleType === "video") return "video"; - if (handleType === "3d") return "3d"; - return handleType === "image" ? "image" : null; - } - // VideoStitch has dynamic indexed video input handles (video-0, video-1, ...) if (node.type === "videoStitch" && needInput && handleType === "video") { for (let i = 0; i < 50; i++) { @@ -3327,7 +3308,6 @@ export function WorkflowCanvas() { updateNodeData(fallbackDialogState.nodeId, { selectedModel, parameters: {}, - inputSchema: undefined, fallbackModel: undefined, fallbackParameters: undefined, status: currentData?.status === "error" ? "idle" : currentData?.status, diff --git a/src/components/nodes/Generate3DNode.tsx b/src/components/nodes/Generate3DNode.tsx index ceaa703c..61afae54 100644 --- a/src/components/nodes/Generate3DNode.tsx +++ b/src/components/nodes/Generate3DNode.tsx @@ -5,7 +5,7 @@ import { Handle, Position, NodeProps, Node, useReactFlow } from "@xyflow/react"; import { BaseNode } from "./BaseNode"; import { ModelParameters } from "./ModelParameters"; import { useWorkflowStore, useProviderApiKeys } from "@/store/workflowStore"; -import { Generate3DNodeData, ProviderType, SelectedModel, ModelInputDef } from "@/types"; +import { Generate3DNodeData, ProviderType, SelectedModel } from "@/types"; import { ProviderModel, ModelCapability } from "@/lib/providers/types"; import { ModelSearchDialog } from "@/components/modals/ModelSearchDialog"; import { useToast } from "@/components/Toast"; @@ -65,21 +65,12 @@ export function Generate3DNode({ id, data, selected }: NodeProps { - updateNodeData(id, { inputSchema: inputs }); - }, - [id, updateNodeData] - ); - const modelSchema = useSelectedModelSchema({ selectedModel: nodeData.selectedModel, enabled: Boolean(nodeData.selectedModel?.modelId), refreshKey: nodeData.modelSchemaRequestId, - onLoaded: ({ inputs, parameters }) => { + onLoaded: ({ parameters }) => { updateNodeData(id, { - inputSchema: inputs, parameterSchema: parameters, modelSchemaError: null, }); @@ -240,8 +231,8 @@ export function Generate3DNode({ id, data, selected }: NodeProps { - const imageInputs = nodeData.inputSchema!.filter(i => i.type === "image"); - const textInputs = nodeData.inputSchema!.filter(i => i.type === "text"); + const imageInputs: Array<{ name: string; label: string; description?: string }> = []; + const textInputs: Array<{ name: string; label: string; description?: string }> = []; const hasImageInput = imageInputs.length > 0; const hasTextInput = textInputs.length > 0; diff --git a/src/components/nodes/GenerateAudioNode.tsx b/src/components/nodes/GenerateAudioNode.tsx index 9182009c..35ce7ab5 100644 --- a/src/components/nodes/GenerateAudioNode.tsx +++ b/src/components/nodes/GenerateAudioNode.tsx @@ -6,13 +6,12 @@ import { BaseNode } from "./BaseNode"; import { ProviderBadge } from "./ProviderBadge"; import { ModelParameters } from "./ModelParameters"; import { useWorkflowStore } from "@/store/workflowStore"; -import { GenerateAudioNodeData, ProviderType, ModelInputDef, SelectedModel } from "@/types"; +import { GenerateAudioNodeData, ProviderType, SelectedModel } from "@/types"; import { ProviderModel } from "@/lib/providers/types"; import { ModelSearchDialog } from "@/components/modals/ModelSearchDialog"; import { useAudioVisualization } from "@/hooks/useAudioVisualization"; import { useAudioPlayback } from "@/hooks/useAudioPlayback"; import { useInlineParameters } from "@/hooks/useInlineParameters"; -import { useDynamicModelInputEdges } from "@/hooks/useDynamicModelInputEdges"; import { useSelectedModelSchema } from "@/hooks/useSelectedModelSchema"; import { InlineParameterPanel } from "./InlineParameterPanel"; import { SettingsTabBar } from "./SettingsTabBar"; @@ -83,8 +82,6 @@ export function GenerateAudioNodeView({ const { inlineParametersEnabled } = useInlineParameters(); const showInlineNodeSettings = false; const showLabels = useShowHandleLabels(selected); - useDynamicModelInputEdges(id, nodeData.inputSchema, nodeConfig.selectedModel, nodeConfig.parameters); - // Register browse callback for floating header button useEffect(() => { browseRegistry.register(id, () => setIsBrowseDialogOpen(true)); @@ -109,7 +106,7 @@ export function GenerateAudioNodeView({ } updateNodeData(id, { ...buildGenerationModelSwitchPatch(nodeData, selectedModel), - inputSchema: undefined, parameterSchema: undefined, modelSchemaRequestId: Date.now(), + parameterSchema: undefined, modelSchemaRequestId: Date.now(), }); }, [id, nodeConfig.selectedModel, nodeData, popiProviderMode, popiserverAudioModels, updateNodeData]); @@ -156,7 +153,7 @@ export function GenerateAudioNodeView({ !popiProviderMode || popiserverAudioModels.some((model) => model.id === nodeConfig.selectedModel?.modelId) ), refreshKey: nodeData.modelSchemaRequestId, - onLoaded: ({ inputs, parameters, model }) => { + onLoaded: ({ parameters, model }) => { const selectedModelPatch = model && shouldUpdateSelectedModelFromProviderModel(nodeConfig.selectedModel, model) ? buildGenerationSelectedModelPatch(nodeConfig, toSelectedModel(model)) : {}; @@ -168,7 +165,6 @@ export function GenerateAudioNodeView({ String(model?.metadata?.aiModelCodeAlias ?? nodeConfig.selectedModel?.metadata?.aiModelCodeAlias ?? "") ); updateNodeData(id, { - inputSchema: inputs, parameterSchema: nextParameterSchema, ...selectedModelPatch, modelSchemaError: null, @@ -336,7 +332,6 @@ export function GenerateAudioNodeView({ } else { updateNodeData(id, { ...buildGenerationModelSwitchPatch(nodeData, newSelectedModel), - inputSchema: undefined, parameterSchema: undefined, modelSchemaRequestId: Date.now(), }); @@ -359,81 +354,6 @@ export function GenerateAudioNodeView({ updateNodeData(id, { parametersExpanded: !isParamsExpanded }); }, [id, isParamsExpanded, updateNodeData]); - // Dynamic handles based on inputSchema - const dynamicHandles = useMemo(() => { - if (!nodeData.inputSchema || nodeData.inputSchema.length === 0) return null; - - const typeOrder = ["image", "video", "audio", "text"] as const; - const handles = typeOrder - .map((handleType) => { - const inputs = nodeData.inputSchema!.filter((input) => input.type === handleType); - if (inputs.length === 0) return null; - const firstInput = inputs[0]; - return { - id: handleType, - type: handleType, - label: handleType === "image" - ? t("node.image") - : handleType === "video" - ? t("toolbar.video") - : handleType === "audio" - ? t("modelSearch.audio") - : t("node.prompt"), - schemaName: firstInput.name, - description: firstInput.description || firstInput.label, - count: inputs.length, - }; - }) - .filter((handle): handle is NonNullable => handle !== null); - - const getHandleColor = (type: string) => { - if (type === "image") return "var(--handle-color-image)"; - if (type === "video") return "var(--handle-color-video)"; - if (type === "audio") return "var(--handle-color-audio)"; - return "var(--handle-color-text)"; - }; - - const indexedCompatHandles = handles.flatMap((handle, index) => - Array.from({ length: handle.count }, (_, inputIndex) => ({ - id: `${handle.type}-${inputIndex}`, - topPercent: ((index + 1) / (handles.length + 1)) * 100, - })) - ); - - return ( - <> - {handles.map((handle, index) => { - const topPercent = ((index + 1) / (handles.length + 1)) * 100; - return ( - - - - - ); - })} - {indexedCompatHandles.map((handle) => ( - - ))} - - ); - }, [nodeData.inputSchema, showLabels, t]); - return ( <> )} - {false && dynamicHandles} readImageGenerationConfig(nodeData, defaultSelectedModel), [defaultSelectedModel, nodeData] ); - useDynamicModelInputEdges(id, nodeData.inputSchema, nodeConfig.selectedModel, nodeConfig.parameters); - // Get the current selected provider const currentProvider: ProviderType = nodeConfig.selectedModel?.provider || (popiProviderMode ? POPI_PROVIDER_ID : "gemini"); const modelOptions = currentProvider === POPI_PROVIDER_ID ? popiserverImageModels : externalModels; @@ -285,7 +282,7 @@ export function GenerateImageNodeView({ id, data, selected }: GenerateImageNodeV if (!selectedModel || selectedModel.modelId === nodeConfig.selectedModel?.modelId) return; updateNodeData(id, { ...buildGenerationModelSwitchPatch(nodeData, selectedModel), - inputSchema: undefined, parameterSchema: undefined, modelSchemaRequestId: Date.now(), + parameterSchema: undefined, modelSchemaRequestId: Date.now(), }); }, [id, currentProvider, modelOptions, nodeConfig.selectedModel, nodeData, updateNodeData]); @@ -525,7 +522,6 @@ export function GenerateImageNodeView({ id, data, selected }: GenerateImageNodeV } else { updateNodeData(id, { ...buildGenerationModelSwitchPatch(nodeData, newSelectedModel), - inputSchema: undefined, parameterSchema: undefined, modelSchemaRequestId: Date.now(), }); @@ -575,12 +571,11 @@ export function GenerateImageNodeView({ id, data, selected }: GenerateImageNodeV selectedModel: nodeConfig.selectedModel, enabled: shouldFetchPrimaryModelSchema, refreshKey: nodeData.modelSchemaRequestId, - onLoaded: ({ inputs, parameters, model }) => { + onLoaded: ({ parameters, model }) => { const selectedModelPatch = model && shouldUpdateSelectedModelFromProviderModel(nodeConfig.selectedModel, model) ? buildGenerationSelectedModelPatch(nodeConfig, toSelectedModel(model)) : {}; updateNodeData(id, { - inputSchema: inputs, parameterSchema: parameters, ...selectedModelPatch, modelSchemaError: null, diff --git a/src/components/nodes/GenerateVideoNode.tsx b/src/components/nodes/GenerateVideoNode.tsx index c1211583..a9c60104 100644 --- a/src/components/nodes/GenerateVideoNode.tsx +++ b/src/components/nodes/GenerateVideoNode.tsx @@ -7,7 +7,7 @@ import { BaseNode } from "./BaseNode"; import { ModelParameters } from "./ModelParameters"; import { useWorkflowStore, useProviderApiKeys } from "@/store/workflowStore"; import { deduplicatedFetch } from "@/utils/deduplicatedFetch"; -import { GenerateVideoNodeData, ProviderType, SelectedModel, ModelInputDef } from "@/types"; +import { GenerateVideoNodeData, ProviderType, SelectedModel } from "@/types"; import { ProviderModel, ModelCapability } from "@/lib/providers/types"; import { ModelSearchDialog } from "@/components/modals/ModelSearchDialog"; import { useToast } from "@/components/Toast"; @@ -16,7 +16,6 @@ import { ProviderBadge } from "./ProviderBadge"; import { useVideoBlobUrl } from "@/hooks/useVideoBlobUrl"; import { useVideoAutoplay } from "@/hooks/useVideoAutoplay"; import { useInlineParameters } from "@/hooks/useInlineParameters"; -import { useDynamicModelInputEdges } from "@/hooks/useDynamicModelInputEdges"; import { useSelectedModelSchema } from "@/hooks/useSelectedModelSchema"; import { InlineParameterPanel } from "./InlineParameterPanel"; import { SettingsTabBar } from "./SettingsTabBar"; @@ -62,26 +61,6 @@ function isSameSelectedModel(a: SelectedModel | undefined, b: SelectedModel | un return Boolean(a?.provider && b?.provider && a.provider === b.provider && a.modelId === b.modelId); } -/** Returns true for Gemini-native Veo video models */ -function isVeoModel(modelId: string | undefined): boolean { - if (!modelId) return false; - return modelId.startsWith("veo-"); -} - -/** Build the hardcoded inputSchema for a Veo model, or undefined for non-Veo */ -function buildVeoInputSchema(modelId: string): ModelInputDef[] | undefined { - if (!isVeoModel(modelId)) return undefined; - const isI2V = modelId.includes("image-to-video"); - const inputs: ModelInputDef[] = [ - { name: "prompt", type: "text", required: true, label: "Prompt" }, - { name: "negative_prompt", type: "text", required: false, label: "Neg. Prompt" }, - ]; - if (isI2V) { - inputs.unshift({ name: "image", type: "image", required: true, label: "Image" }); - } - return inputs; -} - type GenerateVideoNodeType = Node; export interface GenerateVideoNodeViewProps { @@ -203,8 +182,6 @@ export function GenerateVideoNodeView({ id, data, selected }: GenerateVideoNodeV () => readVideoGenerationConfig(nodeData, defaultSelectedModel), [defaultSelectedModel, nodeData] ); - useDynamicModelInputEdges(id, nodeData.inputSchema, nodeConfig.selectedModel, nodeConfig.parameters); - const currentProvider: ProviderType = nodeConfig.selectedModel?.provider || (popiProviderMode ? POPI_PROVIDER_ID : "fal"); const modelOptions = currentProvider === POPI_PROVIDER_ID ? popiserverVideoModels : externalModels; @@ -301,7 +278,7 @@ export function GenerateVideoNodeView({ id, data, selected }: GenerateVideoNodeV resolution: undefined, durationSeconds: undefined, soundEnabled: getDefaultVideoSoundEnabled(selectedModel), - inputSchema: undefined, parameterSchema: undefined, modelSchemaRequestId: Date.now(), + parameterSchema: undefined, modelSchemaRequestId: Date.now(), }); }, [ id, @@ -336,7 +313,6 @@ export function GenerateVideoNodeView({ id, data, selected }: GenerateVideoNodeV durationSeconds: DEFAULT_VIDEO_DURATION_SECONDS, soundEnabled: undefined, }), - inputSchema: undefined, parameterSchema: undefined, modelSchemaRequestId: Date.now(), soundEnabled: undefined, @@ -353,7 +329,6 @@ export function GenerateVideoNodeView({ id, data, selected }: GenerateVideoNodeV if (model) { const newSelectedModel = toSelectedModel(model); // Clear parameters when changing models (different models have different schemas) - // Set inputSchema immediately for Veo models so handles render in the same update updateNodeData(id, { ...buildGenerationModelSwitchPatch(nodeData, newSelectedModel, { resolution: DEFAULT_VIDEO_RESOLUTION, @@ -363,7 +338,6 @@ export function GenerateVideoNodeView({ id, data, selected }: GenerateVideoNodeV resolution: undefined, durationSeconds: undefined, soundEnabled: getDefaultVideoSoundEnabled(newSelectedModel), - inputSchema: buildVeoInputSchema(model.id), parameterSchema: undefined, modelSchemaRequestId: Date.now(), }); @@ -388,12 +362,11 @@ export function GenerateVideoNodeView({ id, data, selected }: GenerateVideoNodeV selectedModel: nodeConfig.selectedModel, enabled: Boolean(nodeConfig.selectedModel?.modelId) && canFetchPopiModelSchema, refreshKey: nodeData.modelSchemaRequestId, - onLoaded: ({ inputs, parameters, model }) => { + onLoaded: ({ parameters, model }) => { const selectedModelPatch = model && shouldUpdateSelectedModelFromProviderModel(nodeConfig.selectedModel, model) ? buildGenerationSelectedModelPatch(nodeConfig, toSelectedModel(model)) : {}; updateNodeData(id, { - inputSchema: inputs, parameterSchema: parameters, ...selectedModelPatch, modelSchemaError: null, @@ -520,7 +493,6 @@ export function GenerateVideoNodeView({ id, data, selected }: GenerateVideoNodeV setIsBrowseDialogOpen(false); return; } - // Set inputSchema immediately for Veo models so handles render in the same update updateNodeData(id, { ...buildGenerationModelSwitchPatch(nodeData, newSelectedModel, { resolution: DEFAULT_VIDEO_RESOLUTION, @@ -530,7 +502,6 @@ export function GenerateVideoNodeView({ id, data, selected }: GenerateVideoNodeV resolution: undefined, durationSeconds: undefined, soundEnabled: getDefaultVideoSoundEnabled(newSelectedModel), - inputSchema: buildVeoInputSchema(model.id), parameterSchema: undefined, modelSchemaRequestId: Date.now(), }); @@ -720,10 +691,10 @@ export function GenerateVideoNodeView({ id, data, selected }: GenerateVideoNodeV // compatibility. Schema may only have text inputs (text-to-video models) but // we still need the image handle to preserve connections made before model selection. (() => { - const imageInputs = nodeData.inputSchema!.filter(i => i.type === "image"); - const videoInputs = nodeData.inputSchema!.filter(i => i.type === "video"); - const audioInputs = nodeData.inputSchema!.filter(i => i.type === "audio"); - const textInputs = nodeData.inputSchema!.filter(i => i.type === "text"); + const imageInputs: Array<{ name: string; type: string; label: string; description?: string }> = []; + const videoInputs: Array<{ name: string; type: string; label: string; description?: string }> = []; + const audioInputs: Array<{ name: string; type: string; label: string; description?: string }> = []; + const textInputs: Array<{ name: string; type: string; label: string; description?: string }> = []; const hasImageInput = imageInputs.length > 0; const hasVideoInput = videoInputs.length > 0; diff --git a/src/components/nodes/ModelParameters.tsx b/src/components/nodes/ModelParameters.tsx index 0b467982..0760cebf 100644 --- a/src/components/nodes/ModelParameters.tsx +++ b/src/components/nodes/ModelParameters.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect, useCallback, useRef, useMemo } from "react"; import { Select } from "antd"; -import { ModelInputDef, ProviderType } from "@/types"; +import { ProviderType } from "@/types"; import { ModelParameter } from "@/lib/providers/types"; import { TranslationKey, useI18n } from "@/i18n"; @@ -29,7 +29,6 @@ interface ModelParametersProps { parameters: Record; onParametersChange: (parameters: Record) => void; onExpandChange?: (expanded: boolean, parameterCount: number) => void; - onInputsLoaded?: (inputs: ModelInputDef[]) => void; hiddenParameterNames?: string[]; pruneHiddenParameters?: boolean; } diff --git a/src/hooks/useDynamicModelInputEdges.ts b/src/hooks/useDynamicModelInputEdges.ts deleted file mode 100644 index ac828dd7..00000000 --- a/src/hooks/useDynamicModelInputEdges.ts +++ /dev/null @@ -1,79 +0,0 @@ -"use client"; - -import { useEffect, useMemo } from "react"; -import { useUpdateNodeInternals } from "@xyflow/react"; -import { useWorkflowStore } from "@/store/workflowStore"; -import type { ModelInputDef, SelectedModel } from "@/types"; -import { modelSupportsReferenceMedia, type ReferenceMediaType } from "@/utils/generationInputSupport"; - -const DYNAMIC_INPUT_TYPES: readonly ModelInputDef["type"][] = ["image", "video", "audio", "text"]; - -function metadataSupportFlag(model: SelectedModel | undefined, inputType: ReferenceMediaType): boolean | null { - const metadata = model?.metadata; - if (!metadata) return null; - - if (inputType === "image" && typeof metadata.isSupportImages === "boolean") { - return metadata.isSupportImages; - } - if (inputType === "video" && typeof metadata.isSupportVideos === "boolean") { - return metadata.isSupportVideos; - } - if (inputType === "audio") { - if (typeof metadata.isSupportAudios === "boolean") return metadata.isSupportAudios; - if (typeof metadata.isSupportAudio === "boolean") return metadata.isSupportAudio; - } - - return null; -} - -export function useDynamicModelInputEdges( - nodeId: string, - inputSchema: ModelInputDef[] | undefined, - selectedModel?: SelectedModel, - parameters?: Record -): void { - const updateNodeInternals = useUpdateNodeInternals(); - const reconcileDynamicInputEdges = useWorkflowStore((state) => state.reconcileDynamicInputEdges); - - const schemaSignature = useMemo(() => { - if (!inputSchema || inputSchema.length === 0) return ""; - return inputSchema.map((input) => `${input.type}:${input.name}`).join("|"); - }, [inputSchema]); - - const supportSignature = useMemo(() => { - const metadata = selectedModel?.metadata; - const capabilitySignature = selectedModel?.capabilities?.join("|") ?? ""; - const parameterSignature = parameters ? Object.keys(parameters).sort().join("|") : ""; - return [ - selectedModel?.provider ?? "", - selectedModel?.modelId ?? "", - metadata?.isSupportImages, - metadata?.isSupportVideos, - metadata?.isSupportAudios, - metadata?.isSupportAudio, - capabilitySignature, - parameterSignature, - ].join("|"); - }, [parameters, selectedModel]); - - const validInputTypes = useMemo(() => { - const hasSchema = Boolean(inputSchema?.length); - return DYNAMIC_INPUT_TYPES.filter((inputType) => { - if (inputType === "text") return true; - if (hasSchema && !inputSchema?.some((input) => input.type === inputType)) return false; - if (!selectedModel) return hasSchema; - - const mediaType = inputType as ReferenceMediaType; - const supportFlag = metadataSupportFlag(selectedModel, mediaType); - if (supportFlag === false) return false; - if (supportFlag === true) return true; - return modelSupportsReferenceMedia(selectedModel, mediaType, inputSchema, parameters); - }); - }, [inputSchema, parameters, selectedModel]); - - useEffect(() => { - updateNodeInternals(nodeId); - if (!schemaSignature && !selectedModel) return; - reconcileDynamicInputEdges?.(nodeId, validInputTypes); - }, [nodeId, reconcileDynamicInputEdges, schemaSignature, selectedModel, supportSignature, updateNodeInternals, validInputTypes]); -} diff --git a/src/store/execution/generate3dExecutor.ts b/src/store/execution/generate3dExecutor.ts index c370826e..676ccba9 100644 --- a/src/store/execution/generate3dExecutor.ts +++ b/src/store/execution/generate3dExecutor.ts @@ -64,7 +64,6 @@ export async function executeGenerate3D( images: connectedImages, text: connectedText, textItems: connectedTextItems, - dynamicInputs, } = getConnectedInputs(node.id); // Get fresh node data from store @@ -80,11 +79,7 @@ export async function executeGenerate3D( promptText = buildPromptFromConnectedTextItems(connectedTextItems, connectedText, nodeData.inputPrompt); } else { images = connectedImages; - const promptFromDynamic = Array.isArray(dynamicInputs.prompt) - ? dynamicInputs.prompt[0] - : dynamicInputs.prompt; promptText = buildPromptFromConnectedTextItems(connectedTextItems, connectedText, null) - || promptFromDynamic || null; } @@ -116,7 +111,6 @@ export async function executeGenerate3D( prompt: promptText || "", selectedModel: modelToUse, parameters: parametersOverride ?? nodeData.parameters, - dynamicInputs, mediaType: "3d" as const, }; diff --git a/src/store/execution/generateAudioExecutor.ts b/src/store/execution/generateAudioExecutor.ts index 3992217d..647110de 100644 --- a/src/store/execution/generateAudioExecutor.ts +++ b/src/store/execution/generateAudioExecutor.ts @@ -11,7 +11,6 @@ import { runWithFallback } from "./runWithFallback"; import { executePopiartTask } from "./popiartTaskClient"; import type { NodeExecutionContext } from "./types"; import { buildPromptFromConnectedTextItems } from "./textInputPrompt"; -import { uploadPopiserverDynamicInputsForGeneration } from "@/utils/temporaryImageUpload"; import { buildDefaultParametersFromSchema, mergeSchemaDefaults } from "@/utils/modelSchemaDefaults"; import { readAudioGenerationConfig } from "@/utils/generationConfig"; import { createDefaultAudioModel } from "@/store/utils/defaultImageModel"; @@ -21,11 +20,6 @@ export interface GenerateAudioOptions { useStoredFallback?: boolean; } -function firstTextValue(value: string | string[] | undefined): string | null { - if (Array.isArray(value)) return value.find((item) => item.trim().length > 0) ?? null; - return value && value.trim().length > 0 ? value : null; -} - export async function executeGenerateAudio( ctx: NodeExecutionContext, _options: GenerateAudioOptions = {} @@ -42,7 +36,7 @@ export async function executeGenerateAudio( void _options; - const { text: connectedText, textItems: connectedTextItems = [], dynamicInputs } = getConnectedInputs(node.id); + const { text: connectedText, textItems: connectedTextItems = [] } = getConnectedInputs(node.id); // Get fresh node data from store const freshNode = getFreshNode(node.id); @@ -50,10 +44,9 @@ export async function executeGenerateAudio( const nodeConfig = readAudioGenerationConfig(nodeData, createDefaultAudioModel()); const hasConnectedText = connectedTextItems.length > 0 || Boolean(connectedText?.trim()); - const schemaPrompt = firstTextValue(dynamicInputs.prompt) ?? firstTextValue(dynamicInputs.text); const text = hasConnectedText ? buildPromptFromConnectedTextItems(connectedTextItems, connectedText, nodeConfig.prompt) - : schemaPrompt ?? nodeConfig.prompt ?? null; + : nodeConfig.prompt ?? null; if (!text) { updateNodeData(node.id, { @@ -80,7 +73,6 @@ export async function executeGenerateAudio( const runOnce = async (modelToUse: SelectedModel, parametersOverride?: Record): Promise => { const provider = modelToUse.provider; const headers = buildGenerateHeaders(provider, providerSettings); - const preparedDynamicInputs = await uploadPopiserverDynamicInputsForGeneration(dynamicInputs); const requestPayload = { images: [], @@ -90,7 +82,6 @@ export async function executeGenerateAudio( buildDefaultParametersFromSchema(nodeData.parameterSchema ?? []), parametersOverride ?? nodeConfig.parameters ), - dynamicInputs: preparedDynamicInputs.dynamicInputs, mediaType: "audio" as const, }; diff --git a/src/store/execution/generateVideoExecutor.ts b/src/store/execution/generateVideoExecutor.ts index 2ec54d5a..339acade 100644 --- a/src/store/execution/generateVideoExecutor.ts +++ b/src/store/execution/generateVideoExecutor.ts @@ -140,9 +140,9 @@ function sanitizeReferenceSubjectListParameter( modelToUse: SelectedModel ): Record | undefined { if (!parameters || !Array.isArray(parameters.referenceSubjectList)) return parameters; - const supportsImage = modelSupportsReferenceMedia(modelToUse, "image", nodeData.inputSchema, parameters); - const supportsVideo = modelSupportsReferenceMedia(modelToUse, "video", nodeData.inputSchema, parameters); - const supportsAudio = modelSupportsReferenceMedia(modelToUse, "audio", nodeData.inputSchema, parameters); + const supportsImage = modelSupportsReferenceMedia(modelToUse, "image", parameters); + const supportsVideo = modelSupportsReferenceMedia(modelToUse, "video", parameters); + const supportsAudio = modelSupportsReferenceMedia(modelToUse, "audio", parameters); const referenceSubjectList = parameters.referenceSubjectList.filter((item) => { if (!item || typeof item !== "object" || Array.isArray(item)) return false; const type = (item as Record).type; @@ -156,65 +156,6 @@ function sanitizeReferenceSubjectListParameter( : Object.fromEntries(Object.entries(parameters).filter(([key]) => key !== "referenceSubjectList")); } -const hasDynamicVideoInput = (dynamicInputs: Record): boolean => - Object.entries(dynamicInputs).some(([key, value]) => { - if (!key.toLowerCase().includes("video")) return false; - return Array.isArray(value) ? value.length > 0 : Boolean(value); - }); - -const LEGACY_VIDEO_AUDIO_DYNAMIC_KEYS = new Set([ - "audio", - "audios", - "audio_url", - "audio_urls", - "reference_audio", - "reference_audio_url", - "reference_audio_urls", -]); - -function removeLegacyVideoAudioDynamicInputs( - dynamicInputs: Record -): Record { - return Object.fromEntries( - Object.entries(dynamicInputs).filter(([key]) => !LEGACY_VIDEO_AUDIO_DYNAMIC_KEYS.has(key)) - ); -} - -function mergeConnectedVideosIntoDynamicInputs( - dynamicInputs: Record, - videos: string[] -): Record { - if (videos.length === 0 || hasDynamicVideoInput(dynamicInputs)) { - return dynamicInputs; - } - - return { - ...dynamicInputs, - video: videos[0], - videos, - }; -} - -const hasDynamicVoiceInput = (dynamicInputs: Record): boolean => - Object.entries(dynamicInputs).some(([key, value]) => { - if (key !== "voices") return false; - return Array.isArray(value) ? value.length > 0 : Boolean(value); - }); - -function mergeConnectedVoicesIntoDynamicInputs( - dynamicInputs: Record, - voices: string[] -): Record { - if (voices.length === 0 || hasDynamicVoiceInput(dynamicInputs)) { - return dynamicInputs; - } - - return { - ...dynamicInputs, - voices, - }; -} - export async function executeGenerateVideo( ctx: NodeExecutionContext, options: GenerateVideoOptions = {} @@ -238,18 +179,12 @@ export async function executeGenerateVideo( text: connectedText, textItems: connectedTextItems, audio: connectedAudio, - dynamicInputs, } = getConnectedInputs(node.id); - const baseDynamicInputs = removeLegacyVideoAudioDynamicInputs(dynamicInputs); // Get fresh node data from store const freshNode = getFreshNode(node.id); const nodeData = (freshNode?.data || node.data) as GenerateVideoNodeData; const nodeConfig = readVideoGenerationConfig(nodeData, createNewApiWGDefaultVideoModel()); - const requestDynamicInputs = mergeConnectedVoicesIntoDynamicInputs( - mergeConnectedVideosIntoDynamicInputs(baseDynamicInputs, connectedVideos), - connectedAudio - ); if (!isValidVideoDurationSeconds(nodeConfig.durationSeconds)) { const error = "Video duration must be at least 4 seconds"; @@ -264,9 +199,9 @@ export async function executeGenerateVideo( if (useStoredFallback) { images = connectedImages.length > 0 ? connectedImages : nodeConfig.inputImages; text = buildPromptFromConnectedTextItems(connectedTextItems, connectedText, nodeConfig.prompt); - const hasPrompt = text || requestDynamicInputs.prompt || requestDynamicInputs.negative_prompt; + const hasPrompt = Boolean(text); const hasAudio = connectedAudio.length > 0; - const hasVideo = connectedVideos.length > 0 || hasDynamicVideoInput(requestDynamicInputs); + const hasVideo = connectedVideos.length > 0; if (!hasPrompt && images.length === 0 && !hasVideo && !hasAudio) { updateNodeData(node.id, { status: "error", @@ -277,9 +212,9 @@ export async function executeGenerateVideo( } else { images = connectedImages; text = buildPromptFromConnectedTextItems(connectedTextItems, connectedText, nodeConfig.prompt); - const hasPrompt = text || requestDynamicInputs.prompt || requestDynamicInputs.negative_prompt; + const hasPrompt = Boolean(text); const hasAudio = connectedAudio.length > 0; - const hasVideo = connectedVideos.length > 0 || hasDynamicVideoInput(requestDynamicInputs); + const hasVideo = connectedVideos.length > 0; if (!hasPrompt && images.length === 0 && !hasVideo && !hasAudio) { updateNodeData(node.id, { status: "error", @@ -321,7 +256,6 @@ export async function executeGenerateVideo( __usedFallback: undefined, __fallbackModelUsed: undefined, __primaryError: undefined, - inputSchema: undefined, }); } @@ -344,11 +278,7 @@ export async function executeGenerateVideo( const provider = modelToUse.provider; const headers = buildGenerateHeaders(provider, providerSettings); try { - const runDynamicInputs = mergeConnectedVoicesIntoDynamicInputs( - mergeConnectedVideosIntoDynamicInputs(baseDynamicInputs, connectedVideos), - connectedAudio - ); - const preparedInputs = await uploadPopiserverGenerationInputsForGeneration(images, runDynamicInputs); + const preparedInputs = await uploadPopiserverGenerationInputsForGeneration(images, {}); const supportedInputs = provider === "popiserver" ? getPopiSupportedMediaInputs(modelToUse, { images: preparedInputs.images, @@ -377,7 +307,6 @@ export async function executeGenerateVideo( prompt: text || "", selectedModel: modelToUse, parameters, - dynamicInputs: preparedInputs.dynamicInputs, mediaType: "video" as const, }; diff --git a/src/store/execution/nanoBananaExecutor.ts b/src/store/execution/nanoBananaExecutor.ts index 50827643..4895b885 100644 --- a/src/store/execution/nanoBananaExecutor.ts +++ b/src/store/execution/nanoBananaExecutor.ts @@ -96,7 +96,6 @@ export async function executeNanoBanana( images: connectedImages, text: connectedText, textItems: connectedTextItems = [], - dynamicInputs, } = getConnectedInputs(node.id); // Get fresh node data from store @@ -127,13 +126,9 @@ export async function executeNanoBanana( } else { const localImage = (nodeData as NanoBananaNodeData & { image?: string | null }).image; images = connectedImages.length > 0 ? connectedImages : localImage ? [localImage] : []; - // For dynamic inputs, check if we have at least a prompt - const promptFromDynamic = Array.isArray(dynamicInputs.prompt) - ? dynamicInputs.prompt[0] - : dynamicInputs.prompt; promptText = hasConnectedText ? buildPromptFromConnectedTextItems(connectedTextItems, connectedText, nodeConfig.prompt) - : promptFromDynamic || nodeConfig.prompt || null; + : nodeConfig.prompt || null; } // Defensive: ensure promptText is actually a string at runtime @@ -168,14 +163,8 @@ export async function executeNanoBanana( const provider = modelToUse.provider; const headers = buildGenerateHeaders(provider, providerSettings); - // Sanitize dynamicInputs: remove prompt since it's already sent as the top-level - // `prompt` field in requestPayload. Keeping both can cause providers like Replicate - // to prefer dynamicInputs.prompt over the authoritative top-level value. - const sanitizedDynamicInputs = { ...dynamicInputs }; - delete sanitizedDynamicInputs.prompt; - try { - const preparedInputs = await uploadPopiserverGenerationInputsForGeneration(images, sanitizedDynamicInputs); + const preparedInputs = await uploadPopiserverGenerationInputsForGeneration(images, {}); const supportedInputs = provider === "popiserver" ? getPopiSupportedMediaInputs(modelToUse, { images: preparedInputs.images }) : { images: preparedInputs.images, videos: [], voices: [] }; @@ -213,7 +202,6 @@ export async function executeNanoBanana( useImageSearch: (parametersOverride?.useImageSearch as boolean) ?? nodeData.useImageSearch, selectedModel: modelToUse, parameters: effectiveParameters, - dynamicInputs: preparedInputs.dynamicInputs, }; // Final guard: assert that prompt is a string before sending to API @@ -391,7 +379,6 @@ export async function executeNanoBanana( __usedFallback: undefined, __fallbackModelUsed: undefined, __primaryError: undefined, - inputSchema: undefined, }); } diff --git a/src/store/utils/__tests__/connectedInputs.test.ts b/src/store/utils/__tests__/connectedInputs.test.ts index f751336a..8aff806f 100644 --- a/src/store/utils/__tests__/connectedInputs.test.ts +++ b/src/store/utils/__tests__/connectedInputs.test.ts @@ -277,7 +277,7 @@ describe("getConnectedInputsPure", () => { expect(result.images).toEqual(["data:image/png;base64,a", "data:image/png;base64,b"]); }); - it("should populate dynamicInputs with schema mapping", () => { + it("should ignore legacy inputSchema when collecting image inputs", () => { const nodes = [ makeNode("img", "imageInput", { image: "data:image/png;base64,a" }), makeNode("gen", "nanoBanana", { @@ -286,10 +286,11 @@ describe("getConnectedInputsPure", () => { ]; const edges = [makeEdge("img", "gen", "image-0")]; const result = getConnectedInputsPure("gen", nodes, edges); - expect(result.dynamicInputs).toEqual({ image_url: "data:image/png;base64,a" }); + expect(result.images).toEqual(["data:image/png;base64,a"]); + expect(result.dynamicInputs).toEqual({}); }); - it("should populate dynamicInputs with video schema mapping", () => { + it("should ignore legacy inputSchema when collecting video inputs", () => { const nodes = [ makeNode("vid", "videoInput", { video: "data:video/mp4;base64,v" }), makeNode("gen", "generateVideo", { @@ -306,7 +307,7 @@ describe("getConnectedInputsPure", () => { const result = getConnectedInputsPure("gen", nodes, edges); expect(result.videos).toEqual(["data:video/mp4;base64,v"]); expect(result.videoItems).toEqual([{ url: "data:video/mp4;base64,v" }]); - expect(result.dynamicInputs).toEqual({ video_urls: "data:video/mp4;base64,v" }); + expect(result.dynamicInputs).toEqual({}); }); it("should extract easeCurve data", () => { diff --git a/src/store/utils/connectedInputs.ts b/src/store/utils/connectedInputs.ts index afbbb257..37834a08 100644 --- a/src/store/utils/connectedInputs.ts +++ b/src/store/utils/connectedInputs.ts @@ -279,47 +279,6 @@ export function getConnectedInputsPure( } }; - // Get the target node to check for inputSchema - const targetNode = nodes.find((n) => n.id === nodeId); - const inputSchema = (targetNode?.data as { inputSchema?: Array<{ name: string; type: string }> })?.inputSchema; - - // Build mapping from normalized handle IDs to schema names if schema exists - const handleToSchemaName: Record = {}; - if (inputSchema && inputSchema.length > 0) { - const imageInputs = inputSchema.filter(i => i.type === "image"); - const videoInputs = inputSchema.filter(i => i.type === "video"); - const textInputs = inputSchema.filter(i => i.type === "text"); - const audioInputs = inputSchema.filter(i => i.type === "audio"); - - imageInputs.forEach((input, index) => { - handleToSchemaName[`image-${index}`] = input.name; - if (index === 0) { - handleToSchemaName["image"] = input.name; - } - }); - - videoInputs.forEach((input, index) => { - handleToSchemaName[`video-${index}`] = input.name; - if (index === 0) { - handleToSchemaName["video"] = input.name; - } - }); - - textInputs.forEach((input, index) => { - handleToSchemaName[`text-${index}`] = input.name; - if (index === 0) { - handleToSchemaName["text"] = input.name; - } - }); - - audioInputs.forEach((input, index) => { - handleToSchemaName[`audio-${index}`] = input.name; - if (index === 0) { - handleToSchemaName["audio"] = input.name; - } - }); - } - // Cache passthrough node results so multiple edges from the same router/switch // all receive correct data (the _visited set prevents re-traversal, so we cache // the result from the first traversal and reuse it for subsequent edges). @@ -421,19 +380,6 @@ export function getConnectedInputsPure( return; } - // Map normalized handle ID to schema name for dynamicInputs - if (handleId && handleToSchemaName[handleId]) { - const schemaName = handleToSchemaName[handleId]; - const existing = dynamicInputs[schemaName]; - if (existing !== undefined) { - dynamicInputs[schemaName] = Array.isArray(existing) - ? [...existing, value] - : [existing, value]; - } else { - dynamicInputs[schemaName] = value; - } - } - addTypedValue(type, value, sourceNode); if (sourceNode.type === "easeCurve") { const sourceData = sourceNode.data as EaseCurveNodeData; diff --git a/src/store/workflowStore.ts b/src/store/workflowStore.ts index 7dba16d6..d0b2b659 100644 --- a/src/store/workflowStore.ts +++ b/src/store/workflowStore.ts @@ -26,7 +26,6 @@ import { RecentModel, CanvasNavigationSettings, MatchMode, - ModelInputDef, } from "@/types"; import { UndoManager, UndoSnapshot, clonePreservingStrings } from "./undoHistory"; import { useToast } from "@/components/Toast"; @@ -67,7 +66,6 @@ import { isSmartAudioGenerateMode } from "@/utils/smartAudioMode"; import { isSmartTextLlmMode } from "@/utils/smartTextMode"; import { isSmartVideoGenerateMode } from "@/utils/smartVideoMode"; import { - getNodeOutputMediaType, normalizeConnectionHandles, normalizeWorkflowEdgeHandles, SINGLE_INPUT_HANDLE_ID, @@ -211,21 +209,6 @@ function buildConnectionEdgeData( return baseData; } -const DYNAMIC_MODEL_INPUT_TYPES: readonly ModelInputDef["type"][] = ["image", "video", "audio", "text"]; - -function getDynamicModelInputType(handleId: string | null | undefined): ModelInputDef["type"] | null { - if (!handleId) return null; - if (handleId === "prompt") return "text"; - - for (const inputType of DYNAMIC_MODEL_INPUT_TYPES) { - if (handleId === inputType || handleId.startsWith(`${inputType}-`)) { - return inputType; - } - } - - return null; -} - // Workflow file format export interface WorkflowFile { version: 1; @@ -280,7 +263,6 @@ interface WorkflowStore { onConnect: (connection: Connection, edgeDataOverrides?: Record) => void; addEdgeWithType: (connection: Connection, edgeType: string, edgeDataOverrides?: Record) => void; removeEdge: (edgeId: string) => void; - reconcileDynamicInputEdges: (nodeId: string, validInputTypes: readonly ModelInputDef["type"][]) => void; toggleEdgePause: (edgeId: string) => void; setLoopCount: (edgeId: string, count: number) => void; @@ -728,7 +710,6 @@ function promoteUnavailableGeminiFallbacks( __usedFallback: undefined, __fallbackModelUsed: undefined, __primaryError: undefined, - inputSchema: undefined, } as WorkflowNodeData, } as WorkflowNode; }) as WorkflowNode[]; @@ -1499,43 +1480,6 @@ const workflowStoreImpl: StateCreator = (set, get) => ({ get().incrementManualChangeCount(); }, - reconcileDynamicInputEdges: (nodeId: string, validInputTypes: readonly ModelInputDef["type"][]) => { - const validInputTypeSet = new Set(validInputTypes); - const nodes = get().nodes; - const edges = get().edges; - const removedEdges = edges.filter((edge) => { - if (edge.target !== nodeId) return false; - - const sourceNode = nodes.find((node) => node.id === edge.source); - const inputType = getDynamicModelInputType(edge.targetHandle) - ?? (sourceNode ? getNodeOutputMediaType(sourceNode) : null); - - if (inputType !== "image" && inputType !== "video" && inputType !== "audio" && inputType !== "text") { - return false; - } - - return !validInputTypeSet.has(inputType); - }); - - if (removedEdges.length === 0) return; - - const removedEdgeIds = new Set(removedEdges.map((edge) => edge.id)); - pushUndoCheckpoint(get, set); - set((state) => ({ - edges: state.edges.filter((edge) => !removedEdgeIds.has(edge.id)), - hasUnsavedChanges: true, - })); - - deleteCheckpointActive = true; - try { - clearStaleInputImages(removedEdges, get); - } finally { - deleteCheckpointActive = false; - } - get().incrementManualChangeCount(); - get().recomputeDimmedNodes(); - }, - toggleEdgePause: (edgeId: string) => { pushUndoCheckpoint(get, set); set((state) => ({ diff --git a/src/types/nodes.ts b/src/types/nodes.ts index 5cea5cfb..28e6255b 100644 --- a/src/types/nodes.ts +++ b/src/types/nodes.ts @@ -211,9 +211,7 @@ export interface CarouselVideoItem { modelProvider?: ProviderType; } -/** - * Model input definition for dynamic handles - */ +/** Model input definition returned by model detail APIs. */ export interface ModelInputDef { name: string; type: "image" | "text" | "video" | "audio"; @@ -246,7 +244,6 @@ export interface NanoBananaNodeData extends BaseNodeData { useImageSearch: boolean; // Only available for Nano Banana 2 parameters?: Record; // Legacy mirror of config.parameters; model-specific dynamic parameters only fallbackParameters?: Record; // Parameters for fallback model - inputSchema?: ModelInputDef[]; // Model's input schema for dynamic handles parameterSchema?: ModelParameter[]; // Model's parameter schema for per-node controls modelSchemaLoading?: boolean; modelSchemaError?: string | null; @@ -285,7 +282,6 @@ export interface GenerateVideoNodeData extends BaseNodeData { lastUsedModel?: SelectedModel; // The model that produced the current output parameters?: Record; // Legacy mirror of config.parameters; model-specific dynamic parameters only fallbackParameters?: Record; // Parameters for fallback model - inputSchema?: ModelInputDef[]; // Model's input schema for dynamic handles parameterSchema?: ModelParameter[]; // Model's parameter schema for per-node controls modelSchemaLoading?: boolean; modelSchemaError?: string | null; @@ -316,7 +312,6 @@ export interface Generate3DNodeData extends BaseNodeData { config?: PersistedGenerationNodeConfig; // Canonical generation config; legacy fields are kept for compatibility parameters?: Record; // Legacy mirror of config.parameters; model-specific dynamic parameters only fallbackParameters?: Record; // Parameters for fallback model - inputSchema?: ModelInputDef[]; parameterSchema?: ModelParameter[]; modelSchemaLoading?: boolean; modelSchemaError?: string | null; @@ -353,7 +348,6 @@ export interface GenerateAudioNodeData extends BaseNodeData { config?: PersistedGenerationNodeConfig; // Canonical generation config; legacy fields are kept for compatibility parameters?: Record; // Legacy mirror of config.parameters; model-specific dynamic parameters only fallbackParameters?: Record; // Parameters for fallback model - inputSchema?: ModelInputDef[]; // Model's input schema for dynamic handles parameterSchema?: ModelParameter[]; // Model's parameter schema for per-node controls modelSchemaLoading?: boolean; modelSchemaError?: string | null; diff --git a/src/utils/generationConfig.ts b/src/utils/generationConfig.ts index f0476cd4..2fde01f3 100644 --- a/src/utils/generationConfig.ts +++ b/src/utils/generationConfig.ts @@ -251,7 +251,6 @@ export function readAudioGenerationConfig( function resetSchemaCachePatch() { return { fallbackParameters: {}, - inputSchema: undefined, parameterSchema: undefined, modelSchemaRequestId: Date.now(), }; @@ -336,7 +335,6 @@ export function buildInitialGenerationNodeData( selectedModel: config.selectedModel, imageCount: config.imageCount, parameters: config.parameters ?? {}, - inputSchema: undefined, useGoogleSearch: false, useImageSearch: false, status: "idle", @@ -353,7 +351,6 @@ export function buildInitialGenerationNodeData( durationSeconds: normalizeVideoDurationSeconds(config.durationSeconds), soundEnabled: modelSupportsVideoSound(config.selectedModel) ? config.soundEnabled ?? true : undefined, parameters: config.parameters ?? {}, - inputSchema: undefined, status: "idle", error: null, config: persistedGenerationConfig(config), @@ -365,7 +362,6 @@ export function buildInitialGenerationNodeData( inputPrompt: config.prompt.trim(), selectedModel: config.selectedModel, parameters: config.parameters ?? {}, - inputSchema: undefined, status: "idle", error: null, config: persistedGenerationConfig(config), @@ -375,7 +371,6 @@ export function buildInitialGenerationNodeData( inputPrompt: config.prompt.trim(), selectedModel: config.selectedModel, parameters: config.parameters ?? {}, - inputSchema: undefined, status: "idle", error: null, config: persistedGenerationConfig(config), diff --git a/src/utils/generationInputSupport.ts b/src/utils/generationInputSupport.ts index 8a30fb48..f03b8f31 100644 --- a/src/utils/generationInputSupport.ts +++ b/src/utils/generationInputSupport.ts @@ -1,4 +1,4 @@ -import type { ModelInputDef, SelectedModel } from "@/types"; +import type { SelectedModel } from "@/types"; export type ReferenceMediaType = "image" | "video" | "audio"; @@ -39,10 +39,6 @@ const IMAGE_INPUT_PARAMETER_NAMES = new Set([ "reference_image_urls", ]); -function hasSchemaInput(inputSchema: ModelInputDef[] | undefined, mediaType: ReferenceMediaType): boolean { - return Boolean(inputSchema?.some((input) => input.type === mediaType)); -} - function hasNamedParameterInput(parameters: Record | undefined, names: Set): boolean { if (!parameters) return false; return Object.keys(parameters).some((key) => names.has(key)); @@ -56,11 +52,8 @@ function metadataBoolean(model: SelectedModel, key: string): boolean | null { export function modelSupportsReferenceMedia( model: SelectedModel, mediaType: ReferenceMediaType, - inputSchema?: ModelInputDef[], parameters?: Record ): boolean { - if (hasSchemaInput(inputSchema, mediaType)) return true; - if (mediaType === "image") { const supportImages = metadataBoolean(model, "isSupportImages"); if (supportImages !== null) return supportImages; @@ -88,7 +81,6 @@ export function modelSupportsReferenceMedia( export function filterReferenceSubjectList( referenceSubjectList: unknown, model: SelectedModel, - inputSchema?: ModelInputDef[], parameters?: Record ): unknown { if (!Array.isArray(referenceSubjectList)) return referenceSubjectList; @@ -96,6 +88,6 @@ export function filterReferenceSubjectList( if (!item || typeof item !== "object" || Array.isArray(item)) return false; const type = (item as Record).type; if (type !== "image" && type !== "video" && type !== "audio") return true; - return modelSupportsReferenceMedia(model, type, inputSchema, parameters); + return modelSupportsReferenceMedia(model, type, parameters); }); } diff --git a/src/utils/nodeHandles.ts b/src/utils/nodeHandles.ts index 1a59b11a..4871fcce 100644 --- a/src/utils/nodeHandles.ts +++ b/src/utils/nodeHandles.ts @@ -268,35 +268,11 @@ function getSmartMediaInputHandles( const capability = getNodeHandleCapability(node, edges); if (capability.inputs.length === 0) return []; - const data = node.data as { - inputSchema?: Array<{ name: string; type: string }>; - }; - - if (!data.inputSchema || data.inputSchema.length === 0) { - return defaultTypes - .filter((type) => capability.inputs.includes(type)) - .map((type, index) => ({ - id: type, - type, - topPercent: defaultTopPercents[index] ?? ((index + 1) / (defaultTypes.length + 1)) * 100, - })); - } - - const supportedTypes = ["image", "video", "audio", "text"] as const; - const grouped = supportedTypes - .map((type) => { - const inputs = data.inputSchema?.filter((input) => input.type === type && capability.inputs.includes(type)) ?? []; - return inputs.length > 0 ? { type, inputs } : null; - }) - .filter((item): item is NonNullable => item !== null); - - return grouped.flatMap((group, groupIndex) => { - const topPercent = ((groupIndex + 1) / (grouped.length + 1)) * 100; - return group.inputs.map((input, inputIndex) => ({ - id: inputIndex === 0 ? group.type : `${group.type}-${inputIndex}`, - type: group.type, - schemaName: input.name, - topPercent, + return defaultTypes + .filter((type) => capability.inputs.includes(type)) + .map((type, index) => ({ + id: type, + type, + topPercent: defaultTopPercents[index] ?? ((index + 1) / (defaultTypes.length + 1)) * 100, })); - }); }