| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| <title>Hub Semantic Search</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://unpkg.com/lucide@latest"></script> |
| <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"></script> |
| </head> |
| <body> |
| <div class="w-full max-w-4xl mx-auto p-4 space-y-8"> |
| <h1 class="text-3xl font-bold text-gray-800">Hub Semantic Search</h1> |
|
|
| <div |
| class="bg-gradient-to-br from-blue-50 to-indigo-50 p-6 rounded-xl shadow-sm border border-blue-100 mb-6" |
| > |
| <h2 |
| class="text-lg font-semibold mb-2 text-gray-800 flex items-center gap-2" |
| > |
| <i data-lucide="search" class="text-blue-500"></i> |
| Welcome to Hub Semantic Search |
| </h2> |
| <p class="text-gray-700 mb-2 text-sm"> |
| Find and explore the 🤗 Hub using via semantic search on LLM generated |
| summaries! Summaries are generated using |
| <a |
| href="https://huggingface.co/davanstrien/Smol-Hub-tldr" |
| target="_blank" |
| class="text-blue-500 hover:text-blue-700 hover:underline" |
| > |
| Smol-Hub-tldr </a |
| >. |
| </p> |
|
|
| <div |
| class="bg-blue-100 text-blue-800 px-3 py-1.5 rounded-md mb-2 text-sm" |
| > |
| <p class="flex items-center gap-2"> |
| <i data-lucide="info"></i> Search for datasets and models using |
| semantic search! |
| </p> |
| </div> |
|
|
| <button |
| onclick="toggleAccordion()" |
| id="accordionButton" |
| class="text-blue-500 hover:text-blue-700 flex items-center gap-2 text-sm" |
| > |
| <i |
| data-lucide="chevron-right" |
| id="accordionIcon" |
| class="transition-transform" |
| ></i> |
| <span>How it works</span> |
| </button> |
|
|
| <div id="accordionContent" class="hidden"> |
| <ul |
| class="list-disc list-inside space-y-1 text-gray-600 ml-4 mt-2 text-sm" |
| > |
| <li> |
| <strong>AI-Generated Summaries:</strong> Each dataset is indexed |
| using a concise summary generated by an LLM |
| </li> |
| <li> |
| <strong>Semantic Search:</strong> Find semantically similar |
| resources based on these summaries |
| </li> |
| <li> |
| <strong>Find Similar:</strong> Discover related resources using |
| semantic matching |
| </li> |
| </ul> |
| </div> |
| </div> |
|
|
| <div class="tabs w-full"> |
| <div class="tab-list flex gap-2 border-b mb-6"> |
| <button |
| onclick="switchTab('search')" |
| id="searchTab" |
| class="tab-trigger active px-4 sm:px-6 py-3 flex items-center gap-2 border-b-2 border-transparent hover:bg-gray-50 transition-colors flex-1 justify-center" |
| > |
| <i data-lucide="search"></i> Search |
| </button> |
| <button |
| onclick="switchTab('similar')" |
| id="similarTab" |
| class="tab-trigger px-4 sm:px-6 py-3 flex items-center gap-2 border-b-2 border-transparent hover:bg-gray-50 transition-colors flex-1 justify-center" |
| > |
| <i data-lucide="arrow-right"></i> Find Similar |
| </button> |
| <button |
| onclick="switchTab('trending')" |
| id="trendingTab" |
| class="tab-trigger px-4 sm:px-6 py-3 flex items-center gap-2 border-b-2 border-transparent hover:bg-gray-50 transition-colors flex-1 justify-center" |
| > |
| <i data-lucide="trending-up"></i> Trending |
| </button> |
| </div> |
|
|
| <div id="searchContent" class="tab-content space-y-4"> |
| <div |
| class="card bg-white p-8 rounded-xl shadow-sm border border-gray-100" |
| > |
| <div |
| class="flex flex-col sm:flex-row gap-4 items-start sm:items-center justify-between mb-4" |
| > |
| <p class="text-gray-600"> |
| Enter keywords to search through descriptions. The search will |
| automatically update as you type. |
| </p> |
| <div class="flex flex-wrap gap-2"> |
| <select |
| id="searchTypeSelect" |
| class="text-sm border rounded-lg px-3 py-2 bg-white text-gray-700 focus:ring-2 focus:ring-blue-100 focus:border-blue-300 transition-all outline-none" |
| onchange="selectType(this.value)" |
| > |
| <option value="datasets">Datasets</option> |
| <option value="models">Models</option> |
| </select> |
| <select |
| id="searchSortSelect" |
| class="text-sm border rounded-lg px-3 py-2 bg-white text-gray-700 focus:ring-2 focus:ring-blue-100 focus:border-blue-300 transition-all outline-none" |
| onchange="handleFilterChange('search')" |
| > |
| <option value="similarity">Sort by relevance</option> |
| <option value="likes">Sort by likes</option> |
| <option value="downloads">Sort by downloads</option> |
| <option value="trending">Sort by trending</option> |
| </select> |
| <div class="relative"> |
| <button |
| onclick="toggleFilters('search')" |
| class="text-sm border rounded-lg px-3 py-2 bg-white text-gray-700 hover:bg-gray-50 focus:ring-2 focus:ring-blue-100 focus:border-blue-300 transition-all outline-none flex items-center gap-2" |
| > |
| <i data-lucide="filter"></i> |
| Filters |
| <span |
| id="searchActiveFilters" |
| class="hidden px-1.5 py-0.5 bg-blue-100 text-blue-700 text-xs rounded-full" |
| ></span> |
| </button> |
| <div |
| id="searchFiltersPopover" |
| class="hidden absolute right-0 mt-2 w-64 bg-white border border-gray-200 rounded-lg shadow-lg p-4 z-10" |
| > |
| <div class="space-y-4"> |
| <div> |
| <label |
| class="block text-sm font-medium text-gray-700 mb-1" |
| >Minimum Likes</label |
| > |
| <input |
| type="number" |
| id="searchMinLikes" |
| placeholder="0" |
| min="0" |
| class="w-full text-sm border rounded-lg px-3 py-2 bg-white text-gray-700 focus:ring-2 focus:ring-blue-100 focus:border-blue-300 transition-all outline-none" |
| onchange="handleFilterChange('search')" |
| /> |
| </div> |
| <div> |
| <label |
| class="block text-sm font-medium text-gray-700 mb-1" |
| >Minimum Downloads</label |
| > |
| <input |
| type="number" |
| id="searchMinDownloads" |
| placeholder="0" |
| min="0" |
| class="w-full text-sm border rounded-lg px-3 py-2 bg-white text-gray-700 focus:ring-2 focus:ring-blue-100 focus:border-blue-300 transition-all outline-none" |
| onchange="handleFilterChange('search')" |
| /> |
| </div> |
| <div id="searchParamRange" class="hidden"> |
| <label |
| class="block text-sm font-medium text-gray-700 mb-1" |
| >Parameter Range</label |
| > |
| <select |
| id="searchParamRangeSelect" |
| class="w-full text-sm border rounded-lg px-3 py-2 bg-white text-gray-700 focus:ring-2 focus:ring-blue-100 focus:border-blue-300 transition-all outline-none" |
| onchange="handleFilterChange('search')" |
| > |
| <option value="any">Any</option> |
| <option value="0-1">0-1B</option> |
| <option value="1-7">1-7B</option> |
| <option value="7-20">7-20B</option> |
| <option value="20-70">20-70B</option> |
| <option value="70+">70B+</option> |
| </select> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| <div class="relative"> |
| <input |
| type="text" |
| id="searchInput" |
| placeholder="Type to search (minimum 3 characters)..." |
| class="w-full p-3 border rounded-lg pr-10 focus:ring-2 focus:ring-blue-100 focus:border-blue-300 transition-all outline-none" |
| /> |
| <div id="searchLoader" class="hidden absolute right-3 top-2"> |
| <i data-lucide="loader-2" class="animate-spin"></i> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <div id="similarContent" class="hidden tab-content space-y-4"> |
| <div |
| class="card bg-white p-8 rounded-xl shadow-sm border border-gray-100" |
| > |
| <div |
| class="flex flex-col sm:flex-row gap-4 items-start sm:items-center justify-between mb-4" |
| > |
| <p class="text-gray-600"> |
| Enter an ID to find similar resources. Popular items will appear |
| as you type. |
| </p> |
| <div class="flex flex-wrap gap-2"> |
| <select |
| id="similarTypeSelect" |
| class="text-sm border rounded-lg px-3 py-2 bg-white text-gray-700 focus:ring-2 focus:ring-blue-100 focus:border-blue-300 transition-all outline-none" |
| onchange="selectType(this.value)" |
| > |
| <option value="datasets">Datasets</option> |
| <option value="models">Models</option> |
| </select> |
| <select |
| id="similarSortSelect" |
| class="text-sm border rounded-lg px-3 py-2 bg-white text-gray-700 focus:ring-2 focus:ring-blue-100 focus:border-blue-300 transition-all outline-none" |
| onchange="handleFilterChange('similar')" |
| > |
| <option value="similarity">Sort by relevance</option> |
| <option value="likes">Sort by likes</option> |
| <option value="downloads">Sort by downloads</option> |
| <option value="trending">Sort by trending</option> |
| </select> |
| <div class="relative"> |
| <button |
| onclick="toggleFilters('similar')" |
| class="text-sm border rounded-lg px-3 py-2 bg-white text-gray-700 hover:bg-gray-50 focus:ring-2 focus:ring-blue-100 focus:border-blue-300 transition-all outline-none flex items-center gap-2" |
| > |
| <i data-lucide="filter"></i> |
| Filters |
| <span |
| id="similarActiveFilters" |
| class="hidden px-1.5 py-0.5 bg-blue-100 text-blue-700 text-xs rounded-full" |
| ></span> |
| </button> |
| <div |
| id="similarFiltersPopover" |
| class="hidden absolute right-0 mt-2 w-64 bg-white border border-gray-200 rounded-lg shadow-lg p-4 z-10" |
| > |
| <div class="space-y-4"> |
| <div> |
| <label |
| class="block text-sm font-medium text-gray-700 mb-1" |
| >Minimum Likes</label |
| > |
| <input |
| type="number" |
| id="similarMinLikes" |
| placeholder="0" |
| min="0" |
| class="w-full text-sm border rounded-lg px-3 py-2 bg-white text-gray-700 focus:ring-2 focus:ring-blue-100 focus:border-blue-300 transition-all outline-none" |
| onchange="handleFilterChange('similar')" |
| /> |
| </div> |
| <div> |
| <label |
| class="block text-sm font-medium text-gray-700 mb-1" |
| >Minimum Downloads</label |
| > |
| <input |
| type="number" |
| id="similarMinDownloads" |
| placeholder="0" |
| min="0" |
| class="w-full text-sm border rounded-lg px-3 py-2 bg-white text-gray-700 focus:ring-2 focus:ring-blue-100 focus:border-blue-300 transition-all outline-none" |
| onchange="handleFilterChange('similar')" |
| /> |
| </div> |
| <div id="similarParamRange" class="hidden"> |
| <label |
| class="block text-sm font-medium text-gray-700 mb-1" |
| >Parameter Range</label |
| > |
| <select |
| id="similarParamRangeSelect" |
| class="w-full text-sm border rounded-lg px-3 py-2 bg-white text-gray-700 focus:ring-2 focus:ring-blue-100 focus:border-blue-300 transition-all outline-none" |
| onchange="handleFilterChange('similar')" |
| > |
| <option value="any">Any</option> |
| <option value="0-1">0-1B</option> |
| <option value="1-7">1-7B</option> |
| <option value="7-20">7-20B</option> |
| <option value="20-70">20-70B</option> |
| <option value="70+">70B+</option> |
| </select> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| <div class="flex gap-3"> |
| <div class="relative w-full"> |
| <input |
| type="text" |
| id="resourceInput" |
| class="w-full p-3 border border-gray-200 rounded-lg" |
| placeholder="e.g. openai/gsm8k or meta-llama/Llama-2-7b" |
| /> |
| <div id="similarLoader" class="hidden absolute right-3 top-3"> |
| <i data-lucide="loader-2" class="animate-spin"></i> |
| </div> |
| <div |
| id="suggestionsBox" |
| class="hidden absolute w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-lg z-10 max-h-60 overflow-y-auto" |
| ></div> |
| </div> |
| <button onclick="findSimilarResources()" class="btn-primary"> |
| Find Similar |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="trendingContent" class="hidden tab-content space-y-6"> |
| <div class="bg-white p-8 rounded-xl shadow-sm border border-gray-100"> |
| <div |
| class="flex flex-col sm:flex-row gap-4 items-start sm:items-center justify-between mb-6" |
| > |
| <h2 |
| class="text-xl font-semibold text-gray-800 flex items-center gap-2" |
| > |
| <i data-lucide="trending-up" class="text-blue-500"></i> |
| Trending on Hugging Face Hub |
| </h2> |
| <div class="flex gap-2"> |
| <select |
| id="trendingTypeSelect" |
| class="text-sm border rounded-lg px-3 py-2 bg-white text-gray-700 focus:ring-2 focus:ring-blue-100 focus:border-blue-300 transition-all outline-none" |
| onchange="loadTrendingResources(this.value)" |
| > |
| <option value="datasets">Datasets</option> |
| <option value="models">Models</option> |
| </select> |
| <select |
| id="trendingLimitSelect" |
| class="text-sm border rounded-lg px-3 py-2 bg-white text-gray-700 focus:ring-2 focus:ring-blue-100 focus:border-blue-300 transition-all outline-none" |
| onchange="loadTrendingResources(document.getElementById('trendingTypeSelect').value)" |
| > |
| <option value="10">Show 10</option> |
| <option value="20">Show 20</option> |
| <option value="30">Show 30</option> |
| </select> |
| </div> |
| </div> |
|
|
| <div id="trendingLoader" class="flex justify-center py-6"> |
| <i |
| data-lucide="loader-2" |
| class="animate-spin text-blue-500 w-8 h-8" |
| ></i> |
| </div> |
|
|
| <div id="trendingResults" class="space-y-4"></div> |
| </div> |
| </div> |
| |
|
|
| <div |
| id="errorMessage" |
| class="hidden mt-4 p-4 text-red-600 bg-red-50 rounded-md" |
| ></div> |
|
|
| <div id="resultsContainer" class="mt-6 space-y-4"></div> |
| </div> |
| </div> |
|
|
| <style> |
| .tab-trigger.active { |
| border-bottom-color: #3b82f6; |
| color: #3b82f6; |
| } |
| |
| .btn-primary { |
| background-color: #3b82f6; |
| color: white; |
| padding: 0.5rem 1rem; |
| border-radius: 0.5rem; |
| transition: background-color 0.2s; |
| } |
| |
| .btn-primary:hover { |
| background-color: #2563eb; |
| } |
| |
| |
| .resource-card { |
| transition: transform 0.2s, box-shadow 0.2s; |
| } |
| |
| .resource-card:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), |
| 0 10px 10px -5px rgba(0, 0, 0, 0.04); |
| } |
| </style> |
|
|
| <script> |
| |
| const API_URL = |
| "https://davanstrien-huggingface-datasets-search-v2.hf.space"; |
| const MIN_SEARCH_LENGTH = 3; |
| const DEBOUNCE_MS = 300; |
| const RESULTS_PER_PAGE = 5; |
| const MAX_RESULTS = 100; |
| let currentPage = 1; |
| |
| |
| const URL_PARAMS = new URLSearchParams(window.location.search); |
| const INITIAL_SEARCH = URL_PARAMS.get("q"); |
| const INITIAL_SIMILAR = URL_PARAMS.get("similar"); |
| const INITIAL_TYPE = URL_PARAMS.get("type") || "datasets"; |
| const INITIAL_TAB = URL_PARAMS.get("tab") || "search"; |
| |
| |
| let currentSort = "similarity"; |
| let currentType = INITIAL_TYPE; |
| let currentMinLikes = 0; |
| let currentMinDownloads = 0; |
| let currentParamFilter = "any"; |
| |
| |
| lucide.createIcons(); |
| |
| |
| function switchTab(tabId) { |
| currentPage = 1; |
| document |
| .querySelectorAll(".tab-content") |
| .forEach((content) => content.classList.add("hidden")); |
| document |
| .querySelectorAll(".tab-trigger") |
| .forEach((trigger) => trigger.classList.remove("active")); |
| |
| document.getElementById(`${tabId}Content`).classList.remove("hidden"); |
| document.getElementById(`${tabId}Tab`).classList.add("active"); |
| |
| |
| |
| updateParamFilterUI(); |
| |
| |
| if (tabId === "trending") { |
| document.getElementById("resultsContainer").innerHTML = ""; |
| loadTrendingResources( |
| document.getElementById("trendingTypeSelect").value |
| ); |
| } |
| |
| |
| if (tabId === "search") { |
| updateURL({ tab: "search", similar: null }); |
| } else if (tabId === "similar") { |
| updateURL({ tab: "similar", q: null }); |
| } else if (tabId === "trending") { |
| updateURL({ tab: "trending", q: null, similar: null }); |
| } |
| |
| |
| updateParamFilterUI(); |
| } |
| |
| |
| function createResultCard(result) { |
| const isDataset = "dataset_id" in result; |
| const resourceId = isDataset ? result.dataset_id : result.model_id; |
| const resourceType = isDataset ? "datasets" : "models"; |
| const resourceIcon = isDataset ? "database" : "box"; |
| const resourceUrl = isDataset |
| ? `https://huggingface.co/${resourceType}/${resourceId}` |
| : `https://huggingface.co/${resourceId}`; |
| |
| |
| const isTrendingTab = |
| document |
| .getElementById("trendingContent") |
| .classList.contains("hidden") === false; |
| |
| const cardHtml = ` |
| <div class="card resource-card bg-white p-4 sm:p-6 rounded-lg shadow hover:shadow-md transition-shadow"> |
| <div class="space-y-2 w-full"> |
| <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-2"> |
| <div class="flex items-center gap-2"> |
| <i data-lucide="${resourceIcon}" class="text-blue-500"></i> |
| <h3 class="text-lg font-semibold">${resourceId}</h3> |
| </div> |
| <div class="flex flex-wrap items-center gap-2"> |
| <div class="flex items-center gap-4 text-sm text-gray-500"> |
| <span class="flex items-center gap-1"> |
| <i data-lucide="heart" class="w-4 h-4"></i> |
| ${result.likes} |
| </span> |
| <span class="flex items-center gap-1"> |
| <i data-lucide="download" class="w-4 h-4"></i> |
| ${result.downloads} |
| </span> |
| </div> |
| ${ |
| !isTrendingTab && result.similarity |
| ? `<span class="bg-blue-50 px-2 py-1 rounded text-sm"> |
| ${(result.similarity * 100).toFixed( |
| 1 |
| )}% match |
| </span>` |
| : "" |
| } |
| <button |
| onclick="findSimilarFromResult('${resourceId}', '${resourceType}')" |
| class="flex items-center gap-1 text-sm text-blue-500 hover:text-blue-700" |
| > |
| <i data-lucide="arrow-right"></i> |
| Find Similar |
| </button> |
| </div> |
| </div> |
| <p class="text-sm text-gray-600">${result.summary}</p> |
| |
| ${ |
| isDataset |
| ? ` |
| <!-- Add preview section that starts hidden --> |
| <div id="preview-section-${resourceId}" class="mt-4 border-t pt-4 hidden"> |
| <button |
| onclick="togglePreview('${resourceId}')" |
| class="flex items-center gap-2 text-sm text-gray-600 hover:text-gray-800" |
| > |
| <i data-lucide="chevron-right" id="preview-icon-${resourceId}" class="transition-transform"></i> |
| Preview Dataset |
| </button> |
| <div id="preview-content-${resourceId}" class="hidden mt-4"> |
| <iframe |
| src="https://huggingface.co/datasets/${resourceId}/embed/viewer/default/train" |
| frameborder="0" |
| width="100%" |
| height="560px" |
| ></iframe> |
| </div> |
| </div> |
| ` |
| : "" |
| } |
| |
| <a href="${resourceUrl}" |
| target="_blank" |
| class="inline-flex items-center gap-1 text-sm text-blue-500 hover:text-blue-700 mt-2"> |
| <i data-lucide="external-link" class="w-4 h-4"></i> |
| View on Hugging Face Hub |
| </a> |
| </div> |
| </div> |
| `; |
| |
| |
| if (isDataset) { |
| checkDatasetValidity(resourceId); |
| } |
| |
| return cardHtml; |
| } |
| |
| |
| async function checkDatasetValidity(datasetId) { |
| try { |
| const response = await fetch( |
| `https://datasets-server.huggingface.co/is-valid?dataset=${datasetId}` |
| ); |
| const data = await response.json(); |
| |
| |
| if (data.viewer) { |
| const previewSection = document.getElementById( |
| `preview-section-${datasetId}` |
| ); |
| if (previewSection) { |
| previewSection.classList.remove("hidden"); |
| } |
| } |
| } catch (error) { |
| console.error( |
| `Failed to check validity for dataset ${datasetId}:`, |
| error |
| ); |
| } |
| } |
| |
| |
| function updateURL(params) { |
| const newURL = new URL(window.location); |
| Object.entries(params).forEach(([key, value]) => { |
| if (value) { |
| newURL.searchParams.set(key, value); |
| } else { |
| newURL.searchParams.delete(key); |
| } |
| }); |
| window.history.pushState({}, "", newURL); |
| } |
| |
| |
| |
| async function performSearch(query, page = 1) { |
| if (query.length < MIN_SEARCH_LENGTH) { |
| document.getElementById("resultsContainer").innerHTML = ""; |
| updateURL({ q: null, similar: null }); |
| return; |
| } |
| |
| document.getElementById("searchLoader").classList.remove("hidden"); |
| document.getElementById("errorMessage").classList.add("hidden"); |
| |
| |
| updateURL({ |
| q: query, |
| similar: null, |
| type: currentType, |
| tab: "search", |
| }); |
| |
| try { |
| const endpoint = `${API_URL}/search/${currentType}`; |
| const params = new URLSearchParams({ |
| query: query, |
| k: RESULTS_PER_PAGE * page, |
| sort_by: currentSort, |
| min_likes: currentMinLikes, |
| min_downloads: currentMinDownloads, |
| }); |
| |
| |
| if (currentType === "models" && currentParamFilter !== "any") { |
| if (currentParamFilter.includes("-")) { |
| const [min, max] = currentParamFilter.split("-"); |
| params.append("min_param_count", parseInt(min) * 1e9); |
| params.append("max_param_count", parseInt(max) * 1e9); |
| } else if (currentParamFilter.endsWith("+")) { |
| params.append( |
| "min_param_count", |
| parseInt(currentParamFilter.replace("+", "")) * 1e9 |
| ); |
| } |
| } |
| |
| const response = await fetch(`${endpoint}?${params}`); |
| if (!response.ok) throw new Error("Search failed"); |
| |
| const data = await response.json(); |
| displayResults(data.results, page); |
| } catch (error) { |
| console.error("Search error:", error); |
| showError("Failed to perform search. Please try again."); |
| } finally { |
| document.getElementById("searchLoader").classList.add("hidden"); |
| } |
| } |
| |
| |
| |
| function toggleFilters(tab) { |
| const popover = document.getElementById(`${tab}FiltersPopover`); |
| popover.classList.toggle("hidden"); |
| |
| |
| const otherTab = tab === "search" ? "similar" : "search"; |
| document |
| .getElementById(`${otherTab}FiltersPopover`) |
| .classList.add("hidden"); |
| } |
| |
| |
| function syncParamFilterValues() { |
| const searchParamSelect = document.getElementById( |
| "searchParamRangeSelect" |
| ); |
| const similarParamSelect = document.getElementById( |
| "similarParamRangeSelect" |
| ); |
| |
| |
| if (searchParamSelect && similarParamSelect) { |
| if (searchParamSelect.value !== currentParamFilter) { |
| currentParamFilter = searchParamSelect.value; |
| similarParamSelect.value = currentParamFilter; |
| } else if (similarParamSelect.value !== currentParamFilter) { |
| currentParamFilter = similarParamSelect.value; |
| searchParamSelect.value = currentParamFilter; |
| } |
| } |
| } |
| |
| |
| const searchResources = _.debounce(async (query, page = 1) => { |
| performSearch(query, page); |
| }, DEBOUNCE_MS); |
| |
| |
| let trendingResourcesCache = { |
| datasets: null, |
| models: null, |
| }; |
| let cacheTimestamp = { |
| datasets: null, |
| models: null, |
| }; |
| const CACHE_DURATION = 1000 * 60 * 15; |
| |
| async function fetchTrendingResources(type) { |
| if ( |
| trendingResourcesCache[type] && |
| cacheTimestamp[type] && |
| Date.now() - cacheTimestamp[type] < CACHE_DURATION |
| ) { |
| return trendingResourcesCache[type]; |
| } |
| |
| try { |
| const response = await fetch(`https://huggingface.co/api/${type}`); |
| const data = await response.json(); |
| |
| |
| const trendingResources = data |
| .slice(0, 20) |
| .map((resource) => resource.id); |
| |
| trendingResourcesCache[type] = trendingResources; |
| cacheTimestamp[type] = Date.now(); |
| return trendingResources; |
| } catch (error) { |
| console.error(`Error fetching trending ${type}:`, error); |
| return []; |
| } |
| } |
| |
| |
| async function loadTrendingResources(type) { |
| |
| document.getElementById("trendingLoader").style.display = "flex"; |
| document.getElementById("trendingResults").innerHTML = ""; |
| document.getElementById("errorMessage").classList.add("hidden"); |
| |
| |
| document.getElementById("trendingTypeSelect").value = type; |
| |
| |
| const limit = document.getElementById("trendingLimitSelect").value; |
| |
| try { |
| const endpoint = `${API_URL}/trending/${type}`; |
| const queryParams = new URLSearchParams({ |
| limit: limit, |
| }); |
| |
| |
| if (type === "models" && currentParamFilter !== "any") { |
| if (currentParamFilter.includes("-")) { |
| const [min, max] = currentParamFilter.split("-"); |
| queryParams.append("min_param_count", parseInt(min) * 1e9); |
| queryParams.append("max_param_count", parseInt(max) * 1e9); |
| } else if (currentParamFilter.endsWith("+")) { |
| queryParams.append( |
| "min_param_count", |
| parseInt(currentParamFilter.replace("+", "")) * 1e9 |
| ); |
| } |
| } |
| |
| const response = await fetch(`${endpoint}?${queryParams}`); |
| |
| if (!response.ok) { |
| throw new Error(`HTTP error! status: ${response.status}`); |
| } |
| |
| const data = await response.json(); |
| |
| |
| const resultsContainer = document.getElementById("trendingResults"); |
| |
| if (data.results && data.results.length > 0) { |
| resultsContainer.innerHTML = data.results |
| .map((result) => createResultCard(result)) |
| .join(""); |
| |
| |
| lucide.createIcons(); |
| } else { |
| resultsContainer.innerHTML = ` |
| <div class="text-center p-8 bg-gray-50 rounded-lg"> |
| <p class="text-gray-500">No trending ${type} found matching your criteria</p> |
| </div> |
| `; |
| } |
| } catch (error) { |
| console.error("Error loading trending resources:", error); |
| document.getElementById("trendingResults").innerHTML = ` |
| <div class="text-center p-8 bg-gray-50 rounded-lg"> |
| <p class="text-red-500">Failed to load trending ${type}. Please try again later.</p> |
| </div> |
| `; |
| } finally { |
| document.getElementById("trendingLoader").style.display = "none"; |
| } |
| } |
| |
| function displaySuggestions(resources, suggestionsBox) { |
| if (resources.length > 0) { |
| const resourceIcon = currentType === "datasets" ? "database" : "box"; |
| suggestionsBox.innerHTML = resources |
| .map( |
| (resourceId) => ` |
| <div |
| class="p-3 hover:bg-gray-50 cursor-pointer border-b last:border-b-0" |
| onclick="selectSuggestion('${resourceId}')" |
| > |
| <div class="flex items-center gap-2"> |
| <i data-lucide="${resourceIcon}" class="w-4 h-4 text-blue-500"></i> |
| <span>${resourceId}</span> |
| </div> |
| </div> |
| ` |
| ) |
| .join(""); |
| suggestionsBox.classList.remove("hidden"); |
| lucide.createIcons(); |
| } else { |
| suggestionsBox.classList.add("hidden"); |
| } |
| } |
| |
| function selectSuggestion(resource) { |
| const resourceInput = document.getElementById("resourceInput"); |
| const suggestionsBox = document.getElementById("suggestionsBox"); |
| |
| resourceInput.value = resource; |
| suggestionsBox.classList.add("hidden"); |
| findSimilarResources(); |
| } |
| |
| |
| async function findSimilarResources(page = 1) { |
| const resourceId = document.getElementById("resourceInput").value; |
| if (!resourceId) return; |
| |
| |
| updateURL({ |
| similar: resourceId, |
| q: null, |
| type: currentType, |
| tab: "similar", |
| }); |
| |
| |
| document.getElementById("similarLoader").classList.remove("hidden"); |
| document.getElementById("errorMessage").classList.add("hidden"); |
| |
| try { |
| const endpoint = `${API_URL}/similarity/${currentType}`; |
| const paramName = |
| currentType === "datasets" ? "dataset_id" : "model_id"; |
| |
| const params = new URLSearchParams({ |
| [paramName]: resourceId, |
| k: RESULTS_PER_PAGE * page, |
| sort_by: currentSort, |
| min_likes: currentMinLikes, |
| min_downloads: currentMinDownloads, |
| }); |
| |
| |
| if (currentType === "models" && currentParamFilter !== "any") { |
| if (currentParamFilter.includes("-")) { |
| const [min, max] = currentParamFilter.split("-"); |
| params.append("min_param_count", parseInt(min) * 1e9); |
| params.append("max_param_count", parseInt(max) * 1e9); |
| } else if (currentParamFilter.endsWith("+")) { |
| params.append( |
| "min_param_count", |
| parseInt(currentParamFilter.replace("+", "")) * 1e9 |
| ); |
| } |
| } |
| |
| const response = await fetch(`${endpoint}?${params}`); |
| if (!response.ok) throw new Error("Similarity search failed"); |
| |
| const data = await response.json(); |
| displayResults(data.results, page); |
| } catch (error) { |
| showError(`Failed to find similar ${currentType}. Please try again.`); |
| } finally { |
| |
| document.getElementById("similarLoader").classList.add("hidden"); |
| } |
| } |
| |
| |
| function displayResults(results, page = 1) { |
| const container = document.getElementById("resultsContainer"); |
| console.log("Displaying results:", results); |
| if (results && results.length > 0) { |
| |
| if (currentSort !== "similarity") { |
| results.sort((a, b) => b[currentSort] - a[currentSort]); |
| } |
| |
| container.innerHTML = ` |
| <div class="flex justify-between items-center mb-4"> |
| <h2 class="text-lg font-semibold">Results</h2> |
| <div class="flex items-center gap-4"> |
| <span class="text-sm text-gray-500">Found ${ |
| results.length |
| } results</span> |
| <button |
| onclick="shareResults()" |
| class="flex items-center gap-1 text-sm px-3 py-1.5 bg-white border border-gray-200 rounded-lg hover:bg-gray-50" |
| title="Copy link to these search results" |
| > |
| <i data-lucide="link"></i> |
| Copy Search Link |
| </button> |
| </div> |
| </div> |
| ${results.map((result) => createResultCard(result)).join("")} |
| ${ |
| results.length >= RESULTS_PER_PAGE * page && |
| RESULTS_PER_PAGE * (page + 1) <= MAX_RESULTS |
| ? `<div class="mt-4 flex items-center justify-between"> |
| <button |
| onclick="loadMore()" |
| class="px-6 py-3 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors flex items-center gap-2" |
| > |
| <i data-lucide="more-horizontal"></i> |
| Load More Results |
| </button> |
| <button |
| onclick="shareResults()" |
| class="flex items-center gap-1 text-sm px-3 py-1.5 bg-white border border-gray-200 rounded-lg hover:bg-gray-50" |
| title="Copy link to these search results" |
| > |
| <i data-lucide="link"></i> |
| Copy Search Link |
| </button> |
| </div>` |
| : results.length >= MAX_RESULTS |
| ? `<div class="text-center mt-4 p-6 bg-blue-50 rounded-lg"> |
| <p class="text-gray-700 mb-3">You've reached the end of our journey! (${MAX_RESULTS} results)</p> |
| <p class="text-gray-600 mb-4">Can't find what you're looking for? Why not create and share your own?</p> |
| <div class="flex items-center justify-center gap-4"> |
| <a href="https://huggingface.co/docs/${ |
| currentType === "datasets" |
| ? "datasets/upload_dataset" |
| : "hub/upload" |
| }" |
| target="_blank" |
| class="inline-flex items-center gap-2 text-blue-500 hover:text-blue-700"> |
| <i data-lucide="external-link"></i> |
| Learn how to share on Hugging Face |
| </a> |
| <button |
| onclick="shareResults()" |
| class="flex items-center gap-1 text-sm px-3 py-1.5 bg-white border border-gray-200 rounded-lg hover:bg-gray-50" |
| title="Copy link to these search results" |
| > |
| <i data-lucide="link"></i> |
| Copy Search Link |
| </button> |
| </div> |
| </div>` |
| : "" |
| } |
| `; |
| lucide.createIcons(); |
| } else { |
| container.innerHTML = ` |
| <div class="text-center text-gray-500"> |
| No results found |
| </div> |
| `; |
| } |
| } |
| |
| |
| function showError(message) { |
| const errorElement = document.getElementById("errorMessage"); |
| errorElement.textContent = message; |
| errorElement.classList.remove("hidden"); |
| } |
| |
| |
| document |
| .getElementById("searchInput") |
| .addEventListener("input", (e) => searchResources(e.target.value)); |
| document |
| .getElementById("resourceInput") |
| .addEventListener("keydown", (e) => { |
| if (e.key === "Enter") findSimilarResources(); |
| }); |
| |
| |
| function findSimilarFromResult(resourceId, resourceType) { |
| |
| switchTab("similar"); |
| |
| |
| currentType = resourceType; |
| document.getElementById("similarTypeSelect").value = resourceType; |
| document.getElementById("searchTypeSelect").value = resourceType; |
| document.getElementById("trendingTypeSelect").value = resourceType; |
| |
| |
| const resourceInput = document.getElementById("resourceInput"); |
| resourceInput.value = resourceId; |
| |
| |
| const suggestionsBox = document.getElementById("suggestionsBox"); |
| suggestionsBox.classList.add("hidden"); |
| |
| |
| findSimilarResources(); |
| } |
| |
| |
| function toggleAccordion() { |
| const content = document.getElementById("accordionContent"); |
| const icon = document.getElementById("accordionIcon"); |
| |
| content.classList.toggle("hidden"); |
| icon.style.transform = content.classList.contains("hidden") |
| ? "rotate(0deg)" |
| : "rotate(90deg)"; |
| } |
| |
| |
| function loadMore() { |
| currentPage += 1; |
| const activeTab = document.querySelector(".tab-trigger.active").id; |
| |
| if (activeTab === "searchTab") { |
| const searchQuery = document.getElementById("searchInput").value; |
| searchResources(searchQuery, currentPage); |
| } else { |
| findSimilarResources(currentPage); |
| } |
| } |
| |
| |
| function togglePreview(datasetId) { |
| const content = document.getElementById(`preview-content-${datasetId}`); |
| const icon = document.getElementById(`preview-icon-${datasetId}`); |
| |
| content.classList.toggle("hidden"); |
| icon.style.transform = content.classList.contains("hidden") |
| ? "rotate(0deg)" |
| : "rotate(90deg)"; |
| } |
| |
| |
| async function shareResults() { |
| const activeTab = document.querySelector(".tab-trigger.active").id; |
| const currentURL = new URL(window.location); |
| |
| |
| if (activeTab === "searchTab") { |
| const searchQuery = document.getElementById("searchInput").value; |
| currentURL.searchParams.set("q", searchQuery); |
| currentURL.searchParams.delete("similar"); |
| } else if (activeTab === "similarTab") { |
| const resourceId = document.getElementById("resourceInput").value; |
| currentURL.searchParams.set("similar", resourceId); |
| currentURL.searchParams.delete("q"); |
| } else if (activeTab === "trendingTab") { |
| |
| const limit = document.getElementById("trendingLimitSelect").value; |
| |
| currentURL.searchParams.set("limit", limit); |
| currentURL.searchParams.delete("q"); |
| currentURL.searchParams.delete("similar"); |
| } |
| |
| |
| currentURL.searchParams.set("tab", activeTab.replace("Tab", "")); |
| currentURL.searchParams.set("type", currentType); |
| |
| try { |
| await navigator.clipboard.writeText(currentURL.toString()); |
| |
| |
| const buttons = document.querySelectorAll( |
| 'button[onclick="shareResults()"]' |
| ); |
| buttons.forEach((button) => { |
| const originalHTML = button.innerHTML; |
| button.innerHTML = |
| '<i data-lucide="check"></i> Search Link Copied!'; |
| button.classList.add( |
| "bg-green-50", |
| "border-green-200", |
| "text-green-600" |
| ); |
| lucide.createIcons(); |
| |
| setTimeout(() => { |
| button.innerHTML = originalHTML; |
| button.classList.remove( |
| "bg-green-50", |
| "border-green-200", |
| "text-green-600" |
| ); |
| lucide.createIcons(); |
| }, 2000); |
| }); |
| } catch (error) { |
| console.error("Error copying to clipboard:", error); |
| } |
| } |
| |
| |
| function selectType(type) { |
| |
| const currentParamValue = currentParamFilter; |
| |
| |
| currentType = type; |
| |
| |
| document.getElementById("searchTypeSelect").value = type; |
| document.getElementById("similarTypeSelect").value = type; |
| |
| |
| |
| |
| if (type !== "models") { |
| |
| currentParamFilter = "any"; |
| } |
| |
| |
| updateFilterIndicators("search"); |
| updateFilterIndicators("similar"); |
| |
| |
| updateParamFilterUI(); |
| |
| |
| const activeTab = document.querySelector(".tab-trigger.active").id; |
| if (activeTab === "searchTab") { |
| const searchQuery = document.getElementById("searchInput").value; |
| if (searchQuery.length >= MIN_SEARCH_LENGTH) { |
| performSearch(searchQuery, 1); |
| } |
| } else if (activeTab === "similarTab") { |
| const resourceId = document.getElementById("resourceInput").value; |
| if (resourceId) { |
| findSimilarResources(1); |
| } |
| } else if (activeTab === "trendingTab") { |
| loadTrendingResources(type); |
| } |
| } |
| |
| |
| function updateFilterIndicators(tab) { |
| const activeFiltersSpan = document.getElementById( |
| `${tab}ActiveFilters` |
| ); |
| |
| |
| let activeCount = |
| (currentMinLikes > 0 ? 1 : 0) + |
| (currentMinDownloads > 0 ? 1 : 0) + |
| (currentType === "models" && currentParamFilter !== "any" ? 1 : 0); |
| |
| if (activeCount > 0) { |
| activeFiltersSpan.textContent = activeCount; |
| activeFiltersSpan.classList.remove("hidden"); |
| } else { |
| activeFiltersSpan.classList.add("hidden"); |
| } |
| } |
| |
| |
| function handleFilterChange(tab) { |
| const minLikesInput = document.getElementById(`${tab}MinLikes`); |
| const minDownloadsInput = document.getElementById(`${tab}MinDownloads`); |
| const paramRangeSelect = document.getElementById( |
| `${tab}ParamRangeSelect` |
| ); |
| const sortSelect = document.getElementById(`${tab}SortSelect`); |
| const activeFiltersSpan = document.getElementById( |
| `${tab}ActiveFilters` |
| ); |
| |
| |
| currentMinLikes = parseInt(minLikesInput.value) || 0; |
| currentMinDownloads = parseInt(minDownloadsInput.value) || 0; |
| |
| |
| if (sortSelect) { |
| currentSort = sortSelect.value; |
| } |
| |
| |
| if (currentType === "models" && paramRangeSelect) { |
| currentParamFilter = paramRangeSelect.value; |
| syncParamFilterValues(); |
| } |
| |
| |
| let activeCount = |
| (currentMinLikes > 0 ? 1 : 0) + |
| (currentMinDownloads > 0 ? 1 : 0) + |
| (currentType === "models" && currentParamFilter !== "any" ? 1 : 0); |
| |
| if (activeCount > 0) { |
| activeFiltersSpan.textContent = activeCount; |
| activeFiltersSpan.classList.remove("hidden"); |
| } else { |
| activeFiltersSpan.classList.add("hidden"); |
| } |
| |
| |
| if (tab === "search") { |
| const searchQuery = document.getElementById("searchInput").value; |
| if (searchQuery.length >= MIN_SEARCH_LENGTH) { |
| currentPage = 1; |
| performSearch(searchQuery, 1); |
| } |
| } else { |
| const resourceId = document.getElementById("resourceInput").value; |
| if (resourceId) { |
| currentPage = 1; |
| findSimilarResources(1); |
| } |
| } |
| } |
| |
| |
| function updateParamFilterUI() { |
| |
| if (currentType !== "models") { |
| |
| const existingFilter = document.getElementById("paramFilterBar"); |
| if (existingFilter) { |
| existingFilter.remove(); |
| } |
| return; |
| } |
| |
| |
| let paramFilterBar = document.getElementById("paramFilterBar"); |
| |
| |
| if (!paramFilterBar) { |
| paramFilterBar = document.createElement("div"); |
| paramFilterBar.id = "paramFilterBar"; |
| paramFilterBar.className = |
| "bg-white p-4 rounded-lg shadow-sm border border-gray-100 mb-4"; |
| |
| |
| paramFilterBar.innerHTML = ` |
| <div class="flex flex-col gap-2"> |
| <div class="flex items-center justify-between"> |
| <h3 class="text-sm font-medium text-gray-700">Filter by Parameter Size</h3> |
| <button onclick="clearParamFilter()" class="text-xs text-blue-500 hover:text-blue-700">Clear</button> |
| </div> |
| <div class="flex flex-wrap gap-2 mt-1" id="paramFilterButtons"> |
| <button onclick="applyParamFilter('any')" class="filter-btn ${ |
| currentParamFilter === "any" ? "active" : "" |
| }"> |
| Any size |
| </button> |
| <button onclick="applyParamFilter('0-1')" class="filter-btn ${ |
| currentParamFilter === "0-1" ? "active" : "" |
| }"> |
| Small (0-1B) |
| </button> |
| <button onclick="applyParamFilter('1-7')" class="filter-btn ${ |
| currentParamFilter === "1-7" ? "active" : "" |
| }"> |
| Medium (1-7B) |
| </button> |
| <button onclick="applyParamFilter('7-20')" class="filter-btn ${ |
| currentParamFilter === "7-20" ? "active" : "" |
| }"> |
| Large (7-20B) |
| </button> |
| <button onclick="applyParamFilter('20-70')" class="filter-btn ${ |
| currentParamFilter === "20-70" ? "active" : "" |
| }"> |
| XL (20-70B) |
| </button> |
| <button onclick="applyParamFilter('70+')" class="filter-btn ${ |
| currentParamFilter === "70+" ? "active" : "" |
| }"> |
| XXL (70B+) |
| </button> |
| </div> |
| </div> |
| `; |
| |
| |
| const resultsContainer = document.getElementById("resultsContainer"); |
| resultsContainer.parentNode.insertBefore( |
| paramFilterBar, |
| resultsContainer |
| ); |
| |
| |
| const style = document.createElement("style"); |
| style.textContent = ` |
| .filter-btn { |
| padding: 6px 12px; |
| border-radius: 4px; |
| font-size: 0.875rem; |
| background-color: #f3f4f6; |
| color: #4b5563; |
| transition: all 0.2s; |
| } |
| .filter-btn:hover { |
| background-color: #e5e7eb; |
| } |
| .filter-btn.active { |
| background-color: #3b82f6; |
| color: white; |
| } |
| `; |
| document.head.appendChild(style); |
| } else { |
| |
| const buttons = paramFilterBar.querySelectorAll(".filter-btn"); |
| buttons.forEach((btn) => { |
| btn.classList.remove("active"); |
| const filterValue = btn.getAttribute("onclick").match(/'(.*?)'/)[1]; |
| if (filterValue === currentParamFilter) { |
| btn.classList.add("active"); |
| } |
| }); |
| } |
| } |
| |
| |
| function applyParamFilter(value) { |
| |
| currentParamFilter = value; |
| |
| |
| updateParamFilterUI(); |
| |
| |
| updateFilterIndicators("search"); |
| updateFilterIndicators("similar"); |
| |
| |
| const activeTab = document.querySelector(".tab-trigger.active").id; |
| |
| if (activeTab === "searchTab") { |
| const searchQuery = document.getElementById("searchInput").value; |
| if (searchQuery.length >= MIN_SEARCH_LENGTH) { |
| currentPage = 1; |
| performSearch(searchQuery, 1); |
| } |
| } else if (activeTab === "similarTab") { |
| const resourceId = document.getElementById("resourceInput").value; |
| if (resourceId) { |
| currentPage = 1; |
| findSimilarResources(1); |
| } |
| } else if (activeTab === "trendingTab") { |
| loadTrendingResources(currentType); |
| } |
| } |
| |
| |
| function clearParamFilter() { |
| applyParamFilter("any"); |
| } |
| |
| |
| document.addEventListener("DOMContentLoaded", function () { |
| |
| selectType(INITIAL_TYPE); |
| |
| |
| updateParamFilterUI(); |
| |
| |
| switchTab(INITIAL_TAB); |
| |
| |
| if (INITIAL_SEARCH && INITIAL_SEARCH.length >= MIN_SEARCH_LENGTH) { |
| document.getElementById("searchInput").value = INITIAL_SEARCH; |
| performSearch(INITIAL_SEARCH, 1); |
| } |
| |
| |
| if (INITIAL_SIMILAR) { |
| document.getElementById("resourceInput").value = INITIAL_SIMILAR; |
| findSimilarResources(1); |
| } |
| |
| |
| document |
| .getElementById("resourceInput") |
| .addEventListener("input", function (e) { |
| const query = e.target.value.trim(); |
| if (query.length >= 2) { |
| fetchResourceSuggestions(query); |
| } else { |
| document.getElementById("suggestionsBox").classList.add("hidden"); |
| } |
| }); |
| |
| |
| document.addEventListener("click", function (e) { |
| if ( |
| !e.target.closest("#resourceInput") && |
| !e.target.closest("#suggestionsBox") |
| ) { |
| document.getElementById("suggestionsBox").classList.add("hidden"); |
| } |
| }); |
| }); |
| |
| |
| async function fetchResourceSuggestions(query) { |
| if (query.length < 2) return; |
| |
| try { |
| const response = await fetch( |
| `${API_URL}/suggest/${currentType}?q=${encodeURIComponent(query)}` |
| ); |
| if (!response.ok) throw new Error("Failed to fetch suggestions"); |
| |
| const data = await response.json(); |
| if (data && data.suggestions && data.suggestions.length > 0) { |
| displaySuggestions( |
| data.suggestions, |
| document.getElementById("suggestionsBox") |
| ); |
| } else { |
| document.getElementById("suggestionsBox").classList.add("hidden"); |
| } |
| } catch (error) { |
| console.error("Error fetching suggestions:", error); |
| document.getElementById("suggestionsBox").classList.add("hidden"); |
| } |
| } |
| </script> |
| </body> |
| </html> |
|
|