#pragma once #include "segment.hpp" #include #include #include #include #include namespace ZShell::llm { // One attempt at answering a message: a chronologically ordered list // of segments. Content bursts, reasoning bursts and tool calls all // appear in the order the model produced them; a new content (or // reasoning) segment starts whenever the model switches between them. // Together with the attempt's aggregate state. class ChatGeneration : public QObject { Q_OBJECT QML_ELEMENT QML_UNCREATABLE("Chat generations are managed by ChatMessage") Q_PROPERTY(QString content READ content WRITE setContent NOTIFY contentChanged) Q_PROPERTY(QString reasoning READ reasoning NOTIFY reasoningChanged) Q_PROPERTY(qint64 reasoningElapsedMs READ reasoningElapsedMs NOTIFY elapsedMsChanged) Q_PROPERTY(qint64 contentElapsedMs READ contentElapsedMs NOTIFY elapsedMsChanged) Q_PROPERTY(qint64 toolsElapsedMs READ toolsElapsedMs NOTIFY elapsedMsChanged) Q_PROPERTY(bool streaming READ streaming NOTIFY streamingChanged) Q_PROPERTY(bool reasoningActive READ reasoningActive NOTIFY reasoningActiveChanged) Q_PROPERTY(bool hasRunningTool READ hasRunningTool NOTIFY toolStateChanged) Q_PROPERTY(qint64 timestamp READ timestamp CONSTANT) Q_PROPERTY( QList segments READ segments NOTIFY segmentsChanged) Q_PROPERTY(int toolCallCount READ toolCallCount NOTIFY segmentsChanged) public: explicit ChatGeneration(qint64 timestamp, QObject* parent = nullptr); // All content bursts, joined (the model's full answer). [[nodiscard]] QString content() const; // Every reasoning burst, joined. [[nodiscard]] QString reasoning() const; // True while the model is thinking: streaming, no content yet, and // no tool call in flight. [[nodiscard]] bool reasoningActive() const; [[nodiscard]] qint64 reasoningElapsedMs() const; [[nodiscard]] qint64 contentElapsedMs() const; [[nodiscard]] qint64 toolsElapsedMs() const; [[nodiscard]] bool streaming() const { return m_streaming; } [[nodiscard]] qint64 timestamp() const { return m_timestamp; } [[nodiscard]] QList segments() const { return m_segments; } [[nodiscard]] int toolCallCount() const; [[nodiscard]] bool hasRunningTool() const; void setContent(const QString& value); void appendContent(const QString& piece); void appendReasoning(const QString& piece); void setStreaming(bool value); // The in-flight content segment, or a fresh one. A new content // segment starts whenever the model resumes writing after reasoning // or a tool call. [[nodiscard]] LlmSegment* openContentSegment(); // The in-flight reasoning segment, or a fresh one. [[nodiscard]] LlmSegment* openReasoningSegment(); // Creates and appends a running tool-call segment. [[nodiscard]] LlmSegment* beginToolCall( const QString& name, const QString& toolCallId); // Appends a segment created by the persistence layer. void addSegment(LlmSegment* segment); // Stops the clocks of every in-flight segment. void closeOpenSegments(); Q_SIGNALS: void contentChanged(); void reasoningChanged(); void reasoningActiveChanged(); void elapsedMsChanged(); void streamingChanged(); void toolStateChanged(); void segmentsChanged(); private: void updateReasoningActive(); QTimer m_timer; QList m_segments; bool m_reasoningActive = false; bool m_streaming = false; qint64 m_timestamp; }; } // namespace ZShell::llm