Compare commits

..

3 Commits

Author SHA1 Message Date
Will Miao
61c31ecbd0 fix: exclude __init__.py from pytest collection to prevent CI import errors 2026-03-19 14:43:45 +08:00
Will Miao
1ae1b0d607 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>
2026-03-19 14:19:49 +08:00
dogatech
8dd849892d Allow for empty lora (no loras option) in Lora Pool 2026-03-19 09:23:03 +08:00
12 changed files with 406 additions and 152 deletions

View File

@@ -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,42 +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:
lora_path, _ = get_lora_info(current_lora["file_name"]) # "No LoRA" option - return empty stack
if not lora_path:
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],
}, },
} }

View File

@@ -1,5 +1,5 @@
[pytest] [pytest]
addopts = -v --import-mode=importlib -m "not performance" addopts = -v --import-mode=importlib -m "not performance" --ignore=__init__.py
testpaths = tests testpaths = tests
python_files = test_*.py python_files = test_*.py
python_classes = Test* python_classes = Test*

View File

@@ -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()

View File

@@ -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;

View File

@@ -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;

View File

@@ -10,6 +10,7 @@ export interface LoraPoolConfig {
noCreditRequired: boolean noCreditRequired: boolean
allowSelling: boolean allowSelling: boolean
} }
includeEmptyLora?: boolean // Optional, deprecated (moved to Cycler)
} }
preview: { matchCount: number; lastUpdated: number } preview: { matchCount: number; lastUpdated: number }
} }
@@ -84,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

View File

@@ -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,

View File

@@ -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

View File

@@ -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)

View File

