Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multipage #10433

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
41d43d3
changes
Jan 24, 2025
d76ec7c
add changeset
gradio-pr-bot Jan 24, 2025
7852e0b
changes
Jan 24, 2025
f5cd14a
Merge branch 'pages_2' of https://github.com/gradio-app/gradio into p…
Jan 24, 2025
09475d4
Merge branch 'main' into pages_2
abidlabs Jan 24, 2025
275b17c
chnages
Jan 27, 2025
c610bc3
Merge branch 'pages_2' of https://github.com/gradio-app/gradio into p…
Jan 27, 2025
4bae335
Merge branch 'main' into pages_2
abidlabs Jan 27, 2025
291a230
changes
Jan 27, 2025
f50e3b2
Merge branch 'pages_2' of https://github.com/gradio-app/gradio into p…
Jan 27, 2025
cb3d4df
changes
Jan 28, 2025
f6e31eb
add changeset
gradio-pr-bot Jan 28, 2025
2c4e46a
Merge branch 'main' into pages_2
abidlabs Jan 28, 2025
9a4b5bd
Update gradio/blocks.py
aliabid94 Jan 28, 2025
2f938b7
Update gradio/blocks.py
aliabid94 Jan 28, 2025
97db25c
changes
Jan 28, 2025
0c10c40
Merge branch 'pages_2' of https://github.com/gradio-app/gradio into p…
Jan 28, 2025
6da17d2
changes
Jan 29, 2025
c38d5cd
changes
Jan 29, 2025
65c0c75
Merge remote-tracking branch 'origin' into pages_2
Jan 29, 2025
2f2c281
chagnes
Jan 29, 2025
0b84e0a
Update js/core/src/Blocks.svelte
aliabid94 Jan 29, 2025
a640b40
Update js/core/src/Blocks.svelte
aliabid94 Jan 29, 2025
963dddc
changes
Jan 29, 2025
cd391ea
Merge branch 'pages_2' of https://github.com/gradio-app/gradio into p…
Jan 29, 2025
d76cfe9
Merge remote-tracking branch 'origin' into pages_2
Jan 29, 2025
f019ce2
chagnes
Jan 29, 2025
bf3ee51
changes
Jan 29, 2025
24a907a
changes
Jan 29, 2025
6a3064c
changes
Jan 29, 2025
e96e645
changes
Jan 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/large-beans-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@gradio/core": minor
"@self/spa": minor
"gradio": minor
---

