diff --git a/_pycache_/air_quality.cpython-310.pyc b/_pycache_/air_quality.cpython-310.pyc deleted file mode 100644 index fb0827ab..00000000 Binary files a/_pycache_/air_quality.cpython-310.pyc and /dev/null differ diff --git a/_pycache_/chatbot.cpython-310.pyc b/_pycache_/chatbot.cpython-310.pyc deleted file mode 100644 index 033a52e9..00000000 Binary files a/_pycache_/chatbot.cpython-310.pyc and /dev/null differ diff --git a/_pycache_/config.cpython-310.pyc b/_pycache_/config.cpython-310.pyc deleted file mode 100644 index 21a04f66..00000000 Binary files a/_pycache_/config.cpython-310.pyc and /dev/null differ diff --git a/_pycache_/crime.cpython-310.pyc b/_pycache_/crime.cpython-310.pyc deleted file mode 100644 index 1b1a904a..00000000 Binary files a/_pycache_/crime.cpython-310.pyc and /dev/null differ diff --git a/_pycache_/events.cpython-310.pyc b/_pycache_/events.cpython-310.pyc deleted file mode 100644 index 0164097c..00000000 Binary files a/_pycache_/events.cpython-310.pyc and /dev/null differ diff --git a/_pycache_/tourist.cpython-310.pyc b/_pycache_/tourist.cpython-310.pyc deleted file mode 100644 index 0b78053b..00000000 Binary files a/_pycache_/tourist.cpython-310.pyc and /dev/null differ diff --git a/_pycache_/transport.cpython-310.pyc b/_pycache_/transport.cpython-310.pyc deleted file mode 100644 index 98bf53c8..00000000 Binary files a/_pycache_/transport.cpython-310.pyc and /dev/null differ diff --git a/_pycache_/weather.cpython-310.pyc b/_pycache_/weather.cpython-310.pyc deleted file mode 100644 index dc8ca4db..00000000 Binary files a/_pycache_/weather.cpython-310.pyc and /dev/null differ diff --git a/app.py b/app.py index 82ed4e24..3dd11993 100644 --- a/app.py +++ b/app.py @@ -1,8 +1,12 @@ import streamlit as st import pandas as pd import datetime +import time +import plotly.graph_objects as go +import plotly.express as px +from plotly.subplots import make_subplots +import numpy as np -# Import your existing utility functions from utils.weather import get_current_weather, get_monthly_weather from utils.tourist import get_recommendations from config import CITY_COORDS @@ -10,134 +14,752 @@ from utils.crime import get_crime_news from utils.chatbot import search_google +st.set_page_config(page_title="City Pulse", layout="wide", initial_sidebar_state="expanded") -st.set_page_config(page_title="City Pulse", layout="wide") +st.markdown(""" + +""", unsafe_allow_html=True) + +# Enhanced Title with Animation +st.markdown("

🌃 City Pulse

", unsafe_allow_html=True) + +st.markdown("
Real-time Data Active
", unsafe_allow_html=True) + +# Enhanced City Selection with Animation +col_select1, col_select2, col_select3 = st.columns([1, 2, 1]) +with col_select2: + city = st.selectbox( + "Select a City", + list(CITY_COORDS.keys()), + help="Choose a city to explore real-time data" + ) if city: lat = CITY_COORDS[city]["lat"] lon = CITY_COORDS[city]["lon"] - # --- Initialize chat history in session state --- if "messages" not in st.session_state: st.session_state.messages = [] - # Create tabs - tabs = st.tabs(["Weather", "Air Quality", "Tourist Info", "Crime News", "Trends", "Find with City Pulse"]) + # Enhanced Tabs with Icons + tabs = st.tabs([ + "Weather", + "Air Quality", + "Tourist Info", + "Crime News", + "Trends", + "City Assistant" + ]) - # --- Existing Tabs (No changes needed for these sections) --- with tabs[0]: - st.header(f"Current Weather in {city}") - weather = get_current_weather(city, lat, lon) + st.markdown(f"

Current Weather in {city}

