gpt4all
1347 строк · 48.1 Кб
1#include "modellist.h"2#include "mysettings.h"3#include "network.h"4#include "../gpt4all-backend/llmodel.h"5
6#include <QFile>7#include <QStandardPaths>8#include <algorithm>9
10//#define USE_LOCAL_MODELSJSON
11
12#define DEFAULT_EMBEDDING_MODEL "all-MiniLM-L6-v2-f16.gguf"13#define NOMIC_EMBEDDING_MODEL "nomic-embed-text-v1.txt"14
15QString ModelInfo::id() const16{
17return m_id;18}
19
20void ModelInfo::setId(const QString &id)21{
22m_id = id;23}
24
25QString ModelInfo::name() const26{
27return MySettings::globalInstance()->modelName(*this);28}
29
30void ModelInfo::setName(const QString &name)31{
32if (isClone) MySettings::globalInstance()->setModelName(*this, name, isClone /*force*/);33m_name = name;34}
35
36QString ModelInfo::filename() const37{
38return MySettings::globalInstance()->modelFilename(*this);39}
40
41void ModelInfo::setFilename(const QString &filename)42{
43if (isClone) MySettings::globalInstance()->setModelFilename(*this, filename, isClone /*force*/);44m_filename = filename;45}
46
47double ModelInfo::temperature() const48{
49return MySettings::globalInstance()->modelTemperature(*this);50}
51
52void ModelInfo::setTemperature(double t)53{
54if (isClone) MySettings::globalInstance()->setModelTemperature(*this, t, isClone /*force*/);55m_temperature = t;56}
57
58double ModelInfo::topP() const59{
60return MySettings::globalInstance()->modelTopP(*this);61}
62
63double ModelInfo::minP() const64{
65return MySettings::globalInstance()->modelMinP(*this);66}
67
68void ModelInfo::setTopP(double p)69{
70if (isClone) MySettings::globalInstance()->setModelTopP(*this, p, isClone /*force*/);71m_topP = p;72}
73
74void ModelInfo::setMinP(double p)75{
76if (isClone) MySettings::globalInstance()->setModelMinP(*this, p, isClone /*force*/);77m_minP = p;78}
79
80int ModelInfo::topK() const81{
82return MySettings::globalInstance()->modelTopK(*this);83}
84
85void ModelInfo::setTopK(int k)86{
87if (isClone) MySettings::globalInstance()->setModelTopK(*this, k, isClone /*force*/);88m_topK = k;89}
90
91int ModelInfo::maxLength() const92{
93return MySettings::globalInstance()->modelMaxLength(*this);94}
95
96void ModelInfo::setMaxLength(int l)97{
98if (isClone) MySettings::globalInstance()->setModelMaxLength(*this, l, isClone /*force*/);99m_maxLength = l;100}
101
102int ModelInfo::promptBatchSize() const103{
104return MySettings::globalInstance()->modelPromptBatchSize(*this);105}
106
107void ModelInfo::setPromptBatchSize(int s)108{
109if (isClone) MySettings::globalInstance()->setModelPromptBatchSize(*this, s, isClone /*force*/);110m_promptBatchSize = s;111}
112
113int ModelInfo::contextLength() const114{
115return MySettings::globalInstance()->modelContextLength(*this);116}
117
118void ModelInfo::setContextLength(int l)119{
120if (isClone) MySettings::globalInstance()->setModelContextLength(*this, l, isClone /*force*/);121m_contextLength = l;122}
123
124int ModelInfo::maxContextLength() const125{
126if (m_maxContextLength != -1) return m_maxContextLength;127auto path = (dirpath + filename()).toStdString();128int layers = LLModel::Implementation::maxContextLength(path);129if (layers < 0) {130layers = 4096; // fallback value131}132m_maxContextLength = layers;133return m_maxContextLength;134}
135
136int ModelInfo::gpuLayers() const137{
138return MySettings::globalInstance()->modelGpuLayers(*this);139}
140
141void ModelInfo::setGpuLayers(int l)142{
143if (isClone) MySettings::globalInstance()->setModelGpuLayers(*this, l, isClone /*force*/);144m_gpuLayers = l;145}
146
147int ModelInfo::maxGpuLayers() const148{
149if (!installed || isOnline) return -1;150if (m_maxGpuLayers != -1) return m_maxGpuLayers;151auto path = (dirpath + filename()).toStdString();152int layers = LLModel::Implementation::layerCount(path);153if (layers < 0) {154layers = 100; // fallback value155}156m_maxGpuLayers = layers;157return m_maxGpuLayers;158}
159
160double ModelInfo::repeatPenalty() const161{
162return MySettings::globalInstance()->modelRepeatPenalty(*this);163}
164
165void ModelInfo::setRepeatPenalty(double p)166{
167if (isClone) MySettings::globalInstance()->setModelRepeatPenalty(*this, p, isClone /*force*/);168m_repeatPenalty = p;169}
170
171int ModelInfo::repeatPenaltyTokens() const172{
173return MySettings::globalInstance()->modelRepeatPenaltyTokens(*this);174}
175
176void ModelInfo::setRepeatPenaltyTokens(int t)177{
178if (isClone) MySettings::globalInstance()->setModelRepeatPenaltyTokens(*this, t, isClone /*force*/);179m_repeatPenaltyTokens = t;180}
181
182QString ModelInfo::promptTemplate() const183{
184return MySettings::globalInstance()->modelPromptTemplate(*this);185}
186
187void ModelInfo::setPromptTemplate(const QString &t)188{
189if (isClone) MySettings::globalInstance()->setModelPromptTemplate(*this, t, isClone /*force*/);190m_promptTemplate = t;191}
192
193QString ModelInfo::systemPrompt() const194{
195return MySettings::globalInstance()->modelSystemPrompt(*this);196}
197
198void ModelInfo::setSystemPrompt(const QString &p)199{
200if (isClone) MySettings::globalInstance()->setModelSystemPrompt(*this, p, isClone /*force*/);201m_systemPrompt = p;202}
203
204EmbeddingModels::EmbeddingModels(QObject *parent)205: QSortFilterProxyModel(parent)206{
207connect(this, &EmbeddingModels::rowsInserted, this, &EmbeddingModels::countChanged);208connect(this, &EmbeddingModels::rowsRemoved, this, &EmbeddingModels::countChanged);209connect(this, &EmbeddingModels::modelReset, this, &EmbeddingModels::countChanged);210connect(this, &EmbeddingModels::layoutChanged, this, &EmbeddingModels::countChanged);211}
212
213bool EmbeddingModels::filterAcceptsRow(int sourceRow,214const QModelIndex &sourceParent) const215{
216QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);217bool isInstalled = sourceModel()->data(index, ModelList::InstalledRole).toBool();218bool isEmbedding = sourceModel()->data(index, ModelList::FilenameRole).toString() == DEFAULT_EMBEDDING_MODEL ||219sourceModel()->data(index, ModelList::FilenameRole).toString() == NOMIC_EMBEDDING_MODEL;220return isInstalled && isEmbedding;221}
222
223int EmbeddingModels::count() const224{
225return rowCount();226}
227
228ModelInfo EmbeddingModels::defaultModelInfo() const229{
230if (!sourceModel())231return ModelInfo();232
233const ModelList *sourceListModel = qobject_cast<const ModelList*>(sourceModel());234if (!sourceListModel)235return ModelInfo();236
237const int rows = sourceListModel->rowCount();238for (int i = 0; i < rows; ++i) {239QModelIndex sourceIndex = sourceListModel->index(i, 0);240if (filterAcceptsRow(i, sourceIndex.parent())) {241const QString id = sourceListModel->data(sourceIndex, ModelList::IdRole).toString();242return sourceListModel->modelInfo(id);243}244}245
246return ModelInfo();247}
248
249InstalledModels::InstalledModels(QObject *parent)250: QSortFilterProxyModel(parent)251{
252connect(this, &InstalledModels::rowsInserted, this, &InstalledModels::countChanged);253connect(this, &InstalledModels::rowsRemoved, this, &InstalledModels::countChanged);254connect(this, &InstalledModels::modelReset, this, &InstalledModels::countChanged);255connect(this, &InstalledModels::layoutChanged, this, &InstalledModels::countChanged);256}
257
258bool InstalledModels::filterAcceptsRow(int sourceRow,259const QModelIndex &sourceParent) const260{
261QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);262bool isInstalled = sourceModel()->data(index, ModelList::InstalledRole).toBool();263bool showInGUI = !sourceModel()->data(index, ModelList::DisableGUIRole).toBool();264return isInstalled && showInGUI;265}
266
267int InstalledModels::count() const268{
269return rowCount();270}
271
272DownloadableModels::DownloadableModels(QObject *parent)273: QSortFilterProxyModel(parent)274, m_expanded(false)275, m_limit(5)276{
277connect(this, &DownloadableModels::rowsInserted, this, &DownloadableModels::countChanged);278connect(this, &DownloadableModels::rowsRemoved, this, &DownloadableModels::countChanged);279connect(this, &DownloadableModels::modelReset, this, &DownloadableModels::countChanged);280connect(this, &DownloadableModels::layoutChanged, this, &DownloadableModels::countChanged);281}
282
283bool DownloadableModels::filterAcceptsRow(int sourceRow,284const QModelIndex &sourceParent) const285{
286bool withinLimit = sourceRow < (m_expanded ? sourceModel()->rowCount() : m_limit);287QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);288bool isDownloadable = !sourceModel()->data(index, ModelList::DescriptionRole).toString().isEmpty();289return withinLimit && isDownloadable;290}
291
292int DownloadableModels::count() const293{
294return rowCount();295}
296
297bool DownloadableModels::isExpanded() const298{
299return m_expanded;300}
301
302void DownloadableModels::setExpanded(bool expanded)303{
304if (m_expanded != expanded) {305m_expanded = expanded;306invalidateFilter();307emit expandedChanged(m_expanded);308}309}
310
311class MyModelList: public ModelList { };312Q_GLOBAL_STATIC(MyModelList, modelListInstance)313ModelList *ModelList::globalInstance()314{
315return modelListInstance();316}
317
318ModelList::ModelList()319: QAbstractListModel(nullptr)320, m_embeddingModels(new EmbeddingModels(this))321, m_installedModels(new InstalledModels(this))322, m_downloadableModels(new DownloadableModels(this))323, m_asyncModelRequestOngoing(false)324{
325m_embeddingModels->setSourceModel(this);326m_installedModels->setSourceModel(this);327m_downloadableModels->setSourceModel(this);328
329connect(MySettings::globalInstance(), &MySettings::modelPathChanged, this, &ModelList::updateModelsFromDirectory);330connect(MySettings::globalInstance(), &MySettings::modelPathChanged, this, &ModelList::updateModelsFromJson);331connect(MySettings::globalInstance(), &MySettings::modelPathChanged, this, &ModelList::updateModelsFromSettings);332connect(MySettings::globalInstance(), &MySettings::nameChanged, this, &ModelList::updateDataForSettings);333connect(MySettings::globalInstance(), &MySettings::temperatureChanged, this, &ModelList::updateDataForSettings);334connect(MySettings::globalInstance(), &MySettings::topPChanged, this, &ModelList::updateDataForSettings);335connect(MySettings::globalInstance(), &MySettings::minPChanged, this, &ModelList::updateDataForSettings);336connect(MySettings::globalInstance(), &MySettings::topKChanged, this, &ModelList::updateDataForSettings);337connect(MySettings::globalInstance(), &MySettings::maxLengthChanged, this, &ModelList::updateDataForSettings);338connect(MySettings::globalInstance(), &MySettings::promptBatchSizeChanged, this, &ModelList::updateDataForSettings);339connect(MySettings::globalInstance(), &MySettings::contextLengthChanged, this, &ModelList::updateDataForSettings);340connect(MySettings::globalInstance(), &MySettings::gpuLayersChanged, this, &ModelList::updateDataForSettings);341connect(MySettings::globalInstance(), &MySettings::repeatPenaltyChanged, this, &ModelList::updateDataForSettings);342connect(MySettings::globalInstance(), &MySettings::repeatPenaltyTokensChanged, this, &ModelList::updateDataForSettings);;343connect(MySettings::globalInstance(), &MySettings::promptTemplateChanged, this, &ModelList::updateDataForSettings);344connect(MySettings::globalInstance(), &MySettings::systemPromptChanged, this, &ModelList::updateDataForSettings);345connect(&m_networkManager, &QNetworkAccessManager::sslErrors, this, &ModelList::handleSslErrors);346
347updateModelsFromJson();348updateModelsFromSettings();349updateModelsFromDirectory();350}
351
352QString ModelList::incompleteDownloadPath(const QString &modelFile)353{
354return MySettings::globalInstance()->modelPath() + "incomplete-" + modelFile;355}
356
357const QList<ModelInfo> ModelList::exportModelList() const358{
359QMutexLocker locker(&m_mutex);360QList<ModelInfo> infos;361for (ModelInfo *info : m_models)362if (info->installed)363infos.append(*info);364return infos;365}
366
367const QList<QString> ModelList::userDefaultModelList() const368{
369QMutexLocker locker(&m_mutex);370
371const QString userDefaultModelName = MySettings::globalInstance()->userDefaultModel();372QList<QString> models;373bool foundUserDefault = false;374for (ModelInfo *info : m_models) {375if (info->installed && info->id() == userDefaultModelName) {376foundUserDefault = true;377models.prepend(info->name());378} else if (info->installed) {379models.append(info->name());380}381}382
383const QString defaultId = "Application default";384if (foundUserDefault)385models.append(defaultId);386else387models.prepend(defaultId);388return models;389}
390
391int ModelList::defaultEmbeddingModelIndex() const392{
393QMutexLocker locker(&m_mutex);394for (int i = 0; i < m_models.size(); ++i) {395const ModelInfo *info = m_models.at(i);396const bool isEmbedding = info->filename() == DEFAULT_EMBEDDING_MODEL;397if (isEmbedding) return i;398}399return -1;400}
401
402ModelInfo ModelList::defaultModelInfo() const403{
404QMutexLocker locker(&m_mutex);405
406QSettings settings;407settings.sync();408
409// The user default model can be set by the user in the settings dialog. The "default" user410// default model is "Application default" which signals we should use the logic here.411const QString userDefaultModelName = MySettings::globalInstance()->userDefaultModel();412const bool hasUserDefaultName = !userDefaultModelName.isEmpty() && userDefaultModelName != "Application default";413
414ModelInfo *defaultModel = nullptr;415for (ModelInfo *info : m_models) {416if (!info->installed)417continue;418defaultModel = info;419
420const size_t ramrequired = defaultModel->ramrequired;421
422// If we don't have either setting, then just use the first model that requires less than 16GB that is installed423if (!hasUserDefaultName && !info->isOnline && ramrequired > 0 && ramrequired < 16)424break;425
426// If we have a user specified default and match, then use it427if (hasUserDefaultName && (defaultModel->id() == userDefaultModelName))428break;429}430if (defaultModel)431return *defaultModel;432return ModelInfo();433}
434
435bool ModelList::contains(const QString &id) const436{
437QMutexLocker locker(&m_mutex);438return m_modelMap.contains(id);439}
440
441bool ModelList::containsByFilename(const QString &filename) const442{
443QMutexLocker locker(&m_mutex);444for (ModelInfo *info : m_models)445if (info->filename() == filename)446return true;447return false;448}
449
450bool ModelList::lessThan(const ModelInfo* a, const ModelInfo* b)451{
452// Rule 0: Non-clone before clone453if (a->isClone != b->isClone) {454return !a->isClone;455}456
457// Rule 1: Non-empty 'order' before empty458if (a->order.isEmpty() != b->order.isEmpty()) {459return !a->order.isEmpty();460}461
462// Rule 2: Both 'order' are non-empty, sort alphanumerically463if (!a->order.isEmpty() && !b->order.isEmpty()) {464return a->order < b->order;465}466
467// Rule 3: Both 'order' are empty, sort by id468return a->id() < b->id();469}
470
471void ModelList::addModel(const QString &id)472{
473const bool hasModel = contains(id);474Q_ASSERT(!hasModel);475if (hasModel) {476qWarning() << "ERROR: model list already contains" << id;477return;478}479
480int modelSizeBefore = 0;481int modelSizeAfter = 0;482{483QMutexLocker locker(&m_mutex);484modelSizeBefore = m_models.size();485}486beginInsertRows(QModelIndex(), modelSizeBefore, modelSizeBefore);487{488QMutexLocker locker(&m_mutex);489ModelInfo *info = new ModelInfo;490info->setId(id);491m_models.append(info);492m_modelMap.insert(id, info);493std::stable_sort(m_models.begin(), m_models.end(), ModelList::lessThan);494modelSizeAfter = m_models.size();495}496endInsertRows();497emit dataChanged(index(0, 0), index(modelSizeAfter - 1, 0));498emit userDefaultModelListChanged();499}
500
501void ModelList::changeId(const QString &oldId, const QString &newId)502{
503const bool hasModel = contains(oldId);504Q_ASSERT(hasModel);505if (!hasModel) {506qWarning() << "ERROR: model list does not contain" << oldId;507return;508}509
510QMutexLocker locker(&m_mutex);511ModelInfo *info = m_modelMap.take(oldId);512info->setId(newId);513m_modelMap.insert(newId, info);514}
515
516int ModelList::rowCount(const QModelIndex &parent) const517{
518Q_UNUSED(parent)519QMutexLocker locker(&m_mutex);520return m_models.size();521}
522
523QVariant ModelList::dataInternal(const ModelInfo *info, int role) const524{
525switch (role) {526case IdRole:527return info->id();528case NameRole:529return info->name();530case FilenameRole:531return info->filename();532case DirpathRole:533return info->dirpath;534case FilesizeRole:535return info->filesize;536case Md5sumRole:537return info->md5sum;538case CalcHashRole:539return info->calcHash;540case InstalledRole:541return info->installed;542case DefaultRole:543return info->isDefault;544case OnlineRole:545return info->isOnline;546case DisableGUIRole:547return info->disableGUI;548case DescriptionRole:549return info->description;550case RequiresVersionRole:551return info->requiresVersion;552case DeprecatedVersionRole:553return info->deprecatedVersion;554case UrlRole:555return info->url;556case BytesReceivedRole:557return info->bytesReceived;558case BytesTotalRole:559return info->bytesTotal;560case TimestampRole:561return info->timestamp;562case SpeedRole:563return info->speed;564case DownloadingRole:565return info->isDownloading;566case IncompleteRole:567return info->isIncomplete;568case DownloadErrorRole:569return info->downloadError;570case OrderRole:571return info->order;572case RamrequiredRole:573return info->ramrequired;574case ParametersRole:575return info->parameters;576case QuantRole:577return info->quant;578case TypeRole:579return info->type;580case IsCloneRole:581return info->isClone;582case TemperatureRole:583return info->temperature();584case TopPRole:585return info->topP();586case MinPRole:587return info->minP();588case TopKRole:589return info->topK();590case MaxLengthRole:591return info->maxLength();592case PromptBatchSizeRole:593return info->promptBatchSize();594case ContextLengthRole:595return info->contextLength();596case GpuLayersRole:597return info->gpuLayers();598case RepeatPenaltyRole:599return info->repeatPenalty();600case RepeatPenaltyTokensRole:601return info->repeatPenaltyTokens();602case PromptTemplateRole:603return info->promptTemplate();604case SystemPromptRole:605return info->systemPrompt();606}607
608return QVariant();609}
610
611QVariant ModelList::data(const QString &id, int role) const612{
613QMutexLocker locker(&m_mutex);614ModelInfo *info = m_modelMap.value(id);615return dataInternal(info, role);616}
617
618QVariant ModelList::dataByFilename(const QString &filename, int role) const619{
620QMutexLocker locker(&m_mutex);621for (ModelInfo *info : m_models)622if (info->filename() == filename)623return dataInternal(info, role);624return QVariant();625}
626
627QVariant ModelList::data(const QModelIndex &index, int role) const628{
629QMutexLocker locker(&m_mutex);630if (!index.isValid() || index.row() < 0 || index.row() >= m_models.size())631return QVariant();632const ModelInfo *info = m_models.at(index.row());633return dataInternal(info, role);634}
635
636void ModelList::updateData(const QString &id, int role, const QVariant &value)637{
638int modelSize;639bool updateInstalled;640bool updateIncomplete;641int index;642{643QMutexLocker locker(&m_mutex);644if (!m_modelMap.contains(id)) {645qWarning() << "ERROR: cannot update as model map does not contain" << id;646return;647}648
649ModelInfo *info = m_modelMap.value(id);650index = m_models.indexOf(info);651if (index == -1) {652qWarning() << "ERROR: cannot update as model list does not contain" << id;653return;654}655
656switch (role) {657case IdRole:658info->setId(value.toString()); break;659case NameRole:660info->setName(value.toString()); break;661case FilenameRole:662info->setFilename(value.toString()); break;663case DirpathRole:664info->dirpath = value.toString(); break;665case FilesizeRole:666info->filesize = value.toString(); break;667case Md5sumRole:668info->md5sum = value.toByteArray(); break;669case CalcHashRole:670info->calcHash = value.toBool(); break;671case InstalledRole:672info->installed = value.toBool(); break;673case DefaultRole:674info->isDefault = value.toBool(); break;675case OnlineRole:676info->isOnline = value.toBool(); break;677case DisableGUIRole:678info->disableGUI = value.toBool(); break;679case DescriptionRole:680info->description = value.toString(); break;681case RequiresVersionRole:682info->requiresVersion = value.toString(); break;683case DeprecatedVersionRole:684info->deprecatedVersion = value.toString(); break;685case UrlRole:686info->url = value.toString(); break;687case BytesReceivedRole:688info->bytesReceived = value.toLongLong(); break;689case BytesTotalRole:690info->bytesTotal = value.toLongLong(); break;691case TimestampRole:692info->timestamp = value.toLongLong(); break;693case SpeedRole:694info->speed = value.toString(); break;695case DownloadingRole:696info->isDownloading = value.toBool(); break;697case IncompleteRole:698info->isIncomplete = value.toBool(); break;699case DownloadErrorRole:700info->downloadError = value.toString(); break;701case OrderRole:702info->order = value.toString(); break;703case RamrequiredRole:704info->ramrequired = value.toInt(); break;705case ParametersRole:706info->parameters = value.toString(); break;707case QuantRole:708info->quant = value.toString(); break;709case TypeRole:710info->type = value.toString(); break;711case IsCloneRole:712info->isClone = value.toBool(); break;713case TemperatureRole:714info->setTemperature(value.toDouble()); break;715case TopPRole:716info->setTopP(value.toDouble()); break;717case MinPRole:718info->setMinP(value.toDouble()); break;719case TopKRole:720info->setTopK(value.toInt()); break;721case MaxLengthRole:722info->setMaxLength(value.toInt()); break;723case PromptBatchSizeRole:724info->setPromptBatchSize(value.toInt()); break;725case ContextLengthRole:726info->setContextLength(value.toInt()); break;727case GpuLayersRole:728info->setGpuLayers(value.toInt()); break;729case RepeatPenaltyRole:730info->setRepeatPenalty(value.toDouble()); break;731case RepeatPenaltyTokensRole:732info->setRepeatPenaltyTokens(value.toInt()); break;733case PromptTemplateRole:734info->setPromptTemplate(value.toString()); break;735case SystemPromptRole:736info->setSystemPrompt(value.toString()); break;737}738
739// Extra guarantee that these always remains in sync with filesystem740QFileInfo fileInfo(info->dirpath + info->filename());741if (info->installed != fileInfo.exists()) {742info->installed = fileInfo.exists();743updateInstalled = true;744}745QFileInfo incompleteInfo(incompleteDownloadPath(info->filename()));746if (info->isIncomplete != incompleteInfo.exists()) {747info->isIncomplete = incompleteInfo.exists();748updateIncomplete = true;749}750
751std::stable_sort(m_models.begin(), m_models.end(), ModelList::lessThan);752modelSize = m_models.size();753}754emit dataChanged(createIndex(0, 0), createIndex(modelSize - 1, 0));755emit userDefaultModelListChanged();756}
757
758void ModelList::updateDataByFilename(const QString &filename, int role, const QVariant &value)759{
760QVector<QString> modelsById;761{762QMutexLocker locker(&m_mutex);763for (ModelInfo *info : m_models)764if (info->filename() == filename)765modelsById.append(info->id());766}767
768if (modelsById.isEmpty()) {769qWarning() << "ERROR: cannot update model as list does not contain file" << filename;770return;771}772
773for (const QString &id : modelsById)774updateData(id, role, value);;775}
776
777ModelInfo ModelList::modelInfo(const QString &id) const778{
779QMutexLocker locker(&m_mutex);780if (!m_modelMap.contains(id))781return ModelInfo();782return *m_modelMap.value(id);783}
784
785ModelInfo ModelList::modelInfoByFilename(const QString &filename) const786{
787QMutexLocker locker(&m_mutex);788for (ModelInfo *info : m_models)789if (info->filename() == filename)790return *info;791return ModelInfo();792}
793
794bool ModelList::isUniqueName(const QString &name) const795{
796QMutexLocker locker(&m_mutex);797for (const ModelInfo *info : m_models) {798if(info->name() == name)799return false;800}801return true;802}
803
804QString ModelList::clone(const ModelInfo &model)805{
806const QString id = Network::globalInstance()->generateUniqueId();807addModel(id);808updateData(id, ModelList::IsCloneRole, true);809updateData(id, ModelList::NameRole, uniqueModelName(model));810updateData(id, ModelList::FilenameRole, model.filename());811updateData(id, ModelList::DirpathRole, model.dirpath);812updateData(id, ModelList::InstalledRole, model.installed);813updateData(id, ModelList::OnlineRole, model.isOnline);814updateData(id, ModelList::TemperatureRole, model.temperature());815updateData(id, ModelList::TopPRole, model.topP());816updateData(id, ModelList::MinPRole, model.minP());817updateData(id, ModelList::TopKRole, model.topK());818updateData(id, ModelList::MaxLengthRole, model.maxLength());819updateData(id, ModelList::PromptBatchSizeRole, model.promptBatchSize());820updateData(id, ModelList::ContextLengthRole, model.contextLength());821updateData(id, ModelList::GpuLayersRole, model.gpuLayers());822updateData(id, ModelList::RepeatPenaltyRole, model.repeatPenalty());823updateData(id, ModelList::RepeatPenaltyTokensRole, model.repeatPenaltyTokens());824updateData(id, ModelList::PromptTemplateRole, model.promptTemplate());825updateData(id, ModelList::SystemPromptRole, model.systemPrompt());826return id;827}
828
829void ModelList::remove(const ModelInfo &model)830{
831Q_ASSERT(model.isClone);832if (!model.isClone)833return;834
835const bool hasModel = contains(model.id());836Q_ASSERT(hasModel);837if (!hasModel) {838qWarning() << "ERROR: model list does not contain" << model.id();839return;840}841
842int indexOfModel = 0;843int modelSizeAfter = 0;844{845QMutexLocker locker(&m_mutex);846ModelInfo *info = m_modelMap.value(model.id());847indexOfModel = m_models.indexOf(info);848}849beginRemoveRows(QModelIndex(), indexOfModel, indexOfModel);850{851QMutexLocker locker(&m_mutex);852ModelInfo *info = m_models.takeAt(indexOfModel);853m_modelMap.remove(info->id());854delete info;855modelSizeAfter = m_models.size();856}857endRemoveRows();858emit dataChanged(index(0, 0), index(modelSizeAfter - 1, 0));859emit userDefaultModelListChanged();860MySettings::globalInstance()->eraseModel(model);861}
862
863QString ModelList::uniqueModelName(const ModelInfo &model) const864{
865QMutexLocker locker(&m_mutex);866QRegularExpression re("^(.*)~(\\d+)$");867QRegularExpressionMatch match = re.match(model.name());868QString baseName;869if (match.hasMatch())870baseName = match.captured(1);871else872baseName = model.name();873
874int maxSuffixNumber = 0;875bool baseNameExists = false;876
877for (const ModelInfo *info : m_models) {878if(info->name() == baseName)879baseNameExists = true;880
881QRegularExpressionMatch match = re.match(info->name());882if (match.hasMatch()) {883QString currentBaseName = match.captured(1);884int currentSuffixNumber = match.captured(2).toInt();885if (currentBaseName == baseName && currentSuffixNumber > maxSuffixNumber)886maxSuffixNumber = currentSuffixNumber;887}888}889
890if (baseNameExists)891return baseName + "~" + QString::number(maxSuffixNumber + 1);892
893return baseName;894}
895
896QString ModelList::modelDirPath(const QString &modelName, bool isOnline)897{
898QVector<QString> possibleFilePaths;899if (isOnline)900possibleFilePaths << "/" + modelName + ".txt";901else {902possibleFilePaths << "/ggml-" + modelName + ".bin";903possibleFilePaths << "/" + modelName + ".bin";904}905for (const QString &modelFilename : possibleFilePaths) {906QString appPath = QCoreApplication::applicationDirPath() + modelFilename;907QFileInfo infoAppPath(appPath);908if (infoAppPath.exists())909return QCoreApplication::applicationDirPath();910
911QString downloadPath = MySettings::globalInstance()->modelPath() + modelFilename;912QFileInfo infoLocalPath(downloadPath);913if (infoLocalPath.exists())914return MySettings::globalInstance()->modelPath();915}916return QString();917}
918
919void ModelList::updateModelsFromDirectory()920{
921const QString exePath = QCoreApplication::applicationDirPath() + QDir::separator();922const QString localPath = MySettings::globalInstance()->modelPath();923
924auto processDirectory = [&](const QString& path) {925QDirIterator it(path, QDirIterator::Subdirectories);926while (it.hasNext()) {927it.next();928
929if (!it.fileInfo().isDir()) {930QString filename = it.fileName();931
932// All files that end with .bin and have 'ggml' somewhere in the name933if (((filename.endsWith(".bin") || filename.endsWith(".gguf")) && (/*filename.contains("ggml") ||*/ filename.contains("gguf")) && !filename.startsWith("incomplete"))934|| (filename.endsWith(".txt") && (filename.startsWith("chatgpt-") || filename.startsWith("nomic-")))) {935
936QString filePath = it.filePath();937QFileInfo info(filePath);938
939if (!info.exists())940continue;941
942QVector<QString> modelsById;943{944QMutexLocker locker(&m_mutex);945for (ModelInfo *info : m_models)946if (info->filename() == filename)947modelsById.append(info->id());948}949
950if (modelsById.isEmpty()) {951addModel(filename);952modelsById.append(filename);953}954
955for (const QString &id : modelsById) {956updateData(id, FilenameRole, filename);957// FIXME: WE should change this to use a consistent filename for online models958updateData(id, OnlineRole, filename.startsWith("chatgpt-") || filename.startsWith("nomic-"));959updateData(id, DirpathRole, info.dir().absolutePath() + "/");960updateData(id, FilesizeRole, toFileSize(info.size()));961}962}963}964}965};966
967processDirectory(exePath);968if (localPath != exePath)969processDirectory(localPath);970}
971
972#define MODELS_VERSION 3973
974void ModelList::updateModelsFromJson()975{
976#if defined(USE_LOCAL_MODELSJSON)977QUrl jsonUrl("file://" + QDir::homePath() + QString("/dev/large_language_models/gpt4all/gpt4all-chat/metadata/models%1.json").arg(MODELS_VERSION));978#else979QUrl jsonUrl(QString("http://gpt4all.io/models/models%1.json").arg(MODELS_VERSION));980#endif981QNetworkRequest request(jsonUrl);982QSslConfiguration conf = request.sslConfiguration();983conf.setPeerVerifyMode(QSslSocket::VerifyNone);984request.setSslConfiguration(conf);985QNetworkReply *jsonReply = m_networkManager.get(request);986connect(qApp, &QCoreApplication::aboutToQuit, jsonReply, &QNetworkReply::abort);987QEventLoop loop;988connect(jsonReply, &QNetworkReply::finished, &loop, &QEventLoop::quit);989QTimer::singleShot(1500, &loop, &QEventLoop::quit);990loop.exec();991if (jsonReply->error() == QNetworkReply::NoError && jsonReply->isFinished()) {992QByteArray jsonData = jsonReply->readAll();993jsonReply->deleteLater();994parseModelsJsonFile(jsonData, true);995} else {996qWarning() << "WARNING: Could not download models.json synchronously";997updateModelsFromJsonAsync();998
999QSettings settings;1000QFileInfo info(settings.fileName());1001QString dirPath = info.canonicalPath();1002const QString modelsConfig = dirPath + "/models.json";1003QFile file(modelsConfig);1004if (!file.open(QIODeviceBase::ReadOnly)) {1005qWarning() << "ERROR: Couldn't read models config file: " << modelsConfig;1006} else {1007QByteArray jsonData = file.readAll();1008file.close();1009parseModelsJsonFile(jsonData, false);1010}1011}1012delete jsonReply;1013}
1014
1015void ModelList::updateModelsFromJsonAsync()1016{
1017m_asyncModelRequestOngoing = true;1018emit asyncModelRequestOngoingChanged();1019
1020#if defined(USE_LOCAL_MODELSJSON)1021QUrl jsonUrl("file://" + QDir::homePath() + QString("/dev/large_language_models/gpt4all/gpt4all-chat/metadata/models%1.json").arg(MODELS_VERSION));1022#else1023QUrl jsonUrl(QString("http://gpt4all.io/models/models%1.json").arg(MODELS_VERSION));1024#endif1025QNetworkRequest request(jsonUrl);1026QSslConfiguration conf = request.sslConfiguration();1027conf.setPeerVerifyMode(QSslSocket::VerifyNone);1028request.setSslConfiguration(conf);1029QNetworkReply *jsonReply = m_networkManager.get(request);1030connect(qApp, &QCoreApplication::aboutToQuit, jsonReply, &QNetworkReply::abort);1031connect(jsonReply, &QNetworkReply::finished, this, &ModelList::handleModelsJsonDownloadFinished);1032connect(jsonReply, &QNetworkReply::errorOccurred, this, &ModelList::handleModelsJsonDownloadErrorOccurred);1033}
1034
1035void ModelList::handleModelsJsonDownloadFinished()1036{
1037QNetworkReply *jsonReply = qobject_cast<QNetworkReply *>(sender());1038if (!jsonReply) {1039m_asyncModelRequestOngoing = false;1040emit asyncModelRequestOngoingChanged();1041return;1042}1043
1044QByteArray jsonData = jsonReply->readAll();1045jsonReply->deleteLater();1046parseModelsJsonFile(jsonData, true);1047m_asyncModelRequestOngoing = false;1048emit asyncModelRequestOngoingChanged();1049}
1050
1051void ModelList::handleModelsJsonDownloadErrorOccurred(QNetworkReply::NetworkError code)1052{
1053// TODO: Show what error occurred in the GUI1054m_asyncModelRequestOngoing = false;1055emit asyncModelRequestOngoingChanged();1056
1057QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());1058if (!reply)1059return;1060
1061qWarning() << QString("ERROR: Modellist download failed with error code \"%1-%2\"")1062.arg(code).arg(reply->errorString()).toStdString();1063}
1064
1065void ModelList::handleSslErrors(QNetworkReply *reply, const QList<QSslError> &errors)1066{
1067QUrl url = reply->request().url();1068for (const auto &e : errors)1069qWarning() << "ERROR: Received ssl error:" << e.errorString() << "for" << url;1070}
1071
1072void ModelList::updateDataForSettings()1073{
1074emit dataChanged(index(0, 0), index(m_models.size() - 1, 0));1075}
1076
1077static bool compareVersions(const QString &a, const QString &b) {1078QStringList aParts = a.split('.');1079QStringList bParts = b.split('.');1080
1081for (int i = 0; i < std::min(aParts.size(), bParts.size()); ++i) {1082int aInt = aParts[i].toInt();1083int bInt = bParts[i].toInt();1084
1085if (aInt > bInt) {1086return true;1087} else if (aInt < bInt) {1088return false;1089}1090}1091
1092return aParts.size() > bParts.size();1093}
1094
1095void ModelList::parseModelsJsonFile(const QByteArray &jsonData, bool save)1096{
1097QJsonParseError err;1098QJsonDocument document = QJsonDocument::fromJson(jsonData, &err);1099if (err.error != QJsonParseError::NoError) {1100qWarning() << "ERROR: Couldn't parse: " << jsonData << err.errorString();1101return;1102}1103
1104if (save) {1105QSettings settings;1106QFileInfo info(settings.fileName());1107QString dirPath = info.canonicalPath();1108const QString modelsConfig = dirPath + "/models.json";1109QFile file(modelsConfig);1110if (!file.open(QIODeviceBase::WriteOnly)) {1111qWarning() << "ERROR: Couldn't write models config file: " << modelsConfig;1112} else {1113file.write(jsonData.constData());1114file.close();1115}1116}1117
1118QJsonArray jsonArray = document.array();1119const QString currentVersion = QCoreApplication::applicationVersion();1120
1121for (const QJsonValue &value : jsonArray) {1122QJsonObject obj = value.toObject();1123
1124QString modelName = obj["name"].toString();1125QString modelFilename = obj["filename"].toString();1126QString modelFilesize = obj["filesize"].toString();1127QString requiresVersion = obj["requires"].toString();1128QString deprecatedVersion = obj["deprecated"].toString();1129QString url = obj["url"].toString();1130QByteArray modelMd5sum = obj["md5sum"].toString().toLatin1().constData();1131bool isDefault = obj.contains("isDefault") && obj["isDefault"] == QString("true");1132bool disableGUI = obj.contains("disableGUI") && obj["disableGUI"] == QString("true");1133QString description = obj["description"].toString();1134QString order = obj["order"].toString();1135int ramrequired = obj["ramrequired"].toString().toInt();1136QString parameters = obj["parameters"].toString();1137QString quant = obj["quant"].toString();1138QString type = obj["type"].toString();1139
1140// If the currentVersion version is strictly less than required version, then continue1141if (!requiresVersion.isEmpty()1142&& requiresVersion != currentVersion1143&& compareVersions(requiresVersion, currentVersion)) {1144continue;1145}1146
1147// If the current version is strictly greater than the deprecated version, then continue1148if (!deprecatedVersion.isEmpty()1149&& compareVersions(currentVersion, deprecatedVersion)) {1150continue;1151}1152
1153modelFilesize = ModelList::toFileSize(modelFilesize.toULongLong());1154
1155const QString id = modelName;1156Q_ASSERT(!id.isEmpty());1157
1158if (contains(modelFilename))1159changeId(modelFilename, id);1160
1161if (!contains(id))1162addModel(id);1163
1164updateData(id, ModelList::NameRole, modelName);1165updateData(id, ModelList::FilenameRole, modelFilename);1166updateData(id, ModelList::FilesizeRole, modelFilesize);1167updateData(id, ModelList::Md5sumRole, modelMd5sum);1168updateData(id, ModelList::DefaultRole, isDefault);1169updateData(id, ModelList::DescriptionRole, description);1170updateData(id, ModelList::RequiresVersionRole, requiresVersion);1171updateData(id, ModelList::DeprecatedVersionRole, deprecatedVersion);1172updateData(id, ModelList::UrlRole, url);1173updateData(id, ModelList::DisableGUIRole, disableGUI);1174updateData(id, ModelList::OrderRole, order);1175updateData(id, ModelList::RamrequiredRole, ramrequired);1176updateData(id, ModelList::ParametersRole, parameters);1177updateData(id, ModelList::QuantRole, quant);1178updateData(id, ModelList::TypeRole, type);1179if (obj.contains("temperature"))1180updateData(id, ModelList::TemperatureRole, obj["temperature"].toDouble());1181if (obj.contains("topP"))1182updateData(id, ModelList::TopPRole, obj["topP"].toDouble());1183if (obj.contains("minP"))1184updateData(id, ModelList::MinPRole, obj["minP"].toDouble());1185if (obj.contains("topK"))1186updateData(id, ModelList::TopKRole, obj["topK"].toInt());1187if (obj.contains("maxLength"))1188updateData(id, ModelList::MaxLengthRole, obj["maxLength"].toInt());1189if (obj.contains("promptBatchSize"))1190updateData(id, ModelList::PromptBatchSizeRole, obj["promptBatchSize"].toInt());1191if (obj.contains("contextLength"))1192updateData(id, ModelList::ContextLengthRole, obj["contextLength"].toInt());1193if (obj.contains("gpuLayers"))1194updateData(id, ModelList::GpuLayersRole, obj["gpuLayers"].toInt());1195if (obj.contains("repeatPenalty"))1196updateData(id, ModelList::RepeatPenaltyRole, obj["repeatPenalty"].toDouble());1197if (obj.contains("repeatPenaltyTokens"))1198updateData(id, ModelList::RepeatPenaltyTokensRole, obj["repeatPenaltyTokens"].toInt());1199if (obj.contains("promptTemplate"))1200updateData(id, ModelList::PromptTemplateRole, obj["promptTemplate"].toString());1201if (obj.contains("systemPrompt"))1202updateData(id, ModelList::SystemPromptRole, obj["systemPrompt"].toString());1203}1204
1205const QString chatGPTDesc = tr("<ul><li>Requires personal OpenAI API key.</li><li>WARNING: Will send"1206" your chats to OpenAI!</li><li>Your API key will be stored on disk</li><li>Will only be used"1207" to communicate with OpenAI</li><li>You can apply for an API key"1208" <a href=\"https://platform.openai.com/account/api-keys\">here.</a></li>");1209
1210{1211const QString modelName = "ChatGPT-3.5 Turbo";1212const QString id = modelName;1213const QString modelFilename = "chatgpt-gpt-3.5-turbo.txt";1214if (contains(modelFilename))1215changeId(modelFilename, id);1216if (!contains(id))1217addModel(id);1218updateData(id, ModelList::NameRole, modelName);1219updateData(id, ModelList::FilenameRole, modelFilename);1220updateData(id, ModelList::FilesizeRole, "minimal");1221updateData(id, ModelList::OnlineRole, true);1222updateData(id, ModelList::DescriptionRole,1223tr("<strong>OpenAI's ChatGPT model GPT-3.5 Turbo</strong><br>") + chatGPTDesc);1224updateData(id, ModelList::RequiresVersionRole, "2.4.2");1225updateData(id, ModelList::OrderRole, "ca");1226updateData(id, ModelList::RamrequiredRole, 0);1227updateData(id, ModelList::ParametersRole, "?");1228updateData(id, ModelList::QuantRole, "NA");1229updateData(id, ModelList::TypeRole, "GPT");1230}1231
1232{1233const QString chatGPT4Warn = tr("<br><br><i>* Even if you pay OpenAI for ChatGPT-4 this does not guarantee API key access. Contact OpenAI for more info.");1234
1235const QString modelName = "ChatGPT-4";1236const QString id = modelName;1237const QString modelFilename = "chatgpt-gpt-4.txt";1238if (contains(modelFilename))1239changeId(modelFilename, id);1240if (!contains(id))1241addModel(id);1242updateData(id, ModelList::NameRole, modelName);1243updateData(id, ModelList::FilenameRole, modelFilename);1244updateData(id, ModelList::FilesizeRole, "minimal");1245updateData(id, ModelList::OnlineRole, true);1246updateData(id, ModelList::DescriptionRole,1247tr("<strong>OpenAI's ChatGPT model GPT-4</strong><br>") + chatGPTDesc + chatGPT4Warn);1248updateData(id, ModelList::RequiresVersionRole, "2.4.2");1249updateData(id, ModelList::OrderRole, "cb");1250updateData(id, ModelList::RamrequiredRole, 0);1251updateData(id, ModelList::ParametersRole, "?");1252updateData(id, ModelList::QuantRole, "NA");1253updateData(id, ModelList::TypeRole, "GPT");1254}1255
1256{1257const QString nomicEmbedDesc = tr("<ul><li>For use with LocalDocs feature</li>"1258"<li>Used for retrieval augmented generation (RAG)</li>"1259"<li>Requires personal Nomic API key.</li>"1260"<li>WARNING: Will send your localdocs to Nomic Atlas!</li>"1261"<li>You can apply for an API key <a href=\"https://atlas.nomic.ai/\">with Nomic Atlas.</a></li>");1262const QString modelName = "Nomic Embed";1263const QString id = modelName;1264const QString modelFilename = "nomic-embed-text-v1.txt";1265if (contains(modelFilename))1266changeId(modelFilename, id);1267if (!contains(id))1268addModel(id);1269updateData(id, ModelList::NameRole, modelName);1270updateData(id, ModelList::FilenameRole, modelFilename);1271updateData(id, ModelList::FilesizeRole, "minimal");1272updateData(id, ModelList::OnlineRole, true);1273updateData(id, ModelList::DisableGUIRole, true);1274updateData(id, ModelList::DescriptionRole,1275tr("<strong>LocalDocs Nomic Atlas Embed</strong><br>") + nomicEmbedDesc);1276updateData(id, ModelList::RequiresVersionRole, "2.6.3");1277updateData(id, ModelList::OrderRole, "na");1278updateData(id, ModelList::RamrequiredRole, 0);1279updateData(id, ModelList::ParametersRole, "?");1280updateData(id, ModelList::QuantRole, "NA");1281updateData(id, ModelList::TypeRole, "Bert");1282}1283}
1284
1285void ModelList::updateModelsFromSettings()1286{
1287QSettings settings;1288settings.sync();1289QStringList groups = settings.childGroups();1290for (const QString g : groups) {1291if (!g.startsWith("model-"))1292continue;1293
1294const QString id = g.sliced(6);1295if (contains(id))1296continue;1297
1298if (!settings.contains(g+ "/isClone"))1299continue;1300
1301Q_ASSERT(settings.contains(g + "/name"));1302const QString name = settings.value(g + "/name").toString();1303Q_ASSERT(settings.contains(g + "/filename"));1304const QString filename = settings.value(g + "/filename").toString();1305Q_ASSERT(settings.contains(g + "/temperature"));1306const double temperature = settings.value(g + "/temperature").toDouble();1307Q_ASSERT(settings.contains(g + "/topP"));1308const double topP = settings.value(g + "/topP").toDouble();1309Q_ASSERT(settings.contains(g + "/minP"));1310const double minP = settings.value(g + "/minP").toDouble();1311Q_ASSERT(settings.contains(g + "/topK"));1312const int topK = settings.value(g + "/topK").toInt();1313Q_ASSERT(settings.contains(g + "/maxLength"));1314const int maxLength = settings.value(g + "/maxLength").toInt();1315Q_ASSERT(settings.contains(g + "/promptBatchSize"));1316const int promptBatchSize = settings.value(g + "/promptBatchSize").toInt();1317Q_ASSERT(settings.contains(g + "/contextLength"));1318const int contextLength = settings.value(g + "/contextLength").toInt();1319Q_ASSERT(settings.contains(g + "/gpuLayers"));1320const int gpuLayers = settings.value(g + "/gpuLayers").toInt();1321Q_ASSERT(settings.contains(g + "/repeatPenalty"));1322const double repeatPenalty = settings.value(g + "/repeatPenalty").toDouble();1323Q_ASSERT(settings.contains(g + "/repeatPenaltyTokens"));1324const int repeatPenaltyTokens = settings.value(g + "/repeatPenaltyTokens").toInt();1325Q_ASSERT(settings.contains(g + "/promptTemplate"));1326const QString promptTemplate = settings.value(g + "/promptTemplate").toString();1327Q_ASSERT(settings.contains(g + "/systemPrompt"));1328const QString systemPrompt = settings.value(g + "/systemPrompt").toString();1329
1330addModel(id);1331updateData(id, ModelList::IsCloneRole, true);1332updateData(id, ModelList::NameRole, name);1333updateData(id, ModelList::FilenameRole, filename);1334updateData(id, ModelList::TemperatureRole, temperature);1335updateData(id, ModelList::TopPRole, topP);1336updateData(id, ModelList::MinPRole, minP);1337updateData(id, ModelList::TopKRole, topK);1338updateData(id, ModelList::MaxLengthRole, maxLength);1339updateData(id, ModelList::PromptBatchSizeRole, promptBatchSize);1340updateData(id, ModelList::ContextLengthRole, contextLength);1341updateData(id, ModelList::GpuLayersRole, gpuLayers);1342updateData(id, ModelList::RepeatPenaltyRole, repeatPenalty);1343updateData(id, ModelList::RepeatPenaltyTokensRole, repeatPenaltyTokens);1344updateData(id, ModelList::PromptTemplateRole, promptTemplate);1345updateData(id, ModelList::SystemPromptRole, systemPrompt);1346}1347}
1348