@@ -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+ / < p r e > < c o d e (?: c l a s s = " l a n g u a g e - ( \w + ) " ) ? > ( [ \s \S ] * ?) < \/ c o d e > < \/ p r e > / 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 ( / l a n g u a g e - ( \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 && / a b o r t / 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- / < p r e > < c o d e (?: c l a s s = " l a n g u a g e - ( \w + ) " ) ? > ( [ \s \S ] * ?) < \/ c o d e > < \/ p r e > / 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 } ;
0 commit comments