Skip to main content

A chat component for Dash

Project description

dash-chat

PyPI version Supported Python versions CircleCI

dash-chat is a Dash component library chat interface. It provides a customizable and responsive chat UI with support for markdown, chat persistence, typing indicators, themes, and state management.

Installation

$ pip install dash-chat

Basic Usage

The simplest way to use the dash_chat.ChatComponent is to initialize the messages prop as an empty list. This is a list of messages that initialize the chat UI. Each message is an OpenAI-style dictionary that must have the following key-value pairs:

  • role: The message sender, either "user" or "assistant".
  • content: The content of the message.

A dash callback chat function is also required to handle how the messages are updated

Example 1

Using OpenAI with dash-chat (requires the openai package - install it by running pip install openai)

dash-chat-demo

import os
import dash
from dash import callback, html, Input, Output, State
from dash_chat import ChatComponent
from openai import OpenAI


api_key = os.environ.get("OPENAI_API_KEY")
client = OpenAI(api_key=api_key)

app = dash.Dash(__name__)

app.layout = html.Div([
    ChatComponent(
        id="chat-component",
        messages=[],
    )
])

@callback(
    Output("chat-component", "messages"),
    Input("chat-component", "new_message"),
    State("chat-component", "messages"),
    prevent_initial_call=True,
)
def handle_chat(new_message, messages):
    if not new_message:
        return messages

    updated_messages = messages + [new_message]

    if new_message["role"] == "user":
        response = client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=updated_messages,
            temperature=1.0,
            max_tokens=150,
        )

        bot_response = {"role": "assistant", "content": response.choices[0].message.content.strip()}
        return updated_messages + [bot_response]

    return updated_messages

if __name__ == "__main__":
    app.run(debug=True)

Example 2

To send local images and files along with a message to the AI assistant, the structure of content in the messages prop becomes a list of dictionary. The content takes the structure;

    [
        {"type": "text", "text": "Analyze image"},
        {
            "type": "attachment",
            "file": <base64File>,
            "fileName": <file.name>,
            "fileType": <file.type>
        },
    ]

In your dash callback, follow the OpenAI-style for uploading images with text.

dash-chat-demo

import base64
import dash
import os
import re
from io import BytesIO
from dash import callback, html, Input, Output, State
from dash_chat import ChatComponent
from openai import OpenAI


api_key = os.environ.get("OPENAI_API_KEY")
client = OpenAI(api_key=api_key)

app = dash.Dash(__name__)

app.layout = html.Div([
    ChatComponent(
        id="chat-component",
        messages=[],
        supported_input_file_types=[".png", ".jpg", ".pdf", ".doc"]
    )
])


def decode_base64(data):
    match = re.match(r"data:(.*?);base64,(.*)", data)
    if match:
        _, base64_data = match.groups()
    else:
        base64_data = data

    missing_padding = len(base64_data) % 4
    if missing_padding:
        base64_data += "=" * (4 - missing_padding)

    return base64.b64decode(base64_data)


@callback(
    Output("chat-component", "messages"),
    Input("chat-component", "new_message"),
    State("chat-component", "messages"),
    prevent_initial_call=True,
)
def handle_chat(new_message, messages):
    if not new_message:
        return messages

    if isinstance(new_message["content"], list):
        user_content = []
        for item in new_message["content"]:
            if item["type"] == "text":
                user_content.append({"type": "text", "text": item["text"]})
            elif item["type"] == "attachment":
                file_type = item["fileType"]
                file_path = item["file"]
                file_name = item["fileName"]

                if file_type.startswith("image/"):
                    # https://github.com/openai/openai-python#vision
                    user_content.append(
                        {"type": "image_url", "image_url": {"url": file_path}}
                    )
                else:
                    # other file types (PDF, DOCX, etc.)
                    # https://github.com/openai/openai-python?tab=readme-ov-file#file-uploads
                    decoded_bytes = decode_base64(file_path)
                    uploaded_file = client.files.create(
                        file=(file_name, BytesIO(decoded_bytes), file_type),
                        purpose="user_data"
                    )
                    user_content.append({
                        "type": "text",
                        "text": f"File '{file_name}' uploaded. ID: {uploaded_file.id}",
                    })
        updated_messages = messages + [{"role": "user", "content": user_content}]
    else:
        updated_messages = messages + [new_message]

    if new_message["role"] == "user":
        response = client.chat.completions.create(
            model="gpt-4o-mini",
            messages=updated_messages,
            temperature=1.0,
            max_tokens=150,
        )

        bot_response = {
            "role": "assistant",
            "content": response.choices[0].message.content.strip(),
        }

        return updated_messages + [bot_response]

    return updated_messages


