diff --git a/src/platform/assets/composables/useAssetBrowser.test.ts b/src/platform/assets/composables/useAssetBrowser.test.ts index 8f6eb9e5b..0a66e83b5 100644 --- a/src/platform/assets/composables/useAssetBrowser.test.ts +++ b/src/platform/assets/composables/useAssetBrowser.test.ts @@ -501,19 +501,28 @@ describe('useAssetBrowser', () => { it('filters by ownership via filter bar - my-models', async () => { const assets = [ - createApiAsset({ name: 'my-model.safetensors', is_immutable: false }), + createApiAsset({ + name: 'my-model.safetensors', + is_immutable: false, + tags: ['models', 'checkpoints'] + }), createApiAsset({ name: 'public-model.safetensors', - is_immutable: true + is_immutable: true, + tags: ['models', 'checkpoints'] }), createApiAsset({ name: 'another-my-model.safetensors', - is_immutable: false + is_immutable: false, + tags: ['models', 'checkpoints'] }) ] - const { updateFilters, filteredAssets } = useAssetBrowser(ref(assets)) + const { selectedNavItem, updateFilters, filteredAssets } = + useAssetBrowser(ref(assets)) + // Must select a specific category for ownership filter to apply + selectedNavItem.value = 'checkpoints' updateFilters({ sortBy: 'name-asc', fileFormats: [], @@ -530,19 +539,28 @@ describe('useAssetBrowser', () => { it('filters by ownership via filter bar - public-models', async () => { const assets = [ - createApiAsset({ name: 'my-model.safetensors', is_immutable: false }), + createApiAsset({ + name: 'my-model.safetensors', + is_immutable: false, + tags: ['models', 'loras'] + }), createApiAsset({ name: 'public-model.safetensors', - is_immutable: true + is_immutable: true, + tags: ['models', 'loras'] }), createApiAsset({ name: 'another-public-model.safetensors', - is_immutable: true + is_immutable: true, + tags: ['models', 'loras'] }) ] - const { updateFilters, filteredAssets } = useAssetBrowser(ref(assets)) + const { selectedNavItem, updateFilters, filteredAssets } = + useAssetBrowser(ref(assets)) + // Must select a specific category for ownership filter to apply + selectedNavItem.value = 'loras' updateFilters({ sortBy: 'name-asc', fileFormats: [], @@ -559,16 +577,23 @@ describe('useAssetBrowser', () => { it('nav imported selection overrides filter bar ownership', async () => { const assets = [ - createApiAsset({ name: 'my-model.safetensors', is_immutable: false }), + createApiAsset({ + name: 'my-model.safetensors', + is_immutable: false, + tags: ['models', 'checkpoints'] + }), createApiAsset({ name: 'public-model.safetensors', - is_immutable: true + is_immutable: true, + tags: ['models', 'checkpoints'] }) ] const { selectedNavItem, updateFilters, filteredAssets } = useAssetBrowser(ref(assets)) + // Must select a specific category for ownership filter to apply + selectedNavItem.value = 'checkpoints' // Set filter bar to public-models updateFilters({ sortBy: 'name-asc', diff --git a/src/platform/assets/composables/useAssetBrowser.ts b/src/platform/assets/composables/useAssetBrowser.ts index 3b582868d..b0a48f59d 100644 --- a/src/platform/assets/composables/useAssetBrowser.ts +++ b/src/platform/assets/composables/useAssetBrowser.ts @@ -103,6 +103,7 @@ export function useAssetBrowser( const selectedOwnership = computed(() => { if (selectedNavItem.value === 'imported') return 'my-models' + if (selectedNavItem.value === 'all') return 'all' return filters.value.ownership }) diff --git a/src/platform/assets/types/filterTypes.ts b/src/platform/assets/types/filterTypes.ts index 9bb64d378..4d5672e1a 100644 --- a/src/platform/assets/types/filterTypes.ts +++ b/src/platform/assets/types/filterTypes.ts @@ -25,6 +25,14 @@ export interface FilterOption { */ export type OwnershipOption = 'all' | 'my-models' | 'public-models' +/** + * Ownership filter option for dropdowns/selects + */ +export interface OwnershipFilterOption { + id: OwnershipOption + name: string +} + /** * Sort options for asset lists * - 'default': Preserve original order (no sorting) diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue index 0eec37d8e..6d76300f3 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue @@ -12,7 +12,8 @@ import { useToastStore } from '@/platform/updates/common/toastStore' import FormDropdown from '@/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue' import type { FilterOption, - OptionId + OptionId, + OwnershipOption } from '@/platform/assets/types/filterTypes' import { AssetKindKey } from '@/renderer/extensions/vueNodes/widgets/components/form/dropdown/types' import type { @@ -87,6 +88,17 @@ const filterOptions = computed(() => { ] }) +const ownershipSelected = ref('all') +const showOwnershipFilter = computed(() => props.isAssetMode) +const ownershipOptions = computed(() => [ + { id: 'all' as const, name: t('assetBrowser.ownershipAll') }, + { id: 'my-models' as const, name: t('assetBrowser.ownershipMyModels') }, + { + id: 'public-models' as const, + name: t('assetBrowser.ownershipPublicModels') + } +]) + const selectedSet = ref>(new Set()) /** @@ -209,16 +221,26 @@ const assetItems = computed(() => { id: asset.id, name: getAssetFilename(asset), label: getAssetDisplayName(asset), - preview_url: asset.preview_url + preview_url: asset.preview_url, + is_immutable: asset.is_immutable })) }) +/** + * Filters asset items by ownership selection. + */ +const ownershipFilteredAssetItems = computed(() => { + if (ownershipSelected.value === 'all') return assetItems.value + const isPublic = ownershipSelected.value === 'public-models' + return assetItems.value.filter((item) => item.is_immutable === isPublic) +}) + const allItems = computed(() => { if (props.isAssetMode && assetData) { if (missingValueItem.value) { - return [missingValueItem.value, ...assetItems.value] + return [missingValueItem.value, ...ownershipFilteredAssetItems.value] } - return assetItems.value + return ownershipFilteredAssetItems.value } return [ ...(missingValueItem.value ? [missingValueItem.value] : []), @@ -415,12 +437,15 @@ function getMediaUrl( v-model:selected="selectedSet" v-model:filter-selected="filterSelected" v-model:layout-mode="layoutMode" + v-model:ownership-selected="ownershipSelected" :items="dropdownItems" :placeholder="mediaPlaceholder" :multiple="false" :uploadable="uploadable" :accept="acceptTypes" :filter-options="filterOptions" + :show-ownership-filter="showOwnershipFilter" + :ownership-options="ownershipOptions" v-bind="combinedProps" class="w-full" @update:selected="updateSelectedItems" diff --git a/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue b/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue index b7db9e6fb..e2746c8b5 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue @@ -7,7 +7,9 @@ import { useToastStore } from '@/platform/updates/common/toastStore' import type { FilterOption, - OptionId + OptionId, + OwnershipFilterOption, + OwnershipOption } from '@/platform/assets/types/filterTypes' import FormDropdownInput from './FormDropdownInput.vue' @@ -29,6 +31,8 @@ interface Props { accept?: string filterOptions?: FilterOption[] sortOptions?: SortOption[] + showOwnershipFilter?: boolean + ownershipOptions?: OwnershipFilterOption[] isSelected?: ( selected: Set, item: FormDropdownItem, @@ -64,6 +68,9 @@ const layoutMode = defineModel('layoutMode', { }) const files = defineModel('files', { default: [] }) const searchQuery = defineModel('searchQuery', { default: '' }) +const ownershipSelected = defineModel('ownershipSelected', { + default: 'all' +}) const toastStore = useToastStore() const popoverRef = ref>() @@ -202,8 +209,11 @@ async function customSearcher( v-model:layout-mode="layoutMode" v-model:sort-selected="sortSelected" v-model:search-query="searchQuery" + v-model:ownership-selected="ownershipSelected" :filter-options="filterOptions" :sort-options="sortOptions" + :show-ownership-filter="showOwnershipFilter" + :ownership-options="ownershipOptions" :disabled="disabled" :searcher="customSearcher" :items="sortedItems" diff --git a/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenu.vue b/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenu.vue index 686543d28..5c834875d 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenu.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenu.vue @@ -5,7 +5,9 @@ import { cn } from '@/utils/tailwindUtil' import type { FilterOption, - OptionId + OptionId, + OwnershipFilterOption, + OwnershipOption } from '@/platform/assets/types/filterTypes' import FormDropdownMenuActions from './FormDropdownMenuActions.vue' @@ -23,6 +25,8 @@ interface Props { onCleanup: (cleanupFn: () => void) => void ) => Promise updateKey?: MaybeRefOrGetter + showOwnershipFilter?: boolean + ownershipOptions?: OwnershipFilterOption[] } defineProps() @@ -35,6 +39,7 @@ const filterSelected = defineModel('filterSelected') const layoutMode = defineModel('layoutMode') const sortSelected = defineModel('sortSelected') const searchQuery = defineModel('searchQuery') +const ownershipSelected = defineModel('ownershipSelected') // Handle item selection @@ -54,9 +59,12 @@ const searchQuery = defineModel('searchQuery') v-model:layout-mode="layoutMode" v-model:sort-selected="sortSelected" v-model:search-query="searchQuery" + v-model:ownership-selected="ownershipSelected" :sort-options="sortOptions" :searcher :update-key="updateKey" + :show-ownership-filter="showOwnershipFilter" + :ownership-options="ownershipOptions" />
diff --git a/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuActions.vue b/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuActions.vue index f452b3cb4..ec524b094 100644 --- a/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuActions.vue +++ b/src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuActions.vue @@ -3,13 +3,20 @@ import type { MaybeRefOrGetter } from 'vue' import Popover from 'primevue/popover' import { ref, useTemplateRef } from 'vue' +import { useI18n } from 'vue-i18n' -import type { OptionId } from '@/platform/assets/types/filterTypes' +import type { + OptionId, + OwnershipFilterOption, + OwnershipOption +} from '@/platform/assets/types/filterTypes' import { cn } from '@/utils/tailwindUtil' import FormSearchInput from '../FormSearchInput.vue' import type { LayoutMode, SortOption } from './types' +const { t } = useI18n() + defineProps<{ searcher?: ( query: string, @@ -17,11 +24,16 @@ defineProps<{ ) => Promise sortOptions: SortOption[] updateKey?: MaybeRefOrGetter + showOwnershipFilter?: boolean + ownershipOptions?: OwnershipFilterOption[] }>() const layoutMode = defineModel('layoutMode') const searchQuery = defineModel('searchQuery') const sortSelected = defineModel('sortSelected') +const ownershipSelected = defineModel('ownershipSelected', { + default: 'all' +}) const actionButtonStyle = cn( 'h-8 bg-zinc-500/20 rounded-lg outline outline-1 outline-offset-[-1px] outline-node-component-border transition-all duration-150' @@ -50,6 +62,25 @@ function handleSortSelected(item: SortOption) { sortSelected.value = item.id closeSortPopover() } + +const ownershipPopoverRef = useTemplateRef('ownershipPopoverRef') +const ownershipTriggerRef = useTemplateRef('ownershipTriggerRef') +const isOwnershipPopoverOpen = ref(false) + +function toggleOwnershipPopover(event: Event) { + if (!ownershipPopoverRef.value || !ownershipTriggerRef.value) return + isOwnershipPopoverOpen.value = !isOwnershipPopoverOpen.value + ownershipPopoverRef.value.toggle(event, ownershipTriggerRef.value) +} +function closeOwnershipPopover() { + isOwnershipPopoverOpen.value = false + ownershipPopoverRef.value?.hide() +} + +function handleOwnershipSelected(item: OwnershipFilterOption) { + ownershipSelected.value = item.id + closeOwnershipPopover() +}