mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-01-12 15:20:51 +08:00
Merge branch 'comfyanonymous:master' into master
This commit is contained in:
commit
ac75d4e4e0
0
api_server/__init__.py
Normal file
0
api_server/__init__.py
Normal file
0
api_server/routes/__init__.py
Normal file
0
api_server/routes/__init__.py
Normal file
3
api_server/routes/internal/README.md
Normal file
3
api_server/routes/internal/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# ComfyUI Internal Routes
|
||||
|
||||
All routes under the `/internal` path are designated for **internal use by ComfyUI only**. These routes are not intended for use by external applications may change at any time without notice.
|
||||
0
api_server/routes/internal/__init__.py
Normal file
0
api_server/routes/internal/__init__.py
Normal file
40
api_server/routes/internal/internal_routes.py
Normal file
40
api_server/routes/internal/internal_routes.py
Normal file
@ -0,0 +1,40 @@
|
||||
from aiohttp import web
|
||||
from typing import Optional
|
||||
from folder_paths import models_dir, user_directory, output_directory
|
||||
from api_server.services.file_service import FileService
|
||||
|
||||
class InternalRoutes:
|
||||
'''
|
||||
The top level web router for internal routes: /internal/*
|
||||
The endpoints here should NOT be depended upon. It is for ComfyUI frontend use only.
|
||||
Check README.md for more information.
|
||||
|
||||
'''
|
||||
def __init__(self):
|
||||
self.routes: web.RouteTableDef = web.RouteTableDef()
|
||||
self._app: Optional[web.Application] = None
|
||||
self.file_service = FileService({
|
||||
"models": models_dir,
|
||||
"user": user_directory,
|
||||
"output": output_directory
|
||||
})
|
||||
|
||||
def setup_routes(self):
|
||||
@self.routes.get('/files')
|
||||
async def list_files(request):
|
||||
directory_key = request.query.get('directory', '')
|
||||
try:
|
||||
file_list = self.file_service.list_files(directory_key)
|
||||
return web.json_response({"files": file_list})
|
||||
except ValueError as e:
|
||||
return web.json_response({"error": str(e)}, status=400)
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
|
||||
def get_app(self):
|
||||
if self._app is None:
|
||||
self._app = web.Application()
|
||||
self.setup_routes()
|
||||
self._app.add_routes(self.routes)
|
||||
return self._app
|
||||
0
api_server/services/__init__.py
Normal file
0
api_server/services/__init__.py
Normal file
13
api_server/services/file_service.py
Normal file
13
api_server/services/file_service.py
Normal file
@ -0,0 +1,13 @@
|
||||
from typing import Dict, List, Optional
|
||||
from api_server.utils.file_operations import FileSystemOperations, FileSystemItem
|
||||
|
||||
class FileService:
|
||||
def __init__(self, allowed_directories: Dict[str, str], file_system_ops: Optional[FileSystemOperations] = None):
|
||||
self.allowed_directories: Dict[str, str] = allowed_directories
|
||||
self.file_system_ops: FileSystemOperations = file_system_ops or FileSystemOperations()
|
||||
|
||||
def list_files(self, directory_key: str) -> List[FileSystemItem]:
|
||||
if directory_key not in self.allowed_directories:
|
||||
raise ValueError("Invalid directory key")
|
||||
directory_path: str = self.allowed_directories[directory_key]
|
||||
return self.file_system_ops.walk_directory(directory_path)
|
||||
42
api_server/utils/file_operations.py
Normal file
42
api_server/utils/file_operations.py
Normal file
@ -0,0 +1,42 @@
|
||||
import os
|
||||
from typing import List, Union, TypedDict, Literal
|
||||
from typing_extensions import TypeGuard
|
||||
class FileInfo(TypedDict):
|
||||
name: str
|
||||
path: str
|
||||
type: Literal["file"]
|
||||
size: int
|
||||
|
||||
class DirectoryInfo(TypedDict):
|
||||
name: str
|
||||
path: str
|
||||
type: Literal["directory"]
|
||||
|
||||
FileSystemItem = Union[FileInfo, DirectoryInfo]
|
||||
|
||||
def is_file_info(item: FileSystemItem) -> TypeGuard[FileInfo]:
|
||||
return item["type"] == "file"
|
||||
|
||||
class FileSystemOperations:
|
||||
@staticmethod
|
||||
def walk_directory(directory: str) -> List[FileSystemItem]:
|
||||
file_list: List[FileSystemItem] = []
|
||||
for root, dirs, files in os.walk(directory):
|
||||
for name in files:
|
||||
file_path = os.path.join(root, name)
|
||||
relative_path = os.path.relpath(file_path, directory)
|
||||
file_list.append({
|
||||
"name": name,
|
||||
"path": relative_path,
|
||||
"type": "file",
|
||||
"size": os.path.getsize(file_path)
|
||||
})
|
||||
for name in dirs:
|
||||
dir_path = os.path.join(root, name)
|
||||
relative_path = os.path.relpath(dir_path, directory)
|
||||
file_list.append({
|
||||
"name": name,
|
||||
"path": relative_path,
|
||||
"type": "directory"
|
||||
})
|
||||
return file_list
|
||||
@ -383,7 +383,7 @@ def minimum_inference_memory():
|
||||
|
||||
EXTRA_RESERVED_VRAM = 200 * 1024 * 1024
|
||||
if any(platform.win32_ver()):
|
||||
EXTRA_RESERVED_VRAM = 400 * 1024 * 1024 #Windows is higher because of the shared vram issue
|
||||
EXTRA_RESERVED_VRAM = 500 * 1024 * 1024 #Windows is higher because of the shared vram issue
|
||||
|
||||
if args.reserve_vram is not None:
|
||||
EXTRA_RESERVED_VRAM = args.reserve_vram * 1024 * 1024 * 1024
|
||||
|
||||
17
comfy/ops.py
17
comfy/ops.py
@ -250,17 +250,16 @@ def fp8_linear(self, input):
|
||||
return None
|
||||
|
||||
if len(input.shape) == 3:
|
||||
out = torch.empty((input.shape[0], input.shape[1], self.weight.shape[0]), device=input.device, dtype=input.dtype)
|
||||
inn = input.to(dtype)
|
||||
inn = input.view(-1, input.shape[2]).to(dtype)
|
||||
non_blocking = comfy.model_management.device_supports_non_blocking(input.device)
|
||||
w = cast_to(self.weight, device=input.device, non_blocking=non_blocking).t()
|
||||
for i in range(input.shape[0]):
|
||||
if self.bias is not None:
|
||||
o, _ = torch._scaled_mm(inn[i], w, out_dtype=input.dtype, bias=cast_to_input(self.bias, input, non_blocking=non_blocking))
|
||||
else:
|
||||
o, _ = torch._scaled_mm(inn[i], w, out_dtype=input.dtype)
|
||||
out[i] = o
|
||||
return out
|
||||
|
||||
if self.bias is not None:
|
||||
o, _ = torch._scaled_mm(inn, w, out_dtype=input.dtype, bias=cast_to_input(self.bias, input, non_blocking=non_blocking))
|
||||
else:
|
||||
o, _ = torch._scaled_mm(inn, w, out_dtype=input.dtype)
|
||||
|
||||
return o.view((-1, input.shape[1], self.weight.shape[0]))
|
||||
return None
|
||||
|
||||
class fp8_ops(manual_cast):
|
||||
|
||||
@ -4,14 +4,14 @@ class Example:
|
||||
|
||||
Class methods
|
||||
-------------
|
||||
INPUT_TYPES (dict):
|
||||
INPUT_TYPES (dict):
|
||||
Tell the main program input parameters of nodes.
|
||||
IS_CHANGED:
|
||||
optional method to control when the node is re executed.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
RETURN_TYPES (`tuple`):
|
||||
RETURN_TYPES (`tuple`):
|
||||
The type of each element in the output tuple.
|
||||
RETURN_NAMES (`tuple`):
|
||||
Optional: The name of each output in the output tuple.
|
||||
@ -23,13 +23,19 @@ class Example:
|
||||
Assumed to be False if not present.
|
||||
CATEGORY (`str`):
|
||||
The category the node should appear in the UI.
|
||||
DEPRECATED (`bool`):
|
||||
Indicates whether the node is deprecated. Deprecated nodes are hidden by default in the UI, but remain
|
||||
functional in existing workflows that use them.
|
||||
EXPERIMENTAL (`bool`):
|
||||
Indicates whether the node is experimental. Experimental nodes are marked as such in the UI and may be subject to
|
||||
significant changes or removal in future versions. Use with caution in production workflows.
|
||||
execute(s) -> tuple || None:
|
||||
The entry point method. The name of this method must be the same as the value of property `FUNCTION`.
|
||||
For example, if `FUNCTION = "execute"` then this method's name must be `execute`, if `FUNCTION = "foo"` then it must be `foo`.
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
"""
|
||||
|
||||
17
server.py
17
server.py
@ -29,6 +29,8 @@ from app.frontend_management import FrontendManager
|
||||
from app.user_manager import UserManager
|
||||
from model_filemanager import download_model, DownloadModelStatus
|
||||
from typing import Optional
|
||||
from api_server.routes.internal.internal_routes import InternalRoutes
|
||||
|
||||
|
||||
class BinaryEventTypes:
|
||||
PREVIEW_IMAGE = 1
|
||||
@ -72,6 +74,7 @@ class PromptServer():
|
||||
mimetypes.types_map['.js'] = 'application/javascript; charset=utf-8'
|
||||
|
||||
self.user_manager = UserManager()
|
||||
self.internal_routes = InternalRoutes()
|
||||
self.supports = ["custom_nodes_from_web"]
|
||||
self.prompt_queue = None
|
||||
self.loop = loop
|
||||
@ -139,6 +142,14 @@ class PromptServer():
|
||||
embeddings = folder_paths.get_filename_list("embeddings")
|
||||
return web.json_response(list(map(lambda a: os.path.splitext(a)[0], embeddings)))
|
||||
|
||||
@routes.get("/models/{folder}")
|
||||
async def get_models(request):
|
||||
folder = request.match_info.get("folder", None)
|
||||
if not folder in folder_paths.folder_names_and_paths:
|
||||
return web.Response(status=404)
|
||||
files = folder_paths.get_filename_list(folder)
|
||||
return web.json_response(files)
|
||||
|
||||
@routes.get("/extensions")
|
||||
async def get_extensions(request):
|
||||
files = glob.glob(os.path.join(
|
||||
@ -442,6 +453,11 @@ class PromptServer():
|
||||
|
||||
if hasattr(obj_class, 'OUTPUT_TOOLTIPS'):
|
||||
info['output_tooltips'] = obj_class.OUTPUT_TOOLTIPS
|
||||
|
||||
if getattr(obj_class, "DEPRECATED", False):
|
||||
info['deprecated'] = True
|
||||
if getattr(obj_class, "EXPERIMENTAL", False):
|
||||
info['experimental'] = True
|
||||
return info
|
||||
|
||||
@routes.get("/object_info")
|
||||
@ -597,6 +613,7 @@ class PromptServer():
|
||||
|
||||
def add_routes(self):
|
||||
self.user_manager.add_routes(self.routes)
|
||||
self.app.add_subapp('/internal', self.internal_routes.get_app())
|
||||
|
||||
# Prefix every route with /api for easier matching for delegation.
|
||||
# This is very useful for frontend dev server, which need to forward
|
||||
|
||||
115
tests-unit/server/routes/internal_routes_test.py
Normal file
115
tests-unit/server/routes/internal_routes_test.py
Normal file
@ -0,0 +1,115 @@
|
||||
import pytest
|
||||
from aiohttp import web
|
||||
from unittest.mock import MagicMock, patch
|
||||
from api_server.routes.internal.internal_routes import InternalRoutes
|
||||
from api_server.services.file_service import FileService
|
||||
from folder_paths import models_dir, user_directory, output_directory
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def internal_routes():
|
||||
return InternalRoutes()
|
||||
|
||||
@pytest.fixture
|
||||
def aiohttp_client_factory(aiohttp_client, internal_routes):
|
||||
async def _get_client():
|
||||
app = internal_routes.get_app()
|
||||
return await aiohttp_client(app)
|
||||
return _get_client
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_files_valid_directory(aiohttp_client_factory, internal_routes):
|
||||
mock_file_list = [
|
||||
{"name": "file1.txt", "path": "file1.txt", "type": "file", "size": 100},
|
||||
{"name": "dir1", "path": "dir1", "type": "directory"}
|
||||
]
|
||||
internal_routes.file_service.list_files = MagicMock(return_value=mock_file_list)
|
||||
client = await aiohttp_client_factory()
|
||||
resp = await client.get('/files?directory=models')
|
||||
assert resp.status == 200
|
||||
data = await resp.json()
|
||||
assert 'files' in data
|
||||
assert len(data['files']) == 2
|
||||
assert data['files'] == mock_file_list
|
||||
|
||||
# Check other valid directories
|
||||
resp = await client.get('/files?directory=user')
|
||||
assert resp.status == 200
|
||||
resp = await client.get('/files?directory=output')
|
||||
assert resp.status == 200
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_files_invalid_directory(aiohttp_client_factory, internal_routes):
|
||||
internal_routes.file_service.list_files = MagicMock(side_effect=ValueError("Invalid directory key"))
|
||||
client = await aiohttp_client_factory()
|
||||
resp = await client.get('/files?directory=invalid')
|
||||
assert resp.status == 400
|
||||
data = await resp.json()
|
||||
assert 'error' in data
|
||||
assert data['error'] == "Invalid directory key"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_files_exception(aiohttp_client_factory, internal_routes):
|
||||
internal_routes.file_service.list_files = MagicMock(side_effect=Exception("Unexpected error"))
|
||||
client = await aiohttp_client_factory()
|
||||
resp = await client.get('/files?directory=models')
|
||||
assert resp.status == 500
|
||||
data = await resp.json()
|
||||
assert 'error' in data
|
||||
assert data['error'] == "Unexpected error"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_files_no_directory_param(aiohttp_client_factory, internal_routes):
|
||||
mock_file_list = []
|
||||
internal_routes.file_service.list_files = MagicMock(return_value=mock_file_list)
|
||||
client = await aiohttp_client_factory()
|
||||
resp = await client.get('/files')
|
||||
assert resp.status == 200
|
||||
data = await resp.json()
|
||||
assert 'files' in data
|
||||
assert len(data['files']) == 0
|
||||
|
||||
def test_setup_routes(internal_routes):
|
||||
internal_routes.setup_routes()
|
||||
routes = internal_routes.routes
|
||||
assert any(route.method == 'GET' and str(route.path) == '/files' for route in routes)
|
||||
|
||||
def test_get_app(internal_routes):
|
||||
app = internal_routes.get_app()
|
||||
assert isinstance(app, web.Application)
|
||||
assert internal_routes._app is not None
|
||||
|
||||
def test_get_app_reuse(internal_routes):
|
||||
app1 = internal_routes.get_app()
|
||||
app2 = internal_routes.get_app()
|
||||
assert app1 is app2
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_routes_added_to_app(aiohttp_client_factory, internal_routes):
|
||||
client = await aiohttp_client_factory()
|
||||
try:
|
||||
resp = await client.get('/files')
|
||||
print(f"Response received: status {resp.status}")
|
||||
except Exception as e:
|
||||
print(f"Exception occurred during GET request: {e}")
|
||||
raise
|
||||
|
||||
assert resp.status != 404, "Route /files does not exist"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_file_service_initialization():
|
||||
with patch('api_server.routes.internal.internal_routes.FileService') as MockFileService:
|
||||
# Create a mock instance
|
||||
mock_file_service_instance = MagicMock(spec=FileService)
|
||||
MockFileService.return_value = mock_file_service_instance
|
||||
internal_routes = InternalRoutes()
|
||||
|
||||
# Check if FileService was initialized with the correct parameters
|
||||
MockFileService.assert_called_once_with({
|
||||
"models": models_dir,
|
||||
"user": user_directory,
|
||||
"output": output_directory
|
||||
})
|
||||
|
||||
# Verify that the file_service attribute of InternalRoutes is set
|
||||
assert internal_routes.file_service == mock_file_service_instance
|
||||
54
tests-unit/server/services/file_service_test.py
Normal file
54
tests-unit/server/services/file_service_test.py
Normal file
@ -0,0 +1,54 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock
|
||||
from api_server.services.file_service import FileService
|
||||
|
||||
@pytest.fixture
|
||||
def mock_file_system_ops():
|
||||
return MagicMock()
|
||||
|
||||
@pytest.fixture
|
||||
def file_service(mock_file_system_ops):
|
||||
allowed_directories = {
|
||||
"models": "/path/to/models",
|
||||
"user": "/path/to/user",
|
||||
"output": "/path/to/output"
|
||||
}
|
||||
return FileService(allowed_directories, file_system_ops=mock_file_system_ops)
|
||||
|
||||
def test_list_files_valid_directory(file_service, mock_file_system_ops):
|
||||
mock_file_system_ops.walk_directory.return_value = [
|
||||
{"name": "file1.txt", "path": "file1.txt", "type": "file", "size": 100},
|
||||
{"name": "dir1", "path": "dir1", "type": "directory"}
|
||||
]
|
||||
|
||||
result = file_service.list_files("models")
|
||||
|
||||
assert len(result) == 2
|
||||
assert result[0]["name"] == "file1.txt"
|
||||
assert result[1]["name"] == "dir1"
|
||||
mock_file_system_ops.walk_directory.assert_called_once_with("/path/to/models")
|
||||
|
||||
def test_list_files_invalid_directory(file_service):
|
||||
# Does not support walking directories outside of the allowed directories
|
||||
with pytest.raises(ValueError, match="Invalid directory key"):
|
||||
file_service.list_files("invalid_key")
|
||||
|
||||
def test_list_files_empty_directory(file_service, mock_file_system_ops):
|
||||
mock_file_system_ops.walk_directory.return_value = []
|
||||
|
||||
result = file_service.list_files("models")
|
||||
|
||||
assert len(result) == 0
|
||||
mock_file_system_ops.walk_directory.assert_called_once_with("/path/to/models")
|
||||
|
||||
@pytest.mark.parametrize("directory_key", ["models", "user", "output"])
|
||||
def test_list_files_all_allowed_directories(file_service, mock_file_system_ops, directory_key):
|
||||
mock_file_system_ops.walk_directory.return_value = [
|
||||
{"name": f"file_{directory_key}.txt", "path": f"file_{directory_key}.txt", "type": "file", "size": 100}
|
||||
]
|
||||
|
||||
result = file_service.list_files(directory_key)
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0]["name"] == f"file_{directory_key}.txt"
|
||||
mock_file_system_ops.walk_directory.assert_called_once_with(f"/path/to/{directory_key}")
|
||||
42
tests-unit/server/utils/file_operations_test.py
Normal file
42
tests-unit/server/utils/file_operations_test.py
Normal file
@ -0,0 +1,42 @@
|
||||
import pytest
|
||||
from typing import List
|
||||
from api_server.utils.file_operations import FileSystemOperations, FileSystemItem, is_file_info
|
||||
|
||||
@pytest.fixture
|
||||
def temp_directory(tmp_path):
|
||||
# Create a temporary directory structure
|
||||
dir1 = tmp_path / "dir1"
|
||||
dir2 = tmp_path / "dir2"
|
||||
dir1.mkdir()
|
||||
dir2.mkdir()
|
||||
(dir1 / "file1.txt").write_text("content1")
|
||||
(dir2 / "file2.txt").write_text("content2")
|
||||
(tmp_path / "file3.txt").write_text("content3")
|
||||
return tmp_path
|
||||
|
||||
def test_walk_directory(temp_directory):
|
||||
result: List[FileSystemItem] = FileSystemOperations.walk_directory(str(temp_directory))
|
||||
|
||||
assert len(result) == 5 # 2 directories and 3 files
|
||||
|
||||
files = [item for item in result if item['type'] == 'file']
|
||||
dirs = [item for item in result if item['type'] == 'directory']
|
||||
|
||||
assert len(files) == 3
|
||||
assert len(dirs) == 2
|
||||
|
||||
file_names = {file['name'] for file in files}
|
||||
assert file_names == {'file1.txt', 'file2.txt', 'file3.txt'}
|
||||
|
||||
dir_names = {dir['name'] for dir in dirs}
|
||||
assert dir_names == {'dir1', 'dir2'}
|
||||
|
||||
def test_walk_directory_empty(tmp_path):
|
||||
result = FileSystemOperations.walk_directory(str(tmp_path))
|
||||
assert len(result) == 0
|
||||
|
||||
def test_walk_directory_file_size(temp_directory):
|
||||
result: List[FileSystemItem] = FileSystemOperations.walk_directory(str(temp_directory))
|
||||
files = [item for item in result if is_file_info(item)]
|
||||
for file in files:
|
||||
assert file['size'] > 0 # Assuming all files have some content
|
||||
1
web/assets/index--0nRVkuV.js.map
generated
vendored
1
web/assets/index--0nRVkuV.js.map
generated
vendored
File diff suppressed because one or more lines are too long
53618
web/assets/index-D8Zp4vRl.js → web/assets/index-CaD4RONs.js
generated
vendored
53618
web/assets/index-D8Zp4vRl.js → web/assets/index-CaD4RONs.js
generated
vendored
File diff suppressed because one or more lines are too long
1
web/assets/index-CaD4RONs.js.map
generated
vendored
Normal file
1
web/assets/index-CaD4RONs.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
1
web/assets/index-D8Zp4vRl.js.map
generated
vendored
1
web/assets/index-D8Zp4vRl.js.map
generated
vendored
File diff suppressed because one or more lines are too long
500
web/assets/index-BHzRuMlR.css → web/assets/index-DAK31IJJ.css
generated
vendored
500
web/assets/index-BHzRuMlR.css → web/assets/index-DAK31IJJ.css
generated
vendored
@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: 'primeicons';
|
||||
font-display: block;
|
||||
src: url('/assets/primeicons-DMOk5skT.eot');
|
||||
src: url('/assets/primeicons-DMOk5skT.eot?#iefix') format('embedded-opentype'), url('/assets/primeicons-C6QP2o4f.woff2') format('woff2'), url('/assets/primeicons-WjwUDZjB.woff') format('woff'), url('/assets/primeicons-MpK4pl85.ttf') format('truetype'), url('/assets/primeicons-Dr5RGzOO.svg?#primeicons') format('svg');
|
||||
src: url('./primeicons-DMOk5skT.eot');
|
||||
src: url('./primeicons-DMOk5skT.eot?#iefix') format('embedded-opentype'), url('./primeicons-C6QP2o4f.woff2') format('woff2'), url('./primeicons-WjwUDZjB.woff') format('woff'), url('./primeicons-MpK4pl85.ttf') format('truetype'), url('./primeicons-Dr5RGzOO.svg?#primeicons') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@ -1330,6 +1330,184 @@
|
||||
.comfyui-body-right .side-bar-button.side-bar-button-selected[data-v-7a0b94a3]:hover {
|
||||
border-right: 4px solid var(--p-button-text-primary-color);
|
||||
}
|
||||
|
||||
:root {
|
||||
--red-600: #dc3545;
|
||||
}
|
||||
|
||||
.comfy-missing-nodes[data-v-286402f2] {
|
||||
font-family: monospace;
|
||||
color: var(--red-600);
|
||||
padding: 1.5rem;
|
||||
background-color: var(--surface-ground);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--card-shadow);
|
||||
}
|
||||
.warning-title[data-v-286402f2] {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.warning-description[data-v-286402f2] {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.missing-nodes-list[data-v-286402f2] {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.missing-nodes-list.maximized[data-v-286402f2] {
|
||||
max-height: unset;
|
||||
}
|
||||
.missing-node-item[data-v-286402f2] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
.node-type[data-v-286402f2] {
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
}
|
||||
.node-hint[data-v-286402f2] {
|
||||
margin-left: 0.5rem;
|
||||
font-style: italic;
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
[data-v-286402f2] .p-button {
|
||||
margin-left: auto;
|
||||
}
|
||||
.added-nodes-warning[data-v-286402f2] {
|
||||
margin-top: 1rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.input-slider[data-v-fbaf7a8c] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
.slider-part[data-v-fbaf7a8c] {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.input-part[data-v-fbaf7a8c] {
|
||||
width: 5rem !important;
|
||||
}
|
||||
|
||||
.info-chip[data-v-6361f2fb] {
|
||||
background: transparent;
|
||||
}
|
||||
.setting-item[data-v-6361f2fb] {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.setting-label[data-v-6361f2fb] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
.setting-input[data-v-6361f2fb] {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
/* Ensure PrimeVue components take full width of their container */
|
||||
.setting-input[data-v-6361f2fb] .p-inputtext,
|
||||
.setting-input[data-v-6361f2fb] .input-slider,
|
||||
.setting-input[data-v-6361f2fb] .p-select,
|
||||
.setting-input[data-v-6361f2fb] .p-togglebutton {
|
||||
width: 100%;
|
||||
max-width: 200px;
|
||||
}
|
||||
.setting-input[data-v-6361f2fb] .p-inputtext {
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
/* Special case for ToggleSwitch to align it to the right */
|
||||
.setting-input[data-v-6361f2fb] .p-toggleswitch {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.search-box-input[data-v-8160f15b] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.no-results-placeholder[data-v-5a7d148a] {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
padding: 2rem;
|
||||
}
|
||||
.no-results-placeholder[data-v-5a7d148a] .p-card {
|
||||
background-color: var(--surface-ground);
|
||||
text-align: center;
|
||||
}
|
||||
.no-results-placeholder h3[data-v-5a7d148a] {
|
||||
color: var(--text-color);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.no-results-placeholder p[data-v-5a7d148a] {
|
||||
color: var(--text-color-secondary);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Remove after we have tailwind setup */
|
||||
.border-none {
|
||||
border: none !important;
|
||||
}
|
||||
.settings-tab-panels {
|
||||
padding-top: 0px !important;
|
||||
}
|
||||
|
||||
.settings-container[data-v-29723d1f] {
|
||||
display: flex;
|
||||
height: 70vh;
|
||||
width: 60vw;
|
||||
max-width: 1000px;
|
||||
overflow: hidden;
|
||||
/* Prevents container from scrolling */
|
||||
}
|
||||
.settings-sidebar[data-v-29723d1f] {
|
||||
width: 250px;
|
||||
flex-shrink: 0;
|
||||
/* Prevents sidebar from shrinking */
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
}
|
||||
.settings-search-box[data-v-29723d1f] {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.settings-content[data-v-29723d1f] {
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
/* Allows vertical scrolling */
|
||||
}
|
||||
|
||||
/* Ensure the Listbox takes full width of the sidebar */
|
||||
.settings-sidebar[data-v-29723d1f] .p-listbox {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Optional: Style scrollbars for webkit browsers */
|
||||
.settings-sidebar[data-v-29723d1f]::-webkit-scrollbar,
|
||||
.settings-content[data-v-29723d1f]::-webkit-scrollbar {
|
||||
width: 1px;
|
||||
}
|
||||
.settings-sidebar[data-v-29723d1f]::-webkit-scrollbar-thumb,
|
||||
.settings-content[data-v-29723d1f]::-webkit-scrollbar-thumb {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.pi-cog[data-v-969a1066] {
|
||||
font-size: 1.25rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.version-tag[data-v-969a1066] {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
.lds-ring {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
@ -2881,6 +3059,7 @@ body {
|
||||
#graph-canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.comfyui-body-right {
|
||||
@ -3482,184 +3661,6 @@ audio.comfy-audio.empty-audio-widget {
|
||||
max-width: 25vw;
|
||||
}
|
||||
|
||||
:root {
|
||||
--red-600: #dc3545;
|
||||
}
|
||||
|
||||
.comfy-missing-nodes[data-v-286402f2] {
|
||||
font-family: monospace;
|
||||
color: var(--red-600);
|
||||
padding: 1.5rem;
|
||||
background-color: var(--surface-ground);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--card-shadow);
|
||||
}
|
||||
.warning-title[data-v-286402f2] {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.warning-description[data-v-286402f2] {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.missing-nodes-list[data-v-286402f2] {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.missing-nodes-list.maximized[data-v-286402f2] {
|
||||
max-height: unset;
|
||||
}
|
||||
.missing-node-item[data-v-286402f2] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
.node-type[data-v-286402f2] {
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
}
|
||||
.node-hint[data-v-286402f2] {
|
||||
margin-left: 0.5rem;
|
||||
font-style: italic;
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
[data-v-286402f2] .p-button {
|
||||
margin-left: auto;
|
||||
}
|
||||
.added-nodes-warning[data-v-286402f2] {
|
||||
margin-top: 1rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.input-slider[data-v-fbaf7a8c] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
.slider-part[data-v-fbaf7a8c] {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.input-part[data-v-fbaf7a8c] {
|
||||
width: 5rem !important;
|
||||
}
|
||||
|
||||
.info-chip[data-v-4feeb3d2] {
|
||||
background: transparent;
|
||||
}
|
||||
.setting-item[data-v-4feeb3d2] {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.setting-label[data-v-4feeb3d2] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
.setting-input[data-v-4feeb3d2] {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
/* Ensure PrimeVue components take full width of their container */
|
||||
.setting-input[data-v-4feeb3d2] .p-inputtext,
|
||||
.setting-input[data-v-4feeb3d2] .input-slider,
|
||||
.setting-input[data-v-4feeb3d2] .p-select,
|
||||
.setting-input[data-v-4feeb3d2] .p-togglebutton {
|
||||
width: 100%;
|
||||
max-width: 200px;
|
||||
}
|
||||
.setting-input[data-v-4feeb3d2] .p-inputtext {
|
||||
max-width: unset;
|
||||
}
|
||||
|
||||
/* Special case for ToggleSwitch to align it to the right */
|
||||
.setting-input[data-v-4feeb3d2] .p-toggleswitch {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.search-box-input[data-v-3bbe5335] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.no-results-placeholder[data-v-5a7d148a] {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
padding: 2rem;
|
||||
}
|
||||
.no-results-placeholder[data-v-5a7d148a] .p-card {
|
||||
background-color: var(--surface-ground);
|
||||
text-align: center;
|
||||
}
|
||||
.no-results-placeholder h3[data-v-5a7d148a] {
|
||||
color: var(--text-color);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.no-results-placeholder p[data-v-5a7d148a] {
|
||||
color: var(--text-color-secondary);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Remove after we have tailwind setup */
|
||||
.border-none {
|
||||
border: none !important;
|
||||
}
|
||||
.settings-tab-panels {
|
||||
padding-top: 0px !important;
|
||||
}
|
||||
|
||||
.settings-container[data-v-833dbfbb] {
|
||||
display: flex;
|
||||
height: 70vh;
|
||||
width: 60vw;
|
||||
max-width: 1000px;
|
||||
overflow: hidden;
|
||||
/* Prevents container from scrolling */
|
||||
}
|
||||
.settings-sidebar[data-v-833dbfbb] {
|
||||
width: 250px;
|
||||
flex-shrink: 0;
|
||||
/* Prevents sidebar from shrinking */
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
}
|
||||
.settings-search-box[data-v-833dbfbb] {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.settings-content[data-v-833dbfbb] {
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
/* Allows vertical scrolling */
|
||||
}
|
||||
|
||||
/* Ensure the Listbox takes full width of the sidebar */
|
||||
.settings-sidebar[data-v-833dbfbb] .p-listbox {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Optional: Style scrollbars for webkit browsers */
|
||||
.settings-sidebar[data-v-833dbfbb]::-webkit-scrollbar,
|
||||
.settings-content[data-v-833dbfbb]::-webkit-scrollbar {
|
||||
width: 1px;
|
||||
}
|
||||
.settings-sidebar[data-v-833dbfbb]::-webkit-scrollbar-thumb,
|
||||
.settings-content[data-v-833dbfbb]::-webkit-scrollbar-thumb {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.pi-cog[data-v-969a1066] {
|
||||
font-size: 1.25rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.version-tag[data-v-969a1066] {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
:root {
|
||||
--sidebar-width: 64px;
|
||||
--sidebar-icon-size: 1.5rem;
|
||||
@ -3869,77 +3870,81 @@ audio.comfy-audio.empty-audio-widget {
|
||||
color: var(--error-text);
|
||||
}
|
||||
|
||||
.comfy-vue-node-search-container[data-v-b8a4ffdc] {
|
||||
.comfy-vue-node-search-container[data-v-ba2c5897] {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: 24rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.comfy-vue-node-search-container[data-v-b8a4ffdc] * {
|
||||
.comfy-vue-node-search-container[data-v-ba2c5897] * {
|
||||
pointer-events: auto;
|
||||
}
|
||||
.comfy-vue-node-preview-container[data-v-b8a4ffdc] {
|
||||
.comfy-vue-node-preview-container[data-v-ba2c5897] {
|
||||
position: absolute;
|
||||
left: -350px;
|
||||
top: 50px;
|
||||
}
|
||||
.comfy-vue-node-search-box[data-v-b8a4ffdc] {
|
||||
.comfy-vue-node-search-box[data-v-ba2c5897] {
|
||||
z-index: 10;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.option-container[data-v-b8a4ffdc] {
|
||||
.option-container[data-v-ba2c5897] {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
.option-display-name[data-v-b8a4ffdc] {
|
||||
.option-display-name[data-v-ba2c5897] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-weight: 600;
|
||||
}
|
||||
.option-category[data-v-b8a4ffdc] {
|
||||
.option-category[data-v-ba2c5897] {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
font-weight: 300;
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(156 163 175 / var(--tw-text-opacity));
|
||||
/* Keeps the text on a single line by default */
|
||||
white-space: nowrap;
|
||||
}
|
||||
.i-badge[data-v-b8a4ffdc] {
|
||||
.i-badge[data-v-ba2c5897] {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(34 197 94 / var(--tw-bg-opacity));
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
}
|
||||
.o-badge[data-v-b8a4ffdc] {
|
||||
.o-badge[data-v-ba2c5897] {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(239 68 68 / var(--tw-bg-opacity));
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
}
|
||||
.c-badge[data-v-b8a4ffdc] {
|
||||
.c-badge[data-v-ba2c5897] {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(59 130 246 / var(--tw-bg-opacity));
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
}
|
||||
.s-badge[data-v-b8a4ffdc] {
|
||||
.s-badge[data-v-ba2c5897] {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(234 179 8 / var(--tw-bg-opacity));
|
||||
}
|
||||
[data-v-b8a4ffdc] .highlight {
|
||||
[data-v-ba2c5897] .highlight {
|
||||
background-color: var(--p-primary-color);
|
||||
color: var(--p-primary-contrast-color);
|
||||
font-weight: bold;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.125rem 0.25rem;
|
||||
padding: 0rem 0.125rem;
|
||||
margin: -0.125rem 0.125rem;
|
||||
}
|
||||
|
||||
@ -3971,46 +3976,57 @@ audio.comfy-audio.empty-audio-widget {
|
||||
z-index: 99999;
|
||||
}
|
||||
|
||||
.result-container[data-v-0fac61d9] {
|
||||
.broken-image[data-v-1a883642] {
|
||||
display: none;
|
||||
}
|
||||
.broken-image-placeholder[data-v-1a883642] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 2rem;
|
||||
}
|
||||
.broken-image-placeholder i[data-v-1a883642] {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.result-container[data-v-7ceacc88] {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
aspect-ratio: 1 / 1;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
[data-v-0fac61d9] img {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
[data-v-7ceacc88] .task-output-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
-o-object-fit: cover;
|
||||
object-fit: cover;
|
||||
-o-object-position: center;
|
||||
object-position: center;
|
||||
}
|
||||
.p-image-preview[data-v-0fac61d9] {
|
||||
position: static;
|
||||
display: contents;
|
||||
}
|
||||
[data-v-0fac61d9] .image-preview-mask {
|
||||
.image-preview-mask[data-v-7ceacc88] {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: auto;
|
||||
height: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: var(--p-image-preview-mask-color);
|
||||
transition:
|
||||
opacity var(--p-image-transition-duration),
|
||||
background var(--p-image-transition-duration);
|
||||
border-radius: 50%;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
.result-container:hover .image-preview-mask[data-v-7ceacc88] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.task-result-preview[data-v-6cf8179c] {
|
||||
.task-result-preview[data-v-7c099cb7] {
|
||||
aspect-ratio: 1 / 1;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
@ -4019,18 +4035,18 @@ audio.comfy-audio.empty-audio-widget {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.task-result-preview i[data-v-6cf8179c],
|
||||
.task-result-preview span[data-v-6cf8179c] {
|
||||
.task-result-preview i[data-v-7c099cb7],
|
||||
.task-result-preview span[data-v-7c099cb7] {
|
||||
font-size: 2rem;
|
||||
}
|
||||
.task-item[data-v-6cf8179c] {
|
||||
.task-item[data-v-7c099cb7] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.task-item-details[data-v-6cf8179c] {
|
||||
.task-item-details[data-v-7c099cb7] {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
padding: 0.6rem;
|
||||
@ -4041,12 +4057,23 @@ audio.comfy-audio.empty-audio-widget {
|
||||
|
||||
/* In dark mode, transparent background color for tags is not ideal for tags that
|
||||
are floating on top of images. */
|
||||
.tag-wrapper[data-v-6cf8179c] {
|
||||
.tag-wrapper[data-v-7c099cb7] {
|
||||
background-color: var(--p-primary-contrast-color);
|
||||
border-radius: 6px;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
/* PrimeVue's galleria teleports the fullscreen gallery out of subtree so we
|
||||
cannot use scoped style here. */
|
||||
img.galleria-image {
|
||||
max-width: 100vw;
|
||||
max-height: 100vh;
|
||||
-o-object-fit: contain;
|
||||
object-fit: contain;
|
||||
/* Set z-index so the close button doesn't get hidden behind the image when image is large */
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.comfy-vue-side-bar-container[data-v-bde767d2] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -4078,14 +4105,33 @@ are floating on top of images. */
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.queue-grid[data-v-7f831ee9] {
|
||||
.scroll-container[data-v-bd027c46] {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.queue-grid[data-v-bd027c46] {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
padding: 0.5rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.spinner[data-v-40c18658] {
|
||||
.node-lib-tree-node-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: var(--p-tree-node-gap);
|
||||
}
|
||||
|
||||
[data-v-11e12183] .node-lib-search-box {
|
||||
margin-left: 1rem;
|
||||
margin-right: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
[data-v-11e12183] .comfy-vue-side-bar-body {
|
||||
background: var(--p-tree-background);
|
||||
}
|
||||
|
||||
.spinner[data-v-afce9bd6] {
|
||||
position: absolute;
|
||||
inset: 0px;
|
||||
display: flex;
|
||||
136
web/assets/index--0nRVkuV.js → web/assets/index-DkvOTKox.js
generated
vendored
136
web/assets/index--0nRVkuV.js → web/assets/index-DkvOTKox.js
generated
vendored
@ -48,7 +48,7 @@ var __async = (__this, __arguments, generator) => {
|
||||
});
|
||||
};
|
||||
var _PrimitiveNode_instances, onFirstConnection_fn, createWidget_fn, mergeWidgetConfig_fn, isValidConnection_fn, removeWidgets_fn, _convertedToProcess;
|
||||
import { C as ComfyDialog, $ as $el, a as ComfyApp, b as app, L as LGraphCanvas, c as LiteGraph, d as applyTextReplacements, e as ComfyWidgets, f as addValueControlWidgets, D as DraggableList, g as api, h as LGraphGroup, i as LGraphNode } from "./index-D8Zp4vRl.js";
|
||||
import { C as ComfyDialog, $ as $el, a as ComfyApp, b as app, L as LGraphCanvas, c as LiteGraph, d as applyTextReplacements, e as ComfyWidgets, f as addValueControlWidgets, D as DraggableList, g as api, u as useToastStore, h as LGraphGroup, i as LGraphNode } from "./index-CaD4RONs.js";
|
||||
const _ClipspaceDialog = class _ClipspaceDialog extends ComfyDialog {
|
||||
static registerButton(name, contextPredicate, callback) {
|
||||
const item = $el("button", {
|
||||
@ -891,6 +891,7 @@ app.registerExtension({
|
||||
});
|
||||
app.ui.settings.addSetting({
|
||||
id: id$4,
|
||||
category: ["Comfy", "ColorPalette"],
|
||||
name: "Color Palette",
|
||||
type: /* @__PURE__ */ __name((name, setter, value) => {
|
||||
const options = [
|
||||
@ -923,12 +924,6 @@ app.registerExtension({
|
||||
options
|
||||
);
|
||||
return $el("tr", [
|
||||
$el("td", [
|
||||
$el("label", {
|
||||
for: id$4.replaceAll(".", "-"),
|
||||
textContent: "Color palette"
|
||||
})
|
||||
]),
|
||||
$el("td", [
|
||||
els.select,
|
||||
$el(
|
||||
@ -1221,7 +1216,6 @@ app.registerExtension({
|
||||
const floatWeight = parseFloat(weight);
|
||||
if (isNaN(floatWeight)) return weight;
|
||||
const newWeight = floatWeight + delta;
|
||||
if (newWeight < 0) return "0";
|
||||
return String(Number(newWeight.toFixed(10)));
|
||||
}
|
||||
__name(incrementWeight, "incrementWeight");
|
||||
@ -1302,7 +1296,7 @@ app.registerExtension({
|
||||
selectedText = addWeightToParentheses(selectedText);
|
||||
const weightDelta = event.key === "ArrowUp" ? delta : -delta;
|
||||
const updatedText = selectedText.replace(
|
||||
/\((.*):(\d+(?:\.\d+)?)\)/,
|
||||
/\((.*):([+-]?\d+(?:\.\d+)?)\)/,
|
||||
(match, text, weight) => {
|
||||
weight = incrementWeight(weight, weightDelta);
|
||||
if (weight == 1) {
|
||||
@ -1620,9 +1614,9 @@ function getWidgetConfig(slot) {
|
||||
}
|
||||
__name(getWidgetConfig, "getWidgetConfig");
|
||||
function getConfig(widgetName) {
|
||||
var _a, _b, _c, _d;
|
||||
var _a, _b, _c, _d, _e;
|
||||
const { nodeData } = this.constructor;
|
||||
return (_d = (_a = nodeData == null ? void 0 : nodeData.input) == null ? void 0 : _a.required[widgetName]) != null ? _d : (_c = (_b = nodeData == null ? void 0 : nodeData.input) == null ? void 0 : _b.optional) == null ? void 0 : _c[widgetName];
|
||||
return (_e = (_b = (_a = nodeData == null ? void 0 : nodeData.input) == null ? void 0 : _a.required) == null ? void 0 : _b[widgetName]) != null ? _e : (_d = (_c = nodeData == null ? void 0 : nodeData.input) == null ? void 0 : _c.optional) == null ? void 0 : _d[widgetName];
|
||||
}
|
||||
__name(getConfig, "getConfig");
|
||||
function isConvertibleWidget(widget, config) {
|
||||
@ -1854,8 +1848,7 @@ app.registerExtension({
|
||||
init() {
|
||||
useConversionSubmenusSetting = app.ui.settings.addSetting({
|
||||
id: "Comfy.NodeInputConversionSubmenus",
|
||||
name: "Node widget/input conversion sub-menus",
|
||||
tooltip: "In the node context menu, place the entries that convert between input/widget in sub-menus.",
|
||||
name: "In the node context menu, place the entries that convert between input/widget in sub-menus.",
|
||||
type: "boolean",
|
||||
defaultValue: true
|
||||
});
|
||||
@ -3957,7 +3950,8 @@ app.registerExtension({
|
||||
}, "replace");
|
||||
app.ui.settings.addSetting({
|
||||
id: id$2,
|
||||
name: "Invert Menu Scrolling",
|
||||
category: ["Comfy", "Graph", "InvertMenuScrolling"],
|
||||
name: "Invert Context Menu Scrolling",
|
||||
type: "boolean",
|
||||
defaultValue: false,
|
||||
onChange(value) {
|
||||
@ -3974,58 +3968,66 @@ app.registerExtension({
|
||||
name: "Comfy.Keybinds",
|
||||
init() {
|
||||
const keybindListener = /* @__PURE__ */ __name(function(event) {
|
||||
const modifierPressed = event.ctrlKey || event.metaKey;
|
||||
if (modifierPressed && event.key === "Enter") {
|
||||
if (event.altKey) {
|
||||
api.interrupt();
|
||||
return __async(this, null, function* () {
|
||||
const modifierPressed = event.ctrlKey || event.metaKey;
|
||||
if (modifierPressed && event.key === "Enter") {
|
||||
if (event.altKey) {
|
||||
yield api.interrupt();
|
||||
useToastStore().add({
|
||||
severity: "info",
|
||||
summary: "Interrupted",
|
||||
detail: "Execution has been interrupted",
|
||||
life: 1e3
|
||||
});
|
||||
return;
|
||||
}
|
||||
app.queuePrompt(event.shiftKey ? -1 : 0).then();
|
||||
return;
|
||||
}
|
||||
app.queuePrompt(event.shiftKey ? -1 : 0).then();
|
||||
return;
|
||||
}
|
||||
const target = event.composedPath()[0];
|
||||
if (["INPUT", "TEXTAREA"].includes(target.tagName)) {
|
||||
return;
|
||||
}
|
||||
const modifierKeyIdMap = {
|
||||
s: "#comfy-save-button",
|
||||
o: "#comfy-file-input",
|
||||
Backspace: "#comfy-clear-button",
|
||||
d: "#comfy-load-default-button"
|
||||
};
|
||||
const modifierKeybindId = modifierKeyIdMap[event.key];
|
||||
if (modifierPressed && modifierKeybindId) {
|
||||
event.preventDefault();
|
||||
const elem = document.querySelector(modifierKeybindId);
|
||||
elem.click();
|
||||
return;
|
||||
}
|
||||
if (event.ctrlKey || event.altKey || event.metaKey) {
|
||||
return;
|
||||
}
|
||||
if (event.key === "Escape") {
|
||||
const modals = document.querySelectorAll(".comfy-modal");
|
||||
const modal = Array.from(modals).find(
|
||||
(modal2) => window.getComputedStyle(modal2).getPropertyValue("display") !== "none"
|
||||
);
|
||||
if (modal) {
|
||||
modal.style.display = "none";
|
||||
const target = event.composedPath()[0];
|
||||
if (["INPUT", "TEXTAREA"].includes(target.tagName)) {
|
||||
return;
|
||||
}
|
||||
;
|
||||
[...document.querySelectorAll("dialog")].forEach((d) => {
|
||||
d.close();
|
||||
});
|
||||
}
|
||||
const keyIdMap = {
|
||||
q: ".queue-tab-button.side-bar-button",
|
||||
h: ".queue-tab-button.side-bar-button",
|
||||
r: "#comfy-refresh-button"
|
||||
};
|
||||
const buttonId = keyIdMap[event.key];
|
||||
if (buttonId) {
|
||||
const button = document.querySelector(buttonId);
|
||||
button.click();
|
||||
}
|
||||
const modifierKeyIdMap = {
|
||||
s: "#comfy-save-button",
|
||||
o: "#comfy-file-input",
|
||||
Backspace: "#comfy-clear-button",
|
||||
d: "#comfy-load-default-button"
|
||||
};
|
||||
const modifierKeybindId = modifierKeyIdMap[event.key];
|
||||
if (modifierPressed && modifierKeybindId) {
|
||||
event.preventDefault();
|
||||
const elem = document.querySelector(modifierKeybindId);
|
||||
elem.click();
|
||||
return;
|
||||
}
|
||||
if (event.ctrlKey || event.altKey || event.metaKey) {
|
||||
return;
|
||||
}
|
||||
if (event.key === "Escape") {
|
||||
const modals = document.querySelectorAll(".comfy-modal");
|
||||
const modal = Array.from(modals).find(
|
||||
(modal2) => window.getComputedStyle(modal2).getPropertyValue("display") !== "none"
|
||||
);
|
||||
if (modal) {
|
||||
modal.style.display = "none";
|
||||
}
|
||||
;
|
||||
[...document.querySelectorAll("dialog")].forEach((d) => {
|
||||
d.close();
|
||||
});
|
||||
}
|
||||
const keyIdMap = {
|
||||
q: ".queue-tab-button.side-bar-button",
|
||||
h: ".queue-tab-button.side-bar-button",
|
||||
r: "#comfy-refresh-button"
|
||||
};
|
||||
const buttonId = keyIdMap[event.key];
|
||||
if (buttonId) {
|
||||
const button = document.querySelector(buttonId);
|
||||
button.click();
|
||||
}
|
||||
});
|
||||
}, "keybindListener");
|
||||
window.addEventListener("keydown", keybindListener, true);
|
||||
}
|
||||
@ -4037,6 +4039,7 @@ const ext = {
|
||||
return __async(this, null, function* () {
|
||||
app2.ui.settings.addSetting({
|
||||
id: id$1,
|
||||
category: ["Comfy", "Graph", "LinkRenderMode"],
|
||||
name: "Link Render Mode",
|
||||
defaultValue: 2,
|
||||
type: "combo",
|
||||
@ -5684,7 +5687,9 @@ app.registerExtension({
|
||||
LiteGraph.middle_click_slot_add_default_node = true;
|
||||
this.suggestionsNumber = app.ui.settings.addSetting({
|
||||
id: "Comfy.NodeSuggestions.number",
|
||||
category: ["Comfy", "Node Search Box", "NodeSuggestions"],
|
||||
name: "Number of nodes suggestions",
|
||||
tooltip: "Only for litegraph searchbox/context menu",
|
||||
type: "slider",
|
||||
attrs: {
|
||||
min: 1,
|
||||
@ -5766,7 +5771,8 @@ app.registerExtension({
|
||||
init() {
|
||||
app.ui.settings.addSetting({
|
||||
id: "Comfy.SnapToGrid.GridSize",
|
||||
name: "Grid Size",
|
||||
category: ["Comfy", "Graph", "GridSize"],
|
||||
name: "Snap to gird size",
|
||||
type: "slider",
|
||||
attrs: {
|
||||
min: 1,
|
||||
@ -6176,4 +6182,4 @@ app.registerExtension({
|
||||
};
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=index--0nRVkuV.js.map
|
||||
//# sourceMappingURL=index-DkvOTKox.js.map
|
||||
1
web/assets/index-DkvOTKox.js.map
generated
vendored
Normal file
1
web/assets/index-DkvOTKox.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
4
web/assets/userSelection-CH4RQEqW.js → web/assets/userSelection-GRU1gtOt.js
generated
vendored
4
web/assets/userSelection-CH4RQEqW.js → web/assets/userSelection-GRU1gtOt.js
generated
vendored
@ -20,7 +20,7 @@ var __async = (__this, __arguments, generator) => {
|
||||
step((generator = generator.apply(__this, __arguments)).next());
|
||||
});
|
||||
};
|
||||
import { j as createSpinner, g as api, $ as $el } from "./index-D8Zp4vRl.js";
|
||||
import { j as createSpinner, g as api, $ as $el } from "./index-CaD4RONs.js";
|
||||
const _UserSelectionScreen = class _UserSelectionScreen {
|
||||
show(users, user) {
|
||||
return __async(this, null, function* () {
|
||||
@ -139,4 +139,4 @@ window.comfyAPI.userSelection.UserSelectionScreen = UserSelectionScreen;
|
||||
export {
|
||||
UserSelectionScreen
|
||||
};
|
||||
//# sourceMappingURL=userSelection-CH4RQEqW.js.map
|
||||
//# sourceMappingURL=userSelection-GRU1gtOt.js.map
|
||||
2
web/assets/userSelection-CH4RQEqW.js.map → web/assets/userSelection-GRU1gtOt.js.map
generated
vendored
2
web/assets/userSelection-CH4RQEqW.js.map → web/assets/userSelection-GRU1gtOt.js.map
generated
vendored
File diff suppressed because one or more lines are too long
4
web/index.html
vendored
4
web/index.html
vendored
@ -14,8 +14,8 @@
|
||||
</style> -->
|
||||
<link rel="stylesheet" type="text/css" href="user.css" />
|
||||
<link rel="stylesheet" type="text/css" href="materialdesignicons.min.css" />
|
||||
<script type="module" crossorigin src="/assets/index-D8Zp4vRl.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BHzRuMlR.css">
|
||||
<script type="module" crossorigin src="./assets/index-CaD4RONs.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-DAK31IJJ.css">
|
||||
</head>
|
||||
<body class="litegraph">
|
||||
<div id="vue-app"></div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user