Local_AI_WebGPU / index.html
Javare's picture
Update index.html
ace16dd verified
Raw
History Blame Contribute Delete
11.1 kB
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Local AI WebGPU - Benchmark</title>
<script type="module" crossorigin src="/assets/index-Btti6dN2.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CjAcR-nm.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
.benchmark-sidebar {
position: fixed; top: 0; right: 0; width: 420px; height: 100vh;
background: #ffffff; box-shadow: -4px 0 25px rgba(0,0,0,0.15);
z-index: 999999; font-family: system-ui, -apple-system, sans-serif;
display: flex; flex-direction: column; color: #2d3748; border-left: 1px solid #e2e8f0;
}
.benchmark-header { background: #1a202c; color: white; padding: 15px; text-align: center; margin: 0; font-size: 1.2rem; }
.benchmark-content { padding: 15px; overflow-y: auto; flex: 1; }
.form-group { margin-bottom: 10px; display: flex; flex-direction: column; gap: 4px; }
.form-group label { font-size: 0.85rem; font-weight: 600; color: #4a5568; }
.form-group input, .form-group select { padding: 8px 12px; border: 1px solid #cbd5e0; border-radius: 6px; font-size: 0.9rem; background: #f7fafc; }
.power-selector { display: flex; gap: 10px; margin-top: 4px; }
.power-btn { flex: 1; padding: 10px; border: 2px solid #e2e8f0; border-radius: 6px; background: #fff; cursor: pointer; font-weight: bold; font-size: 0.85rem; display: flex; justify-content: center; gap: 6px; }
.power-btn.active { border-color: #3182ce; background: #ebf8ff; color: #2b6cb0; }
.leaderboard-table { width: 100%; border-collapse: collapse; margin-top: 10px; font-size: 0.78rem; }
.leaderboard-table th, .leaderboard-table td { padding: 6px 4px; text-align: left; border-bottom: 1px solid #e2e8f0; }
.leaderboard-table th { background-color: #edf2f7; font-weight: 700; color: #4a5568; }
.badge-top { background: #ecc94b; color: #744210; padding: 1px 4px; border-radius: 4px; font-weight: bold; }
.text-muted { color: #718096; font-size: 0.72rem; }
body { margin-right: 420px !important; }
</style>
<script>
let selectedPower = '🔌 Secteur';
let startTime = 0;
let minTpsRecorded = Infinity;
let maxTpsRecorded = 0;
let tokenLabels = [];
let tpsData = [];
function setPower(type) {
selectedPower = type;
document.getElementById('btnSecteur').classList.toggle('active', type === '🔌 Secteur');
document.getElementById('btnBatterie').classList.toggle('active', type === '🔋 Batterie');
}
// Interception du Web Worker
const OriginalWorker = window.Worker;
window.Worker = function(...args) {
const worker = new OriginalWorker(...args);
worker.addEventListener('message', function(e) {
const data = e.data;
if (data && data.status === 'start') {
startTime = performance.now();
minTpsRecorded = Infinity;
maxTpsRecorded = 0;
tokenLabels = [];
tpsData = [];
if (window.tpsChart) {
window.tpsChart.data.labels = [];
window.tpsChart.data.datasets[0].data = [];
window.tpsChart.update();
}
}
if (data && data.status === 'update' && data.tps !== undefined) {
const currentTps = data.tps;
const currentTokens = data.numTokens;
// On commence à enregistrer min/max après 5 tokens pour éviter le lag de départ
if (currentTokens > 5) {
if (currentTps > maxTpsRecorded) maxTpsRecorded = currentTps;
if (currentTps < minTpsRecorded) minTpsRecorded = currentTps;
}
tokenLabels.push(currentTokens);
tpsData.push(parseFloat(currentTps.toFixed(2)));
if (window.tpsChart) {
window.tpsChart.data.labels = tokenLabels;
window.tpsChart.data.datasets[0].data = tpsData;
window.tpsChart.update('none');
}
}
if (data && data.status === 'complete') {
const totalTime = (performance.now() - startTime) / 1000;
const totalTokens = data.numTokens || tokenLabels.length;
const finalAvgTps = totalTokens / totalTime;
if (minTpsRecorded === Infinity) minTpsRecorded = finalAvgTps;
// Envoi des statistiques complètes au serveur
saveScore(minTpsRecorded, maxTpsRecorded, finalAvgTps, totalTokens, totalTime);
}
});
return worker;
};
async function loadLeaderboard() {
try {
const response = await fetch('/api/scores');
const scores = await response.json();
const tbody = document.getElementById('leaderboardBody');
tbody.innerHTML = '';
if(!scores || scores.length === 0) {
tbody.innerHTML = '<tr><td colspan="5" style="text-align:center; color:#888;">Aucun score.</td></tr>';
return;
}
scores.forEach((item, index) => {
const row = document.createElement('tr');
const isTop3 = index < 3 ? `<span class="badge-top">🥇 ${index + 1}</span>` : index + 1;
row.innerHTML = `
<td>${isTop3}</td>
<td><strong>${item.config}</strong><br><span class="text-muted">${item.browser}${item.power}</span></td>
<td>Min: ${item.min_tps.toFixed(1)}<br>Max: <strong>${item.max_tps.toFixed(1)}</strong></td>
<td style="color: #2b6cb0; font-weight: bold;">${item.avg_tps.toFixed(1)} tps</td>
<td>${item.total_tokens} tok<br><span class="text-muted">en ${item.duration.toFixed(1)}s</span></td>
`;
tbody.appendChild(row);
});
} catch (error) {
console.error("Erreur chargement classement:", error);
}
}
async function saveScore(minTps, maxTps, avgTps, totalTokens, duration) {
const type = document.getElementById('deviceType').value;
const model = document.getElementById('deviceModel').value || "Inconnu";
const os = document.getElementById('osType').value;
const browser = document.getElementById('browserType').value;
const configString = `${type} [${os}] (${model})`;
const payload = {
config: configString, browser: browser, power: selectedPower,
min_tps: minTps, max_tps: maxTps, avg_tps: avgTps,
total_tokens: parseInt(totalTokens), duration: parseFloat(duration)
};
try {
await fetch('/api/score', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
loadLeaderboard();
} catch (error) {
console.error("Erreur envoi score:", error);
}
}
document.addEventListener("DOMContentLoaded", () => {
loadLeaderboard();
const ctx = document.getElementById('stressTestChart').getContext('2d');
window.tpsChart = new Chart(ctx, {
type: 'line',
data: { labels: [], datasets: [{ label: 'tps', data: [], borderColor: '#3182ce', tension: 0.1, pointRadius: 0, borderWidth: 2 }] },
options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true } } }
});
});
</script>
</head>
<body>
<div id="root"></div>
<div class="benchmark-sidebar">
<h3 class="benchmark-header">⚡ WebGPU Stress Test</h3>
<div class="benchmark-content">
<div class="form-group">
<label>Type d'appareil</label>
<select id="deviceType">
<option value="🖥️ UC (Bureau)">🖥️ UC (Bureau)</option>
<option value="💻 Laptop">💻 Laptop</option>
<option value="🖥️ All in One">🖥️ All in One</option>
<option value="📱 Tablette">📱 Tablette</option>
<option value="📱 SmartPhone">📱 SmartPhone</option>
</select>
</div>
<div class="form-group">
<label>Modèle (CPU / GPU / Modèle exact)</label>
<input type="text" id="deviceModel" placeholder="Ex: RTX 4070 / Apple M3 / Galaxy S26" required>
</div>
<div class="form-group">
<label>Système d'exploitation (OS)</label>
<select id="osType">
<option value="Windows">Windows</option>
<option value="macOS">macOS</option>
<option value="Linux">Linux</option>
<option value="Android">Android</option>
<option value="iOS">iOS</option>
</select>
</div>
<div class="form-group">
<label>Navigateur Internet</label>
<select id="browserType">
<option value="Chrome">Chrome</option>
<option value="Firefox">Firefox</option>
<option value="Edge">Edge</option>
<option value="Brave">Brave</option>
<option value="Safari">Safari</option>
<option value="Opera">Opera</option>
</select>
</div>
<div class="form-group">
<label>Source d'alimentation</label>
<div class="power-selector">
<button type="button" class="power-btn active" id="btnSecteur" onclick="setPower('🔌 Secteur')">🔌 Secteur</button>
<button type="button" class="power-btn" id="btnBatterie" onclick="setPower('🔋 Batterie')">🔋 Batterie</button>
</div>
</div>
<hr style="border: 0; border-top: 1px solid #e2e8f0; margin: 10px 0;">
<div style="height: 110px; position: relative; margin-bottom: 10px;">
<canvas id="stressTestChart"></canvas>
</div>
<h4 style="margin: 10px 0 5px 0; color: #1a202c;">🏆 TOP 10 Global</h4>
<table class="leaderboard-table">
<thead>
<tr>
<th style="width: 8%">Rg</th>
<th style="width: 42%">Configuration</th>
<th style="width: 20%">TPS (Min/Max)</th>
<th style="width: 15%">Moyenne</th>
<th style="width: 15%">Tokens/Tps</th>
</tr>
</thead>
<tbody id="leaderboardBody">
</tbody>
</table>
</div>
</div>
</body>
</html>