// ---- VRAM categories ----
const CATEGORIES = [
{ key: "6gb", label: "6GB VRAM" },
{ key: "12gb", label: "12GB VRAM" },
{ key: "16gb", label: "16GB VRAM" },
{ key: "24gb", label: "24GB VRAM" },
{ key: "48gb", label: "48GB VRAM" },
{ key: "72gb", label: "72GB VRAM" },
{ key: "96gb", label: "96GB VRAM" }
];
// ---- State management ----
const state = {
activeCategory: CATEGORIES[0].key,
sortOption: 'votes', // 'votes', 'newest', 'oldest'
data: {},
lastVotedIds: {},
refreshInterval: null,
pollInterval: 10000, // 10 seconds
};
// ---- helpers ----
async function api(url, data) {
const opts = data ? {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
} : {};
const res = await fetch(url, opts);
const json = await res.json();
if (!res.ok) throw new Error(json.error || 'Server error');
return json;
}
function calculatePercentage(votes, totalVotes) {
if (totalVotes === 0) return 0;
return Math.round((votes / totalVotes) * 100);
}
function getTotalVotes(entries) {
return entries.reduce((sum, entry) => sum + entry.votes, 0);
}
function formatNumber(num) {
if (num >= 1000000) {
return (num / 1000000).toFixed(1) + 'M';
} else if (num >= 1000) {
return (num / 1000).toFixed(1) + 'K';
}
return num.toString();
}
function sortEntries(entries, sortOption) {
const entriesCopy = [...entries];
if (sortOption === 'votes') {
return entriesCopy.sort((a, b) => {
// First sort by votes (descending)
const votesDiff = b.votes - a.votes;
if (votesDiff !== 0) return votesDiff;
// If votes are equal, sort by id (newest first)
return parseInt(b.id) - parseInt(a.id);
});
} else if (sortOption === 'newest') {
return entriesCopy.sort((a, b) => parseInt(b.id) - parseInt(a.id));
} else if (sortOption === 'oldest') {
return entriesCopy.sort((a, b) => parseInt(a.id) - parseInt(b.id));
}
// Default to votes sorting
return entriesCopy.sort((a, b) => b.votes - a.votes);
}
// ---- rendering ----
function createCategoryTabs() {
const tabsContainer = document.getElementById('category-tabs');
tabsContainer.innerHTML = '';
CATEGORIES.forEach(category => {
const tab = document.createElement('div');
tab.className = `tab ${category.key === state.activeCategory ? 'active' : ''}`;
tab.setAttribute('data-category', category.key);
tab.textContent = category.label;
tabsContainer.appendChild(tab);
});
}
function createLeaderboardSection(category) {
const section = document.createElement('section');
section.className = `leaderboard-section ${category.key === state.activeCategory ? 'active' : ''}`;
section.id = `section-${category.key}`;
section.innerHTML = `
`;
return section;
}
function renderPollItems(category) {
const container = document.getElementById(`poll-items-${category}`);
if (!container) return;
container.innerHTML = '';
const entries = state.data[category] || [];
if (entries.length === 0) {
container.innerHTML = 'No entries yet. Be the first to add one!
';
return;
}
const sortedEntries = sortEntries(entries, state.sortOption);
const totalVotes = getTotalVotes(sortedEntries);
sortedEntries.forEach((entry, index) => {
const percentage = calculatePercentage(entry.votes, totalVotes);
const isVoted = state.lastVotedIds[category] === entry.id;
const rankClass = index < 3 ? `rank-${index + 1}` : '';
const pollItem = document.createElement('div');
pollItem.className = `poll-item ${isVoted ? 'voted' : ''}`;
pollItem.setAttribute('data-id', entry.id);
if (state.lastVotedIds[category] === entry.id) {
pollItem.classList.add('highlight');
// Remove highlight class after animation completes
setTimeout(() => {
pollItem.classList.remove('highlight');
}, 1000);
}
pollItem.innerHTML = `
`;
container.appendChild(pollItem);
});
}
async function refreshData(category, highlightChanges = false) {
try {
const entries = await api(`/api/entries?category=${category}`);
// Store previous data for comparison if highlighting changes
const prevEntries = state.data[category] || [];
// Update state
state.data[category] = entries;
// Render the updated data
renderPollItems(category);
// Highlight changes if needed
if (highlightChanges && prevEntries.length > 0) {
entries.forEach(entry => {
const prevEntry = prevEntries.find(e => e.id === entry.id);
if (prevEntry && prevEntry.votes !== entry.votes) {
const pollItem = document.querySelector(`.poll-item[data-id="${entry.id}"]`);
if (pollItem) {
pollItem.classList.add('highlight');
setTimeout(() => {
pollItem.classList.remove('highlight');
}, 1000);
}
}
});
}
} catch (err) {
console.error(`Error refreshing ${category}:`, err);
}
}
function setupPolling() {
// Clear any existing interval
if (state.refreshInterval) {
clearInterval(state.refreshInterval);
}
// Set up new polling interval
state.refreshInterval = setInterval(() => {
refreshData(state.activeCategory, true);
}, state.pollInterval);
}
// ---- event handlers ----
function handleCategoryChange(category) {
// Update active category
state.activeCategory = category;
// Update UI
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.toggle('active', tab.getAttribute('data-category') === category);
});
document.querySelectorAll('.leaderboard-section').forEach(section => {
section.classList.toggle('active', section.id === `section-${category}`);
});
// Refresh data for the new category
refreshData(category);
}
function handleSortChange(sortOption) {
// Update sort option
state.sortOption = sortOption;
// Update UI
document.querySelectorAll('.sort-option').forEach(btn => {
btn.classList.remove('active');
if (btn.getAttribute('data-sort') === sortOption) {
btn.classList.add('active');
}
});
// Re-render with new sort
refreshData(state.activeCategory, false).then(() => {
renderPollItems(state.activeCategory);
});
}
// Hugging Face API validation
let debounceTimer;
let selectedModel = null;
async function validateWithHuggingFace(query) {
if (!query || query.length < 2) return [];
try {
// Use our server-side proxy endpoint to avoid CORS issues
const response = await fetch(`/api/huggingface/models?query=${encodeURIComponent(query)}`);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Failed to fetch from Hugging Face API');
}
return await response.json();
} catch (error) {
console.error('Hugging Face API error:', error);
return [];
}
}
function setupModelValidation(form) {
const input = form.querySelector('.add-input');
const dropdownContainer = form.querySelector('.dropdown-container');
const dropdownResults = form.querySelector('.dropdown-results');
const dropdownLoading = form.querySelector('.dropdown-loading');
const submitBtn = form.querySelector('.add-btn');
const validationIndicator = form.querySelector('.validation-indicator');
input.addEventListener('input', function() {
const query = this.value.trim();
selectedModel = null;
// Reset validation state
validationIndicator.className = 'validation-indicator';
submitBtn.disabled = true;
// Clear previous results
dropdownResults.innerHTML = '';
dropdownResults.classList.add('hidden');
if (query.length < 2) return;
// Show loading indicator
dropdownLoading.classList.remove('hidden');
// Debounce API calls
clearTimeout(debounceTimer);
debounceTimer = setTimeout(async () => {
try {
const results = await validateWithHuggingFace(query);
// Hide loading indicator
dropdownLoading.classList.add('hidden');
if (results.length === 0) {
dropdownResults.innerHTML = 'No matching models found';
dropdownResults.classList.remove('hidden');
return;
}
// Populate dropdown with results
results.forEach(model => {
const li = document.createElement('li');
li.className = 'dropdown-item';
// Create a more informative display with model name and author
const displayName = model.modelId || model.id || model.name;
const authorInfo = model.author && model.author !== 'Unknown' ? ` by ${model.author}` : '';
li.innerHTML = `
${displayName}
${authorInfo ? `${authorInfo}
` : ''}
`;
li.addEventListener('click', () => {
input.value = displayName;
selectedModel = model;
dropdownResults.classList.add('hidden');
// Show validation success
validationIndicator.className = 'validation-indicator valid';
submitBtn.disabled = false;
});
dropdownResults.appendChild(li);
});
dropdownResults.classList.remove('hidden');
} catch (error) {
console.error('Validation error:', error);
dropdownLoading.classList.add('hidden');
// Show validation error
validationIndicator.className = 'validation-indicator invalid';
}
}, 300);
});
// Hide dropdown when clicking outside
document.addEventListener('click', (e) => {
if (!dropdownContainer.contains(e.target)) {
dropdownResults.classList.add('hidden');
}
});
// Prevent form submission when pressing Enter in the input field
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !selectedModel) {
e.preventDefault();
}
});
}
async function handleAddEntry(form) {
const category = form.getAttribute('data-category');
const input = form.querySelector('.add-input');
const errorSpan = form.querySelector('.error');
const name = input.value.trim();
errorSpan.textContent = '';
if (!name) {
errorSpan.textContent = 'Please enter a name';
return;
}
if (!selectedModel) {
errorSpan.textContent = 'Please select a validated model from the dropdown';
return;
}
try {
const entry = await api('/api/add', { name, category });
input.value = '';
selectedModel = null;
// Reset validation state
form.querySelector('.validation-indicator').className = 'validation-indicator';
form.querySelector('.add-btn').disabled = true;
// Update state
state.lastVotedIds[category] = entry.id;
// Refresh data
await refreshData(category);
} catch (err) {
errorSpan.textContent = err.message;
}
}
async function handleVote(btn) {
const id = btn.getAttribute('data-id');
const category = btn.getAttribute('data-category');
try {
await api('/api/vote', { id, category });
// Update state
state.lastVotedIds[category] = id;
// Refresh data
await refreshData(category);
} catch (err) {
alert(err.message);
}
}
// ---- main ----
window.addEventListener('DOMContentLoaded', () => {
const leaderboardsDiv = document.getElementById('leaderboards');
leaderboardsDiv.innerHTML = '';
// Create category tabs
createCategoryTabs();
// Render all leaderboard sections
CATEGORIES.forEach(cat => {
const section = createLeaderboardSection(cat);
leaderboardsDiv.appendChild(section);
});
// Set up model validation for all forms
document.querySelectorAll('.add-form').forEach(form => {
setupModelValidation(form);
});
// Initial data load
CATEGORIES.forEach(cat => {
refreshData(cat.key);
});
// Set up polling for real-time updates
setupPolling();
// Tab click handler
document.getElementById('category-tabs').addEventListener('click', e => {
if (e.target.classList.contains('tab')) {
const category = e.target.getAttribute('data-category');
handleCategoryChange(category);
}
});
// Sort option click handler
leaderboardsDiv.addEventListener('click', e => {
if (e.target.classList.contains('sort-option')) {
const sortOption = e.target.getAttribute('data-sort');
if (sortOption && sortOption !== state.sortOption) {
handleSortChange(sortOption);
}
}
});
// Add entry form handler
leaderboardsDiv.addEventListener('submit', async e => {
if (e.target.classList.contains('add-form')) {
e.preventDefault();
await handleAddEntry(e.target);
}
});
// Vote button handler
leaderboardsDiv.addEventListener('click', async e => {
if (e.target.classList.contains('vote-btn') && !e.target.disabled) {
await handleVote(e.target);
}
});
});
// Clean up polling on page unload
window.addEventListener('beforeunload', () => {
if (state.refreshInterval) {
clearInterval(state.refreshInterval);
}
});