diff --git a/demo/calculator/run.py b/demo/calculator/run.py index 28d631c702..f5d4e2fd48 100644 --- a/demo/calculator/run.py +++ b/demo/calculator/run.py @@ -8,6 +8,8 @@ def calculator(num1, operation, num2): elif operation == "multiply": return num1 * num2 elif operation == "divide": + if num2 == 0: + raise gr.Error("Cannot divide by zero!") return num1 / num2 demo = gr.Interface( @@ -28,4 +30,4 @@ demo = gr.Interface( description="Here's a sample toy calculator. Enjoy!", ) if __name__ == "__main__": - demo.launch(show_error=True) + demo.launch() diff --git a/demo/fake_gan_2/run.py b/demo/fake_gan_2/run.py index 967af218ab..cc5f356000 100644 --- a/demo/fake_gan_2/run.py +++ b/demo/fake_gan_2/run.py @@ -7,8 +7,10 @@ import time import gradio as gr -def fake_gan(*args): - time.sleep(15) +def fake_gan(desc): + if desc == "NSFW": + raise gr.Error("NSFW - banned content.") + time.sleep(5) image = random.choice( [ "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=387&q=80", @@ -23,20 +25,12 @@ def fake_gan(*args): demo = gr.Interface( fn=fake_gan, - inputs=[ - gr.Image(label="Initial Image (optional)"), - ], + inputs=gr.Textbox(), outputs=gr.Image(label="Generated Image"), title="FD-GAN", description="This is a fake demo of a GAN. In reality, the images are randomly chosen from Unsplash.", - examples=[ - [os.path.join(os.path.dirname(__file__), "files/cheetah1.jpg")], - [os.path.join(os.path.dirname(__file__), "files/elephant.jpg")], - [os.path.join(os.path.dirname(__file__), "files/tiger.jpg")], - [os.path.join(os.path.dirname(__file__), "files/zebra.jpg")], - ], ) demo.queue(max_size=3) if __name__ == "__main__": - demo.launch() + demo.launch(show_error=True) diff --git a/gradio/__init__.py b/gradio/__init__.py index a5b7d2531c..e83854893b 100644 --- a/gradio/__init__.py +++ b/gradio/__init__.py @@ -44,6 +44,7 @@ from gradio.components import ( component, ) from gradio.examples import create_examples as Examples +from gradio.exceptions import Error from gradio.flagging import ( CSVLogger, FlaggingCallback, diff --git a/gradio/blocks.py b/gradio/blocks.py index e3bbbe056e..3aa77b1973 100644 --- a/gradio/blocks.py +++ b/gradio/blocks.py @@ -33,6 +33,7 @@ from gradio.documentation import ( document_component_api, set_documentation_group, ) +from gradio.exceptions import Error from gradio.utils import component_or_layout_class, delete_none set_documentation_group("blocks") @@ -813,7 +814,7 @@ class Blocks(BlockContext): auth: Optional[Callable | Tuple[str, str] | List[Tuple[str, str]]] = None, auth_message: Optional[str] = None, prevent_thread_lock: bool = False, - show_error: bool = True, + show_error: bool = False, server_name: Optional[str] = None, server_port: Optional[int] = None, show_tips: bool = False, @@ -839,7 +840,7 @@ class Blocks(BlockContext): auth: If provided, username and password (or list of username-password tuples) required to access interface. Can also provide function that takes username and password and returns True if valid login. auth_message: If provided, HTML message provided on login page. prevent_thread_lock: If True, the interface will block the main thread while the server is running. - show_error: If True, any errors in the interface will be printed in the browser console log + show_error: If True, any errors in the interface will be displayed in an alert modal and printed in the browser console log server_port: will start gradio app on this port (if available). Can be set by environment variable GRADIO_SERVER_PORT. If None, will search for an available port starting at 7860. server_name: to make app accessible on local network, set this to "0.0.0.0". Can be set by environment variable GRADIO_SERVER_NAME. If None, will use "127.0.0.1". show_tips: if True, will occasionally show tips about new Gradio features @@ -886,9 +887,9 @@ class Blocks(BlockContext): DeprecationWarning, ) if self.is_space: - self.enable_queue = enable_queue is not False + self.enable_queue = self.enable_queue is not False else: - self.enable_queue = enable_queue is True + self.enable_queue = self.enable_queue is True self.config = self.get_config_file() self.share = share diff --git a/gradio/event_queue.py b/gradio/event_queue.py index 20e190e928..522d0935ae 100644 --- a/gradio/event_queue.py +++ b/gradio/event_queue.py @@ -258,9 +258,15 @@ class Queue: json=event.data, ) end_time = time.time() - cls.update_estimation(end_time - begin_time) + success = response.status == 200 + if success: + cls.update_estimation(end_time - begin_time) client_awake = await event.send_message( - {"msg": "process_completed", "output": response.json} + { + "msg": "process_completed", + "output": response.json, + "success": success, + } ) if client_awake: run_coro_in_background(cls.wait_in_inactive, event) diff --git a/gradio/exceptions.py b/gradio/exceptions.py new file mode 100644 index 0000000000..980bad12e3 --- /dev/null +++ b/gradio/exceptions.py @@ -0,0 +1,7 @@ +class Error(Exception): + def __init__(self, message: str): + self.message = message + super().__init__(self.message) + + def __str__(self): + return repr(self.message) diff --git a/gradio/routes.py b/gradio/routes.py index ac1241f440..6396481027 100644 --- a/gradio/routes.py +++ b/gradio/routes.py @@ -29,6 +29,7 @@ from starlette.websockets import WebSocket, WebSocketState import gradio from gradio import encryptor from gradio.event_queue import Estimation, Event, Queue +from gradio.exceptions import Error STATIC_TEMPLATE_LIB = pkg_resources.resource_filename("gradio", "templates/") STATIC_PATH_LIB = pkg_resources.resource_filename("gradio", "templates/frontend/static") @@ -241,18 +242,21 @@ class App(FastAPI): session_state = app.state_holder[body.session_hash] else: session_state = {} + raw_input = body.data + fn_index = body.fn_index try: - raw_input = body.data - fn_index = body.fn_index output = await app.blocks.process_api( fn_index, raw_input, username, session_state ) + if isinstance(output, Error): + raise output except BaseException as error: - if app.blocks.show_error: - traceback.print_exc() - return JSONResponse(content={"error": str(error)}, status_code=500) - else: - raise error + show_error = app.blocks.show_error or isinstance(error, Error) + traceback.print_exc() + return JSONResponse( + content={"error": str(error) if show_error else None}, + status_code=500, + ) return output @app.post("/api/{api_name}", dependencies=[Depends(login_check)]) diff --git a/gradio/version.txt b/gradio/version.txt index 9cec7165ab..9769eda5bc 100644 --- a/gradio/version.txt +++ b/gradio/version.txt @@ -1 +1 @@ -3.1.6 +3.1.6b1 diff --git a/guides/1)getting_started/2)key_features.md b/guides/1)getting_started/2)key_features.md index 04114a6e6b..00ac9df8ba 100644 --- a/guides/1)getting_started/2)key_features.md +++ b/guides/1)getting_started/2)key_features.md @@ -11,6 +11,10 @@ $demo_calculator You can load a large dataset into the examples to browse and interact with the dataset through Gradio. The examples will be automatically paginated (you can configure this through the `examples_per_page` argument of `Interface`). +## Errors + +You wish to pass custom error messages to the user. To do so, raise a `gr.Error("custom message")` to display an error message. If you try to divide by zero in the the calculator demo above, a popup modal will display the custom error message. + ## Decriptive Content In the previous example, you may have noticed the `title=` and `description=` keyword arguments in the `Interface` constructor that helps users understand your app. diff --git a/ui/packages/app/src/Blocks.svelte b/ui/packages/app/src/Blocks.svelte index c44bcadaa5..f6fe67f37d 100644 --- a/ui/packages/app/src/Blocks.svelte +++ b/ui/packages/app/src/Blocks.svelte @@ -35,7 +35,6 @@ export let target: HTMLElement; export let id: number = 0; export let autoscroll: boolean = false; - export let show_error: boolean = false; let app_mode = window.__gradio_mode__ === "app"; let loading_status = create_loading_status_store(); diff --git a/ui/packages/app/src/api.ts b/ui/packages/app/src/api.ts index 044548378e..d56e3da149 100644 --- a/ui/packages/app/src/api.ts +++ b/ui/packages/app/src/api.ts @@ -46,7 +46,6 @@ async function post_data( body: JSON.stringify(body), headers: { "Content-Type": "application/json" } }); - const output: PostResponse = await response.json(); return [output, response.status]; } @@ -64,12 +63,7 @@ type Output = { const ws_map = new Map(); export const fn = - ( - session_hash: string, - api_endpoint: string, - is_space: boolean, - show_error: boolean - ) => + (session_hash: string, api_endpoint: string, is_space: boolean) => async ({ action, payload, @@ -177,14 +171,16 @@ export const fn = case "process_completed": loading_status.update( fn_index, - "complete", + data.success ? "complete" : "error", queue, null, null, data.output.average_duration, - null + !data.success ? data.output.error : null ); - queue_callback(data.output); + if (data.success) { + queue_callback(data.output); + } websocket_data.connection.close(); break; case "process_starts": @@ -233,8 +229,9 @@ export const fn = null, null, null, - show_error ? output.error : null + output.error ); + throw output.error || "API Error"; } return output; } diff --git a/ui/packages/app/src/components/StatusTracker/StatusTracker.svelte b/ui/packages/app/src/components/StatusTracker/StatusTracker.svelte index d95d9fb1b0..901ae30cc8 100644 --- a/ui/packages/app/src/components/StatusTracker/StatusTracker.svelte +++ b/ui/packages/app/src/components/StatusTracker/StatusTracker.svelte @@ -64,6 +64,7 @@ let timer_start = 0; let timer_diff = 0; let old_eta: number | null = null; + let message_visible: boolean = false; $: progress = eta === null || eta <= 0 || !timer_diff @@ -121,6 +122,19 @@ old_eta = eta; } } + let show_message_timeout: NodeJS.Timeout | null = null; + const close_message = () => { + message_visible = false; + if (show_message_timeout !== null) { + clearTimeout(show_message_timeout); + } + }; + $: { + if (status === "error" && message) { + message_visible = true; + show_message_timeout = setTimeout(close_message, 8000); + } + } $: formatted_timer = timer_diff.toFixed(1); @@ -151,8 +165,22 @@ {/if} {:else if status === "error"} ERROR - {#if message} - {message} + {#if message_visible} +
+
+ Error + +
+
+ {message} +
+
{/if} {/if} @@ -181,8 +209,4 @@ .error { @apply text-red-400 font-mono font-semibold text-lg; } - - .status-message { - @apply font-mono p-2 whitespace-pre; - } diff --git a/ui/packages/app/src/main.ts b/ui/packages/app/src/main.ts index 033a9066eb..9b4c632398 100644 --- a/ui/packages/app/src/main.ts +++ b/ui/packages/app/src/main.ts @@ -34,7 +34,6 @@ interface Config { title: string; version: string; is_space: boolean; - show_error: boolean; // allow_flagging: string; // allow_interpretation: boolean; // article: string; @@ -184,12 +183,7 @@ function mount_app( }); } else { let session_hash = Math.random().toString(36).substring(2); - config.fn = fn( - session_hash, - config.root + "api/", - config.is_space, - config.show_error - ); + config.fn = fn(session_hash, config.root + "api/", config.is_space); new Blocks({ target: wrapper, diff --git a/ui/packages/theme/src/tokens.css b/ui/packages/theme/src/tokens.css index ddfa629026..7678c87fd3 100644 --- a/ui/packages/theme/src/tokens.css +++ b/ui/packages/theme/src/tokens.css @@ -69,7 +69,7 @@ } .gr-button-primary { - @apply from-orange-200/70 to-orange-300/80 hover:from-orange-200/90 text-orange-600 border-orange-200 + @apply from-orange-200/70 to-orange-300/80 hover:to-orange-200/90 text-orange-600 border-orange-200 dark:from-orange-700 dark:to-orange-700 dark:hover:to-orange-500 dark:text-white dark:border-orange-600; }