Save chats on quit, even if window isn't closed first (#3387)

Signed-off-by: Jared Van Bortel <jared@nomic.ai>
This commit is contained in:
Jared Van Bortel 2025-01-16 11:59:32 -05:00 committed by GitHub
parent cc5ed4737f
commit 4812ddf1f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 57 additions and 15 deletions

View File

@ -6,11 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased] ## [Unreleased]
## Fixed ### Fixed
- Fix the timeout error in code interpreter ([#3369](https://github.com/nomic-ai/gpt4all/pull/3369)) - Fix the timeout error in code interpreter ([#3369](https://github.com/nomic-ai/gpt4all/pull/3369))
- Fix code interpreter console.log not accepting multiple arguments ([#3371](https://github.com/nomic-ai/gpt4all/pull/3371)) - Fix code interpreter console.log not accepting multiple arguments ([#3371](https://github.com/nomic-ai/gpt4all/pull/3371))
- Remove 'X is defined' checks from templates as they work incorrectly with Jinja2Cpp ([#3372](https://github.com/nomic-ai/gpt4all/pull/3372)) - Remove 'X is defined' checks from templates as they work incorrectly with Jinja2Cpp ([#3372](https://github.com/nomic-ai/gpt4all/pull/3372))
- Jinja2Cpp: Add 'if' requirement for 'else' parsing to fix crash ([#3373](https://github.com/nomic-ai/gpt4all/pull/3373)) - Jinja2Cpp: Add 'if' requirement for 'else' parsing to fix crash ([#3373](https://github.com/nomic-ai/gpt4all/pull/3373))
- Save chats on quit, even if the window isn't closed first ([#3387](https://github.com/nomic-ai/gpt4all/pull/3387))
## [3.6.1] - 2024-12-20 ## [3.6.1] - 2024-12-20

View File

@ -54,7 +54,7 @@ Window {
systemTrayIcon.shouldClose = true; systemTrayIcon.shouldClose = true;
window.shouldClose = true; window.shouldClose = true;
savingPopup.open(); savingPopup.open();
ChatListModel.saveChats(); ChatListModel.saveChatsForQuit();
} }
} }
} }
@ -231,8 +231,8 @@ Window {
window.shouldClose = true; window.shouldClose = true;
savingPopup.open(); savingPopup.open();
ChatListModel.saveChats(); ChatListModel.saveChatsForQuit();
close.accepted = false close.accepted = false;
} }
Connections { Connections {

View File

@ -52,9 +52,11 @@ void ChatListModel::loadChats()
connect(thread, &ChatsRestoreThread::finished, thread, &QObject::deleteLater); connect(thread, &ChatsRestoreThread::finished, thread, &QObject::deleteLater);
thread->start(); thread->start();
ChatSaver *saver = new ChatSaver; m_chatSaver = std::make_unique<ChatSaver>();
connect(this, &ChatListModel::requestSaveChats, saver, &ChatSaver::saveChats, Qt::QueuedConnection); connect(this, &ChatListModel::requestSaveChats, m_chatSaver.get(), &ChatSaver::saveChats, Qt::QueuedConnection);
connect(saver, &ChatSaver::saveChatsFinished, this, &ChatListModel::saveChatsFinished, Qt::QueuedConnection); connect(m_chatSaver.get(), &ChatSaver::saveChatsFinished, this, &ChatListModel::saveChatsFinished, Qt::QueuedConnection);
// save chats on application quit
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &ChatListModel::saveChatsSync);
connect(MySettings::globalInstance(), &MySettings::serverChatChanged, this, &ChatListModel::handleServerEnabledChanged); connect(MySettings::globalInstance(), &MySettings::serverChatChanged, this, &ChatListModel::handleServerEnabledChanged);
} }
@ -78,16 +80,24 @@ ChatSaver::ChatSaver()
m_thread.start(); m_thread.start();
} }
ChatSaver::~ChatSaver()
{
m_thread.quit();
m_thread.wait();
}
QVector<Chat *> ChatListModel::getChatsToSave() const
{
QVector<Chat *> toSave;
for (auto *chat : m_chats)
if (chat != m_serverChat && !chat->isNewChat())
toSave << chat;
return toSave;
}
void ChatListModel::saveChats() void ChatListModel::saveChats()
{ {
QVector<Chat*> toSave; auto toSave = getChatsToSave();
for (Chat *chat : m_chats) {
if (chat == m_serverChat)
continue;
if (chat->isNewChat())
continue;
toSave.append(chat);
}
if (toSave.isEmpty()) { if (toSave.isEmpty()) {
emit saveChatsFinished(); emit saveChatsFinished();
return; return;
@ -96,8 +106,24 @@ void ChatListModel::saveChats()
emit requestSaveChats(toSave); emit requestSaveChats(toSave);
} }
void ChatListModel::saveChatsForQuit()
{
saveChats();
m_startedFinalSave = true;
}
void ChatListModel::saveChatsSync()
{
auto toSave = getChatsToSave();
if (!m_startedFinalSave && !toSave.isEmpty())
m_chatSaver->saveChats(toSave);
}
void ChatSaver::saveChats(const QVector<Chat *> &chats) void ChatSaver::saveChats(const QVector<Chat *> &chats)
{ {
// we can be called from the main thread instead of a worker thread at quit time, so take a lock
QMutexLocker locker(&m_mutex);
QElapsedTimer timer; QElapsedTimer timer;
timer.start(); timer.start();
const QString savePath = MySettings::globalInstance()->modelPath(); const QString savePath = MySettings::globalInstance()->modelPath();

View File

@ -10,6 +10,7 @@
#include <QDebug> #include <QDebug>
#include <QHash> #include <QHash>
#include <QList> #include <QList>
#include <QMutex>
#include <QObject> #include <QObject>
#include <QThread> #include <QThread>
#include <QVariant> #include <QVariant>
@ -18,6 +19,9 @@
#include <QtGlobal> #include <QtGlobal>
#include <QtLogging> #include <QtLogging>
#include <memory>
class ChatsRestoreThread : public QThread class ChatsRestoreThread : public QThread
{ {
Q_OBJECT Q_OBJECT
@ -33,6 +37,7 @@ class ChatSaver : public QObject
Q_OBJECT Q_OBJECT
public: public:
explicit ChatSaver(); explicit ChatSaver();
~ChatSaver() override;
Q_SIGNALS: Q_SIGNALS:
void saveChatsFinished(); void saveChatsFinished();
@ -42,6 +47,7 @@ public Q_SLOTS:
private: private:
QThread m_thread; QThread m_thread;
QMutex m_mutex;
}; };
class ChatListModel : public QAbstractListModel class ChatListModel : public QAbstractListModel
@ -228,6 +234,7 @@ public:
void removeChatFile(Chat *chat) const; void removeChatFile(Chat *chat) const;
Q_INVOKABLE void saveChats(); Q_INVOKABLE void saveChats();
Q_INVOKABLE void saveChatsForQuit();
void restoreChat(Chat *chat); void restoreChat(Chat *chat);
void chatsRestoredFinished(); void chatsRestoredFinished();
@ -244,6 +251,9 @@ protected:
bool eventFilter(QObject *obj, QEvent *ev) override; bool eventFilter(QObject *obj, QEvent *ev) override;
private Q_SLOTS: private Q_SLOTS:
// Used with QCoreApplication::aboutToQuit. Does not require an event loop.
void saveChatsSync();
void newChatCountChanged() void newChatCountChanged()
{ {
Q_ASSERT(m_newChat && m_newChat->chatModel()->count()); Q_ASSERT(m_newChat && m_newChat->chatModel()->count());
@ -274,11 +284,16 @@ private Q_SLOTS:
} }
} }
private:
QVector<Chat *> getChatsToSave() const;
private: private:
Chat* m_newChat = nullptr; Chat* m_newChat = nullptr;
Chat* m_serverChat = nullptr; Chat* m_serverChat = nullptr;
Chat* m_currentChat = nullptr; Chat* m_currentChat = nullptr;
QList<Chat*> m_chats; QList<Chat*> m_chats;
std::unique_ptr<ChatSaver> m_chatSaver;
bool m_startedFinalSave = false;
private: private:
explicit ChatListModel(); explicit ChatListModel();