Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
<script lang="ts">
import type { ExampleRunOpts, WidgetProps } from "../types.js";
import type { WidgetExample, WidgetExampleAttribute } from "@huggingface/tasks";

type TWidgetExample = $$Generic<WidgetExample>;

import { onMount } from "svelte";
import { slide } from "svelte/transition";

import { randomItem } from "../../../../utils/ViewUtils.js";
import IconCaretDownV2 from "../../..//Icons/IconCaretDownV2.svelte";
import WidgetExamplesGroup from "./WidgetExamplesGroup.svelte";
import { getQueryParamVal } from "../../..//InferenceWidget/shared/helpers.js";

export let isLoading = false;
export let callApiOnMount: WidgetProps["callApiOnMount"];
export let exampleQueryParams: WidgetExampleAttribute[] = [];
export let applyWidgetExample: (sample: TWidgetExample, opts?: ExampleRunOpts) => void;

export let validExamples: TWidgetExample[];

interface ExamplesGroup {
group: string;
examples: TWidgetExample[];
}

$: exampleGroups = getExamplesGroups(validExamples);
$: examples = exampleGroups?.[0]?.examples ?? [];
// for examples with multiple groups, a group needs to be selected first, before an example can be clicked
$: clickable = exampleGroups?.length === 1;
let containerEl: HTMLElement;
let isOptionsVisible = false;
let title = "Examples";

function getExamplesGroups(_examples: TWidgetExample[]): ExamplesGroup[] {
const examples = _examples.map((sample, idx) => ({
example_title: `Example ${++idx}`,
group: "Group 1",
...sample,
}));
const examplesGroups: ExamplesGroup[] = [];
for (const example of examples) {
const groupExists = examplesGroups.find(({ group }) => group === example.group);
if (!groupExists) {
examplesGroups.push({ group: example.group as string, examples: [] });
}
examplesGroups.find(({ group }) => group === example.group)?.examples.push(example);
}
return examplesGroups;
}

function _applyWidgetExample(idx: number) {
hideOptions();
const sample = examples[idx];
title = sample.example_title as string;
applyWidgetExample(sample);
}

function _previewInputSample(idx: number) {
const sample = examples[idx];
applyWidgetExample(sample, { isPreview: true });
}

function toggleOptionsVisibility() {
isOptionsVisible = !isOptionsVisible;
}

function onClick(e: MouseEvent | TouchEvent) {
let targetElement = e.target;
do {
if (targetElement === containerEl) {
// This is a click inside. Do nothing, just return.
return;
}
targetElement = (targetElement as HTMLElement).parentElement;
} while (targetElement);
// This is a click outside
hideOptions();
}

function hideOptions() {
isOptionsVisible = false;
}
function changeGroup(e: CustomEvent<string>) {
const selectedGroup = e.detail;
const newGroup = exampleGroups.find(({ group }) => group === selectedGroup);
if (!newGroup) {
return;
}
examples = newGroup?.examples ?? [];
title = "Examples";
clickable = true;
}

onMount(() => {
// run random example onMount
(async () => {
const exampleFromQueryParams = {} as TWidgetExample;
for (const key of exampleQueryParams) {
const val = getQueryParamVal(key);
if (val) {
// @ts-expect-error complicated type
exampleFromQueryParams[key] = val;
}
}
if (Object.keys(exampleFromQueryParams).length) {
// run widget example from query params
applyWidgetExample(exampleFromQueryParams);
} else {
// run random widget example
const example = randomItem(validExamples);
if (callApiOnMount && example) {
applyWidgetExample(example, { inferenceOpts: { isOnLoadCall: true } });
}
}
})();
});
</script>

<svelte:window on:click={onClick} />

