merged blocks-dev

This commit is contained in:
Abubakar Abid 2022-04-05 12:21:48 -07:00
commit 2fdf26d7ac
125 changed files with 1645 additions and 3421 deletions

View File

@ -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(),

View File

@ -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,

View File

@ -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"),

View File

@ -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__":

View File

@ -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()

View File

@ -4,6 +4,7 @@ import gradio as gr
def image_mod(text):
return text[::-1]
demo = gr.Blocks()
with demo:

View File

@ -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

View File

@ -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?"),
],

View File

@ -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()

View File

@ -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__":

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -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

View File

@ -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:
"""

View File

@ -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 = [

View File

@ -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}

View File

@ -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)

View File

@ -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
View File

View 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())

View 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,
},
],
}

View File

@ -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(

View File

@ -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):

View File

@ -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()

View File

@ -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"
}
}

View File

@ -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",

View File

@ -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();

View File

@ -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;
});

View File

@ -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"}

View File

@ -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"];

View File

@ -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"} />

View File

@ -1,3 +1,2 @@
export { default as Component } from "./Checkbox.svelte";
export { default as Interpretation } from "./Interpretation.svelte";
export const modes = ["static", "dynamic"];

View File

@ -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"}
/>

View File

@ -1,3 +1,2 @@
export { default as Component } from "./CheckboxGroup.svelte";
export { default as Interpretation } from "./Interpretation.svelte";
export const modes = ["static", "dynamic"];

View File

@ -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>

View File

@ -1,3 +1,2 @@
export { default as Component } from "./Dropdown.svelte";
export { default as Interpretation } from "./Interpretation.svelte";
export const modes = ["dynamic"];

View File

@ -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"];

View File

@ -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>

View File

@ -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

View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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>

View File

@ -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">

View File

@ -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"

View File

@ -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">

View File

@ -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"

View 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
};

View File

@ -0,0 +1,2 @@
export { default as Component } from "./Interpretation.svelte";
export const modes = ["dynamic"];

View 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
};
};

View File

@ -21,6 +21,6 @@
$: value, dispatch("change");
</script>
{#if value !== undefined}
{#if value !== undefined && value !== null}
<Label {theme} {style} {value} />
{/if}

View File

@ -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

View File

@ -1,3 +1,2 @@
export { default as Component } from "./Number.svelte";
export { default as Interpretation } from "./Interpretation.svelte";
export const modes = ["static", "dynamic"];

View File

@ -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"}

View File

@ -1,3 +1,2 @@
export { default as Component } from "./Radio.svelte";
export { default as Interpretation } from "./Interpretation.svelte";
export const modes = ["static", "dynamic"];

View File

@ -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>

View File

@ -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}

View File

@ -1,3 +1,2 @@
export { default as Component } from "./Slider.svelte";
export { default as Interpretation } from "./Interpretation.svelte";
export const modes = ["static", "dynamic"];

View File

@ -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}

View File

@ -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"),

View File

@ -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
};
};

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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")]
};

View File

@ -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");

View File

@ -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");
});

View File

@ -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: {

View 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>
```

View 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
}

View 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>

View 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>

View File

@ -0,0 +1,3 @@
<span class="text-gray-600 text-sm mb-2 block">
<slot />
</span>

View 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>

View 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>

View 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

View 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

View 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

View 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";

View File

@ -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"
}

View File

@ -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>

View 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

View File

@ -8,6 +8,7 @@
"license": "ISC",
"private": true,
"dependencies": {
"@gradio/atoms": "workspace:^0.0.1",
"@gradio/upload": "workspace:^0.0.1"
}
}

View File

@ -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>

View 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

View File

@ -6,5 +6,8 @@
"main": "src/index.ts",
"author": "",
"license": "ISC",
"private": true
"private": true,
"dependencies": {
"@gradio/atoms": "workspace:^0.0.1"
}
}

View File

@ -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>

View File

@ -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 {

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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",

View File

@ -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) {

View File

@ -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>

View File

@ -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

View 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

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