Spaces:
Sleeping
Sleeping
Delete app.py
Browse files
app.py
DELETED
|
@@ -1,474 +0,0 @@
|
|
| 1 |
-
# app.py (SparkNova Gradio Space - FIXED)
|
| 2 |
-
import os
|
| 3 |
-
import io
|
| 4 |
-
import json
|
| 5 |
-
import tempfile
|
| 6 |
-
import traceback
|
| 7 |
-
from io import BytesIO
|
| 8 |
-
import pandas as pd
|
| 9 |
-
import numpy as np
|
| 10 |
-
import matplotlib.pyplot as plt
|
| 11 |
-
import plotly.express as px
|
| 12 |
-
import plotly.graph_objects as go
|
| 13 |
-
import gradio as gr
|
| 14 |
-
|
| 15 |
-
from langchain_groq import ChatGroq
|
| 16 |
-
|
| 17 |
-
# ---------------- Config ----------------
|
| 18 |
-
GROQ_API_KEY = os.getenv("GROQ_API_KEY", "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")
|
| 19 |
-
os.environ["GROQ_API_KEY"] = GROQ_API_KEY
|
| 20 |
-
|
| 21 |
-
# ---------------- Core Agent (UNCHANGED) ----------------
|
| 22 |
-
llm = None
|
| 23 |
-
uploaded_df = None
|
| 24 |
-
dataset_name = None
|
| 25 |
-
last_chart = None # Store last generated chart
|
| 26 |
-
|
| 27 |
-
SYSTEM_PROMPT = """You are a data analysis assistant. Respond ONLY in JSON describing a clear analysis plan.
|
| 28 |
-
IMPORTANT: Only create plots when the user EXPLICITLY asks for visualization (chart, graph, plot, visualize, show distribution, etc.)
|
| 29 |
-
For INFORMATIONAL queries (explain, describe, what is, tell me about, how many, etc.) - NO PLOT:
|
| 30 |
-
{
|
| 31 |
-
"type": "explain",
|
| 32 |
-
"operations": [],
|
| 33 |
-
"plot": null,
|
| 34 |
-
"narrative": "detailed answer to the user's question",
|
| 35 |
-
"insights_needed": false
|
| 36 |
-
}
|
| 37 |
-
For STATISTICAL description WITHOUT visualization:
|
| 38 |
-
{
|
| 39 |
-
"type": "describe",
|
| 40 |
-
"operations": [
|
| 41 |
-
{"op": "describe", "columns": ["col1"]}
|
| 42 |
-
],
|
| 43 |
-
"plot": null,
|
| 44 |
-
"narrative": "statistical summary explanation",
|
| 45 |
-
"insights_needed": false
|
| 46 |
-
}
|
| 47 |
-
For VISUALIZATION requests (when user asks for chart/graph/plot):
|
| 48 |
-
{
|
| 49 |
-
"type": "analysis",
|
| 50 |
-
"operations": [
|
| 51 |
-
{
|
| 52 |
-
"op": "groupby",
|
| 53 |
-
"columns": ["col1"],
|
| 54 |
-
"agg_col": "col2",
|
| 55 |
-
"agg_func": "mean"
|
| 56 |
-
}
|
| 57 |
-
],
|
| 58 |
-
"plot": {"type": "bar"|"line"|"pie"|"hist", "x": "col1", "y": "result_col", "title": "..."},
|
| 59 |
-
"narrative": "short explanation",
|
| 60 |
-
"insights_needed": true
|
| 61 |
-
}
|
| 62 |
-
For filtering:
|
| 63 |
-
{"op": "filter", "expr": "Age > 25"}
|
| 64 |
-
For calculations:
|
| 65 |
-
{"op": "calculate", "expr": "Col1 * Col2", "new_col": "Result"}
|
| 66 |
-
Set "plot": null when user is asking informational questions without requesting visualization.
|
| 67 |
-
"""
|
| 68 |
-
|
| 69 |
-
def initialize_llm():
|
| 70 |
-
global llm
|
| 71 |
-
try:
|
| 72 |
-
llm = ChatGroq(model="llama-3.3-70b-versatile", api_key=GROQ_API_KEY, temperature=0.0)
|
| 73 |
-
print("GROQ API initialized")
|
| 74 |
-
return True
|
| 75 |
-
except Exception as e:
|
| 76 |
-
print("LLM init error:", e)
|
| 77 |
-
return False
|
| 78 |
-
|
| 79 |
-
def clean_numeric(df):
|
| 80 |
-
df = df.copy()
|
| 81 |
-
for col in df.columns:
|
| 82 |
-
if pd.api.types.is_string_dtype(df[col]) or df[col].dtype == object:
|
| 83 |
-
s = df[col].astype(str).str.strip()
|
| 84 |
-
if s.str.contains("%", na=False).any():
|
| 85 |
-
numeric_vals = pd.to_numeric(s.str.replace("%", "", regex=False), errors="coerce")
|
| 86 |
-
if numeric_vals.notna().sum() / len(df) > 0.5:
|
| 87 |
-
df[col] = numeric_vals / 100.0
|
| 88 |
-
continue
|
| 89 |
-
cleaned = s.str.replace(",", "", regex=False).str.replace("₹", "", regex=False).str.replace("$", "", regex=False)
|
| 90 |
-
numeric_vals = pd.to_numeric(cleaned, errors="coerce")
|
| 91 |
-
if numeric_vals.notna().sum() / len(df) > 0.5:
|
| 92 |
-
df[col] = numeric_vals
|
| 93 |
-
return df
|
| 94 |
-
|
| 95 |
-
def make_context(df):
|
| 96 |
-
sample_data = df.head(3).to_string(max_cols=10, max_colwidth=20)
|
| 97 |
-
return f"""Dataset: {len(df)} rows, {len(df.columns)} columns
|
| 98 |
-
Columns: {', '.join(df.columns)}
|
| 99 |
-
Data types: {df.dtypes.to_dict()}
|
| 100 |
-
Sample data:
|
| 101 |
-
{sample_data}"""
|
| 102 |
-
|
| 103 |
-
def parse_plan(raw_text):
|
| 104 |
-
txt = raw_text.strip().replace("```json", "").replace("```", "").strip()
|
| 105 |
-
try:
|
| 106 |
-
start = txt.index("{")
|
| 107 |
-
end = txt.rindex("}") + 1
|
| 108 |
-
plan = json.loads(txt[start:end])
|
| 109 |
-
plan.setdefault("type", "analysis")
|
| 110 |
-
plan.setdefault("operations", [])
|
| 111 |
-
plan.setdefault("plot", None)
|
| 112 |
-
plan.setdefault("narrative", "")
|
| 113 |
-
plan.setdefault("insights_needed", False)
|
| 114 |
-
return plan
|
| 115 |
-
except Exception as e:
|
| 116 |
-
return {
|
| 117 |
-
"type": "error",
|
| 118 |
-
"operations": [],
|
| 119 |
-
"plot": None,
|
| 120 |
-
"narrative": f"Error parsing LLM response: {str(e)}",
|
| 121 |
-
"insights_needed": False
|
| 122 |
-
}
|
| 123 |
-
|
| 124 |
-
def execute_plan(df, plan):
|
| 125 |
-
dfw = df.copy()
|
| 126 |
-
describe_stats = {}
|
| 127 |
-
try:
|
| 128 |
-
for op in plan.get("operations", []):
|
| 129 |
-
optype = op.get("op", "").lower()
|
| 130 |
-
if optype == "describe":
|
| 131 |
-
cols = op.get("columns", [])
|
| 132 |
-
for col in cols:
|
| 133 |
-
if col in dfw.columns:
|
| 134 |
-
stats = dfw[col].describe()
|
| 135 |
-
describe_stats[col] = stats
|
| 136 |
-
elif optype == "groupby":
|
| 137 |
-
cols = op.get("columns", [])
|
| 138 |
-
agg_col = op.get("agg_col")
|
| 139 |
-
agg_func = op.get("agg_func", "count")
|
| 140 |
-
if not cols:
|
| 141 |
-
continue
|
| 142 |
-
if agg_func == "count" or not agg_col:
|
| 143 |
-
dfw = dfw.groupby(cols).size().reset_index(name="count")
|
| 144 |
-
else:
|
| 145 |
-
if agg_col in dfw.columns:
|
| 146 |
-
result_col = f"{agg_func}_{agg_col}"
|
| 147 |
-
dfw = dfw.groupby(cols)[agg_col].agg(agg_func).reset_index(name=result_col)
|
| 148 |
-
elif optype == "filter":
|
| 149 |
-
expr = op.get("expr", "")
|
| 150 |
-
if expr:
|
| 151 |
-
dfw = dfw.query(expr)
|
| 152 |
-
elif optype == "calculate":
|
| 153 |
-
expr = op.get("expr", "")
|
| 154 |
-
new_col = op.get("new_col", "Calculated")
|
| 155 |
-
dfw[new_col] = dfw.eval(expr)
|
| 156 |
-
return dfw, describe_stats
|
| 157 |
-
except Exception as e:
|
| 158 |
-
raise Exception(f"Execution error: {str(e)}")
|
| 159 |
-
|
| 160 |
-
def create_plot(df, dfw, plan, describe_stats):
|
| 161 |
-
global last_chart
|
| 162 |
-
plot_spec = plan.get("plot")
|
| 163 |
-
if not plot_spec:
|
| 164 |
-
last_chart = None
|
| 165 |
-
return None
|
| 166 |
-
ptype = plot_spec.get("type", "bar")
|
| 167 |
-
x = plot_spec.get("x")
|
| 168 |
-
y = plot_spec.get("y")
|
| 169 |
-
title = plot_spec.get("title", "Chart")
|
| 170 |
-
plot_df = df if describe_stats else dfw
|
| 171 |
-
if x and x not in plot_df.columns:
|
| 172 |
-
x = None
|
| 173 |
-
if y and y not in plot_df.columns:
|
| 174 |
-
numeric_cols = plot_df.select_dtypes(include=[np.number]).columns
|
| 175 |
-
y = numeric_cols[0] if len(numeric_cols) > 0 else None
|
| 176 |
-
if not y:
|
| 177 |
-
last_chart = None
|
| 178 |
-
return None
|
| 179 |
-
try:
|
| 180 |
-
if ptype == "pie":
|
| 181 |
-
df_pie = plot_df.set_index(x)[y] if x else plot_df[y]
|
| 182 |
-
df_pie = df_pie.dropna()
|
| 183 |
-
fig = go.Figure(data=[go.Pie(labels=df_pie.index, values=df_pie.values, textposition='none')])
|
| 184 |
-
fig.update_layout(title=title, title_font_size=16, showlegend=True, width=900, height=500)
|
| 185 |
-
elif ptype == "bar":
|
| 186 |
-
if x:
|
| 187 |
-
fig = px.bar(plot_df, x=x, y=y, title=title)
|
| 188 |
-
else:
|
| 189 |
-
fig = px.bar(y=plot_df[y], title=title)
|
| 190 |
-
fig.update_layout(width=900, height=500)
|
| 191 |
-
elif ptype == "line":
|
| 192 |
-
if x:
|
| 193 |
-
fig = px.line(plot_df, x=x, y=y, title=title, markers=True)
|
| 194 |
-
else:
|
| 195 |
-
fig = px.line(y=plot_df[y], title=title, markers=True)
|
| 196 |
-
fig.update_layout(width=900, height=500)
|
| 197 |
-
elif ptype == "hist":
|
| 198 |
-
fig = px.histogram(plot_df, x=y, title=title, nbins=20)
|
| 199 |
-
fig.update_layout(width=900, height=500)
|
| 200 |
-
elif ptype == "box":
|
| 201 |
-
fig = px.box(plot_df, y=y, x=x, title=title) if x else px.box(plot_df, y=y, title=title)
|
| 202 |
-
fig.update_layout(width=900, height=500)
|
| 203 |
-
elif ptype == "heatmap":
|
| 204 |
-
numeric = plot_df.select_dtypes(include=[np.number])
|
| 205 |
-
if numeric.shape[1] < 2:
|
| 206 |
-
last_chart = None
|
| 207 |
-
return None
|
| 208 |
-
corr = numeric.corr()
|
| 209 |
-
fig = px.imshow(corr, text_auto=True, title=title)
|
| 210 |
-
fig.update_layout(width=900, height=500)
|
| 211 |
-
else:
|
| 212 |
-
last_chart = None
|
| 213 |
-
return None
|
| 214 |
-
|
| 215 |
-
last_chart = fig
|
| 216 |
-
return fig
|
| 217 |
-
except Exception as e:
|
| 218 |
-
print(f"Plot error: {e}")
|
| 219 |
-
last_chart = None
|
| 220 |
-
return None
|
| 221 |
-
|
| 222 |
-
def generate_insights(df, dfw, plan):
|
| 223 |
-
try:
|
| 224 |
-
context_parts = []
|
| 225 |
-
for op in plan.get("operations", []):
|
| 226 |
-
if op.get("op") == "describe":
|
| 227 |
-
cols = op.get("columns", [])
|
| 228 |
-
for col in cols:
|
| 229 |
-
if col in df.columns:
|
| 230 |
-
desc = df[col].describe()
|
| 231 |
-
context_parts.append(f"\n{col} Statistics:\n{desc.to_string()}")
|
| 232 |
-
elif op.get("op") == "groupby":
|
| 233 |
-
context_parts.append(f"\nGrouped Results:\n{dfw.head(10).to_string()}")
|
| 234 |
-
if len(dfw) > 0:
|
| 235 |
-
context_parts.append(f"\nResult Preview:\n{dfw.head(10).to_string()}")
|
| 236 |
-
insights_context = "\n".join(context_parts)
|
| 237 |
-
insights_prompt = f"""Based on this analysis, provide 4-6 detailed bullet points explaining key insights, patterns, and findings.
|
| 238 |
-
Analysis Context:
|
| 239 |
-
{insights_context}
|
| 240 |
-
Original Question Context:
|
| 241 |
-
{plan.get('narrative', '')}
|
| 242 |
-
Provide insights as bullet points."""
|
| 243 |
-
response = llm.invoke([
|
| 244 |
-
{"role": "system", "content": "You are a data insights expert."},
|
| 245 |
-
{"role": "user", "content": insights_prompt}
|
| 246 |
-
])
|
| 247 |
-
return response.content if hasattr(response, 'content') else str(response)
|
| 248 |
-
except Exception as e:
|
| 249 |
-
return f"Error generating insights: {str(e)}"
|
| 250 |
-
|
| 251 |
-
def analyze_question(question):
|
| 252 |
-
global uploaded_df, llm
|
| 253 |
-
if llm is None:
|
| 254 |
-
return "API not initialized. Please restart the application.", None, None
|
| 255 |
-
if uploaded_df is None:
|
| 256 |
-
return "Please upload a dataset first.", None, None
|
| 257 |
-
if not question.strip():
|
| 258 |
-
return "Please enter a question.", None, None
|
| 259 |
-
try:
|
| 260 |
-
data_ctx = make_context(uploaded_df)
|
| 261 |
-
user_msg = f"{question}\n\nDataset Context:\n{data_ctx}"
|
| 262 |
-
messages = [
|
| 263 |
-
{"role": "system", "content": SYSTEM_PROMPT},
|
| 264 |
-
{"role": "user", "content": user_msg}
|
| 265 |
-
]
|
| 266 |
-
response = llm.invoke(messages)
|
| 267 |
-
raw_text = response.content if hasattr(response, 'content') else str(response)
|
| 268 |
-
plan = parse_plan(raw_text)
|
| 269 |
-
if plan.get("type") == "explain":
|
| 270 |
-
return plan.get("narrative", "No explanation provided"), None, None
|
| 271 |
-
if plan.get("type") == "error":
|
| 272 |
-
return plan.get("narrative", "Error occurred"), None, None
|
| 273 |
-
dfw, describe_stats = execute_plan(uploaded_df, plan)
|
| 274 |
-
response_text = plan.get("narrative", "Analysis complete")
|
| 275 |
-
if describe_stats:
|
| 276 |
-
response_text += "\n\n Statistical Summary:\n"
|
| 277 |
-
for col, stats in describe_stats.items():
|
| 278 |
-
response_text += f"\n{col}:\n{stats.to_string()}\n"
|
| 279 |
-
fig = None
|
| 280 |
-
if plan.get("plot"):
|
| 281 |
-
fig = create_plot(uploaded_df, dfw, plan, describe_stats)
|
| 282 |
-
if fig:
|
| 283 |
-
response_text += "\n\n Chart generated successfully!"
|
| 284 |
-
if plan.get("insights_needed") and fig:
|
| 285 |
-
insights = generate_insights(uploaded_df, dfw, plan)
|
| 286 |
-
response_text += f"\n\n Key Insights:\n{insights}"
|
| 287 |
-
result_table = None
|
| 288 |
-
if not describe_stats and len(dfw) != len(uploaded_df):
|
| 289 |
-
result_table = dfw.head(50)
|
| 290 |
-
return response_text, fig, result_table
|
| 291 |
-
except Exception as e:
|
| 292 |
-
return f"Error during analysis: {str(e)}", None, None
|
| 293 |
-
|
| 294 |
-
# ---------------- UI (Gradio) ----------------
|
| 295 |
-
custom_css = """
|
| 296 |
-
.gradio-container { max-width: 1200px !important; margin: 0 auto !important; }
|
| 297 |
-
.header-box { background: linear-gradient(135deg, #f6f0ff 0%, #fff0fb 100%); border: 2px solid #e9d8ff; border-radius: 12px; padding: 18px; text-align: center;}
|
| 298 |
-
.left-panel { background:#f7f7fb; border-radius:10px; padding:12px; height:100%; }
|
| 299 |
-
.right-panel { background:#ffffff; border-radius:10px; padding:12px; height:100%; box-shadow: 0 6px 18px rgba(20,20,20,0.04); }
|
| 300 |
-
.section-title { font-weight:600; font-size:16px; margin-bottom:8px; }
|
| 301 |
-
.tools-vertical .gr-button { width:100%; margin-bottom:8px; text-align:left; }
|
| 302 |
-
.small-muted { font-size:12px; color:#666; }
|
| 303 |
-
.result-box { border:1px solid #eee; border-radius:8px; padding:10px; background:#fafafa; min-height:120px; }
|
| 304 |
-
"""
|
| 305 |
-
|
| 306 |
-
with gr.Blocks(theme=gr.themes.Soft(primary_hue="purple"), css=custom_css) as demo:
|
| 307 |
-
gr.HTML("<div class='header-box'><h1 style='margin:0;color:#5B21B6;'>SparkNova</h1><div style='margin-top:6px;color:#666;font-style:italic;'>An AI-powered data exploration and insight generator</div></div>")
|
| 308 |
-
|
| 309 |
-
with gr.Row():
|
| 310 |
-
with gr.Column(scale=2):
|
| 311 |
-
file_input = gr.File(label="Upload CSV", file_types=[".csv"])
|
| 312 |
-
format_select = gr.Dropdown(["Preview","JSON","CSV","Excel"], value="Preview", label="Show preview as")
|
| 313 |
-
dataset_info = gr.Markdown()
|
| 314 |
-
dataset_preview = gr.Dataframe(wrap=True, label="Dataset Preview")
|
| 315 |
-
with gr.Column(scale=3):
|
| 316 |
-
gr.Markdown("### Sample Questions")
|
| 317 |
-
gr.Markdown("- What is the average value of numeric columns?\n- Show a bar chart of sales by region\n- Which column correlates most with revenue?\n- Generate a pie chart for product categories\n- Find missing values\n- Create a line chart showing trends over time\n- Top 5 values in the dataset\n- Show statistical summary of all columns", elem_classes="small-muted")
|
| 318 |
-
|
| 319 |
-
# Hook upload to handler
|
| 320 |
-
def upload_handler(file, fmt):
|
| 321 |
-
global uploaded_df, dataset_name
|
| 322 |
-
if file is None:
|
| 323 |
-
return "No file uploaded", None
|
| 324 |
-
try:
|
| 325 |
-
uploaded_df = pd.read_csv(file.name)
|
| 326 |
-
uploaded_df = clean_numeric(uploaded_df)
|
| 327 |
-
dataset_name = os.path.basename(file.name)
|
| 328 |
-
info_text = f"**Dataset Loaded:** {dataset_name} — {uploaded_df.shape[0]} rows × {uploaded_df.shape[1]} cols"
|
| 329 |
-
preview_df = uploaded_df.head(10)
|
| 330 |
-
return info_text, preview_df
|
| 331 |
-
except Exception as e:
|
| 332 |
-
return f"Error loading file: {e}", None
|
| 333 |
-
|
| 334 |
-
file_input.change(upload_handler, inputs=[file_input, format_select], outputs=[dataset_info, dataset_preview])
|
| 335 |
-
|
| 336 |
-
with gr.Row():
|
| 337 |
-
with gr.Column(scale=1, elem_classes="left-panel"):
|
| 338 |
-
gr.Markdown("#### Choose an Analysis Type", elem_classes="section-title")
|
| 339 |
-
btn_describe = gr.Button("Describe Dataset")
|
| 340 |
-
btn_summary = gr.Button("Summary Statistics")
|
| 341 |
-
btn_corr = gr.Button("Show Correlations")
|
| 342 |
-
btn_top5 = gr.Button("Top 5 Rows")
|
| 343 |
-
btn_missing = gr.Button("Missing Values")
|
| 344 |
-
btn_groupby = gr.Button("Group & Aggregate")
|
| 345 |
-
btn_calc = gr.Button("Calculate Expression")
|
| 346 |
-
btn_find = gr.Button("Find Highest Correlation")
|
| 347 |
-
gr.Markdown("#### Visualization Types", elem_classes="section-title")
|
| 348 |
-
chart_bar = gr.Button("Bar Chart")
|
| 349 |
-
chart_line = gr.Button("Line Chart")
|
| 350 |
-
chart_scatter = gr.Button("Scatter Plot")
|
| 351 |
-
chart_pie = gr.Button("Pie Chart")
|
| 352 |
-
chart_hist = gr.Button("Histogram")
|
| 353 |
-
chart_box = gr.Button("Box Plot")
|
| 354 |
-
chart_heat = gr.Button("Heatmap")
|
| 355 |
-
gr.Markdown("#### Other", elem_classes="section-title")
|
| 356 |
-
btn_insights = gr.Button("Generate Insights (LLM)")
|
| 357 |
-
btn_reload = gr.Button("Reload Dataset")
|
| 358 |
-
|
| 359 |
-
with gr.Column(scale=2, elem_classes="right-panel"):
|
| 360 |
-
gr.Markdown("#### Analysis Output", elem_classes="section-title")
|
| 361 |
-
output_text = gr.Textbox(label="Detailed Response", interactive=False, lines=10, show_copy_button=True)
|
| 362 |
-
gr.Markdown("#### Result Data", elem_classes="section-title")
|
| 363 |
-
result_table = gr.Dataframe(wrap=True, interactive=False)
|
| 364 |
-
gr.Markdown("#### Chart Preview", elem_classes="section-title")
|
| 365 |
-
chart_display = gr.Plot()
|
| 366 |
-
chart_explain = gr.Markdown("Chart explanation will appear here.")
|
| 367 |
-
gr.Markdown("#### Ask a Question (LLM)", elem_classes="section-title")
|
| 368 |
-
question_box = gr.Textbox(label="Type your question here", lines=3, placeholder="Ask the agent (LLM)...")
|
| 369 |
-
ask_btn = gr.Button("Ask (LLM)", variant="primary")
|
| 370 |
-
|
| 371 |
-
# Map actions to natural language questions
|
| 372 |
-
action_map = {
|
| 373 |
-
"Describe Dataset": "Describe the dataset statistically.",
|
| 374 |
-
"Summary Statistics": "Provide summary statistics for all columns.",
|
| 375 |
-
"Show Correlations": "Which columns have the highest correlation? Provide correlation table.",
|
| 376 |
-
"Top 5 Rows": "Show the top 5 rows of the dataset.",
|
| 377 |
-
"Missing Values": "List missing values in each column.",
|
| 378 |
-
"Group & Aggregate": "Group data and compute typical aggregations (provide example groupby).",
|
| 379 |
-
"Calculate Expression": "Create a calculated column (example: create Col_Calc = ColA * ColB).",
|
| 380 |
-
"Find Highest Correlation": "Which column has highest correlation with revenue?",
|
| 381 |
-
"Generate Insights (LLM)": "Generate short insights for the dataset (4-6 bullets)."
|
| 382 |
-
}
|
| 383 |
-
|
| 384 |
-
def run_action(action_label):
|
| 385 |
-
if uploaded_df is None:
|
| 386 |
-
return "Please upload a dataset first.", None, None
|
| 387 |
-
question = action_map.get(action_label, action_label)
|
| 388 |
-
text, fig, table = analyze_question(question)
|
| 389 |
-
return text, fig, table
|
| 390 |
-
|
| 391 |
-
# Connect buttons
|
| 392 |
-
btn_describe.click(lambda: run_action("Describe Dataset"), outputs=[output_text, chart_display, result_table])
|
| 393 |
-
btn_summary.click(lambda: run_action("Summary Statistics"), outputs=[output_text, chart_display, result_table])
|
| 394 |
-
btn_corr.click(lambda: run_action("Show Correlations"), outputs=[output_text, chart_display, result_table])
|
| 395 |
-
btn_top5.click(lambda: run_action("Top 5 Rows"), outputs=[output_text, chart_display, result_table])
|
| 396 |
-
btn_missing.click(lambda: run_action("Missing Values"), outputs=[output_text, chart_display, result_table])
|
| 397 |
-
btn_groupby.click(lambda: run_action("Group & Aggregate"), outputs=[output_text, chart_display, result_table])
|
| 398 |
-
btn_calc.click(lambda: run_action("Calculate Expression"), outputs=[output_text, chart_display, result_table])
|
| 399 |
-
btn_find.click(lambda: run_action("Find Highest Correlation"), outputs=[output_text, chart_display, result_table])
|
| 400 |
-
btn_insights.click(lambda: run_action("Generate Insights (LLM)"), outputs=[output_text, chart_display, result_table])
|
| 401 |
-
btn_reload.click(lambda: upload_handler(file_input, format_select), outputs=[dataset_info, dataset_preview])
|
| 402 |
-
|
| 403 |
-
# Chart requests
|
| 404 |
-
def request_chart(chart_type):
|
| 405 |
-
if uploaded_df is None:
|
| 406 |
-
return "Please upload dataset first.", None, "No explanation"
|
| 407 |
-
q = f"Please produce a {chart_type} showing something meaningful from the dataset. Include a plot specification in the JSON."
|
| 408 |
-
text, fig, table = analyze_question(q)
|
| 409 |
-
explanation = f"This is a {chart_type} generated by the agent."
|
| 410 |
-
return text, fig, explanation
|
| 411 |
-
|
| 412 |
-
chart_bar.click(lambda: request_chart("bar"), outputs=[output_text, chart_display, chart_explain])
|
| 413 |
-
chart_line.click(lambda: request_chart("line"), outputs=[output_text, chart_display, chart_explain])
|
| 414 |
-
chart_scatter.click(lambda: request_chart("scatter"), outputs=[output_text, chart_display, chart_explain])
|
| 415 |
-
chart_pie.click(lambda: request_chart("pie"), outputs=[output_text, chart_display, chart_explain])
|
| 416 |
-
chart_hist.click(lambda: request_chart("hist"), outputs=[output_text, chart_display, chart_explain])
|
| 417 |
-
chart_box.click(lambda: request_chart("box"), outputs=[output_text, chart_display, chart_explain])
|
| 418 |
-
chart_heat.click(lambda: request_chart("heatmap"), outputs=[output_text, chart_display, chart_explain])
|
| 419 |
-
|
| 420 |
-
# Freeform question via LLM
|
| 421 |
-
def ask_freeform(q):
|
| 422 |
-
if uploaded_df is None:
|
| 423 |
-
return "Please upload a dataset first.", None, None
|
| 424 |
-
return analyze_question(q)
|
| 425 |
-
|
| 426 |
-
ask_btn.click(ask_freeform, inputs=[question_box], outputs=[output_text, chart_display, result_table])
|
| 427 |
-
|
| 428 |
-
# --------------------- Download Section (FIXED) ---------------------
|
| 429 |
-
gr.Markdown("### Download Options")
|
| 430 |
-
|
| 431 |
-
# Define download functions with proper state management
|
| 432 |
-
def download_result_data():
|
| 433 |
-
"""Download the result table as CSV"""
|
| 434 |
-
if uploaded_df is None:
|
| 435 |
-
return None
|
| 436 |
-
try:
|
| 437 |
-
temp_file = tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.csv', newline='')
|
| 438 |
-
uploaded_df.to_csv(temp_file.name, index=False)
|
| 439 |
-
return temp_file.name
|
| 440 |
-
except Exception as e:
|
| 441 |
-
print(f"Error saving results: {e}")
|
| 442 |
-
return None
|
| 443 |
-
|
| 444 |
-
def download_chart_image():
|
| 445 |
-
"""Download the last generated chart as PNG"""
|
| 446 |
-
global last_chart
|
| 447 |
-
if last_chart is None:
|
| 448 |
-
return None
|
| 449 |
-
try:
|
| 450 |
-
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.png')
|
| 451 |
-
last_chart.write_image(temp_file.name, width=1200, height=700)
|
| 452 |
-
return temp_file.name
|
| 453 |
-
except Exception as e:
|
| 454 |
-
print(f"Error saving chart: {e}")
|
| 455 |
-
return None
|
| 456 |
-
|
| 457 |
-
# Create file outputs
|
| 458 |
-
result_file_output = gr.File(label="Download Results", visible=True)
|
| 459 |
-
chart_file_output = gr.File(label="Download Chart", visible=True)
|
| 460 |
-
|
| 461 |
-
# Buttons with proper outputs
|
| 462 |
-
download_result_btn = gr.Button(" Download Results CSV", variant="secondary")
|
| 463 |
-
download_chart_btn = gr.Button(" Download Chart PNG", variant="secondary")
|
| 464 |
-
|
| 465 |
-
# Connect buttons
|
| 466 |
-
download_result_btn.click(download_result_data, inputs=[], outputs=[result_file_output])
|
| 467 |
-
download_chart_btn.click(download_chart_image, inputs=[], outputs=[chart_file_output])
|
| 468 |
-
|
| 469 |
-
gr.HTML("<div style='text-align:center;color:#666;padding-top:10px;'>Powered by GROQ LLM & Gradio — SparkNova</div>")
|
| 470 |
-
|
| 471 |
-
# Initialize LLM and launch
|
| 472 |
-
if __name__ == "__main__":
|
| 473 |
-
initialize_llm()
|
| 474 |
-
demo.launch(server_name="0.0.0.0", server_port=7860, debug=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|