mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
[Manager] Add suggestions to search (#3041)
This commit is contained in:
@@ -32,6 +32,7 @@
|
||||
v-model:searchQuery="searchQuery"
|
||||
v-model:searchMode="searchMode"
|
||||
:searchResults="searchResults"
|
||||
:suggestions="suggestions"
|
||||
/>
|
||||
<div class="flex-1 overflow-auto">
|
||||
<div
|
||||
@@ -141,8 +142,14 @@ const tabs = ref<TabItem[]>([
|
||||
])
|
||||
const selectedTab = ref<TabItem>(tabs.value[0])
|
||||
|
||||
const { searchQuery, pageNumber, isLoading, searchResults, searchMode } =
|
||||
useRegistrySearch()
|
||||
const {
|
||||
searchQuery,
|
||||
pageNumber,
|
||||
isLoading,
|
||||
searchResults,
|
||||
searchMode,
|
||||
suggestions
|
||||
} = useRegistrySearch()
|
||||
pageNumber.value = 0
|
||||
|
||||
const isInitialLoad = computed(
|
||||
|
||||
@@ -1,15 +1,29 @@
|
||||
<template>
|
||||
<div class="relative w-full p-6">
|
||||
<div class="flex items-center w-full">
|
||||
<IconField class="w-5/12">
|
||||
<InputIcon class="pi pi-search" />
|
||||
<InputText
|
||||
v-model="searchQuery"
|
||||
:placeholder="$t('manager.searchPlaceholder')"
|
||||
class="w-full rounded-2xl"
|
||||
autofocus
|
||||
/>
|
||||
</IconField>
|
||||
<AutoComplete
|
||||
v-model.lazy="searchQuery"
|
||||
:suggestions="suggestions || []"
|
||||
:placeholder="$t('manager.searchPlaceholder')"
|
||||
:complete-on-focus="false"
|
||||
:delay="8"
|
||||
optionLabel="query"
|
||||
class="w-full"
|
||||
@complete="stubTrue"
|
||||
@option-select="onOptionSelect"
|
||||
:pt="{
|
||||
pcInputText: {
|
||||
root: {
|
||||
autofocus: true,
|
||||
class: 'w-5/12 rounded-2xl'
|
||||
}
|
||||
},
|
||||
loader: {
|
||||
style: 'display: none'
|
||||
}
|
||||
}"
|
||||
>
|
||||
</AutoComplete>
|
||||
</div>
|
||||
<div class="flex mt-3 text-sm">
|
||||
<div class="flex gap-6 ml-1">
|
||||
@@ -34,18 +48,21 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IconField from 'primevue/iconfield'
|
||||
import InputIcon from 'primevue/inputicon'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import { stubTrue } from 'lodash'
|
||||
import AutoComplete, {
|
||||
AutoCompleteOptionSelectEvent
|
||||
} from 'primevue/autocomplete'
|
||||
import { computed } from 'vue'
|
||||
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 { components } from '@/types/comfyRegistryTypes'
|
||||
|
||||
const props = defineProps<{
|
||||
const { searchResults } = defineProps<{
|
||||
searchResults?: components['schemas']['Node'][]
|
||||
suggestions?: NodesIndexSuggestion[]
|
||||
}>()
|
||||
|
||||
const searchQuery = defineModel<string>('searchQuery')
|
||||
@@ -55,7 +72,7 @@ const sortField = defineModel<PackField>('sortField', { default: 'downloads' })
|
||||
const { t } = useI18n()
|
||||
|
||||
const hasResults = computed(
|
||||
() => searchQuery.value?.trim() && props.searchResults?.length
|
||||
() => searchQuery.value?.trim() && searchResults?.length
|
||||
)
|
||||
|
||||
const sortOptions: SearchOption<PackField>[] = [
|
||||
@@ -68,4 +85,8 @@ const filterOptions: SearchOption<string>[] = [
|
||||
{ id: 'packs', label: t('manager.filter.nodePack') },
|
||||
{ id: 'nodes', label: t('g.nodes') }
|
||||
]
|
||||
|
||||
const onOptionSelect = (event: AutoCompleteOptionSelectEvent) => {
|
||||
searchQuery.value = event.value.query
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
SearchAttribute,
|
||||
useAlgoliaSearchService
|
||||
} from '@/services/algoliaSearchService'
|
||||
import type { NodesIndexSuggestion } from '@/services/algoliaSearchService'
|
||||
import { PackField } from '@/types/comfyManagerTypes'
|
||||
|
||||
const SEARCH_DEBOUNCE_TIME = 16
|
||||
@@ -23,6 +24,7 @@ export function useRegistrySearch() {
|
||||
const pageNumber = ref(0)
|
||||
const searchQuery = ref('')
|
||||
const results = ref<AlgoliaNodePack[]>([])
|
||||
const suggestions = ref<NodesIndexSuggestion[]>([])
|
||||
|
||||
const searchAttributes = computed<SearchAttribute[]>(() =>
|
||||
searchMode.value === 'nodes' ? ['comfy_nodes'] : ['name', 'description']
|
||||
@@ -49,11 +51,16 @@ export function useRegistrySearch() {
|
||||
|
||||
const onQueryChange = async () => {
|
||||
isLoading.value = true
|
||||
results.value = await searchPacks(searchQuery.value, {
|
||||
pageSize: pageSize.value,
|
||||
pageNumber: pageNumber.value,
|
||||
restrictSearchableAttributes: searchAttributes.value
|
||||
})
|
||||
const { nodePacks, querySuggestions } = await searchPacks(
|
||||
searchQuery.value,
|
||||
{
|
||||
pageSize: pageSize.value,
|
||||
pageNumber: pageNumber.value,
|
||||
restrictSearchableAttributes: searchAttributes.value
|
||||
}
|
||||
)
|
||||
results.value = nodePacks
|
||||
suggestions.value = querySuggestions
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
@@ -71,6 +78,7 @@ export function useRegistrySearch() {
|
||||
sortField,
|
||||
searchMode,
|
||||
searchQuery,
|
||||
suggestions,
|
||||
searchResults: resultsAsRegistryPacks,
|
||||
nodeSearchResults: resultsAsNodes
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type {
|
||||
BaseSearchParamsWithoutQuery,
|
||||
Hit
|
||||
Hit,
|
||||
SearchResponse
|
||||
} from 'algoliasearch/dist/lite/browser'
|
||||
import { liteClient as algoliasearch } from 'algoliasearch/dist/lite/builds/browser'
|
||||
import { omit } from 'lodash'
|
||||
@@ -50,6 +51,8 @@ export interface AlgoliaNodePack {
|
||||
'latest_version',
|
||||
'comfy_node_extract_status'
|
||||
>
|
||||
/** `total_install` index only */
|
||||
icon_url: RegistryNodePack['icon']
|
||||
}
|
||||
|
||||
export type SearchAttribute = keyof AlgoliaNodePack
|
||||
@@ -70,6 +73,20 @@ const RETRIEVE_ATTRIBUTES: SearchAttribute[] = [
|
||||
'id'
|
||||
]
|
||||
|
||||
export interface NodesIndexSuggestion {
|
||||
nb_words: number
|
||||
nodes_index: {
|
||||
exact_nb_hits: number
|
||||
facets: {
|
||||
exact_matches: Record<string, number>
|
||||
analytics: Record<string, any>
|
||||
}
|
||||
}
|
||||
objectID: RegistryNodePack['id']
|
||||
popularity: number
|
||||
query: string
|
||||
}
|
||||
|
||||
type SearchNodePacksParams = BaseSearchParamsWithoutQuery & {
|
||||
pageSize: number
|
||||
pageNumber: number
|
||||
@@ -112,6 +129,7 @@ export const useAlgoliaSearchService = () => {
|
||||
license: algoliaNode.license,
|
||||
downloads: algoliaNode.total_install,
|
||||
status: algoliaNode.status,
|
||||
icon: algoliaNode.icon_url,
|
||||
latest_version: toRegistryLatestVersion(algoliaNode),
|
||||
publisher: toRegistryPublisher(algoliaNode)
|
||||
}
|
||||
@@ -123,11 +141,16 @@ export const useAlgoliaSearchService = () => {
|
||||
const searchPacks = async (
|
||||
query: string,
|
||||
params: SearchNodePacksParams
|
||||
): Promise<Hit<AlgoliaNodePack>[]> => {
|
||||
): Promise<{
|
||||
nodePacks: Hit<AlgoliaNodePack>[]
|
||||
querySuggestions: Hit<NodesIndexSuggestion>[]
|
||||
}> => {
|
||||
const { pageSize, pageNumber } = params
|
||||
const rest = omit(params, ['pageSize', 'pageNumber'])
|
||||
|
||||
const { results } = await searchClient.search<AlgoliaNodePack>({
|
||||
const { results } = await searchClient.search<
|
||||
AlgoliaNodePack | NodesIndexSuggestion
|
||||
>({
|
||||
requests: [
|
||||
{
|
||||
query,
|
||||
@@ -135,15 +158,25 @@ export const useAlgoliaSearchService = () => {
|
||||
attributesToRetrieve: RETRIEVE_ATTRIBUTES,
|
||||
...rest,
|
||||
hitsPerPage: pageSize,
|
||||
length: pageSize,
|
||||
page: pageNumber
|
||||
},
|
||||
{
|
||||
indexName: 'nodes_index_query_suggestions',
|
||||
query
|
||||
}
|
||||
],
|
||||
strategy: 'none'
|
||||
})
|
||||
|
||||
// Narrow from `SearchResponse<T> | SearchForFacetValuesResponse` to `SearchResponse<T>`
|
||||
return 'hits' in results[0] ? results[0].hits : [] // Only querying a single index for now
|
||||
const [nodePacks, querySuggestions] = results as [
|
||||
SearchResponse<AlgoliaNodePack>,
|
||||
SearchResponse<NodesIndexSuggestion>
|
||||
]
|
||||
|
||||
return {
|
||||
nodePacks: nodePacks.hits,
|
||||
querySuggestions: querySuggestions.hits
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user