SmolAgentToolBox / index.html
acecalisto3's picture
Update index.html
f8b6cd2 verified
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Smolagent + ToolBox</title>
<meta name="description" content="Discover all smolagents and tools created by the community." />
<meta property="og:url" content="https://smolagents-tools-gallery.hf.space/" />
<meta property="og:type" content="website" />
<meta property="og:title" content="smolagents and tools gallery" />
<meta property="og:description" content="Discover all smolagents and tools created by the community." />
<meta property="og:image" content="https://huggingface-projects-diffusers-gallery.hf.space/Fo6vR6JX0AEjbw1.jpeg" />
<meta name="twitter:card" content="player" />
<meta property="twitter:url" content="https://davidberenstein1957-smolagents-and-tools.static.hf.space" />
<meta name="twitter:description" content="Discover all smolagents and tools created by the community." />
<meta name="twitter:site" content="@huggingface" />
<meta name="twitter:title" content="smolagents and tools gallery" />
<meta name="twitter:image" content="https://huggingface-projects-diffusers-gallery.hf.space/Fo6vR6JX0AEjbw1.jpeg" />
<meta name="twitter:player" content="https://davidberenstein1957-smolagents-and-tools.static.hf.space" />
<meta name="twitter:player:width" content="100%" />
<meta name="twitter:player:height" content="600" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.1/iframeResizer.contentWindow.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<style>
iframe {
display: block;
border: none;
width: 100%;
height: 600px;
pointer-events: none;
margin-top: 48px; /* Match header height */
}
.grid-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
grid-gap: 10px;
margin-top: 3.5rem;
}
.grid-item {
position: relative;
overflow-y: hidden;
border-radius: 10px;
border: 1px solid rgb(55 65 81);
}
.grid-item:hover {
filter: brightness(75%);
}
.grid-item a {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: block;
z-index: 1;
}
.grid-item-header {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 48px;
background: rgba(0,0,0,0.75);
padding: 8px 16px;
z-index: 10;
display: flex;
align-items: center;
width: 100%;
}
.grid-item-header h2 {
width: 100%;
overflow-wrap: break-word;
word-wrap: break-word;
hyphens: auto;
max-height: 32px;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
line-height: 1.2;
}
/* Modal styles */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background: white;
padding: 2rem;
border-radius: 8px;
max-width: 600px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
}
.space-item {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.5rem;
border-bottom: 1px solid #eee;
}
[x-cloak] { display: none !important; }
</style>
<script type="module">
import Alpine from "https://cdn.skypack.dev/[email protected]";
import Intersect from "https://cdn.skypack.dev/@alpinejs/intersect";
Alpine.plugin(Intersect);
Alpine.data("themesData", () => ({
async init() {
const data = await this.getThemes(this.page, this.sort, this.useTestData);
this.themes = data.themes;
this.totalPages = data.totalPages;
},
themes: [],
filter: "tool",
sort: "likes",
page: 1,
totalPages: -1,
useTestData: false,
searchQuery: "",
searchType: "keyword",
showImport: false,
hfToken: '',
authenticated: false,
userSpaces: [],
selectedSpaces: [],
spaceTypes: {},
authError: '',
buttonClass(attr, filter) {
if (this[attr] === filter) {
return "text-orange-600 bg-gradient-to-br from-orange-300 to-orange-100 px-2 md:px-3 py-1 rounded-full";
}
return "text-gray-800 hover:to-orange-300/100 hover:text-orange-600 dark:hover:bg-white";
},
async switchData() {
this.page = 1;
this.useTestData = !this.useTestData;
const data = await this.getThemes(this.page, this.sort, this.useTestData);
this.themes = data.themes;
this.totalPages = data.totalPages;
},
async sortThemes(sort) {
this.sort = sort;
this.page = 1;
const data = await this.getThemes(this.page, this.sort, this.useTestData);
this.themes = data.themes;
this.totalPages = data.totalPages;
},
async filterType(filter) {
this.filter = filter;
this.page = 1;
if (this.searchQuery) {
await this.searchThemes();
} else {
const data = await this.getThemes(this.page, this.sort, this.useTestData);
this.themes = data.themes;
this.totalPages = data.totalPages;
}
},
async searchThemes() {
this.page = 1;
const data = await this.getThemes(this.page, this.sort, this.useTestData);
this.themes = data.themes;
this.totalPages = data.totalPages;
},
async switchSearchType(type) {
this.searchType = type;
if (this.searchQuery) {
await this.searchThemes();
}
},
async getThemes(page, sort, useTestData) {
let data;
if (useTestData) {
const res = await fetch(
`https://huggingface.co/datasets/freddyaboulton/gradio-theme-subdomains/resolve/main/test_data.json`
);
data = await res.json();
} else {
const searchFilters = this.filter === 'tool' ? 'tool' : 'smolagents';
let searchUrl;
if (this.searchQuery) {
if (this.searchType === 'semantic') {
searchUrl = `https://huggingface.co/api/spaces/semantic-search?limit=100&filter=${searchFilters}&q=${encodeURIComponent(this.searchQuery)}&expand[]=subdomain&expand[]=lastModified&expand[]=likes&expand[]=runtime`;
} else {
searchUrl = `https://huggingface.co/api/spaces?limit=100&filter=${searchFilters}&search=${encodeURIComponent(this.searchQuery)}&expand[]=subdomain&expand[]=lastModified&expand[]=likes&expand[]=runtime`;
}
} else {
searchUrl = `https://huggingface.co/api/spaces?limit=100&filter=${encodeURIComponent(searchFilters)}&expand[]=subdomain&expand[]=lastModified&expand[]=likes&expand[]=runtime`;
}
const res = await fetch(searchUrl);
data = await res.json();
console.log(data);
// Transform the API response to match the expected format
data = data.filter(item => item.runtime?.stage === "RUNNING").map(item => ({
id: item.id,
subdomain: `https://${item.subdomain}.hf.space`,
likes: item.likes,
lastModified: item.lastModified,
type: this.filter // Add type based on current filter
}));
}
if (sort === 'likes') {
data.sort((a, b) => (b.likes - a.likes));
} else {
data.sort((a, b) => (new Date(b.lastModified) - new Date(a.lastModified)));
}
const pageThemes = data.slice((page - 1) * 15, page * 15);
console.log(pageThemes);
return {
themes: pageThemes,
totalPages: Math.ceil(data.length / 15)
};
},
async nextPage() {
if (this.page < this.totalPages) {
this.page += 1;
const data = await this.getThemes(this.page, this.sort, this.useTestData);
this.themes = this.themes.concat(data.themes);
this.totalPages = data.totalPages;
}
},
async authenticate() {
this.authError = ''; // Reset error message
try {
// Validate token format
if (!this.hfToken || this.hfToken.length < 10) {
throw new Error('Invalid token format');
}
// Fetch user info to validate token
const response = await fetch('https://huggingface.co/api/whoami', {
headers: { Authorization: `Bearer ${this.hfToken}` }
});
if (!response.ok) {
throw new Error('Authentication failed. Please check your token.');
}
const userData = await response.json();
console.log('Authenticated as:', userData.name);
// Fetch user spaces
const spacesRes = await fetch(
`https://huggingface.co/api/spaces?user=${userData.name}`,
{ headers: { Authorization: `Bearer ${this.hfToken}` } }
);
if (!spacesRes.ok) {
throw new Error('Failed to fetch spaces. Please try again.');
}
const spacesData = await spacesRes.json();
this.userSpaces = spacesData
.filter(space => space.runtime?.stage === "RUNNING")
.map(space => ({
id: space.id,
subdomain: `https://${space.subdomain}.hf.space`,
likes: space.likes,
lastModified: space.lastModified
}));
this.spaceTypes = Object.fromEntries(
this.userSpaces.map(space => [space.id, 'tool'])
);
this.authenticated = true;
} catch (error) {
console.error('Authentication error:', error);
this.authError = error.message;
}
},
importSpaces() {
const newThemes = this.selectedSpaces.map(id => {
const space = this.userSpaces.find(s => s.id === id);
return {
...space,
type: this.spaceTypes[id]
};
});
this.themes = [...this.themes, ...newThemes];
this.showImport = false;
this.hfToken = '';
this.authenticated = false;
this.userSpaces = [];
this.selectedSpaces = [];
},
}));
Alpine.start();
</script>
</head>
<body class="pb-10 pt-5 bg-white relative">
<section x-data="themesData">
<!-- Add Import Button -->
<div class="container px-6 mx-auto mb-4">
<button
@click="showImport = true"
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
>
Import Your Spaces
</button>
</div>
<!-- Import Modal -->
<div x-show="showImport" class="modal-overlay" x-cloak>
<div class="modal-content">
<template x-if="!authenticated">
<div class="space-y-4">
<h3 class="text-xl font-bold">Authenticate with Hugging Face</h3>
<input
type="password"
x-model="hfToken"
placeholder="Enter your HF access token"
class="w-full p-2 border rounded"
/>
<div class="flex gap-2">
<button
@click="authenticate()"
class="px-4 py-2 bg-green-600 text-white rounded"
>
Authenticate
</button>
<button
@click="showImport = false"
class="px-4 py-2 bg-gray-600 text-white rounded"
>
Cancel
</button>
</div>
<p x-show="authError" class="text-red-500" x-text="authError"></p>
</div>
</template>
<template x-if="authenticated">
<div class="space-y-4">
<h3 class="text-xl font-bold">Select Spaces to Import</h3>
<div class="space-y-2">
<template x-for="space in userSpaces" :key="space.id">
<div class="space-item">
<input
type="checkbox"
x-model="selectedSpaces"
:value="space.id"
>
<span x-text="space.id"></span>
<select
x-model="spaceTypes[space.id]"
class="ml-auto px-2 py-1 border rounded"
>
<option value="tool">Tool</option>
<option value="agent">Agent</option>
</select>
</div>
</template>
</div>
<div class="flex gap-2">
<button
@click="importSpaces()"
class="px-4 py-2 bg-blue-600 text-white rounded"
>
Import Selected
</button>
<button
@click="showImport = false"
class="px-4 py-2 bg-gray-600 text-white rounded"
>
Cancel
</button>
</div>
</div>
</template>
</div>
</div>
<!-- Existing header and grid remain unchanged -->
<section class="container px-6 grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-14 mx-auto relative">
<div class="col-span-2 lg:col-span-1 flex flex-col gap-14 row-start">
<div class="flex items-center gap-2">
<img src="https://camo.githubusercontent.com/a8c1f1d12aa3114010c6e74b29d47fee91d8da10a915f065c38e6d0ea7f16568/68747470733a2f2f68756767696e67666163652e636f2f64617461736574732f68756767696e67666163652f646f63756d656e746174696f6e2d696d616765732f7265736f6c76652f6d61696e2f736d6f6c6167656e74732f6d6173636f742e706e67" alt="Smolagents mascot" class="w-14 h-14 flex-shrink-0">
<h1 class="text-xl font-semibold text-gray-800 break-words">smolagents and tools gallery</h1>
</div>
</div>
<div class="col-span-2 md:col-span-3 flex items-center gap-14 flex flex-wrap lg-auto lg:ml-auto text-sm">
<div class="flex flex-col gap-2">
<div class="flex items-center">
<input
type="text"
x-model="searchQuery"
@input="searchThemes()"
placeholder="Search..."
class="px-3 py-1 border rounded-lg"
>
</div>
</div>
<div class="flex gap-2">
<span class="md:px-3 py-1 text-gray-800">type</span>
<button
:class="buttonClass('filter', 'tool')"
@click="filterType('tool')"
>
Tools
</button>
<button
:class="buttonClass('filter', 'agent')"
@click="filterType('agent')"
>
Agents
</button>
</div>
<div class="flex gap-2">
<span class="md:px-3 py-1 text-gray-800">sort by</span>
<button
:class="buttonClass('sort', 'likes')"
@click="sortThemes('likes')"
>
Most Likes
</button>
<button
:class="buttonClass('sort', 'recent')"
@click="sortThemes('recent')"
>
Recent
</button>
</div>
</div>
</section>
<div class="container px-6 grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2 mx-auto my-8 relative">
<template x-for="theme in themes" :key="theme.id">
<div class="grid-item">
<div class="grid-item-header">
<h2 class="text-sm font-medium text-white" x-text="theme.id"></h2>
</div>
<iframe :src="`${theme.subdomain}?_=${new Date().getTime()}`" :alt="theme.id" scrolling="no" frameborder="0" loading="lazy"></iframe>
<a :href="`https://huggingface.co/spaces/${theme.id}`" target="_blank"></a>
</div>
</template>
</div>
<div class="h-12 relative" x-intersect="nextPage" data-iframe-height></div>
</section>
</body>
</html>