mirror of
https://github.com/nomic-ai/gpt4all.git
synced 2025-06-20 20:53:23 +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
|
||||
QML_FILES
|
||||
main.qml
|
||||
qml/NetworkDialog.qml
|
||||
qml/ModelDownloaderDialog.qml
|
||||
qml/NetworkDialog.qml
|
||||
qml/NewVersionDialog.qml
|
||||
qml/ThumbsDownDialog.qml
|
||||
qml/SettingsDialog.qml
|
||||
qml/StartupDialog.qml
|
||||
qml/PopupDialog.qml
|
||||
qml/Theme.qml
|
||||
RESOURCES
|
||||
|
112
download.cpp
112
download.cpp
@ -30,6 +30,7 @@ Download::Download()
|
||||
&Download::handleSslErrors);
|
||||
connect(this, &Download::downloadLocalModelsPathChanged, this, &Download::updateModelList);
|
||||
updateModelList();
|
||||
updateReleaseNotes();
|
||||
QSettings settings;
|
||||
settings.sync();
|
||||
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;
|
||||
}
|
||||
|
||||
bool operator==(const ReleaseInfo& lhs, const ReleaseInfo& rhs) {
|
||||
return lhs.version == rhs.version;
|
||||
}
|
||||
|
||||
QList<ModelInfo> Download::modelList() const
|
||||
{
|
||||
// We make sure the default model is listed first
|
||||
@ -68,6 +73,42 @@ QList<ModelInfo> Download::modelList() const
|
||||
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 {
|
||||
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 localPath = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)
|
||||
@ -117,7 +169,18 @@ void Download::updateModelList()
|
||||
conf.setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||
request.setSslConfiguration(conf);
|
||||
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)
|
||||
@ -173,7 +236,7 @@ void Download::handleSslErrors(QNetworkReply *reply, const QList<QSslError> &err
|
||||
qWarning() << "ERROR: Received ssl error:" << e.errorString() << "for" << url;
|
||||
}
|
||||
|
||||
void Download::handleJsonDownloadFinished()
|
||||
void Download::handleModelsJsonDownloadFinished()
|
||||
{
|
||||
#if 0
|
||||
QByteArray jsonData = QString(""
|
||||
@ -206,10 +269,10 @@ void Download::handleJsonDownloadFinished()
|
||||
QByteArray jsonData = jsonReply->readAll();
|
||||
jsonReply->deleteLater();
|
||||
#endif
|
||||
parseJsonFile(jsonData);
|
||||
parseModelsJsonFile(jsonData);
|
||||
}
|
||||
|
||||
void Download::parseJsonFile(const QByteArray &jsonData)
|
||||
void Download::parseModelsJsonFile(const QByteArray &jsonData)
|
||||
{
|
||||
QJsonParseError err;
|
||||
QJsonDocument document = QJsonDocument::fromJson(jsonData, &err);
|
||||
@ -273,6 +336,47 @@ void Download::parseJsonFile(const QByteArray &jsonData)
|
||||
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)
|
||||
{
|
||||
QNetworkReply *modelReply = qobject_cast<QNetworkReply *>(sender());
|
||||
|
27
download.h
27
download.h
@ -34,6 +34,18 @@ public:
|
||||
};
|
||||
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
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -56,6 +68,8 @@ class Download : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
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
|
||||
WRITE setDownloadLocalModelsPath
|
||||
NOTIFY downloadLocalModelsPathChanged)
|
||||
@ -64,16 +78,21 @@ public:
|
||||
static Download *globalInstance();
|
||||
|
||||
QList<ModelInfo> modelList() const;
|
||||
ReleaseInfo releaseInfo() const;
|
||||
bool hasNewerRelease() const;
|
||||
Q_INVOKABLE void updateModelList();
|
||||
Q_INVOKABLE void updateReleaseNotes();
|
||||
Q_INVOKABLE void downloadModel(const QString &modelFile);
|
||||
Q_INVOKABLE void cancelDownload(const QString &modelFile);
|
||||
Q_INVOKABLE QString defaultLocalModelsPath() const;
|
||||
Q_INVOKABLE QString downloadLocalModelsPath() const;
|
||||
Q_INVOKABLE void setDownloadLocalModelsPath(const QString &modelPath);
|
||||
Q_INVOKABLE bool isFirstStart() const;
|
||||
|
||||
private Q_SLOTS:
|
||||
void handleSslErrors(QNetworkReply *reply, const QList<QSslError> &errors);
|
||||
void handleJsonDownloadFinished();
|
||||
void handleModelsJsonDownloadFinished();
|
||||
void handleReleaseJsonDownloadFinished();
|
||||
void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||
void handleModelDownloadFinished();
|
||||
void handleHashAndSaveFinished(bool success,
|
||||
@ -84,15 +103,19 @@ Q_SIGNALS:
|
||||
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal, const QString &modelFile);
|
||||
void downloadFinished(const QString &modelFile);
|
||||
void modelListChanged();
|
||||
void releaseInfoChanged();
|
||||
void hasNewerReleaseChanged();
|
||||
void downloadLocalModelsPathChanged();
|
||||
void requestHashAndSave(const QString &hash, const QString &saveFilePath,
|
||||
QTemporaryFile *tempFile, QNetworkReply *modelReply);
|
||||
|
||||
private:
|
||||
void parseJsonFile(const QByteArray &jsonData);
|
||||
void parseModelsJsonFile(const QByteArray &jsonData);
|
||||
void parseReleaseJsonFile(const QByteArray &jsonData);
|
||||
|
||||
HashAndSaveFile *m_hashAndSave;
|
||||
QMap<QString, ModelInfo> m_modelMap;
|
||||
QMap<QString, ReleaseInfo> m_releaseMap;
|
||||
QNetworkAccessManager m_networkManager;
|
||||
QMap<QNetworkReply*, QTemporaryFile*> m_activeDownloads;
|
||||
QString m_downloadLocalModelsPath;
|
||||
|
57
main.qml
57
main.qml
@ -4,6 +4,7 @@ import QtQuick.Controls
|
||||
import QtQuick.Controls.Basic
|
||||
import QtQuick.Layouts
|
||||
import llm
|
||||
import download
|
||||
import network
|
||||
|
||||
Window {
|
||||
@ -21,6 +22,62 @@ Window {
|
||||
|
||||
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 {
|
||||
Accessible.role: Accessible.Window
|
||||
Accessible.name: title
|
||||
|
75
network.cpp
75
network.cpp
@ -21,17 +21,19 @@ Network *Network::globalInstance()
|
||||
Network::Network()
|
||||
: QObject{nullptr}
|
||||
, m_isActive(false)
|
||||
, m_isOptIn(false)
|
||||
, m_usageStatsActive(false)
|
||||
, m_shouldSendStartup(false)
|
||||
{
|
||||
QSettings settings;
|
||||
settings.sync();
|
||||
m_isOptIn = settings.value("track", false).toBool();
|
||||
m_uniqueId = settings.value("uniqueId", generateUniqueId()).toString();
|
||||
settings.setValue("uniqueId", m_uniqueId);
|
||||
settings.sync();
|
||||
setActive(settings.value("network/isActive", false).toBool());
|
||||
if (m_isOptIn)
|
||||
m_isActive = settings.value("network/isActive", false).toBool();
|
||||
if (m_isActive)
|
||||
sendHealth();
|
||||
m_usageStatsActive = settings.value("network/usageStatsActive", false).toBool();
|
||||
if (m_usageStatsActive)
|
||||
sendIpify();
|
||||
connect(&m_networkManager, &QNetworkAccessManager::sslErrors, this,
|
||||
&Network::handleSslErrors);
|
||||
@ -50,6 +52,22 @@ void Network::setActive(bool b)
|
||||
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
|
||||
{
|
||||
return QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
@ -76,9 +94,9 @@ bool Network::packageAndSendJson(const QString &ingestId, const QString &json)
|
||||
|
||||
QSettings settings;
|
||||
settings.sync();
|
||||
QString attribution = settings.value("attribution", QString()).toString();
|
||||
QString attribution = settings.value("network/attribution", QString()).toString();
|
||||
if (!attribution.isEmpty())
|
||||
object.insert("attribution", attribution);
|
||||
object.insert("network/attribution", attribution);
|
||||
|
||||
QJsonDocument newDoc;
|
||||
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;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
if (!m_isOptIn)
|
||||
if (!m_usageStatsActive)
|
||||
return;
|
||||
sendMixpanelEvent("model_load");
|
||||
}
|
||||
|
||||
void Network::sendResetContext()
|
||||
{
|
||||
if (!m_isOptIn)
|
||||
if (!m_usageStatsActive)
|
||||
return;
|
||||
sendMixpanelEvent("reset_context");
|
||||
}
|
||||
|
||||
void Network::sendStartup()
|
||||
{
|
||||
if (!m_isOptIn)
|
||||
if (!m_usageStatsActive)
|
||||
return;
|
||||
m_shouldSendStartup = true;
|
||||
if (m_ipify.isEmpty())
|
||||
@ -169,21 +212,21 @@ void Network::sendStartup()
|
||||
|
||||
void Network::sendShutdown()
|
||||
{
|
||||
if (!m_isOptIn)
|
||||
if (!m_usageStatsActive)
|
||||
return;
|
||||
sendMixpanelEvent("shutdown");
|
||||
}
|
||||
|
||||
void Network::sendCheckForUpdates()
|
||||
{
|
||||
if (!m_isOptIn)
|
||||
if (!m_usageStatsActive)
|
||||
return;
|
||||
sendMixpanelEvent("check_for_updates");
|
||||
}
|
||||
|
||||
void Network::sendMixpanelEvent(const QString &ev)
|
||||
{
|
||||
if (!m_isOptIn)
|
||||
if (!m_usageStatsActive)
|
||||
return;
|
||||
|
||||
QJsonObject properties;
|
||||
@ -217,7 +260,7 @@ void Network::sendMixpanelEvent(const QString &ev)
|
||||
|
||||
void Network::sendIpify()
|
||||
{
|
||||
if (!m_isOptIn)
|
||||
if (!m_usageStatsActive || !m_ipify.isEmpty())
|
||||
return;
|
||||
|
||||
QUrl ipifyUrl("https://api.ipify.org");
|
||||
@ -231,7 +274,7 @@ void Network::sendIpify()
|
||||
|
||||
void Network::sendMixpanel(const QByteArray &json)
|
||||
{
|
||||
if (!m_isOptIn)
|
||||
if (!m_usageStatsActive)
|
||||
return;
|
||||
|
||||
QUrl trackUrl("https://api.mixpanel.com/track");
|
||||
@ -246,7 +289,7 @@ void Network::sendMixpanel(const QByteArray &json)
|
||||
|
||||
void Network::handleIpifyFinished()
|
||||
{
|
||||
Q_ASSERT(m_isOptIn);
|
||||
Q_ASSERT(m_usageStatsActive);
|
||||
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
|
||||
if (!reply)
|
||||
return;
|
||||
@ -272,7 +315,7 @@ void Network::handleIpifyFinished()
|
||||
|
||||
void Network::handleMixpanelFinished()
|
||||
{
|
||||
Q_ASSERT(m_isOptIn);
|
||||
Q_ASSERT(m_usageStatsActive);
|
||||
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
|
||||
if (!reply)
|
||||
return;
|
||||
|
@ -9,20 +9,27 @@ class Network : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool isActive READ isActive WRITE setActive NOTIFY activeChanged)
|
||||
Q_PROPERTY(bool usageStatsActive READ usageStatsActive WRITE setUsageStatsActive NOTIFY usageStatsActiveChanged)
|
||||
|
||||
public:
|
||||
static Network *globalInstance();
|
||||
|
||||
bool isActive() const { return m_isActive; }
|
||||
void setActive(bool b);
|
||||
|
||||
bool usageStatsActive() const { return m_usageStatsActive; }
|
||||
void setUsageStatsActive(bool b);
|
||||
|
||||
Q_INVOKABLE QString generateUniqueId() const;
|
||||
Q_INVOKABLE bool sendConversation(const QString &ingestId, const QString &conversation);
|
||||
|
||||
Q_SIGNALS:
|
||||
void activeChanged();
|
||||
void usageStatsActiveChanged();
|
||||
void healthCheckFailed(int code);
|
||||
|
||||
public Q_SLOTS:
|
||||
void sendOptOut();
|
||||
void sendModelLoaded();
|
||||
void sendResetContext();
|
||||
void sendStartup();
|
||||
@ -44,9 +51,9 @@ private:
|
||||
bool packageAndSendJson(const QString &ingestId, const QString &json);
|
||||
|
||||
private:
|
||||
bool m_isOptIn;
|
||||
bool m_shouldSendStartup;
|
||||
bool m_isActive;
|
||||
bool m_usageStatsActive;
|
||||
QString m_ipify;
|
||||
QString m_uniqueId;
|
||||
QNetworkAccessManager m_networkManager;
|
||||
|
@ -32,8 +32,6 @@ Dialog {
|
||||
|
||||
Component.onCompleted: {
|
||||
Download.downloadLocalModelsPath = settings.modelPath
|
||||
if (LLM.modelList.length === 0)
|
||||
open();
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
|
@ -22,6 +22,7 @@ Dialog {
|
||||
|
||||
Settings {
|
||||
id: settings
|
||||
category: "network"
|
||||
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