lokesh341 commited on
Commit
0cc9270
ยท
verified ยท
1 Parent(s): 471964f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +136 -149
app.py CHANGED
@@ -1,4 +1,4 @@
1
- # app.py - Gym Workout Plan PDF (Fixed Text Display + Workout Numbers)
2
 
3
  import gradio as gr
4
  from fpdf import FPDF
@@ -10,84 +10,82 @@ class GymWorkoutPDF(FPDF):
10
  def footer(self):
11
  self.set_y(-10)
12
  self.set_font('Arial', 'I', 7)
13
- self.cell(0, 8, f'GYM WORKOUT PLAN | Page {self.page_no()} | {datetime.now().strftime("%d/%m/%Y")}', 0, 0, 'C')
14
 
15
- def clean_text_for_pdf(text, max_length=40):
16
- """Fixed text cleaning - preserves more characters"""
17
  if not text:
18
- return "Workout Plan"
19
-
20
- # Convert to string first
21
  text = str(text)
22
-
23
- # Preserve gym-related characters
24
  replacements = {
25
  'โ€“': '-', 'โ€”': '-', 'โ€œ': '"', 'โ€': '"', 'โ€˜': "'", 'โ€™': "'",
26
- 'โ€ข': 'โ€ข', 'ยฐ': 'ยฐ', 'โ„ข': 'TM', 'ยฎ': 'R', 'ยฉ': 'C',
27
- '\n': '\n', '\r': '', '\t': ' ' # Preserve line breaks
28
  }
29
-
30
  for old, new in replacements.items():
31
  text = text.replace(old, new)
32
-
33
- # Keep safe characters including gym symbols
34
- text = re.sub(r'[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]', '', text) # Remove control chars only
35
- text = re.sub(r'\s+', ' ', text) # Normalize spaces
36
- return text.strip()[:max_length]
37
 
38
- def generate_gym_workout_pdf(title, names, image_files):
39
  # Validation
40
- if not title.strip():
41
- return None, "โŒ Workout Plan Title Required!"
42
 
43
- names_list = [n.strip() for n in names.split(',') if n.strip()]
44
 
45
  if not names_list:
46
- return None, "โŒ Add Gym Member Names!"
47
 
48
  if len(names_list) > 30:
49
- return None, "โŒ Max 30 Members!"
50
 
51
  if not image_files or len(names_list) != len(image_files):
52
- return None, f"โŒ Need {len(names_list)} Member Photos!"
53
 
54
  try:
55
  pdf = GymWorkoutPDF()
56
  pdf.set_margins(18, 18, 18)
57
  pdf.set_auto_page_break(auto=True, margin=18)
58
 
59
- # COVER PAGE with PROPER TEXT SIZES
 
 
60
  pdf.add_page()
61
- pdf.set_font('Arial', 'B', 18) # Title size
62
  pdf.set_text_color(0, 128, 0)
63
- pdf.cell(0, 15, "๐Ÿ‹๏ธ GYM WORKOUT PROGRAM", ln=1, align='C')
64
 
65
- pdf.set_font('Arial', 'B', 14) # Subtitle size
66
- clean_title = clean_text_for_pdf(title)
67
- pdf.cell(0, 12, clean_title.upper(), ln=1, align='C')
68
 
69
- pdf.ln(10)
70
- pdf.set_font('Arial', 'B', 11)
71
- pdf.cell(0, 8, f"{'='*45}", ln=1, align='C')
72
- pdf.set_font('Arial', '', 10)
73
- pdf.cell(0, 8, f"๐Ÿ“‹ MEMBERS: {len(names_list)}", ln=1, align='C')
74
- pdf.cell(0, 8, f"๐Ÿ“… DATE: {datetime.now().strftime('%d/%m/%Y %H:%M')}", ln=1, align='C')
75
- pdf.set_font('Arial', 'B', 11)
76
- pdf.cell(0, 8, f"{'='*45}", ln=1, align='C')
77
 
78
- # MEMBER WORKOUT PAGES
79
- for i, (name, img_path) in enumerate(zip(names_list, image_files)):
80
  pdf.add_page()
81
 
