mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
feat(lora-cycler): implement batch queue synchronization with dual-index mechanism
- Add execution_index and next_index fields to CyclerConfig interface - Introduce beforeQueued hook in widget to handle index shifting for batch executions - Use execution_index when provided, fall back to current_index for single executions - Track execution state with Symbol to differentiate first vs subsequent executions - Update state management to handle dual-index logic for proper LoRA cycling in batch queues
This commit is contained in:
@@ -56,6 +56,10 @@ class LoraCyclerNode:
|
|||||||
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"
|
||||||
|
|
||||||
|
# Dual-index mechanism for batch queue synchronization
|
||||||
|
execution_index = cycler_config.get("execution_index") # Can be None
|
||||||
|
# next_index_from_config = cycler_config.get("next_index") # Not used on backend
|
||||||
|
|
||||||
# Get scanner and service
|
# Get scanner and service
|
||||||
scanner = await ServiceRegistry.get_lora_scanner()
|
scanner = await ServiceRegistry.get_lora_scanner()
|
||||||
lora_service = LoraService(scanner)
|
lora_service = LoraService(scanner)
|
||||||
@@ -81,8 +85,16 @@ class LoraCyclerNode:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Determine which index to use for this execution
|
||||||
|
# If execution_index is provided (batch queue case), use it
|
||||||
|
# Otherwise use current_index (first execution or non-batch case)
|
||||||
|
if execution_index is not None:
|
||||||
|
actual_index = execution_index
|
||||||
|
else:
|
||||||
|
actual_index = current_index
|
||||||
|
|
||||||
# Clamp index to valid range (1-based)
|
# Clamp index to valid range (1-based)
|
||||||
clamped_index = max(1, min(current_index, total_count))
|
clamped_index = max(1, min(actual_index, total_count))
|
||||||
|
|
||||||
# Get LoRA at current index (convert to 0-based for list access)
|
# Get LoRA at current index (convert to 0-based for list access)
|
||||||
current_lora = lora_list[clamped_index - 1]
|
current_lora = lora_list[clamped_index - 1]
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ const props = defineProps<{
|
|||||||
// State management
|
// State management
|
||||||
const state = useLoraCyclerState(props.widget)
|
const state = useLoraCyclerState(props.widget)
|
||||||
|
|
||||||
|
// Symbol to track if the widget has been executed at least once
|
||||||
|
const HAS_EXECUTED = Symbol('HAS_EXECUTED')
|
||||||
|
|
||||||
// Track last known pool config hash
|
// Track last known pool config hash
|
||||||
const lastPoolConfigHash = ref('')
|
const lastPoolConfigHash = ref('')
|
||||||
|
|
||||||
@@ -124,6 +127,28 @@ onMounted(async () => {
|
|||||||
state.restoreFromConfig(props.widget.value as CyclerConfig)
|
state.restoreFromConfig(props.widget.value as CyclerConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add beforeQueued hook to handle index shifting for batch queue synchronization
|
||||||
|
// This ensures each execution uses a different LoRA in the cycle
|
||||||
|
;(props.widget as any).beforeQueued = () => {
|
||||||
|
if ((props.widget as any)[HAS_EXECUTED]) {
|
||||||
|
// After first execution: shift indices (previous next_index becomes execution_index)
|
||||||
|
state.generateNextIndex()
|
||||||
|
} else {
|
||||||
|
// First execution: just initialize next_index (execution_index stays null)
|
||||||
|
// This means first execution uses current_index from widget
|
||||||
|
state.initializeNextIndex()
|
||||||
|
;(props.widget as any)[HAS_EXECUTED] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the widget value so the indices are included in the serialized config
|
||||||
|
const config = state.buildConfig()
|
||||||
|
if ((props.widget as any).updateConfig) {
|
||||||
|
;(props.widget as any).updateConfig(config)
|
||||||
|
} else {
|
||||||
|
props.widget.value = config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Mark component as mounted
|
// Mark component as mounted
|
||||||
isMounted.value = true
|
isMounted.value = true
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,9 @@ export interface CyclerConfig {
|
|||||||
sort_by: 'filename' | 'model_name'
|
sort_by: 'filename' | 'model_name'
|
||||||
current_lora_name: string // For display
|
current_lora_name: string // For display
|
||||||
current_lora_filename: string
|
current_lora_filename: string
|
||||||
|
// Dual-index mechanism for batch queue synchronization
|
||||||
|
execution_index?: number | null // Index to use for current execution
|
||||||
|
next_index?: number | null // Index for display after execution
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ComponentWidget {
|
export interface ComponentWidget {
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ export function useLoraCyclerState(widget: ComponentWidget) {
|
|||||||
const currentLoraFilename = ref('')
|
const currentLoraFilename = ref('')
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
|
|
||||||
|
// Dual-index mechanism for batch queue synchronization
|
||||||
|
// execution_index: index for generating execution_stack (= previous next_index)
|
||||||
|
// next_index: index for UI display (= what will be shown after execution)
|
||||||
|
const executionIndex = ref<number | null>(null)
|
||||||
|
const nextIndex = ref<number | null>(null)
|
||||||
|
|
||||||
// Build config object from current state
|
// Build config object from current state
|
||||||
const buildConfig = (): CyclerConfig => ({
|
const buildConfig = (): CyclerConfig => ({
|
||||||
current_index: currentIndex.value,
|
current_index: currentIndex.value,
|
||||||
@@ -30,6 +36,8 @@ export function useLoraCyclerState(widget: ComponentWidget) {
|
|||||||
sort_by: sortBy.value,
|
sort_by: sortBy.value,
|
||||||
current_lora_name: currentLoraName.value,
|
current_lora_name: currentLoraName.value,
|
||||||
current_lora_filename: currentLoraFilename.value,
|
current_lora_filename: currentLoraFilename.value,
|
||||||
|
execution_index: executionIndex.value,
|
||||||
|
next_index: nextIndex.value,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Restore state from config object
|
// Restore state from config object
|
||||||
@@ -43,6 +51,33 @@ export function useLoraCyclerState(widget: ComponentWidget) {
|
|||||||
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 || ''
|
||||||
|
// Note: execution_index and next_index are not restored from config
|
||||||
|
// as they are transient values used only during batch execution
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shift indices for batch queue synchronization
|
||||||
|
// Previous next_index becomes current execution_index, and generate a new next_index
|
||||||
|
const generateNextIndex = () => {
|
||||||
|
executionIndex.value = nextIndex.value // Previous next becomes current execution
|
||||||
|
// 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) {
|
||||||
|
next = 1
|
||||||
|
}
|
||||||
|
nextIndex.value = next
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize next_index for first execution (execution_index stays null)
|
||||||
|
const initializeNextIndex = () => {
|
||||||
|
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) {
|
||||||
|
next = 1
|
||||||
|
}
|
||||||
|
nextIndex.value = next
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate hash from pool config for change detection
|
// Generate hash from pool config for change detection
|
||||||
@@ -193,6 +228,8 @@ export function useLoraCyclerState(widget: ComponentWidget) {
|
|||||||
currentLoraName,
|
currentLoraName,
|
||||||
currentLoraFilename,
|
currentLoraFilename,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
executionIndex,
|
||||||
|
nextIndex,
|
||||||
|
|
||||||
// Computed
|
// Computed
|
||||||
isClipStrengthDisabled,
|
isClipStrengthDisabled,
|
||||||
@@ -204,5 +241,7 @@ export function useLoraCyclerState(widget: ComponentWidget) {
|
|||||||
fetchCyclerList,
|
fetchCyclerList,
|
||||||
refreshList,
|
refreshList,
|
||||||
setIndex,
|
setIndex,
|
||||||
|
generateNextIndex,
|
||||||
|
initializeNextIndex,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1684,7 +1684,7 @@ to {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lora-cycler-widget[data-v-0f9d3d70] {
|
.lora-cycler-widget[data-v-95dec8bd] {
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
background: rgba(40, 44, 52, 0.6);
|
background: rgba(40, 44, 52, 0.6);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
@@ -12833,6 +12833,8 @@ function useLoraCyclerState(widget) {
|
|||||||
const currentLoraName = ref("");
|
const currentLoraName = ref("");
|
||||||
const currentLoraFilename = ref("");
|
const currentLoraFilename = ref("");
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
|
const executionIndex = ref(null);
|
||||||
|
const nextIndex = ref(null);
|
||||||
const buildConfig = () => ({
|
const buildConfig = () => ({
|
||||||
current_index: currentIndex.value,
|
current_index: currentIndex.value,
|
||||||
total_count: totalCount.value,
|
total_count: totalCount.value,
|
||||||
@@ -12842,7 +12844,9 @@ function useLoraCyclerState(widget) {
|
|||||||
use_same_clip_strength: !useCustomClipRange.value,
|
use_same_clip_strength: !useCustomClipRange.value,
|
||||||
sort_by: sortBy.value,
|
sort_by: sortBy.value,
|
||||||
current_lora_name: currentLoraName.value,
|
current_lora_name: currentLoraName.value,
|
||||||
current_lora_filename: currentLoraFilename.value
|
current_lora_filename: currentLoraFilename.value,
|
||||||
|
execution_index: executionIndex.value,
|
||||||
|
next_index: nextIndex.value
|
||||||
});
|
});
|
||||||
const restoreFromConfig = (config) => {
|
const restoreFromConfig = (config) => {
|
||||||
currentIndex.value = config.current_index || 1;
|
currentIndex.value = config.current_index || 1;
|
||||||
@@ -12855,6 +12859,24 @@ function useLoraCyclerState(widget) {
|
|||||||
currentLoraName.value = config.current_lora_name || "";
|
currentLoraName.value = config.current_lora_name || "";
|
||||||
currentLoraFilename.value = config.current_lora_filename || "";
|
currentLoraFilename.value = config.current_lora_filename || "";
|
||||||
};
|
};
|
||||||
|
const generateNextIndex = () => {
|
||||||
|
executionIndex.value = nextIndex.value;
|
||||||
|
const current = executionIndex.value ?? currentIndex.value;
|
||||||
|
let next = current + 1;
|
||||||
|
if (totalCount.value > 0 && next > totalCount.value) {
|
||||||
|
next = 1;
|
||||||
|
}
|
||||||
|
nextIndex.value = next;
|
||||||
|
};
|
||||||
|
const initializeNextIndex = () => {
|
||||||
|
if (nextIndex.value === null) {
|
||||||
|
let next = currentIndex.value + 1;
|
||||||
|
if (totalCount.value > 0 && next > totalCount.value) {
|
||||||
|
next = 1;
|
||||||
|
}
|
||||||
|
nextIndex.value = next;
|
||||||
|
}
|
||||||
|
};
|
||||||
const hashPoolConfig = (poolConfig) => {
|
const hashPoolConfig = (poolConfig) => {
|
||||||
if (!poolConfig || !poolConfig.filters) {
|
if (!poolConfig || !poolConfig.filters) {
|
||||||
return "";
|
return "";
|
||||||
@@ -12967,6 +12989,8 @@ function useLoraCyclerState(widget) {
|
|||||||
currentLoraName,
|
currentLoraName,
|
||||||
currentLoraFilename,
|
currentLoraFilename,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
executionIndex,
|
||||||
|
nextIndex,
|
||||||
// Computed
|
// Computed
|
||||||
isClipStrengthDisabled,
|
isClipStrengthDisabled,
|
||||||
// Methods
|
// Methods
|
||||||
@@ -12975,7 +12999,9 @@ function useLoraCyclerState(widget) {
|
|||||||
hashPoolConfig,
|
hashPoolConfig,
|
||||||
fetchCyclerList,
|
fetchCyclerList,
|
||||||
refreshList,
|
refreshList,
|
||||||
setIndex
|
setIndex,
|
||||||
|
generateNextIndex,
|
||||||
|
initializeNextIndex
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const _hoisted_1$1 = { class: "lora-cycler-widget" };
|
const _hoisted_1$1 = { class: "lora-cycler-widget" };
|
||||||
@@ -12988,6 +13014,7 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|||||||
setup(__props) {
|
setup(__props) {
|
||||||
const props = __props;
|
const props = __props;
|
||||||
const state = useLoraCyclerState(props.widget);
|
const state = useLoraCyclerState(props.widget);
|
||||||
|
const HAS_EXECUTED = Symbol("HAS_EXECUTED");
|
||||||
const lastPoolConfigHash = ref("");
|
const lastPoolConfigHash = ref("");
|
||||||
const isMounted = ref(false);
|
const isMounted = ref(false);
|
||||||
const getPoolConfig = () => {
|
const getPoolConfig = () => {
|
||||||
@@ -13051,6 +13078,20 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|||||||
if (props.widget.value) {
|
if (props.widget.value) {
|
||||||
state.restoreFromConfig(props.widget.value);
|
state.restoreFromConfig(props.widget.value);
|
||||||
}
|
}
|
||||||
|
props.widget.beforeQueued = () => {
|
||||||
|
if (props.widget[HAS_EXECUTED]) {
|
||||||
|
state.generateNextIndex();
|
||||||
|
} else {
|
||||||
|
state.initializeNextIndex();
|
||||||
|
props.widget[HAS_EXECUTED] = true;
|
||||||
|
}
|
||||||
|
const config = state.buildConfig();
|
||||||
|
if (props.widget.updateConfig) {
|
||||||
|
props.widget.updateConfig(config);
|
||||||
|
} else {
|
||||||
|
props.widget.value = config;
|
||||||
|
}
|
||||||
|
};
|
||||||
isMounted.value = true;
|
isMounted.value = true;
|
||||||
try {
|
try {
|
||||||
const poolConfig = getPoolConfig();
|
const poolConfig = getPoolConfig();
|
||||||
@@ -13117,7 +13158,7 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const LoraCyclerWidget = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__scopeId", "data-v-0f9d3d70"]]);
|
const LoraCyclerWidget = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__scopeId", "data-v-95dec8bd"]]);
|
||||||
const _hoisted_1 = { class: "json-display-widget" };
|
const _hoisted_1 = { class: "json-display-widget" };
|
||||||
const _hoisted_2 = {
|
const _hoisted_2 = {
|
||||||
class: "json-content",
|
class: "json-content",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user