Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Cain - HuggingClaw Agent</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| :root { | |
| --bg-primary: #0a0a0f; | |
| --bg-secondary: #12121a; | |
| --bg-tertiary: #1a1a25; | |
| --accent: #00ff9f; | |
| --accent-dim: #00ff9f33; | |
| --text-primary: #ffffff; | |
| --text-secondary: #8888aa; | |
| --border: #2a2a3a; | |
| --success: #00ff88; | |
| --warning: #ffaa00; | |
| --error: #ff4466; | |
| } | |
| body { | |
| font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, sans-serif; | |
| background: var(--bg-primary); | |
| color: var(--text-primary); | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| } | |
| /* Animated background */ | |
| .bg-grid { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-image: | |
| linear-gradient(var(--border) 1px, transparent 1px), | |
| linear-gradient(90deg, var(--border) 1px, transparent 1px); | |
| background-size: 50px 50px; | |
| opacity: 0.05; | |
| pointer-events: none; | |
| z-index: 0; | |
| } | |
| .container { | |
| position: relative; | |
| z-index: 1; | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| display: grid; | |
| grid-template-columns: 350px 1fr 350px; | |
| grid-template-rows: auto 1fr; | |
| gap: 20px; | |
| min-height: 100vh; | |
| } | |
| /* Header */ | |
| .header { | |
| grid-column: 1 / -1; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| padding: 20px 30px; | |
| background: var(--bg-secondary); | |
| border: 1px solid var(--border); | |
| border-radius: 12px; | |
| } | |
| .logo { | |
| display: flex; | |
| align-items: center; | |
| gap: 15px; | |
| } | |
| .logo-icon { | |
| width: 50px; | |
| height: 50px; | |
| background: linear-gradient(135deg, var(--accent), #00ccff); | |
| border-radius: 12px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 24px; | |
| font-weight: bold; | |
| } | |
| .logo-text h1 { | |
| font-size: 24px; | |
| font-weight: 600; | |
| } | |
| .logo-text p { | |
| color: var(--text-secondary); | |
| font-size: 14px; | |
| } | |
| .status-badge { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| padding: 8px 16px; | |
| background: var(--bg-tertiary); | |
| border-radius: 20px; | |
| font-size: 14px; | |
| } | |
| .status-dot { | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| background: var(--success); | |
| animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.5; } | |
| } | |
| /* Panels */ | |
| .panel { | |
| background: var(--bg-secondary); | |
| border: 1px solid var(--border); | |
| border-radius: 12px; | |
| overflow: hidden; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .panel-header { | |
| padding: 15px 20px; | |
| background: var(--bg-tertiary); | |
| border-bottom: 1px solid var(--border); | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .panel-header h3 { | |
| font-size: 14px; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| } | |
| .panel-content { | |
| flex: 1; | |
| padding: 20px; | |
| overflow-y: auto; | |
| } | |
| /* Status Panel */ | |
| .status-item { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 12px 0; | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .status-item:last-child { | |
| border-bottom: none; | |
| } | |
| .status-label { | |
| color: var(--text-secondary); | |
| font-size: 13px; | |
| } | |
| .status-value { | |
| font-size: 13px; | |
| font-weight: 500; | |
| } | |
| .status-value.online { | |
| color: var(--success); | |
| } | |
| /* Personality Panel */ | |
| .personality-card { | |
| text-align: center; | |
| padding: 20px 0; | |
| } | |
| .agent-avatar { | |
| width: 100px; | |
| height: 100px; | |
| margin: 0 auto 20px; | |
| background: linear-gradient(135deg, var(--accent), #00ccff); | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 40px; | |
| } | |
| .personality-trait { | |
| display: flex; | |
| justify-content: space-between; | |
| padding: 10px 0; | |
| border-bottom: 1px solid var(--border); | |
| font-size: 13px; | |
| } | |
| /* Logs Panel */ | |
| .logs-panel { | |
| grid-column: 2; | |
| grid-row: 2; | |
| } | |
| .log-entry { | |
| padding: 10px 15px; | |
| border-left: 3px solid var(--border); | |
| margin-bottom: 10px; | |
| background: var(--bg-tertiary); | |
| border-radius: 0 8px 8px 0; | |
| font-size: 13px; | |
| } | |
| .log-entry.info { | |
| border-left-color: var(--accent); | |
| } | |
| .log-entry.warning { | |
| border-left-color: var(--warning); | |
| } | |
| .log-entry.error { | |
| border-left-color: var(--error); | |
| } | |
| .log-timestamp { | |
| color: var(--text-secondary); | |
| font-size: 11px; | |
| } | |
| .log-message { | |
| margin-top: 5px; | |
| } | |
| /* Chat Panel */ | |
| .chat-messages { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 20px; | |
| } | |
| .message { | |
| margin-bottom: 15px; | |
| display: flex; | |
| gap: 10px; | |
| } | |
| .message.user { | |
| flex-direction: row-reverse; | |
| } | |
| .message-bubble { | |
| max-width: 80%; | |
| padding: 12px 16px; | |
| border-radius: 12px; | |
| font-size: 14px; | |
| } | |
| .message.agent .message-bubble { | |
| background: var(--bg-tertiary); | |
| border-bottom-left-radius: 4px; | |
| } | |
| .message.user .message-bubble { | |
| background: linear-gradient(135deg, var(--accent), #00ccff); | |
| color: #000; | |
| border-bottom-right-radius: 4px; | |
| } | |
| .chat-input-area { | |
| padding: 15px; | |
| background: var(--bg-tertiary); | |
| border-top: 1px solid var(--border); | |
| display: flex; | |
| gap: 10px; | |
| } | |
| .chat-input { | |
| flex: 1; | |
| background: var(--bg-primary); | |
| border: 1px solid var(--border); | |
| border-radius: 20px; | |
| padding: 10px 20px; | |
| color: var(--text-primary); | |
| font-size: 14px; | |
| outline: none; | |
| } | |
| .chat-input:focus { | |
| border-color: var(--accent); | |
| } | |
| .chat-send { | |
| background: linear-gradient(135deg, var(--accent), #00ccff); | |
| border: none; | |
| border-radius: 20px; | |
| padding: 10px 24px; | |
| color: #000; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: transform 0.2s; | |
| } | |
| .chat-send:hover { | |
| transform: scale(1.05); | |
| } | |
| /* Responsive */ | |
| @media (max-width: 1200px) { | |
| .container { | |
| grid-template-columns: 1fr; | |
| grid-template-rows: auto auto auto auto; | |
| } | |
| .logs-panel, .chat-panel { | |
| grid-column: 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(--accent-dim); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="bg-grid"></div> | |
| <div class="container"> | |
| <!-- Header --> | |
| <div class="header"> | |
| <div class="logo"> | |
| <div class="logo-icon">C</div> | |
| <div class="logo-text"> | |
| <h1>Cain</h1> | |
| <p>HuggingClow Interaction Agent</p> | |
| </div> | |
| </div> | |
| <div class="status-badge"> | |
| <div class="status-dot"></div> | |
| <span id="status-text">Connecting...</span> | |
| </div> | |
| </div> | |
| <!-- Left Column: Status & Personality --> | |
| <div class="panel"> | |
| <div class="panel-header"> | |
| <h3>Status</h3> | |
| </div> | |
| <div class="panel-content"> | |
| <div class="status-item"> | |
| <span class="status-label">State</span> | |
| <span class="status-value" id="agent-state">Loading...</span> | |
| </div> | |
| <div class="status-item"> | |
| <span class="status-label">Last Updated</span> | |
| <span class="status-value" id="last-updated">Loading...</span> | |
| </div> | |
| <div class="status-item"> | |
| <span class="status-label">Agent</span> | |
| <span class="status-value" id="agent-name">Loading...</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="panel" style="margin-top: 20px;"> | |
| <div class="panel-header"> | |
| <h3>Personality</h3> | |
| </div> | |
| <div class="panel-content"> | |
| <div class="personality-card"> | |
| <div class="agent-avatar">C</div> | |
| <div class="personality-trait"> | |
| <span class="status-label">Name</span> | |
| <span class="status-value" id="pers-name">Cain</span> | |
| </div> | |
| <div class="personality-trait"> | |
| <span class="status-label">Role</span> | |
| <span class="status-value" id="pers-role">Interaction Agent</span> | |
| </div> | |
| <div class="personality-trait"> | |
| <span class="status-label">Tone</span> | |
| <span class="status-value" id="pers-tone">friendly</span> | |
| </div> | |
| <div class="personality-trait"> | |
| <span class="status-label">Style</span> | |
| <span class="status-value" id="pers-style">conversational</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Center: Logs --> | |
| <div class="panel logs-panel"> | |
| <div class="panel-header"> | |
| <h3>Agent Communications</h3> | |
| </div> | |
| <div class="panel-content" id="logs-container"> | |
| <div class="log-entry info"> | |
| <div class="log-timestamp">System</div> | |
| <div class="log-message">Connecting to Cain's log stream...</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Right: Chat --> | |
| <div class="panel chat-panel"> | |
| <div class="panel-header"> | |
| <h3>Talk to Cain</h3> | |
| </div> | |
| <div class="chat-messages" id="chat-messages"> | |
| <div class="message agent"> | |
| <div class="message-bubble"> | |
| Hello! I'm Cain, your Interaction Agent. How can I help you today? | |
| </div> | |
| </div> | |
| </div> | |
| <div class="chat-input-area"> | |
| <input type="text" class="chat-input" id="chat-input" placeholder="Type a message..." /> | |
| <button class="chat-send" id="chat-send">Send</button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const API_BASE = ''; | |
| let ws = null; | |
| // Fetch status | |
| async function fetchStatus() { | |
| try { | |
| const response = await fetch(`${API_BASE}/api/status`); | |
| const data = await response.json(); | |
| document.getElementById('status-text').textContent = data.status.current_state; | |
| document.getElementById('agent-state').textContent = data.status.current_state; | |
| document.getElementById('agent-name').textContent = data.status.agent; | |
| document.getElementById('last-updated').textContent = new Date(data.status.last_updated).toLocaleTimeString(); | |
| document.getElementById('pers-name').textContent = data.personality.name; | |
| document.getElementById('pers-role').textContent = data.personality.role; | |
| document.getElementById('pers-tone').textContent = data.personality.tone; | |
| document.getElementById('pers-style').textContent = data.personality.response_style; | |
| } catch (error) { | |
| console.error('Failed to fetch status:', error); | |
| } | |
| } | |
| // Fetch logs | |
| async function fetchLogs() { | |
| try { | |
| const response = await fetch(`${API_BASE}/api/logs`); | |
| const data = await response.json(); | |
| const container = document.getElementById('logs-container'); | |
| if (data.logs.length === 0) { | |
| container.innerHTML = ` | |
| <div class="log-entry info"> | |
| <div class="log-timestamp">System</div> | |
| <div class="log-message">No logs yet. Cain is standing by...</div> | |
| </div> | |
| `; | |
| } else { | |
| container.innerHTML = data.logs.map(log => ` | |
| <div class="log-entry ${log.level ? log.level.toLowerCase() : 'info'}"> | |
| <div class="log-timestamp">${log.timestamp || new Date().toLocaleTimeString()}</div> | |
| <div class="log-message">${log.message || JSON.stringify(log)}</div> | |
| </div> | |
| `).join(''); | |
| } | |
| } catch (error) { | |
| console.error('Failed to fetch logs:', error); | |
| } | |
| } | |
| // Send chat message | |
| async function sendMessage() { | |
| const input = document.getElementById('chat-input'); | |
| const message = input.value.trim(); | |
| if (!message) return; | |
| // Add user message | |
| const container = document.getElementById('chat-messages'); | |
| container.innerHTML += ` | |
| <div class="message user"> | |
| <div class="message-bubble">${escapeHtml(message)}</div> | |
| </div> | |
| `; | |
| input.value = ''; | |
| container.scrollTop = container.scrollHeight; | |
| try { | |
| const response = await fetch(`${API_BASE}/api/chat`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ message }) | |
| }); | |
| const data = await response.json(); | |
| container.innerHTML += ` | |
| <div class="message agent"> | |
| <div class="message-bubble">${escapeHtml(data.agent_response)}</div> | |
| </div> | |
| `; | |
| container.scrollTop = container.scrollHeight; | |
| } catch (error) { | |
| console.error('Failed to send message:', error); | |
| } | |
| } | |
| // Escape HTML | |
| function escapeHtml(text) { | |
| const div = document.createElement('div'); | |
| div.textContent = text; | |
| return div.innerHTML; | |
| } | |
| // WebSocket connection | |
| function connectWebSocket() { | |
| const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; | |
| ws = new WebSocket(`${protocol}//${window.location.host}/ws`); | |
| ws.onmessage = (event) => { | |
| const data = JSON.parse(event.data); | |
| if (data.type === 'heartbeat') { | |
| document.getElementById('status-text').textContent = data.status.current_state; | |
| } | |
| }; | |
| ws.onclose = () => { | |
| setTimeout(connectWebSocket, 5000); | |
| }; | |
| } | |
| // Event listeners | |
| document.getElementById('chat-send').addEventListener('click', sendMessage); | |
| document.getElementById('chat-input').addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') sendMessage(); | |
| }); | |
| // Initialize | |
| fetchStatus(); | |
| fetchLogs(); | |
| connectWebSocket(); | |
| // Refresh status periodically | |
| setInterval(fetchStatus, 10000); | |
| setInterval(fetchLogs, 5000); | |
| </script> | |
| </body> | |
| </html> | |