mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-29 18:52:19 +00:00
[feat] reactive filter functions
This commit is contained in:
60
src/platform/assets/composables/useAssetFilterOptions.ts
Normal file
60
src/platform/assets/composables/useAssetFilterOptions.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user