fantaxy commited on
Commit
214ced4
·
verified ·
1 Parent(s): 18ae9ad

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +145 -140
app.py CHANGED
@@ -7,68 +7,73 @@ from collections import Counter
7
 
8
  app = Flask(__name__)
9
 
10
- # Function to fetch trending spaces from Huggingface with pagination
11
- def fetch_trending_spaces(offset=0, limit=72):
12
  try:
13
  # Simple data fetching
14
- url = "https://huggingface.co/api/spaces"
15
- params = {"limit": 10000} # Get max 10000 to fetch more spaces
16
 
17
  # Increase timeout
18
  response = requests.get(url, params=params, timeout=30)
19
 
20
  if response.status_code == 200:
21
- spaces = response.json()
22
- filtered_spaces = [space for space in spaces if space.get('owner') != 'None' and space.get('id', '').split('/', 1)[0] != 'None']
 
 
 
 
23
 
24
  # Slice according to requested offset and limit
25
- start = min(offset, len(filtered_spaces))
26
- end = min(offset + limit, len(filtered_spaces))
27
 
28
- print(f"Fetched {len(filtered_spaces)} spaces, returning {end-start} items from {start} to {end}")
29
 
30
  return {
31
- 'spaces': filtered_spaces[start:end],
32
- 'total': len(filtered_spaces),
33
  'offset': offset,
34
  'limit': limit,
35
- 'all_spaces': filtered_spaces # Return all spaces for stats calculation
36
  }
37
  else:
38
- print(f"Error fetching spaces: {response.status_code}")
39
- # Return empty spaces with fake 200 limit data
40
  return {
41
- 'spaces': generate_dummy_spaces(limit),
42
  'total': 200,
43
  'offset': offset,
44
  'limit': limit,
45
- 'all_spaces': generate_dummy_spaces(500) # Dummy data for stats
46
  }
47
  except Exception as e:
48
- print(f"Exception when fetching spaces: {e}")
49
- # Generate fake data
50
  return {
51
- 'spaces': generate_dummy_spaces(limit),
52
  'total': 200,
53
  'offset': offset,
54
  'limit': limit,
55
- 'all_spaces': generate_dummy_spaces(500) # Dummy data for stats
56
  }
57
 
58
- # Generate dummy spaces in case of error
59
- def generate_dummy_spaces(count):
60
- spaces = []
61
  for i in range(count):
62
- spaces.append({
63
- 'id': f'dummy/space-{i}',
64
  'owner': 'dummy',
65
- 'title': f'Example Space {i+1}',
66
  'likes': 100 - i,
67
  'createdAt': '2023-01-01T00:00:00.000Z'
68
  })
69
- return spaces
70
 
71
- # Transform Huggingface URL to direct space URL
 
72
  def transform_url(owner, name):
73
  # 1. Replace '.' with '-'
74
  name = name.replace('.', '-')
@@ -78,52 +83,53 @@ def transform_url(owner, name):
78
  owner = owner.lower()
79
  name = name.lower()
80
 
81
- return f"https://{owner}-{name}.hf.space"
 
82
 
83
- # Get space details
84
- def get_space_details(space_data, index, offset):
85
  try:
86
  # Extract common info
87
- if '/' in space_data.get('id', ''):
88
- owner, name = space_data.get('id', '').split('/', 1)
89
  else:
90
- owner = space_data.get('owner', '')
91
- name = space_data.get('id', '')
92
 
93
  # Ignore if contains None
94
  if owner == 'None' or name == 'None':
95
  return None
96
 
97
  # Construct URLs
98
- original_url = f"https://huggingface.co/spaces/{owner}/{name}"
99
  embed_url = transform_url(owner, name)
100
 
101
  # Likes count
102
- likes_count = space_data.get('likes', 0)
103
 
104
- # Extract title
105
- title = space_data.get('title', name)
106
 
107
  # Tags
108
- tags = space_data.get('tags', [])
109
 
110
  return {
111
  'url': original_url,
112
  'embedUrl': embed_url,
113
  'title': title,
114
  'owner': owner,
115
- 'name': name, # Store Space name
116
  'likes_count': likes_count,
117
  'tags': tags,
118
  'rank': offset + index + 1
119
  }
120
  except Exception as e:
121
- print(f"Error processing space data: {e}")
122
  # Return basic object even if error occurs
123
  return {
124
- 'url': 'https://huggingface.co/spaces',
125
- 'embedUrl': 'https://huggingface.co/spaces',
126
- 'title': 'Error Loading Space',
127
  'owner': 'huggingface',
128
  'name': 'error',
129
  'likes_count': 0,
@@ -131,14 +137,14 @@ def get_space_details(space_data, index, offset):
131
  'rank': offset + index + 1
132
  }
133
 
134
- # Get owner statistics from all spaces
135
- def get_owner_stats(all_spaces):
136
  owners = []
137
- for space in all_spaces:
138
- if '/' in space.get('id', ''):
139
- owner, _ = space.get('id', '').split('/', 1)
140
  else:
141
- owner = space.get('owner', '')
142
 
143
  if owner != 'None':
144
  owners.append(owner)
@@ -156,30 +162,30 @@ def get_owner_stats(all_spaces):
156
  def home():
157
  return render_template('index.html')
158
 
159
- # Trending spaces API
160
- @app.route('/api/trending-spaces', methods=['GET'])
161
- def trending_spaces():
162
  search_query = request.args.get('search', '').lower()
163
  offset = int(request.args.get('offset', 0))
164
  limit = int(request.args.get('limit', 72)) # Default 72
