ollie/kde/gui/chatblockmodel.h

148 lines
5.3 KiB
C++

#ifndef CHATBLOCKMODEL_H
#define CHATBLOCKMODEL_H
#include <QAbstractListModel>
#include <QList>
#include <KSyntaxHighlighting/State>
#include <QString>
#include <QStringList>
#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
// content updates 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)
QStringList tableLines; // Accumulated table lines (being built)
bool inTable = false; // Currently accumulating table lines
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,
Context,
Reasoning,
Call,
Tool,
Error,
Info,
Retry,
Interrupt,
Default
};
Type type = Default;
QString content;
QString renderedHtml; // Full HTML render of content
QList<BlockSegment> segments; // Structured segments for interactive rendering
BlockRenderState renderState; // Incremental rendering state
QString blockId; // Stable identifier (8-char hex from server)
QString name; // For call blocks: tool name
QString format; // For tool blocks: output format hint
bool partial = false; // True if streaming (not final)
};
class ChatBlockModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
public:
enum Roles {
BlockTypeRole = Qt::UserRole + 1,
ContentRole,
RenderedHtmlRole,
SegmentsRole, // QVariantList of segment maps
BlockIdRole, // Stable block identifier
PartialRole, // True if streaming (not final)
};
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 int indexForBlockId(const QString &blockId) const;
signals:
void countChanged();
private slots:
void onDiagramReply(QNetworkReply *reply);
private:
void processJsonLine(const QByteArray &line);
void rebuildBlockHtml(int blockIndex);
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 renderProse(const QStringList &lines) const;
QString renderTable(const QStringList &tableLines) const;
static bool isTableLine(const QString &line);
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;
// Map block type string to enum
static ChatBlock::Type blockTypeFromString(const QString &role);
KSyntaxHighlighting::Repository *m_repo = nullptr;
QNetworkAccessManager *m_netManager = nullptr;
QList<ChatBlock> m_blocks;
QByteArray m_lineBuffer; // Buffer for incomplete JSON lines
// 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;
};
#endif // CHATBLOCKMODEL_H