diff --git a/idriz/assistant.py b/idriz/assistant.py index c2c00b6..a98a680 100644 --- a/idriz/assistant.py +++ b/idriz/assistant.py @@ -91,6 +91,9 @@ class Assistant: self.conn = None self.jobs: set[asyncio.Task] = set() self.greeted = False + self.current_response = "" + self.response_active = False + self.cancelled_responses: set[str] = set() # ---------- alati ---------- async def tool_wikipedia(self, args: dict) -> str: @@ -155,6 +158,20 @@ class Assistant: ok = False await self.notify_model(("GOTOVO. " if ok else "GREŠKA. ") + result + " Reci to djetetu kratko, na bosanskom.") + async def interrupt_speech(self) -> None: + """Prekini govor koji je u toku (pozadinski poslovi nastavljaju).""" + speaking = self.speaker.is_playing(0.0) or self.response_active + if self.response_active and self.conn: + self.cancelled_responses.add(self.current_response) + try: + await self.conn.send({"type": "response.cancel"}) + except Exception: # noqa: BLE001 + log.exception("response.cancel nije uspio") + if speaking: + await self.speaker.clear() + log.info("govor prekinut (mikrofon ugašen)") + await self.speaker.write(audio.tone_off()) + async def notify_model(self, text: str) -> None: if not self.conn: return @@ -193,19 +210,20 @@ class Assistant: present = False while True: now = asyncio.get_event_loop().time() - if now - last_check > 1.5: + if now - last_check > 0.7: present = await audio.source_present(self.cfg.mic_device) last_check = now if not present: if self.mic.running: await self.mic.stop() log.info("mikrofon isključen (prekidač), ne slušam") - await asyncio.sleep(0.5) + await self.interrupt_speech() + await asyncio.sleep(0.3) continue if not self.mic.running: await self.mic.start() log.info("mikrofon uključen, slušam") - await self.speaker.write(audio.tone()) + await self.speaker.write(audio.tone_on()) chunk = await self.mic.read() if not chunk: await self.mic.stop() @@ -220,7 +238,14 @@ class Assistant: async for event in self.conn: t = event.type if t == "response.output_audio.delta": + if getattr(event, "response_id", "") in self.cancelled_responses: + continue await self.speaker.write(base64.b64decode(event.delta)) + elif t == "response.created": + self.current_response = event.response.id or "" + self.response_active = True + elif t == "response.done": + self.response_active = False elif t == "response.output_audio_transcript.delta": transcript += event.delta elif t == "response.output_audio_transcript.done": diff --git a/idriz/audio.py b/idriz/audio.py index 06379d2..85b3cab 100644 --- a/idriz/audio.py +++ b/idriz/audio.py @@ -36,6 +36,16 @@ def tone(freq: float = 880.0, ms: int = 150, volume: float = 0.3) -> bytes: return samples.tobytes() +def tone_on() -> bytes: + """Mikrofon uključen: dva tona naviše.""" + return tone(660, 120) + tone(990, 160) + + +def tone_off() -> bytes: + """Mikrofon isključen / prekid govora: dva tona naniže.""" + return tone(990, 120) + tone(440, 220) + + async def source_present(name: str) -> bool: """Da li PipeWire trenutno ima izvor tog imena (USB mikrofon nestane kad se isključi prekidač).""" if not name: