|
|
import gradio as gr |
|
|
import google.generativeai as genai |
|
|
import os |
|
|
import json |
|
|
import re |
|
|
import time |
|
|
import random |
|
|
|
|
|
|
|
|
css = """ |
|
|
body { |
|
|
font-family: 'Poppins', -apple-system, BlinkMacSystemFont, sans-serif; |
|
|
#background-color: #fafafa; |
|
|
} |
|
|
.container { |
|
|
max-width: 900px; |
|
|
margin: auto; |
|
|
} |
|
|
.title { |
|
|
text-align: center; |
|
|
margin-bottom: 0.5em; |
|
|
color: #333; |
|
|
font-weight: 800; |
|
|
} |
|
|
.subtitle { |
|
|
text-align: center; |
|
|
margin-bottom: 2em; |
|
|
color: #555; |
|
|
font-style: italic; |
|
|
} |
|
|
.output-box { |
|
|
padding: 20px; |
|
|
margin-top: 15px; |
|
|
font-family: 'Courier New', monospace; |
|
|
white-space: pre-wrap; |
|
|
} |
|
|
footer { |
|
|
text-align: center; |
|
|
margin-top: 2em; |
|
|
padding: 15px; |
|
|
font-size: 0.8em; |
|
|
color: #888; |
|
|
border-top: 1px solid #eee; |
|
|
} |
|
|
.api-key-container { |
|
|
padding: 20px; |
|
|
margin-bottom: 25px; |
|
|
} |
|
|
.prompt-container { |
|
|
margin-bottom: 20px; |
|
|
border-left: 4px solid #e94560; |
|
|
padding-left: 15px; |
|
|
} |
|
|
.gr-button-primary { |
|
|
background-color: #e94560 !important; |
|
|
border: none !important; |
|
|
} |
|
|
.gr-button-primary:hover { |
|
|
background-color: #d63d56 !important; |
|
|
} |
|
|
.copy-btn { |
|
|
margin-top: 10px; |
|
|
text-align: right; |
|
|
} |
|
|
.examples-container { |
|
|
margin-top: 15px; |
|
|
padding: 10px; |
|
|
border-radius: 8px; |
|
|
background-color: #f0f0f0; |
|
|
} |
|
|
.tab-content { |
|
|
padding: 20px; |
|
|
background-color: #fff; |
|
|
} |
|
|
.logo { |
|
|
text-align: center; |
|
|
margin: 20px auto; |
|
|
} |
|
|
.logo img { |
|
|
max-width: 80px; |
|
|
height: auto; |
|
|
} |
|
|
.tabs-container { |
|
|
margin-top: 20px; |
|
|
} |
|
|
.style-options { |
|
|
margin-top: 15px; |
|
|
} |
|
|
.market-box { |
|
|
margin-top: 15px; |
|
|
padding: 15px; |
|
|
border: 1px solid #ddd; |
|
|
border-radius: 8px; |
|
|
background-color: #f9f9f9; |
|
|
} |
|
|
.keyword-list { |
|
|
margin-top: 5px; |
|
|
font-family: 'Courier New', monospace; |
|
|
} |
|
|
""" |
|
|
|
|
|
|
|
|
def generate_pod_structured_output(api_key, quote, num_design_prompts, ui_selected_style_name="None (Let AI decide)"): |
|
|
genai.configure(api_key=api_key) |
|
|
|
|
|
model = genai.GenerativeModel( |
|
|
model_name="gemini-1.5-pro", |
|
|
generation_config={ |
|
|
"temperature": 0.85, |
|
|
"top_p": 0.95, |
|
|
"top_k": 40, |
|
|
"max_output_tokens": 3072, |
|
|
} |
|
|
) |
|
|
|
|
|
style_for_header_tag = "AUTO" |
|
|
style_guidance_for_llm = "AI chooses the best-fitting style automatically" |
|
|
|
|
|
if ui_selected_style_name and ui_selected_style_name != "None (Let AI decide)": |
|
|
style_for_header_tag = ui_selected_style_name |
|
|
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." |
|
|
else: |
|
|
style_guidance_for_llm = "The user has left the style choice empty. AI should choose the best-fitting style automatically for Section 4." |
|
|
|
|
|
|
|
|
system_prompt = f"""You are a smart Print-on-Demand (POD) marketing assistant. |
|
|
Your task is to analyze the quote "{quote}" and return FOUR structured sections that help with marketing, audience targeting, and design execution. |
|
|
|
|
|
The system has an OPTIONAL STYLES section. |
|
|
{style_guidance_for_llm} |
|
|
--- |
|
|
Return only the following four sections: |
|
|
|
|
|
π 1. AMAZON SEARCH KEYWORDS |
|
|
Generate a comma-separated list of 15-25 SEO-rich keywords relevant to the quote "{quote}". |
|
|
- Mix of broad and niche-specific terms |
|
|
- Focus on what real buyers would search for on Amazon |
|
|
Example: dog lover shirt, funny pet shirt, dog humor tee, running with dog |
|
|
|
|
|
--- |
|
|
π
2. HOLIDAYS / EVENTS / NICHES |
|
|
Identify all relevant holidays, occasions, events, and niche categories the quote "{quote}" fits into. (Provide at least 3-5 items) |
|
|
- Be specific and include both evergreen and seasonal contexts |
|
|
Example: Dog Lovers, Pet Adoption Month, National Dog Day, Funny Workout Shirts |
|
|
|
|
|
--- |
|
|
π― 3. TARGET AUDIENCE |
|
|
Describe the ideal customer this quote/design ("{quote}") would appeal to. |
|
|
- Include: age range, gender, interests, lifestyle traits, tone preference |
|
|
Example: Adults 25β45, pet lovers, dog moms, people with sarcastic or playful humor, casual wearers |
|
|
|
|
|
--- |
|
|
π¨ 4. PROMPTS ({num_design_prompts} Ideas) β STYLE: [{style_for_header_tag}] |
|
|
Generate exactly {num_design_prompts} creative design prompts for the quote: "{quote}", based on: |
|
|
- Tone and meaning of the quote |
|
|
- 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) |
|
|
- Each prompt should include layout, typography style, design elements, and color ideas. |
|
|
If a style was provided by the user, apply it consistently. |
|
|
If no style was provided (AUTO), analyze the quote and recommend and apply a fitting visual style. |
|
|
|
|
|
Example (If User Selected Style = Silhouette and num_design_prompts = 1): |
|
|
"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." |
|
|
|
|
|
Example (If Style = AUTO, AI chose Retro, and num_design_prompts = 1): |
|
|
"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." |
|
|
--- |
|
|
β οΈ Output Format Guidelines: |
|
|
- Use headers exactly as shown (including the emojis, numbering, and the dynamic prompt count in Section 4 header). |
|
|
- For Section 4, number each design prompt idea starting from 1. |
|
|
- Do NOT restate the provided quote "{quote}" unless it's within the design prompt itself where it's part of the design concept. |
|
|
- Do NOT explain your reasoning outside the requested sections. |
|
|
- Keep your output clean and implementation-ready, directly providing the content under each header. |
|
|
- Ensure each section is clearly delineated by the headers and newlines. |
|
|
""" |
|
|
print("--- SYSTEM PROMPT BEING SENT TO API (First 500 chars) ---") |
|
|
print(system_prompt[:500] + "...") |
|
|
print("--- END OF SYSTEM PROMPT PREVIEW ---") |
|
|
|
|
|
try: |
|
|
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}'") |
|
|
response = model.generate_content([system_prompt]) |
|
|
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] Received response from API.") |
|
|
return response.text, None |
|
|
except Exception as e: |
|
|
error_message = f"Error during API call: {str(e)}" |
|
|
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {error_message}") |
|
|
if hasattr(e, 'message'): |
|
|
error_message = f"API Error: {e.message}" |
|
|
return None, error_message |
|
|
|
|
|
|
|
|
|
|
|
def parse_structured_output(text_output): |
|
|
data = { |
|
|
"keywords_header": "π 1. AMAZON SEARCH KEYWORDS", |
|
|
"keywords_content": "Not found or parsing error.", |
|
|
"holidays_header": "π
2. HOLIDAYS / EVENTS / NICHES", |
|
|
"holidays_content": "Not found or parsing error.", |
|
|
"audience_header": "π― 3. TARGET AUDIENCE", |
|
|
"audience_content": "Not found or parsing error.", |
|
|
"prompts_header": "π¨ 4. PROMPTS β STYLE: N/A", |
|
|
"prompts_content": "Not found or parsing error." |
|
|
} |
|
|
|
|
|
kw_match = re.search(r"(π 1\. AMAZON SEARCH KEYWORDS)\s*\n(.*?)(?=\n\nπ
2\. HOLIDAYS / EVENTS / NICHES|\Z)", text_output, re.DOTALL) |
|
|
if kw_match: |
|
|
data["keywords_header"] = kw_match.group(1).strip() |
|
|
data["keywords_content"] = kw_match.group(2).strip() |
|
|
|
|
|
hol_match = re.search(r"(π
2\. HOLIDAYS / EVENTS / NICHES)\s*\n(.*?)(?=\n\nπ― 3\. TARGET AUDIENCE|\Z)", text_output, re.DOTALL) |
|
|
if hol_match: |
|
|
data["holidays_header"] = hol_match.group(1).strip() |
|
|
data["holidays_content"] = hol_match.group(2).strip() |
|
|
|
|
|
|
|
|
aud_match = re.search(r"(π― 3\. TARGET AUDIENCE)\s*\n(.*?)(?=\n\nπ¨ 4\. PROMPTS \(\d+ Ideas?\) β STYLE:|\Z)", text_output, re.DOTALL) |
|
|
if aud_match: |
|
|
data["audience_header"] = aud_match.group(1).strip() |
|
|
data["audience_content"] = aud_match.group(2).strip() |
|
|
|
|
|
|
|
|
|
|
|
prompt_header_pattern_match = re.search(r"(π¨ 4\. PROMPTS \(\d+\s*Ideas?\) β STYLE:.*?)\s*\n(.*?)\Z", text_output, re.DOTALL) |
|
|
if prompt_header_pattern_match: |
|
|
data["prompts_header"] = prompt_header_pattern_match.group(1).strip() |
|
|
data["prompts_content"] = prompt_header_pattern_match.group(2).strip() |
|
|
else: |
|
|
|
|
|
prompt_fallback_match = re.search(r"(π¨ 4\. PROMPTS β STYLE:.*?)\s*\n(.*?)\Z", text_output, re.DOTALL) |
|
|
if prompt_fallback_match: |
|
|
data["prompts_header"] = prompt_fallback_match.group(1).strip() |
|
|
data["prompts_content"] = prompt_fallback_match.group(2).strip() |
|
|
print("Warning: Parsed prompts using fallback regex. Header might not exactly match '(N Ideas)'.") |
|
|
|
|
|
return data |
|
|
|
|
|
|
|
|
|
|
|
def analyze_quote(api_key, quote, progress=gr.Progress()): |
|
|
if not api_key: return "Please enter your Gemini API key for analysis." |
|
|
if not quote: return "Please enter a quote to analyze." |
|
|
|
|
|
genai.configure(api_key=api_key) |
|
|
model = genai.GenerativeModel( |
|
|
model_name="gemini-1.5-pro", |
|
|
generation_config={"temperature": 0.7, "top_p": 0.95, "max_output_tokens": 1000} |
|
|
) |
|
|
system_prompt = f"""Analyze the following quote: "{quote}" |
|
|
Provide a brief analysis of: |
|
|
1. The overall mood and sentiment of the quote |
|
|
2. The most appropriate typography styles for this quote |
|
|
3. Suggested color palettes that would complement the meaning |
|
|
4. Best type of decorative elements to include |
|
|
Format your response as a concise paragraph for each point.""" |
|
|
try: |
|
|
response = model.generate_content([system_prompt]) |
|
|
return response.text |
|
|
except Exception as e: |
|
|
return f"Error analyzing quote: {str(e)}" |
|
|
|
|
|
|
|
|
def process_quote(api_key, quote, num_prompts_from_slider, ui_style_preference_str, progress=gr.Progress()): |
|
|
if not api_key: return "Please enter your Gemini API key.", "API key is required." |
|
|
if not quote: return "Please enter a quote.", "A quote is required." |
|
|
|
|
|
|
|
|
try: |
|
|
num_design_prompts = int(num_prompts_from_slider) |
|
|
except ValueError: |
|
|
return "Invalid number of prompts selected.", "Error with prompt count." |
|
|
|
|
|
progress(0, desc="Initializing...") |
|
|
time.sleep(0.1) |
|
|
|
|
|
progress(0.2, desc=f"Generating {num_design_prompts} design prompts & marketing content...") |
|
|
|
|
|
|
|
|
raw_llm_output, error = generate_pod_structured_output(api_key, quote, num_design_prompts, ui_style_preference_str) |
|
|
|
|
|
if error: |
|
|
return f"Error from API: {error}", f"Marketing insights could not be retrieved. Details: {error}" |
|
|
if not raw_llm_output: |
|
|
return "Received no content from API.", "Received no content for marketing insights from API." |
|
|
|
|
|
progress(0.7, desc="Parsing API output...") |
|
|
parsed_data = parse_structured_output(raw_llm_output) |
|
|
|
|
|
market_info_text = ( |
|
|
f"{parsed_data['keywords_header']}\n{parsed_data['keywords_content']}\n\n" |
|
|
f"{parsed_data['holidays_header']}\n{parsed_data['holidays_content']}\n\n" |
|
|
f"{parsed_data['audience_header']}\n{parsed_data['audience_content']}" |
|
|
) |
|
|
|
|
|
prompts_text = f"{parsed_data['prompts_header']}\n{parsed_data['prompts_content']}" |
|
|
|
|
|
if "Not found or parsing error." in prompts_text and "Not found or parsing error." in market_info_text: |
|
|
error_message = "Could not fully parse the API output. This might indicate an unexpected response format from the AI." |
|
|
raw_output_preview = raw_llm_output[:1000] + "..." if len(raw_llm_output) > 1000 else raw_llm_output |
|
|
prompts_text = error_message + "\n\nRaw API Output (preview):\n" + raw_output_preview |
|
|
market_info_text = "See main output area for details on parsing failure." |
|
|
|
|
|
progress(1.0, desc="Done!") |
|
|
return prompts_text, market_info_text |
|
|
|
|
|
example_quotes = [ |
|
|
"good vibes only", "dream big, sparkle more", "best mom ever", |
|
|
"adventure awaits", "but first, coffee", "fueled by caffeine and chaos", |
|
|
"stay wild moon child" |
|
|
] |
|
|
|
|
|
def create_demo(): |
|
|
with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo: |
|
|
gr.Markdown(""" |
|
|
<div class="title"><h1>β¨ POD Marketing & Prompt Generator β¨</h1></div> |
|
|
<div class="subtitle">Generate Amazon keywords, target audiences, niches, and design prompts for your quotes using Gemini 1.5 Pro</div> |
|
|
""") |
|
|
|
|
|
with gr.Tabs() as tabs: |
|
|
with gr.TabItem("Generate Content"): |
|
|
with gr.Group(elem_classes="api-key-container"): |
|
|
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") |
|
|
quote_input = gr.Textbox(label="Your Quote/Text", placeholder="Enter the quote you want to analyze...", info="Example: \"good vibes only\"", lines=2) |
|
|
|
|
|
with gr.Group(elem_classes="style-options"): |
|
|
gr.Markdown("### Style Preferences for Design Prompts (Section 4)") |
|
|
with gr.Row(): |
|
|
|
|
|
num_prompts_slider = gr.Slider( |
|
|
minimum=5, maximum=15, value=7, step=1, |
|
|
label="Number of Design Ideas for Section 4", |
|
|
info="Select how many design prompts to generate for Section 4." |
|
|
) |
|
|
style_preference_radio = gr.Radio( |
|
|
["None (Let AI decide)", "Retro 70s", "Modern Minimal", "Hand-drawn", "Feminine/Girly", "Bold Experimental", "Silhouette", "Cartoon", "Vintage"], |
|
|
label="Preferred Style for Design Prompts", value="None (Let AI decide)", info="Optional: Choose a preferred style for Section 4 design prompts." |
|
|
) |
|
|
|
|
|
submit_btn = gr.Button("Generate Marketing Content & Prompts", variant="primary", size="lg") |
|
|
gr.Examples(examples=example_quotes, inputs=quote_input, label="Example Quotes") |
|
|
|
|
|
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) |
|
|
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) |
|
|
|
|
|
with gr.TabItem("Quote Analysis (Alternative)"): |
|
|
with gr.Group(elem_classes="api-key-container"): |
|
|
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.") |
|
|
analysis_quote_input = gr.Textbox(label="Your Quote/Text", placeholder="Enter a quote to analyze its mood, style, colors...", lines=2) |
|
|
analyze_btn = gr.Button("Analyze Quote", variant="primary") |
|
|
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"]) |
|
|
|
|
|
with gr.TabItem("Help"): |
|
|
gr.Markdown(""" |
|
|
### How to Use the POD Marketing & Prompt Generator |
|
|
1. **Get a Gemini API Key**: Visit [Google AI Studio](https://makersuite.google.com/) and generate an API key. |
|
|
2. **Enter Your API Key**: In the "Generate Content" or "Quote Analysis" tab. |
|
|
3. **Enter Your Quote**: The text you want to base your T-shirt design and marketing on. |
|
|
4. **Number of Design Ideas**: Use the slider to choose how many design prompts (5-15) you want for Section 4. |
|
|
5. **Choose Style (Optional)**: Select a preferred visual style for the design prompts in Section 4. If "None" is selected, the AI will choose. |
|
|
6. **Generate Content**: Click "Generate Marketing Content & Prompts". |
|
|
|
|
|
### Output Sections (Generate Content Tab): |
|
|
* **π 1. AMAZON SEARCH KEYWORDS** |
|
|
* **π
2. HOLIDAYS / EVENTS / NICHES** |
|
|
* **π― 3. TARGET AUDIENCE** |
|
|
* **π¨ 4. PROMPTS ([Number Selected] Ideas) β STYLE: [AUTO or SELECTED STYLE]** |
|
|
Sections 1, 2, and 3 appear in "Marketing Insights". Section 4 appears in "Design Prompts". |
|
|
|
|
|
### Quote Analysis Tab |
|
|
Offers analysis on: mood, general typography styles, color palettes, decorative elements. |
|
|
""") |
|
|
|
|
|
submit_btn.click( |
|
|
fn=process_quote_with_style_mapping, |
|
|
inputs=[api_key_input, quote_input, num_prompts_slider, style_preference_radio], |
|
|
outputs=[prompts_output_textbox, marketing_insights_textbox] |
|
|
) |
|
|
analyze_btn.click(fn=analyze_quote, inputs=[analysis_api_key, analysis_quote_input], outputs=analysis_output_textbox) |
|
|
|
|
|
gr.HTML(""" |
|
|
<script> |
|
|
function syncApiKeys() { |
|
|
const apiKeyInputs = document.querySelectorAll('input[type="password"]'); |
|
|
if (apiKeyInputs.length < 2) return; |
|
|
const mainApiKeyInput = apiKeyInputs[0]; |
|
|
const otherApiKeyInputs = Array.from(apiKeyInputs).slice(1); |
|
|
mainApiKeyInput.addEventListener('input', function(e) { |
|
|
otherApiKeyInputs.forEach(otherElem => { |
|
|
if (otherElem.value !== e.target.value) { |
|
|
otherElem.value = e.target.value; |
|
|
otherElem.dispatchEvent(new Event('input', { bubbles: true })); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
otherApiKeyInputs.forEach(otherInput => { |
|
|
otherInput.addEventListener('input', function(e) { |
|
|
if (mainApiKeyInput.value !== e.target.value) { |
|
|
mainApiKeyInput.value = e.target.value; |
|
|
mainApiKeyInput.dispatchEvent(new Event('input', { bubbles: true })); |
|
|
} |
|
|
otherApiKeyInputs.forEach(syncTarget => { |
|
|
if (syncTarget !== e.target && syncTarget.value !== e.target.value) { |
|
|
syncTarget.value = e.target.value; |
|
|
syncTarget.dispatchEvent(new Event('input', { bubbles: true })); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
let attempts = 0; |
|
|
function trySync() { |
|
|
if (document.querySelectorAll('input[type="password"]').length > 1) { |
|
|
syncApiKeys(); |
|
|
} else if (attempts < 10) { |
|
|
attempts++; |
|
|
setTimeout(trySync, 500); |
|
|
} |
|
|
} |
|
|
if (typeof NProgress !== 'undefined') { |
|
|
const intervalId = setInterval(() => { |
|
|
if (!NProgress.isStarted()) { |
|
|
clearInterval(intervalId); |
|
|
trySync(); |
|
|
} |
|
|
}, 100); |
|
|
} else { |
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
setTimeout(trySync, 1000); |
|
|
}); |
|
|
} |
|
|
</script> |
|
|
""", visible=False) |
|
|
gr.Markdown("<footer>POD Marketing & Prompt Generator | Powered by Gemini AI</footer>") |
|
|
return demo |
|
|
|
|
|
def process_quote_with_style_mapping(api_key, quote, num_prompts, ui_style_preference_str, progress=gr.Progress()): |
|
|
return process_quote(api_key, quote, num_prompts, ui_style_preference_str, progress) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo = create_demo() |
|
|
demo.launch() |