mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-24 16:54:03 +00:00
Asset Browser Design Review + Filters (#5737)
## Summary Fixed design feedback and wired up the filter controls. ## Review Focus Design Feedback: - [4872888](48728881af) - [9a0b63e](9a0b63edce) Filters Hookup: - [07f22f8](07f22f8074) Misc (can focus less on): - claude guidance: [23e6fa9](23e6fa9723) - test helpers: [7801ed9](7801ed9e28) ## Screenshots (if applicable) <img width="1534" height="1175" alt="Screenshot 2025-09-23 at 1 03 12 PM" src="https://github.com/user-attachments/assets/d82088e4-7d72-4c6f-904e-5180774d64a5" /> <img width="1794" height="793" alt="Screenshot 2025-09-23 at 1 03 22 PM" src="https://github.com/user-attachments/assets/56eac2ba-5ecc-4a20-843f-ce683dea668c" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5737-Asset-Browser-Design-Review-Filters-2776d73d3650813e890bd16fa6a0433f) by [Unito](https://www.unito.io) --------- Co-authored-by: Alexander Brown <drjkl@comfy.org> Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { d, t } from '@/i18n'
|
||||
import type { FilterState } from '@/platform/assets/components/AssetFilterBar.vue'
|
||||
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
|
||||
import { assetFilenameSchema } from '@/platform/assets/schemas/assetSchema'
|
||||
import { assetService } from '@/platform/assets/services/assetService'
|
||||
@@ -10,6 +11,43 @@ import {
|
||||
} from '@/platform/assets/utils/assetMetadataUtils'
|
||||
import { formatSize } from '@/utils/formatUtil'
|
||||
|
||||
function filterByCategory(category: string) {
|
||||
return (asset: AssetItem) => {
|
||||
return category === 'all' || asset.tags.includes(category)
|
||||
}
|
||||
}
|
||||
|
||||
function filterByQuery(query: string) {
|
||||
return (asset: AssetItem) => {
|
||||
if (!query) return true
|
||||
const lowerQuery = query.toLowerCase()
|
||||
const description = getAssetDescription(asset)
|
||||
return (
|
||||
asset.name.toLowerCase().includes(lowerQuery) ||
|
||||
(description && description.toLowerCase().includes(lowerQuery)) ||
|
||||
asset.tags.some((tag) => tag.toLowerCase().includes(lowerQuery))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function filterByFileFormats(formats: string[]) {
|
||||
return (asset: AssetItem) => {
|
||||
if (formats.length === 0) return true
|
||||
const formatSet = new Set(formats)
|
||||
const extension = asset.name.split('.').pop()?.toLowerCase()
|
||||
return extension ? formatSet.has(extension) : false
|
||||
}
|
||||
}
|
||||
|
||||
function filterByBaseModels(models: string[]) {
|
||||
return (asset: AssetItem) => {
|
||||
if (models.length === 0) return true
|
||||
const modelSet = new Set(models)
|
||||
const baseModel = getAssetBaseModel(asset)
|
||||
return baseModel ? modelSet.has(baseModel) : false
|
||||
}
|
||||
}
|
||||
|
||||
type AssetBadge = {
|
||||
label: string
|
||||
type: 'type' | 'base' | 'size'
|
||||
@@ -35,7 +73,11 @@ export function useAssetBrowser(assets: AssetItem[] = []) {
|
||||
// State
|
||||
const searchQuery = ref('')
|
||||
const selectedCategory = ref('all')
|
||||
const sortBy = ref('name')
|
||||
const filters = ref<FilterState>({
|
||||
sortBy: 'name-asc',
|
||||
fileFormats: [],
|
||||
baseModels: []
|
||||
})
|
||||
|
||||
// Transform API asset to display asset
|
||||
function transformAssetForDisplay(asset: AssetItem): AssetDisplayItem {
|
||||
@@ -84,16 +126,18 @@ export function useAssetBrowser(assets: AssetItem[] = []) {
|
||||
}
|
||||
}
|
||||
|
||||
// Extract available categories from assets
|
||||
const availableCategories = computed(() => {
|
||||
const categorySet = new Set<string>()
|
||||
const categories = assets
|
||||
.filter((asset) => asset.tags[0] === 'models' && asset.tags[1])
|
||||
.map((asset) => asset.tags[1])
|
||||
|
||||
assets.forEach((asset) => {
|
||||
// Second tag is the category (after 'models' root tag)
|
||||
if (asset.tags.length > 1 && asset.tags[0] === 'models') {
|
||||
categorySet.add(asset.tags[1])
|
||||
}
|
||||
})
|
||||
const uniqueCategories = Array.from(new Set(categories))
|
||||
.sort()
|
||||
.map((category) => ({
|
||||
id: category,
|
||||
label: category.charAt(0).toUpperCase() + category.slice(1),
|
||||
icon: 'icon-[lucide--package]'
|
||||
}))
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -101,13 +145,7 @@ export function useAssetBrowser(assets: AssetItem[] = []) {
|
||||
label: t('assetBrowser.allModels'),
|
||||
icon: 'icon-[lucide--folder]'
|
||||
},
|
||||
...Array.from(categorySet)
|
||||
.sort()
|
||||
.map((category) => ({
|
||||
id: category,
|
||||
label: category.charAt(0).toUpperCase() + category.slice(1),
|
||||
icon: 'icon-[lucide--package]'
|
||||
}))
|
||||
...uniqueCategories
|
||||
]
|
||||
})
|
||||
|
||||
@@ -123,37 +161,25 @@ export function useAssetBrowser(assets: AssetItem[] = []) {
|
||||
return category?.label || t('assetBrowser.assets')
|
||||
})
|
||||
|
||||
// Filter functions
|
||||
const filterByCategory = (category: string) => (asset: AssetItem) => {
|
||||
if (category === 'all') return true
|
||||
return asset.tags.includes(category)
|
||||
}
|
||||
|
||||
const filterByQuery = (query: string) => (asset: AssetItem) => {
|
||||
if (!query) return true
|
||||
const lowerQuery = query.toLowerCase()
|
||||
const description = getAssetDescription(asset)
|
||||
return (
|
||||
asset.name.toLowerCase().includes(lowerQuery) ||
|
||||
(description && description.toLowerCase().includes(lowerQuery)) ||
|
||||
asset.tags.some((tag) => tag.toLowerCase().includes(lowerQuery))
|
||||
)
|
||||
}
|
||||
|
||||
// Computed filtered and transformed assets
|
||||
const filteredAssets = computed(() => {
|
||||
const filtered = assets
|
||||
.filter(filterByCategory(selectedCategory.value))
|
||||
.filter(filterByQuery(searchQuery.value))
|
||||
.filter(filterByFileFormats(filters.value.fileFormats))
|
||||
.filter(filterByBaseModels(filters.value.baseModels))
|
||||
|
||||
// Sort assets
|
||||
filtered.sort((a, b) => {
|
||||
switch (sortBy.value) {
|
||||
case 'date':
|
||||
switch (filters.value.sortBy) {
|
||||
case 'name-desc':
|
||||
return b.name.localeCompare(a.name)
|
||||
case 'recent':
|
||||
return (
|
||||
new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
|
||||
)
|
||||
case 'name':
|
||||
case 'popular':
|
||||
return a.name.localeCompare(b.name)
|
||||
case 'name-asc':
|
||||
default:
|
||||
return a.name.localeCompare(b.name)
|
||||
}
|
||||
@@ -200,18 +226,17 @@ export function useAssetBrowser(assets: AssetItem[] = []) {
|
||||
}
|
||||
}
|
||||
|
||||
function updateFilters(newFilters: FilterState) {
|
||||
filters.value = { ...newFilters }
|
||||
}
|
||||
|
||||
return {
|
||||
// State
|
||||
searchQuery,
|
||||
selectedCategory,
|
||||
sortBy,
|
||||
|
||||
// Computed
|
||||
availableCategories,
|
||||
contentTitle,
|
||||
filteredAssets,
|
||||
|
||||
// Actions
|
||||
selectAssetWithCallback
|
||||
selectAssetWithCallback,
|
||||
updateFilters
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user