Persist template filters (#6657)

This pull request adds persistent filter and sort settings to the
template library, allowing users' filter choices and sort preferences to
be saved and restored across sessions. The main changes include
integrating the settings store with the template filtering composable,
updating the schema and core settings, and ensuring filter changes are
saved efficiently.

**Template Library Filter Persistence:**

*
[`src/composables/useTemplateFiltering.ts`](diffhunk://#diff-a1ec9d65962033526942cbcabeac8538ef3cd723e2e9e889cf668ccf6270d167L1-R32):
The filter state (`selectedModels`, `selectedUseCases`,
`selectedRunsOn`, and `sortBy`) is now initialized from the settings
store and changes are persisted back using debounced watchers. This
ensures user preferences are saved and restored.
[[1]](diffhunk://#diff-a1ec9d65962033526942cbcabeac8538ef3cd723e2e9e889cf668ccf6270d167L1-R32)
[[2]](diffhunk://#diff-a1ec9d65962033526942cbcabeac8538ef3cd723e2e9e889cf668ccf6270d167R259-R291)
*
[`src/platform/settings/constants/coreSettings.ts`](diffhunk://#diff-9fb7e2cdcdc60a92bdb54698fb49909bd2a84a50ffb69e2b60529a948eeb9756R1056-R1083):
Added new hidden settings for template filter selections and sort
preference, with sensible defaults.
*
[`src/schemas/apiSchema.ts`](diffhunk://#diff-b769532e74f826ca909951c0c34331b9246efb3f6901ff95a856ecf01ad826beR504-R514):
Updated the settings schema to include the new template filter and sort
settings, ensuring type safety and validation.

**Default Behavior Adjustment:**

*
[`src/composables/useTemplateFiltering.ts`](diffhunk://#diff-a1ec9d65962033526942cbcabeac8538ef3cd723e2e9e889cf668ccf6270d167L200-R209):
Changed the default sort order when clearing filters to `'newest'` to
match the new default in settings.


https://github.com/user-attachments/assets/259e87e6-20b3-4c91-b1bf-4b7d70649878

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6657-Persist-template-filters-2a86d73d3650818ca46fda23a6528391)
by [Unito](https://www.unito.io)
This commit is contained in:
Johnpaul Chiwetelu
2025-11-12 21:25:09 +01:00
committed by GitHub
parent 2c3c97d4b5
commit 1e71eae177
4 changed files with 122 additions and 8 deletions

View File

@@ -1,8 +1,9 @@
import { refDebounced } from '@vueuse/core'
import { refDebounced, watchDebounced } from '@vueuse/core'
import Fuse from 'fuse.js'
import { computed, ref, watch } from 'vue'
import type { Ref } from 'vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useTelemetry } from '@/platform/telemetry'
import type { TemplateInfo } from '@/platform/workflow/templates/types/template'
import { debounce } from 'es-toolkit/compat'
@@ -10,17 +11,25 @@ import { debounce } from 'es-toolkit/compat'
export function useTemplateFiltering(
templates: Ref<TemplateInfo[]> | TemplateInfo[]
) {
const settingStore = useSettingStore()
const searchQuery = ref('')
const selectedModels = ref<string[]>([])
const selectedUseCases = ref<string[]>([])
const selectedRunsOn = ref<string[]>([])
const selectedModels = ref<string[]>(
settingStore.get('Comfy.Templates.SelectedModels')
)
const selectedUseCases = ref<string[]>(
settingStore.get('Comfy.Templates.SelectedUseCases')
)
const selectedRunsOn = ref<string[]>(
settingStore.get('Comfy.Templates.SelectedRunsOn')
)
const sortBy = ref<
| 'default'
| 'alphabetical'
| 'newest'
| 'vram-low-to-high'
| 'model-size-low-to-high'
>('newest')
>(settingStore.get('Comfy.Templates.SortBy'))
const templatesArray = computed(() => {
const templateData = 'value' in templates ? templates.value : templates
@@ -197,7 +206,7 @@ export function useTemplateFiltering(
selectedModels.value = []
selectedUseCases.value = []
selectedRunsOn.value = []
sortBy.value = 'default'
sortBy.value = 'newest'
}
const removeModelFilter = (model: string) => {
@@ -247,6 +256,39 @@ export function useTemplateFiltering(
{ deep: true }
)
// Persist filter changes to settings (debounced to avoid excessive saves)
watchDebounced(
selectedModels,
(newValue) => {
void settingStore.set('Comfy.Templates.SelectedModels', newValue)
},
{ debounce: 500, deep: true }
)
watchDebounced(
selectedUseCases,
(newValue) => {
void settingStore.set('Comfy.Templates.SelectedUseCases', newValue)
},
{ debounce: 500, deep: true }
)
watchDebounced(
selectedRunsOn,
(newValue) => {
void settingStore.set('Comfy.Templates.SelectedRunsOn', newValue)
},
{ debounce: 500, deep: true }
)
watchDebounced(
sortBy,
(newValue) => {
void settingStore.set('Comfy.Templates.SortBy', newValue)
},
{ debounce: 500 }
)
return {
// State
searchQuery,

View File

@@ -1053,6 +1053,34 @@ export const CORE_SETTINGS: SettingParams[] = [
defaultValue: 0
},
/**
* Template Library Filter Settings
*/
{
id: 'Comfy.Templates.SelectedModels',
name: 'Template library - Selected model filters',
type: 'hidden',
defaultValue: []
},
{
id: 'Comfy.Templates.SelectedUseCases',
name: 'Template library - Selected use case filters',
type: 'hidden',
defaultValue: []
},
{
id: 'Comfy.Templates.SelectedRunsOn',
name: 'Template library - Selected runs on filters',
type: 'hidden',
defaultValue: []
},
{
id: 'Comfy.Templates.SortBy',
name: 'Template library - Sort preference',
type: 'hidden',
defaultValue: 'newest'
},
/**
* Vue Node System Settings
*/

View File

@@ -501,6 +501,17 @@ const zSettings = z.object({
"what's new seen"
]),
'Comfy.Release.Timestamp': z.number(),
/** Template library filter settings */
'Comfy.Templates.SelectedModels': z.array(z.string()),
'Comfy.Templates.SelectedUseCases': z.array(z.string()),
'Comfy.Templates.SelectedRunsOn': z.array(z.string()),
'Comfy.Templates.SortBy': z.enum([
'default',
'alphabetical',
'newest',
'vram-low-to-high',
'model-size-low-to-high'
]),
/** Settings used for testing */
'test.setting': z.any(),
'main.sub.setting.name': z.any(),

View File

@@ -1,10 +1,43 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick, ref } from 'vue'
import { useTemplateFiltering } from '@/composables/useTemplateFiltering'
import type { TemplateInfo } from '@/platform/workflow/templates/types/template'
const defaultSettingStore = {
get: vi.fn((key: string) => {
switch (key) {
case 'Comfy.Templates.SelectedModels':
case 'Comfy.Templates.SelectedUseCases':
case 'Comfy.Templates.SelectedRunsOn':
return []
case 'Comfy.Templates.SortBy':
return 'newest'
default:
return undefined
}
}),
set: vi.fn().mockResolvedValue(undefined)
}
vi.mock('@/platform/settings/settingStore', () => ({
useSettingStore: vi.fn(() => defaultSettingStore)
}))
vi.mock('@/platform/telemetry', () => ({
useTelemetry: vi.fn(() => ({
trackTemplateFilterChanged: vi.fn()
}))
}))
const { useTemplateFiltering } = await import(
'@/composables/useTemplateFiltering'
)
describe('useTemplateFiltering', () => {
beforeEach(() => {
vi.clearAllMocks()
})
afterEach(() => {
vi.useRealTimers()
})