@@ -11,6 +11,7 @@ import {
1111 assetItemSchema
1212} from '@/platform/assets/schemas/assetSchema'
1313import { assetService } from '@/platform/assets/services/assetService'
14+ import { isCloud } from '@/platform/distribution/types'
1415import { useSettingStore } from '@/platform/settings/settingStore'
1516import { transformInputSpecV2ToV1 } from '@/schemas/nodeDef/migration'
1617import { isComboInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
@@ -22,6 +23,8 @@ import { ComponentWidgetImpl, addWidget } from '@/scripts/domWidget'
2223import type { BaseDOMWidget } from '@/scripts/domWidget'
2324import { addValueControlWidgets } from '@/scripts/widgets'
2425import type { ComfyWidgetConstructorV2 } from '@/scripts/widgets'
26+ import { useAssetsStore } from '@/stores/assetsStore'
27+ import { getMediaTypeFromFilename } from '@/utils/formatUtil'
2528
2629import { useRemoteWidget } from './useRemoteWidget'
2730
@@ -32,6 +35,20 @@ const getDefaultValue = (inputSpec: ComboInputSpec) => {
3235 return undefined
3336}
3437
38+ // Map node types to expected media types
39+ const NODE_MEDIA_TYPE_MAP : Record < string , 'image' | 'video' | 'audio' > = {
40+ LoadImage : 'image' ,
41+ LoadVideo : 'video' ,
42+ LoadAudio : 'audio'
43+ }
44+
45+ // Map node types to placeholder i18n keys
46+ const NODE_PLACEHOLDER_MAP : Record < string , string > = {
47+ LoadImage : 'widgets.uploadSelect.placeholderImage' ,
48+ LoadVideo : 'widgets.uploadSelect.placeholderVideo' ,
49+ LoadAudio : 'widgets.uploadSelect.placeholderAudio'
50+ }
51+
3552const addMultiSelectWidget = (
3653 node : LGraphNode ,
3754 inputSpec : ComboInputSpec
@@ -55,94 +72,176 @@ const addMultiSelectWidget = (
5572 return widget
5673}
5774
58- const addComboWidget = (
75+ const createAssetBrowserWidget = (
5976 node : LGraphNode ,
60- inputSpec : ComboInputSpec
77+ inputSpec : ComboInputSpec ,
78+ defaultValue : string | undefined
6179) : IBaseWidget => {
62- const settingStore = useSettingStore ( )
63- const isUsingAssetAPI = settingStore . get ( 'Comfy.Assets.UseAssetAPI' )
64- const isEligible = assetService . isAssetBrowserEligible (
65- node . comfyClass ,
66- inputSpec . name
67- )
80+ const currentValue = defaultValue
81+ const displayLabel = currentValue ?? t ( 'widgets.selectModel' )
82+ const assetBrowserDialog = useAssetBrowserDialog ( )
83+
84+ const widget = node . addWidget (
85+ 'asset' ,
86+ inputSpec . name ,
87+ displayLabel ,
88+ async function ( this : IBaseWidget ) {
89+ if ( ! isAssetWidget ( widget ) ) {
90+ throw new Error ( `Expected asset widget but received ${ widget . type } ` )
91+ }
92+ await assetBrowserDialog . show ( {
93+ nodeType : node . comfyClass || '' ,
94+ inputName : inputSpec . name ,
95+ currentValue : widget . value ,
96+ onAssetSelected : ( asset ) => {
97+ const validatedAsset = assetItemSchema . safeParse ( asset )
6898
69- if ( isUsingAssetAPI && isEligible ) {
70- const currentValue = getDefaultValue ( inputSpec )
71- const displayLabel = currentValue ?? t ( 'widgets.selectModel' )
99+ if ( ! validatedAsset . success ) {
100+ console . error (
101+ 'Invalid asset item:' ,
102+ validatedAsset . error . errors ,
103+ 'Received:' ,
104+ asset
105+ )
106+ return
107+ }
72108
73- const assetBrowserDialog = useAssetBrowserDialog ( )
109+ const filename = validatedAsset . data . user_metadata ?. filename
110+ const validatedFilename = assetFilenameSchema . safeParse ( filename )
74111
75- const widget = node . addWidget (
76- 'asset' ,
77- inputSpec . name ,
78- displayLabel ,
79- async function ( this : IBaseWidget ) {
80- if ( ! isAssetWidget ( widget ) ) {
81- throw new Error ( `Expected asset widget but received ${ widget . type } ` )
82- }
83- await assetBrowserDialog . show ( {
84- nodeType : node . comfyClass || '' ,
85- inputName : inputSpec . name ,
86- currentValue : widget . value ,
87- onAssetSelected : ( asset ) => {
88- const validatedAsset = assetItemSchema . safeParse ( asset )
89-
90- if ( ! validatedAsset . success ) {
91- console . error (
92- 'Invalid asset item:' ,
93- validatedAsset . error . errors ,
94- 'Received:' ,
95- asset
96- )
97- return
98- }
99-
100- const filename = validatedAsset . data . user_metadata ?. filename
101- const validatedFilename = assetFilenameSchema . safeParse ( filename )
102-
103- if ( ! validatedFilename . success ) {
104- console . error (
105- 'Invalid asset filename:' ,
106- validatedFilename . error . errors ,
107- 'for asset:' ,
108- validatedAsset . data . id
109- )
110- return
111- }
112-
113- const oldValue = widget . value
114- this . value = validatedFilename . data
115- node . onWidgetChanged ?.(
116- widget . name ,
117- validatedFilename . data ,
118- oldValue ,
119- widget
112+ if ( ! validatedFilename . success ) {
113+ console . error (
114+ 'Invalid asset filename:' ,
115+ validatedFilename . error . errors ,
116+ 'for asset:' ,
117+ validatedAsset . data . id
120118 )
119+ return
121120 }
122- } )
121+
122+ const oldValue = widget . value
123+ this . value = validatedFilename . data
124+ node . onWidgetChanged ?.(
125+ widget . name ,
126+ validatedFilename . data ,
127+ oldValue ,
128+ widget
129+ )
130+ }
131+ } )
132+ }
133+ )
134+
135+ return widget
136+ }
137+
138+ const createInputMappingWidget = (
139+ node : LGraphNode ,
140+ inputSpec : ComboInputSpec ,
141+ defaultValue : string | undefined
142+ ) : IBaseWidget => {
143+ const assetsStore = useAssetsStore ( )
144+
145+ const widget = node . addWidget (
146+ 'combo' ,
147+ inputSpec . name ,
148+ defaultValue ?? '' ,
149+ ( ) => { } ,
150+ {
151+ values : [ ] ,
152+ getOptionLabel : ( value ?: string | null ) => {
153+ if ( ! value ) {
154+ const placeholderKey =
155+ NODE_PLACEHOLDER_MAP [ node . comfyClass ?? '' ] ??
156+ 'widgets.uploadSelect.placeholder'
157+ return t ( placeholderKey )
158+ }
159+ return assetsStore . getInputName ( value )
123160 }
124- )
161+ }
162+ )
125163
126- return widget
164+ if ( assetsStore . inputAssets . length === 0 && ! assetsStore . inputLoading ) {
165+ void assetsStore . updateInputs ( ) . then ( ( ) => {
166+ // edge for users using nodes with 0 prior inputs
167+ // force canvas refresh the first time they add an asset
168+ // so they see filenames instead of hashes.
169+ node . setDirtyCanvas ( true , false )
170+ } )
127171 }
128172
129- // Create normal combo widget
173+ const origOptions = widget . options
174+ widget . options = new Proxy ( origOptions , {
175+ get ( target , prop ) {
176+ if ( prop !== 'values' ) {
177+ return target [ prop as keyof typeof target ]
178+ }
179+ return assetsStore . inputAssets
180+ . filter (
181+ ( asset ) =>
182+ getMediaTypeFromFilename ( asset . name ) ===
183+ NODE_MEDIA_TYPE_MAP [ node . comfyClass ?? '' ]
184+ )
185+ . map ( ( asset ) => asset . asset_hash )
186+ . filter ( ( hash ) : hash is string => ! ! hash )
187+ }
188+ } )
189+
190+ if ( inputSpec . control_after_generate ) {
191+ if ( ! isComboWidget ( widget ) ) {
192+ throw new Error ( `Expected combo widget but received ${ widget . type } ` )
193+ }
194+ widget . linkedWidgets = addValueControlWidgets (
195+ node ,
196+ widget ,
197+ undefined ,
198+ undefined ,
199+ transformInputSpecV2ToV1 ( inputSpec )
200+ )
201+ }
202+
203+ return widget
204+ }
205+
206+ const addComboWidget = (
207+ node : LGraphNode ,
208+ inputSpec : ComboInputSpec
209+ ) : IBaseWidget => {
130210 const defaultValue = getDefaultValue ( inputSpec )
131- const comboOptions = inputSpec . options ?? [ ]
211+
212+ if ( isCloud ) {
213+ const settingStore = useSettingStore ( )
214+ const isUsingAssetAPI = settingStore . get ( 'Comfy.Assets.UseAssetAPI' )
215+ const isEligible = assetService . isAssetBrowserEligible (
216+ node . comfyClass ,
217+ inputSpec . name
218+ )
219+
220+ if ( isUsingAssetAPI && isEligible ) {
221+ return createAssetBrowserWidget ( node , inputSpec , defaultValue )
222+ }
223+
224+ if ( NODE_MEDIA_TYPE_MAP [ node . comfyClass ?? '' ] ) {
225+ return createInputMappingWidget ( node , inputSpec , defaultValue )
226+ }
227+ }
228+
229+ // Standard combo widget
132230 const widget = node . addWidget (
133231 'combo' ,
134232 inputSpec . name ,
135233 defaultValue ,
136234 ( ) => { } ,
137235 {
138- values : comboOptions
236+ values : inputSpec . options ?? [ ]
139237 }
140238 )
141239
142240 if ( inputSpec . remote ) {
143241 if ( ! isComboWidget ( widget ) ) {
144242 throw new Error ( `Expected combo widget but received ${ widget . type } ` )
145243 }
244+
146245 const remoteWidget = useRemoteWidget ( {
147246 remoteConfig : inputSpec . remote ,
148247 defaultValue,
@@ -166,6 +265,7 @@ const addComboWidget = (
166265 if ( ! isComboWidget ( widget ) ) {
167266 throw new Error ( `Expected combo widget but received ${ widget . type } ` )
168267 }
268+
169269 widget . linkedWidgets = addValueControlWidgets (
170270 node ,
171271 widget ,
0 commit comments