127 lines
4.1 KiB
C++
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
|