From 44a3876db60ce1252783cc63968f8ed782f83fb5 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Mon, 26 Jan 2026 10:29:15 -0500 Subject: [PATCH] Fix model refresh to show partial success when TTS is down --- public/src/uncloseai-embed-modal.js | 3961 ++++++++++++++------------- 1 file changed, 2036 insertions(+), 1925 deletions(-) diff --git a/public/src/uncloseai-embed-modal.js b/public/src/uncloseai-embed-modal.js index 1e7c926..9837a7a 100644 --- a/public/src/uncloseai-embed-modal.js +++ b/public/src/uncloseai-embed-modal.js @@ -1,9 +1,8 @@ // Main Hermes AI modal functionality - COMPLETE WORKING VERSION import { marked } from "https://cdn.jsdelivr.net/npm/marked/lib/marked.esm.js"; import hljs from "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/es/highlight.min.js"; -import { API_KEY, setSystemMessageAppend, TTS_API_URL } from "./config.js"; +import { setSystemMessageAppend } from "./config.js"; import { extractWebpageContent } from "./content.js"; -import { detectPageLanguage } from "./language-detection.js"; import { detectCurrentTheme, getThemeColors, @@ -14,19 +13,19 @@ import { getUserLanguagePreference, setUserLanguagePreference, } from "./ui-translations.js"; -import { NATIVE_LANGUAGE_NAMES } from "./translation.js"; import { UncloseVault } from "./vault.js"; // Load CryptoJS if not already loaded async function ensureCryptoJS() { - if (typeof CryptoJS !== 'undefined') return true; + if (typeof CryptoJS !== "undefined") return true; return new Promise((resolve) => { - const script = document.createElement('script'); - script.src = 'https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js'; + const script = document.createElement("script"); + script.src = + "https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js"; script.onload = () => resolve(true); script.onerror = () => { - console.warn('Failed to load CryptoJS - vault features disabled'); + console.warn("Failed to load CryptoJS - vault features disabled"); resolve(false); }; document.head.appendChild(script); @@ -34,17 +33,17 @@ async function ensureCryptoJS() { } // Get USE_CUSTOM_STYLING from window or default -const USE_CUSTOM_STYLING = window.UNCLOSEAI_CUSTOM_STYLING !== false; +const _USE_CUSTOM_STYLING = window.UNCLOSEAI_CUSTOM_STYLING !== false; // Helper function to parse model:voice format function parseVoiceSelection(selectedValue) { // Check if format is "model:voice" or legacy "voice" - if (selectedValue.includes(':')) { - const [model, voice] = selectedValue.split(':'); + if (selectedValue.includes(":")) { + const [model, voice] = selectedValue.split(":"); return { model, voice }; } // Legacy format - assume tts-1 model - return { model: 'tts-1', voice: selectedValue }; + return { model: "tts-1", voice: selectedValue }; } // Import required functions dynamically to avoid circular dependencies @@ -115,7 +114,7 @@ function addCodeBlockCopyButtons(element) { const pre = codeBlock.parentElement; if (pre.tagName.toLowerCase() === "pre") { // Check if buttons already exist to avoid duplicates - const existingBtn = pre.querySelector('.uncloseai-code-copy-btn'); + const existingBtn = pre.querySelector(".uncloseai-code-copy-btn"); if (existingBtn) { return; } @@ -166,31 +165,31 @@ function refreshModalUI() { // Refresh all elements with data-i18n attributes in the modal const modal = document.getElementById("uncloseai-embedded-modal"); if (!modal) return; - + const elementsWithI18n = modal.querySelectorAll("[data-i18n]"); elementsWithI18n.forEach((element) => { const key = element.getAttribute("data-i18n"); const translatedText = getUIText(key); - + if (element.tagName === "INPUT" && element.type === "text") { element.placeholder = translatedText; } else if (element.tagName === "BUTTON" || element.tagName === "LABEL") { // Preserve any icons/emojis at the start const currentText = element.textContent; const iconMatch = currentText.match(/^[^\w\s]+\s*/); - const icon = iconMatch ? iconMatch[0] : ''; + const icon = iconMatch ? iconMatch[0] : ""; element.textContent = icon + translatedText; } else { element.textContent = translatedText; } }); - + // Update any specific elements that need special handling const titleElement = modal.querySelector("h1"); if (titleElement) { titleElement.innerHTML = `uncloseai. ${getUIText("hermesTitle")}`; } - + console.log("Modal UI language refreshed"); } @@ -204,186 +203,196 @@ async function openUncloseaiEmbeddedModalNew() { // Detect if PicoCSS is actually present on the page const hasPicoCSS = document.querySelector('link[href*="pico"]') !== null; - - // Load appropriate CSS based on actual PicoCSS presence - const cssFile = hasPicoCSS ? 'uncloseai-modal-pico.css' : 'uncloseai-modal-builtin.css'; - - if (!document.querySelector(`link[href*="${cssFile}"]`)) { - const link = document.createElement('link'); - link.rel = 'stylesheet'; - link.href = `https://uncloseai.com/src/${cssFile}`; - document.head.appendChild(link); - } - // Ensure ChunkFive font is loaded and text colors are enforced - initializeChunkFiveFont(); + // Load appropriate CSS based on actual PicoCSS presence + const cssFile = hasPicoCSS + ? "uncloseai-modal-pico.css" + : "uncloseai-modal-builtin.css"; - // Get theme colors - const theme = detectCurrentTheme(); - const colors = getThemeColors(theme); - - // Set CSS custom properties for theme support - const root = document.documentElement; - root.style.setProperty('--uncloseai-button-background', colors.buttonBackground); - root.style.setProperty('--uncloseai-button-hover', colors.buttonHover); - root.style.setProperty('--uncloseai-button-border', colors.buttonBorder); - root.style.setProperty('--uncloseai-primary-text', colors.primaryText); - root.style.setProperty('--uncloseai-action-background', colors.actionBackground); - root.style.setProperty('--uncloseai-action-hover', colors.actionHover); - root.style.setProperty('--uncloseai-action-border', colors.actionBorder); - root.style.setProperty('--uncloseai-action-text', colors.primaryText); + if (!document.querySelector(`link[href*="${cssFile}"]`)) { + const link = document.createElement("link"); + link.rel = "stylesheet"; + link.href = `https://uncloseai.com/src/${cssFile}`; + document.head.appendChild(link); + } - const modal = document.createElement("dialog"); - modal.id = "uncloseai-embedded-modal"; - modal.setAttribute("data-theme", theme); + // Ensure ChunkFive font is loaded and text colors are enforced + initializeChunkFiveFont(); - // CSS handles all styling now - no inline styles needed - // But we still need isMobile for some responsive text sizing - const isMobile = window.innerWidth <= 768; + // Get theme colors + const theme = detectCurrentTheme(); + const colors = getThemeColors(theme); - // Simple container with flex layout - const container = document.createElement("div"); - container.className = "uncloseai-modal-container"; + // Set CSS custom properties for theme support + const root = document.documentElement; + root.style.setProperty( + "--uncloseai-button-background", + colors.buttonBackground, + ); + root.style.setProperty("--uncloseai-button-hover", colors.buttonHover); + root.style.setProperty("--uncloseai-button-border", colors.buttonBorder); + root.style.setProperty("--uncloseai-primary-text", colors.primaryText); + root.style.setProperty( + "--uncloseai-action-background", + colors.actionBackground, + ); + root.style.setProperty("--uncloseai-action-hover", colors.actionHover); + root.style.setProperty("--uncloseai-action-border", colors.actionBorder); + root.style.setProperty("--uncloseai-action-text", colors.primaryText); - // Header - const header = document.createElement("header"); - header.className = "uncloseai-modal-header"; + const modal = document.createElement("dialog"); + modal.id = "uncloseai-embedded-modal"; + modal.setAttribute("data-theme", theme); - const titleContainer = document.createElement("div"); - titleContainer.className = "uncloseai-title-container"; + // CSS handles all styling now - no inline styles needed + // But we still need isMobile for some responsive text sizing + const isMobile = window.innerWidth <= 768; - const title = document.createElement("h2"); - title.innerHTML = - "πŸ€– uncloseai."; - title.className = `uncloseai-modal-title ${isMobile ? "uncloseai-modal-title-mobile" : "uncloseai-modal-title-desktop"}`; + // Simple container with flex layout + const container = document.createElement("div"); + container.className = "uncloseai-modal-container"; - const subtitle = document.createElement("div"); - const pageTitle = document.title || window.location.hostname; - subtitle.innerHTML = ` + // Header + const header = document.createElement("header"); + header.className = "uncloseai-modal-header"; + + const titleContainer = document.createElement("div"); + titleContainer.className = "uncloseai-title-container"; + + const title = document.createElement("h2"); + title.innerHTML = + "πŸ€– uncloseai."; + title.className = `uncloseai-modal-title ${isMobile ? "uncloseai-modal-title-mobile" : "uncloseai-modal-title-desktop"}`; + + const subtitle = document.createElement("div"); + const pageTitle = document.title || window.location.hostname; + subtitle.innerHTML = `
uncloseai. ${getUIText("hermesIntro")}
${getUIText("discussingPage")}: ${pageTitle}
`; - titleContainer.appendChild(title); - titleContainer.appendChild(subtitle); + titleContainer.appendChild(title); + titleContainer.appendChild(subtitle); - const closeBtn = document.createElement("button"); - closeBtn.textContent = "βœ•"; - closeBtn.className = "uncloseai-btn-close"; - // Close button handler will be set after settings panel is created + const closeBtn = document.createElement("button"); + closeBtn.textContent = "βœ•"; + closeBtn.className = "uncloseai-btn-close"; + // Close button handler will be set after settings panel is created - // Add settings/menu button - const menuBtn = document.createElement("button"); - menuBtn.textContent = "βš™οΈ"; - menuBtn.className = "uncloseai-modal-settings-btn"; + // Add settings/menu button + const menuBtn = document.createElement("button"); + menuBtn.textContent = "βš™οΈ"; + menuBtn.className = "uncloseai-modal-settings-btn"; - const headerRight = document.createElement("div"); - headerRight.className = "uncloseai-flex-row"; - headerRight.appendChild(menuBtn); - headerRight.appendChild(closeBtn); + const headerRight = document.createElement("div"); + headerRight.className = "uncloseai-flex-row"; + headerRight.appendChild(menuBtn); + headerRight.appendChild(closeBtn); - header.appendChild(titleContainer); - header.appendChild(headerRight); + header.appendChild(titleContainer); + header.appendChild(headerRight); - // Chat area - const chatArea = document.createElement("div"); - chatArea.className = "uncloseai-modal-chat-area"; + // Chat area + const chatArea = document.createElement("div"); + chatArea.className = "uncloseai-modal-chat-area"; - const chatBox = document.createElement("div"); - chatBox.id = "modal-chat-box"; - chatBox.className = "uncloseai-chat-box"; - chatArea.appendChild(chatBox); + const chatBox = document.createElement("div"); + chatBox.id = "modal-chat-box"; + chatBox.className = "uncloseai-chat-box"; + chatArea.appendChild(chatBox); - // Settings panel (collapsible) - const settingsPanel = document.createElement("div"); - settingsPanel.className = "uncloseai-modal-settings"; + // Settings panel (collapsible) + const settingsPanel = document.createElement("div"); + settingsPanel.className = "uncloseai-modal-settings"; - // Vault UI section - const vaultSection = document.createElement("div"); - vaultSection.className = "uncloseai-section uncloseai-vault-section"; - vaultSection.style.gridColumn = "1 / -1"; // Full width - vaultSection.style.marginBottom = "16px"; - vaultSection.style.padding = "12px"; - vaultSection.style.borderRadius = "8px"; - vaultSection.style.border = "1px solid var(--uncloseai-border-color, #ccc)"; + // Vault UI section + const vaultSection = document.createElement("div"); + vaultSection.className = "uncloseai-section uncloseai-vault-section"; + vaultSection.style.gridColumn = "1 / -1"; // Full width + vaultSection.style.marginBottom = "16px"; + vaultSection.style.padding = "12px"; + vaultSection.style.borderRadius = "8px"; + vaultSection.style.border = "1px solid var(--uncloseai-border-color, #ccc)"; - // Container for the rest of settings (will be hidden when vault locked) - const settingsContent = document.createElement("div"); - settingsContent.className = "uncloseai-settings-content"; + // Container for the rest of settings (will be hidden when vault locked) + const settingsContent = document.createElement("div"); + settingsContent.className = "uncloseai-settings-content"; - // Helper to get vault value or localStorage fallback (for migration) - function getVaultOrStorage(key, defaultValue = null) { - if (UncloseVault.isAvailable() && UncloseVault.isUnlocked()) { - return UncloseVault.get(key, defaultValue); - } - // Fallback for when vault not available - const stored = localStorage.getItem(key); - return stored !== null ? stored : defaultValue; - } - - // Helper to set vault value - function setVaultValue(key, value) { - if (UncloseVault.isAvailable() && UncloseVault.isUnlocked()) { - UncloseVault.set(key, value); - } else { - // If vault not unlocked, store temporarily (will be lost) - console.warn('Vault locked - setting not persisted:', key); - } - } - - // Build vault UI based on current state - function buildVaultUI() { - vaultSection.innerHTML = ''; - - if (!UncloseVault.isAvailable()) { - // CryptoJS not loaded - settings work but won't persist across sessions - vaultSection.style.display = 'none'; - settingsContent.style.display = 'grid'; - return; + // Helper to get vault value or localStorage fallback (for migration) + function getVaultOrStorage(key, defaultValue = null) { + if (UncloseVault.isAvailable() && UncloseVault.isUnlocked()) { + return UncloseVault.get(key, defaultValue); + } + // Fallback for when vault not available + const stored = localStorage.getItem(key); + return stored !== null ? stored : defaultValue; } - if (UncloseVault.isUnlocked()) { - // Vault unlocked - thin bar at top with logged in status and lock button - vaultSection.style.cssText = 'display: grid; grid-template-columns: 1fr auto; align-items: center; grid-column: 1 / -1; padding: 6px 12px; font-size: 0.85em;'; - const unlockedUI = document.createElement("span"); - unlockedUI.style.color = '#28a745'; - unlockedUI.textContent = 'βœ“ ' + getUIText("vaultLoggedIn"); + // Helper to set vault value + function setVaultValue(key, value) { + if (UncloseVault.isAvailable() && UncloseVault.isUnlocked()) { + UncloseVault.set(key, value); + } else { + // If vault not unlocked, store temporarily (will be lost) + console.warn("Vault locked - setting not persisted:", key); + } + } - const lockBtn = document.createElement("button"); - lockBtn.className = 'uncloseai-btn-small'; - lockBtn.title = getUIText("vaultLockTooltip"); - lockBtn.textContent = 'πŸ”’ ' + getUIText("vaultLock"); + // Build vault UI based on current state + function buildVaultUI() { + vaultSection.innerHTML = ""; - lockBtn.onclick = () => { - UncloseVault.lock(); - buildVaultUI(); - // Close settings panel after locking - settingsOpen = false; - settingsPanel.classList.remove("open"); - }; + if (!UncloseVault.isAvailable()) { + // CryptoJS not loaded - settings work but won't persist across sessions + vaultSection.style.display = "none"; + settingsContent.style.display = "grid"; + return; + } - vaultSection.appendChild(unlockedUI); - vaultSection.appendChild(lockBtn); - settingsContent.style.display = 'grid'; // Show settings when unlocked - } else { - // Vault locked or doesn't exist - show centered prompt using CSS grid - vaultSection.style.display = 'grid'; - vaultSection.style.placeContent = 'center'; - vaultSection.style.height = '100%'; - vaultSection.style.minHeight = '400px'; - vaultSection.style.border = 'none'; - vaultSection.style.padding = '20px'; - const hasVault = UncloseVault.hasVault(); + if (UncloseVault.isUnlocked()) { + // Vault unlocked - thin bar at top with logged in status and lock button + vaultSection.style.cssText = + "display: grid; grid-template-columns: 1fr auto; align-items: center; grid-column: 1 / -1; padding: 6px 12px; font-size: 0.85em;"; + const unlockedUI = document.createElement("span"); + unlockedUI.style.color = "#28a745"; + unlockedUI.textContent = `βœ“ ${getUIText("vaultLoggedIn")}`; - const promptUI = document.createElement("div"); - promptUI.style.cssText = 'display: grid; gap: 12px; width: 100%; text-align: center;'; - promptUI.innerHTML = ` + const lockBtn = document.createElement("button"); + lockBtn.className = "uncloseai-btn-small"; + lockBtn.title = getUIText("vaultLockTooltip"); + lockBtn.textContent = `πŸ”’ ${getUIText("vaultLock")}`; + + lockBtn.onclick = () => { + UncloseVault.lock(); + buildVaultUI(); + // Close settings panel after locking + settingsOpen = false; + settingsPanel.classList.remove("open"); + }; + + vaultSection.appendChild(unlockedUI); + vaultSection.appendChild(lockBtn); + settingsContent.style.display = "grid"; // Show settings when unlocked + } else { + // Vault locked or doesn't exist - show centered prompt using CSS grid + vaultSection.style.display = "grid"; + vaultSection.style.placeContent = "center"; + vaultSection.style.height = "100%"; + vaultSection.style.minHeight = "400px"; + vaultSection.style.border = "none"; + vaultSection.style.padding = "20px"; + const hasVault = UncloseVault.hasVault(); + + const promptUI = document.createElement("div"); + promptUI.style.cssText = + "display: grid; gap: 12px; width: 100%; text-align: center;"; + promptUI.innerHTML = `
${hasVault ? getUIText("vaultWelcomeBack") : getUIText("vaultLine1")}
- ${!hasVault ? `
${getUIText("vaultLine2")}
` : ''} + ${!hasVault ? `
${getUIText("vaultLine2")}
` : ""}