#pragma once #include #include #include #include #include namespace ZShell::llm { // One unit of activity within a ChatGeneration. A generation keeps its // segments in chronological order: zero or more reasoning bursts and // tool calls interleaved, plus at most one content segment holding the // final answer. class LlmSegment : public QObject { Q_OBJECT QML_ELEMENT QML_UNCREATABLE("Segments are managed by ChatGeneration") Q_PROPERTY(Type type READ type CONSTANT) Q_PROPERTY(qint64 timestamp READ timestamp CONSTANT) Q_PROPERTY(QString text READ text NOTIFY textChanged) Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString toolCallId READ toolCallId NOTIFY toolCallIdChanged) Q_PROPERTY(QString arguments READ arguments NOTIFY argumentsChanged) Q_PROPERTY(QString result READ result NOTIFY resultChanged) Q_PROPERTY(Status status READ status NOTIFY statusChanged) Q_PROPERTY(bool running READ running NOTIFY runningChanged) Q_PROPERTY(qint64 elapsedMs READ elapsedMs NOTIFY elapsedMsChanged) // Parsed markdown blocks (QVariantList of maps, see MarkdownParser). // Only Content segments are parsed. Refreshes are debounced so a // streaming segment re-parses at a steady cadence rather than per // chunk. Q_PROPERTY(QVariantList markdown READ markdown NOTIFY markdownChanged) public: enum class Type : int { Reasoning = 0, ToolCall, Content }; Q_ENUM(Type) enum class Status : int { None = 0, Running, Success, Error }; Q_ENUM(Status) explicit LlmSegment(Type type, qint64 timestamp, QObject* parent = nullptr); [[nodiscard]] Type type() const { return m_type; } [[nodiscard]] qint64 timestamp() const { return m_timestamp; } [[nodiscard]] QString text() const { return m_text; } [[nodiscard]] QString name() const { return m_name; } [[nodiscard]] QString toolCallId() const { return m_toolCallId; } [[nodiscard]] QString arguments() const { return m_arguments; } [[nodiscard]] QString result() const { return m_result; } [[nodiscard]] Status status() const { return m_status; } [[nodiscard]] bool running() const { return m_running; } [[nodiscard]] qint64 elapsedMs() const; [[nodiscard]] QVariantList markdown() const { return m_markdown; } // Starts the segment's clock; a no-op while already running. void begin(); // Stops the segment's clock; a no-op when not running. void close(); void appendText(const QString& piece); void setText(const QString& value); void setName(const QString& value); void setToolCallId(const QString& value); void appendArguments(const QString& piece); void setResult(const QString& value); void setStatus(Status value); // Completes a tool call with the model-facing result text. void finishTool(const QString& resultText, bool success); // Restores persisted timing without a live clock. void restore(qint64 elapsedMs); Q_SIGNALS: void textChanged(); void markdownChanged(); void nameChanged(); void toolCallIdChanged(); void argumentsChanged(); void resultChanged(); void statusChanged(); void runningChanged(); void elapsedMsChanged(); private: void scheduleMarkdown(); // Kicks off an off-thread parse; returns false when one is already // in flight (the pending change is picked up when it completes). bool parseMarkdown(); Type m_type; qint64 m_timestamp; QString m_text; QString m_name; QString m_toolCallId; QString m_arguments; QString m_result; Status m_status = Status::None; bool m_running = false; qint64 m_startedAt = 0; qint64 m_endedAt = 0; QVariantList m_markdown; QTimer m_markdownTimer; bool m_markdownDirty = false; bool m_parseInFlight = false; }; } // namespace ZShell::llm