Skip to content

Commit b831048

Browse files
committed
fix: highlighting, message sync, searchTool
1 parent 2d536be commit b831048

File tree

2 files changed

+154
-63
lines changed

2 files changed

+154
-63
lines changed

src/pages/aiAssistant/assistant.js

Lines changed: 151 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,18 @@ export default function openAIAssistantPage() {
3636
let aiTabInstance;
3737

3838
const GEMINI_API_KEY = ""; // Replace
39+
40+
const searchTool = {
41+
googleSearch: {},
42+
};
3943
const agentCheckpointer = new MemorySaver();
4044
const model = new ChatGoogleGenerativeAI({
4145
model: "gemini-2.0-flash",
4246
apiKey: GEMINI_API_KEY,
4347
});
4448
const agent = createReactAgent({
4549
llm: model,
46-
tools: [],
50+
tools: [searchTool],
4751
checkpointSaver: agentCheckpointer,
4852
});
4953

@@ -68,6 +72,115 @@ export default function openAIAssistantPage() {
6872
messageContainerRef.el.scrollTop = messageContainerRef.el.scrollHeight;
6973
};
7074

75+
// Format code blocks with custom UI elements
76+
const formatCodeBlocks = (contentElement, content) => {
77+
if (!contentElement) return;
78+
79+
const md = markdownIt({
80+
html: true,
81+
linkify: true,
82+
typographer: true,
83+
});
84+
85+
contentElement.innerHTML = md.render(content);
86+
87+
contentElement.innerHTML = contentElement.innerHTML.replace(
88+
/<pre><code(?: class="language-(\w+)")?>([\s\S]*?)<\/code><\/pre>/g,
89+
(match, language, code) => {
90+
language = language || "plaintext";
91+
code = he.decode(code);
92+
return `
93+
<div class="code-block">
94+
<div class="code-header">
95+
<div class="code-language">
96+
<i class="icon code"></i>
97+
<span>${language}</span>
98+
</div>
99+
<div class="code-actions">
100+
<button class="btn btn-icon code-copy" title="Copy code">
101+
<i class="icon copy"></i>
102+
</button>
103+
</div>
104+
</div>
105+
<div class="code-content">
106+
<pre><code class="language-${language}">${code}</code></pre>
107+
</div>
108+
<div class="code-expand">
109+
<i class="icon keyboard_arrow_down"></i>
110+
<span>Show more</span>
111+
</div>
112+
</div>
113+
`;
114+
},
115+
);
116+
117+
contentElement.querySelectorAll(".code-block").forEach((codeBlock) => {
118+
const codeContent = codeBlock.querySelector(".code-content");
119+
const codeElement = codeBlock.querySelector("pre code");
120+
const copyButton = codeBlock.querySelector(".code-copy");
121+
const expandButton = codeBlock.querySelector(".code-expand");
122+
123+
// Apply Ace highlighting
124+
if (codeElement) {
125+
const langMatch = codeElement.className.match(/language-(\w+)/);
126+
if (langMatch) {
127+
const langMap = {
128+
bash: "sh",
129+
shell: "sh",
130+
};
131+
const lang = langMatch[1];
132+
const mappedLang = langMap[lang] || lang;
133+
const highlight = ace.require("ace/ext/static_highlight");
134+
highlight.render(
135+
codeElement.textContent,
136+
`ace/mode/${mappedLang}`,
137+
settings.value.editorTheme.startsWith("ace/theme/")
138+
? settings.value.editorTheme
139+
: "ace/theme/" + settings.value.editorTheme,
140+
1,
141+
true,
142+
(highlighted) => {
143+
aiTabInstance?.addStyle(highlighted.css);
144+
codeElement.innerHTML = highlighted.html;
145+
},
146+
);
147+
}
148+
}
149+
150+
// copy functionality
151+
copyButton.addEventListener("click", async () => {
152+
const code = codeElement?.textContent || "";
153+
try {
154+
cordova.plugins.clipboard.copy(code);
155+
copyButton.querySelector("i").className = "icon check";
156+
setTimeout(() => {
157+
copyButton.querySelector("i").className = "icon copy";
158+
}, 2000);
159+
} catch (err) {
160+
copyButton.querySelector("i").className =
161+
"icon warningreport_problem";
162+
setTimeout(() => {
163+
copyButton.querySelector("i").className = "icon copy";
164+
}, 2000);
165+
}
166+
});
167+
168+
// expand/collapse functionality
169+
expandButton.addEventListener("click", () => {
170+
const isExpanded = codeContent.classList.contains("expanded");
171+
codeContent.classList.toggle("expanded", !isExpanded);
172+
expandButton.innerHTML = isExpanded
173+
? `<i class="icon keyboard_arrow_down"></i> <span>Show more</span>`
174+
: `<i class="icon keyboard_arrow_up"></i> <span>Show less</span>`;
175+
});
176+
177+
// Only show expand button if content overflows
178+
if (codeContent.scrollHeight <= codeContent.clientHeight) {
179+
expandButton.style.display = "none";
180+
}
181+
});
182+
};
183+
71184
const addMessage = (message) => {
72185
const messageEl = tag("div", {
73186
className: `message ${message.role === "user" ? "user" : ""}`,
@@ -124,7 +237,8 @@ export default function openAIAssistantPage() {
124237
if (message.role === "user") {
125238
messageContent.textContent = message.content;
126239
} else {
127-
messageContent.innerHTML = markdownIt().render(message.content);
240+
const md = markdownIt();
241+
messageContent.innerHTML = md.render(message.content);
128242
}
129243

130244
messageEl.appendChild(messageHeader);
@@ -229,8 +343,10 @@ export default function openAIAssistantPage() {
229343
const item = document.createElement("div");
230344
item.className = `history-item ${conv.id === currentConversationId ? "active" : ""}`;
231345
item.onclick = () => {
232-
if (conv.id !== currentConversationId)
346+
if (conv.id !== currentConversationId) {
233347
loadOrCreateConversation(conv.id);
348+
toggleHistorySidebar();
349+
}
234350
};
235351

236352
const iconWrapper = document.createElement("div");
@@ -265,6 +381,14 @@ export default function openAIAssistantPage() {
265381
chatHistory = [];
266382
messagesFromDB.forEach((msg) => {
267383
addMessage(msg);
384+
if (msg.role === "assistant") {
385+
formatCodeBlocks(
386+
messageContainerRef.el.querySelector(
387+
`#message-${msg.id} .message-content`,
388+
),
389+
msg.content,
390+
);
391+
}
268392
chatHistory.push({
269393
role: msg.role,
270394
content: msg.content,
@@ -374,20 +498,6 @@ export default function openAIAssistantPage() {
374498
});
375499

376500
try {
377-
const assistantPlaceholderMsg = {
378-
id: assistantMsgId,
379-
conversationId: currentConversationId,
380-
role: "assistant",
381-
content: "▌",
382-
timestamp: Date.now(),
383-
};
384-
addMessage(assistantPlaceholderMsg);
385-
386-
const messageElContent = messageContainerRef.el.querySelector(
387-
`#message-${assistantMsgId} .message-content`,
388-
);
389-
removeLoading();
390-
391501
//Chat history not passed anymore, memory saver and checkpoint will handle context
392502
const inputsForAgent = {
393503
messages: messagesForAgentTurn,
@@ -402,6 +512,22 @@ export default function openAIAssistantPage() {
402512
},
403513
});
404514

515+
// Remove loading indicator
516+
removeLoading();
517+
518+
const assistantPlaceholderMsg = {
519+
id: assistantMsgId,
520+
conversationId: currentConversationId,
521+
role: "assistant",
522+
content: "▌",
523+
timestamp: Date.now(),
524+
};
525+
addMessage(assistantPlaceholderMsg);
526+
527+
const messageElContent = messageContainerRef.el.querySelector(
528+
`#message-${assistantMsgId} .message-content`,
529+
);
530+
405531
for await (const eventData of stream) {
406532
let messageChunkPayload = null;
407533
if (
@@ -450,18 +576,18 @@ export default function openAIAssistantPage() {
450576
const isAbort =
451577
err.name === "AbortError" ||
452578
(err.message && /abort/i.test(err.message));
453-
streamedContent = isAbort
454-
? "Streaming cancelled by user."
455-
: `Error: ${err.message || "Unknown error."}`;
579+
580+
const errorContent = isAbort
581+
? `<span class="badge badge-yellow">Streaming cancelled by user.</span>`
582+
: `<span class="badge badge-red">Error: ${err.message || "Unknown error."}</span>`;
583+
584+
streamedContent += errorContent;
456585

457586
const targetMessageElContent = messageContainerRef.el.querySelector(
458587
`#message-${assistantMsgId} .message-content`,
459588
);
460589
if (targetMessageElContent) {
461-
const errorColor = isAbort
462-
? "var(--warning-text-color)"
463-
: "var(--error-text-color)";
464-
targetMessageElContent.innerHTML = `<span style="color: ${errorColor};">${isAbort ? streamedContent : md.render(streamedContent)}</span>`;
590+
targetMessageElContent.innerHTML += errorContent;
465591
}
466592
} finally {
467593
currentController = null;
@@ -499,45 +625,7 @@ export default function openAIAssistantPage() {
499625
);
500626
if (timeEl) timeEl.textContent = formatTime(finalTimestamp);
501627

502-
messageContentElToFinalize.innerHTML =
503-
messageContentElToFinalize.innerHTML.replace(
504-
/<pre><code(?: class="language-(\w+)")?>([\s\S]*?)<\/code><\/pre>/g,
505-
(match, language, code) => {
506-
language = language || "plaintext";
507-
code = he.decode(code);
508-
return `
509-
<div class="code-block">
510-
<div class="code-header">
511-
<div class="code-language"><i class="icon code"></i><span>${language}</span></div>
512-
<div class="code-actions"><button class="btn btn-icon code-copy" title="Copy code"><i class="icon copy"></i></button></div>
513-
</div>
514-
<div class="code-content"><pre><code class="language-${language}">${code}</code></pre></div>
515-
<div class="code-expand"><i class="icon keyboard_arrow_down"></i><span>Show more</span></div>
516-
</div>`;
517-
},
518-
);
519-
520-
messageContentElToFinalize
521-
.querySelectorAll(".code-block")
522-
.forEach((codeBlock) => {
523-
const codeContent = codeBlock.querySelector(".code-content");
524-
const codeElement = codeBlock.querySelector("pre code");
525-
const copyButton = codeBlock.querySelector(".code-copy");
526-
const expandButton = codeBlock.querySelector(".code-expand");
527-
// expand/collapse functionality
528-
expandButton.addEventListener("click", () => {
529-
const isExpanded = codeContent.classList.contains("expanded");
530-
codeContent.classList.toggle("expanded", !isExpanded);
531-
expandButton.innerHTML = isExpanded
532-
? `<i class="icon keyboard_arrow_down"></i> <span>Show more</span>`
533-
: `<i class="icon keyboard_arrow_up"></i> <span>Show less</span>`;
534-
});
535-
536-
// Only show expand button if content overflows
537-
if (codeContent.scrollHeight <= codeContent.clientHeight) {
538-
expandButton.style.display = "none";
539-
}
540-
});
628+
formatCodeBlocks(messageContentElToFinalize, streamedContent);
541629
}
542630
}
543631
};

src/pages/aiAssistant/assistant.module.scss

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,9 @@
413413
font-size: 0.625rem;
414414
font-weight: 500;
415415
margin-right: 0.25rem;
416+
word-break: break-word;
417+
overflow-wrap: break-word;
418+
white-space: normal;
416419
}
417420

418421
.badge-blue {

0 commit comments

Comments
 (0)