diff --git a/src/components/sidebar/tabs/AssetSidebarTemplate.vue b/src/components/sidebar/tabs/AssetSidebarTemplate.vue
index 6e139f7a6..97f75d13d 100644
--- a/src/components/sidebar/tabs/AssetSidebarTemplate.vue
+++ b/src/components/sidebar/tabs/AssetSidebarTemplate.vue
@@ -10,7 +10,7 @@
>
-
diff --git a/src/components/sidebar/tabs/AssetsSidebarTab.vue b/src/components/sidebar/tabs/AssetsSidebarTab.vue
index 5be48cbf3..23862e45d 100644
--- a/src/components/sidebar/tabs/AssetsSidebarTab.vue
+++ b/src/components/sidebar/tabs/AssetsSidebarTab.vue
@@ -39,6 +39,14 @@
{{ $t('sideToolbar.labels.imported') }}
{{ $t('sideToolbar.labels.generated') }}
+
+
+
+
@@ -66,7 +74,7 @@
:grid-style="{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))',
- padding: '0.5rem',
+ padding: '0 0.5rem',
gap: '0.5rem'
}"
@approach-end="handleApproachEnd"
@@ -157,6 +165,7 @@ import IconTextButton from '@/components/button/IconTextButton.vue'
import TextButton from '@/components/button/TextButton.vue'
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
import VirtualGrid from '@/components/common/VirtualGrid.vue'
+import SearchBox from '@/components/input/SearchBox.vue'
import ResultGallery from '@/components/sidebar/tabs/queue/ResultGallery.vue'
import Tab from '@/components/tab/Tab.vue'
import TabList from '@/components/tab/TabList.vue'
@@ -165,6 +174,7 @@ import MediaAssetCard from '@/platform/assets/components/MediaAssetCard.vue'
import { useMediaAssets } from '@/platform/assets/composables/media/useMediaAssets'
import { useAssetSelection } from '@/platform/assets/composables/useAssetSelection'
import { useMediaAssetActions } from '@/platform/assets/composables/useMediaAssetActions'
+import { useMediaAssetFiltering } from '@/platform/assets/composables/useMediaAssetFiltering'
import { getOutputAssetMetadata } from '@/platform/assets/schemas/assetMetadataSchema'
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
import { ResultItemImpl } from '@/stores/queueStore'
@@ -228,13 +238,21 @@ const currentGalleryAssetId = ref(null)
const folderAssets = ref([])
-const displayAssets = computed(() => {
+// Base assets before search filtering
+const baseAssets = computed(() => {
if (isInFolderView.value) {
return folderAssets.value
}
return mediaAssets.value
})
+// Use media asset filtering composable
+const { searchQuery, filteredAssets } = useMediaAssetFiltering(baseAssets)
+
+const displayAssets = computed(() => {
+ return filteredAssets.value
+})
+
watch(displayAssets, (newAssets) => {
if (currentGalleryAssetId.value && galleryActiveIndex.value !== -1) {
const newIndex = newAssets.findIndex(
@@ -293,6 +311,8 @@ watch(
activeTab,
() => {
clearSelection()
+ // Clear search when switching tabs
+ searchQuery.value = ''
// Reset pagination state when tab changes
void refreshAssets()
},
@@ -350,6 +370,7 @@ const exitFolderView = () => {
folderPromptId.value = null
folderExecutionTime.value = undefined
folderAssets.value = []
+ searchQuery.value = ''
clearSelection()
}
diff --git a/src/locales/en/main.json b/src/locales/en/main.json
index d9f04ca4c..3e5f67f08 100644
--- a/src/locales/en/main.json
+++ b/src/locales/en/main.json
@@ -620,6 +620,7 @@
"assets": "Assets",
"mediaAssets": "Media Assets",
"backToAssets": "Back to all assets",
+ "searchAssets": "Search assets...",
"labels": {
"queue": "Queue",
"nodes": "Nodes",
diff --git a/src/platform/assets/composables/useMediaAssetFiltering.ts b/src/platform/assets/composables/useMediaAssetFiltering.ts
new file mode 100644
index 000000000..d15abe1ed
--- /dev/null
+++ b/src/platform/assets/composables/useMediaAssetFiltering.ts
@@ -0,0 +1,37 @@
+import { refDebounced } from '@vueuse/core'
+import Fuse from 'fuse.js'
+import { computed, ref } from 'vue'
+import type { Ref } from 'vue'
+
+import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
+
+/**
+ * Media Asset Filtering composable
+ * Manages search, filter, and sort for media assets
+ */
+export function useMediaAssetFiltering(assets: Ref) {
+ const searchQuery = ref('')
+ const debouncedSearchQuery = refDebounced(searchQuery, 50)
+
+ const fuseOptions = {
+ keys: ['name'],
+ threshold: 0.4,
+ includeScore: true
+ }
+
+ const fuse = computed(() => new Fuse(assets.value, fuseOptions))
+
+ const filteredAssets = computed(() => {
+ if (!debouncedSearchQuery.value.trim()) {
+ return assets.value
+ }
+
+ const results = fuse.value.search(debouncedSearchQuery.value)
+ return results.map((result) => result.item)
+ })
+
+ return {
+ searchQuery,
+ filteredAssets
+ }
+}