Skip to content

Commit a79bbcc

Browse files
committed
[feat] reactive filter functions
1 parent a04247f commit a79bbcc

File tree

2 files changed

+219
-0
lines changed

2 files changed

+219
-0
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { computed } from 'vue'
2+
3+
import type { SelectOption } from '@/components/input/types'
4+
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
5+
6+
/**
7+
* Composable that extracts available filter options from asset data
8+
* Provides reactive computed properties for file formats and base models
9+
*/
10+
export function useAssetFilterOptions(assets: AssetItem[] = []) {
11+
/**
12+
* Extract unique file formats from asset names
13+
* Returns sorted SelectOption array with extensions
14+
*/
15+
const availableFileFormats = computed<SelectOption[]>(() => {
16+
const formats = new Set<string>()
17+
18+
assets.forEach((asset) => {
19+
const extension = asset.name.split('.').pop()
20+
if (extension && extension !== asset.name) {
21+
// Only add if there was actually an extension (not just the filename)
22+
formats.add(extension)
23+
}
24+
})
25+
26+
return Array.from(formats)
27+
.sort()
28+
.map((format) => ({
29+
name: `.${format}`,
30+
value: format
31+
}))
32+
})
33+
34+
/**
35+
* Extract unique base models from asset user metadata
36+
* Returns sorted SelectOption array with base model names
37+
*/
38+
const availableBaseModels = computed<SelectOption[]>(() => {
39+
const models = new Set<string>()
40+
41+
assets.forEach((asset) => {
42+
const baseModel = asset.user_metadata?.base_model
43+
if (baseModel && typeof baseModel === 'string') {
44+
models.add(baseModel)
45+
}
46+
})
47+
48+
return Array.from(models)
49+
.sort()
50+
.map((model) => ({
51+
name: model,
52+
value: model
53+
}))
54+
})
55+
56+
return {
57+
availableFileFormats,
58+
availableBaseModels
59+
}
60+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import { describe, expect, it } from 'vitest'
2+
3+
import { useAssetFilterOptions } from '@/platform/assets/composables/useAssetFilterOptions'
4+
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
5+
6+
// Test factory functions
7+
function createTestAsset(overrides: Partial<AssetItem> = {}): AssetItem {
8+
return {
9+
id: 'test-uuid',
10+
name: 'test-model.safetensors',
11+
asset_hash: 'blake3:test123',
12+
size: 123456,
13+
mime_type: 'application/octet-stream',
14+
tags: ['models', 'checkpoints'],
15+
created_at: '2024-01-01T00:00:00Z',
16+
updated_at: '2024-01-01T00:00:00Z',
17+
last_access_time: '2024-01-01T00:00:00Z',
18+
user_metadata: {
19+
base_model: 'sd15'
20+
},
21+
...overrides
22+
}
23+
}
24+
25+
describe('useAssetFilterOptions', () => {
26+
describe('File Format Extraction', () => {
27+
it('extracts file formats from asset names', () => {
28+
const assets = [
29+
createTestAsset({ name: 'model1.safetensors' }),
30+
createTestAsset({ name: 'model2.ckpt' }),
31+
createTestAsset({ name: 'model3.pt' })
32+
]
33+
34+
const { availableFileFormats } = useAssetFilterOptions(assets)
35+
36+
expect(availableFileFormats.value).toEqual([
37+
{ name: '.ckpt', value: 'ckpt' },
38+
{ name: '.pt', value: 'pt' },
39+
{ name: '.safetensors', value: 'safetensors' }
40+
])
41+
})
42+
43+
it('handles duplicate file formats', () => {
44+
const assets = [
45+
createTestAsset({ name: 'model1.safetensors' }),
46+
createTestAsset({ name: 'model2.safetensors' }),
47+
createTestAsset({ name: 'model3.ckpt' })
48+
]
49+
50+
const { availableFileFormats } = useAssetFilterOptions(assets)
51+
52+
expect(availableFileFormats.value).toEqual([
53+
{ name: '.ckpt', value: 'ckpt' },
54+
{ name: '.safetensors', value: 'safetensors' }
55+
])
56+
})
57+
58+
it('handles assets with no file extension', () => {
59+
const assets = [
60+
createTestAsset({ name: 'model_no_extension' }),
61+
createTestAsset({ name: 'model.safetensors' })
62+
]
63+
64+
const { availableFileFormats } = useAssetFilterOptions(assets)
65+
66+
expect(availableFileFormats.value).toEqual([
67+
{ name: '.safetensors', value: 'safetensors' }
68+
])
69+
})
70+
71+
it('handles empty asset list', () => {
72+
const { availableFileFormats } = useAssetFilterOptions([])
73+
74+
expect(availableFileFormats.value).toEqual([])
75+
})
76+
})
77+
78+
describe('Base Model Extraction', () => {
79+
it('extracts base models from user metadata', () => {
80+
const assets = [
81+
createTestAsset({ user_metadata: { base_model: 'sd15' } }),
82+
createTestAsset({ user_metadata: { base_model: 'sdxl' } }),
83+
createTestAsset({ user_metadata: { base_model: 'sd35' } })
84+
]
85+
86+
const { availableBaseModels } = useAssetFilterOptions(assets)
87+
88+
expect(availableBaseModels.value).toEqual([
89+
{ name: 'sd15', value: 'sd15' },
90+
{ name: 'sd35', value: 'sd35' },
91+
{ name: 'sdxl', value: 'sdxl' }
92+
])
93+
})
94+
95+
it('handles duplicate base models', () => {
96+
const assets = [
97+
createTestAsset({ user_metadata: { base_model: 'sd15' } }),
98+
createTestAsset({ user_metadata: { base_model: 'sd15' } }),
99+
createTestAsset({ user_metadata: { base_model: 'sdxl' } })
100+
]
101+
102+
const { availableBaseModels } = useAssetFilterOptions(assets)
103+
104+
expect(availableBaseModels.value).toEqual([
105+
{ name: 'sd15', value: 'sd15' },
106+
{ name: 'sdxl', value: 'sdxl' }
107+
])
108+
})
109+
110+
it('handles assets with missing user_metadata', () => {
111+
const assets = [
112+
createTestAsset({ user_metadata: undefined }),
113+
createTestAsset({ user_metadata: { base_model: 'sd15' } })
114+
]
115+
116+
const { availableBaseModels } = useAssetFilterOptions(assets)
117+
118+
expect(availableBaseModels.value).toEqual([
119+
{ name: 'sd15', value: 'sd15' }
120+
])
121+
})
122+
123+
it('handles assets with missing base_model field', () => {
124+
const assets = [
125+
createTestAsset({ user_metadata: { description: 'A test model' } }),
126+
createTestAsset({ user_metadata: { base_model: 'sdxl' } })
127+
]
128+
129+
const { availableBaseModels } = useAssetFilterOptions(assets)
130+
131+
expect(availableBaseModels.value).toEqual([
132+
{ name: 'sdxl', value: 'sdxl' }
133+
])
134+
})
135+
136+
it('handles empty asset list', () => {
137+
const { availableBaseModels } = useAssetFilterOptions([])
138+
139+
expect(availableBaseModels.value).toEqual([])
140+
})
141+
})
142+
143+
describe('Reactivity', () => {
144+
it('returns computed properties that can be reactive', () => {
145+
const assets = [createTestAsset({ name: 'model.safetensors' })]
146+
147+
const { availableFileFormats, availableBaseModels } =
148+
useAssetFilterOptions(assets)
149+
150+
// These should be computed refs
151+
expect(availableFileFormats.value).toBeDefined()
152+
expect(availableBaseModels.value).toBeDefined()
153+
expect(typeof availableFileFormats.value).toBe('object')
154+
expect(typeof availableBaseModels.value).toBe('object')
155+
expect(Array.isArray(availableFileFormats.value)).toBe(true)
156+
expect(Array.isArray(availableBaseModels.value)).toBe(true)
157+
})
158+
})
159+
})

0 commit comments

Comments
 (0)