diff options
Diffstat (limited to 'desktop/modules/assistant/AssistantModule.qml')
| -rw-r--r-- | desktop/modules/assistant/AssistantModule.qml | 57 |
1 files changed, 57 insertions, 0 deletions
diff --git a/desktop/modules/assistant/AssistantModule.qml b/desktop/modules/assistant/AssistantModule.qml index e89fe96..a85b2b3 100644 --- a/desktop/modules/assistant/AssistantModule.qml +++ b/desktop/modules/assistant/AssistantModule.qml @@ -52,6 +52,17 @@ Module { property var tools: [] // A write since the last restart, which the daemon has not read yet. property bool pending: false + + // The router's models, [{id, status}], status as it reports it: + // "loaded", "loading", "unloaded" and so on. Empty when it is down. + property var models: [] + // The id being loaded, "" when none is. + property string loading: "" + property string modelError: "" + // The router answers /models and /models/load on the server's root, and + // the OpenAI API under /v1, which is what the daemon's llm points at. + readonly property string llm: am.values.llm ?? am.defaults.llm ?? "http://localhost:8181/v1" + readonly property string routerRoot: am.llm.replace(/\/v1\/?$/, "") property bool restarting: false property string status: "" property string error: "" @@ -59,10 +70,12 @@ Module { searchItems: am.keys.map(k => ({ text: am.labels[k], key: "key:" + k })) .concat(am.envList.map(e => ({ text: e.key + " = " + e.value, key: "env:" + e.key }))) .concat(am.tools.map(t => ({ text: "tool: " + t.name, key: "tool:" + t.name }))) + .concat(am.models.map(m => ({ text: "model: " + m.id, key: "model:" + m.id }))) function refresh() { fv.reload(); am.listTools(); + am.listModels(); if (Object.keys(am.defaults).length === 0) am.run([am.python, am.daemonDir + "/assistant.py", "--defaults"], null, (code, out) => { try { if (code === 0) am.defaults = JSON.parse(out); } catch (e) {} @@ -156,6 +169,50 @@ Module { }); } + function listModels(done) { + am.run(["curl", "-sf", "-m", "3", am.llm + "/models"], null, (code, out) => { + try { + am.models = code === 0 ? JSON.parse(out).data.map(m => ({ id: m.id, status: m.status?.value ?? "" })) : []; + } catch (e) { am.models = []; } + if (done) done(); + }); + } + + // The router keeps one model resident, so this swaps out whatever is + // loaded, for every client: it runs only on a click. The id becomes the + // preferred one, and the list is polled until the router reports it + // loaded, since the request may return before the load finishes. + function loadModel(id) { + Prefs.setAssistantModel(id); + am.modelError = ""; + am.loading = id; + am.run(["curl", "-s", "-m", "600", "-X", "POST", "-H", "Content-Type: application/json", + "-d", JSON.stringify({ model: id }), am.routerRoot + "/models/load"], null, (code, out) => { + let err = code === 0 ? "" : "the router did not answer"; + try { + const e = JSON.parse(out).error; + // Already running is the outcome asked for, not a failure. + if (e && e.message !== "model is already running") err = e.message; + } catch (x) {} + if (err) { am.modelError = `${id}: ${err}`; am.loading = ""; am.listModels(); } + else { poll.left = 300; poll.start(); } + }); + } + property Timer poll: Timer { + property int left: 0 + interval: 1000 + repeat: true + onTriggered: am.listModels(() => { + const m = am.models.find(x => x.id === am.loading); + if (m && m.status === "loaded") { am.loading = ""; am.poll.stop(); } + else if (--am.poll.left <= 0) { + am.modelError = `${am.loading} did not finish loading`; + am.loading = ""; + am.poll.stop(); + } + }) + } + function restart() { am.restarting = true; am.run([am.daemonDir + "/assistant.sh", "restart"], null, (code, out) => { |
