|
<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: getUsername(), |
|
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} |
|
|