{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import panel as pn\n", "from panel.chat import ChatInterface\n", "\n", "pn.extension(\"perspective\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `ChatInterface` is a high-level layout, providing a user-friendly front-end interface for inputting different kinds of messages: text, images, PDFs, etc.\n", "\n", "This layout provides front-end methods to:\n", "\n", "- Input (append) messages to the chat log.\n", "- Re-run (resend) the most recent `user` input [`ChatMessage`](ChatMessage.ipynb).\n", "- Remove messages until the previous `user` input [`ChatMessage`](ChatMessage.ipynb).\n", "- Clear the chat log, erasing all [`ChatMessage`](ChatMessage.ipynb) objects.\n", "\n", "**Since `ChatInterface` inherits from [`ChatFeed`](ChatFeed.ipynb), it features all the capabilities of [`ChatFeed`](ChatFeed.ipynb); please see [ChatFeed.ipynb](ChatFeed.ipynb) for its backend capabilities.**\n", "\n", "Check out the [panel-chat-examples](https://holoviz-topics.github.io/panel-chat-examples/) docs to see applicable examples related to [LangChain](https://python.langchain.com/docs/get_started/introduction), [OpenAI](https://openai.com/blog/chatgpt), [Mistral](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&ved=2ahUKEwjZtP35yvSBAxU00wIHHerUDZAQFnoECBEQAQ&url=https%3A%2F%2Fdocs.mistral.ai%2F&usg=AOvVaw2qpx09O_zOzSksgjBKiJY_&opi=89978449), [Llama](https://ai.meta.com/llama/), etc. If you have an example to demo, we'd love to add it to the panel-chat-examples gallery!\n", "\n", "\"Chat\n", "\n", "#### Parameters:\n", "\n", "##### Core\n", "\n", "* **`widgets`** (`Widget | List[Widget]`): Widgets to use for the input. If not provided, defaults to `[TextInput]`.\n", "* **`user`** (`str`): Name of the ChatInterface user.\n", "* **`avatar`** (`str | bytes | BytesIO | pn.pane.Image`): 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", "* **`reset_on_send`** (`bool`): Whether to reset the widget's value after sending a message; has no effect for `TextInput`.\n", "* **`auto_send_types`** (`tuple`): The widget types to automatically send when the user presses enter or clicks away from the widget. If not provided, defaults to `[TextInput]`.\n", "* **`button_properties`** (`Dict[Dict[str, Any]]`): Allows addition of functionality or customization of buttons by supplying a mapping from the button name to a dictionary containing the `icon`, `callback`, `post_callback`, and/or `js_on_click` keys. \n", " * If the button names correspond to default buttons\n", "(send, rerun, undo, clear), the default icon can be\n", "updated and if a `callback` key value pair is provided,\n", "the specified callback functionality runs before the existing one.\n", " * For button names that don't match existing ones,\n", "new buttons are created and must include a\n", "`callback`, `post_callback`, and/or `js_on_click` key.\n", " * The provided callbacks should have a signature that accepts\n", "two positional arguments: instance (the ChatInterface instance)\n", "and event (the button click event).\n", " * The `js_on_click` key should be a str or dict. If str,\n", "provide the JavaScript code; else if dict, it must have a\n", "`code` key, containing the JavaScript code\n", "to execute when the button is clicked, and optionally an `args` key,\n", "containing dictionary of arguments to pass to the JavaScript\n", "code.\n", "\n", "##### Styling\n", "\n", "* **`show_send`** (`bool`): Whether to show the send button. Default is True.\n", "* **`show_stop`** (`bool`): Whether to show the stop button, temporarily replacing the send button during callback; has no effect if `callback` is not async.\n", "* **`show_rerun`** (`bool`): Whether to show the rerun button. Default is True.\n", "* **`show_undo`** (`bool`): Whether to show the undo button. Default is True.\n", "* **`show_clear`** (`bool`): Whether to show the clear button. Default is True.\n", "* **`show_button_name`** (`bool`): Whether to show the button name. Default is True.\n", "\n", "#### Properties:\n", "\n", "* **`active_widget`** (`Widget`): The currently active widget.\n", "* **`active`** (`int`): The currently active input widget tab index; -1 if there is only one widget available which is not in a tab.\n", "\n", "___" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Basics" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ChatInterface()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Although `ChatInterface` can be initialized without any arguments, it becomes much more useful, and interesting, with a `callback`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def even_or_odd(contents, user, instance):\n", " if len(contents) % 2 == 0:\n", " return \"Even number of characters.\"\n", " return \"Odd number of characters.\"\n", "\n", "ChatInterface(callback=even_or_odd)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You may also provide a more relevant, default `user` name and `avatar`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ChatInterface(\n", " callback=even_or_odd,\n", " user=\"Asker\",\n", " avatar=\"?\",\n", " callback_user=\"Counter\",\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Input Widgets" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also use a different type of widget for input, like `TextAreaInput` instead of `TextInput`, by setting `widgets`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def count_chars(contents, user, instance):\n", " return f\"Found {len(contents)} characters.\"\n", "\n", "\n", "ChatInterface(\n", " callback=count_chars,\n", " widgets=pn.widgets.TextAreaInput(\n", " placeholder=\"Enter some text to get a count!\", auto_grow=True, max_rows=3\n", " ),\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Multiple `widgets` can be set, which will be nested under a `Tabs` layout." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def get_num(contents, user, instance):\n", " if isinstance(contents, str):\n", " num = len(contents)\n", " else:\n", " num = contents\n", " return f\"Got {num}.\"\n", "\n", "ChatInterface(\n", " callback=get_num,\n", " widgets=[\n", " pn.widgets.TextAreaInput(placeholder=\"Enter some text to get a count!\"),\n", " pn.widgets.IntSlider(name=\"Number Input\", end=10)\n", " ],\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Widgets other than `TextInput` will require the user to manually click the `Send` button, unless the type is specified in `auto_send_types`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ChatInterface(\n", " callback=get_num,\n", " widgets=[\n", " pn.widgets.TextAreaInput(placeholder=\"Enter some text to get a count!\"),\n", " pn.widgets.IntSlider(name=\"Number Input\", end=10)\n", " ],\n", " auto_send_types=[pn.widgets.IntSlider],\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you include a `FileInput` in the list of widgets you can enable the user to upload files." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ChatInterface(widgets=pn.widgets.FileInput(name=\"CSV File\", accept=\".csv\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Try uploading a dataset! If you don't have a dataset in hand, download this sample dataset, [`penguins.csv`](https://datasets.holoviz.org/penguins/v1/penguins.csv)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note, if you don't like the default renderer, `pn.pane.DataFrame` for CSVs, you can specify `renderers` to use `pn.pane.Perspective`; just be sure you have the `\"perspective\"` extension added to `pn.extension(...)` at the top of your file!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ChatInterface(\n", " widgets=pn.widgets.FileInput(name=\"CSV File\", accept=\".csv\"),\n", " renderers=pn.pane.Perspective\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If a list is provided to `renderers`, will attempt to use the first renderer that does not raise an exception.\n", "\n", "In addition, you may render the input however you'd like with a custom renderer as long as the signature accepts one argument, namely `value`!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def bad_renderer(value):\n", " raise Exception(\"Won't render using this...\")\n", "\n", "def custom_renderer(value):\n", " return pn.Column(\n", " f\"Found {len(value)} rows in the CSV.\",\n", " pn.pane.Perspective(value, height=600)\n", " )\n", "\n", "ChatInterface(\n", " widgets=pn.widgets.FileInput(name=\"CSV File\", accept=\".csv\"),\n", " renderers=[bad_renderer, custom_renderer]\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you'd like to guide the user into using one widget after another, you can set `active` in the callback." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def guided_get_num(contents, user, instance):\n", " if isinstance(contents, str):\n", " num = len(contents)\n", " instance.active = 1 # change to IntSlider tab\n", " else:\n", " num = contents\n", " instance.active = 0 # Change to TextAreaInput tab\n", " return f\"Got {num}.\"\n", "\n", "pn.chat.ChatInterface(\n", " callback=guided_get_num,\n", " widgets=[\n", " pn.widgets.TextAreaInput(placeholder=\"Enter some text to get a count!\"),\n", " pn.widgets.IntSlider(name=\"Number Input\", end=10)\n", " ],\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Or, simply initialize with a single widget first, then replace with another widget in the callback." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def get_num_guided(contents, user, instance):\n", " if isinstance(contents, str):\n", " num = len(contents)\n", " instance.widgets = [widgets[1]] # change to IntSlider\n", " else:\n", " num = contents\n", " instance.widgets = [widgets[0]] # Change to TextAreaInput\n", " return f\"Got {num}.\"\n", "\n", "\n", "widgets = [\n", " pn.widgets.TextAreaInput(placeholder=\"Enter some text to get a count!\"),\n", " pn.widgets.IntSlider(name=\"Number Input\", end=10)\n", "]\n", "pn.chat.ChatInterface(\n", " callback=get_num_guided,\n", " widgets=widgets[0],\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The currently active widget can be accessed with the `active_widget` property." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "widgets = [\n", " pn.widgets.TextAreaInput(placeholder=\"Enter some text to get a count!\"),\n", " pn.widgets.IntSlider(name=\"Number Input\", end=10)\n", "]\n", "chat_interface = pn.chat.ChatInterface(\n", " widgets=widgets,\n", ")\n", "print(chat_interface.active_widget)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Sometimes, you may not want the widget to be reset after its contents has been sent.\n", "\n", "To have the widgets' `value` persist, set `reset_on_send=False`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pn.chat.ChatInterface(\n", " widgets=pn.widgets.TextAreaInput(),\n", " reset_on_send=False,\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Buttons" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you're not using an LLM to respond, the `Rerun` button may not be practical so it can be hidden by setting `show_rerun=False`.\n", "\n", "The same can be done for other buttons as well with `show_send`, `show_undo`, and `show_clear`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pn.chat.ChatInterface(callback=get_num, show_rerun=False, show_undo=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you want a slimmer `ChatInterface`, use `show_button_name=False` to hide the labels of the buttons and/ or `width` to set the total width of the component." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pn.chat.ChatInterface(callback=get_num, show_button_name=False, width=400)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "New buttons with custom functionality can be added to the input row through `button_properties`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def show_notice(instance, event):\n", " instance.send(\"This is how you add buttons!\", respond=False, user=\"System\")\n", "\n", "\n", "pn.chat.ChatInterface(\n", " button_properties={\"help\": {\"callback\": show_notice, \"icon\": \"help\"}}\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Default buttons can also be updated with custom behaviors, before using `callback` and after using `post_callback`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def run_before(instance, event):\n", " instance.send(\n", " \"This will be cleared so it won't show after clear!\",\n", " respond=False,\n", " user=\"System\",\n", " )\n", "\n", "\n", "def run_after(instance, event):\n", " instance.send(\"This will show after clear!\", respond=False, user=\"System\")\n", "\n", "\n", "pn.chat.ChatInterface(\n", " button_properties={\n", " \"clear\": {\"callback\": run_before, \"post_callback\": run_after, \"icon\": \"help\"}\n", " }\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You may also use custom Javascript code with `js_on_click` containing `code` and `args` keys for the buttons, and also set the `button_properties` after definition.\n", "\n", "Try typing something in the chat input, and then click the new `Help` button on the bottom right." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "chat_interface = pn.chat.ChatInterface()\n", "chat_interface.button_properties = {\n", " \"help\": {\n", " \"icon\": \"help\",\n", " \"js_on_click\": {\n", " \"code\": \"alert(`Typed: '${chat_input.value}'`)\",\n", " \"args\": {\"chat_input\": chat_interface.active_widget},\n", " },\n", " },\n", "}\n", "chat_interface" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Check out the [panel-chat-examples](https://holoviz-topics.github.io/panel-chat-examples/) docs for more examples related to [LangChain](https://python.langchain.com/docs/get_started/introduction), [OpenAI](https://openai.com/blog/chatgpt), [Mistral](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&ved=2ahUKEwjZtP35yvSBAxU00wIHHerUDZAQFnoECBEQAQ&url=https%3A%2F%2Fdocs.mistral.ai%2F&usg=AOvVaw2qpx09O_zOzSksgjBKiJY_&opi=89978449), [Llama](https://ai.meta.com/llama/), etc.\n", "\n", "\n", "Also, since `ChatInterface` inherits from [`ChatFeed`](ChatFeed.ipynb), be sure to also read [ChatFeed.ipynb](ChatFeed.ipynb) to understand `ChatInterface`'s full potential!" ] } ], "metadata": { "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 4 }