- Replace custom onSetValue with ComfyUI's built-in widget.callback - Remove widget.updateConfig, set widget.value directly - Add isRestoring flag to break callback → watch → widget.value loop - Update ComponentWidget types with generic parameter for type-safe callbacks Refs: docs/dom-widgets/value-persistence-best-practices.md
255 lines
8.3 KiB
TypeScript
255 lines
8.3 KiB
TypeScript
import { ref, computed, watch } from 'vue'
|
|
import type { ComponentWidget, RandomizerConfig, LoraEntry } from './types'
|
|
|
|
export function useLoraRandomizerState(widget: ComponentWidget<RandomizerConfig>) {
|
|
// Flag to prevent infinite loops during config restoration
|
|
// callback → restoreFromConfig → watch → widget.value = config → callback → ...
|
|
let isRestoring = false
|
|
|
|
// State refs
|
|
const countMode = ref<'fixed' | 'range'>('range')
|
|
const countFixed = ref(3)
|
|
const countMin = ref(2)
|
|
const countMax = ref(5)
|
|
const modelStrengthMin = ref(0.0)
|
|
const modelStrengthMax = ref(1.0)
|
|
const useCustomClipRange = ref(false)
|
|
const clipStrengthMin = ref(0.0)
|
|
const clipStrengthMax = ref(1.0)
|
|
const rollMode = ref<'fixed' | 'always'>('fixed')
|
|
const isRolling = ref(false)
|
|
const useRecommendedStrength = ref(false)
|
|
const recommendedStrengthScaleMin = ref(0.5)
|
|
const recommendedStrengthScaleMax = ref(1.0)
|
|
|
|
// 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 => {
|
|
// Skip updating widget.value during restoration to prevent infinite loops
|
|
if (isRestoring) {
|
|
return {
|
|
count_mode: countMode.value,
|
|
count_fixed: countFixed.value,
|
|
count_min: countMin.value,
|
|
count_max: countMax.value,
|
|
model_strength_min: modelStrengthMin.value,
|
|
model_strength_max: modelStrengthMax.value,
|
|
use_same_clip_strength: !useCustomClipRange.value,
|
|
clip_strength_min: clipStrengthMin.value,
|
|
clip_strength_max: clipStrengthMax.value,
|
|
roll_mode: rollMode.value,
|
|
last_used: lastUsed.value,
|
|
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,
|
|
}
|
|
}
|
|
return {
|
|
count_mode: countMode.value,
|
|
count_fixed: countFixed.value,
|
|
count_min: countMin.value,
|
|
count_max: countMax.value,
|
|
model_strength_min: modelStrengthMin.value,
|
|
model_strength_max: modelStrengthMax.value,
|
|
use_same_clip_strength: !useCustomClipRange.value,
|
|
clip_strength_min: clipStrengthMin.value,
|
|
clip_strength_max: clipStrengthMax.value,
|
|
roll_mode: rollMode.value,
|
|
last_used: lastUsed.value,
|
|
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) => {
|
|
// Set flag to prevent buildConfig from triggering widget.value updates during restoration
|
|
isRestoring = true
|
|
|
|
try {
|
|
countMode.value = config.count_mode || 'range'
|
|
countFixed.value = config.count_fixed || 3
|
|
countMin.value = config.count_min || 2
|
|
countMax.value = config.count_max || 5
|
|
modelStrengthMin.value = config.model_strength_min ?? 0.0
|
|
modelStrengthMax.value = config.model_strength_max ?? 1.0
|
|
useCustomClipRange.value = !(config.use_same_clip_strength ?? true)
|
|
clipStrengthMin.value = config.clip_strength_min ?? 0.0
|
|
clipStrengthMax.value = config.clip_strength_max ?? 1.0
|
|
// Migrate old roll_mode values to new ones
|
|
const rawRollMode = (config as any).roll_mode as string
|
|
if (rawRollMode === 'frontend') {
|
|
rollMode.value = 'fixed'
|
|
} else if (rawRollMode === 'backend') {
|
|
rollMode.value = 'always'
|
|
} else if (rawRollMode === 'fixed' || rawRollMode === 'always') {
|
|
rollMode.value = rawRollMode as 'fixed' | 'always'
|
|
} else {
|
|
rollMode.value = 'fixed'
|
|
}
|
|
lastUsed.value = config.last_used || null
|
|
useRecommendedStrength.value = config.use_recommended_strength ?? false
|
|
recommendedStrengthScaleMin.value = config.recommended_strength_scale_min ?? 0.5
|
|
recommendedStrengthScaleMax.value = config.recommended_strength_scale_max ?? 1.0
|
|
} finally {
|
|
isRestoring = false
|
|
}
|
|
}
|
|
|
|
// Roll loras - call API to get random selection
|
|
const rollLoras = async (
|
|
poolConfig: any | null,
|
|
lockedLoras: LoraEntry[]
|
|
): Promise<LoraEntry[]> => {
|
|
try {
|
|
isRolling.value = true
|
|
|
|
const config = buildConfig()
|
|
|
|
// Build request body
|
|
const requestBody: any = {
|
|
model_strength_min: config.model_strength_min,
|
|
model_strength_max: config.model_strength_max,
|
|
use_same_clip_strength: !useCustomClipRange.value,
|
|
clip_strength_min: config.clip_strength_min,
|
|
clip_strength_max: config.clip_strength_max,
|
|
locked_loras: lockedLoras,
|
|
use_recommended_strength: config.use_recommended_strength,
|
|
recommended_strength_scale_min: config.recommended_strength_scale_min,
|
|
recommended_strength_scale_max: config.recommended_strength_scale_max,
|
|
}
|
|
|
|
// Add count parameters
|
|
if (config.count_mode === 'fixed') {
|
|
requestBody.count = config.count_fixed
|
|
} else {
|
|
requestBody.count_min = config.count_min
|
|
requestBody.count_max = config.count_max
|
|
}
|
|
|
|
// Add pool config if provided
|
|
if (poolConfig) {
|
|
requestBody.pool_config = poolConfig.filters || {}
|
|
}
|
|
|
|
// Call API endpoint
|
|
const response = await fetch('/api/lm/loras/random-sample', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(requestBody),
|
|
})
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json()
|
|
throw new Error(errorData.error || 'Failed to fetch random LoRAs')
|
|
}
|
|
|
|
const data = await response.json()
|
|
|
|
if (!data.success) {
|
|
throw new Error(data.error || 'Failed to get random LoRAs')
|
|
}
|
|
|
|
return data.loras || []
|
|
} catch (error) {
|
|
console.error('[LoraRandomizerState] Error rolling LoRAs:', error)
|
|
throw error
|
|
} finally {
|
|
isRolling.value = false
|
|
}
|
|
}
|
|
|
|
// Restore last used and return it
|
|
const useLastUsed = () => {
|
|
if (lastUsed.value && lastUsed.value.length > 0) {
|
|
return lastUsed.value
|
|
}
|
|
return null
|
|
}
|
|
|
|
// Computed properties
|
|
const isClipStrengthDisabled = computed(() => !useCustomClipRange.value)
|
|
const isRecommendedStrengthEnabled = computed(() => useRecommendedStrength.value)
|
|
|
|
// Watch all state changes and update widget value
|
|
watch([
|
|
countMode,
|
|
countFixed,
|
|
countMin,
|
|
countMax,
|
|
modelStrengthMin,
|
|
modelStrengthMax,
|
|
useCustomClipRange,
|
|
clipStrengthMin,
|
|
clipStrengthMax,
|
|
rollMode,
|
|
useRecommendedStrength,
|
|
recommendedStrengthScaleMin,
|
|
recommendedStrengthScaleMax,
|
|
], () => {
|
|
widget.value = buildConfig()
|
|
}, { deep: true })
|
|
|
|
return {
|
|
// State refs
|
|
countMode,
|
|
countFixed,
|
|
countMin,
|
|
countMax,
|
|
modelStrengthMin,
|
|
modelStrengthMax,
|
|
useCustomClipRange,
|
|
clipStrengthMin,
|
|
clipStrengthMax,
|
|
rollMode,
|
|
isRolling,
|
|
lastUsed,
|
|
useRecommendedStrength,
|
|
recommendedStrengthScaleMin,
|
|
recommendedStrengthScaleMax,
|
|
executionSeed,
|
|
nextSeed,
|
|
|
|
// Computed
|
|
isClipStrengthDisabled,
|
|
isRecommendedStrengthEnabled,
|
|
|
|
// Methods
|
|
buildConfig,
|
|
restoreFromConfig,
|
|
rollLoras,
|
|
useLastUsed,
|
|
generateNewSeed,
|
|
initializeNextSeed,
|
|
}
|
|
}
|