Spaces:
Running
Running
<script lang="ts"> | |
import { onMount } from "svelte"; | |
import { ProgressBarRound, ArrowLeft } from "carbon-icons-svelte"; | |
interface Scene { | |
name: string; | |
url: string; | |
thumbnail: string; | |
} | |
interface Config { | |
Model: string; | |
Space: string; | |
Paper: string; | |
} | |
export let modelName: string; | |
export let onBack: () => void; | |
export let onSceneClick: (scene: Scene) => void; | |
let scenes: Scene[] = []; | |
let config: Config; | |
async function fetchScenes() { | |
scenes = []; | |
const url = `https://huggingface.co/api/datasets/dylanebert/3d-arena`; | |
const response = await fetch(url); | |
const responseData = await response.json(); | |
const directory = `outputs/${modelName}`; | |
const extensions = ["obj", "glb", "ply", "splat"]; | |
scenes = responseData.siblings | |
.filter((scene: any) => { | |
const fileExtension = scene.rfilename.split(".").pop(); | |
return scene.rfilename.startsWith(directory) && extensions.includes(fileExtension); | |
}) | |
.reduce((acc: Scene[], scene: any) => { | |
const name = scene.rfilename.split("/").pop().split(".").slice(0, -1).join("."); | |
const url = `https://huggingface.co/datasets/dylanebert/3d-arena/resolve/main/${scene.rfilename}`; | |
const thumbnail = url.replace(/\.[^.]+$/, ".png"); | |
acc.push({ name, url, thumbnail }); | |
return acc; | |
}, []); | |
const configUrl = `https://huggingface.co/datasets/dylanebert/3d-arena/resolve/main/outputs/${modelName}/config.json`; | |
const configResponse = await fetch(configUrl); | |
config = (await configResponse.json()) as Config; | |
scenes = [...scenes]; | |
} | |
function isValidUrl(url: string): boolean { | |
try { | |
new URL(url); | |
return true; | |
} catch (error) { | |
return false; | |
} | |
} | |
onMount(fetchScenes); | |
</script> | |
<div class="header"> | |
<div class="back" aria-label="Back" aria-hidden="true" on:click={onBack}> | |
<ArrowLeft size={24} /> | |
</div> | |
<div class="spacer" /> | |
<button class="title-button" on:click={fetchScenes}> | |
<h2 class="muted">{modelName}</h2> | |
</button> | |
<div class="desktop-spacer" /> | |
</div> | |
<div class="model-details"> | |
{#if config && (isValidUrl(config.Model) || isValidUrl(config.Space) || isValidUrl(config.Paper))} | |
<div class="config-container"> | |
{#if config.Model && isValidUrl(config.Model)} | |
<div class="config-item"> | |
<span class="config-label">Model:</span> | |
<a class="muted" href={config.Model} target="_blank"> | |
{config.Model.replace("https://huggingface.co/", "")} | |
</a> | |
</div> | |
{/if} | |
{#if config.Space && isValidUrl(config.Space)} | |
<div class="config-item"> | |
<span class="config-label">Space:</span> | |
<a class="muted" href={config.Space} target="_blank"> | |
{config.Space.replace("https://huggingface.co/spaces/", "")} | |
</a> | |
</div> | |
{/if} | |
{#if config.Paper && isValidUrl(config.Paper)} | |
<div class="config-item"> | |
<span class="config-label">Paper:</span> | |
<a class="muted" href={config.Paper} target="_blank"> | |
{config.Paper.replace("https://huggingface.co/papers/", "").replace( | |
"https://arxiv.org/abs/", | |
"" | |
)} | |
</a> | |
</div> | |
{/if} | |
</div> | |
{:else} | |
<div class="config-container"> | |
<div class="config-item"> | |
<span class="config-label">Model:</span> | |
<span class="muted">Coming Soon</span> | |
</div> | |
</div> | |
{/if} | |
{#if scenes.length > 0} | |
<div class="grid"> | |
{#each scenes as scene} | |
<button class="grid-item" on:click={() => onSceneClick(scene)}> | |
<img loading="lazy" src={scene.thumbnail} alt={scene.name} class="thumbnail" /> | |
<div class="title">{scene.name.length > 16 ? `${scene.name.slice(0, 16)}...` : scene.name}</div> | |
</button> | |
{/each} | |
</div> | |
{:else} | |
<div class="loading-container"> | |
<ProgressBarRound class="loading-icon" /> | |
<div class="loading-text">Loading...</div> | |
</div> | |
{/if} | |
</div> | |