Skip to main content

Dash hooks plugin for WidgetBot Discord embeds

Project description

dash-widgetbot

A Dash 3.x hooks plugin that embeds WidgetBot Discord chat into Plotly Dash applications — no webpack, no React build step, no npm.

Two components are provided:

  • DiscordCrate — floating Discord chat button with full API control (toggle, notify, navigate, style)
  • DiscordWidget — inline embedded Discord channel rendered as a plain <iframe>

Installation

pip install dash-widgetbot

Optional extras (install only what you need):

pip install dash-widgetbot[bot]      # requests + PyNaCl — slash commands + webhooks
pip install dash-widgetbot[ai]       # google-genai — Gemini AI responder
pip install dash-widgetbot[realtime] # flask-socketio + dash-socketio — real-time transport
pip install dash-widgetbot[all]      # everything

Quick Start

DiscordCrate (floating button)

import dash
from dash import html
import dash_widgetbot as dwb

# 1. Register BEFORE creating the Dash app
store_ids = dwb.add_discord_crate(
    server="299881420891881473",
    channel="355719584830980096",
)

app = dash.Dash(__name__)
app.layout = html.Div([...])

if __name__ == "__main__":
    app.run(debug=True)
# In any callback — send commands to the Crate
from dash import Input, Output, callback
import dash_widgetbot as dwb

@callback(
    Output(dwb.STORE_IDS["command"], "data"),
    Input("open-btn", "n_clicks"),
    prevent_initial_call=True,
)
def open_crate(n):
    return dwb.crate_toggle(True)

DiscordWidget (inline embed)

import dash
from dash import html
import dash_widgetbot as dwb

# 1. Register BEFORE creating the Dash app
widget_ids = dwb.add_discord_widget(
    server="299881420891881473",
    channel="355719584830980096",
)

app = dash.Dash(__name__)

# 2. Place the container in your layout
app.layout = html.Div([
    dwb.discord_widget_container(
        server="299881420891881473",
        channel="355719584830980096",
        width="100%",
        height="600px",
    )
])

DiscordCrate

add_discord_crate()

Call once before dash.Dash(). Registers CDN script, layout stores, and clientside callbacks via Dash hooks.

store_ids = dwb.add_discord_crate(
    server,                        # Required — Discord server ID
    channel="",                    # Default channel ID
    color="#5865f2",               # Button background color
    location=["bottom", "right"],  # ["top"|"bottom", "left"|"right"]
    glyph=("", ""),                # Custom icon (open_url, closed_url)
    css="",                        # Extra CSS injected into embed
    notifications=True,            # Enable message notifications
    dm_notifications=True,         # Enable DM notifications
    indicator=True,                # Show unread indicator dot
    timeout=10000,                 # Notification display duration (ms)
    defer=False,                   # Delay Crate init until first interaction
    prefix="",                     # Namespace for multiple Crate instances
    pages=None,                    # List of paths where Crate is visible
)

Returns a dict of store IDs: config, command, event, message, user, status.

Command helpers

All helpers return a dict intended to be stored in the command store:

# Open / close the popup
dwb.crate_toggle(True)          # open
dwb.crate_toggle(False)         # close
dwb.crate_toggle()              # toggle current state

# Show a notification bubble
dwb.crate_notify("Hello!", timeout=5000, avatar="https://...")

# Navigate to a different channel
dwb.crate_navigate("355719584830980096")

# Hide / show the entire Crate button
dwb.crate_hide()
dwb.crate_show()

# Update appearance at runtime
dwb.crate_update_options(color="#ed4245", location=["top", "left"])
dwb.crate_set_color("--color-accent", "#ed4245")

# Send a message on behalf of the signed-in user
dwb.crate_send_message("Hello from Dash!")

# Raw embed-api command
dwb.crate_emit("navigate", {"guild": "...", "channel": "..."})

Reading events

from dash import Input, callback

@callback(
    Output("last-message", "children"),
    Input(dwb.STORE_IDS["message"], "data"),
)
def on_message(data):
    if not data:
        return "No messages yet"
    return f"{data['author']['username']}: {data['content']}"

Available stores:

Store key Fires when Payload keys
event signIn, signOut, sentMessage, toggle, ready, … type, _ts, event-specific fields
message A message is received in the channel content, author, channel, channel_id
user User signs in or out username, id, avatar, signed_in
status Crate opens/closes initialized, open

Multiple Crate instances

# Register with a unique prefix
support_ids = dwb.add_discord_crate(
    server="...", channel="...",
    color="#ed4245", location=["top", "right"],
    prefix="support",
)

# Use prefix-specific store IDs
support_store_ids = dwb.get_crate_store_ids("support")

