Files
silo-server/internal/sections/recipes/personalized.go
T

118 lines
4.2 KiB
Go

package recipes
import (
"encoding/json"
"time"
)
type forYouRecipe struct{}
func (forYouRecipe) Type() string { return "recommended_for_you" }
func (forYouRecipe) NewParams() any { return &struct{}{} }
func (forYouRecipe) Validate(_ json.RawMessage) error { return nil }
func (forYouRecipe) DefaultCacheTTL() time.Duration { return time.Hour }
func (forYouRecipe) Resolve(rc ResolverContext) (ResolvedItems, error) {
return delegateResolve("recommended_for_you", rc)
}
func (forYouRecipe) Definition() RecipeDefinition {
return RecipeDefinition{
Type: "recommended_for_you",
Category: CategoryPersonalized,
AvoidDuplicates: true,
Presets: []GalleryPreset{
{Key: "for_you", DisplayName: "Recommended For You", Icon: "⭐", DescriptionShort: "Per-profile picks from the recommendation engine.", DefaultParams: json.RawMessage(`{}`)},
},
}
}
// BecauseYouWatchedParams is the typed param shape for because_you_watched.
type BecauseYouWatchedParams struct {
// AnchorItemID is the media item this row is anchored to. Empty = auto-pick the latest watched.
AnchorItemID string `json:"anchor_item_id"`
}
type becauseRecipe struct{}
func (becauseRecipe) Type() string { return "because_you_watched" }
func (becauseRecipe) NewParams() any { return &BecauseYouWatchedParams{} }
func (becauseRecipe) DefaultCacheTTL() time.Duration { return time.Hour }
func (becauseRecipe) Resolve(rc ResolverContext) (ResolvedItems, error) {
return delegateResolve("because_you_watched", rc)
}
func (becauseRecipe) Validate(raw json.RawMessage) error {
if len(raw) == 0 {
return nil
}
var p BecauseYouWatchedParams
return json.Unmarshal(raw, &p)
}
func (becauseRecipe) Definition() RecipeDefinition {
return RecipeDefinition{
Type: "because_you_watched",
Category: CategoryPersonalized,
AvoidDuplicates: true,
SupportsRotation: true, // auto-pick anchor rotates as profile completes new items
Presets: []GalleryPreset{
{Key: "bcw_auto", DisplayName: "Because You Watched", Icon: "📺", DescriptionShort: "Picks based on your most recent watch.", DefaultParams: json.RawMessage(`{"anchor_item_id":""}`)},
},
}
}
type similarUsersRecipe struct{}
func (similarUsersRecipe) Type() string { return "similar_users_liked" }
func (similarUsersRecipe) NewParams() any { return &struct{}{} }
func (similarUsersRecipe) Validate(_ json.RawMessage) error { return nil }
func (similarUsersRecipe) DefaultCacheTTL() time.Duration { return time.Hour }
func (similarUsersRecipe) Resolve(rc ResolverContext) (ResolvedItems, error) {
return delegateResolve("similar_users_liked", rc)
}
func (similarUsersRecipe) Definition() RecipeDefinition {
return RecipeDefinition{
Type: "similar_users_liked",
Category: CategoryPersonalized,
AvoidDuplicates: true,
Presets: []GalleryPreset{
{Key: "similar", DisplayName: "Profiles Like You Enjoyed", Icon: "👥", DescriptionShort: "What similar profiles are loving.", DefaultParams: json.RawMessage(`{}`)},
},
}
}
// TasteMatchParams optionally narrows by genre.
type TasteMatchParams struct {
Genre string `json:"genre"`
}
type tasteMatchRecipe struct{}
func (tasteMatchRecipe) Type() string { return "taste_match" }
func (tasteMatchRecipe) NewParams() any { return &TasteMatchParams{} }
func (tasteMatchRecipe) DefaultCacheTTL() time.Duration { return time.Hour }
func (tasteMatchRecipe) Resolve(rc ResolverContext) (ResolvedItems, error) {
return delegateResolve("taste_match", rc)
}
func (tasteMatchRecipe) Validate(raw json.RawMessage) error {
if len(raw) == 0 {
return nil
}
var p TasteMatchParams
return json.Unmarshal(raw, &p)
}
func (tasteMatchRecipe) Definition() RecipeDefinition {
return RecipeDefinition{
Type: "taste_match",
Category: CategoryPersonalized,
AvoidDuplicates: true,
Presets: []GalleryPreset{
{Key: "taste_top", DisplayName: "Top Picks Today", Icon: "🎯", DescriptionShort: "Today's best matches for your taste.", DefaultParams: json.RawMessage(`{}`)},
},
}
}
func init() {
Register(forYouRecipe{})
Register(becauseRecipe{})
Register(similarUsersRecipe{})
Register(tasteMatchRecipe{})
}