openfree commited on
Commit
623a444
ยท
verified ยท
1 Parent(s): 367f8f9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +284 -534
app.py CHANGED
@@ -1,11 +1,9 @@
1
  from flask import Flask, render_template, request, jsonify
2
- import os, re, json, sqlite3
3
 
4
  app = Flask(__name__)
5
 
6
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 1. CONFIGURATION โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
7
- DB_FILE = "favorite_sites.json" # JSON file for backward compatibility
8
- SQLITE_DB = "favorite_sites.db" # SQLite database for persistence
9
 
10
  # Domains that commonly block iframes
11
  BLOCKED_DOMAINS = [
@@ -16,16 +14,215 @@ BLOCKED_DOMAINS = [
16
 
17
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 2. CURATED CATEGORIES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
18
  CATEGORIES = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  "Productivity": [
 
 
 
 
 
 
20
  "https://huggingface.co/spaces/openfree/Chart-GPT",
21
- "https://huggingface.co/spaces/ginipick/Change-Hair",
22
  "https://huggingface.co/spaces/VIDraft/Voice-Clone-Podcast",
23
  "https://huggingface.co/spaces/ginipick/PDF-EXAM",
24
- "https://huggingface.co/spaces/openfree/Naming",
25
  "https://huggingface.co/spaces/ginigen/perflexity-clone",
26
  "https://huggingface.co/spaces/ginipick/IDEA-DESIGN",
27
  "https://huggingface.co/spaces/ginipick/10m-marketing",
28
- "https://huggingface.co/spaces/ginipick/AI-BOOK",
29
  "https://huggingface.co/spaces/openfree/Live-Podcast",
30
  "https://huggingface.co/spaces/openfree/AI-Podcast",
31
  "https://huggingface.co/spaces/ginipick/QR-Canvas-plus",
@@ -34,9 +231,17 @@ CATEGORIES = {
34
  "https://huggingface.co/spaces/openfree/Vibe-Game",
35
  "https://huggingface.co/spaces/VIDraft/NH-Prediction",
36
  "https://huggingface.co/spaces/ginipick/NH-Korea",
37
-
 
38
  ],
39
  "Multimodal": [
 
 
 
 
 
 
 
40
  "https://huggingface.co/spaces/Heartsync/adult",
41
  "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored",
42
  "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-video2",
@@ -56,6 +261,9 @@ CATEGORIES = {
56
  "https://huggingface.co/spaces/fantaxy/Remove-Video-Background",
57
  ],
58
  "Professional": [
 
 
 
59
  "https://huggingface.co/spaces/Heartsync/Novel-NSFW",
60
  "https://huggingface.co/spaces/fantaxy/fantasy-novel",
61
  "https://huggingface.co/spaces/VIDraft/money-radar",
@@ -70,6 +278,8 @@ CATEGORIES = {
70
  "https://huggingface.co/spaces/ginigen/multimodal-chat-mbti-korea",
71
  ],
72
  "Image": [
 
 
73
  "https://huggingface.co/spaces/ginigen/FLUX-Ghibli-LoRA2",
74
  "https://huggingface.co/spaces/aiqcamp/REMOVAL-TEXT-IMAGE",
75
  "https://huggingface.co/spaces/VIDraft/BAGEL-Websearch",
@@ -95,14 +305,13 @@ CATEGORIES = {
95
  "https://huggingface.co/spaces/VIDraft/stable-diffusion-3.5-large-turboX",
96
  "https://huggingface.co/spaces/aiqtech/flxgif",
97
  "https://huggingface.co/spaces/openfree/VectorFlow",
98
-
99
- # "https://huggingface.co/spaces/ginigen/3D-LLAMA",
100
- # "https://huggingface.co/spaces/ginigen/Multi-LoRAgen",
101
-
102
  ],
103
  "LLM / VLM": [
 
104
  "https://huggingface.co/spaces/ginigen/deepseek-r1-0528-API",
105
- "https://huggingface.co/spaces/aiqcamp/Mistral-Devstral-API"
106
  "https://huggingface.co/spaces/aiqcamp/deepseek-r1-0528",
107
  "https://huggingface.co/spaces/aiqcamp/deepseek-r1-0528-qwen3-8b",
108
  "https://huggingface.co/spaces/aiqcamp/deepseek-r1-0528",
@@ -118,136 +327,7 @@ CATEGORIES = {
118
  ],
119
  }
120
 
121
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 3. DATABASE FUNCTIONS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
122
- def init_db():
123
- # Initialize JSON file if it doesn't exist
124
- if not os.path.exists(DB_FILE):
125
- with open(DB_FILE, "w", encoding="utf-8") as f:
126
- json.dump([], f, ensure_ascii=False)
127
-
128
- # Initialize SQLite database
129
- conn = sqlite3.connect(SQLITE_DB)
130
- cursor = conn.cursor()
131
- cursor.execute('''
132
- CREATE TABLE IF NOT EXISTS urls (
133
- id INTEGER PRIMARY KEY AUTOINCREMENT,
134
- url TEXT UNIQUE NOT NULL,
135
- date_added TIMESTAMP DEFAULT CURRENT_TIMESTAMP
136
- )
137
- ''')
138
- conn.commit()
139
-
140
- # If we have data in JSON but not in SQLite (first run with new SQLite DB),
141
- # migrate the data from JSON to SQLite
142
- json_urls = load_json()
143
- if json_urls:
144
- db_urls = load_db_sqlite()
145
- for url in json_urls:
146
- if url not in db_urls:
147
- add_url_to_sqlite(url)
148
-
149
- conn.close()
150
-
151
- def load_json():
152
- """Load URLs from JSON file (for backward compatibility)"""
153
- try:
154
- with open(DB_FILE, "r", encoding="utf-8") as f:
155
- raw = json.load(f)
156
- return raw if isinstance(raw, list) else []
157
- except Exception:
158
- return []
159
-
160
- def save_json(lst):
161
- """Save URLs to JSON file (for backward compatibility)"""
162
- try:
163
- with open(DB_FILE, "w", encoding="utf-8") as f:
164
- json.dump(lst, f, ensure_ascii=False, indent=2)
165
- return True
166
- except Exception:
167
- return False
168
-
169
- def load_db_sqlite():
170
- """Load URLs from SQLite database"""
171
- conn = sqlite3.connect(SQLITE_DB)
172
- cursor = conn.cursor()
173
- cursor.execute("SELECT url FROM urls ORDER BY date_added DESC")
174
- urls = [row[0] for row in cursor.fetchall()]
175
- conn.close()
176
- return urls
177
-
178
- def add_url_to_sqlite(url):
179
- """Add a URL to SQLite database"""
180
- conn = sqlite3.connect(SQLITE_DB)
181
- cursor = conn.cursor()
182
- try:
183
- cursor.execute("INSERT INTO urls (url) VALUES (?)", (url,))
184
- conn.commit()
185
- success = True
186
- except sqlite3.IntegrityError:
187
- # URL already exists
188
- success = False
189
- conn.close()
190
- return success
191
-
192
- def update_url_in_sqlite(old_url, new_url):
193
- """Update a URL in SQLite database"""
194
- conn = sqlite3.connect(SQLITE_DB)
195
- cursor = conn.cursor()
196
- try:
197
- cursor.execute("UPDATE urls SET url = ? WHERE url = ?", (new_url, old_url))
198
- if cursor.rowcount > 0:
199
- conn.commit()
200
- success = True
201
- else:
202
- success = False
203
- except sqlite3.IntegrityError:
204
- # New URL already exists
205
- success = False
206
- conn.close()
207
- return success
208
-
209
- def delete_url_from_sqlite(url):
210
- """Delete a URL from SQLite database"""
211
- conn = sqlite3.connect(SQLITE_DB)
212
- cursor = conn.cursor()
213
- cursor.execute("DELETE FROM urls WHERE url = ?", (url,))
214
- if cursor.rowcount > 0:
215
- conn.commit()
216
- success = True
217
- else:
218
- success = False
219
- conn.close()
220
- return success
221
-
222
- def load_db():
223
- """Primary function to load URLs - prioritizes SQLite DB but falls back to JSON"""
224
- urls = load_db_sqlite()
225
- if not urls:
226
- # If SQLite DB is empty, try loading from JSON
227
- urls = load_json()
228
- # If we found URLs in JSON, migrate them to SQLite
229
- for url in urls:
230
- add_url_to_sqlite(url)
231
- return urls
232
-
233
- def save_db(lst):
234
- """Save URLs to both SQLite and JSON"""
235
- # Get existing URLs from SQLite for comparison
236
- existing_urls = load_db_sqlite()
237
-
238
- # Clear all URLs from SQLite and add the new list
239
- conn = sqlite3.connect(SQLITE_DB)
240
- cursor = conn.cursor()
241
- cursor.execute("DELETE FROM urls")
242
- for url in lst:
243
- cursor.execute("INSERT INTO urls (url) VALUES (?)", (url,))
244
- conn.commit()
245
- conn.close()
246
-
247
- # Also save to JSON for backward compatibility
248
- return save_json(lst)
249
-
250
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 4. URL HELPERS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
251
  def direct_url(hf_url):
252
  m = re.match(r"https?://huggingface\.co/spaces/([^/]+)/([^/?#]+)", hf_url)
253
  if not m:
@@ -285,28 +365,15 @@ def process_url_for_preview(url):
285
  # Default handling
286
  return url, "iframe"
287
 
288
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 5. API ROUTES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
289
  @app.route('/api/category')
290
  def api_category():
291
  cat = request.args.get('name', '')
292
  urls = CATEGORIES.get(cat, [])
293
- return jsonify([
294
- {
295
- "title": url.split('/')[-1],
296
- "owner": url.split('/')[-2] if '/spaces/' in url else '',
297
- "iframe": direct_url(url),
298
- "shot": screenshot_url(url),
299
- "hf": url
300
- } for url in urls
301
- ])
302
-
303
- @app.route('/api/favorites')
304
- def api_favorites():
305
- # Load URLs from SQLite database
306
- urls = load_db()
307
 
 
308
  page = int(request.args.get('page', 1))
309
- per_page = int(request.args.get('per_page', 9))
310
 
311
  total_pages = max(1, (len(urls) + per_page - 1) // per_page)
312
  start = (page - 1) * per_page
@@ -314,109 +381,23 @@ def api_favorites():
314
 
315
  urls_page = urls[start:end]
316
 
317
- result = []
318
- for url in urls_page:
319
- try:
320
- preview_url, mode = process_url_for_preview(url)
321
- result.append({
322
- "title": url.split('/')[-1],
323
- "url": url,
324
- "preview_url": preview_url,
325
- "mode": mode
326
- })
327
- except Exception:
328
- # Fallback to screenshot mode
329
- result.append({
330
- "title": url.split('/')[-1],
331
- "url": url,
332
- "preview_url": screenshot_url(url),
333
- "mode": "snapshot"
334
- })
335
 
336
  return jsonify({
337
- "items": result,
338
  "page": page,
339
  "total_pages": total_pages
340
  })
341
 
342
- # `/api/url/add` ๊ฒฝ๋กœ ํ•จ์ˆ˜ ์ˆ˜์ •
343
- @app.route('/api/url/add', methods=['POST'])
344
- def add_url():
345
- url = request.form.get('url', '').strip()
346
- if not url:
347
- return jsonify({"success": False, "message": "URL is required"})
348
-
349
- # SQLite์— ์ถ”๊ฐ€ ์‹œ๋„
350
- conn = sqlite3.connect(SQLITE_DB)
351
- cursor = conn.cursor()
352
- try:
353
- cursor.execute("INSERT INTO urls (url) VALUES (?)", (url,))
354
- conn.commit()
355
- success = True
356
- except sqlite3.IntegrityError:
357
- # URL์ด ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ
358
- success = False
359
- except Exception as e:
360
- print(f"SQLite error: {str(e)}")
361
- success = False
362
- finally:
363
- conn.close()
364
-
365
- if not success:
366
- return jsonify({"success": False, "message": "URL already exists or could not be added"})
367
-
368
- # JSON ํŒŒ์ผ์—๋„ ์ถ”๊ฐ€ (๋ฐฑ์—…์šฉ)
369
- data = load_json()
370
- if url not in data:
371
- data.insert(0, url)
372
- save_json(data)
373
-
374
- return jsonify({"success": True, "message": "URL added successfully"})
375
-
376
- @app.route('/api/url/update', methods=['POST'])
377
- def update_url():
378
- old = request.form.get('old', '')
379
- new = request.form.get('new', '').strip()
380
-
381
- if not new:
382
- return jsonify({"success": False, "message": "New URL is required"})
383
-
384
- # Update in SQLite DB
385
- if not update_url_in_sqlite(old, new):
386
- return jsonify({"success": False, "message": "URL not found or new URL already exists"})
387
-
388
- # Also update JSON file for backward compatibility
389
- data = load_json()
390
- try:
391
- idx = data.index(old)
392
- data[idx] = new
393
- save_json(data)
394
- except ValueError:
395
- # If URL not in JSON, add it
396
- data.append(new)
397
- save_json(data)
398
-
399
- return jsonify({"success": True, "message": "URL updated successfully"})
400
-
401
- @app.route('/api/url/delete', methods=['POST'])
402
- def delete_url():
403
- url = request.form.get('url', '')
404
-
405
- # Delete from SQLite DB
406
- if not delete_url_from_sqlite(url):
407
- return jsonify({"success": False, "message": "URL not found"})
408
-
409
- # Also update JSON file for backward compatibility
410
- data = load_json()
411
- try:
412
- data.remove(url)
413
- save_json(data)
414
- except ValueError:
415
- pass
416
-
417
- return jsonify({"success": True, "message": "URL deleted successfully"})
418
-
419
- # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 6. MAIN ROUTES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
420
  @app.route('/')
421
  def home():
422
  os.makedirs('templates', exist_ok=True)
@@ -434,11 +415,17 @@ body{margin:0;font-family:Nunito,sans-serif;background:#f6f8fb;}
434
  .tabs{display:flex;flex-wrap:wrap;gap:8px;padding:16px;}
435
  .tab{padding:6px 14px;border:none;border-radius:18px;background:#e2e8f0;font-weight:600;cursor:pointer;}
436
  .tab.active{background:#a78bfa;color:#1a202c;}
437
- .tab.manage{background:#ff6e91;color:white;}
438
- .tab.manage.active{background:#ff2d62;color:white;}
439
- .grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;padding:0 16px 60px;}
 
 
 
 
 
440
  @media(max-width:800px){.grid{grid-template-columns:1fr;}}
441
- .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;}
 
442
  .frame{flex:1;position:relative;overflow:hidden;}
443
  .frame iframe{position:absolute;width:166.667%;height:166.667%;transform:scale(.6);transform-origin:top left;border:0;}
444
  .frame img{width:100%;height:100%;object-fit:cover;}
@@ -450,35 +437,16 @@ body{margin:0;font-family:Nunito,sans-serif;background:#f6f8fb;}
450
  .pagination{display:flex;justify-content:center;margin:20px 0;gap:10px;}
451
  .pagination button{padding:5px 15px;border:none;border-radius:20px;background:#e2e8f0;cursor:pointer;}
452
  .pagination button:disabled{opacity:0.5;cursor:not-allowed;}
453
- .manage-panel{background:white;border-radius:12px;box-shadow:0 2px 8px rgba(0,0,0,.08);margin:16px;padding:20px;}
454
- .form-group{margin-bottom:15px;}
455
- .form-group label{display:block;margin-bottom:5px;font-weight:600;}
456
- .form-control{width:100%;padding:8px;border:1px solid #ddd;border-radius:4px;box-sizing:border-box;}
457
- .btn{padding:8px 15px;border:none;border-radius:4px;cursor:pointer;font-weight:600;}
458
- .btn-primary{background:#4a6dd8;color:white;}
459
- .btn-danger{background:#e53e3e;color:white;}
460
- .btn-success{background:#38a169;color:white;}
461
- .status{padding:10px;margin:10px 0;border-radius:4px;display:none;}
462
- .status.success{display:block;background:#c6f6d5;color:#22543d;}
463
- .status.error{display:block;background:#fed7d7;color:#822727;}
464
- .url-list{margin:20px 0;border:1px solid #eee;border-radius:4px;max-height:300px;overflow-y:auto;}
465
- .url-item{padding:10px;border-bottom:1px solid #eee;display:flex;justify-content:space-between;align-items:center;}
466
- .url-item:last-child{border-bottom:none;}
467
- .url-controls{display:flex;gap:5px;}
468
  </style>
469
  </head>
470
  <body>
471
  <header style="text-align: center; padding: 20px; background: linear-gradient(135deg, #f6f8fb, #e2e8f0); border-bottom: 1px solid #ddd;">
472
- <h1 style="margin-bottom: 10px;">๐ŸŒŸ Web Gallery Manager</h1>
473
- <p class="description" style="margin-bottom: 15px;">
474
- ๐Ÿš€ <strong>Free AI Spaces & Website Gallery</strong> โœจ Save and manage your favorite sites! Supports <span style="background: linear-gradient(135deg, #00c6ff, #0072ff); color: white; padding: 2px 6px; border-radius: 4px; font-size: 12px;">LIVE</span> and <span style="background: linear-gradient(135deg, #ff9a9e, #fad0c4); color: #333; padding: 2px 6px; border-radius: 4px; font-size: 12px;">Static</span> preview modes.
475
- </p>
476
- <p style="font-size: 0.8rem; color: #888; margin-top: 10px;">
477
- Your desired URL will be permanently saved when recorded in the <code>favorite_sites.json</code> file.
478
- </p>
479
  <p>
 
480
  <a href="https://discord.gg/openfreeai" target="_blank"><img src="https://img.shields.io/static/v1?label=Discord&message=Openfree%20AI&color=%230000ff&labelColor=%23800080&logo=discord&logoColor=white&style=for-the-badge" alt="badge"></a>
481
- </p>
 
482
  </header>
483
  <div class="tabs" id="tabs"></div>
484
  <div id="content"></div>
@@ -492,17 +460,6 @@ let active = "";
492
  let currentPage = 1;
493
 
494
  // Simple utility functions
495
- function loadHTML(url, callback) {
496
- const xhr = new XMLHttpRequest();
497
- xhr.open('GET', url, true);
498
- xhr.onreadystatechange = function() {
499
- if (xhr.readyState === 4 && xhr.status === 200) {
500
- callback(xhr.responseText);
501
- }
502
- };
503
- xhr.send();
504
- }
505
-
506
  function makeRequest(url, method, data, callback) {
507
  const xhr = new XMLHttpRequest();
508
  xhr.open(method, url, true);
@@ -525,75 +482,32 @@ function updateTabs() {
525
  }
526
 
527
  // Tab handlers
528
- function loadCategory(cat) {
529
- if(cat === active) return;
530
  active = cat;
531
- updateTabs();
532
-
533
- content.innerHTML = '<p style="text-align:center;padding:40px">Loadingโ€ฆ</p>';
534
-
535
- makeRequest('/api/category?name=' + encodeURIComponent(cat), 'GET', null, function(data) {
536
- let html = '<div class="grid">';
537
-
538
- data.forEach(item => {
539
- html += `
540
- <div class="card">
541
- <div class="card-label label-live">LIVE</div>
542
- <div class="frame">
543
- <iframe src="${item.iframe}" loading="lazy" sandbox="allow-forms allow-modals allow-popups allow-same-origin allow-scripts allow-downloads"></iframe>
544
- </div>
545
- <div class="foot">
546
- <a href="${item.hf}" target="_blank">${item.title}</a>
547
- </div>
548
- </div>
549
- `;
550
- });
551
-
552
- html += '</div>';
553
- content.innerHTML = html;
554
- });
555
- }
556
-
557
- function loadFavorites(page) {
558
- if(active === 'Favorites' && currentPage === page) return;
559
- active = 'Favorites';
560
  currentPage = page || 1;
561
  updateTabs();
562
 
563
  content.innerHTML = '<p style="text-align:center;padding:40px">Loadingโ€ฆ</p>';
564
 
565
- makeRequest('/api/favorites?page=' + currentPage, 'GET', null, function(data) {
566
  let html = '<div class="grid">';
567
 
568
  if(data.items.length === 0) {
569
- html += '<p style="grid-column:1/-1;text-align:center;padding:40px">No favorites saved yet.</p>';
570
  } else {
571
  data.items.forEach(item => {
572
- if(item.mode === 'snapshot') {
573
- html += `
574
- <div class="card">
575
- <div class="card-label label-static">Static</div>
576
- <div class="frame">
577
- <img src="${item.preview_url}" loading="lazy">
578
- </div>
579
- <div class="foot">
580
- <a href="${item.url}" target="_blank">${item.title}</a>
581
- </div>
582
  </div>
583
- `;
584
- } else {
585
- html += `
586
- <div class="card">
587
- <div class="card-label label-live">LIVE</div>
588
- <div class="frame">
589
- <iframe src="${item.preview_url}" loading="lazy" sandbox="allow-forms allow-modals allow-popups allow-same-origin allow-scripts allow-downloads"></iframe>
590
- </div>
591
- <div class="foot">
592
- <a href="${item.url}" target="_blank">${item.title}</a>
593
- </div>
594
  </div>
595
- `;
596
- }
597
  });
598
  }
599
 
@@ -602,9 +516,9 @@ function loadFavorites(page) {
602
  // Add pagination
603
  html += `
604
  <div class="pagination">
605
- <button ${currentPage <= 1 ? 'disabled' : ''} onclick="loadFavorites(${currentPage-1})">ยซ Previous</button>
606
  <span>Page ${currentPage} of ${data.total_pages}</span>
607
- <button ${currentPage >= data.total_pages ? 'disabled' : ''} onclick="loadFavorites(${currentPage+1})">Next ยป</button>
608
  </div>
609
  `;
610
 
@@ -612,166 +526,31 @@ function loadFavorites(page) {
612
  });
613
  }
614
 
615
- function loadManage() {
616
- if(active === 'Manage') return;
617
- active = 'Manage';
618
- updateTabs();
619
-
620
- content.innerHTML = `
621
- <div class="manage-panel">
622
- <h2>Add New URL</h2>
623
- <div class="form-group">
624
- <label for="new-url">URL</label>
625
- <input type="text" id="new-url" class="form-control" placeholder="https://example.com">
626
- </div>
627
- <button onclick="addUrl()" class="btn btn-primary">Add URL</button>
628
- <div id="add-status" class="status"></div>
629
-
630
- <h2>Manage Saved URLs</h2>
631
- <div id="url-list" class="url-list">Loading...</div>
632
- </div>
633
- `;
634
-
635
- loadUrlList();
636
- }
637
-
638
- // URL management functions
639
- function loadUrlList() {
640
- makeRequest('/api/favorites?per_page=100', 'GET', null, function(data) {
641
- const urlList = document.getElementById('url-list');
642
-
643
- if(data.items.length === 0) {
644
- urlList.innerHTML = '<p style="text-align:center;padding:20px">No URLs saved yet.</p>';
645
- return;
646
- }
647
-
648
- let html = '';
649
- data.items.forEach(item => {
650
- // Escape the URL to prevent JavaScript injection when used in onclick handlers
651
- const escapedUrl = item.url.replace(/'/g, "\\'");
652
-
653
- html += `
654
- <div class="url-item">
655
- <div>${item.url}</div>
656
- <div class="url-controls">
657
- <button class="btn" onclick="editUrl('${escapedUrl}')">Edit</button>
658
- <button class="btn btn-danger" onclick="deleteUrl('${escapedUrl}')">Delete</button>
659
- </div>
660
- </div>
661
- `;
662
- });
663
-
664
- urlList.innerHTML = html;
665
- });
666
- }
667
-
668
- function addUrl() {
669
- const url = document.getElementById('new-url').value.trim();
670
-
671
- if(!url) {
672
- showStatus('add-status', 'Please enter a URL', false);
673
- return;
674
- }
675
-
676
- const formData = new FormData();
677
- formData.append('url', url);
678
-
679
- makeRequest('/api/url/add', 'POST', formData, function(data) {
680
- showStatus('add-status', data.message, data.success);
681
- if(data.success) {
682
- document.getElementById('new-url').value = '';
683
- loadUrlList();
684
- // If currently in Favorites tab, reload to see changes immediately
685
- if(active === 'Favorites') {
686
- loadFavorites(currentPage);
687
- }
688
- }
689
- });
690
- }
691
-
692
- function editUrl(url) {
693
- // Decode URL if it was previously escaped
694
- const decodedUrl = url.replace(/\\'/g, "'");
695
- const newUrl = prompt('Edit URL:', decodedUrl);
696
-
697
- if(!newUrl || newUrl === decodedUrl) return;
698
-
699
- const formData = new FormData();
700
- formData.append('old', decodedUrl);
701
- formData.append('new', newUrl);
702
-
703
- makeRequest('/api/url/update', 'POST', formData, function(data) {
704
- if(data.success) {
705
- loadUrlList();
706
- // If currently in Favorites tab, reload to see changes immediately
707
- if(active === 'Favorites') {
708
- loadFavorites(currentPage);
709
- }
710
- } else {
711
- alert(data.message);
712
- }
713
- });
714
- }
715
-
716
- function deleteUrl(url) {
717
- // Decode URL if it was previously escaped
718
- const decodedUrl = url.replace(/\\'/g, "'");
719
- if(!confirm('Are you sure you want to delete this URL?')) return;
720
-
721
- const formData = new FormData();
722
- formData.append('url', decodedUrl);
723
-
724
- makeRequest('/api/url/delete', 'POST', formData, function(data) {
725
- if(data.success) {
726
- loadUrlList();
727
- // If currently in Favorites tab, reload to see changes immediately
728
- if(active === 'Favorites') {
729
- loadFavorites(currentPage);
730
- }
731
- } else {
732
- alert(data.message);
733
- }
734
- });
735
- }
736
-
737
- function showStatus(id, message, success) {
738
- const status = document.getElementById(id);
739
- status.textContent = message;
740
- status.className = success ? 'status success' : 'status error';
741
- setTimeout(() => {
742
- status.className = 'status';
743
- }, 3000);
744
- }
745
-
746
  // Create tabs
747
- // Favorites tab first
748
- const favTab = document.createElement('button');
749
- favTab.className = 'tab';
750
- favTab.textContent = 'Favorites';
751
- favTab.dataset.c = 'Favorites';
752
- favTab.onclick = function() { loadFavorites(1); };
753
- tabs.appendChild(favTab);
754
-
755
- // Category tabs
756
- cats.forEach(c => {
757
  const b = document.createElement('button');
758
- b.className = 'tab';
759
- b.textContent = c;
760
- b.dataset.c = c;
761
- b.onclick = function() { loadCategory(c); };
762
  tabs.appendChild(b);
763
  });
764
 
765
- // Manage tab last
766
- const manageTab = document.createElement('button');
767
- manageTab.className = 'tab manage';
768
- manageTab.textContent = 'Manage';
769
- manageTab.dataset.c = 'Manage';
770
- manageTab.onclick = function() { loadManage(); };
771
- tabs.appendChild(manageTab);
 
 
 
 
772
 
773
- // Start with Favorites tab
774
- loadFavorites(1);
775
  </script>
776
  </body>
777
  </html>''')
@@ -779,34 +558,5 @@ loadFavorites(1);
779
  # Return the rendered template
780
  return render_template('index.html', cats=list(CATEGORIES.keys()))
781
 
782
- # Initialize database on startup
783
- init_db()
784
-
785
- # Define a function to ensure database consistency
786
- def ensure_db_consistency():
787
- # Make sure we have the latest data in both JSON and SQLite
788
- urls = load_db_sqlite()
789
- save_json(urls)
790
-
791
- # For Flask 2.0+ compatibility
792
- @app.before_request
793
- def before_request_func():
794
- # Use a flag to run this only once
795
- if not hasattr(app, '_got_first_request'):
796
- ensure_db_consistency()
797
- app._got_first_request = True
798
-
799
  if __name__ == '__main__':
800
- # ์•ฑ ์‹œ์ž‘ ์ „์— ๋ช…์‹œ์ ์œผ๋กœ DB ์ดˆ๊ธฐํ™”
801
- print("Initializing database...")
802
- init_db()
803
-
804
- # ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŒŒ์ผ ๊ฒฝ๋กœ ๋ฐ ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ
805
- db_path = os.path.abspath(SQLITE_DB)
806
- print(f"SQLite DB path: {db_path}")
807
- if os.path.exists(SQLITE_DB):
808
- print(f"Database file exists, size: {os.path.getsize(SQLITE_DB)} bytes")
809
- else:
810
- print("Warning: Database file does not exist after initialization!")
811
-
812
  app.run(host='0.0.0.0', port=7860)
 
1
  from flask import Flask, render_template, request, jsonify
2
+ import os, re, json
3
 
4
  app = Flask(__name__)
5
 
6
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 1. CONFIGURATION โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 
 
7
 
8
  # Domains that commonly block iframes
9
  BLOCKED_DOMAINS = [
 
14
 
15
  # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 2. CURATED CATEGORIES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
16
  CATEGORIES = {
17
+ "Popular": [
18
+ "https://huggingface.co/spaces/openfree/AGI-Screenplay",
19
+ "https://huggingface.co/spaces/openfree/AGI-WebNovel",
20
+ "https://huggingface.co/spaces/openfree/AGI-NOVEL",
21
+ "https://huggingface.co/spaces/fantaxy/AGI-LEADERBOARD",
22
+ "https://cutechicken-3d-airforce-simulator.static.hf.space",
23
+ "https://huggingface.co/spaces/ginipick/Private-AI",
24
+ "https://huggingface.co/spaces/fantaxy/ofai-flx-logo",
25
+ "https://huggingface.co/spaces/aiqtech/FLUX-Ghibli-Studio-LoRA",
26
+ "https://huggingface.co/spaces/seawolf2357/REALVISXL-V5",
27
+ "https://huggingface.co/spaces/fantos/flx8lora",
28
+ "https://huggingface.co/spaces/ginipick/Realtime-FLUX",
29
+ "https://huggingface.co/spaces/fantaxy/flx-pulid",
30
+ "https://huggingface.co/spaces/ginipick/FLUX-Prompt-Generator",
31
+ "https://huggingface.co/spaces/aiqtech/kofaceid",
32
+ "https://huggingface.co/spaces/aiqtech/flxgif",
33
+ "https://huggingface.co/spaces/fantos/flxfashmodel",
34
+ "https://huggingface.co/spaces/fantos/flxcontrol",
35
+ "https://huggingface.co/spaces/fantos/textcutobject",
36
+ "https://huggingface.co/spaces/seawolf2357/flxloraexp",
37
+ "https://huggingface.co/spaces/fantaxy/flxloraexp",
38
+ "https://huggingface.co/spaces/aiqtech/imaginpaint",
39
+ "https://huggingface.co/spaces/ginipick/FLUXllama",
40
+ "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored",
41
+ "https://huggingface.co/spaces/fantaxy/flx-upscale",
42
+ "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-video",
43
+ "https://huggingface.co/spaces/fantos/VoiceClone",
44
+ "https://huggingface.co/spaces/fantaxy/Rolls-Royce",
45
+ "https://huggingface.co/spaces/aiqtech/FLUX-military",
46
+ "https://huggingface.co/spaces/fantaxy/FLUX-Animations",
47
+ "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-video2",
48
+ "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-photo",
49
+ "https://huggingface.co/spaces/ginipick/Time-Stream",
50
+ "https://huggingface.co/spaces/seawolf2357/sd-prompt-gen",
51
+ "https://huggingface.co/spaces/openfree/MagicFace-V3",
52
+ "https://huggingface.co/spaces/Heartsync/adult",
53
+ "https://huggingface.co/spaces/Heartsync/wan2-1-fast-security",
54
+ "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-REAL",
55
+ "https://huggingface.co/spaces/seawolf2357/img2vid",
56
+ "https://huggingface.co/spaces/openfree/image-to-vector",
57
+ "https://huggingface.co/spaces/openfree/DreamO-video",
58
+ "https://huggingface.co/spaces/VIDraft/FramePack_rotate_landscape",
59
+ "https://huggingface.co/spaces/fantaxy/Sound-AI-SFX",
60
+ "https://huggingface.co/spaces/ginigen/VoiceClone-TTS",
61
+ "https://huggingface.co/spaces/Heartsync/FREE-NSFW-HUB",
62
+ "https://huggingface.co/spaces/Heartsync/NSFW-image",
63
+ "https://huggingface.co/spaces/Heartsync/NSFW-detection",
64
+ "https://huggingface.co/spaces/Heartsync/VEO3-RealTime",
65
+ "https://huggingface.co/spaces/ginigen/VEO3-Free",
66
+ "https://huggingface.co/spaces/ginigen/FLUX-Text-Tree-Image",
67
+ "https://huggingface.co/spaces/ginigen/text3d-r1",
68
+ "https://huggingface.co/spaces/VIDraft/stable-diffusion-3.5-large-turboX",
69
+
70
+ ],
71
+ "BEST": [
72
+ "https://huggingface.co/spaces/MaziyarPanahi/FACTS-Leaderboard",
73
+ "https://huggingface.co/spaces/ginigen/Flux-Kontext-Style",
74
+ "https://huggingface.co/spaces/openfree/Cycle-Navigator",
75
+ "https://huggingface.co/spaces/ginigen/Flux-Kontext-FaceLORA",
76
+ "https://huggingface.co/spaces/ginigen/Seedance-Free",
77
+ "https://huggingface.co/spaces/VIDraft/SOMA-AGI",
78
+ "https://huggingface.co/spaces/aiqtech/Heatmap-Leaderboard",
79
+ "https://huggingface.co/spaces/VIDraft/DNA-CASINO",
80
+ "https://huggingface.co/spaces/aiqtech/SOMA-Oriental",
81
+ "https://huggingface.co/spaces/fantaxy/YTB-TEST",
82
+ "https://huggingface.co/spaces/aiqtech/Contributors-Leaderboard",
83
+ "https://huggingface.co/spaces/ginigen/text3d-r1",
84
+ "https://huggingface.co/spaces/VIDraft/stable-diffusion-3.5-large-turboX",
85
+ "https://huggingface.co/spaces/openfree/Korean-Leaderboard",
86
+ "https://huggingface.co/spaces/fantos/flxcontrol",
87
+ "https://huggingface.co/spaces/aiqtech/FLUX-Ghibli-Studio-LoRA",
88
+ "https://huggingface.co/spaces/openfree/AI-Podcast",
89
+ "https://huggingface.co/spaces/ginigen/Workflow-Canvas",
90
+ "https://huggingface.co/spaces/ginigen/3D-LLAMA",
91
+ "https://huggingface.co/spaces/ginigen/VoiceClone-TTS",
92
+ "https://huggingface.co/spaces/VIDraft/ACE-Singer",
93
+ "https://huggingface.co/spaces/ginipick/AI-BOOK",
94
+ "https://huggingface.co/spaces/immunobiotech/drug-discovery",
95
+ "https://huggingface.co/spaces/VIDraft/Robo-Beam",
96
+ "https://huggingface.co/spaces/fantaxy/fantasy-novel",
97
+ "https://huggingface.co/spaces/immunobiotech/Gemini-MICHELIN",
98
+ "https://huggingface.co/spaces/openfree/Chart-GPT",
99
+ "https://huggingface.co/spaces/ginipick/NH-Korea",
100
+ "https://huggingface.co/spaces/VIDraft/Voice-Clone-Podcast",
101
+ "https://huggingface.co/spaces/ginipick/Private-AI",
102
+ "https://huggingface.co/spaces/ginigen/Flux-VIDEO",
103
+ "https://huggingface.co/spaces/openfree/open-GAMMA",
104
+ "https://huggingface.co/spaces/ginipick/PharmAI-Korea",
105
+ "https://huggingface.co/spaces/ginipick/Pharmacy",
106
+ "https://huggingface.co/spaces/ginipick/PDF-EXAM",
107
+ "https://huggingface.co/spaces/ginipick/IDEA-DESIGN",
108
+ "https://huggingface.co/spaces/openfree/DreamO-video",
109
+ "https://huggingface.co/spaces/ginipick/10m-marketing",
110
+ "https://huggingface.co/spaces/VIDraft/voice-trans",
111
+ "https://huggingface.co/spaces/VIDraft/NH-Prediction",
112
+ "https://huggingface.co/spaces/fantos/flx8lora",
113
+ "https://huggingface.co/spaces/ginigen/MagicFace-V3",
114
+ "https://huggingface.co/spaces/openfree/Live-Podcast",
115
+ "https://huggingface.co/spaces/seawolf2357/ocrlatex",
116
+ "https://huggingface.co/spaces/Heartsync/VEO3-RealTime",
117
+ "https://huggingface.co/spaces/ginigen/VEO3-Free",
118
+ "https://huggingface.co/spaces/openfree/MagicFace-V3",
119
+ "https://huggingface.co/spaces/aiqtech/FLUX-military",
120
+ "https://huggingface.co/spaces/fantaxy/flxloraexp",
121
+ "https://huggingface.co/spaces/Heartsync/WAN2-1-fast-T2V-FusioniX",
122
+ "https://huggingface.co/spaces/ginigen/FLUXllama-Multilingual",
123
+ "https://huggingface.co/spaces/Heartsync/wan2-1-fast-security",
124
+ "https://huggingface.co/spaces/fantaxy/Rolls-Royce",
125
+ "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-REAL",
126
+ "https://huggingface.co/spaces/ginipick/Realtime-FLUX",
127
+ "https://huggingface.co/spaces/aiqtech/imaginpaint",
128
+ "https://huggingface.co/spaces/aiqtech/flxgif",
129
+ "https://huggingface.co/spaces/fantos/flxfashmodel",
130
+ "https://huggingface.co/spaces/aiqtech/kofaceid",
131
+ "https://huggingface.co/spaces/ginipick/FLUX-Prompt-Generator",
132
+ "https://huggingface.co/spaces/seawolf2357/REALVISXL-V5",
133
+ "https://huggingface.co/spaces/fantaxy/FLUX-Animations",
134
+ "https://huggingface.co/spaces/fantaxy/flx-pulid",
135
+ "https://huggingface.co/spaces/fantaxy/ofai-flx-logo",
136
+ "https://huggingface.co/spaces/openfree/image-to-vector",
137
+ "https://huggingface.co/spaces/Heartsync/FREE-NSFW-HUB",
138
+ "https://huggingface.co/spaces/seawolf2357/sd-prompt-gen",
139
+ "https://huggingface.co/spaces/VIDraft/FramePack_rotate_landscape",
140
+ "https://huggingface.co/spaces/ginipick/FLUXllama",
141
+ "https://huggingface.co/spaces/Heartsync/NSFW-image",
142
+ "https://huggingface.co/spaces/seawolf2357/img2vid",
143
+ "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-video2",
144
+ "https://huggingface.co/spaces/Heartsync/NSFW-detection",
145
+ "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-video",
146
+ "https://huggingface.co/spaces/Heartsync/adult",
147
+ "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored",
148
+ "https://huggingface.co/spaces/fantos/VoiceClone",
149
+ "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-photo",
150
+ "https://huggingface.co/spaces/fantaxy/flx-upscale",
151
+ "https://huggingface.co/spaces/seawolf2357/flxloraexp",
152
+ "https://huggingface.co/spaces/ginipick/Time-Stream",
153
+ "https://huggingface.co/spaces/fantos/textcutobject",
154
+
155
+
156
+ ],
157
+ "NEW": [
158
+ "https://huggingface.co/spaces/ginigen/Flux-Kontext-Style",
159
+ "https://cutechicken-3d-airforce-simulator.static.hf.space",
160
+ "https://huggingface.co/spaces/ginipick/Private-AI",
161
+ "https://huggingface.co/spaces/VIDraft/ACE-Singer",
162
+ "https://huggingface.co/spaces/ginipick/AI-BOOK",
163
+ "https://huggingface.co/spaces/openfree/Best-AI",
164
+ "https://huggingface.co/spaces/aiqtech/Heatmap-Leaderboard",
165
+ "https://huggingface.co/spaces/VIDraft/DNA-CASINO",
166
+ "https://huggingface.co/spaces/openfree/AGI-Screenplay",
167
+ "https://huggingface.co/spaces/openfree/AGI-WebNovel",
168
+ "https://huggingface.co/spaces/openfree/AGI-NOVEL",
169
+ "https://huggingface.co/spaces/fantaxy/AGI-LEADERBOARD",
170
+ "https://huggingface.co/spaces/ginigen/Seedance-Free",
171
+ "https://huggingface.co/spaces/aiqtech/SOMA-Oriental",
172
+ "https://huggingface.co/spaces/ginigen/Flux-Kontext-FaceLORA",
173
+ "https://huggingface.co/spaces/VIDraft/SOMA-AGI",
174
+ "https://huggingface.co/spaces/Heartsync/VEO3-RealTime",
175
+ "https://huggingface.co/spaces/openfree/Open-GAMMA",
176
+ "https://huggingface.co/spaces/ginigen/VEO3-Free",
177
+ "https://huggingface.co/spaces/Heartsync/WAN2-1-fast-T2V-FusioniX",
178
+ "https://huggingface.co/spaces/VIDraft/voice-trans",
179
+ "https://huggingface.co/spaces/VIDraft/Robo-Beam",
180
+ "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-REAL",
181
+ "https://huggingface.co/spaces/fantaxy/fantasy-novel",
182
+ "https://huggingface.co/spaces/openfree/Chart-GPT",
183
+ "https://huggingface.co/spaces/Heartsync/Novel-NSFW",
184
+ "https://huggingface.co/spaces/ginigen/FLUX-Ghibli-LoRA2",
185
+ "https://huggingface.co/spaces/Heartsync/WAN-VIDEO-AUDIO",
186
+ "https://huggingface.co/spaces/Heartsync/wan2-1-fast-security",
187
+ "https://huggingface.co/spaces/ginigen/Flux-VIDEO",
188
+ "https://huggingface.co/spaces/aiqcamp/REMOVAL-TEXT-IMAGE",
189
+ "https://huggingface.co/spaces/VIDraft/Mistral-RAG-BitSix",
190
+ "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-video2",
191
+ "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-video",
192
+ "https://huggingface.co/spaces/fantaxy/YTB-TEST",
193
+ "https://huggingface.co/spaces/Heartsync/FREE-NSFW-HUB",
194
+ "https://huggingface.co/spaces/Heartsync/adult",
195
+ "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-photo",
196
+ "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored",
197
+ "https://huggingface.co/spaces/openfree/Live-Podcast",
198
+ "https://huggingface.co/spaces/openfree/AI-Podcast",
199
+ "https://huggingface.co/spaces/ginipick/NH-Korea",
200
+ "https://huggingface.co/spaces/VIDraft/NH-Prediction",
201
+ "https://huggingface.co/spaces/VIDraft/Voice-Clone-Podcast",
202
+ "https://huggingface.co/spaces/ginipick/PDF-EXAM",
203
+ "https://huggingface.co/spaces/openfree/Game-Gallery",
204
+ "https://huggingface.co/spaces/openfree/Vibe-Game",
205
+ "https://huggingface.co/spaces/ginipick/IDEA-DESIGN",
206
+ "https://huggingface.co/spaces/openfree/Cycle-Navigator",
207
+ "https://huggingface.co/spaces/openfree/DreamO-video",
208
+ "https://huggingface.co/spaces/Heartsync/NSFW-detection",
209
+
210
+
211
+ ],
212
  "Productivity": [
213
+ "https://huggingface.co/spaces/aiqtech/Heatmap-Leaderboard",
214
+ "https://huggingface.co/spaces/VIDraft/DNA-CASINO",
215
+ "https://huggingface.co/spaces/openfree/Open-GAMMA",
216
+ "https://huggingface.co/spaces/VIDraft/Robo-Beam",
217
+ "https://huggingface.co/spaces/VIDraft/voice-trans",
218
+ "https://huggingface.co/spaces/Heartsync/FREE-NSFW-HUB",
219
  "https://huggingface.co/spaces/openfree/Chart-GPT",
220
+ "https://huggingface.co/spaces/ginipick/AI-BOOK",
221
  "https://huggingface.co/spaces/VIDraft/Voice-Clone-Podcast",
222
  "https://huggingface.co/spaces/ginipick/PDF-EXAM",
 
223
  "https://huggingface.co/spaces/ginigen/perflexity-clone",
224
  "https://huggingface.co/spaces/ginipick/IDEA-DESIGN",
225
  "https://huggingface.co/spaces/ginipick/10m-marketing",
 
226
  "https://huggingface.co/spaces/openfree/Live-Podcast",
227
  "https://huggingface.co/spaces/openfree/AI-Podcast",
228
  "https://huggingface.co/spaces/ginipick/QR-Canvas-plus",
 
231
  "https://huggingface.co/spaces/openfree/Vibe-Game",
232
  "https://huggingface.co/spaces/VIDraft/NH-Prediction",
233
  "https://huggingface.co/spaces/ginipick/NH-Korea",
234
+ "https://huggingface.co/spaces/openfree/Naming",
235
+ "https://huggingface.co/spaces/ginipick/Change-Hair",
236
  ],
237
  "Multimodal": [
238
+ "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-photo",
239
+ "https://huggingface.co/spaces/fantaxy/YTB-TEST",
240
+ "https://huggingface.co/spaces/ginigen/Seedance-Free",
241
+ "https://huggingface.co/spaces/Heartsync/VEO3-RealTime",
242
+ "https://huggingface.co/spaces/ginigen/VEO3-Free",
243
+ "https://huggingface.co/spaces/ginigen/VEO3-Directors",
244
+ "https://huggingface.co/spaces/Heartsync/WAN2-1-fast-T2V-FusioniX",
245
  "https://huggingface.co/spaces/Heartsync/adult",
246
  "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored",
247
  "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-video2",
 
261
  "https://huggingface.co/spaces/fantaxy/Remove-Video-Background",
262
  ],
263
  "Professional": [
264
+ "https://huggingface.co/spaces/Heartsync/NSFW-novels",
265
+ "https://huggingface.co/spaces/aiqtech/SOMA-Oriental",
266
+ "https://huggingface.co/spaces/VIDraft/SOMA-AGI",
267
  "https://huggingface.co/spaces/Heartsync/Novel-NSFW",
268
  "https://huggingface.co/spaces/fantaxy/fantasy-novel",
269
  "https://huggingface.co/spaces/VIDraft/money-radar",
 
278
  "https://huggingface.co/spaces/ginigen/multimodal-chat-mbti-korea",
279
  ],
280
  "Image": [
281
+ "https://huggingface.co/spaces/ginigen/Flux-Kontext-FaceLORA",
282
+ "https://huggingface.co/spaces/Heartsync/NSFW-Uncensored-REAL",
283
  "https://huggingface.co/spaces/ginigen/FLUX-Ghibli-LoRA2",
284
  "https://huggingface.co/spaces/aiqcamp/REMOVAL-TEXT-IMAGE",
285
  "https://huggingface.co/spaces/VIDraft/BAGEL-Websearch",
 
305
  "https://huggingface.co/spaces/VIDraft/stable-diffusion-3.5-large-turboX",
306
  "https://huggingface.co/spaces/aiqtech/flxgif",
307
  "https://huggingface.co/spaces/openfree/VectorFlow",
308
+ "https://huggingface.co/spaces/ginigen/3D-LLAMA",
309
+ "https://huggingface.co/spaces/ginigen/Multi-LoRAgen",
 
 
310
  ],
311
  "LLM / VLM": [
312
+ "https://huggingface.co/spaces/fantaxy/fantasy-novel",
313
  "https://huggingface.co/spaces/ginigen/deepseek-r1-0528-API",
314
+ "https://huggingface.co/spaces/aiqcamp/Mistral-Devstral-API",
315
  "https://huggingface.co/spaces/aiqcamp/deepseek-r1-0528",
316
  "https://huggingface.co/spaces/aiqcamp/deepseek-r1-0528-qwen3-8b",
317
  "https://huggingface.co/spaces/aiqcamp/deepseek-r1-0528",
 
327
  ],
328
  }
329
 
330
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 3. URL HELPERS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
  def direct_url(hf_url):
332
  m = re.match(r"https?://huggingface\.co/spaces/([^/]+)/([^/?#]+)", hf_url)
333
  if not m:
 
365
  # Default handling
366
  return url, "iframe"
367
 
368
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 4. API ROUTES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
369
  @app.route('/api/category')
370
  def api_category():
371
  cat = request.args.get('name', '')
372
  urls = CATEGORIES.get(cat, [])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
 
374
+ # Add pagination for categories
375
  page = int(request.args.get('page', 1))
376
+ per_page = int(request.args.get('per_page', 4))
377
 
378
  total_pages = max(1, (len(urls) + per_page - 1) // per_page)
379
  start = (page - 1) * per_page
 
381
 
382
  urls_page = urls[start:end]
383
 
384
+ items = [
385
+ {
386
+ "title": url.split('/')[-1],
387
+ "owner": url.split('/')[-2] if '/spaces/' in url else '',
388
+ "iframe": direct_url(url),
389
+ "shot": screenshot_url(url),
390
+ "hf": url
391
+ } for url in urls_page
392
+ ]
 
 
 
 
 
 
 
 
 
393
 
394
  return jsonify({
395
+ "items": items,
396
  "page": page,
397
  "total_pages": total_pages
398
  })
399
 
400
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 5. MAIN ROUTES โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
401
  @app.route('/')
402
  def home():
403
  os.makedirs('templates', exist_ok=True)
 
415
  .tabs{display:flex;flex-wrap:wrap;gap:8px;padding:16px;}
416
  .tab{padding:6px 14px;border:none;border-radius:18px;background:#e2e8f0;font-weight:600;cursor:pointer;}
417
  .tab.active{background:#a78bfa;color:#1a202c;}
418
+ .tab.popular{background:#ff6b6b;color:white;}
419
+ .tab.popular.active{background:#fa5252;color:white;}
420
+ .tab.best{background:#4ecdc4;color:white;}
421
+ .tab.best.active{background:#38d9a9;color:white;}
422
+ .tab.new{background:#ffe066;color:#1a202c;}
423
+ .tab.new.active{background:#ffd43b;color:#1a202c;}
424
+ /* Updated grid to show 2x2 layout */
425
+ .grid{display:grid;grid-template-columns:repeat(2,1fr);gap:20px;padding:0 16px 60px;max-width:1200px;margin:0 auto;}
426
  @media(max-width:800px){.grid{grid-template-columns:1fr;}}
427
+ /* Increased card height for larger display */
428
+ .card{background:#fff;border-radius:12px;box-shadow:0 2px 8px rgba(0,0,0,.08);overflow:hidden;height:540px;display:flex;flex-direction:column;position:relative;}
429
  .frame{flex:1;position:relative;overflow:hidden;}
430
  .frame iframe{position:absolute;width:166.667%;height:166.667%;transform:scale(.6);transform-origin:top left;border:0;}
431
  .frame img{width:100%;height:100%;object-fit:cover;}
 
437
  .pagination{display:flex;justify-content:center;margin:20px 0;gap:10px;}
438
  .pagination button{padding:5px 15px;border:none;border-radius:20px;background:#e2e8f0;cursor:pointer;}
439
  .pagination button:disabled{opacity:0.5;cursor:not-allowed;}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
440
  </style>
441
  </head>
442
  <body>
443
  <header style="text-align: center; padding: 20px; background: linear-gradient(135deg, #f6f8fb, #e2e8f0); border-bottom: 1px solid #ddd;">
444
+ <h1 style="margin-bottom: 10px;">๐ŸŒŸOPEN & Free: BEST AI Playground</h1>
 
 
 
 
 
 
445
  <p>
446
+ <a href="https://huggingface.co/OpenFreeAI" target="_blank"><img src="https://img.shields.io/static/v1?label=Community&message=OpenFree_AI&color=%23800080&labelColor=%23000080&logo=HUGGINGFACE&logoColor=%23ffa500&style=for-the-badge" alt="badge"></a>
447
  <a href="https://discord.gg/openfreeai" target="_blank"><img src="https://img.shields.io/static/v1?label=Discord&message=Openfree%20AI&color=%230000ff&labelColor=%23800080&logo=discord&logoColor=white&style=for-the-badge" alt="badge"></a>
448
+ <a href="https://huggingface.co/spaces/openfree/Best-AI" target="_blank"><img src="https://img.shields.io/static/v1?label=OpenFree&message=BEST%20AI%20Services&color=%230000ff&labelColor=%23000080&logo=huggingface&logoColor=%23ffa500&style=for-the-badge" alt="badge"></a>
449
+ </p>
450
  </header>
451
  <div class="tabs" id="tabs"></div>
452
  <div id="content"></div>
 
460
  let currentPage = 1;
461
 
462
  // Simple utility functions
 
 
 
 
 
 
 
 
 
 
 
463
  function makeRequest(url, method, data, callback) {
464
  const xhr = new XMLHttpRequest();
465
  xhr.open(method, url, true);
 
482
  }
483
 
484
  // Tab handlers
485
+ function loadCategory(cat, page) {
486
+ if(cat === active && currentPage === page) return;
487
  active = cat;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
488
  currentPage = page || 1;
489
  updateTabs();
490
 
491
  content.innerHTML = '<p style="text-align:center;padding:40px">Loadingโ€ฆ</p>';
492
 
493
+ makeRequest('/api/category?name=' + encodeURIComponent(cat) + '&page=' + currentPage + '&per_page=4', 'GET', null, function(data) {
494
  let html = '<div class="grid">';
495
 
496
  if(data.items.length === 0) {
497
+ html += '<p style="grid-column:1/-1;text-align:center;padding:40px">No items in this category.</p>';
498
  } else {
499
  data.items.forEach(item => {
500
+ html += `
501
+ <div class="card">
502
+ <div class="card-label label-live">LIVE</div>
503
+ <div class="frame">
504
+ <iframe src="${item.iframe}" loading="lazy" sandbox="allow-forms allow-modals allow-popups allow-same-origin allow-scripts allow-downloads"></iframe>
 
 
 
 
 
505
  </div>
506
+ <div class="foot">
507
+ <a href="${item.hf}" target="_blank">${item.title}</a>
 
 
 
 
 
 
 
 
 
508
  </div>
509
+ </div>
510
+ `;
511
  });
512
  }
513
 
 
516
  // Add pagination
517
  html += `
518
  <div class="pagination">
519
+ <button ${currentPage <= 1 ? 'disabled' : ''} onclick="loadCategory('${cat}', ${currentPage-1})">ยซ Previous</button>
520
  <span>Page ${currentPage} of ${data.total_pages}</span>
521
+ <button ${currentPage >= data.total_pages ? 'disabled' : ''} onclick="loadCategory('${cat}', ${currentPage+1})">Next ยป</button>
522
  </div>
523
  `;
524
 
 
526
  });
527
  }
528
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
529
  // Create tabs
530
+ // Special tabs first (Popular, BEST, NEW)
531
+ ['Popular', 'BEST', 'NEW'].forEach(specialCat => {
 
 
 
 
 
 
 
 
532
  const b = document.createElement('button');
533
+ b.className = 'tab ' + specialCat.toLowerCase();
534
+ b.textContent = specialCat;
535
+ b.dataset.c = specialCat;
536
+ b.onclick = function() { loadCategory(specialCat, 1); };
537
  tabs.appendChild(b);
538
  });
539
 
540
+ // Regular category tabs
541
+ cats.forEach(c => {
542
+ if (!['Popular', 'BEST', 'NEW'].includes(c)) {
543
+ const b = document.createElement('button');
544
+ b.className = 'tab';
545
+ b.textContent = c;
546
+ b.dataset.c = c;
547
+ b.onclick = function() { loadCategory(c, 1); };
548
+ tabs.appendChild(b);
549
+ }
550
+ });
551
 
552
+ // Start with Popular tab
553
+ loadCategory('Popular', 1);
554
  </script>
555
  </body>
556
  </html>''')
 
558
  # Return the rendered template
559
  return render_template('index.html', cats=list(CATEGORIES.keys()))
560
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
561
  if __name__ == '__main__':
 
 
 
 
 
 
 
 
 
 
 
 
562
  app.run(host='0.0.0.0', port=7860)