gui: fix freeze on agent switch — bulk load chat blocks

appendContent was calling rebuildBlockHtml (full syntax highlighting)
on every single line during initial log load. For a 1000-line chat,
that's O(n²) highlighting passes.

Add beginBulkLoad()/endBulkLoad() — skips per-line rebuilds during
bulk, then rebuilds all blocks once at the end. QML load sites now
wrap appendData(log) with bulk mode.
This commit is contained in:
Levi Neely 2026-08-08 23:14:55 +02:00
parent e24d332d50
commit a3826f2ce8
3 changed files with 31 additions and 3 deletions

View File

@ -480,10 +480,29 @@ void ChatBlockModel::appendContent(const QString &line)
last.content += '\n'; last.content += '\n';
last.content += line; last.content += line;
rebuildBlockHtml(m_blocks.size() - 1); if (!m_bulkLoading) {
rebuildBlockHtml(m_blocks.size() - 1);
QModelIndex idx = index(m_blocks.size() - 1);
emit dataChanged(idx, idx, {ContentRole, RenderedHtmlRole, SegmentsRole});
}
}
QModelIndex idx = index(m_blocks.size() - 1); void ChatBlockModel::beginBulkLoad()
emit dataChanged(idx, idx, {ContentRole, RenderedHtmlRole, SegmentsRole}); {
m_bulkLoading = true;
}
void ChatBlockModel::endBulkLoad()
{
m_bulkLoading = false;
// Rebuild HTML for all blocks once.
for (int i = 0; i < m_blocks.size(); ++i) {
rebuildBlockHtml(i);
}
if (!m_blocks.isEmpty()) {
emit dataChanged(index(0), index(m_blocks.size() - 1),
{ContentRole, RenderedHtmlRole, SegmentsRole});
}
} }
void ChatBlockModel::processLine(const QString &line) void ChatBlockModel::processLine(const QString &line)

View File

@ -78,6 +78,8 @@ public:
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE void appendData(const QString &text); Q_INVOKABLE void appendData(const QString &text);
Q_INVOKABLE void beginBulkLoad();
Q_INVOKABLE void endBulkLoad();
Q_INVOKABLE void reset(); Q_INVOKABLE void reset();
Q_INVOKABLE void trimFront(int keepCount); Q_INVOKABLE void trimFront(int keepCount);
Q_INVOKABLE void clearDiagramCache(); Q_INVOKABLE void clearDiagramCache();
@ -110,6 +112,7 @@ private:
QVariantList segmentsToVariant(const QList<BlockSegment> &segments) const; QVariantList segmentsToVariant(const QList<BlockSegment> &segments) const;
State m_state = State::BlockStart; State m_state = State::BlockStart;
bool m_bulkLoading = false;
KSyntaxHighlighting::Repository *m_repo = nullptr; KSyntaxHighlighting::Repository *m_repo = nullptr;
QNetworkAccessManager *m_netManager = nullptr; QNetworkAccessManager *m_netManager = nullptr;

View File

@ -463,8 +463,10 @@ ApplicationWindow {
if (ollie.activeAgentId) { if (ollie.activeAgentId) {
var log = ollie.readLogForSession(ollie.activeSessionId, ollie.activeAgentId) var log = ollie.readLogForSession(ollie.activeSessionId, ollie.activeAgentId)
if (log) { if (log) {
chatModel.beginBulkLoad()
chatModel.appendData(log) chatModel.appendData(log)
chatModel.trimFront(20) chatModel.trimFront(20)
chatModel.endBulkLoad()
} }
} }
ollie.refreshSessions() ollie.refreshSessions()
@ -479,8 +481,10 @@ ApplicationWindow {
if (ollie.activeSessionId && ollie.activeAgentId) { if (ollie.activeSessionId && ollie.activeAgentId) {
var log = ollie.readLogForSession(ollie.activeSessionId, ollie.activeAgentId) var log = ollie.readLogForSession(ollie.activeSessionId, ollie.activeAgentId)
if (log) { if (log) {
chatModel.beginBulkLoad()
chatModel.appendData(log) chatModel.appendData(log)
chatModel.trimFront(20) chatModel.trimFront(20)
chatModel.endBulkLoad()
} }
} }
} }
@ -490,8 +494,10 @@ ApplicationWindow {
if (ollie.activeSessionId && ollie.activeAgentId) { if (ollie.activeSessionId && ollie.activeAgentId) {
var log = ollie.readLogForSession(ollie.activeSessionId, ollie.activeAgentId) var log = ollie.readLogForSession(ollie.activeSessionId, ollie.activeAgentId)
if (log) { if (log) {
chatModel.beginBulkLoad()
chatModel.appendData(log) chatModel.appendData(log)
chatModel.trimFront(20) chatModel.trimFront(20)
chatModel.endBulkLoad()
} }
} }
} }