User: Code:&nbsp;<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({
          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-grow-0 items-end pl-4&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="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;min-h-[100px] max-h-[300px] overflow-y-auto p-2 focus:outline-none&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>