llm: catch all exceptions, validate response, show transcription preview (v1.9.9)
- llm.py: catch OSError/TimeoutError (socket.timeout is NOT a URLError subclass) and bare Exception so no path kills the background thread silently - daemon.py: catch unexpected exceptions from llm.chat and surface them as overlay errors instead of silent thread death - daemon.py: reject LLM responses that are >80 % whitespace (cold-start model returning spaces/blank lines gets discarded, not typed) - daemon.py: show transcribed text in overlay immediately before thinking animation so user can see what was heard while LLM processes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
749ef78383
commit
0875fe7ac0
@ -510,6 +510,9 @@ class Daemon:
|
||||
return
|
||||
self._emit("busy", label, "Rewriting…")
|
||||
self._dnotify(f"⌛ {label}", "Rewriting…")
|
||||
# Show the transcribed text immediately so the user sees what was heard.
|
||||
if self.text_cb:
|
||||
self.text_cb(f"📝 {text}")
|
||||
# Stream the rewrite into the overlay so you watch the model write
|
||||
# (the bubble updates token-by-token). The delivered text is still
|
||||
# the complete result, typed once the rewrite finishes.
|
||||
@ -559,6 +562,27 @@ class Daemon:
|
||||
self._emit("error", label, str(exc))
|
||||
self._dnotify("Rewrite failed", str(exc), "critical")
|
||||
log(f"ERROR ({label} rewrite): {exc}")
|
||||
if self.text_cb:
|
||||
self.text_cb(f"✗ {exc}")
|
||||
return
|
||||
except Exception as exc: # noqa: BLE001 - guard against any uncaught error
|
||||
msg = f"LLM error: {exc}"
|
||||
self._emit("error", label, msg)
|
||||
log(f"ERROR ({label} rewrite unexpected): {exc}")
|
||||
if self.text_cb:
|
||||
self.text_cb(f"✗ {msg}")
|
||||
return
|
||||
|
||||
# Sanity-check: reject responses that are >80 % whitespace —
|
||||
# a model that's cold-starting or misconfigured sometimes streams
|
||||
# spaces or blank lines instead of real output.
|
||||
non_ws = sum(1 for c in text if not c.isspace())
|
||||
if non_ws < max(1, len(text) * 0.20):
|
||||
msg = "LLM returned mostly whitespace — discarded"
|
||||
self._emit("error", label, msg)
|
||||
log(f"ERROR ({label}): {msg} (len={len(text)})")
|
||||
if self.text_cb:
|
||||
self.text_cb(f"✗ {msg}")
|
||||
return
|
||||
|
||||
if not text:
|
||||
|
||||
@ -93,6 +93,13 @@ def chat(
|
||||
raise LLMError(f"Connection failed: {exc.reason}") from exc
|
||||
except (KeyError, IndexError, TypeError) as exc:
|
||||
raise LLMError(f"Unexpected response: {exc}") from exc
|
||||
except (TimeoutError, OSError) as exc:
|
||||
# socket.timeout (subclass of OSError / TimeoutError) fires when the server
|
||||
# stops sending data mid-stream. Not wrapped in URLError — must be caught
|
||||
# separately or it would propagate uncaught and kill the background thread.
|
||||
raise LLMError(f"Request timed out or connection lost: {exc}") from exc
|
||||
except Exception as exc: # noqa: BLE001
|
||||
raise LLMError(f"Unexpected error: {exc}") from exc
|
||||
|
||||
content = (content or "").strip()
|
||||
if not content:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user