From b449dbd26b7a96046252eb23903736d784832335 Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Sat, 12 Apr 2025 06:45:21 +0800 Subject: [PATCH] [Manager] Allowing changing sort field of registry search results (#3409) Co-authored-by: github-actions --- .../content/manager/ManagerDialogContent.vue | 2 + .../registrySearchBar/RegistrySearchBar.vue | 20 ++++++--- src/composables/useRegistrySearch.ts | 45 ++++++++++++++++--- src/locales/en/main.json | 6 ++- src/locales/es/main.json | 4 +- src/locales/fr/main.json | 4 +- src/locales/ja/main.json | 4 +- src/locales/ko/main.json | 4 +- src/locales/ru/main.json | 4 +- src/locales/zh/main.json | 4 +- src/services/algoliaSearchService.ts | 1 + src/types/comfyManagerTypes.ts | 8 ++++ 12 files changed, 86 insertions(+), 20 deletions(-) diff --git a/src/components/dialog/content/manager/ManagerDialogContent.vue b/src/components/dialog/content/manager/ManagerDialogContent.vue index 4261ff6ff..0c19ea0e9 100644 --- a/src/components/dialog/content/manager/ManagerDialogContent.vue +++ b/src/components/dialog/content/manager/ManagerDialogContent.vue @@ -29,6 +29,7 @@ @@ -166,6 +167,7 @@ const { isLoading: isSearchLoading, searchResults, searchMode, + sortField, suggestions } = useRegistrySearch() pageNumber.value = 0 diff --git a/src/components/dialog/content/manager/registrySearchBar/RegistrySearchBar.vue b/src/components/dialog/content/manager/registrySearchBar/RegistrySearchBar.vue index 413af0438..9fd0cfa3c 100644 --- a/src/components/dialog/content/manager/registrySearchBar/RegistrySearchBar.vue +++ b/src/components/dialog/content/manager/registrySearchBar/RegistrySearchBar.vue @@ -56,7 +56,10 @@ import { useI18n } from 'vue-i18n' import SearchFilterDropdown from '@/components/dialog/content/manager/registrySearchBar/SearchFilterDropdown.vue' import type { NodesIndexSuggestion } from '@/services/algoliaSearchService' -import type { PackField, SearchOption } from '@/types/comfyManagerTypes' +import { + type SearchOption, + SortableAlgoliaField +} from '@/types/comfyManagerTypes' import { components } from '@/types/comfyRegistryTypes' const { searchResults } = defineProps<{ @@ -66,7 +69,9 @@ const { searchResults } = defineProps<{ const searchQuery = defineModel('searchQuery') const searchMode = defineModel('searchMode', { default: 'packs' }) -const sortField = defineModel('sortField', { default: 'downloads' }) +const sortField = defineModel('sortField', { + default: SortableAlgoliaField.Downloads +}) const { t } = useI18n() @@ -74,11 +79,12 @@ const hasResults = computed( () => searchQuery.value?.trim() && searchResults?.length ) -const sortOptions: SearchOption[] = [ - { id: 'downloads', label: t('manager.sort.downloads') }, - { id: 'name', label: t('g.name') }, - { id: 'rating', label: t('manager.sort.rating') }, - { id: 'category', label: t('g.category') } +const sortOptions: SearchOption[] = [ + { id: SortableAlgoliaField.Downloads, label: t('manager.sort.downloads') }, + { id: SortableAlgoliaField.Created, label: t('manager.sort.created') }, + { id: SortableAlgoliaField.Updated, label: t('manager.sort.updated') }, + { id: SortableAlgoliaField.Publisher, label: t('manager.sort.publisher') }, + { id: SortableAlgoliaField.Name, label: t('g.name') } ] const filterOptions: SearchOption[] = [ { id: 'packs', label: t('manager.filter.nodePack') }, diff --git a/src/composables/useRegistrySearch.ts b/src/composables/useRegistrySearch.ts index 7b4234c71..2960dc74a 100644 --- a/src/composables/useRegistrySearch.ts +++ b/src/composables/useRegistrySearch.ts @@ -1,5 +1,6 @@ import { watchDebounced } from '@vueuse/core' -import { memoize } from 'lodash' +import type { Hit } from 'algoliasearch/dist/lite/browser' +import { memoize, orderBy } from 'lodash' import { computed, ref, watch } from 'vue' import { @@ -8,17 +9,30 @@ import { useAlgoliaSearchService } from '@/services/algoliaSearchService' import type { NodesIndexSuggestion } from '@/services/algoliaSearchService' -import { PackField } from '@/types/comfyManagerTypes' +import { SortableAlgoliaField } from '@/types/comfyManagerTypes' const SEARCH_DEBOUNCE_TIME = 256 const DEFAULT_PAGE_SIZE = 64 +const DEFAULT_SORT_FIELD = SortableAlgoliaField.Downloads // Set in the index configuration + +const SORT_DIRECTIONS: Record = { + [SortableAlgoliaField.Downloads]: 'desc', + [SortableAlgoliaField.Created]: 'desc', + [SortableAlgoliaField.Updated]: 'desc', + [SortableAlgoliaField.Publisher]: 'asc', + [SortableAlgoliaField.Name]: 'asc' +} + +const isDateField = (field: SortableAlgoliaField): boolean => + field === SortableAlgoliaField.Created || + field === SortableAlgoliaField.Updated /** * Composable for managing UI state of Comfy Node Registry search. */ export function useRegistrySearch() { const isLoading = ref(false) - const sortField = ref('downloads') + const sortField = ref(SortableAlgoliaField.Downloads) const searchMode = ref<'nodes' | 'packs'>('packs') const pageSize = ref(DEFAULT_PAGE_SIZE) const pageNumber = ref(0) @@ -48,6 +62,15 @@ export function useRegistrySearch() { toRegistryPack, (algoliaNode: AlgoliaNodePack) => algoliaNode.id ) + const getSortValue = (pack: Hit) => { + if (isDateField(sortField.value)) { + const value = pack[sortField.value] + return value ? new Date(value).getTime() : 0 + } else { + const value = pack[sortField.value] + return value ?? 0 + } + } const updateSearchResults = async (options: { append?: boolean }) => { isLoading.value = true @@ -62,10 +85,22 @@ export function useRegistrySearch() { restrictSearchableAttributes: searchAttributes.value } ) + + let sortedPacks = nodePacks + + // Results are sorted by the default field to begin with -- so don't manually sort again + if (sortField.value && sortField.value !== DEFAULT_SORT_FIELD) { + sortedPacks = orderBy( + nodePacks, + [getSortValue], + [SORT_DIRECTIONS[sortField.value]] + ) + } + if (options.append && results.value?.length) { - results.value = results.value.concat(nodePacks) + results.value = results.value.concat(sortedPacks) } else { - results.value = nodePacks + results.value = sortedPacks } suggestions.value = querySuggestions isLoading.value = false diff --git a/src/locales/en/main.json b/src/locales/en/main.json index dcc2c749f..1db35b1b6 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -154,8 +154,10 @@ "unknown": "Unknown" }, "sort": { - "rating": "Rating", - "downloads": "Most Popular" + "downloads": "Most Popular", + "publisher": "Publisher", + "created": "Newest", + "updated": "Updated Recently" }, "filter": { "nodePack": "Node Pack", diff --git a/src/locales/es/main.json b/src/locales/es/main.json index f3876a914..b1a136d6d 100644 --- a/src/locales/es/main.json +++ b/src/locales/es/main.json @@ -438,8 +438,10 @@ "searchPlaceholder": "Buscar", "selectVersion": "Seleccionar Versión", "sort": { + "created": "Más reciente", "downloads": "Más Popular", - "rating": "Calificación" + "publisher": "Editor", + "updated": "Actualizado recientemente" }, "status": { "active": "Activo", diff --git a/src/locales/fr/main.json b/src/locales/fr/main.json index 6c03cf198..159b9ff85 100644 --- a/src/locales/fr/main.json +++ b/src/locales/fr/main.json @@ -438,8 +438,10 @@ "searchPlaceholder": "Recherche", "selectVersion": "Sélectionner la version", "sort": { + "created": "Le plus récent", "downloads": "Le plus populaire", - "rating": "Évaluation" + "publisher": "Éditeur", + "updated": "Mis à jour récemment" }, "status": { "active": "Actif", diff --git a/src/locales/ja/main.json b/src/locales/ja/main.json index 9f2f9e627..d489c5ac1 100644 --- a/src/locales/ja/main.json +++ b/src/locales/ja/main.json @@ -438,8 +438,10 @@ "searchPlaceholder": "検索", "selectVersion": "バージョンを選択", "sort": { + "created": "最新", "downloads": "最も人気", - "rating": "評価" + "publisher": "出版社", + "updated": "最近更新" }, "status": { "active": "アクティブ", diff --git a/src/locales/ko/main.json b/src/locales/ko/main.json index 763a5c18a..40ff7afcf 100644 --- a/src/locales/ko/main.json +++ b/src/locales/ko/main.json @@ -438,8 +438,10 @@ "searchPlaceholder": "검색", "selectVersion": "버전 선택", "sort": { + "created": "최신", "downloads": "가장 인기 있는", - "rating": "평점" + "publisher": "출판사", + "updated": "최근 업데이트" }, "status": { "active": "활성", diff --git a/src/locales/ru/main.json b/src/locales/ru/main.json index fd7864cdd..a849d3c09 100644 --- a/src/locales/ru/main.json +++ b/src/locales/ru/main.json @@ -438,8 +438,10 @@ "searchPlaceholder": "Поиск", "selectVersion": "Выберите версию", "sort": { + "created": "Новейшие", "downloads": "Самые популярные", - "rating": "Рейтинг" + "publisher": "Издатель", + "updated": "Недавно обновленные" }, "status": { "active": "Активный", diff --git a/src/locales/zh/main.json b/src/locales/zh/main.json index 527b943f8..1f90e505d 100644 --- a/src/locales/zh/main.json +++ b/src/locales/zh/main.json @@ -438,8 +438,10 @@ "searchPlaceholder": "搜索", "selectVersion": "选择版本", "sort": { + "created": "最新", "downloads": "最受欢迎", - "rating": "评级" + "publisher": "出版商", + "updated": "最近更新" }, "status": { "active": "活跃", diff --git a/src/services/algoliaSearchService.ts b/src/services/algoliaSearchService.ts index 61f68b4a4..1af48a86d 100644 --- a/src/services/algoliaSearchService.ts +++ b/src/services/algoliaSearchService.ts @@ -61,6 +61,7 @@ const RETRIEVE_ATTRIBUTES: SearchAttribute[] = [ 'status', 'publisher_id', 'total_install', + 'create_time', 'update_time', 'license', 'repository_url', diff --git a/src/types/comfyManagerTypes.ts b/src/types/comfyManagerTypes.ts index 66edc0d05..c7c687519 100644 --- a/src/types/comfyManagerTypes.ts +++ b/src/types/comfyManagerTypes.ts @@ -18,6 +18,14 @@ export enum ManagerTab { UpdateAvailable = 'updateAvailable' } +export enum SortableAlgoliaField { + Downloads = 'total_install', + Created = 'create_time', + Updated = 'update_time', + Publisher = 'publisher_id', + Name = 'name' +} + export interface TabItem { id: string label: string