stable
This commit is contained in:
parent
b7614eb3f8
commit
db4ce8b14f
22 changed files with 3557 additions and 823 deletions
58
voice_rtc.py
58
voice_rtc.py
|
|
@ -64,7 +64,7 @@ except Exception: # pragma: no cover - runtime fallback when aiortc is unavaila
|
|||
|
||||
|
||||
SPEECH_FILTER_RE = re.compile(
|
||||
r"^(spawned nanobot tui|stopped nanobot tui|nanobot tui exited|websocket)",
|
||||
r"^(already connected to nanobot|connected to nanobot|disconnected from nanobot|nanobot closed the connection|websocket)",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
THINKING_STATUS_RE = re.compile(
|
||||
|
|
@ -96,6 +96,38 @@ def _sanitize_tts_text(text: str) -> str:
|
|||
return cleaned
|
||||
|
||||
|
||||
def _coerce_message_metadata(raw: Any) -> dict[str, Any]:
|
||||
def _coerce_jsonish(value: Any, depth: int = 0) -> Any:
|
||||
if depth > 6:
|
||||
return None
|
||||
if value is None or isinstance(value, (str, int, float, bool)):
|
||||
return value
|
||||
if isinstance(value, dict):
|
||||
cleaned_dict: dict[str, Any] = {}
|
||||
for key, item in value.items():
|
||||
cleaned_item = _coerce_jsonish(item, depth + 1)
|
||||
if cleaned_item is not None:
|
||||
cleaned_dict[str(key)] = cleaned_item
|
||||
return cleaned_dict
|
||||
if isinstance(value, list):
|
||||
cleaned_list: list[Any] = []
|
||||
for item in value[:50]:
|
||||
cleaned_item = _coerce_jsonish(item, depth + 1)
|
||||
if cleaned_item is not None:
|
||||
cleaned_list.append(cleaned_item)
|
||||
return cleaned_list
|
||||
return None
|
||||
|
||||
if not isinstance(raw, dict):
|
||||
return {}
|
||||
cleaned: dict[str, Any] = {}
|
||||
for key, value in raw.items():
|
||||
cleaned_value = _coerce_jsonish(value)
|
||||
if cleaned_value is not None:
|
||||
cleaned[str(key)] = cleaned_value
|
||||
return cleaned
|
||||
|
||||
|
||||
def _optional_int_env(name: str) -> int | None:
|
||||
raw_value = os.getenv(name, "").strip()
|
||||
if not raw_value:
|
||||
|
|
@ -876,6 +908,7 @@ class WebRTCVoiceSession:
|
|||
)
|
||||
self._last_stt_backlog_notice_at = 0.0
|
||||
self._ptt_pressed = False
|
||||
self._active_message_metadata: dict[str, Any] = {}
|
||||
|
||||
def set_push_to_talk_pressed(self, pressed: bool) -> None:
|
||||
self._ptt_pressed = bool(pressed)
|
||||
|
|
@ -917,12 +950,17 @@ class WebRTCVoiceSession:
|
|||
|
||||
await self._close_peer_connection()
|
||||
self._ptt_pressed = False
|
||||
self._active_message_metadata = {}
|
||||
|
||||
peer_connection = RTCPeerConnection()
|
||||
self._pc = peer_connection
|
||||
self._outbound_track = QueueAudioTrack()
|
||||
self._outbound_track._on_playing_changed = self._on_track_playing_changed
|
||||
peer_connection.addTrack(self._outbound_track)
|
||||
offer_has_audio = bool(re.search(r"(?im)^m=audio\s", sdp))
|
||||
if offer_has_audio:
|
||||
self._outbound_track = QueueAudioTrack()
|
||||
self._outbound_track._on_playing_changed = self._on_track_playing_changed
|
||||
peer_connection.addTrack(self._outbound_track)
|
||||
else:
|
||||
self._outbound_track = None
|
||||
|
||||
@peer_connection.on("datachannel")
|
||||
def on_datachannel(channel: Any) -> None:
|
||||
|
|
@ -938,13 +976,14 @@ class WebRTCVoiceSession:
|
|||
return
|
||||
msg_type = str(msg.get("type", "")).strip()
|
||||
if msg_type == "voice-ptt":
|
||||
self._active_message_metadata = _coerce_message_metadata(msg.get("metadata", {}))
|
||||
self.set_push_to_talk_pressed(bool(msg.get("pressed", False)))
|
||||
elif msg_type == "command":
|
||||
asyncio.create_task(self._gateway.send_command(str(msg.get("command", ""))))
|
||||
elif msg_type == "ui-response":
|
||||
elif msg_type == "card-response":
|
||||
asyncio.create_task(
|
||||
self._gateway.send_ui_response(
|
||||
str(msg.get("request_id", "")),
|
||||
self._gateway.send_card_response(
|
||||
str(msg.get("card_id", "")),
|
||||
str(msg.get("value", "")),
|
||||
)
|
||||
)
|
||||
|
|
@ -1274,7 +1313,10 @@ class WebRTCVoiceSession:
|
|||
await self._gateway.bus.publish(
|
||||
WisperEvent(role="wisper", text=f"voice transcript: {transcript}")
|
||||
)
|
||||
await self._gateway.send_user_message(transcript)
|
||||
await self._gateway.send_user_message(
|
||||
transcript,
|
||||
metadata=dict(self._active_message_metadata),
|
||||
)
|
||||
|
||||
async def _close_peer_connection(self) -> None:
|
||||
self._dc = None
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue