pin reaction badge on assistant blocks, hide reaction buttons after reacting
This commit is contained in:
parent
e2592d821f
commit
cf1f0f75d9
|
|
@ -683,14 +683,26 @@ Item {
|
|||
Behavior on opacity { NumberAnimation { duration: 150 } }
|
||||
}
|
||||
|
||||
// Reaction buttons
|
||||
Row {
|
||||
// Reaction badge (pinned when reacted)
|
||||
Label {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.margins: 4
|
||||
z: 10
|
||||
text: block.reaction || ""
|
||||
font.pixelSize: 18
|
||||
visible: block.reaction !== ""
|
||||
}
|
||||
|
||||
// Reaction buttons (hover only, hidden when already reacted)
|
||||
Row {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: block.reaction !== "" ? 36 : 4
|
||||
anchors.margins: 4
|
||||
z: 10
|
||||
spacing: 2
|
||||
opacity: assistantHover.hovered ? 0.9 : 0
|
||||
opacity: (assistantHover.hovered && block.reaction === "") ? 0.9 : 0
|
||||
Behavior on opacity { NumberAnimation { duration: 150 } }
|
||||
|
||||
Repeater {
|
||||
|
|
|
|||
|
|
@ -66,6 +66,8 @@ QVariant ChatBlockModel::data(const QModelIndex &index, int role) const
|
|||
return block.content;
|
||||
case ResponseIdRole:
|
||||
return block.responseId;
|
||||
case ReactionRole:
|
||||
return block.reaction;
|
||||
case SubBlocksRole:
|
||||
if (block.cachedContent != block.content) {
|
||||
block.cachedSubBlocks = parseSubBlocks(block.content);
|
||||
|
|
@ -105,6 +107,7 @@ QHash<int, QByteArray> ChatBlockModel::roleNames() const
|
|||
{SubBlocksRole, "subBlocks"},
|
||||
{FormattedContentRole, "formattedContent"},
|
||||
{ResponseIdRole, "responseId"},
|
||||
{ReactionRole, "reaction"},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -236,6 +239,8 @@ QList<ChatBlock> ChatBlockModel::parseBlocks(const QString &text) const
|
|||
static const QRegularExpression headerRe(
|
||||
R"(^\[(user|assistant|reasoning|call|tool|error|info)(?::([^\]]*))?\](.*)$)",
|
||||
QRegularExpression::MultilineOption);
|
||||
static const QRegularExpression reactionRe(
|
||||
R"(^\[reaction to assistant response ([^ ]+): ([^ ]+) \(([^)]+)\)\])");
|
||||
|
||||
auto it = headerRe.globalMatch(text);
|
||||
QList<QPair<int, QRegularExpressionMatch>> headers;
|
||||
|
|
@ -253,6 +258,15 @@ QList<ChatBlock> ChatBlockModel::parseBlocks(const QString &text) const
|
|||
return blocks;
|
||||
}
|
||||
|
||||
// First pass: collect reaction-to-response mappings.
|
||||
QHash<QString, QString> reactionMap;
|
||||
for (const auto &[pos, match] : headers) {
|
||||
auto rm = reactionRe.match(match.captured(0));
|
||||
if (rm.hasMatch()) {
|
||||
reactionMap[rm.captured(1)] = rm.captured(3);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < headers.size(); ++i) {
|
||||
const auto &[pos, match] = headers[i];
|
||||
QString typeStr = match.captured(1);
|
||||
|
|
@ -284,10 +298,14 @@ QList<ChatBlock> ChatBlockModel::parseBlocks(const QString &text) const
|
|||
else if (typeStr == "error") b.type = ChatBlock::Error;
|
||||
else if (typeStr == "info") b.type = ChatBlock::Info;
|
||||
|
||||
if (b.type == ChatBlock::Assistant)
|
||||
if (b.type == ChatBlock::Assistant) {
|
||||
b.responseId = name;
|
||||
else
|
||||
auto it = reactionMap.constFind(name);
|
||||
if (it != reactionMap.constEnd())
|
||||
b.reaction = it.value();
|
||||
} else {
|
||||
b.name = name;
|
||||
}
|
||||
b.content = content;
|
||||
blocks.append(b);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ struct ChatBlock {
|
|||
Type type;
|
||||
QString name; // tool/call name, empty for others
|
||||
QString responseId; // stable assistant response ID
|
||||
QString reaction; // emoji reaction text (empty if not reacted)
|
||||
QString content; // raw text content
|
||||
mutable QVariantList cachedSubBlocks; // cached parseSubBlocks result
|
||||
mutable QString cachedContent; // content when subBlocks were cached
|
||||
|
|
@ -53,6 +54,7 @@ public:
|
|||
SubBlocksRole, // QVariantList of sub-block maps
|
||||
FormattedContentRole, // pre-formatted HTML for call/tool expanded view
|
||||
ResponseIdRole,
|
||||
ReactionRole,
|
||||
};
|
||||
|
||||
explicit ChatBlockModel(QObject *parent = nullptr);
|
||||
|
|
|
|||
|
|
@ -1508,14 +1508,26 @@ ApplicationWindow {
|
|||
Behavior on opacity { NumberAnimation { duration: 150 } }
|
||||
}
|
||||
|
||||
// Reaction buttons
|
||||
Row {
|
||||
// Reaction badge (pinned when reacted)
|
||||
Label {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.margins: 4
|
||||
z: 10
|
||||
text: block.reaction || ""
|
||||
font.pixelSize: 18
|
||||
visible: block.reaction !== ""
|
||||
}
|
||||
|
||||
// Reaction buttons (hover only, hidden when already reacted)
|
||||
Row {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: block.reaction !== "" ? 36 : 4
|
||||
anchors.margins: 4
|
||||
z: 10
|
||||
spacing: 2
|
||||
opacity: assistantHover.hovered ? 0.9 : 0
|
||||
opacity: (assistantHover.hovered && block.reaction === "") ? 0.9 : 0
|
||||
Behavior on opacity { NumberAnimation { duration: 150 } }
|
||||
|
||||
Repeater {
|
||||
|
|
|
|||
Reference in New Issue