11document . addEventListener ( "DOMContentLoaded" , ( ) => {
2- const { chatBox, chatInput, clearChatBtn, voiceToggleBtn, modelSelect, currentSession, synth, autoSpeakEnabled, speakMessage, stopSpeaking, showToast, toggleSpeechRecognition, initSpeechRecognition } = window . _chatInternals ;
2+ const { chatBox, chatInput, clearChatBtn, voiceToggleBtn, modelSelect, synth, autoSpeakEnabled, speakMessage, stopSpeaking, showToast, toggleSpeechRecognition, initSpeechRecognition } = window . _chatInternals ;
3+
4+ // No static currentSession; we'll fetch it fresh each time
35 function randomSeed ( ) {
46 return Math . floor ( Math . random ( ) * 1000000 ) . toString ( ) ;
57 }
8+
69 function generateSessionTitle ( messages ) {
710 let title = "" ;
811 for ( let i = 0 ; i < messages . length ; i ++ ) {
@@ -15,19 +18,22 @@ document.addEventListener("DOMContentLoaded", () => {
1518 if ( title . length > 50 ) title = title . substring ( 0 , 50 ) + "..." ;
1619 return title ;
1720 }
21+
1822 function checkAndUpdateSessionTitle ( ) {
19- const current = Storage . getCurrentSession ( ) ;
20- if ( ! current . name || current . name === "New Chat" ) {
21- const newTitle = generateSessionTitle ( current . messages ) ;
22- if ( newTitle && newTitle !== current . name ) {
23- Storage . renameSession ( current . id , newTitle ) ;
23+ const currentSession = Storage . getCurrentSession ( ) ;
24+ if ( ! currentSession . name || currentSession . name === "New Chat" ) {
25+ const newTitle = generateSessionTitle ( currentSession . messages ) ;
26+ if ( newTitle && newTitle !== currentSession . name ) {
27+ Storage . renameSession ( currentSession . id , newTitle ) ;
2428 }
2529 }
2630 }
31+
2732 function waitForPrism ( callback ) {
2833 if ( window . Prism ) callback ( ) ;
2934 else setTimeout ( ( ) => waitForPrism ( callback ) , 100 ) ;
3035 }
36+
3137 function appendMessage ( { role, content, index } ) {
3238 const container = document . createElement ( "div" ) ;
3339 container . classList . add ( "message" ) ;
@@ -127,14 +133,58 @@ document.addEventListener("DOMContentLoaded", () => {
127133 }
128134 chatBox . appendChild ( container ) ;
129135 waitForPrism ( ( ) => {
130- container . querySelectorAll ( "pre code" ) . forEach ( ( block ) => Prism . highlightElement ( block ) ) ;
136+ const codeBlocks = container . querySelectorAll ( "pre code" ) ;
137+ codeBlocks . forEach ( ( block ) => {
138+ Prism . highlightElement ( block ) ;
139+ const buttonContainer = document . createElement ( "div" ) ;
140+ buttonContainer . style . display = "flex" ;
141+ buttonContainer . style . gap = "5px" ;
142+ buttonContainer . style . marginTop = "5px" ;
143+ const codeContent = block . textContent . trim ( ) ;
144+ const language = block . className . match ( / l a n g u a g e - ( \w + ) / ) ?. [ 1 ] || "text" ;
145+ const copyCodeBtn = document . createElement ( "button" ) ;
146+ copyCodeBtn . className = "message-action-btn" ;
147+ copyCodeBtn . textContent = "Copy Code" ;
148+ copyCodeBtn . style . fontSize = "12px" ;
149+ copyCodeBtn . addEventListener ( "click" , ( ) => {
150+ navigator . clipboard . writeText ( codeContent ) . then ( ( ) => {
151+ showToast ( "Code copied to clipboard" ) ;
152+ } ) . catch ( ( ) => {
153+ showToast ( "Failed to copy code" ) ;
154+ } ) ;
155+ } ) ;
156+ buttonContainer . appendChild ( copyCodeBtn ) ;
157+ const downloadCodeBtn = document . createElement ( "button" ) ;
158+ downloadCodeBtn . className = "message-action-btn" ;
159+ downloadCodeBtn . textContent = "Download" ;
160+ downloadCodeBtn . style . fontSize = "12px" ;
161+ downloadCodeBtn . addEventListener ( "click" , ( ) => {
162+ downloadCodeAsTxt ( codeContent , language ) ;
163+ } ) ;
164+ buttonContainer . appendChild ( downloadCodeBtn ) ;
165+ block . parentNode . parentNode . insertBefore ( buttonContainer , block . parentNode . nextSibling ) ;
166+ } ) ;
131167 } ) ;
132168 chatBox . scrollTop = chatBox . scrollHeight ;
133169 if ( autoSpeakEnabled && role === "ai" ) {
134170 stopSpeaking ( ) ;
135171 speakMessage ( content ) ;
136172 }
137173 }
174+
175+ function downloadCodeAsTxt ( codeContent , language ) {
176+ const blob = new Blob ( [ codeContent ] , { type : "text/plain" } ) ;
177+ const url = URL . createObjectURL ( blob ) ;
178+ const a = document . createElement ( "a" ) ;
179+ a . href = url ;
180+ a . download = `code-${ language } -${ Date . now ( ) } .txt` ;
181+ document . body . appendChild ( a ) ;
182+ a . click ( ) ;
183+ document . body . removeChild ( a ) ;
184+ URL . revokeObjectURL ( url ) ;
185+ showToast ( "Code downloaded as .txt" ) ;
186+ }
187+
138188 function createImageElement ( url ) {
139189 const imageContainer = document . createElement ( "div" ) ;
140190 imageContainer . className = "ai-image-container" ;
@@ -177,6 +227,7 @@ document.addEventListener("DOMContentLoaded", () => {
177227 imageContainer . appendChild ( buttonContainer ) ;
178228 return imageContainer ;
179229 }
230+
180231 function renderMarkdown ( mdText ) {
181232 if ( window . marked ) {
182233 marked . setOptions ( {
@@ -186,25 +237,31 @@ document.addEventListener("DOMContentLoaded", () => {
186237 return code ;
187238 }
188239 } ) ;
189- return marked . parse ( mdText . replace ( / \[ C O D E \] ( .* ?) \n ( [ \s \S ] * ?) \[ \ /C O D E \] / g, "```$1\n$2```" ) ) ;
240+ return marked . parse ( mdText . replace ( / \$ \$ C O D E \$ \$ ( .* ?) \n ( [ \s \S ] * ?) \$ \$ \ /C O D E \$ \$ / g, "```$1\n$2```" ) ) ;
190241 } else {
191242 return mdText . replace ( / \n / g, "<br>" ) ;
192243 }
193244 }
245+
194246 function escapeHTML ( html ) {
195247 return html . replace ( / & / g, "&" ) . replace ( / < / g, "<" ) . replace ( / > / g, ">" ) . replace ( / " / g, """ ) . replace ( / ' / g, "'" ) ;
196248 }
249+
197250 function renderStoredMessages ( messages ) {
198251 chatBox . innerHTML = "" ;
199252 messages . forEach ( ( msg , idx ) => appendMessage ( { role : msg . role , content : msg . content , index : idx } ) ) ;
200253 }
254+
201255 window . addNewMessage = function ( { role, content } ) {
256+ const currentSession = Storage . getCurrentSession ( ) ;
202257 currentSession . messages . push ( { role, content } ) ;
203258 Storage . updateSessionMessages ( currentSession . id , currentSession . messages ) ;
204259 appendMessage ( { role, content, index : currentSession . messages . length - 1 } ) ;
205260 if ( role === "ai" ) checkAndUpdateSessionTitle ( ) ;
206261 } ;
262+
207263 function editMessage ( msgIndex ) {
264+ const currentSession = Storage . getCurrentSession ( ) ;
208265 const oldMessage = currentSession . messages [ msgIndex ] ;
209266 if ( ! oldMessage ) return ;
210267 const newContent = prompt ( "Edit this message:" , oldMessage . content ) ;
@@ -237,16 +294,21 @@ document.addEventListener("DOMContentLoaded", () => {
237294 showToast ( "AI message updated" ) ;
238295 }
239296 }
297+
240298 function reGenerateAIResponse ( aiIndex ) {
299+ const currentSession = Storage . getCurrentSession ( ) ;
241300 if ( aiIndex < 0 || aiIndex >= currentSession . messages . length ) return ;
242301 let userIndex = - 1 ;
243- for ( let i = aiIndex ; i >= 0 ; i -- ) {
302+ for ( let i = aiIndex - 1 ; i >= 0 ; i -- ) {
244303 if ( currentSession . messages [ i ] . role === "user" ) {
245304 userIndex = i ;
246305 break ;
247306 }
248307 }
249- if ( userIndex === - 1 ) return ;
308+ if ( userIndex === - 1 ) {
309+ showToast ( "No preceding user message found to regenerate from." ) ;
310+ return ;
311+ }
250312 currentSession . messages = currentSession . messages . slice ( 0 , userIndex + 1 ) ;
251313 Storage . updateSessionMessages ( currentSession . id , currentSession . messages ) ;
252314 renderStoredMessages ( currentSession . messages ) ;
@@ -261,13 +323,16 @@ document.addEventListener("DOMContentLoaded", () => {
261323 loadingDiv . textContent = "Regenerating response..." ;
262324 chatBox . appendChild ( loadingDiv ) ;
263325 chatBox . scrollTop = chatBox . scrollHeight ;
264- const userMessage = currentSession . messages [ userIndex ] ;
326+ const userMessage = currentSession . messages [ userIndex ] . content ;
265327 sendToPollinations ( ( ) => {
266328 const loadingMsg = document . getElementById ( loadingMsgId ) ;
267329 if ( loadingMsg ) loadingMsg . remove ( ) ;
268- } , userMessage . content ) ;
330+ showToast ( "Response regenerated successfully" ) ;
331+ } , userMessage ) ;
269332 }
333+
270334 window . sendToPollinations = function ( callback = null , overrideContent = null ) {
335+ const currentSession = Storage . getCurrentSession ( ) ;
271336 const loadingMsgId = "loading-" + Date . now ( ) ;
272337 const loadingDiv = document . createElement ( "div" ) ;
273338 loadingDiv . id = loadingMsgId ;
@@ -310,6 +375,28 @@ document.addEventListener("DOMContentLoaded", () => {
310375 const loadingMsg = document . getElementById ( loadingMsgId ) ;
311376 if ( loadingMsg ) loadingMsg . remove ( ) ;
312377 let aiContent = extractAIContent ( data ) ;
378+
379+ // Check if the user's prompt is requesting an image
380+ const lastUserMsg = messages [ messages . length - 1 ] . content . toLowerCase ( ) ;
381+ const isImageRequest = lastUserMsg . includes ( "image" ) || lastUserMsg . includes ( "picture" ) || lastUserMsg . includes ( "show me" ) || lastUserMsg . includes ( "generate an image" ) ;
382+
383+ // If the prompt suggests an image request but no image URL is in the response, generate one
384+ if ( aiContent && isImageRequest && ! aiContent . includes ( "https://image.pollinations.ai" ) ) {
385+ let imagePrompt = lastUserMsg
386+ . replace ( / s h o w m e | g e n e r a t e | i m a g e o f | p i c t u r e o f | i m a g e | p i c t u r e / gi, "" )
387+ . trim ( ) ;
388+
389+ if ( imagePrompt . length < 5 && aiContent . toLowerCase ( ) . includes ( "image" ) ) {
390+ imagePrompt = aiContent
391+ . toLowerCase ( )
392+ . replace ( / h e r e ' s a n i m a g e o f | i m a g e | t o e n j o y v i s u a l l y / gi, "" )
393+ . trim ( ) ;
394+ }
395+
396+ const imageUrl = `https://image.pollinations.ai/prompt/${ encodeURIComponent ( imagePrompt ) } ?width=512&height=512&seed=${ randomSeed ( ) } ` ;
397+ aiContent += `\n\n**Generated Image:**\n${ imageUrl } ` ;
398+ }
399+
313400 if ( aiContent ) {
314401 const foundMemories = parseMemoryBlocks ( aiContent ) ;
315402 foundMemories . forEach ( ( m ) => Memory . addMemoryEntry ( m ) ) ;
@@ -328,6 +415,7 @@ document.addEventListener("DOMContentLoaded", () => {
328415 }
329416 } ) ;
330417 } ;
418+
331419 function extractAIContent ( response ) {
332420 if ( response . choices && response . choices . length > 0 ) {
333421 if ( response . choices [ 0 ] . message && response . choices [ 0 ] . message . content ) return response . choices [ 0 ] . message . content ;
@@ -336,16 +424,19 @@ document.addEventListener("DOMContentLoaded", () => {
336424 else if ( typeof response === "string" ) return response ;
337425 return "Sorry, I couldn't process that response." ;
338426 }
427+
339428 function parseMemoryBlocks ( text ) {
340429 const memRegex = / \[ m e m o r y \] ( [ \s \S ] * ?) \[ \/ m e m o r y \] / gi;
341430 const found = [ ] ;
342431 let match ;
343432 while ( ( match = memRegex . exec ( text ) ) !== null ) found . push ( match [ 1 ] . trim ( ) ) ;
344433 return found ;
345434 }
435+
346436 function removeMemoryBlocks ( text ) {
347437 return text . replace ( / \[ m e m o r y \] [ \s \S ] * ?\[ \/ m e m o r y \] / gi, "" ) ;
348438 }
439+
349440 if ( voiceToggleBtn ) {
350441 voiceToggleBtn . addEventListener ( "click" , window . _chatInternals . toggleAutoSpeak ) ;
351442 window . _chatInternals . updateVoiceToggleUI ( ) ;
@@ -364,8 +455,10 @@ document.addEventListener("DOMContentLoaded", () => {
364455 }
365456 } , 2000 ) ;
366457 }
458+
367459 if ( clearChatBtn ) {
368460 clearChatBtn . addEventListener ( "click" , ( ) => {
461+ const currentSession = Storage . getCurrentSession ( ) ;
369462 if ( confirm ( "Are you sure you want to clear this chat?" ) ) {
370463 currentSession . messages = [ ] ;
371464 Storage . updateSessionMessages ( currentSession . id , currentSession . messages ) ;
@@ -374,6 +467,7 @@ document.addEventListener("DOMContentLoaded", () => {
374467 }
375468 } ) ;
376469 }
470+
377471 function checkFirstLaunch ( ) {
378472 const firstLaunch = localStorage . getItem ( "firstLaunch" ) === "0" ;
379473 if ( firstLaunch ) {
@@ -404,6 +498,7 @@ document.addEventListener("DOMContentLoaded", () => {
404498 }
405499 }
406500 checkFirstLaunch ( ) ;
501+
407502 function setupVoiceInputButton ( ) {
408503 if ( "webkitSpeechRecognition" in window || "SpeechRecognition" in window ) {
409504 const inputButtonsContainer = document . querySelector ( ".input-buttons-container" ) ;
@@ -419,6 +514,7 @@ document.addEventListener("DOMContentLoaded", () => {
419514 }
420515 }
421516 setupVoiceInputButton ( ) ;
517+
422518 const sendButton = document . getElementById ( "send-button" ) ;
423519 function handleSendMessage ( ) {
424520 const message = chatInput . value . trim ( ) ;
@@ -429,21 +525,26 @@ document.addEventListener("DOMContentLoaded", () => {
429525 window . sendToPollinations ( ) ;
430526 sendButton . disabled = true ;
431527 }
528+
432529 chatInput . addEventListener ( "input" , ( ) => {
433530 sendButton . disabled = chatInput . value . trim ( ) === "" ;
434531 chatInput . style . height = "auto" ;
435532 chatInput . style . height = chatInput . scrollHeight + "px" ;
436533 } ) ;
534+
437535 chatInput . addEventListener ( "keydown" , ( e ) => {
438536 if ( e . key === "Enter" && ! e . shiftKey ) {
439537 e . preventDefault ( ) ;
440538 handleSendMessage ( ) ;
441539 }
442540 } ) ;
541+
443542 sendButton . addEventListener ( "click" , ( ) => {
444543 handleSendMessage ( ) ;
445544 } ) ;
446- if ( currentSession . messages && currentSession . messages . length > 0 ) {
447- renderStoredMessages ( currentSession . messages ) ;
545+
546+ const initialSession = Storage . getCurrentSession ( ) ;
547+ if ( initialSession . messages && initialSession . messages . length > 0 ) {
548+ renderStoredMessages ( initialSession . messages ) ;
448549 }
449550} ) ;
0 commit comments