From 9b9b0f245727cd2cc21ef88a61e55b3b77dfc846 Mon Sep 17 00:00:00 2001 From: dante01yoon Date: Wed, 20 May 2026 18:54:07 +0900 Subject: [PATCH 1/2] refactor(assets): remove isCloud guards in widget composables (FE-731) - useComboWidget: drop the `if (isCloud)` wrap so shouldUseAssetBrowser and NODE_MEDIA_TYPE_MAP paths run unconditionally. Rename resolveCloudDefault -> resolveAssetDefault. - useAssetWidgetData: drop the `if (isCloud)` branch and its empty-data fallback; remove the "Cloud-only" docstring. - Tests: drop the isCloud mock + per-test toggles. Delete the isCloud=false desktop spec and the OSS LoadImage fall-through case that asserted the now-removed branch. --- .../useAssetWidgetData.desktop.test.ts | 41 -------- .../composables/useAssetWidgetData.test.ts | 6 +- .../widgets/composables/useAssetWidgetData.ts | 88 ++++++++--------- .../composables/useComboWidget.test.ts | 95 +++++-------------- .../widgets/composables/useComboWidget.ts | 36 ++++--- 5 files changed, 78 insertions(+), 188 deletions(-) delete mode 100644 src/renderer/extensions/vueNodes/widgets/composables/useAssetWidgetData.desktop.test.ts diff --git a/src/renderer/extensions/vueNodes/widgets/composables/useAssetWidgetData.desktop.test.ts b/src/renderer/extensions/vueNodes/widgets/composables/useAssetWidgetData.desktop.test.ts deleted file mode 100644 index f8ac6a7e05..0000000000 --- a/src/renderer/extensions/vueNodes/widgets/composables/useAssetWidgetData.desktop.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { describe, expect, it, vi } from 'vitest' -import { ref } from 'vue' - -import { useAssetWidgetData } from '@/renderer/extensions/vueNodes/widgets/composables/useAssetWidgetData' - -vi.mock('@/platform/distribution/types', () => ({ - isCloud: false -})) - -const mockUpdateModelsForNodeType = vi.fn() -const mockGetCategoryForNodeType = vi.fn() - -vi.mock('@/stores/assetsStore', () => ({ - useAssetsStore: () => ({ - getAssets: () => [], - isModelLoading: () => false, - getError: () => undefined, - hasAssetKey: () => false, - updateModelsForNodeType: mockUpdateModelsForNodeType - }) -})) - -vi.mock('@/stores/modelToNodeStore', () => ({ - useModelToNodeStore: () => ({ - getCategoryForNodeType: mockGetCategoryForNodeType - }) -})) - -describe('useAssetWidgetData (desktop/isCloud=false)', () => { - it('returns empty/default values without calling stores', () => { - const nodeType = ref('CheckpointLoaderSimple') - const { category, assets, isLoading, error } = useAssetWidgetData(nodeType) - - expect(category.value).toBeUndefined() - expect(assets.value).toEqual([]) - expect(isLoading.value).toBe(false) - expect(error.value).toBeNull() - expect(mockUpdateModelsForNodeType).not.toHaveBeenCalled() - expect(mockGetCategoryForNodeType).not.toHaveBeenCalled() - }) -}) diff --git a/src/renderer/extensions/vueNodes/widgets/composables/useAssetWidgetData.test.ts b/src/renderer/extensions/vueNodes/widgets/composables/useAssetWidgetData.test.ts index 4ba8c95e87..ffe0d9e64a 100644 --- a/src/renderer/extensions/vueNodes/widgets/composables/useAssetWidgetData.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/composables/useAssetWidgetData.test.ts @@ -4,10 +4,6 @@ import { nextTick, ref } from 'vue' import type { AssetItem } from '@/platform/assets/schemas/assetSchema' import { useAssetWidgetData } from '@/renderer/extensions/vueNodes/widgets/composables/useAssetWidgetData' -vi.mock('@/platform/distribution/types', () => ({ - isCloud: true -})) - const mockAssetsByKey = new Map() const mockLoadingByKey = new Map() const mockErrorByKey = new Map() @@ -31,7 +27,7 @@ vi.mock('@/stores/modelToNodeStore', () => ({ }) })) -describe('useAssetWidgetData (cloud mode, isCloud=true)', () => { +describe('useAssetWidgetData', () => { beforeEach(() => { vi.clearAllMocks() mockAssetsByKey.clear() diff --git a/src/renderer/extensions/vueNodes/widgets/composables/useAssetWidgetData.ts b/src/renderer/extensions/vueNodes/widgets/composables/useAssetWidgetData.ts index 7ea3df54f4..e517e455ee 100644 --- a/src/renderer/extensions/vueNodes/widgets/composables/useAssetWidgetData.ts +++ b/src/renderer/extensions/vueNodes/widgets/composables/useAssetWidgetData.ts @@ -2,7 +2,6 @@ import { computed, toValue, watch } from 'vue' import type { MaybeRefOrGetter } from 'vue' import type { AssetItem } from '@/platform/assets/schemas/assetSchema' -import { isCloud } from '@/platform/distribution/types' import { useAssetsStore } from '@/stores/assetsStore' import { useModelToNodeStore } from '@/stores/modelToNodeStore' @@ -11,8 +10,6 @@ import { useModelToNodeStore } from '@/stores/modelToNodeStore' * Provides reactive asset data based on node type with automatic category detection. * Uses store-based caching to avoid duplicate fetches across multiple instances. * - * Cloud-only composable - returns empty data when not in cloud environment. - * * @param nodeType - ComfyUI node type (ref, getter, or plain value). Can be undefined. * Accepts: ref('CheckpointLoaderSimple'), () => 'CheckpointLoaderSimple', or 'CheckpointLoaderSimple' * @returns Reactive data including category, assets, dropdown items, loading state, and errors @@ -20,61 +17,52 @@ import { useModelToNodeStore } from '@/stores/modelToNodeStore' export function useAssetWidgetData( nodeType: MaybeRefOrGetter ) { - if (isCloud) { - const assetsStore = useAssetsStore() - const modelToNodeStore = useModelToNodeStore() + const assetsStore = useAssetsStore() + const modelToNodeStore = useModelToNodeStore() - const category = computed(() => { - const resolvedType = toValue(nodeType) - return resolvedType - ? modelToNodeStore.getCategoryForNodeType(resolvedType) - : undefined - }) + const category = computed(() => { + const resolvedType = toValue(nodeType) + return resolvedType + ? modelToNodeStore.getCategoryForNodeType(resolvedType) + : undefined + }) - const assets = computed(() => { - const resolvedType = toValue(nodeType) - return resolvedType ? (assetsStore.getAssets(resolvedType) ?? []) : [] - }) + const assets = computed(() => { + const resolvedType = toValue(nodeType) + return resolvedType ? (assetsStore.getAssets(resolvedType) ?? []) : [] + }) - const isLoading = computed(() => { - const resolvedType = toValue(nodeType) - return resolvedType ? assetsStore.isModelLoading(resolvedType) : false - }) + const isLoading = computed(() => { + const resolvedType = toValue(nodeType) + return resolvedType ? assetsStore.isModelLoading(resolvedType) : false + }) - const error = computed(() => { - const resolvedType = toValue(nodeType) - return resolvedType ? (assetsStore.getError(resolvedType) ?? null) : null - }) + const error = computed(() => { + const resolvedType = toValue(nodeType) + return resolvedType ? (assetsStore.getError(resolvedType) ?? null) : null + }) - watch( - () => toValue(nodeType), - async (currentNodeType) => { - if (!currentNodeType) { - return - } + watch( + () => toValue(nodeType), + async (currentNodeType) => { + if (!currentNodeType) { + return + } - const isLoading = assetsStore.isModelLoading(currentNodeType) - const hasBeenInitialized = assetsStore.hasAssetKey(currentNodeType) + const isLoading = assetsStore.isModelLoading(currentNodeType) + const hasBeenInitialized = assetsStore.hasAssetKey(currentNodeType) - if (!isLoading && !hasBeenInitialized) { - await assetsStore.updateModelsForNodeType(currentNodeType) - } - }, - { immediate: true } - ) - - return { - category, - assets, - isLoading, - error - } - } + if (!isLoading && !hasBeenInitialized) { + await assetsStore.updateModelsForNodeType(currentNodeType) + } + }, + { immediate: true } + ) return { - category: computed(() => undefined), - assets: computed(() => []), - isLoading: computed(() => false), - error: computed(() => null) + category, + assets, + isLoading, + error } } diff --git a/src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.test.ts b/src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.test.ts index 08cede8aa0..6b76a719b2 100644 --- a/src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.test.ts +++ b/src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.test.ts @@ -25,7 +25,6 @@ function createMockAssetItem(overrides: Partial = {}): AssetItem { } // Use vi.hoisted() to ensure mock state is initialized before mocks -const mockDistributionState = vi.hoisted(() => ({ isCloud: false })) const mockUpdateInputs = vi.hoisted(() => vi.fn(() => Promise.resolve())) const mockGetInputName = vi.hoisted(() => vi.fn((hash: string) => hash)) const mockGetAssets = vi.hoisted(() => vi.fn(() => [] as AssetItem[])) @@ -41,12 +40,6 @@ vi.mock('@/scripts/widgets', () => ({ addValueControlWidgets: vi.fn() })) -vi.mock('@/platform/distribution/types', () => ({ - get isCloud() { - return mockDistributionState.isCloud - } -})) - vi.mock('@/stores/assetsStore', () => ({ useAssetsStore: vi.fn(() => ({ get inputAssets() { @@ -147,7 +140,6 @@ describe('useComboWidget', () => { vi.mocked(assetService.isAssetBrowserEligible).mockReturnValue(false) vi.mocked(assetService.shouldUseAssetBrowser).mockReturnValue(false) vi.mocked(useAssetBrowserDialog).mockClear() - mockDistributionState.isCloud = false mockAssetsStoreState.inputAssets = [] mockAssetsStoreState.inputLoading = false mockUpdateInputs.mockClear() @@ -174,8 +166,7 @@ describe('useComboWidget', () => { expect(widget).toBe(mockWidget) }) - it('should create normal combo widget when asset API is disabled', () => { - mockDistributionState.isCloud = true + it('should create normal combo widget when asset browser is not eligible', () => { mockSettingStoreGet.mockReturnValue(false) vi.mocked(assetService.shouldUseAssetBrowser).mockReturnValue(false) @@ -201,15 +192,14 @@ describe('useComboWidget', () => { expect(widget).toBe(mockWidget) }) - describe('cloud asset browser widget', () => { + describe('asset browser widget', () => { // "Select model" is the fallback from t('widgets.selectModel') // in createAssetWidget when defaultValue is undefined. const PLACEHOLDER = 'Select model' - function setupCloudAssetWidget( + function setupAssetBrowserWidget( inputSpecOverrides: Partial = {} ) { - mockDistributionState.isCloud = true vi.mocked(assetService.shouldUseAssetBrowser).mockReturnValue(true) const constructor = useComboWidget() @@ -238,7 +228,7 @@ describe('useComboWidget', () => { createMockAssetItem({ name: 'cloud_model.safetensors' }) ]) - const { mockNode } = setupCloudAssetWidget({ + const { mockNode } = setupAssetBrowserWidget({ options: ['model1.safetensors', 'model2.safetensors'] }) @@ -254,38 +244,37 @@ describe('useComboWidget', () => { ) }) - it('should use first cloud asset as default instead of server combo options', () => { + it('should use first asset as default instead of server combo options', () => { mockGetAssets.mockReturnValue([ - createMockAssetItem({ name: 'cloud_model.safetensors' }) + createMockAssetItem({ name: 'asset_model.safetensors' }) ]) - const { mockNode } = setupCloudAssetWidget({ + const { mockNode } = setupAssetBrowserWidget({ options: ['local_only_model.safetensors'] }) - expect(getWidgetDefault(mockNode)).toBe('cloud_model.safetensors') + expect(getWidgetDefault(mockNode)).toBe('asset_model.safetensors') }) - it('should fallback to assets[0] when inputSpec.default not in cloud assets', () => { + it('should fallback to assets[0] when inputSpec.default not in assets', () => { mockGetAssets.mockReturnValue([ - createMockAssetItem({ name: 'cloud_model.safetensors' }) + createMockAssetItem({ name: 'asset_model.safetensors' }) ]) - const { mockNode } = setupCloudAssetWidget({ - default: 'not_in_cloud.safetensors' + const { mockNode } = setupAssetBrowserWidget({ + default: 'not_in_assets.safetensors' }) - expect(getWidgetDefault(mockNode)).toBe('cloud_model.safetensors') + expect(getWidgetDefault(mockNode)).toBe('asset_model.safetensors') }) - it('should prefer inputSpec.default when it exists in cloud assets', () => { + it('should prefer inputSpec.default when it exists in assets', () => { mockGetAssets.mockReturnValue([ createMockAssetItem({ name: 'other_model.safetensors' }), createMockAssetItem({ name: 'fallback.safetensors' }) ]) - const { mockNode } = setupCloudAssetWidget({ - // Note: no options array provided + const { mockNode } = setupAssetBrowserWidget({ default: 'fallback.safetensors' }) @@ -295,18 +284,17 @@ describe('useComboWidget', () => { it('should create asset browser widget when default value provided without options', () => { mockGetAssets.mockReturnValue([]) - const { mockNode } = setupCloudAssetWidget({ - // Note: no options array provided + const { mockNode } = setupAssetBrowserWidget({ default: 'fallback.safetensors' }) expect(getWidgetDefault(mockNode)).toBe(PLACEHOLDER) }) - it('should fallback to placeholder when cloud assets not loaded', () => { + it('should fallback to placeholder when assets not loaded', () => { mockGetAssets.mockReturnValue([]) - const { mockNode } = setupCloudAssetWidget({ + const { mockNode } = setupAssetBrowserWidget({ options: ['local_model.safetensors'] }) @@ -315,7 +303,6 @@ describe('useComboWidget', () => { }) it('should show Select model when asset widget has undefined current value', () => { - mockDistributionState.isCloud = true vi.mocked(assetService.shouldUseAssetBrowser).mockReturnValue(true) const constructor = useComboWidget() @@ -343,7 +330,7 @@ describe('useComboWidget', () => { expect(widget).toBe(mockWidget) }) - describe('cloud input asset mapping', () => { + describe('input asset mapping', () => { const HASH_FILENAME = '72e786ff2a44d682c4294db0b7098e569832bc394efc6dad644e6ec85a78efb7.png' const HASH_FILENAME_2 = @@ -354,10 +341,8 @@ describe('useComboWidget', () => { { nodeClass: 'LoadVideo', inputName: 'video' }, { nodeClass: 'LoadAudio', inputName: 'audio' } ])( - 'should create combo widget with getOptionLabel for $nodeClass in cloud', + 'should create combo widget with getOptionLabel for $nodeClass', ({ nodeClass, inputName }) => { - mockDistributionState.isCloud = true - const constructor = useComboWidget() const mockWidget = createMockWidget({ type: 'combo', @@ -387,9 +372,7 @@ describe('useComboWidget', () => { } ) - it('should keep the original options object for cloud input mappings', () => { - mockDistributionState.isCloud = true - + it('should keep the original options object for input mappings', () => { const constructor = useComboWidget() const mockNode = createMockNode('LoadImage') const inputSpec = createMockInputSpec({ @@ -405,7 +388,6 @@ describe('useComboWidget', () => { }) it("should format option labels using store's getInputName function", () => { - mockDistributionState.isCloud = true mockGetInputName.mockReturnValue('Beautiful Sunset.png') const constructor = useComboWidget() @@ -445,9 +427,7 @@ describe('useComboWidget', () => { expect(result).toBe('Beautiful Sunset.png') }) - it('should create normal combo widget for non-input nodes in cloud', () => { - mockDistributionState.isCloud = true - + it('should create normal combo widget for non-input nodes', () => { const constructor = useComboWidget() const mockWidget = createMockWidget() const mockNode = createMockNode('SomeOtherNode') @@ -469,34 +449,7 @@ describe('useComboWidget', () => { expect(widget).toBe(mockWidget) }) - it('should create normal combo widget for LoadImage in OSS', () => { - mockDistributionState.isCloud = false - - const constructor = useComboWidget() - const mockWidget = createMockWidget() - const mockNode = createMockNode('LoadImage') - vi.mocked(mockNode.addWidget).mockReturnValue(mockWidget) - const inputSpec = createMockInputSpec({ - name: 'image', - options: [HASH_FILENAME, HASH_FILENAME_2] - }) - - const widget = constructor(mockNode, inputSpec) - - expect(mockNode.addWidget).toHaveBeenCalledWith( - 'combo', - 'image', - HASH_FILENAME, - expect.any(Function), - { - values: [HASH_FILENAME, HASH_FILENAME_2] - } - ) - expect(widget).toBe(mockWidget) - }) - - it('should trigger lazy load for cloud input nodes', () => { - mockDistributionState.isCloud = true + it('should trigger lazy load for input nodes', () => { mockAssetsStoreState.inputAssets = [] mockAssetsStoreState.inputLoading = false @@ -515,7 +468,6 @@ describe('useComboWidget', () => { }) it('should not trigger lazy load if assets already loading', () => { - mockDistributionState.isCloud = true mockAssetsStoreState.inputAssets = [] mockAssetsStoreState.inputLoading = true @@ -534,7 +486,6 @@ describe('useComboWidget', () => { }) it('should not trigger lazy load if assets already loaded', () => { - mockDistributionState.isCloud = true mockAssetsStoreState.inputAssets = [ createMockAssetItem({ id: 'asset-123', diff --git a/src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts b/src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts index 92eb7bf5c6..5dbbd037f0 100644 --- a/src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts +++ b/src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts @@ -8,7 +8,6 @@ import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets' import { assetService } from '@/platform/assets/services/assetService' import { getAssetFilename } from '@/platform/assets/utils/assetMetadataUtils' import { createAssetWidget } from '@/platform/assets/utils/createAssetWidget' -import { isCloud } from '@/platform/distribution/types' import type { ComboInputSpec, InputSpec @@ -106,11 +105,11 @@ const addMultiSelectWidget = ( } /** - * Resolve the default value for a cloud asset widget. - * Priority: inputSpec.default (if present in cloud assets) → first cloud - * asset → undefined (shows placeholder). + * Resolve the default value for an asset widget. + * Priority: inputSpec.default (if present in assets) → first asset → undefined + * (shows placeholder). */ -function resolveCloudDefault( +function resolveAssetDefault( nodeType: string, specDefault: string | undefined ): string | undefined { @@ -119,7 +118,6 @@ function resolveCloudDefault( const inAssets = assets.some((a) => getAssetFilename(a) === specDefault) if (inAssets) return specDefault } - // empty filename → undefined (shows placeholder) const filename = assets.length ? getAssetFilename(assets[0]) : undefined return filename || undefined } @@ -213,21 +211,19 @@ const addComboWidget = ( ): IBaseWidget => { const defaultValue = getDefaultValue(inputSpec) - if (isCloud) { - if (assetService.shouldUseAssetBrowser(node.comfyClass, inputSpec.name)) { - // Default from cloud assets, not from server combo options. - // Server options list local files that may not exist in the user's - // cloud asset library, leading to missing-model errors on undo/reload. - const cloudDefault = resolveCloudDefault( - node.comfyClass ?? '', - inputSpec.default - ) - return createAssetBrowserWidget(node, inputSpec, cloudDefault) - } + if (assetService.shouldUseAssetBrowser(node.comfyClass, inputSpec.name)) { + // Default from asset library, not from server combo options. + // Server options list local files that may not exist in the user's + // asset library, leading to missing-model errors on undo/reload. + const assetDefault = resolveAssetDefault( + node.comfyClass ?? '', + inputSpec.default + ) + return createAssetBrowserWidget(node, inputSpec, assetDefault) + } - if (NODE_MEDIA_TYPE_MAP[node.comfyClass ?? '']) { - return createInputMappingWidget(node, inputSpec, defaultValue) - } + if (NODE_MEDIA_TYPE_MAP[node.comfyClass ?? '']) { + return createInputMappingWidget(node, inputSpec, defaultValue) } // Standard combo widget From c9d9ee1ee512174c4fafb55a15c132dd23320a0a Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Wed, 20 May 2026 09:58:37 +0000 Subject: [PATCH 2/2] [automated] Apply ESLint and Oxfmt fixes --- src/components/common/VirtualGrid.vue | 2 +- src/components/rightSidePanel/RightSidePanel.vue | 2 +- src/components/ui/textarea/Textarea.vue | 2 +- src/platform/assets/components/modelInfo/ModelInfoPanel.vue | 2 +- .../vueNodes/widgets/components/WidgetSelectDefault.vue | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/common/VirtualGrid.vue b/src/components/common/VirtualGrid.vue index a3858e2110..97e568af07 100644 --- a/src/components/common/VirtualGrid.vue +++ b/src/components/common/VirtualGrid.vue @@ -1,7 +1,7 @@