diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-23 17:10:00 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-23 17:10:00 +0200 |
| commit | 9b8b64da15bd6f96f228736f8604aef003304e72 (patch) | |
| tree | adc10cea30fa7317d1901ee6fb4771ac4b8723df /desktop/assistant/Assistant.qml | |
| parent | e7de6f62f438acfa723961000c558b4d9f9da35f (diff) | |
| download | quickshell-9b8b64da15bd6f96f228736f8604aef003304e72.tar.gz quickshell-9b8b64da15bd6f96f228736f8604aef003304e72.zip | |
assistant: pick the chat's model, local or cloud, from the header
The canvas keeps the chat's model from the daemon (hello.chat_model,
conversation.model), falls back to the preferred one (set from shell.qml
later), and sends it as model with every ask, typed or spoken. The To .md
write stays without one: the daemon always writes with the local model.
Models.qml lists the daemon's models event, router models then cloud
ones, in a panel like History. It opens from the header label in chat,
from a Model button next to History outside chat, and with Ctrl+M. The
models are asked for on hello too, so the cloud marker is right before
the panel was ever opened. Moving a chat with turns to a cloud model
says once that its history goes to that provider.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'desktop/assistant/Assistant.qml')
| -rw-r--r-- | desktop/assistant/Assistant.qml | 46 |
1 files changed, 42 insertions, 4 deletions
diff --git a/desktop/assistant/Assistant.qml b/desktop/assistant/Assistant.qml index 2641a5e..0b7026d 100644 --- a/desktop/assistant/Assistant.qml +++ b/desktop/assistant/Assistant.qml @@ -66,6 +66,18 @@ Scope { // The preferred model being loaded on open, shown in the header until // the router reports it loaded. Set from shell.qml. property string swapping: "" + // The model this chat is asked with: its stored pick (hello.chat_model, + // conversation.model), "" for none, then the preferred one (set from + // shell.qml). "" for both: the daemon uses whatever the router has + // loaded. Spec: desktop-assistant, + // docs/superpowers/specs/2026-10-23-cloud-models-design.md. + property string chatModel: "" + property string preferred: "" + readonly property string wanted: root.chatModel || root.preferred + // The daemon's models event: [{id, status, cloud: false} | {id, provider, cloud: true}]. + property var models: [] + readonly property bool cloud: root.models.some(m => m.cloud && m.id === root.wanted) + property bool modelsOpen: false property int ctxUsed: 0 property int ctxMax: 0 property var convId: null @@ -113,11 +125,12 @@ Scope { function close() { root.endRecording(false); - root.open = false; root.historyOpen = false; root.pickerOpen = false; + root.open = false; root.historyOpen = false; root.pickerOpen = false; root.modelsOpen = false; } // Escape: a recording, the picker, then the history panel, then the card. function back() { if (root.recording) root.endRecording(false); + else if (root.modelsOpen) root.modelsOpen = false; else if (root.pickerOpen) root.pickerOpen = false; else if (root.historyOpen) root.historyOpen = false; else root.close(); @@ -258,8 +271,8 @@ Scope { root.notice = "recording failed"; } else if (rec.send && root.connected) { root.chatting = true; - root.send({ type: "ask", text: "", audio: root.micPath, - files: root.staged.map(f => f.path) }); + root.send(root.withModel({ type: "ask", text: "", audio: root.micPath, + files: root.staged.map(f => f.path) })); } rec.destroy(); } @@ -325,16 +338,37 @@ Scope { || (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) }); + root.send(root.withModel({ type: "ask", text: text, files: root.staged.map(f => f.path) })); return true; } function showHistory() { root.pickerOpen = false; + root.modelsOpen = false; root.historyOpen = true; root.send({ type: "list" }); } + function showModels() { + root.historyOpen = root.pickerOpen = false; + root.modelsOpen = true; + root.send({ type: "models" }); + } + // Applies from the next ask; the daemon stores it with the chat then. + // Moving a chat with turns to the cloud sends its history there, so + // that is said once, on the switch. + function pickModel(id) { + const m = root.models.find(x => x.id === id); + if (m && m.cloud && !root.cloud && turns.count) + root.notice = `History in this chat goes to ${m.provider}.`; + root.chatModel = id; + root.modelsOpen = false; + } + function withModel(cmd) { + if (root.wanted) cmd.model = root.wanted; + return cmd; + } + function row(t, live) { return { user: t.user ?? "", reply: t.reply ?? "", think: t.think ?? "", tools: (t.tools ?? []).join(","), error: "", live: live, @@ -371,15 +405,18 @@ Scope { root.state = e.state; root.runningTool = e.state === "tool" ? e.tool : ""; root.model = e.model ?? ""; + root.chatModel = e.chat_model ?? ""; root.ctxUsed = e.ctx_used; root.ctxMax = e.ctx_max; root.load(e.id, e.turns, e.pending); // A restart or reload counts as active: the conversation it // reconnects to is not stale just because this process is new. if (e.pending || e.turns.length) root.lastActive = Date.now(); + root.send({ type: "models" }); break; case "conversation": root.load(e.id, e.turns, null); + root.chatModel = e.model ?? ""; // Opened from History on purpose: it counts as active now. if (e.turns.length) root.lastActive = Date.now(); // Emptied (New, or the current one deleted) with nothing staged: @@ -445,6 +482,7 @@ Scope { case "ctx": root.ctxUsed = e.used; root.ctxMax = e.max; if (e.model) root.model = e.model; break; case "conversations": root.convs = e.items; break; case "results": root.results = e.items; break; + case "models": root.models = e.items; break; } } |
