mirror of
https://github.com/nomic-ai/gpt4all.git
synced 2025-08-11 21:02:12 +00:00
New startup dialog features.
This commit is contained in:
parent
f8754cbe1b
commit
43eef81ca8
@ -65,10 +65,12 @@ qt_add_qml_module(chat
|
|||||||
VERSION 1.0
|
VERSION 1.0
|
||||||
QML_FILES
|
QML_FILES
|
||||||
main.qml
|
main.qml
|
||||||
qml/NetworkDialog.qml
|
|
||||||
qml/ModelDownloaderDialog.qml
|
qml/ModelDownloaderDialog.qml
|
||||||
|
qml/NetworkDialog.qml
|
||||||
|
qml/NewVersionDialog.qml
|
||||||
qml/ThumbsDownDialog.qml
|
qml/ThumbsDownDialog.qml
|
||||||
qml/SettingsDialog.qml
|
qml/SettingsDialog.qml
|
||||||
|
qml/StartupDialog.qml
|
||||||
qml/PopupDialog.qml
|
qml/PopupDialog.qml
|
||||||
qml/Theme.qml
|
qml/Theme.qml
|
||||||
RESOURCES
|
RESOURCES
|
||||||
|
112
download.cpp
112
download.cpp
@ -30,6 +30,7 @@ Download::Download()
|
|||||||
&Download::handleSslErrors);
|
&Download::handleSslErrors);
|
||||||
connect(this, &Download::downloadLocalModelsPathChanged, this, &Download::updateModelList);
|
connect(this, &Download::downloadLocalModelsPathChanged, this, &Download::updateModelList);
|
||||||
updateModelList();
|
updateModelList();
|
||||||
|
updateReleaseNotes();
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
settings.sync();
|
settings.sync();
|
||||||
m_downloadLocalModelsPath = settings.value("modelPath",
|
m_downloadLocalModelsPath = settings.value("modelPath",
|
||||||
@ -40,6 +41,10 @@ bool operator==(const ModelInfo& lhs, const ModelInfo& rhs) {
|
|||||||
return lhs.filename == rhs.filename && lhs.md5sum == rhs.md5sum;
|
return lhs.filename == rhs.filename && lhs.md5sum == rhs.md5sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool operator==(const ReleaseInfo& lhs, const ReleaseInfo& rhs) {
|
||||||
|
return lhs.version == rhs.version;
|
||||||
|
}
|
||||||
|
|
||||||
QList<ModelInfo> Download::modelList() const
|
QList<ModelInfo> Download::modelList() const
|
||||||
{
|
{
|
||||||
// We make sure the default model is listed first
|
// We make sure the default model is listed first
|
||||||
@ -68,6 +73,42 @@ QList<ModelInfo> Download::modelList() const
|
|||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ReleaseInfo Download::releaseInfo() const
|
||||||
|
{
|
||||||
|
const QString currentVersion = QCoreApplication::applicationVersion();
|
||||||
|
if (m_releaseMap.contains(currentVersion))
|
||||||
|
return m_releaseMap.value(currentVersion);
|
||||||
|
return ReleaseInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool compareVersions(const QString &a, const QString &b) {
|
||||||
|
QStringList aParts = a.split('.');
|
||||||
|
QStringList bParts = b.split('.');
|
||||||
|
|
||||||
|
for (int i = 0; i < std::min(aParts.size(), bParts.size()); ++i) {
|
||||||
|
int aInt = aParts[i].toInt();
|
||||||
|
int bInt = bParts[i].toInt();
|
||||||
|
|
||||||
|
if (aInt > bInt) {
|
||||||
|
return true;
|
||||||
|
} else if (aInt < bInt) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return aParts.size() > bParts.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Download::hasNewerRelease() const
|
||||||
|
{
|
||||||
|
const QString currentVersion = QCoreApplication::applicationVersion();
|
||||||
|
QList<QString> versions = m_releaseMap.keys();
|
||||||
|
std::sort(versions.begin(), versions.end(), compareVersions);
|
||||||
|
if (versions.isEmpty())
|
||||||
|
return false;
|
||||||
|
return compareVersions(versions.first(), currentVersion);
|
||||||
|
}
|
||||||
|
|
||||||
QString Download::downloadLocalModelsPath() const {
|
QString Download::downloadLocalModelsPath() const {
|
||||||
return m_downloadLocalModelsPath;
|
return m_downloadLocalModelsPath;
|
||||||
}
|
}
|
||||||
@ -82,6 +123,17 @@ void Download::setDownloadLocalModelsPath(const QString &modelPath) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Download::isFirstStart() const
|
||||||
|
{
|
||||||
|
QSettings settings;
|
||||||
|
settings.sync();
|
||||||
|
QString lastVersionStarted = settings.value("download/lastVersionStarted").toString();
|
||||||
|
bool first = lastVersionStarted != QCoreApplication::applicationVersion();
|
||||||
|
settings.setValue("download/lastVersionStarted", QCoreApplication::applicationVersion());
|
||||||
|
settings.sync();
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
QString Download::defaultLocalModelsPath() const
|
QString Download::defaultLocalModelsPath() const
|
||||||
{
|
{
|
||||||
QString localPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)
|
QString localPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)
|
||||||
@ -117,7 +169,18 @@ void Download::updateModelList()
|
|||||||
conf.setPeerVerifyMode(QSslSocket::VerifyNone);
|
conf.setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||||
request.setSslConfiguration(conf);
|
request.setSslConfiguration(conf);
|
||||||
QNetworkReply *jsonReply = m_networkManager.get(request);
|
QNetworkReply *jsonReply = m_networkManager.get(request);
|
||||||
connect(jsonReply, &QNetworkReply::finished, this, &Download::handleJsonDownloadFinished);
|
connect(jsonReply, &QNetworkReply::finished, this, &Download::handleModelsJsonDownloadFinished);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Download::updateReleaseNotes()
|
||||||
|
{
|
||||||
|
QUrl jsonUrl("http://gpt4all.io/meta/release.json");
|
||||||
|
QNetworkRequest request(jsonUrl);
|
||||||
|
QSslConfiguration conf = request.sslConfiguration();
|
||||||
|
conf.setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||||
|
request.setSslConfiguration(conf);
|
||||||
|
QNetworkReply *jsonReply = m_networkManager.get(request);
|
||||||
|
connect(jsonReply, &QNetworkReply::finished, this, &Download::handleReleaseJsonDownloadFinished);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Download::downloadModel(const QString &modelFile)
|
void Download::downloadModel(const QString &modelFile)
|
||||||
@ -173,7 +236,7 @@ void Download::handleSslErrors(QNetworkReply *reply, const QList<QSslError> &err
|
|||||||
qWarning() << "ERROR: Received ssl error:" << e.errorString() << "for" << url;
|
qWarning() << "ERROR: Received ssl error:" << e.errorString() << "for" << url;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Download::handleJsonDownloadFinished()
|
void Download::handleModelsJsonDownloadFinished()
|
||||||
{
|
{
|
||||||
#if 0
|
#if 0
|
||||||
QByteArray jsonData = QString(""
|
QByteArray jsonData = QString(""
|
||||||
@ -206,10 +269,10 @@ void Download::handleJsonDownloadFinished()
|
|||||||
QByteArray jsonData = jsonReply->readAll();
|
QByteArray jsonData = jsonReply->readAll();
|
||||||
jsonReply->deleteLater();
|
jsonReply->deleteLater();
|
||||||
#endif
|
#endif
|
||||||
parseJsonFile(jsonData);
|
parseModelsJsonFile(jsonData);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Download::parseJsonFile(const QByteArray &jsonData)
|
void Download::parseModelsJsonFile(const QByteArray &jsonData)
|
||||||
{
|
{
|
||||||
QJsonParseError err;
|
QJsonParseError err;
|
||||||
QJsonDocument document = QJsonDocument::fromJson(jsonData, &err);
|
QJsonDocument document = QJsonDocument::fromJson(jsonData, &err);
|
||||||
@ -273,6 +336,47 @@ void Download::parseJsonFile(const QByteArray &jsonData)
|
|||||||
emit modelListChanged();
|
emit modelListChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Download::handleReleaseJsonDownloadFinished()
|
||||||
|
{
|
||||||
|
QNetworkReply *jsonReply = qobject_cast<QNetworkReply *>(sender());
|
||||||
|
if (!jsonReply)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QByteArray jsonData = jsonReply->readAll();
|
||||||
|
jsonReply->deleteLater();
|
||||||
|
parseReleaseJsonFile(jsonData);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Download::parseReleaseJsonFile(const QByteArray &jsonData)
|
||||||
|
{
|
||||||
|
QJsonParseError err;
|
||||||
|
QJsonDocument document = QJsonDocument::fromJson(jsonData, &err);
|
||||||
|
if (err.error != QJsonParseError::NoError) {
|
||||||
|
qDebug() << "ERROR: Couldn't parse: " << jsonData << err.errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonArray jsonArray = document.array();
|
||||||
|
|
||||||
|
m_releaseMap.clear();
|
||||||
|
for (const QJsonValue &value : jsonArray) {
|
||||||
|
QJsonObject obj = value.toObject();
|
||||||
|
|
||||||
|
QString version = obj["version"].toString();
|
||||||
|
QString notes = obj["notes"].toString();
|
||||||
|
QString contributors = obj["contributors"].toString();
|
||||||
|
ReleaseInfo releaseInfo;
|
||||||
|
releaseInfo.version = version;
|
||||||
|
releaseInfo.notes = notes;
|
||||||
|
releaseInfo.contributors = contributors;
|
||||||
|
m_releaseMap.insert(version, releaseInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit hasNewerReleaseChanged();
|
||||||
|
emit releaseInfoChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void Download::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
|
void Download::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
|
||||||
{
|
{
|
||||||
QNetworkReply *modelReply = qobject_cast<QNetworkReply *>(sender());
|
QNetworkReply *modelReply = qobject_cast<QNetworkReply *>(sender());
|
||||||
|
27
download.h
27
download.h
@ -34,6 +34,18 @@ public:
|
|||||||
};
|
};
|
||||||
Q_DECLARE_METATYPE(ModelInfo)
|
Q_DECLARE_METATYPE(ModelInfo)
|
||||||
|
|
||||||
|
struct ReleaseInfo {
|
||||||
|
Q_GADGET
|
||||||
|
Q_PROPERTY(QString version MEMBER version)
|
||||||
|
Q_PROPERTY(QString notes MEMBER notes)
|
||||||
|
Q_PROPERTY(QString contributors MEMBER contributors)
|
||||||
|
|
||||||
|
public:
|
||||||
|
QString version;
|
||||||
|
QString notes;
|
||||||
|
QString contributors;
|
||||||
|
};
|
||||||
|
|
||||||
class HashAndSaveFile : public QObject
|
class HashAndSaveFile : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -56,6 +68,8 @@ class Download : public QObject
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(QList<ModelInfo> modelList READ modelList NOTIFY modelListChanged)
|
Q_PROPERTY(QList<ModelInfo> modelList READ modelList NOTIFY modelListChanged)
|
||||||
|
Q_PROPERTY(bool hasNewerRelease READ hasNewerRelease NOTIFY hasNewerReleaseChanged)
|
||||||
|
Q_PROPERTY(ReleaseInfo releaseInfo READ releaseInfo NOTIFY releaseInfoChanged)
|
||||||
Q_PROPERTY(QString downloadLocalModelsPath READ downloadLocalModelsPath
|
Q_PROPERTY(QString downloadLocalModelsPath READ downloadLocalModelsPath
|
||||||
WRITE setDownloadLocalModelsPath
|
WRITE setDownloadLocalModelsPath
|
||||||
NOTIFY downloadLocalModelsPathChanged)
|
NOTIFY downloadLocalModelsPathChanged)
|
||||||
@ -64,16 +78,21 @@ public:
|
|||||||
static Download *globalInstance();
|
static Download *globalInstance();
|
||||||
|
|
||||||
QList<ModelInfo> modelList() const;
|
QList<ModelInfo> modelList() const;
|
||||||
|
ReleaseInfo releaseInfo() const;
|
||||||
|
bool hasNewerRelease() const;
|
||||||
Q_INVOKABLE void updateModelList();
|
Q_INVOKABLE void updateModelList();
|
||||||
|
Q_INVOKABLE void updateReleaseNotes();
|
||||||
Q_INVOKABLE void downloadModel(const QString &modelFile);
|
Q_INVOKABLE void downloadModel(const QString &modelFile);
|
||||||
Q_INVOKABLE void cancelDownload(const QString &modelFile);
|
Q_INVOKABLE void cancelDownload(const QString &modelFile);
|
||||||
Q_INVOKABLE QString defaultLocalModelsPath() const;
|
Q_INVOKABLE QString defaultLocalModelsPath() const;
|
||||||
Q_INVOKABLE QString downloadLocalModelsPath() const;
|
Q_INVOKABLE QString downloadLocalModelsPath() const;
|
||||||
Q_INVOKABLE void setDownloadLocalModelsPath(const QString &modelPath);
|
Q_INVOKABLE void setDownloadLocalModelsPath(const QString &modelPath);
|
||||||
|
Q_INVOKABLE bool isFirstStart() const;
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void handleSslErrors(QNetworkReply *reply, const QList<QSslError> &errors);
|
void handleSslErrors(QNetworkReply *reply, const QList<QSslError> &errors);
|
||||||
void handleJsonDownloadFinished();
|
void handleModelsJsonDownloadFinished();
|
||||||
|
void handleReleaseJsonDownloadFinished();
|
||||||
void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||||
void handleModelDownloadFinished();
|
void handleModelDownloadFinished();
|
||||||
void handleHashAndSaveFinished(bool success,
|
void handleHashAndSaveFinished(bool success,
|
||||||
@ -84,15 +103,19 @@ Q_SIGNALS:
|
|||||||
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal, const QString &modelFile);
|
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal, const QString &modelFile);
|
||||||
void downloadFinished(const QString &modelFile);
|
void downloadFinished(const QString &modelFile);
|
||||||
void modelListChanged();
|
void modelListChanged();
|
||||||
|
void releaseInfoChanged();
|
||||||
|
void hasNewerReleaseChanged();
|
||||||
void downloadLocalModelsPathChanged();
|
void downloadLocalModelsPathChanged();
|
||||||
void requestHashAndSave(const QString &hash, const QString &saveFilePath,
|
void requestHashAndSave(const QString &hash, const QString &saveFilePath,
|
||||||
QTemporaryFile *tempFile, QNetworkReply *modelReply);
|
QTemporaryFile *tempFile, QNetworkReply *modelReply);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void parseJsonFile(const QByteArray &jsonData);
|
void parseModelsJsonFile(const QByteArray &jsonData);
|
||||||
|
void parseReleaseJsonFile(const QByteArray &jsonData);
|
||||||
|
|
||||||
HashAndSaveFile *m_hashAndSave;
|
HashAndSaveFile *m_hashAndSave;
|
||||||
QMap<QString, ModelInfo> m_modelMap;
|
QMap<QString, ModelInfo> m_modelMap;
|
||||||
|
QMap<QString, ReleaseInfo> m_releaseMap;
|
||||||
QNetworkAccessManager m_networkManager;
|
QNetworkAccessManager m_networkManager;
|
||||||
QMap<QNetworkReply*, QTemporaryFile*> m_activeDownloads;
|
QMap<QNetworkReply*, QTemporaryFile*> m_activeDownloads;
|
||||||
QString m_downloadLocalModelsPath;
|
QString m_downloadLocalModelsPath;
|
||||||
|
57
main.qml
57
main.qml
@ -4,6 +4,7 @@ import QtQuick.Controls
|
|||||||
import QtQuick.Controls.Basic
|
import QtQuick.Controls.Basic
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import llm
|
import llm
|
||||||
|
import download
|
||||||
import network
|
import network
|
||||||
|
|
||||||
Window {
|
Window {
|
||||||
@ -21,6 +22,62 @@ Window {
|
|||||||
|
|
||||||
color: theme.textColor
|
color: theme.textColor
|
||||||
|
|
||||||
|
// Startup code
|
||||||
|
Component.onCompleted: {
|
||||||
|
startupDialogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: firstStartDialog
|
||||||
|
function onClosed() {
|
||||||
|
startupDialogs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: downloadNewModels
|
||||||
|
function onClosed() {
|
||||||
|
startupDialogs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: Download
|
||||||
|
function onHasNewerReleaseChanged() {
|
||||||
|
startupDialogs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startupDialogs() {
|
||||||
|
// check for first time start of this version
|
||||||
|
if (Download.isFirstStart()) {
|
||||||
|
firstStartDialog.open();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for any current models and if not, open download dialog
|
||||||
|
if (LLM.modelList.length === 0) {
|
||||||
|
downloadNewModels.open();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for new version
|
||||||
|
if (Download.hasNewerRelease) {
|
||||||
|
newVersionDialog.open();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StartupDialog {
|
||||||
|
id: firstStartDialog
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
NewVersionDialog {
|
||||||
|
id: newVersionDialog
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
Accessible.role: Accessible.Window
|
Accessible.role: Accessible.Window
|
||||||
Accessible.name: title
|
Accessible.name: title
|
||||||
|
75
network.cpp
75
network.cpp
@ -21,17 +21,19 @@ Network *Network::globalInstance()
|
|||||||
Network::Network()
|
Network::Network()
|
||||||
: QObject{nullptr}
|
: QObject{nullptr}
|
||||||
, m_isActive(false)
|
, m_isActive(false)
|
||||||
, m_isOptIn(false)
|
, m_usageStatsActive(false)
|
||||||
, m_shouldSendStartup(false)
|
, m_shouldSendStartup(false)
|
||||||
{
|
{
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
settings.sync();
|
settings.sync();
|
||||||
m_isOptIn = settings.value("track", false).toBool();
|
|
||||||
m_uniqueId = settings.value("uniqueId", generateUniqueId()).toString();
|
m_uniqueId = settings.value("uniqueId", generateUniqueId()).toString();
|
||||||
settings.setValue("uniqueId", m_uniqueId);
|
settings.setValue("uniqueId", m_uniqueId);
|
||||||
settings.sync();
|
settings.sync();
|
||||||
setActive(settings.value("network/isActive", false).toBool());
|
m_isActive = settings.value("network/isActive", false).toBool();
|
||||||
if (m_isOptIn)
|
if (m_isActive)
|
||||||
|
sendHealth();
|
||||||
|
m_usageStatsActive = settings.value("network/usageStatsActive", false).toBool();
|
||||||
|
if (m_usageStatsActive)
|
||||||
sendIpify();
|
sendIpify();
|
||||||
connect(&m_networkManager, &QNetworkAccessManager::sslErrors, this,
|
connect(&m_networkManager, &QNetworkAccessManager::sslErrors, this,
|
||||||
&Network::handleSslErrors);
|
&Network::handleSslErrors);
|
||||||
@ -50,6 +52,22 @@ void Network::setActive(bool b)
|
|||||||
sendHealth();
|
sendHealth();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Network::setUsageStatsActive(bool b)
|
||||||
|
{
|
||||||
|
QSettings settings;
|
||||||
|
settings.setValue("network/usageStatsActive", b);
|
||||||
|
settings.sync();
|
||||||
|
m_usageStatsActive = b;
|
||||||
|
emit usageStatsActiveChanged();
|
||||||
|
if (!m_usageStatsActive)
|
||||||
|
sendOptOut();
|
||||||
|
else {
|
||||||
|
// model might be loaded already when user opt-in for first time
|
||||||
|
sendStartup();
|
||||||
|
sendIpify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QString Network::generateUniqueId() const
|
QString Network::generateUniqueId() const
|
||||||
{
|
{
|
||||||
return QUuid::createUuid().toString(QUuid::WithoutBraces);
|
return QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||||
@ -76,9 +94,9 @@ bool Network::packageAndSendJson(const QString &ingestId, const QString &json)
|
|||||||
|
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
settings.sync();
|
settings.sync();
|
||||||
QString attribution = settings.value("attribution", QString()).toString();
|
QString attribution = settings.value("network/attribution", QString()).toString();
|
||||||
if (!attribution.isEmpty())
|
if (!attribution.isEmpty())
|
||||||
object.insert("attribution", attribution);
|
object.insert("network/attribution", attribution);
|
||||||
|
|
||||||
QJsonDocument newDoc;
|
QJsonDocument newDoc;
|
||||||
newDoc.setObject(object);
|
newDoc.setObject(object);
|
||||||
@ -143,23 +161,48 @@ void Network::handleSslErrors(QNetworkReply *reply, const QList<QSslError> &erro
|
|||||||
qWarning() << "ERROR: Received ssl error:" << e.errorString() << "for" << url;
|
qWarning() << "ERROR: Received ssl error:" << e.errorString() << "for" << url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Network::sendOptOut()
|
||||||
|
{
|
||||||
|
QJsonObject properties;
|
||||||
|
properties.insert("token", "ce362e568ddaee16ed243eaffb5860a2");
|
||||||
|
properties.insert("time", QDateTime::currentSecsSinceEpoch());
|
||||||
|
properties.insert("distinct_id", m_uniqueId);
|
||||||
|
properties.insert("$insert_id", generateUniqueId());
|
||||||
|
|
||||||
|
QJsonObject event;
|
||||||
|
event.insert("event", "opt_out");
|
||||||
|
event.insert("properties", properties);
|
||||||
|
|
||||||
|
QJsonArray array;
|
||||||
|
array.append(event);
|
||||||
|
|
||||||
|
QJsonDocument doc;
|
||||||
|
doc.setArray(array);
|
||||||
|
sendMixpanel(doc.toJson());
|
||||||
|
|
||||||
|
#if defined(DEBUG)
|
||||||
|
printf("%s %s\n", qPrintable("opt_out"), qPrintable(doc.toJson(QJsonDocument::Indented)));
|
||||||
|
fflush(stdout);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void Network::sendModelLoaded()
|
void Network::sendModelLoaded()
|
||||||
{
|
{
|
||||||
if (!m_isOptIn)
|
if (!m_usageStatsActive)
|
||||||
return;
|
return;
|
||||||
sendMixpanelEvent("model_load");
|
sendMixpanelEvent("model_load");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Network::sendResetContext()
|
void Network::sendResetContext()
|
||||||
{
|
{
|
||||||
if (!m_isOptIn)
|
if (!m_usageStatsActive)
|
||||||
return;
|
return;
|
||||||
sendMixpanelEvent("reset_context");
|
sendMixpanelEvent("reset_context");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Network::sendStartup()
|
void Network::sendStartup()
|
||||||
{
|
{
|
||||||
if (!m_isOptIn)
|
if (!m_usageStatsActive)
|
||||||
return;
|
return;
|
||||||
m_shouldSendStartup = true;
|
m_shouldSendStartup = true;
|
||||||
if (m_ipify.isEmpty())
|
if (m_ipify.isEmpty())
|
||||||
@ -169,21 +212,21 @@ void Network::sendStartup()
|
|||||||
|
|
||||||
void Network::sendShutdown()
|
void Network::sendShutdown()
|
||||||
{
|
{
|
||||||
if (!m_isOptIn)
|
if (!m_usageStatsActive)
|
||||||
return;
|
return;
|
||||||
sendMixpanelEvent("shutdown");
|
sendMixpanelEvent("shutdown");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Network::sendCheckForUpdates()
|
void Network::sendCheckForUpdates()
|
||||||
{
|
{
|
||||||
if (!m_isOptIn)
|
if (!m_usageStatsActive)
|
||||||
return;
|
return;
|
||||||
sendMixpanelEvent("check_for_updates");
|
sendMixpanelEvent("check_for_updates");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Network::sendMixpanelEvent(const QString &ev)
|
void Network::sendMixpanelEvent(const QString &ev)
|
||||||
{
|
{
|
||||||
if (!m_isOptIn)
|
if (!m_usageStatsActive)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QJsonObject properties;
|
QJsonObject properties;
|
||||||
@ -217,7 +260,7 @@ void Network::sendMixpanelEvent(const QString &ev)
|
|||||||
|
|
||||||
void Network::sendIpify()
|
void Network::sendIpify()
|
||||||
{
|
{
|
||||||
if (!m_isOptIn)
|
if (!m_usageStatsActive || !m_ipify.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QUrl ipifyUrl("https://api.ipify.org");
|
QUrl ipifyUrl("https://api.ipify.org");
|
||||||
@ -231,7 +274,7 @@ void Network::sendIpify()
|
|||||||
|
|
||||||
void Network::sendMixpanel(const QByteArray &json)
|
void Network::sendMixpanel(const QByteArray &json)
|
||||||
{
|
{
|
||||||
if (!m_isOptIn)
|
if (!m_usageStatsActive)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QUrl trackUrl("https://api.mixpanel.com/track");
|
QUrl trackUrl("https://api.mixpanel.com/track");
|
||||||
@ -246,7 +289,7 @@ void Network::sendMixpanel(const QByteArray &json)
|
|||||||
|
|
||||||
void Network::handleIpifyFinished()
|
void Network::handleIpifyFinished()
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_isOptIn);
|
Q_ASSERT(m_usageStatsActive);
|
||||||
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
|
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
|
||||||
if (!reply)
|
if (!reply)
|
||||||
return;
|
return;
|
||||||
@ -272,7 +315,7 @@ void Network::handleIpifyFinished()
|
|||||||
|
|
||||||
void Network::handleMixpanelFinished()
|
void Network::handleMixpanelFinished()
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_isOptIn);
|
Q_ASSERT(m_usageStatsActive);
|
||||||
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
|
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
|
||||||
if (!reply)
|
if (!reply)
|
||||||
return;
|
return;
|
||||||
|
@ -9,20 +9,27 @@ class Network : public QObject
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(bool isActive READ isActive WRITE setActive NOTIFY activeChanged)
|
Q_PROPERTY(bool isActive READ isActive WRITE setActive NOTIFY activeChanged)
|
||||||
|
Q_PROPERTY(bool usageStatsActive READ usageStatsActive WRITE setUsageStatsActive NOTIFY usageStatsActiveChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static Network *globalInstance();
|
static Network *globalInstance();
|
||||||
|
|
||||||
bool isActive() const { return m_isActive; }
|
bool isActive() const { return m_isActive; }
|
||||||
void setActive(bool b);
|
void setActive(bool b);
|
||||||
|
|
||||||
|
bool usageStatsActive() const { return m_usageStatsActive; }
|
||||||
|
void setUsageStatsActive(bool b);
|
||||||
|
|
||||||
Q_INVOKABLE QString generateUniqueId() const;
|
Q_INVOKABLE QString generateUniqueId() const;
|
||||||
Q_INVOKABLE bool sendConversation(const QString &ingestId, const QString &conversation);
|
Q_INVOKABLE bool sendConversation(const QString &ingestId, const QString &conversation);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void activeChanged();
|
void activeChanged();
|
||||||
|
void usageStatsActiveChanged();
|
||||||
void healthCheckFailed(int code);
|
void healthCheckFailed(int code);
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
|
void sendOptOut();
|
||||||
void sendModelLoaded();
|
void sendModelLoaded();
|
||||||
void sendResetContext();
|
void sendResetContext();
|
||||||
void sendStartup();
|
void sendStartup();
|
||||||
@ -44,9 +51,9 @@ private:
|
|||||||
bool packageAndSendJson(const QString &ingestId, const QString &json);
|
bool packageAndSendJson(const QString &ingestId, const QString &json);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_isOptIn;
|
|
||||||
bool m_shouldSendStartup;
|
bool m_shouldSendStartup;
|
||||||
bool m_isActive;
|
bool m_isActive;
|
||||||
|
bool m_usageStatsActive;
|
||||||
QString m_ipify;
|
QString m_ipify;
|
||||||
QString m_uniqueId;
|
QString m_uniqueId;
|
||||||
QNetworkAccessManager m_networkManager;
|
QNetworkAccessManager m_networkManager;
|
||||||
|
@ -32,8 +32,6 @@ Dialog {
|
|||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
Download.downloadLocalModelsPath = settings.modelPath
|
Download.downloadLocalModelsPath = settings.modelPath
|
||||||
if (LLM.modelList.length === 0)
|
|
||||||
open();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onDestruction: {
|
Component.onDestruction: {
|
||||||
|
@ -22,6 +22,7 @@ Dialog {
|
|||||||
|
|
||||||
Settings {
|
Settings {
|
||||||
id: settings
|
id: settings
|
||||||
|
category: "network"
|
||||||
property string attribution: ""
|
property string attribution: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
76
qml/NewVersionDialog.qml
Normal file
76
qml/NewVersionDialog.qml
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import QtCore
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Controls.Basic
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import download
|
||||||
|
import network
|
||||||
|
import llm
|
||||||
|
|
||||||
|
Dialog {
|
||||||
|
id: newVerionDialog
|
||||||
|
anchors.centerIn: parent
|
||||||
|
modal: true
|
||||||
|
opacity: 0.9
|
||||||
|
width: contentItem.width
|
||||||
|
height: contentItem.height
|
||||||
|
padding: 20
|
||||||
|
|
||||||
|
Theme {
|
||||||
|
id: theme
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: theme.backgroundDarkest
|
||||||
|
border.width: 1
|
||||||
|
border.color: theme.dialogBorder
|
||||||
|
radius: 10
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: contentItem
|
||||||
|
width: childrenRect.width + 40
|
||||||
|
height: childrenRect.height + 40
|
||||||
|
|
||||||
|
Label {
|
||||||
|
id: label
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
topPadding: 20
|
||||||
|
bottomPadding: 20
|
||||||
|
text: qsTr("New version is available:")
|
||||||
|
color: theme.textColor
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: button
|
||||||
|
anchors.left: label.right
|
||||||
|
anchors.leftMargin: 10
|
||||||
|
anchors.verticalCenter: label.verticalCenter
|
||||||
|
padding: 20
|
||||||
|
contentItem: Text {
|
||||||
|
text: qsTr("Update")
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
color: theme.textColor
|
||||||
|
|
||||||
|
Accessible.role: Accessible.Button
|
||||||
|
Accessible.name: text
|
||||||
|
Accessible.description: qsTr("Use this to launch an external application that will check for updates to the installer")
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
opacity: .5
|
||||||
|
border.color: theme.backgroundLightest
|
||||||
|
border.width: 1
|
||||||
|
radius: 10
|
||||||
|
color: theme.backgroundLight
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (!LLM.checkForUpdates())
|
||||||
|
checkForUpdatesError.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
193
qml/StartupDialog.qml
Normal file
193
qml/StartupDialog.qml
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
import QtCore
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Controls.Basic
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import download
|
||||||
|
import network
|
||||||
|
import llm
|
||||||
|
|
||||||
|
Dialog {
|
||||||
|
id: startupDialog
|
||||||
|
anchors.centerIn: parent
|
||||||
|
modal: false
|
||||||
|
opacity: 0.9
|
||||||
|
padding: 20
|
||||||
|
width: 1024
|
||||||
|
height: column.height + 40
|
||||||
|
|
||||||
|
Theme {
|
||||||
|
id: theme
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: startupDialog
|
||||||
|
function onClosed() {
|
||||||
|
if (!Network.usageStatsActive)
|
||||||
|
Network.usageStatsActive = false // opt-out triggered
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: column
|
||||||
|
spacing: 20
|
||||||
|
Item {
|
||||||
|
width: childrenRect.width
|
||||||
|
height: childrenRect.height
|
||||||
|
Image {
|
||||||
|
id: img
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
width: 60
|
||||||
|
height: 60
|
||||||
|
source: "qrc:/gpt4all/icons/logo.svg"
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
anchors.left: img.right
|
||||||
|
anchors.leftMargin: 30
|
||||||
|
anchors.verticalCenter: img.verticalCenter
|
||||||
|
text: qsTr("Welcome!")
|
||||||
|
color: theme.textColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
clip: true
|
||||||
|
height: 200
|
||||||
|
width: 1024 - 40
|
||||||
|
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
|
||||||
|
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||||
|
|
||||||
|
TextArea {
|
||||||
|
id: welcome
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
width: 1024 - 40
|
||||||
|
padding: 20
|
||||||
|
textFormat: TextEdit.MarkdownText
|
||||||
|
text: qsTr("### Release notes\n")
|
||||||
|
+ Download.releaseInfo.notes
|
||||||
|
+ qsTr("### Contributors\n")
|
||||||
|
+ Download.releaseInfo.contributors
|
||||||
|
color: theme.textColor
|
||||||
|
focus: false
|
||||||
|
readOnly: true
|
||||||
|
Accessible.role: Accessible.Paragraph
|
||||||
|
Accessible.name: qsTr("Release notes")
|
||||||
|
Accessible.description: qsTr("Release notes for this version")
|
||||||
|
background: Rectangle {
|
||||||
|
color: theme.backgroundLight
|
||||||
|
radius: 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
clip: true
|
||||||
|
height: 150
|
||||||
|
width: 1024 - 40
|
||||||
|
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
|
||||||
|
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||||
|
|
||||||
|
TextArea {
|
||||||
|
id: optInTerms
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
width: 1024 - 40
|
||||||
|
padding: 20
|
||||||
|
textFormat: TextEdit.MarkdownText
|
||||||
|
text: qsTr(
|
||||||
|
"### Opt-ins for anonymous usage analytics and datalake
|
||||||
|
By enabling these features, you will be able to participate in the democratic process of training a
|
||||||
|
large language model by contributing data for future model improvements.
|
||||||
|
|
||||||
|
When a GPT4All model responds to you and you have opted-in, you can like/dislike its response. If you
|
||||||
|
dislike a response, you can suggest an alternative response. This data will be collected and aggregated
|
||||||
|
in the GPT4All Datalake.
|
||||||
|
|
||||||
|
NOTE: By turning on this feature, you will be sending your data to the GPT4All Open Source Datalake.
|
||||||
|
You should have no expectation of chat privacy when this feature is enabled. You should; however, have
|
||||||
|
an expectation of an optional attribution if you wish. Your chat data will be openly available for anyone
|
||||||
|
to download and will be used by Nomic AI to improve future GPT4All models. Nomic AI will retain all
|
||||||
|
attribution information attached to your data and you will be credited as a contributor to any GPT4All
|
||||||
|
model release that uses your data!")
|
||||||
|
|
||||||
|
color: theme.textColor
|
||||||
|
focus: false
|
||||||
|
readOnly: true
|
||||||
|
Accessible.role: Accessible.Paragraph
|
||||||
|
Accessible.name: qsTr("Terms for opt-in")
|
||||||
|
Accessible.description: qsTr("Describes what will happen when you opt-in")
|
||||||
|
background: Rectangle {
|
||||||
|
color: theme.backgroundLight
|
||||||
|
radius: 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GridLayout {
|
||||||
|
columns: 2
|
||||||
|
rowSpacing: 10
|
||||||
|
columnSpacing: 10
|
||||||
|
anchors.right: parent.right
|
||||||
|
Label {
|
||||||
|
id: optInStatistics
|
||||||
|
text: "Opt-in to anonymous usage analytics used to improve GPT4All"
|
||||||
|
Layout.row: 0
|
||||||
|
Layout.column: 0
|
||||||
|
Accessible.role: Accessible.Paragraph
|
||||||
|
Accessible.name: qsTr("Opt-in for anonymous usage statistics")
|
||||||
|
Accessible.description: qsTr("Label for opt-in")
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
id: optInStatisticsBox
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.row: 0
|
||||||
|
Layout.column: 1
|
||||||
|
property bool defaultChecked: Network.usageStatsActive
|
||||||
|
checked: defaultChecked
|
||||||
|
Accessible.role: Accessible.CheckBox
|
||||||
|
Accessible.name: qsTr("Opt-in for anonymous usage statistics")
|
||||||
|
Accessible.description: qsTr("Checkbox to allow opt-in for anonymous usage statistics")
|
||||||
|
onClicked: {
|
||||||
|
Network.usageStatsActive = optInStatisticsBox.checked
|
||||||
|
if (optInNetworkBox.checked && optInStatisticsBox.checked)
|
||||||
|
startupDialog.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
id: optInNetwork
|
||||||
|
text: "Opt-in to anonymous sharing of chats to the GPT4All Datalake"
|
||||||
|
Layout.row: 1
|
||||||
|
Layout.column: 0
|
||||||
|
Accessible.role: Accessible.Paragraph
|
||||||
|
Accessible.name: qsTr("Opt-in for network")
|
||||||
|
Accessible.description: qsTr("Checkbox to allow opt-in for network")
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
id: optInNetworkBox
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.row: 1
|
||||||
|
Layout.column: 1
|
||||||
|
property bool defaultChecked: Network.isActive
|
||||||
|
checked: defaultChecked
|
||||||
|
Accessible.role: Accessible.CheckBox
|
||||||
|
Accessible.name: qsTr("Opt-in for network")
|
||||||
|
Accessible.description: qsTr("Label for opt-in")
|
||||||
|
onClicked: {
|
||||||
|
Network.isActive = optInNetworkBox.checked
|
||||||
|
if (optInNetworkBox.checked && optInStatisticsBox.checked)
|
||||||
|
startupDialog.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: theme.backgroundDarkest
|
||||||
|
border.width: 1
|
||||||
|
border.color: theme.dialogBorder
|
||||||
|
radius: 10
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user