mroccuper commited on
Commit
b455c9b
·
verified ·
1 Parent(s): 834e130

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +240 -177
app.py CHANGED
@@ -2,65 +2,65 @@ import gradio as gr
2
  import google.generativeai as genai
3
  import re
4
  import json
 
 
 
 
 
 
 
5
 
6
  def count_characters(text):
7
  """Count characters in text."""
8
  return len(text) if text else 0
9
 
10
  def format_output(title, brand_name, bullet1, bullet2, suggested_keywords=None):
11
- """Format the output with character counts."""
12
- output = f"Title ({count_characters(title)}/60 characters):\n{title}\n\n"
13
- output += f"Brand Name ({count_characters(brand_name)}/50 characters):\n{brand_name}\n\n"
14
- output += f"Bullet Point 1 ({count_characters(bullet1)}/256 characters):\n{bullet1}\n\n"
15
- output += f"Bullet Point 2 ({count_characters(bullet2)}/256 characters):\n{bullet2}"
16
-
 
17
  if suggested_keywords:
18
  output += f"\n\nSuggested Additional Keywords:\n{suggested_keywords}"
19
-
20
  return output
21
 
22
  def generate_prompt(quote, niche, target, keywords):
23
  """Generate the prompt for Gemini API."""
 
24
  combined_prompt = f"""You are an Amazon Merch on Demand SEO expert specializing in creating optimized t-shirt and apparel listings.
25
-
26
  MY INPUT IS ABOUT: A {niche} t-shirt with the design/quote: "{quote}" for {target}.
27
  YOU MUST ONLY create an Amazon apparel listing about that EXACT input - no substitutions or different themes allowed.
28
-
29
  Generate a listing that includes:
30
-
31
- 1. Title (exactly 60 characters): Must include "{niche}" and reference the design/quote "{quote}" and target audience "{target}"
32
- 2. Brand Name (34-50 characters): Create a fitting brand name for this specific {niche} apparel for {target}
33
- 3. Bullet Point 1 (240-256 characters): Highlight key features using ALL CAPS for the first 2-3 words. Focus ONLY on the design, quote, and niche theme.
34
- 4. Bullet Point 2 (240-256 characters): Highlight additional features using ALL CAPS for the first 2-3 words. Focus ONLY on the design, quote, and niche theme.
35
-
36
  IMPORTANT RULES — STRICT ENFORCEMENT:
37
  - DO NOT include generic phrases like "PREMIUM QUALITY" or references to material quality
38
  - DO NOT include phrases like "This comfortable and stylish tee is made with high-quality materials for a soft feel and long-lasting wear"
39
- - Bullet point 1 must be between 240 and 256 characters. Not less than 240. Not more than 256.
40
- - Bullet point 2 must be between 240 and 256 characters. Not less than 240. Not more than 256.
41
- - DO NOT exceed the character limit under any circumstances.
42
  - Count characters carefully. Ensure compliance before outputting.
43
  - Focus ONLY on the specific design, niche, and quote provided
44
  - Every sentence must directly relate to the quote, niche theme, and target audience
45
  - Do not include any content that strays from the specific theme provided
46
-
47
-
48
  The listing should be specifically for t-shirts, hoodies, or sweaters for the Amazon Merch on Demand program.
49
  The listing MUST be about: {niche} + {quote} + for {target}. Do not generate content about other holidays, quotes, or audiences.
50
-
51
  Use these specific keywords in your listing: {keywords}
52
-
53
  Respond ONLY with a JSON object in this format:
54
  {{
55
- "title": "The title with exactly 60 characters",
56
- "brand_name": "Brand name between 34-50 characters",
57
- "bullet_point_1": "First bullet point between 240-256 characters that focuses on the design and theme",
58
- "bullet_point_2": "Second bullet point between 240-256 characters that focuses on the design and theme",
59
  "suggested_keywords": "5 additional keywords separated by commas"
60
  }}
61
-
62
- REMINDER: Make sure to count the characters carefully. Title should be EXACTLY 60 characters. Bullet point 1 must be between 240-256 characters and Bullet point 2 must be between 240-256 characters.
63
- STRICT ENFORCEMENT: Bullet point 1 and 2 MUST be between 240 and 256 characters. Could be less, not more. If it's 257+, the result is invalid. Carefully count and ensure compliance.
64
  """
65
 
66
  return combined_prompt
@@ -68,229 +68,292 @@ STRICT ENFORCEMENT: Bullet point 1 and 2 MUST be between 240 and 256 characters.
68
  def generate_multiple_variations_prompt(quote, niche, target, keywords):
69
  """Generate the prompt for multiple variations of title and brand name."""
