diff --git a/ttstoy/webui/app.py b/ttstoy/webui/app.py index f5006f6..f5c4634 100644 --- a/ttstoy/webui/app.py +++ b/ttstoy/webui/app.py @@ -565,11 +565,48 @@ def api_chatterbox_voices(): user_id = session["user_id"] ucfg = get_user_config(user_id) voices = ucfg.get("chatterbox_voices", []) + + # Validate voices exist on the Chatterbox server + cb_url = get_chatterbox_url() + server_voices = set() + try: + r = requests.get(f"{cb_url.rstrip('/')}/api/ui/initial-data", timeout=3) + if r.status_code == 200: + data = r.json() + server_voices = set(data.get("predefined_voices", [])) + except Exception: + pass # If server unreachable, show all voices without filtering + result = [] + stale = [] for v in voices: + if server_voices and v not in server_voices: + stale.append(v) + continue parts = v.split("_", 1) display = parts[1].rsplit(".", 1)[0] if len(parts) > 1 else v.rsplit(".", 1)[0] result.append({"filename": v, "display": display}) + + # Auto-clean stale voices from config + if stale: + try: + p = COG_CONFIG_PATH / "settings.json" + data = _read_json(p) + if data: + ucfg_data = data.get(COG_IDENTIFIER, {}).get("USER", {}).get(str(user_id), {}) + cfg_voices = ucfg_data.get("chatterbox_voices", []) + for s in stale: + if s in cfg_voices: + cfg_voices.remove(s) + ucfg_data["chatterbox_voices"] = cfg_voices + if ucfg_data.get("minimax_voice") in stale: + ucfg_data["minimax_voice"] = cfg_voices[0] if cfg_voices else None + with open(p, "w") as f: + json.dump(data, f, indent=2) + log.info(f"Auto-cleaned {len(stale)} stale voices for user {user_id}: {stale}") + except Exception as ex: + log.warning(f"Could not auto-clean stale voices: {ex}") + return jsonify(result) @@ -633,6 +670,25 @@ def api_chatterbox_delete_voice(filename): return jsonify({"error": f"Server returned {r.status_code}"}), 500 except Exception as ex: return jsonify({"error": str(ex)}), 500 + + # Immediately remove from settings.json so the UI updates without waiting for bot + try: + p = COG_CONFIG_PATH / "settings.json" + data = _read_json(p) + if data: + ucfg = data.get(COG_IDENTIFIER, {}).get("USER", {}).get(str(user_id), {}) + voices = ucfg.get("chatterbox_voices", []) + if filename in voices: + voices.remove(filename) + ucfg["chatterbox_voices"] = voices + # If active voice was deleted, switch to first remaining or clear + if ucfg.get("minimax_voice") == filename: + ucfg["minimax_voice"] = voices[0] if voices else None + with open(p, "w") as f: + json.dump(data, f, indent=2) + except Exception as ex: + log.warning(f"Could not update settings.json directly: {ex}") + _write_bot_command(user_id, "remove_voice", {"filename": filename}) return jsonify({"ok": True})