# Command helpers accept prefix too
dwb.crate_toggle(True, prefix="support")

DiscordWidget

add_discord_widget()

Call once before dash.Dash(). Registers layout stores and a window.postMessage listener via Dash hooks. No CDN script is loaded.

widget_ids = dwb.add_discord_widget(
    server,                            # Required — Discord server ID
    channel="",                        # Default channel ID
    width="100%",
    height="600px",
    container_id="widgetbot-container",  # Must match discord_widget_container()
)

discord_widget_container()

Place in your layout wherever the inline widget should appear:

dwb.discord_widget_container(
    server="299881420891881473",
    channel="355719584830980096",
    width="100%",
    height="600px",
    container_id="widgetbot-container",  # Must match add_discord_widget()
)

Widget events

widget_ids = dwb.get_widget_store_ids("widgetbot-container")

@callback(
    Output("widget-events", "children"),
    Input(widget_ids["event"], "data"),
)
def on_widget_event(data):
    if not data:
        return "No events yet"
    return f"Event: {data['type']}"

Optional Features

Slash Commands (Discord Interactions Endpoint)

Requires [bot] extra, a public HTTPS URL (e.g. ngrok), and a registered Discord application.

import dash_widgetbot as dwb

dwb.add_discord_interactions(
    public_key="your_discord_public_key_hex",
    application_id="your_app_id",
)

@dwb.register_command("ask")
def handle_ask(interaction):
    question = interaction["data"]["options"][0]["value"]
    return f"You asked: {question}"

Register the endpoint in the Discord Developer Portal:

Interactions Endpoint URL → https://yourdomain.com/api/discord/interactions

Automatically sync at startup using the ngrok auto-detect:

dwb.sync_discord_endpoint()  # detects ngrok or reads INTERACTIONS_URL env var

Discord Components V2

Build rich Discord messages with the full Components V2 builder library:

from dash_widgetbot.components import container, text_display, button, action_row

payload = container(
    text_display("## Hello from Dash!"),
    action_row(
        button("Visit App", url="https://your-app.com"),
    ),
    color=0x5865f2,
)

Structured AI Responses (Gemini)

Requires [ai] extra and GEMINI_API_KEY env var.

result = dwb.generate_structured_response("What is this app?")
ai_response = result["response"]  # AIResponse Pydantic model

# Convert to Discord Components V2 payload
discord_payload = dwb.build_components_v2(ai_response)

# Or render as Dash components (Discord dark preview)
dash_preview = dwb.render_discord_preview(ai_response)

AIResponse supports: title, color, components (text, section, gallery, button_row, separator blocks), footer, image_prompt, actions, and sources (from Google Search grounding).

Multi-Format AI Generation (/gen)

result = dwb.generate_gen_response("Explain Python async/await")
gen_response = result["response"]  # GenResponse Pydantic model

# Render as a styled DMC card
from dash_widgetbot.gen_renderer import render_gen_card
card = render_gen_card(gen_entry)

Supported formats: article, code, data_table, image, callout.

Per-User Private AI Threads

When AI_THREAD_PARENT_CHANNEL is set, Discord AI commands (/ai, /ask, /gen) automatically route responses to a private thread per user:

AI_THREAD_PARENT_CHANNEL=your_text_channel_id

Each Discord user gets their own private thread (type 12, 7-day auto-archive). Bot permissions required: CREATE_PRIVATE_THREADS, SEND_MESSAGES_IN_THREADS, MANAGE_THREADS.

Real-Time Transport ([realtime])

Requires [realtime] extra. Adds Socket.IO alongside the always-active store bridge for zero-latency server → client pushes.

from flask_socketio import SocketIO
from dash_widgetbot import configure_socketio

_socketio = SocketIO(app.server, async_mode='threading', cors_allowed_origins="*")
configure_socketio(_socketio)

# Push a command to all connected clients from a background thread
from dash_widgetbot import emit_command
emit_command(dwb.crate_notify("Job finished!"))

Progress Tracking

ProgressTracker fans out real-time progress updates to multiple sinks during long-running AI generation:

from dash_widgetbot.progress import ProgressTracker, SocketIOSink, EphemeralSink

tracker = ProgressTracker(sinks=[SocketIOSink(), EphemeralSink(app_id, token)])
result = dwb.generate_gen_response(prompt, on_progress=tracker.stream_callback())
tracker.close()

Progress phases: analyzinggenerating (10–80%) → parsingcreating_imagepostingcomplete.

Outbound Webhooks

dwb.send_webhook_message(
    content="Deployed successfully!",
    webhook_url="https://discord.com/api/webhooks/...",
    username="Dash Bot",
)

