From 699f3a792d3434e2b95cce1a7646d0ed62e2bf19 Mon Sep 17 00:00:00 2001 From: codytseng Date: Mon, 3 Feb 2025 16:17:14 +0800 Subject: [PATCH] feat: improve mention search results --- package-lock.json | 6 +++++ package.json | 1 + src/hooks/useSearchProfiles.tsx | 9 +------ src/providers/NostrProvider/index.tsx | 1 + src/services/client.service.ts | 38 +++++++++++++++++++++++++++ 5 files changed, 47 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index e5af65a..899bbfc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "dataloader": "^2.2.3", "dayjs": "^1.11.13", "embla-carousel-react": "^8.5.1", + "flexsearch": "^0.7.43", "i18next": "^24.2.0", "lru-cache": "^11.0.2", "lucide-react": "^0.469.0", @@ -5731,6 +5732,11 @@ "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", "dev": true }, + "node_modules/flexsearch": { + "version": "0.7.43", + "resolved": "https://registry.npmjs.org/flexsearch/-/flexsearch-0.7.43.tgz", + "integrity": "sha512-c5o/+Um8aqCSOXGcZoqZOm+NqtVwNsvVpWv6lfmSclU954O3wvQKxxK8zj74fPaSJbXpSLTs4PRhh+wnoCXnKg==" + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", diff --git a/package.json b/package.json index 9c1c681..7c1aee9 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "dataloader": "^2.2.3", "dayjs": "^1.11.13", "embla-carousel-react": "^8.5.1", + "flexsearch": "^0.7.43", "i18next": "^24.2.0", "lru-cache": "^11.0.2", "lucide-react": "^0.469.0", diff --git a/src/hooks/useSearchProfiles.tsx b/src/hooks/useSearchProfiles.tsx index c2b518e..382d10b 100644 --- a/src/hooks/useSearchProfiles.tsx +++ b/src/hooks/useSearchProfiles.tsx @@ -1,4 +1,3 @@ -import { SEARCHABLE_RELAY_URLS } from '@/constants' import { useFeed } from '@/providers/FeedProvider' import client from '@/services/client.service' import { TProfile } from '@/types' @@ -22,13 +21,7 @@ export function useSearchProfiles(search: string, limit: number) { setIsFetching(true) setProfiles([]) try { - const profiles = await client.fetchProfiles( - searchableRelayUrls.concat(SEARCHABLE_RELAY_URLS).slice(0, 4), - { - search, - limit - } - ) + const profiles = await client.searchProfilesFromIndex(search) if (profiles) { setProfiles(profiles) } diff --git a/src/providers/NostrProvider/index.tsx b/src/providers/NostrProvider/index.tsx index 414ef54..aa72442 100644 --- a/src/providers/NostrProvider/index.tsx +++ b/src/providers/NostrProvider/index.tsx @@ -152,6 +152,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) { setProfileEvent(profileEvent) setProfile(getProfileFromProfileEvent(profileEvent)) }) + client.initUserIndexFromFollowings(account.pubkey) }, [account]) const hasNostrLoginHash = () => { diff --git a/src/services/client.service.ts b/src/services/client.service.ts index 52df03f..522c4cd 100644 --- a/src/services/client.service.ts +++ b/src/services/client.service.ts @@ -5,6 +5,7 @@ import { extractPubkeysFromEventTags } from '@/lib/tag' import { TDraftEvent, TProfile, TRelayInfo, TRelayList } from '@/types' import { sha256 } from '@noble/hashes/sha2' import DataLoader from 'dataloader' +import FlexSearch from 'flexsearch' import { LRUCache } from 'lru-cache' import { EventTemplate, @@ -77,6 +78,10 @@ class ClientService extends EventTarget { fetchMethod: this._fetchFollowListEvent.bind(this) }) + private userIndex = new FlexSearch.Index({ + tokenize: 'full' + }) + constructor() { super() } @@ -499,6 +504,18 @@ class ClientService extends EventTarget { return readRelays } + async searchProfilesFromIndex(query: string) { + const result = await this.userIndex.searchAsync(query, { limit: 10 }) + return Promise.all(result.map((pubkey) => this.fetchProfile(pubkey as string))).then( + (profiles) => profiles.filter(Boolean) as TProfile[] + ) + } + + async initUserIndexFromFollowings(pubkey: string) { + const followings = await this.fetchFollowings(pubkey) + await this.profileEventDataloader.loadMany(followings) + } + private async fetchEventById(relayUrls: string[], id: string): Promise { const event = await this.fetchEventFromDefaultRelaysDataloader.load(id) if (event) { @@ -577,6 +594,7 @@ class ClientService extends EventTarget { const profileFromDefaultRelays = await this.fetchProfileEventFromDefaultRelaysDataloader.load(pubkey) if (profileFromDefaultRelays) { + this.addUsernameToIndex(profileFromDefaultRelays) return profileFromDefaultRelays } @@ -593,9 +611,29 @@ class ClientService extends EventTarget { this.profileEventDataloader.prime(pubkey, Promise.resolve(profileEvent)) } + if (profileEvent) { + this.addUsernameToIndex(profileEvent) + } + return profileEvent } + private addUsernameToIndex(profileEvent: NEvent) { + try { + const profileObj = JSON.parse(profileEvent.content) + const text = [ + profileObj.display_name?.trim() ?? '', + profileObj.name?.trim() ?? '', + profileObj.nip05?.split('@')[0]?.trim() ?? '' + ].join(' ') + if (!text) return + + this.userIndex.add(profileEvent.pubkey, text) + } catch { + return + } + } + private async tryHarderToFetchEvent( relayUrls: string[], filter: Filter,