import os import logging import base64 import gradio as gr import requests # Configure logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) ZODIAC_SIGNS = ['aries','taurus','gemini','cancer','leo','virgo','libra','scorpio','sagittarius','capricorn','aquarius','pisces'] def call_beandev_api(sign: str, date_str: str) -> str: """Call the Beandev Aistrology API. Uses the Beandev Aistrology API to get English horoscope data. Args: sign: Zodiac sign name (e.g., 'aries', 'taurus'). date_str: Date in YYYY-MM-DD format (e.g., '2025-11-21'). Returns: Formatted horoscope text in English, or an error message starting with '__API call failed:' if the request fails. """ try: from datetime import datetime # Validate and format the date if not date_str: date_str = datetime.now().strftime('%Y-%m-%d') logger.info(f"Fetching horoscope for sign={sign}, date={date_str}") # Fetch horoscope data from Beandev API url = "https://api.aistrology.beandev.xyz/v1" # Send JSON and include a User-Agent to avoid trivial blocks headers = { 'Accept': 'application/json', 'Content-Type': 'application/json', 'User-Agent': 'Zodiac-AI/1.0 (+https://huggingface.co/spaces/mixklim/Zodiac-AI)' } payload = {'sign': sign.lower(), 'date': date_str} # Try the request and provide clearer logging on failures (especially 403) try: response = requests.post(url, headers=headers, json=payload, timeout=10) except requests.RequestException as e: logger.error(f"HTTP request to Beandev API failed: {e}") raise # If the API actively forbids access (403), return a helpful message if response.status_code == 403: logger.error(f"Beandev API returned 403 Forbidden. Response body: {response.text}") return ("__API call failed: 403 Forbidden: The external Beandev API refused the request.") # Log unexpected non-2xx responses to aid debugging if not response.ok: logger.error(f"Beandev API error: status={response.status_code}, body={response.text}") response.raise_for_status() # API returns array of all signs, find the requested sign data = response.json() horoscope = None for item in data: if item['sign'].lower() == sign.lower(): horoscope = item break if not horoscope: raise ValueError(f"Sign {sign} not found in API response") logger.info(f"Successfully fetched horoscope for {sign} - {date_str}") # Zodiac sign emojis zodiac_emojis = { 'aries': '♈', 'taurus': '♉', 'gemini': '♊', 'cancer': '♋', 'leo': '♌', 'virgo': '♍', 'libra': '♎', 'scorpio': '♏', 'sagittarius': '♐', 'capricorn': '♑', 'aquarius': '♒', 'pisces': '♓' } emoji = zodiac_emojis.get(sign.lower(), '✨') # Format the response with HTML for better styling formatted = f"""

{emoji} {sign.capitalize()}

{horoscope['date_range']}

📅 {horoscope['current_date']}

✨ Your Daily Horoscope

{horoscope['description']}

😊 Mood

{horoscope['mood'].capitalize()}

💖 Best Match

{horoscope['compatibility'].capitalize()}

🍀 Lucky Elements

Number

{horoscope['lucky_number']}

Time

{horoscope['lucky_time']}

Color

{horoscope['color'].capitalize()}

