QodeAssist/ChatView/qml/RootItem.qml
2025-05-01 00:01:44 +02:00

233 lines
7.4 KiB
QML

/*
* Copyright (C) 2024-2025 Petr Mironychev
*
* This file is part of QodeAssist.
*
* QodeAssist is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QodeAssist 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.
*
* You should have received a copy of the GNU General Public License
* along with QodeAssist. If not, see <https://www.gnu.org/licenses/>.
*/
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic as QQC
import QtQuick.Layouts
import ChatView
import "./controls"
import "./parts"
ChatRootView {
id: root
property SystemPalette sysPalette: SystemPalette {
colorGroup: SystemPalette.Active
}
palette {
window: sysPalette.window
windowText: sysPalette.windowText
base: sysPalette.base
alternateBase: sysPalette.alternateBase
text: sysPalette.text
button: sysPalette.button
buttonText: sysPalette.buttonText
highlight: sysPalette.highlight
highlightedText: sysPalette.highlightedText
light: sysPalette.light
mid: sysPalette.mid
dark: sysPalette.dark
shadow: sysPalette.shadow
brightText: sysPalette.brightText
}
Rectangle {
id: bg
anchors.fill: parent
color: palette.window
}
ColumnLayout {
anchors.fill: parent
spacing: 0
TopBar {
id: topBar
Layout.preferredWidth: parent.width
Layout.preferredHeight: 40
saveButton.onClicked: root.showSaveDialog()
loadButton.onClicked: root.showLoadDialog()
clearButton.onClicked: root.clearChat()
tokensBadge {
text: qsTr("tokens:%1/%2").arg(root.inputTokensCount).arg(root.chatModel.tokensThreshold)
}
recentPath {
text: qsTr("Latest chat file name: %1").arg(root.chatFileName.length > 0 ? root.chatFileName : "Unsaved")
}
openChatHistory.onClicked: root.openChatHistoryFolder()
}
ListView {
id: chatListView
Layout.fillWidth: true
Layout.fillHeight: true
leftMargin: 5
model: root.chatModel
clip: true
spacing: 10
boundsBehavior: Flickable.StopAtBounds
cacheBuffer: 2000
delegate: ChatItem {
required property var model
required property int index
width: ListView.view.width - scroll.width
msgModel: root.chatModel.processMessageContent(model.content)
messageAttachments: model.attachments
isUserMessage: model.roleType === ChatModel.User
messageIndex: index
listViewContentY: chatListView.contentY
textFontFamily: root.textFontFamily
codeFontFamily: root.codeFontFamily
codeFontSize: root.codeFontSize
textFontSize: root.textFontSize
textFormat: root.textFormat
onResetChatToMessage: function(index) {
messageInput.text = model.content
messageInput.cursorPosition = model.content.length
root.chatModel.resetModelTo(index)
}
}
header: Item {
width: ListView.view.width - scroll.width
height: 30
}
ScrollBar.vertical: QQC.ScrollBar {
id: scroll
}
onCountChanged: {
root.scrollToBottom()
}
onContentHeightChanged: {
if (atYEnd) {
root.scrollToBottom()
}
}
}
ScrollView {
id: view
Layout.fillWidth: true
Layout.minimumHeight: 30
Layout.maximumHeight: root.height / 2
QQC.TextArea {
id: messageInput
placeholderText: qsTr("Type your message here...")
placeholderTextColor: palette.mid
color: palette.text
background: Rectangle {
radius: 2
color: palette.base
border.color: messageInput.activeFocus ? palette.highlight : palette.button
border.width: 1
Behavior on border.color {
ColorAnimation { duration: 150 }
}
Rectangle {
anchors.fill: parent
color: palette.highlight
opacity: messageInput.hovered ? 0.1 : 0
radius: parent.radius
}
}
onTextChanged: root.calculateMessageTokensCount(messageInput.text)
Keys.onPressed: function(event) {
if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && !(event.modifiers & Qt.ShiftModifier)) {
root.sendChatMessage()
event.accepted = true;
}
}
}
}
AttachedFilesPlace {
id: attachedFilesPlace
Layout.fillWidth: true
attachedFilesModel: root.attachmentFiles
iconPath: palette.window.hslLightness > 0.5 ? "qrc:/qt/qml/ChatView/icons/attach-file-dark.svg"
: "qrc:/qt/qml/ChatView/icons/attach-file-light.svg"
accentColor: Qt.tint(palette.mid, Qt.rgba(0, 0.8, 0.3, 0.4))
onRemoveFileFromListByIndex: (index) => root.removeFileFromAttachList(index)
}
AttachedFilesPlace {
id: linkedFilesPlace
Layout.fillWidth: true
attachedFilesModel: root.linkedFiles
iconPath: palette.window.hslLightness > 0.5 ? "qrc:/qt/qml/ChatView/icons/link-file-dark.svg"
: "qrc:/qt/qml/ChatView/icons/link-file-light.svg"
accentColor: Qt.tint(palette.mid, Qt.rgba(0, 0.3, 0.8, 0.4))
onRemoveFileFromListByIndex: (index) => root.removeFileFromLinkList(index)
}
BottomBar {
id: bottomBar
Layout.preferredWidth: parent.width
Layout.preferredHeight: 40
sendButton.onClicked: root.sendChatMessage()
stopButton.onClicked: root.cancelRequest()
syncOpenFiles {
checked: root.isSyncOpenFiles
onCheckedChanged: root.setIsSyncOpenFiles(bottomBar.syncOpenFiles.checked)
}
attachFiles.onClicked: root.showAttachFilesDialog()
linkFiles.onClicked: root.showLinkFilesDialog()
}
}
function clearChat() {
root.chatModel.clear()
root.clearAttachmentFiles()
root.updateInputTokensCount()
}
function scrollToBottom() {
Qt.callLater(chatListView.positionViewAtEnd)
}
function sendChatMessage() {
root.sendMessage(messageInput.text)
messageInput.text = ""
scrollToBottom()
}
}