feat: romaji index for Japanese kanji song search

Backend:
- Add /api/romaji-index endpoint using kuromoji morphological analyzer
- Fetches all Navidrome songs, converts kanji → hiragana readings → romaji
- Only indexes songs with Japanese characters; caches for 20 min
- /api/romaji-index DELETE clears cache (force rebuild)

Frontend:
- fetchRomajiIndex() in api.js: lazy-fetches and caches the index
- doLocalSearch: when query is romaji (ASCII), queries romaji index in
  parallel with Navidrome search3 and merges+deduplicates results

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-05-17 21:15:07 +02:00
1 parent 369a5e82b3
commit aa239f8e6a
4 files changed
+176 -6

No files matched your search

+19
View File
@@ -39,6 +39,25 @@ export async function ndAuth() {
return S._ndToken;
}
// ─── ROMAJI INDEX ───
// Fetched once per session from the backend, cached in memory.
let _romajiIndex = null;
let _romajiIndexFetch = null; // in-flight promise dedup
export async function fetchRomajiIndex() {
if (!S.cfg.backendUrl || !S.cfg.url || !S.cfg.user || !S.cfg.pass) return null;
if (_romajiIndex) return _romajiIndex;
if (_romajiIndexFetch) return _romajiIndexFetch;
_romajiIndexFetch = fetch(
`${S.cfg.backendUrl}/api/romaji-index?ndUrl=${encodeURIComponent(S.cfg.url)}&user=${encodeURIComponent(S.cfg.user)}&pass=${encodeURIComponent(S.cfg.pass)}`
).then(r => r.ok ? r.json() : null)
.then(idx => { _romajiIndex = idx; _romajiIndexFetch = null; return idx; })
.catch(() => { _romajiIndexFetch = null; return null; });
return _romajiIndexFetch;
}
export function invalidateRomajiIndex() { _romajiIndex = null; }
// ─── ITUNES ───
// Route through backend proxy to avoid CORS; fall back to direct if no backend configured.
function _itunesBase() {