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 <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: aliabd <ali.si3luwa@gmail.com>
This commit is contained in:
Freddy Boulton 2024-06-11 19:27:59 -04:00 committed by GitHub
parent b03da67d8f
commit c2a0d056d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 149 additions and 30 deletions

View File

@ -0,0 +1,9 @@
---
"@gradio/app": minor
"@gradio/client": minor
"@gradio/statustracker": minor
"gradio": minor
"website": minor
---
feat:Control Display of Error, Info, Warning

View File

@ -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

View File

@ -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 {

View File

@ -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;

View File

@ -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):

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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;
}

View File

@ -17,13 +17,19 @@
<!--- Usage -->
```python
gradio.Error(···)
raise gradio.Error("An error occured 💥!", duration=5)
```
<!--- Description -->
### 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)
<!-- Example Usage -->
{#if obj.example}

View File

@ -17,7 +17,7 @@
<!--- Usage -->
```python
gradio.Info(···)
gradio.Info("Helpful info message ", duration=5)
```
<!--- Description -->

View File

@ -17,7 +17,7 @@
<!--- Usage -->
```python
gradio.Warning(···)
gradio.Warning("A warning occured ⛔️!", duration=5)
```
<!--- Description -->

View File

@ -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 (

View File

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

View File

@ -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`;
</script>
<!-- TODO: fix-->
@ -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 @@
<span aria-hidden="true">&#215;</span>
</button>
<div class="timer {type}" />
<div
class="timer {type}"
style={`animation-duration: ${timer_animation_duration};`}
/>
</div>
<style>
@ -275,4 +288,8 @@
:global(.dark) .timer.info {
background: var(--color-grey-500);
}
.hidden {
display: none;
}
</style>

View File

@ -21,4 +21,6 @@ export interface ToastMessage {
type: "error" | "warning" | "info";
message: string;
id: number;
duration: number | null;
visible: boolean;
}