feat(lora_randomizer): implement dual seed mechanism for batch queue synchronization, fixes #773

- Add execution_seed and next_seed parameters to support deterministic randomization across batch executions
- Separate UI display generation from execution stack generation to maintain consistency in batch queues
- Update LoraService to accept optional seed parameter for reproducible randomization
- Ensure each execution with a different seed produces unique results without affecting global random state
This commit is contained in:
Will Miao
2026-01-21 00:52:08 +08:00
parent 50c012ae33
commit fd06086a05
7 changed files with 134 additions and 19 deletions

View File

@@ -54,6 +54,9 @@ const props = defineProps<{
// State management
const state = useLoraRandomizerState(props.widget)
// Symbol to track if the widget has been executed at least once
const HAS_EXECUTED = Symbol('HAS_EXECUTED')
// Track current loras from the loras widget
const currentLoras = ref<LoraEntry[]>([])
@@ -190,6 +193,31 @@ onMounted(async () => {
state.restoreFromConfig(props.widget.value as RandomizerConfig)
}
// Add beforeQueued hook to handle seed shifting for batch queue synchronization
// This ensures each execution uses the loras that were displayed before that execution
;(props.widget as any).beforeQueued = () => {
// Only process when roll_mode is 'always' (randomize on each execution)
if (state.rollMode.value === 'always') {
if ((props.widget as any)[HAS_EXECUTED]) {
// After first execution: shift seeds (previous next_seed becomes execution_seed)
state.generateNewSeed()
} else {
// First execution: just initialize next_seed (execution_seed stays null)
// This means first execution uses loras from widget input
state.initializeNextSeed()
;(props.widget as any)[HAS_EXECUTED] = true
}
// Update the widget value so the seeds 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
}
}
}
// Override onExecuted to handle backend UI updates
const originalOnExecuted = (props.node as any).onExecuted?.bind(props.node)

View File

@@ -69,6 +69,8 @@ export interface RandomizerConfig {
use_recommended_strength: boolean
recommended_strength_scale_min: number
recommended_strength_scale_max: number
execution_seed?: number | null // Seed for execution_stack (previous next_seed)
next_seed?: number | null // Seed for ui_loras (current)
}
export interface LoraEntry {

View File

@@ -21,6 +21,12 @@ export function useLoraRandomizerState(widget: ComponentWidget) {
// Track last used combination (for backend roll mode)
const lastUsed = ref<LoraEntry[] | null>(null)
// Dual seed mechanism for batch queue synchronization
// execution_seed: seed for generating execution_stack (= previous next_seed)
// next_seed: seed for generating ui_loras (= what will be displayed after execution)
const executionSeed = ref<number | null>(null)
const nextSeed = ref<number | null>(null)
// Build config object from current state
const buildConfig = (): RandomizerConfig => ({
count_mode: countMode.value,
@@ -37,8 +43,24 @@ export function useLoraRandomizerState(widget: ComponentWidget) {
use_recommended_strength: useRecommendedStrength.value,
recommended_strength_scale_min: recommendedStrengthScaleMin.value,
recommended_strength_scale_max: recommendedStrengthScaleMax.value,
execution_seed: executionSeed.value,
next_seed: nextSeed.value,
})
// Shift seeds for batch queue synchronization
// Previous next_seed becomes current execution_seed, and generate a new next_seed
const generateNewSeed = () => {
executionSeed.value = nextSeed.value // Previous next becomes current execution
nextSeed.value = Math.floor(Math.random() * 2147483647)
}
// Initialize next_seed for first execution (execution_seed stays null)
const initializeNextSeed = () => {
if (nextSeed.value === null) {
nextSeed.value = Math.floor(Math.random() * 2147483647)
}
}
// Restore state from config object
const restoreFromConfig = (config: RandomizerConfig) => {
countMode.value = config.count_mode || 'range'
@@ -185,6 +207,8 @@ export function useLoraRandomizerState(widget: ComponentWidget) {
useRecommendedStrength,
recommendedStrengthScaleMin,
recommendedStrengthScaleMax,
executionSeed,
nextSeed,
// Computed
isClipStrengthDisabled,
@@ -195,5 +219,7 @@ export function useLoraRandomizerState(widget: ComponentWidget) {
restoreFromConfig,
rollLoras,
useLastUsed,
generateNewSeed,
initializeNextSeed,
}
}