", unsafe_allow_html=True) + + # Create weather container with loading animation + weather_container = st.container() + with weather_container: + with st.spinner("Fetching weather data..."): + weather = get_current_weather(city, lat, lon) + time.sleep(0.5) # Brief pause for animation effect + if "error" in weather: - st.error(weather["error"]) + st.error(f"❌ {weather['error']}") else: + # Enhanced Weather Cards col1, col2, col3 = st.columns(3) - col1.metric("Temperature (°C)", weather["temperature"]) - col2.metric("Feels Like (°C)", weather["feels_like"]) - col3.metric("Humidity (%)", weather["humidity"]) - st.write(f"**Description:** {weather['description'].capitalize()}") - st.image(f"http://openweathermap.org/img/wn/{weather['icon']}@2x.png") + + with col1: + st.markdown(f""" +
+

Temperature

+

{weather["temperature"]}°C

+
+ """, unsafe_allow_html=True) + + with col2: + st.markdown(f""" +
+

Feels Like

+

{weather["feels_like"]}°C

+
+ """, unsafe_allow_html=True) + + with col3: + st.markdown(f""" +
+

Humidity

+

{weather["humidity"]}%

+
+ """, unsafe_allow_html=True) + + # Weather Description with Icon + st.markdown(f""" +
+
+ +
+

{weather['description']}

+

Current conditions in {city}

+
+
+
+ """, unsafe_allow_html=True) + # Enhanced Monthly Weather Chart monthly_weather = get_monthly_weather(city) if isinstance(monthly_weather, list) and monthly_weather: - st.subheader("Monthly Weather Summary") + st.markdown("

Monthly Weather Trends

", unsafe_allow_html=True) + df_monthly = pd.DataFrame(monthly_weather) - df_monthly = df_monthly.rename(columns={ - "month": "Month", - "avg_temp": "Avg Temp (°C)", - "humidity": "Humidity (%)", - "precip": "Precipitation (mm)" - }) - st.dataframe(df_monthly) + + # Create animated plotly chart + fig = make_subplots( + rows=2, cols=2, + subplot_titles=('Temperature Trend', 'Humidity Levels', 'Precipitation', 'Weather Overview'), + specs=[[{"secondary_y": False}, {"secondary_y": False}], + [{"secondary_y": False}, {"secondary_y": False}]] + ) + + # Temperature line + fig.add_trace( + go.Scatter( + x=df_monthly['month'], + y=df_monthly['avg_temp'], + mode='lines+markers', + name='Temperature (°C)', + line=dict(color='#ff6b6b', width=3), + marker=dict(size=8) + ), + row=1, col=1 + ) + + # Humidity bar + fig.add_trace( + go.Bar( + x=df_monthly['month'], + y=df_monthly['humidity'], + name='Humidity (%)', + marker_color='#4ecdc4' + ), + row=1, col=2 + ) + + # Precipitation area + fig.add_trace( + go.Scatter( + x=df_monthly['month'], + y=df_monthly['precip'], + fill='tonexty', + mode='lines', + name='Precipitation (mm)', + line=dict(color='#45b7d1') + ), + row=2, col=1 + ) + + # Combined overview + fig.add_trace( + go.Scatter( + x=df_monthly['month'], + y=df_monthly['avg_temp'], + mode='lines+markers', + name='Temp Overview', + line=dict(color='#96ceb4', width=2) + ), + row=2, col=2 + ) + + fig.update_layout( + height=600, + showlegend=True, + title_text="Weather Analytics Dashboard", + font=dict(family="Poppins, sans-serif") + ) + + st.plotly_chart(fig, use_container_width=True) with tabs[1]: - st.header(f"Air Quality in {city}") - air_quality = get_air_quality(city) + st.markdown(f"

Air Quality in {city}

", unsafe_allow_html=True) + + with st.spinner("Analyzing air quality..."): + air_quality = get_air_quality(city) + time.sleep(0.3) + if "error" in air_quality: - st.error(air_quality["error"]) + st.error(f"❌ {air_quality['error']}") else: aqi_level = {1: "Good", 2: "Fair", 3: "Moderate", 4: "Poor", 5: "Very Poor"} - st.markdown(f"### AQI Level: {air_quality['aqi']} - **{aqi_level.get(air_quality['aqi'], 'Unknown')}**") - with st.expander("Pollutant Details (μg/m³)"): - comp_df = pd.DataFrame(list(air_quality["components"].items()), columns=["Pollutant", "Value"]) - comp_df["Pollutant"] = comp_df["Pollutant"].str.upper() - st.table(comp_df) + aqi_colors = {1: "#00e400", 2: "#ffff00", 3: "#ff7e00", 4: "#ff0000", 5: "#8f3f97"} + + # AQI Gauge Chart + fig_gauge = go.Figure(go.Indicator( + mode = "gauge+number+delta", + value = air_quality['aqi'], + domain = {'x': [0, 1], 'y': [0, 1]}, + title = {'text': "Air Quality Index"}, + delta = {'reference': 3}, + gauge = { + 'axis': {'range': [None, 5]}, + 'bar': {'color': aqi_colors.get(air_quality['aqi'], "#gray")}, + 'steps': [ + {'range': [0, 1], 'color': "#e8f5e8"}, + {'range': [1, 2], 'color': "#fff8e1"}, + {'range': [2, 3], 'color': "#fff3e0"}, + {'range': [3, 4], 'color': "#ffebee"}, + {'range': [4, 5], 'color': "#f3e5f5"} + ], + 'threshold': { + 'line': {'color': "red", 'width': 4}, + 'thickness': 0.75, + 'value': 4 + } + } + )) + + fig_gauge.update_layout( + height=400, + font=dict(family="Poppins, sans-serif") + ) + + col1, col2 = st.columns([2, 1]) + with col1: + st.plotly_chart(fig_gauge, use_container_width=True) + + with col2: + st.markdown(f""" +
+

