Cleanup UI buttons + smarter series-aware search title

UI: Hide developer tools (Cover-aus-Datei, Tags-lesen,
Connectivity-Check) behind a '+ Tools' toggle. Default view has only
Play, Match, Auto-Match. Tag extraction runs automatically on scan
anyway, so the buttons were noise.

Matcher: Item metadata (series, author from tags or earlier matches)
now flows into the search:
- detect_series() also scans inside title, not only prefix — handles
  garbage chars (◆ U+25C6 etc.) before the series name
- New _strip_series_prefix removes "Die drei ???" from search title so
  APIs see only the episode title ("Die Villa der Toten") which is how
  most databases index these
- _build_search_title also strips non-printable / exotic chars and
  bracketed content anywhere (not just trailing)
- Effective series falls back to item.series when detect_series misses
- Search call now logs which series the search is using

Example: title='◆Die◆ drei ??? Die Villa der Toten (drei Fragezeichen)'
  detected_series='Die drei ???', search_title='Die Villa der Toten'

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Audiolib
2026-05-26 20:31:40 +02:00
parent 0824894a7f
commit edf057f36e
3 changed files with 89 additions and 110 deletions

View File

@@ -4,7 +4,7 @@ import {
Play, ArrowLeft, RefreshCw, Search, Check,
Loader2, Trash2, X
} from 'lucide-react'
import { getItem, updateItem, triggerMatch, searchMatch, applyMatch, debugMatch, checkConnectivity, extractCover, extractTags, coverUrl } from '../api/items'
import { getItem, updateItem, triggerMatch, searchMatch, applyMatch, debugMatch, coverUrl } from '../api/items'
import { getMe, createBookmark, deleteBookmark } from '../api/me'
import { usePlayerStore } from '../store/playerStore'
import CoverImage from '../components/common/CoverImage'
@@ -23,8 +23,7 @@ export default function BookDetail() {
const [showMatchPanel, setShowMatchPanel] = useState(false)
const [debugData, setDebugData] = useState<any>(null)
const [debugLoading, setDebugLoading] = useState(false)
const [connData, setConnData] = useState<any>(null)
const [connLoading, setConnLoading] = useState(false)
const [showDevTools, setShowDevTools] = useState(false)
const { play, item: currentItem, currentTime } = usePlayerStore()
useEffect(() => {
@@ -93,39 +92,6 @@ export default function BookDetail() {
}
}
const handleConnectivity = async () => {
setConnLoading(true)
try {
const data = await checkConnectivity()
setConnData(data)
} finally {
setConnLoading(false)
}
}
const handleExtractCover = async () => {
if (!id) return
const res = await extractCover(id)
if (res.success) {
const updated = await getItem(id)
setItem(updated)
} else {
alert('Kein lokales Cover gefunden')
}
}
const handleExtractTags = async () => {
if (!id) return
const res = await extractTags(id)
if (res.success) {
const updated = await getItem(id)
setItem(updated)
const tagKeys = Object.keys(res.tags || {})
alert(`Tags gelesen: ${tagKeys.join(', ') || '(keine)'}\n\nVorher: ${JSON.stringify(res.before, null, 2)}\n\nNachher: ${JSON.stringify(res.after, null, 2)}`)
} else {
alert(res.message || 'Keine Tags gefunden')
}
}
const fmtTime = (s: number) => {
const h = Math.floor(s / 3600)
@@ -216,65 +182,31 @@ export default function BookDetail() {
Auto-Match
</button>
<button
onClick={handleDebug}
disabled={debugLoading}
className="flex items-center gap-2 bg-card border border-divider px-4 py-2 rounded-lg text-sm text-muted hover:text-ink hover:bg-card/80 disabled:opacity-50 transition-colors"
title="Zeigt was die APIs tatsächlich zurückgeben"
onClick={() => setShowDevTools(!showDevTools)}
className="text-muted hover:text-ink text-sm px-2 py-2 transition-colors"
style={{ fontSize: '12px' }}
title="Entwickler-Werkzeuge"
>
{debugLoading ? <Loader2 size={14} className="animate-spin" /> : <Search size={14} />}
Debug
</button>
<button
onClick={handleExtractCover}
className="flex items-center gap-2 bg-card border border-divider px-4 py-2 rounded-lg text-sm text-muted hover:text-ink hover:bg-card/80 transition-colors"
title="Cover aus Ordner-Datei oder MP3-Tag extrahieren"
>
Cover aus Datei
</button>
<button
onClick={handleExtractTags}
className="flex items-center gap-2 bg-card border border-divider px-4 py-2 rounded-lg text-sm text-muted hover:text-ink hover:bg-card/80 transition-colors"
title="ID3-Tags (Artist, Album, Jahr) aus MP3 lesen und Metadaten füllen"
>
Tags lesen
</button>
<button
onClick={handleConnectivity}
disabled={connLoading}
className="flex items-center gap-2 bg-card border border-divider px-4 py-2 rounded-lg text-sm text-muted hover:text-ink hover:bg-card/80 disabled:opacity-50 transition-colors"
title="Prüft ob Backend die externen Metadaten-APIs erreicht"
>
{connLoading ? <Loader2 size={14} className="animate-spin" /> : null}
Connectivity-Check
{showDevTools ? '' : '+'} Tools
</button>
</div>
{showDevTools && (
<div className="flex gap-2 mt-2 flex-wrap">
<button
onClick={handleDebug}
disabled={debugLoading}
className="flex items-center gap-1 bg-card border border-divider px-3 py-1 rounded-lg text-muted hover:text-ink transition-colors disabled:opacity-50"
style={{ fontSize: '11px' }}
>
{debugLoading ? <Loader2 size={11} className="animate-spin" /> : null}
API-Debug
</button>
</div>
)}
</div>
</div>
{connData && (
<div className="mb-6 bg-surface border border-divider rounded-xl p-4">
<div className="flex items-center justify-between mb-3">
<h3 className="text-ink" style={{ fontSize: '13px', fontWeight: 600 }}>Externe Verbindungen</h3>
<button onClick={() => setConnData(null)} className="text-muted hover:text-ink">
<X size={14} />
</button>
</div>
{connData.proxy_env && connData.proxy_env !== 'keine' && (
<p className="text-yellow-400 mb-2" style={{ fontSize: '11px' }}>
Proxy-Variablen aktiv: <span className="font-mono">{JSON.stringify(connData.proxy_env)}</span>
</p>
)}
{connData.results?.map((r: any) => (
<div key={r.name} className="flex items-center justify-between py-1.5 border-t border-divider" style={{ fontSize: '12px' }}>
<span className="text-ink font-medium">{r.name}</span>
<span className={r.ok && r.status === 200 ? 'text-primary' : 'text-red-400'} style={{ fontSize: '11px' }}>
{r.ok ? `HTTP ${r.status} · ${r.bytes}B · ${r.ms}ms` : `${r.error} (${r.ms}ms)`}
</span>
</div>
))}
</div>
)}
{debugData && (
<div className="mb-6 bg-surface border border-divider rounded-xl p-4">
<div className="flex items-center justify-between mb-3">