Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,8 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import gradio as gr
|
2 |
import google.generativeai as genai
|
3 |
import re
|
4 |
import json
|
5 |
-
import os #
|
6 |
|
7 |
# --- Constants for Limits ---
|
8 |
TITLE_MAX_LEN = 60
|
@@ -95,19 +107,27 @@ REMINDER: Make sure each title aims for EXACTLY {TITLE_MAX_LEN} characters. Coun
|
|
95 |
|
96 |
return combined_prompt
|
97 |
|
98 |
-
|
|
|
99 |
"""Generate Amazon listing using Gemini API, enforcing character limits."""
|
100 |
-
|
101 |
-
|
102 |
-
|
|
|
|
|
|
|
|
|
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
|
108 |
-
#
|
109 |
-
|
110 |
-
|
|
|
|
|
|
|
111 |
|
112 |
# Create model with optimized settings
|
113 |
model = genai.GenerativeModel(
|
@@ -116,7 +136,6 @@ def generate_amazon_listing(api_key, quote, niche, target, keywords):
|
|
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 |
)
|
@@ -128,9 +147,8 @@ def generate_amazon_listing(api_key, quote, niche, target, keywords):
|
|
128 |
# --- Generate Main Listing Content ---
|
129 |
response = model.generate_content(prompt)
|
130 |
|
131 |
-
#
|
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)
|
@@ -143,37 +161,30 @@ def generate_amazon_listing(api_key, quote, niche, target, keywords):
|
|
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')}")
|
149 |
-
|
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")
|
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 |
-
|
|
|
|
|
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:
|
@@ -188,7 +199,7 @@ def generate_amazon_listing(api_key, quote, niche, target, keywords):
|
|
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
|
192 |
main_output = format_output(
|
193 |
title,
|
194 |
brand_name,
|
@@ -200,12 +211,10 @@ def generate_amazon_listing(api_key, quote, niche, target, keywords):
|
|
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={
|
208 |
-
"temperature": 0.4,
|
209 |
"top_p": 0.8,
|
210 |
"max_output_tokens": 1024,
|
211 |
"response_mime_type": "application/json"
|
@@ -215,7 +224,6 @@ def generate_amazon_listing(api_key, quote, niche, target, keywords):
|
|
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)
|
@@ -224,52 +232,58 @@ def generate_amazon_listing(api_key, quote, niche, target, keywords):
|
|
224 |
|
225 |
variations = json.loads(json_str_var)
|
226 |
|
227 |
-
# Format variations output, enforcing limits
|
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]
|
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]
|
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 |
-
|
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 |
-
|
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 |
-
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
268 |
except Exception as e:
|
269 |
-
|
270 |
-
|
|
|
|
|
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:
|
@@ -278,12 +292,12 @@ def create_interface():
|
|
278 |
|
279 |
with gr.Row():
|
280 |
with gr.Column(scale=1):
|
281 |
-
#
|
282 |
-
|
283 |
label="Gemini API Key",
|
284 |
-
placeholder="Enter your Gemini API key
|
285 |
type="password",
|
286 |
-
#
|
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")
|
@@ -300,21 +314,23 @@ def create_interface():
|
|
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 |
-
|
304 |
-
|
305 |
-
#
|
306 |
-
|
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 |
-
#
|
317 |
-
result = generate_amazon_listing(
|
318 |
|
319 |
# Update status and output based on the result
|
320 |
if "Error:" in result:
|
@@ -322,11 +338,13 @@ def create_interface():
|
|
322 |
else:
|
323 |
yield "Listing generated successfully!", result
|
324 |
|
|
|
325 |
submit_btn.click(
|
326 |
fn=on_submit,
|
327 |
-
|
|
|
328 |
outputs=[status, output],
|
329 |
-
show_progress="full"
|
330 |
)
|
331 |
|
332 |
gr.Markdown("## Example Input")
|
@@ -340,11 +358,11 @@ Use the pre-filled example above or enter your own details:
|
|
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 |
-
* **
|
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
|
348 |
""")
|
349 |
|
350 |
return app
|
@@ -352,8 +370,8 @@ Use the pre-filled example above or enter your own details:
|
|
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 |
-
#
|
358 |
-
app.launch(
|
359 |
-
# app.launch() # For deployment (e.g., Hugging Face Spaces)
|
|
|
1 |
+
# -*- coding: utf-8 -*-
|
2 |
+
|
3 |
+
################################################################################
|
4 |
+
# WARNING: API Key Security
|
5 |
+
# This application requires you to enter your Gemini API key manually in the UI.
|
6 |
+
# DO NOT paste your API key directly into this code if you plan to share it.
|
7 |
+
# Ensure your API key is kept confidential. Sharing your key can lead to
|
8 |
+
# unauthorized use and potential charges to your account.
|
9 |
+
# Consider using secure methods like environment variables or secrets management
|
10 |
+
# for deployed applications.
|
11 |
+
################################################################################
|
12 |
+
|
13 |
import gradio as gr
|
14 |
import google.generativeai as genai
|
15 |
import re
|
16 |
import json
|
17 |
+
import os # Still useful for potential future path operations, etc.
|
18 |
|
19 |
# --- Constants for Limits ---
|
20 |
TITLE_MAX_LEN = 60
|
|
|
107 |
|
108 |
return combined_prompt
|
109 |
|
110 |
+
# Takes the API Key directly as an argument
|
111 |
+
def generate_amazon_listing(api_key_input, quote, niche, target, keywords):
|
112 |
"""Generate Amazon listing using Gemini API, enforcing character limits."""
|
113 |
+
|
114 |
+
# --- Input Validation ---
|
115 |
+
# Clean the API key first to handle potential whitespace issues
|
116 |
+
cleaned_api_key = api_key_input.strip() if api_key_input else ""
|
117 |
+
|
118 |
+
if not cleaned_api_key:
|
119 |
+
return "Error: Please enter a valid Gemini API key."
|
120 |
if not quote or not niche or not target:
|
121 |
+
return "Error: Please fill in all required fields (Quote, Holiday/Event, and Target Audience)."
|
122 |
|
123 |
try:
|
124 |
+
# --- Configure API ---
|
125 |
+
# Use the cleaned API key from the input
|
126 |
+
print(f"DEBUG: Configuring with API Key (length {len(cleaned_api_key)}): '{cleaned_api_key[:5]}...{cleaned_api_key[-5:]}'") # Log sanitized key
|
127 |
+
if '\n' in cleaned_api_key or '\r' in cleaned_api_key:
|
128 |
+
print("WARNING: API Key may contain newline characters!")
|
129 |
+
|
130 |
+
genai.configure(api_key=cleaned_api_key)
|
131 |
|
132 |
# Create model with optimized settings
|
133 |
model = genai.GenerativeModel(
|
|
|
136 |
"temperature": 0.3, # Lower temperature for more predictable output
|
137 |
"top_p": 0.8,
|
138 |
"max_output_tokens": 1024, # Maximum tokens the API can *return*
|
|
|
139 |
"response_mime_type": "application/json" # Request JSON directly
|
140 |
}
|
141 |
)
|
|
|
147 |
# --- Generate Main Listing Content ---
|
148 |
response = model.generate_content(prompt)
|
149 |
|
150 |
+
# Parse JSON response
|
151 |
try:
|
|
|
152 |
response_text = response.text
|
153 |
# Sometimes the API might still wrap it in markdown, try to strip it
|
154 |
json_match = re.search(r'```json\s*({.*?})\s*```', response_text, re.DOTALL | re.IGNORECASE)
|
|
|
161 |
result = json.loads(json_str)
|
162 |
|
163 |
except (json.JSONDecodeError, AttributeError, IndexError) as json_err:
|
|
|
164 |
print(f"JSON Parsing Error: {json_err}")
|
165 |
+
print(f"Raw response text: {getattr(response, 'text', 'N/A')}")
|
166 |
+
if hasattr(response, 'prompt_feedback') and response.prompt_feedback.block_reason:
|
|
|
167 |
return f"Error: Generation blocked due to: {response.prompt_feedback.block_reason}. Content filters may have been triggered."
|
168 |
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."
|
169 |
|
|
|
170 |
# --- ENFORCE CHARACTER LIMITS ---
|
|
|
171 |
title = result.get("title", "")[:TITLE_MAX_LEN]
|
172 |
brand_name = result.get("brand_name", "")[:BRAND_NAME_MAX_LEN]
|
173 |
bullet1 = result.get("bullet_point_1", "")[:BULLET_POINT_MAX_LEN]
|
174 |
bullet2 = result.get("bullet_point_2", "")[:BULLET_POINT_MAX_LEN]
|
175 |
+
suggested_keywords = result.get("suggested_keywords", "Error generating suggested keywords")
|
176 |
|
177 |
# --- VALIDATION (using potentially truncated values) ---
|
|
|
|
|
|
|
178 |
target_parts = [t.strip().lower() for t in target.split(',')]
|
179 |
title_lower = title.lower()
|
180 |
if not (quote.lower() in title_lower or
|
181 |
niche.lower() in title_lower or
|
182 |
any(t_part in title_lower for t_part in target_parts)):
|
183 |
+
# This is a soft warning, not necessarily a hard error
|
184 |
+
print(f"Warning: Generated title ('{title}') might not strongly match the requested theme: '{quote}', '{niche}', or '{target}'.")
|
185 |
+
|
186 |
|
187 |
# --- Optional: Validate the *lower* bound for bullet points ---
|
|
|
|
|
188 |
# if len(bullet1) < BULLET_POINT_MIN_LEN:
|
189 |
# 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."
|
190 |
# if len(bullet2) < BULLET_POINT_MIN_LEN:
|
|
|
199 |
if phrase in bullet1_lower or phrase in bullet2_lower:
|
200 |
return f"Error: Generated bullet points contain disallowed generic phrase '{phrase}'. Please try again."
|
201 |
|
202 |
+
# Format main output first
|
203 |
main_output = format_output(
|
204 |
title,
|
205 |
brand_name,
|
|
|
211 |
# --- Generate Variations (Optional Second Call) ---
|
212 |
try:
|
213 |
variations_prompt = generate_multiple_variations_prompt(quote, niche, target, keywords)
|
|
|
|
|
214 |
response_var = model.generate_content(
|
215 |
variations_prompt,
|
216 |
+
generation_config={
|
217 |
+
"temperature": 0.4,
|
218 |
"top_p": 0.8,
|
219 |
"max_output_tokens": 1024,
|
220 |
"response_mime_type": "application/json"
|
|
|
224 |
# Parse variations JSON
|
225 |
try:
|
226 |
response_var_text = response_var.text
|
|
|
227 |
json_match_var = re.search(r'```json\s*({.*?})\s*```', response_var_text, re.DOTALL | re.IGNORECASE)
|
228 |
if json_match_var:
|
229 |
json_str_var = json_match_var.group(1)
|
|
|
232 |
|
233 |
variations = json.loads(json_str_var)
|
234 |
|
235 |
+
# Format variations output, enforcing limits
|
236 |
variations_output = "\n\nADDITIONAL VARIATIONS:\n\n"
|
237 |
variations_output += "Title Variations:\n"
|
238 |
for i, var in enumerate(variations.get("title_variations", []), 1):
|
239 |
+
truncated_var = var[:TITLE_MAX_LEN]
|
240 |
variations_output += f"{i}. {truncated_var} ({count_characters(truncated_var)}/{TITLE_MAX_LEN} characters)\n"
|
241 |
|
242 |
variations_output += "\nBrand Name Variations:\n"
|
243 |
for i, var in enumerate(variations.get("brand_name_variations", []), 1):
|
244 |
+
truncated_var = var[:BRAND_NAME_MAX_LEN]
|
245 |
variations_output += f"{i}. {truncated_var} ({count_characters(truncated_var)}/{BRAND_NAME_MAX_LEN} characters)\n"
|
246 |
|
|
|
247 |
return main_output + variations_output
|
248 |
|
249 |
except (json.JSONDecodeError, AttributeError, IndexError) as json_var_err:
|
250 |
print(f"JSON Parsing Error (Variations): {json_var_err}")
|
251 |
print(f"Raw variations response text: {getattr(response_var, 'text', 'N/A')}")
|
252 |
+
if hasattr(response_var, 'prompt_feedback') and response_var.prompt_feedback.block_reason:
|
|
|
253 |
return main_output + f"\n\n(Could not generate variations: Blocked - {response_var.prompt_feedback.block_reason})"
|
254 |
return main_output + "\n\n(Could not parse variations response)"
|
255 |
|
256 |
except genai.types.generation_types.BlockedPromptException as var_block_error:
|
257 |
return main_output + f"\n\n(Variations prompt blocked: {var_block_error})"
|
258 |
except Exception as var_error:
|
259 |
+
print(f"Error generating variations: {var_error}")
|
|
|
260 |
return main_output + f"\n\n(Could not generate variations due to an error)"
|
261 |
|
262 |
except genai.types.generation_types.BlockedPromptException as block_error:
|
|
|
263 |
return f"Error: The main prompt was blocked by Gemini API safety filters: {block_error}. Please modify your input and try again."
|
264 |
except Exception as e:
|
265 |
+
print(f"Error during main listing generation: {e}")
|
266 |
+
# Attempt to get more specific feedback if available
|
267 |
+
finish_reason = "Unknown"
|
268 |
+
safety_ratings = "Unknown"
|
269 |
+
if hasattr(response, 'candidates') and response.candidates:
|
270 |
+
finish_reason = getattr(response.candidates[0], 'finish_reason', 'UNKNOWN')
|
271 |
+
safety_ratings = getattr(response.candidates[0], 'safety_ratings', 'UNKNOWN')
|
272 |
+
print(f"Finish Reason: {finish_reason}, Safety Ratings: {safety_ratings}")
|
273 |
+
return f"Error generating main listing (Reason: {finish_reason}). Please check logs or try again."
|
274 |
+
|
275 |
+
# Catch configuration errors or other unexpected issues outside the API call block
|
276 |
+
except google.api_core.exceptions.PermissionDenied as perm_denied:
|
277 |
+
print(f"Permission Denied Error: {perm_denied}")
|
278 |
+
return "Error: Permission Denied. Please check if your API key is valid, correctly entered, and has the necessary permissions enabled."
|
279 |
except Exception as e:
|
280 |
+
print(f"General Error: {e}")
|
281 |
+
# Check if it's an authentication error which often manifests differently
|
282 |
+
if "authentication" in str(e).lower() or "credentials" in str(e).lower():
|
283 |
+
return "Error: Authentication failed. Please double-check your API key."
|
284 |
return f"An unexpected error occurred: {str(e)}"
|
285 |
|
286 |
+
|
287 |
# --- Create the Gradio Interface ---
|
288 |
def create_interface():
|
289 |
with gr.Blocks(title="Amazon Merch on Demand Listing Generator", theme=gr.themes.Soft()) as app:
|
|
|
292 |
|
293 |
with gr.Row():
|
294 |
with gr.Column(scale=1):
|
295 |
+
# API Key input is now mandatory here
|
296 |
+
api_key_ui = gr.Textbox(
|
297 |
label="Gemini API Key",
|
298 |
+
placeholder="Enter your Gemini API key here",
|
299 |
type="password",
|
300 |
+
# Make sure this input component is mandatory in the logic below
|
301 |
)
|
302 |
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\"")
|
303 |
niche = gr.Textbox(label="Niche/Holiday/Event", placeholder="e.g., St Patrick's Day", value="St Patricks Day")
|
|
|
314 |
status = gr.Textbox(label="Status", value="Ready", interactive=False, lines=1)
|
315 |
output = gr.Textbox(label="Generated Amazon Listing", lines=25, interactive=True) # Make output selectable
|
316 |
|
317 |
+
# This function is called when the button is clicked
|
318 |
+
def on_submit(api_key_from_ui, quote, niche, target, keywords):
|
319 |
+
# --- Validation directly in the submit handler ---
|
320 |
+
if not api_key_from_ui or not api_key_from_ui.strip():
|
321 |
+
# Update status first before returning error message
|
|
|
|
|
322 |
yield "Error: Gemini API Key is required.", ""
|
323 |
+
return # Stop execution if API key is missing
|
324 |
+
|
325 |
+
if not quote or not niche or not target:
|
326 |
+
yield "Error: Quote, Niche/Holiday, and Target Audience are required.", ""
|
327 |
+
return # Stop if other core fields are missing
|
328 |
|
329 |
# Update status to indicate processing
|
330 |
yield "Generating listing... This may take a moment.", "Processing..."
|
331 |
|
332 |
+
# Call the main generation function, passing the API key from the UI
|
333 |
+
result = generate_amazon_listing(api_key_from_ui, quote, niche, target, keywords)
|
334 |
|
335 |
# Update status and output based on the result
|
336 |
if "Error:" in result:
|
|
|
338 |
else:
|
339 |
yield "Listing generated successfully!", result
|
340 |
|
341 |
+
# Connect the button click to the on_submit function
|
342 |
submit_btn.click(
|
343 |
fn=on_submit,
|
344 |
+
# Pass the API key from the UI component as the first input
|
345 |
+
inputs=[api_key_ui, quote, niche, target, keywords],
|
346 |
outputs=[status, output],
|
347 |
+
show_progress="full"
|
348 |
)
|
349 |
|
350 |
gr.Markdown("## Example Input")
|
|
|
358 |
|
359 |
gr.Markdown("""
|
360 |
## Notes & Troubleshooting
|
361 |
+
* **API Key:** You MUST enter a valid Gemini API key in the field above for the generator to work. Keep your key secure.
|
362 |
* **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.
|
363 |
+
* **Errors:** If you see errors related to 'BlockedPromptException' or 'Safety', your input might have triggered content filters. Try rephrasing. 'Permission Denied' or 'Authentication Failed' errors usually indicate an invalid or incorrectly entered API key. Other errors might relate to API connectivity or quota.
|
|
|
364 |
* **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.
|
365 |
+
* **JSON Request:** The app explicitly requests JSON output from the API (`response_mime_type`).
|
366 |
""")
|
367 |
|
368 |
return app
|
|
|
370 |
# --- Create and Launch the Gradio App ---
|
371 |
app = create_interface()
|
372 |
|
373 |
+
# Launch the app (remove debug=True for production deployments)
|
374 |
+
# Set share=True to get a temporary public link for testing (use with caution due to API key input)
|
375 |
if __name__ == "__main__":
|
376 |
+
app.launch(debug=True) # Add share=True here if needed: app.launch(debug=True, share=True)
|
377 |
+
# app.launch() # Use for deployment (e.g., on Hugging Face Spaces - remember security implications)
|
|