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)
|