Switching the mic off interrupts speech (distinct tone); background jobs keep running

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LzKpYaEsuWDevuNnvSjcFW
This commit is contained in:
idriz
2026-09-02 20:07:01 +02:00
parent 69b6c838b0
commit ab638aa690
2 changed files with 38 additions and 3 deletions

View File

@@ -91,6 +91,9 @@ class Assistant:
self.conn = None self.conn = None
self.jobs: set[asyncio.Task] = set() self.jobs: set[asyncio.Task] = set()
self.greeted = False self.greeted = False
self.current_response = ""
self.response_active = False
self.cancelled_responses: set[str] = set()
# ---------- alati ---------- # ---------- alati ----------
async def tool_wikipedia(self, args: dict) -> str: async def tool_wikipedia(self, args: dict) -> str:
@@ -155,6 +158,20 @@ class Assistant:
ok = False ok = False
await self.notify_model(("GOTOVO. " if ok else "GREŠKA. ") + result + " Reci to djetetu kratko, na bosanskom.") 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: async def notify_model(self, text: str) -> None:
if not self.conn: if not self.conn:
return return
@@ -193,19 +210,20 @@ class Assistant:
present = False present = False
while True: while True:
now = asyncio.get_event_loop().time() 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) present = await audio.source_present(self.cfg.mic_device)
last_check = now last_check = now
if not present: if not present:
if self.mic.running: if self.mic.running:
await self.mic.stop() await self.mic.stop()
log.info("mikrofon isključen (prekidač), ne slušam") log.info("mikrofon isključen (prekidač), ne slušam")
await asyncio.sleep(0.5) await self.interrupt_speech()
await asyncio.sleep(0.3)
continue continue
if not self.mic.running: if not self.mic.running:
await self.mic.start() await self.mic.start()
log.info("mikrofon uključen, slušam") 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() chunk = await self.mic.read()
if not chunk: if not chunk:
await self.mic.stop() await self.mic.stop()
@@ -220,7 +238,14 @@ class Assistant:
async for event in self.conn: async for event in self.conn:
t = event.type t = event.type
if t == "response.output_audio.delta": if t == "response.output_audio.delta":
if getattr(event, "response_id", "") in self.cancelled_responses:
continue
await self.speaker.write(base64.b64decode(event.delta)) 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": elif t == "response.output_audio_transcript.delta":
transcript += event.delta transcript += event.delta
elif t == "response.output_audio_transcript.done": elif t == "response.output_audio_transcript.done":

View File

@@ -36,6 +36,16 @@ def tone(freq: float = 880.0, ms: int = 150, volume: float = 0.3) -> bytes:
return samples.tobytes() 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: async def source_present(name: str) -> bool:
"""Da li PipeWire trenutno ima izvor tog imena (USB mikrofon nestane kad se isključi prekidač).""" """Da li PipeWire trenutno ima izvor tog imena (USB mikrofon nestane kad se isključi prekidač)."""
if not name: if not name: