davanstrien HF staff commited on
Commit
ecc20d1
·
1 Parent(s): ab4d3d5

add param filter

Browse files
Files changed (1) hide show
  1. index.html +443 -181
index.html CHANGED
@@ -123,7 +123,7 @@
123
  <select
124
  id="searchSortSelect"
125
  class="text-sm border rounded-lg px-3 py-2 bg-white text-gray-700 focus:ring-2 focus:ring-blue-100 focus:border-blue-300 transition-all outline-none"
126
- onchange="handleSortChange('search')"
127
  >
128
  <option value="similarity">Sort by relevance</option>
129
  <option value="likes">Sort by likes</option>
@@ -175,6 +175,24 @@
175
  onchange="handleFilterChange('search')"
176
  />
177
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  </div>
179
  </div>
180
  </div>
@@ -217,7 +235,7 @@
217
  <select
218
  id="similarSortSelect"
219
  class="text-sm border rounded-lg px-3 py-2 bg-white text-gray-700 focus:ring-2 focus:ring-blue-100 focus:border-blue-300 transition-all outline-none"
220
- onchange="handleSortChange('similar')"
221
  >
222
  <option value="similarity">Sort by relevance</option>
223
  <option value="likes">Sort by likes</option>
@@ -269,6 +287,24 @@
269
  onchange="handleFilterChange('similar')"
270
  />
271
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  </div>
273
  </div>
274
  </div>
@@ -282,6 +318,9 @@
282
  class="w-full p-3 border border-gray-200 rounded-lg"
283
  placeholder="e.g. openai/gsm8k or meta-llama/Llama-2-7b"
284
  />
 
 
 
285
  <div
286
  id="suggestionsBox"
287
  class="hidden absolute w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-lg z-10 max-h-60 overflow-y-auto"
@@ -400,6 +439,7 @@
400
  let currentType = INITIAL_TYPE;
401
  let currentMinLikes = 0;
402
  let currentMinDownloads = 0;
 
403
 
404
  // Initialize Lucide icons
405
  lucide.createIcons();
@@ -417,6 +457,10 @@
417
  document.getElementById(`${tabId}Content`).classList.remove("hidden");
418
  document.getElementById(`${tabId}Tab`).classList.add("active");
419
 
 
 
 
 
420
  // Clear results container when switching to trending tab
421
  if (tabId === "trending") {
422
  document.getElementById("resultsContainer").innerHTML = "";
@@ -433,6 +477,9 @@
433
  } else if (tabId === "trending") {
434
  updateURL({ tab: "trending", q: null, similar: null });
435
  }
 
 
 
436
  }
437
 
438
  // Create result card
@@ -571,6 +618,97 @@
571
  window.history.pushState({}, "", newURL);
572
  }
573
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
574
  // Modify the search function to handle both datasets and models
575
  const searchResources = _.debounce(async (query, page = 1) => {
576
  performSearch(query, page);
@@ -633,6 +771,20 @@
633
  limit: limit,
634
  });
635
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
636
  const response = await fetch(`${endpoint}?${queryParams}`);
637
 
638
  if (!response.ok) {
@@ -717,10 +869,8 @@
717
  tab: "similar",
718
  });
719
 
720
- const similarLoader = document.getElementById("similarLoader");
721
- if (similarLoader) {
722
- similarLoader.classList.remove("hidden");
723
- }
724
  document.getElementById("errorMessage").classList.add("hidden");
725
 
726
  try {
@@ -728,11 +878,29 @@
728
  const paramName =
729
  currentType === "datasets" ? "dataset_id" : "model_id";
730
 
731
- const response = await fetch(
732
- `${endpoint}?${paramName}=${encodeURIComponent(resourceId)}&k=${
733
- RESULTS_PER_PAGE * page
734
- }&sort_by=${currentSort}&min_likes=${currentMinLikes}&min_downloads=${currentMinDownloads}`
735
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
736
  if (!response.ok) throw new Error("Similarity search failed");
737
 
738
  const data = await response.json();
@@ -740,9 +908,8 @@
740
  } catch (error) {
741
  showError(`Failed to find similar ${currentType}. Please try again.`);
742
  } finally {
743
- if (similarLoader) {
744
- similarLoader.classList.add("hidden");
745
- }
746
  }
747
  }
748
 
@@ -967,28 +1134,34 @@
967
  }
968
  }
969
 
