merged blocks-dev
@ -14,9 +14,7 @@ def diff_texts(text1, text2):
|
||||
demo = gr.Interface(
|
||||
diff_texts,
|
||||
[
|
||||
gr.Textbox(
|
||||
lines=3, default="The quick brown fox jumped over the lazy dogs."
|
||||
),
|
||||
gr.Textbox(lines=3, default="The quick brown fox jumped over the lazy dogs."),
|
||||
gr.Textbox(lines=3, default="The fast brown fox jumps over lazy dogs."),
|
||||
],
|
||||
gr.HighlightedText(),
|
||||
|
@ -18,9 +18,7 @@ def recognize_digit(image):
|
||||
return {str(i): prediction[i] for i in range(10)}
|
||||
|
||||
|
||||
im = gradio.Image(
|
||||
shape=(28, 28), image_mode="L", invert_colors=False, source="canvas"
|
||||
)
|
||||
im = gradio.Image(shape=(28, 28), image_mode="L", invert_colors=False, source="canvas")
|
||||
|
||||
demo = gr.Interface(
|
||||
recognize_digit,
|
||||
|
@ -26,9 +26,7 @@ demo = gr.Interface(
|
||||
plot_forecast,
|
||||
[
|
||||
gr.Radio([2025, 2030, 2035, 2040], label="Project to:"),
|
||||
gr.CheckboxGroup(
|
||||
["Google", "Microsoft", "Gradio"], label="Company Selection"
|
||||
),
|
||||
gr.CheckboxGroup(["Google", "Microsoft", "Gradio"], label="Company Selection"),
|
||||
gr.Slider(minimum=1, maximum=100, label="Noise Level"),
|
||||
gr.Checkbox(label="Show Legend"),
|
||||
gr.Dropdown(["cross", "line", "circle"], label="Style"),
|
||||
|
@ -35,7 +35,7 @@ demo = gr.Interface(
|
||||
fn=gender_of_sentence,
|
||||
inputs=gr.Textbox(default="She went to his house to get her keys."),
|
||||
outputs="label",
|
||||
interpretation=interpret_gender
|
||||
interpretation=interpret_gender,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -20,8 +20,9 @@ def classify_image(inp):
|
||||
image = gr.Image(shape=(224, 224))
|
||||
label = gr.Label(num_top_classes=3)
|
||||
|
||||
demo = gr.Interface(fn=classify_image, inputs=image, outputs=label,
|
||||
interpretation="default")
|
||||
demo = gr.Interface(
|
||||
fn=classify_image, inputs=image, outputs=label, interpretation="default"
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
demo.launch()
|
||||
demo.launch()
|
||||
|
@ -4,6 +4,7 @@ import gradio as gr
|
||||
def image_mod(text):
|
||||
return text[::-1]
|
||||
|
||||
|
||||
demo = gr.Blocks()
|
||||
|
||||
with demo:
|
||||
|
@ -37,7 +37,7 @@ def main_note(audio):
|
||||
if pitch not in volume_per_pitch:
|
||||
volume_per_pitch[pitch] = 0
|
||||
volume_per_pitch[pitch] += 1.0 * volume / total_volume
|
||||
volume_per_pitch = {k:float(v) for k,v in volume_per_pitch.items()}
|
||||
volume_per_pitch = {k: float(v) for k, v in volume_per_pitch.items()}
|
||||
return volume_per_pitch
|
||||
|
||||
|
||||
|
@ -31,9 +31,7 @@ demo = gr.Interface(
|
||||
outbreak,
|
||||
[
|
||||
gr.Slider(minimum=1, maximum=4, default_value=3.2, label="R"),
|
||||
gr.Dropdown(
|
||||
["January", "February", "March", "April", "May"], label="Month"
|
||||
),
|
||||
gr.Dropdown(["January", "February", "March", "April", "May"], label="Month"),
|
||||
gr.CheckboxGroup(["USA", "Canada", "Mexico", "UK"], label="Countries"),
|
||||
gr.Checkbox(label="Social Distancing?"),
|
||||
],
|
||||
|
@ -5,25 +5,28 @@ import gradio as gr
|
||||
asr = pipeline("automatic-speech-recognition", "facebook/wav2vec2-base-960h")
|
||||
classifier = pipeline("text-classification")
|
||||
|
||||
|
||||
def speech_to_text(speech):
|
||||
text = asr(speech)["text"]
|
||||
return text
|
||||
|
||||
|
||||
def text_to_sentiment(text):
|
||||
return classifier(text)[0]["label"]
|
||||
|
||||
|
||||
demo = gr.Blocks()
|
||||
|
||||
with demo:
|
||||
m = gr.Audio(type="filepath")
|
||||
t = gr.Textbox()
|
||||
l = gr.Label()
|
||||
|
||||
|
||||
b1 = gr.Button("Recognize Speech")
|
||||
b2 = gr.Button("Classify Sentiment")
|
||||
|
||||
|
||||
b1.click(speech_to_text, inputs=m, outputs=t)
|
||||
b2.click(text_to_sentiment, inputs=t, outputs=l)
|
||||
|
||||
if __name__ == "__main__":
|
||||
demo.launch()
|
||||
demo.launch()
|
||||
|
@ -69,6 +69,8 @@ predictions = clf.predict(X_test)
|
||||
|
||||
|
||||
def predict_survival(passenger_class, is_male, age, company, fare, embark_point):
|
||||
if passenger_class is None or embark_point is None:
|
||||
return None
|
||||
df = pd.DataFrame.from_dict(
|
||||
{
|
||||
"Pclass": [passenger_class + 1],
|
||||
@ -93,9 +95,7 @@ demo = gr.Interface(
|
||||
gr.Dropdown(["first", "second", "third"], type="index"),
|
||||
"checkbox",
|
||||
gr.Slider(minimum=0, maximum=80),
|
||||
gr.CheckboxGroup(
|
||||
["Sibling", "Child"], label="Travelling with (select all)"
|
||||
),
|
||||
gr.CheckboxGroup(["Sibling", "Child"], label="Travelling with (select all)"),
|
||||
gr.Number(),
|
||||
gr.Radio(["S", "C", "Q"], type="index"),
|
||||
],
|
||||
@ -106,6 +106,7 @@ demo = gr.Interface(
|
||||
["third", True, 30, ["Child"], 20, "S"],
|
||||
],
|
||||
interpretation="default",
|
||||
live=True,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -5,11 +5,10 @@ import random
|
||||
xray_model = lambda diseases, img: {disease: random.random() for disease in diseases}
|
||||
ct_model = lambda diseases, img: {disease: 0.1 for disease in diseases}
|
||||
|
||||
demo = gr.Blocks()
|
||||
|
||||
with demo:
|
||||
with gr.Blocks() as demo:
|
||||
gr.Markdown(
|
||||
"""
|
||||
"""
|
||||
# Detect Disease From Scan
|
||||
With this model you can lorem ipsum
|
||||
- ipsum 1
|
||||
|
@ -12,7 +12,9 @@ if TYPE_CHECKING: # Only import for type checking (is False at runtime).
|
||||
|
||||
|
||||
class Block:
|
||||
def __init__(self):
|
||||
def __init__(self, without_rendering=False):
|
||||
if without_rendering:
|
||||
return
|
||||
self.render()
|
||||
|
||||
def render(self):
|
||||
@ -33,6 +35,8 @@ class Block:
|
||||
fn: Callable,
|
||||
inputs: List[Component],
|
||||
outputs: List[Component],
|
||||
preprocess=True,
|
||||
queue=False,
|
||||
) -> None:
|
||||
"""
|
||||
Adds an event to the component's dependencies.
|
||||
@ -49,54 +53,60 @@ class Block:
|
||||
if not isinstance(outputs, list):
|
||||
outputs = [outputs]
|
||||
|
||||
Context.root_block.fns.append(fn)
|
||||
Context.root_block.fns.append((fn, preprocess))
|
||||
Context.root_block.dependencies.append(
|
||||
{
|
||||
"targets": [self._id],
|
||||
"trigger": event_name,
|
||||
"inputs": [block._id for block in inputs],
|
||||
"outputs": [block._id for block in outputs],
|
||||
"queue": queue,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class BlockContext(Block):
|
||||
def __init__(self, css: Optional[Dict[str, str]] = None):
|
||||
def __init__(self, visible: bool = True, css: Optional[Dict[str, str]] = None):
|
||||
"""
|
||||
css: Css rules to apply to block.
|
||||
"""
|
||||
self.children = []
|
||||
self.css = css if css is not None else {}
|
||||
self.visible = visible
|
||||
super().__init__()
|
||||
|
||||
def __enter__(self):
|
||||
self.parent = Context.block
|
||||
Context.block = self
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
Context.block = self.parent
|
||||
|
||||
def get_template_context(self):
|
||||
return {"css": self.css}
|
||||
return {"css": self.css, "default_value": self.visible}
|
||||
|
||||
def postprocess(self, y):
|
||||
return y
|
||||
|
||||
|
||||
class Row(BlockContext):
|
||||
def __init__(self, css: Optional[str] = None):
|
||||
def __init__(self, visible: bool = True, css: Optional[Dict[str, str]] = None):
|
||||
"""
|
||||
css: Css rules to apply to block.
|
||||
"""
|
||||
super().__init__(css)
|
||||
super().__init__(visible, css)
|
||||
|
||||
def get_template_context(self):
|
||||
return {"type": "row", **super().get_template_context()}
|
||||
|
||||
|
||||
class Column(BlockContext):
|
||||
def __init__(self, css: Optional[str] = None):
|
||||
def __init__(self, visible: bool = True, css: Optional[Dict[str, str]] = None):
|
||||
"""
|
||||
css: Css rules to apply to block.
|
||||
"""
|
||||
super().__init__(css)
|
||||
super().__init__(visible, css)
|
||||
|
||||
def get_template_context(self):
|
||||
return {
|
||||
@ -106,11 +116,11 @@ class Column(BlockContext):
|
||||
|
||||
|
||||
class Tabs(BlockContext):
|
||||
def __init__(self, css: Optional[dict] = None):
|
||||
def __init__(self, visible: bool = True, css: Optional[Dict[str, str]] = None):
|
||||
"""
|
||||
css: css rules to apply to block.
|
||||
"""
|
||||
super().__init__(css)
|
||||
super().__init__(visible, css)
|
||||
|
||||
def change(self, fn: Callable, inputs: List[Component], outputs: List[Component]):
|
||||
"""
|
||||
@ -124,13 +134,14 @@ class Tabs(BlockContext):
|
||||
|
||||
|
||||
class TabItem(BlockContext):
|
||||
def __init__(self, label, css: Optional[str] = None):
|
||||
def __init__(
|
||||
self, label, visible: bool = True, css: Optional[Dict[str, str]] = None
|
||||
):
|
||||
"""
|
||||
css: Css rules to apply to block.
|
||||
"""
|
||||
super().__init__(css)
|
||||
super().__init__(visible, css)
|
||||
self.label = label
|
||||
super(TabItem, self).__init__()
|
||||
|
||||
def get_template_context(self):
|
||||
return {"label": self.label, **super().get_template_context()}
|
||||
@ -159,22 +170,28 @@ class Blocks(Launchable, BlockContext):
|
||||
self.is_space = True if os.getenv("SYSTEM") == "spaces" else False
|
||||
|
||||
super().__init__()
|
||||
Context.root_block = self
|
||||
self.blocks = {}
|
||||
self.fns = []
|
||||
self.dependencies = []
|
||||
|
||||
def render(self):
|
||||
self._id = Context.id
|
||||
Context.id += 1
|
||||
|
||||
def process_api(self, data: Dict[str, Any], username: str = None) -> Dict[str, Any]:
|
||||
raw_input = data["data"]
|
||||
fn_index = data["fn_index"]
|
||||
fn = self.fns[fn_index]
|
||||
fn, preprocess = self.fns[fn_index]
|
||||
dependency = self.dependencies[fn_index]
|
||||
|
||||
processed_input = [
|
||||
self.blocks[input_id].preprocess(raw_input[i])
|
||||
for i, input_id in enumerate(dependency["inputs"])
|
||||
]
|
||||
predictions = fn(*processed_input)
|
||||
if preprocess:
|
||||
processed_input = [
|
||||
self.blocks[input_id].preprocess(raw_input[i])
|
||||
for i, input_id in enumerate(dependency["inputs"])
|
||||
]
|
||||
predictions = fn(*processed_input)
|
||||
else:
|
||||
predictions = fn(*raw_input)
|
||||
if len(dependency["outputs"]) == 1:
|
||||
predictions = (predictions,)
|
||||
processed_output = [
|
||||
@ -189,8 +206,6 @@ class Blocks(Launchable, BlockContext):
|
||||
return {"type": "column"}
|
||||
|
||||
def get_config_file(self):
|
||||
from gradio.components import Component
|
||||
|
||||
config = {"mode": "blocks", "components": [], "theme": self.theme}
|
||||
for _id, block in self.blocks.items():
|
||||
config["components"].append(
|
||||
@ -216,9 +231,15 @@ class Blocks(Launchable, BlockContext):
|
||||
return config
|
||||
|
||||
def __enter__(self):
|
||||
BlockContext.__enter__(self)
|
||||
Context.root_block = self
|
||||
if Context.block is None:
|
||||
Context.root_block = self
|
||||
self.parent = Context.block
|
||||
Context.block = self
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
BlockContext.__exit__(self, *args)
|
||||
Context.root_block = self.parent
|
||||
Context.block = self.parent
|
||||
if self.parent is None:
|
||||
Context.root_block = None
|
||||
else:
|
||||
self.parent.children.extend(self.children)
|
||||
|
@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import json
|
||||
import math
|
||||
import numbers
|
||||
@ -35,6 +36,7 @@ class Component(Block):
|
||||
label: Optional[str] = None,
|
||||
requires_permissions: bool = False,
|
||||
css: Optional[Dict] = None,
|
||||
without_rendering: bool = False,
|
||||
**kwargs,
|
||||
):
|
||||
if "optional" in kwargs:
|
||||
@ -47,7 +49,7 @@ class Component(Block):
|
||||
self.css = css if css is not None else {}
|
||||
|
||||
self.set_interpret_parameters()
|
||||
super().__init__()
|
||||
super().__init__(without_rendering=without_rendering)
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
@ -925,7 +927,10 @@ class Radio(Component):
|
||||
if self.type == "value":
|
||||
return x
|
||||
elif self.type == "index":
|
||||
return self.choices.index(x)
|
||||
if x is None:
|
||||
return None
|
||||
else:
|
||||
return self.choices.index(x)
|
||||
else:
|
||||
raise ValueError(
|
||||
"Unknown type: "
|
||||
@ -2791,7 +2796,8 @@ class Markdown(Component):
|
||||
"""
|
||||
super().__init__(label=label, css=css, **kwargs)
|
||||
self.md = MarkdownIt()
|
||||
self.default_value = self.md.render(default_value)
|
||||
unindented_default_value = inspect.cleandoc(default_value)
|
||||
self.default_value = self.md.render(unindented_default_value)
|
||||
|
||||
def get_template_context(self):
|
||||
return {"default_value": self.default_value, **super().get_template_context()}
|
||||
@ -2822,7 +2828,13 @@ class Button(Component):
|
||||
def get_template_context(self):
|
||||
return {"default_value": self.default_value, **super().get_template_context()}
|
||||
|
||||
def click(self, fn: Callable, inputs: List[Component], outputs: List[Component]):
|
||||
def click(
|
||||
self,
|
||||
fn: Callable,
|
||||
inputs: List[Component],
|
||||
outputs: List[Component],
|
||||
queue=False,
|
||||
):
|
||||
"""
|
||||
Parameters:
|
||||
fn: Callable function
|
||||
@ -2830,7 +2842,19 @@ class Button(Component):
|
||||
outputs: List of outputs
|
||||
Returns: None
|
||||
"""
|
||||
self.set_event_trigger("click", fn, inputs, outputs)
|
||||
self.set_event_trigger("click", fn, inputs, outputs, queue=queue)
|
||||
|
||||
def _click_no_preprocess(
|
||||
self, fn: Callable, inputs: List[Component], outputs: List[Component]
|
||||
):
|
||||
"""
|
||||
Parameters:
|
||||
fn: Callable function
|
||||
inputs: List of inputs
|
||||
outputs: List of outputs
|
||||
Returns: None
|
||||
"""
|
||||
self.set_event_trigger("click", fn, inputs, outputs, preprocess=False)
|
||||
|
||||
|
||||
class Dataset(Component):
|
||||
@ -2875,6 +2899,29 @@ class Dataset(Component):
|
||||
self.set_event_trigger("click", fn, inputs, outputs)
|
||||
|
||||
|
||||
class Interpretation(Component):
|
||||
"""
|
||||
Used to create an interpretation widget for a component.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
component: Component,
|
||||
*,
|
||||
label: Optional[str] = None,
|
||||
css: Optional[Dict] = None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(label=label, css=css, **kwargs)
|
||||
self.component = component
|
||||
|
||||
def get_template_context(self):
|
||||
return {
|
||||
"component": self.component.__class__.__name__.lower(),
|
||||
"component_props": self.component.get_template_context(),
|
||||
}
|
||||
|
||||
|
||||
# TODO: (faruk) does this take component or interface as a input?
|
||||
# see this line in Carousel
|
||||
# self.components = [get_component_instance(component) for component in components]
|
||||
@ -2883,7 +2930,7 @@ def get_component_instance(iface: Component):
|
||||
# https://github.com/gradio-app/gradio/issues/731
|
||||
if isinstance(iface, str):
|
||||
shortcut = Component.get_all_shortcut_implementations()[iface]
|
||||
return shortcut[0](**shortcut[1])
|
||||
return shortcut[0](**shortcut[1], without_rendering=True)
|
||||
elif isinstance(
|
||||
iface, dict
|
||||
): # a dict with `name` as the input component type and other keys as parameters
|
||||
@ -2893,7 +2940,7 @@ def get_component_instance(iface: Component):
|
||||
break
|
||||
else:
|
||||
raise ValueError(f"No such Component: {name}")
|
||||
return component(**iface)
|
||||
return component(**iface, without_rendering=True)
|
||||
elif isinstance(iface, Component):
|
||||
return iface
|
||||
else:
|
||||
|
@ -1,4 +1,14 @@
|
||||
# Defines the Context class, which is used to store the state of all Blocks that are being rendered.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, List
|
||||
|
||||
if TYPE_CHECKING: # Only import for type checking (is False at runtime).
|
||||
from gradio.blocks import Block
|
||||
|
||||
|
||||
class Context:
|
||||
root_block = None
|
||||
block = None
|
||||
id = 0
|
||||
root_block: Block = None # The current root block that holds all blocks.
|
||||
block: Block = None # The current block that all children should be added to.
|
||||
id = 0 # Running id to uniquely refer to any block that gets defined
|
||||
|
@ -13,30 +13,27 @@ import re
|
||||
import time
|
||||
import warnings
|
||||
import weakref
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple
|
||||
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Tuple
|
||||
|
||||
from markdown_it import MarkdownIt
|
||||
from mdit_py_plugins.footnote import footnote_plugin
|
||||
|
||||
from gradio import interpretation, utils
|
||||
from gradio.blocks import Block, BlockContext, Blocks, Column, Row
|
||||
from gradio import context, interpretation, utils
|
||||
from gradio.blocks import Blocks, Column, Row, TabItem, Tabs
|
||||
from gradio.components import (
|
||||
Button,
|
||||
Component,
|
||||
Dataset,
|
||||
Interpretation,
|
||||
Markdown,
|
||||
get_component_instance,
|
||||
)
|
||||
from gradio.external import load_from_pipeline, load_interface # type: ignore
|
||||
from gradio.flagging import CSVLogger, FlaggingCallback # type: ignore
|
||||
from gradio.inputs import State as i_State # type: ignore
|
||||
from gradio.launchable import Launchable
|
||||
from gradio.outputs import State as o_State # type: ignore
|
||||
from gradio.process_examples import load_from_cache, process_example
|
||||
from gradio.routes import predict
|
||||
|
||||
if TYPE_CHECKING: # Only import for type checking (is False at runtime).
|
||||
import flask
|
||||
import transformers
|
||||
|
||||
|
||||
@ -489,11 +486,20 @@ class Interface(Blocks):
|
||||
"border-radius": "0.5rem",
|
||||
}
|
||||
):
|
||||
for component in self.input_components:
|
||||
component.render()
|
||||
input_component_column = Column()
|
||||
with input_component_column:
|
||||
for component in self.input_components:
|
||||
component.render()
|
||||
if self.interpretation:
|
||||
interpret_component_column = Column(visible=False)
|
||||
interpretation_set = []
|
||||
with interpret_component_column:
|
||||
for component in self.input_components:
|
||||
interpretation_set.append(Interpretation(component))
|
||||
with Row():
|
||||
clear_btn = Button("Clear")
|
||||
submit_btn = Button("Submit")
|
||||
if not self.live:
|
||||
submit_btn = Button("Submit")
|
||||
with Column(
|
||||
css={
|
||||
"background-color": "rgb(249,250,251)",
|
||||
@ -505,18 +511,39 @@ class Interface(Blocks):
|
||||
component.render()
|
||||
with Row():
|
||||
flag_btn = Button("Flag")
|
||||
submit_btn.click(
|
||||
if self.interpretation:
|
||||
interpretation_btn = Button("Interpret")
|
||||
submit_fn = (
|
||||
lambda *args: self.run_prediction(args, return_duration=False)[0]
|
||||
if len(self.output_components) == 1
|
||||
else self.run_prediction(args, return_duration=False),
|
||||
self.input_components,
|
||||
self.output_components,
|
||||
else self.run_prediction(args, return_duration=False)
|
||||
)
|
||||
if self.live:
|
||||
for component in self.input_components:
|
||||
component.change(
|
||||
submit_fn, self.input_components, self.output_components
|
||||
)
|
||||
else:
|
||||
submit_btn.click(
|
||||
submit_fn,
|
||||
self.input_components,
|
||||
self.output_components,
|
||||
queue=self.enable_queue,
|
||||
)
|
||||
clear_btn.click(
|
||||
lambda: [None]
|
||||
* (len(self.input_components) + len(self.output_components)),
|
||||
lambda: [
|
||||
component.default_value
|
||||
if hasattr(component, "default_value")
|
||||
else None
|
||||
for component in self.input_components + self.output_components
|
||||
]
|
||||
+ [True]
|
||||
+ ([False] if self.interpretation else []),
|
||||
[],
|
||||
self.input_components + self.output_components,
|
||||
self.input_components
|
||||
+ self.output_components
|
||||
+ [input_component_column]
|
||||
+ ([interpret_component_column] if self.interpretation else []),
|
||||
)
|
||||
if self.examples:
|
||||
examples = Dataset(
|
||||
@ -530,6 +557,13 @@ class Interface(Blocks):
|
||||
inputs=self.input_components + self.output_components,
|
||||
outputs=[],
|
||||
)
|
||||
if self.interpretation:
|
||||
interpretation_btn._click_no_preprocess(
|
||||
lambda *data: self.interpret(data) + [False, True],
|
||||
inputs=self.input_components + self.output_components,
|
||||
outputs=interpretation_set
|
||||
+ [input_component_column, interpret_component_column],
|
||||
)
|
||||
|
||||
def __call__(self, *params):
|
||||
if (
|
||||
@ -655,7 +689,12 @@ class Interface(Blocks):
|
||||
return processed_output, durations
|
||||
|
||||
def interpret(self, raw_input: List[Any]) -> List[Any]:
|
||||
return interpretation.run_interpret(self, raw_input)
|
||||
return [
|
||||
{"original": raw_value, "interpretation": interpretation}
|
||||
for interpretation, raw_value in zip(
|
||||
interpretation.run_interpret(self, raw_input)[0], raw_input
|
||||
)
|
||||
]
|
||||
|
||||
def test_launch(self) -> None:
|
||||
"""
|
||||
|
@ -153,7 +153,7 @@ def run_interpret(interface, raw_input):
|
||||
scores.append(None)
|
||||
alternative_outputs.append([])
|
||||
else:
|
||||
raise ValueError("Uknown intepretation method: {}".format(interp))
|
||||
raise ValueError("Unknown intepretation method: {}".format(interp))
|
||||
return scores, alternative_outputs
|
||||
else: # custom interpretation function
|
||||
processed_input = [
|
||||
|
@ -16,7 +16,7 @@ import requests
|
||||
import uvicorn
|
||||
|
||||
from gradio import queueing
|
||||
from gradio.routes import app
|
||||
from gradio.routes import create_app
|
||||
from gradio.tunneling import create_tunnel
|
||||
|
||||
if TYPE_CHECKING: # Only import for type checking (to avoid circular imports).
|
||||
@ -124,6 +124,8 @@ def start_server(
|
||||
path_to_local_server = "http://{}:{}/".format(url_host_name, port)
|
||||
|
||||
auth = launchable.auth
|
||||
app = create_app()
|
||||
|
||||
if auth is not None:
|
||||
if not callable(auth):
|
||||
app.auth = {account[0]: account[1] for account in auth}
|
||||
|
413
gradio/routes.py
@ -46,14 +46,6 @@ class ORJSONResponse(JSONResponse):
|
||||
return orjson.dumps(content)
|
||||
|
||||
|
||||
app = FastAPI(default_response_class=ORJSONResponse)
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
templates = Jinja2Templates(directory=STATIC_TEMPLATE_LIB)
|
||||
|
||||
|
||||
@ -62,212 +54,214 @@ templates = Jinja2Templates(directory=STATIC_TEMPLATE_LIB)
|
||||
###########
|
||||
|
||||
|
||||
@app.get("/user")
|
||||
@app.get("/user/")
|
||||
def get_current_user(request: Request) -> Optional[str]:
|
||||
token = request.cookies.get("access-token")
|
||||
return app.tokens.get(token)
|
||||
def create_app() -> FastAPI:
|
||||
|
||||
|
||||
@app.get("/login_check")
|
||||
@app.get("/login_check/")
|
||||
def login_check(user: str = Depends(get_current_user)):
|
||||
if app.auth is None or not (user is None):
|
||||
return
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated"
|
||||
app = FastAPI(default_response_class=ORJSONResponse)
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
@app.get("/user")
|
||||
@app.get("/user/")
|
||||
def get_current_user(request: Request) -> Optional[str]:
|
||||
token = request.cookies.get("access-token")
|
||||
return app.tokens.get(token)
|
||||
|
||||
@app.get("/token")
|
||||
@app.get("/token/")
|
||||
def get_token(request: Request) -> Optional[str]:
|
||||
token = request.cookies.get("access-token")
|
||||
return {"token": token, "user": app.tokens.get(token)}
|
||||
|
||||
|
||||
@app.post("/login")
|
||||
@app.post("/login/")
|
||||
def login(form_data: OAuth2PasswordRequestForm = Depends()):
|
||||
username, password = form_data.username, form_data.password
|
||||
if (
|
||||
not callable(app.auth)
|
||||
and username in app.auth
|
||||
and app.auth[username] == password
|
||||
) or (callable(app.auth) and app.auth.__call__(username, password)):
|
||||
token = secrets.token_urlsafe(16)
|
||||
app.tokens[token] = username
|
||||
response = RedirectResponse(url="/", status_code=status.HTTP_302_FOUND)
|
||||
response.set_cookie(key="access-token", value=token, httponly=True)
|
||||
return response
|
||||
else:
|
||||
raise HTTPException(status_code=400, detail="Incorrect credentials.")
|
||||
|
||||
|
||||
###############
|
||||
# Main Routes
|
||||
###############
|
||||
|
||||
|
||||
@app.head("/", response_class=HTMLResponse)
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
def main(request: Request, user: str = Depends(get_current_user)):
|
||||
if app.auth is None or not (user is None):
|
||||
config = app.launchable.config
|
||||
else:
|
||||
config = {"auth_required": True, "auth_message": app.launchable.auth_message}
|
||||
|
||||
try:
|
||||
return templates.TemplateResponse(
|
||||
"frontend/index.html", {"request": request, "config": config}
|
||||
)
|
||||
except TemplateNotFound:
|
||||
raise ValueError(
|
||||
"Did you install Gradio from source files? You need to build "
|
||||
"the frontend by running /scripts/build_frontend.sh"
|
||||
@app.get("/login_check")
|
||||
@app.get("/login_check/")
|
||||
def login_check(user: str = Depends(get_current_user)):
|
||||
if app.auth is None or not (user is None):
|
||||
return
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated"
|
||||
)
|
||||
|
||||
@app.get("/token")
|
||||
@app.get("/token/")
|
||||
def get_token(request: Request) -> Optional[str]:
|
||||
token = request.cookies.get("access-token")
|
||||
return {"token": token, "user": app.tokens.get(token)}
|
||||
|
||||
@app.get("/config/", dependencies=[Depends(login_check)])
|
||||
@app.get("/config", dependencies=[Depends(login_check)])
|
||||
def get_config():
|
||||
return app.launchable.config
|
||||
|
||||
|
||||
@app.get("/static/{path:path}")
|
||||
def static_resource(path: str):
|
||||
if app.launchable.share:
|
||||
return RedirectResponse(GRADIO_STATIC_ROOT + path)
|
||||
else:
|
||||
static_file = safe_join(STATIC_PATH_LIB, path)
|
||||
if static_file is not None:
|
||||
return FileResponse(static_file)
|
||||
raise HTTPException(status_code=404, detail="Static file not found")
|
||||
|
||||
|
||||
@app.get("/assets/{path:path}")
|
||||
def build_resource(path: str):
|
||||
if app.launchable.share:
|
||||
return RedirectResponse(GRADIO_BUILD_ROOT + path)
|
||||
else:
|
||||
build_file = safe_join(BUILD_PATH_LIB, path)
|
||||
if build_file is not None:
|
||||
return FileResponse(build_file)
|
||||
raise HTTPException(status_code=404, detail="Build file not found")
|
||||
|
||||
|
||||
@app.get("/file/{path:path}", dependencies=[Depends(login_check)])
|
||||
def file(path):
|
||||
if (
|
||||
app.launchable.encrypt
|
||||
and isinstance(app.launchable.examples, str)
|
||||
and path.startswith(app.launchable.examples)
|
||||
):
|
||||
with open(safe_join(app.cwd, path), "rb") as encrypted_file:
|
||||
encrypted_data = encrypted_file.read()
|
||||
file_data = encryptor.decrypt(app.launchable.encryption_key, encrypted_data)
|
||||
return FileResponse(
|
||||
io.BytesIO(file_data), attachment_filename=os.path.basename(path)
|
||||
)
|
||||
else:
|
||||
return FileResponse(safe_join(app.cwd, path))
|
||||
|
||||
|
||||
@app.get("/api", response_class=HTMLResponse) # Needed for Spaces
|
||||
@app.get("/api/", response_class=HTMLResponse)
|
||||
def api_docs(request: Request):
|
||||
inputs = [type(inp) for inp in app.launchable.input_components]
|
||||
outputs = [type(out) for out in app.launchable.output_components]
|
||||
input_types_doc, input_types = get_types(inputs, "input")
|
||||
output_types_doc, output_types = get_types(outputs, "output")
|
||||
input_names = [type(inp).__name__ for inp in app.launchable.input_components]
|
||||
output_names = [type(out).__name__ for out in app.launchable.output_components]
|
||||
if app.launchable.examples is not None:
|
||||
sample_inputs = app.launchable.examples[0]
|
||||
else:
|
||||
sample_inputs = [
|
||||
inp.generate_sample() for inp in app.launchable.input_components
|
||||
]
|
||||
docs = {
|
||||
"inputs": input_names,
|
||||
"outputs": output_names,
|
||||
"len_inputs": len(inputs),
|
||||
"len_outputs": len(outputs),
|
||||
"inputs_lower": [name.lower() for name in input_names],
|
||||
"outputs_lower": [name.lower() for name in output_names],
|
||||
"input_types": input_types,
|
||||
"output_types": output_types,
|
||||
"input_types_doc": input_types_doc,
|
||||
"output_types_doc": output_types_doc,
|
||||
"sample_inputs": sample_inputs,
|
||||
"auth": app.launchable.auth,
|
||||
"local_login_url": urllib.parse.urljoin(app.launchable.local_url, "login"),
|
||||
"local_api_url": urllib.parse.urljoin(app.launchable.local_url, "api/predict"),
|
||||
}
|
||||
return templates.TemplateResponse("api_docs.html", {"request": request, **docs})
|
||||
|
||||
|
||||
@app.post("/api/predict/", dependencies=[Depends(login_check)])
|
||||
async def predict(request: Request, username: str = Depends(get_current_user)):
|
||||
body = await request.json()
|
||||
try:
|
||||
output = await run_in_threadpool(app.launchable.process_api, body, username)
|
||||
except BaseException as error:
|
||||
if app.launchable.show_error:
|
||||
traceback.print_exc()
|
||||
return JSONResponse(content={"error": str(error)}, status_code=500)
|
||||
@app.post("/login")
|
||||
@app.post("/login/")
|
||||
def login(form_data: OAuth2PasswordRequestForm = Depends()):
|
||||
username, password = form_data.username, form_data.password
|
||||
if (
|
||||
not callable(app.auth)
|
||||
and username in app.auth
|
||||
and app.auth[username] == password
|
||||
) or (callable(app.auth) and app.auth.__call__(username, password)):
|
||||
token = secrets.token_urlsafe(16)
|
||||
app.tokens[token] = username
|
||||
response = RedirectResponse(url="/", status_code=status.HTTP_302_FOUND)
|
||||
response.set_cookie(key="access-token", value=token, httponly=True)
|
||||
return response
|
||||
else:
|
||||
raise error
|
||||
return output
|
||||
raise HTTPException(status_code=400, detail="Incorrect credentials.")
|
||||
|
||||
###############
|
||||
# Main Routes
|
||||
###############
|
||||
|
||||
@app.post("/api/flag/", dependencies=[Depends(login_check)])
|
||||
async def flag(request: Request, username: str = Depends(get_current_user)):
|
||||
if app.launchable.analytics_enabled:
|
||||
await utils.log_feature_analytics(app.launchable.ip_address, "flag")
|
||||
body = await request.json()
|
||||
data = body["data"]
|
||||
await run_in_threadpool(
|
||||
app.launchable.flagging_callback.flag,
|
||||
app.launchable,
|
||||
data["input_data"],
|
||||
data["output_data"],
|
||||
flag_option=data.get("flag_option"),
|
||||
flag_index=data.get("flag_index"),
|
||||
username=username,
|
||||
)
|
||||
return {"success": True}
|
||||
@app.head("/", response_class=HTMLResponse)
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
def main(request: Request, user: str = Depends(get_current_user)):
|
||||
if app.auth is None or not (user is None):
|
||||
config = app.launchable.config
|
||||
else:
|
||||
config = {
|
||||
"auth_required": True,
|
||||
"auth_message": app.launchable.auth_message,
|
||||
}
|
||||
|
||||
try:
|
||||
return templates.TemplateResponse(
|
||||
"frontend/index.html", {"request": request, "config": config}
|
||||
)
|
||||
except TemplateNotFound:
|
||||
raise ValueError(
|
||||
"Did you install Gradio from source files? You need to build "
|
||||
"the frontend by running /scripts/build_frontend.sh"
|
||||
)
|
||||
|
||||
@app.post("/api/interpret/", dependencies=[Depends(login_check)])
|
||||
async def interpret(request: Request):
|
||||
if app.launchable.analytics_enabled:
|
||||
await utils.log_feature_analytics(app.launchable.ip_address, "interpret")
|
||||
body = await request.json()
|
||||
raw_input = body["data"]
|
||||
interpretation_scores, alternative_outputs = await run_in_threadpool(
|
||||
app.launchable.interpret, raw_input
|
||||
)
|
||||
return {
|
||||
"interpretation_scores": interpretation_scores,
|
||||
"alternative_outputs": alternative_outputs,
|
||||
}
|
||||
@app.get("/config/", dependencies=[Depends(login_check)])
|
||||
@app.get("/config", dependencies=[Depends(login_check)])
|
||||
def get_config():
|
||||
return app.launchable.config
|
||||
|
||||
@app.get("/static/{path:path}")
|
||||
def static_resource(path: str):
|
||||
if app.launchable.share:
|
||||
return RedirectResponse(GRADIO_STATIC_ROOT + path)
|
||||
else:
|
||||
static_file = safe_join(STATIC_PATH_LIB, path)
|
||||
if static_file is not None:
|
||||
return FileResponse(static_file)
|
||||
raise HTTPException(status_code=404, detail="Static file not found")
|
||||
|
||||
@app.post("/api/queue/push/", dependencies=[Depends(login_check)])
|
||||
async def queue_push(request: Request):
|
||||
body = await request.json()
|
||||
action = body["action"]
|
||||
job_hash, queue_position = queueing.push(body, action)
|
||||
return {"hash": job_hash, "queue_position": queue_position}
|
||||
@app.get("/assets/{path:path}")
|
||||
def build_resource(path: str):
|
||||
if app.launchable.share:
|
||||
return RedirectResponse(GRADIO_BUILD_ROOT + path)
|
||||
else:
|
||||
build_file = safe_join(BUILD_PATH_LIB, path)
|
||||
if build_file is not None:
|
||||
return FileResponse(build_file)
|
||||
raise HTTPException(status_code=404, detail="Build file not found")
|
||||
|
||||
@app.get("/file/{path:path}", dependencies=[Depends(login_check)])
|
||||
def file(path):
|
||||
if (
|
||||
app.launchable.encrypt
|
||||
and isinstance(app.launchable.examples, str)
|
||||
and path.startswith(app.launchable.examples)
|
||||
):
|
||||
with open(safe_join(app.cwd, path), "rb") as encrypted_file:
|
||||
encrypted_data = encrypted_file.read()
|
||||
file_data = encryptor.decrypt(app.launchable.encryption_key, encrypted_data)
|
||||
return FileResponse(
|
||||
io.BytesIO(file_data), attachment_filename=os.path.basename(path)
|
||||
)
|
||||
else:
|
||||
return FileResponse(safe_join(app.cwd, path))
|
||||
|
||||
@app.post("/api/queue/status/", dependencies=[Depends(login_check)])
|
||||
async def queue_status(request: Request):
|
||||
body = await request.json()
|
||||
hash = body["hash"]
|
||||
status, data = queueing.get_status(hash)
|
||||
return {"status": status, "data": data}
|
||||
@app.get("/api", response_class=HTMLResponse) # Needed for Spaces
|
||||
@app.get("/api/", response_class=HTMLResponse)
|
||||
def api_docs(request: Request):
|
||||
inputs = [type(inp) for inp in app.launchable.input_components]
|
||||
outputs = [type(out) for out in app.launchable.output_components]
|
||||
input_types_doc, input_types = get_types(inputs, "input")
|
||||
output_types_doc, output_types = get_types(outputs, "output")
|
||||
input_names = [type(inp).__name__ for inp in app.launchable.input_components]
|
||||
output_names = [type(out).__name__ for out in app.launchable.output_components]
|
||||
if app.launchable.examples is not None:
|
||||
sample_inputs = app.launchable.examples[0]
|
||||
else:
|
||||
sample_inputs = [
|
||||
inp.generate_sample() for inp in app.launchable.input_components
|
||||
]
|
||||
docs = {
|
||||
"inputs": input_names,
|
||||
"outputs": output_names,
|
||||
"len_inputs": len(inputs),
|
||||
"len_outputs": len(outputs),
|
||||
"inputs_lower": [name.lower() for name in input_names],
|
||||
"outputs_lower": [name.lower() for name in output_names],
|
||||
"input_types": input_types,
|
||||
"output_types": output_types,
|
||||
"input_types_doc": input_types_doc,
|
||||
"output_types_doc": output_types_doc,
|
||||
"sample_inputs": sample_inputs,
|
||||
"auth": app.launchable.auth,
|
||||
"local_login_url": urllib.parse.urljoin(app.launchable.local_url, "login"),
|
||||
"local_api_url": urllib.parse.urljoin(
|
||||
app.launchable.local_url, "api/predict"
|
||||
),
|
||||
}
|
||||
return templates.TemplateResponse("api_docs.html", {"request": request, **docs})
|
||||
|
||||
@app.post("/api/predict/", dependencies=[Depends(login_check)])
|
||||
async def predict(request: Request, username: str = Depends(get_current_user)):
|
||||
body = await request.json()
|
||||
try:
|
||||
output = await run_in_threadpool(app.launchable.process_api, body, username)
|
||||
except BaseException as error:
|
||||
if app.launchable.show_error:
|
||||
traceback.print_exc()
|
||||
return JSONResponse(content={"error": str(error)}, status_code=500)
|
||||
else:
|
||||
raise error
|
||||
return output
|
||||
|
||||
@app.post("/api/flag/", dependencies=[Depends(login_check)])
|
||||
async def flag(request: Request, username: str = Depends(get_current_user)):
|
||||
if app.launchable.analytics_enabled:
|
||||
await utils.log_feature_analytics(app.launchable.ip_address, "flag")
|
||||
body = await request.json()
|
||||
data = body["data"]
|
||||
await run_in_threadpool(
|
||||
app.launchable.flagging_callback.flag,
|
||||
app.launchable,
|
||||
data["input_data"],
|
||||
data["output_data"],
|
||||
flag_option=data.get("flag_option"),
|
||||
flag_index=data.get("flag_index"),
|
||||
username=username,
|
||||
)
|
||||
return {"success": True}
|
||||
|
||||
@app.post("/api/interpret/", dependencies=[Depends(login_check)])
|
||||
async def interpret(request: Request):
|
||||
if app.launchable.analytics_enabled:
|
||||
await utils.log_feature_analytics(app.launchable.ip_address, "interpret")
|
||||
body = await request.json()
|
||||
raw_input = body["data"]
|
||||
interpretation_scores, alternative_outputs = await run_in_threadpool(
|
||||
app.launchable.interpret, raw_input
|
||||
)
|
||||
return {
|
||||
"interpretation_scores": interpretation_scores,
|
||||
"alternative_outputs": alternative_outputs,
|
||||
}
|
||||
|
||||
@app.post("/api/queue/push/", dependencies=[Depends(login_check)])
|
||||
async def queue_push(request: Request):
|
||||
body = await request.json()
|
||||
action = body["action"]
|
||||
job_hash, queue_position = queueing.push(body, action)
|
||||
return {"hash": job_hash, "queue_position": queue_position}
|
||||
|
||||
@app.post("/api/queue/status/", dependencies=[Depends(login_check)])
|
||||
async def queue_status(request: Request):
|
||||
body = await request.json()
|
||||
hash = body["hash"]
|
||||
status, data = queueing.get_status(hash)
|
||||
return {"status": status, "data": data}
|
||||
|
||||
return app
|
||||
|
||||
|
||||
########
|
||||
@ -328,26 +322,3 @@ def set_state(*args):
|
||||
"as both an input and output component. Please see the getting started"
|
||||
"guide for more information."
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__": # Run directly for debugging: python app.py
|
||||
from gradio import Interface
|
||||
|
||||
app.launchable = Interface(
|
||||
lambda x: "Hello, " + x, "text", "text", analytics_enabled=False
|
||||
)
|
||||
app.launchable.favicon_path = None
|
||||
app.launchable.config = app.launchable.get_config_file()
|
||||
app.launchable.show_error = True
|
||||
app.launchable.flagging_callback.setup(app.launchable.flagging_dir)
|
||||
app.tokens = {}
|
||||
|
||||
auth = True
|
||||
if auth:
|
||||
app.launchable.auth = ("a", "b")
|
||||
app.auth = {"a": "b"}
|
||||
app.launchable.auth_message = None
|
||||
else:
|
||||
app.auth = None
|
||||
|
||||
uvicorn.run(app)
|
||||
|
@ -45,9 +45,8 @@
|
||||
</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.1/iframeResizer.contentWindow.min.js"></script>
|
||||
<title>Gradio</title>
|
||||
<script type="module" crossorigin src="./assets/index.9cf03fca.js"></script>
|
||||
<link rel="modulepreload" href="./assets/vendor.906e418a.js">
|
||||
<link rel="stylesheet" href="./assets/index.cdf32a5f.css">
|
||||
<script type="module" crossorigin src="./assets/index.18732393.js"></script>
|
||||
<link rel="stylesheet" href="./assets/index.ff8c1728.css">
|
||||
</head>
|
||||
|
||||
<body style="height: 100%; margin: 0; padding: 0">
|
||||
|
0
scripts/format_frontend.sh
Normal file → Executable file
@ -51,6 +51,7 @@ class TestBlocks(unittest.TestCase):
|
||||
|
||||
_ = gr.components.Textbox()
|
||||
|
||||
# print(xray_blocks.get_config_file())
|
||||
self.assertEqual(XRAY_CONFIG, xray_blocks.get_config_file())
|
||||
|
||||
|
||||
|
@ -5,7 +5,7 @@ XRAY_CONFIG = {
|
||||
"id": 1,
|
||||
"type": "markdown",
|
||||
"props": {
|
||||
"default_value": "<pre><code> # Detect Disease From Scan\n With this model you can lorem ipsum\n - ipsum 1\n - ipsum 2\n</code></pre>\n",
|
||||
"default_value": "<h1>Detect Disease From Scan</h1>\n<p>With this model you can lorem ipsum</p>\n<ul>\n<li>ipsum 1</li>\n<li>ipsum 2</li>\n</ul>\n",
|
||||
"name": "markdown",
|
||||
"label": None,
|
||||
"css": {},
|
||||
@ -22,12 +22,19 @@ XRAY_CONFIG = {
|
||||
"css": {},
|
||||
},
|
||||
},
|
||||
{"id": 3, "type": "tabs", "props": {"css": {}}},
|
||||
{"id": 4, "type": "tabitem", "props": {"label": "X-ray", "css": {}}},
|
||||
{"id": 5, "type": "tabitem", "props": {"label": "X-ray", "css": {}}},
|
||||
{"id": 6, "type": "row", "props": {"type": "row", "css": {}}},
|
||||
{"id": 3, "type": "tabs", "props": {"css": {}, "default_value": True}},
|
||||
{
|
||||
"id": 7,
|
||||
"id": 4,
|
||||
"type": "tabitem",
|
||||
"props": {"label": "X-ray", "css": {}, "default_value": True},
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "row",
|
||||
"props": {"type": "row", "css": {}, "default_value": True},
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "image",
|
||||
"props": {
|
||||
"image_mode": "RGB",
|
||||
@ -41,12 +48,12 @@ XRAY_CONFIG = {
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"id": 7,
|
||||
"type": "json",
|
||||
"props": {"default_value": '""', "name": "json", "label": None, "css": {}},
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"id": 8,
|
||||
"type": "button",
|
||||
"props": {
|
||||
"default_value": "Run",
|
||||
@ -55,11 +62,18 @@ XRAY_CONFIG = {
|
||||
"css": {"background-color": "red", "--hover-color": "orange"},
|
||||
},
|
||||
},
|
||||
{"id": 10, "type": "tabitem", "props": {"label": "CT Scan", "css": {}}},
|
||||
{"id": 11, "type": "tabitem", "props": {"label": "CT Scan", "css": {}}},
|
||||
{"id": 12, "type": "row", "props": {"type": "row", "css": {}}},
|
||||
{
|
||||
"id": 13,
|
||||
"id": 9,
|
||||
"type": "tabitem",
|
||||
"props": {"label": "CT Scan", "css": {}, "default_value": True},
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "row",
|
||||
"props": {"type": "row", "css": {}, "default_value": True},
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "image",
|
||||
"props": {
|
||||
"image_mode": "RGB",
|
||||
@ -73,12 +87,12 @@ XRAY_CONFIG = {
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"id": 12,
|
||||
"type": "json",
|
||||
"props": {"default_value": '""', "name": "json", "label": None, "css": {}},
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"id": 13,
|
||||
"type": "button",
|
||||
"props": {
|
||||
"default_value": "Run",
|
||||
@ -88,7 +102,7 @@ XRAY_CONFIG = {
|
||||
},
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"id": 14,
|
||||
"type": "textbox",
|
||||
"props": {
|
||||
"lines": 1,
|
||||
@ -110,40 +124,38 @@ XRAY_CONFIG = {
|
||||
"id": 3,
|
||||
"children": [
|
||||
{
|
||||
"id": 5,
|
||||
"id": 4,
|
||||
"children": [
|
||||
{"id": 6, "children": [{"id": 7}, {"id": 8}]},
|
||||
{"id": 9},
|
||||
{"id": 5, "children": [{"id": 6}, {"id": 7}]},
|
||||
{"id": 8},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"id": 9,
|
||||
"children": [
|
||||
{"id": 6, "children": [{"id": 7}, {"id": 8}]},
|
||||
{"id": 9},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"children": [
|
||||
{"id": 12, "children": [{"id": 13}, {"id": 14}]},
|
||||
{"id": 15},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"children": [
|
||||
{"id": 12, "children": [{"id": 13}, {"id": 14}]},
|
||||
{"id": 15},
|
||||
{"id": 10, "children": [{"id": 11}, {"id": 12}]},
|
||||
{"id": 13},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{"id": 16},
|
||||
{"id": 14},
|
||||
],
|
||||
},
|
||||
"dependencies": [
|
||||
{"targets": [9], "trigger": "click", "inputs": [2, 7], "outputs": [8]},
|
||||
{"targets": [15], "trigger": "click", "inputs": [2, 13], "outputs": [14]},
|
||||
{
|
||||
"targets": [8],
|
||||
"trigger": "click",
|
||||
"inputs": [2, 6],
|
||||
"outputs": [7],
|
||||
"queue": False,
|
||||
},
|
||||
{
|
||||
"targets": [13],
|
||||
"trigger": "click",
|
||||
"inputs": [2, 11],
|
||||
"outputs": [12],
|
||||
"queue": False,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
@ -73,40 +73,34 @@ class TestTextbox(unittest.TestCase):
|
||||
"number",
|
||||
interpretation="default",
|
||||
)
|
||||
scores, alternative_outputs = iface.interpret(
|
||||
scores = iface.interpret(
|
||||
["Return the length of the longest word in this sentence"]
|
||||
)
|
||||
)[0]["interpretation"]
|
||||
self.assertEqual(
|
||||
scores,
|
||||
[
|
||||
[
|
||||
("Return", 0.0),
|
||||
(" ", 0),
|
||||
("the", 0.0),
|
||||
(" ", 0),
|
||||
("length", 0.0),
|
||||
(" ", 0),
|
||||
("of", 0.0),
|
||||
(" ", 0),
|
||||
("the", 0.0),
|
||||
(" ", 0),
|
||||
("longest", 0.0),
|
||||
(" ", 0),
|
||||
("word", 0.0),
|
||||
(" ", 0),
|
||||
("in", 0.0),
|
||||
(" ", 0),
|
||||
("this", 0.0),
|
||||
(" ", 0),
|
||||
("sentence", 1.0),
|
||||
(" ", 0),
|
||||
]
|
||||
("Return", 0.0),
|
||||
(" ", 0),
|
||||
("the", 0.0),
|
||||
(" ", 0),
|
||||
("length", 0.0),
|
||||
(" ", 0),
|
||||
("of", 0.0),
|
||||
(" ", 0),
|
||||
("the", 0.0),
|
||||
(" ", 0),
|
||||
("longest", 0.0),
|
||||
(" ", 0),
|
||||
("word", 0.0),
|
||||
(" ", 0),
|
||||
("in", 0.0),
|
||||
(" ", 0),
|
||||
("this", 0.0),
|
||||
(" ", 0),
|
||||
("sentence", 1.0),
|
||||
(" ", 0),
|
||||
],
|
||||
)
|
||||
self.assertEqual(
|
||||
alternative_outputs,
|
||||
[[[8], [8], [8], [8], [8], [8], [8], [8], [8], [7]]],
|
||||
)
|
||||
|
||||
|
||||
class TestNumber(unittest.TestCase):
|
||||
@ -143,32 +137,17 @@ class TestNumber(unittest.TestCase):
|
||||
iface = gr.Interface(
|
||||
lambda x: x**2, "number", "number", interpretation="default"
|
||||
)
|
||||
scores, alternative_outputs = iface.interpret([2])
|
||||
scores = iface.interpret([2])[0]["interpretation"]
|
||||
self.assertEqual(
|
||||
scores,
|
||||
[
|
||||
[
|
||||
(1.94, -0.23640000000000017),
|
||||
(1.96, -0.15840000000000032),
|
||||
(1.98, -0.07960000000000012),
|
||||
[2, None],
|
||||
(2.02, 0.08040000000000003),
|
||||
(2.04, 0.16159999999999997),
|
||||
(2.06, 0.24359999999999982),
|
||||
]
|
||||
],
|
||||
)
|
||||
self.assertEqual(
|
||||
alternative_outputs,
|
||||
[
|
||||
[
|
||||
[3.7636],
|
||||
[3.8415999999999997],
|
||||
[3.9204],
|
||||
[4.0804],
|
||||
[4.1616],
|
||||
[4.2436],
|
||||
]
|
||||
(1.94, -0.23640000000000017),
|
||||
(1.96, -0.15840000000000032),
|
||||
(1.98, -0.07960000000000012),
|
||||
[2, None],
|
||||
(2.02, 0.08040000000000003),
|
||||
(2.04, 0.16159999999999997),
|
||||
(2.06, 0.24359999999999982),
|
||||
],
|
||||
)
|
||||
|
||||
@ -208,35 +187,18 @@ class TestSlider(unittest.TestCase):
|
||||
iface = gr.Interface(
|
||||
lambda x: x**2, "slider", "number", interpretation="default"
|
||||
)
|
||||
scores, alternative_outputs = iface.interpret([2])
|
||||
scores = iface.interpret([2])[0]["interpretation"]
|
||||
self.assertEqual(
|
||||
scores,
|
||||
[
|
||||
[
|
||||
-4.0,
|
||||
200.08163265306123,
|
||||
812.3265306122449,
|
||||
1832.7346938775513,
|
||||
3261.3061224489797,
|
||||
5098.040816326531,
|
||||
7342.938775510205,
|
||||
9996.0,
|
||||
]
|
||||
],
|
||||
)
|
||||
self.assertEqual(
|
||||
alternative_outputs,
|
||||
[
|
||||
[
|
||||
[0.0],
|
||||
[204.08163265306123],
|
||||
[816.3265306122449],
|
||||
[1836.7346938775513],
|
||||
[3265.3061224489797],
|
||||
[5102.040816326531],
|
||||
[7346.938775510205],
|
||||
[10000.0],
|
||||
]
|
||||
-4.0,
|
||||
200.08163265306123,
|
||||
812.3265306122449,
|
||||
1832.7346938775513,
|
||||
3261.3061224489797,
|
||||
5098.040816326531,
|
||||
7342.938775510205,
|
||||
9996.0,
|
||||
],
|
||||
)
|
||||
|
||||
@ -270,12 +232,10 @@ class TestCheckbox(unittest.TestCase):
|
||||
iface = gr.Interface(
|
||||
lambda x: 1 if x else 0, "checkbox", "number", interpretation="default"
|
||||
)
|
||||
scores, alternative_outputs = iface.interpret([False])
|
||||
self.assertEqual(scores, [(None, 1.0)])
|
||||
self.assertEqual(alternative_outputs, [[[1]]])
|
||||
scores, alternative_outputs = iface.interpret([True])
|
||||
self.assertEqual(scores, [(-1.0, None)])
|
||||
self.assertEqual(alternative_outputs, [[[0]]])
|
||||
scores = iface.interpret([False])[0]["interpretation"]
|
||||
self.assertEqual(scores, (None, 1.0))
|
||||
scores = iface.interpret([True])[0]["interpretation"]
|
||||
self.assertEqual(scores, (-1.0, None))
|
||||
|
||||
|
||||
class TestCheckboxGroup(unittest.TestCase):
|
||||
@ -355,9 +315,8 @@ class TestRadio(unittest.TestCase):
|
||||
lambda x: 2 * x, radio_input, "number", interpretation="default"
|
||||
)
|
||||
self.assertEqual(iface.process(["c"])[0], [4])
|
||||
scores, alternative_outputs = iface.interpret(["b"])
|
||||
self.assertEqual(scores, [[-2.0, None, 2.0]])
|
||||
self.assertEqual(alternative_outputs, [[[0], [4]]])
|
||||
scores = iface.interpret(["b"])[0]["interpretation"]
|
||||
self.assertEqual(scores, [-2.0, None, 2.0])
|
||||
|
||||
|
||||
class TestDropdown(unittest.TestCase):
|
||||
@ -400,9 +359,8 @@ class TestDropdown(unittest.TestCase):
|
||||
lambda x: 2 * x, dropdown, "number", interpretation="default"
|
||||
)
|
||||
self.assertEqual(iface.process(["c"])[0], [4])
|
||||
scores, alternative_outputs = iface.interpret(["b"])
|
||||
self.assertEqual(scores, [[-2.0, None, 2.0]])
|
||||
self.assertEqual(alternative_outputs, [[[0], [4]]])
|
||||
scores = iface.interpret(["b"])[0]["interpretation"]
|
||||
self.assertEqual(scores, [-2.0, None, 2.0])
|
||||
|
||||
|
||||
class TestImage(unittest.TestCase):
|
||||
@ -482,23 +440,15 @@ class TestImage(unittest.TestCase):
|
||||
iface = gr.Interface(
|
||||
lambda x: np.sum(x), image_input, "number", interpretation="default"
|
||||
)
|
||||
scores, alternative_outputs = iface.interpret([img])
|
||||
self.assertEqual(scores, media_data.SUM_PIXELS_INTERPRETATION["scores"])
|
||||
self.assertEqual(
|
||||
alternative_outputs,
|
||||
media_data.SUM_PIXELS_INTERPRETATION["alternative_outputs"],
|
||||
)
|
||||
scores = iface.interpret([img])[0]["interpretation"]
|
||||
self.assertEqual(scores, media_data.SUM_PIXELS_INTERPRETATION["scores"][0])
|
||||
iface = gr.Interface(
|
||||
lambda x: np.sum(x), image_input, "label", interpretation="shap"
|
||||
)
|
||||
scores, alternative_outputs = iface.interpret([img])
|
||||
scores = iface.interpret([img])[0]["interpretation"]
|
||||
self.assertEqual(
|
||||
len(scores[0]),
|
||||
len(media_data.SUM_PIXELS_SHAP_INTERPRETATION["scores"][0]),
|
||||
)
|
||||
self.assertEqual(
|
||||
len(alternative_outputs[0]),
|
||||
len(media_data.SUM_PIXELS_SHAP_INTERPRETATION["alternative_outputs"][0]),
|
||||
len(media_data.SUM_PIXELS_SHAP_INTERPRETATION["scores"][0][0]),
|
||||
)
|
||||
image_input = gr.inputs.Image(shape=(30, 10))
|
||||
iface = gr.Interface(
|
||||
|
@ -100,8 +100,8 @@ class TestInterface(unittest.TestCase):
|
||||
|
||||
def test_interface_none_interp(self):
|
||||
interface = Interface(lambda x: x, "textbox", "label", interpretation=[None])
|
||||
scores, alternative_outputs = interface.interpret(["quickest brown fox"])
|
||||
self.assertIsNone(scores[0])
|
||||
scores = interface.interpret(["quickest brown fox"])[0]["interpretation"]
|
||||
self.assertIsNone(scores)
|
||||
|
||||
@mock.patch("webbrowser.open")
|
||||
def test_interface_browser(self, mock_browser):
|
||||
|
@ -18,7 +18,9 @@ class TestDefault(unittest.TestCase):
|
||||
text_interface = Interface(
|
||||
max_word_len, "textbox", "label", interpretation="default"
|
||||
)
|
||||
interpretation = text_interface.interpret(["quickest brown fox"])[0][0]
|
||||
interpretation = text_interface.interpret(["quickest brown fox"])[0][
|
||||
"interpretation"
|
||||
]
|
||||
self.assertGreater(
|
||||
interpretation[0][1], 0
|
||||
) # Checks to see if the first word has >0 score.
|
||||
@ -33,13 +35,12 @@ class TestShapley(unittest.TestCase):
|
||||
text_interface = Interface(
|
||||
max_word_len, "textbox", "label", interpretation="shapley"
|
||||
)
|
||||
interpretation = text_interface.interpret(["quickest brown fox"])[0][0]
|
||||
interpretation = text_interface.interpret(["quickest brown fox"])[0][
|
||||
"interpretation"
|
||||
][0]
|
||||
self.assertGreater(
|
||||
interpretation[0][1], 0
|
||||
interpretation[1], 0
|
||||
) # Checks to see if the first word has >0 score.
|
||||
self.assertEqual(
|
||||
interpretation[-1][1], 0
|
||||
) # Checks to see if the last word has 0 score.
|
||||
|
||||
|
||||
class TestCustom(unittest.TestCase):
|
||||
@ -49,9 +50,11 @@ class TestCustom(unittest.TestCase):
|
||||
text_interface = Interface(
|
||||
max_word_len, "textbox", "label", interpretation=custom
|
||||
)
|
||||
result = text_interface.interpret(["quickest brown fox"])[0][0]
|
||||
result = text_interface.interpret(["quickest brown fox"])[0]["interpretation"][
|
||||
0
|
||||
]
|
||||
self.assertEqual(
|
||||
result[0][1], 1
|
||||
result[1], 1
|
||||
) # Checks to see if the first letter has score of 1.
|
||||
|
||||
def test_custom_img(self):
|
||||
@ -60,7 +63,9 @@ class TestCustom(unittest.TestCase):
|
||||
img_interface = Interface(
|
||||
max_pixel_value, "image", "label", interpretation=custom
|
||||
)
|
||||
result = img_interface.interpret([deepcopy(media_data.BASE64_IMAGE)])[0][0]
|
||||
result = img_interface.interpret([deepcopy(gradio.test_data.BASE64_IMAGE)])[0][
|
||||
"interpretation"
|
||||
]
|
||||
expected_result = np.asarray(
|
||||
decode_base64_to_image(deepcopy(media_data.BASE64_IMAGE)).convert("RGB")
|
||||
).tolist()
|
||||
|
@ -24,6 +24,7 @@
|
||||
"@gradio/tootils": "workspace:^0.0.1",
|
||||
"@playwright/test": "^1.20.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.36",
|
||||
"@tailwindcss/forms": "^0.5.0",
|
||||
"@testing-library/dom": "^8.11.3",
|
||||
"@testing-library/svelte": "^3.1.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
@ -43,7 +44,7 @@
|
||||
"svelte-preprocess": "^4.10.1",
|
||||
"tailwindcss": "^3.0.23",
|
||||
"tinyspy": "^0.3.0",
|
||||
"vite": "^2.7.13",
|
||||
"vite": "^2.9.1",
|
||||
"vitest": "^0.7.4"
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"test:browser": "pnpx playwright test test/ --config=../../playwright.config.js",
|
||||
"test:browser:debug": "pnpx playwright test test/ --debug"
|
||||
"test:browser:debug": "pnpx playwright test test/ --debug --config=../../playwright.config.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gradio/audio": "workspace:^0.0.1",
|
||||
|
@ -8,11 +8,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Interface from "./Interface.svelte";
|
||||
import "./global.css";
|
||||
import "./themes/huggingface.css";
|
||||
import "./themes/grass.css";
|
||||
import "./themes/peach.css";
|
||||
import "./themes/seafoam.css";
|
||||
|
||||
import { _ } from "svelte-i18n";
|
||||
import { setupi18n } from "./i18n";
|
||||
setupi18n();
|
||||
|
@ -28,6 +28,7 @@
|
||||
targets: Array<number>;
|
||||
inputs: Array<string>;
|
||||
outputs: Array<string>;
|
||||
queue: boolean;
|
||||
}
|
||||
|
||||
export let root: string;
|
||||
@ -114,7 +115,7 @@
|
||||
|
||||
async function handle_mount({ detail }) {
|
||||
await tick();
|
||||
dependencies.forEach(({ targets, trigger, inputs, outputs }, i) => {
|
||||
dependencies.forEach(({ targets, trigger, inputs, outputs, queue }, i) => {
|
||||
const target_instances: [number, Instance][] = targets.map((t) => [
|
||||
t,
|
||||
instance_map[t]
|
||||
@ -125,10 +126,15 @@
|
||||
if (handled_dependencies[i]?.includes(id) || !instance) return;
|
||||
// console.log(trigger, target_instances, instance);
|
||||
instance?.$on(trigger, () => {
|
||||
fn("predict", {
|
||||
fn_index: i,
|
||||
data: inputs.map((id) => instance_map[id].value)
|
||||
}).then((output) => {
|
||||
fn(
|
||||
"predict",
|
||||
{
|
||||
fn_index: i,
|
||||
data: inputs.map((id) => instance_map[id].value)
|
||||
},
|
||||
queue,
|
||||
() => {}
|
||||
).then((output) => {
|
||||
output.data.forEach((value, i) => {
|
||||
instance_map[outputs[i]].value = value;
|
||||
});
|
||||
|
@ -9,6 +9,7 @@
|
||||
export let name: string;
|
||||
export let source: "microphone" | "upload";
|
||||
export let type: "normal" | "numpy" = "normal";
|
||||
export let label: string;
|
||||
</script>
|
||||
|
||||
{#if mode === "dynamic"}
|
||||
|
@ -1,4 +1,3 @@
|
||||
export { default as Component } from "./Audio.svelte";
|
||||
export { default as Interpretation } from "./Interpretation.svelte";
|
||||
export { loadAsFile } from "../utils/example_processors";
|
||||
export const modes = ["static", "dynamic"];
|
||||
|
@ -3,11 +3,11 @@
|
||||
|
||||
export let value: boolean = false;
|
||||
export let default_value: boolean = false;
|
||||
export let theme: string;
|
||||
export let style: string | null;
|
||||
export let label: string;
|
||||
export let mode: "static" | "dynamic";
|
||||
|
||||
if (default_value) value = default_value;
|
||||
</script>
|
||||
|
||||
<Checkbox {theme} {style} bind:value on:change disabled={mode === "static"} />
|
||||
<Checkbox {style} {label} bind:value on:change disabled={mode === "static"} />
|
||||
|
@ -1,3 +1,2 @@
|
||||
export { default as Component } from "./Checkbox.svelte";
|
||||
export { default as Interpretation } from "./Interpretation.svelte";
|
||||
export const modes = ["static", "dynamic"];
|
||||
|
@ -6,8 +6,8 @@
|
||||
export let choices: Array<string>;
|
||||
|
||||
export let mode: "static" | "dynamic";
|
||||
export let theme: string;
|
||||
export let style: string | null;
|
||||
export let label: string;
|
||||
|
||||
if (default_value) value = default_value;
|
||||
</script>
|
||||
@ -15,8 +15,8 @@
|
||||
<CheckboxGroup
|
||||
bind:value
|
||||
{choices}
|
||||
{theme}
|
||||
{style}
|
||||
{label}
|
||||
on:change
|
||||
disabled={mode === "static"}
|
||||
/>
|
||||
|
@ -1,3 +1,2 @@
|
||||
export { default as Component } from "./CheckboxGroup.svelte";
|
||||
export { default as Interpretation } from "./Interpretation.svelte";
|
||||
export const modes = ["static", "dynamic"];
|
||||
|
@ -1,7 +1,10 @@
|
||||
<script lang="ts">
|
||||
export let value: boolean = true;
|
||||
export let style: string = "";
|
||||
|
||||
if ($$props.default) value = $$props.default;
|
||||
</script>
|
||||
|
||||
<div {style} class="flex flex-1 flex-col gap-4">
|
||||
<div {style} class:hidden={!value} class="flex flex-1 flex-col gap-4">
|
||||
<slot />
|
||||
</div>
|
||||
|
@ -1,3 +1,2 @@
|
||||
export { default as Component } from "./Dropdown.svelte";
|
||||
export { default as Interpretation } from "./Interpretation.svelte";
|
||||
export const modes = ["dynamic"];
|
||||
|
@ -1,5 +1,4 @@
|
||||
export { default as Component } from "./Image.svelte";
|
||||
export { default as ExampleComponent } from "../Dataset/ExampleComponents/Image.svelte";
|
||||
export { default as Interpretation } from "./Interpretation.svelte";
|
||||
export { loadAsData } from "../utils/example_processors";
|
||||
export const modes = ["static", "dynamic"];
|
||||
|
@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { component_map } from "./directory";
|
||||
|
||||
export let component: string;
|
||||
export let component_props: Record<string, any>;
|
||||
export let value: any;
|
||||
export let theme: string;
|
||||
</script>
|
||||
|
||||
{#if value}
|
||||
<svelte:component
|
||||
this={component_map[component]}
|
||||
{theme}
|
||||
{...component_props}
|
||||
original={value.original}
|
||||
interpretation={value.interpretation}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<style lang="postcss" global>
|
||||
</style>
|
@ -1,17 +1,10 @@
|
||||
<script lang="ts">
|
||||
import type { AudioData } from "@gradio/audio";
|
||||
|
||||
import { getSaliencyColor } from "../utils/helpers";
|
||||
export let value: AudioData;
|
||||
import { getSaliencyColor } from "../utils";
|
||||
export let interpretation: Array<number>;
|
||||
export let theme: string;
|
||||
export let style: string | null;
|
||||
</script>
|
||||
|
||||
<div class="input-audio" {theme} {style}>
|
||||
<audio class="w-full" controls>
|
||||
<source src={value.data} />
|
||||
</audio>
|
||||
<div class="input-audio" {theme}>
|
||||
<div class="interpret_range flex">
|
||||
{#each interpretation as interpret_value}
|
||||
<div
|
@ -1,16 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { getSaliencyColor } from "../utils/helpers";
|
||||
import { getSaliencyColor } from "../utils";
|
||||
|
||||
export let value: boolean;
|
||||
export let original: boolean;
|
||||
export let interpretation: [number, number];
|
||||
export let theme: string;
|
||||
export let style: string | null;
|
||||
</script>
|
||||
|
||||
<div class="input-checkbox inline-block" {theme} {style}>
|
||||
<div class="input-checkbox inline-block" {theme}>
|
||||
<button
|
||||
class="checkbox-item py-2 px-3 rounded cursor-pointer flex gap-1"
|
||||
class:selected={value}
|
||||
class:selected={original}
|
||||
>
|
||||
<div
|
||||
class="checkbox w-4 h-4 bg-white flex items-center justify-center border border-gray-400 box-border"
|
@ -1,19 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { getSaliencyColor } from "../utils/helpers";
|
||||
import { getSaliencyColor } from "../utils";
|
||||
|
||||
export let value: Array<string>;
|
||||
export let original: Array<string>;
|
||||
export let interpretation: Array<[number, number]>;
|
||||
export let choices: Array<string>;
|
||||
|
||||
export let theme: string;
|
||||
export let style: string | null;
|
||||
</script>
|
||||
|
||||
<div class="input-checkbox-group flex flex-wrap gap-2" {theme} {style}>
|
||||
<div class="input-checkbox-group flex flex-wrap gap-2" {theme}>
|
||||
{#each choices as choice, i}
|
||||
<button
|
||||
class="checkbox-item py-2 px-3 font-semibold rounded cursor-pointer flex items-center gap-1"
|
||||
class:selected={value.includes(choice)}
|
||||
class:selected={original.includes(choice)}
|
||||
>
|
||||
<div
|
||||
class="checkbox w-4 h-4 bg-white flex items-center justify-center border border-gray-400 box-border"
|
@ -1,14 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { getSaliencyColor } from "../utils/helpers";
|
||||
import { getSaliencyColor } from "../utils";
|
||||
|
||||
export let value: string;
|
||||
export let original: string;
|
||||
export let interpretation: Array<number>;
|
||||
export let theme: string;
|
||||
export let style: string | null;
|
||||
export let choices: Array<string>;
|
||||
</script>
|
||||
|
||||
<div class="input-dropdown" {theme} {style}>
|
||||
<div class="input-dropdown" {theme}>
|
||||
<ul class="dropdown-menu">
|
||||
{#each choices as choice, i}
|
||||
<li
|
@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { getObjectFitSize, getSaliencyColor } from "../utils/helpers";
|
||||
import { getSaliencyColor, getObjectFitSize } from "../utils";
|
||||
import { afterUpdate } from "svelte";
|
||||
|
||||
export let value: string;
|
||||
export let original: string;
|
||||
export let interpretation: Array<Array<number>>;
|
||||
export let shape: undefined | [number, number];
|
||||
|
||||
@ -70,7 +70,11 @@
|
||||
<canvas bind:this={saliency_layer} />
|
||||
</div>
|
||||
<!-- svelte-ignore a11y-missing-attribute -->
|
||||
<img class="w-full h-full object-contain" bind:this={image} src={value} />
|
||||
<img
|
||||
class="w-full h-full object-contain"
|
||||
bind:this={image}
|
||||
src={original}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,10 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { getSaliencyColor } from "../utils/helpers";
|
||||
import { getSaliencyColor } from "../utils";
|
||||
|
||||
export let value: string;
|
||||
export let original: string;
|
||||
export let interpretation: Array<[number, number]>;
|
||||
export let theme: string;
|
||||
export let style: string | null;
|
||||
</script>
|
||||
|
||||
<div class="input-number">
|
@ -1,18 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { getSaliencyColor } from "../utils/helpers";
|
||||
import { getSaliencyColor } from "../utils";
|
||||
|
||||
export let value: string;
|
||||
export let original: string;
|
||||
export let interpretation: Array<number>;
|
||||
export let theme: string;
|
||||
export let style: string | null;
|
||||
export let choices: Array<string>;
|
||||
</script>
|
||||
|
||||
<div class="input-radio flex flex-wrap gap-2" {theme} {style}>
|
||||
<div class="input-radio flex flex-wrap gap-2" {theme}>
|
||||
{#each choices as choice, i}
|
||||
<button
|
||||
class="radio-item py-2 px-3 font-semibold rounded cursor-pointer flex items-center gap-2"
|
||||
class:selected={value === choice}
|
||||
class:selected={original === choice}
|
||||
>
|
||||
<div
|
||||
class="radio-circle w-4 h-4 rounded-full box-border"
|
@ -1,21 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { getSaliencyColor } from "../utils/helpers";
|
||||
import { getSaliencyColor } from "../utils";
|
||||
|
||||
export let value: number;
|
||||
export let original: number;
|
||||
export let interpretation: Array<number>;
|
||||
export let theme: string;
|
||||
export let style: string | null;
|
||||
export let minimum: number;
|
||||
export let maximum: number;
|
||||
export let step: number;
|
||||
</script>
|
||||
|
||||
<div class="input-slider text-center" {theme} {style}>
|
||||
<div class="input-slider text-center" {theme}>
|
||||
<input
|
||||
type="range"
|
||||
class="range w-full appearance-none transition rounded h-4"
|
||||
disabled
|
||||
{value}
|
||||
{original}
|
||||
min={minimum}
|
||||
max={maximum}
|
||||
{step}
|
||||
@ -28,7 +27,9 @@
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
<div class="value inline-block mx-auto mt-1 px-2 py-0.5 rounded">{value}</div>
|
||||
<div class="original inline-block mx-auto mt-1 px-2 py-0.5 rounded">
|
||||
{original}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
@ -1,16 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { getSaliencyColor } from "../../utils/helpers";
|
||||
import { getSaliencyColor } from "../utils";
|
||||
|
||||
export let interpretation: Array<[string, number]>;
|
||||
export let theme: string;
|
||||
export let style: string | null;
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="input-text w-full rounded box-border p-2 break-word"
|
||||
{theme}
|
||||
{style}
|
||||
>
|
||||
<div class="input-text w-full rounded box-border p-2 break-word" {theme}>
|
||||
{#each interpretation as [text, saliency]}
|
||||
<span
|
||||
class="textspan p-1 bg-opacity-20 dark:bg-opacity-80"
|
21
ui/packages/app/src/components/Interpretation/directory.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import InterpretationNumber from "./InterpretationComponents/Number.svelte";
|
||||
import InterpretationDropdown from "./InterpretationComponents/Dropdown.svelte";
|
||||
import InterpretationCheckbox from "./InterpretationComponents/Checkbox.svelte";
|
||||
import InterpretationCheckboxGroup from "./InterpretationComponents/CheckboxGroup.svelte";
|
||||
import InterpretationSlider from "./InterpretationComponents/Slider.svelte";
|
||||
import InterpretationRadio from "./InterpretationComponents/Radio.svelte";
|
||||
import InterpretationImage from "./InterpretationComponents/Image.svelte";
|
||||
import InterpretationAudio from "./InterpretationComponents/Audio.svelte";
|
||||
import InterpretationTextbox from "./InterpretationComponents/Textbox.svelte";
|
||||
|
||||
export const component_map = {
|
||||
audio: InterpretationAudio,
|
||||
dropdown: InterpretationDropdown,
|
||||
checkbox: InterpretationCheckbox,
|
||||
checkboxgroup: InterpretationCheckboxGroup,
|
||||
number: InterpretationNumber,
|
||||
slider: InterpretationSlider,
|
||||
radio: InterpretationRadio,
|
||||
image: InterpretationImage,
|
||||
textbox: InterpretationTextbox
|
||||
};
|
2
ui/packages/app/src/components/Interpretation/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { default as Component } from "./Interpretation.svelte";
|
||||
export const modes = ["dynamic"];
|
59
ui/packages/app/src/components/Interpretation/utils.ts
Normal file
@ -0,0 +1,59 @@
|
||||
export const getSaliencyColor = (value: number): string => {
|
||||
var color: [number, number, number] | null = null;
|
||||
if (value < 0) {
|
||||
color = [52, 152, 219];
|
||||
} else {
|
||||
color = [231, 76, 60];
|
||||
}
|
||||
return colorToString(interpolate(Math.abs(value), [255, 255, 255], color));
|
||||
};
|
||||
|
||||
const interpolate = (
|
||||
val: number,
|
||||
rgb1: [number, number, number],
|
||||
rgb2: [number, number, number]
|
||||
): [number, number, number] => {
|
||||
if (val > 1) {
|
||||
val = 1;
|
||||
}
|
||||
val = Math.sqrt(val);
|
||||
var rgb: [number, number, number] = [0, 0, 0];
|
||||
var i;
|
||||
for (i = 0; i < 3; i++) {
|
||||
rgb[i] = Math.round(rgb1[i] * (1.0 - val) + rgb2[i] * val);
|
||||
}
|
||||
return rgb;
|
||||
};
|
||||
|
||||
const colorToString = (rgb: [number, number, number]): string => {
|
||||
return "rgb(" + rgb[0] + ", " + rgb[1] + ", " + rgb[2] + ")";
|
||||
};
|
||||
|
||||
export const getObjectFitSize = (
|
||||
contains: boolean /* true = contain, false = cover */,
|
||||
containerWidth: number,
|
||||
containerHeight: number,
|
||||
width: number,
|
||||
height: number
|
||||
) => {
|
||||
var doRatio = width / height;
|
||||
var cRatio = containerWidth / containerHeight;
|
||||
var targetWidth = 0;
|
||||
var targetHeight = 0;
|
||||
var test = contains ? doRatio > cRatio : doRatio < cRatio;
|
||||
|
||||
if (test) {
|
||||
targetWidth = containerWidth;
|
||||
targetHeight = targetWidth / doRatio;
|
||||
} else {
|
||||
targetHeight = containerHeight;
|
||||
targetWidth = targetHeight * doRatio;
|
||||
}
|
||||
|
||||
return {
|
||||
width: targetWidth,
|
||||
height: targetHeight,
|
||||
x: (containerWidth - targetWidth) / 2,
|
||||
y: (containerHeight - targetHeight) / 2
|
||||
};
|
||||
};
|
@ -21,6 +21,6 @@
|
||||
$: value, dispatch("change");
|
||||
</script>
|
||||
|
||||
{#if value !== undefined}
|
||||
{#if value !== undefined && value !== null}
|
||||
<Label {theme} {style} {value} />
|
||||
{/if}
|
||||
|
@ -1,10 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { Number } from "@gradio/form";
|
||||
|
||||
export let label: string;
|
||||
export let value: number = 0;
|
||||
export let default_value: number;
|
||||
|
||||
export let theme: string;
|
||||
export let style: string | null;
|
||||
|
||||
export let mode: "static" | "dynamic";
|
||||
|
||||
if (default_value) value = default_value;
|
||||
@ -12,7 +15,7 @@
|
||||
|
||||
<Number
|
||||
bind:value
|
||||
{theme}
|
||||
{label}
|
||||
{style}
|
||||
disabled={mode === "static"}
|
||||
on:change
|
||||
|
@ -1,3 +1,2 @@
|
||||
export { default as Component } from "./Number.svelte";
|
||||
export { default as Interpretation } from "./Interpretation.svelte";
|
||||
export const modes = ["static", "dynamic"];
|
||||
|
@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { Radio } from "@gradio/form";
|
||||
|
||||
export let label: string;
|
||||
export let value: string = "";
|
||||
export let default_value: string;
|
||||
export let theme: string;
|
||||
export let style: string | null;
|
||||
export let choices: Array<string> = [];
|
||||
export let mode: "static" | "dynamic";
|
||||
@ -13,7 +13,7 @@
|
||||
|
||||
<Radio
|
||||
bind:value
|
||||
{theme}
|
||||
{label}
|
||||
{style}
|
||||
{choices}
|
||||
disabled={mode === "static"}
|
||||
|
@ -1,3 +1,2 @@
|
||||
export { default as Component } from "./Radio.svelte";
|
||||
export { default as Interpretation } from "./Interpretation.svelte";
|
||||
export const modes = ["static", "dynamic"];
|
||||
|
@ -1,7 +1,10 @@
|
||||
<script lang="ts">
|
||||
export let value: boolean;
|
||||
export let style: string = "";
|
||||
|
||||
if ($$props.default_value) value = $$props.default_value;
|
||||
</script>
|
||||
|
||||
<div {style} class="flex flex-row gap-4">
|
||||
<div {style} class:hidden={!value} class="flex flex-row gap-4">
|
||||
<slot />
|
||||
</div>
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
export let value: number = 0;
|
||||
|
||||
export let label: string;
|
||||
export let default_value: number;
|
||||
export let theme: string;
|
||||
export let style: string | null;
|
||||
@ -16,6 +17,7 @@
|
||||
|
||||
<Range
|
||||
bind:value
|
||||
{label}
|
||||
{theme}
|
||||
{style}
|
||||
{minimum}
|
||||
|
@ -1,3 +1,2 @@
|
||||
export { default as Component } from "./Slider.svelte";
|
||||
export { default as Interpretation } from "./Interpretation.svelte";
|
||||
export const modes = ["static", "dynamic"];
|
||||
|
@ -3,9 +3,9 @@
|
||||
<script lang="ts">
|
||||
import { TextBox } from "@gradio/form";
|
||||
|
||||
export let label: string;
|
||||
export let value: string = " ";
|
||||
export let default_value: string;
|
||||
export let theme: string;
|
||||
export let style: string = "";
|
||||
export let lines: number;
|
||||
export let placeholder: string = "";
|
||||
@ -18,7 +18,6 @@
|
||||
{#if mode === "static"}
|
||||
<div
|
||||
class="output-text w-full bg-white dark:bg-gray-800 rounded box-border p-2 whitespace-pre-wrap"
|
||||
{theme}
|
||||
{style}
|
||||
>
|
||||
{value}
|
||||
@ -26,7 +25,7 @@
|
||||
{:else}
|
||||
<TextBox
|
||||
bind:value
|
||||
{theme}
|
||||
{label}
|
||||
{style}
|
||||
{lines}
|
||||
{placeholder}
|
||||
|
@ -1,4 +1,4 @@
|
||||
export const component_map = {
|
||||
export const component_map: Record<string, any> = {
|
||||
audio: () => import("./Audio"),
|
||||
button: () => import("./Button"),
|
||||
carousel: () => import("./Carousel"),
|
||||
@ -14,6 +14,7 @@ export const component_map = {
|
||||
highlightedtext: () => import("./HighlightedText"),
|
||||
html: () => import("./HTML"),
|
||||
image: () => import("./Image"),
|
||||
interpretation: () => import("./Interpretation"),
|
||||
json: () => import("./Json"),
|
||||
label: () => import("./Label"),
|
||||
number: () => import("./Number"),
|
||||
|
@ -58,63 +58,3 @@ export const prettyBytes = (bytes: number): string => {
|
||||
let unit = units[i];
|
||||
return bytes.toFixed(1) + " " + unit;
|
||||
};
|
||||
|
||||
export const getSaliencyColor = (value: number): string => {
|
||||
var color: [number, number, number] | null = null;
|
||||
if (value < 0) {
|
||||
color = [52, 152, 219];
|
||||
} else {
|
||||
color = [231, 76, 60];
|
||||
}
|
||||
return colorToString(interpolate(Math.abs(value), [255, 255, 255], color));
|
||||
};
|
||||
|
||||
const interpolate = (
|
||||
val: number,
|
||||
rgb1: [number, number, number],
|
||||
rgb2: [number, number, number]
|
||||
): [number, number, number] => {
|
||||
if (val > 1) {
|
||||
val = 1;
|
||||
}
|
||||
val = Math.sqrt(val);
|
||||
var rgb: [number, number, number] = [0, 0, 0];
|
||||
var i;
|
||||
for (i = 0; i < 3; i++) {
|
||||
rgb[i] = Math.round(rgb1[i] * (1.0 - val) + rgb2[i] * val);
|
||||
}
|
||||
return rgb;
|
||||
};
|
||||
|
||||
const colorToString = (rgb: [number, number, number]): string => {
|
||||
return "rgb(" + rgb[0] + ", " + rgb[1] + ", " + rgb[2] + ")";
|
||||
};
|
||||
|
||||
export const getObjectFitSize = (
|
||||
contains: boolean /* true = contain, false = cover */,
|
||||
containerWidth: number,
|
||||
containerHeight: number,
|
||||
width: number,
|
||||
height: number
|
||||
) => {
|
||||
var doRatio = width / height;
|
||||
var cRatio = containerWidth / containerHeight;
|
||||
var targetWidth = 0;
|
||||
var targetHeight = 0;
|
||||
var test = contains ? doRatio > cRatio : doRatio < cRatio;
|
||||
|
||||
if (test) {
|
||||
targetWidth = containerWidth;
|
||||
targetHeight = targetWidth / doRatio;
|
||||
} else {
|
||||
targetHeight = containerHeight;
|
||||
targetWidth = targetHeight * doRatio;
|
||||
}
|
||||
|
||||
return {
|
||||
width: targetWidth,
|
||||
height: targetHeight,
|
||||
x: (containerWidth - targetWidth) / 2,
|
||||
y: (containerHeight - targetHeight) / 2
|
||||
};
|
||||
};
|
||||
|
@ -3,6 +3,8 @@ import Blocks from "./Blocks.svelte";
|
||||
import Login from "./Login.svelte";
|
||||
import { fn } from "./api";
|
||||
|
||||
import * as t from "@gradio/theme";
|
||||
|
||||
interface CustomWindow extends Window {
|
||||
gradio_mode: "app" | "website";
|
||||
launchGradio: Function;
|
||||
@ -12,6 +14,7 @@ interface CustomWindow extends Window {
|
||||
|
||||
declare let window: CustomWindow;
|
||||
declare let BACKEND_URL: string;
|
||||
declare let BACKEND_URL_TEST: string;
|
||||
declare let BUILD_MODE: string;
|
||||
|
||||
interface Component {
|
||||
@ -20,6 +23,7 @@ interface Component {
|
||||
}
|
||||
|
||||
interface Config {
|
||||
auth_required: boolean | undefined;
|
||||
allow_flagging: string;
|
||||
allow_interpretation: boolean;
|
||||
allow_screenshot: boolean;
|
||||
@ -77,7 +81,7 @@ window.launchGradio = (config: Config, element_query: string) => {
|
||||
style.innerHTML = config.css;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
if (config.detail === "Not authenticated") {
|
||||
if (config.detail === "Not authenticated" || config.auth_required) {
|
||||
new Login({
|
||||
target: target,
|
||||
props: config
|
||||
|
@ -1,55 +0,0 @@
|
||||
<script>
|
||||
import TabSet from "./TabSet.svelte";
|
||||
// import { all_components_map } from "../components/directory";
|
||||
|
||||
export let component_id_map,
|
||||
children,
|
||||
dependencies,
|
||||
type,
|
||||
values,
|
||||
setValues,
|
||||
triggerTarget,
|
||||
theme,
|
||||
static_src;
|
||||
</script>
|
||||
|
||||
<div class="flex gap-4" class:flex-col={type !== "row"}>
|
||||
<!-- {#each children as child}
|
||||
{#if typeof child === "object"}
|
||||
{#if child.type === "tabset"}
|
||||
<TabSet
|
||||
{component_id_map}
|
||||
{...child}
|
||||
{values}
|
||||
{setValues}
|
||||
{triggerTarget}
|
||||
{static_src}
|
||||
{theme}
|
||||
/>
|
||||
{:else}
|
||||
<svelte:self
|
||||
{component_id_map}
|
||||
{...child}
|
||||
{values}
|
||||
{setValues}
|
||||
{triggerTarget}
|
||||
{static_src}
|
||||
{theme}
|
||||
/>
|
||||
{/if}
|
||||
{:else if !(component_id_map[child].type === "output" && values[child] === null)}
|
||||
<div class:flex-1={type === "row"} on:click={() => triggerTarget(child)}>
|
||||
<svelte:component
|
||||
this={all_components_map[component_id_map[child].type][
|
||||
component_id_map[child].props.name
|
||||
].component}
|
||||
value={values[child]}
|
||||
setValue={setValues.bind(this, child)}
|
||||
{...component_id_map[child].props}
|
||||
{static_src}
|
||||
{theme}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/each} -->
|
||||
</div>
|
@ -1,54 +0,0 @@
|
||||
<script>
|
||||
import Pane from "./Pane.svelte";
|
||||
export let component_id_map,
|
||||
children,
|
||||
type,
|
||||
values,
|
||||
setValues,
|
||||
triggerTarget,
|
||||
theme,
|
||||
static_src;
|
||||
|
||||
let selected_tab = 0;
|
||||
console.log("tabs", children);
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div class="flex">
|
||||
{#each children as child, i}
|
||||
{#if i === selected_tab}
|
||||
<button
|
||||
class="px-4 py-2 font-semibold border-2 border-b-0 rounded-t border-gray-200"
|
||||
>
|
||||
{child.name}
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
class="px-4 py-2 border-b-2 border-gray-200"
|
||||
on:click={() => {
|
||||
selected_tab = i;
|
||||
}}
|
||||
>
|
||||
{child.name}
|
||||
</button>
|
||||
{/if}
|
||||
{/each}
|
||||
<div class="flex-1 border-b-2 border-gray-200" />
|
||||
</div>
|
||||
{#each children as child, i}
|
||||
<div
|
||||
class="p-2 border-2 border-t-0 border-gray-200"
|
||||
class:hidden={i !== selected_tab}
|
||||
>
|
||||
<Pane
|
||||
{component_id_map}
|
||||
{...child}
|
||||
{values}
|
||||
{setValues}
|
||||
{triggerTarget}
|
||||
{static_src}
|
||||
{theme}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
@ -1,13 +1,22 @@
|
||||
const production = !process.env.ROLLUP_WATCH;
|
||||
module.exports = {
|
||||
content: ["./src/**/*.svelte"],
|
||||
content: [
|
||||
"./src/**/*.{html,js,svelte,ts}",
|
||||
"**/@gradio/**/*.{html,js,svelte,ts,css}"
|
||||
],
|
||||
|
||||
theme: {
|
||||
extend: {},
|
||||
fontFamily: {
|
||||
mono: ["monospace"],
|
||||
sans: ["IBM Plex Sans", "system-ui"]
|
||||
}
|
||||
},
|
||||
mode: "jit",
|
||||
darkMode: "class", // or 'media' or 'class'
|
||||
theme: {
|
||||
extend: {}
|
||||
},
|
||||
|
||||
variants: {
|
||||
extend: {}
|
||||
},
|
||||
plugins: []
|
||||
plugins: [require("@tailwindcss/forms")]
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { test, expect, Page } from "@playwright/test";
|
||||
|
||||
function mock_demo(page: Page, demo: string) {
|
||||
return page.route("http://localhost:7860/config", (route) => {
|
||||
return page.route("**/config", (route) => {
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
@ -12,7 +12,7 @@ function mock_demo(page: Page, demo: string) {
|
||||
}
|
||||
|
||||
function mock_api(page: Page, body: Array<unknown>) {
|
||||
return page.route("http://localhost:7860/api/predict/", (route) => {
|
||||
return page.route("**/api/predict/", (route) => {
|
||||
const id = JSON.parse(route.request().postData()!).fn_index;
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
@ -65,7 +65,7 @@ test("can run an api request and display the data", async ({ page }) => {
|
||||
|
||||
await Promise.all([
|
||||
run_button.click(),
|
||||
page.waitForResponse("http://localhost:7860/api/predict/")
|
||||
page.waitForResponse("**/api/predict/")
|
||||
]);
|
||||
|
||||
const json = await page.locator(".output-json");
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { test, expect, Page } from "@playwright/test";
|
||||
|
||||
function mock_demo(page: Page, demo: string) {
|
||||
return page.route("http://localhost:7860/config", (route) => {
|
||||
return page.route("**/config", (route) => {
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
@ -12,7 +12,7 @@ function mock_demo(page: Page, demo: string) {
|
||||
}
|
||||
|
||||
function mock_api(page: Page, body: Array<unknown>) {
|
||||
return page.route("http://localhost:7860/api/predict/", (route) => {
|
||||
return page.route("**/api/predict/", (route) => {
|
||||
const id = JSON.parse(route.request().postData()!).fn_index;
|
||||
return route.fulfill({
|
||||
headers: {
|
||||
@ -35,10 +35,7 @@ test("a component acts as both input and output", async ({ page }) => {
|
||||
|
||||
await textbox.fill("hello world");
|
||||
|
||||
await Promise.all([
|
||||
button.click(),
|
||||
page.waitForResponse("http://localhost:7860/api/predict/")
|
||||
]);
|
||||
await Promise.all([button.click(), page.waitForResponse("**/api/predict/")]);
|
||||
|
||||
await expect(await page.inputValue(".input-text")).toEqual("world hello");
|
||||
});
|
||||
|
@ -17,7 +17,9 @@ export default defineConfig(({ mode }) => {
|
||||
},
|
||||
define: {
|
||||
BUILD_MODE: production ? JSON.stringify("prod") : JSON.stringify("dev"),
|
||||
BACKEND_URL: JSON.stringify("http://localhost:7860/")
|
||||
BACKEND_URL: production
|
||||
? JSON.stringify("")
|
||||
: JSON.stringify("http://localhost:7860/")
|
||||
},
|
||||
css: {
|
||||
postcss: {
|
||||
|
11
ui/packages/atoms/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# `@gradio/button`
|
||||
|
||||
```html
|
||||
<script>
|
||||
import { Button } from "@gradio/button";
|
||||
</script>
|
||||
|
||||
<button type="primary|secondary" href="string" on:click="{e.detail === href}">
|
||||
content
|
||||
</button>
|
||||
```
|
10
ui/packages/atoms/package.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "@gradio/atoms",
|
||||
"version": "0.0.1",
|
||||
"description": "Gradio UI packages",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"private": true
|
||||
}
|
19
ui/packages/atoms/src/Block.svelte
Normal file
@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
export let variant: "solid" | "dashed" = "solid";
|
||||
export let color: "grey" | "green" = "grey";
|
||||
export let padding: boolean = true;
|
||||
|
||||
const styles = {
|
||||
dashed: "border-dashed border-2",
|
||||
solid: "border-solid border",
|
||||
grey: "border-gray-200",
|
||||
green: "border-green-400"
|
||||
};
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="gr-box overflow-hidden {styles[variant]} {styles[color]}"
|
||||
class:gr-panel={padding}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
29
ui/packages/atoms/src/BlockLabel.svelte
Normal file
@ -0,0 +1,29 @@
|
||||
<script lang="ts">
|
||||
// import video_icon from "./video.svg";
|
||||
// import audio_icon from "./music.svg";
|
||||
// import file_icon from "./file.svg";
|
||||
// import csv_icon from "./table.svg";
|
||||
|
||||
export let label: string | null = null;
|
||||
export let image: string | undefined = undefined;
|
||||
|
||||
// const type_labels = {
|
||||
// "audio/*": { text: "Audio", icon: audio_icon },
|
||||
// file: { text: "File", icon: file_icon },
|
||||
// "image/x-png,image/gif,image/jpeg": { text: "Image", icon: image_icon },
|
||||
// "text/csv": { text: "CSV", icon: csv_icon },
|
||||
// "video/mp4,video/x-m4v,video/*": { text: "Video", icon: video_icon }
|
||||
// } as const;
|
||||
|
||||
// if (!label && label !== "" && filetype) {
|
||||
// label = type_labels[filetype as keyof typeof type_labels].text;
|
||||
// }
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="absolute left-0 top-0 py-1 px-2 rounded-br-lg shadow-sm text-xs text-gray-500 flex items-center pointer-events-none bg-white z-20"
|
||||
>
|
||||
<img src={image} alt="" class="mr-2 h-[12px] w-[12px] opacity-50" />
|
||||
|
||||
{label}
|
||||
</div>
|
3
ui/packages/atoms/src/BlockTitle.svelte
Normal file
@ -0,0 +1,3 @@
|
||||
<span class="text-gray-600 text-sm mb-2 block">
|
||||
<slot />
|
||||
</span>
|
11
ui/packages/atoms/src/IconButton.svelte
Normal file
@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
export let image: string;
|
||||
export let alt = "";
|
||||
</script>
|
||||
|
||||
<button
|
||||
on:click
|
||||
class="text-gray-500 bg-white/90 h-5 w-5 flex items-center justify-center rounded shadow-sm hover:shadow-xl hover:ring-1 ring-inset ring-gray-200 z-10"
|
||||
>
|
||||
<img src={image} {alt} class="m-t-1 w-[60%] h-[60%] opacity-60" />
|
||||
</button>
|
5
ui/packages/atoms/src/Panel.svelte
Normal file
@ -0,0 +1,5 @@
|
||||
<div
|
||||
class="m-12 flex flex-col space-y-4 bg-gray-50 p-4 rounded-xl border border-gray-200/60"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
67
ui/packages/atoms/src/icons/clear.svg
Normal file
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="5.9403949mm"
|
||||
height="5.9403949mm"
|
||||
viewBox="0 0 5.9403949 5.9403949"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
sodipodi:docname="clear.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:zoom="10.925474"
|
||||
inkscape:cx="4.1188143"
|
||||
inkscape:cy="15.559965"
|
||||
inkscape:window-width="1248"
|
||||
inkscape:window-height="770"
|
||||
inkscape:window-x="-6"
|
||||
inkscape:window-y="-6"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-115.10942,-119.22353)">
|
||||
<g
|
||||
id="g239"
|
||||
transform="matrix(0.05138986,0.05138986,-0.05138986,0.05138986,117.0869,112.75317)">
|
||||
<rect
|
||||
style="fill:#000000;stroke-width:0.295287"
|
||||
id="rect31"
|
||||
width="20"
|
||||
height="80"
|
||||
x="-111.51107"
|
||||
y="42.193726"
|
||||
rx="2.9434128"
|
||||
ry="2.6448057"
|
||||
transform="scale(-1,1)" />
|
||||
<rect
|
||||
style="fill:#000000;stroke-width:0.295287"
|
||||
id="rect31-3"
|
||||
width="20"
|
||||
height="80"
|
||||
x="-92.193726"
|
||||
y="-141.51106"
|
||||
rx="2.9434128"
|
||||
ry="2.6448057"
|
||||
transform="matrix(0,-1,-1,0,0,0)" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
39
ui/packages/atoms/src/icons/edit.svg
Normal file
@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
fill="#000000"
|
||||
viewBox="0 0 24 24"
|
||||
width="24px"
|
||||
height="24px"
|
||||
version="1.1"
|
||||
id="svg4"
|
||||
sodipodi:docname="edit.svg"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs8" />
|
||||
<sodipodi:namedview
|
||||
id="namedview6"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="11.291667"
|
||||
inkscape:cx="10.538745"
|
||||
inkscape:cy="16.383764"
|
||||
inkscape:window-width="1248"
|
||||
inkscape:window-height="770"
|
||||
inkscape:window-x="-6"
|
||||
inkscape:window-y="-6"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg4" />
|
||||
<path
|
||||
d="m 19.701578,1.2915129 c -0.814834,0 -1.629669,0.307743 -2.251701,0.9246243 l -1.319356,1.3084307 4.503402,4.46611 1.319356,-1.3084308 c 1.242939,-1.2326462 1.242939,-3.232347 0,-4.4661099 C 21.331247,1.5992559 20.516413,1.2915129 19.701578,1.2915129 Z M 14.441745,5.1993591 1.494465,18.039425 v 4.46611 H 5.997867 L 18.945148,9.665469 Z"
|
||||
id="path2"
|
||||
style="stroke-width:1.12118" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
1
ui/packages/atoms/src/icons/file.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline></svg>
|
After Width: | Height: | Size: 339 B |
5
ui/packages/atoms/src/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export { default as Panel } from "./Panel.svelte";
|
||||
export { default as Block } from "./Block.svelte";
|
||||
export { default as BlockTitle } from "./BlockTitle.svelte";
|
||||
export { default as BlockLabel } from "./BlockLabel.svelte";
|
||||
export { default as IconButton } from "./IconButton.svelte";
|
@ -8,6 +8,7 @@
|
||||
"license": "ISC",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@gradio/atoms": "workspace:^0.0.1",
|
||||
"@gradio/upload": "workspace:^0.0.1",
|
||||
"svelte-range-slider-pips": "^2.0.1"
|
||||
}
|
||||
|
@ -9,11 +9,13 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy, createEventDispatcher } from "svelte";
|
||||
import { Upload, ModifyUpload } from "@gradio/upload";
|
||||
import { Block, BlockLabel } from "@gradio/atoms";
|
||||
//@ts-ignore
|
||||
import Range from "svelte-range-slider-pips";
|
||||
|
||||
import audio_icon from "./music.svg";
|
||||
|
||||
export let value: null | { name: string; data: string } = null;
|
||||
export let theme: string;
|
||||
export let style: string | null;
|
||||
export let name: string;
|
||||
export let source: "microphone" | "upload" | "none";
|
||||
@ -143,28 +145,35 @@
|
||||
value = detail;
|
||||
dispatch("change", { data: detail.data, name: detail.name });
|
||||
}
|
||||
|
||||
export let dragging = false;
|
||||
</script>
|
||||
|
||||
<div class="input-audio">
|
||||
<Block
|
||||
variant={value === null && source === "upload" ? "dashed" : "solid"}
|
||||
color={dragging ? "green" : "grey"}
|
||||
padding={false}
|
||||
>
|
||||
<BlockLabel image={audio_icon} label={"Audio"} />
|
||||
{#if value === null}
|
||||
{#if source === "microphone"}
|
||||
{#if recording}
|
||||
<button
|
||||
class="p-2 rounded font-semibold bg-red-200 text-red-500 dark:bg-red-600 dark:text-red-100 shadow transition hover:shadow-md"
|
||||
class="ml-2 mt-8 mb-2 p-2 rounded font-semibold bg-red-200 text-red-500 dark:bg-red-600 dark:text-red-100 shadow transition hover:shadow-md"
|
||||
on:click={stop}
|
||||
>
|
||||
Stop Recording
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
class="p-2 rounded font-semibold shadow transition hover:shadow-md bg-white dark:bg-gray-800"
|
||||
class="ml-2 mt-8 mb-2 p-2 rounded font-semibold shadow transition hover:shadow-md bg-white dark:bg-gray-800"
|
||||
on:click={record}
|
||||
>
|
||||
Record
|
||||
</button>
|
||||
{/if}
|
||||
{:else if source === "upload"}
|
||||
<Upload filetype="audio/*" on:load={handle_load} {theme}>
|
||||
<Upload filetype="audio/*" on:load={handle_load} bind:dragging>
|
||||
{drop_text}
|
||||
<br />- {or_text} -<br />
|
||||
{upload_text}
|
||||
@ -176,7 +185,6 @@
|
||||
on:edit={() => (mode = "edit")}
|
||||
editable
|
||||
absolute={false}
|
||||
{theme}
|
||||
/>
|
||||
|
||||
<audio
|
||||
@ -202,4 +210,4 @@
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</Block>
|
||||
|
1
ui/packages/audio/src/music.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="feather feather-music"><path d="M9 18V5l12-2v13"></path><circle cx="6" cy="18" r="3"></circle><circle cx="18" cy="16" r="3"></circle></svg>
|
After Width: | Height: | Size: 329 B |
@ -8,6 +8,7 @@
|
||||
"license": "ISC",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@gradio/atoms": "workspace:^0.0.1",
|
||||
"@gradio/upload": "workspace:^0.0.1"
|
||||
}
|
||||
}
|
||||
|
@ -2,14 +2,17 @@
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { Upload, ModifyUpload } from "@gradio/upload";
|
||||
import type { FileData } from "@gradio/upload";
|
||||
import { Block, BlockLabel } from "@gradio/atoms";
|
||||
|
||||
import { prettyBytes } from "./utils";
|
||||
import file_icon from "./file.svg";
|
||||
|
||||
export let value: null | FileData;
|
||||
|
||||
export let theme: string = "default";
|
||||
export let drop_text: string = "Drop an audio file";
|
||||
export let drop_text: string = "Drop a file file";
|
||||
export let or_text: string = "or";
|
||||
export let upload_text: string = "click to upload";
|
||||
export let label: string = "";
|
||||
let file_count: string;
|
||||
|
||||
function handle_upload({ detail }: CustomEvent<FileData>) {
|
||||
@ -25,11 +28,18 @@
|
||||
createEventDispatcher<{ change: FileData | null; clear: undefined }>();
|
||||
|
||||
$: dispatch("change", value);
|
||||
let dragging = false;
|
||||
</script>
|
||||
|
||||
<div class="input-file" {theme}>
|
||||
<Block
|
||||
variant={value === null ? "dashed" : "solid"}
|
||||
color={dragging ? "green" : "grey"}
|
||||
padding={false}
|
||||
>
|
||||
<BlockLabel image={file_icon} label={label || "File"} />
|
||||
|
||||
{#if value === null}
|
||||
<Upload on:load={handle_upload} {theme}>
|
||||
<Upload on:load={handle_upload} filetype="file" bind:dragging>
|
||||
{drop_text}
|
||||
<br />- {or_text} -<br />
|
||||
{upload_text}
|
||||
@ -38,7 +48,7 @@
|
||||
<div
|
||||
class="file-preview w-full flex flex-row flex-wrap justify-center items-center relative overflow-y-auto"
|
||||
>
|
||||
<ModifyUpload on:clear={handle_clear} {theme} />
|
||||
<ModifyUpload on:clear={handle_clear} />
|
||||
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -65,10 +75,4 @@
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.input-file[theme="default"] .file-preview {
|
||||
@apply h-60;
|
||||
}
|
||||
</style>
|
||||
</Block>
|
||||
|
1
ui/packages/file/src/file.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-file"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline></svg>
|
After Width: | Height: | Size: 337 B |
@ -6,5 +6,8 @@
|
||||
"main": "src/index.ts",
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"private": true
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@gradio/atoms": "workspace:^0.0.1"
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
import { BlockTitle, Block } from "@gradio/atoms";
|
||||
|
||||
export let value: boolean;
|
||||
export let disabled: boolean = false;
|
||||
export let theme: string = "default";
|
||||
export let label: string;
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: boolean }>();
|
||||
|
||||
@ -13,52 +15,16 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="input-checkbox inline-block" {theme} on:click={handle_change}>
|
||||
<button
|
||||
class="checkbox-item py-2 px-3 rounded cursor-pointer"
|
||||
class:selected={value}
|
||||
<Block>
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<BlockTitle>{label}</BlockTitle>
|
||||
<label class="w-auto gr-box-sm gr-box inline-block">
|
||||
<input
|
||||
{disabled}
|
||||
type="checkbox"
|
||||
name="test"
|
||||
class="gr-check-radio rounded checked:shadow-inner"
|
||||
/>
|
||||
<span class="ml-2">{label}</span></label
|
||||
>
|
||||
<div class="checkbox w-4 h-4 bg-white flex items-center justify-center">
|
||||
<svg class="check opacity-0 h-3 w-4" viewBox="-10 -10 20 20">
|
||||
<line
|
||||
x1="-7.5"
|
||||
y1="0"
|
||||
x2="-2.5"
|
||||
y2="5"
|
||||
stroke="white"
|
||||
stroke-width="4"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<line
|
||||
x1="-2.5"
|
||||
y1="5"
|
||||
x2="7.5"
|
||||
y2="-7.5"
|
||||
stroke="white"
|
||||
stroke-width="4"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.selected .check {
|
||||
@apply opacity-100;
|
||||
}
|
||||
.input-checkbox[theme="default"] {
|
||||
.checkbox-item {
|
||||
@apply bg-white dark:bg-gray-800 shadow transition hover:shadow-md;
|
||||
}
|
||||
.checkbox {
|
||||
@apply bg-gray-100 dark:bg-gray-400 transition;
|
||||
}
|
||||
.checkbox-item.selected {
|
||||
@apply bg-amber-500 dark:bg-red-600 text-white;
|
||||
}
|
||||
.selected .checkbox {
|
||||
@apply bg-amber-600 dark:bg-red-700;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</Block>
|
||||
|
@ -1,10 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { BlockTitle } from "@gradio/atoms";
|
||||
|
||||
export let value: Array<string> = [];
|
||||
export let choices: Array<string>;
|
||||
export let theme: string = "default";
|
||||
export let disabled: boolean = false;
|
||||
export let label: string;
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: Array<string> }>();
|
||||
|
||||
@ -19,39 +20,25 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="input-checkbox-group flex flex-wrap gap-2" {theme}>
|
||||
{#each choices as choice, i}
|
||||
<button
|
||||
class="checkbox-item py-2 px-3 font-semibold rounded cursor-pointer flex items-center gap-2"
|
||||
class:selected={value.includes(choice)}
|
||||
on:click={() => toggleChoice(choice)}
|
||||
>
|
||||
<div class="checkbox w-4 h-4 bg-white flex items-center justify-center">
|
||||
<svg class="check opacity-0 h-3 w-4" viewBox="-10 -10 20 20">
|
||||
<line
|
||||
x1="-7.5"
|
||||
y1="0"
|
||||
x2="-2.5"
|
||||
y2="5"
|
||||
stroke="white"
|
||||
stroke-width="4"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
<line
|
||||
x1="-2.5"
|
||||
y1="5"
|
||||
x2="7.5"
|
||||
y2="-7.5"
|
||||
stroke="white"
|
||||
stroke-width="4"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
{choice}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<fieldset
|
||||
class="gr-box overflow-hidden border-solid border border-gray-200 gr-panel"
|
||||
>
|
||||
<BlockTitle>{label}</BlockTitle>
|
||||
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#each choices as choice, i}
|
||||
<label class="gr-box gr-box-sm ">
|
||||
<input
|
||||
{disabled}
|
||||
on:change={() => toggleChoice(choice)}
|
||||
type="checkbox"
|
||||
name="test"
|
||||
class="gr-check-radio rounded checked:shadow-inner"
|
||||
/> <span class="ml-2">{choice}</span></label
|
||||
>
|
||||
{/each}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<style lang="postcss">
|
||||
.selected .check {
|
||||
|
@ -1,55 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { Block, BlockTitle } from "@gradio/atoms";
|
||||
|
||||
export let label: string;
|
||||
export let value: string | undefined = undefined;
|
||||
export let theme: string = "default";
|
||||
export let choices: Array<string>;
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: string }>();
|
||||
|
||||
function handle_change(choice: string) {
|
||||
dispatch("change", choice);
|
||||
value = choice;
|
||||
}
|
||||
$: dispatch("change", value);
|
||||
</script>
|
||||
|
||||
<div class="input-dropdown group inline-block relative" {theme}>
|
||||
<button
|
||||
class="selector py-2 px-3 font-semibold rounded inline-flex items-center"
|
||||
>
|
||||
{value || label || choices[0]}
|
||||
<svg class="caret ml-2 fill-current h-4 w-4" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
class="dropdown-menu-holder absolute hidden group-hover:block pt-1 z-10 bg-none"
|
||||
>
|
||||
<ul class="dropdown-menu max-h-80 overflow-y-auto">
|
||||
<Block>
|
||||
<label>
|
||||
<BlockTitle>{label}</BlockTitle>
|
||||
<select class="gr-box gr-input w-full" bind:value>
|
||||
{#each choices as choice, i}
|
||||
<li
|
||||
class="dropdown-item first:rounded-t transition last:rounded-b py-2 px-3 block whitespace-nowrap cursor-pointer"
|
||||
on:click={() => handle_change(choice)}
|
||||
>
|
||||
{choice}
|
||||
</li>
|
||||
<option>{choice}</option>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="postcss" global>
|
||||
.input-dropdown[theme="default"] {
|
||||
.selector {
|
||||
@apply bg-white dark:bg-gray-800 shadow transition hover:shadow-md;
|
||||
}
|
||||
.dropdown-menu {
|
||||
@apply shadow;
|
||||
}
|
||||
.dropdown-item {
|
||||
@apply bg-white dark:bg-gray-800 hover:bg-amber-500 dark:hover:bg-red-600 hover:text-gray-50 hover:font-semibold;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</select>
|
||||
</label>
|
||||
</Block>
|
||||
|
@ -1,10 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { debounce } from "./utils";
|
||||
import { BlockTitle, Block } from "@gradio/atoms";
|
||||
|
||||
export let value: number = 0;
|
||||
export let theme: string = "default";
|
||||
export let disabled: boolean = false;
|
||||
export let label: string;
|
||||
|
||||
const dispatch =
|
||||
createEventDispatcher<{ change: number; submit: undefined }>();
|
||||
@ -26,24 +27,16 @@
|
||||
$: debounced_handle_change(value);
|
||||
</script>
|
||||
|
||||
<input
|
||||
type="number"
|
||||
class="input-number w-full rounded box-border p-2 focus:outline-none appearance-none"
|
||||
bind:value
|
||||
{theme}
|
||||
on:keypress={debounced_handle_keypress}
|
||||
/>
|
||||
|
||||
<style lang="postcss" global>
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
.input-number[theme="default"] {
|
||||
@apply shadow transition hover:shadow-md dark:bg-gray-800;
|
||||
}
|
||||
</style>
|
||||
<Block>
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<label class="block">
|
||||
<BlockTitle>{label}</BlockTitle>
|
||||
<input
|
||||
type="number"
|
||||
class="gr-box gr-input w-full gr-text-input"
|
||||
bind:value
|
||||
on:keypress={debounced_handle_keypress}
|
||||
{disabled}
|
||||
/>
|
||||
</label>
|
||||
</Block>
|
||||
|
@ -1,48 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { BlockTitle } from "@gradio/atoms";
|
||||
|
||||
export let value: string;
|
||||
export let theme: string = "default";
|
||||
export let choices: Array<string>;
|
||||
export let disabled: boolean = false;
|
||||
export let label: string;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
function handle_change(choice: string) {
|
||||
dispatch("change", choice);
|
||||
value = choice;
|
||||
}
|
||||
$: dispatch("change", value);
|
||||
</script>
|
||||
|
||||
<div class="input-radio flex flex-wrap gap-2" {theme}>
|
||||
{#each choices as choice}
|
||||
<button
|
||||
class="radio-item py-2 px-3 font-semibold rounded cursor-pointer flex items-center gap-2"
|
||||
class:selected={value === choice}
|
||||
on:click={() => handle_change(choice)}
|
||||
>
|
||||
<div class="radio-circle w-4 h-4 rounded-full box-border" />
|
||||
{choice}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
<fieldset
|
||||
class="gr-box overflow-hidden border-solid border border-gray-200 gr-panel"
|
||||
>
|
||||
<BlockTitle>{label}</BlockTitle>
|
||||
|
||||
<style lang="postcss">
|
||||
.input-radio[theme="default"] {
|
||||
.radio-item {
|
||||
@apply bg-white dark:bg-gray-800 shadow transition hover:shadow-md;
|
||||
}
|
||||
.radio-circle {
|
||||
@apply bg-gray-50 dark:bg-gray-400 border-4 border-gray-200 dark:border-gray-600;
|
||||
}
|
||||
.radio-item.selected {
|
||||
@apply bg-amber-500 dark:bg-red-600 text-white shadow;
|
||||
}
|
||||
.radio-circle {
|
||||
@apply w-4 h-4 bg-white transition rounded-full box-border;
|
||||
}
|
||||
.selected .radio-circle {
|
||||
@apply border-amber-600 dark:border-red-700;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#each choices as choice, i}
|
||||
<label class="gr-box gr-box-sm ">
|
||||
<input
|
||||
{disabled}
|
||||
bind:group={value}
|
||||
type="radio"
|
||||
name="test"
|
||||
class="rounded-full gr-check-radio "
|
||||
value={choice}
|
||||
/> <span class="ml-2">{choice}</span></label
|
||||
>
|
||||
{/each}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
@ -1,51 +1,45 @@
|
||||
<script context="module">
|
||||
let _id = 0;
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
export let value: number;
|
||||
export let theme: string;
|
||||
import { Block, BlockTitle } from "@gradio/atoms";
|
||||
|
||||
export let value: number = 0;
|
||||
export let style: string | null;
|
||||
export let minimum: number;
|
||||
export let maximum: number;
|
||||
export let step: number;
|
||||
export let minimum: number = 0;
|
||||
export let maximum: number = 100;
|
||||
export let step: number = 1;
|
||||
export let disabled: boolean = false;
|
||||
export let label: string;
|
||||
|
||||
const id = `range_id_${_id++}`;
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: number }>();
|
||||
|
||||
$: dispatch("change", value);
|
||||
</script>
|
||||
|
||||
<div class="input-slider text-center" {theme}>
|
||||
<Block>
|
||||
<div class="w-full flex flex-col">
|
||||
<div class="flex justify-between">
|
||||
<label for={id}>
|
||||
<BlockTitle>{label}</BlockTitle>
|
||||
</label>
|
||||
<div class="font-medium">{value}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="range"
|
||||
class="range w-full appearance-none transition rounded h-4"
|
||||
{id}
|
||||
name="cowbell"
|
||||
class="w-full"
|
||||
bind:value
|
||||
min={minimum}
|
||||
max={maximum}
|
||||
{step}
|
||||
{disabled}
|
||||
/>
|
||||
<div class="value inline-block mx-auto mt-1 px-2 py-0.5 rounded">{value}</div>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.range::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
@apply appearance-none w-5 h-5 rounded cursor-pointer;
|
||||
}
|
||||
.range::-moz-range-thumb {
|
||||
@apply appearance-none w-5 h-5 rounded cursor-pointer;
|
||||
}
|
||||
|
||||
.input-slider[theme="default"] {
|
||||
.range {
|
||||
@apply bg-white dark:bg-gray-800 shadow h-3 transition hover:shadow-md;
|
||||
}
|
||||
.range::-webkit-slider-thumb {
|
||||
@apply bg-gradient-to-b from-amber-400 to-amber-500 dark:from-red-500 dark:to-red-600 shadow;
|
||||
}
|
||||
.range::-moz-range-thumb {
|
||||
@apply bg-gradient-to-b from-amber-400 to-amber-500 shadow;
|
||||
}
|
||||
.value {
|
||||
@apply bg-gray-100 dark:bg-gray-600 font-semibold;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</Block>
|
||||
|
@ -1,12 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { debounce } from "./utils";
|
||||
import { BlockTitle, Block } from "@gradio/atoms";
|
||||
|
||||
export let value: string = "";
|
||||
export let theme: string = "default";
|
||||
export let lines: number = 1;
|
||||
export let placeholder: string = "";
|
||||
export let style = "";
|
||||
export let label: string;
|
||||
|
||||
const dispatch =
|
||||
createEventDispatcher<{ change: string; submit: undefined }>();
|
||||
@ -35,30 +37,32 @@
|
||||
const debounced_handle_keypress = debounce(handle_keypress, 300);
|
||||
</script>
|
||||
|
||||
{#if lines > 1}
|
||||
<textarea
|
||||
class="input-text w-full rounded box-border p-2 focus:outline-none appearance-none"
|
||||
{value}
|
||||
{placeholder}
|
||||
on:input={debounced_handle_change}
|
||||
{theme}
|
||||
{style}
|
||||
/>
|
||||
{:else}
|
||||
<input
|
||||
type="text"
|
||||
class="input-text w-full rounded box-border p-2 focus:outline-none appearance-none"
|
||||
{value}
|
||||
{placeholder}
|
||||
on:input={debounced_handle_change}
|
||||
{theme}
|
||||
on:keypress={debounced_handle_keypress}
|
||||
{style}
|
||||
/>
|
||||
{/if}
|
||||
<Block>
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<label class="block">
|
||||
<BlockTitle>{label}</BlockTitle>
|
||||
|
||||
<style lang="postcss" global>
|
||||
.input-text[theme="default"] {
|
||||
@apply shadow transition hover:shadow-md dark:bg-gray-800;
|
||||
}
|
||||
</style>
|
||||
{#if lines > 1}
|
||||
<textarea
|
||||
class="block gr-box gr-input w-full gr-text-input"
|
||||
{value}
|
||||
{placeholder}
|
||||
on:input={debounced_handle_change}
|
||||
{theme}
|
||||
{style}
|
||||
rows={lines}
|
||||
/>
|
||||
{:else}
|
||||
<input
|
||||
type="text"
|
||||
class="gr-box gr-input w-full gr-text-input"
|
||||
{value}
|
||||
{placeholder}
|
||||
on:input={debounced_handle_change}
|
||||
{theme}
|
||||
on:keypress={debounced_handle_keypress}
|
||||
{style}
|
||||
/>
|
||||
{/if}
|
||||
</label>
|
||||
</Block>
|
||||
|
@ -8,6 +8,7 @@
|
||||
"license": "ISC",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@gradio/atoms": "workspace:^0.0.1",
|
||||
"@gradio/upload": "workspace:^0.0.1",
|
||||
"cropperjs": "^1.5.12",
|
||||
"lazy-brush": "^1.0.1",
|
||||
|
@ -1,5 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { Block, BlockLabel } from "@gradio/atoms";
|
||||
import image_icon from "./image.svg";
|
||||
import sketch_icon from "./sketch.svg";
|
||||
|
||||
import Cropper from "./Cropper.svelte";
|
||||
import Sketch from "./Sketch.svelte";
|
||||
import Webcam from "./Webcam.svelte";
|
||||
@ -9,8 +13,8 @@
|
||||
import { Upload, ModifyUpload } from "@gradio/upload";
|
||||
|
||||
export let value: null | string;
|
||||
export let label: string;
|
||||
|
||||
export let theme: string = "default";
|
||||
export let source: "canvas" | "webcam" | "upload" = "upload";
|
||||
export let tool: "editor" | "select" = "editor";
|
||||
|
||||
@ -43,14 +47,21 @@
|
||||
}>();
|
||||
|
||||
$: dispatch("change", value);
|
||||
|
||||
let dragging = false;
|
||||
</script>
|
||||
|
||||
<div class="input-image w-full">
|
||||
<div
|
||||
class="image-preview w-full h-60 flex justify-center items-center dark:bg-gray-600 relative"
|
||||
class:bg-gray-200={value}
|
||||
class:h-60={source !== "webcam"}
|
||||
>
|
||||
<Block
|
||||
variant={value === null && source === "upload" ? "dashed" : "solid"}
|
||||
color={dragging ? "green" : "grey"}
|
||||
padding={false}
|
||||
>
|
||||
<BlockLabel
|
||||
image={source === "canvas" ? sketch_icon : image_icon}
|
||||
label={label || source === "canvas" ? "Sketch" : "Image"}
|
||||
/>
|
||||
|
||||
<div class:bg-gray-200={value} class:h-60={source !== "webcam"}>
|
||||
{#if source === "canvas"}
|
||||
<ModifySketch
|
||||
on:undo={() => sketch.undo()}
|
||||
@ -60,10 +71,10 @@
|
||||
{:else if value === null}
|
||||
{#if source === "upload"}
|
||||
<Upload
|
||||
bind:dragging
|
||||
filetype="image/x-png,image/gif,image/jpeg"
|
||||
on:load={handle_upload}
|
||||
include_file_metadata={false}
|
||||
{theme}
|
||||
>
|
||||
{drop_text}
|
||||
<br />- {or_text} -<br />
|
||||
@ -86,13 +97,12 @@
|
||||
on:edit={() => (mode = "edit")}
|
||||
on:clear={handle_clear}
|
||||
editable
|
||||
{theme}
|
||||
/>
|
||||
|
||||
<img class="w-full h-full object-contain" src={value} alt="" />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</Block>
|
||||
|
||||
<style lang="postcss">
|
||||
:global(.image_editor_buttons) {
|
||||
|
@ -1,23 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { IconButton } from "@gradio/atoms";
|
||||
|
||||
import undo_solid from "./undo-solid.svg";
|
||||
import undo from "./undo.svg";
|
||||
import clear from "./clear.svg";
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
</script>
|
||||
|
||||
<div class="z-50 top-0 right-0 justify-end m-1 flex gap-1 absolute">
|
||||
<button
|
||||
class="bg-opacity-30 hover:bg-opacity-100 transition p-1.5 bg-amber-500 dark:bg-red-600 rounded shadow w-8 h-8"
|
||||
<div class="z-50 top-2 right-2 justify-end flex gap-1 absolute">
|
||||
<IconButton
|
||||
image={undo}
|
||||
alt="undo sketch"
|
||||
on:click={() => dispatch("undo")}
|
||||
>
|
||||
<img alt="undo sketch" src={undo_solid} />
|
||||
</button>
|
||||
<button
|
||||
class="clear bg-opacity-30 hover:bg-opacity-100 transition p-1 bg-gray-50 dark:bg-gray-500 rounded shadow w-8 h-8"
|
||||
/>
|
||||
<IconButton
|
||||
image={clear}
|
||||
alt="clear sketch"
|
||||
on:click={() => dispatch("clear")}
|
||||
>
|
||||
<img alt="clear sketch" src={clear} />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,67 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="5.9403949mm"
|
||||
height="5.9403949mm"
|
||||
viewBox="0 0 5.9403949 5.9403949"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
||||
sodipodi:docname="clear.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
inkscape:zoom="10.925474"
|
||||
inkscape:cx="4.1188143"
|
||||
inkscape:cy="15.559965"
|
||||
inkscape:window-width="1248"
|
||||
inkscape:window-height="770"
|
||||
inkscape:window-x="-6"
|
||||
inkscape:window-y="-6"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-115.10942,-119.22353)">
|
||||
<g
|
||||
id="g239"
|
||||
transform="matrix(0.05138986,0.05138986,-0.05138986,0.05138986,117.0869,112.75317)">
|
||||
<rect
|
||||
style="fill:#000000;stroke-width:0.295287"
|
||||
id="rect31"
|
||||
width="20"
|
||||
height="80"
|
||||
x="-111.51107"
|
||||
y="42.193726"
|
||||
rx="2.9434128"
|
||||
ry="2.6448057"
|
||||
transform="scale(-1,1)" />
|
||||
<rect
|
||||
style="fill:#000000;stroke-width:0.295287"
|
||||
id="rect31-3"
|
||||
width="20"
|
||||
height="80"
|
||||
x="-92.193726"
|
||||
y="-141.51106"
|
||||
rx="2.9434128"
|
||||
ry="2.6448057"
|
||||
transform="matrix(0,-1,-1,0,0,0)" />
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;">
|
||||
<g transform="matrix(1.14096,-0.140958,-0.140958,1.14096,-0.0559523,0.0559523)">
|
||||
<path d="M18,6L6.087,17.913" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:2px;"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="M4.364,4.364L19.636,19.636" style="fill:none;fill-rule:nonzero;stroke:black;stroke-width:2px;"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 761 B |
1
ui/packages/image/src/image.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-image"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg>
|
After Width: | Height: | Size: 369 B |