#ifndef CHATBLOCKMODEL_H #define CHATBLOCKMODEL_H #include #include #include #include #include namespace KSyntaxHighlighting { class Repository; } struct ChatBlock { enum Type { User, Assistant, Reasoning, Call, Tool, Error, Info, Retry, Interrupt, Default }; Type type = Default; QString name; QString content; QString renderedHtml; // Rendered HTML with syntax highlighting }; class ChatBlockModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(int count READ rowCount NOTIFY countChanged) public: // Two-state parser for [[[blockname]]] ... [[[end]]] blocks. enum class State { BlockStart, // Waiting for a block header BlockEnd // Collecting content until [[[end]]] }; enum Roles { BlockTypeRole = Qt::UserRole + 1, BlockNameRole, ContentRole, RenderedHtmlRole, }; explicit ChatBlockModel(QObject *parent = nullptr); ~ChatBlockModel() override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QHash roleNames() const override; Q_INVOKABLE void appendData(const QString &text); Q_INVOKABLE void reset(); Q_INVOKABLE void trimFront(int keepCount); signals: void countChanged(); private: void processLine(const QString &line); void appendContent(const QString &line); void rebuildBlockHtml(int blockIndex); QString highlightCode(const QString &code, const QString &language) const; QString escapeForHtml(const QString &text) const; QString getDefaultTextColor() const; State m_state = State::BlockStart; KSyntaxHighlighting::Repository *m_repo = nullptr; QList m_blocks; QString m_lineBuffer; // Code fence state (for streaming) bool m_inCodeFence = false; QString m_fenceLanguage; QString m_fenceContent; static const QRegularExpression s_headerRe; static const QRegularExpression s_endRe; static const QRegularExpression s_fenceOpenRe; static const QRegularExpression s_fenceCloseRe; }; #endif // CHATBLOCKMODEL_H