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