Duplicate spaces integration into gradio (#4458)

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes

* changes
This commit is contained in:
aliabid94 2023-06-21 12:25:25 -07:00 committed by GitHub
parent 4d1ad7c820
commit 0909c35102
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 156 additions and 27 deletions

View File

@ -3,6 +3,7 @@
## New Features:
- Allow the web component `space`, `src`, and `host` attributes to be updated dynamically by [@pngwn](https://github.com/pngwn) in [PR 4461](https://github.com/gradio-app/gradio/pull/4461)
- Spaces Duplication built into Gradio, by [@aliabid94](https://github.com/aliabid94) in [PR 4458](https://github.com/gradio-app/gradio/pull/4458)
## Bug Fixes:

View File

@ -23,7 +23,7 @@ export interface Config {
theme: string;
title: string;
version: string;
is_space: boolean;
space_id: string | null;
is_colab: boolean;
show_api: boolean;
stylesheets: string[];

View File

@ -13,7 +13,7 @@ export interface Config {
theme: string;
title: string;
version: string;
is_space: boolean;
space_id: string | null;
is_colab: boolean;
show_api: boolean;
stylesheets: string[];

View File

@ -1 +1 @@
{"cells": [{"cell_type": "markdown", "id": 302934307671667531413257853548643485645, "metadata": {}, "source": ["# Gradio Demo: blocks_xray"]}, {"cell_type": "code", "execution_count": null, "id": 272996653310673477252411125948039410165, "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": 288918539441861185822528903084949547379, "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import time\n", "\n", "disease_values = [0.25, 0.5, 0.75]\n", "\n", "def xray_model(diseases, img):\n", " return [{disease: disease_values[idx] for idx,disease in enumerate(diseases)}]\n", "\n", "\n", "def ct_model(diseases, img):\n", " return [{disease: 0.1 for disease in diseases}]\n", "\n", "with gr.Blocks() as demo:\n", " gr.Markdown(\n", " \"\"\"\n", "# Detect Disease From Scan\n", "With this model you can lorem ipsum\n", "- ipsum 1\n", "- ipsum 2\n", "\"\"\"\n", " )\n", " disease = gr.CheckboxGroup(\n", " info=\"Select the diseases you want to scan for.\",\n", " choices=[\"Covid\", \"Malaria\", \"Lung Cancer\"], label=\"Disease to Scan For\"\n", " )\n", " slider = gr.Slider(0, 100)\n", "\n", " with gr.Tab(\"X-ray\") as x_tab:\n", " with gr.Row():\n", " xray_scan = gr.Image()\n", " xray_results = gr.JSON()\n", " xray_run = gr.Button(\"Run\")\n", " xray_run.click(\n", " xray_model,\n", " inputs=[disease, xray_scan],\n", " outputs=xray_results,\n", " api_name=\"xray_model\"\n", " )\n", "\n", " with gr.Tab(\"CT Scan\"):\n", " with gr.Row():\n", " ct_scan = gr.Image()\n", " ct_results = gr.JSON()\n", " ct_run = gr.Button(\"Run\")\n", " ct_run.click(\n", " ct_model,\n", " inputs=[disease, ct_scan],\n", " outputs=ct_results,\n", " api_name=\"ct_model\"\n", " )\n", "\n", " upload_btn = gr.Button(\"Upload Results\", variant=\"primary\")\n", " upload_btn.click(\n", " lambda ct, xr: None,\n", " inputs=[ct_results, xray_results],\n", " outputs=[],\n", " )\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
{"cells": [{"cell_type": "markdown", "id": 302934307671667531413257853548643485645, "metadata": {}, "source": ["# Gradio Demo: blocks_xray"]}, {"cell_type": "code", "execution_count": null, "id": 272996653310673477252411125948039410165, "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": 288918539441861185822528903084949547379, "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import time\n", "\n", "disease_values = [0.25, 0.5, 0.75]\n", "\n", "def xray_model(diseases, img):\n", " return [{disease: disease_values[idx] for idx,disease in enumerate(diseases)}]\n", "\n", "\n", "def ct_model(diseases, img):\n", " return [{disease: 0.1 for disease in diseases}]\n", "\n", "with gr.Blocks() as demo:\n", " gr.Markdown(\n", " \"\"\"\n", "# Detect Disease From Scan\n", "With this model you can lorem ipsum\n", "- ipsum 1\n", "- ipsum 2\n", "\"\"\"\n", " )\n", " gr.DuplicateButton()\n", " disease = gr.CheckboxGroup(\n", " info=\"Select the diseases you want to scan for.\",\n", " choices=[\"Covid\", \"Malaria\", \"Lung Cancer\"], label=\"Disease to Scan For\"\n", " )\n", " slider = gr.Slider(0, 100)\n", "\n", " with gr.Tab(\"X-ray\") as x_tab:\n", " with gr.Row():\n", " xray_scan = gr.Image()\n", " xray_results = gr.JSON()\n", " xray_run = gr.Button(\"Run\")\n", " xray_run.click(\n", " xray_model,\n", " inputs=[disease, xray_scan],\n", " outputs=xray_results,\n", " api_name=\"xray_model\"\n", " )\n", "\n", " with gr.Tab(\"CT Scan\"):\n", " with gr.Row():\n", " ct_scan = gr.Image()\n", " ct_results = gr.JSON()\n", " ct_run = gr.Button(\"Run\")\n", " ct_run.click(\n", " ct_model,\n", " inputs=[disease, ct_scan],\n", " outputs=ct_results,\n", " api_name=\"ct_model\"\n", " )\n", "\n", " upload_btn = gr.Button(\"Upload Results\", variant=\"primary\")\n", " upload_btn.click(\n", " lambda ct, xr: None,\n", " inputs=[ct_results, xray_results],\n", " outputs=[],\n", " )\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}

View File

@ -19,6 +19,7 @@ With this model you can lorem ipsum
- ipsum 2
"""
)
gr.DuplicateButton()
disease = gr.CheckboxGroup(
info="Select the diseases you want to scan for.",
choices=["Covid", "Malaria", "Lung Cancer"], label="Disease to Scan For"

2
globals.d.ts vendored
View File

@ -23,7 +23,7 @@ export interface Config {
theme: string;
title: string;
version: string;
is_space: boolean;
space_id: string | null;
is_colab: boolean;
show_api: boolean;
stylesheets: string[];

View File

@ -27,6 +27,7 @@ from gradio.components import (
Dataframe,
Dataset,
Dropdown,
DuplicateButton,
File,
Gallery,
Highlight,

View File

@ -399,7 +399,7 @@ class BlockFunction:
def spaces_auto_wrap(self):
if spaces is None:
return
if not utils.is_space():
if utils.get_space() is None:
return
self.fn = spaces.gradio_auto_wrap(self.fn)
@ -718,7 +718,7 @@ class Blocks(BlockContext):
self.height = None
self.api_open = True
self.is_space = utils.is_space()
self.space_id = utils.get_space()
self.favicon_path = None
self.auth = None
self.dev_mode = True
@ -1384,7 +1384,7 @@ Received outputs:
"components": [],
"css": self.css,
"title": self.title or "Gradio",
"is_space": self.is_space,
"space_id": self.space_id,
"enable_queue": getattr(self, "enable_queue", False), # launch attributes
"show_error": getattr(self, "show_error", False),
"show_api": self.show_api,
@ -1735,7 +1735,7 @@ Received outputs:
DeprecationWarning,
)
if self.is_space:
if self.space_id:
self.enable_queue = self.enable_queue is not False
else:
self.enable_queue = self.enable_queue is True
@ -1859,7 +1859,7 @@ Received outputs:
)
if self.share:
if self.is_space:
if self.space_id:
raise RuntimeError("Share is not supported when you are in Spaces")
try:
if self.share_url is None:
@ -1958,7 +1958,7 @@ Received outputs:
"show_tips": self.show_tips,
"server_name": server_name,
"server_port": server_port,
"is_spaces": self.is_space,
"is_space": self.space_id is not None,
"mode": self.mode,
}
analytics.launched_analytics(self, data)

View File

@ -23,6 +23,7 @@ from gradio.components.color_picker import ColorPicker
from gradio.components.dataframe import Dataframe
from gradio.components.dataset import Dataset
from gradio.components.dropdown import Dropdown
from gradio.components.duplicate_button import DuplicateButton
from gradio.components.file import File
from gradio.components.gallery import Gallery
from gradio.components.highlighted_text import HighlightedText
@ -74,6 +75,7 @@ __all__ = [
"Dataframe",
"DataFrame",
"Dataset",
"DuplicateButton",
"Form",
"FormComponent",
"Gallery",

View File

@ -0,0 +1,61 @@
""" Predefined buttons with bound events that can be included in a gr.Blocks for convenience. """
from __future__ import annotations
from typing import Literal
from gradio_client.documentation import document, set_documentation_group
from gradio.components import Button
from gradio.utils import get_space
set_documentation_group("component")
@document()
class DuplicateButton(Button):
"""
Button that appears only on Hugging Face Spaces, triggering a Spaces Duplication.
Preprocessing: passes the button value as a {str} into the function
Postprocessing: expects a {str} to be returned from a function, which is set as the label of the button
"""
is_template = True
def __init__(
self,
*,
value: str = "Duplicate Space",
variant: Literal["primary", "secondary", "stop"] = "secondary",
size: Literal["sm", "lg"] | None = "sm",
visible: bool = True,
interactive: bool = True,
elem_id: str | None = None,
elem_classes: list[str] | str | None = None,
scale: int | None = 0,
min_width: int | None = None,
_activate: bool = True,
**kwargs,
):
super().__init__(
value,
variant=variant,
size=size,
visible=visible,
interactive=interactive,
elem_id=elem_id,
elem_classes=elem_classes,
scale=scale,
min_width=min_width,
**kwargs,
)
if _activate:
self.activate()
def activate(self):
space_name = get_space()
if space_name is not None:
self.click(
fn=None,
_js=f"() => {{ window.open(`https://huggingface.co/spaces/{space_name}?duplicate=true`, '_blank') }}",
)

View File

@ -19,6 +19,7 @@ from gradio.blocks import Blocks
from gradio.components import (
Button,
ClearButton,
DuplicateButton,
Interpretation,
IOComponent,
Markdown,
@ -141,6 +142,7 @@ class Interface(Blocks):
batch: bool = False,
max_batch_size: int = 4,
_api_mode: bool = False,
allow_duplication: bool = False,
**kwargs,
):
"""
@ -167,6 +169,7 @@ class Interface(Blocks):
analytics_enabled: Whether to allow basic telemetry. If None, will use GRADIO_ANALYTICS_ENABLED environment variable if defined, or default to True.
batch: If True, then the function should process a batch of inputs, meaning that it should accept a list of input values for each parameter. The lists should be of equal length (and be up to length `max_batch_size`). The function is then *required* to return a tuple of lists (even if there is only 1 output component), with each list in the tuple corresponding to one output component.
max_batch_size: Maximum number of inputs to batch together if this is called from the queue (only relevant if batch=True)
allow_duplication: If True, then will show a 'Duplicate Spaces' button on Hugging Face Spaces.
"""
super().__init__(
analytics_enabled=analytics_enabled,
@ -202,7 +205,7 @@ class Interface(Blocks):
if not isinstance(outputs, list):
outputs = [outputs]
if self.is_space and cache_examples is None:
if self.space_id and cache_examples is None:
self.cache_examples = True
else:
self.cache_examples = cache_examples or False
@ -358,6 +361,7 @@ class Interface(Blocks):
self.flagging_dir = flagging_dir
self.batch = batch
self.max_batch_size = max_batch_size
self.allow_duplication = allow_duplication
self.save_to = None # Used for selenium tests
self.share = None
@ -400,7 +404,13 @@ class Interface(Blocks):
with self:
self.render_title_description()
submit_btn, clear_btn, stop_btn, flag_btns = None, None, None, None
submit_btn, clear_btn, stop_btn, flag_btns, duplicate_btn = (
None,
None,
None,
None,
None,
)
interpretation_btn, interpretation_set = None, None
input_component_column, interpret_component_column = None, None
@ -426,6 +436,7 @@ class Interface(Blocks):
(
submit_btn_out,
clear_btn_2_out,
duplicate_btn,
stop_btn_2_out,
flag_btns_out,
interpretation_btn,
@ -441,6 +452,8 @@ class Interface(Blocks):
self.attach_clear_events(
clear_btn, input_component_column, interpret_component_column
)
if duplicate_btn is not None:
duplicate_btn.activate()
self.attach_interpretation_events(
interpretation_btn,
interpretation_set,
@ -533,10 +546,21 @@ class Interface(Blocks):
self,
submit_btn_in: Button | None,
) -> tuple[
Button | None, ClearButton | None, Button | None, list | None, Button | None
Button | None,
ClearButton | None,
DuplicateButton,
Button | None,
list | None,
Button | None,
]:
submit_btn = submit_btn_in
interpretation_btn, clear_btn, flag_btns, stop_btn = None, None, None, None
interpretation_btn, clear_btn, duplicate_btn, flag_btns, stop_btn = (
None,
None,
None,
None,
None,
)
with Column(variant="panel"):
for component in self.output_components:
@ -561,10 +585,21 @@ class Interface(Blocks):
elif self.allow_flagging == "auto":
assert submit_btn is not None, "Submit button not rendered"
flag_btns = [submit_btn]
if self.interpretation:
interpretation_btn = Button("Interpret")
return submit_btn, clear_btn, stop_btn, flag_btns, interpretation_btn
if self.allow_duplication:
duplicate_btn = DuplicateButton(scale=1, size="lg", _activate=False)
return (
submit_btn,
clear_btn,
duplicate_btn,
stop_btn,
flag_btns,
interpretation_btn,
)
def render_article(self):
if self.article:

View File

@ -258,7 +258,7 @@ class App(FastAPI):
config = {
"auth_required": True,
"auth_message": blocks.auth_message,
"is_space": app.get_blocks().is_space,
"space_id": app.get_blocks().space_id,
"root": root_path,
}

View File

@ -211,7 +211,7 @@ XRAY_CONFIG = {
],
"css": None,
"title": "Gradio",
"is_space": False,
"space_id": False,
"enable_queue": None,
"show_error": True,
"show_api": True,
@ -525,7 +525,7 @@ XRAY_CONFIG_DIFF_IDS = {
],
"css": None,
"title": "Gradio",
"is_space": False,
"space_id": False,
"enable_queue": None,
"show_error": True,
"show_api": True,

View File

@ -107,8 +107,10 @@ def ipython_check() -> bool:
return is_ipython
def is_space() -> bool:
return os.getenv("SYSTEM") == "spaces"
def get_space() -> str | None:
if os.getenv("SYSTEM") == "spaces":
return os.getenv("SPACE_ID")
return None
def readme_to_html(article: str) -> str:

View File

@ -43,6 +43,7 @@
export let app_mode: boolean;
export let theme_mode: ThemeMode;
export let app: Awaited<ReturnType<typeof client>>;
export let space_id: string | null;
let loading_status = create_loading_status_store();
@ -237,6 +238,11 @@
let _error_id = -1;
const MESSAGE_QUOTE_RE = /^'([^]+)'$/;
const DUPLICATE_MESSAGE =
"There is a long queue of requests pending. Duplicate this Space to skip.";
const SHOW_DUPLICATE_MESSAGE_ON_ETA = 15;
let showed_duplicate_message = false;
const trigger_api_call = async (
dep_index: number,
event_data: unknown = null
@ -298,6 +304,25 @@
progress: status.progress_data,
fn_index
});
if (
!showed_duplicate_message &&
space_id !== null &&
status.position !== undefined &&
status.position >= 2 &&
status.eta !== undefined &&
status.eta > SHOW_DUPLICATE_MESSAGE_ON_ETA
) {
showed_duplicate_message = true;
messages = [
{
type: "warning",
message: DUPLICATE_MESSAGE,
id: ++_error_id,
fn_index
},
...messages
];
}
if (status.stage === "complete") {
dependencies.map(async (dep, i) => {

View File

@ -23,7 +23,7 @@
theme: string;
title: string;
version: string;
is_space: boolean;
space_id: string | null;
is_colab: boolean;
show_api: boolean;
stylesheets?: string[];
@ -321,7 +321,7 @@
<Login
auth_message={config.auth_message}
root={config.root}
is_space={config.is_space}
space_id={space}
{app_mode}
/>
{:else if config && Blocks && css_ready}

View File

@ -6,7 +6,7 @@
export let root: string;
export let auth_message: string | null;
export let app_mode: boolean;
export let is_space: boolean;
export let space_id: string | null;
let username = "";
let password = "";
@ -37,7 +37,7 @@
{#if auth_message}
<p class="auth">{auth_message}</p>
{/if}
{#if is_space}
{#if space_id}
<p class="auth">
If you are visiting a HuggingFace Space in Incognito mode, you must
enable third party cookies.

View File

@ -9,9 +9,9 @@
<div class="toast-wrap">
{#each messages as { type, message, id } (id)}
<div animate:flip={{ duration: 300 }} style:width="100%">
{#if type === "error"}
<Error {message} on:close {id} />
{/if}
<!-- {#if type === "error"} -->
<Error {message} on:close {id} />
<!-- {/if} -->
</div>
{/each}
</div>

View File

@ -14,6 +14,7 @@
class:hide={!visible}
class="{size} {variant} {elem_classes.join(' ')}"
style:flex-grow={scale}
style:width={scale === 0 ? "fit-content" : null}
style:min-width={typeof min_width === "number"
? `calc(min(${min_width}px, 100%))`
: null}