ChatInterface#

Open this notebook in Jupyterlite | Download this notebook from GitHub (right-click to download).


import panel as pn
from panel.chat import ChatInterface

pn.extension("perspective")

The ChatInterface is a high-level layout, providing a user-friendly front-end interface for inputting different kinds of messages: text, images, PDFs, etc.

This layout provides front-end methods to:

  • Input (append) messages to the chat log.

  • Re-run (resend) the most recent user input ChatMessage.

  • Remove messages until the previous user input ChatMessage.

  • Clear the chat log, erasing all ChatMessage objects.

Since ChatInterface inherits from ChatFeed, it features all the capabilities of ChatFeed; please see ChatFeed.ipynb for its backend capabilities.

Check out the panel-chat-examples docs to see applicable examples related to LangChain, OpenAI, Mistral, Llama, etc. If you have an example to demo, we’d love to add it to the panel-chat-examples gallery!

Chat Design Specification

Parameters:#

Core#

  • widgets (Widget | List[Widget]): Widgets to use for the input. If not provided, defaults to [TextInput].

  • user (str): Name of the ChatInterface user.

  • 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.

  • reset_on_send (bool): Whether to reset the widget’s value after sending a message; has no effect for TextInput.

  • 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].

  • 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, and/or post_callback keys. If the button names correspond to default buttons (send, rerun, undo, clear), the default icon can be updated and if a callback key value pair is provided, the specified callback functionality runs before the existing one. For button names that don’t match existing ones, new buttons are created and must include a callback or post_callback key. The provided callbacks should have a signature that accepts two positional arguments: instance (the ChatInterface instance) and event (the button click event).

Styling#

  • show_send (bool): Whether to show the send button. Default is True.

  • show_rerun (bool): Whether to show the rerun button. Default is True.

  • show_undo (bool): Whether to show the undo button. Default is True.

  • show_clear (bool): Whether to show the clear button. Default is True.

  • show_button_name (bool): Whether to show the button name. Default is True.

Properties:#

  • active_widget (Widget): The currently active widget.

  • active (int): The currently active input widget tab index; -1 if there is only one widget available which is not in a tab.


ChatInterface()

Although ChatInterface can be initialized without any arguments, it becomes much more useful, and interesting, with a callback.

def even_or_odd(contents, user, instance):
    if len(contents) % 2 == 0:
        return "Even number of characters."
    return "Odd number of characters."

ChatInterface(callback=even_or_odd)

You may also provide a more relevant, default user name and avatar.

ChatInterface(
    callback=even_or_odd,
    user="Asker",
    avatar="?",
    callback_user="Counter",
)

You can also use a different type of widget for input, like TextAreaInput instead of TextInput, by setting widgets.

def count_chars(contents, user, instance):
    return f"Found {len(contents)} characters."


ChatInterface(
    callback=count_chars,
    widgets=pn.widgets.TextAreaInput(
        placeholder="Enter some text to get a count!", auto_grow=True, max_rows=3
    ),
)

Multiple widgets can be set, which will be nested under a Tabs layout.

def get_num(contents, user, instance):
    if isinstance(contents, str):
        num = len(contents)
    else:
        num = contents
    return f"Got {num}."

ChatInterface(
    callback=get_num,
    widgets=[
        pn.widgets.TextAreaInput(placeholder="Enter some text to get a count!"),
        pn.widgets.IntSlider(name="Number Input", end=10)
    ],
)

Widgets other than TextInput will require the user to manually click the Send button, unless the type is specified in auto_send_types.

ChatInterface(
    callback=get_num,
    widgets=[
        pn.widgets.TextAreaInput(placeholder="Enter some text to get a count!"),
        pn.widgets.IntSlider(name="Number Input", end=10)
    ],
    auto_send_types=[pn.widgets.IntSlider],
)

If you include a FileInput in the list of widgets you can enable the user to upload files.

