machineuser commited on
Commit
76d4920
1 Parent(s): 3b081d5

Sync widgets demo

Browse files
packages/widgets/src/lib/components/Icons/IconHuggingFace.svelte ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ export let classNames = "";
3
+ </script>
4
+
5
+ <svg
6
+ class={classNames}
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ xmlns:xlink="http://www.w3.org/1999/xlink"
9
+ aria-hidden="true"
10
+ focusable="false"
11
+ role="img"
12
+ preserveAspectRatio="xMidYMid meet"
13
+ width="1em"
14
+ height="1em"
15
+ viewBox="0 0 95 88"
16
+ >
17
+ <path
18
+ fill="#fff"
19
+ d="M94.25 70.08a8.28 8.28 0 0 1-.43 6.46 10.57 10.57 0 0 1-3 3.6 25.18 25.18 0 0 1-5.7 3.2 65.74 65.74 0 0 1-7.56 2.65 46.67 46.67 0 0 1-11.42 1.68c-5.42.05-10.09-1.23-13.4-4.5a40.4 40.4 0 0 1-10.14.03c-3.34 3.25-7.99 4.52-13.39 4.47a46.82 46.82 0 0 1-11.43-1.68 66.37 66.37 0 0 1-7.55-2.65c-2.28-.98-4.17-2-5.68-3.2a10.5 10.5 0 0 1-3.02-3.6c-.99-2-1.18-4.3-.42-6.46a8.54 8.54 0 0 1-.33-5.63c.25-.95.66-1.83 1.18-2.61a8.67 8.67 0 0 1 2.1-8.47 8.23 8.23 0 0 1 2.82-2.07 41.75 41.75 0 1 1 81.3-.12 8.27 8.27 0 0 1 3.11 2.19 8.7 8.7 0 0 1 2.1 8.47c.52.78.93 1.66 1.18 2.61a8.61 8.61 0 0 1-.32 5.63Z"
20
+ />
21
+ <path fill="#FFD21E" d="M47.21 76.5a34.75 34.75 0 1 0 0-69.5 34.75 34.75 0 0 0 0 69.5Z" />
22
+ <path
23
+ fill="#FF9D0B"
24
+ d="M81.96 41.75a34.75 34.75 0 1 0-69.5 0 34.75 34.75 0 0 0 69.5 0Zm-73.5 0a38.75 38.75 0 1 1 77.5 0 38.75 38.75 0 0 1-77.5 0Z"
25
+ />
26
+ <path
27
+ fill="#3A3B45"
28
+ d="M58.5 32.3c1.28.44 1.78 3.06 3.07 2.38a5 5 0 1 0-6.76-2.07c.61 1.15 2.55-.72 3.7-.32ZM34.95 32.3c-1.28.44-1.79 3.06-3.07 2.38a5 5 0 1 1 6.76-2.07c-.61 1.15-2.56-.72-3.7-.32Z"
29
+ />
30
+ <path
31
+ fill="#FF323D"
32
+ d="M46.96 56.29c9.83 0 13-8.76 13-13.26 0-2.34-1.57-1.6-4.09-.36-2.33 1.15-5.46 2.74-8.9 2.74-7.19 0-13-6.88-13-2.38s3.16 13.26 13 13.26Z"
33
+ />
34
+ <path
35
+ fill="#3A3B45"
36
+ fill-rule="evenodd"
37
+ d="M39.43 54a8.7 8.7 0 0 1 5.3-4.49c.4-.12.81.57 1.24 1.28.4.68.82 1.37 1.24 1.37.45 0 .9-.68 1.33-1.35.45-.7.89-1.38 1.32-1.25a8.61 8.61 0 0 1 5 4.17c3.73-2.94 5.1-7.74 5.1-10.7 0-2.34-1.57-1.6-4.09-.36l-.14.07c-2.31 1.15-5.39 2.67-8.77 2.67s-6.45-1.52-8.77-2.67c-2.6-1.29-4.23-2.1-4.23.29 0 3.05 1.46 8.06 5.47 10.97Z"
38
+ clip-rule="evenodd"
39
+ />
40
+ <path
41
+ fill="#FF9D0B"
42
+ d="M70.71 37a3.25 3.25 0 1 0 0-6.5 3.25 3.25 0 0 0 0 6.5ZM24.21 37a3.25 3.25 0 1 0 0-6.5 3.25 3.25 0 0 0 0 6.5ZM17.52 48c-1.62 0-3.06.66-4.07 1.87a5.97 5.97 0 0 0-1.33 3.76 7.1 7.1 0 0 0-1.94-.3c-1.55 0-2.95.59-3.94 1.66a5.8 5.8 0 0 0-.8 7 5.3 5.3 0 0 0-1.79 2.82c-.24.9-.48 2.8.8 4.74a5.22 5.22 0 0 0-.37 5.02c1.02 2.32 3.57 4.14 8.52 6.1 3.07 1.22 5.89 2 5.91 2.01a44.33 44.33 0 0 0 10.93 1.6c5.86 0 10.05-1.8 12.46-5.34 3.88-5.69 3.33-10.9-1.7-15.92-2.77-2.78-4.62-6.87-5-7.77-.78-2.66-2.84-5.62-6.25-5.62a5.7 5.7 0 0 0-4.6 2.46c-1-1.26-1.98-2.25-2.86-2.82A7.4 7.4 0 0 0 17.52 48Zm0 4c.51 0 1.14.22 1.82.65 2.14 1.36 6.25 8.43 7.76 11.18.5.92 1.37 1.31 2.14 1.31 1.55 0 2.75-1.53.15-3.48-3.92-2.93-2.55-7.72-.68-8.01.08-.02.17-.02.24-.02 1.7 0 2.45 2.93 2.45 2.93s2.2 5.52 5.98 9.3c3.77 3.77 3.97 6.8 1.22 10.83-1.88 2.75-5.47 3.58-9.16 3.58-3.81 0-7.73-.9-9.92-1.46-.11-.03-13.45-3.8-11.76-7 .28-.54.75-.76 1.34-.76 2.38 0 6.7 3.54 8.57 3.54.41 0 .7-.17.83-.6.79-2.85-12.06-4.05-10.98-8.17.2-.73.71-1.02 1.44-1.02 3.14 0 10.2 5.53 11.68 5.53.11 0 .2-.03.24-.1.74-1.2.33-2.04-4.9-5.2-5.21-3.16-8.88-5.06-6.8-7.33.24-.26.58-.38 1-.38 3.17 0 10.66 6.82 10.66 6.82s2.02 2.1 3.25 2.1c.28 0 .52-.1.68-.38.86-1.46-8.06-8.22-8.56-11.01-.34-1.9.24-2.85 1.31-2.85Z"
43
+ />
44
+ <path
45
+ fill="#FFD21E"
46
+ d="M38.6 76.69c2.75-4.04 2.55-7.07-1.22-10.84-3.78-3.77-5.98-9.3-5.98-9.3s-.82-3.2-2.69-2.9c-1.87.3-3.24 5.08.68 8.01 3.91 2.93-.78 4.92-2.29 2.17-1.5-2.75-5.62-9.82-7.76-11.18-2.13-1.35-3.63-.6-3.13 2.2.5 2.79 9.43 9.55 8.56 11-.87 1.47-3.93-1.71-3.93-1.71s-9.57-8.71-11.66-6.44c-2.08 2.27 1.59 4.17 6.8 7.33 5.23 3.16 5.64 4 4.9 5.2-.75 1.2-12.28-8.53-13.36-4.4-1.08 4.11 11.77 5.3 10.98 8.15-.8 2.85-9.06-5.38-10.74-2.18-1.7 3.21 11.65 6.98 11.76 7.01 4.3 1.12 15.25 3.49 19.08-2.12Z"
47
+ />
48
+ <path
49
+ fill="#FF9D0B"
50
+ d="M77.4 48c1.62 0 3.07.66 4.07 1.87a5.97 5.97 0 0 1 1.33 3.76 7.1 7.1 0 0 1 1.95-.3c1.55 0 2.95.59 3.94 1.66a5.8 5.8 0 0 1 .8 7 5.3 5.3 0 0 1 1.78 2.82c.24.9.48 2.8-.8 4.74a5.22 5.22 0 0 1 .37 5.02c-1.02 2.32-3.57 4.14-8.51 6.1-3.08 1.22-5.9 2-5.92 2.01a44.33 44.33 0 0 1-10.93 1.6c-5.86 0-10.05-1.8-12.46-5.34-3.88-5.69-3.33-10.9 1.7-15.92 2.78-2.78 4.63-6.87 5.01-7.77.78-2.66 2.83-5.62 6.24-5.62a5.7 5.7 0 0 1 4.6 2.46c1-1.26 1.98-2.25 2.87-2.82A7.4 7.4 0 0 1 77.4 48Zm0 4c-.51 0-1.13.22-1.82.65-2.13 1.36-6.25 8.43-7.76 11.18a2.43 2.43 0 0 1-2.14 1.31c-1.54 0-2.75-1.53-.14-3.48 3.91-2.93 2.54-7.72.67-8.01a1.54 1.54 0 0 0-.24-.02c-1.7 0-2.45 2.93-2.45 2.93s-2.2 5.52-5.97 9.3c-3.78 3.77-3.98 6.8-1.22 10.83 1.87 2.75 5.47 3.58 9.15 3.58 3.82 0 7.73-.9 9.93-1.46.1-.03 13.45-3.8 11.76-7-.29-.54-.75-.76-1.34-.76-2.38 0-6.71 3.54-8.57 3.54-.42 0-.71-.17-.83-.6-.8-2.85 12.05-4.05 10.97-8.17-.19-.73-.7-1.02-1.44-1.02-3.14 0-10.2 5.53-11.68 5.53-.1 0-.19-.03-.23-.1-.74-1.2-.34-2.04 4.88-5.2 5.23-3.16 8.9-5.06 6.8-7.33-.23-.26-.57-.38-.98-.38-3.18 0-10.67 6.82-10.67 6.82s-2.02 2.1-3.24 2.1a.74.74 0 0 1-.68-.38c-.87-1.46 8.05-8.22 8.55-11.01.34-1.9-.24-2.85-1.31-2.85Z"
51
+ />
52
+ <path
53
+ fill="#FFD21E"
54
+ d="M56.33 76.69c-2.75-4.04-2.56-7.07 1.22-10.84 3.77-3.77 5.97-9.3 5.97-9.3s.82-3.2 2.7-2.9c1.86.3 3.23 5.08-.68 8.01-3.92 2.93.78 4.92 2.28 2.17 1.51-2.75 5.63-9.82 7.76-11.18 2.13-1.35 3.64-.6 3.13 2.2-.5 2.79-9.42 9.55-8.55 11 .86 1.47 3.92-1.71 3.92-1.71s9.58-8.71 11.66-6.44c2.08 2.27-1.58 4.17-6.8 7.33-5.23 3.16-5.63 4-4.9 5.2.75 1.2 12.28-8.53 13.36-4.4 1.08 4.11-11.76 5.3-10.97 8.15.8 2.85 9.05-5.38 10.74-2.18 1.69 3.21-11.65 6.98-11.76 7.01-4.31 1.12-15.26 3.49-19.08-2.12Z"
55
+ />
56
+ </svg>
packages/widgets/src/lib/components/InferenceWidget/InferenceWidget.svelte CHANGED
@@ -28,6 +28,7 @@
28
  import ZeroShotImageClassificationWidget from "./widgets/ZeroShotImageClassificationWidget/ZeroShotImageClassificationWidget.svelte";