70
  combined_prompt = f"""You are an Amazon Merch on Demand SEO expert specializing in creating optimized t-shirt and apparel listings.
71
-
72
  MY INPUT IS ABOUT: A {niche} t-shirt with the design/quote: "{quote}" for {target}.
73
  YOU MUST ONLY create variations about that EXACT input - no substitutions or different themes allowed.
74
-
75
  Generate 3 different variations for the Title and Brand Name based on the provided information.
76
  All variations MUST be about {niche} + "{quote}" + for {target} audience.
77
-
78
  The titles MUST include:
79
  - The specific holiday/event: {niche}
80
  - Reference to the quote/design: "{quote}"
81
  - The target audience: {target}
82
-
83
  Focus only on t-shirts, sweaters, and hoodies for the Amazon Merch on Demand program.
84
- All titles must be exactly 60 characters and brand names between 34-50 characters.
85
-
86
  Respond ONLY with a JSON object in this format:
87
  {{
88
  "title_variations": [
89
- "Title variation 1 - exactly 60 characters, count carefully",
90
- "Title variation 2 - exactly 60 characters, count carefully",
91
- "Title variation 3 - exactly 60 characters, count carefully"
92
  ],
93
  "brand_name_variations": [
94
- "Brand name variation 1 (34-50 characters)",
95
- "Brand name variation 2 (34-50 characters)",
96
- "Brand name variation 3 (34-50 characters)"
97
  ]
98
  }}
99
-
100
- REMINDER: Make sure each title is EXACTLY 60 characters. Count carefully!"""
101
 
102
  return combined_prompt
103
 
104
  def generate_amazon_listing(api_key, quote, niche, target, keywords):
105
- """Generate Amazon listing using Gemini API."""
106
  # Input validation
107
  if not api_key:
108
  return "Error: Please enter a valid Gemini API key"
109
  if not quote or not niche or not target:
110
  return "Error: Please fill in all required fields (Quote, Holiday/Event, and Target Audience)"
111
-
112
  try:
113
  # Configure the Gemini API with the provided key
 
 
114
  genai.configure(api_key=api_key)
115
-
116
  # Create model with optimized settings
117
  model = genai.GenerativeModel(
118
- 'gemini-1.5-pro',
119
  generation_config={
120
- "temperature": 0.3,
121
  "top_p": 0.8,
122
- "max_output_tokens": 1024, # Reduced for faster response
 
 
123
  }
124
  )
125
-
126
  # Generate the main listing
127
  prompt = generate_prompt(quote, niche, target, keywords)
128
-
129
  try:
130
- # First try to get just the main listing for faster response
131
  response = model.generate_content(prompt)
132
-
133
- # Extract JSON from the response
134
- response_text = response.text
135
- match = re.search(r'{.*}', response_text, re.DOTALL)
136
- if not match:
137
- return "Error: Could not extract JSON from Gemini API response. Please try again."
138
-
139
- json_str = match.group(0)
140
  try:
 
 
 
 
 
 
 
 
 
 
141
  result = json.loads(json_str)
142
- except json.JSONDecodeError:
143
- return "Error parsing JSON response from Gemini API. Please try again."
144
-
145
- # Validate that the output actually matches the input criteria
146
- title = result.get("title", "")
147
- if not (quote.lower() in title.lower() or
148
- niche.lower() in title.lower() or
149
- any(t.lower() in title.lower() for t in target.lower().split(','))):
150
- return f"Error: Generated title doesn't match the requested theme: '{quote}', '{niche}', or '{target}'. Please try again."
151
-
152
- # Validate bullet point lengths
153
- bullet1 = result.get("bullet_point_1", "")[:256]
154
- bullet2 = result.get("bullet_point_2", "")[:256]
155
-
156
-
157
- #if len(bullet1) < 240 or len(bullet1) > 256:
158
- #return f"Error: Bullet point 1 length ({len(bullet1)}) is not between 240-256 characters. Please try again."
159
-
160
- #if len(bullet2) < 240 or len(bullet2) > 256:
161
- #return f"Error: Bullet point 2 length ({len(bullet2)}) is not between 240-256 characters. Please try again."
162
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  # Check for generic content in bullet points
164
- generic_phrases = ["premium quality", "high-quality materials", "soft feel", "long-lasting wear",
165
  "comfortable and stylish"]
166
-
 
167
  for phrase in generic_phrases:
168
- if phrase in bullet1.lower() or phrase in bullet2.lower():
169
- return f"Error: Generated bullet points contain generic phrase '{phrase}'. Please try again."
170
-
171
- # Format main output first - so we have something to show quickly
172
  main_output = format_output(
173
- result.get("title", "Error generating title"),
174
- result.get("brand_name", "Error generating brand name"),
175
- result.get("bullet_point_1", "Error generating bullet point 1"),
176
- result.get("bullet_point_2", "Error generating bullet point 2"),
177
- result.get("suggested_keywords", "Error generating suggested keywords")
178
  )