165
 
166
- # Fetch trending spaces
167
- spaces_data = fetch_trending_spaces(offset, limit)
168
 
169
- # Process and filter spaces
170
  results = []
171
- for index, space_data in enumerate(spaces_data['spaces']):
172
- space_info = get_space_details(space_data, index, offset)
173
 
174
- if not space_info:
175
  continue
176
 
177
  # Apply search filter if needed
178
  if search_query:
179
- title = space_info['title'].lower()
180
- owner = space_info['owner'].lower()
181
- url = space_info['url'].lower()
182
- tags = ' '.join([str(tag) for tag in space_info.get('tags', [])]).lower()
183
 
184
  if (search_query not in title and
185
  search_query not in owner and
@@ -187,31 +193,31 @@ def trending_spaces():
187
  search_query not in tags):
188
  continue
189
 
190
- results.append(space_info)
191
 
192
- # Get owner statistics for all spaces
193
- top_owners = get_owner_stats(spaces_data.get('all_spaces', []))
194
 
195
  return jsonify({
196
- 'spaces': results,
197
- 'total': spaces_data['total'],
198
  'offset': offset,
199
  'limit': limit,
200
  'top_owners': top_owners # Add top owners data
201
  })
202
 
203
  if __name__ == '__main__':
204
- # Create templates folder
205
  os.makedirs('templates', exist_ok=True)
206
 
207
- # Create index.html file
208
  with open('templates/index.html', 'w', encoding='utf-8') as f:
209
  f.write('''<!DOCTYPE html>
210
  <html lang="en">
211
  <head>
212
  <meta charset="UTF-8">
213
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
214
- <title>Huggingface Spaces Gallery</title>
215
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
216
  <style>
217
  @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;500;600;700&display=swap');
@@ -757,8 +763,8 @@ if __name__ == '__main__':
757
 
758
  <div class="mac-content">
759
  <div class="header">
760
- <h1>HF Space Leaderboard</h1>
761
- <p>Discover the top 500 trending spaces from the Huggingface</p>
762
  </div>
763
 
764
  <!-- Stats Section -->
@@ -769,11 +775,11 @@ if __name__ == '__main__':
769
  <div class="mac-button mac-minimize"></div>
770
  <div class="mac-button mac-maximize"></div>
771
  </div>
772
- <div class="mac-title">Creator Statistics</div>
773
  </div>
774
  <div class="mac-content">
775
  <div class="stats-header">
776
- <div class="stats-title">Top 30 Creators by Number of Spaces</div>
777
  <button id="statsToggle" class="stats-toggle">Show Stats</button>
778
  </div>
779
  <div id="statsContent" class="stats-content">
@@ -804,7 +810,7 @@ if __name__ == '__main__':
804
  <div id="loadingIndicator" class="loading">
805
  <div class="loading-content">
806
  <div class="loading-spinner"></div>
807
- <div class="loading-text">Loading amazing spaces...</div>
808
  <div id="loadingError" class="loading-error">
809
  If this takes too long, try refreshing the page.
810
  </div>
@@ -828,12 +834,12 @@ if __name__ == '__main__':
828
  // Application state
829
  const state = {
830
  isLoading: false,
831
- spaces: [],
832
  currentPage: 0,
833
  itemsPerPage: 72, // 72 items per page
834
  totalItems: 0,
835
  loadingTimeout: null,
836
- staticModeAttempted: {}, // Track which spaces have attempted static mode
837
  statsVisible: false,
838
  chartInstance: null,
839
  topOwners: [],
@@ -847,9 +853,9 @@ if __name__ == '__main__':
847
  checkInterval: 5000, // Check every 5 seconds
848
 
849
  // Start checking iframe loading status
850
- startChecking: function(iframe, owner, name, title, spaceKey) {
851
  // Initialize tracking
852
- this.checkQueue[spaceKey] = {
853
  iframe: iframe,
854
  owner: owner,
855
  name: name,
@@ -859,19 +865,19 @@ if __name__ == '__main__':
859
  };
860
 
861
  // Start recursive checking
862
- this.checkIframeStatus(spaceKey);
863
  },
864
 
865
  // Check iframe loading status
866
- checkIframeStatus: function(spaceKey) {
867
- if (!this.checkQueue[spaceKey]) return;
868
 
869
- const item = this.checkQueue[spaceKey];
870
  const iframe = item.iframe;
871
 
872
  // If already processed, stop checking
873
  if (item.status !== 'loading') {
874
- delete this.checkQueue[spaceKey];
875
  return;
876
  }
877
 
@@ -881,7 +887,7 @@ if __name__ == '__main__':
881
  try {
882
  // 1. Check if iframe was removed from DOM
883
  if (!iframe || !iframe.parentNode) {
884
- delete this.checkQueue[spaceKey];
885
  return;
886
  }
887
 
@@ -904,11 +910,11 @@ if __name__ == '__main__':
904
  } else {
905
  item.status = 'success';
906
  }
907
- delete this.checkQueue[spaceKey];
908
  return;
909
  }
910
  } catch(e) {
911
- // Cross-origin access errors are expected - might be normal loading
912
  }
913
 
914
  // 3. Check iframe's visible size
@@ -916,7 +922,7 @@ if __name__ == '__main__':
916
  if (rect.width > 50 && rect.height > 50 && item.attempts > 2) {
917
  // If it has sufficient size, mark as success
918
  item.status = 'success';
919
- delete this.checkQueue[spaceKey];
920
  return;
921
  }
922
 
@@ -931,13 +937,13 @@ if __name__ == '__main__':
931
  item.status = 'error';
932
  handleIframeError(iframe, item.owner, item.name, item.title);
933
  }
934
- delete this.checkQueue[spaceKey];
935
  return;
936
  }
937
 
938
  // Schedule next check with exponential backoff
939
  const nextDelay = this.checkInterval * Math.pow(1.5, item.attempts - 1);
940
- setTimeout(() => this.checkIframeStatus(spaceKey), nextDelay);
941
 
942
  } catch (e) {
943
  console.error('Error checking iframe status:', e);
@@ -946,10 +952,10 @@ if __name__ == '__main__':
946
  if (item.attempts >= this.maxAttempts) {
947
  item.status = 'error';
948
  handleIframeError(iframe, item.owner, item.name, item.title);
949
- delete this.checkQueue[spaceKey];
950
  } else {
951
  // Try again
952
- setTimeout(() => this.checkIframeStatus(spaceKey), this.checkInterval);
953
  }
954
  }
955
  }
@@ -991,7 +997,7 @@ if __name__ == '__main__':
991
  data: {
992
  labels: labels,
993
  datasets: [{
994
- label: 'Number of Spaces',
995
  data: data,
996
  backgroundColor: colors,
997
  borderColor: colors.map(color => color.replace('0.7', '1')),
@@ -1012,7 +1018,7 @@ if __name__ == '__main__':
1012
  return tooltipItems[0].label;
1013
  },
1014
  label: function(context) {
1015
- return `Spaces: ${context.raw}`;
1016
  }
1017
  }
1018
  }
@@ -1022,7 +1028,7 @@ if __name__ == '__main__':
1022
  beginAtZero: true,
1023
  title: {
1024
  display: true,
1025
- text: 'Number of Spaces'
1026
  }
1027
  },
1028
  y: {
@@ -1034,7 +1040,7 @@ if __name__ == '__main__':
1034
  ticks: {
1035
  autoSkip: false,
1036
  font: function(context) {
1037
- // Adjust font size to fit all labels if needed
1038
  const defaultSize = 11;
1039
  return {
1040
  size: labels.length > 20 ? defaultSize - 1 : defaultSize
@@ -1047,8 +1053,8 @@ if __name__ == '__main__':
1047
  });
1048
  }
1049
 
1050
- // Load spaces with timeout
1051
- async function loadSpaces(page = 0) {
1052
  setLoading(true);
1053
 
1054
  try {
@@ -1060,19 +1066,19 @@ if __name__ == '__main__':
1060
  setTimeout(() => reject(new Error('Request timeout')), 30000)
1061
  );
1062
 
1063
- const fetchPromise = fetch(`/api/trending-spaces?search=${encodeURIComponent(searchText)}&offset=${offset}&limit=${state.itemsPerPage}`);
1064
 
1065
  // Use the first Promise that completes
1066
  const response = await Promise.race([fetchPromise, timeoutPromise]);
1067
  const data = await response.json();
1068
 
1069
  // Update state on successful load
1070
- state.spaces = data.spaces;
1071
  state.totalItems = data.total;
1072
  state.currentPage = page;
1073
  state.topOwners = data.top_owners || [];
1074
 
1075
- renderGrid(data.spaces);
1076
  renderPagination();
1077
 
1078
  // If stats are visible, update chart
@@ -1080,13 +1086,13 @@ if __name__ == '__main__':
1080
  renderCreatorStats();
1081
  }
1082
  } catch (error) {
1083
- console.error('Error loading spaces:', error);
1084
 
1085
  // Show empty grid with error message
1086
  elements.gridContainer.innerHTML = `
1087
  <div style="grid-column: 1/-1; text-align: center; padding: 40px;">
1088
  <div style="font-size: 3rem; margin-bottom: 20px;">⚠️</div>
1089
- <h3 style="margin-bottom: 10px;">Unable to load spaces</h3>
1090
  <p style="color: #666;">Please try refreshing the page. If the problem persists, try again later.</p>
1091
  <button id="retryButton" style="margin-top: 20px; padding: 10px 20px; background: var(--pastel-purple); border: none; border-radius: 5px; cursor: pointer;">
1092
  Try Again
@@ -1095,7 +1101,7 @@ if __name__ == '__main__':
1095
  `;
1096
 
1097
  // Add event listener to retry button
1098
- document.getElementById('retryButton')?.addEventListener('click', () => loadSpaces(0));
1099
 
1100
  // Render simple pagination
1101
  renderPagination();
@@ -1117,7 +1123,7 @@ if __name__ == '__main__':
1117
  prevButton.disabled = state.currentPage === 0;
1118
  prevButton.addEventListener('click', () => {
1119
  if (state.currentPage > 0) {
1120
- loadSpaces(state.currentPage - 1);
1121
  }
1122
  });
1123
  elements.pagination.appendChild(prevButton);
@@ -1127,7 +1133,7 @@ if __name__ == '__main__':
1127
  let startPage = Math.max(0, state.currentPage - Math.floor(maxButtons / 2));
1128
  let endPage = Math.min(totalPages - 1, startPage + maxButtons - 1);
1129
 
1130
- // Adjust start page if the end page is less than maximum buttons
1131
  if (endPage - startPage + 1 < maxButtons) {
1132
  startPage = Math.max(0, endPage - maxButtons + 1);
1133
  }
@@ -1138,7 +1144,7 @@ if __name__ == '__main__':
1138
  pageButton.textContent = i + 1;
1139
  pageButton.addEventListener('click', () => {
1140
  if (i !== state.currentPage) {
1141
- loadSpaces(i);
1142
  }
1143
  });
1144
  elements.pagination.appendChild(pageButton);
@@ -1151,7 +1157,7 @@ if __name__ == '__main__':
1151
  nextButton.disabled = state.currentPage >= totalPages - 1;
1152
  nextButton.addEventListener('click', () => {
1153
  if (state.currentPage < totalPages - 1) {
1154
- loadSpaces(state.currentPage + 1);
1155
  }
1156
  });
1157
  elements.pagination.appendChild(nextButton);
@@ -1167,14 +1173,14 @@ if __name__ == '__main__':
1167
 
1168
  // Error message
1169
  const errorMessage = document.createElement('p');
1170
- errorMessage.textContent = `"${title}" space couldn't be loaded`;
1171
  errorPlaceholder.appendChild(errorMessage);
1172
 
1173
  // Direct HF link
1174
  const directLink = document.createElement('a');
1175
- directLink.href = `https://huggingface.co/spaces/${owner}/${name}`;
1176
  directLink.target = '_blank';
1177
- directLink.textContent = 'Visit HF Space';
1178
  directLink.style.color = '#3182ce';
1179
  directLink.style.marginTop = '10px';
1180
  directLink.style.display = 'inline-block';
@@ -1189,13 +1195,13 @@ if __name__ == '__main__':
1189
  container.appendChild(errorPlaceholder);
1190
  }
1191
 
1192
- // Render grid
1193
- function renderGrid(spaces) {
1194
  elements.gridContainer.innerHTML = '';
1195
 
1196
- if (!spaces || spaces.length === 0) {
1197
  const noResultsMsg = document.createElement('p');
1198
- noResultsMsg.textContent = 'No spaces found matching your search.';
1199
  noResultsMsg.style.padding = '2rem';
1200
  noResultsMsg.style.textAlign = 'center';
1201
  noResultsMsg.style.fontStyle = 'italic';
@@ -1204,7 +1210,7 @@ if __name__ == '__main__':
1204
  return;
1205
  }
1206
 
1207
- spaces.forEach((item) => {
1208
  try {
1209
  const { url, title, likes_count, owner, name, rank } = item;
1210
 
@@ -1213,7 +1219,7 @@ if __name__ == '__main__':
1213
  return;
1214
  }
1215
 
1216
- // Create grid item - Apply rotating pastel colors
1217
  const gridItem = document.createElement('div');
1218
  gridItem.className = 'grid-item';
1219
 
@@ -1228,7 +1234,7 @@ if __name__ == '__main__':
1228
  // Title
1229
  const titleEl = document.createElement('h3');
1230
  titleEl.textContent = title;
1231
- titleEl.title = title; // For tooltip on hover
1232
  headerTop.appendChild(titleEl);
1233
 
1234
  // Rank badge
@@ -1283,26 +1289,26 @@ if __name__ == '__main__':
1283
  const iframeId = `iframe-${owner}-${name}`;
1284
  iframe.id = iframeId;
1285
 
1286
- // Track this space
1287
- const spaceKey = `${owner}/${name}`;
1288
- state.iframeStatuses[spaceKey] = 'loading';
1289
 
1290
  // Use the advanced loader for better error detection
1291
  iframe.onload = function() {
1292
- iframeLoader.startChecking(iframe, owner, name, title, spaceKey);
1293
  };
1294
 
1295
  // Direct error handling
1296
  iframe.onerror = function() {
1297
  handleIframeError(iframe, owner, name, title);
1298
- state.iframeStatuses[spaceKey] = 'error';
1299
  };
1300
 
1301
  // Final fallback - if nothing has happened after 30 seconds, show error
1302
  setTimeout(() => {
1303
- if (state.iframeStatuses[spaceKey] === 'loading') {
1304
  handleIframeError(iframe, owner, name, title);
1305
- state.iframeStatuses[spaceKey] = 'error';
1306
  }
1307
  }, 30000);
1308
 
@@ -1330,43 +1336,41 @@ if __name__ == '__main__':
1330
  elements.gridContainer.appendChild(gridItem);
1331
  } catch (error) {
1332
  console.error('Item rendering error:', error);
1333
- // Continue rendering other items even if one fails
1334
  }
1335
  });
1336
  }
1337
 
1338
- // Filter event listeners
1339
  elements.searchInput.addEventListener('input', () => {
1340
  // Debounce input to prevent API calls on every keystroke
1341
  clearTimeout(state.searchTimeout);
1342
- state.searchTimeout = setTimeout(() => loadSpaces(0), 300);
1343
  });
1344
 
1345
  // Enter key in search box
1346
  elements.searchInput.addEventListener('keyup', (event) => {
1347
  if (event.key === 'Enter') {
1348
- loadSpaces(0);
1349
  }
1350
  });
1351
 
1352
  // Refresh button event listener
1353
- elements.refreshButton.addEventListener('click', () => loadSpaces(0));
1354
 
1355
- // Stats toggle button event listener
1356
  elements.statsToggle.addEventListener('click', toggleStats);
1357
 
1358
- // Mac buttons functionality (just for show)
1359
  document.querySelectorAll('.mac-button').forEach(button => {
1360
  button.addEventListener('click', function(e) {
1361
  e.preventDefault();
1362
- // Mac buttons don't do anything, just for style
1363
  });
1364
  });
1365
 
1366
  // Page load complete event detection
1367
  window.addEventListener('load', function() {
1368
  // Start loading data when page is fully loaded
1369
- setTimeout(() => loadSpaces(0), 500);
1370
  });
1371
 
1372
  // Safety mechanism to prevent infinite loading
@@ -1387,7 +1391,7 @@ if __name__ == '__main__':
1387
  }, 20000); // Force end loading state after 20 seconds
1388
 
1389
  // Start loading immediately - dual call with window.load for reliability
1390
- loadSpaces(0);
1391
 
1392
  // Display loading indicator control
1393
  function setLoading(isLoading) {
@@ -1408,7 +1412,7 @@ if __name__ == '__main__':
1408
  }
1409
  }
1410
 
1411
- // Create direct URL function with fixes for static sites
1412
  function createDirectUrl(owner, name) {
1413
  try {
1414
  // 1. Replace '.' characters with '-'
@@ -1419,7 +1423,8 @@ if __name__ == '__main__':
1419
  owner = owner.toLowerCase();
1420
  name = name.toLowerCase();
1421
 
1422
- return `https://${owner}-${name}.hf.space`;
 
1423
  } catch (error) {
1424
  console.error('URL creation error:', error);
1425
  return 'https://huggingface.co';
@@ -1430,5 +1435,5 @@ if __name__ == '__main__':
1430
  </html>
1431
  ''')
1432
 
1433
- # Use port 7860 for Huggingface Spaces
1434
  app.run(host='0.0.0.0', port=7860)
 
7
 
8
  app = Flask(__name__)
9
 
10
+ # Function to fetch trending models from Huggingface with pagination
11
+ def fetch_trending_models(offset=0, limit=72):
12
  try:
13
  # Simple data fetching
14
+ url = "https://huggingface.co/api/models"
15
+ params = {"limit": 10000} # Get max 10000 to fetch more models
16
 
17
  # Increase timeout
18
  response = requests.get(url, params=params, timeout=30)
19
 
20
  if response.status_code == 200:
21
+ models = response.json()
22
+ # Filter out any malformed data where owner or model id is 'None'
23
+ filtered_models = [
24
+ model for model in models
25
+ if model.get('owner') != 'None' and model.get('id', '').split('/', 1)[0] != 'None'
26
+ ]
27
 
28
  # Slice according to requested offset and limit
29
+ start = min(offset, len(filtered_models))
30
+ end = min(offset + limit, len(filtered_models))
31
 
32
+ print(f"Fetched {len(filtered_models)} models, returning {end-start} items from {start} to {end}")
33
 
34
  return {
35
+ 'models': filtered_models[start:end],
36
+ 'total': len(filtered_models),
37
  'offset': offset,
38
  'limit': limit,
39
+ 'all_models': filtered_models # Return all models for stats calculation
40
  }
