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 @@
+
+
+
+
+
+
+
+
{{ $t(filter.label) }}
+
+
+
+
+
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[]
}