gui: fix incremental parser producing phantom tool_call blocks

When the tail contains only one header (common case during streaming),
just update the last block content directly. Fall back to full reparse
only when a genuinely new block boundary appears.
This commit is contained in:
Levi Neely 2026-07-18 18:04:42 +02:00
parent 7ccce95445
commit c46f38fc93
1 changed files with 23 additions and 19 deletions

View File

@ -131,29 +131,33 @@ static constexpr int LOAD_MORE_COUNT = 30;
void ChatBlockModel::reparseIncremental()
{
// Only re-parse from where the last block started — O(tail) not O(full text)
// 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);
QList<ChatBlock> tailBlocks = parseBlocks(tail);
// Replace the last block (which may have grown) and append any new blocks
if (!m_allBlocks.isEmpty())
m_allBlocks.removeLast();
m_allBlocks.append(tailBlocks);
// Update offset: last block starts at the last header in the tail
if (tailBlocks.size() > 1) {
// Multiple blocks parsed from tail — advance offset to where the last one starts
// Find the last header boundary in m_rawText
// 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(m_rawText);
int lastPos = 0;
while (it.hasNext()) {
auto match = it.next();
lastPos = match.capturedStart();
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<ChatBlock> tailBlocks = parseBlocks(tail);
if (!tailBlocks.isEmpty()) {
m_allBlocks.last() = tailBlocks.last();
}
m_lastBlockTextOffset = lastPos;
}
updateVisibleWindow();