chartremotely 0.2.14__tar.gz → 0.2.15__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.
- {chartremotely-0.2.14 → chartremotely-0.2.15}/PKG-INFO +1 -1
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/shortcut.py +2 -2
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/voice.py +16 -8
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely.egg-info/PKG-INFO +1 -1
- {chartremotely-0.2.14 → chartremotely-0.2.15}/pyproject.toml +1 -1
- {chartremotely-0.2.14 → chartremotely-0.2.15}/tests/test_voice.py +14 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/LICENSE +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/README.md +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/__init__.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/answer.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/assets/shortcut.plist +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/ax.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/cli.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/config.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/displays.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/doctor.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/guilock.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/hearing.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/keystore.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/layout.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/mcpclient.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/patron.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/popups.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/push.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/recent.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/registry.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/relay.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/requestlog.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/resolve.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/scales.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/screen.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/server.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/services.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/setup.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/snapshot.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/studies.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/symbol.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/tailnet.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/timeframe.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/understand.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/vocab.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely/window.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely.egg-info/SOURCES.txt +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely.egg-info/dependency_links.txt +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely.egg-info/entry_points.txt +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely.egg-info/requires.txt +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/chartremotely.egg-info/top_level.txt +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/setup.cfg +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/tests/test_hearing.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/tests/test_patron.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/tests/test_popups.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/tests/test_push.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/tests/test_recent.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/tests/test_relay.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/tests/test_requestlog.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/tests/test_resolve.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/tests/test_scales.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/tests/test_screen.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/tests/test_setup.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/tests/test_snapshot.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/tests/test_stamp_changelog.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/tests/test_timeframe.py +0 -0
- {chartremotely-0.2.14 → chartremotely-0.2.15}/tests/test_understand.py +0 -0
|
@@ -5,8 +5,8 @@ placeholders where its address and token go. Talk, or type when it asks:
|
|
|
5
5
|
|
|
6
6
|
* It records one sentence - "Palantir, half, on mac mini" - posts it to this
|
|
7
7
|
Mac, which hears it with Whisper (:mod:`voice`), and speaks the reply. When
|
|
8
|
-
the reply is a question (it ends "I'm listening.") it records again
|
|
9
|
-
|
|
8
|
+
the reply is a question (it ends "I'm listening.") it records again; the
|
|
9
|
+
third spoken miss asks its question in the text box instead.
|
|
10
10
|
* When nothing usable was recorded (no microphone, silence, noise) the reply
|
|
11
11
|
is ``TYPE:`` and a prompt: the Shortcut shows that prompt in a one-line
|
|
12
12
|
text box and posts the typed sentence instead. On a Mac it goes straight
|
|
@@ -10,7 +10,9 @@ PLTR at half scale sent to mac mini. Good luck."
|
|
|
10
10
|
A follow-up question ends with :data:`LISTENING`. The Shortcut records again
|
|
11
11
|
when it hears that word, and what was understood the first time is held for
|
|
12
12
|
:data:`FOLLOW_UP_SECONDS`: "Palantir" then "half" is one request, as is
|
|
13
|
-
"Palantir half" then "PLTR" after "Did you mean PLTR or PLTX?".
|
|
13
|
+
"Palantir half" then "PLTR" after "Did you mean PLTR or PLTX?". Speech gets
|
|
14
|
+
:data:`VOICE_TRIES` chances, as Siri gives: the last spoken miss asks its
|
|
15
|
+
question in the text box instead, with what was heard so far still held.
|
|
14
16
|
|
|
15
17
|
When nothing usable was recorded - no microphone, silence, noise - the
|
|
16
18
|
reply is not a sentence but :data:`TYPE` and a prompt. The Shortcut shows
|
|
@@ -42,25 +44,28 @@ TYPED_MAX = 200
|
|
|
42
44
|
#: The word every follow-up question ends with; the Shortcut listens again on it.
|
|
43
45
|
LISTENING = "I'm listening."
|
|
44
46
|
FOLLOW_UP_SECONDS = 45.0
|
|
47
|
+
#: Spoken tries at one request before the question moves to the text box.
|
|
48
|
+
VOICE_TRIES = 3
|
|
45
49
|
|
|
46
50
|
_pending: dict = {}
|
|
47
51
|
_pending_lock = threading.Lock()
|
|
48
52
|
|
|
49
53
|
|
|
50
|
-
def _held(now: float) -> Understanding | None:
|
|
54
|
+
def _held(now: float) -> tuple[Understanding | None, int]:
|
|
55
|
+
"""The request being completed and how many spoken tries it has had."""
|
|
51
56
|
with _pending_lock:
|
|
52
57
|
got = _pending.get("u")
|
|
53
58
|
if got and now - _pending.get("at", 0.0) <= FOLLOW_UP_SECONDS:
|
|
54
|
-
return got
|
|
59
|
+
return got, _pending.get("misses", 0)
|
|
55
60
|
_pending.clear()
|
|
56
|
-
return None
|
|
61
|
+
return None, 0
|
|
57
62
|
|
|
58
63
|
|
|
59
|
-
def _hold(u: Understanding | None, now: float) -> None:
|
|
64
|
+
def _hold(u: Understanding | None, now: float, misses: int = 0) -> None:
|
|
60
65
|
with _pending_lock:
|
|
61
66
|
_pending.clear()
|
|
62
67
|
if u is not None:
|
|
63
|
-
_pending.update(u=u, at=now)
|
|
68
|
+
_pending.update(u=u, at=now, misses=misses)
|
|
64
69
|
|
|
65
70
|
|
|
66
71
|
def merge(new: Understanding, old: Understanding | None) -> Understanding:
|
|
@@ -110,11 +115,14 @@ def command(u: Understanding) -> str:
|
|
|
110
115
|
def _answer(text: str, rows: list[dict], lately: list[str], places: list[str],
|
|
111
116
|
send: Callable[[str, str], str], at: float, typed: bool) -> str:
|
|
112
117
|
"""Understand one request, spoken or typed: ask for what is missing, or send it."""
|
|
113
|
-
|
|
118
|
+
old, misses = _held(at)
|
|
119
|
+
got = merge(understand(text, recent=lately, displays=places, rows=rows), old)
|
|
114
120
|
names = {t: resolve.spoken_name(r["n"]) for r in rows if (t := r["t"]) in got.ambiguous}
|
|
121
|
+
misses = misses if typed else misses + 1
|
|
122
|
+
typed = typed or misses >= VOICE_TRIES
|
|
115
123
|
ask = question(got, names, "Type" if typed else "Say")
|
|
116
124
|
if ask:
|
|
117
|
-
_hold(got, at)
|
|
125
|
+
_hold(got, at, misses)
|
|
118
126
|
requestlog.request("type" if typed else "hear", "ask", got.where or "", "")
|
|
119
127
|
return typing(ask) if typed else f"{ask} {LISTENING}"
|
|
120
128
|
_hold(None, at)
|
|
@@ -97,6 +97,20 @@ def test_a_follow_up_expires():
|
|
|
97
97
|
assert ear.respond() == f"Which company at daily? {voice.LISTENING}"
|
|
98
98
|
|
|
99
99
|
|
|
100
|
+
def test_the_third_spoken_miss_asks_in_the_text_box_and_keeps_what_was_heard():
|
|
101
|
+
ear = Ear("mumble", "Nvidia", "grumble")
|
|
102
|
+
assert ear.respond().endswith(voice.LISTENING)
|
|
103
|
+
assert ear.respond() == f"I heard Nvidia but no scale. Say the scale, like half or daily. {voice.LISTENING}"
|
|
104
|
+
assert ear.respond() == "TYPE:I heard Nvidia but no scale. Type the scale, like half or daily."
|
|
105
|
+
assert ear.typed("daily") == "sent set NVDA | daily to this Mac"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_a_heard_request_resets_the_spoken_tries():
|
|
109
|
+
ear = Ear("mumble", "mumble", "Palantir half", "mumble")
|
|
110
|
+
ear.respond(), ear.respond(), ear.respond()
|
|
111
|
+
assert ear.respond().endswith(voice.LISTENING)
|
|
112
|
+
|
|
113
|
+
|
|
100
114
|
def test_a_completed_request_is_not_carried_into_the_next():
|
|
101
115
|
ear = Ear("Palantir half", "daily")
|
|
102
116
|
ear.respond()
|
|
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
|