Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -3,7 +3,7 @@ import os, re, json
|
|
3 |
|
4 |
app = Flask(__name__)
|
5 |
|
6 |
-
#
|
7 |
CATEGORIES = {
|
8 |
"Productivity": [
|
9 |
"https://huggingface.co/spaces/ginigen/perflexity-clone",
|
@@ -61,110 +61,83 @@ CATEGORIES = {
|
|
61 |
],
|
62 |
}
|
63 |
|
64 |
-
#
|
65 |
-
def
|
66 |
-
m = re.match(r"https?://huggingface\.co/spaces/([^/]+)/([^/?#]+)",
|
67 |
if not m:
|
68 |
-
return
|
69 |
-
owner,
|
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 |
-
|
80 |
-
return f"https://{owner}-{
|
81 |
|
82 |
-
|
|
|
|
|
|
|
83 |
@app.route('/api/category')
|
84 |
-
def
|
85 |
-
|
86 |
-
urls = CATEGORIES.get(
|
87 |
-
|
88 |
"title": url.split('/')[-1],
|
89 |
-
"
|
90 |
-
"
|
91 |
-
} for url in urls]
|
92 |
-
return jsonify({"spaces": spaces})
|
93 |
|
94 |
-
#
|
95 |
@app.route('/')
|
96 |
def home():
|
97 |
-
return render_template('index.html',
|
98 |
-
|
99 |
-
# โโโโโโโโโโโโโ 5. CREATE index.html with tabbed UI (once) โโโโโโโโโโโโโโโ
|
100 |
-
if not os.path.exists('templates'):
|
101 |
-
os.makedirs('templates')
|
102 |
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
<meta charset="
|
108 |
-
<title>Curated
|
109 |
<style>
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
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
|
127 |
-
|
128 |
<script>
|
129 |
-
const cats={{
|
130 |
-
const
|
131 |
-
const
|
132 |
-
let active=
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
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 |
-
|
157 |
-
|
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 |
-
#
|
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)
|