82
- # WORKOUT NUMBER HEADER (NOT MEMBER #)
83
- pdf.set_font('Arial', 'B', 14)
84
  pdf.set_text_color(0, 128, 0)
85
- pdf.cell(0, 12, f"WORKOUT {i+1}", ln=1, align='C')
86
 
87
- # PHOTO SECTION (Medium size)
88
- pdf.ln(8)
89
- img_width = 100
90
- img_height = 120
 
 
 
 
91
  page_width = pdf.w - 36
92
  x_pos = (page_width - img_width) / 2 + 18
93
 
@@ -103,193 +101,182 @@ def generate_gym_workout_pdf(title, names, image_files):
103
  pass
104
 
105
  if not image_success:
 
106
  pdf.set_fill_color(240, 248, 240)
107
  pdf.set_draw_color(0, 128, 0)
108
  pdf.rect(x_pos, pdf.get_y(), img_width, img_height, 'FD')
109
- pdf.set_font('Arial', 'B', 10)
110
  pdf.set_text_color(0, 128, 0)
111
- pdf.set_xy(x_pos + 25, pdf.get_y() + 45)
112
  pdf.cell(img_width - 50, 10, "WORKOUT", ln=1, align='C')
113
- pdf.set_xy(x_pos + 25, pdf.get_y() + 60)
114
- pdf.cell(img_width - 50, 10, "PHOTO", ln=1, align='C')
115
 
116
- # MEMBER NAME (CORRECT SIZE & POSITION)
117
  pdf.set_y(pdf.get_y() + img_height + 15)
118
- pdf.set_font('Arial', 'B', 15) # Medium name size
119
- pdf.set_text_color(0, 0, 0)
120
- clean_name = clean_text_for_pdf(name, 30)
121
- pdf.multi_cell(0, 12, clean_name.upper(), align='C') # Use multi_cell for better text
122
-
123
- # WORKOUT INFO SECTION
124
- pdf.ln(8)
125
- pdf.set_font('Arial', 'B', 11)
126
  pdf.set_text_color(0, 128, 0)
127
- pdf.cell(0, 10, "๐Ÿ’ช WORKOUT DETAILS", ln=1, align='C')
128
 
129
  pdf.set_draw_color(0, 128, 0)
130
  pdf.line(18, pdf.get_y(), pdf.w - 18, pdf.get_y())
131
- pdf.ln(5)
132
 
133
- # Workout plan template with proper spacing
134
  pdf.set_font('Arial', '', 9)
135
  pdf.set_text_color(60, 60, 60)
136
 
137
- workout_lines = [
138
- "๐Ÿ“‹ EXERCISES: _______________________________________________",
139
- "โšก SETS/REPS: ______________________________________________",
140
- "โฑ๏ธ REST TIME: _______________________________________________",
141
- "๐Ÿ’ง WATER BREAKS: __________________________________________",
142
- "๐Ÿ“ˆ PROGRESS NOTES: ________________________________________"
 
 
143
  ]
144
 
145
- for line in workout_lines:
146
  pdf.ln(6)
147
- pdf.cell(0, 8, line, ln=1, align='L')
148
 
149
- # Workout number footer
150
- pdf.ln(10)
151
- pdf.set_font('Arial', 'B', 10)
152
  pdf.set_text_color(0, 128, 0)
153
- pdf.cell(0, 8, f"WORKOUT SESSION #{i+1} - TRAIN HARD!", ln=1, align='C')
 
 
154
 
155
  # Save PDF
156
  timestamp = int(datetime.now().timestamp())
157
- pdf_path = f"gym_workout_{timestamp}.pdf"
158
  pdf.output(pdf_path)
159
 
160
  success_msg = f"""
161
- โœ… **GYM WORKOUT PLAN READY!**
162
- ๐Ÿ‹๏ธ **Title**: {clean_title[:30]}...
163
- ๐Ÿ’ช **{len(names_list)} Workouts** (Workout 1, Workout 2, etc.)
164
- ๐Ÿ“„ **Medium Sizes**: Photos 100ร—120px | Names 15pt
165
- ๐Ÿ“ **TEXT FIXED**: All input text displays correctly
166
- ๐Ÿ“‘ **{len(names_list) + 1} Pages** - Perfect gym layout!
 
 
167
  """
