Skip to content

Commit

Permalink
Chatbot Avatar Images (#5258)
Browse files Browse the repository at this point in the history
* avatar images

* images

* add changeset

* fixes

* format

* type fix

* test fix

* format

* fixes

* tuple

* change to tuple

* test fix

* remove index file

* fixes

* save

* test fix

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: Abubakar Abid <abubakar@huggingface.co>
  • Loading branch information
3 people committed Aug 21, 2023
1 parent 390624d commit 92282ce
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 71 deletions.
6 changes: 6 additions & 0 deletions .changeset/red-trains-wonder.md
@@ -0,0 +1,6 @@
---
"@gradio/chatbot": minor
"gradio": minor
---

feat:Chatbot Avatar Images
Binary file added demo/chatbot_multimodal/avatar.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion demo/chatbot_multimodal/run.ipynb
@@ -1 +1 @@
{"cells": [{"cell_type": "markdown", "id": 302934307671667531413257853548643485645, "metadata": {}, "source": ["# Gradio Demo: chatbot_multimodal"]}, {"cell_type": "code", "execution_count": null, "id": 272996653310673477252411125948039410165, "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": 288918539441861185822528903084949547379, "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import random\n", "import time\n", "\n", "# Chatbot demo with multimodal input (text, markdown, LaTeX, code blocks, image, audio, & video). Plus shows support for streaming text.\n", "\n", "def add_text(history, text):\n", " history = history + [(text, None)]\n", " return history, gr.update(value=\"\", interactive=False)\n", "\n", "\n", "def add_file(history, file):\n", " history = history + [((file.name,), None)]\n", " return history\n", "\n", "\n", "def bot(history):\n", " response = \"**That's cool!**\"\n", " history[-1][1] = \"\"\n", " for character in response:\n", " history[-1][1] += character\n", " time.sleep(0.05)\n", " yield history\n", "\n", "\n", "with gr.Blocks() as demo:\n", " chatbot = gr.Chatbot([], elem_id=\"chatbot\").style(height=750)\n", "\n", " with gr.Row():\n", " with gr.Column(scale=0.85):\n", " txt = gr.Textbox(\n", " show_label=False,\n", " placeholder=\"Enter text and press enter, or upload an image\",\n", " ).style(container=False)\n", " with gr.Column(scale=0.15, min_width=0):\n", " btn = gr.UploadButton(\"\ud83d\udcc1\", file_types=[\"image\", \"video\", \"audio\"])\n", "\n", " txt_msg = txt.submit(add_text, [chatbot, txt], [chatbot, txt], queue=False).then(\n", " bot, chatbot, chatbot\n", " )\n", " txt_msg.then(lambda: gr.update(interactive=True), None, [txt], queue=False)\n", " file_msg = btn.upload(add_file, [chatbot, btn], [chatbot], queue=False).then(\n", " bot, chatbot, chatbot\n", " )\n", "\n", "demo.queue()\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
{"cells": [{"cell_type": "markdown", "id": 302934307671667531413257853548643485645, "metadata": {}, "source": ["# Gradio Demo: chatbot_multimodal"]}, {"cell_type": "code", "execution_count": null, "id": 272996653310673477252411125948039410165, "metadata": {}, "outputs": [], "source": ["!pip install -q gradio "]}, {"cell_type": "code", "execution_count": null, "id": 288918539441861185822528903084949547379, "metadata": {}, "outputs": [], "source": ["# Downloading files from the demo repo\n", "import os\n", "!wget -q https://github.com/gradio-app/gradio/raw/main/demo/chatbot_multimodal/avatar.png"]}, {"cell_type": "code", "execution_count": null, "id": 44380577570523278879349135829904343037, "metadata": {}, "outputs": [], "source": ["import gradio as gr\n", "import os\n", "import time\n", "\n", "# Chatbot demo with multimodal input (text, markdown, LaTeX, code blocks, image, audio, & video). Plus shows support for streaming text.\n", "\n", "def add_text(history, text):\n", " history = history + [(text, None)]\n", " return history, gr.update(value=\"\", interactive=False)\n", "\n", "\n", "def add_file(history, file):\n", " history = history + [((file.name,), None)]\n", " return history\n", "\n", "\n", "def bot(history):\n", " response = \"**That's cool!**\"\n", " history[-1][1] = \"\"\n", " for character in response:\n", " history[-1][1] += character\n", " time.sleep(0.05)\n", " yield history\n", "\n", "\n", "with gr.Blocks() as demo:\n", " chatbot = gr.Chatbot([], elem_id=\"chatbot\", height=750, avatar_images=(None, (os.path.join(os.path.abspath(''), \"avatar.png\"))))\n", "\n", " with gr.Row():\n", " with gr.Column(scale=0.85):\n", " txt = gr.Textbox(\n", " show_label=False,\n", " placeholder=\"Enter text and press enter, or upload an image\",\n", " container=False)\n", " with gr.Column(scale=0.15, min_width=0):\n", " btn = gr.UploadButton(\"\ud83d\udcc1\", file_types=[\"image\", \"video\", \"audio\"])\n", "\n", " txt_msg = txt.submit(add_text, [chatbot, txt], [chatbot, txt], queue=False).then(\n", " bot, chatbot, chatbot\n", " )\n", " txt_msg.then(lambda: gr.update(interactive=True), None, [txt], queue=False)\n", " file_msg = btn.upload(add_file, [chatbot, btn], [chatbot], queue=False).then(\n", " bot, chatbot, chatbot\n", " )\n", "\n", "demo.queue()\n", "if __name__ == \"__main__\":\n", " demo.launch()\n"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}
4 changes: 2 additions & 2 deletions demo/chatbot_multimodal/run.py
@@ -1,5 +1,5 @@
import gradio as gr
import random
import os
import time

# Chatbot demo with multimodal input (text, markdown, LaTeX, code blocks, image, audio, & video). Plus shows support for streaming text.
Expand All @@ -24,7 +24,7 @@ def bot(history):


with gr.Blocks() as demo:
chatbot = gr.Chatbot([], elem_id="chatbot", height=750)
chatbot = gr.Chatbot([], elem_id="chatbot", height=750, avatar_images=(None, (os.path.join(os.path.dirname(__file__), "avatar.png"))))

with gr.Row():
with gr.Column(scale=0.85):
Expand Down
7 changes: 6 additions & 1 deletion gradio/components/chatbot.py
Expand Up @@ -54,6 +54,7 @@ def __init__(
rtl: bool = False,
show_share_button: bool | None = None,
show_copy_button: bool = False,
avatar_images: tuple[str | Path | None, str | Path | None] | None = None,
**kwargs,
):
"""
Expand All @@ -74,6 +75,7 @@ def __init__(
rtl: If True, sets the direction of the rendered text to right-to-left. Default is False, which renders text left-to-right.
show_share_button: If True, will show a share icon in the corner of the component that allows user to share outputs to Hugging Face Spaces Discussions. If False, icon does not appear. If set to None (default behavior), then the icon appears if this Gradio app is launched on Spaces, but not otherwise.
show_copy_button: If True, will show a copy button for each chatbot message.
avatar_images: Tuple of two avatar image paths or URLs for user and bot (in that order). Pass None for either the user or bot image to skip. Must be within the working directory of the Gradio app or an external URL.
"""
if color_map is not None:
warn_deprecation("The 'color_map' parameter has been deprecated.")
Expand All @@ -88,13 +90,13 @@ def __init__(
if latex_delimiters is None:
latex_delimiters = [{"left": "$$", "right": "$$", "display": True}]
self.latex_delimiters = latex_delimiters
self.avatar_images = avatar_images or (None, None)
self.show_share_button = (
(utils.get_space() is not None)
if show_share_button is None
else show_share_button
)
self.show_copy_button = show_copy_button

IOComponent.__init__(
self,
label=label,
Expand All @@ -119,6 +121,7 @@ def get_config(self):
"show_share_button": self.show_share_button,
"rtl": self.rtl,
"show_copy_button": self.show_copy_button,
"avatar_images": self.avatar_images,
**IOComponent.get_config(self),
}

Expand All @@ -138,6 +141,7 @@ def update(
latex_delimiters: list[dict[str, str | bool]] | None = None,
show_share_button: bool | None = None,
show_copy_button: bool | None = None,
avatar_images: tuple[str | Path | None] | None = None,
):
updated_config = {
"label": label,
Expand All @@ -152,6 +156,7 @@ def update(
"rtl": rtl,
"latex_delimiters": latex_delimiters,
"show_copy_button": show_copy_button,
"avatar_images": avatar_images,
"__type__": "update",
}
return updated_config
Expand Down
170 changes: 103 additions & 67 deletions js/chatbot/static/ChatBot.svelte
Expand Up @@ -33,7 +33,9 @@
export let theme_mode: ThemeMode;
export let rtl = false;
export let show_copy_button = false;
export let avatar_images: (string | null)[] | null = null;
export let root: string;
export let root_url: null | string;
$: if (theme_mode == "dark") {
code_highlight_css.dark();
} else {
Expand All @@ -42,6 +44,9 @@
let div: HTMLDivElement;
let autoscroll: boolean;
let images_dir: string = root_url
? "proxy=" + root_url + "file="
: root + "/file=";
const dispatch = createEventDispatcher<{
change: undefined;
Expand Down Expand Up @@ -104,75 +109,84 @@
{#if value !== null}
{#each value as message_pair, i}
{#each message_pair as message, j}
<!-- TODO: fix-->
<!-- svelte-ignore a11y-no-static-element-interactions-->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
data-testid={j == 0 ? "user" : "bot"}
class:latest={i === value.length - 1}
class="message {j == 0 ? 'user' : 'bot'}"
class:hide={message === null}
class:selectable
on:click={() => handle_select(i, j, message)}
dir={rtl ? "rtl" : "ltr"}
>
{#if typeof message === "string"}
<Markdown {message} {latex_delimiters} on:load={scroll} />
{#if feedback && j == 1}
<div class="feedback">
{#each feedback as f}
<button>{f}</button>
{/each}
</div>
{/if}

{#if show_copy_button && message}
<div class="icon-button">
<Copy value={message} />
</div>
{/if}
{:else if message !== null && message.mime_type?.includes("audio")}
<audio
data-testid="chatbot-audio"
controls
preload="metadata"
src={message.data}
title={message.alt_text}
on:play
on:pause
on:ended
/>
{:else if message !== null && message.mime_type?.includes("video")}
<video
data-testid="chatbot-video"
controls
src={message.data}
title={message.alt_text}
preload="auto"
on:play
on:pause
on:ended
>
<track kind="captions" />
</video>
{:else if message !== null && message.mime_type?.includes("image")}
<div class="message-row">
{#if avatar_images && avatar_images[j]}
<img
data-testid="chatbot-image"
src={message.data}
alt={message.alt_text}
class="avatar-image-{j == 0 ? 'user' : 'bot'}"
src={images_dir + avatar_images[j]}
alt="avatar"
/>
{:else if message !== null && message.data !== null}
<a
data-testid="chatbot-file"
href={message.data}
target="_blank"
download={window.__is_colab__
? null
: message.orig_name || message.name}
>
{message.orig_name || message.name}
</a>
{/if}
<!-- TODO: fix-->
<!-- svelte-ignore a11y-no-static-element-interactions-->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
data-testid={j == 0 ? "user" : "bot"}
class:latest={i === value.length - 1}
class="message {j == 0 ? 'user' : 'bot'}"
class:hide={message === null}
class:selectable
on:click={() => handle_select(i, j, message)}
dir={rtl ? "rtl" : "ltr"}
>
{#if typeof message === "string"}
<Markdown {message} {latex_delimiters} on:load={scroll} />
{#if feedback && j == 1}
<div class="feedback">
{#each feedback as f}
<button>{f}</button>
{/each}
</div>
{/if}

{#if show_copy_button && message}
<div class="icon-button">
<Copy value={message} />
</div>
{/if}
{:else if message !== null && message.mime_type?.includes("audio")}
<audio
data-testid="chatbot-audio"
controls
preload="metadata"
src={message.data}
title={message.alt_text}
on:play
on:pause
on:ended
/>
{:else if message !== null && message.mime_type?.includes("video")}
<video
data-testid="chatbot-video"
controls
src={message.data}
title={message.alt_text}
preload="auto"
on:play
on:pause
on:ended
>
<track kind="captions" />
</video>
{:else if message !== null && message.mime_type?.includes("image")}
<img
data-testid="chatbot-image"
src={message.data}
alt={message.alt_text}
/>
{:else if message !== null && message.data !== null}
<a
data-testid="chatbot-file"
href={message.data}
target="_blank"
download={window.__is_colab__
? null
: message.orig_name || message.name}
>
{message.orig_name || message.name}
</a>
{/if}
</div>
</div>
{/each}
{/each}
Expand Down Expand Up @@ -256,6 +270,28 @@
border-color: var(--border-color-accent-subdued);
background-color: var(--color-accent-soft);
}
.message-row {
display: flex;
flex-direction: row;
}
.avatar-image-user,
.avatar-image-bot {
align-self: flex-end;
position: relative;
justify-content: center;
max-width: 35px;
max-height: 35px;
border-radius: 50%;
bottom: 0px;
}
.avatar-image-user {
order: 2;
margin-left: 10px;
}
.avatar-image-bot {
margin-right: 10px;
}
.feedback {
display: flex;
position: absolute;
Expand Down
5 changes: 5 additions & 0 deletions js/chatbot/static/StaticChatbot.svelte
Expand Up @@ -28,6 +28,7 @@
right: string;
display: boolean;
}[];
export let avatar_images: (string | null)[] | null = null;
let _value: [string | FileData | null, string | FileData | null][];
Expand All @@ -44,6 +45,7 @@
: normalise_file(bot_msg, root, root_url)
])
: [];
export let loading_status: LoadingStatus | undefined = undefined;
export let height = 400;
</script>
Expand Down Expand Up @@ -84,6 +86,9 @@
pending_message={loading_status?.status === "pending"}
{rtl}
{show_copy_button}
{avatar_images}
{root_url}
{root}
on:change
on:select
on:share
Expand Down
1 change: 1 addition & 0 deletions test/test_components.py
Expand Up @@ -2015,6 +2015,7 @@ def test_component_functions(self):
"latex_delimiters": [{"display": True, "left": "$$", "right": "$$"}],
"rtl": False,
"show_copy_button": False,
"avatar_images": (None, None),
}


Expand Down

0 comments on commit 92282ce

Please sign in to comment.