Javare/Local_AI_WebGPU
0
1<!DOCTYPE html>2<html lang="fr">3 <head>4 <meta charset="UTF-8" />5 <meta name="viewport" content="width=device-width, initial-scale=1.0" />6 <title>Local AI WebGPU - Benchmark</title>7 8 <script type="module" crossorigin src="/assets/index-Btti6dN2.js"></script>9 <link rel="stylesheet" crossorigin href="/assets/index-CjAcR-nm.css">10 <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>11 12 <style>13 .benchmark-sidebar {14 position: fixed; top: 0; right: 0; width: 420px; height: 100vh;15 background: #ffffff; box-shadow: -4px 0 25px rgba(0,0,0,0.15);16 z-index: 999999; font-family: system-ui, -apple-system, sans-serif;17 display: flex; flex-direction: column; color: #2d3748; border-left: 1px solid #e2e8f0;18 }19 .benchmark-header { background: #1a202c; color: white; padding: 15px; text-align: center; margin: 0; font-size: 1.2rem; }20 .benchmark-content { padding: 15px; overflow-y: auto; flex: 1; }21 .form-group { margin-bottom: 10px; display: flex; flex-direction: column; gap: 4px; }22 .form-group label { font-size: 0.85rem; font-weight: 600; color: #4a5568; }23 .form-group input, .form-group select { padding: 8px 12px; border: 1px solid #cbd5e0; border-radius: 6px; font-size: 0.9rem; background: #f7fafc; }24 .power-selector { display: flex; gap: 10px; margin-top: 4px; }25 .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; }26 .power-btn.active { border-color: #3182ce; background: #ebf8ff; color: #2b6cb0; }27 28 .leaderboard-table { width: 100%; border-collapse: collapse; margin-top: 10px; font-size: 0.78rem; }29 .leaderboard-table th, .leaderboard-table td { padding: 6px 4px; text-align: left; border-bottom: 1px solid #e2e8f0; }30 .leaderboard-table th { background-color: #edf2f7; font-weight: 700; color: #4a5568; }31 .badge-top { background: #ecc94b; color: #744210; padding: 1px 4px; border-radius: 4px; font-weight: bold; }32 .text-muted { color: #718096; font-size: 0.72rem; }33 body { margin-right: 420px !important; }34 </style>35 36 <script>37 let selectedPower = '🔌 Secteur';38 let startTime = 0;39 let minTpsRecorded = Infinity;40 let maxTpsRecorded = 0;41 let tokenLabels = [];42 let tpsData = [];43 44 function setPower(type) {45 selectedPower = type;46 document.getElementById('btnSecteur').classList.toggle('active', type === '🔌 Secteur');47 document.getElementById('btnBatterie').classList.toggle('active', type === '🔋 Batterie');48 }49 50 // Interception du Web Worker51 const OriginalWorker = window.Worker;52 window.Worker = function(...args) {53 const worker = new OriginalWorker(...args);54 55 worker.addEventListener('message', function(e) {56 const data = e.data;57 58 if (data && data.status === 'start') {59 startTime = performance.now();60 minTpsRecorded = Infinity;61 maxTpsRecorded = 0;62 tokenLabels = [];63 tpsData = [];64 if (window.tpsChart) {65 window.tpsChart.data.labels = [];66 window.tpsChart.data.datasets[0].data = [];67 window.tpsChart.update();68 }69 }70 71 if (data && data.status === 'update' && data.tps !== undefined) {72 const currentTps = data.tps;73 const currentTokens = data.numTokens;74 75 // On commence à enregistrer min/max après 5 tokens pour éviter le lag de départ76 if (currentTokens > 5) {77 if (currentTps > maxTpsRecorded) maxTpsRecorded = currentTps;78 if (currentTps < minTpsRecorded) minTpsRecorded = currentTps;79 }80 81 tokenLabels.push(currentTokens);82 tpsData.push(parseFloat(currentTps.toFixed(2)));83 if (window.tpsChart) {84 window.tpsChart.data.labels = tokenLabels;85 window.tpsChart.data.datasets[0].data = tpsData;86 window.tpsChart.update('none');87 }88 }89 90 if (data && data.status === 'complete') {91 const totalTime = (performance.now() - startTime) / 1000;92 const totalTokens = data.numTokens || tokenLabels.length;93 const finalAvgTps = totalTokens / totalTime;94 95 if (minTpsRecorded === Infinity) minTpsRecorded = finalAvgTps;96 97 // Envoi des statistiques complètes au serveur98 saveScore(minTpsRecorded, maxTpsRecorded, finalAvgTps, totalTokens, totalTime);99 }100 });101 102 return worker;103 };104 105 async function loadLeaderboard() {106 try {107 const response = await fetch('/api/scores');108 const scores = await response.json();109 const tbody = document.getElementById('leaderboardBody');110 tbody.innerHTML = '';111 112 if(!scores || scores.length === 0) {113 tbody.innerHTML = '<tr><td colspan="5" style="text-align:center; color:#888;">Aucun score.</td></tr>';114 return;115 }116 117 scores.forEach((item, index) => {118 const row = document.createElement('tr');119 const isTop3 = index < 3 ? `<span class="badge-top">🥇 ${index + 1}</span>` : index + 1;120 121 row.innerHTML = `122 <td>${isTop3}</td>123 <td><strong>${item.config}</strong><br><span class="text-muted">${item.browser} • ${item.power}</span></td>124 <td>Min: ${item.min_tps.toFixed(1)}<br>Max: <strong>${item.max_tps.toFixed(1)}</strong></td>125 <td style="color: #2b6cb0; font-weight: bold;">${item.avg_tps.toFixed(1)} tps</td>126 <td>${item.total_tokens} tok<br><span class="text-muted">en ${item.duration.toFixed(1)}s</span></td>127 `;128 tbody.appendChild(row);129 });130 } catch (error) {131 console.error("Erreur chargement classement:", error);132 }133 }134 135 async function saveScore(minTps, maxTps, avgTps, totalTokens, duration) {136 const type = document.getElementById('deviceType').value;137 const model = document.getElementById('deviceModel').value || "Inconnu";138 const os = document.getElementById('osType').value;139 const browser = document.getElementById('browserType').value;140 141 const configString = `${type} [${os}] (${model})`;142 143 const payload = {144 config: configString, browser: browser, power: selectedPower,145 min_tps: minTps, max_tps: maxTps, avg_tps: avgTps,146 total_tokens: parseInt(totalTokens), duration: parseFloat(duration)147 };148 149 try {150 await fetch('/api/score', {151 method: 'POST',152 headers: { 'Content-Type': 'application/json' },153 body: JSON.stringify(payload)154 });155 loadLeaderboard(); 156 } catch (error) {157 console.error("Erreur envoi score:", error);158 }159 }160 161 document.addEventListener("DOMContentLoaded", () => {162 loadLeaderboard();163 const ctx = document.getElementById('stressTestChart').getContext('2d');164 window.tpsChart = new Chart(ctx, {165 type: 'line',166 data: { labels: [], datasets: [{ label: 'tps', data: [], borderColor: '#3182ce', tension: 0.1, pointRadius: 0, borderWidth: 2 }] },167 options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true } } }168 });169 });170 </script>171 </head>172 <body>173 174 <div id="root"></div>175 176 <div class="benchmark-sidebar">177 <h3 class="benchmark-header">⚡ WebGPU Stress Test</h3>178 179 <div class="benchmark-content">180 <div class="form-group">181 <label>Type d'appareil</label>182 <select id="deviceType">183 <option value="🖥️ UC (Bureau)">🖥️ UC (Bureau)</option>184 <option value="💻 Laptop">💻 Laptop</option>185 <option value="🖥️ All in One">🖥️ All in One</option>186 <option value="📱 Tablette">📱 Tablette</option>187 <option value="📱 SmartPhone">📱 SmartPhone</option>188 </select>189 </div>190 191 <div class="form-group">192 <label>Modèle (CPU / GPU / Modèle exact)</label>193 <input type="text" id="deviceModel" placeholder="Ex: RTX 4070 / Apple M3 / Galaxy S26" required>194 </div>195 196 <div class="form-group">197 <label>Système d'exploitation (OS)</label>198 <select id="osType">199 <option value="Windows">Windows</option>200 <option value="macOS">macOS</option>201 <option value="Linux">Linux</option>202 <option value="Android">Android</option>203 <option value="iOS">iOS</option>204 </select>205 </div>206 207 <div class="form-group">208 <label>Navigateur Internet</label>209 <select id="browserType">210 <option value="Chrome">Chrome</option>211 <option value="Firefox">Firefox</option>212 <option value="Edge">Edge</option>213 <option value="Brave">Brave</option>214 <option value="Safari">Safari</option>215 <option value="Opera">Opera</option>216 </select>217 </div>218 219 <div class="form-group">220 <label>Source d'alimentation</label>221 <div class="power-selector">222 <button type="button" class="power-btn active" id="btnSecteur" onclick="setPower('🔌 Secteur')">🔌 Secteur</button>223 <button type="button" class="power-btn" id="btnBatterie" onclick="setPower('🔋 Batterie')">🔋 Batterie</button>224 </div>225 </div>226 227 <hr style="border: 0; border-top: 1px solid #e2e8f0; margin: 10px 0;">228 229 <div style="height: 110px; position: relative; margin-bottom: 10px;">230 <canvas id="stressTestChart"></canvas>231 </div>232 233 <h4 style="margin: 10px 0 5px 0; color: #1a202c;">🏆 TOP 10 Global</h4>234 <table class="leaderboard-table">235 <thead>236 <tr>237 <th style="width: 8%">Rg</th>238 <th style="width: 42%">Configuration</th>239 <th style="width: 20%">TPS (Min/Max)</th>240 <th style="width: 15%">Moyenne</th>241 <th style="width: 15%">Tokens/Tps</th>242 </tr>243 </thead>244 <tbody id="leaderboardBody">245 </tbody>246 </table>247 </div>248 </div>249 250 </body>251</html>