SPARKNOVA / app_backup.py
Tamannathakur's picture
Upload 7 files
75bedb4 verified
raw
history blame
24.7 kB
import gradio as gr
import pandas as pd
import os
import base64
import json
from data_engine import (
clean_numeric, run_analysis, create_visualization, handle_missing_data,
undo_last_change, undo_all_changes, download_dataset,
display_data_format, display_text_format
)
try:
from ai_agent import initialize_llm, analyze_question
except (ImportError, RuntimeError) as e:
print(f"Warning: Full AI agent not available: {e}")
def initialize_llm():
return None
def analyze_question(question, columns, df, llm):
return "AI agent not available. Please check dependencies.", None, None
from prompts import SAMPLE_QUESTIONS
llm = None
uploaded_df = None
original_df = None
dataset_name = None
change_history = []
logo_path = os.path.join(os.getcwd(), "public/main-logo.png")
def embed_image_base64(path):
with open(path, "rb") as f:
return "data:image/png;base64," + base64.b64encode(f.read()).decode()
logo_b64 = embed_image_base64(logo_path)
with open("public/style.css") as f:
css = f.read()
# Enhanced custom CSS for dropdown
custom_css = css + """
.dropdown-wrapper {
width: 100%;
margin: 10px 0;
}
.dropdown-button {
width: 100%;
padding: 12px 16px;
background: rgba(138, 43, 226, 0.15);
border: 1px solid rgba(138, 43, 226, 0.3);
border-radius: 8px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
color: #e0e0e0;
transition: all 0.3s;
}
.dropdown-button:hover {
background: rgba(138, 43, 226, 0.25);
border-color: rgba(138, 43, 226, 0.5);
}
.dropdown-menu {
display: none;
position: absolute;
width: calc(100% - 32px);
max-height: 300px;
overflow-y: auto;
background: #2a2a3e;
border: 1px solid rgba(138, 43, 226, 0.3);
border-radius: 8px;
margin-top: 5px;
z-index: 1000;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.dropdown-menu.show {
display: block;
}
.dropdown-item {
padding: 12px 16px;
cursor: pointer;
color: #e0e0e0;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 10px;
}
.dropdown-item:hover {
background: rgba(138, 43, 226, 0.2);
}
.dropdown-item.selected {
background: rgba(138, 43, 226, 0.3);
color: #fff;
font-weight: 500;
}
.dropdown-item::before {
content: '☐';
font-size: 18px;
}
.dropdown-item.selected::before {
content: '☑';
color: #8a2be2;
}
.selected-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 10px;
min-height: 20px;
}
.tag {
background: rgba(138, 43, 226, 0.3);
color: #fff;
padding: 6px 12px;
border-radius: 20px;
font-size: 13px;
display: flex;
align-items: center;
gap: 8px;
}
.tag .remove {
cursor: pointer;
font-weight: bold;
color: #ff6b6b;
font-size: 16px;
line-height: 1;
}
.tag .remove:hover {
color: #ff4444;
}
.dropdown-arrow {
transition: transform 0.3s;
}
.dropdown-arrow.open {
transform: rotate(180deg);
}
"""
def upload_dataset(file):
global uploaded_df, original_df, dataset_name, change_history
if file is None:
return "No file uploaded", None, [], ""
try:
if file.name.endswith('.csv'):
uploaded_df = pd.read_csv(file.name)
elif file.name.endswith(('.xlsx', '.xls')):
uploaded_df = pd.read_excel(file.name)
else:
return "Unsupported file format", None, [], ""
original_df = uploaded_df.copy()
dataset_name = os.path.basename(file.name)
change_history = []
info = f"**Dataset:** {dataset_name}\n**Shape:** {uploaded_df.shape}\n**Columns:** {', '.join(uploaded_df.columns)}"
columns = list(uploaded_df.columns)
# Create the dropdown HTML with updated columns
dropdown_html = create_dropdown_html(columns, [])
return info, uploaded_df.head(), columns, dropdown_html
except Exception as e:
return f"Error uploading file: {str(e)}", None, [], ""
def create_dropdown_html(available_columns, selected_columns):
"""Create the dropdown HTML structure"""
if not available_columns:
return """
<div class="dropdown-wrapper">
<div class="dropdown-button" style="opacity: 0.5; cursor: not-allowed;">
<span>No columns available</span>
<span class="dropdown-arrow">▼</span>
</div>
</div>
"""
columns_json = json.dumps(available_columns)
selected_json = json.dumps(selected_columns)
dropdown_items = ''.join([
f'<div class="dropdown-item{" selected" if col in selected_columns else ""}" data-column="{col}">{col}</div>'
for col in available_columns
])
selected_tags = ''.join([
f'<span class="tag" data-column="{col}">{col}<span class="remove">×</span></span>'
for col in selected_columns
])
return f"""
<div class="dropdown-wrapper">
<div class="dropdown-button" onclick="toggleDropdown()">
<span id="dropdown-text">{len(selected_columns)} column(s) selected" if selected_columns else "Choose columns to work with</span>
<span class="dropdown-arrow" id="dropdown-arrow">▼</span>
</div>
<div class="dropdown-menu" id="dropdown-menu">
{dropdown_items}
</div>
<div class="selected-tags" id="selected-tags">
{selected_tags}
</div>
</div>
<input type="hidden" id="available-columns" value='{columns_json}'>
<input type="hidden" id="selected-columns-data" value='{selected_json}'>
<script>
(function() {{
let selectedColumns = {selected_json};
let availableColumns = {columns_json};
function updateHiddenInput() {{
const hiddenInput = document.getElementById('selected-columns-data');
if (hiddenInput) {{
hiddenInput.value = JSON.stringify(selectedColumns);
// Trigger change event for Gradio
hiddenInput.dispatchEvent(new Event('input', {{ bubbles: true }}));
}}
}}
window.toggleDropdown = function() {{
const menu = document.getElementById('dropdown-menu');
const arrow = document.getElementById('dropdown-arrow');
if (menu && arrow) {{
menu.classList.toggle('show');
arrow.classList.toggle('open');
}}
}}
// Handle dropdown item clicks
document.addEventListener('click', function(e) {{
if (e.target.classList.contains('dropdown-item')) {{
const column = e.target.getAttribute('data-column');
if (selectedColumns.includes(column)) {{
selectedColumns = selectedColumns.filter(c => c !== column);
e.target.classList.remove('selected');
}} else {{
selectedColumns.push(column);
e.target.classList.add('selected');
}}
updateSelectedDisplay();
updateHiddenInput();
}}
// Handle tag remove
if (e.target.classList.contains('remove')) {{
const tag = e.target.parentElement;
const column = tag.getAttribute('data-column');
selectedColumns = selectedColumns.filter(c => c !== column);
// Update dropdown item
const dropdownItem = document.querySelector(`.dropdown-item[data-column="${{column}}"]`);
if (dropdownItem) {{
dropdownItem.classList.remove('selected');
}}
updateSelectedDisplay();
updateHiddenInput();
}}
// Close dropdown when clicking outside
if (!e.target.closest('.dropdown-wrapper')) {{
const menu = document.getElementById('dropdown-menu');
const arrow = document.getElementById('dropdown-arrow');
if (menu && arrow) {{
menu.classList.remove('show');
arrow.classList.remove('open');
}}
}}
}});
function updateSelectedDisplay() {{
const tagsContainer = document.getElementById('selected-tags');
const dropdownText = document.getElementById('dropdown-text');
if (tagsContainer && dropdownText) {{
if (selectedColumns.length === 0) {{
dropdownText.textContent = 'Choose columns to work with';
tagsContainer.innerHTML = '';
}} else {{
dropdownText.textContent = selectedColumns.length + ' column(s) selected';
tagsContainer.innerHTML = selectedColumns.map(col =>
`<span class="tag" data-column="${{col}}">${{col}}<span class="remove">×</span></span>`
).join('');
}}
}}
}}
}})();
</script>
"""
def get_selected_columns_from_html():
"""This would normally extract from the hidden input, but we'll use State instead"""
return []
def clear_dataset():
global uploaded_df, original_df, dataset_name, change_history
uploaded_df = None
original_df = None
dataset_name = None
change_history = []
return "", None, [], create_dropdown_html([], [])
def update_preview(format_type, columns):
global uploaded_df
if uploaded_df is None or format_type == "None":
return None, "", gr.update(visible=False), gr.update(visible=False)
try:
# Use selected columns or all columns if none selected
selected_columns = columns if columns else list(uploaded_df.columns)
selected_df = uploaded_df[selected_columns]
if format_type == "DataFrame":
return selected_df, "", gr.update(visible=True), gr.update(visible=False)
elif format_type == "JSON":
json_str = selected_df.to_json(indent=2)
return None, json_str, gr.update(visible=False), gr.update(visible=True)
elif format_type == "Dictionary":
dict_str = str(selected_df.to_dict())
return None, dict_str, gr.update(visible=False), gr.update(visible=True)
except Exception as e:
return None, f"Error: {str(e)}", gr.update(visible=False), gr.update(visible=True)
def handle_analysis_change(analysis_type, columns):
global uploaded_df
if uploaded_df is None or analysis_type == "None":
return "", "", None
try:
result = run_analysis(uploaded_df, analysis_type, columns)
if isinstance(result, tuple):
text_result, data_result = result
return text_result, "", data_result
else:
return str(result), "", None
except Exception as e:
return f"Error in analysis: {str(e)}", "", None
def handle_viz_change(viz_type, columns):
global uploaded_df
if uploaded_df is None or viz_type == "None":
return None, None, "", ""
try:
chart, explanation = create_visualization(uploaded_df, viz_type, columns)
return chart, None, explanation, ""
except Exception as e:
return None, None, f"Error creating visualization: {str(e)}", ""
def show_constant_input(handler_type):
return gr.update(visible=(handler_type == "Constant Fill"))
def handle_data_and_refresh(handler, columns, constant, analysis_type):
global uploaded_df, change_history
if uploaded_df is None or handler == "None":
return "", "", [], ""
try:
result = handle_missing_data(uploaded_df, handler, columns, constant)
change_history.append(uploaded_df.copy())
analysis_result = ""
if analysis_type != "None":
analysis_result = str(run_analysis(uploaded_df, analysis_type, columns))
new_columns = list(uploaded_df.columns)
info = f"Applied {handler} to dataset\nShape: {uploaded_df.shape}"
dropdown_html = create_dropdown_html(new_columns, new_columns)
return result, analysis_result, new_columns, info, dropdown_html
except Exception as e:
return f"Error: {str(e)}", "", [], "", ""
def handle_undo_and_refresh(analysis_type, undo_all):
global uploaded_df, change_history
if uploaded_df is None:
return "", "", [], "", ""
try:
if undo_all:
result = undo_all_changes()
else:
result = undo_last_change()
analysis_result = ""
if analysis_type != "None":
analysis_result = str(run_analysis(uploaded_df, analysis_type, []))
new_columns = list(uploaded_df.columns)
info = f"Dataset restored\nShape: {uploaded_df.shape}"
dropdown_html = create_dropdown_html(new_columns, new_columns)
return result, analysis_result, new_columns, info, dropdown_html
except Exception as e:
return f"Error: {str(e)}", "", [], "", ""
def handle_question_analysis(question, columns):
global llm, uploaded_df
if llm is None:
llm = initialize_llm()
return analyze_question(question, columns, uploaded_df, llm)
def sync_selected_columns(selected_json_str):
"""Sync selected columns from hidden input"""
try:
if selected_json_str:
return json.loads(selected_json_str)
except:
pass
return []
with gr.Blocks(theme=gr.themes.Soft(), css=custom_css) as demo:
popup_visible = gr.State(False)
selected_columns_state = gr.State([])
gr.HTML(f"""
<!-- How to Use Section - Fixed Left Side -->
<div class="how-to-use-sidebar">
<div class="how-to-use-content">
<h3>How to Use</h3>
<ul>
<li>• Use Display Format to preview or view summary of your data.</li>
<li>• Select an Analysis Type to explore key insights and patterns.</li>
<li>• Choose a Visualization Type to generate charts and graphical views.</li>
<li>• Ask any question about your data in the text box and click Analyze to get AI-driven results.</li>
</ul>
</div>
</div>
<div class="header-box">
<div style="flex:1; display:flex; justify-content:flex-start; width: 100px;">
<img src="{logo_b64}" width="120" style="margin-bottom:70px; margin-top: 30px; opacity:0.95;">
</div>
<div style="flex:1; display:flex; justify-content:center;">
<h1 class="header-title">SparkNova</h1>
</div>
<div style="flex:1;"></div>
</div>
<div style="text-align: center; display: flex; justify-content: center;">
<p style="margin-top:20px; font-size:1.30em; opacity:0.92; color: white; max-width: 1450px; line-height: 1.45;">
SparkNova is a data analysis platform that allows users to upload datasets, explore insights, visualize patterns, and ask questions about their data. It simplifies data analytics by automating cleaning, visualization, and intelligent interpretation for quick decision-making.
</p>
</div>
""")
with gr.Row(elem_classes="first-row"):
# Left Column - Upload Dataset
with gr.Column(scale=1):
with gr.Group(elem_id="upload-wrapper", elem_classes="upload-section"):
gr.Markdown("### Upload Dataset", elem_classes="upload-title")
file_input = gr.File(
label="Choose File",
file_types=[".csv", ".xlsx", ".xls"],
elem_classes="upload-card",
)
dataset_info = gr.Markdown(value="", elem_classes="upload-info")
clear_btn = gr.Button("Clear Dataset", variant="secondary", size="sm")
# Middle Column - Custom Dropdown Column Selector
with gr.Column(scale=1):
with gr.Group():
gr.Markdown("### 📋 Column Selector")
column_dropdown_html = gr.HTML(create_dropdown_html([], []))
# Hidden textbox to capture selected columns from JavaScript
selected_columns_json = gr.Textbox(visible=False, elem_id="selected-columns-data")
# Right Column - Display Format
with gr.Column(scale=1):
with gr.Group():
gr.Markdown("### 🔍 Display Format")
format_selector = gr.Dropdown(
choices=["None", "DataFrame", "JSON", "Dictionary"],
value="None",
label="Format Type"
)
# Hidden elements
constant_input = gr.Textbox(
label="Constant Value",
placeholder="Enter value",
visible=False
)
data_handling_output = gr.Textbox(label="Results", lines=2, visible=False, interactive=False)
download_file = gr.File(label="Download", visible=False)
with gr.Row(elem_classes="second-row"):
with gr.Group(elem_classes="overflow-class"):
gr.Markdown("### 📊 Analysis Type")
analysis_selector = gr.Dropdown(
choices=["None", "Summary", "Describe", "Top 5 Rows", "Bottom 5 Rows",
"Missing Values", "Group & Aggregate", "Calculate Expressions", "Highest Correlation"],
value="None",
label="Select Analysis"
)
with gr.Group():
gr.Markdown("### 📈 Visualization")
viz_selector = gr.Dropdown(
choices=["None", "Bar Chart", "Line Chart", "Scatter Plot", "Pie Chart",
"Histogram", "Box Plot", "Heat Map"],
value="None",
label="Chart Type"
)
with gr.Group():
gr.Markdown("### 🔧 Data Handling")
data_handler = gr.Dropdown(
choices=["None", "Forward Fill", "Backward Fill", "Constant Fill",
"Mean Fill", "Median Fill", "Mode Fill", "Drop Columns"],
value="None",
label="Method"
)
with gr.Row():
apply_btn = gr.Button("Apply", variant="primary", size="sm")
undo_last_btn = gr.Button("Undo", variant="secondary", size="sm")
with gr.Row():
undo_all_btn = gr.Button("Undo All", variant="secondary", size="sm")
download_btn = gr.Button("Download", variant="secondary", size="sm")
with gr.Column(scale=2):
preview_heading = gr.Markdown("", visible=False)
dataset_preview = gr.Dataframe(visible=False)
text_preview = gr.Textbox(label="Text Preview", lines=8, visible=False)
analysis_heading = gr.Markdown("### Analysis Results", visible=False)
analysis_output = gr.Textbox(label="Analysis Output", lines=6, visible=False, interactive=False)
analysis_data_table = gr.Dataframe(label="Data Table", visible=False)
chart_output_new = gr.Plot(label="Chart", visible=False)
chart_explanation = gr.Textbox(label="Chart Analysis", lines=3, visible=False, interactive=False)
with gr.Column(visible=False, elem_classes="chat-popup-box") as chat_popup:
gr.HTML('<div class="chat-header"><h3>💬 Ask a Question</h3></div>')
with gr.Column(elem_classes="chat-content-area"):
gr.HTML('''
<div class="sample-questions-box">
<h4>Sample Questions</h4>
<ul style="list-style: none; padding: 0; margin: 10px 0;">
<li style="margin: 8px 0; color: #555;">• What is the average of all numeric columns?</li>
<li style="margin: 8px 0; color: #555;">• Show me the correlation between columns</li>
<li style="margin: 8px 0; color: #555;">• What are the missing values in my dataset?</li>
</ul>
</div>
''')
gr.HTML('<div style="margin: 20px 0 10px 0; font-weight: 600; color: #333; font-size: 15px;">✍️ Type Your Question Here</div>')
question_input = gr.Textbox(
placeholder="Ask anything about your dataset...",
lines=2,
elem_classes="question-input-field",
show_label=False
)
with gr.Row():
ask_btn = gr.Button("Ask Question", variant="primary", size="sm")
close_chat_btn = gr.Button("Close", variant="secondary", size="sm")
answer_output = gr.Textbox(
label="Answer",
lines=4,
interactive=False,
elem_id="answer-output"
)
answer_chart = gr.Plot(label="Generated Chart", visible=False)
answer_data = gr.Dataframe(label="Generated Data", visible=False)
# Floating Chat Button
floating_chat_button = gr.Button("💬", elem_classes="floating-chat-btn")
# Event handlers
file_input.upload(
fn=upload_dataset,
inputs=[file_input],
outputs=[dataset_info, dataset_preview, selected_columns_state, column_dropdown_html]
)
clear_btn.click(
fn=clear_dataset,
outputs=[dataset_info, dataset_preview, selected_columns_state, column_dropdown_html]
)
# Sync selected columns from hidden input
selected_columns_json.change(
fn=sync_selected_columns,
inputs=[selected_columns_json],
outputs=[selected_columns_state]
)
format_selector.change(
fn=update_preview,
inputs=[format_selector, selected_columns_state],
outputs=[dataset_preview, text_preview, preview_heading, analysis_heading]
)
analysis_selector.change(
fn=handle_analysis_change,
inputs=[analysis_selector, selected_columns_state],
outputs=[analysis_output, chart_explanation, analysis_data_table]
)
viz_selector.change(
fn=handle_viz_change,
inputs=[viz_selector, selected_columns_state],
outputs=[chart_output_new, analysis_data_table, chart_explanation, analysis_output]
)
data_handler.change(
fn=show_constant_input,
inputs=[data_handler],
outputs=[constant_input]
)
apply_btn.click(
fn=handle_data_and_refresh,
inputs=[data_handler, selected_columns_state, constant_input, analysis_selector],
outputs=[data_handling_output, analysis_output, selected_columns_state, dataset_info, column_dropdown_html]
)
undo_last_btn.click(
fn=lambda analysis_type: handle_undo_and_refresh(analysis_type, False),
inputs=[analysis_selector],
outputs=[data_handling_output, analysis_output, selected_columns_state, dataset_info, column_dropdown_html]
)
undo_all_btn.click(
fn=lambda analysis_type: handle_undo_and_refresh(analysis_type, True),
inputs=[analysis_selector],
outputs=[data_handling_output, analysis_output, selected_columns_state, dataset_info, column_dropdown_html]
)
# Toggle chat popup visibility
def toggle_chat_popup(current_visible):
new_visible = not current_visible
return gr.update(visible=new_visible), new_visible
floating_chat_button.click(
toggle_chat_popup,
inputs=[popup_visible],
outputs=[chat_popup, popup_visible]
)
close_chat_btn.click(
fn=lambda: (gr.update(visible=False), False),
outputs=[chat_popup, popup_visible]
)
ask_btn.click(
fn=handle_question_analysis,
inputs=[question_input, selected_columns_state],
outputs=[answer_output, answer_chart, answer_data]
)
def handle_download():
if uploaded_df is not None:
filepath = download_dataset(uploaded_df, dataset_name)
return gr.update(value=filepath, visible=bool(filepath))
return gr.update(visible=False)
download_btn.click(handle_download, outputs=[download_file])
if __name__ == "__main__":
demo.launch(share=True, debug=True)