168
  return pdf_path, success_msg
169
 
170
  except Exception as e:
171
  return None, f"โŒ Error: {str(e)}"
172
 
173
- # ENHANCED GYM UI/UX
174
  with gr.Blocks(
175
  title="Gym Workout Plan Generator",
176
  theme=gr.themes.Soft(primary_hue="green"),
177
  css="""
178
- .gradio-container {max-width: 950px !important;}
179
  .header {background: linear-gradient(135deg, #16a34a, #15803d); color: white; padding: 25px; border-radius: 15px; text-align: center;}
180
  .input-box {background: #f0fdf4; padding: 18px; border-radius: 10px; border: 2px solid #dcfce7; margin: 10px 0;}
181
- .btn-generate {background: linear-gradient(45deg, #16a34a, #15803d) !important; font-size: 17px; padding: 12px;}
182
  .status {padding: 15px; border-radius: 10px; font-size: 15px; margin: 12px 0;}
183
  .success {background: #dcfce7; color: #166534; border-left: 5px solid #16a34a;}
184
- .gym-info {background: #ecfdf5; padding: 12px; border-radius: 8px; border-left: 4px solid #16a34a; margin: 8px 0;}
185
- .preview-text {font-family: monospace; background: #f3f4f6; padding: 10px; border-radius: 5px; font-size: 12px;}
186
  """
187
  ) as demo:
188
 
189
- # Enhanced Header
190
  gr.HTML("""
191
  <div class="header">
192
- <h2>๐Ÿ‹๏ธโ€โ™‚๏ธ Professional Gym Workout Generator</h2>
193
- <p><strong>Fixed Text Display โ€ข Workout 1, 2, 3... โ€ข Perfect Sizes</strong></p>
194
  </div>
195
  """)
196
 
197
  gr.Markdown("""
198
- ### ๐Ÿš€ Features:
199
- - **โœ… TEXT FIXED**: All your input text displays correctly in PDF
200
- - **๐Ÿ“ Workout Numbers**: "Workout 1", "Workout 2", etc. (not member #)
201
- - **๐Ÿ“ Medium Sizes**: Photos 100ร—120px | Names 15pt | Perfect fit
202
- - **๐Ÿ’ช Gym Template**: Workout details + trainer notes space
203
- - **๐ŸŽจ Green Theme**: Professional gym colors
 
204
  """)
205
 
206
- gr.HTML('<div class="gym-info">๐Ÿ’ก <strong>Input Text Preview:</strong> Your title and names will display exactly as entered (with cleaning for PDF compatibility).</div>')
207
 
208
- # Enhanced Inputs with Preview
209
  with gr.Row():
210
  with gr.Column(scale=2):
211
- title_input = gr.Textbox(
212
- label="๐Ÿ‹๏ธ Workout Plan Title *",
213
- placeholder="e.g., '6 Week Strength Program' or 'Beginner Muscle Building'",
214
  lines=2,
215
  elem_classes="input-box"
216
  )
217
 
218
- title_preview = gr.Markdown("", elem_classes="preview-text")
219
-
220
- names_input = gr.Textbox(
221
- label="๐Ÿ‘ฅ Member Names * (Comma separated)",
222
- placeholder="John Smith, Jane Doe, Mike Johnson, Sarah Wilson",
223
- lines=3,
224
  elem_classes="input-box"
225
  )
226
 
227
- names_preview = gr.Markdown("", elem_classes="preview-text")
228
 
229
  with gr.Column(scale=1):
230
  images_input = gr.File(
231
- label="๐Ÿ“ธ Member Photos *",
232
  file_count="multiple",
233
  file_types=[".jpg", ".jpeg", ".png", ".webp"],
234
- elem_classes="input-box"
 
235
  )
236
 
237
  # Controls
238
  with gr.Row():
239
- generate_btn = gr.Button("๐Ÿš€ Generate Gym Workout PDF", variant="primary", elem_classes="btn-generate")
240
  clear_btn = gr.Button("๐Ÿ—‘๏ธ Clear All", variant="secondary")
241
 
242
  # Outputs