""" return formatted except Exception as e: logger.error(f"Failed to fetch horoscope for {sign} - {date_str}: {e}", exc_info=True) error_msg = str(e) if "503" in error_msg: return "__API call failed: The Beandev API service is currently unavailable (503 Service Unavailable). This is a temporary issue with the external API provider." return f"__API call failed: {e}" def get_horoscope(sign: str, date_str: str) -> str: """Retrieve a horoscope from Beandev API. Calls the Beandev Aistrology API for English horoscope data. Args: sign: Zodiac sign name (e.g., 'aries', 'taurus'). date_str: Date in YYYY-MM-DD format (e.g., '2025-11-21'). Returns: A formatted horoscope string in English from the Beandev API, or an error message if the API is unavailable. """ logger.info(f"User requested horoscope: sign={sign}, date={date_str}") result = call_beandev_api(sign, date_str) # If the call failed, return error message if result.startswith('__API call failed'): logger.warning(f"Returning error message to user for {sign} - {date_str}") return f"❌ Unable to fetch horoscope data.\n\nThe Beandev API is currently unavailable. Please check your internet connection and try again later.\n\nError details: {result[18:]}" # Strip '__API call failed: ' logger.info(f"Successfully returning horoscope for {sign} - {date_str}") return result CSS_TEMPLATE = """ @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700&family=Poppins:wght@400;500;600&display=swap'); body { background-color: #0f0a23; color: #e6e6fa; font-family: 'Poppins', sans-serif; min-height: 100vh; } .gradio-container { background-image: url('{{BACKGROUND_IMAGE}}'); background-size: cover; background-attachment: fixed; background-position: center; background-repeat: no-repeat; position: relative; } .gradio-container::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(15, 10, 35, 0.75); z-index: 0; pointer-events: none; } .gradio-container > * { position: relative; z-index: 1; } .gradio-container { max-width: 100% !important; width: 100% !important; padding: 20px !important; box-sizing: border-box !important; } .main-header { text-align: center; padding: 20px; background: linear-gradient(135deg, rgba(102, 126, 234, 0.25) 0%, rgba(118, 75, 162, 0.25) 100%); border-radius: 20px; margin-bottom: 20px; backdrop-filter: blur(15px); border: 1px solid rgba(150, 120, 220, 0.4); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); } .main-title { font-family: 'Playfair Display', serif; font-size: 2.5em; background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; margin: 0; text-shadow: 0 0 30px rgba(102, 126, 234, 0.5); letter-spacing: 2px; } .subtitle { color: #c8c8e0; font-size: 1.1em; margin-top: 8px; font-weight: 300; letter-spacing: 1px; } .input-card { background: rgba(20, 15, 45, 0.85); backdrop-filter: blur(20px); border-radius: 20px; padding: 25px; border: 1px solid rgba(150, 120, 220, 0.3); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); height: 100%; display: flex; flex-direction: column; } .row { display: flex !important; align-items: stretch !important; gap: 20px !important; height: calc(100vh - 280px) !important; } .row > * { flex: 1 1 0 !important; display: flex !important; flex-direction: column !important; } .output-card { background: rgba(20, 15, 45, 0.85); backdrop-filter: blur(20px); border-radius: 20px; padding: 25px; border: 1px solid rgba(150, 120, 220, 0.3); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); height: 100%; overflow-y: auto; } .zodiac-emoji { font-size: 80px; animation: float 3s ease-in-out infinite, rotate 20s linear infinite; display: inline-block; filter: drop-shadow(0 0 20px rgba(255, 255, 255, 0.3)); } @keyframes float { 0%, 100% { transform: translateY(0px) rotate(0deg); } 50% { transform: translateY(-15px) rotate(5deg); } } @keyframes rotate { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } label { font-weight: 600 !important; font-size: 1.1em !important; color: #d0d0e8 !important; margin-bottom: 8px !important; } .input-section-title { color: #a8a8d0; font-size: 0.95em; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid rgba(255, 255, 255, 0.1); font-weight: 500; } button { margin-top: 15px !important; } .tips-section { background: rgba(102, 126, 234, 0.08); padding: 15px; border-radius: 12px; border-left: 3px solid #667eea; margin-top: 20px; } """ def build_interface(): """Build and configure the Gradio user interface. Creates a two-column layout with zodiac sign and time range dropdowns on the left, and a horoscope output textbox on the right. Applies custom CSS styling for a horoscope-themed appearance. Returns: A configured Gradio Blocks interface ready to launch. """ from datetime import datetime # Use a remote background image URL instead of a tracked local file # (keeps repository small and avoids storing binaries in git) bg_image_data = "https://astrogyanvi.com/uploads/blog/1719644082-blog_image.png" logger.info("Using remote background image URL for CSS background") # Generate CSS with background image CSS = CSS_TEMPLATE.replace('{{BACKGROUND_IMAGE}}', bg_image_data) with gr.Blocks(title="🔮 Horoscope — Zodiac Insights") as demo: gr.HTML(f"") # Header gr.HTML("""

🔮 Cosmic Horoscope

✨ Unveil Your Celestial Destiny ✨

""") with gr.Row(elem_classes="row"): # Left column - Input controls with gr.Column(scale=1, min_width=400): gr.HTML("
") gr.Markdown("
🌟 Select Your Details
") sign = gr.Dropdown( label="♈ Zodiac Sign", choices=[s.capitalize() for s in ZODIAC_SIGNS], value='Aries', info="Choose your astrological sign" ) date_picker = gr.DateTime( label="📅 Date", value=datetime.now(), include_time=False, info="Select the date for your reading" ) btn = gr.Button('✨ Reveal My Horoscope', size='lg', variant='primary') gr.HTML("""

💫 How to use:
1. Select your zodiac sign
2. Choose your desired date
3. Click to reveal your cosmic forecast

""") gr.HTML("
") # Right column - Horoscope display with gr.Column(scale=1, min_width=500): output = gr.HTML( label='Your Horoscope', value="" ) def on_get(s, d): s_norm = s.lower() # Convert datetime to string format YYYY-MM-DD if isinstance(d, str): # Handle string format date_str = d.split('T')[0] if 'T' in d else d.split(' ')[0] elif isinstance(d, float) or isinstance(d, int): # Handle timestamp (Unix timestamp in seconds) date_str = datetime.fromtimestamp(d).strftime('%Y-%m-%d') elif hasattr(d, 'strftime'): # Handle datetime object date_str = d.strftime('%Y-%m-%d') else: # Default to today date_str = datetime.now().strftime('%Y-%m-%d') return get_horoscope(s_norm, date_str) btn.click(on_get, inputs=[sign, date_picker], outputs=[output]) return demo if __name__ == '__main__': app = build_interface() app.launch(share=True)