Skip to content

Commit 8ff9113

Browse files
committed
feat(chat): add syntax highlighting for JSON in tool popover
1 parent 40880b7 commit 8ff9113

3 files changed

Lines changed: 125 additions & 41 deletions

File tree

src/Dashboard/frontend/package-lock.json

Lines changed: 11 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Dashboard/frontend/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
},
1616
"dependencies": {
1717
"@microsoft/applicationinsights-web": "^3.3.11",
18-
"echarts": "^6.0.0"
18+
"echarts": "^6.0.0",
19+
"highlight.js": "^11.11.1"
1920
}
2021
}

src/Dashboard/frontend/src/components/ChatView.vue

Lines changed: 112 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1671,15 +1671,15 @@
16711671
<div class="tool-popover-body">
16721672
<div v-if="hoveredTool.args" class="tool-popover-section">
16731673
<div class="tool-popover-label">INPUT</div>
1674-
<pre class="tool-popover-pre">{{
1675-
formatJson(hoveredTool.args)
1676-
}}</pre>
1674+
<pre
1675+
class="tool-popover-pre hljs"
1676+
><code class="language-json" v-html="highlightJson(hoveredTool.args)"></code></pre>
16771677
</div>
16781678
<div v-if="hoveredTool.result" class="tool-popover-section">
16791679
<div class="tool-popover-label">RESPONSE</div>
1680-
<pre class="tool-popover-pre">{{
1681-
truncate(hoveredTool.result, 4000)
1682-
}}</pre>
1680+
<pre
1681+
class="tool-popover-pre hljs"
1682+
><code class="language-json" v-html="highlightJson(truncate(hoveredTool.result, 4000))"></code></pre>
16831683
</div>
16841684
<div v-if="hoveredTool.error" class="tool-popover-section">
16851685
<div class="tool-popover-label">ERROR</div>
@@ -1698,6 +1698,9 @@
16981698

