mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-05-06 16:36:45 -03:00
Compare commits
5 Commits
1ed5eef985
...
12bbb0572d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12bbb0572d | ||
|
|
00f5c1e887 | ||
|
|
89b1675ec7 | ||
|
|
dcc7bd33b5 | ||
|
|
e5152108ba |
141
py/routes/handlers/base_model_handlers.py
Normal file
141
py/routes/handlers/base_model_handlers.py
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
"""Handlers for base model related endpoints."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Any, Awaitable, Callable, Dict
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
|
||||||
|
from ...services.civitai_base_model_service import get_civitai_base_model_service
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseModelHandlerSet:
|
||||||
|
"""Collection of handlers for base model operations."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
base_model_service_factory: Callable[[], Any] = get_civitai_base_model_service,
|
||||||
|
) -> None:
|
||||||
|
self._base_model_service_factory = base_model_service_factory
|
||||||
|
|
||||||
|
def to_route_mapping(
|
||||||
|
self,
|
||||||
|
) -> Dict[str, Callable[[web.Request], Awaitable[web.StreamResponse]]]:
|
||||||
|
"""Return mapping of route names to handler methods."""
|
||||||
|
return {
|
||||||
|
"get_base_models": self.get_base_models,
|
||||||
|
"refresh_base_models": self.refresh_base_models,
|
||||||
|
"get_base_model_categories": self.get_base_model_categories,
|
||||||
|
"get_base_model_cache_status": self.get_base_model_cache_status,
|
||||||
|
}
|
||||||
|
|
||||||
|
async def get_base_models(self, request: web.Request) -> web.Response:
|
||||||
|
"""Get merged base models (hardcoded + remote from Civitai).
|
||||||
|
|
||||||
|
Query Parameters:
|
||||||
|
refresh: If 'true', force refresh from API
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON response with:
|
||||||
|
- models: List of base model names
|
||||||
|
- source: 'cache', 'api', or 'fallback'
|
||||||
|
- last_updated: ISO timestamp
|
||||||
|
- counts: hardcoded_count, remote_count, merged_count
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
service = await self._base_model_service_factory()
|
||||||
|
|
||||||
|
# Check for refresh parameter
|
||||||
|
force_refresh = request.query.get("refresh", "").lower() == "true"
|
||||||
|
|
||||||
|
result = await service.get_base_models(force_refresh=force_refresh)
|
||||||
|
|
||||||
|
return web.json_response(
|
||||||
|
{
|
||||||
|
"success": True,
|
||||||
|
"data": result,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in get_base_models: {e}")
|
||||||
|
return web.json_response(
|
||||||
|
{"success": False, "error": str(e)},
|
||||||
|
status=500,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def refresh_base_models(self, request: web.Request) -> web.Response:
|
||||||
|
"""Force refresh base models from Civitai API.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON response with refreshed data
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
service = await self._base_model_service_factory()
|
||||||
|
result = await service.refresh_cache()
|
||||||
|
|
||||||
|
return web.json_response(
|
||||||
|
{
|
||||||
|
"success": True,
|
||||||
|
"data": result,
|
||||||
|
"message": "Base models cache refreshed successfully",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in refresh_base_models: {e}")
|
||||||
|
return web.json_response(
|
||||||
|
{"success": False, "error": str(e)},
|
||||||
|
status=500,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def get_base_model_categories(self, request: web.Request) -> web.Response:
|
||||||
|
"""Get categorized base models.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON response with categorized models
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
service = await self._base_model_service_factory()
|
||||||
|
categories = service.get_model_categories()
|
||||||
|
|
||||||
|
return web.json_response(
|
||||||
|
{
|
||||||
|
"success": True,
|
||||||
|
"data": categories,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in get_base_model_categories: {e}")
|
||||||
|
return web.json_response(
|
||||||
|
{"success": False, "error": str(e)},
|
||||||
|
status=500,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def get_base_model_cache_status(self, request: web.Request) -> web.Response:
|
||||||
|
"""Get cache status for base models.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON response with cache status
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
service = await self._base_model_service_factory()
|
||||||
|
status = service.get_cache_status()
|
||||||
|
|
||||||
|
return web.json_response(
|
||||||
|
{
|
||||||
|
"success": True,
|
||||||
|
"data": status,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in get_base_model_cache_status: {e}")
|
||||||
|
return web.json_response(
|
||||||
|
{"success": False, "error": str(e)},
|
||||||
|
status=500,
|
||||||
|
)
|
||||||
@@ -40,6 +40,7 @@ from ...utils.civitai_utils import rewrite_preview_url
|
|||||||
from ...utils.example_images_paths import is_valid_example_images_root
|
from ...utils.example_images_paths import is_valid_example_images_root
|
||||||
from ...utils.lora_metadata import extract_trained_words
|
from ...utils.lora_metadata import extract_trained_words
|
||||||
from ...utils.usage_stats import UsageStats
|
from ...utils.usage_stats import UsageStats
|
||||||
|
from .base_model_handlers import BaseModelHandlerSet
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -1618,6 +1619,7 @@ class MiscHandlerSet:
|
|||||||
custom_words: CustomWordsHandler,
|
custom_words: CustomWordsHandler,
|
||||||
supporters: SupportersHandler,
|
supporters: SupportersHandler,
|
||||||
example_workflows: ExampleWorkflowsHandler,
|
example_workflows: ExampleWorkflowsHandler,
|
||||||
|
base_model: BaseModelHandlerSet,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.health = health
|
self.health = health
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
@@ -1632,6 +1634,7 @@ class MiscHandlerSet:
|
|||||||
self.custom_words = custom_words
|
self.custom_words = custom_words
|
||||||
self.supporters = supporters
|
self.supporters = supporters
|
||||||
self.example_workflows = example_workflows
|
self.example_workflows = example_workflows
|
||||||
|
self.base_model = base_model
|
||||||
|
|
||||||
def to_route_mapping(
|
def to_route_mapping(
|
||||||
self,
|
self,
|
||||||
@@ -1663,6 +1666,11 @@ class MiscHandlerSet:
|
|||||||
"get_supporters": self.supporters.get_supporters,
|
"get_supporters": self.supporters.get_supporters,
|
||||||
"get_example_workflows": self.example_workflows.get_example_workflows,
|
"get_example_workflows": self.example_workflows.get_example_workflows,
|
||||||
"get_example_workflow": self.example_workflows.get_example_workflow,
|
"get_example_workflow": self.example_workflows.get_example_workflow,
|
||||||
|
# Base model handlers
|
||||||
|
"get_base_models": self.base_model.get_base_models,
|
||||||
|
"refresh_base_models": self.base_model.refresh_base_models,
|
||||||
|
"get_base_model_categories": self.base_model.get_base_model_categories,
|
||||||
|
"get_base_model_cache_status": self.base_model.get_base_model_cache_status,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,15 @@ MISC_ROUTE_DEFINITIONS: tuple[RouteDefinition, ...] = (
|
|||||||
RouteDefinition(
|
RouteDefinition(
|
||||||
"GET", "/api/lm/example-workflows/{filename}", "get_example_workflow"
|
"GET", "/api/lm/example-workflows/{filename}", "get_example_workflow"
|
||||||
),
|
),
|
||||||
|
# Base model management routes
|
||||||
|
RouteDefinition("GET", "/api/lm/base-models", "get_base_models"),
|
||||||
|
RouteDefinition("POST", "/api/lm/base-models/refresh", "refresh_base_models"),
|
||||||
|
RouteDefinition(
|
||||||
|
"GET", "/api/lm/base-models/categories", "get_base_model_categories"
|
||||||
|
),
|
||||||
|
RouteDefinition(
|
||||||
|
"GET", "/api/lm/base-models/cache-status", "get_base_model_cache_status"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ from .handlers.misc_handlers import (
|
|||||||
UsageStatsHandler,
|
UsageStatsHandler,
|
||||||
build_service_registry_adapter,
|
build_service_registry_adapter,
|
||||||
)
|
)
|
||||||
|
from .handlers.base_model_handlers import BaseModelHandlerSet
|
||||||
from .misc_route_registrar import MiscRouteRegistrar
|
from .misc_route_registrar import MiscRouteRegistrar
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -128,6 +129,7 @@ class MiscRoutes:
|
|||||||
custom_words = CustomWordsHandler()
|
custom_words = CustomWordsHandler()
|
||||||
supporters = SupportersHandler()
|
supporters = SupportersHandler()
|
||||||
example_workflows = ExampleWorkflowsHandler()
|
example_workflows = ExampleWorkflowsHandler()
|
||||||
|
base_model = BaseModelHandlerSet()
|
||||||
|
|
||||||
return self._handler_set_factory(
|
return self._handler_set_factory(
|
||||||
health=health,
|
health=health,
|
||||||
@@ -143,6 +145,7 @@ class MiscRoutes:
|
|||||||
custom_words=custom_words,
|
custom_words=custom_words,
|
||||||
supporters=supporters,
|
supporters=supporters,
|
||||||
example_workflows=example_workflows,
|
example_workflows=example_workflows,
|
||||||
|
base_model=base_model,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
430
py/services/civitai_base_model_service.py
Normal file
430
py/services/civitai_base_model_service.py
Normal file
@@ -0,0 +1,430 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from typing import Any, Dict, List, Optional, Set, Tuple
|
||||||
|
|
||||||
|
from ..utils.constants import SUPPORTED_DOWNLOAD_SKIP_BASE_MODELS
|
||||||
|
from .downloader import get_downloader
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CivitaiBaseModelService:
|
||||||
|
"""Service for fetching and managing Civitai base models.
|
||||||
|
|
||||||
|
This service provides:
|
||||||
|
- Fetching base models from Civitai API
|
||||||
|
- Caching with TTL (7 days default)
|
||||||
|
- Merging hardcoded and remote base models
|
||||||
|
- Generating abbreviations for new/unknown models
|
||||||
|
"""
|
||||||
|
|
||||||
|
_instance: Optional[CivitaiBaseModelService] = None
|
||||||
|
_lock = asyncio.Lock()
|
||||||
|
|
||||||
|
# Default TTL for cache in seconds (7 days)
|
||||||
|
DEFAULT_CACHE_TTL = 7 * 24 * 60 * 60
|
||||||
|
|
||||||
|
# Civitai API endpoint for enums
|
||||||
|
CIVITAI_ENUMS_URL = "https://civitai.com/api/v1/enums"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def get_instance(cls) -> CivitaiBaseModelService:
|
||||||
|
"""Get singleton instance of the service."""
|
||||||
|
async with cls._lock:
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = cls()
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize the service."""
|
||||||
|
if hasattr(self, "_initialized"):
|
||||||
|
return
|
||||||
|
self._initialized = True
|
||||||
|
|
||||||
|
# Cache storage
|
||||||
|
self._cache: Optional[Dict[str, Any]] = None
|
||||||
|
self._cache_timestamp: Optional[datetime] = None
|
||||||
|
self._cache_ttl = self.DEFAULT_CACHE_TTL
|
||||||
|
|
||||||
|
# Hardcoded models for fallback
|
||||||
|
self._hardcoded_models = set(SUPPORTED_DOWNLOAD_SKIP_BASE_MODELS)
|
||||||
|
|
||||||
|
logger.info("CivitaiBaseModelService initialized")
|
||||||
|
|
||||||
|
async def get_base_models(self, force_refresh: bool = False) -> Dict[str, Any]:
|
||||||
|
"""Get merged base models (hardcoded + remote).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
force_refresh: If True, fetch from API regardless of cache state.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary containing:
|
||||||
|
- models: List of merged base model names
|
||||||
|
- source: 'cache', 'api', or 'fallback'
|
||||||
|
- last_updated: ISO timestamp of last successful API fetch
|
||||||
|
- hardcoded_count: Number of hardcoded models
|
||||||
|
- remote_count: Number of remote models
|
||||||
|
- merged_count: Total unique models
|
||||||
|
"""
|
||||||
|
# Check if cache is valid
|
||||||
|
if not force_refresh and self._is_cache_valid():
|
||||||
|
logger.debug("Returning cached base models")
|
||||||
|
return self._build_response("cache")
|
||||||
|
|
||||||
|
# Try to fetch from API
|
||||||
|
try:
|
||||||
|
remote_models = await self._fetch_from_civitai()
|
||||||
|
if remote_models:
|
||||||
|
self._update_cache(remote_models)
|
||||||
|
return self._build_response("api")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to fetch base models from Civitai: {e}")
|
||||||
|
|
||||||
|
# Fallback to hardcoded models
|
||||||
|
return self._build_response("fallback")
|
||||||
|
|
||||||
|
async def refresh_cache(self) -> Dict[str, Any]:
|
||||||
|
"""Force refresh the cache from Civitai API.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response dict same as get_base_models()
|
||||||
|
"""
|
||||||
|
return await self.get_base_models(force_refresh=True)
|
||||||
|
|
||||||
|
def get_cache_status(self) -> Dict[str, Any]:
|
||||||
|
"""Get current cache status.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary containing:
|
||||||
|
- has_cache: Whether cache exists
|
||||||
|
- last_updated: ISO timestamp or None
|
||||||
|
- is_expired: Whether cache is expired
|
||||||
|
- ttl_seconds: TTL in seconds
|
||||||
|
- age_seconds: Age of cache in seconds (if exists)
|
||||||
|
"""
|
||||||
|
if self._cache is None or self._cache_timestamp is None:
|
||||||
|
return {
|
||||||
|
"has_cache": False,
|
||||||
|
"last_updated": None,
|
||||||
|
"is_expired": True,
|
||||||
|
"ttl_seconds": self._cache_ttl,
|
||||||
|
"age_seconds": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
age = (datetime.now(timezone.utc) - self._cache_timestamp).total_seconds()
|
||||||
|
return {
|
||||||
|
"has_cache": True,
|
||||||
|
"last_updated": self._cache_timestamp.isoformat(),
|
||||||
|
"is_expired": age > self._cache_ttl,
|
||||||
|
"ttl_seconds": self._cache_ttl,
|
||||||
|
"age_seconds": int(age),
|
||||||
|
}
|
||||||
|
|
||||||
|
def generate_abbreviation(self, model_name: str) -> str:
|
||||||
|
"""Generate abbreviation for a base model name.
|
||||||
|
|
||||||
|
Algorithm:
|
||||||
|
1. Extract version patterns (e.g., "2.5" from "Wan Video 2.5")
|
||||||
|
2. Extract main acronym (e.g., "SD" from "SD 1.5")
|
||||||
|
3. Handle special cases (Flux, Wan, etc.)
|
||||||
|
4. Fallback to first letters of words (max 4 chars)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
model_name: Full base model name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Generated abbreviation (max 4 characters)
|
||||||
|
"""
|
||||||
|
if not model_name or not isinstance(model_name, str):
|
||||||
|
return "OTH"
|
||||||
|
|
||||||
|
name = model_name.strip()
|
||||||
|
if not name:
|
||||||
|
return "OTH"
|
||||||
|
|
||||||
|
# Check if it's already in hardcoded abbreviations
|
||||||
|
# This is a simplified check - in practice you'd have a mapping
|
||||||
|
lower_name = name.lower()
|
||||||
|
|
||||||
|
# Special cases
|
||||||
|
special_cases = {
|
||||||
|
"sd 1.4": "SD1",
|
||||||
|
"sd 1.5": "SD1",
|
||||||
|
"sd 1.5 lcm": "SD1",
|
||||||
|
"sd 1.5 hyper": "SD1",
|
||||||
|
"sd 2.0": "SD2",
|
||||||
|
"sd 2.1": "SD2",
|
||||||
|
"sd 3": "SD3",
|
||||||
|
"sd 3.5": "SD3",
|
||||||
|
"sd 3.5 medium": "SD3",
|
||||||
|
"sd 3.5 large": "SD3",
|
||||||
|
"sd 3.5 large turbo": "SD3",
|
||||||
|
"sdxl 1.0": "XL",
|
||||||
|
"sdxl lightning": "XL",
|
||||||
|
"sdxl hyper": "XL",
|
||||||
|
"flux.1 d": "F1D",
|
||||||
|
"flux.1 s": "F1S",
|
||||||
|
"flux.1 krea": "F1KR",
|
||||||
|
"flux.1 kontext": "F1KX",
|
||||||
|
"flux.2 d": "F2D",
|
||||||
|
"flux.2 klein 9b": "FK9",
|
||||||
|
"flux.2 klein 9b-base": "FK9B",
|
||||||
|
"flux.2 klein 4b": "FK4",
|
||||||
|
"flux.2 klein 4b-base": "FK4B",
|
||||||
|
"auraflow": "AF",
|
||||||
|
"chroma": "CHR",
|
||||||
|
"pixart a": "PXA",
|
||||||
|
"pixart e": "PXE",
|
||||||
|
"hunyuan 1": "HY",
|
||||||
|
"hunyuan video": "HYV",
|
||||||
|
"lumina": "L",
|
||||||
|
"kolors": "KLR",
|
||||||
|
"noobai": "NAI",
|
||||||
|
"illustrious": "IL",
|
||||||
|
"pony": "PONY",
|
||||||
|
"pony v7": "PNY7",
|
||||||
|
"hidream": "HID",
|
||||||
|
"qwen": "QWEN",
|
||||||
|
"zimageturbo": "ZIT",
|
||||||
|
"zimagebase": "ZIB",
|
||||||
|
"anima": "ANI",
|
||||||
|
"svd": "SVD",
|
||||||
|
"ltxv": "LTXV",
|
||||||
|
"ltxv2": "LTV2",
|
||||||
|
"ltxv 2.3": "LTX",
|
||||||
|
"cogvideox": "CVX",
|
||||||
|
"mochi": "MCHI",
|
||||||
|
"wan video": "WAN",
|
||||||
|
"wan video 1.3b t2v": "WAN",
|
||||||
|
"wan video 14b t2v": "WAN",
|
||||||
|
"wan video 14b i2v 480p": "WAN",
|
||||||
|
"wan video 14b i2v 720p": "WAN",
|
||||||
|
"wan video 2.2 ti2v-5b": "WAN",
|
||||||
|
"wan video 2.2 t2v-a14b": "WAN",
|
||||||
|
"wan video 2.2 i2v-a14b": "WAN",
|
||||||
|
"wan video 2.5 t2v": "WAN",
|
||||||
|
"wan video 2.5 i2v": "WAN",
|
||||||
|
}
|
||||||
|
|
||||||
|
if lower_name in special_cases:
|
||||||
|
return special_cases[lower_name]
|
||||||
|
|
||||||
|
# Try to extract acronym from version pattern
|
||||||
|
# e.g., "Model Name 2.5" -> "MN25"
|
||||||
|
version_match = re.search(r"(\d+(?:\.\d+)?)", name)
|
||||||
|
version = version_match.group(1) if version_match else ""
|
||||||
|
|
||||||
|
# Remove version and common words
|
||||||
|
words = re.sub(r"\d+(?:\.\d+)?", "", name)
|
||||||
|
words = re.sub(
|
||||||
|
r"\b(model|video|diffusion|checkpoint|textualinversion)\b",
|
||||||
|
"",
|
||||||
|
words,
|
||||||
|
flags=re.I,
|
||||||
|
)
|
||||||
|
words = words.strip()
|
||||||
|
|
||||||
|
# Get first letters of remaining words
|
||||||
|
tokens = re.findall(r"[A-Za-z]+", words)
|
||||||
|
if tokens:
|
||||||
|
# Build abbreviation from first letters
|
||||||
|
abbrev = "".join(token[0].upper() for token in tokens)
|
||||||
|
# Add version if present
|
||||||
|
if version:
|
||||||
|
# Clean version (remove dots for abbreviation)
|
||||||
|
version_clean = version.replace(".", "")
|
||||||
|
abbrev = abbrev[: 4 - len(version_clean)] + version_clean
|
||||||
|
return abbrev[:4]
|
||||||
|
|
||||||
|
# Final fallback: just take first 4 alphanumeric chars
|
||||||
|
alphanumeric = re.sub(r"[^A-Za-z0-9]", "", name)
|
||||||
|
if alphanumeric:
|
||||||
|
return alphanumeric[:4].upper()
|
||||||
|
|
||||||
|
return "OTH"
|
||||||
|
|
||||||
|
async def _fetch_from_civitai(self) -> Optional[Set[str]]:
|
||||||
|
"""Fetch base models from Civitai API.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Set of base model names, or None if failed
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
downloader = await get_downloader()
|
||||||
|
success, result = await downloader.make_request(
|
||||||
|
"GET",
|
||||||
|
self.CIVITAI_ENUMS_URL,
|
||||||
|
use_auth=False, # enums endpoint doesn't require auth
|
||||||
|
)
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
logger.warning(f"Failed to fetch enums from Civitai: {result}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
if isinstance(result, str):
|
||||||
|
data = json.loads(result)
|
||||||
|
else:
|
||||||
|
data = result
|
||||||
|
|
||||||
|
# Extract base models from response
|
||||||
|
base_models = set()
|
||||||
|
|
||||||
|
# Use ActiveBaseModel if available (recommended active models)
|
||||||
|
if "ActiveBaseModel" in data:
|
||||||
|
base_models.update(data["ActiveBaseModel"])
|
||||||
|
logger.info(f"Fetched {len(base_models)} models from ActiveBaseModel")
|
||||||
|
# Fallback to full BaseModel list
|
||||||
|
elif "BaseModel" in data:
|
||||||
|
base_models.update(data["BaseModel"])
|
||||||
|
logger.info(f"Fetched {len(base_models)} models from BaseModel")
|
||||||
|
else:
|
||||||
|
logger.warning("No base model data found in Civitai response")
|
||||||
|
return None
|
||||||
|
|
||||||
|
return base_models
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error fetching from Civitai: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _update_cache(self, remote_models: Set[str]) -> None:
|
||||||
|
"""Update internal cache with fetched models.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
remote_models: Set of base model names from API
|
||||||
|
"""
|
||||||
|
self._cache = {
|
||||||
|
"remote_models": sorted(remote_models),
|
||||||
|
"hardcoded_models": sorted(self._hardcoded_models),
|
||||||
|
}
|
||||||
|
self._cache_timestamp = datetime.now(timezone.utc)
|
||||||
|
logger.info(f"Cache updated with {len(remote_models)} remote models")
|
||||||
|
|
||||||
|
def _is_cache_valid(self) -> bool:
|
||||||
|
"""Check if current cache is valid (not expired).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if cache exists and is not expired
|
||||||
|
"""
|
||||||
|
if self._cache is None or self._cache_timestamp is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
age = (datetime.now(timezone.utc) - self._cache_timestamp).total_seconds()
|
||||||
|
return age <= self._cache_ttl
|
||||||
|
|
||||||
|
def _build_response(self, source: str) -> Dict[str, Any]:
|
||||||
|
"""Build response dictionary.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source: 'cache', 'api', or 'fallback'
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response dictionary
|
||||||
|
"""
|
||||||
|
if source == "fallback" or self._cache is None:
|
||||||
|
# Use only hardcoded models
|
||||||
|
merged = sorted(self._hardcoded_models)
|
||||||
|
return {
|
||||||
|
"models": merged,
|
||||||
|
"source": source,
|
||||||
|
"last_updated": None,
|
||||||
|
"hardcoded_count": len(self._hardcoded_models),
|
||||||
|
"remote_count": 0,
|
||||||
|
"merged_count": len(merged),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Merge hardcoded and remote models
|
||||||
|
remote_set = set(self._cache.get("remote_models", []))
|
||||||
|
merged = sorted(self._hardcoded_models | remote_set)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"models": merged,
|
||||||
|
"source": source,
|
||||||
|
"last_updated": self._cache_timestamp.isoformat()
|
||||||
|
if self._cache_timestamp
|
||||||
|
else None,
|
||||||
|
"hardcoded_count": len(self._hardcoded_models),
|
||||||
|
"remote_count": len(remote_set),
|
||||||
|
"merged_count": len(merged),
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_model_categories(self) -> Dict[str, List[str]]:
|
||||||
|
"""Get categorized base models.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary mapping category names to lists of model names
|
||||||
|
"""
|
||||||
|
# Define category patterns
|
||||||
|
categories = {
|
||||||
|
"Stable Diffusion 1.x": ["SD 1.4", "SD 1.5", "SD 1.5 LCM", "SD 1.5 Hyper"],
|
||||||
|
"Stable Diffusion 2.x": ["SD 2.0", "SD 2.1"],
|
||||||
|
"Stable Diffusion 3.x": [
|
||||||
|
"SD 3",
|
||||||
|
"SD 3.5",
|
||||||
|
"SD 3.5 Medium",
|
||||||
|
"SD 3.5 Large",
|
||||||
|
"SD 3.5 Large Turbo",
|
||||||
|
],
|
||||||
|
"SDXL": ["SDXL 1.0", "SDXL Lightning", "SDXL Hyper"],
|
||||||
|
"Flux Models": [
|
||||||
|
"Flux.1 D",
|
||||||
|
"Flux.1 S",
|
||||||
|
"Flux.1 Krea",
|
||||||
|
"Flux.1 Kontext",
|
||||||
|
"Flux.2 D",
|
||||||
|
"Flux.2 Klein 9B",
|
||||||
|
"Flux.2 Klein 9B-base",
|
||||||
|
"Flux.2 Klein 4B",
|
||||||
|
"Flux.2 Klein 4B-base",
|
||||||
|
],
|
||||||
|
"Video Models": [
|
||||||
|
"SVD",
|
||||||
|
"LTXV",
|
||||||
|
"LTXV2",
|
||||||
|
"LTXV 2.3",
|
||||||
|
"CogVideoX",
|
||||||
|
"Mochi",
|
||||||
|
"Hunyuan Video",
|
||||||
|
"Wan Video",
|
||||||
|
"Wan Video 1.3B t2v",
|
||||||
|
"Wan Video 14B t2v",
|
||||||
|
"Wan Video 14B i2v 480p",
|
||||||
|
"Wan Video 14B i2v 720p",
|
||||||
|
"Wan Video 2.2 TI2V-5B",
|
||||||
|
"Wan Video 2.2 T2V-A14B",
|
||||||
|
"Wan Video 2.2 I2V-A14B",
|
||||||
|
"Wan Video 2.5 T2V",
|
||||||
|
"Wan Video 2.5 I2V",
|
||||||
|
],
|
||||||
|
"Other Models": [
|
||||||
|
"Illustrious",
|
||||||
|
"Pony",
|
||||||
|
"Pony V7",
|
||||||
|
"HiDream",
|
||||||
|
"Qwen",
|
||||||
|
"AuraFlow",
|
||||||
|
"Chroma",
|
||||||
|
"ZImageTurbo",
|
||||||
|
"ZImageBase",
|
||||||
|
"PixArt a",
|
||||||
|
"PixArt E",
|
||||||
|
"Hunyuan 1",
|
||||||
|
"Lumina",
|
||||||
|
"Kolors",
|
||||||
|
"NoobAI",
|
||||||
|
"Anima",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
return categories
|
||||||
|
|
||||||
|
|
||||||
|
# Convenience function for getting the singleton instance
|
||||||
|
async def get_civitai_base_model_service() -> CivitaiBaseModelService:
|
||||||
|
"""Get the singleton instance of CivitaiBaseModelService."""
|
||||||
|
return await CivitaiBaseModelService.get_instance()
|
||||||
@@ -110,6 +110,8 @@ DIFFUSION_MODEL_BASE_MODELS = frozenset(
|
|||||||
"Wan Video 2.2 T2V-A14B",
|
"Wan Video 2.2 T2V-A14B",
|
||||||
"Wan Video 2.5 T2V",
|
"Wan Video 2.5 T2V",
|
||||||
"Wan Video 2.5 I2V",
|
"Wan Video 2.5 I2V",
|
||||||
|
"CogVideoX",
|
||||||
|
"Mochi",
|
||||||
"Qwen",
|
"Qwen",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -151,6 +153,7 @@ SUPPORTED_DOWNLOAD_SKIP_BASE_MODELS = frozenset(
|
|||||||
"NoobAI",
|
"NoobAI",
|
||||||
"Illustrious",
|
"Illustrious",
|
||||||
"Pony",
|
"Pony",
|
||||||
|
"Pony V7",
|
||||||
"HiDream",
|
"HiDream",
|
||||||
"Qwen",
|
"Qwen",
|
||||||
"ZImageTurbo",
|
"ZImageTurbo",
|
||||||
@@ -158,6 +161,9 @@ SUPPORTED_DOWNLOAD_SKIP_BASE_MODELS = frozenset(
|
|||||||
"SVD",
|
"SVD",
|
||||||
"LTXV",
|
"LTXV",
|
||||||
"LTXV2",
|
"LTXV2",
|
||||||
|
"LTXV 2.3",
|
||||||
|
"CogVideoX",
|
||||||
|
"Mochi",
|
||||||
"Wan Video",
|
"Wan Video",
|
||||||
"Wan Video 1.3B t2v",
|
"Wan Video 1.3B t2v",
|
||||||
"Wan Video 14B t2v",
|
"Wan Video 14B t2v",
|
||||||
@@ -166,6 +172,9 @@ SUPPORTED_DOWNLOAD_SKIP_BASE_MODELS = frozenset(
|
|||||||
"Wan Video 2.2 TI2V-5B",
|
"Wan Video 2.2 TI2V-5B",
|
||||||
"Wan Video 2.2 T2V-A14B",
|
"Wan Video 2.2 T2V-A14B",
|
||||||
"Wan Video 2.2 I2V-A14B",
|
"Wan Video 2.2 I2V-A14B",
|
||||||
|
"Wan Video 2.5 T2V",
|
||||||
|
"Wan Video 2.5 I2V",
|
||||||
"Hunyuan Video",
|
"Hunyuan Video",
|
||||||
|
"Anima",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|||||||
164
static/js/api/civitaiBaseModelApi.js
Normal file
164
static/js/api/civitaiBaseModelApi.js
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
/**
|
||||||
|
* API client for Civitai base model management
|
||||||
|
* Handles fetching and refreshing base models from Civitai API
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { showToast } from '../utils/uiHelpers.js';
|
||||||
|
|
||||||
|
const BASE_MODEL_ENDPOINTS = {
|
||||||
|
getModels: '/api/lm/base-models',
|
||||||
|
refresh: '/api/lm/base-models/refresh',
|
||||||
|
categories: '/api/lm/base-models/categories',
|
||||||
|
cacheStatus: '/api/lm/base-models/cache-status',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Civitai Base Model API Client
|
||||||
|
*/
|
||||||
|
export class CivitaiBaseModelApi {
|
||||||
|
constructor() {
|
||||||
|
this.cache = null;
|
||||||
|
this.cacheTimestamp = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get base models (with caching)
|
||||||
|
* @param {boolean} forceRefresh - Force refresh from API
|
||||||
|
* @returns {Promise<Object>} Response with models, source, and counts
|
||||||
|
*/
|
||||||
|
async getBaseModels(forceRefresh = false) {
|
||||||
|
try {
|
||||||
|
const url = new URL(BASE_MODEL_ENDPOINTS.getModels, window.location.origin);
|
||||||
|
if (forceRefresh) {
|
||||||
|
url.searchParams.append('refresh', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch base models: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
this.cache = data.data;
|
||||||
|
this.cacheTimestamp = Date.now();
|
||||||
|
return data.data;
|
||||||
|
} else {
|
||||||
|
throw new Error(data.error || 'Failed to fetch base models');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching base models:', error);
|
||||||
|
showToast('Failed to fetch base models', { message: error.message }, 'error');
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force refresh base models from Civitai API
|
||||||
|
* @returns {Promise<Object>} Refreshed data
|
||||||
|
*/
|
||||||
|
async refreshBaseModels() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(BASE_MODEL_ENDPOINTS.refresh, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to refresh base models: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
this.cache = data.data;
|
||||||
|
this.cacheTimestamp = Date.now();
|
||||||
|
showToast('Base models refreshed successfully', {}, 'success');
|
||||||
|
return data.data;
|
||||||
|
} else {
|
||||||
|
throw new Error(data.error || 'Failed to refresh base models');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error refreshing base models:', error);
|
||||||
|
showToast('Failed to refresh base models', { message: error.message }, 'error');
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get base model categories
|
||||||
|
* @returns {Promise<Object>} Categories with model lists
|
||||||
|
*/
|
||||||
|
async getCategories() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(BASE_MODEL_ENDPOINTS.categories);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch categories: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
return data.data;
|
||||||
|
} else {
|
||||||
|
throw new Error(data.error || 'Failed to fetch categories');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching categories:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache status
|
||||||
|
* @returns {Promise<Object>} Cache status information
|
||||||
|
*/
|
||||||
|
async getCacheStatus() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(BASE_MODEL_ENDPOINTS.cacheStatus);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch cache status: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
return data.data;
|
||||||
|
} else {
|
||||||
|
throw new Error(data.error || 'Failed to fetch cache status');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching cache status:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cached models (if available)
|
||||||
|
* @returns {Object|null} Cached data or null
|
||||||
|
*/
|
||||||
|
getCachedModels() {
|
||||||
|
return this.cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if cache is available
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
hasCache() {
|
||||||
|
return this.cache !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache age in milliseconds
|
||||||
|
* @returns {number|null} Age in ms or null if no cache
|
||||||
|
*/
|
||||||
|
getCacheAge() {
|
||||||
|
if (!this.cacheTimestamp) return null;
|
||||||
|
return Date.now() - this.cacheTimestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export singleton instance
|
||||||
|
export const civitaiBaseModelApi = new CivitaiBaseModelApi();
|
||||||
@@ -17,6 +17,8 @@ import { onboardingManager } from './managers/OnboardingManager.js';
|
|||||||
import { BulkContextMenu } from './components/ContextMenu/BulkContextMenu.js';
|
import { BulkContextMenu } from './components/ContextMenu/BulkContextMenu.js';
|
||||||
import { createPageContextMenu, createGlobalContextMenu } from './components/ContextMenu/index.js';
|
import { createPageContextMenu, createGlobalContextMenu } from './components/ContextMenu/index.js';
|
||||||
import { initializeEventManagement } from './utils/eventManagementInit.js';
|
import { initializeEventManagement } from './utils/eventManagementInit.js';
|
||||||
|
import { civitaiBaseModelApi } from './api/civitaiBaseModelApi.js';
|
||||||
|
import { setDynamicBaseModels } from './utils/constants.js';
|
||||||
|
|
||||||
// Core application class
|
// Core application class
|
||||||
export class AppCore {
|
export class AppCore {
|
||||||
@@ -42,6 +44,10 @@ export class AppCore {
|
|||||||
await settingsManager.waitForInitialization();
|
await settingsManager.waitForInitialization();
|
||||||
console.log('AppCore: Settings initialized');
|
console.log('AppCore: Settings initialized');
|
||||||
|
|
||||||
|
// Initialize dynamic base models (async, non-blocking)
|
||||||
|
console.log('AppCore: Initializing dynamic base models...');
|
||||||
|
this.initializeDynamicBaseModels();
|
||||||
|
|
||||||
// Initialize managers
|
// Initialize managers
|
||||||
state.loadingManager = new LoadingManager();
|
state.loadingManager = new LoadingManager();
|
||||||
modalManager.initialize();
|
modalManager.initialize();
|
||||||
@@ -116,6 +122,21 @@ export class AppCore {
|
|||||||
window.globalContextMenuInstance = createGlobalContextMenu();
|
window.globalContextMenuInstance = createGlobalContextMenu();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize dynamic base models from Civitai API
|
||||||
|
// This is non-blocking - runs in background
|
||||||
|
async initializeDynamicBaseModels() {
|
||||||
|
try {
|
||||||
|
const result = await civitaiBaseModelApi.getBaseModels();
|
||||||
|
if (result && result.models) {
|
||||||
|
setDynamicBaseModels(result.models, result.last_updated);
|
||||||
|
console.log(`AppCore: Loaded ${result.merged_count} base models (${result.hardcoded_count} hardcoded + ${result.remote_count} remote)`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('AppCore: Failed to load dynamic base models:', error);
|
||||||
|
// Non-critical error - app continues with hardcoded models
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and export a singleton instance
|
// Create and export a singleton instance
|
||||||
|
|||||||
@@ -2,7 +2,14 @@ import { modalManager } from './ModalManager.js';
|
|||||||
import { showToast } from '../utils/uiHelpers.js';
|
import { showToast } from '../utils/uiHelpers.js';
|
||||||
import { state, createDefaultSettings } from '../state/index.js';
|
import { state, createDefaultSettings } from '../state/index.js';
|
||||||
import { resetAndReload } from '../api/modelApiFactory.js';
|
import { resetAndReload } from '../api/modelApiFactory.js';
|
||||||
import { DOWNLOAD_PATH_TEMPLATES, MAPPABLE_BASE_MODELS, PATH_TEMPLATE_PLACEHOLDERS, DEFAULT_PATH_TEMPLATES, DEFAULT_PRIORITY_TAG_CONFIG } from '../utils/constants.js';
|
import {
|
||||||
|
DOWNLOAD_PATH_TEMPLATES,
|
||||||
|
MAPPABLE_BASE_MODELS,
|
||||||
|
PATH_TEMPLATE_PLACEHOLDERS,
|
||||||
|
DEFAULT_PATH_TEMPLATES,
|
||||||
|
DEFAULT_PRIORITY_TAG_CONFIG,
|
||||||
|
getMappableBaseModelsDynamic
|
||||||
|
} from '../utils/constants.js';
|
||||||
import { translate } from '../utils/i18nHelpers.js';
|
import { translate } from '../utils/i18nHelpers.js';
|
||||||
import { i18n } from '../i18n/index.js';
|
import { i18n } from '../i18n/index.js';
|
||||||
import { configureModelCardVideo } from '../components/shared/ModelCard.js';
|
import { configureModelCardVideo } from '../components/shared/ModelCard.js';
|
||||||
@@ -184,7 +191,9 @@ export class SettingsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAvailableDownloadSkipBaseModels() {
|
getAvailableDownloadSkipBaseModels() {
|
||||||
return MAPPABLE_BASE_MODELS.filter(model => model !== 'Other');
|
// Use dynamic base models if available, fallback to hardcoded
|
||||||
|
const models = getMappableBaseModelsDynamic();
|
||||||
|
return models.filter(model => model !== 'Other');
|
||||||
}
|
}
|
||||||
|
|
||||||
normalizeDownloadSkipBaseModels(value) {
|
normalizeDownloadSkipBaseModels(value) {
|
||||||
@@ -1517,7 +1526,7 @@ export class SettingsManager {
|
|||||||
const row = document.createElement('div');
|
const row = document.createElement('div');
|
||||||
row.className = 'mapping-row';
|
row.className = 'mapping-row';
|
||||||
|
|
||||||
const availableModels = MAPPABLE_BASE_MODELS.filter(model => {
|
const availableModels = getMappableBaseModelsDynamic().filter(model => {
|
||||||
const existingMappings = state.global.settings.base_model_path_mappings || {};
|
const existingMappings = state.global.settings.base_model_path_mappings || {};
|
||||||
return !existingMappings.hasOwnProperty(model) || model === baseModel;
|
return !existingMappings.hasOwnProperty(model) || model === baseModel;
|
||||||
});
|
});
|
||||||
@@ -1619,7 +1628,7 @@ export class SettingsManager {
|
|||||||
const currentValue = select.value;
|
const currentValue = select.value;
|
||||||
|
|
||||||
// Get available models (not already mapped, except current)
|
// Get available models (not already mapped, except current)
|
||||||
const availableModels = MAPPABLE_BASE_MODELS.filter(model =>
|
const availableModels = getMappableBaseModelsDynamic().filter(model =>
|
||||||
!existingMappings.hasOwnProperty(model) || model === currentValue
|
!existingMappings.hasOwnProperty(model) || model === currentValue
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,9 @@ export const BASE_MODELS = {
|
|||||||
SVD: "SVD",
|
SVD: "SVD",
|
||||||
LTXV: "LTXV",
|
LTXV: "LTXV",
|
||||||
LTXV2: "LTXV2",
|
LTXV2: "LTXV2",
|
||||||
|
LTXV_2_3: "LTXV 2.3",
|
||||||
|
COGVIDE_X: "CogVideoX",
|
||||||
|
MOCHI: "Mochi",
|
||||||
WAN_VIDEO: "Wan Video",
|
WAN_VIDEO: "Wan Video",
|
||||||
WAN_VIDEO_1_3B_T2V: "Wan Video 1.3B t2v",
|
WAN_VIDEO_1_3B_T2V: "Wan Video 1.3B t2v",
|
||||||
WAN_VIDEO_14B_T2V: "Wan Video 14B t2v",
|
WAN_VIDEO_14B_T2V: "Wan Video 14B t2v",
|
||||||
@@ -58,7 +61,12 @@ export const BASE_MODELS = {
|
|||||||
WAN_VIDEO_2_2_TI2V_5B: "Wan Video 2.2 TI2V-5B",
|
WAN_VIDEO_2_2_TI2V_5B: "Wan Video 2.2 TI2V-5B",
|
||||||
WAN_VIDEO_2_2_T2V_A14B: "Wan Video 2.2 T2V-A14B",
|
WAN_VIDEO_2_2_T2V_A14B: "Wan Video 2.2 T2V-A14B",
|
||||||
WAN_VIDEO_2_2_I2V_A14B: "Wan Video 2.2 I2V-A14B",
|
WAN_VIDEO_2_2_I2V_A14B: "Wan Video 2.2 I2V-A14B",
|
||||||
|
WAN_VIDEO_2_5_T2V: "Wan Video 2.5 T2V",
|
||||||
|
WAN_VIDEO_2_5_I2V: "Wan Video 2.5 I2V",
|
||||||
HUNYUAN_VIDEO: "Hunyuan Video",
|
HUNYUAN_VIDEO: "Hunyuan Video",
|
||||||
|
// Other models
|
||||||
|
ANIMA: "Anima",
|
||||||
|
PONY_V7: "Pony V7",
|
||||||
// Default
|
// Default
|
||||||
UNKNOWN: "Other"
|
UNKNOWN: "Other"
|
||||||
};
|
};
|
||||||
@@ -151,6 +159,9 @@ export const BASE_MODEL_ABBREVIATIONS = {
|
|||||||
[BASE_MODELS.SVD]: 'SVD',
|
[BASE_MODELS.SVD]: 'SVD',
|
||||||
[BASE_MODELS.LTXV]: 'LTXV',
|
[BASE_MODELS.LTXV]: 'LTXV',
|
||||||
[BASE_MODELS.LTXV2]: 'LTV2',
|
[BASE_MODELS.LTXV2]: 'LTV2',
|
||||||
|
[BASE_MODELS.LTXV_2_3]: 'LTX',
|
||||||
|
[BASE_MODELS.COGVIDE_X]: 'CVX',
|
||||||
|
[BASE_MODELS.MOCHI]: 'MCHI',
|
||||||
[BASE_MODELS.WAN_VIDEO]: 'WAN',
|
[BASE_MODELS.WAN_VIDEO]: 'WAN',
|
||||||
[BASE_MODELS.WAN_VIDEO_1_3B_T2V]: 'WAN',
|
[BASE_MODELS.WAN_VIDEO_1_3B_T2V]: 'WAN',
|
||||||
[BASE_MODELS.WAN_VIDEO_14B_T2V]: 'WAN',
|
[BASE_MODELS.WAN_VIDEO_14B_T2V]: 'WAN',
|
||||||
@@ -159,8 +170,28 @@ export const BASE_MODEL_ABBREVIATIONS = {
|
|||||||
[BASE_MODELS.WAN_VIDEO_2_2_TI2V_5B]: 'WAN',
|
[BASE_MODELS.WAN_VIDEO_2_2_TI2V_5B]: 'WAN',
|
||||||
[BASE_MODELS.WAN_VIDEO_2_2_T2V_A14B]: 'WAN',
|
[BASE_MODELS.WAN_VIDEO_2_2_T2V_A14B]: 'WAN',
|
||||||
[BASE_MODELS.WAN_VIDEO_2_2_I2V_A14B]: 'WAN',
|
[BASE_MODELS.WAN_VIDEO_2_2_I2V_A14B]: 'WAN',
|
||||||
|
[BASE_MODELS.WAN_VIDEO_2_5_T2V]: 'WAN',
|
||||||
|
[BASE_MODELS.WAN_VIDEO_2_5_I2V]: 'WAN',
|
||||||
[BASE_MODELS.HUNYUAN_VIDEO]: 'HYV',
|
[BASE_MODELS.HUNYUAN_VIDEO]: 'HYV',
|
||||||
|
|
||||||
|
// Other diffusion models
|
||||||
|
[BASE_MODELS.AURAFLOW]: 'AF',
|
||||||
|
[BASE_MODELS.CHROMA]: 'CHR',
|
||||||
|
[BASE_MODELS.PIXART_A]: 'PXA',
|
||||||
|
[BASE_MODELS.PIXART_E]: 'PXE',
|
||||||
|
[BASE_MODELS.HUNYUAN_1]: 'HY',
|
||||||
|
[BASE_MODELS.LUMINA]: 'L',
|
||||||
|
[BASE_MODELS.KOLORS]: 'KLR',
|
||||||
|
[BASE_MODELS.NOOBAI]: 'NAI',
|
||||||
|
[BASE_MODELS.ILLUSTRIOUS]: 'IL',
|
||||||
|
[BASE_MODELS.PONY]: 'PONY',
|
||||||
|
[BASE_MODELS.PONY_V7]: 'PNY7',
|
||||||
|
[BASE_MODELS.HIDREAM]: 'HID',
|
||||||
|
[BASE_MODELS.QWEN]: 'QWEN',
|
||||||
|
[BASE_MODELS.ZIMAGE_TURBO]: 'ZIT',
|
||||||
|
[BASE_MODELS.ZIMAGE_BASE]: 'ZIB',
|
||||||
|
[BASE_MODELS.ANIMA]: 'ANI',
|
||||||
|
|
||||||
// Default
|
// Default
|
||||||
[BASE_MODELS.UNKNOWN]: 'OTH'
|
[BASE_MODELS.UNKNOWN]: 'OTH'
|
||||||
};
|
};
|
||||||
@@ -349,18 +380,20 @@ export const BASE_MODEL_CATEGORIES = {
|
|||||||
'Stable Diffusion 3.x': [BASE_MODELS.SD_3, BASE_MODELS.SD_3_5, BASE_MODELS.SD_3_5_MEDIUM, BASE_MODELS.SD_3_5_LARGE, BASE_MODELS.SD_3_5_LARGE_TURBO],
|
'Stable Diffusion 3.x': [BASE_MODELS.SD_3, BASE_MODELS.SD_3_5, BASE_MODELS.SD_3_5_MEDIUM, BASE_MODELS.SD_3_5_LARGE, BASE_MODELS.SD_3_5_LARGE_TURBO],
|
||||||
'SDXL': [BASE_MODELS.SDXL, BASE_MODELS.SDXL_LIGHTNING, BASE_MODELS.SDXL_HYPER],
|
'SDXL': [BASE_MODELS.SDXL, BASE_MODELS.SDXL_LIGHTNING, BASE_MODELS.SDXL_HYPER],
|
||||||
'Video Models': [
|
'Video Models': [
|
||||||
BASE_MODELS.SVD, BASE_MODELS.LTXV, BASE_MODELS.LTXV2, BASE_MODELS.HUNYUAN_VIDEO, BASE_MODELS.WAN_VIDEO,
|
BASE_MODELS.SVD, BASE_MODELS.LTXV, BASE_MODELS.LTXV2, BASE_MODELS.LTXV_2_3,
|
||||||
BASE_MODELS.WAN_VIDEO_1_3B_T2V, BASE_MODELS.WAN_VIDEO_14B_T2V,
|
BASE_MODELS.COGVIDE_X, BASE_MODELS.MOCHI, BASE_MODELS.HUNYUAN_VIDEO,
|
||||||
|
BASE_MODELS.WAN_VIDEO, BASE_MODELS.WAN_VIDEO_1_3B_T2V, BASE_MODELS.WAN_VIDEO_14B_T2V,
|
||||||
BASE_MODELS.WAN_VIDEO_14B_I2V_480P, BASE_MODELS.WAN_VIDEO_14B_I2V_720P,
|
BASE_MODELS.WAN_VIDEO_14B_I2V_480P, BASE_MODELS.WAN_VIDEO_14B_I2V_720P,
|
||||||
BASE_MODELS.WAN_VIDEO_2_2_TI2V_5B, BASE_MODELS.WAN_VIDEO_2_2_T2V_A14B,
|
BASE_MODELS.WAN_VIDEO_2_2_TI2V_5B, BASE_MODELS.WAN_VIDEO_2_2_T2V_A14B,
|
||||||
BASE_MODELS.WAN_VIDEO_2_2_I2V_A14B
|
BASE_MODELS.WAN_VIDEO_2_2_I2V_A14B, BASE_MODELS.WAN_VIDEO_2_5_T2V,
|
||||||
|
BASE_MODELS.WAN_VIDEO_2_5_I2V
|
||||||
],
|
],
|
||||||
'Flux Models': [BASE_MODELS.FLUX_1_D, BASE_MODELS.FLUX_1_S, BASE_MODELS.FLUX_1_KONTEXT, BASE_MODELS.FLUX_1_KREA, BASE_MODELS.FLUX_2_D, BASE_MODELS.FLUX_2_KLEIN_9B, BASE_MODELS.FLUX_2_KLEIN_9B_BASE, BASE_MODELS.FLUX_2_KLEIN_4B, BASE_MODELS.FLUX_2_KLEIN_4B_BASE],
|
'Flux Models': [BASE_MODELS.FLUX_1_D, BASE_MODELS.FLUX_1_S, BASE_MODELS.FLUX_1_KONTEXT, BASE_MODELS.FLUX_1_KREA, BASE_MODELS.FLUX_2_D, BASE_MODELS.FLUX_2_KLEIN_9B, BASE_MODELS.FLUX_2_KLEIN_9B_BASE, BASE_MODELS.FLUX_2_KLEIN_4B, BASE_MODELS.FLUX_2_KLEIN_4B_BASE],
|
||||||
'Other Models': [
|
'Other Models': [
|
||||||
BASE_MODELS.ILLUSTRIOUS, BASE_MODELS.PONY, BASE_MODELS.HIDREAM,
|
BASE_MODELS.ILLUSTRIOUS, BASE_MODELS.PONY, BASE_MODELS.PONY_V7, BASE_MODELS.HIDREAM,
|
||||||
BASE_MODELS.QWEN, BASE_MODELS.AURAFLOW, BASE_MODELS.CHROMA, BASE_MODELS.ZIMAGE_TURBO, BASE_MODELS.ZIMAGE_BASE,
|
BASE_MODELS.QWEN, BASE_MODELS.AURAFLOW, BASE_MODELS.CHROMA, BASE_MODELS.ZIMAGE_TURBO, BASE_MODELS.ZIMAGE_BASE,
|
||||||
BASE_MODELS.PIXART_A, BASE_MODELS.PIXART_E, BASE_MODELS.HUNYUAN_1,
|
BASE_MODELS.PIXART_A, BASE_MODELS.PIXART_E, BASE_MODELS.HUNYUAN_1,
|
||||||
BASE_MODELS.LUMINA, BASE_MODELS.KOLORS, BASE_MODELS.NOOBAI,
|
BASE_MODELS.LUMINA, BASE_MODELS.KOLORS, BASE_MODELS.NOOBAI, BASE_MODELS.ANIMA,
|
||||||
BASE_MODELS.UNKNOWN
|
BASE_MODELS.UNKNOWN
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@@ -378,3 +411,94 @@ export const DEFAULT_PRIORITY_TAG_CONFIG = {
|
|||||||
checkpoint: DEFAULT_PRIORITY_TAG_ENTRIES.join(', '),
|
checkpoint: DEFAULT_PRIORITY_TAG_ENTRIES.join(', '),
|
||||||
embedding: DEFAULT_PRIORITY_TAG_ENTRIES.join(', ')
|
embedding: DEFAULT_PRIORITY_TAG_ENTRIES.join(', ')
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Dynamic Base Model Support
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamic base model cache
|
||||||
|
* Stores models fetched from Civitai API
|
||||||
|
*/
|
||||||
|
let dynamicBaseModels = null;
|
||||||
|
let dynamicBaseModelsTimestamp = null;
|
||||||
|
const CACHE_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set dynamic base models (called after fetching from API)
|
||||||
|
* @param {Array} models - Array of base model names
|
||||||
|
* @param {string} timestamp - ISO timestamp of fetch
|
||||||
|
*/
|
||||||
|
export function setDynamicBaseModels(models, timestamp) {
|
||||||
|
dynamicBaseModels = models;
|
||||||
|
dynamicBaseModelsTimestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get dynamic base models
|
||||||
|
* @returns {Object|null} { models, timestamp } or null if not set
|
||||||
|
*/
|
||||||
|
export function getDynamicBaseModels() {
|
||||||
|
if (!dynamicBaseModels) return null;
|
||||||
|
|
||||||
|
// Check if cache is expired
|
||||||
|
if (dynamicBaseModelsTimestamp) {
|
||||||
|
const age = Date.now() - new Date(dynamicBaseModelsTimestamp).getTime();
|
||||||
|
if (age > CACHE_TTL_MS) {
|
||||||
|
dynamicBaseModels = null;
|
||||||
|
dynamicBaseModelsTimestamp = null;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
models: dynamicBaseModels,
|
||||||
|
timestamp: dynamicBaseModelsTimestamp
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get merged base models (hardcoded + dynamic)
|
||||||
|
* Returns unique sorted list of all available base models
|
||||||
|
* @returns {Array} Sorted array of base model names
|
||||||
|
*/
|
||||||
|
export function getMergedBaseModels() {
|
||||||
|
const hardcoded = Object.values(BASE_MODELS);
|
||||||
|
const dynamic = getDynamicBaseModels();
|
||||||
|
|
||||||
|
if (!dynamic || !dynamic.models) {
|
||||||
|
return hardcoded.sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge and deduplicate
|
||||||
|
const merged = new Set([...hardcoded, ...dynamic.models]);
|
||||||
|
return Array.from(merged).sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get mappable base models (for UI selection)
|
||||||
|
* Excludes 'Other' value
|
||||||
|
* @returns {Array} Sorted array of base model names (excluding 'Other')
|
||||||
|
*/
|
||||||
|
export function getMappableBaseModelsDynamic() {
|
||||||
|
const merged = getMergedBaseModels();
|
||||||
|
return merged.filter(model => model !== 'Other');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear dynamic base models cache
|
||||||
|
*/
|
||||||
|
export function clearDynamicBaseModels() {
|
||||||
|
dynamicBaseModels = null;
|
||||||
|
dynamicBaseModelsTimestamp = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if dynamic base models cache is valid
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function isDynamicBaseModelsCacheValid() {
|
||||||
|
if (!dynamicBaseModels || !dynamicBaseModelsTimestamp) return false;
|
||||||
|
const age = Date.now() - new Date(dynamicBaseModelsTimestamp).getTime();
|
||||||
|
return age <= CACHE_TTL_MS;
|
||||||
|
}
|
||||||
|
|||||||
@@ -69,6 +69,9 @@ describe('AutoComplete widget interactions', () => {
|
|||||||
if (key === 'loramanager.autocomplete_append_comma') {
|
if (key === 'loramanager.autocomplete_append_comma') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (key === 'loramanager.autocomplete_accept_key') {
|
||||||
|
return 'both';
|
||||||
|
}
|
||||||
if (key === 'loramanager.prompt_tag_autocomplete') {
|
if (key === 'loramanager.prompt_tag_autocomplete') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -210,6 +213,157 @@ describe('AutoComplete widget interactions', () => {
|
|||||||
expect(insertSelectionSpy).toHaveBeenCalledWith('example_completion');
|
expect(insertSelectionSpy).toHaveBeenCalledWith('example_completion');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('prefers the latest best match when Tab is pressed before debounced suggestions fully refresh', async () => {
|
||||||
|
caretHelperInstance.getBeforeCursor.mockReturnValue('loop');
|
||||||
|
|
||||||
|
const input = document.createElement('textarea');
|
||||||
|
input.value = 'loop';
|
||||||
|
input.selectionStart = input.value.length;
|
||||||
|
input.focus = vi.fn();
|
||||||
|
input.setSelectionRange = vi.fn();
|
||||||
|
document.body.append(input);
|
||||||
|
|
||||||
|
const { AutoComplete } = await import(AUTOCOMPLETE_MODULE);
|
||||||
|
const autoComplete = new AutoComplete(input,'prompt', { showPreview: false, minChars: 1 });
|
||||||
|
|
||||||
|
autoComplete.searchType = 'custom_words';
|
||||||
|
autoComplete.items = [
|
||||||
|
{ tag_name: 'looking_to_the_side', category: 0, post_count: 1000 },
|
||||||
|
{ tag_name: 'loop', category: 0, post_count: 500 },
|
||||||
|
];
|
||||||
|
autoComplete.currentSearchTerm = 'loo';
|
||||||
|
autoComplete.selectedIndex = 0;
|
||||||
|
autoComplete.isVisible = true;
|
||||||
|
const insertSelectionSpy = vi.spyOn(autoComplete,'insertSelection').mockResolvedValue();
|
||||||
|
|
||||||
|
const tabEvent = new KeyboardEvent('keydown', { key: 'Tab', bubbles: true, cancelable: true });
|
||||||
|
input.dispatchEvent(tabEvent);
|
||||||
|
|
||||||
|
expect(tabEvent.defaultPrevented).toBe(true);
|
||||||
|
expect(autoComplete.selectedIndex).toBe(1);
|
||||||
|
expect(insertSelectionSpy).toHaveBeenCalledWith('loop');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts the first available suggestion with Tab even if delayed auto-selection has not happened yet', async () => {
|
||||||
|
caretHelperInstance.getBeforeCursor.mockReturnValue('loop');
|
||||||
|
|
||||||
|
const input = document.createElement('textarea');
|
||||||
|
input.value = 'loop';
|
||||||
|
input.selectionStart = input.value.length;
|
||||||
|
input.focus = vi.fn();
|
||||||
|
input.setSelectionRange = vi.fn();
|
||||||
|
document.body.append(input);
|
||||||
|
|
||||||
|
const { AutoComplete } = await import(AUTOCOMPLETE_MODULE);
|
||||||
|
const autoComplete = new AutoComplete(input,'custom_words', { showPreview: false });
|
||||||
|
|
||||||
|
autoComplete.items = ['loop'];
|
||||||
|
autoComplete.selectedIndex = -1;
|
||||||
|
autoComplete.isVisible = true;
|
||||||
|
const insertSelectionSpy = vi.spyOn(autoComplete,'insertSelection').mockResolvedValue();
|
||||||
|
|
||||||
|
const tabEvent = new KeyboardEvent('keydown', { key: 'Tab', bubbles: true, cancelable: true });
|
||||||
|
input.dispatchEvent(tabEvent);
|
||||||
|
|
||||||
|
expect(tabEvent.defaultPrevented).toBe(true);
|
||||||
|
expect(autoComplete.selectedIndex).toBe(0);
|
||||||
|
expect(insertSelectionSpy).toHaveBeenCalledWith('loop');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('only accepts with Tab when autocomplete accept key is set to tab_only', async () => {
|
||||||
|
settingGetMock.mockImplementation((key) => {
|
||||||
|
if (key === 'loramanager.autocomplete_append_comma') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (key === 'loramanager.autocomplete_accept_key') {
|
||||||
|
return 'tab_only';
|
||||||
|
}
|
||||||
|
if (key === 'loramanager.prompt_tag_autocomplete') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (key === 'loramanager.tag_space_replacement') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
caretHelperInstance.getBeforeCursor.mockReturnValue('example');
|
||||||
|
|
||||||
|
const input = document.createElement('textarea');
|
||||||
|
input.value = 'example';
|
||||||
|
input.selectionStart = input.value.length;
|
||||||
|
input.focus = vi.fn();
|
||||||
|
input.setSelectionRange = vi.fn();
|
||||||
|
document.body.append(input);
|
||||||
|
|
||||||
|
const { AutoComplete } = await import(AUTOCOMPLETE_MODULE);
|
||||||
|
const autoComplete = new AutoComplete(input,'custom_words', { showPreview: false });
|
||||||
|
|
||||||
|
autoComplete.items = ['example_completion'];
|
||||||
|
autoComplete.selectedIndex = 0;
|
||||||
|
autoComplete.isVisible = true;
|
||||||
|
const insertSelectionSpy = vi.spyOn(autoComplete,'insertSelection').mockResolvedValue();
|
||||||
|
|
||||||
|
const enterEvent = new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, cancelable: true });
|
||||||
|
input.dispatchEvent(enterEvent);
|
||||||
|
|
||||||
|
expect(enterEvent.defaultPrevented).toBe(false);
|
||||||
|
expect(insertSelectionSpy).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
const tabEvent = new KeyboardEvent('keydown', { key: 'Tab', bubbles: true, cancelable: true });
|
||||||
|
input.dispatchEvent(tabEvent);
|
||||||
|
|
||||||
|
expect(tabEvent.defaultPrevented).toBe(true);
|
||||||
|
expect(insertSelectionSpy).toHaveBeenCalledWith('example_completion');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('only accepts with Enter when autocomplete accept key is set to enter_only', async () => {
|
||||||
|
settingGetMock.mockImplementation((key) => {
|
||||||
|
if (key === 'loramanager.autocomplete_append_comma') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (key === 'loramanager.autocomplete_accept_key') {
|
||||||
|
return 'enter_only';
|
||||||
|
}
|
||||||
|
if (key === 'loramanager.prompt_tag_autocomplete') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (key === 'loramanager.tag_space_replacement') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
caretHelperInstance.getBeforeCursor.mockReturnValue('example');
|
||||||
|
|
||||||
|
const input = document.createElement('textarea');
|
||||||
|
input.value = 'example';
|
||||||
|
input.selectionStart = input.value.length;
|
||||||
|
input.focus = vi.fn();
|
||||||
|
input.setSelectionRange = vi.fn();
|
||||||
|
document.body.append(input);
|
||||||
|
|
||||||
|
const { AutoComplete } = await import(AUTOCOMPLETE_MODULE);
|
||||||
|
const autoComplete = new AutoComplete(input,'custom_words', { showPreview: false });
|
||||||
|
|
||||||
|
autoComplete.items = ['example_completion'];
|
||||||
|
autoComplete.selectedIndex = 0;
|
||||||
|
autoComplete.isVisible = true;
|
||||||
|
const insertSelectionSpy = vi.spyOn(autoComplete,'insertSelection').mockResolvedValue();
|
||||||
|
|
||||||
|
const tabEvent = new KeyboardEvent('keydown', { key: 'Tab', bubbles: true, cancelable: true });
|
||||||
|
input.dispatchEvent(tabEvent);
|
||||||
|
|
||||||
|
expect(tabEvent.defaultPrevented).toBe(false);
|
||||||
|
expect(insertSelectionSpy).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
const enterEvent = new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, cancelable: true });
|
||||||
|
input.dispatchEvent(enterEvent);
|
||||||
|
|
||||||
|
expect(enterEvent.defaultPrevented).toBe(true);
|
||||||
|
expect(insertSelectionSpy).toHaveBeenCalledWith('example_completion');
|
||||||
|
});
|
||||||
|
|
||||||
it('does not intercept Tab when the dropdown is not visible', async () => {
|
it('does not intercept Tab when the dropdown is not visible', async () => {
|
||||||
caretHelperInstance.getBeforeCursor.mockReturnValue('example');
|
caretHelperInstance.getBeforeCursor.mockReturnValue('example');
|
||||||
|
|
||||||
@@ -947,6 +1101,44 @@ describe('AutoComplete widget interactions', () => {
|
|||||||
expect(input.value).toBe('1girl ');
|
expect(input.value).toBe('1girl ');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('treats a newline as a hard boundary after dismissing autocomplete', async () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
|
||||||
|
fetchApiMock.mockResolvedValue({
|
||||||
|
json: () => Promise.resolve({ success: true, words: [{ tag_name: '1girl', category: 4, post_count: 500000 }] }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const input = document.createElement('textarea');
|
||||||
|
input.value = '1gi\n';
|
||||||
|
input.selectionStart = input.value.length;
|
||||||
|
document.body.append(input);
|
||||||
|
|
||||||
|
const { AutoComplete } = await import(AUTOCOMPLETE_MODULE);
|
||||||
|
const autoComplete = new AutoComplete(input,'prompt', {
|
||||||
|
debounceDelay: 0,
|
||||||
|
showPreview: false,
|
||||||
|
minChars: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
caretHelperInstance.getBeforeCursor.mockReturnValue('1gi');
|
||||||
|
autoComplete.handleInput('1gi');
|
||||||
|
await vi.runAllTimersAsync();
|
||||||
|
await Promise.resolve();
|
||||||
|
expect(fetchApiMock).toHaveBeenCalled();
|
||||||
|
|
||||||
|
fetchApiMock.mockClear();
|
||||||
|
autoComplete.hide();
|
||||||
|
|
||||||
|
caretHelperInstance.getBeforeCursor.mockReturnValue('1gi\n');
|
||||||
|
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||||
|
await vi.runAllTimersAsync();
|
||||||
|
await Promise.resolve();
|
||||||
|
|
||||||
|
expect(autoComplete.getSearchTerm(input.value)).toBe('');
|
||||||
|
expect(fetchApiMock).not.toHaveBeenCalled();
|
||||||
|
expect(autoComplete.isVisible).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it('omits the trailing comma for LoRA insertions when the setting is disabled', async () => {
|
it('omits the trailing comma for LoRA insertions when the setting is disabled', async () => {
|
||||||
settingGetMock.mockImplementation((key) => {
|
settingGetMock.mockImplementation((key) => {
|
||||||
if (key === 'loramanager.autocomplete_append_comma') {
|
if (key === 'loramanager.autocomplete_append_comma') {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ vi.mock('../../../static/js/utils/constants.js', () => ({
|
|||||||
checkpoint: 'base, guide',
|
checkpoint: 'base, guide',
|
||||||
embedding: 'hint',
|
embedding: 'hint',
|
||||||
},
|
},
|
||||||
|
getMappableBaseModelsDynamic: () => ['Flux.1 D', 'Pony', 'SDXL 1.0', 'Other'],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../../../static/js/utils/i18nHelpers.js', () => ({
|
vi.mock('../../../static/js/utils/i18nHelpers.js', () => ({
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ vi.mock('../../../static/js/utils/constants.js', () => ({
|
|||||||
checkpoint: 'base, guide',
|
checkpoint: 'base, guide',
|
||||||
embedding: 'hint',
|
embedding: 'hint',
|
||||||
},
|
},
|
||||||
|
getMappableBaseModelsDynamic: () => [],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../../../static/js/utils/i18nHelpers.js', () => ({
|
vi.mock('../../../static/js/utils/i18nHelpers.js', () => ({
|
||||||
|
|||||||
133
tests/services/test_civitai_base_model_service.py
Normal file
133
tests/services/test_civitai_base_model_service.py
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
"""Tests for CivitaiBaseModelService."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from py.services.civitai_base_model_service import CivitaiBaseModelService
|
||||||
|
|
||||||
|
|
||||||
|
class TestCivitaiBaseModelService:
|
||||||
|
"""Test suite for CivitaiBaseModelService."""
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def setup_service(self):
|
||||||
|
"""Create a fresh service instance for each test."""
|
||||||
|
self.service = CivitaiBaseModelService()
|
||||||
|
# Reset cache
|
||||||
|
self.service._cache = None
|
||||||
|
self.service._cache_timestamp = None
|
||||||
|
yield
|
||||||
|
|
||||||
|
def test_generate_abbreviation_known_models(self):
|
||||||
|
"""Test abbreviation generation for known models."""
|
||||||
|
test_cases = [
|
||||||
|
("SD 1.5", "SD1"),
|
||||||
|
("SDXL 1.0", "XL"),
|
||||||
|
("Flux.1 D", "F1D"),
|
||||||
|
("Wan Video 2.5 T2V", "WAN"),
|
||||||
|
("Pony V7", "PNY7"),
|
||||||
|
("CogVideoX", "CVX"),
|
||||||
|
("Mochi", "MCHI"),
|
||||||
|
("Anima", "ANI"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for model_name, expected in test_cases:
|
||||||
|
result = self.service.generate_abbreviation(model_name)
|
||||||
|
assert result == expected, (
|
||||||
|
f"Failed for {model_name}: got {result}, expected {expected}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_generate_abbreviation_unknown_models(self):
|
||||||
|
"""Test abbreviation generation for unknown models."""
|
||||||
|
result = self.service.generate_abbreviation("New Model 2.0")
|
||||||
|
assert len(result) <= 4
|
||||||
|
assert result.isupper()
|
||||||
|
|
||||||
|
def test_generate_abbreviation_edge_cases(self):
|
||||||
|
"""Test abbreviation generation edge cases."""
|
||||||
|
assert self.service.generate_abbreviation("") == "OTH"
|
||||||
|
assert self.service.generate_abbreviation(None) == "OTH"
|
||||||
|
|
||||||
|
def test_cache_status_no_cache(self):
|
||||||
|
"""Test cache status when no cache exists."""
|
||||||
|
status = self.service.get_cache_status()
|
||||||
|
|
||||||
|
assert status["has_cache"] is False
|
||||||
|
assert status["last_updated"] is None
|
||||||
|
assert status["is_expired"] is True
|
||||||
|
assert status["age_seconds"] is None
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_base_models_fallback(self):
|
||||||
|
"""Test that fallback to hardcoded models works."""
|
||||||
|
with patch.object(self.service, "_fetch_from_civitai", return_value=None):
|
||||||
|
result = await self.service.get_base_models()
|
||||||
|
|
||||||
|
assert result["source"] == "fallback"
|
||||||
|
assert len(result["models"]) > 0
|
||||||
|
assert result["hardcoded_count"] > 0
|
||||||
|
assert result["remote_count"] == 0
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_base_models_from_api(self):
|
||||||
|
"""Test fetching models from API."""
|
||||||
|
mock_models = {"SD 1.5", "SDXL 1.0", "New Model"}
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
self.service, "_fetch_from_civitai", return_value=mock_models
|
||||||
|
):
|
||||||
|
result = await self.service.get_base_models()
|
||||||
|
|
||||||
|
assert result["source"] == "api"
|
||||||
|
assert result["remote_count"] == 3
|
||||||
|
assert "New Model" in result["models"]
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_get_base_models_uses_cache(self):
|
||||||
|
"""Test that cached data is used when available and not expired."""
|
||||||
|
# First call - populate cache
|
||||||
|
mock_models = {"SD 1.5", "SDXL 1.0"}
|
||||||
|
with patch.object(
|
||||||
|
self.service, "_fetch_from_civitai", return_value=mock_models
|
||||||
|
):
|
||||||
|
await self.service.get_base_models()
|
||||||
|
|
||||||
|
# Second call - should use cache
|
||||||
|
with patch.object(self.service, "_fetch_from_civitai") as mock_fetch:
|
||||||
|
result = await self.service.get_base_models()
|
||||||
|
mock_fetch.assert_not_called()
|
||||||
|
|
||||||
|
assert result["source"] == "cache"
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_refresh_cache(self):
|
||||||
|
"""Test force refresh clears cache and fetches fresh data."""
|
||||||
|
# Populate cache
|
||||||
|
mock_models = {"SD 1.5"}
|
||||||
|
with patch.object(
|
||||||
|
self.service, "_fetch_from_civitai", return_value=mock_models
|
||||||
|
):
|
||||||
|
await self.service.get_base_models()
|
||||||
|
|
||||||
|
# Force refresh with different data
|
||||||
|
new_models = {"SD 1.5", "SDXL 1.0", "New Model"}
|
||||||
|
with patch.object(self.service, "_fetch_from_civitai", return_value=new_models):
|
||||||
|
result = await self.service.refresh_cache()
|
||||||
|
|
||||||
|
assert result["source"] == "api"
|
||||||
|
assert result["remote_count"] == 3
|
||||||
|
|
||||||
|
def test_get_model_categories(self):
|
||||||
|
"""Test model categories are returned."""
|
||||||
|
categories = self.service.get_model_categories()
|
||||||
|
|
||||||
|
assert "Stable Diffusion 1.x" in categories
|
||||||
|
assert "Video Models" in categories
|
||||||
|
assert "Flux Models" in categories
|
||||||
|
assert "Other Models" in categories
|
||||||
|
|
||||||
|
# Check that video models include new additions
|
||||||
|
video_models = categories["Video Models"]
|
||||||
|
assert "CogVideoX" in video_models
|
||||||
|
assert "Mochi" in video_models
|
||||||
|
assert "Wan Video 2.5 T2V" in video_models
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="lora-cycler-widget">
|
<div class="lora-cycler-widget" @wheel="onWheel">
|
||||||
<LoraCyclerSettingsView
|
<LoraCyclerSettingsView
|
||||||
:current-index="state.currentIndex.value"
|
:current-index="state.currentIndex.value"
|
||||||
:total-count="displayTotalCount"
|
:total-count="displayTotalCount"
|
||||||
@@ -257,6 +257,53 @@ const handleResetIndex = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle mouse wheel events on the widget.
|
||||||
|
* Forwards the event to the ComfyUI canvas for zooming when appropriate.
|
||||||
|
*/
|
||||||
|
const onWheel = (event: WheelEvent) => {
|
||||||
|
// Check if the event originated from a slider component
|
||||||
|
// Sliders have data-capture-wheel="true" attribute
|
||||||
|
const target = event.target as HTMLElement
|
||||||
|
if (target?.closest('[data-capture-wheel="true"]')) {
|
||||||
|
// Event is from a slider, slider already handled it
|
||||||
|
// Just stop propagation to prevent canvas zoom
|
||||||
|
event.stopPropagation()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access ComfyUI app from global window
|
||||||
|
const app = (window as any).app
|
||||||
|
if (!app || !app.canvas || typeof app.canvas.processMouseWheel !== 'function') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const deltaX = event.deltaX
|
||||||
|
const deltaY = event.deltaY
|
||||||
|
const isHorizontal = Math.abs(deltaX) > Math.abs(deltaY)
|
||||||
|
|
||||||
|
// 1. Handle pinch-to-zoom (ctrlKey is true for pinch-to-zoom on most browsers)
|
||||||
|
if (event.ctrlKey) {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
app.canvas.processMouseWheel(event)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Horizontal scroll: pass to canvas (widgets usually don't scroll horizontally)
|
||||||
|
if (isHorizontal) {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
app.canvas.processMouseWheel(event)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Vertical scrolling: forward to canvas
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
app.canvas.processMouseWheel(event)
|
||||||
|
}
|
||||||
|
|
||||||
// Check for pool config changes
|
// Check for pool config changes
|
||||||
const checkPoolConfigChanges = async () => {
|
const checkPoolConfigChanges = async () => {
|
||||||
if (!isMounted.value) return
|
if (!isMounted.value) return
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="lora-pool-widget">
|
<div class="lora-pool-widget" @wheel="onWheel">
|
||||||
<!-- Summary View -->
|
<!-- Summary View -->
|
||||||
<LoraPoolSummaryView
|
<LoraPoolSummaryView
|
||||||
:selected-base-models="state.selectedBaseModels.value"
|
:selected-base-models="state.selectedBaseModels.value"
|
||||||
@@ -99,6 +99,53 @@ const openModal = (modal: ModalType) => {
|
|||||||
modalState.openModal(modal)
|
modalState.openModal(modal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle mouse wheel events on the widget.
|
||||||
|
* Forwards the event to the ComfyUI canvas for zooming when appropriate.
|
||||||
|
*/
|
||||||
|
const onWheel = (event: WheelEvent) => {
|
||||||
|
// Check if the event originated from a slider component
|
||||||
|
// Sliders have data-capture-wheel="true" attribute
|
||||||
|
const target = event.target as HTMLElement
|
||||||
|
if (target?.closest('[data-capture-wheel="true"]')) {
|
||||||
|
// Event is from a slider, slider already handled it
|
||||||
|
// Just stop propagation to prevent canvas zoom
|
||||||
|
event.stopPropagation()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access ComfyUI app from global window
|
||||||
|
const app = (window as any).app
|
||||||
|
if (!app || !app.canvas || typeof app.canvas.processMouseWheel !== 'function') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const deltaX = event.deltaX
|
||||||
|
const deltaY = event.deltaY
|
||||||
|
const isHorizontal = Math.abs(deltaX) > Math.abs(deltaY)
|
||||||
|
|
||||||
|
// 1. Handle pinch-to-zoom (ctrlKey is true for pinch-to-zoom on most browsers)
|
||||||
|
if (event.ctrlKey) {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
app.canvas.processMouseWheel(event)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Horizontal scroll: pass to canvas (widgets usually don't scroll horizontally)
|
||||||
|
if (isHorizontal) {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
app.canvas.processMouseWheel(event)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Vertical scrolling: forward to canvas
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
app.canvas.processMouseWheel(event)
|
||||||
|
}
|
||||||
|
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// Setup callback for external value updates (e.g., workflow load, undo/redo)
|
// Setup callback for external value updates (e.g., workflow load, undo/redo)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="lora-randomizer-widget">
|
<div class="lora-randomizer-widget" @wheel="onWheel">
|
||||||
<LoraRandomizerSettingsView
|
<LoraRandomizerSettingsView
|
||||||
:count-mode="state.countMode.value"
|
:count-mode="state.countMode.value"
|
||||||
:count-fixed="state.countFixed.value"
|
:count-fixed="state.countFixed.value"
|
||||||
@@ -154,6 +154,53 @@ const handleReuseLast = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle mouse wheel events on the widget.
|
||||||
|
* Forwards the event to the ComfyUI canvas for zooming when appropriate.
|
||||||
|
*/
|
||||||
|
const onWheel = (event: WheelEvent) => {
|
||||||
|
// Check if the event originated from a slider component
|
||||||
|
// Sliders have data-capture-wheel="true" attribute
|
||||||
|
const target = event.target as HTMLElement
|
||||||
|
if (target?.closest('[data-capture-wheel="true"]')) {
|
||||||
|
// Event is from a slider, slider already handled it
|
||||||
|
// Just stop propagation to prevent canvas zoom
|
||||||
|
event.stopPropagation()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access ComfyUI app from global window
|
||||||
|
const app = (window as any).app
|
||||||
|
if (!app || !app.canvas || typeof app.canvas.processMouseWheel !== 'function') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const deltaX = event.deltaX
|
||||||
|
const deltaY = event.deltaY
|
||||||
|
const isHorizontal = Math.abs(deltaX) > Math.abs(deltaY)
|
||||||
|
|
||||||
|
// 1. Handle pinch-to-zoom (ctrlKey is true for pinch-to-zoom on most browsers)
|
||||||
|
if (event.ctrlKey) {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
app.canvas.processMouseWheel(event)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Horizontal scroll: pass to canvas (widgets usually don't scroll horizontally)
|
||||||
|
if (isHorizontal) {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
app.canvas.processMouseWheel(event)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Vertical scrolling: forward to canvas
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
app.canvas.processMouseWheel(event)
|
||||||
|
}
|
||||||
|
|
||||||
// Watch for changes to the loras widget to track current loras
|
// Watch for changes to the loras widget to track current loras
|
||||||
watch(() => props.node.widgets?.find((w: any) => w.name === 'loras')?.value, (newVal) => {
|
watch(() => props.node.widgets?.find((w: any) => w.name === 'loras')?.value, (newVal) => {
|
||||||
// Only update after component is mounted
|
// Only update after component is mounted
|
||||||
|
|||||||
@@ -280,8 +280,7 @@ const onWheel = (event: WheelEvent) => {
|
|||||||
if (event.clientX < rootRect.left || event.clientX > rootRect.right ||
|
if (event.clientX < rootRect.left || event.clientX > rootRect.right ||
|
||||||
event.clientY < rootRect.top || event.clientY > rootRect.bottom) return
|
event.clientY < rootRect.top || event.clientY > rootRect.bottom) return
|
||||||
|
|
||||||
event.preventDefault()
|
// Adjust slider values when wheeling over the slider area
|
||||||
|
|
||||||
const delta = event.deltaY > 0 ? -1 : 1
|
const delta = event.deltaY > 0 ? -1 : 1
|
||||||
const relativeX = event.clientX - rect.left
|
const relativeX = event.clientX - rect.left
|
||||||
const rangeWidth = rect.width
|
const rangeWidth = rect.width
|
||||||
@@ -320,6 +319,9 @@ const onWheel = (event: WheelEvent) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop propagation to prevent canvas zoom
|
||||||
|
event.stopPropagation()
|
||||||
}
|
}
|
||||||
|
|
||||||
const stopDrag = (event?: PointerEvent) => {
|
const stopDrag = (event?: PointerEvent) => {
|
||||||
|
|||||||
@@ -131,11 +131,13 @@ const onWheel = (event: WheelEvent) => {
|
|||||||
if (event.clientX < rootRect.left || event.clientX > rootRect.right ||
|
if (event.clientX < rootRect.left || event.clientX > rootRect.right ||
|
||||||
event.clientY < rootRect.top || event.clientY > rootRect.bottom) return
|
event.clientY < rootRect.top || event.clientY > rootRect.bottom) return
|
||||||
|
|
||||||
event.preventDefault()
|
// Adjust slider value when wheeling over the slider area
|
||||||
|
|
||||||
const delta = event.deltaY > 0 ? -1 : 1
|
const delta = event.deltaY > 0 ? -1 : 1
|
||||||
const newValue = snapToStep(props.value + delta * props.step)
|
const newValue = snapToStep(props.value + delta * props.step)
|
||||||
emit('update:value', newValue)
|
emit('update:value', newValue)
|
||||||
|
|
||||||
|
// Stop propagation to prevent canvas zoom
|
||||||
|
event.stopPropagation()
|
||||||
}
|
}
|
||||||
|
|
||||||
const stopDrag = (event?: PointerEvent) => {
|
const stopDrag = (event?: PointerEvent) => {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { app } from "../../scripts/app.js";
|
|||||||
import { TextAreaCaretHelper } from "./textarea_caret_helper.js";
|
import { TextAreaCaretHelper } from "./textarea_caret_helper.js";
|
||||||
import {
|
import {
|
||||||
getAutocompleteAppendCommaPreference,
|
getAutocompleteAppendCommaPreference,
|
||||||
|
getAutocompleteAcceptKeyPreference,
|
||||||
getPromptTagAutocompletePreference,
|
getPromptTagAutocompletePreference,
|
||||||
getTagSpaceReplacementPreference,
|
getTagSpaceReplacementPreference,
|
||||||
} from "./settings.js";
|
} from "./settings.js";
|
||||||
@@ -121,6 +122,24 @@ function formatAutocompleteInsertion(text = '') {
|
|||||||
return getAutocompleteAppendCommaPreference() ? `${trimmed},` : `${trimmed} `;
|
return getAutocompleteAppendCommaPreference() ? `${trimmed},` : `${trimmed} `;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function shouldAcceptAutocompleteKey(key) {
|
||||||
|
const mode = getAutocompleteAcceptKeyPreference();
|
||||||
|
|
||||||
|
if (mode === 'tab_only') {
|
||||||
|
return key === 'Tab';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode === 'enter_only') {
|
||||||
|
return key === 'Enter';
|
||||||
|
}
|
||||||
|
|
||||||
|
return key === 'Tab' || key === 'Enter';
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeAutocompleteMatchText(text = '') {
|
||||||
|
return text.toLowerCase().replace(/[-_\s']/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
const AUTOCOMPLETE_METADATA_VERSION = 1;
|
const AUTOCOMPLETE_METADATA_VERSION = 1;
|
||||||
|
|
||||||
function createAutocompleteMetadataBase(textWidgetName = 'text') {
|
function createAutocompleteMetadataBase(textWidgetName = 'text') {
|
||||||
@@ -676,7 +695,8 @@ class AutoComplete {
|
|||||||
_getHardBoundaryStart(beforeCursor = '') {
|
_getHardBoundaryStart(beforeCursor = '') {
|
||||||
const lastComma = beforeCursor.lastIndexOf(',');
|
const lastComma = beforeCursor.lastIndexOf(',');
|
||||||
const lastAngle = beforeCursor.lastIndexOf('>');
|
const lastAngle = beforeCursor.lastIndexOf('>');
|
||||||
return Math.max(lastComma, lastAngle) + 1;
|
const lastNewline = Math.max(beforeCursor.lastIndexOf('\n'), beforeCursor.lastIndexOf('\r'));
|
||||||
|
return Math.max(lastComma, lastAngle, lastNewline) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getMetadataWidget() {
|
_getMetadataWidget() {
|
||||||
@@ -872,6 +892,92 @@ class AutoComplete {
|
|||||||
return { matched: false, isExactMatch: false };
|
return { matched: false, isExactMatch: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getLiveSearchTermForAcceptance() {
|
||||||
|
const rawSearchTerm = this.getSearchTerm(this.inputElement.value);
|
||||||
|
|
||||||
|
if (this.modelType === 'embeddings') {
|
||||||
|
const match = rawSearchTerm.match(/^emb:(.*)$/i);
|
||||||
|
return (match?.[1] || '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.modelType === 'prompt') {
|
||||||
|
const embeddingMatch = rawSearchTerm.match(/^emb:(.*)$/i);
|
||||||
|
if (embeddingMatch) {
|
||||||
|
return (embeddingMatch[1] || '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const commandResult = this._parseCommandInput(rawSearchTerm);
|
||||||
|
return commandResult.searchTerm ?? rawSearchTerm;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawSearchTerm;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getPreferredSelectedIndex(searchTerm = '') {
|
||||||
|
if (!this.items?.length) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.showingCommands) {
|
||||||
|
if (this.selectedIndex >= 0 && this.selectedIndex < this.items.length) {
|
||||||
|
return this.selectedIndex;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const trimmedSearchTerm = searchTerm.trim();
|
||||||
|
if (!trimmedSearchTerm) {
|
||||||
|
if (this.selectedIndex >= 0 && this.selectedIndex < this.items.length) {
|
||||||
|
return this.selectedIndex;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchLower = trimmedSearchTerm.toLowerCase();
|
||||||
|
const normalizedSearch = normalizeAutocompleteMatchText(trimmedSearchTerm);
|
||||||
|
let bestIndex = -1;
|
||||||
|
let bestScore = -Infinity;
|
||||||
|
|
||||||
|
this.items.forEach((item, index) => {
|
||||||
|
const displayText = this._getDisplayText(item);
|
||||||
|
const textLower = displayText.toLowerCase();
|
||||||
|
const normalizedText = normalizeAutocompleteMatchText(displayText);
|
||||||
|
let score = -1;
|
||||||
|
|
||||||
|
if (textLower === searchLower) {
|
||||||
|
score = 5000;
|
||||||
|
} else if (normalizedText === normalizedSearch) {
|
||||||
|
score = 4500;
|
||||||
|
} else if (textLower.startsWith(searchLower)) {
|
||||||
|
score = 4000;
|
||||||
|
} else if (normalizedText.startsWith(normalizedSearch)) {
|
||||||
|
score = 3500;
|
||||||
|
} else if (textLower.includes(searchLower)) {
|
||||||
|
score = 3000;
|
||||||
|
} else if (normalizedText.includes(normalizedSearch)) {
|
||||||
|
score = 2500;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (score > -1) {
|
||||||
|
score -= index;
|
||||||
|
if (score > bestScore) {
|
||||||
|
bestScore = score;
|
||||||
|
bestIndex = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (bestIndex !== -1) {
|
||||||
|
return bestIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.selectedIndex >= 0 && this.selectedIndex < this.items.length) {
|
||||||
|
return this.selectedIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
async search(term = '', endpoint = null) {
|
async search(term = '', endpoint = null) {
|
||||||
try {
|
try {
|
||||||
this.currentSearchTerm = term;
|
this.currentSearchTerm = term;
|
||||||
@@ -1134,9 +1240,9 @@ class AutoComplete {
|
|||||||
lastChild.style.borderBottom = 'none';
|
lastChild.style.borderBottom = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-select first item
|
// Auto-select immediately so accept keys remain stable.
|
||||||
if (this.items.length > 0) {
|
if (this.items.length > 0) {
|
||||||
setTimeout(() => this.selectItem(0), 100);
|
this.selectItem(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update virtual scroll height for virtual scrolling mode
|
// Update virtual scroll height for virtual scrolling mode
|
||||||
@@ -1299,11 +1405,10 @@ class AutoComplete {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-select the first item with a small delay
|
// Auto-select immediately so accept keys do not fall through
|
||||||
|
// to native focus traversal while the dropdown is visible.
|
||||||
if (this.items.length > 0) {
|
if (this.items.length > 0) {
|
||||||
setTimeout(() => {
|
this.selectItem(0);
|
||||||
this.selectItem(0);
|
|
||||||
}, 100);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1988,8 +2093,20 @@ class AutoComplete {
|
|||||||
|
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
case 'Tab':
|
case 'Tab':
|
||||||
e.preventDefault();
|
if (!shouldAcceptAutocompleteKey(e.key)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const liveSearchTerm = this._getLiveSearchTermForAcceptance();
|
||||||
|
const preferredIndex = this._getPreferredSelectedIndex(liveSearchTerm);
|
||||||
|
if (preferredIndex !== -1 && preferredIndex !== this.selectedIndex) {
|
||||||
|
this.selectItem(preferredIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.selectedIndex >= 0 && this.selectedIndex < this.items.length) {
|
if (this.selectedIndex >= 0 && this.selectedIndex < this.items.length) {
|
||||||
|
e.preventDefault();
|
||||||
if (this.showingCommands) {
|
if (this.showingCommands) {
|
||||||
// Insert command
|
// Insert command
|
||||||
this._insertCommand(this.items[this.selectedIndex].command);
|
this._insertCommand(this.items[this.selectedIndex].command);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
EMPTY_CONTAINER_HEIGHT
|
EMPTY_CONTAINER_HEIGHT
|
||||||
} from "./loras_widget_utils.js";
|
} from "./loras_widget_utils.js";
|
||||||
import { initDrag, createContextMenu, initHeaderDrag, initReorderDrag, handleKeyboardNavigation } from "./loras_widget_events.js";
|
import { initDrag, createContextMenu, initHeaderDrag, initReorderDrag, handleKeyboardNavigation } from "./loras_widget_events.js";
|
||||||
import { forwardMiddleMouseToCanvas } from "./utils.js";
|
import { forwardMiddleMouseToCanvas, forwardWheelToCanvas } from "./utils.js";
|
||||||
import { PreviewTooltip } from "./preview_tooltip.js";
|
import { PreviewTooltip } from "./preview_tooltip.js";
|
||||||
import { ensureLmStyles } from "./lm_styles_loader.js";
|
import { ensureLmStyles } from "./lm_styles_loader.js";
|
||||||
import { getStrengthStepPreference } from "./settings.js";
|
import { getStrengthStepPreference } from "./settings.js";
|
||||||
@@ -24,6 +24,7 @@ export function addLorasWidget(node, name, opts, callback) {
|
|||||||
container.className = "lm-loras-container";
|
container.className = "lm-loras-container";
|
||||||
|
|
||||||
forwardMiddleMouseToCanvas(container);
|
forwardMiddleMouseToCanvas(container);
|
||||||
|
forwardWheelToCanvas(container);
|
||||||
|
|
||||||
// Set initial height using CSS variables approach
|
// Set initial height using CSS variables approach
|
||||||
const defaultHeight = 200;
|
const defaultHeight = 200;
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ const PROMPT_TAG_AUTOCOMPLETE_DEFAULT = true;
|
|||||||
const AUTOCOMPLETE_APPEND_COMMA_SETTING_ID = "loramanager.autocomplete_append_comma";
|
const AUTOCOMPLETE_APPEND_COMMA_SETTING_ID = "loramanager.autocomplete_append_comma";
|
||||||
const AUTOCOMPLETE_APPEND_COMMA_DEFAULT = true;
|
const AUTOCOMPLETE_APPEND_COMMA_DEFAULT = true;
|
||||||
|
|
||||||
|
const AUTOCOMPLETE_ACCEPT_KEY_SETTING_ID = "loramanager.autocomplete_accept_key";
|
||||||
|
const AUTOCOMPLETE_ACCEPT_KEY_DEFAULT = "both";
|
||||||
|
const AUTOCOMPLETE_ACCEPT_KEY_OPTION_BOTH = "Tab or Enter";
|
||||||
|
const AUTOCOMPLETE_ACCEPT_KEY_OPTION_TAB_ONLY = "Tab only";
|
||||||
|
const AUTOCOMPLETE_ACCEPT_KEY_OPTION_ENTER_ONLY = "Enter only";
|
||||||
|
|
||||||
const TAG_SPACE_REPLACEMENT_SETTING_ID = "loramanager.tag_space_replacement";
|
const TAG_SPACE_REPLACEMENT_SETTING_ID = "loramanager.tag_space_replacement";
|
||||||
const TAG_SPACE_REPLACEMENT_DEFAULT = false;
|
const TAG_SPACE_REPLACEMENT_DEFAULT = false;
|
||||||
|
|
||||||
@@ -186,6 +192,41 @@ const getAutocompleteAppendCommaPreference = (() => {
|
|||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
const getAutocompleteAcceptKeyPreference = (() => {
|
||||||
|
let settingsUnavailableLogged = false;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const settingManager = app?.extensionManager?.setting;
|
||||||
|
if (!settingManager || typeof settingManager.get !== "function") {
|
||||||
|
if (!settingsUnavailableLogged) {
|
||||||
|
console.warn("LoRA Manager: settings API unavailable, using default autocomplete accept key setting.");
|
||||||
|
settingsUnavailableLogged = true;
|
||||||
|
}
|
||||||
|
return AUTOCOMPLETE_ACCEPT_KEY_DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const value = settingManager.get(AUTOCOMPLETE_ACCEPT_KEY_SETTING_ID);
|
||||||
|
if (value === AUTOCOMPLETE_ACCEPT_KEY_OPTION_TAB_ONLY) {
|
||||||
|
return "tab_only";
|
||||||
|
}
|
||||||
|
if (value === AUTOCOMPLETE_ACCEPT_KEY_OPTION_ENTER_ONLY) {
|
||||||
|
return "enter_only";
|
||||||
|
}
|
||||||
|
if (value === AUTOCOMPLETE_ACCEPT_KEY_OPTION_BOTH || value == null) {
|
||||||
|
return AUTOCOMPLETE_ACCEPT_KEY_DEFAULT;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
} catch (error) {
|
||||||
|
if (!settingsUnavailableLogged) {
|
||||||
|
console.warn("LoRA Manager: unable to read autocomplete accept key setting, using default.", error);
|
||||||
|
settingsUnavailableLogged = true;
|
||||||
|
}
|
||||||
|
return AUTOCOMPLETE_ACCEPT_KEY_DEFAULT;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
const getTagSpaceReplacementPreference = (() => {
|
const getTagSpaceReplacementPreference = (() => {
|
||||||
let settingsUnavailableLogged = false;
|
let settingsUnavailableLogged = false;
|
||||||
|
|
||||||
@@ -332,7 +373,20 @@ app.registerExtension({
|
|||||||
type: "boolean",
|
type: "boolean",
|
||||||
defaultValue: AUTOCOMPLETE_APPEND_COMMA_DEFAULT,
|
defaultValue: AUTOCOMPLETE_APPEND_COMMA_DEFAULT,
|
||||||
tooltip: "When enabled, accepted autocomplete suggestions append ', ' to the inserted text.",
|
tooltip: "When enabled, accepted autocomplete suggestions append ', ' to the inserted text.",
|
||||||
category: ["LoRA Manager", "Autocomplete", "Behavior"],
|
category: ["LoRA Manager", "Autocomplete", "Append comma"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: AUTOCOMPLETE_ACCEPT_KEY_SETTING_ID,
|
||||||
|
name: "Autocomplete accept key",
|
||||||
|
type: "combo",
|
||||||
|
options: [
|
||||||
|
AUTOCOMPLETE_ACCEPT_KEY_OPTION_BOTH,
|
||||||
|
AUTOCOMPLETE_ACCEPT_KEY_OPTION_TAB_ONLY,
|
||||||
|
AUTOCOMPLETE_ACCEPT_KEY_OPTION_ENTER_ONLY,
|
||||||
|
],
|
||||||
|
defaultValue: AUTOCOMPLETE_ACCEPT_KEY_OPTION_BOTH,
|
||||||
|
tooltip: "Choose which key accepts the selected autocomplete suggestion. Keys not selected here keep their normal textarea behavior.",
|
||||||
|
category: ["LoRA Manager", "Autocomplete", "Accept key"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: TAG_SPACE_REPLACEMENT_SETTING_ID,
|
id: TAG_SPACE_REPLACEMENT_SETTING_ID,
|
||||||
@@ -451,6 +505,7 @@ export {
|
|||||||
getWheelSensitivity,
|
getWheelSensitivity,
|
||||||
getAutoPathCorrectionPreference,
|
getAutoPathCorrectionPreference,
|
||||||
getAutocompleteAppendCommaPreference,
|
getAutocompleteAppendCommaPreference,
|
||||||
|
getAutocompleteAcceptKeyPreference,
|
||||||
getPromptTagAutocompletePreference,
|
getPromptTagAutocompletePreference,
|
||||||
getTagSpaceReplacementPreference,
|
getTagSpaceReplacementPreference,
|
||||||
getUsageStatisticsPreference,
|
getUsageStatisticsPreference,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { forwardMiddleMouseToCanvas } from "./utils.js";
|
import { forwardMiddleMouseToCanvas, forwardWheelToCanvas } from "./utils.js";
|
||||||
|
|
||||||
const MIN_HEIGHT = 150;
|
const MIN_HEIGHT = 150;
|
||||||
|
|
||||||
@@ -10,6 +10,7 @@ export function addTagsWidget(node, name, opts, callback, wheelSensitivity = 0.0
|
|||||||
const { allowStrengthAdjustment = true } = options;
|
const { allowStrengthAdjustment = true } = options;
|
||||||
|
|
||||||
forwardMiddleMouseToCanvas(container);
|
forwardMiddleMouseToCanvas(container);
|
||||||
|
forwardWheelToCanvas(container);
|
||||||
|
|
||||||
Object.assign(container.style, {
|
Object.assign(container.style, {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
|||||||
@@ -670,6 +670,69 @@ export function forwardMiddleMouseToCanvas(container) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forward wheel events from a container to the ComfyUI canvas for zooming,
|
||||||
|
* unless the container has scrollable content.
|
||||||
|
* This allows canvas zoom to work when hovering over DOM widgets.
|
||||||
|
* @param {HTMLElement} container - The root DOM element of the widget
|
||||||
|
* @param {Object} options - Configuration options
|
||||||
|
* @param {boolean} options.captureWheel - If true, always capture wheel events (default: false)
|
||||||
|
*/
|
||||||
|
export function forwardWheelToCanvas(container, options = {}) {
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const { captureWheel = false } = options;
|
||||||
|
|
||||||
|
container.addEventListener('wheel', (event) => {
|
||||||
|
// If explicitly capturing wheel (for internal scrolling), stop here
|
||||||
|
if (captureWheel) {
|
||||||
|
event.stopPropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access ComfyUI app from global window
|
||||||
|
const comfyApp = window.app;
|
||||||
|
if (!comfyApp || !comfyApp.canvas || typeof comfyApp.canvas.processMouseWheel !== 'function') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deltaX = event.deltaX;
|
||||||
|
const deltaY = event.deltaY;
|
||||||
|
const isHorizontal = Math.abs(deltaX) > Math.abs(deltaY);
|
||||||
|
|
||||||
|
// 1. Handle pinch-to-zoom (ctrlKey is true for pinch-to-zoom on most browsers)
|
||||||
|
if (event.ctrlKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
comfyApp.canvas.processMouseWheel(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Horizontal scroll: pass to canvas (widgets usually don't scroll horizontally)
|
||||||
|
if (isHorizontal) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
comfyApp.canvas.processMouseWheel(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Vertical scrolling: check if container is scrollable
|
||||||
|
const canScrollY = container.scrollHeight > container.clientHeight;
|
||||||
|
|
||||||
|
if (canScrollY) {
|
||||||
|
// Container is scrollable, let it handle the wheel event but stop propagation
|
||||||
|
// to prevent the canvas from zooming while the user is trying to scroll
|
||||||
|
event.stopPropagation();
|
||||||
|
} else {
|
||||||
|
// Container is NOT scrollable, forward the wheel event to the canvas
|
||||||
|
// so it can trigger zoom in/out
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
comfyApp.canvas.processMouseWheel(event);
|
||||||
|
}
|
||||||
|
}, { passive: false });
|
||||||
|
}
|
||||||
|
|
||||||
// Get connected Lora Pool node from pool_config input
|
// Get connected Lora Pool node from pool_config input
|
||||||
export function getConnectedPoolConfigNode(node) {
|
export function getConnectedPoolConfigNode(node) {
|
||||||
if (!node?.inputs) {
|
if (!node?.inputs) {
|
||||||
|
|||||||
@@ -1094,7 +1094,7 @@ to { transform: rotate(360deg);
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lora-pool-widget[data-v-0bbd50ea] {
|
.lora-pool-widget[data-v-ed73eab5] {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
background: rgba(40, 44, 52, 0.6);
|
background: rgba(40, 44, 52, 0.6);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -1177,7 +1177,7 @@ to { transform: rotate(360deg);
|
|||||||
padding: 4px;
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.single-slider[data-v-d04b968b] {
|
.single-slider[data-v-04874dd7] {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
@@ -1185,14 +1185,14 @@ to { transform: rotate(360deg);
|
|||||||
cursor: default !important;
|
cursor: default !important;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
}
|
}
|
||||||
.single-slider.disabled[data-v-d04b968b] {
|
.single-slider.disabled[data-v-04874dd7] {
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.single-slider.is-dragging[data-v-d04b968b] {
|
.single-slider.is-dragging[data-v-04874dd7] {
|
||||||
cursor: ew-resize !important;
|
cursor: ew-resize !important;
|
||||||
}
|
}
|
||||||
.slider-track[data-v-d04b968b] {
|
.slider-track[data-v-04874dd7] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 12px;
|
top: 12px;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -1202,13 +1202,13 @@ to { transform: rotate(360deg);
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: default !important;
|
cursor: default !important;
|
||||||
}
|
}
|
||||||
.slider-track__bg[data-v-d04b968b] {
|
.slider-track__bg[data-v-04874dd7] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
background: rgba(66, 153, 225, 0.15);
|
background: rgba(66, 153, 225, 0.15);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
.slider-track__active[data-v-d04b968b] {
|
.slider-track__active[data-v-04874dd7] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@@ -1217,14 +1217,14 @@ to { transform: rotate(360deg);
|
|||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
transition: width 0.05s linear;
|
transition: width 0.05s linear;
|
||||||
}
|
}
|
||||||
.slider-track__default[data-v-d04b968b] {
|
.slider-track__default[data-v-04874dd7] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: rgba(66, 153, 225, 0.1);
|
background: rgba(66, 153, 225, 0.1);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
.slider-handle[data-v-d04b968b] {
|
.slider-handle[data-v-04874dd7] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
@@ -1232,7 +1232,7 @@ to { transform: rotate(360deg);
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
}
|
}
|
||||||
.slider-handle__thumb[data-v-d04b968b] {
|
.slider-handle__thumb[data-v-04874dd7] {
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 14px;
|
height: 14px;
|
||||||
background: var(--fg-color, #fff);
|
background: var(--fg-color, #fff);
|
||||||
@@ -1243,13 +1243,13 @@ to { transform: rotate(360deg);
|
|||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||||
transition: transform 0.15s ease;
|
transition: transform 0.15s ease;
|
||||||
}
|
}
|
||||||
.slider-handle:hover .slider-handle__thumb[data-v-d04b968b] {
|
.slider-handle:hover .slider-handle__thumb[data-v-04874dd7] {
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
.slider-handle:active .slider-handle__thumb[data-v-d04b968b] {
|
.slider-handle:active .slider-handle__thumb[data-v-04874dd7] {
|
||||||
transform: scale(1.15);
|
transform: scale(1.15);
|
||||||
}
|
}
|
||||||
.slider-handle__value[data-v-d04b968b] {
|
.slider-handle__value[data-v-04874dd7] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -6px;
|
top: -6px;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
@@ -1263,7 +1263,7 @@ to { transform: rotate(360deg);
|
|||||||
line-height: 14px;
|
line-height: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dual-range-slider[data-v-9f6c6950] {
|
.dual-range-slider[data-v-e0c8dc9f] {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
@@ -1271,14 +1271,14 @@ to { transform: rotate(360deg);
|
|||||||
cursor: default !important;
|
cursor: default !important;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
}
|
}
|
||||||
.dual-range-slider.disabled[data-v-9f6c6950] {
|
.dual-range-slider.disabled[data-v-e0c8dc9f] {
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
.dual-range-slider.is-dragging[data-v-9f6c6950] {
|
.dual-range-slider.is-dragging[data-v-e0c8dc9f] {
|
||||||
cursor: ew-resize !important;
|
cursor: ew-resize !important;
|
||||||
}
|
}
|
||||||
.slider-track[data-v-9f6c6950] {
|
.slider-track[data-v-e0c8dc9f] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 12px;
|
top: 12px;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -1288,13 +1288,13 @@ to { transform: rotate(360deg);
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: default !important;
|
cursor: default !important;
|
||||||
}
|
}
|
||||||
.slider-track__bg[data-v-9f6c6950] {
|
.slider-track__bg[data-v-e0c8dc9f] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
background: rgba(66, 153, 225, 0.15);
|
background: rgba(66, 153, 225, 0.15);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
.slider-track__active[data-v-9f6c6950] {
|
.slider-track__active[data-v-e0c8dc9f] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@@ -1302,24 +1302,24 @@ to { transform: rotate(360deg);
|
|||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
transition: left 0.05s linear, width 0.05s linear;
|
transition: left 0.05s linear, width 0.05s linear;
|
||||||
}
|
}
|
||||||
.slider-track__default[data-v-9f6c6950] {
|
.slider-track__default[data-v-e0c8dc9f] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: rgba(66, 153, 225, 0.1);
|
background: rgba(66, 153, 225, 0.1);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
.slider-track__segment[data-v-9f6c6950] {
|
.slider-track__segment[data-v-e0c8dc9f] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background: rgba(66, 153, 225, 0.08);
|
background: rgba(66, 153, 225, 0.08);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
.slider-track__segment--expanded[data-v-9f6c6950] {
|
.slider-track__segment--expanded[data-v-e0c8dc9f] {
|
||||||
background: rgba(66, 153, 225, 0.15);
|
background: rgba(66, 153, 225, 0.15);
|
||||||
}
|
}
|
||||||
.slider-track__segment[data-v-9f6c6950]:not(:last-child)::after {
|
.slider-track__segment[data-v-e0c8dc9f]:not(:last-child)::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -1px;
|
top: -1px;
|
||||||
@@ -1328,7 +1328,7 @@ to { transform: rotate(360deg);
|
|||||||
width: 1px;
|
width: 1px;
|
||||||
background: rgba(255, 255, 255, 0.1);
|
background: rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
.slider-handle[data-v-9f6c6950] {
|
.slider-handle[data-v-e0c8dc9f] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
@@ -1336,7 +1336,7 @@ to { transform: rotate(360deg);
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
}
|
}
|
||||||
.slider-handle__thumb[data-v-9f6c6950] {
|
.slider-handle__thumb[data-v-e0c8dc9f] {
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 14px;
|
height: 14px;
|
||||||
background: var(--fg-color, #fff);
|
background: var(--fg-color, #fff);
|
||||||
@@ -1347,13 +1347,13 @@ to { transform: rotate(360deg);
|
|||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||||
transition: transform 0.15s ease;
|
transition: transform 0.15s ease;
|
||||||
}
|
}
|
||||||
.slider-handle:hover .slider-handle__thumb[data-v-9f6c6950] {
|
.slider-handle:hover .slider-handle__thumb[data-v-e0c8dc9f] {
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
.slider-handle:active .slider-handle__thumb[data-v-9f6c6950] {
|
.slider-handle:active .slider-handle__thumb[data-v-e0c8dc9f] {
|
||||||
transform: scale(1.15);
|
transform: scale(1.15);
|
||||||
}
|
}
|
||||||
.slider-handle__value[data-v-9f6c6950] {
|
.slider-handle__value[data-v-e0c8dc9f] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -6px;
|
top: -6px;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
@@ -1366,10 +1366,10 @@ to { transform: rotate(360deg);
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
line-height: 14px;
|
line-height: 14px;
|
||||||
}
|
}
|
||||||
.slider-handle--min .slider-handle__value[data-v-9f6c6950] {
|
.slider-handle--min .slider-handle__value[data-v-e0c8dc9f] {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.slider-handle--max .slider-handle__value[data-v-9f6c6950] {
|
.slider-handle--max .slider-handle__value[data-v-e0c8dc9f] {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1577,7 +1577,7 @@ to { transform: rotate(360deg);
|
|||||||
transform: translateY(4px);
|
transform: translateY(4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.lora-randomizer-widget[data-v-8063df56] {
|
.lora-randomizer-widget[data-v-94d3fca2] {
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
background: rgba(40, 44, 52, 0.6);
|
background: rgba(40, 44, 52, 0.6);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
@@ -2077,7 +2077,7 @@ to { transform: rotate(360deg);
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lora-cycler-widget[data-v-f6dad3ae] {
|
.lora-cycler-widget[data-v-b97187b9] {
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
background: rgba(40, 44, 52, 0.6);
|
background: rgba(40, 44, 52, 0.6);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
@@ -10620,7 +10620,7 @@ var PrimeVue = {
|
|||||||
setup(app2, configOptions);
|
setup(app2, configOptions);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const _hoisted_1$n = { class: "filter-chip__text" };
|
const _hoisted_1$k = { class: "filter-chip__text" };
|
||||||
const _hoisted_2$j = {
|
const _hoisted_2$j = {
|
||||||
key: 0,
|
key: 0,
|
||||||
class: "filter-chip__count"
|
class: "filter-chip__count"
|
||||||
@@ -10643,7 +10643,7 @@ const _sfc_main$o = /* @__PURE__ */ defineComponent({
|
|||||||
return openBlock(), createElementBlock("span", {
|
return openBlock(), createElementBlock("span", {
|
||||||
class: normalizeClass(["filter-chip", variantClass.value])
|
class: normalizeClass(["filter-chip", variantClass.value])
|
||||||
}, [
|
}, [
|
||||||
createBaseVNode("span", _hoisted_1$n, toDisplayString(__props.label), 1),
|
createBaseVNode("span", _hoisted_1$k, toDisplayString(__props.label), 1),
|
||||||
__props.count !== void 0 ? (openBlock(), createElementBlock("span", _hoisted_2$j, "(" + toDisplayString(__props.count) + ")", 1)) : createCommentVNode("", true),
|
__props.count !== void 0 ? (openBlock(), createElementBlock("span", _hoisted_2$j, "(" + toDisplayString(__props.count) + ")", 1)) : createCommentVNode("", true),
|
||||||
__props.removable ? (openBlock(), createElementBlock("button", {
|
__props.removable ? (openBlock(), createElementBlock("button", {
|
||||||
key: 1,
|
key: 1,
|
||||||
@@ -10686,7 +10686,7 @@ const _sfc_main$n = /* @__PURE__ */ defineComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const EditButton = /* @__PURE__ */ _export_sfc(_sfc_main$n, [["__scopeId", "data-v-8da8aa4b"]]);
|
const EditButton = /* @__PURE__ */ _export_sfc(_sfc_main$n, [["__scopeId", "data-v-8da8aa4b"]]);
|
||||||
const _hoisted_1$m = { class: "section" };
|
const _hoisted_1$j = { class: "section" };
|
||||||
const _hoisted_2$i = { class: "section__header" };
|
const _hoisted_2$i = { class: "section__header" };
|
||||||
const _hoisted_3$g = { class: "section__content" };
|
const _hoisted_3$g = { class: "section__content" };
|
||||||
const _hoisted_4$e = {
|
const _hoisted_4$e = {
|
||||||
@@ -10711,7 +10711,7 @@ const _sfc_main$m = /* @__PURE__ */ defineComponent({
|
|||||||
return model == null ? void 0 : model.count;
|
return model == null ? void 0 : model.count;
|
||||||
};
|
};
|
||||||
return (_ctx, _cache) => {
|
return (_ctx, _cache) => {
|
||||||
return openBlock(), createElementBlock("div", _hoisted_1$m, [
|
return openBlock(), createElementBlock("div", _hoisted_1$j, [
|
||||||
createBaseVNode("div", _hoisted_2$i, [
|
createBaseVNode("div", _hoisted_2$i, [
|
||||||
_cache[1] || (_cache[1] = createBaseVNode("span", { class: "section__title" }, "BASE MODEL", -1)),
|
_cache[1] || (_cache[1] = createBaseVNode("span", { class: "section__title" }, "BASE MODEL", -1)),
|
||||||
createVNode(EditButton, {
|
createVNode(EditButton, {
|
||||||
@@ -10735,7 +10735,7 @@ const _sfc_main$m = /* @__PURE__ */ defineComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const BaseModelSection = /* @__PURE__ */ _export_sfc(_sfc_main$m, [["__scopeId", "data-v-12f059e2"]]);
|
const BaseModelSection = /* @__PURE__ */ _export_sfc(_sfc_main$m, [["__scopeId", "data-v-12f059e2"]]);
|
||||||
const _hoisted_1$l = { class: "section" };
|
const _hoisted_1$i = { class: "section" };
|
||||||
const _hoisted_2$h = { class: "section__columns" };
|
const _hoisted_2$h = { class: "section__columns" };
|
||||||
const _hoisted_3$f = { class: "section__column" };
|
const _hoisted_3$f = { class: "section__column" };
|
||||||
const _hoisted_4$d = { class: "section__column-header" };
|
const _hoisted_4$d = { class: "section__column-header" };
|
||||||
@@ -10768,7 +10768,7 @@ const _sfc_main$l = /* @__PURE__ */ defineComponent({
|
|||||||
emits: ["edit-include", "edit-exclude"],
|
emits: ["edit-include", "edit-exclude"],
|
||||||
setup(__props) {
|
setup(__props) {
|
||||||
return (_ctx, _cache) => {
|
return (_ctx, _cache) => {
|
||||||
return openBlock(), createElementBlock("div", _hoisted_1$l, [
|
return openBlock(), createElementBlock("div", _hoisted_1$i, [
|
||||||
_cache[4] || (_cache[4] = createBaseVNode("div", { class: "section__header" }, [
|
_cache[4] || (_cache[4] = createBaseVNode("div", { class: "section__header" }, [
|
||||||
createBaseVNode("span", { class: "section__title" }, "TAGS")
|
createBaseVNode("span", { class: "section__title" }, "TAGS")
|
||||||
], -1)),
|
], -1)),
|
||||||
@@ -10817,7 +10817,7 @@ const _sfc_main$l = /* @__PURE__ */ defineComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const TagsSection = /* @__PURE__ */ _export_sfc(_sfc_main$l, [["__scopeId", "data-v-b869b780"]]);
|
const TagsSection = /* @__PURE__ */ _export_sfc(_sfc_main$l, [["__scopeId", "data-v-b869b780"]]);
|
||||||
const _hoisted_1$k = { class: "section" };
|
const _hoisted_1$h = { class: "section" };
|
||||||
const _hoisted_2$g = { class: "section__columns" };
|
const _hoisted_2$g = { class: "section__columns" };
|
||||||
const _hoisted_3$e = { class: "section__column" };
|
const _hoisted_3$e = { class: "section__column" };
|
||||||
const _hoisted_4$c = { class: "section__column-header" };
|
const _hoisted_4$c = { class: "section__column-header" };
|
||||||
@@ -10862,7 +10862,7 @@ const _sfc_main$k = /* @__PURE__ */ defineComponent({
|
|||||||
emit2("update:excludeFolders", props.excludeFolders.filter((p2) => p2 !== path));
|
emit2("update:excludeFolders", props.excludeFolders.filter((p2) => p2 !== path));
|
||||||
};
|
};
|
||||||
return (_ctx, _cache) => {
|
return (_ctx, _cache) => {
|
||||||
return openBlock(), createElementBlock("div", _hoisted_1$k, [
|
return openBlock(), createElementBlock("div", _hoisted_1$h, [
|
||||||
_cache[6] || (_cache[6] = createBaseVNode("div", { class: "section__header" }, [
|
_cache[6] || (_cache[6] = createBaseVNode("div", { class: "section__header" }, [
|
||||||
createBaseVNode("span", { class: "section__title" }, "FOLDERS")
|
createBaseVNode("span", { class: "section__title" }, "FOLDERS")
|
||||||
], -1)),
|
], -1)),
|
||||||
@@ -10933,7 +10933,7 @@ const _sfc_main$k = /* @__PURE__ */ defineComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const FoldersSection = /* @__PURE__ */ _export_sfc(_sfc_main$k, [["__scopeId", "data-v-af9caf84"]]);
|
const FoldersSection = /* @__PURE__ */ _export_sfc(_sfc_main$k, [["__scopeId", "data-v-af9caf84"]]);
|
||||||
const _hoisted_1$j = { class: "section" };
|
const _hoisted_1$g = { class: "section" };
|
||||||
const _hoisted_2$f = { class: "section__header" };
|
const _hoisted_2$f = { class: "section__header" };
|
||||||
const _hoisted_3$d = { class: "section__toggle" };
|
const _hoisted_3$d = { class: "section__toggle" };
|
||||||
const _hoisted_4$b = ["checked"];
|
const _hoisted_4$b = ["checked"];
|
||||||
@@ -10988,7 +10988,7 @@ const _sfc_main$j = /* @__PURE__ */ defineComponent({
|
|||||||
emit2("update:excludePatterns", props.excludePatterns.filter((p2) => p2 !== pattern));
|
emit2("update:excludePatterns", props.excludePatterns.filter((p2) => p2 !== pattern));
|
||||||
};
|
};
|
||||||
return (_ctx, _cache) => {
|
return (_ctx, _cache) => {
|
||||||
return openBlock(), createElementBlock("div", _hoisted_1$j, [
|
return openBlock(), createElementBlock("div", _hoisted_1$g, [
|
||||||
createBaseVNode("div", _hoisted_2$f, [
|
createBaseVNode("div", _hoisted_2$f, [
|
||||||
_cache[4] || (_cache[4] = createBaseVNode("span", { class: "section__title" }, "NAME PATTERNS", -1)),
|
_cache[4] || (_cache[4] = createBaseVNode("span", { class: "section__title" }, "NAME PATTERNS", -1)),
|
||||||
createBaseVNode("label", _hoisted_3$d, [
|
createBaseVNode("label", _hoisted_3$d, [
|
||||||
@@ -11073,7 +11073,7 @@ const _sfc_main$j = /* @__PURE__ */ defineComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const NamePatternsSection = /* @__PURE__ */ _export_sfc(_sfc_main$j, [["__scopeId", "data-v-9995b5ed"]]);
|
const NamePatternsSection = /* @__PURE__ */ _export_sfc(_sfc_main$j, [["__scopeId", "data-v-9995b5ed"]]);
|
||||||
const _hoisted_1$i = { class: "section" };
|
const _hoisted_1$f = { class: "section" };
|
||||||
const _hoisted_2$e = { class: "section__toggles" };
|
const _hoisted_2$e = { class: "section__toggles" };
|
||||||
const _hoisted_3$c = { class: "toggle-item" };
|
const _hoisted_3$c = { class: "toggle-item" };
|
||||||
const _hoisted_4$a = ["aria-checked"];
|
const _hoisted_4$a = ["aria-checked"];
|
||||||
@@ -11088,7 +11088,7 @@ const _sfc_main$i = /* @__PURE__ */ defineComponent({
|
|||||||
emits: ["update:noCreditRequired", "update:allowSelling"],
|
emits: ["update:noCreditRequired", "update:allowSelling"],
|
||||||
setup(__props) {
|
setup(__props) {
|
||||||
return (_ctx, _cache) => {
|
return (_ctx, _cache) => {
|
||||||
return openBlock(), createElementBlock("div", _hoisted_1$i, [
|
return openBlock(), createElementBlock("div", _hoisted_1$f, [
|
||||||
_cache[6] || (_cache[6] = createBaseVNode("div", { class: "section__header" }, [
|
_cache[6] || (_cache[6] = createBaseVNode("div", { class: "section__header" }, [
|
||||||
createBaseVNode("span", { class: "section__title" }, "LICENSE")
|
createBaseVNode("span", { class: "section__title" }, "LICENSE")
|
||||||
], -1)),
|
], -1)),
|
||||||
@@ -11125,7 +11125,7 @@ const _sfc_main$i = /* @__PURE__ */ defineComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const LicenseSection = /* @__PURE__ */ _export_sfc(_sfc_main$i, [["__scopeId", "data-v-dea4adf6"]]);
|
const LicenseSection = /* @__PURE__ */ _export_sfc(_sfc_main$i, [["__scopeId", "data-v-dea4adf6"]]);
|
||||||
const _hoisted_1$h = { class: "preview" };
|
const _hoisted_1$e = { class: "preview" };
|
||||||
const _hoisted_2$d = { class: "preview__title" };
|
const _hoisted_2$d = { class: "preview__title" };
|
||||||
const _hoisted_3$b = ["disabled"];
|
const _hoisted_3$b = ["disabled"];
|
||||||
const _hoisted_4$9 = {
|
const _hoisted_4$9 = {
|
||||||
@@ -11162,7 +11162,7 @@ const _sfc_main$h = /* @__PURE__ */ defineComponent({
|
|||||||
img.style.display = "none";
|
img.style.display = "none";
|
||||||
};
|
};
|
||||||
return (_ctx, _cache) => {
|
return (_ctx, _cache) => {
|
||||||
return openBlock(), createElementBlock("div", _hoisted_1$h, [
|
return openBlock(), createElementBlock("div", _hoisted_1$e, [
|
||||||
createBaseVNode("div", {
|
createBaseVNode("div", {
|
||||||
class: "preview__header",
|
class: "preview__header",
|
||||||
onMouseenter: _cache[1] || (_cache[1] = ($event) => showTooltip.value = true),
|
onMouseenter: _cache[1] || (_cache[1] = ($event) => showTooltip.value = true),
|
||||||
@@ -11226,7 +11226,7 @@ const _sfc_main$h = /* @__PURE__ */ defineComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const LoraPoolPreview = /* @__PURE__ */ _export_sfc(_sfc_main$h, [["__scopeId", "data-v-6a4b50a1"]]);
|
const LoraPoolPreview = /* @__PURE__ */ _export_sfc(_sfc_main$h, [["__scopeId", "data-v-6a4b50a1"]]);
|
||||||
const _hoisted_1$g = { class: "summary-view" };
|
const _hoisted_1$d = { class: "summary-view" };
|
||||||
const _hoisted_2$c = { class: "summary-view__filters" };
|
const _hoisted_2$c = { class: "summary-view__filters" };
|
||||||
const _sfc_main$g = /* @__PURE__ */ defineComponent({
|
const _sfc_main$g = /* @__PURE__ */ defineComponent({
|
||||||
__name: "LoraPoolSummaryView",
|
__name: "LoraPoolSummaryView",
|
||||||
@@ -11249,7 +11249,7 @@ const _sfc_main$g = /* @__PURE__ */ defineComponent({
|
|||||||
emits: ["open-modal", "update:includeFolders", "update:excludeFolders", "update:includePatterns", "update:excludePatterns", "update:useRegex", "update:noCreditRequired", "update:allowSelling", "refresh"],
|
emits: ["open-modal", "update:includeFolders", "update:excludeFolders", "update:includePatterns", "update:excludePatterns", "update:useRegex", "update:noCreditRequired", "update:allowSelling", "refresh"],
|
||||||
setup(__props) {
|
setup(__props) {
|
||||||
return (_ctx, _cache) => {
|
return (_ctx, _cache) => {
|
||||||
return openBlock(), createElementBlock("div", _hoisted_1$g, [
|
return openBlock(), createElementBlock("div", _hoisted_1$d, [
|
||||||
createBaseVNode("div", _hoisted_2$c, [
|
createBaseVNode("div", _hoisted_2$c, [
|
||||||
createVNode(BaseModelSection, {
|
createVNode(BaseModelSection, {
|
||||||
selected: __props.selectedBaseModels,
|
selected: __props.selectedBaseModels,
|
||||||
@@ -11296,7 +11296,7 @@ const _sfc_main$g = /* @__PURE__ */ defineComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const LoraPoolSummaryView = /* @__PURE__ */ _export_sfc(_sfc_main$g, [["__scopeId", "data-v-83235a00"]]);
|
const LoraPoolSummaryView = /* @__PURE__ */ _export_sfc(_sfc_main$g, [["__scopeId", "data-v-83235a00"]]);
|
||||||
const _hoisted_1$f = { class: "lora-pool-modal__header" };
|
const _hoisted_1$c = { class: "lora-pool-modal__header" };
|
||||||
const _hoisted_2$b = { class: "lora-pool-modal__title-container" };
|
const _hoisted_2$b = { class: "lora-pool-modal__title-container" };
|
||||||
const _hoisted_3$a = { class: "lora-pool-modal__title" };
|
const _hoisted_3$a = { class: "lora-pool-modal__title" };
|
||||||
const _hoisted_4$8 = {
|
const _hoisted_4$8 = {
|
||||||
@@ -11356,7 +11356,7 @@ const _sfc_main$f = /* @__PURE__ */ defineComponent({
|
|||||||
role: "dialog",
|
role: "dialog",
|
||||||
"aria-modal": "true"
|
"aria-modal": "true"
|
||||||
}, [
|
}, [
|
||||||
createBaseVNode("div", _hoisted_1$f, [
|
createBaseVNode("div", _hoisted_1$c, [
|
||||||
createBaseVNode("div", _hoisted_2$b, [
|
createBaseVNode("div", _hoisted_2$b, [
|
||||||
createBaseVNode("h3", _hoisted_3$a, toDisplayString(__props.title), 1),
|
createBaseVNode("h3", _hoisted_3$a, toDisplayString(__props.title), 1),
|
||||||
__props.subtitle ? (openBlock(), createElementBlock("p", _hoisted_4$8, toDisplayString(__props.subtitle), 1)) : createCommentVNode("", true)
|
__props.subtitle ? (openBlock(), createElementBlock("p", _hoisted_4$8, toDisplayString(__props.subtitle), 1)) : createCommentVNode("", true)
|
||||||
@@ -11384,7 +11384,7 @@ const _sfc_main$f = /* @__PURE__ */ defineComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const ModalWrapper = /* @__PURE__ */ _export_sfc(_sfc_main$f, [["__scopeId", "data-v-7b4de03d"]]);
|
const ModalWrapper = /* @__PURE__ */ _export_sfc(_sfc_main$f, [["__scopeId", "data-v-7b4de03d"]]);
|
||||||
const _hoisted_1$e = { class: "search-container" };
|
const _hoisted_1$b = { class: "search-container" };
|
||||||
const _hoisted_2$a = { class: "model-list" };
|
const _hoisted_2$a = { class: "model-list" };
|
||||||
const _hoisted_3$9 = ["checked", "onChange"];
|
const _hoisted_3$9 = ["checked", "onChange"];
|
||||||
const _hoisted_4$7 = { class: "model-checkbox-visual" };
|
const _hoisted_4$7 = { class: "model-checkbox-visual" };
|
||||||
@@ -11450,7 +11450,7 @@ const _sfc_main$e = /* @__PURE__ */ defineComponent({
|
|||||||
onClose: _cache[1] || (_cache[1] = ($event) => _ctx.$emit("close"))
|
onClose: _cache[1] || (_cache[1] = ($event) => _ctx.$emit("close"))
|
||||||
}, {
|
}, {
|
||||||
search: withCtx(() => [
|
search: withCtx(() => [
|
||||||
createBaseVNode("div", _hoisted_1$e, [
|
createBaseVNode("div", _hoisted_1$b, [
|
||||||
_cache[3] || (_cache[3] = createBaseVNode("svg", {
|
_cache[3] || (_cache[3] = createBaseVNode("svg", {
|
||||||
class: "search-icon",
|
class: "search-icon",
|
||||||
viewBox: "0 0 16 16",
|
viewBox: "0 0 16 16",
|
||||||
@@ -11515,7 +11515,7 @@ const _sfc_main$e = /* @__PURE__ */ defineComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const BaseModelModal = /* @__PURE__ */ _export_sfc(_sfc_main$e, [["__scopeId", "data-v-e02ca44a"]]);
|
const BaseModelModal = /* @__PURE__ */ _export_sfc(_sfc_main$e, [["__scopeId", "data-v-e02ca44a"]]);
|
||||||
const _hoisted_1$d = { class: "search-container" };
|
const _hoisted_1$a = { class: "search-container" };
|
||||||
const _hoisted_2$9 = ["onClick"];
|
const _hoisted_2$9 = ["onClick"];
|
||||||
const _hoisted_3$8 = {
|
const _hoisted_3$8 = {
|
||||||
key: 0,
|
key: 0,
|
||||||
@@ -11613,7 +11613,7 @@ const _sfc_main$d = /* @__PURE__ */ defineComponent({
|
|||||||
onClose: _cache[1] || (_cache[1] = ($event) => _ctx.$emit("close"))
|
onClose: _cache[1] || (_cache[1] = ($event) => _ctx.$emit("close"))
|
||||||
}, {
|
}, {
|
||||||
search: withCtx(() => [
|
search: withCtx(() => [
|
||||||
createBaseVNode("div", _hoisted_1$d, [
|
createBaseVNode("div", _hoisted_1$a, [
|
||||||
_cache[3] || (_cache[3] = createBaseVNode("svg", {
|
_cache[3] || (_cache[3] = createBaseVNode("svg", {
|
||||||
class: "search-icon",
|
class: "search-icon",
|
||||||
viewBox: "0 0 16 16",
|
viewBox: "0 0 16 16",
|
||||||
@@ -11671,7 +11671,7 @@ const _sfc_main$d = /* @__PURE__ */ defineComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const TagsModal = /* @__PURE__ */ _export_sfc(_sfc_main$d, [["__scopeId", "data-v-48c2535d"]]);
|
const TagsModal = /* @__PURE__ */ _export_sfc(_sfc_main$d, [["__scopeId", "data-v-48c2535d"]]);
|
||||||
const _hoisted_1$c = { class: "tree-node" };
|
const _hoisted_1$9 = { class: "tree-node" };
|
||||||
const _hoisted_2$8 = {
|
const _hoisted_2$8 = {
|
||||||
key: 1,
|
key: 1,
|
||||||
class: "tree-node__toggle-spacer"
|
class: "tree-node__toggle-spacer"
|
||||||
@@ -11714,7 +11714,7 @@ const _sfc_main$c = /* @__PURE__ */ defineComponent({
|
|||||||
};
|
};
|
||||||
return (_ctx, _cache) => {
|
return (_ctx, _cache) => {
|
||||||
const _component_FolderTreeNode = resolveComponent("FolderTreeNode", true);
|
const _component_FolderTreeNode = resolveComponent("FolderTreeNode", true);
|
||||||
return openBlock(), createElementBlock("div", _hoisted_1$c, [
|
return openBlock(), createElementBlock("div", _hoisted_1$9, [
|
||||||
createBaseVNode("div", {
|
createBaseVNode("div", {
|
||||||
class: normalizeClass(["tree-node__item", [
|
class: normalizeClass(["tree-node__item", [
|
||||||
`tree-node__item--${__props.variant}`,
|
`tree-node__item--${__props.variant}`,
|
||||||
@@ -11781,7 +11781,7 @@ const _sfc_main$c = /* @__PURE__ */ defineComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const FolderTreeNode = /* @__PURE__ */ _export_sfc(_sfc_main$c, [["__scopeId", "data-v-90187dd4"]]);
|
const FolderTreeNode = /* @__PURE__ */ _export_sfc(_sfc_main$c, [["__scopeId", "data-v-90187dd4"]]);
|
||||||
const _hoisted_1$b = { class: "search-container" };
|
const _hoisted_1$8 = { class: "search-container" };
|
||||||
const _hoisted_2$7 = { class: "folder-tree" };
|
const _hoisted_2$7 = { class: "folder-tree" };
|
||||||
const _hoisted_3$6 = {
|
const _hoisted_3$6 = {
|
||||||
key: 1,
|
key: 1,
|
||||||
@@ -11851,7 +11851,7 @@ const _sfc_main$b = /* @__PURE__ */ defineComponent({
|
|||||||
onClose: _cache[1] || (_cache[1] = ($event) => _ctx.$emit("close"))
|
onClose: _cache[1] || (_cache[1] = ($event) => _ctx.$emit("close"))
|
||||||
}, {
|
}, {
|
||||||
search: withCtx(() => [
|
search: withCtx(() => [
|
||||||
createBaseVNode("div", _hoisted_1$b, [
|
createBaseVNode("div", _hoisted_1$8, [
|
||||||
_cache[2] || (_cache[2] = createBaseVNode("svg", {
|
_cache[2] || (_cache[2] = createBaseVNode("svg", {
|
||||||
class: "search-icon",
|
class: "search-icon",
|
||||||
viewBox: "0 0 16 16",
|
viewBox: "0 0 16 16",
|
||||||
@@ -12156,7 +12156,6 @@ function useModalState() {
|
|||||||
isModalOpen
|
isModalOpen
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const _hoisted_1$a = { class: "lora-pool-widget" };
|
|
||||||
const _sfc_main$a = /* @__PURE__ */ defineComponent({
|
const _sfc_main$a = /* @__PURE__ */ defineComponent({
|
||||||
__name: "LoraPoolWidget",
|
__name: "LoraPoolWidget",
|
||||||
props: {
|
props: {
|
||||||
@@ -12170,6 +12169,35 @@ const _sfc_main$a = /* @__PURE__ */ defineComponent({
|
|||||||
const openModal = (modal) => {
|
const openModal = (modal) => {
|
||||||
modalState.openModal(modal);
|
modalState.openModal(modal);
|
||||||
};
|
};
|
||||||
|
const onWheel = (event) => {
|
||||||
|
const target = event.target;
|
||||||
|
if (target == null ? void 0 : target.closest('[data-capture-wheel="true"]')) {
|
||||||
|
event.stopPropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const app2 = window.app;
|
||||||
|
if (!app2 || !app2.canvas || typeof app2.canvas.processMouseWheel !== "function") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const deltaX = event.deltaX;
|
||||||
|
const deltaY = event.deltaY;
|
||||||
|
const isHorizontal = Math.abs(deltaX) > Math.abs(deltaY);
|
||||||
|
if (event.ctrlKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
app2.canvas.processMouseWheel(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isHorizontal) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
app2.canvas.processMouseWheel(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
app2.canvas.processMouseWheel(event);
|
||||||
|
};
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
props.widget.callback = (v2) => {
|
props.widget.callback = (v2) => {
|
||||||
if (v2) {
|
if (v2) {
|
||||||
@@ -12185,7 +12213,10 @@ const _sfc_main$a = /* @__PURE__ */ defineComponent({
|
|||||||
await state.refreshPreview();
|
await state.refreshPreview();
|
||||||
});
|
});
|
||||||
return (_ctx, _cache) => {
|
return (_ctx, _cache) => {
|
||||||
return openBlock(), createElementBlock("div", _hoisted_1$a, [
|
return openBlock(), createElementBlock("div", {
|
||||||
|
class: "lora-pool-widget",
|
||||||
|
onWheel
|
||||||
|
}, [
|
||||||
createVNode(LoraPoolSummaryView, {
|
createVNode(LoraPoolSummaryView, {
|
||||||
"selected-base-models": unref(state).selectedBaseModels.value,
|
"selected-base-models": unref(state).selectedBaseModels.value,
|
||||||
"available-base-models": unref(state).availableBaseModels.value,
|
"available-base-models": unref(state).availableBaseModels.value,
|
||||||
@@ -12250,12 +12281,12 @@ const _sfc_main$a = /* @__PURE__ */ defineComponent({
|
|||||||
onClose: unref(modalState).closeModal,
|
onClose: unref(modalState).closeModal,
|
||||||
"onUpdate:selected": _cache[11] || (_cache[11] = ($event) => unref(state).excludeFolders.value = $event)
|
"onUpdate:selected": _cache[11] || (_cache[11] = ($event) => unref(state).excludeFolders.value = $event)
|
||||||
}, null, 8, ["visible", "folders", "selected", "onClose"])
|
}, null, 8, ["visible", "folders", "selected", "onClose"])
|
||||||
]);
|
], 32);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const LoraPoolWidget = /* @__PURE__ */ _export_sfc(_sfc_main$a, [["__scopeId", "data-v-0bbd50ea"]]);
|
const LoraPoolWidget = /* @__PURE__ */ _export_sfc(_sfc_main$a, [["__scopeId", "data-v-ed73eab5"]]);
|
||||||
const _hoisted_1$9 = { class: "last-used-preview" };
|
const _hoisted_1$7 = { class: "last-used-preview" };
|
||||||
const _hoisted_2$6 = { class: "last-used-preview__content" };
|
const _hoisted_2$6 = { class: "last-used-preview__content" };
|
||||||
const _hoisted_3$5 = ["src", "onError"];
|
const _hoisted_3$5 = ["src", "onError"];
|
||||||
const _hoisted_4$4 = {
|
const _hoisted_4$4 = {
|
||||||
@@ -12297,7 +12328,7 @@ const _sfc_main$9 = /* @__PURE__ */ defineComponent({
|
|||||||
previewUrls.value[loraName] = "";
|
previewUrls.value[loraName] = "";
|
||||||
};
|
};
|
||||||
return (_ctx, _cache) => {
|
return (_ctx, _cache) => {
|
||||||
return openBlock(), createElementBlock("div", _hoisted_1$9, [
|
return openBlock(), createElementBlock("div", _hoisted_1$7, [
|
||||||
createBaseVNode("div", _hoisted_2$6, [
|
createBaseVNode("div", _hoisted_2$6, [
|
||||||
(openBlock(true), createElementBlock(Fragment, null, renderList(displayLoras.value, (lora) => {
|
(openBlock(true), createElementBlock(Fragment, null, renderList(displayLoras.value, (lora) => {
|
||||||
return openBlock(), createElementBlock("div", {
|
return openBlock(), createElementBlock("div", {
|
||||||
@@ -12331,7 +12362,7 @@ const _sfc_main$9 = /* @__PURE__ */ defineComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const LastUsedPreview = /* @__PURE__ */ _export_sfc(_sfc_main$9, [["__scopeId", "data-v-b940502e"]]);
|
const LastUsedPreview = /* @__PURE__ */ _export_sfc(_sfc_main$9, [["__scopeId", "data-v-b940502e"]]);
|
||||||
const _hoisted_1$8 = { class: "slider-handle__value" };
|
const _hoisted_1$6 = { class: "slider-handle__value" };
|
||||||
const _sfc_main$8 = /* @__PURE__ */ defineComponent({
|
const _sfc_main$8 = /* @__PURE__ */ defineComponent({
|
||||||
__name: "SingleSlider",
|
__name: "SingleSlider",
|
||||||
props: {
|
props: {
|
||||||
@@ -12407,10 +12438,10 @@ const _sfc_main$8 = /* @__PURE__ */ defineComponent({
|
|||||||
if (!rect) return;
|
if (!rect) return;
|
||||||
const rootRect = event.currentTarget.getBoundingClientRect();
|
const rootRect = event.currentTarget.getBoundingClientRect();
|
||||||
if (event.clientX < rootRect.left || event.clientX > rootRect.right || event.clientY < rootRect.top || event.clientY > rootRect.bottom) return;
|
if (event.clientX < rootRect.left || event.clientX > rootRect.right || event.clientY < rootRect.top || event.clientY > rootRect.bottom) return;
|
||||||
event.preventDefault();
|
|
||||||
const delta = event.deltaY > 0 ? -1 : 1;
|
const delta = event.deltaY > 0 ? -1 : 1;
|
||||||
const newValue = snapToStep(props.value + delta * props.step);
|
const newValue = snapToStep(props.value + delta * props.step);
|
||||||
emit2("update:value", newValue);
|
emit2("update:value", newValue);
|
||||||
|
event.stopPropagation();
|
||||||
};
|
};
|
||||||
const stopDrag = (event) => {
|
const stopDrag = (event) => {
|
||||||
if (!dragging.value) return;
|
if (!dragging.value) return;
|
||||||
@@ -12458,14 +12489,14 @@ const _sfc_main$8 = /* @__PURE__ */ defineComponent({
|
|||||||
onPointercancel: withModifiers(stopDrag, ["stop"])
|
onPointercancel: withModifiers(stopDrag, ["stop"])
|
||||||
}, [
|
}, [
|
||||||
_cache[1] || (_cache[1] = createBaseVNode("div", { class: "slider-handle__thumb" }, null, -1)),
|
_cache[1] || (_cache[1] = createBaseVNode("div", { class: "slider-handle__thumb" }, null, -1)),
|
||||||
createBaseVNode("div", _hoisted_1$8, toDisplayString(formatValue(__props.value)), 1)
|
createBaseVNode("div", _hoisted_1$6, toDisplayString(formatValue(__props.value)), 1)
|
||||||
], 36)
|
], 36)
|
||||||
], 34);
|
], 34);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const SingleSlider = /* @__PURE__ */ _export_sfc(_sfc_main$8, [["__scopeId", "data-v-d04b968b"]]);
|
const SingleSlider = /* @__PURE__ */ _export_sfc(_sfc_main$8, [["__scopeId", "data-v-04874dd7"]]);
|
||||||
const _hoisted_1$7 = { class: "slider-handle__value" };
|
const _hoisted_1$5 = { class: "slider-handle__value" };
|
||||||
const _hoisted_2$5 = { class: "slider-handle__value" };
|
const _hoisted_2$5 = { class: "slider-handle__value" };
|
||||||
const _sfc_main$7 = /* @__PURE__ */ defineComponent({
|
const _sfc_main$7 = /* @__PURE__ */ defineComponent({
|
||||||
__name: "DualRangeSlider",
|
__name: "DualRangeSlider",
|
||||||
@@ -12637,7 +12668,6 @@ const _sfc_main$7 = /* @__PURE__ */ defineComponent({
|
|||||||
if (!rect) return;
|
if (!rect) return;
|
||||||
const rootRect = event.currentTarget.getBoundingClientRect();
|
const rootRect = event.currentTarget.getBoundingClientRect();
|
||||||
if (event.clientX < rootRect.left || event.clientX > rootRect.right || event.clientY < rootRect.top || event.clientY > rootRect.bottom) return;
|
if (event.clientX < rootRect.left || event.clientX > rootRect.right || event.clientY < rootRect.top || event.clientY > rootRect.bottom) return;
|
||||||
event.preventDefault();
|
|
||||||
const delta = event.deltaY > 0 ? -1 : 1;
|
const delta = event.deltaY > 0 ? -1 : 1;
|
||||||
const relativeX = event.clientX - rect.left;
|
const relativeX = event.clientX - rect.left;
|
||||||
const rangeWidth = rect.width;
|
const rangeWidth = rect.width;
|
||||||
@@ -12673,6 +12703,7 @@ const _sfc_main$7 = /* @__PURE__ */ defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
event.stopPropagation();
|
||||||
};
|
};
|
||||||
const stopDrag = (event) => {
|
const stopDrag = (event) => {
|
||||||
if (!dragging.value) return;
|
if (!dragging.value) return;
|
||||||
@@ -12730,7 +12761,7 @@ const _sfc_main$7 = /* @__PURE__ */ defineComponent({
|
|||||||
onPointercancel: withModifiers(stopDrag, ["stop"])
|
onPointercancel: withModifiers(stopDrag, ["stop"])
|
||||||
}, [
|
}, [
|
||||||
_cache[3] || (_cache[3] = createBaseVNode("div", { class: "slider-handle__thumb" }, null, -1)),
|
_cache[3] || (_cache[3] = createBaseVNode("div", { class: "slider-handle__thumb" }, null, -1)),
|
||||||
createBaseVNode("div", _hoisted_1$7, toDisplayString(formatValue(__props.valueMin)), 1)
|
createBaseVNode("div", _hoisted_1$5, toDisplayString(formatValue(__props.valueMin)), 1)
|
||||||
], 36),
|
], 36),
|
||||||
createBaseVNode("div", {
|
createBaseVNode("div", {
|
||||||
class: "slider-handle slider-handle--max",
|
class: "slider-handle slider-handle--max",
|
||||||
@@ -12747,8 +12778,8 @@ const _sfc_main$7 = /* @__PURE__ */ defineComponent({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const DualRangeSlider = /* @__PURE__ */ _export_sfc(_sfc_main$7, [["__scopeId", "data-v-9f6c6950"]]);
|
const DualRangeSlider = /* @__PURE__ */ _export_sfc(_sfc_main$7, [["__scopeId", "data-v-e0c8dc9f"]]);
|
||||||
const _hoisted_1$6 = { class: "randomizer-settings" };
|
const _hoisted_1$4 = { class: "randomizer-settings" };
|
||||||
const _hoisted_2$4 = { class: "setting-section" };
|
const _hoisted_2$4 = { class: "setting-section" };
|
||||||
const _hoisted_3$4 = { class: "count-mode-tabs" };
|
const _hoisted_3$4 = { class: "count-mode-tabs" };
|
||||||
const _hoisted_4$3 = ["checked"];
|
const _hoisted_4$3 = ["checked"];
|
||||||
@@ -12809,7 +12840,7 @@ const _sfc_main$6 = /* @__PURE__ */ defineComponent({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
return (_ctx, _cache) => {
|
return (_ctx, _cache) => {
|
||||||
return openBlock(), createElementBlock("div", _hoisted_1$6, [
|
return openBlock(), createElementBlock("div", _hoisted_1$4, [
|
||||||
_cache[29] || (_cache[29] = createBaseVNode("div", { class: "settings-header" }, [
|
_cache[29] || (_cache[29] = createBaseVNode("div", { class: "settings-header" }, [
|
||||||
createBaseVNode("h3", { class: "settings-title" }, "RANDOMIZER SETTINGS")
|
createBaseVNode("h3", { class: "settings-title" }, "RANDOMIZER SETTINGS")
|
||||||
], -1)),
|
], -1)),
|
||||||
@@ -13205,7 +13236,6 @@ function useLoraRandomizerState(widget) {
|
|||||||
initializeNextSeed
|
initializeNextSeed
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const _hoisted_1$5 = { class: "lora-randomizer-widget" };
|
|
||||||
const _sfc_main$5 = /* @__PURE__ */ defineComponent({
|
const _sfc_main$5 = /* @__PURE__ */ defineComponent({
|
||||||
__name: "LoraRandomizerWidget",
|
__name: "LoraRandomizerWidget",
|
||||||
props: {
|
props: {
|
||||||
@@ -13277,6 +13307,35 @@ const _sfc_main$5 = /* @__PURE__ */ defineComponent({
|
|||||||
state.rollMode.value = "fixed";
|
state.rollMode.value = "fixed";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const onWheel = (event) => {
|
||||||
|
const target = event.target;
|
||||||
|
if (target == null ? void 0 : target.closest('[data-capture-wheel="true"]')) {
|
||||||
|
event.stopPropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const app2 = window.app;
|
||||||
|
if (!app2 || !app2.canvas || typeof app2.canvas.processMouseWheel !== "function") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const deltaX = event.deltaX;
|
||||||
|
const deltaY = event.deltaY;
|
||||||
|
const isHorizontal = Math.abs(deltaX) > Math.abs(deltaY);
|
||||||
|
if (event.ctrlKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
app2.canvas.processMouseWheel(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isHorizontal) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
app2.canvas.processMouseWheel(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
app2.canvas.processMouseWheel(event);
|
||||||
|
};
|
||||||
watch(() => {
|
watch(() => {
|
||||||
var _a2, _b;
|
var _a2, _b;
|
||||||
return (_b = (_a2 = props.node.widgets) == null ? void 0 : _a2.find((w2) => w2.name === "loras")) == null ? void 0 : _b.value;
|
return (_b = (_a2 = props.node.widgets) == null ? void 0 : _a2.find((w2) => w2.name === "loras")) == null ? void 0 : _b.value;
|
||||||
@@ -13336,7 +13395,10 @@ const _sfc_main$5 = /* @__PURE__ */ defineComponent({
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
return (_ctx, _cache) => {
|
return (_ctx, _cache) => {
|
||||||
return openBlock(), createElementBlock("div", _hoisted_1$5, [
|
return openBlock(), createElementBlock("div", {
|
||||||
|
class: "lora-randomizer-widget",
|
||||||
|
onWheel
|
||||||
|
}, [
|
||||||
createVNode(LoraRandomizerSettingsView, {
|
createVNode(LoraRandomizerSettingsView, {
|
||||||
"count-mode": unref(state).countMode.value,
|
"count-mode": unref(state).countMode.value,
|
||||||
"count-fixed": unref(state).countFixed.value,
|
"count-fixed": unref(state).countFixed.value,
|
||||||
@@ -13373,12 +13435,12 @@ const _sfc_main$5 = /* @__PURE__ */ defineComponent({
|
|||||||
onAlwaysRandomize: handleAlwaysRandomize,
|
onAlwaysRandomize: handleAlwaysRandomize,
|
||||||
onReuseLast: handleReuseLast
|
onReuseLast: handleReuseLast
|
||||||
}, null, 8, ["count-mode", "count-fixed", "count-min", "count-max", "model-strength-min", "model-strength-max", "use-custom-clip-range", "clip-strength-min", "clip-strength-max", "roll-mode", "is-rolling", "is-clip-strength-disabled", "last-used", "current-loras", "can-reuse-last", "use-recommended-strength", "recommended-strength-scale-min", "recommended-strength-scale-max"])
|
}, null, 8, ["count-mode", "count-fixed", "count-min", "count-max", "model-strength-min", "model-strength-max", "use-custom-clip-range", "clip-strength-min", "clip-strength-max", "roll-mode", "is-rolling", "is-clip-strength-disabled", "last-used", "current-loras", "can-reuse-last", "use-recommended-strength", "recommended-strength-scale-min", "recommended-strength-scale-max"])
|
||||||
]);
|
], 32);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const LoraRandomizerWidget = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["__scopeId", "data-v-8063df56"]]);
|
const LoraRandomizerWidget = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["__scopeId", "data-v-94d3fca2"]]);
|
||||||
const _hoisted_1$4 = { class: "cycler-settings" };
|
const _hoisted_1$3 = { class: "cycler-settings" };
|
||||||
const _hoisted_2$3 = { class: "setting-section progress-section" };
|
const _hoisted_2$3 = { class: "setting-section progress-section" };
|
||||||
const _hoisted_3$3 = { class: "progress-label" };
|
const _hoisted_3$3 = { class: "progress-label" };
|
||||||
const _hoisted_4$2 = ["title"];
|
const _hoisted_4$2 = ["title"];
|
||||||
@@ -13488,7 +13550,7 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
|
|||||||
tempRepeat.value = "";
|
tempRepeat.value = "";
|
||||||
};
|
};
|
||||||
return (_ctx, _cache) => {
|
return (_ctx, _cache) => {
|
||||||
return openBlock(), createElementBlock("div", _hoisted_1$4, [
|
return openBlock(), createElementBlock("div", _hoisted_1$3, [
|
||||||
_cache[24] || (_cache[24] = createBaseVNode("div", { class: "settings-header" }, [
|
_cache[24] || (_cache[24] = createBaseVNode("div", { class: "settings-header" }, [
|
||||||
createBaseVNode("h3", { class: "settings-title" }, "CYCLER SETTINGS")
|
createBaseVNode("h3", { class: "settings-title" }, "CYCLER SETTINGS")
|
||||||
], -1)),
|
], -1)),
|
||||||
@@ -13669,7 +13731,7 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const LoraCyclerSettingsView = /* @__PURE__ */ _export_sfc(_sfc_main$4, [["__scopeId", "data-v-f65566fd"]]);
|
const LoraCyclerSettingsView = /* @__PURE__ */ _export_sfc(_sfc_main$4, [["__scopeId", "data-v-f65566fd"]]);
|
||||||
const _hoisted_1$3 = { class: "search-container" };
|
const _hoisted_1$2 = { class: "search-container" };
|
||||||
const _hoisted_2$2 = { class: "lora-list" };
|
const _hoisted_2$2 = { class: "lora-list" };
|
||||||
const _hoisted_3$2 = ["onMouseenter", "onClick"];
|
const _hoisted_3$2 = ["onMouseenter", "onClick"];
|
||||||
const _hoisted_4$1 = { class: "lora-index" };
|
const _hoisted_4$1 = { class: "lora-index" };
|
||||||
@@ -13800,7 +13862,7 @@ const _sfc_main$3 = /* @__PURE__ */ defineComponent({
|
|||||||
onClose: _cache[1] || (_cache[1] = ($event) => _ctx.$emit("close"))
|
onClose: _cache[1] || (_cache[1] = ($event) => _ctx.$emit("close"))
|
||||||
}, {
|
}, {
|
||||||
search: withCtx(() => [
|
search: withCtx(() => [
|
||||||
createBaseVNode("div", _hoisted_1$3, [
|
createBaseVNode("div", _hoisted_1$2, [
|
||||||
_cache[3] || (_cache[3] = createBaseVNode("svg", {
|
_cache[3] || (_cache[3] = createBaseVNode("svg", {
|
||||||
class: "search-icon",
|
class: "search-icon",
|
||||||
viewBox: "0 0 16 16",
|
viewBox: "0 0 16 16",
|
||||||
@@ -14106,7 +14168,6 @@ function useLoraCyclerState(widget) {
|
|||||||
togglePause
|
togglePause
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const _hoisted_1$2 = { class: "lora-cycler-widget" };
|
|
||||||
const _sfc_main$2 = /* @__PURE__ */ defineComponent({
|
const _sfc_main$2 = /* @__PURE__ */ defineComponent({
|
||||||
__name: "LoraCyclerWidget",
|
__name: "LoraCyclerWidget",
|
||||||
props: {
|
props: {
|
||||||
@@ -14218,6 +14279,35 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
|
|||||||
console.error("[LoraCyclerWidget] Error resetting index:", error);
|
console.error("[LoraCyclerWidget] Error resetting index:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const onWheel = (event) => {
|
||||||
|
const target = event.target;
|
||||||
|
if (target == null ? void 0 : target.closest('[data-capture-wheel="true"]')) {
|
||||||
|
event.stopPropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const app2 = window.app;
|
||||||
|
if (!app2 || !app2.canvas || typeof app2.canvas.processMouseWheel !== "function") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const deltaX = event.deltaX;
|
||||||
|
const deltaY = event.deltaY;
|
||||||
|
const isHorizontal = Math.abs(deltaX) > Math.abs(deltaY);
|
||||||
|
if (event.ctrlKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
app2.canvas.processMouseWheel(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isHorizontal) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
app2.canvas.processMouseWheel(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
app2.canvas.processMouseWheel(event);
|
||||||
|
};
|
||||||
const checkPoolConfigChanges = async () => {
|
const checkPoolConfigChanges = async () => {
|
||||||
if (!isMounted.value) return;
|
if (!isMounted.value) return;
|
||||||
const poolConfig = getPoolConfig();
|
const poolConfig = getPoolConfig();
|
||||||
@@ -14374,7 +14464,10 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
return (_ctx, _cache) => {
|
return (_ctx, _cache) => {
|
||||||
return openBlock(), createElementBlock("div", _hoisted_1$2, [
|
return openBlock(), createElementBlock("div", {
|
||||||
|
class: "lora-cycler-widget",
|
||||||
|
onWheel
|
||||||
|
}, [
|
||||||
createVNode(LoraCyclerSettingsView, {
|
createVNode(LoraCyclerSettingsView, {
|
||||||
"current-index": unref(state).currentIndex.value,
|
"current-index": unref(state).currentIndex.value,
|
||||||
"total-count": displayTotalCount.value,
|
"total-count": displayTotalCount.value,
|
||||||
@@ -14411,11 +14504,11 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
|
|||||||
onClose: _cache[3] || (_cache[3] = ($event) => isModalOpen.value = false),
|
onClose: _cache[3] || (_cache[3] = ($event) => isModalOpen.value = false),
|
||||||
onSelect: handleModalSelect
|
onSelect: handleModalSelect
|
||||||
}, null, 8, ["visible", "lora-list", "current-index", "include-no-lora"])
|
}, null, 8, ["visible", "lora-list", "current-index", "include-no-lora"])
|
||||||
]);
|
], 32);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const LoraCyclerWidget = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-f6dad3ae"]]);
|
const LoraCyclerWidget = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-b97187b9"]]);
|
||||||
const _hoisted_1$1 = { class: "json-display-widget" };
|
const _hoisted_1$1 = { class: "json-display-widget" };
|
||||||
const _hoisted_2$1 = {
|
const _hoisted_2$1 = {
|
||||||
class: "json-content",
|
class: "json-content",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user