179
-
180
- # Now try to get variations in a separate call
181
  try:
182
  variations_prompt = generate_multiple_variations_prompt(quote, niche, target, keywords)
 
 
183
  response_var = model.generate_content(
184
  variations_prompt,
185
- generation_config={
186
- "temperature": 0.4,
187
  "top_p": 0.8,
188
- "max_output_tokens": 1024
 
189
  }
190
  )
191
-
192
- # Extract JSON from the variations response
193
- response_var_text = response_var.text
194
- match_var = re.search(r'{.*}', response_var_text, re.DOTALL)
195
- if match_var:
196
- json_str_var = match_var.group(0)
197
- try:
198
- variations = json.loads(json_str_var)
199
-
200
- # Format variations output
201
- variations_output = "\n\nADDITIONAL VARIATIONS:\n\n"
202
- variations_output += "Title Variations:\n"
203
- for i, var in enumerate(variations.get("title_variations", []), 1):
204
- variations_output += f"{i}. {var} ({count_characters(var)}/60 characters)\n"
205
-
206
- variations_output += "\nBrand Name Variations:\n"
207
- for i, var in enumerate(variations.get("brand_name_variations", []), 1):
208
- variations_output += f"{i}. {var} ({count_characters(var)}/50 characters)\n"
209
-
210
- # Combine main output with variations
211
- return main_output + variations_output
212
-
213
- except json.JSONDecodeError:
214
- # Return just the main output if we can't parse variations
215
- return main_output + "\n\n(Could not generate variations)"
216
- else:
217
- # Return just the main output if we can't extract JSON for variations
218
- return main_output + "\n\n(Could not generate variations)"
219
-
 
 
 
 
 
 
 
 
 
220
  except Exception as var_error:
221
- # Return just the main output if variations fail
222
- return main_output + f"\n\n(Could not generate variations: {str(var_error)})"
223
-
224
- except genai.types.generation_types.BlockedPromptException as e:
225
- return f"Error: The prompt was blocked by Gemini API safety filters. Please modify your input and try again."
226
-
 
227
  except Exception as e:
228
- return f"Error generating main listing: {str(e)}"
229
-
 
 
 
 
 
230
  except Exception as e:
231
- return f"Error: {str(e)}"
 
 
232
 
233
- # Create the Gradio interface
234
  def create_interface():
235
- with gr.Blocks(title="Amazon Merch on Demand Listing Generator") as app:
236
  gr.Markdown("# Amazon Merch on Demand Listing Generator")
237
- gr.Markdown("Generate SEO-optimized t-shirt and apparel listings for Amazon Merch on Demand using Gemini 1.5 Pro AI.")
238
-
239
  with gr.Row():
240
- with gr.Column():
241
- api_key = gr.Textbox(label="Gemini API Key", placeholder="Enter your Gemini API key", type="password")
242
- quote = gr.Textbox(label="Quote/Design/Idea", placeholder="Enter the quote or design idea", value="")
243
- niche = gr.Textbox(label="Holiday/Event", placeholder="Enter the holiday or event (e.g., St Patrick's Day)", value="")
244
- target = gr.Textbox(label="Target Audience", placeholder="Teacher, Mom, Dad, etc.", value="")
245
- keywords = gr.Textbox(label="Target Keywords", placeholder="Enter keywords separated by commas", lines=5, value="")
 
 
 
 
 
 
 
 
 
 
 
246
  submit_btn = gr.Button("Generate Amazon Listing", variant="primary")
247
-
248
- with gr.Column():
249
- status = gr.Textbox(label="Status", value="Ready to generate listing", interactive=False)
250
- output = gr.Textbox(label="Generated Amazon Listing", lines=25)
251
-
252
- def on_submit(api_key, quote, niche, target, keywords):
253
- # Update status first
254
- yield "Generating listing... Please wait.", output.value
255
-
 
 
 
 
 
 
 
 
 
256
  # Generate the listing
257
- result = generate_amazon_listing(api_key, quote, niche, target, keywords)
258
-
259
- # Update status with completion message
260
- if "Error" in result:
261
- yield "Error occurred. See details below.", result
262
  else:
263
  yield "Listing generated successfully!", result
264
-
265
  submit_btn.click(
266
  fn=on_submit,
267
  inputs=[api_key, quote, niche, target, keywords],
268
  outputs=[status, output],
269
- show_progress="minimal"
270
  )
271
-
272
  gr.Markdown("## Example Input")
