mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-27 07:58:52 -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:
@@ -10,7 +10,7 @@ export interface LoraPoolConfig {
|
||||
noCreditRequired: boolean
|
||||
allowSelling: boolean
|
||||
}
|
||||
includeEmptyLora: boolean
|
||||
includeEmptyLora?: boolean // Optional, deprecated (moved to Cycler)
|
||||
}
|
||||
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_used: number // How many times current index has been used
|
||||
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
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { ComponentWidget, CyclerConfig, LoraPoolConfig } from './types'
|
||||
export interface CyclerLoraItem {
|
||||
file_name: string
|
||||
model_name: string
|
||||
file_path: string
|
||||
}
|
||||
|
||||
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 displayRepeatUsed = ref(0) // For UI display, deferred updates like currentIndex
|
||||
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)
|
||||
const isWorkflowExecuting = ref(false) // Workflow is currently running
|
||||
@@ -58,6 +60,7 @@ export function useLoraCyclerState(widget: ComponentWidget<CyclerConfig>) {
|
||||
repeat_count: repeatCount.value,
|
||||
repeat_used: repeatUsed.value,
|
||||
is_paused: isPaused.value,
|
||||
include_no_lora: includeNoLora.value,
|
||||
}
|
||||
}
|
||||
return {
|
||||
@@ -75,6 +78,7 @@ export function useLoraCyclerState(widget: ComponentWidget<CyclerConfig>) {
|
||||
repeat_count: repeatCount.value,
|
||||
repeat_used: repeatUsed.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'
|
||||
currentLoraName.value = config.current_lora_name || ''
|
||||
currentLoraFilename.value = config.current_lora_filename || ''
|
||||
// Advanced index control features
|
||||
repeatCount.value = config.repeat_count ?? 1
|
||||
repeatUsed.value = config.repeat_used ?? 0
|
||||
isPaused.value = config.is_paused ?? false
|
||||
// Note: execution_index and next_index are not restored from config
|
||||
// as they are transient values used only during batch execution
|
||||
// Advanced index control features
|
||||
repeatCount.value = config.repeat_count ?? 1
|
||||
repeatUsed.value = config.repeat_used ?? 0
|
||||
isPaused.value = config.is_paused ?? false
|
||||
includeNoLora.value = config.include_no_lora ?? false
|
||||
// Note: execution_index and next_index are not restored from config
|
||||
// as they are transient values used only during batch execution
|
||||
} finally {
|
||||
isRestoring = false
|
||||
}
|
||||
@@ -111,7 +116,9 @@ export function useLoraCyclerState(widget: ComponentWidget<CyclerConfig>) {
|
||||
// Calculate the next index (wrap to 1 if at end)
|
||||
const current = executionIndex.value ?? currentIndex.value
|
||||
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
|
||||
}
|
||||
nextIndex.value = next
|
||||
@@ -122,7 +129,9 @@ export function useLoraCyclerState(widget: ComponentWidget<CyclerConfig>) {
|
||||
if (nextIndex.value === null) {
|
||||
// First execution uses current_index, so next is current + 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
|
||||
}
|
||||
nextIndex.value = next
|
||||
@@ -230,7 +239,9 @@ export function useLoraCyclerState(widget: ComponentWidget<CyclerConfig>) {
|
||||
|
||||
// Set index manually
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -272,6 +283,7 @@ export function useLoraCyclerState(widget: ComponentWidget<CyclerConfig>) {
|
||||
repeatCount,
|
||||
repeatUsed,
|
||||
isPaused,
|
||||
includeNoLora,
|
||||
], () => {
|
||||
widget.value = buildConfig()
|
||||
}, { deep: true })
|
||||
@@ -294,6 +306,7 @@ export function useLoraCyclerState(widget: ComponentWidget<CyclerConfig>) {
|
||||
repeatUsed,
|
||||
displayRepeatUsed,
|
||||
isPaused,
|
||||
includeNoLora,
|
||||
isWorkflowExecuting,
|
||||
executingRepeatStep,
|
||||
|
||||
|
||||
@@ -62,7 +62,6 @@ export function useLoraPoolApi() {
|
||||
foldersExclude?: string[]
|
||||
noCreditRequired?: boolean
|
||||
allowSelling?: boolean
|
||||
includeEmptyLora?: boolean
|
||||
page?: number
|
||||
pageSize?: number
|
||||
}
|
||||
@@ -93,10 +92,6 @@ export function useLoraPoolApi() {
|
||||
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 data = await response.json()
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ export function useLoraPoolState(widget: ComponentWidget<LoraPoolConfig>) {
|
||||
const excludeFolders = ref<string[]>([])
|
||||
const noCreditRequired = ref(false)
|
||||
const allowSelling = ref(false)
|
||||
const includeEmptyLora = ref(false)
|
||||
|
||||
// Available options from API
|
||||
const availableBaseModels = ref<BaseModelOption[]>([])
|
||||
@@ -53,8 +52,7 @@ export function useLoraPoolState(widget: ComponentWidget<LoraPoolConfig>) {
|
||||
license: {
|
||||
noCreditRequired: noCreditRequired.value,
|
||||
allowSelling: allowSelling.value
|
||||
},
|
||||
includeEmptyLora: includeEmptyLora.value
|
||||
}
|
||||
},
|
||||
preview: {
|
||||
matchCount: matchCount.value,
|
||||
@@ -96,7 +94,6 @@ export function useLoraPoolState(widget: ComponentWidget<LoraPoolConfig>) {
|
||||
updateIfChanged(excludeFolders, filters.folders?.exclude || [])
|
||||
updateIfChanged(noCreditRequired, filters.license?.noCreditRequired ?? false)
|
||||
updateIfChanged(allowSelling, filters.license?.allowSelling ?? false)
|
||||
updateIfChanged(includeEmptyLora, filters.includeEmptyLora ?? false)
|
||||
|
||||
// matchCount doesn't trigger watchers, so direct assignment is fine
|
||||
matchCount.value = preview?.matchCount || 0
|
||||
@@ -128,7 +125,6 @@ export function useLoraPoolState(widget: ComponentWidget<LoraPoolConfig>) {
|
||||
foldersExclude: excludeFolders.value,
|
||||
noCreditRequired: noCreditRequired.value || undefined,
|
||||
allowSelling: allowSelling.value || undefined,
|
||||
includeEmptyLora: includeEmptyLora.value || undefined,
|
||||
pageSize: 6
|
||||
})
|
||||
|
||||
@@ -154,8 +150,7 @@ export function useLoraPoolState(widget: ComponentWidget<LoraPoolConfig>) {
|
||||
includeFolders,
|
||||
excludeFolders,
|
||||
noCreditRequired,
|
||||
allowSelling,
|
||||
includeEmptyLora
|
||||
allowSelling
|
||||
], onFilterChange, { deep: true })
|
||||
|
||||
return {
|
||||
@@ -167,7 +162,6 @@ export function useLoraPoolState(widget: ComponentWidget<LoraPoolConfig>) {
|
||||
excludeFolders,
|
||||
noCreditRequired,
|
||||
allowSelling,
|
||||
includeEmptyLora,
|
||||
|
||||
// Available options
|
||||
availableBaseModels,
|
||||
|
||||
Reference in New Issue
Block a user