diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-16 14:11:17 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-16 14:11:17 +0200 |
| commit | b7f33e54c93c5e3c764aa083c2f354fb8751e166 (patch) | |
| tree | 6b9b66af1f0a5897137d13df66b19b504270da21 /desktop/assistant/Message.qml | |
| parent | 72d3ff1f0c8c2cadf315214f31312895154cd20e (diff) | |
| download | quickshell-b7f33e54c93c5e3c764aa083c2f354fb8751e166.tar.gz quickshell-b7f33e54c93c5e3c764aa083c2f354fb8751e166.zip | |
feat(assistant): the canvas, a view over the assistant daemon
The socket, the turn model and the window live in Assistant.qml, outside
the window's loader, so closing the card loses nothing and a reply keeps
streaming while it is closed. Tokens reach the model on a 50 ms tick
rather than per token. Prose goes through Notify.sanitize, because model
output can quote a search result and a remote image in it would be
fetched on render. Code blocks get a copy button once their fence closes.
The history panel lists conversations and searches them through the
daemon's full-text index.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Diffstat (limited to 'desktop/assistant/Message.qml')
| -rw-r--r-- | desktop/assistant/Message.qml | 107 |
1 files changed, 107 insertions, 0 deletions
diff --git a/desktop/assistant/Message.qml b/desktop/assistant/Message.qml new file mode 100644 index 0000000..c724c2e --- /dev/null +++ b/desktop/assistant/Message.qml @@ -0,0 +1,107 @@ +// 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 ".." + +// One turn: the question, then the reply with its thinking line, the tool +// chips, and an error or stopped mark. Both texts go through Segments, so +// the question renders as markdown too. +Column { + id: msg + required property int index + required property string user + required property string reply + required property string think + required property string tools + required property string error + required property bool live + required property bool stopped + required property bool expanded + required property var model // the row, for setting expanded + property string running: "" // the tool running now, if any + + spacing: 6 + + Rectangle { + width: msg.width + height: question.height + 16 + radius: 8 + color: Qt.alpha(Theme.surface, 0.7) + Segments { + id: question + anchors { left: parent.left; right: parent.right; top: parent.top; margins: 8 } + text: msg.user + } + } + + // Collapsed by default; a click shows the reasoning as plain text. + Text { + visible: msg.think !== "" + text: (msg.expanded ? "▾ " : "▸ ") + (msg.live && msg.reply === "" ? "thinking…" : "thinking") + color: Theme.overlay + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: msg.model.expanded = !msg.expanded + } + } + Text { + width: msg.width + visible: msg.expanded && msg.think !== "" + text: msg.think + textFormat: Text.PlainText + wrapMode: Text.Wrap + color: Theme.subtext + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + } + + Flow { + width: msg.width + spacing: 6 + visible: msg.tools !== "" + Repeater { + model: msg.tools ? msg.tools.split(",") : [] + Rectangle { + required property string modelData + readonly property bool on: msg.live && msg.running === modelData + width: chip.implicitWidth + 14 + height: chip.implicitHeight + 6 + radius: height / 2 + color: Qt.alpha(Theme.surface, 0.9) + opacity: on ? 1 : 0.55 + Text { + id: chip + anchors.centerIn: parent + text: parent.modelData + color: parent.on ? Theme.text : Theme.subtext + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 5 } + } + } + } + } + + Segments { + width: msg.width + text: msg.reply + visible: msg.reply !== "" + } + + Text { + visible: msg.error !== "" || msg.stopped + width: msg.width + text: msg.error !== "" ? msg.error : "stopped" + color: msg.error !== "" ? Theme.red : Theme.overlay + wrapMode: Text.Wrap + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + } +} |
