@gradio/multimodaltextbox 0.11.4 → 0.11.5
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 +16 -0
- package/Example.svelte +14 -10
- package/Index.svelte +17 -19
- package/dist/Example.svelte +14 -10
- package/dist/Example.svelte.d.ts +4 -18
- package/dist/Index.svelte +17 -19
- package/dist/shared/MultimodalTextbox.svelte +191 -152
- package/dist/shared/MultimodalTextbox.svelte.d.ts +18 -36
- package/dist/shared/types.d.ts +1 -1
- package/dist/types.d.ts +0 -2
- package/package.json +8 -8
- package/shared/MultimodalTextbox.svelte +191 -152
- package/shared/types.ts +1 -1
- package/types.ts +0 -2
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
onMount,
|
|
4
|
-
beforeUpdate,
|
|
5
|
-
afterUpdate,
|
|
6
|
-
createEventDispatcher,
|
|
7
|
-
tick
|
|
8
|
-
} from "svelte";
|
|
2
|
+
import { tick } from "svelte";
|
|
9
3
|
import { text_area_resize, resize } from "../shared/utils";
|
|
10
4
|
import { BlockTitle, ScrollFade } from "@gradio/atoms";
|
|
11
5
|
import { Upload } from "@gradio/upload";
|
|
@@ -25,120 +19,169 @@
|
|
|
25
19
|
Check
|
|
26
20
|
} from "@gradio/icons";
|
|
27
21
|
import { should_show_scroll_fade, type SelectData } from "@gradio/utils";
|
|
28
|
-
import { BaseInteractiveAudio as InteractiveAudio } from "@gradio/audio";
|
|
29
22
|
import {
|
|
30
23
|
MinimalAudioPlayer,
|
|
31
24
|
MinimalAudioRecorder
|
|
32
25
|
} from "@gradio/audio/shared";
|
|
33
26
|
import type { InputHTMLAttributes } from "./types";
|
|
34
27
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
28
|
+
let {
|
|
29
|
+
value = $bindable(),
|
|
30
|
+
value_is_output = false,
|
|
31
|
+
lines = 1,
|
|
32
|
+
i18n: _i18n,
|
|
33
|
+
placeholder = "",
|
|
34
|
+
disabled = false,
|
|
35
|
+
label,
|
|
36
|
+
info = undefined,
|
|
37
|
+
show_label = true,
|
|
38
|
+
max_lines,
|
|
39
|
+
submit_btn = null,
|
|
40
|
+
stop_btn = null,
|
|
41
|
+
rtl = false,
|
|
42
|
+
autofocus = false,
|
|
43
|
+
text_align = undefined,
|
|
44
|
+
autoscroll = true,
|
|
45
|
+
root,
|
|
46
|
+
file_types_string = null,
|
|
47
|
+
max_file_size = null,
|
|
48
|
+
upload,
|
|
49
|
+
stream_handler,
|
|
50
|
+
file_count = "multiple",
|
|
51
|
+
max_plain_text_length = 1000,
|
|
52
|
+
waveform_settings,
|
|
53
|
+
waveform_options: _waveform_options = { show_recording_waveform: true },
|
|
54
|
+
sources_string = "upload",
|
|
55
|
+
active_source = $bindable<"microphone" | null>(),
|
|
56
|
+
html_attributes = null,
|
|
57
|
+
upload_promise = $bindable<Promise<any> | null>(),
|
|
58
|
+
dragging = $bindable<boolean>(),
|
|
59
|
+
onchange,
|
|
60
|
+
onsubmit,
|
|
61
|
+
onstop,
|
|
62
|
+
onblur,
|
|
63
|
+
onselect,
|
|
64
|
+
oninput,
|
|
65
|
+
onfocus,
|
|
66
|
+
ondrag,
|
|
67
|
+
onupload,
|
|
68
|
+
onclear,
|
|
69
|
+
onload: _onload,
|
|
70
|
+
onerror,
|
|
71
|
+
onstop_recording
|
|
72
|
+
}: {
|
|
73
|
+
value?: { text: string; files: FileData[] };
|
|
74
|
+
value_is_output?: boolean;
|
|
75
|
+
lines?: number;
|
|
76
|
+
i18n: I18nFormatter;
|
|
77
|
+
placeholder?: string;
|
|
78
|
+
disabled?: boolean;
|
|
79
|
+
label: string;
|
|
80
|
+
info?: string | undefined;
|
|
81
|
+
show_label?: boolean;
|
|
82
|
+
max_lines: number;
|
|
83
|
+
submit_btn?: string | boolean | null;
|
|
84
|
+
stop_btn?: string | boolean | null;
|
|
85
|
+
rtl?: boolean;
|
|
86
|
+
autofocus?: boolean;
|
|
87
|
+
text_align?: "left" | "right" | undefined;
|
|
88
|
+
autoscroll?: boolean;
|
|
89
|
+
root: string;
|
|
90
|
+
file_types_string?: string | null;
|
|
91
|
+
max_file_size?: number | null;
|
|
92
|
+
upload: Client["upload"];
|
|
93
|
+
stream_handler: Client["stream"];
|
|
94
|
+
file_count?: "single" | "multiple" | "directory";
|
|
95
|
+
max_plain_text_length?: number;
|
|
96
|
+
waveform_settings: Record<string, any>;
|
|
97
|
+
waveform_options?: WaveformOptions;
|
|
98
|
+
sources_string?:
|
|
99
|
+
| "upload"
|
|
100
|
+
| "upload,microphone"
|
|
101
|
+
| "microphone"
|
|
102
|
+
| "microphone,upload";
|
|
103
|
+
active_source?: "microphone" | null;
|
|
104
|
+
html_attributes?: InputHTMLAttributes | null;
|
|
105
|
+
upload_promise?: Promise<any> | null;
|
|
106
|
+
dragging?: boolean;
|
|
107
|
+
onchange?: (value: { text: string; files: FileData[] }) => void;
|
|
108
|
+
onsubmit?: () => void;
|
|
109
|
+
onstop?: () => void;
|
|
110
|
+
onblur?: () => void;
|
|
111
|
+
onselect?: (data: SelectData) => void;
|
|
112
|
+
oninput?: () => void;
|
|
113
|
+
onfocus?: () => void;
|
|
114
|
+
ondrag?: (dragging: boolean) => void;
|
|
115
|
+
onupload?: (files: FileData[] | FileData) => void;
|
|
116
|
+
onclear?: () => void;
|
|
117
|
+
onload?: (files: FileData[] | FileData) => void;
|
|
118
|
+
onerror?: (error: string) => void;
|
|
119
|
+
onstop_recording?: () => void;
|
|
120
|
+
} = $props();
|
|
74
121
|
|
|
75
122
|
let upload_component: Upload;
|
|
76
123
|
let el: HTMLTextAreaElement | HTMLInputElement;
|
|
77
|
-
let can_scroll
|
|
78
|
-
let previous_scroll_top = 0;
|
|
79
|
-
let user_has_scrolled_up = false;
|
|
80
|
-
let show_fade = false;
|
|
81
|
-
|
|
82
|
-
let
|
|
83
|
-
|
|
84
|
-
let
|
|
85
|
-
let recording = false;
|
|
86
|
-
|
|
87
|
-
$: sources = sources_string
|
|
88
|
-
.split(",")
|
|
89
|
-
.map((s) => s.trim())
|
|
90
|
-
.filter((s) => s === "upload" || s === "microphone") as (
|
|
91
|
-
| "upload"
|
|
92
|
-
| "microphone"
|
|
93
|
-
)[];
|
|
94
|
-
$: file_types = file_types_string
|
|
95
|
-
? file_types_string.split(",").map((s) => s.trim())
|
|
96
|
-
: null;
|
|
97
|
-
$: dispatch("drag", dragging);
|
|
98
|
-
$: show_upload =
|
|
99
|
-
sources &&
|
|
100
|
-
sources.includes("upload") &&
|
|
101
|
-
!(file_count === "single" && value.files.length > 0);
|
|
102
|
-
let mic_audio: FileData | null = null;
|
|
103
|
-
|
|
124
|
+
let can_scroll = $state(false);
|
|
125
|
+
let previous_scroll_top = $state(0);
|
|
126
|
+
let user_has_scrolled_up = $state(false);
|
|
127
|
+
let show_fade = $state(false);
|
|
128
|
+
let uploading = $state(false);
|
|
129
|
+
let oldValue = $state(value?.text ?? "");
|
|
130
|
+
let recording = $state(false);
|
|
131
|
+
let mic_audio = $state<FileData | null>(null);
|
|
104
132
|
let full_container: HTMLDivElement;
|
|
105
133
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
134
|
+
let sources = $derived(
|
|
135
|
+
sources_string
|
|
136
|
+
.split(",")
|
|
137
|
+
.map((s) => s.trim())
|
|
138
|
+
.filter((s) => s === "upload" || s === "microphone") as (
|
|
139
|
+
| "upload"
|
|
140
|
+
| "microphone"
|
|
141
|
+
)[]
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
let file_types = $derived(
|
|
145
|
+
file_types_string ? file_types_string.split(",").map((s) => s.trim()) : null
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
let show_upload = $derived(
|
|
149
|
+
sources &&
|
|
150
|
+
sources.includes("upload") &&
|
|
151
|
+
!(file_count === "single" && value?.files?.length > 0)
|
|
152
|
+
);
|
|
113
153
|
|
|
114
154
|
function update_fade(): void {
|
|
115
155
|
show_fade = should_show_scroll_fade(el);
|
|
116
156
|
}
|
|
117
157
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
158
|
+
$effect(() => {
|
|
159
|
+
if (el && value?.text !== undefined) {
|
|
160
|
+
tick().then(update_fade);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
$effect(() => {
|
|
165
|
+
ondrag?.(dragging);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
$effect(() => {
|
|
169
|
+
if (value && oldValue !== value.text) {
|
|
170
|
+
onchange?.(value);
|
|
171
|
+
oldValue = value.text;
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
$effect(() => {
|
|
176
|
+
if (el && lines !== max_lines) {
|
|
177
|
+
resize(el, lines, max_lines);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
$effect.pre(() => {
|
|
182
|
+
if (el && el.offsetHeight + el.scrollTop > el.scrollHeight - 100) {
|
|
183
|
+
can_scroll = true;
|
|
184
|
+
}
|
|
142
185
|
});
|
|
143
186
|
|
|
144
187
|
const scroll = (): void => {
|
|
@@ -148,28 +191,24 @@
|
|
|
148
191
|
};
|
|
149
192
|
|
|
150
193
|
async function handle_change(): Promise<void> {
|
|
151
|
-
|
|
194
|
+
onchange?.(value);
|
|
152
195
|
if (!value_is_output) {
|
|
153
|
-
|
|
196
|
+
oninput?.();
|
|
154
197
|
}
|
|
155
198
|
}
|
|
156
199
|
|
|
157
|
-
|
|
158
|
-
|
|
200
|
+
$effect(() => {
|
|
201
|
+
value;
|
|
202
|
+
if (autofocus && el) {
|
|
159
203
|
el.focus();
|
|
160
204
|
}
|
|
161
205
|
});
|
|
162
206
|
|
|
163
|
-
|
|
207
|
+
$effect(() => {
|
|
164
208
|
if (can_scroll && autoscroll) {
|
|
165
209
|
scroll();
|
|
166
210
|
}
|
|
167
|
-
|
|
168
|
-
el.focus();
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
afterUpdate(after_update);
|
|
211
|
+
});
|
|
173
212
|
|
|
174
213
|
function handle_select(event: Event): void {
|
|
175
214
|
const target: HTMLTextAreaElement | HTMLInputElement = event.target as
|
|
@@ -180,14 +219,14 @@
|
|
|
180
219
|
target.selectionStart as number,
|
|
181
220
|
target.selectionEnd as number
|
|
182
221
|
];
|
|
183
|
-
|
|
222
|
+
onselect?.({ value: text.substring(...index), index: index });
|
|
184
223
|
}
|
|
185
224
|
|
|
186
225
|
async function handle_keypress(e: KeyboardEvent): Promise<void> {
|
|
187
226
|
if (e.key === "Enter" && e.shiftKey && lines > 1) {
|
|
188
227
|
e.preventDefault();
|
|
189
228
|
await tick();
|
|
190
|
-
|
|
229
|
+
onsubmit?.();
|
|
191
230
|
} else if (
|
|
192
231
|
e.key === "Enter" &&
|
|
193
232
|
!e.shiftKey &&
|
|
@@ -198,7 +237,7 @@
|
|
|
198
237
|
add_mic_audio_to_files();
|
|
199
238
|
active_source = null;
|
|
200
239
|
await tick();
|
|
201
|
-
|
|
240
|
+
onsubmit?.();
|
|
202
241
|
}
|
|
203
242
|
}
|
|
204
243
|
|
|
@@ -230,8 +269,8 @@
|
|
|
230
269
|
value = value;
|
|
231
270
|
}
|
|
232
271
|
await tick();
|
|
233
|
-
|
|
234
|
-
|
|
272
|
+
onchange?.(value);
|
|
273
|
+
onupload?.(detail);
|
|
235
274
|
}
|
|
236
275
|
|
|
237
276
|
function remove_thumbnail(event: MouseEvent, index: number): void {
|
|
@@ -246,7 +285,7 @@
|
|
|
246
285
|
}
|
|
247
286
|
|
|
248
287
|
function handle_stop(): void {
|
|
249
|
-
|
|
288
|
+
onstop?.();
|
|
250
289
|
}
|
|
251
290
|
|
|
252
291
|
function add_mic_audio_to_files(): void {
|
|
@@ -254,14 +293,14 @@
|
|
|
254
293
|
value.files.push(mic_audio);
|
|
255
294
|
value = value;
|
|
256
295
|
mic_audio = null;
|
|
257
|
-
|
|
296
|
+
onchange?.(value);
|
|
258
297
|
}
|
|
259
298
|
}
|
|
260
299
|
|
|
261
300
|
function handle_submit(): void {
|
|
262
301
|
add_mic_audio_to_files();
|
|
263
302
|
active_source = null;
|
|
264
|
-
|
|
303
|
+
onsubmit?.();
|
|
265
304
|
}
|
|
266
305
|
|
|
267
306
|
async function handle_paste(event: ClipboardEvent): Promise<void> {
|
|
@@ -327,8 +366,7 @@
|
|
|
327
366
|
|
|
328
367
|
const invalid_files = files.length - valid_files.length;
|
|
329
368
|
if (invalid_files > 0) {
|
|
330
|
-
|
|
331
|
-
"error",
|
|
369
|
+
onerror?.(
|
|
332
370
|
`${invalid_files} file(s) were rejected. Accepted formats: ${file_types.join(", ")}`
|
|
333
371
|
);
|
|
334
372
|
}
|
|
@@ -347,10 +385,10 @@
|
|
|
347
385
|
class="full-container"
|
|
348
386
|
class:dragging
|
|
349
387
|
bind:this={full_container}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
388
|
+
ondragenter={handle_dragenter}
|
|
389
|
+
ondragleave={handle_dragleave}
|
|
390
|
+
ondragover={(e) => e.preventDefault()}
|
|
391
|
+
ondrop={handle_drop}
|
|
354
392
|
role="group"
|
|
355
393
|
aria-label="Multimedia input field"
|
|
356
394
|
>
|
|
@@ -368,17 +406,18 @@
|
|
|
368
406
|
{root}
|
|
369
407
|
{max_file_size}
|
|
370
408
|
bind:upload_promise
|
|
371
|
-
onchange={(
|
|
372
|
-
mic_audio =
|
|
409
|
+
onchange={(audio_value) => {
|
|
410
|
+
mic_audio = audio_value;
|
|
373
411
|
}}
|
|
374
412
|
onstoprecording={() => {
|
|
375
413
|
recording = false;
|
|
376
|
-
|
|
414
|
+
onstop_recording?.();
|
|
377
415
|
}}
|
|
378
416
|
onclear={() => {
|
|
379
417
|
active_source = null;
|
|
380
418
|
recording = false;
|
|
381
419
|
mic_audio = null;
|
|
420
|
+
onclear?.();
|
|
382
421
|
}}
|
|
383
422
|
/>
|
|
384
423
|
</div>
|
|
@@ -392,7 +431,7 @@
|
|
|
392
431
|
<div class="action-buttons">
|
|
393
432
|
<button
|
|
394
433
|
class="confirm-button"
|
|
395
|
-
|
|
434
|
+
onclick={() => {
|
|
396
435
|
add_mic_audio_to_files();
|
|
397
436
|
active_source = null;
|
|
398
437
|
recording = false;
|
|
@@ -403,7 +442,7 @@
|
|
|
403
442
|
</button>
|
|
404
443
|
<button
|
|
405
444
|
class="cancel-button"
|
|
406
|
-
|
|
445
|
+
onclick={() => {
|
|
407
446
|
active_source = null;
|
|
408
447
|
recording = false;
|
|
409
448
|
mic_audio = null;
|
|
@@ -430,7 +469,7 @@
|
|
|
430
469
|
bind:uploading
|
|
431
470
|
show_progress={false}
|
|
432
471
|
disable_click={true}
|
|
433
|
-
onerror
|
|
472
|
+
{onerror}
|
|
434
473
|
hidden={true}
|
|
435
474
|
{upload}
|
|
436
475
|
{stream_handler}
|
|
@@ -439,9 +478,9 @@
|
|
|
439
478
|
|
|
440
479
|
<div
|
|
441
480
|
class="input-wrapper"
|
|
442
|
-
class:has-files={value
|
|
481
|
+
class:has-files={(value?.files?.length ?? 0) > 0 || uploading}
|
|
443
482
|
>
|
|
444
|
-
{#if value
|
|
483
|
+
{#if (value?.files?.length ?? 0) > 0 || uploading}
|
|
445
484
|
<div
|
|
446
485
|
class="thumbnails"
|
|
447
486
|
aria-label="Uploaded files"
|
|
@@ -452,13 +491,13 @@
|
|
|
452
491
|
data-testid="upload-button"
|
|
453
492
|
class="upload-button thumbnail-add"
|
|
454
493
|
{disabled}
|
|
455
|
-
|
|
494
|
+
onclick={handle_upload_click}
|
|
456
495
|
aria-label="Upload a file"
|
|
457
496
|
>
|
|
458
497
|
<Paperclip />
|
|
459
498
|
</button>
|
|
460
499
|
{/if}
|
|
461
|
-
{#each value
|
|
500
|
+
{#each value?.files ?? [] as file, index}
|
|
462
501
|
<span
|
|
463
502
|
class="thumbnail-wrapper"
|
|
464
503
|
role="listitem"
|
|
@@ -484,7 +523,7 @@
|
|
|
484
523
|
{/if}
|
|
485
524
|
<button
|
|
486
525
|
class="delete-button"
|
|
487
|
-
|
|
526
|
+
onclick={(event) => remove_thumbnail(event, index)}
|
|
488
527
|
aria-label="Remove file"
|
|
489
528
|
>
|
|
490
529
|
<Clear />
|
|
@@ -499,12 +538,12 @@
|
|
|
499
538
|
{/if}
|
|
500
539
|
|
|
501
540
|
<div class="input-row">
|
|
502
|
-
{#if show_upload && value
|
|
541
|
+
{#if show_upload && (value?.files?.length ?? 0) === 0 && !uploading}
|
|
503
542
|
<button
|
|
504
543
|
data-testid="upload-button"
|
|
505
544
|
class="upload-button icon-button"
|
|
506
545
|
{disabled}
|
|
507
|
-
|
|
546
|
+
onclick={handle_upload_click}
|
|
508
547
|
aria-label="Upload a file"
|
|
509
548
|
>
|
|
510
549
|
<Paperclip />
|
|
@@ -525,12 +564,12 @@
|
|
|
525
564
|
{placeholder}
|
|
526
565
|
rows={lines}
|
|
527
566
|
{disabled}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
567
|
+
onkeypress={handle_keypress}
|
|
568
|
+
onblur={() => onblur?.()}
|
|
569
|
+
onselect={handle_select}
|
|
570
|
+
onfocus={() => onfocus?.()}
|
|
571
|
+
onscroll={handle_scroll}
|
|
572
|
+
onpaste={handle_paste}
|
|
534
573
|
style={text_align ? "text-align: " + text_align : ""}
|
|
535
574
|
autocapitalize={html_attributes?.autocapitalize}
|
|
536
575
|
autocorrect={html_attributes?.autocorrect}
|
|
@@ -549,7 +588,7 @@
|
|
|
549
588
|
class="microphone-button"
|
|
550
589
|
class:recording
|
|
551
590
|
{disabled}
|
|
552
|
-
|
|
591
|
+
onclick={async () => {
|
|
553
592
|
if (active_source !== "microphone") {
|
|
554
593
|
active_source = "microphone";
|
|
555
594
|
await tick();
|
|
@@ -571,7 +610,7 @@
|
|
|
571
610
|
data-testid="submit-button"
|
|
572
611
|
class:padded-button={submit_btn !== true}
|
|
573
612
|
{disabled}
|
|
574
|
-
|
|
613
|
+
onclick={handle_submit}
|
|
575
614
|
aria-label="Submit"
|
|
576
615
|
>
|
|
577
616
|
{#if submit_btn === true}
|
|
@@ -585,7 +624,7 @@
|
|
|
585
624
|
<button
|
|
586
625
|
class="stop-button"
|
|
587
626
|
class:padded-button={stop_btn !== true}
|
|
588
|
-
|
|
627
|
+
onclick={handle_stop}
|
|
589
628
|
aria-label="Stop"
|
|
590
629
|
>
|
|
591
630
|
{#if stop_btn === true}
|
package/shared/types.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export type { InputHTMLAttributes } from "../../textbox/
|
|
1
|
+
export type { InputHTMLAttributes } from "../../textbox/types";
|
package/types.ts
CHANGED