@@ -9,10 +9,42 @@ import {
99import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
1010import { assetService } from '@/platform/assets/services/assetService'
1111import { isCloud } from '@/platform/distribution/types'
12+ import { reconcileHistory } from '@/platform/remote/comfyui/history/reconciliation'
13+ import type { TaskItem } from '@/schemas/apiSchema'
1214import { api } from '@/scripts/api'
1315
1416import { TaskItemImpl } from './queueStore'
1517
18+ /**
19+ * Extract promptId from asset ID
20+ */
21+ const extractPromptId = ( assetId : string ) : string => {
22+ return assetId . split ( '_' ) [ 0 ]
23+ }
24+
25+ /**
26+ * Binary search to find insertion index in sorted array
27+ */
28+ const findInsertionIndex = ( array : AssetItem [ ] , item : AssetItem ) : number => {
29+ let left = 0
30+ let right = array . length
31+ const itemTime = new Date ( item . created_at ) . getTime ( )
32+
33+ while ( left < right ) {
34+ const mid = Math . floor ( ( left + right ) / 2 )
35+ const midTime = new Date ( array [ mid ] . created_at ) . getTime ( )
36+
37+ // Sort by date descending (newest first)
38+ if ( midTime < itemTime ) {
39+ right = mid
40+ } else {
41+ left = mid + 1
42+ }
43+ }
44+
45+ return left
46+ }
47+
1648/**
1749 * Fetch input files from the internal API (OSS version)
1850 */
@@ -43,10 +75,15 @@ async function fetchInputFilesFromCloud(): Promise<AssetItem[]> {
4375/**
4476 * Convert history task items to asset items
4577 */
46- function mapHistoryToAssets ( historyItems : any [ ] ) : AssetItem [ ] {
78+ function mapHistoryToAssets ( historyItems : TaskItem [ ] ) : AssetItem [ ] {
4779 const assetItems : AssetItem [ ] = [ ]
4880
4981 for ( const item of historyItems ) {
82+ // Type guard for HistoryTaskItem which has status and outputs
83+ if ( item . taskType !== 'History' ) {
84+ continue
85+ }
86+
5087 if ( ! item . outputs || ! item . status || item . status ?. status_str === 'error' ) {
5188 continue
5289 }
@@ -81,13 +118,25 @@ function mapHistoryToAssets(historyItems: any[]): AssetItem[] {
81118}
82119
83120const BATCH_SIZE = 200
121+ const MAX_HISTORY_ITEMS = 1000 // Maximum items to keep in memory
84122
85123export const useAssetsStore = defineStore ( 'assets' , ( ) => {
86124 const historyOffset = ref ( 0 )
87125 const hasMoreHistory = ref ( true )
88126 const isLoadingMore = ref ( false )
89127 const allHistoryItems = ref < AssetItem [ ] > ( [ ] )
90128
129+ // Map to track TaskItems for reconciliation
130+ const taskItemsMap = new Map < string , TaskItem > ( )
131+ // Map to track AssetItems by promptId for efficient reuse
132+ const assetItemsByPromptId = new Map < string , AssetItem > ( )
133+
134+ // Keep track of last known queue index for V1 reconciliation
135+ let lastKnownQueueIndex : number | undefined = undefined
136+
137+ // Promise-based guard to prevent race conditions
138+ let loadingPromise : Promise < void > | null = null
139+
91140 const fetchInputFiles = isCloud
92141 ? fetchInputFilesFromCloud
93142 : fetchInputFilesFromAPI
@@ -110,25 +159,96 @@ export const useAssetsStore = defineStore('assets', () => {
110159 historyOffset . value = 0
111160 hasMoreHistory . value = true
112161 allHistoryItems . value = [ ]
162+ taskItemsMap . clear ( )
163+ assetItemsByPromptId . clear ( )
164+ lastKnownQueueIndex = undefined
113165 }
114166
115167 const history = await api . getHistory ( BATCH_SIZE , {
116168 offset : historyOffset . value
117169 } )
118- const newAssets = mapHistoryToAssets ( history . History )
170+
171+ let itemsToProcess : TaskItem [ ]
119172
120173 if ( loadMore ) {
121- const existingIds = new Set ( allHistoryItems . value . map ( ( item ) => item . id ) )
122- const uniqueNewAssets = newAssets . filter (
123- ( item ) => ! existingIds . has ( item . id )
174+ // For pagination: just add new items, don't use reconcileHistory
175+ // Since we're fetching with offset, these should be different items
176+ itemsToProcess = history . History
177+
178+ // Add new items to taskItemsMap
179+ itemsToProcess . forEach ( ( item ) => {
180+ const promptId = item . prompt [ 1 ]
181+ // Only add if not already present (avoid duplicates)
182+ if ( ! taskItemsMap . has ( promptId ) ) {
183+ taskItemsMap . set ( promptId , item )
184+ }
185+ } )
186+ } else {
187+ // Initial load - use reconcileHistory for deduplication
188+ itemsToProcess = reconcileHistory (
189+ history . History ,
190+ [ ] ,
191+ MAX_HISTORY_ITEMS ,
192+ lastKnownQueueIndex
193+ )
194+
195+ // Clear and rebuild taskItemsMap
196+ taskItemsMap . clear ( )
197+ itemsToProcess . forEach ( ( item ) => {
198+ taskItemsMap . set ( item . prompt [ 1 ] , item )
199+ } )
200+ }
201+
202+ // Update last known queue index
203+ const allTaskItems = Array . from ( taskItemsMap . values ( ) )
204+ if ( allTaskItems . length > 0 ) {
205+ lastKnownQueueIndex = allTaskItems . reduce (
206+ ( max , item ) => Math . max ( max , item . prompt [ 0 ] ) ,
207+ - Infinity
124208 )
125- allHistoryItems . value = [ ...allHistoryItems . value , ...uniqueNewAssets ]
209+ }
210+
211+ // Convert new items to AssetItems
212+ const newAssets = mapHistoryToAssets ( itemsToProcess )
213+
214+ if ( loadMore ) {
215+ // For pagination: insert new assets in sorted order
216+ newAssets . forEach ( ( asset ) => {
217+ const promptId = extractPromptId ( asset . id )
218+ // Only add if not already present
219+ if ( ! assetItemsByPromptId . has ( promptId ) ) {
220+ assetItemsByPromptId . set ( promptId , asset )
221+ // Insert at correct position to maintain sort order
222+ const index = findInsertionIndex ( allHistoryItems . value , asset )
223+ allHistoryItems . value . splice ( index , 0 , asset )
224+ }
225+ } )
226+ } else {
227+ // Initial load: replace all
228+ assetItemsByPromptId . clear ( )
229+ allHistoryItems . value = [ ]
230+
231+ newAssets . forEach ( ( asset ) => {
232+ const promptId = extractPromptId ( asset . id )
233+ assetItemsByPromptId . set ( promptId , asset )
234+ allHistoryItems . value . push ( asset )
235+ } )
236+ }
237+
238+ // Check if there are more items to load
239+ hasMoreHistory . value = history . History . length === BATCH_SIZE
240+
241+ // Use fixed batch size for offset to avoid pagination gaps
242+ if ( loadMore ) {
243+ historyOffset . value += BATCH_SIZE
126244 } else {
127- allHistoryItems . value = newAssets
245+ historyOffset . value = BATCH_SIZE
128246 }
129247
130- hasMoreHistory . value = newAssets . length === BATCH_SIZE
131- historyOffset . value += newAssets . length
248+ // Ensure we don't exceed MAX_HISTORY_ITEMS
249+ if ( allHistoryItems . value . length > MAX_HISTORY_ITEMS ) {
250+ allHistoryItems . value = allHistoryItems . value . slice ( 0 , MAX_HISTORY_ITEMS )
251+ }
132252
133253 return allHistoryItems . value
134254 }
@@ -141,28 +261,46 @@ export const useAssetsStore = defineStore('assets', () => {
141261 historyLoading . value = true
142262 historyError . value = null
143263 try {
144- const assets = await fetchHistoryAssets ( false )
145- historyAssets . value = assets
264+ await fetchHistoryAssets ( false )
265+ historyAssets . value = allHistoryItems . value
146266 } catch ( err ) {
147267 console . error ( 'Error fetching history assets:' , err )
148268 historyError . value = err
269+ // Keep existing data when error occurs
270+ if ( ! historyAssets . value . length ) {
271+ historyAssets . value = [ ]
272+ }
149273 } finally {
150274 historyLoading . value = false
151275 }
152276 }
153277
154278 const loadMoreHistory = async ( ) => {
155- if ( ! hasMoreHistory . value || isLoadingMore . value ) return
279+ // Check if we should load more
280+ if ( ! hasMoreHistory . value ) return
156281
157- isLoadingMore . value = true
282+ // Prevent race conditions with promise-based guard
283+ if ( loadingPromise ) return loadingPromise
284+
285+ const doLoadMore = async ( ) => {
286+ isLoadingMore . value = true
287+ historyError . value = null // Clear error before new attempt
288+ try {
289+ await fetchHistoryAssets ( true )
290+ historyAssets . value = allHistoryItems . value
291+ } catch ( err ) {
292+ console . error ( 'Error loading more history:' , err )
293+ historyError . value = err
294+ } finally {
295+ isLoadingMore . value = false
296+ }
297+ }
298+
299+ loadingPromise = doLoadMore ( )
158300 try {
159- const updatedAssets = await fetchHistoryAssets ( true )
160- historyAssets . value = updatedAssets
161- } catch ( err ) {
162- console . error ( 'Error loading more history:' , err )
163- historyError . value = err
301+ await loadingPromise
164302 } finally {
165- isLoadingMore . value = false
303+ loadingPromise = null
166304 }
167305 }
168306
0 commit comments