From e3f19ab856bdad838ef5736724b520751751dbcd Mon Sep 17 00:00:00 2001 From: Jin Yi Date: Sat, 15 Nov 2025 14:20:00 +0900 Subject: [PATCH] feat: Add media type filtering to Media Asset Panel (#6701) --- .../sidebar/tabs/AssetsSidebarTab.vue | 3 +- src/locales/en/main.json | 7 +- .../assets/components/MediaAssetFilterBar.vue | 21 ++++++ .../components/MediaAssetFilterButton.vue | 66 +++++++++++++++++ .../components/MediaAssetFilterMenu.vue | 72 +++++++++++++++++++ .../composables/useMediaAssetFiltering.ts | 27 +++++-- .../assets/schemas/assetMetadataSchema.ts | 3 +- 7 files changed, 190 insertions(+), 9 deletions(-) create mode 100644 src/platform/assets/components/MediaAssetFilterButton.vue create mode 100644 src/platform/assets/components/MediaAssetFilterMenu.vue diff --git a/src/components/sidebar/tabs/AssetsSidebarTab.vue b/src/components/sidebar/tabs/AssetsSidebarTab.vue index 8b8695a59..5ffc7cd01 100644 --- a/src/components/sidebar/tabs/AssetsSidebarTab.vue +++ b/src/components/sidebar/tabs/AssetsSidebarTab.vue @@ -43,6 +43,7 @@ @@ -255,7 +256,7 @@ const baseAssets = computed(() => { }) // Use media asset filtering composable -const { searchQuery, sortBy, filteredAssets } = +const { searchQuery, sortBy, mediaTypeFilters, filteredAssets } = useMediaAssetFiltering(baseAssets) const displayAssets = computed(() => { diff --git a/src/locales/en/main.json b/src/locales/en/main.json index 92e1e08c1..63514a571 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -623,7 +623,11 @@ "sortNewestFirst": "Newest first", "sortOldestFirst": "Oldest first", "sortLongestFirst": "Generation time (longest first)", - "sortFastestFirst": "Generation time (fastest first)" + "sortFastestFirst": "Generation time (fastest first)", + "filterImage": "Image", + "filterVideo": "Video", + "filterAudio": "Audio", + "filter3D": "3D" }, "backToAssets": "Back to all assets", "searchAssets": "Search assets...", @@ -2009,6 +2013,7 @@ "unknown": "Unknown", "fileFormats": "File formats", "baseModels": "Base models", + "filterBy": "Filter by", "sortBy": "Sort by", "sortAZ": "A-Z", "sortZA": "Z-A", diff --git a/src/platform/assets/components/MediaAssetFilterBar.vue b/src/platform/assets/components/MediaAssetFilterBar.vue index c3b561202..9150162c3 100644 --- a/src/platform/assets/components/MediaAssetFilterBar.vue +++ b/src/platform/assets/components/MediaAssetFilterBar.vue @@ -6,6 +6,19 @@ size="lg" @update:model-value="handleSearchChange" /> + + + () const emit = defineEmits<{ 'update:searchQuery': [value: string] 'update:sortBy': [value: 'newest' | 'oldest' | 'longest' | 'fastest'] + 'update:mediaTypeFilters': [value: string[]] }>() const handleSearchChange = (value: string | undefined) => { @@ -50,4 +67,8 @@ const handleSortChange = ( ) => { emit('update:sortBy', value) } + +const handleMediaTypeFiltersChange = (value: string[]) => { + emit('update:mediaTypeFilters', value) +} diff --git a/src/platform/assets/components/MediaAssetFilterButton.vue b/src/platform/assets/components/MediaAssetFilterButton.vue new file mode 100644 index 000000000..f98e2eaf3 --- /dev/null +++ b/src/platform/assets/components/MediaAssetFilterButton.vue @@ -0,0 +1,66 @@ + + + diff --git a/src/platform/assets/components/MediaAssetFilterMenu.vue b/src/platform/assets/components/MediaAssetFilterMenu.vue new file mode 100644 index 000000000..290b13bd5 --- /dev/null +++ b/src/platform/assets/components/MediaAssetFilterMenu.vue @@ -0,0 +1,72 @@ + + + + diff --git a/src/platform/assets/composables/useMediaAssetFiltering.ts b/src/platform/assets/composables/useMediaAssetFiltering.ts index c3f71b0f6..818d166f4 100644 --- a/src/platform/assets/composables/useMediaAssetFiltering.ts +++ b/src/platform/assets/composables/useMediaAssetFiltering.ts @@ -5,6 +5,7 @@ import { computed, ref } from 'vue' import type { Ref } from 'vue' import type { AssetItem } from '@/platform/assets/schemas/assetSchema' +import { getMediaTypeFromFilename } from '@/utils/formatUtil' type SortOption = 'newest' | 'oldest' | 'longest' | 'fastest' @@ -33,6 +34,7 @@ export function useMediaAssetFiltering(assets: Ref) { const searchQuery = ref('') const debouncedSearchQuery = refDebounced(searchQuery, 50) const sortBy = ref('newest') + const mediaTypeFilters = ref([]) const fuseOptions = { keys: ['name'], @@ -51,32 +53,45 @@ export function useMediaAssetFiltering(assets: Ref) { return results.map((result) => result.item) }) + const typeFiltered = computed(() => { + // Apply media type filter + if (mediaTypeFilters.value.length === 0) { + return searchFiltered.value + } + + return searchFiltered.value.filter((asset) => { + const mediaType = getMediaTypeFromFilename(asset.name) + // Convert '3D' to '3d' for comparison + const normalizedType = mediaType.toLowerCase() + return mediaTypeFilters.value.includes(normalizedType) + }) + }) + const filteredAssets = computed(() => { // Sort by create_time (output assets) or created_at (input assets) switch (sortBy.value) { case 'oldest': // Ascending order (oldest first) - return sortByUtil(searchFiltered.value, [getAssetTime]) + return sortByUtil(typeFiltered.value, [getAssetTime]) case 'longest': // Descending order (longest execution time first) - return sortByUtil(searchFiltered.value, [ + return sortByUtil(typeFiltered.value, [ (asset) => -getAssetExecutionTime(asset) ]) case 'fastest': // Ascending order (fastest execution time first) - return sortByUtil(searchFiltered.value, [getAssetExecutionTime]) + return sortByUtil(typeFiltered.value, [getAssetExecutionTime]) case 'newest': default: // Descending order (newest first) - negate for descending - return sortByUtil(searchFiltered.value, [ - (asset) => -getAssetTime(asset) - ]) + return sortByUtil(typeFiltered.value, [(asset) => -getAssetTime(asset)]) } }) return { searchQuery, sortBy, + mediaTypeFilters, filteredAssets } } diff --git a/src/platform/assets/schemas/assetMetadataSchema.ts b/src/platform/assets/schemas/assetMetadataSchema.ts index 335e89908..1a9b02f82 100644 --- a/src/platform/assets/schemas/assetMetadataSchema.ts +++ b/src/platform/assets/schemas/assetMetadataSchema.ts @@ -1,3 +1,4 @@ +import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema' import type { ResultItemImpl } from '@/stores/queueStore' /** @@ -10,7 +11,7 @@ export interface OutputAssetMetadata extends Record { subfolder: string executionTimeInSeconds?: number format?: string - workflow?: unknown + workflow?: ComfyWorkflowJSON outputCount?: number allOutputs?: ResultItemImpl[] }