273
  gr.Markdown('''
274
- ```
275
- Quote/Design: Rainbow with a quote "Lucky To Be A Teacher"
276
- Holiday: St Patricks Day
277
- Target: Teacher, Teacher Mom
278
- Keywords: lucky, teacher, rainbow, st, patricks, day, t-shirt, patrick's, outfit, design, leopard, cheetah, print, shamrock, clover, perfect, men, women, teachers, celebrate, saint, patrick, special, unique, makes, great, gifts, idea, substitute, love, irish, culture, pattys, holiday, teach, shamrocks, cute, design, awesome, show, students
279
- ```
280
  ''')
281
-
282
  gr.Markdown("""
283
- ## Troubleshooting Tips
284
- - If you experience timeouts, try using shorter, more specific inputs
285
- - Make sure your Gemini API key is valid and has sufficient quota
286
- - The app will prioritize showing the main listing first, then try to generate variations
 
 
287
  """)
288
-
289
  return app
290
 
291
- # Create and launch the app
292
  app = create_interface()
293
 
294
- # For deployment on Hugging Face Spaces
295
  if __name__ == "__main__":
296
- app.launch()
 
 
 
2
  import google.generativeai as genai
3
  import re
4
  import json
5
+ import os # Good practice to import os if using env variables later
6
+
7
+ # --- Constants for Limits ---
8
+ TITLE_MAX_LEN = 60
9
+ BRAND_NAME_MAX_LEN = 50
10
+ BULLET_POINT_MAX_LEN = 256
11
+ BULLET_POINT_MIN_LEN = 240 # Keep this if you want to check minimum length later
12
 
13
  def count_characters(text):
14
  """Count characters in text."""
15
  return len(text) if text else 0
16
 
17
  def format_output(title, brand_name, bullet1, bullet2, suggested_keywords=None):
18
+ """Format the output with character counts (reflecting potentially truncated lengths)."""
19
+ # Display the actual length after potential truncation
20
+ output = f"Title ({count_characters(title)}/{TITLE_MAX_LEN} characters):\n{title}\n\n"
21
+ output += f"Brand Name ({count_characters(brand_name)}/{BRAND_NAME_MAX_LEN} characters):\n{brand_name}\n\n"
22
+ output += f"Bullet Point 1 ({count_characters(bullet1)}/{BULLET_POINT_MAX_LEN} characters):\n{bullet1}\n\n"
23
+ output += f"Bullet Point 2 ({count_characters(bullet2)}/{BULLET_POINT_MAX_LEN} characters):\n{bullet2}"
24
+
25
  if suggested_keywords:
26
  output += f"\n\nSuggested Additional Keywords:\n{suggested_keywords}"
27
+
28
  return output
29
 
30
  def generate_prompt(quote, niche, target, keywords):
31
  """Generate the prompt for Gemini API."""
32
+ # Keep the detailed prompt instructions as they help guide the AI
33
  combined_prompt = f"""You are an Amazon Merch on Demand SEO expert specializing in creating optimized t-shirt and apparel listings.
 
34
  MY INPUT IS ABOUT: A {niche} t-shirt with the design/quote: "{quote}" for {target}.
35
  YOU MUST ONLY create an Amazon apparel listing about that EXACT input - no substitutions or different themes allowed.
 
36
  Generate a listing that includes:
37
+ 1. Title (try for exactly {TITLE_MAX_LEN} characters): Must include "{niche}" and reference the design/quote "{quote}" and target audience "{target}"
38
+ 2. Brand Name (try for 34-{BRAND_NAME_MAX_LEN} characters): Create a fitting brand name for this specific {niche} apparel for {target}
39
+ 3. Bullet Point 1 (try for {BULLET_POINT_MIN_LEN}-{BULLET_POINT_MAX_LEN} characters): Highlight key features using ALL CAPS for the first 2-3 words. Focus ONLY on the design, quote, and niche theme.
40
+ 4. Bullet Point 2 (try for {BULLET_POINT_MIN_LEN}-{BULLET_POINT_MAX_LEN} characters): Highlight additional features using ALL CAPS for the first 2-3 words. Focus ONLY on the design, quote, and niche theme.
 
 
41
  IMPORTANT RULES — STRICT ENFORCEMENT:
42
  - DO NOT include generic phrases like "PREMIUM QUALITY" or references to material quality
43
  - DO NOT include phrases like "This comfortable and stylish tee is made with high-quality materials for a soft feel and long-lasting wear"
44
+ - Bullet point 1 must be between {BULLET_POINT_MIN_LEN} and {BULLET_POINT_MAX_LEN} characters. Aim for the higher end.
45
+ - Bullet point 2 must be between {BULLET_POINT_MIN_LEN} and {BULLET_POINT_MAX_LEN} characters. Aim for the higher end.
46
+ - DO NOT exceed the character limits ({TITLE_MAX_LEN} for title, {BRAND_NAME_MAX_LEN} for brand, {BULLET_POINT_MAX_LEN} for bullets).
47
  - Count characters carefully. Ensure compliance before outputting.
48
  - Focus ONLY on the specific design, niche, and quote provided
49
  - Every sentence must directly relate to the quote, niche theme, and target audience
50
  - Do not include any content that strays from the specific theme provided
 
 
51
  The listing should be specifically for t-shirts, hoodies, or sweaters for the Amazon Merch on Demand program.
52
  The listing MUST be about: {niche} + {quote} + for {target}. Do not generate content about other holidays, quotes, or audiences.
 
53
  Use these specific keywords in your listing: {keywords}
 
54
  Respond ONLY with a JSON object in this format:
55
  {{
56
+ "title": "The title aiming for {TITLE_MAX_LEN} characters",
57
+ "brand_name": "Brand name aiming for 34-{BRAND_NAME_MAX_LEN} characters",
58
+ "bullet_point_1": "First bullet point aiming for {BULLET_POINT_MIN_LEN}-{BULLET_POINT_MAX_LEN} characters",
59
+ "bullet_point_2": "Second bullet point aiming for {BULLET_POINT_MIN_LEN}-{BULLET_POINT_MAX_LEN} characters",
60
  "suggested_keywords": "5 additional keywords separated by commas"
61
  }}
62
+ REMINDER: Make sure to count the characters carefully. Aim for Title exactly {TITLE_MAX_LEN} characters. Aim for Bullet points 1 and 2 between {BULLET_POINT_MIN_LEN}-{BULLET_POINT_MAX_LEN} characters.
63
+ STRICT ENFORCEMENT: Bullet point 1 and 2 MUST be between {BULLET_POINT_MIN_LEN} and {BULLET_POINT_MAX_LEN} characters. If it's {BULLET_POINT_MAX_LEN+1}+, the result is invalid. Carefully count and ensure compliance.
 
64
  """
