Spaces:
Sleeping
Sleeping
| <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 ; } | |
| </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> |