// Clone by Asif Ur Rehman Shaik of:
// "Character recognition neural network (clone by Asif Ur Rehman Shaik)" by Asif Ur Rehman Shaik
// https://ancientbrain.com/world.php?world=1947554267
// Please leave this clone trail here.
// Clone by Asif Ur Rehman Shaik of:
// "Character recognition neural network" by "Coding Train" project
// https://ancientbrain.com/world.php?world=2337620282
// Please leave this clone trail here.
// Port of Character recognition neural network from here:
// https://github.com/CodingTrain/Toy-Neural-Network-JS/tree/master/examples/mnist
// with many modifications
/*
Single-file Translation API Accuracy Tester
- Only this file is needed
- How to run: Load this JS in any browser environment (e.g., Ancient Brain world, or a minimal HTML wrapper).
- No server required. The script renders all UI dynamically.
All HTML/CSS/JS live here inside clientApp().
*/
// -----------------------
// Client-side application (runs in the browser)
// -----------------------
function clientApp() {
'use strict';
// --- defined by app - do not change these ---------------------------------------
// --- can modify all these --------------------------------------------------------
const DEFAULT_OPENROUTER_MODEL = 'qwen/qwen-2.5-7b-instruct';
const SIMILARITY_THRESHOLD = 70; // percent
const OPENROUTER_TEMPERATURE = 0.2;
const OPENROUTER_MAX_TOKENS = 128;
const GEMINI_URL = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent';
const OPENROUTER_URL = 'https://openrouter.ai/api/v1/chat/completions';
// Built-in test cases (no external data.json needed)
const builtInTests = [
{ id: 1, english: 'Hello', spanish: 'Hola', category: 'greetings', difficulty: 'easy' },
{ id: 2, english: 'Good morning', spanish: 'Buenos días', category: 'greetings', difficulty: 'easy' },
{ id: 3, english: 'How are you?', spanish: '¿Cómo estás?', category: 'greetings', difficulty: 'easy' },
{ id: 4, english: 'See you tomorrow', spanish: 'Nos vemos mañana', category: 'farewell', difficulty: 'easy' },
{ id: 5, english: 'I would like a cup of coffee, please.', spanish: 'Me gustaría una taza de café, por favor.', category: 'food', difficulty: 'medium' },
{ id: 6, english: 'The weather today is sunnier than yesterday.', spanish: 'El clima de hoy es más soleado que el de ayer.', category: 'weather', difficulty: 'medium' },
{ id: 7, english: 'She has been studying Spanish for three years.', spanish: 'Ella ha estado estudiando español durante tres años.', category: 'education', difficulty: 'hard' },
{ id: 8, english: 'Where is the nearest train station?', spanish: '¿Dónde está la estación de tren más cercana?', category: 'travel', difficulty: 'medium' },
{ id: 9, english: 'I need help with my homework.', spanish: 'Necesito ayuda con mi tarea.', category: 'education', difficulty: 'easy' },
{ id: 10, english: 'What time does the store close?', spanish: '¿A qué hora cierra la tienda?', category: 'shopping', difficulty: 'easy' },
{ id: 11, english: 'Please speak more slowly.', spanish: 'Por favor, hable más despacio.', category: 'conversation', difficulty: 'easy' },
{ id: 12, english: 'I am looking for a pharmacy.', spanish: 'Estoy buscando una farmacia.', category: 'travel', difficulty: 'easy' },
{ id: 13, english: 'Can you recommend a good restaurant?', spanish: '¿Puede recomendarme un buen restaurante?', category: 'food', difficulty: 'medium' },
{ id: 14, english: 'It’s raining but I forgot my umbrella.', spanish: 'Está lloviendo pero olvidé mi paraguas.', category: 'weather', difficulty: 'medium' },
{ id: 15, english: 'I will call you later this evening.', spanish: 'Te llamaré más tarde esta noche.', category: 'conversation', difficulty: 'easy' },
{ id: 16, english: 'They have already finished their project.', spanish: 'Ya han terminado su proyecto.', category: 'work', difficulty: 'medium' },
{ id: 17, english: 'He doesn’t like to wake up early.', spanish: 'No le gusta despertarse temprano.', category: 'daily', difficulty: 'easy' },
{ id: 18, english: 'We are thinking about moving next year.', spanish: 'Estamos pensando mudarnos el próximo año.', category: 'family', difficulty: 'medium' },
{ id: 19, english: 'My flight was delayed due to fog.', spanish: 'Mi vuelo se retrasó por la niebla.', category: 'travel', difficulty: 'medium' },
{ id: 20, english: 'The book was better than the movie.', spanish: 'El libro fue mejor que la película.', category: 'entertainment', difficulty: 'easy' },
{ id: 21, english: 'I prefer tea without sugar.', spanish: 'Prefiero el té sin azúcar.', category: 'food', difficulty: 'easy' },
{ id: 22, english: 'The children are playing in the park.', spanish: 'Los niños están jugando en el parque.', category: 'daily', difficulty: 'easy' },
{ id: 23, english: 'Turn left at the second traffic light.', spanish: 'Gira a la izquierda en el segundo semáforo.', category: 'directions', difficulty: 'medium' },
{ id: 24, english: 'She forgot where she parked the car.', spanish: 'Ella olvidó dónde aparcó el coche.', category: 'daily', difficulty: 'medium' },
{ id: 25, english: 'Do you accept credit cards?', spanish: '¿Acepta tarjetas de crédito?', category: 'shopping', difficulty: 'easy' },
{ id: 26, english: 'I am allergic to peanuts.', spanish: 'Soy alérgico a los cacahuetes.', category: 'health', difficulty: 'easy' },
{ id: 27, english: 'This app needs an internet connection.', spanish: 'Esta aplicación necesita una conexión a internet.', category: 'technology', difficulty: 'easy' },
{ id: 28, english: 'We should arrive before noon.', spanish: 'Deberíamos llegar antes del mediodía.', category: 'travel', difficulty: 'easy' },
{ id: 29, english: 'Thank you for your patience.', spanish: 'Gracias por su paciencia.', category: 'conversation', difficulty: 'easy' },
{ id: 30, english: 'Could you please open the window?', spanish: '¿Podría abrir la ventana, por favor?', category: 'conversation', difficulty: 'easy' }
];
// Global-like state (scoped to this function)
let testCases = builtInTests.slice();
let currentTestIndex = 0;
let results = {
gemini: { correct: 0, total: 0, responses: [] },
openrouter: { correct: 0, total: 0, responses: [] }
};
let geminiKey = '';
let openrouterKey = '';
let openrouterModel = localStorage.getItem('openrouterModel') || DEFAULT_OPENROUTER_MODEL;
let isRunning = false;
// Application Initialization
async function initializeApp() {
console.log('Initializing Translation API Tester...');
// Load keys from localStorage (no automatic prompts)
loadKeysFromStorage();
// Use built-in tests
testCases = builtInTests.slice();
// Render UI into the container (all markup and styling lives in JS)
renderUI();
wireEvents();
wireDelegation();
updateKeyUI();
displayCurrentTest();
}
// Render the full UI into #appContainer (HTML + inline styles)
function renderUI() {
let container = document.getElementById('appContainer');
if (!container) {
container = document.createElement('div');
container.id = 'appContainer';
// Ensure the UI stays clickable above potential canvases/overlays (e.g., Ancient Brain)
try {
const host = (window.location && window.location.hostname) || '';
if (host.includes('ancientbrain.com')) {
container.style.position = 'fixed';
container.style.top = '0';
container.style.left = '0';
container.style.width = '100%';
// Make overlay scrollable so long content (like Results) can be viewed
container.style.height = '100vh';
container.style.overflowY = 'auto';
container.style.webkitOverflowScrolling = 'touch';
container.style.zIndex = '2147483647'; // max
} else {
container.style.position = 'relative';
container.style.zIndex = '99999';
}
} catch(_) {
container.style.position = 'relative';
container.style.zIndex = '99999';
}
container.style.pointerEvents = 'auto';
document.body.appendChild(container);
}
container.innerHTML = `
<div style="max-width: 900px; margin: 0 auto; padding: 16px; font-family: Arial, sans-serif; color:#222;">
<h1 style="margin:0 0 8px 0; font-size: 22px;">Translation Tester</h1>
<p style="margin:0 0 12px 0; color:#555;">Compare Google Gemini and Qwen (OpenRouter) — English → Spanish</p>
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px; background:#fff; border:1px solid #ccc; padding:10px; border-radius:6px;">
<div>
<strong>API Keys</strong>
<div style="font-size:13px; color:#555; margin-top:6px;">
Gemini: <span id="geminiKeyStatus">—</span> · OpenRouter: <span id="openrouterKeyStatus">—</span>
</div>
<div style="font-size:12px; color:#666; margin-top:6px;">Model: <span id="orModel">${openrouterModel}</span></div>
</div>
<div style="display:flex; gap:8px;">
<button id="changeKeysBtn" style="padding:6px 10px; background:#f0f0f0; color:#222; border:1px solid #ccc; border-radius:4px; cursor:pointer;">Change Keys</button>
<button id="clearKeysBtn" style="padding:6px 10px; background:#fff; color:#c00; border:1px solid #ccc; border-radius:4px; cursor:pointer;">Clear</button>
</div>
</div>
<div id="progressBar" style="background: #eee; height: 12px; border-radius: 4px; margin: 14px 0; overflow: hidden;">
<div id="progress" style="background: #4CAF50; height: 100%; width: 0%; transition: width 0.2s;"></div>
</div>
<div id="testContainer" style="background: #fafafa; padding: 12px; border: 1px solid #ddd; border-radius: 6px; margin: 14px 0;">
<h3 id="testNumber">Test 0/${testCases.length}</h3>
<div style="background: white; padding: 10px; border-radius: 4px; border:1px solid #eee; margin: 10px 0;">
<p><strong>English:</strong> <span id="englishText"></span></p>
<p><strong>Correct Spanish:</strong> <span id="spanishText" style="color: green; font-weight: bold;"></span></p>
<p><strong>Difficulty:</strong> <span id="difficulty"></span></p>
<p><strong>Category:</strong> <span id="category"></span></p>
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin: 14px 0;">
<!-- Google Gemini -->
<div style="border: 1px solid #ccc; padding: 10px; border-radius: 6px;">
<h3 style="margin:0 0 8px 0;">Google Gemini</h3>
<div id="geminiLoading" style="text-align: center; padding: 20px;">
<p>Waiting to test...</p>
</div>
<div id="geminiResult" style="display: none;">
<p><strong>Response:</strong></p>
<p id="geminiResponse" style="background: #f7f7f7; padding: 8px; border-radius: 4px; border:1px solid #eee;"></p>
<p><strong>Match:</strong> <span id="geminiMatch" style="font-weight: bold;"></span></p>
<p><strong>Confidence:</strong> <span id="geminiConfidence"></span></p>
</div>
</div>
<!-- Second Provider -->
<div style="border: 1px solid #ccc; padding: 10px; border-radius: 6px;">
<h3 style="margin:0 0 8px 0;">OpenRouter (Qwen)</h3>
<div id="openrouterLoading" style="text-align: center; padding: 20px;">
<p>Waiting to test...</p>
</div>
<div id="openrouterResult" style="display: none;">
<p><strong>Response:</strong></p>
<p id="openrouterResponse" style="background: #f7f7f7; padding: 8px; border-radius: 4px; border:1px solid #eee;"></p>
<p><strong>Match:</strong> <span id="openrouterMatch" style="font-weight: bold;"></span></p>
<p><strong>Confidence:</strong> <span id="openrouterConfidence"></span></p>
</div>
</div>
</div>
<div style="text-align: center; margin: 14px 0;">
<button id="testBtn" style="padding: 8px 18px; font-size: 14px; background: #f0f0f0; color:#222; border:1px solid #ccc; border-radius: 4px; cursor: pointer; margin-right: 8px;">Test</button>
<button id="runAllBtn" style="padding: 8px 18px; font-size: 14px; background: #f0f0f0; color:#222; border:1px solid #ccc; border-radius: 4px; cursor: pointer; margin-right: 8px;">Run All</button>
<button id="nextBtn" style="padding: 8px 18px; font-size: 14px; background: #f0f0f0; color:#222; border:1px solid #ccc; border-radius: 4px; cursor: pointer; margin-right: 8px; display: none;">Next</button>
<button id="resultsBtn" style="padding: 8px 18px; font-size: 14px; background: #fff; color:#222; border:1px solid #ccc; border-radius: 4px; cursor: pointer;">Results</button>
</div>
<div id="resultsContainer" style="display: none; background: #fafafa; padding: 12px; border: 1px solid #ddd; border-radius: 6px; margin-top: 14px; max-height: 70vh; overflow-y: auto;">
<h2 style="margin:0 0 10px 0; font-size:18px;">Results</h2>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
<div style="background: white; padding: 10px; border:1px solid #eee; border-radius: 4px;">
<h3 style="margin:0 0 8px 0;">Google Gemini</h3>
<p><span id="geminiScore" style="font-size: 18px; font-weight: bold;"></span></p>
<p>Accuracy: <span id="geminiAccuracy" style="font-weight: bold;"></span>%</p>
</div>
<div style="background: white; padding: 10px; border:1px solid #eee; border-radius: 4px;">
<h3 style="margin:0 0 8px 0;">OpenRouter (Qwen)</h3>
<p><span id="openrouterScore" style="font-size: 18px; font-weight: bold;"></span></p>
<p>Accuracy: <span id="openrouterAccuracy" style="font-weight: bold;"></span>%</p>
</div>
</div>
<div style="margin-top: 12px;">
<h3>Detailed Results</h3>
<div id="detailedResults"></div>
</div>
</div>
<!-- Simple Keys Modal -->
<div id="keysModalBackdrop" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.4); z-index:2147483647;">
<div id="keysModal" style="background:#fff; width:90%; max-width:420px; margin:10% auto; padding:16px; border:1px solid #ccc; border-radius:6px; box-shadow:0 2px 8px rgba(0,0,0,0.15);">
<h3 style="margin-top:0;">Set API Keys</h3>
<div style="display:flex; flex-direction:column; gap:8px;">
<label style="font-size:13px;">Gemini API Key
<input id="geminiKeyInput" type="text" style="width:100%; padding:6px; border:1px solid #ccc; border-radius:4px;" />
</label>
<label style="font-size:13px;">OpenRouter API Key
<input id="openrouterKeyInput" type="text" style="width:100%; padding:6px; border:1px solid #ccc; border-radius:4px;" />
</label>
<label style="font-size:13px;">OpenRouter Model
<input id="openrouterModelInput" type="text" value="${openrouterModel}" style="width:100%; padding:6px; border:1px solid #ccc; border-radius:4px;" />
</label>
</div>
<div style="text-align:right; margin-top:12px;">
<button id="keysCancelBtn" style="padding:6px 10px; background:#fff; color:#222; border:1px solid #ccc; border-radius:4px; margin-right:8px; cursor:pointer;">Cancel</button>
<button id="keysSaveBtn" style="padding:6px 10px; background:#f0f0f0; color:#222; border:1px solid #ccc; border-radius:4px; cursor:pointer;">Save</button>
</div>
</div>
</div>
</div>
`;
}
// Wire up events without inline handlers (robust across CSP/sandboxes)
function wireEvents() {
const changeKeysBtn = document.getElementById('changeKeysBtn');
const clearKeysBtn = document.getElementById('clearKeysBtn');
const testBtn = document.getElementById('testBtn');
const runAllBtn = document.getElementById('runAllBtn');
const nextBtn = document.getElementById('nextBtn');
const resultsBtn = document.getElementById('resultsBtn');
const keysSaveBtn = document.getElementById('keysSaveBtn');
const keysCancelBtn = document.getElementById('keysCancelBtn');
changeKeysBtn && changeKeysBtn.addEventListener('click', openKeysModal);
clearKeysBtn && clearKeysBtn.addEventListener('click', clearKeys);
testBtn && testBtn.addEventListener('click', testCurrentSentence);
runAllBtn && runAllBtn.addEventListener('click', runAllTestsSequential);
nextBtn && nextBtn.addEventListener('click', nextTest);
resultsBtn && resultsBtn.addEventListener('click', showResults);
keysSaveBtn && keysSaveBtn.addEventListener('click', saveKeysFromModal);
keysCancelBtn && keysCancelBtn.addEventListener('click', closeKeysModal);
}
// Fallback: Delegate clicks from the document in capture phase to beat overlays
function wireDelegation() {
document.addEventListener(
'click',
(ev) => {
const target = ev.target && ev.target.closest ? ev.target.closest('#changeKeysBtn, #clearKeysBtn, #testBtn, #runAllBtn, #nextBtn, #resultsBtn, #keysSaveBtn, #keysCancelBtn') : null;
if (!target) return;
switch (target.id) {
case 'changeKeysBtn':
openKeysModal();
break;
case 'clearKeysBtn':
clearKeys();
break;
case 'testBtn':
testCurrentSentence();
break;
case 'runAllBtn':
runAllTestsSequential();
break;
case 'nextBtn':
nextTest();
break;
case 'resultsBtn':
showResults();
break;
case 'keysSaveBtn':
saveKeysFromModal();
break;
case 'keysCancelBtn':
closeKeysModal();
break;
}
},
true
);
}
// Key storage helpers
function loadKeysFromStorage() {
const g = localStorage.getItem('geminiKey');
const h = localStorage.getItem('openrouterKey');
if (g) geminiKey = g;
if (h) openrouterKey = h;
return Boolean(g && h);
}
function saveKeysToStorage() {
if (geminiKey) localStorage.setItem('geminiKey', geminiKey);
if (openrouterKey) localStorage.setItem('openrouterKey', openrouterKey);
if (openrouterModel) localStorage.setItem('openrouterModel', openrouterModel);
}
function maskKey(key) {
if (!key || key.length < 8) return key ? '••••' : '—';
return `${key.slice(0, 4)}•••${key.slice(-4)}`;
}
async function promptForKeys() {
const g = prompt('Enter your Google Gemini API Key:');
const h = prompt('Enter your OpenRouter API Key (free-tier supported):');
const m = prompt('OpenRouter Model (e.g., qwen/qwen-2.5-7b-instruct):', openrouterModel || 'qwen/qwen-2.5-7b-instruct');
if (g && h) {
geminiKey = g.trim();
openrouterKey = h.trim();
if (m) openrouterModel = m.trim();
saveKeysToStorage();
return true;
}
return false;
}
function editKeys() {
const g = prompt('Update Google Gemini API Key:', geminiKey || '');
const h = prompt('Update OpenRouter API Key:', openrouterKey || '');
const m = prompt('Update OpenRouter Model:', openrouterModel || 'qwen/qwen-2.5-7b-instruct');
if (g && h) {
geminiKey = g.trim();
openrouterKey = h.trim();
openrouterModel = m ? m.trim() : openrouterModel;
saveKeysToStorage();
updateKeyUI();
alert('Keys updated.');
} else {
alert('Both keys are required.');
}
}
function clearKeys() {
localStorage.removeItem('geminiKey');
localStorage.removeItem('openrouterKey');
localStorage.removeItem('openrouterModel');
geminiKey = '';
openrouterKey = '';
openrouterModel = DEFAULT_OPENROUTER_MODEL;
updateKeyUI();
alert('Saved keys cleared.');
}
// Simple modal helpers
function openKeysModal() {
const back = document.getElementById('keysModalBackdrop');
const gIn = document.getElementById('geminiKeyInput');
const hIn = document.getElementById('openrouterKeyInput');
const mIn = document.getElementById('openrouterModelInput');
if (!back || !gIn || !hIn || !mIn) return;
gIn.value = geminiKey || '';
hIn.value = openrouterKey || '';
mIn.value = openrouterModel || DEFAULT_OPENROUTER_MODEL;
back.style.display = 'block';
setTimeout(() => gIn.focus(), 0);
}
function closeKeysModal() {
const back = document.getElementById('keysModalBackdrop');
if (back) back.style.display = 'none';
}
function saveKeysFromModal() {
const gIn = document.getElementById('geminiKeyInput');
const hIn = document.getElementById('openrouterKeyInput');
const mIn = document.getElementById('openrouterModelInput');
if (!gIn || !hIn || !mIn) return;
geminiKey = (gIn.value || '').trim();
openrouterKey = (hIn.value || '').trim();
openrouterModel = (mIn.value || DEFAULT_OPENROUTER_MODEL).trim();
saveKeysToStorage();
updateKeyUI();
closeKeysModal();
alert('Keys saved.');
}
// UI helpers
function updateKeyUI() {
const gEl = document.getElementById('geminiKeyStatus');
const hEl = document.getElementById('openrouterKeyStatus');
const mEl = document.getElementById('orModel');
if (!gEl || !hEl) return;
gEl.textContent = geminiKey ? `Saved (${maskKey(geminiKey)})` : 'Missing';
hEl.textContent = openrouterKey ? `Saved (${maskKey(openrouterKey)})` : 'Missing';
if (mEl) mEl.textContent = openrouterModel || 'qwen/qwen-2.5-7b-instruct';
}
function displayCurrentTest() {
const test = testCases[currentTestIndex];
document.getElementById('testNumber').textContent = `Test ${currentTestIndex + 1}/${testCases.length}`;
document.getElementById('englishText').textContent = test.english;
document.getElementById('spanishText').textContent = test.spanish;
document.getElementById('difficulty').textContent = test.difficulty.toUpperCase();
document.getElementById('category').textContent = test.category.replace(/_/g, ' ');
const progress = ((currentTestIndex + 1) / testCases.length) * 100;
document.getElementById('progress').style.width = progress + '%';
// reset UI
document.getElementById('geminiLoading').style.display = 'block';
document.getElementById('geminiResult').style.display = 'none';
document.getElementById('openrouterLoading').style.display = 'block';
document.getElementById('openrouterResult').style.display = 'none';
document.getElementById('testBtn').style.display = 'inline-block';
document.getElementById('nextBtn').style.display = 'none';
}
// API calls
async function callGeminiAPI(englishText) {
const url = GEMINI_URL;
const prompt = `Translate this English sentence to Spanish. ONLY provide the Spanish translation, nothing else:\n\n${englishText}`;
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-goog-api-key': geminiKey
},
body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] })
});
if (!response.ok) {
const msg = await response.text().catch(() => response.statusText);
throw new Error(msg || 'Gemini request failed');
}
const data = await response.json();
const translation = data?.candidates?.[0]?.content?.parts?.[0]?.text?.trim?.();
if (!translation) {
const msg = data?.error?.message || 'No translation returned';
throw new Error(msg);
}
return { translation, error: null };
} catch (error) {
return { translation: null, error: error?.message || String(error) };
}
}
async function callOpenRouterAPI(englishText) {
if (!openrouterKey) {
return { translation: null, error: 'OpenRouter API key missing. Click "Change keys" to set it.' };
}
const url = OPENROUTER_URL;
const system = 'You are a translation engine. Translate the user\'s English input into Spanish. Return ONLY the Spanish translation, no quotes or extra text.';
try {
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${openrouterKey}`,
'HTTP-Referer': window.location.origin,
'X-Title': 'Assignment-APIs Translation Tester'
},
body: JSON.stringify({
model: openrouterModel || DEFAULT_OPENROUTER_MODEL,
messages: [
{ role: 'system', content: system },
{ role: 'user', content: englishText }
],
temperature: OPENROUTER_TEMPERATURE,
max_tokens: OPENROUTER_MAX_TOKENS
})
});
if (!res.ok) {
const msg = await res.text().catch(() => res.statusText);
throw new Error(msg || 'OpenRouter request failed');
}
const data = await res.json();
const text = data?.choices?.[0]?.message?.content?.trim?.();
if (!text) {
const err = data?.error?.message || 'No content returned';
throw new Error(err);
}
return { translation: text, error: null };
} catch (err) {
return { translation: null, error: err?.message || String(err) };
}
}
// Results processing
function calculateSimilarity(str1, str2) {
const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9\sáéíóúñü¿¡]/g, '').trim();
const s1 = normalize(str1).split(/\s+/);
const s2 = normalize(str2).split(/\s+/);
const matches = s1.filter(word => s2.includes(word)).length;
const similarity = (matches / Math.max(s1.length, s2.length)) * 100;
return Math.round(similarity);
}
function processGeminiResult(result, test) {
const container = document.getElementById('geminiResult');
const loadingDiv = document.getElementById('geminiLoading');
loadingDiv.style.display = 'none';
container.style.display = 'block';
if (result.error) {
document.getElementById('geminiResponse').textContent = '❌ Error: ' + result.error;
document.getElementById('geminiMatch').textContent = 'ERROR';
document.getElementById('geminiConfidence').textContent = 'N/A';
results.gemini.responses.push({ test: test.id, response: 'ERROR', correct: false });
} else {
const similarity = calculateSimilarity(result.translation, test.spanish);
const isCorrect = similarity >= SIMILARITY_THRESHOLD;
document.getElementById('geminiResponse').textContent = result.translation;
document.getElementById('geminiMatch').textContent = isCorrect ? '✅ CORRECT' : '❌ INCORRECT';
document.getElementById('geminiMatch').style.color = isCorrect ? 'green' : 'red';
document.getElementById('geminiConfidence').textContent = `${similarity}% match`;
if (isCorrect) results.gemini.correct++;
results.gemini.total++;
results.gemini.responses.push({ test: test.id, response: result.translation, correct: isCorrect, similarity });
}
}
function processOpenRouterResult(result, test) {
const container = document.getElementById('openrouterResult');
const loadingDiv = document.getElementById('openrouterLoading');
loadingDiv.style.display = 'none';
container.style.display = 'block';
if (result.error) {
document.getElementById('openrouterResponse').textContent = '❌ Error: ' + result.error;
document.getElementById('openrouterMatch').textContent = 'ERROR';
document.getElementById('openrouterConfidence').textContent = 'N/A';
results.openrouter.responses.push({ test: test.id, response: 'ERROR', correct: false });
} else {
const similarity = calculateSimilarity(result.translation, test.spanish);
const isCorrect = similarity >= SIMILARITY_THRESHOLD;
document.getElementById('openrouterResponse').textContent = result.translation;
document.getElementById('openrouterMatch').textContent = isCorrect ? '✅ CORRECT' : '❌ INCORRECT';
document.getElementById('openrouterMatch').style.color = isCorrect ? 'green' : 'red';
document.getElementById('openrouterConfidence').textContent = `${similarity}% match`;
if (isCorrect) results.openrouter.correct++;
results.openrouter.total++;
results.openrouter.responses.push({ test: test.id, response: result.translation, correct: isCorrect, similarity });
}
}
// Shared single-test executor
async function performTestForIndex(index) {
const test = testCases[index];
const [geminiResult, orResult] = await Promise.all([
callGeminiAPI(test.english),
callOpenRouterAPI(test.english)
]);
processGeminiResult(geminiResult, test);
processOpenRouterResult(orResult, test);
}
// Flow actions (manual single test)
async function testCurrentSentence() {
if (isRunning) return;
isRunning = true;
const testBtn = document.getElementById('testBtn');
if (testBtn) testBtn.disabled = true;
try {
await performTestForIndex(currentTestIndex);
} catch (error) {
alert('Error: ' + (error?.message || error));
}
if (testBtn) testBtn.disabled = false;
const nextBtn = document.getElementById('nextBtn');
if (nextBtn) nextBtn.style.display = 'inline-block';
isRunning = false;
}
// Run all tests sequentially
async function runAllTestsSequential() {
if (isRunning) return;
isRunning = true;
const testBtn = document.getElementById('testBtn');
const runAllBtn = document.getElementById('runAllBtn');
const nextBtn = document.getElementById('nextBtn');
if (testBtn) testBtn.disabled = true;
if (runAllBtn) runAllBtn.disabled = true;
if (nextBtn) nextBtn.style.display = 'none';
try {
for (let i = currentTestIndex; i < testCases.length; i++) {
currentTestIndex = i;
displayCurrentTest();
await performTestForIndex(i);
if (nextBtn) nextBtn.style.display = 'none';
await new Promise((r) => setTimeout(r, 200));
}
showResults();
} catch (e) {
alert('Run all failed: ' + (e?.message || e));
}
if (testBtn) testBtn.disabled = false;
if (runAllBtn) runAllBtn.disabled = false;
isRunning = false;
}
function nextTest() {
if (currentTestIndex < testCases.length - 1) {
currentTestIndex++;
displayCurrentTest();
} else {
alert('All tests completed! Click "Show Results" to see summary.');
}
}
function showResults() {
const resultsDiv = document.getElementById('resultsContainer');
resultsDiv.style.display = 'block';
const geminiAccuracy = results.gemini.total > 0 ? Math.round((results.gemini.correct / results.gemini.total) * 100) : 0;
const openrouterAccuracy = results.openrouter.total > 0 ? Math.round((results.openrouter.correct / results.openrouter.total) * 100) : 0;
document.getElementById('geminiScore').textContent = `${results.gemini.correct}/${results.gemini.total}`;
document.getElementById('geminiAccuracy').textContent = geminiAccuracy;
document.getElementById('openrouterScore').textContent = `${results.openrouter.correct}/${results.openrouter.total}`;
document.getElementById('openrouterAccuracy').textContent = openrouterAccuracy;
let detailedHTML = `
<table style="width: 100%; border-collapse: collapse;">
<tr style="background: #ddd;">
<th style="border: 1px solid #999; padding: 8px;">Test #</th>
<th style="border: 1px solid #999; padding: 8px;">English</th>
<th style="border: 1px solid #999; padding: 8px;">Correct Spanish</th>
<th style="border: 1px solid #999; padding: 8px;">Gemini Result</th>
<th style="border: 1px solid #999; padding: 8px;">Qwen (OpenRouter) Result</th>
</tr>
`;
for (let i = 0; i < testCases.length; i++) {
const test = testCases[i];
const gemini = results.gemini.responses[i];
const hf = results.openrouter.responses[i];
detailedHTML += `
<tr>
<td style="border: 1px solid #999; padding: 8px;">${test.id}</td>
<td style="border: 1px solid #999; padding: 8px;">${test.english}</td>
<td style="border: 1px solid #999; padding: 8px;"><strong>${test.spanish}</strong></td>
<td style="border: 1px solid #999; padding: 8px;">${gemini ? (gemini.correct ? '✅' : '❌') + ' ' + gemini.response : '—'}</td>
<td style="border: 1px solid #999; padding: 8px;">${hf ? (hf.correct ? '✅' : '❌') + ' ' + hf.response : '—'}</td>
</tr>
`;
}
detailedHTML += '</table>';
document.getElementById('detailedResults').innerHTML = detailedHTML;
// Prefer scrolling the app container (overlay) if it's scrollable (Ancient Brain)
const app = document.getElementById('appContainer');
if (app && typeof app.scrollTo === 'function') {
try {
app.scrollTo({ top: resultsDiv.offsetTop - 12, behavior: 'smooth' });
} catch (_) {
resultsDiv.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
} else {
resultsDiv.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}
// Expose handlers used by inline onclick attributes
Object.assign(window, { editKeys, clearKeys, testCurrentSentence, nextTest, showResults });
// Start after DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => initializeApp());
} else {
// DOM already ready
initializeApp();
}
}
// If running in a browser directly (no host framework), auto-run the app
if (typeof window !== 'undefined') {
// Immediately invoke clientApp to set up event listeners and render UI
try { clientApp(); } catch (e) { console.error('Initialization error:', e); }
}