merge changes

This commit is contained in:
Ali Abid 2021-12-30 08:24:38 +00:00
commit 55eb120704
7 changed files with 160 additions and 118 deletions

View File

@ -1,5 +1,5 @@
# Contributing to Gradio
You can start by forking or cloning the repo (https://github.com/gradio-app/gradio.git) and creating your own branch to work from.
More than 30 awesome developers have contributed to the `gradio` library, and we'd be thrilled if you would like be the next `gradio` contributor! You can start by forking or cloning the repo (https://github.com/gradio-app/gradio.git) and creating your own branch to work from.
Next, to install the local development version of Gradio:
* Navigate to the `/gradio` subfolder and run `pip install -e .`.
@ -7,23 +7,24 @@ Next, to install the local development version of Gradio:
When installing locally, you may also need to build the front end:
* Navigate to the `/frontend` subfolder and run `npm install`.
* Then run `npm run build`.
* Then you can run `npm run start` to start a local development server (on port 3000) that responds to any changes in the frontend
### Structure of the Repository
It's helpful to know the overall structure of the repository so that you can focus on the part of the source code you'd like to contribute to
* `/gradio`: contains the source code for the actual Python library
* `/gradio/interface.py`: contains the source code for the core `Interface` class
* `/test`: contains unit tests for the Python library
* `/gradio`: contains the Python source code for the library
* `/gradio/interface.py`: contains the Python source code for the core `Interface` class (**start HERE!**)
* `/frontend`: contains the HTML/JS/CSS source code for the library
* `/test`: contains Python unit tests for the library
* `/demo`: contains demos that are used in the documentation as well as for integration tests
* `/website`: contains the code for the Gradio website (www.gradio.app). See the README in the `/website` folder for more details
### Continuous Integration and Testing
All PRs must pass the continuous integration tests before merging. To test locally, you can run `python -m unittest` from the `/gradio` subfolder.
### Submitting PRs
All PRs must pass the continuous integration tests before merging. To test locally, you can run `python -m unittest` from `/` (the directory where you cloned this repo).
## Submitting PRs
All PRs should be against `master`. Direct commits to master are blocked, and PRs require an approving review
to merge into master. By convention, the Gradio maintainers will review PRs when:
* An initial review has been requested, and
@ -31,3 +32,5 @@ to merge into master. By convention, the Gradio maintainers will review PRs when
We ask that you make sure initial CI checks are passing before requesting a review.
One of the Gradio maintainers will merge the PR when all the checks are passing.
*Could these guidelines be clearer? Feel free to open a PR to help us faciltiate open-source contributions!*

View File

@ -37,6 +37,7 @@
"start": "cross-env REACT_APP_BACKEND_URL='http://localhost:7860/' craco start",
"format": "prettier-eslint --write '**/*.js*'",
"build": "cross-env REACT_APP_BACKEND_URL='' REACT_APP_VERSION=$(cat ../gradio/version.txt) GENERATE_SOURCEMAP=false craco build",
"build:win": "cross-env REACT_APP_BACKEND_URL='' GENERATE_SOURCEMAP=false craco build",
"eject": "react-scripts eject"
},
"eslintConfig": {

View File

@ -464,7 +464,7 @@ export class GradioInterface extends React.Component {
) : (
false
)}
{this.props.allow_flagging ? (
{this.props.allow_flagging === "manual" ? (
this.props.flagging_options === null ? (
<button
className={classNames("panel_button", "flag", {

View File

@ -64,11 +64,9 @@ class Interface:
(gradio.Interface): a Gradio Interface object for the given model
"""
interface_info = load_interface(name, src, api_key, alias)
# create a dictionary of kwargs without overwriting the original interface_info dict because it is mutable
# and that can cause some issues since the internal prediction function may rely on the original interface_info dict
kwargs = dict(interface_info, **kwargs)
interface = cls(**kwargs)
interface.api_mode = True # set api mode to true so that the interface will not preprocess/postprocess
interface.api_mode = True # So interface doesn't run pre/postprocess.
return interface
@classmethod
@ -77,7 +75,8 @@ class Interface:
pipeline: transformers.Pipeline,
**kwargs) -> Interface:
"""
Class method to construct an Interface from a Hugging Face transformers.Pipeline.
Construct an Interface from a Hugging Face transformers.Pipeline.
Parameters:
pipeline (transformers.Pipeline):
Returns:
(gradio.Interface): a Gradio Interface object from the given Pipeline
@ -112,7 +111,7 @@ class Interface:
height=None,
width=None,
allow_screenshot: bool = True,
allow_flagging: bool = None,
allow_flagging: Optional[str] = None,
flagging_options: List[str]=None,
encrypt=None,
show_tips=None,
@ -120,8 +119,7 @@ class Interface:
analytics_enabled: Optional[bool] = None,
enable_queue=None,
api_mode=None,
flagging_callback: FlaggingCallback = CSVLogger()
):
flagging_callback: FlaggingCallback = CSVLogger()):
"""
Parameters:
fn (Union[Callable, List[Callable]]): the function to wrap an interface around.
@ -142,8 +140,8 @@ class Interface:
theme (str): Theme to use - one of "default", "huggingface", "grass", "peach". Add "dark" prefix, e.g. "darkpeach" or "darkdefault" for darktheme.
css (str): custom css or path to custom css file to use with interface.
allow_screenshot (bool): if False, users will not see a button to take a screenshot of the interface.
allow_flagging (bool): if False, users will not see a button to flag an input and output.
flagging_options (List[str]): if not None, provides options a user must select when flagging.
allow_flagging (str): one of "never", "auto", or "manual". If "never" or "auto", users will not see a button to flag an input and output. If "manual", users will see a button to flag. If "auto", every prediction will be automatically flagged. If "manual", samples are flagged when the user clicks flag button. Can be set with environmental variable GRADIO_ALLOW_FLAGGING.
flagging_options (List[str]): if provided, allows user to select from the list of options when flagging. Only applies if allow_flagging is "manual".
encrypt (bool): DEPRECATED. If True, flagged data will be encrypted by key provided by creator at launch
flagging_dir (str): what to name the dir where flagged data is stored.
show_tips (bool): DEPRECATED. if True, will occasionally show tips about new Gradio features
@ -204,6 +202,10 @@ class Interface:
self.theme = theme
self.height = height
self.width = width
if self.height is not None or self.width is not None:
warnings.warn("The `height` and `width` parameters in `Interface` "
"are deprecated and should be passed into launch().")
if css is not None and os.path.exists(css):
with open(css) as css_file:
self.css = css_file.read()
@ -222,11 +224,33 @@ class Interface:
self.simple_server = None
self.allow_screenshot = allow_screenshot
# For allow_flagging and analytics_enabled: (1) first check for parameter, (2) check for environment variable, (3) default to True
# For analytics_enabled and allow_flagging: (1) first check for
# parameter, (2) check for env variable, (3) default to True/"manual"
self.analytics_enabled = analytics_enabled if analytics_enabled is not None else os.getenv("GRADIO_ANALYTICS_ENABLED", "True")=="True"
self.allow_flagging = allow_flagging if allow_flagging is not None else os.getenv("GRADIO_ALLOW_FLAGGING", "True")=="True"
if allow_flagging is None:
allow_flagging = os.getenv("GRADIO_ALLOW_FLAGGING", "manual")
if allow_flagging==True:
warnings.warn("The `allow_flagging` parameter in `Interface` now"
"takes a string value ('auto', 'manual', or 'never')"
", not a boolean. Setting parameter to: 'manual'.")
self.allow_flagging = "manual"
elif allow_flagging=="manual":
self.allow_flagging = "manual"
elif allow_flagging==False:
warnings.warn("The `allow_flagging` parameter in `Interface` now"
"takes a string value ('auto', 'manual', or 'never')"
", not a boolean. Setting parameter to: 'never'.")
self.allow_flagging = "never"
elif allow_flagging=="never":
self.allow_flagging = "never"
elif allow_flagging=="auto":
self.allow_flagging = "auto"
else:
raise ValueError("Invalid value for `allow_flagging` parameter."
"Must be: 'auto', 'manual', or 'never'.")
self.flagging_options = flagging_options
self.flagging_callback: FlaggingCallback = flagging_callback
self.flagging_callback = flagging_callback
self.flagging_dir = flagging_dir
self.save_to = None
@ -319,14 +343,16 @@ class Interface:
called_directly: bool = False
) -> List[Any] | Tuple[List[Any], List[float]]:
"""
This is the method that actually runs the prediction function with the given (processed) inputs.
Runs the prediction function with the given (already processed) inputs.
Parameters:
processed_input (list): A list of processed inputs.
return_duration (bool): Whether to return the duration of the prediction.
called_directly (bool): Whether the prediction is being called directly (i.e. as a function, not through the GUI).
called_directly (bool): Whether the prediction is being called
directly (i.e. as a function, not through the GUI).
Returns:
predictions (list): A list of predictions (not post-processed).
durations (list): A list of durations for each prediction (only if `return_duration` is True).
durations (list): A list of durations for each prediction
(only returned if `return_duration` is True).
"""
if self.api_mode: # Serialize the input
processed_input = [input_component.serialize(processed_input[i], called_directly)
@ -363,6 +389,8 @@ class Interface:
raw_input: List[Any]
) -> Tuple[List[Any], List[float]]:
"""
First preprocesses the input, then runs prediction using
self.run_prediction(), then postprocesses the output.
Parameters:
raw_input: a list of raw inputs to process and apply the prediction(s) on.
Returns:
@ -370,7 +398,8 @@ class Interface:
duration: a list of time deltas measuring inference time for each prediction fn.
"""
processed_input = [input_component.preprocess(raw_input[i])
for i, input_component in enumerate(self.input_components)]
for i, input_component in enumerate(
self.input_components)]
predictions, durations = self.run_prediction(
processed_input, return_duration=True)
processed_output = [output_component.postprocess(predictions[i]) if predictions[i] is not None else None
@ -411,13 +440,12 @@ class Interface:
def test_launch(self) -> None:
for predict_fn in self.predict:
print("Test launch: {}()...".format(predict_fn.__name__), end=' ')
raw_input = []
for input_component in self.input_components:
if input_component.test_input is None: # If no test input is defined for that input interface
if input_component.test_input is None:
print("SKIPPED")
break
else: # If a test input is defined for each interface object
else:
raw_input.append(input_component.test_input)
else:
self.process(raw_input)
@ -456,8 +484,8 @@ class Interface:
private_endpoint (str): If provided, the public URL of the interface will be this endpoint (should generally be unchanged).
prevent_thread_lock (bool): If True, the interface will block the main thread while the server is running.
show_error (bool): If True, any errors in the interface will be printed in the browser console log
server_port (int): will start gradio app on this port (if available)
server_name (str): to make app accessible on local network, set this to "0.0.0.0".
server_port (int): will start gradio app on this port (if available). Can be set by environment variable GRADIO_SERVER_PORT.
server_name (str): to make app accessible on local network, set this to "0.0.0.0". Can be set by environment variable GRADIO_SERVER_NAME.
show_tips (bool): if True, will occasionally show tips about new Gradio features
enable_queue (bool): if True, inference requests will be served through a queue instead of with parallel threads. Required for longer inference times (> 1min) to prevent timeout.
width (int): The width in pixels of the <iframe> element containing the interface (used if inline=True)
@ -469,7 +497,6 @@ class Interface:
path_to_local_server (str): Locally accessible link
share_url (str): Publicly accessible link (if share=True)
"""
# Set up local flask server
self.cache_examples = cache_examples
if auth and not callable(auth) and not isinstance(auth[0], tuple) and not isinstance(auth[0], list):
auth = [auth]
@ -477,13 +504,15 @@ class Interface:
self.auth_message = auth_message
self.show_tips = show_tips
self.show_error = show_error
self.height = self.height or height # if height is not set in constructor, use the one provided here
self.width = self.width or width # if width is not set in constructor, use the one provided here
self.height = self.height or height
self.width = self.width or width
if self.encrypt is None:
self.encrypt = encrypt # if encrypt is not set in constructor, use the one provided here
self.encrypt = encrypt
if self.encrypt:
self.encryption_key = encryptor.get_key(
getpass.getpass("Enter key for encryption: "))
if self.enable_queue is None:
self.enable_queue = enable_queue
if self.allow_flagging:
@ -495,7 +524,6 @@ class Interface:
if self.cache_examples:
cache_interface_examples(self)
# Launch local flask server
server_port, path_to_local_server, app, thread, server = networking.start_server(
self, server_name, server_port, self.auth)
self.local_url = path_to_local_server
@ -505,10 +533,10 @@ class Interface:
self.server_app = app
self.server_thread = thread
# Count number of launches
utils.launch_counter()
# If running in a colab or not able to access localhost, automatically create a shareable link
# If running in a colab or not able to access localhost,
# automatically create a shareable link.
is_colab = utils.colab_check()
if is_colab or not (networking.url_ok(path_to_local_server)):
share = True
@ -524,7 +552,6 @@ class Interface:
if private_endpoint is not None:
share = True
# Set up shareable link
self.share = share
if share:
@ -545,7 +572,6 @@ class Interface:
print(strings.en["PUBLIC_SHARE_TRUE"])
share_url = None
# Open a browser tab with the interface.
if inbrowser:
link = share_url if share else path_to_local_server
webbrowser.open(link)
@ -556,7 +582,6 @@ class Interface:
if inline:
try:
from IPython.display import IFrame, display # type: ignore
# Embed the remote interface page if on google colab; otherwise, embed the local page.
if share:
while not networking.url_ok(share_url):
time.sleep(1)
@ -565,7 +590,7 @@ class Interface:
display(IFrame(path_to_local_server,
width=self.width, height=self.height))
except ImportError:
pass # IPython is not available so does not print inline.
pass
data = {
'launch_method': 'browser' if inbrowser else 'inline',
@ -608,9 +633,7 @@ class Interface:
self.server_thread.join()
if verbose:
print("Closing server running on port: {}".format(self.server_port))
except AttributeError: # can't close if not running
pass
except OSError: # sometimes OSError is thrown when shutting down
except (AttributeError, OSError): # can't close if not running
pass
def integrate(
@ -658,7 +681,6 @@ class Interface:
def close_all(verbose: bool = True) -> None:
# Tries to close all running interfaces, but method is a little flaky.
for io in Interface.get_instances():
io.close(verbose)

View File

@ -159,6 +159,39 @@ def login():
return abort(401)
@app.route("/api/", methods=["GET"])
def api_docs():
inputs = [type(inp) for inp in app.interface.input_components]
outputs = [type(out) for out in app.interface.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.interface.input_components]
output_names = [type(out).__name__ for out in app.interface.output_components]
if app.interface.examples is not None:
sample_inputs = app.interface.examples[0]
else:
sample_inputs = [inp.generate_sample() for inp in app.interface.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.interface.auth,
"local_login_url": urllib.parse.urljoin(
app.interface.local_url, "login"),
"local_api_url": urllib.parse.urljoin(
app.interface.local_url, "api/predict")
}
return render_template("api_docs.html", **docs)
@app.route("/config/", methods=["GET"])
def get_config():
if app.interface.auth is None or current_user.is_authenticated:
@ -210,8 +243,7 @@ def predict():
if app.interface.allow_flagging == "auto":
flag_index = app.interface.flagging_callback.flag(app.interface, raw_input, prediction,
flag_option=(None if app.interface.flagging_options is None else ""),
username=current_user.id if current_user.is_authenticated else None,
flag_path=os.path.join(app.cwd, app.interface.flagging_dir))
username=current_user.id if current_user.is_authenticated else None)
output = {
"data": prediction,
"durations": durations,
@ -221,74 +253,14 @@ def predict():
return output
def get_types(cls_set, component):
docset = []
types = []
if component == "input":
for cls in cls_set:
doc = inspect.getdoc(cls.preprocess)
doc_lines = doc.split("\n")
docset.append(doc_lines[1].split(":")[-1])
types.append(doc_lines[1].split(")")[0].split("(")[-1])
else:
for cls in cls_set:
doc = inspect.getdoc(cls.postprocess)
doc_lines = doc.split("\n")
docset.append(doc_lines[-1].split(":")[-1])
types.append(doc_lines[-1].split(")")[0].split("(")[-1])
return docset, types
@app.route("/api/", methods=["GET"])
def api_docs():
inputs = [type(inp) for inp in app.interface.input_components]
outputs = [type(out) for out in app.interface.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.interface.input_components]
output_names = [type(out).__name__ for out in app.interface.output_components]
if app.interface.examples is not None:
sample_inputs = app.interface.examples[0]
else:
sample_inputs = [inp.generate_sample() for inp in app.interface.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.interface.auth,
"local_login_url": urllib.parse.urljoin(
app.interface.local_url, "login"),
"local_api_url": urllib.parse.urljoin(
app.interface.local_url, "api/predict")
}
return render_template("api_docs.html", **docs)
def log_feature_analytics(feature):
if app.interface.analytics_enabled:
try:
requests.post(GRADIO_FEATURE_ANALYTICS_URL,
data={
'ip_address': IP_ADDRESS,
'feature': feature}, timeout=3)
except (requests.ConnectionError, requests.exceptions.ReadTimeout):
pass # do not push analytics if no network
@app.route("/api/flag/", methods=["POST"])
@login_check
def flag():
log_feature_analytics('flag')
data = request.json['data']
app.interface.flagging_callback.flag(app.interface, data['input_data'], data['output_data'], data.get("flag_option"), data.get("flag_index"),
app.interface.flagging_callback.flag(
app.interface, data['input_data'], data['output_data'],
data.get("flag_option"), data.get("flag_index"),
current_user.id if current_user.is_authenticated else None)
return jsonify(success=True)
@ -446,3 +418,35 @@ def url_ok(url):
return True
except (ConnectionError, requests.exceptions.ConnectionError):
return False
def get_types(cls_set, component):
docset = []
types = []
if component == "input":
for cls in cls_set:
doc = inspect.getdoc(cls.preprocess)
doc_lines = doc.split("\n")
docset.append(doc_lines[1].split(":")[-1])
types.append(doc_lines[1].split(")")[0].split("(")[-1])
else:
for cls in cls_set:
doc = inspect.getdoc(cls.postprocess)
doc_lines = doc.split("\n")
docset.append(doc_lines[-1].split(":")[-1])
types.append(doc_lines[-1].split(")")[0].split("(")[-1])
return docset, types
def log_feature_analytics(feature):
if app.interface.analytics_enabled:
try:
requests.post(GRADIO_FEATURE_ANALYTICS_URL,
data={
'ip_address': IP_ADDRESS,
'feature': feature}, timeout=3)
except (requests.ConnectionError, requests.exceptions.ReadTimeout):
pass # do not push analytics if no network

View File

@ -1,19 +1,27 @@
{
"files": {
<<<<<<< HEAD
"main.css": "/static/css/main.8fe32992.css",
=======
"main.css": "/static/css/main.9666dfea.css",
>>>>>>> fdf679c4fa34c778bfda8977be6d4860d082322f
"main.js": "/static/bundle.js",
"index.html": "/index.html",
"static/media/api-logo.b3893a52.svg": "/static/media/api-logo.b3893a52.svg",
"static/media/arrow-left.e497f657.svg": "/static/media/arrow-left.e497f657.svg",
"static/media/arrow-right.ea6059fd.svg": "/static/media/arrow-right.ea6059fd.svg",
"static/media/clear.33f9b5f3.svg": "/static/media/clear.33f9b5f3.svg",
"static/media/edit.44bd4fe1.svg": "/static/media/edit.44bd4fe1.svg",
"static/media/logo.411acfd1.svg": "/static/media/logo.411acfd1.svg",
"static/media/logo_error.0d6fa515.svg": "/static/media/logo_error.0d6fa515.svg"
"static/media/api-logo.93f17adb.svg": "/static/media/api-logo.93f17adb.svg",
"static/media/arrow-left.794a4706.svg": "/static/media/arrow-left.794a4706.svg",
"static/media/arrow-right.5a7d4ada.svg": "/static/media/arrow-right.5a7d4ada.svg",
"static/media/clear.85cf6de8.svg": "/static/media/clear.85cf6de8.svg",
"static/media/edit.c6b7d6f7.svg": "/static/media/edit.c6b7d6f7.svg",
"static/media/logo.36a8f455.svg": "/static/media/logo.36a8f455.svg",
"static/media/logo_error.52ef12b4.svg": "/static/media/logo_error.52ef12b4.svg"
},
"entrypoints": [
"static/bundle.css",
<<<<<<< HEAD
"static/css/main.8fe32992.css",
=======
"static/css/main.9666dfea.css",
>>>>>>> fdf679c4fa34c778bfda8977be6d4860d082322f
"static/bundle.js"
]
}

View File

@ -1 +1,5 @@
<!doctype html><html lang="en" style="height:100%;margin:0;padding:0"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><title>{{ config['title'] or 'Gradio' }}</title><meta property="og:url" content="https://gradio.app/"/><meta property="og:type" content="website"/><meta property="og:image" content="{{ config['thumbnail'] or '' }}"/><meta property="og:title" content="{{ config['title'] or '' }}"/><meta property="og:description" content="{{ config['description'] or '' }}"/><meta name="twitter:card" content="summary_large_image"><meta name="twitter:creator" content="@teamGradio"><meta name="twitter:title" content="{{ config['title'] or '' }}"><meta name="twitter:description" content="{{ config['description'] or '' }}"><meta name="twitter:image" content="{{ config['thumbnail'] or '' }}"><script async src="https://www.googletagmanager.com/gtag/js?id=UA-156449732-1"></script><script>function gtag(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],gtag("js",new Date),gtag("config","UA-156449732-1"),window.gradio_mode="app"</script><script>window.gradio_config = {{ config|tojson }};</script><script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.1/iframeResizer.contentWindow.min.js"></script><title>Gradio</title><link href="static/bundle.css" rel="stylesheet"><link href="static/css/main.8fe32992.css" rel="stylesheet"></head><body style="height:100%;margin:0;padding:0"><div id="root" style="height:100%"></div><script src="static/bundle.js"></script></body></html>
<<<<<<< HEAD
<!doctype html><html lang="en" style="height:100%;margin:0;padding:0"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><title>{{ config['title'] or 'Gradio' }}</title><meta property="og:url" content="https://gradio.app/"/><meta property="og:type" content="website"/><meta property="og:image" content="{{ config['thumbnail'] or '' }}"/><meta property="og:title" content="{{ config['title'] or '' }}"/><meta property="og:description" content="{{ config['description'] or '' }}"/><meta name="twitter:card" content="summary_large_image"><meta name="twitter:creator" content="@teamGradio"><meta name="twitter:title" content="{{ config['title'] or '' }}"><meta name="twitter:description" content="{{ config['description'] or '' }}"><meta name="twitter:image" content="{{ config['thumbnail'] or '' }}"><script async src="https://www.googletagmanager.com/gtag/js?id=UA-156449732-1"></script><script>function gtag(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],gtag("js",new Date),gtag("config","UA-156449732-1"),window.gradio_mode="app"</script><script>window.gradio_config = {{ config|tojson }};</script><script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.1/iframeResizer.contentWindow.min.js"></script><title>Gradio</title><link href="static/bundle.css" rel="stylesheet"><link href="static/css/main.8fe32992.css" rel="stylesheet"></head><body style="height:100%;margin:0;padding:0"><div id="root" style="height:100%"></div><script src="static/bundle.js"></script></body></html>
=======
<!doctype html><html lang="en" style="height:100%;margin:0;padding:0"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"><title>{{ config['title'] or 'Gradio' }}</title><meta property="og:url" content="https://gradio.app/"/><meta property="og:type" content="website"/><meta property="og:image" content="{{ config['thumbnail'] or '' }}"/><meta property="og:title" content="{{ config['title'] or '' }}"/><meta property="og:description" content="{{ config['description'] or '' }}"/><meta name="twitter:card" content="summary_large_image"><meta name="twitter:creator" content="@teamGradio"><meta name="twitter:title" content="{{ config['title'] or '' }}"><meta name="twitter:description" content="{{ config['description'] or '' }}"><meta name="twitter:image" content="{{ config['thumbnail'] or '' }}"><script async src="https://www.googletagmanager.com/gtag/js?id=UA-156449732-1"></script><script>function gtag(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],gtag("js",new Date),gtag("config","UA-156449732-1"),window.gradio_mode="app";try{let a="";window.gradio_config=JSON.parse("{{ config|tojson }}"+a)}catch(a){}</script><script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.1/iframeResizer.contentWindow.min.js"></script><title>Gradio</title><link href="static/bundle.css" rel="stylesheet"><link href="static/css/main.9666dfea.css" rel="stylesheet"></head><body style="height:100%;margin:0;padding:0"><div id="root" style="height:100%"></div><script src="static/bundle.js"></script></body></html>
>>>>>>> fdf679c4fa34c778bfda8977be6d4860d082322f