Spaces:
Running
Running
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(""" | |
<style> | |
#MainMenu {visibility: hidden;} | |
footer {visibility: hidden;} | |
header {visibility: hidden;} | |
</style> | |
""", unsafe_allow_html=True) | |
# Custom CSS | |
st.markdown(get_custom_css(), unsafe_allow_html=True) | |
# App title and description | |
st.markdown('<h1 style="text-align: center;">Great Offer Generator</h1>', unsafe_allow_html=True) | |
st.markdown('<h3 style="text-align: center;">Transform your skills into compelling offers!</h3>', 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('<div style="height: 15px;"></div>', unsafe_allow_html=True) | |
# Apply the custom button style before rendering the download button | |
st.markdown('<style>div.stDownloadButton > button {your-custom-styles-here}</style>', 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') |