User: I've got an issue currently with my AI assistant interface website, where the code bubbles are inconsistent in keeping their code contained. If I attach HTML or CSS, it works fine, but if I attach python code, it spills out with a bunch of escaped characters. However, this only happens in the actual message - in the prompt input, it stays contained. I assume this may have something to do with certain characters used in Python that are causing issues. How can I fix this?<div><br></div>
      <span class="inline-flex items-center bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-300 px-3 py-1 rounded-full mr-2 mb-2 code-bubble cursor-pointer" data-filename="escapeHtml.js" data-notes="" data-codecontent="%2F%2F%20src%2Futils%2FescapeHtml.js%0A%0Aexport%20const%20escapeHtml%20%3D%20(unsafe)%20%3D%26gt%3B%20%7B%0A%20%20%20%20if%20(typeof%20unsafe%20!%3D%3D%20%26%23039%3Bstring%26%23039%3B)%20%7B%0A%20%20%20%20%20%20console.warn(%26%23039%3BescapeHtml%20received%20non-string%3A%26%23039%3B%2C%20unsafe)%3B%0A%20%20%20%20%20%20unsafe%20%3D%20String(unsafe)%3B%0A%20%20%20%20%7D%0A%20%20%20%20return%20unsafe%0A%20%20%20%20%20%20.replace(%2F%26amp%3B%2Fg%2C%20%26quot%3B%26amp%3Bamp%3B%26quot%3B)%0A%20%20%20%20%20%20.replace(%2F%26lt%3B%2Fg%2C%20%26quot%3B%26amp%3Blt%3B%26quot%3B)%0A%20%20%20%20%20%20.replace(%2F%26gt%3B%2Fg%2C%20%26quot%3B%26amp%3Bgt%3B%26quot%3B)%0A%20%20%20%20%20%20.replace(%2F%26quot%3B%2Fg%2C%20%26quot%3B%26amp%3Bquot%3B%26quot%3B)%0A%20%20%20%20%20%20.replace(%2F%26%23039%3B%2Fg%2C%20%26quot%3B%26amp%3B%23039%3B%26quot%3B)%3B%0A%20%20%7D%3B%0A%20%20">
        escapeHtml.js
      </span>
      ​
    
      <span class="inline-flex items-center bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-300 px-3 py-1 rounded-full mr-2 mb-2 code-bubble cursor-pointer" data-filename="renderCodeBubble.js" data-notes="" data-codecontent="%2F%2F%20src%2Futils%2FrenderCodeBubble.js%0Aexport%20const%20renderCodeBubble%20%3D%20(fileName%2C%20notes%2C%20codeContent)%20%3D%26gt%3B%20%7B%0A%20%20%20%20%2F%2F%20Encode%20the%20codeContent%20to%20safely%20include%20it%20in%20a%20data%20attribute%0A%20%20%20%20const%20encodedCodeContent%20%3D%20encodeURIComponent(codeContent%20%7C%7C%20%26%23039%3B%26%23039%3B)%3B%0A%20%20%20%20%0A%20%20%20%20return%20%60%0A%20%20%20%20%20%20%26lt%3Bspan%0A%20%20%20%20%20%20%20%20class%3D%26quot%3Binline-flex%20items-center%20bg-blue-100%20dark%3Abg-blue-900%20text-blue-800%20dark%3Atext-blue-300%20px-3%20py-1%20rounded-full%20mr-2%20mb-2%20code-bubble%20cursor-pointer%26quot%3B%0A%20%20%20%20%20%20%20%20data-filename%3D%26quot%3B%24%7BfileName%20%7C%7C%20%26%23039%3BUntitled%26%23039%3B%7D%26quot%3B%0A%20%20%20%20%20%20%20%20data-notes%3D%26quot%3B%24%7Bnotes%20%7C%7C%20%26%23039%3B%26%23039%3B%7D%26quot%3B%0A%20%20%20%20%20%20%20%20data-codecontent%3D%26quot%3B%24%7BencodedCodeContent%7D%26quot%3B%0A%20%20%20%20%20%20%26gt%3B%0A%20%20%20%20%20%20%20%20%24%7BfileName%20%7C%7C%20%26%23039%3BUntitled%26%23039%3B%7D%0A%20%20%20%20%20%20%26lt%3B%2Fspan%26gt%3B%0A%20%20%20%20%20%20%5Cu200B%0A%20%20%20%20%60%3B%0A%20%20%7D%3B%0A%20%20">
        renderCodeBubble.js
      </span>
      ​
    <code fileName="ChatContext.js" notes="">// src/context/ChatContext.js
import React, { createContext, useState, useEffect } from &#039;react&#039;;
import { v4 as uuidv4 } from &#039;uuid&#039;;

export const ChatContext = createContext();

export const ChatProvider = ({ children }) =&gt; {
  const [chats, setChats] = useState(() =&gt; {
    const saved = localStorage.getItem(&#039;chats&#039;);
    return saved ? JSON.parse(saved) : [];
  });

  useEffect(() =&gt; {
    const saved = localStorage.getItem(&#039;chats&#039;);
    if (saved) {
      const parsedChats = JSON.parse(saved).map(chat =&gt; ({
        ...chat,
        messages: chat.messages.map(msg =&gt; ({
          ...msg,
          text: String(msg.text) // Ensure text is string
        }))
      }));
      setChats(parsedChats);
    } else {
      setChats([]);
    }
  }, []);

  const [currentChatId, setCurrentChatId] = useState(() =&gt; {
    const saved = localStorage.getItem(&#039;currentChatId&#039;);
    return saved ? JSON.parse(saved) : null;
  });

  const [models, setModels] = useState([]); // List of available models
  const [selectedModelId, setSelectedModelId] = useState(() =&gt; {
    const saved = localStorage.getItem(&#039;selectedModelId&#039;);
    return saved ? JSON.parse(saved) : null;
  });

  // Fetch models from the backend on component mount
  useEffect(() =&gt; {
    fetch(&#039;http://localhost:8000/api/models&#039;)
      .then((res) =&gt; {
        if (!res.ok) {
          throw new Error(`HTTP error! Status: ${res.status}`);
        }
        return res.json();
      })
      .then((data) =&gt; {
        console.log(&#039;Fetched models:&#039;, data); // Debugging line
        if (Array.isArray(data) &amp;&amp; data.length &gt; 0 &amp;&amp; !selectedModelId) {
          setSelectedModelId(data[0].id); // Select the first model by default
        }
        setModels(data);
      })
      .catch((error) =&gt; {
        console.error(&#039;Error fetching models:&#039;, error);
        setModels([]); // Prevent `.map` errors
      });
  }, []);

  // Persist chats to localStorage
  useEffect(() =&gt; {
    localStorage.setItem(&#039;chats&#039;, JSON.stringify(chats));
  }, [chats]);

  // Persist currentChatId to localStorage
  useEffect(() =&gt; {
    localStorage.setItem(&#039;currentChatId&#039;, JSON.stringify(currentChatId));
  }, [currentChatId]);

  // Persist selectedModelId to localStorage
  useEffect(() =&gt; {
    if (selectedModelId) {
      localStorage.setItem(&#039;selectedModelId&#039;, JSON.stringify(selectedModelId));
    }
  }, [selectedModelId]);

  const addChat = (name = `Chat ${chats.length + 1}`) =&gt; {
    const newChat = {
      id: uuidv4(),
      name: name,
      messages: [],
      systemPrompt: &#039;&#039;,
      model_id: selectedModelId, // Associate with selected model
    };
    setChats((prev) =&gt; [newChat, ...prev]);
    setCurrentChatId(newChat.id);
  };  

  const updateChat = (id, updatedChat) =&gt; {
    console.log(`Updating chat with ID: ${id}`, updatedChat); // Add this line
    setChats((prevChats) =&gt;
      prevChats.map((chat) =&gt; (chat.id === id ? { ...chat, ...updatedChat } : chat))
    );
  };

  const addMessage = (id, message) =&gt; {
    setChats((prevChats) =&gt;
      prevChats.map((chat) =&gt;
        chat.id === id
          ? { ...chat, messages: [...chat.messages, { ...message, text: String(message.text) }] }
          : chat
      )
    );
  };

  const deleteChat = (id) =&gt; {
    setChats((prevChats) =&gt; prevChats.filter((chat) =&gt; chat.id !== id));
    // If the deleted chat was the current one, reset currentChatId
    if (id === currentChatId) {
      setCurrentChatId(null);
    }
  };

  const clearChats = () =&gt; {
    setChats([]);
    setCurrentChatId(null);
  };

  const updateMessage = (chatId, messageIndex, updatedMessage) =&gt; {
    setChats((prevChats) =&gt;
      prevChats.map((chat) =&gt;
        chat.id === chatId
          ? {
              ...chat,
              messages: chat.messages.map((msg, idx) =&gt;
                idx === messageIndex
                  ? { ...msg, text: String(updatedMessage.text) }
                  : msg
              ),
            }
          : chat
      )
    );
  };

  const addModel = (model) =&gt; {
    setModels((prev) =&gt; [...prev, model]);
  };

  const removeModel = (modelId) =&gt; {
    setModels((prev) =&gt; prev.filter((model) =&gt; model.id !== modelId));
    // Optionally, handle chat associations if the removed model was in use
    if (selectedModelId === modelId) {
      setSelectedModelId(models.length &gt; 0 ? models[0].id : null);
    }
  };

  return (
    &lt;ChatContext.Provider
      value={{
        chats,
        currentChatId,
        setCurrentChatId,
        addChat,
        updateChat,
        addMessage,
        updateMessage,
        deleteChat,
        clearChats,
        models,
        addModel,
        removeModel,
        selectedModelId,
        setSelectedModelId,
      }}
    &gt;
      {children}
    &lt;/ChatContext.Provider&gt;
  );
};
</code><code fileName="CodeContext.js" notes="">// src/context/CodeContext.js
import React, { createContext, useState } from &#039;react&#039;;

export const CodeContext = createContext();

export const CodeProvider = ({ children }) =&gt; {
  const [isSidebarOpen, setIsSidebarOpen] = useState(false);
  const [currentCode, setCurrentCode] = useState(null);

  const showCode = (codeAttachment) =&gt; {
    setCurrentCode(codeAttachment);
    setIsSidebarOpen(true);
  };

  const hideCode = () =&gt; {
    setIsSidebarOpen(false);
    setCurrentCode(null);
  };

  return (
    &lt;CodeContext.Provider value={{ isSidebarOpen, currentCode, showCode, hideCode }}&gt;
      {children}
    &lt;/CodeContext.Provider&gt;
  );
};
</code><code fileName="ChatInterface.js" notes="">// src/components/ChatInterface.js
import React, { useState, useContext, useRef, useEffect, useCallback } from &quot;react&quot;;
import { ChatContext } from &quot;../context/ChatContext&quot;;
import Message from &quot;./Message&quot;;
import CodeModal from &quot;./CodeModal&quot;;
import InlineCodeEditor from &quot;./InlineCodeEditor&quot;;
import { escapeHtml } from &quot;../utils/escapeHtml&quot;;

function ChatInterface() {
  const {
    chats,
    currentChatId,
    addMessage,
    updateChat,
    selectedModelId,
    models,
  } = useContext(ChatContext);
  const currentChat = chats.find((chat) =&gt; chat.id === currentChatId);
  const [message, setMessage] = useState(&quot;&quot;);
  const [isTyping, setIsTyping] = useState(false);
  const messagesEndRef = useRef(null);
  const [isCodeModalOpen, setIsCodeModalOpen] = useState(false);
  const [currentEditingCode, setCurrentEditingCode] = useState(null);
  const [isSystemPromptOpen, setIsSystemPromptOpen] = useState(false);

  const addCodeAttachment = ({ codeContent, fileName, notes }) =&gt; {
    const escapedCode = escapeHtml(codeContent);
    const escapedFileName = escapeHtml(fileName || &quot;untitled&quot;);
    const escapedNotes = escapeHtml(notes || &quot;&quot;);
    const codeTag = `&lt;code fileName=&quot;${escapedFileName}&quot; notes=&quot;${escapedNotes}&quot;&gt;${escapedCode}&lt;/code&gt;`;
    setMessage((prev) =&gt; prev + codeTag);
  };

  const handleAttachCode = useCallback((codeSegment = null) =&gt; {
    setCurrentEditingCode(codeSegment);
    setIsCodeModalOpen(true);
  }, []);

  const handleCodeSave = (updatedCode) =&gt; {
    if (currentEditingCode) {
      // Update existing code
      const escapedCode = escapeHtml(updatedCode.codeContent);
      const escapedFileName = escapeHtml(updatedCode.fileName || &quot;untitled&quot;);
      const escapedNotes = escapeHtml(updatedCode.notes || &quot;&quot;);

      // Improved regex to handle multi-line and special characters
      const regex = new RegExp(
        `&lt;code\\s+fileName=&quot;${currentEditingCode.fileName}&quot;\\s+notes=&quot;${currentEditingCode.notes}&quot;&gt;([\\s\\S]*?)&lt;\\/code&gt;`
      );

      const updatedMessage = message.replace(
        regex,
        `&lt;code fileName=&quot;${escapedFileName}&quot; notes=&quot;${escapedNotes}&quot;&gt;${escapedCode}&lt;/code&gt;`
      );
      setMessage(updatedMessage);
    } else {
      // Add new code
      addCodeAttachment(updatedCode);
    }
    setIsCodeModalOpen(false);
    setCurrentEditingCode(null);
  };

  const handleSend = useCallback(async () =&gt; {
    if (!message.trim() || !currentChatId || !selectedModelId) return;

    // Retrieve the system prompt from the current chat
    const systemPrompt = currentChat.systemPrompt || &quot;&quot;;

    // Combine the system prompt with the user message
    // Ensure there&#039;s a separator (like a newline) for clarity
    const fullMessage = systemPrompt ? `${systemPrompt}\n${message}` : message;

    const userMessage = { sender: &quot;user&quot;, text: message };
    addMessage(currentChatId, userMessage);
    setMessage(&quot;&quot;);
    scrollToBottom();

    // Set typing indicator
    setIsTyping(true);

    try {
      const response = await fetch(&quot;http://localhost:8000/api/chat&quot;, {
        method: &quot;POST&quot;,
        headers: { &quot;Content-Type&quot;: &quot;application/json&quot; },
        body: JSON.stringify({
          session_id: currentChatId,
          text: fullMessage,
          model_id: selectedModelId, // Ensure this is correctly set
        }),
      });

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(
          errorData.detail || &quot;Failed to get response from the server.&quot;
        );
      }

      const data = await response.json();
      const responseText = data.response;
      const responseModelId = data.model_id; // Extract model_id from response

      const botMessage = {
        sender: &quot;bot&quot;,
        text: responseText,
        model_id: responseModelId, // Include model_id
      };
      addMessage(currentChatId, botMessage);
      scrollToBottom();
    } catch (error) {
      console.error(&quot;Error:&quot;, error);
      const errorMessage = {
        sender: &quot;bot&quot;,
        text: &quot;Sorry, something went wrong. Please try again.&quot;,
      };
      addMessage(currentChatId, errorMessage);
      scrollToBottom();
    } finally {
      // Remove typing indicator
      setIsTyping(false);
    }
  });

  const handleKeyDown = useCallback(
    (e) =&gt; {
      if (e.key === &quot;Enter&quot; &amp;&amp; !e.shiftKey) {
        e.preventDefault();
        handleSend();
      }
    },
    [handleSend]
  );

  const handleEditorKeyDown = useCallback((e) =&gt; {
    if (e.key === &#039;Enter&#039; &amp;&amp; !e.shiftKey) {
      e.preventDefault();
      handleSend();
    }
  }, [handleSend]);


  const scrollToBottom = useCallback(() =&gt; {
    messagesEndRef.current?.scrollIntoView({ behavior: &quot;smooth&quot; });
  }, []);

  if (!currentChat) {
    return (
      &lt;div className=&quot;flex-1 p-4&quot;&gt;No active chat. Please start a new chat.&lt;/div&gt;
    );
  }

  const handleSystemPromptChange = (e) =&gt; {
    const newPrompt = e.target.value;
    updateChat(currentChatId, { systemPrompt: newPrompt });
  };

  const toggleSystemPrompt = () =&gt; {
    setIsSystemPromptOpen(!isSystemPromptOpen);
  };

  return (
    &lt;div className=&quot;flex flex-col h-screen&quot;&gt;
      &lt;div className=&quot;flex-1 p-32 pt-8 pb-8 overflow-y-auto bg-[#eeeeee] dark:bg-[#161618]&quot;&gt;
        {/* Removed Model Selector Dropdown */}

        {/* Chat Messages */}
        {currentChat.messages.map((msg, index) =&gt; (
          &lt;Message
            key={index}
            chatId={currentChatId}
            messageIndex={index}
            sender={msg.sender}
            text={msg.text}
            model_id={msg.model_id} // Pass model_id
          /&gt;
        ))}
        {isTyping &amp;&amp; (
          &lt;div className=&quot;mb-4 flex justify-start&quot;&gt;
            &lt;div className=&quot;max-w-xs md:max-w-md lg:max-w-lg p-3 rounded-lg bg-gray-200 dark:bg-gray-800 text-gray-800 dark:text-gray-200 flex items-center&quot;&gt;
              &lt;div className=&quot;flex space-x-1 mr-2&quot;&gt;
                &lt;div className=&quot;w-2 h-2 bg-gray-600 rounded-full animate-bounce-custom animation-delay-0&quot;&gt;&lt;/div&gt;
                &lt;div className=&quot;w-2 h-2 bg-gray-600 rounded-full animate-bounce-custom animation-delay-200&quot;&gt;&lt;/div&gt;
                &lt;div className=&quot;w-2 h-2 bg-gray-600 rounded-full animate-bounce-custom animation-delay-400&quot;&gt;&lt;/div&gt;
              &lt;/div&gt;
              &lt;span className=&quot;text-sm&quot;&gt;Echo is typing...&lt;/span&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        )}
        &lt;div ref={messagesEndRef} /&gt;
      &lt;/div&gt;
      &lt;div
        id=&quot;message_area&quot;
        className=&quot;p-4 border-t dark:border-gray-700 bg-white dark:bg-[#161618]&quot;
      &gt;
        &lt;button
          onClick={toggleSystemPrompt}
          className=&quot;mb-2 text-sm text-blue-500 hover:underline focus:outline-none&quot;
        &gt;
          {isSystemPromptOpen ? &quot;Hide System Prompt&quot; : &quot;Show System Prompt&quot;}
        &lt;/button&gt;
        {isSystemPromptOpen &amp;&amp; (
          &lt;textarea
            className=&quot;w-full p-2 border rounded-lg resize-none dark:bg-gray-800 dark:text-gray-200 mb-2&quot;
            rows=&quot;3&quot;
            placeholder=&quot;Enter system prompt...&quot;
            value={currentChat.systemPrompt}
            onChange={handleSystemPromptChange}
          /&gt;
        )}
        &lt;div className=&quot;flex flex-row&quot;&gt;
          &lt;div className=&quot;flex-grow&quot;&gt;
            &lt;InlineCodeEditor
              value={message}
              onChange={setMessage}
              onAttachCode={handleAttachCode}
              onKeyDown={handleEditorKeyDown}
            /&gt;
          &lt;/div&gt;
          &lt;div className=&quot;flex flex-col flex-shrink-0 items-start pl-4 space-y-2&quot;&gt;
            &lt;button
              onClick={() =&gt; setIsCodeModalOpen(true)}
              className=&quot;bg-blue-500 text-white px-3 py-2 rounded-lg hover:bg-blue-600 flex-grow w-full&quot;
            &gt;
              Attach Code
            &lt;/button&gt;
            &lt;button
              className=&quot;bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 flex-grow mt-2 w-full&quot;
              onClick={handleSend}
            &gt;
              Send
            &lt;/button&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      {isCodeModalOpen &amp;&amp; (
        &lt;CodeModal
          onClose={() =&gt; {
            setIsCodeModalOpen(false);
            setCurrentEditingCode(null);
          }}
          onSave={handleCodeSave}
          initialData={currentEditingCode}
        /&gt;
      )}
    &lt;/div&gt;
  );
}

export default ChatInterface;
</code><code fileName="CodeAttachmentBubble.js" notes="">// src/components/CodeAttachmentBubble.js
import React from &#039;react&#039;;
import { EyeIcon, TrashIcon } from &#039;@heroicons/react/solid&#039;;

function CodeAttachmentBubble({ attachment, onEdit, onDelete }) {
  return (
    &lt;div className=&quot;inline-flex items-center bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-300 px-3 py-1 rounded-full mr-2 mb-2&quot;&gt;
      &lt;span className=&quot;mr-2 text-xs md:text-sm truncate&quot; title={attachment.fileName}&gt;
        {attachment.fileName || &#039;Untitled&#039;}
      &lt;/span&gt;
      &lt;button
        onClick={onEdit}
        className=&quot;hover:text-blue-600 dark:hover:text-blue-500 focus:outline-none&quot;
        title=&quot;View Attachment&quot;
        aria-label=&quot;View Attachment&quot;
      &gt;
        &lt;EyeIcon className=&quot;h-4 w-4&quot; /&gt;
      &lt;/button&gt;
    &lt;/div&gt;
  );
}

export default CodeAttachmentBubble;
</code><code fileName="InlineCodeEditor.js" notes="">// src/components/InlineCodeEditor.js
import React, { useRef, useEffect, useCallback } from &#039;react&#039;;
import CodeAttachmentBubble from &#039;./CodeAttachmentBubble&#039;;
import { renderCodeBubble } from &#039;../utils/renderCodeBubble&#039;;

function InlineCodeEditor({ value, onChange, onAttachCode }) {
  const editorRef = useRef(null);

  useEffect(() =&gt; {
    if (editorRef.current &amp;&amp; editorRef.current.innerHTML !== formatContent(value)) {
      editorRef.current.innerHTML = formatContent(value);
    }
  }, [value]);

  const formatContent = (content) =&gt; {
    return content.replace(/&lt;code([^&gt;]*)&gt;([\s\S]*?)&lt;\/code&gt;/g, (match, attributes, code) =&gt; {
      const fileName = attributes.match(/fileName=&quot;([^&quot;]*)&quot;/)?.[1] || &#039;Untitled&#039;;
      const notes = attributes.match(/notes=&quot;([^&quot;]*)&quot;/)?.[1] || &#039;&#039;;
      // Pass the actual codeContent to renderCodeBubble
      return renderCodeBubble(fileName, notes, code);
    });
  };

  const handleInput = useCallback(() =&gt; {
    if (editorRef.current) {
      const content = editorRef.current.innerHTML;
      const plainText = content.replace(/&lt;span class=&quot;code-bubble&quot;[^&gt;]*&gt;[^&lt;]*&lt;\/span&gt;/g, (match) =&gt; {
        const fileName = match.match(/data-filename=&quot;([^&quot;]*)&quot;/)?.[1];
        const notes = match.match(/data-notes=&quot;([^&quot;]*)&quot;/)?.[1];
        const codeContentEscaped = match.match(/data-codecontent=&quot;([^&quot;]*)&quot;/)?.[1];
        // Unescape the codeContent
        const codeContent = codeContentEscaped ? decodeURIComponent(codeContentEscaped) : &#039;&#039;;
        return `&lt;code fileName=&quot;${fileName}&quot; notes=&quot;${notes}&quot;&gt;${codeContent}&lt;/code&gt;`;
      });
      onChange(plainText);
    }
  }, [onChange]);

  const handleKeyDown = useCallback((e) =&gt; {
    if (e.key === &#039;Enter&#039;) {
      if (e.shiftKey) {
        // Allow Shift+Enter to insert a newline
        // Let the event propagate naturally
      } else {
        // Prevent default behavior and emit a custom event or handle sending the message
        // Here, you might want to trigger the send action if needed
        // For InlineCodeEditor, we&#039;ll allow the parent to handle message sending
        // by not preventing default here
      }
    }
  }, []);

  const handlePaste = (e) =&gt; {
    e.preventDefault();
    const text = e.clipboardData.getData(&#039;text/plain&#039;);
    document.execCommand(&#039;insertText&#039;, false, text);
  };

  const handleBubbleClick = useCallback((e) =&gt; {
    if (e.target.classList.contains(&#039;code-bubble&#039;)) {
      const fileName = e.target.getAttribute(&#039;data-filename&#039;);
      const notes = e.target.getAttribute(&#039;data-notes&#039;);
      const codeContentEscaped = e.target.getAttribute(&#039;data-codecontent&#039;);
      const codeContent = codeContentEscaped ? decodeURIComponent(codeContentEscaped) : &#039;&#039;;
      onAttachCode({ fileName, notes, codeContent });
    }
  }, [onAttachCode]);

  return (
    &lt;div className=&quot;relative border rounded-lg p-2 bg-white dark:bg-[#18181b] text-black dark:text-white&quot;&gt;
      &lt;div
        ref={editorRef}
        className=&quot;w-full min-h-[100px] max-h-[300px] overflow-y-auto p-2 focus:outline-none break-all&quot;
        contentEditable
        onInput={handleInput}
        onKeyDown={handleKeyDown}
        onPaste={handlePaste}
        onClick={handleBubbleClick}
        role=&quot;textbox&quot;
        aria-multiline=&quot;true&quot;
        aria-label=&quot;Code Editor&quot;
      /&gt;
    &lt;/div&gt;
  );
}

export default InlineCodeEditor;
</code><code fileName="MarkdownRenderer.js" notes="">import React, { useContext, useState } from &#039;react&#039;;
import ReactMarkdown from &#039;react-markdown&#039;;
import { Prism as SyntaxHighlighter } from &#039;react-syntax-highlighter&#039;;
import { coldarkDark } from &#039;react-syntax-highlighter/dist/esm/styles/prism&#039;;
import { CodeContext } from &#039;../context/CodeContext&#039;;
import { ExternalLinkIcon, ClipboardCopyIcon } from &#039;@heroicons/react/outline&#039;;
import { CopyToClipboard } from &#039;react-copy-to-clipboard&#039;;
import &#039;../styles/MarkdownRenderer.css&#039;

const MarkdownRenderer = ({ text }) =&gt; {
  const { showCode } = useContext(CodeContext);
  const [copiedCode, setCopiedCode] = useState({}); // Track copied state per code block

  return (
    &lt;ReactMarkdown
      components={{
        code({ node, inline, className, children, ...props }) {
          const match = /language-(\w+)/.exec(className || &#039;&#039;);
          if (!inline &amp;&amp; match) {
            const language = match[1];
            const codeContent = String(children).replace(/\n$/, &#039;&#039;);
            const key = node.position.start.line; // Unique key per block based on line number

            const handleCopy = () =&gt; {
              setCopiedCode((prev) =&gt; ({ ...prev, [key]: true }));
              setTimeout(() =&gt; {
                setCopiedCode((prev) =&gt; ({ ...prev, [key]: false }));
              }, 2000);
            };

            return (
              &lt;div className=&quot;relative my-4&quot;&gt;
                &lt;SyntaxHighlighter style={coldarkDark} language={language} PreTag=&quot;div&quot; {...props}&gt;
                  {codeContent}
                &lt;/SyntaxHighlighter&gt;
                &lt;div className=&quot;absolute top-2 right-2 flex space-x-2&quot;&gt;
                  &lt;CopyToClipboard text={codeContent} onCopy={handleCopy}&gt;
                    &lt;button
                      className=&quot;bg-gray-700 bg-opacity-50 text-white p-1 rounded hover:bg-opacity-75 flex items-center&quot;
                      title=&quot;Copy Code&quot;
                    &gt;
                      &lt;ClipboardCopyIcon className=&quot;h-4 w-4&quot; /&gt;
                      {copiedCode[key] &amp;&amp; &lt;span className=&quot;ml-1 text-xs&quot;&gt;Copied!&lt;/span&gt;}
                    &lt;/button&gt;
                  &lt;/CopyToClipboard&gt;
                  &lt;button
                    className=&quot;bg-gray-700 bg-opacity-50 text-white p-1 rounded hover:bg-opacity-75 flex items-center&quot;
                    onClick={() =&gt;
                      showCode({
                        codeContent: codeContent,
                        fileName: &#039;codeblock&#039;,
                        notes: &#039;&#039;,
                      })
                    }
                    title=&quot;Pop Out Code&quot;
                  &gt;
                    &lt;ExternalLinkIcon className=&quot;h-4 w-4&quot; /&gt;
                  &lt;/button&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            );
          } else {
            return (
              &lt;code className={className} {...props}&gt;
                {children}
              &lt;/code&gt;
            );
          }
        },
        h1: ({ node, ...props }) =&gt; &lt;h1 style={{ fontSize: &#039;2.5em&#039;, marginBottom: &#039;1em&#039;, marginTop: &#039;1.25em&#039; }} {...props} /&gt;,
        h2: ({ node, ...props }) =&gt; &lt;h2 style={{ fontSize: &#039;2em&#039;, marginBottom: &#039;0.5em&#039;, marginTop: &#039;0.75em&#039; }} {...props} /&gt;,
        h3: ({ node, ...props }) =&gt; &lt;h3 style={{ fontSize: &#039;1.75em&#039;, marginBottom: &#039;0.5em&#039;, marginTop: &#039;0.75em&#039; }} {...props} /&gt;,
        h4: ({ node, ...props }) =&gt; &lt;h4 style={{ fontSize: &#039;1.5em&#039;, marginBottom: &#039;0.5em&#039;, marginTop: &#039;0.75em&#039; }} {...props} /&gt;,
        h5: ({ node, ...props }) =&gt; &lt;h5 style={{ fontSize: &#039;1.25em&#039;, marginBottom: &#039;0.5em&#039; }} {...props} /&gt;,
        h6: ({ node, ...props }) =&gt; &lt;h6 style={{ fontSize: &#039;1.1em&#039;, marginBottom: &#039;0.5em&#039; }} {...props} /&gt;,
        hr: ({ node, ...props }) =&gt; &lt;hr style={{ marginTop: &#039;1.5em&#039;, marginBottom: &#039;1em&#039; }} {...props} /&gt;,
        li: ({ node, ...props }) =&gt; &lt;li className=&#039;p&#039; style={{ marginLeft: &#039;0.5em&#039; }} {...props} /&gt;,
      }}
    &gt;
      {text}
    &lt;/ReactMarkdown&gt;
  );
};

export default MarkdownRenderer;</code><code fileName="Message.js" notes="">// src/components/Message.js
import React, { useState, useContext } from &#039;react&#039;;
import MarkdownRenderer from &#039;./MarkdownRenderer&#039;;
import { CopyToClipboard } from &#039;react-copy-to-clipboard&#039;;
import { ClipboardCopyIcon } from &#039;@heroicons/react/solid&#039;;
import { CodeContext } from &#039;../context/CodeContext&#039;;
import CodeAttachmentBubble from &#039;./CodeAttachmentBubble&#039;;
import CodeModal from &#039;./CodeModal&#039;;
import { ChatContext } from &#039;../context/ChatContext&#039;;

function Message({ chatId, messageIndex, sender, text, model_id }) {
  const [copied, setCopied] = useState(false);
  const { showCode } = useContext(CodeContext);
  const [isEditModalOpen, setIsEditModalOpen] = useState(false);
  const [currentAttachment, setCurrentAttachment] = useState(null);
  const { updateMessage } = useContext(ChatContext); // Ensure ChatContext is imported
  const { models } = useContext(ChatContext);  // Access models from context

  const modelName = model_id 
    ? models.find((model) =&gt; model.id === model_id)?.name || &quot;Unknown Model&quot; 
    : null;

  const handleCopy = () =&gt; {
    setCopied(true);
    setTimeout(() =&gt; setCopied(false), 2000);
  };

  const safeText = String(text);

  // Function to extract code attachments
  const extractCodeAttachments = (text) =&gt; {
    const codeRegex = /&lt;code(?:\s+fileName=&quot;([^&quot;]*)&quot;)?(?:\s+notes=&quot;([^&quot;]*)&quot;)?&gt;([\s\S]*?)&lt;\/code&gt;/g;
    let match;
    const attachments = [];
    while ((match = codeRegex.exec(text)) !== null) {
      attachments.push({
        fileName: match[1] || &#039;Untitled&#039;,
        notes: match[2] || &#039;&#039;,
        codeContent: match[3],
      });
    }
    return attachments;
  };

  const codeAttachments = extractCodeAttachments(safeText);
  const cleanedText = safeText.replace(/&lt;code(?:\s+fileName=&quot;[^&quot;]*&quot;)?(?:\s+notes=&quot;[^&quot;]*&quot;)?&gt;[\s\S]*?&lt;\/code&gt;/g, &#039;&#039;);

  // Handlers for editing and deleting attachments
  const handleEdit = (attachment) =&gt; {
    setCurrentAttachment(attachment);
    setIsEditModalOpen(true);
  };

  const handleDelete = (attachment) =&gt; {
    // Implement deletion logic
    // For example, remove the attachment&#039;s &lt;code&gt; block from the message text
    const updatedText = text.replace(
      `&lt;code fileName=&quot;${attachment.fileName}&quot; notes=&quot;${attachment.notes}&quot;&gt;${attachment.codeContent}&lt;/code&gt;`,
      &#039;&#039;
    );
    updateMessage(chatId, messageIndex, { text: updatedText });
  };

  const handleUpdateAttachment = (updatedAttachment) =&gt; {
    // Replace the old &lt;code&gt; block with the updated one
    const oldCodeBlockRegex = new RegExp(
      `&lt;code(?:\\s+fileName=&quot;${updatedAttachment.fileName || &#039;Untitled&#039;}&quot;)?(?:\\s+notes=&quot;${updatedAttachment.notes || &#039;&#039;}&quot;)?&gt;[\\s\\S]*?&lt;\\/code&gt;`,
      &#039;g&#039;
    );
    const newCodeBlock = `&lt;code fileName=&quot;${updatedAttachment.fileName}&quot; notes=&quot;${updatedAttachment.notes}&quot;&gt;${updatedAttachment.codeContent}&lt;/code&gt;`;
    const updatedText = text.replace(oldCodeBlockRegex, newCodeBlock);
    updateMessage(chatId, messageIndex, { text: updatedText });
  };

  return (
    &lt;&gt;
      &lt;div className={`mb-4 flex ${sender === &#039;user&#039; ? &#039;justify-end&#039; : &#039;justify-start&#039;}`}&gt;
        &lt;div
          className={`max-w-xs md:max-w-md lg:max-w-5xl p-5 rounded-xl ${
            sender === &#039;user&#039;
              ? &#039;bg-zinc-950 text-white&#039;
              : &#039;bg-gray-200 dark:bg-[#282828] text-gray-800 dark:text-gray-200&#039;
          }`}
        &gt;
          {sender === &#039;bot&#039; &amp;&amp; modelName &amp;&amp; (
            &lt;div className=&quot;mb-2 text-sm text-gray-500 dark:text-gray-400&quot;&gt;
              &lt;strong&gt;Model:&lt;/strong&gt; {modelName}
            &lt;/div&gt;
          )}

          &lt;MarkdownRenderer text={cleanedText} /&gt;
          &lt;div className=&quot;mt-2 flex flex-wrap&quot;&gt;
            {codeAttachments.map((attachment, index) =&gt; (
              &lt;CodeAttachmentBubble
                key={index}
                attachment={attachment}
                onEdit={() =&gt; handleEdit(attachment)}
                onDelete={() =&gt; handleDelete(attachment)}
              /&gt;
            ))}
          &lt;/div&gt;
          &lt;CopyToClipboard text={text} onCopy={handleCopy}&gt;
            &lt;button className=&quot;mt-2 text-sm text-gray-500 hover:text-gray-700 flex items-center&quot;&gt;
              &lt;ClipboardCopyIcon className=&quot;h-5 w-5 mr-1&quot; /&gt; {copied ? &#039;Copied!&#039; : &#039;Copy&#039;}
            &lt;/button&gt;
          &lt;/CopyToClipboard&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      {isEditModalOpen &amp;&amp; currentAttachment &amp;&amp; (
        &lt;CodeModal
          onClose={() =&gt; setIsEditModalOpen(false)}
          onSave={handleUpdateAttachment}
          initialData={currentAttachment}
        /&gt;
      )}
    &lt;/&gt;
  );
}

export default React.memo(Message);</code>
Ai: It sounds like the issue with Python code attachments spilling out with escaped characters stems from the way the code content is being escaped and encoded. Specifically, you might be encountering **double escaping**, which can cause special characters in Python (like quotes, backslashes, etc.) to render incorrectly.

Let's walk through the problem and provide a step-by-step solution to ensure that Python code (and other languages with special characters) is handled correctly within your code bubbles.

---

## **Understanding the Problem**

1. **Escaping and Encoding Workflow:**
   - **Escaping (`escapeHtml`)**: Converts special HTML characters like `<`, `>`, `&`, `"`, and `'` to their corresponding HTML entities to prevent HTML injection and ensure that the content is safely embedded within HTML attributes.
   - **Encoding (`encodeURIComponent`)**: Encodes characters so that the string can be safely included in a URL or transmitted over the web without misinterpretation.

2. **Current Flow in Your Code:**
   - When attaching code, **both** `escapeHtml` and `encodeURIComponent` are applied to the `codeContent`.
   - This likely results in **double escaping**, especially problematic for Python code which contains various special characters.

3. **Impact on Python Code:**
   - Double escaping causes characters like `\n`, `'`, `"` in Python to be transformed into their entity equivalents multiple times, leading to them being displayed incorrectly in the final rendered message.

---

## **Proposed Solution**

1. **Remove Double Escaping:**
   - **Avoid using `escapeHtml` on `codeContent`** since `encodeURIComponent` sufficiently handles the encoding for embedding within data attributes.
   - This ensures that special characters are only encoded once, preventing them from being displayed incorrectly.

2. **Adjust the Code Attachment Process:**
   - **In `ChatInterface.js`**, modify the `addCodeAttachment` function to encode `codeContent` without escaping it with `escapeHtml`.
   - **Ensure** that `fileName` and `notes` are still safely escaped, as they are used within HTML attributes and need protection against injection.

3. **Ensure Proper Decoding During Rendering:**
   - When retrieving the `codeContent` from the `data-codecontent` attribute, use `decodeURIComponent` to revert it back to its original form.
   - This step ensures that the code is rendered correctly without any residual escaped characters.

---

## **Step-by-Step Implementation**

### 1. **Modify `ChatInterface.js`**

**Original `addCodeAttachment` Function:**

```javascript
const addCodeAttachment = ({ codeContent, fileName, notes }) => {
  const escapedCode = escapeHtml(codeContent);
  const escapedFileName = escapeHtml(fileName || "untitled");
  const escapedNotes = escapeHtml(notes || "");
  const codeTag = `<code fileName="${escapedFileName}" notes="${escapedNotes}">${escapedCode}</code>`;
  setMessage((prev) => prev + codeTag);
};
```

**Updated `addCodeAttachment` Function:**

```javascript
const addCodeAttachment = ({ codeContent, fileName, notes }) => {
  // Remove escapeHtml for codeContent to prevent double escaping
  const encodedCode = encodeURIComponent(codeContent);
  
  // Continue to escape fileName and notes for safe embedding in HTML attributes
  const escapedFileName = escapeHtml(fileName || "untitled");
  const escapedNotes = escapeHtml(notes || "");
  
  const codeTag = `<code fileName="${escapedFileName}" notes="${escapedNotes}">${encodedCode}</code>`;
  setMessage((prev) => prev + codeTag);
};
```

**Explanation:**
- **Removed `escapeHtml` from `codeContent`**: This avoids unnecessary escaping that can lead to double-encoded characters.
- **Retained `escapeHtml` for `fileName` and `notes`**: These fields are embedded within HTML attributes and need to be escaped to prevent injection attacks.

### 2. **Ensure Correct Decoding in `InlineCodeEditor.js`**

**Original Code Extraction:**

```javascript
const codeContent = codeContentEscaped ? decodeURIComponent(codeContentEscaped) : '';
```

**Ensure Consistent Decoding:**
- Since `codeContent` is now only encoded with `encodeURIComponent`, using `decodeURIComponent` remains appropriate and will correctly revert it back to its original form without residual escaped characters.

### 3. **Verify Handling in `MarkdownRenderer.js` and `Message.js`**

Ensure that during rendering:
- **`MarkdownRenderer`** correctly interprets the decoded `codeContent`.
- **`Message.js`** does not perform any additional escaping or encoding that could interfere with the already decoded content.

### 4. **Review and Test All Points of Code Attachment and Rendering**

- **Adding Code Attachments**: When attaching code (especially Python), ensure that it's inserted without additional escaping.
- **Rendering in Messages**: Verify that the rendered code appears correctly without unexpected characters.
- **Editing Code Attachments**: Ensure that editing and saving code attachments handle encoding and decoding consistently.

---

## **Final Adjusted Code Snippets**

### **`ChatInterface.js`**

```javascript
// ... [other imports and code]

const addCodeAttachment = ({ codeContent, fileName, notes }) => {
  // Encode codeContent to safely include it in a data attribute
  const encodedCode = encodeURIComponent(codeContent);

  // Escape fileName and notes for safe embedding in HTML attributes
  const escapedFileName = escapeHtml(fileName || "untitled");
  const escapedNotes = escapeHtml(notes || "");

  // Create the code tag with encoded codeContent
  const codeTag = `<code fileName="${escapedFileName}" notes="${escapedNotes}">${encodedCode}</code>`;
  setMessage((prev) => prev + codeTag);
};

// ... [rest of the code]
```

### **`InlineCodeEditor.js`**

```javascript
// ... [other imports and code]

const handleInput = useCallback(() => {
  if (editorRef.current) {
    const content = editorRef.current.innerHTML;
    const plainText = content.replace(/<span class="code-bubble"[^>]*>[^<]*<\/span>/g, (match) => {
      const fileName = match.match(/data-filename="([^"]*)"/)?.[1];
      const notes = match.match(/data-notes="([^"]*)"/)?.[1];
      const codeContentEscaped = match.match(/data-codecontent="([^"]*)"/)?.[1];
      // Decode the codeContent
      const codeContent = codeContentEscaped ? decodeURIComponent(codeContentEscaped) : '';
      return `<code fileName="${fileName}" notes="${notes}">${codeContent}</code>`;
    });
    onChange(plainText);
  }
}, [onChange]);

// ... [rest of the code]
```

### **`Message.js`**

Ensure that when extracting and rendering code attachments, `codeContent` is properly decoded.

```javascript
// ... [other imports and code]

const extractCodeAttachments = (text) => {
  const codeRegex = /<code(?:\s+fileName="([^"]*)")?(?:\s+notes="([^"]*)")?>([\s\S]*?)<\/code>/g;
  let match;
  const attachments = [];
  while ((match = codeRegex.exec(text)) !== null) {
    attachments.push({
      fileName: match[1] || 'Untitled',
      notes: match[2] || '',
      codeContent: decodeURIComponent(match[3]), // Decode codeContent here
    });
  }
  return attachments;
};

// ... [rest of the code]
```

---

## **Testing the Solution**

1. **Attach Python Code:**
   - Use the updated interface to attach a Python snippet containing special characters (e.g., quotes, backslashes, etc.).

2. **Verify Message Display:**
   - Ensure that the Python code appears correctly within the code bubbles without unexpected escaped characters.

3. **Edit and Save Attachments:**
   - Test editing the Python code within a code bubble to confirm that encoding and decoding remain consistent.

4. **Cross-Browser Testing:**
   - Test across different browsers to ensure that encoding and escaping work uniformly.

---

## **Additional Recommendations**

1. **Use JSON Encoding (Optional):**
   - For more complex data handling, consider using `JSON.stringify` and `JSON.parse` for encoding and decoding, especially if you have multiple nested attributes.

2. **Sanitize Inputs:**
   - Always sanitize user inputs to prevent XSS attacks, even though you're encoding data attributes.

3. **Consistency:**
   - Ensure that all parts of your application follow the same encoding and decoding logic to prevent discrepancies.

4. **Logging and Debugging:**
   - Implement comprehensive logging at each step of the encoding and decoding process to quickly identify any lingering issues.

5. **Documentation:**
   - Document the encoding and decoding workflows within your codebase to help future developers understand the data handling logic.

---

By adjusting the encoding strategy to prevent double escaping, especially for `codeContent`, you should resolve the issue with Python code spilling out with escaped characters. This approach ensures that the code is safely embedded within your application while retaining its original formatting and special characters.

---

If you continue to experience issues after implementing these changes, please provide specific examples of Python code that are not rendering correctly, and we can further diagnose the problem.
User: <div>Now adding code attachments doesn't work at all.</div>