970
- // Modify the selectType function to be simpler
971
  function selectType(type) {
 
 
 
972
  // Update the current type
973
  currentType = type;
974
 
975
- // Update all select elements to match
976
  document.getElementById("searchTypeSelect").value = type;
977
  document.getElementById("similarTypeSelect").value = type;
978
- document.getElementById("trendingTypeSelect").value = type;
979
 
980
- // Update placeholder text for similar tab
981
- const resourceInput = document.getElementById("resourceInput");
982
- resourceInput.placeholder =
983
- type === "datasets"
984
- ? "e.g. openai/gsm8k"
985
- : "e.g. meta-llama/Llama-2-7b";
986
 
987
- // Clear results and reset page
988
- currentPage = 1;
989
- document.getElementById("resultsContainer").innerHTML = "";
 
990
 
991
- // Re-run the current search with new type
 
 
 
 
 
 
 
992
  const activeTab = document.querySelector(".tab-trigger.active").id;
993
  if (activeTab === "searchTab") {
994
  const searchQuery = document.getElementById("searchInput").value;
@@ -1001,206 +1174,295 @@
1001
  findSimilarResources(1);
1002
  }
1003
  } else if (activeTab === "trendingTab") {
1004
- // Reload trending tab with new type
1005
  loadTrendingResources(type);
1006
  }
1007
-
1008
- // Update URL with the new type
1009
- updateURL({ type: currentType });
1010
  }
1011
 
1012
- // Add a non-debounced version of the search function
1013
- async function performSearch(query, page = 1) {
1014
- if (query.length < MIN_SEARCH_LENGTH) {
1015
- document.getElementById("resultsContainer").innerHTML = "";
1016
- updateURL({ q: null, similar: null }); // Clear URL params
1017
- return;
1018
- }
1019
 
1020
- document.getElementById("searchLoader").classList.remove("hidden");
1021
- document.getElementById("errorMessage").classList.add("hidden");
 
 
 
1022
 
1023
- // Update URL with search query and type
1024
- updateURL({
1025
- q: query,
1026
- similar: null,
1027
- type: currentType,
1028
- tab: "search",
1029
- });
1030
 
1031
- try {
1032
- const endpoint = `${API_URL}/search/${currentType}`;
1033
- const params = new URLSearchParams({
1034
- query: query,
1035
- k: RESULTS_PER_PAGE * page,
1036
- sort_by: currentSort,
1037
- min_likes: currentMinLikes,
1038
- min_downloads: currentMinDownloads,
1039
- });
 
 
1040
 
1041
- const response = await fetch(`${endpoint}?${params}`);
1042
- if (!response.ok) throw new Error("Search failed");
 
1043
 
1044
- const data = await response.json();
1045
- displayResults(data.results, page);
1046
- } catch (error) {
1047
- console.error("Search error:", error);
1048
- showError("Failed to perform search. Please try again.");
1049
- } finally {
1050
- document.getElementById("searchLoader").classList.add("hidden");
1051
  }
1052
- }
1053
 
1054
- // Update the event listeners section
1055
- document.addEventListener("DOMContentLoaded", async () => {
1056
- const resourceInput = document.getElementById("resourceInput");
1057
- let programmaticFocus = false;
1058
-
1059
- // Set initial type from URL
1060
- if (INITIAL_TYPE) {
1061
- document.getElementById("searchTypeSelect").value = INITIAL_TYPE;
1062
- document.getElementById("similarTypeSelect").value = INITIAL_TYPE;
1063
- document.getElementById("trendingTypeSelect").value = INITIAL_TYPE;
1064
- currentType = INITIAL_TYPE;
1065
  }
1066
 
1067
- // Add input event listener for suggestions
1068
- resourceInput.addEventListener("input", async (e) => {
1069
- const suggestionsBox = document.getElementById("suggestionsBox");
1070
- const value = e.target.value;
 
1071
 
1072
- if (!programmaticFocus) {
1073
- if (!value) {
1074
- // Show trending resources when input is empty
1075
- const trending = await fetchTrendingResources(currentType);
1076
- displaySuggestions(trending, suggestionsBox);
1077
- } else {
1078
- // Filter trending resources based on input
1079
- const trending = await fetchTrendingResources(currentType);
1080
- const filtered = trending.filter((resource) =>
1081
- resource.toLowerCase().includes(value.toLowerCase())
1082
- );
1083
- displaySuggestions(filtered, suggestionsBox);
1084
- }
1085
- }
1086
- });
1087
 
1088
- // Show trending resources on focus only when not programmatically focused
1089
- resourceInput.addEventListener("focus", async () => {
1090
- if (!programmaticFocus) {
1091
- const suggestionsBox = document.getElementById("suggestionsBox");
1092
- const trending = await fetchTrendingResources(currentType);
1093
- displaySuggestions(trending, suggestionsBox);
1094
  }
1095
- programmaticFocus = false;
1096
- });
 
 
 
 
 
 
1097
 
1098
- // Handle initial URL parameters
1099
- if (INITIAL_TAB) {
1100
- switchTab(INITIAL_TAB);
 
 
 
 
 
 
 
1101
  }
1102
 
1103
- if (INITIAL_SEARCH) {
1104
- switchTab("search");
1105
- document.getElementById("searchInput").value = INITIAL_SEARCH;
1106
- await searchResources(INITIAL_SEARCH);
1107
- } else if (INITIAL_SIMILAR) {
1108
- switchTab("similar");
1109
- document.getElementById("resourceInput").value = INITIAL_SIMILAR;
1110
- await findSimilarResources();
1111
- } else if (INITIAL_TAB === "trending") {
1112
- // Load trending data with any URL parameters
1113
- const limit = URL_PARAMS.get("limit") || 10;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1114
 
1115
- document.getElementById("trendingLimitSelect").value = limit;
 
 
 
 
 
1116
 
1117
- loadTrendingResources(currentType);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1118
  }
1119
- });
1120
 
