import streamlit as st
import os
import json
import pandas as pd
import random
from os.path import join
from datetime import datetime
from src import (
preprocess_and_load_df,
load_agent,
ask_agent,
decorate_with_code,
show_response,
get_from_user,
load_smart_df,
ask_question,
)
from dotenv import load_dotenv
from langchain_groq import ChatGroq
from langchain_google_genai import ChatGoogleGenerativeAI
from streamlit_feedback import streamlit_feedback
from huggingface_hub import HfApi
from datasets import load_dataset, get_dataset_config_info, Dataset
from PIL import Image
import time
import uuid
# Page config with beautiful theme
st.set_page_config(
page_title="VayuChat - AI Air Quality Assistant",
page_icon="๐ฌ๏ธ",
layout="wide",
initial_sidebar_state="expanded"
)
# Custom CSS for beautiful styling
st.markdown("""
""", unsafe_allow_html=True)
# Auto-scroll JavaScript
st.markdown("""
""", unsafe_allow_html=True)
# FORCE reload environment variables
load_dotenv(override=True)
# Get API keys
Groq_Token = os.getenv("GROQ_API_KEY")
hf_token = os.getenv("HF_TOKEN")
gemini_token = os.getenv("GEMINI_TOKEN")
models = {
"gpt-oss-20b": "openai/gpt-oss-20b",
"gpt-oss-120b": "openai/gpt-oss-120b",
"llama3.1": "llama-3.1-8b-instant",
"llama3.3": "llama-3.3-70b-versatile",
"deepseek-R1": "deepseek-r1-distill-llama-70b",
"llama4 maverik":"meta-llama/llama-4-maverick-17b-128e-instruct",
"llama4 scout":"meta-llama/llama-4-scout-17b-16e-instruct",
"gemini-pro": "gemini-1.5-pro"
}
self_path = os.path.dirname(os.path.abspath(__file__))
# Initialize session ID for this session
if "session_id" not in st.session_state:
st.session_state.session_id = str(uuid.uuid4())
def upload_feedback(feedback, error, output, last_prompt, code, status):
"""Enhanced feedback upload function with better logging and error handling"""
try:
if not hf_token or hf_token.strip() == "":
st.warning("โ ๏ธ Cannot upload feedback - HF_TOKEN not available")
return False
# Create comprehensive feedback data
feedback_data = {
"timestamp": datetime.now().isoformat(),
"session_id": st.session_state.session_id,
"feedback_score": feedback.get("score", ""),
"feedback_comment": feedback.get("text", ""),
"user_prompt": last_prompt,
"ai_output": str(output),
"generated_code": code or "",
"error_message": error or "",
"is_image_output": status.get("is_image", False),
"success": not bool(error)
}
# Create unique folder name with timestamp
timestamp_str = datetime.now().strftime("%Y%m%d_%H%M%S")
random_id = str(uuid.uuid4())[:8]
folder_name = f"feedback_{timestamp_str}_{random_id}"
# Create markdown feedback file
markdown_content = f"""# VayuChat Feedback Report
## Session Information
- **Timestamp**: {feedback_data['timestamp']}
- **Session ID**: {feedback_data['session_id']}
## User Interaction
**Prompt**: {feedback_data['user_prompt']}
## AI Response
**Output**: {feedback_data['ai_output']}
## Generated Code
```python
{feedback_data['generated_code']}
```
## Technical Details
- **Error Message**: {feedback_data['error_message']}
- **Is Image Output**: {feedback_data['is_image_output']}
- **Success**: {feedback_data['success']}
## User Feedback
- **Score**: {feedback_data['feedback_score']}
- **Comments**: {feedback_data['feedback_comment']}
"""
# Save markdown file locally
markdown_filename = f"{folder_name}.md"
markdown_local_path = f"/tmp/{markdown_filename}"
with open(markdown_local_path, "w", encoding="utf-8") as f:
f.write(markdown_content)
# Upload to Hugging Face
api = HfApi(token=hf_token)
# Upload markdown feedback
api.upload_file(
path_or_fileobj=markdown_local_path,
path_in_repo=f"data/{markdown_filename}",
repo_id="SustainabilityLabIITGN/VayuChat_Feedback",
repo_type="dataset",
)
# Upload image if it exists and is an image output
if status.get("is_image", False) and isinstance(output, str) and os.path.exists(output):
try:
image_filename = f"{folder_name}_plot.png"
api.upload_file(
path_or_fileobj=output,
path_in_repo=f"data/{image_filename}",
repo_id="SustainabilityLabIITGN/VayuChat_Feedback",
repo_type="dataset",
)
except Exception as img_error:
print(f"Error uploading image: {img_error}")
# Clean up local files
if os.path.exists(markdown_local_path):
os.remove(markdown_local_path)
st.success("๐ Feedback uploaded successfully!")
return True
except Exception as e:
st.error(f"โ Error uploading feedback: {e}")
print(f"Feedback upload error: {e}")
return False
# --- MODERN HEADER & LAYOUT ---
st.markdown("""
""", unsafe_allow_html=True)
st.markdown("
๐ฌ๏ธ VayuChat
", unsafe_allow_html=True)
st.markdown("AI-Powered Air Quality, Funding & Population Insights
Simplifying analysis using conversational AI.
", unsafe_allow_html=True)
st.markdown("How to Use:
Select a model from the sidebar and ask questions in the chat. Use quick prompts for common queries. Switch models and rerun easily!
", unsafe_allow_html=True)
os.environ["PANDASAI_API_KEY"] = "$2a$10$gbmqKotzJOnqa7iYOun8eO50TxMD/6Zw1pLI2JEoqncwsNx4XeBS2"
# --- LOAD ALL DATAFRAMES ---
try:
df = preprocess_and_load_df(join(self_path, "Data.csv"))
import pickle
ncap_data = pd.read_pickle(join(self_path, "ncap_funding_data.pkl"))
states_data = pd.read_pickle(join(self_path, "states_data.pkl"))
st.success("โ
Data loaded successfully!")
except Exception as e:
st.error(f"โ Error loading data: {e}")
st.stop()
inference_server = "https://api-inference.huggingface.co/models/mistralai/Mistral-7B-Instruct-v0.2"
image_path = "IITGN_Logo.png"
# --- MODERN SIDEBAR ---
with st.sidebar:
st.image(image_path, width=120)
st.markdown("", unsafe_allow_html=True)
available_models = []
model_names = list(models.keys())
groq_models = [m for m in model_names if "gemini" not in m]
gemini_models = [m for m in model_names if "gemini" in m]
if Groq_Token and Groq_Token.strip():
available_models.extend(groq_models)
if gemini_token and gemini_token.strip():
available_models.extend(gemini_models)
if not available_models:
st.error("โ No API keys available! Please set up your API keys in the .env file")
st.stop()
model_name = st.selectbox("Choose your AI assistant:", available_models)
model_descriptions = {
"llama3.1": "๐ฆ Fast and efficient for general queries",
"llama3.3": "๐ฆ Most advanced LLaMA model for complex reasoning",
"mistral": "โก Balanced performance and speed",
"gemma": "๐ Google's lightweight model",
"gemini-pro": "๐ง Google's most powerful model",
"gpt-oss-20b": "๐ OpenAI's compact open-weight GPT for everyday tasks",
"gpt-oss-120b": "๐ OpenAI's massive open-weight GPT for nuanced responses",
"deepseek-R1": "๐ DeepSeek's distilled LLaMA model for efficient reasoning",
"llama4 maverik": "๐ Meta's LLaMA 4 Maverick โ high-performance instruction model",
"llama4 scout": "๐ฐ๏ธ Meta's LLaMA 4 Scout โ optimized for adaptive reasoning"
}
if model_name in model_descriptions:
st.info(model_descriptions[model_name])
st.markdown("", unsafe_allow_html=True)
st.markdown("", unsafe_allow_html=True)
st.markdown("", unsafe_allow_html=True)
if st.button("Clear Chat"):
st.session_state.responses = []
st.session_state.processing = False
st.session_state.session_id = str(uuid.uuid4())
st.rerun()
st.markdown(f"", unsafe_allow_html=True)
# Load quick prompts
questions = []
questions_file = join(self_path, "questions.txt")
if os.path.exists(questions_file):
try:
with open(questions_file, 'r', encoding='utf-8') as f:
content = f.read()
questions = [q.strip() for q in content.split("\n") if q.strip()]
print(f"Loaded {len(questions)} quick prompts") # Debug
except Exception as e:
st.error(f"Error loading questions: {e}")
questions = []
# Add some default prompts if file doesn't exist or is empty
if not questions:
questions = [
"What is the average PM2.5 level in the dataset?",
"Show me the air quality trend over time",
"Which pollutant has the highest concentration?",
"Create a correlation plot between different pollutants",
"What are the peak pollution hours?",
"Compare weekday vs weekend pollution levels"
]
# --- MODERN QUICK PROMPTS ---
st.markdown("๐ญ Quick Prompts
", unsafe_allow_html=True)
selected_prompt = None
prompt_buttons = []
for i, q in enumerate(questions):
if st.button(q, key=f"quick_{i}", help=q, use_container_width=True):
selected_prompt = q
# Initialize chat history and processing state
if "responses" not in st.session_state:
st.session_state.responses = []
if "processing" not in st.session_state:
st.session_state.processing = False
def show_custom_response(response):
"""Custom response display function"""
role = response.get("role", "assistant")
content = response.get("content", "")
if role == "user":
st.markdown(f"""
""", unsafe_allow_html=True)
elif role == "assistant":
st.markdown(f"""
๐ค VayuChat
{content if isinstance(content, str) else str(content)}
""", unsafe_allow_html=True)
# Show generated code if available
if response.get("gen_code"):
with st.expander("๐ View Generated Code"):
st.code(response["gen_code"], language="python")
# Try to display image if content is a file path
try:
if isinstance(content, str) and (content.endswith('.png') or content.endswith('.jpg')):
if os.path.exists(content):
st.image(content)
return {"is_image": True}
except:
pass
return {"is_image": False}
def show_processing_indicator(model_name, question):
"""Show processing indicator"""
st.markdown(f"""
๐ค VayuChat โข Processing with {model_name}
Question: {question}
๐ Generating response...
""", unsafe_allow_html=True)
# --- MODERN MAIN CHAT AREA ---
st.markdown("", unsafe_allow_html=True)
for response in st.session_state.responses:
if response["role"] == "user":
st.markdown(f"
{response['content']}
", unsafe_allow_html=True)
else:
st.markdown(f"
{response['content']}
", unsafe_allow_html=True)
# Show code details if available
if response.get("gen_code"):
with st.expander("๐ View Generated Code"):
st.code(response["gen_code"], language="python")
# Feedback section
if "feedback" in response:
feedback_data = response["feedback"]
feedback_text = feedback_data.get('text', '')
feedback_score = feedback_data.get('score', '')
feedback_str = f"- {feedback_text}" if feedback_text else ''
st.markdown(f"
๐ Your Feedback: {feedback_score} {feedback_str}
", unsafe_allow_html=True)
else:
st.markdown("
How was this response?", unsafe_allow_html=True)
col1, col2 = st.columns(2)
with col1:
thumbs_up = st.button("๐ Helpful", key=f"fb_up_{id(response)}")
with col2:
thumbs_down = st.button("๐ Not Helpful", key=f"fb_down_{id(response)}")
if thumbs_up or thumbs_down:
thumbs = "๐ Helpful" if thumbs_up else "๐ Not Helpful"
comments = st.text_area("๐ฌ Tell us more (optional):", key=f"fb_comments_{id(response)}", placeholder="What could be improved? Any suggestions?", max_chars=500)
if st.button("๐ Submit Feedback", key=f"fb_submit_{id(response)}"):
feedback = {"score": thumbs, "text": comments}
if upload_feedback(feedback, response.get("error", ""), response.get("content", ""), response.get("last_prompt", ""), response.get("gen_code", ""), {}):
response["feedback"] = feedback
time.sleep(1)
st.rerun()
st.markdown("
", unsafe_allow_html=True)
st.markdown("
", unsafe_allow_html=True)
# --- MODERN CHAT INPUT & RERUN ---
st.markdown("", unsafe_allow_html=True)
rerun_col, input_col = st.columns([1, 8])
with rerun_col:
if st.button("๐ Rerun Last", help="Rerun the last question with the selected model"):
if "last_prompt" in st.session_state and st.session_state["last_prompt"]:
st.session_state.processing = True
st.session_state.current_model = model_name
st.session_state.current_question = st.session_state["last_prompt"]
st.rerun()
with input_col:
prompt = st.text_input("Ask about air quality, funding, or population...", value=selected_prompt or "", key="main_chat")
st.markdown("
", unsafe_allow_html=True)
# Handle new queries
if prompt and not st.session_state.get("processing"):
if "last_prompt" in st.session_state:
last_prompt = st.session_state["last_prompt"]
last_model_name = st.session_state.get("last_model_name", "")
if (prompt == last_prompt) and (model_name == last_model_name):
prompt = None
if prompt:
user_response = get_from_user(prompt)
st.session_state.responses.append(user_response)
st.session_state.processing = True
st.session_state.current_model = model_name
st.session_state.current_question = prompt
st.rerun()
# Process the question if we're in processing state
if st.session_state.get("processing"):
prompt = st.session_state.get("current_question")
model_name = st.session_state.get("current_model")
try:
from src import SYSTEM_PROMPT
agent = load_agent(df, SYSTEM_PROMPT, inference_server, name=model_name)
response = ask_agent(agent, prompt)
if not isinstance(response, dict):
response = {
"role": "assistant",
"content": "โ Error: Invalid response format",
"gen_code": "",
"ex_code": "",
"last_prompt": prompt,
"error": "Invalid response format"
}
response.setdefault("role", "assistant")
response.setdefault("content", "No content generated")
response.setdefault("gen_code", "")
response.setdefault("ex_code", "")
response.setdefault("last_prompt", prompt)
response.setdefault("error", None)
except Exception as e:
response = {
"role": "assistant",
"content": f"Sorry, I encountered an error: {str(e)}",
"gen_code": "",
"ex_code": "",
"last_prompt": prompt,
"error": str(e)
}
st.session_state.responses.append(response)
st.session_state["last_prompt"] = prompt
st.session_state["last_model_name"] = model_name
st.session_state.processing = False
# Clear processing state
if "current_model" in st.session_state:
del st.session_state.current_model
if "current_question" in st.session_state:
del st.session_state.current_question
st.rerun()
# Auto-scroll to bottom
if st.session_state.responses:
st.markdown("", unsafe_allow_html=True)
# Beautiful sidebar footer
# with st.sidebar:
# st.markdown("---")
# st.markdown("""
#
# """, unsafe_allow_html=True)
# Statistics (if logging is enabled)
if hf_token and hf_token.strip():
st.markdown("### ๐ Session Stats")
total_interactions = len([r for r in st.session_state.get("responses", []) if r.get("role") == "assistant"])
st.metric("Interactions", total_interactions)
feedbacks_given = len([r for r in st.session_state.get("responses", []) if r.get("role") == "assistant" and "feedback" in r])
st.metric("Feedbacks Given", feedbacks_given)
# Footer
st.markdown("""
๐ Together for Cleaner Air
VayuChat - Empowering environmental awareness through AI
ยฉ 2025 IIT Gandhinagar Sustainability Lab
""", unsafe_allow_html=True)