seawolf2357 commited on
Commit
fbf1f3d
ยท
verified ยท
1 Parent(s): 03dcabd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +150 -107
app.py CHANGED
@@ -1,108 +1,101 @@
1
  from flask import Flask, render_template, request, jsonify
2
  import requests
3
  import os
4
- import random
 
5
 
6
  app = Flask(__name__)
7
 
8
- # Huggingface URL list
9
- HUGGINGFACE_URLS = [
10
- "https://huggingface.co/spaces/ginipick/Tech_Hangman_Game",
11
- "https://huggingface.co/spaces/openfree/deepseek_r1_API",
12
- "https://huggingface.co/spaces/ginipick/open_Deep-Research",
13
- "https://huggingface.co/spaces/aiqmaster/open-deep-research",
14
- "https://huggingface.co/spaces/seawolf2357/DeepSeek-R1-32b-search",
15
- "https://huggingface.co/spaces/ginigen/LLaDA",
16
- "https://huggingface.co/spaces/VIDraft/PHI4-Multimodal",
17
- "https://huggingface.co/spaces/ginigen/Ovis2-8B",
18
- "https://huggingface.co/spaces/ginigen/Graph-Mind",
19
- "https://huggingface.co/spaces/ginigen/Workflow-Canvas",
20
- "https://huggingface.co/spaces/ginigen/Design",
21
- "https://huggingface.co/spaces/ginigen/Diagram",
22
- "https://huggingface.co/spaces/ginigen/Mockup",
23
- "https://huggingface.co/spaces/ginigen/Infographic",
24
- "https://huggingface.co/spaces/ginigen/Flowchart",
25
- "https://huggingface.co/spaces/aiqcamp/FLUX-Vision",
26
- "https://huggingface.co/spaces/ginigen/VoiceClone-TTS",
27
- "https://huggingface.co/spaces/openfree/Perceptron-Network",
28
- "https://huggingface.co/spaces/openfree/Article-Generator",
29
- ]
30
 
31
  # Transform Huggingface URL to direct space URL
32
- def transform_url(url):
33
- prefix = "https://huggingface.co/spaces/"
34
- if url.startswith(prefix):
35
- rest = url[len(prefix):]
36
- return f"https://{rest.replace('/', '-')}.hf.space"
37
- return url
38
 
39
- # Extract model/space info from URL
40
- def extract_model_info(url):
41
- parts = url.split('/')
42
- if len(parts) < 6:
43
- return None
44
-
45
- if parts[3] == 'spaces' or parts[3] == 'models':
46
- return {
47
- 'type': parts[3],
48
- 'owner': parts[4],
49
- 'repo': parts[5],
50
- 'full_id': f"{parts[4]}/{parts[5]}"
51
- }
52
- elif len(parts) >= 5:
53
- # Other URL format
 
 
 
54
  return {
55
- 'type': 'models', # Default
56
- 'owner': parts[3],
57
- 'repo': parts[4],
58
- 'full_id': f"{parts[3]}/{parts[4]}"
 
 
59
  }
60
-
61
- return None
62
-
63
- # Extract title from the last part of URL
64
- def extract_title(url):
65
- parts = url.split("/")
66
- title = parts[-1] if parts else ""
67
- return title.replace("_", " ").replace("-", " ")
68
-
69
- # Generate random likes count (since we're removing the actual likes functionality)
70
- def generate_likes_count():
71
- return random.randint(10, 500)
72
 
73
  # Homepage route
74
  @app.route('/')
75
  def home():
76
  return render_template('index.html')
77
 
78
- # URL list API
79
- @app.route('/api/urls', methods=['GET'])
80
- def get_urls():
81
  search_query = request.args.get('search', '').lower()
 
 
 
 
82
 
 
83
  results = []
84
- for url in HUGGINGFACE_URLS:
85
- title = extract_title(url)
86
- model_info = extract_model_info(url)
87
- transformed_url = transform_url(url)
88
 
