├── .github └── workflows │ └── publish.yml ├── .gitignore ├── AiHelper.py ├── Comfly.py ├── Comflyapi.json ├── LICENSE ├── README.md ├── __init__.py ├── docs └── mjstyle │ ├── mj_art.json │ └── mj_hd.json ├── pyproject.toml ├── requirements.txt ├── utils.py ├── web └── js │ ├── Comfly_kling_videoPreview.js │ ├── Comfly_manager.js │ ├── Comfly_mjstyle.js │ ├── chat_button.js │ ├── comfly.js │ ├── drag_handle.js │ ├── help_button.js │ └── split_image.js └── workflow ├── Comfly Doubao SeedEdit.json ├── ComflyGeminiAPI.json ├── Comfly_lip_sync.json ├── Comfly_mj.json ├── comfly-gemmi-editimage.json ├── comfly-gpt4o-api.json ├── comfly-kling-V1.0-text2video.json ├── comfly-kling-image2video.json └── comfly-kling-text2video.json /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to Comfy registry 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | paths: 9 | - "pyproject.toml" 10 | 11 | permissions: 12 | issues: write 13 | 14 | jobs: 15 | publish-node: 16 | name: Publish Custom Node to registry 17 | runs-on: ubuntu-latest 18 | if: ${{ github.repository_owner == 'ainewsto' }} 19 | steps: 20 | - name: Check out code 21 | uses: actions/checkout@v4 22 | with: 23 | submodules: true 24 | - name: Publish Custom Node 25 | uses: Comfy-Org/publish-node-action@v1 26 | with: 27 | ## Add your own personal access token to your Github Repository secrets and reference it here. 28 | personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # API key 2 | Comflyapi.json merge=ours 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | cover/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | .pybuilder/ 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | # For a library or package, you might want to ignore these files since the code is 90 | # intended to run in multiple environments; otherwise, check them in: 91 | # .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # UV 101 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. 102 | # This is especially recommended for binary packages to ensure reproducibility, and is more 103 | # commonly ignored for libraries. 104 | #uv.lock 105 | 106 | # poetry 107 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 108 | # This is especially recommended for binary packages to ensure reproducibility, and is more 109 | # commonly ignored for libraries. 110 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 111 | #poetry.lock 112 | 113 | # pdm 114 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 115 | #pdm.lock 116 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 117 | # in version control. 118 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 119 | .pdm.toml 120 | .pdm-python 121 | .pdm-build/ 122 | 123 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 124 | __pypackages__/ 125 | 126 | # Celery stuff 127 | celerybeat-schedule 128 | celerybeat.pid 129 | 130 | # SageMath parsed files 131 | *.sage.py 132 | 133 | # Environments 134 | .env 135 | .venv 136 | env/ 137 | venv/ 138 | ENV/ 139 | env.bak/ 140 | venv.bak/ 141 | 142 | # Spyder project settings 143 | .spyderproject 144 | .spyproject 145 | 146 | # Rope project settings 147 | .ropeproject 148 | 149 | # mkdocs documentation 150 | /site 151 | 152 | # mypy 153 | .mypy_cache/ 154 | .dmypy.json 155 | dmypy.json 156 | 157 | # Pyre type checker 158 | .pyre/ 159 | 160 | # pytype static type analyzer 161 | .pytype/ 162 | 163 | # Cython debug symbols 164 | cython_debug/ 165 | 166 | # PyCharm 167 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 168 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 169 | # and can be added to the global gitignore or merged into this file. For a more nuclear 170 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 171 | #.idea/ 172 | 173 | # Ruff stuff: 174 | .ruff_cache/ 175 | 176 | # PyPI configuration file 177 | .pypirc 178 | -------------------------------------------------------------------------------- /AiHelper.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import aiohttp 3 | from aiohttp import web 4 | from aiohttp_cors import setup, ResourceOptions 5 | import subprocess 6 | import os 7 | import json 8 | import shutil 9 | import git 10 | import requests 11 | import threading 12 | import logging 13 | import time 14 | import numpy as np 15 | import torch 16 | from PIL import Image 17 | from io import BytesIO 18 | import re 19 | import sys 20 | import zipfile 21 | import sysconfig 22 | import hashlib 23 | from io import StringIO 24 | from contextlib import redirect_stdout, redirect_stderr 25 | import psutil 26 | import urllib.parse 27 | 28 | 29 | async def on_prepare(request, response): 30 | request.start_time = time.time() 31 | 32 | async def on_response(request, response): 33 | pass 34 | 35 | async def on_request_start(request, *args, **kwargs): 36 | pass 37 | 38 | async def on_request_end(request, *args, **kwargs): 39 | pass 40 | 41 | 42 | async def get_python_path(): 43 | current_path = os.path.abspath(__file__) 44 | comfyui_path = os.path.abspath(os.path.join(current_path, "..", "..", "..")) 45 | python_paths = [ 46 | os.path.join(comfyui_path, "python_miniconda", "python.exe"), 47 | os.path.join(comfyui_path, "python_miniconda", "bin", "python"), 48 | os.path.join(comfyui_path, "venv", "bin", "python") 49 | ] 50 | for path in python_paths: 51 | if os.path.exists(path): 52 | return path 53 | return "python" 54 | 55 | async def get_plugins_path(): 56 | current_path = os.path.dirname(os.path.abspath(__file__)) 57 | plugins_dir = os.path.abspath(os.path.join(current_path, "..")) 58 | return plugins_dir 59 | 60 | async def get_plugin_path(plugin_name): 61 | plugins_dir = await get_plugins_path() 62 | plugin_path = os.path.join(plugins_dir, plugin_name) 63 | return plugin_path 64 | 65 | async def get_dependencies(request): 66 | try: 67 | python_path = await get_python_path() 68 | output = subprocess.check_output([python_path, '-m', 'pip', 'list', '--format=json']) 69 | dependencies = json.loads(output) 70 | formatted_dependencies = [f"{dep['name']}=={dep['version']}" for dep in dependencies] 71 | return web.json_response(formatted_dependencies) 72 | except subprocess.CalledProcessError as e: 73 | logging.error(f"Error getting dependencies: {str(e)}") 74 | return web.json_response({'error': str(e)}, status=500) 75 | 76 | async def install_dependency(request): 77 | name = request.query.get('name') 78 | if not name: 79 | return web.json_response({'error': 'Name is required'}, status=400) 80 | try: 81 | python_path = await get_python_path() 82 | 83 | process = subprocess.Popen([python_path, '-m', 'pip', 'install', name], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) 84 | 85 | output = [] 86 | while True: 87 | line = process.stdout.readline() 88 | if line: 89 | logging.info(line.strip()) 90 | output.append(line) 91 | else: 92 | break 93 | 94 | return_code = process.wait() 95 | 96 | if return_code == 0: 97 | response_data = {'message': 'Installation successful', 'output': ''.join(output)} 98 | logging.info(f"Message: {response_data}") 99 | return web.json_response(response_data) 100 | else: 101 | response_data = {'error': 'Installation failed', 'output': ''.join(output)} 102 | logging.error(f"Message: {response_data}") 103 | return web.json_response(response_data, status=500) 104 | 105 | except subprocess.CalledProcessError as e: 106 | logging.error(f"Error installing dependency: {name}") 107 | logging.error(e.output.decode('utf-8')) 108 | return web.json_response({'error': str(e)}, status=500) 109 | 110 | 111 | async def manage_dependency(request): 112 | name = request.query.get('name') 113 | action = request.query.get('action') 114 | if not name or not action: 115 | return web.json_response({'error': 'Name and action are required'}, status=400) 116 | try: 117 | python_path = await get_python_path() 118 | if action == 'uninstall': 119 | subprocess.check_call([python_path, '-m', 'pip', 'uninstall', '-y', name]) 120 | else: 121 | return web.json_response({'error': f'Invalid action: {action}'}, status=400) 122 | return web.json_response({'message': f'{action.capitalize()} successful'}) 123 | except subprocess.CalledProcessError as e: 124 | logging.error(f"Error {action}ing dependency: {name}") 125 | return web.json_response({'error': str(e)}, status=500) 126 | 127 | async def replace_dependency(request): 128 | name = request.query.get('name') 129 | version = request.query.get('version') 130 | if not name or not version: 131 | return web.json_response({'error': 'Name and version are required'}, status=400) 132 | try: 133 | python_path = await get_python_path() 134 | subprocess.check_call([python_path, '-m', 'pip', 'install', f'{name}=={version}']) 135 | return web.json_response({'message': 'Replacement successful'}) 136 | except subprocess.CalledProcessError as e: 137 | logging.error(f"Error replacing dependency: {name} with version {version}") 138 | return web.json_response({'error': str(e)}, status=500) 139 | 140 | comfyui_versions_cache = None 141 | 142 | async def get_comfyui_versions(request): 143 | global comfyui_versions_cache 144 | if comfyui_versions_cache is not None: 145 | return web.json_response(comfyui_versions_cache) 146 | 147 | try: 148 | async with aiohttp.ClientSession() as session: 149 | async with session.get("https://api.github.com/repos/comfyanonymous/ComfyUI/commits?per_page=1000") as response: 150 | if response.status == 200: 151 | commits = await response.json() 152 | versions = [{'id': commit['sha'][:7], 'message': commit['commit']['message'], 'date': commit['commit']['committer']['date']} for commit in commits] 153 | comfyui_versions_cache = versions 154 | return web.json_response(versions) 155 | else: 156 | error_message = f"Error fetching ComfyUI versions: {response.status}" 157 | logging.error(error_message) 158 | raise Exception(error_message) 159 | except Exception as e: 160 | error_message = f"An unexpected error occurred while fetching ComfyUI versions: {str(e)}" 161 | logging.error(error_message) 162 | return web.json_response({'error': error_message}, status=500) 163 | 164 | current_comfyui_version_cache = None 165 | 166 | async def select_comfyui_version(request): 167 | version_id = request.query.get('version_id') 168 | if not version_id: 169 | return web.json_response({'error': 'Version ID is required'}, status=400) 170 | try: 171 | comfyui_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) 172 | repo = git.Repo(comfyui_path) 173 | 174 | repo.remotes.origin.fetch() 175 | 176 | default_branch = repo.active_branch.name 177 | if default_branch.startswith('version-'): 178 | default_branch = next((b.name for b in repo.branches if not b.name.startswith('version-')), 'main') 179 | 180 | repo.git.checkout(version_id) 181 | 182 | if repo.head.is_detached: 183 | repo.heads[default_branch].checkout() 184 | repo.git.reset('--hard', version_id) 185 | 186 | return web.json_response({'message': f'ComfyUI version switched to {version_id}', 'branch': default_branch}) 187 | except Exception as e: 188 | error_message = f"Error selecting ComfyUI version: {str(e)}" 189 | logging.error(error_message) 190 | return web.json_response({'error': error_message}, status=500) 191 | 192 | 193 | async def get_current_comfyui_version(request): 194 | global current_comfyui_version_cache 195 | if current_comfyui_version_cache is not None: 196 | return web.Response(text=current_comfyui_version_cache) 197 | try: 198 | comfyui_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) 199 | repo = git.Repo(comfyui_path) 200 | current_version = repo.head.object.hexsha[:7] 201 | current_comfyui_version_cache = current_version 202 | return web.Response(text=current_version) 203 | except Exception as e: 204 | error_message = f"Error getting current ComfyUI version: {str(e)}" 205 | logging.error(error_message) 206 | return web.json_response({'error': error_message}, status=500) 207 | 208 | 209 | async def get_current_comfyui_branch(request): 210 | try: 211 | comfyui_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) 212 | repo = git.Repo(comfyui_path) 213 | 214 | if repo.head.is_detached: 215 | current_branch = 'Detached' 216 | else: 217 | current_branch = repo.active_branch.name 218 | 219 | return web.Response(text=current_branch) 220 | except Exception as e: 221 | error_message = f"Error getting current ComfyUI branch: {str(e)}" 222 | logging.error(error_message) 223 | return web.json_response({'error': error_message}, status=500) 224 | 225 | async def fix_comfyui_detached_branch(request): 226 | try: 227 | comfyui_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) 228 | repo = git.Repo(comfyui_path) 229 | 230 | if repo.head.is_detached: 231 | try: 232 | repo.git.checkout('master') 233 | except git.GitCommandError: 234 | try: 235 | repo.git.checkout('main') 236 | except git.GitCommandError as e: 237 | error_message = f"Error switching to default branch for ComfyUI. Git command error: {str(e)}" 238 | logging.error(error_message) 239 | return web.json_response({'error': error_message}, status=500) 240 | 241 | return web.json_response({'message': 'Fixed ComfyUI detached branch'}) 242 | except Exception as e: 243 | error_message = f"Error fixing ComfyUI detached branch: {str(e)}" 244 | logging.error(error_message) 245 | return web.json_response({'error': error_message}, status=500) 246 | 247 | 248 | async def get_plugins(request): 249 | try: 250 | plugins_dir = await get_plugins_path() 251 | plugins = [] 252 | exclude_files = ["__pycache__", "example_node.py.example", "websocket_image_save.py"] 253 | for entry in os.listdir(plugins_dir): 254 | if entry in exclude_files: 255 | continue 256 | plugin_path = await get_plugin_path(entry) 257 | if os.path.isdir(plugin_path): 258 | plugin_name = entry 259 | if plugin_name.endswith(".disabled"): 260 | plugin_name = plugin_name[:-9] 261 | enabled = False 262 | else: 263 | enabled = True 264 | git_config_path = os.path.join(plugin_path, ".git", "config") 265 | if os.path.exists(git_config_path): 266 | with open(git_config_path, "r") as git_config_file: 267 | git_config = git_config_file.read() 268 | url_match = re.search(r'url = (.*)', git_config) 269 | if url_match: 270 | url = url_match.group(1) 271 | else: 272 | url = '' 273 | else: 274 | url = '' 275 | 276 | try: 277 | repo = git.Repo(plugin_path) 278 | 279 | if repo.head.is_detached: 280 | branch = 'Detached' 281 | else: 282 | branch = repo.active_branch.name 283 | 284 | except git.InvalidGitRepositoryError: 285 | branch = 'unknown' 286 | 287 | plugin = { 288 | 'name': plugin_name, 289 | 'type': 'directory', 290 | 'url': url, 291 | 'version': '', 292 | 'date': '', 293 | 'enabled': enabled, 294 | 'branch': branch 295 | } 296 | plugins.append(plugin) 297 | elif os.path.isfile(plugin_path) and entry.endswith(".py"): 298 | plugin_name = os.path.splitext(entry)[0] 299 | if plugin_name.endswith(".disabled"): 300 | plugin_name = plugin_name[:-9] 301 | enabled = False 302 | else: 303 | enabled = True 304 | plugin = { 305 | 'name': plugin_name, 306 | 'type': 'file', 307 | 'url': '', 308 | 'version': '', 309 | 'date': '', 310 | 'enabled': enabled, 311 | 'branch': '' 312 | } 313 | plugins.append(plugin) 314 | return web.json_response(plugins) 315 | except Exception as e: 316 | error_message = f"An unexpected error occurred while fetching plugins: {str(e)}" 317 | logging.error(error_message) 318 | logging.error(traceback.format_exc()) 319 | return web.json_response({'error': error_message}, status=500) 320 | 321 | async def install_plugin(request): 322 | git_url = request.query.get('git_url') 323 | if not git_url: 324 | return web.json_response({'error': 'Git URL is required'}, status=400) 325 | try: 326 | plugin_name = request.query.get('plugin_name') 327 | overwrite = request.query.get('overwrite') == 'true' 328 | logging.info(f"Installing plugin: {plugin_name} from {git_url}") 329 | 330 | plugins_dir = await get_plugins_path() 331 | plugin_path = await get_plugin_path(plugin_name) 332 | 333 | if os.path.exists(plugin_path): 334 | if overwrite: 335 | logging.info(f"Plugin {plugin_name} already exists, overwriting") 336 | shutil.rmtree(plugin_path) 337 | else: 338 | logging.info(f"Plugin {plugin_name} already exists, skipping installation") 339 | return web.json_response({'error': f'Plugin {plugin_name} already exists'}, status=400) 340 | 341 | start_time = time.time() 342 | process = subprocess.Popen(['git', 'clone', git_url, plugin_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) 343 | 344 | output = [] 345 | while True: 346 | line = process.stdout.readline() 347 | if line: 348 | logging.info(line.strip()) 349 | output.append(line) 350 | else: 351 | break 352 | 353 | return_code = process.wait() 354 | end_time = time.time() 355 | logging.info(f"Plugin update took {end_time - start_time:.2f} seconds") 356 | 357 | return web.json_response({'message': 'Plugin updated successfully'}) 358 | except git.GitCommandError as e: 359 | error_message = f"Error updating plugin: {plugin_name}. Git command error: {str(e)}" 360 | logging.error(error_message) 361 | return web.json_response({'error': error_message}, status=500) 362 | 363 | except Exception as e: 364 | error_message = f"Error updating plugin: {plugin_name}. {str(e)}" 365 | logging.error(error_message) 366 | return web.json_response({'error': str(e)}, status=500) 367 | 368 | async def select_plugin_version(request): 369 | plugin_name = request.query.get('plugin_name') 370 | version = request.query.get('version') 371 | if not plugin_name or not version: 372 | return web.json_response({'success': False, 'message': 'Plugin name and version are required'}, status=400) 373 | try: 374 | plugin_path = await get_plugin_path(plugin_name) 375 | if not os.path.exists(plugin_path): 376 | return web.json_response({'success': False, 'message': f'Plugin {plugin_name} does not exist'}, status=400) 377 | 378 | repo = git.Repo(plugin_path) 379 | 380 | repo.git.stash() 381 | 382 | try: 383 | repo.git.checkout(version) 384 | 385 | if repo.head.is_detached: 386 | current_branch = 'Detached' 387 | else: 388 | current_branch = repo.active_branch.name 389 | 390 | if current_branch == 'Detached': 391 | temp_branch_name = f'temp-{version[:7]}' 392 | repo.git.checkout('-b', temp_branch_name) 393 | current_branch = temp_branch_name 394 | 395 | plugin = next((p for p in await get_plugins_list() if p['name'] == plugin_name), None) 396 | if plugin: 397 | plugin['version'] = version 398 | plugin['branch'] = current_branch 399 | 400 | return web.json_response({ 401 | 'success': True, 402 | 'message': f'Successfully switched plugin {plugin_name} to version {version}', 403 | 'version': version, 404 | 'branch': current_branch 405 | }) 406 | 407 | except git.GitCommandError as e: 408 | repo.git.stash('pop') 409 | return web.json_response({'success': False, 'message': f'Git command error: {str(e)}'}, status=500) 410 | 411 | except Exception as e: 412 | logging.error(f"Error selecting plugin version: {str(e)}") 413 | return web.json_response({'success': False, 'message': str(e)}, status=500) 414 | 415 | 416 | async def get_plugins_list(): 417 | response = await get_plugins(None) 418 | return json.loads(response.text) 419 | 420 | async def update_plugin(request): 421 | plugin_name = get_query_param(request, 'plugin_name') 422 | if not plugin_name: 423 | return web.json_response({'error': 'Plugin name is required'}, status=400) 424 | try: 425 | plugin_path = await get_plugin_path(plugin_name) 426 | if not os.path.exists(plugin_path): 427 | error_message = f'Plugin {plugin_name} does not exist' 428 | logging.error(error_message) 429 | return web.json_response({'error': error_message}, status=400) 430 | repo = git.Repo(plugin_path) 431 | 432 | try: 433 | default_branch = await get_plugin_default_branch(plugin_name) 434 | 435 | if default_branch and default_branch != 'Detached': 436 | start_time = time.time() 437 | repo.remotes.origin.fetch() 438 | repo.git.reset('--hard', f'origin/{default_branch}') 439 | end_time = time.time() 440 | logging.info(f"Plugin update took {end_time - start_time:.2f} seconds") 441 | else: 442 | start_time = time.time() 443 | repo.remotes.origin.fetch() 444 | repo.git.pull() 445 | end_time = time.time() 446 | logging.info(f"Plugin update took {end_time - start_time:.2f} seconds") 447 | 448 | return web.json_response({'message': 'Plugin updated successfully'}) 449 | 450 | except git.GitCommandError as e: 451 | error_message = f"Error updating plugin: {plugin_name}. Git command error: {str(e)}" 452 | logging.error(error_message) 453 | return web.json_response({'error': error_message}, status=500) 454 | 455 | except Exception as e: 456 | error_message = f"Error updating plugin: {plugin_name}. {str(e)}" 457 | logging.error(error_message) 458 | return web.json_response({'error': str(e)}, status=500) 459 | 460 | 461 | async def get_plugin_versions(request): 462 | plugin_name = get_query_param(request, 'plugin_name') 463 | if not plugin_name: 464 | return web.json_response({'error': 'Plugin name is required'}, status=400) 465 | try: 466 | plugin_path = await get_plugin_path(plugin_name) 467 | if not os.path.exists(plugin_path): 468 | return web.json_response({'error': f'Plugin {plugin_name} does not exist'}, status=400) 469 | repo = git.Repo(plugin_path) 470 | commits = list(repo.iter_commits()) 471 | versions = [{"id": c.hexsha[:7], "message": c.message.strip(), "date": c.committed_datetime.strftime("%Y-%m-%d %H:%M:%S")} for c in commits] 472 | 473 | plugin_author = 'unknown' 474 | try: 475 | remote_urls = list(repo.remote().urls) 476 | if remote_urls: 477 | plugin_author = remote_urls[0].split('/')[-2] 478 | except (AttributeError, IndexError): 479 | pass 480 | 481 | return web.json_response({"versions": versions, "author": plugin_author}) 482 | except Exception as e: 483 | logging.error(f"Error in get_plugin_versions: {str(e)}") 484 | return web.json_response({'error': str(e)}, status=500) 485 | 486 | async def view_plugin_requirements(request): 487 | plugin_name = request.query.get('plugin_name') 488 | if not plugin_name: 489 | return web.json_response({'error': 'Plugin name is required'}, status=400) 490 | try: 491 | plugin_path = await get_plugin_path(plugin_name) 492 | 493 | if not os.path.exists(plugin_path): 494 | return web.json_response({'error': f'Plugin {plugin_name} does not exist'}, status=400) 495 | requirements_path = os.path.join(plugin_path, 'requirements.txt') 496 | if not os.path.exists(requirements_path): 497 | return web.json_response({'message': 'No requirements found'}) 498 | with open(requirements_path, 'r') as file: 499 | requirements = file.read() 500 | return web.json_response(requirements) 501 | except Exception as e: 502 | logging.error(f"Error viewing plugin requirements: {str(e)}") 503 | return web.json_response({'error': str(e)}, status=500) 504 | 505 | async def edit_plugin_requirements(request): 506 | plugin_name = request.query.get('plugin_name') 507 | if not plugin_name: 508 | return web.json_response({'error': 'Plugin name is required'}, status=400) 509 | try: 510 | plugin_path = await get_plugin_path(plugin_name) 511 | 512 | if not os.path.exists(plugin_path): 513 | return web.json_response({'error': f'Plugin {plugin_name} does not exist'}, status=400) 514 | 515 | requirements_path = os.path.join(plugin_path, 'requirements.txt') 516 | 517 | requirements = await request.text() 518 | with open(requirements_path, 'w') as file: 519 | file.write(requirements) 520 | return web.json_response({'message': 'Requirements updated successfully'}) 521 | except Exception as e: 522 | logging.error(f"Error editing plugin requirements: {str(e)}") 523 | return web.json_response({'error': str(e)}, status=500) 524 | 525 | async def toggle_plugin(request): 526 | plugin_name = request.query.get('plugin_name') 527 | enabled = request.query.get('enabled') 528 | if not plugin_name or enabled is None: 529 | return web.json_response({'error': 'Plugin name and enabled state are required'}, status=400) 530 | try: 531 | plugins_dir = await get_plugins_path() 532 | plugin_path = await get_plugin_path(plugin_name) 533 | disabled_plugin_path = plugin_path + '.disabled' 534 | if not os.path.exists(plugin_path) and not os.path.exists(disabled_plugin_path): 535 | return web.json_response({'error': f'Plugin {plugin_name} does not exist'}, status=400) 536 | 537 | if enabled == 'true': 538 | if os.path.exists(disabled_plugin_path): 539 | os.rename(disabled_plugin_path, plugin_path) 540 | else: 541 | if os.path.exists(plugin_path): 542 | os.rename(plugin_path, disabled_plugin_path) 543 | 544 | return web.json_response({'message': f'Plugin {plugin_name} {"enabled" if enabled == "true" else "disabled"} successfully'}) 545 | except Exception as e: 546 | logging.error(f"Error toggling plugin: {plugin_name}. {str(e)}") 547 | return web.json_response({'error': str(e)}, status=500) 548 | 549 | 550 | async def open_plugin_folder(request): 551 | plugin_name = request.query.get('plugin_name') 552 | if not plugin_name: 553 | return web.json_response({'error': 'Plugin name is required'}, status=400) 554 | try: 555 | plugin_path = await get_plugin_path(plugin_name) 556 | 557 | if not os.path.exists(plugin_path): 558 | return web.json_response({'error': f'Plugin {plugin_name} does not exist'}, status=400) 559 | 560 | 561 | if sys.platform == "win32": 562 | os.startfile(plugin_path) 563 | else: 564 | subprocess.Popen(["xdg-open", plugin_path]) 565 | 566 | return web.json_response({'message': 'Plugin folder opened successfully'}) 567 | except Exception as e: 568 | logging.error(f"Error opening plugin folder: {plugin_name}. {str(e)}") 569 | return web.json_response({'error': str(e)}, status=500) 570 | 571 | async def open_site_packages_folder(request): 572 | try: 573 | site_packages_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "python_miniconda", "Lib", "site-packages")) 574 | 575 | if sys.platform == "win32": 576 | os.startfile(site_packages_path) 577 | else: 578 | subprocess.Popen(["xdg-open", site_packages_path]) 579 | 580 | return web.json_response({'message': 'Site-packages folder opened successfully'}) 581 | except Exception as e: 582 | logging.error(f"Error opening site-packages folder: {str(e)}") 583 | return web.json_response({'error': str(e)}, status=500) 584 | 585 | async def check_dependency_conflicts(request): 586 | try: 587 | python_path = await get_python_path() 588 | logging.info("Checking dependency conflicts...") 589 | process = subprocess.Popen([python_path, '-m', 'pip', 'check'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 590 | stdout, stderr = process.communicate() 591 | if stdout: 592 | return web.Response(text=stdout.decode('utf-8', errors='replace')) 593 | elif stderr: 594 | logging.error("Error checking dependency conflicts:") 595 | return web.Response(text=stderr.decode('utf-8', errors='replace')) 596 | else: 597 | logging.info("No conflicts found.") 598 | return web.Response(text="No conflicts found.") 599 | except Exception as e: 600 | logging.error(f"Error checking dependency conflicts: {str(e)}") 601 | return web.json_response({'error': str(e)}, status=500) 602 | 603 | 604 | async def get_dependency_versions(request): 605 | name = request.query.get('name') 606 | if not name: 607 | return web.json_response({'error': 'Name is required'}, status=400) 608 | 609 | if name in version_cache: 610 | return web.json_response(version_cache[name]) 611 | 612 | try: 613 | python_path = await get_python_path() 614 | process = subprocess.Popen([python_path, '-m', 'pip', 'install', f'{name}=='], stdout=subprocess.PIPE, stderr=subprocess.PIPE) 615 | _, stderr = process.communicate() 616 | output_text = stderr.decode('utf-8').strip() 617 | 618 | if '(from versions:' in output_text: 619 | start_index = output_text.index('(from versions:') + len('(from versions:') 620 | end_index = output_text.index(')', start_index) 621 | versions_text = output_text[start_index:end_index].strip() 622 | versions = [v.strip() for v in versions_text.split(',')] 623 | formatted_versions = [f"{name}=={version}" for version in versions] 624 | 625 | version_cache[name] = formatted_versions 626 | 627 | return web.json_response(formatted_versions) 628 | else: 629 | return web.json_response([]) 630 | except Exception as e: 631 | logging.error(f"Error getting dependency versions for {name}: {str(e)}") 632 | return web.json_response([]) 633 | 634 | version_cache = {} 635 | 636 | async def install_dependency_version(request): 637 | name = request.query.get('name') 638 | version = request.query.get('version') 639 | if not name or not version: 640 | return web.json_response({'error': 'Name and version are required'}, status=400) 641 | try: 642 | python_path = await get_python_path() 643 | process = subprocess.Popen([python_path, '-m', 'pip', 'install', f'{name}=={version}'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) 644 | 645 | output = [] 646 | while True: 647 | line = process.stdout.readline() 648 | if line: 649 | logging.info(line.strip()) 650 | output.append(line) 651 | else: 652 | break 653 | 654 | if process.returncode == 0: 655 | filtered_output = [line for line in output if not line.startswith('WARNING')] 656 | response_data = {'message': 'Installation successful', 'output': ''.join(filtered_output), 'error': ''} 657 | logging.info(f"Message: {response_data}") 658 | return web.json_response(response_data) 659 | else: 660 | response_data = {'message': 'Installation failed', 'output': ''.join(output), 'error': ''} 661 | logging.error(f"Message: {response_data}") 662 | return web.json_response(response_data, status=500) 663 | except subprocess.CalledProcessError as e: 664 | error_message = f"Error installing dependency: {name}=={version}. {str(e)}" 665 | logging.error(error_message) 666 | logging.error(e.output.decode('utf-8')) 667 | return web.json_response({'error': error_message}, status=500) 668 | 669 | 670 | async def install_plugin_requirements(request): 671 | plugin_name = request.query.get('plugin_name') 672 | if not plugin_name: 673 | return web.json_response({'error': 'Plugin name is required'}, status=400) 674 | 675 | try: 676 | plugin_path = await get_plugin_path(plugin_name) 677 | requirements_path = os.path.join(plugin_path, 'requirements.txt') 678 | 679 | if not os.path.exists(plugin_path): 680 | error_message = f'Plugin {plugin_name} does not exist' 681 | logging.error(error_message) 682 | return web.json_response({'error': error_message}, status=400) 683 | 684 | if not os.path.exists(requirements_path): 685 | logging.info(f'No requirements found for plugin {plugin_name}') 686 | return web.json_response({'message': 'No requirements found for this plugin'}) 687 | 688 | python_path = await get_python_path() 689 | 690 | logging.info(f"Installing requirements for plugin: {plugin_name}") 691 | 692 | process = await asyncio.create_subprocess_exec( 693 | python_path, '-m', 'pip', 'install', '-r', requirements_path, 694 | stdout=asyncio.subprocess.PIPE, 695 | stderr=asyncio.subprocess.STDOUT 696 | ) 697 | 698 | response = web.StreamResponse() 699 | response.headers['Content-Type'] = 'text/plain' 700 | await response.prepare(request) 701 | 702 | while True: 703 | line = await process.stdout.readline() 704 | if not line: 705 | break 706 | await response.write(line) 707 | await response.drain() 708 | 709 | await process.wait() 710 | 711 | if process.returncode == 0: 712 | await response.write(b"Installation completed successfully.\n") 713 | else: 714 | await response.write(b"Installation failed.\n") 715 | 716 | await response.write_eof() 717 | return response 718 | 719 | except Exception as e: 720 | error_message = f"Error installing requirements for plugin: {plugin_name}. {str(e)}" 721 | logging.error(error_message) 722 | return web.json_response({'error': error_message}, status=500) 723 | 724 | 725 | async def checkout_plugin_branch(request): 726 | plugin_name = get_query_param(request, 'plugin_name') 727 | if not plugin_name: 728 | return web.json_response({'error': 'Plugin name is required'}, status=400) 729 | try: 730 | plugin_path = await get_plugin_path(plugin_name) 731 | if not os.path.exists(plugin_path): 732 | error_message = f'Plugin {plugin_name} does not exist' 733 | logging.error(error_message) 734 | return web.json_response({'error': error_message}, status=400) 735 | repo = git.Repo(plugin_path) 736 | 737 | if repo.head.is_detached: 738 | try: 739 | repo.git.checkout('master') 740 | except git.GitCommandError: 741 | try: 742 | repo.git.checkout('main') 743 | except git.GitCommandError as e: 744 | error_message = f"Error switching to default branch for plugin: {plugin_name}. Git command error: {str(e)}" 745 | logging.error(error_message) 746 | return web.json_response({'error': error_message}, status=500) 747 | else: 748 | default_branch = repo.active_branch.name 749 | try: 750 | repo.git.checkout(default_branch) 751 | except git.GitCommandError as e: 752 | error_message = f"Error switching to default branch for plugin: {plugin_name}. Git command error: {str(e)}" 753 | logging.error(error_message) 754 | return web.json_response({'error': error_message}, status=500) 755 | 756 | return web.json_response({'message': f'Switched to default branch for plugin: {plugin_name}'}) 757 | except Exception as e: 758 | error_message = f"Error switching to default branch for plugin: {plugin_name}. {str(e)}" 759 | logging.error(error_message) 760 | return web.json_response({'error': error_message}, status=500) 761 | 762 | def get_query_param(request, param_name): 763 | if isinstance(request, aiohttp.web.Request): 764 | return request.rel_url.query.get(param_name) 765 | else: 766 | query_params = urllib.parse.parse_qs(request) 767 | param_values = query_params.get(param_name, []) 768 | return param_values[0] if param_values else None 769 | 770 | async def get_plugin_default_branch(request): 771 | plugin_name = get_query_param(request, 'plugin_name') 772 | if not plugin_name: 773 | return web.json_response({'error': 'Plugin name is required'}, status=400) 774 | try: 775 | plugin_path = await get_plugin_path(plugin_name) 776 | if not os.path.exists(plugin_path): 777 | error_message = f'Plugin {plugin_name} does not exist' 778 | logging.error(error_message) 779 | raise Exception(error_message) 780 | repo = git.Repo(plugin_path) 781 | 782 | if repo.head.is_detached: 783 | default_branch = 'Detached' 784 | else: 785 | default_branch = repo.active_branch.name 786 | 787 | return web.json_response({'default_branch': default_branch}) 788 | except Exception as e: 789 | error_message = f"Error getting default branch for plugin: {plugin_name}. {str(e)}" 790 | return web.json_response({'error': error_message}, status=500) 791 | 792 | async def fix_plugin_branch(request): 793 | plugin_name = request.query.get('plugin_name') 794 | if not plugin_name: 795 | return web.json_response({'error': 'Plugin name is required'}, status=400) 796 | try: 797 | plugin_path = await get_plugin_path(plugin_name) 798 | repo = git.Repo(plugin_path) 799 | 800 | for branch in ['main', 'master']: 801 | try: 802 | repo.git.checkout(branch) 803 | return web.json_response({'message': f'Switched to {branch} branch for plugin: {plugin_name}'}) 804 | except git.GitCommandError: 805 | continue 806 | 807 | default_branch = repo.active_branch.name 808 | repo.git.checkout(default_branch) 809 | return web.json_response({'message': f'Switched to default branch {default_branch} for plugin: {plugin_name}'}) 810 | except Exception as e: 811 | error_message = f"Error fixing branch for plugin: {plugin_name}. {str(e)}" 812 | logging.error(error_message) 813 | return web.json_response({'error': error_message}, status=500) 814 | 815 | 816 | async def get_plugin_details(request): 817 | plugin_name = request.query.get('plugin_name') 818 | if not plugin_name: 819 | return web.json_response({'error': 'Plugin name is required'}, status=400) 820 | try: 821 | plugin_path = await get_plugin_path(plugin_name) 822 | if not os.path.exists(plugin_path): 823 | return web.json_response({'error': f'Plugin {plugin_name} does not exist'}, status=400) 824 | 825 | plugin_details = { 826 | 'name': plugin_name, 827 | 'type': 'directory' if os.path.isdir(plugin_path) else 'file', 828 | 'url': '', 829 | 'version': '', 830 | 'date': '', 831 | 'enabled': not plugin_name.endswith(".disabled"), 832 | 'branch': '' 833 | } 834 | 835 | git_config_path = os.path.join(plugin_path, ".git", "config") 836 | if os.path.exists(git_config_path): 837 | with open(git_config_path, "r") as git_config_file: 838 | git_config = git_config_file.read() 839 | url_match = re.search(r'url = (.*)', git_config) 840 | if url_match: 841 | plugin_details['url'] = url_match.group(1) 842 | 843 | try: 844 | repo = git.Repo(plugin_path) 845 | 846 | if repo.head.is_detached: 847 | plugin_details['branch'] = 'Detached' 848 | else: 849 | plugin_details['branch'] = repo.active_branch.name 850 | 851 | except git.InvalidGitRepositoryError: 852 | plugin_details['branch'] = 'unknown' 853 | 854 | return web.json_response(plugin_details) 855 | except Exception as e: 856 | logging.error(f"Error getting details for plugin {plugin_name}: {str(e)}") 857 | return web.json_response({'error': str(e)}, status=500) 858 | 859 | 860 | def get_package_version(module_path): 861 | try: 862 | parts = module_path.split('.') 863 | package_name = parts[0] 864 | 865 | url = f"https://pypi.org/pypi/{package_name}/json" 866 | response = requests.get(url) 867 | data = response.json() 868 | 869 | versions = list(data["releases"].keys()) 870 | latest_version = versions[-1] 871 | 872 | return f"{package_name}=={latest_version}" 873 | except requests.exceptions.RequestException as e: 874 | logging.error(f"Error fetching package metadata for {package_name}: {str(e)}") 875 | return f"Error: Failed to fetch package metadata for {package_name}" 876 | except KeyError: 877 | logging.error(f"Package {package_name} not found on PyPI") 878 | return f"Error: Package {package_name} not found on PyPI" 879 | except Exception as e: 880 | logging.error(f"Error getting package version for {module_path}: {str(e)}") 881 | return f"Error: {str(e)}" 882 | 883 | async def get_module_version(request): 884 | module_path = request.query.get('module_path') 885 | if not module_path: 886 | return web.json_response({'error': 'Module path is required'}, status=400) 887 | try: 888 | result = get_package_version(module_path) 889 | return web.Response(text=result) 890 | except Exception as e: 891 | logging.error(f"Error getting module version for {module_path}: {str(e)}") 892 | return web.json_response({'error': str(e)}, status=500) 893 | 894 | 895 | async def get_mjstyle_json(request): 896 | name = request.match_info['name'] 897 | base_path = os.path.dirname(os.path.abspath(__file__)) 898 | file_path = os.path.join(base_path, 'docs', 'mjstyle', f'{name}.json') 899 | 900 | if os.path.exists(file_path): 901 | with open(file_path, 'rb') as f: 902 | json_data = f.read() 903 | return web.Response(body=json_data, content_type='application/json') 904 | else: 905 | return web.Response(status=404) 906 | 907 | async def get_marked_js(request): 908 | base_path = os.path.dirname(os.path.abspath(__file__)) 909 | file_path = os.path.join(base_path, 'web', 'lib', 'marked.min.js') 910 | 911 | if os.path.exists(file_path): 912 | with open(file_path, 'rb') as f: 913 | js_data = f.read() 914 | return web.Response(body=js_data, content_type='application/javascript') 915 | else: 916 | return web.Response(status=404) 917 | 918 | async def get_purify_js(request): 919 | base_path = os.path.dirname(os.path.abspath(__file__)) 920 | file_path = os.path.join(base_path, 'web', 'lib', 'purify.min.js') 921 | 922 | if os.path.exists(file_path): 923 | with open(file_path, 'rb') as f: 924 | js_data = f.read() 925 | return web.Response(body=js_data, content_type='application/javascript') 926 | else: 927 | return web.Response(status=404) 928 | 929 | 930 | def load_api_config(): 931 | try: 932 | current_dir = os.path.dirname(os.path.realpath(__file__)) 933 | config_path = os.path.join(current_dir, 'Comflyapi.json') 934 | 935 | if not os.path.exists(config_path): 936 | return {} 937 | 938 | with open(config_path, 'r') as f: 939 | config = json.load(f) 940 | return config 941 | except Exception as e: 942 | logging.error(f"Error loading API config: {str(e)}") 943 | return {} 944 | 945 | async def get_config(request): 946 | config = load_api_config() 947 | return web.json_response(config) 948 | 949 | 950 | app = web.Application() 951 | 952 | # Configure CORS 953 | cors = setup(app, defaults={ 954 | "*": ResourceOptions( 955 | allow_credentials=True, 956 | expose_headers="*", 957 | allow_headers="*", 958 | allow_methods="*", 959 | ) 960 | }) 961 | 962 | 963 | # Define routes 964 | routes = [ 965 | ("/fix_plugin_branch", fix_plugin_branch), 966 | ("/api/get_config", get_config), 967 | ("/lib/marked.min.js", get_marked_js), 968 | ("/lib/purify.min.js", get_purify_js), 969 | ("/mjstyle/{name}.json", get_mjstyle_json), 970 | ("/get_dependencies", get_dependencies), 971 | ("/install_dependency", install_dependency), 972 | ("/manage_dependency", manage_dependency), 973 | ("/replace_dependency", replace_dependency), 974 | ("/get_comfyui_versions", get_comfyui_versions), 975 | ("/select_comfyui_version", select_comfyui_version), 976 | ("/get_current_comfyui_version", get_current_comfyui_version), 977 | ("/get_current_comfyui_branch", get_current_comfyui_branch), 978 | ("/fix_comfyui_detached_branch", fix_comfyui_detached_branch), 979 | ("/get_plugins", get_plugins), 980 | ("/install_plugin", install_plugin), 981 | ("/select_plugin_version", select_plugin_version), 982 | ("/update_plugin", update_plugin), 983 | ("/get_plugin_details", get_plugin_details), 984 | ("/get_plugin_versions", get_plugin_versions), 985 | ("/view_plugin_requirements", view_plugin_requirements), 986 | ("/edit_plugin_requirements", edit_plugin_requirements), 987 | ("/open_plugin_folder", open_plugin_folder), 988 | ("/open_site_packages_folder", open_site_packages_folder), 989 | ("/toggle_plugin", toggle_plugin), 990 | ("/check_dependency_conflicts", check_dependency_conflicts), 991 | ("/get_dependency_versions", get_dependency_versions), 992 | ("/install_dependency_version", install_dependency_version), 993 | ("/install_plugin_requirements", install_plugin_requirements), 994 | ("/get_plugin_default_branch", get_plugin_default_branch), 995 | ("/checkout_plugin_branch", checkout_plugin_branch), 996 | ("/get_module_version", get_module_version), 997 | ] 998 | 999 | # Add routes to the application with CORS 1000 | for route in routes: 1001 | resource = cors.add(app.router.add_resource(route[0])) 1002 | cors.add(resource.add_route("GET", route[1])) 1003 | cors.add(resource.add_route("POST", route[1])) 1004 | 1005 | def stop_process_on_port(port): 1006 | for proc in psutil.process_iter(['pid', 'name']): 1007 | try: 1008 | try: 1009 | connections = proc.net_connections() 1010 | except AttributeError: 1011 | connections = proc.connections() 1012 | 1013 | for conn in connections: 1014 | if conn.laddr.port == port: 1015 | proc.terminate() 1016 | proc.wait() 1017 | except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): 1018 | pass 1019 | 1020 | def start_api_server(): 1021 | stop_process_on_port(8080) 1022 | logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') 1023 | web.run_app(app, port=8080, access_log=None, print=None) 1024 | 1025 | if __name__ == '__main__': 1026 | print("\033[32m ** Comfly Loaded :\033[33m fly, just fly\033[0m") 1027 | start_api_server() 1028 | -------------------------------------------------------------------------------- /Comflyapi.json: -------------------------------------------------------------------------------- 1 | { 2 | "api_key": "" 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![资源 1小显示器-svg](https://github.com/ainewsto/Comfyui_Comfly/assets/113163264/e36d75e0-2cba-4026-936e-1ba8aba9cc7b) 3 | 4 |
5 | 6 | Static Badge 7 | # 👋🏻 Welcome to Comfly 8 | 9 |
10 | 11 | 12 | 13 | 我喜欢comfyui,它就像风一样的自由,所以我取名为:comfly 14 | 同样我也喜欢绘画和设计,所以我非常佩服每一位画家,艺术家,在ai的时代,我希望自己能接收ai知识的同时,也要记住尊重关于每个画师的版权问题。 15 | 我一直认为ai最好的方式应该是在购买版权的前提下,有序的完成ai的良性发展. 在学习comfyui的这段旅程中,我遇到了,几位可爱的小伙伴。 16 | 17 | 并且他们格外的温暖,调皮,傻不拉几。虽然他们没有参与这个项目,但是有几位朋友帮忙做测试。 18 | 第一张图片由"走走走"修改后的版本. 19 | 20 | ![新建项目](https://github.com/ainewsto/Comfyui_Comfly/assets/113163264/de5d1b7c-f909-4a3d-892e-6f38c56e4e85) 21 | 22 | 23 | > **Warning** 24 | > 25 | > 插件只在windows11上测试,mac电脑后续我也会测试,如果其他系统有任何问题,可以提交issues 26 | > 目前界面没有做太多的美化,有时间再说,必要性不到,主要是功能性插件 27 | 28 | # 更新 Update: 29 | 30 | 20250613: 31 | 32 | `Flux节点`: 新增bfl官方节点:Comfly_Flux_Kontext_bfl节点,价格不变 33 | 34 | 35 | 20250611: 36 | 37 | `Flux节点`: Comfly_Flux_Kontext_Edit节点支持设置出图数量(1-4张范围),这个节点不会消耗上传图片费用,直接传入图片即可, 38 | 跟Comfly_Flux_Kontext一样,就是上传图片不会扣费,图片输入支持base64图片编码格式,可以做为稳定性的备用节点。 39 | 40 | 20250601: 41 | 42 | `Flux节点`: Comfly_Flux_Kontext节点支持设置出图数量(1-4张范围),去掉了match_input_image对输入图片尺寸选项。 43 | 支持多图输入。已经支持对上一次生成的图片再次提示词编辑(但只有当出土数量选择1时才可以使用这个。) 44 | 45 | 46 | 47 | 20250530: 48 | 49 | `Flux节点`: 新增Comfly_Flux_Kontext节点,支持:flux-kontext-pro和flux-kontext-max模型,按次收费:pro模型大约0.096元,max大约0.192元,比官方便宜很多。 50 | 51 | 52 | 20250526: 53 | 54 | `Jimeng即梦视频节点`: 新增ComflyJimengVideoApi节点。即梦视频,按次收费,5s是0.6元,10s是1.2元。 55 |
56 | 查看更新/Update 57 | 58 | ![75ae4f4c3b061c0a7f7d1b1eb1b0264](https://github.com/user-attachments/assets/a8533eef-8233-4c35-ab1b-c9a26d5ddf72) 59 | 60 |
61 | 62 | 20250518: 63 | 64 | `Kling节点`: 可灵节点新增kling-v2-master的可灵2.0模型。价格很贵,按需使用。 65 | 66 | 20250429: 67 | 68 | `Chatgpt节点`: Comfly_gpt_image_1_edit新增chats输出口,输出多轮对话。 69 | 新增clear_chats,当为Ture的时候,只能image输入什么图片修改什么图片,不支持显示上下文对话。 70 | 当为Flase的时候,支持对上一次生成的图片进行二次修改。支持显示上下文对话。并且支持多图模式下新增图片参考。 71 | 72 |
73 | 查看更新/Update 74 | 75 | ![2eaf76b077612170647f6861e43e2af](https://github.com/user-attachments/assets/1c4c484f-c3c6-48c6-96c5-58c4ef4e59d5) 76 | 77 | ![6a43cb051fece84815ac6036bee3a4c](https://github.com/user-attachments/assets/f0fbf71e-8cfb-448e-87cd-1e147bb2f552) 78 | 79 |
80 | 81 | 20250425: 82 | 83 | 视频教程: https://www.bilibili.com/video/BV1jxLUz9ECX 84 | 85 | `Chatgpt节点`: 86 | 新增Comfly_gpt_image_1和Comfly_gpt_image_1_edit官方gpt_image_1模型api接口节点。 87 | 88 | ![image](https://github.com/user-attachments/assets/9d08d5fc-dde9-4523-955c-31652a74f1a5) 89 | 90 | 模型名都是gpt_image_1,区别只是分组不同: 91 | 92 | 一共四个分组:default默认分组为官方逆向,价格便宜,缺点就是不稳定,速度慢。按次收费。不支持额外参数选择。这个分组的apikey只能用于ComflyChatGPTApi节点。 93 | 94 | 其他三个组都是官方api组,最优惠的目前是ssvip组。分组需要再令牌里面去修改选择。这3个官方分组优点就是速度快,稳定性高。支持官方参数调整。 95 | 缺点就是贵,但是也比官方便宜。大家可以按照自己的情况选择。这3个分组的令牌的apikey只能用在下面2个新节点上面!!! 96 | 97 | 1. Comfly_gpt_image_1 节点:文生图,有耕读参数调整,支持调整生图限制为low。 98 | 99 | 2. Comfly_gpt_image_1_edit 节点:图生图,支持mask遮罩,支持多图参考。 100 | 101 |
102 | 查看更新/Update 103 | 104 | ![3bc790641c44e373aca97ea4a1de47e](https://github.com/user-attachments/assets/1a7a0615-46e5-46b3-af04-32246a23d6f4) 105 | 106 | ![5efe58fcf7055d675962f40c1ad1cbb](https://github.com/user-attachments/assets/8a90eab5-4242-43bb-ae01-74493b90b6ce) 107 | 108 |
109 | 110 | 20250424: 111 | `Chatgpt节点`: ComflyChatGPTApi节点新增官方gpt-image-1,按次计费 0.06, 112 | 旧版的gpt4o-image,gpt4o-image-vip,sora_image, sora_image-vip可以做为备选。首选gpt-image-1。 113 | 114 | `jimeng即梦节点`: 即梦的ComflyJimengApi节点新增参考图生成图片,image url图片链接参考生成图片。 115 | 注意:参考图生成图片会额外消耗上传图片的token费用(具体根据你图片大小来,大部分都是0.000几到0.00几元不等。图片链接有时效性,不做长期储存), 116 | 这个只适用于你没有image url图片链接的前提下使用。 117 | 如果你有image url图片链接,就直接填写在image url里面既可以。 118 | 119 |
120 | 查看更新/Update 121 | 122 | ![e1abc11e855680b70985ec9f339a967](https://github.com/user-attachments/assets/6d77c103-d35a-4c6b-804a-4b5add172bcf) 123 | 124 | ![307e5ea0d789b785fd0a60f01f2b8cf](https://github.com/user-attachments/assets/5c8a7984-ae5e-4cbf-aa47-b09bc7e6f8d6) 125 | 126 |
127 | 128 | 20250422: 129 | `Chatgpt节点`: ComflyChatGPTApi节点新增chats输出口,输出多轮对话。 130 | 新增clear_chats,当为Ture的时候,只能image输入什么图片修改什么图片,不支持显示上下文对话。 131 | 当为Flase的时候,支持对上一次生成的图片进行二次修改。支持显示上下文对话。 132 | 133 |
134 | 查看更新/Update 135 | 136 | ![cad243f2bf4a3aa11163f1a007db469](https://github.com/user-attachments/assets/ef0f6a34-3de7-42a2-8543-c1930575e1bb) 137 | 138 | ![bd6493050affdf156143c8dc5286988](https://github.com/user-attachments/assets/0906caf3-35ec-4061-bfc9-5f611a19abf2) 139 | 140 | ![e5b3d375b700dcbf921b12a8aa527c4](https://github.com/user-attachments/assets/75537100-e5d2-403c-b2e0-1f662680092f) 141 | 142 | 143 |
144 | 145 | 20250418: 146 | `jimeng即梦节点`: 新增即梦的ComflyJimengApi节点。 147 | 目前只支持文生图,使用的是 https://ai.comfly.chat 的 api key 148 | 149 | 参数说明: 150 | use_pre_llm:开启文本扩写,会针对输入prompt进行扩写优化,如果输入prompt较短建议开启,如果输入prompt较长建议关闭。 151 | scale:影响文本描述的程度,默认值:2.5,取值范围:[1, 10] 152 | width,height:生成图像的宽和高,默认值:1328,取值范围:[512, 2048] 153 | add_logo:是否添加水印。True为添加,False不添加。默认不添加 154 | opacity:水印的不透明度,取值范围0-1,1表示完全不透明,默认0.3 155 | 156 |
157 | 查看更新/Update 158 | 159 | ![3c1a498bea1853be7aafda2d7ea41b1](https://github.com/user-attachments/assets/13b84330-25d0-420b-9111-8e653f3ada99) 160 | 161 | ![16be3f66454ae4a74c7c5bc723f847f](https://github.com/user-attachments/assets/96351739-bbcb-476b-a516-1a6a265151db) 162 | 163 |
164 | 165 | `ai模块`: 简单化ai模块,支持上传文件解析(复杂的图片pdf会根据具体模型来确定).优化交互体验。 166 | 设置里面可以设置api key。 167 | 168 |
169 | 查看更新/Update 170 | 171 | ![4490ee27d100d5414028f7d0e293748](https://github.com/user-attachments/assets/ae088d19-2289-424d-b030-39411cdfca2a) 172 | ![9ae58c50da8676e6286e2018e103083](https://github.com/user-attachments/assets/8f45b580-1981-4f22-924c-3c492b54eb68) 173 | ![621183f77a72d4ffaf89abde650f4f6](https://github.com/user-attachments/assets/6d838ad9-3679-401c-86c3-a139f57ad3ee) 174 | 175 |
176 | 177 | 20250401: 178 | `所有节点`: 所有调用apikey的节点新增apikey输入框。优化可灵图生图视频节点。 179 | 180 | 20250329: 181 | `Chatgpt节点`: 新增openai的ComflyChatGPTApi节点,。 182 | 目前单图和多图输入,文本输入,生成图片,图片编辑.使用的是 https://ai.comfly.chat 的 api key 183 | 固定一次生成消耗0.06元(显示是逆向api,稳定性还不高,想尝鲜的可以注册网站用免费送的0.2美金玩玩) 184 | 速度不快,因为官网速度也不快,所以需要点耐心。 files输入接口还没有完善,先忽略。 185 | 用sora_image现在先对稳定点 186 | 187 |
188 | 查看更新/Update 189 | 190 | ![fdedd73cffa278d2a8cf81478b58e90](https://github.com/user-attachments/assets/36e78cdd-33b2-41ed-a15c-ad9c1886bede) 191 | 192 | 193 | ![0a2394c0b41efe190a5d0880f4c584b](https://github.com/user-attachments/assets/267fbe73-7113-4120-a829-a7aa2247bd4d) 194 | 195 |
196 | 197 | 198 | 20250325: 199 | 200 | `Kling节点`: 新增可灵Comfly_lip_sync对口型节点,生成效果还行吧。速度也一般般。支持中文和英文。 201 | 202 | `Gemmi节点`: ComflyGeminiAPI节点resolution新增:object_image size,subject_image size,scene_image size根据输入的图片的尺寸来确定输出图片的尺寸。增加image url输出接口。 203 | 204 | `Doubao豆包节点`: ComflySeededit节点文字驱动生成图片,编辑图片。支持添加自己的水印logo。目前只支持单图修改和参考。使用的是 https://ai.comfly.chat 的 api key 205 | 206 | 用于编辑图像的提示词 。建议: 207 | 208 | 添加/删除实体:添加/删除xxx(删除图上的女孩/添加一道彩虹) 209 | 210 | 修改实体:把xxx改成xxx(把手里的鸡腿变成汉堡) 211 | 212 | 修改风格:改成xxx风格(改成漫画风格) 213 | 214 | 修改色彩:把xxx改成xx颜色(把衣服改成粉色的) 215 | 216 | 修改动作:修改表情动作(让他哭/笑/生气) 217 | 218 | 修改环境背景:背景换成xxx,在xxx(背景换成海边/在星空下) 219 | 220 | 1:图片格式:JPG(JPEG), PNG, BMP 等常见格式, 建议使用JPG格式. 221 | 222 | 2:图片要求:小于4.7 MB,小于4096*4096 223 | 224 | 3:长边与短边比例在3以内,超出此比例或比例相对极端,会导致报错 225 | 226 |
227 | 228 | 查看更新/Update 229 | 230 | ![95836fbdda83551ca81ebc3db93b2d5](https://github.com/user-attachments/assets/0ce70dd3-eb0a-4e2e-bf6c-68642a48288d) 231 | 232 | ![563e17009b9100533f169aa1d87b37f](https://github.com/user-attachments/assets/48bd20e9-9d87-43d7-aa81-370c7c7f1bec) 233 | 234 | 235 |
236 | 237 | 20250321:`Gemmi节点`: 谷歌ComflyGeminiAPI节点支持生成文生多图(最多4张,控制时间)。 238 | 支持多图片参考,我是借用google labs的whisk思路,我感觉比较实用,并不需要太多参考图,3种足够.无需谷歌账户和梯子魔法就能用。使用的是 https://ai.comfly.chat 的 api key 239 |
240 | 查看更新/Update 241 | 242 | ![微信图片_20250321225149](https://github.com/user-attachments/assets/593f479b-51d5-476e-bcf7-f36c4f01eb29) 243 | 244 |
245 | 246 | 20250319:`Gemmi节点`: 新增谷歌ComflyGeminiAPI节点,gemini-2.0-flash-exp-image多模态模型。 247 | 目前只支持简单的单图输入和输出,图片回复,图片编辑. 248 |
249 | 查看更新/Update 250 | 251 | ![微信图片_20250319214344](https://github.com/user-attachments/assets/4ef9216d-1a27-4b71-a5f9-ad4ef4bfc7eb) 252 | 253 | 254 |
255 | 256 | 20250318:`kling节点`: 新增可灵文生视频,图生视频,视频延长(只支持v1.0模型)3个节点. 257 | 可灵视频生成时间大概要5-6分钟左右,使用的是 https://ai.comfly.chat 的 api key. 258 |
259 | 查看更新/Update 260 | 261 | ![微信图片_20250318201313](https://github.com/user-attachments/assets/96836710-95f7-4100-96ed-58e5d6553124) 262 | 263 |
264 | 265 | 20250220:`chat按钮`: 修复在ai模块单击选择模型不生效问题。 删除一些节点,简化mj节点组。 266 | 267 | 20250219:`mj按钮`: 修复在ai模块里面,mj生成图片后点击U1等按钮失效问题。新增:ai模块上方的模型选择可以双击搜索模型功能。 268 | 删除了侧边栏helpinfo按钮。 269 |
270 | 查看更新/Update 271 | 272 | ![8b8ec1ca909343daae7a0a64b542b54](https://github.com/user-attachments/assets/28e93ac5-558a-49f2-88de-cc8b9151a49c) 273 | 274 |
275 | 276 | 20241021:`comfly按钮`: 修改悬浮按钮关闭方式,直接按键盘的“A”+“X”按钮关闭和隐藏悬浮按钮,再次按键盘快捷键:“A”+“S”按钮显示悬浮按钮 277 | 注意:A不是Alt,而只是键盘的字母键而已!!!!! 278 |
279 | 查看更新/Update 280 | 281 | ![1](https://github.com/user-attachments/assets/57c56b10-e9ea-4162-8193-31a52fc6a6fd) 282 | 283 | 284 |
285 | 286 | 20241012:`插件页面`: 增加Re按钮:插件增加查看本地readme按钮。修改一些更新按钮的bug。 287 |
288 | 查看更新/Update 289 | 290 | ![e7db5bfab6500542eb994d8dd78baeb](https://github.com/user-attachments/assets/ed800a3d-56ef-427e-8b8d-893117ce2c74) 291 | 292 | ![7650f3adaee7eae0f0ef8d2b3a97542](https://github.com/user-attachments/assets/ce6f4085-68a6-462c-b7a2-94b3376b226e) 293 | 294 |
295 | 296 | 297 | 20240913:`comfly按钮`: 修改悬浮按钮,鼠标放置上去可以点击右上角关闭按钮,再次按键盘快捷键:“A”+“S”按钮显示悬浮按钮 298 |
299 | 查看更新/Update 300 | 301 | ![058a06b98ea23688ce3cb3e0c41f418](https://github.com/user-attachments/assets/6c6ed9d4-fd82-45e4-b32a-9d7ecce5c3ea) 302 | 303 |
304 | 305 | 306 | 307 | # 主要功能界面: 308 | 309 | ## Ai Chat and Midjourney 310 | 311 | * `Ai Chatbox `: 这个就是悬浮按钮,可以点击展开功能模块 312 | 313 | ![comfly编辑 (4)](https://github.com/ainewsto/Comfyui_Comfly/assets/113163264/ad5b4fde-2953-4706-a528-0d99ad8d62ee) 314 | 315 | 316 | 317 | * `Midjourney `:和各类大语言模型 318 | 319 | ![comfly编辑 (5)](https://github.com/ainewsto/Comfyui_Comfly/assets/113163264/d8656f33-0ea7-4a10-beba-0a44886cf8f4) 320 | 321 | 322 | 323 | * `Midjourney `:的节点组,workflow: 324 | 325 | 326 | ![ER%`0A514D7` 6C3WLQ6)BA](https://github.com/ainewsto/Comfyui_Comfly/assets/113163264/e8b559c6-bfd1-4dde-801e-8f49b4e1a897) 327 | 328 | 329 | 330 | > \[!IMPORTANT]\ 331 | > 由于ai和 midjourney api需要api key,请直接在这个网址:https://ai.comfly.chat 332 | > 333 | > api key可以用在任何支持自定义的第三方软件上面,直接在线使用网址:https://ai.comfly.chat/chat 334 | > 335 | > 本插件还带有coze,kling可灵文生图节点,都是免费的,需要自己填写自己的bot api或者cookie即可。 336 | > 337 | > ai模块有suno还有几个大模型没有适配,现在有点忙,还没有时间测试和适配,后续有时间再说。 338 | > 339 | 340 | ## api key数据填写文件:Comflyapi.json 341 | 342 | ![1721054266467](https://github.com/user-attachments/assets/4164b383-090c-4bfe-8c09-f3d0daae0de7) 343 | 344 | 345 | 346 | # 🥵 Comfly的QQ群 / my wechat 347 | 348 | ![86601b471a343671e7240c74aa8e1fd](https://github.com/ainewsto/Comfyui_Comfly/assets/113163264/3e1c2d15-ba5b-4aa5-a76b-08f87e7c8e2c) 349 | 350 | ![86601b471a343671e7240c74aa8e1fd](https://github.com/ainewsto/Comfyui_Comfly/assets/113163264/fdc2f849-5937-4cce-a36d-8444ecca3030) 351 | 352 | 353 | 354 | 355 | ## :sparkling_heart:依赖管理器 356 | * `Dependencies `: 这个主要是用来管理comfyui安装好的依赖,安装和卸载,包括查看依赖的所有版本,还有环境的冲突有哪些 357 | 358 | ![comfly编辑](https://github.com/ainewsto/Comfyui_Comfly/assets/113163264/dc1752c8-8d64-4364-9ba3-21507cbaacd8) 359 | 360 | 361 | 362 | 363 | ## :tangerine:Comfyui版本管理 364 | * `Comfyui version`: comfyui本体版本的管理和切换. 365 | 366 | 367 | ![comfly编辑 (1)](https://github.com/ainewsto/Comfyui_Comfly/assets/113163264/fee00ca2-b4e3-474a-a002-708a05f2adcb) 368 | 369 | 370 | 371 | ## :cactus:插件管理 372 | * `Costum nodes`: 管理插件的安装,启用和禁用,插件的版本管理,更新等等. 373 | 374 | ![comfly编辑 (2)](https://github.com/ainewsto/Comfyui_Comfly/assets/113163264/d060808f-7408-4bb5-bd62-981299da79f8) 375 | 376 | 377 | 378 | 379 | ## :partying_face:插件依赖文件安装和修改 380 | * `Requirements`: 可以查看和修改依赖版本,并且直接一键安装. 381 | 382 | 383 | ![comfly编辑 (3)](https://github.com/ainewsto/Comfyui_Comfly/assets/113163264/8d685533-52cb-4de7-ae8e-3420b6fa804d) 384 | 385 | 386 | 387 | 388 | # :dizzy:插件有参考项目 Comfy Resources: 389 | 390 | https://github.com/kijai/ComfyUI-KJNodes 391 | 392 | 感谢原项目: 393 | https://github.com/comfyanonymous/ComfyUI 394 | 395 | 396 | 397 | ## Star History 398 | 399 | [![Star History Chart](https://api.star-history.com/svg?repos=ainewsto/Comfyui_Comfly&type=Date)](https://star-history.com/#ainewsto/Comfyui_Comfly&Date) 400 | 401 | 402 | 403 | ## 🚀 About me 404 | * website: https://comfly.chat 405 | * Welcome valuable suggestions! 📧 **Email**: [3508432500@qq.com](mailto:1544007699@qq.com) 406 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .Comfly import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS, WEB_DIRECTORY 2 | from . import AiHelper 3 | 4 | __all__ = ['AiHelper', 'NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY', 'AiHelperExtension'] 5 | 6 | def start_ai_helper(): 7 | import threading 8 | import subprocess 9 | import os 10 | import sys 11 | import aiohttp 12 | 13 | def run_ai_helper(): 14 | ai_helper_path = os.path.join(os.path.dirname(__file__), "AiHelper.py") 15 | subprocess.run([sys.executable, ai_helper_path]) 16 | 17 | ai_helper_thread = threading.Thread(target=run_ai_helper) 18 | ai_helper_thread.start() 19 | 20 | start_ai_helper() 21 | 22 | -------------------------------------------------------------------------------- /docs/mjstyle/mj_art.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Conceptual art", 4 | "prompt": "Conceptual art", 5 | "negative_prompt": "ugly, deformed, noisy, blurry, NSFW" 6 | }, 7 | { 8 | "name": "Social realism", 9 | "prompt": "Social realism", 10 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 11 | }, 12 | { 13 | "name": "interior architecture", 14 | "prompt": "interior architecture", 15 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 16 | }, 17 | { 18 | "name": "modern", 19 | "prompt": "modern", 20 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 21 | }, 22 | { 23 | "name": "anime", 24 | "prompt": "anime", 25 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 26 | }, 27 | { 28 | "name": "Minimalism", 29 | "prompt": "Minimalism", 30 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 31 | }, 32 | { 33 | "name": "Romanticism", 34 | "prompt": "Romanticism", 35 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 36 | }, 37 | { 38 | "name": "Gothic art", 39 | "prompt": "Gothic art", 40 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 41 | }, 42 | { 43 | "name": "American propaganda poster", 44 | "prompt": "American propaganda poster", 45 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 46 | }, 47 | { 48 | "name": "Expressionism", 49 | "prompt": "Expressionism", 50 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 51 | }, 52 | { 53 | "name": "Realism", 54 | "prompt": "Realism", 55 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 56 | }, 57 | { 58 | "name": "Contemporary art", 59 | "prompt": "Contemporary art", 60 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 61 | }, 62 | { 63 | "name": "Dutch Golden Age painting", 64 | "prompt": "Dutch Golden Age painting", 65 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 66 | }, 67 | { 68 | "name": "Pop art", 69 | "prompt": "Pop art", 70 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 71 | }, 72 | { 73 | "name": "Monet", 74 | "prompt": "Monet", 75 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 76 | }, 77 | { 78 | "name": "Northern Renaissance", 79 | "prompt": "Northern Renaissance", 80 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 81 | }, 82 | { 83 | "name": "Dadaism", 84 | "prompt": "Dadaism", 85 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 86 | }, 87 | { 88 | "name": "Luminism", 89 | "prompt": "Luminism", 90 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 91 | }, 92 | { 93 | "name": "Art Deco", 94 | "prompt": "Art Deco", 95 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 96 | }, 97 | { 98 | "name": "rococo style", 99 | "prompt": "rococo style", 100 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 101 | }, 102 | { 103 | "name": "Tonalism", 104 | "prompt": "Tonalism", 105 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 106 | }, 107 | { 108 | "name": "Fauvism", 109 | "prompt": "Fauvism", 110 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 111 | }, 112 | { 113 | "name": "Carl Larsson", 114 | "prompt": "Carl Larsson", 115 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 116 | }, 117 | { 118 | "name": "Mannerism", 119 | "prompt": "Mannerism", 120 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 121 | }, 122 | { 123 | "name": "Action painting", 124 | "prompt": "Action painting", 125 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 126 | }, 127 | { 128 | "name": "Cubist Futurism", 129 | "prompt": "Cubist Futurism", 130 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 131 | }, 132 | { 133 | "name": "Futurism", 134 | "prompt": "Futurism", 135 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 136 | }, 137 | { 138 | "name": "Neoclassicism", 139 | "prompt": "Neoclassicism", 140 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 141 | }, 142 | { 143 | "name": "Baroque", 144 | "prompt": "Baroque", 145 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 146 | }, 147 | { 148 | "name": "Genre painting", 149 | "prompt": "Genre painting", 150 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 151 | }, 152 | { 153 | "name": "Constructivism", 154 | "prompt": "Constructivism", 155 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 156 | }, 157 | { 158 | "name": "by Alfons Mucha", 159 | "prompt": "by Alfons Mucha", 160 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 161 | }, 162 | { 163 | "name": "blind box toy style", 164 | "prompt": "blind box toy style", 165 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 166 | }, 167 | { 168 | "name": "Ukiyo-e", 169 | "prompt": "Ukiyo-e", 170 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 171 | }, 172 | { 173 | "name": "high detail", 174 | "prompt": "high detail", 175 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 176 | }, 177 | { 178 | "name": "Op art", 179 | "prompt": "Op art", 180 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 181 | }, 182 | { 183 | "name": "Color Field painting", 184 | "prompt": "Color Field painting", 185 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 186 | }, 187 | { 188 | "name": "Renaissance", 189 | "prompt": "Renaissance", 190 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 191 | }, 192 | { 193 | "name": "Surrealism", 194 | "prompt": "Surrealism", 195 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 196 | }, 197 | { 198 | "name": "Bauhaus", 199 | "prompt": "Bauhaus", 200 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 201 | }, 202 | { 203 | "name": "Abstract expressionism", 204 | "prompt": "Abstract expressionism", 205 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 206 | }, 207 | { 208 | "name": "Pre-Raphaelite Brotherhood", 209 | "prompt": "Pre-Raphaelite Brotherhood", 210 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 211 | }, 212 | { 213 | "name": "Impressionism", 214 | "prompt": "Impressionism", 215 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 216 | }, 217 | { 218 | "name": "Classicism", 219 | "prompt": "Classicism", 220 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 221 | }, 222 | { 223 | "name": "Hyperrealism", 224 | "prompt": "Hyperrealism", 225 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 226 | }, 227 | { 228 | "name": "Ghibli-like colours", 229 | "prompt": "Ghibli-like colours", 230 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 231 | }, 232 | { 233 | "name": "Abstractionism", 234 | "prompt": "Abstractionism", 235 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 236 | }, 237 | { 238 | "name": "pre-rephaëlite painting", 239 | "prompt": "pre-rephaëlite painting", 240 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 241 | }, 242 | { 243 | "name": "En plein air", 244 | "prompt": "En plein air", 245 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 246 | }, 247 | { 248 | "name": "Pointillism", 249 | "prompt": "Pointillism", 250 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 251 | }, 252 | { 253 | "name": "Verism", 254 | "prompt": "Verism", 255 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 256 | }, 257 | { 258 | "name": "raised fist", 259 | "prompt": "raised fist", 260 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 261 | }, 262 | { 263 | "name": "Ashcan School", 264 | "prompt": "Ashcan School", 265 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 266 | }, 267 | { 268 | "name": "Pixar", 269 | "prompt": "Pixar", 270 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 271 | }, 272 | { 273 | "name": "Cubism", 274 | "prompt": "Cubism", 275 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 276 | } 277 | 278 | ] 279 | 280 | -------------------------------------------------------------------------------- /docs/mjstyle/mj_hd.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "UHD", 4 | "prompt": "UHD", 5 | "negative_prompt": "ugly, deformed, noisy, blurry, NSFW" 6 | }, 7 | { 8 | "name": "anatomically correct", 9 | "prompt": "anatomically correct", 10 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 11 | }, 12 | { 13 | "name": "ccurate", 14 | "prompt": "ccurate", 15 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 16 | }, 17 | { 18 | "name": "textured skin", 19 | "prompt": "textured skin", 20 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 21 | }, 22 | { 23 | "name": "super detail", 24 | "prompt": "super detail", 25 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 26 | }, 27 | { 28 | "name": "high details", 29 | "prompt": "high details", 30 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 31 | }, 32 | { 33 | "name": "award winning", 34 | "prompt": "award winning", 35 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 36 | }, 37 | { 38 | "name": "best quality", 39 | "prompt": "best quality", 40 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 41 | }, 42 | { 43 | "name": "high quality", 44 | "prompt": "high quality", 45 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 46 | }, 47 | { 48 | "name": "1080P", 49 | "prompt": "1080P", 50 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 51 | }, 52 | { 53 | "name": "retina", 54 | "prompt": "retina", 55 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 56 | }, 57 | { 58 | "name": "HD", 59 | "prompt": "HD", 60 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 61 | }, 62 | { 63 | "name": "16k", 64 | "prompt": "16k", 65 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 66 | }, 67 | { 68 | "name": "8k", 69 | "prompt": "8k", 70 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 71 | }, 72 | { 73 | "name": "4K", 74 | "prompt": "4K", 75 | "negative_prompt": "ugly, deformed, noisy, blurry, low contrast, realism, photorealistic" 76 | } 77 | 78 | ] 79 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "Comfyui_Comfly" 3 | description = "NODES: Comfly_Mj, Comfly_mjstyle, Comfly_upload, Comfly_Mju, Comfly_Mjv, Comfly_kling_videoPreview" 4 | version = "1.0.0" 5 | license = {file = "LICENSE"} 6 | dependencies = ["aiohttp", "aiohttp-cors", "GitPython", "numpy", "Pillow", "requests", "matrix-client", "transformers", "huggingface-hub", "psutil"] 7 | 8 | [project.urls] 9 | Repository = "https://github.com/ainewsto/Comfyui_Comfly" 10 | # Used by Comfy Registry https://comfyregistry.org 11 | 12 | [tool.comfy] 13 | PublisherId = "ainewsto" 14 | DisplayName = "Comfyui_Comfly" 15 | Icon = "" 16 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp 2 | aiohttp-cors 3 | GitPython 4 | numpy 5 | Pillow 6 | requests 7 | torch 8 | GitPython 9 | matrix-client 10 | transformers 11 | huggingface-hub 12 | psutil 13 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import torch 3 | from PIL import Image 4 | from typing import List, Union 5 | 6 | def pil2tensor(image: Union[Image.Image, List[Image.Image]]) -> torch.Tensor: 7 | """ 8 | Convert PIL image(s) to tensor, matching ComfyUI's implementation. 9 | 10 | Args: 11 | image: Single PIL Image or list of PIL Images 12 | 13 | Returns: 14 | torch.Tensor: Image tensor with values normalized to [0, 1] 15 | """ 16 | if isinstance(image, list): 17 | if len(image) == 0: 18 | return torch.empty(0) 19 | return torch.cat([pil2tensor(img) for img in image], dim=0) 20 | 21 | # Convert PIL image to RGB if needed 22 | if image.mode == 'RGBA': 23 | image = image.convert('RGB') 24 | elif image.mode != 'RGB': 25 | image = image.convert('RGB') 26 | 27 | # Convert to numpy array and normalize to [0, 1] 28 | img_array = np.array(image).astype(np.float32) / 255.0 29 | 30 | # Return tensor with shape [1, H, W, 3] 31 | return torch.from_numpy(img_array)[None,] 32 | 33 | def tensor2pil(image: torch.Tensor) -> List[Image.Image]: 34 | """ 35 | Convert tensor to PIL image(s), matching ComfyUI's implementation. 36 | 37 | Args: 38 | image: Tensor with shape [B, H, W, 3] or [H, W, 3], values in range [0, 1] 39 | 40 | Returns: 41 | List[Image.Image]: List of PIL Images 42 | """ 43 | batch_count = image.size(0) if len(image.shape) > 3 else 1 44 | if batch_count > 1: 45 | out = [] 46 | for i in range(batch_count): 47 | out.extend(tensor2pil(image[i])) 48 | return out 49 | 50 | # Convert tensor to numpy array, scale to [0, 255], and clip values 51 | numpy_image = np.clip(255.0 * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8) 52 | 53 | # Convert numpy array to PIL Image 54 | return [Image.fromarray(numpy_image)] -------------------------------------------------------------------------------- /web/js/Comfly_kling_videoPreview.js: -------------------------------------------------------------------------------- 1 | import { app } from "../../../scripts/app.js"; 2 | import { api } from "../../../scripts/api.js"; 3 | 4 | function fitHeight(node) { 5 | node.setSize([node.size[0], node.computeSize([node.size[0], node.size[1]])[1]]); 6 | node?.graph?.setDirtyCanvas(true); 7 | } 8 | 9 | function chainCallback(object, property, callback) { 10 | if (object == undefined) { 11 | console.error("Tried to add callback to non-existant object"); 12 | return; 13 | } 14 | if (property in object) { 15 | const callback_orig = object[property]; 16 | object[property] = function () { 17 | const r = callback_orig.apply(this, arguments); 18 | callback.apply(this, arguments); 19 | return r; 20 | }; 21 | } else { 22 | object[property] = callback; 23 | } 24 | } 25 | 26 | function addPreviewOptions(nodeType) { 27 | chainCallback(nodeType.prototype, "getExtraMenuOptions", function (_, options) { 28 | let optNew = []; 29 | try { 30 | const previewWidget = this.widgets.find((w) => w.name === "videopreview"); 31 | 32 | let url = null; 33 | if (previewWidget?.videoEl?.hidden === false && previewWidget.videoEl.src) { 34 | url = previewWidget.videoEl.src; 35 | } 36 | if (url) { 37 | optNew.push( 38 | { 39 | content: "Open preview", 40 | callback: () => { 41 | window.open(url, "_blank"); 42 | }, 43 | }, 44 | { 45 | content: "Save preview", 46 | callback: () => { 47 | const a = document.createElement("a"); 48 | a.href = url; 49 | a.setAttribute("download", new URLSearchParams(previewWidget.value.params).get("filename")); 50 | document.body.append(a); 51 | a.click(); 52 | requestAnimationFrame(() => a.remove()); 53 | }, 54 | } 55 | ); 56 | } 57 | if (options.length > 0 && options[0] != null && optNew.length > 0) { 58 | optNew.push(null); 59 | } 60 | options.unshift(...optNew); 61 | } catch (error) { 62 | console.log(error); 63 | } 64 | }); 65 | } 66 | 67 | function previewVideo(node, file, type) { 68 | const params = { 69 | filename: file, 70 | type: type, 71 | force_size: "256x?" 72 | }; 73 | 74 | const videoUrl = api.apiURL("/view?" + new URLSearchParams(params)); 75 | 76 | // 🟢 若已有 video preview widget,复用它 77 | const existing = node.widgets?.find(w => w.name === "videopreview"); 78 | if (existing && existing.videoEl) { 79 | existing.videoEl.src = videoUrl; 80 | existing.videoEl.hidden = false; 81 | existing.parentEl.hidden = false; 82 | existing.value.params = params; 83 | fitHeight(node); 84 | return; 85 | } 86 | 87 | // 🆕 创建 widget(首次或缺失时) 88 | const element = document.createElement("div"); 89 | const previewWidget = node.addDOMWidget("videopreview", "preview", element, { 90 | serialize: false, 91 | hideOnZoom: false, 92 | getValue() { 93 | return element.value; 94 | }, 95 | setValue(v) { 96 | element.value = v; 97 | }, 98 | }); 99 | 100 | previewWidget.computeSize = function (width) { 101 | if (this.aspectRatio && !this.parentEl.hidden) { 102 | let height = (node.size[0] - 20) / this.aspectRatio + 10; 103 | if (!(height > 0)) height = 0; 104 | this.computedHeight = height + 10; 105 | return [width, height]; 106 | } 107 | return [width, -4]; 108 | }; 109 | 110 | previewWidget.value = { hidden: false, paused: false, params }; 111 | previewWidget.parentEl = document.createElement("div"); 112 | previewWidget.parentEl.className = "video_preview"; 113 | previewWidget.parentEl.style.width = "100%"; 114 | element.appendChild(previewWidget.parentEl); 115 | 116 | previewWidget.videoEl = document.createElement("video"); 117 | previewWidget.videoEl.controls = true; 118 | previewWidget.videoEl.loop = false; 119 | previewWidget.videoEl.muted = false; 120 | previewWidget.videoEl.style.width = "100%"; 121 | previewWidget.videoEl.src = videoUrl; 122 | 123 | previewWidget.videoEl.addEventListener("loadedmetadata", () => { 124 | previewWidget.aspectRatio = previewWidget.videoEl.videoWidth / previewWidget.videoEl.videoHeight; 125 | fitHeight(node); 126 | }); 127 | 128 | previewWidget.videoEl.addEventListener("error", () => { 129 | previewWidget.parentEl.hidden = true; 130 | fitHeight(node); 131 | }); 132 | 133 | previewWidget.videoEl.autoplay = !previewWidget.value.paused && !previewWidget.value.hidden; 134 | previewWidget.videoEl.hidden = false; 135 | 136 | previewWidget.parentEl.hidden = previewWidget.value.hidden; 137 | previewWidget.parentEl.appendChild(previewWidget.videoEl); 138 | } 139 | 140 | app.registerExtension({ 141 | name: "Comfly.VideoPreview", 142 | async beforeRegisterNodeDef(nodeType, nodeData, app) { 143 | if (nodeData?.name === "Comfly_kling_videoPreview") { 144 | nodeType.prototype.onExecuted = function (data) { 145 | previewVideo(this, data.video[0], data.video[1]); 146 | }; 147 | addPreviewOptions(nodeType); 148 | } 149 | } 150 | }); 151 | -------------------------------------------------------------------------------- /web/js/Comfly_mjstyle.js: -------------------------------------------------------------------------------- 1 | import{app}from"../../../scripts/app.js";import{$el}from"../../../scripts/ui.js";let pb_cache={};async function getStyles(e){if(pb_cache[e])return pb_cache[e];const t=await fetch(`http://localhost:8080/mjstyle/${e}.json`);if(200===t.status){let s=await t.json();return pb_cache[e]=s,s}}function createTagElement(e,t,s){return $el("label.Comfly_style-model-tag.comfy-btn",{dataset:{tag:e.name,name:e.name,negativePrompt:e.negative_prompt},style:{margin:"5px",display:"inline-flex",alignItems:"center",justifyContent:"space-between",fontSize:"16px"},onclick:t=>{t.preventDefault(),t.stopPropagation();let o=t.currentTarget.querySelector("input[type='checkbox']");o.checked=!o.checked,t.currentTarget.classList.toggle("Comfly_style-model-tag--selected",o.checked),s(e,o.checked)}},[$el("span",{textContent:e.name}),$el("input",{type:"checkbox",checked:t,style:{accentColor:"var(--comfy-menu-bg)"}})])}app.registerExtension({name:"Comfly_mjstyle",async beforeRegisterNodeDef(e,t,s){if(["Comfly_mjstyle"].indexOf(t.name)>=0){const t=e.prototype.onNodeCreated;e.prototype.onNodeCreated=function(){const e=t?.apply(this,arguments);this.properties.values=this.properties.values||[],this.properties.currentStyleType="";const s=this.widgets.find((e=>"styles_type"===e.name)),o=$el("div",{style:{display:"none"}},[$el("div",{textContent:"Selected Styles:",style:{marginBottom:"5px",fontWeight:"bold",fontSize:"18px"}}),$el("div.Comfly_selected-tags-list",{style:{marginBottom:"10px",padding:"5px",border:"1px solid var(--border-color)",borderRadius:"5px",maxHeight:"100px",overflowY:"auto"}})]),l=$el("div.Comfly_style-model-tags-list",{style:{height:"200px",overflowY:"auto",backgroundColor:"var(--comfy-menu-bg)",color:"var(--fg-color)",border:"1px solid var(--border-color)",borderRadius:"5px",padding:"5px",display:"none"},onwheel:e=>{e.stopPropagation()}}),i=$el("button.comfy-btn",{textContent:"Clear all",style:{marginTop:"10px",alignSelf:"flex-end",fontSize:"16px",padding:"4px 10px"},onclick:()=>{this.properties.values=[],n(),p()}}),r=$el("div.Comfly_style-preview",{style:{display:"flex",flexDirection:"column",alignItems:"stretch",position:"relative",height:"100%"}},[o,$el("div",{textContent:"Available Styles:",style:{marginTop:"10px",marginBottom:"5px",fontWeight:"bold",fontSize:"18px"}}),l,$el("div",{style:{display:"flex",justifyContent:"flex-end"}},[i])]),n=()=>{const e=o.querySelector(".Comfly_selected-tags-list");e.innerHTML="",this.properties.values.length>0?(o.style.display="block",this.properties.values.forEach((t=>{e.appendChild(createTagElement({name:t,negative_prompt:""},!0,((e,t)=>{t||(this.properties.values=this.properties.values.filter((t=>t!==e.name)),n(),p())})))}))):o.style.display="none"},p=()=>{pb_cache[this.properties.currentStyleType]&&(l.innerHTML="",pb_cache[this.properties.currentStyleType].forEach((e=>{const t=this.properties.values.includes(e.name);l.appendChild(createTagElement(e,t,((e,t)=>{t&&!this.properties.values.includes(e.name)?this.properties.values.push(e.name):t||(this.properties.values=this.properties.values.filter((t=>t!==e.name))),n()})))})))};if(s){const e=s.callback;s.callback=t=>{e&&e(t),this.properties.currentStyleType=t,t?getStyles(t).then((e=>{e&&(pb_cache[t]=e,p(),n(),l.style.display="block")})):(l.innerHTML="",l.style.display="none")}}return this.addDOMWidget("button","btn",r).getValue=()=>{const e=this.properties.values,t=e.join(",");this.properties.style_positive=e.join(", ");let s=e.map((e=>{const t=pb_cache[this.properties.currentStyleType]?.find((t=>t.name===e));return t?.negative_prompt||""})).filter((e=>e)).join(", ");return this.properties.style_negative=s,t},this.setSize([500,550]),e}}}}); 2 | -------------------------------------------------------------------------------- /web/js/comfly.js: -------------------------------------------------------------------------------- 1 | import{app}from"../../../scripts/app.js";import{helpButton}from"./help_button.js";import{dragHandle}from"./drag_handle.js";import{manageDependenciesButton}from"./Comfly_manager.js";import{chatButton}from"./chat_button.js";const ComflyExtension={name:"Comfly",async init(){console.log("[Comfly]","Extension initialized"),await chatButton.init(),this.createUI()},createUI(){const t=document.createElement("div");t.className="comfly-container",t.style.position="fixed",t.style.left="95px",t.style.bottom="20px",t.style.zIndex="1000",t.style.display="flex",t.style.flexDirection="row",t.style.alignItems="center",document.body.appendChild(t);const e=document.createElement("div");e.style.display="none",e.style.flexDirection="row",e.style.alignItems="center",e.style.marginLeft="10px";const n=dragHandle.createButton(t,e);t.appendChild(n);const o=chatButton.createButton(t);e.appendChild(o);const a=manageDependenciesButton.createButton();e.appendChild(a);const l=helpButton.createButton();e.appendChild(l),t.appendChild(e),"true"===localStorage.getItem("comflyContainerHidden")&&(t.style.display="none")}};app.registerExtension(ComflyExtension); 2 | -------------------------------------------------------------------------------- /web/js/drag_handle.js: -------------------------------------------------------------------------------- 1 | export const dragHandle={createButton(e,t){const n=document.createElement("div");n.style.position="relative",n.style.cursor="move",n.classList.add("comfly-main-button");const s=document.createElement("button");return s.style.backgroundImage="url('https://api.comfly.chat/wp-content/uploads/2024/09/Comfly-t拷贝-2.png')",s.style.backgroundSize="cover",s.style.backgroundRepeat="no-repeat",s.style.backgroundPosition="center",s.textContent="",s.style.width="38px",s.style.height="38px",s.style.padding="10px",s.style.borderRadius="50%",s.style.margin="5px",s.style.backgroundColor="transparent",s.style.border="none",s.style.boxShadow="0 0 10px 8px rgba(255, 253, 1, 0.7)",s.style.transition="transform 0.2s, box-shadow 0.2s",s.style.animation="flame 1.5s infinite alternate",n.appendChild(s),n.addEventListener("mouseover",(()=>{s.style.transition="transform 0.5s ease",s.style.transform="rotate(360deg)"})),n.addEventListener("mouseout",(()=>{s.style.transform="rotate(0deg)"})),n.addEventListener("mousedown",(()=>{s.style.transform="scale(0.95)",s.style.boxShadow="0 0 6px 5px rgba(255, 253, 1, 0.7)"})),n.addEventListener("mouseup",(()=>{s.style.transform="scale(1)",s.style.boxShadow="0 0 12px 8px rgba(255, 253, 1, 0.7)"})),n.addEventListener("click",(()=>{"none"===t.style.display?this.showContainer(t):this.hideContainer(t)})),this.makeDraggable(e,n),n.style.display="flex",t.style.display="none",n},hideContainer(e){e.style.opacity=0,setTimeout((()=>{e.style.display="none"}),500)},showContainer(e){e.style.display="flex",e.style.opacity=0,e.style.transition="opacity 0.5s ease-in-out",setTimeout((()=>{e.style.opacity=1}),10)},makeDraggable(e,t){let n,s,o,a,r=!1,d=0,i=0;const l=e=>{"touchstart"===e.type?(o=e.touches[0].clientX-d,a=e.touches[0].clientY-i):(o=e.clientX-d,a=e.clientY-i),(e.target===t||t.contains(e.target))&&(r=!0)},c=e=>{o=n,a=s,r=!1},y=t=>{r&&(t.preventDefault(),"touchmove"===t.type?(n=t.touches[0].clientX-o,s=t.touches[0].clientY-a):(n=t.clientX-o,s=t.clientY-a),d=n,i=s,u(n,s,e))},u=(e,t,n)=>{n.style.transform=`translate3d(${e}px, ${t}px, 0)`};let m;const p=(e,t)=>{m||(m=!0,setTimeout((()=>{e(),m=!1}),t))};t.addEventListener("mousedown",l),t.addEventListener("touchstart",l),document.addEventListener("mousemove",(e=>{p((()=>y(e)),10)})),document.addEventListener("touchmove",(e=>{p((()=>y(e)),10)})),document.addEventListener("mouseup",c),document.addEventListener("touchend",c)}};const style=document.createElement("style");style.textContent="\n@keyframes flame {\n 0% {\n box-shadow: 0 0 8px 3px rgba(255, 253, 1, 0.5);\n }\n 100% {\n box-shadow: 0 0 8px 5px rgba(255, 165, 0, 0.7);\n }\n}\n",document.head.append(style);let isAPressed=!1;document.addEventListener("keydown",(e=>{if("a"===e.key.toLowerCase()&&(isAPressed=!0),isAPressed&&"s"===e.key.toLowerCase()){e.preventDefault();const t=document.querySelector(".comfly-main-button");t&&dragHandle.showContainer(t)}if(isAPressed&&"x"===e.key.toLowerCase()){e.preventDefault();const t=document.querySelector(".comfly-main-button");t&&dragHandle.hideContainer(t)}})),document.addEventListener("keyup",(e=>{"a"===e.key.toLowerCase()&&(isAPressed=!1)})); 2 | -------------------------------------------------------------------------------- /web/js/help_button.js: -------------------------------------------------------------------------------- 1 | export const helpButton = { 2 | createButton() { 3 | const button = document.createElement("button"); 4 | button.innerHTML = ` 5 | 6 | 7 | 8 | `; 9 | button.style.backgroundSize = "cover"; 10 | button.style.backgroundRepeat = "no-repeat"; 11 | button.style.backgroundPosition = "center"; 12 | button.style.padding = "0px"; 13 | button.style.borderRadius = "5px"; 14 | button.style.margin = "5px"; 15 | button.style.backgroundColor = "#007bff00"; 16 | button.style.color = "white"; 17 | button.style.border = "none"; 18 | button.style.cursor = "pointer"; 19 | button.addEventListener("click", () => { 20 | this.openHelpWindow(); 21 | }); 22 | return button; 23 | }, 24 | openHelpWindow() { 25 | const helpWindow = window.open("https://comfly.chat", "_blank", "width=800,height=600,resizable=yes,menubar=no,location=no,status=no"); 26 | 27 | // Remove default window border and margin 28 | helpWindow.document.body.style.margin = "0"; 29 | helpWindow.document.body.style.padding = "0"; 30 | helpWindow.document.body.style.overflow = "hidden"; 31 | } 32 | }; -------------------------------------------------------------------------------- /web/js/split_image.js: -------------------------------------------------------------------------------- 1 | import{app}from"../../../scripts/app.js";import{$el}from"../../../scripts/ui.js";import{createImageHost}from"../../../scripts/ui/imagePreview.js";app.registerExtension({name:"split_image",async beforeRegisterNodeDef(t,e,o){if("Comfly_split_image"===e.name){t.prototype.outputType="output";const e=t.prototype.onNodeCreated;t.prototype.onNodeCreated=function(){const t=e?.apply(this,arguments),o=createImageHost(this);return this.addCustomWidget(o.el),this.onExecuted=async()=>{const t=await this.invokeMethod("get_image_paths"),e=await Promise.all(t.map((async t=>{const e=await fetch(`http://localhost:8080/view?filename=${encodeURIComponent(t)}`),o=await e.blob();return $el("img",{src:URL.createObjectURL(o),style:{maxWidth:"100%",maxHeight:"100%",objectFit:"contain"}})})));o.updateImages(e),this.setSize([500,500])},t}}}}); -------------------------------------------------------------------------------- /workflow/Comfly Doubao SeedEdit.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "f6ca7c92-3b93-49d0-b107-4bc211e77151", 3 | "revision": 0, 4 | "last_node_id": 4, 5 | "last_link_id": 3, 6 | "nodes": [ 7 | { 8 | "id": 2, 9 | "type": "LoadImage", 10 | "pos": [ 11 | -106.79183197021484, 12 | -1344.8896484375 13 | ], 14 | "size": [ 15 | 355.26800537109375, 16 | 624.8040161132812 17 | ], 18 | "flags": {}, 19 | "order": 0, 20 | "mode": 0, 21 | "inputs": [], 22 | "outputs": [ 23 | { 24 | "name": "IMAGE", 25 | "type": "IMAGE", 26 | "links": [ 27 | 1 28 | ] 29 | }, 30 | { 31 | "name": "MASK", 32 | "type": "MASK", 33 | "links": null 34 | } 35 | ], 36 | "properties": { 37 | "cnr_id": "comfy-core", 38 | "ver": "0.3.27", 39 | "Node name for S&R": "LoadImage" 40 | }, 41 | "widgets_values": [ 42 | "image_fx_ (30).jpg", 43 | "image", 44 | "" 45 | ] 46 | }, 47 | { 48 | "id": 3, 49 | "type": "PreviewImage", 50 | "pos": [ 51 | 775.0960083007812, 52 | -1335.6016845703125 53 | ], 54 | "size": [ 55 | 422.5010070800781, 56 | 596.8040161132812 57 | ], 58 | "flags": {}, 59 | "order": 2, 60 | "mode": 0, 61 | "inputs": [ 62 | { 63 | "name": "images", 64 | "type": "IMAGE", 65 | "link": 2 66 | } 67 | ], 68 | "outputs": [], 69 | "properties": { 70 | "cnr_id": "comfy-core", 71 | "ver": "0.3.27", 72 | "Node name for S&R": "PreviewImage" 73 | }, 74 | "widgets_values": [ 75 | "" 76 | ] 77 | }, 78 | { 79 | "id": 4, 80 | "type": "ShowText|pysssss", 81 | "pos": [ 82 | 1254.6778564453125, 83 | -1332.5792236328125 84 | ], 85 | "size": [ 86 | 377.956298828125, 87 | 370.8584899902344 88 | ], 89 | "flags": {}, 90 | "order": 3, 91 | "mode": 0, 92 | "inputs": [ 93 | { 94 | "name": "text", 95 | "type": "STRING", 96 | "widget": { 97 | "name": "text" 98 | }, 99 | "link": 3 100 | } 101 | ], 102 | "outputs": [ 103 | { 104 | "name": "STRING", 105 | "shape": 6, 106 | "type": "STRING", 107 | "links": null 108 | } 109 | ], 110 | "properties": { 111 | "Node name for S&R": "ShowText|pysssss" 112 | }, 113 | "widgets_values": [ 114 | "", 115 | "**SeedEdit Request**\n\nPrompt: 把黑猫变成白猫\nScale: 0.5\nSeed: 1651735202\nTime: 2025-03-25 12:17:44\n\nSuccess!\n\nImage URL: https://p26-aiop-sign.byteimg.com/tos-cn-i-vuqhorh59i/2025032512183166658E08495CC1A2D49F-0~tplv-vuqhorh59i-image.image?rk3s=7f9e702d&x-expires=1742962724&x-signature=3S1CjM3qqtl6hHymu7Fhd4V%2BwoE%3D\n\nVLM Description: 一只穿着水手服滑雪的白猫在雪山上\nRequest ID: 2025032512183166658E08495CC1A2D49F\nProcessing Time: 12.836374606s\n" 116 | ] 117 | }, 118 | { 119 | "id": 1, 120 | "type": "ComflySeededit", 121 | "pos": [ 122 | 315.6972351074219, 123 | -1337.4764404296875 124 | ], 125 | "size": [ 126 | 400, 127 | 276 128 | ], 129 | "flags": {}, 130 | "order": 1, 131 | "mode": 0, 132 | "inputs": [ 133 | { 134 | "name": "image", 135 | "type": "IMAGE", 136 | "link": 1 137 | } 138 | ], 139 | "outputs": [ 140 | { 141 | "name": "edited_image", 142 | "type": "IMAGE", 143 | "links": [ 144 | 2 145 | ] 146 | }, 147 | { 148 | "name": "response", 149 | "type": "STRING", 150 | "links": [ 151 | 3 152 | ] 153 | } 154 | ], 155 | "properties": { 156 | "Node name for S&R": "ComflySeededit" 157 | }, 158 | "widgets_values": [ 159 | "把黑猫变成白猫", 160 | 0.5, 161 | 1543254865, 162 | "randomize", 163 | true, 164 | "右下角", 165 | "英文", 166 | "ainewsto" 167 | ] 168 | } 169 | ], 170 | "links": [ 171 | [ 172 | 1, 173 | 2, 174 | 0, 175 | 1, 176 | 0, 177 | "IMAGE" 178 | ], 179 | [ 180 | 2, 181 | 1, 182 | 0, 183 | 3, 184 | 0, 185 | "IMAGE" 186 | ], 187 | [ 188 | 3, 189 | 1, 190 | 1, 191 | 4, 192 | 0, 193 | "STRING" 194 | ] 195 | ], 196 | "groups": [], 197 | "config": {}, 198 | "extra": { 199 | "ds": { 200 | "scale": 0.7513148009015777, 201 | "offset": [ 202 | 276.43375323608717, 203 | 1607.2258088279223 204 | ] 205 | } 206 | }, 207 | "version": 0.4 208 | } -------------------------------------------------------------------------------- /workflow/ComflyGeminiAPI.json: -------------------------------------------------------------------------------- 1 | { 2 | "last_node_id": 5, 3 | "last_link_id": 4, 4 | "nodes": [ 5 | { 6 | "id": 2, 7 | "type": "LoadImage", 8 | "pos": [ 9 | -694.7574462890625, 10 | -3232.098388671875 11 | ], 12 | "size": [ 13 | 315, 14 | 314 15 | ], 16 | "flags": {}, 17 | "order": 0, 18 | "mode": 0, 19 | "inputs": [], 20 | "outputs": [ 21 | { 22 | "name": "IMAGE", 23 | "type": "IMAGE", 24 | "links": [ 25 | 1 26 | ] 27 | }, 28 | { 29 | "name": "MASK", 30 | "type": "MASK", 31 | "links": null 32 | } 33 | ], 34 | "properties": { 35 | "cnr_id": "comfy-core", 36 | "ver": "0.3.26", 37 | "Node name for S&R": "LoadImage" 38 | }, 39 | "widgets_values": [ 40 | "1.jpg", 41 | "image" 42 | ] 43 | }, 44 | { 45 | "id": 5, 46 | "type": "ShowText|pysssss", 47 | "pos": [ 48 | -299.1976013183594, 49 | -3423.918212890625 50 | ], 51 | "size": [ 52 | 315, 53 | 100 54 | ], 55 | "flags": {}, 56 | "order": 4, 57 | "mode": 0, 58 | "inputs": [ 59 | { 60 | "name": "text", 61 | "type": "STRING", 62 | "widget": { 63 | "name": "text" 64 | }, 65 | "link": 4 66 | } 67 | ], 68 | "outputs": [ 69 | { 70 | "name": "STRING", 71 | "type": "STRING", 72 | "shape": 6, 73 | "links": null 74 | } 75 | ], 76 | "properties": { 77 | "cnr_id": "comfyui-custom-scripts", 78 | "ver": "2c09d59ab5ac27ac59022832bfde4eeeb9c55825", 79 | "Node name for S&R": "ShowText|pysssss" 80 | }, 81 | "widgets_values": [ 82 | "", 83 | "https://oss.ffire.cc/cdn/2025-03-25/gSQ8bUrVtykXaKsTzsUxve.png" 84 | ] 85 | }, 86 | { 87 | "id": 3, 88 | "type": "PreviewImage", 89 | "pos": [ 90 | 111.88241577148438, 91 | -3226.768310546875 92 | ], 93 | "size": [ 94 | 228, 95 | 304 96 | ], 97 | "flags": {}, 98 | "order": 2, 99 | "mode": 0, 100 | "inputs": [ 101 | { 102 | "name": "images", 103 | "type": "IMAGE", 104 | "link": 2 105 | } 106 | ], 107 | "outputs": [], 108 | "properties": { 109 | "cnr_id": "comfy-core", 110 | "ver": "0.3.26", 111 | "Node name for S&R": "PreviewImage" 112 | } 113 | }, 114 | { 115 | "id": 4, 116 | "type": "ShowText|pysssss", 117 | "pos": [ 118 | 379.5123596191406, 119 | -3225.048583984375 120 | ], 121 | "size": [ 122 | 386.5, 123 | 297.20001220703125 124 | ], 125 | "flags": {}, 126 | "order": 3, 127 | "mode": 0, 128 | "inputs": [ 129 | { 130 | "name": "text", 131 | "type": "STRING", 132 | "widget": { 133 | "name": "text" 134 | }, 135 | "link": 3 136 | } 137 | ], 138 | "outputs": [ 139 | { 140 | "name": "STRING", 141 | "type": "STRING", 142 | "shape": 6, 143 | "links": null 144 | } 145 | ], 146 | "properties": { 147 | "cnr_id": "comfyui-custom-scripts", 148 | "ver": "2c09d59ab5ac27ac59022832bfde4eeeb9c55825", 149 | "Node name for S&R": "ShowText|pysssss" 150 | }, 151 | "widgets_values": [ 152 | "", 153 | "**User prompt**: 把背景换成海洋背景\n\n**Response** (2025-03-25 13:22:18):\n![Image_0](https://oss.ffire.cc/cdn/2025-03-25/gSQ8bUrVtykXaKsTzsUxve.png)" 154 | ] 155 | }, 156 | { 157 | "id": 1, 158 | "type": "ComflyGeminiAPI", 159 | "pos": [ 160 | -336.70770263671875, 161 | -3221.64990234375 162 | ], 163 | "size": [ 164 | 400, 165 | 296 166 | ], 167 | "flags": {}, 168 | "order": 1, 169 | "mode": 0, 170 | "inputs": [ 171 | { 172 | "name": "object_image", 173 | "type": "IMAGE", 174 | "shape": 7, 175 | "link": 1 176 | }, 177 | { 178 | "name": "subject_image", 179 | "type": "IMAGE", 180 | "shape": 7, 181 | "link": null 182 | }, 183 | { 184 | "name": "scene_image", 185 | "type": "IMAGE", 186 | "shape": 7, 187 | "link": null 188 | } 189 | ], 190 | "outputs": [ 191 | { 192 | "name": "generated_images", 193 | "type": "IMAGE", 194 | "links": [ 195 | 2 196 | ], 197 | "slot_index": 0 198 | }, 199 | { 200 | "name": "response", 201 | "type": "STRING", 202 | "links": [ 203 | 3 204 | ], 205 | "slot_index": 1 206 | }, 207 | { 208 | "name": "image_url", 209 | "type": "STRING", 210 | "links": [ 211 | 4 212 | ], 213 | "slot_index": 2 214 | } 215 | ], 216 | "properties": { 217 | "cnr_id": "Comfyui_Comfly", 218 | "ver": "2929caa9b2742aae0b66cb7b3dd69355a8af46b4\n", 219 | "Node name for S&R": "ComflyGeminiAPI" 220 | }, 221 | "widgets_values": [ 222 | "把背景换成海洋背景", 223 | "gemini-2.0-flash-exp-image", 224 | "object_image size", 225 | 1, 226 | 1, 227 | 0.95, 228 | 467600140, 229 | "randomize" 230 | ] 231 | } 232 | ], 233 | "links": [ 234 | [ 235 | 1, 236 | 2, 237 | 0, 238 | 1, 239 | 0, 240 | "IMAGE" 241 | ], 242 | [ 243 | 2, 244 | 1, 245 | 0, 246 | 3, 247 | 0, 248 | "IMAGE" 249 | ], 250 | [ 251 | 3, 252 | 1, 253 | 1, 254 | 4, 255 | 0, 256 | "STRING" 257 | ], 258 | [ 259 | 4, 260 | 1, 261 | 2, 262 | 5, 263 | 0, 264 | "STRING" 265 | ] 266 | ], 267 | "groups": [], 268 | "config": {}, 269 | "extra": { 270 | "ds": { 271 | "scale": 0.9090909090909091, 272 | "offset": [ 273 | 886.3175936029033, 274 | 3574.2588311438362 275 | ] 276 | } 277 | }, 278 | "version": 0.4 279 | } -------------------------------------------------------------------------------- /workflow/Comfly_lip_sync.json: -------------------------------------------------------------------------------- 1 | { 2 | "last_node_id": 10, 3 | "last_link_id": 8, 4 | "nodes": [ 5 | { 6 | "id": 4, 7 | "type": "LoadImage", 8 | "pos": [ 9 | -161.66549682617188, 10 | -2891.03564453125 11 | ], 12 | "size": [ 13 | 329.2816162109375, 14 | 432.9647521972656 15 | ], 16 | "flags": {}, 17 | "order": 0, 18 | "mode": 0, 19 | "inputs": [], 20 | "outputs": [ 21 | { 22 | "name": "IMAGE", 23 | "type": "IMAGE", 24 | "links": [ 25 | 1 26 | ] 27 | }, 28 | { 29 | "name": "MASK", 30 | "type": "MASK", 31 | "links": null 32 | } 33 | ], 34 | "properties": { 35 | "cnr_id": "comfy-core", 36 | "ver": "0.3.26", 37 | "Node name for S&R": "LoadImage" 38 | }, 39 | "widgets_values": [ 40 | "leo19000_A_real_Chinese_little_boy_with_rich_facial_expressio_90864af4-95be-4b50-bbf3-13d9bbe5556c_2.png", 41 | "image" 42 | ] 43 | }, 44 | { 45 | "id": 7, 46 | "type": "Comfly_lip_sync", 47 | "pos": [ 48 | 535.78125, 49 | -3409.396484375 50 | ], 51 | "size": [ 52 | 400, 53 | 440 54 | ], 55 | "flags": {}, 56 | "order": 4, 57 | "mode": 0, 58 | "inputs": [ 59 | { 60 | "name": "video_id", 61 | "type": "STRING", 62 | "widget": { 63 | "name": "video_id" 64 | }, 65 | "link": 4 66 | }, 67 | { 68 | "name": "task_id", 69 | "type": "STRING", 70 | "widget": { 71 | "name": "task_id" 72 | }, 73 | "link": 5 74 | } 75 | ], 76 | "outputs": [ 77 | { 78 | "name": "video", 79 | "type": "VIDEO", 80 | "links": [ 81 | 6 82 | ], 83 | "slot_index": 0 84 | }, 85 | { 86 | "name": "video_url", 87 | "type": "STRING", 88 | "links": [ 89 | 7 90 | ], 91 | "slot_index": 1 92 | }, 93 | { 94 | "name": "task_id", 95 | "type": "STRING", 96 | "links": [ 97 | 8 98 | ], 99 | "slot_index": 2 100 | } 101 | ], 102 | "properties": { 103 | "cnr_id": "Comfyui_Comfly", 104 | "ver": "2929caa9b2742aae0b66cb7b3dd69355a8af46b4\n", 105 | "Node name for S&R": "Comfly_lip_sync" 106 | }, 107 | "widgets_values": [ 108 | "", 109 | "", 110 | "text2video", 111 | "今天天气真好啊", 112 | "zh", 113 | "阳光少年", 114 | "Sunny", 115 | 1, 116 | 1045320018, 117 | "randomize", 118 | "", 119 | "file", 120 | "", 121 | "" 122 | ] 123 | }, 124 | { 125 | "id": 9, 126 | "type": "ShowText|pysssss", 127 | "pos": [ 128 | 730.3987426757812, 129 | -3540.884765625 130 | ], 131 | "size": [ 132 | 315, 133 | 58 134 | ], 135 | "flags": {}, 136 | "order": 6, 137 | "mode": 0, 138 | "inputs": [ 139 | { 140 | "name": "text", 141 | "type": "STRING", 142 | "widget": { 143 | "name": "text" 144 | }, 145 | "link": 7 146 | } 147 | ], 148 | "outputs": [ 149 | { 150 | "name": "STRING", 151 | "type": "STRING", 152 | "shape": 6, 153 | "links": null 154 | } 155 | ], 156 | "properties": { 157 | "cnr_id": "comfyui-custom-scripts", 158 | "ver": "2c09d59ab5ac27ac59022832bfde4eeeb9c55825", 159 | "Node name for S&R": "ShowText|pysssss" 160 | }, 161 | "widgets_values": [ 162 | "" 163 | ] 164 | }, 165 | { 166 | "id": 10, 167 | "type": "ShowText|pysssss", 168 | "pos": [ 169 | 354.4396057128906, 170 | -3541.42724609375 171 | ], 172 | "size": [ 173 | 315, 174 | 58 175 | ], 176 | "flags": {}, 177 | "order": 7, 178 | "mode": 0, 179 | "inputs": [ 180 | { 181 | "name": "text", 182 | "type": "STRING", 183 | "widget": { 184 | "name": "text" 185 | }, 186 | "link": 8 187 | } 188 | ], 189 | "outputs": [ 190 | { 191 | "name": "STRING", 192 | "type": "STRING", 193 | "shape": 6, 194 | "links": null 195 | } 196 | ], 197 | "properties": { 198 | "cnr_id": "comfyui-custom-scripts", 199 | "ver": "2c09d59ab5ac27ac59022832bfde4eeeb9c55825", 200 | "Node name for S&R": "ShowText|pysssss" 201 | }, 202 | "widgets_values": [ 203 | "" 204 | ] 205 | }, 206 | { 207 | "id": 6, 208 | "type": "ShowText|pysssss", 209 | "pos": [ 210 | -35.430030822753906, 211 | -3534.5078125 212 | ], 213 | "size": [ 214 | 315, 215 | 58 216 | ], 217 | "flags": {}, 218 | "order": 3, 219 | "mode": 0, 220 | "inputs": [ 221 | { 222 | "name": "text", 223 | "type": "STRING", 224 | "widget": { 225 | "name": "text" 226 | }, 227 | "link": 3 228 | } 229 | ], 230 | "outputs": [ 231 | { 232 | "name": "STRING", 233 | "type": "STRING", 234 | "shape": 6, 235 | "links": null 236 | } 237 | ], 238 | "properties": { 239 | "cnr_id": "comfyui-custom-scripts", 240 | "ver": "2c09d59ab5ac27ac59022832bfde4eeeb9c55825", 241 | "Node name for S&R": "ShowText|pysssss" 242 | }, 243 | "widgets_values": [ 244 | "" 245 | ] 246 | }, 247 | { 248 | "id": 8, 249 | "type": "Comfly_kling_videoPreview", 250 | "pos": [ 251 | 642.485595703125, 252 | -2891.872802734375 253 | ], 254 | "size": [ 255 | 432.8571472167969, 256 | 434.2159729003906 257 | ], 258 | "flags": {}, 259 | "order": 5, 260 | "mode": 0, 261 | "inputs": [ 262 | { 263 | "name": "video", 264 | "type": "VIDEO", 265 | "link": 6 266 | } 267 | ], 268 | "outputs": [], 269 | "properties": { 270 | "cnr_id": "Comfyui_Comfly", 271 | "ver": "2929caa9b2742aae0b66cb7b3dd69355a8af46b4\n", 272 | "Node name for S&R": "Comfly_kling_videoPreview" 273 | }, 274 | "widgets_values": [] 275 | }, 276 | { 277 | "id": 3, 278 | "type": "Comfly_kling_image2video", 279 | "pos": [ 280 | 42.41004943847656, 281 | -3406.02099609375 282 | ], 283 | "size": [ 284 | 400, 285 | 442 286 | ], 287 | "flags": {}, 288 | "order": 1, 289 | "mode": 0, 290 | "inputs": [ 291 | { 292 | "name": "image", 293 | "type": "IMAGE", 294 | "link": 1 295 | }, 296 | { 297 | "name": "image_tail", 298 | "type": "IMAGE", 299 | "shape": 7, 300 | "link": null 301 | } 302 | ], 303 | "outputs": [ 304 | { 305 | "name": "video", 306 | "type": "VIDEO", 307 | "links": [ 308 | 2 309 | ], 310 | "slot_index": 0 311 | }, 312 | { 313 | "name": "video_url", 314 | "type": "STRING", 315 | "links": [ 316 | 3 317 | ], 318 | "slot_index": 1 319 | }, 320 | { 321 | "name": "task_id", 322 | "type": "STRING", 323 | "links": [ 324 | 5 325 | ], 326 | "slot_index": 2 327 | }, 328 | { 329 | "name": "video_id", 330 | "type": "STRING", 331 | "links": [ 332 | 4 333 | ] 334 | } 335 | ], 336 | "properties": { 337 | "cnr_id": "Comfyui_Comfly", 338 | "ver": "2929caa9b2742aae0b66cb7b3dd69355a8af46b4\n", 339 | "Node name for S&R": "Comfly_kling_image2video" 340 | }, 341 | "widgets_values": [ 342 | "小男孩面对镜头笑起来", 343 | "kling-v1-6", 344 | 0.5, 345 | "1:1", 346 | "std", 347 | "5", 348 | 1, 349 | "", 350 | 1537541256, 351 | "randomize", 352 | "none", 353 | 0 354 | ] 355 | }, 356 | { 357 | "id": 5, 358 | "type": "Comfly_kling_videoPreview", 359 | "pos": [ 360 | 201.4353485107422, 361 | -2892.9912109375 362 | ], 363 | "size": [ 364 | 412.43585205078125, 365 | 439.1280212402344 366 | ], 367 | "flags": {}, 368 | "order": 2, 369 | "mode": 0, 370 | "inputs": [ 371 | { 372 | "name": "video", 373 | "type": "VIDEO", 374 | "link": 2 375 | } 376 | ], 377 | "outputs": [], 378 | "properties": { 379 | "cnr_id": "Comfyui_Comfly", 380 | "ver": "2929caa9b2742aae0b66cb7b3dd69355a8af46b4\n", 381 | "Node name for S&R": "Comfly_kling_videoPreview" 382 | }, 383 | "widgets_values": [] 384 | } 385 | ], 386 | "links": [ 387 | [ 388 | 1, 389 | 4, 390 | 0, 391 | 3, 392 | 0, 393 | "IMAGE" 394 | ], 395 | [ 396 | 2, 397 | 3, 398 | 0, 399 | 5, 400 | 0, 401 | "VIDEO" 402 | ], 403 | [ 404 | 3, 405 | 3, 406 | 1, 407 | 6, 408 | 0, 409 | "STRING" 410 | ], 411 | [ 412 | 4, 413 | 3, 414 | 3, 415 | 7, 416 | 0, 417 | "STRING" 418 | ], 419 | [ 420 | 5, 421 | 3, 422 | 2, 423 | 7, 424 | 1, 425 | "STRING" 426 | ], 427 | [ 428 | 6, 429 | 7, 430 | 0, 431 | 8, 432 | 0, 433 | "VIDEO" 434 | ], 435 | [ 436 | 7, 437 | 7, 438 | 1, 439 | 9, 440 | 0, 441 | "STRING" 442 | ], 443 | [ 444 | 8, 445 | 7, 446 | 2, 447 | 10, 448 | 0, 449 | "STRING" 450 | ] 451 | ], 452 | "groups": [], 453 | "config": {}, 454 | "extra": { 455 | "ds": { 456 | "scale": 0.6830134553650706, 457 | "offset": [ 458 | 495.68704270290425, 459 | 3680.28229484384 460 | ] 461 | } 462 | }, 463 | "version": 0.4 464 | } -------------------------------------------------------------------------------- /workflow/Comfly_mj.json: -------------------------------------------------------------------------------- 1 | { 2 | "last_node_id": 245, 3 | "last_link_id": 294, 4 | "nodes": [ 5 | { 6 | "id": 234, 7 | "type": "ShowText|pysssss", 8 | "pos": [ 9 | -900, 10 | -960 11 | ], 12 | "size": [ 13 | 590.6380615234375, 14 | 217.42532348632812 15 | ], 16 | "flags": {}, 17 | "order": 4, 18 | "mode": 0, 19 | "inputs": [ 20 | { 21 | "name": "text", 22 | "type": "STRING", 23 | "link": 282, 24 | "widget": { 25 | "name": "text" 26 | } 27 | } 28 | ], 29 | "outputs": [ 30 | { 31 | "name": "STRING", 32 | "type": "STRING", 33 | "links": null, 34 | "shape": 6 35 | } 36 | ], 37 | "title": "mjtext_view", 38 | "properties": { 39 | "Node name for S&R": "ShowText|pysssss" 40 | }, 41 | "widgets_values": [ 42 | "", 43 | "A stylized illustration of a black cat wearing an orange baseball cap with \\\"LEO\\\" in black capital letters. The cat has bright green eyes, a small pink nose, and a simple, smiling mouth. Its fur is textured and slightly rough. The cat is anthropomorphized, with a human-like body, arms, and legs, and is wearing pink swim trunks with a repeating pattern and a logo. The cat is surfing a large, dark teal and green wave with whitecaps, standing on a pink surfboard. The background is a cloudy sky. The illustration style is hand-drawn or painted.\\n , super detail, best quality, 8k --v 6.1 --ar 3:4" 44 | ] 45 | }, 46 | { 47 | "id": 237, 48 | "type": "ShowText|pysssss", 49 | "pos": [ 50 | -900, 51 | -1189 52 | ], 53 | "size": [ 54 | 588.4142456054688, 55 | 154.83099365234375 56 | ], 57 | "flags": {}, 58 | "order": 1, 59 | "mode": 0, 60 | "inputs": [ 61 | { 62 | "name": "text", 63 | "type": "STRING", 64 | "link": 271, 65 | "widget": { 66 | "name": "text" 67 | } 68 | } 69 | ], 70 | "outputs": [ 71 | { 72 | "name": "STRING", 73 | "type": "STRING", 74 | "links": null, 75 | "shape": 6 76 | } 77 | ], 78 | "title": "mjstyle_view", 79 | "properties": { 80 | "Node name for S&R": "ShowText|pysssss" 81 | }, 82 | "widgets_values": [ 83 | "", 84 | ", super detail, best quality, 8k" 85 | ] 86 | }, 87 | { 88 | "id": 127, 89 | "type": "PreviewImage", 90 | "pos": [ 91 | -82.0888900756836, 92 | -629 93 | ], 94 | "size": [ 95 | 788.0629272460938, 96 | 818.4010620117188 97 | ], 98 | "flags": {}, 99 | "order": 6, 100 | "mode": 0, 101 | "inputs": [ 102 | { 103 | "name": "images", 104 | "type": "IMAGE", 105 | "link": 292, 106 | "label": "images" 107 | } 108 | ], 109 | "outputs": [], 110 | "properties": { 111 | "Node name for S&R": "PreviewImage" 112 | }, 113 | "widgets_values": [] 114 | }, 115 | { 116 | "id": 220, 117 | "type": "PreviewImage", 118 | "pos": [ 119 | -907, 120 | -622 121 | ], 122 | "size": [ 123 | 787.3584594726562, 124 | 815.804443359375 125 | ], 126 | "flags": {}, 127 | "order": 3, 128 | "mode": 0, 129 | "inputs": [ 130 | { 131 | "name": "images", 132 | "type": "IMAGE", 133 | "link": 289 134 | } 135 | ], 136 | "outputs": [], 137 | "properties": { 138 | "Node name for S&R": "PreviewImage" 139 | }, 140 | "widgets_values": [] 141 | }, 142 | { 143 | "id": 236, 144 | "type": "Comfly_mjstyle", 145 | "pos": [ 146 | -1468.6104736328125, 147 | -1280.557861328125 148 | ], 149 | "size": [ 150 | 515.4985961914062, 151 | 591.5321655273438 152 | ], 153 | "flags": {}, 154 | "order": 0, 155 | "mode": 0, 156 | "inputs": [ 157 | { 158 | "name": "positive", 159 | "type": "STRING", 160 | "link": null, 161 | "widget": { 162 | "name": "positive" 163 | } 164 | }, 165 | { 166 | "name": "negative", 167 | "type": "STRING", 168 | "link": null, 169 | "widget": { 170 | "name": "negative" 171 | } 172 | } 173 | ], 174 | "outputs": [ 175 | { 176 | "name": "style_positive", 177 | "type": "STRING", 178 | "links": [ 179 | 271, 180 | 279 181 | ], 182 | "slot_index": 0, 183 | "shape": 3 184 | }, 185 | { 186 | "name": "style_negative", 187 | "type": "STRING", 188 | "links": [], 189 | "slot_index": 1, 190 | "shape": 3 191 | } 192 | ], 193 | "properties": { 194 | "Node name for S&R": "Comfly_mjstyle", 195 | "values": [ 196 | "super detail", 197 | "best quality", 198 | "8k" 199 | ], 200 | "style_positive": "super detail, best quality, 8k", 201 | "style_negative": "" 202 | }, 203 | "widgets_values": [ 204 | "mj_art", 205 | "", 206 | "", 207 | "super detail,best quality,8k" 208 | ] 209 | }, 210 | { 211 | "id": 239, 212 | "type": "Comfly_Mj", 213 | "pos": [ 214 | -1475, 215 | -616 216 | ], 217 | "size": [ 218 | 523.5960693359375, 219 | 815.4779663085938 220 | ], 221 | "flags": {}, 222 | "order": 2, 223 | "mode": 0, 224 | "inputs": [ 225 | { 226 | "name": "no", 227 | "type": "STRING", 228 | "link": null, 229 | "widget": { 230 | "name": "no" 231 | } 232 | }, 233 | { 234 | "name": "c", 235 | "type": "INT", 236 | "link": null, 237 | "widget": { 238 | "name": "c" 239 | } 240 | }, 241 | { 242 | "name": "s", 243 | "type": "INT", 244 | "link": null, 245 | "widget": { 246 | "name": "s" 247 | } 248 | }, 249 | { 250 | "name": "iw", 251 | "type": "FLOAT", 252 | "link": null, 253 | "widget": { 254 | "name": "iw" 255 | } 256 | }, 257 | { 258 | "name": "r", 259 | "type": "INT", 260 | "link": null, 261 | "widget": { 262 | "name": "r" 263 | } 264 | }, 265 | { 266 | "name": "sw", 267 | "type": "INT", 268 | "link": null, 269 | "widget": { 270 | "name": "sw" 271 | } 272 | }, 273 | { 274 | "name": "cw", 275 | "type": "INT", 276 | "link": null, 277 | "widget": { 278 | "name": "cw" 279 | } 280 | }, 281 | { 282 | "name": "sv", 283 | "type": "COMBO", 284 | "link": null, 285 | "widget": { 286 | "name": "sv" 287 | } 288 | }, 289 | { 290 | "name": "cref", 291 | "type": "STRING", 292 | "link": null, 293 | "widget": { 294 | "name": "cref" 295 | } 296 | }, 297 | { 298 | "name": "sref", 299 | "type": "STRING", 300 | "link": null, 301 | "slot_index": 9, 302 | "widget": { 303 | "name": "sref" 304 | } 305 | }, 306 | { 307 | "name": "positive", 308 | "type": "STRING", 309 | "link": 279, 310 | "widget": { 311 | "name": "positive" 312 | } 313 | } 314 | ], 315 | "outputs": [ 316 | { 317 | "name": "image", 318 | "type": "IMAGE", 319 | "links": [ 320 | 289 321 | ], 322 | "slot_index": 0, 323 | "shape": 3 324 | }, 325 | { 326 | "name": "text", 327 | "type": "STRING", 328 | "links": [ 329 | 282 330 | ], 331 | "slot_index": 1, 332 | "shape": 3 333 | }, 334 | { 335 | "name": "taskId", 336 | "type": "STRING", 337 | "links": [ 338 | 294 339 | ], 340 | "slot_index": 2, 341 | "shape": 3 342 | } 343 | ], 344 | "properties": { 345 | "Node name for S&R": "Comfly_Mj" 346 | }, 347 | "widgets_values": [ 348 | "relax mode", 349 | "", 350 | "A stylized illustration of a black cat wearing an orange baseball cap with \\\"LEO\\\" in black capital letters. The cat has bright green eyes, a small pink nose, and a simple, smiling mouth. Its fur is textured and slightly rough. The cat is anthropomorphized, with a human-like body, arms, and legs, and is wearing pink swim trunks with a repeating pattern and a logo. The cat is surfing a large, dark teal and green wave with whitecaps, standing on a pink surfboard. The background is a cloudy sky. The illustration style is hand-drawn or painted.\\n", 351 | "v 6.1", 352 | "3:4", 353 | "", 354 | 0, 355 | 0, 356 | 0, 357 | 1, 358 | 0, 359 | 0, 360 | "1", 361 | false, 362 | false, 363 | 2101087553, 364 | "randomize", 365 | "none", 366 | "1851638940", 367 | "" 368 | ] 369 | }, 370 | { 371 | "id": 244, 372 | "type": "Comfly_Mju", 373 | "pos": [ 374 | 178.3460693359375, 375 | -917.4812622070312 376 | ], 377 | "size": [ 378 | 353.06658935546875, 379 | 166.67950439453125 380 | ], 381 | "flags": {}, 382 | "order": 5, 383 | "mode": 0, 384 | "inputs": [ 385 | { 386 | "name": "taskId", 387 | "type": "STRING", 388 | "link": 294, 389 | "widget": { 390 | "name": "taskId" 391 | } 392 | } 393 | ], 394 | "outputs": [ 395 | { 396 | "name": "image", 397 | "type": "IMAGE", 398 | "links": [ 399 | 292 400 | ], 401 | "slot_index": 0 402 | }, 403 | { 404 | "name": "taskId", 405 | "type": "STRING", 406 | "links": [ 407 | 293 408 | ], 409 | "slot_index": 1 410 | } 411 | ], 412 | "properties": { 413 | "Node name for S&R": "Comfly_Mju" 414 | }, 415 | "widgets_values": [ 416 | "", 417 | true, 418 | false, 419 | false, 420 | false 421 | ] 422 | }, 423 | { 424 | "id": 245, 425 | "type": "Comfly_Mjv", 426 | "pos": [ 427 | 1005.7476196289062, 428 | -967.2124633789062 429 | ], 430 | "size": [ 431 | 374.2900085449219, 432 | 253.6300048828125 433 | ], 434 | "flags": {}, 435 | "order": 7, 436 | "mode": 0, 437 | "inputs": [ 438 | { 439 | "name": "taskId", 440 | "type": "STRING", 441 | "link": 293, 442 | "widget": { 443 | "name": "taskId" 444 | } 445 | } 446 | ], 447 | "outputs": [ 448 | { 449 | "name": "image", 450 | "type": "IMAGE", 451 | "links": [ 452 | 291 453 | ], 454 | "slot_index": 0 455 | } 456 | ], 457 | "properties": { 458 | "Node name for S&R": "Comfly_Mjv" 459 | }, 460 | "widgets_values": [ 461 | "", 462 | false, 463 | false, 464 | false, 465 | 1, 466 | true, 467 | false, 468 | false, 469 | false 470 | ] 471 | }, 472 | { 473 | "id": 110, 474 | "type": "PreviewImage", 475 | "pos": [ 476 | 760, 477 | -629 478 | ], 479 | "size": [ 480 | 852.7117919921875, 481 | 815.7576904296875 482 | ], 483 | "flags": {}, 484 | "order": 8, 485 | "mode": 0, 486 | "inputs": [ 487 | { 488 | "name": "images", 489 | "type": "IMAGE", 490 | "link": 291, 491 | "label": "images" 492 | } 493 | ], 494 | "outputs": [], 495 | "properties": { 496 | "Node name for S&R": "PreviewImage" 497 | }, 498 | "widgets_values": [] 499 | } 500 | ], 501 | "links": [ 502 | [ 503 | 271, 504 | 236, 505 | 0, 506 | 237, 507 | 0, 508 | "STRING" 509 | ], 510 | [ 511 | 279, 512 | 236, 513 | 0, 514 | 239, 515 | 10, 516 | "STRING" 517 | ], 518 | [ 519 | 282, 520 | 239, 521 | 1, 522 | 234, 523 | 0, 524 | "STRING" 525 | ], 526 | [ 527 | 289, 528 | 239, 529 | 0, 530 | 220, 531 | 0, 532 | "IMAGE" 533 | ], 534 | [ 535 | 291, 536 | 245, 537 | 0, 538 | 110, 539 | 0, 540 | "IMAGE" 541 | ], 542 | [ 543 | 292, 544 | 244, 545 | 0, 546 | 127, 547 | 0, 548 | "IMAGE" 549 | ], 550 | [ 551 | 293, 552 | 244, 553 | 1, 554 | 245, 555 | 0, 556 | "STRING" 557 | ], 558 | [ 559 | 294, 560 | 239, 561 | 2, 562 | 244, 563 | 0, 564 | "STRING" 565 | ] 566 | ], 567 | "groups": [ 568 | { 569 | "id": 1, 570 | "title": "", 571 | "bounding": [ 572 | -1503, 573 | -1381, 574 | 3170, 575 | 1682 576 | ], 577 | "color": "#88A", 578 | "font_size": 35, 579 | "flags": {} 580 | }, 581 | { 582 | "id": 2, 583 | "title": "🤩Comfly x Midjourney", 584 | "bounding": [ 585 | -1503, 586 | -1801, 587 | 3170, 588 | 420 589 | ], 590 | "color": "#e1ff00", 591 | "font_size": 300, 592 | "flags": {} 593 | }, 594 | { 595 | "id": 5, 596 | "title": "U1-U4", 597 | "bounding": [ 598 | -100, 599 | -1056, 600 | 829, 601 | 1254 602 | ], 603 | "color": "#88A", 604 | "font_size": 24, 605 | "flags": {} 606 | }, 607 | { 608 | "id": 6, 609 | "title": "More", 610 | "bounding": [ 611 | 747, 612 | -1056, 613 | 881, 614 | 1255 615 | ], 616 | "color": "#88A", 617 | "font_size": 24, 618 | "flags": {} 619 | } 620 | ], 621 | "config": {}, 622 | "extra": { 623 | "ds": { 624 | "scale": 0.35049389948139287, 625 | "offset": [ 626 | 2033.2303599279435, 627 | 2007.5497715180475 628 | ] 629 | }, 630 | "0246.VERSION": [ 631 | 0, 632 | 0, 633 | 4 634 | ] 635 | }, 636 | "version": 0.4 637 | } -------------------------------------------------------------------------------- /workflow/comfly-gemmi-editimage.json: -------------------------------------------------------------------------------- 1 | { 2 | "last_node_id": 11, 3 | "last_link_id": 16, 4 | "nodes": [ 5 | { 6 | "id": 9, 7 | "type": "LoadImage", 8 | "pos": [ 9 | -311.8957824707031, 10 | -2835.795654296875 11 | ], 12 | "size": [ 13 | 331.3999938964844, 14 | 412.9998779296875 15 | ], 16 | "flags": {}, 17 | "order": 0, 18 | "mode": 0, 19 | "inputs": [], 20 | "outputs": [ 21 | { 22 | "name": "IMAGE", 23 | "type": "IMAGE", 24 | "links": [ 25 | 14 26 | ], 27 | "slot_index": 0 28 | }, 29 | { 30 | "name": "MASK", 31 | "type": "MASK", 32 | "links": null 33 | } 34 | ], 35 | "properties": { 36 | "cnr_id": "comfy-core", 37 | "ver": "0.3.26", 38 | "Node name for S&R": "LoadImage" 39 | }, 40 | "widgets_values": [ 41 | "1.jpg", 42 | "image" 43 | ] 44 | }, 45 | { 46 | "id": 10, 47 | "type": "LoadImage", 48 | "pos": [ 49 | 57.1302490234375, 50 | -2834.20263671875 51 | ], 52 | "size": [ 53 | 347.2222900390625, 54 | 409.1423034667969 55 | ], 56 | "flags": {}, 57 | "order": 1, 58 | "mode": 0, 59 | "inputs": [], 60 | "outputs": [ 61 | { 62 | "name": "IMAGE", 63 | "type": "IMAGE", 64 | "links": [ 65 | 15 66 | ] 67 | }, 68 | { 69 | "name": "MASK", 70 | "type": "MASK", 71 | "links": null 72 | } 73 | ], 74 | "properties": { 75 | "cnr_id": "comfy-core", 76 | "ver": "0.3.26", 77 | "Node name for S&R": "LoadImage" 78 | }, 79 | "widgets_values": [ 80 | "2.jpg", 81 | "image" 82 | ] 83 | }, 84 | { 85 | "id": 11, 86 | "type": "LoadImage", 87 | "pos": [ 88 | 436.45709228515625, 89 | -2831.99658203125 90 | ], 91 | "size": [ 92 | 351.1669006347656, 93 | 406.27459716796875 94 | ], 95 | "flags": {}, 96 | "order": 2, 97 | "mode": 0, 98 | "inputs": [], 99 | "outputs": [ 100 | { 101 | "name": "IMAGE", 102 | "type": "IMAGE", 103 | "links": [ 104 | 16 105 | ] 106 | }, 107 | { 108 | "name": "MASK", 109 | "type": "MASK", 110 | "links": null 111 | } 112 | ], 113 | "properties": { 114 | "cnr_id": "comfy-core", 115 | "ver": "0.3.26", 116 | "Node name for S&R": "LoadImage" 117 | }, 118 | "widgets_values": [ 119 | "3.jpg", 120 | "image" 121 | ] 122 | }, 123 | { 124 | "id": 3, 125 | "type": "ShowText|pysssss", 126 | "pos": [ 127 | 488.1055908203125, 128 | -2365.448486328125 129 | ], 130 | "size": [ 131 | 359.760009765625, 132 | 407.8718566894531 133 | ], 134 | "flags": {}, 135 | "order": 5, 136 | "mode": 0, 137 | "inputs": [ 138 | { 139 | "name": "text", 140 | "type": "STRING", 141 | "widget": { 142 | "name": "text" 143 | }, 144 | "link": 10 145 | } 146 | ], 147 | "outputs": [ 148 | { 149 | "name": "STRING", 150 | "type": "STRING", 151 | "shape": 6, 152 | "links": null 153 | } 154 | ], 155 | "properties": { 156 | "cnr_id": "comfyui-custom-scripts", 157 | "ver": "2c09d59ab5ac27ac59022832bfde4eeeb9c55825", 158 | "Node name for S&R": "ShowText|pysssss" 159 | }, 160 | "widgets_values": [ 161 | "", 162 | "API timeout error: API request timed out after 90 seconds" 163 | ] 164 | }, 165 | { 166 | "id": 2, 167 | "type": "PreviewImage", 168 | "pos": [ 169 | 79.46993255615234, 170 | -2371.641845703125 171 | ], 172 | "size": [ 173 | 363.3907470703125, 174 | 414.07855224609375 175 | ], 176 | "flags": {}, 177 | "order": 4, 178 | "mode": 0, 179 | "inputs": [ 180 | { 181 | "name": "images", 182 | "type": "IMAGE", 183 | "link": 9 184 | } 185 | ], 186 | "outputs": [], 187 | "properties": { 188 | "cnr_id": "comfy-core", 189 | "ver": "0.3.26", 190 | "Node name for S&R": "PreviewImage" 191 | }, 192 | "widgets_values": [] 193 | }, 194 | { 195 | "id": 8, 196 | "type": "ComflyGeminiAPI", 197 | "pos": [ 198 | -371.1843566894531, 199 | -2360.95703125 200 | ], 201 | "size": [ 202 | 405.94873046875, 203 | 397.86920166015625 204 | ], 205 | "flags": {}, 206 | "order": 3, 207 | "mode": 0, 208 | "inputs": [ 209 | { 210 | "name": "object_image", 211 | "type": "IMAGE", 212 | "shape": 7, 213 | "link": 14 214 | }, 215 | { 216 | "name": "subject_image", 217 | "type": "IMAGE", 218 | "shape": 7, 219 | "link": 15 220 | }, 221 | { 222 | "name": "scene_image", 223 | "type": "IMAGE", 224 | "shape": 7, 225 | "link": 16 226 | } 227 | ], 228 | "outputs": [ 229 | { 230 | "name": "generated_images", 231 | "type": "IMAGE", 232 | "links": [ 233 | 9 234 | ], 235 | "slot_index": 0 236 | }, 237 | { 238 | "name": "response", 239 | "type": "STRING", 240 | "links": [ 241 | 10 242 | ], 243 | "slot_index": 1 244 | } 245 | ], 246 | "properties": { 247 | "cnr_id": "Comfyui_Comfly", 248 | "ver": "2929caa9b2742aae0b66cb7b3dd69355a8af46b4\n", 249 | "Node name for S&R": "ComflyGeminiAPI" 250 | }, 251 | "widgets_values": [ 252 | "以图片3.jpg为背景, 图片2.jpg拿着图片1.jpg的化妆瓶,。", 253 | "gemini-2.0-flash-exp-image", 254 | "1024x1024", 255 | 1, 256 | 1, 257 | 0.95, 258 | 1595151616, 259 | "randomize" 260 | ] 261 | } 262 | ], 263 | "links": [ 264 | [ 265 | 9, 266 | 8, 267 | 0, 268 | 2, 269 | 0, 270 | "IMAGE" 271 | ], 272 | [ 273 | 10, 274 | 8, 275 | 1, 276 | 3, 277 | 0, 278 | "STRING" 279 | ], 280 | [ 281 | 14, 282 | 9, 283 | 0, 284 | 8, 285 | 0, 286 | "IMAGE" 287 | ], 288 | [ 289 | 15, 290 | 10, 291 | 0, 292 | 8, 293 | 1, 294 | "IMAGE" 295 | ], 296 | [ 297 | 16, 298 | 11, 299 | 0, 300 | 8, 301 | 2, 302 | "IMAGE" 303 | ] 304 | ], 305 | "groups": [], 306 | "config": {}, 307 | "extra": { 308 | "ds": { 309 | "scale": 0.6830134553650711, 310 | "offset": [ 311 | 690.4164246859499, 312 | 3077.7769017139763 313 | ] 314 | } 315 | }, 316 | "version": 0.4 317 | } -------------------------------------------------------------------------------- /workflow/comfly-gpt4o-api.json: -------------------------------------------------------------------------------- 1 | { 2 | "last_node_id": 23, 3 | "last_link_id": 26, 4 | "nodes": [ 5 | { 6 | "id": 7, 7 | "type": "CLIPTextEncode", 8 | "pos": [ 9 | 413, 10 | 389 11 | ], 12 | "size": [ 13 | 425.27801513671875, 14 | 180.6060791015625 15 | ], 16 | "flags": {}, 17 | "order": 6, 18 | "mode": 0, 19 | "inputs": [ 20 | { 21 | "name": "clip", 22 | "type": "CLIP", 23 | "link": 5 24 | } 25 | ], 26 | "outputs": [ 27 | { 28 | "name": "CONDITIONING", 29 | "type": "CONDITIONING", 30 | "links": [ 31 | 6 32 | ], 33 | "slot_index": 0 34 | } 35 | ], 36 | "properties": { 37 | "cnr_id": "comfy-core", 38 | "ver": "0.3.26", 39 | "Node name for S&R": "CLIPTextEncode" 40 | }, 41 | "widgets_values": [ 42 | "text, watermark" 43 | ] 44 | }, 45 | { 46 | "id": 6, 47 | "type": "CLIPTextEncode", 48 | "pos": [ 49 | 415, 50 | 186 51 | ], 52 | "size": [ 53 | 422.84503173828125, 54 | 164.31304931640625 55 | ], 56 | "flags": {}, 57 | "order": 5, 58 | "mode": 0, 59 | "inputs": [ 60 | { 61 | "name": "clip", 62 | "type": "CLIP", 63 | "link": 3 64 | } 65 | ], 66 | "outputs": [ 67 | { 68 | "name": "CONDITIONING", 69 | "type": "CONDITIONING", 70 | "links": [ 71 | 4 72 | ], 73 | "slot_index": 0 74 | } 75 | ], 76 | "properties": { 77 | "cnr_id": "comfy-core", 78 | "ver": "0.3.26", 79 | "Node name for S&R": "CLIPTextEncode" 80 | }, 81 | "widgets_values": [ 82 | "beautiful scenery nature glass bottle landscape, , purple galaxy bottle," 83 | ] 84 | }, 85 | { 86 | "id": 5, 87 | "type": "EmptyLatentImage", 88 | "pos": [ 89 | 473, 90 | 609 91 | ], 92 | "size": [ 93 | 315, 94 | 106 95 | ], 96 | "flags": {}, 97 | "order": 0, 98 | "mode": 0, 99 | "inputs": [], 100 | "outputs": [ 101 | { 102 | "name": "LATENT", 103 | "type": "LATENT", 104 | "links": [ 105 | 2 106 | ], 107 | "slot_index": 0 108 | } 109 | ], 110 | "properties": { 111 | "cnr_id": "comfy-core", 112 | "ver": "0.3.26", 113 | "Node name for S&R": "EmptyLatentImage" 114 | }, 115 | "widgets_values": [ 116 | 512, 117 | 512, 118 | 1 119 | ] 120 | }, 121 | { 122 | "id": 3, 123 | "type": "KSampler", 124 | "pos": [ 125 | 863, 126 | 186 127 | ], 128 | "size": [ 129 | 315, 130 | 262 131 | ], 132 | "flags": {}, 133 | "order": 8, 134 | "mode": 0, 135 | "inputs": [ 136 | { 137 | "name": "model", 138 | "type": "MODEL", 139 | "link": 1 140 | }, 141 | { 142 | "name": "positive", 143 | "type": "CONDITIONING", 144 | "link": 4 145 | }, 146 | { 147 | "name": "negative", 148 | "type": "CONDITIONING", 149 | "link": 6 150 | }, 151 | { 152 | "name": "latent_image", 153 | "type": "LATENT", 154 | "link": 2 155 | } 156 | ], 157 | "outputs": [ 158 | { 159 | "name": "LATENT", 160 | "type": "LATENT", 161 | "links": [ 162 | 7 163 | ], 164 | "slot_index": 0 165 | } 166 | ], 167 | "properties": { 168 | "cnr_id": "comfy-core", 169 | "ver": "0.3.26", 170 | "Node name for S&R": "KSampler" 171 | }, 172 | "widgets_values": [ 173 | 156680208700286, 174 | "randomize", 175 | 20, 176 | 8, 177 | "euler", 178 | "normal", 179 | 1 180 | ] 181 | }, 182 | { 183 | "id": 8, 184 | "type": "VAEDecode", 185 | "pos": [ 186 | 1209, 187 | 188 188 | ], 189 | "size": [ 190 | 210, 191 | 46 192 | ], 193 | "flags": {}, 194 | "order": 10, 195 | "mode": 0, 196 | "inputs": [ 197 | { 198 | "name": "samples", 199 | "type": "LATENT", 200 | "link": 7 201 | }, 202 | { 203 | "name": "vae", 204 | "type": "VAE", 205 | "link": 8 206 | } 207 | ], 208 | "outputs": [ 209 | { 210 | "name": "IMAGE", 211 | "type": "IMAGE", 212 | "links": [ 213 | 9 214 | ], 215 | "slot_index": 0 216 | } 217 | ], 218 | "properties": { 219 | "cnr_id": "comfy-core", 220 | "ver": "0.3.26", 221 | "Node name for S&R": "VAEDecode" 222 | }, 223 | "widgets_values": [] 224 | }, 225 | { 226 | "id": 9, 227 | "type": "SaveImage", 228 | "pos": [ 229 | 1451, 230 | 189 231 | ], 232 | "size": [ 233 | 210, 234 | 58 235 | ], 236 | "flags": {}, 237 | "order": 15, 238 | "mode": 0, 239 | "inputs": [ 240 | { 241 | "name": "images", 242 | "type": "IMAGE", 243 | "link": 9 244 | } 245 | ], 246 | "outputs": [], 247 | "properties": { 248 | "cnr_id": "comfy-core", 249 | "ver": "0.3.26" 250 | }, 251 | "widgets_values": [ 252 | "ComfyUI" 253 | ] 254 | }, 255 | { 256 | "id": 4, 257 | "type": "CheckpointLoaderSimple", 258 | "pos": [ 259 | 26, 260 | 474 261 | ], 262 | "size": [ 263 | 315, 264 | 98 265 | ], 266 | "flags": {}, 267 | "order": 1, 268 | "mode": 0, 269 | "inputs": [], 270 | "outputs": [ 271 | { 272 | "name": "MODEL", 273 | "type": "MODEL", 274 | "links": [ 275 | 1 276 | ], 277 | "slot_index": 0 278 | }, 279 | { 280 | "name": "CLIP", 281 | "type": "CLIP", 282 | "links": [ 283 | 3, 284 | 5 285 | ], 286 | "slot_index": 1 287 | }, 288 | { 289 | "name": "VAE", 290 | "type": "VAE", 291 | "links": [ 292 | 8 293 | ], 294 | "slot_index": 2 295 | } 296 | ], 297 | "properties": { 298 | "cnr_id": "comfy-core", 299 | "ver": "0.3.26", 300 | "Node name for S&R": "CheckpointLoaderSimple" 301 | }, 302 | "widgets_values": [ 303 | "v1-5-pruned-emaonly-fp16.safetensors" 304 | ] 305 | }, 306 | { 307 | "id": 12, 308 | "type": "MultiImagesInput", 309 | "pos": [ 310 | -3809.592529296875, 311 | -6912.06787109375 312 | ], 313 | "size": [ 314 | 210, 315 | 122 316 | ], 317 | "flags": {}, 318 | "order": 7, 319 | "mode": 0, 320 | "inputs": [ 321 | { 322 | "name": "image_1", 323 | "type": "IMAGE", 324 | "shape": 7, 325 | "link": 11 326 | }, 327 | { 328 | "name": "image_2", 329 | "type": "IMAGE", 330 | "shape": 7, 331 | "link": 12 332 | }, 333 | { 334 | "name": "image_3", 335 | "type": "IMAGE", 336 | "link": null 337 | } 338 | ], 339 | "outputs": [ 340 | { 341 | "name": "images", 342 | "type": "IMAGE", 343 | "links": [ 344 | 22 345 | ], 346 | "slot_index": 0 347 | } 348 | ], 349 | "properties": {}, 350 | "widgets_values": [ 351 | 3, 352 | null 353 | ] 354 | }, 355 | { 356 | "id": 17, 357 | "type": "ShowText|pysssss", 358 | "pos": [ 359 | -3049.71142578125, 360 | -6695.4765625 361 | ], 362 | "size": [ 363 | 315, 364 | 58 365 | ], 366 | "flags": {}, 367 | "order": 12, 368 | "mode": 0, 369 | "inputs": [ 370 | { 371 | "name": "text", 372 | "type": "STRING", 373 | "widget": { 374 | "name": "text" 375 | }, 376 | "link": 24 377 | } 378 | ], 379 | "outputs": [ 380 | { 381 | "name": "STRING", 382 | "type": "STRING", 383 | "shape": 6, 384 | "links": null 385 | } 386 | ], 387 | "properties": { 388 | "cnr_id": "comfyui-custom-scripts", 389 | "ver": "2c09d59ab5ac27ac59022832bfde4eeeb9c55825", 390 | "Node name for S&R": "ShowText|pysssss" 391 | }, 392 | "widgets_values": [ 393 | "" 394 | ] 395 | }, 396 | { 397 | "id": 16, 398 | "type": "PreviewImage", 399 | "pos": [ 400 | -3383.67236328125, 401 | -6687.01025390625 402 | ], 403 | "size": [ 404 | 210, 405 | 26 406 | ], 407 | "flags": {}, 408 | "order": 11, 409 | "mode": 0, 410 | "inputs": [ 411 | { 412 | "name": "images", 413 | "type": "IMAGE", 414 | "link": 23 415 | } 416 | ], 417 | "outputs": [], 418 | "properties": { 419 | "cnr_id": "comfy-core", 420 | "ver": "0.3.26", 421 | "Node name for S&R": "PreviewImage" 422 | }, 423 | "widgets_values": [] 424 | }, 425 | { 426 | "id": 18, 427 | "type": "ShowText|pysssss", 428 | "pos": [ 429 | -3541.840576171875, 430 | -6904.86572265625 431 | ], 432 | "size": [ 433 | 315, 434 | 58 435 | ], 436 | "flags": {}, 437 | "order": 13, 438 | "mode": 0, 439 | "inputs": [ 440 | { 441 | "name": "text", 442 | "type": "STRING", 443 | "widget": { 444 | "name": "text" 445 | }, 446 | "link": 25 447 | } 448 | ], 449 | "outputs": [ 450 | { 451 | "name": "STRING", 452 | "type": "STRING", 453 | "shape": 6, 454 | "links": [] 455 | } 456 | ], 457 | "properties": { 458 | "cnr_id": "comfyui-custom-scripts", 459 | "ver": "2c09d59ab5ac27ac59022832bfde4eeeb9c55825", 460 | "Node name for S&R": "ShowText|pysssss" 461 | }, 462 | "widgets_values": [ 463 | "" 464 | ] 465 | }, 466 | { 467 | "id": 22, 468 | "type": "ShowText|pysssss", 469 | "pos": [ 470 | -3144.32470703125, 471 | -6906.60888671875 472 | ], 473 | "size": [ 474 | 315, 475 | 58 476 | ], 477 | "flags": {}, 478 | "order": 14, 479 | "mode": 0, 480 | "inputs": [ 481 | { 482 | "name": "text", 483 | "type": "STRING", 484 | "widget": { 485 | "name": "text" 486 | }, 487 | "link": 26 488 | } 489 | ], 490 | "outputs": [ 491 | { 492 | "name": "STRING", 493 | "type": "STRING", 494 | "shape": 6, 495 | "links": null 496 | } 497 | ], 498 | "properties": { 499 | "cnr_id": "comfyui-custom-scripts", 500 | "ver": "2c09d59ab5ac27ac59022832bfde4eeeb9c55825", 501 | "Node name for S&R": "ShowText|pysssss" 502 | }, 503 | "widgets_values": [ 504 | "" 505 | ] 506 | }, 507 | { 508 | "id": 13, 509 | "type": "LoadImage", 510 | "pos": [ 511 | -4290.37939453125, 512 | -6890.12744140625 513 | ], 514 | "size": [ 515 | 315, 516 | 102 517 | ], 518 | "flags": {}, 519 | "order": 2, 520 | "mode": 0, 521 | "inputs": [], 522 | "outputs": [ 523 | { 524 | "name": "IMAGE", 525 | "type": "IMAGE", 526 | "links": [ 527 | 11 528 | ] 529 | }, 530 | { 531 | "name": "MASK", 532 | "type": "MASK", 533 | "links": null 534 | } 535 | ], 536 | "properties": { 537 | "cnr_id": "comfy-core", 538 | "ver": "0.3.26", 539 | "Node name for S&R": "LoadImage" 540 | }, 541 | "widgets_values": [ 542 | "07cfd7be-fbef-4d11-9408-5076ee9188ac.mp4", 543 | "image" 544 | ] 545 | }, 546 | { 547 | "id": 14, 548 | "type": "LoadImage", 549 | "pos": [ 550 | -4300.0166015625, 551 | -6664.62255859375 552 | ], 553 | "size": [ 554 | 315, 555 | 102 556 | ], 557 | "flags": {}, 558 | "order": 3, 559 | "mode": 0, 560 | "inputs": [], 561 | "outputs": [ 562 | { 563 | "name": "IMAGE", 564 | "type": "IMAGE", 565 | "links": [ 566 | 12 567 | ] 568 | }, 569 | { 570 | "name": "MASK", 571 | "type": "MASK", 572 | "links": null 573 | } 574 | ], 575 | "properties": { 576 | "cnr_id": "comfy-core", 577 | "ver": "0.3.26", 578 | "Node name for S&R": "LoadImage" 579 | }, 580 | "widgets_values": [ 581 | "07cfd7be-fbef-4d11-9408-5076ee9188ac.mp4", 582 | "image" 583 | ] 584 | }, 585 | { 586 | "id": 15, 587 | "type": "LoadImage", 588 | "pos": [ 589 | -4309.70556640625, 590 | -6385.1572265625 591 | ], 592 | "size": [ 593 | 315, 594 | 102 595 | ], 596 | "flags": {}, 597 | "order": 4, 598 | "mode": 0, 599 | "inputs": [], 600 | "outputs": [ 601 | { 602 | "name": "IMAGE", 603 | "type": "IMAGE", 604 | "links": [ 605 | 13 606 | ] 607 | }, 608 | { 609 | "name": "MASK", 610 | "type": "MASK", 611 | "links": null 612 | } 613 | ], 614 | "properties": { 615 | "cnr_id": "comfy-core", 616 | "ver": "0.3.26", 617 | "Node name for S&R": "LoadImage" 618 | }, 619 | "widgets_values": [ 620 | "07cfd7be-fbef-4d11-9408-5076ee9188ac.mp4", 621 | "image" 622 | ] 623 | }, 624 | { 625 | "id": 23, 626 | "type": "ComflyChatGPTApi", 627 | "pos": [ 628 | -3887.965576171875, 629 | -6679.4189453125 630 | ], 631 | "size": [ 632 | 400, 633 | 388 634 | ], 635 | "flags": {}, 636 | "order": 9, 637 | "mode": 0, 638 | "inputs": [ 639 | { 640 | "name": "files", 641 | "type": "FILES", 642 | "shape": 7, 643 | "link": null 644 | }, 645 | { 646 | "name": "images", 647 | "type": "IMAGE", 648 | "shape": 7, 649 | "link": 22 650 | } 651 | ], 652 | "outputs": [ 653 | { 654 | "name": "images", 655 | "type": "IMAGE", 656 | "links": [ 657 | 23 658 | ], 659 | "slot_index": 0 660 | }, 661 | { 662 | "name": "response", 663 | "type": "STRING", 664 | "links": [ 665 | 24 666 | ], 667 | "slot_index": 1 668 | }, 669 | { 670 | "name": "image_urls", 671 | "type": "STRING", 672 | "links": [ 673 | 25 674 | ], 675 | "slot_index": 2 676 | }, 677 | { 678 | "name": "prompt", 679 | "type": "STRING", 680 | "links": [ 681 | 26 682 | ], 683 | "slot_index": 3 684 | } 685 | ], 686 | "properties": { 687 | "cnr_id": "Comfyui_Comfly", 688 | "ver": "2929caa9b2742aae0b66cb7b3dd69355a8af46b4\n", 689 | "Node name for S&R": "ComflyChatGPTApi" 690 | }, 691 | "widgets_values": [ 692 | "", 693 | "gpt-4o-image-vip", 694 | "", 695 | 0.7, 696 | 4096, 697 | 1, 698 | 0, 699 | 0, 700 | -1, 701 | "randomize", 702 | 100 703 | ] 704 | } 705 | ], 706 | "links": [ 707 | [ 708 | 1, 709 | 4, 710 | 0, 711 | 3, 712 | 0, 713 | "MODEL" 714 | ], 715 | [ 716 | 2, 717 | 5, 718 | 0, 719 | 3, 720 | 3, 721 | "LATENT" 722 | ], 723 | [ 724 | 3, 725 | 4, 726 | 1, 727 | 6, 728 | 0, 729 | "CLIP" 730 | ], 731 | [ 732 | 4, 733 | 6, 734 | 0, 735 | 3, 736 | 1, 737 | "CONDITIONING" 738 | ], 739 | [ 740 | 5, 741 | 4, 742 | 1, 743 | 7, 744 | 0, 745 | "CLIP" 746 | ], 747 | [ 748 | 6, 749 | 7, 750 | 0, 751 | 3, 752 | 2, 753 | "CONDITIONING" 754 | ], 755 | [ 756 | 7, 757 | 3, 758 | 0, 759 | 8, 760 | 0, 761 | "LATENT" 762 | ], 763 | [ 764 | 8, 765 | 4, 766 | 2, 767 | 8, 768 | 1, 769 | "VAE" 770 | ], 771 | [ 772 | 9, 773 | 8, 774 | 0, 775 | 9, 776 | 0, 777 | "IMAGE" 778 | ], 779 | [ 780 | 11, 781 | 13, 782 | 0, 783 | 12, 784 | 0, 785 | "IMAGE" 786 | ], 787 | [ 788 | 12, 789 | 14, 790 | 0, 791 | 12, 792 | 1, 793 | "IMAGE" 794 | ], 795 | [ 796 | 13, 797 | 15, 798 | 0, 799 | 12, 800 | 2, 801 | "IMAGE" 802 | ], 803 | [ 804 | 22, 805 | 12, 806 | 0, 807 | 23, 808 | 1, 809 | "IMAGE" 810 | ], 811 | [ 812 | 23, 813 | 23, 814 | 0, 815 | 16, 816 | 0, 817 | "IMAGE" 818 | ], 819 | [ 820 | 24, 821 | 23, 822 | 1, 823 | 17, 824 | 0, 825 | "STRING" 826 | ], 827 | [ 828 | 25, 829 | 23, 830 | 2, 831 | 18, 832 | 0, 833 | "STRING" 834 | ], 835 | [ 836 | 26, 837 | 23, 838 | 3, 839 | 22, 840 | 0, 841 | "STRING" 842 | ] 843 | ], 844 | "groups": [], 845 | "config": {}, 846 | "extra": { 847 | "ds": { 848 | "scale": 0.8264462809917354, 849 | "offset": [ 850 | 4340.5059189280055, 851 | 7102.921197925815 852 | ] 853 | } 854 | }, 855 | "version": 0.4 856 | } 857 | -------------------------------------------------------------------------------- /workflow/comfly-kling-V1.0-text2video.json: -------------------------------------------------------------------------------- 1 | { 2 | "last_node_id": 23, 3 | "last_link_id": 23, 4 | "nodes": [ 5 | { 6 | "id": 12, 7 | "type": "Comfly_kling_text2video", 8 | "pos": [ 9 | 508.3873596191406, 10 | 100.78446960449219 11 | ], 12 | "size": [ 13 | 391.20001220703125, 14 | 426 15 | ], 16 | "flags": {}, 17 | "order": 0, 18 | "mode": 0, 19 | "inputs": [], 20 | "outputs": [ 21 | { 22 | "name": "video", 23 | "type": "VIDEO", 24 | "links": [ 25 | 12 26 | ], 27 | "slot_index": 0 28 | }, 29 | { 30 | "name": "video_url", 31 | "type": "STRING", 32 | "links": [ 33 | 14 34 | ], 35 | "slot_index": 1 36 | }, 37 | { 38 | "name": "task_id", 39 | "type": "STRING", 40 | "links": null 41 | }, 42 | { 43 | "name": "video_id", 44 | "type": "STRING", 45 | "links": [ 46 | 20 47 | ], 48 | "slot_index": 3 49 | } 50 | ], 51 | "properties": { 52 | "Node name for S&R": "Comfly_kling_text2video" 53 | }, 54 | "widgets_values": [ 55 | "一只粉色的猫在天上飞", 56 | "kling-v1", 57 | 0.5, 58 | "1:1", 59 | "std", 60 | "5", 61 | 1, 62 | "", 63 | 1806499942, 64 | "randomize", 65 | "none", 66 | 0 67 | ] 68 | }, 69 | { 70 | "id": 10, 71 | "type": "ShowText|pysssss", 72 | "pos": [ 73 | 516.880126953125, 74 | -64.20478820800781 75 | ], 76 | "size": [ 77 | 372.1260986328125, 78 | 87.67584228515625 79 | ], 80 | "flags": {}, 81 | "order": 2, 82 | "mode": 0, 83 | "inputs": [ 84 | { 85 | "name": "text", 86 | "type": "STRING", 87 | "link": 14, 88 | "widget": { 89 | "name": "text" 90 | } 91 | } 92 | ], 93 | "outputs": [ 94 | { 95 | "name": "STRING", 96 | "type": "STRING", 97 | "links": null, 98 | "shape": 6 99 | } 100 | ], 101 | "properties": { 102 | "Node name for S&R": "ShowText|pysssss" 103 | }, 104 | "widgets_values": [ 105 | "" 106 | ] 107 | }, 108 | { 109 | "id": 8, 110 | "type": "Comfly_kling_videoPreview", 111 | "pos": [ 112 | 1432.8990478515625, 113 | 24.682924270629883 114 | ], 115 | "size": [ 116 | 451.5916748046875, 117 | 480.7630310058594 118 | ], 119 | "flags": {}, 120 | "order": 5, 121 | "mode": 0, 122 | "inputs": [ 123 | { 124 | "name": "video", 125 | "type": "VIDEO", 126 | "link": 5 127 | } 128 | ], 129 | "outputs": [], 130 | "properties": { 131 | "Node name for S&R": "Comfly_kling_videoPreview" 132 | }, 133 | "widgets_values": [ 134 | { 135 | "hidden": false, 136 | "paused": false, 137 | "params": {} 138 | } 139 | ] 140 | }, 141 | { 142 | "id": 23, 143 | "type": "ShowText|pysssss", 144 | "pos": [ 145 | 521.4036254882812, 146 | -212.97837829589844 147 | ], 148 | "size": [ 149 | 360.48590087890625, 150 | 72.04704284667969 151 | ], 152 | "flags": {}, 153 | "order": 3, 154 | "mode": 0, 155 | "inputs": [ 156 | { 157 | "name": "text", 158 | "type": "STRING", 159 | "link": 20, 160 | "widget": { 161 | "name": "text" 162 | } 163 | } 164 | ], 165 | "outputs": [ 166 | { 167 | "name": "STRING", 168 | "type": "STRING", 169 | "links": [ 170 | 23 171 | ], 172 | "shape": 6, 173 | "slot_index": 0 174 | } 175 | ], 176 | "properties": { 177 | "Node name for S&R": "ShowText|pysssss" 178 | }, 179 | "widgets_values": [ 180 | "" 181 | ] 182 | }, 183 | { 184 | "id": 7, 185 | "type": "Comfly_video_extend", 186 | "pos": [ 187 | 1003.2788696289062, 188 | -194.6882781982422 189 | ], 190 | "size": [ 191 | 318.736083984375, 192 | 114.19198608398438 193 | ], 194 | "flags": {}, 195 | "order": 4, 196 | "mode": 0, 197 | "inputs": [ 198 | { 199 | "name": "video_id", 200 | "type": "STRING", 201 | "link": 23, 202 | "widget": { 203 | "name": "video_id" 204 | } 205 | } 206 | ], 207 | "outputs": [ 208 | { 209 | "name": "video", 210 | "type": "VIDEO", 211 | "links": [ 212 | 5 213 | ], 214 | "slot_index": 0 215 | }, 216 | { 217 | "name": "video_id", 218 | "type": "STRING", 219 | "links": [ 220 | 6 221 | ], 222 | "slot_index": 1 223 | } 224 | ], 225 | "properties": { 226 | "Node name for S&R": "Comfly_video_extend" 227 | }, 228 | "widgets_values": [ 229 | "32b6cb35-1dbc-44d3-b1e8-494a70f1624e", 230 | "" 231 | ] 232 | }, 233 | { 234 | "id": 6, 235 | "type": "Comfly_kling_videoPreview", 236 | "pos": [ 237 | 953.5421752929688, 238 | 18.28876304626465 239 | ], 240 | "size": [ 241 | 438.3602294921875, 242 | 496.71234130859375 243 | ], 244 | "flags": {}, 245 | "order": 1, 246 | "mode": 0, 247 | "inputs": [ 248 | { 249 | "name": "video", 250 | "type": "VIDEO", 251 | "link": 12 252 | } 253 | ], 254 | "outputs": [], 255 | "properties": { 256 | "Node name for S&R": "Comfly_kling_videoPreview" 257 | }, 258 | "widgets_values": [] 259 | }, 260 | { 261 | "id": 9, 262 | "type": "ShowText|pysssss", 263 | "pos": [ 264 | 1444.631103515625, 265 | -190.37957763671875 266 | ], 267 | "size": [ 268 | 334.32611083984375, 269 | 76 270 | ], 271 | "flags": {}, 272 | "order": 6, 273 | "mode": 0, 274 | "inputs": [ 275 | { 276 | "name": "text", 277 | "type": "STRING", 278 | "link": 6, 279 | "widget": { 280 | "name": "text" 281 | } 282 | } 283 | ], 284 | "outputs": [ 285 | { 286 | "name": "STRING", 287 | "type": "STRING", 288 | "links": null, 289 | "shape": 6 290 | } 291 | ], 292 | "properties": { 293 | "Node name for S&R": "ShowText|pysssss" 294 | }, 295 | "widgets_values": [ 296 | "", 297 | "ca4bc1e3-cb5a-4490-a479-7b9e054cb847" 298 | ] 299 | } 300 | ], 301 | "links": [ 302 | [ 303 | 5, 304 | 7, 305 | 0, 306 | 8, 307 | 0, 308 | "VIDEO" 309 | ], 310 | [ 311 | 6, 312 | 7, 313 | 1, 314 | 9, 315 | 0, 316 | "STRING" 317 | ], 318 | [ 319 | 12, 320 | 12, 321 | 0, 322 | 6, 323 | 0, 324 | "VIDEO" 325 | ], 326 | [ 327 | 14, 328 | 12, 329 | 1, 330 | 10, 331 | 0, 332 | "STRING" 333 | ], 334 | [ 335 | 20, 336 | 12, 337 | 3, 338 | 23, 339 | 0, 340 | "STRING" 341 | ], 342 | [ 343 | 23, 344 | 23, 345 | 0, 346 | 7, 347 | 0, 348 | "STRING" 349 | ] 350 | ], 351 | "groups": [], 352 | "config": {}, 353 | "extra": { 354 | "ds": { 355 | "scale": 0.6830134553650705, 356 | "offset": [ 357 | -302.39015550392986, 358 | 383.69692544634916 359 | ] 360 | } 361 | }, 362 | "version": 0.4 363 | } -------------------------------------------------------------------------------- /workflow/comfly-kling-image2video.json: -------------------------------------------------------------------------------- 1 | { 2 | "last_node_id": 28, 3 | "last_link_id": 31, 4 | "nodes": [ 5 | { 6 | "id": 26, 7 | "type": "LoadImage", 8 | "pos": [ 9 | 148.95240783691406, 10 | 175.29641723632812 11 | ], 12 | "size": [ 13 | 325.5415344238281, 14 | 335.0829772949219 15 | ], 16 | "flags": {}, 17 | "order": 0, 18 | "mode": 0, 19 | "inputs": [], 20 | "outputs": [ 21 | { 22 | "name": "IMAGE", 23 | "type": "IMAGE", 24 | "links": [ 25 | 28 26 | ] 27 | }, 28 | { 29 | "name": "MASK", 30 | "type": "MASK", 31 | "links": null 32 | } 33 | ], 34 | "properties": { 35 | "Node name for S&R": "LoadImage" 36 | }, 37 | "widgets_values": [ 38 | "ComfyUI_temp_cqaav_00001_.png", 39 | "image" 40 | ] 41 | }, 42 | { 43 | "id": 25, 44 | "type": "LoadImage", 45 | "pos": [ 46 | 150.36965942382812, 47 | -215.4776611328125 48 | ], 49 | "size": [ 50 | 315, 51 | 314 52 | ], 53 | "flags": {}, 54 | "order": 1, 55 | "mode": 0, 56 | "inputs": [], 57 | "outputs": [ 58 | { 59 | "name": "IMAGE", 60 | "type": "IMAGE", 61 | "links": [ 62 | 27 63 | ] 64 | }, 65 | { 66 | "name": "MASK", 67 | "type": "MASK", 68 | "links": null 69 | } 70 | ], 71 | "properties": { 72 | "Node name for S&R": "LoadImage" 73 | }, 74 | "widgets_values": [ 75 | "ComfyUI_temp_cqaav_00005_.png", 76 | "image" 77 | ] 78 | }, 79 | { 80 | "id": 24, 81 | "type": "Comfly_kling_image2video", 82 | "pos": [ 83 | 517.8356323242188, 84 | -52.11933135986328 85 | ], 86 | "size": [ 87 | 418.4242858886719, 88 | 564.8904418945312 89 | ], 90 | "flags": {}, 91 | "order": 2, 92 | "mode": 0, 93 | "inputs": [ 94 | { 95 | "name": "image", 96 | "type": "IMAGE", 97 | "link": 27 98 | }, 99 | { 100 | "name": "image_tail", 101 | "type": "IMAGE", 102 | "link": 28 103 | } 104 | ], 105 | "outputs": [ 106 | { 107 | "name": "video", 108 | "type": "VIDEO", 109 | "links": [ 110 | 24 111 | ], 112 | "slot_index": 0 113 | }, 114 | { 115 | "name": "video_url", 116 | "type": "STRING", 117 | "links": [ 118 | 25 119 | ], 120 | "slot_index": 1 121 | }, 122 | { 123 | "name": "task_id", 124 | "type": "STRING", 125 | "links": null 126 | }, 127 | { 128 | "name": "video_id", 129 | "type": "STRING", 130 | "links": [], 131 | "slot_index": 3 132 | } 133 | ], 134 | "properties": { 135 | "Node name for S&R": "Comfly_kling_image2video" 136 | }, 137 | "widgets_values": [ 138 | "", 139 | "kling-v1", 140 | 0.5, 141 | "1:1", 142 | "std", 143 | "5", 144 | 1, 145 | "", 146 | 0, 147 | "randomize", 148 | "none", 149 | 0 150 | ] 151 | }, 152 | { 153 | "id": 10, 154 | "type": "ShowText|pysssss", 155 | "pos": [ 156 | 532.9033813476562, 157 | -211.59860229492188 158 | ], 159 | "size": [ 160 | 399.0537414550781, 161 | 96.1793441772461 162 | ], 163 | "flags": {}, 164 | "order": 4, 165 | "mode": 0, 166 | "inputs": [ 167 | { 168 | "name": "text", 169 | "type": "STRING", 170 | "link": 25, 171 | "widget": { 172 | "name": "text" 173 | } 174 | } 175 | ], 176 | "outputs": [ 177 | { 178 | "name": "STRING", 179 | "type": "STRING", 180 | "links": null, 181 | "shape": 6 182 | } 183 | ], 184 | "properties": { 185 | "Node name for S&R": "ShowText|pysssss" 186 | }, 187 | "widgets_values": [ 188 | "" 189 | ] 190 | }, 191 | { 192 | "id": 6, 193 | "type": "Comfly_kling_videoPreview", 194 | "pos": [ 195 | 988.9732055664062, 196 | -209.64236450195312 197 | ], 198 | "size": [ 199 | 635.3577880859375, 200 | 719.2203979492188 201 | ], 202 | "flags": {}, 203 | "order": 3, 204 | "mode": 0, 205 | "inputs": [ 206 | { 207 | "name": "video", 208 | "type": "VIDEO", 209 | "link": 24 210 | } 211 | ], 212 | "outputs": [], 213 | "properties": { 214 | "Node name for S&R": "Comfly_kling_videoPreview" 215 | }, 216 | "widgets_values": [] 217 | } 218 | ], 219 | "links": [ 220 | [ 221 | 24, 222 | 24, 223 | 0, 224 | 6, 225 | 0, 226 | "VIDEO" 227 | ], 228 | [ 229 | 25, 230 | 24, 231 | 1, 232 | 10, 233 | 0, 234 | "STRING" 235 | ], 236 | [ 237 | 27, 238 | 25, 239 | 0, 240 | 24, 241 | 0, 242 | "IMAGE" 243 | ], 244 | [ 245 | 28, 246 | 26, 247 | 0, 248 | 24, 249 | 1, 250 | "IMAGE" 251 | ] 252 | ], 253 | "groups": [], 254 | "config": {}, 255 | "extra": { 256 | "ds": { 257 | "scale": 0.5644739300537774, 258 | "offset": [ 259 | 132.6056697981938, 260 | 564.9437977196416 261 | ] 262 | } 263 | }, 264 | "version": 0.4 265 | } -------------------------------------------------------------------------------- /workflow/comfly-kling-text2video.json: -------------------------------------------------------------------------------- 1 | { 2 | "last_node_id": 29, 3 | "last_link_id": 33, 4 | "nodes": [ 5 | { 6 | "id": 6, 7 | "type": "Comfly_kling_videoPreview", 8 | "pos": [ 9 | 988.9732055664062, 10 | -209.64236450195312 11 | ], 12 | "size": [ 13 | 635.3577880859375, 14 | 719.2203979492188 15 | ], 16 | "flags": {}, 17 | "order": 1, 18 | "mode": 0, 19 | "inputs": [ 20 | { 21 | "name": "video", 22 | "type": "VIDEO", 23 | "link": 32 24 | } 25 | ], 26 | "outputs": [], 27 | "properties": { 28 | "Node name for S&R": "Comfly_kling_videoPreview" 29 | }, 30 | "widgets_values": [] 31 | }, 32 | { 33 | "id": 29, 34 | "type": "Comfly_kling_text2video", 35 | "pos": [ 36 | 522.1868896484375, 37 | -53.700565338134766 38 | ], 39 | "size": [ 40 | 428.344970703125, 41 | 559.221435546875 42 | ], 43 | "flags": {}, 44 | "order": 0, 45 | "mode": 0, 46 | "inputs": [], 47 | "outputs": [ 48 | { 49 | "name": "video", 50 | "type": "VIDEO", 51 | "links": [ 52 | 32 53 | ], 54 | "slot_index": 0 55 | }, 56 | { 57 | "name": "video_url", 58 | "type": "STRING", 59 | "links": [ 60 | 33 61 | ], 62 | "slot_index": 1 63 | }, 64 | { 65 | "name": "task_id", 66 | "type": "STRING", 67 | "links": null 68 | }, 69 | { 70 | "name": "video_id", 71 | "type": "STRING", 72 | "links": null 73 | } 74 | ], 75 | "properties": { 76 | "Node name for S&R": "Comfly_kling_text2video" 77 | }, 78 | "widgets_values": [ 79 | "", 80 | "kling-v1-6", 81 | 0.5, 82 | "1:1", 83 | "std", 84 | "5", 85 | 1, 86 | "", 87 | 0, 88 | "randomize", 89 | "none", 90 | 0 91 | ] 92 | }, 93 | { 94 | "id": 10, 95 | "type": "ShowText|pysssss", 96 | "pos": [ 97 | 530.5608520507812, 98 | -211.59860229492188 99 | ], 100 | "size": [ 101 | 413.10906982421875, 102 | 86.80906677246094 103 | ], 104 | "flags": {}, 105 | "order": 2, 106 | "mode": 0, 107 | "inputs": [ 108 | { 109 | "name": "text", 110 | "type": "STRING", 111 | "link": 33, 112 | "widget": { 113 | "name": "text" 114 | } 115 | } 116 | ], 117 | "outputs": [ 118 | { 119 | "name": "STRING", 120 | "type": "STRING", 121 | "links": null, 122 | "shape": 6, 123 | "slot_index": 0 124 | } 125 | ], 126 | "properties": { 127 | "Node name for S&R": "ShowText|pysssss" 128 | }, 129 | "widgets_values": [ 130 | "" 131 | ] 132 | } 133 | ], 134 | "links": [ 135 | [ 136 | 32, 137 | 29, 138 | 0, 139 | 6, 140 | 0, 141 | "VIDEO" 142 | ], 143 | [ 144 | 33, 145 | 29, 146 | 1, 147 | 10, 148 | 0, 149 | "STRING" 150 | ] 151 | ], 152 | "groups": [], 153 | "config": {}, 154 | "extra": { 155 | "ds": { 156 | "scale": 0.620921323059155, 157 | "offset": [ 158 | -87.07468637203304, 159 | 417.6518882089604 160 | ] 161 | } 162 | }, 163 | "version": 0.4 164 | } --------------------------------------------------------------------------------