index.html (105788B)
1 <!DOCTYPE html> 2 <html lang="en" class="dark"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> 6 <title>Ada AI</title> 7 <link rel="icon" type="image/png" href="logo.png"> 8 9 <!-- Tailwind CSS --> 10 <script src="https://cdn.tailwindcss.com"></script> 11 <script> 12 tailwind.config = { 13 darkMode: 'class', 14 theme: { 15 extend: { 16 colors: { 17 gemini: { 18 bg: '#131314', // Deepest charcoal 19 surface: '#1E1F20', // Sidebar/Panels 20 surfaceHover: '#28292A', 21 accent: '#444746', // Borders/Icons 22 text: '#E3E3E3', // Primary text 23 textSecondary: '#C4C7C5', 24 blue: '#A8C7FA', // Google Blue 25 purple: '#D0BCFF' // Google Purple 26 } 27 }, 28 fontFamily: { 29 sans: ['Inter', 'sans-serif'], 30 mono: ['JetBrains Mono', 'monospace'], 31 }, 32 animation: { 33 'fade-in': 'fadeIn 0.4s ease-out forwards', 34 'slide-up': 'slideUp 0.5s cubic-bezier(0.2, 0.0, 0, 1.0) forwards', 35 'spin-slow-entry': 'spinSlowEntry 1.2s cubic-bezier(0.34, 1.56, 0.64, 1) forwards', 36 'shimmer': 'shimmer 2s linear infinite', 37 }, 38 keyframes: { 39 fadeIn: { 40 '0%': { opacity: '0' }, 41 '100%': { opacity: '1' }, 42 }, 43 slideUp: { 44 '0%': { opacity: '0', transform: 'translateY(20px)' }, 45 '100%': { opacity: '1', transform: 'translateY(0)' }, 46 }, 47 spinSlowEntry: { 48 '0%': { opacity: '0', transform: 'rotate(-180deg) scale(0.5)' }, 49 '100%': { opacity: '1', transform: 'rotate(0deg) scale(1)' } 50 }, 51 shimmer: { 52 '0%': { backgroundPosition: '-1000px 0' }, 53 '100%': { backgroundPosition: '1000px 0' } 54 } 55 } 56 } 57 } 58 } 59 </script> 60 61 <!-- Google Fonts --> 62 <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"> 63 64 <!-- Icons --> 65 <script src="https://unpkg.com/lucide@latest"></script> 66 <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> 67 68 <!-- CodeMirror (Editor) --> 69 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css"> 70 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/theme/dracula.min.css"> 71 <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script> 72 <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/javascript/javascript.min.js"></script> 73 <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/python/python.min.js"></script> 74 <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/xml/xml.min.js"></script> 75 <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/css/css.min.js"></script> 76 77 <!-- Firebase SDKs --> 78 <script src="https://www.gstatic.com/firebasejs/10.7.1/firebase-app-compat.js"></script> 79 <script src="https://www.gstatic.com/firebasejs/10.7.1/firebase-auth-compat.js"></script> 80 <script src="https://www.gstatic.com/firebasejs/10.7.1/firebase-firestore-compat.js"></script> 81 82 <style> 83 body { 84 background-color: #131314; 85 color: #E3E3E3; 86 font-family: 'Inter', sans-serif; 87 overflow: hidden; 88 -webkit-font-smoothing: antialiased; 89 } 90 91 /* Custom Scrollbar */ 92 ::-webkit-scrollbar { width: 6px; height: 6px; } 93 ::-webkit-scrollbar-track { background: transparent; } 94 ::-webkit-scrollbar-thumb { background: #444746; border-radius: 3px; } 95 ::-webkit-scrollbar-thumb:hover { background: #5E5E5E; } 96 97 /* Prose / Markdown Styles */ 98 .prose p { margin-bottom: 0.8rem; line-height: 1.6; color: #E3E3E3; font-weight: 300; } 99 .prose strong { color: #fff; font-weight: 600; } 100 .prose ul { list-style-type: disc; padding-left: 1.5rem; margin-bottom: 1rem; } 101 .prose ol { list-style-type: decimal; padding-left: 1.5rem; margin-bottom: 1rem; } 102 .prose code { 103 font-family: 'JetBrains Mono', monospace; 104 background: #28292A; 105 padding: 2px 6px; 106 border-radius: 4px; 107 color: #A8C7FA; 108 font-size: 0.85em; 109 } 110 .prose pre { display: none; } /* Hidden in chat, shown in Artifact Panel */ 111 .prose h1, .prose h2, .prose h3 { margin-top: 1.5rem; margin-bottom: 0.5rem; font-weight: 500; color: white; } 112 .prose a { color: #A8C7FA; text-decoration: none; } 113 .prose a:hover { text-decoration: underline; } 114 115 /* Message Bubbles */ 116 .msg-user { 117 background-color: #28292A; 118 color: #E3E3E3; 119 border-radius: 20px; 120 padding: 12px 20px; 121 max-width: 80%; 122 font-weight: 400; 123 } 124 .msg-ai { 125 background: transparent; 126 color: #E3E3E3; 127 padding: 0; 128 max-width: 100%; 129 } 130 131 /* Code Block Card in Chat */ 132 .code-card { 133 background: #1E1F20; 134 border: 1px solid #444746; 135 border-radius: 12px; 136 overflow: hidden; 137 margin: 1rem 0; 138 transition: all 0.2s; 139 cursor: pointer; 140 position: relative; 141 } 142 .code-card:hover { 143 border-color: #A8C7FA; 144 transform: translateY(-1px); 145 } 146 147 /* CodeMirror Customization */ 148 .CodeMirror { 149 height: 100% !important; 150 font-family: 'JetBrains Mono', monospace; 151 font-size: 14px; 152 background-color: #1e1e1e !important; 153 } 154 .cm-s-dracula.CodeMirror { 155 background-color: #1e1e1e !important; 156 } 157 158 /* Typing Indicator */ 159 .typing-dot { 160 width: 6px; height: 6px; 161 background: #E3E3E3; 162 border-radius: 50%; 163 animation: bounce 1.4s infinite ease-in-out both; 164 } 165 .typing-dot:nth-child(1) { animation-delay: -0.32s; } 166 .typing-dot:nth-child(2) { animation-delay: -0.16s; } 167 @keyframes bounce { 0%, 80%, 100% { transform: scale(0); } 40% { transform: scale(1); } } 168 169 /* Mobile Height Fix */ 170 .mobile-height { height: 100vh; height: calc(var(--vh, 1vh) * 100); } 171 172 /* Hide Scrollbar for Inputs */ 173 .no-scrollbar::-webkit-scrollbar { display: none; } 174 .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; } 175 176 /* Drawer Transitions */ 177 .drawer { transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); } 178 179 /* Custom Ada Icon Animation */ 180 .ada-icon path { transform-origin: center; } 181 182 /* Shimmer Effect for Skeletons */ 183 .skeleton-shimmer { 184 background: #1E1F20; 185 background-image: linear-gradient(to right, #1E1F20 0%, #28292A 20%, #1E1F20 40%, #1E1F20 100%); 186 background-repeat: no-repeat; 187 background-size: 1000px 100%; 188 animation: shimmer 1.5s infinite linear forwards; 189 } 190 191 /* Context Menu */ 192 .context-menu { 193 position: fixed; /* Changed from absolute to fixed for better mobile handling */ 194 background: #28292A; 195 border: 1px solid #444746; 196 border-radius: 12px; /* Slightly more rounded */ 197 padding: 4px; 198 z-index: 9999; 199 box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5), 0 8px 10px -6px rgba(0, 0, 0, 0.1); 200 min-width: 180px; 201 animation: menuFadeIn 0.15s ease-out; 202 } 203 @keyframes menuFadeIn { 204 from { opacity: 0; transform: scale(0.95); } 205 to { opacity: 1; transform: scale(1); } 206 } 207 .context-menu-item { 208 display: flex; 209 align-items: center; 210 gap: 12px; 211 padding: 12px 16px; /* Larger tap targets for mobile */ 212 cursor: pointer; 213 border-radius: 8px; 214 color: #E3E3E3; 215 font-size: 14px; 216 transition: background 0.2s; 217 } 218 .context-menu-item:hover { 219 background: #3C4043; 220 } 221 .context-menu-item.danger { color: #ff8b8b; } 222 .context-menu-item.danger:hover { background: rgba(255, 139, 139, 0.1); } 223 224 /* Custom Switch (From Uiverse.io by Galahhad) */ 225 .switch { 226 /* switch */ 227 --switch-width: 46px; 228 --switch-height: 24px; 229 --switch-bg: rgb(68, 71, 70); 230 --switch-checked-bg: #A8C7FA; 231 --switch-offset: calc((var(--switch-height) - var(--circle-diameter)) / 2); 232 --switch-transition: all .2s cubic-bezier(0.27, 0.2, 0.25, 1.51); 233 /* circle */ 234 --circle-diameter: 18px; 235 --circle-bg: #fff; 236 --circle-shadow: 1px 1px 2px rgba(146, 146, 146, 0.45); 237 --circle-checked-shadow: -1px 1px 2px rgba(163, 163, 163, 0.45); 238 --circle-transition: var(--switch-transition); 239 /* icon */ 240 --icon-transition: all .2s cubic-bezier(0.27, 0.2, 0.25, 1.51); 241 --icon-cross-color: var(--switch-bg); 242 --icon-cross-size: 6px; 243 --icon-checkmark-color: var(--switch-checked-bg); 244 --icon-checkmark-size: 10px; 245 /* effect line */ 246 --effect-width: calc(var(--circle-diameter) / 2); 247 --effect-height: calc(var(--effect-width) / 2 - 1px); 248 --effect-bg: var(--circle-bg); 249 --effect-border-radius: 1px; 250 --effect-transition: all .2s ease-in-out; 251 252 display: inline-block; 253 position: relative; 254 } 255 256 .switch input { display: none; } 257 258 .switch svg { 259 -webkit-transition: var(--icon-transition); 260 -o-transition: var(--icon-transition); 261 transition: var(--icon-transition); 262 position: absolute; 263 height: auto; 264 } 265 266 .switch .checkmark { 267 width: var(--icon-checkmark-size); 268 color: var(--icon-checkmark-color); 269 -webkit-transform: scale(0); 270 -ms-transform: scale(0); 271 transform: scale(0); 272 } 273 274 .switch .cross { 275 width: var(--icon-cross-size); 276 color: var(--icon-cross-color); 277 } 278 279 .slider { 280 -webkit-box-sizing: border-box; 281 box-sizing: border-box; 282 width: var(--switch-width); 283 height: var(--switch-height); 284 background: var(--switch-bg); 285 border-radius: 999px; 286 display: -webkit-box; 287 display: -ms-flexbox; 288 display: flex; 289 -webkit-box-align: center; 290 -ms-flex-align: center; 291 align-items: center; 292 position: relative; 293 -webkit-transition: var(--switch-transition); 294 -o-transition: var(--switch-transition); 295 transition: var(--switch-transition); 296 cursor: pointer; 297 } 298 299 .circle { 300 width: var(--circle-diameter); 301 height: var(--circle-diameter); 302 background: var(--circle-bg); 303 border-radius: inherit; 304 -webkit-box-shadow: var(--circle-shadow); 305 box-shadow: var(--circle-shadow); 306 display: -webkit-box; 307 display: -ms-flexbox; 308 display: flex; 309 -webkit-box-align: center; 310 -ms-flex-align: center; 311 align-items: center; 312 -webkit-box-pack: center; 313 -ms-flex-pack: center; 314 justify-content: center; 315 -webkit-transition: var(--circle-transition); 316 -o-transition: var(--circle-transition); 317 transition: var(--circle-transition); 318 z-index: 1; 319 position: absolute; 320 left: var(--switch-offset); 321 } 322 323 .slider::before { 324 content: ""; 325 position: absolute; 326 width: var(--effect-width); 327 height: var(--effect-height); 328 left: calc(var(--switch-offset) + (var(--effect-width) / 2)); 329 background: var(--effect-bg); 330 border-radius: var(--effect-border-radius); 331 -webkit-transition: var(--effect-transition); 332 -o-transition: var(--effect-transition); 333 transition: var(--effect-transition); 334 } 335 336 .switch input:checked+.slider { 337 background: var(--switch-checked-bg); 338 } 339 340 .switch input:checked+.slider .checkmark { 341 -webkit-transform: scale(1); 342 -ms-transform: scale(1); 343 transform: scale(1); 344 } 345 346 .switch input:checked+.slider .cross { 347 -webkit-transform: scale(0); 348 -ms-transform: scale(0); 349 transform: scale(0); 350 } 351 352 .switch input:checked+.slider::before { 353 left: calc(100% - var(--effect-width) - (var(--effect-width) / 2) - var(--switch-offset)); 354 } 355 356 .switch input:checked+.slider .circle { 357 left: calc(100% - var(--circle-diameter) - var(--switch-offset)); 358 -webkit-box-shadow: var(--circle-checked-shadow); 359 box-shadow: var(--circle-checked-shadow); 360 } 361 362 /* Command Palette */ 363 #command-palette { 364 transition: opacity 0.15s ease-out; 365 } 366 .cmd-item { 367 display: flex; 368 align-items: center; 369 justify-content: space-between; 370 padding: 10px 16px; 371 cursor: pointer; 372 border-radius: 8px; 373 color: #E3E3E3; 374 } 375 .cmd-item:hover, .cmd-item.selected { 376 background: #28292A; 377 } 378 .cmd-shortcut { 379 font-size: 10px; 380 background: #3C4043; 381 padding: 2px 6px; 382 border-radius: 4px; 383 color: #C4C7C5; 384 font-family: sans-serif; 385 } 386 </style> 387 </head> 388 <body class="mobile-height flex text-sm selection:bg-gemini-blue selection:text-black bg-gemini-bg"> 389 390 <!-- SVG Definitions --> 391 <svg style="display: none;"> 392 <symbol id="icon-ada" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> 393 <defs> 394 <linearGradient id="ada-gradient" x1="0" y1="0" x2="24" y2="24" gradientUnits="userSpaceOnUse"> 395 <stop offset="0%" stop-color="#4facfe" /> 396 <stop offset="50%" stop-color="#00f2fe" /> 397 <stop offset="100%" stop-color="#a8c7fa" /> 398 </linearGradient> 399 </defs> 400 <path d="M12 2L14.4 9.6L22 12L14.4 14.4L12 22L9.6 14.4L2 12L9.6 9.6L12 2Z" fill="url(#ada-gradient)" /> 401 <path d="M12 6L13.2 10.8L18 12L13.2 13.2L12 18L10.8 13.2L6 12L10.8 10.8L12 6Z" fill="white" fill-opacity="0.3" /> 402 </symbol> 403 </svg> 404 405 <!-- Full Screen Loading/Verifying Overlay --> 406 <div id="loading-overlay" class="fixed inset-0 z-[200] flex flex-col items-center justify-center bg-[#131314] transition-opacity duration-500"> 407 <div class="w-16 h-16 mb-4 relative"> 408 <div class="absolute inset-0 bg-blue-500/20 blur-xl rounded-full"></div> 409 <svg class="w-full h-full animate-spin-slow-entry"><use href="#icon-ada"></use></svg> 410 </div> 411 <p class="text-gemini-textSecondary text-sm font-medium animate-pulse">Verifying Identity...</p> 412 </div> 413 414 <!-- Command Palette --> 415 <div id="command-palette" class="fixed inset-0 z-[150] bg-black/50 backdrop-blur-sm hidden flex items-start justify-center pt-24 opacity-0 pointer-events-none"> 416 <div class="bg-gemini-surface w-full max-w-lg rounded-xl shadow-2xl border border-gemini-surfaceHover overflow-hidden flex flex-col max-h-[60vh]"> 417 <div class="p-4 border-b border-gemini-surfaceHover flex items-center gap-3"> 418 <i data-lucide="search" class="w-5 h-5 text-gemini-textSecondary"></i> 419 <input type="text" id="cmd-input" class="bg-transparent border-none outline-none text-white w-full placeholder-gemini-textSecondary/50" placeholder="Type a command..."> 420 <span class="text-xs text-gemini-textSecondary border border-gemini-surfaceHover px-1.5 py-0.5 rounded">ESC</span> 421 </div> 422 <div id="cmd-list" class="p-2 overflow-y-auto"> 423 <!-- Commands injected via JS --> 424 </div> 425 </div> 426 </div> 427 428 <!-- Confirm Dialog --> 429 <div id="confirm-modal" class="fixed inset-0 z-[210] flex items-center justify-center bg-black/70 backdrop-blur-sm hidden opacity-0 transition-opacity"> 430 <div class="bg-gemini-surface p-6 rounded-2xl w-full max-w-sm border border-gemini-surfaceHover transform scale-95 transition-transform" id="confirm-modal-content"> 431 <h3 class="text-lg font-semibold text-white mb-2" id="confirm-title">Confirm</h3> 432 <p class="text-gemini-textSecondary text-sm mb-4" id="confirm-message">Are you sure?</p> 433 434 <div id="confirm-input-container" class="hidden mb-4"> 435 <label class="block text-xs text-gemini-textSecondary mb-1">Type <span class="font-mono text-white select-all" id="confirm-match-text"></span> to confirm:</label> 436 <input type="text" id="confirm-input" class="w-full bg-[#131314] border border-gemini-surfaceHover rounded-lg p-2 text-white text-sm focus:outline-none focus:border-gemini-blue" placeholder=""> 437 </div> 438 439 <div class="flex justify-end gap-3"> 440 <button onclick="closeConfirmModal()" class="px-4 py-2 rounded-lg text-gemini-textSecondary hover:text-white hover:bg-gemini-surfaceHover transition-colors">Cancel</button> 441 <button id="confirm-action-btn" class="px-4 py-2 rounded-lg bg-red-500/10 text-red-400 hover:bg-red-500/20 transition-colors font-medium">Confirm</button> 442 </div> 443 </div> 444 </div> 445 446 <!-- Toast Notification (Custom Popups) --> 447 <div id="toast" class="fixed bottom-24 md:bottom-10 left-1/2 -translate-x-1/2 bg-gemini-surface border border-gemini-surfaceHover text-white px-6 py-3 rounded-full shadow-2xl z-[220] transition-all duration-300 transform translate-y-20 opacity-0 pointer-events-none flex items-center gap-2 max-w-[90vw] w-max"> 448 <i data-lucide="info" class="w-4 h-4 text-gemini-blue flex-shrink-0"></i> 449 <span id="toast-message" class="text-sm font-medium whitespace-normal text-center">Notification</span> 450 </div> 451 452 <!-- Auth Modal (Updated for Email/Pass + Verification) --> 453 <div id="auth-modal" class="fixed inset-0 z-[100] flex items-center justify-center bg-[#131314] transition-opacity duration-300 hidden opacity-0 pointer-events-none"> 454 <div class="w-full max-w-md p-8 text-center bg-[#131314] rounded-2xl md:bg-[#131314] md:border-none relative max-h-[90vh] overflow-y-auto"> 455 <div class="mx-auto w-20 h-20 mb-6 rounded-full bg-gemini-surface flex items-center justify-center relative overflow-hidden group"> 456 <div class="absolute inset-0 bg-gradient-to-tr from-blue-500/20 to-purple-500/20 opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div> 457 <svg class="w-10 h-10 animate-spin-slow-entry drop-shadow-lg"><use href="#icon-ada"></use></svg> 458 </div> 459 460 <h1 id="auth-title" class="text-3xl font-medium text-white mb-2">Welcome to Ada</h1> 461 <p id="auth-subtitle" class="text-gemini-textSecondary mb-8 text-sm">Sign in to access your workspace.</p> 462 463 <form id="auth-form" onsubmit="handleAuthSubmit(event)" class="space-y-4 text-left"> 464 <input type="email" id="auth-email" placeholder="Email" autocomplete="email" required class="w-full bg-gemini-surface border border-gemini-surfaceHover rounded-xl p-3 text-white focus:outline-none focus:border-gemini-blue placeholder-gemini-textSecondary"> 465 466 <div class="relative"> 467 <input type="password" id="auth-password" placeholder="Password" autocomplete="current-password" required class="w-full bg-gemini-surface border border-gemini-surfaceHover rounded-xl p-3 pr-10 text-white focus:outline-none focus:border-gemini-blue placeholder-gemini-textSecondary"> 468 <button type="button" onclick="togglePasswordVisibility()" class="absolute right-3 top-1/2 -translate-y-1/2 text-gemini-textSecondary hover:text-white focus:outline-none"> 469 <i id="password-toggle-icon" data-lucide="eye" class="w-5 h-5"></i> 470 </button> 471 </div> 472 473 <div id="auth-feedback" class="hidden text-center text-sm mt-2"></div> 474 475 <div id="signin-extras" class="flex justify-end"> 476 <button type="button" onclick="handleForgotPassword()" class="text-xs text-gemini-textSecondary hover:text-gemini-blue hover:underline">Forgot Password?</button> 477 </div> 478 479 <button type="submit" id="auth-submit-btn" class="w-full bg-gemini-blue text-black font-semibold py-3 rounded-xl hover:bg-white transition-colors"> 480 Sign In 481 </button> 482 </form> 483 484 <div class="mt-6"> 485 <button type="button" id="toggle-auth-mode" onclick="toggleAuthMode()" class="text-sm text-gemini-blue hover:underline"> 486 Don't have an account? Create one 487 </button> 488 </div> 489 490 <div class="relative my-8"> 491 <div class="absolute inset-0 flex items-center"><div class="w-full border-t border-gemini-surfaceHover"></div></div> 492 <span class="relative px-3 bg-[#131314] text-xs text-gemini-textSecondary uppercase tracking-widest">Or</span> 493 </div> 494 495 <div class="space-y-3"> 496 <button onclick="signInWithGoogle()" class="w-full bg-white text-[#131314] hover:bg-gray-100 font-medium py-3 px-6 rounded-full flex items-center justify-center gap-3 transition-colors"> 497 <img src="https://www.google.com/favicon.ico" class="w-5 h-5" alt="G"> 498 <span>Sign In with Google</span> 499 </button> 500 <button onclick="signInAsGuest()" class="w-full bg-[#3C4043] text-white hover:bg-[#494c50] font-medium py-3 px-6 rounded-full flex items-center justify-center gap-3 transition-colors"> 501 <i data-lucide="user" class="w-5 h-5"></i> 502 <span>Continue as Guest</span> 503 </button> 504 </div> 505 </div> 506 </div> 507 508 <!-- Profile Modal --> 509 <div id="profile-modal" onclick="toggleProfileModal()" class="fixed inset-0 z-[110] flex items-center justify-center bg-black/60 backdrop-blur-sm transition-opacity duration-300 hidden opacity-0 pointer-events-none"> 510 <div onclick="event.stopPropagation()" class="w-full max-w-sm bg-gemini-surface p-6 rounded-2xl shadow-2xl border border-gemini-surfaceHover transform scale-95 transition-transform duration-300" id="profile-modal-content"> 511 <div class="flex flex-col items-center"> 512 <div class="w-20 h-20 rounded-full bg-gradient-to-tr from-blue-500 to-purple-500 flex items-center justify-center text-2xl font-bold text-white mb-4 overflow-hidden border-4 border-gemini-surfaceHover relative shadow-lg"> 513 <img id="profile-avatar" class="w-full h-full object-cover hidden" src="" alt="User"> 514 <span id="profile-initial">U</span> 515 </div> 516 <h2 id="profile-name" class="text-xl font-semibold text-white">User Name</h2> 517 <p id="profile-email" class="text-sm text-gemini-textSecondary mb-6 break-all text-center">user@example.com</p> 518 519 <div class="w-full grid grid-cols-2 gap-3 mb-6"> 520 <div class="bg-gemini-bg p-3 rounded-xl border border-gemini-surfaceHover text-center"> 521 <span class="block text-xs text-gemini-textSecondary uppercase tracking-wider mb-1">Status</span> 522 <span class="text-sm text-white font-medium">Free</span> 523 </div> 524 <div class="bg-gemini-bg p-3 rounded-xl border border-gemini-surfaceHover text-center"> 525 <span class="block text-xs text-gemini-textSecondary uppercase tracking-wider mb-1">Joined</span> 526 <span class="text-sm text-white font-medium">Jan '26</span> 527 </div> 528 </div> 529 530 <button onclick="confirmSignOut()" class="w-full bg-[#3C4043] hover:bg-[#494c50] text-white py-2.5 rounded-full flex items-center justify-center gap-2 transition-colors font-medium"> 531 <i data-lucide="log-out" class="w-4 h-4"></i> 532 Sign Out 533 </button> 534 </div> 535 </div> 536 </div> 537 538 <!-- Settings Modal --> 539 <div id="settings-modal" onclick="toggleSettingsModal()" class="fixed inset-0 z-[110] flex items-center justify-center bg-black/60 backdrop-blur-sm transition-opacity duration-300 hidden opacity-0 pointer-events-none"> 540 <div onclick="event.stopPropagation()" class="w-full max-w-md bg-gemini-surface p-6 rounded-2xl shadow-2xl border border-gemini-surfaceHover transform scale-95 transition-transform duration-300 max-h-[85vh] overflow-y-auto" id="settings-modal-content"> 541 <div class="flex items-center justify-between mb-6 border-b border-gemini-surfaceHover pb-4"> 542 <h2 class="text-xl font-semibold text-white">Settings</h2> 543 <button onclick="toggleSettingsModal()" class="text-gemini-textSecondary hover:text-white p-1 rounded-full hover:bg-gemini-surfaceHover transition-colors"> 544 <i data-lucide="x" class="w-5 h-5"></i> 545 </button> 546 </div> 547 548 <div class="space-y-2"> 549 <!-- Chat Sharing --> 550 <div class="p-3 hover:bg-gemini-surfaceHover rounded-xl transition-colors cursor-pointer flex items-center justify-between group"> 551 <div class="flex items-center gap-3"> 552 <div class="p-2 bg-purple-500/10 rounded-lg text-purple-400 group-hover:bg-purple-500/20"><i data-lucide="share-2" class="w-4 h-4"></i></div> 553 <div> 554 <h3 class="text-white text-sm font-medium">Chat Sharing</h3> 555 <p class="text-xs text-gemini-textSecondary">Allow public links</p> 556 </div> 557 </div> 558 <!-- Custom Toggle UI --> 559 <label class="switch"> 560 <input type="checkbox" id="share-toggle-checkbox" onchange="toggleShareSetting(this)"> 561 <div class="slider"> 562 <div class="circle"> 563 <svg class="cross" xml:space="preserve" style="enable-background:new 0 0 512 512" viewBox="0 0 365.696 365.696" y="0" x="0" height="6" width="6" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" xmlns="http://www.w3.org/2000/svg"> 564 <g> 565 <path data-original="#000000" fill="currentColor" d="M243.188 182.86 356.32 69.726c12.5-12.5 12.5-32.766 0-45.247L341.238 9.398c-12.504-12.503-32.77-12.503-45.25 0L182.86 122.528 69.727 9.374c-12.5-12.5-32.766-12.5-45.247 0L9.375 24.457c-12.5 12.504-12.5 32.77 0 45.25l113.152 113.152L9.398 295.99c-12.503 12.503-12.503 32.769 0 45.25L24.48 356.32c12.5 12.5 32.766 12.5 45.247 0l113.132-113.132L295.99 356.32c12.503 12.5 32.769 12.5 45.25 0l15.081-15.082c12.5-12.504 12.5-32.77 0-45.25zm0 0"></path> 566 </g> 567 </svg> 568 <svg class="checkmark" xml:space="preserve" style="enable-background:new 0 0 512 512" viewBox="0 0 24 24" y="0" x="0" height="10" width="10" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" xmlns="http://www.w3.org/2000/svg"> 569 <g> 570 <path class="" data-original="#000000" fill="currentColor" d="M9.707 19.121a.997.997 0 0 1-1.414 0l-5.646-5.647a1.5 1.5 0 0 1 0-2.121l.707-.707a1.5 1.5 0 0 1 2.121 0L9 14.171l9.525-9.525a1.5 1.5 0 0 1 2.121 0l.707.707a1.5 1.5 0 0 1 0 2.121z"></path> 571 </g> 572 </svg> 573 </div> 574 </div> 575 </label> 576 </div> 577 578 <!-- Font Size --> 579 <div class="p-3 hover:bg-gemini-surfaceHover rounded-xl transition-colors flex items-center justify-between group"> 580 <div class="flex items-center gap-3"> 581 <div class="p-2 bg-gray-500/10 rounded-lg text-gray-400 group-hover:bg-gray-500/20"><i data-lucide="type" class="w-4 h-4"></i></div> 582 <div> 583 <h3 class="text-white text-sm font-medium">Font Size</h3> 584 <p class="text-xs text-gemini-textSecondary">Adjust readability</p> 585 </div> 586 </div> 587 <div class="flex items-center gap-2 bg-gemini-bg p-1 rounded-lg border border-gemini-surfaceHover"> 588 <button onclick="changeFontSize(-1)" class="p-1 hover:bg-gemini-surfaceHover rounded text-gemini-textSecondary hover:text-white"><i data-lucide="minus" class="w-3 h-3"></i></button> 589 <span id="font-size-display" class="text-xs font-mono w-4 text-center">14</span> 590 <button onclick="changeFontSize(1)" class="p-1 hover:bg-gemini-surfaceHover rounded text-gemini-textSecondary hover:text-white"><i data-lucide="plus" class="w-3 h-3"></i></button> 591 </div> 592 </div> 593 594 <div class="h-px bg-gemini-surfaceHover my-2"></div> 595 596 <!-- Help & Support --> 597 <a href="https://github.com/notamitgamer/ada-web/issues" target="_blank" class="p-3 hover:bg-gemini-surfaceHover rounded-xl transition-colors cursor-pointer flex items-center gap-3 group"> 598 <div class="p-2 bg-green-500/10 rounded-lg text-green-400 group-hover:bg-green-500/20"><i data-lucide="help-circle" class="w-4 h-4"></i></div> 599 <div> 600 <h3 class="text-white text-sm font-medium">Help & Support</h3> 601 <p class="text-xs text-gemini-textSecondary">Report bugs or request features</p> 602 </div> 603 <i data-lucide="external-link" class="w-4 h-4 text-gemini-textSecondary ml-auto"></i> 604 </a> 605 606 <!-- Contribute --> 607 <a href="https://github.com/notamitgamer/ada-web" target="_blank" class="p-3 hover:bg-gemini-surfaceHover rounded-xl transition-colors cursor-pointer flex items-center gap-3 group"> 608 <div class="p-2 bg-orange-500/10 rounded-lg text-orange-400 group-hover:bg-orange-500/20"><i data-lucide="git-branch" class="w-4 h-4"></i></div> 609 <div> 610 <h3 class="text-white text-sm font-medium">Contribute</h3> 611 <p class="text-xs text-gemini-textSecondary">Fork repo & submit PRs</p> 612 </div> 613 <i data-lucide="external-link" class="w-4 h-4 text-gemini-textSecondary ml-auto"></i> 614 </a> 615 616 <!-- Developer Contact --> 617 <a href="mailto:amitdutta4255@gmail.com?subject=Ada%20AI%20Critical%20Support" class="p-3 hover:bg-gemini-surfaceHover rounded-xl transition-colors cursor-pointer flex items-center gap-3 group"> 618 <div class="p-2 bg-red-500/10 rounded-lg text-red-400 group-hover:bg-red-500/20"><i data-lucide="alert-circle" class="w-4 h-4"></i></div> 619 <div> 620 <h3 class="text-white text-sm font-medium">Critical Support</h3> 621 <p class="text-xs text-gemini-textSecondary">Contact developer directly</p> 622 </div> 623 </a> 624 625 <!-- General Feedback --> 626 <a href="mailto:mail@amit.is-a.dev?subject=Ada%20AI%20Feedback" class="p-3 hover:bg-gemini-surfaceHover rounded-xl transition-colors cursor-pointer flex items-center gap-3 group"> 627 <div class="p-2 bg-yellow-500/10 rounded-lg text-yellow-400 group-hover:bg-yellow-500/20"><i data-lucide="message-square" class="w-4 h-4"></i></div> 628 <div> 629 <h3 class="text-white text-sm font-medium">Send Feedback</h3> 630 <p class="text-xs text-gemini-textSecondary">Suggestions & improvements</p> 631 </div> 632 </a> 633 634 <div class="h-px bg-gemini-surfaceHover my-2"></div> 635 636 <!-- Terms --> 637 <a href="/terms.html" class="p-3 hover:bg-gemini-surfaceHover rounded-xl transition-colors cursor-pointer flex items-center gap-3 group"> 638 <div class="p-2 bg-gray-500/10 rounded-lg text-gray-400 group-hover:bg-gray-500/20"><i data-lucide="file-text" class="w-4 h-4"></i></div> 639 <div> 640 <h3 class="text-white text-sm font-medium">Terms & Conditions</h3> 641 <p class="text-xs text-gemini-textSecondary">Usage policies</p> 642 </div> 643 </a> 644 <!-- Docs --> 645 <a href="/docs.html" class="p-3 hover:bg-gemini-surfaceHover rounded-xl transition-colors cursor-pointer flex items-center gap-3 group"> 646 <div class="p-2 bg-gray-500/10 rounded-lg text-gray-400 group-hover:bg-gray-500/20"><i data-lucide="file-text" class="w-4 h-4"></i></div> 647 <div> 648 <h3 class="text-white text-sm font-medium">Docs</h3> 649 <p class="text-xs text-gemini-textSecondary">Service Documentation</p> 650 </div> 651 </a> 652 653 <!-- Delete History --> 654 <button onclick="confirmClearHistory()" class="w-full p-3 hover:bg-red-500/10 rounded-xl transition-colors cursor-pointer flex items-center gap-3 group text-left"> 655 <div class="p-2 bg-red-500/10 rounded-lg text-red-400 group-hover:bg-red-500/20"><i data-lucide="trash-2" class="w-4 h-4"></i></div> 656 <div> 657 <h3 class="text-red-400 text-sm font-medium">Clear Activity</h3> 658 <p class="text-xs text-gemini-textSecondary">Remove local session data</p> 659 </div> 660 </button> 661 662 <p class="text-center text-xs text-gemini-textSecondary pt-4 font-mono">Ada AI v1.4.0 • <a href="https://github.com/notamitgamer" class="hover:text-white underline">@notamitgamer</a></p> 663 </div> 664 </div> 665 </div> 666 667 <!-- Sidebar (Collapsible Drawer on ALL screens) --> 668 <aside id="sidebar" class="bg-gemini-surface w-[280px] flex-shrink-0 flex flex-col border-r border-gemini-surfaceHover fixed inset-y-0 left-0 transform -translate-x-full z-[60] drawer shadow-2xl"> 669 <!-- Sidebar Header --> 670 <div class="h-16 flex items-center justify-between px-4"> 671 <button onclick="toggleSidebar()" class="p-2 text-gemini-textSecondary hover:text-white hover:bg-gemini-surfaceHover rounded-full transition-colors"> 672 <i data-lucide="menu" class="w-5 h-5"></i> 673 </button> 674 <span id="history-label" class="text-gemini-textSecondary text-xs font-medium uppercase tracking-wider">History</span> 675 676 <div id="history-search-container" class="relative w-full max-w-[140px] ml-4"> 677 <input type="text" id="history-search" placeholder="Search..." oninput="filterHistory(this.value)" class="w-full bg-[#131314] text-xs text-white border border-gemini-surfaceHover rounded-md px-2 py-1 focus:outline-none focus:border-gemini-blue"> 678 <i data-lucide="search" class="absolute right-2 top-1.5 w-3 h-3 text-gemini-textSecondary"></i> 679 </div> 680 </div> 681 682 <!-- New Chat Button --> 683 <div class="px-3 mb-2"> 684 <button onclick="startNewChat()" class="w-full bg-gemini-surfaceHover hover:bg-[#333537] text-gemini-textSecondary hover:text-white py-3 px-4 rounded-full flex items-center gap-3 transition-colors text-sm font-medium"> 685 <i data-lucide="plus" class="w-5 h-5"></i> 686 <span>New chat</span> 687 </button> 688 </div> 689 690 <!-- Chat History List --> 691 <div id="chat-history-list" class="flex-1 overflow-y-auto px-2 py-2 space-y-1"> 692 <!-- Skeleton Loader injected via JS --> 693 </div> 694 695 <!-- User Profile (Bottom) --> 696 <div class="p-4 border-t border-gemini-surfaceHover space-y-1"> 697 <button onclick="toggleSettingsModal()" class="w-full flex items-center gap-3 p-2 hover:bg-gemini-surfaceHover rounded-lg transition-colors text-left text-gemini-textSecondary hover:text-white"> 698 <i data-lucide="settings" class="w-4 h-4"></i> 699 <span class="text-sm font-medium">Settings</span> 700 </button> 701 702 <button onclick="toggleProfileModal()" class="w-full flex items-center gap-3 p-2 hover:bg-gemini-surfaceHover rounded-lg transition-colors text-left group mt-1"> 703 <div class="w-7 h-7 rounded-full bg-gradient-to-tr from-blue-500 to-purple-500 flex items-center justify-center text-[10px] font-bold text-white overflow-hidden"> 704 <img id="user-avatar" class="w-full h-full object-cover hidden" src="" alt="User"> 705 <span id="user-initial">U</span> 706 </div> 707 <div class="flex-1 min-w-0"> 708 <p id="user-email" class="text-xs text-gemini-textSecondary truncate">Not signed in</p> 709 </div> 710 </button> 711 </div> 712 </aside> 713 714 <!-- Overlay for Sidebar --> 715 <div id="sidebar-overlay" onclick="toggleSidebar()" class="fixed inset-0 bg-black/50 z-[55] hidden backdrop-blur-sm transition-opacity"></div> 716 717 <!-- Main Content Area --> 718 <main class="flex-1 flex flex-col relative w-full h-full min-w-0 bg-gemini-bg"> 719 720 <!-- Top Bar (Fixed) --> 721 <header class="h-16 flex items-center justify-between px-4 md:px-6 sticky top-0 z-30 bg-gemini-bg/80 backdrop-blur-md border-b border-transparent transition-colors"> 722 <div class="flex items-center gap-2"> 723 <button onclick="toggleSidebar()" class="p-2 -ml-2 text-gemini-textSecondary hover:text-white hover:bg-gemini-surfaceHover rounded-full transition-colors" title="Toggle History"> 724 <i data-lucide="menu" class="w-6 h-6"></i> 725 </button> 726 <span class="text-lg font-medium text-white tracking-tight flex items-center gap-2"> 727 <span class="flex items-center gap-2"> 728 Ada <span class="text-gemini-blue text-xs font-normal border border-gemini-surfaceHover px-2 py-0.5 rounded-full bg-gemini-surface">Beta</span> 729 </span> 730 </span> 731 </div> 732 733 <!-- Code Viewer Toggle & Login Trigger for Shared View --> 734 <div class="flex items-center gap-2"> 735 <button id="shared-login-btn" onclick="signInWithGoogle()" class="hidden px-4 py-2 bg-white text-black rounded-full text-sm font-medium hover:bg-gray-200 transition-colors"> 736 Sign In to Save 737 </button> 738 739 <button onclick="toggleArtifactPanel()" class="p-2 text-gemini-textSecondary hover:text-gemini-blue relative rounded-full hover:bg-gemini-surfaceHover transition-colors" title="Toggle Code Viewer"> 740 <i data-lucide="panel-right" class="w-6 h-6"></i> 741 <span id="artifact-indicator" class="absolute top-2 right-2 w-2.5 h-2.5 bg-gemini-blue border-2 border-gemini-bg rounded-full hidden shadow-glow"></span> 742 </button> 743 </div> 744 </header> 745 746 <!-- Chat Container --> 747 <div id="chat-container" class="flex-1 overflow-y-auto px-4 md:px-0 pb-32 pt-4 scroll-smooth"> 748 <div class="max-w-3xl mx-auto flex flex-col gap-6"> 749 <!-- Welcome Message --> 750 <div id="welcome-message" class="flex flex-col items-start justify-center min-h-[50vh] space-y-8 animate-slide-up px-2"> 751 <div class="space-y-2 w-full"> 752 <div class="w-12 h-12 mb-4 animate-spin-slow-entry"> 753 <svg class="w-full h-full drop-shadow-[0_0_15px_rgba(168,199,250,0.5)]"><use href="#icon-ada"></use></svg> 754 </div> 755 <h1 class="text-4xl md:text-5xl font-medium text-transparent bg-clip-text bg-gradient-to-r from-[#4285F4] via-[#9B72CB] to-[#D96570] pb-2"> 756 Hello, <span id="welcome-name" class="text-white truncate block md:inline max-w-[200px] md:max-w-none">Student</span> 757 </h1> 758 <!-- UPDATED COLOR --> 759 <h2 class="text-3xl md:text-5xl font-medium text-gemini-textSecondary/30">How can I help you code today?</h2> 760 </div> 761 762 <!-- Suggestion Chips (Hidden on Mobile) --> 763 <div class="hidden md:flex flex-wrap gap-3 w-full" id="suggestion-chips"> 764 <button onclick="setPrompt('Debug this Python script')" class="group bg-gemini-surface hover:bg-gemini-surfaceHover text-gemini-text px-5 py-4 rounded-2xl text-sm font-medium transition-all text-left flex-1 min-w-[200px] border border-transparent hover:border-gemini-surfaceHover"> 765 <span class="block mb-1 text-gemini-blue group-hover:translate-x-1 transition-transform"><i data-lucide="bug" class="w-4 h-4 inline mr-1"></i> Debug</span> 766 Find errors in code 767 </button> 768 <button onclick="setPrompt('Explain this concept')" class="group bg-gemini-surface hover:bg-gemini-surfaceHover text-gemini-text px-5 py-4 rounded-2xl text-sm font-medium transition-all text-left flex-1 min-w-[200px] border border-transparent hover:border-gemini-surfaceHover"> 769 <span class="block mb-1 text-gemini-purple group-hover:translate-x-1 transition-transform"><i data-lucide="book-open" class="w-4 h-4 inline mr-1"></i> Explain</span> 770 Understand algorithms 771 </button> 772 <button onclick="setPrompt('Refactor this function')" class="group bg-gemini-surface hover:bg-gemini-surfaceHover text-gemini-text px-5 py-4 rounded-2xl text-sm font-medium transition-all text-left flex-1 min-w-[200px] border border-transparent hover:border-gemini-surfaceHover"> 773 <span class="block mb-1 text-green-400 group-hover:translate-x-1 transition-transform"><i data-lucide="wand-2" class="w-4 h-4 inline mr-1"></i> Refactor</span> 774 Optimize performance 775 </button> 776 </div> 777 </div> 778 </div> 779 </div> 780 781 <!-- Input Area (Fixed Bottom) --> 782 <div class="absolute bottom-0 left-0 right-0 p-4 bg-gradient-to-t from-gemini-bg via-gemini-bg to-transparent z-20" id="input-area"> 783 <div class="max-w-3xl mx-auto"> 784 <form id="chat-form" onsubmit="handleFormSubmit(event)" class="bg-gemini-surface rounded-[28px] flex items-end p-2 border border-gemini-surfaceHover focus-within:bg-gemini-surfaceHover focus-within:border-gemini-textSecondary/20 transition-all shadow-xl"> 785 <!-- ADDED: transition-all duration-200 for smooth growth --> 786 <textarea 787 id="user-input" 788 class="w-full bg-transparent text-white placeholder-gemini-textSecondary py-3.5 px-4 max-h-48 min-h-[52px] resize-none focus:outline-none text-base no-scrollbar transition-all duration-200 ease-in-out" 789 placeholder="Enter a prompt here" 790 rows="1" 791 oninput="autoResize(this)" 792 onkeydown="handleEnter(event)" 793 enterkeyhint="send" 794 ></textarea> 795 796 <button type="submit" id="send-btn" class="p-3 text-gemini-textSecondary hover:text-white disabled:opacity-50 disabled:cursor-not-allowed transition-colors rounded-full"> 797 <i data-lucide="send-horizontal" class="w-6 h-6"></i> 798 </button> 799 </form> 800 <div class="text-center mt-3 text-xs text-gemini-textSecondary/60"> 801 Ada may display inaccurate info, including about people, so double-check its responses. 802 </div> 803 </div> 804 </div> 805 </main> 806 807 <!-- Right Artifact Panel (Drawer on ALL screens) --> 808 <aside id="artifact-panel" class="fixed inset-y-0 right-0 z-[70] w-full md:w-[600px] bg-[#1e1e1e] border-l border-[#333] transform translate-x-full drawer shadow-2xl flex flex-col"> 809 <!-- Panel Header --> 810 <div class="h-14 flex-shrink-0 flex items-center justify-between px-4 bg-[#252526] border-b border-[#333]"> 811 <div class="flex items-center gap-3"> 812 <span class="text-xs uppercase tracking-wide text-[#969696] font-medium">Workspace</span> 813 <span id="lang-display" class="text-xs text-[#cccccc] bg-[#3c3c3c] px-2 py-0.5 rounded">PLAINTEXT</span> 814 </div> 815 <div class="flex gap-2"> 816 <button onclick="copyArtifact()" class="p-2 text-[#cccccc] hover:text-white hover:bg-[#3c3c3c] rounded transition-colors" title="Copy Code"> 817 <i data-lucide="copy" class="w-4 h-4"></i> 818 </button> 819 <button onclick="toggleArtifactPanel()" class="p-2 text-[#cccccc] hover:text-white hover:bg-[#3c3c3c] rounded transition-colors" title="Close"> 820 <i data-lucide="x" class="w-5 h-5"></i> 821 </button> 822 </div> 823 </div> 824 825 <!-- Editor Content --> 826 <div class="flex-1 overflow-hidden relative bg-[#1e1e1e]"> 827 <textarea id="artifact-editor" class="w-full h-full"></textarea> 828 </div> 829 830 <!-- ADDED: Status Bar --> 831 <div class="h-6 bg-[#007acc] text-white flex items-center px-3 text-xs justify-end gap-4 select-none"> 832 <div class="flex items-center gap-1"> 833 <i data-lucide="code" class="w-3 h-3"></i> 834 <span id="status-lang">JavaScript</span> 835 </div> 836 <div class="flex items-center gap-1"> 837 <span id="status-cursor">Ln 1, Col 1</span> 838 </div> 839 <div>UTF-8</div> 840 </div> 841 </aside> 842 843 <script> 844 // --- CONFIGURATION --- 845 const API_BASE_URL = "https://ada-web.onrender.com/api"; 846 847 const firebaseConfig = { 848 apiKey: "AIzaSyAhyoNYUSXWIxdBiTxwSDcU7HkhhLEVlOc", 849 authDomain: "ada-ai-aranag.firebaseapp.com", 850 projectId: "ada-ai-aranag", 851 storageBucket: "ada-ai-aranag.firebasestorage.app", 852 messagingSenderId: "865895114061", 853 appId: "1:865895114061:web:5fd4493b0e388727e6c304" 854 }; 855 856 // --- STATE & INIT --- 857 let auth, db; 858 let isSignUpMode = false; 859 let state = { 860 messages: [], 861 isTyping: false, 862 currentChatId: null, 863 user: null, 864 isSharedView: false 865 }; 866 let editor = null; 867 let settings = { 868 allowSharing: localStorage.getItem('ada_allow_sharing') === 'true', 869 fontSize: parseInt(localStorage.getItem('ada_font_size')) || 14 870 }; 871 872 // --- COMMAND PALETTE DATA --- 873 const commands = [ 874 { name: "New Chat", icon: "plus", action: startNewChat, shortcut: "N" }, 875 { name: "Toggle Sidebar", icon: "sidebar", action: toggleSidebar, shortcut: "Ctrl+/" }, 876 { name: "Clear History", icon: "trash-2", action: confirmClearHistory, shortcut: "" }, 877 { name: "Settings", icon: "settings", action: toggleSettingsModal, shortcut: "" }, 878 { name: "Sign Out", icon: "log-out", action: confirmSignOut, shortcut: "" } 879 ]; 880 881 function initApp() { 882 lucide.createIcons(); 883 884 // Set initial settings 885 applyFontSize(settings.fontSize); 886 887 const shareToggle = document.getElementById('share-toggle-checkbox'); 888 if(shareToggle) shareToggle.checked = settings.allowSharing; 889 890 // Check for Shared URL param 891 const urlParams = new URLSearchParams(window.location.search); 892 const sharedChatId = urlParams.get('chat'); 893 if (sharedChatId) { 894 state.isSharedView = true; 895 state.currentChatId = sharedChatId; 896 enterSharedMode(sharedChatId); 897 } 898 899 // Initialize CodeMirror (Standard setup) 900 editor = CodeMirror.fromTextArea(document.getElementById("artifact-editor"), { 901 lineNumbers: true, 902 mode: "javascript", 903 theme: "dracula", 904 readOnly: true, // Read-only as it's an output viewer primarily 905 lineWrapping: true 906 }); 907 908 // ADDED: Cursor Activity Listener for Status Bar 909 editor.on("cursorActivity", () => { 910 const pos = editor.getCursor(); 911 document.getElementById('status-cursor').innerText = `Ln ${pos.line + 1}, Col ${pos.ch + 1}`; 912 }); 913 914 // Initialize Firebase 915 try { 916 firebase.initializeApp(firebaseConfig); 917 auth = firebase.auth(); 918 db = firebase.firestore(); 919 if (!state.isSharedView) checkAuth(); 920 else checkAuthShared(); 921 } catch (e) { 922 console.error("Init Failed:", e); 923 document.getElementById('loading-overlay').classList.add('opacity-0', 'pointer-events-none'); 924 } 925 926 // Mobile Height Fix 927 const setVh = () => { 928 let vh = window.innerHeight * 0.01; 929 document.documentElement.style.setProperty('--vh', `${vh}px`); 930 }; 931 window.addEventListener('resize', setVh); 932 setVh(); 933 934 // Context Menu Listener 935 document.addEventListener('click', (e) => { 936 const menu = document.querySelector('.context-menu'); 937 if (menu) menu.remove(); 938 }); 939 940 // ADDED: Global Keyboard Shortcuts 941 document.addEventListener('keydown', handleGlobalKeydown); 942 943 // ADDED: Swipe Gestures 944 initSwipeGestures(); 945 } 946 947 function autoResize(textarea) { 948 textarea.style.height = 'auto'; 949 textarea.style.height = Math.min(textarea.scrollHeight, 200) + 'px'; 950 } 951 952 // ADDED: Swipe Gesture Logic 953 function initSwipeGestures() { 954 let touchStartX = 0; 955 let touchStartY = 0; 956 957 document.addEventListener('touchstart', e => { 958 touchStartX = e.changedTouches[0].screenX; 959 touchStartY = e.changedTouches[0].screenY; 960 }, {passive: true}); 961 962 document.addEventListener('touchend', e => { 963 const touchEndX = e.changedTouches[0].screenX; 964 const touchEndY = e.changedTouches[0].screenY; 965 966 handleSwipe(touchStartX, touchStartY, touchEndX, touchEndY); 967 }, {passive: true}); 968 } 969 970 function handleSwipe(startX, startY, endX, endY) { 971 const diffX = endX - startX; 972 const diffY = endY - startY; 973 974 // Only trigger if horizontal swipe is dominant and significant 975 if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 50) { 976 const sidebar = document.getElementById('sidebar'); 977 const isClosed = sidebar.classList.contains('-translate-x-full'); 978 979 // Swipe Right (Open) - Only works from left edge area (first 30px) 980 if (diffX > 0 && startX < 30 && isClosed) { 981 toggleSidebar(); 982 } 983 984 // Swipe Left (Close) 985 if (diffX < 0 && !isClosed) { 986 toggleSidebar(); 987 } 988 } 989 } 990 991 // ADDED: Keyboard Shortcuts Handler 992 function handleGlobalKeydown(e) { 993 // Toggle Sidebar: Ctrl + / (or Cmd + /) 994 if ((e.ctrlKey || e.metaKey) && e.key === '/') { 995 e.preventDefault(); 996 toggleSidebar(); 997 } 998 999 // Command Palette: Ctrl + K (or Cmd + K) 1000 if ((e.ctrlKey || e.metaKey) && e.key === 'k') { 1001 e.preventDefault(); 1002 toggleCommandPalette(); 1003 } 1004 1005 // Close Palette on ESC 1006 if (e.key === 'Escape') { 1007 const cmd = document.getElementById('command-palette'); 1008 if (!cmd.classList.contains('hidden')) { 1009 toggleCommandPalette(); 1010 } 1011 } 1012 } 1013 1014 // ADDED: Command Palette Logic 1015 function toggleCommandPalette() { 1016 const palette = document.getElementById('command-palette'); 1017 const input = document.getElementById('cmd-input'); 1018 const list = document.getElementById('cmd-list'); 1019 1020 if (palette.classList.contains('hidden')) { 1021 // Show 1022 palette.classList.remove('hidden', 'pointer-events-none'); 1023 requestAnimationFrame(() => palette.classList.remove('opacity-0')); 1024 input.value = ''; 1025 input.focus(); 1026 renderCommands(commands); 1027 } else { 1028 // Hide 1029 palette.classList.add('opacity-0'); 1030 setTimeout(() => palette.classList.add('hidden', 'pointer-events-none'), 150); 1031 } 1032 1033 input.oninput = (e) => { 1034 const val = e.target.value.toLowerCase(); 1035 const filtered = commands.filter(c => c.name.toLowerCase().includes(val)); 1036 renderCommands(filtered); 1037 }; 1038 } 1039 1040 function renderCommands(cmds) { 1041 const list = document.getElementById('cmd-list'); 1042 list.innerHTML = ''; 1043 1044 if (cmds.length === 0) { 1045 list.innerHTML = `<div class="text-gemini-textSecondary text-xs p-3 text-center">No commands found</div>`; 1046 return; 1047 } 1048 1049 cmds.forEach(cmd => { 1050 const div = document.createElement('div'); 1051 div.className = 'cmd-item group'; 1052 div.onclick = () => { 1053 cmd.action(); 1054 toggleCommandPalette(); 1055 }; 1056 div.innerHTML = ` 1057 <div class="flex items-center gap-3"> 1058 <i data-lucide="${cmd.icon}" class="w-4 h-4 text-gemini-textSecondary group-hover:text-white"></i> 1059 <span class="text-sm">${cmd.name}</span> 1060 </div> 1061 ${cmd.shortcut ? `<span class="cmd-shortcut">${cmd.shortcut}</span>` : ''} 1062 `; 1063 list.appendChild(div); 1064 }); 1065 lucide.createIcons(); 1066 } 1067 1068 // --- SETTINGS --- 1069 function changeFontSize(delta) { 1070 settings.fontSize = Math.max(10, Math.min(24, settings.fontSize + delta)); 1071 localStorage.setItem('ada_font_size', settings.fontSize); 1072 applyFontSize(settings.fontSize); 1073 } 1074 1075 function applyFontSize(size) { 1076 document.getElementById('font-size-display').innerText = size; 1077 if(editor) { 1078 editor.getWrapperElement().style.fontSize = `${size}px`; 1079 editor.refresh(); 1080 } 1081 } 1082 1083 // --- SHARED MODE LOGIC --- 1084 async function enterSharedMode(chatId) { 1085 document.getElementById('sidebar').classList.add('hidden'); 1086 const inputArea = document.getElementById('input-area'); 1087 const sidebarToggle = document.querySelector('header button[title="Toggle History"]'); 1088 1089 inputArea.classList.add('hidden'); 1090 if(sidebarToggle) sidebarToggle.style.display = 'none'; 1091 } 1092 1093 function checkAuthShared() { 1094 auth.onAuthStateChanged(user => { 1095 const loadingOverlay = document.getElementById('loading-overlay'); 1096 loadingOverlay.classList.add('opacity-0', 'pointer-events-none'); 1097 1098 if (user) { 1099 state.user = user; 1100 document.getElementById('sidebar').classList.remove('hidden'); 1101 document.getElementById('input-area').classList.remove('hidden'); 1102 const sidebarToggle = document.querySelector('header button[title="Toggle History"]'); 1103 if(sidebarToggle) sidebarToggle.style.display = 'block'; 1104 document.getElementById('shared-login-btn').classList.add('hidden'); 1105 1106 fetchChats(); 1107 loadChat(state.currentChatId); 1108 } else { 1109 state.user = null; 1110 document.getElementById('shared-login-btn').classList.remove('hidden'); 1111 loadChatReadOnly(state.currentChatId); 1112 } 1113 }); 1114 } 1115 1116 async function loadChatReadOnly(chatId) { 1117 const container = document.querySelector('#chat-container > div'); 1118 container.innerHTML = ` 1119 <div class="flex flex-col items-center justify-center min-h-[50vh] text-center p-6"> 1120 <div class="p-4 bg-gemini-surface rounded-2xl border border-gemini-surfaceHover mb-4"> 1121 <i data-lucide="lock" class="w-8 h-8 text-gemini-textSecondary mx-auto mb-2"></i> 1122 <p class="text-white font-medium">Shared Chat</p> 1123 </div> 1124 <p class="text-gemini-textSecondary max-w-md">To view and continue this conversation, please sign in. The chat will be saved to your history.</p> 1125 </div> 1126 `; 1127 lucide.createIcons(); 1128 } 1129 1130 // --- UI TOGGLES & HELPERS --- 1131 let toastTimeout; 1132 1133 function showToast(msg, type = 'info') { 1134 // Check for Mobile + Auth Modal Open case 1135 const authModal = document.getElementById('auth-modal'); 1136 const isMobile = window.innerWidth < 768; // Tailwind md breakpoint 1137 const isAuthOpen = !authModal.classList.contains('hidden') && !authModal.classList.contains('opacity-0'); 1138 1139 if (isMobile && isAuthOpen) { 1140 const feedback = document.getElementById('auth-feedback'); 1141 feedback.innerText = msg; 1142 1143 // Style based on type 1144 if (type === 'error') { 1145 feedback.className = 'text-center text-sm mt-3 p-3 rounded-lg bg-red-500/10 text-red-400 border border-red-500/20'; 1146 } else if (type === 'success') { 1147 feedback.className = 'text-center text-sm mt-3 p-3 rounded-lg bg-green-500/10 text-green-400 border border-green-500/20'; 1148 } else { 1149 feedback.className = 'text-center text-sm mt-3 p-3 rounded-lg bg-blue-500/10 text-blue-400 border border-blue-500/20'; 1150 } 1151 1152 feedback.classList.remove('hidden'); 1153 1154 // Auto-hide inline feedback after 6s 1155 if (toastTimeout) clearTimeout(toastTimeout); 1156 toastTimeout = setTimeout(() => { 1157 feedback.classList.add('hidden'); 1158 }, 6000); 1159 return; 1160 } 1161 1162 // Standard Toast Logic (PC or Auth closed) 1163 const t = document.getElementById('toast'); 1164 1165 if (toastTimeout) clearTimeout(toastTimeout); 1166 1167 // 1. Determine icon and style based on type 1168 let iconName = 'info'; 1169 let iconColor = 'text-gemini-blue'; 1170 let containerClass = 'bg-gemini-surface border-gemini-accent text-white'; 1171 1172 if (type === 'error') { 1173 iconName = 'alert-circle'; 1174 iconColor = 'text-red-400'; 1175 containerClass = 'bg-red-500/10 border-red-500/20 text-red-400'; 1176 } else if (type === 'success') { 1177 iconName = 'check-circle'; 1178 iconColor = 'text-green-400'; 1179 } 1180 1181 // 2. Completely rebuild innerHTML to ensure we have a fresh <i> tag for Lucide 1182 t.innerHTML = `<i data-lucide="${iconName}" class="w-4 h-4 ${iconColor} flex-shrink-0"></i><span id="toast-message" class="text-sm font-medium whitespace-normal text-center">${msg}</span>`; 1183 1184 // 3. Set container classes and visibility 1185 t.className = `fixed bottom-24 md:bottom-10 left-1/2 -translate-x-1/2 border px-6 py-3 rounded-full shadow-2xl z-[220] transition-all duration-300 transform translate-y-0 opacity-100 flex items-center gap-2 max-w-[90vw] w-max ${containerClass}`; 1186 1187 // 4. Render icons 1188 lucide.createIcons(); 1189 1190 // 5. Auto-hide 1191 toastTimeout = setTimeout(() => { 1192 t.classList.remove('opacity-100', 'translate-y-0'); 1193 t.classList.add('opacity-0', 'translate-y-20', 'pointer-events-none'); 1194 }, 3000); 1195 } 1196 1197 function togglePasswordVisibility() { 1198 const input = document.getElementById('auth-password'); 1199 const icon = document.getElementById('password-toggle-icon'); 1200 1201 if (input.type === 'password') { 1202 input.type = 'text'; 1203 icon.setAttribute('data-lucide', 'eye-off'); 1204 } else { 1205 input.type = 'password'; 1206 icon.setAttribute('data-lucide', 'eye'); 1207 } 1208 lucide.createIcons(); 1209 } 1210 1211 function toggleShareSetting(checkbox) { 1212 settings.allowSharing = checkbox.checked; 1213 localStorage.setItem('ada_allow_sharing', settings.allowSharing); 1214 } 1215 1216 function toggleSidebar() { 1217 const sb = document.getElementById('sidebar'); 1218 const overlay = document.getElementById('sidebar-overlay'); 1219 const isClosed = sb.classList.contains('-translate-x-full'); 1220 1221 if (isClosed) { 1222 sb.classList.remove('-translate-x-full'); 1223 overlay.classList.remove('hidden'); 1224 } else { 1225 sb.classList.add('-translate-x-full'); 1226 overlay.classList.add('hidden'); 1227 } 1228 } 1229 1230 function toggleArtifactPanel() { 1231 const panel = document.getElementById('artifact-panel'); 1232 const isClosed = panel.classList.contains('translate-x-full'); 1233 1234 if (isClosed) { 1235 panel.classList.remove('translate-x-full'); 1236 document.getElementById('artifact-indicator').classList.add('hidden'); 1237 setTimeout(() => editor.refresh(), 300); 1238 } else { 1239 panel.classList.add('translate-x-full'); 1240 } 1241 } 1242 1243 function toggleProfileModal() { 1244 const modal = document.getElementById('profile-modal'); 1245 const content = document.getElementById('profile-modal-content'); 1246 1247 if (modal.classList.contains('hidden')) { 1248 modal.classList.remove('hidden', 'pointer-events-none'); 1249 requestAnimationFrame(() => { 1250 modal.classList.remove('opacity-0'); 1251 content.classList.remove('scale-95'); 1252 content.classList.add('scale-100'); 1253 }); 1254 1255 if (state.user) { 1256 // Handle Guest / User Display 1257 if (state.user.isAnonymous) { 1258 document.getElementById('profile-name').innerText = "Guest User"; 1259 document.getElementById('profile-email').innerText = "Anonymous Session"; 1260 document.getElementById('profile-initial').innerText = "G"; 1261 document.getElementById('profile-avatar').classList.add('hidden'); 1262 document.getElementById('profile-initial').classList.remove('hidden'); 1263 } else { 1264 document.getElementById('profile-name').innerText = state.user.displayName || 'User'; 1265 document.getElementById('profile-email').innerText = state.user.email || ''; 1266 const initial = document.getElementById('profile-initial'); 1267 const avatar = document.getElementById('profile-avatar'); 1268 1269 if (state.user.photoURL) { 1270 avatar.src = state.user.photoURL; 1271 avatar.classList.remove('hidden'); 1272 initial.classList.add('hidden'); 1273 } else { 1274 initial.innerText = (state.user.displayName || 'U')[0]; 1275 avatar.classList.add('hidden'); 1276 initial.classList.remove('hidden'); 1277 } 1278 } 1279 } 1280 } else { 1281 modal.classList.add('opacity-0'); 1282 content.classList.remove('scale-100'); 1283 content.classList.add('scale-95'); 1284 setTimeout(() => modal.classList.add('hidden', 'pointer-events-none'), 300); 1285 } 1286 } 1287 1288 function toggleSettingsModal() { 1289 const modal = document.getElementById('settings-modal'); 1290 const content = document.getElementById('settings-modal-content'); 1291 1292 if (modal.classList.contains('hidden')) { 1293 modal.classList.remove('hidden', 'pointer-events-none'); 1294 requestAnimationFrame(() => { 1295 modal.classList.remove('opacity-0'); 1296 content.classList.remove('scale-95'); 1297 content.classList.add('scale-100'); 1298 }); 1299 } else { 1300 modal.classList.add('opacity-0'); 1301 content.classList.remove('scale-100'); 1302 content.classList.add('scale-95'); 1303 setTimeout(() => modal.classList.add('hidden', 'pointer-events-none'), 300); 1304 } 1305 } 1306 1307 // --- FEATURES IMPLEMENTATION --- 1308 1309 // 1. History Search 1310 function filterHistory(query) { 1311 const list = document.getElementById('chat-history-list'); 1312 const items = list.querySelectorAll('div[onclick]'); 1313 query = query.toLowerCase(); 1314 1315 items.forEach(item => { 1316 const text = item.querySelector('span').innerText.toLowerCase(); 1317 if (text.includes(query)) { 1318 item.style.display = 'flex'; 1319 } else { 1320 item.style.display = 'none'; 1321 } 1322 }); 1323 } 1324 1325 // --- CONFIRM MODALS --- 1326 let confirmCallback = null; 1327 1328 function showConfirmModal(title, message, callback, matchText = null) { 1329 const modal = document.getElementById('confirm-modal'); 1330 const content = document.getElementById('confirm-modal-content'); 1331 1332 document.getElementById('confirm-title').innerText = title; 1333 document.getElementById('confirm-message').innerText = message; 1334 1335 const inputContainer = document.getElementById('confirm-input-container'); 1336 const input = document.getElementById('confirm-input'); 1337 const btn = document.getElementById('confirm-action-btn'); 1338 1339 if (matchText) { 1340 inputContainer.classList.remove('hidden'); 1341 document.getElementById('confirm-match-text').innerText = matchText; 1342 input.value = ''; 1343 input.oninput = () => { 1344 if (input.value === matchText) { 1345 btn.disabled = false; 1346 btn.classList.remove('opacity-50', 'cursor-not-allowed'); 1347 } else { 1348 btn.disabled = true; 1349 btn.classList.add('opacity-50', 'cursor-not-allowed'); 1350 } 1351 }; 1352 btn.disabled = true; 1353 btn.classList.add('opacity-50', 'cursor-not-allowed'); 1354 } else { 1355 inputContainer.classList.add('hidden'); 1356 btn.disabled = false; 1357 btn.classList.remove('opacity-50', 'cursor-not-allowed'); 1358 } 1359 1360 confirmCallback = callback; 1361 1362 modal.classList.remove('hidden'); 1363 requestAnimationFrame(() => { 1364 modal.classList.remove('opacity-0'); 1365 content.classList.remove('scale-95'); 1366 content.classList.add('scale-100'); 1367 }); 1368 1369 btn.onclick = () => { 1370 closeConfirmModal(); 1371 if (confirmCallback) confirmCallback(); 1372 }; 1373 } 1374 1375 function closeConfirmModal() { 1376 const modal = document.getElementById('confirm-modal'); 1377 const content = document.getElementById('confirm-modal-content'); 1378 modal.classList.add('opacity-0'); 1379 content.classList.remove('scale-100'); 1380 content.classList.add('scale-95'); 1381 setTimeout(() => modal.classList.add('hidden'), 300); 1382 } 1383 1384 function confirmSignOut() { 1385 toggleProfileModal(); 1386 showConfirmModal('Sign Out', 'Are you sure you want to sign out?', signOutUser); 1387 } 1388 1389 function confirmClearHistory() { 1390 toggleSettingsModal(); 1391 if (!state.user || !state.user.email) return; 1392 showConfirmModal( 1393 'Clear Activity', 1394 'This will permanently delete all your chat history. Type your email to confirm.', 1395 clearHistory, 1396 state.user.email 1397 ); 1398 } 1399 1400 async function clearHistory() { 1401 if (!state.user) return; 1402 const loading = document.getElementById('loading-overlay'); 1403 loading.classList.remove('opacity-0', 'pointer-events-none'); 1404 1405 try { 1406 // FIXED: Use correct path users/{uid}/chats 1407 const chatsRef = db.collection('users').doc(state.user.uid).collection('chats'); 1408 const snapshot = await chatsRef.get(); 1409 1410 const batch = db.batch(); 1411 snapshot.docs.forEach((doc) => { 1412 batch.delete(doc.ref); 1413 }); 1414 await batch.commit(); 1415 1416 location.reload(); 1417 } catch (error) { 1418 console.error("Error clearing history:", error); 1419 loading.classList.add('opacity-0', 'pointer-events-none'); 1420 showToast("Failed to clear history.", "error"); 1421 } 1422 } 1423 1424 function confirmDeleteChat(chatId) { 1425 showConfirmModal('Delete Chat', 'This action cannot be undone.', () => deleteChat(chatId)); 1426 } 1427 1428 async function deleteChat(chatId) { 1429 if (!state.user) return; 1430 try { 1431 // FIXED: Use correct path users/{uid}/chats/{chatId} 1432 await db.collection('users').doc(state.user.uid).collection('chats').doc(chatId).delete(); 1433 1434 if (state.currentChatId === chatId) { 1435 location.reload(); 1436 } else { 1437 fetchChats(); 1438 showToast("Chat deleted.", "success"); 1439 } 1440 } catch (e) { 1441 console.error("Delete Error:", e); 1442 showToast("Failed to delete chat.", "error"); 1443 } 1444 } 1445 1446 // --- CONTEXT MENU & EXPORT --- 1447 function showChatContextMenu(e, chatId, chatTitle) { 1448 e.preventDefault(); 1449 e.stopPropagation(); 1450 1451 const existing = document.querySelector('.context-menu'); 1452 if (existing) existing.remove(); 1453 1454 const menu = document.createElement('div'); 1455 menu.className = 'context-menu'; 1456 1457 // Share Option 1458 if (settings.allowSharing) { 1459 const shareBtn = document.createElement('div'); 1460 shareBtn.className = 'context-menu-item'; 1461 shareBtn.innerHTML = `<i data-lucide="link" class="w-4 h-4"></i> Share Chat`; 1462 shareBtn.onclick = async () => { 1463 const url = `${window.location.origin}?chat=${chatId}`; 1464 if (navigator.share) { 1465 try { 1466 await navigator.share({ title: 'Ada Chat', url: url }); 1467 } catch (err) { /* Share cancelled */ } 1468 } else { 1469 navigator.clipboard.writeText(url).then(() => showToast('Link copied!', 'success')); 1470 } 1471 menu.remove(); 1472 }; 1473 menu.appendChild(shareBtn); 1474 } 1475 1476 // Export Options (Expanded) 1477 const jsonBtn = document.createElement('div'); 1478 jsonBtn.className = 'context-menu-item'; 1479 jsonBtn.innerHTML = `<i data-lucide="file-json" class="w-4 h-4"></i> Export JSON`; 1480 jsonBtn.onclick = () => { 1481 downloadChat(chatId, chatTitle, 'json'); 1482 menu.remove(); 1483 }; 1484 menu.appendChild(jsonBtn); 1485 1486 const mdBtn = document.createElement('div'); 1487 mdBtn.className = 'context-menu-item'; 1488 mdBtn.innerHTML = `<i data-lucide="file-text" class="w-4 h-4"></i> Export Markdown`; 1489 mdBtn.onclick = () => { 1490 downloadChat(chatId, chatTitle, 'md'); 1491 menu.remove(); 1492 }; 1493 menu.appendChild(mdBtn); 1494 1495 // Delete Option 1496 const delBtn = document.createElement('div'); 1497 delBtn.className = 'context-menu-item danger'; 1498 delBtn.innerHTML = `<i data-lucide="trash-2" class="w-4 h-4"></i> Delete Chat`; 1499 delBtn.onclick = () => { 1500 confirmDeleteChat(chatId); 1501 menu.remove(); 1502 }; 1503 menu.appendChild(delBtn); 1504 1505 document.body.appendChild(menu); 1506 1507 // Position 1508 const rect = e.target.getBoundingClientRect(); 1509 const menuWidth = 180; 1510 const menuHeight = 200; 1511 1512 let left = rect.left; 1513 let top = rect.bottom + 5; 1514 1515 if (left + menuWidth > window.innerWidth) left = window.innerWidth - menuWidth - 16; 1516 if (left < 16) left = 16; 1517 if (top + menuHeight > window.innerHeight) top = rect.top - menuHeight - 5; 1518 1519 menu.style.top = `${top}px`; 1520 menu.style.left = `${left}px`; 1521 1522 lucide.createIcons(); 1523 } 1524 1525 async function downloadChat(chatId, title, format) { 1526 try { 1527 const token = await state.user.getIdToken(); 1528 const res = await fetch(`${API_BASE_URL}/chats/${chatId}`, { headers: { 'Authorization': `Bearer ${token}` } }); 1529 const data = await res.json(); 1530 1531 let content, type, ext; 1532 if (format === 'json') { 1533 content = JSON.stringify(data, null, 2); 1534 type = 'application/json'; 1535 ext = 'json'; 1536 } else { 1537 // Markdown conversion 1538 content = `# ${title}\n\n`; 1539 data.messages.forEach(msg => { 1540 content += `### ${msg.role === 'user' ? 'User' : 'Ada AI'}\n${msg.content}\n\n`; 1541 }); 1542 type = 'text/markdown'; 1543 ext = 'md'; 1544 } 1545 1546 const blob = new Blob([content], { type: type }); 1547 const url = URL.createObjectURL(blob); 1548 const a = document.createElement('a'); 1549 a.href = url; 1550 a.download = `ada_chat_${title.replace(/\s+/g, '_')}.${ext}`; 1551 document.body.appendChild(a); 1552 a.click(); 1553 document.body.removeChild(a); 1554 URL.revokeObjectURL(url); 1555 showToast("Chat exported.", "success"); 1556 } catch(e) { 1557 console.error(e); 1558 showToast("Export failed.", "error"); 1559 } 1560 } 1561 1562 // --- AUTH & FIREBASE --- 1563 async function checkAuth() { 1564 auth.onAuthStateChanged(async (user) => { 1565 const loadingOverlay = document.getElementById('loading-overlay'); 1566 const authModal = document.getElementById('auth-modal'); 1567 1568 if (user) { 1569 try { 1570 // Force refresh user to get latest emailVerified status 1571 await user.reload(); 1572 } catch (e) { 1573 console.log("Session reload failed, signing out."); 1574 auth.signOut(); 1575 return; 1576 } 1577 1578 const updatedUser = auth.currentUser; 1579 if (!updatedUser) return; 1580 1581 // Check if email is verified for email/password users 1582 const isEmailProvider = updatedUser.providerData.some(p => p.providerId === 'password'); 1583 if (isEmailProvider && !updatedUser.emailVerified) { 1584 showToast("Please verify your email. Check your inbox!", "error"); 1585 auth.signOut(); 1586 loadingOverlay.classList.add('opacity-0', 'pointer-events-none'); 1587 authModal.classList.remove('hidden', 'opacity-0', 'pointer-events-none'); 1588 return; 1589 } 1590 1591 state.user = updatedUser; 1592 localStorage.setItem('ada_user_cached', 'true'); 1593 1594 loadingOverlay.classList.add('opacity-0', 'pointer-events-none'); 1595 authModal.classList.add('hidden', 'opacity-0', 'pointer-events-none'); 1596 1597 // --- SIDEBAR VISIBILITY LOGIC (Guest vs User) --- 1598 const historyLabel = document.getElementById('history-label'); 1599 const historySearch = document.getElementById('history-search-container'); 1600 const historyList = document.getElementById('chat-history-list'); 1601 1602 if (updatedUser.isAnonymous) { 1603 // GUEST: Hide History 1604 if(historyLabel) historyLabel.classList.add('hidden'); 1605 if(historySearch) historySearch.classList.add('hidden'); 1606 if(historyList) historyList.classList.add('hidden'); 1607 1608 document.getElementById('user-email').innerText = 'Guest Mode'; 1609 document.getElementById('user-initial').innerText = 'G'; 1610 document.getElementById('user-initial').classList.remove('hidden'); 1611 document.getElementById('user-avatar').classList.add('hidden'); 1612 } else { 1613 // USER: Show History 1614 if(historyLabel) historyLabel.classList.remove('hidden'); 1615 if(historySearch) historySearch.classList.remove('hidden'); 1616 if(historyList) historyList.classList.remove('hidden'); 1617 1618 document.getElementById('user-email').innerText = updatedUser.email || ''; 1619 document.getElementById('user-initial').innerText = (updatedUser.displayName || 'U')[0]; 1620 1621 if (updatedUser.photoURL) { 1622 const img = document.getElementById('user-avatar'); 1623 img.src = updatedUser.photoURL; 1624 img.classList.remove('hidden'); 1625 document.getElementById('user-initial').classList.add('hidden'); 1626 } 1627 } 1628 1629 const welcomeName = document.getElementById('welcome-name'); 1630 if (welcomeName) { 1631 const name = updatedUser.isAnonymous ? 'Guest' : (updatedUser.email || updatedUser.displayName || 'Student'); 1632 welcomeName.innerText = name; 1633 welcomeName.title = name; 1634 } 1635 1636 if(!state.currentChatId) state.currentChatId = 'session_' + Date.now(); 1637 1638 // Only fetch history if not guest 1639 if (!updatedUser.isAnonymous) { 1640 fetchChats(); 1641 } 1642 } else { 1643 state.user = null; 1644 localStorage.removeItem('ada_user_cached'); 1645 loadingOverlay.classList.add('opacity-0', 'pointer-events-none'); 1646 authModal.classList.remove('hidden', 'opacity-0', 'pointer-events-none'); 1647 } 1648 }); 1649 } 1650 1651 // --- AUTH HANDLERS --- 1652 function toggleAuthMode() { 1653 isSignUpMode = !isSignUpMode; 1654 const title = document.getElementById('auth-title'); 1655 const subtitle = document.getElementById('auth-subtitle'); 1656 const btn = document.getElementById('auth-submit-btn'); 1657 const toggle = document.getElementById('toggle-auth-mode'); 1658 const extras = document.getElementById('signin-extras'); 1659 1660 // Hide feedback when switching modes 1661 document.getElementById('auth-feedback').classList.add('hidden'); 1662 1663 if (isSignUpMode) { 1664 title.innerText = "Create Account"; 1665 subtitle.innerText = "Join Ada to save your code history."; 1666 btn.innerText = "Register"; 1667 toggle.innerText = "Already have an account? Sign In"; 1668 extras.classList.add('hidden'); 1669 } else { 1670 title.innerText = "Welcome to Ada"; 1671 subtitle.innerText = "Sign in to access your workspace."; 1672 btn.innerText = "Sign In"; 1673 toggle.innerText = "Don't have an account? Create one"; 1674 extras.classList.remove('hidden'); 1675 } 1676 } 1677 1678 async function handleAuthSubmit(e) { 1679 e.preventDefault(); 1680 const email = document.getElementById('auth-email').value; 1681 const pass = document.getElementById('auth-password').value; 1682 const btn = document.getElementById('auth-submit-btn'); 1683 1684 // Hide previous feedback 1685 document.getElementById('auth-feedback').classList.add('hidden'); 1686 1687 if (!email || !pass) return; 1688 1689 try { 1690 btn.disabled = true; 1691 btn.innerText = isSignUpMode ? "Registering..." : "Signing in..."; 1692 1693 if (isSignUpMode) { 1694 // Create account 1695 const cred = await auth.createUserWithEmailAndPassword(email, pass); 1696 // Send verification email 1697 await cred.user.sendEmailVerification(); 1698 // Sign out immediately so they can't log in without verification 1699 await auth.signOut(); 1700 // Switch to Sign In mode FIRST so the toast can populate the correct feedback container 1701 toggleAuthMode(); 1702 showToast("Verification email sent! Please check your inbox and spam folder.", "success"); 1703 } else { 1704 // Sign in 1705 const cred = await auth.signInWithEmailAndPassword(email, pass); 1706 // Reload to check verification status handled in checkAuth 1707 } 1708 } catch (err) { 1709 showToast(err.message, "error"); 1710 } finally { 1711 btn.disabled = false; 1712 btn.innerText = isSignUpMode ? "Register" : "Sign In"; 1713 } 1714 } 1715 1716 async function handleForgotPassword() { 1717 const email = document.getElementById('auth-email').value; 1718 if (!email) { 1719 showToast("Enter your email first.", "error"); 1720 return; 1721 } 1722 try { 1723 await auth.sendPasswordResetEmail(email); 1724 showToast("Password reset link sent!", "success"); 1725 } catch (err) { 1726 showToast(err.message, "error"); 1727 } 1728 } 1729 1730 function signInWithGoogle() { 1731 const provider = new firebase.auth.GoogleAuthProvider(); 1732 auth.signInWithPopup(provider).catch(e => { 1733 console.error(e); 1734 showToast("Sign in failed.", "error"); 1735 }); 1736 } 1737 1738 function signInAsGuest() { 1739 auth.signInAnonymously().catch(e => { 1740 console.error(e); 1741 showToast("Guest login failed.", "error"); 1742 }); 1743 } 1744 1745 function signOutUser() { 1746 auth.signOut().then(() => { 1747 localStorage.removeItem('ada_user_cached'); 1748 location.reload(); 1749 }); 1750 } 1751 1752 // ADDED: Skeleton Loader Generator 1753 function renderSkeletonLoader() { 1754 const list = document.getElementById('chat-history-list'); 1755 list.innerHTML = ''; 1756 for(let i=0; i<5; i++) { 1757 list.innerHTML += ` 1758 <div class="flex items-center gap-3 p-2.5 rounded-full skeleton-shimmer opacity-50 mb-1"> 1759 <div class="w-4 h-4 bg-gemini-surfaceHover rounded-full"></div> 1760 <div class="h-4 bg-gemini-surfaceHover rounded w-24"></div> 1761 </div> 1762 `; 1763 } 1764 } 1765 1766 async function fetchChats() { 1767 if (!state.user || state.user.isAnonymous) return; 1768 try { 1769 // USE SKELETON LOADER 1770 renderSkeletonLoader(); 1771 1772 const token = await state.user.getIdToken(); 1773 const res = await fetch(`${API_BASE_URL}/chats`, { headers: { 'Authorization': `Bearer ${token}` } }); 1774 const data = await res.json(); 1775 1776 const list = document.getElementById('chat-history-list'); 1777 list.innerHTML = ''; 1778 1779 if (data.chats && data.chats.length > 0) { 1780 data.chats.forEach(chat => { 1781 const div = document.createElement('div'); 1782 const isActive = chat.id === state.currentChatId; 1783 div.className = `group relative p-2.5 rounded-full cursor-pointer text-sm flex items-center gap-3 transition-colors truncate ${isActive ? 'bg-[#004A77] text-white font-medium' : 'text-gemini-textSecondary hover:bg-gemini-surfaceHover hover:text-white'}`; 1784 div.onclick = () => loadChat(chat.id); 1785 1786 let pressTimer; 1787 div.ontouchstart = (e) => { 1788 pressTimer = setTimeout(() => showChatContextMenu(e, chat.id, chat.title), 500); 1789 }; 1790 div.ontouchend = () => clearTimeout(pressTimer); 1791 div.oncontextmenu = (e) => showChatContextMenu(e, chat.id, chat.title); 1792 1793 div.innerHTML = ` 1794 <i data-lucide="message-square" class="w-4 h-4 flex-shrink-0"></i> 1795 <span class="truncate flex-1">${chat.title}</span> 1796 <button class="opacity-0 group-hover:opacity-100 p-1 hover:bg-white/10 rounded-full transition-opacity" onclick="showChatContextMenu(event, '${chat.id}', '${chat.title}')"> 1797 <i data-lucide="more-vertical" class="w-3 h-3"></i> 1798 </button> 1799 `; 1800 list.appendChild(div); 1801 }); 1802 } else { 1803 list.innerHTML = `<div class="p-4 text-center text-xs text-gemini-textSecondary">No recent chats</div>`; 1804 } 1805 lucide.createIcons(); 1806 } catch (e) { console.error(e); } 1807 } 1808 1809 async function loadChat(chatId) { 1810 if (state.isTyping) return; 1811 state.currentChatId = chatId; 1812 1813 // Skeleton Loading for Main Chat 1814 const container = document.querySelector('#chat-container > div'); 1815 container.innerHTML = ` 1816 <div class="flex flex-col space-y-6 p-4"> 1817 <div class="flex justify-end"><div class="h-10 w-2/3 rounded-2xl skeleton-shimmer"></div></div> 1818 <div class="flex justify-start gap-4"> 1819 <div class="w-8 h-8 rounded-full skeleton-shimmer flex-shrink-0"></div> 1820 <div class="h-24 w-3/4 rounded-2xl skeleton-shimmer"></div> 1821 </div> 1822 <div class="flex justify-end"><div class="h-10 w-1/2 rounded-2xl skeleton-shimmer"></div></div> 1823 </div>`; 1824 1825 try { 1826 const token = await state.user.getIdToken(); 1827 const res = await fetch(`${API_BASE_URL}/chats/${chatId}`, { headers: { 'Authorization': `Bearer ${token}` } }); 1828 const data = await res.json(); 1829 1830 state.messages = []; 1831 container.innerHTML = ''; 1832 1833 if (data.messages && data.messages.length > 0) { 1834 data.messages.forEach(msg => { 1835 const role = msg.role === 'model' ? 'ai' : 'user'; 1836 state.messages.push({ role, content: msg.content }); 1837 renderMessage(role, msg.content); 1838 }); 1839 } else { 1840 container.innerHTML = `<div class="text-center p-8 text-gemini-textSecondary">Start conversation...</div>`; 1841 } 1842 1843 // Only refresh history if NOT shared view AND NOT guest 1844 if(!state.isSharedView && state.user && !state.user.isAnonymous) fetchChats(); 1845 } catch (e) { 1846 console.error(e); 1847 container.innerHTML = `<div class="text-center text-red-400 p-4">Failed to load chat.</div>`; 1848 } 1849 } 1850 1851 function setPrompt(text) { 1852 const input = document.getElementById('user-input'); 1853 if(input) { 1854 input.value = text; 1855 input.focus(); 1856 autoResize(input); 1857 } 1858 } 1859 1860 function scrollToBottom() { 1861 const container = document.getElementById('chat-container'); 1862 container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' }); 1863 } 1864 1865 function renderMessage(role, content) { 1866 const container = document.querySelector('#chat-container > div'); 1867 const welcome = document.getElementById('welcome-message'); 1868 if (welcome) welcome.remove(); 1869 1870 let wrapper = container.lastElementChild; 1871 let bubble = null; 1872 1873 if (role === 'ai' && wrapper && wrapper.dataset.role === 'ai' && state.isTyping) { 1874 bubble = wrapper.querySelector('.prose'); 1875 } else { 1876 wrapper = document.createElement('div'); 1877 wrapper.className = `flex gap-4 w-full animate-fade-in ${role === 'user' ? 'justify-end' : 'justify-start group'}`; 1878 wrapper.dataset.role = role; 1879 1880 if (role === 'user') { 1881 wrapper.innerHTML = `<div class="msg-user text-sm md:text-base">${content}</div>`; 1882 } else { 1883 wrapper.innerHTML = ` 1884 <div class="flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center mt-1"> 1885 <svg class="w-6 h-6 animate-spin-slow-entry"><use href="#icon-ada"></use></svg> 1886 </div> 1887 <div class="flex-1 min-w-0 py-1"> 1888 <div class="prose prose-invert max-w-none text-sm md:text-base"></div> 1889 </div> 1890 `; 1891 bubble = wrapper.querySelector('.prose'); 1892 } 1893 container.appendChild(wrapper); 1894 } 1895 1896 if (role === 'ai') { 1897 if(!bubble) bubble = wrapper.querySelector('.prose'); 1898 1899 const customTagRegex = /<<<CODE_START>>>([\s\S]*?)<<<CODE_END>>>/g; 1900 const markdownRegex = /```(\w*)([\s\S]*?)```/g; 1901 1902 let processedContent = content; 1903 let extractedCode = ""; 1904 let lang = "PLAINTEXT"; 1905 1906 const cardReplacement = (code, language) => { 1907 extractedCode = code.trim(); 1908 if(language) lang = language.toUpperCase(); 1909 return `<div class="code-card group" onclick="toggleArtifactPanel()"> 1910 <div class="px-4 py-3 bg-[#252526] border-b border-[#333] flex items-center justify-between"> 1911 <div class="flex items-center gap-2"> 1912 <i data-lucide="code-2" class="w-4 h-4 text-gemini-blue"></i> 1913 <span class="text-xs font-mono text-[#cccccc]">Generated Code</span> 1914 </div> 1915 <span class="text-xs text-gemini-blue group-hover:underline">View Code →</span> 1916 </div> 1917 <div class="px-4 py-3 text-xs font-mono text-gray-400 bg-[#1e1e1e]"> 1918 // Code extracted to workspace... 1919 </div> 1920 </div>`; 1921 }; 1922 1923 processedContent = processedContent.replace(customTagRegex, (m, c) => cardReplacement(c)); 1924 processedContent = processedContent.replace(markdownRegex, (m, l, c) => cardReplacement(c, l)); 1925 1926 try { 1927 bubble.innerHTML = marked.parse(processedContent); 1928 } catch (e) { 1929 bubble.innerText = content; 1930 } 1931 1932 if (extractedCode) { 1933 updateArtifactPanel(extractedCode, lang); 1934 const indicator = document.getElementById('artifact-indicator'); 1935 if(indicator) indicator.classList.remove('hidden'); 1936 1937 if (window.innerWidth >= 1280) { 1938 const panel = document.getElementById('artifact-panel'); 1939 if (panel.classList.contains('translate-x-full')) { 1940 toggleArtifactPanel(); 1941 } 1942 } 1943 } 1944 } 1945 1946 scrollToBottom(); 1947 lucide.createIcons(); 1948 } 1949 1950 function renderTypingIndicator() { 1951 const container = document.querySelector('#chat-container > div'); 1952 const wrapper = document.createElement('div'); 1953 wrapper.id = 'typing-indicator'; 1954 wrapper.className = 'flex gap-4 w-full justify-start animate-fade-in'; 1955 wrapper.innerHTML = ` 1956 <div class="flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center mt-1"> 1957 <svg class="w-6 h-6 animate-pulse"><use href="#icon-ada"></use></svg> 1958 </div> 1959 <div class="py-3"> 1960 <div class="flex gap-1"> 1961 <div class="typing-dot"></div> 1962 <div class="typing-dot"></div> 1963 <div class="typing-dot"></div> 1964 </div> 1965 </div> 1966 `; 1967 container.appendChild(wrapper); 1968 scrollToBottom(); 1969 lucide.createIcons(); 1970 } 1971 1972 function removeTypingIndicator() { 1973 const el = document.getElementById('typing-indicator'); 1974 if (el) el.remove(); 1975 } 1976 1977 function updateArtifactPanel(code, lang) { 1978 const langDisplay = document.getElementById('lang-display'); 1979 const statusLang = document.getElementById('status-lang'); // Update status bar 1980 if(lang) { 1981 langDisplay.innerText = lang; 1982 statusLang.innerText = lang; // Sync status bar 1983 const modeMap = { 1984 'PYTHON': 'python', 1985 'JAVASCRIPT': 'javascript', 1986 'JS': 'javascript', 1987 'HTML': 'xml', 1988 'CSS': 'css' 1989 }; 1990 if(editor) editor.setOption("mode", modeMap[lang] || "javascript"); 1991 } 1992 1993 if(editor) { 1994 editor.setValue(code); 1995 setTimeout(() => editor.refresh(), 100); 1996 } 1997 } 1998 1999 function copyArtifact() { 2000 if(editor) { 2001 const content = editor.getValue(); 2002 navigator.clipboard.writeText(content); 2003 showToast("Code copied to clipboard!", "success"); 2004 } 2005 } 2006 2007 function handleEnter(e) { 2008 if (e.key === 'Enter' && !e.shiftKey) { 2009 e.preventDefault(); 2010 handleFormSubmit(e); 2011 } 2012 } 2013 2014 async function handleFormSubmit(e) { 2015 if (e) e.preventDefault(); 2016 const input = document.getElementById('user-input'); 2017 const message = input.value.trim(); 2018 if (!message || state.isTyping || !state.user) return; 2019 2020 // UI Updates 2021 renderMessage('user', message); 2022 input.value = ''; 2023 autoResize(input); 2024 state.isTyping = true; 2025 renderTypingIndicator(); 2026 2027 try { 2028 const token = await state.user.getIdToken(); 2029 const response = await fetch(`${API_BASE_URL}/chat`, { 2030 method: 'POST', 2031 headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, 2032 body: JSON.stringify({ 2033 message: message, 2034 history: state.messages.map(m => ({ user: m.role === 'user' ? m.content : '', model: m.role === 'ai' ? m.content : '' })), 2035 sessionId: state.currentChatId 2036 }) 2037 }); 2038 2039 if (!response.ok) throw new Error("API Error"); 2040 2041 removeTypingIndicator(); 2042 const reader = response.body.getReader(); 2043 const decoder = new TextDecoder(); 2044 let fullText = ""; 2045 renderMessage('ai', ''); 2046 2047 while (true) { 2048 const { done, value } = await reader.read(); 2049 if (done) break; 2050 fullText += decoder.decode(value); 2051 renderMessage('ai', fullText); 2052 } 2053 2054 state.messages.push({ role: 'user', content: message }); 2055 state.messages.push({ role: 'ai', content: fullText }); 2056 2057 // Only refresh history if not guest 2058 if (!state.user.isAnonymous) { 2059 setTimeout(fetchChats, 1500); 2060 } 2061 2062 } catch (error) { 2063 removeTypingIndicator(); 2064 renderMessage('ai', "I'm having trouble connecting right now."); 2065 showToast("Error generating response", "error"); 2066 } finally { 2067 state.isTyping = false; 2068 } 2069 } 2070 2071 function startNewChat() { 2072 state.messages = []; 2073 state.currentChatId = 'session_' + Date.now(); 2074 2075 const userName = (state.user && !state.user.isAnonymous) ? (state.user.email || state.user.displayName || 'Student') : 'Guest'; 2076 2077 const container = document.querySelector('#chat-container > div'); 2078 container.innerHTML = ` 2079 <div id="welcome-message" class="flex flex-col items-start justify-center min-h-[50vh] space-y-8 animate-slide-up px-2"> 2080 <div class="space-y-2 w-full"> 2081 <div class="w-12 h-12 mb-4 animate-spin-slow-entry"> 2082 <svg class="w-full h-full drop-shadow-[0_0_15px_rgba(168,199,250,0.5)]"><use href="#icon-ada"></use></svg> 2083 </div> 2084 <h1 class="text-4xl md:text-5xl font-medium text-transparent bg-clip-text bg-gradient-to-r from-[#4285F4] via-[#9B72CB] to-[#D96570] pb-2"> 2085 Hello, <span id="welcome-name" class="text-white truncate block md:inline max-w-[200px] md:max-w-none">${userName}</span> 2086 </h1> 2087 <!-- UPDATED COLOR --> 2088 <h2 class="text-3xl md:text-5xl font-medium text-gemini-textSecondary/5">How can I help you code today?</h2> 2089 </div> 2090 <div class="hidden md:flex flex-wrap gap-3 w-full"> 2091 <button onclick="setPrompt('Debug this Python script')" class="group bg-gemini-surface hover:bg-gemini-surfaceHover text-gemini-text px-5 py-4 rounded-2xl text-sm font-medium transition-all text-left flex-1 min-w-[200px] border border-transparent hover:border-gemini-surfaceHover"> 2092 <span class="block mb-1 text-gemini-blue group-hover:translate-x-1 transition-transform"><i data-lucide="bug" class="w-4 h-4 inline mr-1"></i> Debug</span> 2093 </button> 2094 <button onclick="setPrompt('Explain this concept')" class="group bg-gemini-surface hover:bg-gemini-surfaceHover text-gemini-text px-5 py-4 rounded-2xl text-sm font-medium transition-all text-left flex-1 min-w-[200px] border border-transparent hover:border-gemini-surfaceHover"> 2095 <span class="block mb-1 text-gemini-purple group-hover:translate-x-1 transition-transform"><i data-lucide="book-open" class="w-4 h-4 inline mr-1"></i> Explain</span> 2096 </button> 2097 <button onclick="setPrompt('Refactor this function')" class="group bg-gemini-surface hover:bg-gemini-surfaceHover text-gemini-text px-5 py-4 rounded-2xl text-sm font-medium transition-all text-left flex-1 min-w-[200px] border border-transparent hover:border-gemini-surfaceHover"> 2098 <span class="block mb-1 text-green-400 group-hover:translate-x-1 transition-transform"><i data-lucide="wand-2" class="w-4 h-4 inline mr-1"></i> Refactor</span> 2099 </button> 2100 </div> 2101 </div>`; 2102 lucide.createIcons(); 2103 if (state.user && !state.user.isAnonymous) fetchChats(); 2104 } 2105 2106 window.onload = initApp; 2107 2108 </script> 2109 </body> 2110 </html>