gui: render user-interrupt with yellow background and warning icon
- Add Interrupt block type to ChatBlock enum - Detect <system-user-interruption> and </system-user-interruption> tags as block openers/closers in the parser - Render interrupt blocks with bright yellow header (#3d2e00 dark, #fff8e1 light) - Show ⚠ User Interrupt header label
This commit is contained in:
parent
ada6dc1feb
commit
8617aa5293
|
|
@ -49,6 +49,12 @@ const QRegularExpression ChatBlockModel::s_headerRe(
|
|||
const QRegularExpression ChatBlockModel::s_endRe(
|
||||
R"(^\[\[\[end\]\]\]$)");
|
||||
|
||||
const QRegularExpression ChatBlockModel::s_interruptOpenRe(
|
||||
R"(^<system-user-interruption>\s*$)");
|
||||
|
||||
const QRegularExpression ChatBlockModel::s_interruptCloseRe(
|
||||
R"(^</system-user-interruption>\s*$)");
|
||||
|
||||
ChatBlockModel::ChatBlockModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
|
|
@ -74,10 +80,11 @@ QVariant ChatBlockModel::data(const QModelIndex &index, int role) const
|
|||
case ChatBlock::Reasoning: return QStringLiteral("reasoning");
|
||||
case ChatBlock::Call: return QStringLiteral("call");
|
||||
case ChatBlock::Tool: return QStringLiteral("tool");
|
||||
case ChatBlock::Error: return QStringLiteral("error");
|
||||
case ChatBlock::Info: return QStringLiteral("info");
|
||||
case ChatBlock::Retry: return QStringLiteral("retry");
|
||||
case ChatBlock::Default: return QStringLiteral("default");
|
||||
case ChatBlock::Error: return QStringLiteral("error");
|
||||
case ChatBlock::Info: return QStringLiteral("info");
|
||||
case ChatBlock::Retry: return QStringLiteral("retry");
|
||||
case ChatBlock::Interrupt: return QStringLiteral("interrupt");
|
||||
case ChatBlock::Default: return QStringLiteral("default");
|
||||
}
|
||||
return {};
|
||||
case BlockNameRole:
|
||||
|
|
@ -235,6 +242,12 @@ void ChatBlockModel::processLine(const QString &line)
|
|||
return;
|
||||
}
|
||||
|
||||
// --- Interrupt close tag ---
|
||||
if (s_interruptCloseRe.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);
|
||||
|
|
@ -254,6 +267,16 @@ void ChatBlockModel::processLine(const QString &line)
|
|||
return;
|
||||
}
|
||||
|
||||
// Interrupt open tag — creates its own block
|
||||
if (s_interruptOpenRe.match(line).hasMatch()) {
|
||||
beginInsertRows(QModelIndex(), m_blocks.size(), m_blocks.size());
|
||||
m_blocks.append({ChatBlock::Interrupt, QString(), QString(), QString()});
|
||||
endInsertRows();
|
||||
emit countChanged();
|
||||
m_inBlock = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Header line
|
||||
QRegularExpressionMatch m = s_headerRe.match(line);
|
||||
if (m.hasMatch()) {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ struct ChatBlock {
|
|||
Error,
|
||||
Info,
|
||||
Retry,
|
||||
Interrupt,
|
||||
Default
|
||||
};
|
||||
|
||||
|
|
@ -75,6 +76,8 @@ private:
|
|||
|
||||
static const QRegularExpression s_headerRe;
|
||||
static const QRegularExpression s_endRe;
|
||||
static const QRegularExpression s_interruptOpenRe;
|
||||
static const QRegularExpression s_interruptCloseRe;
|
||||
};
|
||||
|
||||
#endif // CHATBLOCKMODEL_H
|
||||
|
|
|
|||
24
gui/main.qml
24
gui/main.qml
|
|
@ -727,16 +727,17 @@ delegate: ColumnLayout {
|
|||
width: chatView.width - 8
|
||||
spacing: 2
|
||||
|
||||
property bool blockExpanded: {
|
||||
switch (blockType) {
|
||||
case "user": return true
|
||||
case "assistant": return true
|
||||
case "tool": return true
|
||||
case "info": return true
|
||||
case "retry": return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
property bool blockExpanded: {
|
||||
switch (blockType) {
|
||||
case "user": return true
|
||||
case "assistant": return true
|
||||
case "tool": return true
|
||||
case "info": return true
|
||||
case "retry": return true
|
||||
case "interrupt": return true
|
||||
default: return false
|
||||
}
|
||||
}
|
||||
|
||||
// Clickable header
|
||||
Rectangle {
|
||||
|
|
@ -755,6 +756,7 @@ delegate: ColumnLayout {
|
|||
case "error": return Qt.rgba(0.6, 0.15, 0.15, 0.7)
|
||||
case "info": return Qt.lighter(bg, 1.4)
|
||||
case "retry": return Qt.rgba(0.6, 0.5, 0.0, 0.5)
|
||||
case "interrupt": return "#3d2e00"
|
||||
default: return bg
|
||||
}
|
||||
} else {
|
||||
|
|
@ -767,6 +769,7 @@ delegate: ColumnLayout {
|
|||
case "error": return Qt.rgba(1.0, 0.8, 0.8, 0.7)
|
||||
case "info": return Qt.darker(bg, 1.02)
|
||||
case "retry": return Qt.rgba(1.0, 0.9, 0.6, 0.5)
|
||||
case "interrupt": return "#fff8e1"
|
||||
default: return bg
|
||||
}
|
||||
}
|
||||
|
|
@ -801,6 +804,7 @@ delegate: ColumnLayout {
|
|||
case "error": return "Error"
|
||||
case "info": return "Info"
|
||||
case "retry": return "Retry"
|
||||
case "interrupt": return "\u26A0 User Interrupt"
|
||||
default: return ""
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Reference in New Issue