| <!DOCTYPE html> |
| <html lang="en"> |
|
|
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Nexa AI - Your Intelligent Assistant</title> |
| <meta name="description" content="Chat Smarter, Create Faster with Nexa AI. Your personal AI assistant for work and creativity, featuring advanced chat and image generation."> |
| |
| |
| <meta property="og:type" content="website"> |
| <meta property="og:url" content="https://nexa-ai.hf.space/"> |
| <meta property="og:title" content="Nexa AI - Your Intelligent Assistant"> |
| <meta property="og:description" content="Chat Smarter, Create Faster with Nexa AI. Your personal AI assistant for work and creativity."> |
| <meta property="og:image" content="https://nexa-ai.hf.space/og-image.png"> |
|
|
| |
| <meta property="twitter:card" content="summary_large_image"> |
| <meta property="twitter:title" content="Nexa AI - Your Intelligent Assistant"> |
| <meta property="twitter:description" content="Chat Smarter, Create Faster with Nexa AI. Your personal AI assistant for work and creativity."> |
|
|
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet"> |
| <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> |
| <script src="https://unpkg.com/lucide@latest"></script> |
| <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
| <style> |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| } |
| |
| :root { |
| --bg-primary: #050505; |
| --bg-secondary: #0f0f0f; |
| --bg-tertiary: #1a1a1a; |
| --text-primary: #ffffff; |
| --text-secondary: #a3a3a3; |
| --border-color: rgba(255, 255, 255, 0.08); |
| --accent-blue: #10a37f; |
| --accent-hover: #14b88f; |
| } |
| |
| body.light-mode { |
| --bg-primary: #ffffff; |
| --bg-secondary: #f7f7f8; |
| --bg-tertiary: #ececec; |
| --text-primary: #374151; |
| --text-secondary: #6b7280; |
| --border-color: #e5e5e5; |
| --accent-blue: #10a37f; |
| --accent-hover: #1a7f64; |
| } |
| |
| body { |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; |
| background: var(--bg-primary); |
| color: var(--text-primary); |
| height: 100vh; |
| overflow: hidden; |
| position: relative; |
| } |
| |
| |
| ::-webkit-scrollbar { |
| width: 8px; |
| } |
| |
| ::-webkit-scrollbar-track { |
| background: transparent; |
| } |
| |
| ::-webkit-scrollbar-thumb { |
| background: rgba(255, 255, 255, 0.1); |
| border-radius: 10px; |
| border: 2px solid transparent; |
| background-clip: content-box; |
| } |
| |
| ::-webkit-scrollbar-thumb:hover { |
| background: rgba(255, 255, 255, 0.2); |
| border: 2px solid transparent; |
| background-clip: content-box; |
| } |
| |
| .chat-history::-webkit-scrollbar { |
| width: 6px; |
| } |
| |
| .app-container { |
| display: flex; |
| height: 100vh; |
| position: relative; |
| } |
| |
| |
| .main-background-lighting { |
| position: absolute; |
| top: 0; |
| left: 0; |
| right: 0; |
| bottom: 0; |
| pointer-events: none; |
| z-index: 0; |
| overflow: hidden; |
| } |
| |
| .ambient-glow { |
| position: absolute; |
| border-radius: 50%; |
| filter: blur(80px); |
| opacity: 0; |
| transition: opacity 1.2s ease, transform 1s ease; |
| } |
| |
| .ambient-glow.glow-1 { |
| width: 400px; |
| height: 400px; |
| background: rgba(16, 163, 127, 0.15); |
| top: -100px; |
| right: -100px; |
| } |
| |
| .ambient-glow.glow-2 { |
| width: 500px; |
| height: 500px; |
| background: rgba(16, 163, 127, 0.1); |
| bottom: -200px; |
| left: -100px; |
| } |
| |
| |
| .main-area.hero-mode .ambient-glow, |
| body.glowing .ambient-glow { |
| opacity: 1; |
| transform: scale(1.1); |
| animation: floatGlow 10s infinite alternate ease-in-out; |
| } |
| |
| @keyframes floatGlow { |
| 0% { |
| transform: scale(1) translate(0, 0); |
| } |
| |
| 100% { |
| transform: scale(1.2) translate(20px, -20px); |
| } |
| } |
| |
| |
| |
| .sidebar-overlay { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background: rgba(0, 0, 0, 0.5); |
| backdrop-filter: blur(4px); |
| z-index: 99; |
| opacity: 0; |
| visibility: hidden; |
| transition: all 0.3s ease; |
| } |
| |
| .sidebar-overlay.active { |
| opacity: 1; |
| visibility: visible; |
| } |
| |
| .sidebar { |
| width: 280px; |
| background-color: var(--bg-secondary); |
| display: flex; |
| flex-direction: column; |
| padding: 16px; |
| border-right: 1px solid var(--border-color); |
| transition: transform 0.4s var(--spring-easing), margin-left 0.4s ease, width 0.3s ease; |
| z-index: 100; |
| height: 100vh; |
| overflow: hidden; |
| position: relative; |
| } |
| |
| .sidebar-header-mobile { |
| display: none; |
| justify-content: space-between; |
| align-items: center; |
| margin-bottom: 20px; |
| padding-bottom: 12px; |
| border-bottom: 1px solid var(--border-color); |
| } |
| |
| .sidebar-close-btn { |
| background: transparent; |
| border: none; |
| color: var(--text-secondary); |
| cursor: pointer; |
| padding: 8px; |
| border-radius: 50%; |
| transition: all 0.2s; |
| } |
| |
| .sidebar-close-btn:hover { |
| background: rgba(255, 255, 255, 0.05); |
| color: var(--text-primary); |
| } |
| |
| .top-bar { |
| position: sticky; |
| top: 0; |
| z-index: 90; |
| background: rgba(13, 13, 13, 0.8); |
| backdrop-filter: blur(12px); |
| padding: 12px 20px; |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| border-bottom: 1px solid var(--border-color); |
| } |
| |
| .hamburger-menu { |
| display: none; |
| background: transparent; |
| border: none; |
| color: var(--text-primary); |
| cursor: pointer; |
| padding: 8px; |
| border-radius: 8px; |
| margin-right: 12px; |
| } |
| |
| @media (max-width: 768px) { |
| .sidebar { |
| position: fixed; |
| left: 0; |
| top: 0; |
| width: 80%; |
| max-width: 300px; |
| transform: translateX(-100%); |
| box-shadow: 20px 0 50px rgba(0,0,0,0.5); |
| } |
| |
| .sidebar.open { |
| transform: translateX(0); |
| } |
| |
| .sidebar-header-mobile { |
| display: flex; |
| } |
| |
| .hamburger-menu { |
| display: flex; |
| } |
| |
| .model-selector svg { |
| display: none; |
| } |
| |
| body.sidebar-open { |
| overflow: hidden; |
| } |
| } |
| |
| |
| :root { |
| --spring-easing: cubic-bezier(0.175, 0.885, 0.32, 1.275); |
| --smooth-easing: cubic-bezier(0.4, 0, 0.2, 1); |
| --premium-blur: blur(20px); |
| --glass-bg: rgba(255, 255, 255, 0.03); |
| --glass-border: rgba(255, 255, 255, 0.08); |
| --accent-glow: 0 0 20px rgba(16, 163, 127, 0.2); |
| } |
| |
| .image-card, .tool-output-card { |
| min-height: 100px; |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
| contain: content; |
| } |
| |
| .img-skeleton { |
| background: linear-gradient(90deg, rgba(255,255,255,0.03) 25%, rgba(255,255,255,0.08) 50%, rgba(255,255,255,0.03) 75%); |
| background-size: 200% 100%; |
| animation: skeleton-loading 1.5s infinite; |
| } |
| |
| @keyframes skeleton-loading { |
| 0% { background-position: 200% 0; } |
| 100% { background-position: -200% 0; } |
| } |
| |
| |
| .message-content { |
| min-width: 0; |
| overflow-wrap: break-word; |
| } |
| |
| |
| .message { |
| will-change: transform, opacity; |
| animation: premiumFadeUp 0.5s var(--spring-easing) forwards; |
| opacity: 0; |
| } |
| |
| @keyframes premiumFadeUp { |
| from { |
| opacity: 0; |
| transform: translateY(24px) scale(0.96); |
| filter: blur(8px); |
| } |
| to { |
| opacity: 1; |
| transform: translateY(0) scale(1); |
| filter: blur(0); |
| } |
| } |
| |
| |
| .suggestion-card { |
| animation: premiumFadeUp 0.5s var(--spring-easing) backwards; |
| } |
| .suggestion-card:nth-child(1) { animation-delay: 0.1s; } |
| .suggestion-card:nth-child(2) { animation-delay: 0.15s; } |
| .suggestion-card:nth-child(3) { animation-delay: 0.2s; } |
| .suggestion-card:nth-child(4) { animation-delay: 0.25s; } |
| |
| |
| .sidebar { |
| transition: width 0.4s var(--smooth-easing), transform 0.4s var(--smooth-easing), background 0.4s ease; |
| } |
| |
| .conversation-item { |
| transition: all 0.25s var(--smooth-easing); |
| transform-origin: left; |
| } |
| |
| .conversation-item:hover { |
| transform: translateX(4px); |
| background: rgba(255, 255, 255, 0.06); |
| } |
| |
| .conversation-item.active { |
| box-shadow: var(--accent-glow); |
| } |
| |
| |
| .btn-primary, .btn-secondary, .send-button, .tool-btn, .new-chat-btn { |
| transition: all 0.3s var(--smooth-easing) !important; |
| } |
| |
| .btn-primary:active, .btn-secondary:active, .send-button:active { |
| transform: scale(0.95); |
| } |
| |
| .tool-btn:hover { |
| transform: translateY(-3px) scale(1.05); |
| background: rgba(16, 163, 127, 0.15); |
| color: var(--accent-blue); |
| box-shadow: 0 8px 20px rgba(0,0,0,0.2); |
| } |
| |
| |
| .input-wrapper { |
| transition: all 0.4s var(--smooth-easing); |
| } |
| |
| .input-wrapper:focus-within { |
| transform: translateY(-2px); |
| border-color: rgba(16, 163, 127, 0.4); |
| box-shadow: 0 12px 40px rgba(0,0,0,0.4), var(--accent-glow); |
| background: rgba(30, 30, 30, 0.85); |
| } |
| |
| |
| .message.ai .message-avatar { |
| animation: floatingAvatar 3s ease-in-out infinite; |
| } |
| |
| @keyframes floatingAvatar { |
| 0%, 100% { transform: translateY(0); } |
| 50% { transform: translateY(-4px); } |
| } |
| |
| |
| .gen-loader { |
| animation: pulseGlow 2s ease-in-out infinite; |
| } |
| |
| .mermaid-render-container { |
| background: rgba(255, 255, 255, 0.03); |
| border-radius: 12px; |
| padding: 20px; |
| margin: 15px 0; |
| display: flex; |
| justify-content: center; |
| border: 1px solid var(--border-color); |
| } |
| |
| .chart-render-canvas { |
| background: rgba(255, 255, 255, 0.03); |
| border-radius: 12px; |
| padding: 20px; |
| margin: 15px 0; |
| border: 1px solid var(--border-color); |
| width: 100% !important; |
| } |
| |
| .error-box { |
| color: #f87171; |
| background: rgba(239, 68, 68, 0.1); |
| padding: 10px; |
| border-radius: 8px; |
| font-size: 12px; |
| } |
| |
| @keyframes pulseGlow { |
| 0%, 100% { opacity: 0.6; transform: scale(1); } |
| 50% { opacity: 1; transform: scale(1.1); filter: drop-shadow(0 0 8px var(--accent-blue)); } |
| } |
| |
| |
| .sidebar.collapsed { |
| width: 72px; |
| padding: 16px 12px; |
| } |
| |
| .sidebar.collapsed .sidebar-section-title, |
| .sidebar.collapsed .research-toggle-container span, |
| .sidebar.collapsed .new-chat-btn span, |
| .sidebar.collapsed .menu-item span, |
| .sidebar.collapsed .conversation-item span, |
| .sidebar.collapsed .sidebar-footer .menu-item span, |
| .sidebar.collapsed .mode-dropdown-trigger span, |
| .sidebar.collapsed .mode-dropdown-trigger i:last-child { |
| display: none; |
| } |
| |
| .sidebar.collapsed .new-chat-btn { |
| padding: 12px; |
| width: 48px; |
| height: 48px; |
| justify-content: center; |
| } |
| |
| .sidebar.collapsed .conversation-item { |
| width: 48px; |
| height: 48px; |
| padding: 0; |
| justify-content: center; |
| border-radius: 12px; |
| } |
| |
| .sidebar.collapsed .conversation-item i { |
| margin: 0; |
| width: 20px; |
| height: 20px; |
| } |
| |
| |
| .streaming-text { |
| display: inline; |
| animation: textFadeIn 0.3s ease-out forwards; |
| } |
| |
| @keyframes textFadeIn { |
| from { opacity: 0; filter: blur(2px); transform: translateY(2px); } |
| to { opacity: 1; filter: blur(0); transform: translateY(0); } |
| } |
| |
| @keyframes floatingLogo { |
| 0%, 100% { transform: translateY(0); } |
| 50% { transform: translateY(-10px); } |
| } |
| |
| |
| .message.ai .message-content { |
| background: var(--glass-bg); |
| backdrop-filter: var(--premium-blur); |
| border: 1px solid var(--glass-border); |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); |
| line-height: 1.7; |
| letter-spacing: 0.01em; |
| } |
| |
| |
| .message-content h3 { |
| margin-top: 24px; |
| margin-bottom: 12px; |
| color: #10a37f; |
| font-size: 1.1em; |
| font-weight: 700; |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| .message-content p { |
| margin-bottom: 16px; |
| } |
| |
| .message-content ul, .message-content ol { |
| margin-bottom: 16px; |
| padding-left: 20px; |
| } |
| |
| .message-content li { |
| margin-bottom: 8px; |
| } |
| |
| .message-content strong { |
| color: #10a37f; |
| } |
| |
| .important-highlight { |
| background: rgba(16, 163, 127, 0.1); |
| color: #10a37f; |
| padding: 2px 6px; |
| border-radius: 4px; |
| font-weight: 600; |
| } |
| |
| |
| .input-wrapper:focus-within { |
| border-color: rgba(16, 163, 127, 0.4); |
| box-shadow: 0 0 0 4px rgba(16, 163, 127, 0.1), 0 20px 50px rgba(0,0,0,0.4); |
| } |
| |
| .settings-modal-overlay { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background: rgba(0, 0, 0, 0.8); |
| backdrop-filter: blur(12px); |
| z-index: 3000; |
| display: none; |
| align-items: center; |
| justify-content: center; |
| opacity: 0; |
| transition: all 0.3s ease; |
| } |
| |
| .settings-modal-overlay.open, |
| .settings-modal-overlay.show { |
| display: flex; |
| opacity: 1; |
| } |
| |
| .settings-modal { |
| background: var(--bg-secondary); |
| border: 1px solid var(--border-color); |
| border-radius: 24px; |
| width: 90%; |
| max-width: 500px; |
| padding: 32px; |
| position: relative; |
| box-shadow: 0 25px 50px rgba(0,0,0,0.5); |
| animation: slideUp 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); |
| } |
| |
| .settings-header { |
| margin-bottom: 24px; |
| } |
| |
| .settings-title { |
| font-size: 24px; |
| font-weight: 700; |
| margin-bottom: 8px; |
| } |
| |
| .settings-subtitle { |
| color: var(--text-secondary); |
| font-size: 14px; |
| } |
| |
| .settings-section { |
| margin-bottom: 24px; |
| padding-bottom: 20px; |
| border-bottom: 1px solid var(--border-color); |
| } |
| |
| .settings-section:last-child { |
| border-bottom: none; |
| } |
| |
| .settings-section-title { |
| font-size: 12px; |
| font-weight: 700; |
| text-transform: uppercase; |
| color: var(--text-secondary); |
| margin-bottom: 16px; |
| letter-spacing: 0.05em; |
| } |
| |
| .settings-item { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| margin-bottom: 16px; |
| } |
| |
| .settings-item-info h4 { |
| font-size: 15px; |
| margin-bottom: 4px; |
| } |
| |
| .settings-item-info p { |
| font-size: 12px; |
| color: var(--text-secondary); |
| } |
| |
| .settings-btn { |
| padding: 8px 16px; |
| border-radius: 10px; |
| font-size: 13px; |
| font-weight: 600; |
| cursor: pointer; |
| transition: all 0.2s; |
| border: 1px solid var(--border-color); |
| background: rgba(255, 255, 255, 0.03); |
| color: var(--text-primary); |
| } |
| |
| .settings-btn:hover { |
| background: rgba(255, 255, 255, 0.08); |
| } |
| |
| .settings-btn.danger { |
| color: #ef4444; |
| border-color: rgba(239, 68, 68, 0.2); |
| background: rgba(239, 68, 68, 0.05); |
| } |
| |
| .settings-btn.danger:hover { |
| background: rgba(239, 68, 68, 0.1); |
| border-color: rgba(239, 68, 68, 0.3); |
| } |
| |
| .settings-close { |
| position: absolute; |
| top: 20px; |
| right: 20px; |
| background: none; |
| border: none; |
| color: var(--text-secondary); |
| cursor: pointer; |
| padding: 8px; |
| border-radius: 50%; |
| transition: all 0.2s; |
| } |
| |
| .settings-close:hover { |
| background: rgba(255, 255, 255, 0.05); |
| color: var(--text-primary); |
| } |
| |
| |
| .confirm-overlay { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background: rgba(0, 0, 0, 0.6); |
| backdrop-filter: blur(4px); |
| z-index: 4000; |
| display: none; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .confirm-overlay.open { |
| display: flex; |
| } |
| |
| .confirm-card { |
| background: var(--bg-secondary); |
| border: 1px solid var(--border-color); |
| border-radius: 20px; |
| padding: 24px; |
| max-width: 400px; |
| width: 90%; |
| text-align: center; |
| box-shadow: 0 20px 40px rgba(0,0,0,0.4); |
| } |
| |
| .confirm-title { |
| font-size: 18px; |
| font-weight: 700; |
| margin-bottom: 12px; |
| color: #ef4444; |
| } |
| |
| .confirm-text { |
| font-size: 14px; |
| color: var(--text-secondary); |
| margin-bottom: 24px; |
| line-height: 1.5; |
| } |
| |
| .confirm-actions { |
| display: flex; |
| gap: 12px; |
| } |
| |
| .confirm-btn { |
| flex: 1; |
| padding: 10px; |
| border-radius: 12px; |
| font-size: 14px; |
| font-weight: 600; |
| cursor: pointer; |
| border: none; |
| } |
| |
| .confirm-btn.cancel { |
| background: rgba(255, 255, 255, 0.05); |
| color: var(--text-primary); |
| } |
| |
| .confirm-btn.delete { |
| background: #ef4444; |
| color: white; |
| } |
| |
| |
| .history-date-group { |
| font-size: 11px; |
| font-weight: 700; |
| color: var(--text-secondary); |
| text-transform: uppercase; |
| letter-spacing: 0.1em; |
| margin: 20px 0 8px 12px; |
| opacity: 0.6; |
| } |
| |
| .new-chat-btn { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| padding: 10px 16px; |
| background: rgba(255, 255, 255, 0.05); |
| border: 1px solid var(--border-color); |
| border-radius: 12px; |
| color: var(--text-primary); |
| font-size: 14px; |
| font-weight: 600; |
| cursor: pointer; |
| transition: all 0.2s; |
| width: 100%; |
| margin-bottom: 20px; |
| justify-content: flex-start; |
| position: relative; |
| } |
| |
| .new-chat-btn:hover { |
| background: rgba(255, 255, 255, 0.08); |
| border-color: rgba(255, 255, 255, 0.2); |
| } |
| |
| .new-chat-btn .shortcut-hint { |
| margin-left: auto; |
| display: flex; |
| gap: 4px; |
| } |
| |
| .new-chat-btn .key { |
| background: rgba(255, 255, 255, 0.1); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| border-radius: 4px; |
| padding: 2px 6px; |
| font-size: 10px; |
| color: var(--text-secondary); |
| font-family: inherit; |
| } |
| |
| .conversation-list { |
| flex: 1; |
| display: flex; |
| flex-direction: column; |
| gap: 4px; |
| overflow-y: auto; |
| margin-bottom: 16px; |
| padding-right: 4px; |
| } |
| |
| |
| .conversation-list::-webkit-scrollbar { |
| width: 4px; |
| } |
| |
| .conversation-list::-webkit-scrollbar-thumb { |
| background: rgba(255, 255, 255, 0.1); |
| border-radius: 4px; |
| } |
| |
| .conversation-item { |
| padding: 12px 16px; |
| border-radius: 12px; |
| cursor: pointer; |
| font-size: 14px; |
| color: var(--text-secondary); |
| white-space: nowrap; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); |
| position: relative; |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| border: 1px solid transparent; |
| margin: 2px 0; |
| } |
| |
| .conversation-item:hover { |
| background: rgba(255, 255, 255, 0.04); |
| color: var(--text-primary); |
| transform: translateX(4px); |
| } |
| |
| .conversation-item.active { |
| background: #2f2f2f; |
| color: white; |
| font-weight: 500; |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); |
| } |
| |
| .conversation-item .delete-btn { |
| position: absolute; |
| right: 12px; |
| top: 50%; |
| transform: translateY(-50%); |
| background: #2f2f2f; |
| border: none; |
| color: var(--text-secondary); |
| cursor: pointer; |
| padding: 6px; |
| border-radius: 8px; |
| opacity: 0; |
| transition: all 0.2s; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .conversation-item.active .delete-btn { |
| background: #3f3f3f; |
| color: white; |
| } |
| |
| .sidebar-footer { |
| border-top: 1px solid var(--border-color); |
| padding: 16px 8px; |
| margin-top: auto; |
| display: flex; |
| flex-direction: column; |
| gap: 8px; |
| flex-shrink: 0; |
| background: var(--bg-secondary); |
| } |
| |
| .menu-item { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| padding: 10px 12px; |
| border-radius: 8px; |
| cursor: pointer; |
| font-size: 14px; |
| color: var(--text-secondary); |
| transition: background 0.2s; |
| margin-bottom: 4px; |
| } |
| |
| .menu-item:hover { |
| background: var(--bg-tertiary); |
| } |
| |
| .menu-item.upgrade { |
| background: linear-gradient(135deg, #10a37f 0%, #1a7f64 100%); |
| color: white; |
| font-weight: 600; |
| } |
| |
| |
| .main-area { |
| flex: 1; |
| display: flex; |
| flex-direction: column; |
| position: relative; |
| overflow: hidden; |
| height: 100vh; |
| } |
| |
| |
| :root { |
| --3d-perspective: 1200px; |
| --3d-tilt-intensity: 15deg; |
| --transition-smooth: all 0.5s cubic-bezier(0.19, 1, 0.22, 1); |
| } |
| |
| body { |
| perspective: var(--3d-perspective); |
| transform-style: preserve-3d; |
| } |
| |
| .main-area { |
| transform-style: preserve-3d; |
| transition: var(--transition-smooth); |
| } |
| |
| |
| .suggestion-card, .feature-item, .image-card, .tool-output-card { |
| position: relative; |
| transform-style: preserve-3d; |
| transition: transform 0.3s cubic-bezier(0.23, 1, 0.32, 1), box-shadow 0.3s ease; |
| will-change: transform; |
| } |
| |
| .suggestion-card:hover, .feature-item:hover { |
| transform: translateZ(20px) rotateX(2deg) rotateY(-2deg); |
| box-shadow: 0 20px 40px rgba(0,0,0,0.4), 0 0 0 1px var(--glass-border); |
| } |
| |
| |
| #canvas3d { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| z-index: -1; |
| pointer-events: none; |
| opacity: 0.6; |
| } |
| |
| |
| .page-fade-enter { |
| opacity: 0; |
| transform: scale(0.98) translateZ(-50px); |
| } |
| |
| .page-fade-exit { |
| opacity: 0; |
| transform: scale(1.02) translateZ(50px); |
| } |
| |
| .transitioning { |
| pointer-events: none; |
| filter: blur(4px); |
| transition: var(--transition-smooth); |
| } |
| |
| |
| .send-button, .action-btn, .new-chat-btn { |
| transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275), box-shadow 0.2s ease; |
| } |
| |
| .send-button:hover, .action-btn:hover { |
| transform: translateY(-2px) scale(1.05); |
| box-shadow: 0 5px 15px rgba(16, 163, 127, 0.3); |
| } |
| |
| .send-button:active { |
| transform: translateY(0) scale(0.95); |
| } |
| |
| |
| .grid-overlay { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background-image: linear-gradient(var(--glass-border) 1px, transparent 1px), |
| linear-gradient(90deg, var(--glass-border) 1px, transparent 1px); |
| background-size: 50px 50px; |
| perspective: 500px; |
| transform: rotateX(60deg) translateY(-50%) translateZ(-200px); |
| opacity: 0.05; |
| z-index: -1; |
| mask-image: radial-gradient(circle at center, black, transparent 80%); |
| pointer-events: none; |
| } |
| |
| |
| .message { |
| transform-origin: left center; |
| animation: messageAppear3d 0.6s cubic-bezier(0.23, 1, 0.32, 1) forwards; |
| opacity: 0; |
| } |
| |
| @keyframes messageAppear3d { |
| 0% { |
| opacity: 0; |
| transform: translateX(-20px) rotateY(-15deg) scale(0.95); |
| } |
| 100% { |
| opacity: 1; |
| transform: translateX(0) rotateY(0) scale(1); |
| } |
| } |
| |
| .chat-history { |
| flex: 1; |
| overflow-y: auto; |
| padding: 20px; |
| display: flex; |
| flex-direction: column; |
| gap: 16px; |
| z-index: 2; |
| transition: opacity 0.6s ease; |
| } |
| |
| |
| .main-area.hero-mode .chat-history { |
| flex: 1; |
| opacity: 1; |
| } |
| |
| .main-area.hero-mode .chat-history.empty-mode { |
| justify-content: center; |
| align-items: center; |
| } |
| |
| .top-bar { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| padding: 12px 20px; |
| border-bottom: 1px solid var(--border-color); |
| background: var(--bg-primary); |
| } |
| |
| .model-selector { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| padding: 8px 12px; |
| border-radius: 8px; |
| cursor: pointer; |
| font-weight: 600; |
| font-size: 14px; |
| transition: background 0.2s; |
| } |
| |
| .model-selector:hover { |
| background: var(--bg-tertiary); |
| } |
| |
| .account-btn { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| padding: 6px 10px; |
| border-radius: 8px; |
| background: none; |
| border: none; |
| cursor: pointer; |
| color: var(--text-primary); |
| font-size: 14px; |
| transition: background 0.2s; |
| } |
| |
| .account-btn:hover { |
| background: var(--bg-tertiary); |
| } |
| |
| .account-avatar { |
| width: 28px; |
| height: 28px; |
| border-radius: 4px; |
| background: linear-gradient(135deg, #10a37f 0%, #1a7f64 100%); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| color: white; |
| font-size: 12px; |
| font-weight: 600; |
| } |
| |
| .account-dropdown { |
| position: absolute; |
| top: 100%; |
| right: 0; |
| margin-top: 4px; |
| background: var(--bg-secondary); |
| border: 1px solid var(--border-color); |
| border-radius: 8px; |
| padding: 8px; |
| min-width: 200px; |
| display: none; |
| z-index: 1000; |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); |
| } |
| |
| .account-dropdown.show { |
| display: block; |
| } |
| |
| .dropdown-item { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| padding: 10px 12px; |
| border-radius: 6px; |
| cursor: pointer; |
| font-size: 14px; |
| color: var(--text-primary); |
| transition: background 0.2s; |
| } |
| |
| .dropdown-item:hover { |
| background: var(--bg-tertiary); |
| } |
| |
| |
| .empty-state { |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| height: 100%; |
| text-align: center; |
| color: var(--text-secondary); |
| animation: fadeIn 0.8s ease-out; |
| transition: opacity 0.4s ease, transform 0.4s ease; |
| } |
| |
| .empty-state h2 { |
| font-size: 28px; |
| font-weight: 600; |
| margin-bottom: 24px; |
| color: var(--text-primary); |
| } |
| |
| .suggestions-grid { |
| display: grid; |
| grid-template-columns: repeat(2, 1fr); |
| gap: 12px; |
| margin-top: 30px; |
| width: 100%; |
| max-width: 600px; |
| } |
| |
| .suggestion-card { |
| background: rgba(40, 40, 40, 0.4); |
| border: 1px solid var(--border-color); |
| padding: 16px 20px; |
| border-radius: 12px; |
| text-align: left; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| backdrop-filter: blur(8px); |
| } |
| |
| .suggestion-card:hover { |
| background: rgba(60, 60, 60, 0.6); |
| transform: translateY(-2px); |
| border-color: rgba(255, 255, 255, 0.15); |
| } |
| |
| .suggestion-card h4 { |
| font-size: 14px; |
| font-weight: 500; |
| color: var(--text-primary); |
| margin-bottom: 6px; |
| } |
| |
| .suggestion-card p { |
| font-size: 13px; |
| color: var(--text-secondary); |
| } |
| |
| |
| .tool-output-card { |
| background: rgba(30, 30, 30, 0.4); |
| border: 1px solid var(--border-color); |
| border-radius: 16px; |
| padding: 16px; |
| margin-top: 12px; |
| backdrop-filter: blur(8px); |
| animation: slideUp 0.3s ease-out; |
| width: 100%; |
| max-width: 600px; |
| } |
| |
| .tool-output-header { |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| margin-bottom: 12px; |
| padding-bottom: 8px; |
| border-bottom: 1px solid var(--border-color); |
| } |
| |
| .tool-output-header i { |
| color: var(--accent-blue); |
| width: 18px; |
| height: 18px; |
| } |
| |
| .tool-output-header span { |
| font-size: 14px; |
| font-weight: 600; |
| color: var(--text-primary); |
| } |
| |
| |
| .search-results { |
| display: flex; |
| flex-direction: column; |
| gap: 10px; |
| max-height: 300px; |
| overflow-y: auto; |
| padding: 12px; |
| background: rgba(0, 0, 0, 0.2); |
| border-radius: 12px; |
| margin-bottom: 16px; |
| border: 1px solid rgba(255, 255, 255, 0.05); |
| } |
| |
| .search-card { |
| background: rgba(45, 45, 45, 0.4); |
| border: 1px solid rgba(255, 255, 255, 0.05); |
| border-radius: 10px; |
| padding: 10px 14px; |
| text-decoration: none; |
| color: inherit; |
| transition: all 0.2s ease; |
| display: block; |
| } |
| |
| .search-card:hover { |
| background: rgba(55, 55, 55, 0.6); |
| border-color: #10a37f; |
| transform: translateX(4px); |
| } |
| |
| .search-card h5 { |
| color: #10a37f; |
| font-size: 13px; |
| margin-bottom: 4px; |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| font-weight: 600; |
| } |
| |
| .search-card p { |
| font-size: 12px; |
| color: var(--text-secondary); |
| line-height: 1.4; |
| display: -webkit-box; |
| -webkit-line-clamp: 2; |
| -webkit-box-orient: vertical; |
| overflow: hidden; |
| } |
| |
| |
| .pricing-container { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background: var(--bg-primary); |
| z-index: 5000; |
| display: none; |
| flex-direction: column; |
| align-items: center; |
| justify-content: flex-start; |
| padding: 80px 20px; |
| overflow-y: auto; |
| } |
| |
| .pricing-header { |
| text-align: center; |
| margin-bottom: 50px; |
| } |
| |
| .pricing-header h1 { |
| font-size: 36px; |
| font-weight: 700; |
| margin-bottom: 12px; |
| } |
| |
| .pricing-header p { |
| color: var(--text-secondary); |
| font-size: 18px; |
| } |
| |
| .pricing-grid { |
| display: flex; |
| flex-wrap: wrap; |
| justify-content: center; |
| gap: 30px; |
| max-width: 1000px; |
| width: 100%; |
| } |
| |
| .pricing-card { |
| background: var(--bg-secondary); |
| border: 1px solid var(--border-color); |
| border-radius: 24px; |
| padding: 40px; |
| display: flex; |
| flex-direction: column; |
| width: 350px; |
| transition: transform 0.3s ease, border-color 0.3s ease; |
| position: relative; |
| } |
| |
| .pricing-card:hover { |
| transform: translateY(-10px); |
| border-color: rgba(16, 163, 127, 0.4); |
| } |
| |
| .pricing-card.featured { |
| border: 2px solid var(--accent-blue); |
| background: rgba(16, 163, 127, 0.02); |
| } |
| |
| .pricing-card .badge { |
| position: absolute; |
| top: 20px; |
| right: 20px; |
| background: var(--accent-blue); |
| color: white; |
| padding: 4px 12px; |
| border-radius: 20px; |
| font-size: 12px; |
| font-weight: 700; |
| text-transform: uppercase; |
| } |
| |
| .pricing-card h3 { |
| font-size: 24px; |
| margin-bottom: 12px; |
| } |
| |
| .pricing-card .price { |
| font-size: 48px; |
| font-weight: 800; |
| margin-bottom: 30px; |
| } |
| |
| .pricing-card .price span { |
| font-size: 16px; |
| color: var(--text-secondary); |
| font-weight: 400; |
| } |
| |
| .pricing-card ul { |
| list-style: none; |
| margin-bottom: 40px; |
| flex: 1; |
| padding: 0; |
| } |
| |
| .pricing-card ul li { |
| margin-bottom: 16px; |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| color: var(--text-secondary); |
| font-size: 15px; |
| } |
| |
| .pricing-card ul li i { |
| color: var(--accent-blue); |
| width: 18px; |
| height: 18px; |
| } |
| |
| .pricing-card .btn-primary { |
| text-align: center; |
| padding: 14px; |
| border-radius: 12px; |
| text-decoration: none; |
| font-weight: 700; |
| transition: all 0.2s; |
| cursor: pointer; |
| } |
| |
| .pricing-card.featured .btn-primary { |
| background: var(--accent-blue); |
| color: white; |
| border: none; |
| } |
| |
| .pricing-card:not(.featured) .btn-primary { |
| background: rgba(255, 255, 255, 0.05); |
| color: var(--text-primary); |
| border: 1px solid var(--border-color); |
| } |
| |
| .pricing-card:not(.featured) .btn-primary:hover { |
| background: rgba(255, 255, 255, 0.1); |
| } |
| |
| .compiling-loader { |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| padding: 12px; |
| background: rgba(16, 163, 127, 0.05); |
| border-radius: 10px; |
| margin-bottom: 16px; |
| border: 1px dashed rgba(16, 163, 127, 0.3); |
| font-size: 13px; |
| color: #10a37f; |
| font-weight: 500; |
| } |
| |
| .compiling-loader i { |
| animation: spin 2s linear infinite; |
| } |
| |
| @keyframes spin { |
| from { transform: rotate(0deg); } |
| to { transform: rotate(360deg); } |
| } |
| |
| .compiled-search-badge { |
| display: inline-flex; |
| align-items: center; |
| gap: 6px; |
| background: linear-gradient(135deg, rgba(16, 163, 127, 0.2) 0%, rgba(26, 127, 100, 0.2) 100%); |
| color: #10a37f; |
| padding: 6px 12px; |
| border-radius: 8px; |
| font-size: 12px; |
| font-weight: 700; |
| text-transform: uppercase; |
| letter-spacing: 0.05em; |
| margin-bottom: 16px; |
| border: 1px solid rgba(16, 163, 127, 0.3); |
| box-shadow: 0 4px 12px rgba(0,0,0,0.1); |
| } |
| |
| |
| .message-actions { |
| display: flex; |
| gap: 10px; |
| margin-top: 14px; |
| opacity: 0.8; |
| transition: all 0.2s ease; |
| } |
| |
| .message:hover .message-actions { |
| opacity: 1; |
| transform: translateY(-2px); |
| } |
| |
| .action-icon-btn { |
| background: transparent; |
| border: 1px solid rgba(255,255,255,0.1); |
| color: var(--text-secondary); |
| padding: 6px; |
| border-radius: 8px; |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| transition: all 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); |
| } |
| |
| .action-icon-btn:hover { |
| background: rgba(255,255,255,0.05); |
| color: var(--accent-blue); |
| transform: scale(1.15) translateY(-2px); |
| border-color: var(--accent-blue); |
| box-shadow: 0 4px 12px rgba(0,0,0,0.2); |
| } |
| |
| .action-icon-btn:active { |
| transform: scale(0.95); |
| } |
| |
| .action-icon-btn i, .action-icon-btn svg, |
| .action-btn i, .action-btn svg, |
| .send-button i, .send-button svg, |
| .stop-button i, .stop-button svg { |
| display: block !important; |
| width: 18px !important; |
| height: 18px !important; |
| stroke-width: 2.5px !important; |
| } |
| |
| .send-button svg, .stop-button svg, .send-button i, .stop-button i { |
| color: white !important; |
| stroke: white !important; |
| } |
| |
| .action-btn svg, .action-btn i { |
| color: var(--text-secondary) !important; |
| stroke: var(--text-secondary) !important; |
| } |
| |
| .action-btn:hover svg, .action-btn:hover i { |
| color: var(--text-primary) !important; |
| stroke: var(--text-primary) !important; |
| } |
| |
| |
| .blinking-emoji { |
| display: flex; |
| gap: 3px; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .blinking-emoji .eye { |
| width: 8px; |
| height: 8px; |
| background-color: white; |
| border-radius: 50%; |
| animation: eyeMove 4s infinite ease-in-out; |
| } |
| |
| @keyframes eyeMove { |
| 0%, 100% { transform: translate(0, 0); } |
| 20% { transform: translate(2px, -1px); } |
| 40% { transform: translate(-2px, 1px); } |
| 60% { transform: translate(1px, 2px); } |
| 80% { transform: translate(-1px, -2px); } |
| |
| 90% { transform: scaleY(1); } |
| 95% { transform: scaleY(0.1); } |
| } |
| |
| .message.ai .message-avatar { |
| background: #10a37f; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .chat-history { |
| flex: 1; |
| overflow-y: auto; |
| padding: 40px 20px; |
| display: flex; |
| flex-direction: column; |
| gap: 32px; |
| scroll-behavior: smooth; |
| max-width: 900px; |
| margin: 0 auto; |
| width: 100%; |
| } |
| |
| .message { |
| display: flex; |
| gap: 16px; |
| width: 100%; |
| animation: fadeUp 0.4s cubic-bezier(0.16, 1, 0.3, 1); |
| position: relative; |
| } |
| |
| @keyframes fadeUp { |
| from { opacity: 0; transform: translateY(10px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| |
| .message.user { |
| flex-direction: row-reverse; |
| } |
| |
| .message-avatar { |
| width: 36px; |
| height: 36px; |
| border-radius: 50%; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| flex-shrink: 0; |
| font-size: 12px; |
| font-weight: 700; |
| box-shadow: 0 4px 12px rgba(0,0,0,0.2); |
| border: 1px solid rgba(255,255,255,0.1); |
| } |
| |
| .message.ai .message-avatar { |
| background: linear-gradient(135deg, #10a37f 0%, #0d8a6b 100%); |
| color: white; |
| } |
| |
| .message.user .message-avatar { |
| background: linear-gradient(135deg, #343541 0%, #202123 100%); |
| color: var(--text-secondary); |
| } |
| |
| .message-content { |
| max-width: 80%; |
| line-height: 1.6; |
| padding: 14px 18px; |
| word-wrap: break-word; |
| border-radius: 20px; |
| font-size: var(--chat-font-size, 15px); |
| position: relative; |
| transition: all 0.3s ease; |
| } |
| |
| .message.user .message-content { |
| background: #2f2f2f; |
| color: var(--text-primary); |
| border-bottom-right-radius: 4px; |
| box-shadow: 0 4px 15px rgba(0,0,0,0.1), 0 0 1px rgba(255,255,255,0.1); |
| border: 1px solid rgba(255,255,255,0.05); |
| } |
| |
| .message.ai .message-content { |
| background: rgba(255, 255, 255, 0.03); |
| backdrop-filter: blur(10px); |
| color: var(--text-primary); |
| border-bottom-left-radius: 4px; |
| border: 1px solid rgba(255,255,255,0.05); |
| box-shadow: 0 4px 20px rgba(0,0,0,0.15); |
| } |
| |
| |
| .message-content h1, .message-content h2, .message-content h3 { |
| margin-top: 16px; |
| margin-bottom: 8px; |
| color: var(--accent-blue); |
| font-weight: 700; |
| } |
| |
| .message-content h3 { font-size: 1.1rem; } |
| |
| .message-content p { |
| margin-bottom: 12px; |
| line-height: 1.6; |
| } |
| |
| .message-content ul, .message-content ol { |
| margin-bottom: 12px; |
| padding-left: 24px; |
| } |
| |
| .message-content li { |
| margin-bottom: 6px; |
| } |
| |
| .message-content strong { |
| color: var(--accent-blue); |
| } |
| |
| .message-content code { |
| background: rgba(255, 255, 255, 0.1); |
| padding: 2px 4px; |
| border-radius: 4px; |
| font-family: 'Fira Code', monospace; |
| font-size: 0.9em; |
| } |
| |
| |
| .message-actions { |
| display: flex; |
| gap: 12px; |
| margin-top: 12px; |
| padding-top: 8px; |
| border-top: 1px solid rgba(255, 255, 255, 0.05); |
| } |
| |
| .action-icon-btn { |
| background: none; |
| border: none; |
| color: var(--text-secondary); |
| cursor: pointer; |
| padding: 6px; |
| border-radius: 6px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| transition: all 0.2s; |
| gap: 4px; |
| font-size: 12px; |
| font-family: inherit; |
| } |
| |
| .action-icon-btn:hover { |
| background: rgba(255, 255, 255, 0.05); |
| color: var(--text-primary); |
| } |
| |
| .action-icon-btn i { |
| width: 14px; |
| height: 14px; |
| } |
| |
| .action-icon-btn.active { |
| color: #10a37f; |
| } |
| |
| .mic-btn.recording { |
| color: #ef4444; |
| animation: pulse-red 1.5s infinite; |
| } |
| |
| @keyframes pulse-red { |
| 0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); } |
| 70% { transform: scale(1.1); box-shadow: 0 0 0 10px rgba(239, 68, 68, 0); } |
| 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); } |
| } |
| |
| |
| .important-highlight { |
| background: rgba(16, 163, 127, 0.15); |
| border-bottom: 2px solid #10a37f; |
| padding: 0 2px; |
| border-radius: 2px; |
| font-weight: 600; |
| } |
| |
| |
| .image-card { |
| background: rgba(30, 30, 30, 0.6); |
| border: 1px solid rgba(255,255,255,0.1); |
| border-radius: 24px; |
| padding: 12px; |
| margin-top: 12px; |
| overflow: hidden; |
| box-shadow: 0 10px 30px rgba(0,0,0,0.3); |
| transition: transform 0.3s ease; |
| max-width: 500px; |
| } |
| |
| .image-card:hover { |
| transform: translateY(-2px); |
| } |
| |
| .image-card img { |
| width: 100%; |
| height: auto; |
| border-radius: 16px; |
| cursor: zoom-in; |
| transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1); |
| } |
| |
| .image-card img:hover { |
| transform: scale(1.02); |
| } |
| |
| .image-card-header { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| margin-bottom: 12px; |
| padding: 4px 8px; |
| } |
| |
| .image-card-header span { |
| font-size: 12px; |
| font-weight: 700; |
| text-transform: uppercase; |
| letter-spacing: 0.05em; |
| color: #10a37f; |
| } |
| |
| .image-card-actions { |
| display: flex; |
| gap: 8px; |
| margin-top: 12px; |
| padding: 0 4px; |
| } |
| |
| .img-btn { |
| flex: 1; |
| padding: 8px; |
| border-radius: 10px; |
| background: rgba(255,255,255,0.05); |
| border: 1px solid rgba(255,255,255,0.1); |
| color: var(--text-secondary); |
| font-size: 12px; |
| font-weight: 600; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| gap: 6px; |
| cursor: pointer; |
| transition: all 0.2s; |
| text-decoration: none; |
| } |
| |
| .img-btn:hover { |
| background: rgba(255,255,255,0.1); |
| color: var(--text-primary); |
| } |
| |
| |
| .input-container { |
| max-width: 840px; |
| width: 95%; |
| margin: 0 auto 24px; |
| z-index: 100; |
| background: rgba(20, 20, 20, 0.7); |
| backdrop-filter: blur(20px); |
| border: 1px solid rgba(255,255,255,0.1); |
| border-radius: 24px; |
| padding: 10px; |
| box-shadow: 0 20px 50px rgba(0,0,0,0.5); |
| transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1); |
| flex-shrink: 0; |
| position: relative; |
| } |
| |
| |
| .hero-section { |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| height: 100%; |
| text-align: center; |
| animation: fadeIn 1s ease; |
| padding: 40px 20px; |
| } |
| |
| .hero-logo { |
| font-size: 42px; |
| font-weight: 800; |
| background: linear-gradient(135deg, #10a37f 0%, #00d2ff 100%); |
| -webkit-background-clip: text; |
| -webkit-text-fill-color: transparent; |
| margin-bottom: 8px; |
| } |
| |
| .hero-subtitle { |
| font-size: 18px; |
| color: var(--text-secondary); |
| margin-bottom: 40px; |
| } |
| |
| .suggestion-grid { |
| display: grid; |
| grid-template-columns: repeat(2, 1fr); |
| gap: 12px; |
| max-width: 600px; |
| width: 100%; |
| } |
| |
| .suggestion-card { |
| background: rgba(255, 255, 255, 0.03); |
| border: 1px solid rgba(255,255,255,0.08); |
| border-radius: 16px; |
| padding: 16px; |
| cursor: pointer; |
| transition: all 0.2s; |
| text-align: left; |
| } |
| |
| .suggestion-card:hover { |
| background: rgba(255, 255, 255, 0.06); |
| border-color: rgba(255,255,255,0.15); |
| } |
| |
| .suggestion-card h4 { |
| font-size: 14px; |
| margin-bottom: 4px; |
| color: var(--text-primary); |
| } |
| |
| .suggestion-card p { |
| font-size: 12px; |
| color: var(--text-secondary); |
| } |
| |
| @media (max-width: 768px) { |
| .chat-history { |
| padding: 20px 10px; |
| } |
| .message-content { |
| max-width: 90%; |
| } |
| .suggestion-grid { |
| grid-template-columns: 1fr; |
| } |
| .input-container { |
| bottom: 10px; |
| margin-bottom: 10px; |
| border-radius: 16px; |
| } |
| } |
| |
| |
| |
| .message-content pre { |
| background-color: #000; |
| padding: 12px; |
| border-radius: 8px; |
| overflow-x: auto; |
| margin: 10px 0; |
| border: 1px solid var(--border-color); |
| } |
| |
| .message-content code { |
| font-family: 'Fira Code', 'Courier New', monospace; |
| font-size: 0.9em; |
| background-color: rgba(255, 255, 255, 0.05); |
| padding: 2px 4px; |
| border-radius: 4px; |
| } |
| |
| .message-content pre code { |
| background-color: transparent; |
| padding: 0; |
| } |
| |
| .message-content p { |
| margin-bottom: 8px; |
| } |
| |
| .message-content ul, |
| .message-content ol { |
| margin-left: 20px; |
| margin-bottom: 8px; |
| } |
| |
| @keyframes messageSlide { |
| from { |
| opacity: 0; |
| transform: translateY(10px); |
| } |
| |
| to { |
| opacity: 1; |
| transform: translateY(0); |
| } |
| } |
| |
| @keyframes fadeIn { |
| from { |
| opacity: 0; |
| } |
| |
| to { |
| opacity: 1; |
| } |
| } |
| |
| |
| .input-container { |
| padding: 24px 20px; |
| background: linear-gradient(to top, var(--bg-primary) 80%, transparent); |
| position: relative; |
| z-index: 10; |
| transition: all 0.8s cubic-bezier(0.2, 0.8, 0.2, 1); |
| } |
| |
| |
| .main-area.hero-mode .input-container { |
| transform: translateY(-35vh) scale(1.05); |
| background: transparent; |
| } |
| |
| .input-wrapper { |
| max-width: 800px; |
| margin: 0 auto; |
| position: relative; |
| display: flex; |
| flex-direction: column; |
| align-items: stretch; |
| background: rgba(30, 30, 30, 0.6); |
| backdrop-filter: blur(16px); |
| border-radius: 24px; |
| padding: 12px 18px; |
| border: 1px solid var(--border-color); |
| box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2); |
| transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); |
| gap: 8px; |
| } |
| |
| .input-wrapper.glow-active { |
| box-shadow: 0 0 40px rgba(16, 163, 127, 0.2), 0 0 80px rgba(16, 163, 127, 0.1), 0 8px 32px rgba(0, 0, 0, 0.4); |
| border-color: rgba(16, 163, 127, 0.4); |
| background: rgba(30, 30, 30, 0.8); |
| } |
| |
| .message-input { |
| width: 100%; |
| background: transparent; |
| border: none; |
| outline: none; |
| color: var(--text-primary); |
| font-size: 15px; |
| line-height: 1.5; |
| resize: none; |
| max-height: 200px; |
| min-height: 24px; |
| padding: 4px 0; |
| margin: 0; |
| font-family: inherit; |
| } |
| |
| .message-input::placeholder { |
| color: var(--text-secondary); |
| } |
| |
| .input-actions { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| .action-btn { |
| background: none; |
| border: none; |
| color: var(--text-secondary); |
| cursor: pointer; |
| padding: 6px; |
| border-radius: 8px; |
| transition: all 0.2s; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .action-btn:hover { |
| color: var(--text-primary); |
| background: var(--bg-tertiary); |
| } |
| |
| .image-mode-btn { |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| padding: 6px 12px; |
| border-radius: 12px; |
| font-size: 13px; |
| font-weight: 500; |
| color: var(--text-secondary); |
| cursor: pointer; |
| transition: all 0.2s; |
| background: transparent; |
| border: 1px solid transparent; |
| } |
| |
| .image-mode-btn:hover { |
| background: var(--bg-tertiary); |
| color: var(--text-primary); |
| } |
| |
| .image-mode-btn.active { |
| background: rgba(16, 163, 127, 0.1); |
| color: var(--accent-blue); |
| border-color: rgba(16, 163, 127, 0.2); |
| } |
| |
| .input-wrapper.image-mode { |
| border-color: var(--accent-blue); |
| box-shadow: 0 0 15px rgba(16, 163, 127, 0.15); |
| } |
| |
| |
| .input-wrapper.feature-active { |
| flex-direction: column; |
| align-items: stretch; |
| padding: 15px 18px 10px 18px; |
| border-radius: 20px; |
| gap: 12px; |
| min-height: 110px; |
| } |
| |
| .input-wrapper.feature-active .message-input { |
| min-height: 40px; |
| padding: 5px 0; |
| font-size: 16px; |
| display: block; |
| } |
| |
| .input-wrapper.feature-active .input-container-bottom { |
| margin-top: 5px; |
| border-top: 1px solid rgba(255, 255, 255, 0.05); |
| padding-top: 8px; |
| } |
| |
| .input-container-bottom { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| width: 100%; |
| } |
| |
| .input-actions-left, .input-actions-right { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| |
| .feature-badge { |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| padding: 4px 10px; |
| background: rgba(16, 163, 127, 0.1); |
| border: 1px solid rgba(16, 163, 127, 0.2); |
| border-radius: 12px; |
| color: var(--accent-blue); |
| font-size: 13px; |
| font-weight: 500; |
| margin-right: 8px; |
| animation: slideIn 0.2s ease; |
| } |
| |
| .feature-badge i { |
| width: 14px; |
| height: 14px; |
| } |
| |
| .remove-feature-btn { |
| background: none; |
| border: none; |
| color: var(--text-secondary); |
| cursor: pointer; |
| padding: 2px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| border-radius: 4px; |
| transition: all 0.2s; |
| } |
| |
| .remove-feature-btn:hover { |
| background: rgba(255, 255, 255, 0.1); |
| color: #ef4444; |
| } |
| |
| .remove-feature-btn i { |
| width: 12px; |
| height: 12px; |
| } |
| |
| .send-button { |
| background-color: var(--accent-blue); |
| color: white; |
| border: none; |
| border-radius: 12px; |
| width: 34px; |
| height: 34px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| cursor: pointer; |
| transition: all 0.2s; |
| opacity: 0.6; |
| } |
| |
| .send-button.active { |
| opacity: 1; |
| box-shadow: 0 0 15px rgba(16, 163, 127, 0.5); |
| } |
| |
| .send-button:hover.active { |
| background-color: var(--accent-hover); |
| transform: scale(1.05); |
| } |
| |
| .stop-button { |
| background-color: #ef4444; |
| color: white; |
| border: none; |
| border-radius: 12px; |
| width: 34px; |
| height: 34px; |
| display: none; |
| align-items: center; |
| justify-content: center; |
| cursor: pointer; |
| transition: all 0.2s; |
| box-shadow: 0 0 15px rgba(239, 68, 68, 0.3); |
| } |
| |
| .stop-button:hover { |
| background-color: #dc2626; |
| transform: scale(1.05); |
| } |
| |
| .stop-button.active { |
| display: flex; |
| } |
| |
| .disclaimer { |
| text-align: center; |
| font-size: 11px; |
| color: var(--text-secondary); |
| margin-top: 12px; |
| opacity: 0.7; |
| } |
| |
| |
| .thinking { |
| display: flex; |
| gap: 4px; |
| padding: 8px 0; |
| } |
| |
| .thinking span { |
| width: 8px; |
| height: 8px; |
| background-color: var(--accent-blue); |
| border-radius: 50%; |
| animation: bounce 1.4s infinite ease-in-out both; |
| box-shadow: 0 0 10px var(--accent-blue); |
| } |
| |
| .thinking-status-text { |
| font-size: 14px; |
| color: var(--text-primary); |
| font-weight: 600; |
| letter-spacing: 0.02em; |
| animation: statusPulse 2s infinite ease-in-out; |
| } |
| |
| @keyframes statusPulse { |
| 0%, 100% { opacity: 0.7; transform: translateX(0); } |
| 50% { opacity: 1; transform: translateX(4px); } |
| } |
| |
| .ai-processing-ring { |
| position: relative; |
| width: 40px; |
| height: 40px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| margin-bottom: 10px; |
| } |
| |
| .ai-processing-ring::before { |
| content: ''; |
| position: absolute; |
| width: 100%; |
| height: 100%; |
| border: 2px solid var(--accent-blue); |
| border-top-color: transparent; |
| border-radius: 50%; |
| animation: spin 1s linear infinite; |
| } |
| |
| |
| .drag-drop-overlay { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background: rgba(16, 163, 127, 0.15); |
| backdrop-filter: blur(8px); |
| border: 2px dashed var(--accent-blue); |
| z-index: 500; |
| display: none; |
| align-items: center; |
| justify-content: center; |
| flex-direction: column; |
| gap: 20px; |
| pointer-events: none; |
| transition: all 0.3s ease; |
| } |
| |
| .drag-drop-overlay.active { |
| display: flex; |
| animation: fadeIn 0.3s ease; |
| } |
| |
| .drag-drop-icon { |
| width: 80px; |
| height: 80px; |
| background: rgba(16, 163, 127, 0.1); |
| border-radius: 50%; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| color: var(--accent-blue); |
| animation: pulse-glow 2s infinite; |
| } |
| |
| @keyframes pulse-glow { |
| 0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(16, 163, 127, 0.4); } |
| 50% { transform: scale(1.1); box-shadow: 0 0 20px 10px rgba(16, 163, 127, 0); } |
| } |
| |
| .drag-drop-text { |
| font-size: 24px; |
| font-weight: 700; |
| color: var(--text-primary); |
| } |
| |
| .llm-badge { |
| display: inline-flex; |
| align-items: center; |
| gap: 6px; |
| background: rgba(255, 255, 255, 0.05); |
| border: 1px solid var(--border-color); |
| padding: 4px 10px; |
| border-radius: 8px; |
| font-size: 11px; |
| color: var(--text-secondary); |
| font-weight: 600; |
| margin-bottom: 8px; |
| } |
| |
| @keyframes bounce { |
| |
| 0%, |
| 80%, |
| 100% { |
| transform: scale(0); |
| } |
| |
| 40% { |
| transform: scale(1.0); |
| } |
| } |
| |
| |
| .auth-overlay { |
| position: fixed; |
| top: 0; |
| left: 0; |
| right: 0; |
| bottom: 0; |
| background: rgba(0, 0, 0, 0.7); |
| backdrop-filter: blur(8px); |
| z-index: 2000; |
| display: none; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .auth-overlay.show { |
| display: flex; |
| } |
| |
| .auth-card { |
| background-color: var(--bg-secondary); |
| width: 400px; |
| padding: 32px; |
| border-radius: 20px; |
| border: 1px solid var(--border-color); |
| box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4); |
| animation: fadeIn 0.4s ease; |
| } |
| |
| .auth-title { |
| font-size: 24px; |
| font-weight: 600; |
| margin-bottom: 8px; |
| text-align: center; |
| } |
| |
| .auth-subtitle { |
| font-size: 14px; |
| color: var(--text-secondary); |
| margin-bottom: 24px; |
| text-align: center; |
| } |
| |
| .auth-form { |
| display: flex; |
| flex-direction: column; |
| gap: 16px; |
| } |
| |
| .auth-input { |
| background: var(--bg-primary); |
| border: 1px solid var(--border-color); |
| color: var(--text-primary); |
| padding: 12px 16px; |
| border-radius: 10px; |
| outline: none; |
| font-size: 14px; |
| transition: border-color 0.2s; |
| } |
| |
| .auth-input:focus { |
| border-color: var(--accent-blue); |
| } |
| |
| .password-container { |
| position: relative; |
| display: flex; |
| align-items: center; |
| } |
| |
| .password-toggle { |
| position: absolute; |
| right: 12px; |
| background: none; |
| border: none; |
| color: var(--text-secondary); |
| cursor: pointer; |
| padding: 4px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| transition: color 0.2s; |
| } |
| |
| .password-toggle:hover { |
| color: var(--text-primary); |
| } |
| |
| .password-toggle.active { |
| color: var(--accent-blue); |
| } |
| |
| .auth-submit { |
| background-color: var(--accent-blue); |
| color: white; |
| border: none; |
| padding: 12px; |
| border-radius: 10px; |
| font-weight: 600; |
| cursor: pointer; |
| transition: background 0.2s; |
| } |
| |
| .auth-submit:hover { |
| background-color: var(--accent-hover); |
| } |
| |
| .auth-error { |
| color: #ef4444; |
| font-size: 13px; |
| text-align: center; |
| margin-top: 12px; |
| display: none; |
| } |
| |
| .auth-footer { |
| margin-top: 24px; |
| text-align: center; |
| font-size: 13px; |
| color: var(--text-secondary); |
| } |
| |
| .auth-toggle { |
| color: var(--accent-blue); |
| cursor: pointer; |
| font-weight: 500; |
| } |
| |
| |
| @media (max-width: 768px) { |
| .sidebar { |
| position: absolute; |
| left: 0; |
| height: 100%; |
| z-index: 1000; |
| transform: translateX(-100%); |
| } |
| |
| .sidebar.open { |
| transform: translateX(0); |
| box-shadow: 0 0 20px rgba(0, 0, 0, 0.5); |
| } |
| |
| .main-area.hero-mode .input-container { |
| transform: translateY(-20vh); |
| } |
| |
| .suggestions-grid { |
| grid-template-columns: 1fr; |
| } |
| } |
| |
| |
| .blinking-emoji { |
| width: 32px; |
| height: 32px; |
| background: linear-gradient(135deg, #10a37f 0%, #1a7f64 100%); |
| border-radius: 50%; |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| gap: 4px; |
| box-shadow: 0 0 15px rgba(16, 163, 127, 0.4); |
| position: relative; |
| flex-shrink: 0; |
| transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); |
| } |
| |
| .blinking-emoji:hover { |
| transform: scale(1.1) rotate(5deg); |
| } |
| |
| .blinking-emoji .eye { |
| width: 4px; |
| height: 6px; |
| background: white; |
| border-radius: 2px; |
| animation: eye-blink 4s infinite; |
| } |
| |
| @keyframes eye-blink { |
| |
| 0%, |
| 90%, |
| 100% { |
| transform: scaleY(1); |
| } |
| |
| 92% { |
| transform: scaleY(0.1); |
| } |
| } |
| |
| |
| .attachments-container { |
| display: flex; |
| flex-wrap: wrap; |
| gap: 8px; |
| margin-bottom: 12px; |
| max-width: 800px; |
| margin: 0 auto 12px; |
| } |
| |
| .file-attachment-card { |
| background: var(--bg-tertiary); |
| border: 1px solid var(--border-color); |
| border-radius: 12px; |
| padding: 8px 12px; |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| font-size: 13px; |
| animation: fadeIn 0.3s ease; |
| } |
| |
| .file-attachment-icon { |
| width: 28px; |
| height: 28px; |
| border-radius: 6px; |
| background: #e53e3e; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| color: white; |
| } |
| |
| .file-attachment-info { |
| display: flex; |
| flex-direction: column; |
| } |
| |
| .file-attachment-name { |
| font-weight: 500; |
| color: var(--text-primary); |
| max-width: 150px; |
| white-space: nowrap; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| } |
| |
| .file-attachment-type { |
| font-size: 11px; |
| color: var(--text-secondary); |
| } |
| |
| .file-attachment-remove { |
| cursor: pointer; |
| color: var(--text-secondary); |
| padding: 4px; |
| border-radius: 4px; |
| transition: background 0.2s; |
| } |
| |
| .file-attachment-remove:hover { |
| background: rgba(255, 255, 255, 0.1); |
| color: #ef4444; |
| } |
| |
| .upload-menu { |
| position: absolute; |
| bottom: calc(100% + 15px); |
| left: 0; |
| background: #1e1e1e; |
| backdrop-filter: blur(16px); |
| border: 1px solid var(--border-color); |
| border-radius: 24px; |
| padding: 12px; |
| display: none; |
| grid-template-columns: repeat(3, 1fr); |
| gap: 8px; |
| z-index: 1000; |
| box-shadow: 0 15px 50px rgba(0, 0, 0, 0.6); |
| animation: slideUp 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.2); |
| width: max-content; |
| } |
| |
| .upload-menu.show { |
| display: grid; |
| } |
| |
| .upload-menu .tool-btn { |
| position: relative; |
| width: 80px; |
| height: 80px; |
| background: rgba(255, 255, 255, 0.03); |
| border-radius: 18px; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| gap: 8px; |
| cursor: pointer; |
| transition: all 0.2s; |
| border: 1px solid rgba(255, 255, 255, 0.05); |
| color: var(--text-secondary); |
| } |
| |
| .coming-soon-badge { |
| position: absolute; |
| bottom: 6px; |
| left: 50%; |
| transform: translateX(-50%); |
| font-size: 7px; |
| background: linear-gradient(135deg, #10a37f 0%, #0d8a6a 100%); |
| color: white; |
| padding: 1px 5px; |
| border-radius: 4px; |
| white-space: nowrap; |
| font-weight: 800; |
| text-transform: uppercase; |
| letter-spacing: 0.02em; |
| pointer-events: none; |
| box-shadow: 0 2px 4px rgba(0,0,0,0.3); |
| display: none; |
| } |
| |
| .tool-btn.restricted .coming-soon-badge { |
| display: block; |
| } |
| |
| .tool-btn.restricted { |
| opacity: 0.8; |
| } |
| |
| .upload-menu .tool-btn:hover { |
| background: rgba(255, 255, 255, 0.08); |
| color: var(--text-primary); |
| transform: translateY(-3px); |
| border-color: var(--accent-blue); |
| box-shadow: 0 5px 15px rgba(16, 163, 127, 0.1); |
| } |
| |
| .upload-menu .tool-btn i { |
| width: 24px; |
| height: 24px; |
| color: var(--accent-blue); |
| } |
| |
| .upload-menu .tool-btn span { |
| font-size: 11px; |
| font-weight: 600; |
| text-transform: uppercase; |
| letter-spacing: 0.02em; |
| } |
| |
| |
| .landing-page { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background: var(--bg-primary); |
| z-index: 2000; |
| display: flex; |
| flex-direction: column; |
| overflow-y: auto; |
| transition: opacity 0.8s cubic-bezier(0.4, 0, 0.2, 1), transform 0.8s cubic-bezier(0.4, 0, 0.2, 1); |
| } |
| |
| .landing-page.hidden { |
| opacity: 0; |
| pointer-events: none; |
| transform: scale(1.05); |
| } |
| |
| .landing-header { |
| padding: 24px 40px; |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| backdrop-filter: blur(10px); |
| position: sticky; |
| top: 0; |
| z-index: 10; |
| } |
| |
| .landing-logo { |
| font-size: 24px; |
| font-weight: 800; |
| color: #fff; |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| letter-spacing: -0.02em; |
| } |
| |
| .landing-nav { |
| display: flex; |
| gap: 32px; |
| align-items: center; |
| } |
| |
| .nav-link { |
| color: var(--text-secondary); |
| text-decoration: none; |
| font-size: 14px; |
| font-weight: 600; |
| transition: color 0.2s; |
| } |
| |
| .nav-link:hover { |
| color: var(--text-primary); |
| } |
| |
| .hero-section { |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| padding: 120px 20px; |
| text-align: center; |
| max-width: 1000px; |
| margin: 0 auto; |
| } |
| |
| |
| .lightning-overlay { |
| position: fixed; |
| inset: 0; |
| pointer-events: none; |
| z-index: 1; |
| opacity: 0; |
| background: radial-gradient(circle at 50% 50%, rgba(0, 242, 255, 0.15) 0%, transparent 70%); |
| transition: opacity 0.1s ease-out; |
| } |
| |
| .lightning-flash { |
| animation: flash 0.6s ease-out forwards; |
| } |
| |
| @keyframes flash { |
| 0% { opacity: 0; background: radial-gradient(circle at 50% 50%, rgba(0, 242, 255, 0.4) 0%, transparent 80%); } |
| 10% { opacity: 1; } |
| 20% { opacity: 0.3; } |
| 30% { opacity: 0.8; } |
| 100% { opacity: 0; } |
| } |
| |
| .hero-badge { |
| background: rgba(16, 163, 127, 0.1); |
| color: #10a37f; |
| padding: 8px 16px; |
| border-radius: 100px; |
| font-size: 13px; |
| font-weight: 600; |
| margin-bottom: 24px; |
| border: 1px solid rgba(16, 163, 127, 0.2); |
| animation: fadeInDown 0.8s ease-out; |
| } |
| |
| |
| .trust-badges { |
| display: flex; |
| gap: 24px; |
| margin-top: 40px; |
| flex-wrap: wrap; |
| justify-content: center; |
| animation: fadeInUp 0.8s ease-out 0.8s backwards; |
| } |
| .trust-badge { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| font-size: 14px; |
| color: var(--text-secondary); |
| } |
| .trust-badge svg, .trust-badge i { width: 16px; height: 16px; color: #10a37f; } |
| |
| |
| .section-wrapper { |
| padding: 100px 40px; |
| max-width: 1200px; |
| margin: 0 auto; |
| width: 100%; |
| } |
| .section-header { |
| text-align: center; |
| margin-bottom: 60px; |
| } |
| .section-header h2 { |
| font-size: 36px; |
| font-weight: 700; |
| margin-bottom: 16px; |
| } |
| .section-header p { |
| font-size: 18px; |
| color: var(--text-secondary); |
| max-width: 600px; |
| margin: 0 auto; |
| } |
| |
| |
| .how-it-works-steps { |
| display: grid; |
| grid-template-columns: repeat(3, 1fr); |
| gap: 40px; |
| } |
| .step-item { |
| text-align: center; |
| position: relative; |
| } |
| .step-number { |
| width: 48px; |
| height: 48px; |
| background: rgba(16, 163, 127, 0.1); |
| color: #10a37f; |
| border-radius: 50%; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-weight: 700; |
| margin: 0 auto 24px; |
| border: 1px solid rgba(16, 163, 127, 0.2); |
| font-size: 20px; |
| } |
| .step-item h3 { margin-bottom: 12px; font-size: 20px; font-weight: 600; } |
| .step-item p { color: var(--text-secondary); line-height: 1.6; } |
| |
| |
| .testimonials-grid { |
| display: grid; |
| grid-template-columns: repeat(3, 1fr); |
| gap: 24px; |
| } |
| .testimonial-card { |
| background: rgba(255, 255, 255, 0.02); |
| border: 1px solid var(--border-color); |
| padding: 32px; |
| border-radius: 24px; |
| transition: all 0.3s ease; |
| } |
| .testimonial-card:hover { |
| background: rgba(255, 255, 255, 0.04); |
| transform: translateY(-5px); |
| } |
| .testimonial-card p { |
| font-style: italic; |
| color: var(--text-secondary); |
| line-height: 1.6; |
| margin-bottom: 20px; |
| } |
| .testimonial-author { |
| font-weight: 600; |
| color: var(--text-primary); |
| font-size: 14px; |
| } |
| |
| |
| .pricing-grid { |
| display: grid; |
| grid-template-columns: 1fr; |
| max-width: 450px; |
| margin: 0 auto; |
| } |
| .pricing-card { |
| background: linear-gradient(135deg, rgba(16, 163, 127, 0.1) 0%, rgba(16, 163, 127, 0.02) 100%); |
| border: 1px solid rgba(16, 163, 127, 0.3); |
| padding: 48px; |
| border-radius: 32px; |
| text-align: center; |
| position: relative; |
| overflow: hidden; |
| } |
| .pricing-card::before { |
| content: ''; |
| position: absolute; |
| top: 0; |
| left: 0; |
| right: 0; |
| height: 4px; |
| background: linear-gradient(90deg, #10a37f, #1a7f64); |
| } |
| |
| |
| .faq-container { |
| max-width: 800px; |
| margin: 0 auto; |
| } |
| .faq-item { |
| border-bottom: 1px solid var(--border-color); |
| padding: 24px 0; |
| } |
| .faq-question { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| cursor: pointer; |
| font-weight: 600; |
| font-size: 18px; |
| transition: color 0.2s; |
| } |
| .faq-question:hover { color: #10a37f; } |
| .faq-answer { |
| margin-top: 16px; |
| color: var(--text-secondary); |
| line-height: 1.6; |
| display: none; |
| animation: fadeIn 0.3s ease; |
| } |
| .faq-item.active .faq-answer { display: block; } |
| .faq-item.active .faq-question svg { transform: rotate(180deg); } |
| .faq-question svg { transition: transform 0.3s ease; width: 20px; height: 20px; } |
| |
| |
| .landing-footer { |
| padding: 100px 40px 40px; |
| background: var(--bg-secondary); |
| border-top: 1px solid var(--border-color); |
| margin-top: 100px; |
| } |
| .footer-content { |
| display: grid; |
| grid-template-columns: 2fr 1fr 1fr 1fr; |
| gap: 60px; |
| max-width: 1200px; |
| margin: 0 auto; |
| } |
| .footer-info p { margin: 20px 0; line-height: 1.6; color: var(--text-secondary); } |
| .footer-bottom { |
| margin-top: 80px; |
| padding-top: 32px; |
| border-top: 1px solid var(--border-color); |
| text-align: center; |
| color: var(--text-secondary); |
| font-size: 14px; |
| } |
| |
| .hero-title { |
| font-size: clamp(48px, 8vw, 84px); |
| font-weight: 800; |
| line-height: 1.1; |
| margin-bottom: 24px; |
| background: linear-gradient(to bottom, #fff 0%, #a3a3a3 100%); |
| -webkit-background-clip: text; |
| -webkit-text-fill-color: transparent; |
| letter-spacing: -0.04em; |
| animation: fadeInUp 0.8s ease-out 0.2s backwards; |
| } |
| |
| .typing-cursor { |
| border-right: 4px solid var(--accent-blue); |
| margin-left: 4px; |
| animation: blink-cursor 0.75s step-end infinite; |
| display: inline-block; |
| vertical-align: middle; |
| height: 1em; |
| width: 0; |
| } |
| |
| @keyframes blink-cursor { |
| from, to { border-color: transparent } |
| 50% { border-color: var(--accent-blue); } |
| } |
| |
| .hero-subtitle { |
| font-size: clamp(18px, 2vw, 22px); |
| color: var(--text-secondary); |
| max-width: 600px; |
| line-height: 1.6; |
| margin-bottom: 48px; |
| animation: fadeInUp 0.8s ease-out 0.4s backwards; |
| } |
| |
| .cta-container { |
| display: flex; |
| gap: 16px; |
| animation: fadeInUp 0.8s ease-out 0.6s backwards; |
| } |
| |
| .btn-primary { |
| padding: 16px 32px; |
| background: #10a37f; |
| color: #fff; |
| border-radius: 12px; |
| font-weight: 600; |
| font-size: 16px; |
| text-decoration: none; |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
| box-shadow: 0 4px 15px rgba(16, 163, 127, 0.3); |
| cursor: pointer; |
| border: none; |
| } |
| |
| .btn-primary:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 8px 30px rgba(16, 163, 127, 0.4); |
| background: #14b88f; |
| } |
| |
| .btn-secondary { |
| padding: 16px 32px; |
| background: rgba(255, 255, 255, 0.05); |
| color: #fff; |
| border-radius: 12px; |
| font-weight: 600; |
| font-size: 16px; |
| text-decoration: none; |
| transition: all 0.3s ease; |
| border: 1px solid var(--border-color); |
| cursor: pointer; |
| } |
| |
| .btn-secondary:hover { |
| background: rgba(255, 255, 255, 0.1); |
| border-color: rgba(255, 255, 255, 0.2); |
| transform: translateY(-2px); |
| } |
| |
| .features-grid { |
| display: grid; |
| grid-template-columns: repeat(3, 1fr); |
| gap: 24px; |
| padding: 100px 40px; |
| max-width: 1200px; |
| margin: 0 auto; |
| width: 100%; |
| animation: fadeInUp 1s ease-out 0.8s backwards; |
| } |
| |
| .feature-card { |
| background: rgba(255, 255, 255, 0.02); |
| border: 1px solid var(--border-color); |
| padding: 32px; |
| border-radius: 24px; |
| text-align: left; |
| transition: all 0.3s ease; |
| } |
| |
| .feature-card:hover { |
| background: rgba(255, 255, 255, 0.04); |
| transform: translateY(-5px); |
| border-color: rgba(255, 255, 255, 0.15); |
| } |
| |
| .feature-icon { |
| width: 48px; |
| height: 48px; |
| background: rgba(16, 163, 127, 0.1); |
| color: #10a37f; |
| border-radius: 12px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| margin-bottom: 20px; |
| } |
| |
| .feature-card h3 { |
| font-size: 20px; |
| font-weight: 600; |
| margin-bottom: 12px; |
| } |
| |
| .feature-card p { |
| font-size: 15px; |
| color: var(--text-secondary); |
| line-height: 1.6; |
| } |
| |
| @keyframes fadeInUp { |
| from { |
| opacity: 0; |
| transform: translateY(20px); |
| } |
| |
| to { |
| opacity: 1; |
| transform: translateY(0); |
| } |
| } |
| |
| @keyframes fadeInDown { |
| from { |
| opacity: 0; |
| transform: translateY(-20px); |
| } |
| |
| to { |
| opacity: 1; |
| transform: translateY(0); |
| } |
| } |
| |
| @media (max-width: 992px) { |
| .footer-content { grid-template-columns: 1fr 1fr; } |
| .hero-title { font-size: 56px; } |
| } |
| |
| @media (max-width: 768px) { |
| .features-grid, .how-it-works-steps, .testimonials-grid, .footer-content { grid-template-columns: 1fr; } |
| .landing-header { padding: 16px 20px; } |
| .landing-nav .nav-link { display: none; } |
| .landing-nav { gap: 12px; } |
| .hero-title { font-size: 42px; } |
| .hero-section { padding: 60px 20px; } |
| .section-wrapper { padding: 60px 20px; } |
| .cta-container { flex-direction: column; width: 100%; } |
| .btn-primary, .btn-secondary { width: 100%; text-align: center; } |
| .trust-badges { gap: 16px; } |
| .landing-logo { font-size: 20px; } |
| .blinking-emoji { width: 28px; height: 28px; } |
| } |
| |
| |
| .img-modal-overlay { |
| position: fixed; |
| inset: 0; |
| background: rgba(0, 0, 0, 0.85); |
| backdrop-filter: blur(12px); |
| z-index: 3000; |
| display: none; |
| align-items: center; |
| justify-content: center; |
| opacity: 0; |
| transition: opacity 0.3s ease; |
| } |
| |
| .img-modal-overlay.open { |
| display: flex; |
| opacity: 1; |
| } |
| |
| .img-modal-card { |
| background: var(--bg-secondary); |
| border: 1px solid var(--border-color); |
| border-radius: 24px; |
| width: min(800px, 94vw); |
| max-height: 90vh; |
| overflow-y: auto; |
| padding: 40px; |
| position: relative; |
| box-shadow: 0 30px 60px rgba(0, 0, 0, 0.6); |
| transform: translateY(20px); |
| transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); |
| } |
| |
| .img-modal-overlay.open .img-modal-card { |
| transform: translateY(0); |
| } |
| |
| .img-modal-close { |
| position: absolute; |
| top: 24px; |
| right: 24px; |
| background: rgba(255, 255, 255, 0.05); |
| border: none; |
| color: var(--text-secondary); |
| width: 40px; |
| height: 40px; |
| border-radius: 50%; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| cursor: pointer; |
| transition: all 0.2s; |
| } |
| |
| .img-modal-close:hover { |
| background: rgba(255, 255, 255, 0.1); |
| color: var(--text-primary); |
| transform: rotate(90deg); |
| } |
| |
| .img-gen-grid { |
| display: grid; |
| grid-template-columns: 1fr 1fr; |
| gap: 40px; |
| align-items: start; |
| } |
| |
| @media (max-width: 768px) { |
| .img-gen-grid { |
| grid-template-columns: 1fr; |
| } |
| } |
| |
| .img-prompt-area h2 { |
| font-size: 28px; |
| margin-bottom: 12px; |
| } |
| |
| .img-prompt-area p { |
| color: var(--text-secondary); |
| margin-bottom: 24px; |
| font-size: 15px; |
| } |
| |
| .img-textarea { |
| width: 100%; |
| background: var(--bg-primary); |
| border: 1px solid var(--border-color); |
| border-radius: 16px; |
| padding: 16px; |
| color: var(--text-primary); |
| font-family: inherit; |
| font-size: 15px; |
| resize: none; |
| min-height: 120px; |
| outline: none; |
| transition: border-color 0.2s; |
| margin-bottom: 20px; |
| } |
| |
| .img-textarea:focus { |
| border-color: var(--accent-blue); |
| } |
| |
| .img-preview-container { |
| aspect-ratio: 1; |
| background: var(--bg-primary); |
| border: 1px solid var(--border-color); |
| border-radius: 20px; |
| overflow: hidden; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| position: relative; |
| } |
| |
| .img-preview-container img { |
| width: 100%; |
| height: 100%; |
| object-fit: cover; |
| display: none; |
| } |
| |
| .img-preview-container.visible img { |
| display: block; |
| animation: fadeIn 0.5s ease; |
| } |
| |
| .img-skeleton { |
| width: 100%; |
| height: 100%; |
| background: linear-gradient(110deg, var(--bg-primary) 8%, var(--bg-tertiary) 18%, var(--bg-primary) 33%); |
| background-size: 200% 100%; |
| animation: skeleton-shimmer 1.5s infinite linear; |
| display: none; |
| } |
| |
| .img-skeleton.visible { |
| display: block; |
| } |
| |
| @keyframes skeleton-shimmer { |
| to { |
| background-position: -200% 0; |
| } |
| } |
| |
| .img-placeholder-text { |
| color: var(--text-secondary); |
| text-align: center; |
| padding: 20px; |
| } |
| |
| .img-preview-container.visible .img-placeholder-text { |
| display: none; |
| } |
| |
| .img-download-btn { |
| position: absolute; |
| bottom: 16px; |
| right: 16px; |
| background: rgba(0, 0, 0, 0.6); |
| backdrop-filter: blur(8px); |
| border: 1px solid rgba(255, 255, 255, 0.2); |
| color: white; |
| padding: 8px 16px; |
| border-radius: 10px; |
| font-size: 13px; |
| text-decoration: none; |
| display: none; |
| align-items: center; |
| gap: 8px; |
| transition: all 0.2s; |
| } |
| |
| .img-preview-container.visible .img-download-btn { |
| display: flex; |
| } |
| |
| .img-download-btn:hover { |
| background: rgba(0, 0, 0, 0.8); |
| transform: translateY(-2px); |
| } |
| |
| |
| .image-card { |
| background: var(--bg-tertiary); |
| border-radius: 16px; |
| overflow: hidden; |
| margin-top: 10px; |
| border: 1px solid var(--border-color); |
| box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3); |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
| max-width: 512px; |
| width: 100%; |
| position: relative; |
| } |
| |
| .image-card:hover { |
| transform: translateY(-4px) scale(1.01); |
| border-color: var(--accent-blue); |
| } |
| |
| .image-card img { |
| width: 100%; |
| height: auto; |
| display: block; |
| opacity: 0; |
| transition: opacity 0.8s ease; |
| } |
| |
| .image-card img.loaded { |
| opacity: 1; |
| } |
| |
| |
| .motion-image { |
| animation: kenBurns 10s ease-in-out infinite alternate; |
| transform-origin: center; |
| } |
| |
| @keyframes kenBurns { |
| 0% { transform: scale(1); } |
| 100% { transform: scale(1.1); } |
| } |
| |
| .image-card-actions { |
| padding: 12px; |
| display: flex; |
| gap: 8px; |
| background: rgba(0, 0, 0, 0.4); |
| backdrop-filter: blur(8px); |
| } |
| |
| .img-btn { |
| flex: 1; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| gap: 8px; |
| padding: 10px; |
| border-radius: 10px; |
| border: 1px solid var(--border-color); |
| background: rgba(255, 255, 255, 0.05); |
| color: var(--text-primary); |
| font-size: 13px; |
| text-decoration: none; |
| cursor: pointer; |
| transition: all 0.2s; |
| } |
| |
| .img-btn:hover { |
| background: var(--accent-blue); |
| color: white; |
| border-color: var(--accent-blue); |
| } |
| |
| .gen-status { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| padding: 16px; |
| color: var(--accent-blue); |
| font-size: 14px; |
| font-weight: 500; |
| } |
| |
| .gen-loader { |
| width: 18px; |
| height: 18px; |
| border: 2px solid var(--accent-blue); |
| border-bottom-color: transparent; |
| border-radius: 50%; |
| display: inline-block; |
| animation: rotation 1s linear infinite; |
| } |
| |
| @keyframes rotation { |
| 0% { transform: rotate(0deg); } |
| 100% { transform: rotate(360deg); } |
| } |
| |
| |
| .command-help { |
| position: absolute; |
| bottom: calc(100% + 15px); |
| left: 20px; |
| background: var(--bg-secondary); |
| border: 1px solid var(--border-color); |
| border-radius: 14px; |
| padding: 14px 18px; |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); |
| display: none; |
| z-index: 1000; |
| animation: slideUp 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); |
| max-width: 320px; |
| } |
| |
| @keyframes slideUp { |
| from { transform: translateY(15px); opacity: 0; } |
| to { transform: translateY(0); opacity: 1; } |
| } |
| |
| .command-help.show { |
| display: block; |
| } |
| |
| .command-help h5 { |
| font-size: 13px; |
| color: var(--accent-blue); |
| margin-bottom: 6px; |
| font-weight: 600; |
| } |
| |
| .command-help p { |
| font-size: 12px; |
| color: var(--text-secondary); |
| line-height: 1.4; |
| } |
| |
| .command-help code { |
| background: var(--bg-tertiary); |
| padding: 4px 8px; |
| border-radius: 6px; |
| color: var(--text-primary); |
| font-family: monospace; |
| display: block; |
| margin-top: 10px; |
| font-size: 11px; |
| border: 1px solid var(--border-color); |
| white-space: pre-wrap; |
| } |
| |
| |
| .video-container { |
| position: relative; |
| width: 100%; |
| border-radius: 12px; |
| overflow: hidden; |
| background: #000; |
| border: 1px solid var(--border-color); |
| } |
| |
| .video-container video { |
| width: 100%; |
| display: block; |
| } |
| |
| .custom-video-controls { |
| position: absolute; |
| bottom: 0; |
| left: 0; |
| right: 0; |
| background: linear-gradient(transparent, rgba(0, 0, 0, 0.8)); |
| display: flex; |
| align-items: center; |
| gap: 15px; |
| padding: 10px 15px; |
| opacity: 0; |
| transition: opacity 0.3s; |
| z-index: 10; |
| } |
| |
| .video-container:hover .custom-video-controls { |
| opacity: 1; |
| } |
| |
| .vid-ctrl-btn { |
| background: none; |
| border: none; |
| color: white; |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| padding: 5px; |
| transition: transform 0.2s; |
| } |
| |
| .vid-ctrl-btn:hover { |
| transform: scale(1.1); |
| color: var(--accent-blue); |
| } |
| |
| .volume-container { |
| position: relative; |
| display: flex; |
| align-items: center; |
| } |
| |
| .volume-slider-vertical { |
| position: absolute; |
| bottom: 40px; |
| left: 50%; |
| transform: translateX(-50%); |
| width: 30px; |
| height: 100px; |
| background: rgba(0, 0, 0, 0.8); |
| border-radius: 15px; |
| padding: 10px 5px; |
| display: none; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| z-index: 100; |
| } |
| |
| .volume-container:hover .volume-slider-vertical { |
| display: flex; |
| } |
| |
| .volume-slider-vertical input { |
| appearance: none; |
| -webkit-appearance: none; |
| width: 80px; |
| height: 4px; |
| background: rgba(255, 255, 255, 0.2); |
| border-radius: 2px; |
| outline: none; |
| transform: rotate(-90deg); |
| cursor: pointer; |
| } |
| |
| .volume-slider-vertical input::-webkit-slider-thumb { |
| -webkit-appearance: none; |
| width: 12px; |
| height: 12px; |
| background: var(--accent-blue); |
| border-radius: 50%; |
| cursor: pointer; |
| box-shadow: 0 0 10px rgba(0, 163, 127, 0.5); |
| } |
| |
| .video-progress { |
| flex: 1; |
| height: 4px; |
| background: rgba(255, 255, 255, 0.2); |
| border-radius: 2px; |
| cursor: pointer; |
| position: relative; |
| } |
| |
| .video-progress-filled { |
| height: 100%; |
| background: var(--accent-blue); |
| border-radius: 2px; |
| width: 0%; |
| } |
| |
| |
| .auth-overlay { |
| position: fixed; |
| inset: 0; |
| background: var(--bg-primary); |
| z-index: 6000; |
| display: none; |
| align-items: center; |
| justify-content: center; |
| padding: 20px; |
| } |
| |
| .auth-card { |
| background: var(--bg-secondary); |
| border: 1px solid var(--border-color); |
| border-radius: 24px; |
| width: 100%; |
| max-width: 420px; |
| padding: 40px; |
| box-shadow: 0 25px 50px rgba(0,0,0,0.4); |
| animation: slideUp 0.4s ease; |
| } |
| |
| .auth-logo { |
| text-align: center; |
| margin-bottom: 30px; |
| font-size: 24px; |
| font-weight: 800; |
| color: var(--accent-blue); |
| } |
| |
| .auth-title { |
| font-size: 24px; |
| font-weight: 700; |
| margin-bottom: 8px; |
| text-align: center; |
| } |
| |
| .auth-subtitle { |
| color: var(--text-secondary); |
| font-size: 14px; |
| text-align: center; |
| margin-bottom: 30px; |
| } |
| |
| .auth-form { |
| display: flex; |
| flex-direction: column; |
| gap: 16px; |
| } |
| |
| .auth-input-group { |
| display: flex; |
| flex-direction: column; |
| gap: 8px; |
| } |
| |
| .auth-input-group label { |
| font-size: 13px; |
| font-weight: 600; |
| color: var(--text-secondary); |
| } |
| |
| .auth-input { |
| background: var(--bg-primary); |
| border: 1px solid var(--border-color); |
| border-radius: 12px; |
| padding: 12px 16px; |
| color: var(--text-primary); |
| font-size: 15px; |
| outline: none; |
| transition: border-color 0.2s; |
| } |
| |
| .auth-input:focus { |
| border-color: var(--accent-blue); |
| } |
| |
| .auth-submit-btn { |
| background: var(--accent-blue); |
| color: white; |
| border: none; |
| border-radius: 12px; |
| padding: 14px; |
| font-size: 15px; |
| font-weight: 700; |
| cursor: pointer; |
| transition: all 0.2s; |
| margin-top: 10px; |
| } |
| |
| .auth-submit-btn:hover { |
| background: var(--accent-hover); |
| transform: translateY(-2px); |
| } |
| |
| .auth-divider { |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| margin: 24px 0; |
| color: var(--text-secondary); |
| font-size: 13px; |
| } |
| |
| .auth-divider::before, .auth-divider::after { |
| content: ""; |
| flex: 1; |
| height: 1px; |
| background: var(--border-color); |
| } |
| |
| .oauth-btn { |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| gap: 12px; |
| background: var(--bg-primary); |
| border: 1px solid var(--border-color); |
| border-radius: 12px; |
| padding: 12px; |
| color: var(--text-primary); |
| font-size: 14px; |
| font-weight: 600; |
| cursor: pointer; |
| transition: background 0.2s; |
| } |
| |
| .oauth-btn:hover { |
| background: var(--bg-tertiary); |
| } |
| |
| .auth-switch { |
| text-align: center; |
| margin-top: 24px; |
| font-size: 14px; |
| color: var(--text-secondary); |
| } |
| |
| |
| .search-results { |
| display: flex; |
| flex-direction: column; |
| gap: 12px; |
| margin-top: 10px; |
| width: 100%; |
| } |
| |
| .search-card { |
| background: var(--bg-secondary); |
| border: 1px solid var(--border-color); |
| border-radius: 12px; |
| padding: 14px; |
| transition: all 0.2s ease; |
| text-decoration: none; |
| color: inherit; |
| display: block; |
| } |
| |
| .search-card:hover { |
| border-color: var(--accent-blue); |
| transform: translateY(-2px); |
| background: var(--bg-tertiary); |
| } |
| |
| .search-card h5 { |
| color: var(--accent-blue); |
| font-size: 15px; |
| margin-bottom: 6px; |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| .search-card p { |
| font-size: 13px; |
| color: var(--text-secondary); |
| line-height: 1.5; |
| } |
| |
| .search-card .link { |
| font-size: 11px; |
| color: #10a37f; |
| opacity: 0.7; |
| margin-top: 8px; |
| word-break: break-all; |
| } |
| |
| |
| .file-status-card { |
| background: rgba(16, 163, 127, 0.1); |
| border: 1px solid rgba(16, 163, 127, 0.2); |
| border-radius: 12px; |
| padding: 12px 16px; |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| margin-bottom: 12px; |
| animation: slideIn 0.3s ease; |
| } |
| |
| .file-status-icon { |
| color: var(--accent-blue); |
| } |
| |
| .file-status-info { |
| flex: 1; |
| } |
| |
| .file-status-name { |
| font-size: 14px; |
| font-weight: 600; |
| color: var(--text-primary); |
| } |
| |
| .file-status-msg { |
| font-size: 12px; |
| color: var(--text-secondary); |
| } |
| |
| @keyframes slideIn { |
| from { opacity: 0; transform: translateY(10px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| |
| |
| .attachments-container { |
| display: none; |
| flex-wrap: wrap; |
| gap: 12px; |
| padding: 12px 16px; |
| border-bottom: 1px solid var(--border-color); |
| background: rgba(255, 255, 255, 0.02); |
| border-top-left-radius: 20px; |
| border-top-right-radius: 20px; |
| } |
| |
| .file-attachment-card { |
| background: var(--bg-secondary); |
| border: 1px solid var(--border-color); |
| border-radius: 14px; |
| padding: 10px 14px; |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| min-width: 180px; |
| max-width: 240px; |
| position: relative; |
| animation: slideIn 0.3s ease; |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); |
| } |
| |
| .file-attachment-icon { |
| width: 36px; |
| height: 36px; |
| border-radius: 10px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| color: white; |
| flex-shrink: 0; |
| } |
| |
| .file-attachment-info { |
| display: flex; |
| flex-direction: column; |
| overflow: hidden; |
| flex: 1; |
| } |
| |
| .file-attachment-name { |
| font-size: 13px; |
| font-weight: 600; |
| color: var(--text-primary); |
| white-space: nowrap; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| max-width: 130px; |
| } |
| |
| .file-attachment-type { |
| font-size: 11px; |
| color: var(--text-secondary); |
| text-transform: uppercase; |
| display: flex; |
| align-items: center; |
| gap: 4px; |
| } |
| |
| .upload-progress-container { |
| width: 100%; |
| height: 3px; |
| background: rgba(255, 255, 255, 0.1); |
| border-radius: 10px; |
| margin-top: 4px; |
| overflow: hidden; |
| display: none; |
| } |
| |
| .upload-progress-bar { |
| height: 100%; |
| background: var(--accent-blue); |
| width: 0%; |
| transition: width 0.3s ease; |
| } |
| |
| .file-attachment-remove { |
| position: absolute; |
| top: -8px; |
| right: -8px; |
| width: 20px; |
| height: 20px; |
| background: #ef4444; |
| color: white; |
| border-radius: 50%; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| cursor: pointer; |
| transition: all 0.2s; |
| border: 2px solid var(--bg-secondary); |
| box-shadow: 0 2px 4px rgba(0,0,0,0.2); |
| z-index: 10; |
| } |
| |
| .file-attachment-remove svg { |
| width: 12px; |
| height: 12px; |
| } |
| |
| .file-attachment-remove:hover { |
| background: #dc2626; |
| transform: scale(1.1) rotate(90deg); |
| } |
| |
| @keyframes slideIn { |
| from { opacity: 0; transform: translateY(10px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| |
| |
| .typing-cursor-ai { |
| display: inline-block; |
| width: 8px; |
| height: 18px; |
| background: var(--accent-blue); |
| margin-left: 4px; |
| vertical-align: middle; |
| animation: blink 0.8s infinite; |
| border-radius: 2px; |
| box-shadow: 0 0 8px var(--accent-blue); |
| } |
| |
| @keyframes blink { |
| 0%, 100% { opacity: 1; } |
| 50% { opacity: 0; } |
| } |
| |
| |
| .message { |
| display: flex; |
| gap: 16px; |
| max-width: 850px; |
| margin: 0 auto; |
| width: 100%; |
| padding: 10px 0; |
| opacity: 0; |
| transform: translateY(10px); |
| animation: messageAppear 0.4s cubic-bezier(0.2, 0.8, 0.2, 1) forwards; |
| } |
| |
| @keyframes messageAppear { |
| to { |
| opacity: 1; |
| transform: translateY(0); |
| } |
| } |
| |
| |
| .message.ai.thinking-glow .message-content { |
| filter: blur(0.5px); |
| opacity: 0.8; |
| transition: all 0.3s ease; |
| } |
| |
| .features-modal-overlay { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background: rgba(0, 0, 0, 0.8); |
| backdrop-filter: blur(12px); |
| z-index: 2000; |
| display: none; |
| align-items: center; |
| justify-content: center; |
| opacity: 0; |
| transition: all 0.3s ease; |
| } |
| |
| .features-modal-overlay.open { |
| display: flex; |
| opacity: 1; |
| } |
| |
| .features-modal { |
| background: var(--bg-secondary); |
| border: 1px solid var(--border-color); |
| border-radius: 24px; |
| width: 90%; |
| max-width: 650px; |
| max-height: 85vh; |
| overflow-y: auto; |
| padding: 32px; |
| position: relative; |
| box-shadow: 0 25px 50px rgba(0,0,0,0.5); |
| } |
| |
| .features-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); |
| gap: 20px; |
| margin-top: 24px; |
| } |
| |
| .feature-item { |
| background: rgba(255, 255, 255, 0.03); |
| border: 1px solid var(--border-color); |
| border-radius: 16px; |
| padding: 20px; |
| transition: all 0.2s; |
| } |
| |
| .feature-item:hover { |
| background: rgba(255, 255, 255, 0.05); |
| border-color: var(--accent-blue); |
| } |
| |
| .feature-icon { |
| width: 40px; |
| height: 40px; |
| background: rgba(16, 163, 127, 0.1); |
| color: var(--accent-blue); |
| border-radius: 10px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| margin-bottom: 16px; |
| } |
| |
| .feature-item h3 { |
| font-size: 16px; |
| margin-bottom: 8px; |
| color: var(--text-primary); |
| } |
| |
| .feature-item p { |
| font-size: 13px; |
| color: var(--text-secondary); |
| line-height: 1.5; |
| } |
| |
| .features-close { |
| position: absolute; |
| top: 20px; |
| right: 20px; |
| background: none; |
| border: none; |
| color: var(--text-secondary); |
| cursor: pointer; |
| padding: 8px; |
| border-radius: 50%; |
| transition: all 0.2s; |
| } |
| |
| .features-close:hover { |
| background: rgba(255, 255, 255, 0.05); |
| color: var(--text-primary); |
| } |
| |
| |
| .memory-indicator { |
| position: absolute; |
| top: 70px; |
| right: 20px; |
| z-index: 100; |
| display: none; |
| align-items: center; |
| gap: 8px; |
| background: rgba(16, 163, 127, 0.1); |
| backdrop-filter: blur(12px); |
| color: #10a37f; |
| padding: 8px 16px; |
| border-radius: 12px; |
| font-size: 13px; |
| font-weight: 600; |
| border: 1px solid rgba(16, 163, 127, 0.2); |
| box-shadow: 0 4px 12px rgba(0,0,0,0.2); |
| animation: slideInRight 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); |
| } |
| |
| @keyframes slideInRight { |
| from { transform: translateX(100%); opacity: 0; } |
| to { transform: translateX(0); opacity: 1; } |
| } |
| |
| .memory-indicator.visible { |
| display: flex; |
| } |
| |
| |
| @media (max-width: 768px) { |
| .tools-panel { |
| bottom: 100px; |
| width: 90%; |
| justify-content: space-around; |
| padding: 6px; |
| } |
| .tool-btn { |
| width: 50px; |
| height: 50px; |
| } |
| } |
| |
| .status-badge-container { |
| display: flex; |
| gap: 8px; |
| margin-bottom: 12px; |
| animation: fadeIn 0.5s ease; |
| } |
| |
| .status-badge { |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| padding: 4px 10px; |
| border-radius: 20px; |
| font-size: 11px; |
| font-weight: 600; |
| text-transform: uppercase; |
| letter-spacing: 0.5px; |
| border: 1px solid transparent; |
| } |
| |
| .status-badge.auto-mode { |
| background: rgba(16, 163, 127, 0.1); |
| color: #10a37f; |
| border-color: rgba(16, 163, 127, 0.2); |
| box-shadow: 0 0 10px rgba(16, 163, 127, 0.1); |
| } |
| |
| .status-badge.online { |
| background: rgba(59, 130, 246, 0.1); |
| color: #3b82f6; |
| border-color: rgba(59, 130, 246, 0.2); |
| } |
| |
| .status-badge.offline { |
| background: rgba(239, 68, 68, 0.1); |
| color: #ef4444; |
| border-color: rgba(239, 68, 68, 0.2); |
| animation: pulse-red 2s infinite; |
| } |
| |
| @keyframes pulse-red { |
| 0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); } |
| 70% { box-shadow: 0 0 0 10px rgba(239, 68, 68, 0); } |
| 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); } |
| } |
| |
| |
| .sidebar-section { |
| margin-bottom: 20px; |
| padding: 0 4px; |
| } |
| |
| .sidebar-section-title { |
| font-size: 13px; |
| font-weight: 600; |
| color: var(--text-primary); |
| margin-bottom: 12px; |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| padding: 0 4px; |
| } |
| |
| .ai-modes-grid { |
| display: grid; |
| grid-template-columns: 1fr 1fr; |
| gap: 8px; |
| margin-bottom: 16px; |
| } |
| |
| .mode-btn { |
| background: rgba(255, 255, 255, 0.03); |
| border: 1px solid var(--border-color); |
| border-radius: 10px; |
| padding: 8px; |
| color: var(--text-secondary); |
| font-size: 12px; |
| font-weight: 500; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| gap: 4px; |
| text-align: center; |
| } |
| |
| .mode-btn i { |
| width: 16px; |
| height: 16px; |
| } |
| |
| .mode-btn:hover { |
| background: rgba(255, 255, 255, 0.08); |
| color: var(--text-primary); |
| border-color: rgba(255, 255, 255, 0.2); |
| } |
| |
| .mode-btn.active { |
| background: rgba(16, 163, 127, 0.1); |
| color: var(--accent-blue); |
| border-color: var(--accent-blue); |
| box-shadow: 0 0 10px rgba(16, 163, 127, 0.1); |
| } |
| |
| |
| .research-toggle-container { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| padding: 10px 12px; |
| background: rgba(255, 255, 255, 0.03); |
| border: 1px solid var(--border-color); |
| border-radius: 12px; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| } |
| |
| .research-toggle-container:hover { |
| background: rgba(255, 255, 255, 0.06); |
| } |
| |
| .research-label { |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| font-size: 13px; |
| font-weight: 500; |
| color: var(--text-primary); |
| } |
| |
| .research-label i { |
| color: var(--accent-blue); |
| width: 18px; |
| height: 18px; |
| } |
| |
| .switch { |
| position: relative; |
| display: inline-block; |
| width: 36px; |
| height: 20px; |
| } |
| |
| .switch input { |
| opacity: 0; |
| width: 0; |
| height: 0; |
| } |
| |
| .slider { |
| position: absolute; |
| cursor: pointer; |
| top: 0; |
| left: 0; |
| right: 0; |
| bottom: 0; |
| background-color: #3f3f3f; |
| transition: .3s; |
| border-radius: 20px; |
| } |
| |
| .slider:before { |
| position: absolute; |
| content: ""; |
| height: 14px; |
| width: 14px; |
| left: 3px; |
| bottom: 3px; |
| background-color: white; |
| transition: .3s; |
| border-radius: 50%; |
| } |
| |
| input:checked + .slider { |
| background-color: var(--accent-blue); |
| } |
| |
| input:checked + .slider:before { |
| transform: translateX(16px); |
| } |
| |
| |
| .research-active-indicator { |
| display: none; |
| align-items: center; |
| gap: 6px; |
| background: rgba(16, 163, 127, 0.1); |
| color: var(--accent-blue); |
| padding: 6px 12px; |
| border-radius: 12px; |
| font-size: 12px; |
| font-weight: 600; |
| margin-bottom: 10px; |
| border: 1px solid rgba(16, 163, 127, 0.2); |
| animation: fadeIn 0.3s ease; |
| width: fit-content; |
| } |
| |
| .research-active-indicator.visible { |
| display: flex; |
| } |
| |
| .mode-dropdown { |
| position: relative; |
| margin-right: 8px; |
| padding-right: 8px; |
| border-right: 1px solid var(--border-color); |
| } |
| |
| .mode-dropdown-trigger { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| background: rgba(255, 255, 255, 0.05); |
| border: 1px solid var(--border-color); |
| border-radius: 12px; |
| padding: 6px 12px; |
| color: var(--text-primary); |
| font-size: 13px; |
| font-weight: 600; |
| cursor: pointer; |
| transition: all 0.2s; |
| min-width: 100px; |
| } |
| |
| .mode-dropdown-trigger:hover { |
| background: rgba(255, 255, 255, 0.08); |
| border-color: rgba(255, 255, 255, 0.2); |
| } |
| |
| .mode-dropdown-trigger i:first-child { |
| color: var(--accent-blue); |
| width: 14px; |
| height: 14px; |
| } |
| |
| .mode-dropdown-trigger i:last-child { |
| width: 12px; |
| height: 12px; |
| color: var(--text-secondary); |
| transition: transform 0.2s; |
| } |
| |
| .mode-dropdown.open .mode-dropdown-trigger i:last-child { |
| transform: rotate(180deg); |
| } |
| |
| .mode-dropdown-content { |
| position: absolute; |
| bottom: calc(100% + 10px); |
| left: 0; |
| background: #1e1e1e; |
| border: 1px solid var(--border-color); |
| border-radius: 16px; |
| padding: 8px; |
| width: 180px; |
| box-shadow: 0 10px 30px rgba(0,0,0,0.5); |
| display: none; |
| flex-direction: column; |
| gap: 4px; |
| z-index: 1000; |
| animation: slideUp 0.2s ease-out; |
| } |
| |
| .mode-dropdown.open .mode-dropdown-content { |
| display: flex; |
| } |
| |
| .mode-option { |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| padding: 10px 12px; |
| border-radius: 10px; |
| color: var(--text-secondary); |
| font-size: 13px; |
| cursor: pointer; |
| transition: all 0.2s; |
| } |
| |
| .mode-option:hover { |
| background: rgba(255, 255, 255, 0.05); |
| color: var(--text-primary); |
| } |
| |
| .mode-option.active { |
| background: rgba(16, 163, 127, 0.1); |
| color: var(--accent-blue); |
| } |
| |
| .mode-option i { |
| width: 16px; |
| height: 16px; |
| } |
| |
| .dropdown-label { |
| padding: 8px 12px 4px 12px; |
| font-size: 10px; |
| font-weight: 700; |
| color: var(--text-secondary); |
| text-transform: uppercase; |
| letter-spacing: 0.5px; |
| } |
| |
| .dropdown-divider { |
| height: 1px; |
| background: var(--border-color); |
| margin: 4px 8px; |
| } |
| |
| |
| @media (max-width: 768px) { |
| .mode-dropdown { |
| display: none; |
| } |
| } |
| |
| .send-button:disabled { |
| opacity: 0.3 !important; |
| cursor: not-allowed !important; |
| transform: none !important; |
| } |
| |
| .btn-primary:disabled { |
| opacity: 0.6 !important; |
| cursor: wait !important; |
| } |
| |
| |
| #authOverlay { |
| z-index: 9999 !important; |
| } |
| |
| |
| .input-actions { |
| position: relative; |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| .stop-button { |
| display: none; |
| background: var(--bg-secondary); |
| border: 1px solid var(--border-color); |
| color: var(--text-primary); |
| } |
| |
| .stop-button.active { |
| display: flex; |
| } |
| </style> |
| </head> |
|
|
| <body> |
| |
| <div class="landing-page" id="landingPage"> |
| <div class="lightning-overlay" id="lightningOverlay"></div> |
| <header class="landing-header"> |
| <div class="landing-logo"> |
| <div class="blinking-emoji"> |
| <div class="eye"></div> |
| <div class="eye"></div> |
| </div> |
| Nexa AI |
| </div> |
| <nav class="landing-nav" id="landingNav"> |
| <a href="javascript:void(0)" class="nav-link" data-section="featuresSection">Features</a> |
| <a href="javascript:void(0)" class="nav-link" data-section="howItWorksSection">How It Works</a> |
| <a href="javascript:void(0)" class="nav-link" data-section="safetySection">Safety</a> |
| <a href="javascript:void(0)" class="nav-link" data-section="pricingSection">Pricing</a> |
| <button onclick="enterApp()" class="btn-primary landing-cta" style="padding: 10px 20px; font-size: 14px;">Try Nexa AI</button> |
| </nav> |
| </header> |
|
|
| <section class="hero-section"> |
| <div class="hero-badge">Chat Smarter, Create Faster</div> |
| <h1 class="hero-title">Your <span id="typing-text"></span><span class="typing-cursor"></span><br>AI Assistant.</h1> |
| <p class="hero-subtitle">Chat with Nexa AI to get answers, generate stunning images, and boost your productivity. Your personal AI assistant for work and creativity.</p> |
| <div class="cta-container"> |
| <button onclick="enterApp()" class="btn-primary landing-cta">Start Chatting — Free Forever</button> |
| |
| <button class="btn-secondary" onclick="scrollToSection('featuresSection')">View Demo</button> |
| </div> |
| <div class="trust-badges"> |
| <div class="trust-badge"><i data-lucide="shield-check"></i> No credit card required</div> |
| <div class="trust-badge"><i data-lucide="lock"></i> Your data is encrypted</div> |
| <div class="trust-badge"><i data-lucide="zap"></i> Free forever</div> |
| </div> |
| </section> |
|
|
| <div class="features-grid" id="featuresSection"> |
| <div class="feature-card"> |
| <div class="feature-icon"><i data-lucide="message-square"></i></div> |
| <h3>Fast Responses</h3> |
| <p>Experience lightning-fast chat interactions. Nexa AI processes your queries in milliseconds for a seamless experience.</p> |
| </div> |
| <div class="feature-card"> |
| <div class="feature-icon"><i data-lucide="image"></i></div> |
| <h3>Image Generation</h3> |
| <p>Create stunning, high-quality images directly from your chat. Powered by advanced AI models for creative freedom.</p> |
| </div> |
| <div class="feature-card"> |
| <div class="feature-icon"><i data-lucide="shield"></i></div> |
| <h3>Secure Chats</h3> |
| <p>Your privacy is our priority. All conversations are encrypted and your personal data stays yours.</p> |
| </div> |
| <div class="feature-card"> |
| <div class="feature-icon"><i data-lucide="infinity"></i></div> |
| <h3>Unlimited Messages</h3> |
| <p>Communicate without boundaries. Nexa AI is free forever with true unlimited power for everyone.</p> |
| </div> |
| </div> |
|
|
| |
| <section class="section-wrapper" id="howItWorksSection"> |
| <div class="section-header"> |
| <h2>How It Works</h2> |
| <p>Getting started with Nexa AI is simple and takes less than a minute.</p> |
| </div> |
| <div class="how-it-works-steps"> |
| <div class="step-item"> |
| <div class="step-number">1</div> |
| <h3>Sign Up</h3> |
| <p>Create your free account in seconds. No credit card or complex setup required.</p> |
| </div> |
| <div class="step-item"> |
| <div class="step-number">2</div> |
| <h3>Start Chat</h3> |
| <p>Type your questions, requests, or image prompts into the intuitive chat interface.</p> |
| </div> |
| <div class="step-item"> |
| <div class="step-number">3</div> |
| <h3>Get Answers</h3> |
| <p>Receive intelligent responses, creative content, or generated images instantly.</p> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section class="section-wrapper" id="safetySection"> |
| <div class="section-header"> |
| <h2>Safety & Privacy</h2> |
| <p>We build Nexa AI with safety and ethical considerations at the core.</p> |
| </div> |
| <div class="features-grid" style="padding: 0;"> |
| <div class="feature-card"> |
| <div class="feature-icon"><i data-lucide="user-check"></i></div> |
| <h3>User First</h3> |
| <p>We don't sell your data to third parties. Your interactions are private and secure.</p> |
| </div> |
| <div class="feature-card"> |
| <div class="feature-icon"><i data-lucide="eye-off"></i></div> |
| <h3>Anonymized Learning</h3> |
| <p>Our models learn from patterns, not personal identities, ensuring your anonymity.</p> |
| </div> |
| <div class="feature-card"> |
| <div class="feature-icon"><i data-lucide="alert-circle"></i></div> |
| <h3>Content Filtering</h3> |
| <p>Advanced filters ensure safe and helpful responses, preventing harmful or inappropriate content.</p> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section class="section-wrapper" id="testimonialsSection"> |
| <div class="section-header"> |
| <h2>What Our Users Say</h2> |
| <p>Join thousands of users who rely on Nexa AI every day.</p> |
| </div> |
| <div class="testimonials-grid"> |
| <div class="testimonial-card"> |
| <p>"Nexa AI has completely transformed how I handle my daily tasks. The image generation is a game-changer!"</p> |
| <div class="testimonial-author">Sarah J., Designer</div> |
| </div> |
| <div class="testimonial-card"> |
| <p>"The fastest AI assistant I've used. It's like having a brilliant partner available 24/7."</p> |
| <div class="testimonial-author">Michael R., Developer</div> |
| </div> |
| <div class="testimonial-card"> |
| <p>"I love the focus on privacy. It's rare to find an AI tool that feels this secure and powerful at the same time."</p> |
| <div class="testimonial-author">Elena T., Writer</div> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section class="section-wrapper" id="pricingSection"> |
| <div class="section-header"> |
| <h2>Free Forever for Everyone</h2> |
| <p>Nexa AI is committed to open intelligence. All features are now completely free.</p> |
| </div> |
| <div class="pricing-grid"> |
| |
| <div class="pricing-card" style="border-color: var(--accent-blue); transform: scale(1.05); grid-column: 1 / -1; max-width: 600px; margin: 0 auto;"> |
| <div style="position: absolute; top: -12px; left: 50%; transform: translateX(-50%); background: var(--accent-blue); color: white; padding: 4px 12px; border-radius: 12px; font-size: 12px; font-weight: 600;">UNLIMITED ACCESS</div> |
| <div class="hero-badge" style="margin-bottom: 16px; background: rgba(16, 163, 127, 0.2);">Free Forever</div> |
| <div style="font-size: 48px; font-weight: 700; margin-bottom: 8px;">$0<span style="font-size: 16px; color: var(--text-secondary);">/forever</span></div> |
| <p style="color: var(--text-secondary); margin-bottom: 24px;">No credit card, no limits, just pure AI power.</p> |
| <ul style="text-align: left; margin-bottom: 32px; list-style: none; padding: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 12px;"> |
| <li style="display: flex; align-items: center; gap: 8px;"><i data-lucide="check" style="color: #10a37f; width: 18px;"></i> Unlimited Messages</li> |
| <li style="display: flex; align-items: center; gap: 8px;"><i data-lucide="check" style="color: #10a37f; width: 18px;"></i> All Premium Models (GPT-4o)</li> |
| <li style="display: flex; align-items: center; gap: 8px;"><i data-lucide="check" style="color: #10a37f; width: 18px;"></i> Unlimited Image Generation</li> |
| <li style="display: flex; align-items: center; gap: 8px;"><i data-lucide="check" style="color: #10a37f; width: 18px;"></i> AI Video Generation</li> |
| <li style="display: flex; align-items: center; gap: 8px;"><i data-lucide="check" style="color: #10a37f; width: 18px;"></i> File Upload & Analysis</li> |
| <li style="display: flex; align-items: center; gap: 8px;"><i data-lucide="check" style="color: #10a37f; width: 18px;"></i> Real-time Web Search</li> |
| <li style="display: flex; align-items: center; gap: 8px;"><i data-lucide="check" style="color: #10a37f; width: 18px;"></i> Deep Research Mode</li> |
| <li style="display: flex; align-items: center; gap: 8px;"><i data-lucide="check" style="color: #10a37f; width: 18px;"></i> Persistent Memory</li> |
| </ul> |
| <button onclick="enterApp()" class="btn-primary landing-cta" style="width: 100%; background: var(--accent-blue);">Get Started Now</button> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section class="section-wrapper" id="faqSection"> |
| <div class="section-header"> |
| <h2>Frequently Asked Questions</h2> |
| <p>Everything you need to know about Nexa AI.</p> |
| </div> |
| <div class="faq-container"> |
| <div class="faq-item"> |
| <div class="faq-question">What is Nexa AI? <i data-lucide="chevron-down"></i></div> |
| <div class="faq-answer">Nexa AI is an intelligent chat assistant designed to help you with writing, coding, image generation, and more.</div> |
| </div> |
| <div class="faq-item"> |
| <div class="faq-question">Is it really free? <i data-lucide="chevron-down"></i></div> |
| <div class="faq-answer">Yes! Nexa AI is now completely free with unlimited messages and access to all premium features for everyone.</div> |
| </div> |
| <div class="faq-item"> |
| <div class="faq-question">How does image generation work? <i data-lucide="chevron-down"></i></div> |
| <div class="faq-answer">Simply describe what you want to see in the chat, and Nexa AI will generate a high-quality image for you using integrated engines like Imagen 3.</div> |
| </div> |
| <div class="faq-item"> |
| <div class="faq-question">Is my data secure? <i data-lucide="chevron-down"></i></div> |
| <div class="faq-answer">Absolutely. We use industry-standard encryption and do not sell your personal data to anyone.</div> |
| </div> |
| <div class="faq-item"> |
| <div class="faq-question">Can I use Nexa AI for coding? <i data-lucide="chevron-down"></i></div> |
| <div class="faq-answer">Yes, Nexa AI is highly capable of writing, debugging, and explaining code across multiple programming languages.</div> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <footer class="landing-footer"> |
| <div class="footer-content"> |
| <div class="footer-info"> |
| <div class="landing-logo" style="margin-bottom: 20px;">Nexa AI</div> |
| <p style="color: var(--text-secondary); margin-bottom: 24px;">Your intelligent AI assistant for the modern age. Chat smarter, create faster, and explore the future of AI.</p> |
| <div style="display: flex; gap: 16px;"> |
| <a href="#" style="color: var(--text-secondary);"><i data-lucide="twitter"></i></a> |
| <a href="#" style="color: var(--text-secondary);"><i data-lucide="github"></i></a> |
| <a href="#" style="color: var(--text-secondary);"><i data-lucide="linkedin"></i></a> |
| </div> |
| </div> |
| <div> |
| <h4 style="margin-bottom: 20px;">Product</h4> |
| <ul style="list-style: none; padding: 0; color: var(--text-secondary);"> |
| <li style="margin-bottom: 12px;"><a href="javascript:void(0)" class="nav-link" data-section="featuresSection" style="color: inherit; text-decoration: none;">Features</a></li> |
| <li style="margin-bottom: 12px;"><a href="javascript:void(0)" class="nav-link" data-section="pricingSection" style="color: inherit; text-decoration: none;">Pricing</a></li> |
| <li style="margin-bottom: 12px;"><a href="javascript:void(0)" class="nav-link" data-section="howItWorksSection" style="color: inherit; text-decoration: none;">How It Works</a></li> |
| </ul> |
| </div> |
| <div> |
| <h4 style="margin-bottom: 20px;">Company</h4> |
| <ul style="list-style: none; padding: 0; color: var(--text-secondary);"> |
| <li style="margin-bottom: 12px;"><a href="#" style="color: inherit; text-decoration: none;">About Us</a></li> |
| <li style="margin-bottom: 12px;"><a href="#" style="color: inherit; text-decoration: none;">Privacy Policy</a></li> |
| <li style="margin-bottom: 12px;"><a href="#" style="color: inherit; text-decoration: none;">Terms of Service</a></li> |
| </ul> |
| </div> |
| <div> |
| <h4 style="margin-bottom: 20px;">Contact</h4> |
| <p style="color: var(--text-secondary); margin-bottom: 12px;">support@nexa-ai.com</p> |
| <p style="color: var(--text-secondary);">Available 24/7</p> |
| </div> |
| </div> |
| <div class="footer-bottom"> |
| © 2026 Nexa AI. All rights reserved. Made for Hugging Face. |
| </div> |
| </footer> |
| </div> |
|
|
| |
| <div class="sidebar-overlay" id="sidebarOverlay"></div> |
| <div class="app-container"> |
| |
| <aside class="sidebar" id="sidebar"> |
| <div class="sidebar-header-mobile"> |
| <div style="display: flex; align-items: center; gap: 8px;"> |
| <div class="blinking-emoji"><div class="eye"></div><div class="eye"></div></div> |
| <div class="landing-logo" style="font-size: 18px; margin: 0;">Nexa AI</div> |
| </div> |
| <button class="sidebar-close-btn"> |
| <i data-lucide="x"></i> |
| </button> |
| </div> |
| <button class="new-chat-btn"> |
| <i data-lucide="plus-circle" style="width: 18px; height: 18px;"></i> |
| <span>New Chat</span> |
| <div class="shortcut-hint"> |
| <span class="key">Ctrl</span> |
| <span class="key">K</span> |
| </div> |
| </button> |
|
|
| <div class="sidebar-section-title" style="margin-top: 10px; display: flex; align-items: center; gap: 8px;"> |
| <i data-lucide="clock" style="width: 16px; height: 16px;"></i> Chat History |
| </div> |
| <div class="conversation-list" id="conversationList"> |
| |
| </div> |
| |
| <div class="menu-item" id="allChatsBtn" style="margin-bottom: 20px;"> |
| <span>All Chats</span> |
| </div> |
|
|
| <div class="sidebar-footer"> |
| <div class="menu-item" id="sidebarProfileBtn"> |
| <div class="account-avatar" id="sidebarAvatar">G</div> |
| <span id="sidebarUserName">Guest User</span> |
| </div> |
| <div class="menu-item" id="sidebarFeaturesBtn" style="color: var(--accent-blue);"> |
| <i data-lucide="sparkles"></i> |
| <span>AI Features</span> |
| </div> |
| <div class="menu-item" id="sidebarExportBtn" style="color: #10a37f;"> |
| <i data-lucide="file-down"></i> |
| <span>Export Chat</span> |
| </div> |
| <div class="menu-item" id="sidebarLogoutBtn"> |
| <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" /> |
| </svg> |
| Log out |
| </div> |
| </div> |
| </aside> |
|
|
| |
| <main class="main-area hero-mode" id="mainArea"> |
| <div class="drag-drop-overlay" id="dragDropOverlay"> |
| <div class="drag-drop-icon"> |
| <i data-lucide="upload-cloud" style="width: 40px; height: 40px;"></i> |
| </div> |
| <div class="drag-drop-text">Drop files to upload to Nexa AI</div> |
| </div> |
| <div class="main-background-lighting"> |
| <div class="ambient-glow glow-1"></div> |
| <div class="ambient-glow glow-2"></div> |
| <div class="grid-overlay"></div> |
| <canvas id="canvas3d"></canvas> |
| </div> |
|
|
| <div id="memoryIndicator" class="memory-indicator"> |
| <i data-lucide="brain"></i> |
| <span>Nexa learned something new about you!</span> |
| </div> |
|
|
| <div class="top-bar"> |
| <div class="model-selector"> |
| <button class="hamburger-menu"> |
| <i data-lucide="menu"></i> |
| </button> |
| <div class="blinking-emoji" id="headerEmoji"> |
| <div class="eye"></div> |
| <div class="eye"></div> |
| </div> |
| <span>Nexa AI</span> |
| </div> |
|
|
| <div class="top-right-menu"> |
| <button class="account-btn"> |
| <div class="account-avatar" id="accountAvatar">G</div> |
| <span id="accountUserName">Guest User</span> |
| <svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /> |
| </svg> |
| </button> |
| <div class="account-dropdown" id="accountDropdown"> |
| <div class="dropdown-item" id="dropdownProfileBtn"> |
| <i data-lucide="user" style="width:16px;"></i> |
| Profile |
| </div> |
| <div class="dropdown-item" id="dropdownSettingsBtn"> |
| <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /> |
| </svg> |
| Settings |
| </div> |
| <div class="dropdown-item" id="dropdownThemeBtn"> |
| <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" /> |
| </svg> |
| Toggle Dark/Light |
| </div> |
| <div class="dropdown-item" id="dropdownLogoutBtn"> |
| <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" /> |
| </svg> |
| Log out |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="chat-history empty-mode" id="chatHistory"> |
| <div class="status-badge-container"> |
| <div class="status-badge auto-mode"> |
| <i data-lucide="zap" style="width:12px;"></i> Auto Mode |
| </div> |
| <div class="status-badge online" id="serverStatusBadge"> |
| <i data-lucide="globe" style="width:12px;"></i> Online |
| </div> |
| </div> |
| <div class="memory-indicator" id="memoryIndicator"> |
| <i data-lucide="brain"></i> |
| <span>Using memory...</span> |
| </div> |
| <div class="research-active-indicator" id="researchIndicator"> |
| <i data-lucide="microscope"></i> |
| <span>Deep Research Active</span> |
| </div> |
| <div class="empty-state" id="emptyState"> |
| <div class="hero-logo-container"> |
| <div class="blinking-emoji" style="width: 100px; height: 100px; gap: 12px; margin-bottom: 24px; animation: floatingLogo 3s ease-in-out infinite;"> |
| <div class="eye" style="width: 12px; height: 20px; border-radius: 6px;"></div> |
| <div class="eye" style="width: 12px; height: 20px; border-radius: 6px;"></div> |
| </div> |
| </div> |
| <div class="blinking-emoji" style="margin-bottom: 20px; gap: 8px;"> |
| <div class="eye" style="width: 12px; height: 12px;"></div> |
| <div class="eye" style="width: 12px; height: 12px;"></div> |
| </div> |
| <h2 class="hero-logo" style="margin-bottom: 12px;">Nexa AI</h2> |
| <p class="hero-subtitle">Your futuristic AI assistant.</p> |
| <div class="suggestions-grid"> |
| <div class="suggestion-card" onclick="document.getElementById('messageInput').value='Create a stunning digital art of a neon city'; sendMessage();"> |
| <div style="display:flex; align-items:center; gap:10px; margin-bottom:8px;"> |
| <i data-lucide="image" style="width:16px; color:#10a37f;"></i> |
| <h4 style="margin:0;">Generate Images</h4> |
| </div> |
| <p>Like a neon futuristic city</p> |
| </div> |
| <div class="suggestion-card" onclick="document.getElementById('messageInput').value='Write a high-performance Python script for data analysis'; sendMessage();"> |
| <div style="display:flex; align-items:center; gap:10px; margin-bottom:8px;"> |
| <i data-lucide="code" style="width:16px; color:#10a37f;"></i> |
| <h4 style="margin:0;">Write Code</h4> |
| </div> |
| <p>For data analysis or web apps</p> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="input-container"> |
| <div class="attachments-container" id="attachmentsContainer" style="display: none;"> |
| |
| </div> |
| <div class="input-wrapper" id="inputWrapper"> |
| <textarea class="message-input" id="messageInput" placeholder="Message Nexa AI..." rows="1"></textarea> |
| |
| <div class="input-container-bottom" id="inputBottomRow"> |
| <div class="input-actions-left"> |
| <button class="action-btn" id="uploadBtn" onclick="toggleUploadMenu()"> |
| <i data-lucide="plus"></i> |
| </button> |
| <div class="upload-menu" id="uploadMenu"> |
| <div class="tool-btn" data-action="search" title="Web Search"> |
| <i data-lucide="search"></i> |
| <span>Search</span> |
| </div> |
| <div class="tool-btn" data-action="image" title="AI Image"> |
| <i data-lucide="image"></i> |
| <span>Image</span> |
| </div> |
| <div class="tool-btn" data-action="upload" title="Upload File"> |
| <i data-lucide="upload"></i> |
| <span>Upload</span> |
| </div> |
| <div class="tool-btn" data-action="voice" id="toolVoiceBtn" title="Voice Mode"> |
| <i data-lucide="mic"></i> |
| <span>Voice</span> |
| </div> |
| <div class="tool-btn" data-action="web" title="Browse Web"> |
| <i data-lucide="globe"></i> |
| <span>Web</span> |
| </div> |
| <div class="tool-btn" id="videoToolBtn" data-action="video" title="AI Video"> |
| <i data-lucide="video"></i> |
| <span>Video</span> |
| </div> |
| </div> |
| <div id="activeFeatureContainer" style="display: none;"></div> |
| </div> |
|
|
| <div class="input-actions-right"> |
| <div class="mode-dropdown" id="modeDropdown"> |
| <button class="mode-dropdown-trigger" onclick="toggleModeDropdown(event)"> |
| <i data-lucide="zap" id="activeModeIcon"></i> |
| <span id="activeModeLabel">Instant</span> |
| <i data-lucide="chevron-down"></i> |
| </button> |
| <div class="mode-dropdown-content"> |
| <div class="dropdown-label">Chat Modes</div> |
| <div class="mode-option" onclick="setAiMode('instant')"> |
| <i data-lucide="zap"></i> |
| <span>Instant</span> |
| </div> |
| <div class="mode-option" onclick="setAiMode('study')"> |
| <i data-lucide="book"></i> |
| <span>Study</span> |
| </div> |
| <div class="mode-option" onclick="setAiMode('dev')"> |
| <i data-lucide="code"></i> |
| <span>Developer</span> |
| </div> |
| <div class="mode-option" onclick="setAiMode('research')"> |
| <i data-lucide="search"></i> |
| <span>Research</span> |
| </div> |
| <div class="mode-option" onclick="setAiMode('creative')"> |
| <i data-lucide="palette"></i> |
| <span>Creative</span> |
| </div> |
| <div class="dropdown-divider"></div> |
| <div class="dropdown-label">Advanced Models</div> |
| <div class="mode-option" onclick="setAiModel('gpt-4o')"> |
| <i data-lucide="sparkles"></i> |
| <span>GPT-4o</span> |
| </div> |
| <div class="mode-option" onclick="setAiModel('claude-3-5')"> |
| <i data-lucide="brain"></i> |
| <span>Claude 3.5</span> |
| </div> |
| <div class="mode-option" onclick="setAiModel('gemini-1-5')"> |
| <i data-lucide="gem"></i> |
| <span>Gemini 1.5 Pro</span> |
| </div> |
| </div> |
| </div> |
| <button class="action-btn mic-btn" id="micBtn" onclick="toggleSpeechRecognition()" title="Voice Input"> |
| <i data-lucide="mic"></i> |
| </button> |
| |
| <button class="send-button" id="sendBtn" title="Send message (Enter)"> |
| <i data-lucide="arrow-up"></i> |
| </button> |
| <button class="stop-button" id="stopBtn" title="Stop generating (Esc)"> |
| <i data-lucide="square"></i> |
| </button> |
| </div> |
| </div> |
| </div> |
| <div class="disclaimer"> |
| Nexa AI can make mistakes. Consider checking important information. |
| </div> |
| </div> |
| </main> |
| </div> |
|
|
| |
| <div class="features-modal-overlay" id="featuresModalOverlay" onclick="handleFeaturesOverlayClick(event)"> |
| <div class="features-modal"> |
| <button class="features-close" onclick="closeFeaturesModal()"> |
| <i data-lucide="x"></i> |
| </button> |
| <h2 style="font-size: 24px; margin-bottom: 8px;">Nexa AI Features</h2> |
| <p style="color: var(--text-secondary); font-size: 14px; margin-bottom: 24px;">Discover what your intelligent assistant can do</p> |
| |
| <div class="features-grid"> |
| <div class="feature-item"> |
| <div class="feature-icon"><i data-lucide="zap"></i></div> |
| <h3>Instant Intelligence</h3> |
| <p>Powered by Qwen 2.5, optimized for speed and complex reasoning.</p> |
| </div> |
| <div class="feature-item"> |
| <div class="feature-icon"><i data-lucide="image"></i></div> |
| <h3>AI Image Generation</h3> |
| <p>Create stunning visuals directly in the chat using /image command.</p> |
| </div> |
| <div class="feature-item"> |
| <div class="feature-icon"><i data-lucide="mic"></i></div> |
| <h3>Voice Recognition</h3> |
| <p>Hands-free interaction with high-accuracy Speech-to-Text.</p> |
| </div> |
| <div class="feature-item"> |
| <div class="feature-icon"><i data-lucide="volume-2"></i></div> |
| <h3>Text-to-Speech</h3> |
| <p>AI responses can be read aloud with premium, natural voices.</p> |
| </div> |
| <div class="feature-item"> |
| <div class="feature-icon"><i data-lucide="search"></i></div> |
| <h3>Real-time Web Search</h3> |
| <p>Access the latest news and facts with live internet browsing.</p> |
| </div> |
| <div class="feature-item"> |
| <div class="feature-icon"><i data-lucide="file-text"></i></div> |
| <h3>File Analysis</h3> |
| <p>Upload PDFs, images, or docs for deep analysis and summaries.</p> |
| </div> |
| <div class="feature-item"> |
| <div class="feature-icon"><i data-lucide="brain"></i></div> |
| <h3>Persistent Memory</h3> |
| <p>Learns your preferences and coding style over multiple sessions.</p> |
| </div> |
| <div class="feature-item"> |
| <div class="feature-icon"><i data-lucide="download"></i></div> |
| <h3>Export Chat</h3> |
| <p>Save important responses as Markdown files for later use.</p> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="settings-modal-overlay" id="settingsModalOverlay" onclick="handleSettingsOverlayClick(event)"> |
| <div class="settings-modal"> |
| <button class="settings-close" onclick="closeSettings()"> |
| <i data-lucide="x"></i> |
| </button> |
| <div class="settings-header"> |
| <div class="settings-title">Settings</div> |
| <div class="settings-subtitle">Manage your preferences and data</div> |
| </div> |
|
|
| <div class="settings-section"> |
| <div class="settings-section-title">Personalization</div> |
| <div class="settings-item"> |
| <div class="settings-item-info"> |
| <h4>Appearance</h4> |
| <p>Switch between light and dark mode</p> |
| </div> |
| <button class="settings-btn" onclick="toggleTheme()">Toggle Theme</button> |
| </div> |
| <div class="settings-item"> |
| <div class="settings-item-info"> |
| <h4>Voice Output</h4> |
| <p>Auto-speak AI responses</p> |
| </div> |
| <button class="settings-btn" id="voiceToggleBtn" onclick="toggleAutoVoice()">Enabled</button> |
| </div> |
| <div class="settings-item"> |
| <div class="settings-item-info"> |
| <h4>Font Size</h4> |
| <p>Adjust chat text size</p> |
| </div> |
| <div style="display: flex; gap: 8px;"> |
| <button class="settings-btn" onclick="setFontSize('small')">Small</button> |
| <button class="settings-btn" onclick="setFontSize('medium')">Med</button> |
| <button class="settings-btn" onclick="setFontSize('large')">Large</button> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="settings-section"> |
| <div class="settings-section-title">Data Management</div> |
| <div class="settings-item"> |
| <div class="settings-item-info"> |
| <h4>Clear Memory</h4> |
| <p>Forget everything Nexa learned about you</p> |
| </div> |
| <button class="settings-btn danger" onclick="confirmClearMemory()">Clear Memory</button> |
| </div> |
| <div class="settings-item"> |
| <div class="settings-item-info"> |
| <h4>Delete All Chats</h4> |
| <p>Permanently delete your entire conversation history</p> |
| </div> |
| <button class="settings-btn danger" onclick="confirmDeleteAllChats()">Delete All</button> |
| </div> |
| </div> |
|
|
| <div class="settings-section"> |
| <div class="settings-section-title">About</div> |
| <div class="settings-item"> |
| <div class="settings-item-info"> |
| <h4>Version</h4> |
| <p>Nexa AI v2.4.0 (Production)</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="confirm-overlay" id="confirmOverlay" onclick="handleConfirmOverlayClick(event)"> |
| <div class="confirm-card"> |
| <div class="confirm-title" id="confirmTitle">Delete All Chats?</div> |
| <div class="confirm-text" id="confirmText">This action cannot be undone. All your conversations will be permanently deleted.</div> |
| <div class="confirm-actions"> |
| <button class="confirm-btn cancel" onclick="closeConfirm()">Cancel</button> |
| <button class="confirm-btn delete" id="confirmDeleteBtn" onclick="">Delete Permanently</button> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="img-modal-overlay" id="imgModalOverlay" onclick="handleImgOverlayClick(event)"> |
| <div class="img-modal-card"> |
| <button class="img-modal-close" onclick="closeImageGenModal()"> |
| <svg width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> |
| </svg> |
| </button> |
|
|
| <div class="img-gen-grid"> |
| <div class="img-prompt-area"> |
| <h2>Generate Image</h2> |
| <p>Describe what you want to create with Nexa AI's advanced visual engine.</p> |
| <textarea class="img-textarea" id="imgPromptInput" placeholder="A futuristic city with floating cars and neon lights, digital art style..."></textarea> |
| <button class="btn-primary" style="width: 100%;" id="imgGenerateBtn" onclick="generateImage()"> |
| <i data-lucide="sparkles"></i> Generate Masterpiece |
| </button> |
| <div id="imgModalStatus" style="margin-top: 16px; font-size: 14px; color: var(--accent-blue);"></div> |
| </div> |
|
|
| <div class="img-preview-area"> |
| <div class="img-preview-container" id="imgResultArea"> |
| <div class="img-skeleton" id="imgSkeleton"></div> |
| <div class="img-placeholder-text">Your generated image will appear here</div> |
| <img id="imgResultImg" src="" alt="Generated preview"> |
| <a href="#" class="img-download-btn" id="imgDownloadBtn"> |
| <i data-lucide="download"></i> Download |
| </a> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="auth-overlay" id="authOverlay"> |
| <div class="auth-card"> |
| <button class="img-modal-close" onclick="loginAsGuest()" style="position: absolute; top: 15px; right: 15px;"> |
| <i data-lucide="x"></i> |
| </button> |
| <div class="auth-title" id="authTitle">Sign In</div> |
| <div class="auth-subtitle" id="authSubtitle">Use your credentials to access Nexa AI</div> |
| <form class="auth-form" id="authForm" onsubmit="handleAuth(event)"> |
| <input type="text" class="auth-input" id="authEmail" placeholder="Email or Username" required> |
| <div class="password-container"> |
| <input type="password" class="auth-input" id="authPassword" placeholder="Password" required style="width: 100%; padding-right: 45px;"> |
| <button type="button" class="password-toggle" id="passwordToggle" onclick="togglePasswordVisibility()" title="Show Password"> |
| <i data-lucide="check" id="toggleIcon"></i> |
| </button> |
| </div> |
| <button type="submit" class="auth-submit" id="authSubmitBtn">Sign In</button> |
| </form> |
| <div class="auth-error" id="authError">Invalid credentials</div> |
| <div class="auth-footer"> |
| <span id="authToggleText">Don't have an account?</span> |
| <span class="auth-toggle" id="authToggleBtn" onclick="toggleAuthMode()">Sign Up</span> |
| </div> |
| <div style="margin-top: 15px; text-align: center; font-size: 13px; opacity: 0.7;"> |
| <span style="color: var(--text-secondary);">Or </span> |
| <span class="auth-toggle" onclick="loginAsGuest()" style="color: #10a37f; cursor: pointer; font-weight: 600;">Continue as Guest</span> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="img-modal-overlay" id="videoOverlayModal" style="display:none; align-items: center; justify-content: center;" onclick="handleVideoOverlayClick(event)"> |
| <div class="img-modal-card" style="width: min(500px, 96vw); max-height: 90vh; overflow-y: auto;"> |
| <button class="img-modal-close" onclick="closeVideoOverlayModal()"> |
| <svg width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> |
| </svg> |
| </button> |
| <div style="padding: 20px;"> |
| <h3 style="margin-bottom: 20px; display: flex; align-items: center; gap: 10px;"> |
| <i data-lucide="type" style="color: var(--accent-blue);"></i> Add Text Overlay |
| </h3> |
| |
| <div style="margin-bottom: 15px;"> |
| <label style="display: block; font-size: 12px; color: var(--text-secondary); margin-bottom: 5px;">Overlay Text</label> |
| <textarea id="overlayTextInput" class="img-textarea" style="min-height: 80px;" placeholder="Enter text to overlay on video..."></textarea> |
| </div> |
|
|
| <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 15px;"> |
| <div> |
| <label style="display: block; font-size: 12px; color: var(--text-secondary); margin-bottom: 5px;">Position</label> |
| <select id="overlayPositionSelect" class="btn-secondary" style="width: 100%; padding: 8px; background: rgba(255,255,255,0.05); border: 1px solid var(--border-color); color: var(--text-primary); border-radius: 8px;"> |
| <option value="Top Left">Top Left</option> |
| <option value="Top Center">Top Center</option> |
| <option value="Top Right">Top Right</option> |
| <option value="Center Left">Center Left</option> |
| <option value="Center">Center</option> |
| <option value="Center Right">Center Right</option> |
| <option value="Bottom Left">Bottom Left</option> |
| <option value="Bottom Center" selected>Bottom Center</option> |
| <option value="Bottom Right">Bottom Right</option> |
| </select> |
| </div> |
| <div> |
| <label style="display: block; font-size: 12px; color: var(--text-secondary); margin-bottom: 5px;">Font Size</label> |
| <input type="number" id="overlayFontSizeInput" class="btn-secondary" style="width: 100%; padding: 8px; background: rgba(255,255,255,0.05); border: 1px solid var(--border-color); color: var(--text-primary); border-radius: 8px;" value="60"> |
| </div> |
| </div> |
|
|
| <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 20px;"> |
| <div> |
| <label style="display: block; font-size: 12px; color: var(--text-secondary); margin-bottom: 5px;">Text Color</label> |
| <input type="color" id="overlayTextColorInput" style="width: 100%; height: 35px; border: none; background: none; cursor: pointer;" value="#FFFFFF"> |
| </div> |
| <div> |
| <label style="display: block; font-size: 12px; color: var(--text-secondary); margin-bottom: 5px;">BG Opacity</label> |
| <input type="range" id="overlayBgOpacityInput" min="0" max="1" step="0.1" value="0.5" style="width: 100%;"> |
| </div> |
| </div> |
|
|
| <button id="applyOverlayBtn" class="btn-primary" style="width: 100%; background: linear-gradient(135deg, #6366f1 0%, #a855f7 100%);" onclick="applyVideoOverlay()"> |
| <i data-lucide="sparkles"></i> Process & Apply Overlay |
| </button> |
| <div id="overlayModalStatus" style="margin-top: 15px; font-size: 13px; text-align: center; color: var(--accent-blue);"></div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| |
| let sidebar, chatHistory, messageInput, sendBtn, stopBtn, accountDropdown, conversationList, authOverlay; |
| |
| |
| window.onerror = function(message, source, lineno, colno, error) { |
| console.error("Global Error:", message, "at", source, lineno, ":", colno); |
| return false; |
| }; |
| |
| |
| |
| function enterApp() { |
| try { |
| const landing = document.getElementById('landingPage'); |
| const appContainer = document.querySelector('.app-container'); |
| const authOvl = document.getElementById('authOverlay'); |
| |
| |
| if (!currentUser) { |
| if (landing) landing.style.display = 'none'; |
| window.history.pushState({}, '', '/login'); |
| if (typeof handleRouting === 'function') { |
| handleRouting(); |
| } else { |
| |
| if (authOvl) { |
| authOvl.style.display = 'flex'; |
| authOvl.style.zIndex = '9999'; |
| authOvl.classList.add('show'); |
| } |
| } |
| if (window.lightningTimeout) { |
| clearTimeout(window.lightningTimeout); |
| window.lightningTimeout = null; |
| } |
| return; |
| } |
| |
| |
| if (landing) { |
| landing.classList.add('hidden'); |
| landing.style.pointerEvents = 'none'; |
| } |
| |
| setTimeout(() => { |
| if (landing) landing.style.display = 'none'; |
| if (appContainer) { |
| appContainer.style.display = 'flex'; |
| appContainer.classList.add('visible'); |
| } |
| document.body.style.overflow = 'auto'; |
| if (window.location.pathname !== '/chat') { |
| window.history.pushState({}, '', '/chat'); |
| } |
| }, 800); |
| |
| if (window.lightningTimeout) { |
| clearTimeout(window.lightningTimeout); |
| window.lightningTimeout = null; |
| } |
| } catch (e) { |
| console.error("enterApp error:", e); |
| } |
| } |
| |
| |
| function scrollToSection(id) { |
| try { |
| const el = document.getElementById(id); |
| if (el) { |
| el.scrollIntoView({ behavior: 'smooth' }); |
| } |
| } catch (e) { |
| console.error("scrollToSection error:", e); |
| } |
| } |
| |
| |
| let isLightMode = false; |
| let isSignUp = false; |
| let currentUser = JSON.parse(localStorage.getItem('chatgpt_current_user') || 'null'); |
| let conversations = []; |
| let currentConversationId = null; |
| let messageCount = 0; |
| let isGeneratingImage = false; |
| let isSearching = false; |
| let isAnalyzingFile = false; |
| let activeUploadsCount = 0; |
| let isTyping = false; |
| let isVoiceActive = false; |
| let searchCooldown = false; |
| let abortController = null; |
| const MAX_MESSAGES = Infinity; |
| |
| |
| let userMemory = JSON.parse(localStorage.getItem('nexa_ai_memory') || '{}'); |
| |
| function updateMemoryFromText(text) { |
| const lowerText = text.toLowerCase(); |
| let changed = false; |
| |
| |
| const preferences = [ |
| { pattern: /i (?:prefer|like) (python|javascript|java|c\+\+|rust|go|php|ruby|html|css)/i, key: 'coding_preference' }, |
| { pattern: /my name is ([\w\s]+)/i, key: 'user_name' }, |
| { pattern: /i (?:am|work as) a ([\w\s]+)/i, key: 'occupation' }, |
| { pattern: /i (?:live|reside) in ([\w\s]+)/i, key: 'location' }, |
| { pattern: /i (?:love|enjoy) ([\w\s]+)/i, key: 'hobby' } |
| ]; |
| |
| preferences.forEach(pref => { |
| const match = text.match(pref.pattern); |
| if (match && match[1]) { |
| userMemory[pref.key] = match[1].trim(); |
| changed = true; |
| } |
| }); |
| |
| if (changed) { |
| localStorage.setItem('nexa_ai_memory', JSON.stringify(userMemory)); |
| showMemoryIndicator(); |
| } |
| } |
| |
| function showMemoryIndicator() { |
| const indicator = document.getElementById('memoryIndicator'); |
| if (indicator) { |
| indicator.classList.add('visible'); |
| setTimeout(() => indicator.classList.remove('visible'), 3000); |
| } |
| } |
| let currentAiMode = localStorage.getItem('nexa_ai_mode') || 'instant'; |
| let currentAiModel = localStorage.getItem('nexa_ai_model') || 'llama-3'; |
| let isDeepResearch = localStorage.getItem('nexa_ai_deep_research') === 'true'; |
| |
| function setAiMode(mode) { |
| currentAiMode = mode; |
| localStorage.setItem('nexa_ai_mode', mode); |
| |
| |
| const label = document.getElementById('activeModeLabel'); |
| const icon = document.getElementById('activeModeIcon'); |
| const modeMap = { |
| 'instant': { label: 'Instant', icon: 'zap' }, |
| 'study': { label: 'Study', icon: 'book' }, |
| 'dev': { label: 'Developer', icon: 'code' }, |
| 'research': { label: 'Research', icon: 'search' }, |
| 'creative': { label: 'Creative', icon: 'palette' } |
| }; |
| |
| if (label && icon && modeMap[mode]) { |
| label.textContent = modeMap[mode].label; |
| icon.setAttribute('data-lucide', modeMap[mode].icon); |
| if (window.lucide) lucide.createIcons(); |
| } |
| |
| |
| document.querySelectorAll('#modeDropdown .mode-option').forEach(opt => { |
| const optLabel = opt.querySelector('span').textContent.toLowerCase(); |
| if (optLabel.includes(mode) || (mode === 'dev' && optLabel === 'developer')) { |
| opt.classList.add('active'); |
| } else { |
| opt.classList.remove('active'); |
| } |
| }); |
| |
| |
| const dropdown = document.getElementById('modeDropdown'); |
| if (dropdown) dropdown.classList.remove('open'); |
| } |
| |
| function setAiModel(model) { |
| currentAiModel = model; |
| localStorage.setItem('nexa_ai_model', model); |
| |
| const label = document.getElementById('activeModelLabel'); |
| const icon = document.getElementById('activeModelIcon'); |
| const modelMap = { |
| 'gpt-4o': { label: 'GPT-4o', icon: 'sparkles' }, |
| 'claude-3-5': { label: 'Claude 3.5', icon: 'brain' }, |
| 'gemini-1-5': { label: 'Gemini 1.5 Pro', icon: 'gem' }, |
| 'llama-3': { label: 'Llama 3', icon: 'cpu' }, |
| 'mistral-large': { label: 'Mistral Large', icon: 'wind' } |
| }; |
| |
| if (label && icon && modelMap[model]) { |
| label.textContent = modelMap[model].label; |
| icon.setAttribute('data-lucide', modelMap[model].icon); |
| if (window.lucide) lucide.createIcons(); |
| } |
| |
| document.querySelectorAll('#modelDropdown .mode-option').forEach(opt => { |
| const optModel = opt.getAttribute('onclick').match(/'(.*)'/)[1]; |
| if (optModel === model) opt.classList.add('active'); |
| else opt.classList.remove('active'); |
| }); |
| |
| const dropdown = document.getElementById('modelDropdown'); |
| if (dropdown) dropdown.classList.remove('open'); |
| } |
| |
| function toggleModeDropdown(e) { |
| e.stopPropagation(); |
| const dropdown = document.getElementById('modeDropdown'); |
| if (dropdown) dropdown.classList.toggle('open'); |
| const modelDropdown = document.getElementById('modelDropdown'); |
| if (modelDropdown) modelDropdown.classList.remove('open'); |
| } |
| |
| function toggleModelDropdown(e) { |
| e.stopPropagation(); |
| const dropdown = document.getElementById('modelDropdown'); |
| if (dropdown) dropdown.classList.toggle('open'); |
| const modeDropdown = document.getElementById('modeDropdown'); |
| if (modeDropdown) modeDropdown.classList.remove('open'); |
| } |
| |
| |
| window.addEventListener('click', () => { |
| const modeDropdown = document.getElementById('modeDropdown'); |
| const modelDropdown = document.getElementById('modelDropdown'); |
| if (modeDropdown) modeDropdown.classList.remove('open'); |
| if (modelDropdown) modelDropdown.classList.remove('open'); |
| }); |
| |
| <!-- FIXED: Removed inversion logic and added explicit parameter --> |
| function toggleDeepResearch(checked) { |
| isDeepResearch = !!checked; |
| localStorage.setItem('nexa_ai_deep_research', isDeepResearch); |
| |
| |
| const indicator = document.getElementById('researchIndicator'); |
| if (indicator) { |
| if (isDeepResearch) indicator.classList.add('visible'); |
| else indicator.classList.remove('visible'); |
| } |
| } |
| |
| function initAiModes() { |
| setAiMode(currentAiMode); |
| const toggle = document.getElementById('deepResearchToggle'); |
| if (toggle) toggle.checked = isDeepResearch; |
| |
| const indicator = document.getElementById('researchIndicator'); |
| if (indicator && isDeepResearch) indicator.classList.add('visible'); |
| } |
| |
| function saveMemory(key, value) { |
| userMemory[key] = value; |
| localStorage.setItem('nexa_ai_memory', JSON.stringify(userMemory)); |
| } |
| |
| function getMemoryString() { |
| if (Object.keys(userMemory).length === 0) return ""; |
| return "\n[AI Memory of User]: " + Object.entries(userMemory) |
| .map(([k, v]) => `${k}: ${v}`) |
| .join(", "); |
| } |
| |
| function clearMemory() { |
| userMemory = {}; |
| localStorage.removeItem('nexa_ai_memory'); |
| } |
| |
| |
| let activeFeature = null; |
| |
| function handleToolAction(action) { |
| const input = document.getElementById('messageInput'); |
| const uploadMenu = document.getElementById('uploadMenu'); |
| if (uploadMenu) uploadMenu.classList.remove('show'); |
| |
| |
| if (activeFeature) deactivateFeature(); |
| |
| switch(action) { |
| case 'search': |
| case 'web': |
| activateFeature('search'); |
| break; |
| case 'image': |
| activateFeature('image'); |
| break; |
| case 'video': |
| activateFeature('video'); |
| break; |
| case 'upload': |
| |
| let fileInput = document.getElementById('hiddenFileInput'); |
| if (!fileInput) { |
| fileInput = document.createElement('input'); |
| fileInput.type = 'file'; |
| fileInput.id = 'hiddenFileInput'; |
| fileInput.multiple = true; |
| fileInput.style.display = 'none'; |
| fileInput.onchange = (e) => handleFiles(e.target.files); |
| document.body.appendChild(fileInput); |
| } |
| fileInput.click(); |
| return; |
| case 'voice': |
| toggleVoiceMode(); |
| return; |
| } |
| input.focus(); |
| } |
| |
| function activateFeature(feature) { |
| activeFeature = feature; |
| const container = document.getElementById('activeFeatureContainer'); |
| const input = document.getElementById('messageInput'); |
| const wrapper = document.getElementById('inputWrapper'); |
| |
| if (!container || !input || !wrapper) return; |
| |
| let icon = 'sparkles'; |
| let label = 'Feature'; |
| let placeholder = 'Message Nexa AI...'; |
| |
| switch(feature) { |
| case 'image': |
| icon = 'image'; |
| label = 'Image'; |
| placeholder = 'Describe or edit an image'; |
| wrapper.classList.add('image-mode'); |
| break; |
| case 'search': |
| icon = 'search'; |
| label = 'Search'; |
| placeholder = 'Search for anything...'; |
| break; |
| case 'video': |
| icon = 'video'; |
| label = 'Video'; |
| placeholder = 'Describe a video to generate...'; |
| break; |
| } |
| |
| container.innerHTML = ` |
| <div class="feature-badge"> |
| <i data-lucide="${icon}"></i> |
| <span>${label}</span> |
| <button class="remove-feature-btn" onclick="deactivateFeature()"> |
| <i data-lucide="x"></i> |
| </button> |
| </div> |
| `; |
| container.style.display = 'flex'; |
| input.placeholder = placeholder; |
| wrapper.classList.add('feature-active'); |
| |
| if (window.lucide) lucide.createIcons(); |
| } |
| |
| function deactivateFeature() { |
| activeFeature = null; |
| const container = document.getElementById('activeFeatureContainer'); |
| const input = document.getElementById('messageInput'); |
| const wrapper = document.getElementById('inputWrapper'); |
| |
| if (container) { |
| container.innerHTML = ''; |
| container.style.display = 'none'; |
| } |
| if (input) { |
| input.placeholder = 'Message Nexa AI...'; |
| } |
| if (wrapper) { |
| wrapper.classList.remove('image-mode'); |
| wrapper.classList.remove('feature-active'); |
| } |
| } |
| |
| async function handleVideoCommand(fullText) { |
| const prompt = fullText.replace('/video', '').trim(); |
| if (!prompt) { |
| addMessage('Please provide a prompt for the video. Example: `/video a cinematic sunset`', 'ai'); |
| return; |
| } |
| |
| |
| messageInput.value = ''; |
| messageInput.style.height = 'auto'; |
| if (sendBtn) sendBtn.classList.remove('active'); |
| |
| if (!currentConversationId) { |
| currentConversationId = await createConversation(fullText); |
| } else { |
| addMessageToConversation(currentConversationId, fullText, 'user'); |
| } |
| |
| const thinkingDiv = showThinking(); |
| const messageContent = thinkingDiv.querySelector('.message-content'); |
| |
| messageContent.innerHTML = ` |
| <div class="tool-output-card"> |
| <div class="tool-output-header"> |
| <i data-lucide="video"></i> |
| <span>AI Video Generation</span> |
| </div> |
| <div style="padding: 20px; text-align: center;"> |
| <div class="gen-loader" style="margin: 0 auto 15px;"></div> |
| <div style="color: var(--text-secondary); font-size: 14px; font-weight: 500;"> |
| 🎬 Generating your video... (5-15 seconds) |
| </div> |
| </div> |
| </div> |
| `; |
| if (window.lucide) lucide.createIcons(); |
| smoothScrollToBottom(); |
| |
| try { |
| const response = await fetch('/api/video/generate', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ prompt: prompt }) |
| }); |
| const data = await response.json(); |
| |
| if (data.status === 'success') { |
| hideThinking(thinkingDiv); |
| addMessage(`AI Video ready: "${prompt}"`, 'ai'); |
| |
| const lastMsg = chatHistory.lastElementChild; |
| const content = lastMsg.querySelector('.message-content'); |
| const videoCard = document.createElement('div'); |
| videoCard.className = 'image-card'; |
| const videoId = 'vid-' + Date.now(); |
| |
| videoCard.innerHTML = ` |
| <div class="video-container" id="${videoId}-container"> |
| <video id="${videoId}" autoplay loop muted playsinline> |
| <source src="${data.video_url}" type="video/mp4"> |
| </video> |
| <div class="custom-video-controls"> |
| <button class="vid-ctrl-btn play-pause"> |
| <i data-lucide="pause"></i> |
| </button> |
| <div class="video-progress"> |
| <div class="video-progress-filled"></div> |
| </div> |
| <div class="volume-container"> |
| <button class="vid-ctrl-btn volume-btn"> |
| <i data-lucide="volume-2"></i> |
| </button> |
| <div class="volume-slider-vertical"> |
| <input type="range" min="0" max="1" step="0.1" value="1"> |
| </div> |
| </div> |
| </div> |
| </div> |
| <div class="image-card-actions"> |
| <a href="${data.video_url}" download class="img-btn"><i data-lucide="download"></i> Download</a> |
| <button class="img-btn" onclick="openVideoOverlayModal('${data.video_url}')"><i data-lucide="type"></i> Add Text Overlay</button> |
| </div> |
| `; |
| content.appendChild(videoCard); |
| if (window.lucide) lucide.createIcons(); |
| initCustomVideoPlayer(videoId); |
| smoothScrollToBottom(); |
| addMessageToConversation(currentConversationId, `Generated video: ${data.video_url}`, 'ai'); |
| } else { |
| throw new Error(data.error || "Video generation failed"); |
| } |
| } catch (err) { |
| hideThinking(thinkingDiv); |
| addMessage(err.message, 'ai'); |
| } |
| } |
| |
| async function applyVideoOverlay() { |
| const videoUrl = document.getElementById('videoOverlayModal').getAttribute('data-video-url'); |
| const text = document.getElementById('overlayTextInput').value; |
| const pos = document.getElementById('overlayPositionSelect').value; |
| const fontSize = document.getElementById('overlayFontSizeInput').value; |
| const color = document.getElementById('overlayTextColorInput').value; |
| const bgOpacity = document.getElementById('overlayBgOpacityInput').value; |
| const btn = document.getElementById('applyOverlayBtn'); |
| const status = document.getElementById('overlayModalStatus'); |
| |
| if (!text) { |
| alert("Please enter some text to overlay."); |
| return; |
| } |
| |
| btn.disabled = true; |
| btn.innerHTML = `<i data-lucide="loader-2" class="animate-spin"></i> Processing...`; |
| if (window.lucide) lucide.createIcons(); |
| status.textContent = "Applying text overlay to your video..."; |
| |
| try { |
| |
| |
| |
| |
| const response = await fetch('/api/video/overlay-url', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| video_url: videoUrl, |
| text: text, |
| pos: pos, |
| font_size: fontSize, |
| color: color, |
| bg_opacity: bgOpacity |
| }) |
| }); |
| |
| const data = await response.json(); |
| |
| if (data.status === 'success') { |
| closeVideoOverlayModal(); |
| addMessage(`Text overlay applied successfully!`, 'ai'); |
| |
| const lastMsg = chatHistory.lastElementChild; |
| const content = lastMsg.querySelector('.message-content'); |
| const videoCard = document.createElement('div'); |
| videoCard.className = 'image-card'; |
| const videoId = 'vid-' + Date.now(); |
| |
| videoCard.innerHTML = ` |
| <div class="video-container" id="${videoId}-container"> |
| <video id="${videoId}" autoplay loop muted playsinline> |
| <source src="${data.video_url}" type="video/mp4"> |
| </video> |
| <div class="custom-video-controls"> |
| <button class="vid-ctrl-btn play-pause"> |
| <i data-lucide="pause"></i> |
| </button> |
| <div class="video-progress"> |
| <div class="video-progress-filled"></div> |
| </div> |
| <div class="volume-container"> |
| <button class="vid-ctrl-btn volume-btn"> |
| <i data-lucide="volume-2"></i> |
| </button> |
| <div class="volume-slider-vertical"> |
| <input type="range" min="0" max="1" step="0.1" value="1"> |
| </div> |
| </div> |
| </div> |
| </div> |
| <div class="image-card-actions"> |
| <a href="${data.video_url}" download class="img-btn"><i data-lucide="download"></i> Download Video</a> |
| </div> |
| `; |
| content.appendChild(videoCard); |
| if (window.lucide) lucide.createIcons(); |
| initCustomVideoPlayer(videoId); |
| smoothScrollToBottom(); |
| } else { |
| status.textContent = "Error: " + data.error; |
| status.style.color = "#ef4444"; |
| } |
| } catch (err) { |
| console.error(err); |
| status.textContent = "Processing failed. Try a shorter text."; |
| status.style.color = "#ef4444"; |
| } finally { |
| btn.disabled = false; |
| btn.innerHTML = `<i data-lucide="sparkles"></i> Process & Apply Overlay`; |
| if (window.lucide) lucide.createIcons(); |
| } |
| } |
| |
| function closeVideoOverlayModal() { |
| document.getElementById('videoOverlayModal').style.display = 'none'; |
| } |
| |
| function handleVideoOverlayClick(e) { |
| if (e.target.id === 'videoOverlayModal') closeVideoOverlayModal(); |
| } |
| |
| function initCustomVideoPlayer(videoId) { |
| const video = document.getElementById(videoId); |
| const container = document.getElementById(videoId + '-container'); |
| if (!video || !container) return; |
| |
| |
| container.addEventListener('click', (e) => { |
| const playPauseBtn = e.target.closest('.play-pause'); |
| const volumeBtn = e.target.closest('.volume-btn'); |
| const progress = e.target.closest('.video-progress'); |
| |
| if (playPauseBtn) { |
| if (video.paused) { |
| video.play(); |
| playPauseBtn.innerHTML = '<i data-lucide="pause"></i>'; |
| } else { |
| video.pause(); |
| playPauseBtn.innerHTML = '<i data-lucide="play"></i>'; |
| } |
| if (window.lucide) lucide.createIcons(); |
| } |
| |
| if (volumeBtn) { |
| video.muted = !video.muted; |
| volumeBtn.innerHTML = video.muted ? '<i data-lucide="volume-x"></i>' : '<i data-lucide="volume-2"></i>'; |
| if (window.lucide) lucide.createIcons(); |
| } |
| |
| if (progress) { |
| const scrubTime = (e.offsetX / progress.offsetWidth) * video.duration; |
| video.currentTime = scrubTime; |
| } |
| }); |
| |
| video.addEventListener('timeupdate', () => { |
| const progressFilled = container.querySelector('.video-progress-filled'); |
| if (progressFilled) { |
| const percent = (video.currentTime / video.duration) * 100; |
| progressFilled.style.width = percent + '%'; |
| } |
| }); |
| |
| const volumeSlider = container.querySelector('input[type="range"]'); |
| if (volumeSlider) { |
| volumeSlider.addEventListener('input', (e) => { |
| video.volume = e.target.value; |
| video.muted = video.volume === 0; |
| const volumeBtn = container.querySelector('.volume-btn'); |
| if (volumeBtn) { |
| volumeBtn.innerHTML = video.muted ? '<i data-lucide="volume-x"></i>' : '<i data-lucide="volume-2"></i>'; |
| if (window.lucide) lucide.createIcons(); |
| } |
| }); |
| } |
| } |
| |
| function toggleVoiceMode() { |
| if (!sendBtn) return; |
| isVoiceActive = !isVoiceActive; |
| const voiceBtn = document.getElementById('toolVoiceBtn'); |
| if (isVoiceActive) { |
| voiceBtn.classList.add('active'); |
| addMessage("Voice mode activated. (Simulated)", "ai"); |
| } else { |
| voiceBtn.classList.remove('active'); |
| addMessage("Voice mode deactivated.", "ai"); |
| } |
| } |
| |
| |
| |
| function toggleSidebar() { |
| if (!sidebar) return; |
| |
| const isMobile = window.innerWidth <= 768; |
| const overlay = document.getElementById('sidebarOverlay'); |
| |
| if (isMobile) { |
| sidebar.classList.toggle('open'); |
| if (overlay) overlay.classList.toggle('active'); |
| document.body.classList.toggle('sidebar-open'); |
| } else { |
| sidebar.classList.toggle('collapsed'); |
| |
| localStorage.setItem('nexa_sidebar_collapsed', sidebar.classList.contains('collapsed')); |
| } |
| } |
| |
| function closeSidebarOnMobile() { |
| if (window.innerWidth <= 768) { |
| const overlay = document.getElementById('sidebarOverlay'); |
| if (sidebar) sidebar.classList.remove('open'); |
| if (overlay) overlay.classList.remove('active'); |
| document.body.classList.remove('sidebar-open'); |
| } |
| } |
| |
| function toggleUploadMenu() { |
| const uploadMenu = document.getElementById('uploadMenu'); |
| if (uploadMenu) uploadMenu.classList.toggle('show'); |
| } |
| |
| |
| function stopAI() { |
| try { |
| if (abortController) { |
| abortController.abort(); |
| } |
| } catch (e) { |
| console.error("Stop AI error:", e); |
| } finally { |
| |
| abortController = null; |
| isTyping = false; |
| isGeneratingImage = false; |
| isSearching = false; |
| isAnalyzingFile = false; |
| |
| |
| const thinking = document.getElementById('thinkingMessage'); |
| if (thinking) thinking.remove(); |
| |
| if (stopBtn) stopBtn.classList.remove('active'); |
| if (sendBtn) { |
| sendBtn.style.display = 'flex'; |
| sendBtn.disabled = false; |
| } |
| updateSendButtonState(); |
| } |
| } |
| |
| function handleImageRemix(encodedQuery) { |
| const query = atob(encodedQuery); |
| const input = document.getElementById('messageInput'); |
| if (input) { |
| input.value = `Remix this image: ${query}`; |
| input.focus(); |
| autoResizeTextarea(); |
| } |
| } |
| |
| function getUserInitial() { |
| return currentUser?.username?.charAt(0).toUpperCase() || 'G'; |
| } |
| |
| function getConversationStorageKey() { |
| return `chatgpt_conversations_${currentUser?.username || 'guest'}`; |
| } |
| |
| function loadConversationsFromStorage() { |
| const key = getConversationStorageKey(); |
| conversations = JSON.parse(localStorage.getItem(key) || '[]'); |
| } |
| |
| function updateMessageCounter() { |
| |
| } |
| |
| function autoResizeTextarea() { |
| if (!messageInput) return; |
| let timeout; |
| messageInput.addEventListener('input', function () { |
| if (timeout) clearTimeout(timeout); |
| timeout = setTimeout(() => { |
| this.style.height = 'auto'; |
| this.style.height = (this.scrollHeight) + 'px'; |
| updateSendButtonState(); |
| }, 10); |
| }); |
| } |
| |
| function setupDragAndDrop() { |
| const mainArea = document.getElementById('mainArea'); |
| const overlay = document.getElementById('dragDropOverlay'); |
| |
| if (!mainArea || !overlay) return; |
| |
| ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { |
| mainArea.addEventListener(eventName, (e) => { |
| e.preventDefault(); |
| e.stopPropagation(); |
| }, false); |
| }); |
| |
| ['dragenter', 'dragover'].forEach(eventName => { |
| mainArea.addEventListener(eventName, () => { |
| overlay.classList.add('active'); |
| }, false); |
| }); |
| |
| ['dragleave', 'drop'].forEach(eventName => { |
| mainArea.addEventListener(eventName, () => { |
| overlay.classList.remove('active'); |
| }, false); |
| }); |
| |
| mainArea.addEventListener('drop', (e) => { |
| const dt = e.dataTransfer; |
| const files = dt.files; |
| if (files && files.length > 0) { |
| handleFiles(files); |
| } |
| }, false); |
| } |
| |
| function handleFiles(files) { |
| const container = document.getElementById('attachmentsContainer'); |
| if (!container) return; |
| |
| container.style.display = 'flex'; |
| |
| Array.from(files).forEach(file => { |
| const card = document.createElement('div'); |
| card.className = 'file-attachment-card'; |
| card.setAttribute('data-filename', file.name); |
| |
| const fileExt = file.name.split('.').pop().toUpperCase(); |
| let iconColor = '#3182ce'; |
| let iconName = 'file-text'; |
| |
| if (['JPG', 'JPEG', 'PNG', 'GIF', 'WEBP'].includes(fileExt)) { |
| iconColor = '#10a37f'; |
| iconName = 'image'; |
| } else if (fileExt === 'PDF') { |
| iconColor = '#e53e3e'; |
| iconName = 'file-text'; |
| } |
| |
| card.innerHTML = ` |
| <div class="file-attachment-icon" style="background: ${iconColor}"> |
| <i data-lucide="${iconName}" style="width: 18px; height: 18px;"></i> |
| </div> |
| <div class="file-attachment-info"> |
| <span class="file-attachment-name">${file.name}</span> |
| <span class="file-attachment-type">${fileExt} File</span> |
| </div> |
| <div class="file-attachment-remove" onclick="this.parentElement.remove(); if(document.getElementById('attachmentsContainer').children.length === 0) document.getElementById('attachmentsContainer').style.display='none';"> |
| <i data-lucide="x"></i> |
| </div> |
| `; |
| container.appendChild(card); |
| if (window.lucide) lucide.createIcons(); |
| |
| |
| handleFileAnalysis(file, card); |
| }); |
| } |
| |
| function setupEventListeners() { |
| |
| document.addEventListener('click', (e) => { |
| const target = e.target; |
| |
| |
| |
| |
| const navLink = target.closest('.nav-link[data-section]'); |
| if (navLink) { |
| e.preventDefault(); |
| scrollToSection(navLink.getAttribute('data-section')); |
| return; |
| } |
| |
| |
| const faqQuestion = target.closest('.faq-question'); |
| if (faqQuestion) { |
| const item = faqQuestion.parentElement; |
| const wasActive = item.classList.contains('active'); |
| document.querySelectorAll('.faq-item').forEach(i => i.classList.remove('active')); |
| if (!wasActive) item.classList.add('active'); |
| return; |
| } |
| |
| |
| |
| |
| if (target.closest('.hamburger-menu') || target.closest('.sidebar-close-btn') || target.closest('.sidebar-overlay')) { |
| toggleSidebar(); |
| return; |
| } |
| |
| |
| if (target.closest('.new-chat-btn')) { |
| startNewChat(); |
| return; |
| } |
| |
| |
| const toolBtn = target.closest('.tool-btn'); |
| if (toolBtn) { |
| e.preventDefault(); |
| const action = toolBtn.dataset.action; |
| if (action) handleToolAction(action); |
| return; |
| } |
| |
| |
| const convItem = target.closest('.conversation-item'); |
| if (convItem && !target.closest('.delete-btn') && !target.closest('.action-btn')) { |
| const convId = convItem.getAttribute('data-id'); |
| if (convId) { |
| closeSidebarOnMobile(); |
| loadConversation(parseInt(convId)); |
| } |
| return; |
| } |
| |
| |
| if (target.closest('.account-btn')) { |
| toggleAccountDropdown(); |
| return; |
| } |
| |
| |
| if (!target.closest('.mode-dropdown') && !target.closest('.account-btn') && !target.closest('.top-right-menu')) { |
| isDropdownOpen = false; |
| document.querySelectorAll('.mode-dropdown, .account-dropdown').forEach(d => d.classList.remove('open', 'show')); |
| } |
| |
| if (!target.closest('#uploadBtn') && !target.closest('#uploadMenu')) { |
| const uploadMenu = document.getElementById('uploadMenu'); |
| if (uploadMenu) uploadMenu.classList.remove('show'); |
| } |
| }); |
| |
| |
| |
| messageInput = messageInput || document.getElementById('messageInput'); |
| sendBtn = sendBtn || document.getElementById('sendBtn'); |
| |
| if (messageInput) { |
| messageInput.addEventListener('keydown', (e) => { |
| if (e.key === 'Enter' && !e.shiftKey) { |
| e.preventDefault(); |
| sendMessage(); |
| } |
| }); |
| } |
| |
| |
| if (sendBtn) { |
| sendBtn.addEventListener('click', (e) => { |
| e.preventDefault(); |
| sendMessage(); |
| }); |
| } |
| } |
| |
| function showAuthOverlay() { |
| if (authOverlay) { |
| authOverlay.style.display = 'flex'; |
| authOverlay.classList.add('show'); |
| } |
| } |
| |
| function hideAuthOverlay() { |
| if (authOverlay) { |
| authOverlay.style.display = 'none'; |
| authOverlay.classList.remove('show'); |
| } |
| } |
| |
| |
| function loginAsGuest() { |
| currentUser = { |
| id: 0, |
| username: 'Guest', |
| email: 'guest@nexa.ai', |
| avatar: null |
| }; |
| localStorage.setItem('chatgpt_current_user', JSON.stringify(currentUser)); |
| applyUserUI(); |
| loadConversationsFromStorage(); |
| hideAuthOverlay(); |
| |
| |
| if (window.location.pathname !== '/chat') { |
| window.history.pushState({}, '', '/chat'); |
| } |
| enterApp(); |
| } |
| |
| function togglePasswordVisibility() { |
| const passwordInput = document.getElementById('authPassword'); |
| const toggleIcon = document.getElementById('toggleIcon'); |
| const toggleBtn = document.getElementById('passwordToggle'); |
| |
| if (passwordInput.type === 'password') { |
| passwordInput.type = 'text'; |
| toggleBtn.classList.add('active'); |
| toggleBtn.title = "Hide Password"; |
| } else { |
| passwordInput.type = 'password'; |
| toggleBtn.classList.remove('active'); |
| toggleBtn.title = "Show Password"; |
| } |
| } |
| |
| function toggleAuthMode() { |
| isSignUp = !isSignUp; |
| const title = document.getElementById('authTitle'); |
| const submitBtn = document.getElementById('authSubmitBtn'); |
| const toggleBtn = document.getElementById('authToggleBtn'); |
| const toggleText = document.getElementById('authToggleText'); |
| const emailInput = document.getElementById('authEmail'); |
| |
| if (isSignUp) { |
| if (title) title.textContent = 'Sign Up'; |
| if (submitBtn) submitBtn.textContent = 'Sign Up'; |
| if (toggleText) toggleText.textContent = 'Already have an account?'; |
| if (toggleBtn) toggleBtn.textContent = 'Sign In'; |
| if (emailInput) { |
| emailInput.placeholder = 'Email Address'; |
| emailInput.type = 'email'; |
| } |
| } else { |
| if (title) title.textContent = 'Sign In'; |
| if (submitBtn) submitBtn.textContent = 'Sign In'; |
| if (toggleText) toggleText.textContent = "Don't have an account?"; |
| if (toggleBtn) toggleBtn.textContent = 'Sign Up'; |
| if (emailInput) { |
| emailInput.placeholder = 'Email or Username'; |
| emailInput.type = 'text'; |
| } |
| } |
| } |
| |
| async function handleAuth(event) { |
| event.preventDefault(); |
| const email = document.getElementById('authEmail').value; |
| const password = document.getElementById('authPassword').value; |
| const authError = document.getElementById('authError'); |
| const endpoint = isSignUp ? '/api/auth/signup' : '/api/auth/login'; |
| |
| |
| const payload = { email, password }; |
| |
| try { |
| const response = await fetch(endpoint, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(payload) |
| }); |
| |
| const data = await response.json(); |
| |
| if (response.ok) { |
| if (isSignUp) { |
| isSignUp = false; |
| toggleAuthMode(); |
| if (authError) { |
| authError.textContent = 'Account created! Please login.'; |
| authError.style.color = '#10a37f'; |
| authError.style.display = 'block'; |
| } |
| } else { |
| currentUser = data.user; |
| localStorage.setItem('chatgpt_current_user', JSON.stringify(currentUser)); |
| applyUserUI(); |
| loadConversationsFromStorage(); |
| hideAuthOverlay(); |
| |
| |
| if (document.getElementById('landingPage').style.display !== 'none') { |
| enterApp(); |
| } |
| |
| if (window.location.pathname !== '/chat') { |
| window.history.pushState({}, '', '/chat'); |
| } |
| } |
| } else { |
| if (authError) { |
| |
| authError.textContent = isSignUp ? (data.error || 'Signup failed') : 'Incorrect candidate details'; |
| authError.style.color = '#ef4444'; |
| authError.style.display = 'block'; |
| } |
| } |
| } catch (error) { |
| if (authError) { |
| authError.textContent = 'Server error. Please try again.'; |
| authError.style.display = 'block'; |
| } |
| } |
| } |
| |
| async function logoutUser() { |
| try { |
| await fetch('/api/auth/logout', { method: 'POST' }); |
| } catch (e) {} |
| currentUser = null; |
| localStorage.removeItem('chatgpt_current_user'); |
| location.href = '/'; |
| } |
| |
| function applyUserUI() { |
| if (currentUser) { |
| const initial = getUserInitial(); |
| const accAvatar = document.getElementById('accountAvatar'); |
| const sideAvatar = document.getElementById('sidebarAvatar'); |
| const accUser = document.getElementById('accountUserName'); |
| const sideUser = document.getElementById('sidebarUserName'); |
| |
| if (accAvatar) accAvatar.textContent = initial; |
| if (sideAvatar) sideAvatar.textContent = initial; |
| if (accUser) accUser.textContent = currentUser.username; |
| if (sideUser) sideUser.textContent = currentUser.username; |
| } |
| } |
| |
| function addMessage(content, type, isStreaming = false) { |
| if (chatHistory.classList.contains('empty-mode')) { |
| chatHistory.innerHTML = ''; |
| chatHistory.classList.remove('empty-mode'); |
| document.querySelector('.main-area').classList.remove('hero-mode'); |
| } |
| |
| const messageDiv = document.createElement('div'); |
| messageDiv.className = `message ${type}`; |
| |
| const avatar = document.createElement('div'); |
| avatar.className = 'message-avatar'; |
| |
| if (type === 'ai') { |
| avatar.innerHTML = '<div class="blinking-emoji"><div class="eye"></div><div class="eye"></div></div>'; |
| } else { |
| avatar.textContent = getUserInitial(); |
| } |
| |
| const messageContent = document.createElement('div'); |
| messageContent.className = 'message-content'; |
| |
| messageDiv.appendChild(avatar); |
| messageDiv.appendChild(messageContent); |
| chatHistory.appendChild(messageDiv); |
| |
| refreshIcons(); |
| |
| if (type === 'ai' && isStreaming) { |
| typeWordByWord(content, messageContent); |
| } else { |
| let processedContent = content; |
| if (type === 'ai') { |
| |
| processedContent = content.replace(/\*\*(.*?)\*\*/g, '<span class="important-highlight">$1</span>'); |
| } |
| |
| if (typeof marked !== 'undefined') { |
| messageContent.innerHTML = marked.parse(processedContent); |
| } else { |
| messageContent.textContent = processedContent; |
| } |
| |
| renderNexusComponents(messageContent); |
| |
| if (type === 'ai') { |
| addMessageActions(messageContent, content); |
| } |
| smoothScrollToBottom(); |
| } |
| |
| return messageDiv; |
| } |
| |
| async function renderNexusComponents(container) { |
| |
| const mermaidBlocks = container.querySelectorAll('pre code.language-mermaid'); |
| mermaidBlocks.forEach(async (block, idx) => { |
| const syntax = block.textContent; |
| const id = `mermaid-${Date.now()}-${idx}`; |
| const mermaidDiv = document.createElement('div'); |
| mermaidDiv.className = 'mermaid-render-container'; |
| mermaidDiv.id = id; |
| |
| block.parentElement.replaceWith(mermaidDiv); |
| |
| try { |
| const { svg } = await mermaid.render(id + '-svg', syntax); |
| mermaidDiv.innerHTML = svg; |
| } catch (e) { |
| console.error('Mermaid Render Error:', e); |
| mermaidDiv.innerHTML = `<div class="error-box">Diagram render failed: ${e.message}</div>`; |
| } |
| }); |
| |
| |
| const chartBlocks = container.querySelectorAll('pre code.language-chart'); |
| chartBlocks.forEach((block, idx) => { |
| try { |
| const config = JSON.parse(block.textContent); |
| const canvas = document.createElement('canvas'); |
| canvas.className = 'chart-render-canvas'; |
| canvas.style.maxHeight = '300px'; |
| |
| block.parentElement.replaceWith(canvas); |
| new Chart(canvas, config); |
| } catch (e) { |
| console.error('Chart Render Error:', e); |
| } |
| }); |
| } |
| |
| async function typeWordByWord(content, container) { |
| const words = content.split(' '); |
| let currentText = ''; |
| isTyping = true; |
| |
| if (stopBtn) stopBtn.classList.add('active'); |
| if (sendBtn) sendBtn.style.display = 'none'; |
| |
| const textContainer = document.createElement('span'); |
| const cursor = document.createElement('span'); |
| cursor.className = 'typing-cursor-ai'; |
| container.appendChild(textContainer); |
| container.appendChild(cursor); |
| |
| |
| for (let i = 0; i < words.length; i += 8) { |
| if (!isTyping) break; |
| |
| const chunk = words.slice(i, i + 8).join(' '); |
| const span = document.createElement('span'); |
| span.className = 'streaming-text'; |
| span.innerHTML = (i === 0 ? '' : ' ') + chunk; |
| |
| textContainer.appendChild(span); |
| currentText += (i === 0 ? '' : ' ') + chunk; |
| |
| smoothScrollToBottom(); |
| |
| |
| await new Promise(resolve => setTimeout(resolve, 5)); |
| } |
| |
| cursor.remove(); |
| |
| const processedContent = content.replace(/\*\*(.*?)\*\*/g, '<span class="important-highlight">$1</span>'); |
| container.innerHTML = marked.parse(processedContent); |
| renderNexusComponents(container); |
| addMessageActions(container, content); |
| isTyping = false; |
| if (stopBtn) stopBtn.classList.remove('active'); |
| if (sendBtn) sendBtn.style.display = 'flex'; |
| smoothScrollToBottom(); |
| } |
| |
| function smoothScrollToBottom() { |
| if (chatHistory) { |
| chatHistory.scrollTo({ |
| top: chatHistory.scrollHeight, |
| behavior: 'smooth' |
| }); |
| } |
| } |
| |
| function showThinking() { |
| const thinkingDiv = document.createElement('div'); |
| thinkingDiv.className = 'message ai thinking-glow'; |
| thinkingDiv.id = 'thinkingMessage'; |
| |
| const avatar = document.createElement('div'); |
| avatar.className = 'message-avatar'; |
| avatar.innerHTML = '<div class="blinking-emoji"><div class="eye"></div><div class="eye"></div></div>'; |
| |
| const messageContent = document.createElement('div'); |
| messageContent.className = 'message-content'; |
| |
| const statusMessages = [ |
| "Nexa is analyzing your request...", |
| "Searching internal knowledge base...", |
| "Synthesizing the perfect response...", |
| "Optimizing for accuracy and clarity...", |
| "Nexa is drafting your answer..." |
| ]; |
| let statusIdx = 0; |
| |
| const modelLabel = currentAiModel || 'Qwen 2.5'; |
| |
| messageContent.innerHTML = ` |
| <div style="display:flex; flex-direction:column; align-items:flex-start; gap:12px; padding: 10px 0;"> |
| <div class="llm-badge"> |
| <i data-lucide="cpu" style="width:12px;"></i> |
| <span>Using ${modelLabel}</span> |
| </div> |
| <div class="ai-processing-ring"> |
| <div class="blinking-emoji" style="width:24px; height:24px;"> |
| <div class="eye" style="width:3px; height:5px;"></div> |
| <div class="eye" style="width:3px; height:5px;"></div> |
| </div> |
| </div> |
| <div style="display:flex; align-items:center; gap:12px;"> |
| <div class="thinking"> |
| <span></span> |
| <span></span> |
| <span></span> |
| </div> |
| <span class="thinking-status-text" id="thinkingStatusText">Nexa is thinking...</span> |
| </div> |
| </div> |
| `; |
| |
| thinkingDiv.appendChild(avatar); |
| thinkingDiv.appendChild(messageContent); |
| if (chatHistory) chatHistory.appendChild(thinkingDiv); |
| if (window.lucide) lucide.createIcons(); |
| smoothScrollToBottom(); |
| |
| |
| const statusInterval = setInterval(() => { |
| const statusText = document.getElementById('thinkingStatusText'); |
| if (statusText && isTyping) { |
| statusIdx = (statusIdx + 1) % statusMessages.length; |
| statusText.textContent = statusMessages[statusIdx]; |
| } else { |
| clearInterval(statusInterval); |
| } |
| }, 3000); |
| |
| return thinkingDiv; |
| } |
| |
| function hideThinking(thinkingDiv) { |
| if (thinkingDiv) thinkingDiv.remove(); |
| } |
| |
| async function handleAgentMode(query) { |
| const thinkingDiv = showThinking(); |
| const messageContent = thinkingDiv.querySelector('.message-content'); |
| |
| |
| addMessage(query, 'user'); |
| |
| try { |
| abortController = new AbortController(); |
| const response = await fetch('/api/chat', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| signal: abortController.signal, |
| body: JSON.stringify({ |
| message: query, |
| username: currentUser ? currentUser.username : 'Guest', |
| history: getConversationHistorySnapshot(currentConversationId), |
| memory: userMemory, |
| mode: currentAiMode, |
| deep_research: isDeepResearch |
| }) |
| }); |
| |
| hideThinking(thinkingDiv); |
| if (!response.ok) throw new Error('Chat API failed'); |
| |
| const reader = response.body.getReader(); |
| const decoder = new TextDecoder(); |
| let fullResponse = ""; |
| let aiMsgDiv = null; |
| let aiContentEl = null; |
| let cursorEl = null; |
| let buffer = ''; |
| let toolsUsed = []; |
| |
| while (true) { |
| const { done, value } = await reader.read(); |
| if (done) break; |
| |
| buffer += decoder.decode(value, { stream: true }); |
| const lines = buffer.split('\n'); |
| buffer = lines.pop(); |
| |
| for (const line of lines) { |
| if (!line.trim()) continue; |
| try { |
| const data = JSON.parse(line); |
| |
| if (data.error) { |
| hideThinking(thinkingDiv); |
| addMessage(data.error, 'ai'); |
| return; |
| } |
| |
| |
| if (data.search_results) { |
| const searchCard = document.createElement('div'); |
| searchCard.className = 'tool-output-card'; |
| searchCard.innerHTML = ` |
| <div class="tool-output-header"> |
| <i data-lucide="search"></i> |
| <span>Intelligence Sources Discovered</span> |
| </div> |
| <div class="search-results"> |
| ${data.search_results.results.map(res => ` |
| <a href="${res.href || res.link}" target="_blank" class="search-card"> |
| <h5><i data-lucide="external-link" style="width:14px;"></i> ${res.title}</h5> |
| <p>${res.body || res.snippet}</p> |
| </a> |
| `).join('')} |
| </div> |
| `; |
| chatHistory.appendChild(searchCard); |
| if (window.lucide) lucide.createIcons(); |
| smoothScrollToBottom(); |
| } |
| |
| if (data.token) { |
| if (!aiMsgDiv) { |
| aiMsgDiv = addMessage("", 'ai'); |
| aiContentEl = aiMsgDiv.querySelector('.message-content'); |
| |
| |
| if (toolsUsed.includes('search') || data.search_results) { |
| aiContentEl.innerHTML = '<div class="compiled-search-badge"><i data-lucide="brain"></i> Synthesized Research Report</div>'; |
| } else { |
| aiContentEl.innerHTML = ''; |
| } |
| |
| cursorEl = document.createElement('span'); |
| cursorEl.className = 'typing-cursor-ai'; |
| aiContentEl.appendChild(cursorEl); |
| if (window.lucide) lucide.createIcons(); |
| } |
| fullResponse += data.token; |
| const tempSpan = document.createElement('span'); |
| tempSpan.className = 'streaming-text'; |
| tempSpan.textContent = data.token; |
| aiContentEl.insertBefore(tempSpan, cursorEl); |
| |
| if (data.tools_used) toolsUsed = data.tools_used; |
| smoothScrollToBottom(); |
| } |
| } catch (e) { |
| console.error("Stream parse error:", e); |
| } |
| } |
| } |
| |
| if (aiMsgDiv) { |
| if (cursorEl) cursorEl.remove(); |
| const badge = (toolsUsed.includes('search')) ? '<div class="compiled-search-badge"><i data-lucide="brain"></i> Synthesized Research Report</div>' : ''; |
| aiContentEl.innerHTML = badge + marked.parse(fullResponse); |
| |
| |
| addMessageActions(aiContentEl, fullResponse); |
| refreshIcons(); |
| |
| |
| if (toolsUsed.includes('video')) { |
| const videoContainer = document.createElement('div'); |
| videoContainer.className = 'image-card'; |
| videoContainer.innerHTML = `<div class="img-skeleton visible" style="height: 300px; background: rgba(255,255,255,0.05); border-radius: 12px; animation: pulse 1.5s infinite; display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 10px;"> |
| <i data-lucide="video" class="animate-pulse" style="width:40px; height:40px; color: #10a37f;"></i> |
| <span style="font-size: 14px; color: var(--text-secondary);">Generating AI Video...</span> |
| </div>`; |
| aiContentEl.appendChild(videoContainer); |
| smoothScrollToBottom(); |
| if (window.lucide) lucide.createIcons(); |
| |
| try { |
| const videoRes = await fetch('/api/video/generate', { |
| method: 'POST', |
| headers: {'Content-Type': 'application/json'}, |
| body: JSON.stringify({ prompt: query }) |
| }); |
| const videoData = await videoRes.json(); |
| |
| if (videoData.status === 'success') { |
| const videoId = 'vid-' + Date.now(); |
| if (videoData.is_motion_image) { |
| videoContainer.innerHTML = ` |
| <div class="video-container" style="background:none; position: relative;"> |
| <img src="${videoData.video_url}" alt="${query}" class="motion-image" style="width:100%; border-radius:12px; display:block;"> |
| <div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0,0,0,0.5); border-radius: 50%; width: 60px; height: 60px; display: flex; align-items: center; justify-content: center; pointer-events: none;"> |
| <i data-lucide="play" style="width: 30px; height: 30px; color: white; fill: white;"></i> |
| </div> |
| </div> |
| <div class="image-card-actions"> |
| <a href="${videoData.video_url}" class="img-btn" onclick="downloadImage(event, '${videoData.video_url}')"><i data-lucide="download"></i> Download</a> |
| <button class="img-btn" onclick="openVideoOverlayModal('${videoData.video_url}')"><i data-lucide="type"></i> Add Text</button> |
| </div> |
| `; |
| } else { |
| videoContainer.innerHTML = ` |
| <div class="video-container" id="${videoId}-container"> |
| <video id="${videoId}" autoplay loop muted playsinline> |
| <source src="${videoData.video_url}" type="video/mp4"> |
| </video> |
| <div class="custom-video-controls"> |
| <button class="vid-ctrl-btn play-pause"><i data-lucide="pause"></i></button> |
| <div class="video-progress"><div class="video-progress-filled"></div></div> |
| <div class="volume-container"> |
| <button class="vid-ctrl-btn volume-btn"><i data-lucide="volume-x"></i></button> |
| <div class="volume-slider-vertical"><input type="range" min="0" max="1" step="0.1" value="0"></div> |
| </div> |
| </div> |
| </div> |
| <div class="image-card-actions"> |
| <a href="${videoData.video_url}" download class="img-btn"><i data-lucide="download"></i> Download Video</a> |
| <button class="img-btn" onclick="openVideoOverlayModal('${videoData.video_url}')"><i data-lucide="type"></i> Add Text</button> |
| </div> |
| `; |
| } |
| if (window.lucide) lucide.createIcons(); |
| if (!videoData.is_motion_image) initCustomVideoPlayer(videoId); |
| } else { |
| videoContainer.innerHTML = `<div style="padding: 20px; color: #f87171;"><i data-lucide="alert-circle"></i> Video failed: ${videoData.error}</div>`; |
| if (window.lucide) lucide.createIcons(); |
| } |
| } catch (vErr) { |
| videoContainer.innerHTML = `<div style="padding: 20px; color: #f87171;"><i data-lucide="alert-circle"></i> Video connection error.</div>`; |
| if (window.lucide) lucide.createIcons(); |
| } |
| smoothScrollToBottom(); |
| } |
| |
| |
| if (toolsUsed.includes('image')) { |
| const imageContainer = document.createElement('div'); |
| imageContainer.className = 'image-card'; |
| imageContainer.innerHTML = `<div class="img-skeleton visible" style="height: 300px; background: rgba(255,255,255,0.05); border-radius: 12px; animation: pulse 1.5s infinite;"></div>`; |
| aiContentEl.appendChild(imageContainer); |
| smoothScrollToBottom(); |
| |
| const imgRes = await fetch('/api/image', { |
| method: 'POST', |
| headers: {'Content-Type': 'application/json'}, |
| body: JSON.stringify({ prompt: query }) |
| }); |
| const imgData = await imgRes.json(); |
| |
| imageContainer.innerHTML = ` |
| <div class="image-card-header"><i data-lucide="image" style="width:14px;color:#10a37f;"></i><span>Generated Image</span></div> |
| <img src="${imgData.image_url}" class="loaded" onload="this.parentElement.querySelector('.img-skeleton')?.remove();"> |
| <div class="image-card-actions"> |
| <a href="${imgData.image_url}" target="_blank" class="img-btn"><i data-lucide="external-link"></i> Open</a> |
| <a href="${imgData.image_url}" download class="img-btn" onclick="downloadImage(event, '${imgData.image_url}')"><i data-lucide="download"></i> Download</a> |
| </div> |
| `; |
| if (window.lucide) lucide.createIcons(); |
| } |
| |
| addMessageToConversation(currentConversationId, fullResponse, 'ai'); |
| if (window.lucide) lucide.createIcons({ attrs: { 'stroke-width': 2.5 } }); |
| } |
| |
| } catch (e) { |
| console.error("Agent mode failed:", e); |
| hideThinking(thinkingDiv); |
| addMessage("I encountered a problem while processing your request.", 'ai'); |
| } finally { |
| abortController = null; |
| if (stopBtn) stopBtn.classList.remove('active'); |
| if (sendBtn) sendBtn.style.display = 'flex'; |
| } |
| } |
| |
| |
| async function handlePlannerMode(query) { |
| const thinkingDiv = showThinking(); |
| const messageContent = thinkingDiv.querySelector('.message-content'); |
| |
| messageContent.innerHTML = ` |
| <div class="agent-thinking-text"><span class="gen-loader"></span> Planning your schedule...</div> |
| <div class="planner-timeline" id="plannerTimeline"></div> |
| `; |
| smoothScrollToBottom(); |
| |
| try { |
| const response = await fetch('/api/chat', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| message: `Create a detailed hour-by-hour schedule for: "${query}". Format the response as a valid JSON array of objects with "time" and "task" keys only. Example: [{"time": "9:00 AM", "task": "Study Math"}]`, |
| username: currentUser.username, |
| history: [], |
| conversation_id: currentConversationId |
| }) |
| }); |
| const data = await response.json(); |
| hideThinking(thinkingDiv); |
| |
| const aiMsgDiv = addMessage("Here is your structured plan:", 'ai'); |
| const aiContent = aiMsgDiv.querySelector('.message-content'); |
| |
| const timeline = document.createElement('div'); |
| timeline.className = 'planner-timeline'; |
| |
| try { |
| |
| const jsonStr = data.response.match(/\[.*\]/s)?.[0] || "[]"; |
| const tasks = JSON.parse(jsonStr); |
| tasks.forEach(item => { |
| const taskEl = document.createElement('div'); |
| taskEl.className = 'planner-item'; |
| taskEl.innerHTML = ` |
| <div class="planner-time">${item.time}</div> |
| <div class="planner-task">${item.task}</div> |
| `; |
| timeline.appendChild(taskEl); |
| }); |
| aiContent.appendChild(timeline); |
| } catch (e) { |
| |
| aiContent.innerHTML += marked.parse(data.response); |
| } |
| |
| addMessageToConversation(currentConversationId, query, 'user'); |
| addMessageToConversation(currentConversationId, data.response, 'ai'); |
| smoothScrollToBottom(); |
| } catch (e) { |
| hideThinking(thinkingDiv); |
| addMessage("I couldn't generate the plan right now.", 'ai'); |
| } |
| } |
| |
| async function handleImageCommand(fullText) { |
| if (isGeneratingImage) return; |
| |
| const promptPart = fullText.replace('/image', '').trim(); |
| if (!promptPart) { |
| addMessage('Please provide a prompt for the image. Example: `/image a cat in space`', 'ai'); |
| return; |
| } |
| |
| isGeneratingImage = true; |
| |
| let prompt = promptPart; |
| let width = 1024; |
| let height = 1024; |
| let seed = Math.floor(Math.random() * 1000000); |
| |
| const widthMatch = prompt.match(/--width\s+(\d+)/); |
| if (widthMatch) { |
| width = parseInt(widthMatch[1]); |
| prompt = prompt.replace(widthMatch[0], '').trim(); |
| } |
| |
| const heightMatch = prompt.match(/--height\s+(\d+)/); |
| if (heightMatch) { |
| height = parseInt(heightMatch[1]); |
| prompt = prompt.replace(heightMatch[0], '').trim(); |
| } |
| |
| const seedMatch = prompt.match(/--seed\s+(\d+)/); |
| if (seedMatch) { |
| seed = parseInt(seedMatch[1]); |
| prompt = prompt.replace(seedMatch[0], '').trim(); |
| } |
| |
| |
| messageInput.value = ''; |
| messageInput.style.height = 'auto'; |
| if (sendBtn) sendBtn.classList.remove('active'); |
| |
| if (!currentConversationId) { |
| currentConversationId = await createConversation(fullText); |
| } else { |
| addMessageToConversation(currentConversationId, fullText, 'user'); |
| } |
| |
| |
| const statusDiv = document.createElement('div'); |
| statusDiv.className = 'message ai'; |
| statusDiv.innerHTML = ` |
| <div class="message-avatar">AI</div> |
| <div class="message-content"> |
| <div class="gen-status"> |
| <span class="gen-loader"></span> |
| Generating your masterpiece... |
| </div> |
| </div> |
| `; |
| chatHistory.appendChild(statusDiv); |
| smoothScrollToBottom(); |
| |
| if (stopBtn) stopBtn.classList.add('active'); |
| if (sendBtn) sendBtn.style.display = 'none'; |
| |
| try { |
| const encodedPrompt = encodeURIComponent(prompt); |
| const imageUrl = `https://image.pollinations.ai/prompt/${encodedPrompt}?width=${width}&height=${height}&seed=${seed}&nologo=true`; |
| |
| |
| const imageCard = document.createElement('div'); |
| imageCard.className = 'image-card'; |
| imageCard.innerHTML = ` |
| <img src="${imageUrl}" alt="${prompt}" onload="this.classList.add('loaded')" onerror="this.parentElement.innerHTML='<div style=\'padding:20px;color:#ef4444;font-size:13px;\'><i data-lucide=\'alert-circle\'></i> Image failed to load from provider.</div>'; lucide.createIcons();"> |
| <div class="image-card-actions"> |
| <a href="${imageUrl}" target="_blank" class="img-btn"> |
| <i data-lucide="external-link"></i> Open |
| </a> |
| <a href="${imageUrl}" download="nexa-ai-${Date.now()}.jpg" class="img-btn" onclick="downloadImage(event, '${imageUrl}')"> |
| <i data-lucide="download"></i> Download |
| </a> |
| </div> |
| `; |
| |
| |
| const messageContent = statusDiv.querySelector('.message-content'); |
| messageContent.innerHTML = ''; |
| messageContent.appendChild(document.createTextNode(`Here is your generated image for: "${prompt}"`)); |
| messageContent.appendChild(imageCard); |
| |
| addMessageToConversation(currentConversationId, `Generated image: ${imageUrl}`, 'ai'); |
| |
| if (window.lucide) lucide.createIcons(); |
| smoothScrollToBottom(); |
| } catch (error) { |
| console.error("Image generation failed:", error); |
| statusDiv.querySelector('.message-content').innerHTML = `Sorry, image generation failed: ${error.message}. Please try again.`; |
| } finally { |
| isGeneratingImage = false; |
| if (stopBtn) stopBtn.classList.remove('active'); |
| if (sendBtn) sendBtn.style.display = 'flex'; |
| } |
| } |
| |
| async function handleSearchCommand(fullText) { |
| if (isSearching || searchCooldown) return; |
| |
| const query = fullText.replace(/^\/(search|news|youtube)\s*/, '').trim(); |
| if (!query) { |
| addMessage('Please provide a search query. Example: `/search latest AI news`', 'ai'); |
| return; |
| } |
| |
| isSearching = true; |
| searchCooldown = true; |
| setTimeout(() => searchCooldown = false, 3000); |
| |
| |
| messageInput.value = ''; |
| messageInput.style.height = 'auto'; |
| if (sendBtn) sendBtn.classList.remove('active'); |
| |
| if (!currentConversationId) { |
| currentConversationId = await createConversation(fullText); |
| } else { |
| addMessageToConversation(currentConversationId, fullText, 'user'); |
| } |
| |
| const thinkingDiv = showThinking(); |
| const messageContent = thinkingDiv.querySelector('.message-content'); |
| |
| messageContent.innerHTML = ` |
| <div class="tool-output-card"> |
| <div class="tool-output-header"> |
| <i data-lucide="search"></i> |
| <span>Web Search</span> |
| </div> |
| <div class="gen-status"> |
| <span class="gen-loader"></span> |
| Searching the web for "${query}"... |
| </div> |
| </div> |
| `; |
| if (window.lucide) lucide.createIcons(); |
| smoothScrollToBottom(); |
| |
| try { |
| |
| const searchUrl = window.location.origin + '/api/search'; |
| |
| const response = await fetch(searchUrl, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ query }) |
| }); |
| |
| if (!response.ok) { |
| const errorText = await response.text(); |
| throw new Error(`Server returned ${response.status}: ${errorText.substring(0, 50)}...`); |
| } |
| |
| const data = await response.json(); |
| |
| if (data.results && data.results.length > 0) { |
| const count = data.results.length; |
| addMessage(`🔍 I found **${count} search results** related to your question. Let me visit the most relevant sources to gather accurate information.`, 'ai'); |
| |
| messageContent.innerHTML = ` |
| <div class="tool-output-header"> |
| <i data-lucide="search"></i> |
| <span>Intelligence Sources Discovered</span> |
| </div> |
| <div class="search-results"> |
| ${data.results.map(res => ` |
| <a href="${res.href || res.link}" target="_blank" class="search-card"> |
| <h5><i data-lucide="external-link"></i> ${res.title}</h5> |
| <p>${res.body || res.snippet}</p> |
| </a> |
| `).join('')} |
| </div> |
| <div class="compiling-loader" id="compilingLoader"> |
| <i data-lucide="loader-2"></i> |
| <span>Synthesizing verified research report...</span> |
| </div> |
| `; |
| if (window.lucide) lucide.createIcons(); |
| } else { |
| messageContent.innerHTML = `<p style="font-size:13px; color:var(--text-secondary);">No results found.</p>`; |
| } |
| |
| |
| abortController = new AbortController(); |
| const chatRes = await fetch('/api/chat', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| signal: abortController.signal, |
| body: JSON.stringify({ |
| message: fullText, |
| username: currentUser ? currentUser.username : 'Guest', |
| history: getConversationHistorySnapshot(currentConversationId), |
| memory: userMemory, |
| mode: currentAiMode, |
| deep_research: isDeepResearch, |
| conversation_id: currentConversationId |
| }) |
| }); |
| |
| if (!chatRes.ok) throw new Error('Chat API failed'); |
| |
| const reader = chatRes.body.getReader(); |
| const decoder = new TextDecoder(); |
| let fullResponse = ""; |
| let reportStarted = false; |
| let buffer = ''; |
| |
| while (true) { |
| const { done, value } = await reader.read(); |
| if (done) break; |
| |
| buffer += decoder.decode(value, { stream: true }); |
| const lines = buffer.split('\n'); |
| buffer = lines.pop(); |
| |
| for (const line of lines) { |
| if (!line.trim()) continue; |
| try { |
| const chunkData = JSON.parse(line); |
| if (chunkData.token) { |
| if (!reportStarted) { |
| reportStarted = true; |
| const loader = messageContent.querySelector('#compilingLoader'); |
| if (loader) loader.remove(); |
| |
| const badge = document.createElement('div'); |
| badge.className = 'compiled-search-badge'; |
| badge.innerHTML = '<i data-lucide="brain"></i> Synthesized Research Report'; |
| messageContent.appendChild(badge); |
| |
| const textContainer = document.createElement('div'); |
| textContainer.className = 'synthesized-report-text'; |
| messageContent.appendChild(textContainer); |
| |
| cursorEl = document.createElement('span'); |
| cursorEl.className = 'typing-cursor-ai'; |
| textContainer.appendChild(cursorEl); |
| if (window.lucide) lucide.createIcons(); |
| } |
| |
| fullResponse += chunkData.token; |
| const reportText = messageContent.querySelector('.synthesized-report-text'); |
| const tempSpan = document.createElement('span'); |
| tempSpan.className = 'streaming-text'; |
| tempSpan.textContent = chunkData.token; |
| reportText.insertBefore(tempSpan, cursorEl); |
| smoothScrollToBottom(); |
| } |
| } catch (e) {} |
| } |
| } |
| |
| if (reportStarted) { |
| if (cursorEl) cursorEl.remove(); |
| const reportText = messageContent.querySelector('.synthesized-report-text'); |
| reportText.innerHTML = marked.parse(fullResponse); |
| addMessageActions(messageContent, fullResponse); |
| addMessageToConversation(currentConversationId, fullResponse, 'ai'); |
| if (window.lucide) lucide.createIcons({ attrs: { 'stroke-width': 2 } }); |
| } |
| |
| } catch (err) { |
| console.error("Search error:", err); |
| hideThinking(thinkingDiv); |
| addMessage(`Sorry, search failed: ${err.message}. Please check if the backend is running correctly.`, 'ai'); |
| } finally { |
| isSearching = false; |
| abortController = null; |
| if (stopBtn) stopBtn.classList.remove('active'); |
| if (sendBtn) sendBtn.style.display = 'flex'; |
| } |
| } |
| |
| |
| function updateSendButtonState() { |
| if (!sendBtn) return; |
| const content = messageInput.value.trim(); |
| if (activeUploadsCount > 0) { |
| sendBtn.disabled = true; |
| sendBtn.classList.remove('active'); |
| sendBtn.style.opacity = '0.5'; |
| sendBtn.style.cursor = 'not-allowed'; |
| } else { |
| if (content !== '') { |
| sendBtn.disabled = false; |
| sendBtn.classList.add('active'); |
| sendBtn.style.opacity = '1'; |
| sendBtn.style.cursor = 'pointer'; |
| } else { |
| sendBtn.disabled = true; |
| sendBtn.classList.remove('active'); |
| sendBtn.style.opacity = '0.5'; |
| sendBtn.style.cursor = 'default'; |
| } |
| } |
| } |
| |
| async function handleFileAnalysis(file, card) { |
| activeUploadsCount++; |
| updateSendButtonState(); |
| |
| const infoEl = card.querySelector('.file-attachment-info'); |
| const typeEl = infoEl.querySelector('.file-attachment-type'); |
| |
| |
| const progressContainer = document.createElement('div'); |
| progressContainer.className = 'upload-progress-container'; |
| progressContainer.style.display = 'block'; |
| progressContainer.innerHTML = '<div class="upload-progress-bar"></div>'; |
| infoEl.appendChild(progressContainer); |
| const progressBar = progressContainer.querySelector('.upload-progress-bar'); |
| |
| const formData = new FormData(); |
| formData.append('file', file); |
| |
| |
| const xhr = new XMLHttpRequest(); |
| |
| |
| if (abortController) { |
| abortController.signal.addEventListener('abort', () => xhr.abort()); |
| } |
| |
| xhr.upload.addEventListener('progress', (e) => { |
| if (e.lengthComputable) { |
| const percent = Math.round((e.loaded / e.total) * 100); |
| progressBar.style.width = percent + '%'; |
| typeEl.innerHTML = `<i data-lucide="upload-cloud" style="width:10px;"></i> ${percent}%`; |
| if (window.lucide) lucide.createIcons(); |
| } |
| }); |
| |
| xhr.onload = async () => { |
| activeUploadsCount--; |
| updateSendButtonState(); |
| |
| if (xhr.status === 200) { |
| try { |
| const data = JSON.parse(xhr.responseText); |
| if (data.error) throw new Error(data.error); |
| |
| card.setAttribute('data-analysis', data.content); |
| card.setAttribute('data-analyzed', 'true'); |
| |
| typeEl.innerHTML = `<i data-lucide="check-circle" style="width:10px;color:#10a37f;"></i> Ready`; |
| progressContainer.style.display = 'none'; |
| } catch (err) { |
| typeEl.innerHTML = `<i data-lucide="alert-circle" style="width:10px;color:#ef4444;"></i> Failed`; |
| console.error("Analysis failed:", err); |
| } |
| } else { |
| typeEl.innerHTML = `<i data-lucide="alert-circle" style="width:10px;color:#ef4444;"></i> Error ${xhr.status}`; |
| } |
| if (window.lucide) lucide.createIcons(); |
| }; |
| |
| xhr.onerror = () => { |
| activeUploadsCount--; |
| updateSendButtonState(); |
| typeEl.innerHTML = `<i data-lucide="alert-circle" style="width:10px;color:#ef4444;"></i> Connection Error`; |
| if (window.lucide) lucide.createIcons(); |
| }; |
| |
| xhr.open('POST', '/api/analyze-file'); |
| xhr.send(formData); |
| } |
| |
| |
| async function downloadImage(e, url) { |
| e.preventDefault(); |
| try { |
| const response = await fetch(url); |
| const blob = await response.blob(); |
| const blobUrl = window.URL.createObjectURL(blob); |
| const link = document.createElement('a'); |
| link.href = blobUrl; |
| |
| |
| let ext = 'jpg'; |
| if (url.includes('.mp4')) ext = 'mp4'; |
| else if (url.includes('.png')) ext = 'png'; |
| else if (url.includes('.webp')) ext = 'webp'; |
| |
| link.download = `nexa-download-${Date.now()}.${ext}`; |
| document.body.appendChild(link); |
| link.click(); |
| document.body.removeChild(link); |
| window.URL.revokeObjectURL(blobUrl); |
| } catch (err) { |
| window.open(url, '_blank'); |
| } |
| } |
| |
| async function sendMessage() { |
| if (!messageInput) return; |
| let content = messageInput.value.trim(); |
| if (!content) return; |
| |
| |
| if (content.length > 4000) { |
| const blob = new Blob([content], { type: 'text/plain' }); |
| const file = new File([blob], `long_message_${Date.now()}.txt`, { type: 'text/plain' }); |
| |
| const attachContainer = document.getElementById('attachmentsContainer'); |
| attachContainer.style.display = 'flex'; |
| |
| const card = document.createElement('div'); |
| card.className = 'file-attachment-card'; |
| card.setAttribute('data-filename', file.name); |
| card.setAttribute('data-preview', content); |
| card.innerHTML = ` |
| <div class="file-attachment-icon" style="background: #38a169"> |
| <i data-lucide="file-text" style="width: 18px; height: 18px;"></i> |
| </div> |
| <div class="file-attachment-info"> |
| <span class="file-attachment-name">${file.name}</span> |
| <span class="file-attachment-type">TXT (Auto-generated)</span> |
| </div> |
| <div class="file-attachment-remove" onclick="this.parentElement.remove(); if(document.getElementById('attachmentsContainer').children.length === 0) document.getElementById('attachmentsContainer').style.display='none';"> |
| <i data-lucide="x"></i> |
| </div> |
| `; |
| attachContainer.appendChild(card); |
| if (window.lucide) lucide.createIcons(); |
| |
| content = "I've uploaded a long message as a text file for analysis. Please process the attached file."; |
| messageInput.value = ''; |
| } |
| |
| |
| if (activeFeature) { |
| const commands = ['/image', '/search', '/video', '/news', '/youtube', '/upload', '/remember', '/forget', '/my memory']; |
| const hasCommand = commands.some(cmd => content.startsWith(cmd)); |
| |
| if (!hasCommand) { |
| if (activeFeature === 'image') content = `/image ${content}`; |
| else if (activeFeature === 'search') content = `/search ${content}`; |
| else if (activeFeature === 'video') content = `/video ${content}`; |
| } |
| |
| deactivateFeature(); |
| } |
| |
| |
| if (isTyping || isGeneratingImage || isSearching) return; |
| |
| |
| updateMemoryFromText(content); |
| const { text: attachmentText, html: attachmentHtml } = buildAttachmentContext(); |
| closeSidebarOnMobile(); |
| |
| const userMsgDiv = addMessage(content, 'user'); |
| if (attachmentHtml) { |
| const contentEl = userMsgDiv.querySelector('.message-content'); |
| const cardsContainer = document.createElement('div'); |
| cardsContainer.innerHTML = attachmentHtml; |
| contentEl.prepend(cardsContainer); |
| } |
| |
| messageInput.value = ''; |
| messageInput.style.height = 'auto'; |
| if (sendBtn) sendBtn.classList.remove('active'); |
| |
| |
| if (content.startsWith('/image')) { handleImageCommand(content); return; } |
| if (content.startsWith('/video')) { handleVideoCommand(content); return; } |
| if (content.startsWith('/search') || content.startsWith('/news') || content.startsWith('/youtube')) { handleSearchCommand(content); return; } |
| if (content.startsWith('/upload')) { triggerUpload('Document'); messageInput.value = ''; return; } |
| |
| |
| const rememberMatch = content.match(/^\/remember\s+(.+?)\s+is\s+(.+)$/i); |
| if (content.startsWith('/remember')) { |
| if (rememberMatch) { |
| const key = rememberMatch[1].trim(); |
| const value = rememberMatch[2].trim(); |
| saveMemory(key, value); |
| addMessage(`I'll remember that ${key} is ${value}. 🧠`, 'ai'); |
| } else { |
| addMessage("Please use format: `/remember [key] is [value]`", 'ai'); |
| } |
| messageInput.value = ''; |
| return; |
| } |
| if (content.startsWith('/forget')) { clearMemory(); addMessage("I've forgotten everything I knew about you. 🗑️", 'ai'); messageInput.value = ''; return; } |
| if (content.startsWith('/my memory')) { |
| const mem = Object.entries(userMemory); |
| if (mem.length === 0) addMessage("My memory is currently empty.", 'ai'); |
| else { |
| const memStr = mem.map(([k, v]) => `• **${k}**: ${v}`).join('\n'); |
| addMessage(`Here's what I remember about you:\n${memStr}`, 'ai'); |
| } |
| messageInput.value = ''; |
| return; |
| } |
| |
| const plannerKeywords = ['plan', 'schedule', 'timeline', 'routine', 'study day', 'itinerary']; |
| const isPlannerRequest = plannerKeywords.some(k => content.toLowerCase().includes(k)) && !content.startsWith('/'); |
| |
| |
| const attachContainer = document.getElementById('attachmentsContainer'); |
| if (attachContainer) { |
| attachContainer.innerHTML = ''; |
| attachContainer.style.display = 'none'; |
| } |
| |
| if (isPlannerRequest) { |
| if (!currentConversationId) currentConversationId = await createConversation(content); |
| else addMessageToConversation(currentConversationId, content, 'user'); |
| handlePlannerMode(content); |
| return; |
| } |
| |
| if (sendBtn) sendBtn.disabled = true; |
| |
| if (!currentConversationId && currentUser) { |
| currentConversationId = await createConversation(content); |
| } else if (currentConversationId) { |
| addMessageToConversation(currentConversationId, content, 'user'); |
| } |
| |
| |
| const thinkingDiv = showThinking(); |
| isTyping = true; |
| if (stopBtn) stopBtn.classList.add('active'); |
| if (sendBtn) sendBtn.style.display = 'none'; |
| |
| try { |
| abortController = new AbortController(); |
| const history = getConversationHistorySnapshot(currentConversationId); |
| |
| const response = await fetch('/api/chat', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| signal: abortController.signal, |
| body: JSON.stringify({ |
| message: content, |
| username: currentUser ? currentUser.username : 'Guest', |
| history: history, |
| memory: userMemory, |
| attachments: attachmentText, |
| mode: currentAiMode, |
| deep_research: isDeepResearch, |
| conversation_id: currentConversationId |
| }) |
| }); |
| |
| hideThinking(thinkingDiv); |
| |
| if (!response.ok) throw new Error('Network response was not ok'); |
| |
| const reader = response.body.getReader(); |
| const decoder = new TextDecoder(); |
| let fullResponse = ""; |
| let aiMsgDiv = null; |
| let aiContentEl = null; |
| let cursorEl = null; |
| let buffer = ''; |
| let toolsUsed = []; |
| |
| while (true) { |
| const { done, value } = await reader.read(); |
| if (done) break; |
| |
| buffer += decoder.decode(value, { stream: true }); |
| const lines = buffer.split('\n'); |
| buffer = lines.pop(); |
| |
| for (const line of lines) { |
| if (!line.trim()) continue; |
| try { |
| const data = JSON.parse(line); |
| if (data.error) throw new Error(data.error); |
| |
| |
| if (data.search_results) { |
| |
| if (!aiMsgDiv) { |
| aiMsgDiv = addMessage("", 'ai'); |
| aiContentEl = aiMsgDiv.querySelector('.message-content'); |
| } |
| |
| const sourcesHtml = ` |
| <div class="tool-output-header"> |
| <i data-lucide="search"></i> |
| <span>Sources Discovered</span> |
| </div> |
| <div class="search-results"> |
| ${data.search_results.results.map(res => ` |
| <a href="${res.href || res.link}" target="_blank" class="search-card"> |
| <h5><i data-lucide="external-link"></i> ${res.title}</h5> |
| <p>${res.body || res.snippet}</p> |
| </a> |
| `).join('')} |
| </div> |
| <div class="compiling-loader" id="compilingLoader"> |
| <i data-lucide="loader-2"></i> |
| <span>Compiling synthesized research report...</span> |
| </div> |
| `; |
| aiContentEl.innerHTML = sourcesHtml; |
| if (window.lucide) lucide.createIcons(); |
| smoothScrollToBottom(); |
| } |
| |
| if (data.tools_used) { |
| toolsUsed = data.tools_used; |
| |
| if (toolsUsed.includes('image') && !aiMsgDiv) { |
| |
| } |
| } |
| |
| if (data.token) { |
| if (!aiMsgDiv) { |
| aiMsgDiv = addMessage("", 'ai'); |
| aiContentEl = aiMsgDiv.querySelector('.message-content'); |
| aiContentEl.innerHTML = ''; |
| } |
| |
| |
| const loader = document.getElementById('compilingLoader'); |
| if (loader) { |
| loader.remove(); |
| |
| const badge = document.createElement('div'); |
| badge.className = 'compiled-search-badge'; |
| badge.innerHTML = '<i data-lucide="brain"></i> Synthesized Research Report'; |
| aiContentEl.appendChild(badge); |
| if (window.lucide) lucide.createIcons(); |
| |
| cursorEl = document.createElement('span'); |
| cursorEl.className = 'typing-cursor-ai'; |
| aiContentEl.appendChild(cursorEl); |
| } |
| |
| if (!cursorEl) { |
| cursorEl = document.createElement('span'); |
| cursorEl.className = 'typing-cursor-ai'; |
| aiContentEl.appendChild(cursorEl); |
| } |
| |
| fullResponse += data.token; |
| const tempSpan = document.createElement('span'); |
| tempSpan.className = 'streaming-text'; |
| tempSpan.textContent = data.token; |
| aiContentEl.insertBefore(tempSpan, cursorEl); |
| smoothScrollToBottom(); |
| } |
| } catch (e) { |
| console.error("Error parsing stream chunk:", e, line); |
| } |
| } |
| } |
| |
| |
| if (aiMsgDiv) { |
| if (cursorEl) cursorEl.remove(); |
| const badge = (toolsUsed.includes('search')) ? '<div class="compiled-search-badge"><i data-lucide="brain"></i> Synthesized Research Report</div>' : ''; |
| const processedContent = fullResponse.replace(/\*\*(.*?)\*\*/g, '<span class="important-highlight">$1</span>'); |
| aiContentEl.innerHTML = badge + marked.parse(processedContent); |
| |
| |
| if (toolsUsed.includes('image')) { |
| const imageContainer = document.createElement('div'); |
| imageContainer.className = 'image-card'; |
| imageContainer.innerHTML = `<div class="img-skeleton visible" style="height: 300px; background: rgba(255,255,255,0.05); border-radius: 12px; animation: pulse 1.5s infinite;"></div>`; |
| aiContentEl.appendChild(imageContainer); |
| smoothScrollToBottom(); |
| |
| try { |
| const imgRes = await fetch('/api/image', { |
| method: 'POST', |
| headers: {'Content-Type': 'application/json'}, |
| body: JSON.stringify({ prompt: content }) |
| }); |
| const imgData = await imgRes.json(); |
| |
| if (imgData.status === 'success') { |
| imageContainer.innerHTML = ` |
| <div class="image-card-header"><i data-lucide="image" style="width:14px;color:#10a37f;"></i><span>Generated Image</span></div> |
| <img src="${imgData.image_url}" class="loaded" onload="this.parentElement.querySelector('.img-skeleton')?.remove();"> |
| <div class="image-card-actions"> |
| <a href="${imgData.image_url}" target="_blank" class="img-btn"><i data-lucide="external-link"></i> Open</a> |
| <a href="${imgData.image_url}" download class="img-btn" onclick="downloadImage(event, '${imgData.image_url}')"><i data-lucide="download"></i> Download</a> |
| </div> |
| `; |
| } else { |
| imageContainer.innerHTML = ` |
| <div class="image-card-header"><i data-lucide="alert-circle" style="width:14px;color:#ef4444;"></i><span>Generation Error</span></div> |
| <div style="padding: 20px; text-align: center; color: var(--text-secondary); font-size: 13px;"> |
| ${imgData.error || 'The image service is currently busy.'} |
| </div> |
| `; |
| } |
| if (window.lucide) lucide.createIcons(); |
| } catch (imgErr) { |
| console.error("Image generation failed:", imgErr); |
| imageContainer.innerHTML = `<p style="font-size:12px; color:#ef4444; padding:10px;">Failed to generate image.</p>`; |
| } |
| } |
| |
| addMessageActions(aiContentEl, fullResponse); |
| addMessageToConversation(currentConversationId, fullResponse, 'ai'); |
| if (window.lucide) lucide.createIcons({ attrs: { 'stroke-width': 2 } }); |
| } |
| |
| messageCount++; |
| if (currentUser) { |
| currentUser.messages_used = messageCount; |
| localStorage.setItem('chatgpt_current_user', JSON.stringify(currentUser)); |
| } |
| |
| |
| const attachContainer = document.getElementById('attachmentsContainer'); |
| if (attachContainer) { |
| attachContainer.innerHTML = ''; |
| attachContainer.style.display = 'none'; |
| } |
| |
| if (isAutoVoiceEnabled && fullResponse) speakText(fullResponse); |
| |
| } catch (error) { |
| if (error.name === 'AbortError') { |
| console.log('Fetch aborted'); |
| } else { |
| hideThinking(thinkingDiv); |
| addMessage('I encountered a problem. Please check your connection and try again.', 'ai'); |
| } |
| } finally { |
| isTyping = false; |
| abortController = null; |
| if (stopBtn) stopBtn.classList.remove('active'); |
| if (sendBtn) { |
| sendBtn.style.display = 'flex'; |
| sendBtn.disabled = false; |
| } |
| messageInput.focus(); |
| } |
| } |
| |
| function buildAttachmentContext() { |
| const attachContainer = document.getElementById('attachmentsContainer'); |
| if (!attachContainer) return { text: '', html: '' }; |
| const attachedCards = attachContainer.querySelectorAll('.file-attachment-card'); |
| if (!attachedCards.length) return { text: '', html: '' }; |
| |
| const lines = ["IMPORTANT: The user has attached the following files for your analysis. Please prioritize this content for your response:"]; |
| let cardsHtml = '<div class="message-attachments-preview" style="display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px;">'; |
| |
| attachedCards.forEach(card => { |
| const fname = card.getAttribute('data-filename') || 'unknown-file'; |
| const preview = card.getAttribute('data-preview') || ''; |
| const analysis = card.getAttribute('data-analysis') || ''; |
| const type = card.querySelector('.file-attachment-type')?.textContent || 'FILE'; |
| const iconHtml = card.querySelector('.file-attachment-icon')?.outerHTML || ''; |
| |
| if (analysis) { |
| lines.push(`[File: ${fname}] Content Analysis:\n${analysis}`); |
| } else if (preview) { |
| lines.push(`[File: ${fname}] Preview:\n${preview}`); |
| } else { |
| lines.push(`[Attached File: ${fname}]`); |
| } |
| |
| cardsHtml += ` |
| <div class="user-file-card" style="background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); border-radius: 10px; padding: 8px 12px; display: flex; align-items: center; gap: 10px; font-size: 12px; min-width: 150px;"> |
| ${iconHtml} |
| <div style="display: flex; flex-direction: column; overflow: hidden;"> |
| <span style="font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${fname}</span> |
| <span style="font-size: 10px; color: var(--text-secondary);">${type}</span> |
| </div> |
| </div> |
| `; |
| }); |
| |
| cardsHtml += '</div>'; |
| |
| return { |
| text: lines.join('\n\n'), |
| html: cardsHtml |
| }; |
| } |
| |
| function getConversationHistorySnapshot(convId) { |
| if (!convId) return []; |
| const conv = conversations.find(c => c.id === convId); |
| if (!conv || !Array.isArray(conv.messages)) return []; |
| return conv.messages.slice(-8).map(msg => ({ |
| role: msg.type === 'ai' ? 'assistant' : 'user', |
| content: msg.content |
| })); |
| } |
| |
| |
| async function createConversation(firstMessage) { |
| if (!currentUser) return null; |
| try { |
| const title = firstMessage.substring(0, 30) + (firstMessage.length > 30 ? '...' : ''); |
| const resp = await fetch('/api/conversations', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ title, model: currentAiMode }) |
| }); |
| if (resp.ok) { |
| const conv = await resp.json(); |
| conversations.unshift(conv); |
| currentConversationId = conv.id; |
| renderConversations(); |
| return conv.id; |
| } |
| } catch (e) { |
| console.error("Failed to create conversation", e); |
| } |
| return null; |
| } |
| |
| async function addMessageToConversation(convId, content, role) { |
| if (!currentUser || !convId) return; |
| |
| |
| const conv = conversations.find(c => c.id === convId); |
| if (conv) { |
| if (!conv.messages) conv.messages = []; |
| conv.messages.push({ content, role: role === 'ai' ? 'assistant' : 'user' }); |
| } |
| |
| |
| try { |
| await fetch('/api/messages', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| conversation_id: convId, |
| content: content, |
| role: role === 'ai' ? 'assistant' : 'user' |
| }) |
| }); |
| } catch (e) { |
| console.error("Failed to persist message", e); |
| } |
| } |
| |
| async function loadConversation(convId) { |
| if (!currentUser) return; |
| try { |
| const resp = await fetch(`/api/conversations/${convId}`); |
| if (resp.ok) { |
| const data = await resp.json(); |
| currentConversationId = convId; |
| |
| chatHistory.innerHTML = ''; |
| chatHistory.classList.remove('empty-mode'); |
| document.querySelector('.main-area').classList.remove('hero-mode'); |
| |
| data.messages.forEach(msg => { |
| addMessage(msg.content, msg.role === 'assistant' ? 'ai' : 'user', false); |
| }); |
| |
| renderConversations(); |
| smoothScrollToBottom(); |
| } |
| } catch (e) { |
| console.error("Failed to load conversation", e); |
| } |
| } |
| |
| async function deleteConversation(event, convId) { |
| event.stopPropagation(); |
| if (!confirm('Are you sure you want to delete this chat?')) return; |
| try { |
| const resp = await fetch(`/api/conversations/${convId}`, { method: 'DELETE' }); |
| if (resp.ok) { |
| conversations = conversations.filter(c => c.id !== convId); |
| if (currentConversationId === convId) { |
| startNewChat(); |
| } |
| renderConversations(); |
| } |
| } catch (e) { |
| console.error("Failed to delete conversation", e); |
| } |
| } |
| |
| |
| function startNewChat() { |
| |
| if (window.innerWidth <= 768) { |
| const sidebar = document.getElementById('sidebar'); |
| const overlay = document.getElementById('sidebarOverlay'); |
| if (sidebar) sidebar.classList.remove('open'); |
| if (overlay) overlay.classList.remove('active'); |
| document.body.classList.remove('sidebar-open'); |
| } |
| |
| currentConversationId = null; |
| const messageInput = document.getElementById('messageInput'); |
| if (messageInput) { |
| messageInput.placeholder = "Message Nexa AI..."; |
| messageInput.value = ''; |
| messageInput.style.height = 'auto'; |
| } |
| |
| chatHistory.innerHTML = ` |
| <div class="empty-state" id="emptyState"> |
| <div class="blinking-emoji" style="width: 80px; height: 80px; gap: 10px; margin-bottom: 24px;"> |
| <div class="eye" style="width: 10px; height: 16px; border-radius: 5px;"></div> |
| <div class="eye" style="width: 10px; height: 16px; border-radius: 5px;"></div> |
| </div> |
| <h2>How can I help you today?</h2> |
| <div class="suggestions-grid"> |
| <div class="suggestion-card" onclick="document.getElementById('messageInput').value='Help me plan a trip'; document.getElementById('messageInput').focus();"> |
| <h4>Plan a trip</h4> |
| <p>To explore new destinations</p> |
| </div> |
| <div class="suggestion-card" onclick="document.getElementById('messageInput').value='Explain quantum computing'; document.getElementById('messageInput').focus();"> |
| <h4>Explain concepts</h4> |
| <p>Like quantum computing</p> |
| </div> |
| <div class="suggestion-card" onclick="document.getElementById('messageInput').value='Write a thank you email'; document.getElementById('messageInput').focus();"> |
| <h4>Write a message</h4> |
| <p>A thank you email to my boss</p> |
| </div> |
| <div class="suggestion-card" onclick="document.getElementById('messageInput').value='Help me find a bug in my code'; document.getElementById('messageInput').focus();"> |
| <h4>Debug code</h4> |
| <p>Find errors in my script</p> |
| </div> |
| </div> |
| </div> |
| `; |
| |
| const mainArea = document.querySelector('.main-area'); |
| if (mainArea) mainArea.classList.add('hero-mode'); |
| chatHistory.classList.add('empty-mode'); |
| document.body.classList.remove('glowing'); |
| if (messageInput) messageInput.focus(); |
| renderConversations(); |
| } |
| |
| function refreshIcons() { |
| if (typeof lucide !== 'undefined') { |
| lucide.createIcons({ |
| attrs: { |
| 'stroke-width': 2.5, |
| 'class': 'lucide-icon' |
| } |
| }); |
| } |
| } |
| |
| async function init() { |
| |
| sidebar = document.getElementById('sidebar'); |
| chatHistory = document.getElementById('chatHistory'); |
| messageInput = document.getElementById('messageInput'); |
| sendBtn = document.getElementById('sendBtn'); |
| stopBtn = document.getElementById('stopBtn'); |
| accountDropdown = document.getElementById('accountDropdown'); |
| conversationList = document.getElementById('conversationList'); |
| authOverlay = document.getElementById('authOverlay'); |
| |
| |
| const charCounter = document.getElementById('charCounter'); |
| if (messageInput) { |
| messageInput.addEventListener('input', () => { |
| const len = messageInput.value.length; |
| if (charCounter) charCounter.textContent = `${len}/4000`; |
| }); |
| } |
| |
| |
| document.addEventListener('keydown', (e) => { |
| |
| if ((e.ctrlKey || e.metaKey) && e.key === 'k') { |
| e.preventDefault(); |
| startNewChat(); |
| } |
| |
| if ((e.ctrlKey || e.metaKey) && e.key === '/') { |
| e.preventDefault(); |
| const searchInput = document.querySelector('.sidebar-search input'); |
| if (searchInput) searchInput.focus(); |
| } |
| |
| if (e.key === 'Escape' && isTyping) { |
| stopAI(); |
| } |
| }); |
| |
| window.addEventListener('popstate', handleRouting); |
| |
| |
| document.addEventListener('click', e => { |
| const link = e.target.closest('a[href^="/"]'); |
| if (link && !link.target && !e.ctrlKey && !e.metaKey) { |
| e.preventDefault(); |
| const url = link.getAttribute('href'); |
| window.history.pushState({}, '', url); |
| handleRouting(); |
| } |
| }); |
| |
| |
| try { |
| const resp = await fetch('/api/auth/me'); |
| if (resp.ok) { |
| currentUser = await resp.json(); |
| localStorage.setItem('chatgpt_current_user', JSON.stringify(currentUser)); |
| applyUserUI(); |
| loadConversationsFromStorage(); |
| } else { |
| currentUser = null; |
| localStorage.removeItem('chatgpt_current_user'); |
| } |
| } catch (e) { |
| console.error("Auth check failed", e); |
| } |
| |
| handleRouting(); |
| |
| loadConversationsFromStorage(); |
| applyUserUI(); |
| initAiModes(); |
| messageCount = currentUser?.messages_used || 0; |
| updateMessageCounter(); |
| autoResizeTextarea(); |
| |
| |
| const savedState = localStorage.getItem('nexa_sidebar_collapsed'); |
| if (sidebar && savedState === 'true' && window.innerWidth > 768) { |
| sidebar.classList.add('collapsed'); |
| } |
| |
| |
| const savedConvId = localStorage.getItem('nexa_current_conversation_id'); |
| if (savedConvId && conversations.some(c => c.id === parseInt(savedConvId))) { |
| loadConversation(parseInt(savedConvId)); |
| } else { |
| startNewChat(); |
| } |
| |
| refreshIcons(); |
| setupEventListeners(); |
| setupDragAndDrop(); |
| } |
| |
| |
| const routes = { |
| '/': renderLanding, |
| '/chat': renderChat, |
| '/login': renderLogin, |
| '/signup': renderSignup, |
| '/pricing': renderPricing, |
| '/profile': renderProfile |
| }; |
| |
| function handleRouting() { |
| const path = window.location.pathname; |
| const mainArea = document.querySelector('.main-area'); |
| |
| |
| if (mainArea) { |
| mainArea.classList.add('transitioning'); |
| mainArea.style.opacity = '0'; |
| mainArea.style.transform = 'scale(0.98) translateZ(-50px)'; |
| } |
| |
| setTimeout(() => { |
| |
| if (path === '/chat' && !currentUser) { |
| window.history.pushState({}, '', '/login'); |
| renderLogin(); |
| finishTransition(); |
| return; |
| } |
| |
| if ((path === '/login' || path === '/signup') && currentUser) { |
| window.history.pushState({}, '', '/chat'); |
| renderChat(); |
| finishTransition(); |
| return; |
| } |
| |
| const renderFunc = routes[path] || renderLanding; |
| renderFunc(); |
| |
| if (window.innerWidth <= 768) { |
| sidebar?.classList.remove('open'); |
| } |
| finishTransition(); |
| }, 300); |
| } |
| |
| function finishTransition() { |
| const mainArea = document.querySelector('.main-area'); |
| if (mainArea) { |
| setTimeout(() => { |
| mainArea.classList.remove('transitioning'); |
| mainArea.style.opacity = '1'; |
| mainArea.style.transform = 'scale(1) translateZ(0)'; |
| }, 50); |
| } |
| } |
| |
| function renderLanding() { |
| const landing = document.getElementById('landingPage'); |
| const appContainer = document.querySelector('.app-container'); |
| const authOverlay = document.getElementById('authOverlay'); |
| const pricingPage = document.getElementById('pricingPage'); |
| |
| if (landing) { |
| landing.style.display = 'block'; |
| landing.classList.remove('hidden'); |
| landing.style.pointerEvents = 'auto'; |
| } |
| if (appContainer) appContainer.style.display = 'none'; |
| if (authOverlay) authOverlay.style.display = 'none'; |
| if (pricingPage) pricingPage.style.display = 'none'; |
| } |
| |
| function renderChat() { |
| const landing = document.getElementById('landingPage'); |
| const appContainer = document.querySelector('.app-container'); |
| const authOverlay = document.getElementById('authOverlay'); |
| const pricingPage = document.getElementById('pricingPage'); |
| |
| if (landing) landing.style.display = 'none'; |
| if (appContainer) appContainer.style.display = 'flex'; |
| if (authOverlay) authOverlay.style.display = 'none'; |
| if (pricingPage) pricingPage.style.display = 'none'; |
| |
| |
| if (chatHistory && chatHistory.children.length === 0) { |
| startNewChat(); |
| } |
| } |
| |
| function renderLogin() { |
| renderAuth('login'); |
| } |
| |
| function renderSignup() { |
| renderAuth('signup'); |
| } |
| |
| function renderAuth(mode) { |
| const landing = document.getElementById('landingPage'); |
| const appContainer = document.querySelector('.app-container'); |
| const authOverlay = document.getElementById('authOverlay'); |
| const pricingPage = document.getElementById('pricingPage'); |
| |
| if (landing) landing.style.display = 'none'; |
| if (appContainer) appContainer.style.display = 'none'; |
| if (authOverlay) { |
| authOverlay.style.display = 'flex'; |
| authOverlay.style.zIndex = '9999'; |
| } |
| if (pricingPage) pricingPage.style.display = 'none'; |
| |
| const title = document.getElementById('authTitle'); |
| const submitBtn = document.getElementById('authSubmitBtn'); |
| const switchText = document.getElementById('authSwitchText'); |
| |
| if (mode === 'signup') { |
| if (title) title.textContent = 'Create your account'; |
| if (submitBtn) submitBtn.textContent = 'Sign up'; |
| if (switchText) switchText.innerHTML = 'Already have an account? <a href="/login" style="color:var(--accent-blue);text-decoration:none;font-weight:600;">Log in</a>'; |
| } else { |
| if (title) title.textContent = 'Welcome back'; |
| if (submitBtn) submitBtn.textContent = 'Log in'; |
| if (switchText) switchText.innerHTML = 'Don\'t have an account? <a href="/signup" style="color:var(--accent-blue);text-decoration:none;font-weight:600;">Sign up</a>'; |
| } |
| } |
| |
| function renderPricing() { |
| const landing = document.getElementById('landingPage'); |
| const appContainer = document.querySelector('.app-container'); |
| const authOverlay = document.getElementById('authOverlay'); |
| |
| if (landing) { |
| landing.style.display = 'block'; |
| landing.classList.remove('hidden'); |
| landing.style.pointerEvents = 'auto'; |
| } |
| if (appContainer) appContainer.style.display = 'none'; |
| if (authOverlay) authOverlay.style.display = 'none'; |
| |
| setTimeout(() => { |
| const pricingSection = document.getElementById('pricingSection'); |
| if (pricingSection) { |
| pricingSection.scrollIntoView({ behavior: 'smooth' }); |
| } |
| }, 100); |
| } |
| |
| function renderProfile() { |
| const landing = document.getElementById('landingPage'); |
| const appContainer = document.querySelector('.app-container'); |
| const authOverlay = document.getElementById('authOverlay'); |
| |
| if (landing) landing.style.display = 'none'; |
| if (appContainer) appContainer.style.display = 'none'; |
| if (authOverlay) authOverlay.style.display = 'none'; |
| |
| let profileEl = document.getElementById('profilePage'); |
| if (!profileEl) { |
| profileEl = document.createElement('div'); |
| profileEl.id = 'profilePage'; |
| profileEl.className = 'profile-container'; |
| profileEl.innerHTML = ` |
| <div class="profile-card"> |
| <h2>User Profile</h2> |
| <div class="profile-avatar-large">${getUserInitial()}</div> |
| <div class="profile-info"> |
| <div class="info-item"> |
| <label>Username</label> |
| <span>${currentUser?.username || 'Guest'}</span> |
| </div> |
| <div class="info-item"> |
| <label>Email</label> |
| <span>${currentUser?.email || 'N/A'}</span> |
| </div> |
| </div> |
| <button class="btn-secondary" onclick="window.history.pushState({}, '', '/chat'); handleRouting();">Back to Chat</button> |
| </div> |
| `; |
| document.body.appendChild(profileEl); |
| } |
| profileEl.style.display = 'flex'; |
| } |
| |
| function renderConversations() { |
| if (!conversationList) return; |
| conversationList.innerHTML = ''; |
| |
| if (!conversations || conversations.length === 0) return; |
| |
| |
| const sorted = conversations; |
| |
| const now = new Date(); |
| const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); |
| const yesterday = today - 86400000; |
| const lastWeek = today - (86400000 * 7); |
| |
| let lastGroup = ""; |
| |
| sorted.forEach(conv => { |
| const convDate = new Date(conv.updated_at || conv.created_at || Date.now()); |
| const convDay = new Date(convDate.getFullYear(), convDate.getMonth(), convDate.getDate()).getTime(); |
| |
| let group = "Older"; |
| if (convDay === today) group = "Today"; |
| else if (convDay === yesterday) group = "Yesterday"; |
| else if (convDay > lastWeek) group = "Previous 7 Days"; |
| |
| if (group !== lastGroup) { |
| const groupLabel = document.createElement('div'); |
| groupLabel.className = 'history-date-group'; |
| groupLabel.textContent = group; |
| conversationList.appendChild(groupLabel); |
| lastGroup = group; |
| } |
| |
| const item = document.createElement('div'); |
| item.className = `conversation-item ${conv.id === currentConversationId ? 'active' : ''}`; |
| item.setAttribute('data-id', conv.id); |
| if (conv.pinned) item.classList.add('pinned'); |
| |
| const icon = document.createElement('i'); |
| icon.setAttribute('data-lucide', conv.pinned ? 'pin' : 'message-square'); |
| icon.style.width = "14px"; |
| icon.style.flexShrink = "0"; |
| |
| const titleSpan = document.createElement('span'); |
| titleSpan.textContent = conv.title; |
| titleSpan.style.flex = "1"; |
| titleSpan.style.overflow = "hidden"; |
| titleSpan.style.textOverflow = "ellipsis"; |
| titleSpan.style.whiteSpace = "nowrap"; |
| |
| const actionsDiv = document.createElement('div'); |
| actionsDiv.className = 'item-actions'; |
| actionsDiv.style.display = 'flex'; |
| actionsDiv.style.gap = '4px'; |
| |
| const pinBtn = document.createElement('button'); |
| pinBtn.className = 'action-btn'; |
| pinBtn.innerHTML = `<i data-lucide="${conv.pinned ? 'pin-off' : 'pin'}" style="width:12px;"></i>`; |
| pinBtn.onclick = async (e) => { |
| e.stopPropagation(); |
| try { |
| const resp = await fetch(`/api/conversations/${conv.id}`, { |
| method: 'PUT', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ pinned: conv.pinned ? 0 : 1 }) |
| }); |
| if (resp.ok) loadConversationsFromStorage(); |
| } catch (e) {} |
| }; |
| |
| const deleteBtn = document.createElement('button'); |
| deleteBtn.className = 'delete-btn'; |
| deleteBtn.innerHTML = `<i data-lucide="trash-2" style="width:14px;"></i>`; |
| deleteBtn.onclick = (e) => { |
| e.stopPropagation(); |
| deleteConversation(e, conv.id); |
| }; |
| |
| item.appendChild(icon); |
| item.appendChild(titleSpan); |
| actionsDiv.appendChild(pinBtn); |
| actionsDiv.appendChild(deleteBtn); |
| item.appendChild(actionsDiv); |
| |
| item.onclick = () => { |
| closeSidebarOnMobile(); |
| loadConversation(conv.id); |
| }; |
| conversationList.appendChild(item); |
| }); |
| |
| if (window.lucide) lucide.createIcons(); |
| } |
| |
| |
| function toggleTheme() { |
| isLightMode = !isLightMode; |
| document.body.className = isLightMode ? 'light-mode' : ''; |
| localStorage.setItem('chatgpt_theme', isLightMode ? 'light' : 'dark'); |
| } |
| |
| |
| let isDropdownOpen = false; |
| function toggleAccountDropdown() { |
| if (accountDropdown) { |
| isDropdownOpen = !isDropdownOpen; |
| accountDropdown.classList.toggle('show', isDropdownOpen); |
| } |
| } |
| |
| |
| const fileUploadInput = document.createElement('input'); |
| fileUploadInput.type = 'file'; |
| fileUploadInput.style.display = 'none'; |
| document.body.appendChild(fileUploadInput); |
| |
| fileUploadInput.addEventListener('change', (e) => { |
| if (e.target.files && e.target.files.length > 0) { |
| const file = e.target.files[0]; |
| const fileName = file.name; |
| const fileExt = fileName.split('.').pop().toUpperCase(); |
| |
| let bgColors = '#e53e3e'; |
| if (['JPG','JPEG','PNG','GIF','WEBP'].includes(fileExt)) bgColors = '#3182ce'; |
| if (['DOC','DOCX','TXT','RTF'].includes(fileExt)) bgColors = '#38a169'; |
| |
| const attachContainer = document.getElementById('attachmentsContainer'); |
| attachContainer.style.display = 'flex'; |
| |
| const card = document.createElement('div'); |
| card.className = 'file-attachment-card'; |
| card.setAttribute('data-filename', fileName); |
| card.innerHTML = ` |
| <div class="file-attachment-icon" style="background: ${bgColors}"> |
| <i data-lucide="file-text" style="width: 18px; height: 18px;"></i> |
| </div> |
| <div class="file-attachment-info"> |
| <span class="file-attachment-name">${fileName}</span> |
| <span class="file-attachment-type">${fileExt}</span> |
| </div> |
| <div class="file-attachment-remove" onclick="this.parentElement.remove(); if(document.getElementById('attachmentsContainer').children.length === 0) document.getElementById('attachmentsContainer').style.display='none';"> |
| <i data-lucide="x"></i> |
| </div> |
| `; |
| if (window.lucide) lucide.createIcons(); |
| |
| if (['TXT', 'CSV', 'JSON', 'MD', 'PY', 'JS', 'HTML', 'CSS'].includes(fileExt)) { |
| const reader = new FileReader(); |
| reader.onload = () => { |
| card.setAttribute('data-preview', String(reader.result || '').substring(0, 800)); |
| attachContainer.appendChild(card); |
| }; |
| reader.readAsText(file); |
| } else if (['PDF', 'DOCX', 'PNG', 'JPG', 'JPEG', 'WEBP'].includes(fileExt)) { |
| |
| handleFileAnalysis(file, card); |
| attachContainer.appendChild(card); |
| } else { |
| attachContainer.appendChild(card); |
| } |
| |
| messageInput.focus(); |
| fileUploadInput.value = ''; |
| } |
| }); |
| |
| function triggerUpload(type) { |
| document.getElementById('uploadMenu').classList.remove('show'); |
| if (type === 'PDF') fileUploadInput.accept = '.pdf'; |
| else if (type === 'Image') fileUploadInput.accept = 'image/*'; |
| else if (type === 'Document') fileUploadInput.accept = '.doc,.docx,.txt,.rtf'; |
| else fileUploadInput.accept = '*/*'; |
| fileUploadInput.click(); |
| } |
| |
| |
| let recognition = null; |
| let isRecording = false; |
| |
| function toggleSpeechRecognition() { |
| if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) { |
| alert("Speech recognition is not supported in this browser."); |
| return; |
| } |
| |
| const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; |
| |
| if (!recognition) { |
| recognition = new SpeechRecognition(); |
| recognition.continuous = false; |
| recognition.interimResults = true; |
| recognition.lang = 'en-US'; |
| |
| recognition.onstart = () => { |
| isRecording = true; |
| document.getElementById('micBtn').classList.add('recording'); |
| }; |
| |
| recognition.onresult = (event) => { |
| const transcript = Array.from(event.results) |
| .map(result => result[0]) |
| .map(result => result.transcript) |
| .join(''); |
| |
| document.getElementById('messageInput').value = transcript; |
| }; |
| |
| recognition.onend = () => { |
| isRecording = false; |
| document.getElementById('micBtn').classList.remove('recording'); |
| recognition = null; |
| }; |
| |
| recognition.onerror = (event) => { |
| console.error("Speech recognition error:", event.error); |
| isRecording = false; |
| document.getElementById('micBtn').classList.remove('recording'); |
| }; |
| } |
| |
| if (isRecording) { |
| recognition.stop(); |
| } else { |
| recognition.start(); |
| } |
| } |
| |
| let currentUtterance = null; |
| |
| function speakText(text, callbacks = {}) { |
| if (!('speechSynthesis' in window)) return; |
| |
| |
| stopSpeech(); |
| |
| const cleanText = text.replace(/[*_#]/g, '').replace(/\[.*?\]/g, '').replace(/```[\s\S]*?```/g, 'Code block.'); |
| const utterance = new SpeechSynthesisUtterance(cleanText); |
| currentUtterance = utterance; |
| |
| |
| const voices = window.speechSynthesis.getVoices(); |
| |
| |
| let femaleVoice = voices.find(v => v.name.includes('Google US English') && v.name.includes('Female')) || |
| voices.find(v => v.name.includes('Microsoft Zira')) || |
| voices.find(v => v.name.toLowerCase().includes('female')) || |
| voices.find(v => v.name.toLowerCase().includes('woman')) || |
| voices.find(v => v.lang.startsWith('en') && (v.name.includes('Samantha') || v.name.includes('Victoria') || v.name.includes('Google'))); |
| |
| if (femaleVoice) { |
| utterance.voice = femaleVoice; |
| } |
| |
| utterance.pitch = 1.1; |
| utterance.rate = 1.0; |
| utterance.volume = 1.0; |
| |
| if (callbacks.onStart) utterance.onstart = callbacks.onStart; |
| if (callbacks.onEnd) { |
| utterance.onend = callbacks.onEnd; |
| utterance.onerror = callbacks.onEnd; |
| } |
| |
| window.speechSynthesis.speak(utterance); |
| } |
| |
| function stopSpeech() { |
| if ('speechSynthesis' in window) { |
| window.speechSynthesis.cancel(); |
| currentUtterance = null; |
| } |
| } |
| |
| |
| if ('speechSynthesis' in window) { |
| window.speechSynthesis.onvoiceschanged = () => { |
| window.speechSynthesis.getVoices(); |
| }; |
| } |
| |
| function addMessageActions(container, text) { |
| const actionsDiv = document.createElement('div'); |
| actionsDiv.className = 'message-actions'; |
| |
| |
| const copyBtn = document.createElement('button'); |
| copyBtn.className = 'action-icon-btn'; |
| copyBtn.innerHTML = '<i data-lucide="copy"></i>'; |
| copyBtn.title = 'Copy response'; |
| copyBtn.onclick = () => { |
| navigator.clipboard.writeText(text); |
| const originalContent = copyBtn.innerHTML; |
| copyBtn.innerHTML = '<i data-lucide="check"></i>'; |
| setTimeout(() => { |
| copyBtn.innerHTML = originalContent; |
| refreshIcons(); |
| }, 2000); |
| refreshIcons(); |
| }; |
| |
| |
| const likeBtn = document.createElement('button'); |
| likeBtn.className = 'action-icon-btn'; |
| likeBtn.innerHTML = '<i data-lucide="thumbs-up"></i>'; |
| likeBtn.title = 'Like'; |
| likeBtn.onclick = () => { |
| likeBtn.classList.toggle('active'); |
| dislikeBtn.classList.remove('active'); |
| }; |
| |
| |
| const dislikeBtn = document.createElement('button'); |
| dislikeBtn.className = 'action-icon-btn'; |
| dislikeBtn.innerHTML = '<i data-lucide="thumbs-down"></i>'; |
| dislikeBtn.title = 'Bad'; |
| dislikeBtn.onclick = () => { |
| dislikeBtn.classList.toggle('active'); |
| likeBtn.classList.remove('active'); |
| }; |
| |
| |
| const speakBtn = document.createElement('button'); |
| speakBtn.className = 'action-icon-btn'; |
| speakBtn.innerHTML = '<i data-lucide="play"></i>'; |
| speakBtn.title = 'Start reading'; |
| |
| |
| const stopBtn = document.createElement('button'); |
| stopBtn.className = 'action-icon-btn'; |
| stopBtn.style.display = 'none'; |
| stopBtn.innerHTML = '<i data-lucide="square"></i>'; |
| stopBtn.title = 'Stop reading'; |
| |
| speakBtn.onclick = () => { |
| |
| document.querySelectorAll('.action-icon-btn.speaking-stop').forEach(btn => { |
| btn.click(); |
| }); |
| |
| speakText(text, { |
| onStart: () => { |
| speakBtn.style.display = 'none'; |
| stopBtn.style.display = 'flex'; |
| stopBtn.classList.add('speaking-stop'); |
| }, |
| onEnd: () => { |
| speakBtn.style.display = 'flex'; |
| stopBtn.style.display = 'none'; |
| stopBtn.classList.remove('speaking-stop'); |
| refreshIcons(); |
| } |
| }); |
| }; |
| |
| stopBtn.onclick = () => { |
| stopSpeech(); |
| speakBtn.style.display = 'flex'; |
| stopBtn.style.display = 'none'; |
| stopBtn.classList.remove('speaking-stop'); |
| refreshIcons(); |
| }; |
| |
| |
| const exportBtn = document.createElement('button'); |
| exportBtn.className = 'action-icon-btn'; |
| exportBtn.innerHTML = '<i data-lucide="download"></i>'; |
| exportBtn.title = 'Export as Markdown'; |
| exportBtn.onclick = () => { |
| const blob = new Blob([text], { type: 'text/markdown' }); |
| const url = URL.createObjectURL(blob); |
| const a = document.createElement('a'); |
| a.href = url; |
| a.download = `nexa-response-${Date.now()}.md`; |
| a.click(); |
| URL.revokeObjectURL(url); |
| }; |
| |
| actionsDiv.appendChild(copyBtn); |
| actionsDiv.appendChild(likeBtn); |
| actionsDiv.appendChild(dislikeBtn); |
| actionsDiv.appendChild(speakBtn); |
| actionsDiv.appendChild(stopBtn); |
| actionsDiv.appendChild(exportBtn); |
| |
| container.appendChild(actionsDiv); |
| if (window.lucide) lucide.createIcons(); |
| } |
| |
| |
| function openImageGenModal() { |
| document.getElementById('imgModalOverlay').classList.add('open'); |
| setTimeout(() => document.getElementById('imgPromptInput').focus(), 350); |
| } |
| |
| function closeImageGenModal() { |
| document.getElementById('imgModalOverlay').classList.remove('open'); |
| } |
| |
| |
| let currentOverlayVideoUrl = ""; |
| |
| function openVideoOverlayModal(videoUrl) { |
| currentOverlayVideoUrl = videoUrl; |
| const modal = document.getElementById('videoOverlayModal'); |
| modal.style.display = 'flex'; |
| modal.setAttribute('data-video-url', videoUrl); |
| document.getElementById('overlayModalStatus').textContent = ""; |
| if (window.lucide) lucide.createIcons(); |
| } |
| |
| async function handleImgOverlayClick(e) { |
| if (e.target === document.getElementById('imgModalOverlay')) closeImageGenModal(); |
| } |
| |
| let isGeneratingImageModal = false; |
| async function generateImage() { |
| if (isGeneratingImageModal) return; |
| const promptInput = document.getElementById('imgPromptInput'); |
| const prompt = promptInput ? promptInput.value.trim() : ''; |
| if (!prompt) return; |
| |
| isGeneratingImageModal = true; |
| const genBtn = document.getElementById('imgGenerateBtn'); |
| const skeleton = document.getElementById('imgSkeleton'); |
| const resultArea = document.getElementById('imgResultArea'); |
| const statusEl = document.getElementById('imgModalStatus'); |
| const resultImg = document.getElementById('imgResultImg'); |
| |
| if (genBtn) genBtn.disabled = true; |
| if (resultArea) resultArea.classList.remove('visible'); |
| if (skeleton) skeleton.classList.add('visible'); |
| if (statusEl) statusEl.textContent = '✦ Generating Masterpiece...'; |
| |
| try { |
| const seed = Math.floor(Math.random() * 1000000); |
| const encodedPrompt = encodeURIComponent(prompt); |
| const imageUrl = `https://image.pollinations.ai/prompt/${encodedPrompt}?width=1024&height=1024&seed=${seed}&nologo=true`; |
| |
| const img = new Image(); |
| img.onload = () => { |
| if (resultImg) resultImg.src = imageUrl; |
| const downloadBtn = document.getElementById('imgDownloadBtn'); |
| if (downloadBtn) { |
| downloadBtn.href = imageUrl; |
| downloadBtn.download = `nexa-${Date.now()}.png`; |
| } |
| |
| if (skeleton) skeleton.classList.remove('visible'); |
| if (resultArea) resultArea.classList.add('visible'); |
| if (statusEl) statusEl.textContent = '✓ Done!'; |
| if (genBtn) genBtn.disabled = false; |
| isGeneratingImageModal = false; |
| }; |
| img.onerror = () => { |
| throw new Error("Image provider error"); |
| }; |
| img.src = imageUrl; |
| |
| } catch (err) { |
| if (skeleton) skeleton.classList.remove('visible'); |
| if (statusEl) statusEl.textContent = '✗ Error: ' + err.message; |
| if (genBtn) genBtn.disabled = false; |
| isGeneratingImageModal = false; |
| } |
| } |
| |
| |
| let currentIDEPlan = []; |
| let currentIDEMode = 'plan'; |
| |
| function switchIDEMode(mode) { |
| currentIDEMode = mode; |
| const planBtn = document.getElementById('ideModePlan'); |
| const debugBtn = document.getElementById('ideModeDebug'); |
| const planArea = document.getElementById('idePlanInputArea'); |
| const debugArea = document.getElementById('ideDebugInputArea'); |
| const planContainer = document.getElementById('idePlanContainer'); |
| |
| planContainer.innerHTML = ''; |
| if (mode === 'plan') { |
| planBtn.style.background = 'var(--accent-blue)'; |
| debugBtn.style.background = 'rgba(255,255,255,0.05)'; |
| planArea.style.display = 'block'; |
| debugArea.style.display = 'none'; |
| } else { |
| debugBtn.style.background = 'var(--accent-blue)'; |
| planBtn.style.background = 'rgba(255,255,255,0.05)'; |
| planArea.style.display = 'none'; |
| debugArea.style.display = 'block'; |
| } |
| } |
| |
| async function analyzeIDEError() { |
| const errorText = document.getElementById('ideErrorInput').value.trim(); |
| if (!errorText) return; |
| |
| const debugBtn = document.getElementById('ideDebugBtn'); |
| const statusArea = document.getElementById('ideStatusArea'); |
| const planContainer = document.getElementById('idePlanContainer'); |
| |
| debugBtn.disabled = true; |
| statusArea.innerHTML = '<i data-lucide="loader-2" class="animate-spin"></i> Analyzing root cause and finding fixes...'; |
| planContainer.innerHTML = ''; |
| if (window.lucide) lucide.createIcons(); |
| |
| try { |
| const resp = await fetch('/api/ide/debug', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| error: errorText, |
| stack: errorText, |
| file: 'app.py', |
| line: 10 |
| }) |
| }); |
| const data = await resp.json(); |
| if (data.error) throw new Error(data.error); |
| |
| statusArea.innerHTML = `<i data-lucide="check-circle" class="text-green-500"></i> Found ${data.suggestions.length} possible fixes. Seen this ${data.frequency} times.`; |
| |
| planContainer.innerHTML = ` |
| <div style="background: rgba(239, 68, 68, 0.1); border: 1px solid rgba(239, 68, 68, 0.2); padding: 16px; border-radius: 12px; margin-bottom: 20px;"> |
| <h4 style="font-size: 12px; color: #f87171; font-weight: 700; text-transform: uppercase; margin-bottom: 8px;">Root Cause Analysis</h4> |
| <div style="font-size: 14px; color: var(--text-primary); line-height: 1.5;">${data.root_cause}</div> |
| </div> |
| <div style="display: flex; flex-direction: column; gap: 12px;"> |
| ${data.suggestions.map((s, idx) => ` |
| <div class="feature-card" style="padding: 16px; border-radius: 12px; flex-direction: column; align-items: stretch;"> |
| <div style="display: flex; justify-content: space-between; margin-bottom: 12px;"> |
| <div style="font-size: 11px; font-weight: 700; color: #10a37f;">FIX OPTION #${idx + 1} (${Math.round(s.confidence * 100)}% Confidence)</div> |
| <div style="font-size: 11px; color: var(--text-secondary);"><i data-lucide="clock" style="width:10px; display:inline;"></i> Saved ~${s.time_saved}</div> |
| </div> |
| <p style="font-size: 14px; margin-bottom: 12px; color: var(--text-secondary);">${s.explanation}</p> |
| <pre style="background: rgba(0,0,0,0.4); padding: 12px; border-radius: 8px; font-size: 12px; color: #60a5fa; overflow-x: auto; margin-bottom: 12px;"><code>${s.fix}</code></pre> |
| <button class="btn-primary" style="width: 100%; padding: 8px; font-size: 13px;" onclick="applyIDEFix('${btoa(s.fix)}')">Apply Fix</button> |
| </div> |
| `).join('')} |
| </div> |
| `; |
| } catch (err) { |
| statusArea.innerHTML = `<i data-lucide="alert-circle" class="text-red-500"></i> Debugging failed: ${err.message}`; |
| } finally { |
| debugBtn.disabled = false; |
| if (window.lucide) lucide.createIcons(); |
| } |
| } |
| |
| async function applyIDEFix(encodedFix) { |
| const fix = atob(encodedFix); |
| try { |
| |
| await fetch('/api/ide/learn', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ fix }) |
| }); |
| alert('Fix applied successfully! The AI has learned from this resolution.'); |
| } catch (e) { |
| alert('Failed to apply fix.'); |
| } |
| } |
| |
| function openIDEAssistant() { |
| document.getElementById('ideModalOverlay').classList.add('open'); |
| fetchFileTree(); |
| setTimeout(() => document.getElementById('ideCommandInput').focus(), 350); |
| } |
| |
| function closeIDEAssistant() { |
| document.getElementById('ideModalOverlay').classList.remove('open'); |
| } |
| |
| function openFeaturesModal() { |
| document.getElementById('featuresModalOverlay').classList.add('open'); |
| } |
| |
| function closeFeaturesModal() { |
| document.getElementById('featuresModalOverlay').classList.remove('open'); |
| } |
| |
| function openSettings() { |
| document.getElementById('settingsModalOverlay').classList.add('open'); |
| updateSettingsUI(); |
| if (window.lucide) lucide.createIcons(); |
| } |
| |
| function closeSettings() { |
| document.getElementById('settingsModalOverlay').classList.remove('open'); |
| } |
| |
| function handleSettingsOverlayClick(e) { |
| |
| const modal = e.target.closest('.settings-modal'); |
| if (!modal && e.target.classList.contains('settings-modal-overlay')) { |
| closeSettings(); |
| } |
| } |
| |
| let isAutoVoiceEnabled = localStorage.getItem('nexa_auto_voice') !== 'false'; |
| |
| function toggleAutoVoice() { |
| isAutoVoiceEnabled = !isAutoVoiceEnabled; |
| localStorage.setItem('nexa_auto_voice', isAutoVoiceEnabled); |
| updateSettingsUI(); |
| } |
| |
| function setFontSize(size) { |
| const root = document.documentElement; |
| if (size === 'small') root.style.setProperty('--chat-font-size', '13px'); |
| else if (size === 'medium') root.style.setProperty('--chat-font-size', '15px'); |
| else if (size === 'large') root.style.setProperty('--chat-font-size', '18px'); |
| localStorage.setItem('nexa_font_size', size); |
| } |
| |
| |
| const savedFontSize = localStorage.getItem('nexa_font_size') || 'medium'; |
| setFontSize(savedFontSize); |
| |
| function updateSettingsUI() { |
| const voiceBtn = document.getElementById('voiceToggleBtn'); |
| if (voiceBtn) { |
| voiceBtn.textContent = isAutoVoiceEnabled ? 'Enabled' : 'Disabled'; |
| voiceBtn.classList.toggle('danger', !isAutoVoiceEnabled); |
| } |
| } |
| |
| function confirmClearMemory() { |
| openConfirm("Clear Nexa's Memory?", "This will forget everything Nexa has learned about your preferences, name, and style.", () => { |
| userMemory = {}; |
| localStorage.removeItem('nexa_ai_memory'); |
| addMessage("I've cleared my memory. We're starting fresh! 🧠✨", 'ai'); |
| closeConfirm(); |
| closeSettings(); |
| }); |
| } |
| |
| function confirmDeleteAllChats() { |
| openConfirm("Delete All Conversations?", "This will permanently delete your entire chat history. This action cannot be undone.", () => { |
| conversations = []; |
| localStorage.setItem(getConversationStorageKey(), JSON.stringify([])); |
| currentConversationId = null; |
| renderConversations(); |
| startNewChat(); |
| addMessage("All conversations have been deleted permanently.", 'ai'); |
| closeConfirm(); |
| closeSettings(); |
| }); |
| } |
| |
| function openConfirm(title, text, onConfirm) { |
| document.getElementById('confirmTitle').textContent = title; |
| document.getElementById('confirmText').textContent = text; |
| const deleteBtn = document.getElementById('confirmDeleteBtn'); |
| deleteBtn.onclick = onConfirm; |
| document.getElementById('confirmOverlay').classList.add('open'); |
| } |
| |
| function closeConfirm() { |
| document.getElementById('confirmOverlay').classList.remove('open'); |
| } |
| |
| function handleConfirmOverlayClick(e) { |
| if (e.target === document.getElementById('confirmOverlay')) closeConfirm(); |
| } |
| |
| function handleFeaturesOverlayClick(e) { |
| if (e.target === document.getElementById('featuresModalOverlay')) closeFeaturesModal(); |
| } |
| |
| function exportFullChat() { |
| if (!currentConversationId) { |
| alert("No active conversation to export."); |
| return; |
| } |
| const conv = conversations.find(c => c.id === currentConversationId); |
| if (!conv || !conv.messages || conv.messages.length === 0) { |
| alert("Conversation is empty."); |
| return; |
| } |
| |
| |
| const stripHtml = (html) => { |
| const tmp = document.createElement("DIV"); |
| tmp.innerHTML = html; |
| return tmp.textContent || tmp.innerText || ""; |
| }; |
| |
| let markdown = `# Nexa AI Conversation: ${conv.title}\n\n`; |
| conv.messages.forEach(msg => { |
| const role = msg.type === 'ai' ? 'AI' : 'User'; |
| const cleanContent = stripHtml(msg.content); |
| markdown += `### ${role}\n${cleanContent}\n\n---\n\n`; |
| }); |
| |
| const blob = new Blob([markdown], { type: 'text/markdown' }); |
| const url = URL.createObjectURL(blob); |
| const a = document.createElement('a'); |
| a.href = url; |
| a.download = `nexa-chat-${conv.id}.md`; |
| a.click(); |
| URL.revokeObjectURL(url); |
| } |
| |
| function handleIDEModalClick(e) { |
| if (e.target === document.getElementById('ideModalOverlay')) closeIDEAssistant(); |
| } |
| |
| async function fetchFileTree() { |
| try { |
| const resp = await fetch('/api/ide/execute', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ action: 'list_files' }) |
| }); |
| const data = await resp.json(); |
| if (data.files) { |
| const tree = document.getElementById('ideFileTree'); |
| tree.innerHTML = data.files.slice(0, 15).map(f => `<div><i data-lucide="file-code" style="width:12px;display:inline;margin-right:4px;"></i>${f}</div>`).join(''); |
| if (window.lucide) lucide.createIcons(); |
| } |
| } catch (e) {} |
| } |
| |
| async function generateIDEPlan() { |
| const command = document.getElementById('ideCommandInput').value.trim(); |
| if (!command) return; |
| |
| const runBtn = document.getElementById('ideRunBtn'); |
| const statusArea = document.getElementById('ideStatusArea'); |
| const planContainer = document.getElementById('idePlanContainer'); |
| |
| runBtn.disabled = true; |
| statusArea.innerHTML = '<i data-lucide="loader-2" class="animate-spin"></i> Analyzing project and planning steps...'; |
| planContainer.innerHTML = ''; |
| if (window.lucide) lucide.createIcons(); |
| |
| try { |
| const resp = await fetch('/api/ide/plan', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ command }) |
| }); |
| const data = await resp.json(); |
| |
| if (data.error) throw new Error(data.error); |
| |
| currentIDEPlan = data.plan; |
| document.getElementById('ideCostStats').textContent = `${data.stats.costSavings} Saved`; |
| statusArea.innerHTML = '<i data-lucide="check-circle" class="text-green-500"></i> Plan generated. Awaiting approval.'; |
| |
| renderIDEPlan(); |
| } catch (err) { |
| statusArea.innerHTML = `<i data-lucide="alert-circle" class="text-red-500"></i> Error: ${err.message}`; |
| } finally { |
| runBtn.disabled = false; |
| if (window.lucide) lucide.createIcons(); |
| } |
| } |
| |
| function renderIDEPlan() { |
| const container = document.getElementById('idePlanContainer'); |
| container.innerHTML = currentIDEPlan.map(step => ` |
| <div class="feature-card" style="padding: 16px; border-radius: 12px; display: flex; justify-content: space-between; align-items: center;" id="step-${step.id}"> |
| <div style="display: flex; gap: 12px;"> |
| <div style="font-weight: 700; color: var(--text-secondary);">${step.id}</div> |
| <div> |
| <div style="font-size: 11px; text-transform: uppercase; font-weight: 700; color: var(--accent-blue);">${step.action.replace('_', ' ')}</div> |
| <div style="font-size: 14px;">${step.description}</div> |
| ${step.path ? `<code style="font-size: 11px; background: rgba(0,0,0,0.3); padding: 2px 6px; border-radius: 4px; color: #60a5fa; margin-top: 4px; display: inline-block;">${step.path}</code>` : ''} |
| </div> |
| </div> |
| <div id="status-container-${step.id}"> |
| <button class="btn-primary" style="padding: 6px 12px; font-size: 12px; border-radius: 8px;" onclick="executeIDEStep(${step.id})">Approve</button> |
| </div> |
| </div> |
| `).join(''); |
| if (window.lucide) lucide.createIcons(); |
| } |
| |
| async function executeIDEStep(stepId) { |
| const step = currentIDEPlan.find(s => s.id === stepId); |
| const statusContainer = document.getElementById(`status-container-${stepId}`); |
| |
| statusContainer.innerHTML = '<i data-lucide="loader-2" class="animate-spin" style="width: 18px;"></i>'; |
| if (window.lucide) lucide.createIcons(); |
| |
| try { |
| const resp = await fetch('/api/ide/execute', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| action: step.action, |
| payload: step.action === 'create_file' ? { path: step.path, content: step.content } : { command: step.command } |
| }) |
| }); |
| const data = await resp.json(); |
| |
| if (data.status === 'success') { |
| statusContainer.innerHTML = '<i data-lucide="check-circle" class="text-green-500"></i>'; |
| } else { |
| throw new Error(data.stderr || 'Execution failed'); |
| } |
| } catch (err) { |
| statusContainer.innerHTML = '<i data-lucide="x-circle" class="text-red-500"></i>'; |
| alert(`Step failed: ${err.message}`); |
| } finally { |
| if (window.lucide) lucide.createIcons(); |
| } |
| } |
| |
| |
| const words = ["Intelligent", "Smart", "Powerful", "Creative"]; |
| let wordIndex = 0; |
| let charIndex = 0; |
| let isDeleting = false; |
| let typingDelay = 150; |
| let erasingDelay = 100; |
| let newWordDelay = 2500; |
| |
| function typeEffect() { |
| const typingText = document.getElementById("typing-text"); |
| if (!typingText) return; |
| |
| const currentWord = words[wordIndex]; |
| |
| if (isDeleting) { |
| if (charIndex > 0) { |
| typingText.textContent = currentWord.substring(0, charIndex - 1); |
| charIndex--; |
| } |
| } else { |
| typingText.textContent = currentWord.substring(0, charIndex + 1); |
| charIndex++; |
| } |
| |
| if (!isDeleting && charIndex === currentWord.length) { |
| isDeleting = true; |
| setTimeout(typeEffect, newWordDelay); |
| } else if (isDeleting && charIndex === 0) { |
| isDeleting = false; |
| wordIndex = (wordIndex + 1) % words.length; |
| setTimeout(typeEffect, 500); |
| } else { |
| setTimeout(typeEffect, isDeleting ? erasingDelay : typingDelay); |
| } |
| } |
| |
| function triggerLightning() { |
| const lightningOverlay = document.getElementById("lightningOverlay"); |
| if (!lightningOverlay) return; |
| lightningOverlay.classList.remove("lightning-flash"); |
| void lightningOverlay.offsetWidth; |
| lightningOverlay.classList.add("lightning-flash"); |
| setTimeout(triggerLightning, Math.random() * 8000 + 4000); |
| } |
| |
| |
| function init3DBackground() { |
| const canvas = document.getElementById('canvas3d'); |
| if (!canvas) return; |
| const ctx = canvas.getContext('2d'); |
| let w, h; |
| const particles = []; |
| const particleCount = 60; |
| |
| function resize() { |
| w = canvas.width = window.innerWidth; |
| h = canvas.height = window.innerHeight; |
| } |
| |
| class Particle { |
| constructor() { |
| this.reset(); |
| } |
| reset() { |
| this.x = Math.random() * w; |
| this.y = Math.random() * h; |
| this.z = Math.random() * 1000; |
| this.size = 1 + Math.random() * 2; |
| this.speed = 0.5 + Math.random() * 1.5; |
| this.vx = (Math.random() - 0.5) * 0.5; |
| this.vy = (Math.random() - 0.5) * 0.5; |
| } |
| update() { |
| this.z -= this.speed; |
| this.x += this.vx; |
| this.y += this.vy; |
| if (this.z <= 0) this.reset(); |
| } |
| draw() { |
| const scale = 500 / (500 + this.z); |
| const x2d = (this.x - w / 2) * scale + w / 2; |
| const y2d = (this.y - h / 2) * scale + h / 2; |
| const opacity = Math.min(1, (1000 - this.z) / 800) * 0.3; |
| |
| ctx.fillStyle = `rgba(16, 163, 127, ${opacity})`; |
| ctx.beginPath(); |
| ctx.arc(x2d, y2d, this.size * scale, 0, Math.PI * 2); |
| ctx.fill(); |
| } |
| } |
| |
| for (let i = 0; i < particleCount; i++) particles.push(new Particle()); |
| |
| function animate() { |
| ctx.clearRect(0, 0, w, h); |
| particles.forEach(p => { |
| p.update(); |
| p.draw(); |
| }); |
| requestAnimationFrame(animate); |
| } |
| |
| window.addEventListener('resize', resize); |
| resize(); |
| animate(); |
| } |
| |
| |
| function init3DTilt() { |
| document.addEventListener('mousemove', e => { |
| const cards = document.querySelectorAll('.suggestion-card, .feature-item'); |
| cards.forEach(card => { |
| const rect = card.getBoundingClientRect(); |
| const x = e.clientX - rect.left; |
| const y = e.clientY - rect.top; |
| |
| if (x > 0 && x < rect.width && y > 0 && y < rect.height) { |
| const centerX = rect.width / 2; |
| const centerY = rect.height / 2; |
| const rotateX = (y - centerY) / 10; |
| const rotateY = (centerX - x) / 10; |
| |
| card.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) translateZ(10px)`; |
| } else { |
| card.style.transform = ''; |
| } |
| }); |
| }); |
| } |
| |
| |
| window.addEventListener('DOMContentLoaded', async () => { |
| try { |
| |
| if (typeof marked !== 'undefined') { |
| marked.setOptions({ breaks: true, gfm: true }); |
| } |
| |
| |
| try { |
| await init(); |
| } catch (initErr) { |
| console.error("init() failed:", initErr); |
| } |
| |
| |
| setupEventListeners(); |
| |
| |
| setTimeout(typeEffect, 1000); |
| setTimeout(triggerLightning, 3000); |
| init3DBackground(); |
| init3DTilt(); |
| |
| refreshIcons(); |
| |
| |
| fetch('/api/chat', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ message: "status_check" }) |
| }) |
| .then(res => res.ok ? res.json() : { status: 'Offline' }) |
| .then(data => { |
| const badge = document.getElementById('serverStatusBadge'); |
| if (badge) { |
| if (data.status === 'Offline') { |
| badge.className = 'status-badge offline'; |
| badge.innerHTML = `<i data-lucide="alert-triangle" style="width:12px;"></i> Offline Mode`; |
| } else { |
| badge.className = 'status-badge online'; |
| badge.innerHTML = `<i data-lucide="globe" style="width:12px;"></i> Online Mode`; |
| } |
| if (window.lucide) lucide.createIcons(); |
| } |
| }) |
| .catch(() => { |
| const badge = document.getElementById('serverStatusBadge'); |
| if (badge) { |
| badge.className = 'status-badge offline'; |
| badge.innerHTML = `<i data-lucide="alert-triangle" style="width:12px;"></i> Connection Error`; |
| if (window.lucide) lucide.createIcons(); |
| } |
| }); |
| |
| } catch (err) { |
| console.error("Critical initialization error:", err); |
| } |
| }); |
| </script> |
| </body> |
|
|
| </html> |