Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Streamlit Chat Migration Guide #5642

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,5 @@ doc/reference/*
.config/code-server/*
.conda/*
.jupyter/*
app_panel.py
app_streamlit.py
Binary file added doc/_static/images/panel_chat_entry.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/_static/images/panel_chat_input.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/_static/images/streamlit_chat_input.png
philippjfr marked this conversation as resolved.
Show resolved Hide resolved
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/_static/images/streamlit_chat_message.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
'global',
'indicators',
'widgets',
'chat',
],
'titles': {
'Vega': 'Altair & Vega',
Expand Down
145 changes: 145 additions & 0 deletions doc/how_to/streamlit_migration/chat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# Create Chat Interfaces
Copy link
Collaborator Author

@MarcSkovMadsen MarcSkovMadsen Oct 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Asset for upload

streamlit-status

panel-status


Both Streamlit and Panel provides special components to help you build conversational apps.

| Streamlit | Panel | Description |
| -------------------- | ------------------- | -------------------------------------- |
| `st.chat_message` | [`pn.chat.ChatEntry`](../../../examples/reference/chat/ChatEntry.ipynb) | Output a single chat message |
| `st_chat_input` | | Input a chat message |
| `st.status` | | Display output of long-running tasks in a container |
| | [`pn.chat.ChatFeed`](../../../examples/reference/chat/ChatFeed.ipynb) | Output multiple of chat messages |
| | [`pn.chat.ChatInterface`](../../../examples/reference/chat/ChatInterface.ipynb) | High-level, easy to use chat interface |
| `langchain.callbacks.StreamlitCallbackHandler` | [`pn.chat.PanelCallbackHandler`](../../../examples/reference/chat/ChatInterface.ipynb) | Display the thoughts and actions of a LangChain agent |

The starting point for most Panel users would be the *high-level*, easy to use `ChatInterface` and `PanelCallbackHandler` components. Not the lower level `ChatEntry` and `ChatFeed` components.

You can find specialized chat components and examples at [panel-chat-examples/](https://holoviz-topics.github.io/panel-chat-examples/).

## Chat Message

Lets see how-to migrate an app using `st.chat_message`

### Streamlit Chat Message Example

```python
import streamlit as st

with st.chat_message("user"):
st.image("https://streamlit.io/images/brand/streamlit-logo-primary-colormark-darktext.png")
st.write("# A faster way to build and share data apps")
```

![Streamlit chat_entry](../../_static/images/streamlit_chat_message.png)

### Panel Chat Message Example

```python
import panel as pn

pn.extension(design="material")

message = pn.Column(
"https://panel.holoviz.org/_images/logo_horizontal_light_theme.png",
"# The powerful data exploration & web app framework for Python"
)
pn.chat.ChatEntry(value=message, user="user").servable()
```

![Panel ChatEntry](../../_static/images/panel_chat_entry.png)

## Chat Input

### Streamlit Chat Input

```python
import streamlit as st

prompt = st.chat_input("Say something")
if prompt:
st.write(f"User has sent the following prompt: {prompt}")
```

![Streamlit chat_input](../../_static/images/streamlit_chat_input.png)

### Panel Chat Input

Panel does not provide a dedicated *chat input* component because it is built into Panels high-level `ChatInterface`.

Below we will show you how to build a custom `ChatInput` widget.

```python
Copy link
Collaborator Author

@MarcSkovMadsen MarcSkovMadsen Oct 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a component like this to Panels chat components @ahuang11 and @philippjfr ?

Copy link
Collaborator Author

@MarcSkovMadsen MarcSkovMadsen Oct 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This custom Viewer component also shows some of the challenges it has.

  • The component is not and cannot be used as a widget. I.e. you need to pn.bind to and pn.depends on chat_input.param.value not chat_input. There is no .from_param method etc.
  • It takes a lot of boiler plate code to separate the viewer params from the _layout params. And getting sizing_mode, width, ... properly linked up to the _layout is almost impossible.

I.e. you can make custom components using Viewer. But I can't see how you can create something that Panel would be willing to include as a proper widget without really a lot of code.

import param

import panel as pn

pn.extension(design="material")


class ChatInput(pn.viewable.Viewer):
value = param.String()

disabled = param.Boolean()
max_length = param.Integer(default=5000)
placeholder = param.String("Send a message")

def __init__(self, **params):
layout_params = {
key: value
for key, value in params.items()
if not key in ["value", "placeholder", "disabled", "max_length"]
}
params = {
key: value for key, value in params.items() if key not in layout_params
}

super().__init__(**params)

self._text_input = pn.widgets.TextInput(
align="center",
disabled=self.param.disabled,
max_length=self.param.max_length,
name="Message",
placeholder=self.param.placeholder,
sizing_mode="stretch_width",
)
self._submit_button = pn.widgets.Button(
align="center",
disabled=self.param.disabled,
icon="send",
margin=(18, 5, 10, 0),
name="",
sizing_mode="fixed",
)
pn.bind(
self._update_value,
value=self._text_input,
event=self._submit_button,
watch=True,
)

self._layout = pn.Row(
self._text_input, self._submit_button, align="center", **layout_params
)

def __panel__(self):
return self._layout

def _update_value(self, value, event):
self.value = value or self.value
self._text_input.value = ""


chat_input = ChatInput(placeholder="Say something")


@pn.depends(chat_input.param.value)
def message(prompt):
if not prompt:
return ""
return f"User has sent the following prompt: {prompt}"


pn.Column(message, chat_input, margin=50).servable()
```

![Panel ChatInput](../../_static/images/panel_chat_input.png)
7 changes: 7 additions & 0 deletions doc/how_to/streamlit_migration/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ How to improve the performance with caching
How to store state for a session
:::

:::{grid-item-card} {octicon}`dependabottack;2.5em;sd-mr-1 sd-animate-grow50` Chat Interfaces
:link: chat
:link-type: doc

How to create create chat interfaces
:::

:::{grid-item-card} {octicon}`stack;2.5em;sd-mr-1 sd-animate-grow50` Multi Page Apps
:link: multipage_apps
:link-type: doc
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"import asyncio\n",
"\n",
"import panel as pn\n",
"from panel.widgets import ChatEntry\n",
"from panel.chat import ChatEntry\n",
"\n",
"pn.extension()\n"
]
Expand Down Expand Up @@ -175,7 +175,7 @@
"metadata": {},
"outputs": [],
"source": [
"chat_entry = pn.widgets.ChatEntry()\n",
"chat_entry = pn.chat.ChatEntry()\n",
"chat_entry"
]
},
Expand Down Expand Up @@ -266,7 +266,7 @@
"metadata": {},
"outputs": [],
"source": [
"pn.widgets.ChatEntry(timestamp_format=\"%b %d, %Y %I:%M %p\")"
"pn.chat.ChatEntry(timestamp_format=\"%b %d, %Y %I:%M %p\")"
]
},
{
Expand Down Expand Up @@ -328,7 +328,7 @@
"metadata": {},
"outputs": [],
"source": [
"pn.widgets.ChatEntry(value=\"Love this!\", reactions=[\"favorite\"])"
"pn.chat.ChatEntry(value=\"Love this!\", reactions=[\"favorite\"])"
]
},
{
Expand All @@ -344,7 +344,7 @@
"metadata": {},
"outputs": [],
"source": [
"entry = pn.widgets.ChatEntry(\n",
"entry = pn.chat.ChatEntry(\n",
" value=\"Looks good!\",\n",
" reactions=[\"like\"],\n",
" reaction_icons={\"like\": \"thumb-up\", \"dislike\": \"thumb-down\"},\n",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
"metadata": {},
"outputs": [],
"source": [
"chat_feed = pn.widgets.ChatFeed()\n",
"chat_feed = pn.chat.ChatFeed()\n",
"chat_feed"
]
},
Expand Down Expand Up @@ -147,9 +147,9 @@
"metadata": {},
"outputs": [],
"source": [
"pn.widgets.ChatFeed(value=[\n",
" pn.widgets.ChatEntry(value=\"I'm an emoji!\", avatar=\"🤖\"),\n",
" pn.widgets.ChatEntry(value=\"I'm an image!\", avatar=\"https://upload.wikimedia.org/wikipedia/commons/6/63/Yumi_UBports.png\"),\n",
"pn.chat.ChatFeed(value=[\n",
" pn.chat.ChatEntry(value=\"I'm an emoji!\", avatar=\"🤖\"),\n",
" pn.chat.ChatEntry(value=\"I'm an image!\", avatar=\"https://upload.wikimedia.org/wikipedia/commons/6/63/Yumi_UBports.png\"),\n",
"])"
]
},
Expand Down Expand Up @@ -187,7 +187,7 @@
"def echo_message(contents, user, instance):\n",
" return f\"Echoing... {contents}\"\n",
"\n",
"chat_feed = pn.widgets.ChatFeed(callback=echo_message)\n",
"chat_feed = pn.chat.ChatFeed(callback=echo_message)\n",
"chat_feed"
]
},
Expand All @@ -213,7 +213,7 @@
"metadata": {},
"outputs": [],
"source": [
"chat_feed = pn.widgets.ChatFeed(callback=echo_message, callback_user=\"Echo Bot\")\n",
"chat_feed = pn.chat.ChatFeed(callback=echo_message, callback_user=\"Echo Bot\")\n",
"chat_feed"
]
},
Expand Down Expand Up @@ -242,7 +242,7 @@
"def parrot_message(contents, user, instance):\n",
" return {\"value\": f\"No, {contents.lower()}\", \"user\": \"Parrot\", \"avatar\": \"🦜\"}\n",
"\n",
"chat_feed = pn.widgets.ChatFeed(callback=parrot_message, callback_user=\"Echo Bot\")\n",
"chat_feed = pn.chat.ChatFeed(callback=parrot_message, callback_user=\"Echo Bot\")\n",
"chat_feed"
]
},
Expand Down Expand Up @@ -289,7 +289,7 @@
"def bad_callback(contents, user, instance):\n",
" return 1 / 0\n",
"\n",
"chat_feed = pn.widgets.ChatFeed(callback=bad_callback, callback_exception=\"summary\")\n",
"chat_feed = pn.chat.ChatFeed(callback=bad_callback, callback_exception=\"summary\")\n",
"chat_feed"
]
},
Expand Down Expand Up @@ -325,7 +325,7 @@
" await asyncio.sleep(2.8)\n",
" return {\"value\": f\"No, {contents.lower()}\", \"user\": \"Parrot\", \"avatar\": \"🦜\"}\n",
"\n",
"chat_feed = pn.widgets.ChatFeed(callback=parrot_message, callback_user=\"Echo Bot\")\n",
"chat_feed = pn.chat.ChatFeed(callback=parrot_message, callback_user=\"Echo Bot\")\n",
"chat_feed"
]
},
Expand Down Expand Up @@ -361,7 +361,7 @@
" message += character\n",
" yield message\n",
"\n",
"chat_feed = pn.widgets.ChatFeed(callback=stream_message)\n",
"chat_feed = pn.chat.ChatFeed(callback=stream_message)\n",
"chat_feed"
]
},
Expand Down Expand Up @@ -392,7 +392,7 @@
" await asyncio.sleep(0.1)\n",
" yield character\n",
"\n",
"chat_feed = pn.widgets.ChatFeed(callback=replace_message)\n",
"chat_feed = pn.chat.ChatFeed(callback=replace_message)\n",
"chat_feed"
]
},
Expand Down Expand Up @@ -428,7 +428,7 @@
" message += chunk[\"choices\"][0][\"delta\"].get(\"content\", \"\")\n",
" yield message\n",
"\n",
"chat_feed = pn.widgets.ChatFeed(callback=openai_callback)\n",
"chat_feed = pn.chat.ChatFeed(callback=openai_callback)\n",
"chat_feed.send(\"Have you heard of HoloViz Panel?\")\n",
"```\n",
"\n",
Expand Down Expand Up @@ -466,7 +466,7 @@
" }\n",
"\n",
"\n",
"chat_feed = pn.widgets.ChatFeed(callback=chain_message)\n",
"chat_feed = pn.chat.ChatFeed(callback=chain_message)\n",
"chat_feed"
]
},
Expand Down Expand Up @@ -514,7 +514,7 @@
" instance.respond()\n",
"\n",
"\n",
"chat_feed = pn.widgets.ChatFeed(callback=openai_self_chat, sizing_mode=\"stretch_width\", height=1000).servable()\n",
"chat_feed = pn.chat.ChatFeed(callback=openai_self_chat, sizing_mode=\"stretch_width\", height=1000).servable()\n",
"chat_feed.send(\"What is HoloViz Panel?\")\n",
"```\n",
"\n",
Expand All @@ -534,7 +534,7 @@
"metadata": {},
"outputs": [],
"source": [
"chat_feed = pn.widgets.ChatFeed()\n",
"chat_feed = pn.chat.ChatFeed()\n",
"\n",
"# creates a new entry\n",
"entry = chat_feed.stream(\"Hello\", user=\"Aspiring User\", avatar=\"🤓\")\n",
Expand Down Expand Up @@ -564,7 +564,7 @@
"metadata": {},
"outputs": [],
"source": [
"chat_feed = pn.widgets.ChatFeed()\n",
"chat_feed = pn.chat.ChatFeed()\n",
"chat_feed"
]
},
Expand Down Expand Up @@ -596,7 +596,7 @@
"entry_params = dict(\n",
" default_avatars={\"System\": \"S\", \"User\": \"👤\"}, reaction_icons={\"like\": \"thumb-up\"}\n",
")\n",
"chat_feed = pn.widgets.ChatFeed(entry_params=entry_params)\n",
"chat_feed = pn.chat.ChatFeed(entry_params=entry_params)\n",
"chat_feed.send(user=\"System\", value=\"This is the System speaking.\")\n",
"chat_feed.send(user=\"User\", value=\"This is the User speaking.\")\n",
"chat_feed"
Expand All @@ -619,7 +619,7 @@
"source": [
"import asyncio\n",
"import panel as pn\n",
"from panel.widgets.chat import ChatEntry, ChatFeed\n",
"from panel.chat import ChatEntry, ChatFeed\n",
"\n",
"pn.extension()\n",
"\n",
Expand Down
Loading
Loading