mirror of
https://github.com/gradio-app/gradio.git
synced 2024-11-21 01:01:05 +08:00
changes
This commit is contained in:
commit
0e4931fbe2
1
.github/workflows/ui.yml
vendored
1
.github/workflows/ui.yml
vendored
@ -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:
|
||||
|
1
demo/blocks_flag/requirements.txt
Normal file
1
demo/blocks_flag/requirements.txt
Normal file
@ -0,0 +1 @@
|
||||
numpy
|
33
demo/blocks_flag/run.py
Normal file
33
demo/blocks_flag/run.py
Normal 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()
|
1
demo/blocks_interpretation/requirements.txt
Normal file
1
demo/blocks_interpretation/requirements.txt
Normal file
@ -0,0 +1 @@
|
||||
shap
|
57
demo/blocks_interpretation/run.py
Normal file
57
demo/blocks_interpretation/run.py
Normal 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()
|
@ -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
|
||||
]
|
||||
]
|
||||
},
|
||||
|
63
demo/color_generator/run.py
Normal file
63
demo/color_generator/run.py
Normal 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 |
BIN
demo/color_picker/rabbit.png
Normal file
BIN
demo/color_picker/rabbit.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
@ -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"],
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -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(
|
||||
|
2
demo/ner_pipeline/requirements.txt
Normal file
2
demo/ner_pipeline/requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
torch
|
||||
transformers
|
18
demo/ner_pipeline/run.py
Normal file
18
demo/ner_pipeline/run.py
Normal 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()
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
@ -26,6 +26,7 @@ from gradio.components import (
|
||||
Highlightedtext,
|
||||
HighlightedText,
|
||||
Image,
|
||||
Interpretation,
|
||||
Json,
|
||||
Label,
|
||||
Markdown,
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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))
|
||||
|
@ -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.
|
||||
|
@ -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.",
|
||||
|
@ -1 +1 @@
|
||||
3.1.0
|
||||
3.1.1
|
||||
|
388
guides/4)other_guides/creating_a_new_component.md
Normal file
388
guides/4)other_guides/creating_a_new_component.md
Normal 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).
|
195
guides/4)other_guides/custom_interpretations_with_blocks.md
Normal file
195
guides/4)other_guides/custom_interpretations_with_blocks.md
Normal 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!
|
86
guides/4)other_guides/named_entity_recognition.md
Normal file
86
guides/4)other_guides/named_entity_recognition.md
Normal 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()`.
|
||||
|
||||
|
191
guides/4)other_guides/using_flagging.md
Normal file
191
guides/4)other_guides/using_flagging.md
Normal 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 :)
|
@ -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
|
||||
|
2
ui/global.d.ts → ui/JSX.d.ts
vendored
2
ui/global.d.ts → ui/JSX.d.ts
vendored
@ -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
14
ui/globals.d.ts
vendored
Normal 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 {};
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
`;
|
@ -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;
|
||||
|
||||
|
@ -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)}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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") {
|
||||
|
@ -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";
|
||||
|
@ -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"]]
|
||||
});
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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"
|
||||
});
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -27,6 +27,8 @@
|
||||
|
||||
$: value, dispatch("change");
|
||||
let dragging: boolean;
|
||||
|
||||
$: value = !value ? null : value;
|
||||
</script>
|
||||
|
||||
<Block
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -77,6 +77,3 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
</style>
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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}<!--
|
||||
-->,
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 ",
|
||||
|
@ -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")
|
||||
};
|
||||
|
39
ui/packages/app/src/components/types.ts
Normal file
39
ui/packages/app/src/components/types.ts
Normal 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>;
|
||||
}
|
@ -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) != "";
|
||||
|
@ -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",
|
||||
|
@ -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 }
|
||||
|
@ -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.
Binary file not shown.
File diff suppressed because one or more lines are too long
77
ui/packages/app/test/outbreak-forecast.spec.ts
Normal file
77
ui/packages/app/test/outbreak-forecast.spec.ts
Normal file
File diff suppressed because one or more lines are too long
@ -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";
|
||||
|
@ -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";
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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 = {};
|
||||
|
@ -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;
|
||||
|
@ -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";
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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}"
|
||||
|
@ -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;
|
||||
|
@ -25,7 +25,7 @@
|
||||
bind:value
|
||||
{disabled}
|
||||
>
|
||||
{#each choices as choice, i}
|
||||
{#each choices as choice}
|
||||
<option>{choice}</option>
|
||||
{/each}
|
||||
</select>
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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]> = [];
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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}
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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 }}
|
||||
|
@ -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"
|
||||
|
@ -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]);
|
||||
|
@ -32,7 +32,7 @@
|
||||
addNewModel();
|
||||
}
|
||||
|
||||
async function handle_clear({ detail }: CustomEvent<null>) {
|
||||
async function handle_clear() {
|
||||
if (scene) {
|
||||
scene.dispose();
|
||||
}
|
||||
|
@ -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}
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user