diff --git a/src/components/widget/layout/BaseModalLayout.vue b/src/components/widget/layout/BaseModalLayout.vue index 2e4905aab..8ff9c0a7e 100644 --- a/src/components/widget/layout/BaseModalLayout.vue +++ b/src/components/widget/layout/BaseModalLayout.vue @@ -80,7 +80,9 @@ > {{ contentTitle }} -
+
diff --git a/src/components/widget/nav/NavItem.vue b/src/components/widget/nav/NavItem.vue index 30ccbb3d9..c564dfcae 100644 --- a/src/components/widget/nav/NavItem.vue +++ b/src/components/widget/nav/NavItem.vue @@ -9,7 +9,7 @@ role="button" @click="onClick" > -
+
diff --git a/src/platform/assets/components/AssetBadgeGroup.vue b/src/platform/assets/components/AssetBadgeGroup.vue index e78fe3a60..5c85713e4 100644 --- a/src/platform/assets/components/AssetBadgeGroup.vue +++ b/src/platform/assets/components/AssetBadgeGroup.vue @@ -5,7 +5,7 @@ :key="badge.label" :class=" cn( - 'px-2 py-1 rounded text-xs font-bold uppercase tracking-wider text-modal-card-tag-foreground bg-modal-card-tag-background' + 'px-2 py-1 rounded text-xs font-bold uppercase tracking-wider text-modal-card-tag-foreground bg-modal-card-tag-background break-all' ) " > diff --git a/src/platform/assets/components/AssetFilterBar.vue b/src/platform/assets/components/AssetFilterBar.vue index 0ffcba32d..560e3138e 100644 --- a/src/platform/assets/components/AssetFilterBar.vue +++ b/src/platform/assets/components/AssetFilterBar.vue @@ -48,7 +48,7 @@ @update:model-value="handleFilterChange" >
diff --git a/src/platform/assets/composables/useAssetBrowser.test.ts b/src/platform/assets/composables/useAssetBrowser.test.ts index 9c724f32b..1661e3dbb 100644 --- a/src/platform/assets/composables/useAssetBrowser.test.ts +++ b/src/platform/assets/composables/useAssetBrowser.test.ts @@ -98,6 +98,48 @@ describe('useAssetBrowser', () => { expect(result.description).toBe('loras model') }) + + it('removes category prefix from badge labels', () => { + const apiAsset = createApiAsset({ + tags: ['models', 'checkpoint/stable-diffusion-v1-5'] + }) + + const { filteredAssets } = useAssetBrowser(ref([apiAsset])) + const result = filteredAssets.value[0] + + expect(result.badges).toContainEqual({ + label: 'stable-diffusion-v1-5', + type: 'type' + }) + }) + + it('handles tags without slash for badges', () => { + const apiAsset = createApiAsset({ + tags: ['models', 'checkpoints'] + }) + + const { filteredAssets } = useAssetBrowser(ref([apiAsset])) + const result = filteredAssets.value[0] + + expect(result.badges).toContainEqual({ + label: 'checkpoints', + type: 'type' + }) + }) + + it('handles tags with multiple slashes in badges', () => { + const apiAsset = createApiAsset({ + tags: ['models', 'checkpoint/subfolder/model-name'] + }) + + const { filteredAssets } = useAssetBrowser(ref([apiAsset])) + const result = filteredAssets.value[0] + + expect(result.badges).toContainEqual({ + label: 'subfolder/model-name', + type: 'type' + }) + }) }) describe('Tag-Based Filtering', () => { @@ -533,5 +575,58 @@ describe('useAssetBrowser', () => { selectedCategory.value = 'unknown' expect(contentTitle.value).toBe('Assets') }) + + it('groups models by top-level folder name', () => { + const assets = [ + createApiAsset({ + id: 'asset-1', + tags: ['models', 'Chatterbox/subfolder1/model1'] + }), + createApiAsset({ + id: 'asset-2', + tags: ['models', 'Chatterbox/subfolder2/model2'] + }), + createApiAsset({ + id: 'asset-3', + tags: ['models', 'Chatterbox/subfolder3/model3'] + }), + createApiAsset({ + id: 'asset-4', + tags: ['models', 'OtherFolder/subfolder1/model4'] + }) + ] + + const { availableCategories, selectedCategory, categoryFilteredAssets } = + useAssetBrowser(ref(assets)) + + // Should group all Chatterbox subfolders under single category + expect(availableCategories.value).toEqual([ + { id: 'all', label: 'All Models', icon: 'icon-[lucide--folder]' }, + { + id: 'Chatterbox', + label: 'Chatterbox', + icon: 'icon-[lucide--package]' + }, + { + id: 'OtherFolder', + label: 'OtherFolder', + icon: 'icon-[lucide--package]' + } + ]) + + // When selecting Chatterbox category, should include all models from its subfolders + selectedCategory.value = 'Chatterbox' + expect(categoryFilteredAssets.value).toHaveLength(3) + expect(categoryFilteredAssets.value.map((a) => a.id)).toEqual([ + 'asset-1', + 'asset-2', + 'asset-3' + ]) + + // When selecting OtherFolder category, should include only its models + selectedCategory.value = 'OtherFolder' + expect(categoryFilteredAssets.value).toHaveLength(1) + expect(categoryFilteredAssets.value[0].id).toBe('asset-4') + }) }) }) diff --git a/src/platform/assets/composables/useAssetBrowser.ts b/src/platform/assets/composables/useAssetBrowser.ts index 799834614..2817be81c 100644 --- a/src/platform/assets/composables/useAssetBrowser.ts +++ b/src/platform/assets/composables/useAssetBrowser.ts @@ -15,7 +15,18 @@ export type OwnershipOption = 'all' | 'my-models' | 'public-models' function filterByCategory(category: string) { return (asset: AssetItem) => { - return category === 'all' || asset.tags.includes(category) + if (category === 'all') return true + + // Check if any tag matches the category (for exact matches) + if (asset.tags.includes(category)) return true + + // Check if any tag's top-level folder matches the category + return asset.tags.some((tag) => { + if (typeof tag === 'string' && tag.includes('/')) { + return tag.split('/')[0] === category + } + return false + }) } } @@ -93,7 +104,12 @@ export function useAssetBrowser( // Type badge from non-root tag if (typeTag) { - badges.push({ label: typeTag, type: 'type' }) + // Remove category prefix from badge label (e.g. "checkpoint/model" → "model") + const badgeLabel = typeTag.includes('/') + ? typeTag.substring(typeTag.indexOf('/') + 1) + : typeTag + + badges.push({ label: badgeLabel, type: 'type' }) } // Base model badge from metadata @@ -125,6 +141,7 @@ export function useAssetBrowser( .filter((asset) => asset.tags[0] === 'models') .map((asset) => asset.tags[1]) .filter((tag): tag is string => typeof tag === 'string' && tag.length > 0) + .map((tag) => tag.split('/')[0]) // Extract top-level folder name const uniqueCategories = Array.from(new Set(categories)) .sort()