ada-web

Ada AI is a specialized coding assistant designed for developers and students. Powered by Google Gemini 3.1, it provides real-time, streamin...

git clone https://github.com/notamitgamer/ada-web.git
· rebuilt hourlyOpen on GitHub
commit 736262f56a1ca26882e2cc893bb5d25d3758994a
parent fc9db4427330373575d50c920f2ec614dcaff9ec
Author: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Date:   Sun, 18 Jan 2026 07:18:43 +0000

Add profile/settings modals, chat history, file upload fix, and all navigation features

Co-authored-by: notamitgamer <199483577+notamitgamer@users.noreply.github.com>

Diffstat:
Mindex.html | 401++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 380 insertions(+), 21 deletions(-)

diff --git a/index.html b/index.html @@ -222,20 +222,35 @@ <div id="left-pane" class="w-full md:w-[40%] flex flex-col h-full border-r border-white/10 relative bg-neutral-900/50"> <!-- HEADER --> - <div class="h-14 border-b border-white/10 flex items-center justify-between px-4 bg-neutral-900"> - <div class="flex items-center gap-2"> - <div class="w-2 h-2 bg-lime-300 rounded-full animate-pulse"></div> - <span class="font-mono font-bold text-lime-300 tracking-wider">ADA // AI</span> - </div> + <div class="h-14 border-b border-[var(--border)] flex items-center justify-between px-4 bg-[var(--bg-secondary)]"> <div class="flex items-center gap-3"> - <a href="docs.html" class="hidden md:block text-xs text-neutral-400 hover:text-lime-300 transition-colors font-mono" title="Documentation"> - <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path></svg> - </a> - <button onclick="toggleSidebar()" class="md:hidden text-neutral-400 hover:text-white"> + <button onclick="toggleSidebar()" class="md:hidden text-[var(--text-secondary)] hover:text-[var(--text-primary)]"> <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path></svg> </button> - <div id="user-profile" class="hidden md:flex items-center gap-2 cursor-pointer" onclick="signOutUser()"> - <img id="user-avatar" src="" class="w-6 h-6 rounded-full border border-lime-300/30"> + <div class="flex items-center gap-2"> + <div class="w-2 h-2 bg-[var(--accent-success)] rounded-full animate-pulse"></div> + <span class="font-mono font-semibold text-[var(--accent-primary)] text-sm">ADA</span> + </div> + <span id="current-chat-title" class="text-sm text-[var(--text-secondary)] hidden md:block">New Chat</span> + </div> + <div class="flex items-center gap-2"> + <button onclick="newChat()" class="hidden md:flex items-center gap-2 px-3 py-1.5 text-sm text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)] rounded-lg transition-smooth"> + <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path></svg> + New + </button> + <button onclick="openSettings()" class="p-2 text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)] rounded-lg transition-smooth"> + <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path></svg> + </button> + <div class="relative"> + <button onclick="toggleUserMenu()" id="user-menu-btn" class="flex items-center gap-2 p-1 hover:bg-[var(--bg-tertiary)] rounded-lg transition-smooth"> + <img id="user-avatar" src="" class="w-8 h-8 rounded-full border border-[var(--border)]"> + </button> + <div id="user-menu" class="hidden absolute right-0 mt-2 w-48 bg-[var(--bg-secondary)] border border-[var(--border)] rounded-lg shadow-xl py-1 z-50"> + <button onclick="openProfile()" class="w-full text-left px-4 py-2 text-sm text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)] transition-smooth">Profile</button> + <button onclick="openSettings()" class="w-full text-left px-4 py-2 text-sm text-[var(--text-primary)] hover:bg-[var(--bg-tertiary)] transition-smooth">Settings</button> + <div class="border-t border-[var(--border)] my-1"></div> + <button onclick="confirmLogout()" class="w-full text-left px-4 py-2 text-sm text-red-400 hover:bg-[var(--bg-tertiary)] transition-smooth">Logout</button> + </div> </div> </div> </div> @@ -317,6 +332,107 @@ <button onclick="switchTab('code')" id="tab-code" class="flex-1 py-3 text-center text-xs font-mono uppercase tab-btn text-neutral-500">Code Canvas</button> </div> + <!-- PROFILE MODAL --> + <div id="profile-modal" class="fixed inset-0 z-50 backdrop-blur-sm bg-black/70 hidden items-center justify-center"> + <div class="bg-[var(--bg-secondary)] border border-[var(--border)] rounded-xl max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto animate-fade-in"> + <div class="flex items-center justify-between p-6 border-b border-[var(--border)]"> + <h2 class="text-xl font-semibold text-[var(--text-primary)]">Profile</h2> + <button onclick="closeProfile()" class="text-[var(--text-secondary)] hover:text-[var(--text-primary)] transition-smooth"> + <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg> + </button> + </div> + + <div class="p-6 space-y-6"> + <div class="flex items-center gap-4"> + <img id="profile-photo" src="" class="w-20 h-20 rounded-full border-2 border-[var(--border)]"> + <div> + <button onclick="uploadProfilePicture()" class="px-4 py-2 bg-[var(--accent-primary)] text-white rounded-lg text-sm hover:bg-[var(--accent-secondary)] transition-smooth">Change Photo</button> + <p class="text-xs text-[var(--text-secondary)] mt-1">Upload a new profile picture</p> + <input type="file" id="photo-upload" class="hidden" accept="image/*" onchange="handlePhotoUpload(this)"> + </div> + </div> + + <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> + <div> + <label class="block text-sm font-medium text-[var(--text-secondary)] mb-2">Display Name</label> + <input type="text" id="profile-displayName" class="w-full bg-[var(--bg-tertiary)] border border-[var(--border)] rounded-lg p-2 text-sm text-[var(--text-primary)] focus:border-[var(--accent-primary)] outline-none transition-smooth"> + </div> + <div> + <label class="block text-sm font-medium text-[var(--text-secondary)] mb-2">Email (read-only)</label> + <input type="email" id="profile-email" class="w-full bg-[var(--bg-elevated)] border border-[var(--border)] rounded-lg p-2 text-sm text-[var(--text-secondary)] cursor-not-allowed" readonly> + </div> + <div> + <label class="block text-sm font-medium text-[var(--text-secondary)] mb-2">Age</label> + <input type="text" id="profile-age" class="w-full bg-[var(--bg-tertiary)] border border-[var(--border)] rounded-lg p-2 text-sm text-[var(--text-primary)] focus:border-[var(--accent-primary)] outline-none transition-smooth"> + </div> + <div> + <label class="block text-sm font-medium text-[var(--text-secondary)] mb-2">Location</label> + <input type="text" id="profile-location" class="w-full bg-[var(--bg-tertiary)] border border-[var(--border)] rounded-lg p-2 text-sm text-[var(--text-primary)] focus:border-[var(--accent-primary)] outline-none transition-smooth"> + </div> + </div> + + <div> + <label class="block text-sm font-medium text-[var(--text-secondary)] mb-2">Bio</label> + <textarea id="profile-bio" rows="3" class="w-full bg-[var(--bg-tertiary)] border border-[var(--border)] rounded-lg p-2 text-sm text-[var(--text-primary)] focus:border-[var(--accent-primary)] outline-none resize-none transition-smooth"></textarea> + </div> + + <div class="grid grid-cols-3 gap-4 p-4 bg-[var(--bg-tertiary)] rounded-lg"> + <div class="text-center"> + <div class="text-2xl font-bold text-[var(--accent-primary)]" id="stat-chats">0</div> + <div class="text-xs text-[var(--text-secondary)]">Total Chats</div> + </div> + <div class="text-center"> + <div class="text-2xl font-bold text-[var(--accent-primary)]" id="stat-messages">0</div> + <div class="text-xs text-[var(--text-secondary)]">Messages</div> + </div> + <div class="text-center"> + <div class="text-2xl font-bold text-[var(--accent-primary)]" id="stat-snippets">0</div> + <div class="text-xs text-[var(--text-secondary)]">Code Snippets</div> + </div> + </div> + + <div class="flex justify-end gap-3"> + <button onclick="closeProfile()" class="px-4 py-2 bg-[var(--bg-tertiary)] text-[var(--text-primary)] rounded-lg hover:bg-[var(--bg-elevated)] transition-smooth">Cancel</button> + <button onclick="saveProfile()" class="px-4 py-2 bg-[var(--accent-primary)] text-white rounded-lg hover:bg-[var(--accent-secondary)] transition-smooth">Save Changes</button> + </div> + </div> + </div> + </div> + + <!-- SETTINGS MODAL --> + <div id="settings-modal" class="fixed inset-0 z-50 backdrop-blur-sm bg-black/70 hidden items-center justify-center"> + <div class="bg-[var(--bg-secondary)] border border-[var(--border)] rounded-xl max-w-md w-full mx-4 animate-fade-in"> + <div class="flex items-center justify-between p-6 border-b border-[var(--border)]"> + <h2 class="text-xl font-semibold text-[var(--text-primary)]">Settings</h2> + <button onclick="closeSettings()" class="text-[var(--text-secondary)] hover:text-[var(--text-primary)] transition-smooth"> + <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path></svg> + </button> + </div> + + <div class="p-6 space-y-4"> + <div> + <label class="block text-sm font-medium text-[var(--text-secondary)] mb-2">Editor Theme</label> + <select id="settings-theme" class="w-full bg-[var(--bg-tertiary)] border border-[var(--border)] rounded-lg p-2 text-sm text-[var(--text-primary)] outline-none"> + <option value="dracula">Dracula</option> + <option value="monokai">Monokai</option> + <option value="default">Default</option> + </select> + </div> + + <div> + <label class="block text-sm font-medium text-[var(--text-secondary)] mb-2">Font Size</label> + <input type="range" id="settings-fontSize" min="10" max="20" value="13" class="w-full" oninput="document.getElementById('fontSize-value').innerText = this.value"> + <div class="text-xs text-[var(--text-secondary)] text-right"><span id="fontSize-value">13</span>px</div> + </div> + + <div class="flex justify-end gap-3 pt-4"> + <button onclick="closeSettings()" class="px-4 py-2 bg-[var(--bg-tertiary)] text-[var(--text-primary)] rounded-lg hover:bg-[var(--bg-elevated)] transition-smooth">Cancel</button> + <button onclick="saveSettings()" class="px-4 py-2 bg-[var(--accent-primary)] text-white rounded-lg hover:bg-[var(--accent-secondary)] transition-smooth">Save</button> + </div> + </div> + </div> + </div> + <!-- JS LOGIC: Converted to Modules to prevent 'firebase is not defined' --> <script type="module"> import { initializeApp } from "https://www.gstatic.com/firebasejs/10.8.0/firebase-app.js"; @@ -415,27 +531,42 @@ } // --- FILE UPLOAD --- - let fileContextContent = ""; + let fileContextData = null; window.handleFileUpload = function(input) { const file = input.files[0]; if (!file) return; + // Validate file size (max 1MB) + if (file.size > 1024 * 1024) { + alert('File size must be less than 1MB'); + input.value = ''; + return; + } + const reader = new FileReader(); reader.onload = (e) => { - fileContextContent = e.target.result; + fileContextData = { + name: file.name, + content: e.target.result, + size: file.size, + type: file.type + }; + document.getElementById('file-preview').classList.remove('hidden'); document.getElementById('file-name').innerText = file.name; + document.getElementById('file-size').innerText = `(${(file.size / 1024).toFixed(1)} KB)`; + // Auto-detect language if(file.name.endsWith('.py')) changeLanguage('python'); - if(file.name.endsWith('.js')) changeLanguage('javascript'); - if(file.name.endsWith('.c') || file.name.endsWith('.cpp')) changeLanguage('clike'); + else if(file.name.endsWith('.js')) changeLanguage('javascript'); + else if(file.name.endsWith('.c') || file.name.endsWith('.cpp') || file.name.endsWith('.java')) changeLanguage('clike'); }; reader.readAsText(file); } window.clearFile = function() { - fileContextContent = ""; + fileContextData = null; document.getElementById('file-upload').value = ""; document.getElementById('file-preview').classList.add('hidden'); } @@ -454,7 +585,7 @@ window.sendMessage = async function() { const text = chatInput.value.trim(); - if (!text && !fileContextContent) return; + if (!text && !fileContextData) return; if (!currentUser) { alert("Please login first."); return; @@ -485,7 +616,7 @@ message: text, history: chatHistory.slice(-10), codeContext: codeContext, - fileContext: fileContextContent, + fileContext: fileContextData, // Send as object now sessionId: currentSessionId }) }); @@ -504,7 +635,7 @@ message: text, history: chatHistory.slice(-10), codeContext: codeContext, - fileContext: fileContextContent, + fileContext: fileContextData, sessionId: currentSessionId }) }); @@ -569,7 +700,7 @@ } chatHistory.push({ user: userText, model: fullResponse }); - fileContextContent = ""; + fileContextData = null; document.getElementById('file-preview').classList.add('hidden'); } @@ -686,8 +817,236 @@ } async function loadHistory() { - // Placeholder + if (!currentUser || !db) return; + + try { + const API_URL = 'https://ada-web.onrender.com/api/chats'; + const token = await currentUser.getIdToken(); + + const response = await fetch(API_URL, { + headers: { 'Authorization': `Bearer ${token}` } + }); + + if (response.ok) { + const data = await response.json(); + displayChatHistory(data.chats || []); + } + } catch (e) { + console.error("Error loading chat history:", e); + } } + + function displayChatHistory(chats) { + const container = document.getElementById('chat-history'); + if (!container) return; + + if (chats.length === 0) { + container.innerHTML = '<div class="p-4 text-sm text-[var(--text-secondary)] text-center">No chats yet</div>'; + return; + } + + container.innerHTML = chats.map(chat => ` + <div class="p-2 hover:bg-[var(--bg-tertiary)] rounded-lg cursor-pointer transition-smooth mb-1" onclick="loadChat('${chat.id}')"> + <div class="text-sm text-[var(--text-primary)] truncate">${chat.title || 'New Chat'}</div> + <div class="text-xs text-[var(--text-secondary)]">${chat.messageCount || 0} messages</div> + </div> + `).join(''); + } + + window.loadChat = async function(chatId) { + if (!currentUser) return; + + try { + const token = await currentUser.getIdToken(); + const response = await fetch(`https://ada-web.onrender.com/api/chats/${chatId}`, { + headers: { 'Authorization': `Bearer ${token}` } + }); + + if (response.ok) { + const chat = await response.json(); + currentSessionId = chatId; + document.getElementById('current-chat-title').innerText = chat.title || 'Chat'; + + // Clear and load messages + const container = document.getElementById('chat-container'); + container.innerHTML = ''; + + chat.messages.forEach(msg => { + if (msg.role === 'user') { + appendMessage('user', msg.content); + } else { + const div = document.createElement('div'); + div.className = "flex gap-4 animate-fade-in"; + div.innerHTML = ` + <div class="w-8 h-8 rounded-lg bg-[var(--accent-primary)]/10 flex items-center justify-center border border-[var(--accent-primary)]/20 text-[var(--accent-primary)] text-xs font-mono">AI</div> + <div class="flex-1"> + <div class="text-sm text-[var(--accent-primary)] font-medium mb-1">Ada</div> + <div class="prose text-sm text-[var(--text-primary)] max-w-none">${marked.parse(msg.content)}</div> + </div> + `; + container.appendChild(div); + } + }); + } + } catch (e) { + console.error("Error loading chat:", e); + } + } + + window.newChat = function() { + currentSessionId = crypto.randomUUID(); + chatHistory = []; + document.getElementById('chat-container').innerHTML = ` + <div class="flex gap-4 mb-6"> + <div class="w-8 h-8 rounded-lg bg-[var(--accent-primary)]/10 flex items-center justify-center border border-[var(--accent-primary)]/20 text-[var(--accent-primary)] text-xs font-mono">AI</div> + <div class="flex-1"> + <div class="text-sm text-[var(--accent-primary)] font-medium mb-1">Ada</div> + <div class="prose"> + <p>Hello! I'm Ada, your AI coding assistant.</p> + <p>Upload a file, paste code, or ask me a question to get started.</p> + </div> + </div> + </div> + `; + document.getElementById('current-chat-title').innerText = 'New Chat'; + editor.setValue(''); + } + + window.toggleUserMenu = function() { + const menu = document.getElementById('user-menu'); + menu.classList.toggle('hidden'); + } + + window.confirmLogout = function() { + if (confirm("Are you sure you want to logout?")) { + signOut(auth); + } + } + + window.openProfile = async function() { + const modal = document.getElementById('profile-modal'); + modal.classList.remove('hidden'); + modal.classList.add('flex'); + + if (!currentUser) return; + + try { + const token = await currentUser.getIdToken(); + const response = await fetch('https://ada-web.onrender.com/api/profile', { + headers: { 'Authorization': `Bearer ${token}` } + }); + + if (response.ok) { + const profile = await response.json(); + document.getElementById('profile-displayName').value = profile.displayName || ''; + document.getElementById('profile-email').value = currentUser.email || ''; + document.getElementById('profile-age').value = profile.age || ''; + document.getElementById('profile-location').value = profile.location || ''; + document.getElementById('profile-bio').value = profile.bio || ''; + document.getElementById('profile-photo').src = currentUser.photoURL || 'https://ui-avatars.com/api/?name=User'; + document.getElementById('stat-chats').innerText = profile.totalChats || 0; + document.getElementById('stat-messages').innerText = profile.totalMessages || 0; + document.getElementById('stat-snippets').innerText = profile.totalCodeSnippets || 0; + } + } catch (e) { + console.error("Error loading profile:", e); + } + } + + window.closeProfile = function() { + const modal = document.getElementById('profile-modal'); + modal.classList.add('hidden'); + modal.classList.remove('flex'); + } + + window.saveProfile = async function() { + if (!currentUser) return; + + const data = { + displayName: document.getElementById('profile-displayName').value, + age: document.getElementById('profile-age').value, + location: document.getElementById('profile-location').value, + bio: document.getElementById('profile-bio').value + }; + + try { + const token = await currentUser.getIdToken(); + const response = await fetch('https://ada-web.onrender.com/api/profile', { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify(data) + }); + + if (response.ok) { + alert('Profile updated successfully!'); + closeProfile(); + } + } catch (e) { + console.error("Error saving profile:", e); + alert('Failed to update profile'); + } + } + + window.uploadProfilePicture = function() { + document.getElementById('photo-upload').click(); + } + + window.handlePhotoUpload = async function(input) { + // Firebase Storage upload would go here + alert('Profile picture upload not yet implemented'); + } + + window.openSettings = function() { + const modal = document.getElementById('settings-modal'); + modal.classList.remove('hidden'); + modal.classList.add('flex'); + } + + window.closeSettings = function() { + const modal = document.getElementById('settings-modal'); + modal.classList.add('hidden'); + modal.classList.remove('flex'); + } + + window.saveSettings = function() { + const theme = document.getElementById('settings-theme').value; + const fontSize = document.getElementById('settings-fontSize').value; + + editor.setOption('theme', theme); + editor.setOption('fontSize', fontSize + 'px'); + + localStorage.setItem('editorTheme', theme); + localStorage.setItem('editorFontSize', fontSize); + + closeSettings(); + } + + // Close user menu when clicking outside + document.addEventListener('click', function(event) { + const menu = document.getElementById('user-menu'); + const btn = document.getElementById('user-menu-btn'); + if (menu && !menu.contains(event.target) && !btn.contains(event.target)) { + menu.classList.add('hidden'); + } + }); + + // Keyboard shortcuts + document.addEventListener('keydown', function(e) { + // Cmd/Ctrl + K = New Chat + if ((e.metaKey || e.ctrlKey) && e.key === 'k') { + e.preventDefault(); + newChat(); + } + // Escape = Close modals + if (e.key === 'Escape') { + closeProfile(); + closeSettings(); + document.getElementById('user-menu').classList.add('hidden'); + } + }); </script> </body>