1121
- // Modify the handleSortChange function
1122
- function handleSortChange(tab) {
1123
- const sortSelect = document.getElementById(`${tab}SortSelect`);
1124
- currentSort = sortSelect.value;
1125
 
1126
- // Re-run the current search with new sort
 
 
 
 
 
 
 
1127
  const activeTab = document.querySelector(".tab-trigger.active").id;
 
1128
  if (activeTab === "searchTab") {
1129
  const searchQuery = document.getElementById("searchInput").value;
1130
  if (searchQuery.length >= MIN_SEARCH_LENGTH) {
1131
- searchResources(searchQuery, 1);
 
1132
  }
1133
- } else {
1134
  const resourceId = document.getElementById("resourceInput").value;
1135
  if (resourceId) {
 
1136
  findSimilarResources(1);
1137
  }
 
 
1138
  }
1139
  }
1140
 
1141
- // Add these new functions to handle the filters UI
1142
- function toggleFilters(tab) {
1143
- const popover = document.getElementById(`${tab}FiltersPopover`);
1144
- popover.classList.toggle("hidden");
1145
-
1146
- // Close other popovers
1147
- const otherTab = tab === "search" ? "similar" : "search";
1148
- document
1149
- .getElementById(`${otherTab}FiltersPopover`)
1150
- .classList.add("hidden");
1151
  }
1152
 
1153
- // Close filters when clicking outside
1154
- document.addEventListener("click", (event) => {
1155
- const searchPopover = document.getElementById("searchFiltersPopover");
1156
- const similarPopover = document.getElementById("similarFiltersPopover");
1157
- const searchButton = event.target.closest(
1158
- "button[onclick=\"toggleFilters('search')\"]"
1159
- );
1160
- const similarButton = event.target.closest(
1161
- "button[onclick=\"toggleFilters('similar')\"]"
1162
- );
1163
 
1164
- if (!searchButton && !event.target.closest("#searchFiltersPopover")) {
1165
- searchPopover.classList.add("hidden");
1166
- }
1167
- if (!similarButton && !event.target.closest("#similarFiltersPopover")) {
1168
- similarPopover.classList.add("hidden");
1169
- }
1170
- });
1171
 
1172
- // Modify handleFilterChange to update the active filters indicator
1173
- function handleFilterChange(tab) {
1174
- const minLikesInput = document.getElementById(`${tab}MinLikes`);
1175
- const minDownloadsInput = document.getElementById(`${tab}MinDownloads`);
1176
- const activeFiltersSpan = document.getElementById(
1177
- `${tab}ActiveFilters`
1178
- );
1179
 
1180
- currentMinLikes = parseInt(minLikesInput.value) || 0;
1181
- currentMinDownloads = parseInt(minDownloadsInput.value) || 0;
 
 
 
1182
 
1183
- // Update active filters indicator
1184
- const activeCount =
1185
- (currentMinLikes > 0 ? 1 : 0) + (currentMinDownloads > 0 ? 1 : 0);
1186
- if (activeCount > 0) {
1187
- activeFiltersSpan.textContent = activeCount;
1188
- activeFiltersSpan.classList.remove("hidden");
1189
- } else {
1190
- activeFiltersSpan.classList.add("hidden");
1191
  }
1192
 
1193
- // Re-run the current search with new filters
1194
- if (tab === "search") {
1195
- const searchQuery = document.getElementById("searchInput").value;
1196
- if (searchQuery.length >= MIN_SEARCH_LENGTH) {
1197
- searchResources(searchQuery, 1);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1198
  }
1199
- } else {
1200
- const resourceId = document.getElementById("resourceInput").value;
1201
- if (resourceId) {
1202
- findSimilarResources(1);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1203
  }
 
 
 
1204
  }
1205
  }
1206
  </script>
 
123
  <select
124
  id="searchSortSelect"
125
  class="text-sm border rounded-lg px-3 py-2 bg-white text-gray-700 focus:ring-2 focus:ring-blue-100 focus:border-blue-300 transition-all outline-none"
126
+ onchange="handleFilterChange('search')"
127
  >
128
  <option value="similarity">Sort by relevance</option>
