fix: group local search series results

This commit is contained in:
ShukeBta
2026-05-29 10:55:01 +08:00
parent 4361ba73ba
commit 2f7ec3ab17
4 changed files with 42 additions and 21 deletions
+18 -6
View File
@@ -1,4 +1,4 @@
import { FormEvent, useEffect, useState } from 'react'
import { FormEvent, useEffect, useMemo, useState } from 'react'
import { Link } from 'react-router-dom'
import { Loader2, Rss, Search, Sparkles, Wand2 } from 'lucide-react'
import toast from 'react-hot-toast'
@@ -8,6 +8,7 @@ import { imageURL } from '../api/client'
import { subscriptionsAPI } from '../api/subscriptions'
import { MediaCard } from '../components/MediaCard'
import type { Media } from '../types'
import { groupSeries, seriesCardLink } from '../utils/groupSeries'
// AIAssistantPage exposes the two AI helpers backed by the Go server:
// - smart search: parses a natural-language query into a SearchIntent +
@@ -30,6 +31,7 @@ export function AIAssistantPage() {
const [items, setItems] = useState<Media[]>([])
const [externalItems, setExternalItems] = useState<ExternalMediaResult[]>([])
const [subscribing, setSubscribing] = useState('')
const localCards = useMemo(() => groupSeries(items), [items])
const [recs, setRecs] = useState<string[] | null>(null)
const [recommending, setRecommending] = useState(false)
@@ -186,11 +188,21 @@ export function AIAssistantPage() {
</div>
)}
{items.length > 0 && (
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6">
{items.map((m) => (
<MediaCard key={m.id} media={m} />
))}
{localCards.length > 0 && (
<div className="space-y-2">
<div className="text-sm font-semibold text-ink-100">
本地媒体库 · {localCards.length} 个合集 / {items.length} 个条目
</div>
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6">
{localCards.map((card) => (
<MediaCard
key={card.key}
media={card.rep}
count={card.count}
linkTo={seriesCardLink(card)}
/>
))}
</div>
</div>
)}
+2 -9
View File
@@ -7,7 +7,7 @@ import { imageURL } from '../api/client'
import { libraryAPI } from '../api/library'
import { MediaCard } from '../components/MediaCard'
import type { Library, Media } from '../types'
import { artworkScore, groupSeries, type SeriesCard } from '../utils/groupSeries'
import { artworkScore, groupSeries, seriesCardLink, type SeriesCard } from '../utils/groupSeries'
type LibraryPreview = {
library: Library
@@ -206,7 +206,7 @@ function LibraryShelf({ preview }: { preview: LibraryPreview }) {
<MediaCard
media={card.rep}
count={card.count}
linkTo={mediaCardLink(card)}
linkTo={seriesCardLink(card)}
/>
</div>
))}
@@ -226,13 +226,6 @@ function latestCards(items: Media[]): SeriesCard[] {
.slice(0, 10)
}
function mediaCardLink(card: SeriesCard): string {
if (card.count > 1) {
return `/library/${card.rep.library_id}?series=${encodeURIComponent(card.key)}`
}
return `/media/${card.rep.id}`
}
function mediaTime(media: Media): number {
return Date.parse(media.updated_at || media.created_at || '') || 0
}
+15 -6
View File
@@ -1,4 +1,4 @@
import { ChangeEvent, FormEvent, useCallback, useEffect, useState } from 'react'
import { ChangeEvent, FormEvent, useCallback, useEffect, useMemo, useState } from 'react'
import toast from 'react-hot-toast'
import { Rss, Sparkles } from 'lucide-react'
@@ -8,6 +8,7 @@ import { mediaAPI } from '../api/library'
import { subscriptionsAPI } from '../api/subscriptions'
import { MediaCard } from '../components/MediaCard'
import type { Media } from '../types'
import { groupSeries, seriesCardLink } from '../utils/groupSeries'
export function SearchPage() {
const [q, setQ] = useState('')
@@ -20,6 +21,7 @@ export function SearchPage() {
const [hasSearched, setHasSearched] = useState(false)
const [externalItems, setExternalItems] = useState<ExternalMediaResult[]>([])
const [subscribing, setSubscribing] = useState('')
const localCards = useMemo(() => groupSeries(items), [items])
useEffect(() => {
aiAPI
@@ -84,7 +86,7 @@ export function SearchPage() {
}
}
const showEmpty = !loading && !error && hasSearched && items.length === 0
const showEmpty = !loading && !error && hasSearched && localCards.length === 0
const showIdle = !loading && !error && !hasSearched
return (
@@ -160,12 +162,19 @@ export function SearchPage() {
</div>
)}
{items.length > 0 && (
{localCards.length > 0 && (
<>
<div className="text-sm font-semibold text-ink-100">本地媒体库</div>
<div className="text-sm font-semibold text-ink-100">
本地媒体库 · {localCards.length} 个合集 / {items.length} 个条目
</div>
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6">
{items.map((m) => (
<MediaCard key={m.id} media={m} />
{localCards.map((card) => (
<MediaCard
key={card.key}
media={card.rep}
count={card.count}
linkTo={seriesCardLink(card)}
/>
))}
</div>
</>
+7
View File
@@ -82,6 +82,13 @@ export function groupSeries(items: Media[]): SeriesCard[] {
return Array.from(groups.values())
}
export function seriesCardLink(card: SeriesCard): string {
if (card.count > 1) {
return `/library/${card.rep.library_id}?series=${encodeURIComponent(card.key)}`
}
return `/media/${card.rep.id}`
}
export function artworkScore(media: Media): number {
const poster = (media.poster_url ?? '').toLowerCase()
const backdrop = (media.backdrop_url ?? '').toLowerCase()