mirror of
https://github.com/nomic-ai/gpt4all.git
synced 2024-10-01 01:06:10 -04:00
WIP
This commit is contained in:
parent
f6c8c7cb90
commit
c13b33fb4d
@ -109,7 +109,7 @@ endif()
|
|||||||
qt_add_executable(chat
|
qt_add_executable(chat
|
||||||
main.cpp
|
main.cpp
|
||||||
chat.h chat.cpp
|
chat.h chat.cpp
|
||||||
llmodel.h
|
llmodel.h llmodel.cpp
|
||||||
llamacpp_model.h llamacpp_model.cpp
|
llamacpp_model.h llamacpp_model.cpp
|
||||||
chatmodel.h chatlistmodel.h chatlistmodel.cpp
|
chatmodel.h chatlistmodel.h chatlistmodel.cpp
|
||||||
chatapi.h chatapi.cpp
|
chatapi.h chatapi.cpp
|
||||||
|
@ -94,7 +94,7 @@ public:
|
|||||||
Q_INVOKABLE void reloadModel();
|
Q_INVOKABLE void reloadModel();
|
||||||
Q_INVOKABLE void forceUnloadModel();
|
Q_INVOKABLE void forceUnloadModel();
|
||||||
Q_INVOKABLE void forceReloadModel();
|
Q_INVOKABLE void forceReloadModel();
|
||||||
Q_INVOKABLE void trySwitchContextOfLoadedModel();
|
void trySwitchContextOfLoadedModel();
|
||||||
void unloadAndDeleteLater();
|
void unloadAndDeleteLater();
|
||||||
void markForDeletion();
|
void markForDeletion();
|
||||||
|
|
||||||
|
@ -263,16 +263,17 @@ void Download::installModel(const QString &modelFile, const QString &apiKey)
|
|||||||
QFile file(filePath);
|
QFile file(filePath);
|
||||||
if (file.open(QIODeviceBase::WriteOnly | QIODeviceBase::Text)) {
|
if (file.open(QIODeviceBase::WriteOnly | QIODeviceBase::Text)) {
|
||||||
|
|
||||||
QJsonObject obj;
|
|
||||||
QString modelName(modelFile);
|
QString modelName(modelFile);
|
||||||
modelName.remove(0, 8); // strip "gpt4all-" prefix
|
modelName.remove(0, 8); // strip "gpt4all-" prefix
|
||||||
modelName.chop(7); // strip ".rmodel" extension
|
modelName.chop(7); // strip ".rmodel" extension
|
||||||
obj.insert("apiKey", apiKey);
|
QJsonObject obj {
|
||||||
obj.insert("modelName", modelName);
|
{ "type", ... },
|
||||||
QJsonDocument doc(obj);
|
{ "apiKey", apiKey },
|
||||||
|
{ "modelName", modelName },
|
||||||
|
};
|
||||||
|
|
||||||
QTextStream stream(&file);
|
QTextStream stream(&file);
|
||||||
stream << doc.toJson();
|
stream << QJsonDocument(doc).toJson();
|
||||||
file.close();
|
file.close();
|
||||||
ModelList::globalInstance()->updateModelsFromDirectory();
|
ModelList::globalInstance()->updateModelsFromDirectory();
|
||||||
emit toastMessage(tr("Model \"%1\" is installed successfully.").arg(modelName));
|
emit toastMessage(tr("Model \"%1\" is installed successfully.").arg(modelName));
|
||||||
@ -312,14 +313,15 @@ void Download::installCompatibleModel(const QString &modelName, const QString &a
|
|||||||
QString filePath = MySettings::globalInstance()->modelPath() + modelFile;
|
QString filePath = MySettings::globalInstance()->modelPath() + modelFile;
|
||||||
QFile file(filePath);
|
QFile file(filePath);
|
||||||
if (file.open(QIODeviceBase::WriteOnly | QIODeviceBase::Text)) {
|
if (file.open(QIODeviceBase::WriteOnly | QIODeviceBase::Text)) {
|
||||||
QJsonObject obj;
|
QJsonObject obj {
|
||||||
obj.insert("apiKey", apiKey);
|
{ "type", "openai-generic" },
|
||||||
obj.insert("modelName", modelName);
|
{ "apiKey", apiKey },
|
||||||
obj.insert("baseUrl", apiBaseUrl.toString());
|
{ "modelName", modelName },
|
||||||
QJsonDocument doc(obj);
|
{ "baseUrl", apiBaseUrl.toString() },
|
||||||
|
};
|
||||||
|
|
||||||
QTextStream stream(&file);
|
QTextStream stream(&file);
|
||||||
stream << doc.toJson();
|
stream << QJsonDocument(obj).toJson();
|
||||||
file.close();
|
file.close();
|
||||||
ModelList::globalInstance()->updateModelsFromDirectory();
|
ModelList::globalInstance()->updateModelsFromDirectory();
|
||||||
emit toastMessage(tr("Model \"%1 (%2)\" is installed successfully.").arg(modelName, baseUrl));
|
emit toastMessage(tr("Model \"%1 (%2)\" is installed successfully.").arg(modelName, baseUrl));
|
||||||
@ -336,20 +338,26 @@ void Download::removeModel(const QString &modelFile)
|
|||||||
incompleteFile.remove();
|
incompleteFile.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool shouldRemoveInstalled = false;
|
bool removedFromList = false;
|
||||||
QFile file(filePath);
|
QFile file(filePath);
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
const ModelInfo info = ModelList::globalInstance()->modelInfoByFilename(modelFile);
|
const ModelInfo info = ModelList::globalInstance()->modelInfoByFilename(modelFile);
|
||||||
MySettings::globalInstance()->eraseModel(info);
|
MySettings::globalInstance()->eraseModel(info);
|
||||||
shouldRemoveInstalled = info.installed && !info.isClone() && (info.isDiscovered() || info.isCompatibleApi || info.description() == "" /*indicates sideloaded*/);
|
if (
|
||||||
if (shouldRemoveInstalled)
|
info.installed && !info.isClone() && (
|
||||||
|
info.isDiscovered() || info.description() == "" /*indicates sideloaded*/
|
||||||
|
|| info.provider == ModelInfo::Provider::OpenAIGeneric
|
||||||
|
)
|
||||||
|
) {
|
||||||
ModelList::globalInstance()->removeInstalled(info);
|
ModelList::globalInstance()->removeInstalled(info);
|
||||||
|
removedFromList = true;
|
||||||
|
}
|
||||||
Network::globalInstance()->trackEvent("remove_model", { {"model", modelFile} });
|
Network::globalInstance()->trackEvent("remove_model", { {"model", modelFile} });
|
||||||
file.remove();
|
file.remove();
|
||||||
emit toastMessage(tr("Model \"%1\" is removed.").arg(info.name()));
|
emit toastMessage(tr("Model \"%1\" is removed.").arg(info.name()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shouldRemoveInstalled) {
|
if (!removedFromList) {
|
||||||
QVector<QPair<int, QVariant>> data {
|
QVector<QPair<int, QVariant>> data {
|
||||||
{ ModelList::InstalledRole, false },
|
{ ModelList::InstalledRole, false },
|
||||||
{ ModelList::BytesReceivedRole, 0 },
|
{ ModelList::BytesReceivedRole, 0 },
|
||||||
|
@ -579,34 +579,6 @@ bool LlamaCppModel::isModelLoaded() const
|
|||||||
return m_llModelInfo.model && m_llModelInfo.model->isModelLoaded();
|
return m_llModelInfo.model && m_llModelInfo.model->isModelLoaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string remove_leading_whitespace(const std::string& input)
|
|
||||||
{
|
|
||||||
auto first_non_whitespace = std::find_if(input.begin(), input.end(), [](unsigned char c) {
|
|
||||||
return !std::isspace(c);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (first_non_whitespace == input.end())
|
|
||||||
return std::string();
|
|
||||||
|
|
||||||
return std::string(first_non_whitespace, input.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string trim_whitespace(const std::string& input)
|
|
||||||
{
|
|
||||||
auto first_non_whitespace = std::find_if(input.begin(), input.end(), [](unsigned char c) {
|
|
||||||
return !std::isspace(c);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (first_non_whitespace == input.end())
|
|
||||||
return std::string();
|
|
||||||
|
|
||||||
auto last_non_whitespace = std::find_if(input.rbegin(), input.rend(), [](unsigned char c) {
|
|
||||||
return !std::isspace(c);
|
|
||||||
}).base();
|
|
||||||
|
|
||||||
return std::string(first_non_whitespace, last_non_whitespace);
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME(jared): we don't actually have to re-decode the prompt to generate a new response
|
// FIXME(jared): we don't actually have to re-decode the prompt to generate a new response
|
||||||
void LlamaCppModel::regenerateResponse()
|
void LlamaCppModel::regenerateResponse()
|
||||||
{
|
{
|
||||||
|
34
gpt4all-chat/llmodel.cpp
Normal file
34
gpt4all-chat/llmodel.cpp
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#include "llmodel.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
|
||||||
|
std::string remove_leading_whitespace(const std::string &input)
|
||||||
|
{
|
||||||
|
auto first_non_whitespace = std::find_if(input.begin(), input.end(), [](unsigned char c) {
|
||||||
|
return !std::isspace(c);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (first_non_whitespace == input.end())
|
||||||
|
return std::string();
|
||||||
|
|
||||||
|
return std::string(first_non_whitespace, input.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string trim_whitespace(const std::string &input)
|
||||||
|
{
|
||||||
|
auto first_non_whitespace = std::find_if(input.begin(), input.end(), [](unsigned char c) {
|
||||||
|
return !std::isspace(c);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (first_non_whitespace == input.end())
|
||||||
|
return std::string();
|
||||||
|
|
||||||
|
auto last_non_whitespace = std::find_if(input.rbegin(), input.rend(), [](unsigned char c) {
|
||||||
|
return !std::isspace(c);
|
||||||
|
}).base();
|
||||||
|
|
||||||
|
return std::string(first_non_whitespace, last_non_whitespace);
|
||||||
|
}
|
@ -73,3 +73,6 @@ Q_SIGNALS:
|
|||||||
void databaseResultsChanged(const QList<ResultInfo> &results);
|
void databaseResultsChanged(const QList<ResultInfo> &results);
|
||||||
void modelInfoChanged(const ModelInfo &modelInfo);
|
void modelInfoChanged(const ModelInfo &modelInfo);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::string remove_leading_whitespace(const std::string &input);
|
||||||
|
std::string trim_whitespace(const std::string &input);
|
||||||
|
@ -36,6 +36,8 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
|
#include <optional>
|
||||||
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
@ -43,8 +45,33 @@ using namespace Qt::Literals::StringLiterals;
|
|||||||
|
|
||||||
//#define USE_LOCAL_MODELSJSON
|
//#define USE_LOCAL_MODELSJSON
|
||||||
|
|
||||||
|
|
||||||
static const QStringList FILENAME_BLACKLIST { u"gpt4all-nomic-embed-text-v1.rmodel"_s };
|
static const QStringList FILENAME_BLACKLIST { u"gpt4all-nomic-embed-text-v1.rmodel"_s };
|
||||||
|
|
||||||
|
// Maps "type" of current .rmodel format to a provider.
|
||||||
|
static const QHash<QString, ModelInfo::Provider> RMODEL_TYPES {
|
||||||
|
{ u"openai"_s, ModelInfo::Provider::OpenAI },
|
||||||
|
{ u"mistral"_s, ModelInfo::Provider::Mistral },
|
||||||
|
{ u"openai-generic"_s, ModelInfo::Provider::OpenAIGeneric },
|
||||||
|
};
|
||||||
|
|
||||||
|
// For backwards compatbility only. Do not add to this list.
|
||||||
|
static const QHash<QString, ModelInfo::Provider> BUILTIN_RMODEL_FILENAMES {
|
||||||
|
{ u"gpt4all-gpt-3.5-turbo.rmodel"_s, ModelInfo::Provider::OpenAI },
|
||||||
|
{ u"gpt4all-gpt-4.rmodel"_s, ModelInfo::Provider::OpenAI },
|
||||||
|
{ u"gpt4all-mistral-tiny.rmodel"_s, ModelInfo::Provider::Mistral },
|
||||||
|
{ u"gpt4all-mistral-small.rmodel"_s, ModelInfo::Provider::Mistral },
|
||||||
|
{ u"gpt4all-mistral-medium.rmodel"_s, ModelInfo::Provider::Mistral },
|
||||||
|
};
|
||||||
|
|
||||||
|
static ModelInfo::Provider getBuiltinRmodelFilename(const QString &filename)
|
||||||
|
{
|
||||||
|
auto provider = BUILTIN_RMODEL_FILENAMES.value(filename, ModelInfo::INVALID_PROVIDER);
|
||||||
|
if (provider == ModelInfo::INVALID_PROVIDER)
|
||||||
|
throw std::invalid_arugment("unrecognized rmodel filename: " + filename.toStdString());
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|
||||||
QString ModelInfo::id() const
|
QString ModelInfo::id() const
|
||||||
{
|
{
|
||||||
return m_id;
|
return m_id;
|
||||||
@ -694,6 +721,8 @@ int ModelList::rowCount(const QModelIndex &parent) const
|
|||||||
QVariant ModelList::dataInternal(const ModelInfo *info, int role) const
|
QVariant ModelList::dataInternal(const ModelInfo *info, int role) const
|
||||||
{
|
{
|
||||||
switch (role) {
|
switch (role) {
|
||||||
|
case ProviderRole:
|
||||||
|
return info->provider();
|
||||||
case IdRole:
|
case IdRole:
|
||||||
return info->id();
|
return info->id();
|
||||||
case NameRole:
|
case NameRole:
|
||||||
@ -714,10 +743,6 @@ QVariant ModelList::dataInternal(const ModelInfo *info, int role) const
|
|||||||
return info->installed;
|
return info->installed;
|
||||||
case DefaultRole:
|
case DefaultRole:
|
||||||
return info->isDefault;
|
return info->isDefault;
|
||||||
case OnlineRole:
|
|
||||||
return info->isOnline;
|
|
||||||
case CompatibleApiRole:
|
|
||||||
return info->isCompatibleApi;
|
|
||||||
case DescriptionRole:
|
case DescriptionRole:
|
||||||
return info->description();
|
return info->description();
|
||||||
case RequiresVersionRole:
|
case RequiresVersionRole:
|
||||||
@ -846,6 +871,8 @@ void ModelList::updateData(const QString &id, const QVector<QPair<int, QVariant>
|
|||||||
const int role = d.first;
|
const int role = d.first;
|
||||||
const QVariant value = d.second;
|
const QVariant value = d.second;
|
||||||
switch (role) {
|
switch (role) {
|
||||||
|
case ProviderRole:
|
||||||
|
info->m_provider = value.value<ModelInfo::Provider>();
|
||||||
case IdRole:
|
case IdRole:
|
||||||
{
|
{
|
||||||
if (info->id() != value.toString()) {
|
if (info->id() != value.toString()) {
|
||||||
@ -865,17 +892,13 @@ void ModelList::updateData(const QString &id, const QVector<QPair<int, QVariant>
|
|||||||
case HashRole:
|
case HashRole:
|
||||||
info->hash = value.toByteArray(); break;
|
info->hash = value.toByteArray(); break;
|
||||||
case HashAlgorithmRole:
|
case HashAlgorithmRole:
|
||||||
info->hashAlgorithm = static_cast<ModelInfo::HashAlgorithm>(value.toInt()); break;
|
info->hashAlgorithm = value.value<ModelInfo::HashAlgorithm>(); break;
|
||||||
case CalcHashRole:
|
case CalcHashRole:
|
||||||
info->calcHash = value.toBool(); break;
|
info->calcHash = value.toBool(); break;
|
||||||
case InstalledRole:
|
case InstalledRole:
|
||||||
info->installed = value.toBool(); break;
|
info->installed = value.toBool(); break;
|
||||||
case DefaultRole:
|
case DefaultRole:
|
||||||
info->isDefault = value.toBool(); break;
|
info->isDefault = value.toBool(); break;
|
||||||
case OnlineRole:
|
|
||||||
info->isOnline = value.toBool(); break;
|
|
||||||
case CompatibleApiRole:
|
|
||||||
info->isCompatibleApi = value.toBool(); break;
|
|
||||||
case DescriptionRole:
|
case DescriptionRole:
|
||||||
info->setDescription(value.toString()); break;
|
info->setDescription(value.toString()); break;
|
||||||
case RequiresVersionRole:
|
case RequiresVersionRole:
|
||||||
@ -1090,13 +1113,12 @@ QString ModelList::clone(const ModelInfo &model)
|
|||||||
addModel(id);
|
addModel(id);
|
||||||
|
|
||||||
QVector<QPair<int, QVariant>> data {
|
QVector<QPair<int, QVariant>> data {
|
||||||
|
{ ModelList::ProviderRole, model.provider },
|
||||||
{ ModelList::InstalledRole, model.installed },
|
{ ModelList::InstalledRole, model.installed },
|
||||||
{ ModelList::IsCloneRole, true },
|
{ ModelList::IsCloneRole, true },
|
||||||
{ ModelList::NameRole, uniqueModelName(model) },
|
{ ModelList::NameRole, uniqueModelName(model) },
|
||||||
{ ModelList::FilenameRole, model.filename() },
|
{ ModelList::FilenameRole, model.filename() },
|
||||||
{ ModelList::DirpathRole, model.dirpath },
|
{ ModelList::DirpathRole, model.dirpath },
|
||||||
{ ModelList::OnlineRole, model.isOnline },
|
|
||||||
{ ModelList::CompatibleApiRole, model.isCompatibleApi },
|
|
||||||
{ ModelList::IsEmbeddingModelRole, model.isEmbeddingModel },
|
{ ModelList::IsEmbeddingModelRole, model.isEmbeddingModel },
|
||||||
{ ModelList::TemperatureRole, model.temperature() },
|
{ ModelList::TemperatureRole, model.temperature() },
|
||||||
{ ModelList::TopPRole, model.topP() },
|
{ ModelList::TopPRole, model.topP() },
|
||||||
@ -1129,9 +1151,9 @@ void ModelList::removeClone(const ModelInfo &model)
|
|||||||
|
|
||||||
void ModelList::removeInstalled(const ModelInfo &model)
|
void ModelList::removeInstalled(const ModelInfo &model)
|
||||||
{
|
{
|
||||||
|
Q_ASSERT(model.provider == ModelInfo::Provider::LlamaCpp || model.provider == ModelInfo::Provider::OpenAIGeneric);
|
||||||
Q_ASSERT(model.installed);
|
Q_ASSERT(model.installed);
|
||||||
Q_ASSERT(!model.isClone());
|
Q_ASSERT(!model.isClone());
|
||||||
Q_ASSERT(model.isDiscovered() || model.isCompatibleApi || model.description() == "" /*indicates sideloaded*/);
|
|
||||||
removeInternal(model);
|
removeInternal(model);
|
||||||
emit layoutChanged();
|
emit layoutChanged();
|
||||||
}
|
}
|
||||||
@ -1212,53 +1234,144 @@ bool ModelList::modelExists(const QString &modelFilename) const
|
|||||||
|
|
||||||
static void updateOldRemoteModels(const QString &path)
|
static void updateOldRemoteModels(const QString &path)
|
||||||
{
|
{
|
||||||
QDirIterator it(path, QDirIterator::Subdirectories);
|
QDirIterator it(path, QDir::Files, QDirIterator::Subdirectories);
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
it.next();
|
QFileInfo info = it.nextFileInfo();
|
||||||
if (!it.fileInfo().isDir()) {
|
QString filename = info.fileName();
|
||||||
QString filename = it.fileName();
|
if (!filename.startsWith("chatgpt-") || !filename.endsWith(".txt"))
|
||||||
if (filename.startsWith("chatgpt-") && filename.endsWith(".txt")) {
|
continue;
|
||||||
|
|
||||||
QString apikey;
|
QString apikey;
|
||||||
QString modelname(filename);
|
QString modelname(filename);
|
||||||
modelname.chop(4); // strip ".txt" extension
|
modelname.chop(4); // strip ".txt" extension
|
||||||
modelname.remove(0, 8); // strip "chatgpt-" prefix
|
modelname.remove(0, 8); // strip "chatgpt-" prefix
|
||||||
QFile file(path + filename);
|
QFile file(info.filePath());
|
||||||
if (file.open(QIODevice::ReadWrite)) {
|
if (!file.open(QFile::ReadOnly)) {
|
||||||
|
qWarning(tr("cannot open \"%s\": %s"), info.filePath(), file.errorString());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
QTextStream in(&file);
|
QTextStream in(&file);
|
||||||
apikey = in.readAll();
|
apikey = in.readAll();
|
||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject obj;
|
QJsonObject obj {
|
||||||
obj.insert("apiKey", apikey);
|
{ "type", "openai" },
|
||||||
obj.insert("modelName", modelname);
|
{ "apiKey", apikey },
|
||||||
QJsonDocument doc(obj);
|
{ "modelName", modelname },
|
||||||
|
};
|
||||||
|
|
||||||
auto newfilename = u"gpt4all-%1.rmodel"_s.arg(modelname);
|
QFile newfile(u"%1/gpt4all-%2.rmodel"_s.arg(info.dir().path(), modelname));
|
||||||
QFile newfile(path + newfilename);
|
if (!newfile.open(QFile::ReadWrite)) {
|
||||||
if (newfile.open(QIODevice::ReadWrite)) {
|
qWarning(tr("cannot create \"%s\": %s"), newfile.fileName(), file.errorString());
|
||||||
QTextStream out(&newfile);
|
continue;
|
||||||
out << doc.toJson();
|
|
||||||
newfile.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QTextStream out(&newfile);
|
||||||
|
out << QJsonDocument(obj).toJson();
|
||||||
|
newfile.close();
|
||||||
file.remove();
|
file.remove();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
static bool parseRemoteModel(QVector<QPair<int, QVariant>> &props, const QFileInfo &info)
|
||||||
|
{
|
||||||
|
QJsonObject remoteModel;
|
||||||
|
{
|
||||||
|
QFile file(info.filePath());
|
||||||
|
if (!file.open(QFile::ReadOnly)) {
|
||||||
|
qWarning(tr("cannot open \"%s\": %s"), info.filePath(), file.errorString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
|
||||||
|
remoteModel = doc.object();
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelInfo::Provider provider;
|
||||||
|
QString remoteModelName, remoteApiKey;
|
||||||
|
{
|
||||||
|
const auto INVALID = ModelInfo::INVALID_PROVIDER;
|
||||||
|
|
||||||
|
std::optional<ModelInfo::Provider> providerJson;
|
||||||
|
if (auto type = remoteModel["type"]; type.type() != QJsonValue::Unknown)
|
||||||
|
providerJson.reset(RMODEL_TYPES.value(type, INVALID));
|
||||||
|
|
||||||
|
auto apiKey = remoteModel["apiKey"];
|
||||||
|
auto modelName = remoteModel["modelName"];
|
||||||
|
if (modelName.type() != QJsonValue::String || apiKey.type() != QJsonValue::String || providerJson == INVALID) {
|
||||||
|
qWarning(tr("bad rmodel \"%s\": unrecognized format"), info.filePath());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
remoteModelName = modelName.toString();
|
||||||
|
remoteApiKey = apiKey.toString();
|
||||||
|
|
||||||
|
if (providerJson) {
|
||||||
|
provider = providerJson.value();
|
||||||
|
} else if (auto builtin = BUILTIN_RMODEL_FILENAMES.value(filename, INVALID); builtin != INVALID) {
|
||||||
|
provider = builtin;
|
||||||
|
} else {
|
||||||
|
goto bad_data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString name;
|
||||||
|
QString description;
|
||||||
|
if (provider == ModelInfo::Provider::OpenAIGeneric) {
|
||||||
|
auto baseUrl = remoteModel["baseUrl"];
|
||||||
|
if (baseUrl.type() != QJsonValue::String)
|
||||||
|
goto bad_data;
|
||||||
|
|
||||||
|
QString apiKey = remoteApiKey;
|
||||||
|
apiKey = apiKey.length() < 10 ? "*****" : apiKey.left(5) + "*****";
|
||||||
|
QString baseUrl(remoteModel["baseUrl"].toString());
|
||||||
|
name = tr("%1 (%2)").arg(remoteModelName, baseUrl);
|
||||||
|
description = tr("<strong>OpenAI-Compatible API Model</strong><br>"
|
||||||
|
"<ul><li>API Key: %1</li>"
|
||||||
|
"<li>Base URL: %2</li>"
|
||||||
|
"<li>Model Name: %3</li></ul>")
|
||||||
|
.arg(apiKey, baseUrl, remoteModelName);
|
||||||
|
|
||||||
|
// The description is hard-coded into "GPT4All.ini" due to performance issue.
|
||||||
|
// If the description goes to be dynamic from its .rmodel file, it will get high I/O usage while using the ModelList.
|
||||||
|
props << QVector<QPair<int, QVariant>> {
|
||||||
|
{ NameRole, name },
|
||||||
|
{ DescriptionRole, description },
|
||||||
|
// Prompt template should be clear while using ChatML format which is using in most of OpenAI-Compatible API server.
|
||||||
|
{ PromptTemplateRole, "%1" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
props << QVector<QPair<int, QVariant>> {
|
||||||
|
{ ProviderRole, provider },
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
|
||||||
|
bad_data:
|
||||||
|
qWarning(tr("bad rmodel \"%s\": unrecognized data"), info.filePath());
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelList::processModelDirectory(const QString &path)
|
void ModelList::processModelDirectory(const QString &path)
|
||||||
{
|
{
|
||||||
QDirIterator it(path, QDir::Files, QDirIterator::Subdirectories);
|
QDirIterator it(path, QDir::Files, QDirIterator::Subdirectories);
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
it.next();
|
QFileInfo info = it.nextFileInfo();
|
||||||
|
|
||||||
QString filename = it.fileName();
|
QString filename = info.fileName();
|
||||||
if (filename.startsWith("incomplete") || FILENAME_BLACKLIST.contains(filename))
|
if (filename.startsWith("incomplete") || FILENAME_BLACKLIST.contains(filename))
|
||||||
continue;
|
continue;
|
||||||
if (!filename.endsWith(".gguf") && !filename.endsWith(".rmodel"))
|
if (!filename.endsWith(".gguf") && !filename.endsWith(".rmodel"))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
QVector<QPair<int, QVariant>> props;
|
||||||
|
if (!filename.endswith(".rmodel")) {
|
||||||
|
props.emplaceBack(ProviderRole, ModelInfo::Provider::LlamaCpp);
|
||||||
|
} else if (!parseRemoteModel(props, info))
|
||||||
|
continue;
|
||||||
|
|
||||||
QVector<QString> modelsById;
|
QVector<QString> modelsById;
|
||||||
{
|
{
|
||||||
QMutexLocker locker(&m_mutex);
|
QMutexLocker locker(&m_mutex);
|
||||||
@ -1273,56 +1386,15 @@ void ModelList::processModelDirectory(const QString &path)
|
|||||||
modelsById.append(filename);
|
modelsById.append(filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
QFileInfo info = it.fileInfo();
|
|
||||||
|
|
||||||
bool isOnline(filename.endsWith(".rmodel"));
|
|
||||||
bool isCompatibleApi(filename.endsWith("-capi.rmodel"));
|
|
||||||
|
|
||||||
QString name;
|
|
||||||
QString description;
|
|
||||||
if (isCompatibleApi) {
|
|
||||||
QJsonObject obj;
|
|
||||||
{
|
|
||||||
QFile file(path + filename);
|
|
||||||
bool success = file.open(QIODeviceBase::ReadOnly);
|
|
||||||
(void)success;
|
|
||||||
Q_ASSERT(success);
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
|
|
||||||
obj = doc.object();
|
|
||||||
}
|
|
||||||
{
|
|
||||||
QString apiKey(obj["apiKey"].toString());
|
|
||||||
QString baseUrl(obj["baseUrl"].toString());
|
|
||||||
QString modelName(obj["modelName"].toString());
|
|
||||||
apiKey = apiKey.length() < 10 ? "*****" : apiKey.left(5) + "*****";
|
|
||||||
name = tr("%1 (%2)").arg(modelName, baseUrl);
|
|
||||||
description = tr("<strong>OpenAI-Compatible API Model</strong><br>"
|
|
||||||
"<ul><li>API Key: %1</li>"
|
|
||||||
"<li>Base URL: %2</li>"
|
|
||||||
"<li>Model Name: %3</li></ul>")
|
|
||||||
.arg(apiKey, baseUrl, modelName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const QString &id : modelsById) {
|
for (const QString &id : modelsById) {
|
||||||
QVector<QPair<int, QVariant>> data {
|
props << QVector<QPair<int, QVariant>> {
|
||||||
|
{ ProviderRole, provider },
|
||||||
{ InstalledRole, true },
|
{ InstalledRole, true },
|
||||||
{ FilenameRole, filename },
|
{ FilenameRole, filename },
|
||||||
{ OnlineRole, isOnline },
|
|
||||||
{ CompatibleApiRole, isCompatibleApi },
|
|
||||||
{ DirpathRole, info.dir().absolutePath() + "/" },
|
{ DirpathRole, info.dir().absolutePath() + "/" },
|
||||||
{ FilesizeRole, info.size() },
|
{ FilesizeRole, info.size() },
|
||||||
};
|
};
|
||||||
if (isCompatibleApi) {
|
updateData(id, props);
|
||||||
// The data will be saved to "GPT4All.ini".
|
|
||||||
data.append({ NameRole, name });
|
|
||||||
// The description is hard-coded into "GPT4All.ini" due to performance issue.
|
|
||||||
// If the description goes to be dynamic from its .rmodel file, it will get high I/O usage while using the ModelList.
|
|
||||||
data.append({ DescriptionRole, description });
|
|
||||||
// Prompt template should be clear while using ChatML format which is using in most of OpenAI-Compatible API server.
|
|
||||||
data.append({ PromptTemplateRole, "%1" });
|
|
||||||
}
|
|
||||||
updateData(id, data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1571,6 +1643,7 @@ void ModelList::parseModelsJsonFile(const QByteArray &jsonData, bool save)
|
|||||||
if (!contains(id))
|
if (!contains(id))
|
||||||
addModel(id);
|
addModel(id);
|
||||||
QVector<QPair<int, QVariant>> data {
|
QVector<QPair<int, QVariant>> data {
|
||||||
|
{ ModelList::ProviderRole, BUILTIN_RMODEL_FILENAMES. },
|
||||||
{ ModelList::NameRole, modelName },
|
{ ModelList::NameRole, modelName },
|
||||||
{ ModelList::FilenameRole, modelFilename },
|
{ ModelList::FilenameRole, modelFilename },
|
||||||
{ ModelList::FilesizeRole, 0 },
|
{ ModelList::FilesizeRole, 0 },
|
||||||
@ -1599,6 +1672,7 @@ void ModelList::parseModelsJsonFile(const QByteArray &jsonData, bool save)
|
|||||||
if (!contains(id))
|
if (!contains(id))
|
||||||
addModel(id);
|
addModel(id);
|
||||||
QVector<QPair<int, QVariant>> data {
|
QVector<QPair<int, QVariant>> data {
|
||||||
|
{ ModelList::ProviderRole, getBuiltinRmodelFilename(modelFilename) },
|
||||||
{ ModelList::NameRole, modelName },
|
{ ModelList::NameRole, modelName },
|
||||||
{ ModelList::FilenameRole, modelFilename },
|
{ ModelList::FilenameRole, modelFilename },
|
||||||
{ ModelList::FilesizeRole, 0 },
|
{ ModelList::FilesizeRole, 0 },
|
||||||
@ -1630,6 +1704,7 @@ void ModelList::parseModelsJsonFile(const QByteArray &jsonData, bool save)
|
|||||||
if (!contains(id))
|
if (!contains(id))
|
||||||
addModel(id);
|
addModel(id);
|
||||||
QVector<QPair<int, QVariant>> data {
|
QVector<QPair<int, QVariant>> data {
|
||||||
|
{ ModelList::ProviderRole, getBuiltinRmodelFilename(modelFilename) },
|
||||||
{ ModelList::NameRole, modelName },
|
{ ModelList::NameRole, modelName },
|
||||||
{ ModelList::FilenameRole, modelFilename },
|
{ ModelList::FilenameRole, modelFilename },
|
||||||
{ ModelList::FilesizeRole, 0 },
|
{ ModelList::FilesizeRole, 0 },
|
||||||
@ -1655,6 +1730,7 @@ void ModelList::parseModelsJsonFile(const QByteArray &jsonData, bool save)
|
|||||||
if (!contains(id))
|
if (!contains(id))
|
||||||
addModel(id);
|
addModel(id);
|
||||||
QVector<QPair<int, QVariant>> data {
|
QVector<QPair<int, QVariant>> data {
|
||||||
|
{ ModelList::ProviderRole, getBuiltinRmodelFilename(modelFilename) },
|
||||||
{ ModelList::NameRole, modelName },
|
{ ModelList::NameRole, modelName },
|
||||||
{ ModelList::FilenameRole, modelFilename },
|
{ ModelList::FilenameRole, modelFilename },
|
||||||
{ ModelList::FilesizeRole, 0 },
|
{ ModelList::FilesizeRole, 0 },
|
||||||
@ -1681,10 +1757,10 @@ void ModelList::parseModelsJsonFile(const QByteArray &jsonData, bool save)
|
|||||||
if (!contains(id))
|
if (!contains(id))
|
||||||
addModel(id);
|
addModel(id);
|
||||||
QVector<QPair<int, QVariant>> data {
|
QVector<QPair<int, QVariant>> data {
|
||||||
|
{ ModelList::ProviderRole, getBuiltinRmodelFilename(modelFilename) },
|
||||||
{ ModelList::NameRole, modelName },
|
{ ModelList::NameRole, modelName },
|
||||||
{ ModelList::FilenameRole, modelFilename },
|
{ ModelList::FilenameRole, modelFilename },
|
||||||
{ ModelList::FilesizeRole, 0 },
|
{ ModelList::FilesizeRole, 0 },
|
||||||
{ ModelList::OnlineRole, true },
|
|
||||||
{ ModelList::DescriptionRole,
|
{ ModelList::DescriptionRole,
|
||||||
tr("<strong>Mistral Medium model</strong><br> %1").arg(mistralDesc) },
|
tr("<strong>Mistral Medium model</strong><br> %1").arg(mistralDesc) },
|
||||||
{ ModelList::RequiresVersionRole, "2.7.4" },
|
{ ModelList::RequiresVersionRole, "2.7.4" },
|
||||||
@ -1710,10 +1786,9 @@ void ModelList::parseModelsJsonFile(const QByteArray &jsonData, bool save)
|
|||||||
if (!contains(id))
|
if (!contains(id))
|
||||||
addModel(id);
|
addModel(id);
|
||||||
QVector<QPair<int, QVariant>> data {
|
QVector<QPair<int, QVariant>> data {
|
||||||
|
{ ModelList::ProviderRole, ModelInfo::Provider::OpenAIGeneric },
|
||||||
{ ModelList::NameRole, modelName },
|
{ ModelList::NameRole, modelName },
|
||||||
{ ModelList::FilesizeRole, 0 },
|
{ ModelList::FilesizeRole, 0 },
|
||||||
{ ModelList::OnlineRole, true },
|
|
||||||
{ ModelList::CompatibleApiRole, true },
|
|
||||||
{ ModelList::DescriptionRole,
|
{ ModelList::DescriptionRole,
|
||||||
tr("<strong>Connect to OpenAI-compatible API server</strong><br> %1").arg(compatibleDesc) },
|
tr("<strong>Connect to OpenAI-compatible API server</strong><br> %1").arg(compatibleDesc) },
|
||||||
{ ModelList::RequiresVersionRole, "2.7.4" },
|
{ ModelList::RequiresVersionRole, "2.7.4" },
|
||||||
|
@ -25,6 +25,7 @@ using namespace Qt::Literals::StringLiterals;
|
|||||||
|
|
||||||
struct ModelInfo {
|
struct ModelInfo {
|
||||||
Q_GADGET
|
Q_GADGET
|
||||||
|
Q_PROPERTY(Provider provider READ provider)
|
||||||
Q_PROPERTY(QString id READ id WRITE setId)
|
Q_PROPERTY(QString id READ id WRITE setId)
|
||||||
Q_PROPERTY(QString name READ name WRITE setName)
|
Q_PROPERTY(QString name READ name WRITE setName)
|
||||||
Q_PROPERTY(QString filename READ filename WRITE setFilename)
|
Q_PROPERTY(QString filename READ filename WRITE setFilename)
|
||||||
@ -35,8 +36,7 @@ struct ModelInfo {
|
|||||||
Q_PROPERTY(bool calcHash MEMBER calcHash)
|
Q_PROPERTY(bool calcHash MEMBER calcHash)
|
||||||
Q_PROPERTY(bool installed MEMBER installed)
|
Q_PROPERTY(bool installed MEMBER installed)
|
||||||
Q_PROPERTY(bool isDefault MEMBER isDefault)
|
Q_PROPERTY(bool isDefault MEMBER isDefault)
|
||||||
Q_PROPERTY(bool isOnline MEMBER isOnline)
|
Q_PROPERTY(bool isOnline READ isOnline)
|
||||||
Q_PROPERTY(bool isCompatibleApi MEMBER isCompatibleApi)
|
|
||||||
Q_PROPERTY(QString description READ description WRITE setDescription)
|
Q_PROPERTY(QString description READ description WRITE setDescription)
|
||||||
Q_PROPERTY(QString requiresVersion MEMBER requiresVersion)
|
Q_PROPERTY(QString requiresVersion MEMBER requiresVersion)
|
||||||
Q_PROPERTY(QString versionRemoved MEMBER versionRemoved)
|
Q_PROPERTY(QString versionRemoved MEMBER versionRemoved)
|
||||||
@ -77,10 +77,27 @@ struct ModelInfo {
|
|||||||
Q_PROPERTY(QDateTime recency READ recency WRITE setRecency)
|
Q_PROPERTY(QDateTime recency READ recency WRITE setRecency)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum HashAlgorithm {
|
enum class Provider {
|
||||||
|
LlamaCpp,
|
||||||
|
// Pre-configured model from openai.com or mistral.ai
|
||||||
|
OpenAI,
|
||||||
|
Mistral,
|
||||||
|
// Model with a custom endpoint configured by the user (stored in *-capi.rmodel)
|
||||||
|
OpenAIGeneric,
|
||||||
|
};
|
||||||
|
Q_ENUM(Provider)
|
||||||
|
|
||||||
|
// Not a valid member of the Provider enum. Used as a sentinel with Qt containers.
|
||||||
|
static constexpr Provider INVALID_PROVIDER = Provider(-1);
|
||||||
|
|
||||||
|
enum class HashAlgorithm {
|
||||||
Md5,
|
Md5,
|
||||||
Sha256
|
Sha256
|
||||||
};
|
};
|
||||||
|
Q_ENUM(HashAlgorithm)
|
||||||
|
|
||||||
|
Provider provider() const { return m_provider; }
|
||||||
|
bool isOnline() const { return m_provider != Provider::LlamaCpp; }
|
||||||
|
|
||||||
QString id() const;
|
QString id() const;
|
||||||
void setId(const QString &id);
|
void setId(const QString &id);
|
||||||
@ -109,8 +126,8 @@ public:
|
|||||||
QString description() const;
|
QString description() const;
|
||||||
void setDescription(const QString &d);
|
void setDescription(const QString &d);
|
||||||
|
|
||||||
/* For built-in OpenAI-compatible models (isOnline && !isCompatibleApi), this is the full completions endpoint URL.
|
/* For built-in OpenAI-compatible models, this is the full completions endpoint URL.
|
||||||
* For custom OpenAI-compatible models (isCompatibleApi), this is not set.
|
* For custom OpenAI-compatible models (Provider::OpenAIGeneric), this is not set.
|
||||||
* For discovered models (isDiscovered), this is the resolved URL of the GGUF file. */
|
* For discovered models (isDiscovered), this is the resolved URL of the GGUF file. */
|
||||||
QString url() const;
|
QString url() const;
|
||||||
void setUrl(const QString &u);
|
void setUrl(const QString &u);
|
||||||
@ -142,17 +159,6 @@ public:
|
|||||||
bool calcHash = false;
|
bool calcHash = false;
|
||||||
bool installed = false;
|
bool installed = false;
|
||||||
bool isDefault = false;
|
bool isDefault = false;
|
||||||
// Differences between 'isOnline' and 'isCompatibleApi' in ModelInfo:
|
|
||||||
// 'isOnline':
|
|
||||||
// - Indicates whether this is a online model.
|
|
||||||
// - Linked with the ModelList, fetching info from it.
|
|
||||||
bool isOnline = false;
|
|
||||||
// 'isCompatibleApi':
|
|
||||||
// - Indicates whether the model is using the OpenAI-compatible API which user custom.
|
|
||||||
// - When the property is true, 'isOnline' should also be true.
|
|
||||||
// - Does not link to the ModelList directly; instead, fetches info from the *-capi.rmodel file and works standalone.
|
|
||||||
// - Still needs to copy data from gpt4all.ini and *-capi.rmodel to the ModelList in memory while application getting started(as custom .gguf models do).
|
|
||||||
bool isCompatibleApi = false;
|
|
||||||
QString requiresVersion;
|
QString requiresVersion;
|
||||||
QString versionRemoved;
|
QString versionRemoved;
|
||||||
qint64 bytesReceived = 0;
|
qint64 bytesReceived = 0;
|
||||||
@ -206,6 +212,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
QVariantMap getFields() const;
|
QVariantMap getFields() const;
|
||||||
|
|
||||||
|
Provider m_provider;
|
||||||
QString m_id;
|
QString m_id;
|
||||||
QString m_name;
|
QString m_name;
|
||||||
QString m_filename;
|
QString m_filename;
|
||||||
@ -369,6 +376,7 @@ public:
|
|||||||
QHash<int, QByteArray> roleNames() const override
|
QHash<int, QByteArray> roleNames() const override
|
||||||
{
|
{
|
||||||
static const QHash<int, QByteArray> roles {
|
static const QHash<int, QByteArray> roles {
|
||||||
|
{ ProviderRole, "provider" },
|
||||||
{ IdRole, "id" },
|
{ IdRole, "id" },
|
||||||
{ NameRole, "name" },
|
{ NameRole, "name" },
|
||||||
{ FilenameRole, "filename" },
|
{ FilenameRole, "filename" },
|
||||||
@ -379,8 +387,6 @@ public:
|
|||||||
{ CalcHashRole, "calcHash" },
|
{ CalcHashRole, "calcHash" },
|
||||||
{ InstalledRole, "installed" },
|
{ InstalledRole, "installed" },
|
||||||
{ DefaultRole, "isDefault" },
|
{ DefaultRole, "isDefault" },
|
||||||
{ OnlineRole, "isOnline" },
|
|
||||||
{ CompatibleApiRole, "isCompatibleApi" },
|
|
||||||
{ DescriptionRole, "description" },
|
{ DescriptionRole, "description" },
|
||||||
{ RequiresVersionRole, "requiresVersion" },
|
{ RequiresVersionRole, "requiresVersion" },
|
||||||
{ VersionRemovedRole, "versionRemoved" },
|
{ VersionRemovedRole, "versionRemoved" },
|
||||||
@ -437,7 +443,8 @@ public:
|
|||||||
Q_INVOKABLE bool isUniqueName(const QString &name) const;
|
Q_INVOKABLE bool isUniqueName(const QString &name) const;
|
||||||
Q_INVOKABLE QString clone(const ModelInfo &model);
|
Q_INVOKABLE QString clone(const ModelInfo &model);
|
||||||
Q_INVOKABLE void removeClone(const ModelInfo &model);
|
Q_INVOKABLE void removeClone(const ModelInfo &model);
|
||||||
Q_INVOKABLE void removeInstalled(const ModelInfo &model);
|
// Delist a model that is about to be removed from the model dir
|
||||||
|
void removeInstalled(const ModelInfo &model);
|
||||||
ModelInfo defaultModelInfo() const;
|
ModelInfo defaultModelInfo() const;
|
||||||
|
|
||||||
void addModel(const QString &id);
|
void addModel(const QString &id);
|
||||||
|
Loading…
Reference in New Issue
Block a user