From 11c605b5eff23870e41aea99d4e28e0e757bd5c2 Mon Sep 17 00:00:00 2001 From: silberengel Date: Wed, 6 Aug 2025 09:21:57 +0200 Subject: [PATCH] improved search some more --- src/lib/components/EventSearch.svelte | 132 ++++++++++++++++++++++++++ src/lib/utils/search_constants.ts | 7 +- src/lib/utils/subscription_search.ts | 50 +++++----- src/routes/events/+page.svelte | 12 ++- 4 files changed, 171 insertions(+), 30 deletions(-) diff --git a/src/lib/components/EventSearch.svelte b/src/lib/components/EventSearch.svelte index 10f888b..67dece6 100644 --- a/src/lib/components/EventSearch.svelte +++ b/src/lib/components/EventSearch.svelte @@ -488,6 +488,61 @@ searchType, searchTerm, }); + + // AI-NOTE: 2025-01-24 - Check cache first for profile searches to provide immediate response + if (searchType === "n") { + try { + const { getUserMetadata } = await import("$lib/utils/nostrUtils"); + const cachedProfile = await getUserMetadata(searchTerm, false); + if (cachedProfile && cachedProfile.name) { + console.log("EventSearch: Found cached profile, displaying immediately:", cachedProfile); + + // Create a mock NDKEvent for the cached profile + const { NDKEvent } = await import("@nostr-dev-kit/ndk"); + const { nip19 } = await import("$lib/utils/nostrUtils"); + + // Decode the npub to get the actual pubkey + let pubkey = searchTerm; + try { + const decoded = nip19.decode(searchTerm); + if (decoded && decoded.type === "npub") { + pubkey = decoded.data; + } + } catch (error) { + console.warn("EventSearch: Failed to decode npub for mock event:", error); + } + + const mockEvent = new NDKEvent(undefined, { + kind: 0, + pubkey: pubkey, + content: JSON.stringify(cachedProfile), + tags: [], + created_at: Math.floor(Date.now() / 1000), + id: "", // Will be computed by NDK + sig: "", // Will be computed by NDK + }); + + // Display the cached profile immediately + handleFoundEvent(mockEvent); + updateSearchState(false, true, 1, "profile-cached"); + + // AI-NOTE: 2025-01-24 - Still perform background search for second-order events + // but with better timeout handling to prevent hanging + setTimeout(async () => { + try { + await performBackgroundProfileSearch(searchType, searchTerm); + } catch (error) { + console.warn("EventSearch: Background profile search failed:", error); + } + }, 100); + + return; + } + } catch (error) { + console.warn("EventSearch: Cache check failed, proceeding with subscription search:", error); + } + } + isResetting = false; // Allow effects to run for new searches localError = null; updateSearchState(true); @@ -663,6 +718,83 @@ } } + // AI-NOTE: 2025-01-24 - Function to perform background profile search without blocking UI + async function performBackgroundProfileSearch( + searchType: "d" | "t" | "n", + searchTerm: string, + ) { + console.log("EventSearch: Performing background profile search:", { + searchType, + searchTerm, + }); + + try { + // Cancel existing search + if (currentAbortController) { + currentAbortController.abort(); + } + currentAbortController = new AbortController(); + + // AI-NOTE: 2025-01-24 - Add timeout to prevent hanging background searches + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => { + reject(new Error("Background search timeout")); + }, 10000); // 10 second timeout for background searches + }); + + const searchPromise = searchBySubscription( + searchType, + searchTerm, + { + onSecondOrderUpdate: (updatedResult) => { + console.log("EventSearch: Background second order update:", updatedResult); + // Only update if we have new results + if (updatedResult.events.length > 0) { + onSearchResults( + updatedResult.events, + updatedResult.secondOrder, + updatedResult.tTagEvents, + updatedResult.eventIds, + updatedResult.addresses, + updatedResult.searchType, + updatedResult.searchTerm, + ); + } + }, + onSubscriptionCreated: (sub) => { + console.log("EventSearch: Background subscription created:", sub); + if (activeSub) { + activeSub.stop(); + } + activeSub = sub; + }, + }, + currentAbortController.signal, + ); + + // Race between search and timeout + const result = await Promise.race([searchPromise, timeoutPromise]) as any; + + console.log("EventSearch: Background search completed:", result); + + // Only update results if we have new data + if (result.events.length > 0) { + onSearchResults( + result.events, + result.secondOrder, + result.tTagEvents, + result.eventIds, + result.addresses, + result.searchType, + result.searchTerm, + ); + } + } catch (error) { + console.warn("EventSearch: Background profile search failed:", error); + } + } + + // Search utility functions function handleClear() { isResetting = true; searchQuery = ""; diff --git a/src/lib/utils/search_constants.ts b/src/lib/utils/search_constants.ts index 663e985..cc6f677 100644 --- a/src/lib/utils/search_constants.ts +++ b/src/lib/utils/search_constants.ts @@ -17,7 +17,7 @@ export const TIMEOUTS = { SUBSCRIPTION_SEARCH: 10000, /** Timeout for second-order search operations */ - SECOND_ORDER_SEARCH: 5000, + SECOND_ORDER_SEARCH: 3000, // AI-NOTE: 2025-01-24 - Reduced timeout since we limit scope /** Timeout for relay diagnostics */ RELAY_DIAGNOSTICS: 5000, @@ -44,7 +44,10 @@ export const SEARCH_LIMITS = { SPECIFIC_PROFILE: 10, /** Limit for general profile searches */ - GENERAL_PROFILE: 500, + GENERAL_PROFILE: 100, // AI-NOTE: 2025-01-24 - Reduced from 500 to prevent wild searches + + /** Limit for general content searches (t-tag, d-tag, etc.) */ + GENERAL_CONTENT: 100, // AI-NOTE: 2025-01-24 - Added limit for all content searches /** Limit for community relay checks */ COMMUNITY_CHECK: 1, diff --git a/src/lib/utils/subscription_search.ts b/src/lib/utils/subscription_search.ts index d992f5b..d07067e 100644 --- a/src/lib/utils/subscription_search.ts +++ b/src/lib/utils/subscription_search.ts @@ -59,19 +59,11 @@ export async function searchBySubscription( const cachedResult = searchCache.get(searchType, normalizedSearchTerm); if (cachedResult) { console.log("subscription_search: Found cached result:", cachedResult); - // AI-NOTE: 2025-01-08 - For profile searches, clear cache if it's empty to force fresh search - if (searchType === "n" && cachedResult.events.length === 0) { - console.log("subscription_search: Clearing empty cached profile result to force fresh search"); - searchCache.clear(); // Clear all cache to force fresh search - } else if (searchType === "n" && cachedResult.events.length > 0 && cachedResult.secondOrder.length === 0) { - // AI-NOTE: 2025-01-08 - Clear cache if we have profile results but no second-order events - // This forces a fresh search that includes second-order searching - console.log("subscription_search: Clearing cached profile result with no second-order events to force fresh search"); - searchCache.clear(); - } else if (searchType === "n") { - // AI-NOTE: 2025-01-08 - For profile searches, always clear cache to ensure fresh second-order search - console.log("subscription_search: Clearing cache for profile search to ensure fresh second-order search"); - searchCache.clear(); + // AI-NOTE: 2025-01-24 - For profile searches, return cached results immediately + // The EventSearch component now handles cache checking before calling this function + if (searchType === "n") { + console.log("subscription_search: Returning cached profile result immediately"); + return cachedResult; } else { return cachedResult; } @@ -91,7 +83,7 @@ export async function searchBySubscription( searchState.timeoutId = setTimeout(() => { console.log("subscription_search: Search timeout reached"); cleanup(); - }, searchType === "n" ? 5000 : TIMEOUTS.SUBSCRIPTION_SEARCH); // AI-NOTE: 2025-01-08 - Shorter timeout for profile searches + }, TIMEOUTS.SUBSCRIPTION_SEARCH); // AI-NOTE: 2025-01-24 - Use standard timeout since cache is checked first // Check for abort signal if (abortSignal?.aborted) { @@ -332,7 +324,7 @@ async function createSearchFilter( switch (searchType) { case "d": { const dFilter = { - filter: { "#d": [normalizedSearchTerm] }, + filter: { "#d": [normalizedSearchTerm], limit: SEARCH_LIMITS.GENERAL_CONTENT }, subscriptionType: "d-tag", }; console.log("subscription_search: Created d-tag filter:", dFilter); @@ -340,7 +332,7 @@ async function createSearchFilter( } case "t": { const tFilter = { - filter: { "#t": [normalizedSearchTerm] }, + filter: { "#t": [normalizedSearchTerm], limit: SEARCH_LIMITS.GENERAL_CONTENT }, subscriptionType: "t-tag", }; console.log("subscription_search: Created t-tag filter:", tFilter); @@ -929,21 +921,33 @@ async function performSecondOrderSearchInBackground( if (searchType === "n" && targetPubkey) { console.log("subscription_search: Searching for events mentioning pubkey:", targetPubkey); + // AI-NOTE: 2025-01-24 - Use only active relays for second-order profile search to prevent hanging + const activeRelays = [...get(activeInboxRelays), ...get(activeOutboxRelays)]; + const availableRelays = activeRelays + .map(url => ndk.pool.relays.get(url)) + .filter((relay): relay is any => relay !== undefined); + const relaySet = new NDKRelaySet( + new Set(availableRelays), + ndk + ); + + console.log("subscription_search: Using", activeRelays.length, "active relays for second-order search"); + // Search for events that mention this pubkey via p-tags - const pTagFilter = { "#p": [targetPubkey] }; + const pTagFilter = { "#p": [targetPubkey], limit: 50 }; // AI-NOTE: 2025-01-24 - Limit results to prevent hanging const pTagEvents = await ndk.fetchEvents( pTagFilter, { closeOnEose: true }, - new NDKRelaySet(new Set(Array.from(ndk.pool.relays.values())), ndk), + relaySet, ); console.log("subscription_search: Found", pTagEvents.size, "events with p-tag for", targetPubkey); - // AI-NOTE: 2025-01-08 - Also search for events written by this pubkey - const authorFilter = { authors: [targetPubkey] }; + // AI-NOTE: 2025-01-24 - Also search for events written by this pubkey with limit + const authorFilter = { authors: [targetPubkey], limit: 50 }; // AI-NOTE: 2025-01-24 - Limit results to prevent hanging const authorEvents = await ndk.fetchEvents( authorFilter, { closeOnEose: true }, - new NDKRelaySet(new Set(Array.from(ndk.pool.relays.values())), ndk), + relaySet, ); console.log("subscription_search: Found", authorEvents.size, "events written by", targetPubkey); @@ -964,14 +968,14 @@ async function performSecondOrderSearchInBackground( const [eTagEvents, aTagEvents] = await Promise.all([ eventIds.size > 0 ? ndk.fetchEvents( - { "#e": Array.from(eventIds) }, + { "#e": Array.from(eventIds), limit: SEARCH_LIMITS.SECOND_ORDER_RESULTS }, { closeOnEose: true }, relaySet, ) : Promise.resolve([]), addresses.size > 0 ? ndk.fetchEvents( - { "#a": Array.from(addresses) }, + { "#a": Array.from(addresses), limit: SEARCH_LIMITS.SECOND_ORDER_RESULTS }, { closeOnEose: true }, relaySet, ) diff --git a/src/routes/events/+page.svelte b/src/routes/events/+page.svelte index 389b8bd..f37d5a8 100644 --- a/src/routes/events/+page.svelte +++ b/src/routes/events/+page.svelte @@ -453,15 +453,17 @@ import CommentViewer from "$lib/components/CommentViewer.svelte"; {#if searchResults.length > 0}
- + {#if searchType === "n"} - Search Results for name: "{searchTerm}" ({searchResults.length} profiles) + Search Results for name: "{searchTerm && searchTerm.length > 50 ? searchTerm.slice(0, 50) + '...' : searchTerm || ''}" ({searchResults.length} profiles) {:else if searchType === "t"} - Search Results for t-tag: "{searchTerm}" ({searchResults.length} + Search Results for t-tag: "{searchTerm && searchTerm.length > 50 ? searchTerm.slice(0, 50) + '...' : searchTerm || ''}" ({searchResults.length} events) {:else} - Search Results for d-tag: "{searchTerm || - dTagValue?.toLowerCase()}" ({searchResults.length} events) + Search Results for d-tag: "{(() => { + const term = searchTerm || dTagValue?.toLowerCase() || ''; + return term.length > 50 ? term.slice(0, 50) + '...' : term; + })()}" ({searchResults.length} events) {/if}