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/kate/chat_renderer.h

127 lines
4.1 KiB
C++

#ifndef OLLIE_CHAT_RENDERER_H
#define OLLIE_CHAT_RENDERER_H
#include <QString>
#include <QList>
#include <QStringList>
#include <QTextEdit>
#include <QRegularExpression>
namespace KSyntaxHighlighting {
class Repository;
}
// Parsed chat block
struct ChatBlock {
enum Type {
User,
Assistant,
Reasoning,
Call,
Tool,
Error,
Info
};
Type type;
QString name; // tool/call name
QString content; // raw text content
};
// Sub-block within a message (prose, code fence, diff)
struct SubBlock {
enum Type { Prose, Code, Diff, Interrupt };
Type type;
QString language;
QString fenceMeta; // extra text after language on the fence line (e.g., "/path:10-20")
QString content;
};
/**
* ChatRenderer parses ollie chat output into structured blocks
* and renders them as styled HTML in a QTextEdit.
*
* To avoid performance degradation on long conversations, only a
* tail window of blocks is eagerly rendered. Earlier blocks are
* loaded on demand when the user scrolls up.
*/
class ChatRenderer
{
public:
ChatRenderer();
~ChatRenderer();
/// Set external diff storage. When set, diffs are stored here and
/// rendered as clickable links rather than inline.
void setDiffStorage(QStringList *storage) { m_diffStorage = storage; }
/// Storage for apply-able code blocks. Each entry is the code content.
/// Rendered with a clickable "Apply" link.
struct ApplyBlock {
QString filePath;
QString code;
int startLine = -1; // 1-indexed, inclusive. -1 = insert at cursor
int endLine = -1; // 1-indexed, inclusive. -1 = insert at cursor
};
void setApplyStorage(QList<ApplyBlock> *storage) { m_applyStorage = storage; }
/// Maximum number of blocks to render eagerly (tail window).
static constexpr int DefaultWindowSize = 40;
void setWindowSize(int n) { m_windowSize = n; }
int windowSize() const { return m_windowSize; }
/// Parse raw chat text into blocks
QList<ChatBlock> parseBlocks(const QString &text) const;
/// Parse a block's content into sub-blocks (prose, code, diff)
QList<SubBlock> parseSubBlocks(const QString &content) const;
/// Render the tail window of blocks into the QTextEdit.
/// Parses all blocks but only generates HTML for the last windowSize().
void render(QTextEdit *view, const QString &rawText);
/// Render incrementally: assumes rawText is the full text and only
/// the tail has changed since last render. Returns true if it did
/// an incremental update, false if it did a full re-render.
bool renderIncremental(QTextEdit *view, const QString &rawText);
/// Prepend older blocks into the view. Returns the number of
/// blocks actually prepended (0 if all blocks are already shown).
int prependOlder(QTextEdit *view, int count);
/// True if there are older blocks not yet rendered.
bool hasOlderBlocks() const { return m_renderStart > 0; }
/// Total number of parsed blocks.
int totalBlocks() const { return m_allBlocks.size(); }
/// Number of blocks currently rendered.
int renderedBlocks() const { return m_allBlocks.size() - m_renderStart; }
private:
QString blockToHtml(const ChatBlock &block) const;
QString subBlockToHtml(const SubBlock &sub, bool preserveWhitespace = false) const;
QString highlightCode(const QString &code, const QString &language) const;
QString renderDiff(const QString &diff) const;
QString renderProse(const QString &prose) const;
QString escapeHtml(const QString &text) const;
KSyntaxHighlighting::Repository *m_repo;
QStringList *m_diffStorage = nullptr;
QList<ApplyBlock> *m_applyStorage = nullptr;
QString m_lastRawText; // for incremental rendering
int m_lastBlockCount = 0;
// Windowed rendering state
QList<ChatBlock> m_allBlocks; // all parsed blocks
int m_renderStart = 0; // index of first rendered block
int m_windowSize = DefaultWindowSize;
// Colors (derived from palette)
mutable bool m_dark = false;
void detectTheme() const;
};
#endif // OLLIE_CHAT_RENDERER_H