From 885d65a2bbcde0e8e5094ae4589647875ee5838d Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Sat, 17 Oct 2026 10:50:09 +0200 Subject: assistant: pick the preferred model, loaded on demand The daemon answers with whichever model llama-server has loaded and never asks for one, so it never evicts a model another client is using. The Assistant settings page now lists the router's models with their state. Load saves one as the preferred model, in Prefs, and asks the router to load it now, polling /v1/models until it reports loaded; Prefer marks the loaded model as preferred without swapping. The swap happens only on that click, so the daemon's rule holds. AGENTS.md records the router's load endpoint and its two refusals, both measured: it lives on the server root, not under /v1. Co-Authored-By: Claude Opus 5.5 --- desktop/modules/assistant/AssistantPage.qml | 68 +++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) (limited to 'desktop/modules/assistant/AssistantPage.qml') diff --git a/desktop/modules/assistant/AssistantPage.qml b/desktop/modules/assistant/AssistantPage.qml index e5d76c8..2e40ed4 100644 --- a/desktop/modules/assistant/AssistantPage.qml +++ b/desktop/modules/assistant/AssistantPage.qml @@ -162,6 +162,74 @@ Column { Heading { text: "Model" } + Small { + width: page.width + wrapMode: Text.Wrap + text: "The assistant answers with whichever model llama-server has loaded. Load makes one the " + + "preferred model and loads it now, which swaps out the loaded one for every client." + } + + Small { + visible: page.asst.models.length === 0 + text: "llama-server is not answering at " + page.asst.llm + color: Theme.red + } + + Repeater { + model: page.asst.models + + delegate: Rectangle { + id: modelRow + required property var modelData + readonly property bool loaded: modelData.status === "loaded" + readonly property bool busy: page.asst.loading === modelData.id + readonly property bool preferred: Prefs.assistantModel === modelData.id + objectName: "model:" + modelData.id + width: page.width + height: 40 + radius: 8 + color: loaded ? Qt.alpha(Theme.accent, 0.14) : Qt.alpha(Theme.surface, 0.35) + border.width: 1 + border.color: loaded ? Qt.alpha(Theme.accent, 0.5) : "transparent" + + Rectangle { + id: dot + anchors { left: parent.left; leftMargin: 12; verticalCenter: parent.verticalCenter } + width: 8; height: 8; radius: 4 + color: modelRow.loaded ? Theme.green : modelRow.busy ? Theme.yellow : Theme.overlay + } + Text { + id: modelName + anchors { left: dot.right; leftMargin: 10; verticalCenter: parent.verticalCenter } + text: modelRow.modelData.id + color: Theme.text + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2; bold: modelRow.loaded } + } + Small { + anchors { left: modelName.right; leftMargin: 10; verticalCenter: parent.verticalCenter } + text: [modelRow.busy ? "loading…" : modelRow.modelData.status, + modelRow.preferred ? "preferred" : ""].filter(s => s).join(" · ") + color: modelRow.preferred ? Theme.accent : Theme.subtext + } + Button { + anchors { right: parent.right; rightMargin: 6; verticalCenter: parent.verticalCenter } + text: modelRow.loaded ? (modelRow.preferred ? "Loaded" : "Prefer") : "Load" + enabled: page.asst.loading === "" && !(modelRow.loaded && modelRow.preferred) + // A loaded model only becomes the preferred one: nothing to swap. + onClicked: modelRow.loaded ? Prefs.setAssistantModel(modelRow.modelData.id) + : page.asst.loadModel(modelRow.modelData.id) + } + } + } + + Small { + width: page.width + visible: page.asst.modelError !== "" + wrapMode: Text.Wrap + text: page.asst.modelError + color: Theme.red + } + Labelled { objectName: "key:llm" label: "Server" -- cgit v1.2.3