mirror of
https://github.com/gradio-app/gradio.git
synced 2025-03-31 12:20:26 +08:00
Group chatbot messages by default (#10169)
* handle pasted text as file * test * add changeset * remove unneeded test * update file UI * add changeset * Revert "handle pasted text as file" This reverts commit 1910029f103f89573210ee0a8b62823505a1b1db. * add changeset * Revert "test" This reverts commit 25c17bd8d3505d09cd571382afeeacdec197bd9c. * story * remove border * Revert "add changeset" This reverts commit 29a91ee9dff771663c414880d10815ffe2f6b961. * add changeset * add code * Code * add code * add changeset * Update solid-hands-nail.md * code * add metadata typecheck * trigger ci * remove thought css * Revert "remove thought css" This reverts commit f1ea8f88f6ced9dc8e8897abdfd529952f851954. * fix tuples - add borders * lint * Fix typecheck * css tweak * add code * fix parameter name --------- Co-authored-by: Hannah <hannahblair@users.noreply.github.com> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
This commit is contained in:
parent
c9ba9a4475
commit
25484f4bfb
6
.changeset/solid-hands-nail.md
Normal file
6
.changeset/solid-hands-nail.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
"@gradio/chatbot": minor
|
||||
"gradio": minor
|
||||
---
|
||||
|
||||
feat:By default, consecutive messages are displayed in the same bubble. This is controlled by the new `display_consecutive_in_same_bubble` param of Chatbot.
|
@ -199,6 +199,7 @@ class Chatbot(Component):
|
||||
examples: list[ExampleMessage] | None = None,
|
||||
show_copy_all_button=False,
|
||||
allow_file_downloads=True,
|
||||
group_consecutive_messages: bool = True,
|
||||
):
|
||||
"""
|
||||
Parameters:
|
||||
@ -235,6 +236,7 @@ class Chatbot(Component):
|
||||
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.
|
||||
allow_file_downloads: If True, will show a download button for chatbot messages that contain media. Defaults to True.
|
||||
group_consecutive_messages: If True, will display consecutive messages from the same role in the same bubble. If False, will display each message in a separate bubble. Defaults to True.
|
||||
"""
|
||||
if type is None:
|
||||
warnings.warn(
|
||||
@ -259,6 +261,7 @@ class Chatbot(Component):
|
||||
self.max_height = max_height
|
||||
self.min_height = min_height
|
||||
self.rtl = rtl
|
||||
self.group_consecutive_messages = group_consecutive_messages
|
||||
if latex_delimiters is None:
|
||||
latex_delimiters = [{"left": "$$", "right": "$$", "display": True}]
|
||||
self.latex_delimiters = latex_delimiters
|
||||
|
@ -1,8 +1,11 @@
|
||||
<script>
|
||||
export let top_panel = true;
|
||||
export let display_top_corner = false;
|
||||
</script>
|
||||
|
||||
<div class={`icon-button-wrapper ${top_panel ? "top-panel" : ""}`}>
|
||||
<div
|
||||
class={`icon-button-wrapper ${top_panel ? "top-panel" : ""} ${display_top_corner ? "display-top-corner" : "hide-top-corner"}`}
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
||||
@ -16,11 +19,20 @@
|
||||
gap: var(--spacing-sm);
|
||||
box-shadow: var(--shadow-drop);
|
||||
border: 1px solid var(--border-color-primary);
|
||||
background: var(--block-background-fill);
|
||||
padding: var(--spacing-xxs);
|
||||
}
|
||||
|
||||
.icon-button-wrapper.hide-top-corner {
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
border-radius: var(--block-label-right-radius);
|
||||
background: var(--block-background-fill);
|
||||
padding: var(--spacing-xxs);
|
||||
}
|
||||
|
||||
.icon-button-wrapper.display-top-corner {
|
||||
border-radius: var(--radius-sm) 0 0 var(--radius-sm);
|
||||
top: var(--spacing-sm);
|
||||
right: -1px;
|
||||
}
|
||||
|
||||
.icon-button-wrapper:not(.top-panel) {
|
||||
|
@ -25,6 +25,7 @@
|
||||
export let waveform_options: WaveformOptions;
|
||||
export let editable = true;
|
||||
export let loop: boolean;
|
||||
export let display_icon_button_wrapper_top_corner = false;
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: FileData;
|
||||
@ -45,7 +46,9 @@
|
||||
/>
|
||||
|
||||
{#if value !== null}
|
||||
<IconButtonWrapper>
|
||||
<IconButtonWrapper
|
||||
display_top_corner={display_icon_button_wrapper_top_corner}
|
||||
>
|
||||
{#if show_download_button}
|
||||
<DownloadLink
|
||||
href={value.is_stream
|
||||
|
@ -224,6 +224,38 @@ This document is a showcase of various Markdown capabilities.`,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Consecutive messages grouped in same bubble"
|
||||
args={{
|
||||
type: "messages",
|
||||
display_consecutive_in_same_bubble: true,
|
||||
value: [
|
||||
{
|
||||
role: "user",
|
||||
content: "Show me the file."
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: "Second user message"
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: "Here is the file you requested"
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: {
|
||||
file: {
|
||||
path: "abc/qwerty.txt",
|
||||
url: ""
|
||||
},
|
||||
alt_text: null
|
||||
}
|
||||
}
|
||||
]
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="MultimodalChatbot with examples"
|
||||
args={{
|
||||
@ -327,6 +359,38 @@ This document is a showcase of various Markdown capabilities.`,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Consecutive messages not grouped in same bubble"
|
||||
args={{
|
||||
type: "messages",
|
||||
display_consecutive_in_same_bubble: false,
|
||||
value: [
|
||||
{
|
||||
role: "user",
|
||||
content: "Show me the file."
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: "Second user message"
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: "Here is the file you requested"
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: {
|
||||
file: {
|
||||
path: "abc/qwerty.txt",
|
||||
url: ""
|
||||
},
|
||||
alt_text: null
|
||||
}
|
||||
}
|
||||
]
|
||||
}}
|
||||
/>
|
||||
|
||||
<Story
|
||||
name="Chatbot with examples (not multimodal)"
|
||||
args={{
|
||||
|
@ -44,6 +44,7 @@
|
||||
export let autoscroll = true;
|
||||
export let _retryable = false;
|
||||
export let _undoable = false;
|
||||
export let group_consecutive_messages = true;
|
||||
export let latex_delimiters: {
|
||||
left: string;
|
||||
right: string;
|
||||
@ -127,6 +128,7 @@
|
||||
{show_copy_all_button}
|
||||
value={_value}
|
||||
{latex_delimiters}
|
||||
display_consecutive_in_same_bubble={group_consecutive_messages}
|
||||
{render_markdown}
|
||||
{theme_mode}
|
||||
pending_message={loading_status?.status === "pending"}
|
||||
|
@ -44,6 +44,7 @@
|
||||
export let _fetch: typeof fetch;
|
||||
export let load_component: Gradio["load_component"];
|
||||
export let allow_file_downloads: boolean;
|
||||
export let display_consecutive_in_same_bubble: boolean;
|
||||
|
||||
let _components: Record<string, ComponentType<SvelteComponent>> = {};
|
||||
|
||||
@ -278,6 +279,7 @@
|
||||
{@const opposite_avatar_img = avatar_images[role === "user" ? 0 : 1]}
|
||||
<Message
|
||||
{messages}
|
||||
{display_consecutive_in_same_bubble}
|
||||
{opposite_avatar_img}
|
||||
{avatar_img}
|
||||
{role}
|
||||
|
@ -9,12 +9,14 @@
|
||||
export let upload;
|
||||
export let _fetch;
|
||||
export let allow_file_downloads: boolean;
|
||||
export let display_icon_button_wrapper_top_corner = false;
|
||||
</script>
|
||||
|
||||
{#if type === "gallery"}
|
||||
<svelte:component
|
||||
this={components[type]}
|
||||
{value}
|
||||
{display_icon_button_wrapper_top_corner}
|
||||
show_label={false}
|
||||
{i18n}
|
||||
label=""
|
||||
@ -47,6 +49,7 @@
|
||||
waveform_settings={{ autoplay: props.autoplay }}
|
||||
waveform_options={{}}
|
||||
show_download_button={allow_file_downloads}
|
||||
{display_icon_button_wrapper_top_corner}
|
||||
on:load
|
||||
/>
|
||||
{:else if type === "video"}
|
||||
@ -58,6 +61,7 @@
|
||||
show_share_button={true}
|
||||
{i18n}
|
||||
{upload}
|
||||
{display_icon_button_wrapper_top_corner}
|
||||
show_download_button={allow_file_downloads}
|
||||
on:load
|
||||
>
|
||||
@ -70,6 +74,7 @@
|
||||
show_label={false}
|
||||
label="chatbot-image"
|
||||
show_download_button={allow_file_downloads}
|
||||
{display_icon_button_wrapper_top_corner}
|
||||
on:load
|
||||
{i18n}
|
||||
/>
|
||||
|
@ -45,6 +45,7 @@
|
||||
export let handle_action: (selected: string | null) => void;
|
||||
export let scroll: () => void;
|
||||
export let allow_file_downloads: boolean;
|
||||
export let display_consecutive_in_same_bubble: boolean;
|
||||
|
||||
function handle_select(i: number, message: NormalisedMessage): void {
|
||||
dispatch("select", {
|
||||
@ -116,42 +117,55 @@
|
||||
class="flex-wrap"
|
||||
class:component-wrap={messages[0].type === "component"}
|
||||
>
|
||||
{#each messages as message, thought_index}
|
||||
<div
|
||||
class="message {role} {is_component_message(message)
|
||||
? message?.content.component
|
||||
: ''}"
|
||||
class:panel-full-width={true}
|
||||
class:message-markdown-disabled={!render_markdown}
|
||||
class:component={message.type === "component"}
|
||||
class:html={is_component_message(message) &&
|
||||
message.content.component === "html"}
|
||||
class:thought={thought_index > 0}
|
||||
>
|
||||
<button
|
||||
data-testid={role}
|
||||
class:latest={i === value.length - 1}
|
||||
<div
|
||||
class:message={display_consecutive_in_same_bubble}
|
||||
class={display_consecutive_in_same_bubble ? role : ""}
|
||||
>
|
||||
{#each messages as message, thought_index}
|
||||
<div
|
||||
class="message {!display_consecutive_in_same_bubble ? role : ''}"
|
||||
class:panel-full-width={true}
|
||||
class:message-markdown-disabled={!render_markdown}
|
||||
style:user-select="text"
|
||||
class:selectable
|
||||
style:cursor={selectable ? "pointer" : "default"}
|
||||
style:text-align={rtl ? "right" : "left"}
|
||||
on:click={() => handle_select(i, message)}
|
||||
on:keydown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
handle_select(i, message);
|
||||
}
|
||||
}}
|
||||
dir={rtl ? "rtl" : "ltr"}
|
||||
aria-label={role + "'s message: " + get_message_label_data(message)}
|
||||
class:component={message.type === "component"}
|
||||
class:html={is_component_message(message) &&
|
||||
message.content.component === "html"}
|
||||
class:thought={thought_index > 0}
|
||||
>
|
||||
{#if message.type === "text"}
|
||||
<div class="message-content">
|
||||
{#if message.metadata.title}
|
||||
<MessageBox
|
||||
title={message.metadata.title}
|
||||
expanded={is_last_bot_message([message], value)}
|
||||
>
|
||||
<button
|
||||
data-testid={role}
|
||||
class:latest={i === value.length - 1}
|
||||
class:message-markdown-disabled={!render_markdown}
|
||||
style:user-select="text"
|
||||
class:selectable
|
||||
style:cursor={selectable ? "pointer" : "default"}
|
||||
style:text-align={rtl ? "right" : "left"}
|
||||
on:click={() => handle_select(i, message)}
|
||||
on:keydown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
handle_select(i, message);
|
||||
}
|
||||
}}
|
||||
dir={rtl ? "rtl" : "ltr"}
|
||||
aria-label={role + "'s message: " + get_message_label_data(message)}
|
||||
>
|
||||
{#if message.type === "text"}
|
||||
<div class="message-content">
|
||||
{#if message?.metadata?.title}
|
||||
<MessageBox
|
||||
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
|
||||
message={message.content}
|
||||
{latex_delimiters}
|
||||
@ -161,79 +175,70 @@
|
||||
on:load={scroll}
|
||||
{root}
|
||||
/>
|
||||
</MessageBox>
|
||||
{:else}
|
||||
<Markdown
|
||||
message={message.content}
|
||||
{latex_delimiters}
|
||||
{sanitize_html}
|
||||
{render_markdown}
|
||||
{line_breaks}
|
||||
on:load={scroll}
|
||||
{root}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if message.type === "component" && message.content.component in _components}
|
||||
<Component
|
||||
{target}
|
||||
{theme_mode}
|
||||
props={message.content.props}
|
||||
type={message.content.component}
|
||||
components={_components}
|
||||
value={message.content.value}
|
||||
{i18n}
|
||||
{upload}
|
||||
{_fetch}
|
||||
on:load={() => scroll()}
|
||||
{allow_file_downloads}
|
||||
/>
|
||||
{:else if message.type === "component" && message.content.component === "file"}
|
||||
<div class="file-container">
|
||||
<div class="file-icon">
|
||||
<File />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="file-info">
|
||||
<a
|
||||
data-testid="chatbot-file"
|
||||
class="file-link"
|
||||
href={message.content.value.url}
|
||||
target="_blank"
|
||||
download={window.__is_colab__
|
||||
? null
|
||||
: message.content.value?.orig_name ||
|
||||
message.content.value?.path.split("/").pop() ||
|
||||
"file"}
|
||||
>
|
||||
<span class="file-name"
|
||||
>{message.content.value?.orig_name ||
|
||||
message.content.value?.path.split("/").pop() ||
|
||||
"file"}</span
|
||||
{:else if message.type === "component" && message.content.component in _components}
|
||||
<Component
|
||||
{target}
|
||||
{theme_mode}
|
||||
props={message.content.props}
|
||||
type={message.content.component}
|
||||
components={_components}
|
||||
value={message.content.value}
|
||||
display_icon_button_wrapper_top_corner={thought_index > 0 &&
|
||||
display_consecutive_in_same_bubble}
|
||||
{i18n}
|
||||
{upload}
|
||||
{_fetch}
|
||||
on:load={() => scroll()}
|
||||
{allow_file_downloads}
|
||||
/>
|
||||
{:else if message.type === "component" && message.content.component === "file"}
|
||||
<div class="file-container">
|
||||
<div class="file-icon">
|
||||
<File />
|
||||
</div>
|
||||
<div class="file-info">
|
||||
<a
|
||||
data-testid="chatbot-file"
|
||||
class="file-link"
|
||||
href={message.content.value.url}
|
||||
target="_blank"
|
||||
download={window.__is_colab__
|
||||
? null
|
||||
: message.content.value?.orig_name ||
|
||||
message.content.value?.path.split("/").pop() ||
|
||||
"file"}
|
||||
>
|
||||
</a>
|
||||
<span class="file-type"
|
||||
>{(
|
||||
message.content.value?.orig_name ||
|
||||
message.content.value?.path ||
|
||||
""
|
||||
)
|
||||
.split(".")
|
||||
.pop()
|
||||
.toUpperCase()}</span
|
||||
>
|
||||
<span class="file-name"
|
||||
>{message.content.value?.orig_name ||
|
||||
message.content.value?.path.split("/").pop() ||
|
||||
"file"}</span
|
||||
>
|
||||
</a>
|
||||
<span class="file-type"
|
||||
>{(
|
||||
message.content.value?.orig_name ||
|
||||
message.content.value?.path ||
|
||||
""
|
||||
)
|
||||
.split(".")
|
||||
.pop()
|
||||
.toUpperCase()}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if layout === "panel"}
|
||||
<ButtonPanel
|
||||
{...button_panel_props}
|
||||
on:copy={(e) => dispatch("copy", e.detail)}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{#if layout === "panel"}
|
||||
<ButtonPanel
|
||||
{...button_panel_props}
|
||||
on:copy={(e) => dispatch("copy", e.detail)}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -190,10 +190,6 @@ export function group_messages(
|
||||
let currentRole: MessageRole | null = null;
|
||||
|
||||
for (const message of messages) {
|
||||
if (msg_format === "tuples") {
|
||||
currentRole = null;
|
||||
}
|
||||
|
||||
if (!(message.role === "assistant" || message.role === "user")) {
|
||||
continue;
|
||||
}
|
||||
|
@ -48,6 +48,7 @@
|
||||
export let _fetch: typeof fetch;
|
||||
export let mode: "normal" | "minimal" = "normal";
|
||||
export let show_fullscreen_button = true;
|
||||
export let display_icon_button_wrapper_top_corner = false;
|
||||
|
||||
let is_full_screen = false;
|
||||
let image_container: HTMLElement;
|
||||
@ -244,7 +245,9 @@
|
||||
class="preview"
|
||||
class:minimal={mode === "minimal"}
|
||||
>
|
||||
<IconButtonWrapper>
|
||||
<IconButtonWrapper
|
||||
display_top_corner={display_icon_button_wrapper_top_corner}
|
||||
>
|
||||
{#if show_download_button}
|
||||
<IconButton
|
||||
Icon={Download}
|
||||
|
@ -26,6 +26,7 @@
|
||||
export let show_share_button = false;
|
||||
export let i18n: I18nFormatter;
|
||||
export let show_fullscreen_button = true;
|
||||
export let display_icon_button_wrapper_top_corner = false;
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: string;
|
||||
@ -51,7 +52,9 @@
|
||||
<Empty unpadded_box={true} size="large"><ImageIcon /></Empty>
|
||||
{:else}
|
||||
<div class="image-container" bind:this={image_container}>
|
||||
<IconButtonWrapper>
|
||||
<IconButtonWrapper
|
||||
display_top_corner={display_icon_button_wrapper_top_corner}
|
||||
>
|
||||
{#if show_fullscreen_button}
|
||||
<FullscreenButton container={image_container} />
|
||||
{/if}
|
||||
|
@ -25,6 +25,7 @@
|
||||
export let loop: boolean;
|
||||
export let i18n: I18nFormatter;
|
||||
export let upload: Client["upload"];
|
||||
export let display_icon_button_wrapper_top_corner = false;
|
||||
|
||||
let old_value: FileData | null = null;
|
||||
let old_subtitle: FileData | null = null;
|
||||
@ -86,7 +87,9 @@
|
||||
/>
|
||||
{/key}
|
||||
<div data-testid="download-div">
|
||||
<IconButtonWrapper>
|
||||
<IconButtonWrapper
|
||||
display_top_corner={display_icon_button_wrapper_top_corner}
|
||||
>
|
||||
{#if show_download_button}
|
||||
<DownloadLink
|
||||
href={value.is_stream
|
||||
|
@ -41,6 +41,7 @@ class TestChatbot:
|
||||
"elem_classes": [],
|
||||
"container": True,
|
||||
"min_width": 160,
|
||||
"group_consecutive_messages": True,
|
||||
"scale": None,
|
||||
"placeholder": None,
|
||||
"height": 400,
|
||||
|
Loading…
x
Reference in New Issue
Block a user