mirror of
https://github.com/gradio-app/gradio.git
synced 2025-03-19 12:00:39 +08:00
Merge branch 'master' into Blocks-Events
This commit is contained in:
commit
8a6f53d093
1
.gitignore
vendored
1
.gitignore
vendored
@ -38,6 +38,7 @@ demo/files/*.mp4
|
||||
*.bak
|
||||
workspace.code-workspace
|
||||
*.h5
|
||||
.vscode/
|
||||
|
||||
# log files
|
||||
.pnpm-debug.log
|
@ -10,9 +10,7 @@ coverage:
|
||||
- "gradio/"
|
||||
target: 80%
|
||||
threshold: 0.1
|
||||
patch:
|
||||
default:
|
||||
target: 50% # new contributions should have a coverage at least equal to 50%
|
||||
patch: off
|
||||
|
||||
comment: false
|
||||
codecov:
|
||||
|
@ -1,6 +1,6 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: gradio
|
||||
Version: 2.8.5
|
||||
Version: 2.8.10
|
||||
Summary: Python library for easily interacting with trained machine learning models
|
||||
Home-page: https://github.com/gradio-app/gradio-UI
|
||||
Author: Abubakar Abid, Ali Abid, Ali Abdalla, Dawood Khan, Ahsen Khaliq
|
||||
|
@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple
|
||||
from gradio import utils
|
||||
from gradio.context import Context
|
||||
from gradio.launchable import Launchable
|
||||
from gradio.routes import PredictBody
|
||||
|
||||
if TYPE_CHECKING: # Only import for type checking (is False at runtime).
|
||||
from gradio.component import Component
|
||||
@ -138,6 +139,7 @@ class Blocks(Launchable, BlockContext):
|
||||
self.requires_permissions = False # TODO: needs to be implemented
|
||||
self.enable_queue = False
|
||||
self.is_space = True if os.getenv("SYSTEM") == "spaces" else False
|
||||
self.stateful = False # TODO: implement state
|
||||
|
||||
super().__init__()
|
||||
Context.root_block = self
|
||||
@ -145,9 +147,9 @@ class Blocks(Launchable, BlockContext):
|
||||
self.fns = []
|
||||
self.dependencies = []
|
||||
|
||||
def process_api(self, data: Dict[str, Any], username: str = None) -> Dict[str, Any]:
|
||||
raw_input = data["data"]
|
||||
fn_index = data["fn_index"]
|
||||
def process_api(self, data: PredictBody, username: str = None) -> Dict[str, Any]:
|
||||
raw_input = data.data
|
||||
fn_index = data.fn_index
|
||||
fn = self.fns[fn_index]
|
||||
dependency = self.dependencies[fn_index]
|
||||
|
||||
|
@ -9,7 +9,7 @@ from abc import ABC, abstractmethod
|
||||
from typing import Any, List, Optional
|
||||
|
||||
import gradio as gr
|
||||
from gradio import encryptor
|
||||
from gradio import encryptor, utils
|
||||
|
||||
|
||||
class FlaggingCallback(ABC):
|
||||
@ -99,7 +99,7 @@ class SimpleCSVLogger(FlaggingCallback):
|
||||
|
||||
with open(log_filepath, "a", newline="") as csvfile:
|
||||
writer = csv.writer(csvfile)
|
||||
writer.writerow(csv_data)
|
||||
writer.writerow(utils.santize_for_csv(csv_data))
|
||||
|
||||
with open(log_filepath, "r") as csvfile:
|
||||
line_count = len([None for row in csv.reader(csvfile)]) - 1
|
||||
@ -186,7 +186,7 @@ class CSVLogger(FlaggingCallback):
|
||||
content[flag_index][flag_col_index] = flag_option
|
||||
output = io.StringIO()
|
||||
writer = csv.writer(output)
|
||||
writer.writerows(content)
|
||||
writer.writerows(utils.santize_for_csv(content))
|
||||
return output.getvalue()
|
||||
|
||||
if interface.encrypt:
|
||||
@ -200,7 +200,7 @@ class CSVLogger(FlaggingCallback):
|
||||
file_content = decrypted_csv.decode()
|
||||
if flag_index is not None:
|
||||
file_content = replace_flag_at_index(file_content)
|
||||
output.write(file_content)
|
||||
output.write(utils.santize_for_csv(file_content))
|
||||
writer = csv.writer(output)
|
||||
if flag_index is None:
|
||||
if is_new:
|
||||
@ -208,8 +208,10 @@ class CSVLogger(FlaggingCallback):
|
||||
writer.writerow(csv_data)
|
||||
with open(log_fp, "wb") as csvfile:
|
||||
csvfile.write(
|
||||
encryptor.encrypt(
|
||||
interface.encryption_key, output.getvalue().encode()
|
||||
utils.santize_for_csv(
|
||||
encryptor.encrypt(
|
||||
interface.encryption_key, output.getvalue().encode()
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
@ -217,8 +219,8 @@ class CSVLogger(FlaggingCallback):
|
||||
with open(log_fp, "a", newline="") as csvfile:
|
||||
writer = csv.writer(csvfile)
|
||||
if is_new:
|
||||
writer.writerow(headers)
|
||||
writer.writerow(csv_data)
|
||||
writer.writerow(utils.santize_for_csv(headers))
|
||||
writer.writerow(utils.santize_for_csv(csv_data))
|
||||
else:
|
||||
with open(log_fp) as csvfile:
|
||||
file_content = csvfile.read()
|
||||
@ -226,7 +228,7 @@ class CSVLogger(FlaggingCallback):
|
||||
with open(
|
||||
log_fp, "w", newline=""
|
||||
) as csvfile: # newline parameter needed for Windows
|
||||
csvfile.write(file_content)
|
||||
csvfile.write(utils.santize_for_csv(file_content))
|
||||
with open(log_fp, "r") as csvfile:
|
||||
line_count = len([None for row in csv.reader(csvfile)]) - 1
|
||||
return line_count
|
||||
@ -368,7 +370,7 @@ class HuggingFaceDatasetSaver(FlaggingCallback):
|
||||
"_type": "Value",
|
||||
}
|
||||
|
||||
writer.writerow(headers)
|
||||
writer.writerow(utils.santize_for_csv(headers))
|
||||
|
||||
# Generate the row corresponding to the flagged sample
|
||||
csv_data = []
|
||||
@ -403,7 +405,7 @@ class HuggingFaceDatasetSaver(FlaggingCallback):
|
||||
if flag_option is not None:
|
||||
csv_data.append(flag_option)
|
||||
|
||||
writer.writerow(csv_data)
|
||||
writer.writerow(utils.santize_for_csv(csv_data))
|
||||
|
||||
if is_new:
|
||||
json.dump(infos, open(self.infos_file, "w"))
|
||||
|
@ -28,10 +28,9 @@ from gradio.outputs import OutputComponent
|
||||
from gradio.outputs import State as o_State # type: ignore
|
||||
from gradio.outputs import get_output_instance
|
||||
from gradio.process_examples import load_from_cache, process_example
|
||||
from gradio.routes import predict
|
||||
from gradio.routes import PredictBody
|
||||
|
||||
if TYPE_CHECKING: # Only import for type checking (is False at runtime).
|
||||
import flask
|
||||
import transformers
|
||||
|
||||
|
||||
@ -176,6 +175,7 @@ class Interface(Launchable):
|
||||
if repeat_outputs_per_model:
|
||||
self.output_components *= len(fn)
|
||||
|
||||
self.stateful = False
|
||||
if sum(isinstance(i, i_State) for i in self.input_components) > 1:
|
||||
raise ValueError("Only one input component can be State.")
|
||||
if sum(isinstance(o, o_State) for o in self.output_components) > 1:
|
||||
@ -187,10 +187,24 @@ class Interface(Launchable):
|
||||
state_param_index = [
|
||||
isinstance(i, i_State) for i in self.input_components
|
||||
].index(True)
|
||||
self.stateful = True
|
||||
self.state_param_index = state_param_index
|
||||
state: i_State = self.input_components[state_param_index]
|
||||
if state.default is None:
|
||||
default = utils.get_default_args(fn[0])[state_param_index]
|
||||
state.default = default
|
||||
self.state_default = state.default
|
||||
|
||||
if sum(isinstance(i, o_State) for i in self.output_components) == 1:
|
||||
state_return_index = [
|
||||
isinstance(i, o_State) for i in self.output_components
|
||||
].index(True)
|
||||
self.state_return_index = state_return_index
|
||||
else:
|
||||
raise ValueError(
|
||||
"For a stateful interface, there must be exactly one State"
|
||||
" input component and one State output component."
|
||||
)
|
||||
|
||||
if (
|
||||
interpretation is None
|
||||
@ -280,7 +294,6 @@ class Interface(Launchable):
|
||||
|
||||
self.thumbnail = thumbnail
|
||||
theme = theme if theme is not None else os.getenv("GRADIO_THEME", "default")
|
||||
self.is_space = True if os.getenv("SYSTEM") == "spaces" else False
|
||||
DEPRECATED_THEME_MAP = {
|
||||
"darkdefault": "default",
|
||||
"darkhuggingface": "dark-huggingface",
|
||||
@ -544,17 +557,19 @@ class Interface(Launchable):
|
||||
else:
|
||||
return predictions
|
||||
|
||||
def process_api(self, data: Dict[str, Any], username: str = None) -> Dict[str, Any]:
|
||||
def process_api(self, data: PredictBody, username: str = None) -> Dict[str, Any]:
|
||||
flag_index = None
|
||||
if data.get("example_id") is not None:
|
||||
example_id = data["example_id"]
|
||||
if data.example_id is not None:
|
||||
if self.cache_examples:
|
||||
prediction = load_from_cache(self, example_id)
|
||||
prediction = load_from_cache(self, data.example_id)
|
||||
durations = None
|
||||
else:
|
||||
prediction, durations = process_example(self, example_id)
|
||||
prediction, durations = process_example(self, data.example_id)
|
||||
else:
|
||||
raw_input = data["data"]
|
||||
raw_input = data.data
|
||||
if self.stateful:
|
||||
state = data.state
|
||||
raw_input[self.state_param_index] = state
|
||||
prediction, durations = self.process(raw_input)
|
||||
if self.allow_flagging == "auto":
|
||||
flag_index = self.flagging_callback.flag(
|
||||
@ -564,12 +579,18 @@ class Interface(Launchable):
|
||||
flag_option="" if self.flagging_options else None,
|
||||
username=username,
|
||||
)
|
||||
if self.stateful:
|
||||
updated_state = prediction[self.state_return_index]
|
||||
prediction[self.state_return_index] = None
|
||||
else:
|
||||
updated_state = None
|
||||
|
||||
return {
|
||||
"data": prediction,
|
||||
"durations": durations,
|
||||
"avg_durations": self.config.get("avg_durations"),
|
||||
"flag_index": flag_index,
|
||||
"updated_state": updated_state,
|
||||
}
|
||||
|
||||
def process(self, raw_input: List[Any]) -> Tuple[List[Any], List[float]]:
|
||||
|
@ -11,7 +11,7 @@ from gradio import encryptor, networking, queueing, strings, utils # type: igno
|
||||
from gradio.process_examples import cache_interface_examples
|
||||
|
||||
if TYPE_CHECKING: # Only import for type checking (is False at runtime).
|
||||
import flask
|
||||
import fastapi
|
||||
|
||||
|
||||
class Launchable:
|
||||
@ -42,7 +42,7 @@ class Launchable:
|
||||
ssl_keyfile: Optional[str] = None,
|
||||
ssl_certfile: Optional[str] = None,
|
||||
ssl_keyfile_password: Optional[str] = None,
|
||||
) -> Tuple[flask.Flask, str, str]:
|
||||
) -> Tuple[fastapi.FastAPI, str, str]:
|
||||
"""
|
||||
Launches the webserver that serves the UI for the interface.
|
||||
Parameters:
|
||||
@ -68,7 +68,7 @@ class Launchable:
|
||||
ssl_certfile (str): If a path to a file is provided, will use this as the signed certificate for https. Needs to be provided if ssl_keyfile is provided.
|
||||
ssl_keyfile_password (str): If a password is provided, will use this with the ssl certificate for https.
|
||||
Returns:
|
||||
app (flask.Flask): Flask app object
|
||||
app (fastapi.FastAPI): FastAPI app object
|
||||
path_to_local_server (str): Locally accessible link
|
||||
share_url (str): Publicly accessible link (if share=True)
|
||||
"""
|
||||
@ -88,6 +88,7 @@ class Launchable:
|
||||
self.height = height
|
||||
self.width = width
|
||||
self.favicon_path = favicon_path
|
||||
self.is_space = True if os.getenv("SYSTEM") == "spaces" else False
|
||||
|
||||
if hasattr(self, "encrypt") and self.encrypt is None:
|
||||
self.encrypt = encrypt
|
||||
|
@ -7,6 +7,8 @@ from typing import Dict, Tuple
|
||||
|
||||
import requests
|
||||
|
||||
from gradio.routes import QueuePushBody
|
||||
|
||||
DB_FILE = "gradio_queue.db"
|
||||
|
||||
|
||||
@ -106,8 +108,9 @@ def pop() -> Tuple[int, str, Dict, str]:
|
||||
return result[0], result[1], json.loads(result[2]), result[3]
|
||||
|
||||
|
||||
def push(input_data: Dict, action: str) -> Tuple[str, int]:
|
||||
input_data = json.dumps(input_data)
|
||||
def push(body: QueuePushBody) -> Tuple[str, int]:
|
||||
action = body.action
|
||||
input_data = json.dumps({"data": body.data})
|
||||
hash = generate_hash()
|
||||
conn = sqlite3.connect(DB_FILE)
|
||||
c = conn.cursor()
|
||||
|
@ -9,7 +9,7 @@ import posixpath
|
||||
import secrets
|
||||
import traceback
|
||||
import urllib
|
||||
from typing import Any, List, Optional, Type
|
||||
from typing import Any, Dict, List, Optional, Tuple, Type
|
||||
|
||||
import orjson
|
||||
import pkg_resources
|
||||
@ -21,6 +21,7 @@ from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from jinja2.exceptions import TemplateNotFound
|
||||
from pydantic import BaseModel
|
||||
from starlette.responses import RedirectResponse
|
||||
|
||||
from gradio import encryptor, queueing, utils
|
||||
@ -54,9 +55,49 @@ app.add_middleware(
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
state_holder: Dict[Tuple[str, str], Any] = {}
|
||||
app.state_holder = state_holder
|
||||
|
||||
templates = Jinja2Templates(directory=STATIC_TEMPLATE_LIB)
|
||||
|
||||
|
||||
###########
|
||||
# Data Models
|
||||
###########
|
||||
|
||||
|
||||
class PredictBody(BaseModel):
|
||||
session_hash: Optional[str]
|
||||
example_id: Optional[int]
|
||||
data: List[Any]
|
||||
state: Optional[Any]
|
||||
fn_index: Optional[int]
|
||||
|
||||
|
||||
class FlagData(BaseModel):
|
||||
input_data: List[Any]
|
||||
output_data: List[Any]
|
||||
flag_option: Optional[str]
|
||||
flag_index: Optional[int]
|
||||
|
||||
|
||||
class FlagBody(BaseModel):
|
||||
data: FlagData
|
||||
|
||||
|
||||
class InterpretBody(BaseModel):
|
||||
data: List[Any]
|
||||
|
||||
|
||||
class QueueStatusBody(BaseModel):
|
||||
hash: str
|
||||
|
||||
|
||||
class QueuePushBody(BaseModel):
|
||||
action: str
|
||||
data: Any
|
||||
|
||||
|
||||
###########
|
||||
# Auth
|
||||
###########
|
||||
@ -182,8 +223,13 @@ def api_docs(request: Request):
|
||||
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]
|
||||
if isinstance(app.launchable.examples, list):
|
||||
example = app.launchable.examples[0]
|
||||
sample_inputs = []
|
||||
for index, example_input in enumerate(example):
|
||||
sample_inputs.append(
|
||||
app.launchable.input_components[index].preprocess_example(example_input)
|
||||
)
|
||||
else:
|
||||
sample_inputs = [
|
||||
inp.generate_sample() for inp in app.launchable.input_components
|
||||
@ -208,10 +254,19 @@ def api_docs(request: Request):
|
||||
|
||||
|
||||
@app.post("/api/predict/", dependencies=[Depends(login_check)])
|
||||
async def predict(request: Request, username: str = Depends(get_current_user)):
|
||||
body = await request.json()
|
||||
async def predict(body: PredictBody, username: str = Depends(get_current_user)):
|
||||
if app.launchable.stateful:
|
||||
session_hash = body.session_hash
|
||||
state = app.state_holder.get(
|
||||
(session_hash, "state"), app.launchable.state_default
|
||||
)
|
||||
body.state = state
|
||||
try:
|
||||
output = await run_in_threadpool(app.launchable.process_api, body, username)
|
||||
if app.launchable.stateful:
|
||||
updated_state = output.pop("updated_state")
|
||||
app.state_holder[(session_hash, "state")] = updated_state
|
||||
|
||||
except BaseException as error:
|
||||
if app.launchable.show_error:
|
||||
traceback.print_exc()
|
||||
@ -222,29 +277,26 @@ async def predict(request: Request, username: str = Depends(get_current_user)):
|
||||
|
||||
|
||||
@app.post("/api/flag/", dependencies=[Depends(login_check)])
|
||||
async def flag(request: Request, username: str = Depends(get_current_user)):
|
||||
async def flag(body: FlagBody, 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"),
|
||||
body.data.input_data,
|
||||
body.data.output_data,
|
||||
flag_option=body.data.flag_option,
|
||||
flag_index=body.data.flag_index,
|
||||
username=username,
|
||||
)
|
||||
return {"success": True}
|
||||
|
||||
|
||||
@app.post("/api/interpret/", dependencies=[Depends(login_check)])
|
||||
async def interpret(request: Request):
|
||||
async def interpret(body: InterpretBody):
|
||||
if app.launchable.analytics_enabled:
|
||||
await utils.log_feature_analytics(app.launchable.ip_address, "interpret")
|
||||
body = await request.json()
|
||||
raw_input = body["data"]
|
||||
raw_input = body.data
|
||||
interpretation_scores, alternative_outputs = await run_in_threadpool(
|
||||
app.launchable.interpret, raw_input
|
||||
)
|
||||
@ -255,18 +307,14 @@ async def interpret(request: Request):
|
||||
|
||||
|
||||
@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)
|
||||
async def queue_push(body: QueuePushBody):
|
||||
job_hash, queue_position = queueing.push(body)
|
||||
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)
|
||||
async def queue_status(body: QueueStatusBody):
|
||||
status, data = queueing.get_status(body.hash)
|
||||
return {"status": status, "data": data}
|
||||
|
||||
|
||||
|
@ -22,6 +22,8 @@
|
||||
<noscript>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans&display=swap">
|
||||
</noscript>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans&display=swap">
|
||||
<link rel="stylesheet" href="https://gradio.app/assets/prism.css">
|
||||
|
||||
<style>
|
||||
html {
|
||||
@ -211,6 +213,36 @@
|
||||
color: grey !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
.copy {
|
||||
float: right;
|
||||
padding-right: 1em;
|
||||
background: whitesmoke;
|
||||
border: none !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.float-left {
|
||||
float:left;
|
||||
width: 90%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.copy-svg {
|
||||
visibility: hidden;
|
||||
margin: 1em 0 0 0 !important;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.code-block:hover .copy-svg {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
pre {
|
||||
float:left;
|
||||
width: 90%;
|
||||
overflow: auto !important;
|
||||
background: inherit !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<meta property="og:url" content="https://gradio.app/" />
|
||||
@ -286,23 +318,31 @@
|
||||
</ul>
|
||||
|
||||
<h4 id="payload">Payload: </h4>
|
||||
<div class="json">
|
||||
<div class="json code-block">
|
||||
<div class="float-left">
|
||||
<p>  {</p>
|
||||
<p>    "data": [{%for i in range(0, len_inputs)%} <span>{{ input_types[i]
|
||||
}}</span>{% if i != len_inputs - 1 %} ,{% endif %}{%endfor%} ]</p>
|
||||
<p>  }</p>
|
||||
</div>
|
||||
<button class="copy" onclick="copyCode(this)"><svg class="copy-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" style="fill: #808080;"><path d="M320 448v40c0 13.255-10.745 24-24 24H24c-13.255 0-24-10.745-24-24V120c0-13.255 10.745-24 24-24h72v296c0 30.879 25.121 56 56 56h168zm0-344V0H152c-13.255 0-24 10.745-24 24v368c0 13.255 10.745 24 24 24h272c13.255 0 24-10.745 24-24V128H344c-13.2 0-24-10.8-24-24zm120.971-31.029L375.029 7.029A24 24 0 0 0 358.059 0H352v96h96v-6.059a24 24 0 0 0-7.029-16.97z"/></svg>
|
||||
<div></div></button>
|
||||
</div>
|
||||
{% if auth is not none %}
|
||||
<p>Note: This interface requires authentication. This means you will have to first post to the login api before you can post to the predict endpoint. See below for more info </p>
|
||||
{% endif %}
|
||||
<h4 id="response">Response: </h4>
|
||||
<div class="json">
|
||||
<div class="json code-block">
|
||||
<div class="float-left">
|
||||
<p>  {</p>
|
||||
<p>    "data": [{%for i in range(0, len_outputs)%} <span>{{ output_types[i]
|
||||
}}</span>{% if i != len_outputs - 1 %} ,{% endif %}{%endfor%} ],</p>
|
||||
<p>    "durations": [ float ], # the time taken for the prediction to complete</p>
|
||||
<p>    "avg_durations": [ float ] # the average time taken for all predictions so far (used to estimate the runtime)</p>
|
||||
<p>  }</p>
|
||||
</div>
|
||||
<button class="copy" onclick="copyCode(this)"><svg class="copy-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" style="fill: #808080;"><path d="M320 448v40c0 13.255-10.745 24-24 24H24c-13.255 0-24-10.745-24-24V120c0-13.255 10.745-24 24-24h72v296c0 30.879 25.121 56 56 56h168zm0-344V0H152c-13.255 0-24 10.745-24 24v368c0 13.255 10.745 24 24 24h272c13.255 0 24-10.745 24-24V128H344c-13.2 0-24-10.8-24-24zm120.971-31.029L375.029 7.029A24 24 0 0 0 358.059 0H352v96h96v-6.059a24 24 0 0 0-7.029-16.97z"/></svg>
|
||||
<div></div></button>
|
||||
</div>
|
||||
<h4 id="try-it">Try it (live demo): </h4>
|
||||
|
||||
@ -325,16 +365,9 @@
|
||||
|
||||
|
||||
|
||||
<div class="json">
|
||||
{% if auth is not none %}
|
||||
<!-- import requests-->
|
||||
|
||||
<!-- sess = requests.session()-->
|
||||
<!-- sess.post(url='INTERFACE_URL/login', data={"username": "USERNAME", "password":"PASSWORD"})-->
|
||||
<!-- r = sess.post(url='INTERFACE_URL/api/predict/',json={"data":[INPUT]}, )-->
|
||||
<!-- -->
|
||||
<!-- print(r.json())-->
|
||||
|
||||
<div class="json code-block">
|
||||
<div class="float-left">
|
||||
{% if auth is not none %}
|
||||
<p class="syntax">import requests</p>
|
||||
<br>
|
||||
<p class="syntax">sess = requests.session()</p>
|
||||
@ -363,11 +396,15 @@
|
||||
<p>r.json()</p>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
<button class="copy" onclick="copyCode(this)"><svg class="copy-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" style="fill: #808080;"><path d="M320 448v40c0 13.255-10.745 24-24 24H24c-13.255 0-24-10.745-24-24V120c0-13.255 10.745-24 24-24h72v296c0 30.879 25.121 56 56 56h168zm0-344V0H152c-13.255 0-24 10.745-24 24v368c0 13.255 10.745 24 24 24h272c13.255 0 24-10.745 24-24V128H344c-13.2 0-24-10.8-24-24zm120.971-31.029L375.029 7.029A24 24 0 0 0 358.059 0H352v96h96v-6.059a24 24 0 0 0-7.029-16.97z"/></svg>
|
||||
<div></div></button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="demo-window" demo="2">
|
||||
<div class="json">
|
||||
<div class="json code-block">
|
||||
<div class="float-left">
|
||||
{% if auth is not none %}
|
||||
<p class="syntax">curl -X POST -F 'username=USERNAME' -F 'password=PASSWORD' <span class="syntax" id="curl_syntax_url_login"></span> -c cookies.txt </p>
|
||||
|
||||
@ -386,10 +423,14 @@
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<button class="copy" onclick="copyCode(this)"><svg class="copy-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" style="fill: #808080;"><path d="M320 448v40c0 13.255-10.745 24-24 24H24c-13.255 0-24-10.745-24-24V120c0-13.255 10.745-24 24-24h72v296c0 30.879 25.121 56 56 56h168zm0-344V0H152c-13.255 0-24 10.745-24 24v368c0 13.255 10.745 24 24 24h272c13.255 0 24-10.745 24-24V128H344c-13.2 0-24-10.8-24-24zm120.971-31.029L375.029 7.029A24 24 0 0 0 358.059 0H352v96h96v-6.059a24 24 0 0 0-7.029-16.97z"/></svg>
|
||||
<div></div></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-window" demo="3">
|
||||
<div class="json">
|
||||
<div class="json code-block">
|
||||
<div class="float-left">
|
||||
{% if auth is not none %}
|
||||
<p class="syntax">// Will only work locally.</p>
|
||||
<br>
|
||||
@ -415,6 +456,9 @@
|
||||
console.log(json_response) })</p>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<button class="copy" onclick="copyCode(this)"><svg class="copy-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" style="fill: #808080;"><path d="M320 448v40c0 13.255-10.745 24-24 24H24c-13.255 0-24-10.745-24-24V120c0-13.255 10.745-24 24-24h72v296c0 30.879 25.121 56 56 56h168zm0-344V0H152c-13.255 0-24 10.745-24 24v368c0 13.255 10.745 24 24 24h272c13.255 0 24-10.745 24-24V128H344c-13.2 0-24-10.8-24-24zm120.971-31.029L375.029 7.029A24 24 0 0 0 358.059 0H352v96h96v-6.059a24 24 0 0 0-7.029-16.97z"/></svg>
|
||||
<div></div></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -437,11 +481,20 @@
|
||||
<p>  }</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hidden" id="related-methods-holder">
|
||||
<h4 id="related">Related Methods: </h4>
|
||||
<div class="json code-block">
|
||||
<pre><code class="language-python float-left" id="related-methods"></code></pre>
|
||||
<button class="copy" onclick="copyCode(this)"><svg class="copy-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" style="fill: #808080;"><path d="M320 448v40c0 13.255-10.745 24-24 24H24c-13.255 0-24-10.745-24-24V120c0-13.255 10.745-24 24-24h72v296c0 30.879 25.121 56 56 56h168zm0-344V0H152c-13.255 0-24 10.745-24 24v368c0 13.255 10.745 24 24 24h272c13.255 0 24-10.745 24-24V128H344c-13.2 0-24-10.8-24-24zm120.971-31.029L375.029 7.029A24 24 0 0 0 358.059 0H352v96h96v-6.059a24 24 0 0 0-7.029-16.97z"/></svg>
|
||||
<div></div></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
|
||||
|
||||
<script>
|
||||
var len_inputs = {{ len_inputs }};
|
||||
var len_outputs = {{ len_outputs }};
|
||||
@ -538,6 +591,78 @@
|
||||
$(`.demo-window[demo="${demo_num}"]`).show();
|
||||
})
|
||||
|
||||
var inputRelatedMethods = {
|
||||
'Image': `# To convert your image file into the base64 format required by the API
|
||||
gr.processing_utils.encode_url_or_file_to_base64(path)
|
||||
|
||||
`,
|
||||
'Video':`# To convert your video file into the base64 format required by the API
|
||||
gr.processing_utils.encode_url_or_file_to_base64(path)
|
||||
|
||||
`,
|
||||
'Audio':`# To convert your audio file into the base64 format required by the API
|
||||
gr.processing_utils.encode_url_or_file_to_base64(path)
|
||||
|
||||
`,
|
||||
'File':`# To convert your file into the base64 format required by the API
|
||||
gr.processing_utils.encode_url_or_file_to_base64(path)
|
||||
|
||||
`
|
||||
|
||||
}
|
||||
|
||||
var outputRelatedMethods = {
|
||||
'Image': `# To convert the base64 image returned by the API to an image tmp file object
|
||||
gr.processing_utils.decode_base64_to_file(encoding, encryption_key=None, file_path=None)
|
||||
|
||||
`,
|
||||
'Video': `# To convert the base64 video returned by the API to an video tmp file object
|
||||
gr.processing_utils.decode_base64_to_file(encoding, encryption_key=None, file_path=None)
|
||||
|
||||
`,
|
||||
'Audio': `# To convert the base64 audio returned by the API to an audio tmp file object
|
||||
gr.processing_utils.decode_base64_to_file(encoding, encryption_key=None, file_path=None)
|
||||
|
||||
`,
|
||||
'File': `# To convert the base64 file returned by the API to a regular tmp file object
|
||||
gr.processing_utils.decode_base64_to_file(encoding, encryption_key=None, file_path=None)
|
||||
|
||||
`
|
||||
}
|
||||
|
||||
|
||||
var showRelated = false;
|
||||
var relatedMethods = `import gradio as gr
|
||||
|
||||
`
|
||||
|
||||
{% for i in range(len_inputs) %}
|
||||
if ("{{inputs[i]}}" in inputRelatedMethods) {
|
||||
showRelated = true;
|
||||
relatedMethods += inputRelatedMethods["{{inputs[i]}}"]
|
||||
}
|
||||
{% endfor %}
|
||||
|
||||
{% for i in range(len_outputs) %}
|
||||
if ("{{outputs[i]}}" in outputRelatedMethods) {
|
||||
showRelated = true;
|
||||
relatedMethods += outputRelatedMethods["{{outputs[i]}}"]
|
||||
}
|
||||
{% endfor %}
|
||||
|
||||
if (showRelated) {
|
||||
document.getElementById('related-methods').innerHTML = relatedMethods;
|
||||
$('#related-methods-holder').removeClass("hidden");
|
||||
}
|
||||
|
||||
function copyCode(elem) {
|
||||
var text = elem.parentElement.innerText;
|
||||
navigator.clipboard.writeText(text);
|
||||
elem.firstChild.style="fill: #eb9f59;"
|
||||
setTimeout(function(){
|
||||
elem.firstChild.style="fill: #808080;"
|
||||
}, 600);
|
||||
};
|
||||
|
||||
var friendlyHttpStatus = {
|
||||
'200': 'OK',
|
||||
@ -584,6 +709,8 @@
|
||||
};
|
||||
|
||||
</script>
|
||||
<script src="https://gradio.app/assets/prism.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import csv
|
||||
import inspect
|
||||
import json
|
||||
@ -10,7 +11,7 @@ import os
|
||||
import random
|
||||
import warnings
|
||||
from distutils.version import StrictVersion
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, List
|
||||
|
||||
import aiohttp
|
||||
import analytics
|
||||
@ -286,3 +287,37 @@ def get_default_args(func: Callable) -> Dict[str, Any]:
|
||||
v.default if v.default is not inspect.Parameter.empty else None
|
||||
for v in signature.parameters.values()
|
||||
]
|
||||
|
||||
|
||||
def santize_for_csv(data: str | List[str] | List[List[str]]):
|
||||
"""Sanitizes data so that it can be safely written to a CSV file."""
|
||||
|
||||
def sanitize(item):
|
||||
return "'" + item
|
||||
|
||||
unsafe_prefixes = ("+", "=", "-", "@")
|
||||
warning_message = "Sanitizing flagged data by escaping cell contents that begin "
|
||||
"with one of the following characters: '+', '=', '-', '@'."
|
||||
|
||||
if isinstance(data, str):
|
||||
if data.startswith(unsafe_prefixes):
|
||||
warnings.warn(warning_message)
|
||||
return sanitize(data)
|
||||
return data
|
||||
elif isinstance(data, list) and isinstance(data[0], str):
|
||||
sanitized_data = copy.deepcopy(data)
|
||||
for index, item in enumerate(data):
|
||||
if item.startswith(unsafe_prefixes):
|
||||
warnings.warn(warning_message)
|
||||
sanitized_data[index] = sanitize(item)
|
||||
return sanitized_data
|
||||
elif isinstance(data[0], list) and isinstance(data[0][0], str):
|
||||
sanitized_data = copy.deepcopy(data)
|
||||
for outer_index, sublist in enumerate(data):
|
||||
for inner_index, item in enumerate(sublist):
|
||||
if item.startswith(unsafe_prefixes):
|
||||
warnings.warn(warning_message)
|
||||
sanitized_data[outer_index][inner_index] = sanitize(item)
|
||||
return sanitized_data
|
||||
else:
|
||||
raise ValueError("Unsupported data type: " + str(type(data)))
|
||||
|
@ -1 +1 @@
|
||||
2.8.5
|
||||
2.8.10
|
@ -37,6 +37,7 @@ With these three arguments, we can quickly create interfaces and `launch()` th
|
||||
Let's say we want to customize the input text field - for example, we wanted it to be larger and have a text hint. If we use the actual input class for `Textbox` instead of using the string shortcut, we have access to much more customizability. To see a list of all the components we support and how you can customize them, check out the [Docs](https://gradio.app/docs).
|
||||
|
||||
**Sidenote**: `Interface.launch()` method returns 3 values:
|
||||
|
||||
1. `app`, which is the FastAPI application that is powering the Gradio demo
|
||||
2. `local_url`, which is the local address of the server
|
||||
3. `share_url`, which is the public address for this demo (it is generated if `share=True` more [on this later](https://gradio.app/getting_started/#sharing-interfaces-publicly))
|
||||
@ -161,7 +162,7 @@ Share links expire after 72 hours. For permanent hosting, see Hosting Gradio App
|
||||
|
||||
### Hosting Gradio Apps on Spaces
|
||||
|
||||
Huggingface provides the infrastructure to permanently host your Gradio model on the internet, for free! You can either drag and drop a folder containing your Gradio model and all related files, or you can point HF Spaces to your Git repository and HP Spaces will pull the Gradio interface from there. See [Huggingface Spaces](http://huggingface.co/spaces/) for more information.
|
||||
Huggingface provides the infrastructure to permanently host your Gradio model on the internet, for free! You can either drag and drop a folder containing your Gradio model and all related files, or you can point HF Spaces to your Git repository and HF Spaces will pull the Gradio interface from there. See [Huggingface Spaces](http://huggingface.co/spaces/) for more information.
|
||||
|
||||

|
||||
|
||||
|
161
guides/using_hugging_face_integrations.md
Normal file
161
guides/using_hugging_face_integrations.md
Normal file
@ -0,0 +1,161 @@
|
||||
# Using Hugging Face Integrations
|
||||
|
||||
related_spaces: https://huggingface.co/spaces/osanseviero/helsinki_translation_en_es, https://huggingface.co/spaces/osanseviero/remove-bg-webcam, https://huggingface.co/spaces/mrm8488/GPT-J-6B, https://huggingface.co/spaces/akhaliq/T0pp, https://huggingface.co/spaces/osanseviero/mix_match_gradio
|
||||
tags: HUB, SPACES, EMBED
|
||||
|
||||
Contributed by <a href="https://huggingface.co/osanseviero">Omar Sanseviero</a> 🦙
|
||||
|
||||
## Introduction
|
||||
|
||||
The Hugging Face Hub is a central platform that has over 30,000 [models](https://huggingface.co/models), 3,000 [datasets](https://huggingface.co/datasets) and 2,000 [demos](https://huggingface.co/spaces), also known as Spaces. From Natural Language Processing to Computer Vision and Speech, the Hub supports multiple domains. Although Hugging Face is famous for its 🤗 transformers library, the Hub also supports dozens of ML libraries, such as PyTorch, TensorFlow, spaCy, and many others.
|
||||
|
||||
Gradio has multiple features that make it extremely easy to leverage existing models and Spaces on the Hub. This guide walks through these features.
|
||||
|
||||
## Using regular inference with `pipeline`
|
||||
|
||||
First, let's build a simple interface that translates text from English to Spanish. Between the over a thousand models shared by the University of Helsinki, there is an [existing model](https://huggingface.co/Helsinki-NLP/opus-mt-en-es), `opus-mt-en-es`, that does precisely this!
|
||||
|
||||
The 🤗 transformers library has a very easy-to-use abstraction, [`pipeline()`](https://huggingface.co/docs/transformers/v4.16.2/en/main_classes/pipelines#transformers.pipeline) that handles most of the complex code to offer a simple API for common tasks. By specifying the task and an (optional) model, you can use an existing model with few lines:
|
||||
|
||||
```python
|
||||
import gradio as gr
|
||||
|
||||
from transformers import pipeline
|
||||
|
||||
pipe = pipeline("translation", model="Helsinki-NLP/opus-mt-en-es")
|
||||
|
||||
def predict(text):
|
||||
return pipe(text)[0]["translation_text"]
|
||||
|
||||
iface = gr.Interface(
|
||||
fn=predict,
|
||||
inputs='text',
|
||||
outputs='text',
|
||||
examples=[["Hello! My name is Omar"]]
|
||||
)
|
||||
|
||||
iface.launch()
|
||||
```
|
||||
|
||||
The previous code produces the following interface, which you can try right here in your browser:
|
||||
|
||||
<iframe src="https://hf.space/gradioiframe/osanseviero/helsinki_translation_en_es/+" frameBorder="0" height="450" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>
|
||||
|
||||
This demo requires installing four libraries: gradio, torch, transformers, and sentencepiece. Apart from that, this is a Gradio with the structure you're used to! The demo is a usual Gradio `Interface` with a prediction function, a specified input, and a specified output. The prediction function executes the `pipeline` function with the given input, retrieves the first (and only) translation result, and returns the `translation_text` field, which you're interested in.
|
||||
|
||||
## Using Hugging Face Inference API
|
||||
|
||||
Hugging Face has a service called the [Inference API](https://huggingface.co/inference-api) which allows you to send HTTP requests to models in the Hub. For transformers-based models, the API can be 2 to 10 times faster than running the inference yourself. The API has a friendly [free tier](https://huggingface.co/pricing).
|
||||
|
||||
Let's try the same demo as above but using the Inference API instead of loading the model yourself. Given a Hugging Face model supported in the Inference API, Gradio can automatically infer the expected input and output and make the underlying server calls, so you don't have to worry about defining the prediction function. Here is what the code would look like!
|
||||
|
||||
```python
|
||||
import gradio as gr
|
||||
|
||||
iface = gr.Interface.load("huggingface/Helsinki-NLP/opus-mt-en-es",
|
||||
examples=[["Hello! My name is Omar"]]
|
||||
)
|
||||
|
||||
iface.launch()
|
||||
```
|
||||
|
||||
Let's go over some of the key differences:
|
||||
|
||||
* `Interface.load()` is used instead of the usual `Interface()`.
|
||||
* `Interface.load()` receives a string with the prefix `huggingface/`, and then the model repository ID.
|
||||
* Since the input, output and prediction functions are not needed, you only need to modify the UI parts (such as `title`, `description`, and `examples`).
|
||||
* There is no need to install any dependencies (except Gradio) since you are not loading the model on your computer.
|
||||
|
||||
You might notice that the first inference takes about 20 seconds. This happens since the Inference API is loading the model in the server. You get some benefits afterward:
|
||||
|
||||
* The inference will be much faster.
|
||||
* The server caches your requests.
|
||||
* You get built-in automatic scaling.
|
||||
|
||||
## Hosting your Gradio demos
|
||||
|
||||
|
||||
[Hugging Face Spaces](hf.co/spaces) allows anyone to host their Gradio demos freely. The community shares oven 2,000 Spaces. Uploading your Gradio demos take a couple of minutes. You can head to [hf.co/new-space](https://huggingface.co/new-space), select the Gradio SDK, create an `app.py` file, and voila! You have a demo you can share with anyone else.
|
||||
|
||||
## Building demos based on other demos
|
||||
|
||||
You can use the existing Spaces to tweak the UI or combine multiple demos. Let's find how to do this! First, let's take a look at an existing demo that does background removal.
|
||||
|
||||
This is a Gradio demo [already shared](https://huggingface.co/spaces/eugenesiow/remove-bg) by a community member. You can load an existing demo using `Interface` in a syntax similar to how it's done for the Inference API. It just takes two lines of code and with the prefix `spaces`.
|
||||
|
||||
```python
|
||||
import gradio as gr
|
||||
|
||||
gr.Interface.load("spaces/eugenesiow/remove-bg").launch()
|
||||
```
|
||||
|
||||
The code snippet above will load the same interface as the corresponding Space demo.
|
||||
|
||||
<iframe src="https://hf.space/gradioiframe/eugenesiow/remove-bg/+" frameBorder="0" height="900" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>
|
||||
|
||||
|
||||
You can change UI elements, such as the title or theme, but also change the expected type. The previous Space expected users to upload images. What if you would like users to have their webcam and remove the background from there? You can load the Space but change the source of input as follows:
|
||||
|
||||
```python
|
||||
import gradio as gr
|
||||
|
||||
gr.Interface.load(
|
||||
"spaces/eugenesiow/remove-bg",
|
||||
inputs=[gr.inputs.Image(label="Input Image", source="webcam")]
|
||||
).launch()
|
||||
```
|
||||
|
||||
The code above generates the following demo.
|
||||
|
||||
<iframe src="https://hf.space/gradioiframe/osanseviero/remove-bg-webcam/+" frameBorder="0" height="600" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>
|
||||
|
||||
As you can see, the demo looks the same, but it uses a webcam input instead of user-uploaded images.
|
||||
|
||||
## Using multiple Spaces
|
||||
|
||||
Sometimes a single model inference will not be enough: you might want to call multiple models by piping them (using the output of model A as the input of model B). `Series` can achieve this. Other times, you might want to run two models in parallel to compare them. `Parallel` can do this!
|
||||
|
||||
Let's combine the notion of running things in parallel with the Spaces integration. The [GPT-J-6B](https://huggingface.co/spaces/mrm8488/GPT-J-6B) Space demos a model that generates text using a model called GPT-J. The [T0pp](https://huggingface.co/spaces/akhaliq/T0pp) Space demos another generative model called T0pp. Let's see how to combine both into one.
|
||||
|
||||
```python
|
||||
import gradio as gr
|
||||
|
||||
iface1 = gr.Interface.load("spaces/mrm8488/GPT-J-6B")
|
||||
iface2 = gr.Interface.load("spaces/akhaliq/T0pp")
|
||||
|
||||
iface3 = gr.mix.Parallel(
|
||||
iface1, iface2,
|
||||
examples = [
|
||||
['Which country will win the 2002 World Cup?'],
|
||||
["A is the son's of B's uncle. What is the family relationship between A and B?"],
|
||||
["In 2030, "],
|
||||
])
|
||||
|
||||
iface3.launch()
|
||||
```
|
||||
|
||||
`iface1` and `iface2` are loading existing Spaces. Then, with `Parallel`, you can run the interfaces parallelly. When you click submit, you will get the output for both interfaces. This is how the demo looks like:
|
||||
|
||||
<iframe src="https://hf.space/gradioiframe/osanseviero/mix_match_gradio/+" frameBorder="0" height="450" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>
|
||||
|
||||
Although both models are generative, you can see that the way both models behave is very different. That's a powerful application of `Parallel`!
|
||||
|
||||
## Embedding your Space demo on other websites
|
||||
|
||||
Throughout this guide, you've seen there are Gradio demos embedded. You can also do this on own website! The first step is to create a Space with the demo you want to showcase. You can embed it in your HTML code, as shown in the following self-contained example.
|
||||
|
||||
```bash
|
||||
<iframe src="https://hf.space/gradioiframe/osanseviero/mix_match_gradio/+" frameBorder="0" height="450" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>
|
||||
```
|
||||
|
||||
## Recap
|
||||
|
||||
That's it! Let's recap what you can do:
|
||||
|
||||
1. Host your Gradio demos in Spaces.
|
||||
2. Use the Inference API to build demos in two lines of code.
|
||||
3. Load existing Spaces and modify them.
|
||||
4. Combine multiple Spaces by running them sequentially or parallelly.
|
||||
5. Embed your Space demo directly on a website.
|
||||
|
||||
🤗
|
@ -5,7 +5,7 @@ if [ -z "$(ls | grep CONTRIBUTING.md)" ]; then
|
||||
else
|
||||
echo "Uploading to pypi"
|
||||
set -e
|
||||
git pull
|
||||
git pull origin master
|
||||
old_version=$(grep -Po "(?<=version=\")[^\"]+(?=\")" setup.py)
|
||||
echo "Current version is $old_version. New version?"
|
||||
read new_version
|
||||
|
2
setup.py
2
setup.py
@ -5,7 +5,7 @@ except ImportError:
|
||||
|
||||
setup(
|
||||
name="gradio",
|
||||
version="2.8.5",
|
||||
version="2.8.10",
|
||||
include_package_data=True,
|
||||
description="Python library for easily interacting with trained machine learning models",
|
||||
author="Abubakar Abid, Ali Abid, Ali Abdalla, Dawood Khan, Ahsen Khaliq",
|
||||
|
@ -214,7 +214,7 @@ class TestLoadInterface(unittest.TestCase):
|
||||
|
||||
def test_speech_recognition_model(self):
|
||||
interface_info = gr.external.load_interface(
|
||||
"models/jonatasgrosman/wav2vec2-large-xlsr-53-english"
|
||||
"models/facebook/wav2vec2-base-960h"
|
||||
)
|
||||
io = gr.Interface(**interface_info)
|
||||
io.api_mode = True
|
||||
|
@ -4,6 +4,7 @@ import os
|
||||
import unittest
|
||||
|
||||
from gradio import queueing
|
||||
from gradio.routes import QueuePushBody
|
||||
|
||||
os.environ["GRADIO_ANALYTICS_ENABLED"] = "False"
|
||||
|
||||
@ -30,9 +31,11 @@ class TestQueuingActions(unittest.TestCase):
|
||||
queueing.close()
|
||||
|
||||
def test_push_pop_status(self):
|
||||
hash1, position = queueing.push({"data": "test1"}, "predict")
|
||||
request = QueuePushBody(data="test1", action="predict")
|
||||
hash1, position = queueing.push(request)
|
||||
self.assertEquals(position, 0)
|
||||
hash2, position = queueing.push({"data": "test2"}, "predict")
|
||||
request = QueuePushBody(data="test2", action="predict")
|
||||
hash2, position = queueing.push(request)
|
||||
self.assertEquals(position, 1)
|
||||
status, position = queueing.get_status(hash2)
|
||||
self.assertEquals(status, "QUEUED")
|
||||
@ -43,8 +46,9 @@ class TestQueuingActions(unittest.TestCase):
|
||||
self.assertEquals(action, "predict")
|
||||
|
||||
def test_jobs(self):
|
||||
hash1, _ = queueing.push({"data": "test1"}, "predict")
|
||||
hash2, position = queueing.push({"data": "test1"}, "predict")
|
||||
request = QueuePushBody(data="test1", action="predict")
|
||||
hash1, _ = queueing.push(request)
|
||||
hash2, position = queueing.push(request)
|
||||
self.assertEquals(position, 1)
|
||||
|
||||
queueing.start_job(hash1)
|
||||
|
@ -44,6 +44,22 @@ class TestRoutes(unittest.TestCase):
|
||||
self.assertTrue("durations" in output)
|
||||
self.assertTrue("avg_durations" in output)
|
||||
|
||||
def test_state(self):
|
||||
def predict(input, history=""):
|
||||
history += input
|
||||
return history, history
|
||||
|
||||
io = Interface(predict, ["textbox", "state"], ["textbox", "state"])
|
||||
app, _, _ = io.launch(prevent_thread_lock=True)
|
||||
client = TestClient(app)
|
||||
response = client.post("/api/predict/", json={"data": ["test", None]})
|
||||
output = dict(response.json())
|
||||
print("output", output)
|
||||
self.assertEqual(output["data"], ["test", None])
|
||||
response = client.post("/api/predict/", json={"data": ["test", None]})
|
||||
output = dict(response.json())
|
||||
self.assertEqual(output["data"], ["testtest", None])
|
||||
|
||||
def test_queue_push_route(self):
|
||||
queueing.push = mock.MagicMock(return_value=(None, None))
|
||||
response = self.client.post(
|
||||
|
@ -15,6 +15,7 @@ from gradio.utils import (
|
||||
json,
|
||||
launch_analytics,
|
||||
readme_to_html,
|
||||
santize_for_csv,
|
||||
version_check,
|
||||
)
|
||||
|
||||
@ -116,5 +117,23 @@ class TestIPAddress(unittest.TestCase):
|
||||
self.assertEqual(ip, "No internet connection")
|
||||
|
||||
|
||||
class TestSanitizeForCSV(unittest.TestCase):
|
||||
def test_safe(self):
|
||||
safe_data = santize_for_csv("abc")
|
||||
self.assertEquals(safe_data, "abc")
|
||||
safe_data = santize_for_csv(["def"])
|
||||
self.assertEquals(safe_data, ["def"])
|
||||
safe_data = santize_for_csv([["abc"]])
|
||||
self.assertEquals(safe_data, [["abc"]])
|
||||
|
||||
def test_unsafe(self):
|
||||
safe_data = santize_for_csv("=abc")
|
||||
self.assertEquals(safe_data, "'=abc")
|
||||
safe_data = santize_for_csv(["abc", "+abc"])
|
||||
self.assertEquals(safe_data, ["abc", "'+abc"])
|
||||
safe_data = santize_for_csv([["abc", "=abc"]])
|
||||
self.assertEquals(safe_data, [["abc", "'=abc"]])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
@ -14,12 +14,14 @@ let postData = async (url: string, body: unknown) => {
|
||||
};
|
||||
|
||||
export const fn = async (
|
||||
session_hash: string,
|
||||
api_endpoint: string,
|
||||
action: string,
|
||||
data: Record<string, unknown>,
|
||||
queue: boolean,
|
||||
queue_callback: (pos: number | null, is_initial?: boolean) => void
|
||||
) => {
|
||||
data["session_hash"] = session_hash;
|
||||
if (queue && ["predict", "interpret"].includes(action)) {
|
||||
data["action"] = action;
|
||||
const output = await postData(api_endpoint + "queue/push/", data);
|
||||
|
@ -358,12 +358,12 @@
|
||||
/>
|
||||
{/if}
|
||||
<span
|
||||
class=" cursor-default w-full"
|
||||
class="cursor-default w-full"
|
||||
class:opacity-0={editing === id}
|
||||
tabindex="-1"
|
||||
role="button"
|
||||
>
|
||||
{value}
|
||||
{value ?? ""}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
export let value: Array<Array<string | number>>;
|
||||
</script>
|
||||
|
||||
<table class="input-dataframe-example">
|
||||
{#each value.slice(0, 3) as row}
|
||||
<tr>
|
||||
{#each row.slice(0, 3) as cell}
|
||||
<td class="p-2">{cell}</td>
|
||||
{/each}
|
||||
{#if row.length > 3}
|
||||
<td class="p-2">...</td>
|
||||
{/if}
|
||||
</tr>
|
||||
{/each}
|
||||
{#if value.length > 3}
|
||||
<tr>
|
||||
{#each Array(Math.min(4, value[0].length)) as _}
|
||||
<td class="p-2">...</td>
|
||||
{/each}
|
||||
</tr>
|
||||
{/if}
|
||||
</table>
|
@ -1,5 +1,7 @@
|
||||
import Component from "./DataFrame.svelte";
|
||||
import ExampleComponent from "./Example.svelte";
|
||||
|
||||
export default {
|
||||
component: Component
|
||||
component: Component,
|
||||
example: ExampleComponent
|
||||
};
|
||||
|
@ -66,7 +66,11 @@
|
||||
{$_("interface.click_to_upload")}
|
||||
</Upload>
|
||||
{:else if source === "webcam"}
|
||||
<Webcam on:capture={({ detail }) => setValue(detail)} {static_src} />
|
||||
<Webcam
|
||||
mode="image"
|
||||
on:capture={({ detail }) => setValue(detail)}
|
||||
{static_src}
|
||||
/>
|
||||
{/if}
|
||||
{:else if tool === "select"}
|
||||
<Cropper image={value} on:crop={({ detail }) => setValue(detail)} />
|
||||
@ -85,6 +89,8 @@
|
||||
{static_src}
|
||||
/>
|
||||
|
||||
<img class="w-full h-full object-contain" src={value} alt="" />
|
||||
{:else}
|
||||
<img class="w-full h-full object-contain" src={value} alt="" />
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -2,6 +2,7 @@
|
||||
import Upload from "../../utils/Upload.svelte";
|
||||
import ModifyUpload from "../../utils/ModifyUpload.svelte";
|
||||
import { prettyBytes, playable } from "../../utils/helpers";
|
||||
import Webcam from "../../utils/Webcam.svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
|
||||
interface Data {
|
||||
@ -44,6 +45,14 @@
|
||||
<br />- {$_("interface.or")} -<br />
|
||||
{$_("interface.click_to_upload")}
|
||||
</Upload>
|
||||
{:else if source === "webcam"}
|
||||
<Webcam
|
||||
mode="video"
|
||||
on:capture={({ detail }) => {
|
||||
setValue(detail);
|
||||
}}
|
||||
{static_src}
|
||||
/>
|
||||
{/if}
|
||||
{:else}
|
||||
<ModifyUpload clear={() => setValue(null)} {theme} {static_src} />
|
||||
|
@ -1,11 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { afterUpdate } from "svelte";
|
||||
export let value: string;
|
||||
export let theme: string;
|
||||
|
||||
let audio: HTMLAudioElement;
|
||||
afterUpdate(() => (audio.src = value));
|
||||
</script>
|
||||
|
||||
<audio {theme} controls>
|
||||
<audio bind:this={audio} class="w-full" {theme} controls>
|
||||
<source src={value} />
|
||||
</audio>
|
||||
|
||||
<style lang="postcss">
|
||||
</style>
|
||||
|
@ -2,6 +2,13 @@
|
||||
import { createEventDispatcher, onMount } from "svelte";
|
||||
|
||||
export let static_src: string;
|
||||
export let mode: "video" | "image";
|
||||
|
||||
let recording = false;
|
||||
let recorded_blobs: BlobPart[] = [];
|
||||
let stream: MediaStream;
|
||||
let mimeType: string;
|
||||
let media_recorder: MediaRecorder;
|
||||
|
||||
let video_source: HTMLVideoElement;
|
||||
let canvas: HTMLCanvasElement;
|
||||
@ -10,9 +17,9 @@
|
||||
|
||||
onMount(() => (canvas = document.createElement("canvas")));
|
||||
|
||||
async function access_webcam() {
|
||||
async function accessWebcam() {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({
|
||||
stream = await navigator.mediaDevices.getUserMedia({
|
||||
video: true
|
||||
});
|
||||
video_source.srcObject = stream;
|
||||
@ -22,13 +29,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
function clearphoto() {
|
||||
var context = canvas.getContext("2d")!;
|
||||
context.fillStyle = "#AAA";
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
|
||||
function takepicture() {
|
||||
function takePicture() {
|
||||
var context = canvas.getContext("2d")!;
|
||||
|
||||
if (video_source.videoWidth && video_source.videoHeight) {
|
||||
@ -47,29 +48,72 @@
|
||||
}
|
||||
}
|
||||
|
||||
access_webcam();
|
||||
function takeRecording() {
|
||||
if (recording) {
|
||||
media_recorder.stop();
|
||||
let video_blob = new Blob(recorded_blobs, { type: mimeType });
|
||||
let ReaderObj = new FileReader();
|
||||
ReaderObj.onload = function (e) {
|
||||
if (e.target) {
|
||||
dispatch("capture", {
|
||||
data: e.target.result,
|
||||
name: "sample." + mimeType.substring(6),
|
||||
is_example: false
|
||||
});
|
||||
}
|
||||
};
|
||||
ReaderObj.readAsDataURL(video_blob);
|
||||
} else {
|
||||
recorded_blobs = [];
|
||||
let validMimeTypes = ["video/webm", "video/mp4"];
|
||||
for (let validMimeType of validMimeTypes) {
|
||||
if (MediaRecorder.isTypeSupported(validMimeType)) {
|
||||
mimeType = validMimeType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mimeType === null) {
|
||||
console.error("No supported MediaRecorder mimeType");
|
||||
return;
|
||||
}
|
||||
media_recorder = new MediaRecorder(stream, {
|
||||
mimeType: mimeType
|
||||
});
|
||||
media_recorder.addEventListener("dataavailable", function (e) {
|
||||
recorded_blobs.push(e.data);
|
||||
});
|
||||
media_recorder.start(200);
|
||||
}
|
||||
recording = !recording;
|
||||
}
|
||||
|
||||
accessWebcam();
|
||||
</script>
|
||||
|
||||
<div class="h-full w-full relative">
|
||||
<!-- svelte-ignore a11y-media-has-caption -->
|
||||
<video bind:this={video_source} class=" h-full w-full" />
|
||||
<button
|
||||
on:click={takepicture}
|
||||
style="background-color: #333;"
|
||||
on:click={mode === "image" ? takePicture : takeRecording}
|
||||
class="rounded-full w-10 h-10 flex justify-center items-center absolute inset-x-0 bottom-2 m-auto drop-shadow-lg"
|
||||
class:recording
|
||||
>
|
||||
<img
|
||||
style="color: white"
|
||||
src="{static_src}/static/img/camera.svg"
|
||||
alt="take a screenshot"
|
||||
class="w-2/4 h-2/4"
|
||||
/>
|
||||
{#if !recording}
|
||||
<img
|
||||
style="color: white"
|
||||
src="{static_src}/static/img/camera.svg"
|
||||
alt="take a screenshot"
|
||||
class="w-2/4 h-2/4"
|
||||
/>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
video {
|
||||
-webkit-transform: scaleX(-1);
|
||||
transform: scaleX(-1);
|
||||
button {
|
||||
@apply bg-gray-700;
|
||||
}
|
||||
button.recording {
|
||||
@apply bg-red-700 border-4 border-red-600;
|
||||
}
|
||||
</style>
|
||||
|
@ -51,6 +51,7 @@ interface Config {
|
||||
space?: string;
|
||||
detail: string;
|
||||
dark: boolean;
|
||||
auth_required: boolean;
|
||||
}
|
||||
|
||||
window.launchGradio = (config: Config, element_query: string) => {
|
||||
@ -77,7 +78,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
|
||||
@ -96,7 +97,8 @@ window.launchGradio = (config: Config, element_query: string) => {
|
||||
config.dark = true;
|
||||
target.classList.add("dark");
|
||||
}
|
||||
config.fn = fn.bind(null, config.root + "api/");
|
||||
let session_hash = Math.random().toString(36).substring(2);
|
||||
config.fn = fn.bind(null, session_hash, config.root + "api/");
|
||||
if (config.mode === "blocks") {
|
||||
new Blocks({
|
||||
target: target,
|
||||
|
@ -52,8 +52,11 @@ def render_index():
|
||||
generated_template.write(output_html)
|
||||
|
||||
|
||||
guide_files = ["getting_started.md"]
|
||||
all_guides = sorted(os.listdir(GRADIO_GUIDES_DIR))
|
||||
guide_files.extend([file for file in all_guides if file != "getting_started.md"])
|
||||
guides = []
|
||||
for guide in sorted(os.listdir(GRADIO_GUIDES_DIR)):
|
||||
for guide in guide_files:
|
||||
if guide.lower() == "readme.md":
|
||||
continue
|
||||
guide_name = guide[:-3]
|
||||
@ -73,13 +76,23 @@ for guide in sorted(os.listdir(GRADIO_GUIDES_DIR)):
|
||||
spaces = None
|
||||
if "related_spaces: " in guide_content:
|
||||
spaces = guide_content.split("related_spaces: ")[1].split("\n")[0].split(", ")
|
||||
title = guide_content.split("\n")[0]
|
||||
contributor = None
|
||||
if "Contributed by " in guide_content:
|
||||
contributor = guide_content.split("Contributed by ")[1].split("\n")[0]
|
||||
|
||||
url = f"https://gradio.app/{guide_name}/"
|
||||
|
||||
guide_content = "\n".join(
|
||||
[
|
||||
line
|
||||
for line in guide_content.split("\n")
|
||||
if not (line.startswith("tags: ") or line.startswith("related_spaces: "))
|
||||
if not (
|
||||
line.startswith("tags: ")
|
||||
or line.startswith("related_spaces: ")
|
||||
or line.startswith("Contributed by ")
|
||||
or line == title
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
@ -91,15 +104,15 @@ for guide in sorted(os.listdir(GRADIO_GUIDES_DIR)):
|
||||
"tags": tags,
|
||||
"spaces": spaces,
|
||||
"url": url,
|
||||
"contributor": contributor,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def render_guides_main():
|
||||
filtered_guides = [guide for guide in guides if guide["name"] != "getting_started"]
|
||||
with open("src/guides_main_template.html", encoding="utf-8") as template_file:
|
||||
template = Template(template_file.read())
|
||||
output_html = template.render(guides=filtered_guides, navbar_html=navbar_html)
|
||||
output_html = template.render(guides=guides, navbar_html=navbar_html)
|
||||
os.makedirs(os.path.join("generated", "guides"), exist_ok=True)
|
||||
with open(
|
||||
os.path.join("generated", "guides", "index.html"), "w", encoding="utf-8"
|
||||
@ -188,6 +201,7 @@ def render_guides():
|
||||
guide_name=guide["name"],
|
||||
spaces=guide["spaces"],
|
||||
tags=guide["tags"],
|
||||
contributor=guide["contributor"],
|
||||
**GRADIO_ASSETS,
|
||||
)
|
||||
generated_template.write(output_html)
|
||||
|
@ -129,12 +129,14 @@
|
||||
<h2 class="font-semibold group-hover:underline text-xl">{{ guide.pretty_name }}
|
||||
</h2>
|
||||
<div class="tags-holder">
|
||||
{% if guide.tags is not none %}
|
||||
<p>
|
||||
{% for tag in guide.tags %}
|
||||
{{ tag }}<!--
|
||||
-->{% if not loop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
@ -220,7 +222,7 @@
|
||||
if (txtValue.toUpperCase().indexOf(filter) > -1 || guideContent.toUpperCase().indexOf(filter) > -1) {
|
||||
a[{{ loop.index - 1}}].style.display = "";
|
||||
} else {
|
||||
a[{{ loop.index - 1 }}].style.display = "none";
|
||||
a[{{ loop.index - 1}}].style.display = "none";
|
||||
counter++;
|
||||
}
|
||||
{% endfor %}
|
||||
|
@ -83,6 +83,11 @@
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: auto;
|
||||
padding-inline-start: 40px;
|
||||
list-style-type: none;
|
||||
}
|
||||
</style>
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-156449732-1"></script>
|
||||
<script>
|
||||
@ -98,6 +103,13 @@
|
||||
<body class="bg-white text-gray-900 text-md sm:text-lg">
|
||||
{{navbar_html|safe}}
|
||||
<div class="container mx-auto max-w-4xl px-4 mb-12 mt-6" id="guide-template">
|
||||
<div class="prose mt-6 mb-6">
|
||||
<h1 id="{{guide_name}}" class="header">{{title}}</h1>
|
||||
{% if contributor is not none %}
|
||||
<p>Contributed by {{contributor}}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if spaces is not none %}
|
||||
<div id='spaces-holder'>
|
||||
<a href='https://hf.co/spaces' target='_blank'>
|
||||
@ -113,7 +125,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="prose mt-6">
|
||||
<div class="prose mt-6 mb-6">
|
||||
{{ template_html|safe }}
|
||||
</div>
|
||||
</div>
|
||||
|
7
website/reload_website.sh
Executable file → Normal file
7
website/reload_website.sh
Executable file → Normal file
@ -1,4 +1,5 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
. /home/ubuntu/.bashrc
|
||||
export PATH="/usr/local/bin:/usr/bin:/bin"
|
||||
|
||||
@ -13,4 +14,8 @@ else
|
||||
fi
|
||||
docker-compose build
|
||||
docker-compose up -d
|
||||
fi
|
||||
|
||||
LATEST=$(git log -1 | fgrep commit)$(git log -1 | tail -1)
|
||||
curl -X POST -H 'Content-type: application/json' --data '{"text":"gradio.app relaoded successfully! :ship:\n\n Latest live commit:\n>`'"${LATEST}"'`"}' ${SLACK_WEBHOOK}
|
||||
|
||||
fi
|
||||
|
11
website/reload_with_notification.sh
Normal file
11
website/reload_with_notification.sh
Normal file
@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
. /home/ubuntu/.bashrc
|
||||
export PATH="/usr/local/bin:/usr/bin:/bin"
|
||||
|
||||
ERROR=$(sh ./reload_website.sh 2>&1)
|
||||
|
||||
if ! [ $? -eq 0 ]; then
|
||||
data=$( jo text="$(echo "gradio.app is not tracking master :o: \nError:\n\n\`\`\`'$ERROR'\`\`\`")")
|
||||
echo "$data"
|
||||
curl -X POST -H 'Content-type: application/json' --data "$data" ${SLACK_WEBHOOK}
|
||||
fi
|
Loading…
x
Reference in New Issue
Block a user