[feat] reactive filter functions

This commit is contained in:
Arjan Singh
2025-09-16 01:25:26 -07:00
committed by Arjan Singh
parent e8b969394f
commit 534ae71faa
2 changed files with 219 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
import { computed } from 'vue'
import type { SelectOption } from '@/components/input/types'
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
/**
* Composable that extracts available filter options from asset data
* Provides reactive computed properties for file formats and base models
*/
export function useAssetFilterOptions(assets: AssetItem[] = []) {
/**
* Extract unique file formats from asset names
* Returns sorted SelectOption array with extensions
*/
const availableFileFormats = computed<SelectOption[]>(() => {
const formats = new Set<string>()
assets.forEach((asset) => {
const extension = asset.name.split('.').pop()
if (extension && extension !== asset.name) {
// Only add if there was actually an extension (not just the filename)
formats.add(extension)
}
})
return Array.from(formats)
.sort()
.map((format) => ({
name: `.${format}`,
value: format
}))
})
/**
* Extract unique base models from asset user metadata
* Returns sorted SelectOption array with base model names
*/
const availableBaseModels = computed<SelectOption[]>(() => {
const models = new Set<string>()
assets.forEach((asset) => {
const baseModel = asset.user_metadata?.base_model
if (baseModel && typeof baseModel === 'string') {
models.add(baseModel)
}
})
return Array.from(models)
.sort()
.map((model) => ({
name: model,
value: model
}))
})
return {
availableFileFormats,
availableBaseModels
}
}

View File

@@ -0,0 +1,159 @@
import { describe, expect, it } from 'vitest'
import { useAssetFilterOptions } from '@/platform/assets/composables/useAssetFilterOptions'
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
// Test factory functions
function createTestAsset(overrides: Partial<AssetItem> = {}): AssetItem {
return {
id: 'test-uuid',
name: 'test-model.safetensors',
asset_hash: 'blake3:test123',
size: 123456,
mime_type: 'application/octet-stream',
tags: ['models', 'checkpoints'],
created_at: '2024-01-01T00:00:00Z',
updated_at: '2024-01-01T00:00:00Z',
last_access_time: '2024-01-01T00:00:00Z',
user_metadata: {
base_model: 'sd15'
},
...overrides
}
}
describe('useAssetFilterOptions', () => {
describe('File Format Extraction', () => {
it('extracts file formats from asset names', () => {
const assets = [
createTestAsset({ name: 'model1.safetensors' }),
createTestAsset({ name: 'model2.ckpt' }),
createTestAsset({ name: 'model3.pt' })
]
const { availableFileFormats } = useAssetFilterOptions(assets)
expect(availableFileFormats.value).toEqual([
{ name: '.ckpt', value: 'ckpt' },
{ name: '.pt', value: 'pt' },
{ name: '.safetensors', value: 'safetensors' }
])
})
it('handles duplicate file formats', () => {
const assets = [
createTestAsset({ name: 'model1.safetensors' }),
createTestAsset({ name: 'model2.safetensors' }),
createTestAsset({ name: 'model3.ckpt' })
]
const { availableFileFormats } = useAssetFilterOptions(assets)
expect(availableFileFormats.value).toEqual([
{ name: '.ckpt', value: 'ckpt' },
{ name: '.safetensors', value: 'safetensors' }
])
})
it('handles assets with no file extension', () => {
const assets = [
createTestAsset({ name: 'model_no_extension' }),
createTestAsset({ name: 'model.safetensors' })
]
const { availableFileFormats } = useAssetFilterOptions(assets)
expect(availableFileFormats.value).toEqual([
{ name: '.safetensors', value: 'safetensors' }
])
})
it('handles empty asset list', () => {
const { availableFileFormats } = useAssetFilterOptions([])
expect(availableFileFormats.value).toEqual([])
})
})
describe('Base Model Extraction', () => {
it('extracts base models from user metadata', () => {
const assets = [
createTestAsset({ user_metadata: { base_model: 'sd15' } }),
createTestAsset({ user_metadata: { base_model: 'sdxl' } }),
createTestAsset({ user_metadata: { base_model: 'sd35' } })
]
const { availableBaseModels } = useAssetFilterOptions(assets)
expect(availableBaseModels.value).toEqual([
{ name: 'sd15', value: 'sd15' },
{ name: 'sd35', value: 'sd35' },
{ name: 'sdxl', value: 'sdxl' }
])
})
it('handles duplicate base models', () => {
const assets = [
createTestAsset({ user_metadata: { base_model: 'sd15' } }),
createTestAsset({ user_metadata: { base_model: 'sd15' } }),
createTestAsset({ user_metadata: { base_model: 'sdxl' } })
]
const { availableBaseModels } = useAssetFilterOptions(assets)
expect(availableBaseModels.value).toEqual([
{ name: 'sd15', value: 'sd15' },
{ name: 'sdxl', value: 'sdxl' }
])
})
it('handles assets with missing user_metadata', () => {
const assets = [
createTestAsset({ user_metadata: undefined }),
createTestAsset({ user_metadata: { base_model: 'sd15' } })
]
const { availableBaseModels } = useAssetFilterOptions(assets)
expect(availableBaseModels.value).toEqual([
{ name: 'sd15', value: 'sd15' }
])
})
it('handles assets with missing base_model field', () => {
const assets = [
createTestAsset({ user_metadata: { description: 'A test model' } }),
createTestAsset({ user_metadata: { base_model: 'sdxl' } })
]
const { availableBaseModels } = useAssetFilterOptions(assets)
expect(availableBaseModels.value).toEqual([
{ name: 'sdxl', value: 'sdxl' }
])
})
it('handles empty asset list', () => {
const { availableBaseModels } = useAssetFilterOptions([])
expect(availableBaseModels.value).toEqual([])
})
})
describe('Reactivity', () => {
it('returns computed properties that can be reactive', () => {
const assets = [createTestAsset({ name: 'model.safetensors' })]
const { availableFileFormats, availableBaseModels } =
useAssetFilterOptions(assets)
// These should be computed refs
expect(availableFileFormats.value).toBeDefined()
expect(availableBaseModels.value).toBeDefined()
expect(typeof availableFileFormats.value).toBe('object')
expect(typeof availableBaseModels.value).toBe('object')
expect(Array.isArray(availableFileFormats.value)).toBe(true)
expect(Array.isArray(availableBaseModels.value)).toBe(true)
})
})
})