mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-27 09:45:13 +00:00
feat: implement progressive pagination for Asset Browser model assets (#8212)
## Summary
Implements progressive pagination for model assets - returns the first
batch immediately while loading remaining batches in the background.
## Changes
### Store (`assetsStore.ts`)
- Adds `ModelPaginationState` tracking (assets Map, offset, hasMore,
loading, error)
- `updateModelsForKey()` returns first batch, then calls
`loadRemainingBatches()` to fetch the rest
- Accessor functions `getAssets(key)`, `isModelLoading(key)` replace
direct Map access
### API (`assetService.ts`)
- Adds `PaginationOptions` interface (`{ limit?, offset? }`)
### Components
- `AssetBrowserModal.vue` uses new accessor API
### Tests
- Updated mocks for new accessor pattern
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8212-feat-implement-progressive-pagination-for-Asset-Browser-model-assets-2ef6d73d36508157af04d1264780997e)
by [Unito](https://www.unito.io)
---------
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -12,9 +12,9 @@ const mockGetCategoryForNodeType = vi.fn()
|
||||
|
||||
vi.mock('@/stores/assetsStore', () => ({
|
||||
useAssetsStore: () => ({
|
||||
modelAssetsByNodeType: new Map(),
|
||||
modelLoadingByNodeType: new Map(),
|
||||
modelErrorByNodeType: new Map(),
|
||||
getAssets: () => [],
|
||||
isModelLoading: () => false,
|
||||
getError: () => undefined,
|
||||
updateModelsForNodeType: mockUpdateModelsForNodeType
|
||||
})
|
||||
}))
|
||||
|
||||
@@ -8,17 +8,17 @@ vi.mock('@/platform/distribution/types', () => ({
|
||||
isCloud: true
|
||||
}))
|
||||
|
||||
const mockModelAssetsByNodeType = new Map<string, AssetItem[]>()
|
||||
const mockModelLoadingByNodeType = new Map<string, boolean>()
|
||||
const mockModelErrorByNodeType = new Map<string, Error | null>()
|
||||
const mockAssetsByKey = new Map<string, AssetItem[]>()
|
||||
const mockLoadingByKey = new Map<string, boolean>()
|
||||
const mockErrorByKey = new Map<string, Error | undefined>()
|
||||
const mockUpdateModelsForNodeType = vi.fn()
|
||||
const mockGetCategoryForNodeType = vi.fn()
|
||||
|
||||
vi.mock('@/stores/assetsStore', () => ({
|
||||
useAssetsStore: () => ({
|
||||
modelAssetsByNodeType: mockModelAssetsByNodeType,
|
||||
modelLoadingByNodeType: mockModelLoadingByNodeType,
|
||||
modelErrorByNodeType: mockModelErrorByNodeType,
|
||||
getAssets: (key: string) => mockAssetsByKey.get(key) ?? [],
|
||||
isModelLoading: (key: string) => mockLoadingByKey.get(key) ?? false,
|
||||
getError: (key: string) => mockErrorByKey.get(key),
|
||||
updateModelsForNodeType: mockUpdateModelsForNodeType
|
||||
})
|
||||
}))
|
||||
@@ -32,9 +32,9 @@ vi.mock('@/stores/modelToNodeStore', () => ({
|
||||
describe('useAssetWidgetData (cloud mode, isCloud=true)', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockModelAssetsByNodeType.clear()
|
||||
mockModelLoadingByNodeType.clear()
|
||||
mockModelErrorByNodeType.clear()
|
||||
mockAssetsByKey.clear()
|
||||
mockLoadingByKey.clear()
|
||||
mockErrorByKey.clear()
|
||||
mockGetCategoryForNodeType.mockReturnValue(undefined)
|
||||
|
||||
mockUpdateModelsForNodeType.mockImplementation(
|
||||
@@ -76,8 +76,8 @@ describe('useAssetWidgetData (cloud mode, isCloud=true)', () => {
|
||||
|
||||
mockUpdateModelsForNodeType.mockImplementation(
|
||||
async (_nodeType: string): Promise<AssetItem[]> => {
|
||||
mockModelAssetsByNodeType.set(_nodeType, mockAssets)
|
||||
mockModelLoadingByNodeType.set(_nodeType, false)
|
||||
mockAssetsByKey.set(_nodeType, mockAssets)
|
||||
mockLoadingByKey.set(_nodeType, false)
|
||||
return mockAssets
|
||||
}
|
||||
)
|
||||
@@ -108,9 +108,9 @@ describe('useAssetWidgetData (cloud mode, isCloud=true)', () => {
|
||||
|
||||
mockUpdateModelsForNodeType.mockImplementation(
|
||||
async (_nodeType: string): Promise<AssetItem[]> => {
|
||||
mockModelErrorByNodeType.set(_nodeType, mockError)
|
||||
mockModelAssetsByNodeType.set(_nodeType, [])
|
||||
mockModelLoadingByNodeType.set(_nodeType, false)
|
||||
mockErrorByKey.set(_nodeType, mockError)
|
||||
mockAssetsByKey.set(_nodeType, [])
|
||||
mockLoadingByKey.set(_nodeType, false)
|
||||
return []
|
||||
}
|
||||
)
|
||||
@@ -130,8 +130,8 @@ describe('useAssetWidgetData (cloud mode, isCloud=true)', () => {
|
||||
|
||||
mockUpdateModelsForNodeType.mockImplementation(
|
||||
async (_nodeType: string): Promise<AssetItem[]> => {
|
||||
mockModelAssetsByNodeType.set(_nodeType, [])
|
||||
mockModelLoadingByNodeType.set(_nodeType, false)
|
||||
mockAssetsByKey.set(_nodeType, [])
|
||||
mockLoadingByKey.set(_nodeType, false)
|
||||
return []
|
||||
}
|
||||
)
|
||||
@@ -154,8 +154,8 @@ describe('useAssetWidgetData (cloud mode, isCloud=true)', () => {
|
||||
mockGetCategoryForNodeType.mockReturnValue('checkpoints')
|
||||
mockUpdateModelsForNodeType.mockImplementation(
|
||||
async (_nodeType: string): Promise<AssetItem[]> => {
|
||||
mockModelAssetsByNodeType.set(_nodeType, mockAssets)
|
||||
mockModelLoadingByNodeType.set(_nodeType, false)
|
||||
mockAssetsByKey.set(_nodeType, mockAssets)
|
||||
mockLoadingByKey.set(_nodeType, false)
|
||||
return mockAssets
|
||||
}
|
||||
)
|
||||
@@ -182,8 +182,8 @@ describe('useAssetWidgetData (cloud mode, isCloud=true)', () => {
|
||||
mockGetCategoryForNodeType.mockReturnValue('loras')
|
||||
mockUpdateModelsForNodeType.mockImplementation(
|
||||
async (_nodeType: string): Promise<AssetItem[]> => {
|
||||
mockModelAssetsByNodeType.set(_nodeType, mockAssets)
|
||||
mockModelLoadingByNodeType.set(_nodeType, false)
|
||||
mockAssetsByKey.set(_nodeType, mockAssets)
|
||||
mockLoadingByKey.set(_nodeType, false)
|
||||
return mockAssets
|
||||
}
|
||||
)
|
||||
@@ -209,8 +209,8 @@ describe('useAssetWidgetData (cloud mode, isCloud=true)', () => {
|
||||
mockGetCategoryForNodeType.mockReturnValue('checkpoints')
|
||||
mockUpdateModelsForNodeType.mockImplementation(
|
||||
async (_nodeType: string): Promise<AssetItem[]> => {
|
||||
mockModelAssetsByNodeType.set(_nodeType, mockAssets)
|
||||
mockModelLoadingByNodeType.set(_nodeType, false)
|
||||
mockAssetsByKey.set(_nodeType, mockAssets)
|
||||
mockLoadingByKey.set(_nodeType, false)
|
||||
return mockAssets
|
||||
}
|
||||
)
|
||||
|
||||
@@ -34,23 +34,17 @@ export function useAssetWidgetData(
|
||||
|
||||
const assets = computed<AssetItem[]>(() => {
|
||||
const resolvedType = toValue(nodeType)
|
||||
return resolvedType
|
||||
? (assetsStore.modelAssetsByNodeType.get(resolvedType) ?? [])
|
||||
: []
|
||||
return resolvedType ? (assetsStore.getAssets(resolvedType) ?? []) : []
|
||||
})
|
||||
|
||||
const isLoading = computed(() => {
|
||||
const resolvedType = toValue(nodeType)
|
||||
return resolvedType
|
||||
? (assetsStore.modelLoadingByNodeType.get(resolvedType) ?? false)
|
||||
: false
|
||||
return resolvedType ? assetsStore.isModelLoading(resolvedType) : false
|
||||
})
|
||||
|
||||
const error = computed<Error | null>(() => {
|
||||
const resolvedType = toValue(nodeType)
|
||||
return resolvedType
|
||||
? (assetsStore.modelErrorByNodeType.get(resolvedType) ?? null)
|
||||
: null
|
||||
return resolvedType ? (assetsStore.getError(resolvedType) ?? null) : null
|
||||
})
|
||||
|
||||
const dropdownItems = computed<DropdownItem[]>(() => {
|
||||
@@ -71,7 +65,8 @@ export function useAssetWidgetData(
|
||||
return
|
||||
}
|
||||
|
||||
const hasData = assetsStore.modelAssetsByNodeType.has(currentNodeType)
|
||||
const existingAssets = assetsStore.getAssets(currentNodeType) ?? []
|
||||
const hasData = existingAssets.length > 0
|
||||
|
||||
if (!hasData) {
|
||||
await assetsStore.updateModelsForNodeType(currentNodeType)
|
||||
|
||||
Reference in New Issue
Block a user