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.
Files changed (31) hide show
  1. dash_widgetbot-0.4.1/LICENSE +21 -0
  2. dash_widgetbot-0.4.1/PKG-INFO +532 -0
  3. dash_widgetbot-0.4.1/README.md +492 -0
  4. dash_widgetbot-0.4.1/dash_widgetbot/__init__.py +266 -0
  5. dash_widgetbot-0.4.1/dash_widgetbot/_bridge.py +114 -0
  6. dash_widgetbot-0.4.1/dash_widgetbot/_constants.py +98 -0
  7. dash_widgetbot-0.4.1/dash_widgetbot/_transport.py +36 -0
  8. dash_widgetbot-0.4.1/dash_widgetbot/action_parser.py +61 -0
  9. dash_widgetbot-0.4.1/dash_widgetbot/ai_builder.py +142 -0
  10. dash_widgetbot-0.4.1/dash_widgetbot/ai_image.py +91 -0
  11. dash_widgetbot-0.4.1/dash_widgetbot/ai_responder.py +352 -0
  12. dash_widgetbot-0.4.1/dash_widgetbot/ai_schemas.py +100 -0
  13. dash_widgetbot-0.4.1/dash_widgetbot/components.py +635 -0
  14. dash_widgetbot-0.4.1/dash_widgetbot/crate.py +435 -0
  15. dash_widgetbot-0.4.1/dash_widgetbot/gen_renderer.py +288 -0
  16. dash_widgetbot-0.4.1/dash_widgetbot/gen_responder.py +248 -0
  17. dash_widgetbot-0.4.1/dash_widgetbot/gen_schemas.py +70 -0
  18. dash_widgetbot-0.4.1/dash_widgetbot/gen_store.py +105 -0
  19. dash_widgetbot-0.4.1/dash_widgetbot/interactions.py +857 -0
  20. dash_widgetbot-0.4.1/dash_widgetbot/preview.py +167 -0
  21. dash_widgetbot-0.4.1/dash_widgetbot/progress.py +373 -0
  22. dash_widgetbot-0.4.1/dash_widgetbot/threads.py +155 -0
  23. dash_widgetbot-0.4.1/dash_widgetbot/webhook.py +108 -0
  24. dash_widgetbot-0.4.1/dash_widgetbot/widget.py +196 -0
  25. dash_widgetbot-0.4.1/dash_widgetbot.egg-info/PKG-INFO +532 -0
  26. dash_widgetbot-0.4.1/dash_widgetbot.egg-info/SOURCES.txt +29 -0
  27. dash_widgetbot-0.4.1/dash_widgetbot.egg-info/dependency_links.txt +1 -0
  28. dash_widgetbot-0.4.1/dash_widgetbot.egg-info/requires.txt +21 -0
  29. dash_widgetbot-0.4.1/dash_widgetbot.egg-info/top_level.txt +1 -0
  30. dash_widgetbot-0.4.1/pyproject.toml +41 -0
  31. 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)