aboutsummaryrefslogtreecommitdiffstats
path: root/desktop/assistant/History.qml
blob: 114ab2bf55efe168a89062a55bb36258d266da59 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// 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 QtQuick.Controls
import ".."

// The history panel, over the transcript. Empty search: every conversation,
// newest first. Text in the field: the daemon's full-text results, with the
// matched words in bold from its snippet markers.
Rectangle {
    id: panel
    property var canvas

    color: Qt.alpha(Theme.base, 0.97)
    radius: 8

    onVisibleChanged: if (visible) { field.text = ""; field.forceActiveFocus(); }

    MouseArea { anchors.fill: parent }   // keep clicks off the transcript below

    TextField {
        id: field
        anchors { top: parent.top; left: parent.left; right: parent.right }
        height: 32
        placeholderText: "Search conversations"
        color: Theme.text
        font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 }
        background: Rectangle {
            radius: 6
            color: Qt.alpha(Theme.surface, 0.9)
            border.width: 1
            border.color: Qt.alpha(Theme.text, 0.15)
        }
        onTextChanged: debounce.restart()
    }

    // One query per pause in typing, not per key.
    Timer {
        id: debounce
        interval: 150
        onTriggered: field.text.trim() === "" ? panel.canvas.send({ type: "list" })
                                              : panel.canvas.send({ type: "search", q: field.text })
    }

    ListView {
        id: rows
        anchors { top: field.bottom; topMargin: 8; left: parent.left; right: parent.right; bottom: parent.bottom }
        clip: true
        spacing: 4
        boundsBehavior: Flickable.StopAtBounds
        model: field.text.trim() === "" ? panel.canvas.convs : panel.canvas.results

        delegate: Rectangle {
            id: r
            required property var modelData
            width: rows.width
            height: col.height + 12
            radius: 6
            color: hover.hovered ? Qt.alpha(Theme.surface, 0.9) : "transparent"
            border.width: r.modelData.id === panel.canvas.convId ? 1 : 0
            border.color: Qt.alpha(Theme.accent, 0.5)

            HoverHandler { id: hover }
            MouseArea {
                anchors.fill: parent
                cursorShape: Qt.PointingHandCursor
                onClicked: { panel.canvas.send({ type: "open", id: r.modelData.id }); panel.canvas.historyOpen = false; }
            }

            Column {
                id: col
                anchors { left: parent.left; right: del.left; verticalCenter: parent.verticalCenter; margins: 8 }
                spacing: 2
                Text {
                    width: col.width
                    text: r.modelData.title
                    elide: Text.ElideRight
                    color: Theme.text
                    font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 }
                }
                Text {
                    width: col.width
                    // A list row has its date; a search row its snippet.
                    text: r.modelData.snippet !== undefined
                        // Backticks go: a match inside a code span would
                        // show its ** markers literally.
                        ? r.modelData.snippet.replace(/\s+/g, " ").replace(/`/g, "")
                        : new Date(r.modelData.updated * 1000).toLocaleString(Qt.locale(), Locale.ShortFormat)
                    textFormat: r.modelData.snippet !== undefined ? Text.MarkdownText : Text.PlainText
                    elide: Text.ElideRight
                    maximumLineCount: 1
                    color: Theme.subtext
                    font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
                }
            }

            // Two clicks: the first arms it for 3 seconds, as the clipboard's
            // clear-all does.
            Button {
                id: del
                anchors { right: parent.right; rightMargin: 6; verticalCenter: parent.verticalCenter }
                text: armed.running ? "Sure?" : "Delete"
                danger: true
                onClicked: {
                    if (!armed.running) { armed.restart(); return; }
                    armed.stop();
                    panel.canvas.send({ type: "delete", id: r.modelData.id });
                }
                Timer { id: armed; interval: 3000 }
            }
        }

        Text {
            anchors.centerIn: parent
            visible: rows.count === 0
            text: field.text.trim() === "" ? "No conversations yet." : "Nothing found."
            color: Theme.overlay
            font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 }
        }
    }
}