diff options
Diffstat (limited to 'desktop/assistant/Assistant.qml')
| -rw-r--r-- | desktop/assistant/Assistant.qml | 66 |
1 files changed, 63 insertions, 3 deletions
diff --git a/desktop/assistant/Assistant.qml b/desktop/assistant/Assistant.qml index 340c0cf..865f157 100644 --- a/desktop/assistant/Assistant.qml +++ b/desktop/assistant/Assistant.qml @@ -79,6 +79,7 @@ Scope { onConnectedChanged: if (!root.connected) { root.state = "idle"; root.runningTool = ""; + root.endRecording(false); if (root.opRunning && root.opDaemon) { root.opRunning = false; root.opError = "assistant gone"; } } @@ -107,10 +108,14 @@ Scope { root.open = true; } - function close() { root.open = false; root.historyOpen = false; root.pickerOpen = false; } - // Escape: the picker, then the history panel, then the card. + function close() { + root.endRecording(false); + root.open = false; root.historyOpen = false; root.pickerOpen = false; + } + // Escape: a recording, the picker, then the history panel, then the card. function back() { - if (root.pickerOpen) root.pickerOpen = false; + if (root.recording) root.endRecording(false); + else if (root.pickerOpen) root.pickerOpen = false; else if (root.historyOpen) root.historyOpen = false; else root.close(); } @@ -200,6 +205,61 @@ Scope { } } + // The mic: pw-record into one file, overwritten by each recording and + // never deleted (the runtime dir goes at logout). The daemon has the + // model transcribe it. Spec: docs/superpowers/specs/2026-10-19-assistant-mic-design.md. + readonly property string micPath: `${Quickshell.env("XDG_RUNTIME_DIR") || "/tmp"}/assistant-mic.wav` + property var mic: null // the recording's Process, null when not recording + property int micSecs: 0 + readonly property bool recording: root.mic !== null + + function record() { + if (root.recording || !root.connected || root.busy || root.opRunning || root.blocked) return; + root.notice = ""; + root.micSecs = 0; + root.mic = micRun.createObject(root); + } + + // SIGTERM: pw-record finishes the header, then exited sends the ask. + // ponytail: a recording started before the last one's exit shares the + // file; pw-record exits within milliseconds of the signal. + function endRecording(send) { + if (!root.mic) return; + root.mic.send = send; + root.mic.running = false; + root.mic = null; + } + + // 59, not 60: the daemon refuses over 60 s by file size, and the stop + // lands a little after the tick. + Timer { + running: root.recording + interval: 1000 + repeat: true + onTriggered: if (++root.micSecs >= 59) root.endRecording(true); + } + + Component { + id: micRun + Process { + id: rec + property bool send: false + running: true + command: ["pw-record", "--rate", "16000", "--channels", "1", "--format", "s16", root.micPath] + onExited: { + if (root.mic === rec) { // ended on its own: no mic, PipeWire down + root.mic = null; + root.notice = "recording failed"; + } else if (rec.send && root.connected) { + root.chatting = true; + root.send({ type: "ask", text: "", audio: root.micPath, + files: root.staged.map(f => f.path) }); + } + rec.destroy(); + } + } + } + Process { running: true command: ["xdg-user-dir", "DOCUMENTS"] |
