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:
AT
2024-06-24 18:49:23 -04:00
committed by GitHub
parent 1272b694ae
commit 9273b49b62
111 changed files with 8540 additions and 7879 deletions

View File

@@ -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
}
}
}