243
- pdf_output = gr.File(label="๐Ÿ“ฅ Download Gym Workout PDF", elem_classes="input-box")
244
  status_output = gr.Markdown("Ready to generate workout plan...", elem_classes="status success")
245
 
246
- # Events with Text Preview
247
- def preview_title(title):
248
- clean = clean_text_for_pdf(title)
249
- return f"๐Ÿ“„ PDF Title: '{clean}'"
250
-
251
- def preview_names(names):
252
- names_list = [clean_text_for_pdf(n.strip()) for n in names.split(',') if n.strip()]
253
  if names_list:
254
- return f"๐Ÿ‘ฅ Names in PDF:\n" + " | ".join(names_list)
255
- return "๐Ÿ‘ฅ No names detected"
 
 
 
 
 
256
 
257
- title_input.change(
258
- fn=preview_title,
259
- inputs=[title_input],
260
- outputs=[title_preview]
261
- )
 
 
262
 
263
- names_input.change(
264
- fn=preview_names,
265
- inputs=[names_input],
266
- outputs=[names_preview]
 
267
  )
268
 
269
- def count_workouts(names):
270
- names_list = [n.strip() for n in names.split(',') if n.strip()]
271
- count = len(names_list)
272
- if count == 0:
273
- return "โŒ Add members (1-30)"
274
- elif count > 30:
275
- return f"โš ๏ธ {count} workouts - MAX 30!"
276
- return f"โœ… {count} workouts ready (Workout 1 to {count})"
277
-
278
  generate_btn.click(
279
- fn=generate_gym_workout_pdf,
280
- inputs=[title_input, names_input, images_input],
281
  outputs=[pdf_output, status_output]
282
  )
283
 
284
  clear_btn.click(
285
- fn=lambda: ("", None, "", None, None, "", "Form cleared!"),
286
- outputs=[title_input, title_preview, names_input, images_input, names_preview, pdf_output, status_output]
287
- )
288
-
289
- names_input.change(
290
- fn=count_workouts,
291
- inputs=[names_input],
292
- outputs=[status_output]
293
  )
294
 
295
  if __name__ == "__main__":
 
1
+ # app.py - Gym Workout Plan PDF (Title + Workout Names + Images Layout)
2
 
3
  import gradio as gr
4
  from fpdf import FPDF
 
10
  def footer(self):
11
  self.set_y(-10)
12
  self.set_font('Arial', 'I', 7)
13
+ self.cell(0, 8, f'GYM WORKOUT PLAN | Page {self.page_no()}', 0, 0, 'C')
14
 
15
+ def clean_text_for_pdf(text, max_length=45):
16
+ """Clean text while preserving workout names"""
17
  if not text:
18
+ return "Workout"
 
 
19
  text = str(text)
 
 
20
  replacements = {
21
  'โ€“': '-', 'โ€”': '-', 'โ€œ': '"', 'โ€': '"', 'โ€˜': "'", 'โ€™': "'",
22
+ 'โ€ข': 'โ€ข', 'ยฐ': 'ยฐ', 'ร—': 'x', 'โœ“': 'v', '\n': '\n'
 
23
  }
 
24
  for old, new in replacements.items():
25
  text = text.replace(old, new)
26
+ # Keep workout-related characters
27
+ text = re.sub(r'[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]', '', text)
28
+ text = re.sub(r'\s+', ' ', text.strip())
29
+ return text[:max_length]
 
30
 
31
+ def generate_workout_pdf(main_title, workout_names, image_files):
32
  # Validation
33
+ if not main_title.strip():
34
+ return None, "โŒ Main Title Required!"
35
 
36
+ names_list = [n.strip() for n in workout_names.split(',') if n.strip()]
37
 
38
  if not names_list:
39
+ return None, "โŒ Add Workout Names!"
40
 
41
  if len(names_list) > 30:
42
+ return None, "โŒ Max 30 Workouts!"
43
 
44
  if not image_files or len(names_list) != len(image_files):
45
+ return None, f"โŒ Need {len(names_list)} Workout Images!"
46
 
47
  try:
48
  pdf = GymWorkoutPDF()
49
  pdf.set_margins(18, 18, 18)
