1/** 2 * Relative speed meters in expanded AI model dropdown descriptions. 3 * 100 discrete steps, red (slow) â yellow â green (fast) vs other options 4 * in the same list, using historical completions from get-generation-times.php. 5 */ 6 7const cache = {}; 8 9function generatorFromPanel(panel) { 10 const selectId = panel.dataset.selectId || ''; 11 const container = document.querySelector(`.custom-dropdown[data-select-id="${selectId}"]`); 12 if (container && container.dataset.speedGenerator) return container.dataset.speedGenerator; 13 if (panel.dataset.speedGenerator) return panel.dataset.speedGenerator; 14 const path = (window.location.pathname || '').split('/').filter(Boolean); 15 return path[0] || ''; 16} 17 18async function loadTimes(generator) { 19 if (!generator) return {}; 20 if (cache[generator]) return cache[generator]; 21 cache[generator] = fetch('/apis/get-generation-times.php?list=1&generator=' + encodeURIComponent(generator)) 22 .then((r) => (r.ok ? r.json() : { times: {} })) 23 .then((data) => data.times || {}) 24 .catch(() => ({})); 25 return cache[generator]; 26} 27 28function optionTime(option, times) { 29 const ids = (option.dataset.speedIds || '').split(',').map((s) => s.trim()).filter(Boolean); 30 if (!ids.length) return null; 31 const vals = ids.map((id) => times[id]).filter((t) => typeof t === 'number' && t > 0); 32 if (!vals.length) return null; 33 return vals.reduce((a, b) => a + b, 0) / vals.length; 34} 35 36function scoreFromTimes(t, min, max) { 37 if (max <= min) return 100; 38 return Math.max(1, Math.min(100, Math.round(1 + 99 * (max - t) / (max - min)))); 39} 40 41function ensureSteps(meter) { 42 const track = meter.querySelector('.model-speed-steps'); 43 if (!track || track.childElementCount === 100) return track; 44 const frag = document.createDocumentFragment(); 45 for (let i = 1; i <= 100; i++) { 46 const step = document.createElement('span'); 47 step.className = 'model-speed-step'; 48 step.style.setProperty('--i', String(i)); 49 frag.appendChild(step); 50 } 51 track.appendChild(frag); 52 return track; 53} 54 55function paintMeter(meter, score) { 56 meter.hidden = false; 57 meter.dataset.score = String(score); 58 const valueEl = meter.querySelector('.model-speed-meter-value'); 59 if (valueEl) valueEl.textContent = String(score); 60 const track = ensureSteps(meter); 61 if (!track) return; 62 const steps = track.children; 63 for (let i = 0; i < steps.length; i++) { 64 const n = i + 1; 65 steps[i].classList.toggle('on', n <= score); 66 } 67} 68 69export async function hydrateModelSpeedMeters(panel) { 70 if (!panel) return; 71 const meters = panel.querySelectorAll('.model-speed-meter[data-speed-ids]'); 72 if (!meters.length) return; 73 74 const generator = generatorFromPanel(panel); 75 const times = await loadTimes(generator); 76 77 const optionTimes = []; 78 meters.forEach((meter) => { 79 const option = meter.closest('.custom-dropdown-option'); 80 const t = option ? optionTime(option, times) : null; 81 optionTimes.push({ meter, t }); 82 }); 83 84 const known = optionTimes.map((o) => o.t).filter((t) => t != null); 85 if (!known.length) { 86 meters.forEach((m) => { m.hidden = true; }); 87 return; 88 } 89 const min = Math.min(...known); 90 const max = Math.max(...known); 91 92 optionTimes.forEach(({ meter, t }) => { 93 if (t == null) { 94 meter.hidden = true; 95 return; 96 } 97 paintMeter(meter, scoreFromTimes(t, min, max)); 98 }); 99}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.