@@ -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`
})) }))
} }

View File

@@ -1473,16 +1473,16 @@ to { transform: rotate(360deg);
box-sizing: border-box; box-sizing: border-box;
} }
.cycler-settings[data-v-5b16b9d3] { .cycler-settings[data-v-f65566fd] {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: #e4e4e7; color: #e4e4e7;
} }
.settings-header[data-v-5b16b9d3] { .settings-header[data-v-f65566fd] {
margin-bottom: 8px; margin-bottom: 8px;
} }
.settings-title[data-v-5b16b9d3] { .settings-title[data-v-f65566fd] {
font-size: 10px; font-size: 10px;
font-weight: 600; font-weight: 600;
letter-spacing: 0.05em; letter-spacing: 0.05em;
@@ -1491,10 +1491,10 @@ to { transform: rotate(360deg);
margin: 0; margin: 0;
text-transform: uppercase; text-transform: uppercase;
} }
.setting-section[data-v-5b16b9d3] { .setting-section[data-v-f65566fd] {
margin-bottom: 8px; margin-bottom: 8px;
} }
.setting-label[data-v-5b16b9d3] { .setting-label[data-v-f65566fd] {
font-size: 13px; font-size: 13px;
font-weight: 500; font-weight: 500;
color: rgba(226, 232, 240, 0.8); color: rgba(226, 232, 240, 0.8);
@@ -1503,10 +1503,10 @@ to { transform: rotate(360deg);
} }
/* Progress Display */ /* Progress Display */
.progress-section[data-v-5b16b9d3] { .progress-section[data-v-f65566fd] {
margin-bottom: 12px; margin-bottom: 12px;
} }
.progress-display[data-v-5b16b9d3] { .progress-display[data-v-f65566fd] {
background: rgba(26, 32, 44, 0.9); background: rgba(26, 32, 44, 0.9);
border: 1px solid rgba(226, 232, 240, 0.2); border: 1px solid rgba(226, 232, 240, 0.2);
border-radius: 6px; border-radius: 6px;
@@ -1516,31 +1516,31 @@ to { transform: rotate(360deg);
align-items: center; align-items: center;
transition: border-color 0.3s ease; transition: border-color 0.3s ease;
} }
.progress-display.executing[data-v-5b16b9d3] { .progress-display.executing[data-v-f65566fd] {
border-color: rgba(66, 153, 225, 0.5); border-color: rgba(66, 153, 225, 0.5);
animation: pulse-5b16b9d3 2s ease-in-out infinite; animation: pulse-f65566fd 2s ease-in-out infinite;
} }
@keyframes pulse-5b16b9d3 { @keyframes pulse-f65566fd {
0%, 100% { border-color: rgba(66, 153, 225, 0.3); 0%, 100% { border-color: rgba(66, 153, 225, 0.3);
} }
50% { border-color: rgba(66, 153, 225, 0.7); 50% { border-color: rgba(66, 153, 225, 0.7);
} }
} }
.progress-info[data-v-5b16b9d3] { .progress-info[data-v-f65566fd] {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 2px; gap: 2px;
min-width: 0; min-width: 0;
flex: 1; flex: 1;
} }
.progress-label[data-v-5b16b9d3] { .progress-label[data-v-f65566fd] {
font-size: 10px; font-size: 10px;
font-weight: 500; font-weight: 500;
color: rgba(226, 232, 240, 0.5); color: rgba(226, 232, 240, 0.5);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.03em; letter-spacing: 0.03em;
} }
.progress-name[data-v-5b16b9d3] { .progress-name[data-v-f65566fd] {
font-size: 13px; font-size: 13px;
font-weight: 500; font-weight: 500;
color: rgba(191, 219, 254, 1); color: rgba(191, 219, 254, 1);
@@ -1548,7 +1548,7 @@ to { transform: rotate(360deg);
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.progress-name.clickable[data-v-5b16b9d3] { .progress-name.clickable[data-v-f65566fd] {
cursor: pointer; cursor: pointer;
padding: 2px 6px; padding: 2px 6px;
margin: -2px -6px; margin: -2px -6px;
@@ -1558,34 +1558,42 @@ to { transform: rotate(360deg);
align-items: center; align-items: center;
gap: 4px; gap: 4px;
} }
.progress-name.clickable[data-v-5b16b9d3]:hover:not(.disabled) { .progress-name.clickable[data-v-f65566fd]:hover:not(.disabled) {
background: rgba(66, 153, 225, 0.2); background: rgba(66, 153, 225, 0.2);
color: rgba(191, 219, 254, 1); color: rgba(191, 219, 254, 1);
} }
.progress-name.clickable.disabled[data-v-5b16b9d3] { .progress-name.no-lora[data-v-f65566fd] {
font-style: italic;
color: rgba(226, 232, 240, 0.6);
}
.progress-name.clickable.no-lora[data-v-f65566fd]:hover:not(.disabled) {
background: rgba(160, 174, 192, 0.2);
color: rgba(226, 232, 240, 0.8);
}
.progress-name.clickable.disabled[data-v-f65566fd] {
cursor: not-allowed; cursor: not-allowed;
opacity: 0.5; opacity: 0.5;
} }
.progress-info.disabled[data-v-5b16b9d3] { .progress-info.disabled[data-v-f65566fd] {
cursor: not-allowed; cursor: not-allowed;
} }
.selector-icon[data-v-5b16b9d3] { .selector-icon[data-v-f65566fd] {
width: 16px; width: 16px;
height: 16px; height: 16px;
opacity: 0.5; opacity: 0.5;
flex-shrink: 0; flex-shrink: 0;
} }
.progress-name.clickable:hover .selector-icon[data-v-5b16b9d3] { .progress-name.clickable:hover .selector-icon[data-v-f65566fd] {
opacity: 0.8; opacity: 0.8;
} }
.progress-counter[data-v-5b16b9d3] { .progress-counter[data-v-f65566fd] {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 4px; gap: 4px;
padding-left: 12px; padding-left: 12px;
flex-shrink: 0; flex-shrink: 0;
} }
.progress-index[data-v-5b16b9d3] { .progress-index[data-v-f65566fd] {
font-size: 18px; font-size: 18px;
font-weight: 600; font-weight: 600;
color: rgba(66, 153, 225, 1); color: rgba(66, 153, 225, 1);
@@ -1594,12 +1602,12 @@ to { transform: rotate(360deg);
text-align: right; text-align: right;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
} }
.progress-separator[data-v-5b16b9d3] { .progress-separator[data-v-f65566fd] {
font-size: 14px; font-size: 14px;
color: rgba(226, 232, 240, 0.4); color: rgba(226, 232, 240, 0.4);
margin: 0 2px; margin: 0 2px;
} }
.progress-total[data-v-5b16b9d3] { .progress-total[data-v-f65566fd] {
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
color: rgba(226, 232, 240, 0.6); color: rgba(226, 232, 240, 0.6);
@@ -1610,7 +1618,7 @@ to { transform: rotate(360deg);
} }
/* Repeat Progress */ /* Repeat Progress */
.repeat-progress[data-v-5b16b9d3] { .repeat-progress[data-v-f65566fd] {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px; gap: 6px;
@@ -1620,23 +1628,23 @@ to { transform: rotate(360deg);
border: 1px solid rgba(226, 232, 240, 0.1); border: 1px solid rgba(226, 232, 240, 0.1);
border-radius: 4px; border-radius: 4px;
} }
.repeat-progress-track[data-v-5b16b9d3] { .repeat-progress-track[data-v-f65566fd] {
width: 32px; width: 32px;
height: 4px; height: 4px;
background: rgba(226, 232, 240, 0.15); background: rgba(226, 232, 240, 0.15);
border-radius: 2px; border-radius: 2px;
overflow: hidden; overflow: hidden;
} }
.repeat-progress-fill[data-v-5b16b9d3] { .repeat-progress-fill[data-v-f65566fd] {
height: 100%; height: 100%;
background: linear-gradient(90deg, #f59e0b, #fbbf24); background: linear-gradient(90deg, #f59e0b, #fbbf24);
border-radius: 2px; border-radius: 2px;
transition: width 0.3s ease; transition: width 0.3s ease;
} }
.repeat-progress-fill.is-complete[data-v-5b16b9d3] { .repeat-progress-fill.is-complete[data-v-f65566fd] {
background: linear-gradient(90deg, #10b981, #34d399); background: linear-gradient(90deg, #10b981, #34d399);
} }
.repeat-progress-text[data-v-5b16b9d3] { .repeat-progress-text[data-v-f65566fd] {
font-size: 10px; font-size: 10px;
font-family: 'SF Mono', 'Roboto Mono', monospace; font-family: 'SF Mono', 'Roboto Mono', monospace;
color: rgba(253, 230, 138, 0.9); color: rgba(253, 230, 138, 0.9);
@@ -1645,19 +1653,19 @@ to { transform: rotate(360deg);
} }
/* Index Controls Row - Grouped Layout */ /* Index Controls Row - Grouped Layout */
.index-controls-row[data-v-5b16b9d3] { .index-controls-row[data-v-f65566fd] {
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
gap: 16px; gap: 16px;
} }
/* Control Group */ /* Control Group */
.control-group[data-v-5b16b9d3] { .control-group[data-v-f65566fd] {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 6px; gap: 6px;
} }
.control-group-label[data-v-5b16b9d3] { .control-group-label[data-v-f65566fd] {
font-size: 11px; font-size: 11px;
font-weight: 500; font-weight: 500;
color: rgba(226, 232, 240, 0.5); color: rgba(226, 232, 240, 0.5);
@@ -1665,13 +1673,13 @@ to { transform: rotate(360deg);
letter-spacing: 0.03em; letter-spacing: 0.03em;
line-height: 1; line-height: 1;
} }
.control-group-content[data-v-5b16b9d3] { .control-group-content[data-v-f65566fd] {
display: flex; display: flex;
align-items: baseline; align-items: baseline;
gap: 4px; gap: 4px;
height: 32px; height: 32px;
} }
.index-input[data-v-5b16b9d3] { .index-input[data-v-f65566fd] {
width: 50px; width: 50px;
height: 32px; height: 32px;
padding: 0 8px; padding: 0 8px;
@@ -1684,15 +1692,15 @@ to { transform: rotate(360deg);
line-height: 32px; line-height: 32px;
box-sizing: border-box; box-sizing: border-box;
} }
.index-input[data-v-5b16b9d3]:focus { .index-input[data-v-f65566fd]:focus {
outline: none; outline: none;
border-color: rgba(66, 153, 225, 0.6); border-color: rgba(66, 153, 225, 0.6);
} }
.index-input[data-v-5b16b9d3]:disabled { .index-input[data-v-f65566fd]:disabled {
opacity: 0.4; opacity: 0.4;
cursor: not-allowed; cursor: not-allowed;
} }
.index-hint[data-v-5b16b9d3] { .index-hint[data-v-f65566fd] {
font-size: 12px; font-size: 12px;
color: rgba(226, 232, 240, 0.4); color: rgba(226, 232, 240, 0.4);
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
@@ -1700,7 +1708,7 @@ to { transform: rotate(360deg);
} }
/* Repeat Controls */ /* Repeat Controls */
.repeat-input[data-v-5b16b9d3] { .repeat-input[data-v-f65566fd] {
width: 50px; width: 50px;
height: 32px; height: 32px;
padding: 0 6px; padding: 0 6px;
@@ -1714,11 +1722,11 @@ to { transform: rotate(360deg);
line-height: 32px; line-height: 32px;
box-sizing: border-box; box-sizing: border-box;
} }
.repeat-input[data-v-5b16b9d3]:focus { .repeat-input[data-v-f65566fd]:focus {
outline: none; outline: none;
border-color: rgba(66, 153, 225, 0.6); border-color: rgba(66, 153, 225, 0.6);
} }
.repeat-suffix[data-v-5b16b9d3] { .repeat-suffix[data-v-f65566fd] {
font-size: 13px; font-size: 13px;
color: rgba(226, 232, 240, 0.4); color: rgba(226, 232, 240, 0.4);
font-weight: 500; font-weight: 500;
@@ -1726,7 +1734,7 @@ to { transform: rotate(360deg);
} }
/* Action Buttons */ /* Action Buttons */
.action-buttons[data-v-5b16b9d3] { .action-buttons[data-v-f65566fd] {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px; gap: 6px;
@@ -1734,7 +1742,7 @@ to { transform: rotate(360deg);
} }
/* Control Buttons */ /* Control Buttons */
.control-btn[data-v-5b16b9d3] { .control-btn[data-v-f65566fd] {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -1748,52 +1756,52 @@ to { transform: rotate(360deg);
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.2s;
} }
.control-btn[data-v-5b16b9d3]:hover:not(:disabled) { .control-btn[data-v-f65566fd]:hover:not(:disabled) {
background: rgba(66, 153, 225, 0.2); background: rgba(66, 153, 225, 0.2);
border-color: rgba(66, 153, 225, 0.4); border-color: rgba(66, 153, 225, 0.4);
color: rgba(191, 219, 254, 1); color: rgba(191, 219, 254, 1);
} }
.control-btn[data-v-5b16b9d3]:disabled { .control-btn[data-v-f65566fd]:disabled {
opacity: 0.4; opacity: 0.4;
cursor: not-allowed; cursor: not-allowed;
} }
.control-btn.active[data-v-5b16b9d3] { .control-btn.active[data-v-f65566fd] {
background: rgba(245, 158, 11, 0.2); background: rgba(245, 158, 11, 0.2);
border-color: rgba(245, 158, 11, 0.5); border-color: rgba(245, 158, 11, 0.5);
color: rgba(253, 230, 138, 1); color: rgba(253, 230, 138, 1);
} }
.control-btn.active[data-v-5b16b9d3]:hover { .control-btn.active[data-v-f65566fd]:hover {
background: rgba(245, 158, 11, 0.3); background: rgba(245, 158, 11, 0.3);
border-color: rgba(245, 158, 11, 0.6); border-color: rgba(245, 158, 11, 0.6);
} }
.control-icon[data-v-5b16b9d3] { .control-icon[data-v-f65566fd] {
width: 14px; width: 14px;
height: 14px; height: 14px;
} }
/* Slider Container */ /* Slider Container */
.slider-container[data-v-5b16b9d3] { .slider-container[data-v-f65566fd] {
background: rgba(26, 32, 44, 0.9); background: rgba(26, 32, 44, 0.9);
border: 1px solid rgba(226, 232, 240, 0.2); border: 1px solid rgba(226, 232, 240, 0.2);
border-radius: 6px; border-radius: 6px;
padding: 6px; padding: 6px;
} }
.slider-container--disabled[data-v-5b16b9d3] { .slider-container--disabled[data-v-f65566fd] {
opacity: 0.5; opacity: 0.5;
pointer-events: none; pointer-events: none;
} }
.section-header-with-toggle[data-v-5b16b9d3] { .section-header-with-toggle[data-v-f65566fd] {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
margin-bottom: 8px; margin-bottom: 8px;
} }
.section-header-with-toggle .setting-label[data-v-5b16b9d3] { .section-header-with-toggle .setting-label[data-v-f65566fd] {
margin-bottom: 4px; margin-bottom: 4px;
} }
/* Toggle Switch */ /* Toggle Switch */
.toggle-switch[data-v-5b16b9d3] { .toggle-switch[data-v-f65566fd] {
position: relative; position: relative;
width: 36px; width: 36px;
height: 20px; height: 20px;
@@ -1802,7 +1810,7 @@ to { transform: rotate(360deg);
border: none; border: none;
cursor: pointer; cursor: pointer;
} }
.toggle-switch__track[data-v-5b16b9d3] { .toggle-switch__track[data-v-f65566fd] {
position: absolute; position: absolute;
inset: 0; inset: 0;
background: var(--comfy-input-bg, #333); background: var(--comfy-input-bg, #333);
@@ -1810,11 +1818,11 @@ to { transform: rotate(360deg);
border-radius: 10px; border-radius: 10px;
transition: all 0.2s; transition: all 0.2s;
} }
.toggle-switch--active .toggle-switch__track[data-v-5b16b9d3] { .toggle-switch--active .toggle-switch__track[data-v-f65566fd] {
background: rgba(66, 153, 225, 0.3); background: rgba(66, 153, 225, 0.3);
border-color: rgba(66, 153, 225, 0.6); border-color: rgba(66, 153, 225, 0.6);
} }
.toggle-switch__thumb[data-v-5b16b9d3] { .toggle-switch__thumb[data-v-f65566fd] {
position: absolute; position: absolute;
top: 3px; top: 3px;
left: 2px; left: 2px;
@@ -1825,19 +1833,19 @@ to { transform: rotate(360deg);
transition: all 0.2s; transition: all 0.2s;
opacity: 0.6; opacity: 0.6;
} }
.toggle-switch--active .toggle-switch__thumb[data-v-5b16b9d3] { .toggle-switch--active .toggle-switch__thumb[data-v-f65566fd] {
transform: translateX(16px); transform: translateX(16px);
background: #4299e1; background: #4299e1;
opacity: 1; opacity: 1;
} }
.toggle-switch:hover .toggle-switch__thumb[data-v-5b16b9d3] { .toggle-switch:hover .toggle-switch__thumb[data-v-f65566fd] {
opacity: 1; opacity: 1;
} }
.search-container[data-v-d7fd504d] { .search-container[data-v-83f6f852] {
position: relative; position: relative;
} }
.search-icon[data-v-d7fd504d] { .search-icon[data-v-83f6f852] {
position: absolute; position: absolute;
left: 10px; left: 10px;
top: 50%; top: 50%;
@@ -1847,7 +1855,7 @@ to { transform: rotate(360deg);
color: var(--fg-color, #fff); color: var(--fg-color, #fff);
opacity: 0.5; opacity: 0.5;
} }
.search-input[data-v-d7fd504d] { .search-input[data-v-83f6f852] {
width: 100%; width: 100%;
padding: 8px 32px; padding: 8px 32px;
background: var(--comfy-input-bg, #333); background: var(--comfy-input-bg, #333);
@@ -1858,14 +1866,14 @@ to { transform: rotate(360deg);
outline: none; outline: none;
box-sizing: border-box; box-sizing: border-box;
} }
.search-input[data-v-d7fd504d]:focus { .search-input[data-v-83f6f852]:focus {
border-color: rgba(66, 153, 225, 0.6); border-color: rgba(66, 153, 225, 0.6);
} }
.search-input[data-v-d7fd504d]::placeholder { .search-input[data-v-83f6f852]::placeholder {
color: var(--fg-color, #fff); color: var(--fg-color, #fff);
opacity: 0.4; opacity: 0.4;
} }
.clear-button[data-v-d7fd504d] { .clear-button[data-v-83f6f852] {
position: absolute; position: absolute;
right: 8px; right: 8px;
top: 50%; top: 50%;
@@ -1882,22 +1890,22 @@ to { transform: rotate(360deg);
opacity: 0.5; opacity: 0.5;
transition: opacity 0.15s; transition: opacity 0.15s;
} }
.clear-button[data-v-d7fd504d]:hover { .clear-button[data-v-83f6f852]:hover {
opacity: 0.8; opacity: 0.8;
} }
.clear-button svg[data-v-d7fd504d] { .clear-button svg[data-v-83f6f852] {
width: 12px; width: 12px;
height: 12px; height: 12px;
color: var(--fg-color, #fff); color: var(--fg-color, #fff);
} }
.lora-list[data-v-d7fd504d] { .lora-list[data-v-83f6f852] {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 2px; gap: 2px;
max-height: 400px; max-height: 400px;
overflow-y: auto; overflow-y: auto;
} }
.lora-item[data-v-d7fd504d] { .lora-item[data-v-83f6f852] {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; gap: 12px;
@@ -1907,14 +1915,14 @@ to { transform: rotate(360deg);
transition: all 0.15s; transition: all 0.15s;
border-left: 3px solid transparent; border-left: 3px solid transparent;
} }
.lora-item[data-v-d7fd504d]:hover { .lora-item[data-v-83f6f852]:hover {
background: rgba(66, 153, 225, 0.15); background: rgba(66, 153, 225, 0.15);
} }
.lora-item.active[data-v-d7fd504d] { .lora-item.active[data-v-83f6f852] {
background: rgba(66, 153, 225, 0.25); background: rgba(66, 153, 225, 0.25);
border-left-color: rgba(66, 153, 225, 0.8); border-left-color: rgba(66, 153, 225, 0.8);
} }
.lora-index[data-v-d7fd504d] { .lora-index[data-v-83f6f852] {
font-family: 'SF Mono', 'Roboto Mono', monospace; font-family: 'SF Mono', 'Roboto Mono', monospace;
font-size: 12px; font-size: 12px;
color: rgba(226, 232, 240, 0.5); color: rgba(226, 232, 240, 0.5);
@@ -1922,7 +1930,7 @@ to { transform: rotate(360deg);
text-align: right; text-align: right;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
} }
.lora-name[data-v-d7fd504d] { .lora-name[data-v-83f6f852] {
flex: 1; flex: 1;
font-size: 13px; font-size: 13px;
color: var(--fg-color, #fff); color: var(--fg-color, #fff);
@@ -1930,7 +1938,7 @@ to { transform: rotate(360deg);
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.current-badge[data-v-d7fd504d] { .current-badge[data-v-83f6f852] {
font-size: 11px; font-size: 11px;
padding: 2px 8px; padding: 2px 8px;
background: rgba(66, 153, 225, 0.3); background: rgba(66, 153, 225, 0.3);
@@ -1939,7 +1947,14 @@ to { transform: rotate(360deg);
color: rgba(191, 219, 254, 1); color: rgba(191, 219, 254, 1);
font-weight: 500; font-weight: 500;
} }
.no-results[data-v-d7fd504d] { .lora-item.no-lora-item .lora-name[data-v-83f6f852] {
font-style: italic;
color: rgba(226, 232, 240, 0.6);
}
.lora-item.no-lora-item:hover .lora-name[data-v-83f6f852] {
color: rgba(226, 232, 240, 0.8);
}
.no-results[data-v-83f6f852] {
padding: 32px 20px; padding: 32px 20px;
text-align: center; text-align: center;
color: var(--fg-color, #fff); color: var(--fg-color, #fff);
@@ -1947,7 +1962,7 @@ to { transform: rotate(360deg);
font-size: 13px; font-size: 13px;
} }
.lora-cycler-widget[data-v-f09f4e8b] { .lora-cycler-widget[data-v-f6dad3ae] {
padding: 6px; padding: 6px;
background: rgba(40, 44, 52, 0.6); background: rgba(40, 44, 52, 0.6);
border-radius: 6px; border-radius: 6px;
@@ -13108,6 +13123,9 @@ const _hoisted_26 = { class: "setting-section" };
const _hoisted_27 = { class: "section-header-with-toggle" }; const _hoisted_27 = { class: "section-header-with-toggle" };
const _hoisted_28 = { class: "setting-label" }; const _hoisted_28 = { class: "setting-label" };
const _hoisted_29 = ["aria-checked"]; const _hoisted_29 = ["aria-checked"];
const _hoisted_30 = { class: "setting-section" };
const _hoisted_31 = { class: "section-header-with-toggle" };
const _hoisted_32 = ["aria-checked"];
const _sfc_main$4 = /* @__PURE__ */ defineComponent({ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
__name: "LoraCyclerSettingsView", __name: "LoraCyclerSettingsView",
props: { props: {
@@ -13124,9 +13142,11 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
isPaused: { type: Boolean }, isPaused: { type: Boolean },
isPauseDisabled: { type: Boolean }, isPauseDisabled: { type: Boolean },
isWorkflowExecuting: { type: Boolean }, isWorkflowExecuting: { type: Boolean },
executingRepeatStep: {} executingRepeatStep: {},
includeNoLora: { type: Boolean },
isNoLora: { type: Boolean }
}, },
emits: ["update:currentIndex", "update:modelStrength", "update:clipStrength", "update:useCustomClipRange", "update:repeatCount", "toggle-pause", "reset-index", "open-lora-selector"], emits: ["update:currentIndex", "update:modelStrength", "update:clipStrength", "update:useCustomClipRange", "update:repeatCount", "update:includeNoLora", "toggle-pause", "reset-index", "open-lora-selector"],
setup(__props, { emit: __emit }) { setup(__props, { emit: __emit }) {
const props = __props; const props = __props;
const emit2 = __emit; const emit2 = __emit;
@@ -13172,7 +13192,7 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
}; };
return (_ctx, _cache) => { return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", _hoisted_1$4, [ return openBlock(), createElementBlock("div", _hoisted_1$4, [
_cache[21] || (_cache[21] = createBaseVNode("div", { class: "settings-header" }, [ _cache[24] || (_cache[24] = createBaseVNode("div", { class: "settings-header" }, [
createBaseVNode("h3", { class: "settings-title" }, "CYCLER SETTINGS") createBaseVNode("h3", { class: "settings-title" }, "CYCLER SETTINGS")
], -1)), ], -1)),
createBaseVNode("div", _hoisted_2$3, [ createBaseVNode("div", _hoisted_2$3, [
@@ -13185,11 +13205,11 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
}, [ }, [
createBaseVNode("span", _hoisted_3$3, toDisplayString(__props.isWorkflowExecuting ? "Using LoRA:" : "Next LoRA:"), 1), createBaseVNode("span", _hoisted_3$3, toDisplayString(__props.isWorkflowExecuting ? "Using LoRA:" : "Next LoRA:"), 1),
createBaseVNode("span", { createBaseVNode("span", {
class: normalizeClass(["progress-name clickable", { disabled: __props.isPauseDisabled }]), class: normalizeClass(["progress-name clickable", { disabled: __props.isPauseDisabled, "no-lora": __props.isNoLora }]),
title: __props.currentLoraFilename title: __props.currentLoraFilename
}, [ }, [
createTextVNode(toDisplayString(__props.currentLoraName || "None") + " ", 1), createTextVNode(toDisplayString(__props.currentLoraName || "None") + " ", 1),
_cache[11] || (_cache[11] = createBaseVNode("svg", { _cache[12] || (_cache[12] = createBaseVNode("svg", {
class: "selector-icon", class: "selector-icon",
viewBox: "0 0 24 24", viewBox: "0 0 24 24",
fill: "currentColor" fill: "currentColor"
@@ -13200,7 +13220,7 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
], 2), ], 2),
createBaseVNode("div", _hoisted_5$1, [ createBaseVNode("div", _hoisted_5$1, [
createBaseVNode("span", _hoisted_6$1, toDisplayString(__props.currentIndex), 1), createBaseVNode("span", _hoisted_6$1, toDisplayString(__props.currentIndex), 1),
_cache[12] || (_cache[12] = createBaseVNode("span", { class: "progress-separator" }, "/", -1)), _cache[13] || (_cache[13] = createBaseVNode("span", { class: "progress-separator" }, "/", -1)),
createBaseVNode("span", _hoisted_7$1, toDisplayString(__props.totalCount), 1), createBaseVNode("span", _hoisted_7$1, toDisplayString(__props.totalCount), 1),
__props.repeatCount > 1 ? (openBlock(), createElementBlock("div", _hoisted_8, [ __props.repeatCount > 1 ? (openBlock(), createElementBlock("div", _hoisted_8, [
createBaseVNode("div", _hoisted_9, [ createBaseVNode("div", _hoisted_9, [
@@ -13217,7 +13237,7 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
createBaseVNode("div", _hoisted_11, [ createBaseVNode("div", _hoisted_11, [
createBaseVNode("div", _hoisted_12, [ createBaseVNode("div", _hoisted_12, [
createBaseVNode("div", _hoisted_13, [ createBaseVNode("div", _hoisted_13, [
_cache[13] || (_cache[13] = createBaseVNode("label", { class: "control-group-label" }, "Starting Index", -1)), _cache[14] || (_cache[14] = createBaseVNode("label", { class: "control-group-label" }, "Starting Index", -1)),
createBaseVNode("div", _hoisted_14, [ createBaseVNode("div", _hoisted_14, [
createBaseVNode("input", { createBaseVNode("input", {
type: "number", type: "number",
@@ -13239,7 +13259,7 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
]) ])
]), ]),
createBaseVNode("div", _hoisted_17, [ createBaseVNode("div", _hoisted_17, [
_cache[15] || (_cache[15] = createBaseVNode("label", { class: "control-group-label" }, "Repeat", -1)), _cache[16] || (_cache[16] = createBaseVNode("label", { class: "control-group-label" }, "Repeat", -1)),
createBaseVNode("div", _hoisted_18, [ createBaseVNode("div", _hoisted_18, [
createBaseVNode("input", { createBaseVNode("input", {
type: "number", type: "number",
@@ -13257,7 +13277,7 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
}, ["stop"])), }, ["stop"])),
title: "Each LoRA will be used this many times before moving to the next" title: "Each LoRA will be used this many times before moving to the next"
}, null, 40, _hoisted_19), }, null, 40, _hoisted_19),
_cache[14] || (_cache[14] = createBaseVNode("span", { class: "repeat-suffix" }, "×", -1)) _cache[15] || (_cache[15] = createBaseVNode("span", { class: "repeat-suffix" }, "×", -1))
]) ])
]), ]),
createBaseVNode("div", _hoisted_20, [ createBaseVNode("div", _hoisted_20, [
@@ -13267,9 +13287,9 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
onClick: _cache[6] || (_cache[6] = ($event) => _ctx.$emit("toggle-pause")), onClick: _cache[6] || (_cache[6] = ($event) => _ctx.$emit("toggle-pause")),
title: __props.isPauseDisabled ? "Cannot pause while prompts are queued" : __props.isPaused ? "Continue iteration" : "Pause iteration" title: __props.isPauseDisabled ? "Cannot pause while prompts are queued" : __props.isPaused ? "Continue iteration" : "Pause iteration"
}, [ }, [
__props.isPaused ? (openBlock(), createElementBlock("svg", _hoisted_22, [..._cache[16] || (_cache[16] = [ __props.isPaused ? (openBlock(), createElementBlock("svg", _hoisted_22, [..._cache[17] || (_cache[17] = [
createBaseVNode("path", { d: "M8 5v14l11-7z" }, null, -1) createBaseVNode("path", { d: "M8 5v14l11-7z" }, null, -1)
])])) : (openBlock(), createElementBlock("svg", _hoisted_23, [..._cache[17] || (_cache[17] = [ ])])) : (openBlock(), createElementBlock("svg", _hoisted_23, [..._cache[18] || (_cache[18] = [
createBaseVNode("path", { d: "M6 4h4v16H6zm8 0h4v16h-4z" }, null, -1) createBaseVNode("path", { d: "M6 4h4v16H6zm8 0h4v16h-4z" }, null, -1)
])])) ])]))
], 10, _hoisted_21), ], 10, _hoisted_21),
@@ -13277,7 +13297,7 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
class: "control-btn", class: "control-btn",
onClick: _cache[7] || (_cache[7] = ($event) => _ctx.$emit("reset-index")), onClick: _cache[7] || (_cache[7] = ($event) => _ctx.$emit("reset-index")),
title: "Reset to index 1" title: "Reset to index 1"
}, [..._cache[18] || (_cache[18] = [ }, [..._cache[19] || (_cache[19] = [
createBaseVNode("svg", { createBaseVNode("svg", {
viewBox: "0 0 24 24", viewBox: "0 0 24 24",
fill: "currentColor", fill: "currentColor",
@@ -13290,7 +13310,7 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
]) ])
]), ]),
createBaseVNode("div", _hoisted_24, [ createBaseVNode("div", _hoisted_24, [
_cache[19] || (_cache[19] = createBaseVNode("label", { class: "setting-label" }, "Model Strength", -1)), _cache[20] || (_cache[20] = createBaseVNode("label", { class: "setting-label" }, "Model Strength", -1)),
createBaseVNode("div", _hoisted_25, [ createBaseVNode("div", _hoisted_25, [
createVNode(SingleSlider, { createVNode(SingleSlider, {
min: -10, min: -10,
@@ -13312,7 +13332,7 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
role: "switch", role: "switch",
"aria-checked": __props.useCustomClipRange, "aria-checked": __props.useCustomClipRange,
title: "Use custom clip strength when enabled, otherwise use model strength" title: "Use custom clip strength when enabled, otherwise use model strength"
}, [..._cache[20] || (_cache[20] = [ }, [..._cache[21] || (_cache[21] = [
createBaseVNode("span", { class: "toggle-switch__track" }, null, -1), createBaseVNode("span", { class: "toggle-switch__track" }, null, -1),
createBaseVNode("span", { class: "toggle-switch__thumb" }, null, -1) createBaseVNode("span", { class: "toggle-switch__thumb" }, null, -1)
])], 10, _hoisted_29) ])], 10, _hoisted_29)
@@ -13330,12 +13350,28 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
"onUpdate:value": _cache[10] || (_cache[10] = ($event) => _ctx.$emit("update:clipStrength", $event)) "onUpdate:value": _cache[10] || (_cache[10] = ($event) => _ctx.$emit("update:clipStrength", $event))
}, null, 8, ["value", "disabled"]) }, null, 8, ["value", "disabled"])
], 2) ], 2)
]),
createBaseVNode("div", _hoisted_30, [
createBaseVNode("div", _hoisted_31, [
_cache[23] || (_cache[23] = createBaseVNode("label", { class: "setting-label" }, ' Add "No LoRA" step ', -1)),
createBaseVNode("button", {
type: "button",
class: normalizeClass(["toggle-switch", { "toggle-switch--active": __props.includeNoLora }]),
onClick: _cache[11] || (_cache[11] = ($event) => _ctx.$emit("update:includeNoLora", !__props.includeNoLora)),
role: "switch",
"aria-checked": __props.includeNoLora,
title: "Add an iteration without LoRA for comparison"
}, [..._cache[22] || (_cache[22] = [
createBaseVNode("span", { class: "toggle-switch__track" }, null, -1),
createBaseVNode("span", { class: "toggle-switch__thumb" }, null, -1)
])], 10, _hoisted_32)
])
]) ])
]); ]);
}; };
} }
}); });
const LoraCyclerSettingsView = /* @__PURE__ */ _export_sfc(_sfc_main$4, [["__scopeId", "data-v-5b16b9d3"]]); const LoraCyclerSettingsView = /* @__PURE__ */ _export_sfc(_sfc_main$4, [["__scopeId", "data-v-f65566fd"]]);
const _hoisted_1$3 = { class: "search-container" }; const _hoisted_1$3 = { class: "search-container" };
const _hoisted_2$2 = { class: "lora-list" }; const _hoisted_2$2 = { class: "lora-list" };
const _hoisted_3$2 = ["onMouseenter", "onClick"]; const _hoisted_3$2 = ["onMouseenter", "onClick"];
@@ -13354,7 +13390,8 @@ const _sfc_main$3 = /* @__PURE__ */ defineComponent({
props: { props: {
visible: { type: Boolean }, visible: { type: Boolean },
loraList: {}, loraList: {},
currentIndex: {} currentIndex: {},
includeNoLora: { type: Boolean }
}, },
emits: ["close", "select"], emits: ["close", "select"],
setup(__props, { emit: __emit }) { setup(__props, { emit: __emit }) {
@@ -13364,7 +13401,8 @@ const _sfc_main$3 = /* @__PURE__ */ defineComponent({
const searchInputRef = ref(null); const searchInputRef = ref(null);
let previewTooltip = null; let previewTooltip = 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" : ""}`;
@@ -13376,6 +13414,12 @@ const _sfc_main$3 = /* @__PURE__ */ defineComponent({
index: idx + 1, index: idx + 1,
lora lora
})); }));
if (props.includeNoLora) {
list.push({
index: list.length + 1,
lora: { file_name: "No LoRA" }
});
}
if (!searchQuery.value.trim()) { if (!searchQuery.value.trim()) {
return list; return list;
} }
@@ -13497,7 +13541,10 @@ const _sfc_main$3 = /* @__PURE__ */ defineComponent({
(openBlock(true), createElementBlock(Fragment, null, renderList(filteredList.value, (item) => { (openBlock(true), createElementBlock(Fragment, null, renderList(filteredList.value, (item) => {
return openBlock(), createElementBlock("div", { return openBlock(), createElementBlock("div", {
key: item.index, key: item.index,
class: normalizeClass(["lora-item", { active: __props.currentIndex === item.index }]), class: normalizeClass(["lora-item", {
active: __props.currentIndex === item.index,
"no-lora-item": item.lora.file_name === "No LoRA"
}]),
onMouseenter: ($event) => showPreview(item.lora.file_name, $event), onMouseenter: ($event) => showPreview(item.lora.file_name, $event),
onMouseleave: hidePreview, onMouseleave: hidePreview,
onClick: ($event) => selectLora(item.index) onClick: ($event) => selectLora(item.index)
@@ -13518,7 +13565,7 @@ const _sfc_main$3 = /* @__PURE__ */ defineComponent({
}; };
} }
}); });
const LoraListModal = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["__scopeId", "data-v-d7fd504d"]]); const LoraListModal = /* @__PURE__ */ _export_sfc(_sfc_main$3, [["__scopeId", "data-v-83f6f852"]]);
function useLoraCyclerState(widget) { function useLoraCyclerState(widget) {
let isRestoring = false; let isRestoring = false;
const currentIndex = ref(1); const currentIndex = ref(1);
@@ -13537,6 +13584,7 @@ function useLoraCyclerState(widget) {
const repeatUsed = ref(0); const repeatUsed = ref(0);
const displayRepeatUsed = ref(0); const displayRepeatUsed = ref(0);
const isPaused = ref(false); const isPaused = ref(false);
const includeNoLora = ref(false);
const isWorkflowExecuting = ref(false); const isWorkflowExecuting = ref(false);
const executingRepeatStep = ref(0); const executingRepeatStep = ref(0);
const buildConfig = () => { const buildConfig = () => {
@@ -13555,7 +13603,8 @@ function useLoraCyclerState(widget) {
next_index: nextIndex.value, next_index: nextIndex.value,
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 {
@@ -13572,7 +13621,8 @@ function useLoraCyclerState(widget) {
next_index: nextIndex.value, next_index: nextIndex.value,
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
}; };
}; };
const restoreFromConfig = (config) => { const restoreFromConfig = (config) => {
@@ -13590,6 +13640,7 @@ function useLoraCyclerState(widget) {
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;
includeNoLora.value = config.include_no_lora ?? false;
} finally { } finally {
isRestoring = false; isRestoring = false;
} }
@@ -13598,7 +13649,8 @@ function useLoraCyclerState(widget) {
executionIndex.value = nextIndex.value; executionIndex.value = nextIndex.value;
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) { const effectiveTotalCount = includeNoLora.value ? totalCount.value + 1 : totalCount.value;
if (effectiveTotalCount > 0 && next > effectiveTotalCount) {
next = 1; next = 1;
} }
nextIndex.value = next; nextIndex.value = next;
@@ -13606,7 +13658,8 @@ function useLoraCyclerState(widget) {
const initializeNextIndex = () => { const initializeNextIndex = () => {
if (nextIndex.value === null) { if (nextIndex.value === null) {
let next = currentIndex.value + 1; let next = currentIndex.value + 1;
if (totalCount.value > 0 && next > totalCount.value) { const effectiveTotalCount = includeNoLora.value ? totalCount.value + 1 : totalCount.value;
if (effectiveTotalCount > 0 && next > effectiveTotalCount) {
next = 1; next = 1;
} }
nextIndex.value = next; nextIndex.value = next;
@@ -13684,7 +13737,8 @@ function useLoraCyclerState(widget) {
} }
}; };
const setIndex = (index) => { const setIndex = (index) => {
if (index >= 1 && index <= totalCount.value) { const effectiveTotalCount = includeNoLora.value ? totalCount.value + 1 : totalCount.value;
if (index >= 1 && index <= effectiveTotalCount) {
currentIndex.value = index; currentIndex.value = index;
} }
}; };
@@ -13714,7 +13768,8 @@ function useLoraCyclerState(widget) {
currentLoraFilename, currentLoraFilename,
repeatCount, repeatCount,
repeatUsed, repeatUsed,
isPaused isPaused,
includeNoLora
], () => { ], () => {
widget.value = buildConfig(); widget.value = buildConfig();
}, { deep: true }); }, { deep: true });
@@ -13736,6 +13791,7 @@ function useLoraCyclerState(widget) {
repeatUsed, repeatUsed,
displayRepeatUsed, displayRepeatUsed,
isPaused, isPaused,
includeNoLora,
isWorkflowExecuting, isWorkflowExecuting,
executingRepeatStep, executingRepeatStep,
// Computed // Computed
@@ -13772,6 +13828,21 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
const isMounted = ref(false); const isMounted = ref(false);
const isModalOpen = ref(false); const isModalOpen = ref(false);
const cachedLoraList = ref([]); const cachedLoraList = ref([]);
const displayTotalCount = computed(() => {
const baseCount = state.totalCount.value;
return state.includeNoLora.value ? baseCount + 1 : baseCount;
});
const displayLoraName = computed(() => {
const currentIndex = state.currentIndex.value;
const totalCount = state.totalCount.value;
if (state.includeNoLora.value && currentIndex === totalCount + 1) {
return "No LoRA";
}
return state.currentLoraName.value;
});
const isNoLora = computed(() => {
return state.includeNoLora.value && state.currentIndex.value === state.totalCount.value + 1;
});
const getPoolConfig = () => { const getPoolConfig = () => {
if (props.node.getPoolConfig) { if (props.node.getPoolConfig) {
return props.node.getPoolConfig(); return props.node.getPoolConfig();
@@ -13779,7 +13850,13 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
return null; return null;
}; };
const updateDisplayFromLoraList = (loraList, index) => { const updateDisplayFromLoraList = (loraList, index) => {
if (loraList.length > 0 && index > 0 && index <= loraList.length) { const actualLoraCount = loraList.length;
if (state.includeNoLora.value && index === actualLoraCount + 1) {
state.currentLoraName.value = "No LoRA";
state.currentLoraFilename.value = "No LoRA";
return;
}
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;
@@ -13788,17 +13865,19 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
} }
}; };
const handleIndexUpdate = async (newIndex) => { const handleIndexUpdate = async (newIndex) => {
const maxIndex = state.includeNoLora.value ? state.totalCount.value + 1 : state.totalCount.value;
const clampedIndex = Math.max(1, Math.min(newIndex, maxIndex || 1));
props.widget[HAS_EXECUTED] = false; props.widget[HAS_EXECUTED] = false;
state.executionIndex.value = null; state.executionIndex.value = null;
state.nextIndex.value = null; state.nextIndex.value = null;
executionQueue.length = 0; executionQueue.length = 0;
hasQueuedPrompts.value = false; hasQueuedPrompts.value = false;
state.setIndex(newIndex); state.setIndex(clampedIndex);
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);
} }
@@ -13817,6 +13896,12 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
state.repeatUsed.value = 0; state.repeatUsed.value = 0;
state.displayRepeatUsed.value = 0; state.displayRepeatUsed.value = 0;
}; };
const handleIncludeNoLoraChange = (newValue) => {
state.includeNoLora.value = newValue;
if (!newValue && state.currentIndex.value > state.totalCount.value) {
state.currentIndex.value = Math.max(1, state.totalCount.value);
}
};
const handleTogglePause = () => { const handleTogglePause = () => {
state.togglePause(); state.togglePause();
}; };
@@ -13995,8 +14080,8 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
return openBlock(), createElementBlock("div", _hoisted_1$2, [ return openBlock(), createElementBlock("div", _hoisted_1$2, [
createVNode(LoraCyclerSettingsView, { createVNode(LoraCyclerSettingsView, {
"current-index": unref(state).currentIndex.value, "current-index": unref(state).currentIndex.value,
"total-count": unref(state).totalCount.value, "total-count": displayTotalCount.value,
"current-lora-name": unref(state).currentLoraName.value, "current-lora-name": displayLoraName.value,
"current-lora-filename": unref(state).currentLoraFilename.value, "current-lora-filename": unref(state).currentLoraFilename.value,
"model-strength": unref(state).modelStrength.value, "model-strength": unref(state).modelStrength.value,
"clip-strength": unref(state).clipStrength.value, "clip-strength": unref(state).clipStrength.value,
@@ -14009,27 +14094,31 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
"is-pause-disabled": hasQueuedPrompts.value, "is-pause-disabled": hasQueuedPrompts.value,
"is-workflow-executing": unref(state).isWorkflowExecuting.value, "is-workflow-executing": unref(state).isWorkflowExecuting.value,
"executing-repeat-step": unref(state).executingRepeatStep.value, "executing-repeat-step": unref(state).executingRepeatStep.value,
"include-no-lora": unref(state).includeNoLora.value,
"is-no-lora": isNoLora.value,
"onUpdate:currentIndex": handleIndexUpdate, "onUpdate:currentIndex": handleIndexUpdate,
"onUpdate:modelStrength": _cache[0] || (_cache[0] = ($event) => unref(state).modelStrength.value = $event), "onUpdate:modelStrength": _cache[0] || (_cache[0] = ($event) => unref(state).modelStrength.value = $event),
"onUpdate:clipStrength": _cache[1] || (_cache[1] = ($event) => unref(state).clipStrength.value = $event), "onUpdate:clipStrength": _cache[1] || (_cache[1] = ($event) => unref(state).clipStrength.value = $event),
"onUpdate:useCustomClipRange": handleUseCustomClipRangeChange, "onUpdate:useCustomClipRange": handleUseCustomClipRangeChange,
"onUpdate:repeatCount": handleRepeatCountChange, "onUpdate:repeatCount": handleRepeatCountChange,
"onUpdate:includeNoLora": handleIncludeNoLoraChange,
onTogglePause: handleTogglePause, onTogglePause: handleTogglePause,
onResetIndex: handleResetIndex, onResetIndex: handleResetIndex,
onOpenLoraSelector: _cache[2] || (_cache[2] = ($event) => isModalOpen.value = true) onOpenLoraSelector: _cache[2] || (_cache[2] = ($event) => isModalOpen.value = true)
}, null, 8, ["current-index", "total-count", "current-lora-name", "current-lora-filename", "model-strength", "clip-strength", "use-custom-clip-range", "is-clip-strength-disabled", "is-loading", "repeat-count", "repeat-used", "is-paused", "is-pause-disabled", "is-workflow-executing", "executing-repeat-step"]), }, null, 8, ["current-index", "total-count", "current-lora-name", "current-lora-filename", "model-strength", "clip-strength", "use-custom-clip-range", "is-clip-strength-disabled", "is-loading", "repeat-count", "repeat-used", "is-paused", "is-pause-disabled", "is-workflow-executing", "executing-repeat-step", "include-no-lora", "is-no-lora"]),
createVNode(LoraListModal, { createVNode(LoraListModal, {
visible: isModalOpen.value, visible: isModalOpen.value,
"lora-list": cachedLoraList.value, "lora-list": cachedLoraList.value,
"current-index": unref(state).currentIndex.value, "current-index": unref(state).currentIndex.value,
"include-no-lora": unref(state).includeNoLora.value,
onClose: _cache[3] || (_cache[3] = ($event) => isModalOpen.value = false), onClose: _cache[3] || (_cache[3] = ($event) => isModalOpen.value = false),
onSelect: handleModalSelect onSelect: handleModalSelect
}, null, 8, ["visible", "lora-list", "current-index"]) }, null, 8, ["visible", "lora-list", "current-index", "include-no-lora"])
]); ]);
}; };
} }
}); });
const LoraCyclerWidget = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-f09f4e8b"]]); const LoraCyclerWidget = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-f6dad3ae"]]);
const _hoisted_1$1 = { class: "json-display-widget" }; const _hoisted_1$1 = { class: "json-display-widget" };
const _hoisted_2$1 = { const _hoisted_2$1 = {
class: "json-content", class: "json-content",
@@ -14636,7 +14725,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;

File diff suppressed because one or more lines are too long