a// 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 Sentiment Analysis 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 CONFIDENCE_THRESHOLD = 70; // percent for matching sentiment
const OPENROUTER_TEMPERATURE = 0.3;
const OPENROUTER_MAX_TOKENS = 64;
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, text: 'I absolutely love this product! Best purchase ever!', sentiment: 'positive', category: 'product_review', difficulty: 'easy' },
{ id: 2, text: 'This is the worst experience I have ever had.', sentiment: 'negative', category: 'service', difficulty: 'easy' },
{ id: 3, text: 'The weather is okay today.', sentiment: 'neutral', category: 'weather', difficulty: 'easy' },
{ id: 4, text: 'I am extremely disappointed with the quality.', sentiment: 'negative', category: 'product_review', difficulty: 'easy' },
{ id: 5, text: 'The movie was fantastic! I highly recommend it to everyone.', sentiment: 'positive', category: 'entertainment', difficulty: 'easy' },
{ id: 6, text: 'The service was neither good nor bad, just average.', sentiment: 'neutral', category: 'service', difficulty: 'medium' },
{ id: 7, text: 'While the food was decent, the service left much to be desired.', sentiment: 'mixed', category: 'restaurant', difficulty: 'medium' },
{ id: 8, text: 'I can\'t believe how amazing this is! Exceeded all my expectations!', sentiment: 'positive', category: 'general', difficulty: 'easy' },
{ id: 9, text: 'This is completely unacceptable and unprofessional.', sentiment: 'negative', category: 'business', difficulty: 'easy' },
{ id: 10, text: 'The presentation was informative but could use more engaging visuals.', sentiment: 'mixed', category: 'education', difficulty: 'medium' },
{ id: 11, text: 'It\'s fine, nothing special.', sentiment: 'neutral', category: 'general', difficulty: 'medium' },
{ id: 12, text: 'I had a great time at the event, everyone was so friendly!', sentiment: 'positive', category: 'event', difficulty: 'easy' },
{ id: 13, text: 'The app crashes constantly and the support team is unhelpful.', sentiment: 'negative', category: 'technology', difficulty: 'medium' },
{ id: 14, text: 'While I appreciate the effort, the results were underwhelming.', sentiment: 'mixed', category: 'work', difficulty: 'hard' },
{ id: 15, text: 'Absolutely terrible! Would not recommend to anyone.', sentiment: 'negative', category: 'product_review', 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 Sentiment Analysis 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;">Sentiment Analysis Tester</h1>
<p style="margin:0 0 12px 0; color:#555;">Compare Google Gemini and Qwen (OpenRouter) — Sentiment Classification</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>Text:</strong> <span id="textContent" style="font-style: italic;"></span></p>
<p><strong>Expected Sentiment:</strong> <span id="expectedSentiment" style="color: blue; font-weight: bold; text-transform: uppercase;"></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>Sentiment:</strong></p>
<p id="geminiResponse" style="background: #f7f7f7; padding: 8px; border-radius: 4px; border:1px solid #eee; text-transform: uppercase; font-weight: bold;"></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>Sentiment:</strong></p>
<p id="openrouterResponse" style="background: #f7f7f7; padding: 8px; border-radius: 4px; border:1px solid #eee; text-transform: uppercase; font-weight: bold;"></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)}`;
}
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('textContent').textContent = test.text;
document.getElementById('expectedSentiment').textContent = test.sentiment.toUpperCase();
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(text) {
const url = GEMINI_URL;
const prompt = `Analyze the sentiment of the following text. Respond with ONLY ONE WORD from these options: positive, negative, neutral, or mixed. No explanation, just the sentiment:\n\n${text}`;
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 sentiment = data?.candidates?.[0]?.content?.parts?.[0]?.text?.trim?.().toLowerCase();
if (!sentiment) {
const msg = data?.error?.message || 'No sentiment returned';
throw new Error(msg);
}
return { sentiment, error: null };
} catch (error) {
return { sentiment: null, error: error?.message || String(error) };
}
}
async function callOpenRouterAPI(text) {
if (!openrouterKey) {
return { sentiment: null, error: 'OpenRouter API key missing. Click "Change keys" to set it.' };
}
const url = OPENROUTER_URL;
const system = 'You are a sentiment analysis engine. Analyze the sentiment of the user\'s text. Respond with ONLY ONE WORD: positive, negative, neutral, or mixed. No quotes, no explanation, just the sentiment.';
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 Sentiment Tester'
},
body: JSON.stringify({
model: openrouterModel || DEFAULT_OPENROUTER_MODEL,
messages: [
{ role: 'system', content: system },
{ role: 'user', content: text }
],
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 sentimentText = data?.choices?.[0]?.message?.content?.trim?.().toLowerCase();
if (!sentimentText) {
const err = data?.error?.message || 'No content returned';
throw new Error(err);
}
return { sentiment: sentimentText, error: null };
} catch (err) {
return { sentiment: null, error: err?.message || String(err) };
}
}
// Results processing
function normalizeSentiment(sentiment) {
// Remove quotes, punctuation, and extra whitespace
const normalized = sentiment.toLowerCase().replace(/[^a-z]/g, '').trim();
// Map common variations
const mapping = {
'pos': 'positive',
'positive': 'positive',
'neg': 'negative',
'negative': 'negative',
'neut': 'neutral',
'neutral': 'neutral',
'mix': 'mixed',
'mixed': 'mixed'
};
return mapping[normalized] || normalized;
}
function calculateConfidence(predicted, expected) {
const pred = normalizeSentiment(predicted);
const exp = normalizeSentiment(expected);
// Exact match gets 100%
if (pred === exp) return 100;
// Partial matches (for mixed sentiments)
if (pred === 'mixed' || exp === 'mixed') {
// If one is mixed and other is positive/negative, give partial credit
if ((pred === 'positive' && exp === 'mixed') || (pred === 'mixed' && exp === 'positive')) return 60;
if ((pred === 'negative' && exp === 'mixed') || (pred === 'mixed' && exp === 'negative')) return 60;
}
// Neutral predictions get some credit if actual is mixed
if ((pred === 'neutral' && exp === 'mixed') || (pred === 'mixed' && exp === 'neutral')) return 40;
// Complete mismatch
return 0;
}
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, confidence: 0 });
} else {
const confidence = calculateConfidence(result.sentiment, test.sentiment);
const isCorrect = confidence >= CONFIDENCE_THRESHOLD;
const normalized = normalizeSentiment(result.sentiment);
document.getElementById('geminiResponse').textContent = normalized;
document.getElementById('geminiMatch').textContent = isCorrect ? '✅ CORRECT' : '❌ INCORRECT';
document.getElementById('geminiMatch').style.color = isCorrect ? 'green' : 'red';
document.getElementById('geminiConfidence').textContent = `${confidence}% confidence`;
if (isCorrect) results.gemini.correct++;
results.gemini.total++;
results.gemini.responses.push({ test: test.id, response: normalized, correct: isCorrect, confidence });
}
}
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, confidence: 0 });
} else {
const confidence = calculateConfidence(result.sentiment, test.sentiment);
const isCorrect = confidence >= CONFIDENCE_THRESHOLD;
const normalized = normalizeSentiment(result.sentiment);
document.getElementById('openrouterResponse').textContent = normalized;
document.getElementById('openrouterMatch').textContent = isCorrect ? '✅ CORRECT' : '❌ INCORRECT';
document.getElementById('openrouterMatch').style.color = isCorrect ? 'green' : 'red';
document.getElementById('openrouterConfidence').textContent = `${confidence}% confidence`;
if (isCorrect) results.openrouter.correct++;
results.openrouter.total++;
results.openrouter.responses.push({ test: test.id, response: normalized, correct: isCorrect, confidence });
}
}
// Shared single-test executor
async function performTestForIndex(index) {
const test = testCases[index];
const [geminiResult, orResult] = await Promise.all([
callGeminiAPI(test.text),
callOpenRouterAPI(test.text)
]);
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 "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;">Text</th>
<th style="border: 1px solid #999; padding: 8px;">Expected Sentiment</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.text}</td>
<td style="border: 1px solid #999; padding: 8px;"><strong style="text-transform: uppercase;">${test.sentiment}</strong></td>
<td style="border: 1px solid #999; padding: 8px;">${gemini ? (gemini.correct ? '✅' : '❌') + ' ' + gemini.response + ' (' + gemini.confidence + '%)' : '—'}</td>
<td style="border: 1px solid #999; padding: 8px;">${hf ? (hf.correct ? '✅' : '❌') + ' ' + hf.response + ' (' + hf.confidence + '%)' : '—'}</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, { 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); }
}