indexbackup_20-01-2026_02.html (92526B)
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 --> 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"> 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-10">Sign in to access your coding workspace.</p> 462 463 <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"> 464 <img src="https://www.google.com/favicon.ico" class="w-5 h-5" alt="G"> 465 <span>Continue with Google</span> 466 </button> 467 </div> 468 </div> 469 470 <!-- Profile Modal --> 471 <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"> 472 <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"> 473 <div class="flex flex-col items-center"> 474 <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"> 475 <img id="profile-avatar" class="w-full h-full object-cover hidden" src="" alt="User"> 476 <span id="profile-initial">U</span> 477 </div> 478 <h2 id="profile-name" class="text-xl font-semibold text-white">User Name</h2> 479 <p id="profile-email" class="text-sm text-gemini-textSecondary mb-6 break-all text-center">user@example.com</p> 480 481 <div class="w-full grid grid-cols-2 gap-3 mb-6"> 482 <div class="bg-gemini-bg p-3 rounded-xl border border-gemini-surfaceHover text-center"> 483 <span class="block text-xs text-gemini-textSecondary uppercase tracking-wider mb-1">Status</span> 484 <span class="text-sm text-white font-medium">Free</span> 485 </div> 486 <div class="bg-gemini-bg p-3 rounded-xl border border-gemini-surfaceHover text-center"> 487 <span class="block text-xs text-gemini-textSecondary uppercase tracking-wider mb-1">Joined</span> 488 <span class="text-sm text-white font-medium">Jan '26</span> 489 </div> 490 </div> 491 492 <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"> 493 <i data-lucide="log-out" class="w-4 h-4"></i> 494 Sign Out 495 </button> 496 </div> 497 </div> 498 </div> 499 500 <!-- Settings Modal --> 501 <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"> 502 <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"> 503 <div class="flex items-center justify-between mb-6 border-b border-gemini-surfaceHover pb-4"> 504 <h2 class="text-xl font-semibold text-white">Settings</h2> 505 <button onclick="toggleSettingsModal()" class="text-gemini-textSecondary hover:text-white p-1 rounded-full hover:bg-gemini-surfaceHover transition-colors"> 506 <i data-lucide="x" class="w-5 h-5"></i> 507 </button> 508 </div> 509 510 <div class="space-y-2"> 511 <!-- Chat Sharing --> 512 <div class="p-3 hover:bg-gemini-surfaceHover rounded-xl transition-colors cursor-pointer flex items-center justify-between group"> 513 <div class="flex items-center gap-3"> 514 <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> 515 <div> 516 <h3 class="text-white text-sm font-medium">Chat Sharing</h3> 517 <p class="text-xs text-gemini-textSecondary">Allow public links</p> 518 </div> 519 </div> 520 <!-- Custom Toggle UI --> 521 <label class="switch"> 522 <input type="checkbox" id="share-toggle-checkbox" onchange="toggleShareSetting(this)"> 523 <div class="slider"> 524 <div class="circle"> 525 <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"> 526 <g> 527 <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> 528 </g> 529 </svg> 530 <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/1999/xlink" version="1.1" xmlns="http://www.w3.org/2000/svg"> 531 <g> 532 <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> 533 </g> 534 </svg> 535 </div> 536 </div> 537 </label> 538 </div> 539 540 <!-- Font Size --> 541 <div class="p-3 hover:bg-gemini-surfaceHover rounded-xl transition-colors flex items-center justify-between group"> 542 <div class="flex items-center gap-3"> 543 <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> 544 <div> 545 <h3 class="text-white text-sm font-medium">Font Size</h3> 546 <p class="text-xs text-gemini-textSecondary">Adjust readability</p> 547 </div> 548 </div> 549 <div class="flex items-center gap-2 bg-gemini-bg p-1 rounded-lg border border-gemini-surfaceHover"> 550 <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> 551 <span id="font-size-display" class="text-xs font-mono w-4 text-center">14</span> 552 <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> 553 </div> 554 </div> 555 556 <div class="h-px bg-gemini-surfaceHover my-2"></div> 557 558 <!-- Help & Support --> 559 <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"> 560 <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> 561 <div> 562 <h3 class="text-white text-sm font-medium">Help & Support</h3> 563 <p class="text-xs text-gemini-textSecondary">Report bugs or request features</p> 564 </div> 565 <i data-lucide="external-link" class="w-4 h-4 text-gemini-textSecondary ml-auto"></i> 566 </a> 567 568 <!-- Contribute --> 569 <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"> 570 <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> 571 <div> 572 <h3 class="text-white text-sm font-medium">Contribute</h3> 573 <p class="text-xs text-gemini-textSecondary">Fork repo & submit PRs</p> 574 </div> 575 <i data-lucide="external-link" class="w-4 h-4 text-gemini-textSecondary ml-auto"></i> 576 </a> 577 578 <!-- Developer Contact --> 579 <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"> 580 <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> 581 <div> 582 <h3 class="text-white text-sm font-medium">Critical Support</h3> 583 <p class="text-xs text-gemini-textSecondary">Contact developer directly</p> 584 </div> 585 </a> 586 587 <!-- General Feedback --> 588 <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"> 589 <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> 590 <div> 591 <h3 class="text-white text-sm font-medium">Send Feedback</h3> 592 <p class="text-xs text-gemini-textSecondary">Suggestions & improvements</p> 593 </div> 594 </a> 595 596 <div class="h-px bg-gemini-surfaceHover my-2"></div> 597 598 <!-- Terms --> 599 <a href="/terms.html" class="p-3 hover:bg-gemini-surfaceHover rounded-xl transition-colors cursor-pointer flex items-center gap-3 group"> 600 <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> 601 <div> 602 <h3 class="text-white text-sm font-medium">Terms & Conditions</h3> 603 <p class="text-xs text-gemini-textSecondary">Usage policies</p> 604 </div> 605 </a> 606 <!-- Docs --> 607 <a href="/docs.html" class="p-3 hover:bg-gemini-surfaceHover rounded-xl transition-colors cursor-pointer flex items-center gap-3 group"> 608 <div class="p-2 bg-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> 609 <div> 610 <h3 class="text-white text-sm font-medium">Docs</h3> 611 <p class="text-xs text-gemini-textSecondary">Service Documentation</p> 612 </div> 613 </a> 614 615 <!-- Delete History --> 616 <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"> 617 <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> 618 <div> 619 <h3 class="text-red-400 text-sm font-medium">Clear Activity</h3> 620 <p class="text-xs text-gemini-textSecondary">Remove local session data</p> 621 </div> 622 </button> 623 624 <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> 625 </div> 626 </div> 627 </div> 628 629 <!-- Sidebar (Collapsible Drawer on ALL screens) --> 630 <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"> 631 <!-- Sidebar Header --> 632 <div class="h-16 flex items-center justify-between px-4"> 633 <button onclick="toggleSidebar()" class="p-2 text-gemini-textSecondary hover:text-white hover:bg-gemini-surfaceHover rounded-full transition-colors"> 634 <i data-lucide="menu" class="w-5 h-5"></i> 635 </button> 636 <span class="text-gemini-textSecondary text-xs font-medium uppercase tracking-wider">History</span> 637 638 <div class="relative w-full max-w-[140px] ml-4"> 639 <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"> 640 <i data-lucide="search" class="absolute right-2 top-1.5 w-3 h-3 text-gemini-textSecondary"></i> 641 </div> 642 </div> 643 644 <!-- New Chat Button --> 645 <div class="px-3 mb-2"> 646 <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"> 647 <i data-lucide="plus" class="w-5 h-5"></i> 648 <span>New chat</span> 649 </button> 650 </div> 651 652 <!-- Chat History List --> 653 <div id="chat-history-list" class="flex-1 overflow-y-auto px-2 py-2 space-y-1"> 654 <!-- Skeleton Loader injected via JS --> 655 </div> 656 657 <!-- User Profile (Bottom) --> 658 <div class="p-4 border-t border-gemini-surfaceHover space-y-1"> 659 <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"> 660 <i data-lucide="settings" class="w-4 h-4"></i> 661 <span class="text-sm font-medium">Settings</span> 662 </button> 663 664 <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"> 665 <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"> 666 <img id="user-avatar" class="w-full h-full object-cover hidden" src="" alt="User"> 667 <span id="user-initial">U</span> 668 </div> 669 <div class="flex-1 min-w-0"> 670 <p id="user-email" class="text-xs text-gemini-textSecondary truncate">Not signed in</p> 671 </div> 672 </button> 673 </div> 674 </aside> 675 676 <!-- Overlay for Sidebar --> 677 <div id="sidebar-overlay" onclick="toggleSidebar()" class="fixed inset-0 bg-black/50 z-[55] hidden backdrop-blur-sm transition-opacity"></div> 678 679 <!-- Main Content Area --> 680 <main class="flex-1 flex flex-col relative w-full h-full min-w-0 bg-gemini-bg"> 681 682 <!-- Top Bar (Fixed) --> 683 <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"> 684 <div class="flex items-center gap-2"> 685 <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"> 686 <i data-lucide="menu" class="w-6 h-6"></i> 687 </button> 688 <span class="text-lg font-medium text-white tracking-tight flex items-center gap-2"> 689 <span class="flex items-center gap-2"> 690 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> 691 </span> 692 </span> 693 </div> 694 695 <!-- Code Viewer Toggle & Login Trigger for Shared View --> 696 <div class="flex items-center gap-2"> 697 <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"> 698 Sign In to Save 699 </button> 700 701 <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"> 702 <i data-lucide="panel-right" class="w-6 h-6"></i> 703 <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> 704 </button> 705 </div> 706 </header> 707 708 <!-- Chat Container --> 709 <div id="chat-container" class="flex-1 overflow-y-auto px-4 md:px-0 pb-32 pt-4 scroll-smooth"> 710 <div class="max-w-3xl mx-auto flex flex-col gap-6"> 711 <!-- Welcome Message --> 712 <div id="welcome-message" class="flex flex-col items-start justify-center min-h-[50vh] space-y-8 animate-slide-up px-2"> 713 <div class="space-y-2 w-full"> 714 <div class="w-12 h-12 mb-4 animate-spin-slow-entry"> 715 <svg class="w-full h-full drop-shadow-[0_0_15px_rgba(168,199,250,0.5)]"><use href="#icon-ada"></use></svg> 716 </div> 717 <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"> 718 Hello, <span id="welcome-name" class="text-white truncate block md:inline max-w-[200px] md:max-w-none">Student</span> 719 </h1> 720 <!-- UPDATED COLOR: Made brighter as requested --> 721 <h2 class="text-3xl md:text-5xl font-medium text-gemini-textSecondary/30">How can I help you code today?</h2> 722 </div> 723 724 <!-- Suggestion Chips (Hidden on Mobile) --> 725 <div class="hidden md:flex flex-wrap gap-3 w-full" id="suggestion-chips"> 726 <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"> 727 <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> 728 Find errors in code 729 </button> 730 <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"> 731 <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> 732 Understand algorithms 733 </button> 734 <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"> 735 <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> 736 Optimize performance 737 </button> 738 </div> 739 </div> 740 </div> 741 </div> 742 743 <!-- Input Area (Fixed Bottom) --> 744 <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"> 745 <div class="max-w-3xl mx-auto"> 746 <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"> 747 <!-- ADDED: transition-all duration-200 for smooth growth --> 748 <textarea 749 id="user-input" 750 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" 751 placeholder="Enter a prompt here" 752 rows="1" 753 oninput="autoResize(this)" 754 onkeydown="handleEnter(event)" 755 enterkeyhint="send" 756 ></textarea> 757 758 <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"> 759 <i data-lucide="send-horizontal" class="w-6 h-6"></i> 760 </button> 761 </form> 762 <div class="text-center mt-3 text-xs text-gemini-textSecondary/60"> 763 Ada may display inaccurate info, including about people, so double-check its responses. 764 </div> 765 </div> 766 </div> 767 </main> 768 769 <!-- Right Artifact Panel (Drawer on ALL screens) --> 770 <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"> 771 <!-- Panel Header --> 772 <div class="h-14 flex-shrink-0 flex items-center justify-between px-4 bg-[#252526] border-b border-[#333]"> 773 <div class="flex items-center gap-3"> 774 <span class="text-xs uppercase tracking-wide text-[#969696] font-medium">Workspace</span> 775 <span id="lang-display" class="text-xs text-[#cccccc] bg-[#3c3c3c] px-2 py-0.5 rounded">PLAINTEXT</span> 776 </div> 777 <div class="flex gap-2"> 778 <button onclick="copyArtifact()" class="p-2 text-[#cccccc] hover:text-white hover:bg-[#3c3c3c] rounded transition-colors" title="Copy Code"> 779 <i data-lucide="copy" class="w-4 h-4"></i> 780 </button> 781 <button onclick="toggleArtifactPanel()" class="p-2 text-[#cccccc] hover:text-white hover:bg-[#3c3c3c] rounded transition-colors" title="Close"> 782 <i data-lucide="x" class="w-5 h-5"></i> 783 </button> 784 </div> 785 </div> 786 787 <!-- Editor Content --> 788 <div class="flex-1 overflow-hidden relative bg-[#1e1e1e]"> 789 <textarea id="artifact-editor" class="w-full h-full"></textarea> 790 </div> 791 792 <!-- ADDED: Status Bar --> 793 <div class="h-6 bg-[#007acc] text-white flex items-center px-3 text-xs justify-end gap-4 select-none"> 794 <div class="flex items-center gap-1"> 795 <i data-lucide="code" class="w-3 h-3"></i> 796 <span id="status-lang">JavaScript</span> 797 </div> 798 <div class="flex items-center gap-1"> 799 <span id="status-cursor">Ln 1, Col 1</span> 800 </div> 801 <div>UTF-8</div> 802 </div> 803 </aside> 804 805 <script> 806 // --- CONFIGURATION --- 807 const API_BASE_URL = "https://ada-web.onrender.com/api"; 808 809 const firebaseConfig = { 810 apiKey: "AIzaSyAhyoNYUSXWIxdBiTxwSDcU7HkhhLEVlOc", 811 authDomain: "ada-ai-aranag.firebaseapp.com", 812 projectId: "ada-ai-aranag", 813 storageBucket: "ada-ai-aranag.firebasestorage.app", 814 messagingSenderId: "865895114061", 815 appId: "1:865895114061:web:5fd4493b0e388727e6c304" 816 }; 817 818 // --- STATE & INIT --- 819 let auth, db; 820 let state = { 821 messages: [], 822 isTyping: false, 823 currentChatId: null, 824 user: null, 825 isSharedView: false 826 }; 827 let editor = null; 828 let settings = { 829 allowSharing: localStorage.getItem('ada_allow_sharing') === 'true', 830 fontSize: parseInt(localStorage.getItem('ada_font_size')) || 14 831 }; 832 833 // --- COMMAND PALETTE DATA --- 834 const commands = [ 835 { name: "New Chat", icon: "plus", action: startNewChat, shortcut: "N" }, 836 { name: "Toggle Sidebar", icon: "sidebar", action: toggleSidebar, shortcut: "Ctrl+/" }, 837 { name: "Clear History", icon: "trash-2", action: confirmClearHistory, shortcut: "" }, 838 { name: "Settings", icon: "settings", action: toggleSettingsModal, shortcut: "" }, 839 { name: "Sign Out", icon: "log-out", action: confirmSignOut, shortcut: "" } 840 ]; 841 842 function initApp() { 843 lucide.createIcons(); 844 845 // Set initial settings 846 applyFontSize(settings.fontSize); 847 848 const shareToggle = document.getElementById('share-toggle-checkbox'); 849 if(shareToggle) shareToggle.checked = settings.allowSharing; 850 851 // Check for Shared URL param 852 const urlParams = new URLSearchParams(window.location.search); 853 const sharedChatId = urlParams.get('chat'); 854 if (sharedChatId) { 855 state.isSharedView = true; 856 state.currentChatId = sharedChatId; 857 enterSharedMode(sharedChatId); 858 } 859 860 // Initialize CodeMirror (Standard setup) 861 editor = CodeMirror.fromTextArea(document.getElementById("artifact-editor"), { 862 lineNumbers: true, 863 mode: "javascript", 864 theme: "dracula", 865 readOnly: true, // Read-only as it's an output viewer primarily 866 lineWrapping: true 867 }); 868 869 // ADDED: Cursor Activity Listener for Status Bar 870 editor.on("cursorActivity", () => { 871 const pos = editor.getCursor(); 872 document.getElementById('status-cursor').innerText = `Ln ${pos.line + 1}, Col ${pos.ch + 1}`; 873 }); 874 875 // Initialize Firebase 876 try { 877 firebase.initializeApp(firebaseConfig); 878 auth = firebase.auth(); 879 db = firebase.firestore(); 880 if (!state.isSharedView) checkAuth(); 881 else checkAuthShared(); 882 } catch (e) { 883 console.error("Init Failed:", e); 884 document.getElementById('loading-overlay').classList.add('opacity-0', 'pointer-events-none'); 885 } 886 887 // Mobile Height Fix 888 const setVh = () => { 889 let vh = window.innerHeight * 0.01; 890 document.documentElement.style.setProperty('--vh', `${vh}px`); 891 }; 892 window.addEventListener('resize', setVh); 893 setVh(); 894 895 // Context Menu Listener 896 document.addEventListener('click', (e) => { 897 const menu = document.querySelector('.context-menu'); 898 if (menu) menu.remove(); 899 }); 900 901 // ADDED: Global Keyboard Shortcuts 902 document.addEventListener('keydown', handleGlobalKeydown); 903 904 // ADDED: Swipe Gestures 905 initSwipeGestures(); 906 } 907 908 function autoResize(textarea) { 909 textarea.style.height = 'auto'; 910 textarea.style.height = Math.min(textarea.scrollHeight, 200) + 'px'; 911 } 912 913 // ADDED: Swipe Gesture Logic 914 function initSwipeGestures() { 915 let touchStartX = 0; 916 let touchStartY = 0; 917 918 document.addEventListener('touchstart', e => { 919 touchStartX = e.changedTouches[0].screenX; 920 touchStartY = e.changedTouches[0].screenY; 921 }, {passive: true}); 922 923 document.addEventListener('touchend', e => { 924 const touchEndX = e.changedTouches[0].screenX; 925 const touchEndY = e.changedTouches[0].screenY; 926 927 handleSwipe(touchStartX, touchStartY, touchEndX, touchEndY); 928 }, {passive: true}); 929 } 930 931 function handleSwipe(startX, startY, endX, endY) { 932 const diffX = endX - startX; 933 const diffY = endY - startY; 934 935 // Only trigger if horizontal swipe is dominant and significant 936 if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 50) { 937 const sidebar = document.getElementById('sidebar'); 938 const isClosed = sidebar.classList.contains('-translate-x-full'); 939 940 // Swipe Right (Open) - Only works from left edge area (first 30px) 941 if (diffX > 0 && startX < 30 && isClosed) { 942 toggleSidebar(); 943 } 944 945 // Swipe Left (Close) 946 if (diffX < 0 && !isClosed) { 947 toggleSidebar(); 948 } 949 } 950 } 951 952 // ADDED: Keyboard Shortcuts Handler 953 function handleGlobalKeydown(e) { 954 // Toggle Sidebar: Ctrl + / (or Cmd + /) 955 if ((e.ctrlKey || e.metaKey) && e.key === '/') { 956 e.preventDefault(); 957 toggleSidebar(); 958 } 959 960 // Command Palette: Ctrl + K (or Cmd + K) 961 if ((e.ctrlKey || e.metaKey) && e.key === 'k') { 962 e.preventDefault(); 963 toggleCommandPalette(); 964 } 965 966 // Close Palette on ESC 967 if (e.key === 'Escape') { 968 const cmd = document.getElementById('command-palette'); 969 if (!cmd.classList.contains('hidden')) { 970 toggleCommandPalette(); 971 } 972 } 973 } 974 975 // ADDED: Command Palette Logic 976 function toggleCommandPalette() { 977 const palette = document.getElementById('command-palette'); 978 const input = document.getElementById('cmd-input'); 979 const list = document.getElementById('cmd-list'); 980 981 if (palette.classList.contains('hidden')) { 982 // Show 983 palette.classList.remove('hidden', 'pointer-events-none'); 984 requestAnimationFrame(() => palette.classList.remove('opacity-0')); 985 input.value = ''; 986 input.focus(); 987 renderCommands(commands); 988 } else { 989 // Hide 990 palette.classList.add('opacity-0'); 991 setTimeout(() => palette.classList.add('hidden', 'pointer-events-none'), 150); 992 } 993 994 input.oninput = (e) => { 995 const val = e.target.value.toLowerCase(); 996 const filtered = commands.filter(c => c.name.toLowerCase().includes(val)); 997 renderCommands(filtered); 998 }; 999 } 1000 1001 function renderCommands(cmds) { 1002 const list = document.getElementById('cmd-list'); 1003 list.innerHTML = ''; 1004 1005 if (cmds.length === 0) { 1006 list.innerHTML = `<div class="text-gemini-textSecondary text-xs p-3 text-center">No commands found</div>`; 1007 return; 1008 } 1009 1010 cmds.forEach(cmd => { 1011 const div = document.createElement('div'); 1012 div.className = 'cmd-item group'; 1013 div.onclick = () => { 1014 cmd.action(); 1015 toggleCommandPalette(); 1016 }; 1017 div.innerHTML = ` 1018 <div class="flex items-center gap-3"> 1019 <i data-lucide="${cmd.icon}" class="w-4 h-4 text-gemini-textSecondary group-hover:text-white"></i> 1020 <span class="text-sm">${cmd.name}</span> 1021 </div> 1022 ${cmd.shortcut ? `<span class="cmd-shortcut">${cmd.shortcut}</span>` : ''} 1023 `; 1024 list.appendChild(div); 1025 }); 1026 lucide.createIcons(); 1027 } 1028 1029 // --- SETTINGS --- 1030 function changeFontSize(delta) { 1031 settings.fontSize = Math.max(10, Math.min(24, settings.fontSize + delta)); 1032 localStorage.setItem('ada_font_size', settings.fontSize); 1033 applyFontSize(settings.fontSize); 1034 } 1035 1036 function applyFontSize(size) { 1037 document.getElementById('font-size-display').innerText = size; 1038 if(editor) { 1039 editor.getWrapperElement().style.fontSize = `${size}px`; 1040 editor.refresh(); 1041 } 1042 } 1043 1044 // --- SHARED MODE LOGIC --- 1045 async function enterSharedMode(chatId) { 1046 document.getElementById('sidebar').classList.add('hidden'); 1047 const inputArea = document.getElementById('input-area'); 1048 const sidebarToggle = document.querySelector('header button[title="Toggle History"]'); 1049 1050 inputArea.classList.add('hidden'); 1051 if(sidebarToggle) sidebarToggle.style.display = 'none'; 1052 } 1053 1054 function checkAuthShared() { 1055 auth.onAuthStateChanged(user => { 1056 const loadingOverlay = document.getElementById('loading-overlay'); 1057 loadingOverlay.classList.add('opacity-0', 'pointer-events-none'); 1058 1059 if (user) { 1060 state.user = user; 1061 document.getElementById('sidebar').classList.remove('hidden'); 1062 document.getElementById('input-area').classList.remove('hidden'); 1063 const sidebarToggle = document.querySelector('header button[title="Toggle History"]'); 1064 if(sidebarToggle) sidebarToggle.style.display = 'block'; 1065 document.getElementById('shared-login-btn').classList.add('hidden'); 1066 1067 fetchChats(); 1068 loadChat(state.currentChatId); 1069 } else { 1070 state.user = null; 1071 document.getElementById('shared-login-btn').classList.remove('hidden'); 1072 loadChatReadOnly(state.currentChatId); 1073 } 1074 }); 1075 } 1076 1077 async function loadChatReadOnly(chatId) { 1078 const container = document.querySelector('#chat-container > div'); 1079 container.innerHTML = ` 1080 <div class="flex flex-col items-center justify-center min-h-[50vh] text-center p-6"> 1081 <div class="p-4 bg-gemini-surface rounded-2xl border border-gemini-surfaceHover mb-4"> 1082 <i data-lucide="lock" class="w-8 h-8 text-gemini-textSecondary mx-auto mb-2"></i> 1083 <p class="text-white font-medium">Shared Chat</p> 1084 </div> 1085 <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> 1086 </div> 1087 `; 1088 lucide.createIcons(); 1089 } 1090 1091 // --- UI TOGGLES & HELPERS --- 1092 function showToast(message, type = 'info') { 1093 const toast = document.getElementById('toast'); 1094 const icon = toast.querySelector('i'); 1095 document.getElementById('toast-message').innerText = message; 1096 1097 if (type === 'error') { 1098 icon.setAttribute('data-lucide', 'alert-circle'); 1099 icon.className = 'w-4 h-4 text-red-400'; 1100 } else if (type === 'success') { 1101 icon.setAttribute('data-lucide', 'check-circle'); 1102 icon.className = 'w-4 h-4 text-green-400'; 1103 } else { 1104 icon.setAttribute('data-lucide', 'info'); 1105 icon.className = 'w-4 h-4 text-gemini-blue'; 1106 } 1107 lucide.createIcons(); 1108 1109 toast.classList.remove('opacity-0', 'pointer-events-none', 'translate-y-20'); 1110 setTimeout(() => { 1111 toast.classList.add('opacity-0', 'pointer-events-none', 'translate-y-20'); 1112 }, 3000); 1113 } 1114 1115 function toggleShareSetting(checkbox) { 1116 settings.allowSharing = checkbox.checked; 1117 localStorage.setItem('ada_allow_sharing', settings.allowSharing); 1118 } 1119 1120 function toggleSidebar() { 1121 const sb = document.getElementById('sidebar'); 1122 const overlay = document.getElementById('sidebar-overlay'); 1123 const isClosed = sb.classList.contains('-translate-x-full'); 1124 1125 if (isClosed) { 1126 sb.classList.remove('-translate-x-full'); 1127 overlay.classList.remove('hidden'); 1128 } else { 1129 sb.classList.add('-translate-x-full'); 1130 overlay.classList.add('hidden'); 1131 } 1132 } 1133 1134 function toggleArtifactPanel() { 1135 const panel = document.getElementById('artifact-panel'); 1136 const isClosed = panel.classList.contains('translate-x-full'); 1137 1138 if (isClosed) { 1139 panel.classList.remove('translate-x-full'); 1140 document.getElementById('artifact-indicator').classList.add('hidden'); 1141 setTimeout(() => editor.refresh(), 300); 1142 } else { 1143 panel.classList.add('translate-x-full'); 1144 } 1145 } 1146 1147 function toggleProfileModal() { 1148 const modal = document.getElementById('profile-modal'); 1149 const content = document.getElementById('profile-modal-content'); 1150 1151 if (modal.classList.contains('hidden')) { 1152 modal.classList.remove('hidden', 'pointer-events-none'); 1153 requestAnimationFrame(() => { 1154 modal.classList.remove('opacity-0'); 1155 content.classList.remove('scale-95'); 1156 content.classList.add('scale-100'); 1157 }); 1158 1159 if (state.user) { 1160 document.getElementById('profile-name').innerText = state.user.displayName || 'User'; 1161 document.getElementById('profile-email').innerText = state.user.email || ''; 1162 const initial = document.getElementById('profile-initial'); 1163 const avatar = document.getElementById('profile-avatar'); 1164 1165 if (state.user.photoURL) { 1166 avatar.src = state.user.photoURL; 1167 avatar.classList.remove('hidden'); 1168 initial.classList.add('hidden'); 1169 } else { 1170 initial.innerText = (state.user.displayName || 'U')[0]; 1171 avatar.classList.add('hidden'); 1172 initial.classList.remove('hidden'); 1173 } 1174 } 1175 } else { 1176 modal.classList.add('opacity-0'); 1177 content.classList.remove('scale-100'); 1178 content.classList.add('scale-95'); 1179 setTimeout(() => modal.classList.add('hidden', 'pointer-events-none'), 300); 1180 } 1181 } 1182 1183 function toggleSettingsModal() { 1184 const modal = document.getElementById('settings-modal'); 1185 const content = document.getElementById('settings-modal-content'); 1186 1187 if (modal.classList.contains('hidden')) { 1188 modal.classList.remove('hidden', 'pointer-events-none'); 1189 requestAnimationFrame(() => { 1190 modal.classList.remove('opacity-0'); 1191 content.classList.remove('scale-95'); 1192 content.classList.add('scale-100'); 1193 }); 1194 } else { 1195 modal.classList.add('opacity-0'); 1196 content.classList.remove('scale-100'); 1197 content.classList.add('scale-95'); 1198 setTimeout(() => modal.classList.add('hidden', 'pointer-events-none'), 300); 1199 } 1200 } 1201 1202 // --- FEATURES IMPLEMENTATION --- 1203 1204 // 1. History Search 1205 function filterHistory(query) { 1206 const list = document.getElementById('chat-history-list'); 1207 const items = list.querySelectorAll('div[onclick]'); 1208 query = query.toLowerCase(); 1209 1210 items.forEach(item => { 1211 const text = item.querySelector('span').innerText.toLowerCase(); 1212 if (text.includes(query)) { 1213 item.style.display = 'flex'; 1214 } else { 1215 item.style.display = 'none'; 1216 } 1217 }); 1218 } 1219 1220 // --- CONFIRM MODALS --- 1221 let confirmCallback = null; 1222 1223 function showConfirmModal(title, message, callback, matchText = null) { 1224 const modal = document.getElementById('confirm-modal'); 1225 const content = document.getElementById('confirm-modal-content'); 1226 1227 document.getElementById('confirm-title').innerText = title; 1228 document.getElementById('confirm-message').innerText = message; 1229 1230 const inputContainer = document.getElementById('confirm-input-container'); 1231 const input = document.getElementById('confirm-input'); 1232 const btn = document.getElementById('confirm-action-btn'); 1233 1234 if (matchText) { 1235 inputContainer.classList.remove('hidden'); 1236 document.getElementById('confirm-match-text').innerText = matchText; 1237 input.value = ''; 1238 input.oninput = () => { 1239 if (input.value === matchText) { 1240 btn.disabled = false; 1241 btn.classList.remove('opacity-50', 'cursor-not-allowed'); 1242 } else { 1243 btn.disabled = true; 1244 btn.classList.add('opacity-50', 'cursor-not-allowed'); 1245 } 1246 }; 1247 btn.disabled = true; 1248 btn.classList.add('opacity-50', 'cursor-not-allowed'); 1249 } else { 1250 inputContainer.classList.add('hidden'); 1251 btn.disabled = false; 1252 btn.classList.remove('opacity-50', 'cursor-not-allowed'); 1253 } 1254 1255 confirmCallback = callback; 1256 1257 modal.classList.remove('hidden'); 1258 requestAnimationFrame(() => { 1259 modal.classList.remove('opacity-0'); 1260 content.classList.remove('scale-95'); 1261 content.classList.add('scale-100'); 1262 }); 1263 1264 btn.onclick = () => { 1265 closeConfirmModal(); 1266 if (confirmCallback) confirmCallback(); 1267 }; 1268 } 1269 1270 function closeConfirmModal() { 1271 const modal = document.getElementById('confirm-modal'); 1272 const content = document.getElementById('confirm-modal-content'); 1273 modal.classList.add('opacity-0'); 1274 content.classList.remove('scale-100'); 1275 content.classList.add('scale-95'); 1276 setTimeout(() => modal.classList.add('hidden'), 300); 1277 } 1278 1279 function confirmSignOut() { 1280 toggleProfileModal(); 1281 showConfirmModal('Sign Out', 'Are you sure you want to sign out?', signOutUser); 1282 } 1283 1284 function confirmClearHistory() { 1285 toggleSettingsModal(); 1286 if (!state.user || !state.user.email) return; 1287 showConfirmModal( 1288 'Clear Activity', 1289 'This will permanently delete all your chat history. Type your email to confirm.', 1290 clearHistory, 1291 state.user.email 1292 ); 1293 } 1294 1295 async function clearHistory() { 1296 if (!state.user) return; 1297 const loading = document.getElementById('loading-overlay'); 1298 loading.classList.remove('opacity-0', 'pointer-events-none'); 1299 1300 try { 1301 // FIXED: Use correct path users/{uid}/chats 1302 const chatsRef = db.collection('users').doc(state.user.uid).collection('chats'); 1303 const snapshot = await chatsRef.get(); 1304 1305 const batch = db.batch(); 1306 snapshot.docs.forEach((doc) => { 1307 batch.delete(doc.ref); 1308 }); 1309 await batch.commit(); 1310 1311 location.reload(); 1312 } catch (error) { 1313 console.error("Error clearing history:", error); 1314 loading.classList.add('opacity-0', 'pointer-events-none'); 1315 showToast("Failed to clear history.", "error"); 1316 } 1317 } 1318 1319 function confirmDeleteChat(chatId) { 1320 showConfirmModal('Delete Chat', 'This action cannot be undone.', () => deleteChat(chatId)); 1321 } 1322 1323 async function deleteChat(chatId) { 1324 if (!state.user) return; 1325 try { 1326 // FIXED: Use correct path users/{uid}/chats/{chatId} 1327 await db.collection('users').doc(state.user.uid).collection('chats').doc(chatId).delete(); 1328 1329 if (state.currentChatId === chatId) { 1330 location.reload(); 1331 } else { 1332 fetchChats(); 1333 showToast("Chat deleted.", "success"); 1334 } 1335 } catch (e) { 1336 console.error("Delete Error:", e); 1337 showToast("Failed to delete chat.", "error"); 1338 } 1339 } 1340 1341 // --- CONTEXT MENU & EXPORT --- 1342 function showChatContextMenu(e, chatId, chatTitle) { 1343 e.preventDefault(); 1344 e.stopPropagation(); 1345 1346 const existing = document.querySelector('.context-menu'); 1347 if (existing) existing.remove(); 1348 1349 const menu = document.createElement('div'); 1350 menu.className = 'context-menu'; 1351 1352 // Share Option 1353 if (settings.allowSharing) { 1354 const shareBtn = document.createElement('div'); 1355 shareBtn.className = 'context-menu-item'; 1356 shareBtn.innerHTML = `<i data-lucide="link" class="w-4 h-4"></i> Share Chat`; 1357 shareBtn.onclick = async () => { 1358 const url = `${window.location.origin}?chat=${chatId}`; 1359 if (navigator.share) { 1360 try { 1361 await navigator.share({ title: 'Ada Chat', url: url }); 1362 } catch (err) { /* Share cancelled */ } 1363 } else { 1364 navigator.clipboard.writeText(url).then(() => showToast('Link copied!', 'success')); 1365 } 1366 menu.remove(); 1367 }; 1368 menu.appendChild(shareBtn); 1369 } 1370 1371 // Export Options (Expanded) 1372 const jsonBtn = document.createElement('div'); 1373 jsonBtn.className = 'context-menu-item'; 1374 jsonBtn.innerHTML = `<i data-lucide="file-json" class="w-4 h-4"></i> Export JSON`; 1375 jsonBtn.onclick = () => { 1376 downloadChat(chatId, chatTitle, 'json'); 1377 menu.remove(); 1378 }; 1379 menu.appendChild(jsonBtn); 1380 1381 const mdBtn = document.createElement('div'); 1382 mdBtn.className = 'context-menu-item'; 1383 mdBtn.innerHTML = `<i data-lucide="file-text" class="w-4 h-4"></i> Export Markdown`; 1384 mdBtn.onclick = () => { 1385 downloadChat(chatId, chatTitle, 'md'); 1386 menu.remove(); 1387 }; 1388 menu.appendChild(mdBtn); 1389 1390 // Delete Option 1391 const delBtn = document.createElement('div'); 1392 delBtn.className = 'context-menu-item danger'; 1393 delBtn.innerHTML = `<i data-lucide="trash-2" class="w-4 h-4"></i> Delete Chat`; 1394 delBtn.onclick = () => { 1395 confirmDeleteChat(chatId); 1396 menu.remove(); 1397 }; 1398 menu.appendChild(delBtn); 1399 1400 document.body.appendChild(menu); 1401 1402 // Position 1403 const rect = e.target.getBoundingClientRect(); 1404 const menuWidth = 180; 1405 const menuHeight = 200; 1406 1407 let left = rect.left; 1408 let top = rect.bottom + 5; 1409 1410 if (left + menuWidth > window.innerWidth) left = window.innerWidth - menuWidth - 16; 1411 if (left < 16) left = 16; 1412 if (top + menuHeight > window.innerHeight) top = rect.top - menuHeight - 5; 1413 1414 menu.style.top = `${top}px`; 1415 menu.style.left = `${left}px`; 1416 1417 lucide.createIcons(); 1418 } 1419 1420 async function downloadChat(chatId, title, format) { 1421 try { 1422 const token = await state.user.getIdToken(); 1423 const res = await fetch(`${API_BASE_URL}/chats/${chatId}`, { headers: { 'Authorization': `Bearer ${token}` } }); 1424 const data = await res.json(); 1425 1426 let content, type, ext; 1427 if (format === 'json') { 1428 content = JSON.stringify(data, null, 2); 1429 type = 'application/json'; 1430 ext = 'json'; 1431 } else { 1432 // Markdown conversion 1433 content = `# ${title}\n\n`; 1434 data.messages.forEach(msg => { 1435 content += `### ${msg.role === 'user' ? 'User' : 'Ada AI'}\n${msg.content}\n\n`; 1436 }); 1437 type = 'text/markdown'; 1438 ext = 'md'; 1439 } 1440 1441 const blob = new Blob([content], { type: type }); 1442 const url = URL.createObjectURL(blob); 1443 const a = document.createElement('a'); 1444 a.href = url; 1445 a.download = `ada_chat_${title.replace(/\s+/g, '_')}.${ext}`; 1446 document.body.appendChild(a); 1447 a.click(); 1448 document.body.removeChild(a); 1449 URL.revokeObjectURL(url); 1450 showToast("Chat exported.", "success"); 1451 } catch(e) { 1452 console.error(e); 1453 showToast("Export failed.", "error"); 1454 } 1455 } 1456 1457 // --- AUTH & FIREBASE --- 1458 function checkAuth() { 1459 auth.onAuthStateChanged(user => { 1460 const loadingOverlay = document.getElementById('loading-overlay'); 1461 const authModal = document.getElementById('auth-modal'); 1462 1463 if (user) { 1464 state.user = user; 1465 localStorage.setItem('ada_user_cached', 'true'); 1466 1467 loadingOverlay.classList.add('opacity-0', 'pointer-events-none'); 1468 authModal.classList.add('hidden'); 1469 1470 document.getElementById('user-email').innerText = user.email || ''; 1471 document.getElementById('user-initial').innerText = (user.displayName || 'U')[0]; 1472 1473 const welcomeName = document.getElementById('welcome-name'); 1474 if (welcomeName) { 1475 const name = user.email || user.displayName || 'Student'; 1476 welcomeName.innerText = name; 1477 welcomeName.title = name; 1478 } 1479 1480 if (user.photoURL) { 1481 const img = document.getElementById('user-avatar'); 1482 img.src = user.photoURL; 1483 img.classList.remove('hidden'); 1484 document.getElementById('user-initial').classList.add('hidden'); 1485 } 1486 1487 if(!state.currentChatId) state.currentChatId = 'session_' + Date.now(); 1488 fetchChats(); 1489 } else { 1490 state.user = null; 1491 localStorage.removeItem('ada_user_cached'); 1492 loadingOverlay.classList.add('opacity-0', 'pointer-events-none'); 1493 authModal.classList.remove('hidden', 'opacity-0', 'pointer-events-none'); 1494 } 1495 }); 1496 } 1497 1498 function signInWithGoogle() { 1499 const provider = new firebase.auth.GoogleAuthProvider(); 1500 auth.signInWithPopup(provider).catch(e => { 1501 console.error(e); 1502 showToast("Sign in failed.", "error"); 1503 }); 1504 } 1505 1506 function signOutUser() { 1507 auth.signOut().then(() => { 1508 localStorage.removeItem('ada_user_cached'); 1509 location.reload(); 1510 }); 1511 } 1512 1513 // ADDED: Skeleton Loader Generator 1514 function renderSkeletonLoader() { 1515 const list = document.getElementById('chat-history-list'); 1516 list.innerHTML = ''; 1517 for(let i=0; i<5; i++) { 1518 list.innerHTML += ` 1519 <div class="flex items-center gap-3 p-2.5 rounded-full skeleton-shimmer opacity-50 mb-1"> 1520 <div class="w-4 h-4 bg-gemini-surfaceHover rounded-full"></div> 1521 <div class="h-4 bg-gemini-surfaceHover rounded w-24"></div> 1522 </div> 1523 `; 1524 } 1525 } 1526 1527 async function fetchChats() { 1528 if (!state.user) return; 1529 try { 1530 // USE SKELETON LOADER 1531 renderSkeletonLoader(); 1532 1533 const token = await state.user.getIdToken(); 1534 const res = await fetch(`${API_BASE_URL}/chats`, { headers: { 'Authorization': `Bearer ${token}` } }); 1535 const data = await res.json(); 1536 1537 const list = document.getElementById('chat-history-list'); 1538 list.innerHTML = ''; 1539 1540 if (data.chats && data.chats.length > 0) { 1541 data.chats.forEach(chat => { 1542 const div = document.createElement('div'); 1543 const isActive = chat.id === state.currentChatId; 1544 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'}`; 1545 div.onclick = () => loadChat(chat.id); 1546 1547 let pressTimer; 1548 div.ontouchstart = (e) => { 1549 pressTimer = setTimeout(() => showChatContextMenu(e, chat.id, chat.title), 500); 1550 }; 1551 div.ontouchend = () => clearTimeout(pressTimer); 1552 div.oncontextmenu = (e) => showChatContextMenu(e, chat.id, chat.title); 1553 1554 div.innerHTML = ` 1555 <i data-lucide="message-square" class="w-4 h-4 flex-shrink-0"></i> 1556 <span class="truncate flex-1">${chat.title}</span> 1557 <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}')"> 1558 <i data-lucide="more-vertical" class="w-3 h-3"></i> 1559 </button> 1560 `; 1561 list.appendChild(div); 1562 }); 1563 } else { 1564 list.innerHTML = `<div class="p-4 text-center text-xs text-gemini-textSecondary">No recent chats</div>`; 1565 } 1566 lucide.createIcons(); 1567 } catch (e) { console.error(e); } 1568 } 1569 1570 async function loadChat(chatId) { 1571 if (state.isTyping) return; 1572 state.currentChatId = chatId; 1573 1574 // Skeleton Loading for Main Chat 1575 const container = document.querySelector('#chat-container > div'); 1576 container.innerHTML = ` 1577 <div class="flex flex-col space-y-6 p-4"> 1578 <div class="flex justify-end"><div class="h-10 w-2/3 rounded-2xl skeleton-shimmer"></div></div> 1579 <div class="flex justify-start gap-4"> 1580 <div class="w-8 h-8 rounded-full skeleton-shimmer flex-shrink-0"></div> 1581 <div class="h-24 w-3/4 rounded-2xl skeleton-shimmer"></div> 1582 </div> 1583 <div class="flex justify-end"><div class="h-10 w-1/2 rounded-2xl skeleton-shimmer"></div></div> 1584 </div>`; 1585 1586 try { 1587 const token = await state.user.getIdToken(); 1588 const res = await fetch(`${API_BASE_URL}/chats/${chatId}`, { headers: { 'Authorization': `Bearer ${token}` } }); 1589 const data = await res.json(); 1590 1591 state.messages = []; 1592 container.innerHTML = ''; 1593 1594 if (data.messages && data.messages.length > 0) { 1595 data.messages.forEach(msg => { 1596 const role = msg.role === 'model' ? 'ai' : 'user'; 1597 state.messages.push({ role, content: msg.content }); 1598 renderMessage(role, msg.content); 1599 }); 1600 } else { 1601 container.innerHTML = `<div class="text-center p-8 text-gemini-textSecondary">Start conversation...</div>`; 1602 } 1603 1604 if(!state.isSharedView) fetchChats(); 1605 } catch (e) { 1606 console.error(e); 1607 container.innerHTML = `<div class="text-center text-red-400 p-4">Failed to load chat.</div>`; 1608 } 1609 } 1610 1611 function setPrompt(text) { 1612 const input = document.getElementById('user-input'); 1613 if(input) { 1614 input.value = text; 1615 input.focus(); 1616 autoResize(input); 1617 } 1618 } 1619 1620 function scrollToBottom() { 1621 const container = document.getElementById('chat-container'); 1622 container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' }); 1623 } 1624 1625 function renderMessage(role, content) { 1626 const container = document.querySelector('#chat-container > div'); 1627 const welcome = document.getElementById('welcome-message'); 1628 if (welcome) welcome.remove(); 1629 1630 let wrapper = container.lastElementChild; 1631 let bubble = null; 1632 1633 if (role === 'ai' && wrapper && wrapper.dataset.role === 'ai' && state.isTyping) { 1634 bubble = wrapper.querySelector('.prose'); 1635 } else { 1636 wrapper = document.createElement('div'); 1637 wrapper.className = `flex gap-4 w-full animate-fade-in ${role === 'user' ? 'justify-end' : 'justify-start group'}`; 1638 wrapper.dataset.role = role; 1639 1640 if (role === 'user') { 1641 wrapper.innerHTML = `<div class="msg-user text-sm md:text-base">${content}</div>`; 1642 } else { 1643 wrapper.innerHTML = ` 1644 <div class="flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center mt-1"> 1645 <svg class="w-6 h-6 animate-spin-slow-entry"><use href="#icon-ada"></use></svg> 1646 </div> 1647 <div class="flex-1 min-w-0 py-1"> 1648 <div class="prose prose-invert max-w-none text-sm md:text-base"></div> 1649 </div> 1650 `; 1651 bubble = wrapper.querySelector('.prose'); 1652 } 1653 container.appendChild(wrapper); 1654 } 1655 1656 if (role === 'ai') { 1657 if(!bubble) bubble = wrapper.querySelector('.prose'); 1658 1659 const customTagRegex = /<<<CODE_START>>>([\s\S]*?)<<<CODE_END>>>/g; 1660 const markdownRegex = /```(\w*)([\s\S]*?)```/g; 1661 1662 let processedContent = content; 1663 let extractedCode = ""; 1664 let lang = "PLAINTEXT"; 1665 1666 const cardReplacement = (code, language) => { 1667 extractedCode = code.trim(); 1668 if(language) lang = language.toUpperCase(); 1669 return `<div class="code-card group" onclick="toggleArtifactPanel()"> 1670 <div class="px-4 py-3 bg-[#252526] border-b border-[#333] flex items-center justify-between"> 1671 <div class="flex items-center gap-2"> 1672 <i data-lucide="code-2" class="w-4 h-4 text-gemini-blue"></i> 1673 <span class="text-xs font-mono text-[#cccccc]">Generated Code</span> 1674 </div> 1675 <span class="text-xs text-gemini-blue group-hover:underline">View Code →</span> 1676 </div> 1677 <div class="px-4 py-3 text-xs font-mono text-gray-400 bg-[#1e1e1e]"> 1678 // Code extracted to workspace... 1679 </div> 1680 </div>`; 1681 }; 1682 1683 processedContent = processedContent.replace(customTagRegex, (m, c) => cardReplacement(c)); 1684 processedContent = processedContent.replace(markdownRegex, (m, l, c) => cardReplacement(c, l)); 1685 1686 try { 1687 bubble.innerHTML = marked.parse(processedContent); 1688 } catch (e) { 1689 bubble.innerText = content; 1690 } 1691 1692 if (extractedCode) { 1693 updateArtifactPanel(extractedCode, lang); 1694 const indicator = document.getElementById('artifact-indicator'); 1695 if(indicator) indicator.classList.remove('hidden'); 1696 1697 if (window.innerWidth >= 1280) { 1698 const panel = document.getElementById('artifact-panel'); 1699 if (panel.classList.contains('translate-x-full')) { 1700 toggleArtifactPanel(); 1701 } 1702 } 1703 } 1704 } 1705 1706 scrollToBottom(); 1707 lucide.createIcons(); 1708 } 1709 1710 function renderTypingIndicator() { 1711 const container = document.querySelector('#chat-container > div'); 1712 const wrapper = document.createElement('div'); 1713 wrapper.id = 'typing-indicator'; 1714 wrapper.className = 'flex gap-4 w-full justify-start animate-fade-in'; 1715 wrapper.innerHTML = ` 1716 <div class="flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center mt-1"> 1717 <svg class="w-6 h-6 animate-pulse"><use href="#icon-ada"></use></svg> 1718 </div> 1719 <div class="py-3"> 1720 <div class="flex gap-1"> 1721 <div class="typing-dot"></div> 1722 <div class="typing-dot"></div> 1723 <div class="typing-dot"></div> 1724 </div> 1725 </div> 1726 `; 1727 container.appendChild(wrapper); 1728 scrollToBottom(); 1729 lucide.createIcons(); 1730 } 1731 1732 function removeTypingIndicator() { 1733 const el = document.getElementById('typing-indicator'); 1734 if (el) el.remove(); 1735 } 1736 1737 function updateArtifactPanel(code, lang) { 1738 const langDisplay = document.getElementById('lang-display'); 1739 const statusLang = document.getElementById('status-lang'); // Update status bar 1740 if(lang) { 1741 langDisplay.innerText = lang; 1742 statusLang.innerText = lang; // Sync status bar 1743 const modeMap = { 1744 'PYTHON': 'python', 1745 'JAVASCRIPT': 'javascript', 1746 'JS': 'javascript', 1747 'HTML': 'xml', 1748 'CSS': 'css' 1749 }; 1750 if(editor) editor.setOption("mode", modeMap[lang] || "javascript"); 1751 } 1752 1753 if(editor) { 1754 editor.setValue(code); 1755 setTimeout(() => editor.refresh(), 100); 1756 } 1757 } 1758 1759 function copyArtifact() { 1760 if(editor) { 1761 const content = editor.getValue(); 1762 navigator.clipboard.writeText(content); 1763 showToast("Code copied to clipboard!", "success"); 1764 } 1765 } 1766 1767 function handleEnter(e) { 1768 if (e.key === 'Enter' && !e.shiftKey) { 1769 e.preventDefault(); 1770 handleFormSubmit(e); 1771 } 1772 } 1773 1774 async function handleFormSubmit(e) { 1775 if (e) e.preventDefault(); 1776 const input = document.getElementById('user-input'); 1777 const message = input.value.trim(); 1778 if (!message || state.isTyping || !state.user) return; 1779 1780 // UI Updates 1781 renderMessage('user', message); 1782 input.value = ''; 1783 autoResize(input); 1784 state.isTyping = true; 1785 renderTypingIndicator(); 1786 1787 try { 1788 const token = await state.user.getIdToken(); 1789 const response = await fetch(`${API_BASE_URL}/chat`, { 1790 method: 'POST', 1791 headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, 1792 body: JSON.stringify({ 1793 message: message, 1794 history: state.messages.map(m => ({ user: m.role === 'user' ? m.content : '', model: m.role === 'ai' ? m.content : '' })), 1795 sessionId: state.currentChatId 1796 }) 1797 }); 1798 1799 if (!response.ok) throw new Error("API Error"); 1800 1801 removeTypingIndicator(); 1802 const reader = response.body.getReader(); 1803 const decoder = new TextDecoder(); 1804 let fullText = ""; 1805 renderMessage('ai', ''); 1806 1807 while (true) { 1808 const { done, value } = await reader.read(); 1809 if (done) break; 1810 fullText += decoder.decode(value); 1811 renderMessage('ai', fullText); 1812 } 1813 1814 state.messages.push({ role: 'user', content: message }); 1815 state.messages.push({ role: 'ai', content: fullText }); 1816 setTimeout(fetchChats, 1500); 1817 1818 } catch (error) { 1819 removeTypingIndicator(); 1820 renderMessage('ai', "I'm having trouble connecting right now."); 1821 showToast("Error generating response", "error"); 1822 } finally { 1823 state.isTyping = false; 1824 } 1825 } 1826 1827 function startNewChat() { 1828 state.messages = []; 1829 state.currentChatId = 'session_' + Date.now(); 1830 1831 const userName = state.user ? (state.user.email || state.user.displayName || 'Student') : 'Student'; 1832 1833 const container = document.querySelector('#chat-container > div'); 1834 container.innerHTML = ` 1835 <div id="welcome-message" class="flex flex-col items-start justify-center min-h-[50vh] space-y-8 animate-slide-up px-2"> 1836 <div class="space-y-2 w-full"> 1837 <div class="w-12 h-12 mb-4 animate-spin-slow-entry"> 1838 <svg class="w-full h-full drop-shadow-[0_0_15px_rgba(168,199,250,0.5)]"><use href="#icon-ada"></use></svg> 1839 </div> 1840 <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"> 1841 Hello, <span id="welcome-name" class="text-white truncate block md:inline max-w-[200px] md:max-w-none">${userName}</span> 1842 </h1> 1843 <!-- UPDATED COLOR --> 1844 <h2 class="text-3xl md:text-5xl font-medium text-gemini-textSecondary/80">How can I help you code today?</h2> 1845 </div> 1846 <div class="hidden md:flex flex-wrap gap-3 w-full"> 1847 <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"> 1848 <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> 1849 </button> 1850 <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"> 1851 <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> 1852 </button> 1853 <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"> 1854 <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> 1855 </button> 1856 </div> 1857 </div>`; 1858 lucide.createIcons(); 1859 fetchChats(); 1860 } 1861 1862 window.onload = initApp; 1863 1864 </script> 1865 </body> 1866 </html>