diff --git a/bridge/aria_bridge.py b/bridge/aria_bridge.py
index 56c2f96..0a31319 100644
--- a/bridge/aria_bridge.py
+++ b/bridge/aria_bridge.py
@@ -498,6 +498,10 @@ class ARIABridge:
self._last_chat_final_at: float = 0.0
# requestId → messageId Map fuer XTTS-Audio-Cache (App-seitige Zuordnung)
self._xtts_request_to_message: dict[str, str] = {}
+ # Voice-Override aus letzter Chat-Nachricht einer App.
+ # Wird fuer die direkt folgende ARIA-Antwort genutzt und dann zurueckgesetzt.
+ # So kann jedes Geraet seine bevorzugte Stimme bekommen (pro Request).
+ self._next_voice_override: Optional[str] = None
def initialize(self) -> None:
"""Initialisiert alle Komponenten.
@@ -856,14 +860,19 @@ class ARIABridge:
logger.info("[core] TTS unterdrueckt (Modus: %s)", self.current_mode.config.name)
return
- xtts_voice = getattr(self, 'xtts_voice', '')
+ # Voice bestimmen: App-Override fuer diesen Request > globale Default-Voice
+ xtts_voice = self._next_voice_override or getattr(self, 'xtts_voice', '')
+ # Override verbrauchen (gilt nur fuer genau diese naechste Antwort)
+ if self._next_voice_override:
+ logger.info("[core] Nutze Voice-Override: %s", self._next_voice_override)
+ self._next_voice_override = None
+
tts_text = tts_text_preview or text
if not tts_text:
logger.info("[core] TTS-Text leer nach Cleanup — uebersprungen")
return
try:
xtts_request_id = str(uuid.uuid4())
- # Map fuer audio_pcm/xtts_response → App-Cache Zuordnung
self._xtts_request_to_message[xtts_request_id] = message_id
if len(self._xtts_request_to_message) > 100:
oldest = next(iter(self._xtts_request_to_message))
@@ -1031,6 +1040,11 @@ class ARIABridge:
if sender in ("aria", "stt"):
return
text = payload.get("text", "")
+ # Voice-Override fuer die naechste ARIA-Antwort merken
+ voice_override = payload.get("voice", "")
+ if voice_override:
+ self._next_voice_override = voice_override
+ logger.info("[rvs] Voice-Override fuer naechste Antwort: %s", voice_override)
if text:
logger.info("[rvs] App-Chat: '%s'", text[:80])
await self.send_to_core(text, source="app")
@@ -1096,7 +1110,8 @@ class ARIABridge:
if not text:
return
tts_text = clean_text_for_tts(text) or text
- xtts_voice = getattr(self, 'xtts_voice', '')
+ # Voice aus App-Payload gewinnt, sonst global
+ xtts_voice = payload.get("voice", "") or getattr(self, 'xtts_voice', '')
try:
xtts_request_id = str(uuid.uuid4())
if message_id:
diff --git a/diagnostic/index.html b/diagnostic/index.html
index ac36329..14e12df 100644
--- a/diagnostic/index.html
+++ b/diagnostic/index.html
@@ -419,6 +419,9 @@
+
+
+
Stimme klonen
@@ -752,16 +755,23 @@
if (msg.type === 'xtts_voices_list') {
const select = document.getElementById('diag-xtts-voice');
- // Behalte erste Option (Default)
+ // Aktuelle Auswahl merken damit Rebuild sie nicht zerstoert
+ const previouslySelected = select.value;
while (select.options.length > 1) select.remove(1);
- for (const v of (msg.payload?.voices || [])) {
+ const voices = msg.payload?.voices || [];
+ for (const v of voices) {
const opt = document.createElement('option');
opt.value = v.name;
opt.textContent = `${v.name} (${(v.size / 1024).toFixed(0)}KB)`;
select.appendChild(opt);
}
- document.getElementById('xtts-status').textContent = `XTTS: ${msg.payload?.voices?.length || 0} Stimme(n) verfuegbar`;
+ // Wenn die vorherige Auswahl weiter existiert → wiederherstellen
+ if (previouslySelected && voices.some(v => v.name === previouslySelected)) {
+ select.value = previouslySelected;
+ }
+ document.getElementById('xtts-status').textContent = `XTTS: ${voices.length} Stimme(n) verfuegbar`;
document.getElementById('xtts-status').style.color = '#34C759';
+ renderVoiceList(voices);
return;
}
if (msg.type === 'xtts_voice_saved') {
@@ -1356,6 +1366,35 @@
}
// ── XTTS Panel ─────────────────────────────
+ function renderVoiceList(voices) {
+ const box = document.getElementById('xtts-voice-list');
+ if (!box) return;
+ if (!voices || voices.length === 0) {
+ box.innerHTML = '
Noch keine eigenen Stimmen vorhanden.
';
+ return;
+ }
+ let html = '
Geclonte Stimmen:
';
+ html += '
';
+ for (const v of voices) {
+ const esc = (s) => String(s).replace(/[&<>"']/g, c => ({ "&":"&", "<":"<", ">":">", '"':""", "'":"'" }[c]));
+ html += `