Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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 |
-
|
| 13 |
-
output
|
| 14 |
-
output += f"
|
| 15 |
-
output += f"Bullet Point
|
| 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 |
-
|
| 32 |
-
|
| 33 |
-
|
| 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
|
| 40 |
-
- Bullet point 2 must be between
|
| 41 |
-
- DO NOT exceed the character
|
| 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
|
| 56 |
-
"brand_name": "Brand name
|
| 57 |
-
"bullet_point_1": "First bullet point
|
| 58 |
-
"bullet_point_2": "Second bullet point
|
| 59 |
"suggested_keywords": "5 additional keywords separated by commas"
|
| 60 |
}}
|
| 61 |
-
|
| 62 |
-
|
| 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
|
| 85 |
-
|
| 86 |
Respond ONLY with a JSON object in this format:
|
| 87 |
{{
|
| 88 |
"title_variations": [
|
| 89 |
-
"Title variation 1 -
|
| 90 |
-
"Title variation 2 -
|
| 91 |
-
"Title variation 3 -
|
| 92 |
],
|
| 93 |
"brand_name_variations": [
|
| 94 |
-
"Brand name variation 1 (34-
|
| 95 |
-
"Brand name variation 2 (34-
|
| 96 |
-
"Brand name variation 3 (34-
|
| 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,
|
|
|
|
|
|
|
| 123 |
}
|
| 124 |
)
|
| 125 |
-
|
| 126 |
# Generate the main listing
|
| 127 |
prompt = generate_prompt(quote, niche, target, keywords)
|
| 128 |
-
|
| 129 |
try:
|
| 130 |
-
#
|
| 131 |
response = model.generate_content(prompt)
|
| 132 |
-
|
| 133 |
-
#
|
| 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 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
return
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 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
|
| 169 |
-
return f"Error: Generated bullet points contain generic phrase '{phrase}'. Please try again."
|
| 170 |
-
|
| 171 |
-
# Format main output first -
|
| 172 |
main_output = format_output(
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
)
|
| 179 |
-
|
| 180 |
-
#
|
| 181 |
try:
|
| 182 |
variations_prompt = generate_multiple_variations_prompt(quote, niche, target, keywords)
|
|
|
|
|
|
|
| 183 |
response_var = model.generate_content(
|
| 184 |
variations_prompt,
|
| 185 |
-
|
| 186 |
-
"temperature": 0.4,
|
| 187 |
"top_p": 0.8,
|
| 188 |
-
"max_output_tokens": 1024
|
|
|
|
| 189 |
}
|
| 190 |
)
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
except Exception as var_error:
|
| 221 |
-
#
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
|
|
|
| 227 |
except Exception as e:
|
| 228 |
-
|
| 229 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
except Exception as e:
|
| 231 |
-
|
|
|
|
|
|
|
| 232 |
|
| 233 |
-
# Create the Gradio
|
| 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
|
| 238 |
-
|
| 239 |
with gr.Row():
|
| 240 |
-
with gr.Column():
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 246 |
submit_btn = gr.Button("Generate Amazon Listing", variant="primary")
|
| 247 |
-
|
| 248 |
-
with gr.Column():
|
| 249 |
-
status = gr.Textbox(label="Status", value="Ready
|
| 250 |
-
output = gr.Textbox(label="Generated Amazon Listing", lines=25)
|
| 251 |
-
|
| 252 |
-
def on_submit(
|
| 253 |
-
#
|
| 254 |
-
|
| 255 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
# Generate the listing
|
| 257 |
-
result = generate_amazon_listing(
|
| 258 |
-
|
| 259 |
-
# Update status
|
| 260 |
-
if "Error" in result:
|
| 261 |
-
yield "
|
| 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="
|
| 270 |
)
|
| 271 |
-
|
| 272 |
gr.Markdown("## Example Input")
|
| 273 |
gr.Markdown('''
|
| 274 |
-
|
| 275 |
-
Quote/Design
|
| 276 |
-
Holiday
|
| 277 |
-
Target
|
| 278 |
-
Keywords
|
| 279 |
-
```
|
| 280 |
''')
|
| 281 |
-
|
| 282 |
gr.Markdown("""
|
| 283 |
-
## Troubleshooting
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
|
|
|
|
|
|
| 287 |
""")
|
| 288 |
-
|
| 289 |
return app
|
| 290 |
|
| 291 |
-
# Create and
|
| 292 |
app = create_interface()
|
| 293 |
|
| 294 |
-
#
|
| 295 |
if __name__ == "__main__":
|
| 296 |
-
|
|
|
|
|
|
|
|
|
| 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)
|