Preserve macOS app permissions via dynamic launcher, .env workspace path, and session/audio tools
This commit is contained in:
+63
-2
@@ -25,18 +25,21 @@ _sse_clients: Set[asyncio.Queue] = set()
|
||||
_workspace_dir: Path = Path(__file__).parent.resolve()
|
||||
_model_manager = None
|
||||
_voice_manager = None
|
||||
_audio_controller = None
|
||||
_input_callback: Callable = None
|
||||
_recent_events: list = []
|
||||
_MAX_HISTORY_EVENTS = 200
|
||||
|
||||
|
||||
def set_managers(workspace: Path, model_mgr=None, voice_mgr=None, input_callback=None):
|
||||
global _workspace_dir, _model_manager, _voice_manager, _input_callback
|
||||
def set_managers(workspace: Path, model_mgr=None, voice_mgr=None, input_callback=None, audio_controller=None):
|
||||
global _workspace_dir, _model_manager, _voice_manager, _input_callback, _audio_controller
|
||||
_workspace_dir = Path(workspace)
|
||||
_model_manager = model_mgr
|
||||
_voice_manager = voice_mgr
|
||||
if input_callback is not None:
|
||||
_input_callback = input_callback
|
||||
if audio_controller is not None:
|
||||
_audio_controller = audio_controller
|
||||
|
||||
|
||||
def broadcast_event(event_type: str, payload: dict):
|
||||
@@ -185,6 +188,43 @@ async def handle_set_voice(request):
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
|
||||
async def handle_reset_session(request):
|
||||
try:
|
||||
from bin.session_tool import reset_session
|
||||
ok, msg = reset_session(_workspace_dir)
|
||||
if ok:
|
||||
broadcast_event("session_reset", {"message": msg})
|
||||
return web.json_response({"success": True, "message": msg})
|
||||
return web.json_response({"error": msg}, status=500)
|
||||
except Exception as e:
|
||||
return web.json_response({"error": str(e)}, status=500)
|
||||
|
||||
|
||||
async def handle_get_audio_devices(request):
|
||||
if not _audio_controller:
|
||||
return web.json_response({"error": "Audio controller not active"}, status=503)
|
||||
return web.json_response({"devices": _audio_controller.available_devices()})
|
||||
|
||||
|
||||
async def handle_set_audio_device(request):
|
||||
try:
|
||||
if not _audio_controller:
|
||||
return web.json_response({"error": "Audio controller not active"}, status=503)
|
||||
body = await request.json()
|
||||
direction, device = body.get("direction"), body.get("device")
|
||||
if direction not in {"input", "output"}:
|
||||
return web.json_response({"error": "direction must be input or output"}, status=400)
|
||||
result = await _audio_controller.set_runtime_device(direction, device)
|
||||
broadcast_event("audio_device_selected", result)
|
||||
return web.json_response({"success": True, **result})
|
||||
except (ValueError, RuntimeError) as exc:
|
||||
logger.warning(f"Runtime audio-device selection failed: {exc}")
|
||||
return web.json_response({"error": str(exc)}, status=400)
|
||||
except Exception as exc:
|
||||
logger.exception("Runtime audio-device selection failed")
|
||||
return web.json_response({"error": str(exc)}, status=500)
|
||||
|
||||
|
||||
async def handle_show_file_api(request):
|
||||
try:
|
||||
body = await request.json()
|
||||
@@ -249,9 +289,12 @@ async def start_server(workspace: Path, host: str = "127.0.0.1", port: int = 888
|
||||
app.router.add_get("/api/models", handle_get_models)
|
||||
app.router.add_post("/api/model", handle_set_model)
|
||||
app.router.add_post("/api/voice", handle_set_voice)
|
||||
app.router.add_get("/api/audio-devices", handle_get_audio_devices)
|
||||
app.router.add_post("/api/audio-device", handle_set_audio_device)
|
||||
app.router.add_post("/api/show_file", handle_show_file_api)
|
||||
app.router.add_post("/api/open_browser", handle_open_browser_api)
|
||||
app.router.add_post("/api/send", handle_send_message_api)
|
||||
app.router.add_post("/api/session/reset", handle_reset_session)
|
||||
|
||||
runner = web.AppRunner(app)
|
||||
await runner.setup()
|
||||
@@ -692,6 +735,9 @@ HTML_INDEX = """<!DOCTYPE html>
|
||||
<div class="pill" id="voicePill" onclick="openVoiceModal()">
|
||||
Voice: <strong id="voiceName">Loading...</strong>
|
||||
</div>
|
||||
<div class="pill" id="resetPill" onclick="resetSession()" title="Start a fresh conversation session">
|
||||
🔄 <strong>Reset Session</strong>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -843,6 +889,21 @@ HTML_INDEX = """<!DOCTYPE html>
|
||||
.catch(err => console.error('Failed to send message:', err));
|
||||
}
|
||||
|
||||
async function resetSession() {
|
||||
if (!confirm('Start a fresh Hermes conversation session?')) return;
|
||||
try {
|
||||
const res = await fetch('/api/session/reset', { method: 'POST' });
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
appendToolStep('System', data.message || 'Session reset.');
|
||||
} else {
|
||||
alert('Failed to reset session: ' + (data.error || 'Unknown error'));
|
||||
}
|
||||
} catch (err) {
|
||||
alert('Error resetting session: ' + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyDown(e) {
|
||||
if (e.key === 'Enter') {
|
||||
sendMessage();
|
||||
|
||||
Reference in New Issue
Block a user