fix call/tool pretty-print: use C++ prettyJson() with QJsonDocument

The onVisibleChanged approach failed because block.blockContent could
not resolve through the Loader scope. Now using direct binding:
- call blocks: text bound to chatModel.prettyJson(block.blockContent)
- tool blocks: detect JSON/diff, format accordingly
- prettyJson() uses QJsonDocument for proper indentation + KSyntaxHighlighting
- Only evaluated when visible (lazy)
This commit is contained in:
Levi Neely 2026-07-16 07:00:40 +02:00
parent ee3a0a1e76
commit c39e0f2723
3 changed files with 30 additions and 41 deletions

View File

@ -8,6 +8,8 @@
#include <QTextDocument>
#include <QGuiApplication>
#include <QClipboard>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QPalette>
static KSyntaxHighlighting::Repository *s_repo = nullptr;
@ -368,6 +370,20 @@ QString ChatBlockModel::highlightedHtml(const QString &code, const QString &lang
return html;
}
QString ChatBlockModel::prettyJson(const QString &text) const
{
// Try to parse and pretty-print JSON, then syntax highlight
QJsonParseError err;
QJsonDocument doc = QJsonDocument::fromJson(text.toUtf8(), &err);
QString formatted;
if (err.error == QJsonParseError::NoError) {
formatted = QString::fromUtf8(doc.toJson(QJsonDocument::Indented));
} else {
formatted = text;
}
return highlightedHtml(formatted, QStringLiteral("JSON"));
}
void ChatBlockModel::copyToClipboard(const QString &text) const
{
QGuiApplication::clipboard()->setText(text);

View File

@ -60,6 +60,7 @@ public:
void setRawText(const QString &text);
Q_INVOKABLE QString highlightedHtml(const QString &code, const QString &language) const;
Q_INVOKABLE QString prettyJson(const QString &text) const;
Q_INVOKABLE void copyToClipboard(const QString &text) const;
Q_INVOKABLE bool loadEarlier(); // returns true if more blocks were loaded

View File

@ -622,27 +622,14 @@ ApplicationWindow {
Text {
id: callText
Layout.fillWidth: true
text: block.blockContent
textFormat: Text.PlainText
text: visible ? chatModel.prettyJson(block.blockContent) : ""
textFormat: Text.RichText
wrapMode: Text.Wrap
font.family: "monospace"
font.pixelSize: Qt.application.font.pixelSize * 0.85
opacity: 0.7
visible: false
color: palette.text
onVisibleChanged: {
if (visible && textFormat === Text.PlainText) {
try {
let obj = JSON.parse(block.blockContent)
let pretty = JSON.stringify(obj, null, 2)
text = chatModel.highlightedHtml(pretty, "json")
} catch(e) {
text = chatModel.highlightedHtml(block.blockContent, "json")
}
textFormat = Text.RichText
}
}
}
}
}
@ -664,8 +651,17 @@ ApplicationWindow {
Text {
id: toolText
Layout.fillWidth: true
text: block.blockContent
textFormat: Text.PlainText
text: {
if (!visible) return ""
let content = block.blockContent
let trimmed = content.trimStart()
if (trimmed.startsWith("{") || trimmed.startsWith("["))
return chatModel.prettyJson(content)
if (trimmed.startsWith("---") || trimmed.startsWith("diff "))
return chatModel.highlightedHtml(content, "diff")
return content.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\n/g, '<br>')
}
textFormat: Text.RichText
wrapMode: Text.Wrap
font.family: "monospace"
font.pixelSize: Qt.application.font.pixelSize * 0.85
@ -674,30 +670,6 @@ ApplicationWindow {
color: palette.text
maximumLineCount: 80
elide: Text.ElideRight
onVisibleChanged: {
if (visible && textFormat === Text.PlainText) {
let content = block.blockContent
let trimmed = content.trimStart()
let lang = ""
if (trimmed.startsWith("{") || trimmed.startsWith("[")) lang = "json"
else if (trimmed.startsWith("---") || trimmed.startsWith("diff ")) lang = "diff"
if (lang === "json") {
try {
let obj = JSON.parse(content)
let pretty = JSON.stringify(obj, null, 2)
text = chatModel.highlightedHtml(pretty, "json")
} catch(e) {
text = chatModel.highlightedHtml(content, "json")
}
textFormat = Text.RichText
} else if (lang) {
text = chatModel.highlightedHtml(content, lang)
textFormat = Text.RichText
}
}
}
}
}
}