lokesh341 commited on
Commit
471964f
ยท
verified ยท
1 Parent(s): 770f533

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +161 -118
app.py CHANGED
@@ -1,4 +1,4 @@
1
- # app.py - Gym Workout Plan PDF Generator (Medium Sizes, Perfect Fit)
2
 
3
  import gradio as gr
4
  from fpdf import FPDF
@@ -12,74 +12,83 @@ class GymWorkoutPDF(FPDF):
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(text, max_length=30):
16
- """Clean text for gym workout PDF"""
17
  if not text:
18
  return "Workout Plan"
 
 
 
 
 
19
  replacements = {
20
  'โ€“': '-', 'โ€”': '-', 'โ€œ': '"', 'โ€': '"', 'โ€˜': "'", 'โ€™': "'",
21
- 'โ€ข': '*', 'โ„ข': 'TM', 'ยฎ': 'R', 'ยฉ': 'C', '\n': ' '
 
22
  }
 
23
  for old, new in replacements.items():
24
- text = str(text).replace(old, new)
25
- text = re.sub(r'[^\w\s\-.,!?\(\)]', '', text)
26
- text = re.sub(r'\s+', ' ', text.strip())
27
- return text[:max_length]
 
 
28
 
29
- def generate_gym_pdf(title, names, image_files):
30
- # Validation for gym workout plan
31
  if not title.strip():
32
  return None, "โŒ Workout Plan Title Required!"
33
 
34
  names_list = [n.strip() for n in names.split(',') if n.strip()]
35
 
36
  if not names_list:
37
- return None, "โŒ Add Member Names!"
38
 
39
  if len(names_list) > 30:
40
- return None, "โŒ Max 30 Gym Members!"
41
 
42
  if not image_files or len(names_list) != len(image_files):
43
  return None, f"โŒ Need {len(names_list)} Member Photos!"
44
 
45
  try:
46
  pdf = GymWorkoutPDF()
47
- pdf.set_margins(18, 18, 18) # Slightly larger margins for clean look
48
  pdf.set_auto_page_break(auto=True, margin=18)
49
 
50
- clean_title = clean_text(title, 40)
51
-
52
- # GYM COVER PAGE (Compact)
53
  pdf.add_page()
54
- pdf.set_font('Arial', 'B', 16) # Medium title size
55
- pdf.set_text_color(0, 128, 0) # Gym green
56
- pdf.cell(0, 12, "WORKOUT PLAN", ln=1, align='C')
57
- pdf.set_font('Arial', 'B', 14)
58
- pdf.cell(0, 10, clean_title.upper(), ln=1, align='C')
59
 
60
- pdf.ln(8)
 
 
 
 
61
  pdf.set_font('Arial', 'B', 11)
62
- pdf.cell(0, 8, f"{'='*40}", ln=1, align='C')
63
  pdf.set_font('Arial', '', 10)
64
- pdf.cell(0, 8, f"MEMBERS: {len(names_list)}", ln=1, align='C')
65
- pdf.cell(0, 8, f"PLAN DATE: {datetime.now().strftime('%d/%m/%Y')}", ln=1, align='C')
66
  pdf.set_font('Arial', 'B', 11)
67
- pdf.cell(0, 8, f"{'='*40}", ln=1, align='C')
68
 
69
- # MEMBER PAGES (MEDIUM SIZES - FITS PERFECTLY)
70
  for i, (name, img_path) in enumerate(zip(names_list, image_files)):
71
  pdf.add_page()
72
 
73
- # MEMBER ID HEADER (Medium size)
74
- pdf.set_font('Arial', 'B', 12) # Medium ID size
75
- pdf.set_text_color(0, 128, 0) # Gym green
76
- pdf.cell(0, 10, f"MEMBER #{i+1}", ln=1, align='C')
77
 
78
- # PHOTO SECTION (MEDIUM SIZE - NOT TOO BIG)
79
  pdf.ln(8)
80
- img_width = 100 # Medium width - fits sheet perfectly
81
- img_height = 120 # Medium height - not oversized
82
- page_width = pdf.w - 36 # Account for margins
83
  x_pos = (page_width - img_width) / 2 + 18
84
 
85
  image_success = False
@@ -87,164 +96,198 @@ def generate_gym_pdf(title, names, image_files):
87
  try:
88
  pdf.image(img_path, x=x_pos, y=pdf.get_y(), w=img_width, h=img_height)
89
  image_success = True
