diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-16 14:16:24 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-16 14:16:24 +0200 |
| commit | f99a9329caccdd14d9c530777a5e5b622d34f602 (patch) | |
| tree | fb8351c3485b534cfa2337801f1eea2d973b8f6a /desktop/assistant/Assistant.qml | |
| parent | 729e89b4dce7e6f89a8a20cb919ad1b05d825d09 (diff) | |
| download | quickshell-f99a9329caccdd14d9c530777a5e5b622d34f602.tar.gz quickshell-f99a9329caccdd14d9c530777a5e5b622d34f602.zip | |
fix(assistant): keep focus in the card, scope errors, guard links
From review of the canvas. Escape stopped working in three ways, each
leaving the keyboard grabbed with nothing to receive it: the history
field hiding took focus with it, a disabled input could not hold focus
while the daemon was down, and a click in a streaming reply focused text
the next flush destroyed. The card now gives focus back to the input
whenever it falls out, and the input is read-only rather than disabled.
Only an error the daemon marks as ending the reply (turn) closes the live
row; a command error, possibly another client's, used to freeze the reply
mid-stream. The running tool comes from state events, hello included.
Scrolling up during a stream no longer snaps back, since following stops
when a movement starts. History snippets go through Notify.sanitize like
any model output, links open only for http, https and mailto, and a line
opening with inline triple-backtick code is no longer taken as a fence.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'desktop/assistant/Assistant.qml')
| -rw-r--r-- | desktop/assistant/Assistant.qml | 20 |
1 files changed, 13 insertions, 7 deletions
diff --git a/desktop/assistant/Assistant.qml b/desktop/assistant/Assistant.qml index a04a24b..d143219 100644 --- a/desktop/assistant/Assistant.qml +++ b/desktop/assistant/Assistant.qml @@ -38,6 +38,9 @@ Scope { property var results: [] readonly property bool busy: root.state !== "idle" + // A daemon gone mid-reply sends no idle; its hello on return redraws. + onConnectedChanged: if (!root.connected) { root.state = "idle"; root.runningTool = ""; } + // One row per turn. tools is a comma-joined string: a ListModel role // holding an array becomes a nested model, which a Repeater can read but // setProperty cannot append to. @@ -107,6 +110,7 @@ Scope { switch (e.type) { case "hello": root.state = e.state; + root.runningTool = e.state === "tool" ? e.tool : ""; root.model = e.model ?? ""; root.ctxUsed = e.ctx_used; root.ctxMax = e.ctx_max; @@ -122,11 +126,10 @@ Scope { case "token": root.bufReply += e.delta; break; case "tool": root.flush(); - if (!e.done && i >= 0) { + if (!e.done && i >= 0 && turns.get(i).live) { const t = turns.get(i).tools; turns.setProperty(i, "tools", t ? t + "," + e.name : e.name); } - root.runningTool = e.done ? "" : e.name; break; case "done": root.flush(); @@ -135,19 +138,22 @@ Scope { turns.setProperty(i, "stopped", e.stopped === true); } if (!e.stopped) root.convId = e.id; - root.runningTool = ""; break; case "error": - root.flush(); - if (i >= 0 && turns.get(i).live) { + // Only an error marked turn ended the reply. A command error, + // perhaps another client's, leaves a streaming reply alone. + if (e.turn && i >= 0 && turns.get(i).live) { + root.flush(); turns.setProperty(i, "error", e.message); turns.setProperty(i, "live", false); } else { root.notice = e.message; } - root.runningTool = ""; break; - case "state": root.state = e.state; break; + case "state": + root.state = e.state; + root.runningTool = e.state === "tool" ? e.tool : ""; + break; case "ctx": root.ctxUsed = e.used; root.ctxMax = e.max; break; case "conversations": root.convs = e.items; break; case "results": root.results = e.items; break; |
