refactor: extract shouldUseAssetBrowser(), fix missing inputNameForBrowser, sanitize logs (#8867)

## Summary

Extract duplicated asset-browser eligibility guard into
`shouldUseAssetBrowser()`, fix a missing parameter bug, and sanitize a
log statement.

## Changes

- **What**: 
- DRY: Extract the repeated 3-condition guard (`isCloud &&
isUsingAssetAPI && isAssetBrowserEligible`) into
`assetService.shouldUseAssetBrowser()`, used by `widgetInputs.ts` and
`useComboWidget.ts`
- Bug fix: `createAssetBrowserWidget()` in `useComboWidget.ts` was
missing the `inputNameForBrowser` parameter, which could show wrong
assets for nodes with multiple model inputs
- Security: `createAssetWidget.ts` no longer logs the full raw asset
object on validation failure
- `WidgetSelect.vue` keeps an inline guard because it has a special
`widget.type === "asset"` fallback that must stay gated behind
`isUsingAssetAPI`

## Review Focus

- `shouldUseAssetBrowser()` correctly combines the three conditions that
were previously duplicated
- `WidgetSelect.vue` preserves exact behavioral equivalence (a widget
can have `type === "asset"` when the setting is off if a user toggles
the setting after creating asset widgets)

Fixes #8744

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8867-refactor-extract-shouldUseAssetBrowser-fix-missing-inputNameForBrowser-sanitize-logs-3076d73d3650818cabdcd76a351dac31)
by [Unito](https://www.unito.io)
This commit is contained in:
Christian Byrne
2026-02-20 02:23:29 -08:00
committed by GitHub
parent 0792d26f77
commit c809ac5a43
9 changed files with 146 additions and 503 deletions

View File

@@ -15,27 +15,16 @@ const i18n = createI18n({
})
// Mock modules
vi.mock('@/platform/distribution/types', () => ({
isCloud: true
}))
vi.mock('@/platform/assets/services/assetService', () => ({
assetService: {
isAssetBrowserEligible: vi.fn(() => true)
shouldUseAssetBrowser: vi.fn(() => true),
isAssetAPIEnabled: vi.fn(() => true)
}
}))
const mockSettingStoreGet = vi.fn()
vi.mock('@/platform/settings/settingStore', () => ({
useSettingStore: vi.fn(() => ({
get: mockSettingStoreGet
}))
}))
// Import after mocks are defined
import { assetService } from '@/platform/assets/services/assetService'
const mockAssetServiceEligible = vi.mocked(assetService.isAssetBrowserEligible)
const mockShouldUseAssetBrowser = vi.mocked(assetService.shouldUseAssetBrowser)
describe('WidgetSelect asset mode', () => {
const createWidget = (): SimplifiedWidget<string | undefined> => ({
@@ -49,8 +38,7 @@ describe('WidgetSelect asset mode', () => {
beforeEach(() => {
vi.clearAllMocks()
mockAssetServiceEligible.mockReturnValue(true)
mockSettingStoreGet.mockReturnValue(true) // Default to true for UseAssetAPI
mockShouldUseAssetBrowser.mockReturnValue(true)
})
// Helper to mount with common setup
@@ -76,17 +64,8 @@ describe('WidgetSelect asset mode', () => {
).toBe(true)
})
it('uses default widget when UseAssetAPI setting is false', () => {
mockSettingStoreGet.mockReturnValue(false)
const wrapper = mountWidget()
expect(
wrapper.findComponent({ name: 'WidgetSelectDefault' }).exists()
).toBe(true)
})
it('uses default widget when node is not eligible', () => {
mockAssetServiceEligible.mockReturnValue(false)
it('uses default widget when shouldUseAssetBrowser returns false', () => {
mockShouldUseAssetBrowser.mockReturnValue(false)
const wrapper = mountWidget()
expect(

View File

@@ -18,35 +18,21 @@ import WidgetSelect from '@/renderer/extensions/vueNodes/widgets/components/Widg
import WidgetSelectDefault from '@/renderer/extensions/vueNodes/widgets/components/WidgetSelectDefault.vue'
import WidgetSelectDropdown from '@/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue'
// Mock state for distribution and settings
const mockDistributionState = vi.hoisted(() => ({ isCloud: false }))
const mockSettingStoreGet = vi.hoisted(() => vi.fn(() => false))
const mockIsAssetBrowserEligible = vi.hoisted(() => vi.fn(() => false))
vi.mock('@/platform/distribution/types', () => ({
get isCloud() {
return mockDistributionState.isCloud
}
}))
vi.mock('@/platform/settings/settingStore', () => ({
useSettingStore: vi.fn(() => ({
get: mockSettingStoreGet
}))
}))
// Mock state for asset service
const mockShouldUseAssetBrowser = vi.hoisted(() => vi.fn(() => false))
const mockIsAssetAPIEnabled = vi.hoisted(() => vi.fn(() => false))
vi.mock('@/platform/assets/services/assetService', () => ({
assetService: {
isAssetBrowserEligible: mockIsAssetBrowserEligible
shouldUseAssetBrowser: mockShouldUseAssetBrowser,
isAssetAPIEnabled: mockIsAssetAPIEnabled
}
}))
describe('WidgetSelect Value Binding', () => {
beforeEach(() => {
// Reset all mocks before each test
mockDistributionState.isCloud = false
mockSettingStoreGet.mockReturnValue(false)
mockIsAssetBrowserEligible.mockReturnValue(false)
mockShouldUseAssetBrowser.mockReturnValue(false)
mockIsAssetAPIEnabled.mockReturnValue(false)
vi.clearAllMocks()
})
@@ -263,10 +249,8 @@ describe('WidgetSelect Value Binding', () => {
})
describe('Asset mode detection', () => {
it('enables asset mode when all conditions are met', () => {
mockDistributionState.isCloud = true
mockSettingStoreGet.mockReturnValue(true)
mockIsAssetBrowserEligible.mockReturnValue(true)
it('enables asset mode when shouldUseAssetBrowser returns true', () => {
mockShouldUseAssetBrowser.mockReturnValue(true)
const widget = createMockWidget('test.safetensors')
const wrapper = mount(WidgetSelect, {
@@ -284,8 +268,8 @@ describe('WidgetSelect Value Binding', () => {
expect(wrapper.findComponent(WidgetSelectDropdown).exists()).toBe(true)
})
it('disables asset mode when conditions are not met', () => {
mockDistributionState.isCloud = false
it('disables asset mode when shouldUseAssetBrowser returns false', () => {
mockShouldUseAssetBrowser.mockReturnValue(false)
const widget = createMockWidget('test.safetensors')
const wrapper = mount(WidgetSelect, {

View File

@@ -24,8 +24,6 @@
import { computed } from 'vue'
import { assetService } from '@/platform/assets/services/assetService'
import { isCloud } from '@/platform/distribution/types'
import { useSettingStore } from '@/platform/settings/settingStore'
import WidgetSelectDefault from '@/renderer/extensions/vueNodes/widgets/components/WidgetSelectDefault.vue'
import WidgetSelectDropdown from '@/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue'
import WidgetWithControl from '@/renderer/extensions/vueNodes/widgets/components/WidgetWithControl.vue'
@@ -111,19 +109,11 @@ const specDescriptor = computed<{
}
})
const isAssetMode = computed(() => {
if (isCloud) {
const settingStore = useSettingStore()
const isUsingAssetAPI = settingStore.get('Comfy.Assets.UseAssetAPI')
const isEligible =
assetService.isAssetBrowserEligible(props.nodeType, props.widget.name) ||
props.widget.type === 'asset'
return isUsingAssetAPI && isEligible
}
return false
})
const isAssetMode = computed(
() =>
assetService.shouldUseAssetBrowser(props.nodeType, props.widget.name) ||
(assetService.isAssetAPIEnabled() && props.widget.type === 'asset')
)
const assetKind = computed(() => specDescriptor.value.kind)
const isDropdownUIWidget = computed(

View File

@@ -74,7 +74,8 @@ vi.mock('@/i18n', () => ({
vi.mock('@/platform/assets/services/assetService', () => ({
assetService: {
isAssetBrowserEligible: vi.fn(() => false)
isAssetBrowserEligible: vi.fn(() => false),
shouldUseAssetBrowser: vi.fn(() => false)
}
}))
@@ -135,6 +136,7 @@ describe('useComboWidget', () => {
vi.clearAllMocks()
mockSettingStoreGet.mockReturnValue(false)
vi.mocked(assetService.isAssetBrowserEligible).mockReturnValue(false)
vi.mocked(assetService.shouldUseAssetBrowser).mockReturnValue(false)
vi.mocked(useAssetBrowserDialog).mockClear()
mockDistributionState.isCloud = false
mockAssetsStoreState.inputAssets = []
@@ -165,8 +167,8 @@ describe('useComboWidget', () => {
it('should create normal combo widget when asset API is disabled', () => {
mockDistributionState.isCloud = true
mockSettingStoreGet.mockReturnValue(false) // Asset API disabled
vi.mocked(assetService.isAssetBrowserEligible).mockReturnValue(true) // Widget is eligible
mockSettingStoreGet.mockReturnValue(false)
vi.mocked(assetService.shouldUseAssetBrowser).mockReturnValue(false)
const constructor = useComboWidget()
const mockWidget = createMockWidget()
@@ -187,14 +189,12 @@ describe('useComboWidget', () => {
expect.any(Function),
{ values: ['model1.safetensors', 'model2.safetensors'] }
)
expect(mockSettingStoreGet).toHaveBeenCalledWith('Comfy.Assets.UseAssetAPI')
expect(widget).toBe(mockWidget)
})
it('should create asset browser widget when API enabled', () => {
mockDistributionState.isCloud = true
mockSettingStoreGet.mockReturnValue(true)
vi.mocked(assetService.isAssetBrowserEligible).mockReturnValue(true)
vi.mocked(assetService.shouldUseAssetBrowser).mockReturnValue(true)
const constructor = useComboWidget()
const mockWidget = createMockWidget({
@@ -218,8 +218,7 @@ describe('useComboWidget', () => {
expect.any(Function),
expect.any(Object)
)
expect(mockSettingStoreGet).toHaveBeenCalledWith('Comfy.Assets.UseAssetAPI')
expect(vi.mocked(assetService.isAssetBrowserEligible)).toHaveBeenCalledWith(
expect(vi.mocked(assetService.shouldUseAssetBrowser)).toHaveBeenCalledWith(
'CheckpointLoaderSimple',
'ckpt_name'
)
@@ -228,8 +227,7 @@ describe('useComboWidget', () => {
it('should create asset browser widget when default value provided without options', () => {
mockDistributionState.isCloud = true
mockSettingStoreGet.mockReturnValue(true)
vi.mocked(assetService.isAssetBrowserEligible).mockReturnValue(true)
vi.mocked(assetService.shouldUseAssetBrowser).mockReturnValue(true)
const constructor = useComboWidget()
const mockWidget = createMockWidget({
@@ -254,14 +252,12 @@ describe('useComboWidget', () => {
expect.any(Function),
expect.any(Object)
)
expect(mockSettingStoreGet).toHaveBeenCalledWith('Comfy.Assets.UseAssetAPI')
expect(widget).toBe(mockWidget)
})
it('should show Select model when asset widget has undefined current value', () => {
mockDistributionState.isCloud = true
mockSettingStoreGet.mockReturnValue(true)
vi.mocked(assetService.isAssetBrowserEligible).mockReturnValue(true)
vi.mocked(assetService.shouldUseAssetBrowser).mockReturnValue(true)
const constructor = useComboWidget()
const mockWidget = createMockWidget({
@@ -285,7 +281,6 @@ describe('useComboWidget', () => {
expect.any(Function),
expect.any(Object)
)
expect(mockSettingStoreGet).toHaveBeenCalledWith('Comfy.Assets.UseAssetAPI')
expect(widget).toBe(mockWidget)
})

View File

@@ -8,7 +8,6 @@ import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
import { assetService } from '@/platform/assets/services/assetService'
import { createAssetWidget } from '@/platform/assets/utils/createAssetWidget'
import { isCloud } from '@/platform/distribution/types'
import { useSettingStore } from '@/platform/settings/settingStore'
import type {
ComboInputSpec,
InputSpec
@@ -91,6 +90,7 @@ function createAssetBrowserWidget(
node,
widgetName: inputSpec.name,
nodeTypeForBrowser: node.comfyClass ?? '',
inputNameForBrowser: inputSpec.name,
defaultValue,
onValueChange: (widget, newValue, oldValue) => {
node.onWidgetChanged?.(widget.name, newValue, oldValue, widget)
@@ -177,14 +177,7 @@ const addComboWidget = (
const defaultValue = getDefaultValue(inputSpec)
if (isCloud) {
const settingStore = useSettingStore()
const isUsingAssetAPI = settingStore.get('Comfy.Assets.UseAssetAPI')
const isEligible = assetService.isAssetBrowserEligible(
node.comfyClass,
inputSpec.name
)
if (isUsingAssetAPI && isEligible) {
if (assetService.shouldUseAssetBrowser(node.comfyClass, inputSpec.name)) {
return createAssetBrowserWidget(node, inputSpec, defaultValue)
}