if __name__ == "__main__":
    app.run(debug=True)

Example 3

ChatComponent is agnostic about which chatbot or AI assistant technology you're interacting with, so here's an example not using OpenAI

import time
import dash
from dash import callback, html, Input, Output, State
from dash_chat import ChatComponent


app = dash.Dash(__name__)

app.layout = html.Div([
    ChatComponent(
        id="chat-component",
        messages=[],
    )
])

@callback(
    Output("chat-component", "messages"),
    Input("chat-component", "new_message"),
    State("chat-component", "messages"),
    prevent_initial_call=True,
)
def handle_chat(new_message, messages):
    if not new_message:
        return messages

    updated_messages = messages + [new_message]

    if new_message["role"] == "user":
        time.sleep(2)
        bot_response = {"role": "assistant", "content": "Hello John Doe."}
        return updated_messages + [bot_response]

    return updated_messages

if __name__ == "__main__":
    app.run(debug=True)

Persistence Functionality

The ChatComponent supports persistence, allowing messages to be stored and retrieved across page reloads. When persistence=True, messages are saved in the specified storage (localStorage or sessionStorage).

On initialization, the component checks for stored messages. If stored messages exist, they are loaded; otherwise, an empty message list is used. New messages are automatically saved to storage. When the page is refreshed, stored messages are restored to maintain chat history. To enable persistence, set:

ChatComponent(
    id="chat-component",
    persistence=True,
    persistence_type="local"  # or "session"
)

Renderers (Graphs, Tables, Attachments & Text)

dash-chat supports rich content rendering by allowing messages to contain structured content types like graphs, tables, and images. You can render custom content by passing a structured list to the content field of a message.

Text

{
    "role": "assistant",
    "content": {
        "type": "text",
        "text": "This will be rendered as a markdown message"
    },
}

Attachments (Images & Files)

{
    "role": "assistant",
    "content": {
        "type": "attachment",
        "file": "data:image/png;base64,...",
        "fileName": "example.png",
        "fileType": "image/png"
    }
}

Renders an image or a downloadable file preview.

Graph

{
    "role": "assistant",
    "content": {
        "type": "graph",
        "props": {
            "figure": {
                "data": [
                    {
                        "x": [1, 2, 3],
                        "y": [4, 1, 2],
                        "type": "bar", "name": "Demo"
                    }
                ],
                "layout": {"title": "Bar Chart"},
            },
            "config": {"displaylogo": True},
            "responsive": True
        }
    }
}

Renders an interactive Plotly graph equivalent to dcc.Graph. The props object supports most of the arguments you would pass to a dcc.Graph.

Table

{
    "role": "assistant",
    "content": {
        "type": "table",
        "header": ["Order ID", "Item", "Quantity", "Total"],
        "data": [
            ["#1021", "Apple iPhone", 1, "$799"],
            ["#1022", "Samsung Galaxy", 2, "$1398"]
        ],
        "props": {
            "striped": True,
            "bordered": True,
            "hover": True,
            "responsive": True,
            "size": "lg"
        }
    }
}

Renders an HTML table. You provide the table by setting:

  • header: a list of strings representing the column names.

    Example: ["Order ID", "Item", "Quantity", "Total"]

  • data: a list of rows, where each row is a list of strings (or values) for the cells.

    Example: [["#1021", "Apple iPhone", 1, "$799"], ["#1022", "Samsung Galaxy", 2, "$1398"]]