65
 
66
  return combined_prompt
 
68
  def generate_multiple_variations_prompt(quote, niche, target, keywords):
69
  """Generate the prompt for multiple variations of title and brand name."""
70
  combined_prompt = f"""You are an Amazon Merch on Demand SEO expert specializing in creating optimized t-shirt and apparel listings.
 
71
  MY INPUT IS ABOUT: A {niche} t-shirt with the design/quote: "{quote}" for {target}.
72
  YOU MUST ONLY create variations about that EXACT input - no substitutions or different themes allowed.
 
73
  Generate 3 different variations for the Title and Brand Name based on the provided information.
74
  All variations MUST be about {niche} + "{quote}" + for {target} audience.
 
75
  The titles MUST include:
76
  - The specific holiday/event: {niche}
77
  - Reference to the quote/design: "{quote}"
78
  - The target audience: {target}
 
79
  Focus only on t-shirts, sweaters, and hoodies for the Amazon Merch on Demand program.
80
+ All titles must aim for exactly {TITLE_MAX_LEN} characters and brand names between 34-{BRAND_NAME_MAX_LEN} characters.
 
81
  Respond ONLY with a JSON object in this format:
82
  {{
83
  "title_variations": [
84
+ "Title variation 1 - aim for {TITLE_MAX_LEN} characters, count carefully",
85
+ "Title variation 2 - aim for {TITLE_MAX_LEN} characters, count carefully",
86
+ "Title variation 3 - aim for {TITLE_MAX_LEN} characters, count carefully"
87
  ],
88
  "brand_name_variations": [
89
+ "Brand name variation 1 (aim for 34-{BRAND_NAME_MAX_LEN} characters)",
90
+ "Brand name variation 2 (aim for 34-{BRAND_NAME_MAX_LEN} characters)",
91
+ "Brand name variation 3 (aim for 34-{BRAND_NAME_MAX_LEN} characters)"
92
  ]
93
  }}
94
+ REMINDER: Make sure each title aims for EXACTLY {TITLE_MAX_LEN} characters. Count carefully!"""
 
95
 
96
  return combined_prompt
97
 
98
  def generate_amazon_listing(api_key, quote, niche, target, keywords):
99
+ """Generate Amazon listing using Gemini API, enforcing character limits."""
100
  # Input validation
101
  if not api_key:
102
  return "Error: Please enter a valid Gemini API key"
103
  if not quote or not niche or not target:
104
  return "Error: Please fill in all required fields (Quote, Holiday/Event, and Target Audience)"
105
+
106
  try:
107
  # Configure the Gemini API with the provided key
108
+ # Consider using environment variables for production:
109
+ # api_key = os.getenv("GEMINI_API_KEY") or api_key_input
110
  genai.configure(api_key=api_key)
111
+
112
  # Create model with optimized settings
