This commit is contained in:
Ali Abid 2022-07-26 15:11:08 -04:00
commit 0e4931fbe2
119 changed files with 2408 additions and 807 deletions

View File

@ -39,7 +39,6 @@ jobs:
run: pnpm format:check
- name: typecheck
run: pnpm ts:check
continue-on-error: true
- name: unit tests
run: pnpm test:run
functional-test:

View File

@ -0,0 +1 @@
numpy

33
demo/blocks_flag/run.py Normal file
View File

@ -0,0 +1,33 @@
import numpy as np
import gradio as gr
def sepia(input_img, strength):
sepia_filter = strength * np.array(
[[0.393, 0.769, 0.189], [0.349, 0.686, 0.168], [0.272, 0.534, 0.131]]
) + (1-strength) * np.identity(3)
sepia_img = input_img.dot(sepia_filter.T)
sepia_img /= sepia_img.max()
return sepia_img
callback = gr.CSVLogger()
with gr.Blocks() as demo:
with gr.Row():
with gr.Column():
img_input = gr.Image()
strength = gr.Slider(0, 1, 0.5)
img_output = gr.Image()
with gr.Row():
btn = gr.Button("Flag")
# This needs to be called at some point prior to the first call to callback.flag()
callback.setup([img_input, strength, img_output], "flagged_data_points")
img_input.change(sepia, [img_input, strength], img_output)
strength.change(sepia, [img_input, strength], img_output)
# We can choose which components to flag -- in this case, we'll flag all of them
btn.click(lambda *args: callback.flag(args), [img_input, strength, img_output], None, _preprocess=False)
if __name__ == "__main__":
demo.launch()

View File

@ -0,0 +1 @@
shap

View File

@ -0,0 +1,57 @@
import gradio as gr
import shap
from transformers import pipeline
import matplotlib
import matplotlib.pyplot as plt
matplotlib.use('Agg')
sentiment_classifier = pipeline("text-classification", return_all_scores=True)
def classifier(text):
pred = sentiment_classifier(text)
return {p["label"]: p["score"] for p in pred[0]}
def interpretation_function(text):
explainer = shap.Explainer(sentiment_classifier)
shap_values = explainer([text])
# Dimensions are (batch size, text size, number of classes)
# Since we care about positive sentiment, use index 1
scores = list(zip(shap_values.data[0], shap_values.values[0, :, 1]))
scores_desc = sorted(scores, key=lambda t: t[1])[::-1]
# Filter out empty string added by shap
scores_desc = [t for t in scores_desc if t[0] != ""]
fig_m = plt.figure()
plt.bar(x=[s[0] for s in scores_desc[:5]],
height=[s[1] for s in scores_desc[:5]])
plt.title("Top words contributing to positive sentiment")
plt.ylabel("Shap Value")
plt.xlabel("Word")
return {"original": text, "interpretation": scores}, fig_m
with gr.Blocks() as demo:
with gr.Row():
with gr.Column():
input_text = gr.Textbox(label="Input Text")
with gr.Row():
classify = gr.Button("Classify Sentiment")
interpret = gr.Button("Interpret")
with gr.Column():
label = gr.Label(label="Predicted Sentiment")
with gr.Column():
with gr.Tabs():
with gr.TabItem("Display interpretation with built-in component"):
interpretation = gr.components.Interpretation(input_text)
with gr.TabItem("Display interpretation with plot"):
interpretation_plot = gr.Plot()
classify.click(classifier, input_text, label)
interpret.click(interpretation_function, input_text, [interpretation, interpretation_plot])
demo.launch()

View File

@ -1,5 +1,5 @@
{
"version": "3.0.24\n",
"version": "3.1.1\n",
"mode": "blocks",
"dev_mode": true,
"components": [
@ -64,6 +64,7 @@
"source": "upload",
"tool": "editor",
"streaming": false,
"mirror_webcam": true,
"show_label": true,
"name": "image",
"visible": true,
@ -127,6 +128,7 @@
"source": "upload",
"tool": "editor",
"streaming": false,
"mirror_webcam": true,
"show_label": true,
"name": "image",
"visible": true,
@ -188,6 +190,7 @@
],
"theme": "default",
"css": null,
"title": "Gradio",
"enable_queue": false,
"layout": {
"id": 0,
@ -277,11 +280,11 @@
"show_progress": true,
"documentation": [
[
"(List[str]): list of selected choices",
"(str | dict): base64 url data, or (if tool == \"sketch) a dict of image and mask base64 url data"
null,
null
],
[
"(Dict | List): JSON output"
null
]
]
},
@ -306,11 +309,11 @@
"show_progress": true,
"documentation": [
[
"(List[str]): list of selected choices",
"(str | dict): base64 url data, or (if tool == \"sketch) a dict of image and mask base64 url data"
null,
null
],
[
"(Dict | List): JSON output"
null
]
]
},

View File

@ -0,0 +1,63 @@
import gradio as gr
import cv2
import numpy as np
import random
# Convert decimal color to hexadecimal color
def RGB_to_Hex(rgb):
color = "#"
for i in rgb:
num = int(i)
color += str(hex(num))[-2:].replace("x", "0").upper()
return color
# Randomly generate light or dark colors
def random_color(is_light=True):
return (
random.randint(0, 127) + int(is_light) * 128,
random.randint(0, 127) + int(is_light) * 128,
random.randint(0, 127) + int(is_light) * 128,
)
def switch_color(color_style):
if color_style == "light":
is_light = True
elif color_style == "dark":
is_light = False
back_color_ = random_color(is_light) # Randomly generate colors
back_color = RGB_to_Hex(back_color_) # Convert to hexadecimal
# Draw color pictures.
w, h = 50, 50
img = np.zeros((h, w, 3), np.uint8)
cv2.rectangle(img, (0, 0), (w, h), back_color_, thickness=-1)
return back_color, back_color, img
inputs = [gr.Radio(["light", "dark"], value="light")]
outputs = [
gr.ColorPicker(label="color"),
gr.Textbox(label="hexadecimal color"),
gr.Image(type="numpy", label="color picture"),
]
title = "Color Generator"
description = (
"Click the Submit button, and a dark or light color will be randomly generated."
)
demo = gr.Interface(
fn=switch_color,
inputs=inputs,
outputs=outputs,
title=title,
description=description,
)
if __name__ == "__main__":
demo.launch()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -35,8 +35,8 @@ demo = gr.Interface(
inputs=inputs,
outputs=outputs,
examples=[
[os.path.join(os.path.dirname(__file__), "lion.jpg"), "#ff0000"],
[os.path.join(os.path.dirname(__file__), "lion.jpg"), "#0000FF"],
[os.path.join(os.path.dirname(__file__), "rabbit.png"), "#ff0000"],
[os.path.join(os.path.dirname(__file__), "rabbit.png"), "#0000FF"],
],
)

View File

@ -14,6 +14,8 @@ def gender_of_sentence(sentence):
return {"male": male_count / total, "female": female_count / total}
# Number of arguments to interpretation function must
# match number of inputs to prediction function
def interpret_gender(sentence):
result = gender_of_sentence(sentence)
is_male = result["male"] > result["female"]
@ -28,7 +30,9 @@ def interpret_gender(sentence):
):
score = -1
interpretation.append((word, score))
return interpretation
# Output must be a list of lists containing the same number of elements as inputs
# Each element corresponds to the interpretation scores for the given input
return [interpretation]
demo = gr.Interface(

View File

@ -0,0 +1,2 @@
torch
transformers

18
demo/ner_pipeline/run.py Normal file
View File

@ -0,0 +1,18 @@
from transformers import pipeline
import gradio as gr
ner_pipeline = pipeline("ner")
examples = [
"Does Chicago have any stores and does Joe live here?",
]
def ner(text):
output = ner_pipeline(text)
return {"text": text, "entities": output}
gr.Interface(ner,
gr.Textbox(placeholder="Enter sentence here..."),
gr.HighlightedText(),
examples=examples).launch()

View File

@ -1,13 +1,18 @@
{
"mode": "blocks",
"version": "3.1.1\n",
"mode": "interface",
"dev_mode": true,
"components": [
{
"id": 7,
"type": "row",
"props": {
"type": "row",
"css": {},
"default_value": true
"visible": true,
"style": {
"equal_height": false,
"mobile_collapse": true
}
}
},
{
@ -16,8 +21,8 @@
"props": {
"type": "column",
"variant": "panel",
"css": {},
"default_value": true
"visible": true,
"style": {}
}
},
{
@ -26,8 +31,8 @@
"props": {
"type": "column",
"variant": "default",
"css": {},
"default_value": true
"visible": true,
"style": {}
}
},
{
@ -39,11 +44,11 @@
"Plotly",
"Bokeh"
],
"default_value": "Matplotlib",
"label": "Plot Type",
"show_label": true,
"name": "dropdown",
"css": {}
"visible": true,
"style": {}
}
},
{
@ -53,11 +58,12 @@
"minimum": 1,
"maximum": 4,
"step": 0.01,
"default_value": 3.2,
"value": 3.2,
"label": "R",
"show_label": true,
"name": "slider",
"css": {}
"visible": true,
"style": {}
}
},
{
@ -71,11 +77,11 @@
"April",
"May"
],
"default_value": "January",
"label": "Month",
"show_label": true,
"name": "dropdown",
"css": {}
"visible": true,
"style": {}
}
},
{
@ -88,25 +94,27 @@
"Mexico",
"UK"
],
"default_value": [
"value": [
"USA",
"Canada"
],
"label": "Countries",
"show_label": true,
"name": "checkboxgroup",
"css": {}
"visible": true,
"style": {}
}
},
{
"id": 4,
"type": "checkbox",
"props": {
"default_value": false,
"value": false,
"label": "Social Distancing?",
"show_label": true,
"name": "checkbox",
"css": {}
"visible": true,
"style": {}
}
},
{
@ -114,26 +122,32 @@
"type": "row",
"props": {
"type": "row",
"css": {},
"default_value": true
"visible": true,
"style": {
"mobile_collapse": false
}
}
},
{
"id": 11,
"type": "button",
"props": {
"default_value": "Clear",
"value": "Clear",
"variant": "secondary",
"name": "button",
"css": {}
"visible": true,
"style": {}
}
},
{
"id": 12,
"type": "button",
"props": {
"default_value": "Submit",
"value": "Submit",
"variant": "primary",
"name": "button",
"css": {}
"visible": true,
"style": {}
}
},
{
@ -142,8 +156,8 @@
"props": {
"type": "column",
"variant": "panel",
"css": {},
"default_value": true
"visible": true,
"style": {}
}
},
{
@ -152,7 +166,8 @@
"props": {
"cover_container": true,
"name": "statustracker",
"css": {}
"visible": true,
"style": {}
}
},
{
@ -163,7 +178,8 @@
"show_label": true,
"interactive": false,
"name": "plot",
"css": {}
"visible": true,
"style": {}
}
},
{
@ -171,21 +187,83 @@
"type": "row",
"props": {
"type": "row",
"css": {},
"default_value": true
"visible": true,
"style": {
"mobile_collapse": false
}
}
},
{
"id": 16,
"type": "button",
"props": {
"default_value": "Flag",
"value": "Flag",
"variant": "secondary",
"name": "button",
"css": {}
"visible": true,
"style": {}
}
},
{
"id": 17,
"type": "dataset",
"props": {
"components": [
"dropdown",
"slider",
"dropdown",
"checkboxgroup",
"checkbox"
],
"headers": [
"Plot Type",
"R",
"Month",
"Countries",
"Social Distancing?"
],
"samples": [
[
"Matplotlib",
2,
"March",
[
"Mexico",
"UK"
],
true
],
[
"Plotly",
3.6,
"February",
[
"Canada",
"Mexico",
"UK"
],
false
],
[
"Bokeh",
1.2,
"May",
[
"UK"
],
true
]
],
"type": "index",
"name": "dataset",
"visible": true,
"style": {}
}
}
],
"theme": "default",
"css": null,
"title": "Gradio",
"enable_queue": false,
"layout": {
"id": 6,
@ -249,27 +327,13 @@
]
}
]
},
{
"id": 17
}
]
},
"dependencies": [
{
"targets": [
16
],
"trigger": "click",
"inputs": [
0,
1,
2,
3,
4,
5
],
"outputs": [],
"status_tracker": null,
"queue": null
},
{
"targets": [
12
@ -285,8 +349,25 @@
"outputs": [
5
],
"backend_fn": true,
"js": null,
"status_tracker": 14,
"queue": null
"queue": null,
"api_name": "predict",
"scroll_to_output": true,
"show_progress": true,
"documentation": [
[
null,
null,
null,
null,
null
],
[
null
]
]
},
{
"targets": [
@ -303,8 +384,59 @@
5,
9
],
"backend_fn": false,
"js": "() => [null, 3.2, null, [], null, null, {\"variant\": null, \"visible\": true, \"__type__\": \"update\"}]\n ",
"status_tracker": null,
"queue": null
"queue": null,
"api_name": null,
"scroll_to_output": false,
"show_progress": true
},
{
"targets": [
16
],
"trigger": "click",
"inputs": [
0,
1,
2,
3,
4,
5
],
"outputs": [],
"backend_fn": true,
"js": null,
"status_tracker": null,
"queue": false,
"api_name": null,
"scroll_to_output": false,
"show_progress": true
},
{
"targets": [
17
],
"trigger": "click",
"inputs": [
17
],
"outputs": [
0,
1,
2,
3,
4,
5
],
"backend_fn": true,
"js": null,
"status_tracker": null,
"queue": false,
"api_name": null,
"scroll_to_output": false,
"show_progress": true
}
]
}

View File

