Allow setting title in gr.Info/Warning/Error (#9681)

* Allow setting title in gr.Info/Warning/Error

* fix

* add changeset

* allow title in gr.Error

* make argument consistent

* changes

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: Abubakar Abid <abubakar@huggingface.co>
This commit is contained in:
Tristan Zhang 2024-10-16 00:58:00 +08:00 committed by GitHub
parent ea2367ccb1
commit 2ed236187a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 86 additions and 28 deletions

View File

@ -0,0 +1,10 @@
---
"@gradio/client": minor
"@gradio/core": minor
"@gradio/statustracker": minor
"@self/app": minor
"@self/spa": minor
"gradio": minor
---
feat:Allow setting title in gr.Info/Warning/Error

View File

@ -342,6 +342,7 @@ export function handle_message(
type: "update",
status: {
queue,
title: data.output.title as string,
message: data.output.error as string,
visible: data.output.visible as boolean,
duration: data.output.duration as number,

View File

@ -329,6 +329,7 @@ export type GradioEvent = {
export interface Log {
log: string;
title: string;
level: "warning" | "info";
}
export interface Render {
@ -351,6 +352,7 @@ export interface Status {
size?: number;
position?: number;
eta?: number;
title?: string;
message?: string;
progress_data?: {
progress: number | null;

View File

@ -350,6 +350,7 @@ export function submit(
} else if (type === "log") {
fire_event({
type: "log",
title: data.title,
log: data.log,
level: data.level,
endpoint: _endpoint,
@ -485,6 +486,7 @@ export function submit(
} else if (type === "log") {
fire_event({
type: "log",
title: data.title,
log: data.log,
level: data.level,
endpoint: _endpoint,
@ -645,6 +647,7 @@ export function submit(
} else if (type === "log") {
fire_event({
type: "log",
title: data.title,
log: data.log,
level: data.level,
endpoint: _endpoint,

View File

@ -80,13 +80,16 @@ class Error(Exception):
message: str = "Error raised.",
duration: float | None = 10,
visible: bool = True,
title: str = "Error",
):
"""
Parameters:
message: The error message to be displayed to the user. Can be HTML, which will be rendered in the modal.
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.
title: The title to be displayed to the user at the top of the error modal.
"""
self.title = title
self.message = message
self.duration = duration
self.visible = visible

View File

@ -1036,6 +1036,7 @@ def skip() -> dict:
def log_message(
message: str,
title: str,
level: Literal["info", "warning"] = "info",
duration: float | None = 10,
visible: bool = True,
@ -1053,13 +1054,21 @@ def log_message(
warnings.warn(message)
return
blocks._queue.log_message(
event_id=event_id, log=message, level=level, duration=duration, visible=visible
event_id=event_id,
log=message,
title=title,
level=level,
duration=duration,
visible=visible,
)
@document(documentation_group="modals")
def Warning( # noqa: N802
message: str = "Warning issued.", duration: float | None = 10, visible: bool = True
message: str = "Warning issued.",
duration: float | None = 10,
visible: bool = True,
title: str = "Warning",
):
"""
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.
@ -1068,6 +1077,7 @@ def Warning( # noqa: N802
message: The warning message to be displayed to the user. Can be HTML, which will be rendered in the modal.
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.
title: The title to be displayed to the user at the top of the modal.
Example:
import gradio as gr
def hello_world():
@ -1078,7 +1088,9 @@ def Warning( # noqa: N802
demo.load(hello_world, inputs=None, outputs=[md])
demo.queue().launch()
"""
log_message(message, level="warning", duration=duration, visible=visible)
log_message(
message, title=title, level="warning", duration=duration, visible=visible
)
@document(documentation_group="modals")
@ -1086,6 +1098,7 @@ def Info( # noqa: N802
message: str = "Info issued.",
duration: float | None = 10,
visible: bool = True,
title: str = "Info",
):
"""
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.
@ -1094,6 +1107,7 @@ def Info( # noqa: N802
message: The info message to be displayed to the user. Can be HTML, which will be rendered in the modal.
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.
title: The title to be displayed to the user at the top of the modal.
Example:
import gradio as gr
def hello_world():
@ -1104,4 +1118,4 @@ def Info( # noqa: N802
demo.load(hello_world, inputs=None, outputs=[md])
demo.queue().launch()
"""
log_message(message, level="info", duration=duration, visible=visible)
log_message(message, title=title, level="info", duration=duration, visible=visible)

View File

@ -394,6 +394,7 @@ class Queue:
self,
event_id: str,
log: str,
title: str,
level: Literal["info", "warning"],
duration: float | None = 10,
visible: bool = True,
@ -406,6 +407,7 @@ class Queue:
level=level,
duration=duration,
visible=visible,
title=title,
)
self.send_message(event, log_message)
@ -644,6 +646,7 @@ class Queue:
event,
ProcessCompletedMessage(
output=content,
title=content["title"], # type: ignore
success=False,
),
)

View File

@ -28,6 +28,7 @@ class LogMessage(BaseMessage):
level: Literal["info", "warning"]
duration: Optional[float] = 10
visible: bool = True
title: str
class EstimationMessage(BaseMessage):
@ -46,6 +47,7 @@ class ProcessCompletedMessage(BaseMessage):
msg: Literal[ServerMessage.process_completed] = ServerMessage.process_completed # type: ignore
output: dict
success: bool
title: Optional[str] = None
class ProcessGeneratingMessage(BaseMessage):

View File

@ -1409,10 +1409,13 @@ def error_payload(
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
if isinstance(error, Error):
content["error"] = error.message
content["duration"] = error.duration
content["visible"] = error.visible
content["title"] = error.title
else:
content["error"] = str(error)
return content

View File

@ -172,7 +172,7 @@
let url = new URL(`http://${host}${app.api_prefix}/dev/reload`);
stream = new EventSource(url);
stream.addEventListener("error", async (e) => {
new_message_fn("Error reloading app", "error");
new_message_fn("Error", "Error reloading app", "error");
// @ts-ignore
console.error(JSON.parse(e.data));
});
@ -195,7 +195,7 @@
}
});
let new_message_fn: (message: string, type: string) => void;
let new_message_fn: (title: string, message: string, type: string) => void;
onMount(async () => {
intersecting = create_intersection_store();

View File

@ -152,6 +152,7 @@
let messages: (ToastMessage & { fn_index: number })[] = [];
function new_message(
title: string,
message: string,
fn_index: number,
type: ToastMessage["type"],
@ -159,6 +160,7 @@
visible = true
): ToastMessage & { fn_index: number } {
return {
title,
message,
fn_index,
type,
@ -169,10 +171,11 @@
}
export function add_new_message(
title: string,
message: string,
type: ToastMessage["type"]
): void {
messages = [new_message(message, -1, type), ...messages];
messages = [new_message(title, message, -1, type), ...messages];
}
let _error_id = -1;
@ -241,7 +244,7 @@
if (inputs_waiting.length > 0) {
for (const input of inputs_waiting) {
if (dep.inputs.includes(input)) {
add_new_message(WAITING_FOR_INPUTS_MESSAGE, "warning");
add_new_message("Warning", WAITING_FOR_INPUTS_MESSAGE, "warning");
return;
}
}
@ -346,7 +349,10 @@
);
} catch (e) {
const fn_index = 0; // Mock value for fn_index
messages = [new_message(String(e), fn_index, "error"), ...messages];
messages = [
new_message("Error", String(e), fn_index, "error"),
...messages
];
loading_status.update({
status: "error",
fn_index,
@ -413,9 +419,9 @@
}
function handle_log(msg: LogMessage): void {
const { log, fn_index, level, duration, visible } = msg;
const { title, log, fn_index, level, duration, visible } = msg;
messages = [
new_message(log, fn_index, level, duration, visible),
new_message(title, log, fn_index, level, duration, visible),
...messages
];
}
@ -463,7 +469,7 @@
) {
showed_duplicate_message = true;
messages = [
new_message(DUPLICATE_MESSAGE, fn_index, "warning"),
new_message("Warning", DUPLICATE_MESSAGE, fn_index, "warning"),
...messages
];
}
@ -475,7 +481,7 @@
) {
showed_mobile_warning = true;
messages = [
new_message(MOBILE_QUEUE_WARNING, fn_index, "warning"),
new_message("Warning", MOBILE_QUEUE_WARNING, fn_index, "warning"),
...messages
];
}
@ -503,7 +509,7 @@
if (status.broken && is_mobile_device && user_left_page) {
window.setTimeout(() => {
messages = [
new_message(MOBILE_RECONNECT_MESSAGE, fn_index, "error"),
new_message("Error", MOBILE_RECONNECT_MESSAGE, fn_index, "error"),
...messages
];
}, 0);
@ -515,8 +521,10 @@
MESSAGE_QUOTE_RE,
(_, b) => b
);
const _title = status.title ?? "Error";
messages = [
new_message(
_title,
_message,
fn_index,
"error",
@ -612,8 +620,10 @@
if (event === "share") {
const { title, description } = data as ShareData;
trigger_share(title, description);
} else if (event === "error" || event === "warning") {
messages = [new_message(data, -1, event), ...messages];
} else if (event === "error") {
messages = [new_message("Error", data, -1, event), ...messages];
} else if (event === "warning") {
messages = [new_message("Warning", data, -1, event), ...messages];
} else if (event == "clear_status") {
update_status(id, "complete", data);
} else if (event == "close_stream") {

View File

@ -319,7 +319,7 @@
let url = new URL(`http://${host}${app.api_prefix}/dev/reload`);
stream = new EventSource(url);
stream.addEventListener("error", async (e) => {
new_message_fn("Error reloading app", "error");
new_message_fn("Error", "Error reloading app", "error");
// @ts-ignore
console.error(JSON.parse(e.data));
});
@ -400,7 +400,7 @@
}
};
let new_message_fn: (message: string, type: string) => void;
let new_message_fn: (title: string, message: string, type: string) => void;
onMount(async () => {
intersecting.register(_id, wrapper);

View File

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

View File

@ -5,6 +5,7 @@
import { fade } from "svelte/transition";
import type { ToastMessage } from "./types";
export let title = "";
export let message = "";
export let type: ToastMessage["type"];
export let id: number;
@ -27,9 +28,7 @@
}
}
});
$: message = DOMPurify.sanitize(message);
$: display = visible;
$: duration = duration || null;
@ -73,7 +72,7 @@
</div>
<div class="toast-details {type}">
<div class="toast-title {type}">{type}</div>
<div class="toast-title {type}">{title}</div>
<div class="toast-text {type}">
{@html message}
</div>
@ -211,7 +210,6 @@
font-weight: var(--weight-bold);
font-size: var(--text-lg);
line-height: var(--line-sm);
text-transform: capitalize;
}
.toast-title.error {

View File

@ -20,6 +20,7 @@ export interface LoadingStatus {
export interface ToastMessage {
type: "error" | "warning" | "info";
title: string;
message: string;
id: number;
duration: number | null;