@gradio/multimodaltextbox 0.5.5 → 0.6.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/Example.svelte +1 -1
- package/Index.svelte +18 -2
- package/MultimodalTextbox.test.ts +1 -2
- package/dist/Example.svelte +96 -0
- package/dist/Example.svelte.d.ts +22 -0
- package/dist/Index.svelte +104 -0
- package/dist/Index.svelte.d.ts +171 -0
- package/dist/shared/MultimodalTextbox.svelte +543 -0
- package/dist/shared/MultimodalTextbox.svelte.d.ts +60 -0
- package/dist/shared/utils.d.ts +8 -0
- package/dist/shared/utils.js +39 -0
- package/package.json +28 -12
- package/shared/MultimodalTextbox.svelte +211 -102
- package/shared/utils.ts +6 -7
|
@@ -10,7 +10,15 @@
|
|
|
10
10
|
import { Upload } from "@gradio/upload";
|
|
11
11
|
import { Image } from "@gradio/image/shared";
|
|
12
12
|
import type { FileData, Client } from "@gradio/client";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
Clear,
|
|
15
|
+
File,
|
|
16
|
+
Music,
|
|
17
|
+
Paperclip,
|
|
18
|
+
Video,
|
|
19
|
+
Send,
|
|
20
|
+
Square
|
|
21
|
+
} from "@gradio/icons";
|
|
14
22
|
import type { SelectData } from "@gradio/utils";
|
|
15
23
|
|
|
16
24
|
export let value: { text: string; files: FileData[] } = {
|
|
@@ -28,6 +36,7 @@
|
|
|
28
36
|
export let container = true;
|
|
29
37
|
export let max_lines: number;
|
|
30
38
|
export let submit_btn: string | boolean | null = null;
|
|
39
|
+
export let stop_btn: string | boolean | null = null;
|
|
31
40
|
export let rtl = false;
|
|
32
41
|
export let autofocus = false;
|
|
33
42
|
export let text_align: "left" | "right" | undefined = undefined;
|
|
@@ -45,11 +54,13 @@
|
|
|
45
54
|
let can_scroll: boolean;
|
|
46
55
|
let previous_scroll_top = 0;
|
|
47
56
|
let user_has_scrolled_up = false;
|
|
48
|
-
let dragging = false;
|
|
57
|
+
export let dragging = false;
|
|
49
58
|
let uploading = false;
|
|
50
59
|
let oldValue = value.text;
|
|
51
60
|
$: dispatch("drag", dragging);
|
|
52
61
|
|
|
62
|
+
let full_container: HTMLDivElement;
|
|
63
|
+
|
|
53
64
|
$: if (oldValue !== value.text) {
|
|
54
65
|
dispatch("change", value);
|
|
55
66
|
oldValue = value.text;
|
|
@@ -73,6 +84,7 @@
|
|
|
73
84
|
const dispatch = createEventDispatcher<{
|
|
74
85
|
change: typeof value;
|
|
75
86
|
submit: undefined;
|
|
87
|
+
stop: undefined;
|
|
76
88
|
blur: undefined;
|
|
77
89
|
select: SelectData;
|
|
78
90
|
input: undefined;
|
|
@@ -186,7 +198,11 @@
|
|
|
186
198
|
}
|
|
187
199
|
}
|
|
188
200
|
|
|
189
|
-
|
|
201
|
+
function handle_stop(): void {
|
|
202
|
+
dispatch("stop");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function handle_submit(): void {
|
|
190
206
|
dispatch("submit");
|
|
191
207
|
}
|
|
192
208
|
|
|
@@ -201,109 +217,181 @@
|
|
|
201
217
|
}
|
|
202
218
|
}
|
|
203
219
|
}
|
|
220
|
+
|
|
221
|
+
function handle_dragenter(event: DragEvent): void {
|
|
222
|
+
event.preventDefault();
|
|
223
|
+
dragging = true;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function handle_dragleave(event: DragEvent): void {
|
|
227
|
+
event.preventDefault();
|
|
228
|
+
const rect = full_container.getBoundingClientRect();
|
|
229
|
+
const { clientX, clientY } = event;
|
|
230
|
+
if (
|
|
231
|
+
clientX <= rect.left ||
|
|
232
|
+
clientX >= rect.right ||
|
|
233
|
+
clientY <= rect.top ||
|
|
234
|
+
clientY >= rect.bottom
|
|
235
|
+
) {
|
|
236
|
+
dragging = false;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function handle_drop(event: DragEvent): void {
|
|
241
|
+
event.preventDefault();
|
|
242
|
+
dragging = false;
|
|
243
|
+
if (event.dataTransfer && event.dataTransfer.files) {
|
|
244
|
+
upload_component.load_files(Array.from(event.dataTransfer.files));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
204
247
|
</script>
|
|
205
248
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
{
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
249
|
+
<div
|
|
250
|
+
class="full-container"
|
|
251
|
+
class:dragging
|
|
252
|
+
bind:this={full_container}
|
|
253
|
+
on:dragenter={handle_dragenter}
|
|
254
|
+
on:dragleave={handle_dragleave}
|
|
255
|
+
on:dragover|preventDefault
|
|
256
|
+
on:drop={handle_drop}
|
|
257
|
+
role="group"
|
|
258
|
+
aria-label="Multimedia input field"
|
|
259
|
+
>
|
|
260
|
+
<!-- svelte-ignore a11y-autofocus -->
|
|
261
|
+
<label class:container>
|
|
262
|
+
<BlockTitle {show_label} {info}>{label}</BlockTitle>
|
|
263
|
+
{#if value.files.length > 0 || uploading}
|
|
264
|
+
<div
|
|
265
|
+
class="thumbnails scroll-hide"
|
|
266
|
+
aria-label="Uploaded files"
|
|
267
|
+
data-testid="container_el"
|
|
268
|
+
style="display: {value.files.length > 0 || uploading
|
|
269
|
+
? 'flex'
|
|
270
|
+
: 'none'};"
|
|
271
|
+
>
|
|
272
|
+
{#each value.files as file, index}
|
|
273
|
+
<span role="listitem" aria-label="File thumbnail">
|
|
274
|
+
<button class="thumbnail-item thumbnail-small">
|
|
275
|
+
<button
|
|
276
|
+
class:disabled
|
|
277
|
+
class="delete-button"
|
|
278
|
+
on:click={(event) => remove_thumbnail(event, index)}
|
|
279
|
+
><Clear /></button
|
|
280
|
+
>
|
|
281
|
+
{#if file.mime_type && file.mime_type.includes("image")}
|
|
282
|
+
<Image
|
|
283
|
+
src={file.url}
|
|
284
|
+
title={null}
|
|
285
|
+
alt=""
|
|
286
|
+
loading="lazy"
|
|
287
|
+
class={"thumbnail-image"}
|
|
288
|
+
/>
|
|
289
|
+
{:else if file.mime_type && file.mime_type.includes("audio")}
|
|
290
|
+
<Music />
|
|
291
|
+
{:else if file.mime_type && file.mime_type.includes("video")}
|
|
292
|
+
<Video />
|
|
293
|
+
{:else}
|
|
294
|
+
<File />
|
|
295
|
+
{/if}
|
|
296
|
+
</button>
|
|
297
|
+
</span>
|
|
298
|
+
{/each}
|
|
299
|
+
{#if uploading}
|
|
300
|
+
<div class="loader" role="status" aria-label="Uploading"></div>
|
|
301
|
+
{/if}
|
|
302
|
+
</div>
|
|
303
|
+
{/if}
|
|
304
|
+
<div class="input-container">
|
|
305
|
+
<Upload
|
|
306
|
+
bind:this={upload_component}
|
|
307
|
+
on:load={handle_upload}
|
|
308
|
+
{file_count}
|
|
309
|
+
{root}
|
|
310
|
+
{max_file_size}
|
|
311
|
+
bind:dragging
|
|
312
|
+
bind:uploading
|
|
313
|
+
show_progress={false}
|
|
314
|
+
disable_click={true}
|
|
315
|
+
bind:hidden_upload
|
|
316
|
+
on:error
|
|
317
|
+
hidden={true}
|
|
318
|
+
{upload}
|
|
319
|
+
{stream_handler}
|
|
320
|
+
></Upload>
|
|
321
|
+
<button
|
|
322
|
+
data-testid="upload-button"
|
|
323
|
+
class="upload-button"
|
|
324
|
+
on:click={handle_upload_click}><Paperclip /></button
|
|
325
|
+
>
|
|
326
|
+
<textarea
|
|
327
|
+
data-testid="textbox"
|
|
328
|
+
use:text_area_resize={{
|
|
329
|
+
text: value.text,
|
|
330
|
+
lines: lines,
|
|
331
|
+
max_lines: max_lines
|
|
332
|
+
}}
|
|
333
|
+
class="scroll-hide"
|
|
334
|
+
dir={rtl ? "rtl" : "ltr"}
|
|
335
|
+
bind:value={value.text}
|
|
336
|
+
bind:this={el}
|
|
337
|
+
{placeholder}
|
|
338
|
+
rows={lines}
|
|
339
|
+
{disabled}
|
|
340
|
+
{autofocus}
|
|
341
|
+
on:keypress={handle_keypress}
|
|
342
|
+
on:blur
|
|
343
|
+
on:select={handle_select}
|
|
344
|
+
on:focus
|
|
345
|
+
on:scroll={handle_scroll}
|
|
346
|
+
on:paste={handle_paste}
|
|
347
|
+
style={text_align ? "text-align: " + text_align : ""}
|
|
348
|
+
/>
|
|
349
|
+
{#if submit_btn}
|
|
350
|
+
<button
|
|
351
|
+
class="submit-button"
|
|
352
|
+
class:padded-button={submit_btn !== true}
|
|
353
|
+
on:click={handle_submit}
|
|
354
|
+
>
|
|
355
|
+
{#if submit_btn === true}
|
|
356
|
+
<Send />
|
|
235
357
|
{:else}
|
|
236
|
-
|
|
358
|
+
{submit_btn}
|
|
359
|
+
{/if}
|
|
360
|
+
</button>
|
|
361
|
+
{/if}
|
|
362
|
+
{#if stop_btn}
|
|
363
|
+
<button
|
|
364
|
+
class="stop-button"
|
|
365
|
+
class:padded-button={stop_btn !== true}
|
|
366
|
+
on:click={handle_stop}
|
|
367
|
+
>
|
|
368
|
+
{#if stop_btn === true}
|
|
369
|
+
<Square />
|
|
370
|
+
{:else}
|
|
371
|
+
{stop_btn}
|
|
237
372
|
{/if}
|
|
238
373
|
</button>
|
|
239
|
-
{/each}
|
|
240
|
-
{#if uploading}
|
|
241
|
-
<div class="loader"></div>
|
|
242
374
|
{/if}
|
|
243
375
|
</div>
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
<Upload
|
|
247
|
-
bind:this={upload_component}
|
|
248
|
-
on:load={handle_upload}
|
|
249
|
-
{file_count}
|
|
250
|
-
{root}
|
|
251
|
-
{max_file_size}
|
|
252
|
-
bind:dragging
|
|
253
|
-
bind:uploading
|
|
254
|
-
show_progress={false}
|
|
255
|
-
disable_click={true}
|
|
256
|
-
bind:hidden_upload
|
|
257
|
-
on:error
|
|
258
|
-
hidden={true}
|
|
259
|
-
{upload}
|
|
260
|
-
{stream_handler}
|
|
261
|
-
></Upload>
|
|
262
|
-
<button
|
|
263
|
-
data-testid="upload-button"
|
|
264
|
-
class="upload-button"
|
|
265
|
-
on:click={handle_upload_click}><Paperclip /></button
|
|
266
|
-
>
|
|
267
|
-
<textarea
|
|
268
|
-
data-testid="textbox"
|
|
269
|
-
use:text_area_resize={{
|
|
270
|
-
text: value.text,
|
|
271
|
-
lines: lines,
|
|
272
|
-
max_lines: max_lines
|
|
273
|
-
}}
|
|
274
|
-
class="scroll-hide"
|
|
275
|
-
dir={rtl ? "rtl" : "ltr"}
|
|
276
|
-
bind:value={value.text}
|
|
277
|
-
bind:this={el}
|
|
278
|
-
{placeholder}
|
|
279
|
-
rows={lines}
|
|
280
|
-
{disabled}
|
|
281
|
-
{autofocus}
|
|
282
|
-
on:keypress={handle_keypress}
|
|
283
|
-
on:blur
|
|
284
|
-
on:select={handle_select}
|
|
285
|
-
on:focus
|
|
286
|
-
on:scroll={handle_scroll}
|
|
287
|
-
on:paste={handle_paste}
|
|
288
|
-
style={text_align ? "text-align: " + text_align : ""}
|
|
289
|
-
/>
|
|
290
|
-
{#if submit_btn}
|
|
291
|
-
<button
|
|
292
|
-
class="submit-button"
|
|
293
|
-
class:padded-button={submit_btn !== true}
|
|
294
|
-
on:click={handle_submit}
|
|
295
|
-
>
|
|
296
|
-
{#if submit_btn === true}
|
|
297
|
-
<Send />
|
|
298
|
-
{:else}
|
|
299
|
-
Hello World
|
|
300
|
-
{/if}
|
|
301
|
-
</button>
|
|
302
|
-
{/if}
|
|
303
|
-
</div>
|
|
304
|
-
</label>
|
|
376
|
+
</label>
|
|
377
|
+
</div>
|
|
305
378
|
|
|
306
379
|
<style>
|
|
380
|
+
.full-container {
|
|
381
|
+
width: 100%;
|
|
382
|
+
position: relative;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.full-container.dragging::after {
|
|
386
|
+
content: "";
|
|
387
|
+
position: absolute;
|
|
388
|
+
top: 0;
|
|
389
|
+
left: 0;
|
|
390
|
+
right: 0;
|
|
391
|
+
bottom: 0;
|
|
392
|
+
pointer-events: none;
|
|
393
|
+
}
|
|
394
|
+
|
|
307
395
|
.input-container {
|
|
308
396
|
display: flex;
|
|
309
397
|
position: relative;
|
|
@@ -323,6 +411,8 @@
|
|
|
323
411
|
margin-top: 0px;
|
|
324
412
|
margin-bottom: 0px;
|
|
325
413
|
resize: none;
|
|
414
|
+
position: relative;
|
|
415
|
+
z-index: 1;
|
|
326
416
|
}
|
|
327
417
|
|
|
328
418
|
textarea:disabled {
|
|
@@ -335,9 +425,8 @@
|
|
|
335
425
|
}
|
|
336
426
|
|
|
337
427
|
.upload-button,
|
|
338
|
-
.submit-button
|
|
339
|
-
|
|
340
|
-
color: var(--button-secondary-text-color);
|
|
428
|
+
.submit-button,
|
|
429
|
+
.stop-button {
|
|
341
430
|
border: none;
|
|
342
431
|
text-align: center;
|
|
343
432
|
text-decoration: none;
|
|
@@ -351,11 +440,18 @@
|
|
|
351
440
|
justify-content: center;
|
|
352
441
|
align-items: center;
|
|
353
442
|
margin-bottom: 5px;
|
|
443
|
+
z-index: var(--layer-1);
|
|
354
444
|
}
|
|
355
445
|
.padded-button {
|
|
356
446
|
padding: 0 10px;
|
|
357
447
|
}
|
|
358
448
|
|
|
449
|
+
.upload-button,
|
|
450
|
+
.submit-button {
|
|
451
|
+
background: var(--button-secondary-background-fill);
|
|
452
|
+
color: var(--button-secondary-text-color);
|
|
453
|
+
}
|
|
454
|
+
|
|
359
455
|
.upload-button:hover,
|
|
360
456
|
.submit-button:hover {
|
|
361
457
|
background: var(--button-secondary-background-fill-hover);
|
|
@@ -375,6 +471,19 @@
|
|
|
375
471
|
width: 17px;
|
|
376
472
|
}
|
|
377
473
|
|
|
474
|
+
.stop-button {
|
|
475
|
+
background: var(--button-cancel-background-fill);
|
|
476
|
+
color: var(--button-cancel-text-color);
|
|
477
|
+
}
|
|
478
|
+
.stop-button:hover {
|
|
479
|
+
background: var(--button-cancel-background-fill-hover);
|
|
480
|
+
color: var(--button-cancel-text-color-hover);
|
|
481
|
+
}
|
|
482
|
+
.stop-button :global(svg) {
|
|
483
|
+
height: 18px;
|
|
484
|
+
width: 18px;
|
|
485
|
+
}
|
|
486
|
+
|
|
378
487
|
.loader {
|
|
379
488
|
display: flex;
|
|
380
489
|
justify-content: center;
|
|
@@ -406,11 +515,11 @@
|
|
|
406
515
|
}
|
|
407
516
|
|
|
408
517
|
.thumbnails {
|
|
409
|
-
align-self: flex-start;
|
|
410
518
|
display: flex;
|
|
411
|
-
justify-content: left;
|
|
412
519
|
align-items: center;
|
|
413
520
|
gap: var(--spacing-lg);
|
|
521
|
+
overflow-x: scroll;
|
|
522
|
+
padding-top: var(--spacing-sm);
|
|
414
523
|
}
|
|
415
524
|
|
|
416
525
|
.thumbnail-item {
|
package/shared/utils.ts
CHANGED
|
@@ -42,17 +42,16 @@ export function text_area_resize(
|
|
|
42
42
|
): any | undefined {
|
|
43
43
|
if (_value.lines === _value.max_lines) return;
|
|
44
44
|
_el.style.overflowY = "scroll";
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
|
|
46
|
+
function handle_input(event: Event): void {
|
|
47
|
+
resize(event.target as HTMLTextAreaElement, _value.lines, _value.max_lines);
|
|
48
|
+
}
|
|
49
|
+
_el.addEventListener("input", handle_input);
|
|
48
50
|
|
|
49
51
|
if (!_value.text.trim()) return;
|
|
50
52
|
resize(_el, _value.lines, _value.max_lines);
|
|
51
53
|
|
|
52
54
|
return {
|
|
53
|
-
destroy: () =>
|
|
54
|
-
_el.removeEventListener("input", (e: Event) =>
|
|
55
|
-
resize(e.target as HTMLTextAreaElement, _value.lines, _value.max_lines)
|
|
56
|
-
)
|
|
55
|
+
destroy: () => _el.removeEventListener("input", handle_input)
|
|
57
56
|
};
|
|
58
57
|
}
|