feat: Add pagination support for media assets history (#6373)

## Summary
- Implement pagination for media assets history to handle large datasets
efficiently
- Add infinite scroll support with approach-end event handler  
- Support offset parameter in history API for both V1 and V2 endpoints

## Changes
- Add offset parameter support to `api.getHistory()` method
- Update history fetchers (V1/V2) to include offset in API requests
- Implement `loadMoreHistory()` in assetsStore with pagination state
management
- Add `loadMore`, `hasMore`, and `isLoadingMore` to IAssetsProvider
interface
- Add approach-end handler in AssetsSidebarTab for infinite scroll
- Set BATCH_SIZE to 200 for efficient loading

## Implementation Improvements
Simplified offset-based pagination by removing unnecessary
reconciliation logic:
- Remove `reconcileHistory`, `taskItemsMap`, `lastKnownQueueIndex`
(offset is sufficient)
- Replace `assetItemsByPromptId` Map → `loadedIds` Set (store IDs only)
- Replace `findInsertionIndex` binary search → push + sort (faster for
batch operations)
- Replace `loadingPromise` → `isLoadingMore` boolean (simpler state
management)
- Fix memory leak by cleaning up Set together with array slice

## Test Plan
- [x] TypeScript compilation passes
- [x] ESLint and Prettier formatting applied
- [x] Test infinite scroll in media assets tab
- [x] Verify network requests include correct offset parameter
- [x] Confirm no duplicate items when loading more

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Jin Yi
2025-11-14 04:15:44 +09:00
committed by GitHub
parent e639577685
commit f0f554392d
15 changed files with 735 additions and 222 deletions

View File

@@ -14,8 +14,8 @@
<slot name="header" />
</div>
</div>
<!-- h-0 to force scrollpanel to grow -->
<ScrollPanel class="h-0 grow">
<!-- min-h-0 to force scrollpanel to grow -->
<ScrollPanel class="min-h-0 grow">
<slot name="body" />
</ScrollPanel>
<div v-if="slots.footer">

View File

@@ -41,9 +41,27 @@
</TabList>
</template>
<template #body>
<div v-if="displayAssets.length" class="relative size-full">
<!-- Loading state -->
<div v-if="loading">
<ProgressSpinner class="absolute left-1/2 w-[50px] -translate-x-1/2" />
</div>
<!-- Empty state -->
<div v-else-if="!displayAssets.length">
<NoResultsPlaceholder
icon="pi pi-info-circle"
:title="
$t(
activeTab === 'input'
? 'sideToolbar.noImportedFiles'
: 'sideToolbar.noGeneratedFiles'
)
"
:message="$t('sideToolbar.noFilesFoundMessage')"
/>
</div>
<!-- Content -->
<div v-else class="relative size-full">
<VirtualGrid
v-if="displayAssets.length"
:items="mediaAssetsWithKey"
:grid-style="{
display: 'grid',
@@ -51,6 +69,7 @@
padding: '0.5rem',
gap: '0.5rem'
}"
@approach-end="handleApproachEnd"
>
<template #item="{ item }">
<MediaAssetCard
@@ -66,24 +85,6 @@
/>
</template>
</VirtualGrid>
<div v-else-if="loading">
<ProgressSpinner
class="absolute left-1/2 w-[50px] -translate-x-1/2"
/>
</div>
<div v-else>
<NoResultsPlaceholder
icon="pi pi-info-circle"
:title="
$t(
activeTab === 'input'
? 'sideToolbar.noImportedFiles'
: 'sideToolbar.noGeneratedFiles'
)
"
:message="$t('sideToolbar.noFilesFoundMessage')"
/>
</div>
</div>
</template>
<template #footer>
@@ -147,6 +148,7 @@
</template>
<script setup lang="ts">
import { useDebounceFn } from '@vueuse/core'
import ProgressSpinner from 'primevue/progressspinner'
import { useToast } from 'primevue/usetoast'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
@@ -291,6 +293,7 @@ watch(
activeTab,
() => {
clearSelection()
// Reset pagination state when tab changes
void refreshAssets()
},
{ immediate: true }
@@ -395,4 +398,15 @@ const handleDeleteSelected = async () => {
await deleteMultipleAssets(selectedAssets)
clearSelection()
}
const handleApproachEnd = useDebounceFn(async () => {
if (
activeTab.value === 'output' &&
!isInFolderView.value &&
outputAssets.hasMore.value &&
!outputAssets.isLoadingMore.value
) {
await outputAssets.loadMore()
}
}, 300)
</script>