diff --git a/gpt4all-chat/CMakeLists.txt b/gpt4all-chat/CMakeLists.txt index bc510a0b..3784701f 100644 --- a/gpt4all-chat/CMakeLists.txt +++ b/gpt4all-chat/CMakeLists.txt @@ -73,7 +73,7 @@ qt_add_executable(chat chat.h chat.cpp chatllm.h chatllm.cpp chatmodel.h chatlistmodel.h chatlistmodel.cpp - chatgpt.h chatgpt.cpp + chatapi.h chatapi.cpp database.h database.cpp embeddings.h embeddings.cpp download.h download.cpp diff --git a/gpt4all-chat/chatgpt.cpp b/gpt4all-chat/chatapi.cpp similarity index 69% rename from gpt4all-chat/chatgpt.cpp rename to gpt4all-chat/chatapi.cpp index 21d427be..4d92101e 100644 --- a/gpt4all-chat/chatgpt.cpp +++ b/gpt4all-chat/chatapi.cpp @@ -1,4 +1,4 @@ -#include "chatgpt.h" +#include "chatapi.h" #include #include @@ -13,14 +13,15 @@ //#define DEBUG -ChatGPT::ChatGPT() +ChatAPI::ChatAPI() : QObject(nullptr) , m_modelName("gpt-3.5-turbo") + , m_requestURL("") , m_responseCallback(nullptr) { } -size_t ChatGPT::requiredMem(const std::string &modelPath, int n_ctx, int ngl) +size_t ChatAPI::requiredMem(const std::string &modelPath, int n_ctx, int ngl) { Q_UNUSED(modelPath); Q_UNUSED(n_ctx); @@ -28,7 +29,7 @@ size_t ChatGPT::requiredMem(const std::string &modelPath, int n_ctx, int ngl) return 0; } -bool ChatGPT::loadModel(const std::string &modelPath, int n_ctx, int ngl) +bool ChatAPI::loadModel(const std::string &modelPath, int n_ctx, int ngl) { Q_UNUSED(modelPath); Q_UNUSED(n_ctx); @@ -36,59 +37,59 @@ bool ChatGPT::loadModel(const std::string &modelPath, int n_ctx, int ngl) return true; } -void ChatGPT::setThreadCount(int32_t n_threads) +void ChatAPI::setThreadCount(int32_t n_threads) { Q_UNUSED(n_threads); qt_noop(); } -int32_t ChatGPT::threadCount() const +int32_t ChatAPI::threadCount() const { return 1; } -ChatGPT::~ChatGPT() +ChatAPI::~ChatAPI() { } -bool ChatGPT::isModelLoaded() const +bool ChatAPI::isModelLoaded() const { return true; } // All three of the state virtual functions are handled custom inside of chatllm save/restore -size_t ChatGPT::stateSize() const +size_t ChatAPI::stateSize() const { return 0; } -size_t ChatGPT::saveState(uint8_t *dest) const +size_t ChatAPI::saveState(uint8_t *dest) const { Q_UNUSED(dest); return 0; } -size_t ChatGPT::restoreState(const uint8_t *src) +size_t ChatAPI::restoreState(const uint8_t *src) { Q_UNUSED(src); return 0; } -void ChatGPT::prompt(const std::string &prompt, - const std::string &promptTemplate, - std::function promptCallback, - std::function responseCallback, - std::function recalculateCallback, - PromptContext &promptCtx, - bool special, - std::string *fakeReply) { +void ChatAPI::prompt(const std::string &prompt, + const std::string &promptTemplate, + std::function promptCallback, + std::function responseCallback, + std::function recalculateCallback, + PromptContext &promptCtx, + bool special, + std::string *fakeReply) { Q_UNUSED(promptCallback); Q_UNUSED(recalculateCallback); Q_UNUSED(special); if (!isModelLoaded()) { - std::cerr << "ChatGPT ERROR: prompt won't work with an unloaded model!\n"; + std::cerr << "ChatAPI ERROR: prompt won't work with an unloaded model!\n"; return; } @@ -128,7 +129,7 @@ void ChatGPT::prompt(const std::string &prompt, QJsonArray messages; for (int i = 0; i < m_context.count(); ++i) { QJsonObject message; - message.insert("role", i % 2 == 0 ? "assistant" : "user"); + message.insert("role", i % 2 == 0 ? "user" : "assistant"); message.insert("content", m_context.at(i)); messages.append(message); } @@ -142,7 +143,7 @@ void ChatGPT::prompt(const std::string &prompt, QJsonDocument doc(root); #if defined(DEBUG) - qDebug().noquote() << "ChatGPT::prompt begin network request" << doc.toJson(); + qDebug().noquote() << "ChatAPI::prompt begin network request" << doc.toJson(); #endif m_responseCallback = responseCallback; @@ -150,10 +151,10 @@ void ChatGPT::prompt(const std::string &prompt, // The following code sets up a worker thread and object to perform the actual api request to // chatgpt and then blocks until it is finished QThread workerThread; - ChatGPTWorker worker(this); + ChatAPIWorker worker(this); worker.moveToThread(&workerThread); - connect(&worker, &ChatGPTWorker::finished, &workerThread, &QThread::quit, Qt::DirectConnection); - connect(this, &ChatGPT::request, &worker, &ChatGPTWorker::request, Qt::QueuedConnection); + connect(&worker, &ChatAPIWorker::finished, &workerThread, &QThread::quit, Qt::DirectConnection); + connect(this, &ChatAPI::request, &worker, &ChatAPIWorker::request, Qt::QueuedConnection); workerThread.start(); emit request(m_apiKey, &promptCtx, doc.toJson(QJsonDocument::Compact)); workerThread.wait(); @@ -164,40 +165,40 @@ void ChatGPT::prompt(const std::string &prompt, m_responseCallback = nullptr; #if defined(DEBUG) - qDebug() << "ChatGPT::prompt end network request"; + qDebug() << "ChatAPI::prompt end network request"; #endif } -bool ChatGPT::callResponse(int32_t token, const std::string& string) +bool ChatAPI::callResponse(int32_t token, const std::string& string) { Q_ASSERT(m_responseCallback); if (!m_responseCallback) { - std::cerr << "ChatGPT ERROR: no response callback!\n"; + std::cerr << "ChatAPI ERROR: no response callback!\n"; return false; } return m_responseCallback(token, string); } -void ChatGPTWorker::request(const QString &apiKey, - LLModel::PromptContext *promptCtx, - const QByteArray &array) +void ChatAPIWorker::request(const QString &apiKey, + LLModel::PromptContext *promptCtx, + const QByteArray &array) { m_ctx = promptCtx; - QUrl openaiUrl("https://api.openai.com/v1/chat/completions"); + QUrl apiUrl(m_chat->url()); const QString authorization = QString("Bearer %1").arg(apiKey).trimmed(); - QNetworkRequest request(openaiUrl); + QNetworkRequest request(apiUrl); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); request.setRawHeader("Authorization", authorization.toUtf8()); m_networkManager = new QNetworkAccessManager(this); QNetworkReply *reply = m_networkManager->post(request, array); connect(qApp, &QCoreApplication::aboutToQuit, reply, &QNetworkReply::abort); - connect(reply, &QNetworkReply::finished, this, &ChatGPTWorker::handleFinished); - connect(reply, &QNetworkReply::readyRead, this, &ChatGPTWorker::handleReadyRead); - connect(reply, &QNetworkReply::errorOccurred, this, &ChatGPTWorker::handleErrorOccurred); + connect(reply, &QNetworkReply::finished, this, &ChatAPIWorker::handleFinished); + connect(reply, &QNetworkReply::readyRead, this, &ChatAPIWorker::handleReadyRead); + connect(reply, &QNetworkReply::errorOccurred, this, &ChatAPIWorker::handleErrorOccurred); } -void ChatGPTWorker::handleFinished() +void ChatAPIWorker::handleFinished() { QNetworkReply *reply = qobject_cast(sender()); if (!reply) { @@ -210,14 +211,14 @@ void ChatGPTWorker::handleFinished() bool ok; int code = response.toInt(&ok); if (!ok || code != 200) { - qWarning() << QString("ERROR: ChatGPT responded with error code \"%1-%2\"") - .arg(code).arg(reply->errorString()); + qWarning().noquote() << "ERROR: ChatAPIWorker::handleFinished got HTTP Error" << code << "response:" + << reply->errorString(); } reply->deleteLater(); emit finished(); } -void ChatGPTWorker::handleReadyRead() +void ChatAPIWorker::handleReadyRead() { QNetworkReply *reply = qobject_cast(sender()); if (!reply) { @@ -230,8 +231,11 @@ void ChatGPTWorker::handleReadyRead() bool ok; int code = response.toInt(&ok); if (!ok || code != 200) { - m_chat->callResponse(-1, QString("\nERROR: 2 ChatGPT responded with error code \"%1-%2\" %3\n") - .arg(code).arg(reply->errorString()).arg(reply->readAll()).toStdString()); + m_chat->callResponse( + -1, + QString("ERROR: ChatAPIWorker::handleReadyRead got HTTP Error %1 %2: %3") + .arg(code).arg(reply->errorString()).arg(reply->readAll()).toStdString() + ); emit finished(); return; } @@ -251,8 +255,8 @@ void ChatGPTWorker::handleReadyRead() QJsonParseError err; const QJsonDocument document = QJsonDocument::fromJson(jsonData.toUtf8(), &err); if (err.error != QJsonParseError::NoError) { - m_chat->callResponse(-1, QString("\nERROR: ChatGPT responded with invalid json \"%1\"\n") - .arg(err.errorString()).toStdString()); + m_chat->callResponse(-1, QString("ERROR: ChatAPI responded with invalid json \"%1\"") + .arg(err.errorString()).toStdString()); continue; } @@ -271,7 +275,7 @@ void ChatGPTWorker::handleReadyRead() } } -void ChatGPTWorker::handleErrorOccurred(QNetworkReply::NetworkError code) +void ChatAPIWorker::handleErrorOccurred(QNetworkReply::NetworkError code) { QNetworkReply *reply = qobject_cast(sender()); if (!reply || reply->error() == QNetworkReply::OperationCanceledError /*when we call abort on purpose*/) { @@ -279,7 +283,7 @@ void ChatGPTWorker::handleErrorOccurred(QNetworkReply::NetworkError code) return; } - qWarning() << QString("ERROR: ChatGPT responded with error code \"%1-%2\"") - .arg(code).arg(reply->errorString()); + qWarning().noquote() << "ERROR: ChatAPIWorker::handleErrorOccurred got HTTP Error" << code << "response:" + << reply->errorString(); emit finished(); } diff --git a/gpt4all-chat/chatgpt.h b/gpt4all-chat/chatapi.h similarity index 75% rename from gpt4all-chat/chatgpt.h rename to gpt4all-chat/chatapi.h index 07ceba58..d2f8dc0a 100644 --- a/gpt4all-chat/chatgpt.h +++ b/gpt4all-chat/chatapi.h @@ -1,5 +1,5 @@ -#ifndef CHATGPT_H -#define CHATGPT_H +#ifndef CHATAPI_H +#define CHATAPI_H #include @@ -13,22 +13,22 @@ #include "../gpt4all-backend/llmodel.h" -class ChatGPT; -class ChatGPTWorker : public QObject { +class ChatAPI; +class ChatAPIWorker : public QObject { Q_OBJECT public: - ChatGPTWorker(ChatGPT *chatGPT) + ChatAPIWorker(ChatAPI *chatAPI) : QObject(nullptr) , m_ctx(nullptr) , m_networkManager(nullptr) - , m_chat(chatGPT) {} - virtual ~ChatGPTWorker() {} + , m_chat(chatAPI) {} + virtual ~ChatAPIWorker() {} QString currentResponse() const { return m_currentResponse; } void request(const QString &apiKey, - LLModel::PromptContext *promptCtx, - const QByteArray &array); + LLModel::PromptContext *promptCtx, + const QByteArray &array); Q_SIGNALS: void finished(); @@ -39,17 +39,17 @@ private Q_SLOTS: void handleErrorOccurred(QNetworkReply::NetworkError code); private: - ChatGPT *m_chat; + ChatAPI *m_chat; LLModel::PromptContext *m_ctx; QNetworkAccessManager *m_networkManager; QString m_currentResponse; }; -class ChatGPT : public QObject, public LLModel { +class ChatAPI : public QObject, public LLModel { Q_OBJECT public: - ChatGPT(); - virtual ~ChatGPT(); + ChatAPI(); + virtual ~ChatAPI(); bool supportsEmbedding() const override { return false; } bool supportsCompletion() const override { return true; } @@ -60,19 +60,21 @@ public: size_t saveState(uint8_t *dest) const override; size_t restoreState(const uint8_t *src) override; void prompt(const std::string &prompt, - const std::string &promptTemplate, - std::function promptCallback, - std::function responseCallback, - std::function recalculateCallback, - PromptContext &ctx, - bool special, - std::string *fakeReply) override; + const std::string &promptTemplate, + std::function promptCallback, + std::function responseCallback, + std::function recalculateCallback, + PromptContext &ctx, + bool special, + std::string *fakeReply) override; void setThreadCount(int32_t n_threads) override; int32_t threadCount() const override; void setModelName(const QString &modelName) { m_modelName = modelName; } void setAPIKey(const QString &apiKey) { m_apiKey = apiKey; } + void setRequestURL(const QString &requestURL) { m_requestURL = requestURL; } + QString url() const { return m_requestURL; } QList context() const { return m_context; } void setContext(const QList &context) { m_context = context; } @@ -81,8 +83,8 @@ public: Q_SIGNALS: void request(const QString &apiKey, - LLModel::PromptContext *ctx, - const QByteArray &array); + LLModel::PromptContext *ctx, + const QByteArray &array); protected: // We have to implement these as they are pure virtual in base class, but we don't actually use @@ -128,8 +130,9 @@ private: std::function m_responseCallback; QString m_modelName; QString m_apiKey; + QString m_requestURL; QList m_context; QStringList m_queuedPrompts; }; -#endif // CHATGPT_H +#endif // CHATAPI_H diff --git a/gpt4all-chat/chatllm.cpp b/gpt4all-chat/chatllm.cpp index eba9d2f6..6d812953 100644 --- a/gpt4all-chat/chatllm.cpp +++ b/gpt4all-chat/chatllm.cpp @@ -1,6 +1,6 @@ #include "chatllm.h" #include "chat.h" -#include "chatgpt.h" +#include "chatapi.h" #include "localdocs.h" #include "modellist.h" #include "network.h" @@ -213,7 +213,6 @@ bool ChatLLM::loadModel(const ModelInfo &modelInfo) if (isModelLoaded() && this->modelInfo() == modelInfo) return true; - bool isChatGPT = modelInfo.isOnline; // right now only chatgpt is offered for online chat models... QString filePath = modelInfo.dirpath + modelInfo.filename(); QFileInfo fileInfo(filePath); @@ -279,19 +278,23 @@ bool ChatLLM::loadModel(const ModelInfo &modelInfo) m_llModelInfo.fileInfo = fileInfo; if (fileInfo.exists()) { - if (isChatGPT) { + if (modelInfo.isOnline) { QString apiKey; - QString chatGPTModel = fileInfo.completeBaseName().remove(0, 8); // remove the chatgpt- prefix + QString modelName; { QFile file(filePath); file.open(QIODeviceBase::ReadOnly | QIODeviceBase::Text); QTextStream stream(&file); - apiKey = stream.readAll(); - file.close(); + QString text = stream.readAll(); + QJsonDocument doc = QJsonDocument::fromJson(text.toUtf8()); + QJsonObject obj = doc.object(); + apiKey = obj["apiKey"].toString(); + modelName = obj["modelName"].toString(); } - m_llModelType = LLModelType::CHATGPT_; - ChatGPT *model = new ChatGPT(); - model->setModelName(chatGPTModel); + m_llModelType = LLModelType::API_; + ChatAPI *model = new ChatAPI(); + model->setModelName(modelName); + model->setRequestURL(modelInfo.url()); model->setAPIKey(apiKey); m_llModelInfo.model = model; } else { @@ -468,7 +471,7 @@ void ChatLLM::regenerateResponse() { // ChatGPT uses a different semantic meaning for n_past than local models. For ChatGPT, the meaning // of n_past is of the number of prompt/response pairs, rather than for total tokens. - if (m_llModelType == LLModelType::CHATGPT_) + if (m_llModelType == LLModelType::API_) m_ctx.n_past -= 1; else m_ctx.n_past -= m_promptResponseTokens; @@ -958,12 +961,12 @@ void ChatLLM::saveState() if (!isModelLoaded()) return; - if (m_llModelType == LLModelType::CHATGPT_) { + if (m_llModelType == LLModelType::API_) { m_state.clear(); QDataStream stream(&m_state, QIODeviceBase::WriteOnly); stream.setVersion(QDataStream::Qt_6_4); - ChatGPT *chatGPT = static_cast(m_llModelInfo.model); - stream << chatGPT->context(); + ChatAPI *chatAPI = static_cast(m_llModelInfo.model); + stream << chatAPI->context(); return; } @@ -980,13 +983,13 @@ void ChatLLM::restoreState() if (!isModelLoaded()) return; - if (m_llModelType == LLModelType::CHATGPT_) { + if (m_llModelType == LLModelType::API_) { QDataStream stream(&m_state, QIODeviceBase::ReadOnly); stream.setVersion(QDataStream::Qt_6_4); - ChatGPT *chatGPT = static_cast(m_llModelInfo.model); + ChatAPI *chatAPI = static_cast(m_llModelInfo.model); QList context; stream >> context; - chatGPT->setContext(context); + chatAPI->setContext(context); m_state.clear(); m_state.squeeze(); return; diff --git a/gpt4all-chat/chatllm.h b/gpt4all-chat/chatllm.h index d123aafb..899b7a2c 100644 --- a/gpt4all-chat/chatllm.h +++ b/gpt4all-chat/chatllm.h @@ -12,7 +12,7 @@ enum LLModelType { GPTJ_, LLAMA_, - CHATGPT_, + API_, }; struct LLModelInfo { diff --git a/gpt4all-chat/download.cpp b/gpt4all-chat/download.cpp index da23eaf2..70f3026d 100644 --- a/gpt4all-chat/download.cpp +++ b/gpt4all-chat/download.cpp @@ -182,8 +182,17 @@ void Download::installModel(const QString &modelFile, const QString &apiKey) QString filePath = MySettings::globalInstance()->modelPath() + modelFile; QFile file(filePath); if (file.open(QIODeviceBase::WriteOnly | QIODeviceBase::Text)) { + + QJsonObject obj; + QString modelName(modelFile); + modelName.remove(0, 8); // strip "gpt4all-" prefix + modelName.chop(7); // strip ".rmodel" extension + obj.insert("apiKey", apiKey); + obj.insert("modelName", modelName); + QJsonDocument doc(obj); + QTextStream stream(&file); - stream << apiKey; + stream << doc.toJson(); file.close(); ModelList::globalInstance()->updateModelsFromDirectory(); } diff --git a/gpt4all-chat/modellist.cpp b/gpt4all-chat/modellist.cpp index db21a4a5..80e7e411 100644 --- a/gpt4all-chat/modellist.cpp +++ b/gpt4all-chat/modellist.cpp @@ -1172,6 +1172,44 @@ void ModelList::updateModelsFromDirectory() const QString exePath = QCoreApplication::applicationDirPath() + QDir::separator(); const QString localPath = MySettings::globalInstance()->modelPath(); + auto updateOldRemoteModels = [&](const QString& path) { + QDirIterator it(path, QDirIterator::Subdirectories); + while (it.hasNext()) { + it.next(); + if (!it.fileInfo().isDir()) { + QString filename = it.fileName(); + if (filename.endsWith(".txt")) { + QString apikey; + QString modelname(filename); + modelname.chop(4); // strip ".txt" extension + if (filename.startsWith("chatgpt-")) { + modelname.remove(0, 8); // strip "chatgpt-" prefix + } + QFile file(path + filename); + if (file.open(QIODevice::ReadWrite)) { + QTextStream in(&file); + apikey = in.readAll(); + file.close(); + } + + QJsonObject obj; + obj.insert("apiKey", apikey); + obj.insert("modelName", modelname); + QJsonDocument doc(obj); + + auto newfilename = QString("gpt4all-%1.rmodel").arg(modelname); + QFile newfile(path + newfilename); + if (newfile.open(QIODevice::ReadWrite)) { + QTextStream out(&newfile); + out << doc.toJson(); + newfile.close(); + } + file.remove(); + } + } + } + }; + auto processDirectory = [&](const QString& path) { QDirIterator it(path, QDirIterator::Subdirectories); while (it.hasNext()) { @@ -1180,8 +1218,7 @@ void ModelList::updateModelsFromDirectory() if (!it.fileInfo().isDir()) { QString filename = it.fileName(); - if ((filename.endsWith(".gguf") && !filename.startsWith("incomplete")) - || (filename.endsWith(".txt") && (filename.startsWith("chatgpt-") || filename.startsWith("nomic-")))) { + if ((filename.endsWith(".gguf") && !filename.startsWith("incomplete")) || filename.endsWith(".rmodel")) { QString filePath = it.filePath(); QFileInfo info(filePath); @@ -1207,8 +1244,7 @@ void ModelList::updateModelsFromDirectory() QVector> data { { InstalledRole, true }, { FilenameRole, filename }, - // FIXME: WE should change this to use a consistent filename for online models - { OnlineRole, filename.startsWith("chatgpt-") || filename.startsWith("nomic-") }, + { OnlineRole, filename.endsWith(".rmodel") }, { DirpathRole, info.dir().absolutePath() + "/" }, { FilesizeRole, toFileSize(info.size()) }, }; @@ -1219,9 +1255,13 @@ void ModelList::updateModelsFromDirectory() } }; + + updateOldRemoteModels(exePath); processDirectory(exePath); - if (localPath != exePath) + if (localPath != exePath) { + updateOldRemoteModels(localPath); processDirectory(localPath); + } } #define MODELS_VERSION 3 @@ -1466,7 +1506,7 @@ void ModelList::parseModelsJsonFile(const QByteArray &jsonData, bool save) { const QString modelName = "ChatGPT-3.5 Turbo"; const QString id = modelName; - const QString modelFilename = "chatgpt-gpt-3.5-turbo.txt"; + const QString modelFilename = "gpt4all-gpt-3.5-turbo.rmodel"; if (contains(modelFilename)) changeId(modelFilename, id); if (!contains(id)) @@ -1478,12 +1518,13 @@ void ModelList::parseModelsJsonFile(const QByteArray &jsonData, bool save) { ModelList::OnlineRole, true }, { ModelList::DescriptionRole, tr("OpenAI's ChatGPT model GPT-3.5 Turbo
") + chatGPTDesc }, - { ModelList::RequiresVersionRole, "2.4.2" }, + { ModelList::RequiresVersionRole, "2.7.4" }, { ModelList::OrderRole, "ca" }, { ModelList::RamrequiredRole, 0 }, { ModelList::ParametersRole, "?" }, { ModelList::QuantRole, "NA" }, { ModelList::TypeRole, "GPT" }, + { ModelList::UrlRole, "https://api.openai.com/v1/chat/completions"}, }; updateData(id, data); } @@ -1493,7 +1534,7 @@ void ModelList::parseModelsJsonFile(const QByteArray &jsonData, bool save) const QString modelName = "ChatGPT-4"; const QString id = modelName; - const QString modelFilename = "chatgpt-gpt-4.txt"; + const QString modelFilename = "gpt4all-gpt-4.rmodel"; if (contains(modelFilename)) changeId(modelFilename, id); if (!contains(id)) @@ -1505,15 +1546,99 @@ void ModelList::parseModelsJsonFile(const QByteArray &jsonData, bool save) { ModelList::OnlineRole, true }, { ModelList::DescriptionRole, tr("OpenAI's ChatGPT model GPT-4
") + chatGPTDesc + chatGPT4Warn }, - { ModelList::RequiresVersionRole, "2.4.2" }, + { ModelList::RequiresVersionRole, "2.7.4" }, { ModelList::OrderRole, "cb" }, { ModelList::RamrequiredRole, 0 }, { ModelList::ParametersRole, "?" }, { ModelList::QuantRole, "NA" }, { ModelList::TypeRole, "GPT" }, + { ModelList::UrlRole, "https://api.openai.com/v1/chat/completions"}, }; updateData(id, data); } + + const QString mistralDesc = tr("
  • Requires personal Mistral API key.
  • WARNING: Will send" + " your chats to Mistral!
  • Your API key will be stored on disk
  • Will only be used" + " to communicate with Mistral
  • You can apply for an API key" + " here.
  • "); + + { + const QString modelName = "Mistral Tiny API"; + const QString id = modelName; + const QString modelFilename = "gpt4all-mistral-tiny.rmodel"; + if (contains(modelFilename)) + changeId(modelFilename, id); + if (!contains(id)) + addModel(id); + QVector> data { + { ModelList::NameRole, modelName }, + { ModelList::FilenameRole, modelFilename }, + { ModelList::FilesizeRole, "minimal" }, + { ModelList::OnlineRole, true }, + { ModelList::DescriptionRole, + tr("Mistral Tiny model
    ") + mistralDesc }, + { ModelList::RequiresVersionRole, "2.7.4" }, + { ModelList::OrderRole, "cc" }, + { ModelList::RamrequiredRole, 0 }, + { ModelList::ParametersRole, "?" }, + { ModelList::QuantRole, "NA" }, + { ModelList::TypeRole, "Mistral" }, + { ModelList::UrlRole, "https://api.mistral.ai/v1/chat/completions"}, + }; + updateData(id, data); + } + { + const QString modelName = "Mistral Small API"; + const QString id = modelName; + const QString modelFilename = "gpt4all-mistral-small.rmodel"; + if (contains(modelFilename)) + changeId(modelFilename, id); + if (!contains(id)) + addModel(id); + QVector> data { + { ModelList::NameRole, modelName }, + { ModelList::FilenameRole, modelFilename }, + { ModelList::FilesizeRole, "minimal" }, + { ModelList::OnlineRole, true }, + { ModelList::DescriptionRole, + tr("Mistral Small model
    ") + mistralDesc }, + { ModelList::RequiresVersionRole, "2.7.4" }, + { ModelList::OrderRole, "cd" }, + { ModelList::RamrequiredRole, 0 }, + { ModelList::ParametersRole, "?" }, + { ModelList::QuantRole, "NA" }, + { ModelList::TypeRole, "Mistral" }, + { ModelList::UrlRole, "https://api.mistral.ai/v1/chat/completions"}, + }; + updateData(id, data); + } + + { + const QString modelName = "Mistral Medium API"; + const QString id = modelName; + const QString modelFilename = "gpt4all-mistral-medium.rmodel"; + if (contains(modelFilename)) + changeId(modelFilename, id); + if (!contains(id)) + addModel(id); + QVector> data { + { ModelList::NameRole, modelName }, + { ModelList::FilenameRole, modelFilename }, + { ModelList::FilesizeRole, "minimal" }, + { ModelList::OnlineRole, true }, + { ModelList::DescriptionRole, + tr("Mistral Medium model
    ") + mistralDesc }, + { ModelList::RequiresVersionRole, "2.7.4" }, + { ModelList::OrderRole, "ce" }, + { ModelList::RamrequiredRole, 0 }, + { ModelList::ParametersRole, "?" }, + { ModelList::QuantRole, "NA" }, + { ModelList::TypeRole, "Mistral" }, + { ModelList::UrlRole, "https://api.mistral.ai/v1/chat/completions"}, + }; + updateData(id, data); + } + { const QString nomicEmbedDesc = tr("
    • For use with LocalDocs feature
    • "