Compare commits

...

10 Commits

Author SHA1 Message Date
dante01yoon
55b0329203 test(builder): use exclusive fallback instead of .or() in selectInputWidget
Previous .or() implementation acted as a union — both branches resolved
for number widgets (e.g. 'seed' has aria-label on wrapper + a Decrement
button under widget-layout-field-label), causing Playwright strict mode
violations on tests that previously passed.

Switch to a count-based check: prefer the aria-label match when it
exists, otherwise fall back to the dropdown-trigger button under the
visible label row. This restores grid/number widget behaviour while
keeping asset-mode dropdown support.
2026-05-21 09:43:38 +09:00
GitHub Action
c579c88e90 [automated] Apply ESLint and Oxfmt fixes 2026-05-20 04:03:11 +00:00
dante01yoon
0797b7af7a test(builder): make selectInputWidget work in both grid and asset modes
FE-729 routes model widgets (ckpt_name, lora_name, etc.) through
WidgetSelectDropdown on OSS as well as Cloud. That dropdown's trigger
button has no aria-label — the widget name lives in a sibling label
div ([data-testid="widget-layout-field-label"]). Grid-mode widgets
(WidgetSelectDefault) still carry aria-label on the input directly.

Update the helper to .or() the two strategies so existing aria-label
lookups keep working while asset-mode dropdowns are reached via the
visible label text + sibling trigger button.
2026-05-20 12:59:06 +09:00
dante01yoon
ffe8d0f2ee test(assets): drop OSS legacy sidebar tree tests (FE-729)
After FE-729 the model-library sidebar tab routes to the Asset Browser
dialog and never renders ModelLibrarySidebarTab.vue. The legacy
sidebar-tree tests now validate dead code paths:

- delete browser_tests/tests/sidebar/modelLibrary.spec.ts entirely
  (tab/folders/search/refresh/empty-state — all dead)
- drop the model-library entry from the defaultKeybindings sidebar
  toggle table (KeyM no longer toggles a panel)
- remove the Refresh-clears-resolved-missing-model case from
  errorsTabMissingModels.spec.ts — it waited for the legacy
  /experiment/models endpoint that modelStore no longer hits

Dialog-flow coverage for the new entry point belongs to FE-732.
2026-05-20 08:42:10 +09:00
Dante
ec0711d62e Merge branch 'main' into jaewon/fe-729-delete-is-asset-api-enabled 2026-05-19 22:29:44 +09:00
dante01yoon
e48dcd11f1 ci(temp): enable --enable-assets on Playwright ComfyUI server
Temporary unblock for FE-729 e2e while BE-786 is in flight. FE-729
removes the legacy /api/models fallback in modelStore, so the assets
endpoint must be reachable in CI. BE-786 will make assets always-on
in OSS core; once that ships in the CI ComfyUI image, this flag (and
this commit) MUST be reverted before merging FE-729 to main.
2026-05-19 22:14:38 +09:00
GitHub Action
09943f855a [automated] Apply ESLint and Oxfmt fixes 2026-05-19 01:30:57 +00:00
dante01yoon
b34026527a refactor(assets): remove isAssetPreviewSupported wrapper and simplify callers
Follow-up to the previous FE-729 commit. After deleting
isAssetAPIEnabled, isAssetPreviewSupported() became a wrapper that
always returned true. Remove the function and simplify all callers.

Changes:
- Delete isAssetPreviewSupported() from assetPreviewUtil.ts.
- Media3DTop.vue: drop the isAssetPreviewSupported() arm of the
  loadThumbnail guard (asset.name check is still required).
- saveMesh.ts: unwrap two `if (isAssetPreviewSupported()) { ... }`
  blocks in applySaveGLBOutput and the SaveGLB beforeRegisterNodeDef
  extension callback.
- FormDropdownMenuItem.vue: drop the early return from
  resolveMeshPreview.
- useLoad3d.ts: drop the isAssetPreviewSupported() arm of the
  modelReady guard.
- Tests: remove the dead "asset preview is unsupported" branches
  (useLoad3d, Media3DTop, FormDropdownMenuItem) and clean up the
  associated mocks and hoisted state.

Auto-fixed unrelated tailwind class-order lint errors in five files
(VirtualGrid, RightSidePanel, Textarea, ModelInfoPanel,
WidgetSelectDefault) to keep CI green.
2026-05-19 10:27:08 +09:00
GitHub Action
fcdc4404eb [automated] Apply ESLint and Oxfmt fixes 2026-05-19 00:48:00 +00:00
dante01yoon
7f6d354a8e refactor(assets): delete isAssetAPIEnabled and Comfy.Assets.UseAssetAPI
FE-729 (L1.2 — L1 central). Asset API will be always available
post-BE-786 (`--enable-assets` removed from OSS). Delete the gating
function, the user-facing setting, the experimental warning, and the
toggle command. Simplify all caller sites to assume the asset API
is available.

Changes:
- Delete isAssetAPIEnabled() from assetService.ts; simplify
  shouldUseAssetBrowser() to call isAssetBrowserEligible() directly.
- Remove the EXPERIMENTAL_WARNING prefix from four asset-load error
  messages.
- Drop the Comfy.Assets.UseAssetAPI setting from coreSettings.ts and
  its schema entry in apiSchema.ts.
- Remove the Comfy.ToggleAssetAPI command outright. Keep
  Comfy.BrowseModelAssets but drop its setting-check / setting-set
  preamble and the "Experimental:" label prefix; the command now
  just opens the asset browser dialog.
- modelStore: drop the useAssetAPI fork in createGetModelsFunc and
  loadModelFolders; always route through assetService.
- sidebarTabStore: model-library tab click always routes through
  Comfy.BrowseModelAssets (no more setting check).
- assetPreviewUtil.isAssetPreviewSupported() returns true
  unconditionally; caller-site simplification deferred.
- WidgetSelect.vue: simplify isAssetMode to drop the
  isAssetAPIEnabled() arm of the disjunction.
- Test mocks/expectations updated.

Blocked-merge by BE-786 (OSS `--enable-assets` removal). PR opened
as draft.

Also auto-fixed unrelated tailwind class-order lint errors in four
files (VirtualGrid, RightSidePanel, Textarea, ModelInfoPanel,
WidgetSelectDefault) to keep CI green.
2026-05-19 09:43:27 +09:00
26 changed files with 88 additions and 607 deletions

View File