113
  model = genai.GenerativeModel(
114
+ 'gemini-1.5-pro', # Or 'gemini-1.5-flash' for potentially faster/cheaper generation
115
  generation_config={
116
+ "temperature": 0.3, # Lower temperature for more predictable output
117
  "top_p": 0.8,
118
+ "max_output_tokens": 1024, # Maximum tokens the API can *return*
119
+ # Note: 'max_output_tokens' doesn't directly control character count precisely
120
+ "response_mime_type": "application/json" # Request JSON directly
121
  }
122
  )
123
+
124
  # Generate the main listing
125
  prompt = generate_prompt(quote, niche, target, keywords)
126
+
127
  try:
128
+ # --- Generate Main Listing Content ---
129
  response = model.generate_content(prompt)
130
+
131
+ # Since we requested JSON, parse it directly. Add error handling.
 
 
 
 
 
 
132
  try:
133
+ # Access the text part and load as JSON
134
+ response_text = response.text
135
+ # Sometimes the API might still wrap it in markdown, try to strip it
136
+ json_match = re.search(r'```json\s*({.*?})\s*```', response_text, re.DOTALL | re.IGNORECASE)
137
+ if json_match:
138
+ json_str = json_match.group(1)
139
+ else:
140
+ # Fallback if no markdown backticks are found
141
+ json_str = response_text.strip()
142
+
143
  result = json.loads(json_str)
144
+
145
+ except (json.JSONDecodeError, AttributeError, IndexError) as json_err:
146
+ # Handle cases where response.text is empty, not valid JSON, or API returned unexpected format
147
+ print(f"JSON Parsing Error: {json_err}")
148
+ print(f"Raw response text: {getattr(response, 'text', 'N/A')}") # Log raw response for debugging
149
+ # Check for safety blocks
150
+ if response.prompt_feedback.block_reason:
151
+ return f"Error: Generation blocked due to: {response.prompt_feedback.block_reason}. Content filters may have been triggered."
152
+ return "Error: Could not parse JSON response from Gemini API. The API might have returned an unexpected format or an empty response. Please try again."
153
+
154
+
155
+ # --- ENFORCE CHARACTER LIMITS ---
156
+ # Get raw values and immediately truncate if they exceed the MAX limit
157
+ title = result.get("title", "")[:TITLE_MAX_LEN]
158
+ brand_name = result.get("brand_name", "")[:BRAND_NAME_MAX_LEN]
159
+ bullet1 = result.get("bullet_point_1", "")[:BULLET_POINT_MAX_LEN]
160
+ bullet2 = result.get("bullet_point_2", "")[:BULLET_POINT_MAX_LEN]
161
+ suggested_keywords = result.get("suggested_keywords", "Error generating suggested keywords") # Keywords don't usually need truncation
162
+
163
+ # --- VALIDATION (using potentially truncated values) ---
164
+
165
+ # Validate that the output actually matches the input criteria (optional but good)
166
+ # Check if *any* part of the target audience is in the title if it's comma-separated
167
+ target_parts = [t.strip().lower() for t in target.split(',')]
168
+ title_lower = title.lower()
169
+ if not (quote.lower() in title_lower or
170
+ niche.lower() in title_lower or
171
+ any(t_part in title_lower for t_part in target_parts)):
172
+ return f"Error: Generated title ('{title}') doesn't seem to strongly match the requested theme: '{quote}', '{niche}', or '{target}'. Please try again or adjust input."
173
+
174
+ # --- Optional: Validate the *lower* bound for bullet points ---
175
+ # Uncomment these lines if you strictly need the bullets to be AT LEAST min_len characters
176
+ # Note: This check happens *after* truncation, so if truncation occurred, it might pass this check.
177
+ # if len(bullet1) < BULLET_POINT_MIN_LEN:
178
+ # return f"Error: Bullet point 1 length ({len(bullet1)}) is less than the required minimum {BULLET_POINT_MIN_LEN} characters after generation/truncation. Please try again."
179
+ # if len(bullet2) < BULLET_POINT_MIN_LEN:
180
+ # return f"Error: Bullet point 2 length ({len(bullet2)}) is less than the required minimum {BULLET_POINT_MIN_LEN} characters after generation/truncation. Please try again."
181
+
182
  # Check for generic content in bullet points
183
+ generic_phrases = ["premium quality", "high-quality materials", "soft feel", "long-lasting wear",
184
  "comfortable and stylish"]
185
+ bullet1_lower = bullet1.lower()
186
+ bullet2_lower = bullet2.lower()
187
  for phrase in generic_phrases:
188
+ if phrase in bullet1_lower or phrase in bullet2_lower:
189
+ return f"Error: Generated bullet points contain disallowed generic phrase '{phrase}'. Please try again."
190
+
191
+ # Format main output first - using the enforced length values
192
  main_output = format_output(
193
+ title,
194
+ brand_name,
195
+ bullet1,
196
+ bullet2,
197
+ suggested_keywords
198
  )
