[Manager] Allowing changing sort field of registry search results (#3409)

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Christian Byrne
2025-04-12 06:45:21 +08:00
committed by GitHub
parent 67835edfca
commit b449dbd26b
12 changed files with 86 additions and 20 deletions

View File

@@ -29,6 +29,7 @@
<RegistrySearchBar
v-model:searchQuery="searchQuery"
v-model:searchMode="searchMode"
v-model:sortField="sortField"
:search-results="searchResults"
:suggestions="suggestions"
/>
@@ -166,6 +167,7 @@ const {
isLoading: isSearchLoading,
searchResults,
searchMode,
sortField,
suggestions
} = useRegistrySearch()
pageNumber.value = 0

View File

@@ -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<string>('searchQuery')
const searchMode = defineModel<string>('searchMode', { default: 'packs' })
const sortField = defineModel<PackField>('sortField', { default: 'downloads' })
const sortField = defineModel<SortableAlgoliaField>('sortField', {
default: SortableAlgoliaField.Downloads
})
const { t } = useI18n()
@@ -74,11 +79,12 @@ const hasResults = computed(
() => searchQuery.value?.trim() && searchResults?.length
)
const sortOptions: SearchOption<PackField>[] = [
{ 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<SortableAlgoliaField>[] = [
{ 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<string>[] = [
{ id: 'packs', label: t('manager.filter.nodePack') },

View File

@@ -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, 'asc' | 'desc'> = {
[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<PackField>('downloads')
const sortField = ref<SortableAlgoliaField>(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<AlgoliaNodePack>) => {
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

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -438,8 +438,10 @@
"searchPlaceholder": "検索",
"selectVersion": "バージョンを選択",
"sort": {
"created": "最新",
"downloads": "最も人気",
"rating": "評価"
"publisher": "出版社",
"updated": "最近更新"
},
"status": {
"active": "アクティブ",

View File

@@ -438,8 +438,10 @@
"searchPlaceholder": "검색",
"selectVersion": "버전 선택",
"sort": {
"created": "최신",
"downloads": "가장 인기 있는",
"rating": "평점"
"publisher": "출판사",
"updated": "최근 업데이트"
},
"status": {
"active": "활성",

View File

@@ -438,8 +438,10 @@
"searchPlaceholder": "Поиск",
"selectVersion": "Выберите версию",
"sort": {
"created": "Новейшие",
"downloads": "Самые популярные",
"rating": "Рейтинг"
"publisher": "Издатель",
"updated": "Недавно обновленные"
},
"status": {
"active": "Активный",

View File

@@ -438,8 +438,10 @@
"searchPlaceholder": "搜索",
"selectVersion": "选择版本",
"sort": {
"created": "最新",
"downloads": "最受欢迎",
"rating": "评级"
"publisher": "出版商",
"updated": "最近更新"
},
"status": {
"active": "活跃",

View File

@@ -61,6 +61,7 @@ const RETRIEVE_ATTRIBUTES: SearchAttribute[] = [
'status',
'publisher_id',
'total_install',
'create_time',
'update_time',
'license',
'repository_url',

View File

@@ -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