115 lines
4.1 KiB
C++
115 lines
4.1 KiB
C++
#ifndef CHAT_BLOCK_MODEL_H
|
|
#define CHAT_BLOCK_MODEL_H
|
|
|
|
#include <QAbstractListModel>
|
|
#include <QList>
|
|
#include <QString>
|
|
#include <QFutureWatcher>
|
|
|
|
// A single block in the chat output
|
|
struct ChatBlock {
|
|
enum Type {
|
|
User, // [user] markdown
|
|
Assistant, // [assistant] markdown
|
|
Reasoning, // [reasoning] content
|
|
Call, // [call:name] args
|
|
Tool, // [tool:name] output
|
|
Error, // [error] text
|
|
Info // [info] text
|
|
};
|
|
|
|
Type type;
|
|
QString name; // tool/call name, empty for others
|
|
QString responseId; // stable assistant response ID
|
|
QString content; // raw text content
|
|
mutable QVariantList cachedSubBlocks; // cached parseSubBlocks result
|
|
mutable QString cachedContent; // content when subBlocks were cached
|
|
};
|
|
|
|
// A sub-block within a markdown block (prose, code fence, diff)
|
|
struct ContentBlock {
|
|
enum Type {
|
|
Prose, // regular markdown text
|
|
Code, // fenced code block
|
|
Diff, // unified diff (detected by content pattern)
|
|
Diagram // mermaid/plantuml rendered as image
|
|
};
|
|
|
|
Type type;
|
|
QString language; // for Code and Diagram blocks
|
|
QString content;
|
|
};
|
|
|
|
class ChatBlockModel : public QAbstractListModel
|
|
{
|
|
Q_OBJECT
|
|
Q_PROPERTY(QString rawText READ rawText WRITE setRawText NOTIFY rawTextChanged)
|
|
Q_PROPERTY(QString filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged)
|
|
Q_PROPERTY(bool filtering READ filtering NOTIFY filterTextChanged)
|
|
|
|
public:
|
|
enum Roles {
|
|
BlockTypeRole = Qt::UserRole + 1,
|
|
NameRole,
|
|
ContentRole,
|
|
SubBlocksRole, // QVariantList of sub-block maps
|
|
FormattedContentRole, // pre-formatted HTML for call/tool expanded view
|
|
ResponseIdRole,
|
|
};
|
|
|
|
explicit ChatBlockModel(QObject *parent = nullptr);
|
|
|
|
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;
|
|
|
|
QString rawText() const { return m_rawText; }
|
|
void setRawText(const QString &text);
|
|
|
|
Q_INVOKABLE void appendRawText(const QString &text);
|
|
|
|
QString filterText() const { return m_filterText; }
|
|
void setFilterText(const QString &text);
|
|
bool filtering() const { return !m_filterText.isEmpty(); }
|
|
|
|
Q_INVOKABLE QString highlightedHtml(const QString &code, const QString &language) const;
|
|
Q_INVOKABLE QString prettyJson(const QString &text) const;
|
|
Q_INVOKABLE void copyToClipboard(const QString &text) const;
|
|
Q_INVOKABLE QString linkifyFilePaths(const QString &markdown) const;
|
|
Q_INVOKABLE void openLink(const QString &link) const;
|
|
Q_INVOKABLE bool executeInTerminal(const QString &code, const QString &language,
|
|
const QString &workingDirectory = QString()) const;
|
|
Q_INVOKABLE bool loadEarlier(); // returns true if more blocks were loaded
|
|
Q_INVOKABLE QString diagramUrl(const QString &source, const QString &language) const;
|
|
Q_INVOKABLE bool invalidateDiagram(const QString &source, const QString &language) const;
|
|
|
|
signals:
|
|
void rawTextChanged();
|
|
void filterTextChanged();
|
|
|
|
private slots:
|
|
void onParseFinished();
|
|
|
|
private:
|
|
void scheduleReparse();
|
|
void updateVisibleWindow();
|
|
void applyFilter();
|
|
static QList<ChatBlock> parseBlocks(const QString &text);
|
|
QVariantList parseSubBlocks(const QString &content) const;
|
|
|
|
QString m_rawText;
|
|
QString m_filterText;
|
|
QList<ChatBlock> m_allBlocks; // all parsed blocks
|
|
QList<ChatBlock> m_blocks; // visible window (tail of m_allBlocks)
|
|
QList<ChatBlock> m_filteredBlocks; // filtered subset when filterText is active
|
|
int m_visibleStart = 0; // index into m_allBlocks where visible window starts
|
|
mutable QHash<QString, QString> m_highlightCache;
|
|
|
|
// Async reparse state
|
|
QFutureWatcher<QList<ChatBlock>> m_parseWatcher;
|
|
bool m_parseDirty = false; // rawText changed while parse was in flight
|
|
bool m_parseRunning = false; // a parse is currently executing off-thread
|
|
};
|
|
|
|
#endif // CHAT_BLOCK_MODEL_H
|