diff options
Diffstat (limited to 'desktop')
| -rw-r--r-- | desktop/assistant/Card.qml | 10 | ||||
| -rw-r--r-- | desktop/assistant/README.md | 3 | ||||
| -rw-r--r-- | desktop/modules/ai/AiModule.qml | 18 | ||||
| -rw-r--r-- | desktop/modules/ai/AiPage.qml | 2 | ||||
| -rw-r--r-- | desktop/modules/ai/AiRow.qml | 18 | ||||
| -rw-r--r-- | desktop/modules/ai/README.md | 16 |
6 files changed, 54 insertions, 13 deletions
diff --git a/desktop/assistant/Card.qml b/desktop/assistant/Card.qml index 85066fe..a2f8e28 100644 --- a/desktop/assistant/Card.qml +++ b/desktop/assistant/Card.qml @@ -49,6 +49,16 @@ Rectangle { // Keys reach a focused item, never the window (AGENTS.md); the card is // the ancestor of every text field, so their Escape bubbles here. Keys.onEscapePressed: card.canvas.back() + // Ctrl+N and Ctrl+H do what the New and History buttons do. + Keys.onPressed: event => { + if (!(event.modifiers & Qt.ControlModifier)) return; + if (event.key === Qt.Key_N && card.canvas.connected) { + card.canvas.fresh(); card.canvas.historyOpen = false; input.forceActiveFocus(); + } else if (event.key === Qt.Key_H) { + card.canvas.historyOpen ? card.canvas.back() : card.canvas.showHistory(); + } else return; + event.accepted = true; + } // Clicks on the card must not reach the close-on-click catcher behind it. MouseArea { anchors.fill: parent } diff --git a/desktop/assistant/README.md b/desktop/assistant/README.md index 377796c..58fbb14 100644 --- a/desktop/assistant/README.md +++ b/desktop/assistant/README.md @@ -39,7 +39,8 @@ model in use reports no `audio` input (`AssistantModule.hears`, from the router's `/models`, read on every open). Keys: Enter sends, Shift+Enter is a newline, Escape cancels a recording, -then closes the picker, the history panel, then the card. Stop replaces +then closes the picker, the history panel, then the card. Ctrl+N starts a +new chat, Ctrl+H toggles the history panel. Stop replaces Send while a reply streams. Notes: diff --git a/desktop/modules/ai/AiModule.qml b/desktop/modules/ai/AiModule.qml index a96f484..6df3d6b 100644 --- a/desktop/modules/ai/AiModule.qml +++ b/desktop/modules/ai/AiModule.qml @@ -49,10 +49,16 @@ Module { Quickshell.env("HOME") + "/Programming/GIT/desktop-assistant/assistant.sh" // Each project's own control. Fanfictioner has no start, a run needs a - // story file, and its stop needs the pid, see stop(). + // story file, and its stop needs the pid, see stop(). llamachat has no + // restart, and --quit answers before the app is gone, so a --daemon + // straight after would find it still running and do nothing: wait for + // --ping to fail first, up to 5s. readonly property var controls: ({ - assistant: { start: [assistantSh, "start"], stop: [assistantSh, "stop"] }, - chat: { start: ["llamachat", "--daemon"], stop: ["llamachat", "--quit"] }, + assistant: { start: [assistantSh, "start"], stop: [assistantSh, "stop"], + restart: [assistantSh, "restart"] }, + chat: { start: ["llamachat", "--daemon"], stop: ["llamachat", "--quit"], + restart: ["sh", "-c", "llamachat --quit; for _ in $(seq 50); do " + + "llamachat --ping || break; sleep 0.1; done; exec llamachat --daemon"] }, imggen: { start: ["imggen", "start"], stop: ["imggen", "stop"] }, }) @@ -62,6 +68,12 @@ Module { if (canStart(app)) exec(controls[app.name].start); } + function canRestart(app) { return controls[app.name]?.restart !== undefined; } + + function restart(app) { + if (canRestart(app)) exec(controls[app.name].restart); + } + function stop(app) { if (app.name !== "fanfic") { exec(controls[app.name].stop); diff --git a/desktop/modules/ai/AiPage.qml b/desktop/modules/ai/AiPage.qml index c8e39e9..e89132a 100644 --- a/desktop/modules/ai/AiPage.qml +++ b/desktop/modules/ai/AiPage.qml @@ -69,6 +69,8 @@ Column { } action: modelData.running ? "Stop" : page.ai.canStart(modelData) ? "Start" : "" danger: modelData.running + restartable: modelData.running && page.ai.canRestart(modelData) + onRestart: page.ai.restart(modelData) onTriggered: modelData.running ? page.ai.stop(modelData) : page.ai.start(modelData) } } diff --git a/desktop/modules/ai/AiRow.qml b/desktop/modules/ai/AiRow.qml index ac4a180..2950759 100644 --- a/desktop/modules/ai/AiRow.qml +++ b/desktop/modules/ai/AiRow.qml @@ -12,8 +12,9 @@ import QtQuick import "../.." -// One engine or app: a state dot, a name, a detail line, and at most one -// button. An empty action means no button, which is every engine row. +// One engine or app: a state dot, a name, a detail line, at most one +// button, and a Restart before it when restartable. An empty action means no +// button, which is every engine row. Rectangle { id: row @@ -23,7 +24,9 @@ Rectangle { property bool busy: false property string action: "" property bool danger: false + property bool restartable: false signal triggered + signal restart implicitHeight: Math.max(texts.implicitHeight, btn.visible ? btn.implicitHeight : 0) + 16 radius: 8 @@ -40,7 +43,8 @@ Rectangle { id: texts anchors { left: dot.right; leftMargin: 10 - right: btn.visible ? btn.left : parent.right; rightMargin: 10 + right: restartBtn.visible ? restartBtn.left : btn.visible ? btn.left : parent.right + rightMargin: 10 verticalCenter: parent.verticalCenter } spacing: 2 @@ -63,6 +67,14 @@ Rectangle { } Button { + id: restartBtn + anchors { right: btn.left; rightMargin: 6; verticalCenter: parent.verticalCenter } + visible: row.restartable + text: "Restart" + onClicked: row.restart() + } + + Button { id: btn anchors { right: parent.right; rightMargin: 10; verticalCenter: parent.verticalCenter } visible: row.action !== "" diff --git a/desktop/modules/ai/README.md b/desktop/modules/ai/README.md index 5e204ca..5aec615 100644 --- a/desktop/modules/ai/README.md +++ b/desktop/modules/ai/README.md @@ -27,12 +27,16 @@ written in the last 5s. A step slower than that reads idle. ## Apps -| App | Status | Start | Stop | -| --- | --- | --- | --- | -| desktop-assistant | `assistant.sh status` | `assistant.sh start` | `assistant.sh stop` | -| local chat | `llamachat --ping` | `llamachat --daemon` | `llamachat --quit` | -| imggen | `imggen status` | `imggen start` (default model) | `imggen stop` | -| fanfictioner | `pgrep` on `python3 .../fanfictioner` | none | TERM its children, then it, by pid | +| App | Status | Start | Stop | Restart | +| --- | --- | --- | --- | --- | +| desktop-assistant | `assistant.sh status` | `assistant.sh start` | `assistant.sh stop` | `assistant.sh restart` | +| local chat | `llamachat --ping` | `llamachat --daemon` | `llamachat --quit` | quit, wait for `--ping` to fail (5s at most), `--daemon` | +| imggen | `imggen status` | `imggen start` (default model) | `imggen stop` | none | +| fanfictioner | `pgrep` on `python3 .../fanfictioner` | none | TERM its children, then it, by pid | none | + +Restart shows only while an app is running. llamachat's `--quit` answers +before the app has exited, so a `--daemon` sent straight after finds it still +running and does nothing; hence the wait. Each runs detached, so no exit code comes back; the next poll shows whether it worked. `assistant.sh status` fails when the daemon is down. |
