new UI fixes, part 5 (#2485)

additional new ui changes, part 5 (#2485)

Signed-off-by: Jared Van Bortel <jared@nomic.ai>
This commit is contained in:
Jared Van Bortel 2024-06-28 20:34:03 -04:00 committed by GitHub
parent 22396a6fa1
commit a191844a3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 286 additions and 56 deletions

View File

@ -1493,7 +1493,7 @@ void Database::start()
} else if (!initDb(modelPath, oldCollections)) {
m_databaseValid = false;
} else {
//cleanDB();
cleanDB();
addCurrentFolders();
}
@ -2118,7 +2118,8 @@ void Database::changeFileExtensions(const QStringList &extensions)
m_scannedFileExtensions = extensions;
cleanDB();
if (cleanDB())
updateCollectionStatistics();
QSqlQuery q(m_db);
QList<CollectionItem> collections;

View File

@ -413,7 +413,7 @@ Window {
ColorOverlay {
id: antennaColored
visible: ModelList.installedModels.count !== 0 && (currentChat.isServer || currentChat.modelInfo.isOnline || MySettings.networkIsActive)
visible: ModelList.selectableModels.count !== 0 && (currentChat.isServer || currentChat.modelInfo.isOnline || MySettings.networkIsActive)
anchors.fill: antennaImage
source: antennaImage
color: theme.styledTextColor

View File

@ -312,6 +312,7 @@
"filename": "all-MiniLM-L6-v2.gguf2.f16.gguf",
"filesize": "45949216",
"requires": "2.7.4",
"removedIn": "3.0.0",
"ramrequired": "1",
"parameters": "40 million",
"quant": "f16",

View File

@ -367,8 +367,9 @@ QVariantMap ModelInfo::getFields() const
};
}
InstalledModels::InstalledModels(QObject *parent)
InstalledModels::InstalledModels(QObject *parent, bool selectable)
: QSortFilterProxyModel(parent)
, m_selectable(selectable)
{
connect(this, &InstalledModels::rowsInserted, this, &InstalledModels::countChanged);
connect(this, &InstalledModels::rowsRemoved, this, &InstalledModels::countChanged);
@ -379,11 +380,15 @@ InstalledModels::InstalledModels(QObject *parent)
bool InstalledModels::filterAcceptsRow(int sourceRow,
const QModelIndex &sourceParent) const
{
/* TODO(jared): We should list incomplete models alongside installed models on the
* Models page. Simply replacing isDownloading with isIncomplete here doesn't work for
* some reason - the models show up as something else. */
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
bool isInstalled = sourceModel()->data(index, ModelList::InstalledRole).toBool();
bool isDownloading = sourceModel()->data(index, ModelList::DownloadingRole).toBool();
bool isEmbeddingModel = sourceModel()->data(index, ModelList::IsEmbeddingModelRole).toBool();
// list installed chat models
return isInstalled && !isEmbeddingModel;
return (isInstalled || (!m_selectable && isDownloading)) && !isEmbeddingModel;
}
DownloadableModels::DownloadableModels(QObject *parent)
@ -403,11 +408,9 @@ bool DownloadableModels::filterAcceptsRow(int sourceRow,
// FIXME We can eliminate the 'expanded' code as the UI no longer uses this
bool withinLimit = sourceRow < (m_expanded ? sourceModel()->rowCount() : m_limit);
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
bool isDownloadable = !sourceModel()->data(index, ModelList::DescriptionRole).toString().isEmpty();
bool isInstalled = sourceModel()->data(index, ModelList::InstalledRole).toBool();
bool isIncomplete = sourceModel()->data(index, ModelList::IncompleteRole).toBool();
bool hasDescription = !sourceModel()->data(index, ModelList::DescriptionRole).toString().isEmpty();
bool isClone = sourceModel()->data(index, ModelList::IsCloneRole).toBool();
return withinLimit && !isClone && !isInstalled && (isDownloadable || isIncomplete);
return withinLimit && hasDescription && !isClone;
}
int DownloadableModels::count() const
@ -446,6 +449,7 @@ ModelList *ModelList::globalInstance()
ModelList::ModelList()
: QAbstractListModel(nullptr)
, m_installedModels(new InstalledModels(this))
, m_selectableModels(new InstalledModels(this, /*selectable*/ true))
, m_downloadableModels(new DownloadableModels(this))
, m_asyncModelRequestOngoing(false)
, m_discoverLimit(20)
@ -456,6 +460,7 @@ ModelList::ModelList()
, m_discoverInProgress(false)
{
m_installedModels->setSourceModel(this);
m_selectableModels->setSourceModel(this);
m_downloadableModels->setSourceModel(this);
connect(MySettings::globalInstance(), &MySettings::modelPathChanged, this, &ModelList::updateModelsFromDirectory);

View File

@ -208,7 +208,7 @@ class InstalledModels : public QSortFilterProxyModel
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
public:
explicit InstalledModels(QObject *parent);
explicit InstalledModels(QObject *parent, bool selectable = false);
int count() const { return rowCount(); }
Q_SIGNALS:
@ -216,6 +216,9 @@ Q_SIGNALS:
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
private:
bool m_selectable;
};
class DownloadableModels : public QSortFilterProxyModel
@ -252,6 +255,7 @@ class ModelList : public QAbstractListModel
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
Q_PROPERTY(InstalledModels* installedModels READ installedModels NOTIFY installedModelsChanged)
Q_PROPERTY(InstalledModels* selectableModels READ selectableModels NOTIFY selectableModelsChanged)
Q_PROPERTY(DownloadableModels* downloadableModels READ downloadableModels NOTIFY downloadableModelsChanged)
Q_PROPERTY(QList<QString> userDefaultModelList READ userDefaultModelList NOTIFY userDefaultModelListChanged)
Q_PROPERTY(bool asyncModelRequestOngoing READ asyncModelRequestOngoing NOTIFY asyncModelRequestOngoingChanged)
@ -396,6 +400,7 @@ public:
const QList<QString> userDefaultModelList() const;
InstalledModels *installedModels() const { return m_installedModels; }
InstalledModels *selectableModels() const { return m_selectableModels; }
DownloadableModels *downloadableModels() const { return m_downloadableModels; }
static inline QString toFileSize(quint64 sz) {
@ -433,6 +438,7 @@ public:
Q_SIGNALS:
void countChanged();
void installedModelsChanged();
void selectableModelsChanged();
void downloadableModelsChanged();
void userDefaultModelListChanged();
void asyncModelRequestOngoingChanged();
@ -471,6 +477,7 @@ private:
mutable QMutex m_mutex;
QNetworkAccessManager m_networkManager;
InstalledModels *m_installedModels;
InstalledModels *m_selectableModels;
DownloadableModels *m_downloadableModels;
QList<ModelInfo*> m_models;
QHash<QString, ModelInfo*> m_modelMap;

View File

@ -365,7 +365,12 @@ Rectangle {
Accessible.role: Accessible.Paragraph
Accessible.name: qsTr("Description")
Accessible.description: qsTr("File description")
onLinkActivated: Qt.openUrlExternally(link)
onLinkActivated: function(link) { Qt.openUrlExternally(link); }
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton // pass clicks to parent
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
}
// FIXME Need to overhaul design here which must take into account
@ -418,7 +423,7 @@ Rectangle {
Layout.minimumWidth: 200
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
visible: installed || downloadError !== ""
visible: !isDownloading && (installed || isIncomplete)
Accessible.description: qsTr("Remove model from filesystem")
onClicked: {
Download.removeModel(filename);

View File

@ -242,8 +242,8 @@ Rectangle {
enabled: !currentChat.isServer
&& !currentChat.trySwitchContextInProgress
&& !currentChat.isCurrentlyLoading
&& ModelList.installedModels.count !== 0
model: ModelList.installedModels
&& ModelList.selectableModels.count !== 0
model: ModelList.selectableModels
valueRole: "id"
textRole: "name"
@ -297,7 +297,7 @@ Rectangle {
return 25
}
text: {
if (ModelList.installedModels.count === 0)
if (ModelList.selectableModels.count === 0)
return qsTr("No model installed...")
if (currentChat.modelLoadingError !== "")
return qsTr("Model loading error...")
@ -602,10 +602,10 @@ Rectangle {
id: homePage
color: "transparent"
anchors.fill: parent
visible: !currentChat.isModelLoaded && (ModelList.installedModels.count === 0 || currentModelName() === "") && !currentChat.isServer
visible: !currentChat.isModelLoaded && (ModelList.selectableModels.count === 0 || currentModelName() === "") && !currentChat.isServer
ColumnLayout {
visible: ModelList.installedModels.count !== 0
visible: ModelList.selectableModels.count !== 0
id: modelInstalledLabel
anchors.centerIn: parent
spacing: 0
@ -637,7 +637,7 @@ Rectangle {
MyButton {
id: loadDefaultModelButton
visible: ModelList.installedModels.count !== 0
visible: ModelList.selectableModels.count !== 0
anchors.top: modelInstalledLabel.bottom
anchors.topMargin: 50
anchors.horizontalCenter: modelInstalledLabel.horizontalCenter
@ -683,7 +683,7 @@ Rectangle {
ColumnLayout {
id: noModelInstalledLabel
visible: ModelList.installedModels.count === 0
visible: ModelList.selectableModels.count === 0
anchors.centerIn: parent
spacing: 0
@ -704,7 +704,7 @@ Rectangle {
}
MyButton {
visible: ModelList.installedModels.count === 0
visible: ModelList.selectableModels.count === 0
anchors.top: noModelInstalledLabel.bottom
anchors.topMargin: 50
anchors.horizontalCenter: noModelInstalledLabel.horizontalCenter
@ -721,7 +721,7 @@ Rectangle {
ColumnLayout {
anchors.fill: parent
visible: ModelList.installedModels.count !== 0 && chatModel.count !== 0
visible: ModelList.selectableModels.count !== 0 && chatModel.count !== 0
ListView {
id: listView
Layout.maximumWidth: 1280
@ -1047,10 +1047,9 @@ Rectangle {
}
}
TextArea {
text: consolidatedSources.length + " " + qsTr("Sources")
Text {
text: qsTr("%1 Sources").arg(consolidatedSources.length)
padding: 0
readOnly: true
font.pixelSize: theme.fontSizeLarge
font.bold: true
color: theme.styledTextColor
@ -1095,6 +1094,7 @@ Rectangle {
id: sourcesLayout
Layout.row: 3
Layout.column: 1
Layout.topMargin: 5
visible: consolidatedSources.length !== 0 && MySettings.localDocsShowReferences && (!currentResponse || !currentChat.responseInProgress)
clip: true
Layout.fillWidth: true
@ -1126,7 +1126,6 @@ Rectangle {
Flow {
Layout.fillWidth: true
Layout.topMargin: 5
spacing: 10
visible: consolidatedSources.length !== 0
Repeater {
@ -1397,7 +1396,7 @@ Rectangle {
RectangularGlow {
id: effect
visible: !currentChat.isServer && ModelList.installedModels.count !== 0
visible: !currentChat.isServer && ModelList.selectableModels.count !== 0
anchors.fill: textInputView
glowRadius: 50
spread: 0
@ -1415,7 +1414,7 @@ Rectangle {
anchors.leftMargin: Math.max((parent.width - 1310) / 2, 30)
anchors.rightMargin: Math.max((parent.width - 1310) / 2, 30)
height: Math.min(contentHeight, 200)
visible: !currentChat.isServer && ModelList.installedModels.count !== 0
visible: !currentChat.isServer && ModelList.selectableModels.count !== 0
MyTextArea {
id: textInput
color: theme.textColor
@ -1509,7 +1508,7 @@ Rectangle {
anchors.rightMargin: 15
width: 30
height: 30
visible: !currentChat.isServer && ModelList.installedModels.count !== 0
visible: !currentChat.isServer && ModelList.selectableModels.count !== 0
enabled: !currentChat.responseInProgress
source: "qrc:/gpt4all/icons/send_message.svg"
Accessible.name: qsTr("Send message")

View File

@ -136,7 +136,7 @@ Rectangle {
Text {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
text: qsTr("Chat privately with local files using on-device Large Language Models (LLMs). Keeps data private and secure. Best results with Llama 3 Instruct.")
text: qsTr("Select a collection to make it available to the chat model.")
font.pixelSize: theme.fontSizeLarger
wrapMode: Text.WordWrap
elide: Text.ElideRight

View File

@ -53,7 +53,7 @@ Rectangle {
Text {
Layout.alignment: Qt.AlignHCenter
text: qsTr("the privacy-first LLM chat application")
text: qsTr("The privacy-first LLM chat application")
font.pixelSize: theme.fontSizeLarge
color: theme.titleInfoTextColor
}

View File

@ -17,7 +17,7 @@ MySettingsTab {
columns: 3
rowSpacing: 10
columnSpacing: 10
enabled: ModelList.installedModels.count !== 0
enabled: ModelList.selectableModels.count !== 0
property var currentModelName: comboBox.currentText
property var currentModelId: comboBox.currentValue
@ -43,7 +43,7 @@ MySettingsTab {
MyComboBox {
id: comboBox
Layout.fillWidth: true
model: ModelList.installedModels
model: ModelList.selectableModels
valueRole: "id"
textRole: "name"
currentIndex: {

View File

@ -82,7 +82,7 @@ Rectangle {
}
Text {
text: qsTr("Locally installed large language models")
text: qsTr("Locally installed chat models")
font.pixelSize: theme.fontSizeLarge
color: theme.titleInfoTextColor
}
@ -150,20 +150,240 @@ Rectangle {
color: theme.dividerColor
}
Text {
id: descriptionText
text: description
font.pixelSize: theme.fontSizeLarge
Layout.row: 1
RowLayout {
Layout.topMargin: 10
wrapMode: Text.WordWrap
textFormat: Text.StyledText
color: theme.textColor
linkColor: theme.textColor
Accessible.role: Accessible.Paragraph
Accessible.name: qsTr("Description")
Accessible.description: qsTr("File description")
onLinkActivated: Qt.openUrlExternally(link)
Layout.fillWidth: true
Text {
id: descriptionText
text: description
font.pixelSize: theme.fontSizeLarge
Layout.fillWidth: true
wrapMode: Text.WordWrap
textFormat: Text.StyledText
color: theme.textColor
linkColor: theme.textColor
Accessible.role: Accessible.Paragraph
Accessible.name: qsTr("Description")
Accessible.description: qsTr("File description")
onLinkActivated: function(link) { Qt.openUrlExternally(link); }
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton // pass clicks to parent
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
}
Rectangle {
id: actionBox
width: childrenRect.width + 20
color: "transparent"
border.width: 1
border.color: theme.dividerColor
radius: 10
Layout.rightMargin: 20
Layout.bottomMargin: 20
Layout.minimumHeight: childrenRect.height + 20
Layout.alignment: Qt.AlignRight | Qt.AlignTop
ColumnLayout {
spacing: 0
MySettingsButton {
id: downloadButton
text: isDownloading ? qsTr("Cancel") : qsTr("Resume")
font.pixelSize: theme.fontSizeLarge
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.minimumWidth: 200
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
visible: (isDownloading || isIncomplete) && downloadError === "" && !isOnline && !calcHash
Accessible.description: qsTr("Stop/restart/start the download")
onClicked: {
if (!isDownloading) {
Download.downloadModel(filename);
} else {
Download.cancelDownload(filename);
}
}
}
MySettingsDestructiveButton {
id: removeButton
text: qsTr("Remove")
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.minimumWidth: 200
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
visible: !isDownloading && (installed || isIncomplete)
Accessible.description: qsTr("Remove model from filesystem")
onClicked: {
Download.removeModel(filename);
}
}
MySettingsButton {
id: installButton
visible: !installed && isOnline
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.minimumWidth: 200
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
text: qsTr("Install")
font.pixelSize: theme.fontSizeLarge
onClicked: {
if (apiKey.text === "")
apiKey.showError();
else
Download.installModel(filename, apiKey.text);
}
Accessible.role: Accessible.Button
Accessible.name: qsTr("Install")
Accessible.description: qsTr("Install online model")
}
ColumnLayout {
spacing: 0
Label {
Layout.topMargin: 20
Layout.leftMargin: 20
visible: downloadError !== ""
textFormat: Text.StyledText
text: "<strong><font size=\"1\">"
+ qsTr("<a href=\"#error\">Error</a>")
+ "</strong></font>"
color: theme.textColor
font.pixelSize: theme.fontSizeLarge
linkColor: theme.textErrorColor
Accessible.role: Accessible.Paragraph
Accessible.name: text
Accessible.description: qsTr("Describes an error that occurred when downloading")
onLinkActivated: {
downloadingErrorPopup.text = downloadError;
downloadingErrorPopup.open();
}
}
Label {
visible: LLM.systemTotalRAMInGB() < ramrequired
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.maximumWidth: 300
textFormat: Text.StyledText
text: qsTr("<strong><font size=\"2\">WARNING: Not recommended for your hardware.")
+ qsTr(" Model requires more memory (") + ramrequired
+ qsTr(" GB) than your system has available (")
+ LLM.systemTotalRAMInGBString() + ").</strong></font>"
color: theme.textErrorColor
font.pixelSize: theme.fontSizeLarge
wrapMode: Text.WordWrap
Accessible.role: Accessible.Paragraph
Accessible.name: text
Accessible.description: qsTr("Error for incompatible hardware")
onLinkActivated: {
downloadingErrorPopup.text = downloadError;
downloadingErrorPopup.open();
}
}
}
ColumnLayout {
visible: isDownloading && !calcHash
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.minimumWidth: 200
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
spacing: 20
ProgressBar {
id: itemProgressBar
Layout.fillWidth: true
width: 200
value: bytesReceived / bytesTotal
background: Rectangle {
implicitHeight: 45
color: theme.progressBackground
radius: 3
}
contentItem: Item {
implicitHeight: 40
Rectangle {
width: itemProgressBar.visualPosition * parent.width
height: parent.height
radius: 2
color: theme.progressForeground
}
}
Accessible.role: Accessible.ProgressBar
Accessible.name: qsTr("Download progressBar")
Accessible.description: qsTr("Shows the progress made in the download")
}
Label {
id: speedLabel
color: theme.textColor
Layout.alignment: Qt.AlignRight
text: speed
font.pixelSize: theme.fontSizeLarge
Accessible.role: Accessible.Paragraph
Accessible.name: qsTr("Download speed")
Accessible.description: qsTr("Download speed in bytes/kilobytes/megabytes per second")
}
}
RowLayout {
visible: calcHash
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.minimumWidth: 200
Layout.maximumWidth: 200
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
clip: true
Label {
id: calcHashLabel
color: theme.textColor
text: qsTr("Calculating...")
font.pixelSize: theme.fontSizeLarge
Accessible.role: Accessible.Paragraph
Accessible.name: text
Accessible.description: qsTr("Whether the file hash is being calculated")
}
MyBusyIndicator {
id: busyCalcHash
running: calcHash
Accessible.role: Accessible.Animation
Accessible.name: qsTr("Busy indicator")
Accessible.description: qsTr("Displayed when the file hash is being calculated")
}
}
MyTextField {
id: apiKey
visible: !installed && isOnline
Layout.topMargin: 20
Layout.leftMargin: 20
Layout.minimumWidth: 200
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
wrapMode: Text.WrapAnywhere
function showError() {
apiKey.placeholderTextColor = theme.textErrorColor
}
onTextChanged: {
apiKey.placeholderTextColor = theme.mutedTextColor
}
placeholderText: qsTr("enter $API_KEY")
Accessible.role: Accessible.EditableText
Accessible.name: placeholderText
Accessible.description: qsTr("Whether the file hash is being calculated")
}
}
}
}
Item {
@ -206,7 +426,7 @@ Rectangle {
color: theme.mutedDarkTextColor
}
Text {
text: ramrequired + qsTr(" GB")
text: ramrequired >= 0 ? ramrequired + qsTr(" GB") : "?"
color: theme.textColor
font.pixelSize: theme.fontSizeSmall
font.bold: true
@ -228,7 +448,7 @@ Rectangle {
color: theme.mutedDarkTextColor
}
Text {
text: parameters
text: parameters !== "" ? parameters : "?"
color: theme.textColor
font.pixelSize: theme.fontSizeSmall
font.bold: true
@ -294,14 +514,6 @@ Rectangle {
height: 1
color: theme.dividerColor
}
MySettingsButton {
text: qsTr("Remove")
textColor: theme.red500
onClicked: Download.removeModel(filename)
backgroundColor: "transparent"
backgroundColorHovered: theme.lighterButtonBackgroundHoveredRed
}
}
}
}