AQI Status

+

{air_quality['aqi']}

+

{aqi_level.get(air_quality['aqi'], 'Unknown')}

+
+
+ """, unsafe_allow_html=True) + + # Pollutant Details with Interactive Chart + st.markdown("

Pollutant Breakdown

", unsafe_allow_html=True) + + pollutants = air_quality["components"] + pollutant_df = pd.DataFrame(list(pollutants.items()), columns=["Pollutant", "Value"]) + pollutant_df["Pollutant"] = pollutant_df["Pollutant"].str.upper() + + fig_pollutants = px.bar( + pollutant_df, + x="Pollutant", + y="Value", + color="Value", + color_continuous_scale="Viridis", + title="Pollutant Concentrations (μg/m³)" + ) + fig_pollutants.update_layout( + xaxis_title="Pollutants", + yaxis_title="Concentration (μg/m³)", + font=dict(family="Poppins, sans-serif") + ) + st.plotly_chart(fig_pollutants, use_container_width=True) with tabs[2]: - st.header(f"Tourist Recommendations in {city}") - tourist_data = get_recommendations(city) + st.markdown(f"

Tourist Recommendations in {city}

", unsafe_allow_html=True) + + with st.spinner("Discovering amazing places..."): + tourist_data = get_recommendations(city) + time.sleep(0.4) + places = tourist_data.get("places", []) if places and not places[0].get("error"): - with st.expander("Top Tourist Places"): - for place in places: - st.markdown(f"**{place.get('name', 'N/A')}**") - st.write(f"Address: {place.get('address', 'N/A')}") - if place.get('rating'): - st.write(f"Rating: {place.get('rating', 'N/A')}") - st.markdown("---") + st.markdown("

Top Tourist Destinations

", unsafe_allow_html=True) + + for i, place in enumerate(places[:8]): + st.markdown(f""" +
+

{place.get('name', 'N/A')}

+

{place.get('address', 'N/A')}

