Format: text/html
⬇ Download File (3.4 KB)
   1  <!DOCTYPE html>
   2  <html lang="en">
   3  <head>
   4    <meta charset="UTF-8" />
   5    <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"/>
   6    <title>NovaChat</title>
   7    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
   8  </head>
   9  <body>
  10    <div class="chat-app">
  11      <header class="chat-header">
  12        <h1>NovaChat</h1>
  13        <button id="clear-btn" title="Clear chat">🗑️</button>
  14      </header>
  15      <div id="chat-container">
  16        {% for msg in chat_history %}
  17          {% if msg.role != 'system' %}
  18            <div class="message {{ msg.role }}">
  19              <div class="bubble">{{ msg.content }}</div>
  20            </div>
  21          {% endif %}
  22        {% endfor %}
  23      </div>
  24      <form id="chat-form">
  25        <input
  26          type="text"
  27          id="message-input"
  28          name="message"
  29          placeholder="Type your message…"
  30          autocomplete="off"
  31        />
  32        <button type="submit">Send</button>
  33      </form>
  34    </div>
  35  
  36    <script>
  37      const form = document.getElementById('chat-form');
  38      const input = document.getElementById('message-input');
  39      const chatContainer = document.getElementById('chat-container');
  40      const clearBtn = document.getElementById('clear-btn');
  41  
  42      function renderChat(history) {
  43        chatContainer.innerHTML = '';
  44        history.forEach(msg => {
  45          if (msg.role === 'system') return;
  46          const wrapper = document.createElement('div');
  47          wrapper.className = `message ${msg.role}`;
  48          const bubble = document.createElement('div');
  49          bubble.className = 'bubble';
  50          bubble.textContent = msg.content;
  51          wrapper.appendChild(bubble);
  52          chatContainer.appendChild(wrapper);
  53        });
  54        chatContainer.scrollTop = chatContainer.scrollHeight;
  55      }
  56  
  57      function showTyping() {
  58        const typingWrapper = document.createElement('div');
  59        typingWrapper.className = 'message assistant typing';
  60        const bubble = document.createElement('div');
  61        bubble.className = 'bubble';
  62        bubble.textContent = 'Nova is typing';
  63        typingWrapper.appendChild(bubble);
  64        chatContainer.appendChild(typingWrapper);
  65        chatContainer.scrollTop = chatContainer.scrollHeight;
  66        return typingWrapper;
  67      }
  68  
  69      form.addEventListener('submit', async e => {
  70        e.preventDefault();
  71        const text = input.value.trim();
  72        if (!text) return;
  73        const formData = new FormData();
  74        formData.append('message', text);
  75  
  76        renderChat([...JSON.parse(localStorage.getItem('chat') || '[]'), {role:'user',content:text}]);
  77        input.value = '';
  78  
  79        const typingElem = showTyping();
  80  
  81        const res = await fetch('/chat', { method: 'POST', body: formData });
  82        const data = await res.json();
  83        if (data.chat_history) {
  84          typingElem.remove();
  85          renderChat(data.chat_history);
  86          localStorage.setItem('chat', JSON.stringify(data.chat_history.filter(m=>m.role!=='system')));
  87        }
  88      });
  89  
  90      clearBtn.addEventListener('click', async () => {
  91        const res = await fetch('/clear', { method: 'POST' });
  92        const data = await res.json();
  93        if (data.chat_history) {
  94          renderChat(data.chat_history);
  95          localStorage.removeItem('chat');
  96        }
  97      });
  98  
  99      window.addEventListener('load', () => {
 100        const initial = {{ chat_history|tojson }}.filter(m=>m.role!=='system');
 101        localStorage.setItem('chat', JSON.stringify(initial));
 102        renderChat(initial);
 103      });
 104    </script>
 105  </body>
 106  </html>