...
This commit is contained in:
+52
-2
@@ -63,6 +63,13 @@ class PetController(QObject):
|
||||
self._nap_forced: Optional[bool] = None # petctl nap on/off overrides the schedule
|
||||
self._last_nap_check = 0.0
|
||||
|
||||
# When a reply ends on a question the pet keeps listening for the
|
||||
# answer. _follow_ups counts how many have chained without you
|
||||
# re-triggering, so a server that ends every reply with "?" can't
|
||||
# loop forever off mic noise.
|
||||
self._pending_follow_up = False
|
||||
self._follow_ups = 0
|
||||
|
||||
self._last_update_check = 0.0
|
||||
self._update_pending = False # applied on disk, waiting for the restart
|
||||
|
||||
@@ -200,9 +207,21 @@ class PetController(QObject):
|
||||
self._near_misses.observe(score, threshold, time.time())
|
||||
|
||||
def _handle_conversation_turn(self) -> None:
|
||||
# A turn you started yourself ends any follow-up chain in progress.
|
||||
following_up, self._pending_follow_up = self._pending_follow_up, False
|
||||
if not following_up:
|
||||
self._follow_ups = 0
|
||||
|
||||
self._state.transition(PetState.LISTENING)
|
||||
pcm = mic.record_utterance(self._stream, should_continue=self._should_continue)
|
||||
pcm = mic.record_utterance(
|
||||
self._stream,
|
||||
should_continue=self._should_continue,
|
||||
# Answering a question deserves longer than saying the wake word
|
||||
# on purpose does — you were just asked something.
|
||||
grace_s=config.FOLLOW_UP_GRACE_SECONDS if following_up else None,
|
||||
)
|
||||
if pcm is None:
|
||||
self._follow_ups = 0 # silence ends the chain
|
||||
self._state.transition(PetState.IDLE)
|
||||
return
|
||||
|
||||
@@ -270,6 +289,9 @@ class PetController(QObject):
|
||||
on_error=lambda exc: self.log.emit(f"TTS failed: {exc}"),
|
||||
should_stop=should_stop,
|
||||
)
|
||||
# Read the scoring history *before* resetting, or the log reports the
|
||||
# blank counters instead of what actually fired.
|
||||
detail = self._barge_in_detail()
|
||||
if self._barge_in is not None:
|
||||
# Playback fed the pet's own voice into the wake model's rolling
|
||||
# window. Clear it before the idle listener starts scoring again,
|
||||
@@ -278,8 +300,36 @@ class PetController(QObject):
|
||||
if not completed:
|
||||
# You talked over it — take that as the start of the next turn
|
||||
# rather than making you say the wake word again.
|
||||
self.log.emit(f"Interrupted — listening. {self._barge_in_detail()}")
|
||||
self.log.emit(f"Interrupted — listening. {detail}")
|
||||
self._follow_ups = 0 # you're clearly engaged; start the count over
|
||||
self._talk_now.set()
|
||||
elif self._should_follow_up(text):
|
||||
self._follow_ups += 1
|
||||
cap = config.FOLLOW_UP_MAX_TURNS
|
||||
self.log.emit(
|
||||
f"Asked a question — listening for your answer "
|
||||
f"({self._follow_ups}{'/' + str(cap) if cap > 0 else ''})."
|
||||
)
|
||||
self._pending_follow_up = True
|
||||
self._talk_now.set()
|
||||
|
||||
def _should_follow_up(self, text: str) -> bool:
|
||||
"""Whether *text* leaves the pet waiting on an answer.
|
||||
|
||||
Muted is excluded because mute means "don't listen to me" — an
|
||||
automatic turn would walk straight past it. Napping isn't: quiet
|
||||
hours suppress the pet *starting* something, and a question is only
|
||||
ever asked in reply to you."""
|
||||
if not config.FOLLOW_UP_LISTEN or self._muted:
|
||||
return False
|
||||
if not speech_text.is_question(text):
|
||||
return False
|
||||
# Only worth mentioning the cap on a reply that would otherwise have
|
||||
# kept listening, or it fires on every statement the pet makes.
|
||||
if config.FOLLOW_UP_MAX_TURNS > 0 and self._follow_ups >= config.FOLLOW_UP_MAX_TURNS:
|
||||
self.log.emit("Follow-up limit reached — say the wake word to keep going.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def _barge_in_detail(self) -> str:
|
||||
"""Why the interruption fired, for the log. How far into playback it
|
||||
|
||||
Reference in New Issue
Block a user