feat: 'Übersetzungen nachholen' + 'Audios nachholen' immer verfügbar

Beide Nachhol-Aktionen jetzt dauerhaft im Karten-Header (nicht nur bei
unvollständigen Pairs) — Reihenfolge Übersetzen → Audio, da Audio alle
Sprachen braucht.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 22:03:25 +02:00
parent ae25dc9428
commit 2cec5bc362

View File

@@ -167,6 +167,7 @@ function ReadyCard({ bundle, onPublished, onRefresh }) {
const [done, setDone] = useState(null); const [done, setDone] = useState(null);
const [assigning, setAssigning] = useState(null); const [assigning, setAssigning] = useState(null);
const [audioFilling, setAudioFilling] = useState(false); const [audioFilling, setAudioFilling] = useState(false);
const [translateFilling, setTranslateFilling] = useState(false);
if (!bundle) { if (!bundle) {
return ( return (
@@ -209,6 +210,16 @@ function ReadyCard({ bundle, onPublished, onRefresh }) {
finally { setAssigning(null); } finally { setAssigning(null); }
} }
async function fillTranslations() {
setTranslateFilling(true); setError(null);
try {
const res = await apiPost(`/pipeline/picture/${bundle.picture.id}/translate-fill`, {});
if (res.failed) setError(`${res.translated} Pairs übersetzt, ${res.failed} fehlgeschlagen: ${res.errors?.[0]?.error || ''}`);
await onRefresh?.();
} catch (e) { setError(e.payload?.error || e.message); }
finally { setTranslateFilling(false); }
}
async function fillAudio() { async function fillAudio() {
setAudioFilling(true); setError(null); setAudioFilling(true); setError(null);
try { try {
@@ -261,16 +272,21 @@ function ReadyCard({ bundle, onPublished, onRefresh }) {
{bundle.objects.length} Objekt{bundle.objects.length !== 1 ? 'e' : ''} · {allPairs.length} Pairs · {wordCount} Wörter {bundle.objects.length} Objekt{bundle.objects.length !== 1 ? 'e' : ''} · {allPairs.length} Pairs · {wordCount} Wörter
</div> </div>
{incompletePairs.length > 0 && ( {incompletePairs.length > 0 && (
<div className="flex items-center gap-2 mt-1"> <div className="text-xs text-amber-600 mt-1">
<span className="text-xs text-amber-600"> {incompletePairs.length} Pair{incompletePairs.length !== 1 ? 's' : ''} unvollständig (Text/Audio) erst Übersetzungen, dann Audios nachholen
{incompletePairs.length} Pair{incompletePairs.length !== 1 ? 's' : ''} unvollständig (Text/Audio) </div>
</span> )}
<button onClick={fillAudio} disabled={audioFilling} {/* Nachhol-Aktionen immer verfügbar: Reihenfolge Übersetzen → Audio (Audio braucht alle Sprachen) */}
<div className="flex flex-wrap items-center gap-1.5 mt-2">
<button onClick={fillTranslations} disabled={translateFilling || audioFilling}
className="text-[11px] px-2 py-0.5 rounded-full border border-indigo-200 bg-indigo-50 text-indigo-700 hover:bg-indigo-100 disabled:opacity-50">
{translateFilling ? 'Übersetze…' : '🌍 Übersetzungen nachholen'}
</button>
<button onClick={fillAudio} disabled={audioFilling || translateFilling}
className="text-[11px] px-2 py-0.5 rounded-full border border-amber-300 bg-amber-50 text-amber-700 hover:bg-amber-100 disabled:opacity-50"> className="text-[11px] px-2 py-0.5 rounded-full border border-amber-300 bg-amber-50 text-amber-700 hover:bg-amber-100 disabled:opacity-50">
{audioFilling ? 'Generiere…' : '🔊 Fehlende Audios generieren'} {audioFilling ? 'Generiere…' : '🔊 Fehlende Audios generieren'}
</button> </button>
</div> </div>
)}
</div> </div>
<span className="shrink-0 text-xs font-medium bg-emerald-100 text-emerald-700 rounded-full px-2.5 py-1">bereit</span> <span className="shrink-0 text-xs font-medium bg-emerald-100 text-emerald-700 rounded-full px-2.5 py-1">bereit</span>
</div> </div>