feat(audiobooks): add embedding-based Similar audiobooks rail
Third feature-detected rail on the detail page. When the backend returns similar_audiobooks (server-side embedding similarity, ranked top-N), the rail mounts above Also-by-author and In-series, with the subtitle 'Based on listening patterns' to give the recommendation source a little provenance. Frontend has no embedding logic — purely a display path that lights up the moment the API exposes the field. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
eeb22e47d3
commit
958db9911c
@@ -63,6 +63,7 @@ export interface AudiobookDetailResponse {
|
||||
narrator?: string;
|
||||
files: AudiobookFile[];
|
||||
progress?: AudiobookProgress;
|
||||
similar_audiobooks?: AudiobookRelatedItem[];
|
||||
also_by_author?: AudiobookRelatedItem[];
|
||||
in_series?: {
|
||||
name?: string;
|
||||
|
||||
@@ -233,6 +233,19 @@ export default function AudiobookDetail() {
|
||||
/>
|
||||
{narrator && <NarratorCard narrator={narrator} />}
|
||||
|
||||
{data.similar_audiobooks && data.similar_audiobooks.length > 0 && (
|
||||
<RelatedRail
|
||||
heading="Similar audiobooks"
|
||||
subtitle="Based on listening patterns"
|
||||
items={data.similar_audiobooks.map((it) => ({
|
||||
content_id: it.content_id,
|
||||
title: it.title,
|
||||
poster_url: it.poster_url,
|
||||
subtitle: it.year ? String(it.year) : undefined,
|
||||
}))}
|
||||
/>
|
||||
)}
|
||||
|
||||
{data.also_by_author && data.also_by_author.length > 0 && (
|
||||
<RelatedRail
|
||||
heading={`Also by ${author ?? "this author"}`}
|
||||
|
||||
@@ -10,14 +10,18 @@ interface RelatedRailItem {
|
||||
|
||||
interface RelatedRailProps {
|
||||
heading: string;
|
||||
subtitle?: string;
|
||||
items: RelatedRailItem[];
|
||||
}
|
||||
|
||||
export function RelatedRail({ heading, items }: RelatedRailProps) {
|
||||
export function RelatedRail({ heading, subtitle, items }: RelatedRailProps) {
|
||||
if (items.length === 0) return null;
|
||||
return (
|
||||
<section className="mt-10">
|
||||
<h2 className="mb-4 text-xl font-semibold tracking-tight">{heading}</h2>
|
||||
<div className="mb-4">
|
||||
<h2 className="text-xl font-semibold tracking-tight">{heading}</h2>
|
||||
{subtitle && <p className="text-muted-foreground mt-1 text-xs">{subtitle}</p>}
|
||||
</div>
|
||||
<div className="-mx-2 flex gap-3 overflow-x-auto px-2 pb-2">
|
||||
{items.map((item) => (
|
||||
<ViewTransitionLink
|
||||
|
||||
Reference in New Issue
Block a user