Action Tag Parser

Embed action tags in any text (e.g. AI responses, slash command replies):

text = "Go here [ACTION:navigate:/reports] or [ACTION:notify:Done!]"

actions = dwb.parse_actions(text)
# [{"type": "navigate", "data": "/reports"}, {"type": "notify", "data": "Done!"}]

clean = dwb.strip_actions(text)
# "Go here  or "

Valid actions: navigate, notify, toggle, hide, show, open_url


Environment Variables

# WidgetBot embed
WIDGETBOT_SERVER=your_server_id
WIDGETBOT_CHANNEL=your_channel_id
WIDGETBOT_SHARD=                   # empty = free tier; https://e-business.widgetbot.co for paid

# Discord Bot (required for slash commands)
DISCORD_APPLICATION_ID=
DISCORD_PUBLIC_KEY=
DISCORD_BOT_TOKEN=
DISCORD_WEBHOOK_URL=
DISCORD_GUILD_ID=                  # guild for slash command registration (empty = global)

# Interactions endpoint URL (empty = ngrok auto-detect)
INTERACTIONS_URL=

# Gemini AI
GEMINI_API_KEY=
GEMINI_MODEL=                      # default: gemini-2.0-flash
GEMINI_IMAGE_API_KEY=              # falls back to GEMINI_API_KEY
GEMINI_IMAGE_MODEL=                # default: gemini-2.0-flash-exp-image-generation
GEMINI_SEARCH_GROUNDING=           # default: true; set to "false" to disable

# Private AI Threads (optional)
AI_THREAD_PARENT_CHANNEL=          # channel ID; enables per-user private threads

Use python-dotenv to load them:

from dotenv import load_dotenv
load_dotenv()

Architecture

Python callback  →  dcc.Store (command)  →  clientside_callback  →  Crate API
Crate events     →  set_props()          →  dcc.Store (events)   →  Python callback
Widget iframe    →  window.postMessage   →  set_props()          →  dcc.Store (events)

[realtime] additive path:
server side      →  emit_command()       →  Socket.IO            →  Crate API (direct)
gen_store.add()  →  socketio.emit()      →  DashSocketIO prop    →  Dash callback

Key design decisions:

  • No build toolchain — pure Python + inline JS via Dash hooks
  • Store bridge always activedcc.Store carries all commands and events; Socket.IO is purely additive
  • set_props() — async event push from JS to Dash stores without callback returns
  • CDN-only — WidgetBot JS loaded from jsDelivr; widget uses a plain cross-origin <iframe>
  • Namespaced IDs — all store IDs prefixed with _widgetbot- to avoid collisions
  • Non-blocking sinks — Discord API calls for progress edits fire in daemon threads; generation is never blocked by cosmetic channel edits

Example App

Clone the repo and run the included 13-page example application:

git clone https://github.com/pip-install-python/dash-widgetbot
cd dash-widgetbot
pip install -e ".[all]"
cp .env.example .env          # fill in your server/channel IDs and API keys
python app.py

Open http://127.0.0.1:8150. Pages cover every feature:

Page What it shows
Home Overview and quick-start
Crate Commands toggle, notify, navigate, hide/show
Crate Events live event log, last message, user status
Crate Styling runtime color, position, glyph, embed colors
Widget Embed inline iframe with event display
Multi-Instance two additional named Crate instances
Bot Bridge action tag parsing and execution sandbox
Slash Commands interactions setup guide + local /ask test
AI Chat Gemini structured responses with Discord preview
Webhook Send outbound webhook composer
Rich Messages Components V2 message builder
Rich Message Preview live Components V2 visual builder
Gen Gallery real-time feed of Discord /gen and /ai results

Requirements

Requirement Version
Python ≥ 3.11
Dash ≥ 3.0.3
WidgetBot account Free tier available at widgetbot.io

License

MIT — see LICENSE for details.

Pip Install Python LLCpip-install-python.com

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_widgetbot-0.4.1.tar.gz (56.1 kB view details)

Uploaded Source

File details

Details for the file dash_widgetbot-0.4.1.tar.gz.

File metadata

  • Download URL: dash_widgetbot-0.4.1.tar.gz
  • Upload date:
  • Size: 56.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.3

File hashes

Hashes for dash_widgetbot-0.4.1.tar.gz
Algorithm Hash digest
SHA256 ac272bebfa1e5c3f6746ecd69422f3e273d8e2972b027683118280e963aa6b4f
MD5 902b4851c38f29d251b305fd47d62336
BLAKE2b-256 71d50f0bbf43aa3765929e60ddf5f302a622204465078289bcc8d290e8a2d970

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