| import subprocess |
|
|
| def install_dependencies(): |
| subprocess.check_call(["python", "-m", "pip", "install", "--upgrade", "pip"]) |
| subprocess.check_call(["python", "-m", "pip", "install", "--upgrade", "huggingface_hub"]) |
| subprocess.check_call(["python", "-m", "pip", "install", "--upgrade", "streamlit"]) |
| subprocess.check_call(["python", "-m", "pip", "install", "--upgrade", "requests"]) |
| |
| |
| |
| |
| install_dependencies() |
|
|
| import streamlit as st |
| import requests |
| import json |
| import os |
| from huggingface_hub import InferenceClient |
|
|
|
|
|
|
|
|
| |
| st.set_page_config( |
| page_title="Weather Forecast", |
| page_icon="π€οΈ", |
| layout="wide", |
| ) |
|
|
| |
| |
| |
| |
|
|
| |
|
|
| def local_css(file_name): |
| with open(file_name) as f: |
| |
| st.markdown(f'{f.read()}', unsafe_allow_html=True) |
|
|
| |
| |
| local_css("style.css") |
| st.title("RainyTrek") |
|
|
|
|
|
|
| |
| WMO_CODES = { |
| 0: ("βοΈ", "Clear"), |
| 1: ("π€οΈ", "Mostly Clear"), |
| 2: ("β
", "Partly Cloudy"), |
| 3: ("βοΈ", "Overcast"), |
| 45: ("π«οΈ", "Foggy"), |
| 48: ("π«οΈ", "Icy Fog"), |
| 51: ("π¦οΈ", "Light Drizzle"), |
| 53: ("π¦οΈ", "Drizzle"), |
| 55: ("π§οΈ", "Heavy Drizzle"), |
| 61: ("π§οΈ", "Light Rain"), |
| 63: ("π§οΈ", "Rain"), |
| 65: ("π§οΈ", "Heavy Rain"), |
| 71: ("π¨οΈ", "Light Snow"), |
| 73: ("βοΈ", "Snow"), |
| 75: ("βοΈ", "Heavy Snow"), |
| 77: ("π¨οΈ", "Snow Grains"), |
| 80: ("π¦οΈ", "Rain Showers"), |
| 81: ("π§οΈ", "Heavy Showers"), |
| 82: ("βοΈ", "Violent Showers"), |
| 85: ("π¨οΈ", "Snow Showers"), |
| 86: ("π¨οΈ", "Heavy Snow Showers"), |
| 95: ("βοΈ", "Thunderstorm"), |
| 96: ("βοΈ", "Thunderstorm + Hail"), |
| 99: ("βοΈ", "Thunderstorm + Heavy Hail"), |
| } |
|
|
|
|
| def wmo_info(code): |
| return WMO_CODES.get(code, ("π‘οΈ", "Unknown")) |
|
|
|
|
| |
| def extract_cities_with_llm(user_prompt: str) -> list[str]: |
| """Use HF Inference API to extract city names from a natural-language prompt.""" |
| client = InferenceClient( |
| |
| |
| |
| |
| model="meta-llama/Llama-3.3-70B-Instruct", |
| |
| |
| |
| |
| token=os.getenv("rainytrek010526001read"), |
| provider="fireworks-ai" |
| ) |
|
|
| system_prompt = ( |
| "You are a helpful assistant that extracts city names from user messages. " |
| "Respond ONLY with a JSON array of city name strings. " |
| "Example: [\"Paris\", \"Tokyo\", \"New York\"]. " |
| "If no cities are mentioned, respond with []." |
| ) |
|
|
| messages = [ |
| {"role": "system", "content": system_prompt}, |
| {"role": "user", "content": f"Extract all city names from this text:\n\n{user_prompt}"}, |
| ] |
|
|
| response = client.chat_completion(messages=messages, max_tokens=256, temperature=0.1) |
| |
| raw = response.choices[0].message.content.strip() |
| |
| |
| |
| |
| |
| |
|
|
| |
|
|
| |
| |
| cities = json.loads(raw) |
| |
| return [c.strip() for c in cities if isinstance(c, str) and c.strip()] |
|
|
|
|
| |
| def geocode_city(city: str) -> dict | None: |
| url = "https://geocoding-api.open-meteo.com/v1/search" |
| r = requests.get(url, params={"name": city, "count": 1, "language": "en", "format": "json"}, timeout=10) |
| r.raise_for_status() |
| results = r.json().get("results") |
| if not results: |
| return None |
| loc = results[0] |
| return { |
| "name": loc.get("name", city), |
| "country": loc.get("country", ""), |
| "lat": loc["latitude"], |
| "lon": loc["longitude"], |
| "timezone": loc.get("timezone", "auto"), |
| } |
|
|
|
|
| |
| def fetch_forecast(lat: float, lon: float, timezone: str) -> dict: |
| url = "https://api.open-meteo.com/v1/forecast" |
| params = { |
| "latitude": lat, |
| "longitude": lon, |
| "daily": [ |
| "weathercode", |
| "temperature_2m_max", |
| "temperature_2m_min", |
| "precipitation_sum", |
| "windspeed_10m_max", |
| ], |
| "current_weather": True, |
| "timezone": timezone, |
| "forecast_days": 7, |
| } |
| r = requests.get(url, params=params, timeout=10) |
| r.raise_for_status() |
| return r.json() |
|
|
|
|
| |
| def render_city_card(loc: dict, forecast: dict): |
| daily = forecast["daily"] |
| current = forecast.get("current_weather", {}) |
|
|
| cur_temp = current.get("temperature", "β") |
| cur_wind = current.get("windspeed", "β") |
| cur_code = current.get("weathercode", 0) |
| cur_icon, cur_label = wmo_info(cur_code) |
|
|
| days_html = "" |
| for i in range(len(daily["time"])): |
| date = daily["time"][i] |
| weekday = __import__("datetime").datetime.strptime(date, "%Y-%m-%d").strftime("%a") |
| code = daily["weathercode"][i] |
| icon, _ = wmo_info(code) |
| tmax = daily["temperature_2m_max"][i] |
| tmin = daily["temperature_2m_min"][i] |
| precip = daily["precipitation_sum"][i] |
| days_html += f"<div class=\"day-card\">" |
| days_html += f"<div class=\"day-label\">{weekday}<br>{date[5:]}</div>" |
| days_html += f"<div class=\"day-icon\">{icon}</div>" |
| days_html += f"<div class=\"day-temp-max\">{tmax}Β°</div>" |
| days_html += f"<div class=\"day-temp-min\">{tmin}Β°</div>" |
| days_html += f"<div class=\"day-precip\">π§ {precip}mm</div>" |
| days_html += f"</div>" |
| |
|
|
| st.markdown(f""" |
| <div class="city-card"> |
| <div class="city-name">{cur_icon} {loc['name']}, {loc['country']}</div> |
| <div class="city-coords"> |
| {loc['lat']:.4f}Β°N {loc['lon']:.4f}Β°E |
| </div> |
| <div class="stat-row"> |
| <div class="stat-pill">Now <span>{cur_temp}Β°C</span></div> |
| <div class="stat-pill">Wind <span>{cur_wind} km/h</span></div> |
| <div class="stat-pill">{cur_label}</div> |
| </div> |
| <div class="weather-grid"> |
| {days_html} |
| </div> |
| </div> |
| """, unsafe_allow_html=True) |
|
|
|
|
| |
| st.markdown('<div class="main-title">π€ Weather Forecast</div>', unsafe_allow_html=True) |
| |
| st.markdown('<div class="subtitle">powered by Open-Meteo Β· Llama 70B Β· Hugging Face</div>', unsafe_allow_html=True) |
|
|
|
|
| user_input = st.text_input( |
| label="Your question", |
| placeholder='e.g. "What\'s the weather like in Paris and Tokyo this week?"', |
| label_visibility="collapsed", |
| ) |
|
|
| run = st.button("Get Forecast β", use_container_width=False) |
|
|
| if run and user_input.strip(): |
| with st.spinner("Asking the LLM to find citiesβ¦"): |
| try: |
| cities = extract_cities_with_llm(user_input) |
| except Exception as e: |
| st.markdown(f'<div class="error-box">β οΈ LLM error: {e}</div>', unsafe_allow_html=True) |
| cities = [] |
|
|
| if not cities: |
| st.markdown('<div class="error-box">what cities in particular should I look for?</div>', unsafe_allow_html=True) |
| else: |
| st.markdown(f'<div class="llm-box"><div class="llm-label">Cities detected by LLM</div>{" Β· ".join(cities)}</div>', unsafe_allow_html=True) |
|
|
| for city in cities: |
| with st.spinner(f"Fetching weather for {city}β¦"): |
| try: |
| loc = geocode_city(city) |
| if not loc: |
| st.markdown(f'<div class="error-box">Could not geocode "{city}"</div>', unsafe_allow_html=True) |
| continue |
| forecast = fetch_forecast(loc["lat"], loc["lon"], loc["timezone"]) |
| render_city_card(loc, forecast) |
| except Exception as e: |
| st.markdown(f'<div class="error-box">β οΈ Error for {city}: {e}</div>', unsafe_allow_html=True) |
|
|
| elif run: |
| st.markdown('<div class="error-box">Please enter a question first.</div>', unsafe_allow_html=True) |
|
|
| st.markdown("---") |
| st.markdown( |
| '<div style="font-family: Space Mono, monospace; font-size: 0.65rem; color: #37474f; text-align:center;">' |
| 'Weather data: Open-Meteo (open-source, no API key needed) Β· LLM: Mistral-7B-Instruct via Hugging Face Inference API' |
| '</div>', |
| unsafe_allow_html=True, |
| ) |