{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import panel as pn\n", "from panel.widgets import ChatInterface\n", "\n", "pn.extension()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `ChatInterface` is a high-level widget, providing a user-friendly front-end interface for inputting different kinds of messages: text, images, PDFs, etc.\n", "\n", "This widget provides front-end methods to:\n", "\n", "- Input (append) messages to the chat log.\n", "- Re-run (resend) the most recent `user` input [`ChatEntry`](ChatEntry.ipynb).\n", "- Remove entries until the previous `user` input [`ChatEntry`](ChatEntry.ipynb).\n", "- Clear the chat log, erasing all [`ChatEntry`](ChatEntry.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", "![Chat Design Specification](../../assets/ChatDesignSpecification.png)\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 | 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", "* **`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", "\n", "##### Styling\n", "\n", "* **`show_send`** (`bool`): Whether to show the send button. Default is True.\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": "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": [ "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", "ChatInterface(\n", " callback=count_chars,\n", " widgets=pn.widgets.TextAreaInput(placeholder=\"Enter some text to get a count!\"),\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 extension added!" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pn.extension(\"perspective\")\n", "\n", "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.widgets.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.widgets.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.widgets.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.widgets.ChatInterface(\n", " widgets=pn.widgets.TextAreaInput(),\n", " reset_on_send=False,\n", ")" ] }, { "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.widgets.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." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pn.widgets.ChatInterface(callback=get_num, show_button_name=False, width=400)" ] } ], "metadata": { "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 2 }