|
<script context="module" lang="ts"> |
|
import { tick } from "svelte"; |
|
import { pretty_si } from "./utils"; |
|
|
|
let items: HTMLDivElement[] = []; |
|
|
|
let called = false; |
|
|
|
async function scroll_into_view( |
|
el: HTMLDivElement, |
|
enable: boolean | null = true |
|
): Promise<void> { |
|
if ( |
|
window.__gradio_mode__ === "website" || |
|
(window.__gradio_mode__ !== "app" && enable !== true) |
|
) { |
|
return; |
|
} |
|
|
|
items.push(el); |
|
if (!called) called = true; |
|
else return; |
|
|
|
await tick(); |
|
|
|
requestAnimationFrame(() => { |
|
let min = [0, 0]; |
|
|
|
for (let i = 0; i < items.length; i++) { |
|
const element = items[i]; |
|
|
|
const box = element.getBoundingClientRect(); |
|
if (i === 0 || box.top + window.scrollY <= min[0]) { |
|
min[0] = box.top + window.scrollY; |
|
min[1] = i; |
|
} |
|
} |
|
|
|
window.scrollTo({ top: min[0] - 20, behavior: "smooth" }); |
|
|
|
called = false; |
|
items = []; |
|
}); |
|
} |
|
</script> |
|
|
|
<script lang="ts"> |
|
import { onDestroy } from "svelte"; |
|
|
|
import Loader from "./Loader.svelte"; |
|
import type { LoadingStatus } from "./types"; |
|
import type { I18nFormatter } from "@gradio/utils"; |
|
|
|
export let i18n: I18nFormatter; |
|
export let eta: number | null = null; |
|
export let queue_position: number | null; |
|
export let queue_size: number | null; |
|
export let status: "complete" | "pending" | "error" | "generating"; |
|
export let scroll_to_output = false; |
|
export let timer = true; |
|
export let show_progress: "full" | "minimal" | "hidden" = "full"; |
|
export let message: string | null = null; |
|
export let progress: LoadingStatus["progress"] | null | undefined = null; |
|
export let variant: "default" | "center" = "default"; |
|
export let loading_text = "Loading..."; |
|
export let absolute = true; |
|
export let translucent = false; |
|
export let border = false; |
|
export let autoscroll: boolean; |
|
|
|
let el: HTMLDivElement; |
|
|
|
let _timer = false; |
|
let timer_start = 0; |
|
let timer_diff = 0; |
|
let old_eta: number | null = null; |
|
let eta_from_start: number | null = null; |
|
let message_visible = false; |
|
let eta_level: number | null = 0; |
|
let progress_level: (number | undefined)[] | null = null; |
|
let last_progress_level: number | undefined = undefined; |
|
let progress_bar: HTMLElement | null = null; |
|
let show_eta_bar = true; |
|
|
|
$: eta_level = |
|
eta_from_start === null || eta_from_start <= 0 || !timer_diff |
|
? null |
|
: Math.min(timer_diff / eta_from_start, 1); |
|
$: if (progress != null) { |
|
show_eta_bar = false; |
|
} |
|
|
|
$: { |
|
if (progress != null) { |
|
progress_level = progress.map((p) => { |
|
if (p.index != null && p.length != null) { |
|
return p.index / p.length; |
|
} else if (p.progress != null) { |
|
return p.progress; |
|
} |
|
return undefined; |
|
}); |
|
} else { |
|
progress_level = null; |
|
} |
|
|
|
if (progress_level) { |
|
last_progress_level = progress_level[progress_level.length - 1]; |
|
if (progress_bar) { |
|
if (last_progress_level === 0) { |
|
progress_bar.style.transition = "0"; |
|
} else { |
|
progress_bar.style.transition = "150ms"; |
|
} |
|
} |
|
} else { |
|
last_progress_level = undefined; |
|
} |
|
} |
|
|
|
const start_timer = (): void => { |
|
eta = old_eta = formatted_eta = null; |
|
timer_start = performance.now(); |
|
timer_diff = 0; |
|
_timer = true; |
|
run(); |
|
}; |
|
|
|
function run(): void { |
|
requestAnimationFrame(() => { |
|
timer_diff = (performance.now() - timer_start) / 1000; |
|
if (_timer) run(); |
|
}); |
|
} |
|
|
|
function stop_timer(): void { |
|
timer_diff = 0; |
|
eta = old_eta = formatted_eta = null; |
|
|
|
if (!_timer) return; |
|
_timer = false; |
|
} |
|
|
|
onDestroy(() => { |
|
if (_timer) stop_timer(); |
|
}); |
|
|
|
$: { |
|
if (status === "pending") { |
|
start_timer(); |
|
} else { |
|
stop_timer(); |
|
} |
|
} |
|
|
|
$: el && |
|
scroll_to_output && |
|
(status === "pending" || status === "complete") && |
|
scroll_into_view(el, autoscroll); |
|
|
|
let formatted_eta: string | null = null; |
|
$: { |
|
if (eta === null) { |
|
eta = old_eta; |
|
} |
|
if (eta != null && old_eta !== eta) { |
|
eta_from_start = (performance.now() - timer_start) / 1000 + eta; |
|
formatted_eta = eta_from_start.toFixed(1); |
|
old_eta = eta; |
|
} |
|
} |
|
let show_message_timeout: NodeJS.Timeout | null = null; |
|
function close_message(): void { |
|
message_visible = false; |
|
if (show_message_timeout !== null) { |
|
clearTimeout(show_message_timeout); |
|
} |
|
} |
|
$: { |
|
close_message(); |
|
if (status === "error" && message) { |
|
message_visible = true; |
|
} |
|
} |
|
$: formatted_timer = timer_diff.toFixed(1); |
|
</script> |
|
|
|
<div |
|
class="wrap {variant} {show_progress}" |
|
class:hide={!status || status === "complete" || show_progress === "hidden"} |
|
class:translucent={(variant === "center" && |
|
(status === "pending" || status === "error")) || |
|
translucent || |
|
show_progress === "minimal"} |
|
class:generating={status === "generating"} |
|
class:border |
|
style:position={absolute ? "absolute" : "static"} |
|
style:padding={absolute ? "0" : "var(--size-8) 0"} |
|
bind:this={el} |
|
> |
|
{#if status === "pending"} |
|
{#if variant === "default" && show_eta_bar && show_progress === "full"} |
|
<div |
|
class="eta-bar" |
|
style:transform="translateX({(eta_level || 0) * 100 - 100}%)" |
|
/> |
|
{/if} |
|
<div |
|
class:meta-text-center={variant === "center"} |
|
class:meta-text={variant === "default"} |
|
class="progress-text" |
|
> |
|
{#if progress} |
|
{#each progress as p} |
|
{#if p.index != null} |
|
{#if p.length != null} |
|
{pretty_si(p.index || 0)}/{pretty_si(p.length)} |
|
{:else} |
|
{pretty_si(p.index || 0)} |
|
{/if} |
|
{p.unit} | {" "} |
|
{/if} |
|
{/each} |
|
{:else if queue_position !== null && queue_size !== undefined && queue_position >= 0} |
|
queue: {queue_position + 1}/{queue_size} | |
|
{:else if queue_position === 0} |
|
processing | |
|
{/if} |
|
|
|
{#if timer} |
|
{formatted_timer}{eta ? `/${formatted_eta}` : ""}s |
|
{/if} |
|
</div> |
|
|
|
{#if last_progress_level != null} |
|
<div class="progress-level"> |
|
<div class="progress-level-inner"> |
|
{#if progress != null} |
|
{#each progress as p, i} |
|
{#if p.desc != null || (progress_level && progress_level[i] != null)} |
|
{#if i !== 0} |
|
/ |
|
{/if} |
|
{#if p.desc != null} |
|
{p.desc} |
|
{/if} |
|
{#if p.desc != null && progress_level && progress_level[i] != null} |
|
- |
|
{/if} |
|
{#if progress_level != null} |
|
{(100 * (progress_level[i] || 0)).toFixed(1)}% |
|
{/if} |
|
{/if} |
|
{/each} |
|
{/if} |
|
</div> |
|
|
|
<div class="progress-bar-wrap"> |
|
<div |
|
bind:this={progress_bar} |
|
class="progress-bar" |
|
style:width="{last_progress_level * 100}%" |
|
/> |
|
</div> |
|
</div> |
|
{:else if show_progress === "full"} |
|
<Loader margin={variant === "default"} /> |
|
{/if} |
|
|
|
{#if !timer} |
|
<p class="loading">{loading_text}</p> |
|
{/if} |
|
{:else if status === "error"} |
|
<span class="error">{i18n("common.error")}</span> |
|
<slot name="error" /> |
|
{/if} |
|
</div> |
|
|
|
<style> |
|
.wrap { |
|
display: flex; |
|
flex-direction: column; |
|
justify-content: center; |
|
align-items: center; |
|
z-index: var(--layer-top); |
|
transition: opacity 0.1s ease-in-out; |
|
border-radius: var(--block-radius); |
|
background: var(--block-background-fill); |
|
padding: 0 var(--size-6); |
|
max-height: var(--size-screen-h); |
|
overflow: hidden; |
|
pointer-events: none; |
|
} |
|
|
|
.wrap.center { |
|
top: 0; |
|
right: 0px; |
|
left: 0px; |
|
} |
|
|
|
.wrap.default { |
|
top: 0px; |
|
right: 0px; |
|
bottom: 0px; |
|
left: 0px; |
|
} |
|
|
|
.hide { |
|
opacity: 0; |
|
pointer-events: none; |
|
} |
|
|
|
.generating { |
|
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; |
|
border: 2px solid var(--color-accent); |
|
background: transparent; |
|
} |
|
|
|
.translucent { |
|
background: none; |
|
} |
|
|
|
@keyframes pulse { |
|
0%, |
|
100% { |
|
opacity: 1; |
|
} |
|
50% { |
|
opacity: 0.5; |
|
} |
|
} |
|
|
|
.loading { |
|
z-index: var(--layer-2); |
|
color: var(--body-text-color); |
|
} |
|
.eta-bar { |
|
position: absolute; |
|
top: 0; |
|
right: 0; |
|
bottom: 0; |
|
left: 0; |
|
transform-origin: left; |
|
opacity: 0.8; |
|
z-index: var(--layer-1); |
|
transition: 10ms; |
|
background: var(--background-fill-secondary); |
|
} |
|
.progress-bar-wrap { |
|
border: 1px solid var(--border-color-primary); |
|
background: var(--background-fill-primary); |
|
width: 55.5%; |
|
height: var(--size-4); |
|
} |
|
.progress-bar { |
|
transform-origin: left; |
|
background-color: var(--loader-color); |
|
width: var(--size-full); |
|
height: var(--size-full); |
|
} |
|
|
|
.progress-level { |
|
display: flex; |
|
flex-direction: column; |
|
align-items: center; |
|
gap: 1; |
|
z-index: var(--layer-2); |
|
width: var(--size-full); |
|
} |
|
|
|
.progress-level-inner { |
|
margin: var(--size-2) auto; |
|
color: var(--body-text-color); |
|
font-size: var(--text-sm); |
|
font-family: var(--font-mono); |
|
} |
|
|
|
.meta-text { |
|
position: absolute; |
|
top: 0; |
|
right: 0; |
|
z-index: var(--layer-2); |
|
padding: var(--size-1) var(--size-2); |
|
font-size: var(--text-sm); |
|
font-family: var(--font-mono); |
|
} |
|
|
|
.meta-text-center { |
|
display: flex; |
|
position: absolute; |
|
top: 0; |
|
right: 0; |
|
justify-content: center; |
|
align-items: center; |
|
transform: translateY(var(--size-6)); |
|
z-index: var(--layer-2); |
|
padding: var(--size-1) var(--size-2); |
|
font-size: var(--text-sm); |
|
font-family: var(--font-mono); |
|
text-align: center; |
|
} |
|
|
|
.error { |
|
box-shadow: var(--shadow-drop); |
|
border: solid 1px var(--error-border-color); |
|
border-radius: var(--radius-full); |
|
background: var(--error-background-fill); |
|
padding-right: var(--size-4); |
|
padding-left: var(--size-4); |
|
color: var(--error-text-color); |
|
font-weight: var(--weight-semibold); |
|
font-size: var(--text-lg); |
|
line-height: var(--line-lg); |
|
font-family: var(--font); |
|
} |
|
|
|
.minimal .progress-text { |
|
background: var(--block-background-fill); |
|
} |
|
|
|
.border { |
|
border: 1px solid var(--border-color-primary); |
|
} |
|
</style> |
|
|