mirror of
https://github.com/nomic-ai/gpt4all.git
synced 2025-06-01 20:06:29 +00:00
Add new remote model provider view. (#3506)
Signed-off-by: Adam Treat <treat.adam@gmail.com> Signed-off-by: AT <manyoso@users.noreply.github.com> Signed-off-by: Jared Van Bortel <jared@nomic.ai> Co-authored-by: Jared Van Bortel <jared@nomic.ai>
This commit is contained in:
parent
0c28ee7059
commit
4d171835ac
@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|||||||
### Added
|
### Added
|
||||||
- Whitelist Granite (non-MoE) model architecture (by [@ThiloteE](https://github.com/ThiloteE) in [#3487](https://github.com/nomic-ai/gpt4all/pull/3487))
|
- 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 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
|
### 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))
|
- 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))
|
||||||
|
@ -266,6 +266,7 @@ qt_add_qml_module(chat
|
|||||||
qml/AddModelView.qml
|
qml/AddModelView.qml
|
||||||
qml/AddGPT4AllModelView.qml
|
qml/AddGPT4AllModelView.qml
|
||||||
qml/AddHFModelView.qml
|
qml/AddHFModelView.qml
|
||||||
|
qml/AddRemoteModelView.qml
|
||||||
qml/ApplicationSettings.qml
|
qml/ApplicationSettings.qml
|
||||||
qml/ChatDrawer.qml
|
qml/ChatDrawer.qml
|
||||||
qml/ChatCollapsibleItem.qml
|
qml/ChatCollapsibleItem.qml
|
||||||
@ -314,6 +315,7 @@ qt_add_qml_module(chat
|
|||||||
qml/MyTextField.qml
|
qml/MyTextField.qml
|
||||||
qml/MyToolButton.qml
|
qml/MyToolButton.qml
|
||||||
qml/MyWelcomeButton.qml
|
qml/MyWelcomeButton.qml
|
||||||
|
qml/RemoteModelCard.qml
|
||||||
RESOURCES
|
RESOURCES
|
||||||
icons/antenna_1.svg
|
icons/antenna_1.svg
|
||||||
icons/antenna_2.svg
|
icons/antenna_2.svg
|
||||||
@ -344,6 +346,7 @@ qt_add_qml_module(chat
|
|||||||
icons/gpt4all-48.png
|
icons/gpt4all-48.png
|
||||||
icons/gpt4all.svg
|
icons/gpt4all.svg
|
||||||
icons/gpt4all_transparent.svg
|
icons/gpt4all_transparent.svg
|
||||||
|
icons/groq.svg
|
||||||
icons/home.svg
|
icons/home.svg
|
||||||
icons/image.svg
|
icons/image.svg
|
||||||
icons/info.svg
|
icons/info.svg
|
||||||
@ -351,12 +354,14 @@ qt_add_qml_module(chat
|
|||||||
icons/left_panel_open.svg
|
icons/left_panel_open.svg
|
||||||
icons/local-docs.svg
|
icons/local-docs.svg
|
||||||
icons/models.svg
|
icons/models.svg
|
||||||
|
icons/mistral.svg
|
||||||
icons/network.svg
|
icons/network.svg
|
||||||
icons/nomic_logo.svg
|
icons/nomic_logo.svg
|
||||||
icons/notes.svg
|
icons/notes.svg
|
||||||
icons/paperclip.svg
|
icons/paperclip.svg
|
||||||
icons/plus.svg
|
icons/plus.svg
|
||||||
icons/plus_circle.svg
|
icons/plus_circle.svg
|
||||||
|
icons/openai.svg
|
||||||
icons/recycle.svg
|
icons/recycle.svg
|
||||||
icons/regenerate.svg
|
icons/regenerate.svg
|
||||||
icons/search.svg
|
icons/search.svg
|
||||||
|
3
gpt4all-chat/icons/groq.svg
Normal file
3
gpt4all-chat/icons/groq.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26.3 26.3"><defs><style>.cls-1{fill:#f05237;}.cls-2{fill:#fff;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Content"><circle class="cls-1" cx="13.15" cy="13.15" r="13.15"/><path class="cls-2" d="M13.17,6.88a4.43,4.43,0,0,0,0,8.85h1.45V14.07H13.17a2.77,2.77,0,1,1,2.77-2.76v4.07a2.74,2.74,0,0,1-4.67,2L10.1,18.51a4.37,4.37,0,0,0,3.07,1.29h.06a4.42,4.42,0,0,0,4.36-4.4V11.2a4.43,4.43,0,0,0-4.42-4.32"/></g></g></svg>
|
After Width: | Height: | Size: 620 B |
1
gpt4all-chat/icons/mistral.svg
Normal file
1
gpt4all-chat/icons/mistral.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2"><path d="M189.08 303.228H94.587l.044-94.446h94.497l-.048 94.446z" fill="#1c1c1b" fill-rule="nonzero"/><path d="M283.528 397.674h-94.493l.044-94.446h94.496l-.047 94.446z" fill="#1c1c1b" fill-rule="nonzero"/><path d="M283.575 303.228H189.08l.046-94.446h94.496l-.047 94.446z" fill="#1c1c1b" fill-rule="nonzero"/><path d="M378.07 303.228h-94.495l.044-94.446h94.498l-.047 94.446zM189.128 208.779H94.633l.044-94.448h94.498l-.047 94.448zM378.115 208.779h-94.494l.045-94.448h94.496l-.047 94.448zM94.587 303.227H.093l.044-96.017h94.496l-.046 96.017z" fill="#1c1c1b" fill-rule="nonzero"/><path d="M94.633 208.779H.138l.046-94.448H94.68l-.047 94.448z" fill="#1c1c1b" fill-rule="nonzero"/><path d="M94.68 115.902H.185L.23 19.885h94.498l-.047 96.017zM472.657 114.331h-94.495l.044-94.446h94.497l-.046 94.446zM94.54 399.244H.046l.044-97.588h94.497l-.047 97.588z" fill="#1c1c1b" fill-rule="nonzero"/><path d="M94.495 492.123H0l.044-94.446H94.54l-.045 94.446zM472.563 303.228H378.07l.044-94.446h94.496l-.047 94.446zM472.61 208.779h-94.495l.044-94.448h94.498l-.047 94.448z" fill="#1c1c1b" fill-rule="nonzero"/><path d="M472.517 397.674h-94.494l.044-94.446h94.497l-.047 94.446z" fill="#1c1c1b" fill-rule="nonzero"/><path d="M472.47 492.121h-94.493l.044-96.017h94.496l-.047 96.017z" fill="#1c1c1b" fill-rule="nonzero"/><path d="M228.375 303.22h-96.061l.046-94.446h96.067l-.052 94.446z" fill="#ff7000" fill-rule="nonzero"/><path d="M322.827 397.666h-94.495l.044-96.018h94.498l-.047 96.018z" fill="#ff4900" fill-rule="nonzero"/><path d="M324.444 303.22h-97.636l.046-94.446h97.638l-.048 94.446z" fill="#ff7000" fill-rule="nonzero"/><path d="M418.938 303.22h-96.064l.045-94.446h96.066l-.047 94.446z" fill="#ff7000" fill-rule="nonzero"/><path d="M228.423 208.77H132.36l.045-94.445h96.066l-.05 94.446zM418.985 208.77H322.92l.044-94.445h96.069l-.048 94.446z" fill="#ffa300" fill-rule="nonzero"/><path d="M133.883 304.79H39.392l.044-96.017h94.496l-.049 96.017z" fill="#ff7000" fill-rule="nonzero"/><path d="M133.929 208.77H39.437l.044-95.445h94.496l-.048 95.445z" fill="#ffa300" fill-rule="nonzero"/><path d="M133.976 114.325H39.484l.044-94.448h94.497l-.05 94.448zM511.954 115.325h-94.493l.044-95.448h94.497l-.048 95.448z" fill="#ffce00" fill-rule="nonzero"/><path d="M133.836 399.667H39.345l.044-96.447h94.496l-.049 96.447z" fill="#ff4900" fill-rule="nonzero"/><path d="M133.79 492.117H39.3l.044-94.448h94.496l-.049 94.448z" fill="#ff0107" fill-rule="nonzero"/><path d="M511.862 303.22h-94.495l.046-94.446h94.496l-.047 94.446z" fill="#ff7000" fill-rule="nonzero"/><path d="M511.907 208.77h-94.493l.044-94.445h94.496l-.047 94.446z" fill="#ffa300" fill-rule="nonzero"/><path d="M511.815 398.666h-94.493l.044-95.447h94.496l-.047 95.447z" fill="#ff4900" fill-rule="nonzero"/><path d="M511.77 492.117h-94.496l.046-94.448h94.496l-.047 94.448z" fill="#ff0107" fill-rule="nonzero"/></svg>
|
After Width: | Height: | Size: 2.9 KiB |
2
gpt4all-chat/icons/openai.svg
Normal file
2
gpt4all-chat/icons/openai.svg
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" role="img" xmlns="http://www.w3.org/2000/svg"><title>OpenAI icon</title><path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"/></svg>
|
After Width: | Height: | Size: 1.7 KiB |
@ -204,7 +204,7 @@ ColumnLayout {
|
|||||||
Layout.minimumWidth: 200
|
Layout.minimumWidth: 200
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
|
||||||
visible: !isOnline && !installed && !calcHash && downloadError === ""
|
visible: !installed && !calcHash && downloadError === ""
|
||||||
Accessible.description: qsTr("Stop/restart/start the download")
|
Accessible.description: qsTr("Stop/restart/start the download")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!isDownloading) {
|
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 {
|
ColumnLayout {
|
||||||
spacing: 0
|
spacing: 0
|
||||||
Label {
|
Label {
|
||||||
@ -390,69 +344,6 @@ ColumnLayout {
|
|||||||
Accessible.description: qsTr("Displayed when the file hash is being calculated")
|
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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,6 +89,13 @@ Rectangle {
|
|||||||
gpt4AllModelView.show();
|
gpt4AllModelView.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
MyTabButton {
|
||||||
|
text: qsTr("Remote Providers")
|
||||||
|
isSelected: remoteModelView.isShown()
|
||||||
|
onPressed: {
|
||||||
|
remoteModelView.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
MyTabButton {
|
MyTabButton {
|
||||||
text: qsTr("HuggingFace")
|
text: qsTr("HuggingFace")
|
||||||
isSelected: huggingfaceModelView.isShown()
|
isSelected: huggingfaceModelView.isShown()
|
||||||
@ -112,7 +119,20 @@ Rectangle {
|
|||||||
stackLayout.currentIndex = 0;
|
stackLayout.currentIndex = 0;
|
||||||
}
|
}
|
||||||
function isShown() {
|
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
|
anchors.fill: parent
|
||||||
|
|
||||||
function show() {
|
function show() {
|
||||||
stackLayout.currentIndex = 1;
|
stackLayout.currentIndex = 2;
|
||||||
}
|
}
|
||||||
function isShown() {
|
function isShown() {
|
||||||
return stackLayout.currentIndex === 1
|
return stackLayout.currentIndex === 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
147
gpt4all-chat/qml/AddRemoteModelView.qml
Normal file
147
gpt4all-chat/qml/AddRemoteModelView.qml
Normal file
@ -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.<br><br>Get your API key: <a href="https://console.groq.com/keys">https://groq.com/</a>')
|
||||||
|
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.<br><br>Get your API key: <a href="https://platform.openai.com/signup">https://openai.com/</a>')
|
||||||
|
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.<br><br>Get your API key: <a href="https://mistral.ai/">https://mistral.ai/</a>')
|
||||||
|
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.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -60,27 +60,28 @@ ComboBox {
|
|||||||
highlighted: comboBox.highlightedIndex === index
|
highlighted: comboBox.highlightedIndex === index
|
||||||
}
|
}
|
||||||
popup: Popup {
|
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
|
y: comboBox.height - 1
|
||||||
width: comboBox.width
|
width: comboBox.width
|
||||||
implicitHeight: contentItem.implicitHeight + 20
|
implicitHeight: Math.min(window.height - y, contentItem.implicitHeight + 20)
|
||||||
padding: 0
|
padding: 0
|
||||||
|
|
||||||
contentItem: Rectangle {
|
contentItem: Rectangle {
|
||||||
implicitWidth: myListView.contentWidth
|
implicitWidth: comboBox.width
|
||||||
implicitHeight: myListView.contentHeight
|
implicitHeight: myListView.contentHeight
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
ListView {
|
radius: 10
|
||||||
id: myListView
|
ScrollView {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 10
|
anchors.margins: 10
|
||||||
clip: true
|
clip: true
|
||||||
implicitHeight: contentHeight
|
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||||
model: comboBox.popup.visible ? comboBox.delegateModel : null
|
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||||
currentIndex: comboBox.highlightedIndex
|
ListView {
|
||||||
ScrollIndicator.vertical: ScrollIndicator { }
|
id: myListView
|
||||||
|
implicitHeight: contentHeight
|
||||||
|
model: comboBox.popup.visible ? comboBox.delegateModel : null
|
||||||
|
currentIndex: comboBox.highlightedIndex
|
||||||
|
ScrollIndicator.vertical: ScrollIndicator { }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
221
gpt4all-chat/qml/RemoteModelCard.qml
Normal file
221
gpt4all-chat/qml/RemoteModelCard.qml
Normal file
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -502,10 +502,11 @@ bool GPT4AllDownloadableModels::filterAcceptsRow(int sourceRow,
|
|||||||
bool hasDescription = !description.isEmpty();
|
bool hasDescription = !description.isEmpty();
|
||||||
bool isClone = sourceModel()->data(index, ModelList::IsCloneRole).toBool();
|
bool isClone = sourceModel()->data(index, ModelList::IsCloneRole).toBool();
|
||||||
bool isDiscovered = sourceModel()->data(index, ModelList::IsDiscoveredRole).toBool();
|
bool isDiscovered = sourceModel()->data(index, ModelList::IsDiscoveredRole).toBool();
|
||||||
|
bool isOnline = sourceModel()->data(index, ModelList::OnlineRole).toBool();
|
||||||
bool satisfiesKeyword = m_keywords.isEmpty();
|
bool satisfiesKeyword = m_keywords.isEmpty();
|
||||||
for (const QString &k : m_keywords)
|
for (const QString &k : m_keywords)
|
||||||
satisfiesKeyword = description.contains(k) ? true : satisfiesKeyword;
|
satisfiesKeyword = description.contains(k) ? true : satisfiesKeyword;
|
||||||
return !isDiscovered && hasDescription && !isClone && satisfiesKeyword;
|
return !isOnline && !isDiscovered && hasDescription && !isClone && satisfiesKeyword;
|
||||||
}
|
}
|
||||||
|
|
||||||
int GPT4AllDownloadableModels::count() const
|
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
|
qWarning() << u"ERROR: Discovery item failed with error code \"%1-%2\""_s
|
||||||
.arg(code).arg(reply->errorString()).toStdString();
|
.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;
|
||||||
|
}
|
||||||
|
@ -534,6 +534,8 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE void discoverSearch(const QString &discover);
|
Q_INVOKABLE void discoverSearch(const QString &discover);
|
||||||
|
|
||||||
|
Q_INVOKABLE QStringList remoteModelList(const QString &apiKey, const QUrl &baseUrl);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void countChanged();
|
void countChanged();
|
||||||
void installedModelsChanged();
|
void installedModelsChanged();
|
||||||
|
Loading…
Reference in New Issue
Block a user