129
  <option value="likes">Sort by likes</option>
 
175
  onchange="handleFilterChange('search')"
176
  />
177
  </div>
178
+ <div id="searchParamRange" class="hidden">
179
+ <label
180
+ class="block text-sm font-medium text-gray-700 mb-1"
181
+ >Parameter Range</label
182
+ >
183
+ <select
184
+ id="searchParamRangeSelect"
185
+ class="w-full text-sm border rounded-lg px-3 py-2 bg-white text-gray-700 focus:ring-2 focus:ring-blue-100 focus:border-blue-300 transition-all outline-none"
186
+ onchange="handleFilterChange('search')"
187
+ >
188
+ <option value="any">Any</option>
189
+ <option value="0-1">0-1B</option>
190
+ <option value="1-7">1-7B</option>
191
+ <option value="7-20">7-20B</option>
192
+ <option value="20-70">20-70B</option>
193
+ <option value="70+">70B+</option>
194
+ </select>
195
+ </div>
196
  </div>
197
  </div>
198
  </div>
 
235
  <select
236
  id="similarSortSelect"
237
  class="text-sm border rounded-lg px-3 py-2 bg-white text-gray-700 focus:ring-2 focus:ring-blue-100 focus:border-blue-300 transition-all outline-none"
238
+ onchange="handleFilterChange('similar')"
239
  >
240
  <option value="similarity">Sort by relevance</option>
241
  <option value="likes">Sort by likes</option>
 
287
  onchange="handleFilterChange('similar')"
288
  />
289
  </div>
290
+ <div id="similarParamRange" class="hidden">
291
+ <label
292
+ class="block text-sm font-medium text-gray-700 mb-1"
293
+ >Parameter Range</label
294
+ >
295
+ <select
296
+ id="similarParamRangeSelect"
297
+ class="w-full text-sm border rounded-lg px-3 py-2 bg-white text-gray-700 focus:ring-2 focus:ring-blue-100 focus:border-blue-300 transition-all outline-none"
298
+ onchange="handleFilterChange('similar')"
299
+ >
300
+ <option value="any">Any</option>
301
+ <option value="0-1">0-1B</option>
302
+ <option value="1-7">1-7B</option>
303
+ <option value="7-20">7-20B</option>
304
+ <option value="20-70">20-70B</option>
305
+ <option value="70+">70B+</option>
306
+ </select>
307
+ </div>
308
  </div>
309
  </div>
310
  </div>
 
318
  class="w-full p-3 border border-gray-200 rounded-lg"
319
  placeholder="e.g. openai/gsm8k or meta-llama/Llama-2-7b"
320
  />
321
+ <div id="similarLoader" class="hidden absolute right-3 top-3">
322
+ <i data-lucide="loader-2" class="animate-spin"></i>
323
+ </div>
324
  <div
325
  id="suggestionsBox"
326
  class="hidden absolute w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-lg z-10 max-h-60 overflow-y-auto"
 
439
  let currentType = INITIAL_TYPE;
440
  let currentMinLikes = 0;
441
  let currentMinDownloads = 0;
442
+ let currentParamFilter = "any";
443
 
444
  // Initialize Lucide icons
445
  lucide.createIcons();
 
457
  document.getElementById(`${tabId}Content`).classList.remove("hidden");
458
  document.getElementById(`${tabId}Tab`).classList.add("active");
459
 
460
+ // Remove reference to non-existent elements
461
+ // Display parameter filter UI if needed
462
+ updateParamFilterUI();
463
+
464
  // Clear results container when switching to trending tab
465
  if (tabId === "trending") {
466
  document.getElementById("resultsContainer").innerHTML = "";
 
477
  } else if (tabId === "trending") {
478
  updateURL({ tab: "trending", q: null, similar: null });
479
  }
480
+
481
+ // Update parameter filter UI after tab switch
482
+ updateParamFilterUI();
483
  }
484
 
485
  // Create result card
 
618
  window.history.pushState({}, "", newURL);
619
  }
620
 
