diff --git a/gpt4all-chat/CHANGELOG.md b/gpt4all-chat/CHANGELOG.md
index d06231aa..951422ee 100644
--- a/gpt4all-chat/CHANGELOG.md
+++ b/gpt4all-chat/CHANGELOG.md
@@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Added
- Whitelist Granite (non-MoE) model architecture (by [@ThiloteE](https://github.com/ThiloteE) in [#3487](https://github.com/nomic-ai/gpt4all/pull/3487))
- Add support for CUDA compute 5.0 GPUs such as the GTX 750 ([#3499](https://github.com/nomic-ai/gpt4all/pull/3499))
+- Add a Remote Providers tab to the Add Model page ([#3506](https://github.com/nomic-ai/gpt4all/pull/3506))
### Changed
- Substitute prettier default templates for OLMoE 7B 0924/0125 and Granite 3.1 3B/8B (by [@ThiloteE](https://github.com/ThiloteE) in [#3471](https://github.com/nomic-ai/gpt4all/pull/3471))
diff --git a/gpt4all-chat/CMakeLists.txt b/gpt4all-chat/CMakeLists.txt
index 91ce25fd..a1b64692 100644
--- a/gpt4all-chat/CMakeLists.txt
+++ b/gpt4all-chat/CMakeLists.txt
@@ -266,6 +266,7 @@ qt_add_qml_module(chat
qml/AddModelView.qml
qml/AddGPT4AllModelView.qml
qml/AddHFModelView.qml
+ qml/AddRemoteModelView.qml
qml/ApplicationSettings.qml
qml/ChatDrawer.qml
qml/ChatCollapsibleItem.qml
@@ -314,6 +315,7 @@ qt_add_qml_module(chat
qml/MyTextField.qml
qml/MyToolButton.qml
qml/MyWelcomeButton.qml
+ qml/RemoteModelCard.qml
RESOURCES
icons/antenna_1.svg
icons/antenna_2.svg
@@ -344,6 +346,7 @@ qt_add_qml_module(chat
icons/gpt4all-48.png
icons/gpt4all.svg
icons/gpt4all_transparent.svg
+ icons/groq.svg
icons/home.svg
icons/image.svg
icons/info.svg
@@ -351,12 +354,14 @@ qt_add_qml_module(chat
icons/left_panel_open.svg
icons/local-docs.svg
icons/models.svg
+ icons/mistral.svg
icons/network.svg
icons/nomic_logo.svg
icons/notes.svg
icons/paperclip.svg
icons/plus.svg
icons/plus_circle.svg
+ icons/openai.svg
icons/recycle.svg
icons/regenerate.svg
icons/search.svg
diff --git a/gpt4all-chat/icons/groq.svg b/gpt4all-chat/icons/groq.svg
new file mode 100644
index 00000000..1f895558
--- /dev/null
+++ b/gpt4all-chat/icons/groq.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/gpt4all-chat/icons/mistral.svg b/gpt4all-chat/icons/mistral.svg
new file mode 100644
index 00000000..0775fe7e
--- /dev/null
+++ b/gpt4all-chat/icons/mistral.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/gpt4all-chat/icons/openai.svg b/gpt4all-chat/icons/openai.svg
new file mode 100644
index 00000000..3b4eff96
--- /dev/null
+++ b/gpt4all-chat/icons/openai.svg
@@ -0,0 +1,2 @@
+
+OpenAI icon
\ No newline at end of file
diff --git a/gpt4all-chat/qml/AddGPT4AllModelView.qml b/gpt4all-chat/qml/AddGPT4AllModelView.qml
index 2a1832af..55154e6f 100644
--- a/gpt4all-chat/qml/AddGPT4AllModelView.qml
+++ b/gpt4all-chat/qml/AddGPT4AllModelView.qml
@@ -204,7 +204,7 @@ ColumnLayout {
Layout.minimumWidth: 200
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
- visible: !isOnline && !installed && !calcHash && downloadError === ""
+ visible: !installed && !calcHash && downloadError === ""
Accessible.description: qsTr("Stop/restart/start the download")
onClicked: {
if (!isDownloading) {
@@ -230,52 +230,6 @@ ColumnLayout {
}
}
- 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: {
- var apiKeyText = apiKey.text.trim(),
- baseUrlText = baseUrl.text.trim(),
- modelNameText = modelName.text.trim();
-
- var apiKeyOk = apiKeyText !== "",
- baseUrlOk = !isCompatibleApi || baseUrlText !== "",
- modelNameOk = !isCompatibleApi || modelNameText !== "";
-
- if (!apiKeyOk)
- apiKey.showError();
- if (!baseUrlOk)
- baseUrl.showError();
- if (!modelNameOk)
- modelName.showError();
-
- if (!apiKeyOk || !baseUrlOk || !modelNameOk)
- return;
-
- if (!isCompatibleApi)
- Download.installModel(
- filename,
- apiKeyText,
- );
- else
- Download.installCompatibleModel(
- modelNameText,
- apiKeyText,
- baseUrlText,
- );
- }
- Accessible.role: Accessible.Button
- Accessible.name: qsTr("Install")
- Accessible.description: qsTr("Install online model")
- }
-
ColumnLayout {
spacing: 0
Label {
@@ -390,69 +344,6 @@ ColumnLayout {
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() {
- messageToast.show(qsTr("ERROR: $API_KEY is empty."));
- 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")
- }
-
- MyTextField {
- id: baseUrl
- visible: !installed && isOnline && isCompatibleApi
- Layout.topMargin: 20
- Layout.leftMargin: 20
- Layout.minimumWidth: 200
- Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
- wrapMode: Text.WrapAnywhere
- function showError() {
- messageToast.show(qsTr("ERROR: $BASE_URL is empty."));
- baseUrl.placeholderTextColor = theme.textErrorColor;
- }
- onTextChanged: {
- baseUrl.placeholderTextColor = theme.mutedTextColor;
- }
- placeholderText: qsTr("enter $BASE_URL")
- Accessible.role: Accessible.EditableText
- Accessible.name: placeholderText
- Accessible.description: qsTr("Whether the file hash is being calculated")
- }
-
- MyTextField {
- id: modelName
- visible: !installed && isOnline && isCompatibleApi
- Layout.topMargin: 20
- Layout.leftMargin: 20
- Layout.minimumWidth: 200
- Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
- wrapMode: Text.WrapAnywhere
- function showError() {
- messageToast.show(qsTr("ERROR: $MODEL_NAME is empty."))
- modelName.placeholderTextColor = theme.textErrorColor;
- }
- onTextChanged: {
- modelName.placeholderTextColor = theme.mutedTextColor;
- }
- placeholderText: qsTr("enter $MODEL_NAME")
- Accessible.role: Accessible.EditableText
- Accessible.name: placeholderText
- Accessible.description: qsTr("Whether the file hash is being calculated")
- }
}
}
}
diff --git a/gpt4all-chat/qml/AddModelView.qml b/gpt4all-chat/qml/AddModelView.qml
index 223d7037..0bb06f24 100644
--- a/gpt4all-chat/qml/AddModelView.qml
+++ b/gpt4all-chat/qml/AddModelView.qml
@@ -89,6 +89,13 @@ Rectangle {
gpt4AllModelView.show();
}
}
+ MyTabButton {
+ text: qsTr("Remote Providers")
+ isSelected: remoteModelView.isShown()
+ onPressed: {
+ remoteModelView.show();
+ }
+ }
MyTabButton {
text: qsTr("HuggingFace")
isSelected: huggingfaceModelView.isShown()
@@ -112,7 +119,20 @@ Rectangle {
stackLayout.currentIndex = 0;
}
function isShown() {
- return stackLayout.currentIndex === 0
+ return stackLayout.currentIndex === 0;
+ }
+ }
+
+ AddRemoteModelView {
+ id: remoteModelView
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ function show() {
+ stackLayout.currentIndex = 1;
+ }
+ function isShown() {
+ return stackLayout.currentIndex === 1;
}
}
@@ -126,10 +146,10 @@ Rectangle {
anchors.fill: parent
function show() {
- stackLayout.currentIndex = 1;
+ stackLayout.currentIndex = 2;
}
function isShown() {
- return stackLayout.currentIndex === 1
+ return stackLayout.currentIndex === 2;
}
}
}
diff --git a/gpt4all-chat/qml/AddRemoteModelView.qml b/gpt4all-chat/qml/AddRemoteModelView.qml
new file mode 100644
index 00000000..bca28199
--- /dev/null
+++ b/gpt4all-chat/qml/AddRemoteModelView.qml
@@ -0,0 +1,147 @@
+import QtCore
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Controls.Basic
+import QtQuick.Layouts
+import QtQuick.Dialogs
+import Qt.labs.folderlistmodel
+import Qt5Compat.GraphicalEffects
+
+import llm
+import chatlistmodel
+import download
+import modellist
+import network
+import gpt4all
+import mysettings
+import localdocs
+
+ColumnLayout {
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignTop
+ spacing: 5
+
+ Label {
+ Layout.topMargin: 0
+ Layout.bottomMargin: 25
+ Layout.rightMargin: 150 * theme.fontScale
+ Layout.alignment: Qt.AlignTop
+ Layout.fillWidth: true
+ verticalAlignment: Text.AlignTop
+ text: qsTr("Various remote model providers that use network resources for inference.")
+ font.pixelSize: theme.fontSizeLarger
+ color: theme.textColor
+ wrapMode: Text.WordWrap
+ }
+
+ ScrollView {
+ id: scrollView
+ ScrollBar.vertical.policy: ScrollBar.AsNeeded
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ contentWidth: availableWidth
+ clip: true
+ Flow {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ spacing: 20
+ bottomPadding: 20
+ property int childWidth: 330 * theme.fontScale
+ property int childHeight: 400 + 166 * theme.fontScale
+ RemoteModelCard {
+ width: parent.childWidth
+ height: parent.childHeight
+ providerBaseUrl: "https://api.groq.com/openai/v1/"
+ providerName: qsTr("Groq")
+ providerImage: "qrc:/gpt4all/icons/groq.svg"
+ providerDesc: qsTr('Groq offers a high-performance AI inference engine designed for low-latency and efficient processing. Optimized for real-time applications, Groq’s technology is ideal for users who need fast responses from open large language models and other AI workloads. Get your API key: https://groq.com/ ')
+ modelWhitelist: [
+ // last updated 2025-02-24
+ "deepseek-r1-distill-llama-70b",
+ "deepseek-r1-distill-qwen-32b",
+ "gemma2-9b-it",
+ "llama-3.1-8b-instant",
+ "llama-3.2-1b-preview",
+ "llama-3.2-3b-preview",
+ "llama-3.3-70b-specdec",
+ "llama-3.3-70b-versatile",
+ "llama3-70b-8192",
+ "llama3-8b-8192",
+ "mixtral-8x7b-32768",
+ "qwen-2.5-32b",
+ "qwen-2.5-coder-32b",
+ ]
+ }
+ RemoteModelCard {
+ width: parent.childWidth
+ height: parent.childHeight
+ providerBaseUrl: "https://api.openai.com/v1/"
+ providerName: qsTr("OpenAI")
+ providerImage: "qrc:/gpt4all/icons/openai.svg"
+ providerDesc: qsTr('OpenAI provides access to advanced AI models, including GPT-4 supporting a wide range of applications, from conversational AI to content generation and code completion. Get your API key: https://openai.com/ ')
+ modelWhitelist: [
+ // last updated 2025-02-24
+ "gpt-3.5-turbo",
+ "gpt-3.5-turbo-16k",
+ "gpt-4",
+ "gpt-4-32k",
+ "gpt-4-turbo",
+ "gpt-4o",
+ ]
+ }
+ RemoteModelCard {
+ width: parent.childWidth
+ height: parent.childHeight
+ providerBaseUrl: "https://api.mistral.ai/v1/"
+ providerName: qsTr("Mistral")
+ providerImage: "qrc:/gpt4all/icons/mistral.svg"
+ providerDesc: qsTr('Mistral AI specializes in efficient, open-weight language models optimized for various natural language processing tasks. Their models are designed for flexibility and performance, making them a solid option for applications requiring scalable AI solutions. Get your API key: https://mistral.ai/ ')
+ modelWhitelist: [
+ // last updated 2025-02-24
+ "codestral-2405",
+ "codestral-2411-rc5",
+ "codestral-2412",
+ "codestral-2501",
+ "codestral-latest",
+ "codestral-mamba-2407",
+ "codestral-mamba-latest",
+ "ministral-3b-2410",
+ "ministral-3b-latest",
+ "ministral-8b-2410",
+ "ministral-8b-latest",
+ "mistral-large-2402",
+ "mistral-large-2407",
+ "mistral-large-2411",
+ "mistral-large-latest",
+ "mistral-medium-2312",
+ "mistral-medium-latest",
+ "mistral-saba-2502",
+ "mistral-saba-latest",
+ "mistral-small-2312",
+ "mistral-small-2402",
+ "mistral-small-2409",
+ "mistral-small-2501",
+ "mistral-small-latest",
+ "mistral-tiny-2312",
+ "mistral-tiny-2407",
+ "mistral-tiny-latest",
+ "open-codestral-mamba",
+ "open-mistral-7b",
+ "open-mistral-nemo",
+ "open-mistral-nemo-2407",
+ "open-mixtral-8x22b",
+ "open-mixtral-8x22b-2404",
+ "open-mixtral-8x7b",
+ ]
+ }
+ RemoteModelCard {
+ width: parent.childWidth
+ height: parent.childHeight
+ providerIsCustom: true
+ providerName: qsTr("Custom")
+ providerImage: "qrc:/gpt4all/icons/antenna_3.svg"
+ providerDesc: qsTr("The custom provider option allows users to connect their own OpenAI-compatible AI models or third-party inference services. This is useful for organizations with proprietary models or those leveraging niche AI providers not listed here.")
+ }
+ }
+ }
+}
diff --git a/gpt4all-chat/qml/MyComboBox.qml b/gpt4all-chat/qml/MyComboBox.qml
index 0e8c5aa3..9c23a57c 100644
--- a/gpt4all-chat/qml/MyComboBox.qml
+++ b/gpt4all-chat/qml/MyComboBox.qml
@@ -60,27 +60,28 @@ ComboBox {
highlighted: comboBox.highlightedIndex === index
}
popup: Popup {
- // FIXME This should be made much nicer to take into account lists that are very long so
- // that it is scrollable and also sized optimally taking into account the x,y and the content
- // width and height as well as the window width and height
y: comboBox.height - 1
width: comboBox.width
- implicitHeight: contentItem.implicitHeight + 20
+ implicitHeight: Math.min(window.height - y, contentItem.implicitHeight + 20)
padding: 0
-
contentItem: Rectangle {
- implicitWidth: myListView.contentWidth
+ implicitWidth: comboBox.width
implicitHeight: myListView.contentHeight
color: "transparent"
- ListView {
- id: myListView
+ radius: 10
+ ScrollView {
anchors.fill: parent
anchors.margins: 10
clip: true
- implicitHeight: contentHeight
- model: comboBox.popup.visible ? comboBox.delegateModel : null
- currentIndex: comboBox.highlightedIndex
- ScrollIndicator.vertical: ScrollIndicator { }
+ ScrollBar.vertical.policy: ScrollBar.AsNeeded
+ ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
+ ListView {
+ id: myListView
+ implicitHeight: contentHeight
+ model: comboBox.popup.visible ? comboBox.delegateModel : null
+ currentIndex: comboBox.highlightedIndex
+ ScrollIndicator.vertical: ScrollIndicator { }
+ }
}
}
diff --git a/gpt4all-chat/qml/RemoteModelCard.qml b/gpt4all-chat/qml/RemoteModelCard.qml
new file mode 100644
index 00000000..e8c63765
--- /dev/null
+++ b/gpt4all-chat/qml/RemoteModelCard.qml
@@ -0,0 +1,221 @@
+import QtCore
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Controls.Basic
+import QtQuick.Layouts
+import QtQuick.Dialogs
+import Qt.labs.folderlistmodel
+import Qt5Compat.GraphicalEffects
+
+import llm
+import chatlistmodel
+import download
+import modellist
+import network
+import gpt4all
+import mysettings
+import localdocs
+
+
+Rectangle {
+ property alias providerName: providerNameLabel.text
+ property alias providerImage: myimage.source
+ property alias providerDesc: providerDescLabel.text
+ property string providerBaseUrl: ""
+ property bool providerIsCustom: false
+ property var modelWhitelist: null
+
+ color: theme.conversationBackground
+ radius: 10
+ border.width: 1
+ border.color: theme.controlBorder
+ implicitHeight: topColumn.height + bottomColumn.height + 33 * theme.fontScale
+
+ ColumnLayout {
+ id: topColumn
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: parent.top
+ anchors.margins: 20
+ spacing: 15 * theme.fontScale
+ RowLayout {
+ Layout.alignment: Qt.AlignTop
+ spacing: 10
+ Item {
+ Layout.preferredWidth: 27 * theme.fontScale
+ Layout.preferredHeight: 27 * theme.fontScale
+ Layout.alignment: Qt.AlignLeft
+
+ Image {
+ id: myimage
+ anchors.centerIn: parent
+ sourceSize.width: parent.width
+ sourceSize.height: parent.height
+ mipmap: true
+ fillMode: Image.PreserveAspectFit
+ }
+ }
+
+ Label {
+ id: providerNameLabel
+ color: theme.textColor
+ font.pixelSize: theme.fontSizeBanner
+ }
+ }
+
+ Label {
+ id: providerDescLabel
+ Layout.fillWidth: true
+ wrapMode: Text.Wrap
+ color: theme.settingsTitleTextColor
+ font.pixelSize: theme.fontSizeLarge
+ onLinkActivated: function(link) { Qt.openUrlExternally(link); }
+
+ MouseArea {
+ anchors.fill: parent
+ acceptedButtons: Qt.NoButton // pass clicks to parent
+ cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
+ }
+ }
+ }
+
+ ColumnLayout {
+ id: bottomColumn
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ anchors.margins: 20
+ spacing: 30
+
+ ColumnLayout {
+ MySettingsLabel {
+ text: qsTr("API Key")
+ font.bold: true
+ font.pixelSize: theme.fontSizeLarge
+ color: theme.settingsTitleTextColor
+ }
+
+ MyTextField {
+ id: apiKeyField
+ Layout.fillWidth: true
+ font.pixelSize: theme.fontSizeLarge
+ wrapMode: Text.WrapAnywhere
+ function showError() {
+ messageToast.show(qsTr("ERROR: $API_KEY is empty."));
+ apiKeyField.placeholderTextColor = theme.textErrorColor;
+ }
+ onTextChanged: {
+ apiKeyField.placeholderTextColor = theme.mutedTextColor;
+ if (!providerIsCustom) {
+ let models = ModelList.remoteModelList(apiKeyField.text, providerBaseUrl);
+ if (modelWhitelist !== null)
+ models = models.filter(m => modelWhitelist.includes(m));
+ myModelList.model = models;
+ myModelList.currentIndex = -1;
+ }
+ }
+ placeholderText: qsTr("enter $API_KEY")
+ Accessible.role: Accessible.EditableText
+ Accessible.name: placeholderText
+ Accessible.description: qsTr("Whether the file hash is being calculated")
+ }
+ }
+
+ ColumnLayout {
+ visible: providerIsCustom
+ MySettingsLabel {
+ text: qsTr("Base Url")
+ font.bold: true
+ font.pixelSize: theme.fontSizeLarge
+ color: theme.settingsTitleTextColor
+ }
+ MyTextField {
+ id: baseUrlField
+ Layout.fillWidth: true
+ font.pixelSize: theme.fontSizeLarge
+ wrapMode: Text.WrapAnywhere
+ function showError() {
+ messageToast.show(qsTr("ERROR: $BASE_URL is empty."));
+ baseUrlField.placeholderTextColor = theme.textErrorColor;
+ }
+ onTextChanged: {
+ baseUrlField.placeholderTextColor = theme.mutedTextColor;
+ }
+ placeholderText: qsTr("enter $BASE_URL")
+ Accessible.role: Accessible.EditableText
+ Accessible.name: placeholderText
+ }
+ }
+ ColumnLayout {
+ visible: providerIsCustom
+ MySettingsLabel {
+ text: qsTr("Model Name")
+ font.bold: true
+ font.pixelSize: theme.fontSizeLarge
+ color: theme.settingsTitleTextColor
+ }
+ MyTextField {
+ id: modelNameField
+ Layout.fillWidth: true
+ font.pixelSize: theme.fontSizeLarge
+ wrapMode: Text.WrapAnywhere
+ function showError() {
+ messageToast.show(qsTr("ERROR: $MODEL_NAME is empty."))
+ modelNameField.placeholderTextColor = theme.textErrorColor;
+ }
+ onTextChanged: {
+ modelNameField.placeholderTextColor = theme.mutedTextColor;
+ }
+ placeholderText: qsTr("enter $MODEL_NAME")
+ Accessible.role: Accessible.EditableText
+ Accessible.name: placeholderText
+ }
+ }
+
+ ColumnLayout {
+ visible: myModelList.count > 0 && !providerIsCustom
+
+ MySettingsLabel {
+ text: qsTr("Models")
+ font.bold: true
+ font.pixelSize: theme.fontSizeLarge
+ color: theme.settingsTitleTextColor
+ }
+
+ RowLayout {
+ spacing: 10
+
+ MyComboBox {
+ Layout.fillWidth: true
+ id: myModelList
+ currentIndex: -1;
+ }
+ }
+ }
+
+ MySettingsButton {
+ id: installButton
+ Layout.alignment: Qt.AlignRight
+ text: qsTr("Install")
+ font.pixelSize: theme.fontSizeLarge
+
+ property string apiKeyText: apiKeyField.text.trim()
+ property string baseUrlText: providerIsCustom ? baseUrlField.text.trim() : providerBaseUrl.trim()
+ property string modelNameText: providerIsCustom ? modelNameField.text.trim() : myModelList.currentText.trim()
+
+ enabled: apiKeyText !== "" && baseUrlText !== "" && modelNameText !== ""
+
+ onClicked: {
+ Download.installCompatibleModel(
+ modelNameText,
+ apiKeyText,
+ baseUrlText,
+ );
+ myModelList.currentIndex = -1;
+ }
+ Accessible.role: Accessible.Button
+ Accessible.name: qsTr("Install")
+ Accessible.description: qsTr("Install remote model")
+ }
+ }
+}
diff --git a/gpt4all-chat/src/modellist.cpp b/gpt4all-chat/src/modellist.cpp
index 6311e7e7..248f2f09 100644
--- a/gpt4all-chat/src/modellist.cpp
+++ b/gpt4all-chat/src/modellist.cpp
@@ -502,10 +502,11 @@ bool GPT4AllDownloadableModels::filterAcceptsRow(int sourceRow,
bool hasDescription = !description.isEmpty();
bool isClone = sourceModel()->data(index, ModelList::IsCloneRole).toBool();
bool isDiscovered = sourceModel()->data(index, ModelList::IsDiscoveredRole).toBool();
+ bool isOnline = sourceModel()->data(index, ModelList::OnlineRole).toBool();
bool satisfiesKeyword = m_keywords.isEmpty();
for (const QString &k : m_keywords)
satisfiesKeyword = description.contains(k) ? true : satisfiesKeyword;
- return !isDiscovered && hasDescription && !isClone && satisfiesKeyword;
+ return !isOnline && !isDiscovered && hasDescription && !isClone && satisfiesKeyword;
}
int GPT4AllDownloadableModels::count() const
@@ -2356,3 +2357,56 @@ void ModelList::handleDiscoveryItemErrorOccurred(QNetworkReply::NetworkError cod
qWarning() << u"ERROR: Discovery item failed with error code \"%1-%2\""_s
.arg(code).arg(reply->errorString()).toStdString();
}
+
+QStringList ModelList::remoteModelList(const QString &apiKey, const QUrl &baseUrl)
+{
+ QStringList modelList;
+
+ // Create the request
+ QNetworkRequest request;
+ request.setUrl(baseUrl.resolved(QUrl("models")));
+ request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
+
+ // Add the Authorization header
+ const QString bearerToken = QString("Bearer %1").arg(apiKey);
+ request.setRawHeader("Authorization", bearerToken.toUtf8());
+
+ // Make the GET request
+ QNetworkReply *reply = m_networkManager.get(request);
+
+ // We use a local event loop to wait for the request to complete
+ QEventLoop loop;
+ connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
+ loop.exec();
+
+ // Check for errors
+ if (reply->error() == QNetworkReply::NoError) {
+ // Parse the JSON response
+ const QByteArray responseData = reply->readAll();
+ const QJsonDocument jsonDoc = QJsonDocument::fromJson(responseData);
+
+ if (!jsonDoc.isNull() && jsonDoc.isObject()) {
+ QJsonObject rootObj = jsonDoc.object();
+ QJsonValue dataValue = rootObj.value("data");
+
+ if (dataValue.isArray()) {
+ QJsonArray dataArray = dataValue.toArray();
+ for (const QJsonValue &val : dataArray) {
+ if (val.isObject()) {
+ QJsonObject obj = val.toObject();
+ const QString modelId = obj.value("id").toString();
+ modelList.append(modelId);
+ }
+ }
+ }
+ }
+ } else {
+ // Handle network error (e.g. print it to qDebug)
+ qWarning() << "Error retrieving models:" << reply->errorString();
+ }
+
+ // Clean up
+ reply->deleteLater();
+
+ return modelList;
+}
diff --git a/gpt4all-chat/src/modellist.h b/gpt4all-chat/src/modellist.h
index 49eee8c4..756c6ffe 100644
--- a/gpt4all-chat/src/modellist.h
+++ b/gpt4all-chat/src/modellist.h
@@ -534,6 +534,8 @@ public:
Q_INVOKABLE void discoverSearch(const QString &discover);
+ Q_INVOKABLE QStringList remoteModelList(const QString &apiKey, const QUrl &baseUrl);
+
Q_SIGNALS:
void countChanged();
void installedModelsChanged();