better chat list view + anchor to bottom. sqlite db for chats + LIFO-ordered

This commit is contained in:
2026-08-23 16:37:11 +02:00
parent 37c6a9986e
commit aae3757daf
36 changed files with 2653 additions and 1459 deletions
+15 -61
View File
@@ -1,23 +1,22 @@
#pragma once
#include <QByteArray>
#include <QList>
#include <QNetworkAccessManager>
#include <QObject>
#include <QString>
#include <QStringList>
#include <QtQml>
#include <functional>
#include "chatstore.hpp"
class QQmlEngine;
class QJSEngine;
class QNetworkReply;
namespace ZShell {
namespace ZShell::llm {
class LlmClient;
// QML-facing facade. Persistence lives in ChatStore, network streaming in
// LlmClient; this class only wires them together and exposes the
// application-wide state.
class Chat : public QObject {
Q_OBJECT
QML_ELEMENT
@@ -29,27 +28,22 @@ class Chat : public QObject {
Q_PROPERTY(QStringList availableModels READ availableModels NOTIFY availableModelsChanged)
Q_PROPERTY(int contextSize READ contextSize NOTIFY contextSizeChanged)
Q_PROPERTY(QString lastError READ lastError NOTIFY lastErrorChanged)
Q_PROPERTY(ChatStore* chats READ chats CONSTANT)
Q_PROPERTY(ZShell::llm::ChatStore* chats READ chats CONSTANT)
Q_PROPERTY(QString streamingChatId READ streamingChatId NOTIFY streamingChatIdChanged)
public:
explicit Chat(QObject* parent = nullptr);
~Chat();
[[nodiscard]] bool busy() const { return m_busy; }
[[nodiscard]] QString endpoint() const { return m_endpoint; }
[[nodiscard]] QString model() const { return m_model; }
[[nodiscard]] QStringList availableModels() const { return m_availableModels; }
[[nodiscard]] int contextSize() const { return m_contextSize; }
[[nodiscard]] int lastTokenCount() const { return m_lastTokenCount; }
[[nodiscard]] bool busy() const;
[[nodiscard]] QString endpoint() const;
[[nodiscard]] QString model() const;
[[nodiscard]] QStringList availableModels() const;
[[nodiscard]] int contextSize() const;
[[nodiscard]] QString lastError() const { return m_lastError; }
[[nodiscard]] ChatStore* chats() const { return m_store; }
[[nodiscard]] QString streamingChatId() const { return m_streamingChatId; }
[[nodiscard]] ChatSession* streamingSession() const { return m_active; }
[[nodiscard]] QString streamingChatId() const;
Q_INVOKABLE void send(const QString& chatId, const QString& content);
Q_INVOKABLE void stop();
Q_INVOKABLE void clearConversation(const QString& chatId);
Q_INVOKABLE void dismissError();
Q_INVOKABLE void refreshModels();
Q_INVOKABLE void selectModel(const QString& id);
@@ -67,51 +61,11 @@ class Chat : public QObject {
void streamingChatIdChanged();
private:
void beginAssistant();
void endStream();
void finalize();
void fail(const QString& message);
void handleLine(const QByteArray& line);
void drainBuffer();
void probeContextSize();
void setContextSize(int size);
void updateTokenUsage(const QJsonObject& data);
static void trimHistory(ChatSession* session, int contextSize);
void shortRequest(
const QString& tag,
const QString& systemPrompt,
const QString& userText,
std::function<void(QString result)> onResult);
void requestTitle(const QString& chatId, const QString& userText);
void requestIcon(const QString& chatId, const QString& userText);
static QString titleFrom(const QString& content);
static QString completionsPath(const QString& endpoint, const QString& subpath);
static QString serverErrorMessage(
const QByteArray& body, const QString& fallback);
static void setBusy(Chat* chat, bool value);
static void setStreamingChatId(Chat* chat, const QString& id);
QNetworkAccessManager m_manager;
ChatStore* m_store = nullptr;
QNetworkReply* m_reply = nullptr;
QByteArray m_buffer;
ChatSession* m_active = nullptr;
ChatMessage* m_streaming = nullptr;
QString m_pendingClearChatId;
bool m_busy = false;
QString m_endpoint;
QString m_model;
QStringList m_availableModels;
LlmClient* m_client = nullptr;
QString m_lastError;
QString m_streamingChatId;
double m_temperature = 0.7;
int m_contextSize = 0;
int m_lastTokenCount = 0;
static Chat* s_instance;
friend class ChatStore;
};
} // namespace ZShell
} // namespace ZShell::llm