Skip to content

Commit eae7864

Browse files
Mishig Davaadorjgary149
andauthored
Fix sample chooser for vision & audio widgets (#448)
* Fix sample chooser for img & aud widgets * Rename img widget example attr to `src` * Make widgetinputsamples wider & rounded (#445) * Make widgetinputsamples wider & rounded * larger examples select Co-authored-by: Victor Muštar <[email protected]> * Fix sample chooser for img & aud widgets * Rename img widget example attr to `src` * Implement aud & vision samples previewer for desktop * Revert dev change * Examples previewer for touch devices * Format * Close options on out of focus * Refactor * Fix small css issue for img class widget * Add comment * Format * Aud widgets call Inference on sample chosen * css style * Add preview capacility to NLP widgets Co-authored-by: Victor Muštar <[email protected]>
1 parent 40068f6 commit eae7864

File tree

24 files changed

+355
-167
lines changed

24 files changed

+355
-167
lines changed

widgets/src/lib/InferenceWidget/shared/WidgetDropzone/WidgetDropzone.svelte

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script>
22
import IconSpin from "../../../Icons/IconSpin.svelte";
3-
import { proxify } from "../../shared/helpers";
3+
import { getBlobFromUrl } from "../../shared/helpers";
44
55
export let accept = "image/*";
66
export let classNames = "";
@@ -38,9 +38,7 @@
3838
const url = await new Promise<string>((resolve) =>
3939
uriItem.getAsString((s) => resolve(s))
4040
);
41-
const proxiedUrl = proxify(url);
42-
const res = await fetch(proxiedUrl);
43-
const file = await res.blob();
41+
const file = await getBlobFromUrl(url);
4442
4543
onSelectFile(file);
4644
} else if (fileItem) {
Lines changed: 125 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,132 @@
11
<script>
2+
import { slide } from "svelte/transition";
3+
24
export let inputSamples: Record<string, any>[];
3-
export let applyInputSample: (sample: Record<string, any>[]) => void;
5+
export let applyInputSample: (sample: Record<string, any>) => void;
6+
export let previewInputSample: (sample: Record<string, any>) => void;
7+
8+
let containerEl: HTMLElement;
9+
let isOptionsVisible = false;
10+
let isTouchOptionClicked = false;
11+
let touchSelectedIdx: number;
12+
let title = "Examples";
413
5-
function onChange(e: Event) {
6-
const { value } = e.target as HTMLOptionElement;
7-
const sample = inputSamples[value];
14+
function _applyInputSample(idx: number) {
15+
hideOptions();
16+
const sample = inputSamples[idx];
17+
title = sample.example_title;
818
applyInputSample(sample);
919
}
20+
21+
function _previewInputSample(idx: number, isTocuh = false) {
22+
const sample = inputSamples[idx];
23+
if (isTocuh) {
24+
isTouchOptionClicked = true;
25+
touchSelectedIdx = idx;
26+
}
27+
previewInputSample(sample);
28+
}
29+
30+
function toggleOptionsVisibility() {
31+
isOptionsVisible = !isOptionsVisible;
32+
}
33+
34+
function onClick(e: MouseEvent | TouchEvent) {
35+
let targetElement = e.target;
36+
do {
37+
if (targetElement == containerEl) {
38+
// This is a click inside. Do nothing, just return.
39+
return;
40+
}
41+
targetElement = (targetElement as HTMLElement).parentElement;
42+
} while (targetElement);
43+
// This is a click outside
44+
hideOptions();
45+
}
46+
47+
function hideOptions() {
48+
isOptionsVisible = false;
49+
isTouchOptionClicked = false;
50+
}
1051
</script>
1152

12-
<!-- svelte-ignore a11y-no-onchange -->
13-
<select
14-
class="text-sm py-1 w-32 lg:w-44 border border-gray-100 truncate dark:bg-gray-950 rounded"
15-
on:change={onChange}
16-
>
17-
<option selected disabled>Examples</option>
18-
{#each inputSamples as { example_title }, i}
19-
<option value={i}>{example_title}</option>
20-
{/each}
21-
</select>
53+
<svelte:window on:click={onClick} />
54+
55+
<div class="relative z-10 ml-2" bind:this={containerEl}>
56+
<div
57+
class="no-hover:hidden inline-flex justify-between w-32 lg:w-44 rounded-md border border-gray-100 px-4 py-1"
58+
on:click={toggleOptionsVisibility}
59+
>
60+
<p class="text-sm truncate">{title}</p>
61+
<svg
62+
class="-mr-1 ml-2 h-5 w-5 transition ease-in-out transform {isOptionsVisible &&
63+
'-rotate-180'}"
64+
xmlns="http://www.w3.org/2000/svg"
65+
viewBox="0 0 20 20"
66+
fill="currentColor"
67+
aria-hidden="true"
68+
>
69+
<path
70+
fill-rule="evenodd"
71+
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
72+
clip-rule="evenodd"
73+
/>
74+
</svg>
75+
</div>
76+
{#if !isTouchOptionClicked}
77+
<div
78+
class="with-hover:hidden inline-flex justify-between w-32 lg:w-44 rounded-md border border-gray-100 px-4 py-1"
79+
on:click={toggleOptionsVisibility}
80+
>
81+
<p class="text-sm truncate">{title}</p>
82+
<svg
83+
class="-mr-1 ml-2 h-5 w-5 transition ease-in-out transform {isOptionsVisible &&
84+
'-rotate-180'}"
85+
xmlns="http://www.w3.org/2000/svg"
86+
viewBox="0 0 20 20"
87+
fill="currentColor"
88+
aria-hidden="true"
89+
>
90+
<path
91+
fill-rule="evenodd"
92+
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
93+
clip-rule="evenodd"
94+
/>
95+
</svg>
96+
</div>
97+
{:else}
98+
<!-- Better UX for mobile/table through CSS breakpoints -->
99+
<div
100+
class="with-hover:hidden inline-flex justify-center w-32 lg:w-44 rounded-md border border-green-500 px-4 py-1"
101+
on:click={() => _applyInputSample(touchSelectedIdx)}
102+
>
103+
<p class="text-green-500">Confirm</p>
104+
</div>
105+
{/if}
106+
107+
{#if isOptionsVisible}
108+
<div
109+
class="origin-top-right absolute right-0 mt-1 w-full rounded-md ring-1 ring-black ring-opacity-10"
110+
transition:slide
111+
>
112+
<div class="py-1 bg-white rounded-md" role="none">
113+
{#each inputSamples as { example_title }, i}
114+
<p
115+
class="no-hover:hidden px-4 py-2 text-sm hover:bg-gray-100 hover:text-gray-900 dark:hover:bg-gray-800 dark:hover:text-gray-200"
116+
on:mouseover={() => _previewInputSample(i)}
117+
on:click={() => _applyInputSample(i)}
118+
>
119+
{example_title}
120+
</p>
121+
<!-- Better UX for mobile/table through CSS breakpoints -->
122+
<p
123+
class="with-hover:hidden px-4 py-2 text-sm hover:bg-gray-100 hover:text-gray-900 dark:hover:bg-gray-800 dark:hover:text-gray-200"
124+
on:click={() => _previewInputSample(i, true)}
125+
>
126+
{example_title}
127+
</p>
128+
{/each}
129+
</div>
130+
</div>
131+
{/if}
132+
</div>

widgets/src/lib/InferenceWidget/shared/WidgetWrapper/WidgetWrapper.svelte

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
};
2121
export let noTitle = false;
2222
export let outputJson: string;
23-
export let applyInputSample: (sample: Record<string, any>[]) => void =
24-
([]) => {};
23+
export let applyInputSample: (sample: Record<string, any>) => void =
24+
({}) => {};
25+
export let previewInputSample: (sample: Record<string, any>) => void =
26+
({}) => {};
2527
2628
let isMaximized = false;
2729
let modelStatus: LoadingStatus = "unknown";
@@ -55,13 +57,19 @@
5557
</button>
5658
{/if}
5759
<WidgetHeader {noTitle} pipeline={model.pipeline_tag}>
58-
{#if model.pipeline_tag === "fill-mask"}
59-
Mask token: <code>{model.mask_token}</code>
60-
{/if}
61-
{#if inputSamples.length > 1}
62-
<!-- Show samples selector when there are more than one sample -->
63-
<WidgetInputSamples {inputSamples} {applyInputSample} />
64-
{/if}
60+
<div class="flex items-center">
61+
{#if model.pipeline_tag === "fill-mask"}
62+
Mask token: <code>{model.mask_token}</code>
63+
{/if}
64+
{#if inputSamples.length > 1}
65+
<!-- Show samples selector when there are more than one sample -->
66+
<WidgetInputSamples
67+
{inputSamples}
68+
{applyInputSample}
69+
{previewInputSample}
70+
/>
71+
{/if}
72+
</div>
6573
</WidgetHeader>
6674
<slot name="top" />
6775
<WidgetInfo {computeTime} {error} {modelStatus} />

widgets/src/lib/InferenceWidget/shared/helpers.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,21 @@ export function updateUrl(obj: Record<string, string>) {
4040
}
4141

4242
// Run through our own proxy to bypass CORS:
43-
export function proxify(url: string): string {
43+
function proxify(url: string): string {
4444
return url.startsWith(`http://localhost`)
4545
|| new URL(url).host === window.location.host
4646
? url
4747
: `https://widgets-cors-proxy.huggingface.co/proxy?url=${url}`;
4848
}
4949

50+
// Get BLOB from a given URL after proxifying the URL
51+
export async function getBlobFromUrl(url: string): Promise<Blob>{
52+
const proxiedUrl = proxify(url);
53+
const res = await fetch(proxiedUrl);
54+
const blob = await res.blob();
55+
return blob;
56+
}
57+
5058
async function callApi(
5159
url: string,
5260
repoId: string,

widgets/src/lib/InferenceWidget/shared/types.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,16 @@ export type TableData = Record<string, (string | number)[]>;
1616

1717
export type HighlightCoordinates = Record<string, string>;
1818

19-
export type Box = {
19+
type Box = {
2020
xmin: number;
2121
ymin: number;
2222
xmax: number;
2323
ymax: number;
2424
};
25+
26+
export type DetectedObject = {
27+
box: Box;
28+
label: string;
29+
score: number;
30+
color?: string;
31+
}

widgets/src/lib/InferenceWidget/widgets/AudioClassificationWidget/AudioClassificationWidget.svelte

Lines changed: 23 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,16 @@
44
import WidgetAudioTrack from "../../shared/WidgetAudioTrack/WidgetAudioTrack.svelte";
55
import WidgetFileInput from "../../shared/WidgetFileInput/WidgetFileInput.svelte";
66
import WidgetOutputChart from "../../shared/WidgetOutputChart/WidgetOutputChart.svelte";
7-
import WidgetRadio from "../../shared/WidgetRadio/WidgetRadio.svelte";
87
import WidgetRecorder from "../../shared/WidgetRecorder/WidgetRecorder.svelte";
98
import WidgetSubmitBtn from "../../shared/WidgetSubmitBtn/WidgetSubmitBtn.svelte";
109
import WidgetWrapper from "../../shared/WidgetWrapper/WidgetWrapper.svelte";
11-
import { getResponse, proxify } from "../../shared/helpers";
10+
import { getResponse, getBlobFromUrl } from "../../shared/helpers";
1211
1312
export let apiToken: WidgetProps["apiToken"];
1413
export let apiUrl: WidgetProps["apiUrl"];
1514
export let model: WidgetProps["model"];
1615
export let noTitle: WidgetProps["noTitle"];
1716
18-
let areSamplesVisible = true;
1917
let computeTime = "";
2018
let error: string = "";
2119
let file: Blob | File | null = null;
@@ -30,15 +28,9 @@
3028
let output: Array<{ label: string; score: number }> = [];
3129
let outputJson: string;
3230
let selectedSampleUrl = "";
33-
34-
function onChangeRadio() {
35-
file = null;
36-
filename = "";
37-
fileUrl = "";
38-
}
31+
let shouldAudioAutoplay = true;
3932
4033
function onRecordStart() {
41-
areSamplesVisible = false;
4234
file = null;
4335
filename = "";
4436
fileUrl = "";
@@ -50,7 +42,7 @@
5042
}
5143
5244
function onSelectFile(updatedFile: Blob | File) {
53-
areSamplesVisible = false;
45+
shouldAudioAutoplay = false;
5446
isRecording = false;
5547
selectedSampleUrl = "";
5648
@@ -75,9 +67,7 @@
7567
}
7668
7769
if (!file && selectedSampleUrl) {
78-
const proxiedUrl = proxify(selectedSampleUrl);
79-
const res = await fetch(proxiedUrl);
80-
file = await res.blob();
70+
file = await getBlobFromUrl(selectedSampleUrl);
8171
}
8272
8373
const requestBody = { file };
@@ -135,7 +125,19 @@
135125
}
136126
137127
function applyInputSample(sample: Record<string, any>) {
128+
shouldAudioAutoplay = false;
129+
filename = sample.example_title;
130+
fileUrl = sample.src;
131+
selectedSampleUrl = sample.src;
132+
getOutput();
133+
}
134+
135+
function previewInputSample(sample: Record<string, any>) {
136+
shouldAudioAutoplay = true;
137+
filename = sample.example_title;
138138
fileUrl = sample.src;
139+
output = [];
140+
outputJson = "";
139141
}
140142
</script>
141143

@@ -148,6 +150,7 @@
148150
{modelLoading}
149151
{noTitle}
150152
{outputJson}
153+
{previewInputSample}
151154
>
152155
<svelte:fragment slot="top">
153156
<form>
@@ -166,29 +169,12 @@
166169
/>
167170
</div>
168171
{#if fileUrl}
169-
<WidgetAudioTrack classNames="mt-3" label={filename} src={fileUrl} />
170-
{/if}
171-
{#if model.widgetData}
172-
<details
173-
open={areSamplesVisible}
174-
class="text-gray-500 text-sm mt-4 mb-2"
175-
>
176-
<summary class="mb-2">Or pick a sample audio file</summary>
177-
<div class="mt-4 space-y-5">
178-
<!-- Shouldnt this be an option ? -->
179-
{#each model.widgetData as widgetData}
180-
<WidgetAudioTrack classNames="mt-3" src={String(widgetData.src)}>
181-
<WidgetRadio
182-
bind:group={selectedSampleUrl}
183-
classNames="mb-1.5"
184-
label={String(widgetData.label)}
185-
onChange={onChangeRadio}
186-
value={String(widgetData.src)}
187-
/>
188-
</WidgetAudioTrack>
189-
{/each}
190-
</div>
191-
</details>
172+
<WidgetAudioTrack
173+
classNames="mt-3"
174+
autoplay={shouldAudioAutoplay}
175+
label={filename}
176+
src={fileUrl}
177+
/>
192178
{/if}
193179
<WidgetSubmitBtn
194180
classNames="mt-2"

0 commit comments

Comments
 (0)