Browse Source

improved the not stats accuracy

imwald
Silberengel 5 months ago
parent
commit
a4fe9a5d53
  1. 4
      src/components/NoteStats/index.tsx
  2. 5
      src/components/QuoteList/index.tsx
  3. 78
      src/components/TrendingNotes/index.tsx
  4. 53
      src/services/note-stats.service.ts

4
src/components/NoteStats/index.tsx

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
import { cn } from '@/lib/utils'
import { useNostr } from '@/providers/NostrProvider'
import { useScreenSize } from '@/providers/ScreenSizeProvider'
import { useFavoriteRelays } from '@/providers/FavoriteRelaysProvider'
import noteStatsService from '@/services/note-stats.service'
import { ExtendedKind } from '@/constants'
import { getRootEventHexId } from '@/lib/event'
@ -33,6 +34,7 @@ export default function NoteStats({ @@ -33,6 +34,7 @@ export default function NoteStats({
}) {
const { isSmallScreen } = useScreenSize()
const { pubkey } = useNostr()
const { favoriteRelays } = useFavoriteRelays()
const [loading, setLoading] = useState(false)
// Hide repost button for discussion events and replies to discussions
@ -59,7 +61,7 @@ export default function NoteStats({ @@ -59,7 +61,7 @@ export default function NoteStats({
useEffect(() => {
if (!fetchIfNotExisting) return
setLoading(true)
noteStatsService.fetchNoteStats(event, pubkey).finally(() => setLoading(false))
noteStatsService.fetchNoteStats(event, pubkey, favoriteRelays).finally(() => setLoading(false))
}, [event, fetchIfNotExisting])
if (isSmallScreen) {

5
src/components/QuoteList/index.tsx

@ -15,7 +15,7 @@ const SHOW_COUNT = 10 @@ -15,7 +15,7 @@ const SHOW_COUNT = 10
export default function QuoteList({ event, className }: { event: Event; className?: string }) {
const { t } = useTranslation()
const { startLogin, relayList: userRelayList } = useNostr()
const { relayList: userRelayList } = useNostr()
const { hideUntrustedInteractions, isUserTrusted } = useUserTrust()
const [timelineKey, setTimelineKey] = useState<string | undefined>(undefined)
const [events, setEvents] = useState<Event[]>([])
@ -90,8 +90,7 @@ export default function QuoteList({ event, className }: { event: Event; classNam @@ -90,8 +90,7 @@ export default function QuoteList({ event, className }: { event: Event; classNam
[event, ...oldEvents].sort((a, b) => b.created_at - a.created_at)
)
}
},
{ startLogin }
}
)
setTimelineKey(timelineKey)
return closer

78
src/components/TrendingNotes/index.tsx

@ -49,8 +49,19 @@ export default function TrendingNotes() { @@ -49,8 +49,19 @@ export default function TrendingNotes() {
const [selectedHashtag, setSelectedHashtag] = useState<string | null>(null)
const [popularHashtags, setPopularHashtags] = useState<string[]>([])
const [cacheEvents, setCacheEvents] = useState<NostrEvent[]>([])
const [cacheLoading, setCacheLoading] = useState(false)
const bottomRef = useRef<HTMLDivElement>(null)
// Debug: Track cacheEvents changes
useEffect(() => {
console.log('[TrendingNotes] cacheEvents state changed:', cacheEvents.length, 'events')
}, [cacheEvents])
// Debug: Track cacheLoading changes
useEffect(() => {
console.log('[TrendingNotes] cacheLoading state changed:', cacheLoading)
}, [cacheLoading])
// Extract event IDs from bookmark and pin lists (kinds 10003 and 10001)
const listEventIds = useMemo(() => {
@ -228,16 +239,29 @@ export default function TrendingNotes() { @@ -228,16 +239,29 @@ export default function TrendingNotes() {
if (cachedCustomEvents && (now - cachedCustomEvents.timestamp) < CACHE_DURATION) {
// If cache is valid, set cacheEvents to ALL events from cache
const allEvents = cachedCustomEvents.events.map(item => item.event)
console.log('[TrendingNotes] Using existing cache - loading', allEvents.length, 'events')
setCacheEvents(allEvents)
setCacheLoading(false) // Ensure loading state is cleared
return
}
isInitializing = true
setCacheLoading(true)
const relays = getRelays // This is already a value from useMemo
// Set a timeout to prevent infinite loading
const timeoutId = setTimeout(() => {
console.log('[TrendingNotes] Cache initialization timeout - forcing completion')
isInitializing = false
setCacheLoading(false)
}, 180000) // 3 minute timeout
// Prevent running if we have no relays
if (relays.length === 0) {
console.log('[TrendingNotes] No relays available, skipping cache initialization')
clearTimeout(timeoutId)
isInitializing = false
setCacheLoading(false)
return
}
@ -245,12 +269,15 @@ export default function TrendingNotes() { @@ -245,12 +269,15 @@ export default function TrendingNotes() {
const allEvents: NostrEvent[] = []
const twentyFourHoursAgo = Math.floor(Date.now() / 1000) - 24 * 60 * 60
console.log('[TrendingNotes] Starting cache initialization with', relays.length, 'relays:', relays)
// 1. Fetch top-level posts from last 24 hours - batch requests to avoid overwhelming relays
const batchSize = 3 // Process 3 relays at a time
const recentEvents: NostrEvent[] = []
for (let i = 0; i < relays.length; i += batchSize) {
const batch = relays.slice(i, i + batchSize)
console.log('[TrendingNotes] Processing batch', Math.floor(i/batchSize) + 1, 'of', Math.ceil(relays.length/batchSize), 'relays:', batch)
const batchPromises = batch.map(async (relay) => {
try {
const events = await client.fetchEvents([relay], {
@ -258,6 +285,7 @@ export default function TrendingNotes() { @@ -258,6 +285,7 @@ export default function TrendingNotes() {
since: twentyFourHoursAgo,
limit: 500
})
console.log('[TrendingNotes] Fetched', events.length, 'events from relay', relay)
return events
} catch (error) {
console.warn(`[TrendingNotes] Error fetching from relay ${relay}:`, error)
@ -266,7 +294,9 @@ export default function TrendingNotes() { @@ -266,7 +294,9 @@ export default function TrendingNotes() {
})
const batchResults = await Promise.all(batchPromises)
recentEvents.push(...batchResults.flat())
const batchEvents = batchResults.flat()
recentEvents.push(...batchEvents)
console.log('[TrendingNotes] Batch completed, total events so far:', recentEvents.length)
// Add a small delay between batches to be respectful to relays
if (i + batchSize < relays.length) {
@ -358,21 +388,31 @@ export default function TrendingNotes() { @@ -358,21 +388,31 @@ export default function TrendingNotes() {
// Fetch stats for events in batches with longer delays
const eventsNeedingStats = filteredEvents.filter(event => !noteStatsService.getNoteStats(event.id))
console.log('[TrendingNotes] Need to fetch stats for', eventsNeedingStats.length, 'events')
if (eventsNeedingStats.length > 0) {
const batchSize = 5 // Reduced batch size
const batchSize = 10 // Increased batch size to speed up
const totalBatches = Math.ceil(eventsNeedingStats.length / batchSize)
console.log('[TrendingNotes] Fetching stats in', totalBatches, 'batches')
for (let i = 0; i < eventsNeedingStats.length; i += batchSize) {
const batch = eventsNeedingStats.slice(i, i + batchSize)
const batchNum = Math.floor(i / batchSize) + 1
console.log('[TrendingNotes] Fetching stats batch', batchNum, 'of', totalBatches)
await Promise.all(batch.map(event =>
noteStatsService.fetchNoteStats(event, undefined).catch(() => {})
noteStatsService.fetchNoteStats(event, undefined, favoriteRelays).catch(() => {})
))
if (i + batchSize < eventsNeedingStats.length) {
await new Promise(resolve => setTimeout(resolve, 500)) // Increased delay
await new Promise(resolve => setTimeout(resolve, 200)) // Reduced delay
}
}
console.log('[TrendingNotes] Stats fetching completed')
}
// Score events
console.log('[TrendingNotes] Scoring', filteredEvents.length, 'events')
const scoredEvents = filteredEvents.map((event) => {
const stats = noteStatsService.getNoteStats(event.id)
let score = 0
@ -398,6 +438,7 @@ export default function TrendingNotes() { @@ -398,6 +438,7 @@ export default function TrendingNotes() {
})
// Update cache
console.log('[TrendingNotes] Updating cache with', scoredEvents.length, 'scored events')
cachedCustomEvents = {
events: scoredEvents,
timestamp: now,
@ -407,11 +448,14 @@ export default function TrendingNotes() { @@ -407,11 +448,14 @@ export default function TrendingNotes() {
// Store ALL events from the cache for hashtag analysis
// This includes all events from relays, not just the trending ones
console.log('[TrendingNotes] Cache initialization complete - storing', filteredEvents.length, 'events')
setCacheEvents(filteredEvents)
} catch (error) {
console.error('[TrendingNotes] Error initializing cache:', error)
} finally {
clearTimeout(timeoutId)
isInitializing = false
setCacheLoading(false)
}
}
@ -431,6 +475,7 @@ export default function TrendingNotes() { @@ -431,6 +475,7 @@ export default function TrendingNotes() {
} else if (activeTab === 'relays') {
// "on your relays" tab: use cache events from user's relays
sourceEvents = cacheEvents
console.log('[TrendingNotes] Relays tab - cacheEvents.length:', cacheEvents.length, 'cacheLoading:', cacheLoading)
} else if (activeTab === 'hashtags') {
// Hashtags tab: use cache events for hashtag analysis
sourceEvents = cacheEvents.length > 0 ? cacheEvents : trendingNotes
@ -584,11 +629,15 @@ export default function TrendingNotes() { @@ -584,11 +629,15 @@ export default function TrendingNotes() {
setSortOrder('most-popular')
} else if (activeTab === 'relays') {
setSortOrder('most-popular')
// If cache is empty and not loading, log the issue for debugging
if (cacheEvents.length === 0 && !cacheLoading && !isInitializing) {
console.log('[TrendingNotes] Relays tab selected but cache is empty - this should not happen if cache initialization completed')
}
} else if (activeTab === 'hashtags') {
setSortOrder('most-popular')
setSelectedHashtag(null)
}
}, [activeTab, pubkey])
}, [activeTab, pubkey, cacheEvents.length, cacheLoading])
// Handle case where bookmarks tab is not available
useEffect(() => {
@ -747,6 +796,14 @@ export default function TrendingNotes() { @@ -747,6 +796,14 @@ export default function TrendingNotes() {
</div>
)}
</div>
{/* Show loading message for relays tab when cache is loading */}
{activeTab === 'relays' && cacheLoading && cacheEvents.length === 0 && (
<div className="text-center text-sm text-muted-foreground mt-8">
Loading trending notes from your relays...
</div>
)}
{filteredEvents.map((event) => (
<NoteCard key={event.id} className="w-full" event={event} />
))}
@ -756,7 +813,16 @@ export default function TrendingNotes() { @@ -756,7 +813,16 @@ export default function TrendingNotes() {
activeTab === 'relays' || activeTab === 'hashtags' ? cacheEvents.length :
trendingNotes.length
if (showCount < currentDataLength || loading) {
// Show loading if:
// 1. General loading state is true
// 2. For relays/hashtags tabs, if cache is loading
// 3. If we haven't reached the end of available data
const shouldShowLoading = loading ||
(activeTab === 'relays' && cacheLoading) ||
(activeTab === 'hashtags' && cacheLoading) ||
showCount < currentDataLength
if (shouldShowLoading) {
return (
<div ref={bottomRef}>
<NoteCardLoadingSkeleton />

53
src/services/note-stats.service.ts

@ -1,7 +1,8 @@ @@ -1,7 +1,8 @@
import { BIG_RELAY_URLS, ExtendedKind } from '@/constants'
import { BIG_RELAY_URLS, ExtendedKind, SEARCHABLE_RELAY_URLS } from '@/constants'
import { getReplaceableCoordinateFromEvent, isReplaceableEvent } from '@/lib/event'
import { getZapInfoFromEvent } from '@/lib/event-metadata'
import { getEmojiInfosFromEmojiTags, tagNameEquals } from '@/lib/tag'
import { normalizeUrl } from '@/lib/url'
import client from '@/services/client.service'
import { TEmoji } from '@/types'
import dayjs from 'dayjs'
@ -35,7 +36,7 @@ class NoteStatsService { @@ -35,7 +36,7 @@ class NoteStatsService {
return NoteStatsService.instance
}
async fetchNoteStats(event: Event, pubkey?: string | null) {
async fetchNoteStats(event: Event, pubkey?: string | null, favoriteRelays?: string[]) {
const oldStats = this.noteStatsMap.get(event.id)
let since: number | undefined
if (oldStats?.updatedAt) {
@ -47,6 +48,26 @@ class NoteStatsService { @@ -47,6 +48,26 @@ class NoteStatsService {
client.fetchProfile(event.pubkey)
])
// Build comprehensive relay list: user's inboxes + user's favorite relays + big relays
// For anonymous users, also include fast read relays for better coverage
const allRelays = [
...(relayList.read || []), // User's inboxes (kind 10002)
...(favoriteRelays || []), // User's favorite relays (kind 10012)
...BIG_RELAY_URLS, // Big relays
...(pubkey ? [] : SEARCHABLE_RELAY_URLS) // Fast read relays for anonymous users only
]
// Normalize and deduplicate relay URLs
const normalizedRelays = allRelays
.map(url => normalizeUrl(url))
.filter((url): url is string => !!url)
const finalRelayUrls = Array.from(new Set(normalizedRelays))
const relayTypes = pubkey
? 'inboxes kind 10002 + favorites kind 10012 + big relays'
: 'big relays + searchable relays (anonymous user)'
console.log('[NoteStats] Using', finalRelayUrls.length, 'relays for stats (' + relayTypes + '):', finalRelayUrls)
const replaceableCoordinate = isReplaceableEvent(event.kind)
? getReplaceableCoordinateFromEvent(event)
: undefined
@ -163,12 +184,21 @@ class NoteStatsService { @@ -163,12 +184,21 @@ class NoteStatsService {
})
}
const events: Event[] = []
await client.fetchEvents([...relayList.read, ...BIG_RELAY_URLS].slice(0, 5), filters, {
console.log('[NoteStats] Fetching stats for event', event.id, 'from', finalRelayUrls.length, 'relays')
await client.fetchEvents(finalRelayUrls, filters, {
onevent: (evt) => {
this.updateNoteStatsByEvents([evt])
events.push(evt)
}
})
console.log('[NoteStats] Fetched', events.length, 'events for stats')
// Debug: Count events by kind
const eventsByKind = events.reduce((acc, evt) => {
acc[evt.kind] = (acc[evt.kind] || 0) + 1
return acc
}, {} as Record<number, number>)
console.log('[NoteStats] Events by kind:', eventsByKind)
this.noteStatsMap.set(event.id, {
...(this.noteStatsMap.get(event.id) ?? {}),
updatedAt: dayjs().unix()
@ -345,6 +375,7 @@ class NoteStatsService { @@ -345,6 +375,7 @@ class NoteStatsService {
})
if (parentETag) {
originalEventId = parentETag[1]
console.log('[NoteStats] Found reply with root/reply marker:', evt.id, '->', originalEventId)
} else {
// Look for the last E tag that's not a mention
const embeddedEventIds = this.getEmbeddedNoteBech32Ids(evt)
@ -355,17 +386,26 @@ class NoteStatsService { @@ -355,17 +386,26 @@ class NoteStatsService {
marker !== 'mention' &&
!embeddedEventIds.includes(tagValue)
)
originalEventId = lastETag?.[1]
if (lastETag) {
originalEventId = lastETag[1]
console.log('[NoteStats] Found reply with last E tag:', evt.id, '->', originalEventId)
}
}
// Also check for parent A tag
if (!originalEventId) {
const aTag = evt.tags.find(tagNameEquals('a'))
originalEventId = aTag?.[1]
if (aTag) {
originalEventId = aTag[1]
console.log('[NoteStats] Found reply with A tag:', evt.id, '->', originalEventId)
}
}
}
if (!originalEventId) return
if (!originalEventId) {
console.log('[NoteStats] No original event ID found for potential reply:', evt.id, 'tags:', evt.tags)
return
}
const old = this.noteStatsMap.get(originalEventId) || {}
const replyIdSet = old.replyIdSet || new Set()
@ -376,6 +416,7 @@ class NoteStatsService { @@ -376,6 +416,7 @@ class NoteStatsService {
replyIdSet.add(evt.id)
replies.push({ id: evt.id, pubkey: evt.pubkey, created_at: evt.created_at })
this.noteStatsMap.set(originalEventId, { ...old, replyIdSet, replies })
console.log('[NoteStats] Added reply:', evt.id, 'to event:', originalEventId, 'total replies:', replies.length)
return originalEventId
}

Loading…
Cancel
Save