diff options
Diffstat (limited to 'desktop/modules/assistant/PresetsPage.qml')
| -rw-r--r-- | desktop/modules/assistant/PresetsPage.qml | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/desktop/modules/assistant/PresetsPage.qml b/desktop/modules/assistant/PresetsPage.qml new file mode 100644 index 0000000..ec103cf --- /dev/null +++ b/desktop/modules/assistant/PresetsPage.qml @@ -0,0 +1,215 @@ +// 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 "../.." +import "../../assistant/Presets.js" as Presets + +// One card per preset: its label and question, saved on Enter, and the file +// kinds it is offered for. Every change saves the whole list to Prefs; Reset +// drops the saved list, which brings the built-in one back. +Column { + id: page + spacing: 10 + + readonly property var list: Presets.list(Prefs.presets) + // The kinds a preset can name: the four the canvas chats about, and + // "many", offered whenever two or more files are staged. + readonly property var kinds: ["pdf", "text", "code", "image", "many"] + property string error: "" + property int confirming: -1 + + // Saves a copy of the list with entry i replaced by fn(entry), or + // removed when fn returns null. + function edit(i, fn) { + const out = []; + page.list.forEach((p, j) => { + const c = { label: p.label, icon: p.icon, kinds: p.kinds.slice(), prompt: p.prompt }; + if (j !== i) { out.push(c); return; } + const r = fn(c); + if (r) out.push(r); + }); + page.error = ""; + page.confirming = -1; + Prefs.setPresets(out); + } + + function add() { + Prefs.setPresets(page.list.concat([{ label: "New preset", icon: Presets.FALLBACK, + kinds: ["text"], prompt: "Summarize this." }])); + } + + component Small: Text { + color: Theme.subtext + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + } + + component Field: Rectangle { + id: field + property string value: "" + property string placeholder: "" + signal commit(string text) + height: 32 + radius: 8 + color: Qt.alpha(Theme.base, 0.6) + TextInput { + anchors { fill: parent; leftMargin: 10; rightMargin: 10 } + verticalAlignment: TextInput.AlignVCenter + clip: true + selectByMouse: true + color: Theme.text + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 } + text: field.value + onAccepted: if (text !== field.value) field.commit(text) + Keys.onEscapePressed: event => { + if (text !== field.value) text = field.value; + else event.accepted = false; + } + Text { + anchors.verticalCenter: parent.verticalCenter + visible: parent.text === "" + text: field.placeholder + color: Theme.overlay + font: parent.font + } + } + } + + Small { + width: page.width + wrapMode: Text.Wrap + text: "Offered on the assistant canvas for the staged files: a preset shows when every staged file is " + + "one of its kinds, or, with \"many\", when two or more are staged. Enter saves a field; " + + "changes apply at once." + } + + Text { + width: page.width + visible: page.error !== "" + wrapMode: Text.Wrap + text: page.error + color: Theme.red + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + } + + Repeater { + model: page.list + + delegate: Rectangle { + id: card + required property var modelData + required property int index + objectName: "preset:" + index + width: page.width + height: body.implicitHeight + 20 + radius: 10 + color: Qt.alpha(Theme.surface, 0.45) + + Column { + id: body + anchors { left: parent.left; right: parent.right; top: parent.top; margins: 10 } + spacing: 8 + + Item { + width: parent.width + height: 32 + Text { + id: glyph + anchors { left: parent.left; verticalCenter: parent.verticalCenter } + width: 24 + text: card.modelData.icon ?? Presets.FALLBACK + font { family: Theme.iconFamily; pixelSize: Theme.fontSize } + color: Theme.text + } + Field { + anchors { left: glyph.right; leftMargin: 6; right: del.left; rightMargin: 8; top: parent.top; bottom: parent.bottom } + value: card.modelData.label + placeholder: "label" + onCommit: t => { + if (t.trim() === "") { page.error = "A preset needs a label."; return; } + page.edit(card.index, c => Object.assign(c, { label: t.trim() })); + } + } + Button { + id: del + anchors { right: parent.right; verticalCenter: parent.verticalCenter } + danger: true + text: page.confirming === card.index ? "Confirm" : "Remove" + onClicked: { + if (page.confirming === card.index) page.edit(card.index, c => null); + else page.confirming = card.index; + } + } + } + + Field { + width: parent.width + value: card.modelData.prompt + placeholder: "the question asked" + onCommit: t => { + if (t.trim() === "") { page.error = "A preset needs a question."; return; } + page.edit(card.index, c => Object.assign(c, { prompt: t.trim() })); + } + } + + // A kind toggles on click; the last one cannot be cleared, + // since a preset with no kinds would never be offered. + Flow { + width: parent.width + spacing: 6 + Repeater { + model: page.kinds + Rectangle { + id: chip + required property string modelData + readonly property bool on: card.modelData.kinds.includes(modelData) + implicitWidth: chipText.implicitWidth + 20 + implicitHeight: 26 + radius: 13 + color: on ? Qt.alpha(Theme.accent, 0.85) + : chipArea.containsMouse ? Qt.alpha(Theme.accent, 0.22) : Qt.alpha(Theme.base, 0.6) + Text { + id: chipText + anchors.centerIn: parent + text: chip.modelData + color: chip.on ? Theme.base : Theme.text + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } + } + MouseArea { + id: chipArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + const ks = card.modelData.kinds; + if (chip.on && ks.length === 1) { page.error = "A preset needs at least one kind."; return; } + page.edit(card.index, c => Object.assign(c, { + kinds: chip.on ? ks.filter(k => k !== chip.modelData) : ks.concat([chip.modelData]) })); + } + } + } + } + } + } + } + } + + Row { + spacing: 8 + Button { text: "Add preset"; onClicked: page.add() } + Button { + text: "Reset to defaults" + danger: true + enabled: Prefs.presets !== null + onClicked: Prefs.resetPresets() + } + } +} |
