diff options
Diffstat (limited to 'desktop')
| -rw-r--r-- | desktop/modules/ai/AiModule.qml | 16 | ||||
| -rw-r--r-- | desktop/modules/ai/AiPage.qml | 2 | ||||
| -rw-r--r-- | desktop/modules/ai/README.md | 10 | ||||
| -rwxr-xr-x | desktop/modules/ai/ai-state.sh | 27 | ||||
| -rwxr-xr-x | desktop/modules/ai/test-ai-state.sh | 22 | ||||
| -rw-r--r-- | desktop/modules/notes/NotesModule.qml | 2 | ||||
| -rw-r--r-- | desktop/modules/status/BreakRow.qml | 84 | ||||
| -rw-r--r-- | desktop/modules/status/README.md | 46 | ||||
| -rw-r--r-- | desktop/modules/status/StatusPage.qml | 12 | ||||
| -rw-r--r-- | desktop/modules/status/StatusTile.qml | 9 |
10 files changed, 21 insertions, 209 deletions
diff --git a/desktop/modules/ai/AiModule.qml b/desktop/modules/ai/AiModule.qml index d58ebfe..4c50224 100644 --- a/desktop/modules/ai/AiModule.qml +++ b/desktop/modules/ai/AiModule.qml @@ -41,16 +41,12 @@ Module { sd: "stable-diffusion.cpp", assistant: "Desktop assistant", chat: "Local chat", - imggen: "imggen", - fanfic: "Fanfictioner", }) readonly property string assistantSh: 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(). llamachat has no - // restart, and --quit answers before the app is gone, so a --daemon + // Each project's own control. 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: ({ @@ -59,7 +55,6 @@ Module { 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"] }, }) function canStart(app) { return controls[app.name] !== undefined; } @@ -75,14 +70,7 @@ Module { } function stop(app) { - if (app.name !== "fanfic") { - exec(controls[app.name].stop); - return; - } - // The sd-cli children first, then the script, by pid only: a name - // match would take unrelated sd-cli and python processes with it. - if (!/^[0-9]+$/.test(app.detail)) return; - exec(["sh", "-c", 'pkill -TERM -P "$1"; kill -TERM "$1"', "sh", app.detail]); + exec(controls[app.name].stop); } // The router frees the model's VRAM; the next request for it loads it diff --git a/desktop/modules/ai/AiPage.qml b/desktop/modules/ai/AiPage.qml index 369c1b5..c1cb07c 100644 --- a/desktop/modules/ai/AiPage.qml +++ b/desktop/modules/ai/AiPage.qml @@ -68,7 +68,7 @@ Column { detail: { if (!modelData.running) return "Stopped"; if (modelData.detail === "") return "Running"; - return "Running · " + (modelData.name === "fanfic" ? "pid " : "") + modelData.detail; + return "Running · " + modelData.detail; } action: modelData.running ? "Stop" : page.ai.canStart(modelData) ? "Start" : "" danger: modelData.running diff --git a/desktop/modules/ai/README.md b/desktop/modules/ai/README.md index e71159b..d87087a 100644 --- a/desktop/modules/ai/README.md +++ b/desktop/modules/ai/README.md @@ -18,10 +18,10 @@ llama's state comes from its API: `/models` for the loaded model, then `autoload=false` matters: a plain `/slots?model=` on an unloaded model loads it into VRAM, which is how this was found. -The sd row covers two processes. A running `sd-cli`, the one-shot runs -fanfictioner makes, reads up and generating with its model marked `(sd-cli)`, -whatever the web UI is doing: it holds VRAM for as long as it lives. The first -version watched only `sd-server` and showed the engine down mid-run. +The sd row covers two processes. A running one-shot `sd-cli` reads up and +generating with its model marked `(sd-cli)`, whatever the web UI is doing: it +holds VRAM for as long as it lives. The first version watched only +`sd-server` and showed the engine down mid-run. sd-server has no progress endpoint. Its log, `~/.cache/sd-server.log`, streams progress bars while it generates, so the row reads busy while the log was @@ -33,8 +33,6 @@ written in the last 5s. A step slower than that reads idle. | --- | --- | --- | --- | --- | | 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 diff --git a/desktop/modules/ai/ai-state.sh b/desktop/modules/ai/ai-state.sh index 8b10368..a4406ce 100755 --- a/desktop/modules/ai/ai-state.sh +++ b/desktop/modules/ai/ai-state.sh @@ -12,19 +12,17 @@ # GNU General Public License for more details. # # One sweep of the local AI stack for the drawer's AI page. Always prints -# these six lines, tab separated, and exits 0: +# these four lines, tab separated, and exits 0: # # engine<TAB>llama<TAB>up|down<TAB>idle|busy<TAB>model # engine<TAB>sd<TAB>up|down<TAB>idle|busy<TAB>model, "(sd-cli)" for a CLI run # app<TAB>assistant<TAB>running|stopped<TAB>- # app<TAB>chat<TAB>running|stopped<TAB>- -# app<TAB>imggen<TAB>running|stopped<TAB>model -# app<TAB>fanfic<TAB>running|stopped<TAB>pid # # An empty value is "-". A probe that fails reads as down or stopped, not as # an error: every row is something that may simply not be running. # -# The test stubs curl, pgrep, llamachat and imggen on PATH and points +# The test stubs curl, pgrep and llamachat on PATH and points # AI_ASSISTANT and AI_SD_LOG at fixtures; see test-ai-state.sh. set -u @@ -65,7 +63,7 @@ model_of() { sd() { local line busy=idle mtime - # A one-shot sd-cli (fanfictioner runs them) holds VRAM and generates for + # A one-shot sd-cli holds VRAM and generates for # as long as it lives, so it outranks whatever the web UI is doing. if line="$(pgrep -axo sd-cli)"; then row engine sd up busy "$(model_of "$line") (sd-cli)" @@ -81,8 +79,6 @@ sd() { } apps() { - local out pid - if "$ASSISTANT" status >/dev/null 2>&1; then row app assistant running - else @@ -94,23 +90,6 @@ apps() { else row app chat stopped - fi - - # imggen status exits 0 either way: the daemon's JSON when it is up, - # "daemon down" when it is not. - out="$(imggen status 2>/dev/null)" - if [[ "$out" == "{"* ]]; then - row app imggen running "$(jq -r '.model // "-"' <<<"$out" 2>/dev/null || echo -)" - else - row app imggen stopped - - fi - - # It runs through env, so the command line is python3 then the script, - # by path from ~/bin or by ./fanfictioner from the repo. - if pid="$(pgrep -u "$USER" -fo '^[^ ]*python3[^ ]* [^ ]*fanfictioner( |$)')"; then - row app fanfic running "$pid" - else - row app fanfic stopped - - fi } llama diff --git a/desktop/modules/ai/test-ai-state.sh b/desktop/modules/ai/test-ai-state.sh index e3e8ba0..aac83fb 100755 --- a/desktop/modules/ai/test-ai-state.sh +++ b/desktop/modules/ai/test-ai-state.sh @@ -11,8 +11,8 @@ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # -# The one runnable check for ai-state.sh. It puts stub curl, pgrep, -# llamachat and imggen on PATH and a stub assistant.sh beside them, then runs +# The one runnable check for ai-state.sh. It puts stub curl, pgrep +# and llamachat on PATH and a stub assistant.sh beside them, then runs # the real script, so nothing here touches the live stack. T_* variables pick # the state each stub reports. # @@ -57,9 +57,6 @@ case "$*" in *sd-server*) [[ -n "${T_SD:-}" ]] || exit 1 echo "4242 sd-server --listen-port 7860 --diffusion-model /data/SD/z_image_turbo-Q8_0.gguf --vae /data/SD/vae/flux1-ae.safetensors" ;; - *fanfictioner*) - [[ -n "${T_FANFIC:-}" ]] || exit 1 - echo 31337 ;; *) exit 1 ;; esac STUB @@ -69,13 +66,6 @@ cat > "$stub/llamachat" <<'STUB' [[ "$1" == --ping && -n "${T_CHAT:-}" ]] STUB -# The real imggen exits 0 either way and says which on stdout. -cat > "$stub/imggen" <<'STUB' -#!/bin/bash -[[ "$1" == status ]] || exit 1 -if [[ -n "${T_IMG:-}" ]]; then echo '{"model": "realvis", "ready": true}'; else echo "daemon down"; fi -STUB - cat > "$stub/assistant.sh" <<'STUB' #!/bin/bash [[ "$1" == status && -n "${T_ASSIST:-}" ]] @@ -106,7 +96,7 @@ run() { bash "$here/ai-state.sh" } -apps_down="$(l app assistant stopped -; l app chat stopped -; l app imggen stopped -; l app fanfic stopped -)" +apps_down="$(l app assistant stopped -; l app chat stopped -)" want="$(l engine llama down idle -; l engine sd down idle -) $apps_down" @@ -131,7 +121,7 @@ touch -d '1 minute ago' "$log" got="$(run T_SD=1 | sed -n 2p)" check "sd running, log quiet" "$(l engine sd up idle z_image_turbo-Q8_0.gguf)" "$got" -# A one-shot sd-cli (fanfictioner's) holds VRAM and is generating for as +# A one-shot sd-cli holds VRAM and is generating for as # long as it runs, whatever the web UI is doing. Its prompt is free text, so # a "-m" inside it must not be read as the model. got="$(run T_CLI=1 | sed -n 2p)" @@ -140,8 +130,8 @@ check "sd-cli running" "$(l engine sd up busy "Krea-2-Turbo-Q6_K.gguf (sd-cli)") got="$(run T_CLI=1 T_SD=1 | sed -n 2p)" check "sd-cli wins over an idle web UI" "$(l engine sd up busy "Krea-2-Turbo-Q6_K.gguf (sd-cli)")" "$got" -want="$(l app assistant running -; l app chat running -; l app imggen running realvis; l app fanfic running 31337)" -got="$(run T_ASSIST=1 T_CHAT=1 T_IMG=1 T_FANFIC=1 | tail -n4)" +want="$(l app assistant running -; l app chat running -)" +got="$(run T_ASSIST=1 T_CHAT=1 | tail -n2)" check "every app running" "$want" "$got" printf '\n%d passed, %d failed\n' "$pass" "$fail" diff --git a/desktop/modules/notes/NotesModule.qml b/desktop/modules/notes/NotesModule.qml index 3066c8a..33e1cdf 100644 --- a/desktop/modules/notes/NotesModule.qml +++ b/desktop/modules/notes/NotesModule.qml @@ -238,7 +238,7 @@ Module { mod.lastError = ""; mod.gitPull.command = ["sh", "-c", 'git -C "$1" pull --rebase --autostash 2>&1', "sh", mod.baseDir]; // Reassigning running on a running Process does nothing, so clear it - // first (Status.qml does the same for its breaktimer). + // first (Status.qml does the same for its game poll). mod.gitPull.running = false; mod.gitPull.running = true; } diff --git a/desktop/modules/status/BreakRow.qml b/desktop/modules/status/BreakRow.qml deleted file mode 100644 index 8b732b1..0000000 --- a/desktop/modules/status/BreakRow.qml +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (C) 2026 Danilo M. <danix@danix.xyz> -// -// This program is free software; you can redistribute it and/or modify -// it under the terms of the GNU General Public License version 2 as -// published by the Free Software Foundation. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -import QtQuick -import "../.." - -// Breaktimer: the daemon's phase and countdown, a pause switch and a -// start/stop button. Every control is a verb; nothing here writes the -// daemon's files. -Item { - id: row - - implicitHeight: Math.max(texts.implicitHeight, controls.implicitHeight) + 16 - - readonly property string phaseLabel: { - if (!Status.btRunning) return "Stopped"; - if (Status.btPaused) return "Paused"; - if (Status.btPhase === "breaking") return "Micro-pause"; - if (Status.btPhase === "longbreak") return "Long pause"; - return "Working"; - } - - // A paused countdown is frozen, and a frozen number reads as a bug, so - // the paused state says what it is instead of showing one. - readonly property string detail: { - if (!Status.btRunning) return "Break reminders are not running."; - if (Status.btPaused) return "Countdown frozen until resumed."; - if (Status.btPhase === "breaking" || Status.btPhase === "longbreak") - return "Back to work in " + Status.btCountdown() + "."; - return "Next break in " + Status.btCountdown() + "."; - } - - Column { - id: texts - anchors { - left: parent.left - right: controls.left - rightMargin: 12 - verticalCenter: parent.verticalCenter - } - spacing: 2 - - Text { - text: "Breaktimer · " + row.phaseLabel - font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2; bold: true } - color: Theme.text - } - - Text { - width: parent.width - wrapMode: Text.WordWrap - text: row.detail - font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } - color: Theme.subtext - } - } - - Row { - id: controls - anchors { right: parent.right; verticalCenter: parent.verticalCenter } - spacing: 8 - - Button { - text: Status.btRunning ? "Stop" : "Start" - danger: Status.btRunning - onClicked: Status.runBreaktimer(Status.btRunning ? "stop" : "start") - } - - Switch { - id: sw - enabled: Status.btRunning - checked: Status.btRunning && !Status.btPaused - onToggled: Status.runBreaktimer(Status.btPaused ? "resume" : "pause") - } - } -} diff --git a/desktop/modules/status/README.md b/desktop/modules/status/README.md index d3d0cce..ea069d6 100644 --- a/desktop/modules/status/README.md +++ b/desktop/modules/status/README.md @@ -45,7 +45,7 @@ and reasserted when it returns. `dnd` has none of its own. It is state the notification daemon reads. `presentation` sets `dnd`, `nolock` and `quiet`, asserts a Wayland idle -inhibitor, and pauses breaktimer. Turning it off restores the three to the +inhibitor. Turning it off restores the three to the values they had before rather than clearing them, so hand-set DND or a hand-set disable survives a presentation. @@ -71,52 +71,10 @@ The opencode notifier runs its command for every event, `session_started` and are off in `opencode-notifier.json`, and it rereads that file on every event, so neither change needs an opencode restart. -breaktimer is paused and resumed by verb, never by writing its state file. See -Breaktimer below, which is also the read direction. - -## Breaktimer - -The traffic runs both ways, and only one way writes. - -**Reading.** The daemon publishes three files the singleton watches: - - $XDG_RUNTIME_DIR/breaktimer.state running | paused | stopped - $XDG_RUNTIME_DIR/breaktimer.phase working | breaking | longbreak | stopped - $XDG_RUNTIME_DIR/breaktimer.remain seconds left in the phase - -exposed as `Status.btState`, `btPhase` and `btRemain`, with `btRunning` and -`btPaused` derived from the first. The old bar script read the same three -files; the two consumers never knew about each other. - -**Writing: never.** The daemon owns those files and rewrites state and phase on -every transition, so a second writer would race its loop. Every control calls a -verb through `runBreaktimer()`, which is why presentation mode has always -called `pause` rather than writing `breaktimer.state`. - -**A stopped daemon** is read from the state file, not probed. Both paths that -end the daemon write `stopped` there: `stop_daemon`, and the `cleanup` trap on -`TERM`. A daemon lost to `KILL` leaves a stale `running` and the drawer shows a -frozen countdown, a visible wrong answer the Start button resolves, which is -cheaper than a liveness probe on every repaint. QML cannot send a signal, so -the `kill -0` check the old bar module used is not available here anyway. - -**The countdown counts in five second steps**, because that is the daemon's -tick and the shell does not interpolate between its writes. A local one second -timer would be a second clock drifting against the first, correcting itself -with a visible jump every five seconds, and it would keep counting while the -daemon is frozen outside the work window or paused. - -**The tile** shows breaktimer below every mode, so an active mode still owns -the line and breaktimer replaces only the idle `All clear`. It does not count -toward `activeCount`: a running daemon is not a mode the user switched on. - -The daemon's own configuration lives in `~/.config/breaktimer.conf` and is not -edited from here; `breaktimer.sh config` prints what is in effect. - ## Game detection Presentation is set automatically while a game runs, so a fullscreen game does -not get locked or interrupted by the idle timer, DND or breaktimer. The shell +not get locked or interrupted by the idle timer or DND. The shell polls every five seconds for a `gamescope` process, a running Steam binary, or the DuckStation or PCSX2 emulators, and any one is enough: diff --git a/desktop/modules/status/StatusPage.qml b/desktop/modules/status/StatusPage.qml index 8a21ad5..4741bcc 100644 --- a/desktop/modules/status/StatusPage.qml +++ b/desktop/modules/status/StatusPage.qml @@ -37,7 +37,7 @@ Column { width: page.width mode: "presentation" label: "Presentation" - description: "Do not disturb, quiet, no screen lock, breaktimer paused." + description: "Do not disturb, quiet, no screen lock." value: Status.presentation } @@ -74,14 +74,4 @@ Column { SnoozeRow { width: page.width } - - Rectangle { - width: page.width - height: 1 - color: Qt.alpha(Theme.text, 0.08) - } - - BreakRow { - width: page.width - } } diff --git a/desktop/modules/status/StatusTile.qml b/desktop/modules/status/StatusTile.qml index cec18b6..ab3a7af 100644 --- a/desktop/modules/status/StatusTile.qml +++ b/desktop/modules/status/StatusTile.qml @@ -17,15 +17,8 @@ Text { elide: Text.ElideRight font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } color: Status.activeCount > 0 ? Theme.text : Theme.subtext - // Breaktimer sits below every mode, so an active mode still owns the line - // and breaktimer replaces only the idle "All clear". A stopped daemon - // falls through to it, which is true: nothing is being tracked. text: Status.presentation ? "Presenting" : Status.dnd ? "Do not disturb" : Status.nolock ? "No lock" - : !Status.btRunning ? "All clear" - : Status.btPaused ? "Paused" - : (Status.btPhase === "breaking" || Status.btPhase === "longbreak") - ? "Back in " + Status.btCountdown() - : "Break in " + Status.btCountdown() + : "All clear" } |
