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