621
+ // Add this function before the searchResources definition
622
+ // This function needs to be defined before searchResources uses it
623
+ async function performSearch(query, page = 1) {
624
+ if (query.length < MIN_SEARCH_LENGTH) {
625
+ document.getElementById("resultsContainer").innerHTML = "";
626
+ updateURL({ q: null, similar: null }); // Clear URL params
627
+ return;
628
+ }
629
+
630
+ document.getElementById("searchLoader").classList.remove("hidden");
631
+ document.getElementById("errorMessage").classList.add("hidden");
632
+
633
+ // Update URL with search query and type
634
+ updateURL({
635
+ q: query,
636
+ similar: null,
637
+ type: currentType,
638
+ tab: "search",
639
+ });
640
+
641
+ try {
642
+ const endpoint = `${API_URL}/search/${currentType}`;
643
+ const params = new URLSearchParams({
644
+ query: query,
645
+ k: RESULTS_PER_PAGE * page,
646
+ sort_by: currentSort,
647
+ min_likes: currentMinLikes,
648
+ min_downloads: currentMinDownloads,
649
+ });
650
+
651
+ // Add parameter filters for models
652
+ if (currentType === "models" && currentParamFilter !== "any") {
653
+ if (currentParamFilter.includes("-")) {
654
+ const [min, max] = currentParamFilter.split("-");
655
+ params.append("min_param_count", parseInt(min) * 1e9);
656
+ params.append("max_param_count", parseInt(max) * 1e9);
657
+ } else if (currentParamFilter.endsWith("+")) {
658
+ params.append(
659
+ "min_param_count",
660
+ parseInt(currentParamFilter.replace("+", "")) * 1e9
661
+ );
662
+ }
663
+ }
664
+
665
+ const response = await fetch(`${endpoint}?${params}`);
666
+ if (!response.ok) throw new Error("Search failed");
667
+
668
+ const data = await response.json();
669
+ displayResults(data.results, page);
670
+ } catch (error) {
671
+ console.error("Search error:", error);
672
+ showError("Failed to perform search. Please try again.");
673
+ } finally {
674
+ document.getElementById("searchLoader").classList.add("hidden");
675
+ }
676
+ }
677
+
678
+ // Add this function to handle toggling filters
679
+ // This needs to be defined before it's referenced in the HTML
680
+ function toggleFilters(tab) {
681
+ const popover = document.getElementById(`${tab}FiltersPopover`);
682
+ popover.classList.toggle("hidden");
683
+
684
+ // Close other popovers
685
+ const otherTab = tab === "search" ? "similar" : "search";
686
+ document
687
+ .getElementById(`${otherTab}FiltersPopover`)
688
+ .classList.add("hidden");
689
+ }
690
+
691
+ // Helper function for syncing param values between tabs
692
+ function syncParamFilterValues() {
693
+ const searchParamSelect = document.getElementById(
694
+ "searchParamRangeSelect"
695
+ );
696
+ const similarParamSelect = document.getElementById(
697
+ "similarParamRangeSelect"
698
+ );
699
+
700
+ // When updating one, update both and the global variable
701
+ if (searchParamSelect && similarParamSelect) {
702
+ if (searchParamSelect.value !== currentParamFilter) {
703
+ currentParamFilter = searchParamSelect.value;
704
+ similarParamSelect.value = currentParamFilter;
705
+ } else if (similarParamSelect.value !== currentParamFilter) {
706
+ currentParamFilter = similarParamSelect.value;
707
+ searchParamSelect.value = currentParamFilter;
708
+ }
709
+ }
710
+ }
711
+
712
  // Modify the search function to handle both datasets and models
713
  const searchResources = _.debounce(async (query, page = 1) => {
714
  performSearch(query, page);
 
771
  limit: limit,
772
  });
773
 
774
+ // Add parameter filters for models
775
+ if (type === "models" && currentParamFilter !== "any") {
776
+ if (currentParamFilter.includes("-")) {
777
+ const [min, max] = currentParamFilter.split("-");
778
+ queryParams.append("min_param_count", parseInt(min) * 1e9);
779
+ queryParams.append("max_param_count", parseInt(max) * 1e9);
780
+ } else if (currentParamFilter.endsWith("+")) {
781
+ queryParams.append(
782
+ "min_param_count",
783
+ parseInt(currentParamFilter.replace("+", "")) * 1e9
784
+ );
785
+ }
786
+ }
787
+
788
  const response = await fetch(`${endpoint}?${queryParams}`);
789
 
790
  if (!response.ok) {
 
869
  tab: "similar",
870
  });
871
 
872
+ // Show loader and hide error message
873
+ document.getElementById("similarLoader").classList.remove("hidden");
 
 
874
  document.getElementById("errorMessage").classList.add("hidden");
875
 
876
  try {
 
878
  const paramName =
879
  currentType === "datasets" ? "dataset_id" : "model_id";
880
 
881
+ const params = new URLSearchParams({
882
+ [paramName]: resourceId,
883
+ k: RESULTS_PER_PAGE * page,
884
+ sort_by: currentSort,
885
+ min_likes: currentMinLikes,
886
+ min_downloads: currentMinDownloads,
887
+ });
888
+
889
+ // Add parameter filters for models
890
+ if (currentType === "models" && currentParamFilter !== "any") {
891
+ if (currentParamFilter.includes("-")) {
892
+ const [min, max] = currentParamFilter.split("-");
893
+ params.append("min_param_count", parseInt(min) * 1e9);
894
+ params.append("max_param_count", parseInt(max) * 1e9);
895
+ } else if (currentParamFilter.endsWith("+")) {
896
+ params.append(
897
+ "min_param_count",
898
+ parseInt(currentParamFilter.replace("+", "")) * 1e9
899
+ );
900
+ }
901
+ }
902
+
903
+ const response = await fetch(`${endpoint}?${params}`);
904
  if (!response.ok) throw new Error("Similarity search failed");
905
 
906
  const data = await response.json();
 
908
  } catch (error) {
909
  showError(`Failed to find similar ${currentType}. Please try again.`);
910
  } finally {
911
+ // Hide the loader when done
912
+ document.getElementById("similarLoader").classList.add("hidden");
 
913
  }
