#pragma once #include "messagemodel.hpp" #include "message.hpp" #include #include #include #include namespace ZShell::llm { class LlmClient; class ChatSession : public QObject { Q_OBJECT QML_ELEMENT QML_UNCREATABLE("Chat sessions are managed by Chat.chats") Q_PROPERTY(QString id READ id CONSTANT) Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged) Q_PROPERTY(QString icon READ icon NOTIFY iconChanged) Q_PROPERTY(QDateTime createdAt READ createdAt CONSTANT) Q_PROPERTY(QDateTime updatedAt READ updatedAt NOTIFY updatedAtChanged) Q_PROPERTY(bool pinned READ pinned NOTIFY pinnedChanged) Q_PROPERTY(bool loaded READ isLoaded NOTIFY loaded) Q_PROPERTY(int messageCount READ messageCount NOTIFY messageCountChanged) Q_PROPERTY( ZShell::llm::ChatMessageModel* messagesModel READ messagesModel CONSTANT) public: explicit ChatSession(const QString& id, QObject* parent = nullptr); [[nodiscard]] QString id() const { return m_id; } [[nodiscard]] QString title() const { return m_title; } [[nodiscard]] QString icon() const { return m_icon; } [[nodiscard]] QDateTime createdAt() const { return QDateTime::fromMSecsSinceEpoch(m_createdAt); } [[nodiscard]] QDateTime updatedAt() const { return QDateTime::fromMSecsSinceEpoch(m_updatedAt); } [[nodiscard]] qint64 createdAtMs() const { return m_createdAt; } [[nodiscard]] qint64 updatedAtMs() const { return m_updatedAt; } [[nodiscard]] bool pinned() const { return m_pinned; } [[nodiscard]] int messageCount() const { return m_messageCount; } // The messages model; the first access starts the (async) load // from the store. [[nodiscard]] ChatMessageModel* messagesModel(); void ensureLoaded(); // True once the async load from the store has finished. [[nodiscard]] bool isLoaded() const { return m_loaded; } // The model without triggering a load (ChatStore use during the // load itself). [[nodiscard]] ChatMessageModel* model() const { return m_model; } void markLoaded(); [[nodiscard]] bool takeClearPending(); [[nodiscard]] LlmClient* client() const; [[nodiscard]] int lastTokenCount() const { return m_lastTokenCount; } void setLastTokenCount(int value); void setTitle(const QString& value); void setIcon(const QString& value); void setUpdatedAt(qint64 value); void setPinned(bool value); void setMeta( const QString& title, qint64 createdAt, qint64 updatedAt, int messageCount); // Replaces the model's rows with `messages` (most recent first). void adoptMessages(QList messages); void persist(); void removeMessage(ChatMessage* message); void clearMessages(); Q_INVOKABLE void sendMessage(const QString& text); Q_INVOKABLE void retry(ZShell::llm::ChatMessage* target); Q_INVOKABLE void continueFrom(ZShell::llm::ChatMessage* message); Q_INVOKABLE void clear(); Q_SIGNALS: void titleChanged(); void iconChanged(); void updatedAtChanged(); void pinnedChanged(); void messageCountChanged(); // The messages finished loading from the store. void loaded(); private: void onModelRowsChanged(); ChatMessage* appendNewest( ChatMessage::Role role, const QString& content, qint64 timestamp); void startGeneration(ChatMessage* target); void setCount(int value); QString m_id; QString m_title; QString m_icon; qint64 m_createdAt = 0; qint64 m_updatedAt = 0; bool m_pinned = false; int m_messageCount = 0; ChatMessageModel* m_model = nullptr; bool m_loadRequested = false; bool m_loaded = false; bool m_clearPending = false; int m_lastTokenCount = 0; }; } // namespace ZShell::llm