feat: improve mention search results

This commit is contained in:
codytseng
2025-02-03 16:17:14 +08:00
parent faa7298f89
commit 699f3a792d
5 changed files with 47 additions and 8 deletions

6
package-lock.json generated
View File

@@ -31,6 +31,7 @@
"dataloader": "^2.2.3", "dataloader": "^2.2.3",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"embla-carousel-react": "^8.5.1", "embla-carousel-react": "^8.5.1",
"flexsearch": "^0.7.43",
"i18next": "^24.2.0", "i18next": "^24.2.0",
"lru-cache": "^11.0.2", "lru-cache": "^11.0.2",
"lucide-react": "^0.469.0", "lucide-react": "^0.469.0",
@@ -5731,6 +5732,11 @@
"integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
"dev": true "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": { "node_modules/for-each": {
"version": "0.3.3", "version": "0.3.3",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",

View File

@@ -41,6 +41,7 @@
"dataloader": "^2.2.3", "dataloader": "^2.2.3",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"embla-carousel-react": "^8.5.1", "embla-carousel-react": "^8.5.1",
"flexsearch": "^0.7.43",
"i18next": "^24.2.0", "i18next": "^24.2.0",
"lru-cache": "^11.0.2", "lru-cache": "^11.0.2",
"lucide-react": "^0.469.0", "lucide-react": "^0.469.0",

View File

@@ -1,4 +1,3 @@
import { SEARCHABLE_RELAY_URLS } from '@/constants'
import { useFeed } from '@/providers/FeedProvider' import { useFeed } from '@/providers/FeedProvider'
import client from '@/services/client.service' import client from '@/services/client.service'
import { TProfile } from '@/types' import { TProfile } from '@/types'
@@ -22,13 +21,7 @@ export function useSearchProfiles(search: string, limit: number) {
setIsFetching(true) setIsFetching(true)
setProfiles([]) setProfiles([])
try { try {
const profiles = await client.fetchProfiles( const profiles = await client.searchProfilesFromIndex(search)
searchableRelayUrls.concat(SEARCHABLE_RELAY_URLS).slice(0, 4),
{
search,
limit
}
)
if (profiles) { if (profiles) {
setProfiles(profiles) setProfiles(profiles)
} }

View File

@@ -152,6 +152,7 @@ export function NostrProvider({ children }: { children: React.ReactNode }) {
setProfileEvent(profileEvent) setProfileEvent(profileEvent)
setProfile(getProfileFromProfileEvent(profileEvent)) setProfile(getProfileFromProfileEvent(profileEvent))
}) })
client.initUserIndexFromFollowings(account.pubkey)
}, [account]) }, [account])
const hasNostrLoginHash = () => { const hasNostrLoginHash = () => {

View File

@@ -5,6 +5,7 @@ import { extractPubkeysFromEventTags } from '@/lib/tag'
import { TDraftEvent, TProfile, TRelayInfo, TRelayList } from '@/types' import { TDraftEvent, TProfile, TRelayInfo, TRelayList } from '@/types'
import { sha256 } from '@noble/hashes/sha2' import { sha256 } from '@noble/hashes/sha2'
import DataLoader from 'dataloader' import DataLoader from 'dataloader'
import FlexSearch from 'flexsearch'
import { LRUCache } from 'lru-cache' import { LRUCache } from 'lru-cache'
import { import {
EventTemplate, EventTemplate,
@@ -77,6 +78,10 @@ class ClientService extends EventTarget {
fetchMethod: this._fetchFollowListEvent.bind(this) fetchMethod: this._fetchFollowListEvent.bind(this)
}) })
private userIndex = new FlexSearch.Index({
tokenize: 'full'
})
constructor() { constructor() {
super() super()
} }
@@ -499,6 +504,18 @@ class ClientService extends EventTarget {
return readRelays 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<NEvent | undefined> { private async fetchEventById(relayUrls: string[], id: string): Promise<NEvent | undefined> {
const event = await this.fetchEventFromDefaultRelaysDataloader.load(id) const event = await this.fetchEventFromDefaultRelaysDataloader.load(id)
if (event) { if (event) {
@@ -577,6 +594,7 @@ class ClientService extends EventTarget {
const profileFromDefaultRelays = const profileFromDefaultRelays =
await this.fetchProfileEventFromDefaultRelaysDataloader.load(pubkey) await this.fetchProfileEventFromDefaultRelaysDataloader.load(pubkey)
if (profileFromDefaultRelays) { if (profileFromDefaultRelays) {
this.addUsernameToIndex(profileFromDefaultRelays)
return profileFromDefaultRelays return profileFromDefaultRelays
} }
@@ -593,9 +611,29 @@ class ClientService extends EventTarget {
this.profileEventDataloader.prime(pubkey, Promise.resolve(profileEvent)) this.profileEventDataloader.prime(pubkey, Promise.resolve(profileEvent))
} }
if (profileEvent) {
this.addUsernameToIndex(profileEvent)
}
return 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( private async tryHarderToFetchEvent(
relayUrls: string[], relayUrls: string[],
filter: Filter, filter: Filter,