@@ -19,5 +19,8 @@ runs:
run: |
set -euo pipefail
cp -r ./tools/devtools/* /ComfyUI/custom_nodes/ComfyUI_devtools/
cd /ComfyUI && python3 main.py --cpu --multi-user --front-end-root "${{ inputs.front_end_root }}" &
# TODO(FE-729): remove --enable-assets once BE-786 lands in the CI ComfyUI image
# (BE-786 removes the gate so /api/assets is always on). Until then, FE-729
# routes modelStore through assetService, which 503s without this flag.
cd /ComfyUI && python3 main.py --cpu --multi-user --enable-assets --front-end-root "${{ inputs.front_end_root }}" &
wait-for-it --service 127.0.0.1:8188 -t ${{ inputs.timeout }}

View File

@@ -119,9 +119,22 @@ export class BuilderSelectHelper {
)[0]
if (!nodeRef) throw new Error(`Node ${nodeTitle} not found`)
await nodeRef.centerOnNode()
const widgetLocator = this.comfyPage.vueNodes
.getNodeLocator(String(nodeRef.id))
.getByLabel(widgetName, { exact: true })
const node = this.comfyPage.vueNodes.getNodeLocator(String(nodeRef.id))
// Grid-mode widgets (WidgetSelectDefault) and number widgets expose
// aria-label on a wrapper/input. Asset-mode widgets (WidgetSelectDropdown)
// do not — the widget name lives in a sibling
// [data-testid="widget-layout-field-label"] div, so fall back to clicking
// the dropdown trigger button in the same row.
const byAriaLabel = node.getByLabel(widgetName, { exact: true })
const widgetLocator =
(await byAriaLabel.count()) > 0
? byAriaLabel
: node
.getByTestId('widget-layout-field-label')
.filter({ hasText: widgetName })
.locator('..')
.locator('button')
.first()
// oxlint-disable-next-line playwright/no-force-option -- Node container has conditional pointer-events:none that blocks actionability
await widgetLocator.click({ force: true })
await this.comfyPage.nextFrame()

View File

@@ -27,7 +27,6 @@ test.describe('Default Keybindings', { tag: '@keyboard' }, () => {
const sidebarTabs = [
{ key: 'KeyW', tabId: 'workflows', label: 'workflows' },
{ key: 'KeyN', tabId: 'node-library', label: 'node library' },
{ key: 'KeyM', tabId: 'model-library', label: 'model library' },
{ key: 'KeyA', tabId: 'assets', label: 'assets' }
] as const

View File

@@ -115,42 +115,5 @@ test.describe('Errors tab - Missing models', { tag: '@ui' }, () => {
comfyPage.page.getByTestId(TestIds.dialogs.missingModelRefresh)
).toBeVisible()
})
test('Should clear resolved missing model when Refresh is clicked', async ({
comfyPage
}) => {
await loadWorkflowAndOpenErrorsTab(comfyPage, 'missing/missing_models')
await comfyPage.page.route(/\/object_info$/, async (route) => {
const response = await route.fetch()
const objectInfo = await response.json()
const ckptName =
objectInfo.CheckpointLoaderSimple.input.required.ckpt_name
ckptName[0] = [...ckptName[0], 'fake_model.safetensors']
await route.fulfill({ response, json: objectInfo })
})
const objectInfoResponse = comfyPage.page.waitForResponse((response) => {
const url = new URL(response.url())
return url.pathname.endsWith('/object_info') && response.ok()
})
const modelFoldersResponse = comfyPage.page.waitForResponse(
(response) => {
const url = new URL(response.url())
return url.pathname.endsWith('/experiment/models') && response.ok()
}
)
const refreshButton = comfyPage.page.getByTestId(
TestIds.dialogs.missingModelRefresh
)
await Promise.all([
objectInfoResponse,
modelFoldersResponse,
refreshButton.click()
])
await expect(
comfyPage.page.getByTestId(TestIds.dialogs.missingModelsGroup)
).toBeHidden()
})
})
})

View File

@@ -1,236 +0,0 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
const MOCK_FOLDERS: Record<string, string[]> = {
checkpoints: [
'sd_xl_base_1.0.safetensors',
'dreamshaper_8.safetensors',
'realisticVision_v51.safetensors'
],
loras: ['detail_tweaker_xl.safetensors', 'add_brightness.safetensors'],
vae: ['sdxl_vae.safetensors']
}
// ==========================================================================
// 1. Tab open/close
// ==========================================================================
test.describe('Model library sidebar - tab', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.modelLibrary.mockFoldersWithFiles(MOCK_FOLDERS)
await comfyPage.setup()
})
test.afterEach(async ({ comfyPage }) => {
await comfyPage.modelLibrary.clearMocks()
})
test('Opens model library tab and shows tree', async ({ comfyPage }) => {
const tab = comfyPage.menu.modelLibraryTab
await tab.open()
await expect(tab.modelTree).toBeVisible()
await expect(tab.searchInput).toBeVisible()
})
test('Shows refresh and load all folders buttons', async ({ comfyPage }) => {
const tab = comfyPage.menu.modelLibraryTab
await tab.open()
await expect(tab.refreshButton).toBeVisible()
await expect(tab.loadAllFoldersButton).toBeVisible()
})
})
// ==========================================================================
// 2. Folder display
// ==========================================================================
test.describe('Model library sidebar - folders', () => {
// Mocks are set up before setup(), so app.ts's loadModelFolders()
// call during initialization hits the mock and populates the store.
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.modelLibrary.mockFoldersWithFiles(MOCK_FOLDERS)
await comfyPage.setup()
})
test.afterEach(async ({ comfyPage }) => {
await comfyPage.modelLibrary.clearMocks()
})
test('Displays model folders after opening tab', async ({ comfyPage }) => {
const tab = comfyPage.menu.modelLibraryTab
await tab.open()
await expect(tab.getFolderByLabel('checkpoints')).toBeVisible()
await expect(tab.getFolderByLabel('loras')).toBeVisible()
await expect(tab.getFolderByLabel('vae')).toBeVisible()
})
test('Expanding a folder loads and shows models', async ({ comfyPage }) => {
const tab = comfyPage.menu.modelLibraryTab
await tab.open()
// Click the folder to expand it
await tab.getFolderByLabel('checkpoints').click()
// Models should appear as leaf nodes
await expect(tab.getLeafByLabel('sd_xl_base_1.0')).toBeVisible()
await expect(tab.getLeafByLabel('dreamshaper_8')).toBeVisible()
await expect(tab.getLeafByLabel('realisticVision_v51')).toBeVisible()
})
test('Expanding a different folder shows its models', async ({
comfyPage
}) => {
const tab = comfyPage.menu.modelLibraryTab
await tab.open()
await tab.getFolderByLabel('loras').click()
await expect(tab.getLeafByLabel('detail_tweaker_xl')).toBeVisible()
await expect(tab.getLeafByLabel('add_brightness')).toBeVisible()
})
})
// ==========================================================================
// 3. Search
// ==========================================================================
test.describe('Model library sidebar - search', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.modelLibrary.mockFoldersWithFiles(MOCK_FOLDERS)
await comfyPage.setup()
})
test.afterEach(async ({ comfyPage }) => {
await comfyPage.modelLibrary.clearMocks()
})
test('Search filters models by filename', async ({ comfyPage }) => {
const tab = comfyPage.menu.modelLibraryTab
await tab.open()
await tab.searchInput.fill('dreamshaper')
// Wait for debounce (300ms) + load + render
await expect(tab.getLeafByLabel('dreamshaper_8')).toBeVisible()
// Other models should not be visible
await expect(tab.getLeafByLabel('sd_xl_base_1.0')).toBeHidden()
})
test('Clearing search restores folder view', async ({ comfyPage }) => {
const tab = comfyPage.menu.modelLibraryTab
await tab.open()
await tab.searchInput.fill('dreamshaper')
await expect(tab.getLeafByLabel('dreamshaper_8')).toBeVisible()
// Clear the search
await tab.searchInput.fill('')
// Folders should be visible again (collapsed)
await expect(tab.getFolderByLabel('checkpoints')).toBeVisible()
await expect(tab.getFolderByLabel('loras')).toBeVisible()
})
test('Search with no matches shows empty tree', async ({ comfyPage }) => {
const tab = comfyPage.menu.modelLibraryTab
await tab.open()
// Expand a folder and verify models are present before searching
await tab.getFolderByLabel('checkpoints').click()
await expect(tab.leafNodes).not.toHaveCount(0)
await tab.searchInput.fill('nonexistent_model_xyz')
// Wait for debounce, then verify no leaf nodes
await expect.poll(() => tab.leafNodes.count()).toBe(0)
})
})
// ==========================================================================
// 4. Refresh and load all
// ==========================================================================
test.describe('Model library sidebar - refresh', () => {
test.afterEach(async ({ comfyPage }) => {
await comfyPage.modelLibrary.clearMocks()
})
test('Refresh button reloads folder list', async ({ comfyPage }) => {
await comfyPage.modelLibrary.mockFoldersWithFiles({
checkpoints: ['model_a.safetensors']
})
await comfyPage.setup()
const tab = comfyPage.menu.modelLibraryTab
await tab.open()
await expect(tab.getFolderByLabel('checkpoints')).toBeVisible()
// Update mock to include a new folder
await comfyPage.modelLibrary.clearMocks()
await comfyPage.modelLibrary.mockFoldersWithFiles({
checkpoints: ['model_a.safetensors'],
loras: ['lora_b.safetensors']
})
// Wait for the refresh request to complete
const refreshRequest = comfyPage.page.waitForRequest(
(req) => req.url().endsWith('/experiment/models'),
{ timeout: 5000 }
)
await tab.refreshButton.click()
await refreshRequest
await expect(tab.getFolderByLabel('loras')).toBeVisible()
})
test('Load all folders button triggers loading all model data', async ({
comfyPage
}) => {
await comfyPage.modelLibrary.mockFoldersWithFiles(MOCK_FOLDERS)
await comfyPage.setup()
const tab = comfyPage.menu.modelLibraryTab
await tab.open()
// Wait for a per-folder model files request triggered by load all
const folderRequest = comfyPage.page.waitForRequest(
(req) =>
/\/api\/experiment\/models\/[^/]+$/.test(req.url()) &&
req.method() === 'GET',
{ timeout: 5000 }
)
await tab.loadAllFoldersButton.click()
await folderRequest
})
})
// ==========================================================================
// 5. Empty state
// ==========================================================================
test.describe('Model library sidebar - empty state', () => {
test.afterEach(async ({ comfyPage }) => {
await comfyPage.modelLibrary.clearMocks()
})
test('Shows empty tree when no model folders exist', async ({
comfyPage
}) => {
await comfyPage.modelLibrary.mockFoldersWithFiles({})
await comfyPage.setup()
const tab = comfyPage.menu.modelLibraryTab
await tab.open()
await expect(tab.modelTree).toBeVisible()
await expect(tab.folderNodes).toHaveCount(0)
await expect(tab.leafNodes).toHaveCount(0)
})
})

View File

@@ -1283,23 +1283,9 @@ export function useCoreCommands(): ComfyCommand[] {
{
id: 'Comfy.BrowseModelAssets',
icon: 'pi pi-folder-open',
label: 'Experimental: Browse Model Assets',
label: 'Browse Model Assets',
versionAdded: '1.28.3',
function: async () => {
if (!useSettingStore().get('Comfy.Assets.UseAssetAPI')) {
const confirmed = await dialogService.confirm({
title: 'Enable Asset API',
message:
'The Asset API is currently disabled. Would you like to enable it?',
type: 'default'
})
if (!confirmed) return
const settingStore = useSettingStore()
await settingStore.set('Comfy.Assets.UseAssetAPI', true)
await workflowService.reloadCurrentWorkflow()
}
const assetBrowserDialog = useAssetBrowserDialog()
await assetBrowserDialog.browse({
assetType: 'models',
@@ -1318,22 +1304,6 @@ export function useCoreCommands(): ComfyCommand[] {
})
}
},
{
id: 'Comfy.ToggleAssetAPI',
icon: 'pi pi-database',
label: () =>
`Experimental: ${
useSettingStore().get('Comfy.Assets.UseAssetAPI')
? 'Disable'
: 'Enable'
} AssetAPI`,
function: async () => {
const settingStore = useSettingStore()
const current = settingStore.get('Comfy.Assets.UseAssetAPI') ?? false
await settingStore.set('Comfy.Assets.UseAssetAPI', !current)
await useWorkflowService().reloadCurrentWorkflow() // ensure changes take effect immediately
}
},
{
id: 'Comfy.ToggleQPOV2',
icon: 'pi pi-list',

View File

@@ -75,7 +75,6 @@ vi.mock('@/renderer/core/canvas/canvasStore', () => ({
}))
vi.mock('@/platform/assets/utils/assetPreviewUtil', () => ({
isAssetPreviewSupported: vi.fn(() => false),
persistThumbnail: vi.fn().mockResolvedValue(undefined)
}))
@@ -1483,22 +1482,9 @@ describe('useLoad3d', () => {
expect(composable).toBeDefined()
})
it('does not call captureThumbnail when asset preview is unsupported', async () => {
const { isAssetPreviewSupported } =
it('captures thumbnail and persists it when a model_file widget has a value', async () => {
const { persistThumbnail } =
await import('@/platform/assets/utils/assetPreviewUtil')
vi.mocked(isAssetPreviewSupported).mockReturnValue(false)
const { handler } = await getModelReadyHandler()
handler()
await Promise.resolve()
expect(mockLoad3d.captureThumbnail).not.toHaveBeenCalled()
})
it('captures thumbnail and persists it when asset preview is supported and a model_file widget has a value', async () => {
const { isAssetPreviewSupported, persistThumbnail } =
await import('@/platform/assets/utils/assetPreviewUtil')
vi.mocked(isAssetPreviewSupported).mockReturnValue(true)
vi.mocked(Load3dUtils.splitFilePath).mockReturnValue([
'',
'cube.glb'
@@ -1523,9 +1509,8 @@ describe('useLoad3d', () => {
})
it('skips persistence when the model widget has no value', async () => {
const { isAssetPreviewSupported, persistThumbnail } =
const { persistThumbnail } =
await import('@/platform/assets/utils/assetPreviewUtil')
vi.mocked(isAssetPreviewSupported).mockReturnValue(true)
mockNode.widgets = [
{ name: 'model_file', value: '' } as unknown as IWidget
]
@@ -1539,9 +1524,8 @@ describe('useLoad3d', () => {
})
it('swallows captureThumbnail rejections silently', async () => {
const { isAssetPreviewSupported, persistThumbnail } =
const { persistThumbnail } =
await import('@/platform/assets/utils/assetPreviewUtil')
vi.mocked(isAssetPreviewSupported).mockReturnValue(true)
vi.mocked(Load3dUtils.splitFilePath).mockReturnValue([
'',
'broken.glb'

View File

@@ -8,10 +8,7 @@ import { useChainCallback } from '@/composables/functional/useChainCallback'
import type Load3d from '@/extensions/core/load3d/Load3d'
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
import { createLoad3d } from '@/extensions/core/load3d/createLoad3d'
import {
isAssetPreviewSupported,
persistThumbnail
} from '@/platform/assets/utils/assetPreviewUtil'
import { persistThumbnail } from '@/platform/assets/utils/assetPreviewUtil'
import type {
AnimationItem,
CameraConfig,
@@ -862,7 +859,7 @@ export const useLoad3d = (nodeOrRef: MaybeRef<LGraphNode | null>) => {
isFirstModelLoad = false
},
modelReady: () => {
if (!load3d || !isAssetPreviewSupported()) return
if (!load3d) return
const node = nodeRef.value
const modelWidget = node?.widgets?.find(

View File

@@ -50,7 +50,6 @@ vi.mock('@/scripts/domWidget', () => ({
}))
vi.mock('@/platform/assets/utils/assetPreviewUtil', () => ({
isAssetPreviewSupported: vi.fn(() => false),
persistThumbnail: vi.fn()
}))

View File

@@ -17,10 +17,7 @@ type SaveMeshOutput = NodeOutputWith<{
'3d'?: ResultItem[]
}>
import type { CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import {
isAssetPreviewSupported,
persistThumbnail
} from '@/platform/assets/utils/assetPreviewUtil'
import { persistThumbnail } from '@/platform/assets/utils/assetPreviewUtil'
import { app } from '@/scripts/app'
import { ComponentWidgetImpl, addWidget } from '@/scripts/domWidget'
import { useExtensionService } from '@/services/extensionService'
@@ -60,15 +57,13 @@ function applySaveGLBOutput(node: LGraphNode, fileInfo: ResultItem): void {
silentOnNotFound: true
})
if (isAssetPreviewSupported()) {
const filename = fileInfo.filename ?? ''
void load3d
.whenLoadIdle()
.then(() => load3d.captureThumbnail(256, 256))
.then((dataUrl) => fetch(dataUrl).then((r) => r.blob()))
.then((blob) => persistThumbnail(filename, blob))
.catch(() => {})
}
const filename = fileInfo.filename ?? ''
void load3d
.whenLoadIdle()
.then(() => load3d.captureThumbnail(256, 256))
.then((dataUrl) => fetch(dataUrl).then((r) => r.blob()))
.then((blob) => persistThumbnail(filename, blob))
.catch(() => {})
})
}
@@ -194,16 +189,14 @@ useExtensionService().registerExtension({
silentOnNotFound: true
})
if (isAssetPreviewSupported()) {
const filename = fileInfo.filename ?? ''
const filename = fileInfo.filename ?? ''
void load3d
.whenLoadIdle()
.then(() => load3d.captureThumbnail(256, 256))
.then((dataUrl) => fetch(dataUrl).then((r) => r.blob()))
.then((blob) => persistThumbnail(filename, blob))
.catch(() => {})
}
void load3d
.whenLoadIdle()
.then(() => load3d.captureThumbnail(256, 256))
.then((dataUrl) => fetch(dataUrl).then((r) => r.blob()))
.then((blob) => persistThumbnail(filename, blob))
.catch(() => {})
}
})
}

View File

@@ -5,15 +5,12 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { AssetMeta } from '../schemas/mediaAssetSchema'
import Media3DTop from './Media3DTop.vue'
const {
mockUseIntersectionObserver,
mockFindServerPreviewUrl,
mockIsAssetPreviewSupported
} = vi.hoisted(() => ({
mockUseIntersectionObserver: vi.fn(),
mockFindServerPreviewUrl: vi.fn(),
mockIsAssetPreviewSupported: vi.fn(() => true)
}))
const { mockUseIntersectionObserver, mockFindServerPreviewUrl } = vi.hoisted(
() => ({
mockUseIntersectionObserver: vi.fn(),
mockFindServerPreviewUrl: vi.fn()
})
)
vi.mock('@vueuse/core', async (importOriginal) => {
const actual = await importOriginal<typeof VueUseCore>()
@@ -24,8 +21,7 @@ vi.mock('@vueuse/core', async (importOriginal) => {
})
vi.mock('../utils/assetPreviewUtil', () => ({
findServerPreviewUrl: mockFindServerPreviewUrl,
isAssetPreviewSupported: mockIsAssetPreviewSupported
findServerPreviewUrl: mockFindServerPreviewUrl
}))
function makeAsset(overrides: Partial<AssetMeta> = {}): AssetMeta {
@@ -66,7 +62,6 @@ const globalConfig = { mocks: { $t: (key: string) => key } }
describe('Media3DTop', () => {
beforeEach(() => {
vi.clearAllMocks()
mockIsAssetPreviewSupported.mockReturnValue(true)
})
it('renders the placeholder when no thumbnail has loaded', () => {
@@ -117,18 +112,6 @@ describe('Media3DTop', () => {
expect(img).toHaveAttribute('src', 'http://server/from-name.png')
})
it('skips the server query when isAssetPreviewSupported is false', async () => {
fireObserverIntersecting()
mockIsAssetPreviewSupported.mockReturnValue(false)
render(Media3DTop, {
props: { asset: makeAsset() },
global: globalConfig
})
await flush()
expect(mockFindServerPreviewUrl).not.toHaveBeenCalled()
})
it('picks up a patched preview_url after the IntersectionObserver gate has closed', async () => {
// Initial render: observer fires, server has no preview yet — hasAttempted=true
fireObserverIntersecting()

View File

@@ -23,10 +23,7 @@ import { useIntersectionObserver } from '@vueuse/core'
import { onBeforeUnmount, ref, watch } from 'vue'
import type { AssetMeta } from '../schemas/mediaAssetSchema'
import {
findServerPreviewUrl,
isAssetPreviewSupported
} from '../utils/assetPreviewUtil'
import { findServerPreviewUrl } from '../utils/assetPreviewUtil'
const { asset } = defineProps<{ asset: AssetMeta }>()
@@ -49,7 +46,7 @@ async function loadThumbnail() {
if (!asset?.src) return
if (asset.name && isAssetPreviewSupported()) {
if (asset.name) {
const serverPreviewUrl = await findServerPreviewUrl(asset.name)
if (serverPreviewUrl) {
thumbnailSrc.value = serverPreviewUrl

View File

@@ -10,21 +10,6 @@ import {
} from '@/platform/assets/services/assetService'
import { api } from '@/scripts/api'
const mockDistributionState = vi.hoisted(() => ({ isCloud: false }))
const mockSettingStoreGet = 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
}))
}))
vi.mock('@/stores/modelToNodeStore', () => {
const registeredNodeTypes: Record<string, string> = {
CheckpointLoaderSimple: 'ckpt_name',
@@ -85,59 +70,27 @@ function validAsset(overrides: Partial<AssetItem> = {}): AssetItem {
describe(assetService.shouldUseAssetBrowser, () => {
beforeEach(() => {
vi.clearAllMocks()
mockDistributionState.isCloud = false
mockSettingStoreGet.mockReturnValue(false)
})
it('returns false when not on cloud', () => {
mockDistributionState.isCloud = false
mockSettingStoreGet.mockReturnValue(true)
expect(
assetService.shouldUseAssetBrowser('CheckpointLoaderSimple', 'ckpt_name')
).toBe(false)
})
it('returns false when asset API setting is disabled', () => {
mockDistributionState.isCloud = true
mockSettingStoreGet.mockReturnValue(false)
expect(
assetService.shouldUseAssetBrowser('CheckpointLoaderSimple', 'ckpt_name')
).toBe(false)
})
it('returns false when node type is not eligible', () => {
mockDistributionState.isCloud = true
mockSettingStoreGet.mockReturnValue(true)
expect(
assetService.shouldUseAssetBrowser('UnknownNode', 'some_input')
).toBe(false)
})
it('returns true when cloud, setting enabled, and node is eligible', () => {
mockDistributionState.isCloud = true
mockSettingStoreGet.mockReturnValue(true)
it('returns true when the node is eligible', () => {
expect(
assetService.shouldUseAssetBrowser('CheckpointLoaderSimple', 'ckpt_name')
).toBe(true)
})
it('returns false when nodeType is undefined', () => {
mockDistributionState.isCloud = true
mockSettingStoreGet.mockReturnValue(true)
expect(assetService.shouldUseAssetBrowser(undefined, 'ckpt_name')).toBe(
false
)
})
it('returns false when widget name does not match registered input', () => {
mockDistributionState.isCloud = true
mockSettingStoreGet.mockReturnValue(true)
expect(
assetService.shouldUseAssetBrowser(
'CheckpointLoaderSimple',

View File

@@ -20,8 +20,6 @@ import type {
ModelFolder,
TagsOperationResult
} from '@/platform/assets/schemas/assetSchema'
import { isCloud } from '@/platform/distribution/types'
import { useSettingStore } from '@/platform/settings/settingStore'
import { api } from '@/scripts/api'
import { useModelToNodeStore } from '@/stores/modelToNodeStore'
@@ -175,7 +173,6 @@ function getLocalizedErrorMessage(errorCode: string): string {
const ASSETS_ENDPOINT = '/assets'
const ASSETS_DOWNLOAD_ENDPOINT = '/assets/download'
const ASSETS_EXPORT_ENDPOINT = '/assets/export'
const EXPERIMENTAL_WARNING = `EXPERIMENTAL: If you are seeing this please make sure "Comfy.Assets.UseAssetAPI" is set to "false" in your ComfyUI Settings.\n`
const DEFAULT_LIMIT = 500
const INPUT_ASSETS_WITH_PUBLIC_LIMIT = 500
@@ -229,9 +226,7 @@ function validateAssetResponse(data: unknown): AssetResponse {
if (result.success) return result.data
const error = fromZodError(result.error)
throw new Error(
`${EXPERIMENTAL_WARNING}Invalid asset response against zod schema:\n${error}`
)
throw new Error(`Invalid asset response against zod schema:\n${error}`)
}
function validateUploadedAssetResponse(
@@ -309,7 +304,7 @@ function createAssetService() {
: await api.fetchApi(url)
if (!res.ok) {
throw new Error(
`${EXPERIMENTAL_WARNING}Unable to load ${context}: Server returned ${res.status}. Please try again.`
`Unable to load ${context}: Server returned ${res.status}. Please try again.`
)
}
const data = await res.json()
@@ -378,17 +373,8 @@ function createAssetService() {
)
}
/**
* Checks if the asset API is enabled (cloud environment + user setting).
*/
function isAssetAPIEnabled(): boolean {
if (!isCloud) return false
return !!useSettingStore().get('Comfy.Assets.UseAssetAPI')
}
/**
* Checks if the asset browser should be used for a given node input.
* Combines the cloud environment check, user setting, and eligibility check.
*
* @param nodeType - The ComfyUI node comfyClass
* @param widgetName - The name of the widget to check
@@ -398,7 +384,7 @@ function createAssetService() {
nodeType: string | undefined,
widgetName: string
): boolean {
return isAssetAPIEnabled() && isAssetBrowserEligible(nodeType, widgetName)
return isAssetBrowserEligible(nodeType, widgetName)
}
/**
@@ -448,7 +434,7 @@ function createAssetService() {
const res = await api.fetchApi(`${ASSETS_ENDPOINT}/${id}`)
if (!res.ok) {
throw new Error(
`${EXPERIMENTAL_WARNING}Unable to load asset details for ${id}: Server returned ${res.status}. Please try again.`
`Unable to load asset details for ${id}: Server returned ${res.status}. Please try again.`
)
}
const data = await res.json()
@@ -459,9 +445,7 @@ function createAssetService() {
const error = result.error
? fromZodError(result.error)
: 'Unknown validation error'
throw new Error(
`${EXPERIMENTAL_WARNING}Invalid asset response against zod schema:\n${error}`
)
throw new Error(`Invalid asset response against zod schema:\n${error}`)
}
/**
@@ -939,7 +923,6 @@ function createAssetService() {
return {
getAssetModelFolders,
getAssetModels,
isAssetAPIEnabled,
isAssetBrowserEligible,
shouldUseAssetBrowser,
getAssetsForNodeType,

View File

@@ -3,7 +3,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
import {
findOutputAsset,
findServerPreviewUrl,
isAssetPreviewSupported,
persistThumbnail
} from '@/platform/assets/utils/assetPreviewUtil'
@@ -11,8 +10,6 @@ const mockFetchApi = vi.hoisted(() => vi.fn())
const mockApiURL = vi.hoisted(() =>
vi.fn((path: string) => `http://localhost:8188${path}`)
)
const mockGetServerFeature = vi.hoisted(() => vi.fn(() => false))
const mockIsAssetAPIEnabled = vi.hoisted(() => vi.fn(() => false))
const mockUploadAssetFromBase64 = vi.hoisted(() => vi.fn())
const mockUpdateAsset = vi.hoisted(() => vi.fn())
const mockSetAssetPreview = vi.hoisted(() => vi.fn())
@@ -21,14 +18,12 @@ vi.mock('@/scripts/api', () => ({
api: {
fetchApi: mockFetchApi,
apiURL: mockApiURL,
api_base: '',
getServerFeature: mockGetServerFeature
api_base: ''
}
}))
vi.mock('@/platform/assets/services/assetService', () => ({
assetService: {
isAssetAPIEnabled: mockIsAssetAPIEnabled,
uploadAssetFromBase64: mockUploadAssetFromBase64,
updateAsset: mockUpdateAsset
}
@@ -81,26 +76,6 @@ const localAssetWithPreview = {
preview_url: '/api/view?type=output&filename=preview.png'
}
describe('isAssetPreviewSupported', () => {
beforeEach(() => vi.clearAllMocks())
it('returns true when asset API is enabled (cloud)', () => {
mockIsAssetAPIEnabled.mockReturnValue(true)
expect(isAssetPreviewSupported()).toBe(true)
})
it('returns true when server assets feature is enabled (local)', () => {
mockGetServerFeature.mockReturnValue(true)
expect(isAssetPreviewSupported()).toBe(true)
})
it('returns false when neither is enabled', () => {
mockIsAssetAPIEnabled.mockReturnValue(false)
mockGetServerFeature.mockReturnValue(false)
expect(isAssetPreviewSupported()).toBe(false)
})
})
describe('findOutputAsset', () => {
beforeEach(() => vi.clearAllMocks())

View File

@@ -10,12 +10,6 @@ interface AssetRecord {
preview_id?: string | null
}
export function isAssetPreviewSupported(): boolean {
return (
assetService.isAssetAPIEnabled() || api.getServerFeature('assets', false)
)
}
async function fetchAssets(
params: Record<string, string>
): Promise<AssetRecord[]> {

View File

@@ -1218,14 +1218,6 @@ export const CORE_SETTINGS: SettingParams[] = [
defaultValue: true,
versionAdded: '1.30.3'
},
{
id: 'Comfy.Assets.UseAssetAPI',
name: 'Use Asset API for model library',
type: 'hidden',
tooltip: 'Use new Asset API for model browsing',
defaultValue: isCloud ? true : false,
experimental: true
},
{
id: 'Comfy.VersionCompatibility.DisableWarnings',
name: 'Disable version compatibility warnings',

View File

@@ -18,8 +18,7 @@ const flushPromises = () =>
vi.mock('@/platform/assets/services/assetService', () => ({
assetService: {
shouldUseAssetBrowser: vi.fn(() => true),
isAssetAPIEnabled: vi.fn(() => true)
shouldUseAssetBrowser: vi.fn(() => true)
}
}))

View File

@@ -17,12 +17,10 @@ const i18n = createI18n({
// 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: {
shouldUseAssetBrowser: mockShouldUseAssetBrowser,
isAssetAPIEnabled: mockIsAssetAPIEnabled
shouldUseAssetBrowser: mockShouldUseAssetBrowser
}
}))
@@ -68,7 +66,6 @@ const globalConfig = {
describe('WidgetSelect Value Binding', () => {
beforeEach(() => {
mockShouldUseAssetBrowser.mockReturnValue(false)
mockIsAssetAPIEnabled.mockReturnValue(false)
vi.clearAllMocks()
})

View File

@@ -112,7 +112,7 @@ const specDescriptor = computed<{
const isAssetMode = computed(
() =>
assetService.shouldUseAssetBrowser(props.nodeType, props.widget.name) ||
(assetService.isAssetAPIEnabled() && props.widget.type === 'asset')
props.widget.type === 'asset'
)
const assetKind = computed(() => specDescriptor.value.kind)

View File

@@ -12,14 +12,12 @@ import { AssetKindKey } from './types'
import type { FormDropdownMenuItemProps } from './types'
const mockFindServerPreviewUrl = vi.hoisted(() => vi.fn())
const mockIsAssetPreviewSupported = vi.hoisted(() => vi.fn(() => true))
const intersectionCallbacks = vi.hoisted(
() => [] as Array<(entries: Array<{ isIntersecting: boolean }>) => void>
)
vi.mock('@/platform/assets/utils/assetPreviewUtil', () => ({
findServerPreviewUrl: (name: string) => mockFindServerPreviewUrl(name),
isAssetPreviewSupported: () => mockIsAssetPreviewSupported()
findServerPreviewUrl: (name: string) => mockFindServerPreviewUrl(name)
}))
vi.mock('@vueuse/core', () => ({
@@ -83,7 +81,6 @@ describe('FormDropdownMenuItem', () => {
beforeEach(() => {
intersectionCallbacks.length = 0
mockFindServerPreviewUrl.mockReset()
mockIsAssetPreviewSupported.mockReset().mockReturnValue(true)
})
describe('Label and name', () => {
@@ -167,14 +164,6 @@ describe('FormDropdownMenuItem', () => {
expect(img.getAttribute('src')).toBe('/api/preview/resolved.png')
})
it('skips lookup when asset preview is unsupported', async () => {
mockIsAssetPreviewSupported.mockReturnValue(false)
renderItem({ name: '3d/model.glb' }, { assetKind: 'mesh' })
fireIntersection(true)
await flushPromises()
expect(mockFindServerPreviewUrl).not.toHaveBeenCalled()
})
it('only looks up once for repeated intersection events', async () => {
mockFindServerPreviewUrl.mockResolvedValue(null)
renderItem({ name: '3d/model.glb' }, { assetKind: 'mesh' })

View File

@@ -5,10 +5,7 @@ import { useI18n } from 'vue-i18n'
import { cn } from '@comfyorg/tailwind-utils'
import {
findServerPreviewUrl,
isAssetPreviewSupported
} from '@/platform/assets/utils/assetPreviewUtil'
import { findServerPreviewUrl } from '@/platform/assets/utils/assetPreviewUtil'
import { AssetKindKey } from './types'
import type { FormDropdownMenuItemProps } from './types'
@@ -40,7 +37,6 @@ function toLookupName(name: string): string {
}
async function resolveMeshPreview() {
if (!isAssetPreviewSupported()) return
const url = await findServerPreviewUrl(toLookupName(props.name))
if (url) resolvedMeshPreview.value = url
}

View File

@@ -425,7 +425,6 @@ const zSettings = z.object({
'Comfy.VueNodes.Enabled': z.boolean(),
'Comfy.AppBuilder.VueNodeSwitchDismissed': z.boolean(),
'Comfy.VueNodes.AutoScaleLayout': z.boolean(),
'Comfy.Assets.UseAssetAPI': z.boolean(),
'Comfy.Queue.QPOV2': z.boolean(),
'Comfy.Queue.ShowRunProgressBar': z.boolean(),
'Comfy-Desktop.AutoUpdate': z.boolean(),

View File

@@ -3,15 +3,12 @@ import { setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { assetService } from '@/platform/assets/services/assetService'
import { useSettingStore } from '@/platform/settings/settingStore'
import { api } from '@/scripts/api'
import { useModelStore } from '@/stores/modelStore'
// Mock the api
vi.mock('@/scripts/api', () => ({
api: {
getModels: vi.fn(),
getModelFolders: vi.fn(),
viewMetadata: vi.fn(),
apiURL: vi.fn((path: string) => `http://localhost:8188${path}`),
addEventListener: vi.fn(),
@@ -27,37 +24,8 @@ vi.mock('@/platform/assets/services/assetService', () => ({
}
}))
// Mock the settingStore
vi.mock('@/platform/settings/settingStore', () => ({
useSettingStore: vi.fn()
}))
function enableMocks(useAssetAPI = false) {
// Mock settingStore to return the useAssetAPI setting
const mockSettingStore = {
get: vi.fn().mockImplementation((key: string) => {
if (key === 'Comfy.Assets.UseAssetAPI') {
return useAssetAPI
}
return false
})
}
vi.mocked(useSettingStore, { partial: true }).mockReturnValue(
mockSettingStore
)
// Mock experimental API - returns objects with name and folders properties
vi.mocked(api.getModels).mockResolvedValue([
{ name: 'sdxl.safetensors', pathIndex: 0 },
{ name: 'sdv15.safetensors', pathIndex: 0 },
{ name: 'noinfo.safetensors', pathIndex: 0 }
])
vi.mocked(api.getModelFolders).mockResolvedValue([
{ name: 'checkpoints', folders: ['/path/to/checkpoints'] },
{ name: 'vae', folders: ['/path/to/vae'] }
])
// Mock asset API - also returns objects with name and folders properties
function enableMocks() {
// Mock asset API - returns objects with name and folders properties
vi.mocked(assetService.getAssetModelFolders).mockResolvedValue([
{ name: 'checkpoints', folders: ['/path/to/checkpoints'] },
{ name: 'vae', folders: ['/path/to/vae'] }
@@ -141,11 +109,11 @@ describe('useModelStore', () => {
enableMocks()
store = useModelStore()
await store.loadModelFolders()
expect(api.getModels).toHaveBeenCalledTimes(0)
expect(assetService.getAssetModels).toHaveBeenCalledTimes(0)
await store.getLoadedModelFolder('checkpoints')
expect(api.getModels).toHaveBeenCalledTimes(1)
expect(assetService.getAssetModels).toHaveBeenCalledTimes(1)
await store.getLoadedModelFolder('checkpoints')
expect(api.getModels).toHaveBeenCalledTimes(1)
expect(assetService.getAssetModels).toHaveBeenCalledTimes(1)
})
describe('refreshModelFolder', () => {
@@ -154,9 +122,9 @@ describe('useModelStore', () => {
store = useModelStore()
await store.loadModelFolders()
await store.getLoadedModelFolder('checkpoints')
expect(api.getModels).toHaveBeenCalledTimes(1)
expect(assetService.getAssetModels).toHaveBeenCalledTimes(1)
vi.mocked(api.getModels).mockResolvedValueOnce([
vi.mocked(assetService.getAssetModels).mockResolvedValueOnce([
{ name: 'sdxl.safetensors', pathIndex: 0 },
{ name: 'sdv15.safetensors', pathIndex: 0 },
{ name: 'noinfo.safetensors', pathIndex: 0 },
@@ -165,7 +133,7 @@ describe('useModelStore', () => {
await store.refreshModelFolder('checkpoints')
expect(api.getModels).toHaveBeenCalledTimes(2)
expect(assetService.getAssetModels).toHaveBeenCalledTimes(2)
const folder = await store.getLoadedModelFolder('checkpoints')
expect(Object.keys(folder!.models)).toHaveLength(4)
expect(folder!.models['0/new-upload.safetensors']).toBeDefined()
@@ -175,12 +143,12 @@ describe('useModelStore', () => {
enableMocks()
store = useModelStore()
await store.loadModelFolders()
expect(api.getModelFolders).toHaveBeenCalledTimes(1)
expect(assetService.getAssetModelFolders).toHaveBeenCalledTimes(1)
await store.refreshModelFolder('loras')
expect(api.getModelFolders).toHaveBeenCalledTimes(2)
expect(api.getModels).not.toHaveBeenCalled()
expect(assetService.getAssetModelFolders).toHaveBeenCalledTimes(2)
expect(assetService.getAssetModels).not.toHaveBeenCalled()
})
})
@@ -190,13 +158,15 @@ describe('useModelStore', () => {
store = useModelStore()
await store.loadModelFolders()
await store.getLoadedModelFolder('checkpoints')
expect(api.getModels).toHaveBeenCalledTimes(1)
expect(assetService.getAssetModels).toHaveBeenCalledTimes(1)
await store.refresh()
expect(api.getModelFolders).toHaveBeenCalledTimes(2)
expect(api.getModels).toHaveBeenCalledTimes(2)
expect(api.getModels).toHaveBeenLastCalledWith('checkpoints')
expect(assetService.getAssetModelFolders).toHaveBeenCalledTimes(2)
expect(assetService.getAssetModels).toHaveBeenCalledTimes(2)
expect(assetService.getAssetModels).toHaveBeenLastCalledWith(
'checkpoints'
)
})
it('does not load folders that were never opened', async () => {
@@ -206,38 +176,20 @@ describe('useModelStore', () => {
await store.refresh()
expect(api.getModelFolders).toHaveBeenCalledTimes(2)
expect(api.getModels).not.toHaveBeenCalled()
expect(assetService.getAssetModelFolders).toHaveBeenCalledTimes(2)
expect(assetService.getAssetModels).not.toHaveBeenCalled()
})
})
describe('API switching functionality', () => {
it('should use experimental API for complete workflow when UseAssetAPI setting is false', async () => {
enableMocks(false) // useAssetAPI = false
describe('asset API usage', () => {
it('uses the asset API for model folders and models', async () => {
enableMocks()
store = useModelStore()
await store.loadModelFolders()
const folderStore = await store.getLoadedModelFolder('checkpoints')
// Both APIs return objects with .name property, modelStore extracts folder.name in both cases
expect(api.getModelFolders).toHaveBeenCalledTimes(1)
expect(api.getModels).toHaveBeenCalledWith('checkpoints')
expect(assetService.getAssetModelFolders).toHaveBeenCalledTimes(0)
expect(assetService.getAssetModels).toHaveBeenCalledTimes(0)
expect(folderStore).toBeDefined()
expect(Object.keys(folderStore!.models)).toHaveLength(3)
})
it('should use asset API for complete workflow when UseAssetAPI setting is true', async () => {
enableMocks(true) // useAssetAPI = true
store = useModelStore()
await store.loadModelFolders()
const folderStore = await store.getLoadedModelFolder('checkpoints')
// Both APIs return objects with .name property, modelStore extracts folder.name in both cases
expect(assetService.getAssetModelFolders).toHaveBeenCalledTimes(1)
expect(assetService.getAssetModels).toHaveBeenCalledWith('checkpoints')
expect(api.getModelFolders).toHaveBeenCalledTimes(0)
expect(api.getModels).toHaveBeenCalledTimes(0)
expect(folderStore).toBeDefined()
expect(Object.keys(folderStore!.models)).toHaveLength(3)
})

View File

@@ -3,7 +3,6 @@ import { computed, ref } from 'vue'
import type { ModelFile } from '@/platform/assets/schemas/assetSchema'
import { assetService } from '@/platform/assets/services/assetService'
import { useSettingStore } from '@/platform/settings/settingStore'
import { api } from '@/scripts/api'
/** (Internal helper) finds a value in a metadata object from any of a list of keys. */
@@ -193,7 +192,6 @@ export class ModelFolder {
/** Model store handler, wraps individual per-folder model stores */
export const useModelStore = defineStore('models', () => {
const settingStore = useSettingStore()
const modelFolderNames = ref<string[]>([])
const modelFolderByName = ref<Record<string, ModelFolder>>({})
const modelFolders = computed<ModelFolder[]>(() =>
@@ -206,21 +204,14 @@ export const useModelStore = defineStore('models', () => {
)
function createGetModelsFunc(): (folder: string) => Promise<ModelFile[]> {
const useAssetAPI: boolean = settingStore.get('Comfy.Assets.UseAssetAPI')
return useAssetAPI
? (folder) => assetService.getAssetModels(folder)
: (folder) => api.getModels(folder)
return (folder) => assetService.getAssetModels(folder)
}
/**
* Loads the model folders from the server
*/
async function loadModelFolders() {
const useAssetAPI: boolean = settingStore.get('Comfy.Assets.UseAssetAPI')
const resData = useAssetAPI
? await assetService.getAssetModelFolders()
: await api.getModelFolders()
const resData = await assetService.getAssetModelFolders()
modelFolderNames.value = resData.map((folder) => folder.name)
modelFolderByName.value = {}
const getModelsFunc = createGetModelsFunc()

View File

@@ -73,13 +73,9 @@ export const useSidebarTabStore = defineStore('sidebarTab', () => {
versionAdded: '1.3.9',
category: 'view-controls' as const,
function: async () => {
const settingStore = useSettingStore()
const commandStore = useCommandStore()
if (
tab.id === 'model-library' &&
settingStore.get('Comfy.Assets.UseAssetAPI')
) {
if (tab.id === 'model-library') {
await commandStore.commands
.find((cmd) => cmd.id === 'Comfy.BrowseModelAssets')
?.function?.()