feat: hot-reload voice models without server restart

Voice enabled/STT model are now DB-backed (admin settings), not env
vars. Added reload_stt_model()/reload_tts_model() that clear singletons
under lock and re-trigger loading. POST /api/admin/voice/reload triggers
both in background tasks. Settings UI polls /api/voice/status every 2.5s
until models are ready, with spinner feedback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-29 20:52:07 -04:00
parent eaf70500b8
commit f146485df3
5 changed files with 95 additions and 5 deletions
+11
View File
@@ -228,6 +228,17 @@ async def update_voice_config():
return jsonify({"status": "ok"})
@admin_bp.route("/voice/reload", methods=["POST"])
@admin_required
async def reload_voice_models():
"""Reload STT and TTS models in the background without a server restart."""
from fabledassistant.services.stt import reload_stt_model
from fabledassistant.services.tts import reload_tts_model
asyncio.create_task(reload_stt_model())
asyncio.create_task(reload_tts_model())
return jsonify({"status": "loading"})
@admin_bp.route("/invitations", methods=["POST"])
@admin_required
async def create_invite():
+9
View File
@@ -42,6 +42,15 @@ async def load_stt_model() -> None:
logger.exception(_load_error)
async def reload_stt_model() -> None:
"""Unload the current model and reload with the current config. Safe to call at runtime."""
global _model, _load_error
async with _model_lock:
_model = None
_load_error = None
await load_stt_model()
def stt_available() -> bool:
return _model is not None
+9
View File
@@ -56,6 +56,15 @@ async def load_tts_model() -> None:
logger.exception(_load_error)
async def reload_tts_model() -> None:
"""Unload and reload the TTS pipeline. Safe to call at runtime."""
global _pipeline, _load_error
async with _pipeline_lock:
_pipeline = None
_load_error = None
await load_tts_model()
def tts_available() -> bool:
return _pipeline is not None