From 1e71eae1774e7eacaeaf6b85d32b810da25dd174 Mon Sep 17 00:00:00 2001 From: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com> Date: Wed, 12 Nov 2025 21:25:09 +0100 Subject: [PATCH] Persist template filters (#6657) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- src/composables/useTemplateFiltering.ts | 54 ++++++++++++++++--- .../settings/constants/coreSettings.ts | 28 ++++++++++ src/schemas/apiSchema.ts | 11 ++++ .../composables/useTemplateFiltering.test.ts | 37 ++++++++++++- 4 files changed, 122 insertions(+), 8 deletions(-) diff --git a/src/composables/useTemplateFiltering.ts b/src/composables/useTemplateFiltering.ts index 4d399e757..c181e2f91 100644 --- a/src/composables/useTemplateFiltering.ts +++ b/src/composables/useTemplateFiltering.ts @@ -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[] ) { + const settingStore = useSettingStore() + const searchQuery = ref('') - const selectedModels = ref([]) - const selectedUseCases = ref([]) - const selectedRunsOn = ref([]) + const selectedModels = ref( + settingStore.get('Comfy.Templates.SelectedModels') + ) + const selectedUseCases = ref( + settingStore.get('Comfy.Templates.SelectedUseCases') + ) + const selectedRunsOn = ref( + 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, diff --git a/src/platform/settings/constants/coreSettings.ts b/src/platform/settings/constants/coreSettings.ts index 2ae706bb7..5d47e1e80 100644 --- a/src/platform/settings/constants/coreSettings.ts +++ b/src/platform/settings/constants/coreSettings.ts @@ -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 */ diff --git a/src/schemas/apiSchema.ts b/src/schemas/apiSchema.ts index ce81c0368..ba5a0e16d 100644 --- a/src/schemas/apiSchema.ts +++ b/src/schemas/apiSchema.ts @@ -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(), diff --git a/tests-ui/tests/composables/useTemplateFiltering.test.ts b/tests-ui/tests/composables/useTemplateFiltering.test.ts index b6428704b..00cc7d856 100644 --- a/tests-ui/tests/composables/useTemplateFiltering.test.ts +++ b/tests-ui/tests/composables/useTemplateFiltering.test.ts @@ -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() })