29
  import type { WidgetType } from "@huggingface/tasks";
30
  import WidgetInfo from "./shared/WidgetInfo/WidgetInfo.svelte";
 
31
 
32
  export let apiToken: WidgetProps["apiToken"] = undefined;
33
  export let callApiOnMount = false;
@@ -85,6 +86,8 @@
85
  ? WIDGET_COMPONENTS[model.pipeline_tag as keyof typeof WIDGET_COMPONENTS]
86
  : undefined;
87
 
 
 
88
  // prettier-ignore
89
  $: widgetProps = ({
90
  apiToken,
 
28
  import ZeroShotImageClassificationWidget from "./widgets/ZeroShotImageClassificationWidget/ZeroShotImageClassificationWidget.svelte";
29
  import type { WidgetType } from "@huggingface/tasks";
30
  import WidgetInfo from "./shared/WidgetInfo/WidgetInfo.svelte";
31
+ import { isLoggedIn as isLoggedInStore } from "./stores.js";
32
 
33
  export let apiToken: WidgetProps["apiToken"] = undefined;
34
  export let callApiOnMount = false;
 
86
  ? WIDGET_COMPONENTS[model.pipeline_tag as keyof typeof WIDGET_COMPONENTS]
87
  : undefined;
88
 
89
+ $isLoggedInStore = isLoggedIn;
90
+
91
  // prettier-ignore
92
  $: widgetProps = ({
93
  apiToken,
packages/widgets/src/lib/components/InferenceWidget/shared/WidgetDropzone/WidgetDropzone.svelte CHANGED
@@ -1,6 +1,8 @@
1
  <script lang="ts">
 
2
  import IconSpin from "../../../Icons/IconSpin.svelte";
3
  import { getBlobFromUrl } from "../../shared/helpers.js";
 
4
 
5
  export let accept = "image/*";
6
  export let classNames = "";
@@ -13,6 +15,7 @@
13
 
14
  let fileInput: HTMLInputElement;
15
  let isDragging = false;
 
16
 
17
  function onChange() {
18
  const file = fileInput.files?.[0];
@@ -23,6 +26,12 @@
23
 
24
  async function onDrop(e: DragEvent) {
25
  isDragging = false;
 
 
 
 
 
 
26
  const itemList = e.dataTransfer?.items;
27
  if (!itemList || isLoading) {
28
  return;
@@ -54,36 +63,43 @@
54
  style="display: none;"
55
  type="file"
56
  />
57
- <!-- svelte-ignore a11y-click-events-have-key-events -->
58
- <div
59
- class="relative cursor-pointer rounded border-2 border-dashed px-3 py-7 text-center
 
 
60
  {isDisabled ? 'pointer-events-none' : ''}
61
  {isDragging ? 'border-green-300 bg-green-50 text-green-500' : 'text-gray-500'}
62
  {classNames}"
63
- on:click={() => {
64
- fileInput.click();
65
- }}
66
- on:dragenter={() => {
67
- isDragging = true;
68
- }}
69
- on:dragleave={() => {
70
- isDragging = false;
71
- }}
72
- on:dragover|preventDefault
73
- on:drop|preventDefault={onDrop}
74
- >
75
- {#if !imgSrc && !isDisabled}
76
- <span class="pointer-events-none text-sm">{label}</span>
77
- {:else}
78
- <div class={isDragging ? "pointer-events-none" : ""}>
79
- <slot />
80
- </div>
81
- {/if}
82
- {#if isLoading}
83
- <div
84
- class="absolute top-1/2 left-1/2 flex h-12 w-12 -translate-x-1/2 -translate-y-1/2 transform items-center justify-center rounded-full border border-gray-100 bg-white shadow"
85
- >
86
- <IconSpin classNames="text-purple-500 animate-spin h-6 w-6" />
87
- </div>
88
- {/if}
89
- </div>
 
 
 
 
 
 
1
  <script lang="ts">
2
+ import LogInPopover from "../../../LogInPopover/LogInPopover.svelte";
3
  import IconSpin from "../../../Icons/IconSpin.svelte";
4
  import { getBlobFromUrl } from "../../shared/helpers.js";
5
+ import { isLoggedIn } from "../../stores.js";
6
 
7
  export let accept = "image/*";
8
  export let classNames = "";
 
15
 
16
  let fileInput: HTMLInputElement;
17
  let isDragging = false;
18
+ let popOverOpen = false;
19
 
20
  function onChange() {
21
  const file = fileInput.files?.[0];
 
26
 
27
  async function onDrop(e: DragEvent) {
28
  isDragging = false;
29
+
30
+ if (!$isLoggedIn) {
31
+ popOverOpen = true;
32
+ return;
33
+ }
34
+
35
  const itemList = e.dataTransfer?.items;
36
  if (!itemList || isLoading) {
37
  return;
 
63
  style="display: none;"
64
  type="file"
65
  />
66
+
67
+ <LogInPopover bind:open={popOverOpen}>
68
+ <!-- svelte-ignore a11y-click-events-have-key-events -->
69
+ <div
70
+ class="relative cursor-pointer rounded border-2 border-dashed px-3 py-7 text-center
71
  {isDisabled ? 'pointer-events-none' : ''}
72
  {isDragging ? 'border-green-300 bg-green-50 text-green-500' : 'text-gray-500'}
73
  {classNames}"
74
+ on:click={() => {
75
+ if (!$isLoggedIn) {
76
+ popOverOpen = true;
77
+ return;
78
+ }
79
+ fileInput.click();
80
+ }}
81
+ on:dragenter={() => {
82
+ isDragging = true;
83
+ }}
84
+ on:dragleave={() => {
85
+ isDragging = false;
86
+ }}
87
+ on:dragover|preventDefault
88
+ on:drop|preventDefault={onDrop}
89
+ >
90
+ {#if !imgSrc && !isDisabled}
91
+ <span class="pointer-events-none text-sm">{label}</span>
92
+ {:else}
93
+ <div class={isDragging ? "pointer-events-none" : ""}>
94
+ <slot />
95
+ </div>
96
+ {/if}
97
+ {#if isLoading}
98
+ <div
99
+ class="absolute top-1/2 left-1/2 flex h-12 w-12 -translate-x-1/2 -translate-y-1/2 transform items-center justify-center rounded-full border border-gray-100 bg-white shadow"
100
+ >
101
+ <IconSpin classNames="text-purple-500 animate-spin h-6 w-6" />
102
+ </div>
103
+ {/if}
104
+ </div>
105
+ </LogInPopover>
packages/widgets/src/lib/components/InferenceWidget/shared/WidgetFileInput/WidgetFileInput.svelte CHANGED
@@ -1,6 +1,8 @@
1
  <script lang="ts">
2
  import IconSpin from "../../../Icons/IconSpin.svelte";
3
  import IconFile from "../../../Icons/IconFile.svelte";
 
 
4
 
5
  export let accept: string | undefined;
6
  export let classNames = "";
@@ -11,8 +13,14 @@
11
 
12
  let fileInput: HTMLInputElement;
13
  let isDragging = false;
 
14
 
15
  function onChange() {
 
 
 
 
 
16
  const file = fileInput.files?.[0];
17
  if (file) {
18
  onSelectFile(file);
@@ -21,36 +29,45 @@
21
  </script>
22
 
23
  {#if !isDisabled}
24
- <div
25
- class={classNames}
26
- on:dragenter={() => {
27
- isDragging = true;
28
- }}
29
- on:dragover|preventDefault
30
- on:dragleave={() => {
31
- isDragging = false;
32
- }}
33
- on:drop|preventDefault={(e) => {
34
- isDragging = false;
35
- fileInput.files = e.dataTransfer?.files ?? null;
36
- onChange();
37
- }}
38
- >
39
- <label class="btn-widget {isDragging ? 'ring' : ''} {isLoading ? 'text-gray-600' : ''}">
40
- {#if isLoading}
41
- <IconSpin classNames="-ml-1 mr-1.5 text-gray-600 animate-spin" />
42
- {:else}
43
- <IconFile classNames="-ml-1 mr-1.5" />
44
- {/if}
45
- <input
46
- {accept}
47
- bind:this={fileInput}
48
- on:change={onChange}
49
- disabled={isLoading || isDisabled}
50
- style="display: none;"
51
- type="file"
52
- />
53
- {label}
54
- </label>
55
- </div>
 
 
 
 
 
 
 
 
 
56
  {/if}
 
1
  <script lang="ts">
2
  import IconSpin from "../../../Icons/IconSpin.svelte";
3
  import IconFile from "../../../Icons/IconFile.svelte";
4
+ import { isLoggedIn } from "../../stores.js";
5
+ import LogInPopover from "../../../LogInPopover/LogInPopover.svelte";
6
 
7
  export let accept: string | undefined;
8
  export let classNames = "";
 
13
 
14
  let fileInput: HTMLInputElement;
15
  let isDragging = false;
16
+ let popOverOpen = false;
17
 
18
  function onChange() {
19
+ if (!$isLoggedIn) {
20
+ popOverOpen = true;
21
+ return;
22
+ }
23
+
24
  const file = fileInput.files?.[0];
25
  if (file) {
26
  onSelectFile(file);
 
29
  </script>
30
 
31
  {#if !isDisabled}
32
+ <LogInPopover bind:open={popOverOpen}>
33
+ <button
34
+ class={classNames}
35
+ on:click={(e) => {
36
+ if (!$isLoggedIn) {
37
+ popOverOpen = true;
38
+ e.preventDefault();
39
+ return;
40
+ }
41
+ }}
42
+ on:dragenter={() => {
43
+ isDragging = true;
44
+ }}
45
+ on:dragover|preventDefault
46
+ on:dragleave={() => {
47
+ isDragging = false;
48
+ }}
49
+ on:drop|preventDefault={(e) => {
50
+ isDragging = false;
51
+ fileInput.files = e.dataTransfer?.files ?? null;
52
+ onChange();
53
+ }}
54
+ >
55
+ <label class="btn-widget {isDragging ? 'ring' : ''} {isLoading ? 'text-gray-600' : ''}">
56
+ {#if isLoading}
57
+ <IconSpin classNames="-ml-1 mr-1.5 text-gray-600 animate-spin" />
58
+ {:else}
59
+ <IconFile classNames="-ml-1 mr-1.5" />
60
+ {/if}
61
+ <input
62
+ {accept}
63
+ bind:this={fileInput}
64
+ on:change={onChange}
65
+ disabled={isLoading || isDisabled}
66
+ style="display: none;"
67
+ type="file"
68
+ />
69
+ {label}
70
+ </label>
71
+ </button>
72
+ </LogInPopover>
73
  {/if}
packages/widgets/src/lib/components/InferenceWidget/shared/WidgetFooter/WidgetFooter.svelte CHANGED
@@ -1,9 +1,9 @@
1
  <script lang="ts">
2
  import type { WidgetProps } from "../types.js";
3
- import { identity } from "svelte/internal";
4
- import { widgetStates, updateWidgetState } from "../../stores.js";
5
  import IconCode from "../../..//Icons/IconCode.svelte";
6
  import IconMaximize from "../../..//Icons/IconMaximize.svelte";
 
7
 
8
  export let model: WidgetProps["model"];
9
  export let outputJson: string;
@@ -12,6 +12,7 @@
12
  $: isMaximized = $widgetStates?.[model.id]?.isMaximized;
13
 
14
  let isOutputJsonVisible = false;
 
15
  </script>
16
 
17
  <div class="mt-auto flex items-center pt-4 text-xs text-gray-500">
@@ -28,18 +29,26 @@
28
  JSON Output
29
  </button>
30
  {/if}
31
- <button
32
- class="ml-auto flex items-center"
33
- type="button"
34
- on:click|preventDefault={() => updateWidgetState(model.id, "isMaximized", !isMaximized)}
35
- >
36
- <IconMaximize classNames="mr-1" />
37
- {#if !isMaximized}
38
- Maximize
39
- {:else}
40
- Minimize
41
- {/if}
42
- </button>
 
 
 
 
 
 
 
 
43
  </div>
44
  {#if outputJson && isOutputJsonVisible}
45
  <pre
 
1
  <script lang="ts">
2
  import type { WidgetProps } from "../types.js";
3
+ import { widgetStates, updateWidgetState, isLoggedIn } from "../../stores.js";
 
4
  import IconCode from "../../..//Icons/IconCode.svelte";
5
  import IconMaximize from "../../..//Icons/IconMaximize.svelte";
6
+ import LogInPopover from "../../../../components/LogInPopover/LogInPopover.svelte";
7
 
8
  export let model: WidgetProps["model"];
9
  export let outputJson: string;
 
12
  $: isMaximized = $widgetStates?.[model.id]?.isMaximized;
13
 
14
  let isOutputJsonVisible = false;
15
+ let popOverOpen = false;
16
  </script>
17
 
18
  <div class="mt-auto flex items-center pt-4 text-xs text-gray-500">
 
29
  JSON Output
30
  </button>
31
  {/if}
32
+ <LogInPopover bind:open={popOverOpen} classNames="ml-auto">
33
+ <button
34
+ class="flex items-center"
35
+ type="button"
36
+ on:click|preventDefault={() => {
37
+ if (!$isLoggedIn) {
38
+ popOverOpen = true;
39
+ return;
40
+ }
41
+ updateWidgetState(model.id, "isMaximized", !isMaximized);
42
+ }}
43
+ >
44
+ <IconMaximize classNames="mr-1" />
45
+ {#if !isMaximized}
46
+ Maximize
47
+ {:else}
48
+ Minimize
49
+ {/if}
50
+ </button>
51
+ </LogInPopover>
52
  </div>
53
  {#if outputJson && isOutputJsonVisible}
54
  <pre
packages/widgets/src/lib/components/InferenceWidget/shared/WidgetQuickInput/WidgetQuickInput.svelte CHANGED
@@ -1,6 +1,9 @@
1
  <script lang="ts">
 
2
  import { onCmdEnter } from "../../../../utils/ViewUtils.js";
3
  import WidgetSubmitBtn from "../WidgetSubmitBtn/WidgetSubmitBtn.svelte";
 
 
4
 
5
  export let flatTop = false;
6
  export let isLoading: boolean;
@@ -9,25 +12,39 @@
9
  export let placeholder = "Your sentence here...";
10
  export let submitButtonLabel: string | undefined = undefined;
11
  export let value: string = "";
 
 
 
 
12
  </script>
13
 
14
- <div class="flex h-10">
15
- <input
16
- bind:value
17
- class="form-input-alt min-w-0 flex-1 rounded-r-none {flatTop ? 'rounded-t-none' : ''}"
18
- placeholder={isDisabled ? "" : placeholder}
19
- required={true}
20
- type="text"
21
- disabled={isLoading || isDisabled}
22
- autocomplete="off"
23
- use:onCmdEnter={{ disabled: isLoading || isDisabled }}
24
- on:cmdEnter
25
- />
26
- <WidgetSubmitBtn
27
- classNames="rounded-l-none border-l-0 {flatTop ? 'rounded-t-none' : ''}"
28
- {isLoading}
29
- {isDisabled}
30
- label={submitButtonLabel}
31
- onClick={onClickSubmitBtn}
32
- />
33
- </div>
 
 
 
 
 
 
 
 
 
 
 
1
  <script lang="ts">
2
+ import { createEventDispatcher } from "svelte";
3
  import { onCmdEnter } from "../../../../utils/ViewUtils.js";
4
  import WidgetSubmitBtn from "../WidgetSubmitBtn/WidgetSubmitBtn.svelte";
5
+ import { isLoggedIn } from "../../stores.js";
6
+ import LogInPopover from "../../../LogInPopover/LogInPopover.svelte";
7
 
8
  export let flatTop = false;
9
  export let isLoading: boolean;
 
12
  export let placeholder = "Your sentence here...";
13
  export let submitButtonLabel: string | undefined = undefined;
14
  export let value: string = "";
15
+
16
+ let popOverOpen = false;
17
+
18
+ const dispatch = createEventDispatcher<{ cmdEnter: void }>();
19
  </script>
20
 
21
+ <LogInPopover bind:open={popOverOpen}>
22
+ <div class="flex h-10">
23
+ <input
24
+ bind:value
25
+ class="form-input-alt min-w-0 flex-1 rounded-r-none {flatTop ? 'rounded-t-none' : ''}"
26
+ placeholder={isDisabled ? "" : placeholder}
27
+ required={true}
28
+ type="text"
29
+ disabled={isLoading || isDisabled}
30
+ autocomplete="off"
31
+ use:onCmdEnter={{ disabled: isLoading || isDisabled }}
32
+ on:cmdEnter={() => {
33
+ if (!$isLoggedIn) {
34
+ popOverOpen = true;
35
+ return;
36
+ }
37
+ dispatch("cmdEnter");
38
+ }}
39
+ />
40
+ <WidgetSubmitBtn
41
+ classNames="rounded-l-none border-l-0 {flatTop ? 'rounded-t-none' : ''}"
42
+ {isLoading}
43
+ {isDisabled}
44
+ label={submitButtonLabel}
45
+ onClick={onClickSubmitBtn}
46
+ withParentLoginPopover={true}
47
+ on:logInPopover={() => (popOverOpen = true)}
48
+ />
49
+ </div>
50
+ </LogInPopover>
packages/widgets/src/lib/components/InferenceWidget/shared/WidgetSubmitBtn/WidgetSubmitBtn.svelte CHANGED
@@ -1,24 +1,48 @@
1
  <script lang="ts">
 
2
  import IconSpin from "../../..//Icons/IconSpin.svelte";
 
 
3
 
4
  export let classNames = "";
5
  export let isDisabled = false;
6
  export let isLoading: boolean;
7
  export let label = "Compute";
8
  export let onClick: () => void;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  </script>
10
 
11
  {#if !isDisabled}
12
- <button
13
- class="btn-widget h-10 w-24 px-5 {classNames}"
14
- disabled={isDisabled || isLoading}
15
- on:click|preventDefault={onClick}
16
- type="submit"
17
- >
18
- {#if isLoading}
19
- <IconSpin classNames="text-gray-600 animate-spin" />
20
- {:else}
21
- {label}
22
- {/if}
23
- </button>
 
 
24
  {/if}
 
1
  <script lang="ts">
2
+ import LogInPopover from "../../../LogInPopover/LogInPopover.svelte";
3
  import IconSpin from "../../..//Icons/IconSpin.svelte";
4
+ import { isLoggedIn } from "../../stores.js";
5
+ import { createEventDispatcher } from "svelte";
6
 
7
  export let classNames = "";
8
  export let isDisabled = false;
9
  export let isLoading: boolean;
10
  export let label = "Compute";
11
  export let onClick: () => void;
12
+ export let withParentLoginPopover = false;
13
+
14
+ let popOverOpen = false;
15
+
16
+ const dispatch = createEventDispatcher<{ logInPopover: void }>();
17
+
18
+ function _onClick() {
19
+ if (!$isLoggedIn) {
20
+ if (withParentLoginPopover) {
21
+ // tell parent element to show log in pop over
22
+ dispatch("logInPopover");
23
+ } else {
24
+ popOverOpen = true;
25
+ }
26
+ return;
27
+ }
28
+
29
+ onClick();
30
+ }
31
  </script>
32
 
33
  {#if !isDisabled}
34
+ <LogInPopover bind:open={popOverOpen}>
35
+ <button
36
+ class="btn-widget h-10 w-24 px-5 {classNames}"
37
+ disabled={isDisabled || isLoading}
38
+ on:click|preventDefault={_onClick}
39
+ type="submit"
40
+ >
41
+ {#if isLoading}
42
+ <IconSpin classNames="text-gray-600 animate-spin" />
43
+ {:else}
44
+ {label}
45
+ {/if}
46
+ </button>
47
+ </LogInPopover>
48
  {/if}
packages/widgets/src/lib/components/InferenceWidget/shared/WidgetTextInput/WidgetTextInput.svelte CHANGED
@@ -1,24 +1,39 @@
1
  <script lang="ts">
 
 
2
  import { onCmdEnter } from "../../../../utils/ViewUtils.js";
3
  import WidgetLabel from "../WidgetLabel/WidgetLabel.svelte";
 
4
 
5
  export let label: string | undefined = undefined;
6
  export let placeholder: string = "Your sentence here...";
7
  export let isLoading = false;
8
  export let isDisabled = false;
9
  export let value: string;
 
 
 
 
10
  </script>
11
 
12
- <WidgetLabel {label}>
13
- <svelte:fragment slot="after">
14
- <input
15
- bind:value
16
- class="{label ? 'mt-1.5' : ''} form-input-alt block w-full"
17
- placeholder={isDisabled ? "" : placeholder}
18
- disabled={isDisabled}
19
- type="text"
20
- use:onCmdEnter={{ disabled: isLoading || isDisabled }}
21
- on:cmdEnter
22
- />
23
- </svelte:fragment>
24
- </WidgetLabel>
 
 
 
 
 
 
 
 
 
1
  <script lang="ts">
2
+ import LogInPopover from "../../../LogInPopover/LogInPopover.svelte";
3
+ import { createEventDispatcher } from "svelte";
4
  import { onCmdEnter } from "../../../../utils/ViewUtils.js";
5
  import WidgetLabel from "../WidgetLabel/WidgetLabel.svelte";
6
+ import { isLoggedIn } from "../../stores.js";
7
 
8
  export let label: string | undefined = undefined;
9
  export let placeholder: string = "Your sentence here...";
10
  export let isLoading = false;
11
  export let isDisabled = false;
12
  export let value: string;
13
+
14
+ let popOverOpen = false;
15
+
16
+ const dispatch = createEventDispatcher<{ cmdEnter: void }>();
17
  </script>
18
 
19
+ <LogInPopover bind:open={popOverOpen}>
20
+ <WidgetLabel {label}>
21
+ <svelte:fragment slot="after">
22
+ <input
23
+ bind:value
24
+ class="{label ? 'mt-1.5' : ''} form-input-alt block w-full"
25
+ placeholder={isDisabled ? "" : placeholder}
26
+ disabled={isDisabled}
27
+ type="text"
28
+ use:onCmdEnter={{ disabled: isLoading || isDisabled }}
29
+ on:cmdEnter={() => {
30
+ if (!$isLoggedIn) {
31
+ popOverOpen = true;
32
+ return;
33
+ }
34
+ dispatch("cmdEnter");
35
+ }}
36
+ />
37
+ </svelte:fragment>
38
+ </WidgetLabel>
39
+ </LogInPopover>
packages/widgets/src/lib/components/InferenceWidget/shared/WidgetTextarea/WidgetTextarea.svelte CHANGED
@@ -1,8 +1,10 @@
1
  <script lang="ts">
2
- import { tick } from "svelte";
3
 
4
  import { delay, onCmdEnter } from "../../../../utils/ViewUtils.js";
5
  import WidgetLabel from "../WidgetLabel/WidgetLabel.svelte";
 
 
6
 
7
  export let label: string = "";
8
  export let placeholder: string = "Your sentence here...";
@@ -13,10 +15,13 @@
13
 
14
  let containerSpanEl: HTMLSpanElement;
15
  let isOnFocus = false;
 
16
  const typingEffectSpeedMs = 12;
17
  const classNamesInput = "whitespace-pre-wrap inline font-normal text-black dark:text-white";
18
  const classNamesOutput = "whitespace-pre-wrap inline text-blue-600 dark:text-blue-400";
19
 
 
 
20
  export async function renderTextOutput(outputTxt: string, typingEffect = true): Promise<void> {
21
  const spanEl = document.createElement("span");
22
  spanEl.contentEditable = isDisabled ? "false" : "true";
@@ -90,29 +95,37 @@
90
  }
91
  </script>
92
 
93
- <WidgetLabel {label}>
94
- <svelte:fragment slot="after">
95
- <!-- `whitespace-pre-wrap inline-block` are needed to get correct newlines from `el.textContent` on Chrome -->
96
- <span
97
- class="{label ? 'mt-1.5' : ''} block w-full resize-y overflow-auto py-2 px-3 {size === 'small'
98
- ? 'min-h-[42px]'
99
- : 'min-h-[144px]'} inline-block max-h-[500px] whitespace-pre-wrap rounded-lg border border-gray-200 shadow-inner outline-none focus:shadow-inner focus:ring focus:ring-blue-200 dark:bg-gray-925"
100
- role="textbox"
101
- style="--placeholder: '{isDisabled ? '' : placeholder}'"
102
- spellcheck="false"
103
- dir="auto"
104
- contenteditable
105
- class:pointer-events-none={isLoading || isDisabled}
106
- use:onCmdEnter={{ disabled: isLoading || isDisabled }}
107
- on:cmdEnter
108
- bind:this={containerSpanEl}
109
- on:paste|preventDefault={handlePaste}
110
- on:input={updateInnerTextValue}
111
- on:focus={onFocus}
112
- on:blur={() => (isOnFocus = false)}
113
- />
114
- </svelte:fragment>
115
- </WidgetLabel>
 
 
 
 
 
 
 
 
116
 
117
  <style>
118
  span[contenteditable]:empty::before {
 
1
  <script lang="ts">
2
+ import { createEventDispatcher, tick } from "svelte";
3
 
4
  import { delay, onCmdEnter } from "../../../../utils/ViewUtils.js";
5
  import WidgetLabel from "../WidgetLabel/WidgetLabel.svelte";
6
+ import LogInPopover from "../../../LogInPopover/LogInPopover.svelte";
7
+ import { isLoggedIn } from "../../stores.js";
8
 
9
  export let label: string = "";
10
  export let placeholder: string = "Your sentence here...";
 
15
 
16
  let containerSpanEl: HTMLSpanElement;
17
  let isOnFocus = false;
18
+ let popOverOpen = false;
19
  const typingEffectSpeedMs = 12;
20
  const classNamesInput = "whitespace-pre-wrap inline font-normal text-black dark:text-white";
21
  const classNamesOutput = "whitespace-pre-wrap inline text-blue-600 dark:text-blue-400";
22
 
23
+ const dispatch = createEventDispatcher<{ cmdEnter: void }>();
24
+
25
  export async function renderTextOutput(outputTxt: string, typingEffect = true): Promise<void> {
26
  const spanEl = document.createElement("span");
27
  spanEl.contentEditable = isDisabled ? "false" : "true";
 
95
  }
96
  </script>
97
 
98
+ <LogInPopover bind:open={popOverOpen}>
99
+ <WidgetLabel {label}>
100
+ <svelte:fragment slot="after">
101
+ <!-- `whitespace-pre-wrap inline-block` are needed to get correct newlines from `el.textContent` on Chrome -->
102
+ <span
103
+ class="{label ? 'mt-1.5' : ''} block w-full resize-y overflow-auto py-2 px-3 {size === 'small'
104
+ ? 'min-h-[42px]'
105
+ : 'min-h-[144px]'} inline-block max-h-[500px] whitespace-pre-wrap rounded-lg border border-gray-200 shadow-inner outline-none focus:shadow-inner focus:ring focus:ring-blue-200 dark:bg-gray-925"
106
+ role="textbox"
107
+ style="--placeholder: '{isDisabled ? '' : placeholder}'"
108
+ spellcheck="false"
109
+ dir="auto"
110
+ contenteditable
111
+ class:pointer-events-none={isLoading || isDisabled}
112
+ use:onCmdEnter={{ disabled: isLoading || isDisabled }}
113
+ on:cmdEnter={() => {
114
+ if (!$isLoggedIn) {
115
+ popOverOpen = true;
116
+ return;
117
+ }
118
+ dispatch("cmdEnter");
119
+ }}
120
+ bind:this={containerSpanEl}
121
+ on:paste|preventDefault={handlePaste}
122
+ on:input={updateInnerTextValue}
123
+ on:focus={onFocus}
124
+ on:blur={() => (isOnFocus = false)}
125
+ />
126
+ </svelte:fragment>
127
+ </WidgetLabel>
128
+ </LogInPopover>
129
 
130
  <style>
131
  span[contenteditable]:empty::before {
packages/widgets/src/lib/components/InferenceWidget/shared/helpers.ts CHANGED
@@ -2,6 +2,8 @@ import type { ModelData, WidgetExampleAttribute } from "@huggingface/tasks";
2
  import { parseJSON } from "../../../utils/ViewUtils.js";
3
  import { ComputeType, type ModelLoadInfo, type TableData } from "./types.js";
4
  import { LoadState } from "./types.js";
 
 
5
 
6
  const KEYS_TEXT: WidgetExampleAttribute[] = ["text", "context", "candidate_labels"];
7
  const KEYS_TABLE: WidgetExampleAttribute[] = ["table", "structured_data"];
@@ -102,10 +104,10 @@ export async function callInferenceApi<T>(
102
  if (waitForModel) {
103
  headers.set("X-Wait-For-Model", "true");
104
  }
105
- if (useCache === false) {
106
  headers.set("X-Use-Cache", "false");
107
  }
108
- if (isOnLoadCall) {
109
  headers.set("X-Load-Model", "0");
110
  }
111
 
 
2
  import { parseJSON } from "../../../utils/ViewUtils.js";
3
  import { ComputeType, type ModelLoadInfo, type TableData } from "./types.js";
4
  import { LoadState } from "./types.js";
5
+ import { isLoggedIn } from "../stores.js";
6
+ import { get } from "svelte/store";
7
 
8
  const KEYS_TEXT: WidgetExampleAttribute[] = ["text", "context", "candidate_labels"];
9
  const KEYS_TABLE: WidgetExampleAttribute[] = ["table", "structured_data"];
 
104
  if (waitForModel) {
105
  headers.set("X-Wait-For-Model", "true");
106
  }
107
+ if (useCache === false && get(isLoggedIn)) {
108
  headers.set("X-Use-Cache", "false");
109
  }
110
+ if (isOnLoadCall || !get(isLoggedIn)) {
111
  headers.set("X-Load-Model", "0");
112
  }
113
 
packages/widgets/src/lib/components/InferenceWidget/stores.ts CHANGED
@@ -6,6 +6,8 @@ export const modelLoadStates = writable<Record<ModelData["id"], ModelLoadInfo>>(
6
 
7
  export const widgetNoInference = writable<Record<ModelData["id"], boolean>>({});
8
 
 
 
9
  export const widgetStates = writable<Record<ModelData["id"], WidgetState>>({});
10
 
11
  const tgiSupportedModels = writable<Set<string> | undefined>(undefined);
 
6
 
7
  export const widgetNoInference = writable<Record<ModelData["id"], boolean>>({});
8
 
9
+ export const isLoggedIn = writable<boolean>(false);
10
+
11
  export const widgetStates = writable<Record<ModelData["id"], WidgetState>>({});
12
 
13
  const tgiSupportedModels = writable<Set<string> | undefined>(undefined);
packages/widgets/src/lib/components/InferenceWidget/widgets/ConversationalWidget/ConversationalWidget.svelte CHANGED
@@ -21,7 +21,7 @@
21
  import WidgetQuickInput from "../../shared/WidgetQuickInput/WidgetQuickInput.svelte";
22
  import WidgetWrapper from "../../shared/WidgetWrapper/WidgetWrapper.svelte";
23
  import { addInferenceParameters, updateUrl } from "../../shared/helpers.js";
24
- import { widgetStates, getTgiSupportedModels } from "../../stores.js";
25
  import type { Writable } from "svelte/store";
26
  import { isChatInput, isTextInput } from "../../shared/inputValidation.js";
27
  import { isValidOutputText } from "../../shared/outputValidation.js";
@@ -152,10 +152,10 @@
152
  error = "";
153
  try {
154
  const opts = {
155
- dont_load_model: isOnLoadCall,
156
  includeCredentials,
157
  signal: abort?.signal,
158
- use_cache: useCache,
159
  wait_for_model: withModelLoading,
160
  } satisfies Options;
161
 
 
21
  import WidgetQuickInput from "../../shared/WidgetQuickInput/WidgetQuickInput.svelte";
22
  import WidgetWrapper from "../../shared/WidgetWrapper/WidgetWrapper.svelte";
23
  import { addInferenceParameters, updateUrl } from "../../shared/helpers.js";
24
+ import { widgetStates, getTgiSupportedModels, isLoggedIn } from "../../stores.js";
25
  import type { Writable } from "svelte/store";
26
  import { isChatInput, isTextInput } from "../../shared/inputValidation.js";
27
  import { isValidOutputText } from "../../shared/outputValidation.js";
 
152
  error = "";
153
  try {
154
  const opts = {
155
+ dont_load_model: isOnLoadCall || !$isLoggedIn,
156
  includeCredentials,
157
  signal: abort?.signal,
158
+ use_cache: useCache || !$isLoggedIn,
159
  wait_for_model: withModelLoading,
160
  } satisfies Options;
161
 
packages/widgets/src/lib/components/LogInPopover/LogInPopover.svelte ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import Popover from "../Popover/Popover.svelte";
3
+ import IconHuggingFace from "../Icons/IconHuggingFace.svelte";
4
+ import { isLoggedIn } from "../InferenceWidget/stores.js";
5
+
6
+ export let open = false;
7
+ export let classNames = "";
8
+
9
+ let anchorElement: HTMLElement;
10
+ </script>
11
+
12
+ <div bind:this={anchorElement} class={classNames}>
13
+ <slot />
14
+ </div>
15
+
16
+ {#if open && !$isLoggedIn}
17
+ <Popover classNames="w-80" {anchorElement} on:close={() => (open = false)}>
18
+ <div class="flex items-center gap-x-3 text-sm leading-tight">
19
+ <IconHuggingFace classNames="text-5xl" />
20
+ Please login with your Hugging Face account to run the widgets.
21
+ </div>
22
+ <div class="flex text-sm items-center gap-x-2.5 mt-2">
23
+ <a
24
+ href="https://huggingface.co/login{typeof window !== 'undefined'
25
+ ? `?next=${encodeURIComponent(window.location.href)}`
26
+ : ''}"
27
+ class="bg-black text-white px-3 py-1 rounded-full dark:!bg-gray-100 dark:!text-black">Log In</a
28
+ >
29
+ <span class="text-gray-400">or</span>
30
+ <a
31
+ href="https://huggingface.co/join{typeof window !== 'undefined'
32
+ ? `?next=${encodeURIComponent(window.location.href)}`
33
+ : ''}"
34
+ class="py-1 rounded-full underline decoration-gray-400 underline-offset-2">Create a free account</a
35
+ >
36
+ </div>
37
+ </Popover>
38
+ {/if}
packages/widgets/src/lib/components/Popover/Popover.svelte ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { onMount, tick, createEventDispatcher } from "svelte";
3
+ import { fade } from "svelte/transition";
4
+ import { debounce, portalToBody } from "../../utils/ViewUtils.js";
5
+
6
+ export let classNames = "";
7
+ export let anchorElement: HTMLElement;
8
+ export let alignment: "start" | "center" | "end" | "auto" = "auto";
9
+ export let placement: "top" | "bottom" | "auto" | "prefer-top" | "prefer-bottom" = "auto";
10
+ export let waitForContent = false;
11
+ export let size: "sm" | "md" = "md";
12
+ export let invertedColors = false;
13
+ export let touchOnly = false;
14
+
15
+ let popoverElement: HTMLDivElement;
16
+
17
+ /// sizes of the arrow and its padding, needed to position the popover position correctly
18
+ const ARROW_PADDING = 24;
19
+ const ARROW_SIZE = 10;
20
+
21
+ /// to prevent the toast from being too close to the edge of the screen
22
+ const HIT_ZONE_MARGIN = 80;
23
+
24
+ const dispatch = createEventDispatcher<{ close: void }>();
25
+
26
+ let computedAlignment = alignment === "auto" ? "center" : alignment;
27
+ let computedPlacement = placement === "auto" ? "bottom" : placement;
28
+
29
+ let left: number;
30
+ let top: number;
31
+ let width: number;
32
+ let height: number;
33
+
34
+ let popoverShift: number;
35
+ let isTouchOnly = false;
36
+ let isActive = true;
37
+
38
+ function updatePlacement(anchorBbox: DOMRect, pageHeight: number) {
39
+ if (pageHeight > 0) {
40
+ if (placement === "auto") {
41
+ /// check if the anchor is closer to the top or bottom of the page
42
+ computedPlacement = anchorBbox.top > pageHeight / 2 ? "top" : "bottom";
43
+ } else if (placement === "prefer-top") {
44
+ /// check if the toast has enough space to be placed above the anchor
45
+ const popoverHeight = popoverElement.getBoundingClientRect().height;
46
+ computedPlacement = anchorBbox.top > popoverHeight + HIT_ZONE_MARGIN ? "top" : "bottom";
47
+ } else if (placement === "prefer-bottom") {
48
+ /// check if the toast has enough space to be placed below the anchor
49
+ const popoverHeight = popoverElement.getBoundingClientRect().height;
50
+ computedPlacement =
51
+ anchorBbox.top + anchorBbox.height + popoverHeight + HIT_ZONE_MARGIN > pageHeight ? "top" : "bottom";
52
+ }
53
+ }
54
+ }
55
+
56
+ function updateAlignment(anchorBbox: DOMRect, pageWidth: number) {
57
+ if (alignment === "auto" && pageWidth > 0) {
58
+ const popoverWidth = popoverElement.getBoundingClientRect().width;
59
+ if (anchorBbox.left + popoverWidth > pageWidth - HIT_ZONE_MARGIN) {
60
+ computedAlignment = "end";
61
+ } else {
62
+ computedAlignment = "start";
63
+ }
64
+ }
65
+ }
66
+
67
+ async function updatePosition() {
68
+ if (anchorElement && !waitForContent) {
69
+ await tick();
70
+
71
+ const bbox = anchorElement.getBoundingClientRect();
72
+ updateAlignment(bbox, window.innerWidth);
73
+ updatePlacement(bbox, window.innerHeight);
74
+
75
+ left = bbox.left + window.scrollX;
76
+ top = bbox.top + window.scrollY;
77
+ width = bbox.width;
78
+ height = bbox.height;
79
+
80
+ /// shift the popover so the arrow is exaclty at the middle of the anchor
81
+ popoverShift = width / 2 - ARROW_SIZE / 2 - ARROW_PADDING;
82
+ }
83
+ }
84
+
85
+ const debouncedShow = debounce(() => (isActive = true), 250);
86
+
87
+ function hide() {
88
+ if (!popoverElement?.matches(":hover")) {
89
+ isActive = false;
90
+ }
91
+ }
92
+ const debouncedHide = debounce(hide, 250);
93
+
94
+ onMount(() => {
95
+ isTouchOnly = touchOnly && window.matchMedia("(any-hover: none)").matches;
96
+
97
+ if (!isTouchOnly) {
98
+ updatePosition();
99
+ if (anchorElement) {
100
+ anchorElement.addEventListener("mouseover", debouncedShow);
101
+ anchorElement.addEventListener("mouseleave", debouncedHide);
102
+ return () => {
103
+ anchorElement.removeEventListener("mouseover", debouncedShow);
104
+ anchorElement.removeEventListener("mouseleave", debouncedHide);
105
+ };
106
+ }
107
+ }
108
+ });
109
+ </script>
110
+
111
+ <svelte:window on:resize={() => dispatch("close")} on:scroll={() => dispatch("close")} />
112
+
113
+ <div class={isTouchOnly ? "hidden sm:contents" : "contents"} use:portalToBody>
114
+ <div
115
+ class="pointer-events-none absolute bg-transparent hidden"
116
+ class:hidden={!isActive}
117
+ style:top="{top}px"
118
+ style:left="{left}px"
119
+ style:width="{width}px"
120
+ style:height="{height}px"
121
+ >
122
+ <div
123
+ bind:this={popoverElement}
124
+ in:fade={{ duration: 100 }}
125
+ on:mouseleave={debouncedHide}
126
+ class="pointer-events-auto absolute z-10 transform
127
+ {computedPlacement === 'top' ? 'bottom-full -translate-y-3' : 'top-full translate-y-2.5'}
128
+ {computedAlignment === 'start' ? 'left-0' : computedAlignment === 'end' ? 'right-0' : 'left-1/2 -translate-x-1/2'}
129
+ {classNames}"
130
+ >
131
+ <div
132
+ class="absolute z-0 rotate-45 transform
133
+ {size === 'sm' ? 'h-2 w-2' : 'h-2.5 w-2.5 rounded-sm'}
134
+ {invertedColors ? 'bg-black dark:bg-gray-800' : 'border bg-white shadow dark:bg-gray-800'}
135
+ {computedPlacement === 'top' ? 'top-full -translate-y-1' : 'bottom-full translate-y-1'}
136
+ {computedAlignment === 'start' ? 'left-6' : computedAlignment === 'center' ? 'left-1/2' : 'right-6'}"
137
+ />
138
+ <div
139
+ class="shadow-alternate-xl relative z-5 border font-normal leading-tight transition-opacity
140
+ {size === 'sm' ? 'rounded px-2 py-1.5' : 'rounded-xl p-4'}
141
+ {invertedColors ? 'border-black bg-black text-white dark:bg-gray-800' : 'bg-white text-black dark:bg-gray-925'}
142
+ "
143
+ >
144
+ <slot />
145
+ </div>
146
+ </div>
147
+ </div>
148
+ </div>
packages/widgets/src/lib/utils/ViewUtils.ts CHANGED
@@ -156,6 +156,42 @@ export function onCmdEnter(node: HTMLElement, opts: { disabled: boolean }): Acti
156
  };
157
  }
158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  /**
160
  * For Tailwind:
161
  bg-blue-100 border-blue-100 dark:bg-blue-800 dark:border-blue-800
 
156
  };
157
  }
158
 
159
+ /**
160
+ * A debounce function that works in both browser and Nodejs.
161
+ */
162
+ export function debounce<T extends unknown[]>(callback: (...rest: T) => unknown, limit: number): (...rest: T) => void {
163
+ let timer: ReturnType<typeof setTimeout>;
164
+
165
+ return function (...rest) {
166
+ clearTimeout(timer);
167
+ timer = setTimeout(() => {
168
+ callback(...rest);
169
+ }, limit);
170
+ };
171
+ }
172
+
173
+ /**
174
+ * Teleports the children of a node to another node....
175
+ */
176
+ export function portal(node: HTMLElement, targetNode: HTMLElement): { destroy: () => void } {
177
+ const portalChildren = [...node.children];
178
+ targetNode.append(...portalChildren);
179
+ return {
180
+ destroy() {
181
+ for (const portalChild of portalChildren) {
182
+ portalChild.remove();
183
+ }
184
+ },
185
+ };
186
+ }
187
+
188
+ /**
189
+ * Teleports the children of a node under the body element
190
+ */
191
+ export function portalToBody(node: HTMLElement): { destroy: () => void } {
192
+ return portal(node, document.body);
193
+ }
194
+
195
  /**
196
  * For Tailwind:
197
  bg-blue-100 border-blue-100 dark:bg-blue-800 dark:border-blue-800
packages/widgets/src/routes/+page.svelte CHANGED
@@ -6,6 +6,7 @@
6
  import ModeSwitcher from "$lib/components/DemoThemeSwitcher/DemoThemeSwitcher.svelte";
7
  import { onMount } from "svelte";
8
  import { browser } from "$app/environment";
 
9
 
10
  export let data;
11
  let apiToken = data.session?.access_token || "";
@@ -627,6 +628,12 @@
627
  <input class="form-input" type="text" bind:value={apiToken} placeholder="hf_..." on:change={storeHFToken} />
628
  </label>
629
  {/if}
 
 
 
 
 
 
630
 
631
  <div>
632
  <h1 class="mb-8 text-4xl font-semibold">Showcase of all types of inference widgets running</h1>
 
6
  import ModeSwitcher from "$lib/components/DemoThemeSwitcher/DemoThemeSwitcher.svelte";
7
  import { onMount } from "svelte";
8
  import { browser } from "$app/environment";
9
+ import { isLoggedIn } from "$lib/components/InferenceWidget/stores.js";
10
 
11
  export let data;
12
  let apiToken = data.session?.access_token || "";
 
628
  <input class="form-input" type="text" bind:value={apiToken} placeholder="hf_..." on:change={storeHFToken} />
629
  </label>
630
  {/if}
631
+ <label>
632
+ <div class="text-xl font-semibold">
633
+ isLoggedIn <span class="text-sm">(simulate isLoggedIn store by toggling)</span>
634
+ </div>
635
+ <input type="checkbox" bind:checked={$isLoggedIn} />
636
+ </label>
637
 
638
  <div>
639
  <h1 class="mb-8 text-4xl font-semibold">Showcase of all types of inference widgets running</h1>