<!-- This container ID links the HTML to the Custom CSS you added --> <div id="nova-interface-container"> <!-- Load Tailwind CSS & Google Fonts --> <script src="https://cdn.tailwindcss.com"></script> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> <!-- Main Dashboard HTML Structure --> <div class="w-full max-w-7xl mx-auto"> <div class="bg-white rounded-2xl shadow-lg flex flex-col h-[95vh]"> <!-- Header --> <header class="p-4 border-b border-[var(--trm-border-gray)] flex justify-between items-center flex-shrink-0"> <div> <h1 class="text-xl font-bold text-[var(--trm-dark-gray)]">TRM Nova Command Interface</h1> <p class="text-sm text-[var(--trm-mid-gray)]">System Status: <span id="system-status" class="font-semibold text-green-600">Idle</span></p> </div> <img src="https://images.squarespace-cdn.com/content/v1/5f5f679483332c1c53b2039a/1600089531688-O5P9Y1J6J9Y2Z8J9Q8J9/TRM-Logo-Horizontal-Color.png?format=1500w" alt="Tether Rock Management Logo" class="h-10"> </header> <div class="flex flex-1 min-h-0"> <!-- Left Panel: AI Team Roster --> <aside class="w-1/4 p-4 border-r border-[var(--trm-border-gray)] flex flex-col"> <h2 class="text-lg font-semibold mb-4 text-[var(--trm-dark-gray)]">AI Team Roster</h2> <div id="role-roster" class="space-y-2"> <button class="role-btn w-full text-left p-3 rounded-lg transition-all duration-200 hover:bg-gray-100 active" data-role="ronan"> <p class="font-semibold">Ronan (COO)</p> <p class="text-xs text-[var(--trm-mid-gray)]">Trajectory & Logic</p> </button> <button class="role-btn w-full text-left p-3 rounded-lg transition-all duration-200 hover:bg-gray-100" data-role="gemini"> <p class="font-semibold">Gemini</p> <p class="text-xs text-[var(--trm-mid-gray)]">Deviation & Research</p> </button> <button class="role-btn w-full text-left p-3 rounded-lg transition-all duration-200 hover:bg-gray-100" data-role="pm"> <p class="font-semibold">Project Manager</p> <p class="text-xs text-[var(--trm-mid-gray)]">Triage & Tasking</p> </button> <button class="role-btn w-full text-left p-3 rounded-lg transition-all duration-200 hover:bg-gray-100" data-role="glenn"> <p class="font-semibold">Glenn</p> <p class="text-xs text-[var(--trm-mid-gray)]">Legal Oversight</p> </button> </div> <div class="mt-auto pt-4 border-t border-[var(--trm-border-gray)]"> <button id="board-meeting-btn" class="w-full text-left p-3 rounded-lg transition-all duration-200 bg-gray-800 text-white hover:bg-gray-700"> <p class="font-semibold">Board Meeting</p> <p class="text-xs text-gray-300">Synthesize All Perspectives</p> </button> </div> </aside> <!-- Right Panel: Chat and Input --> <main class="flex-1 flex flex-col"> <div id="chat-window" class="flex-1 p-6 overflow-y-auto"> <div class="ai-response ai-system p-4 rounded-lg mb-4"> <p class="font-semibold text-[var(--trm-dark-gray)]">Nova System</p> <p class="text-sm text-gray-700 mt-1">Welcome to the Command Interface. Select a specialist from the roster, type your directive, and press send. All interactions are logged to the Brain.</p> </div> </div> <div class="p-4 border-t border-[var(--trm-border-gray)] bg-white flex-shrink-0"> <div class="relative"> <textarea id="chat-input" class="w-full p-4 pr-16 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-[var(--trm-accent)] resize-none" placeholder="Enter directive, task, or prompt..." rows="2"></textarea> <button id="send-button" class="absolute right-3 top-1/2 -translate-y-1/2 bg-[var(--trm-blue)] text-white rounded-lg p-2.5 hover:bg-[var(--trm-accent)] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[var(--trm-accent)] disabled:bg-gray-400 disabled:cursor-not-allowed"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5"> <path d="M3.105 2.289a.75.75 0 00-.826.95l1.414 4.949a.75.75 0 00.95.54l3.852-1.101a.75.75 0 010 1.392l-3.852-1.101a.75.75 0 00-.95.54l-1.414 4.95a.75.75 0 00.826.95L19 10 3.105 2.289z" /> </svg> </button> </div> <div class="text-xs text-gray-400 mt-2">To include a document, paste its Google Drive link in your message.</div> </div> </main> </div> </div> </div> <!-- The JavaScript that makes the dashboard interactive --> <script> const BACKEND_URL = 'https://your-google-cloud-function-url-goes-here.cloudfunctions.net/nova-engine'; const chatWindow = document.getElementById('chat-window'); const chatInput = document.getElementById('chat-input'); const sendButton = document.getElementById('send-button'); const roleRoster = document.getElementById('role-roster'); const boardMeetingBtn = document.getElementById('board-meeting-btn'); const systemStatusEl = document.getElementById('system-status'); let activeRole = 'ronan'; function appendMessage(sender, text, roleClass) { const senderP = document.createElement('p'); senderP.className = 'font-semibold text-gray-800'; senderP.textContent = sender; const textP = document.createElement('p'); textP.className = 'text-sm text-gray-700 mt-1 whitespace-pre-wrap'; textP.textContent = text; const messageDiv = document.createElement('div'); messageDiv.className = `ai-response p-4 rounded-lg mb-4 ${roleClass}`; messageDiv.appendChild(senderP); messageDiv.appendChild(textP); chatWindow.appendChild(messageDiv); chatWindow.scrollTop = chatWindow.scrollHeight; } function appendUserMessage(text) { const messageDiv = document.createElement('div'); messageDiv.className = 'mb-4 flex justify-end'; messageDiv.innerHTML = `<div class="user-message p-4 rounded-lg max-w-lg"><p class="text-sm">${text.replace(/</g, "<").replace(/>/g, ">")}</p></div>`; chatWindow.appendChild(messageDiv); chatWindow.scrollTop = chatWindow.scrollHeight; } function setSystemStatus(status, isError = false) { systemStatusEl.textContent = status; systemStatusEl.className = `font-semibold ${isError ? 'text-red-600' : 'text-green-600'}`; } async function sendMessage() { const messageText = chatInput.value.trim(); if (!messageText) return; appendUserMessage(messageText); chatInput.value = ''; chatInput.disabled = true; sendButton.disabled = true; setSystemStatus('Processing...'); const thinkingDiv = document.createElement('div'); thinkingDiv.id = 'thinking-indicator'; thinkingDiv.className = 'mb-4'; thinkingDiv.innerHTML = `<div class="ai-response ai-system p-4 rounded-lg"><p class="text-sm text-gray-700 animate-pulse">Nova engine is processing...</p></div>`; chatWindow.appendChild(thinkingDiv); chatWindow.scrollTop = chatWindow.scrollHeight; try { const payload = { prompt: messageText, role: activeRole }; const response = await fetch(BACKEND_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); document.getElementById('thinking-indicator')?.remove(); if (!response.ok) { const errorResult = await response.json(); throw new Error(errorResult.error || `HTTP error! Status: ${response.status}`); } const result = await response.json(); if (Array.isArray(result.responses)) { result.responses.forEach(res => appendMessage(res.roleName, res.text, `ai-${res.roleClass}`)); } else { appendMessage(result.roleName, result.text, `ai-${result.roleClass}`); } setSystemStatus('Idle'); } catch (error) { console.error('Error:', error); document.getElementById('thinking-indicator')?.remove(); appendMessage('Nova System Error', error.message, 'ai-pm'); setSystemStatus('Error', true); } finally { chatInput.disabled = false; sendButton.disabled = false; chatInput.focus(); } } function selectRole(role) { activeRole = role; document.querySelectorAll('.role-btn').forEach(btn => btn.classList.remove('active')); const selectedBtn = document.querySelector(`.role-btn[data-role='${role}']`); if (selectedBtn) { selectedBtn.classList.add('active'); } else { boardMeetingBtn.classList.add('active'); } } sendButton.addEventListener('click', sendMessage); chatInput.addEventListener('keydown', (event) => { if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); sendMessage(); } }); roleRoster.addEventListener('click', (e) => { const button = e.target.closest('.role-btn'); if (button) { boardMeetingBtn.classList.remove('active'); selectRole(button.dataset.role); } }); boardMeetingBtn.addEventListener('click', () => { selectRole('board_meeting'); }); </script> </div>