refactor(lora-pool-widget): make ComponentWidget generic and remove legacy config

- Add generic type parameter to ComponentWidget<T> for type-safe callbacks
- Remove LegacyLoraPoolConfig interface and migrateConfig function
- Update LoraPoolWidget to use ComponentWidget<LoraPoolConfig>
- Clean up type imports across widget files
This commit is contained in:
Will Miao
2026-01-28 00:04:45 +08:00
parent ad4574e02f
commit a02462fff4
6 changed files with 22 additions and 87 deletions

View File

@@ -76,11 +76,11 @@ import TagsModal from './lora-pool/modals/TagsModal.vue'
import FoldersModal from './lora-pool/modals/FoldersModal.vue'
import { useLoraPoolState } from '../composables/useLoraPoolState'
import { useModalState, type ModalType } from '../composables/useModalState'
import type { ComponentWidget, LoraPoolConfig, LegacyLoraPoolConfig } from '../composables/types'
import type { ComponentWidget, LoraPoolConfig } from '../composables/types'
// Props
const props = defineProps<{
widget: ComponentWidget
widget: ComponentWidget<LoraPoolConfig>
node: { id: number }
}>()
@@ -99,7 +99,7 @@ onMounted(async () => {
// ComfyUI calls this automatically after setValue() in domWidget.ts
// NOTE: callback should NOT call refreshPreview() to avoid infinite loops:
// watch(filters) → refreshPreview() → buildConfig() → widget.value = v → callback → refreshPreview() → ...
props.widget.callback = (v: LoraPoolConfig | LegacyLoraPoolConfig) => {
props.widget.callback = (v: LoraPoolConfig) => {
if (v) {
console.log('[LoraPoolWidget] Restoring config from callback')
state.restoreFromConfig(v)
@@ -110,7 +110,7 @@ onMounted(async () => {
// Restore from saved value if workflow was already loaded
if (props.widget.value) {
console.log('[LoraPoolWidget] Restoring from initial value')
state.restoreFromConfig(props.widget.value as LoraPoolConfig | LegacyLoraPoolConfig)
state.restoreFromConfig(props.widget.value as LoraPoolConfig)
}
// Fetch filter options

View File

@@ -37,22 +37,6 @@ export interface FolderTreeNode {
children?: FolderTreeNode[]
}
// Legacy config for migration (v1)
export interface LegacyLoraPoolConfig {
version: 1
filters: {
baseModels: string[]
tags: { include: string[]; exclude: string[] }
folder: { path: string | null; recursive: boolean }
favoritesOnly: boolean
license: {
noCreditRequired: boolean | null
allowSellingGeneratedContent: boolean | null
}
}
preview: { matchCount: number; lastUpdated: number }
}
// Randomizer config
export interface RandomizerConfig {
count_mode: 'fixed' | 'range'
@@ -98,14 +82,17 @@ export interface CyclerConfig {
next_index?: number | null // Index for display after execution
}
export interface ComponentWidget {
// Widget config union type
export type WidgetConfig = LoraPoolConfig | RandomizerConfig | CyclerConfig
export interface ComponentWidget<T = WidgetConfig> {
/** @deprecated Use callback instead. Kept for backward compatibility with other widgets. */
serializeValue?: () => Promise<LoraPoolConfig | RandomizerConfig | CyclerConfig>
value?: LoraPoolConfig | LegacyLoraPoolConfig | RandomizerConfig | CyclerConfig
serializeValue?: () => Promise<T>
value?: T
/** @deprecated Use callback instead. Kept for backward compatibility with other widgets. */
onSetValue?: (v: LoraPoolConfig | LegacyLoraPoolConfig | RandomizerConfig | CyclerConfig) => void
onSetValue?: (v: T) => void
/** @deprecated Directly set widget.value instead. Kept for backward compatibility with other widgets. */
updateConfig?: (v: LoraPoolConfig | RandomizerConfig | CyclerConfig) => void
updateConfig?: (v: T) => void
/** Called by ComfyUI automatically after setValue() - use this for UI sync */
callback?: (v: LoraPoolConfig | LegacyLoraPoolConfig | RandomizerConfig | CyclerConfig) => void
callback?: (v: T) => void
}

View File

@@ -1,7 +1,6 @@
import { ref, computed, watch } from 'vue'
import type {
LoraPoolConfig,
LegacyLoraPoolConfig,
BaseModelOption,
TagOption,
FolderTreeNode,
@@ -10,7 +9,7 @@ import type {
} from './types'
import { useLoraPoolApi } from './useLoraPoolApi'
export function useLoraPoolState(widget: ComponentWidget) {
export function useLoraPoolState(widget: ComponentWidget<LoraPoolConfig>) {
const api = useLoraPoolApi()
// Flag to prevent infinite loops during config restoration
@@ -70,41 +69,13 @@ export function useLoraPoolState(widget: ComponentWidget) {
return config
}
// Migrate legacy config (v1) to current format (v2)
const migrateConfig = (legacy: LegacyLoraPoolConfig): LoraPoolConfig => {
return {
version: 2,
filters: {
baseModels: legacy.filters.baseModels || [],
tags: {
include: legacy.filters.tags?.include || [],
exclude: legacy.filters.tags?.exclude || []
},
folders: {
include: legacy.filters.folder?.path ? [legacy.filters.folder.path] : [],
exclude: []
},
license: {
noCreditRequired: legacy.filters.license?.noCreditRequired ?? false,
allowSelling: legacy.filters.license?.allowSellingGeneratedContent ?? false
}
},
preview: legacy.preview || { matchCount: 0, lastUpdated: 0 }
}
}
// Restore state from config
const restoreFromConfig = (rawConfig: LoraPoolConfig | LegacyLoraPoolConfig) => {
const restoreFromConfig = (config: LoraPoolConfig) => {
// Set flag to prevent buildConfig from triggering widget.value updates during restoration
// This breaks the infinite loop: callback → restoreFromConfig → watch → refreshPreview → buildConfig → widget.value = config → callback
isRestoring = true
try {
// Migrate if needed
const config = rawConfig.version === 1
? migrateConfig(rawConfig as LegacyLoraPoolConfig)
: rawConfig as LoraPoolConfig
if (!config?.filters) return
const { filters, preview } = config

View File

@@ -5,7 +5,7 @@ import LoraRandomizerWidget from '@/components/LoraRandomizerWidget.vue'
import LoraCyclerWidget from '@/components/LoraCyclerWidget.vue'
import JsonDisplayWidget from '@/components/JsonDisplayWidget.vue'
import AutocompleteTextWidget from '@/components/AutocompleteTextWidget.vue'
import type { LoraPoolConfig, LegacyLoraPoolConfig, RandomizerConfig, CyclerConfig } from './composables/types'
import type { LoraPoolConfig, RandomizerConfig, CyclerConfig } from './composables/types'
import {
setupModeChangeHandler,
createModeChangeCallback,
@@ -78,7 +78,7 @@ function createLoraPoolWidget(node) {
forwardMiddleMouseToCanvas(container)
let internalValue: LoraPoolConfig | LegacyLoraPoolConfig | undefined
let internalValue: LoraPoolConfig | undefined
const widget = node.addDOMWidget(
'pool_config',
@@ -88,7 +88,7 @@ function createLoraPoolWidget(node) {
getValue() {
return internalValue
},
setValue(v: LoraPoolConfig | LegacyLoraPoolConfig) {
setValue(v: LoraPoolConfig) {
internalValue = v
// ComfyUI automatically calls widget.callback after setValue
// No need for custom onSetValue mechanism

View File

@@ -970,7 +970,7 @@ to { transform: rotate(360deg);
font-size: 13px;
}
.lora-pool-widget[data-v-1cc8816c] {
.lora-pool-widget[data-v-4456abba] {
padding: 12px;
background: rgba(40, 44, 52, 0.6);
border-radius: 4px;
@@ -11378,33 +11378,10 @@ function useLoraPoolState(widget) {
}
return config;
};
const migrateConfig = (legacy) => {
var _a2, _b, _c, _d, _e2;
return {
version: 2,
filters: {
baseModels: legacy.filters.baseModels || [],
tags: {
include: ((_a2 = legacy.filters.tags) == null ? void 0 : _a2.include) || [],
exclude: ((_b = legacy.filters.tags) == null ? void 0 : _b.exclude) || []
},
folders: {
include: ((_c = legacy.filters.folder) == null ? void 0 : _c.path) ? [legacy.filters.folder.path] : [],
exclude: []
},
license: {
noCreditRequired: ((_d = legacy.filters.license) == null ? void 0 : _d.noCreditRequired) ?? false,
allowSelling: ((_e2 = legacy.filters.license) == null ? void 0 : _e2.allowSellingGeneratedContent) ?? false
}
},
preview: legacy.preview || { matchCount: 0, lastUpdated: 0 }
};
};
const restoreFromConfig = (rawConfig) => {
const restoreFromConfig = (config) => {
var _a2, _b, _c, _d, _e2, _f;
isRestoring = true;
try {
const config = rawConfig.version === 1 ? migrateConfig(rawConfig) : rawConfig;
if (!(config == null ? void 0 : config.filters)) return;
const { filters, preview } = config;
const updateIfChanged = (refValue, newValue) => {
@@ -11601,7 +11578,7 @@ const _sfc_main$9 = /* @__PURE__ */ defineComponent({
};
}
});
const LoraPoolWidget = /* @__PURE__ */ _export_sfc(_sfc_main$9, [["__scopeId", "data-v-1cc8816c"]]);
const LoraPoolWidget = /* @__PURE__ */ _export_sfc(_sfc_main$9, [["__scopeId", "data-v-4456abba"]]);
const _hoisted_1$8 = { class: "last-used-preview" };
const _hoisted_2$5 = { class: "last-used-preview__content" };
const _hoisted_3$3 = ["src", "onError"];

File diff suppressed because one or more lines are too long