| <script lang="ts"> | |
| import { onMount, onDestroy } from "svelte"; | |
| import { v4 as uuidv4 } from "uuid"; | |
| import type { IViewer } from "./viewers/IViewer"; | |
| import { createViewer } from "./viewers/ViewerFactory"; | |
| interface Data { | |
| input: string; | |
| input_path: string; | |
| model1: string; | |
| model1_path: string; | |
| model2: string; | |
| model2_path: string; | |
| } | |
| let viewerA: IViewer; | |
| let viewerB: IViewer; | |
| let canvasA: HTMLCanvasElement; | |
| let canvasB: HTMLCanvasElement; | |
| let containerA: HTMLDivElement; | |
| let containerB: HTMLDivElement; | |
| let overlayA: HTMLDivElement; | |
| let overlayB: HTMLDivElement; | |
| let loadingBarFillA: HTMLDivElement; | |
| let loadingBarFillB: HTMLDivElement; | |
| let statusMessage: string = "Loading..."; | |
| let errorMessage: string = ""; | |
| let data: Data; | |
| function getUsername() { | |
| let storedUsername = sessionStorage.getItem("username"); | |
| if (!storedUsername) { | |
| storedUsername = uuidv4(); | |
| sessionStorage.setItem("username", storedUsername); | |
| } | |
| return storedUsername; | |
| } | |
| async function fetchScenes() { | |
| statusMessage = "Loading..."; | |
| errorMessage = ""; | |
| try { | |
| const username = getUsername(); | |
| console.log(`Fetching with username: ${username}`); | |
| const url = `https://dylanebert-3d-arena-backend.hf.space/pair?username=${username}`; | |
| const response = await fetch(url, { | |
| method: "GET", | |
| headers: { | |
| Authorization: "Bearer " + import.meta.env.VITE_HF_TOKEN, | |
| "Cache-Control": "no-cache", | |
| }, | |
| }); | |
| const result = await response.json(); | |
| if (result.input) { | |
| data = result; | |
| statusMessage = ""; | |
| return true; | |
| } else { | |
| statusMessage = "Voting complete."; | |
| return false; | |
| } | |
| } catch (error) { | |
| errorMessage = "Failed to fetch pair."; | |
| statusMessage = ""; | |
| return false; | |
| } | |
| } | |
| async function loadScenes() { | |
| const success = await fetchScenes(); | |
| if (!success) return; | |
| overlayA.style.display = "flex"; | |
| overlayB.style.display = "flex"; | |
| const baseUrl = "https://huggingface.co/datasets/dylanebert/3d-arena/resolve/main/"; | |
| const model1_path = `${baseUrl}${data.model1_path}`; | |
| const model2_path = `${baseUrl}${data.model2_path}`; | |
| try { | |
| const promises = [ | |
| createViewer(model1_path, canvasA, (progress) => { | |
| loadingBarFillA.style.width = `${progress * 100}%`; | |
| }), | |
| createViewer(model2_path, canvasB, (progress) => { | |
| loadingBarFillB.style.width = `${progress * 100}%`; | |
| }), | |
| ]; | |
| await Promise.all(promises); | |
| window.addEventListener("resize", handleResize); | |
| handleResize(); | |
| } catch (error) { | |
| errorMessage = "Failed to load scenes."; | |
| } | |
| overlayA.style.display = "none"; | |
| overlayB.style.display = "none"; | |
| } | |
| async function vote(option: "A" | "B") { | |
| statusMessage = "Processing vote..."; | |
| errorMessage = ""; | |
| const payload = { | |
| username: "dylanebert", | |
| input: data.input, | |
| better: option == "A" ? data.model1 : data.model2, | |
| worse: option == "A" ? data.model2 : data.model1, | |
| }; | |
| const url = `https://dylanebert-3d-arena-backend.hf.space/vote`; | |
| try { | |
| const response = await fetch(url, { | |
| method: "POST", | |
| headers: { | |
| Authorization: "Bearer " + import.meta.env.VITE_HF_TOKEN, | |
| "Cache-Control": "no-cache", | |
| "Content-Type": "application/json", | |
| }, | |
| body: JSON.stringify(payload), | |
| }); | |
| if (response.ok) { | |
| const result = await response.json(); | |
| console.log(result); | |
| loadScenes(); | |
| } else { | |
| errorMessage = "Failed to process vote."; | |
| } | |
| } catch (error) { | |
| errorMessage = "Failed to process vote."; | |
| statusMessage = ""; | |
| } | |
| } | |
| function handleResize() { | |
| requestAnimationFrame(() => { | |
| if (canvasA && containerA) { | |
| const maxWidth = containerA.clientHeight; | |
| const maxHeight = containerA.clientWidth; | |
| canvasA.width = Math.min(containerA.clientWidth, maxWidth); | |
| canvasA.height = Math.min(containerA.clientHeight, maxHeight); | |
| } | |
| if (canvasB && containerB) { | |
| const maxWidth = containerB.clientHeight; | |
| const maxHeight = containerB.clientWidth; | |
| canvasB.width = Math.min(containerB.clientWidth, maxWidth); | |
| canvasB.height = Math.min(containerB.clientHeight, maxHeight); | |
| } | |
| }); | |
| } | |
| onMount(loadScenes); | |
| onDestroy(() => { | |
| viewerA?.dispose(); | |
| viewerB?.dispose(); | |
| if (typeof window !== "undefined") { | |
| window.removeEventListener("resize", handleResize); | |
| } | |
| }); | |
| </script> | |
| {#if errorMessage} | |
| <p class="center-title muted" style="color: red;">{errorMessage}</p> | |
| {:else if statusMessage} | |
| <p class="center-title muted">{statusMessage}</p> | |
| {:else} | |
| <h2 class="center-title">Which is better?</h2> | |
| <div class="voting-container"> | |
| <div bind:this={containerA} class="voting-canvas-wrapper"> | |
| <div bind:this={overlayA} class="loading-overlay"> | |
| <div class="loading-bar"> | |
| <div bind:this={loadingBarFillA} class="loading-bar-fill" /> | |
| </div> | |
| </div> | |
| <canvas bind:this={canvasA} class="voting-canvas" id="canvas1"></canvas> | |
| <button class="vote-button" on:click={() => vote("A")}>A is Better</button> | |
| </div> | |
| <div bind:this={containerB} class="voting-canvas-wrapper"> | |
| <div bind:this={overlayB} class="loading-overlay"> | |
| <div class="loading-bar"> | |
| <div bind:this={loadingBarFillB} class="loading-bar-fill" /> | |
| </div> | |
| </div> | |
| <canvas bind:this={canvasB} class="voting-canvas" id="canvas2"></canvas> | |
| <button class="vote-button" on:click={() => vote("B")}>B is Better</button> | |
| </div> | |
| </div> | |
| {/if} | |