Spaces:
Sleeping
Sleeping
| 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 # Updated import statement | |
| from angles import angles | |
| # Cargar las variables de entorno | |
| load_dotenv() | |
| # Configurar la API de Google | |
| genai.configure(api_key=os.getenv("GOOGLE_API_KEY")) | |
| # Fórmulas con ejemplos y explicaciones | |
| # email_formulas dictionary has been moved to formulas/email_formulas.py | |
| # Cambiar el nombre de la función | |
| def generate_emails(target_audience, product, temperature, selected_formula, selected_angle, file_content="", image_parts=None, is_image=False, emotion="", desired_action="", creative_idea=""): | |
| # Crear la configuración del modelo | |
| 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, | |
| ) | |
| # Definir las instrucciones de formato una sola vez | |
| format_instructions = """ | |
| FORMAT RULES: | |
| - 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 with a dividing line | |
| - Do not include greetings like 'Hello [Name]' | |
| - Make sure that the postscripts (P.D.) are smaller and more discrete than the main body | |
| """ | |
| # Incluir las instrucciones del sistema en el prompt principal | |
| 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""" | |
| # Iniciar el prompt con las instrucciones del sistema | |
| email_instruction = f"{system_prompt}\n\n" | |
| # Añadir instrucciones para la idea creativa si existe | |
| 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 | |
| """ | |
| # Añadir contenido del archivo si existe | |
| 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: | |
| """ | |
| # Updated conditions for specific input 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. | |
| """ | |
| # Preparar instrucciones específicas del ángulo una sola vez | |
| 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" | |
| # Añadir las instrucciones del ángulo al prompt principal | |
| email_instruction += angle_instructions | |
| # Dentro de la función, actualizar el prompt para incluir emoción y acción deseada | |
| 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}. " | |
| ) | |
| # Usar la variable angle_instructions para determinar si hay un ángulo seleccionado | |
| if angle_instructions: | |
| email_instruction += f"IMPORTANT: Each email MUST follow the {selected_angle} angle clearly and consistently.\n\n" | |
| email_instruction += ( | |
| f"Avoid obvious mentions of {product} and focus on generating genuine interest" | |
| ) | |
| if angle_instructions: | |
| email_instruction += f" using the selected angle" | |
| email_instruction += ".\n\n" | |
| # Agregar ejemplos de la fórmula | |
| examples_to_use = selected_formula['examples'][:min(2, len(selected_formula['examples']))] # Reduced to only 2 examples | |
| email_instruction += "FORMULA EXAMPLES TO STUDY (ONLY FOR STRUCTURE, NOT CONTENT):\n" | |
| for i, example in enumerate(examples_to_use, 1): | |
| email_instruction += f"{i}. {example}\n" | |
| # Añadir advertencia crítica con emoji para llamar la atención | |
| email_instruction += "\n⚠️ WARNING: DO NOT COPY THESE EXAMPLES. Create completely original content with different stories, scenarios and language. The examples are ONLY for understanding the structure.\n" | |
| # Añadir instrucciones específicas sobre la temperatura creativa | |
| 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" | |
| # Consolidar las instrucciones finales en un solo bloque | |
| final_instructions = [ | |
| "Follow the logical structure but not the exact wording", | |
| "Be creative and original with your content", | |
| "Maintain the sequence flow between emails" | |
| ] | |
| # Añadir instrucciones específicas para el ángulo si es necesario | |
| if selected_angle != "NINGUNO": | |
| final_instructions.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, instruction in enumerate(final_instructions, 1): | |
| email_instruction += f"{i}. {instruction}\n" | |
| # Simplify the final instruction to clearly specify 5 emails | |
| email_instruction += f"\nGENERATE NOW:\nCreate 5 CREATIVE emails that follow the logical structure but with original content.\n" | |
| # Modificar la forma de enviar el mensaje según si hay imagen o no | |
| message_parts = [email_instruction] | |
| # Add the image to the message parts if it exists | |
| if is_image and image_parts: | |
| message_parts.append(image_parts) | |
| instruction_text = "Generate the emails in Spanish following only the STRUCTURE of the examples, but with completely original content, drawing inspiration from the provided image." | |
| else: | |
| instruction_text = "Generate the emails in Spanish following only the STRUCTURE of the examples, but with completely original content and stories." | |
| # Simplificar las instrucciones finales para evitar redundancia | |
| instruction_text += " Do not include explanations, only the emails. IMPORTANT: Do not include greetings like 'Hello [Name]' and make sure that the postscripts (P.D.) are smaller and more discrete than the main body of the email, using a lighter format." | |
| # Create the chat session with the message parts | |
| chat_session = model.start_chat( | |
| history=[ | |
| { | |
| "role": "user", | |
| "parts": message_parts, | |
| }, | |
| ] | |
| ) | |
| # Enviar el mensaje con las instrucciones | |
| response = chat_session.send_message(instruction_text) | |
| return response.text | |
| # Define the clean_response_text function before it's used | |
| def clean_response_text(text): | |
| """Remove extra spaces and normalize whitespace in the response text""" | |
| import re | |
| # Replace multiple newlines with just two | |
| text = re.sub(r'\n{3,}', '\n\n', text) | |
| # Remove leading/trailing whitespace | |
| text = text.strip() | |
| return text | |
| # Configurar la interfaz de usuario con Streamlit | |
| st.set_page_config( | |
| page_title="Email Composer", | |
| layout="wide", | |
| menu_items={}, # Empty dict removes the three-dot menu | |
| initial_sidebar_state="expanded" | |
| ) | |
| # Add custom CSS to reduce margins | |
| st.markdown(""" | |
| <style> | |
| .main > div { | |
| padding-top: 1rem; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # Leer el contenido del archivo manual.md | |
| with open("manual.md", "r", encoding="utf-8") as file: | |
| manual_content = file.read() | |
| # Mostrar el contenido del manual en el sidebar | |
| st.sidebar.markdown(manual_content) | |
| # Load CSS from file | |
| with open("styles/main.css", "r") as f: | |
| css = f.read() | |
| # Apply the CSS | |
| st.markdown(f"<style>{css}</style>", unsafe_allow_html=True) | |
| # Centrar el título y el subtítulo | |
| 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) | |
| # Crear columnas | |
| col1, col2 = st.columns([1, 2]) | |
| # Columnas de entrada | |
| 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") | |
| # Move "Acción deseada" outside the accordion | |
| desired_action = st.text_input("Acción deseada", placeholder="Ejemplo: Registrarse para una prueba gratuita") | |
| # Move formula selection here, right after desired action | |
| selected_formula_key = st.selectbox( | |
| "Selecciona una fórmula para tus emails", | |
| options=list(email_formulas.email_formulas.keys()), | |
| key="formula_selectbox" # Add a unique key | |
| ) | |
| # Only one submit button with a unique key | |
| submit = st.button("Generar Emails", key="generate_emails_button") | |
| # Crear un único acordeón para fórmula, creatividad y ángulo | |
| with st.expander("Personaliza tus emails"): | |
| # 1. Idea Creativa al principio con altura de 70px | |
| 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) | |
| # 2. Lo demás (cargador de archivos) | |
| 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: | |
| # El código para manejar archivos permanece igual | |
| file_type = uploaded_file.name.split('.')[-1].lower() | |
| # Manejar archivos de texto | |
| if file_type in ['txt', 'pdf', 'docx']: | |
| # El código para manejar archivos de texto permanece igual | |
| # ... | |
| 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 = "" | |
| # Manejar archivos de imagen | |
| 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 | |
| # 4. Ángulo | |
| 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" # Add a unique key | |
| ) | |
| # 5. Emoción | |
| emotion = st.selectbox( | |
| "¿Qué emoción quieres evocar?", | |
| options=["Curiosidad", "Miedo", "Esperanza", "Entusiasmo", "Confianza", "Urgencia"], | |
| key="emotion_selectbox" # Add a unique key | |
| ) | |
| # 6. Creatividad (slider) | |
| 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] # Updated reference | |
| # Removed the submit button from here | |
| # Mostrar los emails generados | |
| if submit: | |
| # Check if we have a valid combination of inputs | |
| has_file = 'file_content' in locals() and file_content.strip() != "" | |
| has_product = product.strip() != "" | |
| has_audience = target_audience.strip() != "" | |
| has_emotion = 'emotion' in locals() and emotion.strip() != "" | |
| has_action = 'desired_action' in locals() and desired_action.strip() != "" | |
| # Valid combinations: | |
| # 1. File + Product (no audience needed) | |
| # 2. File + Audience (no product needed) | |
| # 3. Product + Audience (traditional way) | |
| valid_inputs = ( | |
| (has_file and has_product) or | |
| (has_file and has_audience) or | |
| (has_product and has_audience and has_emotion and has_action) | |
| ) | |
| if valid_inputs and selected_formula: | |
| try: | |
| # Use spinner within col2 context | |
| with col2: | |
| with st.spinner("Creando los emails..."): | |
| # Update the function call to include creative_idea | |
| generated_emails = generate_emails( | |
| target_audience, | |
| product, | |
| temperature, | |
| selected_formula, | |
| selected_angle, | |
| file_content if 'file_content' in locals() else "", | |
| image_parts if 'image_parts' in locals() else None, | |
| is_image if 'is_image' in locals() else False, | |
| emotion, | |
| desired_action, | |
| creative_idea # Add the creative idea parameter | |
| ) | |
| # Clean the response text to remove extra spaces | |
| generated_emails = clean_response_text(generated_emails) | |
| # Display the generated emails in col2 (still within col2 context) | |
| st.markdown(f""" | |
| <div class="results-container"> | |
| <h4>Tus emails persuasivos:</h4> | |
| <p>{generated_emails}</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Add download button (styling is in styles/main.css) | |
| import datetime | |
| # Get current timestamp for the filename | |
| timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") | |
| # Download button | |
| st.download_button( | |
| label="DESCARGAR EMAILS", | |
| data=generated_emails, | |
| file_name=f"emails_persuasivos_{timestamp}.txt", | |
| mime="text/plain" | |
| ) | |
| except ValueError as e: | |
| col2.error(f"Error: {str(e)}") | |
| else: | |
| if not selected_formula: | |
| col2.error("Por favor selecciona una fórmula.") | |
| elif not (has_emotion and has_action): | |
| col2.error("Por favor especifica la emoción que quieres evocar y la acción deseada.") | |
| else: | |
| col2.error("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.") | |