db-chat-widget 0.1.0__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 (37) hide show
  1. db_chat_widget-0.1.0/.github/workflows/ci.yml +28 -0
  2. db_chat_widget-0.1.0/.gitignore +18 -0
  3. db_chat_widget-0.1.0/LICENSE +21 -0
  4. db_chat_widget-0.1.0/PKG-INFO +233 -0
  5. db_chat_widget-0.1.0/README.md +187 -0
  6. db_chat_widget-0.1.0/examples/demo_app.py +78 -0
  7. db_chat_widget-0.1.0/examples/embed_example.html +32 -0
  8. db_chat_widget-0.1.0/pyproject.toml +80 -0
  9. db_chat_widget-0.1.0/src/db_chat_widget/__init__.py +6 -0
  10. db_chat_widget-0.1.0/src/db_chat_widget/chat/__init__.py +0 -0
  11. db_chat_widget-0.1.0/src/db_chat_widget/chat/engine.py +99 -0
  12. db_chat_widget-0.1.0/src/db_chat_widget/cli.py +89 -0
  13. db_chat_widget-0.1.0/src/db_chat_widget/config.py +27 -0
  14. db_chat_widget-0.1.0/src/db_chat_widget/db/__init__.py +0 -0
  15. db_chat_widget-0.1.0/src/db_chat_widget/db/connection.py +24 -0
  16. db_chat_widget-0.1.0/src/db_chat_widget/db/executor.py +76 -0
  17. db_chat_widget-0.1.0/src/db_chat_widget/db/introspection.py +71 -0
  18. db_chat_widget-0.1.0/src/db_chat_widget/db/safety.py +60 -0
  19. db_chat_widget-0.1.0/src/db_chat_widget/llm/__init__.py +0 -0
  20. db_chat_widget-0.1.0/src/db_chat_widget/llm/anthropic_provider.py +30 -0
  21. db_chat_widget-0.1.0/src/db_chat_widget/llm/base.py +49 -0
  22. db_chat_widget-0.1.0/src/db_chat_widget/llm/factory.py +40 -0
  23. db_chat_widget-0.1.0/src/db_chat_widget/llm/groq_provider.py +32 -0
  24. db_chat_widget-0.1.0/src/db_chat_widget/llm/ollama_provider.py +40 -0
  25. db_chat_widget-0.1.0/src/db_chat_widget/llm/openai_provider.py +32 -0
  26. db_chat_widget-0.1.0/src/db_chat_widget/server/__init__.py +0 -0
  27. db_chat_widget-0.1.0/src/db_chat_widget/server/app.py +69 -0
  28. db_chat_widget-0.1.0/src/db_chat_widget/server/static/widget.css +253 -0
  29. db_chat_widget-0.1.0/src/db_chat_widget/server/static/widget.js +221 -0
  30. db_chat_widget-0.1.0/src/db_chat_widget/server/templates/demo.html +35 -0
  31. db_chat_widget-0.1.0/tests/conftest.py +51 -0
  32. db_chat_widget-0.1.0/tests/test_api.py +36 -0
  33. db_chat_widget-0.1.0/tests/test_connection.py +16 -0
  34. db_chat_widget-0.1.0/tests/test_engine.py +60 -0
  35. db_chat_widget-0.1.0/tests/test_executor.py +28 -0
  36. db_chat_widget-0.1.0/tests/test_introspection.py +25 -0
  37. db_chat_widget-0.1.0/tests/test_safety.py +54 -0
