mirror of
https://github.com/yusufipk/dikte.git
synced 2026-09-11 10:56:10 +00:00
Let no failure eat a dictation, and none strand the state machine
The recorded WAV was deleted in a finally that did not care why the run ended, so a whisper server being down destroyed the only copy of the user's speech; a failed run now keeps its audio in the recordings directory and names the path in the error. The history was written only after the paste, and a failed key press restored the previous clipboard over the fresh transcript: text not pasted, not on the clipboard, not in the history, audio gone, all from one refused key. The history now comes first, a failed press is a warning the row is amended to carry, and the clipboard keeps the text the user has to paste by hand. Kept recordings no longer overwrite each other inside one second, the keep_audio move failing no longer falls through to the delete, and the history row records whether cleanup actually ran rather than what the dictation gate implies about an ask. In the application: a recorder that failed to start emitted its error synchronously and start() then wrote RECORDING over the handler's IDLE, one more key press away from a BUSY nothing would ever end; start now checks the recorder is running, like start_meeting always has. The new died signal ends the run properly and transcribes what was captured. A --paste override armed by a request that no-opped stopped haunting some later unrelated run, and it dies with a cancelled or failed one. The toggle, ask and pause debounce timers are per action, so a pause right after a toggle is a pause and not a duplicate. Waiters outstanding at quit or restart are settled instead of being read back as "the instance is too old". The listing warm-up and the one-per-press backend lookup move off the key press. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
69194186c8
commit
6f79e93d53
+54
-4
@@ -30,6 +30,7 @@ class Chain(DikteTest):
|
||||
|
||||
def run_chain(self, ask=False, paste_override=None, duration=2.0,
|
||||
transcript="uh, book it for Thursday",
|
||||
transcribe_error=None,
|
||||
cleaned="Book it for Thursday.",
|
||||
cleanup_error=None, answer=("Booked.", ""), rms=None,
|
||||
clipboard=b"what was there before", paste_error=None):
|
||||
@@ -46,7 +47,10 @@ class Chain(DikteTest):
|
||||
# The chain reports its own failures on stderr, which a test run has no
|
||||
# use for.
|
||||
with contextlib.redirect_stderr(io.StringIO()), \
|
||||
mock.patch.object(api, "transcribe", return_value=transcript) as tr, \
|
||||
mock.patch.object(
|
||||
api, "transcribe",
|
||||
**({"side_effect": transcribe_error} if transcribe_error
|
||||
else {"return_value": transcript})) as tr, \
|
||||
mock.patch.object(api, "cleanup", cleanup), \
|
||||
mock.patch.object(assistant, "ask", return_value=answer) as ask_call, \
|
||||
mock.patch.object(paste, "copy") as copy, \
|
||||
@@ -108,11 +112,57 @@ class Chain(DikteTest):
|
||||
run = self.run_chain()
|
||||
run["copy_bytes"].assert_not_called()
|
||||
|
||||
def test_the_clipboard_is_put_back_when_the_keypress_fails(self):
|
||||
def test_a_failed_keypress_leaves_the_transcript_on_the_clipboard(self):
|
||||
"""The press failing is a warning, not a lost dictation: restoring the
|
||||
old clipboard over the text would leave nothing to paste by hand."""
|
||||
self.conf["restore_clipboard"] = True
|
||||
run = self.run_chain(paste_error=paste.PasteError("not trusted"))
|
||||
self.assertIn("not trusted", run["failures"][0])
|
||||
run["copy_bytes"].assert_called_once_with(b"what was there before")
|
||||
self.assertEqual(run["failures"], [])
|
||||
raw, text, warning = run["done"][0]
|
||||
self.assertIn("not trusted", warning)
|
||||
run["copy_bytes"].assert_not_called()
|
||||
|
||||
def test_a_failed_keypress_still_reaches_the_history(self):
|
||||
self.run_chain(paste_error=paste.PasteError("not trusted"))
|
||||
rows = cfg.read_history()
|
||||
self.assertEqual(len(rows), 1)
|
||||
self.assertEqual(rows[0]["text"], "Book it for Thursday.")
|
||||
# The row goes in before the paste is attempted, so the paste failing
|
||||
# has to be written back into it: the record tells the whole truth.
|
||||
self.assertIn("not trusted", rows[0]["cleanup_error"])
|
||||
|
||||
def test_a_failed_transcription_keeps_the_audio(self):
|
||||
"""Speech the user cannot repeat from memory must survive the failure."""
|
||||
run = self.run_chain(transcribe_error=api.ApiError("server down"))
|
||||
self.assertIn("server down", run["failures"][0])
|
||||
self.assertIn("kept", run["failures"][0])
|
||||
kept = list(cfg.RECORDINGS_DIR.glob("*.wav"))
|
||||
self.assertEqual(len(kept), 1)
|
||||
self.assertFalse(os.path.exists(self.wav))
|
||||
|
||||
def test_two_failures_in_one_second_keep_both_recordings(self):
|
||||
self.run_chain(transcribe_error=api.ApiError("down"))
|
||||
self.wav = make_wav(self.path("clip2.wav"), speech(2.0))
|
||||
with mock.patch.object(worker.time, "strftime",
|
||||
return_value="20260820-120000"):
|
||||
self.run_chain(transcribe_error=api.ApiError("down"))
|
||||
self.wav = make_wav(self.path("clip3.wav"), speech(2.0))
|
||||
self.run_chain(transcribe_error=api.ApiError("down"))
|
||||
self.assertEqual(len(list(cfg.RECORDINGS_DIR.glob("*.wav"))), 3)
|
||||
|
||||
def test_the_history_row_says_whether_cleanup_actually_ran(self):
|
||||
"""The ask path cleans under its own setting; the record follows the
|
||||
run, not the dictation gate."""
|
||||
self.conf["cleanup_enabled"] = False
|
||||
self.conf["assistant_cleanup"] = True
|
||||
self.run_chain(ask=True)
|
||||
row = cfg.read_history()[0]
|
||||
self.assertNotEqual(row["cleanup_model"], "")
|
||||
cfg.clear_history()
|
||||
self.conf["cleanup_enabled"] = True
|
||||
self.conf["assistant_cleanup"] = False
|
||||
self.run_chain(ask=True)
|
||||
self.assertEqual(cfg.read_history()[0]["cleanup_model"], "")
|
||||
|
||||
def test_the_transcription_is_told_the_language_and_the_glossary(self):
|
||||
self.conf["language"] = "tr"
|
||||
|
||||
Reference in New Issue
Block a user