import streamlit as st import google.generativeai as genai import os import time from dotenv import load_dotenv from styles import get_custom_css, get_response_html_wrapper from formulas import offer_formulas, formula_formatting import PyPDF2 import docx from PIL import Image import io # Import the bullet generator from bullets.generator import create_bullet_instruction # Import the bonus generator from bonuses.generator import create_bonus_instruction # Import the offer instruction generator from prompts import create_offer_instruction # Import sophistication levels from sophistication import sophistication_levels # Set page to wide mode to use full width st.set_page_config(layout="wide") # Load environment variables load_dotenv() # Configure Google Gemini API genai.configure(api_key=os.getenv('GOOGLE_API_KEY')) model = genai.GenerativeModel('gemini-2.0-flash') # Initialize session state variables if they don't exist if 'submitted' not in st.session_state: st.session_state.submitted = False if 'offer_result' not in st.session_state: st.session_state.offer_result = "" if 'generated' not in st.session_state: st.session_state.generated = False # Hide Streamlit menu and footer st.markdown(""" """, unsafe_allow_html=True) # Custom CSS st.markdown(get_custom_css(), unsafe_allow_html=True) # App title and description st.markdown('

Great Offer Generator

', unsafe_allow_html=True) st.markdown('

Transform your skills into compelling offers!

