diff --git a/src/components/sidebar/tabs/AssetsSidebarTab.vue b/src/components/sidebar/tabs/AssetsSidebarTab.vue
index 4bede6891a..ec780cfa48 100644
--- a/src/components/sidebar/tabs/AssetsSidebarTab.vue
+++ b/src/components/sidebar/tabs/AssetsSidebarTab.vue
@@ -79,8 +79,21 @@
-
-
+
import {
+ useAsyncState,
useDebounceFn,
useElementHover,
useResizeObserver,
@@ -213,7 +227,6 @@ import {
} from '@vueuse/core'
import { storeToRefs } from 'pinia'
import Divider from 'primevue/divider'
-import ProgressSpinner from 'primevue/progressspinner'
import { useToast } from 'primevue/usetoast'
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
@@ -225,6 +238,7 @@ const Load3dViewerContent = () =>
import AssetsSidebarGridView from '@/components/sidebar/tabs/AssetsSidebarGridView.vue'
import AssetsSidebarListView from '@/components/sidebar/tabs/AssetsSidebarListView.vue'
import SidebarTabTemplate from '@/components/sidebar/tabs/SidebarTabTemplate.vue'
+import Skeleton from '@/components/ui/skeleton/Skeleton.vue'
import ResultGallery from '@/components/sidebar/tabs/queue/ResultGallery.vue'
import Tab from '@/components/tab/Tab.vue'
import TabList from '@/components/tab/TabList.vue'
@@ -237,6 +251,7 @@ import { useAssetSelection } from '@/platform/assets/composables/useAssetSelecti
import { useMediaAssetActions } from '@/platform/assets/composables/useMediaAssetActions'
import { useMediaAssetFiltering } from '@/platform/assets/composables/useMediaAssetFiltering'
import { useOutputStacks } from '@/platform/assets/composables/useOutputStacks'
+import type { OutputAssetMetadata } from '@/platform/assets/schemas/assetMetadataSchema'
import { getOutputAssetMetadata } from '@/platform/assets/schemas/assetMetadataSchema'
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
import type { MediaKind } from '@/platform/assets/schemas/mediaAssetSchema'
@@ -260,6 +275,7 @@ const settingStore = useSettingStore()
const activeTab = ref<'input' | 'output'>('output')
const folderPromptId = ref(null)
const folderExecutionTime = ref(undefined)
+const expectedFolderCount = ref(0)
const isInFolderView = computed(() => folderPromptId.value !== null)
const viewMode = useStorage<'list' | 'grid'>(
'Comfy.Assets.Sidebar.ViewMode',
@@ -376,7 +392,24 @@ const mediaAssets = computed(() => currentAssets.value.media.value)
const galleryActiveIndex = ref(-1)
const currentGalleryAssetId = ref(null)
-const folderAssets = ref([])
+const DEFAULT_SKELETON_COUNT = 6
+const skeletonCount = computed(() =>
+ expectedFolderCount.value > 0
+ ? expectedFolderCount.value
+ : DEFAULT_SKELETON_COUNT
+)
+
+const {
+ state: folderAssets,
+ isLoading: folderLoading,
+ error: folderError,
+ execute: loadFolderAssets
+} = useAsyncState(
+ (metadata: OutputAssetMetadata, options: { createdAt?: string } = {}) =>
+ resolveOutputAssetItems(metadata, options),
+ [] as AssetItem[],
+ { immediate: false, resetOnExecute: true }
+)
// Base assets before search filtering
const baseAssets = computed(() => {
@@ -414,9 +447,13 @@ const isBulkMode = computed(
() => hasSelection.value && selectedAssets.value.length > 1
)
+const isFolderLoading = computed(
+ () => isInFolderView.value && folderLoading.value
+)
+
const showLoadingState = computed(
() =>
- loading.value &&
+ (loading.value || isFolderLoading.value) &&
displayAssets.value.length === 0 &&
activeJobsCount.value === 0
)
@@ -424,6 +461,7 @@ const showLoadingState = computed(
const showEmptyState = computed(
() =>
!loading.value &&
+ !isFolderLoading.value &&
displayAssets.value.length === 0 &&
activeJobsCount.value === 0
)
@@ -599,27 +637,25 @@ const enterFolderView = async (asset: AssetItem) => {
folderPromptId.value = promptId
folderExecutionTime.value = executionTimeInSeconds
+ expectedFolderCount.value = metadata.outputCount ?? 0
- let folderItems: AssetItem[] = []
- try {
- folderItems = await resolveOutputAssetItems(metadata, {
- createdAt: asset.created_at
+ await loadFolderAssets(0, metadata, { createdAt: asset.created_at })
+
+ if (folderError.value) {
+ toast.add({
+ severity: 'error',
+ summary: t('sideToolbar.folderView.errorSummary'),
+ detail: t('sideToolbar.folderView.errorDetail'),
+ life: 5000
})
- } catch (error) {
- console.error('Failed to resolve outputs for folder view:', error)
+ exitFolderView()
}
-
- if (folderItems.length === 0) {
- console.warn('No outputs available for folder view')
- return
- }
-
- folderAssets.value = folderItems
}
const exitFolderView = () => {
folderPromptId.value = null
folderExecutionTime.value = undefined
+ expectedFolderCount.value = 0
folderAssets.value = []
searchQuery.value = ''
}
diff --git a/src/components/ui/skeleton/Skeleton.vue b/src/components/ui/skeleton/Skeleton.vue
new file mode 100644
index 0000000000..369c246835
--- /dev/null
+++ b/src/components/ui/skeleton/Skeleton.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/src/locales/en/main.json b/src/locales/en/main.json
index 5bd6498595..d5898d20a1 100644
--- a/src/locales/en/main.json
+++ b/src/locales/en/main.json
@@ -743,6 +743,10 @@
"filterText": "Text"
},
"backToAssets": "Back to all assets",
+ "folderView": {
+ "errorSummary": "Failed to load outputs",
+ "errorDetail": "Could not retrieve outputs for this job. Please try again."
+ },
"searchAssets": "Search Assets",
"labels": {
"queue": "Queue",