const style = document.createElement("style");
style.innerHTML = `
body {
background: radial-gradient(circle at top, #1c1c1c, #0b0b0b 70%);
font-family: 'Inter', sans-serif;
color: #eaeaea;
padding: 26px;
margin: 0;
overflow-x: hidden;
animation: bgFloat 10s ease-in-out infinite alternate;
}
@keyframes bgFloat {
0% { background-position: 0px 0px; }
100% { background-position: 20px 20px; }
}
h2, h3 {
font-weight: 700;
margin-bottom: 12px;
background: linear-gradient(90deg, #c6cbff, #727bff, #6f9dff);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
text-shadow: 0 0 14px rgba(180,180,255,0.25);
animation: headerGlow 3s ease-in-out infinite alternate;
}
@keyframes headerGlow {
from { filter: brightness(1); }
to { filter: brightness(1.4); }
}
.card {
background: rgba(255,255,255,0.06);
padding: 22px;
border-radius: 18px;
border: 1px solid rgba(255,255,255,0.12);
backdrop-filter: blur(16px);
box-shadow: 0 10px 28px rgba(0,0,0,0.45);
margin-bottom: 28px;
animation: slideFade 0.6s ease forwards;
opacity: 0;
}
@keyframes slideFade {
from { opacity: 0; transform: translateY(20px) scale(0.98); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
p {
background: rgba(0,0,0,0.32);
padding: 14px;
border-radius: 12px;
border: 1px solid rgba(255,255,255,0.14);
font-size: 1.05rem;
animation: floatText 6s ease-in-out infinite alternate;
}
@keyframes floatText {
from { transform: translateY(0px); }
to { transform: translateY(-4px); }
}
button {
background: linear-gradient(135deg, #5050ff, #6868ff, #9d78ff);
padding: 12px 20px;
color: white;
border-radius: 12px;
border: none;
margin: 8px 6px 0 0;
font-weight: 600;
cursor: pointer;
transition: transform 0.25s ease, box-shadow 0.25s ease;
box-shadow: 0 0 16px rgba(120,120,255,0.45);
position: relative;
overflow: hidden;
}
button:hover {
transform: translateY(-4px);
box-shadow: 0 0 28px rgba(160,160,255,0.9);
}
button:active::after {
content: "";
position: absolute;
left: 50%; top: 50%;
width: 0; height: 0;
background: rgba(255,255,255,0.5);
border-radius: 50%;
transform: translate(-50%, -50%);
animation: ripple 0.5s ease-out;
}
@keyframes ripple {
to { width: 180px; height: 180px; opacity: 0; }
}
#results {
background: rgba(255,255,255,0.05);
padding: 20px;
border-radius: 16px;
border: 1px solid rgba(255,255,255,0.2);
margin-bottom: 28px;
animation: popIn 0.5s ease forwards;
opacity: 0;
}
@keyframes popIn {
from { transform: scale(0.9); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
.highlight {
background: #ffe457 !important;
color: black !important;
box-shadow: 0 0 16px rgba(255,220,80,0.85);
animation: pulse 1.2s infinite ease-in-out;
}
.wrong {
background: #ff5c5c !important;
color: white !important;
box-shadow: 0 0 18px rgba(255,90,90,0.9);
animation: pulseWrong 1.1s infinite ease-in-out;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.015); }
100% { transform: scale(1); }
}
@keyframes pulseWrong {
0% { transform: translateY(0); }
50% { transform: translateY(-2px); }
100% { transform: translateY(0); }
}
#historyTableWrapper {
max-height: 260px;
overflow-y: auto;
background: rgba(0,0,0,0.4);
border-radius: 12px;
border: 1px solid rgba(255,255,255,0.06);
}
table {
width: 100%;
border-collapse: collapse;
font-size: 0.8rem;
}
th, td {
padding: 8px 10px;
border-bottom: 1px solid rgba(255,255,255,0.06);
vertical-align: top;
text-align: left;
}
th {
position: sticky;
top: 0;
background: rgba(15,15,15,0.95);
z-index: 1;
}
.tag-ok {
color: #7cffb2;
font-weight: 600;
}
.tag-bad {
color: #ff8686;
font-weight: 600;
}
#accuracyChart {
width: 100%;
max-width: 100%;
}
/* Layout */
.layout {
display: flex;
flex-direction: column;
gap: 24px;
}
.left-column,
.right-column {
display: flex;
flex-direction: column;
gap: 24px;
}
@media (min-width: 960px) {
.layout {
display: grid;
grid-template-columns: minmax(0, 2.1fr) minmax(280px, 1.1fr);
align-items: flex-start;
gap: 28px;
}
#results {
margin-bottom: 0;
}
}
.button-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
}
.button-row-note {
font-size: 0.9rem;
opacity: 0.8;
margin-top: 6px;
background: none;
border: none;
padding: 0;
animation: none;
}
`;
document.head.appendChild(style);
/* API KEYS */
let groq_api_key = "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";
let google_api_key ="AIzaSyBK4kUVcCvrWp7iTqh7oO2YCLIf3Iup0Rs";
/* GLOBAL STATS & HISTORY */
let totalProcessed = 0;
let groqCorrectCount = 0;
let googleCorrectCount = 0;
let historyEntries = []; // per-query history
let accuracyLabels = []; // e.g., ["#1", "#2", ...]
let groqAccuracyHistory = [];
let googleAccuracyHistory = [];
let accuracyChart = null;
let chartJsLoaded = false;
/* DATASET */
const dataset = [
{ english: "Hello, how are you?", french: "Bonjour, comment ça va ?" },
{ english: "What is your name?", french: "Comment vous appelez-vous ?" },
{ english: "I like to eat apples.", french: "J'aime manger des pommes." },
{ english: "She is reading a book.", french: "Elle lit un livre." },
{ english: "We are going to the park.", french: "Nous allons au parc." },
{ english: "He is my brother.", french: "C'est mon frère." },
{ english: "I have a cat.", french: "J'ai un chat." },
{ english: "Can you help me?", french: "Pouvez-vous m'aider ?" },
{ english: "It is raining today.", french: "Il pleut aujourd'hui." },
{
english: "Where are you going?",
french: "Où allez-vous ?",
accepted: ["Où allez-vous ?", "Où vas-tu ?", "Où vas tu ?", "Où vas-tu ?"]
},
];
/* TRANSLATION FUNCTIONS (Groq + Gemini) */
async function translateWithGroq(text) {
try {
const response = await fetch("https://api.groq.com/openai/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer ${groq_api_key}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "openai/gpt-oss-20b",
messages: [
{ role: "system", content: "Translate all text to French only." },
{ role: "user", content: text }
]
})
});
const data = await response.json();
return (data.choices?.[0]?.message?.content || "").toString().trim();
} catch (e) {
console.warn("Groq call failed", e);
return "(error calling Groq)";
}
}
async function translateWithGoogle(text) {
try {
const response = await fetch(
`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite:generateContent?key=${google_api_key}`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
contents: [{ parts: [{ text: `Translate this to French only:\n"${text}"` }] }],
generationConfig: { temperature: 0.0, responseMimeType: "text/plain" }
})
}
);
const data = await response.json();
const candidates = data?.candidates || [];
if (!candidates.length) return "(no response from Gemini)";
const first = candidates[0];
const parts = first?.content?.parts || [];
const textParts = parts.map(p => (p?.text || "").toString().trim()).filter(Boolean);
const joined = textParts.join(" ").trim();
const fallback = data?.output?.[0]?.content?.[0]?.text || data?.output?.[0]?.text || "";
const final = joined || fallback || (candidates[0]?.content?.parts?.[0]?.text || "");
return final.toString().trim();
} catch (e) {
console.warn("Gemini call failed", e);
return "(error calling Gemini)";
}
}
/* NORMALIZATION & FUZZY MATCHING */
const SIMILARITY_THRESHOLD = 0.55;
function stripDiacritics(s) {
return s.normalize("NFD").replace(/\p{Diacritic}/gu, "");
}
function cleanForMatching(s) {
if (!s && s !== "") return "";
let out = String(s);
out = out.replace(/^["'`]+|["'`]+$/g, "");
// Normalizing unicode & remove diacritics
out = stripDiacritics(out);
out = out.toLowerCase();
// replace smart apostrophes with ascii
out = out.replace(/\u2019/g, "'");
out = out.replace(/[^0-9a-z%.'\s\-]/g, " ");
// collapse runs of whitespace
out = out.replace(/\s+/g, " ").trim();
return out;
}
function tokenize(s) {
const c = cleanForMatching(s);
if (!c) return [];
return c.split(" ").map(t => t.trim()).filter(Boolean);
}
function tokenSimilarity(a, b) {
const ta = Array.from(new Set(tokenize(a)));
const tb = Array.from(new Set(tokenize(b)));
if (!ta.length || !tb.length) return 0;
let intersection = 0;
const bSet = new Set(tb);
for (const t of ta) if (bSet.has(t)) intersection++;
const unionSize = new Set([...ta, ...tb]).size;
return unionSize ? (intersection / unionSize) : 0;
}
function numericToleranceMatch(generated, reference, tol = 0.12) {
const numRx = /-?\d+(\.\d+)?/g;
const genNums = (generated.match(numRx) || []).map(Number);
const refNums = (reference.match(numRx) || []).map(Number);
if (!genNums.length || !refNums.length) return null;
// simple heuristic: if any pair matches within tol fraction, we consider numeric match
for (const gn of genNums) {
for (const rn of refNums) {
if (isFinite(gn) && isFinite(rn)) {
const diff = Math.abs(gn - rn);
if (diff <= Math.abs(rn) * tol) return true;
}
}
}
return false;
}
function isMatch(generated, acceptedArrayOrSingle) {
const gen = (generated || "").toString();
const acceptedArray = Array.isArray(acceptedArrayOrSingle)
? acceptedArrayOrSingle
: [acceptedArrayOrSingle];
const genClean = cleanForMatching(gen);
for (const a of acceptedArray) {
const aClean = cleanForMatching(a);
if (!aClean && !genClean) return true;
if (aClean === genClean) return true;
}
for (const a of acceptedArray) {
const numericResult = numericToleranceMatch(gen, a);
if (numericResult === true) return true;
}
for (const a of acceptedArray) {
const sim = tokenSimilarity(gen, a);
if (sim >= SIMILARITY_THRESHOLD) return true;
}
return false;
}
/* TTS + HIGHLIGHT SYSTEM */
let loadedVoices = [];
function loadVoices() {
return new Promise(res => {
let v = speechSynthesis.getVoices();
if (v.length) {
loadedVoices = v;
res();
} else {
speechSynthesis.onvoiceschanged = () => {
loadedVoices = speechSynthesis.getVoices();
res();
};
}
});
}
function getVoice(lang) {
if (lang === "en-US")
return loadedVoices.find(v => v.name.includes("Aria")) ||
loadedVoices.find(v => v.name.includes("Jenny")) ||
loadedVoices.find(v => v.lang && v.lang.startsWith("en")) ||
loadedVoices[0];
if (lang === "fr-FR")
return loadedVoices.find(v => v.name.includes("Denise")) ||
loadedVoices.find(v => v.name.includes("Heloise")) ||
loadedVoices.find(v => v.lang && v.lang.startsWith("fr")) ||
loadedVoices[0];
}
function normalizeText(s) {
if (!s) return "";
return s.normalize("NFC")
.replace(/\u2019/g,"'")
.replace(/\s+/g," ")
.replace(/\s+([?!.,;:])/g,"$1")
.trim()
.toLowerCase();
}
async function speak(text, lang, id, isWrong = false) {
await loadVoices();
const el = document.getElementById(id);
if (el) {
if (isWrong) el.classList.add("wrong");
else el.classList.add("highlight");
}
const utter = new SpeechSynthesisUtterance(text);
utter.lang = lang;
utter.voice = getVoice(lang);
return new Promise(res => {
utter.onend = async () => {
if (el) {
if (isWrong) el.classList.remove("wrong");
else el.classList.remove("highlight");
}
await new Promise(r => setTimeout(r, 700));
res();
};
speechSynthesis.speak(utter);
});
}
async function autoPlayAll(groqText, googleText, groqCorrect = true, googleCorrect = true) {
await speak(current.english, "en-US", "engText");
await speak(current.french, "fr-FR", "frText");
await speak(groqText, "fr-FR", "groqText", !groqCorrect);
await speak(googleText, "fr-FR", "googleText", !googleCorrect);
}
/* CHART.JS LOADER & CHART */
function loadChartJs() {
return new Promise(resolve => {
if (window.Chart) {
chartJsLoaded = true;
return resolve();
}
if (chartJsLoaded) return resolve();
const script = document.createElement("script");
script.src = "https://cdn.jsdelivr.net/npm/chart.js";
script.onload = () => {
chartJsLoaded = true;
resolve();
};
document.head.appendChild(script);
});
}
function updateChart() {
if (!chartJsLoaded || !window.Chart) return;
const canvas = document.getElementById("accuracyChart");
if (!canvas) return;
const ctx = canvas.getContext("2d");
const groqData = groqAccuracyHistory;
const googleData = googleAccuracyHistory;
const gapData = groqData.map((v, i) => +(v - (googleData[i] ?? 0)).toFixed(1));
if (!accuracyChart) {
accuracyChart = new Chart(ctx, {
type: "bar",
data: {
labels: accuracyLabels,
datasets: [
{
label: "Groq Accuracy (%)",
data: groqData,
backgroundColor: "rgba(120, 120, 255, 0.6)",
borderColor: "rgba(180, 180, 255, 1)",
borderWidth: 1
},
{
label: "Gemini Accuracy (%)",
data: googleData,
backgroundColor: "rgba(255, 130, 180, 0.6)",
borderColor: "rgba(255, 190, 220, 1)",
borderWidth: 1
},
{
type: "line",
label: "Gap (Groq - Gemini)",
data: gapData,
borderColor: "rgba(255, 240, 120, 1)",
borderWidth: 2,
fill: false,
tension: 0.25,
pointRadius: 3
}
]
},
options: {
responsive: true,
plugins: {
legend: {
labels: {
color: "#eaeaea"
}
},
tooltip: {
callbacks: {
label: function(ctx) {
return `${ctx.dataset.label}: ${ctx.parsed.y}%`;
}
}
}
},
scales: {
x: {
ticks: { color: "#bbbbff" },
grid: { color: "rgba(255,255,255,0.05)" }
},
y: {
beginAtZero: true,
max: 100,
ticks: { color: "#bbbbff" },
grid: { color: "rgba(255,255,255,0.06)" }
}
}
}
});
} else {
accuracyChart.data.labels = accuracyLabels;
accuracyChart.data.datasets[0].data = groqData;
accuracyChart.data.datasets[1].data = googleData;
accuracyChart.data.datasets[2].data = gapData;
accuracyChart.update();
}
}
/* STATS, SUMMARY & HISTORY RENDER */
function updateStats() {
const el = document.getElementById("statsText");
if (!el) return;
const groqAcc = totalProcessed ? ((groqCorrectCount / totalProcessed) * 100).toFixed(1) : 0;
const googleAcc = totalProcessed ? ((googleCorrectCount / totalProcessed) * 100).toFixed(1) : 0;
el.innerHTML = `
Total processed: ${totalProcessed}<br>
Groq correct: ${groqCorrectCount} (${groqAcc}%)<br>
Google correct: ${googleCorrectCount} (${googleAcc}%)<br>
`;
}
function updateSummary() {
const el = document.getElementById("summaryText");
if (!el) return;
if (!totalProcessed) {
el.innerHTML = "Run at least one test to see a detailed comparison between Groq and Gemini.";
return;
}
const groqAcc = (groqCorrectCount / totalProcessed) * 100;
const googleAcc = (googleCorrectCount / totalProcessed) * 100;
let best;
if (Math.abs(groqAcc - googleAcc) < 0.01) {
best = "Both models are currently tied.";
} else if (groqAcc > googleAcc) {
best = "Groq is currently performing better overall.";
} else {
best = "Google Gemini is currently performing better overall.";
}
const overallPairAcc =
((groqCorrectCount + googleCorrectCount) / (2 * totalProcessed)) * 100;
el.innerHTML = `
<strong>Best model so far:</strong> ${
groqAcc > googleAcc ? "Groq" : (googleAcc > groqAcc ? "Google Gemini" : "Tie")
}<br>
Groq accuracy: ${groqAcc.toFixed(1)}%<br>
Gemini accuracy: ${googleAcc.toFixed(1)}%<br>
Overall pair accuracy (both answers combined): ${overallPairAcc.toFixed(1)}%<br>
${best}
`;
}
function truncateText(str, max = 60) {
if (!str) return "";
return str.length > max ? str.slice(0, max) + "…" : str;
}
function renderHistory() {
const wrapper = document.getElementById("historyTableWrapper");
if (!wrapper) return;
if (!historyEntries.length) {
wrapper.innerHTML = `<p style="background:none;border:none;padding:10px;font-size:0.9rem;">
No queries tested yet. Press <strong>Test APIs</strong> or <strong>Run All</strong> to start logging results.
</p>`;
return;
}
const rows = historyEntries
.map((h, i) => {
return `
<tr>
<td>${i + 1}</td>
<td>${truncateText(h.english, 50)}</td>
<td>${truncateText(h.reference, 50)}</td>
<td>${truncateText(h.groqResult, 50)}</td>
<td>${truncateText(h.googleResult, 50)}</td>
<td class="${h.groqCorrect ? "tag-ok" : "tag-bad"}">
${h.groqCorrect ? "✅" : "❌"}
</td>
<td class="${h.googleCorrect ? "tag-ok" : "tag-bad"}">
${h.googleCorrect ? "✅" : "❌"}
</td>
</tr>
`;
})
.join("");
wrapper.innerHTML = `
<table>
<thead>
<tr>
<th>#</th>
<th>English</th>
<th>Reference French</th>
<th>Groq Output</th>
<th>Gemini Output</th>
<th>Groq</th>
<th>Gemini</th>
</tr>
</thead>
<tbody>
${rows}
</tbody>
</table>
`;
}
/* UI RENDER */
let index = Math.floor(Math.random() * dataset.length);
let current = dataset[index];
function renderPage() {
document.body.innerHTML = `
<div class="layout">
<!-- LEFT COLUMN: main interaction -->
<div class="left-column">
<div class="card">
<h2>Select Sentence</h2>
<select id="sentencePicker" style="padding:12px;border-radius:12px;width:100%;background:#111;color:#fff;border:1px solid #444;font-size:1rem;">
<option value="-1">0. All sentences (use "Run All")</option>
${dataset
.map((d, i) => `<option value="${i}">${i + 1}. ${d.english}</option>`)
.join("")}
</select>
</div>
<div class="card">
<h2>English Sentence</h2>
<p id="engText">${current.english}</p>
<button id="playEng">🔊 English</button>
</div>
<div class="card">
<h3>Correct French</h3>
<p id="frText">${current.french}</p>
<button id="playFr">🔊 French</button>
</div>
<div class="card">
<h3>Groq Translation</h3>
<p id="groqText">---</p>
<button id="playGroq">🔊 Groq</button>
</div>
<div class="card">
<h3>Google Gemini Translation</h3>
<p id="googleText">---</p>
<button id="playGoogle">🔊 Google</button>
</div>
<div class="card">
<h3>Run Tests</h3>
<div class="button-row">
<button id="btnTest">Test APIs</button>
<button id="btnRunAll">Run All (All Sentences)</button>
</div>
</div>
<div id="results"></div>
<div class="card">
<h3>📜 History (per sentence)</h3>
<div id="historyTableWrapper"></div>
</div>
</div>
<!-- RIGHT COLUMN: stats & analytics -->
<div class="right-column">
<div class="card" id="statsCard">
<h3>📊 Translation Statistics</h3>
<p id="statsText">
Total processed: 0 <br>
Groq correct: 0 (0%)<br>
Google correct: 0 (0%)<br>
</p>
</div>
<div class="card">
<h3>🧠 Advanced Model Comparison</h3>
<p id="summaryText">
Run at least one test to see a detailed comparison between Groq and Gemini.
</p>
</div>
<div class="card">
<h3>📈 Accuracy Trend (Groq vs Gemini)</h3>
<canvas id="accuracyChart" height="210"></canvas>
</div>
</div>
</div>
`;
document.querySelectorAll(".card").forEach((c, i) => {
c.style.animationDelay = (i * 0.12) + "s";
});
// button bindings
document.getElementById("btnTest").onclick = testAPIs;
document.getElementById("btnRunAll").onclick = runAllTests;
const picker = document.getElementById("sentencePicker");
picker.value = index; // ensure a valid sentence is selected
picker.onchange = e => {
const value = Number(e.target.value);
if (value === -1) {
e.target.value = index;
return;
}
index = value;
current = dataset[index];
renderPage();
};
document.getElementById("playEng").onclick = () =>
speak(current.english, "en-US", "engText");
document.getElementById("playFr").onclick = () =>
speak(current.french, "fr-FR", "frText");
document.getElementById("playGroq").onclick = async () => {
let t = await translateWithGroq(current.english);
document.getElementById("groqText").innerText = t;
const accepted = current.accepted || [current.french];
const correct = isMatch(t, accepted);
await speak(t, "fr-FR", "groqText", !correct);
};
document.getElementById("playGoogle").onclick = async () => {
let t = await translateWithGoogle(current.english);
document.getElementById("googleText").innerText = t;
const accepted = current.accepted || [current.french];
const correct = isMatch(t, accepted);
await speak(t, "fr-FR", "googleText", !correct);
};
updateStats();
updateSummary();
renderHistory();
loadChartJs().then(updateChart);
}
/* TEST APIs */
async function testAPIs() {
const resultsDiv = document.getElementById("results");
resultsDiv.style.opacity = "1";
resultsDiv.innerHTML = "<p>Testing APIs... ⏳</p>";
const groqResult = await translateWithGroq(current.english);
const googleResult = await translateWithGoogle(current.english);
document.getElementById("groqText").innerText = groqResult;
document.getElementById("googleText").innerText = googleResult;
const accepted = current.accepted || [current.french];
const groqCorrect = isMatch(groqResult, accepted);
const googleCorrect = isMatch(googleResult, accepted);
totalProcessed++;
if (groqCorrect) groqCorrectCount++;
if (googleCorrect) googleCorrectCount++;
const groqAcc = (groqCorrectCount / totalProcessed) * 100;
const googleAcc = (googleCorrectCount / totalProcessed) * 100;
accuracyLabels.push(`#${totalProcessed}`);
groqAccuracyHistory.push(+groqAcc.toFixed(1));
googleAccuracyHistory.push(+googleAcc.toFixed(1));
historyEntries.push({
index,
english: current.english,
reference: current.french,
groqResult,
googleResult,
groqCorrect,
googleCorrect
});
updateStats();
updateSummary();
renderHistory();
loadChartJs().then(updateChart);
resultsDiv.innerHTML = `
<h3>Groq Translation</h3>
<p>${groqResult}</p>
<strong>${groqCorrect ? "✅ Correct" : "❌ Incorrect"}</strong>
<h3>Google Gemini Translation</h3>
<p>${googleResult}</p>
<strong>${googleCorrect ? "✅ Correct" : "❌ Incorrect"}</strong>
<h2>Total Correct: ${(groqCorrect + googleCorrect)} / 2</h2>
`;
autoPlayAll(groqResult, googleResult, groqCorrect, googleCorrect);
}
/* RUN ALL TESTS*/
async function runAllTests() {
const resultsDiv = document.getElementById("results");
resultsDiv.style.opacity = "1";
resultsDiv.innerHTML = "<p>Running all sentences... ⏳</p>";
totalProcessed = 0;
groqCorrectCount = 0;
googleCorrectCount = 0;
historyEntries = [];
accuracyLabels = [];
groqAccuracyHistory = [];
googleAccuracyHistory = [];
if (accuracyChart) {
accuracyChart.destroy();
accuracyChart = null;
}
for (let i = 0; i < dataset.length; i++) {
const item = dataset[i];
const accepted = item.accepted || [item.french];
let groqResult = "";
let googleResult = "";
try {
groqResult = await translateWithGroq(item.english);
} catch (e) {
groqResult = "(error calling Groq)";
}
try {
googleResult = await translateWithGoogle(item.english);
} catch (e) {
googleResult = "(error calling Gemini)";
}
const groqCorrect = isMatch(groqResult, accepted);
const googleCorrect = isMatch(googleResult, accepted);
totalProcessed++;
if (groqCorrect) groqCorrectCount++;
if (googleCorrect) googleCorrectCount++;
const groqAcc = (groqCorrectCount / totalProcessed) * 100;
const googleAcc = (googleCorrectCount / totalProcessed) * 100;
accuracyLabels.push(`#${totalProcessed}`);
groqAccuracyHistory.push(+groqAcc.toFixed(1));
googleAccuracyHistory.push(+googleAcc.toFixed(1));
historyEntries.push({
index: i,
english: item.english,
reference: item.french,
groqResult,
googleResult,
groqCorrect,
googleCorrect
});
// live updates
updateStats();
updateSummary();
renderHistory();
await loadChartJs();
updateChart();
resultsDiv.innerHTML = `
<p>Running all sentences... (${totalProcessed} / ${dataset.length})</p>
`;
}
const finalGroqAcc = totalProcessed ? ((groqCorrectCount / totalProcessed) * 100).toFixed(1) : 0;
const finalGoogleAcc = totalProcessed ? ((googleCorrectCount / totalProcessed) * 100).toFixed(1) : 0;
resultsDiv.innerHTML = `
<h3>Run All Complete ✅</h3>
<p>Processed ${totalProcessed} sentences.</p>
<p>Groq correct: ${groqCorrectCount} / ${totalProcessed} (${finalGroqAcc}%)</p>
<p>Gemini correct: ${googleCorrectCount} / ${totalProcessed} (${finalGoogleAcc}%)</p>
`;
}
renderPage();