DailyPal / app.py
nharshavardhana's picture
commit
9f84710
raw
history blame
12 kB
import os
import requests
import gradio as gr
from dotenv import load_dotenv
import re
from typing import Optional
import base64
from time import sleep
def get_book_recommendations(prompt: str) -> str:
"""
Fetches a book recommendation from Google Books API.
Args:
prompt: User search query, e.g., "thriller novel by Gillian Flynn"
Returns:
Top book recommendation in formatted text.
"""
load_dotenv() # Make sure this is called before accessing environment variables
api_key = os.getenv("GOOGLE_BOOKS_API_KEY")
if not api_key:
return "API key not found."
search_query = prompt.strip()
url = f"https://www.googleapis.com/books/v1/volumes?q={search_query}&key={api_key}"
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
books = response.json().get("items", [])
if not books:
return "No books found."
volume_info = books[0].get("volumeInfo", {})
title = volume_info.get("title", "Unknown Title")
authors = ", ".join(volume_info.get("authors", ["Unknown Author"]))
description = volume_info.get("description", "No description available.")
return f"πŸ“š **{title}** by *{authors}*\n\n{description}"
except Exception as e:
return f"Error: {str(e)}"
def get_random_recipe(tags: str = "", exclude_ingredients: str = "") -> str:
"""
Fetches a random recipe using the Spoonacular API and returns it in a formatted string.
Args:
tags (str): Comma-separated tags like 'vegetarian,dessert'.
exclude_ingredients (str): Comma-separated ingredients to exclude.
Returns:
str: A formatted string with title, preparation time, ingredients, and cooking steps.
"""
load_dotenv()
recipe_api_key = os.getenv("SPOONACULAR_API_KEY")
if not recipe_api_key:
return "❌ API key not found in environment."
url = "https://api.spoonacular.com/recipes/random"
params = {
"number": 1,
"tags": tags,
"excludeIngredients": exclude_ingredients,
"apiKey": recipe_api_key
}
try:
response = requests.get(url, params=params, timeout=10)
data = response.json()
except Exception:
return "❌ Failed to parse JSON from API response."
if response.status_code != 200:
return f"❌ API Error: {data.get('message', 'Unknown error')}"
if not data.get("recipes"):
return "❌ No recipes returned."
recipe = data["recipes"][0]
title = recipe.get("title", "Unknown")
ready_in = recipe.get("readyInMinutes", "?")
ingredients = [i["name"] for i in recipe.get("extendedIngredients", [])]
steps = []
for block in recipe.get("analyzedInstructions", []):
for step in block.get("steps", []):
steps.append(step["step"])
formatted = f"🍽️ **Title**: {title}\n"
formatted += f"πŸ•’ **Ready in**: {ready_in} minutes\n"
formatted += f"πŸ“‹ **Ingredients**: {', '.join(ingredients) if ingredients else 'N/A'}\n"
formatted += "πŸ‘¨β€πŸ³ **Steps**:\n"
for idx, step in enumerate(steps, 1):
formatted += f" {idx}. {step.strip()}\n"
return formatted.strip()
def get_now_playing_movies(retries=3) -> str:
api_key = os.getenv("TMDB_API_KEY")
url = f"https://api.themoviedb.org/3/movie/now_playing?api_key={api_key}&language=en-US&page=1"
for attempt in range(1, retries + 1):
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
movies = response.json().get("results", [])
if not movies:
return "No movies currently playing."
formatted = []
for movie in movies[:5]:
title = movie.get("title", "Untitled")
overview = movie.get("overview", "No description available.")
formatted.append(f"🎬 **{title}**\n{overview}\n")
return "\n".join(formatted)
except Exception as e:
if attempt == retries:
return f"❌ Failed after {retries} attempts: {e}"
sleep(1) # Wait before retrying
def get_music_recommendations(prompt: str, num_songs: int = 3, min_popularity: int = 0, year: str = "") -> str:
"""
Fetches multiple music recommendations from Spotify API.
Args:
prompt: Search query (genre, mood, etc.)
num_songs: Number of song recommendations to return.
min_popularity: Minimum popularity score (0–100).
year: Optional release year filter, e.g., '2022' or '2010-2020'.
Returns:
Markdown-formatted string of top song recommendations.
"""
load_dotenv()
client_id = os.getenv("SPOTIFY_CLIENT_ID")
client_secret = os.getenv("SPOTIFY_CLIENT_SECRET")
if not client_id or not client_secret:
return "Spotify API credentials not found."
# Encode client_id:client_secret
auth_str = f"{client_id}:{client_secret}"
b64_auth_str = base64.b64encode(auth_str.encode()).decode()
try:
token_response = requests.post(
"https://accounts.spotify.com/api/token",
data={"grant_type": "client_credentials"},
headers={"Authorization": f"Basic {b64_auth_str}"}
)
token_response.raise_for_status()
access_token = token_response.json().get("access_token")
except Exception as e:
return f"Token Error: {str(e)}"
try:
# Append year filter if provided
search_query = prompt.strip()
if year:
search_query += f" year:{year}"
url = f"https://api.spotify.com/v1/search?q={search_query}&type=track&limit={num_songs}"
headers = {"Authorization": f"Bearer {access_token}"}
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
tracks = response.json().get("tracks", {}).get("items", [])
if not tracks:
return "No songs found."
# Filter by popularity and format results
filtered_tracks = [
track for track in tracks if track["popularity"] >= min_popularity
]
if not filtered_tracks:
return f"No songs found with popularity β‰₯ {min_popularity}."
output_lines = []
for track in filtered_tracks:
name = track["name"]
artist = ", ".join(artist["name"] for artist in track["artists"])
popularity = track["popularity"]
spotify_url = track["external_urls"]["spotify"]
preview_url = track.get("preview_url")
entry = f"🎡 **{name}** by *{artist}* (Popularity: {popularity})\n[Listen on Spotify]({spotify_url})"
if preview_url:
entry += f"\nPreview: {preview_url}"
output_lines.append(entry)
return "\n\n---\n\n".join(output_lines)
except Exception as e:
return f"Error: {str(e)}"
def get_current_weather(location: str) -> str:
"""
Fetches current weather for a given city name or coordinates using OpenWeatherMap API.
Args:
location: A city name (e.g., "New York") or "lat,lon" format (e.g., "44.34,10.99")
Returns:
A formatted weather report string.
"""
load_dotenv()
api_key = os.getenv("OPENWEATHER_API_KEY")
if not api_key:
return "OpenWeatherMap API key not found."
try:
# Check if input is coordinates
if "," in location:
lat, lon = map(str.strip, location.split(","))
else:
# Use geocoding API to get coordinates from city name
geo_url = f"http://api.openweathermap.org/geo/1.0/direct?q={location}&limit=1&appid={api_key}"
geo_resp = requests.get(geo_url, timeout=10)
geo_resp.raise_for_status()
geo_data = geo_resp.json()
if not geo_data:
return f"Could not find location: {location}"
lat = geo_data[0]["lat"]
lon = geo_data[0]["lon"]
# Get weather
weather_url = f"https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={api_key}&units=metric"
weather_resp = requests.get(weather_url, timeout=10)
weather_resp.raise_for_status()
data = weather_resp.json()
city = f"{data.get('name', 'Unknown')}, {data['sys'].get('country', '')}"
condition = data["weather"][0]["description"].capitalize()
temp = data["main"]["temp"]
feels_like = data["main"]["feels_like"]
humidity = data["main"]["humidity"]
wind = data["wind"]["speed"]
return (
f"🌀 **Weather in {city}**\n"
f"Condition: {condition}\n"
f"🌑 Temperature: {temp}°C (Feels like {feels_like}°C)\n"
f"πŸ’§ Humidity: {humidity}%\n"
f"🌬 Wind Speed: {wind} m/s"
)
except Exception as e:
return f"Error fetching weather: {str(e)}"
# Define Gradio interface
book_interface = gr.Interface(
fn=get_book_recommendations,
inputs=gr.Textbox(label="Enter your book query"),
outputs=gr.Markdown(label="Top Recommendation"),
api_name="get_book_recommendations"
)
recipe_interface = gr.Interface(
fn=get_random_recipe,
inputs=[
gr.Textbox(label="Tags (e.g., vegetarian,dessert)", placeholder="Enter tags or leave blank"),
gr.Textbox(label="Exclude Ingredients", placeholder="e.g., nuts,gluten")
],
outputs=gr.Markdown(label="Random Recipe"),
api_name="get_random_recipe" # This is critical for Hugging Face MCP endpoints
)
movie_interface = gr.Interface(
fn=get_now_playing_movies,
inputs=[], # No input needed for now-playing movies
outputs=gr.Markdown(label="Now Playing Movies"),
api_name="get_now_playing_movies"
)
music_interface = gr.Interface(
fn=get_music_recommendations,
inputs=[
gr.Textbox(label="Enter your music query (e.g., 'lofi beats', 'happy pop')"),
gr.Slider(minimum=1, maximum=10, value=3, label="Number of Songs"),
gr.Slider(minimum=0, maximum=100, value=0, label="Minimum Popularity"),
gr.Textbox(label="Release Year (e.g., 2022 or 2015-2020)", placeholder="Optional")
],
outputs=gr.Markdown(label="Song Recommendations"),
api_name="get_music_recommendations"
)
weather_interface = gr.Interface(
fn=get_current_weather,
inputs=gr.Textbox(label="Enter a location (e.g., 'New York' or '44.34,10.99')"),
outputs=gr.Markdown(label="Current Weather"),
api_name="get_current_weather"
)
with gr.Blocks(title="DailyPal: Your Daily Discovery Assistant") as demo:
gr.Markdown("""
# 🌟 DailyPal
Welcome to **DailyPal**, your all-in-one assistant to discover:
- πŸ“š Great books
- 🍳 Random recipes
- 🎬 Movies now playing
- 🎡 Music recommendations
- ☁️ Current weather updates
---
βš™οΈ **How to use**:
Just select a tab and enter your query or location. Get instant suggestions to brighten your day!
""")
gr.TabbedInterface(
[
book_interface,
recipe_interface,
movie_interface,
music_interface,
weather_interface,
# Add more tools here
],
[
"Book Finder",
"Random Recipe",
"Now Playing Movies",
"Music Recommendations",
"Current Weather"
]
)
gr.Markdown("**Example prompt using mcp client like claude desktop**: I am in new york, if the weather is good, then get me a recipe to grill outdoors, if not get me a book on mars to read, and also get me the songs by weekend to listen.")
gr.Markdown("### πŸ”— [Watch Demo Video](https://drive.google.com/file/d/1DV0Plrhdr7kAWtLRQS91BnVuvRElPbOm/view?usp=drive_link)")
if __name__ == "__main__":
demo.launch(mcp_server=True)