Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>Agent Chat</title> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.5.4/socket.io.js"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| --primary: #10a37f; | |
| --primary-dark: #0d8a6a; | |
| --bg-color: #343541; | |
| --chat-bg: #444654; | |
| --user-bg: #343541; | |
| --text-color: #ececf1; | |
| --text-secondary: #acacbe; | |
| --border-color: #565869; | |
| --log-bg: #2a2b32; | |
| --error-color: #ef4146; | |
| --warning-color: #f0b72f; | |
| --panel-width: 300px; /* Define panel width */ | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| body { | |
| background-color: var(--bg-color); | |
| color: var(--text-color); | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| display: flex; | |
| flex-direction: column; | |
| height: 100vh; | |
| overflow: hidden; | |
| } | |
| nav { | |
| background-color: var(--user-bg); | |
| padding: 12px 20px; | |
| border-bottom: 1px solid var(--border-color); | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| flex-shrink: 0; /* Prevent nav from shrinking */ | |
| } | |
| .nav-title { | |
| font-size: 1.2rem; | |
| font-weight: 600; | |
| } | |
| .nav-actions { | |
| display: flex; | |
| gap: 10px; | |
| } | |
| .btn { | |
| background-color: var(--primary); | |
| color: white; | |
| border: none; | |
| border-radius: 4px; | |
| padding: 8px 12px; | |
| font-size: 0.9rem; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| transition: background-color 0.2s; | |
| } | |
| .btn:hover { | |
| background-color: var(--primary-dark); | |
| } | |
| .btn-sm { | |
| padding: 6px 10px; | |
| font-size: 0.8rem; | |
| } | |
| .btn-icon { | |
| padding: 8px; | |
| border-radius: 4px; | |
| background: transparent; | |
| color: var(--text-color); | |
| border: none; /* Ensure no default button border */ | |
| cursor: pointer; | |
| } | |
| .btn-icon:hover { | |
| background-color: rgba(255,255,255,0.1); | |
| } | |
| main { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| } | |
| .chat-container { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 20px 0; | |
| } | |
| .message { | |
| padding: 20px; | |
| display: flex; | |
| max-width: 900px; | |
| margin: 0 auto; | |
| gap: 20px; | |
| } | |
| .message-user { | |
| background-color: var(--user-bg); | |
| } | |
| .message-agent { | |
| background-color: var(--chat-bg); | |
| } | |
| .avatar { | |
| width: 36px; | |
| height: 36px; | |
| border-radius: 4px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| flex-shrink: 0; | |
| } | |
| .avatar-user { | |
| background-color: var(--primary); | |
| } | |
| .avatar-agent { | |
| background-color: #5436da; | |
| } | |
| .message-content { | |
| flex: 1; | |
| line-height: 1.5; | |
| padding-top: 4px; | |
| } | |
| .input-container { | |
| padding: 20px; | |
| border-top: 1px solid var(--border-color); | |
| background-color: var(--user-bg); | |
| position: relative; | |
| flex-shrink: 0; /* Prevent input area from shrinking */ | |
| } | |
| .input-wrapper { | |
| max-width: 900px; | |
| margin: 0 auto; | |
| position: relative; | |
| } | |
| .input-actions { | |
| position: absolute; | |
| right: 12px; | |
| bottom: 12px; | |
| display: flex; | |
| gap: 8px; | |
| z-index: 2; | |
| } | |
| textarea { | |
| width: 100%; | |
| min-height: 60px; | |
| max-height: 200px; | |
| padding: 12px 50px 12px 16px; | |
| border-radius: 8px; | |
| border: 1px solid var(--border-color); | |
| background-color: var(--chat-bg); | |
| color: var(--text-color); | |
| resize: none; | |
| font-size: 1rem; | |
| line-height: 1.5; | |
| } | |
| textarea:focus { | |
| outline: none; | |
| border-color: var(--primary); | |
| box-shadow: 0 0 0 1px var(--primary); | |
| } | |
| .typing-indicator { | |
| display: flex; | |
| gap: 4px; | |
| padding: 0 20px 10px; | |
| color: var(--text-secondary); | |
| font-size: 0.9rem; | |
| flex-shrink: 0; /* Prevent indicator from shrinking */ | |
| } | |
| .typing-dots { | |
| display: flex; | |
| gap: 2px; | |
| align-items: flex-end; | |
| } | |
| .typing-dot { | |
| width: 6px; | |
| height: 6px; | |
| background-color: var(--text-secondary); | |
| border-radius: 50%; | |
| animation: typingAnimation 1.4s infinite ease-in-out; | |
| } | |
| .typing-dot:nth-child(1) { | |
| animation-delay: 0s; | |
| } | |
| .typing-dot:nth-child(2) { | |
| animation-delay: 0.2s; | |
| } | |
| .typing-dot:nth-child(3) { | |
| animation-delay: 0.4s; | |
| } | |
| @keyframes typingAnimation { | |
| 0%, 60%, 100% { transform: translateY(0); } | |
| 30% { transform: translateY(-4px); } | |
| } | |
| footer { | |
| background-color: var(--user-bg); | |
| padding: 12px 20px; | |
| border-top: 1px solid var(--border-color); | |
| font-size: 0.8rem; | |
| color: var(--text-secondary); | |
| text-align: center; | |
| flex-shrink: 0; /* Prevent footer from shrinking */ | |
| } | |
| /* Side Panel Styles */ | |
| .side-panel { | |
| position: fixed; | |
| top: 0; | |
| right: calc(-1 * var(--panel-width)); /* Initially hidden */ | |
| width: var(--panel-width); | |
| height: 100vh; | |
| background-color: var(--log-bg); | |
| border-left: 1px solid var(--border-color); | |
| box-shadow: -4px 0 12px rgba(0,0,0,0.15); | |
| transition: right 0.3s ease-in-out; | |
| display: flex; | |
| flex-direction: column; | |
| z-index: 999; /* Ensure it's above other content */ | |
| } | |
| .side-panel.open { | |
| right: 0; /* Slide in */ | |
| } | |
| .side-panel-header { | |
| padding: 12px 20px; | |
| background-color: var(--user-bg); | |
| border-bottom: 1px solid var(--border-color); | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| flex-shrink: 0; | |
| } | |
| .side-panel-header h3 { | |
| font-size: 1rem; | |
| margin: 0; | |
| } | |
| .side-panel-content { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 12px 20px; | |
| font-size: 0.85rem; | |
| color: var(--text-secondary); | |
| } | |
| .log-entry { | |
| margin-bottom: 6px; | |
| display: flex; | |
| gap: 10px; | |
| } | |
| .log-timestamp { | |
| color: var(--text-secondary); | |
| flex-shrink: 0; | |
| } | |
| .log-message { | |
| flex: 1; | |
| word-break: break-word; /* Prevent long words from overflowing */ | |
| } | |
| .log-error { | |
| color: var(--error-color); | |
| } | |
| .log-warning { | |
| color: var(--warning-color); | |
| } | |
| .alert { | |
| position: fixed; | |
| top: 20px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| padding: 12px 20px; | |
| border-radius: 8px; | |
| background-color: var(--error-color); | |
| color: white; | |
| z-index: 1000; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.15); | |
| animation: slideIn 0.3s ease-out; | |
| } | |
| .alert-success { | |
| background-color: var(--primary); | |
| } | |
| .alert-warning { | |
| background-color: var(--warning-color); | |
| } | |
| @keyframes slideIn { | |
| from { top: -50px; opacity: 0; } | |
| to { top: 20px; opacity: 1; } | |
| } | |
| .markdown-content pre { | |
| background-color: rgba(0,0,0,0.2); | |
| padding: 12px; | |
| border-radius: 6px; | |
| overflow-x: auto; | |
| margin: 12px 0; | |
| } | |
| .markdown-content code { | |
| font-family: 'Courier New', Courier, monospace; | |
| font-size: 0.9rem; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <nav> | |
| <div class="nav-title">Agent Chat</div> | |
| <div class="nav-actions"> | |
| <button class="btn"> | |
| <i class="fas fa-plus"></i> New Chat | |
| </button> | |
| <!-- Button to open logs panel --> | |
| <button class="btn-icon" id="open-logs-btn" title="Show Logs"> | |
| <i class="fas fa-list-alt"></i> | |
| </button> | |
| </div> | |
| </nav> | |
| <main> | |
| <div class="chat-container" id="chat"> | |
| <!-- Messages will be inserted here --> | |
| </div> | |
| <div class="typing-indicator" id="typing-indicator" style="display: none;"> | |
| <div class="typing-dots"> | |
| <div class="typing-dot"></div> | |
| <div class="typing-dot"></div> | |
| <div class="typing-dot"></div> | |
| </div> | |
| <span>Agent is typing...</span> | |
| </div> | |
| <div class="input-container"> | |
| <div class="input-wrapper"> | |
| <textarea id="prompt" rows="1" placeholder="Message Agent..." autofocus></textarea> | |
| <div class="input-actions"> | |
| <button class="btn-icon" id="upload-btn" title="Upload Database"> | |
| <i class="fas fa-database"></i> | |
| </button> | |
| <button class="btn-icon" id="send-btn" title="Send message"> | |
| <i class="fas fa-paper-plane"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <footer> | |
| <div>Agent Chat v1.0 · © 2023</div> | |
| </footer> | |
| <!-- Side Panel for Logs --> | |
| <div id="side-panel" class="side-panel"> | |
| <div class="side-panel-header"> | |
| <h3>Logs</h3> | |
| <button class="btn-icon" id="close-panel-btn" title="Hide Logs"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="side-panel-content" id="logs"> | |
| <!-- Logs will be inserted here --> | |
| </div> | |
| </div> | |
| <div id="flash-message" class="alert" style="display: none;"></div> | |
| <script> | |
| const socket = io(); | |
| const chatContainer = document.getElementById("chat"); | |
| const logsContainer = document.getElementById("logs"); // This is now the content div inside the panel | |
| const sendButton = document.getElementById("send-btn"); | |
| const uploadButton = document.getElementById("upload-btn"); | |
| const promptTextarea = document.getElementById("prompt"); | |
| const flashMessage = document.getElementById('flash-message'); | |
| const typingIndicator = document.getElementById('typing-indicator'); | |
| // New elements for side panel | |
| const sidePanel = document.getElementById('side-panel'); | |
| const openLogsBtn = document.getElementById('open-logs-btn'); | |
| const closePanelBtn = document.getElementById('close-panel-btn'); | |
| // Auto-resize textarea | |
| promptTextarea.addEventListener('input', function() { | |
| this.style.height = 'auto'; | |
| this.style.height = (this.scrollHeight) + 'px'; | |
| }); | |
| // Handle Enter key (Shift+Enter for new line) | |
| promptTextarea.addEventListener('keydown', function(e) { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault(); | |
| sendMessage(); | |
| } | |
| }); | |
| // Send button click handler | |
| sendButton.addEventListener('click', sendMessage); | |
| // Upload button click handler | |
| uploadButton.addEventListener('click', () => { | |
| window.location.href = '/upload'; | |
| }); | |
| // Side panel toggle handlers | |
| openLogsBtn.addEventListener('click', () => { | |
| sidePanel.classList.add('open'); | |
| }); | |
| closePanelBtn.addEventListener('click', () => { | |
| sidePanel.classList.remove('open'); | |
| }); | |
| function sendMessage() { | |
| const prompt = promptTextarea.value.trim(); | |
| if (!prompt) return; | |
| addMessage("user", prompt); | |
| promptTextarea.value = ''; | |
| promptTextarea.style.height = 'auto'; | |
| typingIndicator.style.display = 'flex'; | |
| fetch("/generate", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ prompt: prompt }) | |
| }); | |
| } | |
| function addMessage(sender, text) { | |
| const messageDiv = document.createElement("div"); | |
| messageDiv.classList.add("message", `message-${sender}`); | |
| const avatarDiv = document.createElement("div"); | |
| avatarDiv.classList.add("avatar", `avatar-${sender}`); | |
| avatarDiv.innerHTML = sender === 'user' ? | |
| '<i class="fas fa-user"></i>' : | |
| '<i class="fas fa-robot"></i>'; | |
| const contentDiv = document.createElement("div"); | |
| contentDiv.classList.add("message-content"); | |
| contentDiv.innerHTML = `<div class="markdown-content">${text}</div>`; | |
| messageDiv.appendChild(avatarDiv); | |
| messageDiv.appendChild(contentDiv); | |
| chatContainer.appendChild(messageDiv); | |
| chatContainer.scrollTop = chatContainer.scrollHeight; | |
| } | |
| function addLogMessage(text, type = 'info') { | |
| const now = new Date(); | |
| const timestamp = now.toLocaleTimeString(); | |
| const logDiv = document.createElement("div"); | |
| logDiv.classList.add("log-entry"); | |
| const timeDiv = document.createElement("div"); | |
| timeDiv.classList.add("log-timestamp"); | |
| timeDiv.textContent = timestamp; | |
| const messageDiv = document.createElement("div"); | |
| messageDiv.classList.add("log-message"); | |
| if (type !== 'info') messageDiv.classList.add(`log-${type}`); | |
| messageDiv.textContent = text; | |
| logDiv.appendChild(timeDiv); | |
| logDiv.appendChild(messageDiv); | |
| logsContainer.appendChild(logDiv); // Append to the logs content div | |
| logsContainer.scrollTop = logsContainer.scrollHeight; // Scroll logs panel | |
| } | |
| function showFlashMessage(message, type = 'error') { | |
| flashMessage.textContent = message; | |
| flashMessage.className = `alert alert-${type}`; | |
| flashMessage.style.display = 'flex'; | |
| setTimeout(() => { | |
| flashMessage.style.display = 'none'; | |
| }, 3000); | |
| } | |
| // Socket.io handlers | |
| let agentMessageDiv = null; | |
| socket.on("final_stream", (data) => { | |
| if (!agentMessageDiv) { | |
| agentMessageDiv = document.createElement("div"); | |
| agentMessageDiv.classList.add("message", "message-agent"); | |
| const avatarDiv = document.createElement("div"); | |
| avatarDiv.classList.add("avatar", "avatar-agent"); | |
| avatarDiv.innerHTML = '<i class="fas fa-robot"></i>'; | |
| const contentDiv = document.createElement("div"); | |
| contentDiv.classList.add("message-content"); | |
| contentDiv.id = "agent-message-content"; | |
| agentMessageDiv.appendChild(avatarDiv); | |
| agentMessageDiv.appendChild(contentDiv); | |
| chatContainer.appendChild(agentMessageDiv); | |
| } | |
| const contentDiv = document.getElementById("agent-message-content"); | |
| contentDiv.innerHTML = `<div class="markdown-content">${data.message}</div>`; | |
| chatContainer.scrollTop = chatContainer.scrollHeight; | |
| }); | |
| socket.on("final", (data) => { | |
| typingIndicator.style.display = 'none'; | |
| if (agentMessageDiv) { | |
| const contentDiv = document.getElementById("agent-message-content"); | |
| contentDiv.innerHTML = `<div class="markdown-content">${data.message}</div>`; | |
| agentMessageDiv = null; | |
| } else { | |
| addMessage("agent", data.message); | |
| } | |
| }); | |
| socket.on("log", (data) => { | |
| addLogMessage(data.message, data.type || 'info'); | |
| }); | |
| socket.on("error", (data) => { | |
| showFlashMessage(data.message, 'error'); | |
| typingIndicator.style.display = 'none'; | |
| }); | |
| // Handle flash messages from server | |
| if (flashMessage) { | |
| setTimeout(() => { | |
| flashMessage.style.display = 'none'; | |
| }, 3000); | |
| } | |
| </script> | |
| </body> | |
| </html> | |