mirror of
https://github.com/nomic-ai/gpt4all.git
synced 2025-09-05 02:20:28 +00:00
chat: major UI redesign for v3.0.0 (#2396)
Signed-off-by: Adam Treat <treat.adam@gmail.com> Signed-off-by: Jared Van Bortel <jared@nomic.ai> Co-authored-by: Jared Van Bortel <jared@nomic.ai>
This commit is contained in:
@@ -14,187 +14,155 @@ MySettingsTab {
|
||||
MySettings.restoreLocalDocsDefaults();
|
||||
}
|
||||
|
||||
property bool hasEmbeddingModel: ModelList.installedEmbeddingModels.count !== 0
|
||||
showAdvancedSettingsButton: hasEmbeddingModel
|
||||
showRestoreDefaultsButton: hasEmbeddingModel
|
||||
showRestoreDefaultsButton: true
|
||||
|
||||
title: qsTr("LocalDocs")
|
||||
contentItem: ColumnLayout {
|
||||
id: root
|
||||
spacing: 10
|
||||
|
||||
property alias collection: collection.text
|
||||
property alias folder_path: folderEdit.text
|
||||
|
||||
MySettingsLabel {
|
||||
id: downloadLabel
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
visible: !hasEmbeddingModel
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
text: qsTr("This feature requires the download of a text embedding model in order to index documents for later search. Please download the <b>SBert</a> text embedding model from the download dialog to proceed.")
|
||||
Label {
|
||||
color: theme.styledTextColor
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
font.bold: true
|
||||
text: "Indexing"
|
||||
}
|
||||
|
||||
MySettingsButton {
|
||||
visible: !hasEmbeddingModel
|
||||
Layout.topMargin: 20
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
text: qsTr("Download")
|
||||
font.pixelSize: theme.fontSizeLarger
|
||||
onClicked: {
|
||||
downloadClicked()
|
||||
Rectangle {
|
||||
Layout.bottomMargin: 15
|
||||
Layout.fillWidth: true
|
||||
height: 2
|
||||
color: theme.settingsDivider
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
MySettingsLabel {
|
||||
id: extsLabel
|
||||
text: qsTr("Allowed File Extensions")
|
||||
helpText: qsTr("Comma-separated list. LocalDocs will only attempt to process files with these extensions.")
|
||||
}
|
||||
MyTextField {
|
||||
id: extsField
|
||||
text: MySettings.localDocsFileExtensions.join(',')
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.minimumWidth: 200
|
||||
validator: RegularExpressionValidator {
|
||||
regularExpression: /([^ ,\/"']+,?)*/
|
||||
}
|
||||
onEditingFinished: {
|
||||
// split and remove empty elements
|
||||
var exts = text.split(',').filter(e => e);
|
||||
// normalize and deduplicate
|
||||
exts = exts.map(e => e.toLowerCase());
|
||||
exts = Array.from(new Set(exts));
|
||||
/* Blacklist common unsupported file extensions. We only support plain text and PDFs, and although we
|
||||
* reject binary data, we don't want to waste time trying to index files that we don't support. */
|
||||
exts = exts.filter(e => ![
|
||||
/* Microsoft documents */ "rtf", "docx", "ppt", "pptx", "xls", "xlsx",
|
||||
/* OpenOffice */ "odt", "ods", "odp", "odg",
|
||||
/* photos */ "jpg", "jpeg", "png", "gif", "bmp", "tif", "tiff", "webp",
|
||||
/* audio */ "mp3", "wma", "m4a", "wav", "flac",
|
||||
/* videos */ "mp4", "mov", "webm", "mkv", "avi", "flv", "wmv",
|
||||
/* executables */ "exe", "com", "dll", "so", "dylib", "msi",
|
||||
/* binary images */ "iso", "img", "dmg",
|
||||
/* archives */ "zip", "jar", "apk", "rar", "7z", "tar", "gz", "xz", "bz2", "tar.gz",
|
||||
"tgz", "tar.xz", "tar.bz2",
|
||||
/* misc */ "bin",
|
||||
].includes(e));
|
||||
MySettings.localDocsFileExtensions = exts;
|
||||
extsField.text = exts.join(',');
|
||||
focus = false;
|
||||
}
|
||||
Accessible.role: Accessible.EditableText
|
||||
Accessible.name: extsLabel.text
|
||||
Accessible.description: extsLabel.helpText
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
visible: hasEmbeddingModel
|
||||
Layout.fillWidth: true
|
||||
height: row.height
|
||||
RowLayout {
|
||||
id: row
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: collection.height
|
||||
spacing: 10
|
||||
MyTextField {
|
||||
id: collection
|
||||
width: 225
|
||||
horizontalAlignment: Text.AlignJustify
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
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...")
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
MySettingsButton {
|
||||
id: browseButton
|
||||
text: qsTr("Browse")
|
||||
onClicked: {
|
||||
openFolderDialog(StandardPaths.writableLocation(StandardPaths.HomeLocation), function(selectedFolder) {
|
||||
root.folder_path = selectedFolder
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
MySettingsButton {
|
||||
id: addButton
|
||||
text: qsTr("Add")
|
||||
Accessible.role: Accessible.Button
|
||||
Accessible.name: text
|
||||
Accessible.description: qsTr("Add collection")
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
Label {
|
||||
Layout.topMargin: 15
|
||||
color: theme.grayRed900
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
font.bold: true
|
||||
text: "Embedding"
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
visible: hasEmbeddingModel
|
||||
spacing: 0
|
||||
Repeater {
|
||||
model: LocalDocs.localDocsModel
|
||||
delegate: Rectangle {
|
||||
id: item
|
||||
Layout.fillWidth: true
|
||||
height: buttons.height + 20
|
||||
color: index % 2 === 0 ? theme.darkContrast : theme.lightContrast
|
||||
property bool removing: false
|
||||
Rectangle {
|
||||
Layout.bottomMargin: 15
|
||||
Layout.fillWidth: true
|
||||
height: 2
|
||||
color: theme.grayRed500
|
||||
}
|
||||
|
||||
Text {
|
||||
id: collectionId
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.margins: 20
|
||||
text: collection
|
||||
elide: Text.ElideRight
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
width: 200
|
||||
}
|
||||
RowLayout {
|
||||
MySettingsLabel {
|
||||
text: qsTr("Use Nomic Embed API")
|
||||
helpText: qsTr("Embed documents using the fast Nomic API instead of a private local model.")
|
||||
}
|
||||
|
||||
Text {
|
||||
id: folderId
|
||||
anchors.left: collectionId.right
|
||||
anchors.right: buttons.left
|
||||
anchors.margins: 20
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: folder_path
|
||||
elide: Text.ElideRight
|
||||
color: theme.textColor
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
}
|
||||
|
||||
Item {
|
||||
id: buttons
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: 20
|
||||
width: removeButton.width
|
||||
height:removeButton.height
|
||||
MySettingsButton {
|
||||
id: removeButton
|
||||
anchors.centerIn: parent
|
||||
text: qsTr("Remove")
|
||||
visible: !item.removing
|
||||
onClicked: {
|
||||
item.removing = true
|
||||
LocalDocs.removeFolder(collection, folder_path)
|
||||
}
|
||||
}
|
||||
}
|
||||
MyCheckBox {
|
||||
id: useNomicAPIBox
|
||||
Component.onCompleted: {
|
||||
useNomicAPIBox.checked = MySettings.localDocsUseRemoteEmbed;
|
||||
}
|
||||
onClicked: {
|
||||
MySettings.localDocsUseRemoteEmbed = useNomicAPIBox.checked && MySettings.localDocsNomicAPIKey !== "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: hasEmbeddingModel
|
||||
MySettingsLabel {
|
||||
id: apiKeyLabel
|
||||
text: qsTr("Nomic API Key")
|
||||
helpText: qsTr("API key to use for Nomic Embed. Get one at https://atlas.nomic.ai/")
|
||||
}
|
||||
|
||||
MyTextField {
|
||||
id: apiKeyField
|
||||
text: MySettings.localDocsNomicAPIKey
|
||||
color: apiKeyField.acceptableInput ? theme.textColor : theme.textErrorColor
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.minimumWidth: 200
|
||||
enabled: useNomicAPIBox.checked
|
||||
validator: RegularExpressionValidator {
|
||||
// may be empty
|
||||
regularExpression: /|nk-[a-zA-Z0-9_-]{43}/
|
||||
}
|
||||
onEditingFinished: {
|
||||
MySettings.localDocsNomicAPIKey = apiKeyField.text;
|
||||
MySettings.localDocsUseRemoteEmbed = useNomicAPIBox.checked && MySettings.localDocsNomicAPIKey !== "";
|
||||
focus = false;
|
||||
}
|
||||
Accessible.role: Accessible.EditableText
|
||||
Accessible.name: apiKeyLabel.text
|
||||
Accessible.description: apiKeyLabel.helpText
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.topMargin: 15
|
||||
color: theme.grayRed900
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
font.bold: true
|
||||
text: "Display"
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.bottomMargin: 15
|
||||
Layout.fillWidth: true
|
||||
height: 2
|
||||
color: theme.grayRed500
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
MySettingsLabel {
|
||||
id: showReferencesLabel
|
||||
text: qsTr("Show references")
|
||||
text: qsTr("Show sources")
|
||||
helpText: qsTr("Shows sources in GUI generated by localdocs")
|
||||
}
|
||||
MyCheckBox {
|
||||
id: showReferencesBox
|
||||
@@ -202,104 +170,92 @@ MySettingsTab {
|
||||
onClicked: {
|
||||
MySettings.localDocsShowReferences = !MySettings.localDocsShowReferences
|
||||
}
|
||||
ToolTip.text: qsTr("Shows any references in GUI generated by localdocs")
|
||||
ToolTip.visible: hovered
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.topMargin: 15
|
||||
color: theme.styledTextColor
|
||||
font.pixelSize: theme.fontSizeLarge
|
||||
font.bold: true
|
||||
text: "Advanced"
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: hasEmbeddingModel
|
||||
Layout.bottomMargin: 15
|
||||
Layout.fillWidth: true
|
||||
height: 3
|
||||
color: theme.accentColor
|
||||
}
|
||||
}
|
||||
advancedSettings: GridLayout {
|
||||
id: gridLayout
|
||||
columns: 3
|
||||
rowSpacing: 10
|
||||
columnSpacing: 10
|
||||
visible: hasEmbeddingModel
|
||||
|
||||
Rectangle {
|
||||
Layout.row: 3
|
||||
Layout.column: 0
|
||||
Layout.fillWidth: true
|
||||
Layout.columnSpan: 3
|
||||
height: 3
|
||||
color: theme.accentColor
|
||||
height: 2
|
||||
color: theme.settingsDivider
|
||||
}
|
||||
|
||||
MySettingsLabel {
|
||||
id: chunkLabel
|
||||
Layout.row: 1
|
||||
Layout.column: 0
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MySettingsLabel {
|
||||
id: contextItemsPerPrompt
|
||||
Layout.row: 2
|
||||
Layout.column: 0
|
||||
text: qsTr("Max document snippets per prompt")
|
||||
}
|
||||
|
||||
MyTextField {
|
||||
Layout.row: 2
|
||||
Layout.column: 1
|
||||
ToolTip.text: qsTr("Max 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.row: 1
|
||||
Layout.column: 2
|
||||
Layout.rowSpan: 2
|
||||
id: warningLabel
|
||||
Layout.bottomMargin: 15
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.minimumHeight: warningLabel.height
|
||||
color: theme.textErrorColor
|
||||
wrapMode: Text.WordWrap
|
||||
text: qsTr("Warning: Advanced usage only.")
|
||||
helpText: qsTr("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 <a href=\"https://docs.gpt4all.io/gpt4all_chat.html#localdocs-beta-plugin-chat-with-your-data\">here.</a>")
|
||||
onLinkActivated: function(link) { Qt.openUrlExternally(link) }
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
MySettingsLabel {
|
||||
id: warningLabel
|
||||
width: parent.width
|
||||
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 <a href=\"https://docs.gpt4all.io/gpt4all_chat.html#localdocs-beta-plugin-chat-with-your-data\">here.</a>")
|
||||
onLinkActivated: function(link) { Qt.openUrlExternally(link) }
|
||||
id: chunkLabel
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Document snippet size (characters)")
|
||||
helpText: qsTr("Number of characters per document snippet. Larger numbers increase likelihood of factual responses, but also result in slower generation.")
|
||||
}
|
||||
|
||||
MyTextField {
|
||||
id: chunkSizeTextField
|
||||
text: MySettings.localDocsChunkSize
|
||||
validator: IntValidator {
|
||||
bottom: 1
|
||||
}
|
||||
onEditingFinished: {
|
||||
var val = parseInt(text)
|
||||
if (!isNaN(val)) {
|
||||
MySettings.localDocsChunkSize = val
|
||||
focus = false
|
||||
} else {
|
||||
text = MySettings.localDocsChunkSize
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.topMargin: 15
|
||||
MySettingsLabel {
|
||||
id: contextItemsPerPrompt
|
||||
text: qsTr("Max document snippets per prompt")
|
||||
helpText: qsTr("Max best N matches of retrieved document snippets to add to the context for prompt. Larger numbers increase likelihood of factual responses, but also result in slower generation.")
|
||||
|
||||
}
|
||||
|
||||
MyTextField {
|
||||
text: MySettings.localDocsRetrievalSize
|
||||
validator: IntValidator {
|
||||
bottom: 1
|
||||
}
|
||||
onEditingFinished: {
|
||||
var val = parseInt(text)
|
||||
if (!isNaN(val)) {
|
||||
MySettings.localDocsRetrievalSize = val
|
||||
focus = false
|
||||
} else {
|
||||
text = MySettings.localDocsRetrievalSize
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.topMargin: 15
|
||||
Layout.fillWidth: true
|
||||
height: 2
|
||||
color: theme.settingsDivider
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user