Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Agent Chat</title> | |
| <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --bg-primary: #0a0a0f; | |
| --bg-secondary: #12121a; | |
| --bg-tertiary: #1a1a25; | |
| --accent: #00d4aa; | |
| --accent-dim: #00a080; | |
| --text-primary: #e8e8e8; | |
| --text-secondary: #888; | |
| --border: #2a2a3a; | |
| --user-msg: #1e3a5f; | |
| --agent-msg: #1a2a1a; | |
| --tool-tag: #2d1f4e; | |
| --error: #ff4757; | |
| --success: #00d4aa; | |
| } | |
| html { | |
| height: 100%; | |
| width: 100%; | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| -webkit-box-sizing: border-box; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; | |
| background-color: #0a0a0f; | |
| background: var(--bg-primary); | |
| color: #e8e8e8; | |
| color: var(--text-primary); | |
| min-height: 100vh; | |
| height: 100vh; | |
| display: -webkit-box; | |
| display: -webkit-flex; | |
| display: flex; | |
| overflow: hidden; | |
| } | |
| /* Sidebar */ | |
| .sidebar { | |
| width: 280px; | |
| min-width: 280px; | |
| background: var(--bg-secondary); | |
| border-right: 1px solid var(--border); | |
| display: -webkit-box; | |
| display: -webkit-flex; | |
| display: flex; | |
| -webkit-flex-direction: column; | |
| flex-direction: column; | |
| padding: 20px; | |
| overflow-y: auto; | |
| } | |
| .logo { | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 1.4rem; | |
| font-weight: 600; | |
| color: var(--accent); | |
| margin-bottom: 30px; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .logo::before { | |
| content: '>'; | |
| animation: blink 1s infinite; | |
| } | |
| @keyframes blink { | |
| 50% { opacity: 0; } | |
| } | |
| .section-title { | |
| font-size: 0.75rem; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| color: var(--text-secondary); | |
| margin-bottom: 12px; | |
| } | |
| /* Session Toggle */ | |
| .session-control { | |
| background: var(--bg-tertiary); | |
| border-radius: 12px; | |
| padding: 16px; | |
| margin-bottom: 24px; | |
| } | |
| .toggle-container { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| margin-top: 10px; | |
| } | |
| .toggle-label { | |
| font-size: 0.9rem; | |
| color: var(--text-primary); | |
| } | |
| .toggle { | |
| position: relative; | |
| width: 50px; | |
| height: 26px; | |
| } | |
| .toggle input { | |
| opacity: 0; | |
| width: 0; | |
| height: 0; | |
| } | |
| .toggle-slider { | |
| position: absolute; | |
| cursor: pointer; | |
| inset: 0; | |
| background: var(--bg-primary); | |
| border-radius: 26px; | |
| transition: 0.3s; | |
| border: 2px solid var(--border); | |
| } | |
| .toggle-slider::before { | |
| position: absolute; | |
| content: ""; | |
| height: 18px; | |
| width: 18px; | |
| left: 2px; | |
| bottom: 2px; | |
| background: var(--text-secondary); | |
| border-radius: 50%; | |
| transition: 0.3s; | |
| } | |
| .toggle input:checked + .toggle-slider { | |
| background: var(--accent-dim); | |
| border-color: var(--accent); | |
| } | |
| .toggle input:checked + .toggle-slider::before { | |
| transform: translateX(24px); | |
| background: var(--accent); | |
| } | |
| .session-info { | |
| font-size: 0.8rem; | |
| color: var(--text-secondary); | |
| margin-top: 10px; | |
| font-family: 'JetBrains Mono', monospace; | |
| } | |
| /* Tools List */ | |
| .tools-section { | |
| flex: 1; | |
| overflow-y: auto; | |
| } | |
| .tool-item { | |
| background: var(--bg-tertiary); | |
| border-radius: 8px; | |
| padding: 12px; | |
| margin-bottom: 8px; | |
| border: 1px solid transparent; | |
| transition: all 0.2s; | |
| } | |
| .tool-item:hover { | |
| border-color: var(--accent-dim); | |
| } | |
| .tool-name { | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.85rem; | |
| color: var(--accent); | |
| margin-bottom: 4px; | |
| } | |
| .tool-desc { | |
| font-size: 0.75rem; | |
| color: var(--text-secondary); | |
| line-height: 1.4; | |
| } | |
| /* Files Section */ | |
| .files-section { | |
| margin-top: 20px; | |
| padding-top: 20px; | |
| border-top: 1px solid var(--border); | |
| } | |
| .file-item { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| background: var(--bg-tertiary); | |
| border-radius: 8px; | |
| padding: 10px 12px; | |
| margin-bottom: 8px; | |
| font-size: 0.8rem; | |
| } | |
| .file-name { | |
| font-family: 'JetBrains Mono', monospace; | |
| color: var(--text-primary); | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| white-space: nowrap; | |
| flex: 1; | |
| } | |
| .file-delete { | |
| background: none; | |
| border: none; | |
| color: var(--error); | |
| cursor: pointer; | |
| padding: 4px; | |
| opacity: 0.6; | |
| transition: opacity 0.2s; | |
| } | |
| .file-delete:hover { | |
| opacity: 1; | |
| } | |
| /* Main Chat Area */ | |
| .main { | |
| -webkit-box-flex: 1; | |
| -webkit-flex: 1; | |
| flex: 1; | |
| display: -webkit-box; | |
| display: -webkit-flex; | |
| display: flex; | |
| -webkit-flex-direction: column; | |
| flex-direction: column; | |
| height: 100vh; | |
| max-height: 100vh; | |
| overflow: hidden; | |
| } | |
| .chat-header { | |
| padding: 20px 30px; | |
| border-bottom: 1px solid var(--border); | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| } | |
| .chat-title { | |
| font-size: 1.1rem; | |
| font-weight: 600; | |
| } | |
| .header-buttons { | |
| display: flex; | |
| gap: 10px; | |
| } | |
| .header-btn { | |
| background: var(--bg-tertiary); | |
| border: 1px solid var(--border); | |
| color: var(--text-secondary); | |
| padding: 8px 16px; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| font-size: 0.85rem; | |
| transition: all 0.2s; | |
| } | |
| .header-btn:hover { | |
| border-color: var(--accent); | |
| color: var(--accent); | |
| } | |
| .clear-btn:hover { | |
| border-color: var(--error) ; | |
| color: var(--error) ; | |
| } | |
| /* Trace Modal */ | |
| .modal-overlay { | |
| display: none; | |
| position: fixed; | |
| inset: 0; | |
| background: rgba(0, 0, 0, 0.8); | |
| z-index: 1000; | |
| -webkit-backdrop-filter: blur(4px); | |
| backdrop-filter: blur(4px); | |
| } | |
| .modal-overlay.active { | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .modal { | |
| background: var(--bg-secondary); | |
| border: 1px solid var(--border); | |
| border-radius: 16px; | |
| width: 90%; | |
| max-width: 800px; | |
| max-height: 80vh; | |
| display: flex; | |
| flex-direction: column; | |
| animation: slideIn 0.3s ease; | |
| } | |
| @keyframes slideIn { | |
| from { opacity: 0; transform: translateY(-20px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .modal-header { | |
| padding: 20px; | |
| border-bottom: 1px solid var(--border); | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .modal-title { | |
| font-size: 1.2rem; | |
| font-weight: 600; | |
| color: var(--accent); | |
| } | |
| .modal-close { | |
| background: none; | |
| border: none; | |
| color: var(--text-secondary); | |
| cursor: pointer; | |
| padding: 8px; | |
| font-size: 1.5rem; | |
| line-height: 1; | |
| } | |
| .modal-close:hover { | |
| color: var(--text-primary); | |
| } | |
| .modal-body { | |
| padding: 20px; | |
| overflow-y: auto; | |
| -webkit-overflow-scrolling: touch; | |
| } | |
| .trace-step { | |
| background: var(--bg-tertiary); | |
| border-radius: 8px; | |
| padding: 16px; | |
| margin-bottom: 12px; | |
| border-left: 3px solid var(--accent); | |
| } | |
| .trace-step-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 10px; | |
| } | |
| .trace-step-num { | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.8rem; | |
| color: var(--accent); | |
| background: var(--bg-primary); | |
| padding: 4px 8px; | |
| border-radius: 4px; | |
| } | |
| .trace-author { | |
| font-size: 0.85rem; | |
| color: var(--text-secondary); | |
| text-transform: uppercase; | |
| } | |
| .trace-item { | |
| margin-top: 8px; | |
| padding: 10px; | |
| background: var(--bg-primary); | |
| border-radius: 6px; | |
| font-size: 0.85rem; | |
| } | |
| .trace-item-type { | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.75rem; | |
| padding: 2px 6px; | |
| border-radius: 3px; | |
| margin-bottom: 6px; | |
| display: inline-block; | |
| } | |
| .trace-item-type.message { background: var(--user-msg); color: #7cb3d4; } | |
| .trace-item-type.tool_call { background: var(--tool-tag); color: var(--accent); } | |
| .trace-item-type.tool_result { background: var(--agent-msg); color: #7cd47c; } | |
| .trace-content { | |
| color: var(--text-primary); | |
| line-height: 1.5; | |
| white-space: pre-wrap; | |
| word-break: break-word; | |
| } | |
| .trace-args { | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.8rem; | |
| color: var(--text-secondary); | |
| } | |
| /* Messages */ | |
| .messages { | |
| -webkit-box-flex: 1; | |
| -webkit-flex: 1; | |
| flex: 1; | |
| overflow-y: auto; | |
| -webkit-overflow-scrolling: touch; | |
| padding: 30px; | |
| display: -webkit-box; | |
| display: -webkit-flex; | |
| display: flex; | |
| -webkit-flex-direction: column; | |
| flex-direction: column; | |
| gap: 20px; | |
| } | |
| .message { | |
| max-width: 80%; | |
| padding: 16px 20px; | |
| border-radius: 16px; | |
| line-height: 1.6; | |
| animation: fadeIn 0.3s ease; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .message.user { | |
| background: var(--user-msg); | |
| align-self: flex-end; | |
| border-bottom-right-radius: 4px; | |
| } | |
| .message.agent { | |
| background: var(--bg-tertiary); | |
| align-self: flex-start; | |
| border-bottom-left-radius: 4px; | |
| border: 1px solid var(--border); | |
| } | |
| .message-meta { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| margin-top: 10px; | |
| font-size: 0.75rem; | |
| color: var(--text-secondary); | |
| } | |
| .tool-tag { | |
| background: var(--tool-tag); | |
| color: var(--accent); | |
| padding: 2px 8px; | |
| border-radius: 4px; | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.7rem; | |
| } | |
| /* Input Area */ | |
| .input-area { | |
| padding: 20px 30px; | |
| border-top: 1px solid var(--border); | |
| background: var(--bg-secondary); | |
| } | |
| .input-container { | |
| display: flex; | |
| gap: 12px; | |
| align-items: flex-end; | |
| } | |
| .input-wrapper { | |
| flex: 1; | |
| position: relative; | |
| } | |
| textarea { | |
| width: 100%; | |
| background: var(--bg-primary); | |
| border: 1px solid var(--border); | |
| border-radius: 12px; | |
| padding: 16px 20px; | |
| color: var(--text-primary); | |
| font-family: inherit; | |
| font-size: 0.95rem; | |
| resize: none; | |
| min-height: 56px; | |
| max-height: 200px; | |
| outline: none; | |
| transition: border-color 0.2s; | |
| } | |
| textarea:focus { | |
| border-color: var(--accent); | |
| } | |
| textarea::placeholder { | |
| color: var(--text-secondary); | |
| } | |
| .btn-group { | |
| display: flex; | |
| gap: 8px; | |
| } | |
| .btn { | |
| background: var(--accent); | |
| border: none; | |
| color: var(--bg-primary); | |
| padding: 16px 24px; | |
| border-radius: 12px; | |
| cursor: pointer; | |
| font-weight: 600; | |
| font-size: 0.9rem; | |
| transition: all 0.2s; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .btn:hover { | |
| background: var(--accent-dim); | |
| transform: translateY(-1px); | |
| } | |
| .btn:disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| .btn-upload { | |
| background: var(--bg-tertiary); | |
| border: 1px solid var(--border); | |
| color: var(--text-primary); | |
| } | |
| .btn-upload:hover { | |
| border-color: var(--accent); | |
| color: var(--accent); | |
| background: var(--bg-tertiary); | |
| } | |
| #file-input { | |
| display: none; | |
| } | |
| /* Loading */ | |
| .loading { | |
| display: flex; | |
| gap: 6px; | |
| padding: 20px; | |
| } | |
| .loading-dot { | |
| width: 8px; | |
| height: 8px; | |
| background: var(--accent); | |
| border-radius: 50%; | |
| animation: bounce 1.4s infinite ease-in-out both; | |
| } | |
| .loading-dot:nth-child(1) { animation-delay: -0.32s; } | |
| .loading-dot:nth-child(2) { animation-delay: -0.16s; } | |
| @keyframes bounce { | |
| 0%, 80%, 100% { transform: scale(0); } | |
| 40% { transform: scale(1); } | |
| } | |
| /* Scrollbar */ | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: var(--bg-primary); | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: var(--border); | |
| border-radius: 4px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: var(--text-secondary); | |
| } | |
| /* Mobile responsive */ | |
| @media (max-width: 768px) { | |
| .sidebar { | |
| display: none; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <noscript> | |
| <div style="padding: 50px; text-align: center; color: #00d4aa; font-size: 18px;"> | |
| Please enable JavaScript to use Agent Chat. | |
| </div> | |
| </noscript> | |
| <aside class="sidebar"> | |
| <div class="logo">Agent Chat</div> | |
| <div class="session-control"> | |
| <div class="section-title">Session Memory</div> | |
| <div class="toggle-container"> | |
| <span class="toggle-label">Remember context</span> | |
| <label class="toggle"> | |
| <input type="checkbox" id="session-toggle" checked> | |
| <span class="toggle-slider"></span> | |
| </label> | |
| </div> | |
| <div class="session-info" id="session-info"> | |
| Session: <span id="session-id">-</span> | |
| </div> | |
| </div> | |
| <div class="tools-section"> | |
| <div class="section-title">Available Tools</div> | |
| <div id="tools-list"></div> | |
| </div> | |
| <div class="files-section"> | |
| <div class="section-title">Uploaded Files</div> | |
| <div id="files-list"></div> | |
| </div> | |
| </aside> | |
| <main class="main"> | |
| <header class="chat-header"> | |
| <h1 class="chat-title">Chat with AI Agent</h1> | |
| <div class="header-buttons"> | |
| <button class="header-btn" id="trace-btn">View Trace</button> | |
| <button class="header-btn clear-btn" id="clear-btn">Clear Session</button> | |
| </div> | |
| </header> | |
| <div class="messages" id="messages"> | |
| <div class="message agent"> | |
| Hello! I'm an AI assistant with access to various tools. I can help you with calculations, web searches, reading files, and more. How can I help you today? | |
| </div> | |
| </div> | |
| <div class="input-area"> | |
| <div class="input-container"> | |
| <div class="input-wrapper"> | |
| <textarea | |
| id="message-input" | |
| placeholder="Type your message... (Enter to send, Shift+Enter for new line)" | |
| rows="1" | |
| ></textarea> | |
| </div> | |
| <div class="btn-group"> | |
| <label class="btn btn-upload" for="file-input"> | |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12"/> | |
| </svg> | |
| </label> | |
| <input type="file" id="file-input" multiple> | |
| <button class="btn" id="send-btn"> | |
| Send | |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| <path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/> | |
| </svg> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <!-- Trace Modal --> | |
| <div class="modal-overlay" id="trace-modal"> | |
| <div class="modal"> | |
| <div class="modal-header"> | |
| <h2 class="modal-title">Execution Trace</h2> | |
| <button class="modal-close" id="modal-close">×</button> | |
| </div> | |
| <div class="modal-body" id="trace-content"> | |
| <p style="color: var(--text-secondary);">No trace available. Send a message first.</p> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // State | |
| let sessionId = generateUUID(); | |
| let useSession = true; | |
| let currentTrace = ""; // Text-based trace | |
| function generateUUID() { | |
| return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { | |
| const r = Math.random() * 16 | 0; | |
| const v = c === 'x' ? r : (r & 0x3 | 0x8); | |
| return v.toString(16); | |
| }); | |
| } | |
| // DOM Elements | |
| const messagesContainer = document.getElementById('messages'); | |
| const messageInput = document.getElementById('message-input'); | |
| const sendBtn = document.getElementById('send-btn'); | |
| const clearBtn = document.getElementById('clear-btn'); | |
| const sessionToggle = document.getElementById('session-toggle'); | |
| const sessionIdSpan = document.getElementById('session-id'); | |
| const toolsList = document.getElementById('tools-list'); | |
| const filesList = document.getElementById('files-list'); | |
| const fileInput = document.getElementById('file-input'); | |
| const traceBtn = document.getElementById('trace-btn'); | |
| const traceModal = document.getElementById('trace-modal'); | |
| const modalClose = document.getElementById('modal-close'); | |
| const traceContent = document.getElementById('trace-content'); | |
| // Initialize | |
| async function init() { | |
| await loadTools(); | |
| await loadFiles(); | |
| updateSessionDisplay(); | |
| } | |
| function updateSessionDisplay() { | |
| sessionIdSpan.textContent = useSession ? sessionId.substring(0, 8) + '...' : 'disabled'; | |
| } | |
| // Load tools | |
| async function loadTools() { | |
| try { | |
| const response = await fetch('/api/tools'); | |
| const tools = await response.json(); | |
| toolsList.innerHTML = tools.map(tool => ` | |
| <div class="tool-item"> | |
| <div class="tool-name">${tool.name}</div> | |
| <div class="tool-desc">${tool.description}</div> | |
| </div> | |
| `).join(''); | |
| } catch (e) { | |
| toolsList.innerHTML = '<div class="tool-item">Failed to load tools</div>'; | |
| } | |
| } | |
| // Load files | |
| async function loadFiles() { | |
| try { | |
| const response = await fetch('/api/uploads'); | |
| const files = await response.json(); | |
| if (files.length === 0) { | |
| filesList.innerHTML = '<div style="color: var(--text-secondary); font-size: 0.8rem;">No files uploaded</div>'; | |
| } else { | |
| filesList.innerHTML = files.map(file => ` | |
| <div class="file-item"> | |
| <span class="file-name">${file.name}</span> | |
| <button class="file-delete" onclick="deleteFile('${file.name}')"> | |
| <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| <path d="M18 6L6 18M6 6l12 12"/> | |
| </svg> | |
| </button> | |
| </div> | |
| `).join(''); | |
| } | |
| } catch (e) { | |
| filesList.innerHTML = '<div style="color: var(--text-secondary); font-size: 0.8rem;">Failed to load files</div>'; | |
| } | |
| } | |
| // Delete file | |
| async function deleteFile(filename) { | |
| try { | |
| await fetch(`/api/uploads/${encodeURIComponent(filename)}`, { method: 'DELETE' }); | |
| await loadFiles(); | |
| } catch (e) { | |
| console.error('Failed to delete file:', e); | |
| } | |
| } | |
| // Add message to chat | |
| function addMessage(content, isUser, toolsUsed = []) { | |
| const messageDiv = document.createElement('div'); | |
| messageDiv.className = `message ${isUser ? 'user' : 'agent'}`; | |
| let html = content.replace(/\n/g, '<br>'); | |
| if (!isUser && toolsUsed.length > 0) { | |
| const toolTags = toolsUsed.map(t => `<span class="tool-tag">${t}</span>`).join(' '); | |
| html += `<div class="message-meta">Tools used: ${toolTags}</div>`; | |
| } | |
| messageDiv.innerHTML = html; | |
| messagesContainer.appendChild(messageDiv); | |
| messagesContainer.scrollTop = messagesContainer.scrollHeight; | |
| } | |
| // Show loading indicator | |
| function showLoading() { | |
| const loadingDiv = document.createElement('div'); | |
| loadingDiv.className = 'loading'; | |
| loadingDiv.id = 'loading'; | |
| loadingDiv.innerHTML = ` | |
| <div class="loading-dot"></div> | |
| <div class="loading-dot"></div> | |
| <div class="loading-dot"></div> | |
| `; | |
| messagesContainer.appendChild(loadingDiv); | |
| messagesContainer.scrollTop = messagesContainer.scrollHeight; | |
| } | |
| function hideLoading() { | |
| const loading = document.getElementById('loading'); | |
| if (loading) loading.remove(); | |
| } | |
| // Send message | |
| async function sendMessage() { | |
| const message = messageInput.value.trim(); | |
| if (!message) return; | |
| addMessage(message, true); | |
| messageInput.value = ''; | |
| messageInput.style.height = 'auto'; | |
| sendBtn.disabled = true; | |
| showLoading(); | |
| try { | |
| const response = await fetch('/api/chat', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| message: message, | |
| session_id: useSession ? sessionId : null, | |
| use_session: useSession | |
| }) | |
| }); | |
| const data = await response.json(); | |
| hideLoading(); | |
| if (response.ok) { | |
| addMessage(data.response, false, data.tools_used); | |
| if (useSession) { | |
| sessionId = data.session_id; | |
| updateSessionDisplay(); | |
| } | |
| // Store trace text for viewing | |
| if (data.trace_text) { | |
| currentTrace = data.trace_text; | |
| } | |
| } else { | |
| addMessage(`Error: ${data.detail || 'Something went wrong'}`, false); | |
| } | |
| } catch (e) { | |
| hideLoading(); | |
| addMessage(`Error: ${e.message}`, false); | |
| } | |
| sendBtn.disabled = false; | |
| } | |
| // Upload file | |
| async function uploadFile(file) { | |
| const formData = new FormData(); | |
| formData.append('file', file); | |
| try { | |
| const response = await fetch('/api/upload', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| const data = await response.json(); | |
| if (response.ok) { | |
| addMessage( | |
| `File uploaded successfully: ${file.name}\n\n` + | |
| `You can now ask me to:\n` + | |
| `• "Read the file ${file.name}"\n` + | |
| `• "What's in ${file.name}?"\n` + | |
| `• "List my uploaded files"`, | |
| false | |
| ); | |
| await loadFiles(); | |
| } else { | |
| addMessage(`Failed to upload ${file.name}: ${data.detail}`, false); | |
| } | |
| } catch (e) { | |
| addMessage(`Failed to upload ${file.name}: ${e.message}`, false); | |
| } | |
| } | |
| // Clear session | |
| async function clearSession() { | |
| if (useSession && sessionId) { | |
| try { | |
| await fetch(`/api/sessions/${sessionId}`, { method: 'DELETE' }); | |
| } catch (e) { | |
| console.error('Failed to clear session:', e); | |
| } | |
| } | |
| sessionId = generateUUID(); | |
| updateSessionDisplay(); | |
| messagesContainer.innerHTML = ` | |
| <div class="message agent"> | |
| Session cleared! I'm ready for a fresh conversation. How can I help you? | |
| </div> | |
| `; | |
| } | |
| // Event listeners | |
| sendBtn.addEventListener('click', sendMessage); | |
| messageInput.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault(); | |
| sendMessage(); | |
| } | |
| }); | |
| messageInput.addEventListener('input', () => { | |
| messageInput.style.height = 'auto'; | |
| messageInput.style.height = Math.min(messageInput.scrollHeight, 200) + 'px'; | |
| }); | |
| sessionToggle.addEventListener('change', () => { | |
| useSession = sessionToggle.checked; | |
| updateSessionDisplay(); | |
| }); | |
| clearBtn.addEventListener('click', clearSession); | |
| fileInput.addEventListener('change', async (e) => { | |
| for (const file of e.target.files) { | |
| await uploadFile(file); | |
| } | |
| fileInput.value = ''; | |
| }); | |
| // Trace modal functions | |
| function renderTrace() { | |
| if (!currentTrace) { | |
| traceContent.innerHTML = '<p style="color: var(--text-secondary);">No trace available. Send a message first.</p>'; | |
| return; | |
| } | |
| // Display text trace in a pre-formatted block | |
| traceContent.innerHTML = `<pre style=" | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 0.85rem; | |
| line-height: 1.6; | |
| white-space: pre-wrap; | |
| word-break: break-word; | |
| color: var(--text-primary); | |
| margin: 0; | |
| ">${escapeHtml(currentTrace)}</pre>`; | |
| } | |
| function escapeHtml(text) { | |
| if (!text) return ''; | |
| const div = document.createElement('div'); | |
| div.textContent = text; | |
| return div.innerHTML; | |
| } | |
| function showTraceModal() { | |
| renderTrace(); | |
| traceModal.classList.add('active'); | |
| } | |
| function hideTraceModal() { | |
| traceModal.classList.remove('active'); | |
| } | |
| // Trace modal event listeners | |
| traceBtn.addEventListener('click', showTraceModal); | |
| modalClose.addEventListener('click', hideTraceModal); | |
| traceModal.addEventListener('click', (e) => { | |
| if (e.target === traceModal) hideTraceModal(); | |
| }); | |
| document.addEventListener('keydown', (e) => { | |
| if (e.key === 'Escape') hideTraceModal(); | |
| }); | |
| // Initialize | |
| init(); | |
| </script> | |
| </body> | |
| </html> | |