diff --git a/public/uncloseai.js b/public/uncloseai.js index 7d0b833..ba12c1b 100644 --- a/public/uncloseai.js +++ b/public/uncloseai.js @@ -22,6 +22,12 @@ import * as TTS from "./src/tts.js"; import * as UI from "./src/ui.js"; import { UncloseVault } from "./src/vault.js"; +// Duplicate load guard: if a page already includes uncloseai.js natively, +// the extension should not re-initialize. Module exports remain available, +// but window globals, event listeners, and DOM side effects only run once. +const __alreadyLoaded = window.__UNCLOSEAI_LOADED__ === true; +window.__UNCLOSEAI_LOADED__ = true; + // ------------------------- // Vault Support // ------------------------- @@ -120,90 +126,102 @@ export { chatHistory } from "./src/chat.js"; // ------------------------- // Global Scope Exports (for backward compatibility) +// Side effects only run once: if the page already loaded uncloseai.js, +// the extension copy skips initialization but module exports still work. // ------------------------- -// Export functions to global scope for HTML onclick handlers -window.handleUserInput = Chat.handleUserInput; -window.readPageWithHermes = PageReader.readPageWithHermes; -window.sendMessage = Chat.sendMessage; -window.speakText = TTS.speakText; -window.uploadFile = FileUpload.uploadFile; -window.openTTSModal = UI.openTTSModal; -window.openTranslateModal = UI.openTranslateModal; -window.toggleUncloseaiEmbeddedModal = async () => - await UI.toggleUncloseaiEmbeddedModal(); -window.extractWebpageContent = Content.extractWebpageContent; -window.getSelectedModel = Models.getSelectedModel; -window.getSelectedModelEndpoint = Models.getSelectedModelEndpoint; -window.showProgressIndicator = FileUpload.showProgressIndicator; -window.hideProgressIndicator = FileUpload.hideProgressIndicator; -window.handleTTSFromElement = UI.handleTTSFromElement; -window.handleUploadFromElement = UI.handleUploadFromElement; -window.handleSmartTranslate = UI.handleSmartTranslate; -window.UncloseVault = UncloseVault; +if (!__alreadyLoaded) { + // Export functions to global scope for HTML onclick handlers + window.handleUserInput = Chat.handleUserInput; + window.readPageWithHermes = PageReader.readPageWithHermes; + window.sendMessage = Chat.sendMessage; + window.speakText = TTS.speakText; + window.uploadFile = FileUpload.uploadFile; + window.openTTSModal = UI.openTTSModal; + window.openTranslateModal = UI.openTranslateModal; + window.toggleUncloseaiEmbeddedModal = async () => + await UI.toggleUncloseaiEmbeddedModal(); + window.extractWebpageContent = Content.extractWebpageContent; + window.getSelectedModel = Models.getSelectedModel; + window.getSelectedModelEndpoint = Models.getSelectedModelEndpoint; + window.showProgressIndicator = FileUpload.showProgressIndicator; + window.hideProgressIndicator = FileUpload.hideProgressIndicator; + window.handleTTSFromElement = UI.handleTTSFromElement; + window.handleUploadFromElement = UI.handleUploadFromElement; + window.handleSmartTranslate = UI.handleSmartTranslate; + window.UncloseVault = UncloseVault; -// Initialize on page load -window.addEventListener("load", async () => { - console.log("uncloseai.js: window.onload event fired."); + // Initialize on page load + window.addEventListener("load", async () => { + console.log("uncloseai.js: window.onload event fired."); - // Load CryptoJS and initialize vault early - await ensureCryptoJS(); - if (UncloseVault.isAvailable()) { - const restored = UncloseVault.init(); - console.log("uncloseai.js: Vault initialized, session restored:", restored); - } - - // Check skip init flag first - if (window.UNCLOSEAI_SKIP_INIT === true) { - console.log( - "uncloseai.js: Skipping full initialization as requested by flag.", - ); - console.log( - "uncloseai.js: Creating floating button only for preview windows.", - ); - - // Still create floating button for preview windows, but skip other initialization - const SHOW_FLOATING_BUTTON = window.UNCLOSEAI_FLOATING_BUTTON !== false; - if (SHOW_FLOATING_BUTTON) { - UI.createFloatingAIButton(); + // Load CryptoJS and initialize vault early + await ensureCryptoJS(); + if (UncloseVault.isAvailable()) { + const restored = UncloseVault.init(); + console.log( + "uncloseai.js: Vault initialized, session restored:", + restored, + ); } - return; - } - UI.initializeSystem(); -}); + // Check skip init flag first + if (window.UNCLOSEAI_SKIP_INIT === true) { + console.log( + "uncloseai.js: Skipping full initialization as requested by flag.", + ); + console.log( + "uncloseai.js: Creating floating button only for preview windows.", + ); -// Export helper functions for class-based integrations -window.handleCustomChat = async (button) => { - const container = button.parentElement; - const input = container.querySelector("[data-chat-input]"); - const chatBox = container.querySelector("[data-chat-box]"); - const message = input.value.trim(); - - if (!message) return; - - // Add user message - chatBox.innerHTML += `
You: ${message}
`; - input.value = ""; - - // Add AI thinking indicator - const thinkingDiv = document.createElement("div"); - thinkingDiv.style.cssText = - "margin: 2px 0; padding: 2px; background: #f5f5f5; border-radius: 2px; font-size: 0.9em;"; - thinkingDiv.innerHTML = "AI: thinking..."; - chatBox.appendChild(thinkingDiv); - chatBox.scrollTop = chatBox.scrollHeight; - - try { - let response = ""; - for await (const chunk of Chat.sendMessage(message)) { - response += chunk; - thinkingDiv.innerHTML = `AI: ${response}`; - chatBox.scrollTop = chatBox.scrollHeight; + // Still create floating button for preview windows, but skip other initialization + const SHOW_FLOATING_BUTTON = + window.UNCLOSEAI_FLOATING_BUTTON !== false; + if (SHOW_FLOATING_BUTTON) { + UI.createFloatingAIButton(); + } + return; } - } catch (error) { - thinkingDiv.innerHTML = `Error: ${error.message}`; - } -}; -console.log("uncloseai.js: Modular version loaded successfully"); + UI.initializeSystem(); + }); + + // Export helper functions for class-based integrations + window.handleCustomChat = async (button) => { + const container = button.parentElement; + const input = container.querySelector("[data-chat-input]"); + const chatBox = container.querySelector("[data-chat-box]"); + const message = input.value.trim(); + + if (!message) return; + + // Add user message + chatBox.innerHTML += `
You: ${message}
`; + input.value = ""; + + // Add AI thinking indicator + const thinkingDiv = document.createElement("div"); + thinkingDiv.style.cssText = + "margin: 2px 0; padding: 2px; background: #f5f5f5; border-radius: 2px; font-size: 0.9em;"; + thinkingDiv.innerHTML = "AI: thinking..."; + chatBox.appendChild(thinkingDiv); + chatBox.scrollTop = chatBox.scrollHeight; + + try { + let response = ""; + for await (const chunk of Chat.sendMessage(message)) { + response += chunk; + thinkingDiv.innerHTML = `AI: ${response}`; + chatBox.scrollTop = chatBox.scrollHeight; + } + } catch (error) { + thinkingDiv.innerHTML = `Error: ${error.message}`; + } + }; + + console.log("uncloseai.js: Modular version loaded successfully"); +} else { + console.log( + "uncloseai.js: page already has uncloseai loaded, skipping duplicate initialization", + ); +}