File size: 8,473 Bytes
03dcabd
93d180e
d927a6e
 
 
769faf5
93d180e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b1a8e9d
857f721
769faf5
 
93d180e
769faf5
 
b1a8e9d
769faf5
 
cf37bae
769faf5
 
3140ee8
857f721
93d180e
769faf5
 
 
857f721
 
 
 
769faf5
 
 
857f721
 
4372751
857f721
fb08ce6
 
3140ee8
fb08ce6
857f721
3140ee8
 
 
 
769faf5
93d180e
769faf5
3140ee8
 
 
 
857f721
 
769faf5
3140ee8
769faf5
 
3140ee8
769faf5
857f721
 
fb08ce6
93d180e
3140ee8
769faf5
93d180e
857f721
 
 
 
 
 
769faf5
857f721
 
 
769faf5
857f721
 
 
769faf5
 
857f721
 
 
 
 
 
 
 
 
 
 
 
769faf5
857f721
769faf5
 
857f721
 
 
 
769faf5
857f721
 
3140ee8
857f721
 
769faf5
 
 
857f721
 
 
 
93d180e
857f721
 
93d180e
769faf5
857f721
 
 
 
93d180e
 
3140ee8
93d180e
857f721
93d180e
e2255ea
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
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. API FOR TABS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@app.route('/api/category')
def api_category():
    cat = request.args.get('name', '')
    urls = CATEGORIES.get(cat, [])
    return jsonify([
        {
            "title": url.split('/')[-1],
            "owner": url.split('/')[-2] if '/spaces/' in url else '',
            "iframe": direct_url(url),
            "shot":   screenshot_url(url),
            "hf":     url
        } for url in urls
    ])

# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 4. ROUTES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@app.route('/')
def home():
    return render_template('index.html', cats=list(CATEGORIES.keys()))

# โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 5. CREATE TEMPLATE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
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(3,1fr);gap:14px;padding:0 16px 60px}
@media(max-width:800px){.grid{grid-template-columns:1fr}}
.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>
const cats={{cats|tojson}};
const tabs=document.getElementById('tabs');
const grid=document.getElementById('grid');
let active="";

/* ---------- card builder (dynamic + fallback) ---------- */
function createCard(sp){
  const card=document.createElement('div');card.className='card';

  const frameBox=document.createElement('div');frameBox.className='frame';
  const ifr=document.createElement('iframe');
  ifr.src=sp.iframe; ifr.loading='lazy';
  frameBox.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>`;
  frameBox.appendChild(err);

  let loaded=false;
  ifr.onload = ()=>{ loaded=true; };          // success
  ifr.onerror= fallback;                      // immediate fail
  setTimeout(()=>{ if(!loaded) fallback(); },10000); // watchdog

  function fallback(){
    ifr.remove();
    const img=new Image(); img.src=sp.shot; img.loading='lazy';
    img.onerror=()=>{ err.style.display='flex'; };
    frameBox.prepend(img);
  }

  const foot=document.createElement('div');foot.className='foot';
  foot.innerHTML=`<a href="${sp.iframe}" target="_blank">${sp.title}</a>`;

  card.appendChild(frameBox);
  card.appendChild(foot);
  return card;
}

/* ---------- load tab ---------- */
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)));
    });
}

/* ---------- init tabs ---------- */
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 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=7860)