diff --git a/clientlibs/main.js b/clientlibs/main.js index e401a53..b7ffd31 100644 --- a/clientlibs/main.js +++ b/clientlibs/main.js @@ -1,6 +1,6 @@ import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js"; -import { promptEnhanceHandler, syncDimensionsNodeHandler, searchNodeHandler, APIKeyHandler, captionNodeHandler, mediaUUIDHandler, videoTranscriptionHandler, handleCustomErrors, videoInferenceDimensionsHandler, videoModelSearchFilterHandler, audioModelSearchFilterHandler, useParameterToggleHandler, imageInferenceToggleHandler, upscalerToggleHandler, videoUpscalerToggleHandler, audioInferenceToggleHandler, acceleratorOptionsToggleHandler, bytedanceProviderSettingsToggleHandler, openaiProviderSettingsToggleHandler, lightricksProviderSettingsToggleHandler, klingProviderSettingsToggleHandler, lumaProviderSettingsToggleHandler, briaProviderSettingsToggleHandler, pixverseProviderSettingsToggleHandler, alibabaProviderSettingsToggleHandler, googleProviderSettingsToggleHandler, syncProviderSettingsToggleHandler, syncSegmentToggleHandler, settingsToggleHandler } from "./utils.js"; +import { promptEnhanceHandler, syncDimensionsNodeHandler, searchNodeHandler, APIKeyHandler, captionNodeHandler, mediaUUIDHandler, videoTranscriptionHandler, handleCustomErrors, videoInferenceDimensionsHandler, videoModelSearchFilterHandler, audioModelSearchFilterHandler, useParameterToggleHandler, imageInferenceToggleHandler, upscalerToggleHandler, videoUpscalerToggleHandler, audioInferenceToggleHandler, acceleratorOptionsToggleHandler, bytedanceProviderSettingsToggleHandler, openaiProviderSettingsToggleHandler, lightricksProviderSettingsToggleHandler, klingProviderSettingsToggleHandler, lumaProviderSettingsToggleHandler, briaProviderSettingsToggleHandler, pixverseProviderSettingsToggleHandler, alibabaProviderSettingsToggleHandler, googleProviderSettingsToggleHandler, syncProviderSettingsToggleHandler, syncSegmentToggleHandler, settingsToggleHandler, audioInputToggleHandler, speechInputToggleHandler } from "./utils.js"; import { RUNWARE_NODE_TYPES, RUNWARE_NODE_PROPS, SEARCH_TERMS } from "./types.js"; const nodeInitList = []; @@ -81,6 +81,10 @@ app.registerExtension({ syncSegmentToggleHandler(node); } else if(nodeClass === RUNWARE_NODE_TYPES.SETTINGS) { settingsToggleHandler(node); + } else if(nodeClass === RUNWARE_NODE_TYPES.AUDIOINPUT) { + audioInputToggleHandler(node); + } else if(nodeClass === RUNWARE_NODE_TYPES.SPEECHINPUT) { + speechInputToggleHandler(node); } if(crNodeProps.colorModeOnly === true) return; diff --git a/clientlibs/utils.js b/clientlibs/utils.js index 40e1a86..873eb78 100644 --- a/clientlibs/utils.js +++ b/clientlibs/utils.js @@ -1684,6 +1684,8 @@ function videoModelSearchFilterHandler(videoModelSearchNode) { "klingai:7@1 (KlingAI Lip-Sync)", "klingai:kling@o1 (Kling VIDEO O1)", "klingai:kling-video@2.6-pro (Kling VIDEO 2.6 Pro)", + "klingai:avatar@2.0-standard (KlingAI Avatar 2.0 Standard)", + "klingai:avatar@2.0-pro (KlingAI Avatar 2.0 Pro)", ], "Veo": [ "google:2@0 (Veo 2.0)", "google:3@0 (Veo 3.0)", "google:3@1 (Veo 3.0 Fast)", @@ -1752,6 +1754,8 @@ function videoModelSearchFilterHandler(videoModelSearchNode) { "klingai:7@1": {"width": 0, "height": 0}, "klingai:kling@o1": {"width": 1440, "height": 1440}, "klingai:kling-video@2.6-pro": {"width": 1920, "height": 1080}, + "klingai:avatar@2.0-standard": {"width": 0, "height": 0}, + "klingai:avatar@2.0-pro": {"width": 0, "height": 0}, "google:2@0": {"width": 1280, "height": 720}, "google:3@0": {"width": 1280, "height": 720}, "google:3@1": {"width": 1280, "height": 720}, @@ -1811,6 +1815,8 @@ function videoModelSearchFilterHandler(videoModelSearchNode) { "klingai:7@1": null, // No resolution support "klingai:kling@o1": null, // No standard resolution "klingai:kling-video@2.6-pro": "1080p", + "klingai:avatar@2.0-standard": null, // No resolution support + "klingai:avatar@2.0-pro": null, // No resolution support "google:2@0": "720p", "google:3@0": "720p", "google:3@1": "720p", @@ -2517,6 +2523,120 @@ function briaProviderSettingsToggleHandler(briaNode) { } } +function audioInputToggleHandler(audioInputNode) { + // Find "use" parameter widget and "id" widget for Audio Input + const useIdWidget = audioInputNode.widgets.find(w => w.name === "useId"); + const idWidget = audioInputNode.widgets.find(w => w.name === "id"); + + // Helper function to toggle widget enabled state + function toggleWidgetState(useWidget, paramWidget, paramName) { + if (!useWidget || !paramWidget) return; + + function toggleEnabled() { + const enabled = useWidget.value === true; + + // Disable/enable widgets using inputEl if available + if (paramWidget.inputEl) { + paramWidget.inputEl.disabled = !enabled; + paramWidget.inputEl.style.opacity = enabled ? "1" : "0.5"; + paramWidget.inputEl.style.cursor = enabled ? "text" : "not-allowed"; + paramWidget.inputEl.readOnly = !enabled; + } + // Also set widget property + paramWidget.disabled = !enabled; + + // Fallback: try to find inputs via DOM if inputEl is not available + if (!paramWidget.inputEl) { + const nodeElement = audioInputNode.htmlElements?.widgetsContainer || audioInputNode.htmlElements; + if (nodeElement) { + const input = nodeElement.querySelector(`input[name="${paramName}"], textarea[name="${paramName}"], select[name="${paramName}"]`); + if (input) { + input.disabled = !enabled; + input.style.opacity = enabled ? "1" : "0.5"; + input.style.cursor = enabled ? "text" : "not-allowed"; + input.readOnly = !enabled; + if (input.tagName === "SELECT") { + input.style.pointerEvents = enabled ? "auto" : "none"; + } + } + } + } + + audioInputNode.setDirtyCanvas(true); + } + + // Set up callback + appendWidgetCB(useWidget, () => { + setTimeout(toggleEnabled, 50); + }); + + // Initial call to set initial state + setTimeout(toggleEnabled, 100); + } + + // Set up toggle handler for "useId" parameter + if (useIdWidget && idWidget) { + toggleWidgetState(useIdWidget, idWidget, "id"); + } +} + +function speechInputToggleHandler(speechInputNode) { + // Find "use" parameter widget and "id" widget for Speech Input + const useIdWidget = speechInputNode.widgets.find(w => w.name === "useId"); + const idWidget = speechInputNode.widgets.find(w => w.name === "id"); + + // Helper function to toggle widget enabled state + function toggleWidgetState(useWidget, paramWidget, paramName) { + if (!useWidget || !paramWidget) return; + + function toggleEnabled() { + const enabled = useWidget.value === true; + + // Disable/enable widgets using inputEl if available + if (paramWidget.inputEl) { + paramWidget.inputEl.disabled = !enabled; + paramWidget.inputEl.style.opacity = enabled ? "1" : "0.5"; + paramWidget.inputEl.style.cursor = enabled ? "text" : "not-allowed"; + paramWidget.inputEl.readOnly = !enabled; + } + // Also set widget property + paramWidget.disabled = !enabled; + + // Fallback: try to find inputs via DOM if inputEl is not available + if (!paramWidget.inputEl) { + const nodeElement = speechInputNode.htmlElements?.widgetsContainer || speechInputNode.htmlElements; + if (nodeElement) { + const input = nodeElement.querySelector(`input[name="${paramName}"], textarea[name="${paramName}"], select[name="${paramName}"]`); + if (input) { + input.disabled = !enabled; + input.style.opacity = enabled ? "1" : "0.5"; + input.style.cursor = enabled ? "text" : "not-allowed"; + input.readOnly = !enabled; + if (input.tagName === "SELECT") { + input.style.pointerEvents = enabled ? "auto" : "none"; + } + } + } + } + + speechInputNode.setDirtyCanvas(true); + } + + // Set up callback + appendWidgetCB(useWidget, () => { + setTimeout(toggleEnabled, 50); + }); + + // Initial call to set initial state + setTimeout(toggleEnabled, 100); + } + + // Set up toggle handler for "useId" parameter + if (useIdWidget && idWidget) { + toggleWidgetState(useIdWidget, idWidget, "id"); + } +} + export { notifyUser, promptEnhanceHandler, @@ -2548,6 +2668,8 @@ export { syncProviderSettingsToggleHandler, syncSegmentToggleHandler, settingsToggleHandler, + audioInputToggleHandler, + speechInputToggleHandler, }; function googleProviderSettingsToggleHandler(googleNode) { diff --git a/modules/audioInput.py b/modules/audioInput.py index fdec4be..6420d3a 100644 --- a/modules/audioInput.py +++ b/modules/audioInput.py @@ -13,16 +13,21 @@ class RunwareAudioInput: def INPUT_TYPES(cls): return { "required": { - "id": ("STRING", { - "tooltip": "Reference ID used to map audio input. Required when using segments array.", - "default": "", - }), "source": ("STRING", { "tooltip": "URL, base64, data URI, or UUID for the audio file. Audio formats: WAV, MP3, OGG, M4A, M3A, AAC, WMA, FLAC, MP4. Recommended: WAV/MP3 at 44.1kHz or 48kHz sampling rate.", "default": "", }), }, - "optional": {} + "optional": { + "useId": ("BOOLEAN", { + "tooltip": "Enable to include reference ID for mapping audio input. Required when using segments array.", + "default": False, + }), + "id": ("STRING", { + "tooltip": "Reference ID used to map audio input. Required when using segments array.", + "default": "", + }), + } } RETURN_TYPES = ("RUNWAREAUDIOINPUT",) @@ -34,17 +39,21 @@ class RunwareAudioInput: def createAudioInput(self, **kwargs) -> tuple[Dict[str, Any]]: """Create audio input configuration""" + useId = kwargs.get("useId", False) audio_id = kwargs.get("id", "").strip() source = kwargs.get("source", "").strip() - if not audio_id or not source: + if not source: return ({},) audioInput: Dict[str, Any] = { - "id": audio_id, "source": source, } + # Only include id if useId is enabled and id is provided + if useId and audio_id: + audioInput["id"] = audio_id + return (audioInput,) diff --git a/modules/speechInput.py b/modules/speechInput.py index 48b9e57..84ab6c9 100644 --- a/modules/speechInput.py +++ b/modules/speechInput.py @@ -13,10 +13,6 @@ class RunwareSpeechInput: def INPUT_TYPES(cls): return { "required": { - "id": ("STRING", { - "tooltip": "Reference ID used to map speech input. Required when using segments array.", - "default": "", - }), "provider": ("STRING", { "tooltip": "TTS provider name. Currently supports: elevenlabs", "default": "elevenlabs", @@ -31,7 +27,16 @@ class RunwareSpeechInput: "default": "", }), }, - "optional": {} + "optional": { + "useId": ("BOOLEAN", { + "tooltip": "Enable to include reference ID for mapping speech input. Required when using segments array.", + "default": False, + }), + "id": ("STRING", { + "tooltip": "Reference ID used to map speech input. Required when using segments array.", + "default": "", + }), + } } RETURN_TYPES = ("RUNWARESPEECHINPUT",) @@ -43,21 +48,25 @@ class RunwareSpeechInput: def createSpeechInput(self, **kwargs) -> tuple[Dict[str, Any]]: """Create speech input configuration""" + useId = kwargs.get("useId", False) speech_id = kwargs.get("id", "").strip() provider = kwargs.get("provider", "elevenlabs").strip() voiceId = kwargs.get("voiceId", "").strip() text = kwargs.get("text", "").strip() - if not speech_id or not provider or not voiceId or not text: + if not provider or not voiceId or not text: return ({},) speechInput: Dict[str, Any] = { - "id": speech_id, "provider": provider, "voiceId": voiceId, "text": text, } + # Only include id if useId is enabled and id is provided + if useId and speech_id: + speechInput["id"] = speech_id + return (speechInput,)