|
<script lang="ts"> |
|
import { onMount } from 'svelte'; |
|
import type { Fields, PipelineInfo } from '$lib/types'; |
|
import { PipelineMode } from '$lib/types'; |
|
import ImagePlayer from '$lib/components/ImagePlayer.svelte'; |
|
import VideoInput from '$lib/components/VideoInput.svelte'; |
|
import Button from '$lib/components/Button.svelte'; |
|
import PipelineOptions from '$lib/components/PipelineOptions.svelte'; |
|
import Spinner from '$lib/icons/spinner.svelte'; |
|
import Warning from '$lib/components/Warning.svelte'; |
|
import { lcmLiveStatus, lcmLiveActions, LCMLiveStatus } from '$lib/lcmLive'; |
|
import { mediaStreamActions, onFrameChangeStore } from '$lib/mediaStream'; |
|
import { getPipelineValues, deboucedPipelineValues } from '$lib/store'; |
|
|
|
let pipelineParams: Fields; |
|
let pipelineInfo: PipelineInfo; |
|
let pageContent: string; |
|
let isImageMode: boolean = false; |
|
let maxQueueSize: number = 0; |
|
let currentQueueSize: number = 0; |
|
let queueCheckerRunning: boolean = false; |
|
let warningMessage: string = ''; |
|
onMount(() => { |
|
getSettings(); |
|
}); |
|
|
|
async function getSettings() { |
|
const settings = await fetch('/api/settings').then((r) => r.json()); |
|
pipelineParams = settings.input_params.properties; |
|
pipelineInfo = settings.info.properties; |
|
isImageMode = pipelineInfo.input_mode.default === PipelineMode.IMAGE; |
|
maxQueueSize = settings.max_queue_size; |
|
pageContent = settings.page_content; |
|
console.log(pipelineParams); |
|
toggleQueueChecker(true); |
|
} |
|
function toggleQueueChecker(start: boolean) { |
|
queueCheckerRunning = start && maxQueueSize > 0; |
|
if (start) { |
|
getQueueSize(); |
|
} |
|
} |
|
async function getQueueSize() { |
|
if (!queueCheckerRunning) { |
|
return; |
|
} |
|
const data = await fetch('/api/queue').then((r) => r.json()); |
|
currentQueueSize = data.queue_size; |
|
setTimeout(getQueueSize, 10000); |
|
} |
|
|
|
function getSreamdata() { |
|
if (isImageMode) { |
|
return [getPipelineValues(), $onFrameChangeStore?.blob]; |
|
} else { |
|
return [$deboucedPipelineValues]; |
|
} |
|
} |
|
|
|
$: isLCMRunning = |
|
$lcmLiveStatus !== LCMLiveStatus.DISCONNECTED && $lcmLiveStatus !== LCMLiveStatus.ERROR; |
|
$: isConnecting = $lcmLiveStatus === LCMLiveStatus.CONNECTING; |
|
|
|
$: { |
|
// Set warning messages based on lcmLiveStatus |
|
if ($lcmLiveStatus === LCMLiveStatus.TIMEOUT) { |
|
warningMessage = 'Session timed out. Please try again.'; |
|
} else if ($lcmLiveStatus === LCMLiveStatus.ERROR) { |
|
warningMessage = 'Connection error occurred. Please try again.'; |
|
} |
|
} |
|
let disabled = false; |
|
async function toggleLcmLive() { |
|
try { |
|
if (!isLCMRunning) { |
|
if (isConnecting) { |
|
return; // Don't allow multiple connection attempts |
|
} |
|
|
|
|
|
warningMessage = ''; |
|
disabled = true; |
|
|
|
try { |
|
if (isImageMode) { |
|
await mediaStreamActions.enumerateDevices(); |
|
await mediaStreamActions.start(); |
|
} |
|
|
|
await lcmLiveActions.start(getSreamdata); |
|
toggleQueueChecker(false); |
|
} finally { |
|
// Always re-enable the button even if there was an error |
|
disabled = false; |
|
} |
|
} else { |
|
// Handle stopping - disable button during this process too |
|
disabled = true; |
|
|
|
try { |
|
if (isImageMode) { |
|
mediaStreamActions.stop(); |
|
} |
|
await lcmLiveActions.stop(); |
|
toggleQueueChecker(true); |
|
} finally { |
|
disabled = false; |
|
} |
|
} |
|
} catch (e) { |
|
console.error('Error in toggleLcmLive:', e); |
|
warningMessage = e instanceof Error ? e.message : 'An unknown error occurred'; |
|
disabled = false; |
|
toggleQueueChecker(true); |
|
} |
|
} |
|
|
|
|
|
async function reconnect() { |
|
try { |
|
disabled = true; |
|
warningMessage = 'Reconnecting...'; |
|
|
|
if (isImageMode) { |
|
await mediaStreamActions.stop(); |
|
await mediaStreamActions.enumerateDevices(); |
|
await mediaStreamActions.start(); |
|
} |
|
|
|
await lcmLiveActions.reconnect(getSreamdata); |
|
warningMessage = ''; |
|
toggleQueueChecker(false); |
|
} catch (e) { |
|
warningMessage = e instanceof Error ? e.message : 'Reconnection failed'; |
|
toggleQueueChecker(true); |
|
} finally { |
|
disabled = false; |
|
} |
|
} |
|
</script> |
|
|
|
<svelte:head> |
|
<script |
|
src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.9/iframeResizer.contentWindow.min.js" |
|
></script> |
|
</svelte:head> |
|
|
|
<main class="container mx-auto flex max-w-5xl flex-col gap-3 px-4 py-4"> |
|
<Warning bind:message={warningMessage}></Warning> |
|
<article class="text-center"> |
|
{#if pageContent} |
|
{@html pageContent} |
|
{/if} |
|
{#if maxQueueSize > 0} |
|
<p class="text-sm"> |
|
There are <span id="queue_size" class="font-bold">{currentQueueSize}</span> |
|
user(s) sharing the same GPU, affecting real-time performance. Maximum queue size is {maxQueueSize}. |
|
<a |
|
href="https://huggingface.co/spaces/radames/Real-Time-Latent-Consistency-Model?duplicate=true" |
|
target="_blank" |
|
class="text-blue-500 underline hover:no-underline">Duplicate</a |
|
> and run it on your own GPU. |
|
</p> |
|
{/if} |
|
|
|
{#if $lcmLiveStatus === LCMLiveStatus.ERROR} |
|
<p class="mt-2 text-sm"> |
|
<button class="text-blue-500 underline hover:no-underline" on:click={reconnect} {disabled}> |
|
Try reconnecting |
|
</button> |
|
</p> |
|
{/if} |
|
</article> |
|
{#if pipelineParams} |
|
<article class="my-3 grid grid-cols-1 gap-3 sm:grid-cols-4"> |
|
{#if isImageMode} |
|
<div class="col-span-2 sm:col-start-1"> |
|
<VideoInput |
|
width={Number(pipelineParams.width.default)} |
|
height={Number(pipelineParams.height.default)} |
|
></VideoInput> |
|
</div> |
|
{/if} |
|
<div class={isImageMode ? 'col-span-2 sm:col-start-3' : 'col-span-4'}> |
|
<ImagePlayer /> |
|
</div> |
|
<div class="sm:col-span-4 sm:row-start-2"> |
|
<Button on:click={toggleLcmLive} {disabled} classList={'text-lg my-1 p-2'}> |
|
{#if isConnecting} |
|
Connecting... |
|
{:else if isLCMRunning} |
|
Stop |
|
{:else} |
|
Start |
|
{/if} |
|
</Button> |
|
<PipelineOptions {pipelineParams}></PipelineOptions> |
|
</div> |
|
</article> |
|
{:else} |
|
<!-- loading --> |
|
<div class="flex items-center justify-center gap-3 py-48 text-2xl"> |
|
<Spinner classList={'animate-spin opacity-50'}></Spinner> |
|
<p>Loading...</p> |
|
</div> |
|
{/if} |
|
</main> |
|
|
|
<style lang="postcss"> |
|
:global(html) { |
|
@apply text-black dark:bg-gray-900 dark:text-white; |
|
} |
|
</style> |
|
|