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