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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -7,7 +7,7 @@
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>Mission Control 2.0</title>
<script type="module" crossorigin src="/assets/index-eeooup-h.js"></script>
<script type="module" crossorigin src="/assets/index-DzmNpOtE.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DueAT_Qc.css">
</head>
<body>
+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>