ChatInterface(widgets=pn.widgets.FileInput(name="CSV File", accept=".csv"))

Try uploading a dataset! If you don’t have a dataset in hand, download this sample dataset, penguins.csv.

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!

ChatInterface(
    widgets=pn.widgets.FileInput(name="CSV File", accept=".csv"),
    renderers=pn.pane.Perspective
)

If a list is provided to renderers, will attempt to use the first renderer that does not raise an exception.

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!

def bad_renderer(value):
    raise Exception("Won't render using this...")

def custom_renderer(value):
    return pn.Column(
        f"Found {len(value)} rows in the CSV.",
        pn.pane.Perspective(value, height=600)
    )

ChatInterface(
    widgets=pn.widgets.FileInput(name="CSV File", accept=".csv"),
    renderers=[bad_renderer, custom_renderer]
)

If you’d like to guide the user into using one widget after another, you can set active in the callback.

def guided_get_num(contents, user, instance):
    if isinstance(contents, str):
        num = len(contents)
        instance.active = 1  # change to IntSlider tab
    else:
        num = contents
        instance.active = 0  # Change to TextAreaInput tab
    return f"Got {num}."

pn.chat.ChatInterface(
    callback=guided_get_num,
    widgets=[
        pn.widgets.TextAreaInput(placeholder="Enter some text to get a count!"),
        pn.widgets.IntSlider(name="Number Input", end=10)
    ],
)

Or, simply initialize with a single widget first, then replace with another widget in the callback.

def get_num_guided(contents, user, instance):
    if isinstance(contents, str):
        num = len(contents)
        instance.widgets = [widgets[1]]  # change to IntSlider
    else:
        num = contents
        instance.widgets = [widgets[0]]  # Change to TextAreaInput
    return f"Got {num}."


widgets = [
    pn.widgets.TextAreaInput(placeholder="Enter some text to get a count!"),
    pn.widgets.IntSlider(name="Number Input", end=10)
]
pn.chat.ChatInterface(
    callback=get_num_guided,
    widgets=widgets[0],
)

The currently active widget can be accessed with the active_widget property.

widgets = [
    pn.widgets.TextAreaInput(placeholder="Enter some text to get a count!"),
    pn.widgets.IntSlider(name="Number Input", end=10)
]
chat_interface = pn.chat.ChatInterface(
    widgets=widgets,
)
print(chat_interface.active_widget)
TextAreaInput(css_classes=['chat-interface-input-wid...], placeholder='Enter some text t..., sizing_mode='stretch_width')

Sometimes, you may not want the widget to be reset after its contents has been sent.

To have the widgets’ value persist, set reset_on_send=False.

pn.chat.ChatInterface(
    widgets=pn.widgets.TextAreaInput(),
    reset_on_send=False,
)

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.

The same can be done for other buttons as well with show_send, show_undo, and show_clear.

pn.chat.ChatInterface(callback=get_num, show_rerun=False, show_undo=False)

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.

pn.chat.ChatInterface(callback=get_num, show_button_name=False, width=400)

New buttons with custom functionality can be added to the input row through button_properties.

def show_notice(instance, event):
    instance.send("This is how you add buttons!", respond=False, user="System")


pn.chat.ChatInterface(
    button_properties={"help": {"callback": show_notice, "icon": "help"}}
)

Default buttons can also be updated with custom behaviors, before using callback and after using post_callback.

def run_before(instance, event):
    instance.send(
        "This will be cleared so it won't show after clear!",
        respond=False,
        user="System",
    )


def run_after(instance, event):
    instance.send("This will show after clear!", respond=False, user="System")


pn.chat.ChatInterface(
    button_properties={
        "clear": {"callback": run_before, "post_callback": run_after, "icon": "help"}
    }
)

Check out the panel-chat-examples docs for more examples related to LangChain, OpenAI, Mistral, Llama, etc.


Open this notebook in Jupyterlite | Download this notebook from GitHub (right-click to download).