diff --git a/gpt4all-chat/CMakeLists.txt b/gpt4all-chat/CMakeLists.txt index 034a5101..8bd4d483 100644 --- a/gpt4all-chat/CMakeLists.txt +++ b/gpt4all-chat/CMakeLists.txt @@ -208,6 +208,7 @@ qt_add_qml_module(chat icons/thumbs_up.svg icons/thumbs_down.svg icons/twitter.svg + icons/up_down.svg icons/left_panel_closed.svg icons/left_panel_open.svg icons/gpt4all.svg diff --git a/gpt4all-chat/icons/eject.svg b/gpt4all-chat/icons/eject.svg index 9649c487..1c0e004e 100644 --- a/gpt4all-chat/icons/eject.svg +++ b/gpt4all-chat/icons/eject.svg @@ -1,6 +1 @@ - - - + \ No newline at end of file diff --git a/gpt4all-chat/icons/up_down.svg b/gpt4all-chat/icons/up_down.svg new file mode 100644 index 00000000..4e11ebad --- /dev/null +++ b/gpt4all-chat/icons/up_down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/gpt4all-chat/qml/ChatView.qml b/gpt4all-chat/qml/ChatView.qml index 5b12c838..dca00017 100644 --- a/gpt4all-chat/qml/ChatView.qml +++ b/gpt4all-chat/qml/ChatView.qml @@ -205,13 +205,12 @@ Rectangle { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - spacing: 20 + spacing: 0 Rectangle { Layout.alignment: Qt.AlignLeft Layout.leftMargin: 30 Layout.fillWidth: true - Layout.preferredWidth: 100 color: "transparent" Layout.preferredHeight: childrenRect.height MyToolButton { @@ -237,8 +236,15 @@ Rectangle { id: comboBox Layout.alignment: Qt.AlignHCenter Layout.fillHeight: true - Layout.preferredWidth: 350 - Layout.maximumWidth: 675 + Layout.preferredWidth: 550 + Layout.leftMargin: { + // This function works in tandem with the preferredWidth and the layout to + // provide the maximum size combobox we can have at the smallest window width + // we allow with the largest font size we allow. It is unfortunately based + // upon a magic number that was produced through trial and error for something + // I don't fully understand. + return -Math.max(0, comboBox.width / 2 + collectionsButton.width + 110 /*magic*/ - comboLayout.width / 2); + } enabled: !currentChat.isServer && !currentChat.trySwitchContextInProgress && !currentChat.isCurrentlyLoading @@ -266,8 +272,8 @@ Rectangle { ProgressBar { id: modelProgress anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right + anchors.horizontalCenter: parent.horizontalCenter + width: contentRow.width + 20 visible: currentChat.isCurrentlyLoading height: 10 value: currentChat.modelLoadingPercentage @@ -286,38 +292,110 @@ Rectangle { } } } - contentItem: Text { - anchors.horizontalCenter: parent.horizontalCenter - leftPadding: 10 - rightPadding: { - if (ejectButton.visible && reloadButton) - return 105; - if (reloadButton.visible) - return 65 - return 25 + + contentItem: Item { + RowLayout { + id: contentRow + anchors.centerIn: parent + spacing: 0 + RowLayout { + id: miniButtonsRow + clip: true + Behavior on Layout.preferredWidth { + NumberAnimation { + duration: 300 + easing.type: Easing.InOutQuad + } + } + + Layout.preferredWidth: { + if (!comboBox.hovered) + return 0 + return (reloadButton.visible ? reloadButton.width : 0) + (ejectButton.visible ? ejectButton.width : 0) + } + + MyMiniButton { + id: reloadButton + Layout.alignment: Qt.AlignCenter + visible: currentChat.modelLoadingError === "" + && !currentChat.trySwitchContextInProgress + && !currentChat.isCurrentlyLoading + && (currentChat.isModelLoaded || currentModelName() !== "") + source: "qrc:/gpt4all/icons/regenerate.svg" + backgroundColor: theme.textColor + backgroundColorHovered: theme.styledTextColor + onClicked: { + if (currentChat.isModelLoaded) + currentChat.forceReloadModel(); + else + currentChat.reloadModel(); + } + ToolTip.text: qsTr("Reload the currently loaded model") + ToolTip.visible: hovered + } + + MyMiniButton { + id: ejectButton + Layout.alignment: Qt.AlignCenter + visible: currentChat.isModelLoaded && !currentChat.isCurrentlyLoading + source: "qrc:/gpt4all/icons/eject.svg" + backgroundColor: theme.textColor + backgroundColorHovered: theme.styledTextColor + onClicked: { + currentChat.forceUnloadModel(); + } + ToolTip.text: qsTr("Eject the currently loaded model") + ToolTip.visible: hovered + } + } + + Text { + id: comboBoxText + leftPadding: 10 + rightPadding: 10 + text: { + if (ModelList.selectableModels.count === 0) + return qsTr("No model installed...") + if (currentChat.modelLoadingError !== "") + return qsTr("Model loading error...") + if (currentChat.trySwitchContextInProgress === 1) + return qsTr("Waiting for model...") + if (currentChat.trySwitchContextInProgress === 2) + return qsTr("Switching context...") + if (currentModelName() === "") + return qsTr("Choose a model...") + if (currentChat.modelLoadingPercentage === 0.0) + return qsTr("Reload \u00B7 ") + currentModelName() + if (currentChat.isCurrentlyLoading) + return qsTr("Loading \u00B7 ") + currentModelName() + return currentModelName() + } + font.pixelSize: theme.fontSizeLarger + color: theme.iconBackgroundLight + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideRight + } + Item { + Layout.minimumWidth: updown.width + Layout.minimumHeight: updown.height + Image { + id: updown + anchors.verticalCenter: parent.verticalCenter + sourceSize.width: comboBoxText.font.pixelSize + sourceSize.height: comboBoxText.font.pixelSize + mipmap: true + visible: false + source: "qrc:/gpt4all/icons/up_down.svg" + } + + ColorOverlay { + anchors.fill: updown + source: updown + color: comboBoxText.color + } + } } - text: { - if (ModelList.selectableModels.count === 0) - return qsTr("No model installed...") - if (currentChat.modelLoadingError !== "") - return qsTr("Model loading error...") - if (currentChat.trySwitchContextInProgress === 1) - return qsTr("Waiting for model...") - if (currentChat.trySwitchContextInProgress === 2) - return qsTr("Switching context...") - if (currentModelName() === "") - return qsTr("Choose a model...") - if (currentChat.modelLoadingPercentage === 0.0) - return qsTr("Reload \u00B7 ") + currentModelName() - if (currentChat.isCurrentlyLoading) - return qsTr("Loading \u00B7 ") + currentModelName() - return currentModelName() - } - font.pixelSize: theme.fontSizeLarger - color: theme.iconBackgroundLight - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - elide: Text.ElideRight } delegate: ItemDelegate { id: comboItemDelegate @@ -330,12 +408,12 @@ Rectangle { verticalAlignment: Text.AlignVCenter } background: Rectangle { - color: (index % 2 === 0 ? theme.darkContrast : theme.lightContrast) - border.width: highlighted - border.color: theme.accentColor + color: highlighted ? theme.lightContrast : theme.darkContrast } highlighted: comboBox.highlightedIndex === index } + indicator: Item { + } popup: Popup { id: comboItemPopup y: comboBox.height - 1 @@ -378,46 +456,6 @@ Rectangle { comboBox.changeModel(index); } } - - MyMiniButton { - id: ejectButton - visible: currentChat.isModelLoaded && !currentChat.isCurrentlyLoading - z: 500 - anchors.right: parent.right - anchors.rightMargin: 50 - anchors.verticalCenter: parent.verticalCenter - source: "qrc:/gpt4all/icons/eject.svg" - backgroundColor: theme.mutedLightTextColor - backgroundColorHovered: theme.iconBackgroundLight - onClicked: { - currentChat.forceUnloadModel(); - } - ToolTip.text: qsTr("Eject the currently loaded model") - ToolTip.visible: hovered - } - - MyMiniButton { - id: reloadButton - visible: currentChat.modelLoadingError === "" - && !currentChat.trySwitchContextInProgress - && !currentChat.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.mutedLightTextColor - backgroundColorHovered: theme.iconBackgroundLight - onClicked: { - if (currentChat.isModelLoaded) - currentChat.forceReloadModel(); - else - currentChat.reloadModel(); - } - ToolTip.text: qsTr("Reload the currently loaded model") - ToolTip.visible: hovered - } } Rectangle { @@ -425,99 +463,97 @@ Rectangle { Layout.alignment: Qt.AlignRight Layout.rightMargin: 30 Layout.fillWidth: true - Layout.preferredWidth: 100 Layout.preferredHeight: childrenRect.height + clip: true - RowLayout { - spacing: 20 + MyButton { + id: collectionsButton + clip: true anchors.right: parent.right - MyButton { - id: collectionsButton - borderWidth: 0 - backgroundColor: theme.collectionsButtonBackground - backgroundColorHovered: theme.collectionsButtonBackgroundHovered - backgroundRadius: 5 - padding: 15 - topPadding: 8 - bottomPadding: 8 + borderWidth: 0 + backgroundColor: theme.collectionsButtonBackground + backgroundColorHovered: theme.collectionsButtonBackgroundHovered + backgroundRadius: 5 + padding: 15 + topPadding: 8 + bottomPadding: 8 - contentItem: RowLayout { - spacing: 10 - Item { - visible: currentChat.collectionModel.count === 0 - Layout.minimumWidth: collectionsImage.width - Layout.minimumHeight: collectionsImage.height - Image { - id: collectionsImage - anchors.verticalCenter: parent.verticalCenter - sourceSize.width: 24 - sourceSize.height: 24 - mipmap: true - visible: false - source: "qrc:/gpt4all/icons/db.svg" - } - - ColorOverlay { - anchors.fill: collectionsImage - source: collectionsImage - color: theme.collectionsButtonForeground - } + contentItem: RowLayout { + spacing: 10 + Item { + visible: currentChat.collectionModel.count === 0 + Layout.minimumWidth: collectionsImage.width + Layout.minimumHeight: collectionsImage.height + Image { + id: collectionsImage + anchors.verticalCenter: parent.verticalCenter + sourceSize.width: 24 + sourceSize.height: 24 + mipmap: true + visible: false + source: "qrc:/gpt4all/icons/db.svg" } - MyBusyIndicator { - visible: currentChat.collectionModel.updatingCount !== 0 - color: theme.collectionsButtonProgress - size: 24 - Layout.minimumWidth: 24 - Layout.minimumHeight: 24 - Text { - anchors.centerIn: parent - text: currentChat.collectionModel.updatingCount - color: theme.collectionsButtonForeground - font.pixelSize: 14 // fixed regardless of theme - } - } - - Rectangle { - visible: currentChat.collectionModel.count !== 0 - radius: 6 + ColorOverlay { + anchors.fill: collectionsImage + source: collectionsImage color: theme.collectionsButtonForeground - Layout.minimumWidth: collectionsImage.width - Layout.minimumHeight: collectionsImage.height - Text { - anchors.centerIn: parent - text: currentChat.collectionModel.count - color: theme.collectionsButtonText - font.pixelSize: 14 // fixed regardless of theme - } } + } + MyBusyIndicator { + visible: currentChat.collectionModel.updatingCount !== 0 + color: theme.collectionsButtonProgress + size: 24 + Layout.minimumWidth: 24 + Layout.minimumHeight: 24 Text { - text: qsTr("LocalDocs") + anchors.centerIn: parent + text: currentChat.collectionModel.updatingCount color: theme.collectionsButtonForeground - font.pixelSize: theme.fontSizeLarge + font.pixelSize: 14 // fixed regardless of theme } } - fontPixelSize: theme.fontSizeLarge - - background: Rectangle { - radius: collectionsButton.backgroundRadius - // TODO(jared): either use collectionsButton-specific theming, or don't - this is inconsistent - color: conversation.state == "expanded" ? ( - collectionsButton.hovered ? theme.lightButtonBackgroundHovered : theme.lightButtonBackground - ) : ( - collectionsButton.hovered ? theme.lighterButtonBackground : theme.lighterButtonBackgroundHovered - ) + Rectangle { + visible: currentChat.collectionModel.count !== 0 + radius: 6 + color: theme.collectionsButtonForeground + Layout.minimumWidth: collectionsImage.width + Layout.minimumHeight: collectionsImage.height + Text { + anchors.centerIn: parent + text: currentChat.collectionModel.count + color: theme.collectionsButtonText + font.pixelSize: 14 // fixed regardless of theme + } } - Accessible.name: qsTr("Add documents") - Accessible.description: qsTr("add collections of documents to the chat") - - onClicked: { - conversation.toggleRightPanel() + Text { + text: qsTr("LocalDocs") + color: theme.collectionsButtonForeground + font.pixelSize: theme.fontSizeLarge } } + + fontPixelSize: theme.fontSizeLarge + + background: Rectangle { + radius: collectionsButton.backgroundRadius + // TODO(jared): either use collectionsButton-specific theming, or don't - this is inconsistent + color: conversation.state === "expanded" ? ( + collectionsButton.hovered ? theme.lightButtonBackgroundHovered : theme.lightButtonBackground + ) : ( + collectionsButton.hovered ? theme.lighterButtonBackground : theme.lighterButtonBackgroundHovered + ) + } + + Accessible.name: qsTr("Add documents") + Accessible.description: qsTr("add collections of documents to the chat") + + onClicked: { + conversation.toggleRightPanel() + } } } } @@ -1418,8 +1454,8 @@ Rectangle { MyTextArea { id: textInput color: theme.textColor - topPadding: 30 - bottomPadding: 30 + topPadding: 15 + bottomPadding: 15 leftPadding: 20 rightPadding: 40 enabled: currentChat.isModelLoaded && !currentChat.isServer @@ -1506,8 +1542,8 @@ Rectangle { anchors.right: textInputView.right anchors.verticalCenter: textInputView.verticalCenter anchors.rightMargin: 15 - width: 30 - height: 30 + imageWidth: theme.fontSizeLarger + imageHeight: theme.fontSizeLarger visible: !currentChat.isServer && ModelList.selectableModels.count !== 0 enabled: !currentChat.responseInProgress source: "qrc:/gpt4all/icons/send_message.svg" diff --git a/gpt4all-chat/qml/MyComboBox.qml b/gpt4all-chat/qml/MyComboBox.qml index eca28256..7f25c60c 100644 --- a/gpt4all-chat/qml/MyComboBox.qml +++ b/gpt4all-chat/qml/MyComboBox.qml @@ -1,6 +1,8 @@ import QtQuick import QtQuick.Controls import QtQuick.Controls.Basic +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects ComboBox { id: comboBox @@ -8,15 +10,39 @@ ComboBox { spacing: 0 padding: 10 Accessible.role: Accessible.ComboBox - contentItem: Text { - id: text - leftPadding: 10 - rightPadding: 20 - text: comboBox.displayText - font: comboBox.font - color: theme.textColor - verticalAlignment: Text.AlignVCenter - elide: Text.ElideRight + contentItem: RowLayout { + id: contentRow + spacing: 0 + Text { + id: text + Layout.fillWidth: true + leftPadding: 10 + rightPadding: 20 + text: comboBox.displayText + font: comboBox.font + color: theme.textColor + verticalAlignment: Text.AlignLeft + elide: Text.ElideRight + } + Item { + Layout.preferredWidth: updown.width + Layout.preferredHeight: updown.height + Image { + id: updown + anchors.verticalCenter: parent.verticalCenter + sourceSize.width: comboBox.font.pixelSize + sourceSize.height: comboBox.font.pixelSize + mipmap: true + visible: false + source: "qrc:/gpt4all/icons/up_down.svg" + } + + ColorOverlay { + anchors.fill: updown + source: updown + color: theme.textColor + } + } } delegate: ItemDelegate { width: comboBox.width @@ -53,33 +79,7 @@ ComboBox { color: theme.black } } - indicator: Canvas { - id: canvas - x: comboBox.width - width - comboBox.rightPadding - y: comboBox.topPadding + (comboBox.availableHeight - height) / 2 - width: 12 - height: 18 - contextType: "2d" - - Connections { - target: comboBox - function onPressedChanged() { canvas.requestPaint(); } - } - - onPaint: { - var context = getContext("2d"); - context.reset(); - context.lineWidth = 2; - context.moveTo(0, height / 2 - 2); - context.lineTo(width / 2, 0); - context.lineTo(width, height / 2 - 2); - context.moveTo(0, height / 2 + 2); - context.lineTo(width / 2, height); - context.lineTo(width, height / 2 + 2); - context.strokeStyle = comboBox.pressed ? theme.mutedLightTextColor : theme.mutedLighterTextColor; - context.stroke(); - - } + indicator: Item { } background: Rectangle { color: theme.controlBackground diff --git a/gpt4all-chat/qml/MyMiniButton.qml b/gpt4all-chat/qml/MyMiniButton.qml index 293026cd..2d3ef728 100644 --- a/gpt4all-chat/qml/MyMiniButton.qml +++ b/gpt4all-chat/qml/MyMiniButton.qml @@ -33,8 +33,8 @@ Button { anchors.centerIn: parent visible: false mipmap: true - sourceSize.width: 20 - sourceSize.height: 20 + sourceSize.width: 16 + sourceSize.height: 16 } ColorOverlay { anchors.fill: image