Skip to content

Commit

Permalink
Add component <-> server direct communication support, as well as a "…
Browse files Browse the repository at this point in the history
…file explorer" component (#5672)

* changes

* changes

* add changeset

* add changeset

* Server fns ext (#5760)

* start changes

* changes

* changes

* fix arrows

* add changeset

* rename demo

* fix some ci

* add changeset

* add changeset

* fix

* remove configs

* fix

* fix

* add changeset

* fixes

* linting

* Update gradio/components/file_explorer.py

* notebook

* typing

* tweaks

* fixed class method problem

* fix test

* file explorer

* gr.load

* format

* tweaks

* fix

* fix

* fix

* fix

* final tweaks + changelog

* changelog

* changelog

* changelog

* lint

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: pngwn <hello@pngwn.io>
Co-authored-by: Abubakar Abid <abubakar@huggingface.co>
  • Loading branch information
4 people committed Oct 5, 2023
1 parent caeee8b commit e4a307e
Show file tree
Hide file tree
Showing 40 changed files with 1,460 additions and 15 deletions.
24 changes: 24 additions & 0 deletions .changeset/itchy-radios-pay.md
@@ -0,0 +1,24 @@
---
"@gradio/app": minor
"@gradio/client": minor
"@gradio/file": minor
"@gradio/fileexplorer": minor
"@gradio/theme": minor
"gradio": minor
"gradio_client": minor
---

highlight:

#### new `FileExplorer` component

Thanks to a new capability that allows components to communicate directly with the server _without_ passing data via the value, we have created a new `FileExplorer` component.

This component allows you to populate the explorer by passing a glob, but only provides the selected file(s) in your prediction function.

Users can then navigate the virtual filesystem and select files which will be accessible in your predict function. This component will allow developers to build more complex spaces, with more flexible input options.

![output](https://github.com/pngwn/MDsveX/assets/12937446/ef108f0b-0e84-4292-9984-9dc66b3e144d)

For more information check the [`FileExplorer` documentation](https://gradio.app/docs/fileexplorer).

53 changes: 52 additions & 1 deletion client/js/src/client.ts
Expand Up @@ -45,6 +45,11 @@ type client_return = {
data?: unknown[],
event_data?: unknown
) => SubmitReturn;
component_server: (
component_id: number,
fn_name: string,
data: unknown[]
) => any;
view_api: (c?: Config) => Promise<ApiInfo<JsApiData>>;
};

Expand Down Expand Up @@ -243,7 +248,8 @@ export function api_factory(
const return_obj = {
predict,
submit,
view_api
view_api,
component_server
// duplicate
};

Expand Down Expand Up @@ -710,6 +716,51 @@ export function api_factory(
};
}

async function component_server(
component_id: number,
fn_name: string,
data: unknown[]
): Promise<any> {
const headers: {
Authorization?: string;
"Content-Type": "application/json";
} = { "Content-Type": "application/json" };
if (hf_token) {
headers.Authorization = `Bearer ${hf_token}`;
}
let root_url: string;
let component = config.components.find(
(comp) => comp.id === component_id
);
if (component?.props?.root_url) {
root_url = component.props.root_url;
} else {
root_url = `${http_protocol}//${host + config.path}/`;
}
const response = await fetch_implementation(
`${root_url}component_server/`,
{
method: "POST",
body: JSON.stringify({
data: data,
component_id: component_id,
fn_name: fn_name,
session_hash: session_hash
}),
headers
}
);

if (!response.ok) {
throw new Error(
"Could not connect to component server: " + response.statusText
);
}

const output = await response.json();
return output;
}

async function view_api(config?: Config): Promise<ApiInfo<JsApiData>> {
if (api) return api;

Expand Down
1 change: 1 addition & 0 deletions client/python/gradio_client/serializing.py
Expand Up @@ -573,6 +573,7 @@ def deserialize(
"file": FileSerializable,
"dataframe": JSONSerializable,
"timeseries": JSONSerializable,
"fileexplorer": JSONSerializable,
"state": SimpleSerializable,
"button": StringSerializable,
"uploadbutton": FileSerializable,
Expand Down
1 change: 1 addition & 0 deletions demo/file_explorer/run.ipynb
@@ -0,0 +1 @@
{"cells": [{"cell_type": "markdown", "id": 302934307671667531413257853548643485645, "metadata": {}, "source": ["# Gradio Demo: file_explorer"]}, {"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", "from pathlib import Path\n", "\n", "current_file_path = Path(__file__).resolve()\n", "relative_path = \"path/to/file\"\n", "absolute_path = (current_file_path.parent / \"..\" / \"..\" / \"gradio\").resolve()\n", "\n", "\n", "def get_file_content(file):\n", " return (file,)\n", "\n", "\n", "with gr.Blocks() as demo:\n", " gr.Markdown('### `FileExplorer` to `FileExplorer` -- `file_count=\"multiple\"`')\n", " submit_btn = gr.Button(\"Select\")\n", " with gr.Row():\n", " file = gr.FileExplorer(\n", " glob=\"**/{components,themes}/*.py\",\n", " # value=[\"themes/utils\"],\n", " root=absolute_path,\n", " ignore_glob=\"**/__init__.py\",\n", " )\n", "\n", " file2 = gr.FileExplorer(\n", " glob=\"**/{components,themes}/**/*.py\",\n", " root=absolute_path,\n", " ignore_glob=\"**/__init__.py\",\n", " )\n", " submit_btn.click(lambda x: x, file, file2)\n", "\n", " gr.Markdown(\"---\")\n", " gr.Markdown('### `FileExplorer` to `Code` -- `file_count=\"single\"`')\n", " with gr.Group():\n", " with gr.Row():\n", " file_3 = gr.FileExplorer(\n", " scale=1,\n", " glob=\"**/{components,themes}/**/*.py\",\n", " value=[\"themes/utils\"],\n", " file_count=\"single\",\n", " root=absolute_path,\n", " ignore_glob=\"**/__init__.py\",\n", " elem_id=\"file\",\n", " )\n", "\n", " code = gr.Code(lines=30, scale=2, language=\"python\")\n", "\n", " file_3.change(get_file_content, file_3, code)\n", "\n", "\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
51 changes: 51 additions & 0 deletions demo/file_explorer/run.py
@@ -0,0 +1,51 @@
import gradio as gr
from pathlib import Path

current_file_path = Path(__file__).resolve()
relative_path = "path/to/file"
absolute_path = (current_file_path.parent / ".." / ".." / "gradio").resolve()


def get_file_content(file):
return (file,)


with gr.Blocks() as demo:
gr.Markdown('### `FileExplorer` to `FileExplorer` -- `file_count="multiple"`')
submit_btn = gr.Button("Select")
with gr.Row():
file = gr.FileExplorer(
glob="**/{components,themes}/*.py",
# value=["themes/utils"],
root=absolute_path,
ignore_glob="**/__init__.py",
)

file2 = gr.FileExplorer(
glob="**/{components,themes}/**/*.py",
root=absolute_path,
ignore_glob="**/__init__.py",
)
submit_btn.click(lambda x: x, file, file2)

gr.Markdown("---")
gr.Markdown('### `FileExplorer` to `Code` -- `file_count="single"`')
with gr.Group():
with gr.Row():
file_3 = gr.FileExplorer(
scale=1,
glob="**/{components,themes}/**/*.py",
value=["themes/utils"],
file_count="single",
root=absolute_path,
ignore_glob="**/__init__.py",
elem_id="file",
)

code = gr.Code(lines=30, scale=2, language="python")

file_3.change(get_file_content, file_3, code)


if __name__ == "__main__":
demo.launch()
1 change: 1 addition & 0 deletions gradio/__init__.py
Expand Up @@ -30,6 +30,7 @@
Dropdown,
DuplicateButton,
File,
FileExplorer,
Gallery,
Highlight,
HighlightedText,
Expand Down
1 change: 1 addition & 0 deletions gradio/blocks.py
Expand Up @@ -731,6 +731,7 @@ def get_block_instance(id: int) -> Block:
block_config["props"].pop("type", None)
block_config["props"].pop("name", None)
block_config["props"].pop("selectable", None)
block_config["props"].pop("server_fns", None)

# If a Gradio app B is loaded into a Gradio app A, and B itself loads a
# Gradio app C, then the root_urls of the components in A need to be the
Expand Down
2 changes: 2 additions & 0 deletions gradio/components/__init__.py
Expand Up @@ -25,6 +25,7 @@
from gradio.components.dropdown import Dropdown
from gradio.components.duplicate_button import DuplicateButton
from gradio.components.file import File
from gradio.components.file_explorer import FileExplorer
from gradio.components.gallery import Gallery
from gradio.components.highlighted_text import HighlightedText
from gradio.components.html import HTML
Expand Down Expand Up @@ -82,6 +83,7 @@
"FormComponent",
"Gallery",
"HTML",
"FileExplorer",
"Image",
"IOComponent",
"Interpretation",
Expand Down
16 changes: 16 additions & 0 deletions gradio/components/base.py
Expand Up @@ -58,6 +58,11 @@ class Component(Updateable, Block, Serializable):
def __init__(self, *args, **kwargs):
Block.__init__(self, *args, **kwargs)
EventListener.__init__(self)
self.server_fns = [
value
for value in self.__class__.__dict__.values()
if callable(value) and getattr(value, "_is_server_fn", False)
]

def __str__(self):
return self.__repr__()
Expand Down Expand Up @@ -112,6 +117,17 @@ def style(self, *args, **kwargs):
self.parent.variant = "compact"
return self

def get_config(self):
config = super().get_config()
if len(self.server_fns):
config["server_fns"] = [fn.__name__ for fn in self.server_fns]
return config


def server(fn):
fn._is_server_fn = True
return fn


class IOComponent(Component):
"""
Expand Down

0 comments on commit e4a307e

Please sign in to comment.