Files
Google-Ads/app.py

362 lines
13 KiB
Python

"""
Google Ads Generator - Interface Streamlit
Aplicação que extrai conteúdo de Landing Pages e gera automaticamente
ativos de campanha para Google Ads usando IA (OpenAI ou Gemini).
"""
import os
import re
import streamlit as st
from dotenv import load_dotenv
from src.scraper import scrape_landing_page
from src.ai_generator import generate_google_ads_assets, MODELS
from src.exporter import (
create_keywords_df,
create_negative_keywords_df,
create_ads_df,
create_sitelinks_df,
create_callouts_df,
export_all_to_excel,
)
# ─── Carregar variáveis de ambiente ───────────────────────────────
load_dotenv()
def _highlight_keywords(text: str, keywords: list[str]) -> str:
"""Destaca em negrito as palavras-chave encontradas no texto."""
result = text
for kw in keywords:
pattern = re.compile(re.escape(kw), re.IGNORECASE)
match = pattern.search(result)
if match:
result = result[:match.start()] + f"**{match.group()}**" + result[match.end():]
break # Destacar apenas a primeira keyword encontrada
return result
# ─── Configuração da Página ───────────────────────────────────────
st.set_page_config(
page_title="Google Ads Generator",
page_icon="📊",
layout="wide",
)
# ─── CSS Customizado ──────────────────────────────────────────────
st.markdown("""
<style>
.main-header {
font-size: 2.2rem;
font-weight: 700;
color: #1a73e8;
margin-bottom: 0.2rem;
}
.sub-header {
font-size: 1.1rem;
color: #5f6368;
margin-bottom: 2rem;
}
.metric-card {
background: #f8f9fa;
border-radius: 12px;
padding: 1.2rem;
text-align: center;
border: 1px solid #e8eaed;
}
.metric-number {
font-size: 2rem;
font-weight: 700;
color: #1a73e8;
}
.metric-label {
font-size: 0.85rem;
color: #5f6368;
}
.stTabs [data-baseweb="tab-list"] {
gap: 8px;
}
.stTabs [data-baseweb="tab"] {
padding: 10px 20px;
border-radius: 8px 8px 0 0;
}
</style>
""", unsafe_allow_html=True)
# ─── Header ───────────────────────────────────────────────────────
st.markdown('<p class="main-header">Google Ads Generator</p>', unsafe_allow_html=True)
st.markdown(
'<p class="sub-header">Gere ativos de campanha automaticamente a partir da sua Landing Page</p>',
unsafe_allow_html=True,
)
# ─── Sidebar ──────────────────────────────────────────────────────
with st.sidebar:
st.header("Configurações")
provider = st.selectbox(
"Provider de IA",
["OpenAI", "Gemini"],
index=0,
help="Escolha o provider de IA. As chaves devem estar configuradas no arquivo .env",
)
model = st.selectbox(
"Modelo",
MODELS.get(provider, []),
index=0,
help="Modelo a ser utilizado para geração dos ativos.",
)
# Status das chaves configuradas
st.divider()
openai_ok = bool(os.environ.get("OPENAI_API_KEY"))
gemini_ok = bool(os.environ.get("GEMINI_API_KEY"))
st.caption("Status das API Keys (.env):")
st.markdown(f"- OpenAI: {'✅ Configurada' if openai_ok else '❌ Não encontrada'}")
st.markdown(f"- Gemini: {'✅ Configurada' if gemini_ok else '❌ Não encontrada'}")
st.divider()
campaign_name = st.text_input(
"Nome da Campanha",
value="Campanha LP",
help="Nome que aparecerá na coluna 'Campaign' do CSV.",
)
ad_group = st.text_input(
"Nome do Grupo de Anúncios",
value="Grupo 1",
help="Nome que aparecerá na coluna 'Ad Group' do CSV.",
)
st.divider()
st.caption("Desenvolvido com Streamlit + OpenAI + Gemini")
# ─── Área Principal ───────────────────────────────────────────────
url = st.text_input(
"URL da Landing Page",
placeholder="https://www.seusite.com.br/landing-page",
help="Cole a URL completa da sua Landing Page aqui.",
)
col_btn, col_status = st.columns([1, 3])
with col_btn:
generate_btn = st.button("Gerar Campanha", type="primary", use_container_width=True)
# ─── Lógica Principal ────────────────────────────────────────────
if generate_btn:
# Validações
if not url:
st.error("Por favor, insira a URL da Landing Page.")
st.stop()
if not url.startswith(("http://", "https://")):
st.error("A URL deve começar com http:// ou https://")
st.stop()
# Verificar se a chave do provider selecionado está configurada
if provider == "OpenAI" and not os.environ.get("OPENAI_API_KEY"):
st.error("OPENAI_API_KEY não encontrada no arquivo .env. Configure antes de continuar.")
st.stop()
elif provider == "Gemini" and not os.environ.get("GEMINI_API_KEY"):
st.error("GEMINI_API_KEY não encontrada no arquivo .env. Configure antes de continuar.")
st.stop()
# Step 1: Scraping
with st.status("Processando...", expanded=True) as status:
st.write("Extraindo conteúdo da Landing Page...")
try:
lp_data = scrape_landing_page(url)
except Exception as e:
st.error(f"Erro ao acessar a URL: {e}")
st.stop()
st.write(f"Conteúdo extraído: {len(lp_data['paragraphs'])} parágrafos, "
f"{len(lp_data['h1'])} H1, {len(lp_data['h2'])} H2, "
f"{len(lp_data.get('list_items', []))} itens de lista, "
f"{len(lp_data['ctas'])} CTAs")
# Step 2: IA
st.write(f"Gerando ativos com {provider} ({model})...")
try:
assets, prompts = generate_google_ads_assets(
lp_content=lp_data["full_text"],
provider=provider,
model=model,
)
except ValueError as e:
st.error(str(e))
st.stop()
except Exception as e:
st.error(f"Erro ao gerar ativos: {e}")
st.stop()
status.update(label="Concluído!", state="complete", expanded=False)
# Salvar no session_state
st.session_state["assets"] = assets
st.session_state["prompts"] = prompts
st.session_state["lp_data"] = lp_data
st.session_state["campaign_name"] = campaign_name
st.session_state["ad_group"] = ad_group
st.session_state["provider_used"] = provider
st.session_state["model_used"] = model
# ─── Exibição dos Resultados ─────────────────────────────────────
if "assets" in st.session_state:
assets = st.session_state["assets"]
prompts = st.session_state.get("prompts", {})
camp = st.session_state.get("campaign_name", "Campanha LP")
adg = st.session_state.get("ad_group", "Grupo 1")
prov_used = st.session_state.get("provider_used", "")
model_used = st.session_state.get("model_used", "")
st.divider()
# Métricas resumo
col1, col2, col3, col4, col5, col6 = st.columns(6)
with col1:
st.metric("Keywords", len(assets.get("keywords", [])))
with col2:
st.metric("Negativas", len(assets.get("negative_keywords", [])))
with col3:
st.metric("Títulos", len(assets.get("headlines", [])))
with col4:
st.metric("Descrições", len(assets.get("descriptions", [])))
with col5:
st.metric("Sitelinks", len(assets.get("sitelinks", [])))
with col6:
st.metric("Callouts", len(assets.get("callouts", [])))
st.divider()
# Abas com resultados
tab_kw, tab_neg, tab_ads, tab_sl, tab_co, tab_prompts, tab_raw = st.tabs([
"Keywords", "Negativas", "Anúncios RSA", "Sitelinks", "Callouts",
"Prompts Utilizados", "Dados da LP",
])
with tab_kw:
st.subheader("Palavras-chave")
kw_df = create_keywords_df(assets, camp, adg)
if not kw_df.empty:
st.dataframe(kw_df, use_container_width=True, hide_index=True)
else:
st.info("Nenhuma palavra-chave gerada.")
with tab_neg:
st.subheader("Palavras-chave Negativas")
nkw_df = create_negative_keywords_df(assets, camp)
if not nkw_df.empty:
st.dataframe(nkw_df, use_container_width=True, hide_index=True)
else:
st.info("Nenhuma palavra-chave negativa gerada.")
with tab_ads:
st.subheader("Anúncio Responsivo de Pesquisa (RSA)")
st.write("**Títulos (Headlines):**")
# Coletar palavras-chave para destacar nos títulos
kw_list = sorted(
[kw["keyword"].lower() for kw in assets.get("keywords", []) if kw.get("keyword")],
key=len, reverse=True, # Maior primeiro para evitar match parcial
)
for i, h in enumerate(assets.get("headlines", []), 1):
char_count = len(h)
color = "green" if char_count <= 30 else "red"
h_highlighted = _highlight_keywords(h, kw_list)
st.markdown(f"{i}. {h_highlighted} — :{color}[{char_count} chars]")
st.write("**Descrições:**")
for i, d in enumerate(assets.get("descriptions", []), 1):
char_count = len(d)
color = "green" if char_count <= 90 else "red"
st.markdown(f"{i}. {d} — :{color}[{char_count} chars]")
st.divider()
ads_df = create_ads_df(assets, camp, adg)
if not ads_df.empty:
st.dataframe(ads_df, use_container_width=True, hide_index=True)
with tab_sl:
st.subheader("Sitelinks")
sl_df = create_sitelinks_df(assets, camp)
if not sl_df.empty:
st.dataframe(sl_df, use_container_width=True, hide_index=True)
else:
st.info("Nenhum sitelink gerado.")
with tab_co:
st.subheader("Callouts (Frases de Destaque)")
co_df = create_callouts_df(assets, camp)
if not co_df.empty:
st.dataframe(co_df, use_container_width=True, hide_index=True)
else:
st.info("Nenhum callout gerado.")
with tab_prompts:
st.subheader("Prompts Utilizados")
st.caption(f"Provider: **{prov_used}** | Modelo: **{model_used}**")
st.markdown("#### Prompt de Sistema (System Prompt)")
st.code(prompts.get("system_prompt", "N/A"), language=None)
st.markdown("#### Prompt do Usuário (User Prompt)")
st.code(prompts.get("user_prompt", "N/A"), language=None)
with tab_raw:
st.subheader("Dados Extraídos da Landing Page")
lp_data = st.session_state.get("lp_data", {})
if lp_data:
st.write(f"**URL:** {lp_data.get('url', '')}")
st.write(f"**Título:** {lp_data.get('title', '')}")
st.write(f"**Meta Description:** {lp_data.get('meta_description', '')}")
with st.expander("Headings (H1, H2, H3)"):
for h in lp_data.get("h1", []):
st.write(f"**H1:** {h}")
for h in lp_data.get("h2", []):
st.write(f"**H2:** {h}")
for h in lp_data.get("h3", []):
st.write(f"**H3:** {h}")
with st.expander("Itens de Lista / Benefícios"):
for li in lp_data.get("list_items", []):
st.write(f"- {li}")
with st.expander("CTAs Encontrados"):
for c in lp_data.get("ctas", []):
st.write(f"- {c}")
with st.expander("Texto Completo Enviado à IA"):
st.code(lp_data.get("full_text", ""), language=None)
# ─── Botões de Download ───────────────────────────────────────
st.divider()
st.subheader("Download dos Ativos")
col_dl1, col_dl2 = st.columns(2)
with col_dl1:
excel_data = export_all_to_excel(assets, camp, adg)
st.download_button(
label="Baixar Excel (.xlsx)",
data=excel_data,
file_name="google_ads_assets.xlsx",
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
use_container_width=True,
)
with col_dl2:
kw_csv = create_keywords_df(assets, camp, adg).to_csv(index=False)
st.download_button(
label="Baixar Keywords (.csv)",
data=kw_csv,
file_name="google_ads_keywords.csv",
mime="text/csv",
use_container_width=True,
)