<div class="ml-auto flex gap-x-1">
<!-- Example Groups -->
{#if exampleGroups.length > 1}
<WidgetExamplesGroup
on:groupSelected={changeGroup}
{isLoading}
groupNames={exampleGroups.map(({ group }) => group)}
/>
{/if}

<!-- Example picker -->
<div
class="relative mb-1.5
{isLoading || !clickable ? 'pointer-events-none opacity-50' : ''}
{isOptionsVisible ? 'z-10' : ''}"
bind:this={containerEl}
>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="inline-flex w-32 justify-between rounded-md border border-gray-100 px-4 py-1"
on:click={toggleOptionsVisibility}
>
<div class="truncate text-sm">{title}</div>
<IconCaretDownV2
classNames="-mr-1 ml-2 h-5 w-5 transition ease-in-out transform {isOptionsVisible && '-rotate-180'}"
/>
</div>

{#if isOptionsVisible}
<div
class="absolute right-0 mt-1 w-full origin-top-right rounded-md ring-1 ring-black ring-opacity-10"
transition:slide
>
<div class="rounded-md bg-white py-1" role="none">
{#each examples as { example_title }, i}
<!-- svelte-ignore a11y-click-events-have-key-events a11y-mouse-events-have-key-events -->
<div
class="cursor-pointer truncate px-4 py-2 text-sm hover:bg-gray-100 hover:text-gray-900 dark:hover:bg-gray-800 dark:hover:text-gray-200"
on:mouseover={() => _previewInputSample(i)}
on:click={() => _applyWidgetExample(i)}
>
{example_title}
</div>
{/each}
</div>
</div>
{/if}
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { slide } from "svelte/transition";

import IconCaretDownV2 from "../../..//Icons/IconCaretDownV2.svelte";

export let classNames = "";
export let isLoading = false;
export let inputGroups: string[];
export let selectedInputGroup: string;
export let groupNames: string[];

const dispatch = createEventDispatcher<{ groupSelected: string }>();

let containerEl: HTMLElement;
let isOptionsVisible = false;
let title = "Groups";
let selectedGroupName: string;

function chooseInputGroup(idx: number) {
hideOptions();
const inputGroup = inputGroups[idx];
title = inputGroup;
selectedInputGroup = inputGroup;
const inputGroup = groupNames[idx];
selectedGroupName = inputGroup;
dispatch("groupSelected", selectedGroupName);
}

function toggleOptionsVisibility() {
Expand Down Expand Up @@ -54,7 +56,7 @@
class="inline-flex w-32 justify-between rounded-md border border-gray-100 px-4 py-1"
on:click={toggleOptionsVisibility}
>
<div class="truncate text-sm">{title}</div>
<div class="truncate text-sm">{selectedGroupName ?? "Groups"}</div>
<IconCaretDownV2
classNames="-mr-1 ml-2 h-5 w-5 transition ease-in-out transform {isOptionsVisible && '-rotate-180'}"
/>
Expand All @@ -66,7 +68,7 @@
transition:slide
>
<div class="rounded-md bg-white py-1" role="none">
{#each inputGroups as inputGroup, i}
{#each groupNames as inputGroup, i}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="truncate px-4 py-2 text-sm hover:bg-gray-100 hover:text-gray-900 dark:hover:bg-gray-800 dark:hover:text-gray-200"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
<script lang="ts">
import type { WidgetProps } from "../types.js";
import { identity } from "svelte/internal";
import { widgetStates, updateWidgetState } from "../../stores.js";
import IconCode from "../../..//Icons/IconCode.svelte";
import IconMaximize from "../../..//Icons/IconMaximize.svelte";

export let isMaximized = false;
export let model: WidgetProps["model"];
export let outputJson: string;
export let isDisabled = false;

$: isMaximized = $widgetStates?.[model.id]?.isMaximized;

let isOutputJsonVisible = false;
</script>

Expand All @@ -22,7 +27,10 @@
JSON Output
</button>
{/if}
<button class="ml-auto flex items-center" on:click|preventDefault={() => (isMaximized = !isMaximized)}>
<button
class="ml-auto flex items-center"
on:click|preventDefault={() => updateWidgetState(model.id, "isMaximized", true)}
>
<IconMaximize classNames="mr-1" />
{#if !isMaximized}
Maximize
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,46 @@
<script lang="ts">
import { TASKS_DATA, type PipelineType } from "@huggingface/tasks";
import { updateWidgetState } from "../../stores.js";
import { TASKS_DATA } from "@huggingface/tasks";
import type { WidgetExample, WidgetExampleAttribute } from "@huggingface/tasks";
import type { WidgetProps, ExampleRunOpts } from "../types.js";
import { getPipelineTask } from "../../../../utils/ViewUtils.js";
import IconInfo from "../../..//Icons/IconInfo.svelte";
import IconLightning from "../../..//Icons/IconLightning.svelte";
import PipelineTag from "../../../PipelineTag/PipelineTag.svelte";
import WidgetExamples from "../WidgetExamples/WidgetExamples.svelte";

type TWidgetExample = $$Generic<WidgetExample>;

export let model: WidgetProps["model"];
export let noTitle = false;
export let title: string | null = null;
export let pipeline: PipelineType | undefined;
export let isLoading = false;
export let isDisabled = false;
export let applyWidgetExample: ((sample: TWidgetExample, opts?: ExampleRunOpts) => void) | undefined = undefined;
export let validateExample: ((sample: WidgetExample) => sample is TWidgetExample) | undefined = undefined;
export let callApiOnMount: WidgetProps["callApiOnMount"] = false;
export let exampleQueryParams: WidgetExampleAttribute[] = [];

const pipeline = model?.pipeline_tag;

$: task = pipeline ? getPipelineTask(pipeline) : undefined;

$: validExamples = getValidExamples(isDisabled);

function getValidExamples(isDisabled: boolean): TWidgetExample[] {
const examples = (model?.widgetData ?? []).filter(
(sample): sample is TWidgetExample =>
(validateExample?.(sample) ?? false) && (!isDisabled || sample.output !== undefined)
);

// if there are no examples with outputs AND model.inference !== InferenceDisplayability.Yes
// then widget will show InferenceDisplayability error to the user without showing anything else
if (isDisabled && !examples.length) {
updateWidgetState(model.id, "noInference", true);
}

return examples;
}
</script>

<div class="mb-2 flex items-center font-semibold">
Expand Down Expand Up @@ -45,5 +75,7 @@
<PipelineTag classNames="mr-2 mb-1.5" {pipeline} />
</a>
{/if}
<slot />
{#if validExamples.length && applyWidgetExample}
<WidgetExamples {validExamples} {isLoading} {applyWidgetExample} {callApiOnMount} {exampleQueryParams} />
{/if}
</div>
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
<script lang="ts">
import { InferenceDisplayability } from "@huggingface/tasks";
import { type WidgetProps, type ModelLoadInfo, LoadState, ComputeType } from "../types.js";
import WidgetModelLoading from "../WidgetModelLoading/WidgetModelLoading.svelte";
import IconAzureML from "../../..//Icons/IconAzureML.svelte";
import IconInfo from "../../..//Icons/IconInfo.svelte";
import { modelLoadStates } from "../../stores.js";

export let model: WidgetProps["model"];
export let computeTime: string = "";
export let error: string = "";
export let modelLoadInfo: ModelLoadInfo | undefined = undefined;
export let modelTooBig = false;
export let modelLoading = {
isLoading: false,
estimatedTime: 0,
};

$: modelTooBig = $modelLoadStates[model.id]?.state === "TooBig";

const state = {
[LoadState.Loadable]: "This model can be loaded on the Inference API on-demand.",
Expand All @@ -26,11 +32,11 @@
[LoadState.Error]: "⚠️ This model could not be loaded.",
} as const;

function getStatusReport(modelLoadInfo: ModelLoadInfo | undefined, statuses: Record<LoadState, string>): string {
if (!modelLoadInfo) {
function getStatusReport(modelLoadStates: ModelLoadInfo | undefined, statuses: Record<LoadState, string>): string {
if (!modelLoadStates) {
return "Model state unknown";
}
return statuses[modelLoadInfo.state];
return statuses[modelLoadStates.state];
}
</script>

Expand All @@ -48,13 +54,13 @@
</div>
<div class="border-dotter mx-2 flex flex-1 -translate-y-px border-b border-gray-100" />
<div>
{@html getStatusReport(modelLoadInfo, azureState)}
{@html getStatusReport($modelLoadStates[model.id], azureState)}
</div>
</div>
{:else if computeTime}
Computation time on {modelLoadInfo?.compute_type ?? ComputeType.CPU}: {computeTime}
Computation time on {$modelLoadStates[model.id]?.compute_type ?? ComputeType.CPU}: {computeTime}
{:else if (model.inference === InferenceDisplayability.Yes || model.pipeline_tag === "reinforcement-learning") && !modelTooBig}
{@html getStatusReport(modelLoadInfo, state)}
{@html getStatusReport($modelLoadStates[model.id], state)}
{:else if model.inference === InferenceDisplayability.ExplicitOptOut}
<span class="text-sm text-gray-500">Inference API has been turned off for this model.</span>
{:else if model.inference === InferenceDisplayability.CustomCode}
Expand Down Expand Up @@ -98,4 +104,7 @@
{#if error}
<div class="alert alert-error mt-3">{error}</div>
{/if}
{#if modelLoading.isLoading}
<WidgetModelLoading estimatedTime={modelLoading.estimatedTime} />
{/if}
</div>
Loading