refactor: rename Model Linker to Model Resolver
Replace all link dialog terminology with resolver terminology throughout the codebase, including module names, CSS classes, IDs, JS variables, SVG gradient IDs, and log module names. Remove the old web/linker/ tree and introduce the new web/resolver/ tree with parallel structure. Also replace core/linker.py with core/resolver.py.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<h1 align="center">ModelLinker – Relink missing models and download from HuggingFace/CivitAI</h1>
|
||||
<h1 align="center">ModelResolver – Relink missing models and download from HuggingFace/CivitAI</h1>
|
||||
|
||||
<p align="center"><i>ModelLinker is a powerful ComfyUI extension for automatically resolving missing models in loaded workflows. It features intelligent local fuzzy matching, direct cloud downloads, background download tracking, and automated in-place workflow updating.</i></p>
|
||||
<p align="center"><i>ModelResolver is a powerful ComfyUI extension for automatically resolving missing models in loaded workflows. It features intelligent local fuzzy matching, direct cloud downloads, background download tracking, and automated in-place workflow updating.</i></p>
|
||||
|
||||
> [!WARNING]
|
||||
> **WIP (Work in Progress) — Download and use at your own risk!**
|
||||
@@ -9,8 +9,8 @@
|
||||
|
||||
<p align="center">
|
||||
<img alt="ComfyUI" src="https://img.shields.io/badge/ComfyUI-1a1a1a?style=for-the-badge&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAMAAABF0y+mAAAASFBMVEVHcEwYLtsYLtkXLtkXLdkYLtkWLdcFIdoAD95uerfI1XLR3mq3xIP8/yj0/zvw/0FSYMP5/zKMmKQtPNOuuozj8FOhrZW7x4FMWFFbAAAABnRSTlMAUrPX87KxijklAAAA00lEQVR4AX3SBw6DMAxA0UzbrIzO+9+02GkEpoWP9hPZZs06Hw75aI3k4W/+wkQtnGZNhF1I34BzalQcxkmasY0b9raklNcvLYU1GNiiOeVWauOa/XS526gRyzpV/7HeUOG9Jp6vcsvUrCPeKg/3KBKBQhoTD1dQggPWzPVfFOIgo85/kR4y6oB/8SlIEh7wvmTuKd3wgLVW1sTfRBoR7oWVqy/U2NcrWDYMINE7NUuJuoV+2fhaWmnbjzcOWnRv7XbiLh/Y9dNUqk2y0QcNwTu7wgf+/BhsPUhf4QAAAABJRU5ErkJggg==" />
|
||||
<a href="https://github.com/Azornes/comfyui-model-linker">
|
||||
<img alt="License" src="https://img.shields.io/github/license/Azornes/comfyui-model-linker?style=for-the-badge&color=2F80ED" />
|
||||
<a href="https://github.com/Azornes/comfyui-model-resolver">
|
||||
<img alt="License" src="https://img.shields.io/github/license/Azornes/comfyui-model-resolver?style=for-the-badge&color=2F80ED" />
|
||||
</a>
|
||||
<img alt="Python 3.10+" src="https://img.shields.io/badge/-Python_3.10+-4B8BBE?logo=python&logoColor=FFFFFF&style=for-the-badge&logoWidth=20">
|
||||
<img alt="JavaScript" src="https://img.shields.io/badge/-JavaScript-000000?logo=javascript&logoColor=F7DF1E&style=for-the-badge&logoWidth=20">
|
||||
@@ -51,7 +51,7 @@
|
||||
|
||||
## 📂 Supported Nodes & Model Types
|
||||
|
||||
Model Linker supports standard ComfyUI mechanisms as well as custom implementations of popular loader nodes:
|
||||
Model Resolver supports standard ComfyUI mechanisms as well as custom implementations of popular loader nodes:
|
||||
* **Standard loaders**: CheckpointLoader, LoraLoader, VAELoader, ControlNetLoader, UpscaleModelLoader, etc.
|
||||
* **Advanced loaders**: Nodes from the `LoraManager` suite (`LoraLoaderV2`, `Lora Loader`, `Lora Stacker`), `rgthree` (`Power Lora Loader`), and LTX-Video nodes.
|
||||
* **Subgraphs**: Full support for scanning and updating nodes inside nested group subgraphs.
|
||||
@@ -73,7 +73,7 @@ In the extension settings panel, you can configure API keys and authorization to
|
||||
## ⚡ Installation
|
||||
|
||||
### Install via ComfyUI-Manager
|
||||
1. Search `ComfyUI Model Linker` in ComfyUI-Manager and click the `Install` button.
|
||||
1. Search `ComfyUI Model Resolver` in ComfyUI-Manager and click the `Install` button.
|
||||
2. Restart ComfyUI.
|
||||
|
||||
### Manual Install
|
||||
@@ -83,7 +83,7 @@ In the extension settings panel, you can configure API keys and authorization to
|
||||
```
|
||||
2. Clone this repository:
|
||||
```bash
|
||||
git clone https://github.com/Azornes/comfyui-model-linker.git
|
||||
git clone https://github.com/Azornes/comfyui-model-resolver.git
|
||||
```
|
||||
3. Start or restart ComfyUI. The extension will automatically install any missing dependencies (`requests`, `aiohttp`) and load the web interface.
|
||||
|
||||
|
||||
+63
-63
@@ -1,9 +1,9 @@
|
||||
"""
|
||||
@author: Model Linker Team
|
||||
@title: ComfyUI Model Linker
|
||||
@nickname: Model Linker
|
||||
"""
|
||||
@author: Model Resolver Team
|
||||
@title: ComfyUI Model Resolver
|
||||
@nickname: Model Resolver
|
||||
@version: 1.1.0
|
||||
@description: Extension for relinking missing models and downloading from HuggingFace/CivitAI
|
||||
@description: Extension for resolving missing models and downloading from HuggingFace/CivitAI
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
@@ -24,8 +24,8 @@ NODE_CLASS_MAPPINGS = {}
|
||||
__all__ = ["WEB_DIRECTORY"]
|
||||
|
||||
|
||||
class ModelLinkerExtension:
|
||||
"""Main extension class for Model Linker."""
|
||||
class ModelResolverExtension:
|
||||
"""Main extension class for Model Resolver."""
|
||||
|
||||
def __init__(self):
|
||||
self.routes_setup = False
|
||||
@@ -36,14 +36,14 @@ class ModelLinkerExtension:
|
||||
"""Initialize the extension and set up API routes."""
|
||||
try:
|
||||
self.setup_routes()
|
||||
self.logger.info("Model Linker: Extension initialized successfully")
|
||||
self.logger.info("Model Resolver: Extension initialized successfully")
|
||||
except Exception as e:
|
||||
self.logger.error(
|
||||
f"Model Linker: Extension initialization failed: {e}", exc_info=True
|
||||
f"Model Resolver: Extension initialization failed: {e}", exc_info=True
|
||||
)
|
||||
|
||||
def setup_routes(self):
|
||||
"""Register API routes for the Model Linker extension."""
|
||||
"""Register API routes for the Model Resolver extension."""
|
||||
if self.routes_setup:
|
||||
return # Already set up
|
||||
|
||||
@@ -58,24 +58,24 @@ class ModelLinkerExtension:
|
||||
not hasattr(PromptServer, "instance")
|
||||
or PromptServer.instance is None
|
||||
):
|
||||
self.logger.debug("Model Linker: PromptServer not available yet")
|
||||
self.logger.debug("Model Resolver: PromptServer not available yet")
|
||||
return False
|
||||
|
||||
routes = PromptServer.instance.routes
|
||||
except (ImportError, AttributeError) as e:
|
||||
self.logger.debug(f"Model Linker: Could not access PromptServer: {e}")
|
||||
self.logger.debug(f"Model Resolver: Could not access PromptServer: {e}")
|
||||
return False
|
||||
|
||||
# Import linker modules
|
||||
# Import resolver modules
|
||||
try:
|
||||
from .core.linker import (
|
||||
from .core.resolver import (
|
||||
analyze_and_find_matches,
|
||||
apply_resolution,
|
||||
search_local_matches,
|
||||
)
|
||||
from .core.scanner import get_model_files
|
||||
except ImportError as e:
|
||||
self.logger.error(f"Model Linker: Could not import core modules: {e}")
|
||||
self.logger.error(f"Model Resolver: Could not import core modules: {e}")
|
||||
return False
|
||||
|
||||
# Import download modules
|
||||
@@ -129,13 +129,13 @@ class ModelLinkerExtension:
|
||||
download_available = True
|
||||
except ImportError as e:
|
||||
self.logger.warning(
|
||||
f"Model Linker: Download features not available: {e}"
|
||||
f"Model Resolver: Download features not available: {e}"
|
||||
)
|
||||
download_available = False
|
||||
|
||||
# ==================== ANALYZE ROUTES ====================
|
||||
|
||||
@routes.post("/model_linker/analyze")
|
||||
@routes.post("/model_resolver/analyze")
|
||||
async def analyze_workflow(request):
|
||||
"""Analyze workflow and return missing models with matches."""
|
||||
try:
|
||||
@@ -310,7 +310,7 @@ class ModelLinkerExtension:
|
||||
continue
|
||||
|
||||
# NOTE: Search for online sources (HuggingFace, CivitAI) is
|
||||
# now done on-demand via /model_linker/search endpoint
|
||||
# now done on-demand via /model_resolver/search endpoint
|
||||
# when user clicks "Search Online" button, not automatically
|
||||
|
||||
if analysis_id:
|
||||
@@ -333,10 +333,10 @@ class ModelLinkerExtension:
|
||||
"current": 0,
|
||||
"total": 0,
|
||||
}
|
||||
self.logger.error(f"Model Linker analyze error: {e}", exc_info=True)
|
||||
self.logger.error(f"Model Resolver analyze error: {e}", exc_info=True)
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
@routes.get("/model_linker/analyze-progress/{analysis_id}")
|
||||
@routes.get("/model_resolver/analyze-progress/{analysis_id}")
|
||||
async def get_analyze_progress(request):
|
||||
"""Get workflow analysis progress."""
|
||||
analysis_id = request.match_info.get("analysis_id", "").strip()
|
||||
@@ -359,7 +359,7 @@ class ModelLinkerExtension:
|
||||
|
||||
return web.json_response(progress)
|
||||
|
||||
@routes.post("/model_linker/resolve")
|
||||
@routes.post("/model_resolver/resolve")
|
||||
async def resolve_models(request):
|
||||
"""Apply model resolution and return updated workflow."""
|
||||
try:
|
||||
@@ -384,12 +384,12 @@ class ModelLinkerExtension:
|
||||
{"workflow": updated_workflow, "success": True}
|
||||
)
|
||||
except Exception as e:
|
||||
self.logger.error(f"Model Linker resolve error: {e}", exc_info=True)
|
||||
self.logger.error(f"Model Resolver resolve error: {e}", exc_info=True)
|
||||
return web.json_response(
|
||||
{"error": str(e), "success": False}, status=500
|
||||
)
|
||||
|
||||
@routes.post("/model_linker/local-matches")
|
||||
@routes.post("/model_resolver/local-matches")
|
||||
async def local_matches(request):
|
||||
"""Search local model files by filename/path."""
|
||||
try:
|
||||
@@ -411,11 +411,11 @@ class ModelLinkerExtension:
|
||||
return web.json_response({"matches": matches})
|
||||
except Exception as e:
|
||||
self.logger.error(
|
||||
f"Model Linker local-matches error: {e}", exc_info=True
|
||||
f"Model Resolver local-matches error: {e}", exc_info=True
|
||||
)
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
@routes.post("/model_linker/open-containing-folder")
|
||||
@routes.post("/model_resolver/open-containing-folder")
|
||||
async def open_containing_folder(request):
|
||||
"""Open Explorer at the folder containing the selected model."""
|
||||
try:
|
||||
@@ -448,12 +448,12 @@ class ModelLinkerExtension:
|
||||
return web.json_response({"success": True})
|
||||
except Exception as e:
|
||||
self.logger.error(
|
||||
f"Model Linker open-containing-folder error: {e}",
|
||||
f"Model Resolver open-containing-folder error: {e}",
|
||||
exc_info=True,
|
||||
)
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
@routes.get("/model_linker/models")
|
||||
@routes.get("/model_resolver/models")
|
||||
async def get_models(request):
|
||||
"""Get list of all available models."""
|
||||
try:
|
||||
@@ -461,11 +461,11 @@ class ModelLinkerExtension:
|
||||
return web.json_response(models)
|
||||
except Exception as e:
|
||||
self.logger.error(
|
||||
f"Model Linker get_models error: {e}", exc_info=True
|
||||
f"Model Resolver get_models error: {e}", exc_info=True
|
||||
)
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
@routes.post("/model_linker/loaded")
|
||||
@routes.post("/model_resolver/loaded")
|
||||
async def get_loaded_models(request):
|
||||
"""Get all currently loaded models in the workflow."""
|
||||
try:
|
||||
@@ -656,13 +656,13 @@ class ModelLinkerExtension:
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(
|
||||
f"Model Linker get_loaded_models error: {e}", exc_info=True
|
||||
f"Model Resolver get_loaded_models error: {e}", exc_info=True
|
||||
)
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
# ==================== CIVITAI SEARCH ROUTE ====================
|
||||
|
||||
@routes.post("/model_linker/civitai-search")
|
||||
@routes.post("/model_resolver/civitai-search")
|
||||
async def civitai_search(request):
|
||||
"""Search CivitAI for a model using file hash."""
|
||||
try:
|
||||
@@ -786,7 +786,7 @@ class ModelLinkerExtension:
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(
|
||||
f"Model Linker civitai-search error: {e}", exc_info=True
|
||||
f"Model Resolver civitai-search error: {e}", exc_info=True
|
||||
)
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
@@ -794,7 +794,7 @@ class ModelLinkerExtension:
|
||||
|
||||
if download_available:
|
||||
|
||||
@routes.post("/model_linker/search")
|
||||
@routes.post("/model_resolver/search")
|
||||
async def search_sources(request):
|
||||
"""Search for model download sources."""
|
||||
try:
|
||||
@@ -1257,10 +1257,10 @@ class ModelLinkerExtension:
|
||||
return web.json_response(results)
|
||||
|
||||
except Exception as e:
|
||||
log_exception(f"Model Linker search error: {e}")
|
||||
log_exception(f"Model Resolver search error: {e}")
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
@routes.post("/model_linker/clear-search-cache")
|
||||
@routes.post("/model_resolver/clear-search-cache")
|
||||
async def clear_search_cache_route(request):
|
||||
"""Clear backend search caches after token/settings changes."""
|
||||
try:
|
||||
@@ -1274,7 +1274,7 @@ class ModelLinkerExtension:
|
||||
log_exception(f"Clear search cache error: {e}")
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
@routes.post("/model_linker/civitai/session-token/check")
|
||||
@routes.post("/model_resolver/civitai/session-token/check")
|
||||
async def civitai_session_token_check_route(request):
|
||||
"""Check whether a CivitAI browser session token is valid."""
|
||||
try:
|
||||
@@ -1288,7 +1288,7 @@ class ModelLinkerExtension:
|
||||
log_exception(f"CivitAI session token check error: {e}")
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
@routes.post("/model_linker/civitai/api-key/check")
|
||||
@routes.post("/model_resolver/civitai/api-key/check")
|
||||
async def civitai_api_key_check_route(request):
|
||||
"""Check whether a CivitAI API key is valid."""
|
||||
try:
|
||||
@@ -1302,7 +1302,7 @@ class ModelLinkerExtension:
|
||||
log_exception(f"CivitAI API key check error: {e}")
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
@routes.post("/model_linker/huggingface/token/check")
|
||||
@routes.post("/model_resolver/huggingface/token/check")
|
||||
async def huggingface_token_check_route(request):
|
||||
"""Check whether a HuggingFace token is valid."""
|
||||
try:
|
||||
@@ -1314,7 +1314,7 @@ class ModelLinkerExtension:
|
||||
log_exception(f"HuggingFace token check error: {e}")
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
@routes.post("/model_linker/brave/api-key/check")
|
||||
@routes.post("/model_resolver/brave/api-key/check")
|
||||
async def brave_api_key_check_route(request):
|
||||
"""Check whether a Brave Search API key is valid."""
|
||||
try:
|
||||
@@ -1328,7 +1328,7 @@ class ModelLinkerExtension:
|
||||
log_exception(f"Brave Search API key check error: {e}")
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
@routes.get("/model_linker/huggingface/author-index/status")
|
||||
@routes.get("/model_resolver/huggingface/author-index/status")
|
||||
async def huggingface_author_index_status_route(request):
|
||||
"""Return local HuggingFace author fallback index status."""
|
||||
try:
|
||||
@@ -1337,7 +1337,7 @@ class ModelLinkerExtension:
|
||||
log_exception(f"HuggingFace author index status error: {e}")
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
@routes.post("/model_linker/huggingface/author-index/refresh")
|
||||
@routes.post("/model_resolver/huggingface/author-index/refresh")
|
||||
async def huggingface_author_index_refresh_route(request):
|
||||
"""Refresh HuggingFace author fallback index."""
|
||||
try:
|
||||
@@ -1352,7 +1352,7 @@ class ModelLinkerExtension:
|
||||
log_exception(f"HuggingFace author index refresh error: {e}")
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
@routes.get("/model_linker/model-list/status")
|
||||
@routes.get("/model_resolver/model-list/status")
|
||||
async def model_list_status_route(request):
|
||||
"""Return local model-list status and optionally compare with GitHub."""
|
||||
try:
|
||||
@@ -1367,7 +1367,7 @@ class ModelLinkerExtension:
|
||||
log_exception(f"Model list status error: {e}")
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
@routes.post("/model_linker/model-list/update")
|
||||
@routes.post("/model_resolver/model-list/update")
|
||||
async def model_list_update_route(request):
|
||||
"""Download latest ComfyUI-Manager model-list.json."""
|
||||
try:
|
||||
@@ -1381,7 +1381,7 @@ class ModelLinkerExtension:
|
||||
log_exception(f"Model list update error: {e}")
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
@routes.post("/model_linker/download")
|
||||
@routes.post("/model_resolver/download")
|
||||
async def download_model(request):
|
||||
"""Start downloading a model."""
|
||||
try:
|
||||
@@ -1441,13 +1441,13 @@ class ModelLinkerExtension:
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(
|
||||
f"Model Linker download error: {e}", exc_info=True
|
||||
f"Model Resolver download error: {e}", exc_info=True
|
||||
)
|
||||
return web.json_response(
|
||||
{"error": str(e), "success": False}, status=500
|
||||
)
|
||||
|
||||
@routes.get("/model_linker/progress/{download_id}")
|
||||
@routes.get("/model_resolver/progress/{download_id}")
|
||||
async def get_download_progress(request):
|
||||
"""Get progress for a specific download."""
|
||||
try:
|
||||
@@ -1462,11 +1462,11 @@ class ModelLinkerExtension:
|
||||
)
|
||||
except Exception as e:
|
||||
self.logger.error(
|
||||
f"Model Linker progress error: {e}", exc_info=True
|
||||
f"Model Resolver progress error: {e}", exc_info=True
|
||||
)
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
@routes.get("/model_linker/progress")
|
||||
@routes.get("/model_resolver/progress")
|
||||
async def get_all_downloads_progress(request):
|
||||
"""Get progress for all downloads."""
|
||||
try:
|
||||
@@ -1474,11 +1474,11 @@ class ModelLinkerExtension:
|
||||
return web.json_response(progress)
|
||||
except Exception as e:
|
||||
self.logger.error(
|
||||
f"Model Linker progress error: {e}", exc_info=True
|
||||
f"Model Resolver progress error: {e}", exc_info=True
|
||||
)
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
@routes.post("/model_linker/cancel/{download_id}")
|
||||
@routes.post("/model_resolver/cancel/{download_id}")
|
||||
async def cancel_download_route(request):
|
||||
"""Cancel a download in progress."""
|
||||
try:
|
||||
@@ -1487,13 +1487,13 @@ class ModelLinkerExtension:
|
||||
return web.json_response({"success": True})
|
||||
except Exception as e:
|
||||
self.logger.error(
|
||||
f"Model Linker cancel error: {e}", exc_info=True
|
||||
f"Model Resolver cancel error: {e}", exc_info=True
|
||||
)
|
||||
return web.json_response(
|
||||
{"error": str(e), "success": False}, status=500
|
||||
)
|
||||
|
||||
@routes.get("/model_linker/directories")
|
||||
@routes.get("/model_resolver/directories")
|
||||
async def get_directories(request):
|
||||
"""Get available model directories."""
|
||||
try:
|
||||
@@ -1521,11 +1521,11 @@ class ModelLinkerExtension:
|
||||
return web.json_response(directories)
|
||||
except Exception as e:
|
||||
self.logger.error(
|
||||
f"Model Linker directories error: {e}", exc_info=True
|
||||
f"Model Resolver directories error: {e}", exc_info=True
|
||||
)
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
@routes.get("/model_linker/capabilities")
|
||||
@routes.get("/model_resolver/capabilities")
|
||||
async def get_capabilities(request):
|
||||
"""Get optional source capabilities available in this install."""
|
||||
try:
|
||||
@@ -1539,11 +1539,11 @@ class ModelLinkerExtension:
|
||||
)
|
||||
except Exception as e:
|
||||
self.logger.error(
|
||||
f"Model Linker capabilities error: {e}", exc_info=True
|
||||
f"Model Resolver capabilities error: {e}", exc_info=True
|
||||
)
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
@routes.get("/model_linker/subfolders/{category}")
|
||||
@routes.get("/model_resolver/subfolders/{category}")
|
||||
async def get_subfolders(request):
|
||||
"""Get known subfolders for a category using ComfyUI folder_paths."""
|
||||
try:
|
||||
@@ -1579,7 +1579,7 @@ class ModelLinkerExtension:
|
||||
known_categories = set(folder_paths.folder_names_and_paths.keys())
|
||||
if category not in known_categories:
|
||||
self.logger.debug(
|
||||
f"Model Linker: skipping subfolder lookup for unknown category '{raw_category}' -> '{category}'"
|
||||
f"Model Resolver: skipping subfolder lookup for unknown category '{raw_category}' -> '{category}'"
|
||||
)
|
||||
return web.json_response([])
|
||||
|
||||
@@ -1599,31 +1599,31 @@ class ModelLinkerExtension:
|
||||
return web.json_response(sorted(subfolders))
|
||||
except Exception as e:
|
||||
self.logger.error(
|
||||
f"Model Linker subfolders error: {e}", exc_info=True
|
||||
f"Model Resolver subfolders error: {e}", exc_info=True
|
||||
)
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
self.routes_setup = True
|
||||
self.logger.info("Model Linker: API routes registered successfully")
|
||||
self.logger.info("Model Resolver: API routes registered successfully")
|
||||
return True
|
||||
|
||||
except ImportError as e:
|
||||
self.logger.warning(
|
||||
f"Model Linker: Could not register routes (missing dependency): {e}"
|
||||
f"Model Resolver: Could not register routes (missing dependency): {e}"
|
||||
)
|
||||
return False
|
||||
except Exception as e:
|
||||
self.logger.error(
|
||||
f"Model Linker: Error setting up routes: {e}", exc_info=True
|
||||
f"Model Resolver: Error setting up routes: {e}", exc_info=True
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
# Initialize the extension
|
||||
try:
|
||||
extension = ModelLinkerExtension()
|
||||
extension = ModelResolverExtension()
|
||||
extension.initialize()
|
||||
except Exception as e:
|
||||
log_error(
|
||||
f"ComfyUI Model Linker extension initialization failed: {e}", exc_info=True
|
||||
f"ComfyUI Model Resolver extension initialization failed: {e}", exc_info=True
|
||||
)
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Core modules for Model Linker extension.
|
||||
Core modules for Model Resolver extension.
|
||||
|
||||
Modules:
|
||||
- linker: Main API for analyzing and resolving missing models
|
||||
@@ -13,7 +13,7 @@ Modules:
|
||||
|
||||
from .log_system.log_funcs import log_debug, log_info, log_warn, log_error, log_exception
|
||||
from .log_system.logger import logger
|
||||
from .linker import analyze_and_find_matches, apply_resolution
|
||||
from .resolver import analyze_and_find_matches, apply_resolution
|
||||
from .scanner import get_model_files
|
||||
from .matcher import find_matches
|
||||
|
||||
|
||||
+16
-16
@@ -1,4 +1,4 @@
|
||||
"""
|
||||
"""
|
||||
Model Downloader Module
|
||||
|
||||
Handles downloading models from various sources with progress tracking.
|
||||
@@ -213,9 +213,9 @@ def download_file(
|
||||
if "civitai.com" in url
|
||||
else "URL"
|
||||
)
|
||||
print(f"\n[Model Linker] Starting download: {filename}")
|
||||
print(f"[Model Linker] Source: {source}")
|
||||
print(f"[Model Linker] URL: {url}")
|
||||
print(f"\n[Model Resolver] Starting download: {filename}")
|
||||
print(f"[Model Resolver] Source: {source}")
|
||||
print(f"[Model Resolver] URL: {url}")
|
||||
|
||||
# Start download
|
||||
response = requests.get(url, headers=headers, stream=True, timeout=30)
|
||||
@@ -224,7 +224,7 @@ def download_file(
|
||||
# Get total size
|
||||
total_size = int(response.headers.get("content-length", 0))
|
||||
total_size_str = format_bytes(total_size) if total_size > 0 else "unknown"
|
||||
print(f"[Model Linker] Size: {total_size_str}")
|
||||
print(f"[Model Resolver] Size: {total_size_str}")
|
||||
|
||||
with download_lock:
|
||||
download_progress[download_id]["total_size"] = total_size
|
||||
@@ -292,7 +292,7 @@ def download_file(
|
||||
)
|
||||
speed_str = format_bytes(int(smoothed_speed)) + "/s"
|
||||
print(
|
||||
f"[Model Linker] Progress: {downloaded_str} / {total_str} ({progress_pct}%) - {speed_str}"
|
||||
f"[Model Resolver] Progress: {downloaded_str} / {total_str} ({progress_pct}%) - {speed_str}"
|
||||
)
|
||||
else:
|
||||
# Just update downloaded bytes without recalculating speed
|
||||
@@ -316,13 +316,13 @@ def download_file(
|
||||
if os.path.exists(dest_path):
|
||||
os.remove(dest_path)
|
||||
print(
|
||||
f"[Model Linker] Cancelled: {filename} - incomplete file deleted"
|
||||
f"[Model Resolver] Cancelled: {filename} - incomplete file deleted"
|
||||
)
|
||||
else:
|
||||
print(f"[Model Linker] Cancelled: {filename} - no file to delete")
|
||||
print(f"[Model Resolver] Cancelled: {filename} - no file to delete")
|
||||
except Exception as e:
|
||||
print(
|
||||
f"[Model Linker] Warning: Could not delete incomplete file {dest_path}: {e}"
|
||||
f"[Model Resolver] Warning: Could not delete incomplete file {dest_path}: {e}"
|
||||
)
|
||||
# Try harder on Windows - sometimes the file handle takes a moment to release
|
||||
try:
|
||||
@@ -330,7 +330,7 @@ def download_file(
|
||||
if os.path.exists(dest_path):
|
||||
os.remove(dest_path)
|
||||
print(
|
||||
f"[Model Linker] Cancelled: {filename} - incomplete file deleted (delayed)"
|
||||
f"[Model Resolver] Cancelled: {filename} - incomplete file deleted (delayed)"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
@@ -350,9 +350,9 @@ def download_file(
|
||||
# CLI completion log
|
||||
elapsed = time.time() - start_time
|
||||
avg_speed = downloaded / elapsed if elapsed > 0 else 0
|
||||
print(f"[Model Linker] ✓ Download complete: {filename}")
|
||||
print(f"[Model Resolver] ✓ Download complete: {filename}")
|
||||
print(
|
||||
f"[Model Linker] Size: {format_bytes(downloaded)}, Time: {elapsed:.1f}s, Avg speed: {format_bytes(int(avg_speed))}/s"
|
||||
f"[Model Resolver] Size: {format_bytes(downloaded)}, Time: {elapsed:.1f}s, Avg speed: {format_bytes(int(avg_speed))}/s"
|
||||
)
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
@@ -378,8 +378,8 @@ def download_file(
|
||||
result["error"] = error_msg
|
||||
|
||||
# CLI error log
|
||||
print(f"[Model Linker] ✗ Download failed: {os.path.basename(dest_path)}")
|
||||
print(f"[Model Linker] Error: {error_msg}")
|
||||
print(f"[Model Resolver] ✗ Download failed: {os.path.basename(dest_path)}")
|
||||
print(f"[Model Resolver] Error: {error_msg}")
|
||||
|
||||
# Clean up partial file
|
||||
try:
|
||||
@@ -396,8 +396,8 @@ def download_file(
|
||||
result["error"] = error_msg
|
||||
|
||||
# CLI error log
|
||||
print(f"[Model Linker] ✗ Download failed: {os.path.basename(dest_path)}")
|
||||
print(f"[Model Linker] Error: {error_msg}")
|
||||
print(f"[Model Resolver] ✗ Download failed: {os.path.basename(dest_path)}")
|
||||
print(f"[Model Resolver] Error: {error_msg}")
|
||||
log_error(f"Download error: {e}", exc_info=True)
|
||||
|
||||
return result
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""
|
||||
"""
|
||||
@author: Azornes
|
||||
@title: AzLogs
|
||||
@version: 1.5.2
|
||||
@@ -9,7 +9,7 @@
|
||||
LOG_LEVEL = "DEBUG"
|
||||
|
||||
# Logger module name. Set to None to auto-detect from folder name.
|
||||
LOG_MODULE_NAME = "MLinker"
|
||||
LOG_MODULE_NAME = "MResolver"
|
||||
|
||||
# Enable ANSI colors in console logs.
|
||||
USE_COLORS = True
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Core Linker Module
|
||||
"""
|
||||
Core Resolver Module
|
||||
|
||||
Integrates all components to provide high-level API for model linking.
|
||||
"""
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
"""
|
||||
"""
|
||||
Directory Scanner Module
|
||||
|
||||
Scans configured model directories and finds available model files.
|
||||
@@ -21,7 +21,7 @@ try:
|
||||
import folder_paths
|
||||
except ImportError:
|
||||
folder_paths = None
|
||||
log_warn("Model Linker: folder_paths not available yet - will retry later")
|
||||
log_warn("Model Resolver: folder_paths not available yet - will retry later")
|
||||
|
||||
# Model file extensions to look for
|
||||
# This matches folder_paths.supported_pt_extensions
|
||||
@@ -61,7 +61,7 @@ def get_model_directories() -> Dict[str, Tuple[List[str], set]]:
|
||||
|
||||
folder_paths = fp
|
||||
except ImportError:
|
||||
log_error("Model Linker: folder_paths still not available")
|
||||
log_error("Model Resolver: folder_paths still not available")
|
||||
return {}
|
||||
|
||||
return folder_paths.folder_names_and_paths.copy()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""
|
||||
"""
|
||||
LoRA Manager Archive Source Module
|
||||
|
||||
Search archived CivitAI metadata stored by comfyui-lora-manager.
|
||||
@@ -56,7 +56,7 @@ def get_lora_manager_archive_db_path() -> Optional[str]:
|
||||
if _db_path_cache is not None:
|
||||
return _db_path_cache if os.path.exists(_db_path_cache) else None
|
||||
|
||||
env_path = os.environ.get("MODEL_LINKER_LORA_MANAGER_ARCHIVE_DB", "").strip()
|
||||
env_path = os.environ.get("model_resolver_LORA_MANAGER_ARCHIVE_DB", "").strip()
|
||||
candidates: List[str] = []
|
||||
if env_path:
|
||||
candidates.append(env_path)
|
||||
|
||||
@@ -49,7 +49,7 @@ def _fetch_json_url(url: str) -> Dict[str, Any]:
|
||||
url,
|
||||
headers={
|
||||
"Accept": "application/json",
|
||||
"User-Agent": "comfyui-model-linker",
|
||||
"User-Agent": "comfyui-model-resolver",
|
||||
},
|
||||
)
|
||||
with urlopen(request, timeout=HTTP_TIMEOUT) as response:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""
|
||||
"""
|
||||
Workflow Analyzer Module
|
||||
|
||||
Extracts model references from workflow JSON and identifies missing models.
|
||||
@@ -21,7 +21,7 @@ try:
|
||||
import folder_paths
|
||||
except ImportError:
|
||||
folder_paths = None
|
||||
log_warn("Model Linker: folder_paths not available yet - will retry later")
|
||||
log_warn("Model Resolver: folder_paths not available yet - will retry later")
|
||||
|
||||
|
||||
# Common model file extensions
|
||||
@@ -196,7 +196,7 @@ def try_resolve_model_path(
|
||||
|
||||
folder_paths = fp
|
||||
except ImportError:
|
||||
log_error("Model Linker: folder_paths not available")
|
||||
log_error("Model Resolver: folder_paths not available")
|
||||
return None
|
||||
|
||||
# If categories not provided, try all categories
|
||||
|
||||
+197
-197
@@ -1,199 +1,199 @@
|
||||
:root {
|
||||
--ml-color-primary: #5af;
|
||||
--ml-color-primary-light: #7cf;
|
||||
--ml-color-primary-lighter: #7bf;
|
||||
--ml-color-success: #5f5;
|
||||
--ml-color-error: #f00;
|
||||
--ml-color-error-light: #f55;
|
||||
--ml-color-error-lighter: #f77;
|
||||
--ml-color-warning: #fa0;
|
||||
--ml-color-white: #fff;
|
||||
--ml-color-black: #000;
|
||||
--ml-color-gray-100: #ddd;
|
||||
--ml-color-gray-200: #ccc;
|
||||
--ml-color-gray-300: #aaa;
|
||||
--ml-color-gray-400: #999;
|
||||
--ml-color-gray-500: #888;
|
||||
--ml-color-gray-600: #666;
|
||||
--ml-color-gray-700: #555;
|
||||
--ml-color-gray-800: #444;
|
||||
--ml-color-gray-900: #333;
|
||||
--ml-color-bg-dark-1: #2a2a2a;
|
||||
--ml-color-bg-dark-2: #1e1e1e;
|
||||
--ml-color-bg-dark-3: #1a1a1a;
|
||||
--ml-color-bg-dark-4: #0d0d0d;
|
||||
--ml-color-bg-hover: #3a3a3a;
|
||||
--ml-overlay-dark-20: rgba(0, 0, 0, 0.2);
|
||||
--ml-overlay-dark-50: rgba(0, 0, 0, 0.5);
|
||||
--ml-overlay-dark-70: rgba(0, 0, 0, 0.7);
|
||||
--ml-overlay-dark-80: rgba(0, 0, 0, 0.8);
|
||||
--ml-overlay-light-05: rgba(255, 255, 255, 0.05);
|
||||
--ml-overlay-light-08: rgba(255, 255, 255, 0.08);
|
||||
--ml-overlay-light-10: rgba(255, 255, 255, 0.1);
|
||||
--ml-overlay-light-15: rgba(255, 255, 255, 0.15);
|
||||
--ml-overlay-light-20: rgba(255, 255, 255, 0.2);
|
||||
--ml-bg-primary-10: rgba(90, 170, 255, 0.1);
|
||||
--ml-bg-primary-20: rgba(90, 170, 255, 0.2);
|
||||
--ml-bg-primary-30: rgba(90, 170, 255, 0.3);
|
||||
--ml-bg-primary-40: rgba(90, 170, 255, 0.4);
|
||||
--ml-bg-primary-50: rgba(90, 170, 255, 0.5);
|
||||
--ml-bg-success-10: rgba(80, 255, 80, 0.1);
|
||||
--ml-bg-success-50: rgba(80, 255, 80, 0.5);
|
||||
--ml-bg-error-10: rgba(255, 0, 0, 0.1);
|
||||
--ml-bg-error-20: rgba(255, 0, 0, 0.2);
|
||||
--ml-bg-gray-30: rgba(100, 100, 100, 0.3);
|
||||
--ml-bg-gray-40: rgba(120, 120, 120, 0.4);
|
||||
--ml-line-height-tight: 1.3;
|
||||
--ml-line-height-normal: 1.5;
|
||||
--ml-line-height-relaxed: 1.6;
|
||||
--ml-line-height-editor: 19.5px;
|
||||
--ml-width-dropdown-min: 250px;
|
||||
--ml-width-dropdown-max: 400px;
|
||||
--ml-width-input-inline-sm: 120px;
|
||||
--ml-width-input-inline-md: 200px;
|
||||
--ml-width-dialog-sm: 320px;
|
||||
--ml-width-dialog-md: 800px;
|
||||
--ml-z-dropdown: 9998;
|
||||
--ml-z-dialog: 9999;
|
||||
--ml-z-rename-dialog: 10000;
|
||||
--ml-z-json-editor: 10001;
|
||||
--ml-z-floating-button: 10000;
|
||||
--ml-z-notification: 100000;
|
||||
--ml-z-context-menu: 100001;
|
||||
--ml-z-tooltip: 100002;
|
||||
--ml-spacing-1: 1px;
|
||||
--ml-spacing-2: 2px;
|
||||
--ml-spacing-3: 3px;
|
||||
--ml-spacing-4: 4px;
|
||||
--ml-spacing-5: 5px;
|
||||
--ml-spacing-6: 6px;
|
||||
--ml-spacing-7: 7px;
|
||||
--ml-spacing-8: 8px;
|
||||
--ml-spacing-10: 10px;
|
||||
--ml-spacing-12: 12px;
|
||||
--ml-spacing-15: 15px;
|
||||
--ml-spacing-16: 16px;
|
||||
--ml-spacing-20: 20px;
|
||||
--ml-spacing-24: 24px;
|
||||
--ml-spacing-32: 32px;
|
||||
--ml-spacing-40: 40px;
|
||||
--ml-spacing-48: 48px;
|
||||
--ml-radius-sm: 2px;
|
||||
--ml-radius-md: 4px;
|
||||
--ml-radius-lg: 6px;
|
||||
--ml-radius-xl: 8px;
|
||||
--ml-radius-2xl: 12px;
|
||||
--ml-radius-3xl: 16px;
|
||||
--ml-font-xs: 10px;
|
||||
--ml-font-sm: 11px;
|
||||
--ml-font-md: 12px;
|
||||
--ml-font-base: 13px;
|
||||
--ml-font-lg: 14px;
|
||||
--ml-font-xl: 16px;
|
||||
--ml-font-2xl: 18px;
|
||||
--ml-font-3xl: 20px;
|
||||
--ml-font-4xl: 24px;
|
||||
--ml-font-5xl: 48px;
|
||||
--ml-transition-fast: 0.1s;
|
||||
--ml-transition-normal: 0.2s;
|
||||
--ml-transition-all-fast: all 0.1s;
|
||||
--ml-transition-all: all 0.2s;
|
||||
--ml-transition-bg-fast: background 0.1s;
|
||||
--ml-transition-bg: background 0.2s;
|
||||
--ml-transition-color: color 0.2s;
|
||||
--ml-transition-border: border-color 0.2s;
|
||||
--ml-transition-opacity: opacity 0.2s;
|
||||
--ml-shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.8);
|
||||
--ml-gradient-dark: linear-gradient(135deg, var(--ml-color-bg-dark-1) 0%, var(--ml-color-bg-dark-2) 100%);
|
||||
--ml-drag-color-reorder: var(--ml-color-primary);
|
||||
--ml-drag-color-move: var(--ml-color-warning);
|
||||
--ml-drag-color-error: var(--ml-color-error);
|
||||
--ml-drag-color-clone: var(--ml-color-success);
|
||||
:root {
|
||||
--mr-color-primary: #5af;
|
||||
--mr-color-primary-light: #7cf;
|
||||
--mr-color-primary-lighter: #7bf;
|
||||
--mr-color-success: #5f5;
|
||||
--mr-color-error: #f00;
|
||||
--mr-color-error-light: #f55;
|
||||
--mr-color-error-lighter: #f77;
|
||||
--mr-color-warning: #fa0;
|
||||
--mr-color-white: #fff;
|
||||
--mr-color-black: #000;
|
||||
--mr-color-gray-100: #ddd;
|
||||
--mr-color-gray-200: #ccc;
|
||||
--mr-color-gray-300: #aaa;
|
||||
--mr-color-gray-400: #999;
|
||||
--mr-color-gray-500: #888;
|
||||
--mr-color-gray-600: #666;
|
||||
--mr-color-gray-700: #555;
|
||||
--mr-color-gray-800: #444;
|
||||
--mr-color-gray-900: #333;
|
||||
--mr-color-bg-dark-1: #2a2a2a;
|
||||
--mr-color-bg-dark-2: #1e1e1e;
|
||||
--mr-color-bg-dark-3: #1a1a1a;
|
||||
--mr-color-bg-dark-4: #0d0d0d;
|
||||
--mr-color-bg-hover: #3a3a3a;
|
||||
--mr-overlay-dark-20: rgba(0, 0, 0, 0.2);
|
||||
--mr-overlay-dark-50: rgba(0, 0, 0, 0.5);
|
||||
--mr-overlay-dark-70: rgba(0, 0, 0, 0.7);
|
||||
--mr-overlay-dark-80: rgba(0, 0, 0, 0.8);
|
||||
--mr-overlay-light-05: rgba(255, 255, 255, 0.05);
|
||||
--mr-overlay-light-08: rgba(255, 255, 255, 0.08);
|
||||
--mr-overlay-light-10: rgba(255, 255, 255, 0.1);
|
||||
--mr-overlay-light-15: rgba(255, 255, 255, 0.15);
|
||||
--mr-overlay-light-20: rgba(255, 255, 255, 0.2);
|
||||
--mr-bg-primary-10: rgba(90, 170, 255, 0.1);
|
||||
--mr-bg-primary-20: rgba(90, 170, 255, 0.2);
|
||||
--mr-bg-primary-30: rgba(90, 170, 255, 0.3);
|
||||
--mr-bg-primary-40: rgba(90, 170, 255, 0.4);
|
||||
--mr-bg-primary-50: rgba(90, 170, 255, 0.5);
|
||||
--mr-bg-success-10: rgba(80, 255, 80, 0.1);
|
||||
--mr-bg-success-50: rgba(80, 255, 80, 0.5);
|
||||
--mr-bg-error-10: rgba(255, 0, 0, 0.1);
|
||||
--mr-bg-error-20: rgba(255, 0, 0, 0.2);
|
||||
--mr-bg-gray-30: rgba(100, 100, 100, 0.3);
|
||||
--mr-bg-gray-40: rgba(120, 120, 120, 0.4);
|
||||
--mr-line-height-tight: 1.3;
|
||||
--mr-line-height-normal: 1.5;
|
||||
--mr-line-height-relaxed: 1.6;
|
||||
--mr-line-height-editor: 19.5px;
|
||||
--mr-width-dropdown-min: 250px;
|
||||
--mr-width-dropdown-max: 400px;
|
||||
--mr-width-input-inline-sm: 120px;
|
||||
--mr-width-input-inline-md: 200px;
|
||||
--mr-width-dialog-sm: 320px;
|
||||
--mr-width-dialog-md: 800px;
|
||||
--mr-z-dropdown: 9998;
|
||||
--mr-z-dialog: 9999;
|
||||
--mr-z-rename-dialog: 10000;
|
||||
--mr-z-json-editor: 10001;
|
||||
--mr-z-floating-button: 10000;
|
||||
--mr-z-notification: 100000;
|
||||
--mr-z-context-menu: 100001;
|
||||
--mr-z-tooltip: 100002;
|
||||
--mr-spacing-1: 1px;
|
||||
--mr-spacing-2: 2px;
|
||||
--mr-spacing-3: 3px;
|
||||
--mr-spacing-4: 4px;
|
||||
--mr-spacing-5: 5px;
|
||||
--mr-spacing-6: 6px;
|
||||
--mr-spacing-7: 7px;
|
||||
--mr-spacing-8: 8px;
|
||||
--mr-spacing-10: 10px;
|
||||
--mr-spacing-12: 12px;
|
||||
--mr-spacing-15: 15px;
|
||||
--mr-spacing-16: 16px;
|
||||
--mr-spacing-20: 20px;
|
||||
--mr-spacing-24: 24px;
|
||||
--mr-spacing-32: 32px;
|
||||
--mr-spacing-40: 40px;
|
||||
--mr-spacing-48: 48px;
|
||||
--mr-radius-sm: 2px;
|
||||
--mr-radius-md: 4px;
|
||||
--mr-radius-lg: 6px;
|
||||
--mr-radius-xl: 8px;
|
||||
--mr-radius-2xl: 12px;
|
||||
--mr-radius-3xl: 16px;
|
||||
--mr-font-xs: 10px;
|
||||
--mr-font-sm: 11px;
|
||||
--mr-font-md: 12px;
|
||||
--mr-font-base: 13px;
|
||||
--mr-font-lg: 14px;
|
||||
--mr-font-xl: 16px;
|
||||
--mr-font-2xl: 18px;
|
||||
--mr-font-3xl: 20px;
|
||||
--mr-font-4xl: 24px;
|
||||
--mr-font-5xl: 48px;
|
||||
--mr-transition-fast: 0.1s;
|
||||
--mr-transition-normal: 0.2s;
|
||||
--mr-transition-all-fast: all 0.1s;
|
||||
--mr-transition-all: all 0.2s;
|
||||
--mr-transition-bg-fast: background 0.1s;
|
||||
--mr-transition-bg: background 0.2s;
|
||||
--mr-transition-color: color 0.2s;
|
||||
--mr-transition-border: border-color 0.2s;
|
||||
--mr-transition-opacity: opacity 0.2s;
|
||||
--mr-shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.8);
|
||||
--mr-gradient-dark: linear-gradient(135deg, var(--mr-color-bg-dark-1) 0%, var(--mr-color-bg-dark-2) 100%);
|
||||
--mr-drag-color-reorder: var(--mr-color-primary);
|
||||
--mr-drag-color-move: var(--mr-color-warning);
|
||||
--mr-drag-color-error: var(--mr-color-error);
|
||||
--mr-drag-color-clone: var(--mr-color-success);
|
||||
|
||||
--ml-bg: #121212;
|
||||
--ml-bg-soft: #171717;
|
||||
--ml-surface-elevated: #232323;
|
||||
--ml-card-bg: #1a1a1a;
|
||||
--ml-card-bg-alt: #1d1d1d;
|
||||
--ml-panel-bg: #202020;
|
||||
--ml-panel-bg-strong: #262626;
|
||||
--ml-border: rgba(255, 255, 255, 0.08);
|
||||
--ml-border-strong: rgba(255, 255, 255, 0.14);
|
||||
--ml-divider: rgba(255, 255, 255, 0.08);
|
||||
--ml-text: #f3f3f3;
|
||||
--ml-text-muted: #b1b1b1;
|
||||
--ml-text-dim: #7b7b7b;
|
||||
--ml-text-soft: #dfdfdf;
|
||||
--ml-accent: #72d38a;
|
||||
--ml-accent-hover: #88de9e;
|
||||
--ml-accent-blue: #72d38a;
|
||||
--ml-warning: #e1b24d;
|
||||
--ml-confidence-high: #72d38a;
|
||||
--ml-confidence-medium: #e1b24d;
|
||||
--ml-confidence-low: #e06a6a;
|
||||
--ml-link-color: #d8d8d8;
|
||||
--ml-shadow-elevated: 0 18px 42px rgba(0, 0, 0, 0.28);
|
||||
--ml-ui-radius-sm: var(--ml-radius-xl);
|
||||
--ml-ui-radius-md: var(--ml-radius-2xl);
|
||||
--ml-ui-radius-lg: var(--ml-radius-3xl);
|
||||
--ml-missing-control-bg: rgba(255, 255, 255, 0.035);
|
||||
--ml-missing-control-bg-hover: rgba(255, 255, 255, 0.06);
|
||||
--ml-missing-control-border: rgba(255, 255, 255, 0.105);
|
||||
--ml-missing-control-border-hover: rgba(255, 255, 255, 0.17);
|
||||
--ml-missing-control-focus-border: rgba(114, 211, 138, 0.30);
|
||||
--ml-missing-control-focus-ring: rgba(114, 211, 138, 0.08);
|
||||
--ml-missing-control-radius: 7px;
|
||||
--ml-missing-control-shadow: none;
|
||||
--ml-missing-control-transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
|
||||
--ml-missing-icon-button-size: 28px;
|
||||
--ml-missing-icon-size: 13px;
|
||||
--ml-missing-field-height: 38px;
|
||||
--ml-missing-search-button-height: 34px;
|
||||
--ml-missing-chip-bg: rgba(255, 255, 255, 0.045);
|
||||
--ml-missing-chip-bg-hover: rgba(255, 255, 255, 0.06);
|
||||
--ml-missing-chip-border: rgba(255, 255, 255, 0.08);
|
||||
--ml-missing-chip-border-hover: rgba(255, 255, 255, 0.18);
|
||||
--ml-missing-chip-radius: 999px;
|
||||
--ml-missing-chip-height: 20px;
|
||||
--ml-missing-chip-icon-size: var(--ml-spacing-12);
|
||||
--ml-type-chip-bg-alpha-strong: 0.155;
|
||||
--ml-type-chip-bg-alpha: 0.12;
|
||||
--ml-type-chip-border-alpha: 0.26;
|
||||
--ml-type-chip-glow-alpha: 0.045;
|
||||
--ml-type-generic-rgb: 176, 176, 176;
|
||||
--ml-type-generic-text: #d6d6d6;
|
||||
--ml-type-model-rgb: 179, 157, 219;
|
||||
--ml-type-model-text: #dbcfff;
|
||||
--ml-type-lora-rgb: 194, 255, 174;
|
||||
--ml-type-lora-text: #d7ffcb;
|
||||
--ml-type-style-model-rgb: 194, 255, 174;
|
||||
--ml-type-style-model-text: #d7ffcb;
|
||||
--ml-type-clip-rgb: 255, 213, 0;
|
||||
--ml-type-clip-text: #ffe678;
|
||||
--ml-type-clip-vision-rgb: 168, 218, 220;
|
||||
--ml-type-clip-vision-text: #c7eff0;
|
||||
--ml-type-clip-vision-output-rgb: 173, 116, 82;
|
||||
--ml-type-clip-vision-output-text: #e8b79a;
|
||||
--ml-type-conditioning-rgb: 255, 169, 49;
|
||||
--ml-type-conditioning-text: #ffd08c;
|
||||
--ml-type-controlnet-rgb: 110, 231, 183;
|
||||
--ml-type-controlnet-text: #b1f4db;
|
||||
--ml-type-image-rgb: 100, 181, 246;
|
||||
--ml-type-image-text: #b9dcff;
|
||||
--ml-type-latent-rgb: 255, 156, 249;
|
||||
--ml-type-latent-text: #ffc8fb;
|
||||
--ml-type-mask-rgb: 129, 199, 132;
|
||||
--ml-type-mask-text: #bdebc0;
|
||||
--ml-type-vae-rgb: 255, 110, 110;
|
||||
--ml-type-vae-text: #ffb8b8;
|
||||
--ml-type-noise-rgb: 176, 176, 176;
|
||||
--ml-type-noise-text: #d6d6d6;
|
||||
--ml-type-sampler-rgb: 236, 180, 180;
|
||||
--ml-type-sampler-text: #f2cccc;
|
||||
--ml-type-sigmas-rgb: 205, 255, 205;
|
||||
--ml-type-sigmas-text: #dcffdc;
|
||||
--ml-type-guider-rgb: 102, 255, 255;
|
||||
--ml-type-guider-text: #c7ffff;
|
||||
--ml-type-taesd-rgb: 220, 194, 116;
|
||||
--ml-type-taesd-text: #efd991;
|
||||
--mr-bg: #121212;
|
||||
--mr-bg-soft: #171717;
|
||||
--mr-surface-elevated: #232323;
|
||||
--mr-card-bg: #1a1a1a;
|
||||
--mr-card-bg-alt: #1d1d1d;
|
||||
--mr-panel-bg: #202020;
|
||||
--mr-panel-bg-strong: #262626;
|
||||
--mr-border: rgba(255, 255, 255, 0.08);
|
||||
--mr-border-strong: rgba(255, 255, 255, 0.14);
|
||||
--mr-divider: rgba(255, 255, 255, 0.08);
|
||||
--mr-text: #f3f3f3;
|
||||
--mr-text-muted: #b1b1b1;
|
||||
--mr-text-dim: #7b7b7b;
|
||||
--mr-text-soft: #dfdfdf;
|
||||
--mr-accent: #72d38a;
|
||||
--mr-accent-hover: #88de9e;
|
||||
--mr-accent-blue: #72d38a;
|
||||
--mr-warning: #e1b24d;
|
||||
--mr-confidence-high: #72d38a;
|
||||
--mr-confidence-medium: #e1b24d;
|
||||
--mr-confidence-low: #e06a6a;
|
||||
--mr-link-color: #d8d8d8;
|
||||
--mr-shadow-elevated: 0 18px 42px rgba(0, 0, 0, 0.28);
|
||||
--mr-ui-radius-sm: var(--mr-radius-xl);
|
||||
--mr-ui-radius-md: var(--mr-radius-2xl);
|
||||
--mr-ui-radius-lg: var(--mr-radius-3xl);
|
||||
--mr-missing-control-bg: rgba(255, 255, 255, 0.035);
|
||||
--mr-missing-control-bg-hover: rgba(255, 255, 255, 0.06);
|
||||
--mr-missing-control-border: rgba(255, 255, 255, 0.105);
|
||||
--mr-missing-control-border-hover: rgba(255, 255, 255, 0.17);
|
||||
--mr-missing-control-focus-border: rgba(114, 211, 138, 0.30);
|
||||
--mr-missing-control-focus-ring: rgba(114, 211, 138, 0.08);
|
||||
--mr-missing-control-radius: 7px;
|
||||
--mr-missing-control-shadow: none;
|
||||
--mr-missing-control-transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
|
||||
--mr-missing-icon-button-size: 28px;
|
||||
--mr-missing-icon-size: 13px;
|
||||
--mr-missing-field-height: 38px;
|
||||
--mr-missing-search-button-height: 34px;
|
||||
--mr-missing-chip-bg: rgba(255, 255, 255, 0.045);
|
||||
--mr-missing-chip-bg-hover: rgba(255, 255, 255, 0.06);
|
||||
--mr-missing-chip-border: rgba(255, 255, 255, 0.08);
|
||||
--mr-missing-chip-border-hover: rgba(255, 255, 255, 0.18);
|
||||
--mr-missing-chip-radius: 999px;
|
||||
--mr-missing-chip-height: 20px;
|
||||
--mr-missing-chip-icon-size: var(--mr-spacing-12);
|
||||
--mr-type-chip-bg-alpha-strong: 0.155;
|
||||
--mr-type-chip-bg-alpha: 0.12;
|
||||
--mr-type-chip-border-alpha: 0.26;
|
||||
--mr-type-chip-glow-alpha: 0.045;
|
||||
--mr-type-generic-rgb: 176, 176, 176;
|
||||
--mr-type-generic-text: #d6d6d6;
|
||||
--mr-type-model-rgb: 179, 157, 219;
|
||||
--mr-type-model-text: #dbcfff;
|
||||
--mr-type-lora-rgb: 194, 255, 174;
|
||||
--mr-type-lora-text: #d7ffcb;
|
||||
--mr-type-style-model-rgb: 194, 255, 174;
|
||||
--mr-type-style-model-text: #d7ffcb;
|
||||
--mr-type-clip-rgb: 255, 213, 0;
|
||||
--mr-type-clip-text: #ffe678;
|
||||
--mr-type-clip-vision-rgb: 168, 218, 220;
|
||||
--mr-type-clip-vision-text: #c7eff0;
|
||||
--mr-type-clip-vision-output-rgb: 173, 116, 82;
|
||||
--mr-type-clip-vision-output-text: #e8b79a;
|
||||
--mr-type-conditioning-rgb: 255, 169, 49;
|
||||
--mr-type-conditioning-text: #ffd08c;
|
||||
--mr-type-controlnet-rgb: 110, 231, 183;
|
||||
--mr-type-controlnet-text: #b1f4db;
|
||||
--mr-type-image-rgb: 100, 181, 246;
|
||||
--mr-type-image-text: #b9dcff;
|
||||
--mr-type-latent-rgb: 255, 156, 249;
|
||||
--mr-type-latent-text: #ffc8fb;
|
||||
--mr-type-mask-rgb: 129, 199, 132;
|
||||
--mr-type-mask-text: #bdebc0;
|
||||
--mr-type-vae-rgb: 255, 110, 110;
|
||||
--mr-type-vae-text: #ffb8b8;
|
||||
--mr-type-noise-rgb: 176, 176, 176;
|
||||
--mr-type-noise-text: #d6d6d6;
|
||||
--mr-type-sampler-rgb: 236, 180, 180;
|
||||
--mr-type-sampler-text: #f2cccc;
|
||||
--mr-type-sigmas-rgb: 205, 255, 205;
|
||||
--mr-type-sigmas-text: #dcffdc;
|
||||
--mr-type-guider-rgb: 102, 255, 255;
|
||||
--mr-type-guider-text: #c7ffff;
|
||||
--mr-type-taesd-rgb: 220, 194, 116;
|
||||
--mr-type-taesd-text: #efd991;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,12 @@
|
||||
.model-linker-backdrop {
|
||||
.model-resolver-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: transparent;
|
||||
z-index: var(--ml-z-dropdown);
|
||||
z-index: var(--mr-z-dropdown);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.model-linker-modal {
|
||||
.model-resolver-modal {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
@@ -23,7 +23,7 @@
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
resize: both;
|
||||
z-index: var(--ml-z-dialog);
|
||||
z-index: var(--mr-z-dialog);
|
||||
color: var(--input-text, #fff);
|
||||
background: linear-gradient(180deg, #171717 0%, #111 100%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
@@ -32,30 +32,30 @@
|
||||
box-shadow: 0 18px 46px rgba(0, 0, 0, 0.42);
|
||||
}
|
||||
|
||||
.ml-context-menu {
|
||||
.mr-context-menu {
|
||||
position: fixed;
|
||||
display: none;
|
||||
z-index: var(--ml-z-context-menu);
|
||||
z-index: var(--mr-z-context-menu);
|
||||
}
|
||||
|
||||
.ml-context-menu-item {
|
||||
.mr-context-menu-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ml-global-tooltip {
|
||||
.mr-global-tooltip {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ml-dialog-shell {
|
||||
.mr-dialog-shell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ml-dialog-topbar {
|
||||
.mr-dialog-topbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--ml-spacing-10);
|
||||
gap: var(--mr-spacing-10);
|
||||
min-height: 42px;
|
||||
padding: 6px 10px 0;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.38);
|
||||
@@ -64,32 +64,32 @@
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.ml-dialog-topbar.ml-is-dragging {
|
||||
.mr-dialog-topbar.mr-is-dragging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.ml-dialog-brand,
|
||||
.ml-dialog-controls,
|
||||
.ml-dialog-control-row {
|
||||
.mr-dialog-brand,
|
||||
.mr-dialog-controls,
|
||||
.mr-dialog-control-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--ml-spacing-8);
|
||||
gap: var(--mr-spacing-8);
|
||||
}
|
||||
|
||||
.ml-dialog-brand {
|
||||
.mr-dialog-brand {
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.ml-dialog-controls {
|
||||
.mr-dialog-controls {
|
||||
flex: 0 0 auto;
|
||||
align-self: stretch;
|
||||
gap: 0;
|
||||
margin: -6px -10px 0 0;
|
||||
}
|
||||
|
||||
#model-linker-drag-handle {
|
||||
#model-resolver-drag-handle {
|
||||
display: flex;
|
||||
align-self: flex-start;
|
||||
align-items: center;
|
||||
@@ -107,11 +107,11 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ml-dialog-topbar.ml-is-dragging #model-linker-drag-handle {
|
||||
.mr-dialog-topbar.mr-is-dragging #model-resolver-drag-handle {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
#model-linker-modal .ml-window-btn {
|
||||
#model-resolver-modal .mr-window-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -124,20 +124,20 @@
|
||||
line-height: 1;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
transition: var(--ml-missing-control-transition, background 0.15s ease, border-color 0.15s ease, color 0.15s ease);
|
||||
transition: var(--mr-missing-control-transition, background 0.15s ease, border-color 0.15s ease, color 0.15s ease);
|
||||
}
|
||||
|
||||
#model-linker-modal .ml-window-btn:hover {
|
||||
#model-resolver-modal .mr-window-btn:hover {
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
background: rgba(255, 255, 255, 0.075) !important;
|
||||
}
|
||||
|
||||
#model-linker-modal .ml-window-btn:focus-visible {
|
||||
#model-resolver-modal .mr-window-btn:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
#model-linker-modal .ml-window-btn--fullscreen {
|
||||
#model-resolver-modal .mr-window-btn--fullscreen {
|
||||
width: 46px;
|
||||
min-width: 46px;
|
||||
height: 100%;
|
||||
@@ -145,7 +145,7 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#model-linker-modal .ml-window-btn--dock {
|
||||
#model-resolver-modal .mr-window-btn--dock {
|
||||
width: 46px;
|
||||
min-width: 46px;
|
||||
height: 100%;
|
||||
@@ -153,7 +153,7 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#model-linker-modal .ml-window-btn--close {
|
||||
#model-resolver-modal .mr-window-btn--close {
|
||||
width: 46px;
|
||||
min-width: 46px;
|
||||
height: 100%;
|
||||
@@ -161,19 +161,19 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#model-linker-modal .ml-window-btn--close:hover {
|
||||
#model-resolver-modal .mr-window-btn--close:hover {
|
||||
color: #ffffff;
|
||||
background: #c42b1c !important;
|
||||
}
|
||||
|
||||
#model-linker-modal .ml-window-btn-icon {
|
||||
#model-resolver-modal .mr-window-btn-icon {
|
||||
display: block;
|
||||
width: 23px;
|
||||
height: 23px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#model-linker-body {
|
||||
#model-resolver-body {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
padding: 8px 6px 6px;
|
||||
@@ -181,18 +181,18 @@
|
||||
min-height: 0;
|
||||
align-items: stretch;
|
||||
position: relative;
|
||||
background: var(--ml-bg, #121212);
|
||||
background: var(--mr-bg, #121212);
|
||||
}
|
||||
|
||||
#model-linker-content {
|
||||
#model-resolver-content {
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
contain: layout paint style;
|
||||
background: var(--ml-bg, #222);
|
||||
background: var(--mr-bg, #222);
|
||||
}
|
||||
|
||||
#model-linker-queue {
|
||||
#model-resolver-queue {
|
||||
width: 320px;
|
||||
min-width: 240px;
|
||||
max-width: 70%;
|
||||
@@ -201,28 +201,28 @@
|
||||
padding-left: 0;
|
||||
border-left: 0;
|
||||
contain: layout paint style;
|
||||
background: var(--ml-bg, #121212);
|
||||
background: var(--mr-bg, #121212);
|
||||
}
|
||||
|
||||
#model-linker-body.ml-is-resizing-queue #model-linker-content,
|
||||
#model-linker-body.ml-is-resizing-queue #model-linker-queue {
|
||||
#model-resolver-body.mr-is-resizing-queue #model-resolver-content,
|
||||
#model-resolver-body.mr-is-resizing-queue #model-resolver-queue {
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#model-linker-splitter {
|
||||
#model-resolver-splitter {
|
||||
position: relative;
|
||||
width: 10px;
|
||||
min-width: 10px;
|
||||
opacity: 1;
|
||||
cursor: col-resize;
|
||||
background: var(--ml-bg, #121212);
|
||||
background: var(--mr-bg, #121212);
|
||||
border-left: 1px solid rgba(255, 255, 255, 0.075);
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.075);
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
#model-linker-splitter::before {
|
||||
#model-resolver-splitter::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
@@ -235,10 +235,10 @@
|
||||
transition: background 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
#model-linker-splitter:hover::before,
|
||||
#model-linker-splitter:focus-visible::before,
|
||||
#model-linker-body.ml-is-resizing-queue #model-linker-splitter::before,
|
||||
#model-linker-body.ml-queue-preview-collapsed #model-linker-splitter::before {
|
||||
#model-resolver-splitter:hover::before,
|
||||
#model-resolver-splitter:focus-visible::before,
|
||||
#model-resolver-body.mr-is-resizing-queue #model-resolver-splitter::before,
|
||||
#model-resolver-body.mr-queue-preview-collapsed #model-resolver-splitter::before {
|
||||
background: rgba(114, 211, 138, 0.78);
|
||||
box-shadow: 0 0 0 3px rgba(114, 211, 138, 0.12);
|
||||
}
|
||||
@@ -287,37 +287,37 @@
|
||||
box-shadow: 0 0 0 3px rgba(114, 211, 138, 0.12);
|
||||
}
|
||||
|
||||
.ml-queue-panel,
|
||||
.ml-queue-stack {
|
||||
.mr-queue-panel,
|
||||
.mr-queue-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.ml-queue-header {
|
||||
.mr-queue-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--ml-spacing-10);
|
||||
gap: var(--mr-spacing-10);
|
||||
min-height: 44px;
|
||||
margin-bottom: 0;
|
||||
padding: 0 0 10px;
|
||||
}
|
||||
|
||||
.ml-queue-title {
|
||||
color: var(--ml-text, #f3f3f3);
|
||||
.mr-queue-title {
|
||||
color: var(--mr-text, #f3f3f3);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.ml-queue-actions {
|
||||
.mr-queue-actions {
|
||||
display: flex;
|
||||
gap: var(--ml-spacing-6);
|
||||
gap: var(--mr-spacing-6);
|
||||
}
|
||||
|
||||
.ml-queue-list {
|
||||
.mr-queue-list {
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
@@ -327,19 +327,19 @@
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.ml-queue-empty {
|
||||
.mr-queue-empty {
|
||||
padding: 12px;
|
||||
color: var(--ml-text-muted, #b1b1b1);
|
||||
color: var(--mr-text-muted, #b1b1b1);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.ml-queue-items {
|
||||
.mr-queue-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.ml-queue-item {
|
||||
.mr-queue-item {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
padding: 10px 0;
|
||||
@@ -347,41 +347,41 @@
|
||||
border: 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.052);
|
||||
border-radius: 0;
|
||||
color: var(--ml-text, #f3f3f3);
|
||||
color: var(--mr-text, #f3f3f3);
|
||||
transition: background 0.14s ease, box-shadow 0.14s ease;
|
||||
}
|
||||
|
||||
.ml-queue-item:last-child {
|
||||
.mr-queue-item:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.ml-queue-item:hover {
|
||||
.mr-queue-item:hover {
|
||||
background: rgba(255, 255, 255, 0.032);
|
||||
}
|
||||
|
||||
.ml-queue-item-title,
|
||||
.ml-selected-label {
|
||||
.mr-queue-item-title,
|
||||
.mr-selected-label {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.ml-queue-item-meta,
|
||||
.ml-queue-item-selection {
|
||||
.mr-queue-item-meta,
|
||||
.mr-queue-item-selection {
|
||||
display: grid;
|
||||
grid-template-columns: 50px minmax(0, 1fr);
|
||||
align-items: baseline;
|
||||
gap: 6px;
|
||||
color: var(--ml-text-muted, #b1b1b1);
|
||||
color: var(--mr-text-muted, #b1b1b1);
|
||||
font-size: 11px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.ml-queue-item-meta span,
|
||||
.ml-queue-item-selection span {
|
||||
color: var(--ml-text-dim, #7b7b7b);
|
||||
.mr-queue-item-meta span,
|
||||
.mr-queue-item-selection span {
|
||||
color: var(--mr-text-dim, #7b7b7b);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.ml-queue-item code {
|
||||
.mr-queue-item code {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: #dedede;
|
||||
@@ -391,86 +391,86 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ml-queue-item-actions,
|
||||
.ml-selected-bar-inner {
|
||||
.mr-queue-item-actions,
|
||||
.mr-selected-bar-inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--ml-spacing-8);
|
||||
gap: var(--mr-spacing-8);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ml-queue-item-actions {
|
||||
.mr-queue-item-actions {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.model-linker-selected {
|
||||
.model-resolver-selected {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ml-selected-label {
|
||||
.mr-selected-label {
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.ml-selected-code {
|
||||
.mr-selected-code {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.ml-inline-note-link {
|
||||
.mr-inline-note-link {
|
||||
cursor: pointer;
|
||||
color: var(--ml-accent-blue);
|
||||
color: var(--mr-accent-blue);
|
||||
}
|
||||
|
||||
.ml-footer {
|
||||
.mr-footer {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
background: var(--ml-bg, #222);
|
||||
background: var(--mr-bg, #222);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.ml-notification {
|
||||
.mr-notification {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--ml-spacing-12);
|
||||
gap: var(--mr-spacing-12);
|
||||
min-width: 300px;
|
||||
max-width: 600px;
|
||||
padding: var(--ml-spacing-12) var(--ml-spacing-24);
|
||||
padding: var(--mr-spacing-12) var(--mr-spacing-24);
|
||||
color: #fff;
|
||||
font-size: var(--ml-font-lg);
|
||||
font-size: var(--mr-font-lg);
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
transform: translateX(-50%);
|
||||
border-radius: 0 0 var(--ml-ui-radius-sm) var(--ml-ui-radius-sm);
|
||||
border-radius: 0 0 var(--mr-ui-radius-sm) var(--mr-ui-radius-sm);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
||||
z-index: var(--ml-z-notification);
|
||||
animation: ml-slide-down 0.3s ease;
|
||||
z-index: var(--mr-z-notification);
|
||||
animation: mr-slide-down 0.3s ease;
|
||||
}
|
||||
|
||||
.ml-notification--success {
|
||||
.mr-notification--success {
|
||||
background: #28a745;
|
||||
}
|
||||
|
||||
.ml-notification--error {
|
||||
.mr-notification--error {
|
||||
background: #dc3545;
|
||||
}
|
||||
|
||||
.ml-notification--info {
|
||||
.mr-notification--info {
|
||||
background: #007acc;
|
||||
}
|
||||
|
||||
.ml-notification-icon {
|
||||
font-size: var(--ml-font-2xl);
|
||||
.mr-notification-icon {
|
||||
font-size: var(--mr-font-2xl);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.ml-notification-close {
|
||||
width: var(--ml-spacing-24);
|
||||
height: var(--ml-spacing-24);
|
||||
.mr-notification-close {
|
||||
width: var(--mr-spacing-24);
|
||||
height: var(--mr-spacing-24);
|
||||
margin-left: auto;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
@@ -478,104 +478,104 @@
|
||||
justify-content: center;
|
||||
opacity: 0.8;
|
||||
color: #fff;
|
||||
font-size: var(--ml-font-3xl);
|
||||
font-size: var(--mr-font-3xl);
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: var(--ml-radius-md);
|
||||
border-radius: var(--mr-radius-md);
|
||||
}
|
||||
|
||||
.ml-popup-auto-resolve-btn,
|
||||
.model-linker-floating-button {
|
||||
.mr-popup-auto-resolve-btn,
|
||||
.model-resolver-floating-button {
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ml-popup-auto-resolve-btn {
|
||||
padding: var(--ml-spacing-6) var(--ml-spacing-12);
|
||||
.mr-popup-auto-resolve-btn {
|
||||
padding: var(--mr-spacing-6) var(--mr-spacing-12);
|
||||
color: #fff;
|
||||
font-size: var(--ml-font-md);
|
||||
font-size: var(--mr-font-md);
|
||||
background: #4caf50;
|
||||
border: none;
|
||||
border-radius: var(--ml-radius-lg);
|
||||
border-radius: var(--mr-radius-lg);
|
||||
}
|
||||
|
||||
.ml-popup-auto-resolve-btn:hover {
|
||||
.mr-popup-auto-resolve-btn:hover {
|
||||
background: #45a049;
|
||||
}
|
||||
|
||||
.ml-popup-inline-actions {
|
||||
.mr-popup-inline-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--ml-spacing-16);
|
||||
padding: 0 var(--ml-spacing-16);
|
||||
margin-bottom: var(--ml-spacing-8);
|
||||
gap: var(--mr-spacing-16);
|
||||
padding: 0 var(--mr-spacing-16);
|
||||
margin-bottom: var(--mr-spacing-8);
|
||||
}
|
||||
|
||||
.ml-popup-actions-wrap {
|
||||
.mr-popup-actions-wrap {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 0 var(--ml-spacing-16);
|
||||
margin-bottom: var(--ml-spacing-8);
|
||||
padding: 0 var(--mr-spacing-16);
|
||||
margin-bottom: var(--mr-spacing-8);
|
||||
}
|
||||
|
||||
.ml-resolved-entry {
|
||||
.mr-resolved-entry {
|
||||
transition: all 0.3s ease;
|
||||
background: rgba(76, 175, 80, 0.2);
|
||||
border: 1px solid #4caf50;
|
||||
border-radius: var(--ml-radius-lg);
|
||||
border-radius: var(--mr-radius-lg);
|
||||
}
|
||||
|
||||
.ml-resolved-badge {
|
||||
.mr-resolved-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: var(--ml-spacing-4) var(--ml-spacing-12);
|
||||
padding: var(--mr-spacing-4) var(--mr-spacing-12);
|
||||
color: #fff;
|
||||
font-size: var(--ml-font-md);
|
||||
font-size: var(--mr-font-md);
|
||||
font-weight: 600;
|
||||
background: #4caf50;
|
||||
border-radius: var(--ml-radius-md);
|
||||
border-radius: var(--mr-radius-md);
|
||||
}
|
||||
|
||||
.model-linker-floating-button {
|
||||
.model-resolver-floating-button {
|
||||
position: fixed;
|
||||
top: var(--ml-spacing-10);
|
||||
right: var(--ml-spacing-10);
|
||||
z-index: var(--ml-z-floating-button);
|
||||
padding: var(--ml-spacing-8) var(--ml-spacing-16);
|
||||
top: var(--mr-spacing-10);
|
||||
right: var(--mr-spacing-10);
|
||||
z-index: var(--mr-z-floating-button);
|
||||
padding: var(--mr-spacing-8) var(--mr-spacing-16);
|
||||
color: var(--input-text, #fff);
|
||||
font-size: var(--ml-font-lg);
|
||||
font-size: var(--mr-font-lg);
|
||||
background: var(--comfy-input-bg, #353535);
|
||||
border: 2px solid var(--primary-color, #007acc);
|
||||
border-radius: var(--ml-radius-lg);
|
||||
border-radius: var(--mr-radius-lg);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.model-linker-floating-button:hover {
|
||||
.model-resolver-floating-button:hover {
|
||||
background: var(--primary-color, #007acc);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.comfyui-model-linker-tab-button .mdi,
|
||||
.comfyui-model-linker-tab-button .mdi-link-variant {
|
||||
.comfyui-model-resolver-tab-button .mdi,
|
||||
.comfyui-model-resolver-tab-button .mdi-link-variant {
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.ml-sidebar-dock-panel {
|
||||
.mr-sidebar-dock-panel {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
color: var(--ml-text);
|
||||
background: var(--ml-bg);
|
||||
color: var(--mr-text);
|
||||
background: var(--mr-bg);
|
||||
}
|
||||
|
||||
#model-linker-modal.ml-is-docked {
|
||||
#model-resolver-modal.mr-is-docked {
|
||||
position: relative;
|
||||
inset: auto;
|
||||
top: auto;
|
||||
@@ -594,7 +594,7 @@
|
||||
z-index: auto;
|
||||
}
|
||||
|
||||
#model-linker-modal .ml-window-btn--undock {
|
||||
#model-resolver-modal .mr-window-btn--undock {
|
||||
display: none;
|
||||
width: 46px;
|
||||
min-width: 46px;
|
||||
@@ -603,15 +603,15 @@
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#model-linker-modal.ml-is-docked .ml-window-btn--undock {
|
||||
#model-resolver-modal.mr-is-docked .mr-window-btn--undock {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#model-linker-modal.ml-is-docked .ml-window-btn--dock {
|
||||
#model-resolver-modal.mr-is-docked .mr-window-btn--dock {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@keyframes ml-slide-down {
|
||||
@keyframes mr-slide-down {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-50%) translateY(-100%);
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* ComfyUI Model Linker Extension - Frontend
|
||||
*
|
||||
* Provides a menu button and dialog interface for relinking missing models in workflows.
|
||||
*/
|
||||
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { ModelLinker } from "./linker/model_linker.js";
|
||||
import { registerGlobalHelpers } from "./linker/globals.js";
|
||||
|
||||
registerGlobalHelpers();
|
||||
|
||||
const modelLinker = new ModelLinker();
|
||||
|
||||
app.registerExtension({
|
||||
name: "Model Linker",
|
||||
setup: modelLinker.setup
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
/**
|
||||
author: Azornes
|
||||
title: AzLogs
|
||||
version: 1.5.5
|
||||
@@ -9,10 +9,10 @@ description: Logging Configuration
|
||||
export const LOG_LEVEL = 'DEBUG';
|
||||
|
||||
// Logger module name. Set to null to use the default project name.
|
||||
export const LOG_MODULE_NAME = "MLinker";
|
||||
export const LOG_MODULE_NAME = "MResolver";
|
||||
|
||||
// Default project/logger name used when LOG_MODULE_NAME is not set.
|
||||
export const DEFAULT_LOGGER_NAME = 'MLinker';
|
||||
export const DEFAULT_LOGGER_NAME = 'MResolver';
|
||||
|
||||
// Enable styled console logs.
|
||||
export const USE_COLORS = true;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* ComfyUI Model Resolver Extension - Frontend
|
||||
*
|
||||
* Provides a menu button and dialog interface for relinking missing models in workflows.
|
||||
*/
|
||||
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { ModelResolver as ModelResolverClass } from "./resolver/model_resolver.js";
|
||||
import { registerGlobalHelpers } from "./resolver/globals.js";
|
||||
|
||||
registerGlobalHelpers();
|
||||
|
||||
const modelResolver = new ModelResolverClass();
|
||||
|
||||
app.registerExtension({
|
||||
name: "Model Resolver",
|
||||
setup: modelResolver.setup
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { api } from "../../../../../scripts/api.js";
|
||||
import { $el } from "../../../../../scripts/ui.js";
|
||||
import { getSvgIcon } from "../../utils/icon_utils.js";
|
||||
@@ -6,22 +6,22 @@ export const queueMethods = {
|
||||
createContent() {
|
||||
// Wrap the body in a two-column layout: left = items, right = queued panel
|
||||
const body = $el("div", {
|
||||
id: "model-linker-body"
|
||||
id: "model-resolver-body"
|
||||
});
|
||||
|
||||
this.contentElement = $el("div.ml-scrollable", {
|
||||
id: "model-linker-content"
|
||||
this.contentElement = $el("div.mr-scrollable", {
|
||||
id: "model-resolver-content"
|
||||
});
|
||||
|
||||
this.queueElement = $el("div", {
|
||||
id: "model-linker-queue"
|
||||
id: "model-resolver-queue"
|
||||
}, [
|
||||
this.createQueuePanel()
|
||||
]);
|
||||
|
||||
// Splitter between content and queue
|
||||
this.splitterElement = $el("div", {
|
||||
id: "model-linker-splitter",
|
||||
id: "model-resolver-splitter",
|
||||
ondragstart: (e) => e.preventDefault()
|
||||
});
|
||||
|
||||
@@ -31,7 +31,7 @@ export const queueMethods = {
|
||||
|
||||
// Restore saved queue width and wire splitter
|
||||
try {
|
||||
const savedSplit = localStorage.getItem('model_linker_split_w');
|
||||
const savedSplit = localStorage.getItem('model_resolver_split_w');
|
||||
if (savedSplit) {
|
||||
const w = parseInt(savedSplit, 10);
|
||||
if (!isNaN(w) && w > 0) {
|
||||
@@ -61,7 +61,7 @@ export const queueMethods = {
|
||||
|
||||
// Restore queue collapsed state
|
||||
try {
|
||||
const col = localStorage.getItem('model_linker_queue_collapsed');
|
||||
const col = localStorage.getItem('model_resolver_queue_collapsed');
|
||||
if (col === '1') this.setQueueCollapsed(true);
|
||||
} catch (e) { }
|
||||
|
||||
@@ -70,18 +70,18 @@ export const queueMethods = {
|
||||
|
||||
createQueuePanel() {
|
||||
// Header row with title and clear button
|
||||
this.queueHeader = $el("div.ml-queue-header", {}, [
|
||||
$el("div#queue-title.ml-queue-title", { textContent: "Queued Selections (0)" }),
|
||||
$el("div.ml-queue-actions", {}, [
|
||||
this.queueHeader = $el("div.mr-queue-header", {}, [
|
||||
$el("div#queue-title.mr-queue-title", { textContent: "Queued Selections (0)" }),
|
||||
$el("div.mr-queue-actions", {}, [
|
||||
$el("button", {
|
||||
id: "queue-toggle",
|
||||
className: "ml-btn ml-btn-secondary ml-btn-sm",
|
||||
className: "mr-btn mr-btn-secondary mr-btn-sm",
|
||||
textContent: "Collapse",
|
||||
onclick: () => this.toggleQueueCollapsed()
|
||||
}),
|
||||
$el("button", {
|
||||
id: "queue-clear",
|
||||
className: "ml-btn ml-btn-secondary ml-btn-sm",
|
||||
className: "mr-btn mr-btn-secondary mr-btn-sm",
|
||||
textContent: "Clear All",
|
||||
onclick: () => this.clearAllQueued()
|
||||
})
|
||||
@@ -89,10 +89,10 @@ export const queueMethods = {
|
||||
]);
|
||||
|
||||
// Scrollable list
|
||||
this.queueList = $el("div#queue-list.ml-queue-list");
|
||||
this.queueList = $el("div#queue-list.mr-queue-list");
|
||||
|
||||
const panel = $el("div.ml-queue-panel", {}, [
|
||||
$el("div.ml-queue-stack", {}, [this.queueHeader, this.queueList])
|
||||
const panel = $el("div.mr-queue-panel", {}, [
|
||||
$el("div.mr-queue-stack", {}, [this.queueHeader, this.queueList])
|
||||
]);
|
||||
return panel;
|
||||
},
|
||||
@@ -107,22 +107,22 @@ export const queueMethods = {
|
||||
if (toggleBtn) toggleBtn.textContent = this.queueCollapsed ? 'Expand' : 'Collapse';
|
||||
|
||||
if (!list.length) {
|
||||
this.queueList.innerHTML = '<div class="ml-queue-empty">No selections queued.</div>';
|
||||
this.queueList.innerHTML = '<div class="mr-queue-empty">No selections queued.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<div class="ml-queue-items">';
|
||||
let html = '<div class="mr-queue-items">';
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const r = list[i];
|
||||
const label = (r.resolved_model?.relative_path || r.resolved_model?.filename || r.resolved_path || '').toString();
|
||||
const nodeLabel = r.node_label || r.node_type || (r.subgraph_id ? 'Subgraph' : 'Node');
|
||||
const orig = (r.original_path || '').toString();
|
||||
const rmId = `queue-remove-${i}`;
|
||||
html += `<div class="ml-queue-item">`;
|
||||
html += `<div class="ml-queue-item-title">${this.escapeHtml(nodeLabel)} #${this.escapeHtml(String(r.node_id))}</div>`;
|
||||
html += `<div class="ml-queue-item-meta"><span>Original</span><code>${this.escapeHtml(orig)}</code></div>`;
|
||||
html += `<div class="ml-queue-item-selection"><span>Selected</span><code>${this.escapeHtml(label)}</code></div>`;
|
||||
html += `<div class="ml-queue-item-actions"><button id="${rmId}" class="ml-btn ml-btn-secondary ml-btn-sm">Remove</button></div>`;
|
||||
html += `<div class="mr-queue-item">`;
|
||||
html += `<div class="mr-queue-item-title">${this.escapeHtml(nodeLabel)} #${this.escapeHtml(String(r.node_id))}</div>`;
|
||||
html += `<div class="mr-queue-item-meta"><span>Original</span><code>${this.escapeHtml(orig)}</code></div>`;
|
||||
html += `<div class="mr-queue-item-selection"><span>Selected</span><code>${this.escapeHtml(label)}</code></div>`;
|
||||
html += `<div class="mr-queue-item-actions"><button id="${rmId}" class="mr-btn mr-btn-secondary mr-btn-sm">Remove</button></div>`;
|
||||
html += `</div>`;
|
||||
}
|
||||
html += '</div>';
|
||||
@@ -162,7 +162,7 @@ export const queueMethods = {
|
||||
this.updateApplyPendingButton?.();
|
||||
this.updateQueuePanel();
|
||||
try {
|
||||
document.querySelectorAll('.model-linker-selected').forEach(el => { el.style.display = 'none'; el.innerHTML = ''; });
|
||||
document.querySelectorAll('.model-resolver-selected').forEach(el => { el.style.display = 'none'; el.innerHTML = ''; });
|
||||
} catch (e) { /* ignore */ }
|
||||
},
|
||||
|
||||
@@ -199,10 +199,10 @@ export const queueMethods = {
|
||||
const label = selection.resolved_model?.relative_path || selection.resolved_model?.filename || selection.resolved_path || '';
|
||||
const resolveBtnId = `selected-remove-${nodeId}-${widgetIndex}`;
|
||||
|
||||
selectedBar.innerHTML = `<div class="ml-selected-bar-inner">`;
|
||||
selectedBar.innerHTML += `<span class="ml-selected-label">✓ Selected:</span>`;
|
||||
selectedBar.innerHTML += `<code class="ml-selected-code">${label}</code>`;
|
||||
selectedBar.innerHTML += `<button id="${resolveBtnId}" class="ml-btn ml-btn-secondary ml-btn-sm">Remove</button>`;
|
||||
selectedBar.innerHTML = `<div class="mr-selected-bar-inner">`;
|
||||
selectedBar.innerHTML += `<span class="mr-selected-label">✓ Selected:</span>`;
|
||||
selectedBar.innerHTML += `<code class="mr-selected-code">${label}</code>`;
|
||||
selectedBar.innerHTML += `<button id="${resolveBtnId}" class="mr-btn mr-btn-secondary mr-btn-sm">Remove</button>`;
|
||||
selectedBar.innerHTML += `</div>`;
|
||||
selectedBar.style.display = 'block';
|
||||
|
||||
@@ -254,9 +254,9 @@ export const queueMethods = {
|
||||
setQueueCollapsed(collapsed) {
|
||||
this.queueCollapsed = !!collapsed;
|
||||
if (this.queueCollapsed) {
|
||||
try { localStorage.setItem('model_linker_queue_collapsed', '1'); } catch (e) { }
|
||||
try { localStorage.setItem('model_resolver_queue_collapsed', '1'); } catch (e) { }
|
||||
} else {
|
||||
try { localStorage.setItem('model_linker_queue_collapsed', '0'); } catch (e) { }
|
||||
try { localStorage.setItem('model_resolver_queue_collapsed', '0'); } catch (e) { }
|
||||
}
|
||||
this.updateQueueVisibility();
|
||||
this.updateQueuePanel();
|
||||
@@ -313,7 +313,7 @@ export const queueMethods = {
|
||||
document.body.style.cursor = 'col-resize';
|
||||
this._suppressQueueEdgeClick = true;
|
||||
this.splitterElement.style.display = '';
|
||||
document.getElementById('model-linker-body')?.classList.add('ml-queue-preview-collapsed');
|
||||
document.getElementById('model-resolver-body')?.classList.add('mr-queue-preview-collapsed');
|
||||
this.queueElement.style.display = 'none';
|
||||
this.queueElement.style.width = '0px';
|
||||
this.startSplitDrag(
|
||||
@@ -328,10 +328,10 @@ export const queueMethods = {
|
||||
e?.preventDefault?.();
|
||||
if (!this.queueElement) return;
|
||||
const rect = this.queueElement.getBoundingClientRect();
|
||||
const body = document.getElementById('model-linker-body');
|
||||
const body = document.getElementById('model-resolver-body');
|
||||
const bodyRect = body ? body.getBoundingClientRect() : { width: window.innerWidth };
|
||||
this._splitDragging = true;
|
||||
body?.classList.add('ml-is-resizing-queue');
|
||||
body?.classList.add('mr-is-resizing-queue');
|
||||
this._splitStart = {
|
||||
x: e.clientX,
|
||||
startWidth: Number.isFinite(startWidth) ? startWidth : rect.width,
|
||||
@@ -364,7 +364,7 @@ export const queueMethods = {
|
||||
this._splitEdgeOpenedPastThreshold = true;
|
||||
if (this.queueCollapsed) {
|
||||
this.queueCollapsed = false;
|
||||
try { localStorage.setItem('model_linker_queue_collapsed', '0'); } catch (error) { }
|
||||
try { localStorage.setItem('model_resolver_queue_collapsed', '0'); } catch (error) { }
|
||||
this.updateQueueToggleIcon();
|
||||
this.updateQueuePanel();
|
||||
}
|
||||
@@ -374,8 +374,8 @@ export const queueMethods = {
|
||||
: this._pendingSplitWidth <= 140;
|
||||
if (previewCollapsed !== this._splitPreviewCollapsed) {
|
||||
this._splitPreviewCollapsed = previewCollapsed;
|
||||
const body = document.getElementById('model-linker-body');
|
||||
body?.classList.toggle('ml-queue-preview-collapsed', previewCollapsed);
|
||||
const body = document.getElementById('model-resolver-body');
|
||||
body?.classList.toggle('mr-queue-preview-collapsed', previewCollapsed);
|
||||
this.queueElement.style.display = previewCollapsed ? 'none' : '';
|
||||
}
|
||||
const now = performance.now();
|
||||
@@ -399,9 +399,9 @@ export const queueMethods = {
|
||||
if (this.queueElement && this._pendingSplitWidth && !this._splitPreviewCollapsed) {
|
||||
this.queueElement.style.width = `${this._pendingSplitWidth}px`;
|
||||
}
|
||||
const body = document.getElementById('model-linker-body');
|
||||
body?.classList.remove('ml-is-resizing-queue');
|
||||
body?.classList.remove('ml-queue-preview-collapsed');
|
||||
const body = document.getElementById('model-resolver-body');
|
||||
body?.classList.remove('mr-is-resizing-queue');
|
||||
body?.classList.remove('mr-queue-preview-collapsed');
|
||||
document.removeEventListener('mousemove', this._onSplitMove);
|
||||
if (this._splitEdgeOpen && !this._pendingSplitWidth) {
|
||||
try {
|
||||
@@ -409,10 +409,10 @@ export const queueMethods = {
|
||||
this.queueElement.style.width = `${restoreWidth}px`;
|
||||
this.queueElement.style.display = '';
|
||||
if (this.splitterElement) this.splitterElement.style.display = '';
|
||||
localStorage.setItem('model_linker_split_w', String(restoreWidth));
|
||||
localStorage.setItem('model_resolver_split_w', String(restoreWidth));
|
||||
} catch (e) { }
|
||||
this.queueCollapsed = false;
|
||||
try { localStorage.setItem('model_linker_queue_collapsed', '0'); } catch (e) { }
|
||||
try { localStorage.setItem('model_resolver_queue_collapsed', '0'); } catch (e) { }
|
||||
this.updateQueueVisibility();
|
||||
this.updateQueuePanel();
|
||||
this._splitEdgeOpen = false;
|
||||
@@ -432,7 +432,7 @@ export const queueMethods = {
|
||||
const restoreWidth = Math.max(240, Math.round(this._splitStart?.startWidth || 320));
|
||||
this.queueElement.style.width = `${restoreWidth}px`;
|
||||
this.queueElement.style.display = '';
|
||||
localStorage.setItem('model_linker_split_w', String(restoreWidth));
|
||||
localStorage.setItem('model_resolver_split_w', String(restoreWidth));
|
||||
} catch (e) { }
|
||||
this._pendingSplitWidth = null;
|
||||
this._lastSplitDragApply = 0;
|
||||
@@ -449,7 +449,7 @@ export const queueMethods = {
|
||||
if (this.queueElement) this.queueElement.style.display = '';
|
||||
try {
|
||||
const rect = this.queueElement.getBoundingClientRect();
|
||||
localStorage.setItem('model_linker_split_w', String(Math.round(rect.width)));
|
||||
localStorage.setItem('model_resolver_split_w', String(Math.round(rect.width)));
|
||||
} catch (e) { }
|
||||
this._pendingSplitWidth = null;
|
||||
this._lastSplitDragApply = 0;
|
||||
@@ -520,7 +520,7 @@ export const queueMethods = {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await api.fetchApi('/model_linker/resolve', {
|
||||
const response = await api.fetchApi('/model_resolver/resolve', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ workflow, resolutions: list })
|
||||
@@ -543,7 +543,7 @@ export const queueMethods = {
|
||||
this.showNotification('Failed to apply selections: ' + (data.error || 'Unknown error'), 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Model Linker: applyPendingResolutions error', e);
|
||||
console.error('Model Resolver: applyPendingResolutions error', e);
|
||||
this.showNotification('Error applying selections: ' + e.message, 'error');
|
||||
}
|
||||
},
|
||||
@@ -558,7 +558,7 @@ export const queueMethods = {
|
||||
this.applyPendingBtn.textContent = `Apply Selected (${count})`;
|
||||
this.setTooltip(this.applyPendingBtn, tooltip);
|
||||
this.applyPendingBtn.setAttribute('aria-disabled', String(isEmpty));
|
||||
this.applyPendingBtn.classList.toggle('ml-btn-is-disabled', isEmpty);
|
||||
this.applyPendingBtn.classList.toggle('mr-btn-is-disabled', isEmpty);
|
||||
this.updateBatchFooterButtons();
|
||||
},
|
||||
|
||||
@@ -573,18 +573,18 @@ export const queueMethods = {
|
||||
|
||||
if (this.selectMenuButton) {
|
||||
this.selectMenuButton.textContent = `Select (${selectedCount}/${totalCount})`;
|
||||
this.selectMenuButton.classList.toggle('ml-btn-is-disabled', totalCount === 0);
|
||||
this.selectMenuButton.classList.toggle('mr-btn-is-disabled', totalCount === 0);
|
||||
}
|
||||
if (this.searchMenuButton) {
|
||||
this.searchMenuButton.textContent = this.batchSearchRunning ? 'Searching...' : 'Search';
|
||||
this.searchMenuButton.classList.toggle('ml-btn-is-disabled', totalCount === 0 || this.batchSearchRunning);
|
||||
this.searchMenuButton.classList.toggle('mr-btn-is-disabled', totalCount === 0 || this.batchSearchRunning);
|
||||
}
|
||||
if (this.downloadMenuButton) {
|
||||
const label = activeCount > 0 ? `Cancel Downloads (${activeCount})` : 'Download';
|
||||
this.downloadMenuButton.textContent = label;
|
||||
this.downloadMenuButton.classList.toggle('ml-btn-danger', activeCount > 0);
|
||||
this.downloadMenuButton.classList.toggle('ml-btn-download', activeCount === 0);
|
||||
this.downloadMenuButton.classList.toggle('ml-btn-is-disabled', activeCount === 0 && downloadableAll === 0);
|
||||
this.downloadMenuButton.classList.toggle('mr-btn-danger', activeCount > 0);
|
||||
this.downloadMenuButton.classList.toggle('mr-btn-download', activeCount === 0);
|
||||
this.downloadMenuButton.classList.toggle('mr-btn-is-disabled', activeCount === 0 && downloadableAll === 0);
|
||||
this.setTooltip(
|
||||
this.downloadMenuButton,
|
||||
activeCount > 0
|
||||
@@ -597,14 +597,14 @@ export const queueMethods = {
|
||||
}
|
||||
},
|
||||
|
||||
createFooterMenu(name, label, items = [], buttonClass = 'ml-btn-secondary') {
|
||||
const button = $el(`button.ml-btn.${buttonClass}.ml-footer-btn.ml-footer-menu-button`, {
|
||||
createFooterMenu(name, label, items = [], buttonClass = 'mr-btn-secondary') {
|
||||
const button = $el(`button.mr-btn.${buttonClass}.mr-footer-btn.mr-footer-menu-button`, {
|
||||
type: 'button',
|
||||
'aria-haspopup': 'menu',
|
||||
'aria-expanded': 'false',
|
||||
onclick: (event) => {
|
||||
event.stopPropagation();
|
||||
if (button.classList.contains('ml-btn-is-disabled')) {
|
||||
if (button.classList.contains('mr-btn-is-disabled')) {
|
||||
if (name === 'search' && this.batchSearchRunning) {
|
||||
this.showNotification('Batch search is already running.', 'info');
|
||||
} else if (name === 'download') {
|
||||
@@ -618,10 +618,10 @@ export const queueMethods = {
|
||||
}
|
||||
}, [$el("span", { textContent: label })]);
|
||||
|
||||
const menu = $el("div.ml-footer-menu", { role: 'menu' }, items.map(item => (
|
||||
const menu = $el("div.mr-footer-menu", { role: 'menu' }, items.map(item => (
|
||||
item === 'divider'
|
||||
? $el("div.ml-footer-menu-divider", {})
|
||||
: $el("button.ml-footer-menu-item", {
|
||||
? $el("div.mr-footer-menu-divider", {})
|
||||
: $el("button.mr-footer-menu-item", {
|
||||
type: 'button',
|
||||
role: 'menuitem',
|
||||
onclick: (event) => {
|
||||
@@ -634,7 +634,7 @@ export const queueMethods = {
|
||||
this.footerMenuButtons.set(name, button);
|
||||
this.footerMenus.set(name, menu);
|
||||
|
||||
return $el("div.ml-footer-menu-wrap", {}, [button, menu]);
|
||||
return $el("div.mr-footer-menu-wrap", {}, [button, menu]);
|
||||
},
|
||||
|
||||
createFooter() {
|
||||
@@ -672,25 +672,25 @@ export const queueMethods = {
|
||||
this.searchMenuWrap = searchMenu;
|
||||
|
||||
// Store reference to download all button so we can update its text
|
||||
this.downloadAllButton = $el("button.ml-btn.ml-btn-download.ml-footer-btn", {
|
||||
this.downloadAllButton = $el("button.mr-btn.mr-btn-download.mr-footer-btn", {
|
||||
"data-tooltip": "Download every missing model that has a known download source.",
|
||||
"aria-label": "Download all missing models",
|
||||
onclick: () => this.handleDownloadAllClick()
|
||||
}, [
|
||||
$el("span.ml-btn-icon", { textContent: "☁" }),
|
||||
$el("span.mr-btn-icon", { textContent: "☁" }),
|
||||
$el("span", { textContent: " Download All Missing" })
|
||||
]);
|
||||
|
||||
// Auto-resolve button (secondary style)
|
||||
this.autoResolveButton = $el("button.ml-btn.ml-btn-secondary.ml-footer-btn", {
|
||||
this.autoResolveButton = $el("button.mr-btn.mr-btn-secondary.mr-footer-btn", {
|
||||
"data-tooltip": "Automatically link all missing models with a 100% local match.",
|
||||
"aria-label": "Auto-link all 100 percent local matches",
|
||||
onclick: () => this.autoResolve100Percent()
|
||||
}, [
|
||||
$el("span.ml-btn-icon", { textContent: "🔗" }),
|
||||
$el("span.mr-btn-icon", { textContent: "🔗" }),
|
||||
$el("span", { textContent: " Auto-Link 100%" })
|
||||
]);
|
||||
this.queueExactButton = $el("button.ml-btn.ml-btn-secondary.ml-footer-btn", {
|
||||
this.queueExactButton = $el("button.mr-btn.mr-btn-secondary.mr-footer-btn", {
|
||||
type: 'button',
|
||||
onclick: () => this.queueExactLocalMatchesBatch('selected')
|
||||
}, [
|
||||
@@ -698,7 +698,7 @@ export const queueMethods = {
|
||||
]);
|
||||
|
||||
// Apply pending resolutions button
|
||||
this.applyPendingBtn = $el("button.ml-btn.ml-btn-primary.ml-footer-btn", {
|
||||
this.applyPendingBtn = $el("button.mr-btn.mr-btn-primary.mr-footer-btn", {
|
||||
id: "apply-pending-resolutions",
|
||||
"data-tooltip": "Apply the model links you selected from local matches or search results.",
|
||||
"aria-label": "Apply selected model links",
|
||||
@@ -709,7 +709,7 @@ export const queueMethods = {
|
||||
this.applyPendingResolutions();
|
||||
}
|
||||
});
|
||||
this.applyPendingBtn.classList.add('ml-btn-is-disabled');
|
||||
this.applyPendingBtn.classList.add('mr-btn-is-disabled');
|
||||
this.downloadAllButton.setAttribute('aria-label', 'Download all missing models');
|
||||
this.autoResolveButton.setAttribute('aria-label', 'Auto-link all 100 percent local matches');
|
||||
this.queueExactButton.setAttribute('aria-label', 'Queue exact local matches');
|
||||
@@ -726,14 +726,14 @@ export const queueMethods = {
|
||||
{ label: 'Download All With Sources', action: () => this.downloadMissingBatch('all') },
|
||||
'divider',
|
||||
{ label: 'Cancel Downloads', action: () => { this.closeFooterMenus(); this.cancelAllDownloads(); } }
|
||||
], 'ml-btn-download');
|
||||
], 'mr-btn-download');
|
||||
this.downloadMenuButton = this.footerMenuButtons.get('download');
|
||||
this.downloadMenuWrap = downloadMenu;
|
||||
this.downloadAllButton = this.downloadMenuButton;
|
||||
this.downloadAllButton.setAttribute('aria-label', 'Download missing models');
|
||||
this.setTooltip(this.downloadAllButton, 'Download selected models or all missing models with known download sources.');
|
||||
|
||||
return $el("div.ml-footer", {}, [
|
||||
return $el("div.mr-footer", {}, [
|
||||
selectMenu,
|
||||
searchMenu,
|
||||
this.queueExactButton,
|
||||
@@ -758,7 +758,7 @@ export const queueMethods = {
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.warn('Model Linker: tab content animation failed', error);
|
||||
console.warn('Model Resolver: tab content animation failed', error);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -786,11 +786,11 @@ export const queueMethods = {
|
||||
|
||||
for (const downloadId of downloadIds) {
|
||||
try {
|
||||
await api.fetchApi(`/model_linker/cancel/${downloadId}`, {
|
||||
await api.fetchApi(`/model_resolver/cancel/${downloadId}`, {
|
||||
method: 'POST'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Model Linker: Error cancelling download:', error);
|
||||
console.error('Model Resolver: Error cancelling download:', error);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -807,23 +807,23 @@ export const queueMethods = {
|
||||
|
||||
const activeCount = Object.keys(this.activeDownloads).length;
|
||||
if (activeCount > 0) {
|
||||
this.downloadAllButton.innerHTML = `<span class="ml-btn-icon">✕</span> Cancel All (${activeCount})`;
|
||||
this.downloadAllButton.innerHTML = `<span class="mr-btn-icon">✕</span> Cancel All (${activeCount})`;
|
||||
this.downloadAllButton.setAttribute(
|
||||
'data-tooltip',
|
||||
`Cancel ${activeCount} active download${activeCount > 1 ? 's' : ''}.`
|
||||
);
|
||||
this.downloadAllButton.setAttribute('aria-label', 'Cancel all active downloads');
|
||||
this.downloadAllButton.classList.remove('ml-btn-download');
|
||||
this.downloadAllButton.classList.add('ml-btn-danger');
|
||||
this.downloadAllButton.classList.remove('mr-btn-download');
|
||||
this.downloadAllButton.classList.add('mr-btn-danger');
|
||||
} else {
|
||||
this.downloadAllButton.innerHTML = `<span class="ml-btn-icon">☁</span> Download All Missing`;
|
||||
this.downloadAllButton.innerHTML = `<span class="mr-btn-icon">☁</span> Download All Missing`;
|
||||
this.downloadAllButton.setAttribute(
|
||||
'data-tooltip',
|
||||
'Download every missing model that has a known download source.'
|
||||
);
|
||||
this.downloadAllButton.setAttribute('aria-label', 'Download all missing models');
|
||||
this.downloadAllButton.classList.remove('ml-btn-danger');
|
||||
this.downloadAllButton.classList.add('ml-btn-download');
|
||||
this.downloadAllButton.classList.remove('mr-btn-danger');
|
||||
this.downloadAllButton.classList.add('mr-btn-download');
|
||||
}
|
||||
}
|
||||
};
|
||||
+63
-63
@@ -1,4 +1,4 @@
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { api } from "../../../../../scripts/api.js";
|
||||
import { $el } from "../../../../../scripts/ui.js";
|
||||
import { getSvgIcon } from "../../utils/icon_utils.js";
|
||||
@@ -37,7 +37,7 @@ export const resolveDownloadMethods = {
|
||||
original_lora_name: ref.name || ref.original_path
|
||||
}));
|
||||
|
||||
const response = await api.fetchApi('/model_linker/resolve', {
|
||||
const response = await api.fetchApi('/model_resolver/resolve', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -71,7 +71,7 @@ export const resolveDownloadMethods = {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Model Linker: Error resolving model:', error);
|
||||
console.error('Model Resolver: Error resolving model:', error);
|
||||
this.showNotification('Error resolving model: ' + error.message, 'error');
|
||||
}
|
||||
},
|
||||
@@ -89,7 +89,7 @@ export const resolveDownloadMethods = {
|
||||
}
|
||||
|
||||
// Analyze workflow first
|
||||
const analyzeResponse = await api.fetchApi('/model_linker/analyze', {
|
||||
const analyzeResponse = await api.fetchApi('/model_resolver/analyze', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ workflow })
|
||||
@@ -129,7 +129,7 @@ export const resolveDownloadMethods = {
|
||||
}
|
||||
|
||||
// Apply resolutions
|
||||
const resolveResponse = await api.fetchApi('/model_linker/resolve', {
|
||||
const resolveResponse = await api.fetchApi('/model_resolver/resolve', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -167,7 +167,7 @@ export const resolveDownloadMethods = {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Model Linker: Error auto-resolving:', error);
|
||||
console.error('Model Resolver: Error auto-resolving:', error);
|
||||
this.showNotification('Error auto-resolving: ' + error.message, 'error');
|
||||
return null;
|
||||
}
|
||||
@@ -187,7 +187,7 @@ export const resolveDownloadMethods = {
|
||||
}
|
||||
|
||||
// Analyze workflow first
|
||||
const analyzeResponse = await api.fetchApi('/model_linker/analyze', {
|
||||
const analyzeResponse = await api.fetchApi('/model_resolver/analyze', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ workflow })
|
||||
@@ -232,7 +232,7 @@ export const resolveDownloadMethods = {
|
||||
this.updateDownloadAllButtonState();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Model Linker: Error in downloadAllMissing:', error);
|
||||
console.error('Model Resolver: Error in downloadAllMissing:', error);
|
||||
this.showNotification('Error starting downloads: ' + error.message, 'error');
|
||||
}
|
||||
},
|
||||
@@ -351,7 +351,7 @@ export const resolveDownloadMethods = {
|
||||
await this.searchOnline(missing);
|
||||
} catch (error) {
|
||||
failed += 1;
|
||||
console.error('Model Linker: batch search item failed:', error);
|
||||
console.error('Model Resolver: batch search item failed:', error);
|
||||
}
|
||||
completed += 1;
|
||||
this.updateBatchFooterButtons();
|
||||
@@ -436,7 +436,7 @@ export const resolveDownloadMethods = {
|
||||
}
|
||||
|
||||
// Re-analyze workflow to find the newly downloaded model
|
||||
const analyzeResponse = await api.fetchApi('/model_linker/analyze', {
|
||||
const analyzeResponse = await api.fetchApi('/model_resolver/analyze', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ workflow })
|
||||
@@ -488,7 +488,7 @@ export const resolveDownloadMethods = {
|
||||
original_lora_name: ref.name || ref.original_path
|
||||
}));
|
||||
|
||||
const resolveResponse = await api.fetchApi('/model_linker/resolve', {
|
||||
const resolveResponse = await api.fetchApi('/model_resolver/resolve', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -513,7 +513,7 @@ export const resolveDownloadMethods = {
|
||||
await this.loadWorkflowData();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Model Linker: Error auto-resolving after download:', error);
|
||||
console.error('Model Resolver: Error auto-resolving after download:', error);
|
||||
// Still reload UI even on error
|
||||
await this.loadWorkflowData();
|
||||
}
|
||||
@@ -544,24 +544,24 @@ export const resolveDownloadMethods = {
|
||||
// Disable button and show progress with cancel button immediately
|
||||
if (downloadBtn) {
|
||||
downloadBtn.disabled = true;
|
||||
downloadBtn.classList.remove('ml-is-success-action', 'ml-btn-primary');
|
||||
downloadBtn.classList.remove('mr-is-success-action', 'mr-btn-primary');
|
||||
downloadBtn.textContent = 'Starting...';
|
||||
}
|
||||
if (progressDiv) {
|
||||
progressDiv.classList.remove('ml-is-hidden');
|
||||
progressDiv.classList.add('ml-is-visible');
|
||||
progressDiv.classList.remove('mr-is-hidden');
|
||||
progressDiv.classList.add('mr-is-visible');
|
||||
// Show progress bar with cancel button immediately
|
||||
progressDiv.innerHTML = this.renderProgressWithAction({
|
||||
percent: 0,
|
||||
leftText: '<span class="ml-info-accent-text">Connecting...</span>',
|
||||
leftText: '<span class="mr-info-accent-text">Connecting...</span>',
|
||||
rightText: '',
|
||||
actionClass: 'cancel-download-btn-pending ml-btn ml-btn-danger ml-btn-sm',
|
||||
actionClass: 'cancel-download-btn-pending mr-btn mr-btn-danger mr-btn-sm',
|
||||
actionText: 'Cancel'
|
||||
});
|
||||
}
|
||||
|
||||
// Start download
|
||||
const response = await api.fetchApi('/model_linker/download', {
|
||||
const response = await api.fetchApi('/model_resolver/download', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -599,14 +599,14 @@ export const resolveDownloadMethods = {
|
||||
this.pollDownloadProgress(downloadId);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Model Linker: Download error:', error);
|
||||
console.error('Model Resolver: Download error:', error);
|
||||
if (progressDiv) {
|
||||
progressDiv.innerHTML = this.renderStatusMessage(error.message, 'error');
|
||||
}
|
||||
if (downloadBtn) {
|
||||
downloadBtn.disabled = false;
|
||||
downloadBtn.classList.remove('ml-is-success-action', 'ml-btn-primary');
|
||||
downloadBtn.innerHTML = '<span class="ml-btn-icon">☁</span> Retry';
|
||||
downloadBtn.classList.remove('mr-is-success-action', 'mr-btn-primary');
|
||||
downloadBtn.innerHTML = '<span class="mr-btn-icon">☁</span> Retry';
|
||||
}
|
||||
this.showNotification('Download failed: ' + error.message, 'error');
|
||||
}
|
||||
@@ -620,7 +620,7 @@ export const resolveDownloadMethods = {
|
||||
if (!info) return;
|
||||
|
||||
try {
|
||||
const response = await api.fetchApi(`/model_linker/progress/${downloadId}`);
|
||||
const response = await api.fetchApi(`/model_resolver/progress/${downloadId}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to get progress');
|
||||
}
|
||||
@@ -639,7 +639,7 @@ export const resolveDownloadMethods = {
|
||||
percent,
|
||||
leftText: `${downloaded} / ${total} (${percent}%)`,
|
||||
rightText: speed,
|
||||
actionClass: 'cancel-download-btn ml-btn ml-btn-danger ml-btn-sm',
|
||||
actionClass: 'cancel-download-btn mr-btn mr-btn-danger mr-btn-sm',
|
||||
actionText: 'Cancel',
|
||||
actionDataAttr: `data-download-id="${downloadId}"`
|
||||
});
|
||||
@@ -663,8 +663,8 @@ export const resolveDownloadMethods = {
|
||||
}
|
||||
if (downloadBtn) {
|
||||
downloadBtn.textContent = '✓ Done';
|
||||
downloadBtn.classList.remove('ml-is-success-action');
|
||||
downloadBtn.classList.add('ml-btn-primary');
|
||||
downloadBtn.classList.remove('mr-is-success-action');
|
||||
downloadBtn.classList.add('mr-btn-primary');
|
||||
}
|
||||
delete this.activeDownloads[downloadId];
|
||||
this.updateDownloadAllButtonState();
|
||||
@@ -682,7 +682,7 @@ export const resolveDownloadMethods = {
|
||||
}
|
||||
if (downloadBtn) {
|
||||
downloadBtn.disabled = false;
|
||||
downloadBtn.classList.remove('ml-is-success-action', 'ml-btn-primary');
|
||||
downloadBtn.classList.remove('mr-is-success-action', 'mr-btn-primary');
|
||||
downloadBtn.textContent = 'Retry';
|
||||
}
|
||||
delete this.activeDownloads[downloadId];
|
||||
@@ -694,8 +694,8 @@ export const resolveDownloadMethods = {
|
||||
}
|
||||
if (downloadBtn) {
|
||||
downloadBtn.disabled = false;
|
||||
downloadBtn.classList.remove('ml-is-success-action', 'ml-btn-primary');
|
||||
downloadBtn.innerHTML = '<span class="ml-btn-icon">☁</span> Download';
|
||||
downloadBtn.classList.remove('mr-is-success-action', 'mr-btn-primary');
|
||||
downloadBtn.innerHTML = '<span class="mr-btn-icon">☁</span> Download';
|
||||
}
|
||||
delete this.activeDownloads[downloadId];
|
||||
this.updateDownloadAllButtonState();
|
||||
@@ -707,7 +707,7 @@ export const resolveDownloadMethods = {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Model Linker: Progress poll error:', error);
|
||||
console.error('Model Resolver: Progress poll error:', error);
|
||||
const info = this.activeDownloads[downloadId];
|
||||
// Update UI to show error state instead of just disappearing
|
||||
if (info) {
|
||||
@@ -718,8 +718,8 @@ export const resolveDownloadMethods = {
|
||||
if (downloadBtn) {
|
||||
downloadBtn.disabled = false;
|
||||
downloadBtn.textContent = 'Retry';
|
||||
downloadBtn.classList.remove('ml-btn-primary');
|
||||
downloadBtn.classList.add('ml-is-success-action');
|
||||
downloadBtn.classList.remove('mr-btn-primary');
|
||||
downloadBtn.classList.add('mr-is-success-action');
|
||||
}
|
||||
}
|
||||
delete this.activeDownloads[downloadId];
|
||||
@@ -732,7 +732,7 @@ export const resolveDownloadMethods = {
|
||||
*/
|
||||
async cancelDownload(downloadId) {
|
||||
try {
|
||||
const response = await api.fetchApi(`/model_linker/cancel/${downloadId}`, {
|
||||
const response = await api.fetchApi(`/model_resolver/cancel/${downloadId}`, {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
@@ -746,7 +746,7 @@ export const resolveDownloadMethods = {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Model Linker: Cancel error:', error);
|
||||
console.error('Model Resolver: Cancel error:', error);
|
||||
this.showNotification('Failed to cancel download', 'error');
|
||||
}
|
||||
},
|
||||
@@ -829,8 +829,8 @@ export const resolveDownloadMethods = {
|
||||
searchBtn.innerHTML = `${this.getSearchIconHtml()} Searching ${selectedSourceLabel}...`;
|
||||
}
|
||||
if (resultsDiv) {
|
||||
resultsDiv.classList.remove('ml-is-hidden');
|
||||
resultsDiv.classList.add('ml-is-visible');
|
||||
resultsDiv.classList.remove('mr-is-hidden');
|
||||
resultsDiv.classList.add('mr-is-visible');
|
||||
this.displaySearchResults(missing, state, resultsDiv);
|
||||
}
|
||||
for (const source of sourceIds) {
|
||||
@@ -877,9 +877,9 @@ export const resolveDownloadMethods = {
|
||||
};
|
||||
|
||||
try {
|
||||
console.log('Model Linker: Search request:', JSON.stringify(searchData));
|
||||
console.log('Model Resolver: Search request:', JSON.stringify(searchData));
|
||||
|
||||
const response = await api.fetchApi('/model_linker/search', {
|
||||
const response = await api.fetchApi('/model_resolver/search', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(searchData)
|
||||
@@ -890,7 +890,7 @@ export const resolveDownloadMethods = {
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('Model Linker: Search response:', JSON.stringify(data));
|
||||
console.log('Model Resolver: Search response:', JSON.stringify(data));
|
||||
|
||||
if (state.activeSearchRunId !== searchRunId) {
|
||||
return { source, stale: true };
|
||||
@@ -939,7 +939,7 @@ export const resolveDownloadMethods = {
|
||||
this.applySearchResultSuggestion(missing);
|
||||
return { source, data };
|
||||
} catch (error) {
|
||||
console.error(`Model Linker: Search error for ${source}:`, error);
|
||||
console.error(`Model Resolver: Search error for ${source}:`, error);
|
||||
if (state.activeSearchRunId !== searchRunId) {
|
||||
return { source, stale: true, error };
|
||||
}
|
||||
@@ -975,7 +975,7 @@ export const resolveDownloadMethods = {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Model Linker: Search error:', error);
|
||||
console.error('Model Resolver: Search error:', error);
|
||||
this.clearSearchProgressTimers(searchRunId);
|
||||
state.lastAttemptError = error.message;
|
||||
this.persistSearchStateForActiveWorkflow();
|
||||
@@ -1027,7 +1027,7 @@ export const resolveDownloadMethods = {
|
||||
};
|
||||
console.log('resolveUrnAsync payload:', JSON.stringify(payload));
|
||||
|
||||
const response = await api.fetchApi('/model_linker/search', {
|
||||
const response = await api.fetchApi('/model_resolver/search', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
@@ -1043,16 +1043,16 @@ export const resolveDownloadMethods = {
|
||||
if (data) {
|
||||
const loadingEl = document.getElementById(loadingElementId);
|
||||
if (loadingEl && data.civitai) {
|
||||
loadingEl.classList.remove('ml-is-muted', 'ml-is-error');
|
||||
loadingEl.classList.remove('mr-is-muted', 'mr-is-error');
|
||||
const civitai = data.civitai;
|
||||
const labelHtml = this.renderVersionedModelNameHtml(civitai.name, civitai.version_name)
|
||||
|| this.escapeHtml(civitai.filename || 'Model');
|
||||
const url = modelUrl || `https://civitai.com/models/${modelId}?modelVersionId=${versionId}`;
|
||||
loadingEl.innerHTML = `<a href="${url}" target="_blank" class="ml-inline-civitai-link">${labelHtml}</a>`;
|
||||
loadingEl.innerHTML = `<a href="${url}" target="_blank" class="mr-inline-civitai-link">${labelHtml}</a>`;
|
||||
} else if (loadingEl) {
|
||||
loadingEl.textContent = 'Not found';
|
||||
loadingEl.classList.remove('ml-is-error');
|
||||
loadingEl.classList.add('ml-is-muted');
|
||||
loadingEl.classList.remove('mr-is-error');
|
||||
loadingEl.classList.add('mr-is-muted');
|
||||
}
|
||||
|
||||
const downloadEl = document.getElementById(downloadContainerId);
|
||||
@@ -1065,33 +1065,33 @@ export const resolveDownloadMethods = {
|
||||
this.refreshUrnLocalMatches(missing);
|
||||
}
|
||||
} else if (downloadEl) {
|
||||
downloadEl.innerHTML = `<div class="ml-download-info">Unable to resolve direct download for this URN.</div>`;
|
||||
downloadEl.innerHTML = `<div class="mr-download-info">Unable to resolve direct download for this URN.</div>`;
|
||||
}
|
||||
} else {
|
||||
const loadingEl = document.getElementById(loadingElementId);
|
||||
if (loadingEl) {
|
||||
loadingEl.textContent = 'Error';
|
||||
loadingEl.classList.remove('ml-is-muted');
|
||||
loadingEl.classList.add('ml-is-error');
|
||||
loadingEl.classList.remove('mr-is-muted');
|
||||
loadingEl.classList.add('mr-is-error');
|
||||
}
|
||||
const downloadContainerId = loadingElementId.replace('urn-loading-', 'urn-download-');
|
||||
const downloadEl = document.getElementById(downloadContainerId);
|
||||
if (downloadEl) {
|
||||
downloadEl.innerHTML = `<div class="ml-download-info">Failed to resolve URN download.</div>`;
|
||||
downloadEl.innerHTML = `<div class="mr-download-info">Failed to resolve URN download.</div>`;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Model Linker: URN resolve error:', error);
|
||||
console.error('Model Resolver: URN resolve error:', error);
|
||||
const loadingEl = document.getElementById(loadingElementId);
|
||||
if (loadingEl) {
|
||||
loadingEl.textContent = 'Error';
|
||||
loadingEl.classList.remove('ml-is-muted');
|
||||
loadingEl.classList.add('ml-is-error');
|
||||
loadingEl.classList.remove('mr-is-muted');
|
||||
loadingEl.classList.add('mr-is-error');
|
||||
}
|
||||
const downloadContainerId = loadingElementId.replace('urn-loading-', 'urn-download-');
|
||||
const downloadEl = document.getElementById(downloadContainerId);
|
||||
if (downloadEl) {
|
||||
downloadEl.innerHTML = `<div class="ml-download-info">Failed to resolve URN download.</div>`;
|
||||
downloadEl.innerHTML = `<div class="mr-download-info">Failed to resolve URN download.</div>`;
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1148,10 +1148,10 @@ export const resolveDownloadMethods = {
|
||||
const renderSourceOptions = () => {
|
||||
const options = this.getSearchSourceOptions();
|
||||
sourceList.innerHTML = options
|
||||
.map(option => `<div class="ml-download-target-option" data-value="${encodeURIComponent(option.value)}" data-label="${encodeURIComponent(option.label)}">${this.escapeHtml(option.label)}</div>`)
|
||||
.map(option => `<div class="mr-download-target-option" data-value="${encodeURIComponent(option.value)}" data-label="${encodeURIComponent(option.label)}">${this.escapeHtml(option.label)}</div>`)
|
||||
.join('');
|
||||
sourceList.style.display = 'block';
|
||||
sourceList.querySelectorAll('.ml-download-target-option').forEach(optionEl => {
|
||||
sourceList.querySelectorAll('.mr-download-target-option').forEach(optionEl => {
|
||||
optionEl.addEventListener('mousedown', (event) => {
|
||||
event.preventDefault();
|
||||
const value = decodeURIComponent(optionEl.dataset.value || '');
|
||||
@@ -1372,23 +1372,23 @@ export const resolveDownloadMethods = {
|
||||
|
||||
try {
|
||||
btn.disabled = true;
|
||||
btn.classList.remove('ml-is-success-action', 'ml-btn-primary');
|
||||
btn.classList.remove('mr-is-success-action', 'mr-btn-primary');
|
||||
btn.textContent = 'Starting...';
|
||||
|
||||
if (progressDiv) {
|
||||
progressDiv.classList.remove('ml-is-hidden');
|
||||
progressDiv.classList.add('ml-is-visible');
|
||||
progressDiv.classList.remove('mr-is-hidden');
|
||||
progressDiv.classList.add('mr-is-visible');
|
||||
// Show progress bar with cancel button immediately
|
||||
progressDiv.innerHTML = this.renderProgressWithAction({
|
||||
percent: 0,
|
||||
leftText: '<span class="ml-info-accent-text">Connecting...</span>',
|
||||
leftText: '<span class="mr-info-accent-text">Connecting...</span>',
|
||||
rightText: '',
|
||||
actionClass: 'cancel-download-btn-pending ml-btn ml-btn-danger ml-btn-sm',
|
||||
actionClass: 'cancel-download-btn-pending mr-btn mr-btn-danger mr-btn-sm',
|
||||
actionText: 'Cancel'
|
||||
});
|
||||
}
|
||||
|
||||
const response = await api.fetchApi('/model_linker/download', {
|
||||
const response = await api.fetchApi('/model_resolver/download', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -1426,12 +1426,12 @@ export const resolveDownloadMethods = {
|
||||
this.pollDownloadProgress(downloadId);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Model Linker: Download error:', error);
|
||||
console.error('Model Resolver: Download error:', error);
|
||||
if (progressDiv) {
|
||||
progressDiv.innerHTML = this.renderStatusMessage(error.message, 'error');
|
||||
}
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('ml-is-success-action', 'ml-btn-primary');
|
||||
btn.classList.remove('mr-is-success-action', 'mr-btn-primary');
|
||||
btn.textContent = 'Retry';
|
||||
this.showNotification('Download failed: ' + error.message, 'error');
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { api } from "../../../../../scripts/api.js";
|
||||
import { $el } from "../../../../../scripts/ui.js";
|
||||
import { getSvgIcon } from "../../utils/icon_utils.js";
|
||||
@@ -15,7 +15,7 @@ export const selectionMethods = {
|
||||
|
||||
handleFooterMenuOutsideClick(e) {
|
||||
if (!this.activeFooterMenu) return;
|
||||
if (e.target?.closest?.('.ml-footer-menu-wrap')) return;
|
||||
if (e.target?.closest?.('.mr-footer-menu-wrap')) return;
|
||||
this.closeFooterMenus();
|
||||
},
|
||||
|
||||
@@ -166,11 +166,11 @@ export const selectionMethods = {
|
||||
|
||||
refreshBatchSelectionUi() {
|
||||
const selectedKeys = this.batchSelectedMissingKeys || new Set();
|
||||
this.contentElement?.querySelectorAll?.('.ml-missing-list-row')?.forEach(row => {
|
||||
this.contentElement?.querySelectorAll?.('.mr-missing-list-row')?.forEach(row => {
|
||||
const key = row.getAttribute('data-missing-key');
|
||||
const selected = selectedKeys.has(key);
|
||||
row.classList.toggle('is-batch-selected', selected);
|
||||
const checkbox = row.querySelector('.ml-missing-row-check');
|
||||
const checkbox = row.querySelector('.mr-missing-row-check');
|
||||
if (checkbox) {
|
||||
checkbox.checked = selected;
|
||||
}
|
||||
@@ -179,7 +179,7 @@ export const selectionMethods = {
|
||||
},
|
||||
|
||||
updateBatchSelectAllCheckbox() {
|
||||
const checkbox = this.contentElement?.querySelector?.('.ml-missing-select-all-check');
|
||||
const checkbox = this.contentElement?.querySelector?.('.mr-missing-select-all-check');
|
||||
if (!checkbox) return;
|
||||
|
||||
const totalCount = (this.missingModels || []).length;
|
||||
@@ -1,14 +1,14 @@
|
||||
export function registerGlobalHelpers() {
|
||||
export function registerGlobalHelpers() {
|
||||
// Global helper functions for inline onclick handlers
|
||||
window.MLToggleHidden = function(id, trigger, collapsedText, expandedText) {
|
||||
const element = document.getElementById(id);
|
||||
if (!element) return;
|
||||
|
||||
const isHidden = element.classList.toggle('ml-hidden');
|
||||
const isHidden = element.classList.toggle('mr-hidden');
|
||||
if (trigger) {
|
||||
trigger.setAttribute('aria-expanded', String(!isHidden));
|
||||
const label = trigger.querySelector?.('.ml-local-alternatives-label');
|
||||
const state = trigger.querySelector?.('.ml-local-alternatives-state');
|
||||
const label = trigger.querySelector?.('.mr-local-alternatives-label');
|
||||
const state = trigger.querySelector?.('.mr-local-alternatives-state');
|
||||
if (label || state) {
|
||||
if (label) label.textContent = isHidden ? collapsedText : expandedText;
|
||||
if (state) state.textContent = isHidden ? 'Show' : 'Hide';
|
||||
@@ -23,9 +23,9 @@ export function registerGlobalHelpers() {
|
||||
if (!section) return;
|
||||
|
||||
const normalizedMode = ['all', 'active', 'inactive'].includes(mode) ? mode : 'all';
|
||||
const codeEl = section.querySelector('.ml-copy-code');
|
||||
const labelEl = section.querySelector('.ml-copy-label');
|
||||
const metaEl = section.querySelector('.ml-copy-meta');
|
||||
const codeEl = section.querySelector('.mr-copy-code');
|
||||
const labelEl = section.querySelector('.mr-copy-label');
|
||||
const metaEl = section.querySelector('.mr-copy-meta');
|
||||
const dataKey = normalizedMode === 'all' ? 'mlAll' : normalizedMode === 'active' ? 'mlActive' : 'mlInactive';
|
||||
const countKey = normalizedMode === 'all' ? 'mlAllCount' : normalizedMode === 'active' ? 'mlActiveCount' : 'mlInactiveCount';
|
||||
const labelMap = {
|
||||
@@ -41,7 +41,7 @@ export function registerGlobalHelpers() {
|
||||
metaEl.textContent = `${count} token${count === 1 ? '' : 's'}`;
|
||||
}
|
||||
|
||||
section.querySelectorAll('.ml-copy-mode').forEach(btn => {
|
||||
section.querySelectorAll('.mr-copy-mode').forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.mlCopyMode === normalizedMode);
|
||||
});
|
||||
};
|
||||
@@ -61,16 +61,16 @@ export function registerGlobalHelpers() {
|
||||
if (!filterBtn) return;
|
||||
|
||||
// Update button states
|
||||
document.querySelectorAll('.ml-btn-filter').forEach(b => b.classList.remove('active'));
|
||||
document.querySelectorAll('.mr-btn-filter').forEach(b => b.classList.remove('active'));
|
||||
filterBtn.classList.add('active');
|
||||
|
||||
// Filter model sections
|
||||
document.querySelectorAll('.ml-model-section').forEach(s => {
|
||||
document.querySelectorAll('.mr-model-section').forEach(s => {
|
||||
const hasActive = s.getAttribute('data-ml-active') === 'true';
|
||||
const hasInactive = s.getAttribute('data-ml-inactive') === 'true';
|
||||
|
||||
const activeSection = s.querySelector('.ml-model-group-active');
|
||||
const inactiveSection = s.querySelector('.ml-model-group-inactive');
|
||||
const activeSection = s.querySelector('.mr-model-group-active');
|
||||
const inactiveSection = s.querySelector('.mr-model-group-inactive');
|
||||
|
||||
if (filter === 'all') {
|
||||
s.style.display = 'block';
|
||||
@@ -87,7 +87,7 @@ export function registerGlobalHelpers() {
|
||||
}
|
||||
});
|
||||
|
||||
const copySection = document.querySelector('[id^="ml-copy-"]');
|
||||
const copySection = document.querySelector('[id^="mr-copy-"]');
|
||||
if (copySection) {
|
||||
window.MLSetCopyMode(copySection.id, filter);
|
||||
}
|
||||
@@ -118,12 +118,12 @@ export function registerGlobalHelpers() {
|
||||
const model = JSON.parse(decodeURIComponent(modelData));
|
||||
|
||||
// Get dialog instance
|
||||
const dialog = window.modelLinkerDialog;
|
||||
const dialog = window.ModelResolverDialog;
|
||||
if (dialog && dialog.showContextMenu) {
|
||||
dialog.showContextMenu(event.clientX, event.clientY, model);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Model Linker: Error opening context menu:', e);
|
||||
console.error('Model Resolver: Error opening context menu:', e);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
import { app } from "../../../../scripts/app.js";
|
||||
import { app } from "../../../../scripts/app.js";
|
||||
import { api } from "../../../../scripts/api.js";
|
||||
import { $el } from "../../../../scripts/ui.js";
|
||||
import { loadStylesWhenNeeded } from "../utils/css_loader.js";
|
||||
import { LinkerManagerDialog } from "./linker_dialog.js";
|
||||
import { ResolverManagerDialog } from "./resolver_dialog.js";
|
||||
|
||||
// Main extension class
|
||||
export class ModelLinker {
|
||||
export class ModelResolver {
|
||||
constructor() {
|
||||
this.linkerButton = null;
|
||||
this.resolverButton = null;
|
||||
this.buttonGroup = null;
|
||||
this.buttonId = "model-linker-button";
|
||||
this.sidebarTabId = "comfyui-model-linker";
|
||||
this.buttonId = "model-resolver-button";
|
||||
this.sidebarTabId = "comfyui-model-resolver";
|
||||
this.sidebarRegistered = false;
|
||||
this.openTooltip = "Open Model Linker to find or download missing workflow models. Shortcut: Ctrl+Shift+L.";
|
||||
this.openTooltip = "Open Model Resolver to find or download missing workflow models. Shortcut: Ctrl+Shift+L.";
|
||||
this.dialog = null;
|
||||
this.isCheckingMissing = false; // Prevent multiple simultaneous checks
|
||||
this.lastCheckedWorkflow = null; // Track to avoid duplicate checks
|
||||
@@ -26,15 +26,15 @@ export class ModelLinker {
|
||||
|
||||
// Create dialog instance
|
||||
if (!this.dialog) {
|
||||
this.dialog = new LinkerManagerDialog();
|
||||
window.modelLinkerDialog = this.dialog;
|
||||
this.dialog = new ResolverManagerDialog();
|
||||
window.ModelResolverDialog = this.dialog;
|
||||
}
|
||||
|
||||
// Register keyboard shortcut (Ctrl+Shift+L)
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'l') {
|
||||
e.preventDefault();
|
||||
this.openLinkerManager();
|
||||
this.openResolverManager();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -56,7 +56,7 @@ export class ModelLinker {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.sidebarRegistered || window.__modelLinkerSidebarRegistered) {
|
||||
if (this.sidebarRegistered || window.__ModelResolverSidebarRegistered) {
|
||||
this.sidebarRegistered = true;
|
||||
return true;
|
||||
}
|
||||
@@ -65,29 +65,29 @@ export class ModelLinker {
|
||||
registerSidebarTab.call(app.extensionManager, {
|
||||
id: this.sidebarTabId,
|
||||
icon: "mdi mdi-link-variant",
|
||||
title: "Model Linker",
|
||||
title: "Model Resolver",
|
||||
tooltip: this.openTooltip,
|
||||
type: "custom",
|
||||
render: (element) => this.renderSidebarPanel(element),
|
||||
});
|
||||
this.sidebarRegistered = true;
|
||||
window.__modelLinkerSidebarRegistered = true;
|
||||
window.__ModelResolverSidebarRegistered = true;
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.warn("Model Linker: Sidebar tab registration failed, falling back to top menu button.", error);
|
||||
console.warn("Model Resolver: Sidebar tab registration failed, falling back to top menu button.", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
attachSidebarButtonToggleHandler() {
|
||||
window.__modelLinkerSidebarToggleOwner = this;
|
||||
if (window.__modelLinkerSidebarToggleHandlerAttached) return;
|
||||
window.__ModelResolverSidebarToggleOwner = this;
|
||||
if (window.__ModelResolverSidebarToggleHandlerAttached) return;
|
||||
|
||||
window.__modelLinkerSidebarToggleHandlerAttached = true;
|
||||
window.__modelLinkerSidebarToggleHandler = (event) => {
|
||||
window.__modelLinkerSidebarToggleOwner?.handleSidebarButtonClick(event);
|
||||
window.__ModelResolverSidebarToggleHandlerAttached = true;
|
||||
window.__ModelResolverSidebarToggleHandler = (event) => {
|
||||
window.__ModelResolverSidebarToggleOwner?.handleSidebarButtonClick(event);
|
||||
};
|
||||
document.addEventListener('click', window.__modelLinkerSidebarToggleHandler, true);
|
||||
document.addEventListener('click', window.__ModelResolverSidebarToggleHandler, true);
|
||||
}
|
||||
|
||||
handleSidebarButtonClick(event) {
|
||||
@@ -127,11 +127,11 @@ export class ModelLinker {
|
||||
|
||||
renderSidebarPanel(element) {
|
||||
element.style.height = "100%";
|
||||
element.classList.add("ml-sidebar-dock-panel");
|
||||
element.classList.add("mr-sidebar-dock-panel");
|
||||
|
||||
if (!this.dialog) {
|
||||
this.dialog = new LinkerManagerDialog();
|
||||
window.modelLinkerDialog = this.dialog;
|
||||
this.dialog = new ResolverManagerDialog();
|
||||
window.ModelResolverDialog = this.dialog;
|
||||
}
|
||||
|
||||
if (this.dialog.shouldOpenFromSidebarFloating()) {
|
||||
@@ -141,7 +141,7 @@ export class ModelLinker {
|
||||
return;
|
||||
}
|
||||
|
||||
this.openLinkerManager({
|
||||
this.openResolverManager({
|
||||
forceFloating: true,
|
||||
closeSidebarContainer: element,
|
||||
});
|
||||
@@ -149,7 +149,7 @@ export class ModelLinker {
|
||||
}
|
||||
|
||||
element.replaceChildren();
|
||||
this.openLinkerManager({ dockContainer: element });
|
||||
this.openResolverManager({ dockContainer: element });
|
||||
}
|
||||
|
||||
async registerTopbarButton() {
|
||||
@@ -159,24 +159,24 @@ export class ModelLinker {
|
||||
const { ComfyButtonGroup } = await import("../../../../scripts/ui/components/buttonGroup.js");
|
||||
const { ComfyButton } = await import("../../../../scripts/ui/components/button.js");
|
||||
|
||||
// Create button group with Model Linker button
|
||||
const modelLinkerButton = new ComfyButton({
|
||||
// Create button group with Model Resolver button
|
||||
const ModelResolverButton = new ComfyButton({
|
||||
icon: "link-variant",
|
||||
action: () => this.openLinkerManager(),
|
||||
content: "Model Linker",
|
||||
action: () => this.openResolverManager(),
|
||||
content: "Model Resolver",
|
||||
classList: "comfyui-button comfyui-menu-mobile-collapse"
|
||||
}).element;
|
||||
modelLinkerButton.id = this.buttonId;
|
||||
this.dialog.setTooltip(modelLinkerButton, this.openTooltip);
|
||||
ModelResolverButton.id = this.buttonId;
|
||||
this.dialog.setTooltip(ModelResolverButton, this.openTooltip);
|
||||
this.buttonGroup = new ComfyButtonGroup(
|
||||
modelLinkerButton
|
||||
ModelResolverButton
|
||||
);
|
||||
|
||||
// Insert before settings group in the menu
|
||||
app.menu?.settingsGroup.element.before(this.buttonGroup.element);
|
||||
} catch (e) {
|
||||
// Fallback for older ComfyUI versions without the new button system
|
||||
console.log('Model Linker: New button system not available, using floating button fallback.');
|
||||
console.log('Model Resolver: New button system not available, using floating button fallback.');
|
||||
this.createFloatingButton();
|
||||
}
|
||||
}
|
||||
@@ -188,56 +188,56 @@ export class ModelLinker {
|
||||
// Watch for ComfyUI's Missing Models popup and inject our button
|
||||
this.setupMissingModelsPopupObserver();
|
||||
|
||||
console.log('Model Linker: Missing models popup button injection enabled');
|
||||
console.log('Model Resolver: Missing models popup button injection enabled');
|
||||
}
|
||||
|
||||
setupActiveWorkflowChangeListeners() {
|
||||
if (window.__modelLinkerWorkflowChangeHandlers) {
|
||||
for (const { target, event, handler } of window.__modelLinkerWorkflowChangeHandlers) {
|
||||
if (window.__ModelResolverWorkflowChangeHandlers) {
|
||||
for (const { target, event, handler } of window.__ModelResolverWorkflowChangeHandlers) {
|
||||
target?.removeEventListener?.(event, handler);
|
||||
}
|
||||
}
|
||||
|
||||
if (!window.__modelLinkerHistoryPatched) {
|
||||
if (!window.__ModelResolverHistoryPatched) {
|
||||
const originalPushState = history.pushState;
|
||||
const originalReplaceState = history.replaceState;
|
||||
history.pushState = function(...args) {
|
||||
const result = originalPushState.apply(this, args);
|
||||
window.dispatchEvent(new Event('model-linker-locationchange'));
|
||||
window.dispatchEvent(new Event('model-resolver-locationchange'));
|
||||
return result;
|
||||
};
|
||||
history.replaceState = function(...args) {
|
||||
const result = originalReplaceState.apply(this, args);
|
||||
window.dispatchEvent(new Event('model-linker-locationchange'));
|
||||
window.dispatchEvent(new Event('model-resolver-locationchange'));
|
||||
return result;
|
||||
};
|
||||
window.__modelLinkerHistoryPatched = true;
|
||||
window.__ModelResolverHistoryPatched = true;
|
||||
}
|
||||
|
||||
window.__modelLinkerWorkflowChangeOwner = this;
|
||||
window.__ModelResolverWorkflowChangeOwner = this;
|
||||
|
||||
const routeHandler = () => {
|
||||
window.__modelLinkerWorkflowChangeOwner?.handleActiveWorkflowRouteChange('route-change');
|
||||
window.__ModelResolverWorkflowChangeOwner?.handleActiveWorkflowRouteChange('route-change');
|
||||
};
|
||||
const focusHandler = () => {
|
||||
window.__modelLinkerWorkflowChangeOwner?.handleActiveWorkflowRouteChange('window-focus');
|
||||
window.__ModelResolverWorkflowChangeOwner?.handleActiveWorkflowRouteChange('window-focus');
|
||||
};
|
||||
const visibilityHandler = () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
window.__modelLinkerWorkflowChangeOwner?.handleActiveWorkflowRouteChange('visibility-change');
|
||||
window.__ModelResolverWorkflowChangeOwner?.handleActiveWorkflowRouteChange('visibility-change');
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('hashchange', routeHandler);
|
||||
window.addEventListener('popstate', routeHandler);
|
||||
window.addEventListener('model-linker-locationchange', routeHandler);
|
||||
window.addEventListener('model-resolver-locationchange', routeHandler);
|
||||
window.addEventListener('focus', focusHandler);
|
||||
document.addEventListener('visibilitychange', visibilityHandler);
|
||||
|
||||
window.__modelLinkerWorkflowChangeHandlers = [
|
||||
window.__ModelResolverWorkflowChangeHandlers = [
|
||||
{ target: window, event: 'hashchange', handler: routeHandler },
|
||||
{ target: window, event: 'popstate', handler: routeHandler },
|
||||
{ target: window, event: 'model-linker-locationchange', handler: routeHandler },
|
||||
{ target: window, event: 'model-resolver-locationchange', handler: routeHandler },
|
||||
{ target: window, event: 'focus', handler: focusHandler },
|
||||
{ target: document, event: 'visibilitychange', handler: visibilityHandler }
|
||||
];
|
||||
@@ -294,16 +294,16 @@ export class ModelLinker {
|
||||
if (!dialog) return;
|
||||
|
||||
// Check if we already injected buttons
|
||||
if (dialog.querySelector('#model-linker-btn-container')) return;
|
||||
if (dialog.querySelector('#model-resolver-btn-container')) return;
|
||||
|
||||
// Find a suitable place to inject the button
|
||||
const injectButtons = () => {
|
||||
// Auto-resolve button (green)
|
||||
const autoResolveBtn = document.createElement('button');
|
||||
autoResolveBtn.id = 'model-linker-btn-container'; // Use this ID to prevent duplicate injection
|
||||
autoResolveBtn.className = 'ml-popup-auto-resolve-btn';
|
||||
autoResolveBtn.id = 'model-resolver-btn-container'; // Use this ID to prevent duplicate injection
|
||||
autoResolveBtn.className = 'mr-popup-auto-resolve-btn';
|
||||
autoResolveBtn.textContent = '🔗 Auto-resolve 100%';
|
||||
this.dialog?.setTooltip(autoResolveBtn, 'Link every missing model that already has an exact local match, then open Model Linker for the rest.');
|
||||
this.dialog?.setTooltip(autoResolveBtn, 'Link every missing model that already has an exact local match, then open Model Resolver for the rest.');
|
||||
autoResolveBtn.addEventListener('click', async () => {
|
||||
await this.handleAutoResolveInPopup(dialog, autoResolveBtn);
|
||||
});
|
||||
@@ -314,7 +314,7 @@ export class ModelLinker {
|
||||
const checkboxRow = checkbox.closest('label') || checkbox.parentElement;
|
||||
if (checkboxRow && checkboxRow.parentElement) {
|
||||
// Make the parent a flex container to align checkbox and button
|
||||
checkboxRow.parentElement.classList.add('ml-popup-inline-actions');
|
||||
checkboxRow.parentElement.classList.add('mr-popup-inline-actions');
|
||||
// Insert button at the beginning (left side)
|
||||
checkboxRow.parentElement.insertBefore(autoResolveBtn, checkboxRow);
|
||||
return;
|
||||
@@ -329,7 +329,7 @@ export class ModelLinker {
|
||||
if (modelList) {
|
||||
// Create a wrapper and insert before the model list
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'ml-popup-actions-wrap';
|
||||
wrapper.className = 'mr-popup-actions-wrap';
|
||||
wrapper.appendChild(autoResolveBtn);
|
||||
modelList.parentElement?.insertBefore(wrapper, modelList);
|
||||
} else {
|
||||
@@ -344,7 +344,7 @@ export class ModelLinker {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Model Linker: Injected buttons into Missing Models popup');
|
||||
console.log('Model Resolver: Injected buttons into Missing Models popup');
|
||||
};
|
||||
|
||||
// Small delay to ensure popup is fully rendered
|
||||
@@ -352,7 +352,7 @@ export class ModelLinker {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle auto-resolve in the popup - resolve 100% matches and open Model Linker for remaining
|
||||
* Handle auto-resolve in the popup - resolve 100% matches and open Model Resolver for remaining
|
||||
*/
|
||||
async handleAutoResolveInPopup(dialog, button) {
|
||||
button.textContent = '⏳ Resolving...';
|
||||
@@ -373,14 +373,14 @@ export class ModelLinker {
|
||||
|
||||
// Create dialog if needed
|
||||
if (!this.dialog) {
|
||||
this.dialog = new LinkerManagerDialog();
|
||||
window.modelLinkerDialog = this.dialog;
|
||||
this.dialog = new ResolverManagerDialog();
|
||||
window.ModelResolverDialog = this.dialog;
|
||||
}
|
||||
|
||||
// Run auto-resolve for 100% matches - returns the updated workflow
|
||||
const updatedWorkflow = await this.dialog.autoResolve100Percent();
|
||||
|
||||
// Always open Model Linker to show remaining unresolved models
|
||||
// Always open Model Resolver to show remaining unresolved models
|
||||
// Pass the updated workflow if available to avoid race condition
|
||||
this.dialog.show(updatedWorkflow || null);
|
||||
}
|
||||
@@ -389,7 +389,7 @@ export class ModelLinker {
|
||||
* Mark resolved model items in the popup as linked (green) and hide download buttons
|
||||
*/
|
||||
removeResolvedFromPopup(dialog, resolvedFilenames) {
|
||||
console.log('Model Linker: Looking for resolved filenames:', resolvedFilenames);
|
||||
console.log('Model Resolver: Looking for resolved filenames:', resolvedFilenames);
|
||||
|
||||
// Strategy: For each filename, find text nodes containing it,
|
||||
// then find the nearest Download button and mark that row
|
||||
@@ -413,10 +413,10 @@ export class ModelLinker {
|
||||
// Look for Download button at this level
|
||||
const downloadBtn = Array.from(parent.querySelectorAll('button'))
|
||||
.find(btn => btn.textContent?.includes('Download') &&
|
||||
!btn.id?.includes('model-linker'));
|
||||
!btn.id?.includes('model-resolver'));
|
||||
|
||||
if (downloadBtn) {
|
||||
console.log('Model Linker: Found entry for', filename);
|
||||
console.log('Model Resolver: Found entry for', filename);
|
||||
this.markEntryAsResolved(parent, downloadBtn);
|
||||
break;
|
||||
}
|
||||
@@ -440,16 +440,16 @@ export class ModelLinker {
|
||||
if (container.dataset.resolved === 'true') return;
|
||||
container.dataset.resolved = 'true';
|
||||
|
||||
console.log('Model Linker: Marking entry as resolved', container);
|
||||
console.log('Model Resolver: Marking entry as resolved', container);
|
||||
|
||||
// Add green background/styling to the container
|
||||
container.classList.add('ml-resolved-entry');
|
||||
container.classList.add('mr-resolved-entry');
|
||||
|
||||
// Hide the Download button and replace with badge
|
||||
if (downloadBtn) {
|
||||
// Create badge
|
||||
const badge = document.createElement('span');
|
||||
badge.className = 'ml-resolved-badge';
|
||||
badge.className = 'mr-resolved-badge';
|
||||
badge.textContent = '✓ Linked';
|
||||
|
||||
// Replace download button with badge
|
||||
@@ -474,7 +474,7 @@ export class ModelLinker {
|
||||
const downloadButtons = dialog.querySelectorAll('button');
|
||||
let count = 0;
|
||||
for (const btn of downloadButtons) {
|
||||
if (btn.textContent?.includes('Download') && !btn.id?.includes('model-linker')) {
|
||||
if (btn.textContent?.includes('Download') && !btn.id?.includes('model-resolver')) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
@@ -487,7 +487,7 @@ export class ModelLinker {
|
||||
*/
|
||||
updateNodesDirectly(resolutions) {
|
||||
if (!app?.graph) {
|
||||
console.warn('Model Linker: Cannot update nodes - graph not available');
|
||||
console.warn('Model Resolver: Cannot update nodes - graph not available');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -499,7 +499,7 @@ export class ModelLinker {
|
||||
// Find the node in the graph
|
||||
const node = app.graph.getNodeById(nodeId);
|
||||
if (!node) {
|
||||
console.warn(`Model Linker: Node ${nodeId} not found in graph`);
|
||||
console.warn(`Model Resolver: Node ${nodeId} not found in graph`);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -513,11 +513,11 @@ export class ModelLinker {
|
||||
widget.callback(resolvedPath, app.graph, node, null, null);
|
||||
}
|
||||
|
||||
console.log(`Model Linker: Updated node ${nodeId} widget ${widgetIndex} to ${resolvedPath}`);
|
||||
console.log(`Model Resolver: Updated node ${nodeId} widget ${widgetIndex} to ${resolvedPath}`);
|
||||
} else if (node.widgets_values) {
|
||||
// Fallback: update widgets_values array directly
|
||||
node.widgets_values[widgetIndex] = resolvedPath;
|
||||
console.log(`Model Linker: Updated node ${nodeId} widgets_values[${widgetIndex}] to ${resolvedPath}`);
|
||||
console.log(`Model Resolver: Updated node ${nodeId} widgets_values[${widgetIndex}] to ${resolvedPath}`);
|
||||
}
|
||||
|
||||
// Mark node as dirty to trigger redraw
|
||||
@@ -536,14 +536,14 @@ export class ModelLinker {
|
||||
* Check if auto-open is enabled in user settings
|
||||
*/
|
||||
isAutoOpenEnabled() {
|
||||
return localStorage.getItem('modelLinker.autoOpenOnMissing') !== 'false';
|
||||
return localStorage.getItem('ModelResolver.autoOpenOnMissing') !== 'false';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set auto-open preference
|
||||
*/
|
||||
setAutoOpenEnabled(enabled) {
|
||||
localStorage.setItem('modelLinker.autoOpenOnMissing', enabled ? 'true' : 'false');
|
||||
localStorage.setItem('ModelResolver.autoOpenOnMissing', enabled ? 'true' : 'false');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -582,14 +582,14 @@ export class ModelLinker {
|
||||
this.lastCheckedWorkflow = workflowHash;
|
||||
|
||||
// Call analyze endpoint to check for missing models
|
||||
const response = await api.fetchApi('/model_linker/analyze', {
|
||||
const response = await api.fetchApi('/model_resolver/analyze', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ workflow })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.warn('Model Linker: Failed to analyze workflow for missing models');
|
||||
console.warn('Model Resolver: Failed to analyze workflow for missing models');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -597,12 +597,12 @@ export class ModelLinker {
|
||||
|
||||
// Auto-open dialog if there are missing models
|
||||
if (data.total_missing > 0) {
|
||||
console.log(`Model Linker: Found ${data.total_missing} missing model(s), opening dialog...`);
|
||||
this.openLinkerManager();
|
||||
console.log(`Model Resolver: Found ${data.total_missing} missing model(s), opening dialog...`);
|
||||
this.openResolverManager();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Model Linker: Error checking for missing models:', error);
|
||||
console.error('Model Resolver: Error checking for missing models:', error);
|
||||
} finally {
|
||||
this.isCheckingMissing = false;
|
||||
}
|
||||
@@ -617,7 +617,7 @@ export class ModelLinker {
|
||||
|
||||
document.querySelectorAll('.comfyui-button-group button.comfyui-button').forEach((button) => {
|
||||
const label = button.querySelector('span')?.textContent?.trim() || button.textContent?.trim();
|
||||
if (label === 'Model Linker') {
|
||||
if (label === 'Model Resolver') {
|
||||
button.closest('.comfyui-button-group')?.remove();
|
||||
}
|
||||
});
|
||||
@@ -629,32 +629,32 @@ export class ModelLinker {
|
||||
}
|
||||
|
||||
// Also remove the stored reference if it exists
|
||||
if (this.linkerButton && this.linkerButton.parentNode) {
|
||||
this.linkerButton.remove();
|
||||
this.linkerButton = null;
|
||||
if (this.resolverButton && this.resolverButton.parentNode) {
|
||||
this.resolverButton.remove();
|
||||
this.resolverButton = null;
|
||||
}
|
||||
}
|
||||
|
||||
createFloatingButton() {
|
||||
// Create a floating button as fallback for legacy ComfyUI versions
|
||||
this.linkerButton = $el("button", {
|
||||
this.resolverButton = $el("button", {
|
||||
id: this.buttonId,
|
||||
textContent: "🔗 Model Linker",
|
||||
textContent: "🔗 Model Resolver",
|
||||
onclick: () => {
|
||||
this.openLinkerManager();
|
||||
this.openResolverManager();
|
||||
},
|
||||
className: "model-linker-floating-button"
|
||||
className: "model-resolver-floating-button"
|
||||
});
|
||||
|
||||
document.body.appendChild(this.linkerButton);
|
||||
this.dialog?.setTooltip(this.linkerButton, "Open Model Linker to find or download missing workflow models. Shortcut: Ctrl+Shift+L.");
|
||||
document.body.appendChild(this.resolverButton);
|
||||
this.dialog?.setTooltip(this.resolverButton, "Open Model Resolver to find or download missing workflow models. Shortcut: Ctrl+Shift+L.");
|
||||
}
|
||||
|
||||
async openLinkerManager(options = {}) {
|
||||
async openResolverManager(options = {}) {
|
||||
try {
|
||||
if (!this.dialog) {
|
||||
this.dialog = new LinkerManagerDialog();
|
||||
window.modelLinkerDialog = this.dialog;
|
||||
this.dialog = new ResolverManagerDialog();
|
||||
window.ModelResolverDialog = this.dialog;
|
||||
}
|
||||
if (options.dockContainer && !options.forceFloating) {
|
||||
await this.dialog.showDocked(options.dockContainer, options.workflow || null);
|
||||
@@ -670,16 +670,16 @@ export class ModelLinker {
|
||||
|
||||
await showPromise;
|
||||
} catch (error) {
|
||||
console.error("🔗 Model Linker: Error creating/showing dialog:", error);
|
||||
alert("Error opening Model Linker: " + error.message);
|
||||
console.error("🔗 Model Resolver: Error creating/showing dialog:", error);
|
||||
alert("Error opening Model Resolver: " + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
static switchFilter(filter) {
|
||||
document.querySelectorAll('.ml-btn-filter').forEach(b => b.classList.remove('active'));
|
||||
document.querySelectorAll('.mr-btn-filter').forEach(b => b.classList.remove('active'));
|
||||
document.getElementById('filter-' + filter).classList.add('active');
|
||||
|
||||
document.querySelectorAll('.ml-model-section').forEach(s => {
|
||||
document.querySelectorAll('.mr-model-section').forEach(s => {
|
||||
const hasActive = s.dataset.mlActive === 'true';
|
||||
const hasInactive = s.dataset.mlInactive === 'true';
|
||||
|
||||
@@ -692,7 +692,7 @@ export class ModelLinker {
|
||||
}
|
||||
});
|
||||
|
||||
const copySection = document.querySelector('[id^="ml-copy-"]');
|
||||
const copySection = document.querySelector('[id^="mr-copy-"]');
|
||||
if (copySection) {
|
||||
const codeEl = copySection.querySelector('code');
|
||||
const labelEl = copySection.querySelector('div');
|
||||
@@ -1,4 +1,4 @@
|
||||
import { $el, ComfyDialog } from "../../../../scripts/ui.js";
|
||||
import { $el, ComfyDialog } from "../../../../scripts/ui.js";
|
||||
import { dialogShellMethods } from "./shell/dialog_shell_methods.js";
|
||||
import { lifecycleGraphMethods } from "./shell/lifecycle_graph_methods.js";
|
||||
import { workflowStateMethods } from "./shell/workflow_state_methods.js";
|
||||
@@ -13,7 +13,7 @@ import { queueMethods } from "./actions/queue_methods.js";
|
||||
import { resolveDownloadMethods } from "./actions/resolve_download_methods.js";
|
||||
import { selectionMethods } from "./actions/selection_methods.js";
|
||||
import { renderFormatMethods } from "./utils/render_format_methods.js";
|
||||
export class LinkerManagerDialog extends ComfyDialog {
|
||||
export class ResolverManagerDialog extends ComfyDialog {
|
||||
constructor() {
|
||||
super();
|
||||
this.currentWorkflow = null;
|
||||
@@ -39,7 +39,7 @@ export class LinkerManagerDialog extends ComfyDialog {
|
||||
this.activeFooterMenu = null;
|
||||
this.batchSearchRunning = false;
|
||||
this.boundHandleOutsideClick = this.handleOutsideClick.bind(this);
|
||||
this.activeTabStorageKey = 'model_linker_active_tab';
|
||||
this.activeTabStorageKey = 'model_resolver_active_tab';
|
||||
this.activeTab = this.restoreActiveTab(); // Default tab
|
||||
this.fullscreen = false;
|
||||
this.returnToDockedAfterFullscreen = false;
|
||||
@@ -47,9 +47,9 @@ export class LinkerManagerDialog extends ComfyDialog {
|
||||
this.dockContainer = null;
|
||||
this.lastDockContainer = null;
|
||||
this.pendingDockToSidebar = false;
|
||||
this.sidebarTabId = "comfyui-model-linker";
|
||||
this.sidebarOpenModeStorageKey = "model_linker_sidebar_open_mode";
|
||||
this.missingBrowserSplitStorageKey = "model_linker_missing_browser_detail_w";
|
||||
this.sidebarTabId = "comfyui-model-resolver";
|
||||
this.sidebarOpenModeStorageKey = "model_resolver_sidebar_open_mode";
|
||||
this.missingBrowserSplitStorageKey = "model_resolver_missing_browser_detail_w";
|
||||
this.dockButton = null;
|
||||
this.undockButton = null;
|
||||
this._floatingRectBeforeDock = null;
|
||||
@@ -66,44 +66,44 @@ export class LinkerManagerDialog extends ComfyDialog {
|
||||
this._boundHandleViewportResize = () => this.scheduleModalViewportClamp(true);
|
||||
|
||||
// Create backdrop overlay for click-outside-to-close
|
||||
this.backdrop = $el("div.model-linker-backdrop", {
|
||||
this.backdrop = $el("div.model-resolver-backdrop", {
|
||||
parent: document.body
|
||||
});
|
||||
|
||||
// Create context menu for model chips
|
||||
this.contextMenu = $el("div.ml-context-menu", {
|
||||
this.contextMenu = $el("div.mr-context-menu", {
|
||||
parent: document.body
|
||||
}, [
|
||||
$el("div.ml-context-menu-item", {
|
||||
$el("div.mr-context-menu-item", {
|
||||
onclick: () => this.handleContextMenuAction('showInfo')
|
||||
}, [
|
||||
$el("span.ml-context-menu-item-icon", { textContent: "ℹ" }),
|
||||
$el("span.mr-context-menu-item-icon", { textContent: "ℹ" }),
|
||||
$el("span", { textContent: "Show Info" })
|
||||
]),
|
||||
$el("div.ml-context-menu-divider"),
|
||||
$el("div.ml-context-menu-item", {
|
||||
$el("div.mr-context-menu-divider"),
|
||||
$el("div.mr-context-menu-item", {
|
||||
onclick: () => this.handleContextMenuAction('civitai')
|
||||
}, [
|
||||
$el("span.ml-context-menu-item-icon", { textContent: "🌐" }),
|
||||
$el("span.mr-context-menu-item-icon", { textContent: "🌐" }),
|
||||
$el("span", { textContent: "Open in CivitAI" })
|
||||
]),
|
||||
$el("div.ml-context-menu-divider"),
|
||||
$el("div.ml-context-menu-item", {
|
||||
$el("div.mr-context-menu-divider"),
|
||||
$el("div.mr-context-menu-item", {
|
||||
onclick: () => this.handleContextMenuAction('openFolder')
|
||||
}, [
|
||||
$el("span.ml-context-menu-item-icon", { textContent: "📁" }),
|
||||
$el("span.mr-context-menu-item-icon", { textContent: "📁" }),
|
||||
$el("span", { textContent: "Open Containing Folder" })
|
||||
])
|
||||
]);
|
||||
|
||||
this.tooltipElement = $el("div.ml-global-tooltip", { parent: document.body });
|
||||
this.tooltipElement = $el("div.mr-global-tooltip", { parent: document.body });
|
||||
|
||||
// Selected model for context menu
|
||||
this._contextMenuModel = null;
|
||||
|
||||
// Create dialog element using $el
|
||||
this.element = $el("div.comfy-modal.model-linker-modal", {
|
||||
id: "model-linker-modal",
|
||||
this.element = $el("div.comfy-modal.model-resolver-modal", {
|
||||
id: "model-resolver-modal",
|
||||
parent: document.body
|
||||
}, [
|
||||
this.createHeader(),
|
||||
@@ -140,7 +140,7 @@ function applyDialogMethods(...sources) {
|
||||
for (const key of Reflect.ownKeys(source)) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(source, key);
|
||||
descriptor.enumerable = false;
|
||||
Object.defineProperty(LinkerManagerDialog.prototype, key, descriptor);
|
||||
Object.defineProperty(ResolverManagerDialog.prototype, key, descriptor);
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
-34
@@ -1,4 +1,4 @@
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { api } from "../../../../../scripts/api.js";
|
||||
import { $el } from "../../../../../scripts/ui.js";
|
||||
import { getSvgIcon } from "../../utils/icon_utils.js";
|
||||
@@ -9,7 +9,7 @@ export const downloadTargetMethods = {
|
||||
async ensureAllModelsLoaded() {
|
||||
if (this.allModels && this.allModels.length) return;
|
||||
try {
|
||||
const resp = await api.fetchApi('/model_linker/models');
|
||||
const resp = await api.fetchApi('/model_resolver/models');
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||||
const models = await resp.json();
|
||||
const list = Array.isArray(models) ? models : [];
|
||||
@@ -19,7 +19,7 @@ export const downloadTargetMethods = {
|
||||
__label: `${m.category ? m.category + ': ' : ''}${m.relative_path || m.filename || ''}`
|
||||
})).sort((a, b) => (a.__label || '').localeCompare(b.__label || ''));
|
||||
} catch (e) {
|
||||
console.warn('Model Linker: could not load all models', e);
|
||||
console.warn('Model Resolver: could not load all models', e);
|
||||
this.allModels = [];
|
||||
}
|
||||
},
|
||||
@@ -27,12 +27,12 @@ export const downloadTargetMethods = {
|
||||
async ensureDownloadDirectoriesLoaded() {
|
||||
if (this.downloadDirectories) return;
|
||||
try {
|
||||
const resp = await api.fetchApi('/model_linker/directories');
|
||||
const resp = await api.fetchApi('/model_resolver/directories');
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||||
const directories = await resp.json();
|
||||
this.downloadDirectories = directories && typeof directories === 'object' ? directories : {};
|
||||
} catch (e) {
|
||||
console.warn('Model Linker: could not load download directories', e);
|
||||
console.warn('Model Resolver: could not load download directories', e);
|
||||
this.downloadDirectories = {};
|
||||
}
|
||||
},
|
||||
@@ -40,12 +40,12 @@ export const downloadTargetMethods = {
|
||||
async ensureCapabilitiesLoaded() {
|
||||
if (this.capabilities) return;
|
||||
try {
|
||||
const resp = await api.fetchApi('/model_linker/capabilities');
|
||||
const resp = await api.fetchApi('/model_resolver/capabilities');
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||||
const data = await resp.json();
|
||||
this.capabilities = data && typeof data === 'object' ? data : { sources: {} };
|
||||
} catch (e) {
|
||||
console.warn('Model Linker: could not load capabilities', e);
|
||||
console.warn('Model Resolver: could not load capabilities', e);
|
||||
this.capabilities = { sources: {} };
|
||||
}
|
||||
},
|
||||
@@ -141,7 +141,7 @@ export const downloadTargetMethods = {
|
||||
guider: 'guider',
|
||||
guiders: 'guider'
|
||||
};
|
||||
return `ml-type-chip ml-type-chip--${colorNames[token] || 'generic'}`;
|
||||
return `mr-type-chip mr-type-chip--${colorNames[token] || 'generic'}`;
|
||||
},
|
||||
|
||||
getCategoryTokenName(category = '') {
|
||||
@@ -282,14 +282,14 @@ export const downloadTargetMethods = {
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await api.fetchApi(`/model_linker/subfolders/${encodeURIComponent(key)}`);
|
||||
const resp = await api.fetchApi(`/model_resolver/subfolders/${encodeURIComponent(key)}`);
|
||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
||||
const subfolders = await resp.json();
|
||||
const list = Array.isArray(subfolders) ? subfolders : [];
|
||||
this.downloadSubfolders.set(key, list);
|
||||
return list;
|
||||
} catch (e) {
|
||||
console.warn(`Model Linker: could not load subfolders for ${key}`, e);
|
||||
console.warn(`Model Resolver: could not load subfolders for ${key}`, e);
|
||||
this.downloadSubfolders.set(key, []);
|
||||
return [];
|
||||
}
|
||||
@@ -302,17 +302,17 @@ export const downloadTargetMethods = {
|
||||
const subfolderListId = `download-subfolder-list-${missing.node_id}-${missing.widget_index}`;
|
||||
const selectedCategory = defaultCategory || 'checkpoints';
|
||||
|
||||
let html = `<div class="ml-download-target">`;
|
||||
html += `<div class="ml-download-target-grid">`;
|
||||
html += `<label class="ml-download-target-label" for="${selectId}">Folder</label>`;
|
||||
html += `<label class="ml-download-target-label" for="${subfolderId}">Subfolder (optional)</label>`;
|
||||
html += `<div class="ml-download-target-wrap">`;
|
||||
html += `<input id="${selectId}" class="ml-download-target-input ml-download-target-select" type="text" readonly autocomplete="off" data-value="${this.escapeHtml(selectedCategory)}" value="${this.escapeHtml(this.getCategoryDisplayName(selectedCategory))}">`;
|
||||
html += `<div id="${categoryListId}" class="ml-download-target-list"></div>`;
|
||||
let html = `<div class="mr-download-target">`;
|
||||
html += `<div class="mr-download-target-grid">`;
|
||||
html += `<label class="mr-download-target-label" for="${selectId}">Folder</label>`;
|
||||
html += `<label class="mr-download-target-label" for="${subfolderId}">Subfolder (optional)</label>`;
|
||||
html += `<div class="mr-download-target-wrap">`;
|
||||
html += `<input id="${selectId}" class="mr-download-target-input mr-download-target-select" type="text" readonly autocomplete="off" data-value="${this.escapeHtml(selectedCategory)}" value="${this.escapeHtml(this.getCategoryDisplayName(selectedCategory))}">`;
|
||||
html += `<div id="${categoryListId}" class="mr-download-target-list"></div>`;
|
||||
html += `</div>`;
|
||||
html += `<div class="ml-download-target-wrap">`;
|
||||
html += `<input id="${subfolderId}" class="ml-download-target-input" type="text" placeholder="e.g. ponyxl\\styles" autocomplete="off">`;
|
||||
html += `<div id="${subfolderListId}" class="ml-download-target-list"></div>`;
|
||||
html += `<div class="mr-download-target-wrap">`;
|
||||
html += `<input id="${subfolderId}" class="mr-download-target-input" type="text" placeholder="e.g. ponyxl\\styles" autocomplete="off">`;
|
||||
html += `<div id="${subfolderListId}" class="mr-download-target-list"></div>`;
|
||||
html += `</div>`;
|
||||
html += `</div>`;
|
||||
html += `</div>`;
|
||||
@@ -384,13 +384,13 @@ export const downloadTargetMethods = {
|
||||
.map(option => {
|
||||
const value = String(option.value || '');
|
||||
const label = String(option.label || value);
|
||||
return `<div class="ml-download-target-option" data-value="${encodeURIComponent(value)}" data-label="${encodeURIComponent(label)}">${this.escapeHtml(label)}</div>`;
|
||||
return `<div class="mr-download-target-option" data-value="${encodeURIComponent(value)}" data-label="${encodeURIComponent(label)}">${this.escapeHtml(label)}</div>`;
|
||||
})
|
||||
.join('');
|
||||
|
||||
targetEl.style.display = 'block';
|
||||
|
||||
targetEl.querySelectorAll('.ml-download-target-option').forEach(option => {
|
||||
targetEl.querySelectorAll('.mr-download-target-option').forEach(option => {
|
||||
option.addEventListener('mousedown', (event) => {
|
||||
event.preventDefault();
|
||||
const value = decodeURIComponent(option.dataset.value || '');
|
||||
@@ -463,22 +463,22 @@ export const downloadTargetMethods = {
|
||||
},
|
||||
|
||||
getStoredTokens() {
|
||||
const civitaiCandidateLimitRaw = parseInt(localStorage.getItem('modelLinker.civitaiCandidateLimit') || '5', 10);
|
||||
const civitaiCandidateLimitRaw = parseInt(localStorage.getItem('ModelResolver.civitaiCandidateLimit') || '5', 10);
|
||||
const civitai_candidate_limit = Number.isFinite(civitaiCandidateLimitRaw)
|
||||
? Math.min(20, Math.max(1, civitaiCandidateLimitRaw))
|
||||
: 5;
|
||||
const search_source_enabled = this.getSearchSourceEnabledMap();
|
||||
|
||||
return {
|
||||
civitai_key: localStorage.getItem('modelLinker.civitaiApiKey') || '',
|
||||
civitai_session_token: localStorage.getItem('modelLinker.civitaiSessionToken') || '',
|
||||
hf_token: localStorage.getItem('modelLinker.huggingFaceToken') || '',
|
||||
brave_search_api_key: localStorage.getItem('modelLinker.braveSearchApiKey') || '',
|
||||
civitai_use_trpc_search: localStorage.getItem('modelLinker.civitaiUseTrpcSearch') !== 'false',
|
||||
civitai_use_html_fallback: localStorage.getItem('modelLinker.civitaiUseHtmlFallback') !== 'false',
|
||||
hf_use_api_search: localStorage.getItem('modelLinker.hfUseApiSearch') !== 'false',
|
||||
hf_use_comfy_org_fallback: localStorage.getItem('modelLinker.hfUseComfyOrgFallback') !== 'false',
|
||||
hf_use_brave_fallback: localStorage.getItem('modelLinker.hfUseBraveFallback') !== 'false',
|
||||
civitai_key: localStorage.getItem('ModelResolver.civitaiApiKey') || '',
|
||||
civitai_session_token: localStorage.getItem('ModelResolver.civitaiSessionToken') || '',
|
||||
hf_token: localStorage.getItem('ModelResolver.huggingFaceToken') || '',
|
||||
brave_search_api_key: localStorage.getItem('ModelResolver.braveSearchApiKey') || '',
|
||||
civitai_use_trpc_search: localStorage.getItem('ModelResolver.civitaiUseTrpcSearch') !== 'false',
|
||||
civitai_use_html_fallback: localStorage.getItem('ModelResolver.civitaiUseHtmlFallback') !== 'false',
|
||||
hf_use_api_search: localStorage.getItem('ModelResolver.hfUseApiSearch') !== 'false',
|
||||
hf_use_comfy_org_fallback: localStorage.getItem('ModelResolver.hfUseComfyOrgFallback') !== 'false',
|
||||
hf_use_brave_fallback: localStorage.getItem('ModelResolver.hfUseBraveFallback') !== 'false',
|
||||
civitai_candidate_limit,
|
||||
search_source_enabled
|
||||
};
|
||||
@@ -495,14 +495,14 @@ export const downloadTargetMethods = {
|
||||
this.workflowSearchResultCaches.delete(key);
|
||||
}
|
||||
try {
|
||||
const response = await api.fetchApi('/model_linker/clear-search-cache', {
|
||||
const response = await api.fetchApi('/model_resolver/clear-search-cache', {
|
||||
method: 'POST'
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to clear backend search cache');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Model Linker: Clear search cache error:', error);
|
||||
console.error('Model Resolver: Clear search cache error:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { api } from "../../../../../scripts/api.js";
|
||||
import { $el } from "../../../../../scripts/ui.js";
|
||||
import { getSvgIcon } from "../../utils/icon_utils.js";
|
||||
@@ -57,11 +57,11 @@ export const searchPanelMethods = {
|
||||
},
|
||||
|
||||
getSearchIconHtml() {
|
||||
return `<span class="ml-btn-icon" aria-hidden="true">${getSvgIcon('search')}</span>`;
|
||||
return `<span class="mr-btn-icon" aria-hidden="true">${getSvgIcon('search')}</span>`;
|
||||
},
|
||||
|
||||
getLocateIconHtml() {
|
||||
return `<span class="ml-node-chip-icon" aria-hidden="true">${getSvgIcon('locate')}</span>`;
|
||||
return `<span class="mr-node-chip-icon" aria-hidden="true">${getSvgIcon('locate')}</span>`;
|
||||
},
|
||||
|
||||
showTooltip(target) {
|
||||
@@ -107,7 +107,7 @@ export const searchPanelMethods = {
|
||||
if (target.hasAttribute('title')) {
|
||||
target.removeAttribute('title');
|
||||
}
|
||||
if (target.classList?.contains('ml-tooltip-badge') && !target.hasAttribute('tabindex')) {
|
||||
if (target.classList?.contains('mr-tooltip-badge') && !target.hasAttribute('tabindex')) {
|
||||
target.setAttribute('tabindex', '0');
|
||||
}
|
||||
},
|
||||
@@ -153,7 +153,7 @@ export const searchPanelMethods = {
|
||||
try {
|
||||
return this.getValidTab(localStorage.getItem(this.activeTabStorageKey));
|
||||
} catch (error) {
|
||||
console.warn('Model Linker: Failed to restore active tab:', error);
|
||||
console.warn('Model Resolver: Failed to restore active tab:', error);
|
||||
return 'missing';
|
||||
}
|
||||
},
|
||||
@@ -162,7 +162,7 @@ export const searchPanelMethods = {
|
||||
try {
|
||||
localStorage.setItem(this.activeTabStorageKey, this.getValidTab(tab));
|
||||
} catch (error) {
|
||||
console.warn('Model Linker: Failed to persist active tab:', error);
|
||||
console.warn('Model Resolver: Failed to persist active tab:', error);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -192,27 +192,27 @@ export const searchPanelMethods = {
|
||||
return [
|
||||
{
|
||||
source: 'local',
|
||||
storageKey: 'modelLinker.searchSource.localEnabled',
|
||||
storageKey: 'ModelResolver.searchSource.localEnabled',
|
||||
tooltip: 'Searches bundled known-model data before online providers.'
|
||||
},
|
||||
{
|
||||
source: 'huggingface',
|
||||
storageKey: 'modelLinker.searchSource.huggingFaceEnabled',
|
||||
storageKey: 'ModelResolver.searchSource.huggingFaceEnabled',
|
||||
tooltip: 'Searches Hugging Face when Everything is selected.'
|
||||
},
|
||||
{
|
||||
source: 'civitai',
|
||||
storageKey: 'modelLinker.searchSource.civitaiEnabled',
|
||||
storageKey: 'ModelResolver.searchSource.civitaiEnabled',
|
||||
tooltip: 'Searches CivitAI when Everything is selected.'
|
||||
},
|
||||
{
|
||||
source: 'civarchive',
|
||||
storageKey: 'modelLinker.searchSource.civArchiveEnabled',
|
||||
storageKey: 'ModelResolver.searchSource.civArchiveEnabled',
|
||||
tooltip: 'Searches CivArchive when Everything is selected.'
|
||||
},
|
||||
{
|
||||
source: 'lora_manager_archive',
|
||||
storageKey: 'modelLinker.searchSource.loraManagerArchiveEnabled',
|
||||
storageKey: 'ModelResolver.searchSource.loraManagerArchiveEnabled',
|
||||
tooltip: 'Searches the local LoRA Manager archive when Everything is selected.'
|
||||
}
|
||||
];
|
||||
@@ -272,11 +272,11 @@ export const searchPanelMethods = {
|
||||
if (!this.contentElement || !missing) return;
|
||||
|
||||
const key = this.getMissingModelKey(missing);
|
||||
const rows = this.contentElement.querySelectorAll('.ml-missing-list-row');
|
||||
const rows = this.contentElement.querySelectorAll('.mr-missing-list-row');
|
||||
for (const row of rows) {
|
||||
if (row.getAttribute('data-missing-key') !== key) continue;
|
||||
|
||||
const sourcesEl = row.querySelector('.ml-missing-row-sources');
|
||||
const sourcesEl = row.querySelector('.mr-missing-row-sources');
|
||||
if (sourcesEl) {
|
||||
sourcesEl.innerHTML = this.renderMissingSourcesSummary(missing);
|
||||
}
|
||||
@@ -391,7 +391,7 @@ export const searchPanelMethods = {
|
||||
};
|
||||
|
||||
if (isCompact) {
|
||||
let html = '<div class="ml-search-progress-list ml-search-progress-list-compact">';
|
||||
let html = '<div class="mr-search-progress-list mr-search-progress-list-compact">';
|
||||
for (const [source, progress] of progressEntries) {
|
||||
const status = progress?.status || 'pending';
|
||||
const statusClass = String(status).replace(/[^a-z0-9_-]/gi, '');
|
||||
@@ -399,9 +399,9 @@ export const searchPanelMethods = {
|
||||
const statusLabel = progress?.message || statusLabels[status] || status;
|
||||
const title = `${label}: ${statusLabel}`;
|
||||
html += `
|
||||
<div class="ml-search-progress-item ml-search-progress-${statusClass}" data-tooltip="${this.escapeHtml(title)}">
|
||||
<span class="ml-search-progress-source">${this.escapeHtml(label)}</span>
|
||||
<span class="ml-search-progress-status">${this.escapeHtml(statusLabel)}</span>
|
||||
<div class="mr-search-progress-item mr-search-progress-${statusClass}" data-tooltip="${this.escapeHtml(title)}">
|
||||
<span class="mr-search-progress-source">${this.escapeHtml(label)}</span>
|
||||
<span class="mr-search-progress-status">${this.escapeHtml(statusLabel)}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -409,7 +409,7 @@ export const searchPanelMethods = {
|
||||
return html;
|
||||
}
|
||||
|
||||
let html = '<div class="ml-search-progress-list">';
|
||||
let html = '<div class="mr-search-progress-list">';
|
||||
for (const [source, progress] of progressEntries) {
|
||||
const status = progress?.status || 'pending';
|
||||
const statusClass = String(status).replace(/[^a-z0-9_-]/gi, '');
|
||||
@@ -422,13 +422,13 @@ export const searchPanelMethods = {
|
||||
? `Searching... ${Math.round(safePercent)}%`
|
||||
: (progress?.message || statusLabels[status] || status);
|
||||
html += `
|
||||
<div class="ml-search-progress-item ml-search-progress-${statusClass}">
|
||||
<div class="ml-search-progress-head">
|
||||
<div class="mr-search-progress-item mr-search-progress-${statusClass}">
|
||||
<div class="mr-search-progress-head">
|
||||
<span>${this.escapeHtml(label)}</span>
|
||||
<span>${this.escapeHtml(statusLabel)}</span>
|
||||
</div>
|
||||
<div class="ml-search-progress-bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${safePercent}">
|
||||
<div class="ml-search-progress-fill" style="width: ${safePercent}%;"></div>
|
||||
<div class="mr-search-progress-bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${safePercent}">
|
||||
<div class="mr-search-progress-fill" style="width: ${safePercent}%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -500,7 +500,7 @@ export const searchPanelMethods = {
|
||||
const parts = this.getModelVersionParts(modelName, versionName);
|
||||
const nameHtml = parts.name ? this.escapeHtml(parts.name) : '';
|
||||
const versionHtml = parts.version
|
||||
? `<em class="ml-model-version">${this.escapeHtml(parts.version)}</em>`
|
||||
? `<em class="mr-model-version">${this.escapeHtml(parts.version)}</em>`
|
||||
: '';
|
||||
return [nameHtml, versionHtml].filter(Boolean).join(' ');
|
||||
},
|
||||
@@ -525,9 +525,9 @@ export const searchPanelMethods = {
|
||||
|
||||
renderSearchSourcePill(sourceKey, sourceLabel) {
|
||||
const iconName = this.getSearchSourceIconName(sourceKey);
|
||||
const iconHtml = getSvgIcon(iconName, 'currentColor', 'ml-search-source-icon');
|
||||
const iconHtml = getSvgIcon(iconName, 'currentColor', 'mr-search-source-icon');
|
||||
return `
|
||||
<span class="ml-search-source-pill ml-search-source-${sourceKey}" data-tooltip="${this.escapeHtml(sourceLabel)}">
|
||||
<span class="mr-search-source-pill mr-search-source-${sourceKey}" data-tooltip="${this.escapeHtml(sourceLabel)}">
|
||||
${iconHtml}
|
||||
<span>${this.escapeHtml(sourceLabel)}</span>
|
||||
</span>
|
||||
@@ -649,7 +649,7 @@ export const searchPanelMethods = {
|
||||
},
|
||||
|
||||
renderKnownDownloadPanel(missing, downloadSource) {
|
||||
let html = `<div class="ml-download-section">`;
|
||||
let html = `<div class="mr-download-section">`;
|
||||
html += this.renderSearchControls(missing, { buttonText: 'Search Again' });
|
||||
html += this.renderDownloadTargetControls(
|
||||
missing,
|
||||
@@ -658,7 +658,7 @@ export const searchPanelMethods = {
|
||||
html += `</div>`;
|
||||
|
||||
const downloadSourceRow = this.getDownloadSourceTableRow(missing, downloadSource);
|
||||
html += `<div id="search-results-${missing.node_id}-${missing.widget_index}" class="ml-search-results ml-is-visible">`;
|
||||
html += `<div id="search-results-${missing.node_id}-${missing.widget_index}" class="mr-search-results mr-is-visible">`;
|
||||
html += this.renderSearchResultsTable(downloadSourceRow ? [downloadSourceRow] : []);
|
||||
html += `</div>`;
|
||||
return html;
|
||||
@@ -668,22 +668,22 @@ export const searchPanelMethods = {
|
||||
if (!rows.length) return '';
|
||||
const layout = this.getSearchResultsTableLayout(rows);
|
||||
const tableStyle = [
|
||||
`--ml-source-col:${layout.sourcePx}px`,
|
||||
`--ml-match-col:${layout.matchPx}px`,
|
||||
`--ml-size-col:${layout.sizePx}px`,
|
||||
`--ml-actions-col:${layout.actionsPx}px`,
|
||||
`--ml-table-min:${layout.tableMinPx}px`
|
||||
`--mr-source-col:${layout.sourcePx}px`,
|
||||
`--mr-match-col:${layout.matchPx}px`,
|
||||
`--mr-size-col:${layout.sizePx}px`,
|
||||
`--mr-actions-col:${layout.actionsPx}px`,
|
||||
`--mr-table-min:${layout.tableMinPx}px`
|
||||
].join(';');
|
||||
|
||||
let html = `
|
||||
<div class="ml-search-results-table-wrap">
|
||||
<table class="ml-search-results-table" style="${tableStyle}">
|
||||
<div class="mr-search-results-table-wrap">
|
||||
<table class="mr-search-results-table" style="${tableStyle}">
|
||||
<colgroup>
|
||||
<col class="ml-search-col-source">
|
||||
<col class="ml-search-col-model">
|
||||
<col class="ml-search-col-match">
|
||||
<col class="ml-search-col-size">
|
||||
<col class="ml-search-col-actions">
|
||||
<col class="mr-search-col-source">
|
||||
<col class="mr-search-col-model">
|
||||
<col class="mr-search-col-match">
|
||||
<col class="mr-search-col-size">
|
||||
<col class="mr-search-col-actions">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -719,7 +719,7 @@ export const searchPanelMethods = {
|
||||
let actions = '';
|
||||
if (downloadUrl) {
|
||||
actions += `
|
||||
<button type="button" class="search-download-btn ml-search-result-action-btn"
|
||||
<button type="button" class="search-download-btn mr-search-result-action-btn"
|
||||
data-tooltip="Download"
|
||||
aria-label="Download ${this.escapeHtml(downloadFilename)}"
|
||||
data-url="${this.escapeHtml(downloadUrl)}"
|
||||
@@ -730,28 +730,28 @@ export const searchPanelMethods = {
|
||||
if (openUrl) {
|
||||
actions += `
|
||||
<button type="button"
|
||||
class="search-open-page-btn ml-search-result-action-btn"
|
||||
class="search-open-page-btn mr-search-result-action-btn"
|
||||
data-tooltip="Open model page"
|
||||
aria-label="Open model page"
|
||||
data-url="${this.escapeHtml(openUrl)}">${getSvgIcon('externalLink')}</button>
|
||||
`;
|
||||
}
|
||||
if (!actions) {
|
||||
actions = '<span class="ml-search-result-empty">-</span>';
|
||||
actions = '<span class="mr-search-result-empty">-</span>';
|
||||
}
|
||||
|
||||
html += `
|
||||
<tr>
|
||||
<td>${sourcePill}</td>
|
||||
<td>
|
||||
<div class="ml-search-result-model" data-tooltip="${modelTitle}">
|
||||
<div class="mr-search-result-model" data-tooltip="${modelTitle}">
|
||||
<span>${modelHtml}</span>
|
||||
${secondary || filename ? `<small>${secondary || filename}</small>` : ''}
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="ml-search-match ml-search-match-${matchClass}">${this.escapeHtml(match.label)}</span></td>
|
||||
<td class="ml-search-size">${size}</td>
|
||||
<td><div class="ml-search-result-actions">${actions}</div></td>
|
||||
<td><span class="mr-search-match mr-search-match-${matchClass}">${this.escapeHtml(match.label)}</span></td>
|
||||
<td class="mr-search-size">${size}</td>
|
||||
<td><div class="mr-search-result-actions">${actions}</div></td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
@@ -772,15 +772,15 @@ export const searchPanelMethods = {
|
||||
const state = this.getSearchState(missing);
|
||||
const selectedSource = state.selectedSource || 'all';
|
||||
|
||||
let html = `<div id="${searchSourcesId}" class="ml-search-source-bar">`;
|
||||
html += `<button id="search-${missing.node_id}-${missing.widget_index}" class="ml-btn ml-btn-link">`;
|
||||
let html = `<div id="${searchSourcesId}" class="mr-search-source-bar">`;
|
||||
html += `<button id="search-${missing.node_id}-${missing.widget_index}" class="mr-btn mr-btn-link">`;
|
||||
html += `${this.getSearchIconHtml()} ${buttonText}`;
|
||||
html += `</button>`;
|
||||
html += `<div class="ml-search-source-picker">`;
|
||||
html += `<label class="ml-search-source-picker-label" for="${searchSourceSelectId}">Source</label>`;
|
||||
html += `<div class="ml-download-target-wrap">`;
|
||||
html += `<input id="${searchSourceSelectId}" class="ml-download-target-input ml-search-source-select" type="text" readonly autocomplete="off" data-value="${this.escapeHtml(selectedSource)}" value="${this.escapeHtml(this.getSearchSourceLabel(selectedSource))}">`;
|
||||
html += `<div id="${searchSourceListId}" class="ml-download-target-list ml-search-source-list"></div>`;
|
||||
html += `<div class="mr-search-source-picker">`;
|
||||
html += `<label class="mr-search-source-picker-label" for="${searchSourceSelectId}">Source</label>`;
|
||||
html += `<div class="mr-download-target-wrap">`;
|
||||
html += `<input id="${searchSourceSelectId}" class="mr-download-target-input mr-search-source-select" type="text" readonly autocomplete="off" data-value="${this.escapeHtml(selectedSource)}" value="${this.escapeHtml(this.getSearchSourceLabel(selectedSource))}">`;
|
||||
html += `<div id="${searchSourceListId}" class="mr-download-target-list mr-search-source-list"></div>`;
|
||||
html += `</div>`;
|
||||
html += `</div>`;
|
||||
html += `</div>`;
|
||||
@@ -798,14 +798,14 @@ export const searchPanelMethods = {
|
||||
const safePercent = Math.max(0, Math.min(100, Number(percent) || 0));
|
||||
const actionAttr = actionDataAttr ? ` ${actionDataAttr}` : '';
|
||||
return `
|
||||
<div class="ml-progress-container">
|
||||
<div class="ml-progress-row">
|
||||
<div class="ml-progress-bar ml-progress-bar-grow">
|
||||
<div class="ml-progress-fill" style="width: ${safePercent}%;"></div>
|
||||
<div class="mr-progress-container">
|
||||
<div class="mr-progress-row">
|
||||
<div class="mr-progress-bar mr-progress-bar-grow">
|
||||
<div class="mr-progress-fill" style="width: ${safePercent}%;"></div>
|
||||
</div>
|
||||
<button class="${actionClass}"${actionAttr}>${actionText}</button>
|
||||
</div>
|
||||
<div class="ml-progress-text">
|
||||
<div class="mr-progress-text">
|
||||
<span>${leftText}</span>
|
||||
<span>${rightText}</span>
|
||||
</div>
|
||||
@@ -853,11 +853,11 @@ export const searchPanelMethods = {
|
||||
const contextModel = this.buildContextMenuModelData(match.model || {}, match.filename || '');
|
||||
const modelData = encodeURIComponent(JSON.stringify(contextModel));
|
||||
|
||||
html += `<div class="ml-match-row ${isBestMatch ? 'ml-best-match' : ''}" data-model="${modelData}" oncontextmenu="window.MLOpenContextMenu(event, this)">`;
|
||||
html += `<div class="mr-match-row ${isBestMatch ? 'mr-best-match' : ''}" data-model="${modelData}" oncontextmenu="window.MLOpenContextMenu(event, this)">`;
|
||||
html += this.getConfidenceBadge(match.confidence);
|
||||
html += `<span class="ml-match-filename" data-tooltip="${this.escapeHtml(matchPath)}">${this.escapeHtml(matchPath)}</span>`;
|
||||
html += `<span class="ml-match-status ${match.confidence === 100 ? 'ml-match-status-exact' : 'ml-match-status-partial'}">${match.confidence === 100 ? 'Exact' : 'Partial'}</span>`;
|
||||
html += `<button id="${buttonId}" class="ml-btn ml-btn-secondary ml-btn-sm ml-btn-icon-only ml-local-link-btn" data-tooltip="Link this local match" aria-label="Link this local match">`;
|
||||
html += `<span class="mr-match-filename" data-tooltip="${this.escapeHtml(matchPath)}">${this.escapeHtml(matchPath)}</span>`;
|
||||
html += `<span class="mr-match-status ${match.confidence === 100 ? 'mr-match-status-exact' : 'mr-match-status-partial'}">${match.confidence === 100 ? 'Exact' : 'Partial'}</span>`;
|
||||
html += `<button id="${buttonId}" class="mr-btn mr-btn-secondary mr-btn-sm mr-btn-icon-only mr-local-link-btn" data-tooltip="Link this local match" aria-label="Link this local match">`;
|
||||
html += getSvgIcon('link');
|
||||
html += `</button>`;
|
||||
html += `</div>`;
|
||||
@@ -866,33 +866,33 @@ export const searchPanelMethods = {
|
||||
if (perfectMatches.length > 0 && otherMatches.length > 0) {
|
||||
const matchId = `more-matches-${missing.node_id}-${missing.widget_index}`;
|
||||
const altLabel = `Alternatives (${otherMatches.length})`;
|
||||
html += `<button type="button" class="ml-local-alternatives-toggle" aria-expanded="true" onclick="window.MLToggleHidden('${matchId}', this, '${altLabel}', '${altLabel}')">`;
|
||||
html += `<span class="ml-local-alternatives-label">${altLabel}</span>`;
|
||||
html += `<span class="ml-local-alternatives-state">Hide</span>`;
|
||||
html += `<span class="ml-local-alternatives-chevron" aria-hidden="true"></span>`;
|
||||
html += `<button type="button" class="mr-local-alternatives-toggle" aria-expanded="true" onclick="window.MLToggleHidden('${matchId}', this, '${altLabel}', '${altLabel}')">`;
|
||||
html += `<span class="mr-local-alternatives-label">${altLabel}</span>`;
|
||||
html += `<span class="mr-local-alternatives-state">Hide</span>`;
|
||||
html += `<span class="mr-local-alternatives-chevron" aria-hidden="true"></span>`;
|
||||
html += `</button>`;
|
||||
html += `<div id="${matchId}" class="ml-stack-sm">`;
|
||||
html += `<div id="${matchId}" class="mr-stack-sm">`;
|
||||
for (let mIdx = 0; mIdx < otherMatches.length; mIdx++) {
|
||||
const match = otherMatches[mIdx];
|
||||
const altBtnId = `resolve-alt-${missingIndex}-${missing.node_id}-${missing.widget_index}-${mIdx}`;
|
||||
const contextModel = this.buildContextMenuModelData(match.model || {}, match.filename || '');
|
||||
const modelData = encodeURIComponent(JSON.stringify(contextModel));
|
||||
const matchPath = match.model?.relative_path || match.model?.path || match.path || match.filename || '';
|
||||
html += `<div class="ml-match-row" data-model="${modelData}" oncontextmenu="window.MLOpenContextMenu(event, this)">`;
|
||||
html += `<div class="mr-match-row" data-model="${modelData}" oncontextmenu="window.MLOpenContextMenu(event, this)">`;
|
||||
html += this.getConfidenceBadge(match.confidence);
|
||||
html += `<span class="ml-match-filename" data-tooltip="${this.escapeHtml(matchPath)}">${this.escapeHtml(matchPath)}</span>`;
|
||||
html += `<span class="ml-match-status ml-match-status-partial">Partial</span>`;
|
||||
html += `<button id="${altBtnId}" class="ml-btn ml-btn-secondary ml-btn-sm ml-btn-icon-only ml-local-link-btn" data-tooltip="Link this local match" aria-label="Link this local match">${getSvgIcon('link')}</button>`;
|
||||
html += `<span class="mr-match-filename" data-tooltip="${this.escapeHtml(matchPath)}">${this.escapeHtml(matchPath)}</span>`;
|
||||
html += `<span class="mr-match-status mr-match-status-partial">Partial</span>`;
|
||||
html += `<button id="${altBtnId}" class="mr-btn mr-btn-secondary mr-btn-sm mr-btn-icon-only mr-local-link-btn" data-tooltip="Link this local match" aria-label="Link this local match">${getSvgIcon('link')}</button>`;
|
||||
html += `</div>`;
|
||||
}
|
||||
html += `</div>`;
|
||||
}
|
||||
} else if (missing.is_urn && !missing.civitai_info) {
|
||||
html += `<div class="ml-no-matches">Waiting for CivitAI filename to search local models...</div>`;
|
||||
html += `<div class="mr-no-matches">Waiting for CivitAI filename to search local models...</div>`;
|
||||
} else if (allMatches.length > 0 && filteredMatches.length === 0) {
|
||||
html += `<div class="ml-no-matches">No matches above 70% confidence</div>`;
|
||||
html += `<div class="mr-no-matches">No matches above 70% confidence</div>`;
|
||||
} else {
|
||||
html += `<div class="ml-no-matches">No local matches found</div>`;
|
||||
html += `<div class="mr-no-matches">No local matches found</div>`;
|
||||
}
|
||||
|
||||
return html;
|
||||
@@ -1024,7 +1024,7 @@ export const searchPanelMethods = {
|
||||
civitai_candidate_limit: tokens.civitai_candidate_limit
|
||||
};
|
||||
|
||||
const response = await api.fetchApi('/model_linker/search', {
|
||||
const response = await api.fetchApi('/model_resolver/search', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
@@ -1049,14 +1049,14 @@ export const searchPanelMethods = {
|
||||
|
||||
refreshMissingListStats() {
|
||||
if (!this.contentElement) return;
|
||||
const statsEl = this.contentElement.querySelector('.ml-missing-list-stats');
|
||||
const statsEl = this.contentElement.querySelector('.mr-missing-list-stats');
|
||||
if (!statsEl) return;
|
||||
|
||||
const stats = this.getMissingModelSummaryStats(this.missingModels || []);
|
||||
statsEl.innerHTML = `
|
||||
<span class="ml-missing-stat ml-missing-stat-exact">${stats.exact} exact</span>
|
||||
<span class="ml-missing-stat ml-missing-stat-partial">${stats.partial} partial</span>
|
||||
<span class="ml-missing-stat ml-missing-stat-none">${stats.none} no match</span>
|
||||
<span class="mr-missing-stat mr-missing-stat-exact">${stats.exact} exact</span>
|
||||
<span class="mr-missing-stat mr-missing-stat-partial">${stats.partial} partial</span>
|
||||
<span class="mr-missing-stat mr-missing-stat-none">${stats.none} no match</span>
|
||||
`;
|
||||
},
|
||||
|
||||
@@ -1064,7 +1064,7 @@ export const searchPanelMethods = {
|
||||
if (!this.contentElement) return;
|
||||
|
||||
const key = this.getMissingModelKey(missing);
|
||||
const rows = this.contentElement.querySelectorAll('.ml-missing-list-row');
|
||||
const rows = this.contentElement.querySelectorAll('.mr-missing-list-row');
|
||||
for (const row of rows) {
|
||||
if (row.getAttribute('data-missing-key') !== key) continue;
|
||||
|
||||
@@ -1074,22 +1074,22 @@ export const searchPanelMethods = {
|
||||
const matchDisplay = matchName || 'No local match';
|
||||
const matchClass = confidence === 100 ? 'exact' : (bestMatch ? 'partial' : 'none');
|
||||
|
||||
const bestEl = row.querySelector('.ml-missing-row-best');
|
||||
const bestEl = row.querySelector('.mr-missing-row-best');
|
||||
if (bestEl) {
|
||||
bestEl.setAttribute('data-tooltip', matchDisplay);
|
||||
bestEl.innerHTML = bestMatch
|
||||
? this.escapeHtml(matchDisplay)
|
||||
: '<span class="ml-missing-row-none">-- No local match</span>';
|
||||
: '<span class="mr-missing-row-none">-- No local match</span>';
|
||||
}
|
||||
|
||||
const matchEl = row.querySelector('.ml-missing-row-match');
|
||||
const matchEl = row.querySelector('.mr-missing-row-match');
|
||||
if (matchEl) {
|
||||
matchEl.classList.remove(
|
||||
'ml-missing-row-match-exact',
|
||||
'ml-missing-row-match-partial',
|
||||
'ml-missing-row-match-none'
|
||||
'mr-missing-row-match-exact',
|
||||
'mr-missing-row-match-partial',
|
||||
'mr-missing-row-match-none'
|
||||
);
|
||||
matchEl.classList.add(`ml-missing-row-match-${matchClass}`);
|
||||
matchEl.classList.add(`mr-missing-row-match-${matchClass}`);
|
||||
const valueEl = matchEl.querySelector('strong');
|
||||
if (valueEl) {
|
||||
valueEl.textContent = bestMatch
|
||||
@@ -1116,7 +1116,7 @@ export const searchPanelMethods = {
|
||||
}
|
||||
|
||||
const promise = (async () => {
|
||||
const response = await api.fetchApi('/model_linker/local-matches', {
|
||||
const response = await api.fetchApi('/model_resolver/local-matches', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -1147,7 +1147,7 @@ export const searchPanelMethods = {
|
||||
const bodyId = `local-matches-body-${missing.node_id}-${missing.widget_index}`;
|
||||
const container = this.contentElement.querySelector(`#${bodyId}`);
|
||||
if (container) {
|
||||
container.innerHTML = `<div class="ml-no-matches">Searching local matches for "${missing.civitai_info.expected_filename}"...</div>`;
|
||||
container.innerHTML = `<div class="mr-no-matches">Searching local matches for "${missing.civitai_info.expected_filename}"...</div>`;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -1158,9 +1158,9 @@ export const searchPanelMethods = {
|
||||
}
|
||||
this.refreshMissingListRowLocalMatch(missing);
|
||||
} catch (error) {
|
||||
console.error('Model Linker: URN local match refresh error:', error);
|
||||
console.error('Model Resolver: URN local match refresh error:', error);
|
||||
if (container) {
|
||||
container.innerHTML = `<div class="ml-no-matches">Failed to refresh local matches.</div>`;
|
||||
container.innerHTML = `<div class="mr-no-matches">Failed to refresh local matches.</div>`;
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1179,7 +1179,7 @@ export const searchPanelMethods = {
|
||||
await this.refreshUrnLocalMatches(missing);
|
||||
} catch (error) {
|
||||
missing.__urnLocalRefreshFailed = true;
|
||||
console.error('Model Linker: initial URN local match refresh error:', error);
|
||||
console.error('Model Resolver: initial URN local match refresh error:', error);
|
||||
}
|
||||
})();
|
||||
refreshTasks.push(task);
|
||||
+42
-42
@@ -1,27 +1,27 @@
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { api } from "../../../../../scripts/api.js";
|
||||
import { $el } from "../../../../../scripts/ui.js";
|
||||
import { getSvgIcon } from "../../utils/icon_utils.js";
|
||||
export const dialogShellMethods = {
|
||||
createHeader() {
|
||||
// Create tabs
|
||||
this.missingTab = $el("button.ml-tab.ml-tab-active", {
|
||||
this.missingTab = $el("button.mr-tab.mr-tab-active", {
|
||||
onclick: () => this.switchTab('missing')
|
||||
}, [$el("span.ml-tab-label", { textContent: "Missing Models" })]);
|
||||
}, [$el("span.mr-tab-label", { textContent: "Missing Models" })]);
|
||||
|
||||
this.loadedTab = $el("button.ml-tab", {
|
||||
this.loadedTab = $el("button.mr-tab", {
|
||||
onclick: () => this.switchTab('loaded')
|
||||
}, [$el("span.ml-tab-label", { textContent: "Loaded Models" })]);
|
||||
}, [$el("span.mr-tab-label", { textContent: "Loaded Models" })]);
|
||||
|
||||
this.optionsTab = $el("button.ml-tab", {
|
||||
this.optionsTab = $el("button.mr-tab", {
|
||||
onclick: () => this.switchTab('options')
|
||||
}, [$el("span.ml-tab-label", { textContent: "Options" })]);
|
||||
}, [$el("span.mr-tab-label", { textContent: "Options" })]);
|
||||
if (this.activeTab === 'missing') {
|
||||
this.updateTabButtonStates();
|
||||
}
|
||||
|
||||
const dragHandle = $el("div", {
|
||||
id: "model-linker-drag-handle",
|
||||
id: "model-resolver-drag-handle",
|
||||
ondragstart: (e) => e.preventDefault()
|
||||
}, [
|
||||
$el("span", { textContent: "⠿" })
|
||||
@@ -29,50 +29,50 @@ export const dialogShellMethods = {
|
||||
this.setTooltip(dragHandle, "Drag window");
|
||||
|
||||
const fullscreenButton = $el("button", {
|
||||
id: "model-linker-fullscreen-toggle",
|
||||
className: "ml-window-btn ml-window-btn--fullscreen",
|
||||
innerHTML: getSvgIcon('windowMaximize', 'currentColor', 'ml-window-btn-icon'),
|
||||
id: "model-resolver-fullscreen-toggle",
|
||||
className: "mr-window-btn mr-window-btn--fullscreen",
|
||||
innerHTML: getSvgIcon('windowMaximize', 'currentColor', 'mr-window-btn-icon'),
|
||||
ariaLabel: "Toggle full screen",
|
||||
onclick: () => this.toggleFullScreen()
|
||||
});
|
||||
this.setTooltip(fullscreenButton, "Toggle full screen");
|
||||
|
||||
this.dockButton = $el("button", {
|
||||
id: "model-linker-dock-toggle",
|
||||
className: "ml-window-btn ml-window-btn--dock",
|
||||
innerHTML: getSvgIcon('internalLink', 'currentColor', 'ml-window-btn-icon'),
|
||||
ariaLabel: "Dock Model Linker to sidebar",
|
||||
id: "model-resolver-dock-toggle",
|
||||
className: "mr-window-btn mr-window-btn--dock",
|
||||
innerHTML: getSvgIcon('internalLink', 'currentColor', 'mr-window-btn-icon'),
|
||||
ariaLabel: "Dock Model Resolver to sidebar",
|
||||
onclick: () => this.dockToSidebar()
|
||||
});
|
||||
this.setTooltip(this.dockButton, "Dock to sidebar");
|
||||
|
||||
this.undockButton = $el("button", {
|
||||
id: "model-linker-undock-toggle",
|
||||
className: "ml-window-btn ml-window-btn--undock",
|
||||
innerHTML: getSvgIcon('externalLink', 'currentColor', 'ml-window-btn-icon'),
|
||||
ariaLabel: "Undock Model Linker",
|
||||
id: "model-resolver-undock-toggle",
|
||||
className: "mr-window-btn mr-window-btn--undock",
|
||||
innerHTML: getSvgIcon('externalLink', 'currentColor', 'mr-window-btn-icon'),
|
||||
ariaLabel: "Undock Model Resolver",
|
||||
onclick: () => this.undockToFloating()
|
||||
});
|
||||
this.setTooltip(this.undockButton, "Undock to floating window");
|
||||
|
||||
return $el("div.ml-dialog-shell", {}, [
|
||||
$el("div.ml-dialog-topbar", {}, [
|
||||
$el("div.ml-dialog-brand", {}, [
|
||||
return $el("div.mr-dialog-shell", {}, [
|
||||
$el("div.mr-dialog-topbar", {}, [
|
||||
$el("div.mr-dialog-brand", {}, [
|
||||
dragHandle,
|
||||
$el("div.ml-tabs", {}, [
|
||||
$el("div.mr-tabs", {}, [
|
||||
this.missingTab,
|
||||
this.loadedTab,
|
||||
this.optionsTab
|
||||
])
|
||||
]),
|
||||
$el("div.ml-dialog-controls", {}, [
|
||||
$el("div.mr-dialog-controls", {}, [
|
||||
this.dockButton,
|
||||
this.undockButton,
|
||||
fullscreenButton,
|
||||
$el("button", {
|
||||
className: "ml-window-btn ml-window-btn--close",
|
||||
innerHTML: getSvgIcon('x', 'currentColor', 'ml-window-btn-icon'),
|
||||
ariaLabel: "Close Model Linker",
|
||||
className: "mr-window-btn mr-window-btn--close",
|
||||
innerHTML: getSvgIcon('x', 'currentColor', 'mr-window-btn-icon'),
|
||||
ariaLabel: "Close Model Resolver",
|
||||
onclick: () => this.close()
|
||||
})
|
||||
])
|
||||
@@ -142,13 +142,13 @@ export const dialogShellMethods = {
|
||||
}
|
||||
|
||||
try {
|
||||
const wh = JSON.parse(localStorage.getItem('model_linker_modal_size_before_fs') || 'null');
|
||||
const wh = JSON.parse(localStorage.getItem('model_resolver_modal_size_before_fs') || 'null');
|
||||
if (wh?.w && wh?.h) {
|
||||
el.style.width = `${wh.w}px`;
|
||||
el.style.height = `${wh.h}px`;
|
||||
}
|
||||
|
||||
const pos = JSON.parse(localStorage.getItem('model_linker_modal_pos') || 'null');
|
||||
const pos = JSON.parse(localStorage.getItem('model_resolver_modal_pos') || 'null');
|
||||
if (pos && Number.isFinite(pos.top) && Number.isFinite(pos.left)) {
|
||||
el.style.top = `${pos.top}px`;
|
||||
el.style.left = `${pos.left}px`;
|
||||
@@ -200,8 +200,8 @@ export const dialogShellMethods = {
|
||||
this.pendingDockToSidebar = false;
|
||||
this.rememberSidebarOpenMode('docked');
|
||||
this.backdrop.style.display = 'none';
|
||||
container.classList.add('ml-sidebar-dock-panel');
|
||||
this.element.classList.add('ml-is-docked');
|
||||
container.classList.add('mr-sidebar-dock-panel');
|
||||
this.element.classList.add('mr-is-docked');
|
||||
this.clearModalPlacementStyles();
|
||||
this.element.style.display = 'flex';
|
||||
|
||||
@@ -311,7 +311,7 @@ export const dialogShellMethods = {
|
||||
const dockContainer = this.dockContainer;
|
||||
this.docked = false;
|
||||
this.dockContainer = null;
|
||||
this.element.classList.remove('ml-is-docked');
|
||||
this.element.classList.remove('mr-is-docked');
|
||||
this.clearModalPlacementStyles();
|
||||
|
||||
if (this.element.parentNode !== document.body) {
|
||||
@@ -412,12 +412,12 @@ export const dialogShellMethods = {
|
||||
this.fullscreen = !!enable;
|
||||
const el = this.element;
|
||||
if (!el) return;
|
||||
const btn = document.getElementById('model-linker-fullscreen-toggle');
|
||||
const btn = document.getElementById('model-resolver-fullscreen-toggle');
|
||||
if (enable) {
|
||||
// Save current size
|
||||
try {
|
||||
const rect = el.getBoundingClientRect();
|
||||
localStorage.setItem('model_linker_modal_size_before_fs', JSON.stringify({ w: Math.round(rect.width), h: Math.round(rect.height) }));
|
||||
localStorage.setItem('model_resolver_modal_size_before_fs', JSON.stringify({ w: Math.round(rect.width), h: Math.round(rect.height) }));
|
||||
} catch (e) {}
|
||||
el.style.top = '0';
|
||||
el.style.left = '0';
|
||||
@@ -429,11 +429,11 @@ export const dialogShellMethods = {
|
||||
el.style.borderRadius = '0';
|
||||
el.style.resize = 'none';
|
||||
if (btn) {
|
||||
btn.innerHTML = getSvgIcon('windowRestore', 'currentColor', 'ml-window-btn-icon');
|
||||
btn.innerHTML = getSvgIcon('windowRestore', 'currentColor', 'mr-window-btn-icon');
|
||||
btn.setAttribute('aria-label', 'Exit full screen');
|
||||
this.setTooltip(btn, 'Exit full screen');
|
||||
}
|
||||
try { localStorage.setItem('model_linker_modal_fullscreen', '1'); } catch (e) {}
|
||||
try { localStorage.setItem('model_resolver_modal_fullscreen', '1'); } catch (e) {}
|
||||
} else {
|
||||
this.returnToDockedAfterFullscreen = false;
|
||||
// Restore centered sizing
|
||||
@@ -443,7 +443,7 @@ export const dialogShellMethods = {
|
||||
el.style.resize = 'both';
|
||||
// Restore saved pre-FS size if available
|
||||
let wh = null;
|
||||
try { wh = JSON.parse(localStorage.getItem('model_linker_modal_size_before_fs') || 'null'); } catch (e) {}
|
||||
try { wh = JSON.parse(localStorage.getItem('model_resolver_modal_size_before_fs') || 'null'); } catch (e) {}
|
||||
if (wh && wh.w && wh.h) {
|
||||
el.style.width = `${wh.w}px`;
|
||||
el.style.height = `${wh.h}px`;
|
||||
@@ -453,7 +453,7 @@ export const dialogShellMethods = {
|
||||
}
|
||||
// Restore last known position if available, else center
|
||||
try {
|
||||
const pos = JSON.parse(localStorage.getItem('model_linker_modal_pos') || 'null');
|
||||
const pos = JSON.parse(localStorage.getItem('model_resolver_modal_pos') || 'null');
|
||||
if (pos && Number.isFinite(pos.top) && Number.isFinite(pos.left)) {
|
||||
el.style.top = `${pos.top}px`;
|
||||
el.style.left = `${pos.left}px`;
|
||||
@@ -469,12 +469,12 @@ export const dialogShellMethods = {
|
||||
el.style.transform = 'translate(-50%, -50%)';
|
||||
}
|
||||
if (btn) {
|
||||
btn.innerHTML = getSvgIcon('windowMaximize', 'currentColor', 'ml-window-btn-icon');
|
||||
btn.innerHTML = getSvgIcon('windowMaximize', 'currentColor', 'mr-window-btn-icon');
|
||||
btn.setAttribute('aria-label', 'Enter full screen');
|
||||
this.setTooltip(btn, 'Enter full screen');
|
||||
}
|
||||
this.ensureModalHandleInViewport({ persist: true });
|
||||
try { localStorage.setItem('model_linker_modal_fullscreen', '0'); } catch (e) {}
|
||||
try { localStorage.setItem('model_resolver_modal_fullscreen', '0'); } catch (e) {}
|
||||
|
||||
if (shouldReturnToDocked) {
|
||||
this.dockToSidebar();
|
||||
@@ -489,7 +489,7 @@ export const dialogShellMethods = {
|
||||
const vw = window.innerWidth || document.documentElement.clientWidth || 0;
|
||||
const vh = window.innerHeight || document.documentElement.clientHeight || 0;
|
||||
const pad = 4;
|
||||
const handle = document.getElementById('model-linker-drag-handle');
|
||||
const handle = document.getElementById('model-resolver-drag-handle');
|
||||
|
||||
if (handle) {
|
||||
const elRect = el.getBoundingClientRect();
|
||||
@@ -522,7 +522,7 @@ export const dialogShellMethods = {
|
||||
const el = this.element;
|
||||
if (!el) return;
|
||||
const rect = el.getBoundingClientRect();
|
||||
localStorage.setItem('model_linker_modal_pos', JSON.stringify({ top: Math.round(rect.top), left: Math.round(rect.left) }));
|
||||
localStorage.setItem('model_resolver_modal_pos', JSON.stringify({ top: Math.round(rect.top), left: Math.round(rect.left) }));
|
||||
} catch (e) { /* ignore */ }
|
||||
},
|
||||
|
||||
+17
-17
@@ -1,4 +1,4 @@
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { api } from "../../../../../scripts/api.js";
|
||||
import { $el } from "../../../../../scripts/ui.js";
|
||||
import { getSvgIcon } from "../../utils/icon_utils.js";
|
||||
@@ -30,7 +30,7 @@ export const lifecycleGraphMethods = {
|
||||
|
||||
// Restore fullscreen state if enabled
|
||||
try {
|
||||
const fs = localStorage.getItem('model_linker_modal_fullscreen');
|
||||
const fs = localStorage.getItem('model_resolver_modal_fullscreen');
|
||||
if (!this.docked && restoreFullscreen && fs === '1') this.setFullScreen(true);
|
||||
} catch (e) { }
|
||||
|
||||
@@ -55,17 +55,17 @@ export const lifecycleGraphMethods = {
|
||||
attachDragHandleIfNeeded() {
|
||||
if (this._dragHandleAttached) return;
|
||||
|
||||
const handle = document.getElementById('model-linker-drag-handle');
|
||||
const topbar = this.element?.querySelector('.ml-dialog-topbar');
|
||||
const handle = document.getElementById('model-resolver-drag-handle');
|
||||
const topbar = this.element?.querySelector('.mr-dialog-topbar');
|
||||
if (!handle || !topbar) return;
|
||||
|
||||
const onMouseDown = (e) => {
|
||||
if (this.fullscreen || this.docked) return; // no drag in fullscreen or docked mode
|
||||
if (e.button !== 0 || this.isTopbarDragExcluded(e.target)) return;
|
||||
topbar.classList.add('ml-is-dragging');
|
||||
topbar.classList.add('mr-is-dragging');
|
||||
this.startDrag(e);
|
||||
};
|
||||
const onMouseUp = () => { topbar.classList.remove('ml-is-dragging'); };
|
||||
const onMouseUp = () => { topbar.classList.remove('mr-is-dragging'); };
|
||||
|
||||
topbar.addEventListener('mousedown', onMouseDown);
|
||||
document.addEventListener('mouseup', onMouseUp);
|
||||
@@ -76,8 +76,8 @@ export const lifecycleGraphMethods = {
|
||||
isTopbarDragExcluded(target) {
|
||||
if (!(target instanceof Element)) return false;
|
||||
return !!target.closest([
|
||||
'.ml-tabs',
|
||||
'.ml-dialog-controls',
|
||||
'.mr-tabs',
|
||||
'.mr-dialog-controls',
|
||||
'button',
|
||||
'a',
|
||||
'input',
|
||||
@@ -144,7 +144,7 @@ export const lifecycleGraphMethods = {
|
||||
|
||||
// Call analyze endpoint
|
||||
const progressPromise = this.pollAnalysisProgress(analysisId, analysisId);
|
||||
const response = await api.fetchApi('/model_linker/analyze', {
|
||||
const response = await api.fetchApi('/model_resolver/analyze', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ workflow, analysis_id: analysisId })
|
||||
@@ -166,9 +166,9 @@ export const lifecycleGraphMethods = {
|
||||
|
||||
} catch (error) {
|
||||
this._analysisProgressToken = null;
|
||||
console.error('Model Linker: Error loading workflow data:', error);
|
||||
console.error('Model Resolver: Error loading workflow data:', error);
|
||||
if (this.contentElement) {
|
||||
this.contentElement.innerHTML = `<p class="ml-error-text">Error: ${error.message}</p>`;
|
||||
this.contentElement.innerHTML = `<p class="mr-error-text">Error: ${error.message}</p>`;
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -184,7 +184,7 @@ export const lifecycleGraphMethods = {
|
||||
const workflow = app.graph.serialize();
|
||||
return workflow;
|
||||
} catch (e) {
|
||||
console.warn('Model Linker: Could not serialize workflow from graph:', e);
|
||||
console.warn('Model Resolver: Could not serialize workflow from graph:', e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -243,7 +243,7 @@ export const lifecycleGraphMethods = {
|
||||
|
||||
this.showNotification(`Focused on Node #${nodeId} (${node.type})`, 'info');
|
||||
} catch (e) {
|
||||
console.error('Model Linker: Error locating node:', e);
|
||||
console.error('Model Resolver: Error locating node:', e);
|
||||
this.showNotification('Error locating node: ' + e.message, 'error');
|
||||
}
|
||||
},
|
||||
@@ -417,13 +417,13 @@ export const lifecycleGraphMethods = {
|
||||
info.downloadBtn = newDownloadBtn;
|
||||
|
||||
// Show that download is in progress
|
||||
newProgressDiv.classList.remove('ml-is-hidden');
|
||||
newProgressDiv.classList.add('ml-is-visible');
|
||||
newProgressDiv.classList.remove('mr-is-hidden');
|
||||
newProgressDiv.classList.add('mr-is-visible');
|
||||
newProgressDiv.innerHTML = this.renderProgressWithAction({
|
||||
percent: 0,
|
||||
leftText: '<span class="ml-info-accent-text">Downloading...</span>',
|
||||
leftText: '<span class="mr-info-accent-text">Downloading...</span>',
|
||||
rightText: '',
|
||||
actionClass: 'cancel-download-btn ml-btn ml-btn-danger ml-btn-sm',
|
||||
actionClass: 'cancel-download-btn mr-btn mr-btn-danger mr-btn-sm',
|
||||
actionText: 'Cancel',
|
||||
actionDataAttr: `data-download-id="${downloadId}"`
|
||||
});
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { api } from "../../../../../scripts/api.js";
|
||||
import { $el } from "../../../../../scripts/ui.js";
|
||||
import { getSvgIcon } from "../../utils/icon_utils.js";
|
||||
@@ -8,7 +8,7 @@ export const workflowStateMethods = {
|
||||
try {
|
||||
return JSON.stringify(workflow);
|
||||
} catch (error) {
|
||||
console.warn('Model Linker: workflow signature generation failed', error);
|
||||
console.warn('Model Resolver: workflow signature generation failed', error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
@@ -35,7 +35,7 @@ export const workflowStateMethods = {
|
||||
try {
|
||||
return JSON.parse(JSON.stringify(Array.isArray(list) ? list : []));
|
||||
} catch (error) {
|
||||
console.warn('Model Linker: failed to clone queued selections', error);
|
||||
console.warn('Model Resolver: failed to clone queued selections', error);
|
||||
return Array.isArray(list) ? list.map(item => ({ ...item })) : [];
|
||||
}
|
||||
},
|
||||
@@ -67,7 +67,7 @@ export const workflowStateMethods = {
|
||||
try {
|
||||
clone = JSON.parse(JSON.stringify(state || {}));
|
||||
} catch (error) {
|
||||
console.warn('Model Linker: failed to clone search state', error);
|
||||
console.warn('Model Resolver: failed to clone search state', error);
|
||||
clone = { ...state };
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ export const workflowStateMethods = {
|
||||
|
||||
if (!routeChanged && !signatureChanged) return;
|
||||
|
||||
console.log('Model Linker: active workflow changed, refreshing current tab', {
|
||||
console.log('Model Resolver: active workflow changed, refreshing current tab', {
|
||||
reason,
|
||||
route: currentRoute,
|
||||
tab: this.activeTab
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { api } from "../../../../../scripts/api.js";
|
||||
import { $el } from "../../../../../scripts/ui.js";
|
||||
import { getSvgIcon } from "../../utils/icon_utils.js";
|
||||
@@ -49,7 +49,7 @@ export const workflowUpdateMethods = {
|
||||
*/
|
||||
async updateWorkflowInComfyUI(workflow) {
|
||||
if (!app || !app.graph) {
|
||||
console.warn('Model Linker: Could not update workflow - app or app.graph not available');
|
||||
console.warn('Model Resolver: Could not update workflow - app or app.graph not available');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -78,9 +78,9 @@ export const workflowUpdateMethods = {
|
||||
return;
|
||||
}
|
||||
|
||||
console.warn('Model Linker: No method available to update workflow');
|
||||
console.warn('Model Resolver: No method available to update workflow');
|
||||
} catch (error) {
|
||||
console.error('Model Linker: Error updating workflow in ComfyUI:', error);
|
||||
console.error('Model Resolver: Error updating workflow in ComfyUI:', error);
|
||||
// Don't throw - allow the workflow update to continue even if UI update fails
|
||||
// The backend has already updated the workflow data
|
||||
}
|
||||
+18
-18
@@ -1,4 +1,4 @@
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { api } from "../../../../../scripts/api.js";
|
||||
import { $el } from "../../../../../scripts/ui.js";
|
||||
import { getSvgIcon } from "../../utils/icon_utils.js";
|
||||
@@ -11,17 +11,17 @@ export const renderFormatMethods = {
|
||||
getConfidenceBadge(confidence) {
|
||||
let badgeClass;
|
||||
if (confidence >= 95) {
|
||||
badgeClass = 'ml-badge-high';
|
||||
badgeClass = 'mr-badge-high';
|
||||
} else if (confidence >= 70) {
|
||||
badgeClass = 'ml-badge-medium';
|
||||
badgeClass = 'mr-badge-medium';
|
||||
} else {
|
||||
badgeClass = 'ml-badge-low';
|
||||
badgeClass = 'mr-badge-low';
|
||||
}
|
||||
return `<span class="ml-badge ${badgeClass}">${confidence}%</span>`;
|
||||
return `<span class="mr-badge ${badgeClass}">${confidence}%</span>`;
|
||||
},
|
||||
|
||||
getStatusBadge(label, variant = 'neutral') {
|
||||
return `<span class="ml-badge ml-badge-${variant}">${label}</span>`;
|
||||
return `<span class="mr-badge mr-badge-${variant}">${label}</span>`;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -110,8 +110,8 @@ export const renderFormatMethods = {
|
||||
const icon = icons[type] || icons.info;
|
||||
|
||||
return `
|
||||
<div class="ml-status ml-status-${type}">
|
||||
<span class="ml-status-icon">${icon}</span>
|
||||
<div class="mr-status mr-status-${type}">
|
||||
<span class="mr-status-icon">${icon}</span>
|
||||
<span>${message}</span>
|
||||
</div>
|
||||
`;
|
||||
@@ -126,11 +126,11 @@ export const renderFormatMethods = {
|
||||
*/
|
||||
renderProgressBar(percent, leftText = '', rightText = '') {
|
||||
return `
|
||||
<div class="ml-progress-container">
|
||||
<div class="ml-progress-bar">
|
||||
<div class="ml-progress-fill" style="width: ${percent}%"></div>
|
||||
<div class="mr-progress-container">
|
||||
<div class="mr-progress-bar">
|
||||
<div class="mr-progress-fill" style="width: ${percent}%"></div>
|
||||
</div>
|
||||
<div class="ml-progress-text">
|
||||
<div class="mr-progress-text">
|
||||
<span>${leftText}</span>
|
||||
<span>${rightText}</span>
|
||||
</div>
|
||||
@@ -147,13 +147,13 @@ export const renderFormatMethods = {
|
||||
const detail = total > 0 ? `${current} / ${total}` : 'Preparing...';
|
||||
|
||||
return `
|
||||
<div class="ml-download-section">
|
||||
<div class="ml-status-inline">
|
||||
<div class="mr-download-section">
|
||||
<div class="mr-status-inline">
|
||||
${this.getStatusBadge('Analyzing', 'info')}
|
||||
<span class="ml-download-info">${message}</span>
|
||||
<span class="mr-download-info">${message}</span>
|
||||
</div>
|
||||
${this.renderProgressBar(percent, detail, `${percent}%`)}
|
||||
${modelName ? `<div class="ml-download-info">${modelName}</div>` : ''}
|
||||
${modelName ? `<div class="mr-download-info">${modelName}</div>` : ''}
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
@@ -161,7 +161,7 @@ export const renderFormatMethods = {
|
||||
async pollAnalysisProgress(analysisId, token) {
|
||||
while (this._analysisProgressToken === token) {
|
||||
try {
|
||||
const response = await api.fetchApi(`/model_linker/analyze-progress/${analysisId}`);
|
||||
const response = await api.fetchApi(`/model_resolver/analyze-progress/${analysisId}`);
|
||||
if (response.ok && this.contentElement && this._analysisProgressToken === token) {
|
||||
const progress = await response.json();
|
||||
this.contentElement.innerHTML = this.renderAnalysisProgress(progress);
|
||||
@@ -170,7 +170,7 @@ export const renderFormatMethods = {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Model Linker: analysis progress polling failed', error);
|
||||
console.warn('Model Resolver: analysis progress polling failed', error);
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 250));
|
||||
+95
-95
@@ -1,4 +1,4 @@
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { api } from "../../../../../scripts/api.js";
|
||||
import { $el } from "../../../../../scripts/ui.js";
|
||||
import { getSvgIcon } from "../../utils/icon_utils.js";
|
||||
@@ -152,8 +152,8 @@ export const missingBrowserMethods = {
|
||||
};
|
||||
const title = `${label}: ${statusLabels[status] || status}`;
|
||||
const iconName = this.getSearchSourceIconName(item.source);
|
||||
const iconHtml = getSvgIcon(iconName, 'currentColor', 'ml-missing-source-icon');
|
||||
return `<span class="ml-missing-source-dot ml-missing-source-${statusClass}" data-tooltip="${this.escapeHtml(title)}" aria-label="${this.escapeHtml(title)}">${iconHtml}</span>`;
|
||||
const iconHtml = getSvgIcon(iconName, 'currentColor', 'mr-missing-source-icon');
|
||||
return `<span class="mr-missing-source-dot mr-missing-source-${statusClass}" data-tooltip="${this.escapeHtml(title)}" aria-label="${this.escapeHtml(title)}">${iconHtml}</span>`;
|
||||
}).join('');
|
||||
},
|
||||
|
||||
@@ -203,27 +203,27 @@ export const missingBrowserMethods = {
|
||||
savedDetailWidth = parseInt(localStorage.getItem(this.missingBrowserSplitStorageKey) || '', 10);
|
||||
} catch (e) {}
|
||||
const splitStyle = Number.isFinite(savedDetailWidth) && savedDetailWidth > 0
|
||||
? `--ml-missing-detail-track:${savedDetailWidth}px;`
|
||||
? `--mr-missing-detail-track:${savedDetailWidth}px;`
|
||||
: '';
|
||||
const listStyle = `--ml-missing-model-col:${listLayout.modelPx}px;--ml-missing-type-col:${listLayout.typePx}px;`;
|
||||
const listStyle = `--mr-missing-model-col:${listLayout.modelPx}px;--mr-missing-type-col:${listLayout.typePx}px;`;
|
||||
|
||||
let html = `
|
||||
<div class="ml-missing-browser" style="${listStyle}">
|
||||
<section class="ml-missing-list-pane" aria-label="Missing model list">
|
||||
<div class="ml-missing-list-toolbar">
|
||||
<div class="mr-missing-browser" style="${listStyle}">
|
||||
<section class="mr-missing-list-pane" aria-label="Missing model list">
|
||||
<div class="mr-missing-list-toolbar">
|
||||
<div>
|
||||
<div class="ml-missing-list-title">${totalMissing} missing model${totalMissing === 1 ? '' : 's'}</div>
|
||||
<div class="ml-missing-list-meta">${this.escapeHtml(activeHint)}</div>
|
||||
<div class="mr-missing-list-title">${totalMissing} missing model${totalMissing === 1 ? '' : 's'}</div>
|
||||
<div class="mr-missing-list-meta">${this.escapeHtml(activeHint)}</div>
|
||||
</div>
|
||||
<div class="ml-missing-list-stats">
|
||||
<span class="ml-missing-stat ml-missing-stat-exact">${stats.exact} exact</span>
|
||||
<span class="ml-missing-stat ml-missing-stat-partial">${stats.partial} partial</span>
|
||||
<span class="ml-missing-stat ml-missing-stat-none">${stats.none} no match</span>
|
||||
<div class="mr-missing-list-stats">
|
||||
<span class="mr-missing-stat mr-missing-stat-exact">${stats.exact} exact</span>
|
||||
<span class="mr-missing-stat mr-missing-stat-partial">${stats.partial} partial</span>
|
||||
<span class="mr-missing-stat mr-missing-stat-none">${stats.none} no match</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-missing-list-head">
|
||||
<span class="ml-missing-head-select">
|
||||
<input type="checkbox" class="ml-missing-select-all-check" aria-label="Select or deselect all missing models">
|
||||
<div class="mr-missing-list-head">
|
||||
<span class="mr-missing-head-select">
|
||||
<input type="checkbox" class="mr-missing-select-all-check" aria-label="Select or deselect all missing models">
|
||||
</span>
|
||||
<span>#</span>
|
||||
<span>Missing Model</span>
|
||||
@@ -232,7 +232,7 @@ export const missingBrowserMethods = {
|
||||
<span>Match</span>
|
||||
<span>Sources</span>
|
||||
</div>
|
||||
<div class="ml-missing-list">
|
||||
<div class="mr-missing-list">
|
||||
`;
|
||||
|
||||
missingModels.forEach((missing, index) => {
|
||||
@@ -251,29 +251,29 @@ export const missingBrowserMethods = {
|
||||
const nodeDisplay = this.getMissingNodeDisplay(missing);
|
||||
const nodeId = missing.node_id ?? '';
|
||||
const rowNodeHtml = nodeDisplay.canLocate
|
||||
? `<button type="button" class="ml-node-chip is-locatable ml-missing-row-node ml-missing-row-locate" data-node-id="${this.escapeHtml(String(nodeId))}" data-tooltip="Center this node in the ComfyUI graph." aria-label="Center ${this.escapeHtml(nodeDisplay.text)} in the ComfyUI graph">${this.getLocateIconHtml()}<span class="ml-missing-row-node-label">${this.escapeHtml(nodeDisplay.text)}</span></button>`
|
||||
: `<span class="ml-missing-row-node">${this.escapeHtml(nodeDisplay.text)}</span>`;
|
||||
? `<button type="button" class="mr-node-chip is-locatable mr-missing-row-node mr-missing-row-locate" data-node-id="${this.escapeHtml(String(nodeId))}" data-tooltip="Center this node in the ComfyUI graph." aria-label="Center ${this.escapeHtml(nodeDisplay.text)} in the ComfyUI graph">${this.getLocateIconHtml()}<span class="mr-missing-row-node-label">${this.escapeHtml(nodeDisplay.text)}</span></button>`
|
||||
: `<span class="mr-missing-row-node">${this.escapeHtml(nodeDisplay.text)}</span>`;
|
||||
|
||||
html += `
|
||||
<div role="button" tabindex="0"
|
||||
class="ml-missing-list-row ${isSelected ? 'is-selected' : ''} ${isBatchSelected ? 'is-batch-selected' : ''}"
|
||||
class="mr-missing-list-row ${isSelected ? 'is-selected' : ''} ${isBatchSelected ? 'is-batch-selected' : ''}"
|
||||
data-missing-key="${this.escapeHtml(key)}">
|
||||
<span class="ml-missing-row-select">
|
||||
<input type="checkbox" class="ml-missing-row-check" data-ml-no-drag="1" aria-label="Select ${this.escapeHtml(filename)}" ${isBatchSelected ? 'checked' : ''}>
|
||||
<span class="mr-missing-row-select">
|
||||
<input type="checkbox" class="mr-missing-row-check" data-ml-no-drag="1" aria-label="Select ${this.escapeHtml(filename)}" ${isBatchSelected ? 'checked' : ''}>
|
||||
</span>
|
||||
<span class="ml-missing-row-index">${index + 1}</span>
|
||||
<span class="ml-missing-row-model">
|
||||
<span class="ml-missing-row-name" data-tooltip="${this.escapeHtml(filename)}">${this.escapeHtml(formattedFilename.display)}</span>
|
||||
<span class="mr-missing-row-index">${index + 1}</span>
|
||||
<span class="mr-missing-row-model">
|
||||
<span class="mr-missing-row-name" data-tooltip="${this.escapeHtml(filename)}">${this.escapeHtml(formattedFilename.display)}</span>
|
||||
${rowNodeHtml}
|
||||
</span>
|
||||
<span class="ml-missing-row-type ${typeColorClass}">${this.escapeHtml(typeLabel)}</span>
|
||||
<span class="ml-missing-row-best" data-tooltip="${this.escapeHtml(matchDisplay)}">
|
||||
${bestMatch ? this.escapeHtml(matchDisplay) : '<span class="ml-missing-row-none">-- No local match</span>'}
|
||||
<span class="mr-missing-row-type ${typeColorClass}">${this.escapeHtml(typeLabel)}</span>
|
||||
<span class="mr-missing-row-best" data-tooltip="${this.escapeHtml(matchDisplay)}">
|
||||
${bestMatch ? this.escapeHtml(matchDisplay) : '<span class="mr-missing-row-none">-- No local match</span>'}
|
||||
</span>
|
||||
<span class="ml-missing-row-match ml-missing-row-match-${matchClass}">
|
||||
<span class="mr-missing-row-match mr-missing-row-match-${matchClass}">
|
||||
<strong>${bestMatch ? `${confidence.toFixed(confidence % 1 ? 1 : 0)}%` : '--'}</strong>
|
||||
</span>
|
||||
<span class="ml-missing-row-sources">${this.renderMissingSourcesSummary(missing)}</span>
|
||||
<span class="mr-missing-row-sources">${this.renderMissingSourcesSummary(missing)}</span>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
@@ -281,8 +281,8 @@ export const missingBrowserMethods = {
|
||||
html += `
|
||||
</div>
|
||||
</section>
|
||||
<div class="ml-missing-browser-splitter" role="separator" aria-orientation="vertical" aria-label="Resize missing model panes" tabindex="0"></div>
|
||||
<section class="ml-missing-detail-pane" aria-label="Missing model details" style="${splitStyle}">
|
||||
<div class="mr-missing-browser-splitter" role="separator" aria-orientation="vertical" aria-label="Resize missing model panes" tabindex="0"></div>
|
||||
<section class="mr-missing-detail-pane" aria-label="Missing model details" style="${splitStyle}">
|
||||
${detailMissing ? this.renderMissingModel(detailMissing, detailIndex) : this.renderStatusMessage('Select a missing model to inspect details.', 'info')}
|
||||
</section>
|
||||
</div>
|
||||
@@ -300,7 +300,7 @@ export const missingBrowserMethods = {
|
||||
this.displayMissingModels(container, data);
|
||||
};
|
||||
|
||||
const selectAllCheckbox = container.querySelector('.ml-missing-select-all-check');
|
||||
const selectAllCheckbox = container.querySelector('.mr-missing-select-all-check');
|
||||
if (selectAllCheckbox) {
|
||||
this.updateBatchSelectAllCheckbox();
|
||||
selectAllCheckbox.addEventListener('click', (event) => {
|
||||
@@ -317,8 +317,8 @@ export const missingBrowserMethods = {
|
||||
});
|
||||
}
|
||||
|
||||
container.querySelectorAll('.ml-missing-list-row').forEach(row => {
|
||||
const checkbox = row.querySelector('.ml-missing-row-check');
|
||||
container.querySelectorAll('.mr-missing-list-row').forEach(row => {
|
||||
const checkbox = row.querySelector('.mr-missing-row-check');
|
||||
if (checkbox) {
|
||||
checkbox.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
@@ -348,7 +348,7 @@ export const missingBrowserMethods = {
|
||||
}
|
||||
|
||||
row.addEventListener('click', (event) => {
|
||||
const clickedLocate = event.target instanceof Element && event.target.closest('.ml-missing-row-locate');
|
||||
const clickedLocate = event.target instanceof Element && event.target.closest('.mr-missing-row-locate');
|
||||
if (clickedLocate) return;
|
||||
selectRow(row);
|
||||
});
|
||||
@@ -360,7 +360,7 @@ export const missingBrowserMethods = {
|
||||
});
|
||||
});
|
||||
|
||||
container.querySelectorAll('.ml-missing-row-locate').forEach(button => {
|
||||
container.querySelectorAll('.mr-missing-row-locate').forEach(button => {
|
||||
button.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
@@ -378,8 +378,8 @@ export const missingBrowserMethods = {
|
||||
},
|
||||
|
||||
wireMissingBrowserSplitter(container) {
|
||||
const browser = container.querySelector('.ml-missing-browser');
|
||||
const splitter = browser?.querySelector('.ml-missing-browser-splitter');
|
||||
const browser = container.querySelector('.mr-missing-browser');
|
||||
const splitter = browser?.querySelector('.mr-missing-browser-splitter');
|
||||
if (!(browser instanceof HTMLElement) || !(splitter instanceof HTMLElement)) return;
|
||||
|
||||
this.restoreMissingBrowserSplitWidth(browser);
|
||||
@@ -390,7 +390,7 @@ export const missingBrowserMethods = {
|
||||
});
|
||||
|
||||
splitter.addEventListener('dblclick', () => {
|
||||
browser.querySelector('.ml-missing-detail-pane')?.style.removeProperty('--ml-missing-detail-track');
|
||||
browser.querySelector('.mr-missing-detail-pane')?.style.removeProperty('--mr-missing-detail-track');
|
||||
try {
|
||||
localStorage.removeItem(this.missingBrowserSplitStorageKey);
|
||||
} catch (e) {}
|
||||
@@ -425,7 +425,7 @@ export const missingBrowserMethods = {
|
||||
startMissingBrowserSplitDrag(event, browser) {
|
||||
if (!(browser instanceof HTMLElement)) return;
|
||||
|
||||
const detailPane = browser.querySelector('.ml-missing-detail-pane');
|
||||
const detailPane = browser.querySelector('.mr-missing-detail-pane');
|
||||
if (!(detailPane instanceof HTMLElement)) return;
|
||||
|
||||
event.preventDefault();
|
||||
@@ -463,7 +463,7 @@ export const missingBrowserMethods = {
|
||||
this._missingBrowserSplitFrame = requestAnimationFrame(() => {
|
||||
this._missingBrowserSplitFrame = null;
|
||||
if (!this._missingBrowserSplitDragging || !this._missingBrowserSplitDetailPane || !this._pendingMissingBrowserSplitWidth) return;
|
||||
this._missingBrowserSplitDetailPane.style.setProperty('--ml-missing-detail-track', `${this._pendingMissingBrowserSplitWidth}px`);
|
||||
this._missingBrowserSplitDetailPane.style.setProperty('--mr-missing-detail-track', `${this._pendingMissingBrowserSplitWidth}px`);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -477,7 +477,7 @@ export const missingBrowserMethods = {
|
||||
this._missingBrowserSplitFrame = null;
|
||||
}
|
||||
if (this._missingBrowserSplitDetailPane && this._pendingMissingBrowserSplitWidth) {
|
||||
this._missingBrowserSplitDetailPane.style.setProperty('--ml-missing-detail-track', `${this._pendingMissingBrowserSplitWidth}px`);
|
||||
this._missingBrowserSplitDetailPane.style.setProperty('--mr-missing-detail-track', `${this._pendingMissingBrowserSplitWidth}px`);
|
||||
try {
|
||||
localStorage.setItem(this.missingBrowserSplitStorageKey, String(this._pendingMissingBrowserSplitWidth));
|
||||
} catch (e) {}
|
||||
@@ -495,7 +495,7 @@ export const missingBrowserMethods = {
|
||||
},
|
||||
|
||||
resizeMissingBrowserDetailBy(browser, delta) {
|
||||
const detailPane = browser.querySelector('.ml-missing-detail-pane');
|
||||
const detailPane = browser.querySelector('.mr-missing-detail-pane');
|
||||
if (!(detailPane instanceof HTMLElement)) return;
|
||||
|
||||
this.setMissingBrowserDetailWidth(browser, detailPane.getBoundingClientRect().width + delta);
|
||||
@@ -504,9 +504,9 @@ export const missingBrowserMethods = {
|
||||
setMissingBrowserDetailWidth(browser, width, { persist = true } = {}) {
|
||||
const bounds = this.getMissingBrowserSplitBounds(browser);
|
||||
const nextWidth = Math.round(Math.max(bounds.min, Math.min(bounds.max, width)));
|
||||
const detailPane = browser.querySelector('.ml-missing-detail-pane');
|
||||
const detailPane = browser.querySelector('.mr-missing-detail-pane');
|
||||
const target = detailPane instanceof HTMLElement ? detailPane : browser;
|
||||
target.style.setProperty('--ml-missing-detail-track', `${nextWidth}px`);
|
||||
target.style.setProperty('--mr-missing-detail-track', `${nextWidth}px`);
|
||||
|
||||
if (persist) {
|
||||
try {
|
||||
@@ -517,7 +517,7 @@ export const missingBrowserMethods = {
|
||||
|
||||
getMissingBrowserSplitBounds(browser) {
|
||||
const browserRect = browser.getBoundingClientRect();
|
||||
const splitter = browser.querySelector('.ml-missing-browser-splitter');
|
||||
const splitter = browser.querySelector('.mr-missing-browser-splitter');
|
||||
const splitterWidth = splitter instanceof HTMLElement
|
||||
? splitter.getBoundingClientRect().width
|
||||
: 10;
|
||||
@@ -569,12 +569,12 @@ export const missingBrowserMethods = {
|
||||
const folder = getFolder(m);
|
||||
const isHighlighted = i === highlightIdx;
|
||||
const folderDisplay = folder ? folder.replace(/\\/g, '/').replace(/:/, '') : '';
|
||||
html += `<div data-idx="${allModels.indexOf(m)}" class="ml-combo-option ${isHighlighted ? 'is-highlighted' : ''}">`;
|
||||
html += `<div class="ml-combo-option-row">`;
|
||||
html += `<div data-idx="${allModels.indexOf(m)}" class="mr-combo-option ${isHighlighted ? 'is-highlighted' : ''}">`;
|
||||
html += `<div class="mr-combo-option-row">`;
|
||||
html += `<code>${this.escapeHtml(label)}</code>`;
|
||||
html += `</div>`;
|
||||
if (folderDisplay) {
|
||||
html += `<div class="ml-combo-folder" data-tooltip="${this.escapeHtml(folderDisplay)}">Folder ${this.escapeHtml(folderDisplay)}</div>`;
|
||||
html += `<div class="mr-combo-folder" data-tooltip="${this.escapeHtml(folderDisplay)}">Folder ${this.escapeHtml(folderDisplay)}</div>`;
|
||||
}
|
||||
html += `</div>`;
|
||||
}
|
||||
@@ -604,16 +604,16 @@ export const missingBrowserMethods = {
|
||||
comboInput.addEventListener('input', debouncedFilter);
|
||||
comboInput.addEventListener('focus', () => {
|
||||
if (comboList) {
|
||||
comboList.classList.remove('ml-is-hidden');
|
||||
comboList.classList.add('ml-is-visible');
|
||||
comboList.classList.remove('mr-is-hidden');
|
||||
comboList.classList.add('mr-is-visible');
|
||||
}
|
||||
populateComboOptions(comboInput.value);
|
||||
});
|
||||
comboInput.addEventListener('blur', () => {
|
||||
setTimeout(() => {
|
||||
if (comboList) {
|
||||
comboList.classList.remove('ml-is-visible');
|
||||
comboList.classList.add('ml-is-hidden');
|
||||
comboList.classList.remove('mr-is-visible');
|
||||
comboList.classList.add('mr-is-hidden');
|
||||
}
|
||||
}, 200);
|
||||
});
|
||||
@@ -630,8 +630,8 @@ export const missingBrowserMethods = {
|
||||
const state = this.searchResultCache.get(this.getMissingSearchKey(missing));
|
||||
const searchResultsDiv = container.querySelector(`#search-results-${missing.node_id}-${missing.widget_index}`);
|
||||
if (state && searchResultsDiv && this.hasRenderableSearchState(state)) {
|
||||
searchResultsDiv.classList.remove('ml-is-hidden');
|
||||
searchResultsDiv.classList.add('ml-is-visible');
|
||||
searchResultsDiv.classList.remove('mr-is-hidden');
|
||||
searchResultsDiv.classList.add('mr-is-visible');
|
||||
this.displaySearchResults(missing, state, searchResultsDiv);
|
||||
}
|
||||
},
|
||||
@@ -753,14 +753,14 @@ export const missingBrowserMethods = {
|
||||
const nodeChipText = this.escapeHtml(nodeDisplay.text);
|
||||
|
||||
// Start card
|
||||
let html = `<div class="ml-card">`;
|
||||
let html = `<div class="mr-card">`;
|
||||
|
||||
// Card Header: Filename as headline + node chip
|
||||
html += `<div class="ml-card-header">`;
|
||||
html += `<div class="ml-card-title-wrap">`;
|
||||
html += `<div class="mr-card-header">`;
|
||||
html += `<div class="mr-card-title-wrap">`;
|
||||
|
||||
const titleMetaParts = [];
|
||||
let titlePrimaryHtml = `<span class="ml-card-title-primary" data-tooltip="${this.escapeHtml(missingFilename.full)}">${missingFilename.display}</span>`;
|
||||
let titlePrimaryHtml = `<span class="mr-card-title-primary" data-tooltip="${this.escapeHtml(missingFilename.full)}">${missingFilename.display}</span>`;
|
||||
let titleSecondaryHtml = '';
|
||||
|
||||
const modelId = missing.urn_model_id || missing.urn?.model_id;
|
||||
@@ -769,40 +769,40 @@ export const missingBrowserMethods = {
|
||||
const urnLoadingId = `urn-loading-${missing.node_id}-${missing.widget_index}`;
|
||||
|
||||
if (missing.is_urn) {
|
||||
titleMetaParts.push(`<span class="ml-card-title-eyebrow" data-tooltip="${this.escapeHtml(missingFilename.full)}">${missingFilename.display}</span>`);
|
||||
titleMetaParts.push(`<span class="mr-card-title-eyebrow" data-tooltip="${this.escapeHtml(missingFilename.full)}">${missingFilename.display}</span>`);
|
||||
}
|
||||
|
||||
if (missing.is_urn && !missing.civitai_info) {
|
||||
// URN without info - show Loading and fetch async in background
|
||||
titlePrimaryHtml = `<span class="ml-card-title-primary" id="${urnLoadingId}">Resolving CivitAI model...</span>`;
|
||||
titlePrimaryHtml = `<span class="mr-card-title-primary" id="${urnLoadingId}">Resolving CivitAI model...</span>`;
|
||||
setTimeout(() => this.resolveUrnAsync(modelId, versionId, urnLoadingId, modelUrl), 10);
|
||||
} else if (missing.is_urn && missing.civitai_info) {
|
||||
// URN with resolved info - show model name/version
|
||||
const civitaiInfo = missing.civitai_info;
|
||||
const civitaiLabelHtml = this.renderVersionedModelNameHtml(civitaiInfo.model_name, civitaiInfo.version_name);
|
||||
if (civitaiLabelHtml) {
|
||||
const linkHtml = modelUrl ? `<a href="${modelUrl}" target="_blank" class="ml-inline-civitai-link">${civitaiLabelHtml}</a>` : `<span class="ml-inline-civitai-link">${civitaiLabelHtml}</span>`;
|
||||
titlePrimaryHtml = `<span class="ml-card-title-primary">${linkHtml}</span>`;
|
||||
const linkHtml = modelUrl ? `<a href="${modelUrl}" target="_blank" class="mr-inline-civitai-link">${civitaiLabelHtml}</a>` : `<span class="mr-inline-civitai-link">${civitaiLabelHtml}</span>`;
|
||||
titlePrimaryHtml = `<span class="mr-card-title-primary">${linkHtml}</span>`;
|
||||
}
|
||||
if (civitaiInfo.expected_filename) {
|
||||
titleSecondaryHtml = `<span class="ml-card-title-secondary">Expected file: ${civitaiInfo.expected_filename}</span>`;
|
||||
titleSecondaryHtml = `<span class="mr-card-title-secondary">Expected file: ${civitaiInfo.expected_filename}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
html += `<div class="ml-card-title-meta">`;
|
||||
html += `<div class="mr-card-title-meta">`;
|
||||
html += titleMetaParts.join('');
|
||||
html += `<h3 class="ml-card-title">${titlePrimaryHtml}</h3>`;
|
||||
html += `<h3 class="mr-card-title">${titlePrimaryHtml}</h3>`;
|
||||
if (titleSecondaryHtml) {
|
||||
html += titleSecondaryHtml;
|
||||
}
|
||||
html += `</div>`;
|
||||
const locateId = `locate-${missing.node_id}-${missing.widget_index}`;
|
||||
const nodeChipClasses = missing.is_top_level !== false ? 'ml-node-chip is-locatable' : 'ml-node-chip';
|
||||
const nodeChipClasses = missing.is_top_level !== false ? 'mr-node-chip is-locatable' : 'mr-node-chip';
|
||||
const nodeChipTitle = missing.is_top_level !== false ? 'Center this node in the ComfyUI graph.' : '';
|
||||
|
||||
html += `<div class="ml-card-subtitle">`;
|
||||
html += `<div class="mr-card-subtitle">`;
|
||||
if (missing.category) {
|
||||
html += `<span class="ml-category-chip ${this.getModelTypeColorClass(missing.category)}" data-tooltip="${this.escapeHtml(missing.category)}">${this.getCategoryDisplayName(missing.category)}</span>`;
|
||||
html += `<span class="mr-category-chip ${this.getModelTypeColorClass(missing.category)}" data-tooltip="${this.escapeHtml(missing.category)}">${this.getCategoryDisplayName(missing.category)}</span>`;
|
||||
}
|
||||
html += `<span id="${locateId}" class="${nodeChipClasses}"${nodeChipTitle ? ` data-tooltip="${this.escapeHtml(nodeChipTitle)}"` : ''}>`;
|
||||
if (missing.is_top_level !== false) {
|
||||
@@ -815,34 +815,34 @@ export const missingBrowserMethods = {
|
||||
|
||||
// Selected bar - shows if this slot has a queued selection (BELOW card header)
|
||||
const selectedBarId = `selected-bar-${missing.node_id}-${missing.widget_index}`;
|
||||
html += `<div id="${selectedBarId}" class="model-linker-selected"></div>`;
|
||||
html += `<div id="${selectedBarId}" class="model-resolver-selected"></div>`;
|
||||
|
||||
// Two-column layout
|
||||
html += `<div class="ml-columns">`;
|
||||
html += `<div class="mr-columns">`;
|
||||
|
||||
// LEFT COLUMN: Local Matches
|
||||
html += `<div class="ml-column">`;
|
||||
html += `<div class="ml-column-header">Local Matches</div>`;
|
||||
html += `<div class="mr-column">`;
|
||||
html += `<div class="mr-column-header">Local Matches</div>`;
|
||||
html += `<div id="local-matches-body-${missing.node_id}-${missing.widget_index}">`;
|
||||
html += this.renderLocalMatchesContent(missing, missingIndex);
|
||||
html += `</div>`;
|
||||
|
||||
// Add all-models search picker - combo-style dropdown
|
||||
const comboId = `combo-${missing.node_id}-${missing.widget_index}`;
|
||||
html += `<div class="ml-combo-section">`;
|
||||
html += `<div class="ml-combo-row">`;
|
||||
html += `<label class="ml-combo-label">Model</label>`;
|
||||
html += `<input id="combo-input-${comboId}" class="ml-combo-input" type="text" placeholder="Type to filter local models...">`;
|
||||
html += `<button id="combo-refresh-${comboId}" type="button" aria-label="Reload local model list" data-tooltip="Reload local model list" class="ml-btn ml-btn-secondary ml-btn-sm ml-btn-icon-only ml-combo-refresh-btn">${getSvgIcon('refreshCw', 'currentColor', 'ml-combo-refresh-icon')}</button>`;
|
||||
html += `<div class="mr-combo-section">`;
|
||||
html += `<div class="mr-combo-row">`;
|
||||
html += `<label class="mr-combo-label">Model</label>`;
|
||||
html += `<input id="combo-input-${comboId}" class="mr-combo-input" type="text" placeholder="Type to filter local models...">`;
|
||||
html += `<button id="combo-refresh-${comboId}" type="button" aria-label="Reload local model list" data-tooltip="Reload local model list" class="mr-btn mr-btn-secondary mr-btn-sm mr-btn-icon-only mr-combo-refresh-btn">${getSvgIcon('refreshCw', 'currentColor', 'mr-combo-refresh-icon')}</button>`;
|
||||
html += `</div>`;
|
||||
html += `<div id="combo-list-${comboId}" class="ml-combo-list"></div>`;
|
||||
html += `<div id="combo-list-${comboId}" class="mr-combo-list"></div>`;
|
||||
html += `</div>`;
|
||||
|
||||
html += `</div>`; // End left column
|
||||
|
||||
// RIGHT COLUMN: Download Option
|
||||
html += `<div class="ml-column">`;
|
||||
html += `<div class="ml-column-header">Download</div>`;
|
||||
html += `<div class="mr-column">`;
|
||||
html += `<div class="mr-column-header">Download</div>`;
|
||||
|
||||
const filename = missing.original_path?.split('/').pop()?.split('\\').pop() || '';
|
||||
const downloadSource = missing.download_source;
|
||||
@@ -852,26 +852,26 @@ export const missingBrowserMethods = {
|
||||
html += this.renderKnownDownloadPanel(missing, downloadSource);
|
||||
} else if (perfectMatches.length > 0) {
|
||||
// Has perfect local match - download not needed, but allow online re-check.
|
||||
html += `<div class="ml-download-section">`;
|
||||
html += `<div class="mr-download-section">`;
|
||||
html += this.renderSearchControls(missing, { buttonText: 'Search Online' });
|
||||
html += this.renderDownloadTargetControls(missing, missing.category || 'checkpoints');
|
||||
html += `</div>`;
|
||||
html += `<div id="search-results-${missing.node_id}-${missing.widget_index}" class="ml-search-results"></div>`;
|
||||
html += `<div id="search-results-${missing.node_id}-${missing.widget_index}" class="mr-search-results"></div>`;
|
||||
} else if (missing.is_urn) {
|
||||
html += `<div id="${urnDownloadId}" class="ml-download-section">`;
|
||||
html += `<div class="ml-download-info">Resolving CivitAI download for this URN...</div>`;
|
||||
html += `<div id="${urnDownloadId}" class="mr-download-section">`;
|
||||
html += `<div class="mr-download-info">Resolving CivitAI download for this URN...</div>`;
|
||||
html += `</div>`;
|
||||
} else {
|
||||
// No known download - offer search
|
||||
html += `<div class="ml-download-section">`;
|
||||
html += `<div class="mr-download-section">`;
|
||||
html += this.renderSearchControls(missing);
|
||||
html += this.renderDownloadTargetControls(missing, missing.category || 'checkpoints');
|
||||
html += `</div>`;
|
||||
html += `<div id="search-results-${missing.node_id}-${missing.widget_index}" class="ml-search-results"></div>`;
|
||||
html += `<div id="search-results-${missing.node_id}-${missing.widget_index}" class="mr-search-results"></div>`;
|
||||
}
|
||||
|
||||
// Progress container (for downloads)
|
||||
html += `<div id="download-progress-${missing.node_id}-${missing.widget_index}" class="ml-download-progress-slot"></div>`;
|
||||
html += `<div id="download-progress-${missing.node_id}-${missing.widget_index}" class="mr-download-progress-slot"></div>`;
|
||||
|
||||
html += `</div>`; // End right column
|
||||
html += `</div>`; // End columns
|
||||
@@ -890,30 +890,30 @@ export const missingBrowserMethods = {
|
||||
if (type === 'success') {
|
||||
children.push($el("span", {
|
||||
textContent: "✓",
|
||||
className: "ml-notification-icon"
|
||||
className: "mr-notification-icon"
|
||||
}));
|
||||
} else if (type === 'error') {
|
||||
children.push($el("span", {
|
||||
textContent: "×",
|
||||
className: "ml-notification-icon"
|
||||
className: "mr-notification-icon"
|
||||
}));
|
||||
} else if (type === 'info') {
|
||||
children.push($el("span", {
|
||||
textContent: "ℹ",
|
||||
className: "ml-notification-icon"
|
||||
className: "mr-notification-icon"
|
||||
}));
|
||||
}
|
||||
|
||||
// Create notification banner
|
||||
const notification = $el("div", {
|
||||
className: `ml-notification ml-notification--${type}`
|
||||
className: `mr-notification mr-notification--${type}`
|
||||
}, [
|
||||
...children,
|
||||
$el("span", {
|
||||
textContent: message
|
||||
}),
|
||||
$el("button", {
|
||||
className: "ml-notification-close",
|
||||
className: "mr-notification-close",
|
||||
textContent: "×",
|
||||
onclick: () => {
|
||||
if (notification.parentNode) {
|
||||
@@ -1,4 +1,4 @@
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { api } from "../../../../../scripts/api.js";
|
||||
import { $el } from "../../../../../scripts/ui.js";
|
||||
import { getSvgIcon } from "../../utils/icon_utils.js";
|
||||
@@ -74,7 +74,7 @@ export const modelInfoMethods = {
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await api.fetchApi('/model_linker/open-containing-folder', {
|
||||
const response = await api.fetchApi('/model_resolver/open-containing-folder', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ path })
|
||||
@@ -84,7 +84,7 @@ export const modelInfoMethods = {
|
||||
throw new Error(`Open folder failed: ${response.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Model Linker: Open folder error:', error);
|
||||
console.error('Model Resolver: Open folder error:', error);
|
||||
this.showNotification('Failed to open containing folder', 'error');
|
||||
}
|
||||
},
|
||||
@@ -100,7 +100,7 @@ export const modelInfoMethods = {
|
||||
|
||||
try {
|
||||
// Search CivitAI for this model using hash (pass resolved_path for hash lookup)
|
||||
const response = await api.fetchApi('/model_linker/civitai-search', {
|
||||
const response = await api.fetchApi('/model_resolver/civitai-search', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -125,7 +125,7 @@ export const modelInfoMethods = {
|
||||
window.open(searchUrl, '_blank');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Model Linker: Error searching CivitAI:', e);
|
||||
console.error('Model Resolver: Error searching CivitAI:', e);
|
||||
// Fall back to direct search
|
||||
const searchName = name.replace(/\.(safetensors|ckpt|pt|pth|bin|pkl|sft|onnx|gguf)$/i, '');
|
||||
const searchUrl = `https://civitai.com/search?q=${encodeURIComponent(searchName)}`;
|
||||
@@ -160,9 +160,9 @@ export const modelInfoMethods = {
|
||||
this.bindInfoDialogResizePersistence(dialog);
|
||||
|
||||
// Add close handlers
|
||||
const closeBtn = dialog.querySelector('.ml-info-dialog-close');
|
||||
const footerCloseBtn = dialog.querySelector('.ml-info-dialog-close-btn');
|
||||
const backdrop = dialog.querySelector('.ml-info-dialog-backdrop');
|
||||
const closeBtn = dialog.querySelector('.mr-info-dialog-close');
|
||||
const footerCloseBtn = dialog.querySelector('.mr-info-dialog-close-btn');
|
||||
const backdrop = dialog.querySelector('.mr-info-dialog-backdrop');
|
||||
|
||||
if (closeBtn) {
|
||||
closeBtn.addEventListener('click', () => this.closeInfoDialog(dialog));
|
||||
@@ -190,80 +190,80 @@ export const modelInfoMethods = {
|
||||
const loraDisplayName = loraName.replace(/\.(safetensors|ckpt|pt|pth|bin|pkl|sft|onnx|gguf)$/i, '');
|
||||
|
||||
const dialog = document.createElement('div');
|
||||
dialog.className = 'ml-info-dialog-backdrop';
|
||||
dialog.className = 'mr-info-dialog-backdrop';
|
||||
dialog._selectedTrainedWords = new Set();
|
||||
dialog.innerHTML = `
|
||||
<div class="ml-info-dialog">
|
||||
<div class="ml-info-dialog-header">
|
||||
<h3 class="ml-info-dialog-title">${loraDisplayName}</h3>
|
||||
<button class="ml-info-dialog-close">×</button>
|
||||
<div class="mr-info-dialog">
|
||||
<div class="mr-info-dialog-header">
|
||||
<h3 class="mr-info-dialog-title">${loraDisplayName}</h3>
|
||||
<button class="mr-info-dialog-close">×</button>
|
||||
</div>
|
||||
<div class="ml-info-dialog-content">
|
||||
<div class="ml-info-dialog-loading">Loading...</div>
|
||||
<div class="ml-info-dialog-body ml-hidden-initial">
|
||||
<div class="ml-info-area">
|
||||
<span class="ml-info-tag ml-info-type"></span>
|
||||
<span class="ml-info-tag ml-info-basemodel"></span>
|
||||
<div class="mr-info-dialog-content">
|
||||
<div class="mr-info-dialog-loading">Loading...</div>
|
||||
<div class="mr-info-dialog-body mr-hidden-initial">
|
||||
<div class="mr-info-area">
|
||||
<span class="mr-info-tag mr-info-type"></span>
|
||||
<span class="mr-info-tag mr-info-basemodel"></span>
|
||||
</div>
|
||||
<table class="ml-info-table">
|
||||
<table class="mr-info-table">
|
||||
<tbody>
|
||||
<tr class="ml-info-file-row">
|
||||
<td><span>File <span class="ml-tooltip-badge" data-tooltip="The model file name found locally or returned by CivitAI.">?</span></span></td>
|
||||
<td><span class="ml-info-file"></span></td>
|
||||
<tr class="mr-info-file-row">
|
||||
<td><span>File <span class="mr-tooltip-badge" data-tooltip="The model file name found locally or returned by CivitAI.">?</span></span></td>
|
||||
<td><span class="mr-info-file"></span></td>
|
||||
</tr>
|
||||
<tr class="ml-info-hash-row">
|
||||
<td><span>Hash (sha256) <span class="ml-tooltip-badge" data-tooltip="Unique fingerprint of the local file. Model Linker uses it to confirm the exact CivitAI version.">?</span></span></td>
|
||||
<td><span class="ml-info-hash"></span></td>
|
||||
<tr class="mr-info-hash-row">
|
||||
<td><span>Hash (sha256) <span class="mr-tooltip-badge" data-tooltip="Unique fingerprint of the local file. Model Resolver uses it to confirm the exact CivitAI version.">?</span></span></td>
|
||||
<td><span class="mr-info-hash"></span></td>
|
||||
</tr>
|
||||
<tr class="ml-info-civitai-row">
|
||||
<td><span>CivitAI <span class="ml-tooltip-badge" data-tooltip="Opens the matching CivitAI model or version page when one was found.">?</span></span></td>
|
||||
<td><span class="ml-info-civitai-link"></span></td>
|
||||
<tr class="mr-info-civitai-row">
|
||||
<td><span>CivitAI <span class="mr-tooltip-badge" data-tooltip="Opens the matching CivitAI model or version page when one was found.">?</span></span></td>
|
||||
<td><span class="mr-info-civitai-link"></span></td>
|
||||
</tr>
|
||||
<tr class="ml-info-name-row">
|
||||
<td><span>Name <span class="ml-tooltip-badge" data-tooltip="Model name from CivitAI or local metadata.">?</span></span></td>
|
||||
<td><span class="ml-info-name"></span></td>
|
||||
<tr class="mr-info-name-row">
|
||||
<td><span>Name <span class="mr-tooltip-badge" data-tooltip="Model name from CivitAI or local metadata.">?</span></span></td>
|
||||
<td><span class="mr-info-name"></span></td>
|
||||
</tr>
|
||||
<tr class="ml-info-basemodel-row">
|
||||
<td><span>Base Model <span class="ml-tooltip-badge" data-tooltip="Base model this resource was made for, for example SD1.5, SDXL or Flux.">?</span></span></td>
|
||||
<td><span class="ml-info-base-model"></span></td>
|
||||
<tr class="mr-info-basemodel-row">
|
||||
<td><span>Base Model <span class="mr-tooltip-badge" data-tooltip="Base model this resource was made for, for example SD1.5, SDXL or Flux.">?</span></span></td>
|
||||
<td><span class="mr-info-base-model"></span></td>
|
||||
</tr>
|
||||
<tr class="ml-info-trainedwords-row ml-hidden-initial">
|
||||
<tr class="mr-info-trainedwords-row mr-hidden-initial">
|
||||
<td>
|
||||
<div class="ml-info-trained-words-label">
|
||||
Trained Words <span class="ml-tooltip-badge" data-tooltip="Trigger words recommended by the model author. Click the words you want, then copy them into your prompt.">?</span>
|
||||
<small class="ml-info-trained-words-meta">
|
||||
<span class="ml-info-trained-words-count">0 selected</span>
|
||||
<button type="button" class="ml-info-copy-trained-words" disabled>Copy</button>
|
||||
<div class="mr-info-trained-words-label">
|
||||
Trained Words <span class="mr-tooltip-badge" data-tooltip="Trigger words recommended by the model author. Click the words you want, then copy them into your prompt.">?</span>
|
||||
<small class="mr-info-trained-words-meta">
|
||||
<span class="mr-info-trained-words-count">0 selected</span>
|
||||
<button type="button" class="mr-info-copy-trained-words" disabled>Copy</button>
|
||||
</small>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="ml-info-trained-words-hint">Click words to select them.</div>
|
||||
<div class="ml-info-trained-words"></div>
|
||||
<div class="mr-info-trained-words-hint">Click words to select them.</div>
|
||||
<div class="mr-info-trained-words"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="ml-info-clipskip-row ml-hidden-initial">
|
||||
<td><span>Clip Skip <span class="ml-tooltip-badge" data-tooltip="Recommended Clip Skip value from the model author, if one is provided.">?</span></span></td>
|
||||
<td><span class="ml-info-clip-skip"></span></td>
|
||||
<tr class="mr-info-clipskip-row mr-hidden-initial">
|
||||
<td><span>Clip Skip <span class="mr-tooltip-badge" data-tooltip="Recommended Clip Skip value from the model author, if one is provided.">?</span></span></td>
|
||||
<td><span class="mr-info-clip-skip"></span></td>
|
||||
</tr>
|
||||
<tr class="ml-info-description-row ml-hidden-initial">
|
||||
<td><span>Description <span class="ml-tooltip-badge" data-tooltip="Model description from CivitAI or local metadata. Long descriptions are shortened until you click Show more.">?</span></span></td>
|
||||
<tr class="mr-info-description-row mr-hidden-initial">
|
||||
<td><span>Description <span class="mr-tooltip-badge" data-tooltip="Model description from CivitAI or local metadata. Long descriptions are shortened until you click Show more.">?</span></span></td>
|
||||
<td>
|
||||
<div class="ml-info-description-wrap">
|
||||
<div class="ml-info-description"></div>
|
||||
<div class="ml-info-description-actions ml-hidden-initial">
|
||||
<button type="button" class="ml-info-description-toggle">Show more</button>
|
||||
<div class="mr-info-description-wrap">
|
||||
<div class="mr-info-description"></div>
|
||||
<div class="mr-info-description-actions mr-hidden-initial">
|
||||
<button type="button" class="mr-info-description-toggle">Show more</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="ml-info-images"></div>
|
||||
<div class="mr-info-images"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-info-dialog-footer">
|
||||
<button class="ml-btn ml-btn-secondary ml-info-dialog-close-btn">Close</button>
|
||||
<div class="mr-info-dialog-footer">
|
||||
<button class="mr-btn mr-btn-secondary mr-info-dialog-close-btn">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -400,8 +400,8 @@ export const modelInfoMethods = {
|
||||
updateSelectedTrainedWordsSummary(dialog) {
|
||||
if (!dialog) return;
|
||||
|
||||
const countEl = dialog.querySelector('.ml-info-trained-words-count');
|
||||
const copyBtn = dialog.querySelector('.ml-info-copy-trained-words');
|
||||
const countEl = dialog.querySelector('.mr-info-trained-words-count');
|
||||
const copyBtn = dialog.querySelector('.mr-info-copy-trained-words');
|
||||
const selected = dialog._selectedTrainedWords instanceof Set
|
||||
? Array.from(dialog._selectedTrainedWords)
|
||||
: [];
|
||||
@@ -420,7 +420,7 @@ export const modelInfoMethods = {
|
||||
dialog.dataset.mlInfoBound = 'true';
|
||||
|
||||
dialog.addEventListener('click', async (event) => {
|
||||
const wordBtn = event.target.closest('.ml-info-trained-word');
|
||||
const wordBtn = event.target.closest('.mr-info-trained-word');
|
||||
if (wordBtn && dialog.contains(wordBtn)) {
|
||||
const word = wordBtn.dataset.word || '';
|
||||
if (word) {
|
||||
@@ -443,7 +443,7 @@ export const modelInfoMethods = {
|
||||
return;
|
||||
}
|
||||
|
||||
const copyBtn = event.target.closest('.ml-info-copy-trained-words');
|
||||
const copyBtn = event.target.closest('.mr-info-copy-trained-words');
|
||||
if (copyBtn && dialog.contains(copyBtn)) {
|
||||
const words = dialog._selectedTrainedWords instanceof Set
|
||||
? Array.from(dialog._selectedTrainedWords)
|
||||
@@ -455,7 +455,7 @@ export const modelInfoMethods = {
|
||||
await navigator.clipboard.writeText(words.join(', '));
|
||||
copyBtn.textContent = 'Copied';
|
||||
} catch (error) {
|
||||
console.error('Model Linker: Failed to copy trained words:', error);
|
||||
console.error('Model Resolver: Failed to copy trained words:', error);
|
||||
copyBtn.textContent = 'Failed';
|
||||
}
|
||||
|
||||
@@ -467,9 +467,9 @@ export const modelInfoMethods = {
|
||||
return;
|
||||
}
|
||||
|
||||
const descToggleBtn = event.target.closest('.ml-info-description-toggle');
|
||||
const descToggleBtn = event.target.closest('.mr-info-description-toggle');
|
||||
if (descToggleBtn && dialog.contains(descToggleBtn)) {
|
||||
const descEl = dialog.querySelector('.ml-info-description');
|
||||
const descEl = dialog.querySelector('.mr-info-description');
|
||||
if (!descEl) return;
|
||||
|
||||
const isExpanded = descEl.classList.toggle('is-expanded');
|
||||
@@ -479,7 +479,7 @@ export const modelInfoMethods = {
|
||||
},
|
||||
|
||||
getInfoDialogElement(dialog) {
|
||||
return dialog?.querySelector?.('.ml-info-dialog') || null;
|
||||
return dialog?.querySelector?.('.mr-info-dialog') || null;
|
||||
},
|
||||
|
||||
restoreInfoDialogSize(dialog) {
|
||||
@@ -487,7 +487,7 @@ export const modelInfoMethods = {
|
||||
if (!panel) return;
|
||||
|
||||
try {
|
||||
const saved = JSON.parse(localStorage.getItem('model_linker_info_dialog_size') || 'null');
|
||||
const saved = JSON.parse(localStorage.getItem('model_resolver_info_dialog_size') || 'null');
|
||||
if (!saved || typeof saved !== 'object') return;
|
||||
|
||||
const width = Number(saved.w);
|
||||
@@ -502,7 +502,7 @@ export const modelInfoMethods = {
|
||||
panel.style.width = `${clampedWidth}px`;
|
||||
panel.style.height = `${clampedHeight}px`;
|
||||
} catch (error) {
|
||||
console.warn('Model Linker: Failed to restore info dialog size:', error);
|
||||
console.warn('Model Resolver: Failed to restore info dialog size:', error);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -516,9 +516,9 @@ export const modelInfoMethods = {
|
||||
if (!width || !height) return;
|
||||
|
||||
try {
|
||||
localStorage.setItem('model_linker_info_dialog_size', JSON.stringify({ w: width, h: height }));
|
||||
localStorage.setItem('model_resolver_info_dialog_size', JSON.stringify({ w: width, h: height }));
|
||||
} catch (error) {
|
||||
console.warn('Model Linker: Failed to save info dialog size:', error);
|
||||
console.warn('Model Resolver: Failed to save info dialog size:', error);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -544,7 +544,7 @@ export const modelInfoMethods = {
|
||||
*/
|
||||
async fetchModelInfoForDialog(loraName, modelData, dialog) {
|
||||
try {
|
||||
const response = await api.fetchApi('/model_linker/civitai-search', {
|
||||
const response = await api.fetchApi('/model_resolver/civitai-search', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@@ -561,7 +561,7 @@ export const modelInfoMethods = {
|
||||
this.updateInfoDialogError(dialog, 'Model not found on CivitAI');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Model Linker: Error fetching model info:', e);
|
||||
console.error('Model Resolver: Error fetching model info:', e);
|
||||
this.updateInfoDialogError(dialog, 'Error fetching info');
|
||||
}
|
||||
},
|
||||
@@ -570,8 +570,8 @@ export const modelInfoMethods = {
|
||||
* Update the info dialog with data
|
||||
*/
|
||||
updateInfoDialogWithData(dialog, data) {
|
||||
const loadingDiv = dialog.querySelector('.ml-info-dialog-loading');
|
||||
const bodyDiv = dialog.querySelector('.ml-info-dialog-body');
|
||||
const loadingDiv = dialog.querySelector('.mr-info-dialog-loading');
|
||||
const bodyDiv = dialog.querySelector('.mr-info-dialog-body');
|
||||
this.bindInfoDialogInteractions(dialog);
|
||||
|
||||
if (loadingDiv) loadingDiv.style.display = 'none';
|
||||
@@ -583,7 +583,7 @@ export const modelInfoMethods = {
|
||||
}
|
||||
|
||||
// Update title
|
||||
const titleEl = dialog.querySelector('.ml-info-dialog-title');
|
||||
const titleEl = dialog.querySelector('.mr-info-dialog-title');
|
||||
if (titleEl) {
|
||||
const modelName = data.model_name || data.modelName || 'Unknown Model';
|
||||
const versionName = data.version_name || data.versionName || '';
|
||||
@@ -591,54 +591,54 @@ export const modelInfoMethods = {
|
||||
}
|
||||
|
||||
// Update type tag
|
||||
const typeTag = dialog.querySelector('.ml-info-type');
|
||||
const typeTag = dialog.querySelector('.mr-info-type');
|
||||
if (typeTag) {
|
||||
const modelType = data.model_type || data.modelType || '';
|
||||
typeTag.textContent = modelType.toUpperCase();
|
||||
typeTag.className = `ml-info-tag ml-info-type ${this.getModelTypeColorClass(modelType)}`;
|
||||
typeTag.className = `mr-info-tag mr-info-type ${this.getModelTypeColorClass(modelType)}`;
|
||||
}
|
||||
|
||||
// Update base model tag
|
||||
const baseModelTag = dialog.querySelector('.ml-info-basemodel');
|
||||
const baseModelTag = dialog.querySelector('.mr-info-basemodel');
|
||||
if (baseModelTag) {
|
||||
const baseModel = data.base_model || data.baseModel || '';
|
||||
baseModelTag.textContent = baseModel || '';
|
||||
if (baseModel) {
|
||||
baseModelTag.style.display = '';
|
||||
baseModelTag.className = `ml-info-tag ml-info-basemodel -basemodel-${baseModel.toLowerCase().replace(/\s+/g, '-')}`;
|
||||
baseModelTag.className = `mr-info-tag mr-info-basemodel -basemodel-${baseModel.toLowerCase().replace(/\s+/g, '-')}`;
|
||||
} else {
|
||||
baseModelTag.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Update file
|
||||
const fileEl = dialog.querySelector('.ml-info-file');
|
||||
const fileEl = dialog.querySelector('.mr-info-file');
|
||||
if (fileEl && data.filename) {
|
||||
fileEl.textContent = data.filename;
|
||||
}
|
||||
|
||||
// Update hash
|
||||
const hashEl = dialog.querySelector('.ml-info-hash');
|
||||
const hashEl = dialog.querySelector('.mr-info-hash');
|
||||
if (hashEl) {
|
||||
hashEl.textContent = data.sha256 || data.hash || '';
|
||||
}
|
||||
|
||||
// Update CivitAI link
|
||||
const civitaiLinkEl = dialog.querySelector('.ml-info-civitai-link');
|
||||
const civitaiLinkEl = dialog.querySelector('.mr-info-civitai-link');
|
||||
if (civitaiLinkEl) {
|
||||
if (data.url || data.version_url) {
|
||||
const url = data.version_url || data.url;
|
||||
civitaiLinkEl.innerHTML = `
|
||||
<a href="${url}" target="_blank" class="ml-info-link">
|
||||
${getSvgIcon('civitai', 'currentColor', 'ml-info-civitai-logo')}
|
||||
<a href="${url}" target="_blank" class="mr-info-link">
|
||||
${getSvgIcon('civitai', 'currentColor', 'mr-info-civitai-logo')}
|
||||
View on Civitai
|
||||
</a>
|
||||
`;
|
||||
} else {
|
||||
const searchName = data.model_name || data.modelName || 'Unknown';
|
||||
civitaiLinkEl.innerHTML = `
|
||||
<span class="ml-info-not-found">Model not found</span>
|
||||
<a href="https://civitai.com/search?q=${encodeURIComponent(searchName)}" target="_blank" class="ml-info-link">
|
||||
<span class="mr-info-not-found">Model not found</span>
|
||||
<a href="https://civitai.com/search?q=${encodeURIComponent(searchName)}" target="_blank" class="mr-info-link">
|
||||
${this.getSearchIconHtml()} Search on CivitAI
|
||||
</a>
|
||||
`;
|
||||
@@ -646,13 +646,13 @@ export const modelInfoMethods = {
|
||||
}
|
||||
|
||||
// Update name
|
||||
const nameEl = dialog.querySelector('.ml-info-name');
|
||||
const nameEl = dialog.querySelector('.mr-info-name');
|
||||
if (nameEl) {
|
||||
nameEl.textContent = data.model_name || data.modelName || '';
|
||||
}
|
||||
|
||||
// Update base model row
|
||||
const baseModelRowEl = dialog.querySelector('.ml-info-base-model');
|
||||
const baseModelRowEl = dialog.querySelector('.mr-info-base-model');
|
||||
if (baseModelRowEl) {
|
||||
const baseModel = data.base_model || data.baseModel || '';
|
||||
baseModelRowEl.textContent = baseModel;
|
||||
@@ -665,15 +665,15 @@ export const modelInfoMethods = {
|
||||
}
|
||||
|
||||
// Update trained words
|
||||
const trainedWordsEl = dialog.querySelector('.ml-info-trained-words');
|
||||
const trainedWordsEl = dialog.querySelector('.mr-info-trained-words');
|
||||
if (trainedWordsEl) {
|
||||
const words = this.normalizeTrainedWords(data.trained_words || data.trainedWords || []);
|
||||
if (words.length > 0) {
|
||||
dialog._selectedTrainedWords = new Set();
|
||||
trainedWordsEl.innerHTML = `<div class="ml-info-trained-words-list">${words.map(word => `
|
||||
trainedWordsEl.innerHTML = `<div class="mr-info-trained-words-list">${words.map(word => `
|
||||
<button
|
||||
type="button"
|
||||
class="ml-info-trained-word"
|
||||
class="mr-info-trained-word"
|
||||
data-word="${this.escapeHtml(word)}"
|
||||
data-tooltip="${this.escapeHtml(word)}"
|
||||
aria-pressed="false"
|
||||
@@ -692,7 +692,7 @@ export const modelInfoMethods = {
|
||||
}
|
||||
|
||||
// Update clip skip
|
||||
const clipSkipEl = dialog.querySelector('.ml-info-clip-skip');
|
||||
const clipSkipEl = dialog.querySelector('.mr-info-clip-skip');
|
||||
if (clipSkipEl) {
|
||||
const clipSkip = data.clip_skip || data.clipSkip;
|
||||
if (clipSkip && clipSkip !== 'None') {
|
||||
@@ -706,18 +706,18 @@ export const modelInfoMethods = {
|
||||
}
|
||||
|
||||
// Update description
|
||||
const descEl = dialog.querySelector('.ml-info-description');
|
||||
const descEl = dialog.querySelector('.mr-info-description');
|
||||
if (descEl) {
|
||||
const desc = data.description || data.model_description || data.modelDescription || '';
|
||||
if (desc) {
|
||||
const actionsEl = dialog.querySelector('.ml-info-description-actions');
|
||||
const toggleBtn = dialog.querySelector('.ml-info-description-toggle');
|
||||
const actionsEl = dialog.querySelector('.mr-info-description-actions');
|
||||
const toggleBtn = dialog.querySelector('.mr-info-description-toggle');
|
||||
|
||||
let sanitizedHtml = '';
|
||||
try {
|
||||
sanitizedHtml = this.sanitizeDescriptionHtml(desc);
|
||||
} catch (error) {
|
||||
console.error('Model Linker: Failed to sanitize description HTML:', error);
|
||||
console.error('Model Resolver: Failed to sanitize description HTML:', error);
|
||||
}
|
||||
|
||||
const fallbackText = this.escapeHtml(String(desc).replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim());
|
||||
@@ -754,7 +754,7 @@ export const modelInfoMethods = {
|
||||
* Update images in the info dialog
|
||||
*/
|
||||
updateInfoDialogImages(dialog, images) {
|
||||
const imagesContainer = dialog.querySelector('.ml-info-images');
|
||||
const imagesContainer = dialog.querySelector('.mr-info-images');
|
||||
if (!imagesContainer) return;
|
||||
if (!images.length) {
|
||||
imagesContainer.innerHTML = '';
|
||||
@@ -766,7 +766,7 @@ export const modelInfoMethods = {
|
||||
const renderImageCard = (img) => {
|
||||
const captionParts = [];
|
||||
if (img.civitaiUrl) {
|
||||
captionParts.push(`<a href="${this.escapeHtml(img.civitaiUrl)}" target="_blank" rel="noopener noreferrer" class="ml-info-image-link">civitai</a>`);
|
||||
captionParts.push(`<a href="${this.escapeHtml(img.civitaiUrl)}" target="_blank" rel="noopener noreferrer" class="mr-info-image-link">civitai</a>`);
|
||||
}
|
||||
if (img.seed) captionParts.push(`<span><label>seed</label> ${this.escapeHtml(img.seed)}</span>`);
|
||||
if (img.steps) captionParts.push(`<span><label>steps</label> ${this.escapeHtml(img.steps)}</span>`);
|
||||
@@ -777,7 +777,7 @@ export const modelInfoMethods = {
|
||||
if (img.negative) captionParts.push(`<span><label>negative</label> ${this.escapeHtml(this.truncateText(img.negative, 180))}</span>`);
|
||||
|
||||
return `
|
||||
<div class="ml-info-image-item">
|
||||
<div class="mr-info-image-item">
|
||||
<figure>
|
||||
<img src="${this.escapeHtml(img.url)}" alt="Example" loading="lazy" />
|
||||
<figcaption>${captionParts.join('')}</figcaption>
|
||||
@@ -786,7 +786,7 @@ export const modelInfoMethods = {
|
||||
`;
|
||||
};
|
||||
|
||||
let imagesHtml = '<div class="ml-info-images-header">Example Images</div><div class="ml-info-images-layout">';
|
||||
let imagesHtml = '<div class="mr-info-images-header">Example Images</div><div class="mr-info-images-layout">';
|
||||
imagesHtml += visibleImages.map(renderImageCard).join('');
|
||||
imagesHtml += '</div>';
|
||||
imagesContainer.innerHTML = imagesHtml;
|
||||
@@ -796,9 +796,9 @@ export const modelInfoMethods = {
|
||||
* Update the info dialog with error
|
||||
*/
|
||||
updateInfoDialogError(dialog, message) {
|
||||
const civitaiLink = dialog.querySelector('.ml-info-civitai-link');
|
||||
const civitaiLink = dialog.querySelector('.mr-info-civitai-link');
|
||||
if (civitaiLink) {
|
||||
civitaiLink.innerHTML = `<span class="ml-info-error">${message}</span>`;
|
||||
civitaiLink.innerHTML = `<span class="mr-info-error">${message}</span>`;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { api } from "../../../../../scripts/api.js";
|
||||
import { $el } from "../../../../../scripts/ui.js";
|
||||
import { getSvgIcon } from "../../utils/icon_utils.js";
|
||||
@@ -13,157 +13,157 @@ export const optionsMethods = {
|
||||
.map(def => {
|
||||
const iconName = this.getSearchSourceIconName(def.source);
|
||||
const checked = tokens.search_source_enabled?.[def.source] !== false;
|
||||
const inputId = `ml-options-source-${def.source.replace(/_/g, '-')}`;
|
||||
const inputId = `mr-options-source-${def.source.replace(/_/g, '-')}`;
|
||||
return `
|
||||
<label class="ml-options-toggle-row">
|
||||
<div class="ml-options-toggle-copy">
|
||||
<span class="ml-options-toggle-title">
|
||||
<span class="ml-options-source-icon" aria-hidden="true">${getSvgIcon(iconName)}</span>
|
||||
<label class="mr-options-toggle-row">
|
||||
<div class="mr-options-toggle-copy">
|
||||
<span class="mr-options-toggle-title">
|
||||
<span class="mr-options-source-icon" aria-hidden="true">${getSvgIcon(iconName)}</span>
|
||||
<span>${this.escapeHtml(this.getSearchSourceLabel(def.source))}</span>
|
||||
<span class="ml-tooltip-badge" data-tooltip="${this.escapeHtml(def.tooltip)}">?</span>
|
||||
<span class="mr-tooltip-badge" data-tooltip="${this.escapeHtml(def.tooltip)}">?</span>
|
||||
</span>
|
||||
</div>
|
||||
<span class="ml-options-toggle-control">
|
||||
<input id="${inputId}" class="ml-options-switch-input ml-options-source-enabled" type="checkbox" data-source="${this.escapeHtml(def.source)}" data-storage-key="${this.escapeHtml(def.storageKey)}" ${checked ? 'checked' : ''}>
|
||||
<span class="ml-options-switch"></span>
|
||||
<span class="mr-options-toggle-control">
|
||||
<input id="${inputId}" class="mr-options-switch-input mr-options-source-enabled" type="checkbox" data-source="${this.escapeHtml(def.source)}" data-storage-key="${this.escapeHtml(def.storageKey)}" ${checked ? 'checked' : ''}>
|
||||
<span class="mr-options-switch"></span>
|
||||
</span>
|
||||
</label>
|
||||
`;
|
||||
})
|
||||
.join('');
|
||||
this.contentElement.innerHTML = `
|
||||
<div class="ml-options-wrap">
|
||||
<div class="ml-options-shell">
|
||||
<aside class="ml-options-sidebar">
|
||||
<div class="ml-options-sidebar-group">
|
||||
<h3 class="ml-options-sidebar-title">Application Settings</h3>
|
||||
<div class="mr-options-wrap">
|
||||
<div class="mr-options-shell">
|
||||
<aside class="mr-options-sidebar">
|
||||
<div class="mr-options-sidebar-group">
|
||||
<h3 class="mr-options-sidebar-title">Application Settings</h3>
|
||||
</div>
|
||||
<div class="ml-options-sidebar-group">
|
||||
<div class="ml-options-sidebar-label">Download</div>
|
||||
<div class="ml-options-nav">
|
||||
<button type="button" class="ml-options-nav-btn is-active" data-target="ml-options-section-sources">
|
||||
<span class="ml-options-nav-main">
|
||||
<span class="ml-options-nav-icon" aria-hidden="true">${getSvgIcon('download')}</span>
|
||||
<div class="mr-options-sidebar-group">
|
||||
<div class="mr-options-sidebar-label">Download</div>
|
||||
<div class="mr-options-nav">
|
||||
<button type="button" class="mr-options-nav-btn is-active" data-target="mr-options-section-sources">
|
||||
<span class="mr-options-nav-main">
|
||||
<span class="mr-options-nav-icon" aria-hidden="true">${getSvgIcon('download')}</span>
|
||||
<span>Sources</span>
|
||||
</span>
|
||||
<span class="ml-options-nav-meta">01</span>
|
||||
<span class="mr-options-nav-meta">01</span>
|
||||
</button>
|
||||
<button type="button" class="ml-options-nav-btn" data-target="ml-options-section-local-db">
|
||||
<span class="ml-options-nav-main">
|
||||
<span class="ml-options-nav-icon ml-options-comfyui-icon" aria-hidden="true">${getSvgIcon('comfyui')}</span>
|
||||
<button type="button" class="mr-options-nav-btn" data-target="mr-options-section-local-db">
|
||||
<span class="mr-options-nav-main">
|
||||
<span class="mr-options-nav-icon mr-options-comfyui-icon" aria-hidden="true">${getSvgIcon('comfyui')}</span>
|
||||
<span>Local Database</span>
|
||||
</span>
|
||||
<span class="ml-options-nav-meta">02</span>
|
||||
<span class="mr-options-nav-meta">02</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-options-sidebar-group">
|
||||
<div class="ml-options-sidebar-label">Providers</div>
|
||||
<div class="ml-options-nav">
|
||||
<button type="button" class="ml-options-nav-btn" data-target="ml-options-section-civitai">
|
||||
<span class="ml-options-nav-main">
|
||||
<span class="ml-options-nav-icon ml-options-provider-icon ml-options-provider-icon-civitai" aria-hidden="true">${getSvgIcon('civitai')}</span>
|
||||
<div class="mr-options-sidebar-group">
|
||||
<div class="mr-options-sidebar-label">Providers</div>
|
||||
<div class="mr-options-nav">
|
||||
<button type="button" class="mr-options-nav-btn" data-target="mr-options-section-civitai">
|
||||
<span class="mr-options-nav-main">
|
||||
<span class="mr-options-nav-icon mr-options-provider-icon mr-options-provider-icon-civitai" aria-hidden="true">${getSvgIcon('civitai')}</span>
|
||||
<span>CivitAI</span>
|
||||
</span>
|
||||
<span class="ml-options-nav-meta">03</span>
|
||||
<span class="mr-options-nav-meta">03</span>
|
||||
</button>
|
||||
<button type="button" class="ml-options-nav-btn" data-target="ml-options-section-hf">
|
||||
<span class="ml-options-nav-main">
|
||||
<span class="ml-options-nav-icon ml-options-provider-icon ml-options-provider-icon-huggingface" aria-hidden="true">${getSvgIcon('huggingface')}</span>
|
||||
<button type="button" class="mr-options-nav-btn" data-target="mr-options-section-hf">
|
||||
<span class="mr-options-nav-main">
|
||||
<span class="mr-options-nav-icon mr-options-provider-icon mr-options-provider-icon-huggingface" aria-hidden="true">${getSvgIcon('huggingface')}</span>
|
||||
<span>HuggingFace</span>
|
||||
</span>
|
||||
<span class="ml-options-nav-meta">04</span>
|
||||
<span class="mr-options-nav-meta">04</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-options-actions">
|
||||
<div id="ml-options-status" class="ml-options-status">Saved only on this machine.</div>
|
||||
<button id="ml-options-save" class="ml-btn ml-btn-primary ml-footer-btn">Save</button>
|
||||
<div class="mr-options-actions">
|
||||
<div id="mr-options-status" class="mr-options-status">Saved only on this machine.</div>
|
||||
<button id="mr-options-save" class="mr-btn mr-btn-primary mr-footer-btn">Save</button>
|
||||
</div>
|
||||
</aside>
|
||||
<div class="ml-options-main">
|
||||
<section id="ml-options-section-sources" class="ml-options-card ml-options-section">
|
||||
<div class="ml-options-section-head">
|
||||
<h4 class="ml-options-section-title">Download Sources</h4>
|
||||
<div class="mr-options-main">
|
||||
<section id="mr-options-section-sources" class="mr-options-card mr-options-section">
|
||||
<div class="mr-options-section-head">
|
||||
<h4 class="mr-options-section-title">Download Sources</h4>
|
||||
</div>
|
||||
<div class="ml-options-grid">
|
||||
<div class="ml-options-panel">
|
||||
<div class="ml-options-toggle-list">
|
||||
<div class="mr-options-grid">
|
||||
<div class="mr-options-panel">
|
||||
<div class="mr-options-toggle-list">
|
||||
${sourceDefaultsRows}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="ml-options-section-local-db" class="ml-options-card ml-options-section is-hidden">
|
||||
<div class="ml-options-section-head">
|
||||
<h4 class="ml-options-section-title">Local Database</h4>
|
||||
<section id="mr-options-section-local-db" class="mr-options-card mr-options-section is-hidden">
|
||||
<div class="mr-options-section-head">
|
||||
<h4 class="mr-options-section-title">Local Database</h4>
|
||||
</div>
|
||||
<div class="ml-options-grid">
|
||||
<div class="ml-options-panel">
|
||||
<div class="ml-options-stack">
|
||||
<div class="ml-options-db-summary">
|
||||
<div class="ml-options-db-row">
|
||||
<div class="mr-options-grid">
|
||||
<div class="mr-options-panel">
|
||||
<div class="mr-options-stack">
|
||||
<div class="mr-options-db-summary">
|
||||
<div class="mr-options-db-row">
|
||||
<span>Models</span>
|
||||
<strong id="ml-options-model-list-count">Loading...</strong>
|
||||
<strong id="mr-options-model-list-count">Loading...</strong>
|
||||
</div>
|
||||
<div class="ml-options-db-row">
|
||||
<div class="mr-options-db-row">
|
||||
<span>Last update</span>
|
||||
<strong id="ml-options-model-list-updated">Loading...</strong>
|
||||
<strong id="mr-options-model-list-updated">Loading...</strong>
|
||||
</div>
|
||||
<div class="ml-options-db-row">
|
||||
<div class="mr-options-db-row">
|
||||
<span>Status</span>
|
||||
<strong id="ml-options-model-list-state">Checking local file...</strong>
|
||||
<strong id="mr-options-model-list-state">Checking local file...</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ml-options-model-list-message" class="ml-options-db-message">Local Database uses ComfyUI-Manager model-list.json.</div>
|
||||
<div class="ml-options-db-actions">
|
||||
<button id="ml-options-model-list-check" type="button" class="ml-btn ml-btn-secondary">${getSvgIcon('refreshCw')} Check latest</button>
|
||||
<button id="ml-options-model-list-update" type="button" class="ml-btn ml-btn-primary">${getSvgIcon('download')} Update Local Database</button>
|
||||
<a class="ml-options-inline-link" href="https://github.com/Comfy-Org/ComfyUI-Manager/blob/main/model-list.json" target="_blank" rel="noopener noreferrer">Source</a>
|
||||
<div id="mr-options-model-list-message" class="mr-options-db-message">Local Database uses ComfyUI-Manager model-list.json.</div>
|
||||
<div class="mr-options-db-actions">
|
||||
<button id="mr-options-model-list-check" type="button" class="mr-btn mr-btn-secondary">${getSvgIcon('refreshCw')} Check latest</button>
|
||||
<button id="mr-options-model-list-update" type="button" class="mr-btn mr-btn-primary">${getSvgIcon('download')} Update Local Database</button>
|
||||
<a class="mr-options-inline-link" href="https://github.com/Comfy-Org/ComfyUI-Manager/blob/main/model-list.json" target="_blank" rel="noopener noreferrer">Source</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="ml-options-section-civitai" class="ml-options-card ml-options-section">
|
||||
<div class="ml-options-section-head">
|
||||
<h4 class="ml-options-section-title">CivitAI</h4>
|
||||
<section id="mr-options-section-civitai" class="mr-options-card mr-options-section">
|
||||
<div class="mr-options-section-head">
|
||||
<h4 class="mr-options-section-title">CivitAI</h4>
|
||||
</div>
|
||||
<div class="ml-options-grid">
|
||||
<div class="ml-options-panel">
|
||||
<div class="ml-options-toggle-list">
|
||||
<label class="ml-options-toggle-row">
|
||||
<div class="ml-options-toggle-copy">
|
||||
<span class="ml-options-toggle-title">Use CivitAI tRPC search <span class="ml-tooltip-badge" data-tooltip="Main CivitAI search method. Keep this enabled unless CivitAI search stops working.">?</span></span>
|
||||
<div class="mr-options-grid">
|
||||
<div class="mr-options-panel">
|
||||
<div class="mr-options-toggle-list">
|
||||
<label class="mr-options-toggle-row">
|
||||
<div class="mr-options-toggle-copy">
|
||||
<span class="mr-options-toggle-title">Use CivitAI tRPC search <span class="mr-tooltip-badge" data-tooltip="Main CivitAI search method. Keep this enabled unless CivitAI search stops working.">?</span></span>
|
||||
</div>
|
||||
<span class="ml-options-toggle-control">
|
||||
<input id="ml-options-civitai-use-trpc-search" class="ml-options-switch-input" type="checkbox" ${tokens.civitai_use_trpc_search ? 'checked' : ''}>
|
||||
<span class="ml-options-switch"></span>
|
||||
<span class="mr-options-toggle-control">
|
||||
<input id="mr-options-civitai-use-trpc-search" class="mr-options-switch-input" type="checkbox" ${tokens.civitai_use_trpc_search ? 'checked' : ''}>
|
||||
<span class="mr-options-switch"></span>
|
||||
</span>
|
||||
</label>
|
||||
<div class="ml-options-dependent-block">
|
||||
<div class="ml-options-field">
|
||||
<div class="ml-options-input-row">
|
||||
<label for="ml-options-civitai" class="ml-options-label">CivitAI API Key <a href="https://civitai.com/user/account" target="_blank" rel="noopener noreferrer" class="ml-options-inline-link">Get key</a> <span class="ml-tooltip-badge" data-tooltip="Optional. Used when downloading from CivitAI requires your account. Add this if CivitAI downloads fail or need access to models available to your account.">?</span></label>
|
||||
<input id="ml-options-civitai" class="ml-options-input" type="password" placeholder="Paste CivitAI API key" value="${tokens.civitai_key}">
|
||||
<button id="ml-options-civitai-toggle" type="button" class="ml-options-visibility-btn" aria-label="Toggle visibility for saved CivitAI API key" data-tooltip="Show saved value">
|
||||
<div class="mr-options-dependent-block">
|
||||
<div class="mr-options-field">
|
||||
<div class="mr-options-input-row">
|
||||
<label for="mr-options-civitai" class="mr-options-label">CivitAI API Key <a href="https://civitai.com/user/account" target="_blank" rel="noopener noreferrer" class="mr-options-inline-link">Get key</a> <span class="mr-tooltip-badge" data-tooltip="Optional. Used when downloading from CivitAI requires your account. Add this if CivitAI downloads fail or need access to models available to your account.">?</span></label>
|
||||
<input id="mr-options-civitai" class="mr-options-input" type="password" placeholder="Paste CivitAI API key" value="${tokens.civitai_key}">
|
||||
<button id="mr-options-civitai-toggle" type="button" class="mr-options-visibility-btn" aria-label="Toggle visibility for saved CivitAI API key" data-tooltip="Show saved value">
|
||||
${getSvgIcon('eye')}
|
||||
</button>
|
||||
</div>
|
||||
<div class="ml-options-token-check">
|
||||
<button id="ml-options-civitai-key-check" type="button" class="ml-btn ml-btn-secondary ml-btn-sm">${getSvgIcon('refreshCw')} Check API key</button>
|
||||
<span id="ml-options-civitai-key-check-status" class="ml-options-token-check-status">Not checked</span>
|
||||
<div class="mr-options-token-check">
|
||||
<button id="mr-options-civitai-key-check" type="button" class="mr-btn mr-btn-secondary mr-btn-sm">${getSvgIcon('refreshCw')} Check API key</button>
|
||||
<span id="mr-options-civitai-key-check-status" class="mr-options-token-check-status">Not checked</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-options-field">
|
||||
<div class="ml-options-input-row">
|
||||
<label for="ml-options-civitai-session" class="ml-options-label">CivitAI Session Token <span class="ml-tooltip-badge" data-tooltip="Optional. Makes CivitAI search use your logged-in session, so results can match what you see in the browser. Useful for NSFW or account-visible results.">?</span></label>
|
||||
<input id="ml-options-civitai-session" class="ml-options-input" type="password" placeholder="Paste __Secure-civitai-token" value="${tokens.civitai_session_token}">
|
||||
<button id="ml-options-civitai-session-toggle" type="button" class="ml-options-visibility-btn" aria-label="Toggle visibility for saved CivitAI session token" data-tooltip="Show saved value">
|
||||
<div class="mr-options-field">
|
||||
<div class="mr-options-input-row">
|
||||
<label for="mr-options-civitai-session" class="mr-options-label">CivitAI Session Token <span class="mr-tooltip-badge" data-tooltip="Optional. Makes CivitAI search use your logged-in session, so results can match what you see in the browser. Useful for NSFW or account-visible results.">?</span></label>
|
||||
<input id="mr-options-civitai-session" class="mr-options-input" type="password" placeholder="Paste __Secure-civitai-token" value="${tokens.civitai_session_token}">
|
||||
<button id="mr-options-civitai-session-toggle" type="button" class="mr-options-visibility-btn" aria-label="Toggle visibility for saved CivitAI session token" data-tooltip="Show saved value">
|
||||
${getSvgIcon('eye')}
|
||||
</button>
|
||||
</div>
|
||||
<details class="ml-options-help">
|
||||
<details class="mr-options-help">
|
||||
<summary>How to find the session token</summary>
|
||||
<ol>
|
||||
<li>Sign in on civitai.com in your browser.</li>
|
||||
@@ -172,114 +172,114 @@ export const optionsMethods = {
|
||||
</ol>
|
||||
<p>Treat it like a password. It is only needed when session-aware CivitAI search is required.</p>
|
||||
</details>
|
||||
<div class="ml-options-token-check">
|
||||
<button id="ml-options-civitai-session-check" type="button" class="ml-btn ml-btn-secondary ml-btn-sm">${getSvgIcon('refreshCw')} Check session</button>
|
||||
<span id="ml-options-civitai-session-check-status" class="ml-options-token-check-status">Not checked</span>
|
||||
<div class="mr-options-token-check">
|
||||
<button id="mr-options-civitai-session-check" type="button" class="mr-btn mr-btn-secondary mr-btn-sm">${getSvgIcon('refreshCw')} Check session</button>
|
||||
<span id="mr-options-civitai-session-check-status" class="mr-options-token-check-status">Not checked</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label class="ml-options-toggle-row">
|
||||
<div class="ml-options-toggle-copy">
|
||||
<span class="ml-options-toggle-title">Use CivitAI HTML fallback <span class="ml-tooltip-badge" data-tooltip="Backup CivitAI search. Leave this enabled to try the regular CivitAI page when the main search does not find enough results.">?</span></span>
|
||||
<label class="mr-options-toggle-row">
|
||||
<div class="mr-options-toggle-copy">
|
||||
<span class="mr-options-toggle-title">Use CivitAI HTML fallback <span class="mr-tooltip-badge" data-tooltip="Backup CivitAI search. Leave this enabled to try the regular CivitAI page when the main search does not find enough results.">?</span></span>
|
||||
</div>
|
||||
<span class="ml-options-toggle-control">
|
||||
<input id="ml-options-civitai-use-html-fallback" class="ml-options-switch-input" type="checkbox" ${tokens.civitai_use_html_fallback ? 'checked' : ''}>
|
||||
<span class="ml-options-switch"></span>
|
||||
<span class="mr-options-toggle-control">
|
||||
<input id="mr-options-civitai-use-html-fallback" class="mr-options-switch-input" type="checkbox" ${tokens.civitai_use_html_fallback ? 'checked' : ''}>
|
||||
<span class="mr-options-switch"></span>
|
||||
</span>
|
||||
</label>
|
||||
<div class="ml-options-dependent-block">
|
||||
<div class="ml-options-number-row">
|
||||
<div class="ml-options-number-copy">
|
||||
<span class="ml-options-label">CivitAI Models To Inspect <span class="ml-tooltip-badge" data-tooltip="How many CivitAI results to check for the exact file. Higher values may find more matches, but searches can take longer. Range: 1-20.">?</span></span>
|
||||
<div class="mr-options-dependent-block">
|
||||
<div class="mr-options-number-row">
|
||||
<div class="mr-options-number-copy">
|
||||
<span class="mr-options-label">CivitAI Models To Inspect <span class="mr-tooltip-badge" data-tooltip="How many CivitAI results to check for the exact file. Higher values may find more matches, but searches can take longer. Range: 1-20.">?</span></span>
|
||||
</div>
|
||||
<input id="ml-options-civitai-limit" class="ml-options-input" type="number" min="1" max="20" step="1" value="${tokens.civitai_candidate_limit}">
|
||||
<input id="mr-options-civitai-limit" class="mr-options-input" type="number" min="1" max="20" step="1" value="${tokens.civitai_candidate_limit}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="ml-options-section-hf" class="ml-options-card ml-options-section">
|
||||
<div class="ml-options-section-head">
|
||||
<h4 class="ml-options-section-title">HuggingFace</h4>
|
||||
<section id="mr-options-section-hf" class="mr-options-card mr-options-section">
|
||||
<div class="mr-options-section-head">
|
||||
<h4 class="mr-options-section-title">HuggingFace</h4>
|
||||
</div>
|
||||
<div class="ml-options-grid">
|
||||
<div class="ml-options-panel">
|
||||
<div class="ml-options-toggle-list">
|
||||
<label class="ml-options-toggle-row">
|
||||
<div class="ml-options-toggle-copy">
|
||||
<span class="ml-options-toggle-title">Use HuggingFace API repo search <span class="ml-tooltip-badge" data-tooltip="Main Hugging Face search method. It searches by filename, then checks matching repos for the actual file.">?</span></span>
|
||||
<div class="mr-options-grid">
|
||||
<div class="mr-options-panel">
|
||||
<div class="mr-options-toggle-list">
|
||||
<label class="mr-options-toggle-row">
|
||||
<div class="mr-options-toggle-copy">
|
||||
<span class="mr-options-toggle-title">Use HuggingFace API repo search <span class="mr-tooltip-badge" data-tooltip="Main Hugging Face search method. It searches by filename, then checks matching repos for the actual file.">?</span></span>
|
||||
</div>
|
||||
<span class="ml-options-toggle-control">
|
||||
<input id="ml-options-hf-use-api-search" class="ml-options-switch-input" type="checkbox" ${tokens.hf_use_api_search ? 'checked' : ''}>
|
||||
<span class="ml-options-switch"></span>
|
||||
<span class="mr-options-toggle-control">
|
||||
<input id="mr-options-hf-use-api-search" class="mr-options-switch-input" type="checkbox" ${tokens.hf_use_api_search ? 'checked' : ''}>
|
||||
<span class="mr-options-switch"></span>
|
||||
</span>
|
||||
</label>
|
||||
<div class="ml-options-dependent-block">
|
||||
<div class="ml-options-field">
|
||||
<div class="ml-options-input-row">
|
||||
<label for="ml-options-hf" class="ml-options-label">HuggingFace Token <a href="https://huggingface.co/settings/tokens" target="_blank" rel="noopener noreferrer" class="ml-options-inline-link">Get key</a> <span class="ml-tooltip-badge" data-tooltip="Optional. Used to search and download files from Hugging Face repos your account can access, including gated repos. A read-only token is enough.">?</span></label>
|
||||
<input id="ml-options-hf" class="ml-options-input" type="password" placeholder="Paste HuggingFace token" value="${tokens.hf_token}">
|
||||
<button id="ml-options-hf-toggle" type="button" class="ml-options-visibility-btn" aria-label="Toggle visibility for saved HuggingFace token" data-tooltip="Show saved value">
|
||||
<div class="mr-options-dependent-block">
|
||||
<div class="mr-options-field">
|
||||
<div class="mr-options-input-row">
|
||||
<label for="mr-options-hf" class="mr-options-label">HuggingFace Token <a href="https://huggingface.co/settings/tokens" target="_blank" rel="noopener noreferrer" class="mr-options-inline-link">Get key</a> <span class="mr-tooltip-badge" data-tooltip="Optional. Used to search and download files from Hugging Face repos your account can access, including gated repos. A read-only token is enough.">?</span></label>
|
||||
<input id="mr-options-hf" class="mr-options-input" type="password" placeholder="Paste HuggingFace token" value="${tokens.hf_token}">
|
||||
<button id="mr-options-hf-toggle" type="button" class="mr-options-visibility-btn" aria-label="Toggle visibility for saved HuggingFace token" data-tooltip="Show saved value">
|
||||
${getSvgIcon('eye')}
|
||||
</button>
|
||||
</div>
|
||||
<div class="ml-options-token-check">
|
||||
<button id="ml-options-hf-token-check" type="button" class="ml-btn ml-btn-secondary ml-btn-sm">${getSvgIcon('refreshCw')} Check token</button>
|
||||
<span id="ml-options-hf-token-check-status" class="ml-options-token-check-status">Not checked</span>
|
||||
<div class="mr-options-token-check">
|
||||
<button id="mr-options-hf-token-check" type="button" class="mr-btn mr-btn-secondary mr-btn-sm">${getSvgIcon('refreshCw')} Check token</button>
|
||||
<span id="mr-options-hf-token-check-status" class="mr-options-token-check-status">Not checked</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label class="ml-options-toggle-row">
|
||||
<div class="ml-options-toggle-copy">
|
||||
<span class="ml-options-toggle-title">Use Comfy-Org fallback <span class="ml-tooltip-badge" data-tooltip="Checks Comfy-Org repositories directly. Useful for ComfyUI model packs that normal Hugging Face search may miss.">?</span></span>
|
||||
<label class="mr-options-toggle-row">
|
||||
<div class="mr-options-toggle-copy">
|
||||
<span class="mr-options-toggle-title">Use Comfy-Org fallback <span class="mr-tooltip-badge" data-tooltip="Checks Comfy-Org repositories directly. Useful for ComfyUI model packs that normal Hugging Face search may miss.">?</span></span>
|
||||
</div>
|
||||
<span class="ml-options-toggle-control">
|
||||
<input id="ml-options-hf-use-comfy-org-fallback" class="ml-options-switch-input" type="checkbox" ${tokens.hf_use_comfy_org_fallback ? 'checked' : ''}>
|
||||
<span class="ml-options-switch"></span>
|
||||
<span class="mr-options-toggle-control">
|
||||
<input id="mr-options-hf-use-comfy-org-fallback" class="mr-options-switch-input" type="checkbox" ${tokens.hf_use_comfy_org_fallback ? 'checked' : ''}>
|
||||
<span class="mr-options-switch"></span>
|
||||
</span>
|
||||
</label>
|
||||
<div class="ml-options-dependent-block">
|
||||
<div class="ml-options-db-summary">
|
||||
<div class="ml-options-db-row">
|
||||
<div class="mr-options-dependent-block">
|
||||
<div class="mr-options-db-summary">
|
||||
<div class="mr-options-db-row">
|
||||
<span>Comfy-Org files</span>
|
||||
<strong id="ml-options-hf-index-count">Loading...</strong>
|
||||
<strong id="mr-options-hf-index-count">Loading...</strong>
|
||||
</div>
|
||||
<div class="ml-options-db-row">
|
||||
<div class="mr-options-db-row">
|
||||
<span>Last refresh</span>
|
||||
<strong id="ml-options-hf-index-updated">Loading...</strong>
|
||||
<strong id="mr-options-hf-index-updated">Loading...</strong>
|
||||
</div>
|
||||
<div class="ml-options-db-row">
|
||||
<div class="mr-options-db-row">
|
||||
<span>Status</span>
|
||||
<strong id="ml-options-hf-index-state">Checking local index...</strong>
|
||||
<strong id="mr-options-hf-index-state">Checking local index...</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ml-options-hf-index-message" class="ml-options-db-message">Comfy-Org fallback uses a cached HuggingFace file index.</div>
|
||||
<div class="ml-options-db-actions">
|
||||
<button id="ml-options-hf-index-refresh" type="button" class="ml-btn ml-btn-secondary">${getSvgIcon('refreshCw')} Refresh Comfy-Org Index</button>
|
||||
<div id="mr-options-hf-index-message" class="mr-options-db-message">Comfy-Org fallback uses a cached HuggingFace file index.</div>
|
||||
<div class="mr-options-db-actions">
|
||||
<button id="mr-options-hf-index-refresh" type="button" class="mr-btn mr-btn-secondary">${getSvgIcon('refreshCw')} Refresh Comfy-Org Index</button>
|
||||
</div>
|
||||
</div>
|
||||
<label class="ml-options-toggle-row">
|
||||
<div class="ml-options-toggle-copy">
|
||||
<span class="ml-options-toggle-title">Use Brave fallback <span class="ml-tooltip-badge" data-tooltip="Last-resort web search for the exact filename on huggingface.co. Results are still checked before Model Linker offers them.">?</span></span>
|
||||
<label class="mr-options-toggle-row">
|
||||
<div class="mr-options-toggle-copy">
|
||||
<span class="mr-options-toggle-title">Use Brave fallback <span class="mr-tooltip-badge" data-tooltip="Last-resort web search for the exact filename on huggingface.co. Results are still checked before Model Resolver offers them.">?</span></span>
|
||||
</div>
|
||||
<span class="ml-options-toggle-control">
|
||||
<input id="ml-options-hf-use-brave-fallback" class="ml-options-switch-input" type="checkbox" ${tokens.hf_use_brave_fallback ? 'checked' : ''}>
|
||||
<span class="ml-options-switch"></span>
|
||||
<span class="mr-options-toggle-control">
|
||||
<input id="mr-options-hf-use-brave-fallback" class="mr-options-switch-input" type="checkbox" ${tokens.hf_use_brave_fallback ? 'checked' : ''}>
|
||||
<span class="mr-options-switch"></span>
|
||||
</span>
|
||||
</label>
|
||||
<div class="ml-options-dependent-block">
|
||||
<div class="ml-options-field">
|
||||
<div class="ml-options-input-row">
|
||||
<label for="ml-options-brave" class="ml-options-label">Brave Search API Key <a href="https://api-dashboard.search.brave.com/app/keys" target="_blank" rel="noopener noreferrer" class="ml-options-inline-link">Get key</a> <span class="ml-tooltip-badge" data-tooltip="Optional. Used only by the Brave fallback. It helps find Hugging Face files when Hugging Face search does not show the right repo.">?</span></label>
|
||||
<input id="ml-options-brave" class="ml-options-input" type="password" placeholder="Paste Brave Search API key" value="${tokens.brave_search_api_key}">
|
||||
<button id="ml-options-brave-toggle" type="button" class="ml-options-visibility-btn" aria-label="Toggle visibility for saved Brave Search API key" data-tooltip="Show saved value">
|
||||
<div class="mr-options-dependent-block">
|
||||
<div class="mr-options-field">
|
||||
<div class="mr-options-input-row">
|
||||
<label for="mr-options-brave" class="mr-options-label">Brave Search API Key <a href="https://api-dashboard.search.brave.com/app/keys" target="_blank" rel="noopener noreferrer" class="mr-options-inline-link">Get key</a> <span class="mr-tooltip-badge" data-tooltip="Optional. Used only by the Brave fallback. It helps find Hugging Face files when Hugging Face search does not show the right repo.">?</span></label>
|
||||
<input id="mr-options-brave" class="mr-options-input" type="password" placeholder="Paste Brave Search API key" value="${tokens.brave_search_api_key}">
|
||||
<button id="mr-options-brave-toggle" type="button" class="mr-options-visibility-btn" aria-label="Toggle visibility for saved Brave Search API key" data-tooltip="Show saved value">
|
||||
${getSvgIcon('eye')}
|
||||
</button>
|
||||
</div>
|
||||
<div class="ml-options-token-check">
|
||||
<button id="ml-options-brave-key-check" type="button" class="ml-btn ml-btn-secondary ml-btn-sm">${getSvgIcon('refreshCw')} Check API key</button>
|
||||
<span id="ml-options-brave-key-check-status" class="ml-options-token-check-status">Not checked</span>
|
||||
<div class="mr-options-token-check">
|
||||
<button id="mr-options-brave-key-check" type="button" class="mr-btn mr-btn-secondary mr-btn-sm">${getSvgIcon('refreshCw')} Check API key</button>
|
||||
<span id="mr-options-brave-key-check-status" class="mr-options-token-check-status">Not checked</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -292,44 +292,44 @@ export const optionsMethods = {
|
||||
</div>
|
||||
`;
|
||||
|
||||
const civitaiInput = this.contentElement.querySelector('#ml-options-civitai');
|
||||
const civitaiSessionInput = this.contentElement.querySelector('#ml-options-civitai-session');
|
||||
const hfInput = this.contentElement.querySelector('#ml-options-hf');
|
||||
const braveInput = this.contentElement.querySelector('#ml-options-brave');
|
||||
const civitaiToggle = this.contentElement.querySelector('#ml-options-civitai-toggle');
|
||||
const civitaiSessionToggle = this.contentElement.querySelector('#ml-options-civitai-session-toggle');
|
||||
const hfToggle = this.contentElement.querySelector('#ml-options-hf-toggle');
|
||||
const braveToggle = this.contentElement.querySelector('#ml-options-brave-toggle');
|
||||
const civitaiLimitInput = this.contentElement.querySelector('#ml-options-civitai-limit');
|
||||
const civitaiUseTrpcSearchInput = this.contentElement.querySelector('#ml-options-civitai-use-trpc-search');
|
||||
const civitaiUseHtmlFallbackInput = this.contentElement.querySelector('#ml-options-civitai-use-html-fallback');
|
||||
const civitaiKeyCheckBtn = this.contentElement.querySelector('#ml-options-civitai-key-check');
|
||||
const civitaiKeyCheckStatus = this.contentElement.querySelector('#ml-options-civitai-key-check-status');
|
||||
const civitaiSessionCheckBtn = this.contentElement.querySelector('#ml-options-civitai-session-check');
|
||||
const civitaiSessionCheckStatus = this.contentElement.querySelector('#ml-options-civitai-session-check-status');
|
||||
const hfUseApiSearchInput = this.contentElement.querySelector('#ml-options-hf-use-api-search');
|
||||
const hfUseComfyOrgFallbackInput = this.contentElement.querySelector('#ml-options-hf-use-comfy-org-fallback');
|
||||
const hfUseBraveFallbackInput = this.contentElement.querySelector('#ml-options-hf-use-brave-fallback');
|
||||
const hfTokenCheckBtn = this.contentElement.querySelector('#ml-options-hf-token-check');
|
||||
const hfTokenCheckStatus = this.contentElement.querySelector('#ml-options-hf-token-check-status');
|
||||
const braveKeyCheckBtn = this.contentElement.querySelector('#ml-options-brave-key-check');
|
||||
const braveKeyCheckStatus = this.contentElement.querySelector('#ml-options-brave-key-check-status');
|
||||
const sourceEnabledInputs = Array.from(this.contentElement.querySelectorAll('.ml-options-source-enabled'));
|
||||
const status = this.contentElement.querySelector('#ml-options-status');
|
||||
const saveBtn = this.contentElement.querySelector('#ml-options-save');
|
||||
const modelListCountEl = this.contentElement.querySelector('#ml-options-model-list-count');
|
||||
const modelListUpdatedEl = this.contentElement.querySelector('#ml-options-model-list-updated');
|
||||
const modelListStateEl = this.contentElement.querySelector('#ml-options-model-list-state');
|
||||
const modelListMessageEl = this.contentElement.querySelector('#ml-options-model-list-message');
|
||||
const modelListCheckBtn = this.contentElement.querySelector('#ml-options-model-list-check');
|
||||
const modelListUpdateBtn = this.contentElement.querySelector('#ml-options-model-list-update');
|
||||
const hfIndexCountEl = this.contentElement.querySelector('#ml-options-hf-index-count');
|
||||
const hfIndexUpdatedEl = this.contentElement.querySelector('#ml-options-hf-index-updated');
|
||||
const hfIndexStateEl = this.contentElement.querySelector('#ml-options-hf-index-state');
|
||||
const hfIndexMessageEl = this.contentElement.querySelector('#ml-options-hf-index-message');
|
||||
const hfIndexRefreshBtn = this.contentElement.querySelector('#ml-options-hf-index-refresh');
|
||||
const navButtons = Array.from(this.contentElement.querySelectorAll('.ml-options-nav-btn'));
|
||||
const optionSections = Array.from(this.contentElement.querySelectorAll('.ml-options-section'));
|
||||
const civitaiInput = this.contentElement.querySelector('#mr-options-civitai');
|
||||
const civitaiSessionInput = this.contentElement.querySelector('#mr-options-civitai-session');
|
||||
const hfInput = this.contentElement.querySelector('#mr-options-hf');
|
||||
const braveInput = this.contentElement.querySelector('#mr-options-brave');
|
||||
const civitaiToggle = this.contentElement.querySelector('#mr-options-civitai-toggle');
|
||||
const civitaiSessionToggle = this.contentElement.querySelector('#mr-options-civitai-session-toggle');
|
||||
const hfToggle = this.contentElement.querySelector('#mr-options-hf-toggle');
|
||||
const braveToggle = this.contentElement.querySelector('#mr-options-brave-toggle');
|
||||
const civitaiLimitInput = this.contentElement.querySelector('#mr-options-civitai-limit');
|
||||
const civitaiUseTrpcSearchInput = this.contentElement.querySelector('#mr-options-civitai-use-trpc-search');
|
||||
const civitaiUseHtmlFallbackInput = this.contentElement.querySelector('#mr-options-civitai-use-html-fallback');
|
||||
const civitaiKeyCheckBtn = this.contentElement.querySelector('#mr-options-civitai-key-check');
|
||||
const civitaiKeyCheckStatus = this.contentElement.querySelector('#mr-options-civitai-key-check-status');
|
||||
const civitaiSessionCheckBtn = this.contentElement.querySelector('#mr-options-civitai-session-check');
|
||||
const civitaiSessionCheckStatus = this.contentElement.querySelector('#mr-options-civitai-session-check-status');
|
||||
const hfUseApiSearchInput = this.contentElement.querySelector('#mr-options-hf-use-api-search');
|
||||
const hfUseComfyOrgFallbackInput = this.contentElement.querySelector('#mr-options-hf-use-comfy-org-fallback');
|
||||
const hfUseBraveFallbackInput = this.contentElement.querySelector('#mr-options-hf-use-brave-fallback');
|
||||
const hfTokenCheckBtn = this.contentElement.querySelector('#mr-options-hf-token-check');
|
||||
const hfTokenCheckStatus = this.contentElement.querySelector('#mr-options-hf-token-check-status');
|
||||
const braveKeyCheckBtn = this.contentElement.querySelector('#mr-options-brave-key-check');
|
||||
const braveKeyCheckStatus = this.contentElement.querySelector('#mr-options-brave-key-check-status');
|
||||
const sourceEnabledInputs = Array.from(this.contentElement.querySelectorAll('.mr-options-source-enabled'));
|
||||
const status = this.contentElement.querySelector('#mr-options-status');
|
||||
const saveBtn = this.contentElement.querySelector('#mr-options-save');
|
||||
const modelListCountEl = this.contentElement.querySelector('#mr-options-model-list-count');
|
||||
const modelListUpdatedEl = this.contentElement.querySelector('#mr-options-model-list-updated');
|
||||
const modelListStateEl = this.contentElement.querySelector('#mr-options-model-list-state');
|
||||
const modelListMessageEl = this.contentElement.querySelector('#mr-options-model-list-message');
|
||||
const modelListCheckBtn = this.contentElement.querySelector('#mr-options-model-list-check');
|
||||
const modelListUpdateBtn = this.contentElement.querySelector('#mr-options-model-list-update');
|
||||
const hfIndexCountEl = this.contentElement.querySelector('#mr-options-hf-index-count');
|
||||
const hfIndexUpdatedEl = this.contentElement.querySelector('#mr-options-hf-index-updated');
|
||||
const hfIndexStateEl = this.contentElement.querySelector('#mr-options-hf-index-state');
|
||||
const hfIndexMessageEl = this.contentElement.querySelector('#mr-options-hf-index-message');
|
||||
const hfIndexRefreshBtn = this.contentElement.querySelector('#mr-options-hf-index-refresh');
|
||||
const navButtons = Array.from(this.contentElement.querySelectorAll('.mr-options-nav-btn'));
|
||||
const optionSections = Array.from(this.contentElement.querySelectorAll('.mr-options-section'));
|
||||
const trackedInputs = [
|
||||
civitaiInput,
|
||||
civitaiSessionInput,
|
||||
@@ -401,7 +401,7 @@ export const optionsMethods = {
|
||||
data.valid ? 'is-valid' : 'is-invalid'
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Model Linker: Credential check error:', error);
|
||||
console.error('Model Resolver: Credential check error:', error);
|
||||
setTokenCheckStatus(statusEl, error.message || 'Check failed', 'is-invalid');
|
||||
} finally {
|
||||
if (button) button.disabled = false;
|
||||
@@ -449,7 +449,7 @@ export const optionsMethods = {
|
||||
if (modelListStateEl) {
|
||||
modelListStateEl.textContent = checkRemote ? 'Checking GitHub...' : 'Loading...';
|
||||
}
|
||||
const response = await api.fetchApi(`/model_linker/model-list/status${checkRemote ? '?check_remote=1' : ''}`);
|
||||
const response = await api.fetchApi(`/model_resolver/model-list/status${checkRemote ? '?check_remote=1' : ''}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Status failed: ${response.status}`);
|
||||
}
|
||||
@@ -457,7 +457,7 @@ export const optionsMethods = {
|
||||
renderModelListStatus(data, checkRemote);
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Model Linker: model-list status error:', error);
|
||||
console.error('Model Resolver: model-list status error:', error);
|
||||
if (modelListStateEl) modelListStateEl.textContent = 'Error';
|
||||
if (modelListMessageEl) modelListMessageEl.textContent = error.message || 'Failed to read Local Database status.';
|
||||
return null;
|
||||
@@ -471,7 +471,7 @@ export const optionsMethods = {
|
||||
setModelListBusy(true);
|
||||
if (modelListStateEl) modelListStateEl.textContent = 'Updating...';
|
||||
if (modelListMessageEl) modelListMessageEl.textContent = 'Downloading latest ComfyUI-Manager model-list.json...';
|
||||
const response = await api.fetchApi('/model_linker/model-list/update', {
|
||||
const response = await api.fetchApi('/model_resolver/model-list/update', {
|
||||
method: 'POST'
|
||||
});
|
||||
const data = await response.json();
|
||||
@@ -484,7 +484,7 @@ export const optionsMethods = {
|
||||
if (modelListMessageEl) modelListMessageEl.textContent = `Local Database updated. ${Number(data.local_count || 0).toLocaleString()} models loaded.`;
|
||||
this.showNotification('Local Database updated', 'success');
|
||||
} catch (error) {
|
||||
console.error('Model Linker: model-list update error:', error);
|
||||
console.error('Model Resolver: model-list update error:', error);
|
||||
if (modelListStateEl) modelListStateEl.textContent = 'Error';
|
||||
if (modelListMessageEl) modelListMessageEl.textContent = error.message || 'Failed to update Local Database.';
|
||||
this.showNotification('Local Database update failed', 'error');
|
||||
@@ -528,7 +528,7 @@ export const optionsMethods = {
|
||||
try {
|
||||
setHfIndexBusy(true);
|
||||
if (hfIndexStateEl) hfIndexStateEl.textContent = 'Loading...';
|
||||
const response = await api.fetchApi('/model_linker/huggingface/author-index/status');
|
||||
const response = await api.fetchApi('/model_resolver/huggingface/author-index/status');
|
||||
if (!response.ok) {
|
||||
throw new Error(`Status failed: ${response.status}`);
|
||||
}
|
||||
@@ -536,7 +536,7 @@ export const optionsMethods = {
|
||||
renderHfIndexStatus(data);
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Model Linker: HuggingFace index status error:', error);
|
||||
console.error('Model Resolver: HuggingFace index status error:', error);
|
||||
if (hfIndexStateEl) hfIndexStateEl.textContent = 'Error';
|
||||
if (hfIndexMessageEl) hfIndexMessageEl.textContent = error.message || 'Failed to read HuggingFace index status.';
|
||||
return null;
|
||||
@@ -550,7 +550,7 @@ export const optionsMethods = {
|
||||
setHfIndexBusy(true);
|
||||
if (hfIndexStateEl) hfIndexStateEl.textContent = 'Refreshing...';
|
||||
if (hfIndexMessageEl) hfIndexMessageEl.textContent = 'Downloading Comfy-Org file index from HuggingFace...';
|
||||
const response = await api.fetchApi('/model_linker/huggingface/author-index/refresh', {
|
||||
const response = await api.fetchApi('/model_resolver/huggingface/author-index/refresh', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ hf_token: hfInput?.value || '' })
|
||||
@@ -564,7 +564,7 @@ export const optionsMethods = {
|
||||
if (hfIndexStateEl) hfIndexStateEl.textContent = 'Refreshed';
|
||||
this.showNotification('HuggingFace Comfy-Org index refreshed', 'success');
|
||||
} catch (error) {
|
||||
console.error('Model Linker: HuggingFace index refresh error:', error);
|
||||
console.error('Model Resolver: HuggingFace index refresh error:', error);
|
||||
if (hfIndexStateEl) hfIndexStateEl.textContent = 'Error';
|
||||
if (hfIndexMessageEl) hfIndexMessageEl.textContent = error.message || 'Failed to refresh HuggingFace index.';
|
||||
this.showNotification('HuggingFace index refresh failed', 'error');
|
||||
@@ -594,8 +594,8 @@ export const optionsMethods = {
|
||||
if (!input || !button) return;
|
||||
const visible = input.type === 'text';
|
||||
button.innerHTML = getVisibilityIcon(visible);
|
||||
button.classList.toggle('ml-is-active-text', visible);
|
||||
button.classList.toggle('ml-is-muted', !visible);
|
||||
button.classList.toggle('mr-is-active-text', visible);
|
||||
button.classList.toggle('mr-is-muted', !visible);
|
||||
button.setAttribute('aria-pressed', visible ? 'true' : 'false');
|
||||
this.setTooltip(button, visible ? 'Hide saved value' : 'Show saved value');
|
||||
};
|
||||
@@ -614,7 +614,7 @@ export const optionsMethods = {
|
||||
bindVisibilityToggle(hfInput, hfToggle);
|
||||
bindVisibilityToggle(braveInput, braveToggle);
|
||||
setStatus('Saved only on this machine.');
|
||||
setVisibleSection('ml-options-section-sources');
|
||||
setVisibleSection('mr-options-section-sources');
|
||||
|
||||
trackedInputs.forEach((input) => {
|
||||
const eventName = input.type === 'checkbox' ? 'change' : 'input';
|
||||
@@ -630,9 +630,9 @@ export const optionsMethods = {
|
||||
const targetId = btn.dataset.target;
|
||||
if (!targetId) return;
|
||||
setVisibleSection(targetId);
|
||||
if (targetId === 'ml-options-section-local-db') {
|
||||
if (targetId === 'mr-options-section-local-db') {
|
||||
loadModelListStatus(false);
|
||||
} else if (targetId === 'ml-options-section-hf') {
|
||||
} else if (targetId === 'mr-options-section-hf') {
|
||||
loadHfIndexStatus();
|
||||
}
|
||||
});
|
||||
@@ -662,7 +662,7 @@ export const optionsMethods = {
|
||||
input: civitaiSessionInput,
|
||||
button: civitaiSessionCheckBtn,
|
||||
statusEl: civitaiSessionCheckStatus,
|
||||
endpoint: '/model_linker/civitai/session-token/check',
|
||||
endpoint: '/model_resolver/civitai/session-token/check',
|
||||
payloadKey: 'civitai_session_token',
|
||||
missingText: 'Paste token first'
|
||||
});
|
||||
@@ -675,7 +675,7 @@ export const optionsMethods = {
|
||||
input: civitaiInput,
|
||||
button: civitaiKeyCheckBtn,
|
||||
statusEl: civitaiKeyCheckStatus,
|
||||
endpoint: '/model_linker/civitai/api-key/check',
|
||||
endpoint: '/model_resolver/civitai/api-key/check',
|
||||
payloadKey: 'civitai_key',
|
||||
missingText: 'Paste API key first'
|
||||
});
|
||||
@@ -688,7 +688,7 @@ export const optionsMethods = {
|
||||
input: hfInput,
|
||||
button: hfTokenCheckBtn,
|
||||
statusEl: hfTokenCheckStatus,
|
||||
endpoint: '/model_linker/huggingface/token/check',
|
||||
endpoint: '/model_resolver/huggingface/token/check',
|
||||
payloadKey: 'hf_token',
|
||||
missingText: 'Paste token first'
|
||||
});
|
||||
@@ -701,7 +701,7 @@ export const optionsMethods = {
|
||||
input: braveInput,
|
||||
button: braveKeyCheckBtn,
|
||||
statusEl: braveKeyCheckStatus,
|
||||
endpoint: '/model_linker/brave/api-key/check',
|
||||
endpoint: '/model_resolver/brave/api-key/check',
|
||||
payloadKey: 'brave_search_api_key',
|
||||
missingText: 'Paste API key first'
|
||||
});
|
||||
@@ -717,16 +717,16 @@ export const optionsMethods = {
|
||||
const civitaiCandidateLimit = Number.isFinite(civitaiCandidateLimitRaw)
|
||||
? Math.min(20, Math.max(1, civitaiCandidateLimitRaw))
|
||||
: 5;
|
||||
localStorage.setItem('modelLinker.civitaiApiKey', civitaiInput?.value || '');
|
||||
localStorage.setItem('modelLinker.civitaiSessionToken', civitaiSessionInput?.value || '');
|
||||
localStorage.setItem('modelLinker.civitaiUseTrpcSearch', civitaiUseTrpcSearchInput?.checked ? 'true' : 'false');
|
||||
localStorage.setItem('modelLinker.civitaiUseHtmlFallback', civitaiUseHtmlFallbackInput?.checked ? 'true' : 'false');
|
||||
localStorage.setItem('modelLinker.huggingFaceToken', hfInput?.value || '');
|
||||
localStorage.setItem('modelLinker.braveSearchApiKey', braveInput?.value || '');
|
||||
localStorage.setItem('modelLinker.hfUseApiSearch', hfUseApiSearchInput?.checked ? 'true' : 'false');
|
||||
localStorage.setItem('modelLinker.hfUseComfyOrgFallback', hfUseComfyOrgFallbackInput?.checked ? 'true' : 'false');
|
||||
localStorage.setItem('modelLinker.hfUseBraveFallback', hfUseBraveFallbackInput?.checked ? 'true' : 'false');
|
||||
localStorage.setItem('modelLinker.civitaiCandidateLimit', `${civitaiCandidateLimit}`);
|
||||
localStorage.setItem('ModelResolver.civitaiApiKey', civitaiInput?.value || '');
|
||||
localStorage.setItem('ModelResolver.civitaiSessionToken', civitaiSessionInput?.value || '');
|
||||
localStorage.setItem('ModelResolver.civitaiUseTrpcSearch', civitaiUseTrpcSearchInput?.checked ? 'true' : 'false');
|
||||
localStorage.setItem('ModelResolver.civitaiUseHtmlFallback', civitaiUseHtmlFallbackInput?.checked ? 'true' : 'false');
|
||||
localStorage.setItem('ModelResolver.huggingFaceToken', hfInput?.value || '');
|
||||
localStorage.setItem('ModelResolver.braveSearchApiKey', braveInput?.value || '');
|
||||
localStorage.setItem('ModelResolver.hfUseApiSearch', hfUseApiSearchInput?.checked ? 'true' : 'false');
|
||||
localStorage.setItem('ModelResolver.hfUseComfyOrgFallback', hfUseComfyOrgFallbackInput?.checked ? 'true' : 'false');
|
||||
localStorage.setItem('ModelResolver.hfUseBraveFallback', hfUseBraveFallbackInput?.checked ? 'true' : 'false');
|
||||
localStorage.setItem('ModelResolver.civitaiCandidateLimit', `${civitaiCandidateLimit}`);
|
||||
if (sourceEnabledInputs.length && !sourceEnabledInputs.some(input => input.checked)) {
|
||||
const localInput = sourceEnabledInputs.find(input => input.dataset.source === 'local');
|
||||
if (localInput) {
|
||||
+47
-47
@@ -1,4 +1,4 @@
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { app } from "../../../../../scripts/app.js";
|
||||
import { api } from "../../../../../scripts/api.js";
|
||||
import { $el } from "../../../../../scripts/ui.js";
|
||||
import { getSvgIcon } from "../../utils/icon_utils.js";
|
||||
@@ -16,7 +16,7 @@ export const tabsLoadedMethods = {
|
||||
const button = this.getTabButton(tab);
|
||||
if (!button) return;
|
||||
const isActive = tab === this.activeTab;
|
||||
button.classList.toggle('ml-tab-active', isActive);
|
||||
button.classList.toggle('mr-tab-active', isActive);
|
||||
button.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
||||
button.setAttribute('aria-disabled', isActive ? 'true' : 'false');
|
||||
if (isActive) {
|
||||
@@ -30,7 +30,7 @@ export const tabsLoadedMethods = {
|
||||
switchTab(tab, { force = false } = {}) {
|
||||
const nextTab = this.getValidTab(tab);
|
||||
const nextTabButton = this.getTabButton(nextTab);
|
||||
if (!force && nextTab === this.activeTab && nextTabButton?.classList.contains('ml-tab-active')) {
|
||||
if (!force && nextTab === this.activeTab && nextTabButton?.classList.contains('mr-tab-active')) {
|
||||
this.hideTooltip();
|
||||
this.updateQueueVisibility();
|
||||
return;
|
||||
@@ -77,7 +77,7 @@ export const tabsLoadedMethods = {
|
||||
}
|
||||
this.syncWorkflowScopedQueue(workflow);
|
||||
|
||||
const response = await api.fetchApi('/model_linker/loaded', {
|
||||
const response = await api.fetchApi('/model_resolver/loaded', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ workflow })
|
||||
@@ -91,9 +91,9 @@ export const tabsLoadedMethods = {
|
||||
this.displayLoadedModels(this.contentElement, data);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Model Linker: Error loading loaded models:', error);
|
||||
console.error('Model Resolver: Error loading loaded models:', error);
|
||||
if (this.contentElement) {
|
||||
this.contentElement.innerHTML = `<p class="ml-error-text">Error: ${error.message}</p>`;
|
||||
this.contentElement.innerHTML = `<p class="mr-error-text">Error: ${error.message}</p>`;
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -162,7 +162,7 @@ export const tabsLoadedMethods = {
|
||||
const inactiveString = buildCategoryStrings('inactive');
|
||||
const allString = buildCategoryStrings('all');
|
||||
|
||||
const copyIcon = getSvgIcon('copy', 'currentColor', 'ml-copy-btn-icon');
|
||||
const copyIcon = getSvgIcon('copy', 'currentColor', 'mr-copy-btn-icon');
|
||||
const stripModelExtension = (value) => String(value || 'Unknown').replace(/\.(safetensors|ckpt|pt|pth|bin|pkl|sft|onnx|gguf)$/i, '');
|
||||
const modelToken = (model, category) => {
|
||||
const fullName = model.name || model.original_path?.split(/[\/\\]/).pop() || 'Unknown';
|
||||
@@ -171,24 +171,24 @@ export const tabsLoadedMethods = {
|
||||
return `<${this.getCategoryTokenName(category)}:${name}:${strength}>`;
|
||||
};
|
||||
const copyButtonHtml = (text, extraClass = '') => `
|
||||
<button class="ml-btn-filter ml-btn-copy-compact ${extraClass}" onclick="window.MLCopy(${this.escapeJsString(text)}, this)">
|
||||
<button class="mr-btn-filter mr-btn-copy-compact ${extraClass}" onclick="window.MLCopy(${this.escapeJsString(text)}, this)">
|
||||
${copyIcon}<span>Copy</span>
|
||||
</button>`;
|
||||
const countText = (count, label) => `${count} ${label}${count === 1 ? '' : 's'}`;
|
||||
|
||||
let html = `
|
||||
<div class="ml-loaded-models-header">
|
||||
<div class="ml-loaded-title-block">
|
||||
<h3 class="ml-loaded-models-title">Loaded Models <span class="ml-loaded-total">${total}</span></h3>
|
||||
<p class="ml-loaded-models-subtitle">${countText(activeCount, 'active')} / ${countText(inactiveCount, 'inactive')}</p>
|
||||
<div class="mr-loaded-models-header">
|
||||
<div class="mr-loaded-title-block">
|
||||
<h3 class="mr-loaded-models-title">Loaded Models <span class="mr-loaded-total">${total}</span></h3>
|
||||
<p class="mr-loaded-models-subtitle">${countText(activeCount, 'active')} / ${countText(inactiveCount, 'inactive')}</p>
|
||||
</div>
|
||||
<div class="ml-loaded-filter-row">
|
||||
<button class="ml-btn-filter active" id="filter-all" onclick="window.MLFilterSwitch('all')">All (${activeCount + inactiveCount})</button>
|
||||
<button class="ml-btn-filter" id="filter-active" onclick="window.MLFilterSwitch('active')">Active (${activeCount})</button>
|
||||
<button class="ml-btn-filter" id="filter-inactive" onclick="window.MLFilterSwitch('inactive')">Inactive (${inactiveCount})</button>
|
||||
<div class="mr-loaded-filter-row">
|
||||
<button class="mr-btn-filter active" id="filter-all" onclick="window.MLFilterSwitch('all')">All (${activeCount + inactiveCount})</button>
|
||||
<button class="mr-btn-filter" id="filter-active" onclick="window.MLFilterSwitch('active')">Active (${activeCount})</button>
|
||||
<button class="mr-btn-filter" id="filter-inactive" onclick="window.MLFilterSwitch('inactive')">Inactive (${inactiveCount})</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-models-list ml-models-list-pad">
|
||||
<div class="mr-models-list mr-models-list-pad">
|
||||
`;
|
||||
|
||||
for (const [category, modelsObj] of Object.entries(byCategory)) {
|
||||
@@ -197,35 +197,35 @@ export const tabsLoadedMethods = {
|
||||
const hasInactive = modelsObj.inactive.length > 0;
|
||||
const sectionTotal = modelsObj.active.length + modelsObj.inactive.length;
|
||||
|
||||
html += `<div class="ml-model-section" data-ml-filter="all" data-ml-active="${hasActive}" data-ml-inactive="${hasInactive}">`;
|
||||
html += `<div class="mr-model-section" data-ml-filter="all" data-ml-active="${hasActive}" data-ml-inactive="${hasInactive}">`;
|
||||
|
||||
html += `<div class="ml-model-section-header">
|
||||
<div class="ml-model-section-heading">
|
||||
<span class="ml-model-section-title">${this.escapeHtml(displayName.toUpperCase())}</span>
|
||||
<span class="ml-model-section-total">${countText(sectionTotal, 'model')}</span>
|
||||
html += `<div class="mr-model-section-header">
|
||||
<div class="mr-model-section-heading">
|
||||
<span class="mr-model-section-title">${this.escapeHtml(displayName.toUpperCase())}</span>
|
||||
<span class="mr-model-section-total">${countText(sectionTotal, 'model')}</span>
|
||||
</div>
|
||||
<div class="ml-model-section-counts">
|
||||
${hasActive ? `<span class="ml-model-count-pill is-active">${countText(modelsObj.active.length, 'active')}</span>` : ''}
|
||||
${hasInactive ? `<span class="ml-model-count-pill is-inactive">${countText(modelsObj.inactive.length, 'inactive')}</span>` : ''}
|
||||
<div class="mr-model-section-counts">
|
||||
${hasActive ? `<span class="mr-model-count-pill is-active">${countText(modelsObj.active.length, 'active')}</span>` : ''}
|
||||
${hasInactive ? `<span class="mr-model-count-pill is-inactive">${countText(modelsObj.inactive.length, 'inactive')}</span>` : ''}
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
if (hasActive) {
|
||||
const activeStr = modelsObj.active.map(m => modelToken(m, category)).join(' ');
|
||||
|
||||
html += `<div class="ml-model-group ml-model-group-active">
|
||||
<div class="ml-model-group-head">
|
||||
<span class="ml-model-group-label ml-model-group-label-active"><span class="ml-model-group-dot"></span>Active <span class="ml-model-group-count">${modelsObj.active.length}</span></span>
|
||||
html += `<div class="mr-model-group mr-model-group-active">
|
||||
<div class="mr-model-group-head">
|
||||
<span class="mr-model-group-label mr-model-group-label-active"><span class="mr-model-group-dot"></span>Active <span class="mr-model-group-count">${modelsObj.active.length}</span></span>
|
||||
${copyButtonHtml(activeStr)}
|
||||
</div>
|
||||
<div class="ml-model-chip-list">`;
|
||||
<div class="mr-model-chip-list">`;
|
||||
|
||||
for (const model of modelsObj.active) {
|
||||
const fullName = model.name || model.original_path?.split(/[\/\\]/).pop() || 'Unknown';
|
||||
const name = fullName.replace(/\.(safetensors|ckpt|pt|pth|bin|pkl|sft|onnx|gguf)$/i, '');
|
||||
const strength = model.strength !== null && model.strength !== undefined ? model.strength.toFixed(2) : null;
|
||||
const modelData = encodeURIComponent(JSON.stringify(model));
|
||||
html += `<span class="ml-model-chip" data-model="${modelData}" oncontextmenu="window.MLOpenContextMenu(event, this)">${this.escapeHtml(name)}${strength !== null ? `<span class="ml-model-chip-strength">${this.escapeHtml(strength)}</span>` : ''}</span>`;
|
||||
html += `<span class="mr-model-chip" data-model="${modelData}" oncontextmenu="window.MLOpenContextMenu(event, this)">${this.escapeHtml(name)}${strength !== null ? `<span class="mr-model-chip-strength">${this.escapeHtml(strength)}</span>` : ''}</span>`;
|
||||
}
|
||||
html += `</div></div>`;
|
||||
}
|
||||
@@ -233,19 +233,19 @@ export const tabsLoadedMethods = {
|
||||
if (hasInactive) {
|
||||
const inactiveStr = modelsObj.inactive.map(m => modelToken(m, category)).join(' ');
|
||||
|
||||
html += `<div class="ml-model-group ml-model-group-inactive">
|
||||
<div class="ml-model-group-head">
|
||||
<span class="ml-model-group-label ml-model-group-label-inactive"><span class="ml-model-group-dot"></span>Inactive <span class="ml-model-group-count">${modelsObj.inactive.length}</span></span>
|
||||
html += `<div class="mr-model-group mr-model-group-inactive">
|
||||
<div class="mr-model-group-head">
|
||||
<span class="mr-model-group-label mr-model-group-label-inactive"><span class="mr-model-group-dot"></span>Inactive <span class="mr-model-group-count">${modelsObj.inactive.length}</span></span>
|
||||
${copyButtonHtml(inactiveStr, 'is-muted')}
|
||||
</div>
|
||||
<div class="ml-model-chip-list ml-model-chip-list-inactive">`;
|
||||
<div class="mr-model-chip-list mr-model-chip-list-inactive">`;
|
||||
|
||||
for (const model of modelsObj.inactive) {
|
||||
const fullName = model.name || model.original_path?.split(/[\/\\]/).pop() || 'Unknown';
|
||||
const name = fullName.replace(/\.(safetensors|ckpt|pt|pth|bin|pkl|sft|onnx|gguf)$/i, '');
|
||||
const strength = model.strength !== null && model.strength !== undefined ? model.strength.toFixed(2) : null;
|
||||
const modelData = encodeURIComponent(JSON.stringify(model));
|
||||
html += `<span class="ml-model-chip" data-model="${modelData}" oncontextmenu="window.MLOpenContextMenu(event, this)">${this.escapeHtml(name)}${strength !== null ? `<span class="ml-model-chip-strength">${this.escapeHtml(strength)}</span>` : ''}</span>`;
|
||||
html += `<span class="mr-model-chip" data-model="${modelData}" oncontextmenu="window.MLOpenContextMenu(event, this)">${this.escapeHtml(name)}${strength !== null ? `<span class="mr-model-chip-strength">${this.escapeHtml(strength)}</span>` : ''}</span>`;
|
||||
}
|
||||
html += `</div></div>`;
|
||||
}
|
||||
@@ -253,23 +253,23 @@ export const tabsLoadedMethods = {
|
||||
html += `</div>`;
|
||||
}
|
||||
|
||||
const copySectionId = 'ml-copy-' + Date.now();
|
||||
const copySectionId = 'mr-copy-' + Date.now();
|
||||
html += `
|
||||
</div>
|
||||
<div id="${copySectionId}" class="ml-copy-section" data-ml-active="${this.escapeHtml(activeString)}" data-ml-inactive="${this.escapeHtml(inactiveString)}" data-ml-all="${this.escapeHtml(allString)}" data-ml-active-count="${activeCount}" data-ml-inactive-count="${inactiveCount}" data-ml-all-count="${activeCount + inactiveCount}">
|
||||
<div class="ml-copy-toolbar">
|
||||
<div class="ml-copy-heading">
|
||||
<div class="ml-copy-label" id="${copySectionId}-label">Copy all</div>
|
||||
<div class="ml-copy-meta" id="${copySectionId}-meta">${countText(activeCount + inactiveCount, 'token')}</div>
|
||||
<div id="${copySectionId}" class="mr-copy-section" data-ml-active="${this.escapeHtml(activeString)}" data-ml-inactive="${this.escapeHtml(inactiveString)}" data-ml-all="${this.escapeHtml(allString)}" data-ml-active-count="${activeCount}" data-ml-inactive-count="${inactiveCount}" data-ml-all-count="${activeCount + inactiveCount}">
|
||||
<div class="mr-copy-toolbar">
|
||||
<div class="mr-copy-heading">
|
||||
<div class="mr-copy-label" id="${copySectionId}-label">Copy all</div>
|
||||
<div class="mr-copy-meta" id="${copySectionId}-meta">${countText(activeCount + inactiveCount, 'token')}</div>
|
||||
</div>
|
||||
<div class="ml-copy-actions">
|
||||
<button type="button" class="ml-btn-filter ml-copy-mode active" data-ml-copy-mode="all" onclick="window.MLSetCopyMode('${copySectionId}', 'all', this)">All</button>
|
||||
<button type="button" class="ml-btn-filter ml-copy-mode" data-ml-copy-mode="active" onclick="window.MLSetCopyMode('${copySectionId}', 'active', this)">Active</button>
|
||||
<button type="button" class="ml-btn-filter ml-copy-mode" data-ml-copy-mode="inactive" onclick="window.MLSetCopyMode('${copySectionId}', 'inactive', this)">Inactive</button>
|
||||
<button class="ml-btn-filter ml-copy-main-btn" onclick="window.MLCopyCode('${copySectionId}', this)">${copyIcon}<span>Copy</span></button>
|
||||
<div class="mr-copy-actions">
|
||||
<button type="button" class="mr-btn-filter mr-copy-mode active" data-ml-copy-mode="all" onclick="window.MLSetCopyMode('${copySectionId}', 'all', this)">All</button>
|
||||
<button type="button" class="mr-btn-filter mr-copy-mode" data-ml-copy-mode="active" onclick="window.MLSetCopyMode('${copySectionId}', 'active', this)">Active</button>
|
||||
<button type="button" class="mr-btn-filter mr-copy-mode" data-ml-copy-mode="inactive" onclick="window.MLSetCopyMode('${copySectionId}', 'inactive', this)">Inactive</button>
|
||||
<button class="mr-btn-filter mr-copy-main-btn" onclick="window.MLCopyCode('${copySectionId}', this)">${copyIcon}<span>Copy</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<code class="ml-copy-code" id="${copySectionId}-code">${this.escapeHtml(allString)}</code>
|
||||
<code class="mr-copy-code" id="${copySectionId}-code">${this.escapeHtml(allString)}</code>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/**
|
||||
/**
|
||||
* css_loader
|
||||
* Loads all CSS files for the resolution master components
|
||||
*/
|
||||
@@ -18,8 +18,8 @@ export function loadAllStyles() {
|
||||
// Load all CSS files using getUrl for proper path resolution
|
||||
const cssFiles = [
|
||||
'./css/css-variables.css',
|
||||
'./css/linker-shell.css',
|
||||
'./css/linker-main.css',
|
||||
'./css/resolver-shell.css',
|
||||
'./css/resolver-main.css',
|
||||
];
|
||||
|
||||
cssFiles.forEach(file => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// icon_utils - Utility for loading SVG icons
|
||||
// icon_utils - Utility for loading SVG icons
|
||||
import { app } from "../../../scripts/app.js";
|
||||
|
||||
const SVG_ICONS = {
|
||||
@@ -22,7 +22,7 @@ const SVG_ICONS = {
|
||||
windowMaximize: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="{color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5.5" y="5.5" width="13" height="13"></rect></svg>`,
|
||||
windowRestore: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="{color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 9h8v8H8z"></path><path d="M6 15H4.5V5.5H14V7"></path></svg>`,
|
||||
x: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="{color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6.5 6.5l11 11"></path><path d="M17.5 6.5l-11 11"></path></svg>`,
|
||||
civitai: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 178 178" aria-hidden="true"><defs><linearGradient id="ml-civitai-gradient" gradientUnits="userSpaceOnUse" x1="89.3" y1="-665.5" x2="89.3" y2="-841.1" gradientTransform="matrix(1 0 0 -1 0 -664)"><stop offset="0" stop-color="#1284F7"></stop><stop offset="1" stop-color="#0A20C9"></stop></linearGradient></defs><path fill="#000" d="M13.3,45.4v87.7l76,43.9l76-43.9V45.4l-76-43.9L13.3,45.4z"></path><path fill="url(#ml-civitai-gradient)" d="M89.3,29.2l52,30v60l-52,30l-52-30v-60L89.3,29.2 M89.3,1.5l-76,43.9v87.8l76,43.9l76-43.9V45.4L89.3,1.5z"></path><path fill="#FFF" d="M104.1,97.2l-14.9,8.5l-14.9-8.5v-17l14.9-8.5l14.9,8.5h18.2V69.7l-33-19l-33,19v38.1l33,19l33-19V97.2H104.1z"></path></svg>`,
|
||||
civitai: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 178 178" aria-hidden="true"><defs><linearGradient id="mr-civitai-gradient" gradientUnits="userSpaceOnUse" x1="89.3" y1="-665.5" x2="89.3" y2="-841.1" gradientTransform="matrix(1 0 0 -1 0 -664)"><stop offset="0" stop-color="#1284F7"></stop><stop offset="1" stop-color="#0A20C9"></stop></linearGradient></defs><path fill="#000" d="M13.3,45.4v87.7l76,43.9l76-43.9V45.4l-76-43.9L13.3,45.4z"></path><path fill="url(#mr-civitai-gradient)" d="M89.3,29.2l52,30v60l-52,30l-52-30v-60L89.3,29.2 M89.3,1.5l-76,43.9v87.8l76,43.9l76-43.9V45.4L89.3,1.5z"></path><path fill="#FFF" d="M104.1,97.2l-14.9,8.5l-14.9-8.5v-17l14.9-8.5l14.9,8.5h18.2V69.7l-33-19l-33,19v38.1l33,19l33-19V97.2H104.1z"></path></svg>`,
|
||||
huggingface: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 95 88" fill="none" aria-hidden="true"><path fill="#FFD21E" d="M47.21 76.5a34.75 34.75 0 1 0 0-69.5 34.75 34.75 0 0 0 0 69.5Z"></path><path fill="#FF9D0B" d="M81.96 41.75a34.75 34.75 0 1 0-69.5 0 34.75 34.75 0 0 0 69.5 0Zm-73.5 0a38.75 38.75 0 1 1 77.5 0 38.75 38.75 0 0 1-77.5 0Z"></path><path fill="#3A3B45" d="M58.5 32.3c1.28.44 1.78 3.06 3.07 2.38a5 5 0 1 0-6.76-2.07c.61 1.15 2.55-.72 3.7-.32ZM34.95 32.3c-1.28.44-1.79 3.06-3.07 2.38a5 5 0 1 1 6.76-2.07c-.61 1.15-2.56-.72-3.7-.32Z"></path><path fill="#FF323D" d="M46.96 56.29c9.83 0 13-8.76 13-13.26 0-2.34-1.57-1.6-4.09-.36-2.33 1.15-5.46 2.74-8.9 2.74-7.19 0-13-6.88-13-2.38s3.16 13.26 13 13.26Z"></path><path fill="#3A3B45" fill-rule="evenodd" d="M39.43 54a8.7 8.7 0 0 1 5.3-4.49c.4-.12.81.57 1.24 1.28.4.68.82 1.37 1.24 1.37.45 0 .9-.68 1.33-1.35.45-.7.89-1.38 1.32-1.25a8.61 8.61 0 0 1 5 4.17c3.73-2.94 5.1-7.74 5.1-10.7 0-2.34-1.57-1.6-4.09-.36l-.14.07c-2.31 1.15-5.39 2.67-8.77 2.67s-6.45-1.52-8.77-2.67c-2.6-1.29-4.23-2.1-4.23.29 0 3.05 1.46 8.06 5.47 10.97Z" clip-rule="evenodd"></path><path fill="#FF9D0B" d="M70.71 37a3.25 3.25 0 1 0 0-6.5 3.25 3.25 0 0 0 0 6.5ZM24.21 37a3.25 3.25 0 1 0 0-6.5 3.25 3.25 0 0 0 0 6.5ZM17.52 48c-1.62 0-3.06.66-4.07 1.87a5.97 5.97 0 0 0-1.33 3.76 7.1 7.1 0 0 0-1.94-.3c-1.55 0-2.95.59-3.94 1.66a5.8 5.8 0 0 0-.8 7 5.3 5.3 0 0 0-1.79 2.82c-.24.9-.48 2.8.8 4.74a5.22 5.22 0 0 0-.37 5.02c1.02 2.32 3.57 4.14 8.52 6.1 3.07 1.22 5.89 2 5.91 2.01a44.33 44.33 0 0 0 10.93 1.6c5.86 0 10.05-1.8 12.46-5.34 3.88-5.69 3.33-10.9-1.7-15.92-2.77-2.78-4.62-6.87-5-7.77-.78-2.66-2.84-5.62-6.25-5.62a5.7 5.7 0 0 0-4.6 2.46c-1-1.26-1.98-2.25-2.86-2.82A7.4 7.4 0 0 0 17.52 48Zm0 4c.51 0 1.14.22 1.82.65 2.14 1.36 6.25 8.43 7.76 11.18.5.92 1.37 1.31 2.14 1.31 1.55 0 2.75-1.53.15-3.48-3.92-2.93-2.55-7.72-.68-8.01.08-.02.17-.02.24-.02 1.7 0 2.45 2.93 2.45 2.93s2.2 5.52 5.98 9.3c3.77 3.77 3.97 6.8 1.22 10.83-1.88 2.75-5.47 3.58-9.16 3.58-3.81 0-7.73-.9-9.92-1.46-.11-.03-13.45-3.8-11.76-7 .28-.54.75-.76 1.34-.76 2.38 0 6.7 3.54 8.57 3.54.41 0 .7-.17.83-.6.79-2.85-12.06-4.05-10.98-8.17.2-.73.71-1.02 1.44-1.02 3.14 0 10.2 5.53 11.68 5.53.11 0 .2-.03.24-.1.74-1.2.33-2.04-4.9-5.2-5.21-3.16-8.88-5.06-6.8-7.33.24-.26.58-.38 1-.38 3.17 0 10.66 6.82 10.66 6.82s2.02 2.1 3.25 2.1c.28 0 .52-.1.68-.38.86-1.46-8.06-8.22-8.56-11.01-.34-1.9.24-2.85 1.31-2.85Z"></path><path fill="#FFD21E" d="M38.6 76.69c2.75-4.04 2.55-7.07-1.22-10.84-3.78-3.77-5.98-9.3-5.98-9.3s-.82-3.2-2.69-2.9c-1.87.3-3.24 5.08.68 8.01 3.91 2.93-.78 4.92-2.29 2.17-1.5-2.75-5.62-9.82-7.76-11.18-2.13-1.35-3.63-.6-3.13 2.2.5 2.79 9.43 9.55 8.56 11-.87 1.47-3.93-1.71-3.93-1.71s-9.57-8.71-11.66-6.44c-2.08 2.27 1.59 4.17 6.8 7.33 5.23 3.16 5.64 4 4.9 5.2-.75 1.2-12.28-8.53-13.36-4.4-1.08 4.11 11.77 5.3 10.98 8.15-.8 2.85-9.06-5.38-10.74-2.18-1.7 3.21 11.65 6.98 11.76 7.01 4.3 1.12 15.25 3.49 19.08-2.12Z"></path><path fill="#FF9D0B" d="M77.4 48c1.62 0 3.07.66 4.07 1.87a5.97 5.97 0 0 1 1.33 3.76 7.1 7.1 0 0 1 1.95-.3c1.55 0 2.95.59 3.94 1.66a5.8 5.8 0 0 1 .8 7 5.3 5.3 0 0 1 1.78 2.82c.24.9.48 2.8-.8 4.74a5.22 5.22 0 0 1 .37 5.02c-1.02 2.32-3.57 4.14-8.51 6.1-3.08 1.22-5.9 2-5.92 2.01a44.33 44.33 0 0 1-10.93 1.6c-5.86 0-10.05-1.8-12.46-5.34-3.88-5.69-3.33-10.9 1.7-15.92 2.78-2.78 4.63-6.87 5.01-7.77.78-2.66 2.83-5.62 6.24-5.62a5.7 5.7 0 0 1 4.6 2.46c1-1.26 1.98-2.25 2.87-2.82A7.4 7.4 0 0 1 77.4 48Zm0 4c-.51 0-1.13.22-1.82.65-2.13 1.36-6.25 8.43-7.76 11.18a2.43 2.43 0 0 1-2.14 1.31c-1.54 0-2.75-1.53-.14-3.48 3.91-2.93 2.54-7.72.67-8.01a1.54 1.54 0 0 0-.24-.02c-1.7 0-2.45 2.93-2.45 2.93s-2.2 5.52-5.97 9.3c-3.78 3.77-3.98 6.8-1.22 10.83 1.87 2.75 5.47 3.58 9.15 3.58 3.82 0 7.73-.9 9.93-1.46.1-.03 13.45-3.8 11.76-7-.29-.54-.75-.76-1.34-.76-2.38 0-6.71 3.54-8.57 3.54-.42 0-.71-.17-.83-.6-.8-2.85 12.05-4.05 10.97-8.17-.19-.73-.7-1.02-1.44-1.02-3.14 0-10.2 5.53-11.68 5.53-.1 0-.19-.03-.23-.1-.74-1.2-.34-2.04 4.88-5.2 5.23-3.16 8.9-5.06 6.8-7.33-.23-.26-.57-.38-.98-.38-3.18 0-10.67 6.82-10.67 6.82s-2.02 2.1-3.24 2.1a.74.74 0 0 1-.68-.38c-.87-1.46 8.05-8.22 8.55-11.01.34-1.9-.24-2.85-1.31-2.85Z"></path><path fill="#FFD21E" d="M56.33 76.69c-2.75-4.04-2.56-7.07 1.22-10.84 3.77-3.77 5.97-9.3 5.97-9.3s.82-3.2 2.7-2.9c1.86.3 3.23 5.08-.68 8.01-3.92 2.93.78 4.92 2.28 2.17 1.51-2.75 5.63-9.82 7.76-11.18 2.13-1.35 3.64-.6 3.13 2.2-.5 2.79-9.42 9.55-8.55 11 .86 1.47 3.92-1.71 3.92-1.71s9.58-8.71 11.66-6.44c2.08 2.27-1.58 4.17-6.8 7.33-5.23 3.16-5.63 4-4.9 5.2.75 1.2 12.28-8.53 13.36-4.4 1.08 4.11-11.76 5.3-10.97 8.15.8 2.85 9.05-5.38 10.74-2.18 1.69 3.21-11.65 6.98-11.76 7.01-4.31 1.12-15.26 3.49-19.08-2.12Z"></path></svg>`
|
||||
};
|
||||
|
||||
@@ -37,7 +37,7 @@ const SVG_ICONS = {
|
||||
export function getIconHtml(icon, fallback = '', size = 14, extraStyles = '') {
|
||||
if (!icon) return fallback;
|
||||
const styleAttr = extraStyles ? ` style="${extraStyles}"` : '';
|
||||
return `<img src="${icon.src}" class="ml-icon-img" width="${size}" height="${size}"${styleAttr}>`;
|
||||
return `<img src="${icon.src}" class="mr-icon-img" width="${size}" height="${size}"${styleAttr}>`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user