dash-ai-chatbot 1.0.0__py3-none-any.whl
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_ai_chatbot/__init__.py +70 -0
- dash_ai_chatbot/_core.py +144 -0
- dash_ai_chatbot/_hooks.py +25 -0
- dash_ai_chatbot-1.0.0.dist-info/METADATA +128 -0
- dash_ai_chatbot-1.0.0.dist-info/RECORD +9 -0
- dash_ai_chatbot-1.0.0.dist-info/WHEEL +5 -0
- dash_ai_chatbot-1.0.0.dist-info/entry_points.txt +2 -0
- dash_ai_chatbot-1.0.0.dist-info/licenses/LICENSE +21 -0
- dash_ai_chatbot-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Asyntai AI chat assistant for Plotly Dash apps.
|
|
2
|
+
|
|
3
|
+
Usage::
|
|
4
|
+
|
|
5
|
+
from dash import Dash, html
|
|
6
|
+
import dash_ai_chatbot
|
|
7
|
+
|
|
8
|
+
app = Dash(__name__)
|
|
9
|
+
app.layout = html.Div('Hello')
|
|
10
|
+
dash_ai_chatbot.init_app(app, widget_id='asyntai_ab12cd34')
|
|
11
|
+
|
|
12
|
+
Or, on Dash 3 and later, set ``ASYNTAI_WIDGET_ID`` in the environment and
|
|
13
|
+
change no code at all.
|
|
14
|
+
"""
|
|
15
|
+
from ._core import (
|
|
16
|
+
DEFAULT_SCRIPT_URL,
|
|
17
|
+
clean_script_url,
|
|
18
|
+
clean_widget_id,
|
|
19
|
+
insert_widget,
|
|
20
|
+
widget_snippet,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__version__ = '1.0.0'
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
'DEFAULT_SCRIPT_URL',
|
|
27
|
+
'clean_script_url',
|
|
28
|
+
'clean_widget_id',
|
|
29
|
+
'init_app',
|
|
30
|
+
'insert_widget',
|
|
31
|
+
'widget_snippet',
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
_FLAG = '_asyntai_widget_settings'
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def init_app(app, widget_id=None, script_url=None):
|
|
38
|
+
"""Add the chat widget to every page of a Dash app.
|
|
39
|
+
|
|
40
|
+
``widget_id`` is the ID from the Install page of the Asyntai dashboard,
|
|
41
|
+
or the whole snippet copied from there. When it is left out, the
|
|
42
|
+
``ASYNTAI_WIDGET_ID`` environment variable is read each time the page is
|
|
43
|
+
built. ``script_url`` is only for a case where Asyntai support asks you
|
|
44
|
+
to change it.
|
|
45
|
+
|
|
46
|
+
Calling it twice on the same app only updates the values. The app is
|
|
47
|
+
returned, so the call can be chained.
|
|
48
|
+
"""
|
|
49
|
+
if not hasattr(app, 'interpolate_index'):
|
|
50
|
+
raise TypeError('init_app() needs a dash.Dash app.')
|
|
51
|
+
|
|
52
|
+
settings = {'widget_id': widget_id, 'script_url': script_url}
|
|
53
|
+
|
|
54
|
+
if getattr(app, _FLAG, None) is not None:
|
|
55
|
+
getattr(app, _FLAG).update(settings)
|
|
56
|
+
return app
|
|
57
|
+
|
|
58
|
+
setattr(app, _FLAG, settings)
|
|
59
|
+
original = app.interpolate_index
|
|
60
|
+
|
|
61
|
+
def interpolate_index(**kwargs):
|
|
62
|
+
page = original(**kwargs)
|
|
63
|
+
current = getattr(app, _FLAG)
|
|
64
|
+
return insert_widget(page, current['widget_id'], current['script_url'])
|
|
65
|
+
|
|
66
|
+
# Wrapping the instance method works on Dash 2, 3 and 4, and it also
|
|
67
|
+
# works when the app sets its own index_string or overrides
|
|
68
|
+
# interpolate_index in a subclass.
|
|
69
|
+
app.interpolate_index = interpolate_index
|
|
70
|
+
return app
|
dash_ai_chatbot/_core.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Everything the package decides about the widget lives here.
|
|
2
|
+
|
|
3
|
+
Dash serves one HTML page for the whole app, built by ``Dash.interpolate_index``
|
|
4
|
+
and then passed through the ``index`` hooks. The widget goes into that page
|
|
5
|
+
once, just before the closing body tag, so it is on every view of the app,
|
|
6
|
+
including multi-page apps built with ``dash.page_registry``.
|
|
7
|
+
"""
|
|
8
|
+
import html
|
|
9
|
+
import os
|
|
10
|
+
import re
|
|
11
|
+
|
|
12
|
+
DEFAULT_SCRIPT_URL = 'https://widget.asyntai.com/static/js/chat-widget.js'
|
|
13
|
+
|
|
14
|
+
#: Environment variables read when no value is passed in code.
|
|
15
|
+
ENV_WIDGET_ID = 'ASYNTAI_WIDGET_ID'
|
|
16
|
+
ENV_SCRIPT_URL = 'ASYNTAI_SCRIPT_URL'
|
|
17
|
+
|
|
18
|
+
#: Marker that shows the widget is already on the page.
|
|
19
|
+
MARKER = 'asyntai-widget-config'
|
|
20
|
+
|
|
21
|
+
#: Shape of a widget identifier from the Asyntai dashboard.
|
|
22
|
+
ID_PATTERN = re.compile(r'^asyntai_[A-Za-z0-9]{6,64}$')
|
|
23
|
+
|
|
24
|
+
#: The identifier inside a pasted snippet.
|
|
25
|
+
SNIPPET_PATTERN = re.compile(r'data-asyntai-id\s*=\s*["\']([^"\']+)["\']')
|
|
26
|
+
|
|
27
|
+
_HTTPS_PATTERN = re.compile(r'^https://[^\s"\'<>]+$', re.IGNORECASE)
|
|
28
|
+
|
|
29
|
+
BODY_CLOSE = '</body>'
|
|
30
|
+
|
|
31
|
+
_LOADER = """<div class="asyntai-widget-config" data-asyntai-widget="{widget_id}" data-asyntai-src="{script_url}" style="display: none"></div>
|
|
32
|
+
<script>
|
|
33
|
+
// Loads the Asyntai chat widget after the page has finished loading, so
|
|
34
|
+
// the Dash app draws first. The two values arrive on data attributes,
|
|
35
|
+
// because nothing is ever written into the script itself.
|
|
36
|
+
(function () {{
|
|
37
|
+
var config = document.querySelector('.asyntai-widget-config');
|
|
38
|
+
if (!config) {{ return; }}
|
|
39
|
+
|
|
40
|
+
var widgetId = config.getAttribute('data-asyntai-widget') || '';
|
|
41
|
+
var scriptUrl = config.getAttribute('data-asyntai-src') || '';
|
|
42
|
+
if (!widgetId || scriptUrl.indexOf('https://') !== 0) {{ return; }}
|
|
43
|
+
|
|
44
|
+
var load = function () {{
|
|
45
|
+
if (document.querySelector('script[data-asyntai-id]')) {{ return; }}
|
|
46
|
+
|
|
47
|
+
var script = document.createElement('script');
|
|
48
|
+
script.src = scriptUrl;
|
|
49
|
+
script.async = true;
|
|
50
|
+
script.setAttribute('data-asyntai-id', widgetId);
|
|
51
|
+
document.head.appendChild(script);
|
|
52
|
+
}};
|
|
53
|
+
|
|
54
|
+
if (document.readyState === 'complete') {{
|
|
55
|
+
load();
|
|
56
|
+
}} else {{
|
|
57
|
+
window.addEventListener('load', load);
|
|
58
|
+
}}
|
|
59
|
+
}}());
|
|
60
|
+
</script>
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def clean_widget_id(raw):
|
|
65
|
+
"""Return a usable widget identifier, or an empty string.
|
|
66
|
+
|
|
67
|
+
Accepts a bare identifier or the whole snippet from the Asyntai
|
|
68
|
+
dashboard, because people paste the snippet. An empty string means the
|
|
69
|
+
chat stays off.
|
|
70
|
+
"""
|
|
71
|
+
if not isinstance(raw, str):
|
|
72
|
+
return ''
|
|
73
|
+
|
|
74
|
+
value = raw.strip()
|
|
75
|
+
if not value:
|
|
76
|
+
return ''
|
|
77
|
+
|
|
78
|
+
match = SNIPPET_PATTERN.search(value)
|
|
79
|
+
if match:
|
|
80
|
+
value = match.group(1).strip()
|
|
81
|
+
|
|
82
|
+
return value if ID_PATTERN.match(value) else ''
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def clean_script_url(raw):
|
|
86
|
+
"""Return the script address, or an empty string when the value is bad.
|
|
87
|
+
|
|
88
|
+
Only an https address is accepted, so a bad value can never put a
|
|
89
|
+
javascript: URL on the page. None or an empty value means the Asyntai
|
|
90
|
+
address.
|
|
91
|
+
"""
|
|
92
|
+
if raw is None:
|
|
93
|
+
return DEFAULT_SCRIPT_URL
|
|
94
|
+
if not isinstance(raw, str):
|
|
95
|
+
return ''
|
|
96
|
+
|
|
97
|
+
value = raw.strip()
|
|
98
|
+
if not value:
|
|
99
|
+
return DEFAULT_SCRIPT_URL
|
|
100
|
+
|
|
101
|
+
return value if _HTTPS_PATTERN.match(value) else ''
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def widget_snippet(widget_id, script_url=None):
|
|
105
|
+
"""Return the HTML that loads the widget, or an empty string.
|
|
106
|
+
|
|
107
|
+
The empty string comes back when the identifier or the address is not
|
|
108
|
+
valid, so a typo leaves the app as it was instead of breaking it.
|
|
109
|
+
"""
|
|
110
|
+
clean_id = clean_widget_id(widget_id)
|
|
111
|
+
clean_url = clean_script_url(script_url)
|
|
112
|
+
if not clean_id or not clean_url:
|
|
113
|
+
return ''
|
|
114
|
+
|
|
115
|
+
return _LOADER.format(
|
|
116
|
+
widget_id=html.escape(clean_id, quote=True),
|
|
117
|
+
script_url=html.escape(clean_url, quote=True),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def insert_widget(page, widget_id=None, script_url=None):
|
|
122
|
+
"""Return the page with the widget added before the closing body tag.
|
|
123
|
+
|
|
124
|
+
Values passed in come first; missing ones are read from the environment
|
|
125
|
+
at the moment the page is built, so a deployment can change the ID
|
|
126
|
+
without a code change. The page is returned unchanged when there is no
|
|
127
|
+
valid ID, when it has no body tag, or when the widget is already on it.
|
|
128
|
+
"""
|
|
129
|
+
if not isinstance(page, str) or MARKER in page:
|
|
130
|
+
return page
|
|
131
|
+
|
|
132
|
+
if widget_id is None:
|
|
133
|
+
widget_id = os.environ.get(ENV_WIDGET_ID, '')
|
|
134
|
+
if script_url is None:
|
|
135
|
+
script_url = os.environ.get(ENV_SCRIPT_URL) or None
|
|
136
|
+
|
|
137
|
+
snippet = widget_snippet(widget_id, script_url)
|
|
138
|
+
if not snippet or BODY_CLOSE not in page:
|
|
139
|
+
return page
|
|
140
|
+
|
|
141
|
+
# Split on the last closing tag, so a page that prints the tag inside an
|
|
142
|
+
# example or a code block is left alone.
|
|
143
|
+
head, _, tail = page.rpartition(BODY_CLOSE)
|
|
144
|
+
return head + snippet + BODY_CLOSE + tail
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Zero-code mode for Dash 3 and later.
|
|
2
|
+
|
|
3
|
+
Dash loads every module named in a ``dash_hooks`` entry point when an app
|
|
4
|
+
starts. This one registers an ``index`` hook, so the widget appears on every
|
|
5
|
+
Dash app on the machine once ``ASYNTAI_WIDGET_ID`` is set, with no code
|
|
6
|
+
change. Without that variable the hook leaves every page as it was.
|
|
7
|
+
|
|
8
|
+
Older Dash versions have no hooks; there ``init_app`` does the same job.
|
|
9
|
+
"""
|
|
10
|
+
from ._core import insert_widget
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from dash import hooks
|
|
14
|
+
except ImportError: # Dash 2 has no hooks module.
|
|
15
|
+
hooks = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _add_widget(page):
|
|
19
|
+
# Only the environment is read here. An app that called init_app() has
|
|
20
|
+
# already got the widget, and insert_widget() sees the marker and stops.
|
|
21
|
+
return insert_widget(page)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
if hooks is not None and hasattr(hooks, 'index'):
|
|
25
|
+
hooks.index()(_add_widget)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dash-ai-chatbot
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Adds the Asyntai AI chat assistant to a Plotly Dash app. It answers visitor questions from your own content, in more than 80 languages.
|
|
5
|
+
Author-email: Asyntai <hello@asyntai.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://asyntai.com
|
|
8
|
+
Project-URL: Documentation, https://asyntai.com/documentation/integrations/plotly-dash/
|
|
9
|
+
Project-URL: Source, https://github.com/asyntai/dash-ai-chatbot
|
|
10
|
+
Project-URL: Issues, https://github.com/asyntai/dash-ai-chatbot/issues
|
|
11
|
+
Keywords: dash,plotly,plotly-dash,chatbot,ai,ai-chatbot,live-chat,customer-support,assistant,widget
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Environment :: Web Environment
|
|
14
|
+
Classifier: Framework :: Dash
|
|
15
|
+
Classifier: Framework :: Flask
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: dash>=2.0
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# dash-ai-chatbot
|
|
31
|
+
|
|
32
|
+
Adds the [Asyntai](https://asyntai.com) AI chat assistant to a
|
|
33
|
+
[Plotly Dash](https://dash.plotly.com) app. The assistant answers visitor
|
|
34
|
+
questions from your own content, day and night, in more than 80 languages.
|
|
35
|
+
|
|
36
|
+
Free and open source. Works on every Asyntai plan, including the free plan.
|
|
37
|
+
|
|
38
|
+
- Requires Dash 2.0 or newer and Python 3.10 or newer
|
|
39
|
+
- Documentation: https://asyntai.com/documentation/integrations/plotly-dash/
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
pip install dash-ai-chatbot
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Add one line after you create the app:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from dash import Dash, html
|
|
51
|
+
import dash_ai_chatbot
|
|
52
|
+
|
|
53
|
+
app = Dash(__name__)
|
|
54
|
+
app.layout = html.Div("Hello")
|
|
55
|
+
|
|
56
|
+
dash_ai_chatbot.init_app(app, widget_id="asyntai_ab12cd34")
|
|
57
|
+
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
app.run()
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
You get the ID from the Install page of your
|
|
63
|
+
[Asyntai dashboard](https://asyntai.com/dashboard). You can paste the whole
|
|
64
|
+
snippet; the package keeps only the ID.
|
|
65
|
+
|
|
66
|
+
That is all. The chat button appears on every page of the app, including
|
|
67
|
+
multi-page apps.
|
|
68
|
+
|
|
69
|
+
## No code change (Dash 3 and later)
|
|
70
|
+
|
|
71
|
+
On Dash 3 and later the package also works with no code change. Install it and
|
|
72
|
+
set one environment variable:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
ASYNTAI_WIDGET_ID=asyntai_ab12cd34
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Every Dash app that runs with that variable gets the chat. Without the
|
|
79
|
+
variable the package does nothing.
|
|
80
|
+
|
|
81
|
+
## Keep the ID out of the code
|
|
82
|
+
|
|
83
|
+
Leave out `widget_id` and the package reads `ASYNTAI_WIDGET_ID` each time the
|
|
84
|
+
page is built:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
dash_ai_chatbot.init_app(app)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Options
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
dash_ai_chatbot.init_app(
|
|
94
|
+
app,
|
|
95
|
+
widget_id="asyntai_ab12cd34", # or the whole snippet; empty or invalid = chat off
|
|
96
|
+
script_url=None, # change only if Asyntai support asks you to
|
|
97
|
+
)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`script_url` can also come from the `ASYNTAI_SCRIPT_URL` environment variable.
|
|
101
|
+
Only an `https` address is accepted.
|
|
102
|
+
|
|
103
|
+
## How it works
|
|
104
|
+
|
|
105
|
+
Dash serves one HTML page for the whole app. The package puts a small script
|
|
106
|
+
just before the closing body tag of that page. The script waits for the page to
|
|
107
|
+
finish loading, then fetches the chat widget from Asyntai, so your app draws
|
|
108
|
+
first and its speed is unchanged.
|
|
109
|
+
|
|
110
|
+
The widget ID and the script address travel to the browser on a data
|
|
111
|
+
attribute, never inside the script. The package works with a custom
|
|
112
|
+
`index_string`, with a subclass that overrides `interpolate_index`, and with
|
|
113
|
+
`url_base_pathname`. It never adds the widget twice.
|
|
114
|
+
|
|
115
|
+
## Tests
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
pip install -e . pytest
|
|
119
|
+
pytest
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Support
|
|
123
|
+
|
|
124
|
+
hello@asyntai.com
|
|
125
|
+
|
|
126
|
+
## Licence
|
|
127
|
+
|
|
128
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
dash_ai_chatbot/__init__.py,sha256=EEIEG-_1V0Wv3xFYz9XrY0fgn0fs_v3ELozfVPppkAM,2018
|
|
2
|
+
dash_ai_chatbot/_core.py,sha256=aGnSgJy3oLetM1bpYF8ICZds2oscTbgW2RmXRqPe-6k,4890
|
|
3
|
+
dash_ai_chatbot/_hooks.py,sha256=oTk6Mz0fwcK2n5x7fWX-KiUYwya8Sas1v48IwHm3emg,835
|
|
4
|
+
dash_ai_chatbot-1.0.0.dist-info/licenses/LICENSE,sha256=jGsodKzosaT2Pr7P3ahTkLrMTHQ6uvf111OMRXeGYUM,1064
|
|
5
|
+
dash_ai_chatbot-1.0.0.dist-info/METADATA,sha256=F5Ui67eDLFKVYbDRrSGLSqEJTdWLsFpHn8OALOFqBes,3961
|
|
6
|
+
dash_ai_chatbot-1.0.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
7
|
+
dash_ai_chatbot-1.0.0.dist-info/entry_points.txt,sha256=0ZmnJjX2h3TFKlFRNVSuyeYrWNAxZ1Uj_QgDEgGc1mE,54
|
|
8
|
+
dash_ai_chatbot-1.0.0.dist-info/top_level.txt,sha256=ZMDPPvwvSe1ixS3BS-_qYgGVI8mspvau9wKnNvaAHEE,16
|
|
9
|
+
dash_ai_chatbot-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Asyntai
|
|
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 @@
|
|
|
1
|
+
dash_ai_chatbot
|