@ -26,6 +26,7 @@ from gradio.components import (
Highlightedtext,
HighlightedText,
Image,
Interpretation,
Json,
Label,
Markdown,

View File

@ -202,11 +202,13 @@ class class_or_instancemethod(classmethod):
@document()
def update(**kwargs) -> dict:
"""
Updates component parameters.
Updates component properties.
This is a shorthand for using the update method on a component.
For example, rather than using gr.Number.update(...) you can just use gr.update(...).
Note that your editor's autocompletion will suggest proper parameters
if you use the update method on the component.
Demos: blocks_update, blocks_essay_update
Demos: blocks_essay, blocks_update, blocks_essay_update
Parameters:
kwargs: Key-word arguments used to update the component's properties.

View File

@ -2894,7 +2894,7 @@ class ColorPicker(Changeable, Submittable, IOComponent):
Preprocessing: passes selected color value as a {str} into the function.
Postprocessing: expects a {str} returned from function and sets color picker value to it.
Examples-format: a {str} with a hexadecimal representation of a color, e.g. "#ff0000" for red.
Demos: color_picker
Demos: color_picker, color_generator
"""
def __init__(
@ -3148,14 +3148,14 @@ class HighlightedText(Changeable, IOComponent):
"""
Displays text that contains spans that are highlighted by category or numerical value.
Preprocessing: this component does *not* accept input.
Postprocessing: expects a {List[Tuple[str, float | str]]]} consisting of spans of text and their associated labels.
Postprocessing: expects a {List[Tuple[str, float | str]]]} consisting of spans of text and their associated labels, or a {Dict} with two keys: (1) "text" whose value is the complete text, and "entities", which is a list of dictionaries, each of which have the keys: "entity" (consisting of the entity label), "start" (the character index where the label starts), and "end" (the character index where the label ends).
Demos: diff_texts, text_analysis
"""
def __init__(
self,
value: Optional[str] = None,
value: Optional[List[Tuple[str, str | float | None]] | Dict] = None,
*,
color_map: Dict[str, str] = None, # Parameter moved to HighlightedText.style()
show_legend: bool = False,
@ -3206,7 +3206,7 @@ class HighlightedText(Changeable, IOComponent):
@staticmethod
def update(
value: Optional[Any] = None,
value: Optional[List[Tuple[str, str | float | None]] | Dict] = None,
color_map: Optional[Dict[str, str]] = None,
show_legend: Optional[bool] = None,
label: Optional[str] = None,
@ -3225,8 +3225,8 @@ class HighlightedText(Changeable, IOComponent):
return updated_config
def postprocess(
self, y: List[Tuple[str, str | float | None]]
) -> List[Tuple[str, str | float | None]]:
self, y: Optional[List[Tuple[str, str | float | None]] | Dict]
) -> Optional[List[Tuple[str, str | float | None]]]:
"""
Parameters:
y: List of (word, category) tuples
@ -3235,6 +3235,22 @@ class HighlightedText(Changeable, IOComponent):
"""
if y is None:
return None
if isinstance(y, dict):
text = y["text"]
entities = y["entities"]
if len(entities) == 0:
y = [(text, None)]
else:
list_format = []
index = 0
for entity in entities:
list_format.append((text[index : entity["start"]], None))
list_format.append(
(text[entity["start"] : entity["end"]], entity["entity"])
)
index = entity["end"]
list_format.append((text[index:], None))
y = list_format
if self.combine_adjacent:
output = []
running_text, running_category = None, None
@ -4015,10 +4031,13 @@ class Markdown(IOComponent, Changeable):
############################
@document()
class Dataset(Clickable, Component):
"""
Used to create a output widget for showing datasets. Used to render the examples
box in the interface.
Used to create an output widget for showing datasets. Used to render the examples
box.
Preprocessing: this component does *not* accept input.
Postprocessing: expects a {list} of {lists} corresponding to the dataset data.
"""
def __init__(
@ -4088,9 +4107,12 @@ class Dataset(Clickable, Component):
)
@document()
class Interpretation(Component):
"""
Used to create an interpretation widget for a component.
Preprocessing: this component does *not* accept input.
Postprocessing: expects a {dict} with keys "original" and "interpretation".
"""
def __init__(
@ -4101,6 +4123,12 @@ class Interpretation(Component):
elem_id: Optional[str] = None,
**kwargs,
):
"""
Parameters:
component: Which component to show in the interpretation widget.
visible: Whether or not the interpretation is visible.
elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
"""
Component.__init__(self, visible=visible, elem_id=elem_id, **kwargs)
self.component = component

View File

@ -8,6 +8,7 @@ import os
import shutil
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Tuple
from gradio import utils
from gradio.components import Dataset
from gradio.documentation import document, set_documentation_group
from gradio.flagging import CSVLogger
@ -153,13 +154,13 @@ class Examples:
self.cache_interface_examples()
def load_example(example_id):
processed_example = self.processed_examples[example_id]
if cache_examples:
processed_example += self.load_from_cache(example_id)
if len(processed_example) == 1:
return processed_example[0]
processed_example = self.processed_examples[
example_id
] + self.load_from_cache(example_id)
else:
return processed_example
processed_example = self.processed_examples[example_id]
return utils.resolve_singleton(processed_example)
dataset.click(
load_example,

View File

@ -10,10 +10,13 @@ from typing import TYPE_CHECKING, Any, List, Optional
import gradio as gr
from gradio import encryptor, utils
from gradio.documentation import document, set_documentation_group
if TYPE_CHECKING:
from gradio.components import Component
set_documentation_group("flagging")
class FlaggingCallback(ABC):
"""
@ -54,12 +57,23 @@ class FlaggingCallback(ABC):
pass
@document()
class SimpleCSVLogger(FlaggingCallback):
"""
A simple example implementation of the FlaggingCallback abstract class
provided for illustrative purposes.
A simplified implementation of the FlaggingCallback abstract class
provided for illustrative purposes. Each flagged sample (both the input and output data)
is logged to a CSV file on the machine running the gradio app.
Example:
import gradio as gr
def image_classifier(inp):
return {'cat': 0.3, 'dog': 0.7}
demo = gr.Interface(fn=image_classifier, inputs="image", outputs="label",
flagging_callback=SimpleCSVLogger())
"""
def __init__(self):
pass
def setup(self, components: List[Component], flagging_dir: str):
self.components = components
self.flagging_dir = flagging_dir
@ -95,12 +109,22 @@ class SimpleCSVLogger(FlaggingCallback):
return line_count
@document()
class CSVLogger(FlaggingCallback):
"""
The default implementation of the FlaggingCallback abstract class.
Logs the input and output data to a CSV file. Supports encryption.
The default implementation of the FlaggingCallback abstract class. Each flagged
sample (both the input and output data) is logged to a CSV file with headers on the machine running the gradio app.
Example:
import gradio as gr
def image_classifier(inp):
return {'cat': 0.3, 'dog': 0.7}
demo = gr.Interface(fn=image_classifier, inputs="image", outputs="label",
flagging_callback=CSVLogger())
"""
def __init__(self):
pass
def setup(
self,
components: List[Component],
@ -163,7 +187,7 @@ class CSVLogger(FlaggingCallback):
if self.encryption_key:
output = io.StringIO()
if not is_new:
with open(log_filepath, "rb") as csvfile:
with open(log_filepath, "rb", encoding="utf-8") as csvfile:
encrypted_csv = csvfile.read()
decrypted_csv = encryptor.decrypt(
self.encryption_key, encrypted_csv
@ -177,13 +201,13 @@ class CSVLogger(FlaggingCallback):
if is_new:
writer.writerow(headers)
writer.writerow(csv_data)
with open(log_filepath, "wb") as csvfile:
with open(log_filepath, "wb", encoding="utf-8") as csvfile:
csvfile.write(
encryptor.encrypt(self.encryption_key, output.getvalue().encode())
)
else:
if flag_index is None:
with open(log_filepath, "a", newline="") as csvfile:
with open(log_filepath, "a", newline="", encoding="utf-8") as csvfile:
writer = csv.writer(
csvfile, quoting=csv.QUOTE_NONNUMERIC, quotechar="'"
)
@ -191,49 +215,50 @@ class CSVLogger(FlaggingCallback):
writer.writerow(headers)
writer.writerow(csv_data)
else:
with open(log_filepath) as csvfile:
with open(log_filepath, encoding="utf-8") as csvfile:
file_content = csvfile.read()
file_content = replace_flag_at_index(file_content)
with open(
log_filepath, "w", newline=""
log_filepath, "w", newline="", encoding="utf-8"
) as csvfile: # newline parameter needed for Windows
csvfile.write(file_content)
with open(log_filepath, "r") as csvfile:
with open(log_filepath, "r", encoding="utf-8") as csvfile:
line_count = len([None for row in csv.reader(csvfile)]) - 1
return line_count
@document()
class HuggingFaceDatasetSaver(FlaggingCallback):
"""
A FlaggingCallback that saves flagged data to a HuggingFace dataset.
A callback that saves each flagged sample (both the input and output data)
to a HuggingFace dataset.
Example:
import gradio as gr
hf_writer = gr.HuggingFaceDatasetSaver(HF_API_TOKEN, "image-classification-mistakes")
def image_classifier(inp):
return {'cat': 0.3, 'dog': 0.7}
demo = gr.Interface(fn=image_classifier, inputs="image", outputs="label",
allow_flagging="manual", flagging_callback=hf_writer)
"""
def __init__(
self,
hf_foken: str,
hf_token: str,
dataset_name: str,
organization: Optional[str] = None,
private: bool = False,
verbose: bool = True,
):
"""
Params:
hf_token (str): The token to use to access the huggingface API.
dataset_name (str): The name of the dataset to save the data to, e.g.
"image-classifier-1"
organization (str): The name of the organization to which to attach
the datasets. If None, the dataset attaches to the user only.
private (bool): If the dataset does not already exist, whether it
should be created as a private dataset or public. Private datasets
may require paid huggingface.co accounts
verbose (bool): Whether to print out the status of the dataset
creation.
Parameters:
hf_token: The HuggingFace token to use to create (and write the flagged sample to) the HuggingFace dataset.
dataset_name: The name of the dataset to save the data to, e.g. "image-classifier-1"
organization: The organization to save the dataset under. The hf_token must provide write access to this organization. If not provided, saved under the name of the user corresponding to the hf_token.
private: Whether the dataset should be private (defaults to False).
"""
self.hf_foken = hf_foken
self.hf_token = hf_token
self.dataset_name = dataset_name
self.organization_name = organization
self.dataset_private = private
self.verbose = verbose
def setup(self, components: List[Component], flagging_dir: str):
"""
@ -250,7 +275,7 @@ class HuggingFaceDatasetSaver(FlaggingCallback):
)
path_to_dataset_repo = huggingface_hub.create_repo(
name=self.dataset_name,
token=self.hf_foken,
token=self.hf_token,
private=self.dataset_private,
repo_type="dataset",
exist_ok=True,
@ -262,7 +287,7 @@ class HuggingFaceDatasetSaver(FlaggingCallback):
self.repo = huggingface_hub.Repository(
local_dir=self.dataset_dir,
clone_from=path_to_dataset_repo,
use_auth_token=self.hf_foken,
use_auth_token=self.hf_token,
)
self.repo.git_pull()
@ -282,7 +307,7 @@ class HuggingFaceDatasetSaver(FlaggingCallback):
is_new = not os.path.exists(self.log_file)
infos = {"flagged": {"features": {}}}
with open(self.log_file, "a", newline="") as csvfile:
with open(self.log_file, "a", newline="", encoding="utf-8") as csvfile:
writer = csv.writer(csvfile)
# File previews for certain input and output types
@ -338,7 +363,7 @@ class HuggingFaceDatasetSaver(FlaggingCallback):
if is_new:
json.dump(infos, open(self.infos_file, "w"))
with open(self.log_file, "r") as csvfile:
with open(self.log_file, "r", encoding="utf-8") as csvfile:
line_count = len([None for row in csv.reader(csvfile)]) - 1
self.repo.push_to_hub(commit_message="Flagged sample #{}".format(line_count))

View File

@ -45,10 +45,10 @@ if TYPE_CHECKING: # Only import for type checking (is False at runtime).
@document("launch", "load", "from_pipeline", "integrate")
class Interface(Blocks):
"""
The Interface class is Gradio's main high-level abstraction, and allows you to create a
web-based GUI / demo around a machine learning model (or any Python function). You must specify
three parameters: (1) the function to create a GUI for (2) the desired input components and
(3) the desired output components. Further parameters can be specified to control the appearance
Interface is Gradio's main high-level class, and allows you to create a web-based GUI / demo
around a machine learning model (or any Python function) in a few lines of code.
You must specify three parameters: (1) the function to create a GUI for (2) the desired input components and
(3) the desired output components. Additional parameters can be used to control the appearance
and behavior of the demo.
Example:
@ -160,7 +160,7 @@ class Interface(Blocks):
cache_examples: If True, caches examples in the server for fast runtime in examples. The default option in HuggingFace Spaces is True. The default option elsewhere is False.
examples_per_page: If examples are provided, how many to display per page.
live: whether the interface should automatically rerun if any of the inputs change.
interpretation: function that provides interpretation explaining prediction output. Pass "default" to use simple built-in interpreter, "shap" to use a built-in shapley-based interpreter, or your own custom interpretation function.
interpretation: function that provides interpretation explaining prediction output. Pass "default" to use simple built-in interpreter, "shap" to use a built-in shapley-based interpreter, or your own custom interpretation function. For more information on the different interpretation methods, see the Advanced Interface Features guide.
num_shap: a multiplier that determines how many examples are computed for shap-based interpretation. Increasing this value will increase shap runtime, but improve results. Only applies if interpretation is "shap".
title: a title for the interface; if provided, appears above the input and output components in large font. Also used as the tab title when opened in a browser window.
description: a description for the interface; if provided, appears above the input and output components and beneath the title in regular font. Accepts Markdown and HTML content.

View File

@ -18,7 +18,7 @@ en = {
"COLAB_DEBUG_TRUE": "Colab notebook detected. This cell will run indefinitely so that you can see errors and logs. "
"To turn off, set debug=False in launch().",
"COLAB_DEBUG_FALSE": "Colab notebook detected. To show errors in colab notebook, set debug=True in launch()",
"SHARE_LINK_MESSAGE": "\nThis share link expires in 72 hours. For free permanent hosting, check out Spaces (https://www.huggingface.co/spaces)",
"SHARE_LINK_MESSAGE": "\nThis share link expires in 72 hours. For free permanent hosting, check out Spaces: https://www.huggingface.co/spaces",
"PRIVATE_LINK_MESSAGE": "Since this is a private endpoint, this share link will never expire.",
"INLINE_DISPLAY_BELOW": "Interface loading below...",
"MEDIA_PERMISSIONS_IN_COLAB": "Your interface requires microphone or webcam permissions - this may cause issues in Colab. Use the External URL in case of issues.",

View File

@ -1 +1 @@
3.1.0
3.1.1

View File

@ -0,0 +1,388 @@
# How to Create a New Component
Docs: component
## Introduction
The purpose of this guide is to illustrate how to add a new component, which you can use in your Gradio applications. The guide will be complemented by code snippets showing step by step how the [ColorPicker](https://gradio.app/docs/#colorpicker) component was added.
## Prerequisites
Make sure you have followed the [CONTRIBUTING.md](../CONTRIBUTING.md) guide in order to setup your local development environment (both client and server side).
## Step 1 - Create a New Python Class and Import it
The first thing to do is to create a new class within the [components.py](https://github.com/gradio-app/gradio/blob/main/gradio/components.py) file. This Python class should inherit from a list of base components and should be placed within the file in the correct section with respect to the type of component you want to add (e.g. input, output or static components).
In general, it is advisable to take an existing component as a reference (e.g. [TextBox](https://github.com/gradio-app/gradio/blob/main/gradio/components.py#L290)), copy its code as a skeleton and then adapt it to the case at hand.
Let's take a look at the class added to the [components.py](https://github.com/gradio-app/gradio/blob/main/gradio/components.py) file for the ColorPicker component:
```python
@document()
class ColorPicker(Changeable, Submittable, IOComponent):
"""
Creates a color picker for user to select a color as string input.
Preprocessing: passes selected color value as a {str} into the function.
Postprocessing: expects a {str} returned from function and sets color picker value to it.
Examples-format: a {str} with a hexadecimal representation of a color, e.g. "#ff0000" for red.
Demos: color_picker, color_generator
"""
def __init__(
self,
value: str = None,
*,
label: Optional[str] = None,
show_label: bool = True,
interactive: Optional[bool] = None,
visible: bool = True,
elem_id: Optional[str] = None,
**kwargs,
):
"""
Parameters:
value: default text to provide in color picker.
label: component name in interface.
show_label: if True, will display label.
interactive: if True, will be rendered as an editable color picker; if False, editing will be disabled. If not provided, this is inferred based on whether the component is used as an input or output.
visible: If False, component will be hidden.
elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
"""
self.value = self.postprocess(value)
self.cleared_value = "#000000"
self.test_input = value
IOComponent.__init__(
self,
label=label,
show_label=show_label,
interactive=interactive,
visible=visible,
elem_id=elem_id,
**kwargs,
)
def get_config(self):
return {
"value": self.value,
**IOComponent.get_config(self),
}
@staticmethod
def update(
value: Optional[Any] = None,
label: Optional[str] = None,
show_label: Optional[bool] = None,
visible: Optional[bool] = None,
interactive: Optional[bool] = None,
):
updated_config = {
"value": value,
"label": label,
"show_label": show_label,
"visible": visible,
"__type__": "update",
}
return IOComponent.add_interactive_to_config(updated_config, interactive)
# Input Functionalities
def preprocess(self, x: str | None) -> Any:
"""
Any preprocessing needed to be performed on function input.
Parameters:
x (str): text
Returns:
(str): text
"""
if x is None:
return None
else:
return str(x)
def preprocess_example(self, x: str | None) -> Any:
"""
Any preprocessing needed to be performed on an example before being passed to the main function.
"""
if x is None:
return None
else:
return str(x)
def generate_sample(self) -> str:
return "#000000"
# Output Functionalities
def postprocess(self, y: str | None):
"""
Any postprocessing needed to be performed on function output.
Parameters:
y (str | None): text
Returns:
(str | None): text
"""
if y is None:
return None
else:
return str(y)
def deserialize(self, x):
"""
Convert from serialized output (e.g. base64 representation) from a call() to the interface to a human-readable version of the output (path of an image, etc.)
"""
return x
```
Once defined, it is necessary to import the new class inside the [\_\_init\_\_](https://github.com/gradio-app/gradio/blob/main/gradio/__init__.py) module class in order to make it module visible.
```python
from gradio.components import (
...
ColorPicker,
...
)
```
### Step 1.1 - Writing Unit Test for Python Class
When developing new components, you should also write a suite of unit tests for it. The tests should be placed in the [gradio/test/test_components.py](https://github.com/gradio-app/gradio/blob/main/test/test_components.py) file. Again, as above, take a cue from the tests of other components (e.g. [Textbox](https://github.com/gradio-app/gradio/blob/main/test/test_components.py)) and add as many unit tests as you think are appropriate to test all the different aspects and functionalities of the new component. For example, the following tests were added for the ColorPicker component:
```python
class TestColorPicker(unittest.TestCase):
def test_component_functions(self):
"""
Preprocess, postprocess, serialize, save_flagged, restore_flagged, tokenize, generate_sample, get_config
"""
color_picker_input = gr.ColorPicker()
self.assertEqual(color_picker_input.preprocess("#000000"), "#000000")
self.assertEqual(color_picker_input.preprocess_example("#000000"), "#000000")
self.assertEqual(color_picker_input.postprocess(None), None)
self.assertEqual(color_picker_input.postprocess("#FFFFFF"), "#FFFFFF")
self.assertEqual(color_picker_input.serialize("#000000", True), "#000000")
color_picker_input.interpretation_replacement = "unknown"
self.assertEqual(
color_picker_input.get_config(),
{
"value": None,
"show_label": True,
"label": None,
"style": {},
"elem_id": None,
"visible": True,
"interactive": None,
"name": "colorpicker",
},
)
self.assertIsInstance(color_picker_input.generate_sample(), str)
def test_in_interface_as_input(self):
"""
Interface, process, interpret,
"""
iface = gr.Interface(lambda x: x, "colorpicker", "colorpicker")
self.assertEqual(iface.process(["#000000"]), ["#000000"])
def test_in_interface_as_output(self):
"""
Interface, process
"""
iface = gr.Interface(lambda x: x, "colorpicker", gr.ColorPicker())
self.assertEqual(iface.process(["#000000"]), ["#000000"])
def test_static(self):
"""
postprocess
"""
component = gr.ColorPicker("#000000")
self.assertEqual(component.get_config().get("value"), "#000000")
```
## Step 2 - Create a New Svelte Component
Let's see the steps you need to follow to create the frontend of your new component and to map it to its python code:
- Create a new UI-side Svelte component and figure out where to place it. The options are: create a package for the new component in the [ui/packages folder](https://github.com/gradio-app/gradio/tree/main/ui/packages), if this is completely different from existing components or add the new component to an existing package, such as to the [form package](https://github.com/gradio-app/gradio/tree/main/ui/packages/form). The ColorPicker component for example, was included in the form package because it is similar to components that already exist.
- Create a file with an appropriate name in the src folder of the package where you placed the Svelte component, note: the name must start with a capital letter. This is the 'core' component and it's the generic component that has no knowledge of Gradio specific functionality. Initially add any text/html to this file so that the component renders something. The Svelte application code for the ColorPicker looks like this:
```typescript
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { get_styles } from "@gradio/utils";
import { BlockTitle } from "@gradio/atoms";
import type { Styles } from "@gradio/utils";
export let value: string = "#000000";
export let style: Styles = {};
export let label: string;
export let disabled = false;
export let show_label: boolean = true;
$: value;
$: handle_change(value);
const dispatch = createEventDispatcher<{
change: string;
submit: undefined;
}>();
function handle_change(val: string) {
dispatch("change", val);
}
$: ({ classes } = get_styles(style, ["rounded", "border"]));
</script>
<!-- svelte-ignore a11y-label-has-associated-control -->
<label class="block">
<BlockTitle {show_label}>{label}</BlockTitle>
<input
type="color"
class="gr-box-unrounded {classes}"
bind:value
{disabled}
/>
</label>
```
- Export this file inside the index.ts file of the package where you placed the Svelte component by doing `export { default as FileName } from "./FileName.svelte"`. The ColorPicker file is exported in the [index.ts](https://github.com/gradio-app/gradio/blob/main/ui/packages/form/src/index.ts) file and the export is performed by doing: `export { default as ColorPicker } from "./ColorPicker.svelte";`.
- Create the Gradio specific component in [ui/packages/app/src/components](https://github.com/gradio-app/gradio/tree/main/ui/packages/app/src/components). This is a Gradio wrapper that handles the specific logic of the library, passes the necessary data down to the core component and attaches any necessary event listeners. Copy the folder of another component, rename it and edit the code inside it, keeping the structure.
Here you will have three files, the first file is for the Svelte application, and it will look like this:
```typescript
<svelte:options accessors={true} />
<script lang="ts">
import { ColorPicker } from "@gradio/form";
import { Block } from "@gradio/atoms";
import StatusTracker from "../StatusTracker/StatusTracker.svelte";
import type { LoadingStatus } from "../StatusTracker/types";
import type { Styles } from "@gradio/utils";
export let label: string = "ColorPicker";
export let elem_id: string = "";
export let visible: boolean = true;
export let value: string;
export let form_position: "first" | "last" | "mid" | "single" = "single";
export let show_label: boolean;
export let style: Styles = {};
export let loading_status: LoadingStatus;
export let mode: "static" | "dynamic";
</script>
<Block
{visible}
{form_position}
{elem_id}
disable={typeof style.container === "boolean" && !style.container}
>
<StatusTracker {...loading_status} />
<ColorPicker
{style}
bind:value
{label}
{show_label}
on:change
on:submit
disabled={mode === "static"}
/>
</Block>
```
The second one contains the tests for the frontend, for example for the ColorPicker component:
```typescript
import { test, describe, assert, afterEach } from "vitest";
import { cleanup, render } from "@gradio/tootils";
import ColorPicker from "./ColorPicker.svelte";
import type { LoadingStatus } from "../StatusTracker/types";
const loading_status = {
eta: 0,
queue_position: 1,
status: "complete" as LoadingStatus["status"],
scroll_to_output: false,
visible: true,
fn_index: 0
};
describe("ColorPicker", () => {
afterEach(() => cleanup());
test("renders provided value", () => {
const { getByDisplayValue } = render(ColorPicker, {
loading_status,
show_label: true,
mode: "dynamic",
value: "#000000",
label: "ColorPicker"
});
const item: HTMLInputElement = getByDisplayValue("#000000");
assert.equal(item.value, "#000000");
});
test("changing the color should update the value", async () => {
const { component, getByDisplayValue } = render(ColorPicker, {
loading_status,
show_label: true,
mode: "dynamic",
value: "#000000",
label: "ColorPicker"
});
const item: HTMLInputElement = getByDisplayValue("#000000");
assert.equal(item.value, "#000000");
await component.$set({
value: "#FFFFFF"
});
assert.equal(component.value, "#FFFFFF");
});
});
```
The third one is the index.ts file:
```typescript
export { default as Component } from "./ColorPicker.svelte";
export const modes = ["static", "dynamic"];
```
- Add the mapping for your component in the [directory.ts file](https://github.com/gradio-app/gradio/blob/main/ui/packages/app/src/components/directory.ts). To do this, copy and paste the mapping line of any component and edit its text. The key name must be the lowercase version of the actual component name in the Python library. So for example, for the ColorPicker component the mapping looks like this:
```typescript
export const component_map = {
...
colorpicker: () => import("./ColorPicker"),
...
}
```
### Step 2.1 . Writing Unit Test for Svelte Component
When developing new components, you should also write a suite of unit tests for it. The tests should be placed in the new component's folder in a file named MyAwesomeComponent.test.ts. Again, as above, take a cue from the tests of other components (e.g. [Textbox.test.ts](https://github.com/gradio-app/gradio/blob/main/ui/packages/app/src/components/Textbox/Textbox.test.ts)) and add as many unit tests as you think are appropriate to test all the different aspects and functionalities of the new component.
### Step 3 - Create a New Demo
The last step is to create a demo in the [gradio/demo folder](https://github.com/gradio-app/gradio/tree/main/demo), which will use the newly added component. Again, the suggestion is to reference an existing demo. Write the code for the demo in a file called run.py, add the necessary requirements and an image showing the application interface. Finally add a gif showing its usage.
You can take a look at the [demo](https://github.com/gradio-app/gradio/tree/main/demo/color_picker) created for the ColorPicker, where an icon and a color selected through the new component is taken as input, and the same icon colored with the selected color is returned as output.
To test the application:
- run on a terminal `python path/demo/run.py` which starts the backend at the address [http://localhost:7860](http://localhost:7860);
- in another terminal, from the ui folder, run `pnpm dev` to start the frontend at [localhost:3000](localhost:3000) with hot reload functionalities.
## Conclusion
In this guide, we have shown how simple it is to add a new component to Gradio, seeing step by step how the ColorPicker component was added. For further details, you can refer to PR: [#1695](https://github.com/gradio-app/gradio/pull/1695).

View File

@ -0,0 +1,195 @@
<script type="module" src="https://gradio.s3-us-west-2.amazonaws.com/3.1.0/gradio.js"></script>
# Custom Machine Learning Interpretations with Blocks
Docs: blocks, interpretation
Tags: INTERPRETATION, SENTIMENT ANALYSIS
**Prerequisite**: This Guide requires you to know about Blocks and the interpretation feature of Interfaces.
Make sure to [read the Guide to Blocks first](/introduction_to_blocks) as well as the
interpretation section of the [Advanced Interface Features Guide](/advanced_interface_features#interpreting-your-predictions).
## Introduction
If you have experience working with the Interface class, then you know that interpreting the prediction of your machine learning model
is as easy as setting the `interpretation` parameter to either "default" or "shap".
You may be wondering if it is possible to add the same interpretation functionality to an app built with the Blocks API.
Not only is it possible, but the flexibility of Blocks lets you display the interpretation output in ways that are
impossible to do with Interfaces!
This guide will show how to:
1. Recreate the behavior of Interfaces's interpretation feature in a Blocks app.
2. Customize how interpretations are displayed in a Blocks app.
Let's get started!
## Setting up the Blocks app
Let's build a sentiment classification app with the Blocks API.
This app will take text as input and output the probability that this text expresses either negative or positive sentiment.
We'll have a single input `Textbox` and a single output `Label` component.
Below is the code for the app as well as the app itself.
```python
import gradio as gr
from transformers import pipeline
sentiment_classifier = pipeline("text-classification", return_all_scores=True)
def classifier(text):
pred = sentiment_classifier(text)
return {p["label"]: p["score"] for p in pred[0]}
with gr.Blocks() as demo:
with gr.Row():
with gr.Column():
input_text = gr.Textbox(label="Input Text")
with gr.Row():
classify = gr.Button("Classify Sentiment")
with gr.Column():
label = gr.Label(label="Predicted Sentiment")
classify.click(classifier, input_text, label)
demo.launch()
```
<gradio-app space="freddyaboulton/sentiment-classification"> </gradio-app>
## Adding interpretations to the app
Our goal is to present to our users how the words in the input contribute to the model's prediction.
This will help our users understand how the model works and also evaluate its effectiveness.
For example, we should expect our model to identify the words "happy" and "love" with positive sentiment - if not it's a sign we made a mistake in training it!
For each word in the input, we will compute a score of how much the model's prediction of positive sentiment is changed by that word.
Once we have those `(word, score)` pairs we can use gradio to visualize them to the user.
The [shap](https://shap.readthedocs.io/en/stable/index.html) library will help us compute the `(word, score)` pairs and
gradio will take care of displaying the output to the user.
The following code computes the `(word, score)` pairs:
```python
def interpretation_function(text):
explainer = shap.Explainer(sentiment_classifier)
shap_values = explainer([text])
# Dimensions are (batch size, text size, number of classes)
# Since we care about positive sentiment, use index 1
scores = list(zip(shap_values.data[0], shap_values.values[0, :, 1]))
# Scores contains (word, score) pairs
# Format expected by gr.components.Interpretation
return {"original": text, "interpretation": scores}
```
Now, all we have to do is add a button that runs this function when clicked.
To display the interpretation, we will use `gr.components.Interpretation`.
This will color each word in the input either red or blue.
Red if it contributes to positive sentiment and blue if it contributes to negative sentiment.
This is how `Interface` displays the interpretation output for text.
```python
with gr.Blocks() as demo:
with gr.Row():
with gr.Column():
input_text = gr.Textbox(label="Input Text")
with gr.Row():
classify = gr.Button("Classify Sentiment")
interpret = gr.Button("Interpret")
with gr.Column():
label = gr.Label(label="Predicted Sentiment")
with gr.Column():
interpretation = gr.components.Interpretation(input_text)
classify.click(classifier, input_text, label)
interpret.click(interpretation_function, input_text, interpretation)
demo.launch()
```
<gradio-app space="freddyaboulton/sentiment-classification-interpretation"> </gradio-app>
## Customizing how the interpretation is displayed
The `gr.components.Interpretation` component does a good job of showing how individual words contribute to the sentiment prediction,
but what if we also wanted to display the score themselves along with the words?
One way to do this would be to generate a bar plot where the words are on the horizontal axis and the bar height corresponds
to the shap score.
We can do this by modifying our `interpretation_function` to additionally return a matplotlib bar plot.
We will display it with the `gr.Plot` component in a separate tab.
This is how the interpretation function will look:
```python
def interpretation_function(text):
explainer = shap.Explainer(sentiment_classifier)
shap_values = explainer([text])
# Dimensions are (batch size, text size, number of classes)
# Since we care about positive sentiment, use index 1
scores = list(zip(shap_values.data[0], shap_values.values[0, :, 1]))
scores_desc = sorted(scores, key=lambda t: t[1])[::-1]
# Filter out empty string added by shap
scores_desc = [t for t in scores_desc if t[0] != ""]
fig_m = plt.figure()
# Select top 5 words that contribute to positive sentiment
plt.bar(x=[s[0] for s in scores_desc[:5]],
height=[s[1] for s in scores_desc[:5]])
plt.title("Top words contributing to positive sentiment")
plt.ylabel("Shap Value")
plt.xlabel("Word")
return {"original": text, "interpretation": scores}, fig_m
```
And this is how the app code will look:
```python
with gr.Blocks() as demo:
with gr.Row():
with gr.Column():
input_text = gr.Textbox(label="Input Text")
with gr.Row():
classify = gr.Button("Classify Sentiment")
interpret = gr.Button("Interpret")
with gr.Column():
label = gr.Label(label="Predicted Sentiment")
with gr.Column():
with gr.Tabs():
with gr.TabItem("Display interpretation with built-in component"):
interpretation = gr.components.Interpretation(input_text)
with gr.TabItem("Display interpretation with plot"):
interpretation_plot = gr.Plot()
classify.click(classifier, input_text, label)
interpret.click(interpretation_function, input_text, [interpretation, interpretation_plot])
demo.launch()
```
You can see the demo below!
<gradio-app space="freddyaboulton/sentiment-classification-interpretation-tabs"> </gradio-app>
## Beyond Sentiment Classification
Although we have focused on sentiment classification so far, you can add interpretations to almost any machine learning model.
The output must be an `gr.Image` or `gr.Label` but the input can be almost anything (`gr.Number`, `gr.Slider`, `gr.Radio`, `gr.Image`).
Here is a demo built with blocks of interpretations for an image classification model:
<gradio-app space="freddyaboulton/image-classification-interpretation-blocks"> </gradio-app>
## Closing remarks
We did a deep dive 🤿 into how interpretations work and how you can add them to your Blocks app.
We also showed how the Blocks API gives you the power to control how the interpretation is visualized in your app.
Adding interpretations is a helpful way to make your users understand and gain trust in your model.
Now you have all the tools you need to add them to all of your apps!

View File

@ -0,0 +1,86 @@
# Named-Entity Recognition
Related spaces: https://huggingface.co/spaces/rajistics/biobert_ner_demo, https://huggingface.co/spaces/abidlabs/ner, https://huggingface.co/spaces/rajistics/Financial_Analyst_AI
Tags: NER, TEXT, HIGHLIGHT
Docs: highlightedtext
## Introduction
Named-entity recognition (NER), also known as token classification or text tagging, is the task of taking a sentence and classifying every word (or "token") into different categories, such as names of people or names of locations, or different parts of speech.
For example, given the sentence:
> Does Chicago have any Pakistani restaurants?
A named-entity recognition algorithm may identify:
* "Chicago" as a **location**
* "Pakistani" as an **ethnicity**
and so on.
Using `gradio` (specifically the `HighlightedText` component), you can easily build a web demo of your NER model and share that with the rest of your team.
Here is an example of a demo that you'll be able to build:
$demo_ner_pipeline
This tutorial will show how to take a pretrained NER model and deploy it with a Gradio interface. We will show two different ways to use the `HighlightedText` component -- depending on your NER model, either of these two ways may be easier to learn!
### Prerequisites
Make sure you have the `gradio` Python package already [installed](/getting_started). You will also need a pretrained named-entity recognition model. You can use your own, or this in this tutorial, we will use one from the `transformers` library.
### Approach 1: List of Entity Dictionaries
Many named-entity recognition models output a list of dictionaries. Each dictionary consists of an *entity*, a "start" index, and an "end" index. This is, for example, how NER models in the `transformers` library operate:
```py
from transformers import pipeline
ner_pipeline = pipeline("ner")
ner_pipeline("Does Chicago have any Pakistani restaurants")
```
Output:
```bash
[{'entity': 'I-LOC',
'score': 0.9988978,
'index': 2,
'word': 'Chicago',
'start': 5,
'end': 12},
{'entity': 'I-MISC',
'score': 0.9958592,
'index': 5,
'word': 'Pakistani',
'start': 22,
'end': 31}]
```
If you have such a model, it is very easy to hook it up to Gradio's `HighlightedText` component. All you need to do is pass in this **list of entities**, along with the **original text** to the model, together as dictionary, with the keys being `"entities"` and `"text"` respectively.
Here is a complete example:
$code_ner_pipeline
$demo_ner_pipeline
### Approach 2: List of Tuples
An alternative way to pass data into the `HighlightedText` component is a list of tuples. The first element of each tuple should be the word or words that are being classified into a particular entity. The second element should be the entity label (or `None` if they should be unlabeled). The `HighlightedText` component automatically strings together the words and labels to display the entities.
In some cases, this can be easier than the first approach. Here is a demo showing this approach using Spacy's parts-of-speech tagger:
$code_text_analysis
$demo_text_analysis
--------------------------------------------
And you're done! That's all you need to know to build a web-based GUI for your NER model.
Fun tip: you can share your NER demo instantly with others simply by setting `share=True` in `launch()`.

View File

@ -0,0 +1,191 @@
# Using Flagging
Related spaces: https://huggingface.co/spaces/gradio/calculator-flagging-crowdsourced, https://huggingface.co/spaces/gradio/calculator-flagging-options, https://huggingface.co/spaces/gradio/calculator-flag-basic
Tags: FLAGGING, DATA
## Introduction
When you demo a machine learning model, you might want to collect data from users who try the model, particularly data points in which the model is not behaving as expected. Capturing these "hard" data points is valuable because it allows you to improve your machine learning model and make it more reliable and robust.
Gradio simplifies the collection of this data by including a **Flag** button with every `Interface`. This allows a user or tester to easily send data back to the machine where the demo is running. In this Guide, we discuss more about how to use the flagging feature, both with `gradio.Interface` as well as with `gradio.Blocks`.
## The **Flag** button in `gradio.Interface`
Flagging with Gradio's `Interface` is especially easy. By default, underneath the output components, there is a button marked **Flag**. When a user testing your model sees input with interesting output, they can click the flag button to send the input and output data back to the machine where the demo is running. The sample is saved to a CSV log file (by default). If the demo involves images, audio, video, or other types of files, these are saved separately in a parallel directory and the paths to these files are saved in the CSV file.
There are [four parameters](/docs/#interface-header) in `gradio.Interface` that control how flagging works. We will go over them in greater detail.
* `allow_flagging`: this parameter can be set to either `"manual"` (default), `"auto"`, or `"never"`.
* `manual`: users will see a button to flag, and samples are only flagged when the button is clicked.
* `auto`: users will not see a button to flag, but every sample will be flagged automatically.
* `never`: users will not see a button to flag, and no sample will be flagged.
* `flagging_options`: this parameter can be either `None` (default) or a list of strings.
* If `None`, then the user simply clicks on the **Flag** button and no additional options are shown.
* If a list of strings are provided, then the user sees several buttons, corresponding to each of the strings that are provided. For example, if the value of this parameter is `["Incorrect", "Ambiguous"]`, then buttons labeled **Flag as Incorrect** and **Flag as Ambiguous** appear. This only applies if `allow_flagging` is `"manual"`.
* The chosen option is then logged along with the input and output.
* `flagging_dir`: this parameter takes a string.
* It represents what to name the directory where flagged data is stored.
* `flagging_callback`: this parameter takes an instance of a subclass of the `FlaggingCallback` class
* Using this parameter allows you to write custom code that gets run when the flag button is clicked
* By default, this is set to an instance of `gr.CSVLogger`
* One example is setting it to an instance of `gr.HuggingFaceDatasetSaver` which can allow you to pipe any flagged data into a HuggingFace Dataset. (See more below.)
## What happens to flagged data?
Within the directory provided by the `flagging_dir` argument, a CSV file will log the flagged data.
Here's an example: The code below creates the calculator interface embedded below it:
```python
import gradio as gr
def calculator(num1, operation, num2):
if operation == "add":
return num1 + num2
elif operation == "subtract":
return num1 - num2
elif operation == "multiply":
return num1 * num2
elif operation == "divide":
return num1 / num2
iface = gr.Interface(
calculator,
["number", gr.Radio(["add", "subtract", "multiply", "divide"]), "number"],
"number",
allow_flagging="manual"
)
iface.launch()
```
<gradio-app space="gradio/calculator-flag-basic/"></gradio-app>
When you click the flag button above, the directory where the interface was launched will include a new flagged subfolder, with a csv file inside it. This csv file includes all the data that was flagged.
```directory
+-- flagged/
| +-- logs.csv
```
_flagged/logs.csv_
```csv
num1,operation,num2,Output,timestamp
5,add,7,12,2022-01-31 11:40:51.093412
6,subtract,1.5,4.5,2022-01-31 03:25:32.023542
```
If the interface involves file data, such as for Image and Audio components, folders will be created to store those flagged data as well. For example an `image` input to `image` output interface will create the following structure.
```directory
+-- flagged/
| +-- logs.csv
| +-- image/
| | +-- 0.png
| | +-- 1.png
| +-- Output/
| | +-- 0.png
| | +-- 1.png
```
_flagged/logs.csv_
```csv
im,Output timestamp
im/0.png,Output/0.png,2022-02-04 19:49:58.026963
im/1.png,Output/1.png,2022-02-02 10:40:51.093412
```
If you wish for the user to provide a reason for flagging, you can pass a list of strings to the `flagging_options` argument of Interface. Users will have to select one of these choices when flagging, and the option will be saved as an additional column to the CSV.
If we go back to the calculator example, the following code will create the interface embedded below it.
```python
iface = gr.Interface(
calculator,
["number", gr.Radio(["add", "subtract", "multiply", "divide"]), "number"],
"number",
allow_flagging="manual",
flagging_options=["wrong sign", "off by one", "other"]
)
iface.launch()
```
<gradio-app space="gradio/calculator-flagging-options/"></gradio-app>
When users click the flag button, the csv file will now include a column indicating the selected option.
_flagged/logs.csv_
```csv
num1,operation,num2,Output,flag,timestamp
5,add,7,-12,wrong sign,2022-02-04 11:40:51.093412
6,subtract,1.5,3.5,off by one,2022-02-04 11:42:32.062512
```
## The HuggingFaceDatasetSaver Callback
Sometimes, saving the data to a local CSV file doesn't make sense. For example, on Hugging Face
Spaces, developers typically don't have access to the underlying ephemeral machine hosting the Gradio
demo. That's why, by default, flagging is turned off in Hugging Face Space. However,
you may want to do something else with the flagged data.
We've made this super easy with the `flagging_callback` parameter.
For example, below we're going to pipe flagged data from our calculator example into a Hugging Face Dataset, e.g. so that we can build a "crowd-sourced" dataset:
```python
import os
HF_TOKEN = os.getenv('HF_TOKEN')
hf_writer = gr.HuggingFaceDatasetSaver(HF_TOKEN, "crowdsourced-calculator-demo")
iface = gr.Interface(
calculator,
["number", gr.Radio(["add", "subtract", "multiply", "divide"]), "number"],
"number",
description="Check out the crowd-sourced dataset at: [https://huggingface.co/datasets/aliabd/crowdsourced-calculator-demo](https://huggingface.co/datasets/aliabd/crowdsourced-calculator-demo)",
allow_flagging="manual",
flagging_options=["wrong sign", "off by one", "other"],
flagging_callback=hf_writer
)
iface.launch()
```
Notice that we define our own
instance of `gradio.HuggingFaceDatasetSaver` using our Hugging Face token and
the name of a dataset we'd like to save samples to. In addition, we also set `allow_flagging="manual"`
because on Hugging Face Spaces, `allow_flagging` is set to `"never"` by default. Here's our demo:
<gradio-app space="gradio/calculator-flagging-crowdsourced/"></gradio-app>
You can now see all the examples flagged above in this [public Hugging Face dataset](https://huggingface.co/datasets/aliabd/crowdsourced-calculator-demo).
![flagging callback hf](/assets/guides/flagging-callback-hf.png)
We created the `gradio.HuggingFaceDatasetSaver` class, but you can pass your own custom class as long as it inherits from `FLaggingCallback` defined in [this file](https://github.com/gradio-app/gradio/blob/master/gradio/flagging.py). If you create a cool callback, contribute it to the repo!
## Flagging with Blocks
What about if you are using `gradio.Blocks`? On one hand, you have even more flexibility
with Blocks -- you can write whatever Python code you want to run when a button is clicked,
and assign that using the built-in events in Blocks.
At the same time, you might want to use an existing `FlaggingCallback` to avoid writing extra code.
This requires two steps:
1. You have to run your callback's `.setup()` somewhere in the code prior to the
first time you flag data
2. When the flagging button is clicked, then you trigger the callback's `.flag()` method,
making sure to collect the arguments correctly and disabling the typical preprocessing.
Here is an example with an image sepia filter Blocks demo that lets you flag
data using the default `CSVLogger`:
$code_blocks_flag
$demo_blocks_flag
## Privacy
Important Note: please make sure your users understand when the data they submit is being saved, and what you plan on doing with it. This is especially important when you use `allow_flagging=auto` (when all of the data submitted through the demo is being flagged)
### That's all! Happy building :)

View File

@ -1508,6 +1508,41 @@ class TestLabel(unittest.TestCase):
class TestHighlightedText(unittest.TestCase):
def test_postprocess(self):
"""
postprocess
"""
component = gr.HighlightedText()
result = [
("", None),
("Wolfgang", "PER"),
(" lives in ", None),
("Berlin", "LOC"),
("", None),
]
result_ = component.postprocess(result)
self.assertEqual(result, result_)
text = "Wolfgang lives in Berlin"
entities = [
{"entity": "PER", "start": 0, "end": 8},
{"entity": "LOC", "start": 18, "end": 24},
]
result_ = component.postprocess({"text": text, "entities": entities})
self.assertEqual(result, result_)
text = "I live there"
entities = []
result_ = component.postprocess({"text": text, "entities": entities})
self.assertEqual([(text, None)], result_)
text = "Wolfgang"
entities = [
{"entity": "PER", "start": 0, "end": 8},
]
result_ = component.postprocess({"text": text, "entities": entities})
self.assertEqual([("", None), (text, "PER"), ("", None)], result_)
def test_component_functions(self):
"""
get_config, save_flagged, restore_flagged

View File

@ -2,5 +2,7 @@ declare namespace svelte.JSX {
interface DOMAttributes<T extends EventTarget> {
theme?: string;
"item-type"?: string;
webkitdirectory?: boolean | string;
mozdirectory?: boolean | string;
}
}

14
ui/globals.d.ts vendored Normal file
View File

@ -0,0 +1,14 @@
declare global {
interface Window {
__gradio_mode__: "app" | "website";
launchGradio: Function;
launchGradioFromSpaces: Function;
gradio_config: Config;
scoped_css_attach: (link: HTMLLinkElement) => void;
__gradio_loader__: Array<{
$set: (args: any) => any;
}>;
}
}
export {};

View File

@ -47,16 +47,18 @@
"prettier-plugin-svelte": "^2.7.0",
"sirv": "^2.0.2",
"sirv-cli": "^2.0.2",
"svelte": "^3.47.0",
"svelte-check": "^2.7.0",
"svelte": "^3.49.0",
"svelte-check": "^2.8.0",
"svelte-i18n": "^3.3.13",
"svelte-preprocess": "^4.10.6",
"tailwindcss": "^3.0.23",
"tailwindcss": "^3.1.6",
"tinyspy": "^0.3.0",
"typescript": "^4.7.4",
"vite": "^2.9.5",
"vitest": "^0.12.7"
},
"devDependencies": {
"@types/tailwindcss": "^3.1.0",
"@types/three": "^0.138.0"
}
}

View File

@ -1,6 +0,0 @@
import "./style.css";
document.querySelector("#app").innerHTML = `
<h1>Hello Vite!</h1>
<a href="https://vitejs.dev/guide/features.html" target="_blank">Documentation</a>
`;

View File

@ -1,18 +1,14 @@
<script lang="ts">
interface Component {
id: number;
props: {
name: string;
label?: string;
};
}
import type { ComponentMeta } from "./components/types";
interface Dependency {
inputs: Array<number>;
outputs: Array<number>;
api_name: string | null;
documentation?: Array<Array<string | null>>;
}
export let components: Array<Component>;
export let components: Array<ComponentMeta>;
export let dependencies: Array<Dependency>;
export let root: string;

View File

@ -1,75 +1,48 @@
<script lang="ts">
import type { SvelteComponentTyped } from "svelte";
import { onMount } from "svelte";
import { component_map } from "./components/directory";
import { loading_status, app_state } from "./stores";
import type { LoadingStatus } from "./components/StatusTracker/types";
import { tick } from "svelte";
import { _ } from "svelte-i18n";
import { component_map } from "./components/directory";
import { loading_status, app_state, LoadingStatusCollection } from "./stores";
import type {
ComponentMeta,
Dependency,
LayoutNode
} from "./components/types";
import type { fn as api_fn } from "./api";
import { setupi18n } from "./i18n";
import Render from "./Render.svelte";
import ApiDocs from "./ApiDocs.svelte";
import { tick } from "svelte";
import logo from "./images/logo.svg";
setupi18n();
interface Component {
id: number;
type: string;
has_modes?: boolean;
props: {
name?: keyof typeof component_map;
css?: Record<string, string>;
visible?: boolean;
elem_id?: string;
[key: string]: unknown;
value?: unknown;
};
instance?: SvelteComponentTyped;
component?: any;
children?: Array<Component>;
}
interface LayoutNode {
id: number;
children: Array<LayoutNode>;
}
interface Dependency {
trigger: string;
targets: Array<number>;
inputs: Array<number>;
outputs: Array<number>;
backend_fn: boolean;
js: string | null;
scroll_to_output: boolean;
show_progress: boolean;
frontend_fn?: Function;
status_tracker: number | null;
status?: string;
queue: boolean | null;
api_name: string | null;
documentation?: Array<Array<string>>;
}
export let root: string;
export let fn: (...args: any) => Promise<unknown>;
export let components: Array<Component>;
export let fn: ReturnType<typeof api_fn>;
export let components: Array<ComponentMeta>;
export let layout: LayoutNode;
export let dependencies: Array<Dependency>;
export let theme: string;
export let enable_queue: boolean;
export let title: string = "Gradio";
export let analytics_enabled: boolean = false;
export let target: HTMLElement;
export let css: string;
export let id: number = 0;
export let autoscroll: boolean = false;
$: app_state.update((s) => ({ ...s, autoscroll }));
let rootNode: Component = { id: layout.id, type: "column", props: {} };
let rootNode: ComponentMeta = {
id: layout.id,
type: "column",
props: {},
has_modes: false,
instance: {} as ComponentMeta["instance"],
component: {} as ComponentMeta["component"]
};
components.push(rootNode);
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
@ -114,16 +87,24 @@
return false;
}
const dynamic_ids = components.reduce((acc, { id, props }) => {
const is_input = is_dep(id, "inputs", dependencies);
const is_output = is_dep(id, "outputs", dependencies);
const dynamic_ids: Set<number> = components.reduce<Set<number>>(
(acc, { id, props }) => {
const is_input = is_dep(id, "inputs", dependencies);
const is_output = is_dep(id, "outputs", dependencies);
if (!is_input && !is_output && has_no_default_value(props.value))
acc.add(id); // default dynamic
if (is_input) acc.add(id);
if (
!is_input &&
!is_output &&
"value" in props &&
has_no_default_value(props?.value)
)
acc.add(id); // default dynamic
if (is_input) acc.add(id);
return acc;
}, new Set());
return acc;
},
new Set()
);
function has_no_default_value(value: any) {
return (
@ -137,27 +118,47 @@
let instance_map = components.reduce((acc, next) => {
acc[next.id] = next;
return acc;
}, {} as { [id: number]: Component });
}, {} as { [id: number]: ComponentMeta });
function load_component<T extends keyof typeof component_map>(
type LoadedComponent = {
Component: ComponentMeta["component"];
modes?: Array<string>;
};
function load_component<T extends ComponentMeta["type"]>(
name: T
): Promise<{ name: T; component: SvelteComponentTyped }> {
): Promise<{
name: T;
component: LoadedComponent;
}> {
return new Promise(async (res, rej) => {
try {
const c = await component_map[name]();
res({ name, component: c });
res({
name,
component: c as LoadedComponent
});
} catch (e) {
console.error("failed to load: " + name);
console.error(e);
rej(e);
}
});
}
const component_set = new Set<
Promise<{ name: ComponentMeta["type"]; component: LoadedComponent }>
>();
const _component_map = new Map<
ComponentMeta["type"],
Promise<{ name: ComponentMeta["type"]; component: LoadedComponent }>
>();
async function walk_layout(node: LayoutNode) {
let instance = instance_map[node.id];
const _component = (await _component_map.get(instance.type)).component;
const _component = (await _component_map.get(instance.type))!.component;
instance.component = _component.Component;
if (_component.modes.length > 1) {
if (_component.modes && _component.modes.length > 1) {
instance.has_modes = true;
}
@ -167,9 +168,7 @@
}
}
const component_set = new Set();
const _component_map = new Map();
components.forEach((c) => {
components.forEach(async (c) => {
const _c = load_component(c.type);
component_set.add(_c);
_component_map.set(c.type, _c);
@ -190,7 +189,7 @@
});
});
function set_prop(obj: Component, prop: string, val: any) {
function set_prop<T extends ComponentMeta>(obj: T, prop: string, val: any) {
if (!obj?.props) {
obj.props = {};
}
@ -215,7 +214,7 @@
{ targets, trigger, inputs, outputs, queue, backend_fn, frontend_fn },
i
) => {
const target_instances: [number, Component][] = targets.map((t) => [
const target_instances: [number, ComponentMeta][] = targets.map((t) => [
t,
instance_map[t]
]);
@ -240,11 +239,12 @@
queue: queue === null ? enable_queue : queue
})
.then((output) => {
output.data.forEach((value, i) => {
(output as { data: unknown[] }).data.forEach((value, i) => {
if (
typeof value === "object" &&
value !== null &&
value.__type__ == "update"
(value as { __type__: string; [key: string]: any })
.__type__ === "update"
) {
for (const [update_key, update_value] of Object.entries(
value
@ -269,57 +269,60 @@
handled_dependencies[i] = [-1];
}
target_instances.forEach(([id, { instance }]: [number, Component]) => {
if (handled_dependencies[i]?.includes(id) || !instance) return;
instance?.$on(trigger, () => {
if (loading_status.get_status_for_fn(i) === "pending") {
return;
}
target_instances.forEach(
([id, { instance }]: [number, ComponentMeta]) => {
if (handled_dependencies[i]?.includes(id) || !instance) return;
instance?.$on(trigger, () => {
if (loading_status.get_status_for_fn(i) === "pending") {
return;
}
// page events
fn({
action: "predict",
backend_fn,
frontend_fn,
payload: {
fn_index: i,
data: inputs.map((id) => instance_map[id].props.value)
},
output_data: outputs.map((id) => instance_map[id].props.value),
queue: queue === null ? enable_queue : queue
})
.then((output) => {
output.data.forEach((value, i) => {
if (
typeof value === "object" &&
value !== null &&
value.__type__ === "update"
) {
for (const [update_key, update_value] of Object.entries(
value
)) {
if (update_key === "__type__") {
continue;
} else {
instance_map[outputs[i]].props[update_key] =
update_value;
}
}
rootNode = rootNode;
} else {
instance_map[outputs[i]].props.value = value;
}
});
// page events
fn({
action: "predict",
backend_fn,
frontend_fn,
payload: {
fn_index: i,
data: inputs.map((id) => instance_map[id].props.value)
},
output_data: outputs.map((id) => instance_map[id].props.value),
queue: queue === null ? enable_queue : queue
})
.catch((error) => {
console.error(error);
loading_status.update(i, "error", 0, 0);
});
});
.then((output) => {
(output as { data: unknown[] }).data.forEach((value, i) => {
if (
typeof value === "object" &&
value !== null &&
(value as { __type__: string; [key: string]: any })
.__type__ === "update"
) {
for (const [update_key, update_value] of Object.entries(
value
)) {
if (update_key === "__type__") {
continue;
} else {
instance_map[outputs[i]].props[update_key] =
update_value;
}
}
rootNode = rootNode;
} else {
instance_map[outputs[i]].props.value = value;
}
});
})
.catch((error) => {
console.error(error);
loading_status.update(i, "error", 0, 0);
});
});
if (!handled_dependencies[i]) handled_dependencies[i] = [];
handled_dependencies[i].push(id);
});
if (!handled_dependencies[i]) handled_dependencies[i] = [];
handled_dependencies[i].push(id);
}
);
}
);
}
@ -336,7 +339,7 @@
loading_status.register(i, v.inputs, v.outputs);
});
function set_status(statuses: Record<number, LoadingStatus>) {
function set_status(statuses: LoadingStatusCollection) {
if (window.__gradio_mode__ === "website") {
return;
}
@ -413,13 +416,13 @@
<ApiDocs {components} {dependencies} {root} />
{:else if ready}
<Render
has_modes={rootNode.has_modes}
component={rootNode.component}
id={rootNode.id}
props={rootNode.props}
children={rootNode.children}
{dynamic_ids}
{instance_map}
{theme}
{root}
on:mount={handle_mount}
on:destroy={({ detail }) => handle_destroy(detail)}

View File

@ -1,37 +1,30 @@
<script lang="ts">
import { onMount, createEventDispatcher, setContext } from "svelte";
import { loading_status } from "./stores";
import type { ComponentMeta } from "./components/types";
export let root: string;
export let component;
export let instance_map;
export let component: !ComponentMeta["component"];
export let instance_map: Record<number, ComponentMeta>;
export let id: number;
export let props: {
style: Record<string, unknown>;
visible: boolean;
[key: string]: unknown;
};
interface LayoutNode {
id: number;
children: Array<LayoutNode>;
}
export let children: Array<LayoutNode>;
export let props: ComponentMeta["props"];
export let children: ComponentMeta["children"];
export let dynamic_ids: Set<number>;
export let has_modes: boolean | undefined;
export let status_tracker_values: Record<number, string>;
export let parent: string | null = null;
const dispatch = createEventDispatcher<{ mount: number; destroy: number }>();
if (has_modes) {
if (props.interactive === false) {
props.mode = "static";
} else if (props.interactive === true) {
props.mode = "dynamic";
if ((props as any).interactive === false) {
(props as any).mode = "static";
} else if ((props as any).interactive === true) {
(props as any).mode = "dynamic";
} else if (dynamic_ids.has(id)) {
props.mode = "dynamic";
(props as any).mode = "dynamic";
} else {
props.mode = "static";
(props as any).mode = "static";
}
}
@ -51,7 +44,9 @@
"dropdown"
];
function get_types(i) {
function get_types(i: number) {
if (!children) return;
const current =
children[i]?.id != undefined && instance_map[children[i].id];
const next =
@ -60,9 +55,9 @@
children[i - 1]?.id != undefined && instance_map[children[i - 1].id];
return {
current: current?.type && forms.includes(current.type),
next: next?.type && forms.includes(next.type),
prev: prev?.type && forms.includes(prev.type)
current: current && current?.type && forms.includes(current.type),
next: next && next?.type && forms.includes(next.type),
prev: prev && prev?.type && forms.includes(prev.type)
};
}
@ -72,8 +67,8 @@
});
}
function get_form_context(node, i) {
const { current, next, prev } = get_types(i);
function get_form_context(node: ComponentMeta, i: number) {
const { current, next, prev } = get_types(i) || {};
if (current && next && prev) {
node.props.form_position = "mid";
@ -103,13 +98,13 @@
this={component}
bind:this={instance_map[id].instance}
bind:value={instance_map[id].props.value}
elem_id={props.elem_id || id}
elem_id={("elem_id" in props && props.elem_id) || `${id}`}
on:prop_change={handle_prop_change}
{...props}
{root}
>
{#if children && children.length}
{#each children as { component, id: each_id, props, children, has_modes } (each_id)}
{#each children as { component, id: each_id, props, children: _children, has_modes } (each_id)}
<svelte:self
parent={instance_map[id].type}
{component}
@ -117,7 +112,7 @@
{props}
{root}
{instance_map}
{children}
children={_children}
{dynamic_ids}
{has_modes}
on:destroy

View File

@ -1,9 +1,13 @@
import { LoadingStatus, loading_status } from "./stores";
import { loading_status } from "./stores";
type StatusResponse =
| {
status: "COMPLETE";
data: { duration: number; average_duration: number; data: unknown };
data: {
duration: number;
average_duration: number;
data: Array<unknown>;
};
}
| {
status: "QUEUED";
@ -19,7 +23,7 @@ type StatusResponse =
};
interface Payload {
data: Record<string, unknown>;
data: Array<unknown>;
fn_index: number;
}
@ -43,14 +47,22 @@ async function post_data<
}
const output: Return = await response.json();
return output;
}
interface UpdateOutput {
__type__: string;
[key: string]: unknown;
}
export const fn = async (
session_hash: string,
api_endpoint: string,
{
type Output = {
data: Array<UpdateOutput | unknown>;
duration?: number;
average_duration?: number;
};
export const fn =
(session_hash: string, api_endpoint: string) =>
async ({
action,
payload,
queue,
@ -63,72 +75,71 @@ export const fn = async (
queue: boolean;
backend_fn: boolean;
frontend_fn: Function | undefined;
output_data: Array<any>;
}
) => {
const fn_index = payload.fn_index;
output_data?: Output["data"];
}): Promise<unknown> => {
const fn_index = payload.fn_index;
if (frontend_fn !== undefined) {
payload.data = await frontend_fn(payload.data.concat(output_data));
}
if (backend_fn == false) {
return payload;
}
if (frontend_fn !== undefined) {
payload.data = await frontend_fn(payload.data.concat(output_data));
}
if (backend_fn == false) {
return payload;
}
if (queue && ["predict", "interpret"].includes(action)) {
loading_status.update(fn_index as number, "pending", null, null);
if (queue && ["predict", "interpret"].includes(action)) {
loading_status.update(fn_index as number, "pending", null, null);
const { hash, queue_position } = await post_data<{
hash: string;
queue_position: number;
}>(api_endpoint + "queue/push/", { ...payload, action, session_hash });
const { hash, queue_position } = await post_data<{
hash: string;
queue_position: number;
}>(api_endpoint + "queue/push/", { ...payload, action, session_hash });
loading_status.update(fn_index, "pending", queue_position, null);
loading_status.update(fn_index, "pending", queue_position, null);
for (;;) {
await delay(1);
for (;;) {
await delay(1);
const { status, data } = await post_data<StatusResponse>(
api_endpoint + "queue/status/",
{
hash: hash
}
);
if (status === "QUEUED") {
loading_status.update(fn_index, "pending", data, null);
} else if (status === "PENDING") {
loading_status.update(fn_index, "pending", 0, null);
} else if (status === "FAILED") {
loading_status.update(fn_index, "error", null, null);
throw new Error(status);
} else {
loading_status.update(
fn_index,
"complete",
null,
data.average_duration
const { status, data } = await post_data<StatusResponse>(
api_endpoint + "queue/status/",
{
hash: hash
}
);
return data;
if (status === "QUEUED") {
loading_status.update(fn_index, "pending", data, null);
} else if (status === "PENDING") {
loading_status.update(fn_index, "pending", 0, null);
} else if (status === "FAILED") {
loading_status.update(fn_index, "error", null, null);
throw new Error(status);
} else {
loading_status.update(
fn_index,
"complete",
null,
data.average_duration
);
return data;
}
}
} else {
loading_status.update(fn_index as number, "pending", null, null);
const output = await post_data(api_endpoint + action + "/", {
...payload,
session_hash
});
loading_status.update(
fn_index,
"complete",
null,
output.average_duration as number
);
return output;
}
} else {
loading_status.update(fn_index as number, "pending", null, null);
const output = await post_data(api_endpoint + action + "/", {
...payload,
session_hash
});
loading_status.update(
fn_index,
"complete",
null,
output.average_duration as number
);
return await output;
}
};
};

View File

@ -24,7 +24,6 @@
export let value: null | FileData | string = null;
export let name: string;
export let source: "microphone" | "upload";
export let type: "normal" | "numpy" = "normal";
export let label: string;
export let root: string;
export let show_label: boolean;
@ -67,7 +66,6 @@
on:drag={({ detail }) => (dragging = detail)}
{name}
{source}
{type}
{pending}
{streaming}
on:edit

View File

@ -1,9 +1,7 @@
<script lang="ts">
import { Block } from "@gradio/atoms";
import { create_classes } from "@gradio/utils";
export let elem_id: string;
export let visible: boolean = true;
export let parent: string | null = null;
export let style: Record<string, unknown> = {};
if (typeof style.mobile_collapse !== "boolean") {

View File

@ -1,4 +1,5 @@
<script>
// @ts-nocheck
import Carousel from "./Carousel.svelte";
import CarouselItem from "../CarouselItem/CarouselItem.svelte";
import api_logo from "../../../public/static/img/api-logo.svg";

View File

@ -2,12 +2,24 @@ import { test, describe, assert, afterEach } from "vitest";
import { cleanup, render, get_text } from "@gradio/tootils";
import Chatbot from "./Chatbot.svelte";
import type { LoadingStatus } from "../StatusTracker/types";
const loading_status = {
eta: 0,
queue_position: 1,
status: "complete" as LoadingStatus["status"],
scroll_to_output: false,
visible: true,
fn_index: 0
};
describe("Chatbot", () => {
afterEach(() => cleanup());
test("renders user and bot messages", () => {
const { getAllByTestId } = render(Chatbot, {
loading_status,
label: "hello",
value: [["user message one", "bot message one"]]
});
@ -20,6 +32,8 @@ describe("Chatbot", () => {
test("renders additional message as they are passed", async () => {
const { component, getAllByTestId } = render(Chatbot, {
loading_status,
label: "hello",
value: [["user message one", "bot message one"]]
});

View File

@ -11,7 +11,6 @@
export let label: string = "Checkbox";
export let mode: "static" | "dynamic";
export let form_position: "first" | "last" | "mid" | "single" = "single";
export let show_label: boolean;
export let style: Styles = {};
export let loading_status: LoadingStatus;
</script>
@ -24,12 +23,5 @@
>
<StatusTracker {...loading_status} />
<Checkbox
{label}
{show_label}
{style}
bind:value
on:change
disabled={mode === "static"}
/>
<Checkbox {label} {style} bind:value on:change disabled={mode === "static"} />
</Block>

View File

@ -5,6 +5,7 @@
import { Block } from "@gradio/atoms";
import StatusTracker from "../StatusTracker/StatusTracker.svelte";
import type { LoadingStatus } from "../StatusTracker/types";
import type { Styles } from "@gradio/utils";
export let label: string = "ColorPicker";
export let elem_id: string = "";
@ -13,7 +14,7 @@
export let form_position: "first" | "last" | "mid" | "single" = "single";
export let show_label: boolean;
export let style: Record<string, unknown> = {};
export let style: Styles = {};
export let loading_status: LoadingStatus;

View File

@ -2,12 +2,25 @@ import { test, describe, assert, afterEach } from "vitest";
import { cleanup, render } from "@gradio/tootils";
import ColorPicker from "./ColorPicker.svelte";
import type { LoadingStatus } from "../StatusTracker/types";
const loading_status = {
eta: 0,
queue_position: 1,
status: "complete" as LoadingStatus["status"],
scroll_to_output: false,
visible: true,
fn_index: 0
};
describe("ColorPicker", () => {
afterEach(() => cleanup());
test("renders provided value", () => {
const { getByDisplayValue } = render(ColorPicker, {
loading_status,
show_label: true,
mode: "dynamic",
value: "#000000",
label: "ColorPicker"
});
@ -18,6 +31,9 @@ describe("ColorPicker", () => {
test("changing the color should update the value", async () => {
const { component, getByDisplayValue } = render(ColorPicker, {
loading_status,
show_label: true,
mode: "dynamic",
value: "#000000",
label: "ColorPicker"
});

View File

@ -1,11 +1,11 @@
<script lang="ts">
import { create_classes } from "@gradio/utils";
import type { Styles } from "@gradio/utils";
export let elem_id: string = "";
export let visible: boolean = true;
export let variant: "default" | "panel" = "default";
export let parent: string | null = null;
export let style: Record<string, unknown> = {};
export let style: Styles = {};
</script>
<div

View File

@ -26,7 +26,10 @@
export let loading_status: LoadingStatus;
async function handle_change({ detail }) {
async function handle_change(detail: {
data: Array<Array<string | number>>;
headers: Array<string>;
}) {
value = detail;
await tick();
dispatch("change", detail);
@ -46,7 +49,7 @@
{col_count}
values={value}
{headers}
on:change={handle_change}
on:change={({ detail }) => handle_change(detail)}
editable={mode === "dynamic"}
{style}
{wrap}

View File

@ -1,27 +1,25 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import type { SvelteComponentDev, ComponentType } from "svelte/internal";
import { component_map } from "./directory";
export let components: Array<string>;
export let components: Array<keyof typeof component_map>;
export let headers: Array<string>;
export let samples: Array<Array<any>>;
export let elem_id: string = "";
export let visible: boolean = true;
export let value: Number | null = null;
export let value: number | null = null;
export let root: string;
export let samples_per_page: number = 10;
export let theme: string;
const dispatch = createEventDispatcher<{ click: number }>();
let samples_dir: string = root + "file/";
let sample_id: number | null = null;
let page = 0;
let gallery = headers.length === 1;
let paginate = samples.length > samples_per_page;
let selected_samples: Array<Array<unknown>>;
let selected_samples: Array<Array<any>>;
let page_count: number;
let visible_pages: Array<number> = [];
$: {
@ -49,6 +47,15 @@
selected_samples = samples.slice();
}
}
$: component_meta = selected_samples.map((sample_row) =>
sample_row.map((sample_cell, j) => ({
value: sample_cell,
component: component_map[
components[j]
] as ComponentType<SvelteComponentDev>
}))
);
</script>
<div
@ -77,6 +84,8 @@
{#if gallery}
<div class="gr-samples-gallery">
{#each selected_samples as sample_row, i}
<!-- {@const x = component_map[]} -->
<button
class="group rounded-lg"
on:click={() => {
@ -84,12 +93,13 @@
dispatch("click", value);
}}
>
<svelte:component
this={component_map[components[0]]}
{theme}
value={sample_row[0]}
{samples_dir}
/>
{#if Object.keys(component_map).includes(components[0]) && component_map[components[0]]}
<svelte:component
this={component_meta[0][0].component}
value={sample_row[0]}
{samples_dir}
/>
{/if}
</button>
{/each}
</div>
@ -108,7 +118,7 @@
</tr>
</thead>
<tbody>
{#each selected_samples as sample_row, i}
{#each component_meta as sample_row, i}
<tr
class="group cursor-pointer odd:bg-gray-50 border-b dark:border-gray-800 divide-x dark:divide-gray-800 last:border-none hover:bg-orange-50 hover:divide-orange-100 dark:hover:bg-gray-700"
on:click={() => {
@ -116,15 +126,12 @@
dispatch("click", value);
}}
>
{#each sample_row as sample_cell, j}
<td class="p-2">
<svelte:component
this={component_map[components[j]]}
{theme}
value={sample_cell}
{samples_dir}
/>
</td>
{#each sample_row as { value, component }, j}
{#if components[j] !== undefined && component_map[components[j]] !== undefined}
<td class="p-2">
<svelte:component this={component} {value} {samples_dir} />
</td>
{/if}
{/each}
</tr>
{/each}

View File

@ -18,7 +18,7 @@
<!-- svelte-ignore a11y-media-has-caption -->
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
{#if playable(value)}
{#if playable()}
<video
muted
playsinline

View File

@ -7,6 +7,7 @@
import type { Styles } from "@gradio/utils";
import { get_styles } from "@gradio/utils";
import { Image } from "@gradio/icons";
import { _ } from "svelte-i18n";
export let loading_status: LoadingStatus;
export let show_label: boolean;
@ -160,7 +161,7 @@
/>
</button>
{:else}
<p>Empty</p>
<p>{$_("interface.empty")}</p>
{/each}
</div>
</div>

View File

@ -1,7 +1,7 @@
<script lang="ts">
import { create_classes } from "@gradio/utils";
export let parent: string | null = null;
export let style: Record<string, unknown> = {};
import type { Styles } from "@gradio/utils";
export let style: Styles = {};
if (typeof style.mobile_collapse !== "boolean") {
style.mobile_collapse = true;

View File

@ -27,6 +27,8 @@
$: value, dispatch("change");
let dragging: boolean;
$: value = !value ? null : value;
</script>
<Block

View File

@ -1,14 +1,18 @@
<script lang="ts">
import type { ComponentType } from "svelte";
import type { SvelteComponentDev } from "svelte/internal";
import { component_map } from "./directory";
export let component: string;
export let component: keyof typeof component_map;
export let component_props: Record<string, any>;
export let value: any;
$: _component = component_map[component] as ComponentType<SvelteComponentDev>;
</script>
{#if value}
<svelte:component
this={component_map[component]}
this={_component}
{...component_props}
original={value.original}
interpretation={value.interpretation}

View File

@ -1,10 +1,9 @@
<script lang="ts">
import { getSaliencyColor } from "../utils";
export let interpretation: Array<number>;
export let theme: string;
</script>
<div class="input-audio" {theme}>
<div class="input-audio">
<div class="interpret_range flex">
{#each interpretation as interpret_value}
<div
@ -14,6 +13,3 @@
{/each}
</div>
</div>
<style lang="postcss">
</style>

View File

@ -3,10 +3,9 @@
export let original: boolean;
export let interpretation: [number, number];
export let theme: string;
</script>
<div class="input-checkbox inline-block" {theme}>
<div class="input-checkbox inline-block">
<button
class="checkbox-item py-2 px-3 rounded cursor-pointer flex gap-1"
class:selected={original}
@ -47,7 +46,7 @@
.selected .check {
@apply opacity-100;
}
.input-checkbox[theme="default"] {
.input-checkbox {
.checkbox-item {
@apply bg-white dark:bg-gray-800 shadow transition hover:shadow-md;
}

View File

@ -4,11 +4,9 @@
export let original: Array<string>;
export let interpretation: Array<[number, number]>;
export let choices: Array<string>;
export let theme: string;
</script>
<div class="input-checkbox-group flex flex-wrap gap-2" {theme}>
<div class="input-checkbox-group flex flex-wrap gap-2">
{#each choices as choice, i}
<button
class="checkbox-item py-2 px-3 font-semibold rounded cursor-pointer flex items-center gap-1"
@ -52,7 +50,7 @@
.selected .check {
@apply opacity-100;
}
.input-checkbox-group[theme="default"] {
.input-checkbox-group {
.checkbox-item {
@apply bg-white dark:bg-gray-800 shadow transition hover:shadow-md;
}

View File

@ -1,13 +1,11 @@
<script lang="ts">
import { getSaliencyColor } from "../utils";
export let original: string;
export let interpretation: Array<number>;
export let theme: string;
export let choices: Array<string>;
</script>
<div class="input-dropdown" {theme}>
<div class="input-dropdown">
<ul class="dropdown-menu">
{#each choices as choice, i}
<li
@ -21,7 +19,7 @@
</div>
<style lang="postcss" global>
.input-dropdown[theme="default"] {
.input-dropdown {
.selector {
@apply bg-white dark:bg-gray-800 shadow transition hover:shadow-md;
}

View File

@ -77,6 +77,3 @@
/>
</div>
</div>
<style lang="postcss">
</style>

View File

@ -1,9 +1,7 @@
<script lang="ts">
import { getSaliencyColor } from "../utils";
export let original: string;
export let interpretation: Array<[number, number]>;
export let theme: string;
</script>
<div class="input-number">
@ -25,10 +23,10 @@
-webkit-appearance: none;
margin: 0;
}
input[type="number"] {
input {
-moz-appearance: textfield;
}
.input-number[theme="default"] {
.input-number {
@apply shadow transition hover:shadow-md dark:bg-gray-800;
}
</style>

View File

@ -3,11 +3,10 @@
export let original: string;
export let interpretation: Array<number>;
export let theme: string;
export let choices: Array<string>;
</script>
<div class="input-radio flex flex-wrap gap-2" {theme}>
<div class="input-radio flex flex-wrap gap-2">
{#each choices as choice, i}
<button
class="radio-item py-2 px-3 font-semibold rounded cursor-pointer flex items-center gap-2"
@ -23,7 +22,7 @@
</div>
<style lang="postcss">
.input-radio[theme="default"] {
.input-radio {
.radio-item {
@apply bg-white dark:bg-gray-800 shadow transition hover:shadow-md;
}

View File

@ -3,18 +3,16 @@
export let original: number;
export let interpretation: Array<number>;
export let theme: string;
export let minimum: number;
export let maximum: number;
export let step: number;
</script>
<div class="input-slider text-center" {theme}>
<div class="input-slider text-center">
<input
type="range"
class="range w-full appearance-none transition rounded h-4 bg-blue-400"
disabled
{original}
min={minimum}
max={maximum}
{step}
@ -41,7 +39,7 @@
@apply appearance-none w-5 h-5 rounded cursor-pointer;
}
.input-slider[theme="default"] {
.input-slider {
.range {
@apply bg-white dark:bg-gray-800 shadow h-3 transition hover:shadow-md;
}

View File

@ -2,10 +2,9 @@
import { getSaliencyColor } from "../utils";
export let interpretation: Array<[string, number]>;
export let theme: string;
</script>
<div class="input-text w-full rounded box-border p-2 break-word" {theme}>
<div class="input-text w-full rounded box-border p-2 break-word">
{#each interpretation as [text, saliency]}
<span
class="textspan p-1 bg-opacity-20 dark:bg-opacity-80"
@ -15,6 +14,3 @@
</span>
{/each}
</div>
<style lang="postcss" global>
</style>

View File

@ -7,6 +7,7 @@
import StatusTracker from "../StatusTracker/StatusTracker.svelte";
import type { LoadingStatus } from "../StatusTracker/types";
import type { Styles } from "@gradio/utils";
import { _ } from "svelte-i18n";
export let elem_id: string = "";
export let visible: boolean = true;
@ -37,7 +38,7 @@
<StatusTracker {...loading_status} />
{#if value && value !== '""'}
<JSON {value} />
<JSON {value} copy_to_clipboard={$_("interface.copy_to_clipboard")} />
{:else}
<div class="h-full min-h-[6rem] flex justify-center items-center">
<div class="h-7 dark:text-white opacity-50"><JSONIcon /></div>

View File

@ -1,12 +1,10 @@
<script lang="ts">
export let value: any;
export let theme: string;
export let style: string | null;
export let depth: number;
export let collapsed = depth > 4;
</script>
<div class="json-node inline" {theme}>
<div class="json-node inline">
{#if value instanceof Array}
{#if collapsed}
<button
@ -21,7 +19,7 @@
<div class="json-children pl-4">
{#each value as node, i}
<div class="json-item">
{i}: <svelte:self value={node} depth={depth + 1} key={i} {theme} />
{i}: <svelte:self value={node} depth={depth + 1} key={i} />
{#if i !== value.length - 1}
,
{/if}
@ -48,7 +46,6 @@
value={node[1]}
depth={depth + 1}
key={i}
{theme}
/><!--
-->{#if i !== Object.keys(value).length - 1}<!--
-->,

View File

@ -1,7 +1,8 @@
<script lang="ts">
import { create_classes } from "@gradio/utils";
export let parent: string | null = null;
export let style: Record<string, unknown> = {};
import type { Styles } from "@gradio/utils";
export let style: Styles = {};
export let elem_id: string;
export let visible: boolean = true;

View File

@ -44,7 +44,7 @@
</script>
<script lang="ts">
import { onDestroy, onMount } from "svelte";
import { onDestroy } from "svelte";
import { app_state } from "../../stores";
import Loader from "./Loader.svelte";
@ -172,32 +172,4 @@
.error {
@apply text-red-400 font-mono font-semibold text-lg;
}
@keyframes blink {
0% {
opacity: 100%;
}
50% {
opacity: 60%;
}
100% {
opacity: 100%;
}
}
.blink {
animation: blink 2s infinite;
}
@keyframes bounce {
0% {
left: 0%;
}
50% {
left: 75%;
}
100% {
left: 0%;
}
}
.bounce {
animation: bounce 2s infinite linear;
}
</style>

View File

@ -5,12 +5,11 @@
const dispatch = createEventDispatcher();
export let elem_id: string = "";
export let visible: boolean = true;
export let selected: number | string;
$: dispatch("prop_change", { selected });
</script>
<Tabs {elem_id} {visible} bind:selected on:change>
<Tabs {elem_id} bind:selected on:change>
<slot />
</Tabs>

View File

@ -5,6 +5,7 @@
import { Block } from "@gradio/atoms";
import StatusTracker from "../StatusTracker/StatusTracker.svelte";
import type { LoadingStatus } from "../StatusTracker/types";
import type { Styles } from "@gradio/utils";
export let label: string = "Textbox";
export let elem_id: string = "";
@ -16,7 +17,7 @@
export let show_label: boolean;
export let max_lines: number | false;
export let style: Record<string, unknown> = {};
export let style: Styles = {};
export let loading_status: LoadingStatus;

View File

@ -4,12 +4,25 @@ import { cleanup, fireEvent, render, get_text, wait } from "@gradio/tootils";
import event from "@testing-library/user-event";
import Textbox from "./Textbox.svelte";
import type { LoadingStatus } from "../StatusTracker/types";
const loading_status = {
eta: 0,
queue_position: 1,
status: "complete" as LoadingStatus["status"],
scroll_to_output: false,
visible: true,
fn_index: 0
};
describe("Textbox", () => {
afterEach(() => cleanup());
test("renders provided value", () => {
const { getByDisplayValue } = render(Textbox, {
show_label: true,
max_lines: 1,
loading_status,
lines: 1,
mode: "dynamic",
value: "hello world",
@ -22,6 +35,9 @@ describe("Textbox", () => {
test("changing the text should update the value", async () => {
const { component, getByLabelText, getByDisplayValue } = render(Textbox, {
show_label: true,
max_lines: 10,
loading_status,
lines: 1,
mode: "dynamic",
value: "hi ",

View File

@ -1,5 +1,6 @@
export const component_map: Record<string, any> = {
export const component_map = {
audio: () => import("./Audio"),
box: () => import("./Box"),
button: () => import("./Button"),
carousel: () => import("./Carousel"),
carouselitem: () => import("./CarouselItem"),
@ -12,27 +13,26 @@ export const component_map: Record<string, any> = {
dataset: () => import("./Dataset"),
dropdown: () => import("./Dropdown"),
file: () => import("./File"),
statustracker: () => import("./StatusTracker"),
highlightedtext: () => import("./HighlightedText"),
gallery: () => import("./Gallery"),
group: () => import("./Group"),
highlightedtext: () => import("./HighlightedText"),
html: () => import("./HTML"),
image: () => import("./Image"),
interpretation: () => import("./Interpretation"),
json: () => import("./Json"),
label: () => import("./Label"),
number: () => import("./Number"),
markdown: () => import("./Markdown"),
model3d: () => import("./Model3D"),
number: () => import("./Number"),
plot: () => import("./Plot"),
radio: () => import("./Radio"),
row: () => import("./Row"),
slider: () => import("./Slider"),
statustracker: () => import("./StatusTracker"),
tabs: () => import("./Tabs"),
tabitem: () => import("./TabItem"),
textbox: () => import("./Textbox"),
timeseries: () => import("./TimeSeries"),
video: () => import("./Video"),
model3d: () => import("./Model3D"),
plot: () => import("./Plot"),
variable: () => import("./Variable"),
group: () => import("./Group"),
box: () => import("./Box")
video: () => import("./Video")
};

View File

@ -0,0 +1,39 @@
import type { ComponentType } from "svelte";
import type { SvelteComponentDev } from "svelte/internal";
import type { component_map } from "./directory";
type ComponentMap = typeof component_map;
export interface ComponentMeta {
type: keyof ComponentMap;
id: number;
has_modes: boolean;
props: Record<string, unknown>;
instance: SvelteComponentDev;
component: ComponentType<SvelteComponentDev>;
children?: Array<ComponentMeta>;
value?: any;
}
export interface Dependency {
trigger: string;
targets: Array<number>;
inputs: Array<number>;
outputs: Array<number>;
backend_fn: boolean;
js: string | null;
scroll_to_output: boolean;
show_progress: boolean;
frontend_fn?: Function;
status_tracker: number | null;
status?: string;
queue: boolean | null;
api_name: string | null;
documentation?: Array<Array<string>>;
}
export interface LayoutNode {
id: number;
children: Array<LayoutNode>;
}

View File

@ -1,6 +1,6 @@
// import mime from "mime-types";
export const playable = (filename: string): boolean => {
export const playable = (): boolean => {
// let video_element = document.createElement("video");
// let mime_type = mime.lookup(filename);
// return video_element.canPlayType(mime_type) != "";

View File

@ -7,7 +7,11 @@
"drop_csv": "Drop CSV Here",
"click_to_upload": "Click to Upload",
"view_api": "view the api",
"built_with_Gradio": "built with gradio"
"built_with_Gradio": "built with gradio",
"copy_to_clipboard": "copy to clipboard",
"loading": "Loading",
"error": "ERROR",
"empty": "Empty"
},
"Submit": "Submit",
"Clear": "Clear",

View File

@ -3,67 +3,66 @@ import Login from "./Login.svelte";
import { Component as Loader } from "./components/StatusTracker";
import { fn } from "./api";
import type { ComponentMeta, Dependency, LayoutNode } from "./components/types";
import * as t from "@gradio/theme";
let id = -1;
window.__gradio_loader__ = [];
interface CustomWindow extends Window {
__gradio_mode__: "app" | "website";
launchGradio: Function;
launchGradioFromSpaces: Function;
gradio_config: Config;
scoped_css_attach: (link: HTMLLinkElement) => void;
__gradio_loader__: Array<{
$set: (args: any) => any;
}>;
}
declare let window: CustomWindow;
declare let BACKEND_URL: string;
declare let BUILD_MODE: string;
const ENTRY_CSS = "__ENTRY_CSS__";
const FONTS = "__FONTS_CSS__";
interface Component {
name: string;
[key: string]: unknown;
}
interface Config {
auth_required: boolean | undefined;
allow_flagging: string;
allow_interpretation: boolean;
allow_screenshot: boolean;
article: string;
cached_examples: boolean;
css: null | string;
description: string;
examples: Array<unknown>;
examples_per_page: number;
favicon_path: null | string;
flagging_options: null | unknown;
fn: Function;
function_count: number;
input_components: Array<Component>;
output_components: Array<Component>;
layout: string;
live: boolean;
mode: "blocks" | "interface" | undefined;
auth_message: string;
components: ComponentMeta[];
css: string | null;
dependencies: Dependency[];
dev_mode: boolean;
enable_queue: boolean;
fn: ReturnType<typeof fn>;
layout: LayoutNode;
mode: "blocks" | "interface";
root: string;
show_input: boolean;
show_output: boolean;
simpler_description: string;
target: HTMLElement;
theme: string;
thumbnail: null | string;
title: string;
version: string;
space?: string;
detail: string;
dark: boolean;
dev_mode: boolean;
// allow_flagging: string;
// allow_interpretation: boolean;
// article: string;
// cached_examples: boolean;
// description: string;
// examples: Array<unknown>;
// examples_per_page: number;
// favicon_path: null | string;
// flagging_options: null | unknown;
// function_count: number;
// input_components: Array<ComponentMeta>;
// output_components: Array<ComponentMeta>;
// layout: string;
// live: boolean;
// mode: "blocks" | "interface" | undefined;
// enable_queue: boolean;
// root: string;
// show_input: boolean;
// show_output: boolean;
// simpler_description: string;
// theme: string;
// thumbnail: null | string;
// title: string;
// version: string;
// space?: string;
// detail: string;
// dark: boolean;
// dev_mode: boolean;
}
let app_id: string | null = null;
@ -151,14 +150,12 @@ async function handle_config(
}
mount_custom_css(target, config.css);
if (config.dev_mode) {
reload_check(config.root);
}
if (config.root === undefined) {
config.root = BACKEND_URL;
}
if (config.dev_mode) {
reload_check(config.root);
}
config.target = target;
@ -170,19 +167,24 @@ function mount_app(
target: HTMLElement | ShadowRoot | false,
wrapper: HTMLDivElement,
id: number,
autoscroll?: Boolean
autoscroll?: boolean
) {
//@ts-ignore
if (config.detail === "Not authenticated" || config.auth_required) {
const app = new Login({
new Login({
target: wrapper,
//@ts-ignore
props: { ...config, id }
props: {
auth_message: config.auth_message,
root: config.root,
id
}
});
} else {
let session_hash = Math.random().toString(36).substring(2);
config.fn = fn.bind(null, session_hash, config.root + "api/");
config.fn = fn(session_hash, config.root + "api/");
const app = new Blocks({
new Blocks({
target: wrapper,
//@ts-ignore
props: { ...config, target: wrapper, id, autoscroll: autoscroll }

View File

@ -5,11 +5,14 @@ export interface LoadingStatus {
status: "pending" | "error" | "complete";
queue_position: number | null;
fn_index: number;
outputs: Array<number>;
scroll_to_output?: boolean;
visible?: boolean;
}
export type LoadingStatusCollection = Record<number, LoadingStatus>;
function create_loading_status_store() {
const store = writable<Record<string, Omit<LoadingStatus, "outputs">>>({});
const store = writable<LoadingStatusCollection>({});
const fn_inputs: Array<Array<number>> = [];
const fn_outputs: Array<Array<number>> = [];

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -11,6 +11,7 @@ import {
// this is dupe config, gonna try fix this
import tailwind from "tailwindcss";
// @ts-ignore
import nested from "tailwindcss/nesting/index.js";
const GRADIO_VERSION = process.env.GRADIO_VERSION || "asd_stub_asd";

View File

@ -1,5 +1,5 @@
<script lang="ts">
import { create_classes, get_styles } from "../../utils";
import { get_styles } from "../../utils";
import type { Styles } from "@gradio/utils";
import { getContext } from "svelte";

View File

@ -11,7 +11,7 @@
import { Upload, ModifyUpload } from "@gradio/upload";
import { BlockLabel } from "@gradio/atoms";
import { Music } from "@gradio/icons";
//@ts-ignore
// @ts-ignore
import Range from "svelte-range-slider-pips";
export let value: null | { name: string; data: string } = null;
@ -26,7 +26,7 @@
export let upload_text: string = "click to upload";
// TODO: make use of this
export let type: "normal" | "numpy" = "normal";
// export let type: "normal" | "numpy" = "normal";
let recording = false;
let recorder: MediaRecorder;
@ -51,7 +51,7 @@
return new Promise((fulfill, reject) => {
let reader = new FileReader();
reader.onerror = reject;
reader.onload = (e) => fulfill(reader.result as string);
reader.onload = () => fulfill(reader.result as string);
reader.readAsDataURL(blob);
});
}

View File

@ -8,7 +8,7 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { Block, BlockLabel } from "@gradio/atoms";
import { BlockLabel } from "@gradio/atoms";
import { Music } from "@gradio/icons";

View File

@ -1,5 +1,5 @@
<script lang="ts">
import { create_classes, get_styles } from "../../utils";
import { get_styles } from "../../utils";
import type { Styles } from "@gradio/utils";
export let style: Styles = {};

View File

@ -13,7 +13,7 @@ export function get_domains(
let _vs: number[];
if (Array.isArray(values)) {
_vs = values.reduce<number[]>((acc, { values }) => {
return [...acc, ...values.map(({ x, y }) => y)];
return [...acc, ...values.map(({ y }) => y)];
}, []);
} else {
_vs = values.values;

View File

@ -11,7 +11,7 @@
display_file_size
} from "./utils";
export let value: null | FileData | Array;
export let value: null | FileData | Array<FileData>;
export let drop_text: string = "Drop a file";
export let or_text: string = "or";

View File

@ -11,9 +11,7 @@ export const prettyBytes = (bytes: number): string => {
return bytes.toFixed(1) + " " + unit;
};
export const display_file_name = (
value: FileData | Array<FileData>
): string => {
export const display_file_name = (value: FileData): string => {
var str: string;
str = value.name;
if (str.length > 30) {
@ -21,12 +19,20 @@ export const display_file_name = (
} else return str;
};
export const download_files = (value: FileData | Array<FileData>): string => {
export const download_files = (value: FileData): string => {
return value.data;
};
export const display_file_size = (
value: FileData | Array<FileData>
): string => {
return prettyBytes(value.size || 0);
var total_size = 0;
if (Array.isArray(value)) {
for (var file of value) {
if (file.size !== undefined) total_size += file.size;
}
} else {
total_size = value.size || 0;
}
return prettyBytes(total_size);
};

View File

@ -1,5 +1,5 @@
<script lang="ts">
import { create_classes, get_styles } from "@gradio/utils";
import { get_styles } from "@gradio/utils";
import { createEventDispatcher } from "svelte";
import type { Styles } from "@gradio/utils";
@ -7,12 +7,16 @@
export let style: Styles = {};
export let disabled: boolean = false;
export let label: string;
export let show_label: boolean;
// export let show_label: boolean;
const dispatch = createEventDispatcher<{ change: boolean }>();
function handle_change(evt: Event) {
value = evt.target?.checked;
function handle_change(
evt: Event & {
currentTarget: EventTarget & HTMLInputElement;
}
) {
value = evt.currentTarget.checked;
dispatch("change", value);
}

View File

@ -33,7 +33,7 @@
<BlockTitle {show_label}>{label}</BlockTitle>
<div class="flex flex-wrap gap-2" data-testid="checkbox-group">
{#each choices as choice, i}
{#each choices as choice}
<label
class:!cursor-not-allowed={disabled}
class="gr-input-label flex items-center text-gray-700 text-sm space-x-2 border py-1.5 px-3 rounded-lg cursor-pointer bg-white shadow-sm checked:shadow-inner {item_container}"

View File

@ -2,9 +2,10 @@
import { createEventDispatcher } from "svelte";
import { get_styles } from "@gradio/utils";
import { BlockTitle } from "@gradio/atoms";
import type { Styles } from "@gradio/utils";
export let value: string = "#000000";
export let style: Record<string, unknown> = {};
export let style: Styles = {};
export let label: string;
export let disabled = false;
export let show_label: boolean = true;

View File

@ -25,7 +25,7 @@
bind:value
{disabled}
>
{#each choices as choice, i}
{#each choices as choice}
<option>{choice}</option>
{/each}
</select>

View File

@ -1,7 +1,7 @@
<script lang="ts">
import { createEventDispatcher, tick } from "svelte";
import { get_styles } from "@gradio/utils";
import { BlockTitle, Block } from "@gradio/atoms";
import { BlockTitle } from "@gradio/atoms";
import type { Styles } from "@gradio/utils";
export let value: number = 0;

View File

@ -2,9 +2,10 @@
import { createEventDispatcher, tick } from "svelte";
import { get_styles } from "@gradio/utils";
import { BlockTitle } from "@gradio/atoms";
import type { Styles } from "@gradio/utils";
export let value: string = "";
export let style: Record<string, unknown> = {};
export let style: Styles = {};
export let lines: number = 1;
export let placeholder: string = "Type here...";
export let label: string;

View File

@ -1,6 +1,6 @@
<script lang="ts">
const browser = typeof document !== "undefined";
import { colors, ordered_colors } from "@gradio/theme";
import { colors } from "@gradio/theme";
import { get_next_color } from "@gradio/utils";
export let value: Array<[string, string | number]> = [];

View File

@ -1,4 +1,5 @@
<script lang="ts">
// @ts-nocheck
import { createEventDispatcher, tick } from "svelte";
import { BlockLabel } from "@gradio/atoms";
import { Image, Sketch as SketchIcon } from "@gradio/icons";
@ -34,7 +35,7 @@
(source === "upload" || source === "webcam") &&
tool === "sketch"
) {
value = { image: value, mask: null };
value = { image: value as string, mask: null };
}
function handle_upload({ detail }: CustomEvent<string>) {
@ -68,7 +69,7 @@
drag: boolean;
}>();
$: dispatch("change", value);
$: dispatch("change", value as string);
let dragging = false;

View File

@ -1,4 +1,6 @@
<script>
// @ts-nocheck
import { onMount, onDestroy, createEventDispatcher } from "svelte";
import { fade } from "svelte/transition";
import { LazyBrush } from "lazy-brush/src";
@ -11,8 +13,8 @@
export let brush_color = "#0b0f19";
export let brush_radius = 50;
export let width;
export let height;
export let width = undefined;
export let height = undefined;
let mounted;
@ -482,6 +484,7 @@
on:mousemove={name === "interface" ? handle_draw_move : undefined}
on:mouseup={name === "interface" ? handle_draw_end : undefined}
on:mouseout={name === "interface" ? handle_draw_end : undefined}
on:blur={name === "interface" ? handle_draw_end : undefined}
on:touchstart={name === "interface" ? handle_draw_start : undefined}
on:touchmove={name === "interface" ? handle_draw_move : undefined}
on:touchend={name === "interface" ? handle_draw_end : undefined}

View File

@ -1,6 +1,6 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import { Block, BlockLabel } from "@gradio/atoms";
import { BlockLabel } from "@gradio/atoms";
import { Image } from "@gradio/icons";

View File

@ -4,6 +4,7 @@
import JSONNode from "./JSONNode.svelte";
export let value: any = {};
export let copy_to_clipboard: string = "copy to clipboard";
let copied = false;
let timer: NodeJS.Timeout;
@ -32,7 +33,7 @@
on:click={handle_copy}
class="transition-color overflow-hidden font-sans absolute right-0 top-0 rounded-bl-lg shadow-sm text-xs text-gray-500 flex items-center bg-white z-20 border-l border-b border-gray-100 dark:text-slate-200"
>
<span class="py-1 px-2">copy to clipboard</span>
<span class="py-1 px-2">{copy_to_clipboard}</span>
{#if copied}
<span
in:fade={{ duration: 100 }}

View File

@ -4,11 +4,10 @@
confidences?: Array<{ label: string; confidence: number }>;
};
export let theme: string = "default";
export let show_label: boolean;
</script>
<div class="output-label" space-y-4 {theme}>
<div class="output-label">
<div
class:sr-only={!show_label}
class="output-class font-bold text-2xl py-6 px-4 flex-grow flex items-center justify-center dark:text-slate-200"

View File

@ -16,31 +16,31 @@
let canvas: HTMLCanvasElement;
let scene: BABYLON.Scene;
let engine: BABYLON.Engine;
let engine: BABYLON.Engine | null;
onMount(() => {
engine = new BABYLON.Engine(canvas, true);
window.addEventListener("resize", () => {
engine.resize();
engine?.resize();
});
});
afterUpdate(() => {
if (scene) {
scene.dispose();
engine.stopRenderLoop();
engine.dispose();
engine?.stopRenderLoop();
engine?.dispose();
engine = null;
engine = new BABYLON.Engine(canvas, true);
window.addEventListener("resize", () => {
engine.resize();
engine?.resize();
});
}
addNewModel();
});
function addNewModel() {
scene = new BABYLON.Scene(engine);
scene = new BABYLON.Scene(engine!);
scene.createDefaultCameraOrLight();
scene.clearColor = clearColor
? (scene.clearColor = new BABYLON.Color4(
@ -51,10 +51,12 @@
))
: new BABYLON.Color4(0.2, 0.2, 0.2, 1);
engine.runRenderLoop(() => {
engine?.runRenderLoop(() => {
scene.render();
});
if (!value) return;
let base64_model_content = value["data"];
let raw_content = BABYLON.Tools.DecodeBase64(base64_model_content);
let blob = new Blob([raw_content]);

View File

@ -32,7 +32,7 @@
addNewModel();
}
async function handle_clear({ detail }: CustomEvent<null>) {
async function handle_clear() {
if (scene) {
scene.dispose();
}

View File

@ -1,53 +1,75 @@
<svelte:head>
<!-- Loading Bokeh from CDN -->
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-2.4.2.min.js" on:load={handleBokehLoaded} ></script>
{#if bokehLoaded}
<script src="https://cdn.pydata.org/bokeh/release/bokeh-widgets-2.4.2.min.js" on:load={() => initializeBokeh(1)} ></script>
<script src="https://cdn.pydata.org/bokeh/release/bokeh-tables-2.4.2.min.js" on:load={() => initializeBokeh(2)}></script>
<script src="https://cdn.pydata.org/bokeh/release/bokeh-gl-2.4.2.min.js" on:load={() => initializeBokeh(3)}></script>
<script src="https://cdn.pydata.org/bokeh/release/bokeh-api-2.4.2.min.js" on:load={() => initializeBokeh(4)}></script>
<script src="https://cdn.pydata.org/bokeh/release/bokeh-api-2.4.2.min.js" on:load={() => initializeBokeh(5)} ></script>
{/if}
</svelte:head>
<script lang="ts">
<script>
//@ts-nocheck
import Plotly from "plotly.js-dist-min";
import { Plot as PlotIcon } from "@gradio/icons";
import { afterUpdate } from "svelte";
export let value: null | string;
export let theme: string;
import { afterUpdate, onDestroy } from "svelte";
export let value;
// Plotly
let plotDiv;
const main_src = "https://cdn.bokeh.org/bokeh/release/bokeh-2.4.2.min.js";
const plugins_src = [
"https://cdn.pydata.org/bokeh/release/bokeh-widgets-2.4.2.min.js",
"https://cdn.pydata.org/bokeh/release/bokeh-tables-2.4.2.min.js",
"https://cdn.pydata.org/bokeh/release/bokeh-gl-2.4.2.min.js",
"https://cdn.pydata.org/bokeh/release/bokeh-api-2.4.2.min.js"
];
function load_plugins() {
return plugins_src.map((src, i) => {
const script = document.createElement("script");
script.onload = () => initializeBokeh(i + 1);
script.src = src;
document.head.appendChild(script);
return script;
});
}
function load_bokeh() {
const script = document.createElement("script");
script.onload = handleBokehLoaded;
script.src = main_src;
document.head.appendChild(script);
return script;
}
const main_script = load_bokeh();
let plugin_scripts = [];
// Bokeh
let bokehLoaded = false
const resolves = []
const bokehPromises = Array(6).fill(0).map((_, i) => createPromise(i))
const resolves = [];
const bokehPromises = Array(5)
.fill(0)
.map((_, i) => createPromise(i));
const initializeBokeh = (index) => {
if (value && value["type"] == "bokeh") {
resolves[index]()
}
}
resolves[index]();
}
};
function createPromise(index) {
return new Promise((resolve, reject) => {
resolves[index] = resolve
})
resolves[index] = resolve;
});
}
function handleBokehLoaded() {
initializeBokeh(0)
bokehLoaded = true
initializeBokeh(0);
plugin_scripts = load_plugins();
}
Promise.all(bokehPromises).then(() => {
let plotObj = JSON.parse(value["plot"]);
window.Bokeh.embed.embed_item(plotObj, "bokehDiv");
})
window.Bokeh.embed.embed_item(plotObj, "bokehDiv");
});
// Plotly
afterUpdate(() => {
@ -57,25 +79,27 @@
} else if (value && value["type"] == "bokeh") {
document.getElementById("bokehDiv").innerHTML = "";
let plotObj = JSON.parse(value["plot"]);
window.Bokeh.embed.embed_item(plotObj, "bokehDiv");
window.Bokeh.embed.embed_item(plotObj, "bokehDiv");
}
});
onDestroy(() => {
document.removeChild(main_script);
plugin_scripts.forEach((child) => document.removeChild(child));
});
</script>
{#if value && value["type"] == "plotly"}
<div bind:this={plotDiv}/>
<div bind:this={plotDiv} />
{:else if value && value["type"] == "bokeh"}
<div id="bokehDiv"/>
<div id="bokehDiv" />
{:else if value && value["type"] == "matplotlib"}
<div
class="output-image w-full flex justify-center items-center relative"
{theme}
>
<div class="output-image w-full flex justify-center items-center relative">
<!-- svelte-ignore a11y-missing-attribute -->
<img class="w-full max-h-[30rem] object-contain" src={value["plot"]} />
</div>
{:else}
<div class="h-full min-h-[15rem] flex justify-center items-center">
<div class="h-5 dark:text-white opacity-50"><PlotIcon/></div>
<div class="h-5 dark:text-white opacity-50"><PlotIcon /></div>
</div>
{/if}

View File

@ -1 +1,2 @@
// @ts-ignore
export { default as Plot } from "./Plot.svelte";

Some files were not shown because too many files have changed in this diff Show More