agusrajuthaliyan's picture
Upload 6 files
13942ec verified
import streamlit as st
import pandas as pd
import requests
import folium
from streamlit_folium import st_folium
from astral import LocationInfo
from astral.sun import sun
from datetime import datetime
import os
from dotenv import load_dotenv
from groq import Groq # Moved this import to the top
# --- Configuration and API Key Loading ---
load_dotenv()
OPENWEATHER_API_KEY = os.getenv("OPENWEATHER_API_KEY")
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
# --- Basic API Key Validation ---
if not OPENWEATHER_API_KEY:
st.error("OpenWeatherMap API key not found. Please set OPENWEATHER_API_KEY in your .env file.")
st.stop() # Stop execution if critical key is missing
if not GROQ_API_KEY:
st.error("Groq API key not found. Please set GROQ_API_KEY in your .env file.")
st.stop() # Stop execution if critical key is missing
# Initialize Groq client
groq_client = Groq(api_key=GROQ_API_KEY)
st.set_page_config(page_title="AI Flight Assistant", layout="wide")
# --- Custom CSS for better aesthetics ---
st.markdown("""
<style>
.main {background-color: #f8f9fa;}
h1, h2, h3 {color: #003366;}
.st-emotion-cache-nahz7x { /* Adjust sidebar width, check this class name if not working */
width: 300px;
}
.st-emotion-cache-1cyp85f { /* Adjust main content padding, check this class name if not working */
padding-left: 2rem;
padding-right: 2rem;
}
.stButton>button {
background-color: #007bff;
color: white;
border-radius: 0.5rem;
padding: 0.5rem 1rem;
}
.stButton>button:hover {
background-color: #0056b3;
color: white;
}
</style>
""", unsafe_allow_html=True)
st.title("✈️ AI Flight Planning Assistant")
st.markdown("Plan VFR flights smartly with real-time weather, time-of-day checks, and interactive mapping.")
# --- Initialize session state variables ---
# This ensures these variables exist even on the first run
if 'flight_planned' not in st.session_state:
st.session_state.flight_planned = False
if 'dep_weather' not in st.session_state:
st.session_state.dep_weather = None
if 'arr_weather' not in st.session_state:
st.session_state.arr_weather = None
if 'dep_sun' not in st.session_state:
st.session_state.dep_sun = None
if 'arr_sun' not in st.session_state:
st.session_state.arr_sun = None
# Initialize input values in session state
if 'dep_lat' not in st.session_state: st.session_state.dep_lat = 12.9538
if 'dep_lon' not in st.session_state: st.session_state.dep_lon = 77.4903
if 'arr_lat' not in st.session_state: st.session_state.arr_lat = 13.0827
if 'arr_lon' not in st.session_state: st.session_state.arr_lon = 80.2707
if 'dep_airport' not in st.session_state: st.session_state.dep_airport = "VOBL"
if 'arr_airport' not in st.session_state: st.session_state.arr_airport = "VOMM"
if 'flight_date' not in st.session_state: st.session_state.flight_date = datetime.today()
# Initialize chat history in session state
if "messages" not in st.session_state:
st.session_state.messages = []
# --- Functions for fetching data ---
def get_weather(lat, lon):
url = f"https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={OPENWEATHER_API_KEY}&units=metric"
try:
res = requests.get(url)
res.raise_for_status() # Raises HTTPError for bad responses (4xx or 5xx)
data = res.json()
return {
"temp": data['main']['temp'],
"weather": data['weather'][0]['description'],
"wind_speed": data['wind']['speed'],
"city_name": data.get('name', 'N/A')
}
except requests.exceptions.RequestException as e:
st.error(f"Error fetching weather data: {e}. Check API key or coordinates.")
return None
except KeyError as e:
st.error(f"Unexpected weather data format from API: {e}.")
return None
def get_sun_times(lat, lon, date):
try:
city = LocationInfo("Custom", "World", "UTC", lat, lon)
s = sun(city.observer, date=date)
return s
except Exception as e:
st.error(f"Error calculating sun times: {e}.")
return None
# --- Sidebar for Flight Inputs ---
st.sidebar.header("✈️ Flight Parameters")
with st.sidebar:
dep_airport = st.text_input("Departure Airport ICAO Code", value=st.session_state.dep_airport, key="dep_airport_input")
arr_airport = st.text_input("Arrival Airport ICAO Code", value=st.session_state.arr_airport, key="arr_airport_input")
flight_date = st.date_input("Flight Date", value=st.session_state.flight_date, key="flight_date_input")
dep_lat = st.number_input("Departure Latitude", value=st.session_state.dep_lat, key="dep_lat_input")
dep_lon = st.number_input("Departure Longitude", value=st.session_state.dep_lon, key="dep_lon_input")
arr_lat = st.number_input("Arrival Latitude", value=st.session_state.arr_lat, key="arr_lat_input")
arr_lon = st.number_input("Arrival Longitude", value=st.session_state.arr_lon, key="arr_lon_input")
# Update session state when inputs change (this runs on every rerun)
st.session_state.dep_airport = dep_airport
st.session_state.arr_airport = arr_airport
st.session_state.flight_date = flight_date
st.session_state.dep_lat = dep_lat
st.session_state.dep_lon = dep_lon
st.session_state.arr_lat = arr_lat
st.session_state.arr_lon = arr_lon
# --- Plan Flight Button Logic ---
if st.sidebar.button("Plan Flight"):
with st.spinner("Fetching weather and sunlight data..."):
st.session_state.dep_weather = get_weather(st.session_state.dep_lat, st.session_state.dep_lon)
st.session_state.arr_weather = get_weather(st.session_state.arr_lat, st.session_state.arr_lon)
st.session_state.dep_sun = get_sun_times(st.session_state.dep_lat, st.session_state.dep_lon, st.session_state.flight_date)
st.session_state.arr_sun = get_sun_times(st.session_state.arr_lat, st.session_state.arr_lon, st.session_state.flight_date)
st.session_state.flight_planned = True # Set status to true after fetching
st.rerun() # Trigger a rerun to display content in tabs immediately
# --- Main Content Area with Tabs ---
tab1, tab2 = st.tabs(["📊 Flight Plan Overview", "🤖 AI Flight Assistant"])
with tab1: # Flight Plan Overview Tab
if st.session_state.flight_planned:
st.subheader("Current Flight Plan Details")
display_col1, display_col2 = st.columns(2)
with display_col1:
st.markdown("#### 🌤️ Departure Weather")
if st.session_state.dep_weather:
st.metric(label="Location", value=st.session_state.dep_weather.get('city_name', 'N/A'))
st.metric(label="Temperature", value=f"{st.session_state.dep_weather['temp']}°C")
st.metric(label="Condition", value=st.session_state.dep_weather['weather'])
st.metric(label="Wind Speed", value=f"{st.session_state.dep_weather['wind_speed']} m/s")
else:
st.info("Departure weather data not available.")
st.markdown("#### ☀️ Departure Sunlight")
if st.session_state.dep_sun:
st.write(f"🌅 Sunrise: {st.session_state.dep_sun['sunrise'].time()}")
st.write(f"🌇 Sunset: {st.session_state.dep_sun['sunset'].time()}")
else:
st.info("Departure sun times not available.")
with display_col2:
st.markdown("#### 🌤️ Arrival Weather")
if st.session_state.arr_weather:
st.metric(label="Location", value=st.session_state.arr_weather.get('city_name', 'N/A'))
st.metric(label="Temperature", value=f"{st.session_state.arr_weather['temp']}°C")
st.metric(label="Condition", value=st.session_state.arr_weather['weather'])
st.metric(label="Wind Speed", value=f"{st.session_state.arr_weather['wind_speed']} m/s")
else:
st.info("Arrival weather data not available.")
st.markdown("#### ☀️ Arrival Sunlight")
if st.session_state.arr_sun:
st.write(f"🌅 Sunrise: {st.session_state.arr_sun['sunrise'].time()}")
st.write(f"🌇 Sunset: {st.session_state.arr_sun['sunset'].time()}")
else:
st.info("Arrival sun times not available.")
# --- Map ---
st.subheader("🗺️ Flight Path")
if all(isinstance(coord, (int, float)) for coord in [st.session_state.dep_lat, st.session_state.dep_lon, st.session_state.arr_lat, st.session_state.arr_lon]):
m = folium.Map(location=[(st.session_state.dep_lat + st.session_state.arr_lat)/2, (st.session_state.dep_lon + st.session_state.arr_lon)/2], zoom_start=6)
folium.Marker([st.session_state.dep_lat, st.session_state.dep_lon], tooltip=f"Departure ({st.session_state.dep_airport})").add_to(m)
folium.Marker([st.session_state.arr_lat, st.session_state.arr_lon], tooltip=f"Arrival ({st.session_state.arr_airport})").add_to(m)
folium.PolyLine([[st.session_state.dep_lat, st.session_state.dep_lon], [st.session_state.arr_lat, st.session_state.arr_lon]], color="blue").add_to(m)
st_folium(m, width=800, height=400)
else:
st.warning("Invalid latitude/longitude provided for map display.")
else:
st.info("Enter flight parameters in the sidebar and click 'Plan Flight' to see details.")
with tab2: # AI Flight Assistant Tab
st.subheader("🤖 AI Flight Chat Assistant")
st.markdown("Ask me questions related to your current flight plan, weather, or general aviation topics.")
# --- Display Chat History ---
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# --- Chat Input ---
user_query = st.chat_input("Ask a question about your flight plan...")
if user_query:
st.session_state.messages.append({"role": "user", "content": user_query})
with st.chat_message("user"):
st.markdown(user_query)
# --- RAG for Chatbot: Provide flight context to Llama 3 ---
# Ensure that context is built only if a flight has been planned
flight_context = ""
if st.session_state.flight_planned:
flight_context = f"""
Current Flight Plan Details:
Departure Airport ICAO: {st.session_state.dep_airport}
Arrival Airport ICAO: {st.session_state.arr_airport}
Flight Date: {st.session_state.flight_date}
Departure Latitude: {st.session_state.dep_lat}, Longitude: {st.session_state.dep_lon}
Arrival Latitude: {st.session_state.arr_lat}, Longitude: {st.session_state.arr_lon}
"""
if st.session_state.dep_weather:
flight_context += f"\nDeparture Weather: At {st.session_state.dep_weather.get('city_name', 'N/A')}: Temperature {st.session_state.dep_weather['temp']}°C, Condition {st.session_state.dep_weather['weather']}, Wind Speed {st.session_state.dep_weather['wind_speed']} m/s."
if st.session_state.arr_weather:
flight_context += f"\nArrival Weather: At {st.session_state.arr_weather.get('city_name', 'N/A')}: Temperature {st.session_state.arr_weather['temp']}°C, Condition {st.session_state.arr_weather['weather']}, Wind Speed {st.session_state.arr_weather['wind_speed']} m/s."
if st.session_state.dep_sun:
flight_context += f"\nDeparture Sunrise: {st.session_state.dep_sun['sunrise'].time()}, Sunset: {st.session_state.dep_sun['sunset'].time()}."
if st.session_state.arr_sun:
flight_context += f"\nArrival Sunrise: {st.session_state.arr_sun['sunrise'].time()}, Sunset: {st.session_state.arr_sun['sunset'].time()}."
else:
flight_context = "No specific flight plan data available yet. Please plan a flight first."
messages_for_llm = [
{"role": "system", "content": f"You are a helpful AI flight assistant. Here's the flight context: {flight_context}\nAnswer questions based on this context and general aviation knowledge. If a question is outside the provided context, state that you don't have that specific information but can answer general aviation questions. Keep answers concise."}
]
# Add recent chat history to LLM messages (excluding system message for every turn)
for msg in st.session_state.messages:
if msg["role"] != "system": # Avoid sending system message multiple times
messages_for_llm.append({"role": msg["role"], "content": msg["content"]})
with st.chat_message("assistant"):
with st.spinner("Thinking..."):
try:
chat_completion = groq_client.chat.completions.create(
messages=messages_for_llm,
model="llama3-8b-8192",
temperature=0.7,
max_tokens=500,
stream=True
)
full_response = ""
response_placeholder = st.empty()
for chunk in chat_completion:
if chunk.choices[0].delta.content:
full_response += chunk.choices[0].delta.content
response_placeholder.markdown(full_response + "▌") # Add blinking cursor
response_placeholder.markdown(full_response) # Final response without cursor
st.session_state.messages.append({"role": "assistant", "content": full_response})
except Exception as e:
st.error(f"Error communicating with AI: {e}")
st.session_state.messages.append({"role": "assistant", "content": f"Sorry, I encountered an error: {e}"})