feat(translation): add smart translate dropdown with native language names

- Add smart-translate widget feature with AI language detection
- Implement triple-format language display (code • English • Native)
- Add native script names with proper Unicode (中文, Español, العربية, etc.)
- Export handleSmartTranslate to global scope for HTML onclick
- Update demo.html to showcase both translate and smart-translate options
- Add comprehensive widget documentation with code examples

🤖 Generated with Hermes Staff

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Russell Ballestrini 2025-07-02 18:43:35 -04:00
parent 6a4e9f9f72
commit cae51b3578
5 changed files with 245 additions and 23 deletions

View file

@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#43a047">
<meta name="color-scheme" content="light dark">
<title>Demo Free LLM & Text To Speech Artificial Intelligence Service | ai.unturf.com</title>
<title>Demo Free LLM & Text To Speech Artificial Intelligence Service | uncloseai.com</title>
<!-- PicoCSS -->
<link rel="stylesheet" href="/css/pico.classless.min.css">
@ -49,7 +49,7 @@
setSystemMessageAppend(`
Additional context for this demo page:
- You're helping users explore and test AI features on ai.unturf.com
- You're helping users explore and test AI features on uncloseai.com
- This is a demonstration page showing various AI capabilities
- Encourage users to try different features like TTS, file uploads, and the Hermes chat
- Be enthusiastic and helpful when explaining how the features work
@ -66,8 +66,8 @@
<header>
<hgroup>
<a href="https://ai.unturf.com"><h1 class="unturf" style="font-family: 'ChunkFiveRegular';">unturf.</h1></a>
<p>Welcome to ai.unturf.com - Free LLM & Text To Speech Artificial Intelligence Service</title>
<a href="https://uncloseai.com"><h1 class="unturf" style="font-family: 'ChunkFiveRegular';">uncloseai.</h1></a>
<p>Welcome to uncloseai.com - Free LLM & Text To Speech Artificial Intelligence Service</p>
</hgroup>
<nav>
<ul>
@ -263,20 +263,54 @@
<!-- 8. Translation Only -->
<section id="translate-only">
<h3>8. 🌐 Translation Only</h3>
<p>Just the translation functionality:</p>
<h3>8. 🌐 Translation Options</h3>
<p>Two ways to translate: Modal interface and Smart AI dropdown:</p>
<h4>📄 Code Example</h4>
<details open>
<summary><strong>Translation Only Code</strong></summary>
<p>Embed just the translation button:</p>
<pre><code class="html">&lt;div class="uncloseai" data-features="translate"&gt;&lt;/div&gt;</code></pre>
<summary><strong>Translation Code</strong></summary>
<p>Choose between two translation approaches:</p>
<pre><code class="html">&lt;!-- Traditional Modal (full options) --&gt;
&lt;div class="uncloseai" data-features="translate"&gt;&lt;/div&gt;
&lt;!-- Smart Dropdown (quick translate) --&gt;
&lt;div class="uncloseai" data-features="smart-translate"&gt;&lt;/div&gt;
&lt;!-- Both options together --&gt;
&lt;div class="uncloseai" data-features="translate,smart-translate"&gt;&lt;/div&gt;</code></pre>
</details>
<h4>🎯 Live Example</h4>
<p>Try the translation-only interface below:</p>
<div style="border: 2px dashed #43a047; padding: 20px; margin: 15px 0; border-radius: 8px; background: rgba(67, 160, 71, 0.05);">
<div class="uncloseai" data-features="translate" id="demo-translate-only"></div>
<h4>🎯 Live Examples</h4>
<div style="display: grid; gap: 20px; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));">
<!-- Original Translation Modal -->
<div style="border: 2px dashed #43a047; padding: 20px; border-radius: 8px; background: rgba(67, 160, 71, 0.05);">
<h5>🌐 Translation Modal</h5>
<p>Traditional modal interface for translations:</p>
<div class="uncloseai" data-features="translate" id="demo-translate-modal"></div>
<p><strong>Features:</strong></p>
<ul>
<li>📝 Custom text translation</li>
<li>📄 Full page translation</li>
<li>⚙️ Advanced options</li>
<li>📋 Copy translated content</li>
</ul>
</div>
<!-- New Smart Dropdown -->
<div style="border: 2px dashed #43a047; padding: 20px; border-radius: 8px; background: rgba(67, 160, 71, 0.05);">
<h5>🤖 Smart AI Dropdown (New!)</h5>
<p>AI-powered quick translation:</p>
<div class="uncloseai" data-features="smart-translate" id="demo-translate-smart"></div>
<p><strong>✨ Smart Features:</strong></p>
<ul>
<li>🤖 AI detects page language automatically</li>
<li>🌐 Triple format: code • English • Native (es • Spanish • Español)</li>
<li>📋 Quick dropdown selection</li>
<li>🚀 Opens in new tab instantly</li>
<li>🎨 Dark/light mode themed</li>
</ul>
</div>
</div>
</section>

View file

@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#43a047">
<meta name="color-scheme" content="light dark">
<title>Using Free LLM & Text To Speech Artificial Intelligence Service | ai.unturf.com</title>
<title>Using Free LLM & Text To Speech Artificial Intelligence Service | uncloseai.com</title>
<!-- PicoCSS -->
<link rel="stylesheet" href="/css/pico.classless.min.css">
@ -49,7 +49,7 @@
<header>
<hgroup>
<a href="https://uncloseai.com"><h1 class="unturf" style="font-family: 'ChunkFiveRegular';">russell@unturf. presents, uncloseai</h1></a>
<p>Welcome to uncloseai.com - Free LLM & Text To Speech Artificial Intelligence Service</title>
<p>Welcome to uncloseai.com - Free LLM & Text To Speech Artificial Intelligence Service</p>
</hgroup>
<nav>
<ul>
@ -126,9 +126,10 @@
<p><strong>Specific Features:</strong> Choose which buttons to include</p>
<pre><code class="html">&lt;div class="uncloseai" data-features="tts,translate,read"&gt;&lt;/div&gt;
&lt;div class="uncloseai" data-features="translate"&gt;&lt;/div&gt;</code></pre>
&lt;div class="uncloseai" data-features="translate"&gt;&lt;/div&gt;
&lt;div class="uncloseai" data-features="smart-translate"&gt;&lt;/div&gt;</code></pre>
<p><strong>Available Features:</strong> <code>chat</code>, <code>tts</code>, <code>translate</code>, <code>upload</code>, <code>read</code>, <code>full</code></p>
<p><strong>Available Features:</strong> <code>chat</code>, <code>tts</code>, <code>translate</code>, <code>smart-translate</code>, <code>upload</code>, <code>read</code>, <code>full</code></p>
<h4>What You Get</h4>
<ul>

View file

@ -4,7 +4,7 @@ import { API_KEY } from "./config.js";
import { getSelectedModel, getSelectedModelEndpoint } from "./models.js";
import { countTokens } from "./token_estimator.js";
// Languages supported by Hermes 3 model (from Russell's implementation)
// Languages supported by Hermes 3 model (in English for reference)
export const SUPPORTED_LANGUAGES = {
en: "English",
zh: "Chinese (Simplified)",
@ -27,6 +27,29 @@ export const SUPPORTED_LANGUAGES = {
ko: "Korean",
};
// Native language names with proper scripts, accents, and authentic forms
export const NATIVE_LANGUAGE_NAMES = {
en: "English",
zh: "中文(简体)",
hi: "हिंदी",
es: "Español",
fr: "Français",
ar: "العَرَبِيَّة",
bn: "বাংলা",
ru: "Русский",
pt: "Português",
ur: "اُردُو",
id: "Bahasa Indonesia",
de: "Deutsch",
ja: "日本語",
sw: "Kiswahili",
mr: "मराठी",
te: "తెలుగు",
tr: "Türkçe",
"zh-tw": "中文(繁體)",
ko: "한국어",
};
// Send message with custom history (for translation with minimal system prompt)
async function* sendMessageWithHistory(messageHistory) {
const apiUrl = `${getSelectedModelEndpoint()}/chat/completions`;

173
src/ui.js
View file

@ -460,6 +460,9 @@ export function createCustomInterface(container, features) {
case "translate":
createTranslateFeature(div);
break;
case "smart-translate":
createSmartTranslateFeature(div);
break;
case "upload":
createUploadFeature(div);
break;
@ -509,12 +512,21 @@ export function createUploadFeature(container) {
export function createTranslateFeature(container) {
const translateDiv = document.createElement("div");
translateDiv.innerHTML = `
<h4>Translation</h4>
<button onclick="openTranslateModal()" style="width: 100%; padding: 6px;">🌐 Translate</button>
<h4>Translation Modal</h4>
<button onclick="openTranslateModal()" style="width: 100%; padding: 6px;">🌐 Translation Modal</button>
`;
container.appendChild(translateDiv);
}
export function createSmartTranslateFeature(container) {
const smartTranslateDiv = document.createElement("div");
smartTranslateDiv.innerHTML = `
<h4>Smart Translate</h4>
<button onclick="handleSmartTranslate(this)" style="width: 100%; padding: 6px;">🤖 Smart Translate</button>
`;
container.appendChild(smartTranslateDiv);
}
export function createReadFeature(container) {
const readDiv = document.createElement("div");
readDiv.innerHTML = `
@ -611,6 +623,156 @@ export async function handleUploadFromElement(button) {
}
}
export async function handleSmartTranslate(button) {
// Check if dropdown already exists
const existingDropdown = button.parentElement.querySelector('.translate-dropdown');
if (existingDropdown) {
existingDropdown.remove();
return;
}
// Detect page language
button.textContent = "🔍 Detecting...";
button.disabled = true;
try {
const currentLang = await detectPageLanguage();
button.textContent = "🤖 Smart Translate";
button.disabled = false;
// Import supported languages and native names
const { SUPPORTED_LANGUAGES, NATIVE_LANGUAGE_NAMES, translateCurrentPage } = await import('./translation.js');
const colors = getThemeColors();
// Create dropdown
const dropdown = document.createElement('div');
dropdown.className = 'translate-dropdown';
dropdown.style.cssText = `
position: absolute;
top: 100%;
left: 0;
background: ${colors.panelBackground};
border: 1px solid ${colors.borderColor};
border-radius: 8px;
box-shadow: 0 4px 12px ${colors.shadowColor};
z-index: 1000;
min-width: 200px;
max-height: 300px;
overflow-y: auto;
margin-top: 4px;
`;
// Add current language indicator
const currentLangDiv = document.createElement('div');
currentLangDiv.style.cssText = `
padding: 8px 12px;
font-size: 12px;
color: ${colors.mutedText};
border-bottom: 1px solid ${colors.dividerColor};
background: ${colors.contentBackground};
`;
const currentLangName = NATIVE_LANGUAGE_NAMES[currentLang] || SUPPORTED_LANGUAGES[currentLang] || 'Unknown';
currentLangDiv.textContent = `Current page: ${currentLangName}`;
dropdown.appendChild(currentLangDiv);
// Add translation options with code, English, and native names
Object.entries(NATIVE_LANGUAGE_NAMES).forEach(([code, nativeName]) => {
// Skip current language
if (code === currentLang) return;
const englishName = SUPPORTED_LANGUAGES[code];
const option = document.createElement('div');
option.style.cssText = `
padding: 8px 12px;
cursor: pointer;
transition: background-color 0.2s;
color: ${colors.primaryText};
font-size: 14px;
line-height: 1.4;
`;
// Format: "es • Spanish • Español"
option.innerHTML = `
<div style="display: flex; align-items: center; gap: 8px;">
<span style="font-family: monospace; color: ${colors.mutedText}; font-size: 12px; min-width: 24px;">${code}</span>
<span style="color: ${colors.mutedText};"></span>
<span style="color: ${colors.secondaryText};">${englishName}</span>
<span style="color: ${colors.mutedText};"></span>
<span style="font-weight: 500;">${nativeName}</span>
</div>
`;
option.onmouseenter = () => {
option.style.background = colors.buttonHover;
};
option.onmouseleave = () => {
option.style.background = 'transparent';
};
option.onclick = async () => {
try {
// Show loading
option.innerHTML = `
<div style="display: flex; align-items: center; gap: 8px;">
<span style="font-family: monospace; color: ${colors.mutedText}; font-size: 12px; min-width: 24px;">${code}</span>
<span style="color: ${colors.mutedText};"></span>
<span style="color: ${colors.secondaryText};">Translating...</span>
</div>
`;
option.style.pointerEvents = 'none';
// Translate the page
const translatedHtml = await translateCurrentPage(code);
// Open in new tab
const newWindow = window.open('', '_blank');
newWindow.document.write(translatedHtml);
newWindow.document.close();
newWindow.document.title = `${document.title} (${name})`;
// Close dropdown
dropdown.remove();
} catch (error) {
alert(`Translation failed: ${error.message}`);
// Restore original format
option.innerHTML = `
<div style="display: flex; align-items: center; gap: 8px;">
<span style="font-family: monospace; color: ${colors.mutedText}; font-size: 12px; min-width: 24px;">${code}</span>
<span style="color: ${colors.mutedText};"></span>
<span style="color: ${colors.secondaryText};">${englishName}</span>
<span style="color: ${colors.mutedText};"></span>
<span style="font-weight: 500;">${nativeName}</span>
</div>
`;
option.style.pointerEvents = 'auto';
}
};
dropdown.appendChild(option);
});
// Position dropdown relative to button
button.style.position = 'relative';
button.parentElement.appendChild(dropdown);
// Close dropdown when clicking outside
const closeDropdown = (e) => {
if (!dropdown.contains(e.target) && e.target !== button) {
dropdown.remove();
document.removeEventListener('click', closeDropdown);
}
};
setTimeout(() => document.addEventListener('click', closeDropdown), 100);
} catch (error) {
button.textContent = "🤖 Smart Translate";
button.disabled = false;
alert(`Language detection failed: ${error.message}`);
}
}
// Initialize the legacy chat interface (for backward compatibility)
export function initializeChatInterface() {
// Only initialize if there are legacy elements (chat-container, user-input, etc.)
@ -1406,7 +1568,8 @@ async function openUncloseaiEmbeddedModalNew() {
}
}},
{ text: "🔊 TTS Anything", action: (btn) => window.openTTSModal() },
{ text: "🌐 Translate", action: async (btn) => {
{ text: "🌐 Translation Modal", action: (btn) => window.openTranslateModal() },
{ text: "🤖 Smart Translate", action: async (btn) => {
// Create dropdown instead of opening modal
if (btn.nextElementSibling && btn.nextElementSibling.classList.contains('translate-dropdown')) {
// Toggle existing dropdown
@ -1420,7 +1583,7 @@ async function openUncloseaiEmbeddedModalNew() {
try {
const currentLang = await detectPageLanguage();
btn.textContent = "🌐 Translate";
btn.textContent = "🤖 Smart Translate";
btn.disabled = false;
// Import supported languages
@ -1521,7 +1684,7 @@ async function openUncloseaiEmbeddedModalNew() {
setTimeout(() => document.addEventListener('click', closeDropdown), 100);
} catch (error) {
btn.textContent = "🌐 Translate";
btn.textContent = "🤖 Smart Translate";
btn.disabled = false;
alert(`Language detection failed: ${error.message}`);
}

View file

@ -113,6 +113,7 @@ window.showProgressIndicator = FileUpload.showProgressIndicator;
window.hideProgressIndicator = FileUpload.hideProgressIndicator;
window.handleTTSFromElement = UI.handleTTSFromElement;
window.handleUploadFromElement = UI.handleUploadFromElement;
window.handleSmartTranslate = UI.handleSmartTranslate;
// Initialize on page load
window.addEventListener("load", () => {