feat(randomizer): add LoRA locking and roll modes

- Implement LoRA locking to prevent specific LoRAs from being changed during randomization
- Add visual styling for locked state with amber accents and distinct backgrounds
- Introduce `roll_mode` configuration with 'backend' (execute current selection while generating new) and 'frontend' (execute newly generated selection) behaviors
- Move LoraPoolNode to 'Lora Manager/randomizer' category and remove standalone class mappings
- Standardize RETURN_NAMES in LoraRandomizerNode for consistency
This commit is contained in:
Will Miao
2026-01-12 21:53:47 +08:00
parent 177b20263d
commit bce6b0e610
13 changed files with 706 additions and 232 deletions

View File

@@ -32,12 +32,12 @@
import { onMounted } from 'vue'
import LoraRandomizerSettingsView from './lora-randomizer/LoraRandomizerSettingsView.vue'
import { useLoraRandomizerState } from '../composables/useLoraRandomizerState'
import type { ComponentWidget, RandomizerConfig } from '../composables/types'
import type { ComponentWidget, RandomizerConfig, LoraEntry } from '../composables/types'
// Props
const props = defineProps<{
widget: ComponentWidget
node: { id: number }
node: { id: number; inputs?: any[]; widgets?: any[]; graph?: any }
}>()
// State management
@@ -48,13 +48,15 @@ const handleRoll = async () => {
try {
console.log('[LoraRandomizerWidget] Roll button clicked')
// Get pool config from connected input (if any)
// This would need to be passed from the node's pool_config input
const poolConfig = null // TODO: Get from node input if connected
// Get pool config from connected pool_config input
const poolConfig = (props.node as any).getPoolConfig?.() || null
// Get locked loras from the loras widget
// This would need to be retrieved from the loras widget on the node
const lockedLoras: any[] = [] // TODO: Get from loras widget
const lorasWidget = props.node.widgets?.find((w: any) => w.name === "loras")
const lockedLoras: LoraEntry[] = (lorasWidget?.value || []).filter((lora: LoraEntry) => lora.locked === true)
console.log('[LoraRandomizerWidget] Pool config:', poolConfig)
console.log('[LoraRandomizerWidget] Locked loras:', lockedLoras)
// Call API to get random loras
const randomLoras = await state.rollLoras(poolConfig, lockedLoras)

View File

@@ -154,8 +154,10 @@
:disabled="rollMode !== 'frontend' || isRolling"
@click="$emit('roll')"
>
<span v-if="!isRolling">🎲 Roll</span>
<span v-else>Rolling...</span>
<span class="roll-button__content">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"></rect><path d="M8 8h.01"></path><path d="M16 16h.01"></path><path d="M16 8h.01"></path><path d="M8 16h.01"></path></svg>
Roll
</span>
</button>
</div>
<div class="roll-mode-selector">
@@ -329,7 +331,7 @@ defineEmits<{
}
.roll-button {
padding: 6px 16px;
padding: 8px 16px;
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
border: none;
border-radius: 4px;
@@ -339,6 +341,9 @@ defineEmits<{
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
display: flex;
align-items: center;
justify-content: center;
}
.roll-button:hover:not(:disabled) {
@@ -356,4 +361,10 @@ defineEmits<{
cursor: not-allowed;
background: linear-gradient(135deg, #52525b 0%, #3f3f46 100%);
}
.roll-button__content {
display: inline-flex;
align-items: center;
gap: 6px;
}
</style>

View File

@@ -6,6 +6,8 @@ import type { LoraPoolConfig, LegacyLoraPoolConfig, RandomizerConfig } from './c
// @ts-ignore - ComfyUI external module
import { app } from '../../../scripts/app.js'
// @ts-ignore
import { getPoolConfigFromConnectedNode, getActiveLorasFromNode, updateConnectedTriggerWords, updateDownstreamLoaders } from '../../web/comfyui/utils.js'
const vueApps = new Map<number, VueApp>()
@@ -119,6 +121,9 @@ function createLoraRandomizerWidget(node) {
internalValue = v
}
// Add method to get pool config from connected node
node.getPoolConfig = () => getPoolConfigFromConnectedNode(node)
// Handle roll event from Vue component
widget.onRoll = (randomLoras: any[]) => {
console.log('[createLoraRandomizerWidget] Roll event received:', randomLoras)
@@ -181,10 +186,21 @@ app.registerExtension({
// @ts-ignore
async LORAS(node: any) {
if (!addLorasWidgetCache) {
// @ts-ignore
const module = await import(/* @vite-ignore */ '../loras_widget.js')
addLorasWidgetCache = module.addLorasWidget
}
return addLorasWidgetCache(node, 'loras', {}, null)
// Check if this is a randomizer node to enable lock buttons
const isRandomizerNode = node.comfyClass === 'Lora Randomizer (LoraManager)'
console.log(node)
// For randomizer nodes, add a callback to update connected trigger words
const callback = isRandomizerNode ? (value: any) => {
updateDownstreamLoaders(node)
} : null
return addLorasWidgetCache(node, 'loras', { isRandomizerNode }, callback)
}
}
}