|
from dotenv import load_dotenv |
|
import streamlit as st |
|
import os |
|
import google.generativeai as genai |
|
import random |
|
from streamlit import session_state as state |
|
from formulas import email_formulas |
|
from angles import angles |
|
import re |
|
import datetime |
|
import PyPDF2 |
|
import docx |
|
from PIL import Image |
|
|
|
|
|
load_dotenv() |
|
|
|
|
|
genai.configure(api_key=os.getenv("GOOGLE_API_KEY")) |
|
|
|
|
|
|
|
|
|
|
|
def generate_emails(target_audience, product, temperature, selected_formula, selected_angle, file_content="", image_parts=None, is_image=False, emotion="", desired_action="", creative_idea="", brand_tone=""): |
|
|
|
generation_config = { |
|
"temperature": temperature, |
|
"top_p": 0.65, |
|
"top_k": 360, |
|
"max_output_tokens": 8196, |
|
} |
|
|
|
model = genai.GenerativeModel( |
|
model_name="gemini-2.0-flash", |
|
generation_config=generation_config, |
|
) |
|
|
|
|
|
format_instructions = """ |
|
FORMAT RULES: |
|
- Each email must start with "Correo 1", "Correo 2", etc. |
|
- Each email must have a clear and attractive subject line |
|
- The email body must be persuasive and emotional |
|
- Include a clear call to action |
|
- Add a professional signature |
|
- Separate each email clearly |
|
- Do not include greetings like 'Hello [Name]' |
|
- Make sure that the postscripts (P.D.) are smaller and more discrete than the main body |
|
- IMPORTANT: The first email must be an introduction without referencing any previous communications |
|
- Emails 2-5 should build on the sequence in a logical progression |
|
""" |
|
|
|
|
|
system_prompt = f"""You are a world-class direct response copywriter trained by Gary Halbert, Gary Bencivenga, and David Ogilvy. |
|
|
|
You understand how real people interact with emails in their busy inboxes: |
|
- They quickly scan and delete anything that looks like generic marketing |
|
- They only open emails that feel personal or spark genuine curiosity |
|
- They respond to messages that seem written by a real person, not a corporation |
|
- They engage with content that hooks them from the first line and maintains interest |
|
|
|
Your task is to create a 5-email sequence that makes {target_audience} feel {'' if emotion == 'NINGUNO' else emotion + ' '}about {product} and convince them to {desired_action}. |
|
|
|
AUDIENCE-SPECIFIC LANGUAGE ADAPTATION: |
|
- Analyze the language patterns typical of {target_audience} and mirror them in your writing |
|
- Adjust formality level based on audience demographics (more formal for professionals/executives, more casual for younger audiences) |
|
- Use vocabulary, references, and examples that will resonate specifically with {target_audience} |
|
- Match the communication style preferences of this audience (direct vs indirect, detailed vs concise) |
|
- Include cultural touchpoints or shared experiences relevant to this specific audience |
|
""" |
|
|
|
|
|
if brand_tone != "NINGUNO": |
|
system_prompt += f""" |
|
BRAND TONE: {brand_tone} |
|
- Write all emails using a consistent {brand_tone} tone of voice |
|
- Ensure language, expressions, and style reflect this tone throughout |
|
- Maintain this tone while still being persuasive and effective |
|
""" |
|
|
|
system_prompt += f""" |
|
Each email must follow these four essential actions: |
|
1. ATTRACT with a subject line that feels personal and creates curiosity (never screaming "Buy me!") |
|
2. CONNECT through rhythm, emotion, and storytelling that maintains interest until the end |
|
3. CONVINCE with authentic language that feels believable and relevant |
|
4. GUIDE to action naturally, making readers click almost without realizing it |
|
|
|
WRITING STYLE: |
|
- Write as if speaking to one person, not an audience |
|
- Vary sentence length deliberately (mix short punchy sentences with occasional longer ones) |
|
- Create natural paragraph rhythm (1-3 sentences per paragraph) |
|
- Use conversational transitions between ideas |
|
- Include specific details that make scenarios feel real |
|
|
|
{format_instructions} |
|
|
|
IMPORTANT: |
|
- Each email must be unique and memorable |
|
- Avoid clichés and generalities |
|
- Maintain a persuasive but credible tone |
|
- Adapt language to the target audience |
|
- Focus on transformative benefits""" |
|
|
|
|
|
email_instruction = f"{system_prompt}\n\n" |
|
|
|
|
|
if hasattr(selected_formula, 'get') and selected_formula.get('subject_line_types') and selected_formula.get('email_structure_template'): |
|
email_instruction += """ |
|
ENHANCED FORMULA STRUCTURE: |
|
Use the following structure elements to create more effective emails: |
|
|
|
SUBJECT LINE TYPES: |
|
""" |
|
|
|
for subject_type, description in selected_formula.get('subject_line_types', {}).items(): |
|
email_instruction += f"- {subject_type}: {description}\n" |
|
|
|
|
|
technique_key = next((k for k in selected_formula.keys() if k.endswith('_techniques')), None) |
|
if technique_key: |
|
email_instruction += f"\n{technique_key.upper()}:\n" |
|
for technique, description in selected_formula.get(technique_key, {}).items(): |
|
email_instruction += f"- {technique}: {description}\n" |
|
|
|
|
|
email_instruction += "\nEMAIL STRUCTURE TEMPLATE:\n" |
|
for email_num, structure in selected_formula.get('email_structure_template', {}).items(): |
|
email_instruction += f"\n{email_num.upper()}:\n" |
|
email_instruction += f"- Purpose: {structure.get('purpose', '')}\n" |
|
email_instruction += f"- Emotional Focus: {structure.get('emotional_focus', '')}\n" |
|
|
|
|
|
if structure.get('recommended_subject_types'): |
|
email_instruction += "- Recommended Subject Types: " + ", ".join(structure.get('recommended_subject_types', [])) + "\n" |
|
|
|
|
|
if structure.get('recommended_techniques'): |
|
email_instruction += "- Recommended Techniques: " + ", ".join(structure.get('recommended_techniques', [])) + "\n" |
|
|
|
|
|
if structure.get('key_elements'): |
|
email_instruction += "- Key Elements:\n" |
|
for element in structure.get('key_elements', []): |
|
email_instruction += f" * {element}\n" |
|
|
|
|
|
if creative_idea: |
|
email_instruction += f""" |
|
CREATIVE CONCEPT: |
|
Use the following creative concept as the central theme for all emails in the sequence: |
|
"{creative_idea}" |
|
|
|
CREATIVE CONCEPT INSTRUCTIONS: |
|
1. This concept should be the unifying theme across all emails |
|
2. Use it as a metaphor or analogy throughout the sequence |
|
3. Develop different aspects of this concept in each email |
|
4. Make sure the concept naturally connects to the product benefits |
|
5. The concept should make the emails more memorable and engaging |
|
""" |
|
|
|
|
|
if file_content: |
|
email_instruction += f""" |
|
REFERENCE CONTENT: |
|
Carefully analyze the following content as a reference for generating emails: |
|
{file_content[:3000]} |
|
|
|
ANALYSIS INSTRUCTIONS: |
|
1. Extract key information about the product or service mentioned |
|
2. Identify the tone, style, and language used |
|
3. Detect any data about the target audience or customer avatar |
|
4. Look for benefits, features, or pain points mentioned |
|
5. Use relevant terms, phrases, or concepts from the content |
|
6. Maintain consistency with the brand identity or main message |
|
7. Adapt the emails to resonate with the provided content |
|
|
|
IMPORTANT COMBINATIONS: |
|
""" |
|
|
|
if product and not target_audience: |
|
email_instruction += f"""- FILE + PRODUCT: You have a reference document and product ({product}). Create emails that highlight this specific product's benefits and features using insights from the document. Extract audience information from the document to better target the emails. |
|
""" |
|
elif target_audience and not product: |
|
email_instruction += f"""- FILE + TARGET AUDIENCE: You have a reference document and target audience ({target_audience}). Create emails tailored to this specific audience using language and concepts from the document. Identify products or services from the document that would appeal to this audience. |
|
""" |
|
elif product and target_audience: |
|
email_instruction += f"""- PRODUCT + TARGET AUDIENCE: You have both product ({product}) and target audience ({target_audience}). Create emails that connect this specific product with this specific audience, using insights from the document to strengthen the connection. |
|
""" |
|
|
|
email_instruction += """ |
|
IMPORTANT: Naturally integrate the elements found in the content with the selected formula and angle. |
|
""" |
|
|
|
|
|
angle_instructions = "" |
|
if selected_angle != "NINGUNO": |
|
angle_instructions = f""" |
|
MAIN ANGLE: {selected_angle} |
|
SPECIFIC ANGLE INSTRUCTIONS: |
|
{angles[selected_angle]["instruction"]} |
|
|
|
IMPORTANT: The angle {selected_angle} must be applied as a "style layer" over the formula structure: |
|
1. Keep the base structure of the formula intact |
|
2. Apply the tone and style of the {selected_angle} angle |
|
3. Ensure each element of the formula reflects the angle |
|
4. The angle affects "how" it's said, not "what" is said |
|
|
|
SUCCESSFUL EXAMPLES OF THE {selected_angle} ANGLE: |
|
""" |
|
for example in angles[selected_angle]["examples"]: |
|
angle_instructions += f"- {example}\n" |
|
|
|
|
|
email_instruction += angle_instructions |
|
|
|
|
|
email_instruction += ( |
|
f"\nYour task is to create 5 persuasive emails for {target_audience} " |
|
f"that evoke {emotion} and convince them to {desired_action} about {product}. " |
|
) |
|
|
|
|
|
if angle_instructions: |
|
email_instruction += f"IMPORTANT: Each email MUST follow the {selected_angle} angle clearly and consistently." |
|
|
|
email_instruction += "\n\n" |
|
|
|
|
|
sequential_examples = selected_formula['examples'][:min(5, len(selected_formula['examples']))] |
|
|
|
email_instruction += "FORMULA EXAMPLES TO FOLLOW:\n" |
|
for i, example in enumerate(sequential_examples, 1): |
|
email_instruction += f"{i}. {example}\n" |
|
|
|
|
|
email_instruction += "\nSPECIFIC INSTRUCTIONS:\n" |
|
email_instruction += "1. Maintain the same structure and length as the previous examples\n" |
|
email_instruction += "2. Use the same tone and writing style\n" |
|
email_instruction += "3. Replicate the phrase construction patterns\n" |
|
email_instruction += "4. Preserve the level of specificity and detail\n" |
|
email_instruction += f"5. Adapt the content for {target_audience} while maintaining the essence of the examples\n\n" |
|
|
|
|
|
email_instruction += f"\nCREATIVITY LEVEL: {temperature}. Higher values mean more creative and original content.\n\n" |
|
|
|
email_instruction += f"FORMULA TO FOLLOW:\n{selected_formula['description']}\n\n" |
|
|
|
|
|
final_reminder = ["Follow the structure of the selected formula"] |
|
|
|
|
|
if selected_angle != "NINGUNO": |
|
final_reminder.extend([ |
|
"Apply the angle as a 'style layer'", |
|
"Maintain coherence between formula and angle", |
|
"Ensure each email reflects both elements" |
|
]) |
|
|
|
email_instruction += "\nFINAL REMINDER:\n" |
|
for i, reminder in enumerate(final_reminder, 1): |
|
email_instruction += f"{i}. {reminder}\n" |
|
|
|
email_instruction += "\nGENERATE NOW:\nCreate 5 emails that faithfully follow the style and structure of the examples shown.\n" |
|
|
|
|
|
message_parts = [email_instruction] |
|
|
|
|
|
instruction_components = ["Generate the emails in Spanish following exactly the style and structure of the examples shown."] |
|
|
|
|
|
if is_image and image_parts: |
|
message_parts.append(image_parts) |
|
instruction_components.append("drawing inspiration from the provided image.") |
|
|
|
|
|
instruction_components.append("Do not include explanations, only the emails.") |
|
|
|
|
|
instruction_text = " ".join(instruction_components) |
|
|
|
|
|
try: |
|
chat_session = model.start_chat( |
|
history=[ |
|
{ |
|
"role": "user", |
|
"parts": message_parts, |
|
}, |
|
] |
|
) |
|
|
|
|
|
response = chat_session.send_message(instruction_text) |
|
|
|
return response.text |
|
except genai.types.generation_types.StopCandidateException as e: |
|
|
|
error_message = f"La generación se detuvo debido a restricciones de contenido: {str(e)}" |
|
st.error(error_message) |
|
return f"Error: {error_message}" |
|
except genai.types.generation_types.BlockedPromptException as e: |
|
|
|
error_message = f"El prompt fue bloqueado por políticas de contenido: {str(e)}" |
|
st.error(error_message) |
|
return f"Error: {error_message}" |
|
except Exception as e: |
|
|
|
error_message = f"Error al comunicarse con la API de Google: {str(e)}" |
|
st.error(error_message) |
|
return f"Error: {error_message}" |
|
|
|
|
|
def clean_response_text(text): |
|
"""Remove extra spaces and normalize whitespace in the response text""" |
|
|
|
text = re.sub(r'\n{3,}', '\n\n', text) |
|
|
|
text = text.strip() |
|
return text |
|
|
|
|
|
st.set_page_config( |
|
page_title="Email Composer", |
|
layout="wide", |
|
menu_items={}, |
|
initial_sidebar_state="expanded" |
|
) |
|
|
|
|
|
st.markdown(""" |
|
<style> |
|
.main > div { |
|
padding-top: 1rem; |
|
} |
|
</style> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
manual_path = "manual.md" |
|
if os.path.exists(manual_path): |
|
with open(manual_path, "r", encoding="utf-8") as file: |
|
manual_content = file.read() |
|
|
|
st.sidebar.markdown(manual_content) |
|
else: |
|
st.sidebar.warning("Manual file not found.") |
|
|
|
|
|
css_path = "styles/main.css" |
|
if os.path.exists(css_path): |
|
with open(css_path, "r") as f: |
|
css = f.read() |
|
|
|
st.markdown(f"<style>{css}</style>", unsafe_allow_html=True) |
|
else: |
|
st.warning("CSS file not found.") |
|
|
|
|
|
st.markdown("<h1 style='text-align: center;'>Generador de Emails</h1>", unsafe_allow_html=True) |
|
st.markdown("<h4 style='text-align: center;'>Transforma tu marketing con emails persuasivos que convierten. Esta aplicación es tu arma secreta para crear emails emocionales de respuesta directa que impulsan a la acción.</h4>", unsafe_allow_html=True) |
|
|
|
|
|
col1, col2 = st.columns([1, 2]) |
|
|
|
|
|
file_content = "" |
|
is_image = False |
|
image_parts = None |
|
|
|
|
|
with col1: |
|
target_audience = st.text_input("¿Quién es tu público objetivo?", placeholder="Ejemplo: Estudiantes Universitarios") |
|
product = st.text_input("¿Qué producto/servicio estás promocionando?", placeholder="Ejemplo: Curso de Inglés") |
|
|
|
|
|
desired_action = st.text_input("Acción deseada", placeholder="Ejemplo: Registrarse para una prueba gratuita") |
|
|
|
|
|
selected_formula_key = st.selectbox( |
|
"Selecciona una fórmula para tus emails", |
|
options=list(email_formulas.email_formulas.keys()), |
|
key="formula_selectbox" |
|
) |
|
|
|
|
|
submit = st.button("Generar Emails", key="generate_emails_button") |
|
|
|
|
|
with st.expander("Personaliza tus emails"): |
|
|
|
creative_idea = st.text_area("Idea Creativa", placeholder="Ejemplo: Tu curso es como Netflix: ofrece contenido que engancha y soluciones que la gente realmente quiere ver", height=70) |
|
|
|
|
|
uploaded_file = st.file_uploader("📄 Archivo o imagen de referencia", |
|
type=['txt', 'pdf', 'docx', 'jpg', 'jpeg', 'png']) |
|
|
|
file_content = "" |
|
is_image = False |
|
image_parts = None |
|
|
|
if uploaded_file is not None: |
|
|
|
file_type = uploaded_file.name.split('.')[-1].lower() |
|
|
|
|
|
if file_type in ['txt', 'pdf', 'docx']: |
|
|
|
|
|
if file_type == 'txt': |
|
try: |
|
file_content = uploaded_file.read().decode('utf-8') |
|
st.success(f"Archivo TXT cargado correctamente: {uploaded_file.name}") |
|
except Exception as e: |
|
st.error(f"Error al leer el archivo TXT: {str(e)}") |
|
file_content = "" |
|
|
|
elif file_type == 'pdf': |
|
try: |
|
import PyPDF2 |
|
pdf_reader = PyPDF2.PdfReader(uploaded_file) |
|
file_content = "" |
|
for page in pdf_reader.pages: |
|
file_content += page.extract_text() + "\n" |
|
st.success(f"Archivo PDF cargado correctamente: {uploaded_file.name}") |
|
except Exception as e: |
|
st.error(f"Error al leer el archivo PDF: {str(e)}") |
|
file_content = "" |
|
|
|
elif file_type == 'docx': |
|
try: |
|
import docx |
|
doc = docx.Document(uploaded_file) |
|
file_content = "\n".join([para.text for para in doc.paragraphs]) |
|
st.success(f"Archivo DOCX cargado correctamente: {uploaded_file.name}") |
|
except Exception as e: |
|
st.error(f"Error al leer el archivo DOCX: {str(e)}") |
|
file_content = "" |
|
|
|
|
|
elif file_type in ['jpg', 'jpeg', 'png']: |
|
try: |
|
from PIL import Image |
|
image = Image.open(uploaded_file) |
|
image_bytes = uploaded_file.getvalue() |
|
image_parts = { |
|
"mime_type": uploaded_file.type, |
|
"data": image_bytes |
|
} |
|
is_image = True |
|
st.image(image, caption="Imagen cargada", use_column_width=True) |
|
except Exception as e: |
|
st.error(f"Error processing image: {str(e)}") |
|
is_image = False |
|
|
|
|
|
angle_keys = ["NINGUNO"] + sorted([key for key in angles.keys() if key != "NINGUNO"]) |
|
selected_angle = st.selectbox( |
|
"Selecciona un ángulo para tus emails", |
|
options=angle_keys, |
|
key="angle_selectbox" |
|
) |
|
|
|
|
|
emotion = st.selectbox( |
|
"¿Qué emoción quieres evocar?", |
|
options=["NINGUNO", "Curiosidad", "Miedo", "Esperanza", "Entusiasmo", "Confianza", "Urgencia"], |
|
key="emotion_selectbox" |
|
) |
|
|
|
|
|
brand_tone = st.selectbox( |
|
"Tono de marca", |
|
options=["NINGUNO", "Profesional", "Conversacional", "Entusiasta", "Formal", "Informal", "Humorístico", "Sarcástico", "Serio", "Inspirador", "Educativo"], |
|
key="brand_tone_selectbox" |
|
) |
|
|
|
|
|
temperature = st.slider("Creatividad", min_value=0.0, max_value=2.0, value=1.0, step=0.1) |
|
|
|
selected_formula = email_formulas.email_formulas[selected_formula_key] |
|
|
|
|
|
|
|
def validate_inputs(file_content, product, target_audience, emotion, desired_action): |
|
""" |
|
Validates input combinations and returns a tuple of (is_valid, error_message) |
|
""" |
|
has_file = file_content.strip() != "" if file_content else False |
|
has_product = product.strip() != "" |
|
has_audience = target_audience.strip() != "" |
|
has_emotion = emotion.strip() != "" if emotion else False |
|
has_action = desired_action.strip() != "" if desired_action else False |
|
|
|
|
|
if has_file and has_product: |
|
return True, "" |
|
elif has_file and has_audience: |
|
return True, "" |
|
elif has_product and has_audience and has_emotion and has_action: |
|
return True, "" |
|
|
|
|
|
if not (has_emotion and has_action): |
|
return False, "Por favor especifica la emoción que quieres evocar y la acción deseada." |
|
else: |
|
return False, "Por favor proporciona al menos una de estas combinaciones: archivo + producto, archivo + público objetivo, o producto + público objetivo + emoción + acción deseada." |
|
|
|
|
|
if submit: |
|
|
|
is_valid, error_message = validate_inputs( |
|
file_content, |
|
product, |
|
target_audience, |
|
emotion, |
|
desired_action |
|
) |
|
|
|
if is_valid and selected_formula: |
|
try: |
|
|
|
with col2: |
|
with st.spinner("Creando los emails..."): |
|
|
|
generated_emails = generate_emails( |
|
target_audience, |
|
product, |
|
temperature, |
|
selected_formula, |
|
selected_angle, |
|
file_content, |
|
image_parts, |
|
is_image, |
|
emotion, |
|
desired_action, |
|
creative_idea, |
|
brand_tone |
|
) |
|
|
|
|
|
if generated_emails.startswith("Error:"): |
|
st.error(generated_emails) |
|
else: |
|
|
|
generated_emails = clean_response_text(generated_emails) |
|
|
|
|
|
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") |
|
|
|
|
|
st.download_button( |
|
label="DESCARGAR EMAILS", |
|
data=generated_emails, |
|
file_name=f"emails_persuasivos_{timestamp}.txt", |
|
mime="text/plain", |
|
key="download_top" |
|
) |
|
|
|
|
|
st.markdown(f""" |
|
<div class="results-container"> |
|
<h4>Tus emails persuasivos:</h4> |
|
<p>{generated_emails}</p> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.download_button( |
|
label="DESCARGAR EMAILS", |
|
data=generated_emails, |
|
file_name=f"emails_persuasivos_{timestamp}.txt", |
|
mime="text/plain", |
|
key="download_bottom" |
|
) |
|
except Exception as e: |
|
col2.error(f"Error: {str(e)}") |
|
else: |
|
if not selected_formula: |
|
col2.error("Por favor selecciona una fórmula.") |
|
else: |
|
col2.error(error_message) |
|
|