Klim Mikhailov commited on
Commit
0bee8f7
·
1 Parent(s): 8df3ff4

Remove binary image; store via Hugging Face XET instead

Browse files
Files changed (4) hide show
  1. .gitignore +2 -0
  2. README.md +57 -13
  3. app.py +419 -0
  4. requirements.txt +3 -0
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ .gradio
2
+ .venv
README.md CHANGED
@@ -1,13 +1,57 @@
1
- ---
2
- title: Zodiac AI
3
- emoji: 🏢
4
- colorFrom: pink
5
- colorTo: pink
6
- sdk: gradio
7
- sdk_version: 6.0.0
8
- app_file: app.py
9
- pinned: false
10
- short_description: Find your destiny based on zodiac sign
11
- ---
12
-
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Horoscope — Zodiac Insights (Gradio)
2
+
3
+ This is a Gradio web app that provides astrological horoscope predictions and insights for the 12 zodiac signs. It uses the free Beandev Aistrology API to fetch real horoscope data in English.
4
+
5
+ Features
6
+ - Select a zodiac sign (aries, taurus, gemini, cancer, leo, virgo, libra, scorpio, sagittarius, capricorn, aquarius, pisces)
7
+ - Pick any date using an interactive calendar date picker
8
+ - Real horoscope data from the Beandev Aistrology API in English
9
+ - Comprehensive logging for debugging and monitoring
10
+ - Horoscope-themed UI with gradient background
11
+
12
+ Usage
13
+
14
+ 1. Install dependencies (Windows `cmd.exe`):
15
+
16
+ ```
17
+ python -m venv .venv
18
+ .venv\Scripts\activate
19
+ pip install -r requirements.txt
20
+ ```
21
+
22
+ 2. Run the Gradio app:
23
+
24
+ ```
25
+ python app.py
26
+ ```
27
+
28
+ 3. Open the local URL printed in the console (default: `http://localhost:7860`).
29
+
30
+ 4. Select your zodiac sign and choose a date from the calendar, then click "Get Horoscope" to receive your personalized horoscope!
31
+
32
+ How It Works
33
+
34
+ - The app uses the Python `requests` library to fetch real horoscope data in English
35
+ - Connects to the free Beandev Aistrology API (https://api.aistrology.beandev.xyz/v1)
36
+ - Users select any date via an interactive calendar picker (Gradio DateTime component)
37
+ - The selected date is sent to the API in YYYY-MM-DD format using the `date` parameter
38
+ - If the API is unavailable (no internet or API down), displays a clear error message
39
+ - All horoscopes are displayed in English with sections for mood, compatibility, lucky numbers, colors, and more
40
+ - Comprehensive logging tracks all API calls, successes, and errors
41
+
42
+ Customization
43
+
44
+ - Edit `app.py` to change styling or output format
45
+ - The CSS styling contains the gradient background and UI theme
46
+ - You can modify the response formatting section to customize how API results are displayed
47
+ - Adjust logging level in the `logging.basicConfig()` call (INFO, DEBUG, WARNING, ERROR)
48
+
49
+ API Credits
50
+
51
+ - Real horoscope data provided by [Beandev Aistrology API](https://aistrology.beandev.xyz/docs) (free and open source)
52
+ - API endpoint: https://api.aistrology.beandev.xyz/v1
53
+ - No API key or authentication required
54
+
55
+ License
56
+
57
+ - MIT
app.py ADDED
@@ -0,0 +1,419 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ import base64
4
+ import gradio as gr
5
+ import requests
6
+
7
+ # Configure logging
8
+ logging.basicConfig(
9
+ level=logging.INFO,
10
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
11
+ )
12
+ logger = logging.getLogger(__name__)
13
+
14
+ ZODIAC_SIGNS = ['aries','taurus','gemini','cancer','leo','virgo','libra','scorpio','sagittarius','capricorn','aquarius','pisces']
15
+
16
+
17
+ def call_beandev_api(sign: str, date_str: str) -> str:
18
+ """Call the Beandev Aistrology API.
19
+
20
+ Uses the Beandev Aistrology API to get English horoscope data.
21
+
22
+ Args:
23
+ sign: Zodiac sign name (e.g., 'aries', 'taurus').
24
+ date_str: Date in YYYY-MM-DD format (e.g., '2025-11-21').
25
+
26
+ Returns:
27
+ Formatted horoscope text in English, or an error message starting with
28
+ '__API call failed:' if the request fails.
29
+ """
30
+ try:
31
+ from datetime import datetime
32
+
33
+ # Validate and format the date
34
+ if not date_str:
35
+ date_str = datetime.now().strftime('%Y-%m-%d')
36
+
37
+ logger.info(f"Fetching horoscope for sign={sign}, date={date_str}")
38
+
39
+ # Fetch horoscope data from Beandev API
40
+ url = "https://api.aistrology.beandev.xyz/v1"
41
+ headers = {'Accept': 'application/json'}
42
+ payload = {'sign': sign.lower(), 'date': date_str}
43
+
44
+ response = requests.post(url, headers=headers, data=payload, timeout=10)
45
+ response.raise_for_status()
46
+
47
+ # API returns array of all signs, find the requested sign
48
+ data = response.json()
49
+ horoscope = None
50
+ for item in data:
51
+ if item['sign'].lower() == sign.lower():
52
+ horoscope = item
53
+ break
54
+
55
+ if not horoscope:
56
+ raise ValueError(f"Sign {sign} not found in API response")
57
+
58
+ logger.info(f"Successfully fetched horoscope for {sign} - {date_str}")
59
+
60
+ # Zodiac sign emojis
61
+ zodiac_emojis = {
62
+ 'aries': '♈', 'taurus': '♉', 'gemini': '♊', 'cancer': '♋',
63
+ 'leo': '♌', 'virgo': '♍', 'libra': '♎', 'scorpio': '♏',
64
+ 'sagittarius': '♐', 'capricorn': '♑', 'aquarius': '♒', 'pisces': '♓'
65
+ }
66
+
67
+ emoji = zodiac_emojis.get(sign.lower(), '✨')
68
+
69
+ # Format the response with HTML for better styling
70
+ formatted = f"""<div style="font-family: 'Segoe UI', system-ui, sans-serif; line-height: 1.8; color: #f0f0f0;">
71
+
72
+ <div style="text-align: center; margin-bottom: 25px; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 15px; box-shadow: 0 8px 20px rgba(0,0,0,0.3);">
73
+ <h1 style="margin: 0; font-size: 2.2em; color: white; text-shadow: 2px 2px 4px rgba(0,0,0,0.3);">{emoji} {sign.capitalize()}</h1>
74
+ <p style="margin: 8px 0 0 0; font-size: 1.1em; color: #e0e0ff; opacity: 0.95;">{horoscope['date_range']}</p>
75
+ <p style="margin: 5px 0 0 0; font-size: 0.95em; color: #fff; opacity: 0.8;">📅 {horoscope['current_date']}</p>
76
+ </div>
77
+
78
+ <div style="background: rgba(255,255,255,0.05); padding: 20px; border-radius: 12px; margin-bottom: 15px; border-left: 4px solid #667eea;">
79
+ <h3 style="margin: 0 0 12px 0; color: #a8b3ff; font-size: 1.2em;">✨ Your Daily Horoscope</h3>
80
+ <p style="margin: 0; font-size: 1.05em; line-height: 1.9; color: #e8e8e8;">{horoscope['description']}</p>
81
+ </div>
82
+
83
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 15px;">
84
+ <div style="background: linear-gradient(135deg, rgba(255,107,107,0.15) 0%, rgba(255,107,107,0.05) 100%); padding: 18px; border-radius: 12px; border: 1px solid rgba(255,107,107,0.3);">
85
+ <h4 style="margin: 0 0 8px 0; color: #ffb3ba; font-size: 1em;">😊 Mood</h4>
86
+ <p style="margin: 0; font-size: 1.1em; font-weight: 500; color: #ffd4d4;">{horoscope['mood'].capitalize()}</p>
87
+ </div>
88
+
89
+ <div style="background: linear-gradient(135deg, rgba(255,193,7,0.15) 0%, rgba(255,193,7,0.05) 100%); padding: 18px; border-radius: 12px; border: 1px solid rgba(255,193,7,0.3);">
90
+ <h4 style="margin: 0 0 8px 0; color: #ffd54f; font-size: 1em;">💖 Best Match</h4>
91
+ <p style="margin: 0; font-size: 1.1em; font-weight: 500; color: #ffe082;">{horoscope['compatibility'].capitalize()}</p>
92
+ </div>
93
+ </div>
94
+
95
+ <div style="background: linear-gradient(135deg, rgba(76,175,80,0.15) 0%, rgba(76,175,80,0.05) 100%); padding: 20px; border-radius: 12px; border: 1px solid rgba(76,175,80,0.3);">
96
+ <h3 style="margin: 0 0 15px 0; color: #a5d6a7; font-size: 1.15em;">🍀 Lucky Elements</h3>
97
+ <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px;">
98
+ <div>
99
+ <p style="margin: 0; font-size: 0.85em; color: #c8e6c9; opacity: 0.8;">Number</p>
100
+ <p style="margin: 4px 0 0 0; font-size: 1.3em; font-weight: bold; color: #e8f5e9;">{horoscope['lucky_number']}</p>
101
+ </div>
102
+ <div>
103
+ <p style="margin: 0; font-size: 0.85em; color: #c8e6c9; opacity: 0.8;">Time</p>
104
+ <p style="margin: 4px 0 0 0; font-size: 1.3em; font-weight: bold; color: #e8f5e9;">{horoscope['lucky_time']}</p>
105
+ </div>
106
+ <div>
107
+ <p style="margin: 0; font-size: 0.85em; color: #c8e6c9; opacity: 0.8;">Color</p>
108
+ <p style="margin: 4px 0 0 0; font-size: 1.3em; font-weight: bold; color: #e8f5e9;">{horoscope['color'].capitalize()}</p>
109
+ </div>
110
+ </div>
111
+ </div>
112
+
113
+ </div>"""
114
+
115
+ return formatted
116
+
117
+ except Exception as e:
118
+ logger.error(f"Failed to fetch horoscope for {sign} - {date_str}: {e}", exc_info=True)
119
+ error_msg = str(e)
120
+ if "503" in error_msg:
121
+ return "__API call failed: The Beandev API service is currently unavailable (503 Service Unavailable). This is a temporary issue with the external API provider."
122
+ return f"__API call failed: {e}"
123
+
124
+
125
+ def get_horoscope(sign: str, date_str: str) -> str:
126
+ """Retrieve a horoscope from Beandev API.
127
+
128
+ Calls the Beandev Aistrology API for English horoscope data.
129
+
130
+ Args:
131
+ sign: Zodiac sign name (e.g., 'aries', 'taurus').
132
+ date_str: Date in YYYY-MM-DD format (e.g., '2025-11-21').
133
+
134
+ Returns:
135
+ A formatted horoscope string in English from the Beandev API,
136
+ or an error message if the API is unavailable.
137
+ """
138
+ logger.info(f"User requested horoscope: sign={sign}, date={date_str}")
139
+
140
+ result = call_beandev_api(sign, date_str)
141
+
142
+ # If the call failed, return error message
143
+ if result.startswith('__API call failed'):
144
+ logger.warning(f"Returning error message to user for {sign} - {date_str}")
145
+ 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: '
146
+
147
+ logger.info(f"Successfully returning horoscope for {sign} - {date_str}")
148
+ return result
149
+
150
+
151
+ CSS_TEMPLATE = """
152
+ @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700&family=Poppins:wght@400;500;600&display=swap');
153
+
154
+ body {
155
+ background-color: #0f0a23;
156
+ color: #e6e6fa;
157
+ font-family: 'Poppins', sans-serif;
158
+ min-height: 100vh;
159
+ }
160
+
161
+ .gradio-container {
162
+ background-image: url('{{BACKGROUND_IMAGE}}');
163
+ background-size: cover;
164
+ background-attachment: fixed;
165
+ background-position: center;
166
+ background-repeat: no-repeat;
167
+ position: relative;
168
+ }
169
+
170
+ .gradio-container::before {
171
+ content: '';
172
+ position: absolute;
173
+ top: 0;
174
+ left: 0;
175
+ right: 0;
176
+ bottom: 0;
177
+ background: rgba(15, 10, 35, 0.75);
178
+ z-index: 0;
179
+ pointer-events: none;
180
+ }
181
+
182
+ .gradio-container > * {
183
+ position: relative;
184
+ z-index: 1;
185
+ }
186
+
187
+ .gradio-container {
188
+ max-width: 100% !important;
189
+ width: 100% !important;
190
+ padding: 20px !important;
191
+ box-sizing: border-box !important;
192
+ }
193
+
194
+ .main-header {
195
+ text-align: center;
196
+ padding: 20px;
197
+ background: linear-gradient(135deg, rgba(102, 126, 234, 0.25) 0%, rgba(118, 75, 162, 0.25) 100%);
198
+ border-radius: 20px;
199
+ margin-bottom: 20px;
200
+ backdrop-filter: blur(15px);
201
+ border: 1px solid rgba(150, 120, 220, 0.4);
202
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
203
+ }
204
+
205
+ .main-title {
206
+ font-family: 'Playfair Display', serif;
207
+ font-size: 2.5em;
208
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
209
+ -webkit-background-clip: text;
210
+ -webkit-text-fill-color: transparent;
211
+ background-clip: text;
212
+ margin: 0;
213
+ text-shadow: 0 0 30px rgba(102, 126, 234, 0.5);
214
+ letter-spacing: 2px;
215
+ }
216
+
217
+ .subtitle {
218
+ color: #c8c8e0;
219
+ font-size: 1.1em;
220
+ margin-top: 8px;
221
+ font-weight: 300;
222
+ letter-spacing: 1px;
223
+ }
224
+
225
+ .input-card {
226
+ background: rgba(20, 15, 45, 0.85);
227
+ backdrop-filter: blur(20px);
228
+ border-radius: 20px;
229
+ padding: 25px;
230
+ border: 1px solid rgba(150, 120, 220, 0.3);
231
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
232
+ height: 100%;
233
+ display: flex;
234
+ flex-direction: column;
235
+ }
236
+
237
+ .row {
238
+ display: flex !important;
239
+ align-items: stretch !important;
240
+ gap: 20px !important;
241
+ height: calc(100vh - 280px) !important;
242
+ }
243
+
244
+ .row > * {
245
+ flex: 1 1 0 !important;
246
+ display: flex !important;
247
+ flex-direction: column !important;
248
+ }
249
+
250
+ .output-card {
251
+ background: rgba(20, 15, 45, 0.85);
252
+ backdrop-filter: blur(20px);
253
+ border-radius: 20px;
254
+ padding: 25px;
255
+ border: 1px solid rgba(150, 120, 220, 0.3);
256
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
257
+ height: 100%;
258
+ overflow-y: auto;
259
+ }
260
+
261
+ .zodiac-emoji {
262
+ font-size: 80px;
263
+ animation: float 3s ease-in-out infinite, rotate 20s linear infinite;
264
+ display: inline-block;
265
+ filter: drop-shadow(0 0 20px rgba(255, 255, 255, 0.3));
266
+ }
267
+
268
+ @keyframes float {
269
+ 0%, 100% { transform: translateY(0px) rotate(0deg); }
270
+ 50% { transform: translateY(-15px) rotate(5deg); }
271
+ }
272
+
273
+ @keyframes rotate {
274
+ 0% { transform: rotate(0deg); }
275
+ 100% { transform: rotate(360deg); }
276
+ }
277
+
278
+ label {
279
+ font-weight: 600 !important;
280
+ font-size: 1.1em !important;
281
+ color: #d0d0e8 !important;
282
+ margin-bottom: 8px !important;
283
+ }
284
+
285
+ .input-section-title {
286
+ color: #a8a8d0;
287
+ font-size: 0.95em;
288
+ margin-bottom: 15px;
289
+ padding-bottom: 10px;
290
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
291
+ font-weight: 500;
292
+ }
293
+
294
+ button {
295
+ margin-top: 15px !important;
296
+ }
297
+
298
+ .tips-section {
299
+ background: rgba(102, 126, 234, 0.08);
300
+ padding: 15px;
301
+ border-radius: 12px;
302
+ border-left: 3px solid #667eea;
303
+ margin-top: 20px;
304
+ }
305
+ """
306
+
307
+
308
+ def build_interface():
309
+ """Build and configure the Gradio user interface.
310
+
311
+ Creates a two-column layout with zodiac sign and time range dropdowns on the left,
312
+ and a horoscope output textbox on the right. Applies custom CSS styling for a
313
+ horoscope-themed appearance.
314
+
315
+ Returns:
316
+ A configured Gradio Blocks interface ready to launch.
317
+ """
318
+ from datetime import datetime
319
+
320
+ # Load and encode background image as base64
321
+ bg_image_data = ""
322
+ bg_path = "data/numerology-collage-concept.jpg"
323
+ try:
324
+ if os.path.exists(bg_path):
325
+ with open(bg_path, "rb") as img_file:
326
+ bg_base64 = base64.b64encode(img_file.read()).decode()
327
+ bg_image_data = f"data:image/jpeg;base64,{bg_base64}"
328
+ logger.info(f"Background image loaded successfully from {bg_path}")
329
+ else:
330
+ logger.warning(f"Background image not found at {bg_path}")
331
+ except Exception as e:
332
+ logger.error(f"Failed to load background image: {e}")
333
+
334
+ # Generate CSS with background image
335
+ CSS = CSS_TEMPLATE.replace('{{BACKGROUND_IMAGE}}', bg_image_data)
336
+
337
+ with gr.Blocks(title="🔮 Horoscope — Zodiac Insights") as demo:
338
+ gr.HTML(f"<style>{CSS}</style>")
339
+
340
+ # Header
341
+ gr.HTML("""
342
+ <div class='main-header'>
343
+ <h1 class='main-title'>🔮 Cosmic Horoscope</h1>
344
+ <p class='subtitle'>✨ Unveil Your Celestial Destiny ✨</p>
345
+ </div>
346
+ """)
347
+
348
+ with gr.Row(elem_classes="row"):
349
+ # Left column - Input controls
350
+ with gr.Column(scale=1, min_width=400):
351
+ gr.HTML("<div class='input-card'>")
352
+ gr.Markdown("<div class='input-section-title'>🌟 Select Your Details</div>")
353
+
354
+ sign = gr.Dropdown(
355
+ label="♈ Zodiac Sign",
356
+ choices=[s.capitalize() for s in ZODIAC_SIGNS],
357
+ value='Aries',
358
+ info="Choose your astrological sign"
359
+ )
360
+
361
+ date_picker = gr.DateTime(
362
+ label="📅 Date",
363
+ value=datetime.now(),
364
+ include_time=False,
365
+ info="Select the date for your reading"
366
+ )
367
+
368
+ btn = gr.Button('✨ Reveal My Horoscope', size='lg', variant='primary')
369
+
370
+ gr.HTML("""
371
+ <div class='tips-section'>
372
+ <p style='margin: 0; color: #b8b8e0; font-size: 0.95em;'>
373
+ <strong>💫 How to use:</strong><br/>
374
+ 1. Select your zodiac sign<br/>
375
+ 2. Choose your desired date<br/>
376
+ 3. Click to reveal your cosmic forecast
377
+ </p>
378
+ </div>
379
+ """)
380
+ gr.HTML("</div>")
381
+
382
+ # Right column - Horoscope display
383
+ with gr.Column(scale=1, min_width=500):
384
+ output = gr.HTML(
385
+ label='Your Horoscope',
386
+ value=""
387
+ )
388
+
389
+ # Footer
390
+ gr.HTML("""
391
+ <div style='text-align: center; margin-top: 30px; padding: 20px; color: #8888aa; font-size: 0.9em;'>
392
+ <p>Powered by Beandev Aistrology API • Embrace the wisdom of the cosmos</p>
393
+ </div>
394
+ """)
395
+
396
+ def on_get(s, d):
397
+ s_norm = s.lower()
398
+ # Convert datetime to string format YYYY-MM-DD
399
+ if isinstance(d, str):
400
+ # Handle string format
401
+ date_str = d.split('T')[0] if 'T' in d else d.split(' ')[0]
402
+ elif isinstance(d, float) or isinstance(d, int):
403
+ # Handle timestamp (Unix timestamp in seconds)
404
+ date_str = datetime.fromtimestamp(d).strftime('%Y-%m-%d')
405
+ elif hasattr(d, 'strftime'):
406
+ # Handle datetime object
407
+ date_str = d.strftime('%Y-%m-%d')
408
+ else:
409
+ # Default to today
410
+ date_str = datetime.now().strftime('%Y-%m-%d')
411
+ return get_horoscope(s_norm, date_str)
412
+
413
+ btn.click(on_get, inputs=[sign, date_picker], outputs=[output])
414
+ return demo
415
+
416
+
417
+ if __name__ == '__main__':
418
+ app = build_interface()
419
+ app.launch(share=True)
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio>=6.0
2
+ requests>=2.25.0
3
+ huggingface_hub>=1.1.5