@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.
@@ -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
- export let value: { text: string; files: FileData[] } = {
36
- text: "",
37
- files: []
38
- };
39
-
40
- export let value_is_output = false;
41
- export let lines = 1;
42
- export let i18n: I18nFormatter;
43
- export let placeholder = "";
44
- export let disabled = false;
45
- export let label: string;
46
- export let info: string | undefined = undefined;
47
- export let show_label = true;
48
- export let max_lines: number;
49
- export let submit_btn: string | boolean | null = null;
50
- export let stop_btn: string | boolean | null = null;
51
- export let rtl = false;
52
- export let autofocus = false;
53
- export let text_align: "left" | "right" | undefined = undefined;
54
- export let autoscroll = true;
55
- export let root: string;
56
- export let file_types_string: string | null = null;
57
- export let max_file_size: number | null = null;
58
- export let upload: Client["upload"];
59
- export let stream_handler: Client["stream"];
60
- export let file_count: "single" | "multiple" | "directory" = "multiple";
61
- export let max_plain_text_length = 1000;
62
- export let waveform_settings: Record<string, any>;
63
- export let waveform_options: WaveformOptions = {
64
- show_recording_waveform: true
65
- };
66
- export let sources_string:
67
- | "upload"
68
- | "upload,microphone"
69
- | "microphone"
70
- | "microphone,upload" = "upload";
71
- export let active_source: "microphone" | null = null;
72
- export let html_attributes: InputHTMLAttributes | null = null;
73
- export let upload_promise: Promise<any> | null = null;
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: boolean;
78
- let previous_scroll_top = 0;
79
- let user_has_scrolled_up = false;
80
- let show_fade = false;
81
- export let dragging = false;
82
- let uploading = false;
83
- // value can be null in multimodalchatinterface when loading a deep link
84
- let oldValue = value?.text ?? "";
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
- $: if (oldValue !== value.text) {
107
- dispatch("change", value);
108
- oldValue = value.text;
109
- }
110
-
111
- $: if (value === null) value = { text: "", files: [] };
112
- $: (value, el && lines !== max_lines && resize(el, lines, max_lines));
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
- $: if (el && value.text !== undefined) {
119
- tick().then(update_fade);
120
- }
121
-
122
- const dispatch = createEventDispatcher<{
123
- change: typeof value;
124
- submit: undefined;
125
- stop: undefined;
126
- blur: undefined;
127
- select: SelectData;
128
- input: undefined;
129
- focus: undefined;
130
- drag: boolean;
131
- upload: FileData[] | FileData;
132
- clear: undefined;
133
- load: FileData[] | FileData;
134
- error: string;
135
- start_recording: undefined;
136
- pause_recording: undefined;
137
- stop_recording: undefined;
138
- }>();
139
-
140
- beforeUpdate(() => {
141
- can_scroll = el && el.offsetHeight + el.scrollTop > el.scrollHeight - 100;
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
- dispatch("change", value);
194
+ onchange?.(value);
152
195
  if (!value_is_output) {
153
- dispatch("input");
196
+ oninput?.();
154
197
  }
155
198
  }
156
199
 
157
- onMount(() => {
158
- if (autofocus && el !== null) {
200
+ $effect(() => {
201
+ value;
202
+ if (autofocus && el) {
159
203
  el.focus();
160
204
  }
161
205
  });
162
206
 
163
- const after_update = (): void => {
207
+ $effect(() => {
164
208
  if (can_scroll && autoscroll) {
165
209
  scroll();
166
210
  }
167
- if (autofocus && el) {
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
- dispatch("select", { value: text.substring(...index), index: index });
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
- dispatch("submit");
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
- dispatch("submit");
240
+ onsubmit?.();
202
241
  }
203
242
  }
204
243
 
@@ -230,8 +269,8 @@
230
269
  value = value;
231
270
  }
232
271
  await tick();
233
- dispatch("change", value);
234
- dispatch("upload", detail);
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
- dispatch("stop");
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
- dispatch("change", value);
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
- dispatch("submit");
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
- dispatch(
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
- on:dragenter={handle_dragenter}
351
- on:dragleave={handle_dragleave}
352
- on:dragover|preventDefault
353
- on:drop={handle_drop}
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={(value) => {
372
- mic_audio = value;
409
+ onchange={(audio_value) => {
410
+ mic_audio = audio_value;
373
411
  }}
374
412
  onstoprecording={() => {
375
413
  recording = false;
376
- dispatch("stop_recording");
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
- on:click={() => {
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
- on:click={() => {
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={(e) => dispatch("error", e)}
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.files.length > 0 || uploading}
481
+ class:has-files={(value?.files?.length ?? 0) > 0 || uploading}
443
482
  >
444
- {#if value.files.length > 0 || uploading}
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
- on:click={handle_upload_click}
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.files as file, index}
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
- on:click={(event) => remove_thumbnail(event, index)}
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.files.length === 0 && !uploading}
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
- on:click={handle_upload_click}
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
- on:keypress={handle_keypress}
529
- on:blur
530
- on:select={handle_select}
531
- on:focus
532
- on:scroll={handle_scroll}
533
- on:paste={handle_paste}
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
- on:click={async () => {
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
- on:click={handle_submit}
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
- on:click={handle_stop}
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/shared/types";
1
+ export type { InputHTMLAttributes } from "../../textbox/types";
package/types.ts CHANGED
@@ -14,8 +14,6 @@ export interface MultimodalTextboxEvents {
14
14
  focus: never;
15
15
  error: string;
16
16
  clear_status: LoadingStatus;
17
- start_recording: never;
18
- pause_recording: never;
19
17
  stop_recording: never;
20
18
  upload: FileData[] | FileData;
21
19
  clear: undefined;