{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import asyncio\n", "\n", "import panel as pn\n", "from panel.widgets import ChatEntry\n", "\n", "pn.extension()\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `ChatEntry` is a widget for displaying chat messages with support for various content types.\n", "\n", "This widget provides a structured view of chat messages, including features like:\n", "- Displaying user avatars, which can be text, emoji, or images.\n", "- Showing the user's name.\n", "- Displaying the message timestamp in a customizable format.\n", "- Associating reactions with messages and mapping them to icons.\n", "- Rendering various content types including text, images, audio, video, and more.\n", "\n", "See [`ChatFeed`](ChatFeed.ipynb) for a structured and straightforward way to build a list of `ChatEntry` objects.\n", "\n", "See [`ChatInterface`](ChatInterface.ipynb) for a high-level, *easy to use*, *ChatGPT like* interface.\n", "\n", "![Chat Design Specification](../../assets/ChatDesignSpecification.png)\n", "\n", "#### Parameters:\n", "\n", "For layout and styling related parameters see the [customization user guide](../../user_guide/Customization.ipynb).\n", "\n", "##### Core\n", "\n", "* **`value`** (object): The message contents. Can be a string, pane, widget, layout, etc.\n", "* **`renderers`** (List[Callable]): A callable or list of callables that accept the value and return a Panel object to render the value. If a list is provided, will attempt to use the first renderer that does not raise an exception. If None, will attempt to infer the renderer from the value.\n", "* **`user`** (str): Name of the user who sent the message.\n", "* **`avatar`** (str | BinaryIO): The avatar to use for the user. Can be a single character text, an emoji, or anything supported by `pn.pane.Image`. If not set, uses the first character of the name.\n", "* **`default_avatars`** (Dict[str, str | BinaryIO]): A default mapping of user names to their corresponding avatars to use when the user is set but the avatar is not. You can modify, but not replace the dictionary. Note, the keys are *only* alphanumeric sensitive, meaning spaces, special characters, and case sensitivity is disregarded, e.g. `\"Chat-GPT3.5\"`, `\"chatgpt 3.5\"` and `\"Chat GPT 3.5\"` all map to the same value.\n", "* **`avatar_lookup`** (Callable): A function that can lookup an `avatar` from a user name. The function signature should be `(user: str) -> Avatar`. If this is set, `default_avatars` is disregarded.\n", "* **`reactions`** (List): Reactions associated with the message.\n", "* **`reaction_icons`** (ChatReactionIcons | dict): A mapping of reactions to their reaction icons; if not provided defaults to `{\"favorite\": \"heart\"}`. Provides a visual representation of reactions.\n", "* **`timestamp`** (datetime): Timestamp of the message. Defaults to the instantiation time.\n", "* **`timestamp_format`** (str): The format in which the timestamp should be displayed.\n", "\n", "##### Display\n", "\n", "* **`show_avatar`** (bool): Whether to display the avatar of the user.\n", "* **`show_user`** (bool): Whether to display the name of the user.\n", "* **`show_timestamp`** (bool): Whether to display the timestamp of the message.\n", "* **`show_reaction_icons`** (bool): Whether to display the reaction icons.\n", "* **`name`** (str): The title or name of the chat entry widget, if any.\n", "\n", "___" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ChatEntry(value=\"Hi and welcome!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `ChatEntry` can display any Python object that Panel can display! For example Panel components, dataframes and plot figures." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "\n", "df = pd.DataFrame({\"x\": [1, 2, 3], \"y\": [4, 5, 6]})\n", "\n", "fig, axes = plt.subplots()\n", "axes.plot(df[\"x\"], df[\"y\"])\n", "plt.close(fig)\n", "\n", "pn.Column(\n", " ChatEntry(value=pn.widgets.Button(name=\"Click\")),\n", " ChatEntry(value=df),\n", " ChatEntry(value=fig),\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can specify a custom `user` name and `avatar`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ChatEntry(value=\"Want to hear some beat boxing?\", user=\"Beat Boxer\", avatar=\"🎶\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Instead of searching for emojis online, you can also set a personalized emoji avatar by using its name wrapped in `\\N{}`!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ChatEntry(value=\"Want to hear some beat boxing?\", user=\"Beat Boxer\", avatar=\"\\N{musical note}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can combine `ChatEntry` with other Panel components as you like." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pn.Column(\n", " ChatEntry(\n", " value=\"Yes. I want to hear some beat boxing\", user=\"Music Lover\", avatar=\"🎸\"\n", " ),\n", " ChatEntry(\n", " value=pn.Column(\n", " \"Here goes. Hope you like this one?\",\n", " pn.pane.Audio(\n", " \"https://upload.wikimedia.org/wikipedia/commons/d/d3/Beatboxset1_pepouni.ogg\"\n", " ),\n", " ),\n", " user=\"Beat Boxer\",\n", " avatar=\"🎶\",\n", " ),\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`ChatEntry` can be initialized without any input." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "chat_entry = pn.widgets.ChatEntry()\n", "chat_entry" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That way, the `value`, `user`, and/or `avatar` can be dynamically updated either by setting the `value` like this..." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "chat_entry.value = pn.pane.Markdown(\"## Cheers!\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Or updating multiple values at once with the `.param.update` method!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "chat_entry.param.update(user=\"Jolly Guy\", avatar=\"🎅\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you don't specify an `avatar` on construction, then an `avatar` will be looked up in the `default_avatars` dictionary." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ChatEntry.default_avatars" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can modify the `ChatEntry.default_avatars` in-place.\n", "\n", "Note, the keys are *only* alphanumeric sensitive, meaning spaces, special characters, and case sensitivity is disregarded, e.g. `\"Chat-GPT3.5\"`, `\"chatgpt 3.5\"` and `\"Chat GPT 3.5\"` all map to the same value." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ChatEntry.default_avatars[\"Wolfram\"] = \"🐺\"\n", "ChatEntry.default_avatars[\"#1 good-to-go guy\"] = \"👍\"\n", "\n", "pn.Column(\n", " ChatEntry(value=\"Mathematics!\", user=\"Wolfram\"),\n", " ChatEntry(value=\"Good to go!\", user=\"#1 Good-to-Go Guy\"),\n", " ChatEntry(value=\"What's up?\", user=\"Other Guy\"),\n", " max_width=300,\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `timestamp` can be formatted using `timestamp_format`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pn.widgets.ChatEntry(timestamp_format=\"%b %d, %Y %I:%M %p\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you'd like a plain interface with only the `value` displayed, set `show_user`, `show_avatar`, and `show_timestamp` to `False` and provide an empty `dict` to `reaction_icons`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ChatEntry(\n", " value=\"Plain and simple\",\n", " show_avatar=False,\n", " show_user=False,\n", " show_timestamp=False,\n", " reaction_icons={},\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can set the usual styling and layout parameters like `sizing_mode`, `height`, `width`, `max_height`, `max_width`, `styles` and `stylesheet`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ChatEntry(\n", " value=\"Want to hear some beat boxing?\",\n", " user=\"Beat Boxer\",\n", " avatar=\"🎶\",\n", " height=250,\n", " sizing_mode=\"stretch_width\",\n", " max_width=600,\n", " styles={\"background\": \"#CBC3E3\"},\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Some active `reactions` can be associated with the message too." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pn.widgets.ChatEntry(value=\"Love this!\", reactions=[\"favorite\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you'd like to display other `reactions_icons`, pass a pair of `reaction` key to tabler `icon` name." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "entry = pn.widgets.ChatEntry(\n", " value=\"Looks good!\",\n", " reactions=[\"like\"],\n", " reaction_icons={\"like\": \"thumb-up\", \"dislike\": \"thumb-down\"},\n", ")\n", "entry" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You may bind a callback to the selected `reactions`.\n", "\n", "Here, when the user clicks or sets `reactions`, `print_reactions` activates. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def print_reactions(reactions):\n", " print(f\"{reactions} selected.\")\n", "\n", "pn.bind(print_reactions, entry.param.reactions, watch=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The easiest and most optimal way to stream output to the `ChatEntry` is through async generators.\n", "\n", "If you're unfamiliar with this term, don't fret!\n", "\n", "It's simply prefixing your function with `async` and replacing `return` with `yield`.\n", "\n", "This example will show you how to **replace** the `ChatEntry` `value`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "async def replace_response():\n", " for value in range(0, 28):\n", " await asyncio.sleep(0.2)\n", " yield value\n", "\n", "\n", "ChatEntry(value=replace_response)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This example will show you how to **append** to the `ChatEntry` `value`. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sentence = \"\"\"\n", " The greatest glory in living lies not in never falling,\n", " but in rising every time we fall.\n", "\"\"\"\n", "\n", "\n", "async def append_response():\n", " value = \"\"\n", " for token in sentence.split():\n", " value += f\" {token}\"\n", " await asyncio.sleep(0.2)\n", " yield value\n", "\n", "\n", "ChatEntry(value=append_response, user=\"Wise guy\", avatar=\"🤓\")\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also use this to provide parts of responses as soon as they are ready." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "async def respond_when_ready():\n", " spinner = pn.indicators.LoadingSpinner(value=True, size=25, color=\"light\")\n", " layout = pn.Column(\"Love your question. Let me think.\", spinner)\n", " yield layout\n", "\n", " await asyncio.sleep(1) # Waiting for some external response...\n", " layout.insert(-1, \"I can use Matplotlib to create the Figure\")\n", " yield layout\n", "\n", " await asyncio.sleep(1) # Calculating ...\n", " layout.insert(-1, fig)\n", " layout[-1] = \"I hope that answered your question?\"\n", " yield layout\n", "\n", "\n", "ChatEntry(\n", " value=respond_when_ready,\n", " user=\"Assistant\",\n", " avatar=\"https://upload.wikimedia.org/wikipedia/commons/6/63/Yumi_UBports.png\",\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For an example on `renderers`, see [ChatInterface](ChatInterface.ipynb)." ] } ], "metadata": { "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 4 }