50
  pdf.set_auto_page_break(auto=True, margin=18)
51
 
52
+ clean_main_title = clean_text_for_pdf(main_title, 50)
53
+
54
+ # FIRST: MAIN TITLE PAGE
55
  pdf.add_page()
56
+ pdf.set_font('Arial', 'B', 20)
57
  pdf.set_text_color(0, 128, 0)
58
+ pdf.cell(0, 18, "๐Ÿ‹๏ธ GYM WORKOUT PROGRAM", ln=1, align='C')
59
 
60
+ pdf.set_font('Arial', 'B', 16)
61
+ pdf.cell(0, 15, clean_main_title.upper(), ln=1, align='C')
 
62
 
63
+ pdf.ln(15)
64
+ pdf.set_font('Arial', 'B', 12)
65
+ pdf.cell(0, 10, f"{'='*50}", ln=1, align='C')
66
+ pdf.set_font('Arial', '', 11)
67
+ pdf.cell(0, 10, f"๐Ÿ“‹ TOTAL WORKOUTS: {len(names_list)}", ln=1, align='C')
68
+ pdf.cell(0, 10, f"๐Ÿ“… CREATED: {datetime.now().strftime('%d/%m/%Y %H:%M')}", ln=1, align='C')
69
+ pdf.set_font('Arial', 'B', 12)
70
+ pdf.cell(0, 10, f"{'='*50}", ln=1, align='C')
71
 
72
+ # NEXT: INDIVIDUAL WORKOUT PAGES
73
+ for i, (workout_name, img_path) in enumerate(zip(names_list, image_files)):
74
  pdf.add_page()
75
 
76
+ # WORKOUT NUMBER + NAME HEADER
77
+ pdf.set_font('Arial', 'B', 16)
78
  pdf.set_text_color(0, 128, 0)
79
+ pdf.cell(0, 14, f"WORKOUT {i+1}", ln=1, align='C')
80
 
81
+ pdf.set_font('Arial', 'B', 14)
82
+ clean_workout_name = clean_text_for_pdf(workout_name, 40)
83
+ pdf.multi_cell(0, 12, clean_workout_name.upper(), align='C')
84
+
85
+ # IMAGE FOR WORKOUT (Centered)
86
+ pdf.ln(10)
87
+ img_width = 110
88
+ img_height = 130
89
  page_width = pdf.w - 36
90
  x_pos = (page_width - img_width) / 2 + 18
91
 
 
101
  pass
102
 
103
  if not image_success:
104
+ # Workout placeholder image
105
  pdf.set_fill_color(240, 248, 240)
106
  pdf.set_draw_color(0, 128, 0)
107
  pdf.rect(x_pos, pdf.get_y(), img_width, img_height, 'FD')
108
+ pdf.set_font('Arial', 'B', 11)
109
  pdf.set_text_color(0, 128, 0)
110
+ pdf.set_xy(x_pos + 25, pdf.get_y() + 50)
111
  pdf.cell(img_width - 50, 10, "WORKOUT", ln=1, align='C')
112
+ pdf.set_xy(x_pos + 25, pdf.get_y() + 70)
113
+ pdf.cell(img_width - 50, 10, f"IMAGE {i+1}", ln=1, align='C')
114
 
115
+ # WORKOUT DETAILS SECTION (Below Image)
116
  pdf.set_y(pdf.get_y() + img_height + 15)
117
+ pdf.set_font('Arial', 'B', 12)
 
 
 
 
 
 
 
118
  pdf.set_text_color(0, 128, 0)
119
+ pdf.cell(0, 10, "๐Ÿ’ช WORKOUT EXECUTION", ln=1, align='C')
120
 
121
  pdf.set_draw_color(0, 128, 0)
122
  pdf.line(18, pdf.get_y(), pdf.w - 18, pdf.get_y())
123
+ pdf.ln(8)
124
 
125
+ # Workout plan template
126
  pdf.set_font('Arial', '', 9)
127
  pdf.set_text_color(60, 60, 60)
128
 
