[D1X] Use user action and viewing history to inform suggested follows (#4727)
* Use user action and viewing history to inform suggested follows * Remove dynamic spreads * Track more info about seen posts * Add ranking --------- Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
This commit is contained in:
parent
1c6bfc02fb
commit
3407206f52
6 changed files with 196 additions and 49 deletions
|
@ -17,11 +17,13 @@ import {
|
|||
|
||||
import {HomeFeedAPI} from '#/lib/api/feed/home'
|
||||
import {aggregateUserInterests} from '#/lib/api/feed/utils'
|
||||
import {DISCOVER_FEED_URI} from '#/lib/constants'
|
||||
import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
|
||||
import {logger} from '#/logger'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {DEFAULT_LOGGED_OUT_PREFERENCES} from '#/state/queries/preferences/const'
|
||||
import {useAgent} from '#/state/session'
|
||||
import * as userActionHistory from '#/state/userActionHistory'
|
||||
import {AuthorFeedAPI} from 'lib/api/feed/author'
|
||||
import {CustomFeedAPI} from 'lib/api/feed/custom'
|
||||
import {FollowingFeedAPI} from 'lib/api/feed/following'
|
||||
|
@ -131,6 +133,7 @@ export function usePostFeedQuery(
|
|||
result: InfiniteData<FeedPage>
|
||||
} | null>(null)
|
||||
const lastPageCountRef = useRef(0)
|
||||
const isDiscover = feedDesc.includes(DISCOVER_FEED_URI)
|
||||
|
||||
// Make sure this doesn't invalidate unless really needed.
|
||||
const selectArgs = React.useMemo(
|
||||
|
@ -139,8 +142,15 @@ export function usePostFeedQuery(
|
|||
disableTuner: params?.disableTuner,
|
||||
moderationOpts,
|
||||
ignoreFilterFor: opts?.ignoreFilterFor,
|
||||
isDiscover,
|
||||
}),
|
||||
[feedTuners, params?.disableTuner, moderationOpts, opts?.ignoreFilterFor],
|
||||
[
|
||||
feedTuners,
|
||||
params?.disableTuner,
|
||||
moderationOpts,
|
||||
opts?.ignoreFilterFor,
|
||||
isDiscover,
|
||||
],
|
||||
)
|
||||
|
||||
const query = useInfiniteQuery<
|
||||
|
@ -219,8 +229,13 @@ export function usePostFeedQuery(
|
|||
(data: InfiniteData<FeedPageUnselected, RQPageParam>) => {
|
||||
// If the selection depends on some data, that data should
|
||||
// be included in the selectArgs object and read here.
|
||||
const {feedTuners, disableTuner, moderationOpts, ignoreFilterFor} =
|
||||
selectArgs
|
||||
const {
|
||||
feedTuners,
|
||||
disableTuner,
|
||||
moderationOpts,
|
||||
ignoreFilterFor,
|
||||
isDiscover,
|
||||
} = selectArgs
|
||||
|
||||
const tuner = disableTuner
|
||||
? new NoopFeedTuner()
|
||||
|
@ -293,6 +308,21 @@ export function usePostFeedQuery(
|
|||
}
|
||||
}
|
||||
|
||||
if (isDiscover) {
|
||||
userActionHistory.seen(
|
||||
slice.items.map(item => ({
|
||||
feedContext: item.feedContext,
|
||||
likeCount: item.post.likeCount ?? 0,
|
||||
repostCount: item.post.repostCount ?? 0,
|
||||
replyCount: item.post.replyCount ?? 0,
|
||||
isFollowedBy: Boolean(
|
||||
item.post.author.viewer?.followedBy,
|
||||
),
|
||||
uri: item.post.uri,
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
_reactKey: slice._reactKey,
|
||||
_isFeedPostSlice: true,
|
||||
|
|
|
@ -8,6 +8,7 @@ import {logEvent, LogEvents, toClout} from '#/lib/statsig/statsig'
|
|||
import {updatePostShadow} from '#/state/cache/post-shadow'
|
||||
import {Shadow} from '#/state/cache/types'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import * as userActionHistory from '#/state/userActionHistory'
|
||||
import {useIsThreadMuted, useSetThreadMute} from '../cache/thread-mutes'
|
||||
import {findProfileQueryData} from './profile'
|
||||
|
||||
|
@ -92,6 +93,7 @@ export function usePostLikeMutationQueue(
|
|||
uri: postUri,
|
||||
cid: postCid,
|
||||
})
|
||||
userActionHistory.like([postUri])
|
||||
return likeUri
|
||||
} else {
|
||||
if (prevLikeUri) {
|
||||
|
@ -99,6 +101,7 @@ export function usePostLikeMutationQueue(
|
|||
postUri: postUri,
|
||||
likeUri: prevLikeUri,
|
||||
})
|
||||
userActionHistory.unlike([postUri])
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import {logEvent, LogEvents, toClout} from '#/lib/statsig/statsig'
|
|||
import {Shadow} from '#/state/cache/types'
|
||||
import {STALE} from '#/state/queries'
|
||||
import {resetProfilePostsQueries} from '#/state/queries/post-feed'
|
||||
import * as userActionHistory from '#/state/userActionHistory'
|
||||
import {updateProfileShadow} from '../cache/profile-shadow'
|
||||
import {useAgent, useSession} from '../session'
|
||||
import {
|
||||
|
@ -233,6 +234,7 @@ export function useProfileFollowMutationQueue(
|
|||
const {uri} = await followMutation.mutateAsync({
|
||||
did,
|
||||
})
|
||||
userActionHistory.follow([did])
|
||||
return uri
|
||||
} else {
|
||||
if (prevFollowingUri) {
|
||||
|
@ -240,6 +242,7 @@ export function useProfileFollowMutationQueue(
|
|||
did,
|
||||
followUri: prevFollowingUri,
|
||||
})
|
||||
userActionHistory.unfollow([did])
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
|
71
src/state/userActionHistory.ts
Normal file
71
src/state/userActionHistory.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
import React from 'react'
|
||||
|
||||
const LIKE_WINDOW = 100
|
||||
const FOLLOW_WINDOW = 100
|
||||
const SEEN_WINDOW = 100
|
||||
|
||||
export type SeenPost = {
|
||||
uri: string
|
||||
likeCount: number
|
||||
repostCount: number
|
||||
replyCount: number
|
||||
isFollowedBy: boolean
|
||||
feedContext: string | undefined
|
||||
}
|
||||
|
||||
export type UserActionHistory = {
|
||||
/**
|
||||
* The last 100 post URIs the user has liked
|
||||
*/
|
||||
likes: string[]
|
||||
/**
|
||||
* The last 100 DIDs the user has followed
|
||||
*/
|
||||
follows: string[]
|
||||
/**
|
||||
* The last 100 post URIs the user has seen from the Discover feed only
|
||||
*/
|
||||
seen: SeenPost[]
|
||||
}
|
||||
|
||||
const userActionHistory: UserActionHistory = {
|
||||
likes: [],
|
||||
follows: [],
|
||||
seen: [],
|
||||
}
|
||||
|
||||
export function getActionHistory() {
|
||||
return userActionHistory
|
||||
}
|
||||
|
||||
export function useActionHistorySnapshot() {
|
||||
return React.useState(() => getActionHistory())[0]
|
||||
}
|
||||
|
||||
export function like(postUris: string[]) {
|
||||
userActionHistory.likes = userActionHistory.likes
|
||||
.concat(postUris)
|
||||
.slice(-LIKE_WINDOW)
|
||||
}
|
||||
export function unlike(postUris: string[]) {
|
||||
userActionHistory.likes = userActionHistory.likes.filter(
|
||||
uri => !postUris.includes(uri),
|
||||
)
|
||||
}
|
||||
|
||||
export function follow(dids: string[]) {
|
||||
userActionHistory.follows = userActionHistory.follows
|
||||
.concat(dids)
|
||||
.slice(-FOLLOW_WINDOW)
|
||||
}
|
||||
export function unfollow(dids: string[]) {
|
||||
userActionHistory.follows = userActionHistory.follows.filter(
|
||||
uri => !dids.includes(uri),
|
||||
)
|
||||
}
|
||||
|
||||
export function seen(posts: SeenPost[]) {
|
||||
userActionHistory.seen = userActionHistory.seen
|
||||
.concat(posts)
|
||||
.slice(-SEEN_WINDOW)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue