2023-05-04 15:31:41 -04:00
|
|
|
#include "chatlistmodel.h"
|
2023-05-06 18:51:30 -04:00
|
|
|
#include "download.h"
|
2023-05-11 16:46:25 -04:00
|
|
|
#include "llm.h"
|
2023-05-04 15:31:41 -04:00
|
|
|
|
|
|
|
#include <QFile>
|
|
|
|
#include <QDataStream>
|
|
|
|
|
2023-05-06 18:51:30 -04:00
|
|
|
#define CHAT_FORMAT_MAGIC 0xF5D553CC
|
2023-05-24 14:49:43 -04:00
|
|
|
#define CHAT_FORMAT_VERSION 3
|
2023-05-06 18:51:30 -04:00
|
|
|
|
2023-05-06 20:01:14 -04:00
|
|
|
ChatListModel::ChatListModel(QObject *parent)
|
|
|
|
: QAbstractListModel(parent)
|
|
|
|
, m_newChat(nullptr)
|
|
|
|
, m_dummyChat(nullptr)
|
2023-05-11 16:46:25 -04:00
|
|
|
, m_serverChat(nullptr)
|
2023-05-06 20:01:14 -04:00
|
|
|
, m_currentChat(nullptr)
|
|
|
|
, m_shouldSaveChats(false)
|
|
|
|
{
|
|
|
|
addDummyChat();
|
|
|
|
|
|
|
|
ChatsRestoreThread *thread = new ChatsRestoreThread;
|
2023-05-07 09:20:09 -04:00
|
|
|
connect(thread, &ChatsRestoreThread::chatRestored, this, &ChatListModel::restoreChat);
|
|
|
|
connect(thread, &ChatsRestoreThread::finished, this, &ChatListModel::chatsRestoredFinished);
|
2023-05-06 20:01:14 -04:00
|
|
|
connect(thread, &ChatsRestoreThread::finished, thread, &QObject::deleteLater);
|
|
|
|
thread->start();
|
|
|
|
}
|
|
|
|
|
2023-05-05 12:30:11 -04:00
|
|
|
bool ChatListModel::shouldSaveChats() const
|
|
|
|
{
|
|
|
|
return m_shouldSaveChats;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChatListModel::setShouldSaveChats(bool b)
|
|
|
|
{
|
|
|
|
if (m_shouldSaveChats == b)
|
|
|
|
return;
|
|
|
|
m_shouldSaveChats = b;
|
|
|
|
emit shouldSaveChatsChanged();
|
|
|
|
}
|
|
|
|
|
2023-05-15 18:36:41 -04:00
|
|
|
bool ChatListModel::shouldSaveChatGPTChats() const
|
|
|
|
{
|
|
|
|
return m_shouldSaveChatGPTChats;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChatListModel::setShouldSaveChatGPTChats(bool b)
|
|
|
|
{
|
|
|
|
if (m_shouldSaveChatGPTChats == b)
|
|
|
|
return;
|
|
|
|
m_shouldSaveChatGPTChats = b;
|
|
|
|
emit shouldSaveChatGPTChatsChanged();
|
|
|
|
}
|
|
|
|
|
2023-05-04 15:31:41 -04:00
|
|
|
void ChatListModel::removeChatFile(Chat *chat) const
|
|
|
|
{
|
2023-05-13 19:05:35 -04:00
|
|
|
Q_ASSERT(chat != m_serverChat);
|
2023-05-06 18:51:30 -04:00
|
|
|
const QString savePath = Download::globalInstance()->downloadLocalModelsPath();
|
|
|
|
QFile file(savePath + "/gpt4all-" + chat->id() + ".chat");
|
2023-05-04 15:31:41 -04:00
|
|
|
if (!file.exists())
|
|
|
|
return;
|
|
|
|
bool success = file.remove();
|
|
|
|
if (!success)
|
|
|
|
qWarning() << "ERROR: Couldn't remove chat file:" << file.fileName();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ChatListModel::saveChats() const
|
|
|
|
{
|
2023-05-06 20:01:14 -04:00
|
|
|
QElapsedTimer timer;
|
|
|
|
timer.start();
|
2023-05-06 18:51:30 -04:00
|
|
|
const QString savePath = Download::globalInstance()->downloadLocalModelsPath();
|
2023-05-04 15:31:41 -04:00
|
|
|
for (Chat *chat : m_chats) {
|
2023-05-13 19:05:35 -04:00
|
|
|
if (chat == m_serverChat)
|
|
|
|
continue;
|
2023-05-15 18:36:41 -04:00
|
|
|
const bool isChatGPT = chat->modelName().startsWith("chatgpt-");
|
|
|
|
if (!isChatGPT && !m_shouldSaveChats)
|
|
|
|
continue;
|
|
|
|
if (isChatGPT && !m_shouldSaveChatGPTChats)
|
|
|
|
continue;
|
2023-05-05 10:47:05 -04:00
|
|
|
QString fileName = "gpt4all-" + chat->id() + ".chat";
|
2023-05-06 18:51:30 -04:00
|
|
|
QFile file(savePath + "/" + fileName);
|
2023-05-04 15:31:41 -04:00
|
|
|
bool success = file.open(QIODevice::WriteOnly);
|
|
|
|
if (!success) {
|
|
|
|
qWarning() << "ERROR: Couldn't save chat to file:" << file.fileName();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
QDataStream out(&file);
|
2023-05-06 18:51:30 -04:00
|
|
|
|
|
|
|
out << (quint32)CHAT_FORMAT_MAGIC;
|
|
|
|
out << (qint32)CHAT_FORMAT_VERSION;
|
2023-05-07 06:39:32 -04:00
|
|
|
out.setVersion(QDataStream::Qt_6_2);
|
2023-05-06 18:51:30 -04:00
|
|
|
|
2023-05-05 10:47:05 -04:00
|
|
|
qDebug() << "serializing chat" << fileName;
|
2023-05-08 05:52:57 -04:00
|
|
|
if (!chat->serialize(out, CHAT_FORMAT_VERSION)) {
|
2023-05-04 15:31:41 -04:00
|
|
|
qWarning() << "ERROR: Couldn't serialize chat to file:" << file.fileName();
|
|
|
|
file.remove();
|
|
|
|
}
|
|
|
|
file.close();
|
|
|
|
}
|
2023-05-06 20:01:14 -04:00
|
|
|
qint64 elapsedTime = timer.elapsed();
|
|
|
|
qDebug() << "serializing chats took:" << elapsedTime << "ms";
|
2023-05-04 15:31:41 -04:00
|
|
|
}
|
|
|
|
|
2023-05-06 20:01:14 -04:00
|
|
|
void ChatsRestoreThread::run()
|
2023-05-04 15:31:41 -04:00
|
|
|
{
|
2023-05-06 20:01:14 -04:00
|
|
|
QElapsedTimer timer;
|
|
|
|
timer.start();
|
2023-05-07 09:20:09 -04:00
|
|
|
struct FileInfo {
|
|
|
|
bool oldFile;
|
|
|
|
qint64 creationDate;
|
|
|
|
QString file;
|
|
|
|
};
|
|
|
|
QList<FileInfo> files;
|
2023-05-06 18:51:30 -04:00
|
|
|
{
|
|
|
|
// Look for any files in the original spot which was the settings config directory
|
|
|
|
QSettings settings;
|
|
|
|
QFileInfo settingsInfo(settings.fileName());
|
|
|
|
QString settingsPath = settingsInfo.absolutePath();
|
|
|
|
QDir dir(settingsPath);
|
|
|
|
dir.setNameFilters(QStringList() << "gpt4all-*.chat");
|
|
|
|
QStringList fileNames = dir.entryList();
|
|
|
|
for (QString f : fileNames) {
|
|
|
|
QString filePath = settingsPath + "/" + f;
|
|
|
|
QFile file(filePath);
|
|
|
|
bool success = file.open(QIODevice::ReadOnly);
|
|
|
|
if (!success) {
|
|
|
|
qWarning() << "ERROR: Couldn't restore chat from file:" << file.fileName();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
QDataStream in(&file);
|
2023-05-07 09:20:09 -04:00
|
|
|
FileInfo info;
|
|
|
|
info.oldFile = true;
|
|
|
|
info.file = filePath;
|
|
|
|
in >> info.creationDate;
|
|
|
|
files.append(info);
|
2023-05-06 18:51:30 -04:00
|
|
|
file.close();
|
2023-05-04 15:31:41 -04:00
|
|
|
}
|
2023-05-06 18:51:30 -04:00
|
|
|
}
|
|
|
|
{
|
|
|
|
const QString savePath = Download::globalInstance()->downloadLocalModelsPath();
|
|
|
|
QDir dir(savePath);
|
|
|
|
dir.setNameFilters(QStringList() << "gpt4all-*.chat");
|
|
|
|
QStringList fileNames = dir.entryList();
|
|
|
|
for (QString f : fileNames) {
|
|
|
|
QString filePath = savePath + "/" + f;
|
|
|
|
QFile file(filePath);
|
|
|
|
bool success = file.open(QIODevice::ReadOnly);
|
|
|
|
if (!success) {
|
|
|
|
qWarning() << "ERROR: Couldn't restore chat from file:" << file.fileName();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
QDataStream in(&file);
|
|
|
|
// Read and check the header
|
|
|
|
quint32 magic;
|
|
|
|
in >> magic;
|
|
|
|
if (magic != CHAT_FORMAT_MAGIC) {
|
|
|
|
qWarning() << "ERROR: Chat file has bad magic:" << file.fileName();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read the version
|
|
|
|
qint32 version;
|
|
|
|
in >> version;
|
2023-05-08 16:50:21 -04:00
|
|
|
if (version < 1) {
|
2023-05-06 18:51:30 -04:00
|
|
|
qWarning() << "ERROR: Chat file has non supported version:" << file.fileName();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-05-08 16:50:21 -04:00
|
|
|
if (version <= 1)
|
2023-05-07 06:39:32 -04:00
|
|
|
in.setVersion(QDataStream::Qt_6_2);
|
2023-05-06 18:51:30 -04:00
|
|
|
|
2023-05-07 09:20:09 -04:00
|
|
|
FileInfo info;
|
|
|
|
info.oldFile = false;
|
|
|
|
info.file = filePath;
|
|
|
|
in >> info.creationDate;
|
|
|
|
files.append(info);
|
|
|
|
file.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
std::sort(files.begin(), files.end(), [](const FileInfo &a, const FileInfo &b) {
|
|
|
|
return a.creationDate > b.creationDate;
|
|
|
|
});
|
|
|
|
|
|
|
|
for (FileInfo &f : files) {
|
|
|
|
QFile file(f.file);
|
|
|
|
bool success = file.open(QIODevice::ReadOnly);
|
|
|
|
if (!success) {
|
|
|
|
qWarning() << "ERROR: Couldn't restore chat from file:" << file.fileName();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
QDataStream in(&file);
|
|
|
|
|
2023-05-08 05:52:57 -04:00
|
|
|
qint32 version = 0;
|
2023-05-07 09:20:09 -04:00
|
|
|
if (!f.oldFile) {
|
|
|
|
// Read and check the header
|
|
|
|
quint32 magic;
|
|
|
|
in >> magic;
|
|
|
|
if (magic != CHAT_FORMAT_MAGIC) {
|
|
|
|
qWarning() << "ERROR: Chat file has bad magic:" << file.fileName();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read the version
|
|
|
|
in >> version;
|
2023-05-08 05:52:57 -04:00
|
|
|
if (version < 1) {
|
2023-05-07 09:20:09 -04:00
|
|
|
qWarning() << "ERROR: Chat file has non supported version:" << file.fileName();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-05-08 05:52:57 -04:00
|
|
|
if (version <= 1)
|
2023-05-07 09:20:09 -04:00
|
|
|
in.setVersion(QDataStream::Qt_6_2);
|
|
|
|
}
|
|
|
|
|
|
|
|
qDebug() << "deserializing chat" << f.file;
|
|
|
|
|
2023-05-06 20:01:14 -04:00
|
|
|
Chat *chat = new Chat;
|
|
|
|
chat->moveToThread(qApp->thread());
|
2023-05-08 05:52:57 -04:00
|
|
|
if (!chat->deserialize(in, version)) {
|
2023-05-06 18:51:30 -04:00
|
|
|
qWarning() << "ERROR: Couldn't deserialize chat from file:" << file.fileName();
|
|
|
|
file.remove();
|
|
|
|
} else {
|
2023-05-07 09:20:09 -04:00
|
|
|
emit chatRestored(chat);
|
2023-05-06 18:51:30 -04:00
|
|
|
}
|
2023-05-07 09:20:09 -04:00
|
|
|
if (f.oldFile)
|
|
|
|
file.remove(); // No longer storing in this directory
|
2023-05-06 18:51:30 -04:00
|
|
|
file.close();
|
2023-05-04 15:31:41 -04:00
|
|
|
}
|
2023-05-07 09:20:09 -04:00
|
|
|
|
2023-05-06 20:01:14 -04:00
|
|
|
qint64 elapsedTime = timer.elapsed();
|
|
|
|
qDebug() << "deserializing chats took:" << elapsedTime << "ms";
|
|
|
|
}
|
|
|
|
|
2023-05-07 09:20:09 -04:00
|
|
|
void ChatListModel::restoreChat(Chat *chat)
|
2023-05-06 20:01:14 -04:00
|
|
|
{
|
2023-05-07 09:20:09 -04:00
|
|
|
chat->setParent(this);
|
|
|
|
connect(chat, &Chat::nameChanged, this, &ChatListModel::nameChanged);
|
2023-05-08 20:51:03 -04:00
|
|
|
connect(chat, &Chat::modelLoadingError, this, &ChatListModel::handleModelLoadingError);
|
2023-05-07 09:20:09 -04:00
|
|
|
|
|
|
|
if (m_dummyChat) {
|
|
|
|
beginResetModel();
|
|
|
|
m_chats = QList<Chat*>({chat});
|
|
|
|
setCurrentChat(chat);
|
|
|
|
delete m_dummyChat;
|
|
|
|
m_dummyChat = nullptr;
|
|
|
|
endResetModel();
|
|
|
|
} else {
|
|
|
|
beginInsertRows(QModelIndex(), m_chats.size(), m_chats.size());
|
|
|
|
m_chats.append(chat);
|
|
|
|
endInsertRows();
|
2023-05-06 20:01:14 -04:00
|
|
|
}
|
2023-05-07 09:20:09 -04:00
|
|
|
}
|
2023-05-06 20:01:14 -04:00
|
|
|
|
2023-05-07 09:20:09 -04:00
|
|
|
void ChatListModel::chatsRestoredFinished()
|
|
|
|
{
|
|
|
|
if (m_dummyChat) {
|
2023-05-07 11:24:07 -04:00
|
|
|
beginResetModel();
|
|
|
|
Chat *dummy = m_dummyChat;
|
2023-05-07 09:20:09 -04:00
|
|
|
m_dummyChat = nullptr;
|
2023-05-07 11:24:07 -04:00
|
|
|
m_chats.clear();
|
|
|
|
addChat();
|
|
|
|
delete dummy;
|
|
|
|
endResetModel();
|
2023-05-07 09:20:09 -04:00
|
|
|
}
|
2023-05-06 20:01:14 -04:00
|
|
|
|
2023-05-07 09:20:09 -04:00
|
|
|
if (m_chats.isEmpty())
|
2023-05-06 20:01:14 -04:00
|
|
|
addChat();
|
2023-05-11 16:46:25 -04:00
|
|
|
|
|
|
|
addServerChat();
|
2023-05-04 15:31:41 -04:00
|
|
|
}
|
2023-05-11 16:46:25 -04:00
|
|
|
|
|
|
|
void ChatListModel::handleServerEnabledChanged()
|
|
|
|
{
|
|
|
|
if (LLM::globalInstance()->serverEnabled() || m_serverChat != m_currentChat)
|
|
|
|
return;
|
|
|
|
|
|
|
|
Chat *nextChat = get(0);
|
|
|
|
Q_ASSERT(nextChat && nextChat != m_serverChat);
|
|
|
|
setCurrentChat(nextChat);
|
|
|
|
}
|