aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/assistant/Assistant.qml
diff options
context:
space:
mode:
Diffstat (limited to 'desktop/assistant/Assistant.qml')
-rw-r--r--desktop/assistant/Assistant.qml61
1 files changed, 42 insertions, 19 deletions
diff --git a/desktop/assistant/Assistant.qml b/desktop/assistant/Assistant.qml
index b1329b1..f76c38d 100644
--- a/desktop/assistant/Assistant.qml
+++ b/desktop/assistant/Assistant.qml
@@ -73,7 +73,11 @@ Scope {
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 = ""; }
+ onConnectedChanged: if (!root.connected) {
+ root.state = "idle";
+ root.runningTool = "";
+ if (root.opRunning && root.opDaemon) { root.opRunning = false; root.opError = "assistant gone"; }
+ }
// 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
@@ -117,6 +121,7 @@ Scope {
function attach(paths) {
const files = paths.filter(p => p !== "").map(Files.file);
if (!files.length) return;
+ if (root.opRunning) root.stopOp(); // a drop is a new task
root.fresh();
root.clearOp();
root.staged = files;
@@ -151,31 +156,42 @@ Scope {
root.send({ type: "write", files: paths, prompt: root.mdPrompt, dir: root.outDir, name: name });
} else {
root.opDaemon = false;
- ops.command = [root.fileops, o.op, root.outDir].concat(paths);
- ops.running = false;
- ops.running = true;
+ // A Process per run: a stopped run's late answer must not be
+ // read as the next run's.
+ root.opProc = opRun.createObject(root, { command: [root.fileops, o.op, root.outDir].concat(paths) });
}
}
+ property var opProc: null
+
function stopOp() {
if (!root.opRunning) return;
if (root.opDaemon) root.send({ type: "stop" });
- else ops.running = false; // SIGTERM: fileops kills its tool, removes the partial
+ else if (root.opProc) {
+ root.opProc.running = false; // fileops kills its tool, removes the partial
+ root.opProc = null;
+ }
root.opRunning = false;
root.opError = "stopped";
}
- Process {
- id: ops
- stdout: StdioCollector {
- onStreamFinished: {
- if (!root.opRunning || root.opDaemon) return;
- root.opRunning = false;
- try {
- const r = JSON.parse(text.trim().split("\n").pop());
- if (r.ok) root.opOut = r.out; else root.opError = r.error;
- } catch (e) {
- root.opError = "fileops gave no answer";
+ Component {
+ id: opRun
+ Process {
+ id: proc
+ running: true
+ stdout: StdioCollector {
+ onStreamFinished: {
+ if (root.opProc !== proc) { proc.destroy(); return; } // stopped, or superseded
+ root.opProc = null;
+ root.opRunning = false;
+ try {
+ const r = JSON.parse(text.trim().split("\n").pop());
+ if (r.ok) root.opOut = r.out; else root.opError = r.error;
+ } catch (e) {
+ root.opError = "fileops gave no answer";
+ }
+ proc.destroy();
}
}
}
@@ -185,7 +201,11 @@ Scope {
running: true
command: ["xdg-user-dir", "DOCUMENTS"]
stdout: StdioCollector {
- onStreamFinished: if (text.trim().startsWith("/")) root.outDir = text.trim() + "/Assistant"
+ // With no documents folder set, xdg-user-dir answers $HOME itself.
+ onStreamFinished: {
+ const d = text.trim();
+ if (d.startsWith("/") && d !== Quickshell.env("HOME")) root.outDir = d + "/Assistant";
+ }
}
}
@@ -232,7 +252,8 @@ Scope {
// error, and the chips must still be there to fix it. chatting keeps the
// chat layout meanwhile, so the card does not shrink while a PDF is read.
function ask(text) {
- if (!root.connected || root.blocked || (text.trim() === "" && !root.staged.length)) return false;
+ if (!root.connected || root.blocked || root.opRunning
+ || (text.trim() === "" && !root.staged.length)) return false;
root.notice = "";
root.chatting = true;
root.send({ type: "ask", text: text, files: root.staged.map(f => f.path) });
@@ -324,7 +345,9 @@ Scope {
if (root.opDaemon && root.opRunning) { root.opRunning = false; root.opOut = [e.path]; }
break;
case "error":
- if (e.turn && root.opDaemon && root.opRunning) {
+ // A write's outcome. An older daemon answers it with a plain
+ // error and no turn; while nothing else runs, that is the write's.
+ if (root.opDaemon && root.opRunning && (e.turn || root.state === "idle")) {
root.opRunning = false;
root.opError = e.message;
break;