Web-Manager / app.py
openfree's picture
Update app.py
769faf5 verified
raw
history blame
8.74 kB
from flask import Flask, render_template, request, jsonify
import os, re, json
app = Flask(__name__)
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 1. CURATED CATEGORIES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
CATEGORIES = {
"Productivity": [
"https://huggingface.co/spaces/ginigen/perflexity-clone",
"https://huggingface.co/spaces/ginipick/IDEA-DESIGN",
"https://huggingface.co/spaces/VIDraft/mouse-webgen",
"https://huggingface.co/spaces/openfree/Vibe-Game",
"https://huggingface.co/spaces/openfree/Game-Gallery",
"https://huggingface.co/spaces/aiqtech/Contributors-Leaderboard",
"https://huggingface.co/spaces/fantaxy/Model-Leaderboard",
"https://huggingface.co/spaces/fantaxy/Space-Leaderboard",
"https://huggingface.co/spaces/openfree/Korean-Leaderboard",
],
"Multimodal": [
"https://huggingface.co/spaces/openfree/DreamO-video",
"https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-photo",
"https://huggingface.co/spaces/Heartsync/NSFW-Uncensored",
"https://huggingface.co/spaces/fantaxy/Sound-AI-SFX",
"https://huggingface.co/spaces/ginigen/SFX-Sound-magic",
"https://huggingface.co/spaces/ginigen/VoiceClone-TTS",
"https://huggingface.co/spaces/aiqcamp/MCP-kokoro",
"https://huggingface.co/spaces/aiqcamp/ENGLISH-Speaking-Scoring",
],
"Professional": [
"https://huggingface.co/spaces/ginigen/blogger",
"https://huggingface.co/spaces/VIDraft/money-radar",
"https://huggingface.co/spaces/immunobiotech/drug-discovery",
"https://huggingface.co/spaces/immunobiotech/Gemini-MICHELIN",
"https://huggingface.co/spaces/Heartsync/Papers-Leaderboard",
"https://huggingface.co/spaces/VIDraft/PapersImpact",
"https://huggingface.co/spaces/ginipick/AgentX-Papers",
"https://huggingface.co/spaces/openfree/Cycle-Navigator",
],
"Image": [
"https://huggingface.co/spaces/ginigen/interior-design",
"https://huggingface.co/spaces/ginigen/Workflow-Canvas",
"https://huggingface.co/spaces/ginigen/Multi-LoRAgen",
"https://huggingface.co/spaces/ginigen/Every-Text",
"https://huggingface.co/spaces/ginigen/text3d-r1",
"https://huggingface.co/spaces/ginipick/FLUXllama",
"https://huggingface.co/spaces/Heartsync/FLUX-Vision",
"https://huggingface.co/spaces/ginigen/VisualCloze",
"https://huggingface.co/spaces/seawolf2357/Ghibli-Multilingual-Text-rendering",
"https://huggingface.co/spaces/ginigen/Ghibli-Meme-Studio",
"https://huggingface.co/spaces/VIDraft/Open-Meme-Studio",
"https://huggingface.co/spaces/ginigen/3D-LLAMA",
],
"LLM / VLM": [
"https://huggingface.co/spaces/VIDraft/Gemma-3-R1984-4B",
"https://huggingface.co/spaces/VIDraft/Gemma-3-R1984-12B",
"https://huggingface.co/spaces/ginigen/Mistral-Perflexity",
"https://huggingface.co/spaces/aiqcamp/gemini-2.5-flash-preview",
"https://huggingface.co/spaces/openfree/qwen3-30b-a3b-research",
"https://huggingface.co/spaces/openfree/qwen3-235b-a22b-research",
"https://huggingface.co/spaces/openfree/Llama-4-Maverick-17B-Research",
],
}
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 2. URL HELPERS (iframe + screenshot) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
def direct_url(hf_url: str) -> str:
m = re.match(r"https?://huggingface\.co/spaces/([^/]+)/([^/?#]+)", hf_url)
if not m:
return hf_url
owner, name = m.groups()
owner = owner.lower()
name = name.replace('.', '-').replace('_', '-').lower()
return f"https://{owner}-{name}.hf.space"
def screenshot_url(hf_url: str) -> str:
return f"https://image.thum.io/get/fullpage/{direct_url(hf_url)}"
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 3. CATEGORY API (used by tabs) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@app.route('/api/category')
def api_category():
cat = request.args.get('name', '')
urls = CATEGORIES.get(cat, [])
data = []
for url in urls:
m = re.match(r"https?://huggingface\.co/spaces/([^/]+)/([^/?#]+)", url)
owner, name = m.groups() if m else ('', url.split('/')[-1])
data.append({
"title": name,
"owner": owner,
"name": name,
"iframe": direct_url(url),
"shot": screenshot_url(url),
"hf": url
})
return jsonify(data)
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 4. FRONT PAGE ROUTE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@app.route('/')
def home():
return render_template('index.html', cats=list(CATEGORIES.keys()))
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 5. BUILD index.html (once) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
os.makedirs('templates', exist_ok=True)
with open('templates/index.html', 'w', encoding='utf-8') as fp:
fp.write(r'''<!DOCTYPE html>
<html><head><meta charset="utf-8"><meta name="viewport"content="width=device-width,initial-scale=1">
<title>Dynamic HF Spaces Gallery</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@300;600&display=swap');
body{margin:0;font-family:Nunito,sans-serif;background:#f6f8fb}
.tabs{display:flex;flex-wrap:wrap;gap:8px;padding:16px}
.tab{padding:6px 14px;border:none;border-radius:18px;background:#e2e8f0;font-weight:600;cursor:pointer}
.tab.active{background:#a78bfa;color:#1a202c}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(330px,1fr));gap:14px;padding:0 16px 60px}
.card{background:#fff;border-radius:12px;box-shadow:0 2px 8px rgba(0,0,0,.08);overflow:hidden;height:420px;display:flex;flex-direction:column;position:relative}
.frame{flex:1;position:relative;overflow:hidden}
.frame iframe,.frame img{position:absolute;width:166.667%;height:166.667%;transform:scale(.6);transform-origin:top left;border:0}
.err{display:none;align-items:center;justify-content:center;width:100%;height:100%;background:#fafafa;text-align:center;padding:10px;color:#666;font-size:.9rem}
.foot{height:44px;background:#fafafa;display:flex;align-items:center;justify-content:center;border-top:1px solid #eee}
.foot a{font-size:.82rem;font-weight:700;color:#4a6dd8;text-decoration:none}
</style></head>
<body>
<div class="tabs" id="tabs"></div>
<div class="grid" id="grid"></div>
<script>
// ----- helpers --------------------------------------------------
function createCard(sp){
const c=document.createElement('div');c.className='card';
const f=document.createElement('div');f.className='frame';
const ifr=document.createElement('iframe');
ifr.src=sp.iframe; ifr.loading='lazy'; f.appendChild(ifr);
const err=document.createElement('div');err.className='err';
err.innerHTML=`Preview blocked.<br><a href="${sp.hf}" target="_blank">Open on HF โ†—</a>`;
f.appendChild(err);
ifr.onerror=()=>toSnapshot();
// after 10 s fallback if still blank
setTimeout(()=>{try{
const ok=ifr.contentWindow && ifr.contentWindow.document.body.innerHTML.length>30;
if(!ok) toSnapshot();}catch(e){toSnapshot();}},10000);
function toSnapshot(){
ifr.remove(); const img=new Image();
img.src=sp.shot; img.loading='lazy';
img.onerror=()=>{err.style.display='flex';};
f.prepend(img);
}
const foot=document.createElement('div');foot.className='foot';
foot.innerHTML=`<a href="${sp.iframe}" target="_blank">${sp.title}</a>`;
c.appendChild(f); c.appendChild(foot);
return c;
}
// ----- load tab -------------------------------------------------
const cats={{cats|tojson}};
const tabs=document.getElementById('tabs');
const grid=document.getElementById('grid');
let active="";
function load(cat){
if(cat===active)return; active=cat;
[...tabs.children].forEach(b=>b.classList.toggle('active',b.dataset.c===cat));
grid.innerHTML='<p style="grid-column:1/-1;text-align:center;padding:40px">Loadingโ€ฆ</p>';
fetch('/api/category?name='+encodeURIComponent(cat))
.then(r=>r.json()).then(arr=>{
grid.innerHTML='';
arr.forEach(sp=>grid.appendChild(createCard(sp)));
});
}
// tabs init
cats.forEach((c,i)=>{
const b=document.createElement('button');
b.className='tab';b.textContent=c;b.dataset.c=c;
b.onclick=()=>load(c);tabs.appendChild(b);
if(i===0)load(c);
});
</script>
</body></html>''')
# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 6. RUN APP โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
if __name__ == '__main__':
# hugggingface spaces use 7860 by convention
app.run(host='0.0.0.0', port=7860)