aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/assistant/Assistant.qml
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-16 14:16:24 +0200
committerDanilo M. <danix@danix.xyz>2026-10-16 14:16:24 +0200
commitf99a9329caccdd14d9c530777a5e5b622d34f602 (patch)
treefb8351c3485b534cfa2337801f1eea2d973b8f6a /desktop/assistant/Assistant.qml
parent729e89b4dce7e6f89a8a20cb919ad1b05d825d09 (diff)
downloadquickshell-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.qml20
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;