feat: add ownership filter to AssetFilterBar for modal without left nav

When AssetBrowserModal opens with a category filter (e.g., from a node widget), the left navigation panel is hidden. This adds an ownership dropdown to the filter bar that allows users to filter by 'All', 'My models', or 'Public models' - providing the same functionality as the 'Imported' nav item.

Amp-Thread-ID: https://ampcode.com/threads/T-019c1066-b236-705f-bc33-25781a6f9e9e
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Alexander Brown
2026-01-30 12:09:48 -08:00
parent 6c14ae6f90
commit 845a2b42ea
4 changed files with 134 additions and 12 deletions

View File

@@ -51,6 +51,7 @@
<template #contentFilter>
<AssetFilterBar
:assets="categoryFilteredAssets"
:show-ownership-filter="!shouldShowLeftPanel"
@filter-change="updateFilters"
@click.self="focusedAsset = null"
/>

View File

@@ -7,6 +7,16 @@
class="flex gap-4 items-center"
data-component-id="asset-filter-bar-left"
>
<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"
/>
<MultiSelect
v-if="availableFileFormats.length > 0"
v-model="fileFormats"
@@ -58,6 +68,7 @@ import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
const { t } = useI18n()
type SortOption = 'recent' | 'name-asc' | 'name-desc'
export type OwnershipOption = 'all' | 'my-models' | 'public-models'
const sortOptions = computed(() => [
{ name: t('assetBrowser.sortRecent'), value: 'recent' as const },
@@ -65,19 +76,31 @@ const sortOptions = computed(() => [
{ name: t('assetBrowser.sortZA'), value: 'name-desc' as const }
])
const ownershipOptions = computed(() => [
{ name: t('assetBrowser.ownershipAll'), value: 'all' as const },
{ name: t('assetBrowser.ownershipMyModels'), value: 'my-models' as const },
{
name: t('assetBrowser.ownershipPublicModels'),
value: 'public-models' as const
}
])
export interface FilterState {
fileFormats: string[]
baseModels: string[]
sortBy: SortOption
ownership: OwnershipOption
}
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 ownership = ref<OwnershipOption>('all')
const { availableFileFormats, availableBaseModels } = useAssetFilterOptions(
() => assets
@@ -91,7 +114,8 @@ 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>

View File

@@ -295,7 +295,8 @@ describe('useAssetBrowser', () => {
updateFilters({
sortBy: 'name-asc',
fileFormats: ['safetensors'],
baseModels: []
baseModels: [],
ownership: 'all'
})
await nextTick()
@@ -330,7 +331,8 @@ describe('useAssetBrowser', () => {
updateFilters({
sortBy: 'name-asc',
fileFormats: [],
baseModels: ['SDXL']
baseModels: ['SDXL'],
ownership: 'all'
})
await nextTick()
@@ -384,7 +386,8 @@ describe('useAssetBrowser', () => {
updateFilters({
sortBy: 'name-asc',
fileFormats: [],
baseModels: []
baseModels: [],
ownership: 'all'
})
await nextTick()
@@ -408,7 +411,8 @@ describe('useAssetBrowser', () => {
updateFilters({
sortBy: 'recent',
fileFormats: [],
baseModels: []
baseModels: [],
ownership: 'all'
})
await nextTick()
@@ -440,7 +444,8 @@ describe('useAssetBrowser', () => {
updateFilters({
sortBy: 'name-asc',
fileFormats: [],
baseModels: []
baseModels: [],
ownership: 'all'
})
await nextTick()
@@ -493,6 +498,96 @@ describe('useAssetBrowser', () => {
expect(filteredAssets.value).toHaveLength(3)
})
it('filters by ownership via filter bar - my-models', async () => {
const assets = [
createApiAsset({ name: 'my-model.safetensors', is_immutable: false }),
createApiAsset({
name: 'public-model.safetensors',
is_immutable: true
}),
createApiAsset({
name: 'another-my-model.safetensors',
is_immutable: false
})
]
const { updateFilters, filteredAssets } = useAssetBrowser(ref(assets))
updateFilters({
sortBy: 'name-asc',
fileFormats: [],
baseModels: [],
ownership: 'my-models'
})
await nextTick()
expect(filteredAssets.value).toHaveLength(2)
expect(filteredAssets.value.every((asset) => !asset.is_immutable)).toBe(
true
)
})
it('filters by ownership via filter bar - public-models', async () => {
const assets = [
createApiAsset({ name: 'my-model.safetensors', is_immutable: false }),
createApiAsset({
name: 'public-model.safetensors',
is_immutable: true
}),
createApiAsset({
name: 'another-public-model.safetensors',
is_immutable: true
})
]
const { updateFilters, filteredAssets } = useAssetBrowser(ref(assets))
updateFilters({
sortBy: 'name-asc',
fileFormats: [],
baseModels: [],
ownership: 'public-models'
})
await nextTick()
expect(filteredAssets.value).toHaveLength(2)
expect(filteredAssets.value.every((asset) => asset.is_immutable)).toBe(
true
)
})
it('nav imported selection overrides filter bar ownership', async () => {
const assets = [
createApiAsset({ name: 'my-model.safetensors', is_immutable: false }),
createApiAsset({
name: 'public-model.safetensors',
is_immutable: true
})
]
const { selectedNavItem, updateFilters, filteredAssets } =
useAssetBrowser(ref(assets))
// Set filter bar to public-models
updateFilters({
sortBy: 'name-asc',
fileFormats: [],
baseModels: [],
ownership: 'public-models'
})
await nextTick()
expect(filteredAssets.value).toHaveLength(1)
expect(filteredAssets.value[0].is_immutable).toBe(true)
// Nav selection to 'imported' should override filter bar
selectedNavItem.value = 'imported'
await nextTick()
expect(filteredAssets.value).toHaveLength(1)
expect(filteredAssets.value[0].is_immutable).toBe(false)
})
})
describe('Dynamic Category Extraction', () => {

View File

@@ -5,7 +5,10 @@ import type { UseFuseOptions } from '@vueuse/integrations/useFuse'
import { storeToRefs } from 'pinia'
import { d, t } from '@/i18n'
import type { FilterState } from '@/platform/assets/components/AssetFilterBar.vue'
import type {
FilterState,
OwnershipOption
} from '@/platform/assets/components/AssetFilterBar.vue'
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
import {
getAssetBaseModels,
@@ -15,8 +18,6 @@ import {
import { useAssetDownloadStore } from '@/stores/assetDownloadStore'
import type { NavGroupData, NavItemData } from '@/types/navTypes'
type OwnershipOption = 'all' | 'my-models' | 'public-models'
type NavId = 'all' | 'imported' | (string & {})
function filterByCategory(category: string) {
@@ -96,12 +97,13 @@ export function useAssetBrowser(
const filters = ref<FilterState>({
sortBy: 'recent',
fileFormats: [],
baseModels: []
baseModels: [],
ownership: 'all'
})
const selectedOwnership = computed<OwnershipOption>(() => {
if (selectedNavItem.value === 'imported') return 'my-models'
return 'all'
return filters.value.ownership
})
const selectedCategory = computed(() => {