mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
refactor: move No LoRA feature from LoRA Pool to Lora Cycler widget
Move the 'empty/no LoRA' cycling functionality from the LoRA Pool node to the Lora Cycler widget for cleaner architecture: Frontend changes: - Add include_no_lora field to CyclerConfig interface - Add includeNoLora state and logic to useLoraCyclerState composable - Add toggle UI in LoraCyclerSettingsView with special styling - Show 'No LoRA' entry in LoraListModal when enabled - Update LoraCyclerWidget to integrate new logic Backend changes: - lora_cycler.py reads include_no_lora from config - Calculate effective_total_count (actual count + 1 when enabled) - Return empty lora_stack when on No LoRA position - Return actual LoRA count in total_count (not effective count) Reverted files to pre-PR state: - lora_loader.py, lora_pool.py, lora_randomizer.py, lora_stacker.py - lora_routes.py, lora_service.py - LoraPoolWidget.vue and related files Related to PR #861 Co-authored-by: dogatech <dogatech@dogatech.home>
This commit is contained in:
@@ -56,6 +56,9 @@ class LoraCyclerLM:
|
|||||||
clip_strength = float(cycler_config.get("clip_strength", 1.0))
|
clip_strength = float(cycler_config.get("clip_strength", 1.0))
|
||||||
sort_by = "filename"
|
sort_by = "filename"
|
||||||
|
|
||||||
|
# Include "no lora" option
|
||||||
|
include_no_lora = cycler_config.get("include_no_lora", False)
|
||||||
|
|
||||||
# Dual-index mechanism for batch queue synchronization
|
# Dual-index mechanism for batch queue synchronization
|
||||||
execution_index = cycler_config.get("execution_index") # Can be None
|
execution_index = cycler_config.get("execution_index") # Can be None
|
||||||
# next_index_from_config = cycler_config.get("next_index") # Not used on backend
|
# next_index_from_config = cycler_config.get("next_index") # Not used on backend
|
||||||
@@ -71,7 +74,10 @@ class LoraCyclerLM:
|
|||||||
|
|
||||||
total_count = len(lora_list)
|
total_count = len(lora_list)
|
||||||
|
|
||||||
if total_count == 0:
|
# Calculate effective total count (includes no lora option if enabled)
|
||||||
|
effective_total_count = total_count + 1 if include_no_lora else total_count
|
||||||
|
|
||||||
|
if total_count == 0 and not include_no_lora:
|
||||||
logger.warning("[LoraCyclerLM] No LoRAs available in pool")
|
logger.warning("[LoraCyclerLM] No LoRAs available in pool")
|
||||||
return {
|
return {
|
||||||
"result": ([],),
|
"result": ([],),
|
||||||
@@ -93,47 +99,66 @@ class LoraCyclerLM:
|
|||||||
else:
|
else:
|
||||||
actual_index = current_index
|
actual_index = current_index
|
||||||
|
|
||||||
# Clamp index to valid range (1-based)
|
# Clamp index to valid range (1-based, includes no lora if enabled)
|
||||||
clamped_index = max(1, min(actual_index, total_count))
|
clamped_index = max(1, min(actual_index, effective_total_count))
|
||||||
|
|
||||||
# Get LoRA at current index (convert to 0-based for list access)
|
# Check if current index is the "no lora" option (last position when include_no_lora is True)
|
||||||
current_lora = lora_list[clamped_index - 1]
|
is_no_lora = include_no_lora and clamped_index == effective_total_count
|
||||||
|
|
||||||
# Build LORA_STACK with single LoRA
|
if is_no_lora:
|
||||||
if current_lora["file_name"] == "None":
|
# "No LoRA" option - return empty stack
|
||||||
lora_path = None
|
|
||||||
else:
|
|
||||||
lora_path, _ = get_lora_info(current_lora["file_name"])
|
|
||||||
|
|
||||||
if not lora_path:
|
|
||||||
if current_lora["file_name"] != "None":
|
|
||||||
logger.warning(
|
|
||||||
f"[LoraCyclerLM] Could not find path for LoRA: {current_lora['file_name']}"
|
|
||||||
)
|
|
||||||
lora_stack = []
|
lora_stack = []
|
||||||
|
current_lora_name = "No LoRA"
|
||||||
|
current_lora_filename = "No LoRA"
|
||||||
else:
|
else:
|
||||||
# Normalize path separators
|
# Get LoRA at current index (convert to 0-based for list access)
|
||||||
lora_path = lora_path.replace("/", os.sep)
|
current_lora = lora_list[clamped_index - 1]
|
||||||
lora_stack = [(lora_path, model_strength, clip_strength)]
|
current_lora_name = current_lora["file_name"]
|
||||||
|
current_lora_filename = current_lora["file_name"]
|
||||||
|
|
||||||
|
# Build LORA_STACK with single LoRA
|
||||||
|
if current_lora["file_name"] == "None":
|
||||||
|
lora_path = None
|
||||||
|
else:
|
||||||
|
lora_path, _ = get_lora_info(current_lora["file_name"])
|
||||||
|
|
||||||
|
if not lora_path:
|
||||||
|
if current_lora["file_name"] != "None":
|
||||||
|
logger.warning(
|
||||||
|
f"[LoraCyclerLM] Could not find path for LoRA: {current_lora['file_name']}"
|
||||||
|
)
|
||||||
|
lora_stack = []
|
||||||
|
else:
|
||||||
|
# Normalize path separators
|
||||||
|
lora_path = lora_path.replace("/", os.sep)
|
||||||
|
lora_stack = [(lora_path, model_strength, clip_strength)]
|
||||||
|
|
||||||
# Calculate next index (wrap to 1 if at end)
|
# Calculate next index (wrap to 1 if at end)
|
||||||
next_index = clamped_index + 1
|
next_index = clamped_index + 1
|
||||||
if next_index > total_count:
|
if next_index > effective_total_count:
|
||||||
next_index = 1
|
next_index = 1
|
||||||
|
|
||||||
# Get next LoRA for UI display (what will be used next generation)
|
# Get next LoRA for UI display (what will be used next generation)
|
||||||
next_lora = lora_list[next_index - 1]
|
is_next_no_lora = include_no_lora and next_index == effective_total_count
|
||||||
next_display_name = next_lora["file_name"]
|
if is_next_no_lora:
|
||||||
|
next_display_name = "No LoRA"
|
||||||
|
next_lora_filename = "No LoRA"
|
||||||
|
else:
|
||||||
|
next_lora = lora_list[next_index - 1]
|
||||||
|
next_display_name = next_lora["file_name"]
|
||||||
|
next_lora_filename = next_lora["file_name"]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"result": (lora_stack,),
|
"result": (lora_stack,),
|
||||||
"ui": {
|
"ui": {
|
||||||
"current_index": [clamped_index],
|
"current_index": [clamped_index],
|
||||||
"next_index": [next_index],
|
"next_index": [next_index],
|
||||||
"total_count": [total_count],
|
"total_count": [
|
||||||
"current_lora_name": [current_lora["file_name"]],
|
total_count
|
||||||
"current_lora_filename": [current_lora["file_name"]],
|
], # Return actual LoRA count, not effective_total_count
|
||||||
|
"current_lora_name": [current_lora_name],
|
||||||
|
"current_lora_filename": [current_lora_filename],
|
||||||
"next_lora_name": [next_display_name],
|
"next_lora_name": [next_display_name],
|
||||||
"next_lora_filename": [next_lora["file_name"]],
|
"next_lora_filename": [next_lora_filename],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,8 +53,6 @@ class LoraLoaderLM:
|
|||||||
# First process lora_stack if available
|
# First process lora_stack if available
|
||||||
if lora_stack:
|
if lora_stack:
|
||||||
for lora_path, model_strength, clip_strength in lora_stack:
|
for lora_path, model_strength, clip_strength in lora_stack:
|
||||||
if lora_path == "None" or not lora_path:
|
|
||||||
continue
|
|
||||||
# Extract lora name and convert to absolute path
|
# Extract lora name and convert to absolute path
|
||||||
# lora_stack stores relative paths, but load_torch_file needs absolute paths
|
# lora_stack stores relative paths, but load_torch_file needs absolute paths
|
||||||
lora_name = extract_lora_name(lora_path)
|
lora_name = extract_lora_name(lora_path)
|
||||||
@@ -80,7 +78,7 @@ class LoraLoaderLM:
|
|||||||
# Then process loras from kwargs with support for both old and new formats
|
# Then process loras from kwargs with support for both old and new formats
|
||||||
loras_list = get_loras_list(kwargs)
|
loras_list = get_loras_list(kwargs)
|
||||||
for lora in loras_list:
|
for lora in loras_list:
|
||||||
if not lora.get('active', False) or lora.get('name') == "None":
|
if not lora.get('active', False):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
lora_name = lora['name']
|
lora_name = lora['name']
|
||||||
@@ -199,8 +197,6 @@ class LoraTextLoaderLM:
|
|||||||
# First process lora_stack if available
|
# First process lora_stack if available
|
||||||
if lora_stack:
|
if lora_stack:
|
||||||
for lora_path, model_strength, clip_strength in lora_stack:
|
for lora_path, model_strength, clip_strength in lora_stack:
|
||||||
if lora_path == "None" or not lora_path:
|
|
||||||
continue
|
|
||||||
# Extract lora name and convert to absolute path
|
# Extract lora name and convert to absolute path
|
||||||
# lora_stack stores relative paths, but load_torch_file needs absolute paths
|
# lora_stack stores relative paths, but load_torch_file needs absolute paths
|
||||||
lora_name = extract_lora_name(lora_path)
|
lora_name = extract_lora_name(lora_path)
|
||||||
@@ -227,8 +223,6 @@ class LoraTextLoaderLM:
|
|||||||
parsed_loras = self.parse_lora_syntax(lora_syntax)
|
parsed_loras = self.parse_lora_syntax(lora_syntax)
|
||||||
for lora in parsed_loras:
|
for lora in parsed_loras:
|
||||||
lora_name = lora['name']
|
lora_name = lora['name']
|
||||||
if lora_name == "None":
|
|
||||||
continue
|
|
||||||
model_strength = lora['model_strength']
|
model_strength = lora['model_strength']
|
||||||
clip_strength = lora['clip_strength']
|
clip_strength = lora['clip_strength']
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,6 @@ class LoraPoolLM:
|
|||||||
"folders": {"include": [], "exclude": []},
|
"folders": {"include": [], "exclude": []},
|
||||||
"favoritesOnly": False,
|
"favoritesOnly": False,
|
||||||
"license": {"noCreditRequired": False, "allowSelling": False},
|
"license": {"noCreditRequired": False, "allowSelling": False},
|
||||||
"includeEmptyLora": False,
|
|
||||||
},
|
},
|
||||||
"preview": {"matchCount": 0, "lastUpdated": 0},
|
"preview": {"matchCount": 0, "lastUpdated": 0},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ class LoraRandomizerLM:
|
|||||||
"""
|
"""
|
||||||
lora_stack = []
|
lora_stack = []
|
||||||
for lora in loras:
|
for lora in loras:
|
||||||
if not lora.get("active", False) or lora.get("name") == "None":
|
if not lora.get("active", False):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Get file path
|
# Get file path
|
||||||
|
|||||||
@@ -38,8 +38,6 @@ class LoraStackerLM:
|
|||||||
stack.extend(lora_stack)
|
stack.extend(lora_stack)
|
||||||
# Get trigger words from existing stack entries
|
# Get trigger words from existing stack entries
|
||||||
for lora_path, _, _ in lora_stack:
|
for lora_path, _, _ in lora_stack:
|
||||||
if lora_path == "None" or not lora_path:
|
|
||||||
continue
|
|
||||||
lora_name = extract_lora_name(lora_path)
|
lora_name = extract_lora_name(lora_path)
|
||||||
_, trigger_words = get_lora_info(lora_name)
|
_, trigger_words = get_lora_info(lora_name)
|
||||||
all_trigger_words.extend(trigger_words)
|
all_trigger_words.extend(trigger_words)
|
||||||
@@ -47,7 +45,7 @@ class LoraStackerLM:
|
|||||||
# Process loras from kwargs with support for both old and new formats
|
# Process loras from kwargs with support for both old and new formats
|
||||||
loras_list = get_loras_list(kwargs)
|
loras_list = get_loras_list(kwargs)
|
||||||
for lora in loras_list:
|
for lora in loras_list:
|
||||||
if not lora.get('active', False) or lora.get('name') == "None":
|
if not lora.get('active', False):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
lora_name = lora['name']
|
lora_name = lora['name']
|
||||||
|
|||||||
@@ -97,10 +97,6 @@ class LoraRoutes(BaseModelRoutes):
|
|||||||
h.lower() for h in request.query["lora_hashes"].split(",")
|
h.lower() for h in request.query["lora_hashes"].split(",")
|
||||||
]
|
]
|
||||||
|
|
||||||
include_empty_lora = request.query.get("include_empty_lora")
|
|
||||||
if include_empty_lora is not None:
|
|
||||||
params["include_empty_lora"] = include_empty_lora.lower() == "true"
|
|
||||||
|
|
||||||
return params
|
return params
|
||||||
|
|
||||||
def _validate_civitai_model_type(self, model_type: str) -> bool:
|
def _validate_civitai_model_type(self, model_type: str) -> bool:
|
||||||
|
|||||||
@@ -62,17 +62,6 @@ 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)
|
||||||
|
|
||||||
if kwargs.get("include_empty_lora"):
|
|
||||||
data.append({
|
|
||||||
"file_name": "None",
|
|
||||||
"model_name": "None",
|
|
||||||
"file_path": "",
|
|
||||||
"folder": "",
|
|
||||||
"base_model": "",
|
|
||||||
"tags": [],
|
|
||||||
"civitai": {},
|
|
||||||
})
|
|
||||||
|
|
||||||
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]:
|
||||||
@@ -414,7 +403,7 @@ class LoraService(BaseModelService):
|
|||||||
"""
|
"""
|
||||||
from .model_query import FilterCriteria
|
from .model_query import FilterCriteria
|
||||||
|
|
||||||
filter_section = pool_config.get("filters", pool_config)
|
filter_section = pool_config
|
||||||
|
|
||||||
# Extract filter parameters
|
# Extract filter parameters
|
||||||
selected_base_models = filter_section.get("baseModels", [])
|
selected_base_models = filter_section.get("baseModels", [])
|
||||||
@@ -427,7 +416,6 @@ class LoraService(BaseModelService):
|
|||||||
license_dict = filter_section.get("license", {})
|
license_dict = filter_section.get("license", {})
|
||||||
no_credit_required = license_dict.get("noCreditRequired", False)
|
no_credit_required = license_dict.get("noCreditRequired", False)
|
||||||
allow_selling = license_dict.get("allowSelling", False)
|
allow_selling = license_dict.get("allowSelling", False)
|
||||||
include_empty_lora = filter_section.get("includeEmptyLora", False)
|
|
||||||
|
|
||||||
# Build tag filters dict
|
# Build tag filters dict
|
||||||
tag_filters = {}
|
tag_filters = {}
|
||||||
@@ -497,18 +485,6 @@ class LoraService(BaseModelService):
|
|||||||
if bool(lora.get("license_flags", 127) & (1 << 1))
|
if bool(lora.get("license_flags", 127) & (1 << 1))
|
||||||
]
|
]
|
||||||
|
|
||||||
if include_empty_lora:
|
|
||||||
|
|
||||||
available_loras.append({
|
|
||||||
"file_name": "None",
|
|
||||||
"model_name": "None",
|
|
||||||
"file_path": "",
|
|
||||||
"folder": "",
|
|
||||||
"base_model": "",
|
|
||||||
"tags": [],
|
|
||||||
"civitai": {},
|
|
||||||
})
|
|
||||||
|
|
||||||
return available_loras
|
return available_loras
|
||||||
|
|
||||||
async def get_cycler_list(
|
async def get_cycler_list(
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
<div class="lora-cycler-widget">
|
<div class="lora-cycler-widget">
|
||||||
<LoraCyclerSettingsView
|
<LoraCyclerSettingsView
|
||||||
:current-index="state.currentIndex.value"
|
:current-index="state.currentIndex.value"
|
||||||
:total-count="state.totalCount.value"
|
:total-count="displayTotalCount"
|
||||||
:current-lora-name="state.currentLoraName.value"
|
:current-lora-name="displayLoraName"
|
||||||
:current-lora-filename="state.currentLoraFilename.value"
|
:current-lora-filename="state.currentLoraFilename.value"
|
||||||
:model-strength="state.modelStrength.value"
|
:model-strength="state.modelStrength.value"
|
||||||
:clip-strength="state.clipStrength.value"
|
:clip-strength="state.clipStrength.value"
|
||||||
@@ -16,11 +16,14 @@
|
|||||||
:is-pause-disabled="hasQueuedPrompts"
|
:is-pause-disabled="hasQueuedPrompts"
|
||||||
:is-workflow-executing="state.isWorkflowExecuting.value"
|
:is-workflow-executing="state.isWorkflowExecuting.value"
|
||||||
:executing-repeat-step="state.executingRepeatStep.value"
|
:executing-repeat-step="state.executingRepeatStep.value"
|
||||||
|
:include-no-lora="state.includeNoLora.value"
|
||||||
|
:is-no-lora="isNoLora"
|
||||||
@update:current-index="handleIndexUpdate"
|
@update:current-index="handleIndexUpdate"
|
||||||
@update:model-strength="state.modelStrength.value = $event"
|
@update:model-strength="state.modelStrength.value = $event"
|
||||||
@update:clip-strength="state.clipStrength.value = $event"
|
@update:clip-strength="state.clipStrength.value = $event"
|
||||||
@update:use-custom-clip-range="handleUseCustomClipRangeChange"
|
@update:use-custom-clip-range="handleUseCustomClipRangeChange"
|
||||||
@update:repeat-count="handleRepeatCountChange"
|
@update:repeat-count="handleRepeatCountChange"
|
||||||
|
@update:include-no-lora="handleIncludeNoLoraChange"
|
||||||
@toggle-pause="handleTogglePause"
|
@toggle-pause="handleTogglePause"
|
||||||
@reset-index="handleResetIndex"
|
@reset-index="handleResetIndex"
|
||||||
@open-lora-selector="isModalOpen = true"
|
@open-lora-selector="isModalOpen = true"
|
||||||
@@ -30,6 +33,7 @@
|
|||||||
:visible="isModalOpen"
|
:visible="isModalOpen"
|
||||||
:lora-list="cachedLoraList"
|
:lora-list="cachedLoraList"
|
||||||
:current-index="state.currentIndex.value"
|
:current-index="state.currentIndex.value"
|
||||||
|
:include-no-lora="state.includeNoLora.value"
|
||||||
@close="isModalOpen = false"
|
@close="isModalOpen = false"
|
||||||
@select="handleModalSelect"
|
@select="handleModalSelect"
|
||||||
/>
|
/>
|
||||||
@@ -37,7 +41,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref, computed } from 'vue'
|
||||||
import LoraCyclerSettingsView from './lora-cycler/LoraCyclerSettingsView.vue'
|
import LoraCyclerSettingsView from './lora-cycler/LoraCyclerSettingsView.vue'
|
||||||
import LoraListModal from './lora-cycler/LoraListModal.vue'
|
import LoraListModal from './lora-cycler/LoraListModal.vue'
|
||||||
import { useLoraCyclerState } from '../composables/useLoraCyclerState'
|
import { useLoraCyclerState } from '../composables/useLoraCyclerState'
|
||||||
@@ -102,6 +106,31 @@ const isModalOpen = ref(false)
|
|||||||
// Cache for LoRA list (used by modal)
|
// Cache for LoRA list (used by modal)
|
||||||
const cachedLoraList = ref<LoraItem[]>([])
|
const cachedLoraList = ref<LoraItem[]>([])
|
||||||
|
|
||||||
|
// Computed: display total count (includes no lora option if enabled)
|
||||||
|
const displayTotalCount = computed(() => {
|
||||||
|
const baseCount = state.totalCount.value
|
||||||
|
return state.includeNoLora.value ? baseCount + 1 : baseCount
|
||||||
|
})
|
||||||
|
|
||||||
|
// Computed: display LoRA name (shows "No LoRA" if on the last index and includeNoLora is enabled)
|
||||||
|
const displayLoraName = computed(() => {
|
||||||
|
const currentIndex = state.currentIndex.value
|
||||||
|
const totalCount = state.totalCount.value
|
||||||
|
|
||||||
|
// If includeNoLora is enabled and we're on the last position (no lora slot)
|
||||||
|
if (state.includeNoLora.value && currentIndex === totalCount + 1) {
|
||||||
|
return 'No LoRA'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise show the normal LoRA name
|
||||||
|
return state.currentLoraName.value
|
||||||
|
})
|
||||||
|
|
||||||
|
// Computed: check if currently on "No LoRA" option
|
||||||
|
const isNoLora = computed(() => {
|
||||||
|
return state.includeNoLora.value && state.currentIndex.value === state.totalCount.value + 1
|
||||||
|
})
|
||||||
|
|
||||||
// Get pool config from connected node
|
// Get pool config from connected node
|
||||||
const getPoolConfig = (): LoraPoolConfig | null => {
|
const getPoolConfig = (): LoraPoolConfig | null => {
|
||||||
// Check if getPoolConfig method exists on node (added by main.ts)
|
// Check if getPoolConfig method exists on node (added by main.ts)
|
||||||
@@ -113,7 +142,17 @@ const getPoolConfig = (): LoraPoolConfig | null => {
|
|||||||
|
|
||||||
// Update display from LoRA list and index
|
// Update display from LoRA list and index
|
||||||
const updateDisplayFromLoraList = (loraList: LoraItem[], index: number) => {
|
const updateDisplayFromLoraList = (loraList: LoraItem[], index: number) => {
|
||||||
if (loraList.length > 0 && index > 0 && index <= loraList.length) {
|
const actualLoraCount = loraList.length
|
||||||
|
|
||||||
|
// If index is beyond actual LoRA count, it means we're on the "no lora" option
|
||||||
|
if (state.includeNoLora.value && index === actualLoraCount + 1) {
|
||||||
|
state.currentLoraName.value = 'No LoRA'
|
||||||
|
state.currentLoraFilename.value = 'No LoRA'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, show normal LoRA info
|
||||||
|
if (actualLoraCount > 0 && index > 0 && index <= actualLoraCount) {
|
||||||
const currentLora = loraList[index - 1]
|
const currentLora = loraList[index - 1]
|
||||||
if (currentLora) {
|
if (currentLora) {
|
||||||
state.currentLoraName.value = currentLora.file_name
|
state.currentLoraName.value = currentLora.file_name
|
||||||
@@ -124,6 +163,14 @@ const updateDisplayFromLoraList = (loraList: LoraItem[], index: number) => {
|
|||||||
|
|
||||||
// Handle index update from user
|
// Handle index update from user
|
||||||
const handleIndexUpdate = async (newIndex: number) => {
|
const handleIndexUpdate = async (newIndex: number) => {
|
||||||
|
// Calculate max valid index (includes no lora slot if enabled)
|
||||||
|
const maxIndex = state.includeNoLora.value
|
||||||
|
? state.totalCount.value + 1
|
||||||
|
: state.totalCount.value
|
||||||
|
|
||||||
|
// Clamp index to valid range
|
||||||
|
const clampedIndex = Math.max(1, Math.min(newIndex, maxIndex || 1))
|
||||||
|
|
||||||
// Reset execution state when user manually changes index
|
// Reset execution state when user manually changes index
|
||||||
// This ensures the next execution starts from the user-set index
|
// This ensures the next execution starts from the user-set index
|
||||||
;(props.widget as any)[HAS_EXECUTED] = false
|
;(props.widget as any)[HAS_EXECUTED] = false
|
||||||
@@ -134,14 +181,14 @@ const handleIndexUpdate = async (newIndex: number) => {
|
|||||||
executionQueue.length = 0
|
executionQueue.length = 0
|
||||||
hasQueuedPrompts.value = false
|
hasQueuedPrompts.value = false
|
||||||
|
|
||||||
state.setIndex(newIndex)
|
state.setIndex(clampedIndex)
|
||||||
|
|
||||||
// Refresh list to update current LoRA display
|
// Refresh list to update current LoRA display
|
||||||
try {
|
try {
|
||||||
const poolConfig = getPoolConfig()
|
const poolConfig = getPoolConfig()
|
||||||
const loraList = await state.fetchCyclerList(poolConfig)
|
const loraList = await state.fetchCyclerList(poolConfig)
|
||||||
cachedLoraList.value = loraList
|
cachedLoraList.value = loraList
|
||||||
updateDisplayFromLoraList(loraList, newIndex)
|
updateDisplayFromLoraList(loraList, clampedIndex)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[LoraCyclerWidget] Error updating index:', error)
|
console.error('[LoraCyclerWidget] Error updating index:', error)
|
||||||
}
|
}
|
||||||
@@ -169,6 +216,17 @@ const handleRepeatCountChange = (newValue: number) => {
|
|||||||
state.displayRepeatUsed.value = 0
|
state.displayRepeatUsed.value = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle include no lora toggle
|
||||||
|
const handleIncludeNoLoraChange = (newValue: boolean) => {
|
||||||
|
state.includeNoLora.value = newValue
|
||||||
|
|
||||||
|
// If turning off and current index is beyond the actual LoRA count,
|
||||||
|
// clamp it to the last valid LoRA index
|
||||||
|
if (!newValue && state.currentIndex.value > state.totalCount.value) {
|
||||||
|
state.currentIndex.value = Math.max(1, state.totalCount.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle pause toggle
|
// Handle pause toggle
|
||||||
const handleTogglePause = () => {
|
const handleTogglePause = () => {
|
||||||
state.togglePause()
|
state.togglePause()
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
:exclude-folders="state.excludeFolders.value"
|
:exclude-folders="state.excludeFolders.value"
|
||||||
:no-credit-required="state.noCreditRequired.value"
|
:no-credit-required="state.noCreditRequired.value"
|
||||||
:allow-selling="state.allowSelling.value"
|
:allow-selling="state.allowSelling.value"
|
||||||
:include-empty-lora="state.includeEmptyLora.value"
|
|
||||||
:preview-items="state.previewItems.value"
|
:preview-items="state.previewItems.value"
|
||||||
:match-count="state.matchCount.value"
|
:match-count="state.matchCount.value"
|
||||||
:is-loading="state.isLoading.value"
|
:is-loading="state.isLoading.value"
|
||||||
@@ -19,7 +18,6 @@
|
|||||||
@update:exclude-folders="state.excludeFolders.value = $event"
|
@update:exclude-folders="state.excludeFolders.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"
|
||||||
@update:include-empty-lora="state.includeEmptyLora.value = $event"
|
|
||||||
@refresh="state.refreshPreview"
|
@refresh="state.refreshPreview"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,9 @@
|
|||||||
@click="handleOpenSelector"
|
@click="handleOpenSelector"
|
||||||
>
|
>
|
||||||
<span class="progress-label">{{ isWorkflowExecuting ? 'Using LoRA:' : 'Next LoRA:' }}</span>
|
<span class="progress-label">{{ isWorkflowExecuting ? 'Using LoRA:' : 'Next LoRA:' }}</span>
|
||||||
<span class="progress-name clickable" :class="{ disabled: isPauseDisabled }" :title="currentLoraFilename">
|
<span class="progress-name clickable"
|
||||||
|
:class="{ disabled: isPauseDisabled, 'no-lora': isNoLora }"
|
||||||
|
:title="currentLoraFilename">
|
||||||
{{ currentLoraName || 'None' }}
|
{{ currentLoraName || 'None' }}
|
||||||
<svg class="selector-icon" viewBox="0 0 24 24" fill="currentColor">
|
<svg class="selector-icon" viewBox="0 0 24 24" fill="currentColor">
|
||||||
<path d="M7 10l5 5 5-5z"/>
|
<path d="M7 10l5 5 5-5z"/>
|
||||||
@@ -160,6 +162,27 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Include No LoRA Toggle -->
|
||||||
|
<div class="setting-section">
|
||||||
|
<div class="section-header-with-toggle">
|
||||||
|
<label class="setting-label">
|
||||||
|
Add "No LoRA" step
|
||||||
|
</label>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="toggle-switch"
|
||||||
|
:class="{ 'toggle-switch--active': includeNoLora }"
|
||||||
|
@click="$emit('update:includeNoLora', !includeNoLora)"
|
||||||
|
role="switch"
|
||||||
|
:aria-checked="includeNoLora"
|
||||||
|
title="Add an iteration without LoRA for comparison"
|
||||||
|
>
|
||||||
|
<span class="toggle-switch__track"></span>
|
||||||
|
<span class="toggle-switch__thumb"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -182,6 +205,8 @@ const props = defineProps<{
|
|||||||
isPauseDisabled: boolean
|
isPauseDisabled: boolean
|
||||||
isWorkflowExecuting: boolean
|
isWorkflowExecuting: boolean
|
||||||
executingRepeatStep: number
|
executingRepeatStep: number
|
||||||
|
includeNoLora: boolean
|
||||||
|
isNoLora?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -190,6 +215,7 @@ const emit = defineEmits<{
|
|||||||
'update:clipStrength': [value: number]
|
'update:clipStrength': [value: number]
|
||||||
'update:useCustomClipRange': [value: boolean]
|
'update:useCustomClipRange': [value: boolean]
|
||||||
'update:repeatCount': [value: number]
|
'update:repeatCount': [value: number]
|
||||||
|
'update:includeNoLora': [value: boolean]
|
||||||
'toggle-pause': []
|
'toggle-pause': []
|
||||||
'reset-index': []
|
'reset-index': []
|
||||||
'open-lora-selector': []
|
'open-lora-selector': []
|
||||||
@@ -346,6 +372,16 @@ const onRepeatBlur = (event: Event) => {
|
|||||||
color: rgba(191, 219, 254, 1);
|
color: rgba(191, 219, 254, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.progress-name.no-lora {
|
||||||
|
font-style: italic;
|
||||||
|
color: rgba(226, 232, 240, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-name.clickable.no-lora:hover:not(.disabled) {
|
||||||
|
background: rgba(160, 174, 192, 0.2);
|
||||||
|
color: rgba(226, 232, 240, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
.progress-name.clickable.disabled {
|
.progress-name.clickable.disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
|
|||||||
@@ -35,7 +35,10 @@
|
|||||||
v-for="item in filteredList"
|
v-for="item in filteredList"
|
||||||
:key="item.index"
|
:key="item.index"
|
||||||
class="lora-item"
|
class="lora-item"
|
||||||
:class="{ active: currentIndex === item.index }"
|
:class="{
|
||||||
|
active: currentIndex === item.index,
|
||||||
|
'no-lora-item': item.lora.file_name === 'No LoRA'
|
||||||
|
}"
|
||||||
@mouseenter="showPreview(item.lora.file_name, $event)"
|
@mouseenter="showPreview(item.lora.file_name, $event)"
|
||||||
@mouseleave="hidePreview"
|
@mouseleave="hidePreview"
|
||||||
@click="selectLora(item.index)"
|
@click="selectLora(item.index)"
|
||||||
@@ -65,6 +68,7 @@ const props = defineProps<{
|
|||||||
visible: boolean
|
visible: boolean
|
||||||
loraList: LoraItem[]
|
loraList: LoraItem[]
|
||||||
currentIndex: number
|
currentIndex: number
|
||||||
|
includeNoLora?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -79,7 +83,8 @@ const searchInputRef = ref<HTMLInputElement | null>(null)
|
|||||||
let previewTooltip: any = null
|
let previewTooltip: any = null
|
||||||
|
|
||||||
const subtitleText = computed(() => {
|
const subtitleText = computed(() => {
|
||||||
const total = props.loraList.length
|
const baseTotal = props.loraList.length
|
||||||
|
const total = props.includeNoLora ? baseTotal + 1 : baseTotal
|
||||||
const filtered = filteredList.value.length
|
const filtered = filteredList.value.length
|
||||||
if (filtered === total) {
|
if (filtered === total) {
|
||||||
return `Total: ${total} LoRA${total !== 1 ? 's' : ''}`
|
return `Total: ${total} LoRA${total !== 1 ? 's' : ''}`
|
||||||
@@ -88,11 +93,19 @@ const subtitleText = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const filteredList = computed<LoraListItem[]>(() => {
|
const filteredList = computed<LoraListItem[]>(() => {
|
||||||
const list = props.loraList.map((lora, idx) => ({
|
const list: LoraListItem[] = props.loraList.map((lora, idx) => ({
|
||||||
index: idx + 1,
|
index: idx + 1,
|
||||||
lora
|
lora
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// Add "No LoRA" option at the end if includeNoLora is enabled
|
||||||
|
if (props.includeNoLora) {
|
||||||
|
list.push({
|
||||||
|
index: list.length + 1,
|
||||||
|
lora: { file_name: 'No LoRA' } as LoraItem
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (!searchQuery.value.trim()) {
|
if (!searchQuery.value.trim()) {
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
@@ -303,6 +316,15 @@ onUnmounted(() => {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.lora-item.no-lora-item .lora-name {
|
||||||
|
font-style: italic;
|
||||||
|
color: rgba(226, 232, 240, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lora-item.no-lora-item:hover .lora-name {
|
||||||
|
color: rgba(226, 232, 240, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
.no-results {
|
.no-results {
|
||||||
padding: 32px 20px;
|
padding: 32px 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -27,10 +27,8 @@
|
|||||||
<LicenseSection
|
<LicenseSection
|
||||||
:no-credit-required="noCreditRequired"
|
:no-credit-required="noCreditRequired"
|
||||||
:allow-selling="allowSelling"
|
:allow-selling="allowSelling"
|
||||||
:include-empty-lora="includeEmptyLora"
|
|
||||||
@update:no-credit-required="$emit('update:noCreditRequired', $event)"
|
@update:no-credit-required="$emit('update:noCreditRequired', $event)"
|
||||||
@update:allow-selling="$emit('update:allowSelling', $event)"
|
@update:allow-selling="$emit('update:allowSelling', $event)"
|
||||||
@update:include-empty-lora="$emit('update:includeEmptyLora', $event)"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -63,10 +61,9 @@ defineProps<{
|
|||||||
// Folders
|
// Folders
|
||||||
includeFolders: string[]
|
includeFolders: string[]
|
||||||
excludeFolders: string[]
|
excludeFolders: string[]
|
||||||
// License & Misc
|
// License
|
||||||
noCreditRequired: boolean
|
noCreditRequired: boolean
|
||||||
allowSelling: boolean
|
allowSelling: boolean
|
||||||
includeEmptyLora: boolean
|
|
||||||
// Preview
|
// Preview
|
||||||
previewItems: LoraItem[]
|
previewItems: LoraItem[]
|
||||||
matchCount: number
|
matchCount: number
|
||||||
@@ -79,7 +76,6 @@ defineEmits<{
|
|||||||
'update:excludeFolders': [value: string[]]
|
'update:excludeFolders': [value: string[]]
|
||||||
'update:noCreditRequired': [value: boolean]
|
'update:noCreditRequired': [value: boolean]
|
||||||
'update:allowSelling': [value: boolean]
|
'update:allowSelling': [value: boolean]
|
||||||
'update:includeEmptyLora': [value: boolean]
|
|
||||||
refresh: []
|
refresh: []
|
||||||
}>()
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="section__header">
|
<div class="section__header">
|
||||||
<span class="section__title">LICENSE & OPTIONS</span>
|
<span class="section__title">LICENSE</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="section__toggles">
|
<div class="section__toggles">
|
||||||
<label class="toggle-item">
|
<label class="toggle-item">
|
||||||
@@ -33,22 +33,6 @@
|
|||||||
<span class="toggle-switch__thumb"></span>
|
<span class="toggle-switch__thumb"></span>
|
||||||
</button>
|
</button>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="toggle-item">
|
|
||||||
<span class="toggle-item__label">Include No LoRAs</span>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="toggle-switch"
|
|
||||||
:class="{ 'toggle-switch--active': includeEmptyLora }"
|
|
||||||
@click="$emit('update:includeEmptyLora', !includeEmptyLora)"
|
|
||||||
role="switch"
|
|
||||||
:aria-checked="includeEmptyLora"
|
|
||||||
title="Include an empty/blank LoRA option in the pool results"
|
|
||||||
>
|
|
||||||
<span class="toggle-switch__track"></span>
|
|
||||||
<span class="toggle-switch__thumb"></span>
|
|
||||||
</button>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -57,13 +41,11 @@
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
noCreditRequired: boolean
|
noCreditRequired: boolean
|
||||||
allowSelling: boolean
|
allowSelling: boolean
|
||||||
includeEmptyLora: boolean
|
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
'update:noCreditRequired': [value: boolean]
|
'update:noCreditRequired': [value: boolean]
|
||||||
'update:allowSelling': [value: boolean]
|
'update:allowSelling': [value: boolean]
|
||||||
'update:includeEmptyLora': [value: boolean]
|
|
||||||
}>()
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -87,7 +69,6 @@ defineEmits<{
|
|||||||
|
|
||||||
.section__toggles {
|
.section__toggles {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export interface LoraPoolConfig {
|
|||||||
noCreditRequired: boolean
|
noCreditRequired: boolean
|
||||||
allowSelling: boolean
|
allowSelling: boolean
|
||||||
}
|
}
|
||||||
includeEmptyLora: boolean
|
includeEmptyLora?: boolean // Optional, deprecated (moved to Cycler)
|
||||||
}
|
}
|
||||||
preview: { matchCount: number; lastUpdated: number }
|
preview: { matchCount: number; lastUpdated: number }
|
||||||
}
|
}
|
||||||
@@ -85,6 +85,8 @@ export interface CyclerConfig {
|
|||||||
repeat_count: number // How many times each LoRA should repeat (default: 1)
|
repeat_count: number // How many times each LoRA should repeat (default: 1)
|
||||||
repeat_used: number // How many times current index has been used
|
repeat_used: number // How many times current index has been used
|
||||||
is_paused: boolean // Whether iteration is paused
|
is_paused: boolean // Whether iteration is paused
|
||||||
|
// Include "no LoRA" option in cycle
|
||||||
|
include_no_lora: boolean // Whether to include empty LoRA option
|
||||||
}
|
}
|
||||||
|
|
||||||
// Widget config union type
|
// Widget config union type
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { ComponentWidget, CyclerConfig, LoraPoolConfig } from './types'
|
|||||||
export interface CyclerLoraItem {
|
export interface CyclerLoraItem {
|
||||||
file_name: string
|
file_name: string
|
||||||
model_name: string
|
model_name: string
|
||||||
|
file_path: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useLoraCyclerState(widget: ComponentWidget<CyclerConfig>) {
|
export function useLoraCyclerState(widget: ComponentWidget<CyclerConfig>) {
|
||||||
@@ -34,6 +35,7 @@ export function useLoraCyclerState(widget: ComponentWidget<CyclerConfig>) {
|
|||||||
const repeatUsed = ref(0) // How many times current index has been used (internal tracking)
|
const repeatUsed = ref(0) // How many times current index has been used (internal tracking)
|
||||||
const displayRepeatUsed = ref(0) // For UI display, deferred updates like currentIndex
|
const displayRepeatUsed = ref(0) // For UI display, deferred updates like currentIndex
|
||||||
const isPaused = ref(false) // Whether iteration is paused
|
const isPaused = ref(false) // Whether iteration is paused
|
||||||
|
const includeNoLora = ref(false) // Whether to include empty LoRA option in cycle
|
||||||
|
|
||||||
// Execution progress tracking (visual feedback)
|
// Execution progress tracking (visual feedback)
|
||||||
const isWorkflowExecuting = ref(false) // Workflow is currently running
|
const isWorkflowExecuting = ref(false) // Workflow is currently running
|
||||||
@@ -58,6 +60,7 @@ export function useLoraCyclerState(widget: ComponentWidget<CyclerConfig>) {
|
|||||||
repeat_count: repeatCount.value,
|
repeat_count: repeatCount.value,
|
||||||
repeat_used: repeatUsed.value,
|
repeat_used: repeatUsed.value,
|
||||||
is_paused: isPaused.value,
|
is_paused: isPaused.value,
|
||||||
|
include_no_lora: includeNoLora.value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -75,6 +78,7 @@ export function useLoraCyclerState(widget: ComponentWidget<CyclerConfig>) {
|
|||||||
repeat_count: repeatCount.value,
|
repeat_count: repeatCount.value,
|
||||||
repeat_used: repeatUsed.value,
|
repeat_used: repeatUsed.value,
|
||||||
is_paused: isPaused.value,
|
is_paused: isPaused.value,
|
||||||
|
include_no_lora: includeNoLora.value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,12 +97,13 @@ export function useLoraCyclerState(widget: ComponentWidget<CyclerConfig>) {
|
|||||||
sortBy.value = config.sort_by || 'filename'
|
sortBy.value = config.sort_by || 'filename'
|
||||||
currentLoraName.value = config.current_lora_name || ''
|
currentLoraName.value = config.current_lora_name || ''
|
||||||
currentLoraFilename.value = config.current_lora_filename || ''
|
currentLoraFilename.value = config.current_lora_filename || ''
|
||||||
// Advanced index control features
|
// Advanced index control features
|
||||||
repeatCount.value = config.repeat_count ?? 1
|
repeatCount.value = config.repeat_count ?? 1
|
||||||
repeatUsed.value = config.repeat_used ?? 0
|
repeatUsed.value = config.repeat_used ?? 0
|
||||||
isPaused.value = config.is_paused ?? false
|
isPaused.value = config.is_paused ?? false
|
||||||
// Note: execution_index and next_index are not restored from config
|
includeNoLora.value = config.include_no_lora ?? false
|
||||||
// as they are transient values used only during batch execution
|
// Note: execution_index and next_index are not restored from config
|
||||||
|
// as they are transient values used only during batch execution
|
||||||
} finally {
|
} finally {
|
||||||
isRestoring = false
|
isRestoring = false
|
||||||
}
|
}
|
||||||
@@ -111,7 +116,9 @@ export function useLoraCyclerState(widget: ComponentWidget<CyclerConfig>) {
|
|||||||
// Calculate the next index (wrap to 1 if at end)
|
// Calculate the next index (wrap to 1 if at end)
|
||||||
const current = executionIndex.value ?? currentIndex.value
|
const current = executionIndex.value ?? currentIndex.value
|
||||||
let next = current + 1
|
let next = current + 1
|
||||||
if (totalCount.value > 0 && next > totalCount.value) {
|
// Total count includes no lora option if enabled
|
||||||
|
const effectiveTotalCount = includeNoLora.value ? totalCount.value + 1 : totalCount.value
|
||||||
|
if (effectiveTotalCount > 0 && next > effectiveTotalCount) {
|
||||||
next = 1
|
next = 1
|
||||||
}
|
}
|
||||||
nextIndex.value = next
|
nextIndex.value = next
|
||||||
@@ -122,7 +129,9 @@ export function useLoraCyclerState(widget: ComponentWidget<CyclerConfig>) {
|
|||||||
if (nextIndex.value === null) {
|
if (nextIndex.value === null) {
|
||||||
// First execution uses current_index, so next is current + 1
|
// First execution uses current_index, so next is current + 1
|
||||||
let next = currentIndex.value + 1
|
let next = currentIndex.value + 1
|
||||||
if (totalCount.value > 0 && next > totalCount.value) {
|
// Total count includes no lora option if enabled
|
||||||
|
const effectiveTotalCount = includeNoLora.value ? totalCount.value + 1 : totalCount.value
|
||||||
|
if (effectiveTotalCount > 0 && next > effectiveTotalCount) {
|
||||||
next = 1
|
next = 1
|
||||||
}
|
}
|
||||||
nextIndex.value = next
|
nextIndex.value = next
|
||||||
@@ -230,7 +239,9 @@ export function useLoraCyclerState(widget: ComponentWidget<CyclerConfig>) {
|
|||||||
|
|
||||||
// Set index manually
|
// Set index manually
|
||||||
const setIndex = (index: number) => {
|
const setIndex = (index: number) => {
|
||||||
if (index >= 1 && index <= totalCount.value) {
|
// Total count includes no lora option if enabled
|
||||||
|
const effectiveTotalCount = includeNoLora.value ? totalCount.value + 1 : totalCount.value
|
||||||
|
if (index >= 1 && index <= effectiveTotalCount) {
|
||||||
currentIndex.value = index
|
currentIndex.value = index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -272,6 +283,7 @@ export function useLoraCyclerState(widget: ComponentWidget<CyclerConfig>) {
|
|||||||
repeatCount,
|
repeatCount,
|
||||||
repeatUsed,
|
repeatUsed,
|
||||||
isPaused,
|
isPaused,
|
||||||
|
includeNoLora,
|
||||||
], () => {
|
], () => {
|
||||||
widget.value = buildConfig()
|
widget.value = buildConfig()
|
||||||
}, { deep: true })
|
}, { deep: true })
|
||||||
@@ -294,6 +306,7 @@ export function useLoraCyclerState(widget: ComponentWidget<CyclerConfig>) {
|
|||||||
repeatUsed,
|
repeatUsed,
|
||||||
displayRepeatUsed,
|
displayRepeatUsed,
|
||||||
isPaused,
|
isPaused,
|
||||||
|
includeNoLora,
|
||||||
isWorkflowExecuting,
|
isWorkflowExecuting,
|
||||||
executingRepeatStep,
|
executingRepeatStep,
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ export function useLoraPoolApi() {
|
|||||||
foldersExclude?: string[]
|
foldersExclude?: string[]
|
||||||
noCreditRequired?: boolean
|
noCreditRequired?: boolean
|
||||||
allowSelling?: boolean
|
allowSelling?: boolean
|
||||||
includeEmptyLora?: boolean
|
|
||||||
page?: number
|
page?: number
|
||||||
pageSize?: number
|
pageSize?: number
|
||||||
}
|
}
|
||||||
@@ -93,10 +92,6 @@ export function useLoraPoolApi() {
|
|||||||
urlParams.set('allow_selling_generated_content', String(params.allowSelling))
|
urlParams.set('allow_selling_generated_content', String(params.allowSelling))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.includeEmptyLora !== undefined) {
|
|
||||||
urlParams.set('include_empty_lora', String(params.includeEmptyLora))
|
|
||||||
}
|
|
||||||
|
|
||||||
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,7 +24,6 @@ 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 includeEmptyLora = ref(false)
|
|
||||||
|
|
||||||
// Available options from API
|
// Available options from API
|
||||||
const availableBaseModels = ref<BaseModelOption[]>([])
|
const availableBaseModels = ref<BaseModelOption[]>([])
|
||||||
@@ -53,8 +52,7 @@ export function useLoraPoolState(widget: ComponentWidget<LoraPoolConfig>) {
|
|||||||
license: {
|
license: {
|
||||||
noCreditRequired: noCreditRequired.value,
|
noCreditRequired: noCreditRequired.value,
|
||||||
allowSelling: allowSelling.value
|
allowSelling: allowSelling.value
|
||||||
},
|
}
|
||||||
includeEmptyLora: includeEmptyLora.value
|
|
||||||
},
|
},
|
||||||
preview: {
|
preview: {
|
||||||
matchCount: matchCount.value,
|
matchCount: matchCount.value,
|
||||||
@@ -96,7 +94,6 @@ 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(includeEmptyLora, filters.includeEmptyLora ?? 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
|
||||||
@@ -128,7 +125,6 @@ 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,
|
||||||
includeEmptyLora: includeEmptyLora.value || undefined,
|
|
||||||
pageSize: 6
|
pageSize: 6
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -154,8 +150,7 @@ export function useLoraPoolState(widget: ComponentWidget<LoraPoolConfig>) {
|
|||||||
includeFolders,
|
includeFolders,
|
||||||
excludeFolders,
|
excludeFolders,
|
||||||
noCreditRequired,
|
noCreditRequired,
|
||||||
allowSelling,
|
allowSelling
|
||||||
includeEmptyLora
|
|
||||||
], onFilterChange, { deep: true })
|
], onFilterChange, { deep: true })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -167,7 +162,6 @@ export function useLoraPoolState(widget: ComponentWidget<LoraPoolConfig>) {
|
|||||||
excludeFolders,
|
excludeFolders,
|
||||||
noCreditRequired,
|
noCreditRequired,
|
||||||
allowSelling,
|
allowSelling,
|
||||||
includeEmptyLora,
|
|
||||||
|
|
||||||
// Available options
|
// Available options
|
||||||
availableBaseModels,
|
availableBaseModels,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ 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
|
||||||
const LORA_CYCLER_WIDGET_MIN_WIDTH = 380
|
const LORA_CYCLER_WIDGET_MIN_WIDTH = 380
|
||||||
const LORA_CYCLER_WIDGET_MIN_HEIGHT = 314
|
const LORA_CYCLER_WIDGET_MIN_HEIGHT = 344
|
||||||
const LORA_CYCLER_WIDGET_MAX_HEIGHT = LORA_CYCLER_WIDGET_MIN_HEIGHT
|
const LORA_CYCLER_WIDGET_MAX_HEIGHT = LORA_CYCLER_WIDGET_MIN_HEIGHT
|
||||||
const JSON_DISPLAY_WIDGET_MIN_WIDTH = 300
|
const JSON_DISPLAY_WIDGET_MIN_WIDTH = 300
|
||||||
const JSON_DISPLAY_WIDGET_MIN_HEIGHT = 200
|
const JSON_DISPLAY_WIDGET_MIN_HEIGHT = 200
|
||||||
|
|||||||
@@ -84,7 +84,8 @@ describe('useLoraCyclerState', () => {
|
|||||||
current_lora_filename: '',
|
current_lora_filename: '',
|
||||||
repeat_count: 1,
|
repeat_count: 1,
|
||||||
repeat_used: 0,
|
repeat_used: 0,
|
||||||
is_paused: false
|
is_paused: false,
|
||||||
|
include_no_lora: false
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(state.currentIndex.value).toBe(5)
|
expect(state.currentIndex.value).toBe(5)
|
||||||
|
|||||||
4
vue-widgets/tests/fixtures/mockConfigs.ts
vendored
4
vue-widgets/tests/fixtures/mockConfigs.ts
vendored
@@ -24,6 +24,7 @@ export function createMockCyclerConfig(overrides: Partial<CyclerConfig> = {}): C
|
|||||||
repeat_count: 1,
|
repeat_count: 1,
|
||||||
repeat_used: 0,
|
repeat_used: 0,
|
||||||
is_paused: false,
|
is_paused: false,
|
||||||
|
include_no_lora: false,
|
||||||
...overrides
|
...overrides
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,7 +55,8 @@ export function createMockPoolConfig(overrides: Partial<LoraPoolConfig> = {}): L
|
|||||||
export function createMockLoraList(count: number = 5): CyclerLoraItem[] {
|
export function createMockLoraList(count: number = 5): CyclerLoraItem[] {
|
||||||
return Array.from({ length: count }, (_, i) => ({
|
return Array.from({ length: count }, (_, i) => ({
|
||||||
file_name: `lora${i + 1}.safetensors`,
|
file_name: `lora${i + 1}.safetensors`,
|
||||||
model_name: `LoRA Model ${i + 1}`
|
model_name: `LoRA Model ${i + 1}`,
|
||||||
|
file_path: `/models/loras/lora${i + 1}.safetensors`
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user