python-voiceio 0.9.7__tar.gz → 0.9.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_voiceio-0.9.7/python_voiceio.egg-info → python_voiceio-0.9.9}/PKG-INFO +1 -1
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/pyproject.toml +1 -1
- {python_voiceio-0.9.7 → python_voiceio-0.9.9/python_voiceio.egg-info}/PKG-INFO +1 -1
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_platform.py +31 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_postcorrect.py +8 -16
- python_voiceio-0.9.9/voiceio/__init__.py +1 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/platform.py +21 -6
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/postcorrect.py +37 -29
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/tray/__init__.py +1 -1
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/tray/_pystray.py +1 -1
- python_voiceio-0.9.7/voiceio/__init__.py +0 -1
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/LICENSE +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/README.md +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/python_voiceio.egg-info/SOURCES.txt +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/python_voiceio.egg-info/dependency_links.txt +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/python_voiceio.egg-info/entry_points.txt +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/python_voiceio.egg-info/requires.txt +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/python_voiceio.egg-info/top_level.txt +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/setup.cfg +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_adjudicate.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_app_wiring.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_audio_quality.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_audit.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_autocorrect.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_autocorrect_state.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_backend_probes.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_cli.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_clipboard_read.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_commands.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_concurrency_lockdown.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_config.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_correct_batch.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_corrections.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_evaluate.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_fallback.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_health.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_hints.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_history.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_ibus_pending.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_ibus_ping.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_ibus_typer.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_llm.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_llm_api.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_numbers.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_postprocess.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_prebuffer.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_prompt.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_recorder_integration.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_retention.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_robustness.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_security_hardening.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_snapshots.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_streaming.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_tokens.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_transcriber.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_tts.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_vad.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_vocabulary.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_wizard.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/tests/test_wordfreq.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/__main__.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/app.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/audit.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/autocorrect.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/autocorrect_state.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/backends.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/cli.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/clipboard_read.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/commands.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/config.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/consent.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/corrections.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/demo.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/evaluate.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/feedback.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/health.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/hints.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/history.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/hotkeys/__init__.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/hotkeys/base.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/hotkeys/chain.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/hotkeys/evdev.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/hotkeys/pynput_backend.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/hotkeys/socket_backend.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/ibus/__init__.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/ibus/engine.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/ibus/pending.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/llm.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/llm_api.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/models/__init__.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/models/silero_vad.onnx +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/numbers.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/pidlock.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/postprocess.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/prompt.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/recorder.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/retention.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/service.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/snapshots.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/sounds/__init__.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/sounds/commit.wav +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/sounds/start.wav +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/sounds/stop.wav +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/streaming.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/tokens.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/transcriber.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/tray/_icons.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/tray/_indicator.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/tts/__init__.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/tts/base.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/tts/chain.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/tts/edge_engine.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/tts/espeak.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/tts/piper_engine.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/tts/player.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/typers/__init__.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/typers/base.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/typers/chain.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/typers/clipboard.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/typers/ibus.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/typers/pynput_type.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/typers/wtype.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/typers/xdotool.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/typers/ydotool.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/vad.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/vocab_stats.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/vocabulary.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/wizard.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/wordfreq.py +0 -0
- {python_voiceio-0.9.7 → python_voiceio-0.9.9}/voiceio/worker.py +0 -0
|
@@ -72,3 +72,34 @@ def test_platform_frozen():
|
|
|
72
72
|
import pytest
|
|
73
73
|
with pytest.raises(AttributeError):
|
|
74
74
|
p.os = "darwin"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TestOpenInTerminal:
|
|
78
|
+
"""The tray menu launches print-and-exit commands (history/doctor/logs);
|
|
79
|
+
hold=True must keep the window open instead of flashing it shut."""
|
|
80
|
+
|
|
81
|
+
def _capture(self, monkeypatch, *, hold):
|
|
82
|
+
import voiceio.platform as plat
|
|
83
|
+
monkeypatch.setattr(plat, "_detect_os", lambda: "linux")
|
|
84
|
+
monkeypatch.setenv("TERMINAL", "myterm")
|
|
85
|
+
monkeypatch.setattr(
|
|
86
|
+
plat.shutil, "which",
|
|
87
|
+
lambda b: "/usr/bin/myterm" if b == "myterm" else None,
|
|
88
|
+
)
|
|
89
|
+
calls = []
|
|
90
|
+
with patch("subprocess.Popen", side_effect=lambda args, *a, **k: calls.append(args)):
|
|
91
|
+
ok = plat.open_in_terminal(["voiceio", "history"], hold=hold)
|
|
92
|
+
assert ok is True
|
|
93
|
+
return calls[0]
|
|
94
|
+
|
|
95
|
+
def test_hold_wraps_in_shell_that_pauses(self, monkeypatch):
|
|
96
|
+
args = self._capture(monkeypatch, hold=True)
|
|
97
|
+
assert args[:2] == ["myterm", "-e"]
|
|
98
|
+
assert "sh" in args and "-c" in args
|
|
99
|
+
script = args[-1]
|
|
100
|
+
assert "voiceio history" in script
|
|
101
|
+
assert "read" in script # pauses so the window survives
|
|
102
|
+
|
|
103
|
+
def test_no_hold_runs_command_directly(self, monkeypatch):
|
|
104
|
+
args = self._capture(monkeypatch, hold=False)
|
|
105
|
+
assert args == ["myterm", "-e", "voiceio", "history"]
|
|
@@ -121,23 +121,15 @@ def test_disfluency_mode_rejects_rewording():
|
|
|
121
121
|
assert pc.correct(original) == original
|
|
122
122
|
|
|
123
123
|
|
|
124
|
-
def
|
|
125
|
-
"""
|
|
126
|
-
|
|
124
|
+
def test_disfluency_mode_allows_trailing_shrug_with_negation():
|
|
125
|
+
"""No mechanical negation guard — the LLM decides. Dropping a trailing
|
|
126
|
+
'I don't know' shrug (plus other filler) is accepted; the model, not a
|
|
127
|
+
regex, is trusted to avoid genuine polarity flips (see the prompt)."""
|
|
127
128
|
pc = PostCorrector(_cfg(remove_disfluencies=True))
|
|
128
|
-
original = "I
|
|
129
|
-
|
|
130
|
-
with patch("voiceio.llm_api.chat", return_value=
|
|
131
|
-
assert pc.correct(original) ==
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
def test_disfluency_mode_rejects_replaced_negation():
|
|
135
|
-
"""Replacing a negation with something else also inverts meaning."""
|
|
136
|
-
pc = PostCorrector(_cfg(remove_disfluencies=True))
|
|
137
|
-
original = "we should never deploy on a friday afternoon"
|
|
138
|
-
changed = "we should always deploy on a friday afternoon"
|
|
139
|
-
with patch("voiceio.llm_api.chat", return_value=changed):
|
|
140
|
-
assert pc.correct(original) == original
|
|
129
|
+
original = "the build passed but I don't know we should still check it carefully today"
|
|
130
|
+
cleaned = "the build passed we should still check it carefully today"
|
|
131
|
+
with patch("voiceio.llm_api.chat", return_value=cleaned):
|
|
132
|
+
assert pc.correct(original) == cleaned
|
|
141
133
|
|
|
142
134
|
|
|
143
135
|
def test_fix_mode_still_rejects_big_deletion():
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.9.9"
|
|
@@ -311,8 +311,14 @@ def system_deps_install_cmd(keys: list[str] | None = None) -> str:
|
|
|
311
311
|
return f"{prefix} {' '.join(pkgs)}"
|
|
312
312
|
|
|
313
313
|
|
|
314
|
-
def open_in_terminal(cmd: list[str]) -> bool:
|
|
315
|
-
"""Launch a command in the user's terminal emulator. Returns success.
|
|
314
|
+
def open_in_terminal(cmd: list[str], *, hold: bool = False) -> bool:
|
|
315
|
+
"""Launch a command in the user's terminal emulator. Returns success.
|
|
316
|
+
|
|
317
|
+
``hold=True`` keeps the window open after the command exits — required for
|
|
318
|
+
print-and-exit commands (history, doctor, logs) that would otherwise flash
|
|
319
|
+
a terminal that closes the instant they finish.
|
|
320
|
+
"""
|
|
321
|
+
import shlex
|
|
316
322
|
import subprocess
|
|
317
323
|
|
|
318
324
|
plat_os = _detect_os()
|
|
@@ -334,11 +340,20 @@ def open_in_terminal(cmd: list[str]) -> bool:
|
|
|
334
340
|
log.warning("Failed to open cmd.exe")
|
|
335
341
|
return False
|
|
336
342
|
|
|
337
|
-
# Linux: try common terminal emulators
|
|
343
|
+
# Linux: try common terminal emulators. When holding, wrap the command in a
|
|
344
|
+
# shell that pauses at the end so the window survives a print-and-exit.
|
|
345
|
+
launch = cmd
|
|
346
|
+
if hold:
|
|
347
|
+
inner = " ".join(shlex.quote(c) for c in cmd)
|
|
348
|
+
launch = [
|
|
349
|
+
"sh", "-c",
|
|
350
|
+
f'{inner}; printf "\\n[voiceio] Press Enter to close…"; read _',
|
|
351
|
+
]
|
|
352
|
+
|
|
338
353
|
term = os.environ.get("TERMINAL")
|
|
339
354
|
if term and shutil.which(term):
|
|
340
355
|
try:
|
|
341
|
-
subprocess.Popen([term, "-e", *
|
|
356
|
+
subprocess.Popen([term, "-e", *launch])
|
|
342
357
|
return True
|
|
343
358
|
except OSError:
|
|
344
359
|
pass
|
|
@@ -346,7 +361,7 @@ def open_in_terminal(cmd: list[str]) -> bool:
|
|
|
346
361
|
# x-terminal-emulator (Debian/Ubuntu alternative)
|
|
347
362
|
if shutil.which("x-terminal-emulator"):
|
|
348
363
|
try:
|
|
349
|
-
subprocess.Popen(["x-terminal-emulator", "-e", *
|
|
364
|
+
subprocess.Popen(["x-terminal-emulator", "-e", *launch])
|
|
350
365
|
return True
|
|
351
366
|
except OSError:
|
|
352
367
|
pass
|
|
@@ -365,7 +380,7 @@ def open_in_terminal(cmd: list[str]) -> bool:
|
|
|
365
380
|
for prefix, binary in _TERMINALS:
|
|
366
381
|
if shutil.which(binary):
|
|
367
382
|
try:
|
|
368
|
-
subprocess.Popen([*prefix, *
|
|
383
|
+
subprocess.Popen([*prefix, *launch])
|
|
369
384
|
return True
|
|
370
385
|
except OSError:
|
|
371
386
|
continue
|
|
@@ -34,18 +34,17 @@ _MAX_INSERTED_WORDS = 0 # adding any word = altering meaning → reject
|
|
|
34
34
|
_MAX_REPLACE_FRAC = 0.15 # ASR word-fixes only, never wholesale rewording
|
|
35
35
|
_MAX_DELETE_FRAC = 0.4 # backstop against deleting real content
|
|
36
36
|
|
|
37
|
-
# Words whose deletion/replacement flips meaning — the fraction caps can't catch
|
|
38
|
-
# a single dropped "not". If the edit touches any of these on the original side,
|
|
39
|
-
# reject outright. (Contractions ending in "n't" are handled separately.)
|
|
40
|
-
_MEANING_CRITICAL = frozenset({
|
|
41
|
-
"not", "no", "never", "none", "nor", "neither", "without", "cannot",
|
|
42
|
-
"nothing", "nobody", "nowhere", "n't",
|
|
43
|
-
})
|
|
44
37
|
|
|
38
|
+
def _norm_words(text: str) -> list[str]:
|
|
39
|
+
"""Lowercased, punctuation-stripped word list for the guard's diff, so a
|
|
40
|
+
sentence split or capitalization isn't misread as a content change."""
|
|
41
|
+
out = []
|
|
42
|
+
for w in text.split():
|
|
43
|
+
t = w.lower().strip(".,;:!?\"'()[]{}—–-")
|
|
44
|
+
if t:
|
|
45
|
+
out.append(t)
|
|
46
|
+
return out
|
|
45
47
|
|
|
46
|
-
def _is_meaning_critical(word: str) -> bool:
|
|
47
|
-
w = word.lower().strip(".,;:!?\"'()")
|
|
48
|
-
return w in _MEANING_CRITICAL or w.endswith("n't")
|
|
49
48
|
|
|
50
49
|
_SYSTEM_PROMPT = (
|
|
51
50
|
"You fix automatic speech recognition errors in dictated text. "
|
|
@@ -59,18 +58,27 @@ _SYSTEM_PROMPT = (
|
|
|
59
58
|
# Disfluency mode: also strip spoken filler, delete-only. The strict rules
|
|
60
59
|
# mirror the guards — the model is told exactly what the diff check enforces.
|
|
61
60
|
_SYSTEM_PROMPT_CLEAN = (
|
|
62
|
-
"You convert dictated speech into clean written text. The user
|
|
63
|
-
"about software engineering and their projects
|
|
61
|
+
"You convert dictated speech into clean, concise written text. The user "
|
|
62
|
+
"dictates about software engineering and their projects, and wants it to "
|
|
63
|
+
"read like something they wrote, not spoke. Do exactly two things:\n"
|
|
64
64
|
"1. Fix words the recognizer misheard (wrong proper nouns, homophones, "
|
|
65
65
|
"garbled technical terms).\n"
|
|
66
|
-
"2.
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"
|
|
66
|
+
"2. Cut spoken disfluency so it reads tight and clear:\n"
|
|
67
|
+
" - filler sounds (um, uh, er);\n"
|
|
68
|
+
" - filler discourse markers with no content: leading 'so', 'yeah', "
|
|
69
|
+
"'okay', 'well', 'now', 'right', and 'you know', 'I mean', 'like', 'sort "
|
|
70
|
+
"of'/'kind of' when used as filler, 'or something'/'or whatever', a "
|
|
71
|
+
"trailing 'right?' tag;\n"
|
|
72
|
+
" - false starts and self-corrections (keep only the corrected version);\n"
|
|
73
|
+
" - stray word and phrase repetitions.\n"
|
|
74
|
+
"Lean toward the briefer version — remove hesitation and padding freely.\n"
|
|
75
|
+
"HARD LIMITS (these protect meaning): only DELETE and FIX — NEVER add "
|
|
76
|
+
"words, rephrase, reword, reorder, or summarize. NEVER change what a "
|
|
77
|
+
"statement asserts — above all never flip its polarity (do NOT turn 'I "
|
|
78
|
+
"don't think we should ship' into 'we should ship'), and keep hedges that "
|
|
79
|
+
"carry real uncertainty about a claim ('maybe', 'probably'). A trailing "
|
|
80
|
+
"verbal shrug like 'I don't know' with nothing after it is filler you may "
|
|
81
|
+
"drop. Keep the speaker's own wording. Return only the cleaned text."
|
|
74
82
|
)
|
|
75
83
|
|
|
76
84
|
_MAX_RECENT = 3
|
|
@@ -307,11 +315,13 @@ class PostCorrector:
|
|
|
307
315
|
def _guard(self, text: str, corrected: str) -> tuple[bool, str]:
|
|
308
316
|
"""Decide whether the LLM's edit is within bounds.
|
|
309
317
|
|
|
310
|
-
Returns (accept, reject_reason). In disfluency mode the
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
318
|
+
Returns (accept, reject_reason). In disfluency mode the LLM is trusted
|
|
319
|
+
to judge what's filler; the diff only backstops gross failure — nothing
|
|
320
|
+
ADDED (no rewrite/hallucination) and not more than a bounded fraction
|
|
321
|
+
deleted or word-swapped. Comparison is on normalized tokens (lowercased,
|
|
322
|
+
punctuation stripped) so cosmetic restructuring — a sentence split, a
|
|
323
|
+
capital letter, a comma — is never miscounted as rewording. Outside
|
|
324
|
+
disfluency mode the original conservative fix-only guards apply.
|
|
315
325
|
"""
|
|
316
326
|
aw, bw = text.split(), corrected.split()
|
|
317
327
|
if not self._remove_disfluencies:
|
|
@@ -322,18 +332,16 @@ class PostCorrector:
|
|
|
322
332
|
return False, "editratio"
|
|
323
333
|
return True, ""
|
|
324
334
|
|
|
335
|
+
na, nb = _norm_words(text), _norm_words(corrected)
|
|
336
|
+
aw, bw = na, nb
|
|
325
337
|
inserted = replaced = deleted = 0
|
|
326
338
|
for tag, i1, i2, j1, j2 in difflib.SequenceMatcher(a=aw, b=bw).get_opcodes():
|
|
327
339
|
if tag == "insert":
|
|
328
340
|
inserted += j2 - j1
|
|
329
341
|
elif tag == "replace":
|
|
330
342
|
replaced += max(i2 - i1, j2 - j1)
|
|
331
|
-
if any(_is_meaning_critical(w) for w in aw[i1:i2]):
|
|
332
|
-
return False, "negation" # e.g. "not" → something else
|
|
333
343
|
elif tag == "delete":
|
|
334
344
|
deleted += i2 - i1
|
|
335
|
-
if any(_is_meaning_critical(w) for w in aw[i1:i2]):
|
|
336
|
-
return False, "negation" # dropping "not" inverts meaning
|
|
337
345
|
n = len(aw)
|
|
338
346
|
if inserted > _MAX_INSERTED_WORDS:
|
|
339
347
|
return False, "inserted" # added content — meaning changed
|
|
@@ -146,7 +146,7 @@ def _read_stdout(proc: subprocess.Popen, toggle_cb: Callable[[], None]) -> None:
|
|
|
146
146
|
action = cmd[5:]
|
|
147
147
|
cli_cmd = _MENU_COMMANDS.get(action)
|
|
148
148
|
if cli_cmd:
|
|
149
|
-
if not open_in_terminal(cli_cmd):
|
|
149
|
+
if not open_in_terminal(cli_cmd, hold=True):
|
|
150
150
|
log.warning("Failed to open terminal for: %s", action)
|
|
151
151
|
except (OSError, ValueError):
|
|
152
152
|
pass
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.9.7"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|