914
  }
915
 
 
1134
  }
1135
  }
1136
 
1137
+ // Update selectType to preserve parameter filter selection
1138
  function selectType(type) {
1139
+ // Store current filter value before changing type
1140
+ const currentParamValue = currentParamFilter;
1141
+
1142
  // Update the current type
1143
  currentType = type;
1144
 
1145
+ // Update the type selectors to maintain consistency
1146
  document.getElementById("searchTypeSelect").value = type;
1147
  document.getElementById("similarTypeSelect").value = type;
 
1148
 
1149
+ // We no longer need to show/hide parameter filter sections
1150
+ // since we've removed them from the popovers
 
 
 
 
1151
 
1152
+ if (type !== "models") {
1153
+ // Reset parameter filters when switching away from models
1154
+ currentParamFilter = "any";
1155
+ }
1156
 
1157
+ // Update filter indicators
1158
+ updateFilterIndicators("search");
1159
+ updateFilterIndicators("similar");
1160
+
1161
+ // Update the parameter filter UI - this will show/hide based on type
1162
+ updateParamFilterUI();
1163
+
1164
+ // Re-run search if needed
1165
  const activeTab = document.querySelector(".tab-trigger.active").id;
1166
  if (activeTab === "searchTab") {
1167
  const searchQuery = document.getElementById("searchInput").value;
 
1174
  findSimilarResources(1);
1175
  }
1176
  } else if (activeTab === "trendingTab") {
 
1177
  loadTrendingResources(type);
1178
  }
 
 
 
1179
  }
1180
 
1181
+ // Add this helper function to update filter indicators consistently
1182
+ function updateFilterIndicators(tab) {
1183
+ const activeFiltersSpan = document.getElementById(
1184
+ `${tab}ActiveFilters`
1185
+ );
 
 
1186
 
1187
+ // Count active filters
1188
+ let activeCount =
1189
+ (currentMinLikes > 0 ? 1 : 0) +
1190
+ (currentMinDownloads > 0 ? 1 : 0) +
1191
+ (currentType === "models" && currentParamFilter !== "any" ? 1 : 0);
1192
 
1193
+ if (activeCount > 0) {
1194
+ activeFiltersSpan.textContent = activeCount;
1195
+ activeFiltersSpan.classList.remove("hidden");
1196
+ } else {
1197
+ activeFiltersSpan.classList.add("hidden");
1198
+ }
1199
+ }
1200
 
1201
+ // Improved handleFilterChange function
1202
+ function handleFilterChange(tab) {
1203
+ const minLikesInput = document.getElementById(`${tab}MinLikes`);
1204
+ const minDownloadsInput = document.getElementById(`${tab}MinDownloads`);
1205
+ const paramRangeSelect = document.getElementById(
1206
+ `${tab}ParamRangeSelect`
1207
+ );
1208
+ const sortSelect = document.getElementById(`${tab}SortSelect`);
1209
+ const activeFiltersSpan = document.getElementById(
1210
+ `${tab}ActiveFilters`
1211
+ );
1212
 
1213
+ // Update the global filter values
1214
+ currentMinLikes = parseInt(minLikesInput.value) || 0;
1215
+ currentMinDownloads = parseInt(minDownloadsInput.value) || 0;
1216
 
1217
+ // Update sort value from the select element
1218
+ if (sortSelect) {
1219
+ currentSort = sortSelect.value;
 
 
 
 
1220
  }
 
1221
 
1222
+ // Update parameter filter if we're on models and sync between tabs
1223
+ if (currentType === "models" && paramRangeSelect) {
1224
+ currentParamFilter = paramRangeSelect.value;
1225
+ syncParamFilterValues();
 
 
 
 
 
 
 
1226
  }
1227
 
1228
+ // Update active filters indicator
1229
+ let activeCount =
1230
+ (currentMinLikes > 0 ? 1 : 0) +
1231
+ (currentMinDownloads > 0 ? 1 : 0) +
1232
+ (currentType === "models" && currentParamFilter !== "any" ? 1 : 0);
1233
 
1234
+ if (activeCount > 0) {
1235
+ activeFiltersSpan.textContent = activeCount;
1236
+ activeFiltersSpan.classList.remove("hidden");
1237
+ } else {
1238
+ activeFiltersSpan.classList.add("hidden");
1239
+ }
 
 
 
 
 
 
 
 
 
1240
 
1241
+ // Re-run the current search with new filters
1242
+ if (tab === "search") {
1243
+ const searchQuery = document.getElementById("searchInput").value;
1244
+ if (searchQuery.length >= MIN_SEARCH_LENGTH) {
1245
+ currentPage = 1;
1246
+ performSearch(searchQuery, 1);
1247
  }
1248
+ } else {
1249
+ const resourceId = document.getElementById("resourceInput").value;
1250
+ if (resourceId) {
1251
+ currentPage = 1;
1252
+ findSimilarResources(1);
1253
+ }
1254
+ }
1255
+ }
1256
 
