From 1cd734efdca8abe855a6ec422f75ac404389a9ac Mon Sep 17 00:00:00 2001 From: Adam Treat Date: Wed, 28 Jun 2023 23:46:03 -0400 Subject: [PATCH] Provide an abstraction to break up the settings dialog into managable pieces. --- gpt4all-chat/CMakeLists.txt | 5 +- gpt4all-chat/qml/ApplicationSettings.qml | 231 +++++++++ gpt4all-chat/qml/GenerationSettings.qml | 302 ++++++++++++ gpt4all-chat/qml/LocalDocs.qml | 294 ------------ gpt4all-chat/qml/LocalDocsSettings.qml | 293 ++++++++++++ gpt4all-chat/qml/MySettingsTab.qml | 46 ++ gpt4all-chat/qml/SettingsDialog.qml | 582 +---------------------- 7 files changed, 882 insertions(+), 871 deletions(-) create mode 100644 gpt4all-chat/qml/ApplicationSettings.qml create mode 100644 gpt4all-chat/qml/GenerationSettings.qml delete mode 100644 gpt4all-chat/qml/LocalDocs.qml create mode 100644 gpt4all-chat/qml/LocalDocsSettings.qml create mode 100644 gpt4all-chat/qml/MySettingsTab.qml diff --git a/gpt4all-chat/CMakeLists.txt b/gpt4all-chat/CMakeLists.txt index fdebe740..79e0dadf 100644 --- a/gpt4all-chat/CMakeLists.txt +++ b/gpt4all-chat/CMakeLists.txt @@ -89,7 +89,6 @@ qt_add_qml_module(chat main.qml qml/ChatDrawer.qml qml/CollectionsDialog.qml - qml/LocalDocs.qml qml/ModelDownloaderDialog.qml qml/NetworkDialog.qml qml/NewVersionDialog.qml @@ -99,6 +98,10 @@ qt_add_qml_module(chat qml/PopupDialog.qml qml/AboutDialog.qml qml/Theme.qml + qml/GenerationSettings.qml + qml/ApplicationSettings.qml + qml/LocalDocsSettings.qml + qml/MySettingsTab.qml qml/MyButton.qml qml/MyComboBox.qml qml/MyDirectoryField.qml diff --git a/gpt4all-chat/qml/ApplicationSettings.qml b/gpt4all-chat/qml/ApplicationSettings.qml new file mode 100644 index 00000000..85379488 --- /dev/null +++ b/gpt4all-chat/qml/ApplicationSettings.qml @@ -0,0 +1,231 @@ +import QtCore +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.Basic +import QtQuick.Layouts +import QtQuick.Dialogs +import modellist +import mysettings + +MySettingsTab { + title: qsTr("Application") + contentItem: GridLayout { + id: applicationSettingsTabInner + columns: 3 + rowSpacing: 10 + columnSpacing: 10 + + Label { + id: defaultModelLabel + text: qsTr("Default model:") + color: theme.textColor + Layout.row: 1 + Layout.column: 0 + } + MyComboBox { + id: comboBox + Layout.row: 1 + Layout.column: 1 + Layout.minimumWidth: 350 + model: ModelList.userDefaultModelList + Accessible.role: Accessible.ComboBox + Accessible.name: qsTr("ComboBox for displaying/picking the default model") + Accessible.description: qsTr("Use this for picking the default model to use; the first item is the current default model") + function updateModel() { + comboBox.currentIndex = comboBox.indexOfValue(MySettings.userDefaultModel); + } + Component.onCompleted: { + comboBox.updateModel() + } + Connections { + target: MySettings + function onUserDefaultModelChanged() { + comboBox.updateModel() + } + } + onActivated: { + MySettings.userDefaultModel = comboBox.currentText + } + } + FolderDialog { + id: modelPathDialog + title: "Please choose a directory" + currentFolder: "file://" + MySettings.modelPath + onAccepted: { + MySettings.modelPath = selectedFolder + } + } + Label { + id: modelPathLabel + text: qsTr("Download path:") + color: theme.textColor + Layout.row: 2 + Layout.column: 0 + } + MyDirectoryField { + id: modelPathDisplayField + text: MySettings.modelPath + implicitWidth: 300 + Layout.row: 2 + Layout.column: 1 + Layout.fillWidth: true + ToolTip.text: qsTr("Path where model files will be downloaded to") + ToolTip.visible: hovered + Accessible.role: Accessible.ToolTip + Accessible.name: modelPathDisplayField.text + Accessible.description: ToolTip.text + onEditingFinished: { + if (isValid) { + MySettings.modelPath = modelPathDisplayField.text + } else { + text = MySettings.modelPath + } + } + } + MyButton { + Layout.row: 2 + Layout.column: 2 + text: qsTr("Browse") + Accessible.description: qsTr("Opens a folder picker dialog to choose where to save model files") + onClicked: modelPathDialog.open() + } + Label { + id: nThreadsLabel + text: qsTr("CPU Threads:") + color: theme.textColor + Layout.row: 3 + Layout.column: 0 + } + MyTextField { + text: MySettings.threadCount + color: theme.textColor + ToolTip.text: qsTr("Amount of processing threads to use, a setting of 0 will use the lesser of 4 or your number of CPU threads") + ToolTip.visible: hovered + Layout.row: 3 + Layout.column: 1 + validator: IntValidator { + bottom: 1 + } + onEditingFinished: { + var val = parseInt(text) + if (!isNaN(val)) { + MySettings.threadCount = val + focus = false + } else { + text = MySettings.threadCount + } + } + Accessible.role: Accessible.EditableText + Accessible.name: nThreadsLabel.text + Accessible.description: ToolTip.text + } + Label { + id: saveChatsLabel + text: qsTr("Save chats to disk:") + color: theme.textColor + Layout.row: 4 + Layout.column: 0 + } + MyCheckBox { + id: saveChatsBox + Layout.row: 4 + Layout.column: 1 + checked: MySettings.saveChats + onClicked: { + Network.sendSaveChatsToggled(saveChatsBox.checked); + MySettings.saveChats = !MySettings.saveChats + } + ToolTip.text: qsTr("WARNING: Saving chats to disk can be ~2GB per chat") + ToolTip.visible: hovered + } + Label { + id: saveChatGPTChatsLabel + text: qsTr("Save ChatGPT chats to disk:") + color: theme.textColor + Layout.row: 5 + Layout.column: 0 + } + MyCheckBox { + id: saveChatGPTChatsBox + Layout.row: 5 + Layout.column: 1 + checked: MySettings.saveChatGPTChats + onClicked: { + MySettings.saveChatGPTChats = !MySettings.saveChatGPTChats + } + } + Label { + id: serverChatLabel + text: qsTr("Enable API server:") + color: theme.textColor + Layout.row: 6 + Layout.column: 0 + } + MyCheckBox { + id: serverChatBox + Layout.row: 6 + Layout.column: 1 + checked: MySettings.serverChat + onClicked: { + MySettings.serverChat = !MySettings.serverChat + } + ToolTip.text: qsTr("WARNING: This enables the gui to act as a local REST web server(OpenAI API compliant) for API requests and will increase your RAM usage as well") + ToolTip.visible: hovered + } + Rectangle { + Layout.row: 7 + Layout.column: 0 + Layout.columnSpan: 3 + Layout.fillWidth: true + height: 1 + color: theme.dialogBorder + } + Rectangle { + Layout.row: 9 + Layout.column: 0 + Layout.fillWidth: true + Layout.columnSpan: 3 + height: 1 + color: theme.dialogBorder + } + Label { + id: gpuOverrideLabel + text: qsTr("Force Metal (macOS+arm):") + color: theme.textColor + Layout.row: 8 + Layout.column: 0 + } + RowLayout { + Layout.row: 8 + Layout.column: 1 + Layout.columnSpan: 2 + MyCheckBox { + id: gpuOverrideBox + checked: MySettings.forceMetal + onClicked: { + MySettings.forceMetal = !MySettings.forceMetal + } + } + Label { + id: warningLabel + Layout.maximumWidth: 730 + Layout.alignment: Qt.AlignTop + color: theme.textErrorColor + wrapMode: Text.WordWrap + text: qsTr("WARNING: On macOS with arm (M1+) this setting forces usage of the GPU. Can cause crashes if the model requires more RAM than the system supports. Because of crash possibility the setting will not persist across restarts of the application. This has no effect on non-macs or intel.") + } + } + MyButton { + Layout.row: 10 + Layout.column: 1 + Layout.columnSpan: 2 + Layout.fillWidth: true + text: qsTr("Restore Defaults") + Accessible.description: qsTr("Restores the settings dialog to a default state") + onClicked: { + MySettings.restoreApplicationDefaults(); + } + } + } +} + diff --git a/gpt4all-chat/qml/GenerationSettings.qml b/gpt4all-chat/qml/GenerationSettings.qml new file mode 100644 index 00000000..5e05c6ee --- /dev/null +++ b/gpt4all-chat/qml/GenerationSettings.qml @@ -0,0 +1,302 @@ +import QtCore +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.Basic +import QtQuick.Layouts +import localdocs +import mysettings + +MySettingsTab { + title: qsTr("Generation") + contentItem: GridLayout { + id: generationSettingsTabInner + columns: 2 + rowSpacing: 10 + columnSpacing: 10 + + Label { + id: tempLabel + text: qsTr("Temperature:") + color: theme.textColor + Layout.row: 0 + Layout.column: 0 + } + MyTextField { + text: MySettings.temperature + color: theme.textColor + ToolTip.text: qsTr("Temperature increases the chances of choosing less likely tokens.\nNOTE: Higher temperature gives more creative but less predictable outputs.") + ToolTip.visible: hovered + Layout.row: 0 + Layout.column: 1 + validator: DoubleValidator { + locale: "C" + } + onEditingFinished: { + var val = parseFloat(text) + if (!isNaN(val)) { + MySettings.temperature = val + focus = false + } else { + text = MySettings.temperature + } + } + Accessible.role: Accessible.EditableText + Accessible.name: tempLabel.text + Accessible.description: ToolTip.text + } + Label { + id: topPLabel + text: qsTr("Top P:") + color: theme.textColor + Layout.row: 1 + Layout.column: 0 + } + MyTextField { + text: MySettings.topP + color: theme.textColor + ToolTip.text: qsTr("Only the most likely tokens up to a total probability of top_p can be chosen.\nNOTE: Prevents choosing highly unlikely tokens, aka Nucleus Sampling") + ToolTip.visible: hovered + Layout.row: 1 + Layout.column: 1 + validator: DoubleValidator { + locale: "C" + } + onEditingFinished: { + var val = parseFloat(text) + if (!isNaN(val)) { + MySettings.topP = val + focus = false + } else { + text = MySettings.topP + } + } + Accessible.role: Accessible.EditableText + Accessible.name: topPLabel.text + Accessible.description: ToolTip.text + } + Label { + id: topKLabel + text: qsTr("Top K:") + color: theme.textColor + Layout.row: 2 + Layout.column: 0 + } + MyTextField { + text: MySettings.topK + color: theme.textColor + ToolTip.text: qsTr("Only the top K most likely tokens will be chosen from") + ToolTip.visible: hovered + Layout.row: 2 + Layout.column: 1 + validator: IntValidator { + bottom: 1 + } + onEditingFinished: { + var val = parseInt(text) + if (!isNaN(val)) { + MySettings.topK = val + focus = false + } else { + text = MySettings.topK + } + } + Accessible.role: Accessible.EditableText + Accessible.name: topKLabel.text + Accessible.description: ToolTip.text + } + Label { + id: maxLengthLabel + text: qsTr("Max Length:") + color: theme.textColor + Layout.row: 3 + Layout.column: 0 + } + MyTextField { + text: MySettings.maxLength + color: theme.textColor + ToolTip.text: qsTr("Maximum length of response in tokens") + ToolTip.visible: hovered + Layout.row: 3 + Layout.column: 1 + validator: IntValidator { + bottom: 1 + } + onEditingFinished: { + var val = parseInt(text) + if (!isNaN(val)) { + MySettings.maxLength = val + focus = false + } else { + text = MySettings.maxLength + } + } + Accessible.role: Accessible.EditableText + Accessible.name: maxLengthLabel.text + Accessible.description: ToolTip.text + } + + Label { + id: batchSizeLabel + text: qsTr("Prompt Batch Size:") + color: theme.textColor + Layout.row: 4 + Layout.column: 0 + } + MyTextField { + text: MySettings.promptBatchSize + color: theme.textColor + ToolTip.text: qsTr("Amount of prompt tokens to process at once.\nNOTE: Higher values can speed up reading prompts but will use more RAM") + ToolTip.visible: hovered + Layout.row: 4 + Layout.column: 1 + validator: IntValidator { + bottom: 1 + } + onEditingFinished: { + var val = parseInt(text) + if (!isNaN(val)) { + MySettings.promptBatchSize = val + focus = false + } else { + text = MySettings.promptBatchSize + } + } + Accessible.role: Accessible.EditableText + Accessible.name: batchSizeLabel.text + Accessible.description: ToolTip.text + } + Label { + id: repeatPenaltyLabel + text: qsTr("Repeat Penalty:") + color: theme.textColor + Layout.row: 5 + Layout.column: 0 + } + MyTextField { + text: MySettings.repeatPenalty + color: theme.textColor + ToolTip.text: qsTr("Amount to penalize repetitiveness of the output") + ToolTip.visible: hovered + Layout.row: 5 + Layout.column: 1 + validator: DoubleValidator { + locale: "C" + } + onEditingFinished: { + var val = parseFloat(text) + if (!isNaN(val)) { + MySettings.repeatPenalty = val + focus = false + } else { + text = MySettings.repeatPenalty + } + } + Accessible.role: Accessible.EditableText + Accessible.name: repeatPenaltyLabel.text + Accessible.description: ToolTip.text + } + Label { + id: repeatPenaltyTokensLabel + text: qsTr("Repeat Penalty Tokens:") + color: theme.textColor + Layout.row: 6 + Layout.column: 0 + } + MyTextField { + text: MySettings.repeatPenaltyTokens + color: theme.textColor + ToolTip.text: qsTr("How far back in output to apply repeat penalty") + ToolTip.visible: hovered + Layout.row: 6 + Layout.column: 1 + validator: IntValidator { + bottom: 1 + } + onEditingFinished: { + var val = parseInt(text) + if (!isNaN(val)) { + MySettings.repeatPenaltyTokens = val + focus = false + } else { + text = MySettings.repeatPenaltyTokens + } + } + Accessible.role: Accessible.EditableText + Accessible.name: repeatPenaltyTokensLabel.text + Accessible.description: ToolTip.text + } + + ColumnLayout { + Layout.row: 7 + Layout.column: 0 + Layout.topMargin: 10 + Layout.alignment: Qt.AlignTop + spacing: 20 + + Label { + id: promptTemplateLabel + text: qsTr("Prompt Template:") + color: theme.textColor + } + + Label { + id: promptTemplateLabelHelp + Layout.maximumWidth: promptTemplateLabel.width + visible: templateTextArea.text.indexOf( + "%1") === -1 + color: theme.textErrorColor + text: qsTr("Must contain the string \"%1\" to be replaced with the user's input.") + wrapMode: TextArea.Wrap + Accessible.role: Accessible.EditableText + Accessible.name: text + } + } + + Rectangle { + Layout.row: 7 + Layout.column: 1 + Layout.fillWidth: true + height: 200 + color: "transparent" + clip: true + ScrollView { + id: templateScrollView + anchors.fill: parent + TextArea { + id: templateTextArea + text: MySettings.promptTemplate + color: theme.textColor + background: Rectangle { + implicitWidth: 150 + color: theme.backgroundLighter + radius: 10 + } + padding: 10 + wrapMode: TextArea.Wrap + onTextChanged: { + if (templateTextArea.text.indexOf("%1") !== -1) { + MySettings.promptTemplate = text + } + } + bottomPadding: 10 + Accessible.role: Accessible.EditableText + Accessible.name: promptTemplateLabel.text + Accessible.description: promptTemplateLabelHelp.text + ToolTip.text: qsTr("The prompt template partially determines how models will respond to prompts.\nNOTE: A longer, detailed template can lead to higher quality answers, but can also slow down generation.") + ToolTip.visible: hovered + } + } + } + MyButton { + Layout.row: 8 + Layout.column: 1 + Layout.fillWidth: true + text: qsTr("Restore Defaults") + Accessible.description: qsTr("Restores the settings dialog to a default state") + onClicked: { + MySettings.restoreGenerationDefaults(); + templateTextArea.text = MySettings.promptTemplate + } + } + } +} \ No newline at end of file diff --git a/gpt4all-chat/qml/LocalDocs.qml b/gpt4all-chat/qml/LocalDocs.qml deleted file mode 100644 index be9fa9d7..00000000 --- a/gpt4all-chat/qml/LocalDocs.qml +++ /dev/null @@ -1,294 +0,0 @@ -import QtCore -import QtQuick -import QtQuick.Controls -import QtQuick.Controls.Basic -import QtQuick.Layouts -import QtQuick.Dialogs -import localdocs -import mysettings - -Item { - id: root - - property alias collection: collection.text - property alias folder_path: folderEdit.text - - FolderDialog { - id: folderDialog - title: "Please choose a directory" - currentFolder: StandardPaths.writableLocation(StandardPaths.HomeLocation) - onAccepted: { - root.folder_path = selectedFolder - } - } - - Rectangle { - id: addCollection - anchors.left: parent.left - anchors.right: parent.right - height: collection.height + 20 - color: theme.backgroundDark - - RowLayout { - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.right: parent.right - spacing: 10 - MyTextField { - id: collection - width: 225 - horizontalAlignment: Text.AlignJustify - color: theme.textColor - placeholderText: qsTr("Collection name...") - placeholderTextColor: theme.mutedTextColor - ToolTip.text: qsTr("Name of the collection to add (Required)") - ToolTip.visible: hovered - Accessible.role: Accessible.EditableText - Accessible.name: collection.text - Accessible.description: ToolTip.text - function showError() { - collection.placeholderTextColor = theme.textErrorColor - } - onTextChanged: { - collection.placeholderTextColor = theme.mutedTextColor - } - } - - MyDirectoryField { - id: folderEdit - Layout.fillWidth: true - text: root.folder_path - placeholderText: qsTr("Folder path...") - placeholderTextColor: theme.mutedTextColor - ToolTip.text: qsTr("Folder path to documents (Required)") - ToolTip.visible: hovered - function showError() { - folderEdit.placeholderTextColor = theme.textErrorColor - } - onTextChanged: { - folderEdit.placeholderTextColor = theme.mutedTextColor - } - } - - MyButton { - id: browseButton - text: qsTr("Browse") - onClicked: { - folderDialog.open(); - } - } - - MyButton { - id: addButton - text: qsTr("Add") - Accessible.role: Accessible.Button - Accessible.name: text - Accessible.description: qsTr("Add button") - onClicked: { - var isError = false; - if (root.collection === "") { - isError = true; - collection.showError(); - } - if (root.folder_path === "" || !folderEdit.isValid) { - isError = true; - folderEdit.showError(); - } - if (isError) - return; - LocalDocs.addFolder(root.collection, root.folder_path) - root.collection = "" - root.folder_path = "" - collection.clear() - } - } - } - } - - ScrollView { - id: scrollView - anchors.top: addCollection.bottom - anchors.bottom: gridLayout.top - anchors.bottomMargin: 20 - anchors.left: parent.left - anchors.right: parent.right - clip: true - contentHeight: 300 - ScrollBar.vertical.policy: ScrollBar.AlwaysOn - - background: Rectangle { - color: theme.backgroundLighter - } - - ListView { - id: listView - model: LocalDocs.localDocsModel - boundsBehavior: Flickable.StopAtBounds - delegate: Rectangle { - id: item - width: listView.width - height: buttons.height + 20 - color: index % 2 === 0 ? theme.backgroundLight : theme.backgroundLighter - property bool removing: false - - Text { - id: collectionId - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.margins: 20 - text: collection - elide: Text.ElideRight - color: theme.textColor - width: 200 - } - - Text { - id: folderId - anchors.left: collectionId.right - anchors.margins: 20 - anchors.verticalCenter: parent.verticalCenter - text: folder_path - elide: Text.ElideRight - color: theme.textColor - } - - Item { - id: buttons - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.margins: 20 - width: Math.max(removeButton.width, busyIndicator.width) - height: Math.max(removeButton.height, busyIndicator.height) - MyButton { - id: removeButton - anchors.centerIn: parent - text: qsTr("Remove") - visible: !item.removing && installed - onClicked: { - item.removing = true - LocalDocs.removeFolder(collection, folder_path) - } - } - MyBusyIndicator { - id: busyIndicator - anchors.centerIn: parent - visible: item.removing || !installed - } - } - } - } - } - - GridLayout { - id: gridLayout - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - columns: 3 - rowSpacing: 10 - columnSpacing: 10 - - Rectangle { - Layout.row: 0 - Layout.column: 0 - Layout.fillWidth: true - Layout.columnSpan: 3 - height: 1 - color: theme.dialogBorder - } - - Rectangle { - Layout.row: 3 - Layout.column: 0 - Layout.fillWidth: true - Layout.columnSpan: 3 - height: 1 - color: theme.dialogBorder - } - - Label { - id: chunkLabel - Layout.row: 1 - Layout.column: 0 - color: theme.textColor - text: qsTr("Document snippet size (characters):") - } - - MyTextField { - id: chunkSizeTextField - Layout.row: 1 - Layout.column: 1 - ToolTip.text: qsTr("Number of characters per document snippet.\nNOTE: larger numbers increase likelihood of factual responses, but also result in slower generation.") - ToolTip.visible: hovered - text: MySettings.localDocsChunkSize - validator: IntValidator { - bottom: 1 - } - onEditingFinished: { - var val = parseInt(text) - if (!isNaN(val)) { - MySettings.localDocsChunkSize = val - focus = false - } else { - text = MySettings.localDocsChunkSize - } - } - } - - Label { - id: contextItemsPerPrompt - Layout.row: 2 - Layout.column: 0 - color: theme.textColor - text: qsTr("Document snippets per prompt:") - } - - MyTextField { - Layout.row: 2 - Layout.column: 1 - ToolTip.text: qsTr("Best N matches of retrieved document snippets to add to the context for prompt.\nNOTE: larger numbers increase likelihood of factual responses, but also result in slower generation.") - ToolTip.visible: hovered - text: MySettings.localDocsRetrievalSize - validator: IntValidator { - bottom: 1 - } - onEditingFinished: { - var val = parseInt(text) - if (!isNaN(val)) { - MySettings.localDocsRetrievalSize = val - focus = false - } else { - text = MySettings.localDocsRetrievalSize - } - } - } - - Label { - id: warningLabel - Layout.row: 1 - Layout.column: 2 - Layout.rowSpan: 2 - Layout.maximumWidth: 520 - Layout.alignment: Qt.AlignTop - color: theme.textErrorColor - wrapMode: Text.WordWrap - text: qsTr("Warning: Advanced usage only. Values too large may cause localdocs failure, extremely slow responses or failure to respond at all. Roughly speaking, the {N chars x N snippets} are added to the model's context window. More info here.") - onLinkActivated: function(link) { Qt.openUrlExternally(link) } - } - - MyButton { - id: restoreDefaultsButton - Layout.row: 4 - Layout.column: 1 - Layout.columnSpan: 2 - Layout.fillWidth: true - text: qsTr("Restore Defaults") - Accessible.role: Accessible.Button - Accessible.name: text - Accessible.description: qsTr("Restores the settings dialog to a default state") - onClicked: { - MySettings.restoreLocalDocsDefaults(); - } - } - } -} diff --git a/gpt4all-chat/qml/LocalDocsSettings.qml b/gpt4all-chat/qml/LocalDocsSettings.qml new file mode 100644 index 00000000..ae3c6857 --- /dev/null +++ b/gpt4all-chat/qml/LocalDocsSettings.qml @@ -0,0 +1,293 @@ +import QtCore +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.Basic +import QtQuick.Layouts +import QtQuick.Dialogs +import localdocs +import mysettings + +MySettingsTab { + title: qsTr("LocalDocs Plugin (BETA)") + contentItem: ColumnLayout { + id: root + spacing: 10 + + property alias collection: collection.text + property alias folder_path: folderEdit.text + + FolderDialog { + id: folderDialog + title: "Please choose a directory" + currentFolder: StandardPaths.writableLocation(StandardPaths.HomeLocation) + onAccepted: { + root.folder_path = selectedFolder + } + } + + RowLayout { + Layout.fillWidth: true + height: collection.height + 20 + spacing: 10 + MyTextField { + id: collection + width: 225 + horizontalAlignment: Text.AlignJustify + color: theme.textColor + placeholderText: qsTr("Collection name...") + placeholderTextColor: theme.mutedTextColor + ToolTip.text: qsTr("Name of the collection to add (Required)") + ToolTip.visible: hovered + Accessible.role: Accessible.EditableText + Accessible.name: collection.text + Accessible.description: ToolTip.text + function showError() { + collection.placeholderTextColor = theme.textErrorColor + } + onTextChanged: { + collection.placeholderTextColor = theme.mutedTextColor + } + } + + MyDirectoryField { + id: folderEdit + Layout.fillWidth: true + text: root.folder_path + placeholderText: qsTr("Folder path...") + placeholderTextColor: theme.mutedTextColor + ToolTip.text: qsTr("Folder path to documents (Required)") + ToolTip.visible: hovered + function showError() { + folderEdit.placeholderTextColor = theme.textErrorColor + } + onTextChanged: { + folderEdit.placeholderTextColor = theme.mutedTextColor + } + } + + MyButton { + id: browseButton + text: qsTr("Browse") + onClicked: { + folderDialog.open(); + } + } + + MyButton { + id: addButton + text: qsTr("Add") + Accessible.role: Accessible.Button + Accessible.name: text + Accessible.description: qsTr("Add button") + onClicked: { + var isError = false; + if (root.collection === "") { + isError = true; + collection.showError(); + } + if (root.folder_path === "" || !folderEdit.isValid) { + isError = true; + folderEdit.showError(); + } + if (isError) + return; + LocalDocs.addFolder(root.collection, root.folder_path) + root.collection = "" + root.folder_path = "" + collection.clear() + } + } + } + + ScrollView { + id: scrollView + Layout.fillWidth: true + Layout.bottomMargin: 20 + clip: true + contentHeight: 300 + ScrollBar.vertical.policy: ScrollBar.AlwaysOn + + background: Rectangle { + color: theme.backgroundLighter + } + + ListView { + id: listView + model: LocalDocs.localDocsModel + boundsBehavior: Flickable.StopAtBounds + delegate: Rectangle { + id: item + width: listView.width + height: buttons.height + 20 + color: index % 2 === 0 ? theme.backgroundLight : theme.backgroundLighter + property bool removing: false + + Text { + id: collectionId + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.margins: 20 + text: collection + elide: Text.ElideRight + color: theme.textColor + width: 200 + } + + Text { + id: folderId + anchors.left: collectionId.right + anchors.margins: 20 + anchors.verticalCenter: parent.verticalCenter + text: folder_path + elide: Text.ElideRight + color: theme.textColor + } + + Item { + id: buttons + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.margins: 20 + width: Math.max(removeButton.width, busyIndicator.width) + height: Math.max(removeButton.height, busyIndicator.height) + MyButton { + id: removeButton + anchors.centerIn: parent + text: qsTr("Remove") + visible: !item.removing && installed + onClicked: { + item.removing = true + LocalDocs.removeFolder(collection, folder_path) + } + } + MyBusyIndicator { + id: busyIndicator + anchors.centerIn: parent + visible: item.removing || !installed + } + } + } + } + } + + GridLayout { + id: gridLayout + Layout.fillWidth: true + columns: 3 + rowSpacing: 10 + columnSpacing: 10 + + Rectangle { + Layout.row: 0 + Layout.column: 0 + Layout.fillWidth: true + Layout.columnSpan: 3 + height: 1 + color: theme.dialogBorder + } + + Rectangle { + Layout.row: 3 + Layout.column: 0 + Layout.fillWidth: true + Layout.columnSpan: 3 + height: 1 + color: theme.dialogBorder + } + + // This is here just to stretch out the third column + Rectangle { + Layout.row: 3 + Layout.column: 2 + Layout.fillWidth: true + height: 1 + color: theme.dialogBorder + } + + Label { + id: chunkLabel + Layout.row: 1 + Layout.column: 0 + color: theme.textColor + text: qsTr("Document snippet size (characters):") + } + + MyTextField { + id: chunkSizeTextField + Layout.row: 1 + Layout.column: 1 + ToolTip.text: qsTr("Number of characters per document snippet.\nNOTE: larger numbers increase likelihood of factual responses, but also result in slower generation.") + ToolTip.visible: hovered + text: MySettings.localDocsChunkSize + validator: IntValidator { + bottom: 1 + } + onEditingFinished: { + var val = parseInt(text) + if (!isNaN(val)) { + MySettings.localDocsChunkSize = val + focus = false + } else { + text = MySettings.localDocsChunkSize + } + } + } + + Label { + id: contextItemsPerPrompt + Layout.row: 2 + Layout.column: 0 + color: theme.textColor + text: qsTr("Document snippets per prompt:") + } + + MyTextField { + Layout.row: 2 + Layout.column: 1 + ToolTip.text: qsTr("Best N matches of retrieved document snippets to add to the context for prompt.\nNOTE: larger numbers increase likelihood of factual responses, but also result in slower generation.") + ToolTip.visible: hovered + text: MySettings.localDocsRetrievalSize + validator: IntValidator { + bottom: 1 + } + onEditingFinished: { + var val = parseInt(text) + if (!isNaN(val)) { + MySettings.localDocsRetrievalSize = val + focus = false + } else { + text = MySettings.localDocsRetrievalSize + } + } + } + + Label { + id: warningLabel + Layout.row: 1 + Layout.column: 2 + Layout.rowSpan: 2 + Layout.maximumWidth: 520 + Layout.alignment: Qt.AlignTop + color: theme.textErrorColor + wrapMode: Text.WordWrap + text: qsTr("Warning: Advanced usage only. Values too large may cause localdocs failure, extremely slow responses or failure to respond at all. Roughly speaking, the {N chars x N snippets} are added to the model's context window. More info here.") + onLinkActivated: function(link) { Qt.openUrlExternally(link) } + } + + MyButton { + id: restoreDefaultsButton + Layout.row: 4 + Layout.column: 1 + Layout.columnSpan: 2 + Layout.fillWidth: true + text: qsTr("Restore Defaults") + Accessible.role: Accessible.Button + Accessible.name: text + Accessible.description: qsTr("Restores the settings dialog to a default state") + onClicked: { + MySettings.restoreLocalDocsDefaults(); + } + } + } + } +} diff --git a/gpt4all-chat/qml/MySettingsTab.qml b/gpt4all-chat/qml/MySettingsTab.qml new file mode 100644 index 00000000..cdcaeb6b --- /dev/null +++ b/gpt4all-chat/qml/MySettingsTab.qml @@ -0,0 +1,46 @@ +import QtCore +import QtQuick +import QtQuick.Controls +import QtQuick.Controls.Basic +import QtQuick.Layouts + +Item { + property string title: "" + property Item contentItem: null + + onContentItemChanged: function() { + if (contentItem) { + contentItem.parent = tabInner; + contentItem.anchors.left = tabInner.left; + contentItem.anchors.right = tabInner.right; + } + } + + ScrollView { + id: root + width: parent.width + height: parent.height + padding: 15 + rightPadding: 20 + contentWidth: availableWidth + contentHeight: tabInner.height + ScrollBar.vertical.policy: ScrollBar.AlwaysOn + + Theme { + id: theme + } + + background: Rectangle { + color: 'transparent' + border.color: theme.tabBorder + border.width: 1 + radius: 2 + } + + Column { + id: tabInner + anchors.left: parent.left + anchors.right: parent.right + } + } +} diff --git a/gpt4all-chat/qml/SettingsDialog.qml b/gpt4all-chat/qml/SettingsDialog.qml index 06f16dcc..37113acf 100644 --- a/gpt4all-chat/qml/SettingsDialog.qml +++ b/gpt4all-chat/qml/SettingsDialog.qml @@ -5,7 +5,6 @@ import QtQuick.Controls.Basic import QtQuick.Dialogs import QtQuick.Layouts import Qt.labs.folderlistmodel -import chatlistmodel import download import modellist import network @@ -30,21 +29,10 @@ Dialog { Network.sendSettingsDialog(); } - property var currentChat: ChatListModel.currentChat - Theme { id: theme } - function restoreGenerationDefaults() { - MySettings.restoreGenerationDefaults(); - templateTextArea.text = MySettings.promptTemplate - } - - function restoreApplicationDefaults() { - MySettings.restoreApplicationDefaults(); - } - Item { Accessible.role: Accessible.Dialog Accessible.name: qsTr("Settings dialog") @@ -187,576 +175,18 @@ Dialog { StackLayout { anchors.top: settingsTabBar.bottom - anchors.topMargin: -1 - width: parent.width - height: availableHeight + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom currentIndex: settingsTabBar.currentIndex - Item { - id: generationSettingsTab - ScrollView { - background: Rectangle { - color: 'transparent' - border.color: theme.tabBorder - border.width: 1 - radius: 2 - } - padding: 10 - width: parent.width - height: parent.height - 30 - contentWidth: availableWidth - 20 - contentHeight: generationSettingsTabInner.implicitHeight + 40 - ScrollBar.vertical.policy: ScrollBar.AlwaysOn - - GridLayout { - id: generationSettingsTabInner - anchors.margins: 10 - columns: 2 - rowSpacing: 10 - columnSpacing: 10 - anchors.fill: parent - - Label { - id: tempLabel - text: qsTr("Temperature:") - color: theme.textColor - Layout.row: 0 - Layout.column: 0 - } - MyTextField { - text: MySettings.temperature - color: theme.textColor - ToolTip.text: qsTr("Temperature increases the chances of choosing less likely tokens.\nNOTE: Higher temperature gives more creative but less predictable outputs.") - ToolTip.visible: hovered - Layout.row: 0 - Layout.column: 1 - validator: DoubleValidator { - locale: "C" - } - onEditingFinished: { - var val = parseFloat(text) - if (!isNaN(val)) { - MySettings.temperature = val - focus = false - } else { - text = MySettings.temperature - } - } - Accessible.role: Accessible.EditableText - Accessible.name: tempLabel.text - Accessible.description: ToolTip.text - } - Label { - id: topPLabel - text: qsTr("Top P:") - color: theme.textColor - Layout.row: 1 - Layout.column: 0 - } - MyTextField { - text: MySettings.topP - color: theme.textColor - ToolTip.text: qsTr("Only the most likely tokens up to a total probability of top_p can be chosen.\nNOTE: Prevents choosing highly unlikely tokens, aka Nucleus Sampling") - ToolTip.visible: hovered - Layout.row: 1 - Layout.column: 1 - validator: DoubleValidator { - locale: "C" - } - onEditingFinished: { - var val = parseFloat(text) - if (!isNaN(val)) { - MySettings.topP = val - focus = false - } else { - text = MySettings.topP - } - } - Accessible.role: Accessible.EditableText - Accessible.name: topPLabel.text - Accessible.description: ToolTip.text - } - Label { - id: topKLabel - text: qsTr("Top K:") - color: theme.textColor - Layout.row: 2 - Layout.column: 0 - } - MyTextField { - text: MySettings.topK - color: theme.textColor - ToolTip.text: qsTr("Only the top K most likely tokens will be chosen from") - ToolTip.visible: hovered - Layout.row: 2 - Layout.column: 1 - validator: IntValidator { - bottom: 1 - } - onEditingFinished: { - var val = parseInt(text) - if (!isNaN(val)) { - MySettings.topK = val - focus = false - } else { - text = MySettings.topK - } - } - Accessible.role: Accessible.EditableText - Accessible.name: topKLabel.text - Accessible.description: ToolTip.text - } - Label { - id: maxLengthLabel - text: qsTr("Max Length:") - color: theme.textColor - Layout.row: 3 - Layout.column: 0 - } - MyTextField { - text: MySettings.maxLength - color: theme.textColor - ToolTip.text: qsTr("Maximum length of response in tokens") - ToolTip.visible: hovered - Layout.row: 3 - Layout.column: 1 - validator: IntValidator { - bottom: 1 - } - onEditingFinished: { - var val = parseInt(text) - if (!isNaN(val)) { - MySettings.maxLength = val - focus = false - } else { - text = MySettings.maxLength - } - } - Accessible.role: Accessible.EditableText - Accessible.name: maxLengthLabel.text - Accessible.description: ToolTip.text - } - - Label { - id: batchSizeLabel - text: qsTr("Prompt Batch Size:") - color: theme.textColor - Layout.row: 4 - Layout.column: 0 - } - MyTextField { - text: MySettings.promptBatchSize - color: theme.textColor - ToolTip.text: qsTr("Amount of prompt tokens to process at once.\nNOTE: Higher values can speed up reading prompts but will use more RAM") - ToolTip.visible: hovered - Layout.row: 4 - Layout.column: 1 - validator: IntValidator { - bottom: 1 - } - onEditingFinished: { - var val = parseInt(text) - if (!isNaN(val)) { - MySettings.promptBatchSize = val - focus = false - } else { - text = MySettings.promptBatchSize - } - } - Accessible.role: Accessible.EditableText - Accessible.name: batchSizeLabel.text - Accessible.description: ToolTip.text - } - Label { - id: repeatPenaltyLabel - text: qsTr("Repeat Penalty:") - color: theme.textColor - Layout.row: 5 - Layout.column: 0 - } - MyTextField { - text: MySettings.repeatPenalty - color: theme.textColor - ToolTip.text: qsTr("Amount to penalize repetitiveness of the output") - ToolTip.visible: hovered - Layout.row: 5 - Layout.column: 1 - validator: DoubleValidator { - locale: "C" - } - onEditingFinished: { - var val = parseFloat(text) - if (!isNaN(val)) { - MySettings.repeatPenalty = val - focus = false - } else { - text = MySettings.repeatPenalty - } - } - Accessible.role: Accessible.EditableText - Accessible.name: repeatPenaltyLabel.text - Accessible.description: ToolTip.text - } - Label { - id: repeatPenaltyTokensLabel - text: qsTr("Repeat Penalty Tokens:") - color: theme.textColor - Layout.row: 6 - Layout.column: 0 - } - MyTextField { - text: MySettings.repeatPenaltyTokens - color: theme.textColor - ToolTip.text: qsTr("How far back in output to apply repeat penalty") - ToolTip.visible: hovered - Layout.row: 6 - Layout.column: 1 - validator: IntValidator { - bottom: 1 - } - onEditingFinished: { - var val = parseInt(text) - if (!isNaN(val)) { - MySettings.repeatPenaltyTokens = val - focus = false - } else { - text = MySettings.repeatPenaltyTokens - } - } - Accessible.role: Accessible.EditableText - Accessible.name: repeatPenaltyTokensLabel.text - Accessible.description: ToolTip.text - } - - ColumnLayout { - Layout.row: 7 - Layout.column: 0 - Layout.topMargin: 10 - Layout.alignment: Qt.AlignTop - spacing: 20 - - Label { - id: promptTemplateLabel - text: qsTr("Prompt Template:") - color: theme.textColor - } - - Label { - id: promptTemplateLabelHelp - Layout.maximumWidth: promptTemplateLabel.width - visible: templateTextArea.text.indexOf( - "%1") === -1 - color: theme.textErrorColor - text: qsTr("Must contain the string \"%1\" to be replaced with the user's input.") - wrapMode: TextArea.Wrap - Accessible.role: Accessible.EditableText - Accessible.name: text - } - } - - Rectangle { - Layout.row: 7 - Layout.column: 1 - Layout.fillWidth: true - height: 200 - color: "transparent" - clip: true - ScrollView { - id: templateScrollView - anchors.fill: parent - TextArea { - id: templateTextArea - text: MySettings.promptTemplate - color: theme.textColor - background: Rectangle { - implicitWidth: 150 - color: theme.backgroundLighter - radius: 10 - } - padding: 10 - wrapMode: TextArea.Wrap - onTextChanged: { - if (templateTextArea.text.indexOf("%1") !== -1) { - MySettings.promptTemplate = text - } - } - bottomPadding: 10 - Accessible.role: Accessible.EditableText - Accessible.name: promptTemplateLabel.text - Accessible.description: promptTemplateLabelHelp.text - ToolTip.text: qsTr("The prompt template partially determines how models will respond to prompts.\nNOTE: A longer, detailed template can lead to higher quality answers, but can also slow down generation.") - ToolTip.visible: hovered - } - } - } - MyButton { - Layout.row: 8 - Layout.column: 1 - Layout.fillWidth: true - text: qsTr("Restore Defaults") - Accessible.description: qsTr("Restores the settings dialog to a default state") - onClicked: { - settingsDialog.restoreGenerationDefaults() - } - } - } - } + GenerationSettings { } - Item { - id: applicationSettingsTab - ScrollView { - background: Rectangle { - color: 'transparent' - border.color: theme.tabBorder - border.width: 1 - radius: 2 - } - padding: 10 - width: parent.width - height: parent.height - 30 - contentWidth: availableWidth - 20 - ScrollBar.vertical.policy: ScrollBar.AlwaysOn - GridLayout { - anchors.margins: 10 - columns: 3 - rowSpacing: 10 - columnSpacing: 10 - anchors.fill: parent - Label { - id: defaultModelLabel - text: qsTr("Default model:") - color: theme.textColor - Layout.row: 1 - Layout.column: 0 - } - MyComboBox { - id: comboBox - Layout.row: 1 - Layout.column: 1 - Layout.minimumWidth: 350 - model: ModelList.userDefaultModelList - Accessible.role: Accessible.ComboBox - Accessible.name: qsTr("ComboBox for displaying/picking the default model") - Accessible.description: qsTr("Use this for picking the default model to use; the first item is the current default model") - function updateModel() { - comboBox.currentIndex = comboBox.indexOfValue(MySettings.userDefaultModel); - } - Component.onCompleted: { - comboBox.updateModel() - } - Connections { - target: MySettings - function onUserDefaultModelChanged() { - comboBox.updateModel() - } - } - onActivated: { - MySettings.userDefaultModel = comboBox.currentText - } - } - FolderDialog { - id: modelPathDialog - title: "Please choose a directory" - currentFolder: "file://" + MySettings.modelPath - onAccepted: { - MySettings.modelPath = selectedFolder - } - } - Label { - id: modelPathLabel - text: qsTr("Download path:") - color: theme.textColor - Layout.row: 2 - Layout.column: 0 - } - MyDirectoryField { - id: modelPathDisplayField - text: MySettings.modelPath - implicitWidth: 300 - Layout.row: 2 - Layout.column: 1 - Layout.fillWidth: true - ToolTip.text: qsTr("Path where model files will be downloaded to") - ToolTip.visible: hovered - Accessible.role: Accessible.ToolTip - Accessible.name: modelPathDisplayField.text - Accessible.description: ToolTip.text - onEditingFinished: { - if (isValid) { - MySettings.modelPath = modelPathDisplayField.text - } else { - text = MySettings.modelPath - } - } - } - MyButton { - Layout.row: 2 - Layout.column: 2 - text: qsTr("Browse") - Accessible.description: qsTr("Opens a folder picker dialog to choose where to save model files") - onClicked: modelPathDialog.open() - } - Label { - id: nThreadsLabel - text: qsTr("CPU Threads:") - color: theme.textColor - Layout.row: 3 - Layout.column: 0 - } - MyTextField { - text: MySettings.threadCount - color: theme.textColor - ToolTip.text: qsTr("Amount of processing threads to use, a setting of 0 will use the lesser of 4 or your number of CPU threads") - ToolTip.visible: hovered - Layout.row: 3 - Layout.column: 1 - validator: IntValidator { - bottom: 1 - } - onEditingFinished: { - var val = parseInt(text) - if (!isNaN(val)) { - MySettings.threadCount = val - focus = false - } else { - text = MySettings.threadCount - } - } - Accessible.role: Accessible.EditableText - Accessible.name: nThreadsLabel.text - Accessible.description: ToolTip.text - } - Label { - id: saveChatsLabel - text: qsTr("Save chats to disk:") - color: theme.textColor - Layout.row: 4 - Layout.column: 0 - } - MyCheckBox { - id: saveChatsBox - Layout.row: 4 - Layout.column: 1 - checked: MySettings.saveChats - onClicked: { - Network.sendSaveChatsToggled(saveChatsBox.checked); - MySettings.saveChats = !MySettings.saveChats - } - ToolTip.text: qsTr("WARNING: Saving chats to disk can be ~2GB per chat") - ToolTip.visible: hovered - } - Label { - id: saveChatGPTChatsLabel - text: qsTr("Save ChatGPT chats to disk:") - color: theme.textColor - Layout.row: 5 - Layout.column: 0 - } - MyCheckBox { - id: saveChatGPTChatsBox - Layout.row: 5 - Layout.column: 1 - checked: MySettings.saveChatGPTChats - onClicked: { - MySettings.saveChatGPTChats = !MySettings.saveChatGPTChats - } - } - Label { - id: serverChatLabel - text: qsTr("Enable API server:") - color: theme.textColor - Layout.row: 6 - Layout.column: 0 - } - MyCheckBox { - id: serverChatBox - Layout.row: 6 - Layout.column: 1 - checked: MySettings.serverChat - onClicked: { - MySettings.serverChat = !MySettings.serverChat - } - ToolTip.text: qsTr("WARNING: This enables the gui to act as a local REST web server(OpenAI API compliant) for API requests and will increase your RAM usage as well") - ToolTip.visible: hovered - } - Rectangle { - Layout.row: 7 - Layout.column: 0 - Layout.columnSpan: 3 - Layout.fillWidth: true - height: 1 - color: theme.dialogBorder - } - Rectangle { - Layout.row: 9 - Layout.column: 0 - Layout.fillWidth: true - Layout.columnSpan: 3 - height: 1 - color: theme.dialogBorder - } - Label { - id: gpuOverrideLabel - text: qsTr("Force Metal (macOS+arm):") - color: theme.textColor - Layout.row: 8 - Layout.column: 0 - } - RowLayout { - Layout.row: 8 - Layout.column: 1 - Layout.columnSpan: 2 - MyCheckBox { - id: gpuOverrideBox - checked: MySettings.forceMetal - onClicked: { - MySettings.forceMetal = !MySettings.forceMetal - } - } - Label { - id: warningLabel - Layout.maximumWidth: 730 - Layout.alignment: Qt.AlignTop - color: theme.textErrorColor - wrapMode: Text.WordWrap - text: qsTr("WARNING: On macOS with arm (M1+) this setting forces usage of the GPU. Can cause crashes if the model requires more RAM than the system supports. Because of crash possibility the setting will not persist across restarts of the application. This has no effect on non-macs or intel.") - } - } - MyButton { - Layout.row: 10 - Layout.column: 1 - Layout.columnSpan: 2 - Layout.fillWidth: true - text: qsTr("Restore Defaults") - Accessible.description: qsTr("Restores the settings dialog to a default state") - onClicked: { - settingsDialog.restoreApplicationDefaults() - } - } - } - } + ApplicationSettings { } - Item { - id: localDocsTab - ScrollView { - background: Rectangle { - color: 'transparent' - border.color: theme.tabBorder - border.width: 1 - radius: 2 - } - padding: 10 - width: parent.width - height: parent.height - 30 - contentWidth: availableWidth - 20 - ScrollBar.vertical.policy: ScrollBar.AlwaysOn - LocalDocs { - anchors.margins: 10 - anchors.fill: parent - } - } + LocalDocsSettings { } } }