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()