Skip to content

Commit 9887bc8

Browse files
committed
refactor: address code review feedback for media assets pagination
- Use shallowReactive for loadedIds Set to fix memory leak (High) - Optimize sorting with insertion-based approach instead of full sort (Medium) - Fix race condition in loadMoreHistory with atomic guard (Low) - Add consistent error handling to preserve data on failures (Medium) - Implement 300ms debounce for scroll pagination handler (Medium) - Make IAssetsProvider pagination properties required (Low) - Add offset validation in fetchHistoryV1 and V2 (Medium)
1 parent 877ad38 commit 9887bc8

File tree

5 files changed

+50
-26
lines changed

5 files changed

+50
-26
lines changed

src/components/sidebar/tabs/AssetsSidebarTab.vue

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@
148148
</template>
149149

150150
<script setup lang="ts">
151+
import { useDebounceFn } from '@vueuse/core'
151152
import ProgressSpinner from 'primevue/progressspinner'
152153
import { useToast } from 'primevue/usetoast'
153154
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
@@ -398,15 +399,14 @@ const handleDeleteSelected = async () => {
398399
clearSelection()
399400
}
400401
401-
const handleApproachEnd = async () => {
402+
const handleApproachEnd = useDebounceFn(async () => {
402403
if (
403404
activeTab.value === 'output' &&
404405
!isInFolderView.value &&
405-
outputAssets.loadMore &&
406-
outputAssets.hasMore?.value &&
407-
!outputAssets.isLoadingMore?.value
406+
outputAssets.hasMore.value &&
407+
!outputAssets.isLoadingMore.value
408408
) {
409409
await outputAssets.loadMore()
410410
}
411-
}
411+
}, 300)
412412
</script>

src/platform/assets/composables/media/IAssetsProvider.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,15 @@ export interface IAssetsProvider {
3030
/**
3131
* Load more items (for pagination)
3232
*/
33-
loadMore?: () => Promise<void>
33+
loadMore: () => Promise<void>
3434

3535
/**
3636
* Whether there are more items to load
3737
*/
38-
hasMore?: Ref<boolean>
38+
hasMore: Ref<boolean>
3939

4040
/**
4141
* Whether currently loading more items
4242
*/
43-
isLoadingMore?: Ref<boolean>
43+
isLoadingMore: Ref<boolean>
4444
}

src/platform/remote/comfyui/history/fetchers/fetchHistoryV1.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,22 @@ import type {
1515
* Fetches history from V1 API endpoint
1616
* @param api - API instance with fetchApi method
1717
* @param maxItems - Maximum number of history items to fetch
18+
* @param offset - Offset for pagination (must be non-negative integer)
1819
* @returns Promise resolving to V1 history response
20+
* @throws Error if offset is invalid (negative or non-integer)
1921
*/
2022
export async function fetchHistoryV1(
2123
fetchApi: (url: string) => Promise<Response>,
2224
maxItems: number = 200,
2325
offset?: number
2426
): Promise<HistoryV1Response> {
27+
// Validate offset parameter
28+
if (offset !== undefined && (offset < 0 || !Number.isInteger(offset))) {
29+
throw new Error(
30+
`Invalid offset parameter: ${offset}. Must be a non-negative integer.`
31+
)
32+
}
33+
2534
let url = `/history?max_items=${maxItems}`
2635
if (offset !== undefined) {
2736
url += `&offset=${offset}`

src/platform/remote/comfyui/history/fetchers/fetchHistoryV2.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,22 @@ import type { HistoryResponseV2 } from '../types/historyV2Types'
1414
* Fetches history from V2 API endpoint and adapts to V1 format
1515
* @param fetchApi - API instance with fetchApi method
1616
* @param maxItems - Maximum number of history items to fetch
17+
* @param offset - Offset for pagination (must be non-negative integer)
1718
* @returns Promise resolving to V1 history response (adapted from V2)
19+
* @throws Error if offset is invalid (negative or non-integer)
1820
*/
1921
export async function fetchHistoryV2(
2022
fetchApi: (url: string) => Promise<Response>,
2123
maxItems: number = 200,
2224
offset?: number
2325
): Promise<HistoryV1Response> {
26+
// Validate offset parameter
27+
if (offset !== undefined && (offset < 0 || !Number.isInteger(offset))) {
28+
throw new Error(
29+
`Invalid offset parameter: ${offset}. Must be a non-negative integer.`
30+
)
31+
}
32+
2433
let url = `/history_v2?max_items=${maxItems}`
2534
if (offset !== undefined) {
2635
url += `&offset=${offset}`

src/stores/assetsStore.ts

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export const useAssetsStore = defineStore('assets', () => {
100100

101101
const allHistoryItems = ref<AssetItem[]>([])
102102

103-
const loadedIds = new Set<string>()
103+
const loadedIds = shallowReactive(new Set<string>())
104104

105105
const fetchInputFiles = isCloud
106106
? fetchInputFilesFromCloud
@@ -141,22 +141,27 @@ export const useAssetsStore = defineStore('assets', () => {
141141
const newAssets = mapHistoryToAssets(history.History)
142142

143143
if (loadMore) {
144-
// Filter out duplicates using Set
145-
const uniqueAssets = newAssets.filter((asset) => {
144+
// Filter out duplicates and insert in sorted order
145+
for (const asset of newAssets) {
146146
if (loadedIds.has(asset.id)) {
147-
return false // Already loaded
147+
continue // Skip duplicates
148148
}
149149
loadedIds.add(asset.id)
150-
return true
151-
})
152150

153-
allHistoryItems.value.push(...uniqueAssets)
154-
155-
// Sort by date (newest first)
156-
allHistoryItems.value.sort(
157-
(a, b) =>
158-
new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
159-
)
151+
// Find insertion index to maintain sorted order (newest first)
152+
const assetTime = new Date(asset.created_at).getTime()
153+
const insertIndex = allHistoryItems.value.findIndex(
154+
(item) => new Date(item.created_at).getTime() < assetTime
155+
)
156+
157+
if (insertIndex === -1) {
158+
// Asset is oldest, append to end
159+
allHistoryItems.value.push(asset)
160+
} else {
161+
// Insert at the correct position
162+
allHistoryItems.value.splice(insertIndex, 0, asset)
163+
}
164+
}
160165
} else {
161166
// Initial load: replace all
162167
allHistoryItems.value = newAssets
@@ -207,11 +212,8 @@ export const useAssetsStore = defineStore('assets', () => {
207212
* Load more history items (infinite scroll)
208213
*/
209214
const loadMoreHistory = async () => {
210-
// Guard: check if more items available
211-
if (!hasMoreHistory.value) return
212-
213-
// Guard: prevent concurrent loads
214-
if (isLoadingMore.value) return
215+
// Guard: prevent concurrent loads and check if more items available
216+
if (!hasMoreHistory.value || isLoadingMore.value) return
215217

216218
isLoadingMore.value = true
217219
historyError.value = null
@@ -222,6 +224,10 @@ export const useAssetsStore = defineStore('assets', () => {
222224
} catch (err) {
223225
console.error('Error loading more history:', err)
224226
historyError.value = err
227+
// Keep existing data when error occurs (consistent with updateHistory)
228+
if (!historyAssets.value.length) {
229+
historyAssets.value = []
230+
}
225231
} finally {
226232
isLoadingMore.value = false
227233
}

0 commit comments

Comments
 (0)