dylanebert commited on
Commit
7ceb966
·
1 Parent(s): d3a497e

add topology voting support

Browse files
src/routes/+page.svelte CHANGED
@@ -22,6 +22,7 @@
22
  let showOnlyOpenSource = false;
23
  let showFilter = false;
24
  let filterContainer: HTMLDivElement;
 
25
 
26
  $: apiConfig = getApiConfig();
27
  $: isDevelopment = dev || apiConfig.isLocal;
@@ -89,6 +90,35 @@
89
  </button>
90
  {#if showFilter}
91
  <div class="filter-dropdown" role="menu" aria-label="Filter options">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  <div class="filter-section">
93
  <div class="filter-section-title">Filter Options</div>
94
  <div
@@ -114,7 +144,7 @@
114
  {/if}
115
 
116
  {#if currentView === "Leaderboard"}
117
- <Leaderboard onEntryClick={showModelDetails} {showOnlyOpenSource} />
118
  {:else if currentView === "Vote"}
119
  <Vote />
120
  {:else if currentView === "ModelDetails" && selectedEntry}
 
22
  let showOnlyOpenSource = false;
23
  let showFilter = false;
24
  let filterContainer: HTMLDivElement;
25
+ let voteType: 'render' | 'topology' = 'render';
26
 
27
  $: apiConfig = getApiConfig();
28
  $: isDevelopment = dev || apiConfig.isLocal;
 
90
  </button>
91
  {#if showFilter}
92
  <div class="filter-dropdown" role="menu" aria-label="Filter options">
93
+ <div class="filter-section">
94
+ <div class="filter-section-title">Vote Type</div>
95
+ <div
96
+ class="filter-option {voteType === 'render' ? 'active' : ''}"
97
+ on:click={() => (voteType = 'render')}
98
+ on:keydown={(e) => e.key === "Enter" && (voteType = 'render')}
99
+ role="menuitemradio"
100
+ aria-checked={voteType === 'render'}
101
+ tabindex="0"
102
+ >
103
+ <div class="filter-label">
104
+ Render Quality
105
+ </div>
106
+ <span class="filter-checkbox">✓</span>
107
+ </div>
108
+ <div
109
+ class="filter-option {voteType === 'topology' ? 'active' : ''}"
110
+ on:click={() => (voteType = 'topology')}
111
+ on:keydown={(e) => e.key === "Enter" && (voteType = 'topology')}
112
+ role="menuitemradio"
113
+ aria-checked={voteType === 'topology'}
114
+ tabindex="0"
115
+ >
116
+ <div class="filter-label">
117
+ Topology
118
+ </div>
119
+ <span class="filter-checkbox">✓</span>
120
+ </div>
121
+ </div>
122
  <div class="filter-section">
123
  <div class="filter-section-title">Filter Options</div>
124
  <div
 
144
  {/if}
145
 
146
  {#if currentView === "Leaderboard"}
147
+ <Leaderboard onEntryClick={showModelDetails} {showOnlyOpenSource} {voteType} />
148
  {:else if currentView === "Vote"}
149
  <Vote />
150
  {:else if currentView === "ModelDetails" && selectedEntry}
src/routes/Leaderboard.svelte CHANGED
@@ -14,13 +14,14 @@
14
 
15
  export let onEntryClick: (entry: Entry) => void;
16
  export let showOnlyOpenSource: boolean;
 
17
 
18
  const baseUrl = "https://huggingface.co/datasets/dylanebert/3d-arena/resolve/main/outputs";
19
  let leaderboard: Entry[] = [];
20
  let filteredLeaderboard: Entry[] = [];
21
 
22
  const fetchLeaderboardData = async () => {
23
- const url = "/api/leaderboard";
24
  const response = await fetch(url, {
25
  method: "GET",
26
  headers: {
@@ -49,6 +50,11 @@
49
  updateFilteredLeaderboard();
50
  }
51
 
 
 
 
 
 
52
  onMount(async () => {
53
  await fetchLeaderboardData();
54
  });
 
14
 
15
  export let onEntryClick: (entry: Entry) => void;
16
  export let showOnlyOpenSource: boolean;
17
+ export let voteType: string = 'render';
18
 
19
  const baseUrl = "https://huggingface.co/datasets/dylanebert/3d-arena/resolve/main/outputs";
20
  let leaderboard: Entry[] = [];
21
  let filteredLeaderboard: Entry[] = [];
22
 
23
  const fetchLeaderboardData = async () => {
24
+ const url = `/api/leaderboard?vote_type=${voteType}`;
25
  const response = await fetch(url, {
26
  method: "GET",
27
  headers: {
 
50
  updateFilteredLeaderboard();
51
  }
52
 
53
+ $: {
54
+ voteType;
55
+ fetchLeaderboardData();
56
+ }
57
+
58
  onMount(async () => {
59
  await fetchLeaderboardData();
60
  });
src/routes/Vote.svelte CHANGED
@@ -14,6 +14,7 @@
14
  model2: string;
15
  model2_path: string;
16
  model2_displayName: string;
 
17
  }
18
 
19
  let viewerA: IViewer;
@@ -88,15 +89,21 @@
88
  data.model2_displayName = config2.DisplayName || data.model2;
89
 
90
  try {
 
91
  [viewerA, viewerB] = await Promise.all([
92
  createViewer(model1_path, canvasA, (progress) => {
93
  loadingBarFillA.style.width = `${progress * 100}%`;
94
- }),
95
  createViewer(model2_path, canvasB, (progress) => {
96
  loadingBarFillB.style.width = `${progress * 100}%`;
97
- }),
98
  ]);
99
 
 
 
 
 
 
100
  window.addEventListener("resize", handleResize);
101
  handleResize();
102
  } catch (error) {
@@ -122,6 +129,7 @@
122
  input: data.input,
123
  better: option == "A" ? data.model1 : data.model2,
124
  worse: option == "A" ? data.model2 : data.model1,
 
125
  };
126
  const url = `/api/vote`;
127
 
@@ -210,7 +218,11 @@
210
  />
211
  </div>
212
  <h2 class="center-title">Which is better?</h2>
213
- <p class="center-subtitle">Use mouse/touch to change the view.</p>
 
 
 
 
214
  <div class="voting-container">
215
  <div bind:this={containerA} class="canvas-container">
216
  <div bind:this={overlayA} class="loading-overlay">
@@ -225,7 +237,7 @@
225
  <p>vertex count: {viewerA.vertexCount}</p>
226
  {/if}
227
  </div>
228
- {#if viewerA && !viewerA.topoOnly}
229
  <div class="mode-toggle">
230
  <label>
231
  <input
@@ -264,7 +276,7 @@
264
  <p>vertex count: {viewerB.vertexCount}</p>
265
  {/if}
266
  </div>
267
- {#if viewerB && !viewerB.topoOnly}
268
  <div class="mode-toggle">
269
  <label>
270
  <input
 
14
  model2: string;
15
  model2_path: string;
16
  model2_displayName: string;
17
+ vote_type: string;
18
  }
19
 
20
  let viewerA: IViewer;
 
89
  data.model2_displayName = config2.DisplayName || data.model2;
90
 
91
  try {
92
+ const isTopology = data.vote_type === "topology";
93
  [viewerA, viewerB] = await Promise.all([
94
  createViewer(model1_path, canvasA, (progress) => {
95
  loadingBarFillA.style.width = `${progress * 100}%`;
96
+ }, isTopology),
97
  createViewer(model2_path, canvasB, (progress) => {
98
  loadingBarFillB.style.width = `${progress * 100}%`;
99
+ }, isTopology),
100
  ]);
101
 
102
+ if (isTopology) {
103
+ viewerA.setRenderMode("wireframe");
104
+ viewerB.setRenderMode("wireframe");
105
+ }
106
+
107
  window.addEventListener("resize", handleResize);
108
  handleResize();
109
  } catch (error) {
 
129
  input: data.input,
130
  better: option == "A" ? data.model1 : data.model2,
131
  worse: option == "A" ? data.model2 : data.model1,
132
+ vote_type: data.vote_type,
133
  };
134
  const url = `/api/vote`;
135
 
 
218
  />
219
  </div>
220
  <h2 class="center-title">Which is better?</h2>
221
+ {#if data.vote_type === "topology"}
222
+ <p class="center-subtitle">Topology comparison: wireframe view only</p>
223
+ {:else}
224
+ <p class="center-subtitle">Use mouse/touch to change the view.</p>
225
+ {/if}
226
  <div class="voting-container">
227
  <div bind:this={containerA} class="canvas-container">
228
  <div bind:this={overlayA} class="loading-overlay">
 
237
  <p>vertex count: {viewerA.vertexCount}</p>
238
  {/if}
239
  </div>
240
+ {#if viewerA && !viewerA.topoOnly && data.vote_type !== "topology"}
241
  <div class="mode-toggle">
242
  <label>
243
  <input
 
276
  <p>vertex count: {viewerB.vertexCount}</p>
277
  {/if}
278
  </div>
279
+ {#if viewerB && !viewerB.topoOnly && data.vote_type !== "topology"}
280
  <div class="mode-toggle">
281
  <label>
282
  <input
src/routes/api/leaderboard/+server.ts CHANGED
@@ -1,10 +1,11 @@
1
  import { getApiEndpoint, ApiEndpoints } from "$lib/config";
2
 
3
- export const GET = async () => {
4
- const url = getApiEndpoint(ApiEndpoints.LEADERBOARD);
 
5
 
6
  try {
7
- const response = await fetch(url, {
8
  method: "GET",
9
  headers: {
10
  Authorization: `Bearer ${import.meta.env.VITE_HF_TOKEN}`,
 
1
  import { getApiEndpoint, ApiEndpoints } from "$lib/config";
2
 
3
+ export const GET = async ({ url }) => {
4
+ const voteType = url.searchParams.get('vote_type') || 'render';
5
+ const apiUrl = `${getApiEndpoint(ApiEndpoints.LEADERBOARD)}?vote_type=${voteType}`;
6
 
7
  try {
8
+ const response = await fetch(apiUrl, {
9
  method: "GET",
10
  headers: {
11
  Authorization: `Bearer ${import.meta.env.VITE_HF_TOKEN}`,
src/routes/viewers/ViewerFactory.ts CHANGED
@@ -8,7 +8,8 @@ const splatFormats = ["splat", "ply"];
8
  export async function createViewer(
9
  url: string,
10
  canvas: HTMLCanvasElement,
11
- onProgress: (progress: number) => void
 
12
  ): Promise<IViewer> {
13
  let viewer: IViewer;
14
  if (meshFormats.some((format) => url.endsWith(format))) {
@@ -18,7 +19,7 @@ export async function createViewer(
18
  } else {
19
  throw new Error("Unsupported file format");
20
  }
21
- const topoOnly: boolean = url.endsWith(".obj");
22
  await viewer.loadScene(url, onProgress, topoOnly);
23
  return viewer;
24
  }
 
8
  export async function createViewer(
9
  url: string,
10
  canvas: HTMLCanvasElement,
11
+ onProgress: (progress: number) => void,
12
+ forceTopoOnly: boolean = false
13
  ): Promise<IViewer> {
14
  let viewer: IViewer;
15
  if (meshFormats.some((format) => url.endsWith(format))) {
 
19
  } else {
20
  throw new Error("Unsupported file format");
21
  }
22
+ const topoOnly: boolean = forceTopoOnly || url.endsWith(".obj");
23
  await viewer.loadScene(url, onProgress, topoOnly);
24
  return viewer;
25
  }