@@ -0,0 +1,28 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: ${{ matrix.python-version }}
20
+
21
+ - name: Install package with dev extras
22
+ run: pip install -e ".[dev]"
23
+
24
+ - name: Lint
25
+ run: ruff check .
26
+
27
+ - name: Test
28
+ run: pytest -v
@@ -0,0 +1,18 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .eggs/
5
+ build/
6
+ dist/
7
+ .venv/
8
+ venv/
9
+ .env
10
+ .pytest_cache/
11
+ .mypy_cache/
12
+ .ruff_cache/
13
+ *.db
14
+ *.sqlite3
15
+ .coverage
16
+ htmlcov/
17
+ .idea/
18
+ .vscode/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Armand Kouassi
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,233 @@
1
+ Metadata-Version: 2.4
2
+ Name: db-chat-widget
3
+ Version: 0.1.0
4
+ Summary: Embeddable chatbot widget that answers natural-language questions about your database.
5
+ Project-URL: Homepage, https://github.com/armandkouassi/db-chat-widget
6
+ Project-URL: Issues, https://github.com/armandkouassi/db-chat-widget/issues
7
+ Author: Armand Kouassi
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: chatbot,database,llm,nl2sql,sql,widget
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Database
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.9
22
+ Requires-Dist: anthropic>=0.34
23
+ Requires-Dist: groq>=0.11
24
+ Requires-Dist: httpx>=0.27
25
+ Requires-Dist: openai>=1.30
26
+ Requires-Dist: psycopg2-binary>=2.9
27
+ Requires-Dist: pymysql>=1.1
28
+ Requires-Dist: sqlalchemy>=2.0
29
+ Requires-Dist: sqlglot>=23.0
30
+ Provides-Extra: dev
31
+ Requires-Dist: fastapi>=0.110; extra == 'dev'
32
+ Requires-Dist: httpx>=0.27; extra == 'dev'
33
+ Requires-Dist: jinja2>=3.1; extra == 'dev'
34
+ Requires-Dist: mypy>=1.10; extra == 'dev'
35
+ Requires-Dist: pydantic>=2.0; extra == 'dev'
36
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
37
+ Requires-Dist: pytest>=8.0; extra == 'dev'
38
+ Requires-Dist: ruff>=0.4; extra == 'dev'
39
+ Requires-Dist: uvicorn>=0.29; extra == 'dev'
40
+ Provides-Extra: server
41
+ Requires-Dist: fastapi>=0.110; extra == 'server'
42
+ Requires-Dist: jinja2>=3.1; extra == 'server'
43
+ Requires-Dist: pydantic>=2.0; extra == 'server'
44
+ Requires-Dist: uvicorn>=0.29; extra == 'server'
45
+ Description-Content-Type: text/markdown
46
+
47
+ # db-chat-widget
48
+
49
+ Embeddable chatbot widget that lets end users ask natural-language questions
50
+ about a SQL database and get back answers with the underlying query and data.
51
+
52
+ - **Any SQLAlchemy-compatible database**: PostgreSQL, MySQL, SQLite, etc.
53
+ - **Pluggable LLM backend**: Anthropic Claude, OpenAI, Groq, or a local Ollama model.
54
+ - **Read-only by default**: generated SQL is parsed and only `SELECT`
55
+ statements are allowed unless you explicitly opt into write access.
56
+ - **Embeddable widget**: a small vanilla-JS chat component you can drop into
57
+ any existing web page with a single `<script>` tag, backed by a FastAPI
58
+ server.
59
+
60
+ ## Install
61
+
62
+ ```bash
63
+ pip install "db-chat-widget[server]"
64
+ ```
65
+
66
+ This includes every LLM provider (Anthropic, OpenAI, Groq, Ollama), every
67
+ database driver (PostgreSQL, MySQL), and the standalone FastAPI server + CLI
68
+ `serve` command used below. Pick which provider and database to use
69
+ afterwards, at configuration time, via `--llm-provider` / `Settings.llm_provider`
70
+ and `--db-url` / `Settings.db_url`.
71
+
72
+ If you only need the `ChatEngine` as a library — e.g. from
73
+ [django-db-chat-widget](https://github.com/krak225/django-db-chat-widget),
74
+ which brings its own web layer — `pip install db-chat-widget` (without
75
+ `[server]`) skips FastAPI/uvicorn/Jinja2 entirely.
76
+
77
+ ## Quickstart
78
+
79
+ ```bash
80
+ export DB_CHAT_LLM_API_KEY=gsk_... # your Groq API key
81
+
82
+ db-chat-widget serve \
83
+ --db-url "postgresql://user:pass@localhost/mydb" \
84
+ --llm-provider groq \
85
+ --port 8000
86
+ ```
87
+
88
+ (`--llm-provider anthropic` / `openai` work the same way, with the matching API key.)
89
+
90
+ Open http://127.0.0.1:8000 for a demo chat page, or embed the widget in your
91
+ own site.
92
+
93
+ ### Popup mode (floating chat bubble)
94
+
95
+ Drops a chat bubble launcher into the bottom-right corner of the page — the
96
+ most common way to bolt this onto an existing template with zero markup
97
+ changes. Clicking it opens/closes the chat panel.
98
+
99
+ ```html
100
+ <link rel="stylesheet" href="http://127.0.0.1:8000/static/widget.css" />
101
+ <script
102
+ src="http://127.0.0.1:8000/static/widget.js"
103
+ data-mode="bubble"
104
+ data-api-url="http://127.0.0.1:8000/api/chat"
105
+ data-title="Ask about our data"
106
+ ></script>
107
+ ```
108
+
109
+ Use `data-position="bottom-left"` to anchor it to the bottom-left instead.
110
+
111
+ ### Inline mode
112
+
113
+ Embeds the chat panel directly into an element already on the page, instead
114
+ of floating:
115
+
116
+ ```html
117
+ <link rel="stylesheet" href="http://127.0.0.1:8000/static/widget.css" />
118
+ <div id="my-db-chat"></div>
119
+ <script
120
+ src="http://127.0.0.1:8000/static/widget.js"
121
+ data-target="#my-db-chat"
122
+ data-api-url="http://127.0.0.1:8000/api/chat"
123
+ data-title="Ask about our data"
124
+ ></script>
125
+ ```
126
+
127
+ ### Programmatic API
128
+
129
+ Both modes are also available from JavaScript, e.g. after dynamically
130
+ injecting the script, or to control the popup (`.open()` / `.close()`):
131
+
132
+ ```html
133
+ <script src="http://127.0.0.1:8000/static/widget.js"></script>
134
+ <script>
135
+ const chat = DBChatWidget.init({
136
+ mode: "bubble", // or "inline" (requires `target`)
137
+ apiUrl: "http://127.0.0.1:8000/api/chat",
138
+ title: "Ask about our data",
139
+ position: "bottom-right", // or "bottom-left"
140
+ });
141
+ // chat.open(); chat.close(); chat.ask("How many orders today?");
142
+ </script>
143
+ ```
144
+
145
+ See [`examples/embed_example.html`](examples/embed_example.html) and
146
+ [`examples/demo_app.py`](examples/demo_app.py) for a runnable, self-contained
147
+ example with a seeded SQLite database.
148
+
149
+ ## Using it as a library
150
+
151
+ ```python
152
+ from db_chat_widget import ChatEngine, Settings
153
+
154
+ settings = Settings(
155
+ db_url="sqlite:///mydb.sqlite",
156
+ llm_provider="groq",
157
+ llm_api_key="gsk_...",
158
+ read_only=True, # default: only SELECT statements are allowed
159
+ max_rows=200, # cap on rows returned per query
160
+ allowed_tables=None, # optionally restrict which tables can be queried
161
+ )
162
+
163
+ engine = ChatEngine(settings)
164
+ result = engine.ask("How many orders were placed last month?")
165
+
166
+ print(result.answer)
167
+ print(result.sql)
168
+ print(result.rows)
169
+ ```
170
+
171
+ To mount just the FastAPI app (e.g. behind your own ASGI server or reverse
172
+ proxy):
173
+
174
+ ```python
175
+ from db_chat_widget.config import Settings
176
+ from db_chat_widget.server.app import create_app
177
+
178
+ app = create_app(Settings(db_url="...", llm_provider="openai", llm_api_key="..."))
179
+ ```
180
+
181
+ ## How it works
182
+
183
+ 1. The database schema (tables, columns, types, foreign keys) is introspected
184
+ via SQLAlchemy and sent to the configured LLM as context.
185
+ 2. The LLM is asked to translate the user's question into a single SQL
186
+ `SELECT` statement (or reply `CANNOT_ANSWER`).
187
+ 3. The generated SQL is parsed with `sqlglot` and rejected if it contains
188
+ anything other than a single read-only `SELECT` (no `INSERT`/`UPDATE`/
189
+ `DELETE`/`DROP`/multiple statements/disallowed tables).
190
+ 4. The query is executed with a row limit and the result, generated SQL, and
191
+ a short natural-language summary are returned to the widget.
192
+
193
+ ## Default encoding
194
+
195
+ MySQL/MariaDB servers commonly default their client connection to `latin1`,
196
+ which mangles accented characters (e.g. French text) unless the client asks
197
+ for UTF-8 explicitly. When your `db_url` is a MySQL URL without a `charset`
198
+ query parameter, `db-chat-widget` automatically connects with
199
+ `charset=utf8mb4`:
200
+
201
+ ```
202
+ mysql+pymysql://user:pass@localhost:3306/mydb
203
+ # connects as:
204
+ mysql+pymysql://user:pass@localhost:3306/mydb?charset=utf8mb4
205
+ ```
206
+
207
+ If your URL already specifies a `charset` (e.g. `?charset=latin1`), it is left
208
+ untouched. Other database backends (PostgreSQL, SQLite) already default to
209
+ UTF-8 and are not affected.
210
+
211
+ ## Safety notes
212
+
213
+ - `read_only=True` (the default) is enforced at the SQL-parsing level, not
214
+ just by prompting the model — treat it as the actual security boundary.
215
+ - Use `allowed_tables` to scope the chatbot to specific tables (e.g. exclude
216
+ a `users`/`secrets` table containing sensitive columns).
217
+ - The demo server enables permissive CORS (`*`) for convenience; pass
218
+ `cors_origins=[...]` (or `--cors-origin` on the CLI) to restrict this in
219
+ production.
220
+ - Run the database user backing `db_url` with least-privilege, read-only
221
+ grants where possible, as defense in depth.
222
+
223
+ ## Development
224
+
225
+ ```bash
226
+ pip install -e ".[dev]"
227
+ pytest
228
+ ruff check .
229
+ ```
230
+
231
+ ## License
232
+
233
+ MIT
@@ -0,0 +1,187 @@
1
+ # db-chat-widget
2
+
3
+ Embeddable chatbot widget that lets end users ask natural-language questions
4
+ about a SQL database and get back answers with the underlying query and data.
5
+
6
+ - **Any SQLAlchemy-compatible database**: PostgreSQL, MySQL, SQLite, etc.
7
+ - **Pluggable LLM backend**: Anthropic Claude, OpenAI, Groq, or a local Ollama model.
8
+ - **Read-only by default**: generated SQL is parsed and only `SELECT`
9
+ statements are allowed unless you explicitly opt into write access.
10
+ - **Embeddable widget**: a small vanilla-JS chat component you can drop into
11
+ any existing web page with a single `<script>` tag, backed by a FastAPI
12
+ server.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pip install "db-chat-widget[server]"
18
+ ```
19
+
20
+ This includes every LLM provider (Anthropic, OpenAI, Groq, Ollama), every
21
+ database driver (PostgreSQL, MySQL), and the standalone FastAPI server + CLI
22
+ `serve` command used below. Pick which provider and database to use
23
+ afterwards, at configuration time, via `--llm-provider` / `Settings.llm_provider`
24
+ and `--db-url` / `Settings.db_url`.
25
+
26
+ If you only need the `ChatEngine` as a library — e.g. from
27
+ [django-db-chat-widget](https://github.com/krak225/django-db-chat-widget),
28
+ which brings its own web layer — `pip install db-chat-widget` (without
29
+ `[server]`) skips FastAPI/uvicorn/Jinja2 entirely.
30
+
31
+ ## Quickstart
32
+
33
+ ```bash
34
+ export DB_CHAT_LLM_API_KEY=gsk_... # your Groq API key
35
+
36
+ db-chat-widget serve \
37
+ --db-url "postgresql://user:pass@localhost/mydb" \
38
+ --llm-provider groq \
39
+ --port 8000
40
+ ```
41
+
42
+ (`--llm-provider anthropic` / `openai` work the same way, with the matching API key.)
43
+
44
+ Open http://127.0.0.1:8000 for a demo chat page, or embed the widget in your
45
+ own site.
46
+
47
+ ### Popup mode (floating chat bubble)
48
+
49
+ Drops a chat bubble launcher into the bottom-right corner of the page — the
50
+ most common way to bolt this onto an existing template with zero markup
51
+ changes. Clicking it opens/closes the chat panel.
52
+
53
+ ```html
54
+ <link rel="stylesheet" href="http://127.0.0.1:8000/static/widget.css" />
55
+ <script
56
+ src="http://127.0.0.1:8000/static/widget.js"
57
+ data-mode="bubble"
58
+ data-api-url="http://127.0.0.1:8000/api/chat"
59
+ data-title="Ask about our data"
60
+ ></script>
61
+ ```
62
+
63
+ Use `data-position="bottom-left"` to anchor it to the bottom-left instead.
64
+
65
+ ### Inline mode
66
+
67
+ Embeds the chat panel directly into an element already on the page, instead
68
+ of floating:
69
+
70
+ ```html
71
+ <link rel="stylesheet" href="http://127.0.0.1:8000/static/widget.css" />
72
+ <div id="my-db-chat"></div>
73
+ <script
74
+ src="http://127.0.0.1:8000/static/widget.js"
75
+ data-target="#my-db-chat"
76
+ data-api-url="http://127.0.0.1:8000/api/chat"
77
+ data-title="Ask about our data"
78
+ ></script>
79
+ ```
80
+
81
+ ### Programmatic API
82
+
83
+ Both modes are also available from JavaScript, e.g. after dynamically
84
+ injecting the script, or to control the popup (`.open()` / `.close()`):
85
+
86
+ ```html
87
+ <script src="http://127.0.0.1:8000/static/widget.js"></script>
88
+ <script>
89
+ const chat = DBChatWidget.init({
90
+ mode: "bubble", // or "inline" (requires `target`)
91
+ apiUrl: "http://127.0.0.1:8000/api/chat",
92
+ title: "Ask about our data",
93
+ position: "bottom-right", // or "bottom-left"
94
+ });
95
+ // chat.open(); chat.close(); chat.ask("How many orders today?");
96
+ </script>
97
+ ```
98
+
99
+ See [`examples/embed_example.html`](examples/embed_example.html) and
100
+ [`examples/demo_app.py`](examples/demo_app.py) for a runnable, self-contained
101
+ example with a seeded SQLite database.
102
+
103
+ ## Using it as a library
104
+
105
+ ```python
106
+ from db_chat_widget import ChatEngine, Settings
107
+
108
+ settings = Settings(
109
+ db_url="sqlite:///mydb.sqlite",
110
+ llm_provider="groq",
111
+ llm_api_key="gsk_...",
112
+ read_only=True, # default: only SELECT statements are allowed
113
+ max_rows=200, # cap on rows returned per query
114
+ allowed_tables=None, # optionally restrict which tables can be queried
115
+ )
116
+
117
+ engine = ChatEngine(settings)
118
+ result = engine.ask("How many orders were placed last month?")
119
+
120
+ print(result.answer)
121
+ print(result.sql)
122
+ print(result.rows)
123
+ ```
124
+
125
+ To mount just the FastAPI app (e.g. behind your own ASGI server or reverse
126
+ proxy):
127
+
128
+ ```python
129
+ from db_chat_widget.config import Settings
130
+ from db_chat_widget.server.app import create_app
131
+
132
+ app = create_app(Settings(db_url="...", llm_provider="openai", llm_api_key="..."))
133
+ ```
134
+
135
+ ## How it works
136
+
137
+ 1. The database schema (tables, columns, types, foreign keys) is introspected
138
+ via SQLAlchemy and sent to the configured LLM as context.
139
+ 2. The LLM is asked to translate the user's question into a single SQL
140
+ `SELECT` statement (or reply `CANNOT_ANSWER`).
141
+ 3. The generated SQL is parsed with `sqlglot` and rejected if it contains
142
+ anything other than a single read-only `SELECT` (no `INSERT`/`UPDATE`/
143
+ `DELETE`/`DROP`/multiple statements/disallowed tables).
144
+ 4. The query is executed with a row limit and the result, generated SQL, and
145
+ a short natural-language summary are returned to the widget.
146
+
147
+ ## Default encoding
148
+
149
+ MySQL/MariaDB servers commonly default their client connection to `latin1`,
150
+ which mangles accented characters (e.g. French text) unless the client asks
151
+ for UTF-8 explicitly. When your `db_url` is a MySQL URL without a `charset`
152
+ query parameter, `db-chat-widget` automatically connects with
153
+ `charset=utf8mb4`:
154
+
155
+ ```
156
+ mysql+pymysql://user:pass@localhost:3306/mydb
157
+ # connects as:
158
+ mysql+pymysql://user:pass@localhost:3306/mydb?charset=utf8mb4
159
+ ```
160
+
161
+ If your URL already specifies a `charset` (e.g. `?charset=latin1`), it is left
162
+ untouched. Other database backends (PostgreSQL, SQLite) already default to
163
+ UTF-8 and are not affected.
164
+
165
+ ## Safety notes
166
+
167
+ - `read_only=True` (the default) is enforced at the SQL-parsing level, not
168
+ just by prompting the model — treat it as the actual security boundary.
169
+ - Use `allowed_tables` to scope the chatbot to specific tables (e.g. exclude
170
+ a `users`/`secrets` table containing sensitive columns).
171
+ - The demo server enables permissive CORS (`*`) for convenience; pass
172
+ `cors_origins=[...]` (or `--cors-origin` on the CLI) to restrict this in
173
+ production.
174
+ - Run the database user backing `db_url` with least-privilege, read-only
175
+ grants where possible, as defense in depth.
176
+
177
+ ## Development
178
+
179
+ ```bash
180
+ pip install -e ".[dev]"
181
+ pytest
182
+ ruff check .
183
+ ```
184
+
185
+ ## License
186
+
187
+ MIT
@@ -0,0 +1,78 @@
1
+ """Run a local demo: creates a small SQLite database and serves the chatbot.
2
+
3
+ Usage:
4
+ export DB_CHAT_LLM_API_KEY=sk-...
5
+ python examples/demo_app.py
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ from pathlib import Path
12
+
13
+ from sqlalchemy import create_engine, text
14
+
15
+ DB_PATH = Path(__file__).parent / "demo.db"
16
+
17
+
18
+ def seed_database() -> str:
19
+ db_url = f"sqlite:///{DB_PATH}"
20
+ engine = create_engine(db_url)
21
+ with engine.begin() as conn:
22
+ conn.execute(text("DROP TABLE IF EXISTS customers"))
23
+ conn.execute(text("DROP TABLE IF EXISTS orders"))
24
+ conn.execute(
25
+ text("CREATE TABLE customers (id INTEGER PRIMARY KEY, name TEXT, country TEXT)")
26
+ )
27
+ conn.execute(
28
+ text(
29
+ "CREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER, "
30
+ "amount REAL, status TEXT)"
31
+ )
32
+ )
33
+ customers = [
34
+ (1, "Awa Diallo", "Senegal"),
35
+ (2, "Kofi Mensah", "Ghana"),
36
+ (3, "Amina Traore", "Cote d'Ivoire"),
37
+ ]
38
+ for row in customers:
39
+ conn.execute(
40
+ text("INSERT INTO customers (id, name, country) VALUES (:id, :name, :country)"),
41
+ {"id": row[0], "name": row[1], "country": row[2]},
42
+ )
43
+ orders = [
44
+ (1, 1, 120.0, "paid"),
45
+ (2, 1, 45.5, "pending"),
46
+ (3, 2, 300.0, "paid"),
47
+ ]
48
+ for row in orders:
49
+ conn.execute(
50
+ text(
51
+ "INSERT INTO orders (id, customer_id, amount, status) "
52
+ "VALUES (:id, :customer_id, :amount, :status)"
53
+ ),
54
+ {"id": row[0], "customer_id": row[1], "amount": row[2], "status": row[3]},
55
+ )
56
+ return db_url
57
+
58
+
59
+ def main() -> None:
60
+ import uvicorn
61
+
62
+ from db_chat_widget.config import Settings
63
+ from db_chat_widget.server.app import create_app
64
+
65
+ db_url = seed_database()
66
+
67
+ settings = Settings(
68
+ db_url=db_url,
69
+ llm_provider=os.environ.get("DB_CHAT_LLM_PROVIDER", "anthropic"),
70
+ llm_api_key=os.environ.get("DB_CHAT_LLM_API_KEY"),
71
+ read_only=True,
72
+ )
73
+ app = create_app(settings)
74
+ uvicorn.run(app, host="127.0.0.1", port=8000)
75
+
76
+
77
+ if __name__ == "__main__":
78
+ main()
@@ -0,0 +1,32 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Embedding db-chat-widget</title>
6
+ <link rel="stylesheet" href="http://127.0.0.1:8000/static/widget.css" />
7
+ </head>
8
+ <body>
9
+ <h1>My existing website</h1>
10
+ <p>
11
+ A floating chat bubble is injected in the bottom-right corner by the
12
+ script below — no extra markup needed in this template.
13
+ </p>
14
+
15
+ <!-- Popup mode: no data-target needed, the widget builds its own
16
+ floating launcher button + panel and appends them to <body>. -->
17
+ <script src="http://127.0.0.1:8000/static/widget.js"
18
+ data-mode="bubble"
19
+ data-api-url="http://127.0.0.1:8000/api/chat"
20
+ data-title="Ask about our data"></script>
21
+
22
+ <!-- Alternative: inline mode, embedded directly into a page element
23
+ instead of floating. Swap the script above for this one:
24
+
25
+ <div id="my-db-chat"></div>
26
+ <script src="http://127.0.0.1:8000/static/widget.js"
27
+ data-target="#my-db-chat"
28
+ data-api-url="http://127.0.0.1:8000/api/chat"
29
+ data-title="Ask about our data"></script>
30
+ -->
31
+ </body>
32
+ </html>
@@ -0,0 +1,80 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "db-chat-widget"
7
+ version = "0.1.0"
8
+ description = "Embeddable chatbot widget that answers natural-language questions about your database."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ { name = "Armand Kouassi" },
14
+ ]
15
+ keywords = ["chatbot", "sql", "database", "llm", "nl2sql", "widget"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Topic :: Database",
26
+ "Topic :: Software Development :: Libraries :: Python Modules",
27
+ ]
28
+
29
+ dependencies = [
30
+ "sqlalchemy>=2.0",
31
+ "sqlglot>=23.0",
32
+ # LLM providers
33
+ "anthropic>=0.34",
34
+ "openai>=1.30",
35
+ "groq>=0.11",
36
+ "httpx>=0.27",
37
+ # database drivers
38
+ "psycopg2-binary>=2.9",
39
+ "pymysql>=1.1",
40
+ ]
41
+
42
+ [project.optional-dependencies]
43
+ # Only needed for the standalone FastAPI server / CLI `serve` command and the
44
+ # embeddable demo page. Not required to use ChatEngine as a library (e.g. from
45
+ # django-db-chat-widget, which brings its own web layer).
46
+ server = [
47
+ "fastapi>=0.110",
48
+ "uvicorn>=0.29",
49
+ "pydantic>=2.0",
50
+ "jinja2>=3.1",
51
+ ]
52
+ dev = [
53
+ "db-chat-widget[server]",
54
+ "pytest>=8.0",
55
+ "pytest-asyncio>=0.23",
56
+ "httpx>=0.27",
57
+ "ruff>=0.4",
58
+ "mypy>=1.10",
59
+ ]
60
+
61
+ [project.urls]
62
+ Homepage = "https://github.com/armandkouassi/db-chat-widget"
63
+ Issues = "https://github.com/armandkouassi/db-chat-widget/issues"
64
+
65
+ [project.scripts]
66
+ db-chat-widget = "db_chat_widget.cli:main"
67
+
68
+ [tool.hatch.build.targets.wheel]
69
+ packages = ["src/db_chat_widget"]
70
+
71
+ [tool.pytest.ini_options]
72
+ testpaths = ["tests"]
73
+ asyncio_mode = "auto"
74
+
75
+ [tool.ruff]
76
+ line-length = 100
77
+ target-version = "py39"
78
+
79
+ [tool.ruff.lint]
80
+ select = ["E", "F", "I", "UP", "B"]
@@ -0,0 +1,6 @@
1
+ from db_chat_widget.chat.engine import ChatEngine, ChatResult
2
+ from db_chat_widget.config import Settings
3
+
4
+ __version__ = "0.1.0"
5
+
6
+ __all__ = ["ChatEngine", "ChatResult", "Settings", "__version__"]