129
+ details = [
130
+ f"๐Ÿ“‹ EXERCISES FOR {clean_workout_name.upper()}:",
131
+ "โšก SETS ร— REPS: _______________________________",
132
+ "โฑ๏ธ REST PERIODS: ____________________________",
133
+ "๐Ÿ”ฅ INTENSITY LEVEL: _________________________",
134
+ "๐Ÿ’ง HYDRATION BREAKS: ________________________",
135
+ "๐Ÿ“ˆ PROGRESS TRACKING: ______________________",
136
+ "๐Ÿ‘Ÿ EQUIPMENT NEEDED: _______________________"
137
  ]
138
 
139
+ for detail in details:
140
  pdf.ln(6)
141
+ pdf.cell(0, 8, detail, ln=1, align='L')
142
 
143
+ # Workout summary footer
144
+ pdf.ln(12)
145
+ pdf.set_font('Arial', 'B', 11)
146
  pdf.set_text_color(0, 128, 0)
147
+ pdf.cell(0, 10, f"WORKOUT {i+1}: {clean_workout_name.upper()}", ln=1, align='C')
148
+ pdf.set_font('Arial', '', 8)
149
+ pdf.cell(0, 8, "PRINT AND TRAIN HARD! ๐Ÿ’ช", ln=1, align='C')
150
 
151
  # Save PDF
152
  timestamp = int(datetime.now().timestamp())
153
+ pdf_path = f"workout_plan_{timestamp}.pdf"
154
  pdf.output(pdf_path)
155
 
156
  success_msg = f"""
157
+ โœ… **WORKOUT PLAN GENERATED!**
158
+ ๐Ÿ‹๏ธ **Main Title**: {clean_main_title[:30]}...
159
+ ๐Ÿ’ช **{len(names_list)} Workouts**:
160
+ โ€ข Workout 1: {clean_text_for_pdf(names_list[0])[:20]}...
161
+ โ€ข Workout {len(names_list)}: {clean_text_for_pdf(names_list[-1])[:20]}...
162
+ ๐Ÿ“„ **Layout**: Title Page โ†’ Workout Names + Images
163
+ ๐Ÿ“ **Perfect Sizes**: Images 110ร—130px | Names 14pt
164
+ ๐Ÿ“‘ **{len(names_list) + 1} Pages** - Ready for gym!
165
  """
166
  return pdf_path, success_msg
167
 
168
  except Exception as e:
169
  return None, f"โŒ Error: {str(e)}"
170
 
171
+ # WORKOUT-FOCUSED UI
172
  with gr.Blocks(
173
  title="Gym Workout Plan Generator",
174
  theme=gr.themes.Soft(primary_hue="green"),
175
  css="""
176
+ .gradio-container {max-width: 1000px !important;}
177
  .header {background: linear-gradient(135deg, #16a34a, #15803d); color: white; padding: 25px; border-radius: 15px; text-align: center;}
178
  .input-box {background: #f0fdf4; padding: 18px; border-radius: 10px; border: 2px solid #dcfce7; margin: 10px 0;}
179
+ .btn-generate {background: linear-gradient(45deg, #16a34a, #15803d) !important; font-size: 18px; padding: 12px;}
180
  .status {padding: 15px; border-radius: 10px; font-size: 15px; margin: 12px 0;}
181
  .success {background: #dcfce7; color: #166534; border-left: 5px solid #16a34a;}
182
+ .workout-info {background: #ecfdf5; padding: 12px; border-radius: 8px; border-left: 4px solid #16a34a;}
183
+ .preview-workouts {background: #f0fdf4; padding: 10px; border-radius: 6px; font-family: monospace;}
184
  """
185
  ) as demo:
186
 
187
+ # Header
188
  gr.HTML("""
189
  <div class="header">
190
+ <h2>๐Ÿ‹๏ธโ€โ™‚๏ธ Workout Plan Generator</h2>
191
+ <p><strong>Main Title โ†’ Workout Names โ†’ Images | Perfect Gym Layout</strong></p>
192
  </div>
193
  """)
194
 
195
  gr.Markdown("""
196
+ ### ๐Ÿ“‹ Structure:
197
+ 1. **FIRST**: Main Program Title Page
198
+ 2. **NEXT**: Individual Workout Pages
199
+ - Workout 1 Name + Image
200
+ - Workout 2 Name + Image
201
+ - Workout 3 Name + Image...
202
+ 3. **Each Page**: Workout name, exercise image, workout template
203
  """)
