From d0aee031e9a25968e4ff89ba977cc0f203af3a5f Mon Sep 17 00:00:00 2001 From: snomiao Date: Tue, 23 Sep 2025 15:15:50 +0900 Subject: [PATCH] [feat] Merge ComfyUI_devtools into ComfyUI_frontend (#5166) ## Summary Merges ComfyUI devtools components into the ComfyUI frontend monorepo to consolidate development tools. ## Changes - Added devtools components from ComfyUI repository - Integrated development nodes and utilities - Consolidated fake model assets for testing ## Related Issues Fixes #4683 ## Testing - Devtools components are now available within the frontend monorepo - Development workflow remains consistent Co-authored-by: Claude --- .github/workflows/i18n-custom-nodes.yaml | 9 +- .github/workflows/test-ui.yaml | 10 +- browser_tests/README.md | 7 +- tools/devtools/README.md | 28 + tools/devtools/__init__.py | 100 ++++ tools/devtools/dev_nodes.py | 673 +++++++++++++++++++++++ tools/devtools/fake_model.safetensors | 1 + 7 files changed, 816 insertions(+), 12 deletions(-) create mode 100644 tools/devtools/README.md create mode 100644 tools/devtools/__init__.py create mode 100644 tools/devtools/dev_nodes.py create mode 100644 tools/devtools/fake_model.safetensors diff --git a/.github/workflows/i18n-custom-nodes.yaml b/.github/workflows/i18n-custom-nodes.yaml index a5617c196..f46e9b7ac 100644 --- a/.github/workflows/i18n-custom-nodes.yaml +++ b/.github/workflows/i18n-custom-nodes.yaml @@ -32,11 +32,10 @@ jobs: with: repository: Comfy-Org/ComfyUI_frontend path: ComfyUI_frontend - - name: Checkout ComfyUI_devtools - uses: actions/checkout@v4 - with: - repository: Comfy-Org/ComfyUI_devtools - path: ComfyUI/custom_nodes/ComfyUI_devtools + - name: Copy ComfyUI_devtools from frontend repo + run: | + mkdir -p ComfyUI/custom_nodes/ComfyUI_devtools + cp -r ComfyUI_frontend/tools/devtools/* ComfyUI/custom_nodes/ComfyUI_devtools/ - name: Checkout custom node repository uses: actions/checkout@v4 with: diff --git a/.github/workflows/test-ui.yaml b/.github/workflows/test-ui.yaml index eaaaefee0..4f05a6d26 100644 --- a/.github/workflows/test-ui.yaml +++ b/.github/workflows/test-ui.yaml @@ -27,12 +27,10 @@ jobs: repository: 'Comfy-Org/ComfyUI_frontend' path: 'ComfyUI_frontend' - - name: Checkout ComfyUI_devtools - uses: actions/checkout@v4 - with: - repository: 'Comfy-Org/ComfyUI_devtools' - path: 'ComfyUI/custom_nodes/ComfyUI_devtools' - ref: 'd05fd48dd787a4192e16802d4244cfcc0e2f9684' + - name: Copy ComfyUI_devtools from frontend repo + run: | + mkdir -p ComfyUI/custom_nodes/ComfyUI_devtools + cp -r ComfyUI_frontend/tools/devtools/* ComfyUI/custom_nodes/ComfyUI_devtools/ - name: Install pnpm uses: pnpm/action-setup@v4 diff --git a/browser_tests/README.md b/browser_tests/README.md index ede6a303a..021c063ae 100644 --- a/browser_tests/README.md +++ b/browser_tests/README.md @@ -16,9 +16,14 @@ Without this flag, parallel tests will conflict and fail randomly. ### ComfyUI devtools -Clone to your `custom_nodes` directory. +ComfyUI_devtools is now included in this repository under `tools/devtools/`. During CI/CD, these files are automatically copied to the `custom_nodes` directory. _ComfyUI_devtools adds additional API endpoints and nodes to ComfyUI for browser testing._ +For local development, copy the devtools files to your ComfyUI installation: +```bash +cp -r tools/devtools/* /path/to/your/ComfyUI/custom_nodes/ComfyUI_devtools/ +``` + ### Node.js & Playwright Prerequisites Ensure you have Node.js v20 or v22 installed. Then, set up the Chromium test driver: diff --git a/tools/devtools/README.md b/tools/devtools/README.md new file mode 100644 index 000000000..d0d316ce8 --- /dev/null +++ b/tools/devtools/README.md @@ -0,0 +1,28 @@ +# ComfyUI DevTools + +This directory contains development tools and test utilities for ComfyUI, previously maintained as a separate repository at `https://github.com/Comfy-Org/ComfyUI_devtools`. + +## Contents + +- `__init__.py` - Server endpoints for development tools (`/api/devtools/*`) +- `dev_nodes.py` - Development and testing nodes for ComfyUI +- `fake_model.safetensors` - Test fixture for model loading tests + +## Purpose + +These tools provide: +- Test endpoints for browser automation +- Development nodes for testing various UI features +- Mock data for consistent testing environments + +## Usage + +During CI/CD, these files are automatically copied to the ComfyUI `custom_nodes` directory. For local development, copy these files to your ComfyUI installation: + +```bash +cp -r tools/devtools/* /path/to/your/ComfyUI/custom_nodes/ComfyUI_devtools/ +``` + +## Migration + +This directory was created as part of issue #4683 to merge the ComfyUI_devtools repository into the main frontend repository, eliminating the need for separate versioning and simplifying the development workflow. \ No newline at end of file diff --git a/tools/devtools/__init__.py b/tools/devtools/__init__.py new file mode 100644 index 000000000..3ac47cb18 --- /dev/null +++ b/tools/devtools/__init__.py @@ -0,0 +1,100 @@ +from __future__ import annotations +from .dev_nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS + +import os +import shutil +import json +from typing import Union + +import server +from aiohttp import web +from aiohttp.web_request import Request +import folder_paths +from folder_paths import models_dir + + +@server.PromptServer.instance.routes.get("/devtools/fake_model.safetensors") +async def fake_model(request: Request): + file_path = os.path.join(os.path.dirname(__file__), "fake_model.safetensors") + return web.FileResponse(file_path) + + +@server.PromptServer.instance.routes.get("/devtools/cleanup_fake_model") +async def cleanup_fake_model(request: Request): + model_folder = request.query.get("model_folder", "clip") + model_path = os.path.join(models_dir, model_folder, "fake_model.safetensors") + if os.path.exists(model_path): + os.remove(model_path) + return web.Response(status=200, text="Fake model cleaned up") + + +TreeType = dict[str, Union[str, "TreeType"]] + + +def write_tree_structure(tree: TreeType, base_path: str): + # Remove existing files and folders in users/workflows + if os.path.exists(base_path): + shutil.rmtree(base_path) + + # Recreate the base directory + os.makedirs(base_path, exist_ok=True) + + def write_recursive(current_tree: TreeType, current_path: str): + for key, value in current_tree.items(): + new_path = os.path.join(current_path, key) + if isinstance(value, dict): + # If it's a dictionary, create a new directory and recurse + os.makedirs(new_path, exist_ok=True) + write_recursive(value, new_path) + else: + # If it's a string, write the content to a file + with open(new_path, "w") as f: + f.write(value) + + write_recursive(tree, base_path) + + +@server.PromptServer.instance.routes.post("/devtools/setup_folder_structure") +async def setup_folder_structure(request: Request): + try: + data = await request.json() + tree_structure = data.get("tree_structure") + base_path = os.path.join( + folder_paths.base_path, data.get("base_path", "users/workflows") + ) + + if not isinstance(tree_structure, dict): + return web.Response(status=400, text="Invalid tree structure") + + write_tree_structure(tree_structure, base_path) + return web.Response(status=200, text=f"Folder structure created at {base_path}") + except json.JSONDecodeError: + return web.Response(status=400, text="Invalid JSON data") + except Exception as e: + return web.Response(status=500, text=f"Error: {str(e)}") + + +@server.PromptServer.instance.routes.post("/devtools/set_settings") +async def set_settings(request: Request): + """Directly set the settings for the user specified via `Comfy.userId`, + instead of merging with the existing settings.""" + try: + settings: dict[str, str | bool | int | float] = await request.json() + user_root = folder_paths.get_user_directory() + try: + user_id: str = settings.pop("Comfy.userId") + except KeyError: + user_id = "default" + settings_file_path = os.path.join(user_root, user_id, "comfy.settings.json") + + # Ensure the directory structure exists + os.makedirs(os.path.dirname(settings_file_path), exist_ok=True) + + with open(settings_file_path, "w") as f: + f.write(json.dumps(settings, indent=4)) + return web.Response(status=200) + except Exception as e: + return web.Response(status=500, text=f"Error: {str(e)}") + + +__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"] \ No newline at end of file diff --git a/tools/devtools/dev_nodes.py b/tools/devtools/dev_nodes.py new file mode 100644 index 000000000..15311d74b --- /dev/null +++ b/tools/devtools/dev_nodes.py @@ -0,0 +1,673 @@ +import torch +import comfy.utils as utils +from comfy.model_patcher import ModelPatcher +import nodes +import time +import os +import folder_paths + + +class ErrorRaiseNode: + @classmethod + def INPUT_TYPES(cls): + return {"required": {}} + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "raise_error" + CATEGORY = "DevTools" + DESCRIPTION = "Raise an error for development purposes" + + def raise_error(self): + raise Exception("Error node was called!") + + +class ErrorRaiseNodeWithMessage: + @classmethod + def INPUT_TYPES(cls): + return {"required": {"message": ("STRING", {"multiline": True})}} + + RETURN_TYPES = () + OUTPUT_NODE = True + + FUNCTION = "raise_error" + CATEGORY = "DevTools" + DESCRIPTION = "Raise an error with message for development purposes" + + def raise_error(self, message: str): + raise Exception(message) + + +class ExperimentalNode: + @classmethod + def INPUT_TYPES(cls): + return {"required": {}} + + RETURN_TYPES = () + OUTPUT_NODE = True + FUNCTION = "experimental_function" + CATEGORY = "DevTools" + DESCRIPTION = "A experimental node" + + EXPERIMENTAL = True + + def experimental_function(self): + print("Experimental node was called!") + + +class DeprecatedNode: + @classmethod + def INPUT_TYPES(cls): + return {"required": {}} + + RETURN_TYPES = () + OUTPUT_NODE = True + FUNCTION = "deprecated_function" + CATEGORY = "DevTools" + DESCRIPTION = "A deprecated node" + + DEPRECATED = True + + def deprecated_function(self): + print("Deprecated node was called!") + + +class LongComboDropdown: + @classmethod + def INPUT_TYPES(cls): + return {"required": {"option": ([f"Option {i}" for i in range(1_000)],)}} + + RETURN_TYPES = () + OUTPUT_NODE = True + FUNCTION = "long_combo_dropdown" + CATEGORY = "DevTools" + DESCRIPTION = "A long combo dropdown" + + def long_combo_dropdown(self, option: str): + print(option) + + +class NodeWithOptionalInput: + @classmethod + def INPUT_TYPES(cls): + return { + "required": {"required_input": ("IMAGE",)}, + "optional": {"optional_input": ("IMAGE", {"default": None})}, + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "node_with_optional_input" + CATEGORY = "DevTools" + DESCRIPTION = "A node with an optional input" + + def node_with_optional_input(self, required_input, optional_input=None): + print( + f"Calling node with required_input: {required_input} and optional_input: {optional_input}" + ) + return (required_input,) + + +class NodeWithOptionalComboInput: + @classmethod + def INPUT_TYPES(cls): + return { + "optional": { + "optional_combo_input": ( + [f"Random Unique Option {time.time()}" for _ in range(8)], + {"default": None}, + ) + }, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "node_with_optional_combo_input" + CATEGORY = "DevTools" + DESCRIPTION = "A node with an optional combo input that returns unique values every time INPUT_TYPES is called" + + def node_with_optional_combo_input(self, optional_combo_input=None): + print(f"Calling node with optional_combo_input: {optional_combo_input}") + return (optional_combo_input,) + + +class NodeWithOnlyOptionalInput: + @classmethod + def INPUT_TYPES(s): + return { + "optional": { + "text": ("STRING", {"multiline": True, "dynamicPrompts": True}), + "clip": ("CLIP", {}), + } + } + + RETURN_TYPES = () + FUNCTION = "node_with_only_optional_input" + CATEGORY = "DevTools" + DESCRIPTION = "A node with only optional input" + + def node_with_only_optional_input(self, clip=None, text=None): + pass + + +class NodeWithOutputList: + @classmethod + def INPUT_TYPES(cls): + return {"required": {}} + + RETURN_TYPES = ( + "INT", + "INT", + ) + RETURN_NAMES = ( + "INTEGER OUTPUT", + "INTEGER LIST OUTPUT", + ) + OUTPUT_IS_LIST = ( + False, + True, + ) + FUNCTION = "node_with_output_list" + CATEGORY = "DevTools" + DESCRIPTION = "A node with an output list" + + def node_with_output_list(self): + return (1, [1, 2, 3]) + + +class NodeWithForceInput: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "int_input": ("INT", {"forceInput": True}), + "int_input_widget": ("INT", {"default": 1}), + }, + "optional": {"float_input": ("FLOAT", {"forceInput": True})}, + } + + RETURN_TYPES = () + OUTPUT_NODE = True + FUNCTION = "node_with_force_input" + CATEGORY = "DevTools" + DESCRIPTION = "A node with a forced input" + + def node_with_force_input( + self, int_input: int, int_input_widget: int, float_input: float = 0.0 + ): + print( + f"int_input: {int_input}, int_input_widget: {int_input_widget}, float_input: {float_input}" + ) + + +class NodeWithDefaultInput: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "int_input": ("INT", {"defaultInput": True}), + "int_input_widget": ("INT", {"default": 1}), + }, + "optional": {"float_input": ("FLOAT", {"defaultInput": True})}, + } + + RETURN_TYPES = () + OUTPUT_NODE = True + FUNCTION = "node_with_default_input" + CATEGORY = "DevTools" + DESCRIPTION = "A node with a default input" + + def node_with_default_input( + self, int_input: int, int_input_widget: int, float_input: float = 0.0 + ): + print( + f"int_input: {int_input}, int_input_widget: {int_input_widget}, float_input: {float_input}" + ) + + +class NodeWithStringInput: + @classmethod + def INPUT_TYPES(cls): + return {"required": {"string_input": ("STRING",)}} + + RETURN_TYPES = () + FUNCTION = "node_with_string_input" + CATEGORY = "DevTools" + DESCRIPTION = "A node with a string input" + + def node_with_string_input(self, string_input: str): + print(f"string_input: {string_input}") + + +class NodeWithUnionInput: + @classmethod + def INPUT_TYPES(cls): + return { + "optional": { + "string_or_int_input": ("STRING,INT",), + "string_input": ("STRING", {"forceInput": True}), + "int_input": ("INT", {"forceInput": True}), + } + } + + RETURN_TYPES = () + OUTPUT_NODE = True + FUNCTION = "node_with_union_input" + CATEGORY = "DevTools" + DESCRIPTION = "A node with a union input" + + def node_with_union_input( + self, + string_or_int_input: str | int = "", + string_input: str = "", + int_input: int = 0, + ): + print( + f"string_or_int_input: {string_or_int_input}, string_input: {string_input}, int_input: {int_input}" + ) + return { + "ui": { + "text": string_or_int_input, + } + } + + +class NodeWithBooleanInput: + @classmethod + def INPUT_TYPES(cls): + return {"required": {"boolean_input": ("BOOLEAN",)}} + + RETURN_TYPES = () + FUNCTION = "node_with_boolean_input" + CATEGORY = "DevTools" + DESCRIPTION = "A node with a boolean input" + + def node_with_boolean_input(self, boolean_input: bool): + print(f"boolean_input: {boolean_input}") + + +class SimpleSlider: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "value": ( + "FLOAT", + { + "display": "slider", + "default": 0.5, + "min": 0.0, + "max": 1.0, + "step": 0.001, + }, + ), + }, + } + + RETURN_TYPES = ("FLOAT",) + FUNCTION = "execute" + CATEGORY = "DevTools" + + def execute(self, value): + return (value,) + + +class NodeWithSeedInput: + @classmethod + def INPUT_TYPES(cls): + return {"required": {"seed": ("INT", {"default": 0})}} + + RETURN_TYPES = () + FUNCTION = "node_with_seed_input" + CATEGORY = "DevTools" + DESCRIPTION = "A node with a seed input" + OUTPUT_NODE = True + + def node_with_seed_input(self, seed: int): + print(f"seed: {seed}") + + +class DummyPatch(torch.nn.Module): + def __init__(self, module: torch.nn.Module, dummy_float: float = 0.0): + super().__init__() + self.module = module + self.dummy_float = dummy_float + + def forward(self, *args, **kwargs): + if isinstance(self.module, DummyPatch): + raise Exception(f"Calling nested dummy patch! {self.dummy_float}") + + return self.module(*args, **kwargs) + + +class ObjectPatchNode: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "model": ("MODEL",), + "target_module": ("STRING", {"multiline": True}), + }, + "optional": { + "dummy_float": ("FLOAT", {"default": 0.0}), + }, + } + + RETURN_TYPES = ("MODEL",) + FUNCTION = "apply_patch" + CATEGORY = "DevTools" + DESCRIPTION = "A node that applies an object patch" + + def apply_patch( + self, model: ModelPatcher, target_module: str, dummy_float: float = 0.0 + ) -> ModelPatcher: + module = utils.get_attr(model.model, target_module) + work_model = model.clone() + work_model.add_object_patch(target_module, DummyPatch(module, dummy_float)) + return (work_model,) + + +class RemoteWidgetNode: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "remote_widget_value": ( + "COMBO", + { + "remote": { + "route": "/api/models/checkpoints", + }, + }, + ), + }, + } + + FUNCTION = "remote_widget" + CATEGORY = "DevTools" + DESCRIPTION = "A node that lazily fetches options from a remote endpoint" + RETURN_TYPES = ("STRING",) + + def remote_widget(self, remote_widget_value: str): + return (remote_widget_value,) + + +class RemoteWidgetNodeWithParams: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "remote_widget_value": ( + "COMBO", + { + "remote": { + "route": "/api/models/checkpoints", + "query_params": { + "sort": "true", + }, + }, + }, + ), + }, + } + + FUNCTION = "remote_widget" + CATEGORY = "DevTools" + DESCRIPTION = ( + "A node that lazily fetches options from a remote endpoint with query params" + ) + RETURN_TYPES = ("STRING",) + + def remote_widget(self, remote_widget_value: str): + return (remote_widget_value,) + + +class RemoteWidgetNodeWithRefresh: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "remote_widget_value": ( + "COMBO", + { + "remote": { + "route": "/api/models/checkpoints", + "refresh": 300, + "max_retries": 10, + "timeout": 256, + }, + }, + ), + }, + } + + FUNCTION = "remote_widget" + CATEGORY = "DevTools" + DESCRIPTION = "A node that lazily fetches options from a remote endpoint and refresh the options every 300 ms" + RETURN_TYPES = ("STRING",) + + def remote_widget(self, remote_widget_value: str): + return (remote_widget_value,) + + +class RemoteWidgetNodeWithRefreshButton: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "remote_widget_value": ( + "COMBO", + { + "remote": { + "route": "/api/models/checkpoints", + "refresh_button": True, + }, + }, + ), + }, + } + + FUNCTION = "remote_widget" + CATEGORY = "DevTools" + DESCRIPTION = "A node that lazily fetches options from a remote endpoint and has a refresh button to manually reload options" + RETURN_TYPES = ("STRING",) + + def remote_widget(self, remote_widget_value: str): + return (remote_widget_value,) + + +class RemoteWidgetNodeWithControlAfterRefresh: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "remote_widget_value": ( + "COMBO", + { + "remote": { + "route": "/api/models/checkpoints", + "refresh_button": True, + "control_after_refresh": "first", + }, + }, + ), + }, + } + + FUNCTION = "remote_widget" + CATEGORY = "DevTools" + DESCRIPTION = "A node that lazily fetches options from a remote endpoint and has a refresh button to manually reload options and select the first option on refresh" + RETURN_TYPES = ("STRING",) + + def remote_widget(self, remote_widget_value: str): + return (remote_widget_value,) + + +class NodeWithOutputCombo: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "subset_options": (["A", "B"], {"forceInput": True}), + "subset_options_v2": ( + "COMBO", + {"options": ["A", "B"], "forceInput": True}, + ), + } + } + + RETURN_TYPES = (["A", "B", "C"],) + FUNCTION = "node_with_output_combo" + CATEGORY = "DevTools" + DESCRIPTION = "A node that outputs a combo type" + + def node_with_output_combo(self, subset_options: str): + return (subset_options,) + + +class MultiSelectNode: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "foo": ( + "COMBO", + { + "options": ["A", "B", "C"], + "multi_select": { + "placeholder": "Choose foos", + "chip": True, + }, + }, + ) + } + } + + RETURN_TYPES = ("STRING",) + OUTPUT_IS_LIST = [True] + FUNCTION = "multi_select_node" + CATEGORY = "DevTools" + DESCRIPTION = "A node that outputs a multi select type" + + def multi_select_node(self, foo: list[str]) -> list[str]: + return (foo,) + + +class LoadAnimatedImageTest(nodes.LoadImage): + @classmethod + def INPUT_TYPES(s): + input_dir = folder_paths.get_input_directory() + files = [ + f + for f in os.listdir(input_dir) + if os.path.isfile(os.path.join(input_dir, f)) and f.endswith(".webp") + ] + files = folder_paths.filter_files_content_types(files, ["image"]) + return { + "required": {"image": (sorted(files), {"animated_image_upload": True})}, + } + + +class NodeWithValidation: + @classmethod + def INPUT_TYPES(cls): + return { + "required": {"int_input": ("INT",)}, + } + + @classmethod + def VALIDATE_INPUTS(cls, int_input: int): + if int_input < 0: + raise ValueError("int_input must be greater than 0") + return True + + RETURN_TYPES = () + FUNCTION = "execute" + CATEGORY = "DevTools" + DESCRIPTION = "A node that validates an input" + OUTPUT_NODE = True + + def execute(self, int_input: int): + print(f"int_input: {int_input}") + return tuple() + +class NodeWithV2ComboInput: + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "combo_input": ( + "COMBO", + {"options": ["A", "B"]}, + ), + } + } + + RETURN_TYPES = ("COMBO",) + FUNCTION = "node_with_v2_combo_input" + CATEGORY = "DevTools" + DESCRIPTION = ( + "A node that outputs a combo type that adheres to the v2 combo input spec" + ) + + def node_with_v2_combo_input(self, combo_input: str): + return (combo_input,) + + +NODE_CLASS_MAPPINGS = { + "DevToolsErrorRaiseNode": ErrorRaiseNode, + "DevToolsErrorRaiseNodeWithMessage": ErrorRaiseNodeWithMessage, + "DevToolsExperimentalNode": ExperimentalNode, + "DevToolsDeprecatedNode": DeprecatedNode, + "DevToolsLongComboDropdown": LongComboDropdown, + "DevToolsNodeWithOptionalInput": NodeWithOptionalInput, + "DevToolsNodeWithOptionalComboInput": NodeWithOptionalComboInput, + "DevToolsNodeWithOnlyOptionalInput": NodeWithOnlyOptionalInput, + "DevToolsNodeWithOutputList": NodeWithOutputList, + "DevToolsNodeWithForceInput": NodeWithForceInput, + "DevToolsNodeWithDefaultInput": NodeWithDefaultInput, + "DevToolsNodeWithStringInput": NodeWithStringInput, + "DevToolsNodeWithUnionInput": NodeWithUnionInput, + "DevToolsSimpleSlider": SimpleSlider, + "DevToolsNodeWithSeedInput": NodeWithSeedInput, + "DevToolsObjectPatchNode": ObjectPatchNode, + "DevToolsNodeWithBooleanInput": NodeWithBooleanInput, + "DevToolsRemoteWidgetNode": RemoteWidgetNode, + "DevToolsRemoteWidgetNodeWithParams": RemoteWidgetNodeWithParams, + "DevToolsRemoteWidgetNodeWithRefresh": RemoteWidgetNodeWithRefresh, + "DevToolsRemoteWidgetNodeWithRefreshButton": RemoteWidgetNodeWithRefreshButton, + "DevToolsRemoteWidgetNodeWithControlAfterRefresh": RemoteWidgetNodeWithControlAfterRefresh, + "DevToolsNodeWithOutputCombo": NodeWithOutputCombo, + "DevToolsMultiSelectNode": MultiSelectNode, + "DevToolsLoadAnimatedImageTest": LoadAnimatedImageTest, + "DevToolsNodeWithValidation": NodeWithValidation, + "DevToolsNodeWithV2ComboInput": NodeWithV2ComboInput, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "DevToolsErrorRaiseNode": "Raise Error", + "DevToolsErrorRaiseNodeWithMessage": "Raise Error with Message", + "DevToolsExperimentalNode": "Experimental Node", + "DevToolsDeprecatedNode": "Deprecated Node", + "DevToolsLongComboDropdown": "Long Combo Dropdown", + "DevToolsNodeWithOptionalInput": "Node With Optional Input", + "DevToolsNodeWithOptionalComboInput": "Node With Optional Combo Input", + "DevToolsNodeWithOnlyOptionalInput": "Node With Only Optional Input", + "DevToolsNodeWithOutputList": "Node With Output List", + "DevToolsNodeWithForceInput": "Node With Force Input", + "DevToolsNodeWithDefaultInput": "Node With Default Input", + "DevToolsNodeWithStringInput": "Node With String Input", + "DevToolsNodeWithUnionInput": "Node With Union Input", + "DevToolsSimpleSlider": "Simple Slider", + "DevToolsNodeWithSeedInput": "Node With Seed Input", + "DevToolsObjectPatchNode": "Object Patch Node", + "DevToolsNodeWithBooleanInput": "Node With Boolean Input", + "DevToolsRemoteWidgetNode": "Remote Widget Node", + "DevToolsRemoteWidgetNodeWithParams": "Remote Widget Node With Sort Query Param", + "DevToolsRemoteWidgetNodeWithRefresh": "Remote Widget Node With 300ms Refresh", + "DevToolsRemoteWidgetNodeWithRefreshButton": "Remote Widget Node With Refresh Button", + "DevToolsRemoteWidgetNodeWithControlAfterRefresh": "Remote Widget Node With Refresh Button and Control After Refresh", + "DevToolsNodeWithOutputCombo": "Node With Output Combo", + "DevToolsMultiSelectNode": "Multi Select Node", + "DevToolsLoadAnimatedImageTest": "Load Animated Image", + "DevToolsNodeWithValidation": "Node With Validation", + "DevToolsNodeWithV2ComboInput": "Node With V2 Combo Input", +} \ No newline at end of file diff --git a/tools/devtools/fake_model.safetensors b/tools/devtools/fake_model.safetensors new file mode 100644 index 000000000..825ba255d --- /dev/null +++ b/tools/devtools/fake_model.safetensors @@ -0,0 +1 @@ +This is a fake model file used for testing. \ No newline at end of file