mirror of
https://github.com/gradio-app/gradio.git
synced 2025-02-17 11:29:58 +08:00
Add allow_file_downloads
param to allow downloading image/video/audio media in chatbot (#9905)
* add allow_file_downloads param * add download btn for markdown images * add changeset * tweak * fix test * remove param * revert param removal * fix show logic * rename show_download_button to allow_file_downloads * change default to True --------- 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:
parent
da6f191554
commit
08f4b8b000
6
.changeset/huge-windows-mate.md
Normal file
6
.changeset/huge-windows-mate.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@gradio/chatbot": patch
|
||||||
|
"gradio": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix:Add `allow_file_downloads` param to allow downloading image/video/audio media in chatbot
|
@ -184,6 +184,7 @@ class Chatbot(Component):
|
|||||||
placeholder: str | None = None,
|
placeholder: str | None = None,
|
||||||
examples: list[ExampleMessage] | None = None,
|
examples: list[ExampleMessage] | None = None,
|
||||||
show_copy_all_button=False,
|
show_copy_all_button=False,
|
||||||
|
allow_file_downloads=True,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Parameters:
|
Parameters:
|
||||||
@ -218,6 +219,7 @@ class Chatbot(Component):
|
|||||||
placeholder: a placeholder message to display in the chatbot when it is empty. Centered vertically and horizontally in the Chatbot. Supports Markdown and HTML. If None, no placeholder is displayed.
|
placeholder: a placeholder message to display in the chatbot when it is empty. Centered vertically and horizontally in the Chatbot. Supports Markdown and HTML. If None, no placeholder is displayed.
|
||||||
examples: A list of example messages to display in the chatbot before any user/assistant messages are shown. Each example should be a dictionary with an optional "text" key representing the message that should be populated in the Chatbot when clicked, an optional "files" key, whose value should be a list of files to populate in the Chatbot, an optional "icon" key, whose value should be a filepath or URL to an image to display in the example box, and an optional "display_text" key, whose value should be the text to display in the example box. If "display_text" is not provided, the value of "text" will be displayed.
|
examples: A list of example messages to display in the chatbot before any user/assistant messages are shown. Each example should be a dictionary with an optional "text" key representing the message that should be populated in the Chatbot when clicked, an optional "files" key, whose value should be a list of files to populate in the Chatbot, an optional "icon" key, whose value should be a filepath or URL to an image to display in the example box, and an optional "display_text" key, whose value should be the text to display in the example box. If "display_text" is not provided, the value of "text" will be displayed.
|
||||||
show_copy_all_button: If True, will show a copy all button that copies all chatbot messages to the clipboard.
|
show_copy_all_button: If True, will show a copy all button that copies all chatbot messages to the clipboard.
|
||||||
|
allow_file_downloads: If True, will show a download button for chatbot messages that contain media. Defaults to True.
|
||||||
"""
|
"""
|
||||||
if type is None:
|
if type is None:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
@ -259,6 +261,7 @@ class Chatbot(Component):
|
|||||||
self.line_breaks = line_breaks
|
self.line_breaks = line_breaks
|
||||||
self.layout = layout
|
self.layout = layout
|
||||||
self.show_copy_all_button = show_copy_all_button
|
self.show_copy_all_button = show_copy_all_button
|
||||||
|
self.allow_file_downloads = allow_file_downloads
|
||||||
super().__init__(
|
super().__init__(
|
||||||
label=label,
|
label=label,
|
||||||
every=every,
|
every=every,
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
|
|
||||||
<Story
|
<Story
|
||||||
name="Chatbot with math disabled, small height"
|
name="Chatbot with math disabled, small height"
|
||||||
args={{ latex_delimiters: [], height: 200 }}
|
args={{ latex_delimiters: [], height: 200, show_copy_button: false }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Story
|
<Story
|
||||||
@ -85,24 +85,10 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Story
|
<Story
|
||||||
name="Chatbot with copy button"
|
name="Chatbot with chat bubble full width disabled and copy button"
|
||||||
args={{
|
|
||||||
show_copy_button: true
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Story
|
|
||||||
name="Chatbot with chat bubble full width disabled"
|
|
||||||
args={{
|
|
||||||
bubble_full_width: false
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Story
|
|
||||||
name="Chatbot with panel layout enabled"
|
|
||||||
args={{
|
args={{
|
||||||
bubble_full_width: false,
|
bubble_full_width: false,
|
||||||
layout: "panel"
|
show_copy_button: true
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -79,6 +79,7 @@
|
|||||||
export let placeholder: string | null = null;
|
export let placeholder: string | null = null;
|
||||||
export let examples: ExampleMessage[] | null = null;
|
export let examples: ExampleMessage[] | null = null;
|
||||||
export let theme_mode: "system" | "light" | "dark";
|
export let theme_mode: "system" | "light" | "dark";
|
||||||
|
export let allow_file_downloads = true;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Block
|
<Block
|
||||||
@ -157,6 +158,7 @@
|
|||||||
load_component={gradio.load_component}
|
load_component={gradio.load_component}
|
||||||
msg_format={type}
|
msg_format={type}
|
||||||
root={gradio.root}
|
root={gradio.root}
|
||||||
|
{allow_file_downloads}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Block>
|
</Block>
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
export let show_retry: boolean;
|
export let show_retry: boolean;
|
||||||
export let show_undo: boolean;
|
export let show_undo: boolean;
|
||||||
export let show_copy_button: boolean;
|
export let show_copy_button: boolean;
|
||||||
export let show: boolean;
|
|
||||||
export let message: NormalisedMessage | NormalisedMessage[];
|
export let message: NormalisedMessage | NormalisedMessage[];
|
||||||
export let position: "right" | "left";
|
export let position: "right" | "left";
|
||||||
export let avatar: FileData | null;
|
export let avatar: FileData | null;
|
||||||
@ -48,7 +47,7 @@
|
|||||||
message.content.value?.url;
|
message.content.value?.url;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if show}
|
{#if show_copy || show_retry || show_undo || likeable}
|
||||||
<div
|
<div
|
||||||
class="message-buttons-{position} {layout} message-buttons {avatar !==
|
class="message-buttons-{position} {layout} message-buttons {avatar !==
|
||||||
null && 'with-avatar'}"
|
null && 'with-avatar'}"
|
||||||
@ -57,14 +56,6 @@
|
|||||||
{#if show_copy}
|
{#if show_copy}
|
||||||
<Copy value={message_text} />
|
<Copy value={message_text} />
|
||||||
{/if}
|
{/if}
|
||||||
{#if show_download && !Array.isArray(message) && is_component_message(message)}
|
|
||||||
<DownloadLink
|
|
||||||
href={message?.content?.value.url}
|
|
||||||
download={message.content.value.orig_name || "image"}
|
|
||||||
>
|
|
||||||
<IconButton Icon={DownloadIcon} />
|
|
||||||
</DownloadLink>
|
|
||||||
{/if}
|
|
||||||
{#if show_retry}
|
{#if show_retry}
|
||||||
<IconButton
|
<IconButton
|
||||||
Icon={Retry}
|
Icon={Retry}
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
import type { NormalisedMessage } from "../types";
|
import type { NormalisedMessage } from "../types";
|
||||||
import { copy } from "@gradio/utils";
|
import { copy } from "@gradio/utils";
|
||||||
import Message from "./Message.svelte";
|
import Message from "./Message.svelte";
|
||||||
|
import { DownloadLink } from "@gradio/wasm/svelte";
|
||||||
|
|
||||||
import { dequal } from "dequal/lite";
|
import { dequal } from "dequal/lite";
|
||||||
import {
|
import {
|
||||||
@ -22,7 +23,13 @@
|
|||||||
} from "svelte";
|
} from "svelte";
|
||||||
import { Image } from "@gradio/image/shared";
|
import { Image } from "@gradio/image/shared";
|
||||||
|
|
||||||
import { Clear, Trash, Community, ScrollDownArrow } from "@gradio/icons";
|
import {
|
||||||
|
Clear,
|
||||||
|
Trash,
|
||||||
|
Community,
|
||||||
|
ScrollDownArrow,
|
||||||
|
Download
|
||||||
|
} from "@gradio/icons";
|
||||||
import { IconButtonWrapper, IconButton } from "@gradio/atoms";
|
import { IconButtonWrapper, IconButton } from "@gradio/atoms";
|
||||||
import type { SelectData, LikeData } from "@gradio/utils";
|
import type { SelectData, LikeData } from "@gradio/utils";
|
||||||
import type { ExampleMessage } from "../types";
|
import type { ExampleMessage } from "../types";
|
||||||
@ -40,6 +47,7 @@
|
|||||||
|
|
||||||
export let _fetch: typeof fetch;
|
export let _fetch: typeof fetch;
|
||||||
export let load_component: Gradio["load_component"];
|
export let load_component: Gradio["load_component"];
|
||||||
|
export let allow_file_downloads: boolean;
|
||||||
|
|
||||||
let _components: Record<string, ComponentType<SvelteComponent>> = {};
|
let _components: Record<string, ComponentType<SvelteComponent>> = {};
|
||||||
|
|
||||||
@ -292,6 +300,14 @@
|
|||||||
on:click={() => (is_image_preview_open = false)}
|
on:click={() => (is_image_preview_open = false)}
|
||||||
label={"Clear"}
|
label={"Clear"}
|
||||||
/>
|
/>
|
||||||
|
{#if allow_file_downloads}
|
||||||
|
<DownloadLink
|
||||||
|
href={image_preview_source}
|
||||||
|
download={image_preview_source_alt || "image"}
|
||||||
|
>
|
||||||
|
<IconButton Icon={Download} label={"Download"} />
|
||||||
|
</DownloadLink>
|
||||||
|
{/if}
|
||||||
</IconButtonWrapper>
|
</IconButtonWrapper>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@ -326,6 +342,7 @@
|
|||||||
{show_copy_button}
|
{show_copy_button}
|
||||||
handle_action={(selected) => handle_like(i, messages[0], selected)}
|
handle_action={(selected) => handle_like(i, messages[0], selected)}
|
||||||
scroll={is_browser ? scroll : () => {}}
|
scroll={is_browser ? scroll : () => {}}
|
||||||
|
{allow_file_downloads}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
{#if pending_message}
|
{#if pending_message}
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
export let i18n;
|
export let i18n;
|
||||||
export let upload;
|
export let upload;
|
||||||
export let _fetch;
|
export let _fetch;
|
||||||
|
export let allow_file_downloads: boolean;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if type === "gallery"}
|
{#if type === "gallery"}
|
||||||
@ -45,7 +46,7 @@
|
|||||||
label=""
|
label=""
|
||||||
waveform_settings={{}}
|
waveform_settings={{}}
|
||||||
waveform_options={{}}
|
waveform_options={{}}
|
||||||
show_download_button={false}
|
show_download_button={allow_file_downloads}
|
||||||
on:load
|
on:load
|
||||||
/>
|
/>
|
||||||
{:else if type === "video"}
|
{:else if type === "video"}
|
||||||
@ -57,7 +58,7 @@
|
|||||||
show_share_button={true}
|
show_share_button={true}
|
||||||
{i18n}
|
{i18n}
|
||||||
{upload}
|
{upload}
|
||||||
show_download_button={false}
|
show_download_button={allow_file_downloads}
|
||||||
on:load
|
on:load
|
||||||
>
|
>
|
||||||
<track kind="captions" />
|
<track kind="captions" />
|
||||||
@ -68,7 +69,7 @@
|
|||||||
{value}
|
{value}
|
||||||
show_label={false}
|
show_label={false}
|
||||||
label="chatbot-image"
|
label="chatbot-image"
|
||||||
show_download_button={false}
|
show_download_button={allow_file_downloads}
|
||||||
on:load
|
on:load
|
||||||
{i18n}
|
{i18n}
|
||||||
/>
|
/>
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
export let msg_format: "tuples" | "messages";
|
export let msg_format: "tuples" | "messages";
|
||||||
export let handle_action: (selected: string | null) => void;
|
export let handle_action: (selected: string | null) => void;
|
||||||
export let scroll: () => void;
|
export let scroll: () => void;
|
||||||
|
export let allow_file_downloads: boolean;
|
||||||
|
|
||||||
function handle_select(i: number, message: NormalisedMessage): void {
|
function handle_select(i: number, message: NormalisedMessage): void {
|
||||||
dispatch("select", {
|
dispatch("select", {
|
||||||
@ -71,7 +72,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ButtonPanelProps = {
|
type ButtonPanelProps = {
|
||||||
show: boolean;
|
|
||||||
handle_action: (selected: string | null) => void;
|
handle_action: (selected: string | null) => void;
|
||||||
likeable: boolean;
|
likeable: boolean;
|
||||||
show_retry: boolean;
|
show_retry: boolean;
|
||||||
@ -86,7 +86,6 @@
|
|||||||
|
|
||||||
let button_panel_props: ButtonPanelProps;
|
let button_panel_props: ButtonPanelProps;
|
||||||
$: button_panel_props = {
|
$: button_panel_props = {
|
||||||
show: show_like || show_retry || show_undo || show_copy_button,
|
|
||||||
handle_action,
|
handle_action,
|
||||||
likeable: show_like,
|
likeable: show_like,
|
||||||
show_retry,
|
show_retry,
|
||||||
@ -147,11 +146,23 @@
|
|||||||
aria-label={role + "'s message: " + get_message_label_data(message)}
|
aria-label={role + "'s message: " + get_message_label_data(message)}
|
||||||
>
|
>
|
||||||
{#if message.type === "text"}
|
{#if message.type === "text"}
|
||||||
{#if message.metadata.title}
|
<div class="message-content">
|
||||||
<MessageBox
|
{#if message.metadata.title}
|
||||||
title={message.metadata.title}
|
<MessageBox
|
||||||
expanded={is_last_bot_message([message], value)}
|
title={message.metadata.title}
|
||||||
>
|
expanded={is_last_bot_message([message], value)}
|
||||||
|
>
|
||||||
|
<Markdown
|
||||||
|
message={message.content}
|
||||||
|
{latex_delimiters}
|
||||||
|
{sanitize_html}
|
||||||
|
{render_markdown}
|
||||||
|
{line_breaks}
|
||||||
|
on:load={scroll}
|
||||||
|
{root}
|
||||||
|
/>
|
||||||
|
</MessageBox>
|
||||||
|
{:else}
|
||||||
<Markdown
|
<Markdown
|
||||||
message={message.content}
|
message={message.content}
|
||||||
{latex_delimiters}
|
{latex_delimiters}
|
||||||
@ -161,18 +172,8 @@
|
|||||||
on:load={scroll}
|
on:load={scroll}
|
||||||
{root}
|
{root}
|
||||||
/>
|
/>
|
||||||
</MessageBox>
|
{/if}
|
||||||
{:else}
|
</div>
|
||||||
<Markdown
|
|
||||||
message={message.content}
|
|
||||||
{latex_delimiters}
|
|
||||||
{sanitize_html}
|
|
||||||
{render_markdown}
|
|
||||||
{line_breaks}
|
|
||||||
on:load={scroll}
|
|
||||||
{root}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{:else if message.type === "component" && message.content.component in _components}
|
{:else if message.type === "component" && message.content.component in _components}
|
||||||
<Component
|
<Component
|
||||||
{target}
|
{target}
|
||||||
@ -185,6 +186,7 @@
|
|||||||
{upload}
|
{upload}
|
||||||
{_fetch}
|
{_fetch}
|
||||||
on:load={() => scroll()}
|
on:load={() => scroll()}
|
||||||
|
{allow_file_downloads}
|
||||||
/>
|
/>
|
||||||
{:else if message.type === "component" && message.content.component === "file"}
|
{:else if message.type === "component" && message.content.component === "file"}
|
||||||
<a
|
<a
|
||||||
@ -316,7 +318,6 @@
|
|||||||
box-shadow: var(--shadow-drop);
|
box-shadow: var(--shadow-drop);
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
padding: var(--spacing-sm) var(--spacing-xl);
|
|
||||||
border-color: var(--border-color-accent-subdued);
|
border-color: var(--border-color-accent-subdued);
|
||||||
background-color: var(--color-accent-soft);
|
background-color: var(--color-accent-soft);
|
||||||
}
|
}
|
||||||
@ -330,7 +331,6 @@
|
|||||||
box-shadow: var(--shadow-drop);
|
box-shadow: var(--shadow-drop);
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
padding: var(--spacing-sm) var(--spacing-xl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel .user :global(*) {
|
.panel .user :global(*) {
|
||||||
@ -417,17 +417,6 @@
|
|||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user {
|
|
||||||
border-width: 1px;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
align-self: flex-start;
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
box-shadow: var(--shadow-drop);
|
|
||||||
text-align: right;
|
|
||||||
padding: var(--spacing-sm) var(--spacing-xl);
|
|
||||||
border-color: var(--border-color-accent-subdued);
|
|
||||||
background-color: var(--color-accent-soft);
|
|
||||||
}
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.user-row.bubble {
|
.user-row.bubble {
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
@ -441,6 +430,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-content {
|
||||||
|
padding: var(--spacing-sm) var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
.avatar-container {
|
.avatar-container {
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -51,6 +51,7 @@ class TestChatbot:
|
|||||||
"_selectable": False,
|
"_selectable": False,
|
||||||
"_retryable": False,
|
"_retryable": False,
|
||||||
"_undoable": False,
|
"_undoable": False,
|
||||||
|
"allow_file_downloads": True,
|
||||||
"key": None,
|
"key": None,
|
||||||
"type": "tuples",
|
"type": "tuples",
|
||||||
"latex_delimiters": [{"display": True, "left": "$$", "right": "$$"}],
|
"latex_delimiters": [{"display": True, "left": "$$", "right": "$$"}],
|
||||||
|
Loading…
Reference in New Issue
Block a user