89
- if not model_info:
90
  continue
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
- if search_query and search_query not in url.lower() and search_query not in title.lower():
93
- continue
94
-
95
- # Generate random likes count
96
- likes_count = generate_likes_count()
97
-
98
- results.append({
99
- 'url': url,
100
- 'embedUrl': transformed_url,
101
- 'title': title,
102
- 'model_info': model_info,
103
- 'likes_count': likes_count,
104
- 'owner': model_info['owner'] # Include owner ID
105
- })
106
 
107
  return jsonify(results)
108
 
@@ -118,7 +111,7 @@ if __name__ == '__main__':
118
  <head>
119
  <meta charset="UTF-8">
120
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
121
- <title>ํ—ˆ๊น…ํŽ˜์ด์Šค ์ŠคํŽ˜์ด์Šค ๊ทธ๋ฆฌ๋“œ</title>
122
  <style>
123
  body {
124
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
@@ -159,17 +152,54 @@ if __name__ == '__main__':
159
  display: flex;
160
  justify-content: center;
161
  align-items: center;
 
162
  }
163
 
164
  input[type="text"] {
165
  padding: 0.7rem;
166
  border: 1px solid #ddd;
167
  border-radius: 4px;
168
- margin-right: 5px;
169
  font-size: 1rem;
170
  width: 300px;
171
  }
