From c2a0d056d679d90631d9ccd944dadd67e7e03b7f Mon Sep 17 00:00:00 2001 From: Freddy Boulton Date: Tue, 11 Jun 2024 19:27:59 -0400 Subject: [PATCH] Control Display of Error, Info, Warning (#8489) * first draft * handle animation * No animation * Add code * add changeset * Only use duration * lint * lint * Add docs * add changeset * Fix default * sample usage * formatting * Add visible parameters * float --------- Co-authored-by: gradio-pr-bot Co-authored-by: aliabd --- .changeset/neat-roses-tan.md | 9 ++++++ client/js/src/helpers/api_info.ts | 2 ++ client/js/src/types.ts | 4 +++ client/js/src/utils/submit.ts | 6 ++++ gradio/exceptions.py | 13 ++++++++- gradio/helpers.py | 29 +++++++++++++++---- gradio/queueing.py | 20 +++++++++---- gradio/routes.py | 5 ++-- gradio/server_messages.py | 2 ++ gradio/utils.py | 14 +++++++++ js/_website/src/lib/assets/style.css | 6 ++++ .../lib/templates/gradio/05_modals/error.svx | 8 ++++- .../lib/templates/gradio/05_modals/info.svx | 2 +- .../templates/gradio/05_modals/warning.svx | 2 +- js/app/src/Blocks.svelte | 26 +++++++++++++---- js/statustracker/static/Toast.svelte | 4 +-- js/statustracker/static/ToastContent.svelte | 25 +++++++++++++--- js/statustracker/static/types.ts | 2 ++ 18 files changed, 149 insertions(+), 30 deletions(-) create mode 100644 .changeset/neat-roses-tan.md diff --git a/.changeset/neat-roses-tan.md b/.changeset/neat-roses-tan.md new file mode 100644 index 0000000000..13335759a9 --- /dev/null +++ b/.changeset/neat-roses-tan.md @@ -0,0 +1,9 @@ +--- +"@gradio/app": minor +"@gradio/client": minor +"@gradio/statustracker": minor +"gradio": minor +"website": minor +--- + +feat:Control Display of Error, Info, Warning diff --git a/client/js/src/helpers/api_info.ts b/client/js/src/helpers/api_info.ts index 0c4efcc4fa..d8640cf047 100644 --- a/client/js/src/helpers/api_info.ts +++ b/client/js/src/helpers/api_info.ts @@ -318,6 +318,8 @@ export function handle_message( status: { queue, message: data.output.error as string, + visible: data.output.visible as boolean, + duration: data.output.duration as number, stage: "error", code: data.code, success: data.success diff --git a/client/js/src/types.ts b/client/js/src/types.ts index 8e2d33c27f..315f473192 100644 --- a/client/js/src/types.ts +++ b/client/js/src/types.ts @@ -329,6 +329,8 @@ export interface Status { code?: string; success?: boolean; stage: "pending" | "error" | "complete" | "generating"; + duration?: number; + visible?: boolean; broken?: boolean; size?: number; position?: number; @@ -361,6 +363,8 @@ export interface LogMessage extends Log { type: "log"; endpoint: string; fn_index: number; + duration: number | null; + visible: boolean; } export interface RenderMessage extends Render { diff --git a/client/js/src/utils/submit.ts b/client/js/src/utils/submit.ts index b2c02b3149..6738088d5d 100644 --- a/client/js/src/utils/submit.ts +++ b/client/js/src/utils/submit.ts @@ -343,6 +343,8 @@ export function submit( log: data.log, level: data.level, endpoint: _endpoint, + duration: data.duration, + visible: data.visible, fn_index }); } else if (type === "generating") { @@ -474,6 +476,8 @@ export function submit( log: data.log, level: data.level, endpoint: _endpoint, + duration: data.duration, + visible: data.visible, fn_index }); } else if (type === "generating") { @@ -630,6 +634,8 @@ export function submit( log: data.log, level: data.level, endpoint: _endpoint, + duration: data.duration, + visible: data.visible, fn_index }); return; diff --git a/gradio/exceptions.py b/gradio/exceptions.py index 93fef33061..df70c26c4d 100644 --- a/gradio/exceptions.py +++ b/gradio/exceptions.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from gradio_client.documentation import document @@ -73,12 +75,21 @@ class Error(Exception): Demos: calculator, blocks_chained_events """ - def __init__(self, message: str = "Error raised."): + def __init__( + self, + message: str = "Error raised.", + duration: float | None = 10, + visible: bool = True, + ): """ Parameters: message: The error message to be displayed to the user. + duration: The duration in seconds to display the error message. If None or 0, the error message will be displayed until the user closes it. + visible: Whether the error message should be displayed in the UI. """ self.message = message + self.duration = duration + self.visible = visible super().__init__(self.message) def __str__(self): diff --git a/gradio/helpers.py b/gradio/helpers.py index e450a9e974..c00c24da8a 100644 --- a/gradio/helpers.py +++ b/gradio/helpers.py @@ -1226,7 +1226,12 @@ def make_waveform( return output_mp4.name -def log_message(message: str, level: Literal["info", "warning"] = "info"): +def log_message( + message: str, + level: Literal["info", "warning"] = "info", + duration: float | None = 10, + visible: bool = True, +): from gradio.context import LocalContext blocks = LocalContext.blocks.get() @@ -1239,16 +1244,22 @@ def log_message(message: str, level: Literal["info", "warning"] = "info"): elif level == "warning": warnings.warn(message) return - blocks._queue.log_message(event_id=event_id, log=message, level=level) + blocks._queue.log_message( + event_id=event_id, log=message, level=level, duration=duration, visible=visible + ) @document(documentation_group="modals") -def Warning(message: str = "Warning issued."): # noqa: N802 +def Warning( # noqa: N802 + message: str = "Warning issued.", duration: float | None = 10, visible: bool = True +): """ This function allows you to pass custom warning messages to the user. You can do so simply by writing `gr.Warning('message here')` in your function, and when that line is executed the custom message will appear in a modal on the demo. The modal is yellow by default and has the heading: "Warning." Queue must be enabled for this behavior; otherwise, the warning will be printed to the console using the `warnings` library. Demos: blocks_chained_events Parameters: message: The warning message to be displayed to the user. + duration: The duration in seconds that the warning message should be displayed for. If None or 0, the message will be displayed indefinitely until the user closes it. + visible: Whether the error message should be displayed in the UI. Example: import gradio as gr def hello_world(): @@ -1259,16 +1270,22 @@ def Warning(message: str = "Warning issued."): # noqa: N802 demo.load(hello_world, inputs=None, outputs=[md]) demo.queue().launch() """ - log_message(message, level="warning") + log_message(message, level="warning", duration=duration, visible=visible) @document(documentation_group="modals") -def Info(message: str = "Info issued."): # noqa: N802 +def Info( # noqa: N802 + message: str = "Info issued.", + duration: float | None = 10, + visible: bool = True, +): """ This function allows you to pass custom info messages to the user. You can do so simply by writing `gr.Info('message here')` in your function, and when that line is executed the custom message will appear in a modal on the demo. The modal is gray by default and has the heading: "Info." Queue must be enabled for this behavior; otherwise, the message will be printed to the console. Demos: blocks_chained_events Parameters: message: The info message to be displayed to the user. + duration: The duration in seconds that the info message should be displayed for. If None or 0, the message will be displayed indefinitely until the user closes it. + visible: Whether the error message should be displayed in the UI. Example: import gradio as gr def hello_world(): @@ -1279,4 +1296,4 @@ def Info(message: str = "Info issued."): # noqa: N802 demo.load(hello_world, inputs=None, outputs=[md]) demo.queue().launch() """ - log_message(message, level="info") + log_message(message, level="info", duration=duration, visible=visible) diff --git a/gradio/queueing.py b/gradio/queueing.py index dcb3d1ec69..7e090bdce0 100644 --- a/gradio/queueing.py +++ b/gradio/queueing.py @@ -18,7 +18,6 @@ from gradio import route_utils, routes from gradio.data_classes import ( PredictBody, ) -from gradio.exceptions import Error from gradio.helpers import TrackedIterable from gradio.server_messages import ( EstimationMessage, @@ -30,7 +29,13 @@ from gradio.server_messages import ( ProgressMessage, ProgressUnit, ) -from gradio.utils import LRUCache, run_coro_in_background, safe_get_lock, set_task_name +from gradio.utils import ( + LRUCache, + error_payload, + run_coro_in_background, + safe_get_lock, + set_task_name, +) if TYPE_CHECKING: from gradio.blocks import BlockFunction, Blocks @@ -376,6 +381,8 @@ class Queue: event_id: str, log: str, level: Literal["info", "warning"], + duration: float | None = 10, + visible: bool = True, ): events = [ evt for job in self.active_jobs if job is not None for evt in job @@ -385,6 +392,8 @@ class Queue: log_message = LogMessage( log=log, level=level, + duration=duration, + visible=visible, ) self.send_message(event, log_message) @@ -538,15 +547,15 @@ class Queue: ) err = None except Exception as e: - show_error = app.get_blocks().show_error or isinstance(e, Error) traceback.print_exc() response = None err = e for event in awake_events: + content = error_payload(err, app.get_blocks().show_error) self.send_message( event, ProcessCompletedMessage( - output={"error": str(e) if show_error else None}, + output=content, success=False, ), ) @@ -586,8 +595,7 @@ class Queue: else: success = False error = err or old_err - show_error = app.get_blocks().show_error or isinstance(error, Error) - output = {"error": str(error) if show_error else None} + output = error_payload(error, app.get_blocks().show_error) for event in awake_events: self.send_message( event, ProcessCompletedMessage(output=output, success=success) diff --git a/gradio/routes.py b/gradio/routes.py index df78841f42..fed9e98c4f 100644 --- a/gradio/routes.py +++ b/gradio/routes.py @@ -76,7 +76,6 @@ from gradio.data_classes import ( ResetBody, SimplePredictBody, ) -from gradio.exceptions import Error from gradio.oauth import attach_oauth from gradio.route_utils import ( # noqa: F401 CustomCORSMiddleware, @@ -737,10 +736,10 @@ class App(FastAPI): root_path=root_path, ) except BaseException as error: - show_error = app.get_blocks().show_error or isinstance(error, Error) + content = utils.error_payload(error, app.get_blocks().show_error) traceback.print_exc() return JSONResponse( - content={"error": str(error) if show_error else None}, + content=content, status_code=500, ) return output diff --git a/gradio/server_messages.py b/gradio/server_messages.py index be3c729774..6338c2ec51 100644 --- a/gradio/server_messages.py +++ b/gradio/server_messages.py @@ -31,6 +31,8 @@ class LogMessage(BaseMessage): msg: Literal[ServerMessage.log] = ServerMessage.log log: str level: Literal["info", "warning"] + duration: Optional[float] = 10 + visible: bool = True class EstimationMessage(BaseMessage): diff --git a/gradio/utils.py b/gradio/utils.py index c635543529..76b882ff4d 100644 --- a/gradio/utils.py +++ b/gradio/utils.py @@ -53,6 +53,7 @@ from typing_extensions import ParamSpec import gradio from gradio.context import get_blocks_context from gradio.data_classes import FileData +from gradio.exceptions import Error from gradio.strings import en if TYPE_CHECKING: # Only import for type checking (is False at runtime). @@ -1417,3 +1418,16 @@ def deep_hash(obj): items = str(id(obj)).encode("utf-8") hasher.update(repr(items).encode("utf-8")) return hasher.hexdigest() + + +def error_payload( + error: BaseException | None, show_error: bool +) -> dict[str, bool | str | float | None]: + content: dict[str, bool | str | float | None] = {"error": None} + show_error = show_error or isinstance(error, Error) + if show_error: + content["error"] = str(error) + if isinstance(error, Error): + content["duration"] = error.duration + content["visible"] = error.visible + return content diff --git a/js/_website/src/lib/assets/style.css b/js/_website/src/lib/assets/style.css index 83197d1145..af22db89d2 100644 --- a/js/_website/src/lib/assets/style.css +++ b/js/_website/src/lib/assets/style.css @@ -278,6 +278,12 @@ strong { font-weight: 600; } +.obj a { + color: var(--tw-prose-links); + text-decoration: underline; + font-weight: 500; +} + .codeblock > pre { font-size: 1em; } diff --git a/js/_website/src/lib/templates/gradio/05_modals/error.svx b/js/_website/src/lib/templates/gradio/05_modals/error.svx index 7125dbe6e5..5a074f1bbb 100644 --- a/js/_website/src/lib/templates/gradio/05_modals/error.svx +++ b/js/_website/src/lib/templates/gradio/05_modals/error.svx @@ -17,13 +17,19 @@ ```python -gradio.Error(路路路) +raise gradio.Error("An error occured 馃挜!", duration=5) ``` ### Description ## {@html style_formatted_text(obj.description)} +## You can control for how long the error message is displayed with the `duration` parameter. If it's `None`, the message will be displayed forever until the user closes it. If it's a number, it will be shown for that many seconds. +## You can also hide the error modal from being shown in the UI by setting `visible=False`. +## Below is a demo of how different values of duration control the error, info, and warning messages. You can see the code [here](https://huggingface.co/spaces/freddyaboulton/gradio-error-duration/blob/244331cf53f6b5fa2fd406ece3bf55c6ccb9f5f2/app.py#L17). + +![modal_control](https://github.com/gradio-app/gradio/assets/41651716/f0977bcd-eaec-4eca-a2fd-ede95fdb8fd2) + {#if obj.example} diff --git a/js/_website/src/lib/templates/gradio/05_modals/info.svx b/js/_website/src/lib/templates/gradio/05_modals/info.svx index c884957f00..85e3feb209 100644 --- a/js/_website/src/lib/templates/gradio/05_modals/info.svx +++ b/js/_website/src/lib/templates/gradio/05_modals/info.svx @@ -17,7 +17,7 @@ ```python -gradio.Info(路路路) +gradio.Info("Helpful info message 鈩癸笍", duration=5) ``` diff --git a/js/_website/src/lib/templates/gradio/05_modals/warning.svx b/js/_website/src/lib/templates/gradio/05_modals/warning.svx index ce4b3d6ca4..23e40ed9a1 100644 --- a/js/_website/src/lib/templates/gradio/05_modals/warning.svx +++ b/js/_website/src/lib/templates/gradio/05_modals/warning.svx @@ -17,7 +17,7 @@ ```python -gradio.Warning(路路路) +gradio.Warning("A warning occured 鉀旓笍!", duration=5) ``` diff --git a/js/app/src/Blocks.svelte b/js/app/src/Blocks.svelte index 740c629b30..d7a550464a 100644 --- a/js/app/src/Blocks.svelte +++ b/js/app/src/Blocks.svelte @@ -141,13 +141,17 @@ function new_message( message: string, fn_index: number, - type: ToastMessage["type"] + type: ToastMessage["type"], + duration: number | null = 10, + visible = true ): ToastMessage & { fn_index: number } { return { message, fn_index, type, - id: ++_error_id + id: ++_error_id, + duration, + visible }; } @@ -344,8 +348,11 @@ } function handle_log(msg: LogMessage): void { - const { log, fn_index, level } = msg; - messages = [new_message(log, fn_index, level), ...messages]; + const { log, fn_index, level, duration, visible } = msg; + messages = [ + new_message(log, fn_index, level, duration, visible), + ...messages + ]; } function handle_status_update(message: StatusMessage): void { @@ -416,7 +423,16 @@ MESSAGE_QUOTE_RE, (_, b) => b ); - messages = [new_message(_message, fn_index, "error"), ...messages]; + messages = [ + new_message( + _message, + fn_index, + "error", + status.duration, + status.visible + ), + ...messages + ]; } dependencies.map(async (dep) => { if ( diff --git a/js/statustracker/static/Toast.svelte b/js/statustracker/static/Toast.svelte index d94f854259..7139ef5dbe 100644 --- a/js/statustracker/static/Toast.svelte +++ b/js/statustracker/static/Toast.svelte @@ -17,9 +17,9 @@
- {#each messages as { type, message, id } (id)} + {#each messages as { type, message, id, duration, visible } (id)}
- +
{/each}
diff --git a/js/statustracker/static/ToastContent.svelte b/js/statustracker/static/ToastContent.svelte index 269bf21c8b..1e58771456 100644 --- a/js/statustracker/static/ToastContent.svelte +++ b/js/statustracker/static/ToastContent.svelte @@ -7,6 +7,11 @@ export let message = ""; export let type: ToastMessage["type"]; export let id: number; + export let duration: number | null = 10; + export let visible = true; + + $: display = visible; + $: duration = duration || null; const dispatch = createEventDispatcher(); @@ -15,10 +20,14 @@ } onMount(() => { - setTimeout(() => { - close_message(); - }, 10000); + if (duration !== null) { + setTimeout(() => { + close_message(); + }, duration * 1000); + } }); + + $: timer_animation_duration = `${duration || 0}s`; @@ -27,6 +36,7 @@ class="toast-body {type}" role="alert" data-testid="toast-body" + class:hidden={!display} on:click|stopPropagation on:keydown|stopPropagation in:fade={{ duration: 200, delay: 100 }} @@ -59,7 +69,10 @@ -
+
diff --git a/js/statustracker/static/types.ts b/js/statustracker/static/types.ts index 686149348f..0d74f6fa19 100644 --- a/js/statustracker/static/types.ts +++ b/js/statustracker/static/types.ts @@ -21,4 +21,6 @@ export interface ToastMessage { type: "error" | "warning" | "info"; message: string; id: number; + duration: number | null; + visible: boolean; }