mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 13:12:12 -03:00
feat(lora-pool): add regex include/exclude name pattern filtering (#839)
Add name pattern filtering to LoRA Pool node allowing users to filter LoRAs by filename or model name using either plain text or regex patterns. Features: - Include patterns: only show LoRAs matching at least one pattern - Exclude patterns: exclude LoRAs matching any pattern - Regex toggle: switch between substring and regex matching - Case-insensitive matching for both modes - Invalid regex automatically falls back to substring matching - Filters apply to both file_name and model_name fields Backend: - Update LoraPoolLM._default_config() with namePatterns structure - Add name pattern filtering to _apply_pool_filters() and _apply_specific_filters() - Add API parameter parsing for name_pattern_include/exclude/use_regex - Update LoraPoolConfig type with namePatterns field Frontend: - Add NamePatternsSection.vue component with pattern input UI - Update useLoraPoolState to manage pattern state and API integration - Update LoraPoolSummaryView to display NamePatternsSection - Increase LORA_POOL_WIDGET_MIN_HEIGHT to accommodate new UI Tests: - Add 7 test cases covering text/regex include, exclude, combined filtering, model name fallback, and invalid regex handling Closes #839
This commit is contained in:
@@ -82,6 +82,7 @@ class LoraPoolLM:
|
|||||||
"folders": {"include": [], "exclude": []},
|
"folders": {"include": [], "exclude": []},
|
||||||
"favoritesOnly": False,
|
"favoritesOnly": False,
|
||||||
"license": {"noCreditRequired": False, "allowSelling": False},
|
"license": {"noCreditRequired": False, "allowSelling": False},
|
||||||
|
"namePatterns": {"include": [], "exclude": [], "useRegex": False},
|
||||||
},
|
},
|
||||||
"preview": {"matchCount": 0, "lastUpdated": 0},
|
"preview": {"matchCount": 0, "lastUpdated": 0},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -309,6 +309,13 @@ class ModelListingHandler:
|
|||||||
else:
|
else:
|
||||||
allow_selling_generated_content = None # None means no filter applied
|
allow_selling_generated_content = None # None means no filter applied
|
||||||
|
|
||||||
|
# Name pattern filters for LoRA Pool
|
||||||
|
name_pattern_include = request.query.getall("name_pattern_include", [])
|
||||||
|
name_pattern_exclude = request.query.getall("name_pattern_exclude", [])
|
||||||
|
name_pattern_use_regex = (
|
||||||
|
request.query.get("name_pattern_use_regex", "false").lower() == "true"
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"page": page,
|
"page": page,
|
||||||
"page_size": page_size,
|
"page_size": page_size,
|
||||||
@@ -328,6 +335,9 @@ class ModelListingHandler:
|
|||||||
"credit_required": credit_required,
|
"credit_required": credit_required,
|
||||||
"allow_selling_generated_content": allow_selling_generated_content,
|
"allow_selling_generated_content": allow_selling_generated_content,
|
||||||
"model_types": model_types,
|
"model_types": model_types,
|
||||||
|
"name_pattern_include": name_pattern_include,
|
||||||
|
"name_pattern_exclude": name_pattern_exclude,
|
||||||
|
"name_pattern_use_regex": name_pattern_use_regex,
|
||||||
**self._parse_specific_params(request),
|
**self._parse_specific_params(request),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class LoraService(BaseModelService):
|
|||||||
# Resolve sub_type using priority: sub_type > model_type > civitai.model.type > default
|
# Resolve sub_type using priority: sub_type > model_type > civitai.model.type > default
|
||||||
# Normalize to lowercase for consistent API responses
|
# Normalize to lowercase for consistent API responses
|
||||||
sub_type = resolve_sub_type(lora_data).lower()
|
sub_type = resolve_sub_type(lora_data).lower()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"model_name": lora_data["model_name"],
|
"model_name": lora_data["model_name"],
|
||||||
"file_name": lora_data["file_name"],
|
"file_name": lora_data["file_name"],
|
||||||
@@ -48,7 +48,9 @@ class LoraService(BaseModelService):
|
|||||||
"notes": lora_data.get("notes", ""),
|
"notes": lora_data.get("notes", ""),
|
||||||
"favorite": lora_data.get("favorite", False),
|
"favorite": lora_data.get("favorite", False),
|
||||||
"update_available": bool(lora_data.get("update_available", False)),
|
"update_available": bool(lora_data.get("update_available", False)),
|
||||||
"skip_metadata_refresh": bool(lora_data.get("skip_metadata_refresh", False)),
|
"skip_metadata_refresh": bool(
|
||||||
|
lora_data.get("skip_metadata_refresh", False)
|
||||||
|
),
|
||||||
"sub_type": sub_type,
|
"sub_type": sub_type,
|
||||||
"civitai": self.filter_civitai_data(
|
"civitai": self.filter_civitai_data(
|
||||||
lora_data.get("civitai", {}), minimal=True
|
lora_data.get("civitai", {}), minimal=True
|
||||||
@@ -62,6 +64,68 @@ class LoraService(BaseModelService):
|
|||||||
if first_letter:
|
if first_letter:
|
||||||
data = self._filter_by_first_letter(data, first_letter)
|
data = self._filter_by_first_letter(data, first_letter)
|
||||||
|
|
||||||
|
# Handle name pattern filters
|
||||||
|
name_pattern_include = kwargs.get("name_pattern_include", [])
|
||||||
|
name_pattern_exclude = kwargs.get("name_pattern_exclude", [])
|
||||||
|
name_pattern_use_regex = kwargs.get("name_pattern_use_regex", False)
|
||||||
|
|
||||||
|
if name_pattern_include or name_pattern_exclude:
|
||||||
|
import re
|
||||||
|
|
||||||
|
def matches_pattern(name, pattern, use_regex):
|
||||||
|
"""Check if name matches pattern (regex or substring)"""
|
||||||
|
if not name:
|
||||||
|
return False
|
||||||
|
if use_regex:
|
||||||
|
try:
|
||||||
|
return bool(re.search(pattern, name, re.IGNORECASE))
|
||||||
|
except re.error:
|
||||||
|
# Invalid regex, fall back to substring match
|
||||||
|
return pattern.lower() in name.lower()
|
||||||
|
else:
|
||||||
|
return pattern.lower() in name.lower()
|
||||||
|
|
||||||
|
def matches_any_pattern(name, patterns, use_regex):
|
||||||
|
"""Check if name matches any of the patterns"""
|
||||||
|
if not patterns:
|
||||||
|
return True
|
||||||
|
return any(matches_pattern(name, p, use_regex) for p in patterns)
|
||||||
|
|
||||||
|
filtered = []
|
||||||
|
for lora in data:
|
||||||
|
model_name = lora.get("model_name", "")
|
||||||
|
file_name = lora.get("file_name", "")
|
||||||
|
names_to_check = [n for n in [model_name, file_name] if n]
|
||||||
|
|
||||||
|
# Check exclude patterns first
|
||||||
|
excluded = False
|
||||||
|
if name_pattern_exclude:
|
||||||
|
for name in names_to_check:
|
||||||
|
if matches_any_pattern(
|
||||||
|
name, name_pattern_exclude, name_pattern_use_regex
|
||||||
|
):
|
||||||
|
excluded = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if excluded:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check include patterns
|
||||||
|
if name_pattern_include:
|
||||||
|
included = False
|
||||||
|
for name in names_to_check:
|
||||||
|
if matches_any_pattern(
|
||||||
|
name, name_pattern_include, name_pattern_use_regex
|
||||||
|
):
|
||||||
|
included = True
|
||||||
|
break
|
||||||
|
if not included:
|
||||||
|
continue
|
||||||
|
|
||||||
|
filtered.append(lora)
|
||||||
|
|
||||||
|
data = filtered
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def _filter_by_first_letter(self, data: List[Dict], letter: str) -> List[Dict]:
|
def _filter_by_first_letter(self, data: List[Dict], letter: str) -> List[Dict]:
|
||||||
@@ -368,9 +432,7 @@ class LoraService(BaseModelService):
|
|||||||
rng.uniform(clip_strength_min, clip_strength_max), 2
|
rng.uniform(clip_strength_min, clip_strength_max), 2
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
clip_str = round(
|
clip_str = round(rng.uniform(clip_strength_min, clip_strength_max), 2)
|
||||||
rng.uniform(clip_strength_min, clip_strength_max), 2
|
|
||||||
)
|
|
||||||
|
|
||||||
result_loras.append(
|
result_loras.append(
|
||||||
{
|
{
|
||||||
@@ -485,12 +547,69 @@ class LoraService(BaseModelService):
|
|||||||
if bool(lora.get("license_flags", 127) & (1 << 1))
|
if bool(lora.get("license_flags", 127) & (1 << 1))
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Apply name pattern filters
|
||||||
|
name_patterns = filter_section.get("namePatterns", {})
|
||||||
|
include_patterns = name_patterns.get("include", [])
|
||||||
|
exclude_patterns = name_patterns.get("exclude", [])
|
||||||
|
use_regex = name_patterns.get("useRegex", False)
|
||||||
|
|
||||||
|
if include_patterns or exclude_patterns:
|
||||||
|
import re
|
||||||
|
|
||||||
|
def matches_pattern(name, pattern, use_regex):
|
||||||
|
"""Check if name matches pattern (regex or substring)"""
|
||||||
|
if not name:
|
||||||
|
return False
|
||||||
|
if use_regex:
|
||||||
|
try:
|
||||||
|
return bool(re.search(pattern, name, re.IGNORECASE))
|
||||||
|
except re.error:
|
||||||
|
# Invalid regex, fall back to substring match
|
||||||
|
return pattern.lower() in name.lower()
|
||||||
|
else:
|
||||||
|
return pattern.lower() in name.lower()
|
||||||
|
|
||||||
|
def matches_any_pattern(name, patterns, use_regex):
|
||||||
|
"""Check if name matches any of the patterns"""
|
||||||
|
if not patterns:
|
||||||
|
return True
|
||||||
|
return any(matches_pattern(name, p, use_regex) for p in patterns)
|
||||||
|
|
||||||
|
filtered = []
|
||||||
|
for lora in available_loras:
|
||||||
|
model_name = lora.get("model_name", "")
|
||||||
|
file_name = lora.get("file_name", "")
|
||||||
|
names_to_check = [n for n in [model_name, file_name] if n]
|
||||||
|
|
||||||
|
# Check exclude patterns first
|
||||||
|
excluded = False
|
||||||
|
if exclude_patterns:
|
||||||
|
for name in names_to_check:
|
||||||
|
if matches_any_pattern(name, exclude_patterns, use_regex):
|
||||||
|
excluded = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if excluded:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check include patterns
|
||||||
|
if include_patterns:
|
||||||
|
included = False
|
||||||
|
for name in names_to_check:
|
||||||
|
if matches_any_pattern(name, include_patterns, use_regex):
|
||||||
|
included = True
|
||||||
|
break
|
||||||
|
if not included:
|
||||||
|
continue
|
||||||
|
|
||||||
|
filtered.append(lora)
|
||||||
|
|
||||||
|
available_loras = filtered
|
||||||
|
|
||||||
return available_loras
|
return available_loras
|
||||||
|
|
||||||
async def get_cycler_list(
|
async def get_cycler_list(
|
||||||
self,
|
self, pool_config: Optional[Dict] = None, sort_by: str = "filename"
|
||||||
pool_config: Optional[Dict] = None,
|
|
||||||
sort_by: str = "filename"
|
|
||||||
) -> List[Dict]:
|
) -> List[Dict]:
|
||||||
"""
|
"""
|
||||||
Get filtered and sorted LoRA list for cycling.
|
Get filtered and sorted LoRA list for cycling.
|
||||||
@@ -518,16 +637,16 @@ class LoraService(BaseModelService):
|
|||||||
available_loras,
|
available_loras,
|
||||||
key=lambda x: (
|
key=lambda x: (
|
||||||
(x.get("model_name") or x.get("file_name", "")).lower(),
|
(x.get("model_name") or x.get("file_name", "")).lower(),
|
||||||
x.get("file_path", "").lower()
|
x.get("file_path", "").lower(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
else: # Default to filename
|
else: # Default to filename
|
||||||
available_loras = sorted(
|
available_loras = sorted(
|
||||||
available_loras,
|
available_loras,
|
||||||
key=lambda x: (
|
key=lambda x: (
|
||||||
x.get("file_name", "").lower(),
|
x.get("file_name", "").lower(),
|
||||||
x.get("file_path", "").lower()
|
x.get("file_path", "").lower(),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Return minimal data needed for cycling
|
# Return minimal data needed for cycling
|
||||||
|
|||||||
@@ -369,3 +369,289 @@ async def test_pool_filter_combined_all_filters(lora_service):
|
|||||||
# - tags: tag1 ✓
|
# - tags: tag1 ✓
|
||||||
assert len(filtered) == 1
|
assert len(filtered) == 1
|
||||||
assert filtered[0]["file_name"] == "match_all.safetensors"
|
assert filtered[0]["file_name"] == "match_all.safetensors"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_pool_filter_name_patterns_include_text(lora_service):
|
||||||
|
"""Test filtering by name patterns with text matching (useRegex=False)."""
|
||||||
|
sample_loras = [
|
||||||
|
{
|
||||||
|
"file_name": "character_anime_v1.safetensors",
|
||||||
|
"model_name": "Anime Character",
|
||||||
|
"base_model": "Illustrious",
|
||||||
|
"folder": "",
|
||||||
|
"license_flags": build_license_flags(None),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file_name": "character_realistic_v1.safetensors",
|
||||||
|
"model_name": "Realistic Character",
|
||||||
|
"base_model": "Illustrious",
|
||||||
|
"folder": "",
|
||||||
|
"license_flags": build_license_flags(None),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file_name": "style_watercolor_v1.safetensors",
|
||||||
|
"model_name": "Watercolor Style",
|
||||||
|
"base_model": "Illustrious",
|
||||||
|
"folder": "",
|
||||||
|
"license_flags": build_license_flags(None),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Test include patterns with text matching
|
||||||
|
pool_config = {
|
||||||
|
"baseModels": [],
|
||||||
|
"tags": {"include": [], "exclude": []},
|
||||||
|
"folders": {"include": [], "exclude": []},
|
||||||
|
"license": {"noCreditRequired": False, "allowSelling": False},
|
||||||
|
"namePatterns": {"include": ["character"], "exclude": [], "useRegex": False},
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered = await lora_service._apply_pool_filters(sample_loras, pool_config)
|
||||||
|
assert len(filtered) == 2
|
||||||
|
file_names = {lora["file_name"] for lora in filtered}
|
||||||
|
assert file_names == {
|
||||||
|
"character_anime_v1.safetensors",
|
||||||
|
"character_realistic_v1.safetensors",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_pool_filter_name_patterns_exclude_text(lora_service):
|
||||||
|
"""Test excluding by name patterns with text matching (useRegex=False)."""
|
||||||
|
sample_loras = [
|
||||||
|
{
|
||||||
|
"file_name": "character_anime_v1.safetensors",
|
||||||
|
"model_name": "Anime Character",
|
||||||
|
"base_model": "Illustrious",
|
||||||
|
"folder": "",
|
||||||
|
"license_flags": build_license_flags(None),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file_name": "character_realistic_v1.safetensors",
|
||||||
|
"model_name": "Realistic Character",
|
||||||
|
"base_model": "Illustrious",
|
||||||
|
"folder": "",
|
||||||
|
"license_flags": build_license_flags(None),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file_name": "style_watercolor_v1.safetensors",
|
||||||
|
"model_name": "Watercolor Style",
|
||||||
|
"base_model": "Illustrious",
|
||||||
|
"folder": "",
|
||||||
|
"license_flags": build_license_flags(None),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Test exclude patterns with text matching
|
||||||
|
pool_config = {
|
||||||
|
"baseModels": [],
|
||||||
|
"tags": {"include": [], "exclude": []},
|
||||||
|
"folders": {"include": [], "exclude": []},
|
||||||
|
"license": {"noCreditRequired": False, "allowSelling": False},
|
||||||
|
"namePatterns": {"include": [], "exclude": ["anime"], "useRegex": False},
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered = await lora_service._apply_pool_filters(sample_loras, pool_config)
|
||||||
|
assert len(filtered) == 2
|
||||||
|
file_names = {lora["file_name"] for lora in filtered}
|
||||||
|
assert file_names == {
|
||||||
|
"character_realistic_v1.safetensors",
|
||||||
|
"style_watercolor_v1.safetensors",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_pool_filter_name_patterns_include_regex(lora_service):
|
||||||
|
"""Test filtering by name patterns with regex matching (useRegex=True)."""
|
||||||
|
sample_loras = [
|
||||||
|
{
|
||||||
|
"file_name": "character_anime_v1.safetensors",
|
||||||
|
"model_name": "Anime Character",
|
||||||
|
"base_model": "Illustrious",
|
||||||
|
"folder": "",
|
||||||
|
"license_flags": build_license_flags(None),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file_name": "character_realistic_v1.safetensors",
|
||||||
|
"model_name": "Realistic Character",
|
||||||
|
"base_model": "Illustrious",
|
||||||
|
"folder": "",
|
||||||
|
"license_flags": build_license_flags(None),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file_name": "style_watercolor_v1.safetensors",
|
||||||
|
"model_name": "Watercolor Style",
|
||||||
|
"base_model": "Illustrious",
|
||||||
|
"folder": "",
|
||||||
|
"license_flags": build_license_flags(None),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Test include patterns with regex matching - match files starting with "character_"
|
||||||
|
pool_config = {
|
||||||
|
"baseModels": [],
|
||||||
|
"tags": {"include": [], "exclude": []},
|
||||||
|
"folders": {"include": [], "exclude": []},
|
||||||
|
"license": {"noCreditRequired": False, "allowSelling": False},
|
||||||
|
"namePatterns": {"include": ["^character_"], "exclude": [], "useRegex": True},
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered = await lora_service._apply_pool_filters(sample_loras, pool_config)
|
||||||
|
assert len(filtered) == 2
|
||||||
|
file_names = {lora["file_name"] for lora in filtered}
|
||||||
|
assert file_names == {
|
||||||
|
"character_anime_v1.safetensors",
|
||||||
|
"character_realistic_v1.safetensors",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_pool_filter_name_patterns_exclude_regex(lora_service):
|
||||||
|
"""Test excluding by name patterns with regex matching (useRegex=True)."""
|
||||||
|
sample_loras = [
|
||||||
|
{
|
||||||
|
"file_name": "character_anime_v1.safetensors",
|
||||||
|
"model_name": "Anime Character",
|
||||||
|
"base_model": "Illustrious",
|
||||||
|
"folder": "",
|
||||||
|
"license_flags": build_license_flags(None),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file_name": "character_realistic_v1.safetensors",
|
||||||
|
"model_name": "Realistic Character",
|
||||||
|
"base_model": "Illustrious",
|
||||||
|
"folder": "",
|
||||||
|
"license_flags": build_license_flags(None),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file_name": "style_watercolor_v1.safetensors",
|
||||||
|
"model_name": "Watercolor Style",
|
||||||
|
"base_model": "Illustrious",
|
||||||
|
"folder": "",
|
||||||
|
"license_flags": build_license_flags(None),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Test exclude patterns with regex matching - exclude files ending with "_v1.safetensors"
|
||||||
|
pool_config = {
|
||||||
|
"baseModels": [],
|
||||||
|
"tags": {"include": [], "exclude": []},
|
||||||
|
"folders": {"include": [], "exclude": []},
|
||||||
|
"license": {"noCreditRequired": False, "allowSelling": False},
|
||||||
|
"namePatterns": {
|
||||||
|
"include": [],
|
||||||
|
"exclude": ["_v1\\.safetensors$"],
|
||||||
|
"useRegex": True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered = await lora_service._apply_pool_filters(sample_loras, pool_config)
|
||||||
|
assert len(filtered) == 0 # All files match the exclude pattern
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_pool_filter_name_patterns_combined(lora_service):
|
||||||
|
"""Test combining include and exclude name patterns."""
|
||||||
|
sample_loras = [
|
||||||
|
{
|
||||||
|
"file_name": "character_anime_v1.safetensors",
|
||||||
|
"model_name": "Anime Character",
|
||||||
|
"base_model": "Illustrious",
|
||||||
|
"folder": "",
|
||||||
|
"license_flags": build_license_flags(None),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file_name": "character_realistic_v1.safetensors",
|
||||||
|
"model_name": "Realistic Character",
|
||||||
|
"base_model": "Illustrious",
|
||||||
|
"folder": "",
|
||||||
|
"license_flags": build_license_flags(None),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file_name": "style_watercolor_v1.safetensors",
|
||||||
|
"model_name": "Watercolor Style",
|
||||||
|
"base_model": "Illustrious",
|
||||||
|
"folder": "",
|
||||||
|
"license_flags": build_license_flags(None),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Test include "character" but exclude "anime"
|
||||||
|
pool_config = {
|
||||||
|
"baseModels": [],
|
||||||
|
"tags": {"include": [], "exclude": []},
|
||||||
|
"folders": {"include": [], "exclude": []},
|
||||||
|
"license": {"noCreditRequired": False, "allowSelling": False},
|
||||||
|
"namePatterns": {
|
||||||
|
"include": ["character"],
|
||||||
|
"exclude": ["anime"],
|
||||||
|
"useRegex": False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered = await lora_service._apply_pool_filters(sample_loras, pool_config)
|
||||||
|
assert len(filtered) == 1
|
||||||
|
assert filtered[0]["file_name"] == "character_realistic_v1.safetensors"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_pool_filter_name_patterns_model_name_fallback(lora_service):
|
||||||
|
"""Test that name pattern filtering falls back to model_name when file_name doesn't match."""
|
||||||
|
sample_loras = [
|
||||||
|
{
|
||||||
|
"file_name": "abc123.safetensors",
|
||||||
|
"model_name": "Super Anime Character",
|
||||||
|
"base_model": "Illustrious",
|
||||||
|
"folder": "",
|
||||||
|
"license_flags": build_license_flags(None),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file_name": "def456.safetensors",
|
||||||
|
"model_name": "Realistic Portrait",
|
||||||
|
"base_model": "Illustrious",
|
||||||
|
"folder": "",
|
||||||
|
"license_flags": build_license_flags(None),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Should match model_name even if file_name doesn't contain the pattern
|
||||||
|
pool_config = {
|
||||||
|
"baseModels": [],
|
||||||
|
"tags": {"include": [], "exclude": []},
|
||||||
|
"folders": {"include": [], "exclude": []},
|
||||||
|
"license": {"noCreditRequired": False, "allowSelling": False},
|
||||||
|
"namePatterns": {"include": ["anime"], "exclude": [], "useRegex": False},
|
||||||
|
}
|
||||||
|
|
||||||
|
filtered = await lora_service._apply_pool_filters(sample_loras, pool_config)
|
||||||
|
assert len(filtered) == 1
|
||||||
|
assert filtered[0]["file_name"] == "abc123.safetensors"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_pool_filter_name_patterns_invalid_regex(lora_service):
|
||||||
|
"""Test that invalid regex falls back to substring matching."""
|
||||||
|
sample_loras = [
|
||||||
|
{
|
||||||
|
"file_name": "character_anime[test]_v1.safetensors",
|
||||||
|
"model_name": "Anime Character",
|
||||||
|
"base_model": "Illustrious",
|
||||||
|
"folder": "",
|
||||||
|
"license_flags": build_license_flags(None),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Invalid regex pattern (unclosed character class) should fall back to substring matching
|
||||||
|
# The pattern "anime[" is invalid regex but valid substring - it exists in the filename
|
||||||
|
pool_config = {
|
||||||
|
"baseModels": [],
|
||||||
|
"tags": {"include": [], "exclude": []},
|
||||||
|
"folders": {"include": [], "exclude": []},
|
||||||
|
"license": {"noCreditRequired": False, "allowSelling": False},
|
||||||
|
"namePatterns": {"include": ["anime["], "exclude": [], "useRegex": True},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Should not crash and should match using substring fallback
|
||||||
|
filtered = await lora_service._apply_pool_filters(sample_loras, pool_config)
|
||||||
|
assert len(filtered) == 1 # Substring match works even with invalid regex
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
:exclude-tags="state.excludeTags.value"
|
:exclude-tags="state.excludeTags.value"
|
||||||
:include-folders="state.includeFolders.value"
|
:include-folders="state.includeFolders.value"
|
||||||
:exclude-folders="state.excludeFolders.value"
|
:exclude-folders="state.excludeFolders.value"
|
||||||
|
:include-patterns="state.includePatterns.value"
|
||||||
|
:exclude-patterns="state.excludePatterns.value"
|
||||||
|
:use-regex="state.useRegex.value"
|
||||||
:no-credit-required="state.noCreditRequired.value"
|
:no-credit-required="state.noCreditRequired.value"
|
||||||
:allow-selling="state.allowSelling.value"
|
:allow-selling="state.allowSelling.value"
|
||||||
:preview-items="state.previewItems.value"
|
:preview-items="state.previewItems.value"
|
||||||
@@ -16,6 +19,9 @@
|
|||||||
@open-modal="openModal"
|
@open-modal="openModal"
|
||||||
@update:include-folders="state.includeFolders.value = $event"
|
@update:include-folders="state.includeFolders.value = $event"
|
||||||
@update:exclude-folders="state.excludeFolders.value = $event"
|
@update:exclude-folders="state.excludeFolders.value = $event"
|
||||||
|
@update:include-patterns="state.includePatterns.value = $event"
|
||||||
|
@update:exclude-patterns="state.excludePatterns.value = $event"
|
||||||
|
@update:use-regex="state.useRegex.value = $event"
|
||||||
@update:no-credit-required="state.noCreditRequired.value = $event"
|
@update:no-credit-required="state.noCreditRequired.value = $event"
|
||||||
@update:allow-selling="state.allowSelling.value = $event"
|
@update:allow-selling="state.allowSelling.value = $event"
|
||||||
@refresh="state.refreshPreview"
|
@refresh="state.refreshPreview"
|
||||||
|
|||||||
@@ -24,6 +24,15 @@
|
|||||||
@edit-exclude="$emit('open-modal', 'excludeFolders')"
|
@edit-exclude="$emit('open-modal', 'excludeFolders')"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<NamePatternsSection
|
||||||
|
:include-patterns="includePatterns"
|
||||||
|
:exclude-patterns="excludePatterns"
|
||||||
|
:use-regex="useRegex"
|
||||||
|
@update:include-patterns="$emit('update:includePatterns', $event)"
|
||||||
|
@update:exclude-patterns="$emit('update:excludePatterns', $event)"
|
||||||
|
@update:use-regex="$emit('update:useRegex', $event)"
|
||||||
|
/>
|
||||||
|
|
||||||
<LicenseSection
|
<LicenseSection
|
||||||
:no-credit-required="noCreditRequired"
|
:no-credit-required="noCreditRequired"
|
||||||
:allow-selling="allowSelling"
|
:allow-selling="allowSelling"
|
||||||
@@ -46,6 +55,7 @@
|
|||||||
import BaseModelSection from './sections/BaseModelSection.vue'
|
import BaseModelSection from './sections/BaseModelSection.vue'
|
||||||
import TagsSection from './sections/TagsSection.vue'
|
import TagsSection from './sections/TagsSection.vue'
|
||||||
import FoldersSection from './sections/FoldersSection.vue'
|
import FoldersSection from './sections/FoldersSection.vue'
|
||||||
|
import NamePatternsSection from './sections/NamePatternsSection.vue'
|
||||||
import LicenseSection from './sections/LicenseSection.vue'
|
import LicenseSection from './sections/LicenseSection.vue'
|
||||||
import LoraPoolPreview from './LoraPoolPreview.vue'
|
import LoraPoolPreview from './LoraPoolPreview.vue'
|
||||||
import type { BaseModelOption, LoraItem } from '../../composables/types'
|
import type { BaseModelOption, LoraItem } from '../../composables/types'
|
||||||
@@ -61,6 +71,10 @@ defineProps<{
|
|||||||
// Folders
|
// Folders
|
||||||
includeFolders: string[]
|
includeFolders: string[]
|
||||||
excludeFolders: string[]
|
excludeFolders: string[]
|
||||||
|
// Name patterns
|
||||||
|
includePatterns: string[]
|
||||||
|
excludePatterns: string[]
|
||||||
|
useRegex: boolean
|
||||||
// License
|
// License
|
||||||
noCreditRequired: boolean
|
noCreditRequired: boolean
|
||||||
allowSelling: boolean
|
allowSelling: boolean
|
||||||
@@ -74,6 +88,9 @@ defineEmits<{
|
|||||||
'open-modal': [modal: ModalType]
|
'open-modal': [modal: ModalType]
|
||||||
'update:includeFolders': [value: string[]]
|
'update:includeFolders': [value: string[]]
|
||||||
'update:excludeFolders': [value: string[]]
|
'update:excludeFolders': [value: string[]]
|
||||||
|
'update:includePatterns': [value: string[]]
|
||||||
|
'update:excludePatterns': [value: string[]]
|
||||||
|
'update:useRegex': [value: boolean]
|
||||||
'update:noCreditRequired': [value: boolean]
|
'update:noCreditRequired': [value: boolean]
|
||||||
'update:allowSelling': [value: boolean]
|
'update:allowSelling': [value: boolean]
|
||||||
refresh: []
|
refresh: []
|
||||||
|
|||||||
@@ -0,0 +1,255 @@
|
|||||||
|
<template>
|
||||||
|
<div class="section">
|
||||||
|
<div class="section__header">
|
||||||
|
<span class="section__title">NAME PATTERNS</span>
|
||||||
|
<label class="section__toggle">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:checked="useRegex"
|
||||||
|
@change="$emit('update:useRegex', ($event.target as HTMLInputElement).checked)"
|
||||||
|
/>
|
||||||
|
<span class="section__toggle-label">Use Regex</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="section__columns">
|
||||||
|
<!-- Include column -->
|
||||||
|
<div class="section__column">
|
||||||
|
<div class="section__column-header">
|
||||||
|
<span class="section__column-title section__column-title--include">INCLUDE</span>
|
||||||
|
</div>
|
||||||
|
<div class="section__input-wrapper">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
v-model="includeInput"
|
||||||
|
:placeholder="useRegex ? 'Add regex pattern...' : 'Add text pattern...'"
|
||||||
|
class="section__input"
|
||||||
|
@keydown.enter="addInclude"
|
||||||
|
/>
|
||||||
|
<button type="button" class="section__add-btn" @click="addInclude">+</button>
|
||||||
|
</div>
|
||||||
|
<div class="section__patterns">
|
||||||
|
<FilterChip
|
||||||
|
v-for="pattern in includePatterns"
|
||||||
|
:key="pattern"
|
||||||
|
:label="pattern"
|
||||||
|
variant="include"
|
||||||
|
removable
|
||||||
|
@remove="removeInclude(pattern)"
|
||||||
|
/>
|
||||||
|
<div v-if="includePatterns.length === 0" class="section__empty">
|
||||||
|
{{ useRegex ? 'No regex patterns' : 'No text patterns' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Exclude column -->
|
||||||
|
<div class="section__column">
|
||||||
|
<div class="section__column-header">
|
||||||
|
<span class="section__column-title section__column-title--exclude">EXCLUDE</span>
|
||||||
|
</div>
|
||||||
|
<div class="section__input-wrapper">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
v-model="excludeInput"
|
||||||
|
:placeholder="useRegex ? 'Add regex pattern...' : 'Add text pattern...'"
|
||||||
|
class="section__input"
|
||||||
|
@keydown.enter="addExclude"
|
||||||
|
/>
|
||||||
|
<button type="button" class="section__add-btn" @click="addExclude">+</button>
|
||||||
|
</div>
|
||||||
|
<div class="section__patterns">
|
||||||
|
<FilterChip
|
||||||
|
v-for="pattern in excludePatterns"
|
||||||
|
:key="pattern"
|
||||||
|
:label="pattern"
|
||||||
|
variant="exclude"
|
||||||
|
removable
|
||||||
|
@remove="removeExclude(pattern)"
|
||||||
|
/>
|
||||||
|
<div v-if="excludePatterns.length === 0" class="section__empty">
|
||||||
|
{{ useRegex ? 'No regex patterns' : 'No text patterns' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import FilterChip from '../shared/FilterChip.vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
includePatterns: string[]
|
||||||
|
excludePatterns: string[]
|
||||||
|
useRegex: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
'update:includePatterns': [value: string[]]
|
||||||
|
'update:excludePatterns': [value: string[]]
|
||||||
|
'update:useRegex': [value: boolean]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const includeInput = ref('')
|
||||||
|
const excludeInput = ref('')
|
||||||
|
|
||||||
|
const addInclude = () => {
|
||||||
|
const pattern = includeInput.value.trim()
|
||||||
|
if (pattern && !props.includePatterns.includes(pattern)) {
|
||||||
|
emit('update:includePatterns', [...props.includePatterns, pattern])
|
||||||
|
includeInput.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const addExclude = () => {
|
||||||
|
const pattern = excludeInput.value.trim()
|
||||||
|
if (pattern && !props.excludePatterns.includes(pattern)) {
|
||||||
|
emit('update:excludePatterns', [...props.excludePatterns, pattern])
|
||||||
|
excludeInput.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeInclude = (pattern: string) => {
|
||||||
|
emit('update:includePatterns', props.includePatterns.filter(p => p !== pattern))
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeExclude = (pattern: string) => {
|
||||||
|
emit('update:excludePatterns', props.excludePatterns.filter(p => p !== pattern))
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.section {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__title {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
color: var(--fg-color, #fff);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--fg-color, #fff);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__toggle input[type="checkbox"] {
|
||||||
|
margin: 0;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__toggle-label {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__columns {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__column {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__column-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__column-title {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__column-title--include {
|
||||||
|
color: #4299e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__column-title--exclude {
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__input-wrapper {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__input {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 6px 8px;
|
||||||
|
background: var(--comfy-input-bg, #333);
|
||||||
|
border: 1px solid var(--comfy-input-border, #444);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--fg-color, #fff);
|
||||||
|
font-size: 12px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__input:focus {
|
||||||
|
border-color: #4299e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__add-btn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: var(--comfy-input-bg, #333);
|
||||||
|
border: 1px solid var(--comfy-input-border, #444);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--fg-color, #fff);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__add-btn:hover {
|
||||||
|
background: var(--comfy-input-bg-hover, #444);
|
||||||
|
border-color: #4299e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__patterns {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
min-height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section__empty {
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--fg-color, #fff);
|
||||||
|
opacity: 0.3;
|
||||||
|
font-style: italic;
|
||||||
|
min-height: 22px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -10,6 +10,11 @@ export interface LoraPoolConfig {
|
|||||||
noCreditRequired: boolean
|
noCreditRequired: boolean
|
||||||
allowSelling: boolean
|
allowSelling: boolean
|
||||||
}
|
}
|
||||||
|
namePatterns: {
|
||||||
|
include: string[]
|
||||||
|
exclude: string[]
|
||||||
|
useRegex: boolean
|
||||||
|
}
|
||||||
includeEmptyLora?: boolean // Optional, deprecated (moved to Cycler)
|
includeEmptyLora?: boolean // Optional, deprecated (moved to Cycler)
|
||||||
}
|
}
|
||||||
preview: { matchCount: number; lastUpdated: number }
|
preview: { matchCount: number; lastUpdated: number }
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ export function useLoraPoolApi() {
|
|||||||
foldersExclude?: string[]
|
foldersExclude?: string[]
|
||||||
noCreditRequired?: boolean
|
noCreditRequired?: boolean
|
||||||
allowSelling?: boolean
|
allowSelling?: boolean
|
||||||
|
namePatternsInclude?: string[]
|
||||||
|
namePatternsExclude?: string[]
|
||||||
|
namePatternsUseRegex?: boolean
|
||||||
page?: number
|
page?: number
|
||||||
pageSize?: number
|
pageSize?: number
|
||||||
}
|
}
|
||||||
@@ -92,6 +95,13 @@ export function useLoraPoolApi() {
|
|||||||
urlParams.set('allow_selling_generated_content', String(params.allowSelling))
|
urlParams.set('allow_selling_generated_content', String(params.allowSelling))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name pattern filters
|
||||||
|
params.namePatternsInclude?.forEach(pattern => urlParams.append('name_pattern_include', pattern))
|
||||||
|
params.namePatternsExclude?.forEach(pattern => urlParams.append('name_pattern_exclude', pattern))
|
||||||
|
if (params.namePatternsUseRegex !== undefined) {
|
||||||
|
urlParams.set('name_pattern_use_regex', String(params.namePatternsUseRegex))
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetch(`/api/lm/loras/list?${urlParams}`)
|
const response = await fetch(`/api/lm/loras/list?${urlParams}`)
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ export function useLoraPoolState(widget: ComponentWidget<LoraPoolConfig>) {
|
|||||||
const excludeFolders = ref<string[]>([])
|
const excludeFolders = ref<string[]>([])
|
||||||
const noCreditRequired = ref(false)
|
const noCreditRequired = ref(false)
|
||||||
const allowSelling = ref(false)
|
const allowSelling = ref(false)
|
||||||
|
const includePatterns = ref<string[]>([])
|
||||||
|
const excludePatterns = ref<string[]>([])
|
||||||
|
const useRegex = ref(false)
|
||||||
|
|
||||||
// Available options from API
|
// Available options from API
|
||||||
const availableBaseModels = ref<BaseModelOption[]>([])
|
const availableBaseModels = ref<BaseModelOption[]>([])
|
||||||
@@ -52,6 +55,11 @@ export function useLoraPoolState(widget: ComponentWidget<LoraPoolConfig>) {
|
|||||||
license: {
|
license: {
|
||||||
noCreditRequired: noCreditRequired.value,
|
noCreditRequired: noCreditRequired.value,
|
||||||
allowSelling: allowSelling.value
|
allowSelling: allowSelling.value
|
||||||
|
},
|
||||||
|
namePatterns: {
|
||||||
|
include: includePatterns.value,
|
||||||
|
exclude: excludePatterns.value,
|
||||||
|
useRegex: useRegex.value
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
preview: {
|
preview: {
|
||||||
@@ -94,6 +102,9 @@ export function useLoraPoolState(widget: ComponentWidget<LoraPoolConfig>) {
|
|||||||
updateIfChanged(excludeFolders, filters.folders?.exclude || [])
|
updateIfChanged(excludeFolders, filters.folders?.exclude || [])
|
||||||
updateIfChanged(noCreditRequired, filters.license?.noCreditRequired ?? false)
|
updateIfChanged(noCreditRequired, filters.license?.noCreditRequired ?? false)
|
||||||
updateIfChanged(allowSelling, filters.license?.allowSelling ?? false)
|
updateIfChanged(allowSelling, filters.license?.allowSelling ?? false)
|
||||||
|
updateIfChanged(includePatterns, filters.namePatterns?.include || [])
|
||||||
|
updateIfChanged(excludePatterns, filters.namePatterns?.exclude || [])
|
||||||
|
updateIfChanged(useRegex, filters.namePatterns?.useRegex ?? false)
|
||||||
|
|
||||||
// matchCount doesn't trigger watchers, so direct assignment is fine
|
// matchCount doesn't trigger watchers, so direct assignment is fine
|
||||||
matchCount.value = preview?.matchCount || 0
|
matchCount.value = preview?.matchCount || 0
|
||||||
@@ -125,6 +136,9 @@ export function useLoraPoolState(widget: ComponentWidget<LoraPoolConfig>) {
|
|||||||
foldersExclude: excludeFolders.value,
|
foldersExclude: excludeFolders.value,
|
||||||
noCreditRequired: noCreditRequired.value || undefined,
|
noCreditRequired: noCreditRequired.value || undefined,
|
||||||
allowSelling: allowSelling.value || undefined,
|
allowSelling: allowSelling.value || undefined,
|
||||||
|
namePatternsInclude: includePatterns.value,
|
||||||
|
namePatternsExclude: excludePatterns.value,
|
||||||
|
namePatternsUseRegex: useRegex.value,
|
||||||
pageSize: 6
|
pageSize: 6
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -150,7 +164,10 @@ export function useLoraPoolState(widget: ComponentWidget<LoraPoolConfig>) {
|
|||||||
includeFolders,
|
includeFolders,
|
||||||
excludeFolders,
|
excludeFolders,
|
||||||
noCreditRequired,
|
noCreditRequired,
|
||||||
allowSelling
|
allowSelling,
|
||||||
|
includePatterns,
|
||||||
|
excludePatterns,
|
||||||
|
useRegex
|
||||||
], onFilterChange, { deep: true })
|
], onFilterChange, { deep: true })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -162,6 +179,9 @@ export function useLoraPoolState(widget: ComponentWidget<LoraPoolConfig>) {
|
|||||||
excludeFolders,
|
excludeFolders,
|
||||||
noCreditRequired,
|
noCreditRequired,
|
||||||
allowSelling,
|
allowSelling,
|
||||||
|
includePatterns,
|
||||||
|
excludePatterns,
|
||||||
|
useRegex,
|
||||||
|
|
||||||
// Available options
|
// Available options
|
||||||
availableBaseModels,
|
availableBaseModels,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
} from './mode-change-handler'
|
} from './mode-change-handler'
|
||||||
|
|
||||||
const LORA_POOL_WIDGET_MIN_WIDTH = 500
|
const LORA_POOL_WIDGET_MIN_WIDTH = 500
|
||||||
const LORA_POOL_WIDGET_MIN_HEIGHT = 400
|
const LORA_POOL_WIDGET_MIN_HEIGHT = 520
|
||||||
const LORA_RANDOMIZER_WIDGET_MIN_WIDTH = 500
|
const LORA_RANDOMIZER_WIDGET_MIN_WIDTH = 500
|
||||||
const LORA_RANDOMIZER_WIDGET_MIN_HEIGHT = 448
|
const LORA_RANDOMIZER_WIDGET_MIN_HEIGHT = 448
|
||||||
const LORA_RANDOMIZER_WIDGET_MAX_HEIGHT = LORA_RANDOMIZER_WIDGET_MIN_HEIGHT
|
const LORA_RANDOMIZER_WIDGET_MAX_HEIGHT = LORA_RANDOMIZER_WIDGET_MIN_HEIGHT
|
||||||
|
|||||||
@@ -283,6 +283,121 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.section[data-v-9995b5ed] {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.section__header[data-v-9995b5ed] {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.section__title[data-v-9995b5ed] {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
color: var(--fg-color, #fff);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
.section__toggle[data-v-9995b5ed] {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--fg-color, #fff);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
.section__toggle input[type="checkbox"][data-v-9995b5ed] {
|
||||||
|
margin: 0;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.section__toggle-label[data-v-9995b5ed] {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.section__columns[data-v-9995b5ed] {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.section__column[data-v-9995b5ed] {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.section__column-header[data-v-9995b5ed] {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.section__column-title[data-v-9995b5ed] {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
}
|
||||||
|
.section__column-title--include[data-v-9995b5ed] {
|
||||||
|
color: #4299e1;
|
||||||
|
}
|
||||||
|
.section__column-title--exclude[data-v-9995b5ed] {
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
.section__input-wrapper[data-v-9995b5ed] {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.section__input[data-v-9995b5ed] {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 6px 8px;
|
||||||
|
background: var(--comfy-input-bg, #333);
|
||||||
|
border: 1px solid var(--comfy-input-border, #444);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--fg-color, #fff);
|
||||||
|
font-size: 12px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.section__input[data-v-9995b5ed]:focus {
|
||||||
|
border-color: #4299e1;
|
||||||
|
}
|
||||||
|
.section__add-btn[data-v-9995b5ed] {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: var(--comfy-input-bg, #333);
|
||||||
|
border: 1px solid var(--comfy-input-border, #444);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--fg-color, #fff);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
.section__add-btn[data-v-9995b5ed]:hover {
|
||||||
|
background: var(--comfy-input-bg-hover, #444);
|
||||||
|
border-color: #4299e1;
|
||||||
|
}
|
||||||
|
.section__patterns[data-v-9995b5ed] {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
min-height: 22px;
|
||||||
|
}
|
||||||
|
.section__empty[data-v-9995b5ed] {
|
||||||
|
font-size: 10px;
|
||||||
|
color: var(--fg-color, #fff);
|
||||||
|
opacity: 0.3;
|
||||||
|
font-style: italic;
|
||||||
|
min-height: 22px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.section[data-v-dea4adf6] {
|
.section[data-v-dea4adf6] {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
@@ -483,12 +598,12 @@ to { transform: rotate(360deg);
|
|||||||
transform: translateY(4px);
|
transform: translateY(4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.summary-view[data-v-328e7526] {
|
.summary-view[data-v-83235a00] {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.summary-view__filters[data-v-328e7526] {
|
.summary-view__filters[data-v-83235a00] {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
@@ -979,7 +1094,7 @@ to { transform: rotate(360deg);
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lora-pool-widget[data-v-4456abba] {
|
.lora-pool-widget[data-v-0bbd50ea] {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
background: rgba(40, 44, 52, 0.6);
|
background: rgba(40, 44, 52, 0.6);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -10505,12 +10620,12 @@ var PrimeVue = {
|
|||||||
setup(app2, configOptions);
|
setup(app2, configOptions);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const _hoisted_1$m = { class: "filter-chip__text" };
|
const _hoisted_1$n = { class: "filter-chip__text" };
|
||||||
const _hoisted_2$i = {
|
const _hoisted_2$j = {
|
||||||
key: 0,
|
key: 0,
|
||||||
class: "filter-chip__count"
|
class: "filter-chip__count"
|
||||||
};
|
};
|
||||||
const _sfc_main$n = /* @__PURE__ */ defineComponent({
|
const _sfc_main$o = /* @__PURE__ */ defineComponent({
|
||||||
__name: "FilterChip",
|
__name: "FilterChip",
|
||||||
props: {
|
props: {
|
||||||
label: {},
|
label: {},
|
||||||
@@ -10528,8 +10643,8 @@ const _sfc_main$n = /* @__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$m, toDisplayString(__props.label), 1),
|
createBaseVNode("span", _hoisted_1$n, toDisplayString(__props.label), 1),
|
||||||
__props.count !== void 0 ? (openBlock(), createElementBlock("span", _hoisted_2$i, "(" + 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,
|
||||||
class: "filter-chip__remove",
|
class: "filter-chip__remove",
|
||||||
@@ -10547,8 +10662,8 @@ const _export_sfc = (sfc, props) => {
|
|||||||
}
|
}
|
||||||
return target;
|
return target;
|
||||||
};
|
};
|
||||||
const FilterChip = /* @__PURE__ */ _export_sfc(_sfc_main$n, [["__scopeId", "data-v-7e36267d"]]);
|
const FilterChip = /* @__PURE__ */ _export_sfc(_sfc_main$o, [["__scopeId", "data-v-7e36267d"]]);
|
||||||
const _sfc_main$m = /* @__PURE__ */ defineComponent({
|
const _sfc_main$n = /* @__PURE__ */ defineComponent({
|
||||||
__name: "EditButton",
|
__name: "EditButton",
|
||||||
emits: ["click"],
|
emits: ["click"],
|
||||||
setup(__props) {
|
setup(__props) {
|
||||||
@@ -10570,19 +10685,19 @@ const _sfc_main$m = /* @__PURE__ */ defineComponent({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const EditButton = /* @__PURE__ */ _export_sfc(_sfc_main$m, [["__scopeId", "data-v-8da8aa4b"]]);
|
const EditButton = /* @__PURE__ */ _export_sfc(_sfc_main$n, [["__scopeId", "data-v-8da8aa4b"]]);
|
||||||
const _hoisted_1$l = { class: "section" };
|
const _hoisted_1$m = { class: "section" };
|
||||||
const _hoisted_2$h = { class: "section__header" };
|
const _hoisted_2$i = { class: "section__header" };
|
||||||
const _hoisted_3$f = { class: "section__content" };
|
const _hoisted_3$g = { class: "section__content" };
|
||||||
const _hoisted_4$d = {
|
const _hoisted_4$e = {
|
||||||
key: 0,
|
key: 0,
|
||||||
class: "section__placeholder"
|
class: "section__placeholder"
|
||||||
};
|
};
|
||||||
const _hoisted_5$b = {
|
const _hoisted_5$c = {
|
||||||
key: 1,
|
key: 1,
|
||||||
class: "section__chips"
|
class: "section__chips"
|
||||||
};
|
};
|
||||||
const _sfc_main$l = /* @__PURE__ */ defineComponent({
|
const _sfc_main$m = /* @__PURE__ */ defineComponent({
|
||||||
__name: "BaseModelSection",
|
__name: "BaseModelSection",
|
||||||
props: {
|
props: {
|
||||||
selected: {},
|
selected: {},
|
||||||
@@ -10596,15 +10711,15 @@ const _sfc_main$l = /* @__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$l, [
|
return openBlock(), createElementBlock("div", _hoisted_1$m, [
|
||||||
createBaseVNode("div", _hoisted_2$h, [
|
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, {
|
||||||
onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("edit"))
|
onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("edit"))
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
createBaseVNode("div", _hoisted_3$f, [
|
createBaseVNode("div", _hoisted_3$g, [
|
||||||
__props.selected.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_4$d, " All models ")) : (openBlock(), createElementBlock("div", _hoisted_5$b, [
|
__props.selected.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_4$e, " All models ")) : (openBlock(), createElementBlock("div", _hoisted_5$c, [
|
||||||
(openBlock(true), createElementBlock(Fragment, null, renderList(__props.selected, (name) => {
|
(openBlock(true), createElementBlock(Fragment, null, renderList(__props.selected, (name) => {
|
||||||
return openBlock(), createBlock(FilterChip, {
|
return openBlock(), createBlock(FilterChip, {
|
||||||
key: name,
|
key: name,
|
||||||
@@ -10619,32 +10734,32 @@ const _sfc_main$l = /* @__PURE__ */ defineComponent({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const BaseModelSection = /* @__PURE__ */ _export_sfc(_sfc_main$l, [["__scopeId", "data-v-12f059e2"]]);
|
const BaseModelSection = /* @__PURE__ */ _export_sfc(_sfc_main$m, [["__scopeId", "data-v-12f059e2"]]);
|
||||||
const _hoisted_1$k = { class: "section" };
|
const _hoisted_1$l = { class: "section" };
|
||||||
const _hoisted_2$g = { class: "section__columns" };
|
const _hoisted_2$h = { class: "section__columns" };
|
||||||
const _hoisted_3$e = { class: "section__column" };
|
const _hoisted_3$f = { class: "section__column" };
|
||||||
const _hoisted_4$c = { class: "section__column-header" };
|
const _hoisted_4$d = { class: "section__column-header" };
|
||||||
const _hoisted_5$a = { class: "section__column-content" };
|
const _hoisted_5$b = { class: "section__column-content" };
|
||||||
const _hoisted_6$a = {
|
const _hoisted_6$b = {
|
||||||
key: 0,
|
key: 0,
|
||||||
class: "section__empty"
|
class: "section__empty"
|
||||||
};
|
};
|
||||||
const _hoisted_7$8 = {
|
const _hoisted_7$9 = {
|
||||||
key: 1,
|
key: 1,
|
||||||
class: "section__chips"
|
class: "section__chips"
|
||||||
};
|
};
|
||||||
const _hoisted_8$6 = { class: "section__column" };
|
const _hoisted_8$7 = { class: "section__column" };
|
||||||
const _hoisted_9$4 = { class: "section__column-header" };
|
const _hoisted_9$5 = { class: "section__column-header" };
|
||||||
const _hoisted_10$4 = { class: "section__column-content" };
|
const _hoisted_10$5 = { class: "section__column-content" };
|
||||||
const _hoisted_11$3 = {
|
const _hoisted_11$4 = {
|
||||||
key: 0,
|
key: 0,
|
||||||
class: "section__empty"
|
class: "section__empty"
|
||||||
};
|
};
|
||||||
const _hoisted_12$3 = {
|
const _hoisted_12$4 = {
|
||||||
key: 1,
|
key: 1,
|
||||||
class: "section__chips"
|
class: "section__chips"
|
||||||
};
|
};
|
||||||
const _sfc_main$k = /* @__PURE__ */ defineComponent({
|
const _sfc_main$l = /* @__PURE__ */ defineComponent({
|
||||||
__name: "TagsSection",
|
__name: "TagsSection",
|
||||||
props: {
|
props: {
|
||||||
includeTags: {},
|
includeTags: {},
|
||||||
@@ -10653,20 +10768,20 @@ const _sfc_main$k = /* @__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$k, [
|
return openBlock(), createElementBlock("div", _hoisted_1$l, [
|
||||||
_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)),
|
||||||
createBaseVNode("div", _hoisted_2$g, [
|
createBaseVNode("div", _hoisted_2$h, [
|
||||||
createBaseVNode("div", _hoisted_3$e, [
|
createBaseVNode("div", _hoisted_3$f, [
|
||||||
createBaseVNode("div", _hoisted_4$c, [
|
createBaseVNode("div", _hoisted_4$d, [
|
||||||
_cache[2] || (_cache[2] = createBaseVNode("span", { class: "section__column-title section__column-title--include" }, "INCLUDE", -1)),
|
_cache[2] || (_cache[2] = createBaseVNode("span", { class: "section__column-title section__column-title--include" }, "INCLUDE", -1)),
|
||||||
createVNode(EditButton, {
|
createVNode(EditButton, {
|
||||||
onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("edit-include"))
|
onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("edit-include"))
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
createBaseVNode("div", _hoisted_5$a, [
|
createBaseVNode("div", _hoisted_5$b, [
|
||||||
__props.includeTags.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_6$a, " None ")) : (openBlock(), createElementBlock("div", _hoisted_7$8, [
|
__props.includeTags.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_6$b, " None ")) : (openBlock(), createElementBlock("div", _hoisted_7$9, [
|
||||||
(openBlock(true), createElementBlock(Fragment, null, renderList(__props.includeTags, (tag) => {
|
(openBlock(true), createElementBlock(Fragment, null, renderList(__props.includeTags, (tag) => {
|
||||||
return openBlock(), createBlock(FilterChip, {
|
return openBlock(), createBlock(FilterChip, {
|
||||||
key: tag,
|
key: tag,
|
||||||
@@ -10677,15 +10792,15 @@ const _sfc_main$k = /* @__PURE__ */ defineComponent({
|
|||||||
]))
|
]))
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
createBaseVNode("div", _hoisted_8$6, [
|
createBaseVNode("div", _hoisted_8$7, [
|
||||||
createBaseVNode("div", _hoisted_9$4, [
|
createBaseVNode("div", _hoisted_9$5, [
|
||||||
_cache[3] || (_cache[3] = createBaseVNode("span", { class: "section__column-title section__column-title--exclude" }, "EXCLUDE", -1)),
|
_cache[3] || (_cache[3] = createBaseVNode("span", { class: "section__column-title section__column-title--exclude" }, "EXCLUDE", -1)),
|
||||||
createVNode(EditButton, {
|
createVNode(EditButton, {
|
||||||
onClick: _cache[1] || (_cache[1] = ($event) => _ctx.$emit("edit-exclude"))
|
onClick: _cache[1] || (_cache[1] = ($event) => _ctx.$emit("edit-exclude"))
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
createBaseVNode("div", _hoisted_10$4, [
|
createBaseVNode("div", _hoisted_10$5, [
|
||||||
__props.excludeTags.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_11$3, " None ")) : (openBlock(), createElementBlock("div", _hoisted_12$3, [
|
__props.excludeTags.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_11$4, " None ")) : (openBlock(), createElementBlock("div", _hoisted_12$4, [
|
||||||
(openBlock(true), createElementBlock(Fragment, null, renderList(__props.excludeTags, (tag) => {
|
(openBlock(true), createElementBlock(Fragment, null, renderList(__props.excludeTags, (tag) => {
|
||||||
return openBlock(), createBlock(FilterChip, {
|
return openBlock(), createBlock(FilterChip, {
|
||||||
key: tag,
|
key: tag,
|
||||||
@@ -10701,32 +10816,32 @@ const _sfc_main$k = /* @__PURE__ */ defineComponent({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const TagsSection = /* @__PURE__ */ _export_sfc(_sfc_main$k, [["__scopeId", "data-v-b869b780"]]);
|
const TagsSection = /* @__PURE__ */ _export_sfc(_sfc_main$l, [["__scopeId", "data-v-b869b780"]]);
|
||||||
const _hoisted_1$j = { class: "section" };
|
const _hoisted_1$k = { class: "section" };
|
||||||
const _hoisted_2$f = { class: "section__columns" };
|
const _hoisted_2$g = { class: "section__columns" };
|
||||||
const _hoisted_3$d = { class: "section__column" };
|
const _hoisted_3$e = { class: "section__column" };
|
||||||
const _hoisted_4$b = { class: "section__column-header" };
|
const _hoisted_4$c = { class: "section__column-header" };
|
||||||
const _hoisted_5$9 = { class: "section__content" };
|
const _hoisted_5$a = { class: "section__content" };
|
||||||
const _hoisted_6$9 = {
|
const _hoisted_6$a = {
|
||||||
key: 0,
|
key: 0,
|
||||||
class: "section__paths"
|
class: "section__paths"
|
||||||
};
|
};
|
||||||
const _hoisted_7$7 = {
|
const _hoisted_7$8 = {
|
||||||
key: 1,
|
key: 1,
|
||||||
class: "section__empty"
|
class: "section__empty"
|
||||||
};
|
};
|
||||||
const _hoisted_8$5 = { class: "section__column" };
|
const _hoisted_8$6 = { class: "section__column" };
|
||||||
const _hoisted_9$3 = { class: "section__column-header" };
|
const _hoisted_9$4 = { class: "section__column-header" };
|
||||||
const _hoisted_10$3 = { class: "section__content" };
|
const _hoisted_10$4 = { class: "section__content" };
|
||||||
const _hoisted_11$2 = {
|
const _hoisted_11$3 = {
|
||||||
key: 0,
|
key: 0,
|
||||||
class: "section__paths"
|
class: "section__paths"
|
||||||
};
|
};
|
||||||
const _hoisted_12$2 = {
|
const _hoisted_12$3 = {
|
||||||
key: 1,
|
key: 1,
|
||||||
class: "section__empty"
|
class: "section__empty"
|
||||||
};
|
};
|
||||||
const _sfc_main$j = /* @__PURE__ */ defineComponent({
|
const _sfc_main$k = /* @__PURE__ */ defineComponent({
|
||||||
__name: "FoldersSection",
|
__name: "FoldersSection",
|
||||||
props: {
|
props: {
|
||||||
includeFolders: {},
|
includeFolders: {},
|
||||||
@@ -10747,13 +10862,13 @@ const _sfc_main$j = /* @__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$j, [
|
return openBlock(), createElementBlock("div", _hoisted_1$k, [
|
||||||
_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)),
|
||||||
createBaseVNode("div", _hoisted_2$f, [
|
createBaseVNode("div", _hoisted_2$g, [
|
||||||
createBaseVNode("div", _hoisted_3$d, [
|
createBaseVNode("div", _hoisted_3$e, [
|
||||||
createBaseVNode("div", _hoisted_4$b, [
|
createBaseVNode("div", _hoisted_4$c, [
|
||||||
_cache[3] || (_cache[3] = createBaseVNode("span", { class: "section__column-title section__column-title--include" }, "INCLUDE", -1)),
|
_cache[3] || (_cache[3] = createBaseVNode("span", { class: "section__column-title section__column-title--include" }, "INCLUDE", -1)),
|
||||||
createBaseVNode("button", {
|
createBaseVNode("button", {
|
||||||
type: "button",
|
type: "button",
|
||||||
@@ -10768,8 +10883,8 @@ const _sfc_main$j = /* @__PURE__ */ defineComponent({
|
|||||||
], -1)
|
], -1)
|
||||||
])])
|
])])
|
||||||
]),
|
]),
|
||||||
createBaseVNode("div", _hoisted_5$9, [
|
createBaseVNode("div", _hoisted_5$a, [
|
||||||
__props.includeFolders.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_6$9, [
|
__props.includeFolders.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_6$a, [
|
||||||
(openBlock(true), createElementBlock(Fragment, null, renderList(__props.includeFolders, (path) => {
|
(openBlock(true), createElementBlock(Fragment, null, renderList(__props.includeFolders, (path) => {
|
||||||
return openBlock(), createBlock(FilterChip, {
|
return openBlock(), createBlock(FilterChip, {
|
||||||
key: path,
|
key: path,
|
||||||
@@ -10779,11 +10894,11 @@ const _sfc_main$j = /* @__PURE__ */ defineComponent({
|
|||||||
onRemove: ($event) => removeInclude(path)
|
onRemove: ($event) => removeInclude(path)
|
||||||
}, null, 8, ["label", "onRemove"]);
|
}, null, 8, ["label", "onRemove"]);
|
||||||
}), 128))
|
}), 128))
|
||||||
])) : (openBlock(), createElementBlock("div", _hoisted_7$7, " No folders selected "))
|
])) : (openBlock(), createElementBlock("div", _hoisted_7$8, " No folders selected "))
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
createBaseVNode("div", _hoisted_8$5, [
|
createBaseVNode("div", _hoisted_8$6, [
|
||||||
createBaseVNode("div", _hoisted_9$3, [
|
createBaseVNode("div", _hoisted_9$4, [
|
||||||
_cache[5] || (_cache[5] = createBaseVNode("span", { class: "section__column-title section__column-title--exclude" }, "EXCLUDE", -1)),
|
_cache[5] || (_cache[5] = createBaseVNode("span", { class: "section__column-title section__column-title--exclude" }, "EXCLUDE", -1)),
|
||||||
createBaseVNode("button", {
|
createBaseVNode("button", {
|
||||||
type: "button",
|
type: "button",
|
||||||
@@ -10798,8 +10913,8 @@ const _sfc_main$j = /* @__PURE__ */ defineComponent({
|
|||||||
], -1)
|
], -1)
|
||||||
])])
|
])])
|
||||||
]),
|
]),
|
||||||
createBaseVNode("div", _hoisted_10$3, [
|
createBaseVNode("div", _hoisted_10$4, [
|
||||||
__props.excludeFolders.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_11$2, [
|
__props.excludeFolders.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_11$3, [
|
||||||
(openBlock(true), createElementBlock(Fragment, null, renderList(__props.excludeFolders, (path) => {
|
(openBlock(true), createElementBlock(Fragment, null, renderList(__props.excludeFolders, (path) => {
|
||||||
return openBlock(), createBlock(FilterChip, {
|
return openBlock(), createBlock(FilterChip, {
|
||||||
key: path,
|
key: path,
|
||||||
@@ -10809,7 +10924,7 @@ const _sfc_main$j = /* @__PURE__ */ defineComponent({
|
|||||||
onRemove: ($event) => removeExclude(path)
|
onRemove: ($event) => removeExclude(path)
|
||||||
}, null, 8, ["label", "onRemove"]);
|
}, null, 8, ["label", "onRemove"]);
|
||||||
}), 128))
|
}), 128))
|
||||||
])) : (openBlock(), createElementBlock("div", _hoisted_12$2, " No folders selected "))
|
])) : (openBlock(), createElementBlock("div", _hoisted_12$3, " No folders selected "))
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
@@ -10817,7 +10932,147 @@ const _sfc_main$j = /* @__PURE__ */ defineComponent({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const FoldersSection = /* @__PURE__ */ _export_sfc(_sfc_main$j, [["__scopeId", "data-v-af9caf84"]]);
|
const FoldersSection = /* @__PURE__ */ _export_sfc(_sfc_main$k, [["__scopeId", "data-v-af9caf84"]]);
|
||||||
|
const _hoisted_1$j = { class: "section" };
|
||||||
|
const _hoisted_2$f = { class: "section__header" };
|
||||||
|
const _hoisted_3$d = { class: "section__toggle" };
|
||||||
|
const _hoisted_4$b = ["checked"];
|
||||||
|
const _hoisted_5$9 = { class: "section__columns" };
|
||||||
|
const _hoisted_6$9 = { class: "section__column" };
|
||||||
|
const _hoisted_7$7 = { class: "section__input-wrapper" };
|
||||||
|
const _hoisted_8$5 = ["placeholder"];
|
||||||
|
const _hoisted_9$3 = { class: "section__patterns" };
|
||||||
|
const _hoisted_10$3 = {
|
||||||
|
key: 0,
|
||||||
|
class: "section__empty"
|
||||||
|
};
|
||||||
|
const _hoisted_11$2 = { class: "section__column" };
|
||||||
|
const _hoisted_12$2 = { class: "section__input-wrapper" };
|
||||||
|
const _hoisted_13$2 = ["placeholder"];
|
||||||
|
const _hoisted_14$2 = { class: "section__patterns" };
|
||||||
|
const _hoisted_15$2 = {
|
||||||
|
key: 0,
|
||||||
|
class: "section__empty"
|
||||||
|
};
|
||||||
|
const _sfc_main$j = /* @__PURE__ */ defineComponent({
|
||||||
|
__name: "NamePatternsSection",
|
||||||
|
props: {
|
||||||
|
includePatterns: {},
|
||||||
|
excludePatterns: {},
|
||||||
|
useRegex: { type: Boolean }
|
||||||
|
},
|
||||||
|
emits: ["update:includePatterns", "update:excludePatterns", "update:useRegex"],
|
||||||
|
setup(__props, { emit: __emit }) {
|
||||||
|
const props = __props;
|
||||||
|
const emit2 = __emit;
|
||||||
|
const includeInput = ref("");
|
||||||
|
const excludeInput = ref("");
|
||||||
|
const addInclude = () => {
|
||||||
|
const pattern = includeInput.value.trim();
|
||||||
|
if (pattern && !props.includePatterns.includes(pattern)) {
|
||||||
|
emit2("update:includePatterns", [...props.includePatterns, pattern]);
|
||||||
|
includeInput.value = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const addExclude = () => {
|
||||||
|
const pattern = excludeInput.value.trim();
|
||||||
|
if (pattern && !props.excludePatterns.includes(pattern)) {
|
||||||
|
emit2("update:excludePatterns", [...props.excludePatterns, pattern]);
|
||||||
|
excludeInput.value = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const removeInclude = (pattern) => {
|
||||||
|
emit2("update:includePatterns", props.includePatterns.filter((p2) => p2 !== pattern));
|
||||||
|
};
|
||||||
|
const removeExclude = (pattern) => {
|
||||||
|
emit2("update:excludePatterns", props.excludePatterns.filter((p2) => p2 !== pattern));
|
||||||
|
};
|
||||||
|
return (_ctx, _cache) => {
|
||||||
|
return openBlock(), createElementBlock("div", _hoisted_1$j, [
|
||||||
|
createBaseVNode("div", _hoisted_2$f, [
|
||||||
|
_cache[4] || (_cache[4] = createBaseVNode("span", { class: "section__title" }, "NAME PATTERNS", -1)),
|
||||||
|
createBaseVNode("label", _hoisted_3$d, [
|
||||||
|
createBaseVNode("input", {
|
||||||
|
type: "checkbox",
|
||||||
|
checked: __props.useRegex,
|
||||||
|
onChange: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("update:useRegex", $event.target.checked))
|
||||||
|
}, null, 40, _hoisted_4$b),
|
||||||
|
_cache[3] || (_cache[3] = createBaseVNode("span", { class: "section__toggle-label" }, "Use Regex", -1))
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
createBaseVNode("div", _hoisted_5$9, [
|
||||||
|
createBaseVNode("div", _hoisted_6$9, [
|
||||||
|
_cache[5] || (_cache[5] = createBaseVNode("div", { class: "section__column-header" }, [
|
||||||
|
createBaseVNode("span", { class: "section__column-title section__column-title--include" }, "INCLUDE")
|
||||||
|
], -1)),
|
||||||
|
createBaseVNode("div", _hoisted_7$7, [
|
||||||
|
withDirectives(createBaseVNode("input", {
|
||||||
|
type: "text",
|
||||||
|
"onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => includeInput.value = $event),
|
||||||
|
placeholder: __props.useRegex ? "Add regex pattern..." : "Add text pattern...",
|
||||||
|
class: "section__input",
|
||||||
|
onKeydown: withKeys(addInclude, ["enter"])
|
||||||
|
}, null, 40, _hoisted_8$5), [
|
||||||
|
[vModelText, includeInput.value]
|
||||||
|
]),
|
||||||
|
createBaseVNode("button", {
|
||||||
|
type: "button",
|
||||||
|
class: "section__add-btn",
|
||||||
|
onClick: addInclude
|
||||||
|
}, "+")
|
||||||
|
]),
|
||||||
|
createBaseVNode("div", _hoisted_9$3, [
|
||||||
|
(openBlock(true), createElementBlock(Fragment, null, renderList(__props.includePatterns, (pattern) => {
|
||||||
|
return openBlock(), createBlock(FilterChip, {
|
||||||
|
key: pattern,
|
||||||
|
label: pattern,
|
||||||
|
variant: "include",
|
||||||
|
removable: "",
|
||||||
|
onRemove: ($event) => removeInclude(pattern)
|
||||||
|
}, null, 8, ["label", "onRemove"]);
|
||||||
|
}), 128)),
|
||||||
|
__props.includePatterns.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_10$3, toDisplayString(__props.useRegex ? "No regex patterns" : "No text patterns"), 1)) : createCommentVNode("", true)
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
createBaseVNode("div", _hoisted_11$2, [
|
||||||
|
_cache[6] || (_cache[6] = createBaseVNode("div", { class: "section__column-header" }, [
|
||||||
|
createBaseVNode("span", { class: "section__column-title section__column-title--exclude" }, "EXCLUDE")
|
||||||
|
], -1)),
|
||||||
|
createBaseVNode("div", _hoisted_12$2, [
|
||||||
|
withDirectives(createBaseVNode("input", {
|
||||||
|
type: "text",
|
||||||
|
"onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => excludeInput.value = $event),
|
||||||
|
placeholder: __props.useRegex ? "Add regex pattern..." : "Add text pattern...",
|
||||||
|
class: "section__input",
|
||||||
|
onKeydown: withKeys(addExclude, ["enter"])
|
||||||
|
}, null, 40, _hoisted_13$2), [
|
||||||
|
[vModelText, excludeInput.value]
|
||||||
|
]),
|
||||||
|
createBaseVNode("button", {
|
||||||
|
type: "button",
|
||||||
|
class: "section__add-btn",
|
||||||
|
onClick: addExclude
|
||||||
|
}, "+")
|
||||||
|
]),
|
||||||
|
createBaseVNode("div", _hoisted_14$2, [
|
||||||
|
(openBlock(true), createElementBlock(Fragment, null, renderList(__props.excludePatterns, (pattern) => {
|
||||||
|
return openBlock(), createBlock(FilterChip, {
|
||||||
|
key: pattern,
|
||||||
|
label: pattern,
|
||||||
|
variant: "exclude",
|
||||||
|
removable: "",
|
||||||
|
onRemove: ($event) => removeExclude(pattern)
|
||||||
|
}, null, 8, ["label", "onRemove"]);
|
||||||
|
}), 128)),
|
||||||
|
__props.excludePatterns.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_15$2, toDisplayString(__props.useRegex ? "No regex patterns" : "No text patterns"), 1)) : createCommentVNode("", true)
|
||||||
|
])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const NamePatternsSection = /* @__PURE__ */ _export_sfc(_sfc_main$j, [["__scopeId", "data-v-9995b5ed"]]);
|
||||||
const _hoisted_1$i = { class: "section" };
|
const _hoisted_1$i = { 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" };
|
||||||
@@ -10982,13 +11237,16 @@ const _sfc_main$g = /* @__PURE__ */ defineComponent({
|
|||||||
excludeTags: {},
|
excludeTags: {},
|
||||||
includeFolders: {},
|
includeFolders: {},
|
||||||
excludeFolders: {},
|
excludeFolders: {},
|
||||||
|
includePatterns: {},
|
||||||
|
excludePatterns: {},
|
||||||
|
useRegex: { type: Boolean },
|
||||||
noCreditRequired: { type: Boolean },
|
noCreditRequired: { type: Boolean },
|
||||||
allowSelling: { type: Boolean },
|
allowSelling: { type: Boolean },
|
||||||
previewItems: {},
|
previewItems: {},
|
||||||
matchCount: {},
|
matchCount: {},
|
||||||
isLoading: { type: Boolean }
|
isLoading: { type: Boolean }
|
||||||
},
|
},
|
||||||
emits: ["open-modal", "update:includeFolders", "update:excludeFolders", "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$g, [
|
||||||
@@ -11012,24 +11270,32 @@ const _sfc_main$g = /* @__PURE__ */ defineComponent({
|
|||||||
onEditInclude: _cache[5] || (_cache[5] = ($event) => _ctx.$emit("open-modal", "includeFolders")),
|
onEditInclude: _cache[5] || (_cache[5] = ($event) => _ctx.$emit("open-modal", "includeFolders")),
|
||||||
onEditExclude: _cache[6] || (_cache[6] = ($event) => _ctx.$emit("open-modal", "excludeFolders"))
|
onEditExclude: _cache[6] || (_cache[6] = ($event) => _ctx.$emit("open-modal", "excludeFolders"))
|
||||||
}, null, 8, ["include-folders", "exclude-folders"]),
|
}, null, 8, ["include-folders", "exclude-folders"]),
|
||||||
|
createVNode(NamePatternsSection, {
|
||||||
|
"include-patterns": __props.includePatterns,
|
||||||
|
"exclude-patterns": __props.excludePatterns,
|
||||||
|
"use-regex": __props.useRegex,
|
||||||
|
"onUpdate:includePatterns": _cache[7] || (_cache[7] = ($event) => _ctx.$emit("update:includePatterns", $event)),
|
||||||
|
"onUpdate:excludePatterns": _cache[8] || (_cache[8] = ($event) => _ctx.$emit("update:excludePatterns", $event)),
|
||||||
|
"onUpdate:useRegex": _cache[9] || (_cache[9] = ($event) => _ctx.$emit("update:useRegex", $event))
|
||||||
|
}, null, 8, ["include-patterns", "exclude-patterns", "use-regex"]),
|
||||||
createVNode(LicenseSection, {
|
createVNode(LicenseSection, {
|
||||||
"no-credit-required": __props.noCreditRequired,
|
"no-credit-required": __props.noCreditRequired,
|
||||||
"allow-selling": __props.allowSelling,
|
"allow-selling": __props.allowSelling,
|
||||||
"onUpdate:noCreditRequired": _cache[7] || (_cache[7] = ($event) => _ctx.$emit("update:noCreditRequired", $event)),
|
"onUpdate:noCreditRequired": _cache[10] || (_cache[10] = ($event) => _ctx.$emit("update:noCreditRequired", $event)),
|
||||||
"onUpdate:allowSelling": _cache[8] || (_cache[8] = ($event) => _ctx.$emit("update:allowSelling", $event))
|
"onUpdate:allowSelling": _cache[11] || (_cache[11] = ($event) => _ctx.$emit("update:allowSelling", $event))
|
||||||
}, null, 8, ["no-credit-required", "allow-selling"])
|
}, null, 8, ["no-credit-required", "allow-selling"])
|
||||||
]),
|
]),
|
||||||
createVNode(LoraPoolPreview, {
|
createVNode(LoraPoolPreview, {
|
||||||
items: __props.previewItems,
|
items: __props.previewItems,
|
||||||
"match-count": __props.matchCount,
|
"match-count": __props.matchCount,
|
||||||
"is-loading": __props.isLoading,
|
"is-loading": __props.isLoading,
|
||||||
onRefresh: _cache[9] || (_cache[9] = ($event) => _ctx.$emit("refresh"))
|
onRefresh: _cache[12] || (_cache[12] = ($event) => _ctx.$emit("refresh"))
|
||||||
}, null, 8, ["items", "match-count", "is-loading"])
|
}, null, 8, ["items", "match-count", "is-loading"])
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const LoraPoolSummaryView = /* @__PURE__ */ _export_sfc(_sfc_main$g, [["__scopeId", "data-v-328e7526"]]);
|
const LoraPoolSummaryView = /* @__PURE__ */ _export_sfc(_sfc_main$g, [["__scopeId", "data-v-83235a00"]]);
|
||||||
const _hoisted_1$f = { class: "lora-pool-modal__header" };
|
const _hoisted_1$f = { 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" };
|
||||||
@@ -11672,7 +11938,7 @@ function useLoraPoolApi() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
const fetchLoras = async (params) => {
|
const fetchLoras = async (params) => {
|
||||||
var _a2, _b, _c, _d;
|
var _a2, _b, _c, _d, _e2, _f;
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
try {
|
try {
|
||||||
const urlParams = new URLSearchParams();
|
const urlParams = new URLSearchParams();
|
||||||
@@ -11692,6 +11958,11 @@ function useLoraPoolApi() {
|
|||||||
if (params.allowSelling !== void 0) {
|
if (params.allowSelling !== void 0) {
|
||||||
urlParams.set("allow_selling_generated_content", String(params.allowSelling));
|
urlParams.set("allow_selling_generated_content", String(params.allowSelling));
|
||||||
}
|
}
|
||||||
|
(_e2 = params.namePatternsInclude) == null ? void 0 : _e2.forEach((pattern) => urlParams.append("name_pattern_include", pattern));
|
||||||
|
(_f = params.namePatternsExclude) == null ? void 0 : _f.forEach((pattern) => urlParams.append("name_pattern_exclude", pattern));
|
||||||
|
if (params.namePatternsUseRegex !== void 0) {
|
||||||
|
urlParams.set("name_pattern_use_regex", String(params.namePatternsUseRegex));
|
||||||
|
}
|
||||||
const response = await fetch(`/api/lm/loras/list?${urlParams}`);
|
const response = await fetch(`/api/lm/loras/list?${urlParams}`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return {
|
return {
|
||||||
@@ -11723,6 +11994,9 @@ function useLoraPoolState(widget) {
|
|||||||
const excludeFolders = ref([]);
|
const excludeFolders = ref([]);
|
||||||
const noCreditRequired = ref(false);
|
const noCreditRequired = ref(false);
|
||||||
const allowSelling = ref(false);
|
const allowSelling = ref(false);
|
||||||
|
const includePatterns = ref([]);
|
||||||
|
const excludePatterns = ref([]);
|
||||||
|
const useRegex = ref(false);
|
||||||
const availableBaseModels = ref([]);
|
const availableBaseModels = ref([]);
|
||||||
const availableTags = ref([]);
|
const availableTags = ref([]);
|
||||||
const folderTree = ref([]);
|
const folderTree = ref([]);
|
||||||
@@ -11745,6 +12019,11 @@ function useLoraPoolState(widget) {
|
|||||||
license: {
|
license: {
|
||||||
noCreditRequired: noCreditRequired.value,
|
noCreditRequired: noCreditRequired.value,
|
||||||
allowSelling: allowSelling.value
|
allowSelling: allowSelling.value
|
||||||
|
},
|
||||||
|
namePatterns: {
|
||||||
|
include: includePatterns.value,
|
||||||
|
exclude: excludePatterns.value,
|
||||||
|
useRegex: useRegex.value
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
preview: {
|
preview: {
|
||||||
@@ -11758,7 +12037,7 @@ function useLoraPoolState(widget) {
|
|||||||
return config;
|
return config;
|
||||||
};
|
};
|
||||||
const restoreFromConfig = (config) => {
|
const restoreFromConfig = (config) => {
|
||||||
var _a2, _b, _c, _d, _e2, _f;
|
var _a2, _b, _c, _d, _e2, _f, _g, _h, _i;
|
||||||
isRestoring = true;
|
isRestoring = true;
|
||||||
try {
|
try {
|
||||||
if (!(config == null ? void 0 : config.filters)) return;
|
if (!(config == null ? void 0 : config.filters)) return;
|
||||||
@@ -11775,6 +12054,9 @@ function useLoraPoolState(widget) {
|
|||||||
updateIfChanged(excludeFolders, ((_d = filters.folders) == null ? void 0 : _d.exclude) || []);
|
updateIfChanged(excludeFolders, ((_d = filters.folders) == null ? void 0 : _d.exclude) || []);
|
||||||
updateIfChanged(noCreditRequired, ((_e2 = filters.license) == null ? void 0 : _e2.noCreditRequired) ?? false);
|
updateIfChanged(noCreditRequired, ((_e2 = filters.license) == null ? void 0 : _e2.noCreditRequired) ?? false);
|
||||||
updateIfChanged(allowSelling, ((_f = filters.license) == null ? void 0 : _f.allowSelling) ?? false);
|
updateIfChanged(allowSelling, ((_f = filters.license) == null ? void 0 : _f.allowSelling) ?? false);
|
||||||
|
updateIfChanged(includePatterns, ((_g = filters.namePatterns) == null ? void 0 : _g.include) || []);
|
||||||
|
updateIfChanged(excludePatterns, ((_h = filters.namePatterns) == null ? void 0 : _h.exclude) || []);
|
||||||
|
updateIfChanged(useRegex, ((_i = filters.namePatterns) == null ? void 0 : _i.useRegex) ?? false);
|
||||||
matchCount.value = (preview == null ? void 0 : preview.matchCount) || 0;
|
matchCount.value = (preview == null ? void 0 : preview.matchCount) || 0;
|
||||||
} finally {
|
} finally {
|
||||||
isRestoring = false;
|
isRestoring = false;
|
||||||
@@ -11799,6 +12081,9 @@ function useLoraPoolState(widget) {
|
|||||||
foldersExclude: excludeFolders.value,
|
foldersExclude: excludeFolders.value,
|
||||||
noCreditRequired: noCreditRequired.value || void 0,
|
noCreditRequired: noCreditRequired.value || void 0,
|
||||||
allowSelling: allowSelling.value || void 0,
|
allowSelling: allowSelling.value || void 0,
|
||||||
|
namePatternsInclude: includePatterns.value,
|
||||||
|
namePatternsExclude: excludePatterns.value,
|
||||||
|
namePatternsUseRegex: useRegex.value,
|
||||||
pageSize: 6
|
pageSize: 6
|
||||||
});
|
});
|
||||||
previewItems.value = result.items;
|
previewItems.value = result.items;
|
||||||
@@ -11819,7 +12104,10 @@ function useLoraPoolState(widget) {
|
|||||||
includeFolders,
|
includeFolders,
|
||||||
excludeFolders,
|
excludeFolders,
|
||||||
noCreditRequired,
|
noCreditRequired,
|
||||||
allowSelling
|
allowSelling,
|
||||||
|
includePatterns,
|
||||||
|
excludePatterns,
|
||||||
|
useRegex
|
||||||
], onFilterChange, { deep: true });
|
], onFilterChange, { deep: true });
|
||||||
return {
|
return {
|
||||||
// Filter state
|
// Filter state
|
||||||
@@ -11830,6 +12118,9 @@ function useLoraPoolState(widget) {
|
|||||||
excludeFolders,
|
excludeFolders,
|
||||||
noCreditRequired,
|
noCreditRequired,
|
||||||
allowSelling,
|
allowSelling,
|
||||||
|
includePatterns,
|
||||||
|
excludePatterns,
|
||||||
|
useRegex,
|
||||||
// Available options
|
// Available options
|
||||||
availableBaseModels,
|
availableBaseModels,
|
||||||
availableTags,
|
availableTags,
|
||||||
@@ -11902,6 +12193,9 @@ const _sfc_main$a = /* @__PURE__ */ defineComponent({
|
|||||||
"exclude-tags": unref(state).excludeTags.value,
|
"exclude-tags": unref(state).excludeTags.value,
|
||||||
"include-folders": unref(state).includeFolders.value,
|
"include-folders": unref(state).includeFolders.value,
|
||||||
"exclude-folders": unref(state).excludeFolders.value,
|
"exclude-folders": unref(state).excludeFolders.value,
|
||||||
|
"include-patterns": unref(state).includePatterns.value,
|
||||||
|
"exclude-patterns": unref(state).excludePatterns.value,
|
||||||
|
"use-regex": unref(state).useRegex.value,
|
||||||
"no-credit-required": unref(state).noCreditRequired.value,
|
"no-credit-required": unref(state).noCreditRequired.value,
|
||||||
"allow-selling": unref(state).allowSelling.value,
|
"allow-selling": unref(state).allowSelling.value,
|
||||||
"preview-items": unref(state).previewItems.value,
|
"preview-items": unref(state).previewItems.value,
|
||||||
@@ -11910,16 +12204,19 @@ const _sfc_main$a = /* @__PURE__ */ defineComponent({
|
|||||||
onOpenModal: openModal,
|
onOpenModal: openModal,
|
||||||
"onUpdate:includeFolders": _cache[0] || (_cache[0] = ($event) => unref(state).includeFolders.value = $event),
|
"onUpdate:includeFolders": _cache[0] || (_cache[0] = ($event) => unref(state).includeFolders.value = $event),
|
||||||
"onUpdate:excludeFolders": _cache[1] || (_cache[1] = ($event) => unref(state).excludeFolders.value = $event),
|
"onUpdate:excludeFolders": _cache[1] || (_cache[1] = ($event) => unref(state).excludeFolders.value = $event),
|
||||||
"onUpdate:noCreditRequired": _cache[2] || (_cache[2] = ($event) => unref(state).noCreditRequired.value = $event),
|
"onUpdate:includePatterns": _cache[2] || (_cache[2] = ($event) => unref(state).includePatterns.value = $event),
|
||||||
"onUpdate:allowSelling": _cache[3] || (_cache[3] = ($event) => unref(state).allowSelling.value = $event),
|
"onUpdate:excludePatterns": _cache[3] || (_cache[3] = ($event) => unref(state).excludePatterns.value = $event),
|
||||||
|
"onUpdate:useRegex": _cache[4] || (_cache[4] = ($event) => unref(state).useRegex.value = $event),
|
||||||
|
"onUpdate:noCreditRequired": _cache[5] || (_cache[5] = ($event) => unref(state).noCreditRequired.value = $event),
|
||||||
|
"onUpdate:allowSelling": _cache[6] || (_cache[6] = ($event) => unref(state).allowSelling.value = $event),
|
||||||
onRefresh: unref(state).refreshPreview
|
onRefresh: unref(state).refreshPreview
|
||||||
}, null, 8, ["selected-base-models", "available-base-models", "include-tags", "exclude-tags", "include-folders", "exclude-folders", "no-credit-required", "allow-selling", "preview-items", "match-count", "is-loading", "onRefresh"]),
|
}, null, 8, ["selected-base-models", "available-base-models", "include-tags", "exclude-tags", "include-folders", "exclude-folders", "include-patterns", "exclude-patterns", "use-regex", "no-credit-required", "allow-selling", "preview-items", "match-count", "is-loading", "onRefresh"]),
|
||||||
createVNode(BaseModelModal, {
|
createVNode(BaseModelModal, {
|
||||||
visible: unref(modalState).isModalOpen("baseModels"),
|
visible: unref(modalState).isModalOpen("baseModels"),
|
||||||
models: unref(state).availableBaseModels.value,
|
models: unref(state).availableBaseModels.value,
|
||||||
selected: unref(state).selectedBaseModels.value,
|
selected: unref(state).selectedBaseModels.value,
|
||||||
onClose: unref(modalState).closeModal,
|
onClose: unref(modalState).closeModal,
|
||||||
"onUpdate:selected": _cache[4] || (_cache[4] = ($event) => unref(state).selectedBaseModels.value = $event)
|
"onUpdate:selected": _cache[7] || (_cache[7] = ($event) => unref(state).selectedBaseModels.value = $event)
|
||||||
}, null, 8, ["visible", "models", "selected", "onClose"]),
|
}, null, 8, ["visible", "models", "selected", "onClose"]),
|
||||||
createVNode(TagsModal, {
|
createVNode(TagsModal, {
|
||||||
visible: unref(modalState).isModalOpen("includeTags"),
|
visible: unref(modalState).isModalOpen("includeTags"),
|
||||||
@@ -11927,7 +12224,7 @@ const _sfc_main$a = /* @__PURE__ */ defineComponent({
|
|||||||
selected: unref(state).includeTags.value,
|
selected: unref(state).includeTags.value,
|
||||||
variant: "include",
|
variant: "include",
|
||||||
onClose: unref(modalState).closeModal,
|
onClose: unref(modalState).closeModal,
|
||||||
"onUpdate:selected": _cache[5] || (_cache[5] = ($event) => unref(state).includeTags.value = $event)
|
"onUpdate:selected": _cache[8] || (_cache[8] = ($event) => unref(state).includeTags.value = $event)
|
||||||
}, null, 8, ["visible", "tags", "selected", "onClose"]),
|
}, null, 8, ["visible", "tags", "selected", "onClose"]),
|
||||||
createVNode(TagsModal, {
|
createVNode(TagsModal, {
|
||||||
visible: unref(modalState).isModalOpen("excludeTags"),
|
visible: unref(modalState).isModalOpen("excludeTags"),
|
||||||
@@ -11935,7 +12232,7 @@ const _sfc_main$a = /* @__PURE__ */ defineComponent({
|
|||||||
selected: unref(state).excludeTags.value,
|
selected: unref(state).excludeTags.value,
|
||||||
variant: "exclude",
|
variant: "exclude",
|
||||||
onClose: unref(modalState).closeModal,
|
onClose: unref(modalState).closeModal,
|
||||||
"onUpdate:selected": _cache[6] || (_cache[6] = ($event) => unref(state).excludeTags.value = $event)
|
"onUpdate:selected": _cache[9] || (_cache[9] = ($event) => unref(state).excludeTags.value = $event)
|
||||||
}, null, 8, ["visible", "tags", "selected", "onClose"]),
|
}, null, 8, ["visible", "tags", "selected", "onClose"]),
|
||||||
createVNode(FoldersModal, {
|
createVNode(FoldersModal, {
|
||||||
visible: unref(modalState).isModalOpen("includeFolders"),
|
visible: unref(modalState).isModalOpen("includeFolders"),
|
||||||
@@ -11943,7 +12240,7 @@ const _sfc_main$a = /* @__PURE__ */ defineComponent({
|
|||||||
selected: unref(state).includeFolders.value,
|
selected: unref(state).includeFolders.value,
|
||||||
variant: "include",
|
variant: "include",
|
||||||
onClose: unref(modalState).closeModal,
|
onClose: unref(modalState).closeModal,
|
||||||
"onUpdate:selected": _cache[7] || (_cache[7] = ($event) => unref(state).includeFolders.value = $event)
|
"onUpdate:selected": _cache[10] || (_cache[10] = ($event) => unref(state).includeFolders.value = $event)
|
||||||
}, null, 8, ["visible", "folders", "selected", "onClose"]),
|
}, null, 8, ["visible", "folders", "selected", "onClose"]),
|
||||||
createVNode(FoldersModal, {
|
createVNode(FoldersModal, {
|
||||||
visible: unref(modalState).isModalOpen("excludeFolders"),
|
visible: unref(modalState).isModalOpen("excludeFolders"),
|
||||||
@@ -11951,13 +12248,13 @@ const _sfc_main$a = /* @__PURE__ */ defineComponent({
|
|||||||
selected: unref(state).excludeFolders.value,
|
selected: unref(state).excludeFolders.value,
|
||||||
variant: "exclude",
|
variant: "exclude",
|
||||||
onClose: unref(modalState).closeModal,
|
onClose: unref(modalState).closeModal,
|
||||||
"onUpdate:selected": _cache[8] || (_cache[8] = ($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"])
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const LoraPoolWidget = /* @__PURE__ */ _export_sfc(_sfc_main$a, [["__scopeId", "data-v-4456abba"]]);
|
const LoraPoolWidget = /* @__PURE__ */ _export_sfc(_sfc_main$a, [["__scopeId", "data-v-0bbd50ea"]]);
|
||||||
const _hoisted_1$9 = { class: "last-used-preview" };
|
const _hoisted_1$9 = { 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"];
|
||||||
@@ -14720,7 +15017,7 @@ function updateDownstreamLoaders(startNode, visited = /* @__PURE__ */ new Set())
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const LORA_POOL_WIDGET_MIN_WIDTH = 500;
|
const LORA_POOL_WIDGET_MIN_WIDTH = 500;
|
||||||
const LORA_POOL_WIDGET_MIN_HEIGHT = 400;
|
const LORA_POOL_WIDGET_MIN_HEIGHT = 520;
|
||||||
const LORA_RANDOMIZER_WIDGET_MIN_WIDTH = 500;
|
const LORA_RANDOMIZER_WIDGET_MIN_WIDTH = 500;
|
||||||
const LORA_RANDOMIZER_WIDGET_MIN_HEIGHT = 448;
|
const LORA_RANDOMIZER_WIDGET_MIN_HEIGHT = 448;
|
||||||
const LORA_RANDOMIZER_WIDGET_MAX_HEIGHT = LORA_RANDOMIZER_WIDGET_MIN_HEIGHT;
|
const LORA_RANDOMIZER_WIDGET_MAX_HEIGHT = LORA_RANDOMIZER_WIDGET_MIN_HEIGHT;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user