#include "chatblockmodel.h" #include const QRegularExpression ChatBlockModel::s_headerRe( R"(^\[(user|assistant|reasoning|call|tool|error|info)(?::([^\]]*))?\]$)"); ChatBlockModel::ChatBlockModel(QObject *parent) : QAbstractListModel(parent) { } int ChatBlockModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; return m_blocks.size(); } QVariant ChatBlockModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= m_blocks.size()) return {}; const ChatBlock &b = m_blocks[index.row()]; switch (role) { case BlockTypeRole: switch (b.type) { case ChatBlock::User: return QStringLiteral("user"); case ChatBlock::Assistant: return QStringLiteral("assistant"); case ChatBlock::Reasoning: return QStringLiteral("reasoning"); case ChatBlock::Call: return QStringLiteral("call"); case ChatBlock::Tool: return QStringLiteral("tool"); case ChatBlock::Error: return QStringLiteral("error"); case ChatBlock::Info: return QStringLiteral("info"); case ChatBlock::Default: return QStringLiteral("default"); } return {}; case BlockNameRole: return b.name; case ContentRole: return b.content; } return {}; } QHash ChatBlockModel::roleNames() const { return { {BlockTypeRole, "blockType"}, {BlockNameRole, "blockName"}, {ContentRole, "blockContent"}, }; } void ChatBlockModel::appendData(const QString &text) { m_lineBuffer += text; int idx; while ((idx = m_lineBuffer.indexOf('\n')) >= 0) { QString line = m_lineBuffer.left(idx); m_lineBuffer = m_lineBuffer.mid(idx + 1); processLine(line); } } void ChatBlockModel::reset() { beginResetModel(); m_blocks.clear(); m_lineBuffer.clear(); endResetModel(); emit countChanged(); } void ChatBlockModel::trimFront(int keepCount) { if (m_blocks.size() <= keepCount) return; int removeCount = m_blocks.size() - keepCount; beginRemoveRows(QModelIndex(), 0, removeCount - 1); m_blocks.remove(0, removeCount); endRemoveRows(); emit countChanged(); } void ChatBlockModel::processLine(const QString &line) { QRegularExpressionMatch m = s_headerRe.match(line); if (m.hasMatch()) { // Header line — finalize current block, start new one QString typeStr = m.captured(1); QString qualifier = m.captured(2); ChatBlock::Type type; if (typeStr == "user") type = ChatBlock::User; else if (typeStr == "assistant") type = ChatBlock::Assistant; else if (typeStr == "reasoning") type = ChatBlock::Reasoning; else if (typeStr == "call") type = ChatBlock::Call; else if (typeStr == "tool") type = ChatBlock::Tool; else if (typeStr == "error") type = ChatBlock::Error; else if (typeStr == "info") type = ChatBlock::Info; else type = ChatBlock::Default; beginInsertRows(QModelIndex(), m_blocks.size(), m_blocks.size()); m_blocks.append({type, qualifier, QString()}); endInsertRows(); emit countChanged(); return; } // Content line — append to the last block if any if (m_blocks.isEmpty()) { // Mid-stream connect: create a Default block beginInsertRows(QModelIndex(), 0, 0); m_blocks.append({ChatBlock::Default, QString(), QString()}); endInsertRows(); emit countChanged(); } ChatBlock &last = m_blocks.last(); if (!last.content.isEmpty()) last.content += '\n'; last.content += line; QModelIndex idx = index(m_blocks.size() - 1); emit dataChanged(idx, idx, {ContentRole}); }