Commit inicial - upload de todos os arquivos da pasta
This commit is contained in:
361
app.py
Normal file
361
app.py
Normal file
@@ -0,0 +1,361 @@
|
||||
"""
|
||||
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,
|
||||
)
|
||||
Reference in New Issue
Block a user