199
+
200
+ # --- Generate Variations (Optional Second Call) ---
201
  try:
202
  variations_prompt = generate_multiple_variations_prompt(quote, niche, target, keywords)
203
+ # Use a separate model instance or reuse if configuration is the same
204
+ # Using the same model instance here
205
  response_var = model.generate_content(
206
  variations_prompt,
207
+ generation_config={ # Can reuse or adjust config for variations
208
+ "temperature": 0.4, # Slightly higher temp for more variety
209
  "top_p": 0.8,
210
+ "max_output_tokens": 1024,
211
+ "response_mime_type": "application/json"
212
  }
213
  )
214
+
215
+ # Parse variations JSON
216
+ try:
217
+ response_var_text = response_var.text
218
+ # Try stripping markdown again
219
+ json_match_var = re.search(r'```json\s*({.*?})\s*```', response_var_text, re.DOTALL | re.IGNORECASE)
220
+ if json_match_var:
221
+ json_str_var = json_match_var.group(1)
222
+ else:
223
+ json_str_var = response_var_text.strip()
224
+
225
+ variations = json.loads(json_str_var)
226
+
227
+ # Format variations output, enforcing limits here too
228
+ variations_output = "\n\nADDITIONAL VARIATIONS:\n\n"
229
+ variations_output += "Title Variations:\n"
230
+ for i, var in enumerate(variations.get("title_variations", []), 1):
231
+ truncated_var = var[:TITLE_MAX_LEN] # Enforce limit
232
+ variations_output += f"{i}. {truncated_var} ({count_characters(truncated_var)}/{TITLE_MAX_LEN} characters)\n"
233
+
234
+ variations_output += "\nBrand Name Variations:\n"
235
+ for i, var in enumerate(variations.get("brand_name_variations", []), 1):
236
+ truncated_var = var[:BRAND_NAME_MAX_LEN] # Enforce limit
237
+ variations_output += f"{i}. {truncated_var} ({count_characters(truncated_var)}/{BRAND_NAME_MAX_LEN} characters)\n"
238
+
239
+ # Combine main output with variations
240
+ return main_output + variations_output
241
+
242
+ except (json.JSONDecodeError, AttributeError, IndexError) as json_var_err:
243
+ print(f"JSON Parsing Error (Variations): {json_var_err}")
244
+ print(f"Raw variations response text: {getattr(response_var, 'text', 'N/A')}")
245
+ # Check for safety blocks on variations
246
+ if response_var.prompt_feedback.block_reason:
247
+ return main_output + f"\n\n(Could not generate variations: Blocked - {response_var.prompt_feedback.block_reason})"
248
+ return main_output + "\n\n(Could not parse variations response)"
249
+
250
+ except genai.types.generation_types.BlockedPromptException as var_block_error:
251
+ return main_output + f"\n\n(Variations prompt blocked: {var_block_error})"
252
  except Exception as var_error:
253
+ # Catch other errors during variation generation
254
+ print(f"Error generating variations: {var_error}") # Log the error
255
+ return main_output + f"\n\n(Could not generate variations due to an error)"
256
+
257
+ except genai.types.generation_types.BlockedPromptException as block_error:
258
+ # Catch blocked prompts specifically for better feedback
259
+ return f"Error: The main prompt was blocked by Gemini API safety filters: {block_error}. Please modify your input and try again."
260
  except Exception as e:
261
+ # Catch other potential errors during the main API call
262
+ print(f"Error during main listing generation: {e}") # Log the error
263
+ # You might want to check response.candidates[0].finish_reason if available
264
+ # finish_reason = getattr(response.candidates[0], 'finish_reason', 'UNKNOWN')
265
+ # safety_ratings = getattr(response.candidates[0].safety_ratings, 'name', 'UNKNOWN')
266
+ return f"Error generating main listing. Please check logs or try again."
267
+
268
  except Exception as e:
269
+ # Catch configuration errors or other unexpected issues
270
+ print(f"General Error: {e}") # Log the error
271
+ return f"An unexpected error occurred: {str(e)}"
272
 
273
+ # --- Create the Gradio Interface ---
274
  def create_interface():
275
+ with gr.Blocks(title="Amazon Merch on Demand Listing Generator", theme=gr.themes.Soft()) as app:
276
  gr.Markdown("# Amazon Merch on Demand Listing Generator")
277
+ gr.Markdown("Generate SEO-optimized t-shirt and apparel listings for Amazon Merch on Demand using Gemini AI. Character limits are enforced.")
278
+
279
  with gr.Row():