', unsafe_allow_html=True) # Create two columns for layout - left column 40%, right column 60% col1, col2 = st.columns([4, 6]) # Main input section in left column with col1: # Define the generate_offer function first def handle_generate_button(): # Renamed to avoid conflict has_manual_input = bool(skills or product_service) has_file_input = bool(uploaded_file is not None and not is_image) has_image_input = bool(uploaded_file is not None and is_image) # Simple validation - check if we have at least one input type if not (has_manual_input or has_file_input or has_image_input): st.error('Por favor ingresa texto o sube un archivo/imagen') return st.session_state.submitted = True st.session_state.generated = False # Reset generated flag # Store inputs based on what's available if has_manual_input: st.session_state.skills = skills if skills else "" st.session_state.product_service = product_service if product_service else "" if has_file_input: st.session_state.file_content = file_content if has_image_input: st.session_state.image_parts = image_parts # Set input type based on what's available if has_image_input: if has_manual_input: st.session_state.input_type = "manual_image" else: st.session_state.input_type = "image" else: if has_manual_input and has_file_input: st.session_state.input_type = "both" elif has_file_input: st.session_state.input_type = "file" elif has_manual_input: st.session_state.input_type = "manual" # Store common settings st.session_state.target_audience = target_audience st.session_state.temperature = temperature st.session_state.formula_type = formula_type st.session_state.sophistication_level = sophistication_level # Keep only the manual input tab with st.container(): skills = st.text_area('💪 Tus Habilidades', height=70, help='Lista tus habilidades y experiencia clave') product_service = st.text_area('🎯 Producto/Servicio', height=70, help='Describe tu producto o servicio') # Generate button moved here - right after product/service st.button('Generar Oferta 🎉', on_click=handle_generate_button) # Updated function name # Accordion for additional settings with st.expander('⚙️ Configuración Avanzada'): target_audience = st.text_area('👥 Público Objetivo', height=70, help='Describe tu cliente o público ideal') # Add sophistication level selector sophistication_level = st.selectbox( '🧠 Nivel de Sofisticación del Mercado', options=list(sophistication_levels.keys()), help='Selecciona el nivel de conocimiento que tiene tu público sobre soluciones similares' ) # Add file/image uploader here uploaded_file = st.file_uploader("📄 Sube un archivo o imagen", type=['txt', 'pdf', 'docx', 'jpg', 'jpeg', 'png']) if uploaded_file is not None: file_type = uploaded_file.name.split('.')[-1].lower() # Handle text files if file_type in ['txt', 'pdf', 'docx']: if file_type == 'txt': try: file_content = uploaded_file.read().decode('utf-8') 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" 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]) except Exception as e: st.error(f"Error al leer el archivo DOCX: {str(e)}") file_content = "" # Remove success message - no notification shown # Set file type flag # Initialize is_image variable is_image = False # Handle image files elif file_type in ['jpg', 'jpeg', 'png']: try: image = Image.open(uploaded_file) st.image(image, caption="Imagen cargada", use_container_width=True) image_bytes = uploaded_file.getvalue() image_parts = [ { "mime_type": uploaded_file.type, "data": image_bytes } ] # Set file type flag is_image = True except Exception as e: st.error(f"Error al procesar la imagen: {str(e)}") is_image = False # Selector de fórmula formula_type = st.selectbox( '📋 Tipo de Fórmula', options=list(offer_formulas.keys()), help='Selecciona el tipo de fórmula para tu oferta' ) temperature = st.slider('🌡️ Nivel de Creatividad', min_value=0.0, max_value=2.0, value=1.0, help='Valores más altos hacen que el resultado sea más creativo pero menos enfocado') # Results column # In the section where you're generating the offer with col2: if st.session_state.submitted and not st.session_state.generated: with st.spinner('Creando tu oferta perfecta...'): # Use the create_offer_instruction function to generate the prompt target_audience_value = st.session_state.target_audience if hasattr(st.session_state, 'target_audience') and st.session_state.target_audience else 'General audience' # Get product_service from session state or use the current value product_service_value = st.session_state.product_service if hasattr(st.session_state, 'product_service') and st.session_state.product_service else product_service # Get skills from session state or use empty string skills_value = st.session_state.skills if hasattr(st.session_state, 'skills') and st.session_state.skills else "" # Preparar el contenido del archivo si existe file_content = "" if hasattr(st.session_state, 'file_content') and st.session_state.input_type in ["file", "both"]: file_content = st.session_state.file_content # Preparar el contenido para los bullets y bonos bullet_content = None bonus_content = None if hasattr(st.session_state, 'file_content') and st.session_state.input_type in ["file", "both"]: bullet_content = st.session_state.file_content bonus_content = st.session_state.file_content # Get the instruction using the formula from prompts import create_integrated_instruction # Construir la instrucción integrada instruction = create_integrated_instruction( target_audience=target_audience_value, product_service=product_service_value, selected_formula_name=st.session_state.formula_type, file_content=file_content, bullet_content=bullet_content, bonus_content=bonus_content, # Añadir parámetro explícito skills=skills_value, sophistication_level=st.session_state.sophistication_level if hasattr(st.session_state, 'sophistication_level') else None ) # Validar componentes de la fórmula Contraste Revelador if formula_type == "Contraste Revelador": # Initialize variables with default empty values if they don't exist situacion = st.session_state.get('situacion', '') solucion = st.session_state.get('solucion', '') resultado = st.session_state.get('resultado', '') # Now perform your validation if situacion and not any(keyword in situacion for keyword in ["problema", "frustración", "dificultad", "obstáculo"]): st.warning("La situación debe describir claramente un problema o frustración del público objetivo") # Continue with other validations if solucion and not solucion.isupper(): st.warning("La solución transformadora debe estar completamente en MAYÚSCULAS") if resultado and not any(char.isdigit() for char in resultado): st.warning("El resultado emocional debe incluir algún número específico como prueba social") # Eliminar esta instrucción redundante para los bonos # La función create_integrated_instruction ya incluye las instrucciones para los bonos try: generation_config = genai.GenerationConfig(temperature=st.session_state.temperature) if "image" in st.session_state.input_type: response = model.generate_content([instruction, st.session_state.image_parts[0]], generation_config=generation_config) else: response = model.generate_content(instruction, generation_config=generation_config) # Get the response text response_text = response.text # Apply formatting to all formulas # Obtener la configuración de formato para la fórmula actual current_formula = st.session_state.formula_type format_config = formula_formatting.get(current_formula, { "uppercase_lines": [], "spacing": "single", "product_integration": False, "replace_terms": [], "capitalize_first_only": [] }) # Aplicar formato según la configuración lines = response_text.split('\n') lines = [line.lstrip() for line in lines] # Elimina espacios al inicio de cada línea # Aplicar mayúsculas a líneas específicas for line_index in format_config["uppercase_lines"]: if line_index < len(lines): lines[line_index] = lines[line_index].upper() # Aplicar capitalización solo a la primera letra en líneas específicas for line_index in format_config.get("capitalize_first_only", []): if line_index < len(lines) and lines[line_index].strip(): # Convertir a minúsculas primero, luego capitalizar solo la primera letra lines[line_index] = lines[line_index].lower() lines[line_index] = lines[line_index][0].upper() + lines[line_index][1:] # Eliminar líneas en blanco extras lines = [line for line in lines if line.strip()] # Aplicar espaciado if format_config["spacing"] == "double": response_text = '\n\n'.join(lines) else: response_text = '\n'.join(lines) # Integrar nombre del producto si está configurado if format_config["product_integration"]: # Verificar si el usuario proporcionó un nombre de producto has_product_name = hasattr(st.session_state, 'product_service') and st.session_state.product_service # Si no hay nombre de producto pero el nivel de sofisticación es 3 o mayor, generar uno genérico if not has_product_name and hasattr(st.session_state, 'sophistication_level'): sophistication_level = st.session_state.sophistication_level if sophistication_level and sophistication_level.startswith(('nivel_3', 'nivel_4', 'nivel_5')): # Generar un nombre genérico basado en la industria o tema industry = st.session_state.get('industry', '') if industry: if 'marketing' in industry.lower(): product_service_value = "Sistema de Marketing Estratégico" elif 'finanzas' in industry.lower() or 'inversión' in industry.lower(): product_service_value = "Método de Inversión Inteligente" elif 'salud' in industry.lower() or 'bienestar' in industry.lower(): product_service_value = "Programa de Transformación Integral" elif 'productividad' in industry.lower(): product_service_value = "Sistema de Productividad Avanzada" else: product_service_value = "Sistema Transformador" else: product_service_value = "Sistema Transformador" # Guardar el nombre generado para uso futuro st.session_state.generated_product_name = product_service_value else: # Para niveles 1-2, no hacemos nada especial # Simplemente usamos los términos genéricos que ya están en el texto product_service_value = None else: # Usar el nombre proporcionado por el usuario product_service_value = st.session_state.product_service if has_product_name else None # Procesar cada línea si tenemos un nombre de producto # Solo hacemos reemplazos si hay un nombre de producto Y no estamos en nivel 1-2 # O si el usuario proporcionó explícitamente un nombre if product_service_value: lines = response_text.split('\n') for i in range(len(lines)): # Eliminar comillas alrededor del nombre del producto lines[i] = lines[i].replace(f'"{product_service_value}"', product_service_value) lines[i] = lines[i].replace(f"'{product_service_value}'", product_service_value) # Reemplazar términos genéricos con el nombre del producto for term in format_config["replace_terms"]: if term.lower() in lines[i].lower() and product_service_value.lower() not in lines[i].lower(): lines[i] = lines[i].replace(term, product_service_value, 1) # Rejuntar las líneas con el espaciado adecuado if format_config["spacing"] == "double": response_text = '\n\n'.join(lines) else: response_text = '\n'.join(lines) st.session_state.offer_result = response_text st.session_state.generated = True # Mark as generated except Exception as e: st.error(f'Ocurrió un error: {str(e)}') st.session_state.submitted = False # Display results if we have an offer result if st.session_state.generated: # Remove the visualization mode option # Display the formatted result directly st.markdown(get_response_html_wrapper(st.session_state.offer_result), unsafe_allow_html=True) # Add a small space st.markdown('
', unsafe_allow_html=True) # Apply the custom button style before rendering the download button st.markdown('', unsafe_allow_html=True) st.download_button( label="Descargar Oferta", data=st.session_state.offer_result, file_name="oferta_generada.txt", mime="text/plain" ) # Footer st.markdown('---') st.markdown('Made with ❤️ by Jesús Cabrera')