From 7703d064300e6d686aaeb1c0abc90f91ef246726 Mon Sep 17 00:00:00 2001 From: Levi Neely <141506390+lneely@users.noreply.github.com> Date: Sat, 18 Jul 2026 18:12:10 +0200 Subject: [PATCH] gui: revert incremental parsing, use full reparse always Incremental parsing caused phantom blocks and disappearing content. The 150ms coalescing on the daemon side is sufficient for perf. updateVisibleWindow() extraction kept for clean separation. --- gui/chatblockmodel.cpp | 65 +----------------------------------------- gui/chatblockmodel.h | 2 -- 2 files changed, 1 insertion(+), 66 deletions(-) diff --git a/gui/chatblockmodel.cpp b/gui/chatblockmodel.cpp index e9d9e61..de62ad6 100644 --- a/gui/chatblockmodel.cpp +++ b/gui/chatblockmodel.cpp @@ -106,80 +106,17 @@ QHash ChatBlockModel::roleNames() const void ChatBlockModel::setRawText(const QString &text) { if (text == m_rawText) return; - - // If text is just appended (common during streaming), only reparse from the last block - bool isAppend = text.startsWith(m_rawText) && !m_rawText.isEmpty(); m_rawText = text; - - if (isAppend && !m_allBlocks.isEmpty()) { - reparseIncremental(); - } else { - // Full reparse (session switch, initial load) - m_allBlocks.clear(); - m_lastBlockTextOffset = 0; - beginResetModel(); - m_blocks.clear(); - m_visibleStart = 0; - endResetModel(); - reparse(); - } + reparse(); emit rawTextChanged(); } static constexpr int INITIAL_BLOCKS = 20; static constexpr int LOAD_MORE_COUNT = 30; -void ChatBlockModel::reparseIncremental() -{ - // Fast path: just update the last block's content from the raw text tail. - // Only do a full reparse if a new block header appeared. - QString tail = m_rawText.mid(m_lastBlockTextOffset); - - // Check if a new block header appeared in the NEW portion (after old last-block content) - int oldLastBlockLen = m_allBlocks.isEmpty() ? 0 : m_allBlocks.last().content.size(); - // The header itself is at m_lastBlockTextOffset, content starts after it + newline - // Simple heuristic: if the tail contains another header pattern, do full reparse - static const QRegularExpression headerRe( - R"(^\[(user|assistant|reasoning|call|tool|error|info)(?::([^\]]*))\])", - QRegularExpression::MultilineOption); - auto it = headerRe.globalMatch(tail); - int headerCount = 0; - while (it.hasNext()) { it.next(); headerCount++; } - - if (headerCount > 1) { - // New block boundary appeared — fall back to full reparse - reparse(); - return; - } - - // Common case: just the last block grew. Update its content directly. - if (!m_allBlocks.isEmpty()) { - QList tailBlocks = parseBlocks(tail); - if (!tailBlocks.isEmpty()) { - m_allBlocks.last() = tailBlocks.last(); - } - } - - updateVisibleWindow(); -} - void ChatBlockModel::reparse() { m_allBlocks = parseBlocks(m_rawText); - // Track where the last block starts for future incremental parses - if (!m_allBlocks.isEmpty()) { - static const QRegularExpression headerRe( - R"(^\[(user|assistant|reasoning|call|tool|error|info)(?::([^\]]*))\])", - QRegularExpression::MultilineOption); - auto it = headerRe.globalMatch(m_rawText); - int lastPos = 0; - while (it.hasNext()) { - auto match = it.next(); - lastPos = match.capturedStart(); - } - m_lastBlockTextOffset = lastPos; - } - updateVisibleWindow(); } diff --git a/gui/chatblockmodel.h b/gui/chatblockmodel.h index 6f3db54..eb12690 100644 --- a/gui/chatblockmodel.h +++ b/gui/chatblockmodel.h @@ -74,7 +74,6 @@ signals: private: void reparse(); - void reparseIncremental(); void updateVisibleWindow(); QList parseBlocks(const QString &text) const; QVariantList parseSubBlocks(const QString &content) const; @@ -83,7 +82,6 @@ private: QList m_allBlocks; // all parsed blocks QList m_blocks; // visible window (tail of m_allBlocks) int m_visibleStart = 0; // index into m_allBlocks where visible window starts - int m_lastBlockTextOffset = 0; // char offset in m_rawText where last block starts mutable QHash m_highlightCache; };