204
 
205
+ gr.HTML('<div class="workout-info">๐Ÿ’ก <strong>Workout Names Example:</strong> "Chest Day", "Legs & Core", "Full Body HIIT", "Arms & Shoulders"</div>')
206
 
207
+ # Inputs - Clear Structure
208
  with gr.Row():
209
  with gr.Column(scale=2):
210
+ main_title_input = gr.Textbox(
211
+ label="๐ŸŽฏ MAIN PROGRAM TITLE *",
212
+ placeholder="e.g., '12 Week Muscle Building Program' or 'Beginner Strength Cycle'",
213
  lines=2,
214
  elem_classes="input-box"
215
  )
216
 
217
+ workout_names_input = gr.Textbox(
218
+ label="๐Ÿ’ช WORKOUT NAMES * (Comma separated)",
219
+ placeholder="Chest Day, Leg Day, Back & Biceps, Cardio Burn, Full Body",
220
+ lines=4,
 
 
221
  elem_classes="input-box"
222
  )
223
 
224
+ workout_preview = gr.Markdown("", elem_classes="preview-workouts")
225
 
226
  with gr.Column(scale=1):
227
  images_input = gr.File(
228
+ label="๐Ÿ–ผ๏ธ Workout Images *",
229
  file_count="multiple",
230
  file_types=[".jpg", ".jpeg", ".png", ".webp"],
231
+ elem_classes="input-box",
232
+ info="Upload images for each workout (exercise demo photos)"
233
  )
234
 
235
  # Controls
236
  with gr.Row():
237
+ generate_btn = gr.Button("๐Ÿš€ Generate Workout Plan PDF", variant="primary", elem_classes="btn-generate")
238
  clear_btn = gr.Button("๐Ÿ—‘๏ธ Clear All", variant="secondary")
239
 
240
  # Outputs
241
+ pdf_output = gr.File(label="๐Ÿ“ฅ Download Workout Plan PDF", elem_classes="input-box")
242
  status_output = gr.Markdown("Ready to generate workout plan...", elem_classes="status success")
243
 
244
+ # Events
245
+ def preview_workouts(workout_names):
246
+ names_list = [clean_text_for_pdf(n.strip()) for n in workout_names.split(',') if n.strip()]
 
 
 
 
247
  if names_list:
248
+ preview = "๐Ÿ“‹ **Workouts in PDF:**\n"
249
+ for i, name in enumerate(names_list[:5], 1): # Show first 5
250
+ preview += f"โ€ข **Workout {i}**: {name}\n"
251
+ if len(names_list) > 5:
252
+ preview += f"... and {len(names_list)-5} more"
253
+ return preview
254
+ return "๐Ÿ“‹ No workouts detected"
255
 
256
+ def validate_inputs(main_title, workout_names, images):
257
+ if not main_title.strip():
258
+ return "โŒ Main title required!", gr.update(visible=False)
259
+ names_list = [n.strip() for n in workout_names.split(',') if n.strip()]
260
+ if len(names_list) != len(images or []):
261
+ return f"โŒ {len(names_list)} workouts need {len(names_list)} images!", gr.update(visible=False)
262
+ return "โœ… Ready to generate!", gr.update(visible=True)
263
 
264
+ main_title_input.change(fn=lambda x: x, inputs=[main_title_input], outputs=[status_output])
265
+ workout_names_input.change(
266
+ fn=preview_workouts,
267
+ inputs=[workout_names_input],
268
+ outputs=[workout_preview]
269
  )
270
 
 
 
 
 
 
 
 
 
 
271
  generate_btn.click(
272
+ fn=generate_workout_pdf,
273
+ inputs=[main_title_input, workout_names_input, images_input],
274
  outputs=[pdf_output, status_output]
275
  )
276
 
277
  clear_btn.click(
278
+ fn=lambda: ("", "", None, "", "Form cleared!"),
279
+ outputs=[main_title_input, workout_names_input, images_input, workout_preview, status_output]
 
 
 
 
 
 
280
  )
281
 
282
  if __name__ == "__main__":