diff --git a/py/nodes/lora_loader.py b/py/nodes/lora_loader.py index 272c0643..ad2b3aa1 100644 --- a/py/nodes/lora_loader.py +++ b/py/nodes/lora_loader.py @@ -16,12 +16,9 @@ class LoraManagerLoader: "required": { "model": ("MODEL",), # "clip": ("CLIP",), - "text": ("STRING", { - "multiline": True, - "pysssss.autocomplete": False, - "dynamicPrompts": True, + "text": ("AUTOCOMPLETE_TEXT_LORAS", { + "placeholder": "Type LoRA syntax...", "tooltip": "Format: separated by spaces or punctuation", - "placeholder": "LoRA syntax input: " }), }, "optional": FlexibleOptionalInputType(any_type), diff --git a/py/nodes/lora_stacker.py b/py/nodes/lora_stacker.py index 6cf2ef38..5b67ea83 100644 --- a/py/nodes/lora_stacker.py +++ b/py/nodes/lora_stacker.py @@ -14,12 +14,9 @@ class LoraStacker: def INPUT_TYPES(cls): return { "required": { - "text": ("STRING", { - "multiline": True, - "pysssss.autocomplete": False, - "dynamicPrompts": True, + "text": ("AUTOCOMPLETE_TEXT_LORAS", { + "placeholder": "Type LoRA syntax...", "tooltip": "Format: separated by spaces or punctuation", - "placeholder": "LoRA syntax input: " }), }, "optional": FlexibleOptionalInputType(any_type), diff --git a/py/nodes/prompt.py b/py/nodes/prompt.py index 91c2409a..18d72b56 100644 --- a/py/nodes/prompt.py +++ b/py/nodes/prompt.py @@ -15,11 +15,9 @@ class PromptLoraManager: return { "required": { "text": ( - 'STRING', + "AUTOCOMPLETE_TEXT_EMBEDDINGS", { - "multiline": True, - "pysssss.autocomplete": False, - "dynamicPrompts": True, + "placeholder": "Enter prompt...", "tooltip": "The text to be encoded.", }, ), diff --git a/py/nodes/wanvideo_lora_select.py b/py/nodes/wanvideo_lora_select.py index 50ea9616..29f8edd3 100644 --- a/py/nodes/wanvideo_lora_select.py +++ b/py/nodes/wanvideo_lora_select.py @@ -15,12 +15,9 @@ class WanVideoLoraSelectLM: "required": { "low_mem_load": ("BOOLEAN", {"default": False, "tooltip": "Load LORA models with less VRAM usage, slower loading. This affects ALL LoRAs, not just the current ones. No effect if merge_loras is False"}), "merge_loras": ("BOOLEAN", {"default": True, "tooltip": "Merge LoRAs into the model, otherwise they are loaded on the fly. Always disabled for GGUF and scaled fp8 models. This affects ALL LoRAs, not just the current one"}), - "text": ("STRING", { - "multiline": True, - "pysssss.autocomplete": False, - "dynamicPrompts": True, + "text": ("AUTOCOMPLETE_TEXT_LORAS", { + "placeholder": "Type LoRA syntax...", "tooltip": "Format: separated by spaces or punctuation", - "placeholder": "LoRA syntax input: " }), }, "optional": FlexibleOptionalInputType(any_type), diff --git a/tests/frontend/components/loraLoader.triggerWords.test.js b/tests/frontend/components/loraLoader.triggerWords.test.js index 03ba38ba..fc25a768 100644 --- a/tests/frontend/components/loraLoader.triggerWords.test.js +++ b/tests/frontend/components/loraLoader.triggerWords.test.js @@ -35,7 +35,6 @@ vi.mock(API_MODULE, () => ({ const collectActiveLorasFromChain = vi.fn(); const updateConnectedTriggerWords = vi.fn(); const mergeLoras = vi.fn(); -const setupInputWidgetWithAutocomplete = vi.fn(); const getAllGraphNodes = vi.fn(); const getNodeFromGraph = vi.fn(); @@ -43,7 +42,6 @@ vi.mock(UTILS_MODULE, () => ({ collectActiveLorasFromChain, updateConnectedTriggerWords, mergeLoras, - setupInputWidgetWithAutocomplete, chainCallback: (proto, property, callback) => { proto[property] = callback; }, @@ -73,11 +71,6 @@ describe("Lora Loader trigger word updates", () => { mergeLoras.mockClear(); mergeLoras.mockImplementation(() => [{ name: "Alpha", active: true }]); - setupInputWidgetWithAutocomplete.mockClear(); - setupInputWidgetWithAutocomplete.mockImplementation( - (_node, _widget, originalCallback) => originalCallback - ); - addLorasWidget.mockClear(); addLorasWidget.mockImplementation((_node, _name, _opts, callback) => ({ widget: { value: [], callback }, @@ -94,27 +87,31 @@ describe("Lora Loader trigger word updates", () => { const nodeType = { comfyClass: "Lora Loader (LoraManager)", prototype: {} }; await extension.beforeRegisterNodeDef(nodeType, {}, {}); + // Create mock widget (AUTOCOMPLETE_TEXT_LORAS type created by Vue widgets) + const inputWidget = { + value: "", + options: {}, + callback: null, // Will be set by onNodeCreated + }; + const node = { comfyClass: "Lora Loader (LoraManager)", - widgets: [ - { - value: "", - options: {}, - inputEl: {}, - }, - ], + widgets: [inputWidget], addInput: vi.fn(), graph: {}, }; nodeType.prototype.onNodeCreated.call(node); - expect(setupInputWidgetWithAutocomplete).toHaveBeenCalled(); + // The widget is now the AUTOCOMPLETE_TEXT_LORAS type, created automatically by Vue widgets + expect(node.inputWidget).toBe(inputWidget); expect(node.lorasWidget).toBeDefined(); - const inputCallback = node.widgets[0].callback; + // The callback should have been set up by onNodeCreated + const inputCallback = inputWidget.callback; expect(typeof inputCallback).toBe("function"); + // Simulate typing in the input widget inputCallback(""); expect(mergeLoras).toHaveBeenCalledWith("", []); @@ -128,4 +125,3 @@ describe("Lora Loader trigger word updates", () => { expect([...triggerWordSet]).toEqual(["Alpha"]); }); }); - diff --git a/tests/frontend/components/nodeModeChange.test.js b/tests/frontend/components/nodeModeChange.test.js index 511a5623..3990c1c6 100644 --- a/tests/frontend/components/nodeModeChange.test.js +++ b/tests/frontend/components/nodeModeChange.test.js @@ -7,7 +7,6 @@ const { LORAS_WIDGET_MODULE, LORA_LOADER_MODULE, LORA_STACKER_MODULE, - VUE_WIDGETS_MODULE, } = vi.hoisted(() => ({ APP_MODULE: new URL("../../../scripts/app.js", import.meta.url).pathname, API_MODULE: new URL("../../../scripts/api.js", import.meta.url).pathname, @@ -15,21 +14,17 @@ const { LORAS_WIDGET_MODULE: new URL("../../../web/comfyui/loras_widget.js", import.meta.url).pathname, LORA_LOADER_MODULE: new URL("../../../web/comfyui/lora_loader.js", import.meta.url).pathname, LORA_STACKER_MODULE: new URL("../../../web/comfyui/lora_stacker.js", import.meta.url).pathname, - VUE_WIDGETS_MODULE: new URL("../../../web/comfyui/vue-widgets/lora-manager-widgets.js", import.meta.url).pathname, })); const extensionState = { loraLoader: null, loraStacker: null, - vueWidgets: null, }; const registerExtensionMock = vi.fn((extension) => { if (extension.name === "LoraManager.LoraLoader") { extensionState.loraLoader = extension; } else if (extension.name === "LoraManager.LoraStacker") { extensionState.loraStacker = extension; - } else if (extension.name === "LoraManager.VueWidgets") { - extensionState.vueWidgets = extension; } }); @@ -51,7 +46,6 @@ const updateConnectedTriggerWords = vi.fn(); const updateDownstreamLoaders = vi.fn(); const getActiveLorasFromNode = vi.fn(); const mergeLoras = vi.fn(); -const setupInputWidgetWithAutocomplete = vi.fn(); const getAllGraphNodes = vi.fn(); const getNodeFromGraph = vi.fn(); const getNodeKey = vi.fn(); @@ -69,7 +63,6 @@ vi.mock(UTILS_MODULE, async (importOriginal) => { updateDownstreamLoaders, getActiveLorasFromNode, mergeLoras, - setupInputWidgetWithAutocomplete, chainCallback, getAllGraphNodes, getNodeFromGraph, @@ -90,7 +83,6 @@ describe("Node mode change handling", () => { extensionState.loraLoader = null; extensionState.loraStacker = null; - extensionState.vueWidgets = null; registerExtensionMock.mockClear(); collectActiveLorasFromChain.mockClear(); @@ -106,11 +98,6 @@ describe("Node mode change handling", () => { mergeLoras.mockClear(); mergeLoras.mockImplementation(() => [{ name: "Alpha", active: true }]); - setupInputWidgetWithAutocomplete.mockClear(); - setupInputWidgetWithAutocomplete.mockImplementation( - (_node, _widget, originalCallback) => originalCallback - ); - addLorasWidget.mockClear(); addLorasWidget.mockImplementation((_node, _name, _opts, callback) => ({ widget: { value: [], callback }, @@ -118,33 +105,27 @@ describe("Node mode change handling", () => { }); describe("Lora Stacker mode change handling", () => { - let node, extension, vueWidgetsExtension; + let node, extension; beforeEach(async () => { - // Import the Vue widgets module first to register mode change handlers - await import(VUE_WIDGETS_MODULE); - await import(LORA_STACKER_MODULE); expect(registerExtensionMock).toHaveBeenCalled(); extension = extensionState.loraStacker; expect(extension).toBeDefined(); - vueWidgetsExtension = extensionState.vueWidgets; - expect(vueWidgetsExtension).toBeDefined(); const nodeType = { comfyClass: "Lora Stacker (LoraManager)", prototype: {} }; const nodeData = { name: "Lora Stacker (LoraManager)" }; - // Call both extensions' beforeRegisterNodeDef await extension.beforeRegisterNodeDef(nodeType, nodeData, {}); - await vueWidgetsExtension.beforeRegisterNodeDef(nodeType, nodeData, {}); // Create widgets with proper structure for lora_stacker.js + // Widget at index 0 is the AUTOCOMPLETE_TEXT_LORAS widget (created by Vue widgets) const inputWidget = { - name: "input", + name: "text", value: "", - options: {}, // lora_stacker.js:35 expects options to exist - callback: () => {}, + options: {}, + callback: null, // Will be set by onNodeCreated }; const lorasWidget = { @@ -173,12 +154,6 @@ describe("Node mode change handling", () => { const initialMode = node.mode; expect(initialMode).toBe(0); - // Verify that the mode property is configured as a custom property descriptor - // (set up by the mode change handler from Vue widgets) - const modeDescriptor = Object.getOwnPropertyDescriptor(node, 'mode'); - expect(modeDescriptor).toBeDefined(); - expect(modeDescriptor.set).toBeInstanceOf(Function); - // Change mode from 0 to 3 node.mode = 3; @@ -187,14 +162,7 @@ describe("Node mode change handling", () => { }); it("should update trigger words based on node activity when mode changes", () => { - // The loras widget has Alpha and Beta as active - const activeLoras = new Set(["Alpha", "Beta"]); - - // Verify that the mode property is configured with a custom setter - const modeDescriptor = Object.getOwnPropertyDescriptor(node, 'mode'); - expect(modeDescriptor?.set).toBeInstanceOf(Function); - - // Change to active mode (0) - the mode setter should handle this + // Change to active mode (0) node.mode = 0; expect(node.mode).toBe(0); @@ -221,13 +189,14 @@ describe("Node mode change handling", () => { const nodeType = { comfyClass: "Lora Loader (LoraManager)", prototype: {} }; await extension.beforeRegisterNodeDef(nodeType, {}, {}); + // Widget at index 0 is the AUTOCOMPLETE_TEXT_LORAS widget (created by Vue widgets) node = { comfyClass: "Lora Loader (LoraManager)", widgets: [ { value: "", options: {}, - callback: () => {}, + callback: null, // Will be set by onNodeCreated }, ], addInput: vi.fn(), @@ -291,4 +260,4 @@ describe("Node mode change handling", () => { ); }); }); -}); \ No newline at end of file +}); diff --git a/vue-widgets/src/components/AutocompleteTextWidget.vue b/vue-widgets/src/components/AutocompleteTextWidget.vue new file mode 100644 index 00000000..27c2bf33 --- /dev/null +++ b/vue-widgets/src/components/AutocompleteTextWidget.vue @@ -0,0 +1,143 @@ +