280
+ with gr.Column(scale=1):
281
+ # Recommend using environment variable for API key in real deployments
282
+ api_key = gr.Textbox(
283
+ label="Gemini API Key",
284
+ placeholder="Enter your Gemini API key (or leave blank if set as environment variable)",
285
+ type="password",
286
+ # value=os.getenv("GEMINI_API_KEY", "") # Pre-fill if env var exists
287
+ )
288
+ quote = gr.Textbox(label="Quote/Design/Idea", placeholder="e.g., Lucky To Be A Teacher", value="Rainbow with a quote \"Lucky To Be A Teacher\"")
289
+ niche = gr.Textbox(label="Niche/Holiday/Event", placeholder="e.g., St Patrick's Day", value="St Patricks Day")
290
+ target = gr.Textbox(label="Target Audience", placeholder="e.g., Teacher, Mom, Dad", value="Teacher, Teacher Mom")
291
+ keywords = gr.Textbox(
292
+ label="Target Keywords (comma-separated)",
293
+ placeholder="Enter keywords relevant to your design",
294
+ lines=5,
295
+ value="lucky, teacher, rainbow, st, patricks, day, t-shirt, patrick's, outfit, design, leopard, cheetah, print, shamrock, clover, perfect, men, women, teachers, celebrate, saint, patrick, special, unique, makes, great, gifts, idea, substitute, love, irish, culture, pattys, holiday, teach, shamrocks, cute, design, awesome, show, students"
296
+ )
297
  submit_btn = gr.Button("Generate Amazon Listing", variant="primary")
298
+
299
+ with gr.Column(scale=2):
300
+ status = gr.Textbox(label="Status", value="Ready", interactive=False, lines=1)
301
+ output = gr.Textbox(label="Generated Amazon Listing", lines=25, interactive=True) # Make output selectable
302
+
303
+ def on_submit(api_key_input, quote, niche, target, keywords):
304
+ # Use environment variable if input is blank (optional but good practice)
305
+ # final_api_key = os.getenv("GEMINI_API_KEY") or api_key_input
306
+ final_api_key = api_key_input # Keep it simple for now
307
+
308
+ if not final_api_key:
309
+ # Update status first before returning error message
310
+ yield "Error: Gemini API Key is required.", ""
311
+ return # Stop execution
312
+
313
+ # Update status to indicate processing
314
+ yield "Generating listing... This may take a moment.", "Processing..."
315
+
316
  # Generate the listing
317
+ result = generate_amazon_listing(final_api_key, quote, niche, target, keywords)
318
+
319
+ # Update status and output based on the result
320
+ if "Error:" in result:
321
+ yield f"Finished with Error.", result
322
  else:
323
  yield "Listing generated successfully!", result
324
+
325
  submit_btn.click(
326
  fn=on_submit,
327
  inputs=[api_key, quote, niche, target, keywords],
328
  outputs=[status, output],
329
+ show_progress="full" # Show more detailed progress
330
  )
331
+
332
  gr.Markdown("## Example Input")
333
  gr.Markdown('''
334
+ Use the pre-filled example above or enter your own details:
335
+ * **Quote/Design:** The core text or visual idea on the shirt.
336
+ * **Niche/Holiday:** The specific event, theme, or category (e.g., Halloween, Fishing, Dog Lover).
337
+ * **Target Audience:** Who is this shirt for? (e.g., Nurse, Engineer, Grandpa).
338
+ * **Keywords:** Relevant terms customers might search for.
 
339
  ''')
340
+
341
  gr.Markdown("""
342
+ ## Notes & Troubleshooting
343
+ * **Character Limits:** The app attempts to generate text close to the limits requested in the prompt, but **strictly enforces maximum lengths** by truncating if necessary (Title: 60, Brand: 50, Bullets: 256). The displayed count reflects the final length.
344
+ * **API Key:** For security, consider setting your Gemini API Key as an environment variable (`GEMINI_API_KEY`) instead of pasting it directly, especially if deploying publicly.
345
+ * **Errors:** If you see errors related to 'BlockedPromptException' or 'Safety', your input might have triggered content filters. Try rephrasing. Other errors might relate to API connectivity or quota.
346
+ * **Variations:** The app generates the main listing first, then attempts to generate title/brand variations. Variation generation might fail separately without affecting the main listing.
347
+ * **JSON Request:** The app now explicitly requests JSON output from the API (`response_mime_type`).
348
  """)
349
+
350
  return app
351
 
352
+ # --- Create and Launch the Gradio App ---
353
  app = create_interface()
354
 
355
+ # Launch the app (remove debug=True for production)
356
  if __name__ == "__main__":
357
+ # Set share=True to get a public link (useful for temporary sharing)
358
+ app.launch(debug=True)
359
+ # app.launch() # For deployment (e.g., Hugging Face Spaces)