openfree commited on
Commit
3140ee8
ยท
verified ยท
1 Parent(s): 93d180e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +61 -88
app.py CHANGED
@@ -3,7 +3,7 @@ import os, re, json
3
 
4
  app = Flask(__name__)
5
 
6
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 1. CURATED CATEGORIES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
7
  CATEGORIES = {
8
  "Productivity": [
9
  "https://huggingface.co/spaces/ginigen/perflexity-clone",
@@ -61,110 +61,83 @@ CATEGORIES = {
61
  ],
62
  }
63
 
64
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 2. HELPERS: build embed & direct URLs from HF link โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
65
- def space_embed_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
- return f"https://huggingface.co/spaces/{owner}/{name}/embed"
71
-
72
- def space_direct_url(hf_url: str) -> str:
73
- m = re.match(r"https?://huggingface\.co/spaces/([^/]+)/([^/?#]+)", hf_url)
74
- if not m:
75
- return hf_url
76
- owner, name = m.groups()
77
- # dots/underscores โ†’ dashes, lowercase
78
  owner = owner.lower()
79
- name = name.replace('.', '-').replace('_', '-').lower()
80
- return f"https://{owner}-{name}.hf.space"
81
 
82
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 3. API: return spaces for a given category โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 
 
 
83
  @app.route('/api/category')
84
- def api_category():
85
- cat = request.args.get('name', '')
86
- urls = CATEGORIES.get(cat, [])
87
- spaces = [{
88
  "title": url.split('/')[-1],
89
- "embedUrl": space_embed_url(url),
90
- "directUrl": space_direct_url(url)
91
- } for url in urls]
92
- return jsonify({"spaces": spaces})
93
 
94
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 4. ROUTES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
95
  @app.route('/')
96
  def home():
97
- return render_template('index.html', categories=list(CATEGORIES.keys()))
98
-
99
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 5. CREATE index.html with tabbed UI (once) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
100
- if not os.path.exists('templates'):
101
- os.makedirs('templates')
102
 
103
- with open('templates/index.html', 'w', encoding='utf-8') as f:
104
- f.write(r'''<!DOCTYPE html>
105
- <html lang="en">
106
- <head>
107
- <meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0">
108
- <title>Curated HF Spaces</title>
109
  <style>
110
- @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@300;500;700&display=swap');
111
- body{margin:0;font-family:Nunito,sans-serif;background:#f0f2f8;}
112
- .tabs{display:flex;gap:6px;flex-wrap:wrap;padding:16px;}
113
- .tab-btn{padding:8px 14px;border:none;border-radius:20px;background:#e2e8f0;cursor:pointer;font-weight:600}
114
- .tab-btn.active{background:#c4b5fd;color:#1a202c}
115
- .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:14px;padding:0 16px 40px}
116
- .card{background:#fff;border-radius:12px;box-shadow:0 2px 8px rgba(0,0,0,.06);overflow:hidden;display:flex;flex-direction:column;height:420px;position:relative}
117
- .tag{position:absolute;top:6px;left:6px;background:#10b981;color:#fff;font-size:.65rem;font-weight:700;padding:2px 6px;border-radius:4px;z-index:2}
118
- .frame{flex:1;overflow:hidden}
119
- .frame iframe{position:absolute;width:166.667%;height:166.667%;transform:scale(.6);transform-origin:top left;border:0}
120
- .foot{height:40px;background:#fafafa;display:flex;align-items:center;justify-content:center;border-top:1px solid #eee}
121
- .foot a{font-size:.82rem;font-weight:600;color:#4a6dd8;text-decoration:none}
122
- </style>
123
- </head>
124
  <body>
125
  <div class="tabs" id="tabs"></div>
126
- <div id="grid" class="grid"></div>
127
-
128
  <script>
129
- const cats={{ categories|tojson }};
130
- const tabsEl=document.getElementById('tabs');
131
- const gridEl=document.getElementById('grid');
132
- let active='';
133
-
134
- function loadTab(cat){
135
- if(cat===active) return;
136
- active=cat;
137
- [...tabsEl.children].forEach(b=>b.classList.toggle('active',b.dataset.cat===cat));
138
- gridEl.innerHTML='<p style="grid-column:1/-1;text-align:center;padding:40px">Loadingโ€ฆ</p>';
139
- fetch(`/api/category?name=${encodeURIComponent(cat)}`)
140
- .then(r=>r.json()).then(data=>{
141
- gridEl.innerHTML='';
142
- data.spaces.forEach(sp=>{
143
- const card=document.createElement('div');
144
- card.className='card';
145
- card.innerHTML=`
146
- <span class="tag">LIVE</span>
147
- <div class="frame"><iframe src="${sp.embedUrl}" loading="lazy" allow="accelerometer; gyroscope;"></iframe></div>
148
- <div class="foot"><a href="${sp.directUrl}" target="_blank">Open โ†—</a></div>
149
- `;
150
- gridEl.appendChild(card);
151
- });
152
- });
153
  }
154
-
155
  cats.forEach((c,i)=>{
156
- const b=document.createElement('button');
157
- b.textContent=c;
158
- b.className='tab-btn';
159
- b.dataset.cat=c;
160
- b.onclick=()=>loadTab(c);
161
- tabsEl.appendChild(b);
162
- if(i===0) loadTab(c);
163
  });
164
  </script>
165
- </body>
166
- </html>''')
167
 
168
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 6. RUN โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
169
  if __name__ == '__main__':
170
  app.run(host='0.0.0.0', port=7860)
 
3
 
4
  app = Flask(__name__)
5
 
6
+ # 1๏ธโƒฃ curated lists ----------------------------------------------------------
7
  CATEGORIES = {
8
  "Productivity": [
9
  "https://huggingface.co/spaces/ginigen/perflexity-clone",
 
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)