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