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:
parent
6a4e9f9f72
commit
cae51b3578
5 changed files with 245 additions and 23 deletions
60
demo.html
60
demo.html
|
|
@ -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"><div class="uncloseai" data-features="translate"></div></code></pre>
|
||||
<summary><strong>Translation Code</strong></summary>
|
||||
<p>Choose between two translation approaches:</p>
|
||||
<pre><code class="html"><!-- Traditional Modal (full options) -->
|
||||
<div class="uncloseai" data-features="translate"></div>
|
||||
|
||||
<!-- Smart Dropdown (quick translate) -->
|
||||
<div class="uncloseai" data-features="smart-translate"></div>
|
||||
|
||||
<!-- Both options together -->
|
||||
<div class="uncloseai" data-features="translate,smart-translate"></div></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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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"><div class="uncloseai" data-features="tts,translate,read"></div>
|
||||
<div class="uncloseai" data-features="translate"></div></code></pre>
|
||||
<div class="uncloseai" data-features="translate"></div>
|
||||
<div class="uncloseai" data-features="smart-translate"></div></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>
|
||||
|
|
|
|||
|
|
@ -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
173
src/ui.js
|
|
@ -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}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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", () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue