[Manager] Add Algolia search (#3036)

This commit is contained in:
Christian Byrne
2025-03-13 18:24:38 -07:00
committed by GitHub
parent 20d2eca51e
commit 05dd587928
10 changed files with 449 additions and 116 deletions

View File

@@ -27,6 +27,8 @@ jobs:
- name: Build project
env:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }}
run: |
npm ci
npm run fetch-templates

191
package-lock.json generated
View File

@@ -27,6 +27,7 @@
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-serialize": "^0.13.0",
"@xterm/xterm": "^5.5.0",
"algoliasearch": "^5.21.0",
"axios": "^1.8.2",
"dotenv": "^16.4.5",
"fuse.js": "^7.0.0",
@@ -113,6 +114,173 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/@algolia/client-abtesting": {
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.21.0.tgz",
"integrity": "sha512-I239aSmXa3pXDhp3AWGaIfesqJBNFA7drUM8SIfNxMIzvQXUnHRf4rW1o77QXLI/nIClNsb8KOLaB62gO9LnlQ==",
"dependencies": {
"@algolia/client-common": "5.21.0",
"@algolia/requester-browser-xhr": "5.21.0",
"@algolia/requester-fetch": "5.21.0",
"@algolia/requester-node-http": "5.21.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@algolia/client-analytics": {
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.21.0.tgz",
"integrity": "sha512-OxoUfeG9G4VE4gS7B4q65KkHzdGsQsDwxQfR5J9uKB8poSGuNlHJWsF3ABqCkc5VliAR0m8KMjsQ9o/kOpEGnQ==",
"dependencies": {
"@algolia/client-common": "5.21.0",
"@algolia/requester-browser-xhr": "5.21.0",
"@algolia/requester-fetch": "5.21.0",
"@algolia/requester-node-http": "5.21.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@algolia/client-common": {
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.21.0.tgz",
"integrity": "sha512-iHLgDQFyZNe9M16vipbx6FGOA8NoMswHrfom/QlCGoyh7ntjGvfMb+J2Ss8rRsAlOWluv8h923Ku3QVaB0oWDQ==",
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@algolia/client-insights": {
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.21.0.tgz",
"integrity": "sha512-y7XBO9Iwb75FLDl95AYcWSLIViJTpR5SUUCyKsYhpP9DgyUqWbISqDLXc96TS9shj+H+7VsTKA9cJK8NUfVN6g==",
"dependencies": {
"@algolia/client-common": "5.21.0",
"@algolia/requester-browser-xhr": "5.21.0",
"@algolia/requester-fetch": "5.21.0",
"@algolia/requester-node-http": "5.21.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@algolia/client-personalization": {
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.21.0.tgz",
"integrity": "sha512-6KU658lD9Tss4oCX6c/O15tNZxw7vR+WAUG95YtZzYG/KGJHTpy2uckqbMmC2cEK4a86FAq4pH5azSJ7cGMjuw==",
"dependencies": {
"@algolia/client-common": "5.21.0",
"@algolia/requester-browser-xhr": "5.21.0",
"@algolia/requester-fetch": "5.21.0",
"@algolia/requester-node-http": "5.21.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@algolia/client-query-suggestions": {
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.21.0.tgz",
"integrity": "sha512-pG6MyVh1v0X+uwrKHn3U+suHdgJ2C+gug+UGkNHfMELHMsEoWIAQhxMBOFg7hCnWBFjQnuq6qhM3X9X5QO3d9Q==",
"dependencies": {
"@algolia/client-common": "5.21.0",
"@algolia/requester-browser-xhr": "5.21.0",
"@algolia/requester-fetch": "5.21.0",
"@algolia/requester-node-http": "5.21.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@algolia/client-search": {
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.21.0.tgz",
"integrity": "sha512-nZfgJH4njBK98tFCmCW1VX/ExH4bNOl9DSboxeXGgvhoL0fG1+4DDr/mrLe21OggVCQqHwXBMh6fFInvBeyhiQ==",
"dependencies": {
"@algolia/client-common": "5.21.0",
"@algolia/requester-browser-xhr": "5.21.0",
"@algolia/requester-fetch": "5.21.0",
"@algolia/requester-node-http": "5.21.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@algolia/ingestion": {
"version": "1.21.0",
"resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.21.0.tgz",
"integrity": "sha512-k6MZxLbZphGN5uRri9J/krQQBjUrqNcScPh985XXEFXbSCRvOPKVtjjLdVjGVHXXPOQgKrIZHxIdRNbHS+wVuA==",
"dependencies": {
"@algolia/client-common": "5.21.0",
"@algolia/requester-browser-xhr": "5.21.0",
"@algolia/requester-fetch": "5.21.0",
"@algolia/requester-node-http": "5.21.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@algolia/monitoring": {
"version": "1.21.0",
"resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.21.0.tgz",
"integrity": "sha512-FiW5nnmyHvaGdorqLClw3PM6keXexAMiwbwJ9xzQr4LcNefLG3ln82NafRPgJO/z0dETAOKjds5aSmEFMiITHQ==",
"dependencies": {
"@algolia/client-common": "5.21.0",
"@algolia/requester-browser-xhr": "5.21.0",
"@algolia/requester-fetch": "5.21.0",
"@algolia/requester-node-http": "5.21.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@algolia/recommend": {
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.21.0.tgz",
"integrity": "sha512-+JXavbbliaLmah5QNgc/TDW/+r0ALa+rGhg5Y7+pF6GpNnzO0L+nlUaDNE8QbiJfz54F9BkwFUnJJeRJAuzTFw==",
"dependencies": {
"@algolia/client-common": "5.21.0",
"@algolia/requester-browser-xhr": "5.21.0",
"@algolia/requester-fetch": "5.21.0",
"@algolia/requester-node-http": "5.21.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@algolia/requester-browser-xhr": {
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.21.0.tgz",
"integrity": "sha512-Iw+Yj5hOmo/iixHS94vEAQ3zi5GPpJywhfxn1el/zWo4AvPIte/+1h9Ywgw/+3M7YBj4jgAkScxjxQCxzLBsjA==",
"dependencies": {
"@algolia/client-common": "5.21.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@algolia/requester-fetch": {
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.21.0.tgz",
"integrity": "sha512-Z00SRLlIFj3SjYVfsd9Yd3kB3dUwQFAkQG18NunWP7cix2ezXpJqA+xAoEf9vc4QZHdxU3Gm8gHAtRiM2iVaTQ==",
"dependencies": {
"@algolia/client-common": "5.21.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@algolia/requester-node-http": {
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.21.0.tgz",
"integrity": "sha512-WqU0VumUILrIeVYCTGZlyyZoC/tbvhiyPxfGRRO1cSjxN558bnJLlR2BvS0SJ5b75dRNK7HDvtXo2QoP9eLfiA==",
"dependencies": {
"@algolia/client-common": "5.21.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@alloc/quick-lru": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
@@ -3859,6 +4027,29 @@
"dev": true,
"license": "MIT"
},
"node_modules/algoliasearch": {
"version": "5.21.0",
"resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.21.0.tgz",
"integrity": "sha512-hexLq2lSO1K5SW9j21Ubc+q9Ptx7dyRTY7se19U8lhIlVMLCNXWCyQ6C22p9ez8ccX0v7QVmwkl2l1CnuGoO2Q==",
"dependencies": {
"@algolia/client-abtesting": "5.21.0",
"@algolia/client-analytics": "5.21.0",
"@algolia/client-common": "5.21.0",
"@algolia/client-insights": "5.21.0",
"@algolia/client-personalization": "5.21.0",
"@algolia/client-query-suggestions": "5.21.0",
"@algolia/client-search": "5.21.0",
"@algolia/ingestion": "1.21.0",
"@algolia/monitoring": "1.21.0",
"@algolia/recommend": "5.21.0",
"@algolia/requester-browser-xhr": "5.21.0",
"@algolia/requester-fetch": "5.21.0",
"@algolia/requester-node-http": "5.21.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/alien-signals": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.2.2.tgz",

View File

@@ -88,6 +88,7 @@
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-serialize": "^0.13.0",
"@xterm/xterm": "^5.5.0",
"algoliasearch": "^5.21.0",
"axios": "^1.8.2",
"dotenv": "^16.4.5",
"fuse.js": "^7.0.0",

View File

@@ -30,9 +30,8 @@
<RegistrySearchBar
v-if="!hideSearchBar"
v-model:searchQuery="searchQuery"
v-model:searchMode="searchMode"
:searchResults="searchResults"
@update:sortBy="handleSortChange"
@update:filterBy="handleFilterChange"
/>
<div class="flex-1 overflow-auto">
<div
@@ -42,14 +41,14 @@
<ProgressSpinner />
</div>
<NoResultsPlaceholder
v-else-if="error || searchResults.length === 0"
v-else-if="searchResults.length === 0"
:title="
error
comfyManagerStore.error
? $t('manager.errorConnecting')
: $t('manager.noResultsFound')
"
:message="
error
comfyManagerStore.error
? $t('manager.tryAgainLater')
: $t('manager.tryDifferentSearch')
"
@@ -121,7 +120,7 @@ import { useResponsiveCollapse } from '@/composables/element/useResponsiveCollap
import { useInstalledPacks } from '@/composables/useInstalledPacks'
import { useRegistrySearch } from '@/composables/useRegistrySearch'
import { useComfyManagerStore } from '@/stores/comfyManagerStore'
import type { PackField, TabItem } from '@/types/comfyManagerTypes'
import type { TabItem } from '@/types/comfyManagerTypes'
import { components } from '@/types/comfyRegistryTypes'
const DEFAULT_CARD_SIZE = 512
@@ -142,9 +141,9 @@ const tabs = ref<TabItem[]>([
])
const selectedTab = ref<TabItem>(tabs.value[0])
const { searchQuery, pageNumber, sortField, isLoading, error, searchResults } =
const { searchQuery, pageNumber, isLoading, searchResults, searchMode } =
useRegistrySearch()
pageNumber.value = 1
pageNumber.value = 0
const isInitialLoad = computed(
() => searchResults.value.length === 0 && searchQuery.value === ''
@@ -216,12 +215,4 @@ const handleGridContainerClick = (event: MouseEvent) => {
const showInfoPanel = computed(() => selectedNodePacks.value.length > 0)
const hasMultipleSelections = computed(() => selectedNodePacks.value.length > 1)
const currentFilterBy = ref('all')
const handleSortChange = (sortBy: PackField) => {
sortField.value = sortBy
}
const handleFilterChange = (filterBy: PackField) => {
currentFilterBy.value = filterBy
}
</script>

View File

@@ -4,8 +4,7 @@
<IconField class="w-5/12">
<InputIcon class="pi pi-search" />
<InputText
:model-value="searchQuery"
@update:model-value="$emit('update:searchQuery', $event)"
v-model="searchQuery"
:placeholder="$t('manager.searchPlaceholder')"
class="w-full rounded-2xl"
autofocus
@@ -15,21 +14,19 @@
<div class="flex mt-3 text-sm">
<div class="flex gap-6 ml-1">
<SearchFilterDropdown
v-model="currentFilter"
v-model:modelValue="searchMode"
:options="filterOptions"
:label="$t('g.filter')"
@update:model-value="handleFilterChange"
/>
<SearchFilterDropdown
v-model="currentSort"
v-model:modelValue="sortField"
:options="sortOptions"
:label="$t('g.sort')"
@update:model-value="handleSortChange"
/>
</div>
<div class="flex items-center gap-4 ml-6">
<small v-if="hasResults" class="text-color-secondary">
{{ $t('g.resultsCount', { count: searchResults.length }) }}
{{ $t('g.resultsCount', { count: searchResults?.length || 0 }) }}
</small>
</div>
</div>
@@ -40,34 +37,25 @@
import IconField from 'primevue/iconfield'
import InputIcon from 'primevue/inputicon'
import InputText from 'primevue/inputtext'
import { computed, ref } from 'vue'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import SearchFilterDropdown from '@/components/dialog/content/manager/registrySearchBar/SearchFilterDropdown.vue'
import type { PackField, SearchOption } from '@/types/comfyManagerTypes'
import { components } from '@/types/comfyRegistryTypes'
const DEFAULT_SORT: PackField = 'downloads'
const DEFAULT_FILTER = 'nodePack'
const props = defineProps<{
searchQuery: string
searchResults?: components['schemas']['Node'][]
}>()
const searchQuery = defineModel<string>('searchQuery')
const searchMode = defineModel<string>('searchMode', { default: 'packs' })
const sortField = defineModel<PackField>('sortField', { default: 'downloads' })
const { t } = useI18n()
const currentSort = ref<PackField>(DEFAULT_SORT)
const currentFilter = ref<string>(DEFAULT_FILTER)
const emit = defineEmits<{
'update:searchQuery': [value: string]
'update:sortBy': [value: PackField]
'update:filterBy': [value: string]
}>()
const hasResults = computed(
() => props.searchQuery.trim() && props.searchResults?.length
() => searchQuery.value?.trim() && props.searchResults?.length
)
const sortOptions: SearchOption<PackField>[] = [
@@ -77,16 +65,7 @@ const sortOptions: SearchOption<PackField>[] = [
{ id: 'category', label: t('g.category') }
]
const filterOptions: SearchOption<string>[] = [
{ id: 'nodePack', label: t('manager.filter.nodePack') },
{ id: 'node', label: t('g.nodes') }
{ id: 'packs', label: t('manager.filter.nodePack') },
{ id: 'nodes', label: t('g.nodes') }
]
const handleSortChange = () => {
// TODO: emit to Algolia service
emit('update:sortBy', currentSort.value)
}
const handleFilterChange = () => {
// TODO: emit to Algolia service
emit('update:filterBy', currentFilter.value)
}
</script>

View File

@@ -2,12 +2,12 @@
<div class="flex items-center gap-1">
<span class="text-muted">{{ label }}:</span>
<Dropdown
v-model="selectedValue"
:modelValue="modelValue"
@update:modelValue="$emit('update:modelValue', $event)"
:options="options"
optionLabel="label"
optionValue="id"
class="min-w-[6rem] border-none bg-transparent shadow-none"
@change="handleChange"
:pt="{
input: { class: 'py-0 px-1 border-none' },
trigger: { class: 'hidden' },
@@ -20,26 +20,16 @@
<script setup lang="ts" generic="T">
import Dropdown from 'primevue/dropdown'
import { computed } from 'vue'
import type { SearchOption } from '@/types/comfyManagerTypes'
const { modelValue, options, label } = defineProps<{
modelValue: T
defineProps<{
options: SearchOption<T>[]
label: string
modelValue: T
}>()
const emit = defineEmits<{
defineEmits<{
'update:modelValue': [value: T]
}>()
const selectedValue = computed({
get: () => modelValue,
set: (value) => emit('update:modelValue', value)
})
const handleChange = () => {
emit('update:modelValue', selectedValue.value)
}
</script>

View File

@@ -1,72 +1,77 @@
import { debounce } from 'lodash'
import { onUnmounted, ref, watch } from 'vue'
import { watchDebounced } from '@vueuse/core'
import { memoize } from 'lodash'
import { computed, ref, watch } from 'vue'
import { useComfyRegistryService } from '@/services/comfyRegistryService'
import { useComfyRegistryStore } from '@/stores/comfyRegistryStore'
import type { components } from '@/types/comfyRegistryTypes'
import {
AlgoliaNodePack,
SearchAttribute,
useAlgoliaSearchService
} from '@/services/algoliaSearchService'
import { PackField } from '@/types/comfyManagerTypes'
const SEARCH_DEBOUNCE_TIME = 256
const DEFAULT_PAGE_SIZE = 60
const DEFAULT_SORT_FIELD: keyof components['schemas']['Node'] = 'downloads'
const SEARCH_DEBOUNCE_TIME = 16
const DEFAULT_PAGE_SIZE = 64
/**
* Composable for managing UI state of Comfy Node Registry search.
*/
export function useRegistrySearch() {
const registryStore = useComfyRegistryStore()
const registryService = useComfyRegistryService()
const searchQuery = ref('')
const pageNumber = ref(1)
const isLoading = ref(false)
const sortField = ref<PackField>('downloads')
const searchMode = ref<'nodes' | 'packs'>('packs')
const pageSize = ref(DEFAULT_PAGE_SIZE)
const sortField = ref<keyof components['schemas']['Node']>(DEFAULT_SORT_FIELD)
const searchResults = ref<components['schemas']['Node'][]>([])
const pageNumber = ref(0)
const searchQuery = ref('')
const results = ref<AlgoliaNodePack[]>([])
const search = async () => {
try {
const isEmptySearch = searchQuery.value === ''
const result = isEmptySearch
? await registryStore.listAllPacks({
page: pageNumber.value,
limit: pageSize.value,
sort: [sortField.value]
})
: await registryService.search({
search: searchQuery.value,
page: pageNumber.value,
limit: pageSize.value
})
const searchAttributes = computed<SearchAttribute[]>(() =>
searchMode.value === 'nodes' ? ['comfy_nodes'] : ['name', 'description']
)
if (result) {
searchResults.value = result.nodes || []
} else {
searchResults.value = []
}
} catch (err) {
console.error('Error loading packs:', err)
searchResults.value = []
}
const resultsAsRegistryPacks = computed(() =>
results.value ? results.value.map(algoliaToRegistry) : []
)
const resultsAsNodes = computed(() =>
results.value
? results.value.reduce(
(acc, hit) => acc.concat(hit.comfy_nodes),
[] as string[]
)
: []
)
const { searchPacks, toRegistryPack } = useAlgoliaSearchService()
const algoliaToRegistry = memoize(
toRegistryPack,
(algoliaNode: AlgoliaNodePack) => algoliaNode.id
)
const onQueryChange = async () => {
isLoading.value = true
results.value = await searchPacks(searchQuery.value, {
pageSize: pageSize.value,
pageNumber: pageNumber.value,
restrictSearchableAttributes: searchAttributes.value
})
isLoading.value = false
}
const debouncedSearch = debounce(search, SEARCH_DEBOUNCE_TIME)
// Debounce search when query changes
watch(() => searchQuery.value, debouncedSearch)
watch(() => [pageNumber.value, sortField.value], search, { immediate: true })
onUnmounted(() => {
debouncedSearch.cancel() // Cancel debounced searches
registryStore.cancelRequests() // Cancel in-flight requests
watch([pageNumber, sortField, searchMode], onQueryChange, {
immediate: true
})
watchDebounced(searchQuery, onQueryChange, {
debounce: SEARCH_DEBOUNCE_TIME
})
return {
isLoading,
pageNumber,
pageSize,
sortField,
searchMode,
searchQuery,
searchResults,
isLoading: registryService.isLoading,
error: registryService.error
searchResults: resultsAsRegistryPacks,
nodeSearchResults: resultsAsNodes
}
}

View File

@@ -0,0 +1,153 @@
import type {
BaseSearchParamsWithoutQuery,
Hit
} from 'algoliasearch/dist/lite/browser'
import { liteClient as algoliasearch } from 'algoliasearch/dist/lite/builds/browser'
import { omit } from 'lodash'
import { components } from '@/types/comfyRegistryTypes'
declare const __ALGOLIA_APP_ID__: string
declare const __ALGOLIA_API_KEY__: string
type SafeNestedProperty<
T,
K1 extends keyof T,
K2 extends keyof NonNullable<T[K1]>
> = T[K1] extends undefined | null ? undefined : NonNullable<T[K1]>[K2]
type RegistryNodePack = components['schemas']['Node']
export interface AlgoliaNodePack {
objectID: RegistryNodePack['id']
name: RegistryNodePack['name']
publisher_id: SafeNestedProperty<RegistryNodePack, 'publisher', 'id'>
description: RegistryNodePack['description']
comfy_nodes: string[]
total_install: RegistryNodePack['downloads']
id: RegistryNodePack['id']
create_time: string
update_time: SafeNestedProperty<
RegistryNodePack,
'latest_version',
'createdAt'
>
license: RegistryNodePack['license']
repository_url: RegistryNodePack['repository']
status: RegistryNodePack['status']
latest_version: SafeNestedProperty<
RegistryNodePack,
'latest_version',
'version'
>
latest_version_status: SafeNestedProperty<
RegistryNodePack,
'latest_version',
'status'
>
comfy_node_extract_status: SafeNestedProperty<
RegistryNodePack,
'latest_version',
'comfy_node_extract_status'
>
}
export type SearchAttribute = keyof AlgoliaNodePack
const RETRIEVE_ATTRIBUTES: SearchAttribute[] = [
'comfy_nodes',
'name',
'description',
'latest_version',
'status',
'publisher_id',
'total_install',
'update_time',
'license',
'repository_url',
'latest_version_status',
'comfy_node_extract_status',
'id'
]
type SearchNodePacksParams = BaseSearchParamsWithoutQuery & {
pageSize: number
pageNumber: number
restrictSearchableAttributes: SearchAttribute[]
}
export const useAlgoliaSearchService = () => {
const searchClient = algoliasearch(__ALGOLIA_APP_ID__, __ALGOLIA_API_KEY__)
const toRegistryLatestVersion = (
algoliaNode: AlgoliaNodePack
): RegistryNodePack['latest_version'] => {
return {
version: algoliaNode.latest_version,
createdAt: algoliaNode.update_time,
status: algoliaNode.latest_version_status,
comfy_node_extract_status:
algoliaNode.comfy_node_extract_status ?? undefined
}
}
const toRegistryPublisher = (
algoliaNode: AlgoliaNodePack
): RegistryNodePack['publisher'] => {
return {
id: algoliaNode.publisher_id,
name: algoliaNode.publisher_id
}
}
/**
* Convert from node pack in Algolia format to Comfy Registry format
*/
function toRegistryPack(algoliaNode: AlgoliaNodePack): RegistryNodePack {
return {
id: algoliaNode.id ?? algoliaNode.objectID,
name: algoliaNode.name,
description: algoliaNode.description,
repository: algoliaNode.repository_url,
license: algoliaNode.license,
downloads: algoliaNode.total_install,
status: algoliaNode.status,
latest_version: toRegistryLatestVersion(algoliaNode),
publisher: toRegistryPublisher(algoliaNode)
}
}
/**
* Search for node packs in Algolia
*/
const searchPacks = async (
query: string,
params: SearchNodePacksParams
): Promise<Hit<AlgoliaNodePack>[]> => {
const { pageSize, pageNumber } = params
const rest = omit(params, ['pageSize', 'pageNumber'])
const { results } = await searchClient.search<AlgoliaNodePack>({
requests: [
{
query,
indexName: 'nodes_index',
attributesToRetrieve: RETRIEVE_ATTRIBUTES,
...rest,
hitsPerPage: pageSize,
length: pageSize,
page: pageNumber
}
],
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
}
return {
searchPacks,
toRegistryPack
}
}

19
src/types/algoliasearch-lite.d.ts vendored Normal file
View File

@@ -0,0 +1,19 @@
declare module 'algoliasearch/dist/lite/builds/browser' {
import { LiteClient, ClientOptions } from 'algoliasearch/dist/lite/browser'
/**
* Creates a new Algolia Search client that uses the Lite API Client (Browser version)
*
* @param appId - Your Algolia Application ID
* @param apiKey - Your Algolia API Key
* @param options - Options for the client
* @returns An Algolia Search client instance
*/
export function liteClient(
appId: string,
apiKey: string,
options?: ClientOptions
): LiteClient
export const apiClientVersion: string
}

View File

@@ -182,7 +182,9 @@ export default defineConfig({
__SENTRY_ENABLED__: JSON.stringify(
!(process.env.NODE_ENV === 'development' || !process.env.SENTRY_DSN)
),
__SENTRY_DSN__: JSON.stringify(process.env.SENTRY_DSN || '')
__SENTRY_DSN__: JSON.stringify(process.env.SENTRY_DSN || ''),
__ALGOLIA_APP_ID__: JSON.stringify(process.env.VITE_ALGOLIA_APP_ID || ''),
__ALGOLIA_API_KEY__: JSON.stringify(process.env.VITE_ALGOLIA_API_KEY || '')
},
resolve: {