41
  else:
42
+ print(f"Error fetching models: {response.status_code}")
43
+ # Return dummy data in case of error
44
  return {
45
+ 'models': generate_dummy_models(limit),
46
  'total': 200,
47
  'offset': offset,
48
  'limit': limit,
49
+ 'all_models': generate_dummy_models(500) # Dummy data for stats
50
  }
51
  except Exception as e:
52
+ print(f"Exception when fetching models: {e}")
53
+ # Generate dummy data
54
  return {
55
+ 'models': generate_dummy_models(limit),
56
  'total': 200,
57
  'offset': offset,
58
  'limit': limit,
59
+ 'all_models': generate_dummy_models(500) # Dummy data for stats
60
  }
61
 
62
+ # Generate dummy models in case of error
63
+ def generate_dummy_models(count):
64
+ models = []
65
  for i in range(count):
66
+ models.append({
67
+ 'id': f'dummy/model-{i}',
68
  'owner': 'dummy',
69
+ 'title': f'Example Model {i+1}',
70
  'likes': 100 - i,
71
  'createdAt': '2023-01-01T00:00:00.000Z'
72
  })
73
+ return models
74
 
75
+ # For consistency, we’ll keep a transform_url function.
76
+ # Instead of creating "hf.space" links, we now link to the HF model page directly.
77
  def transform_url(owner, name):
78
  # 1. Replace '.' with '-'
79
  name = name.replace('.', '-')
 
83
  owner = owner.lower()
84
  name = name.lower()
85
 
86
+ # For models, we just point to huggingface.co/{owner}/{name}
87
+ return f"https://huggingface.co/{owner}/{name}"
88
 
89
+ # Get model details
90
+ def get_model_details(model_data, index, offset):
91
  try:
92
  # Extract common info
93
+ if '/' in model_data.get('id', ''):
94
+ owner, name = model_data.get('id', '').split('/', 1)
95
  else:
96
+ owner = model_data.get('owner', '')
97
+ name = model_data.get('id', '')
98
 
99
  # Ignore if contains None
100
  if owner == 'None' or name == 'None':
101
  return None
102
 
103
  # Construct URLs
104
+ original_url = f"https://huggingface.co/{owner}/{name}"
105
  embed_url = transform_url(owner, name)
106
 
107
  # Likes count
108
+ likes_count = model_data.get('likes', 0)
109
 
110
+ # Extract title if available; fallback to model name
111
+ title = model_data.get('title', name)
112
 
113
  # Tags
114
+ tags = model_data.get('tags', [])
115
 
116
  return {
117
  'url': original_url,
118
  'embedUrl': embed_url,
119
  'title': title,
120
  'owner': owner,
121
+ 'name': name, # Store Model name
122
  'likes_count': likes_count,
123
  'tags': tags,
124
  'rank': offset + index + 1
125
  }
126
  except Exception as e:
