#ifndef CHATBLOCKMODEL_H #define CHATBLOCKMODEL_H #include #include #include #include #include #include #include #include class QNetworkAccessManager; class QNetworkReply; namespace KSyntaxHighlighting { class Repository; } // A segment within a block — either text or a diagram struct BlockSegment { enum Type { Text, Diagram }; Type type = Text; QString html; // For Text: rendered HTML; for Diagram: highlighted source HTML QString diagramLang; // For Diagram: language (plantuml, mermaid) QString diagramSource; // For Diagram: raw source code QString diagramSvg; // For Diagram: base64 SVG data URI, or empty if loading/error QString diagramError; // For Diagram: error message if failed bool diagramLoading = false; // For Diagram: true if still loading }; struct ChatBlock { enum Type { User, Assistant, Reasoning, Call, Tool, Error, Info, Retry, Interrupt, Default }; Type type = Default; QString name; QString content; QString renderedHtml; // Legacy: full HTML (kept for compatibility) QList segments; // Structured segments for interactive rendering }; 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, SegmentsRole, // QVariantList of segment maps }; 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); Q_INVOKABLE void clearDiagramCache(); signals: void countChanged(); private slots: void onDiagramReply(QNetworkReply *reply); 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; // Diagram rendering bool isDiagramLanguage(const QString &lang) const; void requestDiagram(const QString &lang, const QString &code, int blockIndex); QString diagramPlaceholder(const QString &lang) const; QString diagramErrorHtml(const QString &error) const; QString diagramSvgHtml(const QByteArray &svg) const; QString diagramPngHtml(const QByteArray &png) const; QString diagramJpegHtml(const QByteArray &jpeg) const; // Convert segments to QVariantList for QML QVariantList segmentsToVariant(const QList &segments) const; State m_state = State::BlockStart; KSyntaxHighlighting::Repository *m_repo = nullptr; QNetworkAccessManager *m_netManager = nullptr; QList m_blocks; QString m_lineBuffer; // Code fence state (for streaming) bool m_inCodeFence = false; QString m_fenceLanguage; QString m_fenceContent; // Diagram cache: hash of (lang + code) -> SVG data QHash m_diagramCache; // Pending requests: hash -> block indices waiting for this diagram QHash> m_pendingDiagrams; // Track which requests are in flight QSet m_inFlightRequests; static const QRegularExpression s_headerRe; static const QRegularExpression s_endRe; static const QRegularExpression s_fenceOpenRe; static const QRegularExpression s_fenceCloseRe; }; #endif // CHATBLOCKMODEL_H