+ {f"

Rating: {place.get('rating', 'N/A')}/5

" if place.get('rating') else ""} +
+ """, unsafe_allow_html=True) else: if places and places[0].get("error"): - st.error(f"Error fetching tourist places: {places[0]['error']}") + st.error(f"❌ Error fetching tourist places: {places[0]['error']}") else: - st.info("No tourist places data available or could not be fetched.") + st.info("No tourist places data available at the moment.") with tabs[3]: - st.header(f"Recent Crime News in {city}") - crime_news = get_crime_news(city) + st.markdown(f"

Recent Crime News in {city}

", unsafe_allow_html=True) + + with st.spinner("Gathering latest news..."): + crime_news = get_crime_news(city) + time.sleep(0.3) + if crime_news and not crime_news[0].get("error"): - with st.expander("Show Crime News Articles"): - for news in crime_news: - st.markdown(f"**[{news['title']}]({news['url']})**") - st.write(news["description"]) - st.write(f"*Published at: {news['publishedAt']}*") - st.markdown("---") + st.markdown("

Latest Crime Reports

", unsafe_allow_html=True) + + for i, news in enumerate(crime_news[:6]): # Limit to 6 articles + st.markdown(f""" +
+

{news['title']}

+

📢 {news['description'][:200]}{'...' if len(news['description']) > 200 else ''}

+

{news['publishedAt']}

+
+ """, unsafe_allow_html=True) else: if crime_news and crime_news[0].get("error"): - st.error(crime_news[0]["error"]) + st.error(f"❌ {crime_news[0]['error']}") else: - st.info("No crime news found.") + st.info("📰 No recent crime news found.") with tabs[4]: - st.header("How Popular is Your City? 📈") + st.markdown("

How Popular is Your City?

", unsafe_allow_html=True) + trends = tourist_data.get("trends", []) if trends and not trends[0].get("error"): + # Create animated trend chart dates = [trend["date"] for trend in trends] interests = [trend["interest"] for trend in trends] - df_trends = pd.DataFrame({"Date": pd.to_datetime(dates), "Interest": interests}) - df_trends = df_trends.set_index("Date") - st.line_chart(df_trends) + + fig_trends = go.Figure() + fig_trends.add_trace(go.Scatter( + x=dates, + y=interests, + mode='lines+markers', + name='Interest Level', + line=dict(color='#667eea', width=4), + marker=dict(size=10, color='#764ba2'), + fill='tonexty', + fillcolor='rgba(102, 126, 234, 0.1)' + )) + + fig_trends.update_layout( + title=f"Tourist Interest Trends for {city}", + xaxis_title="Date", + yaxis_title="Interest Level", + height=500, + font=dict(family="Poppins, sans-serif"), + hovermode='x unified' + ) + + st.plotly_chart(fig_trends, use_container_width=True) + + # Add trend insights + avg_interest = np.mean(interests) + max_interest = max(interests) + st.markdown(f""" +
+

Trend Insights

+

Average Interest Level: {avg_interest:.1f}

+

Peak Interest: {max_interest}

+

Tracking Period: Last 7 days

+
+ """, unsafe_allow_html=True) else: if trends and trends[0].get("error"): - st.error(trends[0]["error"]) + st.error(f"❌ {trends[0]['error']}") else: - st.info("No trends data available.") - + st.info("No trends data available at the moment.") - # --- Chatbot Tab --- - -with tabs[5]: - st.header("🤖 Search CityBot") + with tabs[5]: + st.markdown("

City Assistant

", unsafe_allow_html=True) + st.markdown("

Ask me anything about your city - restaurants, events, attractions, and more!

", unsafe_allow_html=True) - if "search_history" not in st.session_state: - st.session_state.search_history = [] + if "search_history" not in st.session_state: + st.session_state.search_history = [] - user_input = st.chat_input("Ask a question (e.g., top cafes, weekend events)") + # Chat interface + user_input = st.chat_input("Ask me about restaurants, events, attractions...") - if user_input: - st.session_state.search_history.append({"role": "user", "text": user_input}) + if user_input: + st.session_state.search_history.append({"role": "user", "text": user_input}) + + # Show typing indicator + typing_placeholder = st.empty() + typing_placeholder.markdown('
Assistant is thinking...
', unsafe_allow_html=True) + + with st.spinner("Searching for information..."): + time.sleep(1) # Simulate thinking time + answer = search_google(user_input) + + typing_placeholder.empty() + st.session_state.search_history.append({"role": "bot", "text": answer}) - with st.spinner("Searching Google..."): - answer = search_google(user_input) + # Display chat history with animations + for i, msg in enumerate(st.session_state.search_history): + if msg["role"] == "user": + st.chat_message("user").markdown(f'
{msg["text"]}
', unsafe_allow_html=True) + else: + st.chat_message("assistant").markdown(f'
{msg["text"]}
', unsafe_allow_html=True) - st.session_state.search_history.append({"role": "bot", "text": answer}) + # Quick action buttons + st.markdown("

Quick Questions

", unsafe_allow_html=True) + + col1, col2, col3, col4 = st.columns(4) + + with col1: + if st.button("🍕 Best Restaurants", use_container_width=True): + st.session_state.search_history.append({"role": "user", "text": f"best restaurants in {city}"}) + with st.spinner("Finding great restaurants..."): + answer = search_google(f"best restaurants in {city}") + st.session_state.search_history.append({"role": "bot", "text": answer}) + st.rerun() + + with col2: + if st.button("🎉 Weekend Events", use_container_width=True): + st.session_state.search_history.append({"role": "user", "text": f"weekend events in {city}"}) + with st.spinner("Discovering events..."): + answer = search_google(f"weekend events in {city}") + st.session_state.search_history.append({"role": "bot", "text": answer}) + st.rerun() + + with col3: + if st.button("☕ Coffee Shops", use_container_width=True): + st.session_state.search_history.append({"role": "user", "text": f"best coffee shops in {city}"}) + with st.spinner("Finding cozy cafes..."): + answer = search_google(f"best coffee shops in {city}") + st.session_state.search_history.append({"role": "bot", "text": answer}) + st.rerun() + + with col4: + if st.button("🛍️ Shopping", use_container_width=True): + st.session_state.search_history.append({"role": "user", "text": f"shopping places in {city}"}) + with st.spinner("Locating shopping areas..."): + answer = search_google(f"shopping places in {city}") + st.session_state.search_history.append({"role": "bot", "text": answer}) + st.rerun() - # Display chat messages - for msg in st.session_state.search_history: - if msg["role"] == "user": - st.chat_message("user").markdown(msg["text"]) - else: - st.chat_message("assistant").markdown(msg["text"]) +# Sidebar with Real-time City Pulse +with st.sidebar: + st.markdown(""" +
+

🌆 City Pulse Monitor

+
+
+ """, unsafe_allow_html=True) + + # Real-time data indicators + if city: + st.markdown(f"

📍 {city} Status

", unsafe_allow_html=True) + + # Simulate real-time data updates + current_time = datetime.datetime.now().strftime("%H:%M:%S") + st.markdown(f"**Last Update:** {current_time}") + + # Data freshness indicators + indicators = [ + ("Weather Data", "Fresh", "#00c851"), + ("Air Quality", "Live", "#00c851"), + ("News Feed", "Updated", "#ffa726"), + ("Tourist Info", "Active", "#00c851"), + ("Trends", "Real-time", "#00c851") + ] + + for indicator, status, color in indicators: + st.markdown(f""" +
+ {indicator}
+ ● {status} +
+ """, unsafe_allow_html=True) + + # Auto-refresh toggle + auto_refresh = st.toggle("🔄 Auto-refresh data", value=False) + if auto_refresh: + time.sleep(30) # Refresh every 30 seconds + st.rerun() + + # City statistics + st.markdown("

Quick Stats

", unsafe_allow_html=True) + if city and city in CITY_COORDS: + coords = CITY_COORDS[city] + st.markdown(f""" +
+

Coordinates:
+ Lat: {coords['lat']}
+ Lon: {coords['lon']}

+

Local Time:
{datetime.datetime.now().strftime('%I:%M %p')}

+

Date:
{datetime.datetime.now().strftime('%B %d, %Y')}

+
+ """, unsafe_allow_html=True)