gpt4all/gpt4all-chat/qml/ChatView.qml
Jared Van Bortel c622921894
improve mixpanel usage statistics (#2238)
Other changes:
- Always display first start dialog if privacy options are unset (e.g. if the user closed GPT4All without selecting them)
- LocalDocs scanQueue is now always deferred
- Fix a potential crash in magic_match
- LocalDocs indexing is now started after the first start dialog is dismissed so usage stats are included

Signed-off-by: Jared Van Bortel <jared@nomic.ai>
2024-04-25 13:16:52 -04:00

1495 lines
59 KiB
QML

import Qt5Compat.GraphicalEffects
import QtCore
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Basic
import QtQuick.Layouts
import chatlistmodel
import download
import gpt4all
import llm
import localdocs
import modellist
import mysettings
import network
Rectangle {
id: window
Theme {
id: theme
}
property var currentChat: ChatListModel.currentChat
property var chatModel: currentChat.chatModel
color: theme.black
// Startup code
Component.onCompleted: {
startupDialogs();
}
Component.onDestruction: {
Network.trackEvent("session_end")
}
Connections {
target: firstStartDialog
function onClosed() {
startupDialogs();
}
}
Connections {
target: downloadNewModels
function onClosed() {
startupDialogs();
}
}
Connections {
target: Download
function onHasNewerReleaseChanged() {
startupDialogs();
}
}
Connections {
target: currentChat
function onResponseInProgressChanged() {
if (MySettings.networkIsActive && !currentChat.responseInProgress)
Network.sendConversation(currentChat.id, getConversationJson());
}
function onModelLoadingErrorChanged() {
if (currentChat.modelLoadingError !== "")
modelLoadingErrorPopup.open()
}
function onModelLoadingWarning(warning) {
modelLoadingWarningPopup.open_(warning)
}
}
property bool hasShownModelDownload: false
property bool hasCheckedFirstStart: false
property bool hasShownSettingsAccess: false
function startupDialogs() {
if (!LLM.compatHardware()) {
Network.trackEvent("noncompat_hardware")
errorCompatHardware.open();
return;
}
// check if we have access to settings and if not show an error
if (!hasShownSettingsAccess && !LLM.hasSettingsAccess()) {
errorSettingsAccess.open();
hasShownSettingsAccess = true;
return;
}
// check for first time start of this version
if (!hasCheckedFirstStart) {
if (Download.isFirstStart(/*writeVersion*/ true)) {
firstStartDialog.open();
return;
}
// send startup or opt-out now that the user has made their choice
Network.sendStartup()
// start localdocs
LocalDocs.requestStart()
hasCheckedFirstStart = true
}
// check for any current models and if not, open download dialog once
if (!hasShownModelDownload && ModelList.installedModels.count === 0 && !firstStartDialog.opened) {
downloadNewModels.open();
hasShownModelDownload = true;
return;
}
// check for new version
if (Download.hasNewerRelease && !firstStartDialog.opened && !downloadNewModels.opened) {
newVersionDialog.open();
return;
}
}
function currentModelName() {
return ModelList.modelInfo(currentChat.modelInfo.id).name;
}
property bool isCurrentlyLoading: false
property real modelLoadingPercentage: 0.0
property bool trySwitchContextInProgress: false
PopupDialog {
id: errorCompatHardware
anchors.centerIn: parent
shouldTimeOut: false
shouldShowBusy: false
closePolicy: Popup.NoAutoClose
modal: true
text: qsTr("<h3>Encountered an error starting up:</h3><br>")
+ qsTr("<i>\"Incompatible hardware detected.\"</i>")
+ qsTr("<br><br>Unfortunately, your CPU does not meet the minimal requirements to run ")
+ qsTr("this program. In particular, it does not support AVX intrinsics which this ")
+ qsTr("program requires to successfully run a modern large language model. ")
+ qsTr("The only solution at this time is to upgrade your hardware to a more modern CPU.")
+ qsTr("<br><br>See here for more information: <a href=\"https://en.wikipedia.org/wiki/Advanced_Vector_Extensions\">")
+ qsTr("https://en.wikipedia.org/wiki/Advanced_Vector_Extensions</a>")
}
PopupDialog {
id: errorSettingsAccess
anchors.centerIn: parent
shouldTimeOut: false
shouldShowBusy: false
modal: true
text: qsTr("<h3>Encountered an error starting up:</h3><br>")
+ qsTr("<i>\"Inability to access settings file.\"</i>")
+ qsTr("<br><br>Unfortunately, something is preventing the program from accessing ")
+ qsTr("the settings file. This could be caused by incorrect permissions in the local ")
+ qsTr("app config directory where the settings file is located. ")
+ qsTr("Check out our <a href=\"https://discord.gg/4M2QFmTt2k\">discord channel</a> for help.")
}
StartupDialog {
id: firstStartDialog
anchors.centerIn: parent
}
NewVersionDialog {
id: newVersionDialog
anchors.centerIn: parent
}
AboutDialog {
id: aboutDialog
anchors.centerIn: parent
width: Math.min(1024, window.width - (window.width * .2))
height: Math.min(600, window.height - (window.height * .2))
}
Item {
Accessible.role: Accessible.Window
Accessible.name: title
}
PopupDialog {
id: modelLoadingErrorPopup
anchors.centerIn: parent
shouldTimeOut: false
text: qsTr("<h3>Encountered an error loading model:</h3><br>")
+ "<i>\"" + currentChat.modelLoadingError + "\"</i>"
+ qsTr("<br><br>Model loading failures can happen for a variety of reasons, but the most common "
+ "causes include a bad file format, an incomplete or corrupted download, the wrong file "
+ "type, not enough system RAM or an incompatible model type. Here are some suggestions for resolving the problem:"
+ "<br><ul>"
+ "<li>Ensure the model file has a compatible format and type"
+ "<li>Check the model file is complete in the download folder"
+ "<li>You can find the download folder in the settings dialog"
+ "<li>If you've sideloaded the model ensure the file is not corrupt by checking md5sum"
+ "<li>Read more about what models are supported in our <a href=\"https://docs.gpt4all.io/gpt4all_chat.html\">documentation</a> for the gui"
+ "<li>Check out our <a href=\"https://discord.gg/4M2QFmTt2k\">discord channel</a> for help")
}
PopupDialog {
id: modelLoadingWarningPopup
property string message
anchors.centerIn: parent
shouldTimeOut: false
text: qsTr("<h3>Warning</h3><p>%1</p>").arg(message)
function open_(msg) { message = msg; open(); }
}
Rectangle {
id: accentRibbon
anchors.left: parent.left
anchors.right: parent.right
anchors.top: header.bottom
height: 3
color: theme.accentColor
}
Rectangle {
id: titleBar
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: 5
z: 200
height: 25
color: "transparent"
RowLayout {
anchors.left: parent.left
anchors.leftMargin: 30
Text {
textFormat: Text.StyledText
text: "<a href=\"https://gpt4all.io\">gpt4all.io</a> |"
horizontalAlignment: Text.AlignLeft
font.pixelSize: theme.fontSizeFixedSmall
color: theme.iconBackgroundLight
linkColor: hoverHandler1.hovered ? theme.iconBackgroundHovered : theme.iconBackgroundLight
HoverHandler { id: hoverHandler1 }
onLinkActivated: { Qt.openUrlExternally("https://gpt4all.io") }
}
Text {
textFormat: Text.StyledText
text: "<a href=\"https://github.com/nomic-ai/gpt4all\">github</a>"
horizontalAlignment: Text.AlignLeft
font.pixelSize: theme.fontSizeFixedSmall
color: theme.iconBackgroundLight
linkColor: hoverHandler2.hovered ? theme.iconBackgroundHovered : theme.iconBackgroundLight
HoverHandler { id: hoverHandler2 }
onLinkActivated: { Qt.openUrlExternally("https://github.com/nomic-ai/gpt4all") }
}
}
RowLayout {
anchors.right: parent.right
anchors.rightMargin: 30
Text {
textFormat: Text.StyledText
text: "<a href=\"https://nomic.ai\">nomic.ai</a> |"
horizontalAlignment: Text.AlignRight
font.pixelSize: theme.fontSizeFixedSmall
color: theme.iconBackgroundLight
linkColor: hoverHandler3.hovered ? theme.iconBackgroundHovered : theme.iconBackgroundLight
HoverHandler { id: hoverHandler3 }
onLinkActivated: { Qt.openUrlExternally("https://nomic.ai") }
}
Text {
textFormat: Text.StyledText
text: "<a href=\"https://twitter.com/nomic_ai\">twitter</a> |"
horizontalAlignment: Text.AlignRight
font.pixelSize: theme.fontSizeFixedSmall
color: theme.iconBackgroundLight
linkColor: hoverHandler4.hovered ? theme.iconBackgroundHovered : theme.iconBackgroundLight
HoverHandler { id: hoverHandler4 }
onLinkActivated: { Qt.openUrlExternally("https://twitter.com/nomic_ai") }
}
Text {
textFormat: Text.StyledText
text: "<a href=\"https://discord.gg/4M2QFmTt2k\">discord</a>"
horizontalAlignment: Text.AlignRight
font.pixelSize: theme.fontSizeFixedSmall
color: theme.iconBackgroundLight
linkColor: hoverHandler5.hovered ? theme.iconBackgroundHovered : theme.iconBackgroundLight
HoverHandler { id: hoverHandler5 }
onLinkActivated: { Qt.openUrlExternally("https://discord.gg/4M2QFmTt2k") }
}
}
}
SwitchModelDialog {
id: switchModelDialog
anchors.centerIn: parent
Item {
Accessible.role: Accessible.Dialog
Accessible.name: qsTr("Switch model dialog")
Accessible.description: qsTr("Warn the user if they switch models, then context will be erased")
}
}
Rectangle {
id: header
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
height: 100
color: theme.mainHeader
Item {
anchors.centerIn: parent
height: childrenRect.height
visible: true
Label {
id: modelLabel
color: theme.textColor
padding: 20
font.pixelSize: theme.fontSizeLarger
text: ""
background: Rectangle {
color: theme.mainHeader
}
horizontalAlignment: TextInput.AlignRight
}
RowLayout {
id: comboLayout
anchors.top: modelLabel.top
anchors.bottom: modelLabel.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.horizontalCenterOffset: window.width >= 950 ? 0 : Math.max(-((950 - window.width) / 2), -99.5)
spacing: 20
MyComboBox {
id: comboBox
Layout.fillWidth: true
Layout.fillHeight: true
implicitWidth: 575
width: window.width >= 750 ? implicitWidth : implicitWidth - (750 - window.width)
enabled: !currentChat.isServer
&& !window.trySwitchContextInProgress
&& !window.isCurrentlyLoading
model: ModelList.installedModels
valueRole: "id"
textRole: "name"
function changeModel(index) {
window.modelLoadingPercentage = 0.0;
window.isCurrentlyLoading = true;
currentChat.stopGenerating()
currentChat.reset();
currentChat.modelInfo = ModelList.modelInfo(comboBox.valueAt(index))
}
Connections {
target: currentChat
function onModelLoadingPercentageChanged() {
window.modelLoadingPercentage = currentChat.modelLoadingPercentage;
window.isCurrentlyLoading = currentChat.modelLoadingPercentage !== 0.0
&& currentChat.modelLoadingPercentage !== 1.0;
}
function onTrySwitchContextOfLoadedModelAttempted() {
window.trySwitchContextInProgress = true;
}
function onTrySwitchContextOfLoadedModelCompleted() {
window.trySwitchContextInProgress = false;
}
}
Connections {
target: switchModelDialog
function onAccepted() {
comboBox.changeModel(switchModelDialog.index)
}
}
background: ProgressBar {
id: modelProgress
value: window.modelLoadingPercentage
background: Rectangle {
color: theme.mainComboBackground
radius: 10
}
contentItem: Item {
Rectangle {
visible: window.isCurrentlyLoading
anchors.bottom: parent.bottom
width: modelProgress.visualPosition * parent.width
height: 10
radius: 2
color: theme.progressForeground
}
}
}
contentItem: Text {
anchors.horizontalCenter: parent.horizontalCenter
leftPadding: 10
rightPadding: {
if (ejectButton.visible && reloadButton)
return 105;
if (reloadButton.visible)
return 65
return 25
}
text: {
if (currentChat.modelLoadingError !== "")
return qsTr("Model loading error...")
if (window.trySwitchContextInProgress)
return qsTr("Switching context...")
if (currentModelName() === "")
return qsTr("Choose a model...")
if (currentChat.modelLoadingPercentage === 0.0)
return qsTr("Reload \u00B7 ") + currentModelName()
if (window.isCurrentlyLoading)
return qsTr("Loading \u00B7 ") + currentModelName()
return currentModelName()
}
font.pixelSize: theme.fontSizeLarger
color: theme.white
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
}
delegate: ItemDelegate {
id: comboItemDelegate
width: comboBox.width
contentItem: Text {
text: name
color: theme.textColor
font: comboBox.font
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
color: (index % 2 === 0 ? theme.darkContrast : theme.lightContrast)
border.width: highlighted
border.color: theme.accentColor
}
highlighted: comboBox.highlightedIndex === index
}
Accessible.role: Accessible.ComboBox
Accessible.name: currentModelName()
Accessible.description: qsTr("The top item is the current model")
onActivated: function (index) {
var newInfo = ModelList.modelInfo(comboBox.valueAt(index));
if (newInfo === currentChat.modelInfo) {
currentChat.reloadModel();
} else if (currentModelName() !== "" && chatModel.count !== 0) {
switchModelDialog.index = index;
switchModelDialog.open();
} else {
comboBox.changeModel(index);
}
}
MyMiniButton {
id: ejectButton
visible: currentChat.isModelLoaded && !window.isCurrentlyLoading
z: 500
anchors.right: parent.right
anchors.rightMargin: 50
anchors.verticalCenter: parent.verticalCenter
source: "qrc:/gpt4all/icons/eject.svg"
backgroundColor: theme.gray300
backgroundColorHovered: theme.iconBackgroundLight
onClicked: {
currentChat.forceUnloadModel();
}
ToolTip.text: qsTr("Eject the currently loaded model")
ToolTip.visible: hovered
}
MyMiniButton {
id: reloadButton
visible: currentChat.modelLoadingError === ""
&& !window.trySwitchContextInProgress
&& !window.isCurrentlyLoading
&& (currentChat.isModelLoaded || currentModelName() !== "")
z: 500
anchors.right: ejectButton.visible ? ejectButton.left : parent.right
anchors.rightMargin: ejectButton.visible ? 10 : 50
anchors.verticalCenter: parent.verticalCenter
source: "qrc:/gpt4all/icons/regenerate.svg"
backgroundColor: theme.gray300
backgroundColorHovered: theme.iconBackgroundLight
onClicked: {
if (currentChat.isModelLoaded)
currentChat.forceReloadModel();
else
currentChat.reloadModel();
}
ToolTip.text: qsTr("Reload the currently loaded model")
ToolTip.visible: hovered
}
}
}
}
}
SettingsDialog {
id: settingsDialog
anchors.centerIn: parent
width: Math.min(1920, window.width - (window.width * .1))
height: window.height - (window.height * .1)
onDownloadClicked: {
downloadNewModels.showEmbeddingModels = true
downloadNewModels.open()
}
}
MyToolButton {
id: drawerButton
backgroundColor: theme.iconBackgroundLight
anchors.left: parent.left
anchors.top: parent.top
anchors.topMargin: 42.5
anchors.leftMargin: 30
width: 40
height: 40
scale: 1.5
z: 200
padding: 15
source: conversation.state === "expanded" ? "qrc:/gpt4all/icons/left_panel_open.svg" : "qrc:/gpt4all/icons/left_panel_closed.svg"
Accessible.role: Accessible.ButtonMenu
Accessible.name: qsTr("Chat panel")
Accessible.description: qsTr("Chat panel with options")
onClicked: {
conversation.toggleLeftPanel()
}
}
NetworkDialog {
id: networkDialog
anchors.centerIn: parent
width: Math.min(1024, window.width - (window.width * .2))
height: Math.min(600, window.height - (window.height * .2))
Item {
Accessible.role: Accessible.Dialog
Accessible.name: qsTr("Network dialog")
Accessible.description: qsTr("opt-in to share feedback/conversations")
}
}
MyToolButton {
id: networkButton
backgroundColor: theme.iconBackgroundLight
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: 42.5
anchors.rightMargin: 30
width: 40
height: 40
z: 200
padding: 15
toggled: MySettings.networkIsActive
source: "qrc:/gpt4all/icons/network.svg"
Accessible.name: qsTr("Network")
Accessible.description: qsTr("Reveals a dialogue where you can opt-in for sharing data over network")
onClicked: {
if (MySettings.networkIsActive) {
MySettings.networkIsActive = false
} else
networkDialog.open()
}
}
Connections {
target: Network
function onHealthCheckFailed(code) {
healthCheckFailed.open();
}
}
CollectionsDialog {
id: collectionsDialog
anchors.centerIn: parent
onAddRemoveClicked: {
settingsDialog.pageToDisplay = 2;
settingsDialog.open();
}
}
MyToolButton {
id: collectionsButton
backgroundColor: theme.iconBackgroundLight
anchors.right: networkButton.left
anchors.top: parent.top
anchors.topMargin: 42.5
anchors.rightMargin: 10
width: 40
height: 42.5
z: 200
padding: 15
toggled: currentChat.collectionList.length
source: "qrc:/gpt4all/icons/db.svg"
Accessible.name: qsTr("Add documents")
Accessible.description: qsTr("add collections of documents to the chat")
onClicked: {
collectionsDialog.open()
}
}
MyToolButton {
id: settingsButton
backgroundColor: theme.iconBackgroundLight
anchors.right: collectionsButton.left
anchors.top: parent.top
anchors.topMargin: 42.5
anchors.rightMargin: 10
width: 40
height: 40
z: 200
padding: 15
source: "qrc:/gpt4all/icons/settings.svg"
Accessible.name: qsTr("Settings")
Accessible.description: qsTr("Reveals a dialogue with settings")
onClicked: {
settingsDialog.open()
}
}
PopupDialog {
id: copyMessage
anchors.centerIn: parent
text: qsTr("Conversation copied to clipboard.")
font.pixelSize: theme.fontSizeLarge
}
PopupDialog {
id: copyCodeMessage
anchors.centerIn: parent
text: qsTr("Code copied to clipboard.")
font.pixelSize: theme.fontSizeLarge
}
PopupDialog {
id: healthCheckFailed
anchors.centerIn: parent
text: qsTr("Connection to datalake failed.")
font.pixelSize: theme.fontSizeLarge
}
PopupDialog {
id: recalcPopup
anchors.centerIn: parent
shouldTimeOut: false
shouldShowBusy: true
text: qsTr("Recalculating context.")
font.pixelSize: theme.fontSizeLarge
Connections {
target: currentChat
function onRecalcChanged() {
if (currentChat.isRecalc)
recalcPopup.open()
else
recalcPopup.close()
}
}
}
MyToolButton {
id: copyButton
backgroundColor: theme.iconBackgroundLight
anchors.right: settingsButton.left
anchors.top: parent.top
anchors.topMargin: 42.5
anchors.rightMargin: 10
width: 40
height: 40
z: 200
padding: 15
source: "qrc:/gpt4all/icons/copy.svg"
Accessible.name: qsTr("Copy")
Accessible.description: qsTr("Copy the conversation to the clipboard")
TextEdit{
id: copyEdit
visible: false
}
onClicked: {
var conversation = getConversation()
copyEdit.text = conversation
copyEdit.selectAll()
copyEdit.copy()
copyMessage.open()
}
}
function getConversation() {
var conversation = "";
for (var i = 0; i < chatModel.count; i++) {
var item = chatModel.get(i)
var string = item.name;
var isResponse = item.name === qsTr("Response: ")
string += chatModel.get(i).value
if (isResponse && item.stopped)
string += " <stopped>"
string += "\n"
conversation += string
}
return conversation
}
function getConversationJson() {
var str = "{\"conversation\": [";
for (var i = 0; i < chatModel.count; i++) {
var item = chatModel.get(i)
var isResponse = item.name === qsTr("Response: ")
str += "{\"content\": ";
str += JSON.stringify(item.value)
str += ", \"role\": \"" + (isResponse ? "assistant" : "user") + "\"";
if (isResponse && item.thumbsUpState !== item.thumbsDownState)
str += ", \"rating\": \"" + (item.thumbsUpState ? "positive" : "negative") + "\"";
if (isResponse && item.newResponse !== "")
str += ", \"edited_content\": " + JSON.stringify(item.newResponse);
if (isResponse && item.stopped)
str += ", \"stopped\": \"true\""
if (!isResponse)
str += "},"
else
str += ((i < chatModel.count - 1) ? "}," : "}")
}
return str + "]}"
}
MyToolButton {
id: resetContextButton
backgroundColor: theme.iconBackgroundLight
anchors.right: copyButton.left
anchors.top: parent.top
anchors.topMargin: 42.5
anchors.rightMargin: 10
width: 40
height: 40
z: 200
padding: 15
source: "qrc:/gpt4all/icons/regenerate.svg"
Accessible.name: text
Accessible.description: qsTr("Reset the context and erase current conversation")
onClicked: {
Network.trackChatEvent("reset_context", { "length": chatModel.count })
currentChat.reset();
currentChat.processSystemPrompt();
}
}
Dialog {
id: checkForUpdatesError
anchors.centerIn: parent
modal: false
padding: 20
Text {
horizontalAlignment: Text.AlignJustify
text: qsTr("ERROR: Update system could not find the MaintenanceTool used<br>
to check for updates!<br><br>
Did you install this application using the online installer? If so,<br>
the MaintenanceTool executable should be located one directory<br>
above where this application resides on your filesystem.<br><br>
If you can't start it manually, then I'm afraid you'll have to<br>
reinstall.")
color: theme.textErrorColor
font.pixelSize: theme.fontSizeLarge
Accessible.role: Accessible.Dialog
Accessible.name: text
Accessible.description: qsTr("Error dialog")
}
background: Rectangle {
anchors.fill: parent
color: theme.containerBackground
border.width: 1
border.color: theme.dialogBorder
radius: 10
}
}
ModelDownloaderDialog {
id: downloadNewModels
anchors.centerIn: parent
width: Math.min(1920, window.width - (window.width * .1))
height: window.height - (window.height * .1)
Item {
Accessible.role: Accessible.Dialog
Accessible.name: qsTr("Download new models")
Accessible.description: qsTr("Dialog for downloading new models")
}
}
ChatDrawer {
id: drawer
anchors.left: parent.left
anchors.top: accentRibbon.bottom
anchors.bottom: parent.bottom
width: Math.min(600, 0.2 * window.width)
onDownloadClicked: {
downloadNewModels.showEmbeddingModels = false
downloadNewModels.open()
}
onAboutClicked: {
aboutDialog.open()
}
}
PopupDialog {
id: referenceContextDialog
anchors.centerIn: parent
shouldTimeOut: false
shouldShowBusy: false
modal: true
}
Rectangle {
id: conversation
color: theme.conversationBackground
anchors.left: drawer.right
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.top: accentRibbon.bottom
state: "expanded"
states: [
State {
name: "expanded"
AnchorChanges {
target: conversation
anchors.left: drawer.right
}
},
State {
name: "collapsed"
AnchorChanges {
target: conversation
anchors.left: parent.left
}
}
]
function toggleLeftPanel() {
if (conversation.state === "expanded") {
conversation.state = "collapsed";
} else {
conversation.state = "expanded";
}
}
transitions: Transition {
AnchorAnimation {
easing.type: Easing.InOutQuad
duration: 200
}
}
ScrollView {
id: scrollView
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: !currentChat.isServer ? textInputView.top : parent.bottom
anchors.bottomMargin: !currentChat.isServer ? 30 : 0
ScrollBar.vertical.policy: ScrollBar.AlwaysOff
Rectangle {
anchors.fill: parent
color: currentChat.isServer ? theme.black : theme.conversationBackground
Rectangle {
id: homePage
color: "transparent"
anchors.fill: parent
visible: !currentChat.isModelLoaded && (ModelList.installedModels.count === 0 || currentModelName() === "") && !currentChat.isServer
ColumnLayout {
anchors.centerIn: parent
spacing: 0
Text {
Layout.alignment: Qt.AlignHCenter
text: qsTr("GPT4All")
color: theme.titleTextColor
font.pixelSize: theme.fontSizeLargest + 15
font.bold: true
horizontalAlignment: Qt.AlignHCenter
wrapMode: Text.WordWrap
}
Text {
Layout.alignment: Qt.AlignHCenter
textFormat: Text.StyledText
text: qsTr(
"<ul>
<li>Run privacy-aware local chatbots.
<li>No internet required to use.
<li>CPU and GPU acceleration.
<li>Chat with your local data and documents.
<li>Built by Nomic AI and forever open-source.
</ul>
")
color: theme.textColor
font.pixelSize: theme.fontSizeLarge
wrapMode: Text.WordWrap
}
RowLayout {
spacing: 10
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 30
MySlug {
text: "MISTRAL"
color: theme.red600
}
MySlug {
text: "FALCON"
color: theme.green600
}
MySlug {
text: "LLAMA"
color: theme.purple500
}
MySlug {
text: "LLAMA2"
color: theme.red400
}
MySlug {
text: "MPT"
color: theme.green700
}
MySlug {
text: "REPLIT"
color: theme.yellow700
}
MySlug {
text: "STARCODER"
color: theme.purple400
}
MySlug {
text: "SBERT"
color: theme.yellow600
}
MySlug {
text: "GPT-J"
color: theme.gray600
}
}
Text {
Layout.alignment: Qt.AlignHCenter
textFormat: Text.StyledText
text: qsTr(
"<p></p><a href=\"https://docs.gpt4all.io/gpt4all_chat.html\">Documentation
")
onLinkActivated: { Qt.openUrlExternally("https://docs.gpt4all.io/gpt4all_chat.html") }
color: theme.textColor
linkColor: theme.linkColor
font.pixelSize: theme.fontSizeLarge
wrapMode: Text.WordWrap
}
Text {
Layout.alignment: Qt.AlignHCenter
textFormat: Text.StyledText
text: qsTr(
"<a href=\"https://docs.gpt4all.io/gpt4all_faq.html\">Frequently Asked Questions
")
onLinkActivated: { Qt.openUrlExternally("https://docs.gpt4all.io/gpt4all_faq.html") }
color: theme.textColor
linkColor: theme.linkColor
font.pixelSize: theme.fontSizeLarge
wrapMode: Text.WordWrap
}
MyButton {
id: downloadButton
visible: LLM.isNetworkOnline
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 40
text: qsTr("Download models")
fontPixelSize: theme.fontSizeLargest + 10
padding: 18
leftPadding: 50
Image {
id: image
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 15
width: 24
height: 24
mipmap: true
source: "qrc:/gpt4all/icons/download.svg"
}
ColorOverlay {
anchors.fill: image
source: image
color: theme.accentColor
}
onClicked: {
downloadNewModels.open();
}
}
}
}
ListView {
id: listView
visible: ModelList.installedModels.count !== 0 && chatModel.count !== 0
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
width: Math.min(1280, parent.width)
model: chatModel
ScrollBar.vertical: ScrollBar {
policy: ScrollBar.AsNeeded
}
Accessible.role: Accessible.List
Accessible.name: qsTr("Conversation with the model")
Accessible.description: qsTr("prompt / response pairs from the conversation")
delegate: TextArea {
id: myTextArea
text: value + (MySettings.localDocsShowReferences ? references : "")
width: listView.width
color: {
if (!currentChat.isServer)
return theme.textColor
if (name === qsTr("Response: "))
return theme.white
return theme.black
}
wrapMode: Text.WordWrap
textFormat: TextEdit.PlainText
focus: false
readOnly: true
font.pixelSize: theme.fontSizeLarge
cursorVisible: currentResponse ? currentChat.responseInProgress : false
cursorPosition: text.length
background: Rectangle {
opacity: 1.0
color: name === qsTr("Response: ")
? (currentChat.isServer ? theme.black : theme.lightContrast)
: (currentChat.isServer ? theme.white : theme.darkContrast)
}
TapHandler {
id: tapHandler
onTapped: function(eventPoint, button) {
var clickedPos = myTextArea.positionAt(eventPoint.position.x, eventPoint.position.y);
var link = responseText.getLinkAtPosition(clickedPos);
if (link.startsWith("context://")) {
var integer = parseInt(link.split("://")[1]);
referenceContextDialog.text = referencesContext[integer - 1];
referenceContextDialog.open();
} else {
var success = responseText.tryCopyAtPosition(clickedPos);
if (success)
copyCodeMessage.open();
}
}
}
MouseArea {
id: conversationMouseArea
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: {
if (mouse.button === Qt.RightButton) {
conversationContextMenu.x = conversationMouseArea.mouseX
conversationContextMenu.y = conversationMouseArea.mouseY
conversationContextMenu.open()
}
}
}
Menu {
id: conversationContextMenu
MenuItem {
text: qsTr("Copy")
onTriggered: myTextArea.copy()
}
MenuItem {
text: qsTr("Select All")
onTriggered: myTextArea.selectAll()
}
}
ResponseText {
id: responseText
}
Component.onCompleted: {
responseText.setLinkColor(theme.linkColor);
responseText.setHeaderColor(name === qsTr("Response: ") ? theme.darkContrast : theme.lightContrast);
responseText.textDocument = textDocument
}
Accessible.role: Accessible.Paragraph
Accessible.name: text
Accessible.description: name === qsTr("Response: ") ? "The response by the model" : "The prompt by the user"
topPadding: 20
bottomPadding: 20
leftPadding: 70
rightPadding: 100
Item {
anchors.left: parent.left
anchors.leftMargin: 60
y: parent.topPadding + (parent.positionToRectangle(0).height / 2) - (height / 2)
visible: (currentResponse ? true : false) && value === "" && currentChat.responseInProgress
width: childrenRect.width
height: childrenRect.height
Row {
spacing: 5
MyBusyIndicator {
anchors.verticalCenter: parent.verticalCenter
running: (currentResponse ? true : false) && value === "" && currentChat.responseInProgress
Accessible.role: Accessible.Animation
Accessible.name: qsTr("Busy indicator")
Accessible.description: qsTr("The model is thinking")
}
Label {
anchors.verticalCenter: parent.verticalCenter
color: theme.mutedTextColor
text: {
switch (currentChat.responseState) {
case Chat.ResponseStopped: return qsTr("response stopped ...");
case Chat.LocalDocsRetrieval: return qsTr("retrieving localdocs: ") + currentChat.collectionList.join(", ") + " ...";
case Chat.LocalDocsProcessing: return qsTr("searching localdocs: ") + currentChat.collectionList.join(", ") + " ...";
case Chat.PromptProcessing: return qsTr("processing ...")
case Chat.ResponseGeneration: return qsTr("generating response ...");
default: return ""; // handle unexpected values
}
}
}
}
}
Rectangle {
anchors.left: parent.left
anchors.leftMargin: 20
y: parent.topPadding + (parent.positionToRectangle(0).height / 2) - (height / 2)
width: 30
height: 30
radius: 5
color: name === qsTr("Response: ") ? theme.assistantColor : theme.userColor
Text {
anchors.centerIn: parent
text: name === qsTr("Response: ") ? "R" : "P"
color: "white"
}
}
ThumbsDownDialog {
id: thumbsDownDialog
property point globalPoint: mapFromItem(window,
window.width / 2 - width / 2,
window.height / 2 - height / 2)
x: globalPoint.x
y: globalPoint.y
property string text: value
response: newResponse === undefined || newResponse === "" ? text : newResponse
onAccepted: {
var responseHasChanged = response !== text && response !== newResponse
if (thumbsDownState && !thumbsUpState && !responseHasChanged)
return
chatModel.updateNewResponse(index, response)
chatModel.updateThumbsUpState(index, false)
chatModel.updateThumbsDownState(index, true)
Network.sendConversation(currentChat.id, getConversationJson());
}
}
Column {
visible: name === qsTr("Response: ") &&
(!currentResponse || !currentChat.responseInProgress) && MySettings.networkIsActive
anchors.right: parent.right
anchors.rightMargin: 20
y: parent.topPadding + (parent.positionToRectangle(0).height / 2) - (height / 2)
spacing: 10
Item {
width: childrenRect.width
height: childrenRect.height
MyToolButton {
id: thumbsUp
width: 30
height: 30
opacity: thumbsUpState || thumbsUpState == thumbsDownState ? 1.0 : 0.2
source: "qrc:/gpt4all/icons/thumbs_up.svg"
Accessible.name: qsTr("Thumbs up")
Accessible.description: qsTr("Gives a thumbs up to the response")
onClicked: {
if (thumbsUpState && !thumbsDownState)
return
chatModel.updateNewResponse(index, "")
chatModel.updateThumbsUpState(index, true)
chatModel.updateThumbsDownState(index, false)
Network.sendConversation(currentChat.id, getConversationJson());
}
}
MyToolButton {
id: thumbsDown
anchors.top: thumbsUp.top
anchors.topMargin: 10
anchors.left: thumbsUp.right
anchors.leftMargin: 2
width: 30
height: 30
checked: thumbsDownState
opacity: thumbsDownState || thumbsUpState == thumbsDownState ? 1.0 : 0.2
transform: [
Matrix4x4 {
matrix: Qt.matrix4x4(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)
},
Translate {
x: thumbsDown.width
}
]
source: "qrc:/gpt4all/icons/thumbs_down.svg"
Accessible.name: qsTr("Thumbs down")
Accessible.description: qsTr("Opens thumbs down dialog")
onClicked: {
thumbsDownDialog.open()
}
}
}
}
}
property bool shouldAutoScroll: true
property bool isAutoScrolling: false
Connections {
target: currentChat
function onResponseChanged() {
if (listView.shouldAutoScroll) {
listView.isAutoScrolling = true
listView.positionViewAtEnd()
listView.isAutoScrolling = false
}
}
}
onContentYChanged: {
if (!isAutoScrolling)
shouldAutoScroll = atYEnd
}
Component.onCompleted: {
shouldAutoScroll = true
positionViewAtEnd()
}
footer: Item {
id: bottomPadding
width: parent.width
height: 60
}
}
Image {
visible: currentChat.isServer || currentChat.modelInfo.isOnline
anchors.fill: parent
sourceSize.width: 1024
sourceSize.height: 1024
fillMode: Image.PreserveAspectFit
opacity: 0.15
source: "qrc:/gpt4all/icons/network.svg"
}
}
}
RowLayout {
anchors.bottom: textInputView.top
anchors.horizontalCenter: textInputView.horizontalCenter
anchors.bottomMargin: 20
spacing: 10
MyButton {
textColor: theme.textColor
visible: chatModel.count && !currentChat.isServer && currentChat.isModelLoaded
Image {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 15
source: currentChat.responseInProgress ? "qrc:/gpt4all/icons/stop_generating.svg" : "qrc:/gpt4all/icons/regenerate.svg"
}
leftPadding: 50
onClicked: {
var index = Math.max(0, chatModel.count - 1);
var listElement = chatModel.get(index);
if (currentChat.responseInProgress) {
Network.trackChatEvent("stop_generating_clicked")
listElement.stopped = true
currentChat.stopGenerating()
} else {
Network.trackChatEvent("regenerate_clicked")
currentChat.regenerateResponse()
if (chatModel.count) {
if (listElement.name === qsTr("Response: ")) {
chatModel.updateCurrentResponse(index, true);
chatModel.updateStopped(index, false);
chatModel.updateThumbsUpState(index, false);
chatModel.updateThumbsDownState(index, false);
chatModel.updateNewResponse(index, "");
currentChat.prompt(listElement.prompt)
}
}
}
}
borderWidth: 1
backgroundColor: theme.conversationButtonBackground
backgroundColorHovered: theme.conversationButtonBackgroundHovered
backgroundRadius: 5
padding: 15
topPadding: 8
bottomPadding: 8
text: currentChat.responseInProgress ? qsTr("Stop generating") : qsTr("Regenerate response")
fontPixelSize: theme.fontSizeSmall
Accessible.description: qsTr("Controls generation of the response")
}
MyButton {
textColor: theme.textColor
visible: !currentChat.isServer
&& !currentChat.isModelLoaded
&& !window.trySwitchContextInProgress
&& !window.isCurrentlyLoading
&& currentModelName() !== ""
Image {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 15
source: "qrc:/gpt4all/icons/regenerate.svg"
}
leftPadding: 50
onClicked: {
currentChat.reloadModel();
}
borderWidth: 1
backgroundColor: theme.conversationButtonBackground
backgroundColorHovered: theme.conversationButtonBackgroundHovered
backgroundRadius: 5
padding: 15
topPadding: 8
bottomPadding: 8
text: qsTr("Reload \u00B7 ") + currentChat.modelInfo.name
fontPixelSize: theme.fontSizeSmall
Accessible.description: qsTr("Reloads the model")
}
}
Text {
id: device
anchors.bottom: textInputView.top
anchors.bottomMargin: 20
anchors.right: parent.right
anchors.rightMargin: 30
color: theme.mutedTextColor
visible: currentChat.tokenSpeed !== ""
text: qsTr("Speed: ") + currentChat.tokenSpeed + "<br>" + qsTr("Device: ") + currentChat.device + currentChat.fallbackReason
font.pixelSize: theme.fontSizeLarge
}
RectangularGlow {
id: effect
visible: !currentChat.isServer
anchors.fill: textInputView
glowRadius: 50
spread: 0
color: theme.sendGlow
cornerRadius: 10
opacity: 0.1
}
ScrollView {
id: textInputView
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: 30
height: Math.min(contentHeight, 200)
visible: !currentChat.isServer
MyTextArea {
id: textInput
color: theme.textColor
topPadding: 30
bottomPadding: 30
leftPadding: 20
rightPadding: 40
enabled: currentChat.isModelLoaded && !currentChat.isServer
font.pixelSize: theme.fontSizeLarger
placeholderText: currentChat.isModelLoaded ? qsTr("Send a message...") : qsTr("Load a model to continue...")
Accessible.role: Accessible.EditableText
Accessible.name: placeholderText
Accessible.description: qsTr("Send messages/prompts to the model")
Keys.onReturnPressed: (event)=> {
if (event.modifiers & Qt.ControlModifier || event.modifiers & Qt.ShiftModifier)
event.accepted = false;
else {
editingFinished();
sendMessage()
}
}
function sendMessage() {
if (textInput.text === "")
return
Network.trackChatEvent("send_message")
currentChat.stopGenerating()
currentChat.newPromptResponsePair(textInput.text);
currentChat.prompt(textInput.text,
MySettings.promptTemplate,
MySettings.maxLength,
MySettings.topK,
MySettings.topP,
MySettings.minP,
MySettings.temperature,
MySettings.promptBatchSize,
MySettings.repeatPenalty,
MySettings.repeatPenaltyTokens)
textInput.text = ""
}
MouseArea {
id: textInputMouseArea
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: {
if (mouse.button === Qt.RightButton) {
textInputContextMenu.x = textInputMouseArea.mouseX
textInputContextMenu.y = textInputMouseArea.mouseY
textInputContextMenu.open()
}
}
}
Menu {
id: textInputContextMenu
MenuItem {
text: qsTr("Cut")
onTriggered: textInput.cut()
}
MenuItem {
text: qsTr("Copy")
onTriggered: textInput.copy()
}
MenuItem {
text: qsTr("Paste")
onTriggered: textInput.paste()
}
MenuItem {
text: qsTr("Select All")
onTriggered: textInput.selectAll()
}
}
}
}
MyToolButton {
backgroundColor: theme.sendButtonBackground
backgroundColorHovered: theme.sendButtonBackgroundHovered
anchors.right: textInputView.right
anchors.verticalCenter: textInputView.verticalCenter
anchors.rightMargin: 15
width: 30
height: 30
visible: !currentChat.isServer
source: "qrc:/gpt4all/icons/send_message.svg"
Accessible.name: qsTr("Send message")
Accessible.description: qsTr("Sends the message/prompt contained in textfield to the model")
onClicked: {
textInput.sendMessage()
}
}
}
}