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