Spaces:
Sleeping
Sleeping
| # app.py | |
| import streamlit as st | |
| import pandas as pd | |
| import requests | |
| import urllib3 | |
| import base64 | |
| import requests | |
| from pathlib import Path | |
| from datetime import datetime | |
| from requests.exceptions import RequestException | |
| from urllib.parse import quote | |
| urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) | |
| # ----------------------------- | |
| # App Config | |
| # ----------------------------- | |
| st.set_page_config( | |
| page_title="Weather Wear", | |
| page_icon="☀️", | |
| layout="wide" | |
| ) | |
| # ----------------------------- | |
| # Navbar / Header | |
| # ----------------------------- | |
| st.markdown( | |
| """ | |
| <style> | |
| .brand-bar { | |
| background-color: #1f2937; | |
| color: #ffffff; | |
| border-radius: 10px; | |
| display: flex; | |
| align-items: center; | |
| gap: 18px; | |
| width: 100%; | |
| } | |
| .brand-icon-slot { | |
| width: 62px; | |
| height: 62px; | |
| border-radius: 10px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| overflow: hidden; | |
| flex-shrink: 0; | |
| } | |
| .brand-icon-slot img { | |
| width: 100%; | |
| height: 100%; | |
| object-fit: contain; | |
| } | |
| .brand-title { | |
| font-size: 38px; | |
| font-weight: 700; | |
| white-space: nowrap; | |
| } | |
| .chat-button { | |
| position: fixed; | |
| bottom: 20px; | |
| right: 20px; | |
| width: 65px; | |
| height: 65px; | |
| border-radius: 50%; | |
| background-color: #25D366; | |
| color: white; | |
| font-size: 30px; | |
| border: none; | |
| cursor: pointer; | |
| z-index: 9999; | |
| box-shadow: 0 4px 10px rgba(0,0,0,0.3); | |
| } | |
| .chat-box { | |
| position: fixed; | |
| bottom: 100px; | |
| right: 20px; | |
| width: 350px; | |
| height: 450px; | |
| background: white; | |
| border-radius: 15px; | |
| padding: 10px; | |
| z-index: 9999; | |
| border: 1px solid #ddd; | |
| } | |
| </style> | |
| """, | |
| unsafe_allow_html=True | |
| ) | |
| # ----------------------------- | |
| # Nav-bar | |
| # ----------------------------- | |
| nav_left, nav_right = st.columns([5, 1]) | |
| def get_base64_image(image_path): | |
| image_bytes = Path(image_path).read_bytes() | |
| return base64.b64encode(image_bytes).decode() | |
| logo_base64 = get_base64_image("ww_logo.png") | |
| nav_left, nav_right = st.columns([5, 1]) | |
| with nav_left: | |
| st.markdown( | |
| f""" | |
| <div class="brand-bar"> | |
| <div class="brand-icon-slot"> | |
| <img src="data:image/png;base64,{logo_base64}" alt="Weather Wear Logo"> | |
| </div> | |
| <div class="brand-title">Weather Wear</div> | |
| </div> | |
| """, | |
| unsafe_allow_html=True | |
| ) | |
| # -------------------------- | |
| # OPEN BUTTON | |
| # -------------------------- | |
| col1, col2 = st.columns([10, 1]) | |
| with nav_right: | |
| if "chat_open" not in st.session_state: | |
| st.session_state.chat_open = False | |
| if st.button("💬 Chat Assistant"): | |
| st.session_state.chat_open = not st.session_state.chat_open | |
| # ----------------------------- | |
| # Weather Code Mapping | |
| # ----------------------------- | |
| WEATHER_CODES = { | |
| 0: "Sunny", | |
| 1: "Mostly Sunny", | |
| 2: "Partly Cloudy", | |
| 3: "Cloudy", | |
| 45: "Foggy", | |
| 48: "Foggy", | |
| 51: "Light Rain", | |
| 53: "Rainy", | |
| 55: "Rainy", | |
| 61: "Light Rain", | |
| 63: "Rainy", | |
| 65: "Heavy Rain", | |
| 71: "Snowy", | |
| 73: "Snowy", | |
| 75: "Heavy Snow", | |
| 80: "Rainy", | |
| 81: "Rainy", | |
| 82: "Heavy Rain", | |
| 95: "Stormy", | |
| 96: "Stormy", | |
| 99: "Stormy" | |
| } | |
| # ----------------------------- | |
| # API Transformer Class | |
| # ----------------------------- | |
| class WeatherTransformer: | |
| def __init__(self): | |
| self.api_key = st.secrets["OPENWEATHER_API_KEY"] | |
| def get_coordinates(self, location): | |
| geo_url = "https://api.openweathermap.org/geo/1.0/direct" | |
| params = { | |
| "q": location, | |
| "limit": 1, | |
| "appid": self.api_key | |
| } | |
| try: | |
| response = requests.get(geo_url, params=params, timeout=15) | |
| response.raise_for_status() | |
| data = response.json() | |
| if not data: | |
| return None | |
| result = data[0] | |
| return { | |
| "name": result.get("name", location), | |
| "country": result.get("country", ""), | |
| "latitude": result["lat"], | |
| "longitude": result["lon"] | |
| } | |
| except RequestException as error: | |
| st.error("Could not fetch location data.") | |
| st.caption(f"Technical details: {error}") | |
| return None | |
| def fetch_weather(self, latitude, longitude): | |
| weather_url = "https://api.openweathermap.org/data/2.5/weather" | |
| params = { | |
| "lat": latitude, | |
| "lon": longitude, | |
| "appid": self.api_key, | |
| "units": "metric" | |
| } | |
| try: | |
| response = requests.get(weather_url, params=params, timeout=15) | |
| response.raise_for_status() | |
| return response.json() | |
| except RequestException as error: | |
| st.error("Could not fetch weather data.") | |
| st.caption(f"Technical details: {error}") | |
| return None | |
| def transform_weather_data(self, location_info, weather_data): | |
| weather_main = weather_data["weather"][0]["main"] | |
| weather_description = weather_data["weather"][0]["description"] | |
| main = weather_data["main"] | |
| wind = weather_data.get("wind", {}) | |
| df = pd.DataFrame([{ | |
| "location": location_info["name"], | |
| "country": location_info["country"], | |
| "latitude": location_info["latitude"], | |
| "longitude": location_info["longitude"], | |
| "temperature": main["temp"], | |
| "feels_like": main["feels_like"], | |
| "humidity": main["humidity"], | |
| "weather_condition": normalize_weather(weather_main).title(), | |
| "weather_description": weather_description.title(), | |
| "wind_speed": wind.get("speed", 0), | |
| "fetched_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S") | |
| }]) | |
| return df | |
| def get_weather_dataframe(self, location): | |
| location_info = self.get_coordinates(location) | |
| if location_info is None: | |
| return None | |
| weather_data = self.fetch_weather( | |
| location_info["latitude"], | |
| location_info["longitude"] | |
| ) | |
| if weather_data is None: | |
| return None | |
| weather_df = self.transform_weather_data(location_info, weather_data) | |
| weather_df.to_csv("weather_data.csv", index=False) | |
| return weather_df | |
| # ----------------------------- | |
| # Helper Functions | |
| # ----------------------------- | |
| def normalize_weather(condition): | |
| condition = str(condition).lower() | |
| if "thunderstorm" in condition or "storm" in condition: | |
| return "stormy" | |
| if "rain" in condition or "drizzle" in condition: | |
| return "rainy" | |
| if "clear" in condition or "sun" in condition: | |
| return "sunny" | |
| if "cloud" in condition: | |
| return "cloudy" | |
| if "snow" in condition: | |
| return "snowy" | |
| if "mist" in condition or "fog" in condition or "haze" in condition: | |
| return "foggy" | |
| return condition | |
| def load_outfit_dataset(file_path): | |
| df = pd.read_csv(file_path) | |
| df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_") | |
| return df | |
| def recommend_outfit(outfit_df, weather_condition, style): | |
| weather_key = normalize_weather(weather_condition) | |
| df = outfit_df.copy() | |
| df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_") | |
| required_columns = [ | |
| "weather_condition", | |
| "style", | |
| "top", | |
| "bottom", | |
| "footwear", | |
| "accessory", | |
| "outerwear", | |
| "notes" | |
| ] | |
| missing_columns = [col for col in required_columns if col not in df.columns] | |
| if missing_columns: | |
| st.error(f"Missing columns in dataset: {missing_columns}") | |
| return pd.DataFrame() | |
| df["weather_condition"] = df["weather_condition"].astype(str).str.lower() | |
| df["style"] = df["style"].astype(str).str.lower() | |
| filtered = df[ | |
| (df["weather_condition"].str.contains(weather_key, na=False)) & | |
| (df["style"] == style.lower()) | |
| ] | |
| return filtered | |
| # ----------------------------- | |
| # Main App | |
| # ----------------------------- | |
| # Weather consoles | |
| input_col, style_col = st.columns([1, 1]) | |
| with input_col: | |
| location = st.text_input("Enter Location", value="Delhi") | |
| with style_col: | |
| style = st.selectbox( | |
| "Choose Outfit Style", | |
| ["Casual", "Formal", "Sporty", "Party", "Traditional"] | |
| ) | |
| left_space, button_col, right_space = st.columns([2, 1, 2]) | |
| with button_col: | |
| search_button = st.button("Fetch Weather", use_container_width=True) | |
| st.subheader("Weather Based Outfit Recommender") | |
| outfit_file = "outfit_recommendations.csv" | |
| try: | |
| outfit_df = load_outfit_dataset(outfit_file) | |
| except FileNotFoundError: | |
| st.error("Could not find outfit_recommendations.csv. Place it in the same folder as app.py.") | |
| st.stop() | |
| if search_button: | |
| st.session_state.weather_loaded = True | |
| st.session_state["weather_api_failed"] = False | |
| transformer = WeatherTransformer() | |
| with st.spinner("Fetching weather data..."): | |
| weather_df = transformer.get_weather_dataframe(location) | |
| if weather_df is None: | |
| if st.session_state.get("weather_api_failed"): | |
| st.stop() | |
| st.error("Location not found. Please enter a valid city or place name.") | |
| st.stop() | |
| weather = weather_df.iloc[0] | |
| if "weather_data" not in st.session_state: | |
| st.session_state.weather_data = None | |
| if "weather_loaded" not in st.session_state: | |
| st.session_state.weather_loaded = False | |
| st.session_state.weather_data = weather.to_dict() | |
| st.session_state.weather_loaded = True | |
| st.session_state["style"] = style | |
| current_temperature = float(weather["temperature"]) | |
| col1, col2, col3, col4 = st.columns(4) | |
| col1.metric("Location", f"{weather['location']}, {weather['country']}") | |
| col2.metric("Weather", weather["weather_condition"]) | |
| col3.metric("Temperature", f"{current_temperature} °C") | |
| col4.metric("Humidity", f"{weather['humidity']}%") | |
| temp_chart_df = pd.DataFrame({ | |
| "Weather Info": ["Current Temperature"], | |
| "Temperature °C": [current_temperature] | |
| }) | |
| # Create recommendations before trying to display them | |
| recommendations = recommend_outfit( | |
| outfit_df=outfit_df, | |
| weather_condition=weather["weather_condition"], | |
| style=style | |
| ) | |
| st.write("### Outfit Suggestions") | |
| if recommendations.empty: | |
| st.warning("No exact outfit match found for this weather, temperature, and style.") | |
| else: | |
| for _, row in recommendations.head(1).iterrows(): | |
| st.session_state["recommended_outfit"] = { | |
| "top": row["top"], | |
| "bottom": row["bottom"], | |
| "footwear": row["footwear"], | |
| "accessory": row["accessory"], | |
| "outerwear": row["outerwear"], | |
| "notes": row["notes"] | |
| } | |
| st.success("Recommended Outfit") | |
| st.write(f"**Top:** {row['top']}") | |
| st.write(f"**Bottom:** {row['bottom']}") | |
| st.write(f"**Footwear:** {row['footwear']}") | |
| st.write(f"**Accessory:** {row['accessory']}") | |
| st.write(f"**Outerwear:** {row['outerwear']}") | |
| st.write(f"**Notes:** {row['notes']}") | |
| st.divider() | |
| else: | |
| st.info("Enter a location, choose your style, and click Get Recommendation.") | |
| if st.session_state.get("weather_loaded", False): | |
| weather = st.session_state.weather_data | |
| if "weather" not in st.session_state: | |
| st.session_state.weather = None | |
| weather = st.session_state.weather | |
| if st.session_state.weather: | |
| st.write(st.session_state.weather) | |
| # show metrics | |
| # ------------------ | |
| # ------------------ | |
| # AI Chat-bot | |
| # ------------------ | |
| import streamlit as st | |
| import requests | |
| from openai import OpenAI | |
| from google import genai | |
| client = OpenAI( | |
| base_url="http://localhost:11434/v1", | |
| api_key="ollama" | |
| ) | |
| # -------------------------- | |
| # SESSION STATES | |
| # -------------------------- | |
| if "chat_open" not in st.session_state: | |
| st.session_state.chat_open = False | |
| if "messages" not in st.session_state: | |
| st.session_state.messages = [] | |
| if "provider" not in st.session_state: | |
| st.session_state.provider = "Ollama" | |
| # -------------------------- | |
| # AI FUNCTIONS | |
| # -------------------------- | |
| st.markdown(""" | |
| <style> | |
| /* Mobile-style floating chat window */ | |
| .st-key-mobile_chat_box { | |
| position: fixed; | |
| right: 24px; | |
| bottom: 24px; | |
| width: 390px; | |
| height: 640px; | |
| z-index: 9999; | |
| background: #0e1117; | |
| border: 1px solid #30363d; | |
| border-radius: 18px; | |
| padding: 16px; | |
| overflow-y: auto; | |
| box-shadow: 0 18px 50px rgba(0, 0, 0, 0.45); | |
| } | |
| /* Make it fit small screens */ | |
| @media (max-width: 520px) { | |
| .st-key-mobile_chat_box { | |
| right: 10px; | |
| left: 10px; | |
| bottom: 10px; | |
| width: auto; | |
| height: 85vh; | |
| } | |
| } | |
| /* Reduce spacing inside the chat box */ | |
| .st-key-mobile_chat_box h3 { | |
| margin-top: 0; | |
| } | |
| .st-key-mobile_chat_box [data-testid="stChatInput"] { | |
| margin-bottom: 0; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # ------------------------ | |
| def ask_ollama(prompt, model): | |
| try: | |
| r = requests.post( | |
| "http://localhost:11434/api/generate", | |
| json={ | |
| "model": model, | |
| "prompt": prompt, | |
| "stream": False | |
| } | |
| ) | |
| return r.json()["response"] | |
| except Exception as e: | |
| return str(e) | |
| def ask_openai(prompt, api_key): | |
| try: | |
| client = OpenAI(api_key=api_key) | |
| response = client.chat.completions.create( | |
| model="gpt-4.1-mini", | |
| messages=[ | |
| {"role": "user", "content": prompt} | |
| ] | |
| ) | |
| return response.choices[0].message.content | |
| except Exception as e: | |
| return str(e) | |
| def ask_gemini(prompt, api_key): | |
| client = genai.Client(api_key=api_key) | |
| response = client.models.generate_content( | |
| model="gemini-2.5-flash", | |
| contents=prompt | |
| ) | |
| return response.text | |
| # -------------------------- | |
| # CHAT POPUP | |
| # -------------------------- | |
| if st.session_state.chat_open: | |
| with st.container(border=True, key="mobile_chat_box"): | |
| top1, top2 = st.columns([8, 1]) | |
| with top1: | |
| st.markdown("### AI Assistant") | |
| with top2: | |
| if st.button("✕"): | |
| st.session_state.chat_open = False | |
| st.rerun() | |
| provider = st.selectbox( | |
| "Provider", | |
| ["Ollama", "Gemini", "OpenAI"], | |
| key="provider" | |
| ) | |
| if provider == "Ollama": | |
| model = st.text_input( | |
| "Model", | |
| "llama3", | |
| key="ollama_model" | |
| ) | |
| elif provider == "Gemini": | |
| st.text_input( | |
| "Gemini Key", | |
| type="password", | |
| key="api_key" | |
| ) | |
| else: | |
| st.text_input( | |
| "OpenAI Key", | |
| type="password", | |
| key="api_key" | |
| ) | |
| st.divider() | |
| for msg in st.session_state.messages: | |
| with st.chat_message(msg["role"]): | |
| st.write(msg["content"]) | |
| prompt = st.chat_input( | |
| "Ask anything..." | |
| ) | |
| if prompt: | |
| st.session_state.messages.append( | |
| { | |
| "role": "user", | |
| "content": prompt | |
| } | |
| ) | |
| with st.chat_message("user"): | |
| st.write(prompt) | |
| with st.spinner("Thinking..."): | |
| if provider == "Ollama": | |
| answer = ask_ollama( | |
| prompt, | |
| st.session_state.ollama_model | |
| ) | |
| elif provider == "Gemini": | |
| answer = ask_gemini( | |
| prompt, | |
| st.session_state.api_key | |
| ) | |
| else: | |
| answer = ask_openai( | |
| prompt, | |
| st.session_state.api_key | |
| ) | |
| st.session_state.messages.append( | |
| { | |
| "role": "assistant", | |
| "content": answer | |
| } | |
| ) | |
| st.rerun() |