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.
This commit is contained in:
Levi Neely 2026-07-18 18:12:10 +02:00
parent c46f38fc93
commit 7703d06430
2 changed files with 1 additions and 66 deletions

View File

@ -106,80 +106,17 @@ QHash<int, QByteArray> 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<ChatBlock> 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();
}

View File

@ -74,7 +74,6 @@ signals:
private:
void reparse();
void reparseIncremental();
void updateVisibleWindow();
QList<ChatBlock> parseBlocks(const QString &text) const;
QVariantList parseSubBlocks(const QString &content) const;
@ -83,7 +82,6 @@ private:
QList<ChatBlock> m_allBlocks; // all parsed blocks
QList<ChatBlock> 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<QString, QString> m_highlightCache;
};