aboutsummaryrefslogtreecommitdiffstats
path: root/desktop
diff options
context:
space:
mode:
Diffstat (limited to 'desktop')
-rw-r--r--desktop/assistant/Card.qml10
-rw-r--r--desktop/assistant/README.md3
-rw-r--r--desktop/modules/ai/AiModule.qml18
-rw-r--r--desktop/modules/ai/AiPage.qml2
-rw-r--r--desktop/modules/ai/AiRow.qml18
-rw-r--r--desktop/modules/ai/README.md16
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.