mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-21 06:49:37 +00:00
## Summary Add asset browser dialog integration for combo widgets with full animation support and proper state management. (Thank you Claude from saving me me from merge conflict hell on this one.) ## Changes - Widget integration: combo widgets now use AssetBrowserModal for eligible asset types - Dialog animations: added animateHide() for smooth close transitions - Async operations: proper sequencing of widget updates and dialog animations - Service layer: added getAssetsForNodeType() and getAssetDetails() methods - Type safety: comprehensive TypeScript types and error handling - Test coverage: unit tests for all new functionality - Bonus: fixed the hardcoded labels in AssetFilterBar Widget behavior: - Shows asset browser button for eligible widgets when asset API enabled - Handles asset selection with proper callback sequencing - Maintains widget value updates and litegraph notification ## Review Focus I will call out some stuff inline. ## Screenshots https://github.com/user-attachments/assets/9d3a72cf-d2b0-445f-8022-4c49daa04637 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5629-feat-integrate-asset-browser-with-widget-system-2726d73d365081a9a98be9a2307aee0b) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com>
169 lines
4.8 KiB
TypeScript
169 lines
4.8 KiB
TypeScript
import { ref } from 'vue'
|
|
|
|
import MultiSelectWidget from '@/components/graph/widgets/MultiSelectWidget.vue'
|
|
import { t } from '@/i18n'
|
|
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
|
|
import { isAssetWidget, isComboWidget } from '@/lib/litegraph/src/litegraph'
|
|
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
|
|
import { useAssetBrowserDialog } from '@/platform/assets/composables/useAssetBrowserDialog'
|
|
import { assetService } from '@/platform/assets/services/assetService'
|
|
import { useSettingStore } from '@/platform/settings/settingStore'
|
|
import { transformInputSpecV2ToV1 } from '@/schemas/nodeDef/migration'
|
|
import type { ComboInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
|
import {
|
|
type InputSpec,
|
|
isComboInputSpec
|
|
} from '@/schemas/nodeDef/nodeDefSchemaV2'
|
|
import {
|
|
type BaseDOMWidget,
|
|
ComponentWidgetImpl,
|
|
addWidget
|
|
} from '@/scripts/domWidget'
|
|
import {
|
|
type ComfyWidgetConstructorV2,
|
|
addValueControlWidgets
|
|
} from '@/scripts/widgets'
|
|
|
|
import { useRemoteWidget } from './useRemoteWidget'
|
|
|
|
const getDefaultValue = (inputSpec: ComboInputSpec) => {
|
|
if (inputSpec.default) return inputSpec.default
|
|
if (inputSpec.options?.length) return inputSpec.options[0]
|
|
if (inputSpec.remote) return 'Loading...'
|
|
return undefined
|
|
}
|
|
|
|
const addMultiSelectWidget = (
|
|
node: LGraphNode,
|
|
inputSpec: ComboInputSpec
|
|
): IBaseWidget => {
|
|
const widgetValue = ref<string[]>([])
|
|
const widget = new ComponentWidgetImpl({
|
|
node,
|
|
name: inputSpec.name,
|
|
component: MultiSelectWidget,
|
|
inputSpec,
|
|
options: {
|
|
getValue: () => widgetValue.value,
|
|
setValue: (value: string[]) => {
|
|
widgetValue.value = value
|
|
}
|
|
}
|
|
})
|
|
addWidget(node, widget as BaseDOMWidget<object | string>)
|
|
// TODO: Add remote support to multi-select widget
|
|
// https://github.com/Comfy-Org/ComfyUI_frontend/issues/3003
|
|
return widget
|
|
}
|
|
|
|
const addComboWidget = (
|
|
node: LGraphNode,
|
|
inputSpec: ComboInputSpec
|
|
): IBaseWidget => {
|
|
const settingStore = useSettingStore()
|
|
const isUsingAssetAPI = settingStore.get('Comfy.Assets.UseAssetAPI')
|
|
const isEligible = assetService.isAssetBrowserEligible(
|
|
inputSpec.name,
|
|
node.comfyClass || ''
|
|
)
|
|
|
|
if (isUsingAssetAPI && isEligible) {
|
|
// Get the default value for the button text (currently selected model)
|
|
const currentValue = getDefaultValue(inputSpec)
|
|
const displayLabel = currentValue ?? t('widgets.selectModel')
|
|
|
|
const assetBrowserDialog = useAssetBrowserDialog()
|
|
|
|
const widget = node.addWidget(
|
|
'asset',
|
|
inputSpec.name,
|
|
displayLabel,
|
|
async () => {
|
|
if (!isAssetWidget(widget)) {
|
|
throw new Error(`Expected asset widget but received ${widget.type}`)
|
|
}
|
|
await assetBrowserDialog.show({
|
|
nodeType: node.comfyClass || '',
|
|
inputName: inputSpec.name,
|
|
currentValue: widget.value,
|
|
onAssetSelected: (filename: string) => {
|
|
const oldValue = widget.value
|
|
widget.value = filename
|
|
// Using onWidgetChanged prevents a callback race where asset selection could reopen the dialog
|
|
node.onWidgetChanged?.(widget.name, filename, oldValue, widget)
|
|
}
|
|
})
|
|
}
|
|
)
|
|
|
|
return widget
|
|
}
|
|
|
|
// Create normal combo widget
|
|
const defaultValue = getDefaultValue(inputSpec)
|
|
const comboOptions = inputSpec.options ?? []
|
|
const widget = node.addWidget(
|
|
'combo',
|
|
inputSpec.name,
|
|
defaultValue,
|
|
() => {},
|
|
{
|
|
values: comboOptions
|
|
}
|
|
)
|
|
|
|
if (inputSpec.remote) {
|
|
if (!isComboWidget(widget)) {
|
|
throw new Error(`Expected combo widget but received ${widget.type}`)
|
|
}
|
|
const remoteWidget = useRemoteWidget({
|
|
remoteConfig: inputSpec.remote,
|
|
defaultValue,
|
|
node,
|
|
widget
|
|
})
|
|
if (inputSpec.remote.refresh_button) remoteWidget.addRefreshButton()
|
|
|
|
const origOptions = widget.options
|
|
widget.options = new Proxy(origOptions, {
|
|
get(target, prop) {
|
|
// Assertion: Proxy handler passthrough
|
|
return prop !== 'values'
|
|
? target[prop as keyof typeof target]
|
|
: remoteWidget.getValue()
|
|
}
|
|
})
|
|
}
|
|
|
|
if (inputSpec.control_after_generate) {
|
|
if (!isComboWidget(widget)) {
|
|
throw new Error(`Expected combo widget but received ${widget.type}`)
|
|
}
|
|
widget.linkedWidgets = addValueControlWidgets(
|
|
node,
|
|
widget,
|
|
undefined,
|
|
undefined,
|
|
transformInputSpecV2ToV1(inputSpec)
|
|
)
|
|
}
|
|
|
|
return widget
|
|
}
|
|
|
|
export const useComboWidget = () => {
|
|
const widgetConstructor: ComfyWidgetConstructorV2 = (
|
|
node: LGraphNode,
|
|
inputSpec: InputSpec
|
|
) => {
|
|
if (!isComboInputSpec(inputSpec)) {
|
|
throw new Error(`Invalid input data: ${inputSpec}`)
|
|
}
|
|
return inputSpec.multi_select
|
|
? addMultiSelectWidget(node, inputSpec)
|
|
: addComboWidget(node, inputSpec)
|
|
}
|
|
|
|
return widgetConstructor
|
|
}
|