diff --git a/bridge/aria_bridge.py b/bridge/aria_bridge.py
index 7163876..1c19a14 100644
--- a/bridge/aria_bridge.py
+++ b/bridge/aria_bridge.py
@@ -72,7 +72,7 @@ BLOCK_SIZE = 1280 # 80ms bei 16kHz — gut fuer Wake-Word-Erkennung
RECORD_SECONDS = 8 # Max. Aufnahmedauer nach Wake-Word
# Epische Trigger — bei diesen Woertern spricht Thorsten
-EPIC_TRIGGERS = [
+EPIC_TRIGGERS_DEFAULT = [
"deploy",
"erfolgreich",
"alarm",
@@ -84,6 +84,24 @@ EPIC_TRIGGERS = [
"aufgabe abgeschlossen",
]
+# Trigger aus Shared-Config laden (von Diagnostic gespeichert)
+TRIGGERS_FILE = "/shared/config/highlight_triggers.json"
+
+def load_epic_triggers():
+ """Laedt Highlight-Trigger aus Shared-Config oder nutzt Defaults."""
+ try:
+ if os.path.exists(TRIGGERS_FILE):
+ with open(TRIGGERS_FILE) as f:
+ triggers = json.load(f)
+ if isinstance(triggers, list) and len(triggers) > 0:
+ logger.info("Highlight-Trigger geladen: %d aus %s", len(triggers), TRIGGERS_FILE)
+ return triggers
+ except Exception as e:
+ logger.warning("Highlight-Trigger laden fehlgeschlagen: %s — nutze Defaults", e)
+ return EPIC_TRIGGERS_DEFAULT
+
+EPIC_TRIGGERS = load_epic_triggers()
+
def load_config() -> dict[str, str]:
"""Laedt Konfiguration aus /config/aria.env."""
@@ -184,10 +202,7 @@ class VoiceEngine:
tmp_path = tmp.name
with wave.open(tmp_path, "wb") as wav_file:
- wav_file.setnchannels(1)
- wav_file.setsampwidth(2) # 16-bit
- wav_file.setframerate(voice.config.sample_rate)
- voice.synthesize(text, wav_file)
+ voice.synthesize_wav(text, wav_file)
audio_data = Path(tmp_path).read_bytes()
Path(tmp_path).unlink(missing_ok=True)
diff --git a/diagnostic/index.html b/diagnostic/index.html
index 2f430b3..832b5c4 100644
--- a/diagnostic/index.html
+++ b/diagnostic/index.html
@@ -396,6 +396,24 @@
+
+
+
Highlight-Trigger
+
+ Woerter die automatisch die Highlight-Stimme (Thorsten) ausloesen.
+ Eines pro Zeile. Aenderungen werden in der Bridge gespeichert.
+
+
+
+
+
+
+
+
+
+
+
Tool-Berechtigungen
@@ -599,6 +617,14 @@
return;
}
+ if (msg.type === 'trigger_list') {
+ const textarea = document.getElementById('highlight-triggers');
+ textarea.value = (msg.triggers || []).join('\n');
+ document.getElementById('trigger-status').textContent = msg.triggers.length + ' Trigger geladen';
+ document.getElementById('trigger-status').style.color = '#8888AA';
+ return;
+ }
+
if (msg.type === 'watchdog') {
const colors = { warning: '#FFD60A', fixing: '#FF9500', fixed: '#34C759', error: '#FF3B30' };
const color = colors[msg.status] || '#FFD60A';
@@ -1080,6 +1106,20 @@
}, 120000);
}
+ // ── Highlight-Trigger ────────────────────────
+ function loadHighlightTriggers() {
+ send({ action: 'get_triggers' });
+ }
+ function saveHighlightTriggers() {
+ const text = document.getElementById('highlight-triggers').value;
+ const triggers = text.split('\n').map(t => t.trim()).filter(t => t.length > 0);
+ send({ action: 'save_triggers', triggers });
+ document.getElementById('trigger-status').textContent = 'Gespeichert (' + triggers.length + ' Trigger)';
+ document.getElementById('trigger-status').style.color = '#34C759';
+ }
+ // Beim Tab-Wechsel zu Einstellungen: Trigger laden
+ const origSwitchMainTab = typeof switchMainTab === 'function' ? switchMainTab : null;
+
// ── Modus-Wechsel ────────────────────────────
let currentMode = 'normal';
const MODE_LABELS = { normal: 'Normal', dnd: 'Nicht stoeren', whisper: 'Fluestern', hangar: 'Hangar', gaming: 'Gaming' };
@@ -1514,6 +1554,8 @@
document.querySelectorAll('.main-nav-btn').forEach(b => {
if (b.textContent.trim().toLowerCase().includes(tab === 'main' ? 'main' : 'einstellung')) b.classList.add('active');
});
+ // Einstellungen: Trigger laden
+ if (tab === 'settings') loadHighlightTriggers();
}
// ── Einstellungen: Tool-Berechtigungen ──────────────────
diff --git a/diagnostic/server.js b/diagnostic/server.js
index f3aab37..1441d2d 100644
--- a/diagnostic/server.js
+++ b/diagnostic/server.js
@@ -1147,6 +1147,10 @@ wss.on("connection", (ws) => {
if (ws._sshSock) ws._sshSock.write(msg.data);
} else if (msg.action === "live_ssh_close") {
if (ws._sshSock) { ws._sshSock.end(); ws._sshSock = null; }
+ } else if (msg.action === "get_triggers") {
+ handleGetTriggers(ws);
+ } else if (msg.action === "save_triggers") {
+ handleSaveTriggers(ws, msg.triggers || []);
} else if (msg.action === "test_tts") {
handleTestTTS(ws, msg.voice || "ramona", msg.text || "Test");
} else if (msg.action === "check_tts") {
@@ -1277,6 +1281,44 @@ function startLiveSSH(clientWs) {
createReq.end(createBody);
}
+// ── Highlight-Trigger ─────────────────────────────────
+
+const TRIGGERS_FILE = "/shared/config/highlight_triggers.json";
+
+async function handleGetTriggers(clientWs) {
+ try {
+ // Zuerst aus Shared Volume lesen, dann Fallback auf Bridge-Defaults
+ let triggers;
+ if (fs.existsSync(TRIGGERS_FILE)) {
+ triggers = JSON.parse(fs.readFileSync(TRIGGERS_FILE, "utf-8"));
+ } else {
+ // Defaults aus der Bridge lesen
+ const result = await dockerExec("aria-bridge", `python3 -c "
+import sys; sys.path.insert(0,'/app')
+from aria_bridge import EPIC_TRIGGERS
+print('\\n'.join(EPIC_TRIGGERS))
+"`);
+ triggers = result.trim().split("\n").filter(t => t);
+ }
+ clientWs.send(JSON.stringify({ type: "trigger_list", triggers }));
+ } catch (err) {
+ clientWs.send(JSON.stringify({ type: "trigger_list", triggers: [], error: err.message }));
+ }
+}
+
+async function handleSaveTriggers(clientWs, triggers) {
+ try {
+ // In Shared Volume speichern (fuer Bridge lesbar)
+ fs.mkdirSync("/shared/config", { recursive: true });
+ fs.writeFileSync(TRIGGERS_FILE, JSON.stringify(triggers, null, 2));
+ log("info", "server", `${triggers.length} Highlight-Trigger gespeichert`);
+ // Bridge informieren (wird beim naechsten Start geladen)
+ clientWs.send(JSON.stringify({ type: "trigger_list", triggers }));
+ } catch (err) {
+ log("error", "server", `Trigger speichern fehlgeschlagen: ${err.message}`);
+ }
+}
+
// ── TTS Diagnose ──────────────────────────────────────
async function handleTestTTS(clientWs, voice, text) {
try {