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:
@@ -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":
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user