mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-27 17:52:16 +00:00
feat: add ownership and base model filtering, unify asset/dropdown types (#8497)
Add ownership and base model filtering to AssetBrowserModal and FormDropdown widgets. ## Changes - **Ownership filter**: Filter by All/My Models/Public Models (uses `is_immutable` field) - **Base model filter**: Multi-select filter with Clear Filters button - **Type unification**: Replace `AssetDropdownItem` with `FormDropdownItem` - **Sorting unification**: Extract shared utilities to `assetSortUtils.ts` - **UI refactor**: Use `Button` component, Vue 3.5 prop shorthand, i18n improvements --------- Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -51,6 +51,7 @@
|
||||
<template #contentFilter>
|
||||
<AssetFilterBar
|
||||
:assets="categoryFilteredAssets"
|
||||
:show-ownership-filter
|
||||
@filter-change="updateFilters"
|
||||
@click.self="focusedAsset = null"
|
||||
/>
|
||||
@@ -125,19 +126,16 @@ const emit = defineEmits<{
|
||||
|
||||
provide(OnCloseKey, props.onClose ?? (() => {}))
|
||||
|
||||
// Compute the cache key based on nodeType or assetType
|
||||
const cacheKey = computed(() => {
|
||||
if (props.nodeType) return props.nodeType
|
||||
if (props.assetType) return `tag:${props.assetType}`
|
||||
return ''
|
||||
})
|
||||
|
||||
// Read directly from store cache - reactive to any store updates
|
||||
const fetchedAssets = computed(() => assetStore.getAssets(cacheKey.value))
|
||||
|
||||
const isStoreLoading = computed(() => assetStore.isModelLoading(cacheKey.value))
|
||||
|
||||
// Only show loading spinner when loading AND no cached data
|
||||
const isLoading = computed(
|
||||
() => isStoreLoading.value && fetchedAssets.value.length === 0
|
||||
)
|
||||
@@ -150,10 +148,8 @@ async function refreshAssets(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger background refresh on mount
|
||||
void refreshAssets()
|
||||
|
||||
// Eagerly fetch model types so they're available when ModelInfoPanel loads
|
||||
const { fetchModelTypes } = useModelTypes()
|
||||
void fetchModelTypes()
|
||||
|
||||
@@ -210,6 +206,12 @@ const shouldShowLeftPanel = computed(() => {
|
||||
return props.showLeftPanel ?? true
|
||||
})
|
||||
|
||||
const showOwnershipFilter = computed(
|
||||
() =>
|
||||
!shouldShowLeftPanel.value ||
|
||||
(selectedNavItem.value !== 'all' && selectedNavItem.value !== 'imported')
|
||||
)
|
||||
|
||||
const emptyMessage = computed(() => {
|
||||
if (!isImportedSelected.value) {
|
||||
return isUploadButtonEnabled.value
|
||||
|
||||
@@ -3,7 +3,7 @@ import { describe, expect, it, vi } from 'vitest'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
import AssetFilterBar from '@/platform/assets/components/AssetFilterBar.vue'
|
||||
import type { FilterState } from '@/platform/assets/components/AssetFilterBar.vue'
|
||||
import type { AssetFilterState } from '@/platform/assets/types/filterTypes'
|
||||
import {
|
||||
createAssetWithSpecificBaseModel,
|
||||
createAssetWithSpecificExtension,
|
||||
@@ -142,15 +142,16 @@ describe('AssetFilterBar', () => {
|
||||
expect(emitted!.length).toBeGreaterThanOrEqual(3)
|
||||
|
||||
// Check final state
|
||||
const finalState: FilterState = emitted![
|
||||
const finalState: AssetFilterState = emitted![
|
||||
emitted!.length - 1
|
||||
][0] as FilterState
|
||||
][0] as AssetFilterState
|
||||
expect(finalState.fileFormats).toEqual(['ckpt', 'safetensors'])
|
||||
expect(finalState.baseModels).toEqual(['sdxl'])
|
||||
expect(finalState.sortBy).toBe('name-desc')
|
||||
expect(finalState.ownership).toBe('all')
|
||||
})
|
||||
|
||||
it('ensures FilterState interface compliance', async () => {
|
||||
it('ensures AssetFilterState interface compliance', async () => {
|
||||
// Provide assets with options so filters are visible
|
||||
const assets = [
|
||||
createAssetWithSpecificExtension('safetensors'),
|
||||
@@ -167,7 +168,7 @@ describe('AssetFilterBar', () => {
|
||||
await nextTick()
|
||||
|
||||
const emitted = wrapper.emitted('filterChange')
|
||||
const filterState = emitted![0][0] as FilterState
|
||||
const filterState = emitted![0][0] as AssetFilterState
|
||||
|
||||
// Type and structure assertions
|
||||
expect(Array.isArray(filterState.fileFormats)).toBe(true)
|
||||
|
||||
@@ -26,6 +26,16 @@
|
||||
data-component-id="asset-filter-base-models"
|
||||
@update:model-value="handleFilterChange"
|
||||
/>
|
||||
|
||||
<SingleSelect
|
||||
v-if="showOwnershipFilter"
|
||||
v-model="ownership"
|
||||
:label="$t('assetBrowser.ownership')"
|
||||
:options="ownershipOptions"
|
||||
class="min-w-32"
|
||||
data-component-id="asset-filter-ownership"
|
||||
@update:model-value="handleFilterChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center" data-component-id="asset-filter-bar-right">
|
||||
@@ -54,44 +64,43 @@ import SingleSelect from '@/components/input/SingleSelect.vue'
|
||||
import type { SelectOption } from '@/components/input/types'
|
||||
import { useAssetFilterOptions } from '@/platform/assets/composables/useAssetFilterOptions'
|
||||
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
|
||||
import type {
|
||||
AssetFilterState,
|
||||
AssetSortOption,
|
||||
OwnershipOption
|
||||
} from '@/platform/assets/types/filterTypes'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
type SortOption = 'recent' | 'name-asc' | 'name-desc'
|
||||
|
||||
const sortOptions = computed(() => [
|
||||
{ name: t('assetBrowser.sortRecent'), value: 'recent' as const },
|
||||
{ name: t('assetBrowser.sortAZ'), value: 'name-asc' as const },
|
||||
{ name: t('assetBrowser.sortZA'), value: 'name-desc' as const }
|
||||
])
|
||||
|
||||
export interface FilterState {
|
||||
fileFormats: string[]
|
||||
baseModels: string[]
|
||||
sortBy: SortOption
|
||||
}
|
||||
|
||||
const { assets = [] } = defineProps<{
|
||||
const { assets = [], showOwnershipFilter = false } = defineProps<{
|
||||
assets?: AssetItem[]
|
||||
showOwnershipFilter?: boolean
|
||||
}>()
|
||||
|
||||
const fileFormats = ref<SelectOption[]>([])
|
||||
const baseModels = ref<SelectOption[]>([])
|
||||
const sortBy = ref<SortOption>('recent')
|
||||
const sortBy = ref<AssetSortOption>('recent')
|
||||
const ownership = ref<OwnershipOption>('all')
|
||||
|
||||
const { availableFileFormats, availableBaseModels } = useAssetFilterOptions(
|
||||
() => assets
|
||||
)
|
||||
const { availableFileFormats, availableBaseModels, ownershipOptions } =
|
||||
useAssetFilterOptions(() => assets)
|
||||
|
||||
const emit = defineEmits<{
|
||||
filterChange: [filters: FilterState]
|
||||
filterChange: [filters: AssetFilterState]
|
||||
}>()
|
||||
|
||||
function handleFilterChange() {
|
||||
emit('filterChange', {
|
||||
fileFormats: fileFormats.value.map((option: SelectOption) => option.value),
|
||||
baseModels: baseModels.value.map((option: SelectOption) => option.value),
|
||||
sortBy: sortBy.value
|
||||
sortBy: sortBy.value,
|
||||
ownership: ownership.value
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user