Format: text/html
⬇ Download File (14.3 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" />
   6    <title>NovaChat</title>
   7    <!-- Bootstrap CSS -->
   8    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" />
   9    <!-- Animate.css -->
  10    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" />
  11    <!-- Highlight.js CSS -->
  12    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.2.0/styles/atom-one-dark.min.css" />
  13  
  14    <style>
  15      :root {
  16        --bg-color: #1a1a1a;
  17        --sidebar-bg: #2d2d2d;
  18        --sidebar-border: #444;
  19        --text-color: #7ee787;
  20        --text-muted: #9e9e9e;
  21        --content-bg: #1a1a1a;
  22        --topbar-bg: #212121;
  23        --link-hover: #fff;
  24        --assistant-msg-bg: #262b26;
  25        --assistant-msg-color: #9ef1a0;
  26        --user-msg-bg: #15354f;
  27        --user-msg-color: #a0f1f1;
  28        --btn-newchat-bg: #0d6efd;
  29      }
  30      body.light-mode { /* light overrides omitted for brevity */ }
  31  
  32      html, body {
  33        margin: 0; padding: 0; height: 100%; font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
  34        background-color: var(--bg-color); color: var(--text-color);
  35        transition: background-color 0.3s, color 0.3s;
  36      }
  37  
  38      /* Sidebar */
  39      #sidebar {
  40        position: fixed; left: 0; top: 0;
  41        width: 280px; height: 100%; background-color: var(--sidebar-bg);
  42        border-right: 1px solid var(--sidebar-border); padding: 15px;
  43        display: flex; flex-direction: column;
  44        transition: transform 0.3s ease;
  45        transform: translateX(0);
  46        z-index: 1000;
  47      }
  48      #sidebar.collapsed { transform: translateX(-100%); }
  49  
  50      /* Main Content */
  51      #mainContent {
  52        margin-left: 280px; height: 100vh; display: flex; flex-direction: column;
  53        transition: margin-left 0.3s ease;
  54      }
  55      #mainContent.expanded { margin-left: 0; }
  56  
  57      .topbar {
  58        background-color: var(--topbar-bg); padding: 10px;
  59        border-bottom: 1px solid var(--sidebar-border);
  60        display: flex; align-items: center; justify-content: space-between;
  61      }
  62  
  63      .toggle-btn {
  64        background: none; border: none; color: var(--text-color);
  65        font-size: 1.5rem; cursor: pointer;
  66      }
  67  
  68      /* Chat & Input */
  69      .chat-container { flex: 1; padding: 10px; overflow-y: auto; background-color: var(--content-bg); }
  70      .message-area { width: 100%; max-width: 800px; margin: 0 auto; }
  71      .input-area { background-color: var(--topbar-bg); padding: 8px; border-top: 1px solid var(--sidebar-border); display: flex; flex-wrap: wrap; }
  72      #message { flex: 1 1 100%; margin-bottom: 8px; }
  73      .input-area button { margin-left: 0.5rem; margin-bottom: 8px; }
  74  
  75      /* Media Queries */
  76      @media (max-width: 767px) {
  77        #sidebar { width: 100%; transform: translateX(-100%); height: auto; max-height: 80vh; }
  78        #sidebar.show { transform: translateX(0); }
  79        #mainContent, #mainContent.expanded { margin-left: 0; }
  80        .toggle-btn { display: block; }
  81        .message-area { max-width: 100%; padding: 0 5px; }
  82        .chat-container { padding: 5px; }
  83        .input-area { flex-direction: column; }
  84        .input-area button { margin-left: 0; }
  85      }
  86    </style>
  87  </head>
  88  <body>
  89    <!-- Sidebar -->
  90    <div id="sidebar">
  91      <div class="sidebar-header">
  92        <h4>NovaChat</h4>
  93      </div>
  94      <button class="btn-newchat mb-2" onclick="newChat()">+ New Chat</button>
  95      <ul class="sidebar-menu list-unstyled flex-grow-1">
  96        <li><a onclick="showChatHistory()">Chat History</a></li>
  97      </ul>
  98      <div class="sidebar-footer mt-auto pt-2 border-top">
  99        <ul class="list-unstyled">
 100          <li><a href="#">Settings</a></li>
 101          <li><a href="#">About</a></li>
 102        </ul>
 103      </div>
 104    </div>
 105  
 106    <div id="mainContent">
 107      <div class="topbar">
 108        <button class="toggle-btn" id="toggleSidebarBtn">☰</button>
 109        <h5 class="mb-0">NovaChat</h5>
 110        <button id="themeToggle" class="theme-toggle-btn">☾ Dark Mode</button>
 111      </div>
 112  
 113      <div class="chat-container">
 114        <div id="emptyState" class="text-center text-muted" style="display:none;">
 115          <h1>What's your next big idea?</h1>
 116        </div>
 117        <div class="message-area" id="chat"> <!-- messages --> </div>
 118      </div>
 119  
 120      <div class="input-area">
 121        <textarea id="message" class="form-control" rows="3" placeholder="Type a message..."></textarea>
 122        <div class="d-flex">
 123          <button class="btn btn-mic mr-1" id="micButton">🎤</button>
 124          <button class="btn btn-send mr-1" onclick="sendMessage()">Send</button>
 125          <button class="btn btn-clear" onclick="clearChat()">Clear</button>
 126        </div>
 127      </div>
 128    </div>
 129  
 130    <!-- Modal for Voice Recording -->
 131    <div class="modal fade" id="recordingModal" tabindex="-1" role="dialog" aria-labelledby="recordingModalLabel" aria-hidden="true">
 132      <div class="modal-dialog modal-dialog-centered" role="document">
 133        <div class="modal-content">
 134          <div class="modal-header">
 135            <h5 class="modal-title" id="recordingModalLabel">Recording...</h5>
 136            <button type="button" class="close" data-dismiss="modal" aria-label="Close" onclick="stopRecording()">
 137              <span aria-hidden="true">&times;</span>
 138            </button>
 139          </div>
 140          <div class="modal-body text-center">
 141            <p id="modalTranscript">Please speak now.</p>
 142            <div class="spinner-border text-light" role="status">
 143              <span class="sr-only">Recording...</span>
 144            </div>
 145          </div>
 146          <div class="modal-footer">
 147            <button type="button" class="btn btn-primary" onclick="finishRecording()">Send</button>
 148          </div>
 149        </div>
 150      </div>
 151    </div>
 152  
 153    <!-- Modal for Chat History -->
 154    <div class="modal fade" id="chatHistoryModal" tabindex="-1" role="dialog" aria-labelledby="chatHistoryModalLabel" aria-hidden="true">
 155      <div class="modal-dialog modal-dialog-centered modal-lg" role="document">
 156        <div class="modal-content">
 157          <div class="modal-header">
 158            <h5 class="modal-title" id="chatHistoryModalLabel">Chat History</h5>
 159            <button type="button" class="close" data-dismiss="modal" aria-label="Close" onclick="closeChatHistory()">
 160              <span aria-hidden="true">&times;</span>
 161            </button>
 162          </div>
 163          <div class="modal-body">
 164            <div id="chatHistoryContent"></div>
 165          </div>
 166          <div class="modal-footer">
 167            <button type="button" class="btn btn-secondary" data-dismiss="modal" onclick="closeChatHistory()">Close</button>
 168          </div>
 169        </div>
 170      </div>
 171    </div>
 172  
 173    <!-- jQuery & Bootstrap JS -->
 174    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
 175    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
 176    <!-- Marked.js for Markdown parsing -->
 177    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
 178    <!-- Highlight.js for code syntax highlighting -->
 179    <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.2.0/highlight.min.js"></script>
 180  
 181    <script>
 182      // Global chat history from server
 183      var chatHistory = {{ chat_history|tojson }};
 184  
 185      // Initialize theme from localStorage
 186      function initTheme() {
 187        const savedTheme = localStorage.getItem("theme");
 188        if (savedTheme === "light") {
 189          document.body.classList.add("light-mode");
 190          document.getElementById("themeToggleIcon").textContent = "☀";
 191          document.getElementById("themeToggleText").textContent = "Light Mode";
 192        } else {
 193          document.body.classList.remove("light-mode");
 194          document.getElementById("themeToggleIcon").textContent = "☾";
 195          document.getElementById("themeToggleText").textContent = "Dark Mode";
 196        }
 197      }
 198      document.addEventListener("DOMContentLoaded", function() {
 199        initTheme();
 200        checkIfEmpty();
 201      });
 202  
 203      function checkIfEmpty() {
 204        const hasNonSystem = chatHistory.some(msg => msg.role !== "system");
 205        document.getElementById("emptyState").style.display = hasNonSystem ? "none" : "block";
 206      }
 207  
 208      // Update chat window
 209      function updateChat(history) {
 210        chatHistory = history;
 211        const chatContainer = document.getElementById("chat");
 212        chatContainer.innerHTML = "";
 213  
 214        let hasNonSystemMsg = false;
 215        chatHistory.forEach(function(msg) {
 216          if (msg.role === "system") return;  // Hide system prompts
 217          hasNonSystemMsg = true;
 218          const rendered = marked.parse(msg.content);
 219          let msgClass = "", label = "";
 220          if (msg.role === "assistant") {
 221            msgClass = "assistant-msg animate__animated animate__fadeIn";
 222            label = "Assistant";
 223          } else if (msg.role === "user") {
 224            msgClass = "user-msg animate__animated animate__fadeIn";
 225            label = "User";
 226          }
 227          const wrapper = document.createElement("div");
 228          wrapper.className = "message " + msgClass;
 229          wrapper.innerHTML = "<strong>" + label + ":</strong><div class='msg-content'>" + rendered + "</div>";
 230          chatContainer.appendChild(wrapper);
 231        });
 232        document.getElementById("emptyState").style.display = hasNonSystemMsg ? "none" : "block";
 233        chatContainer.scrollTop = chatContainer.scrollHeight;
 234      }
 235  
 236      // Sidebar toggle using topbar button
 237      document.getElementById("toggleSidebarBtn").addEventListener("click", function() {
 238        const sidebar = document.getElementById("sidebar");
 239        const mainContent = document.getElementById("mainContent");
 240        if (sidebar.classList.contains("collapsed")) {
 241          sidebar.classList.remove("collapsed");
 242          mainContent.classList.remove("expanded");
 243        } else {
 244          sidebar.classList.add("collapsed");
 245          mainContent.classList.add("expanded");
 246        }
 247      });
 248  
 249      // New Chat: clear conversation
 250      function newChat() {
 251        console.log("Clearing chat (new chat)...");
 252        $.post("/clear", {}, function(data) {
 253          updateChat(data.chat_history);
 254        });
 255      }
 256  
 257      // Show Chat History modal
 258      function showChatHistory() {
 259        let historyContent = "";
 260        chatHistory.forEach(function(msg) {
 261          if (msg.role === "system") return;
 262          historyContent += "<p><strong>" + (msg.role === "assistant" ? "Assistant" : "User") + ":</strong> " + msg.content + "</p>";
 263        });
 264        document.getElementById("chatHistoryContent").innerHTML = historyContent;
 265        $('#chatHistoryModal').modal('show');
 266      }
 267      function closeChatHistory() {
 268        $('#chatHistoryModal').modal('hide');
 269      }
 270  
 271      // Clear the chat (same /clear endpoint as newChat)
 272      function clearChat() {
 273        console.log("Clear button clicked...");
 274        $.post("/clear", {}, function(data) {
 275          updateChat(data.chat_history);
 276        });
 277      }
 278  
 279      // Theme toggle
 280      const themeToggleBtn = document.getElementById("themeToggle");
 281      themeToggleBtn.addEventListener("click", function() {
 282        if (document.body.classList.contains("light-mode")) {
 283          document.body.classList.remove("light-mode");
 284          document.getElementById("themeToggleIcon").textContent = "☾";
 285          document.getElementById("themeToggleText").textContent = "Dark Mode";
 286          localStorage.setItem("theme", "dark");
 287        } else {
 288          document.body.classList.add("light-mode");
 289          document.getElementById("themeToggleIcon").textContent = "☀";
 290          document.getElementById("themeToggleText").textContent = "Light Mode";
 291          localStorage.setItem("theme", "light");
 292        }
 293      });
 294  
 295      // Voice recognition with live transcription
 296      var recordedTranscript = "";
 297      var recognition;
 298      var SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
 299      if (SpeechRecognition) {
 300        recognition = new SpeechRecognition();
 301        recognition.interimResults = true;
 302        recognition.maxAlternatives = 1;
 303        recognition.onresult = function(event) {
 304          let transcript = "";
 305          for (let i = event.resultIndex; i < event.results.length; i++) {
 306            transcript += event.results[i][0].transcript;
 307          }
 308          recordedTranscript = transcript;
 309          document.getElementById("modalTranscript").textContent = transcript;
 310        };
 311        recognition.onerror = function(event) {
 312          console.error("Speech recognition error", event.error);
 313        };
 314      } else {
 315        document.getElementById("micButton").disabled = true;
 316        document.getElementById("micButton").title = "Speech recognition not supported in this browser.";
 317      }
 318  
 319      // Recording Modal
 320      document.getElementById("micButton").addEventListener("click", function() {
 321        showRecordingModal();
 322      });
 323      function showRecordingModal() {
 324        recordedTranscript = "";
 325        document.getElementById("modalTranscript").textContent = "Please speak now.";
 326        $('#recordingModal').modal('show');
 327        if (recognition) {
 328          recognition.start();
 329        }
 330      }
 331      function stopRecording() {
 332        if (recognition) {
 333          recognition.stop();
 334        }
 335        $('#recordingModal').modal('hide');
 336      }
 337      function finishRecording() {
 338        if (recognition) {
 339          recognition.stop();
 340        }
 341        document.getElementById("modalTranscript").textContent = "Converting to text...";
 342        setTimeout(function() {
 343          document.getElementById("message").value = recordedTranscript;
 344          $('#recordingModal').modal('hide');
 345          sendMessage();
 346        }, 2000);
 347      }
 348  
 349      // Send message
 350      function sendMessage() {
 351        const inputEl = document.getElementById("message");
 352        const text = inputEl.value.trim();
 353        if (!text) return;
 354        
 355        chatHistory.push({role: "user", content: text});
 356        updateChat(chatHistory);
 357        
 358        const chatContainer = document.getElementById("chat");
 359        const typingIndicator = document.createElement("div");
 360        typingIndicator.className = "message assistant-msg animate__animated animate__pulse typing-indicator";
 361        typingIndicator.innerHTML = "<strong>Assistant:</strong><div class='msg-content'><em>Agent is typing...</em></div>";
 362        chatContainer.appendChild(typingIndicator);
 363        chatContainer.scrollTop = chatContainer.scrollHeight;
 364        
 365        inputEl.value = "";
 366  
 367        // Send to server
 368        $.post("/chat", { message: text }, function(data) {
 369          updateChat(data.chat_history);
 370        });
 371      }
 372  
 373      // Shift+Enter for newline, Enter to send
 374      document.getElementById("message").addEventListener("keydown", function(e) {
 375        if (e.key === "Enter" && !e.shiftKey) {
 376          e.preventDefault();
 377          sendMessage();
 378        }
 379      });
 380    </script>
 381  </body>
 382  </html>