|
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=""):
|
|
|
|
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 have helped many marketers before me persuade their clients through emotional email sequences.
|
|
Your task is to create a 5-email sequence that makes my [buyer persona] feel [emotion] about my [product/service] and convince them to register/take [desired action].
|
|
|
|
{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=["Curiosidad", "Miedo", "Esperanza", "Entusiasmo", "Confianza", "Urgencia"],
|
|
key="emotion_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
|
|
)
|
|
|
|
|
|
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)
|
|
|