Add Markdown support in chatbots (#2731)

* started working on this

* demo

* added markdown support

* notebook

* changelog

* tests

* chatbot

* formatting
This commit is contained in:
Abubakar Abid 2022-11-29 14:26:21 -06:00 committed by GitHub
parent d79039beb1
commit 5c8e7887dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 97 additions and 11 deletions

View File

@ -2,6 +2,28 @@
## New Features:
### The `Chatbot` component now supports a subset of Markdown (including bold, italics, code, images)
You can now pass in some Markdown to the Chatbot component and it will show up,
meaning that you can pass in images as well! by [@abidlabs](https://github.com/abidlabs) in [PR 2731](https://github.com/gradio-app/gradio/pull/2731)
Here's a simple example that references a local image `lion.jpg` that is in the same
folder as the Python script:
```py
import gradio as gr
with gr.Blocks() as demo:
gr.Chatbot([("hi", "hello **abubakar**"), ("![](/file=lion.jpg)", "cool pic")])
demo.launch()
```
![Alt text](https://user-images.githubusercontent.com/1778297/204357455-5c1a4002-eee7-479d-9a1e-ba2c12522723.png)
To see a more realistic example, see the new demo `/demo/chatbot_multimodal/run.py`.
### Latex support
Added mathtext (a subset of latex) support to gr.Markdown. Added by [@kashif](https://github.com/kashif) and [@aliabid94](https://github.com/aliabid94) in [PR 2696](https://github.com/gradio-app/gradio/pull/2696).

View File

@ -0,0 +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", "\n", "def add_text(state, text):\n", " state = state + [(text, text + \"?\")]\n", " return state, state\n", "\n", "def add_image(state, image):\n", " state = state + [(f\"![](/file={image.name})\", \"Cool pic!\")]\n", " return state, state\n", "\n", "\n", "with gr.Blocks(css=\"#chatbot .overflow-y-auto{height:500px}\") as demo:\n", " chatbot = gr.Chatbot(elem_id=\"chatbot\")\n", " state = gr.State([])\n", " \n", " with gr.Row():\n", " with gr.Column(scale=0.85):\n", " txt = gr.Textbox(show_label=False, placeholder=\"Enter text and press enter, or upload an image\").style(container=False)\n", " with gr.Column(scale=0.15, min_width=0):\n", " btn = gr.UploadButton(\"\ud83d\uddbc\ufe0f\", file_types=[\"image\"])\n", " \n", " txt.submit(add_text, [state, txt], [state, chatbot])\n", " txt.submit(lambda :\"\", None, txt)\n", " btn.upload(add_image, [state, btn], [state, chatbot])\n", " \n", "if __name__ == \"__main__\":\n", " demo.launch()"]}], "metadata": {}, "nbformat": 4, "nbformat_minor": 5}

View File

@ -0,0 +1,27 @@
import gradio as gr
def add_text(state, text):
state = state + [(text, text + "?")]
return state, state
def add_image(state, image):
state = state + [(f"![](/file={image.name})", "Cool pic!")]
return state, state
with gr.Blocks(css="#chatbot .overflow-y-auto{height:500px}") as demo:
chatbot = gr.Chatbot(elem_id="chatbot")
state = gr.State([])
with gr.Row():
with gr.Column(scale=0.85):
txt = gr.Textbox(show_label=False, placeholder="Enter text and press enter, or upload an image").style(container=False)
with gr.Column(scale=0.15, min_width=0):
btn = gr.UploadButton("🖼️", file_types=["image"])
txt.submit(add_text, [state, txt], [state, chatbot])
txt.submit(lambda :"", None, txt)
btn.upload(add_image, [state, btn], [state, chatbot])
if __name__ == "__main__":
demo.launch()

View File

@ -2862,11 +2862,13 @@ class UploadButton(Clickable, Uploadable, IOComponent, SimpleSerializable):
)
if self.type == "file":
if is_file:
file = processing_utils.create_tmp_copy_of_file(file_name)
file = processing_utils.create_tmp_copy_of_file(
file_name, dir=self.temp_dir
)
file.orig_name = file_name
else:
file = processing_utils.decode_base64_to_file(
data, file_path=file_name
data, file_path=file_name, dir=self.temp_dir
)
file.orig_name = file_name
return file
@ -3615,9 +3617,9 @@ class Carousel(IOComponent, Changeable, SimpleSerializable):
@document("change", "style")
class Chatbot(Changeable, IOComponent, JSONSerializable):
"""
Displays a chatbot output showing both user submitted messages and responses
Displays a chatbot output showing both user submitted messages and responses. Supports a subset of Markdown including bold, italics, code, and images.
Preprocessing: this component does *not* accept input.
Postprocessing: expects a {List[Tuple[str, str]]}, a list of tuples with user inputs and responses.
Postprocessing: expects a {List[Tuple[str, str]]}, a list of tuples with user inputs and responses as strings of HTML.
Demos: chatbot_demo
"""
@ -3646,6 +3648,7 @@ class Chatbot(Changeable, IOComponent, JSONSerializable):
"The 'color_map' parameter has been moved from the constructor to `Chatbot.style()` ",
)
self.color_map = color_map
self.md = MarkdownIt()
IOComponent.__init__(
self,
@ -3685,11 +3688,15 @@ class Chatbot(Changeable, IOComponent, JSONSerializable):
def postprocess(self, y: List[Tuple[str, str]]) -> List[Tuple[str, str]]:
"""
Parameters:
y: List of tuples representing the message and response
y: List of tuples representing the message and response pairs. Each message and response should be a string, which may be in Markdown format.
Returns:
List of tuples representing the message and response
List of tuples representing the message and response. Each message and response will be a string of HTML.
"""
return [] if y is None else y
if y is None:
return []
for i, (message, response) in enumerate(y):
y[i] = (self.md.render(message), self.md.render(response))
return y
def style(self, *, color_map: Optional[List[str, str]] = None, **kwargs):
"""

View File

@ -1479,6 +1479,29 @@ class TestHighlightedText:
]
class TestChatbot:
def test_component_functions(self):
"""
Postprocess, get_config
"""
chatbot = gr.Chatbot()
assert chatbot.postprocess([("You are **cool**", "so are *you*")]) == [
("<p>You are <strong>cool</strong></p>\n", "<p>so are <em>you</em></p>\n")
]
assert chatbot.get_config() == {
"value": [],
"color_map": None,
"label": None,
"show_label": True,
"interactive": None,
"name": "chatbot",
"visible": True,
"elem_id": None,
"style": {},
"root_url": None,
}
class TestJSON:
def test_component_functions(self):
"""

View File

@ -46,18 +46,24 @@
{#each value as message}
<div
data-testid="user"
class="px-3 py-2 rounded-[22px] rounded-br-none text-white text-sm"
class="px-3 py-2 rounded-[22px] rounded-br-none text-white text-sm chat-message"
style={"background-color:" + _colors[0]}
>
{message[0]}
{@html message[0]}
</div>
<div
data-testid="bot"
class="px-3 py-2 rounded-[22px] rounded-bl-none place-self-start text-white text-sm"
class="px-3 py-2 rounded-[22px] rounded-bl-none place-self-start text-white text-sm chat-message"
style={"background-color:" + _colors[1]}
>
{message[1]}
{@html message[1]}
</div>
{/each}
</div>
</div>
<style>
.chat-message :global(img) {
border-radius: 13px;
}
</style>