The props object supports all the arguments you would pass to dbc.Table in dash-bootstrap-components.

Multiple renderers as a list at "content"

Multiple supported renderers can also be provided as the assistants' content:

{
    "role": "assistant",
    "content": [
        {"type": "text", "text": "Here's a bar chart of your data."},
        {
            "type": "graph",
            "props": {
                "figure": {
                    "data": [{"x": [1, 2, 3], "y": [4, 1, 2], "type": "bar", "name": "Demo"}],
                    "layout": {"title": "Bar Chart"},
                }
                "config": {},
                "responsive": True
            },
        },
        {
            "type": "table",
            "header": ["Order ID", "Item", "Quantity", "Total"],
            "data": [
                ["#1021", "iPhone 14", 1, "$799"],
                ["#1022", "Galaxy S22", 2, "$1398"],
                ["#1023", "Pixel 7", 1, "$599"],
            ],
            "props": {
                "striped": True
            },
        },
    ]
}

For a complete example of how to setup dash apps and how to uses renderers see the usage folder.

Props

ChatComponent can be configured with the following properties:

Prop Name Type Default Value Description
id string None Unique identifier for the component, required for Dash callbacks.
container_style dict None Inline css styles to customize the chat container.
fill_height boolean True Whether to vertically fill the screen with the chat container. If False, constrains height.
fill_width boolean True Whether to horizontally fill the screen with the chat container. If False, constrains width.
input_container_style dict None Inline css styles for the container holding the message input field.
input_text_style dict None Inline css styles for the message input field itself.
messages list of dicts None List of chat messages. Each message object must include: role and content. Initialize as an empty list if no on first load.
theme string "light" Theme for the chat interface. Options: "light" or "dark".
typing_indicator string "dots" Type of typing indicator. Options: "dots" (animated dots) or "spinner" (spinner).
user_bubble_style dict {"backgroundColor": "#007bff", "color": "white", "marginLeft": "auto", "textAlign": "right"} Inline css styles to customize the message bubble for user.
assistant_bubble_style dict {"backgroundColor": "#f1f0f0", "color": "black", "marginRight": "auto", "textAlign": "left"} Inline css styles to customize the message bubble for assistant.
input_placeholder string None Placeholder text to be used in the input box.
class_name string None Name to use as class attribute on the main chat container.
persistence boolean False Whether to store chat messages so that it can be persisted.
persistence_type string "local" Where chat messages will be stored for persistence. Options: "local" or "session"
supported_input_file_types string "*/*" String or list of file types to support in the file input

License

This project is licensed under the MIT License. See the LICENSE file for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dash_chat_custom-1.0.0.tar.gz (5.7 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dash_chat_custom-1.0.0-py3-none-any.whl (5.8 MB view details)

Uploaded Python 3

File details

Details for the file dash_chat_custom-1.0.0.tar.gz.

File metadata

  • Download URL: dash_chat_custom-1.0.0.tar.gz
  • Upload date:
  • Size: 5.7 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.10.11

File hashes

Hashes for dash_chat_custom-1.0.0.tar.gz
Algorithm Hash digest
SHA256 3caef3ba12c4e73ae966dc80a0a47c3d932cffa26662a6599a4e4a8e9457c488
MD5 fa956a321d8069187acca5a80f1377eb
BLAKE2b-256 b3b95bdaf96b4bd9777c41757b3cfda2910a56b5bdad54ef68930a1e018789f5

See more details on using hashes here.

File details

Details for the file dash_chat_custom-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for dash_chat_custom-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 59e4da1b7079ca94be978b4fd755662dbcd2d5848777f7b9fa06909de54a0228
MD5 ceaf0cea2d633612a3d9e85f74a727d4
BLAKE2b-256 2dcef935ab92ae6e7a4bc391e6f490d9b41988d9e64f6e8052d2fb5a7a852681

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page