1257
+ // Add this function to create a parameter filter UI above the results
1258
+ function updateParamFilterUI() {
1259
+ // Check if we should show the filter (only for models)
1260
+ if (currentType !== "models") {
1261
+ // Remove the filter UI if it exists
1262
+ const existingFilter = document.getElementById("paramFilterBar");
1263
+ if (existingFilter) {
1264
+ existingFilter.remove();
1265
+ }
1266
+ return;
1267
  }
1268
 
1269
+ // Check if we already have the filter UI
1270
+ let paramFilterBar = document.getElementById("paramFilterBar");
1271
+
1272
+ // If not, create it
1273
+ if (!paramFilterBar) {
1274
+ paramFilterBar = document.createElement("div");
1275
+ paramFilterBar.id = "paramFilterBar";
1276
+ paramFilterBar.className =
1277
+ "bg-white p-4 rounded-lg shadow-sm border border-gray-100 mb-4";
1278
+
1279
+ // Create the filter content
1280
+ paramFilterBar.innerHTML = `
1281
+ <div class="flex flex-col gap-2">
1282
+ <div class="flex items-center justify-between">
1283
+ <h3 class="text-sm font-medium text-gray-700">Filter by Parameter Size</h3>
1284
+ <button onclick="clearParamFilter()" class="text-xs text-blue-500 hover:text-blue-700">Clear</button>
1285
+ </div>
1286
+ <div class="flex flex-wrap gap-2 mt-1" id="paramFilterButtons">
1287
+ <button onclick="applyParamFilter('any')" class="filter-btn ${
1288
+ currentParamFilter === "any" ? "active" : ""
1289
+ }">
1290
+ Any size
1291
+ </button>
1292
+ <button onclick="applyParamFilter('0-1')" class="filter-btn ${
1293
+ currentParamFilter === "0-1" ? "active" : ""
1294
+ }">
1295
+ Small (0-1B)
1296
+ </button>
1297
+ <button onclick="applyParamFilter('1-7')" class="filter-btn ${
1298
+ currentParamFilter === "1-7" ? "active" : ""
1299
+ }">
1300
+ Medium (1-7B)
1301
+ </button>
1302
+ <button onclick="applyParamFilter('7-20')" class="filter-btn ${
1303
+ currentParamFilter === "7-20" ? "active" : ""
1304
+ }">
1305
+ Large (7-20B)
1306
+ </button>
1307
+ <button onclick="applyParamFilter('20-70')" class="filter-btn ${
1308
+ currentParamFilter === "20-70" ? "active" : ""
1309
+ }">
1310
+ XL (20-70B)
1311
+ </button>
1312
+ <button onclick="applyParamFilter('70+')" class="filter-btn ${
1313
+ currentParamFilter === "70+" ? "active" : ""
1314
+ }">
1315
+ XXL (70B+)
1316
+ </button>
1317
+ </div>
1318
+ </div>
1319
+ `;
1320
 
1321
+ // Insert it before the results container
1322
+ const resultsContainer = document.getElementById("resultsContainer");
1323
+ resultsContainer.parentNode.insertBefore(
1324
+ paramFilterBar,
1325
+ resultsContainer
1326
+ );
1327
 
1328
+ // Add CSS for the filter buttons
1329
+ const style = document.createElement("style");
1330
+ style.textContent = `
1331
+ .filter-btn {
1332
+ padding: 6px 12px;
1333
+ border-radius: 4px;
1334
+ font-size: 0.875rem;
1335
+ background-color: #f3f4f6;
1336
+ color: #4b5563;
1337
+ transition: all 0.2s;
1338
+ }
1339
+ .filter-btn:hover {
1340
+ background-color: #e5e7eb;
1341
+ }
1342
+ .filter-btn.active {
1343
+ background-color: #3b82f6;
1344
+ color: white;
1345
+ }
1346
+ `;
1347
+ document.head.appendChild(style);
1348
+ } else {
1349
+ // Just update the active state on existing buttons
1350
+ const buttons = paramFilterBar.querySelectorAll(".filter-btn");
1351
+ buttons.forEach((btn) => {
1352
+ btn.classList.remove("active");
1353
+ const filterValue = btn.getAttribute("onclick").match(/'(.*?)'/)[1];
1354
+ if (filterValue === currentParamFilter) {
1355
+ btn.classList.add("active");
1356
+ }
1357
+ });
1358
  }
1359
+ }
1360
 