172
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  .grid-container {
174
  display: grid;
175
  grid-template-columns: repeat(auto-fill, minmax(450px, 1fr));
@@ -200,6 +230,7 @@ if __name__ == '__main__':
200
  .grid-header-left {
201
  display: flex;
202
  flex-direction: column;
 
203
  }
204
 
205
  .grid-header h3 {
@@ -263,7 +294,7 @@ if __name__ == '__main__':
263
  right: 0;
264
  bottom: 0;
265
  background-color: rgba(255, 255, 255, 0.8);
266
- display: none;
267
  justify-content: center;
268
  align-items: center;
269
  z-index: 1000;
@@ -279,11 +310,6 @@ if __name__ == '__main__':
279
  animation: spin 1s linear infinite;
280
  }
281
 
282
- @keyframes spin {
283
- 0% { transform: rotate(0deg); }
284
- 100% { transform: rotate(360deg); }
285
- }
286
-
287
  @media (max-width: 768px) {
288
  .filter-controls {
289
  flex-direction: column;
@@ -308,11 +334,15 @@ if __name__ == '__main__':
308
  <body>
309
  <div class="container">
310
  <div class="header">
311
- <h1>ํ—ˆ๊น…ํŽ˜์ด์Šค ์ŠคํŽ˜์ด์Šค ์ž„๋ฒ ๋”ฉ ๋ทฐ์–ด</h1>
312
  </div>
313
 
314
  <div class="filter-controls">
315
- <input type="text" id="searchInput" placeholder="URL ๋˜๋Š” ์ œ๋ชฉ์œผ๋กœ ๊ฒ€์ƒ‰" />
 
 
 
 
316
  </div>
317
 
318
  <div id="gridContainer" class="grid-container"></div>
@@ -327,18 +357,26 @@ if __name__ == '__main__':
327
  const elements = {
328
  gridContainer: document.getElementById('gridContainer'),
329
  loadingIndicator: document.getElementById('loadingIndicator'),
330
- searchInput: document.getElementById('searchInput')
 
331
  };
332
 
333
  // Application state
334
  const state = {
335
- isLoading: false
 
336
  };
337
 
338
  // Display loading indicator
339
  function setLoading(isLoading) {
340
  state.isLoading = isLoading;
341
  elements.loadingIndicator.style.display = isLoading ? 'flex' : 'none';
 
 
 
 
 
 
342
  }
343
 
344
  // API error handling
@@ -350,39 +388,40 @@ if __name__ == '__main__':
350
  return response.json();
351
  }
352
 
353
- // Load URL list
354
- async function loadUrls() {
355
  setLoading(true);
356
 
357
  try {
358
  const searchText = elements.searchInput.value;
359
 
360
- const response = await fetch(`/api/urls?search=${encodeURIComponent(searchText)}`);
361
- const urls = await handleApiResponse(response);
362
 
363
- renderGrid(urls);
 
364
  } catch (error) {
365
- console.error('URL ๋ชฉ๋ก ๋กœ๋“œ ์˜ค๋ฅ˜:', error);
366
- alert(`URL ๋กœ๋“œ ์˜ค๋ฅ˜: ${error.message}`);
367
  } finally {
368
  setLoading(false);
369
  }
370
  }
371
 
372
  // Render grid
373
- function renderGrid(urls) {
374
  elements.gridContainer.innerHTML = '';
375
 
376
- if (!urls || urls.length === 0) {
377
  const noResultsMsg = document.createElement('p');
378
- noResultsMsg.textContent = 'ํ‘œ์‹œํ•  URL์ด ์—†์Šต๋‹ˆ๋‹ค.';
379
  noResultsMsg.style.padding = '1rem';
380
  noResultsMsg.style.fontStyle = 'italic';
381
  elements.gridContainer.appendChild(noResultsMsg);
382
  return;
383
  }
384
 
385
- urls.forEach(item => {
386
  const { url, embedUrl, title, likes_count, owner } = item;
387
 
388
  // Create grid item
@@ -399,6 +438,7 @@ if __name__ == '__main__':
399
 
400
  const titleEl = document.createElement('h3');
401
  titleEl.textContent = title;
 
402
  headerLeft.appendChild(titleEl);
403
 
404
  const ownerEl = document.createElement('div');
@@ -458,11 +498,14 @@ if __name__ == '__main__':
458
  elements.searchInput.addEventListener('input', () => {
459
  // Debounce input to prevent API calls on every keystroke
460
  clearTimeout(state.searchTimeout);
461
- state.searchTimeout = setTimeout(loadUrls, 300);
462
  });
463
 
 
 
 
464
  // Initialize
465
- loadUrls();
466
  </script>
467
  </body>
468
  </html>
 
1
  from flask import Flask, render_template, request, jsonify
2
  import requests
3
  import os
4
+ import json
5
+ import time
6
 
7
  app = Flask(__name__)
8
 
9
+ # Function to fetch trending spaces from Huggingface
10
+ def fetch_trending_spaces(limit=100):
11
+ try:
12
+ url = "https://huggingface.co/api/spaces"
13
+ params = {
14
+ "limit": limit,
15
+ "sort": "trending",
16
+ "direction": -1
17
+ }
18
+ response = requests.get(url, params=params, timeout=10)
19
+
20
+ if response.status_code == 200:
21
+ return response.json()
22
+ else:
23
+ print(f"Error fetching trending spaces: {response.status_code}")
24
+ return []
25
+ except Exception as e:
26
+ print(f"Exception when fetching trending spaces: {e}")
27
+ return []
 
 
 
28
 
29
  # Transform Huggingface URL to direct space URL
30
+ def transform_url(owner, name):
31
+ return f"https://{owner}-{name}.hf.space"
 
 
 
 
32
 
33
+ # Get space details
34
+ def get_space_details(space_data):
35
+ try:
36
+ # Basic info
37
+ owner = space_data.get('owner')
38
+ name = space_data.get('id')
39
+ title = space_data.get('title') or name
40
+
41
+ # URL construction
42
+ original_url = f"https://huggingface.co/spaces/{owner}/{name}"
43
+ embed_url = transform_url(owner, name)
44
+
45
+ # Get likes count
46
+ likes_count = space_data.get('likes', 0)
47
+
48
+ # Tags
49
+ tags = space_data.get('tags', [])
50
+
51
  return {
52
+ 'url': original_url,
53
+ 'embedUrl': embed_url,
54
+ 'title': title,
55
+ 'owner': owner,
56
+ 'likes_count': likes_count,
57
+ 'tags': tags
58
  }
59
+ except Exception as e:
60
+ print(f"Error processing space data: {e}")
61
+ return None
 
 
 
 
 
 
 
 
 
62
 
63
  # Homepage route
64
  @app.route('/')
65
  def home():
66
  return render_template('index.html')
67
 
68
+ # Trending spaces API
69
+ @app.route('/api/trending-spaces', methods=['GET'])
70
+ def trending_spaces():
71
  search_query = request.args.get('search', '').lower()
72
+ limit = int(request.args.get('limit', 100))
73
+
74
+ # Fetch trending spaces
75
+ spaces_data = fetch_trending_spaces(limit)
76
 
77
+ # Process and filter spaces
78
  results = []
79
+ for space_data in spaces_data:
80
+ space_info = get_space_details(space_data)
 
 
81
 
82
+ if not space_info:
83
  continue
84
+
85
+ # Apply search filter if needed
86
+ if search_query:
87
+ title = space_info['title'].lower()
88
+ owner = space_info['owner'].lower()
89
+ url = space_info['url'].lower()
90
+ tags = ' '.join(space_info.get('tags', [])).lower()
91
+
92
+ if (search_query not in title and
93
+ search_query not in owner and
94
+ search_query not in url and
95
+ search_query not in tags):
96
+ continue
97
 
98
+ results.append(space_info)
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
  return jsonify(results)
101
 
 
111
  <head>
112
  <meta charset="UTF-8">
113
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
114
+ <title>์ธ๊ธฐ ํ—ˆ๊น…ํŽ˜์ด์Šค ์ŠคํŽ˜์ด์Šค</title>
115
  <style>
116
  body {
117
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
 
152
  display: flex;
153
  justify-content: center;
154
  align-items: center;
155
+ gap: 10px;
156
  }
157
 
158
  input[type="text"] {
159
  padding: 0.7rem;
160
  border: 1px solid #ddd;
161
  border-radius: 4px;
 
162
  font-size: 1rem;
163
  width: 300px;
164
  }
165
 
166
+ button.refresh-btn {
167
+ padding: 0.7rem 1.2rem;
168
+ background-color: #4CAF50;
169
+ color: white;
170
+ border: none;
171
+ border-radius: 4px;
172
+ cursor: pointer;
173
+ font-size: 1rem;
174
+ transition: background-color 0.2s;
175
+ display: flex;
176
+ align-items: center;
177
+ gap: 5px;
178
+ }
179
+
180
+ button.refresh-btn:hover {
181
+ background-color: #45a049;
182
+ }
183
+
184
+ .refresh-icon {
185
+ display: inline-block;
186
+ width: 16px;
187
+ height: 16px;
188
+ border: 2px solid white;
189
+ border-top-color: transparent;
190
+ border-radius: 50%;
191
+ animation: spin 1s linear infinite;
192
+ }
193
+
194
+ .refreshing .refresh-icon {
195
+ animation: spin 1s linear infinite;
196
+ }
197
+
198
+ @keyframes spin {
199
+ 0% { transform: rotate(0deg); }
200
+ 100% { transform: rotate(360deg); }
201
+ }
202
+
203
  .grid-container {
204
  display: grid;
205
  grid-template-columns: repeat(auto-fill, minmax(450px, 1fr));
 
230
  .grid-header-left {
231
  display: flex;
232
  flex-direction: column;
233
+ max-width: 70%;
234
  }
235
 
236
  .grid-header h3 {
 
294
  right: 0;
295
  bottom: 0;
296
  background-color: rgba(255, 255, 255, 0.8);
297
+ display: flex;
298
  justify-content: center;
299
  align-items: center;
300
  z-index: 1000;
 
310
  animation: spin 1s linear infinite;
311
  }
312
 
 
 
 
 
 
313
  @media (max-width: 768px) {
314
  .filter-controls {
315
  flex-direction: column;
 
334
  <body>
335
  <div class="container">
336
  <div class="header">
337
+ <h1>์ธ๊ธฐ ํ—ˆ๊น…ํŽ˜์ด์Šค ์ŠคํŽ˜์ด์Šค Top 100</h1>
338
  </div>
339
 
340
  <div class="filter-controls">
341
+ <input type="text" id="searchInput" placeholder="์ด๋ฆ„, ํƒœ๊ทธ ๋“ฑ์œผ๋กœ ๊ฒ€์ƒ‰" />
342
+ <button id="refreshButton" class="refresh-btn">
343
+ <span class="refresh-icon"></span>
344
+ ์ƒˆ๋กœ๊ณ ์นจ
345
+ </button>
346
  </div>
347
 
348
  <div id="gridContainer" class="grid-container"></div>
 
357
  const elements = {
358
  gridContainer: document.getElementById('gridContainer'),
359
  loadingIndicator: document.getElementById('loadingIndicator'),
360
+ searchInput: document.getElementById('searchInput'),
361
+ refreshButton: document.getElementById('refreshButton')
362
  };
363
 
364
  // Application state
365
  const state = {
366
+ isLoading: false,
367
+ spaces: []
368
  };
369
 
370
  // Display loading indicator
371
  function setLoading(isLoading) {
372
  state.isLoading = isLoading;
373
  elements.loadingIndicator.style.display = isLoading ? 'flex' : 'none';
374
+
375
+ if (isLoading) {
376
+ elements.refreshButton.classList.add('refreshing');
377
+ } else {
378
+ elements.refreshButton.classList.remove('refreshing');
379
+ }
380
  }
381
 
382
  // API error handling
 
388
  return response.json();
389
  }
390
 
391
+ // Load trending spaces
392
+ async function loadTrendingSpaces() {
393
  setLoading(true);
394
 
395
  try {
396
  const searchText = elements.searchInput.value;
397
 
398
+ const response = await fetch(`/api/trending-spaces?search=${encodeURIComponent(searchText)}`);
399
+ const spaces = await handleApiResponse(response);
400
 
401
+ state.spaces = spaces;
402
+ renderGrid(spaces);
403
  } catch (error) {
404
+ console.error('์ŠคํŽ˜์ด์Šค ๋ชฉ๋ก ๋กœ๋“œ ์˜ค๋ฅ˜:', error);
405
+ alert(`์ŠคํŽ˜์ด์Šค ๋กœ๋“œ ์˜ค๋ฅ˜: ${error.message}`);
406
  } finally {
407
  setLoading(false);
408
  }
409
  }
410
 
411
  // Render grid
412
+ function renderGrid(spaces) {
413
  elements.gridContainer.innerHTML = '';
414
 
415
+ if (!spaces || spaces.length === 0) {
416
  const noResultsMsg = document.createElement('p');
417
+ noResultsMsg.textContent = 'ํ‘œ์‹œํ•  ์ŠคํŽ˜์ด์Šค๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.';
418
  noResultsMsg.style.padding = '1rem';
419
  noResultsMsg.style.fontStyle = 'italic';
420
  elements.gridContainer.appendChild(noResultsMsg);
421
  return;
422
  }
423
 
424
+ spaces.forEach(item => {
425
  const { url, embedUrl, title, likes_count, owner } = item;
426
 
427
  // Create grid item
 
438
 
439
  const titleEl = document.createElement('h3');
440
  titleEl.textContent = title;
441
+ titleEl.title = title; // For tooltip on hover
442
  headerLeft.appendChild(titleEl);
443
 
444
  const ownerEl = document.createElement('div');
 
498
  elements.searchInput.addEventListener('input', () => {
499
  // Debounce input to prevent API calls on every keystroke
500
  clearTimeout(state.searchTimeout);
501
+ state.searchTimeout = setTimeout(loadTrendingSpaces, 300);
502
  });
503
 
504
+ // Refresh button event listener
505
+ elements.refreshButton.addEventListener('click', loadTrendingSpaces);
506
+
507
  // Initialize
508
+ loadTrendingSpaces();
509
  </script>
510
  </body>
511
  </html>