openfree commited on
Commit
769faf5
ยท
verified ยท
1 Parent(s): 3140ee8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +78 -42
app.py CHANGED
@@ -3,7 +3,7 @@ import os, re, json
3
 
4
  app = Flask(__name__)
5
 
6
- # 1๏ธโƒฃ curated lists ----------------------------------------------------------
7
  CATEGORIES = {
8
  "Productivity": [
9
  "https://huggingface.co/spaces/ginigen/perflexity-clone",
@@ -61,83 +61,119 @@ CATEGORIES = {
61
  ],
62
  }
63
 
64
- # 2๏ธโƒฃ url helpers -----------------------------------------------------------
65
- def to_direct(url: str) -> str:
66
- m = re.match(r"https?://huggingface\.co/spaces/([^/]+)/([^/?#]+)", url)
67
  if not m:
68
- return url
69
- owner, space = m.groups()
70
  owner = owner.lower()
71
- space = space.replace('.', '-').replace('_', '-').lower()
72
- return f"https://{owner}-{space}.hf.space"
73
 
74
- def to_snapshot(url: str) -> str:
75
- return f"https://image.thum.io/get/fullpage/{to_direct(url)}"
76
 
77
- # 3๏ธโƒฃ simple API -----------------------------------------------------------
78
  @app.route('/api/category')
79
- def api_cat():
80
- name = request.args.get('name', '')
81
- urls = CATEGORIES.get(name, [])
82
- return jsonify([{
83
- "title": url.split('/')[-1],
84
- "img": to_snapshot(url),
85
- "link": to_direct(url)
86
- } for url in urls])
 
 
 
 
 
 
 
 
87
 
88
- # 4๏ธโƒฃ web front ------------------------------------------------------------
89
  @app.route('/')
90
  def home():
91
  return render_template('index.html', cats=list(CATEGORIES.keys()))
92
 
93
- # 5๏ธโƒฃ generate template once ----------------------------------------------
94
  os.makedirs('templates', exist_ok=True)
95
  with open('templates/index.html', 'w', encoding='utf-8') as fp:
96
  fp.write(r'''<!DOCTYPE html>
97
  <html><head><meta charset="utf-8"><meta name="viewport"content="width=device-width,initial-scale=1">
98
- <title>Curated HuggingFace Spaces</title>
99
  <style>
 
100
  body{margin:0;font-family:Nunito,sans-serif;background:#f6f8fb}
101
  .tabs{display:flex;flex-wrap:wrap;gap:8px;padding:16px}
102
  .tab{padding:6px 14px;border:none;border-radius:18px;background:#e2e8f0;font-weight:600;cursor:pointer}
103
  .tab.active{background:#a78bfa;color:#1a202c}
104
- .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:14px;padding:0 16px 40px}
105
- .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}
106
  .frame{flex:1;position:relative;overflow:hidden}
107
- .frame img{width:166.667%;height:166.667%;transform:scale(.6);transform-origin:top left}
 
108
  .foot{height:44px;background:#fafafa;display:flex;align-items:center;justify-content:center;border-top:1px solid #eee}
109
- .foot a{font-size:.85rem;font-weight:700;color:#4a6dd8;text-decoration:none}
110
  </style></head>
111
  <body>
112
  <div class="tabs" id="tabs"></div>
113
  <div class="grid" id="grid"></div>
 
114
  <script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  const cats={{cats|tojson}};
116
  const tabs=document.getElementById('tabs');
117
  const grid=document.getElementById('grid');
118
  let active="";
119
  function load(cat){
120
- if(cat===active) return; active=cat;
121
- [...tabs.children].forEach(b=>b.classList.toggle('active',b.dataset.c===cat));
122
- grid.innerHTML='<p style="grid-column:1/-1;text-align:center;padding:40px">Loadingโ€ฆ</p>';
123
- fetch('/api/category?name='+encodeURIComponent(cat))
124
- .then(r=>r.json()).then(arr=>{
125
- grid.innerHTML="";
126
- arr.forEach(sp=>{
127
- const c=document.createElement('div');c.className='card';
128
- c.innerHTML=`<div class="frame"><img src="${sp.img}" loading="lazy"></div>
129
- <div class="foot"><a href="${sp.link}" target="_blank">${sp.title}</a></div>`;
130
- grid.appendChild(c);
131
- });
132
- });
133
  }
 
134
  cats.forEach((c,i)=>{
135
- const b=document.createElement('button');b.textContent=c;b.className='tab';b.dataset.c=c;
136
- b.onclick=()=>load(c);tabs.appendChild(b);if(i==0)load(c);
 
 
137
  });
138
  </script>
139
  </body></html>''')
140
 
141
- # 6๏ธโƒฃ run ------------------------------------------------------------------
142
  if __name__ == '__main__':
 
143
  app.run(host='0.0.0.0', port=7860)
 
3
 
4
  app = Flask(__name__)
5
 
6
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 1. CURATED CATEGORIES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
7
  CATEGORIES = {
8
  "Productivity": [
9
  "https://huggingface.co/spaces/ginigen/perflexity-clone",
 
61
  ],
62
  }
63
 
64
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 2. URL HELPERS (iframe + screenshot) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
65
+ def direct_url(hf_url: str) -> str:
66
+ m = re.match(r"https?://huggingface\.co/spaces/([^/]+)/([^/?#]+)", hf_url)
67
  if not m:
68
+ return hf_url
69
+ owner, name = m.groups()
70
  owner = owner.lower()
71
+ name = name.replace('.', '-').replace('_', '-').lower()
72
+ return f"https://{owner}-{name}.hf.space"
73
 
74
+ def screenshot_url(hf_url: str) -> str:
75
+ return f"https://image.thum.io/get/fullpage/{direct_url(hf_url)}"
76
 
77
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 3. CATEGORY API (used by tabs) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
78
  @app.route('/api/category')
79
+ def api_category():
80
+ cat = request.args.get('name', '')
81
+ urls = CATEGORIES.get(cat, [])
82
+ data = []
83
+ for url in urls:
84
+ m = re.match(r"https?://huggingface\.co/spaces/([^/]+)/([^/?#]+)", url)
85
+ owner, name = m.groups() if m else ('', url.split('/')[-1])
86
+ data.append({
87
+ "title": name,
88
+ "owner": owner,
89
+ "name": name,
90
+ "iframe": direct_url(url),
91
+ "shot": screenshot_url(url),
92
+ "hf": url
93
+ })
94
+ return jsonify(data)
95
 
96
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 4. FRONT PAGE ROUTE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
97
  @app.route('/')
98
  def home():
99
  return render_template('index.html', cats=list(CATEGORIES.keys()))
100
 
101
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 5. BUILD index.html (once) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
102
  os.makedirs('templates', exist_ok=True)
103
  with open('templates/index.html', 'w', encoding='utf-8') as fp:
104
  fp.write(r'''<!DOCTYPE html>
105
  <html><head><meta charset="utf-8"><meta name="viewport"content="width=device-width,initial-scale=1">
106
+ <title>Dynamic HF Spaces Gallery</title>
107
  <style>
108
+ @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@300;600&display=swap');
109
  body{margin:0;font-family:Nunito,sans-serif;background:#f6f8fb}
110
  .tabs{display:flex;flex-wrap:wrap;gap:8px;padding:16px}
111
  .tab{padding:6px 14px;border:none;border-radius:18px;background:#e2e8f0;font-weight:600;cursor:pointer}
112
  .tab.active{background:#a78bfa;color:#1a202c}
113
+ .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(330px,1fr));gap:14px;padding:0 16px 60px}
114
+ .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}
115
  .frame{flex:1;position:relative;overflow:hidden}
116
+ .frame iframe,.frame img{position:absolute;width:166.667%;height:166.667%;transform:scale(.6);transform-origin:top left;border:0}
117
+ .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}
118
  .foot{height:44px;background:#fafafa;display:flex;align-items:center;justify-content:center;border-top:1px solid #eee}
119
+ .foot a{font-size:.82rem;font-weight:700;color:#4a6dd8;text-decoration:none}
120
  </style></head>
121
  <body>
122
  <div class="tabs" id="tabs"></div>
123
  <div class="grid" id="grid"></div>
124
+
125
  <script>
126
+ // ----- helpers --------------------------------------------------
127
+ function createCard(sp){
128
+ const c=document.createElement('div');c.className='card';
129
+ const f=document.createElement('div');f.className='frame';
130
+ const ifr=document.createElement('iframe');
131
+ ifr.src=sp.iframe; ifr.loading='lazy'; f.appendChild(ifr);
132
+ const err=document.createElement('div');err.className='err';
133
+ err.innerHTML=`Preview blocked.<br><a href="${sp.hf}" target="_blank">Open on HF โ†—</a>`;
134
+ f.appendChild(err);
135
+ ifr.onerror=()=>toSnapshot();
136
+ // after 10 s fallback if still blank
137
+ setTimeout(()=>{try{
138
+ const ok=ifr.contentWindow && ifr.contentWindow.document.body.innerHTML.length>30;
139
+ if(!ok) toSnapshot();}catch(e){toSnapshot();}},10000);
140
+ function toSnapshot(){
141
+ ifr.remove(); const img=new Image();
142
+ img.src=sp.shot; img.loading='lazy';
143
+ img.onerror=()=>{err.style.display='flex';};
144
+ f.prepend(img);
145
+ }
146
+ const foot=document.createElement('div');foot.className='foot';
147
+ foot.innerHTML=`<a href="${sp.iframe}" target="_blank">${sp.title}</a>`;
148
+ c.appendChild(f); c.appendChild(foot);
149
+ return c;
150
+ }
151
+ // ----- load tab -------------------------------------------------
152
  const cats={{cats|tojson}};
153
  const tabs=document.getElementById('tabs');
154
  const grid=document.getElementById('grid');
155
  let active="";
156
  function load(cat){
157
+ if(cat===active)return; active=cat;
158
+ [...tabs.children].forEach(b=>b.classList.toggle('active',b.dataset.c===cat));
159
+ grid.innerHTML='<p style="grid-column:1/-1;text-align:center;padding:40px">Loadingโ€ฆ</p>';
160
+ fetch('/api/category?name='+encodeURIComponent(cat))
161
+ .then(r=>r.json()).then(arr=>{
162
+ grid.innerHTML='';
163
+ arr.forEach(sp=>grid.appendChild(createCard(sp)));
164
+ });
 
 
 
 
 
165
  }
166
+ // tabs init
167
  cats.forEach((c,i)=>{
168
+ const b=document.createElement('button');
169
+ b.className='tab';b.textContent=c;b.dataset.c=c;
170
+ b.onclick=()=>load(c);tabs.appendChild(b);
171
+ if(i===0)load(c);
172
  });
173
  </script>
174
  </body></html>''')
175
 
176
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 6. RUN APP โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
177
  if __name__ == '__main__':
178
+ # hugggingface spaces use 7860 by convention
179
  app.run(host='0.0.0.0', port=7860)