1361
+ // Function to apply parameter filter
1362
+ function applyParamFilter(value) {
1363
+ // Update the filter value
1364
+ currentParamFilter = value;
1365
 
1366
+ // Update the filter UI
1367
+ updateParamFilterUI();
1368
+
1369
+ // Update filter indicators
1370
+ updateFilterIndicators("search");
1371
+ updateFilterIndicators("similar");
1372
+
1373
+ // Re-run the current search
1374
  const activeTab = document.querySelector(".tab-trigger.active").id;
1375
+
1376
  if (activeTab === "searchTab") {
1377
  const searchQuery = document.getElementById("searchInput").value;
1378
  if (searchQuery.length >= MIN_SEARCH_LENGTH) {
1379
+ currentPage = 1;
1380
+ performSearch(searchQuery, 1);
1381
  }
1382
+ } else if (activeTab === "similarTab") {
1383
  const resourceId = document.getElementById("resourceInput").value;
1384
  if (resourceId) {
1385
+ currentPage = 1;
1386
  findSimilarResources(1);
1387
  }
1388
+ } else if (activeTab === "trendingTab") {
1389
+ loadTrendingResources(currentType);
1390
  }
1391
  }
1392
 
1393
+ // Function to clear parameter filter
1394
+ function clearParamFilter() {
1395
+ applyParamFilter("any");
 
 
 
 
 
 
 
1396
  }
1397
 
1398
+ // Add this to initialize the UI when the page loads
1399
+ document.addEventListener("DOMContentLoaded", function () {
1400
+ // Set initial type selection
1401
+ selectType(INITIAL_TYPE);
 
 
 
 
 
 
1402
 
1403
+ // Initialize parameter filter UI
1404
+ updateParamFilterUI();
 
 
 
 
 
1405
 
1406
+ // Switch to the initial tab from URL
1407
+ switchTab(INITIAL_TAB);
 
 
 
 
 
1408
 
1409
+ // Initialize with search query if present in URL
1410
+ if (INITIAL_SEARCH && INITIAL_SEARCH.length >= MIN_SEARCH_LENGTH) {
1411
+ document.getElementById("searchInput").value = INITIAL_SEARCH;
1412
+ performSearch(INITIAL_SEARCH, 1);
1413
+ }
1414
 
1415
+ // Initialize with similar resource if present in URL
1416
+ if (INITIAL_SIMILAR) {
1417
+ document.getElementById("resourceInput").value = INITIAL_SIMILAR;
1418
+ findSimilarResources(1);
 
 
 
 
1419
  }
1420
 
1421
+ // Add input event listener for resource suggestions
1422
+ document
1423
+ .getElementById("resourceInput")
1424
+ .addEventListener("input", function (e) {
1425
+ const query = e.target.value.trim();
1426
+ if (query.length >= 2) {
1427
+ fetchResourceSuggestions(query);
1428
+ } else {
1429
+ document.getElementById("suggestionsBox").classList.add("hidden");
1430
+ }
1431
+ });
1432
+
1433
+ // Close suggestions when clicking outside
1434
+ document.addEventListener("click", function (e) {
1435
+ if (
1436
+ !e.target.closest("#resourceInput") &&
1437
+ !e.target.closest("#suggestionsBox")
1438
+ ) {
1439
+ document.getElementById("suggestionsBox").classList.add("hidden");
1440
  }
1441
+ });
1442
+ });
1443
+
1444
+ // Add function to fetch resource suggestions
1445
+ async function fetchResourceSuggestions(query) {
1446
+ if (query.length < 2) return;
1447
+
1448
+ try {
1449
+ const response = await fetch(
1450
+ `${API_URL}/suggest/${currentType}?q=${encodeURIComponent(query)}`
1451
+ );
1452
+ if (!response.ok) throw new Error("Failed to fetch suggestions");
1453
+
1454
+ const data = await response.json();
1455
+ if (data && data.suggestions && data.suggestions.length > 0) {
1456
+ displaySuggestions(
1457
+ data.suggestions,
1458
+ document.getElementById("suggestionsBox")
1459
+ );
1460
+ } else {
1461
+ document.getElementById("suggestionsBox").classList.add("hidden");
1462
  }
1463
+ } catch (error) {
1464
+ console.error("Error fetching suggestions:", error);
1465
+ document.getElementById("suggestionsBox").classList.add("hidden");
1466
  }
1467
  }
1468
  </script>