dash-widgetbot 0.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dash_widgetbot-0.4.1/LICENSE +21 -0
- dash_widgetbot-0.4.1/PKG-INFO +532 -0
- dash_widgetbot-0.4.1/README.md +492 -0
- dash_widgetbot-0.4.1/dash_widgetbot/__init__.py +266 -0
- dash_widgetbot-0.4.1/dash_widgetbot/_bridge.py +114 -0
- dash_widgetbot-0.4.1/dash_widgetbot/_constants.py +98 -0
- dash_widgetbot-0.4.1/dash_widgetbot/_transport.py +36 -0
- dash_widgetbot-0.4.1/dash_widgetbot/action_parser.py +61 -0
- dash_widgetbot-0.4.1/dash_widgetbot/ai_builder.py +142 -0
- dash_widgetbot-0.4.1/dash_widgetbot/ai_image.py +91 -0
- dash_widgetbot-0.4.1/dash_widgetbot/ai_responder.py +352 -0
- dash_widgetbot-0.4.1/dash_widgetbot/ai_schemas.py +100 -0
- dash_widgetbot-0.4.1/dash_widgetbot/components.py +635 -0
- dash_widgetbot-0.4.1/dash_widgetbot/crate.py +435 -0
- dash_widgetbot-0.4.1/dash_widgetbot/gen_renderer.py +288 -0
- dash_widgetbot-0.4.1/dash_widgetbot/gen_responder.py +248 -0
- dash_widgetbot-0.4.1/dash_widgetbot/gen_schemas.py +70 -0
- dash_widgetbot-0.4.1/dash_widgetbot/gen_store.py +105 -0
- dash_widgetbot-0.4.1/dash_widgetbot/interactions.py +857 -0
- dash_widgetbot-0.4.1/dash_widgetbot/preview.py +167 -0
- dash_widgetbot-0.4.1/dash_widgetbot/progress.py +373 -0
- dash_widgetbot-0.4.1/dash_widgetbot/threads.py +155 -0
- dash_widgetbot-0.4.1/dash_widgetbot/webhook.py +108 -0
- dash_widgetbot-0.4.1/dash_widgetbot/widget.py +196 -0
- dash_widgetbot-0.4.1/dash_widgetbot.egg-info/PKG-INFO +532 -0
- dash_widgetbot-0.4.1/dash_widgetbot.egg-info/SOURCES.txt +29 -0
- dash_widgetbot-0.4.1/dash_widgetbot.egg-info/dependency_links.txt +1 -0
- dash_widgetbot-0.4.1/dash_widgetbot.egg-info/requires.txt +21 -0
- dash_widgetbot-0.4.1/dash_widgetbot.egg-info/top_level.txt +1 -0
- dash_widgetbot-0.4.1/pyproject.toml +41 -0
- dash_widgetbot-0.4.1/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pip Install Python LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dash-widgetbot
|
|
3
|
+
Version: 0.4.1
|
|
4
|
+
Summary: Dash hooks plugin for WidgetBot Discord embeds
|
|
5
|
+
Author-email: Pip Install Python LLC <pip@pip-install-python.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://pip-install-python.com/pip/dash_widgetbot
|
|
8
|
+
Project-URL: Repository, https://github.com/pip-install-python/dash-widgetbot
|
|
9
|
+
Project-URL: Changelog, https://github.com/pip-install-python/dash-widgetbot/blob/main/CHANGELOG.md
|
|
10
|
+
Keywords: dash,discord,widgetbot,chat,embed,hooks,plotly
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Framework :: Dash
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: dash>=3.0.3
|
|
23
|
+
Provides-Extra: bot
|
|
24
|
+
Requires-Dist: requests>=2.31.0; extra == "bot"
|
|
25
|
+
Requires-Dist: PyNaCl>=1.5.0; extra == "bot"
|
|
26
|
+
Provides-Extra: ai
|
|
27
|
+
Requires-Dist: google-genai>=1.0.0; extra == "ai"
|
|
28
|
+
Provides-Extra: realtime
|
|
29
|
+
Requires-Dist: flask-socketio>=5.3.0; extra == "realtime"
|
|
30
|
+
Requires-Dist: dash-socketio>=1.1.1; extra == "realtime"
|
|
31
|
+
Requires-Dist: simple-websocket>=1.0.0; extra == "realtime"
|
|
32
|
+
Provides-Extra: all
|
|
33
|
+
Requires-Dist: requests>=2.31.0; extra == "all"
|
|
34
|
+
Requires-Dist: PyNaCl>=1.5.0; extra == "all"
|
|
35
|
+
Requires-Dist: google-genai>=1.0.0; extra == "all"
|
|
36
|
+
Requires-Dist: flask-socketio>=5.3.0; extra == "all"
|
|
37
|
+
Requires-Dist: dash-socketio>=1.1.1; extra == "all"
|
|
38
|
+
Requires-Dist: simple-websocket>=1.0.0; extra == "all"
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
# dash-widgetbot
|
|
42
|
+
|
|
43
|
+
A [Dash 3.x hooks](https://dash.plotly.com/hooks) plugin that embeds [WidgetBot](https://widgetbot.io) Discord chat into Plotly Dash applications — no webpack, no React build step, no npm.
|
|
44
|
+
|
|
45
|
+
Two components are provided:
|
|
46
|
+
|
|
47
|
+
- **DiscordCrate** — floating Discord chat button with full API control (toggle, notify, navigate, style)
|
|
48
|
+
- **DiscordWidget** — inline embedded Discord channel rendered as a plain `<iframe>`
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install dash-widgetbot
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Optional extras** (install only what you need):
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install dash-widgetbot[bot] # requests + PyNaCl — slash commands + webhooks
|
|
62
|
+
pip install dash-widgetbot[ai] # google-genai — Gemini AI responder
|
|
63
|
+
pip install dash-widgetbot[realtime] # flask-socketio + dash-socketio — real-time transport
|
|
64
|
+
pip install dash-widgetbot[all] # everything
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Quick Start
|
|
70
|
+
|
|
71
|
+
### DiscordCrate (floating button)
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
import dash
|
|
75
|
+
from dash import html
|
|
76
|
+
import dash_widgetbot as dwb
|
|
77
|
+
|
|
78
|
+
# 1. Register BEFORE creating the Dash app
|
|
79
|
+
store_ids = dwb.add_discord_crate(
|
|
80
|
+
server="299881420891881473",
|
|
81
|
+
channel="355719584830980096",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
app = dash.Dash(__name__)
|
|
85
|
+
app.layout = html.Div([...])
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
app.run(debug=True)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
# In any callback — send commands to the Crate
|
|
93
|
+
from dash import Input, Output, callback
|
|
94
|
+
import dash_widgetbot as dwb
|
|
95
|
+
|
|
96
|
+
@callback(
|
|
97
|
+
Output(dwb.STORE_IDS["command"], "data"),
|
|
98
|
+
Input("open-btn", "n_clicks"),
|
|
99
|
+
prevent_initial_call=True,
|
|
100
|
+
)
|
|
101
|
+
def open_crate(n):
|
|
102
|
+
return dwb.crate_toggle(True)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### DiscordWidget (inline embed)
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
import dash
|
|
109
|
+
from dash import html
|
|
110
|
+
import dash_widgetbot as dwb
|
|
111
|
+
|
|
112
|
+
# 1. Register BEFORE creating the Dash app
|
|
113
|
+
widget_ids = dwb.add_discord_widget(
|
|
114
|
+
server="299881420891881473",
|
|
115
|
+
channel="355719584830980096",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
app = dash.Dash(__name__)
|
|
119
|
+
|
|
120
|
+
# 2. Place the container in your layout
|
|
121
|
+
app.layout = html.Div([
|
|
122
|
+
dwb.discord_widget_container(
|
|
123
|
+
server="299881420891881473",
|
|
124
|
+
channel="355719584830980096",
|
|
125
|
+
width="100%",
|
|
126
|
+
height="600px",
|
|
127
|
+
)
|
|
128
|
+
])
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## DiscordCrate
|
|
134
|
+
|
|
135
|
+
### `add_discord_crate()`
|
|
136
|
+
|
|
137
|
+
Call once **before** `dash.Dash()`. Registers CDN script, layout stores, and clientside callbacks via Dash hooks.
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
store_ids = dwb.add_discord_crate(
|
|
141
|
+
server, # Required — Discord server ID
|
|
142
|
+
channel="", # Default channel ID
|
|
143
|
+
color="#5865f2", # Button background color
|
|
144
|
+
location=["bottom", "right"], # ["top"|"bottom", "left"|"right"]
|
|
145
|
+
glyph=("", ""), # Custom icon (open_url, closed_url)
|
|
146
|
+
css="", # Extra CSS injected into embed
|
|
147
|
+
notifications=True, # Enable message notifications
|
|
148
|
+
dm_notifications=True, # Enable DM notifications
|
|
149
|
+
indicator=True, # Show unread indicator dot
|
|
150
|
+
timeout=10000, # Notification display duration (ms)
|
|
151
|
+
defer=False, # Delay Crate init until first interaction
|
|
152
|
+
prefix="", # Namespace for multiple Crate instances
|
|
153
|
+
pages=None, # List of paths where Crate is visible
|
|
154
|
+
)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Returns** a `dict` of store IDs: `config`, `command`, `event`, `message`, `user`, `status`.
|
|
158
|
+
|
|
159
|
+
### Command helpers
|
|
160
|
+
|
|
161
|
+
All helpers return a dict intended to be stored in the `command` store:
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
# Open / close the popup
|
|
165
|
+
dwb.crate_toggle(True) # open
|
|
166
|
+
dwb.crate_toggle(False) # close
|
|
167
|
+
dwb.crate_toggle() # toggle current state
|
|
168
|
+
|
|
169
|
+
# Show a notification bubble
|
|
170
|
+
dwb.crate_notify("Hello!", timeout=5000, avatar="https://...")
|
|
171
|
+
|
|
172
|
+
# Navigate to a different channel
|
|
173
|
+
dwb.crate_navigate("355719584830980096")
|
|
174
|
+
|
|
175
|
+
# Hide / show the entire Crate button
|
|
176
|
+
dwb.crate_hide()
|
|
177
|
+
dwb.crate_show()
|
|
178
|
+
|
|
179
|
+
# Update appearance at runtime
|
|
180
|
+
dwb.crate_update_options(color="#ed4245", location=["top", "left"])
|
|
181
|
+
dwb.crate_set_color("--color-accent", "#ed4245")
|
|
182
|
+
|
|
183
|
+
# Send a message on behalf of the signed-in user
|
|
184
|
+
dwb.crate_send_message("Hello from Dash!")
|
|
185
|
+
|
|
186
|
+
# Raw embed-api command
|
|
187
|
+
dwb.crate_emit("navigate", {"guild": "...", "channel": "..."})
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Reading events
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
from dash import Input, callback
|
|
194
|
+
|
|
195
|
+
@callback(
|
|
196
|
+
Output("last-message", "children"),
|
|
197
|
+
Input(dwb.STORE_IDS["message"], "data"),
|
|
198
|
+
)
|
|
199
|
+
def on_message(data):
|
|
200
|
+
if not data:
|
|
201
|
+
return "No messages yet"
|
|
202
|
+
return f"{data['author']['username']}: {data['content']}"
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Available stores:
|
|
206
|
+
|
|
207
|
+
| Store key | Fires when | Payload keys |
|
|
208
|
+
|-----------|-----------|--------------|
|
|
209
|
+
| `event` | signIn, signOut, sentMessage, toggle, ready, … | `type`, `_ts`, event-specific fields |
|
|
210
|
+
| `message` | A message is received in the channel | `content`, `author`, `channel`, `channel_id` |
|
|
211
|
+
| `user` | User signs in or out | `username`, `id`, `avatar`, `signed_in` |
|
|
212
|
+
| `status` | Crate opens/closes | `initialized`, `open` |
|
|
213
|
+
|
|
214
|
+
### Multiple Crate instances
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
# Register with a unique prefix
|
|
218
|
+
support_ids = dwb.add_discord_crate(
|
|
219
|
+
server="...", channel="...",
|
|
220
|
+
color="#ed4245", location=["top", "right"],
|
|
221
|
+
prefix="support",
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Use prefix-specific store IDs
|
|
225
|
+
support_store_ids = dwb.get_crate_store_ids("support")
|
|
226
|
+
|
|
227
|
+
# Command helpers accept prefix too
|
|
228
|
+
dwb.crate_toggle(True, prefix="support")
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## DiscordWidget
|
|
234
|
+
|
|
235
|
+
### `add_discord_widget()`
|
|
236
|
+
|
|
237
|
+
Call once **before** `dash.Dash()`. Registers layout stores and a `window.postMessage` listener via Dash hooks. No CDN script is loaded.
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
widget_ids = dwb.add_discord_widget(
|
|
241
|
+
server, # Required — Discord server ID
|
|
242
|
+
channel="", # Default channel ID
|
|
243
|
+
width="100%",
|
|
244
|
+
height="600px",
|
|
245
|
+
container_id="widgetbot-container", # Must match discord_widget_container()
|
|
246
|
+
)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### `discord_widget_container()`
|
|
250
|
+
|
|
251
|
+
Place in your layout wherever the inline widget should appear:
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
dwb.discord_widget_container(
|
|
255
|
+
server="299881420891881473",
|
|
256
|
+
channel="355719584830980096",
|
|
257
|
+
width="100%",
|
|
258
|
+
height="600px",
|
|
259
|
+
container_id="widgetbot-container", # Must match add_discord_widget()
|
|
260
|
+
)
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Widget events
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
widget_ids = dwb.get_widget_store_ids("widgetbot-container")
|
|
267
|
+
|
|
268
|
+
@callback(
|
|
269
|
+
Output("widget-events", "children"),
|
|
270
|
+
Input(widget_ids["event"], "data"),
|
|
271
|
+
)
|
|
272
|
+
def on_widget_event(data):
|
|
273
|
+
if not data:
|
|
274
|
+
return "No events yet"
|
|
275
|
+
return f"Event: {data['type']}"
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## Optional Features
|
|
281
|
+
|
|
282
|
+
### Slash Commands (Discord Interactions Endpoint)
|
|
283
|
+
|
|
284
|
+
Requires `[bot]` extra, a public HTTPS URL (e.g. ngrok), and a registered Discord application.
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
import dash_widgetbot as dwb
|
|
288
|
+
|
|
289
|
+
dwb.add_discord_interactions(
|
|
290
|
+
public_key="your_discord_public_key_hex",
|
|
291
|
+
application_id="your_app_id",
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
@dwb.register_command("ask")
|
|
295
|
+
def handle_ask(interaction):
|
|
296
|
+
question = interaction["data"]["options"][0]["value"]
|
|
297
|
+
return f"You asked: {question}"
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Register the endpoint in the Discord Developer Portal:
|
|
301
|
+
```
|
|
302
|
+
Interactions Endpoint URL → https://yourdomain.com/api/discord/interactions
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Automatically sync at startup using the ngrok auto-detect:
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
dwb.sync_discord_endpoint() # detects ngrok or reads INTERACTIONS_URL env var
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Discord Components V2
|
|
312
|
+
|
|
313
|
+
Build rich Discord messages with the full Components V2 builder library:
|
|
314
|
+
|
|
315
|
+
```python
|
|
316
|
+
from dash_widgetbot.components import container, text_display, button, action_row
|
|
317
|
+
|
|
318
|
+
payload = container(
|
|
319
|
+
text_display("## Hello from Dash!"),
|
|
320
|
+
action_row(
|
|
321
|
+
button("Visit App", url="https://your-app.com"),
|
|
322
|
+
),
|
|
323
|
+
color=0x5865f2,
|
|
324
|
+
)
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Structured AI Responses (Gemini)
|
|
328
|
+
|
|
329
|
+
Requires `[ai]` extra and `GEMINI_API_KEY` env var.
|
|
330
|
+
|
|
331
|
+
```python
|
|
332
|
+
result = dwb.generate_structured_response("What is this app?")
|
|
333
|
+
ai_response = result["response"] # AIResponse Pydantic model
|
|
334
|
+
|
|
335
|
+
# Convert to Discord Components V2 payload
|
|
336
|
+
discord_payload = dwb.build_components_v2(ai_response)
|
|
337
|
+
|
|
338
|
+
# Or render as Dash components (Discord dark preview)
|
|
339
|
+
dash_preview = dwb.render_discord_preview(ai_response)
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
`AIResponse` supports: `title`, `color`, `components` (text, section, gallery, button_row, separator blocks), `footer`, `image_prompt`, `actions`, and `sources` (from Google Search grounding).
|
|
343
|
+
|
|
344
|
+
### Multi-Format AI Generation (`/gen`)
|
|
345
|
+
|
|
346
|
+
```python
|
|
347
|
+
result = dwb.generate_gen_response("Explain Python async/await")
|
|
348
|
+
gen_response = result["response"] # GenResponse Pydantic model
|
|
349
|
+
|
|
350
|
+
# Render as a styled DMC card
|
|
351
|
+
from dash_widgetbot.gen_renderer import render_gen_card
|
|
352
|
+
card = render_gen_card(gen_entry)
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Supported formats: `article`, `code`, `data_table`, `image`, `callout`.
|
|
356
|
+
|
|
357
|
+
### Per-User Private AI Threads
|
|
358
|
+
|
|
359
|
+
When `AI_THREAD_PARENT_CHANNEL` is set, Discord AI commands (`/ai`, `/ask`, `/gen`) automatically route responses to a private thread per user:
|
|
360
|
+
|
|
361
|
+
```dotenv
|
|
362
|
+
AI_THREAD_PARENT_CHANNEL=your_text_channel_id
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
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`.
|
|
366
|
+
|
|
367
|
+
### Real-Time Transport (`[realtime]`)
|
|
368
|
+
|
|
369
|
+
Requires `[realtime]` extra. Adds Socket.IO alongside the always-active store bridge for zero-latency server → client pushes.
|
|
370
|
+
|
|
371
|
+
```python
|
|
372
|
+
from flask_socketio import SocketIO
|
|
373
|
+
from dash_widgetbot import configure_socketio
|
|
374
|
+
|
|
375
|
+
_socketio = SocketIO(app.server, async_mode='threading', cors_allowed_origins="*")
|
|
376
|
+
configure_socketio(_socketio)
|
|
377
|
+
|
|
378
|
+
# Push a command to all connected clients from a background thread
|
|
379
|
+
from dash_widgetbot import emit_command
|
|
380
|
+
emit_command(dwb.crate_notify("Job finished!"))
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Progress Tracking
|
|
384
|
+
|
|
385
|
+
`ProgressTracker` fans out real-time progress updates to multiple sinks during long-running AI generation:
|
|
386
|
+
|
|
387
|
+
```python
|
|
388
|
+
from dash_widgetbot.progress import ProgressTracker, SocketIOSink, EphemeralSink
|
|
389
|
+
|
|
390
|
+
tracker = ProgressTracker(sinks=[SocketIOSink(), EphemeralSink(app_id, token)])
|
|
391
|
+
result = dwb.generate_gen_response(prompt, on_progress=tracker.stream_callback())
|
|
392
|
+
tracker.close()
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
Progress phases: `analyzing` → `generating` (10–80%) → `parsing` → `creating_image` → `posting` → `complete`.
|
|
396
|
+
|
|
397
|
+
### Outbound Webhooks
|
|
398
|
+
|
|
399
|
+
```python
|
|
400
|
+
dwb.send_webhook_message(
|
|
401
|
+
content="Deployed successfully!",
|
|
402
|
+
webhook_url="https://discord.com/api/webhooks/...",
|
|
403
|
+
username="Dash Bot",
|
|
404
|
+
)
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Action Tag Parser
|
|
408
|
+
|
|
409
|
+
Embed action tags in any text (e.g. AI responses, slash command replies):
|
|
410
|
+
|
|
411
|
+
```python
|
|
412
|
+
text = "Go here [ACTION:navigate:/reports] or [ACTION:notify:Done!]"
|
|
413
|
+
|
|
414
|
+
actions = dwb.parse_actions(text)
|
|
415
|
+
# [{"type": "navigate", "data": "/reports"}, {"type": "notify", "data": "Done!"}]
|
|
416
|
+
|
|
417
|
+
clean = dwb.strip_actions(text)
|
|
418
|
+
# "Go here or "
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
Valid actions: `navigate`, `notify`, `toggle`, `hide`, `show`, `open_url`
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## Environment Variables
|
|
426
|
+
|
|
427
|
+
```dotenv
|
|
428
|
+
# WidgetBot embed
|
|
429
|
+
WIDGETBOT_SERVER=your_server_id
|
|
430
|
+
WIDGETBOT_CHANNEL=your_channel_id
|
|
431
|
+
WIDGETBOT_SHARD= # empty = free tier; https://e-business.widgetbot.co for paid
|
|
432
|
+
|
|
433
|
+
# Discord Bot (required for slash commands)
|
|
434
|
+
DISCORD_APPLICATION_ID=
|
|
435
|
+
DISCORD_PUBLIC_KEY=
|
|
436
|
+
DISCORD_BOT_TOKEN=
|
|
437
|
+
DISCORD_WEBHOOK_URL=
|
|
438
|
+
DISCORD_GUILD_ID= # guild for slash command registration (empty = global)
|
|
439
|
+
|
|
440
|
+
# Interactions endpoint URL (empty = ngrok auto-detect)
|
|
441
|
+
INTERACTIONS_URL=
|
|
442
|
+
|
|
443
|
+
# Gemini AI
|
|
444
|
+
GEMINI_API_KEY=
|
|
445
|
+
GEMINI_MODEL= # default: gemini-2.0-flash
|
|
446
|
+
GEMINI_IMAGE_API_KEY= # falls back to GEMINI_API_KEY
|
|
447
|
+
GEMINI_IMAGE_MODEL= # default: gemini-2.0-flash-exp-image-generation
|
|
448
|
+
GEMINI_SEARCH_GROUNDING= # default: true; set to "false" to disable
|
|
449
|
+
|
|
450
|
+
# Private AI Threads (optional)
|
|
451
|
+
AI_THREAD_PARENT_CHANNEL= # channel ID; enables per-user private threads
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
Use `python-dotenv` to load them:
|
|
455
|
+
|
|
456
|
+
```python
|
|
457
|
+
from dotenv import load_dotenv
|
|
458
|
+
load_dotenv()
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## Architecture
|
|
464
|
+
|
|
465
|
+
```
|
|
466
|
+
Python callback → dcc.Store (command) → clientside_callback → Crate API
|
|
467
|
+
Crate events → set_props() → dcc.Store (events) → Python callback
|
|
468
|
+
Widget iframe → window.postMessage → set_props() → dcc.Store (events)
|
|
469
|
+
|
|
470
|
+
[realtime] additive path:
|
|
471
|
+
server side → emit_command() → Socket.IO → Crate API (direct)
|
|
472
|
+
gen_store.add() → socketio.emit() → DashSocketIO prop → Dash callback
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
Key design decisions:
|
|
476
|
+
|
|
477
|
+
- **No build toolchain** — pure Python + inline JS via Dash hooks
|
|
478
|
+
- **Store bridge always active** — `dcc.Store` carries all commands and events; Socket.IO is purely additive
|
|
479
|
+
- **`set_props()`** — async event push from JS to Dash stores without callback returns
|
|
480
|
+
- **CDN-only** — WidgetBot JS loaded from jsDelivr; widget uses a plain cross-origin `<iframe>`
|
|
481
|
+
- **Namespaced IDs** — all store IDs prefixed with `_widgetbot-` to avoid collisions
|
|
482
|
+
- **Non-blocking sinks** — Discord API calls for progress edits fire in daemon threads; generation is never blocked by cosmetic channel edits
|
|
483
|
+
|
|
484
|
+
---
|
|
485
|
+
|
|
486
|
+
## Example App
|
|
487
|
+
|
|
488
|
+
Clone the repo and run the included 13-page example application:
|
|
489
|
+
|
|
490
|
+
```bash
|
|
491
|
+
git clone https://github.com/pip-install-python/dash-widgetbot
|
|
492
|
+
cd dash-widgetbot
|
|
493
|
+
pip install -e ".[all]"
|
|
494
|
+
cp .env.example .env # fill in your server/channel IDs and API keys
|
|
495
|
+
python app.py
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
Open `http://127.0.0.1:8150`. Pages cover every feature:
|
|
499
|
+
|
|
500
|
+
| Page | What it shows |
|
|
501
|
+
|------|---------------|
|
|
502
|
+
| Home | Overview and quick-start |
|
|
503
|
+
| Crate Commands | toggle, notify, navigate, hide/show |
|
|
504
|
+
| Crate Events | live event log, last message, user status |
|
|
505
|
+
| Crate Styling | runtime color, position, glyph, embed colors |
|
|
506
|
+
| Widget Embed | inline iframe with event display |
|
|
507
|
+
| Multi-Instance | two additional named Crate instances |
|
|
508
|
+
| Bot Bridge | action tag parsing and execution sandbox |
|
|
509
|
+
| Slash Commands | interactions setup guide + local /ask test |
|
|
510
|
+
| AI Chat | Gemini structured responses with Discord preview |
|
|
511
|
+
| Webhook Send | outbound webhook composer |
|
|
512
|
+
| Rich Messages | Components V2 message builder |
|
|
513
|
+
| Rich Message Preview | live Components V2 visual builder |
|
|
514
|
+
| Gen Gallery | real-time feed of Discord `/gen` and `/ai` results |
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
## Requirements
|
|
519
|
+
|
|
520
|
+
| Requirement | Version |
|
|
521
|
+
|-------------|---------|
|
|
522
|
+
| Python | ≥ 3.11 |
|
|
523
|
+
| Dash | ≥ 3.0.3 |
|
|
524
|
+
| WidgetBot account | Free tier available at [widgetbot.io](https://widgetbot.io) |
|
|
525
|
+
|
|
526
|
+
---
|
|
527
|
+
|
|
528
|
+
## License
|
|
529
|
+
|
|
530
|
+
MIT — see [LICENSE](LICENSE) for details.
|
|
531
|
+
|
|
532
|
+
**Pip Install Python LLC** — [pip-install-python.com](https://pip-install-python.com)
|