90
-
91
- # Clean thin border
92
- pdf.set_draw_color(100, 150, 100) # Green border
93
- pdf.set_line_width(0.3)
94
  pdf.rect(x_pos, pdf.get_y(), img_width, img_height, 'D')
95
-
96
  except:
97
- image_success = False
98
 
99
  if not image_success:
100
- # Gym-style placeholder
101
- pdf.set_fill_color(240, 248, 240) # Light green background
102
- pdf.set_draw_color(100, 150, 100)
103
  pdf.rect(x_pos, pdf.get_y(), img_width, img_height, 'FD')
104
- pdf.set_font('Arial', 'B', 9)
105
- pdf.set_text_color(100, 150, 100)
106
- pdf.set_xy(x_pos + 20, pdf.get_y() + 45)
107
- pdf.cell(img_width - 40, 8, "MEMBER", ln=1, align='C')
108
- pdf.set_xy(x_pos + 20, pdf.get_y() + 60)
109
- pdf.cell(img_width - 40, 8, "PHOTO", ln=1, align='C')
110
 
111
- # NAME SECTION (MEDIUM SIZE - BELOW PHOTO)
112
- pdf.set_y(pdf.get_y() + img_height + 12) # Perfect spacing
113
- pdf.set_font('Arial', 'B', 16) # Medium name size - not too big
114
  pdf.set_text_color(0, 0, 0)
115
- clean_name = clean_text(name, 28)
116
- pdf.cell(0, 14, clean_name.upper(), ln=1, align='C')
117
 
118
- # GYM INFO (Small but clear)
119
- pdf.set_font('Arial', '', 9)
120
- pdf.set_text_color(80, 80, 80)
121
- pdf.cell(0, 8, f"Member ID: {i+1}/{len(names_list)}", ln=1, align='C')
122
- pdf.ln(5)
123
-
124
- # Workout plan section header
125
- pdf.set_font('Arial', 'B', 10)
126
  pdf.set_text_color(0, 128, 0)
127
- pdf.cell(0, 8, "WORKOUT PLAN", ln=1, align='C')
 
128
  pdf.set_draw_color(0, 128, 0)
129
- pdf.set_line_width(0.2)
130
  pdf.line(18, pdf.get_y(), pdf.w - 18, pdf.get_y())
131
-
132
- # Workout notes space (blank for gym staff to fill)
133
  pdf.ln(5)
134
- pdf.set_font('Arial', '', 8)
135
- pdf.set_text_color(120, 120, 120)
136
- pdf.cell(0, 6, "Trainer Notes: __________________________________________________", ln=1, align='C')
137
- pdf.cell(0, 6, "Goals: __________________________________________________________", ln=1, align='C')
138
- pdf.cell(0, 6, "Schedule: ______________________________________________________", ln=1, align='C')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
- # Save gym workout plan
141
  timestamp = int(datetime.now().timestamp())
142
- pdf_path = f"gym_workout_plan_{timestamp}.pdf"
143
  pdf.output(pdf_path)
144
 
145
  success_msg = f"""
146
- โœ… **GYM WORKOUT PLAN GENERATED!**
147
- ๐Ÿ’ช **Plan**: {clean_title}
148
- ๐Ÿ‘ฅ **{len(names_list)} Members** (IDs 1-{len(names_list)})
149
- ๐Ÿ“„ **Medium Sizes**: Photos 100ร—120px | Names 16pt
150
- ๐Ÿ‹๏ธ **Perfect Fit**: No oversized elements, clean layout
151
- ๐Ÿ“‘ **{len(names_list) + 1} Pages** - Ready for gym use!
152
  """
153
  return pdf_path, success_msg
154
 
155
  except Exception as e:
156
  return None, f"โŒ Error: {str(e)}"
157
 
158
- # GYM-SPECIFIC UI
159
  with gr.Blocks(
160
  title="Gym Workout Plan Generator",
161
  theme=gr.themes.Soft(primary_hue="green"),
162
  css="""
163
- .gradio-container {max-width: 900px !important;}
164
- .header {background: linear-gradient(135deg, #16a34a, #15803d); color: white; padding: 20px; border-radius: 12px; text-align: center;}
165
- .input-box {background: #f0fdf4; padding: 15px; border-radius: 8px; border: 2px solid #dcfce7; margin: 8px 0;}
166
- .btn-generate {background: linear-gradient(45deg, #16a34a, #15803d) !important; font-size: 16px;}
167
- .status {padding: 12px; border-radius: 8px; font-size: 14px; margin: 10px 0;}
168
- .success {background: #dcfce7; color: #166534; border-left: 4px solid #16a34a;}
169
- .gym-tip {background: #ecfdf5; padding: 10px; border-radius: 6px; border-left: 3px solid #16a34a;}
 
170
  """
171
  ) as demo:
172
 
173
- # Gym Header
174
  gr.HTML("""
175
  <div class="header">
176
- <h2>๐Ÿ‹๏ธ Gym Workout Plan Generator</h2>
177
- <p><strong>Medium Sizes โ€ข Perfect Sheet Fit โ€ข Professional Gym Plans</strong></p>
178
  </div>
179
  """)
180
 
181
  gr.Markdown("""
182
- ### ๐Ÿ’ช Gym Workout Features:
183
- - **Photos**: Medium 100ร—120px (fits perfectly on sheet)
184
- - **Names**: Medium 16pt (clear but not oversized)
185
- - **Layout**: Member photo + name + workout notes space
186
- - **Colors**: Professional gym green theme
187
- - **Space**: Blank areas for trainer notes & schedules
188
  """)
189
 
190
- gr.HTML('<div class="gym-tip">๐Ÿ’ก <strong>Gym Tip:</strong> Upload member photos and names. PDF includes space for workout plans & trainer notes.</div>')
191
 
192
- # Gym Inputs
193
  with gr.Row():
194
  with gr.Column(scale=2):
195
  title_input = gr.Textbox(
196
  label="๐Ÿ‹๏ธ Workout Plan Title *",
197
- placeholder="e.g., 'January Strength Program' or 'Beginner Fitness Plan'",
198
- lines=1,
199
  elem_classes="input-box"
200
  )
201
 
 
 
202
  names_input = gr.Textbox(
203
- label="๐Ÿ‘ฅ Gym Member Names *",
204
- placeholder="John Smith, Jane Doe, Mike Johnson (1-30 members)",
205
- lines=2,
206
  elem_classes="input-box"
207
  )
 
 
208
 
209
  with gr.Column(scale=1):
210
  images_input = gr.File(
211
  label="๐Ÿ“ธ Member Photos *",
212
  file_count="multiple",
213
- file_types=[".jpg", ".jpeg", ".png"],
214
  elem_classes="input-box"
215
  )
216
 
217
  # Controls
218
- generate_btn = gr.Button("๐Ÿš€ Generate Gym Workout Plan", variant="primary", elem_classes="btn-generate")
219
- clear_btn = gr.Button("๐Ÿ—‘๏ธ Clear", variant="secondary")
 
220
 
221
- # Output
222
  pdf_output = gr.File(label="๐Ÿ“ฅ Download Gym Workout PDF", elem_classes="input-box")
223
- status_output = gr.Markdown("Ready to generate gym workout plan...", elem_classes="status success")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
 
225
- # Events
226
- def count_members(names):
227
  names_list = [n.strip() for n in names.split(',') if n.strip()]
228
  count = len(names_list)
229
  if count == 0:
230
- return "โŒ Add gym members (1-30)"
231
  elif count > 30:
232
- return f"โš ๏ธ {count} members - MAX 30 for gym!"
233
- return f"โœ… {count} gym members ready (perfect medium layout)"
234
 
235
  generate_btn.click(
236
- fn=generate_gym_pdf,
237
  inputs=[title_input, names_input, images_input],
238
  outputs=[pdf_output, status_output]
239
  )
240
 
241
  clear_btn.click(
242
- fn=lambda: ("", "", None, "Gym form cleared!"),
243
- outputs=[title_input, names_input, images_input, status_output]
244
  )
245
 
246
  names_input.change(
247
- fn=count_members,
248
  inputs=[names_input],
249
  outputs=[status_output]
250
  )
 
1
+ # app.py - Gym Workout Plan PDF (Fixed Text Display + Workout Numbers)
2
 
3
  import gradio as gr
4
  from fpdf import FPDF
 
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
 
94
  image_success = False
 
96
  try:
97
  pdf.image(img_path, x=x_pos, y=pdf.get_y(), w=img_width, h=img_height)
98
  image_success = True
99
+ pdf.set_draw_color(0, 128, 0)
100
+ pdf.set_line_width(0.5)
 
 
101
  pdf.rect(x_pos, pdf.get_y(), img_width, img_height, 'D')
 
102
  except:
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
  )