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")}
` : ""}