|
|
function searchModel() { |
|
|
const keyword = document.getElementById('searchKeyword').value.trim(); |
|
|
if (keyword) { |
|
|
const domain = window.location.hostname; |
|
|
const url = `https://${domain}/models?search=${encodeURIComponent(keyword)}`; |
|
|
window.location.href = url; |
|
|
} |
|
|
} |
|
|
|
|
|
let isDropdownNavigationActive = false; |
|
|
document.getElementById('searchKeyword').addEventListener('keypress', function (event) { |
|
|
if (event.keyCode === 13) { |
|
|
event.preventDefault(); |
|
|
if (!isDropdownNavigationActive) { |
|
|
searchModel(); |
|
|
} else { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const items = document.querySelectorAll('.search-result-item'); |
|
|
if (selectedIndex > -1) { |
|
|
items[selectedIndex].click(); |
|
|
} |
|
|
} |
|
|
isDropdownNavigationActive = false; |
|
|
} |
|
|
}); |
|
|
let debounceTimer; |
|
|
const searchInput = document.getElementById('searchKeyword'); |
|
|
const searchResults = document.getElementById('searchResults'); |
|
|
let abortController; |
|
|
|
|
|
function search(){ |
|
|
selectedIndex = -1; |
|
|
clearTimeout(debounceTimer); |
|
|
debounceTimer = setTimeout(async function () { |
|
|
const query = searchInput.value.trim(); |
|
|
if (query) { |
|
|
|
|
|
if (abortController) { |
|
|
abortController.abort(); |
|
|
} |
|
|
|
|
|
|
|
|
abortController = new AbortController(); |
|
|
sessionStorage.setItem('searchKeyword', query); |
|
|
try { |
|
|
const response = await fetch(`https://hf-mirror.com/api/quicksearch?q=${encodeURIComponent(query)}&type=model&type=dataset`, { signal: abortController.signal }); |
|
|
const data = await response.json(); |
|
|
let resultsHtml = ''; |
|
|
['models', 'datasets'].forEach(type => { |
|
|
if (data[type]) { |
|
|
resultsHtml += `<div class="search-result-type"><strong>${type}</strong></div>`; |
|
|
data[type].forEach(item => { |
|
|
resultsHtml += `<div class="search-result-item" onclick="openLink('/${type === 'models' ? '' : type + '/'}${item.id}')">${item.id}</div>`; |
|
|
}); |
|
|
} |
|
|
}); |
|
|
searchResults.innerHTML = resultsHtml; |
|
|
searchResults.style.display = 'block'; |
|
|
|
|
|
} catch (error) { |
|
|
if (error.name === 'AbortError') { |
|
|
|
|
|
} else { |
|
|
|
|
|
console.error(error); |
|
|
} |
|
|
} |
|
|
} else { |
|
|
searchResults.innerHTML = ''; |
|
|
searchResults.style.display = 'none'; |
|
|
} |
|
|
}, 300); |
|
|
} |
|
|
searchInput.addEventListener('input', function () { |
|
|
search(); |
|
|
}); |
|
|
|
|
|
|
|
|
searchInput.addEventListener('focus', function () { |
|
|
if (searchInput.value.trim()) { |
|
|
search(); |
|
|
|
|
|
} |
|
|
}); |
|
|
|
|
|
searchInput.addEventListener('blur', function () { |
|
|
setTimeout(function () { |
|
|
searchResults.style.display = 'none'; |
|
|
}, 2000); |
|
|
}); |
|
|
|
|
|
function openLink(url) { |
|
|
|
|
|
|
|
|
window.location.href = url; |
|
|
} |
|
|
|
|
|
function copyCode(btn) { |
|
|
const code = btn.previousElementSibling.textContent; |
|
|
navigator.clipboard.writeText(code).then(() => { |
|
|
btn.textContent = 'Copied!'; |
|
|
setTimeout(() => { |
|
|
btn.textContent = 'Copy'; |
|
|
}, 2000); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener("DOMContentLoaded", function () { |
|
|
const donateArea = document.querySelector('.donate'); |
|
|
const qrcode = document.querySelector('.qrcode'); |
|
|
|
|
|
|
|
|
const isMobile = /Mobi|Android/i.test(navigator.userAgent); |
|
|
|
|
|
if (isMobile) { |
|
|
|
|
|
donateArea.addEventListener('click', function () { |
|
|
qrcode.classList.toggle('show-qrcode'); |
|
|
}); |
|
|
} else { |
|
|
|
|
|
} |
|
|
}); |
|
|
|
|
|
document.addEventListener("DOMContentLoaded", function () { |
|
|
const donateArea = document.querySelector('.groups'); |
|
|
const qrcode = document.querySelector('.groups_qrcode'); |
|
|
|
|
|
|
|
|
const isMobile = /Mobi|Android/i.test(navigator.userAgent); |
|
|
|
|
|
if (isMobile) { |
|
|
|
|
|
donateArea.addEventListener('click', function () { |
|
|
qrcode.classList.toggle('show-qrcode'); |
|
|
}); |
|
|
} else { |
|
|
|
|
|
} |
|
|
}); |
|
|
|
|
|
let selectedIndex = -1; |
|
|
|
|
|
searchInput.addEventListener('keydown', function (event) { |
|
|
const items = document.querySelectorAll('.search-result-item'); |
|
|
if (event.key === 'ArrowDown') { |
|
|
isDropdownNavigationActive = true; |
|
|
|
|
|
if (selectedIndex < items.length - 1) { |
|
|
selectedIndex++; |
|
|
items[selectedIndex].classList.add('selected'); |
|
|
if (selectedIndex > 0) { |
|
|
items[selectedIndex - 1].classList.remove('selected'); |
|
|
} |
|
|
} |
|
|
} else if (event.key === 'ArrowUp') { |
|
|
isDropdownNavigationActive = true; |
|
|
|
|
|
if (selectedIndex > 0) { |
|
|
selectedIndex--; |
|
|
items[selectedIndex].classList.add('selected'); |
|
|
items[selectedIndex + 1].classList.remove('selected'); |
|
|
} |
|
|
} else if (event.key === 'Enter') { |
|
|
|
|
|
if (selectedIndex > -1) { |
|
|
items[selectedIndex].click(); |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
window.addEventListener('pageshow', function () { |
|
|
const storedKeyword = sessionStorage.getItem('searchKeyword'); |
|
|
if (storedKeyword !== null) { |
|
|
|
|
|
searchInput.value = storedKeyword; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
}); |
|
|
|
|
|
const config = { |
|
|
models: { |
|
|
url: "https://hf-mirror.com/models-json?sort=trending", |
|
|
containerSelector: '.models ul', |
|
|
itemTemplate: (item) => ` |
|
|
<span class="model-id">${item.id}</span> |
|
|
<div class="model-info"> |
|
|
<span class="model-downloads">⬇️${item.downloads}</span> |
|
|
<span class="model-likes">❤️${item.likes}</span> |
|
|
</div> |
|
|
` |
|
|
}, |
|
|
datasets: { |
|
|
url: "https://hf-mirror.com/models-json?sort=trending", |
|
|
containerSelector: '.dataset ul', |
|
|
itemTemplate: (item) => ` |
|
|
<span class="model-id">${item.id}</span> |
|
|
<div class="model-info"> |
|
|
<span class="model-downloads">⬇️${item.downloads}</span> |
|
|
<span class="model-likes">❤️${item.likes}</span> |
|
|
</div> |
|
|
` |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
function fetchAndRenderList({ url, containerSelector, itemTemplate }, limit = 10) { |
|
|
fetch(url) |
|
|
.then(response => response.json()) |
|
|
.then(data => { |
|
|
const items = data["models"]; |
|
|
if (!items || !Array.isArray(items)) { |
|
|
throw new Error('Invalid data format'); |
|
|
} |
|
|
const container = document.querySelector(containerSelector); |
|
|
if (!container) { |
|
|
throw new Error('List container element not found'); |
|
|
} |
|
|
container.innerHTML = ''; |
|
|
items.slice(0, limit).forEach(item => { |
|
|
const li = document.createElement('li'); |
|
|
li.innerHTML = itemTemplate(item); |
|
|
container.appendChild(li); |
|
|
}); |
|
|
}) |
|
|
.catch(error => { |
|
|
console.error('Error:', error); |
|
|
}); |
|
|
} |
|
|
|
|
|
function timeAgo(date) { |
|
|
const now = new Date(); |
|
|
const updatedDate = new Date(date); |
|
|
const diffTime = Math.abs(now - updatedDate); |
|
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); |
|
|
if (diffDays < 60) { |
|
|
return `${diffDays}天前更新`; |
|
|
} else { |
|
|
|
|
|
const year = updatedDate.getFullYear(); |
|
|
let month = updatedDate.getMonth() + 1; |
|
|
let day = updatedDate.getDate(); |
|
|
|
|
|
|
|
|
month = month < 10 ? '0' + month : month; |
|
|
day = day < 10 ? '0' + day : day; |
|
|
|
|
|
|
|
|
return `${year}-${month}-${day}更新`; |
|
|
} |
|
|
} |
|
|
|
|
|
function abbreviateNumber(num) { |
|
|
if (num >= 1000000) { |
|
|
return `${(num / 1000000).toFixed(1)}M`; |
|
|
} else if (num >= 1000) { |
|
|
return `${(num / 1000).toFixed(1)}k`; |
|
|
} else { |
|
|
return num; |
|
|
} |
|
|
} |
|
|
let isExpanded = false; |
|
|
let maxItemsToShow = 3; |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function () { |
|
|
const screenWidth = window.innerWidth; |
|
|
if (sessionStorage.getItem('isExpanded')){ |
|
|
isExpanded = sessionStorage.getItem('isExpanded') === 'true'; |
|
|
} |
|
|
else { |
|
|
isExpanded = screenWidth >= 650; |
|
|
sessionStorage.setItem('isExpanded', isExpanded); |
|
|
} |
|
|
|
|
|
|
|
|
async function fetchTrending(type) { |
|
|
let data = sessionStorage.getItem(`rankingList_${type}`); |
|
|
let expiry = sessionStorage.getItem(`expiry_ranking`); |
|
|
const now = new Date().getTime(); |
|
|
|
|
|
if (!data || !expiry || now >= expiry) { |
|
|
const response = await fetch(`https://hf-mirror.com/api/trending?limit=10&type=${type}`); |
|
|
data = await response.json(); |
|
|
expiry = new Date().getTime() + 1 * 60 * 1000; |
|
|
sessionStorage.setItem(`rankingList_${type}`, JSON.stringify(data)); |
|
|
sessionStorage.setItem(`expiry_ranking`, expiry); |
|
|
} |
|
|
else { |
|
|
data = JSON.parse(data); |
|
|
} |
|
|
const filteredData = data.recentlyTrending.filter(item => item.repoType !== 'space'); |
|
|
lastScrollPosition = 0; |
|
|
updateTrendingItems(filteredData.slice(0, 10)); |
|
|
} |
|
|
|
|
|
function updateTrendingItems(items) { |
|
|
const container = document.getElementById('trendingItems'); |
|
|
container.innerHTML = ''; |
|
|
|
|
|
maxItemsToShow = screenWidth > 650 ? 10: 3; |
|
|
items.forEach((item, index) => { |
|
|
const element = createTrendingItemElement(item, index); |
|
|
container.appendChild(element); |
|
|
}); |
|
|
|
|
|
updateToggleButton(); |
|
|
} |
|
|
|
|
|
function createTrendingItemElement(item, index) { |
|
|
const element = document.createElement('div'); |
|
|
element.className = 'trending-item'; |
|
|
if (index >= maxItemsToShow && !isExpanded) { |
|
|
element.classList.add('hidden-item'); |
|
|
} |
|
|
element.innerHTML = getTrendingItemHTML(item, index); |
|
|
return element; |
|
|
} |
|
|
|
|
|
function getTrendingItemHTML(item, index) { |
|
|
const repoId = item.repoData.id; |
|
|
const repoLink = item.repoType === 'model' ? repoId : `/${item.repoType}s/${repoId}`; |
|
|
const pipeline_tag = item.repoData.pipeline_tag; |
|
|
const pipelineTagHTML = pipeline_tag ? `<a class="pipeline-tag" href="/models?pipeline_tag=${pipeline_tag}">${pipeline_tag}</a>` : ''; |
|
|
const downloadCountHTML = item.repoData.downloads ? `<span class="count">${abbreviateNumber(item.repoData.downloads)}</span>` : `<span class="count">-</span>`; |
|
|
let logoHTML; |
|
|
switch (item.repoType) { |
|
|
case 'model': |
|
|
logoHTML = `<img class="model-logo" src="${item.repoData.authorData.avatarUrl}" alt="${item.repoData.author}" title="${item.repoData.authorData.fullname}">`; |
|
|
break; |
|
|
case 'dataset': |
|
|
logoHTML = `<svg class="model-logo" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 25 25"><ellipse cx="12.5" cy="5" fill="currentColor" fill-opacity="0.25" rx="7.5" ry="2"></ellipse><path d="M12.5 15C16.6421 15 20 14.1046 20 13V20C20 21.1046 16.6421 22 12.5 22C8.35786 22 5 21.1046 5 20V13C5 14.1046 8.35786 15 12.5 15Z" fill="currentColor" opacity="0.5"></path><path d="M12.5 7C16.6421 7 20 6.10457 20 5V11.5C20 12.6046 16.6421 13.5 12.5 13.5C8.35786 13.5 5 12.6046 5 11.5V5C5 6.10457 8.35786 7 12.5 7Z" fill="currentColor" opacity="0.5"></path><path d="M5.23628 12C5.08204 12.1598 5 12.8273 5 13C5 14.1046 8.35786 15 12.5 15C16.6421 15 20 14.1046 20 13C20 12.8273 19.918 12.1598 19.7637 12C18.9311 12.8626 15.9947 13.5 12.5 13.5C9.0053 13.5 6.06886 12.8626 5.23628 12Z" fill="currentColor"></path></svg>`; |
|
|
break; |
|
|
case 'space': |
|
|
logoHTML = `<img class="model-logo" src="${item.repoData.authorData.avatarUrl}" alt="${item.repoData.author}">`; |
|
|
break; |
|
|
default: |
|
|
console.log('BUG'); |
|
|
} |
|
|
return ` |
|
|
<div class="repo-content"> |
|
|
<div class="item-rank">#${index + 1}</div> |
|
|
${logoHTML} |
|
|
<div class="item-info"> |
|
|
<a class= "repo-link" href="${repoLink}"> |
|
|
<div class="model-name">${repoId}</div> |
|
|
</a> |
|
|
<div class="stats-line"> |
|
|
${pipelineTagHTML} |
|
|
<div class="other-stats"> |
|
|
<div class="stats-detail"> |
|
|
<span>${timeAgo(item.repoData.lastModified)}</span> |
|
|
</div> |
|
|
<div class="stats-icons"> |
|
|
<span class="downloads"> |
|
|
<span class="svg-placeholder"><svg class="flex-none w-3 text-gray-400 mr-0.5" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" role="img" width="1em" height="1em" viewBox="0 0 32 32"><path fill="currentColor" d="M26 24v4H6v-4H4v4a2 2 0 0 0 2 2h20a2 2 0 0 0 2-2v-4zm0-10l-1.41-1.41L17 20.17V2h-2v18.17l-7.59-7.58L6 14l10 10l10-10z"></path></svg></span> |
|
|
<span class="count">${downloadCountHTML}</span> |
|
|
</span> |
|
|
<span class="likes"> |
|
|
<span class="svg-placeholder"><svg class="flex-none w-3 text-gray-400 mr-1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" role="img" width="1em" height="1em" preserveAspectRatio="xMidYMid meet" viewBox="0 0 32 32" fill="currentColor"><path d="M22.45,6a5.47,5.47,0,0,1,3.91,1.64,5.7,5.7,0,0,1,0,8L16,26.13,5.64,15.64a5.7,5.7,0,0,1,0-8,5.48,5.48,0,0,1,7.82,0L16,10.24l2.53-2.58A5.44,5.44,0,0,1,22.45,6m0-2a7.47,7.47,0,0,0-5.34,2.24L16,7.36,14.89,6.24a7.49,7.49,0,0,0-10.68,0,7.72,7.72,0,0,0,0,10.82L16,29,27.79,17.06a7.72,7.72,0,0,0,0-10.82A7.49,7.49,0,0,0,22.45,4Z"></path></svg></span> |
|
|
<span class="count">${abbreviateNumber(item.likes)}</span> |
|
|
</span> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div> |
|
|
`; |
|
|
} |
|
|
|
|
|
let lastScrollPosition = 0; |
|
|
function updateToggleButton() { |
|
|
const toggleButton = document.getElementById('toggleButton'); |
|
|
if (!isExpanded) { |
|
|
window.scrollTo({ top: lastScrollPosition, behavior: 'smooth' }); |
|
|
} |
|
|
toggleButton.classList.toggle('hidden', isExpanded); |
|
|
toggleButton.innerText = isExpanded ? '收起' : '展开'; |
|
|
if (isExpanded) { |
|
|
lastScrollPosition = window.scrollY; |
|
|
} |
|
|
} |
|
|
|
|
|
document.getElementById('toggleButton').addEventListener('click', toggleVisibility); |
|
|
document.querySelectorAll('.tab').forEach(tab => tab.addEventListener('click', handleTabClick)); |
|
|
|
|
|
|
|
|
const tabs = document.querySelectorAll('.tab'); |
|
|
const savedTabType = sessionStorage.getItem('activeTabType'); |
|
|
if (savedTabType) { |
|
|
tabs.forEach(tab => { |
|
|
if (tab.getAttribute('data-type') === savedTabType) { |
|
|
tab.click(); |
|
|
} |
|
|
}); |
|
|
} |
|
|
else if (tabs.length > 0) { |
|
|
tabs[0].click(); |
|
|
} |
|
|
|
|
|
function toggleVisibility() { |
|
|
isExpanded = !isExpanded; |
|
|
sessionStorage.setItem('isExpanded', isExpanded); |
|
|
document.querySelectorAll('.trending-item').forEach((item, index) => { |
|
|
if (index >= maxItemsToShow) item.classList.toggle('hidden-item', !isExpanded); |
|
|
}); |
|
|
updateToggleButton(); |
|
|
} |
|
|
|
|
|
function handleTabClick() { |
|
|
if (this.classList.contains('active')) return; |
|
|
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); |
|
|
this.classList.add('active'); |
|
|
const type = this.getAttribute('data-type'); |
|
|
|
|
|
sessionStorage.setItem('activeTabType', type); |
|
|
displayMoreButton(type); |
|
|
fetchTrending(type); |
|
|
} |
|
|
|
|
|
function displayMoreButton(type) { |
|
|
let moreItemsLink = document.getElementById('moreItems'); |
|
|
if (type === 'all') { |
|
|
|
|
|
} else { |
|
|
moreItemsLink.style.display = 'block'; |
|
|
let href = '/' + type + 's'; |
|
|
moreItemsLink.setAttribute('href', href); |
|
|
} |
|
|
} |
|
|
|
|
|
window.addEventListener('resize', adjustDisplayBasedOnWidth); |
|
|
}); |
|
|
|
|
|
function adjustDisplayBasedOnWidth() { |
|
|
const screenWidth = window.innerWidth; |
|
|
|
|
|
maxItemsToShow = screenWidth > 650 ? 10: 3; |
|
|
const items = document.querySelectorAll('.trending-item'); |
|
|
items.forEach((item, index) => { |
|
|
if (index < maxItemsToShow || isExpanded) { |
|
|
item.classList.remove('hidden-item'); |
|
|
} else { |
|
|
item.classList.add('hidden-item'); |
|
|
} |
|
|
}); |
|
|
toggleButton.innerText = isExpanded ? '收起' : '展开'; |
|
|
} |
|
|
|
|
|
window.addEventListener('beforeunload', function () { |
|
|
sessionStorage.setItem('expiry_ranking', new Date().getTime()); |
|
|
}); |