gradio/js/multimodaltextbox/shared/MultimodalTextbox.svelte
Dawood Khan 4795c6e9e9
Fix multimodal textbox styling for certain themes (#8369)
* fix css

* add changeset

* Add `--table-text-color` var to fix body text in `Examples` (#8364)

* add --table-text-color var

* add changeset

* format

* tweak

* add changeset

* fix

* add changeset

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>

* Include JS Client package in CI for PR testing (#8362)

* add js client pkg action

* test

* Revert "test"

This reverts commit 575bcfca1b.

* tweaks (#8374)

* chore: update docs.py (#8378)

* chore: update docs.py

Documention -> Documentation

* add changeset

* add changeset

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>

* Fix encoding error (#8381)

* Set orig_name in python client file uploads (#8371)

* Add code

* add changeset

* URL case

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: Hannah <hannahblair@users.noreply.github.com>

* Include instructions on starting from someone else's custom component repository (#8386)

* Add guide

* add changeset

* Fix link

* add changeset

* Add code

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>

* Fix bug in reload mode equality check. Better equality conversion for state variables (#8385)

* Add code

* Add deep equality

* add changeset

* Add code

* add changeset

* Update gradio/utils.py

Co-authored-by: Abubakar Abid <abubakar@huggingface.co>

* Add code

* Add code

* add code

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: Abubakar Abid <abubakar@huggingface.co>

* Change z-index of status tracker (#8372)

* change z-index of status tracker

* add changeset

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>

* Add CDN installation to JS docs (#8401)

* add cdn copy

* add changeset

* tweak

* tweak

* add changeset

* Update guides/08_gradio-clients-and-lite/02_getting-started-with-the-js-client.md

Co-authored-by: Abubakar Abid <abubakar@huggingface.co>

* remove version from cdn link

* tweak

* tweak

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: Abubakar Abid <abubakar@huggingface.co>

* Improve rendering (#8398)

* changes

* add changeset

* changes

* changes

* changes

* changes

* changes

* changes

* changeas

* changes

* add changeset

* changes

* add changeset

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* add changeset

* changes

* cganges

* changes

* changes

* changes

* changes

* add changeset

* changes

* chagnes

* changes

* changes

* changes

* changes

* changes

* js

* remove console log

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* add changeset

* changes

* chnages

* changes

* cnages

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* add changeset

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* add changeset

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* add changeset

* Add `state.change` listener (#8297)

* state changes

* changes

---------

Co-authored-by: Ali Abid <aliabid94@gmail.com>
Co-authored-by: Abubakar Abid <abubakar@huggingface.co>

* changes

* changes

* add changeset

* changes

* changes

* changes

* changes

* changes

* changes

* updates

* changes

* add changeset

* changes

* changes

* add changeset

* fix

* changes

* changes

* changes

* changes

* changes

* changes

* add changeset

* changes

---------

Co-authored-by: Ali Abid <aliabid94@gmail.com>
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: Abubakar Abid <abubakar@huggingface.co>

* Render decorator documentation (#8409)

* changes

* changes

* add changeset

* fix dependency loop and documentation group

* add changeset

* changes

* fix numbered list

* changes

* changes

---------

Co-authored-by: Ali Abid <aliabid94@gmail.com>
Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: aliabd <ali.si3luwa@gmail.com>

* Connect heartbeat if state created in render. Also fix config cleanup bug #8407 (#8408)

* Add code

* add changeset

* add changeset

* lint

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>

* Model3D right-handed coordinate system (#8376)

* changes (#8411)

Co-authored-by: Ali Abid <aliabid94@gmail.com>

* Editable Docs (#8403)

* docs intro page and guides

* python library docs and js client

* reorg

* changes

* add better hovering

* fix broken version routing

* add redirects and remove duplicate pages

* fix build issues

* fix issues

* formatting

* add changeset

* working templates for all components

* add other pages

* merge

* merge

* changes

* changes

* working from templates

* changes

* refactoring

* changes

* build working

* formatting

* importing meta globs

* add uploading templates to ci

* fixes

* add changeset

* remove fake version

* fix

* ignore

* formatting

* adding render page to docs

* add changeset

* formatting fix

* typo

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>

* chore: update versions (#8348)

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: Hannah <hannahblair@users.noreply.github.com>
Co-authored-by: Ikko Eltociear Ashimine <eltociear@gmail.com>
Co-authored-by: Xu Song <xusong.vip@gmail.com>
Co-authored-by: Freddy Boulton <alfonsoboulton@gmail.com>
Co-authored-by: Abubakar Abid <abubakar@huggingface.co>
Co-authored-by: aliabid94 <aabid94@gmail.com>
Co-authored-by: Ali Abid <aliabid94@gmail.com>
Co-authored-by: aliabd <ali.si3luwa@gmail.com>
Co-authored-by: Dylan Ebert <dylan@huggingface.co>
Co-authored-by: pngwn <hello@pngwn.io>
2024-06-03 12:53:59 -04:00

495 lines
11 KiB
Svelte

<script lang="ts">
import {
beforeUpdate,
afterUpdate,
createEventDispatcher,
tick
} from "svelte";
import { text_area_resize, resize } from "../shared/utils";
import { BlockTitle } from "@gradio/atoms";
import { Upload } from "@gradio/upload";
import { Image } from "@gradio/image/shared";
import type { FileData, Client } from "@gradio/client";
import { Clear, File, Music, Paperclip, Video, Send } from "@gradio/icons";
import type { SelectData } from "@gradio/utils";
export let value: { text: string; files: FileData[] } = {
text: "",
files: []
};
export let value_is_output = false;
export let lines = 1;
export let placeholder = "Type here...";
export let disabled = false;
export let label: string;
export let info: string | undefined = undefined;
export let show_label = true;
export let container = true;
export let max_lines: number;
export let submit_btn: string | null = null;
export let rtl = false;
export let autofocus = false;
export let text_align: "left" | "right" | undefined = undefined;
export let autoscroll = true;
export let root: string;
export let file_types: string[] | null = null;
export let max_file_size: number | null = null;
export let upload: Client["upload"];
export let stream_handler: Client["stream"];
let upload_component: Upload;
let hidden_upload: HTMLInputElement;
let el: HTMLTextAreaElement | HTMLInputElement;
let can_scroll: boolean;
let previous_scroll_top = 0;
let user_has_scrolled_up = false;
let dragging = false;
let uploading = false;
let oldValue = value.text;
$: dispatch("drag", dragging);
$: if (oldValue !== value.text) {
dispatch("change", value);
oldValue = value.text;
}
let accept_file_types: string | null;
if (file_types == null) {
accept_file_types = null;
} else {
file_types = file_types.map((x) => {
if (x.startsWith(".")) {
return x;
}
return x + "/*";
});
accept_file_types = file_types.join(", ");
}
$: if (value === null) value = { text: "", files: [] };
$: value, el && lines !== max_lines && resize(el, lines, max_lines);
const dispatch = createEventDispatcher<{
change: typeof value;
submit: undefined;
blur: undefined;
select: SelectData;
input: undefined;
focus: undefined;
drag: boolean;
upload: FileData[] | FileData;
clear: undefined;
load: FileData[] | FileData;
error: string;
}>();
beforeUpdate(() => {
can_scroll = el && el.offsetHeight + el.scrollTop > el.scrollHeight - 100;
});
const scroll = (): void => {
if (can_scroll && autoscroll && !user_has_scrolled_up) {
el.scrollTo(0, el.scrollHeight);
}
};
async function handle_change(): Promise<void> {
dispatch("change", value);
if (!value_is_output) {
dispatch("input");
}
}
afterUpdate(() => {
if (autofocus && el !== null) {
el.focus();
}
if (can_scroll && autoscroll) {
scroll();
}
value_is_output = false;
});
function handle_select(event: Event): void {
const target: HTMLTextAreaElement | HTMLInputElement = event.target as
| HTMLTextAreaElement
| HTMLInputElement;
const text = target.value;
const index: [number, number] = [
target.selectionStart as number,
target.selectionEnd as number
];
dispatch("select", { value: text.substring(...index), index: index });
}
async function handle_keypress(e: KeyboardEvent): Promise<void> {
await tick();
if (e.key === "Enter" && e.shiftKey && lines > 1) {
e.preventDefault();
dispatch("submit");
} else if (
e.key === "Enter" &&
!e.shiftKey &&
lines === 1 &&
max_lines >= 1
) {
e.preventDefault();
dispatch("submit");
}
}
function handle_scroll(event: Event): void {
const target = event.target as HTMLElement;
const current_scroll_top = target.scrollTop;
if (current_scroll_top < previous_scroll_top) {
user_has_scrolled_up = true;
}
previous_scroll_top = current_scroll_top;
const max_scroll_top = target.scrollHeight - target.clientHeight;
const user_has_scrolled_to_bottom = current_scroll_top >= max_scroll_top;
if (user_has_scrolled_to_bottom) {
user_has_scrolled_up = false;
}
}
async function handle_upload({
detail
}: CustomEvent<FileData | FileData[]>): Promise<void> {
handle_change();
if (Array.isArray(detail)) {
for (let file of detail) {
value.files.push(file);
}
} else {
value.files.push(detail);
value = value;
}
await tick();
dispatch("change", value);
dispatch("upload", detail);
}
function remove_thumbnail(event: MouseEvent, index: number): void {
handle_change();
event.stopPropagation();
value.files.splice(index, 1);
value = value;
}
function handle_upload_click(): void {
if (hidden_upload) {
hidden_upload.value = "";
hidden_upload.click();
}
}
async function handle_submit(): Promise<void> {
dispatch("submit");
}
function handle_paste(event: ClipboardEvent): void {
if (!event.clipboardData) return;
const items = event.clipboardData.items;
for (let index in items) {
const item = items[index];
if (item.kind === "file" && item.type.includes("image")) {
const blob = item.getAsFile();
if (blob) upload_component.load_files([blob]);
}
}
}
</script>
<!-- svelte-ignore a11y-autofocus -->
<label class:container>
<BlockTitle {show_label} {info}>{label}</BlockTitle>
<div class="input-container">
<Upload
bind:this={upload_component}
on:load={handle_upload}
filetype={accept_file_types}
{root}
{max_file_size}
bind:dragging
bind:uploading
show_progress={false}
disable_click={true}
bind:hidden_upload
on:error
{upload}
{stream_handler}
>
{#if submit_btn !== null}
<button class:disabled class="submit-button" on:click={handle_submit}
>{submit_btn}</button
>
{:else}
<button class:disabled class="submit-button" on:click={handle_submit}
><Send /></button
>
{/if}
<button
data-testid="upload-button"
class="upload-button"
on:click={handle_upload_click}><Paperclip /></button
>
{#if value.files.length > 0 || uploading}
<div
class="thumbnails scroll-hide"
data-testid="container_el"
style="display: {value.files.length > 0 || uploading
? 'flex'
: 'none'};"
>
{#each value.files as file, index}
<button class="thumbnail-item thumbnail-small">
<button
class:disabled
class="delete-button"
on:click={(event) => remove_thumbnail(event, index)}
><Clear /></button
>
{#if file.mime_type && file.mime_type.includes("image")}
<Image
src={file.url}
title={null}
alt=""
loading="lazy"
class={"thumbnail-image"}
/>
{:else if file.mime_type && file.mime_type.includes("audio")}
<Music />
{:else if file.mime_type && file.mime_type.includes("video")}
<Video />
{:else}
<File />
{/if}
</button>
{/each}
{#if uploading}
<div class="loader"></div>
{/if}
</div>
{/if}
<textarea
data-testid="textbox"
use:text_area_resize={{
text: value.text,
lines: lines,
max_lines: max_lines
}}
class="scroll-hide"
dir={rtl ? "rtl" : "ltr"}
bind:value={value.text}
bind:this={el}
{placeholder}
rows={lines}
{disabled}
{autofocus}
on:keypress={handle_keypress}
on:blur
on:select={handle_select}
on:focus
on:scroll={handle_scroll}
on:paste={handle_paste}
style={text_align ? "text-align: " + text_align : ""}
/>
</Upload>
</div>
</label>
<style>
.input-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
textarea {
align-self: flex-start;
outline: none !important;
background: var(--input-background-fill);
padding: var(--input-padding);
width: 90%;
max-width: 95%;
max-height: 100%;
height: 25px;
color: var(--body-text-color);
font-weight: var(--input-text-weight);
font-size: var(--input-text-size);
line-height: var(--line-sm);
border: none;
margin-top: 0px;
margin-bottom: 0px;
margin-left: 35px;
padding-top: 12px;
resize: none;
}
textarea:disabled {
-webkit-text-fill-color: var(--body-text-color);
-webkit-opacity: 1;
opacity: 1;
width: 100%;
margin-left: 0px;
}
textarea::placeholder {
color: var(--input-placeholder-color);
}
.upload-button,
.submit-button {
position: absolute;
background: var(--button-secondary-background-fill);
color: var(--button-secondary-text-color);
border: none;
text-align: center;
text-decoration: none;
font-size: 20px;
cursor: pointer;
border-radius: 50%;
width: 30px;
height: 30px;
}
.upload-button:hover,
.submit-button:hover {
background: var(--button-secondary-background-fill-hover);
}
.upload-button:active,
.submit-button:active {
box-shadow: var(--button-shadow-active);
}
.submit-button {
right: 15px;
margin-left: 5px;
padding-bottom: 5px;
padding-left: 2px;
}
.submit-button :global(svg) {
height: 23px;
width: 23px;
padding-left: 4px;
padding-top: 2px;
}
.upload-button {
left: 10px;
margin-right: 5px;
}
.upload-button :global(svg) {
height: 23px;
width: 23px;
padding-left: 7px;
}
.loader {
display: flex;
justify-content: center;
align-items: center;
--ring-color: transparent;
position: relative;
border: 5px solid #f3f3f3;
border-top: 5px solid var(--color-accent);
border-radius: 50%;
width: 25px;
height: 25px;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.thumbnails :global(img) {
width: var(--size-full);
height: var(--size-full);
object-fit: cover;
border-radius: var(--radius-lg);
}
.thumbnails {
align-self: flex-start;
display: flex;
justify-content: left;
align-items: center;
gap: var(--spacing-lg);
}
.thumbnail-item {
display: flex;
justify-content: center;
align-items: center;
--ring-color: transparent;
position: relative;
box-shadow:
0 0 0 2px var(--ring-color),
var(--shadow-drop);
border: 1px solid var(--border-color-primary);
border-radius: var(--radius-lg);
background: var(--background-fill-secondary);
aspect-ratio: var(--ratio-square);
width: var(--size-full);
height: var(--size-full);
cursor: default;
}
.thumbnail-small {
flex: none;
transform: scale(0.9);
transition: 0.075s;
width: var(--size-12);
height: var(--size-12);
}
.thumbnail-item :global(svg) {
width: 30px;
height: 30px;
}
.delete-button {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
right: -7px;
top: -7px;
color: var(--button-secondary-text-color);
background: var(--button-secondary-background-fill);
border: none;
text-align: center;
text-decoration: none;
font-size: 10px;
cursor: pointer;
border-radius: 50%;
width: 20px;
height: 20px;
}
.disabled {
display: none;
}
.delete-button :global(svg) {
width: 12px;
height: 12px;
}
.delete-button:hover {
filter: brightness(1.2);
border: 0.8px solid var(--color-grey-500);
}
</style>