davanstrien's picture
davanstrien HF staff
Fix resource URL construction for models in index.html
ab4d3d5
<!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/[email protected]/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="handleSortChange('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>
</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="handleSortChange('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>
</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="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>
<!-- Start: New Trending Tab Content -->
<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>
<!-- End: New Trending Tab Content -->
<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;
}
/* Add card hover effect */
.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>
// Configuration
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;
// Add these constants near the top with other configurations
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";
// Add these variables with other configurations
let currentSort = "similarity";
let currentType = INITIAL_TYPE;
let currentMinLikes = 0;
let currentMinDownloads = 0;
// Initialize Lucide icons
lucide.createIcons();
// Tab switching
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");
// Clear results container when switching to trending tab
if (tabId === "trending") {
document.getElementById("resultsContainer").innerHTML = "";
loadTrendingResources(
document.getElementById("trendingTypeSelect").value
);
}
// Update URL parameters when switching tabs
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 });
}
}
// Create result card
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}`;
// Check if we're in the trending tab
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>
`;
// After rendering the card, check if preview is available for datasets
if (isDataset) {
checkDatasetValidity(resourceId);
}
return cardHtml;
}
// Add function to check dataset validity
async function checkDatasetValidity(datasetId) {
try {
const response = await fetch(
`https://datasets-server.huggingface.co/is-valid?dataset=${datasetId}`
);
const data = await response.json();
// Show preview section only if viewer is available
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
);
}
}
// Add this function to update the URL
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);
}
// Modify the search function to handle both datasets and models
const searchResources = _.debounce(async (query, page = 1) => {
performSearch(query, page);
}, DEBOUNCE_MS);
// Cache for trending resources
let trendingResourcesCache = {
datasets: null,
models: null,
};
let cacheTimestamp = {
datasets: null,
models: null,
};
const CACHE_DURATION = 1000 * 60 * 15; // 15 minutes
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();
// Just take the first 20 resource IDs since they're already sorted
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 [];
}
}
// New function to load trending resources from our API
async function loadTrendingResources(type) {
// Show loader and hide any errors
document.getElementById("trendingLoader").style.display = "flex";
document.getElementById("trendingResults").innerHTML = "";
document.getElementById("errorMessage").classList.add("hidden");
// Update selector to match current type
document.getElementById("trendingTypeSelect").value = type;
// Get filter values
const limit = document.getElementById("trendingLimitSelect").value;
try {
const endpoint = `${API_URL}/trending/${type}`;
const queryParams = new URLSearchParams({
limit: limit,
});
const response = await fetch(`${endpoint}?${queryParams}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// Display trending results
const resultsContainer = document.getElementById("trendingResults");
if (data.results && data.results.length > 0) {
resultsContainer.innerHTML = data.results
.map((result) => createResultCard(result))
.join("");
// Initialize icons for the new cards
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();
}
// Modify the findSimilarResources function to handle both datasets and models
async function findSimilarResources(page = 1) {
const resourceId = document.getElementById("resourceInput").value;
if (!resourceId) return;
// Update URL with similar resource ID and type
updateURL({
similar: resourceId,
q: null,
type: currentType,
tab: "similar",
});
const similarLoader = document.getElementById("similarLoader");
if (similarLoader) {
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 response = await fetch(
`${endpoint}?${paramName}=${encodeURIComponent(resourceId)}&k=${
RESULTS_PER_PAGE * page
}&sort_by=${currentSort}&min_likes=${currentMinLikes}&min_downloads=${currentMinDownloads}`
);
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 {
if (similarLoader) {
similarLoader.classList.add("hidden");
}
}
}
// Display results
function displayResults(results, page = 1) {
const container = document.getElementById("resultsContainer");
console.log("Displaying results:", results);
if (results && results.length > 0) {
// Sort results if not using similarity
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>
`;
}
}
// Show error message
function showError(message) {
const errorElement = document.getElementById("errorMessage");
errorElement.textContent = message;
errorElement.classList.remove("hidden");
}
// Event listeners
document
.getElementById("searchInput")
.addEventListener("input", (e) => searchResources(e.target.value));
document
.getElementById("resourceInput")
.addEventListener("keydown", (e) => {
if (e.key === "Enter") findSimilarResources();
});
// Update the findSimilarFromResult function
function findSimilarFromResult(resourceId, resourceType) {
// Switch to the similar tab
switchTab("similar");
// Set the resource type
currentType = resourceType;
document.getElementById("similarTypeSelect").value = resourceType;
document.getElementById("searchTypeSelect").value = resourceType;
document.getElementById("trendingTypeSelect").value = resourceType;
// Set the resource ID in the input without triggering the focus event
const resourceInput = document.getElementById("resourceInput");
resourceInput.value = resourceId;
// Hide suggestions box explicitly
const suggestionsBox = document.getElementById("suggestionsBox");
suggestionsBox.classList.add("hidden");
// Trigger the search
findSimilarResources();
}
// Add accordion functionality
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)";
}
// Add the loadMore function
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);
}
}
// Add this new function for toggling the preview
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)";
}
// Update the share function name and remove the datasetId parameter
async function shareResults() {
const activeTab = document.querySelector(".tab-trigger.active").id;
const currentURL = new URL(window.location);
// Update URL based on active tab
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") {
// For trending tab, save the selected options
const limit = document.getElementById("trendingLimitSelect").value;
currentURL.searchParams.set("limit", limit);
currentURL.searchParams.delete("q");
currentURL.searchParams.delete("similar");
}
// Always include the current tab and type
currentURL.searchParams.set("tab", activeTab.replace("Tab", ""));
currentURL.searchParams.set("type", currentType);
try {
await navigator.clipboard.writeText(currentURL.toString());
// Show success message
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);
}
}
// Modify the selectType function to be simpler
function selectType(type) {
// Update the current type
currentType = type;
// Update all select elements to match
document.getElementById("searchTypeSelect").value = type;
document.getElementById("similarTypeSelect").value = type;
document.getElementById("trendingTypeSelect").value = type;
// Update placeholder text for similar tab
const resourceInput = document.getElementById("resourceInput");
resourceInput.placeholder =
type === "datasets"
? "e.g. openai/gsm8k"
: "e.g. meta-llama/Llama-2-7b";
// Clear results and reset page
currentPage = 1;
document.getElementById("resultsContainer").innerHTML = "";
// Re-run the current search with new type
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") {
// Reload trending tab with new type
loadTrendingResources(type);
}
// Update URL with the new type
updateURL({ type: currentType });
}
// Add a non-debounced version of the search function
async function performSearch(query, page = 1) {
if (query.length < MIN_SEARCH_LENGTH) {
document.getElementById("resultsContainer").innerHTML = "";
updateURL({ q: null, similar: null }); // Clear URL params
return;
}
document.getElementById("searchLoader").classList.remove("hidden");
document.getElementById("errorMessage").classList.add("hidden");
// Update URL with search query and type
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,
});
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");
}
}
// Update the event listeners section
document.addEventListener("DOMContentLoaded", async () => {
const resourceInput = document.getElementById("resourceInput");
let programmaticFocus = false;
// Set initial type from URL
if (INITIAL_TYPE) {
document.getElementById("searchTypeSelect").value = INITIAL_TYPE;
document.getElementById("similarTypeSelect").value = INITIAL_TYPE;
document.getElementById("trendingTypeSelect").value = INITIAL_TYPE;
currentType = INITIAL_TYPE;
}
// Add input event listener for suggestions
resourceInput.addEventListener("input", async (e) => {
const suggestionsBox = document.getElementById("suggestionsBox");
const value = e.target.value;
if (!programmaticFocus) {
if (!value) {
// Show trending resources when input is empty
const trending = await fetchTrendingResources(currentType);
displaySuggestions(trending, suggestionsBox);
} else {
// Filter trending resources based on input
const trending = await fetchTrendingResources(currentType);
const filtered = trending.filter((resource) =>
resource.toLowerCase().includes(value.toLowerCase())
);
displaySuggestions(filtered, suggestionsBox);
}
}
});
// Show trending resources on focus only when not programmatically focused
resourceInput.addEventListener("focus", async () => {
if (!programmaticFocus) {
const suggestionsBox = document.getElementById("suggestionsBox");
const trending = await fetchTrendingResources(currentType);
displaySuggestions(trending, suggestionsBox);
}
programmaticFocus = false;
});
// Handle initial URL parameters
if (INITIAL_TAB) {
switchTab(INITIAL_TAB);
}
if (INITIAL_SEARCH) {
switchTab("search");
document.getElementById("searchInput").value = INITIAL_SEARCH;
await searchResources(INITIAL_SEARCH);
} else if (INITIAL_SIMILAR) {
switchTab("similar");
document.getElementById("resourceInput").value = INITIAL_SIMILAR;
await findSimilarResources();
} else if (INITIAL_TAB === "trending") {
// Load trending data with any URL parameters
const limit = URL_PARAMS.get("limit") || 10;
document.getElementById("trendingLimitSelect").value = limit;
loadTrendingResources(currentType);
}
});
// Modify the handleSortChange function
function handleSortChange(tab) {
const sortSelect = document.getElementById(`${tab}SortSelect`);
currentSort = sortSelect.value;
// Re-run the current search with new sort
const activeTab = document.querySelector(".tab-trigger.active").id;
if (activeTab === "searchTab") {
const searchQuery = document.getElementById("searchInput").value;
if (searchQuery.length >= MIN_SEARCH_LENGTH) {
searchResources(searchQuery, 1);
}
} else {
const resourceId = document.getElementById("resourceInput").value;
if (resourceId) {
findSimilarResources(1);
}
}
}
// Add these new functions to handle the filters UI
function toggleFilters(tab) {
const popover = document.getElementById(`${tab}FiltersPopover`);
popover.classList.toggle("hidden");
// Close other popovers
const otherTab = tab === "search" ? "similar" : "search";
document
.getElementById(`${otherTab}FiltersPopover`)
.classList.add("hidden");
}
// Close filters when clicking outside
document.addEventListener("click", (event) => {
const searchPopover = document.getElementById("searchFiltersPopover");
const similarPopover = document.getElementById("similarFiltersPopover");
const searchButton = event.target.closest(
"button[onclick=\"toggleFilters('search')\"]"
);
const similarButton = event.target.closest(
"button[onclick=\"toggleFilters('similar')\"]"
);
if (!searchButton && !event.target.closest("#searchFiltersPopover")) {
searchPopover.classList.add("hidden");
}
if (!similarButton && !event.target.closest("#similarFiltersPopover")) {
similarPopover.classList.add("hidden");
}
});
// Modify handleFilterChange to update the active filters indicator
function handleFilterChange(tab) {
const minLikesInput = document.getElementById(`${tab}MinLikes`);
const minDownloadsInput = document.getElementById(`${tab}MinDownloads`);
const activeFiltersSpan = document.getElementById(
`${tab}ActiveFilters`
);
currentMinLikes = parseInt(minLikesInput.value) || 0;
currentMinDownloads = parseInt(minDownloadsInput.value) || 0;
// Update active filters indicator
const activeCount =
(currentMinLikes > 0 ? 1 : 0) + (currentMinDownloads > 0 ? 1 : 0);
if (activeCount > 0) {
activeFiltersSpan.textContent = activeCount;
activeFiltersSpan.classList.remove("hidden");
} else {
activeFiltersSpan.classList.add("hidden");
}
// Re-run the current search with new filters
if (tab === "search") {
const searchQuery = document.getElementById("searchInput").value;
if (searchQuery.length >= MIN_SEARCH_LENGTH) {
searchResources(searchQuery, 1);
}
} else {
const resourceId = document.getElementById("resourceInput").value;
if (resourceId) {
findSimilarResources(1);
}
}
}
</script>
</body>
</html>