chat log: switch to [[[type]]]/[[[end]]] block format
Replace the single-bracket INI-like format with triple-bracket blocks with explicit [[[end]]] terminators. The parser tracks m_inBlock to ignore content between blocks, making the format robust against accidental header-like content in tool output or LLM responses. - Go sender: FormatEvent + NewEventHandler + replayMessagesToLog - C++ parser: new regex for [[[type]]], new [[[end]]] handler, m_inBlock gate - QML: update hardcoded [info] string
This commit is contained in:
parent
b13dc6064b
commit
998efc15c5
|
|
@ -44,7 +44,10 @@ private:
|
|||
// --- Block Parsing ---
|
||||
|
||||
const QRegularExpression ChatBlockModel::s_headerRe(
|
||||
R"(^\[(user|assistant|reasoning|call|tool|error|info|retry)(?::([^\]]*))?\]$)");
|
||||
R"(^\[\[\[(user|assistant|reasoning|call|tool|error|info|retry)(?::([^\]]*))?\]\]\]$)");
|
||||
|
||||
const QRegularExpression ChatBlockModel::s_endRe(
|
||||
R"(^\[\[\[end\]\]\]$)");
|
||||
|
||||
ChatBlockModel::ChatBlockModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
|
|
@ -118,6 +121,7 @@ void ChatBlockModel::reset()
|
|||
m_fenceState = NoFence;
|
||||
m_fenceLang.clear();
|
||||
m_fenceBuffer.clear();
|
||||
m_inBlock = false;
|
||||
endResetModel();
|
||||
emit countChanged();
|
||||
}
|
||||
|
|
@ -225,6 +229,12 @@ void ChatBlockModel::processLine(const QString &line)
|
|||
return;
|
||||
}
|
||||
|
||||
// --- End block marker ---
|
||||
if (s_endRe.match(line).hasMatch()) {
|
||||
m_inBlock = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// --- NoFence state: look for opening fence or header ---
|
||||
static const QRegularExpression fenceOpenRe(R"(^```(\w*)[ \t]*$)");
|
||||
QRegularExpressionMatch fenceMatch = fenceOpenRe.match(line);
|
||||
|
|
@ -265,16 +275,23 @@ void ChatBlockModel::processLine(const QString &line)
|
|||
m_blocks.append({type, qualifier, QString(), QString()});
|
||||
endInsertRows();
|
||||
emit countChanged();
|
||||
m_inBlock = true;
|
||||
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(), QString()});
|
||||
endInsertRows();
|
||||
emit countChanged();
|
||||
// Content line — only append if inside a block or connecting mid-stream
|
||||
if (!m_inBlock) {
|
||||
// Dropped content between blocks — ignore to avoid accidental parsing.
|
||||
// Mid-stream connect: if no blocks exist yet, create a default container.
|
||||
if (m_blocks.isEmpty()) {
|
||||
beginInsertRows(QModelIndex(), 0, 0);
|
||||
m_blocks.append({ChatBlock::Default, QString(), QString(), QString()});
|
||||
endInsertRows();
|
||||
emit countChanged();
|
||||
m_inBlock = true;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ChatBlock &last = m_blocks.last();
|
||||
|
|
|
|||
|
|
@ -60,6 +60,11 @@ private:
|
|||
QString m_fenceLang;
|
||||
QString m_fenceBuffer;
|
||||
|
||||
// Block state machine — tracks whether we're inside a [[[type]]]...[[[end]]] block.
|
||||
// Content lines outside a block are ignored, making the format robust against
|
||||
// accidental header-like content in the wild.
|
||||
bool m_inBlock = false;
|
||||
|
||||
// Syntax highlighting (shared with Kate plugin pattern)
|
||||
QString highlightCode(const QString &code, const QString &language) const;
|
||||
QString renderDiff(const QString &diff) const;
|
||||
|
|
@ -69,6 +74,7 @@ private:
|
|||
QString m_lineBuffer;
|
||||
|
||||
static const QRegularExpression s_headerRe;
|
||||
static const QRegularExpression s_endRe;
|
||||
};
|
||||
|
||||
#endif // CHATBLOCKMODEL_H
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ ApplicationWindow {
|
|||
buildSessionTree()
|
||||
}
|
||||
function onStreamingDone() {
|
||||
chatModel.appendData("\n[info]\nsession ended\n")
|
||||
chatModel.appendData("\n[[[info]]]\nsession ended\n[[[end]]]\n")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Reference in New Issue