Skip to content

Commit 0197ae0

Browse files
committed
lates version
1 parent fb43417 commit 0197ae0

File tree

4 files changed

+166
-32
lines changed

4 files changed

+166
-32
lines changed

examples/docker/chat-with-csv-solara/Dockerfile

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
FROM python:3.11
22

33
COPY app.py app.py
4-
COPY user-logo.png user-logo.png
5-
COPY system-logo.png system-logo.png
6-
COPY assistant-logo.png assistant-logo.png
74
COPY chat.py chat.py
8-
RUN pip install git+https://github.com/neelasha23/jupysql.git@boxplot
5+
COPY static/ static/
6+
RUN pip install git+https://github.com/ploomber/jupysql.git@master
97
RUN pip install requests solara openai pandas duckdb duckdb-engine matplotlib
108

119

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Data querying and visualisation App
2+
3+
This query and data visualisation application is designed to provide a user-friendly chatbot interface to interact with your data, making data exploration and analysis a breeze.
4+
5+
6+
## Getting Started
7+
8+
To get started with this app, follow these steps:
9+
10+
1. Login to your [Ploomber Cloud](https://ploomber.io/) account.
11+
12+
2. Follow the [steps](https://docs.cloud.ploomber.io/en/latest/apps/solara.html) for deploying a Solara application and upload the `app.zip` file provided in the example.
13+
14+
## How to use
15+
16+
1. **Dataset**: Click the `SAMPLE DATASET` button to load a sample csv file, or upload your own content by dragging a file to the drop area. You may also clear the loaded data by clicking the `Clear Dataset` button.
17+
18+
2. **Number of preview rows**: Input the desired number of preview rows to be displayed.
19+
20+
3. **Interaction**: You may ask the chatbot natural language queries like : `top 20 rows of table`, `unique values of column with counts`, etc.
21+
22+
4. **Data visualisation**: Visualize your data on the fly. Want to see a histogram or a box plot for a specific column? Just ask the chatbot, and it will generate the chart for you, e.g., `histogram on column`.
23+
24+
5. **Export Results**: The app allows you to export the charts, or query results.

examples/docker/chat-with-csv-solara/app.py

Lines changed: 55 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
from sql import inspect
2-
from sql.run import run
3-
from sql.connection import ConnectionManager
4-
from sql.magic import SqlMagic, load_ipython_extension
5-
from IPython.core.interactiveshell import InteractiveShell
6-
7-
81
import uuid
92
import requests
103
from functools import partial
114

5+
import openai
126
import solara
137
import solara.lab
148
from solara.components.file_drop import FileDrop
159

10+
from sql import inspect
11+
from sql.run import run
12+
from sql.connection import ConnectionManager
13+
from sql.magic import SqlMagic, load_ipython_extension
14+
from IPython.core.interactiveshell import InteractiveShell
1615
from sql.plot import boxplot, histogram
16+
from sqlalchemy.exc import ProgrammingError
1717

1818
from chat import *
1919

@@ -28,6 +28,10 @@
2828
margin: auto;
2929
padding: 1em;
3030
}
31+
32+
#app > div > div:nth-child(2) > div:nth-child(2) {
33+
display: none;
34+
}
3135
"""
3236

3337
openai.api_key = "YOUR_API_KEY"
@@ -78,37 +82,47 @@ def delete_data():
7882

7983

8084
class State:
81-
package = solara.reactive("Matplotlib")
8285
initial_prompt = solara.reactive("")
8386
sample_data_loaded = solara.reactive(False)
8487
upload_data = solara.reactive(False)
8588
upload_data_error = solara.reactive("")
8689
results = solara.reactive(20)
90+
input = solara.reactive("")
91+
loading_data = solara.reactive(False)
8792

8893
@staticmethod
8994
def load_sample():
9095
State.reset()
9196
name = gen_name()
97+
State.loading_data.value = True
9298
url = "https://raw.githubusercontent.com/mwaskom/seaborn-data/master/penguins.csv"
9399
response = requests.get(url)
94100
if response.status_code == 200:
95101
with open(name, "wb") as f:
96102
f.write(response.content)
97103
cols = load_data(name)
98104
State.sample_data_loaded.value = True
105+
State.loading_data.value = False
99106
State.initial_prompt.value = prompt_template.format(cols)
100107
else:
101108
solara.Warning("Failed to fetch the data. Check the URL and try again.")
102109

103110
@staticmethod
104111
def load_from_file(file):
112+
if not file["name"].endswith(".csv"):
113+
State.upload_data_error.value = "Only csv files are supported"
114+
return
105115
State.reset()
106116
name = gen_name()
117+
State.loading_data.value = True
107118
try:
108119
df = pd.read_csv(file["file_obj"])
120+
df.columns = df.columns.str.strip()
121+
df.columns = df.columns.str.replace(' ', '_')
109122
df.to_csv(name, index=False)
110123
cols = load_data(name)
111124
State.upload_data.value = True
125+
State.loading_data.value = False
112126
State.initial_prompt.value = prompt_template.format(cols)
113127
except Exception as e:
114128
State.upload_data_error.value = str(e)
@@ -117,10 +131,10 @@ def load_from_file(file):
117131

118132
@staticmethod
119133
def reset():
120-
delete_data()
121-
State.initial_prompt.value = ""
122134
State.sample_data_loaded.value = False
123135
State.upload_data.value = False
136+
delete_data()
137+
State.initial_prompt.value = ""
124138
State.upload_data_error.value = ""
125139

126140
@staticmethod
@@ -162,9 +176,11 @@ def Chat() -> None:
162176
input, set_input = solara.use_state("")
163177

164178
def ask_chatgpt():
179+
input = State.input.value
165180
_messages = messages + [Message(role="user", content=input, df=None, fig=None)]
166181
user_input = input
167182
set_input("")
183+
State.input.value = ""
168184
set_messages(_messages)
169185
if State.initial_prompt.value:
170186
final = None
@@ -173,46 +189,55 @@ def ask_chatgpt():
173189

174190
if final.startswith("%sqlplot"):
175191
_, name, column = final.split(" ")
192+
176193
fig = Figure()
177194
ax = fig.subplots()
178195

179196
fn_map = {"histogram": partial(histogram, bins=50),
180197
"boxplot": boxplot}
181198

182199
fn = fn_map[name]
183-
ax = fn("my_data", column, ax=ax)
184-
set_messages(_messages + [Message(role="assistant", content="", df=None, fig=fig)])
185-
#[Message(role="assistant", content=final, df=None, fig=None)])
200+
try:
201+
ax = fn("my_data", column, ax=ax)
202+
set_messages(_messages + [Message(role="assistant", content="", df=None, fig=fig)])
203+
except Exception as e:
204+
set_messages(_messages + [
205+
Message(role="assistant", content="Please pass relevant columns", df=None, fig=None)])
186206
else:
187-
messages_list = ';'.join([msg.content for msg in _messages])
188-
query_result = run.run_statements(conn, final, sqlmagic)
189-
set_messages(_messages + [Message(role="assistant", content="", df=query_result, fig=None)])
190-
# [Message(role="assistant", content=f"Setting in else part: {final} {user_input} "
191-
# f"{messages_list}",
192-
# df=None, fig=None)])
207+
error = "Sorry, we couldn't run your query on the data"
208+
try:
209+
query_result = run.run_statements(conn, final, sqlmagic)
210+
set_messages(_messages + [Message(role="assistant", content="", df=query_result, fig=None)])
211+
except ProgrammingError as e:
212+
set_messages(_messages + [
213+
Message(role="assistant", content=error, df=None, fig=None)])
214+
except Exception as e:
215+
set_messages(_messages + [
216+
Message(role="assistant", content=error, df=None, fig=None)])
217+
193218
else:
194-
set_messages(_messages + [Message(role="assistant", content="Please load some data first!", df=None, fig=None)])
219+
set_messages(_messages + [Message(role="assistant",
220+
content="Please load some data first!", df=None, fig=None)])
195221

196222
with solara.VBox():
197223
for message in messages:
198224
ChatBox(message)
199225

200226
with solara.Row(justify="center"):
201227
with solara.HBox(align_items="center", classes=["chat-input"]):
202-
rv.Textarea(v_model=input, on_v_model=set_input, solo=True, hide_details=True, outlined=True,
203-
rows=1,
204-
auto_grow=True)
205-
solara.IconButton("send", on_click=ask_chatgpt)
228+
solara.InputText(label="Query", value=State.input, continuous_update=False)
229+
230+
if State.input.value:
231+
ask_chatgpt()
206232

207233

208234
@solara.component
209235
def Page():
210-
package = State.package.value
211236
initial_prompt = State.initial_prompt.value
212237
sample_data_loaded = State.sample_data_loaded.value
213238
upload_data = State.upload_data.value
214239
upload_data_error = State.upload_data_error.value
215-
results =State.results.value
240+
results = State.results.value
216241

217242
with solara.AppBarTitle():
218243
solara.Text("Data Querying and Visualisation App")
@@ -235,11 +260,13 @@ def Page():
235260
solara.Button("Clear dataset", color="primary", text=True, outlined=True, on_click=State.reset)
236261
FileDrop(on_file=State.load_from_file, on_total_progress=lambda *args: None,
237262
label="Drag a .csv file here")
263+
if State.loading_data.value:
264+
with solara.Div():
265+
solara.Text("Loading csv...")
266+
solara.ProgressLinear(True)
238267
if initial_prompt:
239268
solara.InputInt("Number of preview rows", value=State.results, continuous_update=True)
240269

241-
solara.Select(label="Visualisation Library", value=State.package, values=['Matplotlib', 'Plotly', 'Altair'])
242-
243270
solara.Markdown("Hosted in [Ploomber Cloud](https://ploomber.io/)")
244271

245272
if sample_data_loaded:
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import io
2+
import tempfile
3+
from pathlib import Path
4+
from dataclasses import dataclass
5+
6+
import pandas as pd
7+
import solara as sl
8+
from matplotlib.figure import Figure
9+
from matplotlib import pyplot as plt
10+
11+
plt.switch_backend("agg")
12+
13+
14+
chatbox_css = """
15+
.message {
16+
max-width: 450px;
17+
width: 100%;
18+
}
19+
20+
.user-message, .user-message > * {
21+
background-color: #f0f0f0 !important;
22+
}
23+
24+
.assistant-message, .assistant-message > * {
25+
background-color: #9ab2e9 !important;
26+
}
27+
28+
.avatar {
29+
width: 50px;
30+
height: 50px;
31+
border-radius: 50%;
32+
border: 2px solid transparent;
33+
overflow: hidden;
34+
display: flex;
35+
}
36+
37+
.avatar img {
38+
width: 100%;
39+
height: 100%;
40+
object-fit: cover;
41+
}
42+
"""
43+
44+
45+
@dataclass
46+
class Message:
47+
role: str
48+
content: str
49+
df: pd.DataFrame
50+
fig: Figure
51+
52+
53+
def ChatBox(message: Message) -> None:
54+
sl.Style(chatbox_css)
55+
56+
align = "start" if message.role == "assistant" else "end"
57+
with sl.Column(align=align):
58+
with sl.Card(classes=["message", f"{message.role}-message"]):
59+
if message.content:
60+
sl.Markdown(message.content)
61+
elif message.df is not None:
62+
with sl.Card():
63+
sl.DataFrame(message.df)
64+
with sl.Card():
65+
sl.FileDownload(message.df.to_csv(index=False), filename="data.csv", label="Download file")
66+
elif message.fig is not None:
67+
with sl.Card():
68+
sl.FigureMatplotlib(message.fig)
69+
with sl.Card():
70+
buf = io.BytesIO()
71+
message.fig.savefig(buf, format="jpg")
72+
fp = tempfile.NamedTemporaryFile()
73+
with open(f"{fp.name}.jpg", 'wb') as ff:
74+
ff.write(buf.getvalue())
75+
buf.close()
76+
file_object = sl.use_memo(lambda: open(f"{fp.name}.jpg", "rb"), [])
77+
sl.FileDownload(file_object, mime_type="image/jpeg", close_file=False)
78+
79+
# Image reference: https://www.flaticon.com/free-icons/bot;
80+
# https://www.flaticon.com/free-icons/use
81+
82+
with sl.HBox(align_items="center"):
83+
image_path = Path(f"static/{message.role}-logo.png")
84+
sl.Image(str(image_path), classes=["avatar"])
85+
sl.Text(message.role.capitalize())

0 commit comments

Comments
 (0)