add removal of chat sessions, auto title + icon, proper stick-to-bottom as response is streamed

This commit is contained in:
2026-08-20 17:40:33 +02:00
parent 47bab21e22
commit 9657e5092a
12 changed files with 776 additions and 273 deletions
+27 -75
View File
@@ -1,16 +1,14 @@
#include "session.hpp"
#include "chatstore.hpp"
#include <QDebug>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QtGlobal>
namespace ZShell {
ChatSession::ChatSession(const QString& id, QObject* parent)
: QObject(parent), m_id(id) {
}
: QObject(parent), m_id(id) {}
void ChatSession::setTitle(const QString& value) {
if (m_title == value)
@@ -19,6 +17,13 @@ void ChatSession::setTitle(const QString& value) {
Q_EMIT titleChanged();
}
void ChatSession::setIcon(const QString& value) {
if (m_icon == value)
return;
m_icon = value;
Q_EMIT iconChanged();
}
void ChatSession::setUpdatedAt(qint64 value) {
if (m_updatedAt == value)
return;
@@ -26,6 +31,13 @@ void ChatSession::setUpdatedAt(qint64 value) {
Q_EMIT updatedAtChanged();
}
void ChatSession::setPinned(bool value) {
if (m_pinned == value)
return;
m_pinned = value;
Q_EMIT pinnedChanged();
}
void ChatSession::setCount(int value) {
if (m_messageCount == value)
return;
@@ -45,47 +57,17 @@ void ChatSession::setMeta(
}
void ChatSession::ensureLoaded() {
if (m_loaded || m_path.isEmpty())
if (m_loaded)
return;
m_loaded = true;
loadMessages();
if (auto* store = qobject_cast<ChatStore*>(parent()))
store->loadMessagesInto(this);
}
void ChatSession::loadMessages() {
QFile file(m_path);
if (file.open(QIODevice::ReadOnly)) {
const QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
file.close();
if (doc.isObject()) {
const QJsonObject obj = doc.object();
m_title = obj["title"].toString(m_title);
m_createdAt = obj["createdAt"].toVariant().toLongLong() || m_createdAt;
m_updatedAt = obj["updatedAt"].toVariant().toLongLong() || m_updatedAt;
for (const QJsonValue& value : obj["messages"].toArray()) {
const QJsonObject msg = value.toObject();
const QString content = msg["content"].toString();
const QString reasoning = msg["reasoning"].toString();
if (content.isEmpty() && reasoning.isEmpty())
continue;
auto* message = new ChatMessage(
msg["role"].toString() == "user" ? ChatMessage::Role::User
: ChatMessage::Role::Assistant,
content,
msg["timestamp"].toVariant().toLongLong(),
this);
message->setReasoning(reasoning);
message->setElapsedMs(
msg["reasoningElapsedMs"].toVariant().toLongLong(),
msg["contentElapsedMs"].toVariant().toLongLong());
m_messages.append(message);
}
}
} else {
qWarning() << "ChatSession: failed to load" << m_path << ":"
<< file.errorString();
}
setCount(int(m_messages.size()));
Q_EMIT titleChanged();
void ChatSession::adoptMessages(QList<ChatMessage*> messages) {
qDeleteAll(m_messages);
m_messages = messages;
setCount(m_messages.size());
Q_EMIT messagesChanged();
}
@@ -94,7 +76,7 @@ ChatMessage* ChatSession::appendMessage(
ensureLoaded();
auto* message = new ChatMessage(role, content, timestamp, this);
m_messages.append(message);
setCount(int(m_messages.size()));
setCount(m_messages.size());
Q_EMIT messagesChanged();
return message;
}
@@ -103,7 +85,7 @@ void ChatSession::removeMessage(ChatMessage* message) {
if (!message || !m_messages.removeOne(message))
return;
delete message;
setCount(int(m_messages.size()));
setCount(m_messages.size());
Q_EMIT messagesChanged();
}
@@ -117,34 +99,4 @@ void ChatSession::clearMessages() {
Q_EMIT messagesChanged();
}
QJsonObject ChatSession::document() {
ensureLoaded();
QJsonArray arr;
for (const auto* message : m_messages) {
QJsonObject messageObj;
messageObj[QStringLiteral("role")] =
message->role() == ChatMessage::Role::User
? QStringLiteral("user")
: QStringLiteral("assistant");
messageObj[QStringLiteral("content")] = message->content();
if (!message->reasoning().isEmpty())
messageObj[QStringLiteral("reasoning")] = message->reasoning();
const qint64 reasoningMs = message->reasoningElapsedMs();
if (reasoningMs > 0)
messageObj[QStringLiteral("reasoningElapsedMs")] = reasoningMs;
const qint64 contentMs = message->contentElapsedMs();
if (contentMs > 0)
messageObj[QStringLiteral("contentElapsedMs")] = contentMs;
messageObj[QStringLiteral("timestamp")] = message->timestamp();
arr.append(messageObj);
}
QJsonObject doc;
doc[QStringLiteral("id")] = m_id;
doc[QStringLiteral("title")] = m_title;
doc[QStringLiteral("createdAt")] = m_createdAt;
doc[QStringLiteral("updatedAt")] = m_updatedAt;
doc[QStringLiteral("messages")] = arr;
return doc;
}
} // namespace ZShell