16991699
<script setup>
17001700
import * as echarts from "echarts";
1701+
import hljs from "highlight.js/lib/core";
1702+
import hljsJson from "highlight.js/lib/languages/json";
1703+
import "highlight.js/styles/github-dark.css";
17011704
import {
17021705
computed,
17031706
nextTick,
@@ -1711,6 +1714,7 @@ import {
17111714
maturityCategories,
17121715
pricingCategory,
17131716
} from "../data/sidebarCategories.js";
1717+
hljs.registerLanguage("json", hljsJson);
17141718
17151719
const props = defineProps({
17161720
user: { type: Object, default: null },
@@ -3540,6 +3544,40 @@ function formatJson(str) {
35403544
}
35413545
}
35423546
3547+
function highlightJson(str) {
3548+
if (!str) return "";
3549+
const raw = String(str);
3550+
// Try strict JSON pretty-print first
3551+
let pretty = raw;
3552+
try {
3553+
pretty = JSON.stringify(JSON.parse(raw), null, 2);
3554+
} catch {
3555+
// Not valid JSON — try to extract the first JSON object/array within
3556+
const match = raw.match(/[\[{][\s\S]*[\]}]/);
3557+
if (match) {
3558+
try {
3559+
pretty = JSON.stringify(JSON.parse(match[0]), null, 2);
3560+
} catch {
3561+
// leave as raw
3562+
}
3563+
}
3564+
}
3565+
// Highlight as JSON if it now looks like JSON, otherwise plain escape
3566+
const looksLikeJson = /^[\s]*[\[{]/.test(pretty);
3567+
if (looksLikeJson) {
3568+
try {
3569+
return hljs.highlight(pretty, { language: "json", ignoreIllegals: true })
3570+
.value;
3571+
} catch {
3572+
// fall through
3573+
}
3574+
}
3575+
return pretty
3576+
.replace(/&/g, "&amp;")
3577+
.replace(/</g, "&lt;")
3578+
.replace(/>/g, "&gt;");
3579+
}
3580+
35433581
function truncate(str, max) {
35443582
if (!str || str.length <= max) return str;
35453583
return str.slice(0, max) + "\n... (truncated)";
@@ -3739,16 +3777,15 @@ function renderContent(text) {
37393777
(match) => {
37403778
const lines = match.trim().split("\n");
37413779
if (lines.length < 2) return match;
3742-
const headerCells = lines[0]
3743-
.split("|")
3744-
.filter((c) => c.trim())
3745-
.map((c) => c.trim());
3746-
const dataRows = lines.slice(2).map((row) =>
3747-
row
3780+
// Strip outer pipes then split — keeps interior empty cells aligned.
3781+
const splitRow = (line) =>
3782+
line
3783+
.trim()
3784+
.replace(/^\||\|$/g, "")
37483785
.split("|")
3749-
.filter((c) => c.trim().length > 0 || c === "")
3750-
.map((c) => c.trim()),
3751-
);
3786+
.map((c) => c.trim());
3787+
const headerCells = splitRow(lines[0]);
3788+
const dataRows = lines.slice(2).map(splitRow);
37523789
return buildWowTable(headerCells, dataRows);
37533790
},
37543791
);
@@ -4379,28 +4416,27 @@ async function send() {
43794416
display: flex;
43804417
align-items: center;
43814418
width: calc(100% - 20px);
4382-
margin: 2px 10px;
4419+
margin: 1px 10px;
43834420
padding: 8px 12px;
4384-
border: 1px solid #edebe9;
4421+
border: 1px solid transparent;
43854422
border-radius: 6px;
4386-
background: #fff;
4423+
background: transparent;
43874424
font-size: 13px;
43884425
font-weight: 400;
43894426
color: #323130;
43904427
cursor: pointer;
43914428
text-align: left;
43924429
line-height: 1.4;
43934430
transition:
4394-
border-color 0.15s,
4395-
background 0.15s,
4396-
box-shadow 0.15s;
4431+
background 0.15s ease,
4432+
box-shadow 0.15s ease,
4433+
color 0.15s ease;
43974434
font-family: inherit;
43984435
}
43994436
.sidebar-question:hover:not(:disabled) {
4400-
border-color: #0078d4;
4401-
background: #f3f2f1;
4402-
color: #0078d4;
4403-
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
4437+
background: rgba(15, 23, 42, 0.04);
4438+
color: #1a1a1a;
4439+
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.06);
44044440
}
44054441
.sidebar-question:disabled {
44064442
opacity: 0.4;
@@ -4413,16 +4449,18 @@ async function send() {
44134449
.sidebar-question--score-cta {
44144450
margin: 8px 12px 10px;
44154451
padding: 8px 12px;
4416-
background: #f3f9fd;
4417-
border: 1px solid #c7e0f4;
4418-
border-radius: 4px;
4452+
background: transparent;
4453+
border: 1px solid transparent;
4454+
border-radius: 6px;
44194455
text-align: center;
44204456
color: #0078d4;
44214457
width: calc(100% - 24px);
4458+
font-weight: 600;
44224459
}
44234460
.sidebar-question--score-cta:hover {
4424-
background: #deecf9;
4461+
background: rgba(0, 120, 212, 0.06);
44254462
color: #005a9e;
4463+
box-shadow: 0 1px 2px rgba(0, 120, 212, 0.08);
44264464
}
44274465
.sidebar-subgroup {
44284466
border-top: 1px solid #edebe9;
@@ -6575,24 +6613,59 @@ async function send() {
65756613
text-transform: uppercase;
65766614
}
65776615
.tool-popover-pre {
6578-
background: #f3f2f1;
6579-
border: 1px solid #e1dfdd;
6580-
border-radius: 4px;
6581-
padding: 12px 16px;
6616+
background: #0d1117;
6617+
border: 1px solid #21262d;
6618+
border-radius: 6px;
6619+
padding: 12px 14px;
65826620
margin: 0;
6583-
font-size: 13px;
6621+
font-family:
6622+
ui-monospace, "SF Mono", "JetBrains Mono", "Fira Code", "Cascadia Code",
6623+
Menlo, Consolas, "Courier New", monospace;
6624+
font-size: 12px;
6625+
font-variant-ligatures: none;
65846626
white-space: pre-wrap;
65856627
word-break: break-word;
65866628
max-height: 500px;
65876629
overflow-y: auto;
6588-
line-height: 1.5;
6589-
color: #323130;
6630+
line-height: 1.55;
6631+
color: #e6edf3;
65906632
scrollbar-width: thin;
6633+
-webkit-font-smoothing: antialiased;
6634+
}
6635+
.tool-popover-pre code {
6636+
background: transparent;
6637+
padding: 0;
6638+
font: inherit;
6639+
color: inherit;
6640+
display: block;
6641+
}
6642+
/* Force white-dominant scheme regardless of hljs theme */
6643+
.tool-popover-pre :deep(.hljs),
6644+
.tool-popover-pre :deep(.hljs-attr),
6645+
.tool-popover-pre :deep(.hljs-string),
6646+
.tool-popover-pre :deep(.hljs-number),
6647+
.tool-popover-pre :deep(.hljs-literal),
6648+
.tool-popover-pre :deep(.hljs-keyword),
6649+
.tool-popover-pre :deep(.hljs-punctuation),
6650+
.tool-popover-pre :deep(.hljs-built_in) {
6651+
color: #ffffff !important;
6652+
background: transparent !important;
6653+
}
6654+
/* Subtle accent to keep keys/strings distinguishable */
6655+
.tool-popover-pre :deep(.hljs-attr) {
6656+
color: #79c0ff !important;
6657+
}
6658+
.tool-popover-pre :deep(.hljs-string) {
6659+
color: #d2e4ff !important;
6660+
}
6661+
.tool-popover-pre :deep(.hljs-number),
6662+
.tool-popover-pre :deep(.hljs-literal) {
6663+
color: #f5b483 !important;
65916664
}
65926665
.tool-popover-pre--error {
6593-
color: #d13438;
6594-
background: #fde7e9;
6595-
border-color: #d13438;
6666+
color: #ffa198;
6667+
background: #2d1418;
6668+
border-color: #f85149;
65966669
}
65976670
65986671
/* ── Auth buttons ── */

0 commit comments

Comments
 (0)