| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> |
| <title>Rox AI</title> |
| <meta name="description" content="Rox AI - Professional AI chat interface with advanced language models, file processing, and seamless conversations"> |
| <meta name="keywords" content="AI, chat, assistant, Rox AI, language model, conversation"> |
| <meta name="author" content="Rox AI"> |
| <meta name="robots" content="index, follow"> |
| <meta name="theme-color" content="#0f1a24"> |
| <meta name="apple-mobile-web-app-capable" content="yes"> |
| <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> |
| <meta name="apple-mobile-web-app-title" content="Rox AI"> |
| <meta name="mobile-web-app-capable" content="yes"> |
| <meta name="format-detection" content="telephone=no"> |
| <meta name="msapplication-TileColor" content="#0f1a24"> |
| <meta name="msapplication-config" content="none"> |
| |
| <meta name="application-name" content="Rox AI"> |
| |
| <meta name="apple-touch-fullscreen" content="yes"> |
| |
| <link rel="manifest" href="/manifest.json" crossorigin="use-credentials"> |
| |
| <link rel="icon" type="image/svg+xml" sizes="192x192" href="/icon-192.svg"> |
| <link rel="icon" type="image/svg+xml" sizes="512x512" href="/icon-512.svg"> |
| <link rel="apple-touch-icon" sizes="180x180" href="/icon-192.svg"> |
| <link rel="apple-touch-icon" sizes="192x192" href="/icon-192.svg"> |
| <link rel="apple-touch-icon" sizes="512x512" href="/icon-512.svg"> |
| <link rel="mask-icon" href="/icon-512.svg" color="#667eea"> |
| <link rel="stylesheet" href="styles.css"> |
| <link rel="stylesheet" href="screenshare.css"> |
| |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css" integrity="sha384-n8MVd4RsNIU0tAv4ct0nTaAbDJwPJzDEaqSD1odI+WdtXRGWt2kTvGFasHpSy3SV" crossorigin="anonymous"> |
| <script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js" integrity="sha384-XjKyOOlGwcjNTAIQHIpgOno0Ber8PWQV9qAwPrNQfByT6CMDgE8XYZynxBnMi5KQ" crossorigin="anonymous"></script> |
| <style> |
| |
| #loading-screen { |
| position: fixed; |
| inset: 0; |
| background: linear-gradient(135deg, #0f1a24 0%, #1a2b3c 100%); |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| gap: 24px; |
| z-index: 99999; |
| transition: opacity 0.25s ease-out, visibility 0.25s ease-out; |
| } |
| #loading-screen.hidden { |
| opacity: 0; |
| visibility: hidden; |
| pointer-events: none; |
| } |
| .loading-logo { |
| animation: pulse 2s ease-in-out infinite; |
| } |
| .loading-logo svg { |
| filter: drop-shadow(0 0 20px rgba(102, 126, 234, 0.5)); |
| } |
| .loading-text { |
| color: #a0b4c4; |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; |
| font-size: 14px; |
| letter-spacing: 2px; |
| text-transform: uppercase; |
| } |
| .loading-dots { |
| display: inline-flex; |
| gap: 4px; |
| } |
| .loading-dots span { |
| width: 6px; |
| height: 6px; |
| background: #667eea; |
| border-radius: 50%; |
| animation: bounce 1.4s ease-in-out infinite; |
| } |
| .loading-dots span:nth-child(2) { animation-delay: 0.2s; } |
| .loading-dots span:nth-child(3) { animation-delay: 0.4s; } |
| @keyframes pulse { |
| 0%, 100% { transform: scale(1); } |
| 50% { transform: scale(1.05); } |
| } |
| @keyframes bounce { |
| 0%, 60%, 100% { transform: translateY(0); } |
| 30% { transform: translateY(-8px); } |
| } |
| </style> |
| </head> |
| <body> |
| |
| <div id="loading-screen"> |
| <div class="loading-logo"> |
| <svg width="80" height="80" viewBox="0 0 64 64"> |
| <defs> |
| <linearGradient id="loadingGrad" x1="0%" y1="0%" x2="100%" y2="100%"> |
| <stop offset="0%" stop-color="#667eea"/> |
| <stop offset="100%" stop-color="#764ba2"/> |
| </linearGradient> |
| </defs> |
| <path d="M32 8 L56 20 L56 44 L32 56 L8 44 L8 20 Z" fill="none" stroke="url(#loadingGrad)" stroke-width="2"/> |
| <circle cx="32" cy="32" r="8" fill="url(#loadingGrad)"/> |
| </svg> |
| </div> |
| <div class="loading-text"> |
| Loading |
| <span class="loading-dots"> |
| <span></span> |
| <span></span> |
| <span></span> |
| </span> |
| </div> |
| </div> |
| <div class="app"> |
| |
| <aside class="sidebar" id="sidebar"> |
| <div class="sidebar-header"> |
| <button class="btn-new-chat" id="btnNewChat" title="Start new chat" aria-label="Start new chat"> |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <path d="M12 5v14M5 12h14"/> |
| </svg> |
| <span>New chat</span> |
| </button> |
| </div> |
| |
| <div class="chat-list" id="chatList"> |
| |
| </div> |
| |
| <div class="sidebar-footer"> |
| <div class="app-version-display" id="appVersionDisplay" title="Rox AI Version"> |
| <span>Loading...</span> |
| </div> |
| <button class="user-menu" id="userMenu" title="User menu" aria-label="Open user menu"> |
| <div class="user-avatar">U</div> |
| <span class="user-name">User</span> |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <path d="M6 9l6 6 6-6"/> |
| </svg> |
| </button> |
| </div> |
| </aside> |
|
|
| |
| <main class="main"> |
| |
| <header class="header"> |
| <button class="btn-toggle-sidebar" id="btnToggleSidebar" title="Toggle sidebar" aria-label="Toggle sidebar"> |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <path d="M3 12h18M3 6h18M3 18h18"/> |
| </svg> |
| </button> |
| |
| |
| <div class="model-selector" id="modelSelector"> |
| <button class="model-selector-btn" id="modelSelectorBtn" title="Select AI model" aria-label="Select AI model"> |
| <span class="model-name" id="currentModelName">Rox</span> |
| <svg class="model-chevron" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <path d="M6 9l6 6 6-6"/> |
| </svg> |
| </button> |
| <div class="model-dropdown" id="modelDropdown"> |
| <div class="model-dropdown-header">Select Model</div> |
| <div class="model-option active" data-model="rox" data-name="Rox Core"> |
| <div class="model-option-info"> |
| <span class="model-option-name">Rox Core</span> |
| <span class="model-option-desc">Fast & reliable for everyday tasks</span> |
| </div> |
| <svg class="model-check" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <polyline points="20 6 9 17 4 12"/> |
| </svg> |
| </div> |
| <div class="model-option" data-model="rox-2.1-turbo" data-name="Rox 2.1 Turbo"> |
| <div class="model-option-info"> |
| <span class="model-option-name">Rox 2.1 Turbo</span> |
| <span class="model-option-desc">Deep thinking & reasoning</span> |
| </div> |
| <svg class="model-check" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <polyline points="20 6 9 17 4 12"/> |
| </svg> |
| </div> |
| <div class="model-option" data-model="rox-3.5-coder" data-name="Rox 3.5 Coder"> |
| <div class="model-option-info"> |
| <span class="model-option-name">Rox 3.5 Coder</span> |
| <span class="model-option-desc">Best for coding & development</span> |
| </div> |
| <svg class="model-check" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <polyline points="20 6 9 17 4 12"/> |
| </svg> |
| </div> |
| <div class="model-option" data-model="rox-4.5-turbo" data-name="Rox 4.5 Turbo"> |
| <div class="model-option-info"> |
| <span class="model-option-name">Rox 4.5 Turbo</span> |
| <span class="model-option-desc">Advanced reasoning & analysis</span> |
| </div> |
| <svg class="model-check" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <polyline points="20 6 9 17 4 12"/> |
| </svg> |
| </div> |
| <div class="model-option" data-model="rox-5-ultra" data-name="Rox 5 Ultra"> |
| <div class="model-option-info"> |
| <span class="model-option-name">Rox 5 Ultra</span> |
| <span class="model-option-desc">Most powerful flagship model</span> |
| </div> |
| <svg class="model-check" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <polyline points="20 6 9 17 4 12"/> |
| </svg> |
| </div> |
| <div class="model-option" data-model="rox-6-dyno" data-name="Rox 6 Dyno"> |
| <div class="model-option-info"> |
| <span class="model-option-name">Rox 6 Dyno</span> |
| <span class="model-option-desc">Dynamic thinker with native vision</span> |
| </div> |
| <svg class="model-check" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <polyline points="20 6 9 17 4 12"/> |
| </svg> |
| </div> |
| <div class="model-option" data-model="rox-7-coder" data-name="Rox 7 Coder"> |
| <div class="model-option-info"> |
| <span class="model-option-name">Rox 7 Coder</span> |
| <span class="model-option-desc">Ultimate coding powerhouse with reasoning</span> |
| </div> |
| <svg class="model-check" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <polyline points="20 6 9 17 4 12"/> |
| </svg> |
| </div> |
| </div> |
| </div> |
| |
| <div class="header-title"> |
| <h1 id="chatTitle">New Chat</h1> |
| </div> |
| |
| <div class="header-actions"> |
| <button class="btn-icon" id="btnScreenShare" title="Share Your Screen (Desktop Only)" aria-label="Share your screen with AI" style="display: none;"> |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <rect x="2" y="3" width="20" height="14" rx="2" ry="2"/> |
| <line x1="8" y1="21" x2="16" y2="21"/> |
| <line x1="12" y1="17" x2="12" y2="21"/> |
| <circle cx="12" cy="10" r="2" fill="currentColor"/> |
| </svg> |
| </button> |
| <button class="btn-download" id="btnInstallPWA" title="Install Rox AI App" style="display: none;"> |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/> |
| <polyline points="7 10 12 15 17 10"/> |
| <line x1="12" y1="15" x2="12" y2="3"/> |
| </svg> |
| <span class="download-text">Install App</span> |
| </button> |
| <button class="btn-icon" id="btnThemeToggle" title="Toggle theme" aria-label="Toggle light/dark theme"> |
| <svg class="icon-sun" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/> |
| </svg> |
| <svg class="icon-moon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display:none;"> |
| <path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/> |
| </svg> |
| </button> |
| </div> |
| </header> |
|
|
| |
| <div class="chat-container" id="chatContainer"> |
| |
| <div class="welcome" id="welcome"> |
| <div class="welcome-content"> |
| <div class="logo-container"> |
| <div class="logo-glow"></div> |
| <svg class="logo" width="64" height="64" viewBox="0 0 64 64"> |
| <defs> |
| <linearGradient id="logoGrad" x1="0%" y1="0%" x2="100%" y2="100%"> |
| <stop offset="0%" stop-color="#667eea"/> |
| <stop offset="100%" stop-color="#764ba2"/> |
| </linearGradient> |
| </defs> |
| <path d="M32 8 L56 20 L56 44 L32 56 L8 44 L8 20 Z" fill="none" stroke="url(#logoGrad)" stroke-width="2"/> |
| <circle cx="32" cy="32" r="8" fill="url(#logoGrad)"/> |
| </svg> |
| </div> |
| <h2 class="welcome-title">How can I help you today?</h2> |
| |
| <div class="suggestions"> |
| <button class="suggestion-card" data-prompt="Explain quantum computing in simple terms" title="Explain concepts" aria-label="Explain quantum computing in simple terms"> |
| <div class="suggestion-icon"> |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/> |
| </svg> |
| </div> |
| <div class="suggestion-text"> |
| <div class="suggestion-title">Explain concepts</div> |
| <div class="suggestion-desc">Break down complex topics</div> |
| </div> |
| </button> |
| |
| <button class="suggestion-card" data-prompt="Write a Python function to sort a list" title="Code assistance" aria-label="Write a Python function to sort a list"> |
| <div class="suggestion-icon"> |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <path d="M16 18l6-6-6-6M8 6l-6 6 6 6"/> |
| </svg> |
| </div> |
| <div class="suggestion-text"> |
| <div class="suggestion-title">Code assistance</div> |
| <div class="suggestion-desc">Write and debug code</div> |
| </div> |
| </button> |
| |
| <button class="suggestion-card" data-prompt="Analyze this data and provide insights" title="Data analysis" aria-label="Analyze this data and provide insights"> |
| <div class="suggestion-icon"> |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <path d="M21 21H4.6c-.56 0-.6-.44-.6-1V3"/> |
| <path d="M9 18v-6M15 18V9M21 18v-3"/> |
| </svg> |
| </div> |
| <div class="suggestion-text"> |
| <div class="suggestion-title">Data analysis</div> |
| <div class="suggestion-desc">Process and interpret data</div> |
| </div> |
| </button> |
| |
| <button class="suggestion-card" data-prompt="Help me brainstorm ideas for" title="Creative thinking" aria-label="Help me brainstorm ideas"> |
| <div class="suggestion-icon"> |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/> |
| </svg> |
| </div> |
| <div class="suggestion-text"> |
| <div class="suggestion-title">Creative thinking</div> |
| <div class="suggestion-desc">Generate and refine ideas</div> |
| </div> |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="messages" id="messages"></div> |
| </div> |
|
|
| |
| <div class="input-area"> |
| |
| <div class="attachments-preview" id="attachmentsPreview"></div> |
| |
| <div class="input-container"> |
| <button class="btn-attach" id="btnAttach" title="Attach Files & More"> |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <path d="M12 5v14M5 12h14"/> |
| </svg> |
| </button> |
| <input type="file" id="fileInput" multiple hidden accept="image/*,.pdf,.txt,.doc,.docx,.xlsx,.xls,.pptx,.ppt,.rtf,.odt,.csv,.json,.md,.js,.ts,.jsx,.tsx,.py,.java,.c,.cpp,.h,.hpp,.cs,.go,.rs,.rb,.php,.swift,.html,.css,.xml,.yaml,.yml,.toml,.log,.sql,.sh,.bat,.ps1,.ini,.env,.cfg,.conf,.vue,.svelte,.astro,.kt,.scala,.r,.lua,.dart,.zig,.ex,.exs,.erl,.clj,.hs,.ml,.fs" aria-label="Attach files"> |
| |
| <div class="input-wrapper"> |
| <label for="messageInput" class="visually-hidden">Message input</label> |
| <textarea |
| id="messageInput" |
| placeholder="Message AI Assistant... (Paste YouTube URLs for video analysis)" |
| rows="1" |
| aria-label="Type your message here" |
| ></textarea> |
| </div> |
| |
| <button class="btn-send" id="btnSend" disabled title="Send message" aria-label="Send message"> |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"> |
| <path d="M12 19V5M5 12l7-7 7 7"/> |
| </svg> |
| </button> |
| </div> |
| |
| <div class="input-footer"> |
| <span class="input-hint">Rox AI can make mistakes. <a href="#" id="openDocsLink" class="docs-link">Check important info.</a></span> |
| <span class="offline-indicator" id="offlineIndicator" style="display: none;"> |
| <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <line x1="1" y1="1" x2="23" y2="23"/> |
| <path d="M16.72 11.06A10.94 10.94 0 0119 12.55"/> |
| <path d="M5 12.55a10.94 10.94 0 015.17-2.39"/> |
| <path d="M10.71 5.05A16 16 0 0122.58 9"/> |
| <path d="M1.42 9a15.91 15.91 0 014.7-2.88"/> |
| <path d="M8.53 16.11a6 6 0 016.95 0"/> |
| <line x1="12" y1="20" x2="12.01" y2="20"/> |
| </svg> |
| Offline |
| </span> |
| </div> |
| </div> |
| </main> |
| </div> |
|
|
| |
| <div class="sidebar-overlay" id="sidebarOverlay"></div> |
|
|
| |
| <div class="context-menu" id="contextMenu"> |
| <button class="context-menu-item" data-action="rename" title="Rename chat" aria-label="Rename chat"> |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/> |
| <path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/> |
| </svg> |
| <span>Rename</span> |
| </button> |
| <button class="context-menu-item" data-action="delete" title="Delete chat" aria-label="Delete chat"> |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <path d="M3 6h18M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/> |
| </svg> |
| <span>Delete</span> |
| </button> |
| <button class="context-menu-item" data-action="export-pdf" title="Export chat as PDF" aria-label="Export chat as PDF"> |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/> |
| <polyline points="14 2 14 8 20 8"/> |
| <line x1="12" y1="18" x2="12" y2="12"/> |
| <polyline points="9 15 12 18 15 15"/> |
| </svg> |
| <span>Export PDF</span> |
| </button> |
| </div> |
|
|
| |
| <div class="modal" id="renameModal"> |
| <div class="modal-content"> |
| <h3>Rename chat</h3> |
| <label for="renameInput" class="visually-hidden">New chat name</label> |
| <input type="text" id="renameInput" placeholder="Enter new name" aria-label="Enter new chat name"> |
| <div class="modal-actions"> |
| <button class="btn-secondary" id="btnCancelRename" title="Cancel" aria-label="Cancel rename">Cancel</button> |
| <button class="btn-primary" id="btnConfirmRename" title="Confirm rename" aria-label="Confirm rename">Rename</button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="docs-modal-overlay" id="docsModalOverlay"> |
| <div class="docs-modal"> |
| <div class="docs-modal-header"> |
| <div class="docs-modal-title"> |
| <svg width="24" height="24" viewBox="0 0 64 64"> |
| <defs> |
| <linearGradient id="docsLogoGrad" x1="0%" y1="0%" x2="100%" y2="100%"> |
| <stop offset="0%" stop-color="#667eea"/> |
| <stop offset="100%" stop-color="#764ba2"/> |
| </linearGradient> |
| </defs> |
| <path d="M32 8 L56 20 L56 44 L32 56 L8 44 L8 20 Z" fill="none" stroke="url(#docsLogoGrad)" stroke-width="3"/> |
| <circle cx="32" cy="32" r="8" fill="url(#docsLogoGrad)"/> |
| </svg> |
| Rox AI Documentation |
| </div> |
| <button class="docs-modal-close" id="docsModalClose" aria-label="Close documentation"> |
| <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <path d="M18 6L6 18M6 6l12 12"/> |
| </svg> |
| </button> |
| </div> |
| <div class="docs-modal-content" id="docsModalContent"> |
| |
| </div> |
| </div> |
| </div> |
|
|
| |
| <script src="screenshare.js"></script> |
| <script src="app.js"></script> |
| <script> |
| |
| if ('serviceWorker' in navigator) { |
| window.addEventListener('load', async () => { |
| try { |
| |
| const urlParams = new URLSearchParams(window.location.search); |
| const isUpdateFlow = urlParams.has('_v') || urlParams.has('_emergency'); |
| const updateJustCompleted = sessionStorage.getItem('roxai_update_complete') === 'true'; |
| |
| |
| if (isUpdateFlow) { |
| const cleanUrl = window.location.pathname; |
| window.history.replaceState({}, document.title, cleanUrl); |
| console.log('✅ Update complete, URL cleaned'); |
| } |
| |
| const registration = await navigator.serviceWorker.register('/sw.js', { |
| scope: '/', |
| updateViaCache: 'none' |
| }); |
| |
| console.log('✅ Service Worker registered:', registration.scope); |
| |
| |
| if (!updateJustCompleted && !isUpdateFlow) { |
| registration.update(); |
| } |
| |
| |
| setInterval(() => { |
| |
| if (sessionStorage.getItem('roxai_update_complete') !== 'true') { |
| registration.update(); |
| } |
| }, 5 * 60 * 1000); |
| |
| |
| document.addEventListener('visibilitychange', () => { |
| if (document.visibilityState === 'visible') { |
| if (sessionStorage.getItem('roxai_update_complete') !== 'true') { |
| registration.update(); |
| } |
| } |
| }); |
| |
| |
| registration.addEventListener('updatefound', () => { |
| const newWorker = registration.installing; |
| console.log('🔄 Service Worker update found'); |
| |
| newWorker.addEventListener('statechange', () => { |
| if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { |
| |
| console.log('📦 New service worker installed'); |
| |
| if (updateJustCompleted || isUpdateFlow) { |
| console.log('⏸️ Skipping update dialog (just completed update)'); |
| return; |
| } |
| |
| |
| if (window.roxAI && typeof window.roxAI._showUpdateDialog === 'function') { |
| window.roxAI._updateAvailable = true; |
| const currentVer = window.roxAI._appVersion || 'Unknown'; |
| const newVer = window.roxAI._newAppVersion || 'Latest'; |
| window.roxAI._showUpdateDialog(currentVer, newVer); |
| } |
| } |
| }); |
| }); |
| |
| |
| navigator.serviceWorker.addEventListener('controllerchange', () => { |
| console.log('🔄 Service worker controller changed'); |
| |
| if (!window._isUpdating && !updateJustCompleted && !isUpdateFlow) { |
| window._isUpdating = true; |
| window.location.reload(); |
| } |
| }); |
| |
| |
| navigator.serviceWorker.addEventListener('message', (event) => { |
| if (event.data?.type === 'FORCE_RELOAD') { |
| console.log('🔄 Force reload from service worker'); |
| |
| if (updateJustCompleted || isUpdateFlow) { |
| console.log('⏸️ Skipping force reload (just completed update)'); |
| return; |
| } |
| if (!window._isUpdating) { |
| window._isUpdating = true; |
| sessionStorage.setItem('roxai_update_complete', 'true'); |
| if ('caches' in window) { |
| caches.keys().then(names => { |
| Promise.all(names.map(name => caches.delete(name))).then(() => { |
| window.location.reload(); |
| }); |
| }); |
| } else { |
| window.location.reload(); |
| } |
| } |
| } |
| if (event.data?.type === 'CACHES_CLEARED') { |
| console.log('✅ Caches cleared by service worker'); |
| } |
| }); |
| |
| } catch (error) { |
| console.warn('⚠️ Service Worker registration failed:', error); |
| } |
| }); |
| } |
| </script> |
| </body> |
| </html> |
|
|