This repository has been archived on 2026-08-16. You can view files and clone it, but cannot push or open issues or pull requests.
ollie-kde/gui/chatblockmodel.h

157 lines
5.3 KiB
C++

#ifndef CHATBLOCKMODEL_H
#define CHATBLOCKMODEL_H
#include <QAbstractListModel>
#include <QList>
#include <KSyntaxHighlighting/State>
#include <QString>
#include <QStringList>
#include <QRegularExpression>
#include <QHash>
#include <QSet>
#include <QVariantList>
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
};
// Incremental rendering state for a block — tracks where we left off so
// appendContent() can extend the last segment without reparsing from scratch.
struct BlockRenderState {
bool inFence = false; // Currently inside a code fence
QString fenceLang; // Language of the open fence
QStringList fenceLines; // Accumulated lines in the open fence
QStringList proseLines; // Accumulated prose lines (not yet flushed)
int committedLineCount = 0; // Number of content lines already processed incrementally
// Incremental syntax highlighting state for open code fences
KSyntaxHighlighting::State hlState; // Highlighter state after last processed line
QString hlHtml; // Accumulated highlighted HTML so far (without pre wrapper)
};
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<BlockSegment> segments; // Structured segments for interactive rendering
BlockRenderState renderState; // Incremental rendering state
};
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<int, QByteArray> 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);
void incrementalAppend(int blockIndex, const QString &line);
void renderLastSegmentPartial(int blockIndex, const QString &partialLine);
static void rebuildRenderedHtmlFromSegments(ChatBlock &block);
QString highlightCode(const QString &code, const QString &language) const;
QString highlightCodeWrapped(const QString &innerHtml) 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<BlockSegment> &segments) const;
State m_state = State::BlockStart;
KSyntaxHighlighting::Repository *m_repo = nullptr;
QNetworkAccessManager *m_netManager = nullptr;
QList<ChatBlock> 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<QString, QByteArray> m_diagramCache;
// Pending requests: hash -> block indices waiting for this diagram
QHash<QString, QList<int>> m_pendingDiagrams;
// Track which requests are in flight
QSet<QString> 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