[feat] carve out path to call asset browser in combo widget (#5464)

* [ci] ignore local browser tests files

this is where i have claude put its one off playwright scripts

* [feat] carve out path to call asset browser in combo widget

* [feat] use buttons on Model Loaders when Asset API setting is on
This commit is contained in:
Arjan Singh
2025-09-10 22:26:07 -07:00
committed by GitHub
parent ca220440b2
commit 44e470488d
9 changed files with 423 additions and 56 deletions

View File

@@ -1757,6 +1757,9 @@
"copiedTooltip": "Copied",
"copyTooltip": "Copy message to clipboard"
},
"widgets": {
"selectModel": "Select model"
},
"nodeHelpPage": {
"inputs": "Inputs",
"outputs": "Outputs",

View File

@@ -1,8 +1,12 @@
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 type { IComboWidget } from '@/lib/litegraph/src/types/widgets'
import type {
IBaseWidget,
IComboWidget
} from '@/lib/litegraph/src/types/widgets'
import { transformInputSpecV2ToV1 } from '@/schemas/nodeDef/migration'
import {
ComboInputSpec,
@@ -18,6 +22,8 @@ import {
type ComfyWidgetConstructorV2,
addValueControlWidgets
} from '@/scripts/widgets'
import { assetService } from '@/services/assetService'
import { useSettingStore } from '@/stores/settingStore'
import { useRemoteWidget } from './useRemoteWidget'
@@ -28,7 +34,10 @@ const getDefaultValue = (inputSpec: ComboInputSpec) => {
return undefined
}
const addMultiSelectWidget = (node: LGraphNode, inputSpec: ComboInputSpec) => {
const addMultiSelectWidget = (
node: LGraphNode,
inputSpec: ComboInputSpec
): IBaseWidget => {
const widgetValue = ref<string[]>([])
const widget = new ComponentWidgetImpl({
node,
@@ -48,7 +57,36 @@ const addMultiSelectWidget = (node: LGraphNode, inputSpec: ComboInputSpec) => {
return widget
}
const addComboWidget = (node: LGraphNode, inputSpec: ComboInputSpec) => {
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) {
// Create button widget for Asset Browser
const currentValue = getDefaultValue(inputSpec)
const widget = node.addWidget(
'button',
inputSpec.name,
t('widgets.selectModel'),
() => {
console.log(
`Asset Browser would open here for:\nNode: ${node.type}\nWidget: ${inputSpec.name}\nCurrent Value:${currentValue}`
)
}
)
return widget
}
// Create normal combo widget
const defaultValue = getDefaultValue(inputSpec)
const comboOptions = inputSpec.options ?? []
const widget = node.addWidget(
@@ -59,14 +97,14 @@ const addComboWidget = (node: LGraphNode, inputSpec: ComboInputSpec) => {
{
values: comboOptions
}
) as IComboWidget
)
if (inputSpec.remote) {
const remoteWidget = useRemoteWidget({
remoteConfig: inputSpec.remote,
defaultValue,
node,
widget
widget: widget as IComboWidget
})
if (inputSpec.remote.refresh_button) remoteWidget.addRefreshButton()
@@ -84,7 +122,7 @@ const addComboWidget = (node: LGraphNode, inputSpec: ComboInputSpec) => {
if (inputSpec.control_after_generate) {
widget.linkedWidgets = addValueControlWidgets(
node,
widget,
widget as IComboWidget,
undefined,
undefined,
transformInputSpecV2ToV1(inputSpec)

View File

@@ -7,11 +7,17 @@ import {
assetResponseSchema
} from '@/schemas/assetSchema'
import { api } from '@/scripts/api'
import { useModelToNodeStore } from '@/stores/modelToNodeStore'
const ASSETS_ENDPOINT = '/assets'
const MODELS_TAG = 'models'
const MISSING_TAG = 'missing'
/**
* Input names that are eligible for asset browser
*/
const WHITELISTED_INPUTS = new Set(['ckpt_name', 'lora_name', 'vae_name'])
/**
* Validates asset response data using Zod schema
*/
@@ -102,9 +108,29 @@ function createAssetService() {
)
}
/**
* Checks if a widget input should use the asset browser based on both input name and node comfyClass
*
* @param inputName - The input name (e.g., 'ckpt_name', 'lora_name')
* @param nodeType - The ComfyUI node comfyClass (e.g., 'CheckpointLoaderSimple', 'LoraLoader')
* @returns true if this input should use asset browser
*/
function isAssetBrowserEligible(
inputName: string,
nodeType: string
): boolean {
return (
// Must be an approved input name
WHITELISTED_INPUTS.has(inputName) &&
// Must be a registered node type
useModelToNodeStore().getRegisteredNodeTypes().has(nodeType)
)
}
return {
getAssetModelFolders,
getAssetModels
getAssetModels,
isAssetBrowserEligible
}
}

View File

@@ -484,7 +484,18 @@ export const useLitegraphService = () => {
) ?? {}
if (widget) {
widget.label = st(nameKey, widget.label ?? inputName)
// Check if this is an Asset Browser button widget
const isAssetBrowserButton =
widget.type === 'button' && widget.value === 'Select model'
if (isAssetBrowserButton) {
// Preserve Asset Browser button label (don't translate)
widget.label = String(widget.value)
} else {
// Apply normal translation for other widgets
widget.label = st(nameKey, widget.label ?? inputName)
}
widget.options ??= {}
Object.assign(widget.options, {
advanced: inputSpec.advanced,

View File

@@ -1,5 +1,5 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { computed, ref } from 'vue'
import { ComfyNodeDefImpl, useNodeDefStore } from '@/stores/nodeDefStore'
@@ -22,6 +22,22 @@ export const useModelToNodeStore = defineStore('modelToNode', () => {
const modelToNodeMap = ref<Record<string, ModelNodeProvider[]>>({})
const nodeDefStore = useNodeDefStore()
const haveDefaultsLoaded = ref(false)
/** Internal computed for reactive caching of registered node types */
const registeredNodeTypes = computed(() => {
return new Set(
Object.values(modelToNodeMap.value)
.flat()
.map((provider) => provider.nodeDef.name)
)
})
/** Get set of all registered node types for efficient lookup */
function getRegisteredNodeTypes(): Set<string> {
registerDefaults()
return registeredNodeTypes.value
}
/**
* Get the node provider for the given model type name.
* @param modelType The name of the model type to get the node provider for.
@@ -91,6 +107,7 @@ export const useModelToNodeStore = defineStore('modelToNode', () => {
return {
modelToNodeMap,
getRegisteredNodeTypes,
getNodeProvider,
getAllNodeProviders,
registerNodeProvider,