127
+ print(f"Error processing model data: {e}")
128
  # Return basic object even if error occurs
129
  return {
130
+ 'url': 'https://huggingface.co',
131
+ 'embedUrl': 'https://huggingface.co',
132
+ 'title': 'Error Loading Model',
133
  'owner': 'huggingface',
134
  'name': 'error',
135
  'likes_count': 0,
 
137
  'rank': offset + index + 1
138
  }
139
 
140
+ # Get owner statistics from all models
141
+ def get_owner_stats(all_models):
142
  owners = []
143
+ for model in all_models:
144
+ if '/' in model.get('id', ''):
145
+ owner, _ = model.get('id', '').split('/', 1)
146
  else:
147
+ owner = model.get('owner', '')
148
 
149
  if owner != 'None':
150
  owners.append(owner)
 
162
  def home():
163
  return render_template('index.html')
164
 
165
+ # Trending models API
166
+ @app.route('/api/trending-models', methods=['GET'])
167
+ def trending_models():
168
  search_query = request.args.get('search', '').lower()
169
  offset = int(request.args.get('offset', 0))
170
  limit = int(request.args.get('limit', 72)) # Default 72
171
 
172
+ # Fetch models
173
+ models_data = fetch_trending_models(offset, limit)
174
 
175
+ # Process and filter models
176
  results = []
177
+ for index, model_data in enumerate(models_data['models']):
178
+ model_info = get_model_details(model_data, index, offset)
179
 
180
+ if not model_info:
181
  continue
182
 
183
  # Apply search filter if needed
184
  if search_query:
185
+ title = model_info['title'].lower()
186
+ owner = model_info['owner'].lower()
187
+ url = model_info['url'].lower()
188
+ tags = ' '.join([str(tag) for tag in model_info.get('tags', [])]).lower()
189
 
190
  if (search_query not in title and
191
  search_query not in owner and
 
193
  search_query not in tags):
194
  continue
195
 
196
+ results.append(model_info)
197
 
198
+ # Get owner statistics for all models
199
+ top_owners = get_owner_stats(models_data.get('all_models', []))
200
 
201
  return jsonify({
202
+ 'models': results,
203
+ 'total': models_data['total'],
204
  'offset': offset,
205
  'limit': limit,
206
  'top_owners': top_owners # Add top owners data
207
  })
208
 
209
  if __name__ == '__main__':
210
+ # Create templates folder if needed
211
  os.makedirs('templates', exist_ok=True)
212
 
213
+ # Create index.html file (the same UI, but references changed to "Model" instead of "Space")
214
  with open('templates/index.html', 'w', encoding='utf-8') as f:
215
  f.write('''<!DOCTYPE html>
216
  <html lang="en">
217
  <head>
218
  <meta charset="UTF-8">
219
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
220
+ <title>Huggingface Models Gallery</title>
221
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
222
  <style>
223
  @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;500;600;700&display=swap');
 
763
 
764
  <div class="mac-content">
765
  <div class="header">
766
+ <h1>HF Model Leaderboard</h1>
767
+ <p>Discover the top 500 trending models from Huggingface</p>
768
  </div>
769
 
770
  <!-- Stats Section -->
 
775
  <div class="mac-button mac-minimize"></div>
776
  <div class="mac-button mac-maximize"></div>
777
  </div>
778
+ <div class="mac-title">Owner Statistics</div>
779
  </div>
780
  <div class="mac-content">
781
  <div class="stats-header">
782
+ <div class="stats-title">Top 30 Creators by Number of Models</div>
783
  <button id="statsToggle" class="stats-toggle">Show Stats</button>
784
  </div>
785
  <div id="statsContent" class="stats-content">
 
810
  <div id="loadingIndicator" class="loading">
811
  <div class="loading-content">
812
  <div class="loading-spinner"></div>
813
+ <div class="loading-text">Loading amazing models...</div>
814
  <div id="loadingError" class="loading-error">
815
  If this takes too long, try refreshing the page.
816
  </div>
 
834
  // Application state
835
  const state = {
836
  isLoading: false,
837
+ models: [],
838
  currentPage: 0,
839
  itemsPerPage: 72, // 72 items per page
840
  totalItems: 0,
841
  loadingTimeout: null,
842
+ staticModeAttempted: {}, // Track which models have attempted static mode
843
  statsVisible: false,
844
  chartInstance: null,
845
  topOwners: [],
 
853
  checkInterval: 5000, // Check every 5 seconds
854
 
855
  // Start checking iframe loading status
856
+ startChecking: function(iframe, owner, name, title, modelKey) {
857
  // Initialize tracking
858
+ this.checkQueue[modelKey] = {
859
  iframe: iframe,
860
  owner: owner,
861
  name: name,
 
865
  };
866
 
867
  // Start recursive checking
868
+ this.checkIframeStatus(modelKey);
869
  },
870
 
871
  // Check iframe loading status
872
+ checkIframeStatus: function(modelKey) {
873
+ if (!this.checkQueue[modelKey]) return;
874
 
875
+ const item = this.checkQueue[modelKey];
876
  const iframe = item.iframe;
877
 
878
  // If already processed, stop checking
879
  if (item.status !== 'loading') {
880
+ delete this.checkQueue[modelKey];
881
  return;
882
  }
883
 
 
887
  try {
888
  // 1. Check if iframe was removed from DOM
889
  if (!iframe || !iframe.parentNode) {
890
+ delete this.checkQueue[modelKey];
891
  return;
892
  }
893
 
 
910
  } else {
911
  item.status = 'success';
912
  }
913
+ delete this.checkQueue[modelKey];
914
  return;
915
  }
916
  } catch(e) {
917
+ // Cross-origin access errors are normal, might be still loading
918
  }
919
 
920
  // 3. Check iframe's visible size
 
922
  if (rect.width > 50 && rect.height > 50 && item.attempts > 2) {
923
  // If it has sufficient size, mark as success
924
  item.status = 'success';
925
+ delete this.checkQueue[modelKey];
926
  return;
927
  }
928
 
 
937
  item.status = 'error';
938
  handleIframeError(iframe, item.owner, item.name, item.title);
939
  }
940
+ delete this.checkQueue[modelKey];
941
  return;
942
  }
943
 
944
  // Schedule next check with exponential backoff
945
  const nextDelay = this.checkInterval * Math.pow(1.5, item.attempts - 1);
946
+ setTimeout(() => this.checkIframeStatus(modelKey), nextDelay);
947
 
948
  } catch (e) {
949
  console.error('Error checking iframe status:', e);
 
952
  if (item.attempts >= this.maxAttempts) {
953
  item.status = 'error';
954
  handleIframeError(iframe, item.owner, item.name, item.title);
955
+ delete this.checkQueue[modelKey];
956
  } else {
957
  // Try again
958
+ setTimeout(() => this.checkIframeStatus(modelKey), this.checkInterval);
959
  }
960
  }
961
  }
 
997
  data: {
998
  labels: labels,
999
  datasets: [{
1000
+ label: 'Number of Models',
1001
  data: data,
1002
  backgroundColor: colors,
1003
  borderColor: colors.map(color => color.replace('0.7', '1')),
 
1018
  return tooltipItems[0].label;
1019
  },
1020
  label: function(context) {
1021
+ return `Models: ${context.raw}`;
1022
  }
1023
  }
1024
  }
 
1028
  beginAtZero: true,
1029
  title: {
1030
  display: true,
1031
+ text: 'Number of Models'
1032
  }
1033
  },
1034
  y: {
 
1040
  ticks: {
1041
  autoSkip: false,
1042
  font: function(context) {
1043
+ // Adjust font size if needed
1044
  const defaultSize = 11;
1045
  return {
1046
  size: labels.length > 20 ? defaultSize - 1 : defaultSize
 
1053
  });
1054
  }
1055
 
1056
+ // Load models with timeout
1057
+ async function loadModels(page = 0) {
1058
  setLoading(true);
1059
 
1060
  try {
 
1066
  setTimeout(() => reject(new Error('Request timeout')), 30000)
1067
  );
1068
 
1069
+ const fetchPromise = fetch(`/api/trending-models?search=${encodeURIComponent(searchText)}&offset=${offset}&limit=${state.itemsPerPage}`);
1070
 
1071
  // Use the first Promise that completes
1072
  const response = await Promise.race([fetchPromise, timeoutPromise]);
1073
  const data = await response.json();
1074
 
1075
  // Update state on successful load
1076
+ state.models = data.models;
1077
  state.totalItems = data.total;
1078
  state.currentPage = page;
1079
  state.topOwners = data.top_owners || [];
1080
 
1081
+ renderGrid(data.models);
1082
  renderPagination();
1083
 
1084
  // If stats are visible, update chart
 
1086
  renderCreatorStats();
1087
  }
1088
  } catch (error) {
1089
+ console.error('Error loading models:', error);
1090
 
1091
  // Show empty grid with error message
1092
  elements.gridContainer.innerHTML = `
1093
  <div style="grid-column: 1/-1; text-align: center; padding: 40px;">
1094
  <div style="font-size: 3rem; margin-bottom: 20px;">⚠️</div>
1095
+ <h3 style="margin-bottom: 10px;">Unable to load models</h3>
1096
  <p style="color: #666;">Please try refreshing the page. If the problem persists, try again later.</p>
1097
  <button id="retryButton" style="margin-top: 20px; padding: 10px 20px; background: var(--pastel-purple); border: none; border-radius: 5px; cursor: pointer;">
1098
  Try Again
 
1101
  `;
1102
 
1103
  // Add event listener to retry button
1104
+ document.getElementById('retryButton')?.addEventListener('click', () => loadModels(0));
1105
 
1106
  // Render simple pagination
1107
  renderPagination();
 
1123
  prevButton.disabled = state.currentPage === 0;
1124
  prevButton.addEventListener('click', () => {
1125
  if (state.currentPage > 0) {
1126
+ loadModels(state.currentPage - 1);
1127
  }
1128
  });
1129
  elements.pagination.appendChild(prevButton);
 
1133
  let startPage = Math.max(0, state.currentPage - Math.floor(maxButtons / 2));
1134
  let endPage = Math.min(totalPages - 1, startPage + maxButtons - 1);
1135
 
1136
+ // Adjust if not enough pages to fill maxButtons
1137
  if (endPage - startPage + 1 < maxButtons) {
1138
  startPage = Math.max(0, endPage - maxButtons + 1);
1139
  }
 
1144
  pageButton.textContent = i + 1;
1145
  pageButton.addEventListener('click', () => {
1146
  if (i !== state.currentPage) {
1147
+ loadModels(i);
1148
  }
1149
  });
1150
  elements.pagination.appendChild(pageButton);
 
1157
  nextButton.disabled = state.currentPage >= totalPages - 1;
1158
  nextButton.addEventListener('click', () => {
1159
  if (state.currentPage < totalPages - 1) {
1160
+ loadModels(state.currentPage + 1);
1161
  }
1162
  });
1163
  elements.pagination.appendChild(nextButton);
 
1173
 
1174
  // Error message
1175
  const errorMessage = document.createElement('p');
1176
+ errorMessage.textContent = `"${title}" model couldn't be loaded`;
1177
  errorPlaceholder.appendChild(errorMessage);
1178
 
1179
  // Direct HF link
1180
  const directLink = document.createElement('a');
1181
+ directLink.href = `https://huggingface.co/${owner}/${name}`;
1182
  directLink.target = '_blank';
1183
+ directLink.textContent = 'Visit HF Model';
1184
  directLink.style.color = '#3182ce';
1185
  directLink.style.marginTop = '10px';
1186
  directLink.style.display = 'inline-block';
 
1195
  container.appendChild(errorPlaceholder);
1196
  }
1197
 
1198
+ // Render grid of models
1199
+ function renderGrid(models) {
1200
  elements.gridContainer.innerHTML = '';
1201
 
1202
+ if (!models || models.length === 0) {
1203
  const noResultsMsg = document.createElement('p');
1204
+ noResultsMsg.textContent = 'No models found matching your search.';
1205
  noResultsMsg.style.padding = '2rem';
1206
  noResultsMsg.style.textAlign = 'center';
1207
  noResultsMsg.style.fontStyle = 'italic';
 
1210
  return;
1211
  }
1212
 
1213
+ models.forEach((item) => {
1214
  try {
1215
  const { url, title, likes_count, owner, name, rank } = item;
1216
 
 
1219
  return;
1220
  }
1221
 
1222
+ // Create grid item
1223
  const gridItem = document.createElement('div');
1224
  gridItem.className = 'grid-item';
1225
 
 
1234
  // Title
1235
  const titleEl = document.createElement('h3');
1236
  titleEl.textContent = title;
1237
+ titleEl.title = title; // For tooltip
1238
  headerTop.appendChild(titleEl);
1239
 
1240
  // Rank badge
 
1289
  const iframeId = `iframe-${owner}-${name}`;
1290
  iframe.id = iframeId;
1291
 
1292
+ // Track this model
1293
+ const modelKey = `${owner}/${name}`;
1294
+ state.iframeStatuses[modelKey] = 'loading';
1295
 
1296
  // Use the advanced loader for better error detection
1297
  iframe.onload = function() {
1298
+ iframeLoader.startChecking(iframe, owner, name, title, modelKey);
1299
  };
1300
 
1301
  // Direct error handling
1302
  iframe.onerror = function() {
1303
  handleIframeError(iframe, owner, name, title);
1304
+ state.iframeStatuses[modelKey] = 'error';
1305
  };
1306
 
1307
  // Final fallback - if nothing has happened after 30 seconds, show error
1308
  setTimeout(() => {
1309
+ if (state.iframeStatuses[modelKey] === 'loading') {
1310
  handleIframeError(iframe, owner, name, title);
1311
+ state.iframeStatuses[modelKey] = 'error';
1312
  }
1313
  }, 30000);
1314
 
 
1336
  elements.gridContainer.appendChild(gridItem);
1337
  } catch (error) {
1338
  console.error('Item rendering error:', error);
 
1339
  }
1340
  });
1341
  }
1342
 
1343
+ // Input event listeners
1344
  elements.searchInput.addEventListener('input', () => {
1345
  // Debounce input to prevent API calls on every keystroke
1346
  clearTimeout(state.searchTimeout);
1347
+ state.searchTimeout = setTimeout(() => loadModels(0), 300);
1348
  });
1349
 
1350
  // Enter key in search box
1351
  elements.searchInput.addEventListener('keyup', (event) => {
1352
  if (event.key === 'Enter') {
1353
+ loadModels(0);
1354
  }
1355
  });
1356
 
1357
  // Refresh button event listener
1358
+ elements.refreshButton.addEventListener('click', () => loadModels(0));
1359
 
1360
+ // Stats toggle button
1361
  elements.statsToggle.addEventListener('click', toggleStats);
1362
 
1363
+ // Mac buttons (just for style, no actual window control)
1364
  document.querySelectorAll('.mac-button').forEach(button => {
1365
  button.addEventListener('click', function(e) {
1366
  e.preventDefault();
 
1367
  });
1368
  });
1369
 
1370
  // Page load complete event detection
1371
  window.addEventListener('load', function() {
1372
  // Start loading data when page is fully loaded
1373
+ setTimeout(() => loadModels(0), 500);
1374
  });
1375
 
1376
  // Safety mechanism to prevent infinite loading
 
1391
  }, 20000); // Force end loading state after 20 seconds
1392
 
1393
  // Start loading immediately - dual call with window.load for reliability
1394
+ loadModels(0);
1395
 
1396
  // Display loading indicator control
1397
  function setLoading(isLoading) {
 
1412
  }
1413
  }
1414
 
1415
+ // Create direct URL for the model (HF website)
1416
  function createDirectUrl(owner, name) {
1417
  try {
1418
  // 1. Replace '.' characters with '-'
 
1423
  owner = owner.toLowerCase();
1424
  name = name.toLowerCase();
1425
 
1426
+ // For models, direct embed = huggingface.co/owner/name
1427
+ return `https://huggingface.co/${owner}/${name}`;
1428
  } catch (error) {
1429
  console.error('URL creation error:', error);
1430
  return 'https://huggingface.co';
 
1435
  </html>
1436
  ''')
1437
 
1438
+ # Use port 7860 (common in HF Spaces, can be changed if needed)
1439
  app.run(host='0.0.0.0', port=7860)