Update app.py
Browse files
app.py
CHANGED
|
@@ -10,6 +10,7 @@ import random
|
|
| 10 |
css = """
|
| 11 |
body {
|
| 12 |
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
|
|
| 13 |
}
|
| 14 |
.container {
|
| 15 |
max-width: 900px;
|
|
@@ -32,9 +33,6 @@ body {
|
|
| 32 |
margin-top: 15px;
|
| 33 |
font-family: 'Courier New', monospace;
|
| 34 |
white-space: pre-wrap;
|
| 35 |
-
#border-radius: 8px;
|
| 36 |
-
#background-color: #f9f9f9;
|
| 37 |
-
#border: 1px solid #eee;
|
| 38 |
}
|
| 39 |
footer {
|
| 40 |
text-align: center;
|
|
@@ -47,8 +45,6 @@ footer {
|
|
| 47 |
.api-key-container {
|
| 48 |
padding: 20px;
|
| 49 |
margin-bottom: 25px;
|
| 50 |
-
#background-color: #f5f5f5;
|
| 51 |
-
#border-radius: 8px;
|
| 52 |
}
|
| 53 |
.prompt-container {
|
| 54 |
margin-bottom: 20px;
|
|
@@ -68,9 +64,13 @@ footer {
|
|
| 68 |
}
|
| 69 |
.examples-container {
|
| 70 |
margin-top: 15px;
|
| 71 |
-
padding:
|
| 72 |
-
|
| 73 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
}
|
| 75 |
.logo {
|
| 76 |
text-align: center;
|
|
@@ -80,561 +80,361 @@ footer {
|
|
| 80 |
max-width: 80px;
|
| 81 |
height: auto;
|
| 82 |
}
|
|
|
|
|
|
|
|
|
|
| 83 |
.style-options {
|
| 84 |
margin-top: 15px;
|
| 85 |
-
padding: 15px;
|
| 86 |
-
#border-radius: 8px;
|
| 87 |
-
#background-color: #f5f5f5;
|
| 88 |
}
|
| 89 |
.market-box {
|
| 90 |
margin-top: 15px;
|
| 91 |
padding: 15px;
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
}
|
| 96 |
.keyword-list {
|
| 97 |
margin-top: 5px;
|
| 98 |
font-family: 'Courier New', monospace;
|
| 99 |
}
|
| 100 |
-
.style-cards {
|
| 101 |
-
display: grid;
|
| 102 |
-
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
| 103 |
-
gap: 15px;
|
| 104 |
-
margin-top: 20px;
|
| 105 |
-
}
|
| 106 |
-
.style-card {
|
| 107 |
-
#border: 1px solid #ddd;
|
| 108 |
-
#border-radius: 8px;
|
| 109 |
-
padding: 15px;
|
| 110 |
-
background-color: white;
|
| 111 |
-
transition: transform 0.2s, box-shadow 0.2s;
|
| 112 |
-
}
|
| 113 |
-
.style-card:hover {
|
| 114 |
-
transform: translateY(-3px);
|
| 115 |
-
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
| 116 |
-
}
|
| 117 |
-
.style-card.selected {
|
| 118 |
-
border: 2px solid #e94560;
|
| 119 |
-
background-color: #fff5f7;
|
| 120 |
-
}
|
| 121 |
-
.style-title {
|
| 122 |
-
font-weight: bold;
|
| 123 |
-
margin-bottom: 5px;
|
| 124 |
-
color: #333;
|
| 125 |
-
}
|
| 126 |
-
.style-desc {
|
| 127 |
-
font-size: 0.9em;
|
| 128 |
-
color: #666;
|
| 129 |
-
}
|
| 130 |
-
.pod-info {
|
| 131 |
-
display: flex;
|
| 132 |
-
margin-top: 10px;
|
| 133 |
-
font-size: 0.8em;
|
| 134 |
-
}
|
| 135 |
-
.pod-colors, .pod-products {
|
| 136 |
-
flex: 1;
|
| 137 |
-
color: #000;
|
| 138 |
-
}
|
| 139 |
-
.pod-colors span, .pod-products span {
|
| 140 |
-
background: #f0f0f0;
|
| 141 |
-
padding: 2px 6px;
|
| 142 |
-
border-radius: 4px;
|
| 143 |
-
margin-right: 4px;
|
| 144 |
-
white-space: nowrap;
|
| 145 |
-
display: inline-block;
|
| 146 |
-
margin-bottom: 4px;
|
| 147 |
-
color: #000;
|
| 148 |
-
}
|
| 149 |
-
.analytics-section {
|
| 150 |
-
margin-top: 20px;
|
| 151 |
-
padding: 15px;
|
| 152 |
-
#border-radius: 8px;
|
| 153 |
-
background-color: #f0f8ff;
|
| 154 |
-
#border: 1px solid #d0e0ff;
|
| 155 |
-
}
|
| 156 |
"""
|
| 157 |
|
| 158 |
-
#
|
| 159 |
-
|
| 160 |
-
"Auto-Detect": {
|
| 161 |
-
"guidance": "AI will determine the best style based on quote analysis",
|
| 162 |
-
"colors": ["Adaptive Colors"],
|
| 163 |
-
"products": ["Various Products"]
|
| 164 |
-
},
|
| 165 |
-
"Streetwear_Urban": {
|
| 166 |
-
"guidance": "Focus on bold, urban streetwear typography with graffiti influences, distressed elements, and street culture aesthetics. Perfect for hoodies and t-shirts targeting youth culture.",
|
| 167 |
-
"colors": ["Black", "White", "Bold Accents"],
|
| 168 |
-
"products": ["Hoodies", "T-shirts", "Snapbacks"]
|
| 169 |
-
},
|
| 170 |
-
"Vintage_Retro": {
|
| 171 |
-
"guidance": "Focus on nostalgic vintage typography with distressed textures, retro fonts, and classic elements from the 50s-90s era. Include aged effects and throwback aesthetics.",
|
| 172 |
-
"colors": ["Faded", "Weathered", "Retro Palettes"],
|
| 173 |
-
"products": ["T-shirts", "Hoodies", "Tanks"]
|
| 174 |
-
},
|
| 175 |
-
"Minimalist_Clean": {
|
| 176 |
-
"guidance": "Focus on clean, minimal typography with ample white space, elegant simple fonts, and refined layouts. Prioritize readability and sophistication with subtle design elements.",
|
| 177 |
-
"colors": ["Black", "White", "Grey", "Neutrals"],
|
| 178 |
-
"products": ["Premium T-shirts", "Hoodies", "Long Sleeves"]
|
| 179 |
-
},
|
| 180 |
-
"Motivational_Fitness": {
|
| 181 |
-
"guidance": "Focus on dynamic, energetic typography for fitness and motivational quotes. Use strong, powerful fonts with dynamic layouts that convey strength, movement and determination.",
|
| 182 |
-
"colors": ["Bold", "High-contrast", "Energetic"],
|
| 183 |
-
"products": ["Workout Tees", "Gym Hoodies", "Tanks"]
|
| 184 |
-
},
|
| 185 |
-
"Family_Sentimental": {
|
| 186 |
-
"guidance": "Focus on heartfelt, emotional typography designs for family quotes. Use warm, inviting fonts with decorative elements like hearts, flourishes, and family-oriented symbols.",
|
| 187 |
-
"colors": ["Warm", "Comforting", "Heartfelt"],
|
| 188 |
-
"products": ["Family T-shirts", "Gift Hoodies"]
|
| 189 |
-
},
|
| 190 |
-
"Funny_Humor": {
|
| 191 |
-
"guidance": "Focus on playful, humorous typography with comic-style fonts, fun layouts, and witty visual elements that enhance the humorous quote. Include playful decorations.",
|
| 192 |
-
"colors": ["Bright", "Playful", "Attention-grabbing"],
|
| 193 |
-
"products": ["Novelty Tees", "Humorous Hoodies"]
|
| 194 |
-
},
|
| 195 |
-
}
|
| 196 |
-
|
| 197 |
-
# Function to generate prompts using Gemini API
|
| 198 |
-
def generate_typography_prompts(api_key, quote, num_prompts=10, style_preference=None):
|
| 199 |
-
# Configure the Gemini API
|
| 200 |
genai.configure(api_key=api_key)
|
| 201 |
|
| 202 |
-
# Create a model
|
| 203 |
model = genai.GenerativeModel(
|
| 204 |
model_name="gemini-1.5-pro",
|
| 205 |
generation_config={
|
| 206 |
-
"temperature": 0.
|
| 207 |
"top_p": 0.95,
|
| 208 |
"top_k": 40,
|
| 209 |
-
"max_output_tokens":
|
| 210 |
}
|
| 211 |
)
|
| 212 |
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
if style_preference and style_preference in POD_STYLES:
|
| 216 |
-
style_guidance = POD_STYLES[style_preference]["guidance"]
|
| 217 |
-
|
| 218 |
-
# Craft a detailed prompt for Gemini
|
| 219 |
-
system_prompt = f"""You are a creative typography and print-on-demand design expert.
|
| 220 |
-
|
| 221 |
-
Create {num_prompts} unique, creative typography design prompts for the following quote:
|
| 222 |
-
"{quote}"
|
| 223 |
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
4. "Target Audience:" section naming the specific demographic this design would appeal to
|
| 230 |
-
5. "Best Products:" section suggesting 2-3 specific POD products this design works best on
|
| 231 |
|
| 232 |
-
|
|
|
|
|
|
|
| 233 |
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
|
| 239 |
-
|
| 240 |
-
|
|
|
|
|
|
|
|
|
|
| 241 |
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
|
|
|
| 246 |
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
|
|
|
| 251 |
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
prompts = []
|
| 261 |
-
|
| 262 |
-
# Split into individual prompts and clean up
|
| 263 |
-
raw_prompts = re.split(r'\d+\.\s+', result)[1:] # Skip first empty element
|
| 264 |
-
|
| 265 |
-
if not raw_prompts: # If splitting didn't work, try another approach
|
| 266 |
-
raw_prompts = result.split("\n\n")
|
| 267 |
-
|
| 268 |
-
for prompt in raw_prompts:
|
| 269 |
-
# Clean up the prompt and ensure it has proper structure
|
| 270 |
-
prompt = prompt.strip()
|
| 271 |
-
if not prompt:
|
| 272 |
-
continue
|
| 273 |
-
|
| 274 |
-
# Ensure there's a Target Audience section
|
| 275 |
-
if "Target Audience:" not in prompt:
|
| 276 |
-
parts = prompt.split('\n')
|
| 277 |
-
prompt = parts[0] + "\nTarget Audience: Print-on-demand customers, typography enthusiasts."
|
| 278 |
-
|
| 279 |
-
# Ensure there's a Best Products section
|
| 280 |
-
if "Best Products:" not in prompt:
|
| 281 |
-
prompt += "\nBest Products: T-shirt, Hoodie, Long Sleeve Tee"
|
| 282 |
-
|
| 283 |
-
prompts.append(prompt)
|
| 284 |
-
|
| 285 |
-
# If we still don't have enough prompts, try another parsing method
|
| 286 |
-
if len(prompts) < num_prompts/2:
|
| 287 |
-
lines = result.split('\n')
|
| 288 |
-
current_prompt = ""
|
| 289 |
-
|
| 290 |
-
for line in lines:
|
| 291 |
-
if re.match(r'^\d+\.', line): # New prompt begins
|
| 292 |
-
if current_prompt:
|
| 293 |
-
prompts.append(current_prompt.strip())
|
| 294 |
-
current_prompt = line[line.find('.')+1:].strip()
|
| 295 |
-
else:
|
| 296 |
-
current_prompt = line[line.find('.')+1:].strip()
|
| 297 |
-
else:
|
| 298 |
-
current_prompt += "\n" + line
|
| 299 |
-
|
| 300 |
-
if current_prompt: # Add the last prompt
|
| 301 |
-
prompts.append(current_prompt.strip())
|
| 302 |
-
|
| 303 |
-
# Ensure we have the right number of prompts
|
| 304 |
-
prompts = prompts[:num_prompts]
|
| 305 |
-
|
| 306 |
-
return prompts, None
|
| 307 |
-
|
| 308 |
-
except Exception as e:
|
| 309 |
-
return None, f"Error: {str(e)}"
|
| 310 |
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
# Configure the Gemini API
|
| 314 |
-
genai.configure(api_key=api_key)
|
| 315 |
-
|
| 316 |
-
# Create a model
|
| 317 |
-
model = genai.GenerativeModel(
|
| 318 |
-
model_name="gemini-1.5-pro",
|
| 319 |
-
generation_config={
|
| 320 |
-
"temperature": 0.7,
|
| 321 |
-
"top_p": 0.95,
|
| 322 |
-
"max_output_tokens": 1000,
|
| 323 |
-
}
|
| 324 |
-
)
|
| 325 |
-
|
| 326 |
-
# Craft a detailed prompt for Gemini
|
| 327 |
-
system_prompt = f"""Based on the following quote: "{quote}"
|
| 328 |
|
| 329 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 330 |
|
| 331 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 332 |
|
| 333 |
-
3. Identify 3-5 specific customer personas who would be most likely to purchase this design.
|
| 334 |
|
| 335 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 336 |
|
| 337 |
-
HOLIDAYS/EVENTS/NICHES
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
|
| 342 |
-
|
| 343 |
-
|
|
|
|
|
|
|
| 344 |
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 349 |
|
| 350 |
-
Make sure the keywords are relevant to the quote and would be used by actual shoppers looking for POD products with such messages. Focus on gift-giving occasions, product types, recipient types, and sentiment.
|
| 351 |
-
"""
|
| 352 |
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
# Extract the text from the response
|
| 358 |
-
result = response.text
|
| 359 |
-
|
| 360 |
-
return result, None
|
| 361 |
-
|
| 362 |
-
except Exception as e:
|
| 363 |
-
return None, f"Error: {str(e)}"
|
| 364 |
-
|
| 365 |
-
# Function to analyze quote and suggest the best style automatically
|
| 366 |
-
def analyze_quote_for_style(api_key, quote):
|
| 367 |
-
if not api_key or not quote:
|
| 368 |
-
return "Auto-Detect" # Default
|
| 369 |
|
| 370 |
-
# Configure the Gemini API
|
| 371 |
genai.configure(api_key=api_key)
|
| 372 |
-
|
| 373 |
-
# Create a model
|
| 374 |
model = genai.GenerativeModel(
|
| 375 |
model_name="gemini-1.5-pro",
|
| 376 |
-
generation_config={
|
| 377 |
-
"temperature": 0.3, # Lower temperature for more deterministic results
|
| 378 |
-
"top_p": 0.95,
|
| 379 |
-
"max_output_tokens": 500,
|
| 380 |
-
}
|
| 381 |
)
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
system_prompt = f"""Analyze the following quote for a t-shirt/hoodie design:
|
| 390 |
-
"{quote}"
|
| 391 |
-
|
| 392 |
-
Based on the quote's theme, tone, and content, select the MOST appropriate style category from this list:
|
| 393 |
-
{', '.join(style_options)}
|
| 394 |
-
|
| 395 |
-
Return only the exact name of the most appropriate style from the list, nothing else.
|
| 396 |
-
"""
|
| 397 |
-
|
| 398 |
try:
|
| 399 |
-
# Generate content
|
| 400 |
response = model.generate_content([system_prompt])
|
| 401 |
-
|
| 402 |
-
# Extract the text from the response
|
| 403 |
-
result = response.text.strip()
|
| 404 |
-
|
| 405 |
-
# Ensure the result matches one of our styles
|
| 406 |
-
for style in style_options:
|
| 407 |
-
if style.lower() in result.lower():
|
| 408 |
-
return style
|
| 409 |
-
|
| 410 |
-
# Default to the first style if no match
|
| 411 |
-
return style_options[0]
|
| 412 |
-
|
| 413 |
except Exception as e:
|
| 414 |
-
return "
|
| 415 |
|
| 416 |
-
# Function to handle form submission
|
| 417 |
-
def process_quote(api_key, quote,
|
| 418 |
-
if not api_key:
|
| 419 |
-
|
| 420 |
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
|
|
|
|
|
|
| 425 |
progress(0, desc="Initializing...")
|
| 426 |
-
time.sleep(0.
|
| 427 |
|
| 428 |
-
progress(0.2, desc="
|
| 429 |
-
time.sleep(0.3)
|
| 430 |
|
| 431 |
-
#
|
| 432 |
-
|
| 433 |
-
if style_preference == "Auto-Detect":
|
| 434 |
-
progress(0.3, desc="Analyzing quote for best style...")
|
| 435 |
-
detected_style = analyze_quote_for_style(api_key, quote)
|
| 436 |
-
auto_detect_info = f"Based on quote analysis, the AI selected the '{detected_style}' style."
|
| 437 |
-
else:
|
| 438 |
-
auto_detect_info = ""
|
| 439 |
-
|
| 440 |
-
progress(0.5, desc="Generating creative prompts...")
|
| 441 |
-
prompts, error = generate_typography_prompts(api_key, quote, num_prompts, detected_style)
|
| 442 |
|
| 443 |
if error:
|
| 444 |
-
return error, "
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
market_info = "Could not generate market information at this time."
|
| 451 |
|
| 452 |
-
|
| 453 |
-
|
|
|
|
|
|
|
|
|
|
| 454 |
|
| 455 |
-
|
| 456 |
-
result_text = f"Typography Prompts for: \"{quote}\"\n"
|
| 457 |
-
if auto_detect_info:
|
| 458 |
-
result_text += f"\n{auto_detect_info}\n"
|
| 459 |
-
result_text += f"\nStyle: {detected_style}\n\n"
|
| 460 |
|
| 461 |
-
|
| 462 |
-
|
|
|
|
|
|
|
|
|
|
| 463 |
|
| 464 |
progress(1.0, desc="Done!")
|
| 465 |
-
return
|
| 466 |
|
| 467 |
-
# Example quotes for examples
|
| 468 |
example_quotes = [
|
| 469 |
-
"
|
| 470 |
-
"
|
| 471 |
-
"
|
| 472 |
-
"best dad ever",
|
| 473 |
-
"sarcastic comment loading",
|
| 474 |
-
"coffee first, then adulting"
|
| 475 |
]
|
| 476 |
|
| 477 |
-
# Create style cards for selection
|
| 478 |
-
def create_style_cards():
|
| 479 |
-
style_cards_html = ""
|
| 480 |
-
for style_id, style_data in POD_STYLES.items():
|
| 481 |
-
# Create HTML for each style card
|
| 482 |
-
colors_html = "".join([f'<span>{color}</span>' for color in style_data["colors"]])
|
| 483 |
-
products_html = "".join([f'<span>{product}</span>' for product in style_data["products"]])
|
| 484 |
-
|
| 485 |
-
style_cards_html += f"""
|
| 486 |
-
<div class="style-card" id="style-{style_id}" onclick="selectStyle('{style_id}')">
|
| 487 |
-
<div class="style-title">{style_id.replace('_', ' ')}</div>
|
| 488 |
-
<div class="style-desc">{style_data['guidance'][:100]}...</div>
|
| 489 |
-
<div class="pod-info">
|
| 490 |
-
<div class="pod-colors">
|
| 491 |
-
{colors_html}
|
| 492 |
-
</div>
|
| 493 |
-
<div class="pod-products">
|
| 494 |
-
{products_html}
|
| 495 |
-
</div>
|
| 496 |
-
</div>
|
| 497 |
-
</div>
|
| 498 |
-
"""
|
| 499 |
-
|
| 500 |
-
# JavaScript for handling style selection
|
| 501 |
-
js_code = """
|
| 502 |
-
<script>
|
| 503 |
-
function selectStyle(styleId) {
|
| 504 |
-
// Remove selected class from all cards
|
| 505 |
-
document.querySelectorAll('.style-card').forEach(card => {
|
| 506 |
-
card.classList.remove('selected');
|
| 507 |
-
});
|
| 508 |
-
|
| 509 |
-
// Add selected class to clicked card
|
| 510 |
-
document.getElementById('style-' + styleId).classList.add('selected');
|
| 511 |
-
|
| 512 |
-
// Update the hidden style input
|
| 513 |
-
const styleInput = document.getElementById('style-input');
|
| 514 |
-
styleInput.value = styleId;
|
| 515 |
-
|
| 516 |
-
// Trigger change event (for Gradio)
|
| 517 |
-
const event = new Event('change');
|
| 518 |
-
styleInput.dispatchEvent(event);
|
| 519 |
-
}
|
| 520 |
-
|
| 521 |
-
// Initialize with Auto-Detect selected
|
| 522 |
-
document.addEventListener('DOMContentLoaded', () => {
|
| 523 |
-
setTimeout(() => {
|
| 524 |
-
selectStyle('Auto-Detect');
|
| 525 |
-
}, 500);
|
| 526 |
-
});
|
| 527 |
-
</script>
|
| 528 |
-
"""
|
| 529 |
-
|
| 530 |
-
return f"""
|
| 531 |
-
<div class="style-cards">
|
| 532 |
-
{style_cards_html}
|
| 533 |
-
</div>
|
| 534 |
-
{js_code}
|
| 535 |
-
"""
|
| 536 |
-
|
| 537 |
-
# Create the Gradio interface
|
| 538 |
def create_demo():
|
| 539 |
with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
|
| 540 |
-
gr.Markdown(
|
| 541 |
-
""
|
| 542 |
-
<div class="
|
| 543 |
-
|
| 544 |
-
</div>
|
| 545 |
-
<div class="subtitle">
|
| 546 |
-
Generate creative typography prompts for t-shirts & hoodies using Gemini 1.5 Pro
|
| 547 |
-
</div>
|
| 548 |
-
"""
|
| 549 |
-
)
|
| 550 |
-
|
| 551 |
-
with gr.Group(elem_classes="api-key-container"):
|
| 552 |
-
api_key = gr.Textbox(
|
| 553 |
-
label="Gemini API Key",
|
| 554 |
-
placeholder="Enter your Gemini API key here...",
|
| 555 |
-
type="password",
|
| 556 |
-
info="Your API key is not stored and only used for this session"
|
| 557 |
-
)
|
| 558 |
-
|
| 559 |
-
quote = gr.Textbox(
|
| 560 |
-
label="Your Quote/Text",
|
| 561 |
-
placeholder="Enter the quote you want to create typography designs for...",
|
| 562 |
-
info="Example: \"hustle mode: activated\"",
|
| 563 |
-
lines=2
|
| 564 |
-
)
|
| 565 |
-
|
| 566 |
-
with gr.Group(elem_classes="style-options"):
|
| 567 |
-
gr.Markdown("### Style Selection")
|
| 568 |
-
|
| 569 |
-
# Visual style selection cards
|
| 570 |
-
style_cards = gr.HTML(create_style_cards())
|
| 571 |
-
|
| 572 |
-
# Hidden field to store selected style
|
| 573 |
-
style_preference = gr.Textbox(
|
| 574 |
-
value="Auto-Detect",
|
| 575 |
-
visible=False,
|
| 576 |
-
elem_id="style-input"
|
| 577 |
-
)
|
| 578 |
-
|
| 579 |
-
with gr.Row():
|
| 580 |
-
num_prompts = gr.Slider(
|
| 581 |
-
minimum=5,
|
| 582 |
-
maximum=15,
|
| 583 |
-
value=10,
|
| 584 |
-
step=1,
|
| 585 |
-
label="Number of Prompts",
|
| 586 |
-
info="How many unique prompts to generate"
|
| 587 |
-
)
|
| 588 |
-
|
| 589 |
-
submit_btn = gr.Button("Generate Typography Prompts", variant="primary", size="lg")
|
| 590 |
|
| 591 |
-
gr.Examples(
|
| 592 |
-
examples=example_quotes,
|
| 593 |
-
inputs=quote,
|
| 594 |
-
label="Example Quotes"
|
| 595 |
-
)
|
| 596 |
-
|
| 597 |
-
# Output tabs for different sections
|
| 598 |
with gr.Tabs() as tabs:
|
| 599 |
-
with gr.TabItem("
|
| 600 |
-
|
| 601 |
-
label="
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 607 |
|
| 608 |
-
with gr.TabItem("
|
| 609 |
-
|
| 610 |
-
label="
|
| 611 |
-
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
| 625 |
-
|
| 626 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 627 |
|
| 628 |
-
# Handle form submissions
|
| 629 |
submit_btn.click(
|
| 630 |
-
fn=
|
| 631 |
-
inputs=[
|
| 632 |
-
outputs=[
|
| 633 |
)
|
| 634 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 635 |
return demo
|
| 636 |
|
| 637 |
-
|
|
|
|
|
|
|
| 638 |
if __name__ == "__main__":
|
| 639 |
demo = create_demo()
|
| 640 |
demo.launch()
|
|
|
|
| 10 |
css = """
|
| 11 |
body {
|
| 12 |
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, sans-serif;
|
| 13 |
+
#background-color: #fafafa;
|
| 14 |
}
|
| 15 |
.container {
|
| 16 |
max-width: 900px;
|
|
|
|
| 33 |
margin-top: 15px;
|
| 34 |
font-family: 'Courier New', monospace;
|
| 35 |
white-space: pre-wrap;
|
|
|
|
|
|
|
|
|
|
| 36 |
}
|
| 37 |
footer {
|
| 38 |
text-align: center;
|
|
|
|
| 45 |
.api-key-container {
|
| 46 |
padding: 20px;
|
| 47 |
margin-bottom: 25px;
|
|
|
|
|
|
|
| 48 |
}
|
| 49 |
.prompt-container {
|
| 50 |
margin-bottom: 20px;
|
|
|
|
| 64 |
}
|
| 65 |
.examples-container {
|
| 66 |
margin-top: 15px;
|
| 67 |
+
padding: 10px;
|
| 68 |
+
border-radius: 8px;
|
| 69 |
+
background-color: #f0f0f0;
|
| 70 |
+
}
|
| 71 |
+
.tab-content {
|
| 72 |
+
padding: 20px;
|
| 73 |
+
background-color: #fff;
|
| 74 |
}
|
| 75 |
.logo {
|
| 76 |
text-align: center;
|
|
|
|
| 80 |
max-width: 80px;
|
| 81 |
height: auto;
|
| 82 |
}
|
| 83 |
+
.tabs-container {
|
| 84 |
+
margin-top: 20px;
|
| 85 |
+
}
|
| 86 |
.style-options {
|
| 87 |
margin-top: 15px;
|
|
|
|
|
|
|
|
|
|
| 88 |
}
|
| 89 |
.market-box {
|
| 90 |
margin-top: 15px;
|
| 91 |
padding: 15px;
|
| 92 |
+
border: 1px solid #ddd;
|
| 93 |
+
border-radius: 8px;
|
| 94 |
+
background-color: #f9f9f9;
|
| 95 |
}
|
| 96 |
.keyword-list {
|
| 97 |
margin-top: 5px;
|
| 98 |
font-family: 'Courier New', monospace;
|
| 99 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
"""
|
| 101 |
|
| 102 |
+
# MODIFIED FUNCTION: Generate structured POD marketing content
|
| 103 |
+
def generate_pod_structured_output(api_key, quote, num_design_prompts, ui_selected_style_name="None (Let AI decide)"):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
genai.configure(api_key=api_key)
|
| 105 |
|
|
|
|
| 106 |
model = genai.GenerativeModel(
|
| 107 |
model_name="gemini-1.5-pro",
|
| 108 |
generation_config={
|
| 109 |
+
"temperature": 0.85, # Slightly higher for more variety in more prompts
|
| 110 |
"top_p": 0.95,
|
| 111 |
"top_k": 40,
|
| 112 |
+
"max_output_tokens": 3072, # Increased slightly for potentially more prompts
|
| 113 |
}
|
| 114 |
)
|
| 115 |
|
| 116 |
+
style_for_header_tag = "AUTO"
|
| 117 |
+
style_guidance_for_llm = "AI chooses the best-fitting style automatically"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
|
| 119 |
+
if ui_selected_style_name and ui_selected_style_name != "None (Let AI decide)":
|
| 120 |
+
style_for_header_tag = ui_selected_style_name
|
| 121 |
+
style_guidance_for_llm = f"The user has selected a preferred style: '{ui_selected_style_name}'. You MUST apply this style to all relevant prompts in Section 4."
|
| 122 |
+
else:
|
| 123 |
+
style_guidance_for_llm = "The user has left the style choice empty. AI should choose the best-fitting style automatically for Section 4."
|
|
|
|
|
|
|
| 124 |
|
| 125 |
+
# The 'num_design_prompts' now dictates the number of ideas in Section 4
|
| 126 |
+
system_prompt = f"""You are a smart Print-on-Demand (POD) marketing assistant.
|
| 127 |
+
Your task is to analyze the quote "{quote}" and return FOUR structured sections that help with marketing, audience targeting, and design execution.
|
| 128 |
|
| 129 |
+
The system has an OPTIONAL STYLES section.
|
| 130 |
+
{style_guidance_for_llm}
|
| 131 |
+
---
|
| 132 |
+
Return only the following four sections:
|
| 133 |
|
| 134 |
+
π 1. AMAZON SEARCH KEYWORDS
|
| 135 |
+
Generate a comma-separated list of 15-25 SEO-rich keywords relevant to the quote "{quote}".
|
| 136 |
+
- Mix of broad and niche-specific terms
|
| 137 |
+
- Focus on what real buyers would search for on Amazon
|
| 138 |
+
Example: dog lover shirt, funny pet shirt, dog humor tee, running with dog
|
| 139 |
|
| 140 |
+
---
|
| 141 |
+
π
2. HOLIDAYS / EVENTS / NICHES
|
| 142 |
+
Identify all relevant holidays, occasions, events, and niche categories the quote "{quote}" fits into. (Provide at least 3-5 items)
|
| 143 |
+
- Be specific and include both evergreen and seasonal contexts
|
| 144 |
+
Example: Dog Lovers, Pet Adoption Month, National Dog Day, Funny Workout Shirts
|
| 145 |
|
| 146 |
+
---
|
| 147 |
+
π― 3. TARGET AUDIENCE
|
| 148 |
+
Describe the ideal customer this quote/design ("{quote}") would appeal to.
|
| 149 |
+
- Include: age range, gender, interests, lifestyle traits, tone preference
|
| 150 |
+
Example: Adults 25β45, pet lovers, dog moms, people with sarcastic or playful humor, casual wearers
|
| 151 |
|
| 152 |
+
---
|
| 153 |
+
π¨ 4. PROMPTS ({num_design_prompts} Ideas) β STYLE: [{style_for_header_tag}]
|
| 154 |
+
Generate exactly {num_design_prompts} creative design prompts for the quote: "{quote}", based on:
|
| 155 |
+
- Tone and meaning of the quote
|
| 156 |
+
- The style determined above (either user-selected '{style_for_header_tag if style_for_header_tag != "AUTO" else "AI Choice"}' or AI-recommended if AUTO)
|
| 157 |
+
- Each prompt should include layout, typography style, design elements, and color ideas.
|
| 158 |
+
If a style was provided by the user, apply it consistently.
|
| 159 |
+
If no style was provided (AUTO), analyze the quote and recommend and apply a fitting visual style.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
|
| 161 |
+
Example (If User Selected Style = Silhouette and num_design_prompts = 1):
|
| 162 |
+
"1. Bold, stacked text in athletic font for THE PROVIDED QUOTE. A silhouette of a dog pulling a leash with a person chasing behind. High contrast black-and-white design optimized for light shirts."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
|
| 164 |
+
Example (If Style = AUTO, AI chose Retro, and num_design_prompts = 1):
|
| 165 |
+
"1. Retro block lettering in faded orange and teal for THE PROVIDED QUOTE. Text arches above a cartoon dog flexing with sunglasses. Distressed texture for a vintage look."
|
| 166 |
+
---
|
| 167 |
+
β οΈ Output Format Guidelines:
|
| 168 |
+
- Use headers exactly as shown (including the emojis, numbering, and the dynamic prompt count in Section 4 header).
|
| 169 |
+
- For Section 4, number each design prompt idea starting from 1.
|
| 170 |
+
- Do NOT restate the provided quote "{quote}" unless it's within the design prompt itself where it's part of the design concept.
|
| 171 |
+
- Do NOT explain your reasoning outside the requested sections.
|
| 172 |
+
- Keep your output clean and implementation-ready, directly providing the content under each header.
|
| 173 |
+
- Ensure each section is clearly delineated by the headers and newlines.
|
| 174 |
+
"""
|
| 175 |
+
print("--- SYSTEM PROMPT BEING SENT TO API (First 500 chars) ---")
|
| 176 |
+
print(system_prompt[:500] + "...")
|
| 177 |
+
print("--- END OF SYSTEM PROMPT PREVIEW ---")
|
| 178 |
|
| 179 |
+
try:
|
| 180 |
+
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] Attempting to generate {num_design_prompts} prompts for quote: '{quote}' with style: '{ui_selected_style_name}'")
|
| 181 |
+
response = model.generate_content([system_prompt])
|
| 182 |
+
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] Received response from API.")
|
| 183 |
+
return response.text, None
|
| 184 |
+
except Exception as e:
|
| 185 |
+
error_message = f"Error during API call: {str(e)}"
|
| 186 |
+
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {error_message}")
|
| 187 |
+
if hasattr(e, 'message'):
|
| 188 |
+
error_message = f"API Error: {e.message}"
|
| 189 |
+
return None, error_message
|
| 190 |
|
|
|
|
| 191 |
|
| 192 |
+
# MODIFIED PARSING FUNCTION for the structured output
|
| 193 |
+
def parse_structured_output(text_output):
|
| 194 |
+
data = {
|
| 195 |
+
"keywords_header": "π 1. AMAZON SEARCH KEYWORDS",
|
| 196 |
+
"keywords_content": "Not found or parsing error.",
|
| 197 |
+
"holidays_header": "π
2. HOLIDAYS / EVENTS / NICHES",
|
| 198 |
+
"holidays_content": "Not found or parsing error.",
|
| 199 |
+
"audience_header": "π― 3. TARGET AUDIENCE",
|
| 200 |
+
"audience_content": "Not found or parsing error.",
|
| 201 |
+
"prompts_header": "π¨ 4. PROMPTS β STYLE: N/A", # Default, will be replaced
|
| 202 |
+
"prompts_content": "Not found or parsing error."
|
| 203 |
+
}
|
| 204 |
|
| 205 |
+
kw_match = re.search(r"(π 1\. AMAZON SEARCH KEYWORDS)\s*\n(.*?)(?=\n\nπ
2\. HOLIDAYS / EVENTS / NICHES|\Z)", text_output, re.DOTALL)
|
| 206 |
+
if kw_match:
|
| 207 |
+
data["keywords_header"] = kw_match.group(1).strip()
|
| 208 |
+
data["keywords_content"] = kw_match.group(2).strip()
|
| 209 |
|
| 210 |
+
hol_match = re.search(r"(π
2\. HOLIDAYS / EVENTS / NICHES)\s*\n(.*?)(?=\n\nπ― 3\. TARGET AUDIENCE|\Z)", text_output, re.DOTALL)
|
| 211 |
+
if hol_match:
|
| 212 |
+
data["holidays_header"] = hol_match.group(1).strip()
|
| 213 |
+
data["holidays_content"] = hol_match.group(2).strip()
|
| 214 |
|
| 215 |
+
# Lookahead for prompts section needs to be dynamic or very general
|
| 216 |
+
aud_match = re.search(r"(π― 3\. TARGET AUDIENCE)\s*\n(.*?)(?=\n\nπ¨ 4\. PROMPTS \(\d+ Ideas?\) β STYLE:|\Z)", text_output, re.DOTALL)
|
| 217 |
+
if aud_match:
|
| 218 |
+
data["audience_header"] = aud_match.group(1).strip()
|
| 219 |
+
data["audience_content"] = aud_match.group(2).strip()
|
| 220 |
+
|
| 221 |
+
# Regex for prompts header is now more general to capture the number of ideas
|
| 222 |
+
# It matches "π¨ 4. PROMPTS (N Ideas) β STYLE: [STYLE]"
|
| 223 |
+
prompt_header_pattern_match = re.search(r"(π¨ 4\. PROMPTS \(\d+\s*Ideas?\) β STYLE:.*?)\s*\n(.*?)\Z", text_output, re.DOTALL)
|
| 224 |
+
if prompt_header_pattern_match:
|
| 225 |
+
data["prompts_header"] = prompt_header_pattern_match.group(1).strip()
|
| 226 |
+
data["prompts_content"] = prompt_header_pattern_match.group(2).strip()
|
| 227 |
+
else:
|
| 228 |
+
# Fallback if the "(N Ideas)" part is missing for some reason
|
| 229 |
+
prompt_fallback_match = re.search(r"(π¨ 4\. PROMPTS β STYLE:.*?)\s*\n(.*?)\Z", text_output, re.DOTALL)
|
| 230 |
+
if prompt_fallback_match:
|
| 231 |
+
data["prompts_header"] = prompt_fallback_match.group(1).strip()
|
| 232 |
+
data["prompts_content"] = prompt_fallback_match.group(2).strip()
|
| 233 |
+
print("Warning: Parsed prompts using fallback regex. Header might not exactly match '(N Ideas)'.")
|
| 234 |
+
|
| 235 |
+
return data
|
| 236 |
|
|
|
|
|
|
|
| 237 |
|
| 238 |
+
# Function to analyze quote sentiment and suggest styles
|
| 239 |
+
def analyze_quote(api_key, quote, progress=gr.Progress()):
|
| 240 |
+
if not api_key: return "Please enter your Gemini API key for analysis."
|
| 241 |
+
if not quote: return "Please enter a quote to analyze."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
|
|
|
|
| 243 |
genai.configure(api_key=api_key)
|
|
|
|
|
|
|
| 244 |
model = genai.GenerativeModel(
|
| 245 |
model_name="gemini-1.5-pro",
|
| 246 |
+
generation_config={"temperature": 0.7, "top_p": 0.95, "max_output_tokens": 1000}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
)
|
| 248 |
+
system_prompt = f"""Analyze the following quote: "{quote}"
|
| 249 |
+
Provide a brief analysis of:
|
| 250 |
+
1. The overall mood and sentiment of the quote
|
| 251 |
+
2. The most appropriate typography styles for this quote
|
| 252 |
+
3. Suggested color palettes that would complement the meaning
|
| 253 |
+
4. Best type of decorative elements to include
|
| 254 |
+
Format your response as a concise paragraph for each point."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
try:
|
|
|
|
| 256 |
response = model.generate_content([system_prompt])
|
| 257 |
+
return response.text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 258 |
except Exception as e:
|
| 259 |
+
return f"Error analyzing quote: {str(e)}"
|
| 260 |
|
| 261 |
+
# MODIFIED Function to handle form submission
|
| 262 |
+
def process_quote(api_key, quote, num_prompts_from_slider, ui_style_preference_str, progress=gr.Progress()):
|
| 263 |
+
if not api_key: return "Please enter your Gemini API key.", "API key is required."
|
| 264 |
+
if not quote: return "Please enter a quote.", "A quote is required."
|
| 265 |
|
| 266 |
+
# Ensure num_prompts_from_slider is an int for the system prompt
|
| 267 |
+
try:
|
| 268 |
+
num_design_prompts = int(num_prompts_from_slider)
|
| 269 |
+
except ValueError:
|
| 270 |
+
return "Invalid number of prompts selected.", "Error with prompt count."
|
| 271 |
+
|
| 272 |
progress(0, desc="Initializing...")
|
| 273 |
+
time.sleep(0.1)
|
| 274 |
|
| 275 |
+
progress(0.2, desc=f"Generating {num_design_prompts} design prompts & marketing content...")
|
|
|
|
| 276 |
|
| 277 |
+
# Pass num_design_prompts to the generation function
|
| 278 |
+
raw_llm_output, error = generate_pod_structured_output(api_key, quote, num_design_prompts, ui_style_preference_str)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 279 |
|
| 280 |
if error:
|
| 281 |
+
return f"Error from API: {error}", f"Marketing insights could not be retrieved. Details: {error}"
|
| 282 |
+
if not raw_llm_output:
|
| 283 |
+
return "Received no content from API.", "Received no content for marketing insights from API."
|
| 284 |
+
|
| 285 |
+
progress(0.7, desc="Parsing API output...")
|
| 286 |
+
parsed_data = parse_structured_output(raw_llm_output)
|
|
|
|
| 287 |
|
| 288 |
+
market_info_text = (
|
| 289 |
+
f"{parsed_data['keywords_header']}\n{parsed_data['keywords_content']}\n\n"
|
| 290 |
+
f"{parsed_data['holidays_header']}\n{parsed_data['holidays_content']}\n\n"
|
| 291 |
+
f"{parsed_data['audience_header']}\n{parsed_data['audience_content']}"
|
| 292 |
+
)
|
| 293 |
|
| 294 |
+
prompts_text = f"{parsed_data['prompts_header']}\n{parsed_data['prompts_content']}"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 295 |
|
| 296 |
+
if "Not found or parsing error." in prompts_text and "Not found or parsing error." in market_info_text:
|
| 297 |
+
error_message = "Could not fully parse the API output. This might indicate an unexpected response format from the AI."
|
| 298 |
+
raw_output_preview = raw_llm_output[:1000] + "..." if len(raw_llm_output) > 1000 else raw_llm_output
|
| 299 |
+
prompts_text = error_message + "\n\nRaw API Output (preview):\n" + raw_output_preview
|
| 300 |
+
market_info_text = "See main output area for details on parsing failure."
|
| 301 |
|
| 302 |
progress(1.0, desc="Done!")
|
| 303 |
+
return prompts_text, market_info_text
|
| 304 |
|
|
|
|
| 305 |
example_quotes = [
|
| 306 |
+
"good vibes only", "dream big, sparkle more", "best mom ever",
|
| 307 |
+
"adventure awaits", "but first, coffee", "fueled by caffeine and chaos",
|
| 308 |
+
"stay wild moon child"
|
|
|
|
|
|
|
|
|
|
| 309 |
]
|
| 310 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 311 |
def create_demo():
|
| 312 |
with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
|
| 313 |
+
gr.Markdown("""
|
| 314 |
+
<div class="title"><h1>β¨ POD Marketing & Prompt Generator β¨</h1></div>
|
| 315 |
+
<div class="subtitle">Generate Amazon keywords, target audiences, niches, and design prompts for your quotes using Gemini 1.5 Pro</div>
|
| 316 |
+
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 317 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 318 |
with gr.Tabs() as tabs:
|
| 319 |
+
with gr.TabItem("Generate Content"):
|
| 320 |
+
with gr.Group(elem_classes="api-key-container"):
|
| 321 |
+
api_key_input = gr.Textbox(label="Gemini API Key", placeholder="Enter your Gemini API key here...", type="password", info="Your API key is not stored and only used for this session")
|
| 322 |
+
quote_input = gr.Textbox(label="Your Quote/Text", placeholder="Enter the quote you want to analyze...", info="Example: \"good vibes only\"", lines=2)
|
| 323 |
+
|
| 324 |
+
with gr.Group(elem_classes="style-options"):
|
| 325 |
+
gr.Markdown("### Style Preferences for Design Prompts (Section 4)")
|
| 326 |
+
with gr.Row():
|
| 327 |
+
# num_prompts_slider now directly controls the number of design ideas
|
| 328 |
+
num_prompts_slider = gr.Slider(
|
| 329 |
+
minimum=5, maximum=15, value=7, step=1, # Default to 7
|
| 330 |
+
label="Number of Design Ideas for Section 4",
|
| 331 |
+
info="Select how many design prompts to generate for Section 4."
|
| 332 |
+
)
|
| 333 |
+
style_preference_radio = gr.Radio(
|
| 334 |
+
["None (Let AI decide)", "Retro 70s", "Modern Minimal", "Hand-drawn", "Feminine/Girly", "Bold Experimental", "Silhouette", "Cartoon", "Vintage"],
|
| 335 |
+
label="Preferred Style for Design Prompts", value="None (Let AI decide)", info="Optional: Choose a preferred style for Section 4 design prompts."
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
submit_btn = gr.Button("Generate Marketing Content & Prompts", variant="primary", size="lg")
|
| 339 |
+
gr.Examples(examples=example_quotes, inputs=quote_input, label="Example Quotes")
|
| 340 |
+
|
| 341 |
+
prompts_output_textbox = gr.Textbox(label="π¨ Design Prompts (Section 4)", placeholder="Creative design prompts will appear here...", lines=15, elem_classes=["output-box"], show_copy_button=True)
|
| 342 |
+
marketing_insights_textbox = gr.Textbox(label="ππ
π― Marketing Insights (Sections 1-3)", placeholder="Amazon Keywords, Holidays/Niches, and Target Audience will appear here...", lines=15, elem_classes=["output-box", "market-box"], show_copy_button=True)
|
| 343 |
|
| 344 |
+
with gr.TabItem("Quote Analysis (Alternative)"):
|
| 345 |
+
with gr.Group(elem_classes="api-key-container"):
|
| 346 |
+
analysis_api_key = gr.Textbox(label="Gemini API Key", placeholder="Enter your Gemini API key here...", type="password", info="Your API key is not stored.")
|
| 347 |
+
analysis_quote_input = gr.Textbox(label="Your Quote/Text", placeholder="Enter a quote to analyze its mood, style, colors...", lines=2)
|
| 348 |
+
analyze_btn = gr.Button("Analyze Quote", variant="primary")
|
| 349 |
+
analysis_output_textbox = gr.Textbox(label="Quote Analysis Details", placeholder="Detailed analysis of mood, style, colors, elements will appear here...", lines=10, elem_classes=["output-box"])
|
| 350 |
+
|
| 351 |
+
with gr.TabItem("Help"):
|
| 352 |
+
gr.Markdown("""
|
| 353 |
+
### How to Use the POD Marketing & Prompt Generator
|
| 354 |
+
1. **Get a Gemini API Key**: Visit [Google AI Studio](https://makersuite.google.com/) and generate an API key.
|
| 355 |
+
2. **Enter Your API Key**: In the "Generate Content" or "Quote Analysis" tab.
|
| 356 |
+
3. **Enter Your Quote**: The text you want to base your T-shirt design and marketing on.
|
| 357 |
+
4. **Number of Design Ideas**: Use the slider to choose how many design prompts (5-15) you want for Section 4.
|
| 358 |
+
5. **Choose Style (Optional)**: Select a preferred visual style for the design prompts in Section 4. If "None" is selected, the AI will choose.
|
| 359 |
+
6. **Generate Content**: Click "Generate Marketing Content & Prompts".
|
| 360 |
+
|
| 361 |
+
### Output Sections (Generate Content Tab):
|
| 362 |
+
* **π 1. AMAZON SEARCH KEYWORDS**
|
| 363 |
+
* **π
2. HOLIDAYS / EVENTS / NICHES**
|
| 364 |
+
* **π― 3. TARGET AUDIENCE**
|
| 365 |
+
* **π¨ 4. PROMPTS ([Number Selected] Ideas) β STYLE: [AUTO or SELECTED STYLE]**
|
| 366 |
+
Sections 1, 2, and 3 appear in "Marketing Insights". Section 4 appears in "Design Prompts".
|
| 367 |
+
|
| 368 |
+
### Quote Analysis Tab
|
| 369 |
+
Offers analysis on: mood, general typography styles, color palettes, decorative elements.
|
| 370 |
+
""")
|
| 371 |
|
|
|
|
| 372 |
submit_btn.click(
|
| 373 |
+
fn=process_quote_with_style_mapping,
|
| 374 |
+
inputs=[api_key_input, quote_input, num_prompts_slider, style_preference_radio],
|
| 375 |
+
outputs=[prompts_output_textbox, marketing_insights_textbox]
|
| 376 |
)
|
| 377 |
+
analyze_btn.click(fn=analyze_quote, inputs=[analysis_api_key, analysis_quote_input], outputs=analysis_output_textbox)
|
| 378 |
+
|
| 379 |
+
gr.HTML("""
|
| 380 |
+
<script>
|
| 381 |
+
function syncApiKeys() {
|
| 382 |
+
const apiKeyInputs = document.querySelectorAll('input[type="password"]');
|
| 383 |
+
if (apiKeyInputs.length < 2) return;
|
| 384 |
+
const mainApiKeyInput = apiKeyInputs[0];
|
| 385 |
+
const otherApiKeyInputs = Array.from(apiKeyInputs).slice(1);
|
| 386 |
+
mainApiKeyInput.addEventListener('input', function(e) {
|
| 387 |
+
otherApiKeyInputs.forEach(otherElem => {
|
| 388 |
+
if (otherElem.value !== e.target.value) {
|
| 389 |
+
otherElem.value = e.target.value;
|
| 390 |
+
otherElem.dispatchEvent(new Event('input', { bubbles: true }));
|
| 391 |
+
}
|
| 392 |
+
});
|
| 393 |
+
});
|
| 394 |
+
otherApiKeyInputs.forEach(otherInput => {
|
| 395 |
+
otherInput.addEventListener('input', function(e) {
|
| 396 |
+
if (mainApiKeyInput.value !== e.target.value) {
|
| 397 |
+
mainApiKeyInput.value = e.target.value;
|
| 398 |
+
mainApiKeyInput.dispatchEvent(new Event('input', { bubbles: true }));
|
| 399 |
+
}
|
| 400 |
+
otherApiKeyInputs.forEach(syncTarget => {
|
| 401 |
+
if (syncTarget !== e.target && syncTarget.value !== e.target.value) {
|
| 402 |
+
syncTarget.value = e.target.value;
|
| 403 |
+
syncTarget.dispatchEvent(new Event('input', { bubbles: true }));
|
| 404 |
+
}
|
| 405 |
+
});
|
| 406 |
+
});
|
| 407 |
+
});
|
| 408 |
+
}
|
| 409 |
+
let attempts = 0;
|
| 410 |
+
function trySync() {
|
| 411 |
+
if (document.querySelectorAll('input[type="password"]').length > 1) {
|
| 412 |
+
syncApiKeys();
|
| 413 |
+
} else if (attempts < 10) {
|
| 414 |
+
attempts++;
|
| 415 |
+
setTimeout(trySync, 500);
|
| 416 |
+
}
|
| 417 |
+
}
|
| 418 |
+
if (typeof NProgress !== 'undefined') {
|
| 419 |
+
const intervalId = setInterval(() => {
|
| 420 |
+
if (!NProgress.isStarted()) {
|
| 421 |
+
clearInterval(intervalId);
|
| 422 |
+
trySync();
|
| 423 |
+
}
|
| 424 |
+
}, 100);
|
| 425 |
+
} else {
|
| 426 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 427 |
+
setTimeout(trySync, 1000);
|
| 428 |
+
});
|
| 429 |
+
}
|
| 430 |
+
</script>
|
| 431 |
+
""", visible=False)
|
| 432 |
+
gr.Markdown("<footer>POD Marketing & Prompt Generator | Powered by Gemini AI</footer>")
|
| 433 |
return demo
|
| 434 |
|
| 435 |
+
def process_quote_with_style_mapping(api_key, quote, num_prompts, ui_style_preference_str, progress=gr.Progress()):
|
| 436 |
+
return process_quote(api_key, quote, num_prompts, ui_style_preference_str, progress)
|
| 437 |
+
|
| 438 |
if __name__ == "__main__":
|
| 439 |
demo = create_demo()
|
| 440 |
demo.launch()
|