Skip to content

Commit d890d91

Browse files
committed
feat: improved code block and fix highlighting
1 parent c103c75 commit d890d91

File tree

3 files changed

+90
-29
lines changed

3 files changed

+90
-29
lines changed

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@
8989
},
9090
"dependencies": {
9191
"@deadlyjack/ajax": "^1.2.6",
92+
"@langchain/core": "^0.3.57",
93+
"@langchain/google-genai": "^0.2.10",
94+
"@langchain/langgraph": "^0.2.74",
9295
"@ungap/custom-elements": "^1.3.0",
9396
"autosize": "^6.0.1",
9497
"cordova": "12.0.0",
@@ -113,4 +116,4 @@
113116
"yargs": "^17.7.2"
114117
},
115118
"browserslist": "cover 100%,not android < 5"
116-
}
119+
}

src/pages/aiAssistant/assistant.js

Lines changed: 83 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import settings from "lib/settings";
88
import markdownIt from "markdown-it";
99
import styles from "./assistant.module.scss";
1010

11+
let aiTabInstance;
12+
1113
export default function openAIAssistantPage() {
1214
// References
1315
const profileBtnRef = new Ref();
@@ -265,7 +267,7 @@ export default function openAIAssistantPage() {
265267
`#message-${assistantMsgId} .message-content`,
266268
);
267269
if (messageEl) {
268-
messageEl.innerHTML += `<span style="color: var(--error-text-color);background-color: var(--danger-color);">Cancelled by user.</span>`;
270+
messageEl.innerHTML += `<div class="badge badge-red">Cancelled by user.</div>`;
269271
}
270272
} else {
271273
const messageEl = messageContainerRef.el.querySelector(
@@ -276,57 +278,110 @@ export default function openAIAssistantPage() {
276278
}
277279
}
278280
} finally {
279-
// add copy button to code blocks
280-
const codeBlocks = messageContainerRef.el
281-
.querySelector(`#message-${assistantMsgId} .message-content`)
282-
.querySelectorAll("pre");
283-
codeBlocks.forEach((pre) => {
284-
pre.style.position = "relative";
285-
const copyButton = document.createElement("button");
286-
copyButton.className = "copy-button";
287-
copyButton.textContent = "Copy";
288-
289-
const codeElement = pre.querySelector("code");
281+
// add custom code blocks with syntax highlighting
282+
const messageContent = messageContainerRef.el.querySelector(
283+
`#message-${assistantMsgId} .message-content`,
284+
);
285+
286+
// Replace markdown code blocks with custom components
287+
messageContent.innerHTML = messageContent.innerHTML.replace(
288+
/<pre><code class="language-(\w+)">([\s\S]*?)<\/code><\/pre>/g,
289+
(match, language, code) => {
290+
language = language || "plaintext";
291+
292+
return `
293+
<div class="code-block">
294+
<div class="code-header">
295+
<div class="code-language">
296+
<i class="icon code"></i>
297+
<span>${language}</span>
298+
</div>
299+
<div class="code-actions">
300+
<button class="btn btn-icon code-copy" title="Copy code">
301+
<i class="icon copy"></i>
302+
</button>
303+
</div>
304+
</div>
305+
<div class="code-content">
306+
<pre><code class="language-${language}">${code}</code></pre>
307+
</div>
308+
<div class="code-expand">
309+
<i class="icon keyboard_arrow_down"></i>
310+
<span>Show more</span>
311+
</div>
312+
</div>
313+
`;
314+
},
315+
);
316+
317+
// Process all code blocks
318+
const codeBlocks = messageContent.querySelectorAll(".code-block");
319+
codeBlocks.forEach((codeBlock) => {
320+
const codeContent = codeBlock.querySelector(".code-content");
321+
const codeElement = codeBlock.querySelector("code");
322+
const copyButton = codeBlock.querySelector(".code-copy");
323+
const expandButton = codeBlock.querySelector(".code-expand");
324+
325+
// Apply Ace highlighting
290326
if (codeElement) {
291-
const langMatch = codeElement.className.match(
292-
/language-(\w+)|(javascript)/,
293-
);
327+
const langMatch = codeElement.className.match(/language-(\w+)/);
294328
if (langMatch) {
295329
const langMap = {
296330
bash: "sh",
297331
shell: "sh",
298332
};
299-
const lang = langMatch[1] || langMatch[2];
333+
const lang = langMatch[1];
300334
const mappedLang = langMap[lang] || lang;
301335
const highlight = ace.require("ace/ext/static_highlight");
302-
highlight(codeElement, {
303-
mode: `ace/mode/${mappedLang}`,
304-
theme: settings.value.editorTheme.startsWith("ace/theme/")
336+
highlight.render(
337+
codeElement.textContent,
338+
`ace/mode/${mappedLang}`,
339+
settings.value.editorTheme.startsWith("ace/theme/")
305340
? settings.value.editorTheme
306341
: "ace/theme/" + settings.value.editorTheme,
307-
});
342+
1,
343+
true,
344+
(highlighted) => {
345+
aiTabInstance?.addStyle(highlighted.css);
346+
codeElement.innerHTML = highlighted.html;
347+
},
348+
);
308349
}
309350
}
310351

352+
// copy functionality
311353
copyButton.addEventListener("click", async () => {
312-
const code =
313-
pre.querySelector("code")?.textContent || pre.textContent;
354+
const code = codeElement?.textContent || "";
314355
try {
315356
cordova.plugins.clipboard.copy(code);
316-
copyButton.textContent = "Copied!";
357+
copyButton.querySelector("i").className = "icon check";
317358
setTimeout(() => {
318-
copyButton.textContent = "Copy";
359+
copyButton.querySelector("i").className = "icon copy";
319360
}, 2000);
320361
} catch (err) {
321-
copyButton.textContent = "Failed to copy";
362+
copyButton.querySelector("i").className =
363+
"icon warningreport_problem";
322364
setTimeout(() => {
323-
copyButton.textContent = "Copy";
365+
copyButton.querySelector("i").className = "icon copy";
324366
}, 2000);
325367
}
326368
});
327369

328-
pre.appendChild(copyButton);
370+
// expand/collapse functionality
371+
expandButton.addEventListener("click", () => {
372+
const isExpanded = codeContent.classList.contains("expanded");
373+
codeContent.classList.toggle("expanded", !isExpanded);
374+
expandButton.innerHTML = isExpanded
375+
? `<i class="icon keyboard_arrow_down"></i> <span>Show more</span>`
376+
: `<i class="icon keyboard_arrow_up"></i> <span>Show less</span>`;
377+
});
378+
379+
// Only show expand button if content overflows
380+
if (codeContent.scrollHeight <= codeContent.clientHeight) {
381+
expandButton.style.display = "none";
382+
}
329383
});
384+
330385
currentController = null;
331386
sendBtnRef.el.style.display = "block";
332387
stopBtnRef.el.style.display = "none";
@@ -491,7 +546,7 @@ export default function openAIAssistantPage() {
491546
}
492547

493548
// Create a new EditorFile instance for the AI Assistant tab
494-
new EditorFile("AI Assistant", {
549+
aiTabInstance = new EditorFile("AI Assistant", {
495550
uri: uri,
496551
type: "page",
497552
tabIcon: "file file_type_assistant",

src/pages/aiAssistant/assistant.module.scss

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,9 @@
380380
margin: 0;
381381
padding: 0.75rem;
382382
}
383+
.code-content pre code div {
384+
background-color: inherit !important;
385+
}
383386

384387
.code-expand {
385388
display: flex;

0 commit comments

Comments
 (0)