Feat: ElevenLabs als Premium-TTS-Engine (sauberes natives Deutsch, Voice Library)

Sidecar: /tts + /voices + /health um Engine `elevenlabs` erweitert (Flash v2.5, Key zur Laufzeit
aus env ODER ~/.hermes/.env → Nachtragen ohne Deploy). Default bleibt Piper. Frontend: ElevenLabs
im Stimm-Picker (fest sichtbar) inkl. „Key fehlt"-Hinweis + Quota-Note. Chatterbox-DE war zu akzentig.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-06-28 00:32:24 +02:00
parent cf587616dd
commit aa62c98247
5 changed files with 195 additions and 111 deletions
+14 -3
View File
@@ -70,7 +70,8 @@ export function AvatarPicker({ avatarUrl, onAvatarChange }: {
localStorage.setItem("mc_voice_voice", v)
}
const enginesAvail = Array.from(new Set(voices.map((v) => v.engine)))
// Feste Reihenfolge der bekannten Engines (ElevenLabs auch ohne Key sichtbar → „Key fehlt"-Hinweis).
const enginesAvail = ["piper", "chatterbox", "elevenlabs"]
const voicesForEngine = voices.filter((v) => v.engine === engine)
return (
@@ -157,7 +158,7 @@ export function AvatarPicker({ avatarUrl, onAvatarChange }: {
engine === eng ? "border-primary/50 bg-primary/10 text-primary" : "border-border/40 hover:bg-accent"
}`}
>
{eng === "piper" ? "Piper (schnell)" : eng === "chatterbox" ? "Chatterbox (premium)" : eng}
{eng === "piper" ? "Piper (schnell)" : eng === "chatterbox" ? "Chatterbox (lokal)" : eng === "elevenlabs" ? "ElevenLabs (premium)" : eng}
</button>
))}
</div>
@@ -173,7 +174,17 @@ export function AvatarPicker({ avatarUrl, onAvatarChange }: {
</select>
{engine === "chatterbox" && (
<p className="text-[11px] text-muted-foreground">
Chatterbox läuft auf CPU erste Antwort kann ein paar Sekunden dauern. Natürlichste Stimme + Voice-Cloning.
Chatterbox läuft auf CPU erste Antwort kann ein paar Sekunden dauern. Lokal + Voice-Cloning (dt. mit Akzent).
</p>
)}
{engine === "elevenlabs" && (
<p className="text-[11px] text-muted-foreground">
ElevenLabs: sauberes natives Deutsch, junge/Anime-Stimmen via Voice Library. Cloud, Free-Tier ~50 kurze Antworten/Monat.
</p>
)}
{engine === "elevenlabs" && voices.filter((v) => v.engine === "elevenlabs").length === 0 && (
<p className="text-[11px] text-amber-300">
Noch kein ElevenLabs-Key hinterlegt Stimmen erscheinen, sobald der Key in <code>~/.hermes/.env</code> steht.
</p>
)}
</div>