feat:Multipage
1 change: 1 addition & 0 deletions demo/multipage/run.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"cells": [{"cell_type": "markdown", "id": "302934307671667531413257853548643485645", "metadata": {}, "source": ["# Gradio Demo: multipage"]}, {"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 random\n", "\n", "with gr.Blocks() as demo:\n", " name = gr.Textbox(label=\"Name\")\n", " output = gr.Textbox(label=\"Output Box\")\n", " greet_btn = gr.Button(\"Greet\")\n", " @gr.on([greet_btn.click, name.submit], inputs=name, outputs=output)\n", " def greet(name):\n", " return \"Hello \" + name + \"!\"\n", "\n", "with demo.route(\"Up\") as incrementer_demo:\n", " num = gr.Number()\n", " incrementer_demo.load(lambda: random.randint(10, 40), None, num)\n", "\n", " with gr.Row():\n", " inc_btn = gr.Button(\"Increase\")\n", " dec_btn = gr.Button(\"Decrease\")\n", " inc_btn.click(fn=lambda x: x + 1, inputs=num, outputs=num, api_name=\"increment\")\n", " dec_btn.click(fn=lambda x: x - 1, inputs=num, outputs=num, api_name=\"decrement\")\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
23 changes: 23 additions & 0 deletions demo/multipage/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import gradio as gr
import random

with gr.Blocks() as demo:
name = gr.Textbox(label="Name")
output = gr.Textbox(label="Output Box")
greet_btn = gr.Button("Greet")
@gr.on([greet_btn.click, name.submit], inputs=name, outputs=output)
def greet(name):
return "Hello " + name + "!"

with demo.route("Up") as incrementer_demo:
num = gr.Number()
incrementer_demo.load(lambda: random.randint(10, 40), None, num)

with gr.Row():
inc_btn = gr.Button("Increase")
dec_btn = gr.Button("Decrease")
inc_btn.click(fn=lambda x: x + 1, inputs=num, outputs=num, api_name="increment")
dec_btn.click(fn=lambda x: x - 1, inputs=num, outputs=num, api_name="decrement")

if __name__ == "__main__":
demo.launch()
63 changes: 58 additions & 5 deletions gradio/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ def __init__(
self.share_token = secrets.token_urlsafe(32)
self.parent: BlockContext | None = None
self.rendered_in: Renderable | None = None
self.page: str
self.is_rendered: bool = False
self._constructor_args: list[dict]
self.state_session_capacity = 10000
Expand Down Expand Up @@ -187,6 +188,7 @@ def render(self):
f"A block with id: {self._id} has already been rendered in the current Blocks."
)
if render_context is not None:
self.page = root_context.root_block.current_page
render_context.add(self)
if root_context is not None:
root_context.blocks[self._id] = self
Expand Down Expand Up @@ -467,6 +469,7 @@ def fill_expected_parents(self):
pseudo_parent.parent = self
children.append(pseudo_parent)
pseudo_parent.add_child(child)
pseudo_parent.page = child.page
if root_context:
root_context.blocks[pseudo_parent._id] = pseudo_parent
child.parent = pseudo_parent
Expand Down Expand Up @@ -521,6 +524,7 @@ def __init__(
stream_every: float = 0.5,
like_user_message: bool = False,
event_specific_args: list[str] | None = None,
page: str = "",
):
self.fn = fn
self._id = _id
Expand Down Expand Up @@ -554,6 +558,7 @@ def __init__(
) or inspect.isasyncgenfunction(self.fn)
self.renderable = renderable
self.rendered_in = rendered_in
self.page = page

# We need to keep track of which events are cancel events
# so that the client can call the /cancel route directly
Expand Down Expand Up @@ -871,14 +876,27 @@ def set_event_trigger(
stream_every=stream_every,
like_user_message=like_user_message,
event_specific_args=event_specific_args,
page=self.root_block.current_page,
)

self.fns[self.fn_id] = block_fn
self.fn_id += 1
return block_fn, block_fn._id

def get_config(self, renderable: Renderable | None = None):
config = {}
config = {
"page": {},
"components": [],
"dependencies": [],
}

def setup_page(page: str):
if page not in config["page"]:
config["page"][page] = {
"layout": {"id": self._id, "children": []},
"components": [],
"dependencies": [],
}

rendered_ids = []

Expand All @@ -888,16 +906,20 @@ def get_layout(block: Block):
return {"id": block._id}
children_layout = []
for child in block.children:
children_layout.append(get_layout(child))
layout = get_layout(child)
children_layout.append(layout)
return {"id": block._id, "children": children_layout}

if renderable:
root_block = self.blocks[renderable.container_id]
else:
root_block = self.root_block
config["layout"] = get_layout(root_block)
for root_child in config["layout"]["children"]:
block = self.blocks[root_child["id"]]
setup_page(block.page)
config["page"][block.page]["layout"]["children"].append(root_child)

config["components"] = []
blocks_items = list(
self.blocks.items()
) # freeze as list to prevent concurrent re-renders from changing the dict during loop, see https://github.com/gradio-app/gradio/issues/9991
Expand Down Expand Up @@ -930,11 +952,17 @@ def get_layout(block: Block):
block_config["api_info_as_output"] = block.api_info() # type: ignore
block_config["example_inputs"] = block.example_inputs() # type: ignore
config["components"].append(block_config)
setup_page(block.page)
config["page"][block.page]["components"].append(block_config)

dependencies = []
for fn in self.fns.values():
if renderable is None or fn.rendered_in == renderable:
dependencies.append(fn.get_config())
dependency_config = fn.get_config()
dependencies.append(dependency_config)
setup_page(fn.page)
config["page"][fn.page]["dependencies"].append(dependency_config)

config["dependencies"] = dependencies
return config

Expand Down Expand Up @@ -1128,6 +1156,8 @@ def __init__(
self.output_components = None
self.__name__ = None # type: ignore
self.api_mode = None
self.pages: list[tuple[str, str]] = [["", "Home"]]
self.current_page = ""

self.progress_tracking = None
self.ssl_verify = True
Expand Down Expand Up @@ -2172,6 +2202,7 @@ def get_config_file(self) -> BlocksConfigDict:
"fill_width": self.fill_width,
"theme_hash": self.theme_hash,
"pwa": self.pwa,
"pages": self.pages,
}
config.update(self.default_config.get_config()) # type: ignore
config["connect_heartbeat"] = utils.connect_heartbeat(
Expand Down Expand Up @@ -2206,6 +2237,7 @@ def __exit__(self, exc_type: type[BaseException] | None = None, *args):
self.progress_tracking = any(
block_fn.tracks_progress for block_fn in self.fns.values()
)
self.page = ""
self.exited = True

def clear(self):
Expand Down Expand Up @@ -2254,7 +2286,6 @@ def queue(
blocks=self,
default_concurrency_limit=default_concurrency_limit,
)
self.config = self.get_config_file()
self.app = App.create_app(self)
return self

Expand Down Expand Up @@ -3033,3 +3064,25 @@ def get_event_targets(
event = getattr(block, event_name)
target_events.append(event)
return target_events

@document()
def route(self, name: str, path: str | None = None):
aliabid94 marked this conversation as resolved.
Show resolved Hide resolved
"""
Adds a new page to the Blocks app.
Parameters:
name: The name of the page as it appears in the nav bar.
path: The url path of the page (prefixed by 'page-'). If not provided, it is generated from the name.
Example:
with gr.Blocks() as demo:
name = gr.Textbox(label="Name")
...
with demo.route("Test", "/test"):
num = gr.Number()
...
"""
if path is None:
path = name.lower().replace(" ", "-")
path = path.strip("/")
self.pages.append((path, name))
self.current_page = path
return self
2 changes: 0 additions & 2 deletions gradio/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -546,8 +546,6 @@ def __init__(
self.render_examples()
self.render_article()

self.config = self.get_config_file()

def render_title_description(self) -> None:
if self.title:
Markdown(
Expand Down
22 changes: 20 additions & 2 deletions gradio/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,18 +524,35 @@ def _(path: str):
)
)

@app.get("/page-{path}")
async def page_route(
path: str, request: fastapi.Request, user: str = Depends(get_current_user)
):
path = path.strip("/")
return main(request, user, path)

@app.head("/", response_class=HTMLResponse)
@app.get("/", response_class=HTMLResponse)
def main(request: fastapi.Request, user: str = Depends(get_current_user)):
def main(
request: fastapi.Request,
user: str = Depends(get_current_user),
page: str = "",
):
mimetypes.add_type("application/javascript", ".js")
blocks = app.get_blocks()
root = route_utils.get_root_url(
request=request, route_path="/", root_path=app.root_path
request=request,
route_path=f"/page-{page}" if page else "/",
root_path=app.root_path,
)
if (app.auth is None and app.auth_dependency is None) or user is not None:
config = utils.safe_deepcopy(blocks.config)
config = route_utils.update_root_in_config(config, root)
config["username"] = user
config["components"] = config["page"][page]["components"]
config["dependencies"] = config["page"][page]["dependencies"]
config["layout"] = config["page"][page]["layout"]
config["current_page"] = page
elif app.auth_dependency:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated"
Expand Down Expand Up @@ -592,6 +609,7 @@ def api_info(request: fastapi.Request):
@app.get("/config", dependencies=[Depends(login_check)])
def get_config(request: fastapi.Request):
config = utils.safe_deepcopy(app.get_blocks().config)
# del config["page"]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to remove this comment?

root = route_utils.get_root_url(
request=request, route_path="/config", root_path=app.root_path
)
Expand Down
6 changes: 6 additions & 0 deletions guides/03_building-with-blocks/05_more-blocks-features
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,9 @@ In the 2 player tic-tac-toe demo below, a user can select a cell in the `DataFr

$code_tictactoe
$demo_tictactoe

## Multipage Apps

Your Gradio app can support multiple pages with the `gr.Blocks().route()` method.
aliabid94 marked this conversation as resolved.
Show resolved Hide resolved

$code_multipage
23 changes: 23 additions & 0 deletions js/core/src/Blocks.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@
export let max_file_size: number | undefined = undefined;
export let initial_layout: ComponentMeta | undefined = undefined;
export let css: string | null | undefined = null;
export let pages: [string, string][] = [];
export let current_page: string = "";

Check failure on line 55 in js/core/src/Blocks.svelte

View workflow job for this annotation

GitHub Actions / js-test

Type string trivially inferred from a string literal, remove type annotation
aliabid94 marked this conversation as resolved.
Show resolved Hide resolved

let {
layout: _layout,
targets,
Expand Down Expand Up @@ -746,6 +749,18 @@
</svelte:head>

<div class="wrap" style:min-height={app_mode ? "100%" : "auto"}>
{#if pages.length > 1}
<nav>
{#each pages as [route, label], i}
<a
href={route.length ? `page-${route}` : "/"}
style:font-weight={route === current_page ? "bold" : "normal"}
>{label}</a
>
{i < pages.length - 1 ? "·" : ""}
{/each}
</nav>
{/if}
<div class="contain" style:flex-grow={app_mode ? "1" : "auto"}>
{#if $_layout && app.config}
<MountComponents
Expand Down Expand Up @@ -873,6 +888,14 @@
{/if}

<style>
nav {
display: flex;
gap: var(--size-2);
margin-bottom: var(--size-2);
}
nav a:hover {
text-decoration: underline;
}
.wrap {
display: flex;
flex-grow: 1;
Expand Down
2 changes: 2 additions & 0 deletions js/spa/src/Index.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
username: string | null;
api_prefix?: string;
max_file_size?: number;
pages: [string, string][];
current_page: string;
}
let id = -1;
Expand Down
Loading