File size: 6,349 Bytes
3917a5b
f3946f4
 
3917a5b
 
82a06d3
 
 
 
3917a5b
 
 
 
 
 
d59dddf
82a06d3
3917a5b
d59dddf
 
3917a5b
 
d59dddf
82a06d3
d59dddf
 
 
 
82a06d3
3917a5b
 
 
d59dddf
3917a5b
 
 
 
 
 
d59dddf
3917a5b
d59dddf
3917a5b
 
 
 
 
82a06d3
 
 
d59dddf
 
 
 
 
 
3917a5b
 
82a06d3
3917a5b
 
 
 
82a06d3
 
 
 
d59dddf
82a06d3
 
d59dddf
 
82a06d3
d59dddf
f3946f4
 
 
d59dddf
82a06d3
 
 
d59dddf
f3946f4
 
 
d59dddf
f3946f4
d59dddf
f3946f4
d59dddf
82a06d3
 
 
d59dddf
6bf8a42
d59dddf
6bf8a42
 
 
d59dddf
 
3917a5b
 
d59dddf
3917a5b
d59dddf
3917a5b
d59dddf
 
3917a5b
 
 
 
 
d59dddf
 
0133d2a
d59dddf
6bf8a42
0133d2a
d59dddf
6bf8a42
d59dddf
 
 
 
3917a5b
d59dddf
3917a5b
82a06d3
3917a5b
d59dddf
3917a5b
 
d59dddf
3917a5b
0133d2a
d59dddf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import os
import io
import sys
import gradio as gr
import pandas as pd
import matplotlib
# Use a non-interactive backend for Matplotlib to prevent cloud server crashes
matplotlib.use('Agg') 
import matplotlib.pyplot as plt
from google import genai

# 1. Initialize the Gemini client securely from the environment
api_key = os.environ.get('GEMINI_API_KEY')
client = genai.Client(api_key=api_key)

# 2. Define the core data and plotting logic
def analyze_and_plot(uploaded_file, user_question):
    if uploaded_file is None:
        # Return error message, hide chart, disable download button
        return "⚠️ Please upload a CSV file first.", None, gr.update(visible=False)
        
    try:
        # Clear any previous figures and delete old reports to keep clean states
        plt.close('all')
        if os.path.exists("analysis_report.txt"):
            os.remove("analysis_report.txt")
        if os.path.exists("output_chart.png"):
            os.remove("output_chart.png")
        
        # Read the uploaded CSV file using Pandas
        df = pd.read_csv(uploaded_file.name)
        
        # Gather basic details about the dataset to feed the AI context
        row_count = len(df)
        col_count = len(df.columns)
        column_names = ", ".join(df.columns.tolist())
        data_preview = df.head(3).to_string()
        data_types = df.dtypes.to_string()
        
        # System instructions forcing Gemini to return structured code and metrics
        system_context = (
            f"You are an expert Python Data Analyst. The user has uploaded a dataset.\n"
            f"Dataset Statistics:\n"
            f"- Total Rows: {row_count}\n"
            f"- Total Columns: {col_count}\n"
            f"- Column Names: {column_names}\n\n"
            f"Data Types:\n{data_types}\n\n"
            f"Preview:\n{data_preview}\n\n"
            f"INSTRUCTIONS:\n"
            f"1. Answer the user's question clearly.\n"
            f"2. If performing a regression or statistical test, you MUST explicitly state the key numerical metrics "
            f"(such as R-squared value, Slope/Coefficients, Intercept, or p-values) in your final written text response.\n"
            f"3. If a chart is requested, include a clean Python code block using 'matplotlib.pyplot' and 'sklearn'. "
            f"Assume the dataframe is loaded as 'df'.\n"
            f"4. Crucial: The LAST line of your chart code block must save the image exactly as: plt.savefig('output_chart.png')\n"
            f"5. Do not include plt.show(). Only save it to 'output_chart.png'."
        )
        
        # Send the metadata context and user query to Gemini
        response = client.models.generate_content(
            model='gemini-2.5-flash',
            contents=[system_context, f"User Question: {user_question}"]
        )
        
        text_response = response.text
        chart_path = None
        
        # Extract and execute the python code if Gemini generated a graph script
        if "```python" in text_response:
            try:
                # Isolate the code inside the markdown blocks
                code_block = text_response.split("```python")[1].split("```")[0]
                
                # Setup a string buffer to catch anything the executed script tries to print out
                old_stdout = sys.stdout
                redirected_output = sys.stdout = io.StringIO()
                
                # Create a safe execution environment containing the user's data
                local_vars = {'df': df, 'plt': plt}
                exec(code_block, {}, local_vars)
                
                # Restore normal system output
                sys.stdout = old_stdout
                printed_results = redirected_output.getvalue()
                
                # If the script printed extra statistical text, add it to the final display
                if printed_results:
                    text_response += f"\n\n### 🖥️ Calculated Execution Output:\n```text\n{printed_results}\n```"
                
                # Check if the chart image file was successfully written
                if os.path.exists("output_chart.png"):
                    chart_path = "output_chart.png"
            except Exception as e:
                text_response += f"\n\n*(Note: There was a background error rendering the chart: {str(e)})*"
        
        # Save the finalized text analysis into a downloadable file on the server
        with open("analysis_report.txt", "w", encoding="utf-8") as f:
            f.write(text_response)
            
        # Return text, chart path, and make the download button visible with the report file attached
        return text_response, chart_path, gr.update(value="analysis_report.txt", visible=True)
        
    except Exception as e:
        return f"❌ An error occurred processing the file: {str(e)}", None, gr.update(visible=False)

# 3. Design the custom layout using Gradio Blocks
with gr.Blocks(theme=gr.themes.Soft()) as demo:
    gr.Markdown("# 📊 Visual Cloud AI Data Analyst")
    gr.Markdown("Upload a CSV file, ask a question, and download your final text reports easily.")
    
    with gr.Row():
        with gr.Column(scale=1):
            file_input = gr.File(label="Step 1: Upload CSV Dataset", file_types=[".csv"])
            question_input = gr.Textbox(
                label="Step 2: Ask a question or request a model", 
                placeholder="e.g., 'Run a linear regression between ColumnA and ColumnB'..."
            )
            submit_btn = gr.Button("Analyze & Generate Chart", variant="primary")
            
        with gr.Column(scale=1):
            output_text = gr.Markdown(label="AI Written Insights")
            
            # The download button is hidden until an analysis runs successfully
            download_btn = gr.DownloadButton("📥 Download Text Report", visible=False)
            
            output_image = gr.Image(label="Generated Data Visualization", type="filepath")
            
    # Connect inputs to the execution function and distribute the 3 outputs
    submit_btn.click(
        fn=analyze_and_plot, 
        inputs=[file_input, question_input], 
        outputs=[output_text, output_image, download_btn]
    )

# 4. Launch the public cloud server
if __name__ == "__main__":
    demo.launch()