|
# The Frontend 🌐⭐️ |
|
|
|
This guide will cover everything you need to know to implement your custom component's frontend. |
|
|
|
Tip: Gradio components use Svelte. Writing Svelte is fun! If you're not familiar with it, we recommend checking out their interactive [guide](https://learn.svelte.dev/tutorial/welcome-to-svelte). |
|
|
|
## The directory structure |
|
|
|
The frontend code should have, at minimum, three files: |
|
|
|
* `Index.svelte`: This is the main export and where your component's layout and logic should live. |
|
* `Example.svelte`: This is where the example view of the component is defined. |
|
|
|
Feel free to add additional files and subdirectories. |
|
If you want to export any additional modules, remember to modify the `package.json` file |
|
|
|
```json |
|
"exports": { |
|
".": "./Index.svelte", |
|
"./example": "./Example.svelte", |
|
"./package.json": "./package.json" |
|
}, |
|
``` |
|
|
|
## The Index.svelte file |
|
|
|
Your component should expose the following props that will be passed down from the parent Gradio application. |
|
|
|
```typescript |
|
import type { LoadingStatus } from "@gradio/statustracker"; |
|
import type { Gradio } from "@gradio/utils"; |
|
|
|
export let gradio: Gradio<{ |
|
event_1: never; |
|
event_2: never; |
|
}>; |
|
|
|
export let elem_id = ""; |
|
export let elem_classes: string[] = []; |
|
export let scale: number | null = null; |
|
export let min_width: number | undefined = undefined; |
|
export let loading_status: LoadingStatus | undefined = undefined; |
|
export let mode: "static" | "interactive"; |
|
``` |
|
|
|
* `elem_id` and `elem_classes` allow Gradio app developers to target your component with custom CSS and JavaScript from the Python `Blocks` class. |
|
|
|
* `scale` and `min_width` allow Gradio app developers to control how much space your component takes up in the UI. |
|
|
|
* `loading_status` is used to display a loading status over the component when it is the output of an event. |
|
|
|
* `mode` is how the parent Gradio app tells your component whether the `interactive` or `static` version should be displayed. |
|
|
|
* `gradio`: The `gradio` object is created by the parent Gradio app. It stores some application-level configuration that will be useful in your component, like internationalization. You must use it to dispatch events from your component. |
|
|
|
A minimal `Index.svelte` file would look like: |
|
|
|
```typescript |
|
<script lang="ts"> |
|
import type { LoadingStatus } from "@gradio/statustracker"; |
|
import { Block } from "@gradio/atoms"; |
|
import { StatusTracker } from "@gradio/statustracker"; |
|
import type { Gradio } from "@gradio/utils"; |
|
|
|
export let gradio: Gradio<{ |
|
event_1: never; |
|
event_2: never; |
|
}>; |
|
|
|
export let value = ""; |
|
export let elem_id = ""; |
|
export let elem_classes: string[] = []; |
|
export let scale: number | null = null; |
|
export let min_width: number | undefined = undefined; |
|
export let loading_status: LoadingStatus | undefined = undefined; |
|
export let mode: "static" | "interactive"; |
|
</script> |
|
|
|
<Block |
|
visible={true} |
|
{elem_id} |
|
{elem_classes} |
|
{scale} |
|
{min_width} |
|
allow_overflow={false} |
|
padding={true} |
|
> |
|
{#if loading_status} |
|
<StatusTracker |
|
autoscroll={gradio.autoscroll} |
|
i18n={gradio.i18n} |
|
{...loading_status} |
|
/> |
|
{/if} |
|
<p>{value}</p> |
|
</Block> |
|
``` |
|
|
|
## The Example.svelte file |
|
|
|
The `Example.svelte` file should expose the following props: |
|
|
|
```typescript |
|
export let value: string; |
|
export let type: "gallery" | "table"; |
|
export let selected = false; |
|
export let samples_dir: string; |
|
export let index: number; |
|
``` |
|
|
|
* `value`: The example value that should be displayed. |
|
|
|
* `type`: This is a variable that can be either `"gallery"` or `"table"` depending on how the examples are displayed. The `"gallery"` form is used when the examples correspond to a single input component, while the `"table"` form is used when a user has multiple input components, and the examples need to populate all of them. |
|
|
|
* `selected`: You can also adjust how the examples are displayed if a user "selects" a particular example by using the selected variable. |
|
|
|
* `samples_dir`: A URL to prepend to `value` if your example is fetching a file from the server |
|
|
|
* `index`: The current index of the selected value. |
|
|
|
* Any additional props your "non-example" component takes! |
|
|
|
This is the `Example.svelte` file for the code `Radio` component: |
|
|
|
```typescript |
|
<script lang="ts"> |
|
export let value: string; |
|
export let type: "gallery" | "table"; |
|
export let selected = false; |
|
</script> |
|
|
|
<div |
|
class:table={type === "table"} |
|
class:gallery={type === "gallery"} |
|
class:selected |
|
> |
|
{value} |
|
</div> |
|
|
|
<style> |
|
.gallery { |
|
padding: var(--size-1) var(--size-2); |
|
} |
|
</style> |
|
``` |
|
|
|
## Handling Files |
|
|
|
If your component deals with files, these files **should** be uploaded to the backend server. |
|
The `@gradio/client` npm package provides the `upload`, `prepare_files`, and `normalise_file` utility functions to help you do this. |
|
|
|
The `prepare_files` function will convert the browser's `File` datatype to gradio's internal `FileData` type. |
|
You should use the `FileData` data in your component to keep track of uploaded files. |
|
|
|
The `upload` function will upload an array of `FileData` values to the server. |
|
|
|
The `normalise_file` function will generate the correct URL for your component to fetch the file from and set it to the `data` property of the `FileData.` |
|
|
|
|
|
Tip: Be sure you call `normalise_file` whenever your files are updated! |
|
|
|
|
|
Here's an example of loading files from an `<input>` element when its value changes. |
|
|
|
|
|
```typescript |
|
<script lang="ts"> |
|
|
|
import { upload, prepare_files, normalise_file, type FileData } from "@gradio/client"; |
|
export let root; |
|
export let value; |
|
let uploaded_files; |
|
|
|
$: value: normalise_file(uploaded_files, root) |
|
|
|
async function handle_upload(file_data: FileData[]): Promise<void> { |
|
await tick(); |
|
uploaded_files = await upload(file_data, root); |
|
} |
|
|
|
async function loadFiles(files: FileList): Promise<void> { |
|
let _files: File[] = Array.from(files); |
|
if (!files.length) { |
|
return; |
|
} |
|
if (file_count === "single") { |
|
_files = [files[0]]; |
|
} |
|
let file_data = await prepare_files(_files); |
|
await handle_upload(file_data); |
|
} |
|
|
|
async function loadFilesFromUpload(e: Event): Promise<void> { |
|
const target = e.target; |
|
|
|
if (!target.files) return; |
|
await loadFiles(target.files); |
|
} |
|
</script> |
|
|
|
<input |
|
type="file" |
|
on:change={loadFilesFromUpload} |
|
multiple={true} |
|
/> |
|
``` |
|
|
|
The component exposes a prop named `root`. |
|
This is passed down by the parent gradio app and it represents the base url that the files will be uploaded to and fetched from. |
|
|
|
For WASM support, you should get the upload function from the `Context` and pass that as the third parameter of the `upload` function. |
|
|
|
```typescript |
|
<script lang="ts"> |
|
import { getContext } from "svelte"; |
|
const upload_fn = getContext<typeof upload_files>("upload_files"); |
|
|
|
async function handle_upload(file_data: FileData[]): Promise<void> { |
|
await tick(); |
|
await upload(file_data, root, upload_fn); |
|
} |
|
</script> |
|
``` |
|
|
|
## Leveraging Existing Gradio Components |
|
|
|
Most of Gradio's frontend components are published on [npm](https://www.npmjs.com/), the javascript package repository. |
|
This means that you can use them to save yourself time while incorporating common patterns in your component, like uploading files. |
|
For example, the `@gradio/upload` package has `Upload` and `ModifyUpload` components for properly uploading files to the Gradio server. |
|
Here is how you can use them to create a user interface to upload and display PDF files. |
|
|
|
```typescript |
|
<script> |
|
import { type FileData, normalise_file, Upload, ModifyUpload } from "@gradio/upload"; |
|
import { Empty, UploadText, BlockLabel } from "@gradio/atoms"; |
|
</script> |
|
|
|
<BlockLabel Icon={File} label={label || "PDF"} /> |
|
{#if value === null && interactive} |
|
<Upload |
|
filetype="application/pdf" |
|
on:load={handle_load} |
|
{root} |
|
> |
|
<UploadText type="file" i18n={gradio.i18n} /> |
|
</Upload> |
|
{:else if value !== null} |
|
{#if interactive} |
|
<ModifyUpload i18n={gradio.i18n} on:clear={handle_clear}/> |
|
{/if} |
|
<iframe title={value.orig_name || "PDF"} src={value.data} height="{height}px" width="100%"></iframe> |
|
{:else} |
|
<Empty size="large"> <File/> </Empty> |
|
{/if} |
|
``` |
|
|
|
You can also combine existing Gradio components to create entirely unique experiences. |
|
Like rendering a gallery of chatbot conversations. |
|
The possibilities are endless, please read the documentation on our javascript packages [here](https://gradio.app/main/docs/js). |
|
We'll be adding more packages and documentation over the coming weeks! |
|
|
|
## Matching Gradio Core's Design System |
|
|
|
You can explore our component library via Storybook. You'll be able to interact with our components and see them in their various states. |
|
|
|
For those interested in design customization, we provide the CSS variables consisting of our color palette, radii, spacing, and the icons we use - so you can easily match up your custom component with the style of our core components. This Storybook will be regularly updated with any new additions or changes. |
|
|
|
[Storybook Link](https://gradio.app/main/docs/js/storybook) |
|
|
|
|
|
## Conclusion |
|
|
|
You now how to create delightful frontends for your components! |
|
|
|
|