venesis / modules /radar.py
JohnGFX's picture
Venegard AI Hub - production release
ca812a1
import streamlit as st # type: ignore
import requests # type: ignore
import json
import os
from datetime import datetime
import google.generativeai as genai # type: ignore
# Importujeme náš nový Mozek!
from modules.venegard_brain import get_gemini_model, build_prompt
def render_radar_tab(db, scrape_website, extract_pdf_text, uploaded_file):
"""Vykreslí UI a logiku pro záložku Radar."""
# Načtení jazyka rozhraní (pokud chybí, dáme CZ)
ui_lang = st.session_state.get("ui_lang", "CZ")
# Překlady pro záložku Radar
t = {
"CZ": {
"title": "Auto-Prospector Engine",
"subtitle": "Objevujte a prioritizujte reálné firmy, které odpovídají vašemu cílovému profilu, pomocí živého vyhledávání a AI skórování.",
"profile_lbl": "Cílový profil",
"profile_ph": "např. Stavební firmy ve Zlíně",
"count_lbl": "Počet výsledků",
"btn": "Spustit analýzu trhu",
"stat_query": "Hledám reálné firmy přes Google SERP API a filtruji katalogy…",
"err_no_res": "Nebyly nalezeny žádné relevantní firmy (mimo katalogy). Upravte zadání a zkuste to znovu.",
"stat_found": "Nalezeno {} čistých firemních webů. Spouštím hloubkovou analýzu…",
"stat_analyzing": "Analyzuji {}/{}: {}...",
"stat_done": "Skenování trhu dokončeno. Výsledky jsou seřazeny.",
"toast": "Leady byly uloženy do CRM.",
"err_title": "Chyba při hledání",
"err_desc": "Chyba při zpracování:",
"lbl_open": "Otevřít web",
"lbl_score": "Skóre webu:",
"lbl_fit": "Shoda (Potenciál pro Venegard):",
"lbl_reason": "Analýza:"
},
"EN": {
"title": "Auto-Prospector Engine",
"subtitle": "Discover and prioritise real companies that match your target profile using live search and AI scoring.",
"profile_lbl": "Target profile",
"profile_ph": "e.g. Construction companies in Zlín",
"count_lbl": "Result count",
"btn": "Initialize Market Scan",
"stat_query": "Querying live companies & filtering directories…",
"err_no_res": "No direct company websites detected. Adjust the query and try again.",
"stat_found": "Detected {} clean company websites. Running deep-fit analysis…",
"stat_analyzing": "Analyzing {}/{}: {}...",
"stat_done": "Market scan completed. Results sorted.",
"toast": "Leads have been persisted to CRM.",
"err_title": "Search error",
"err_desc": "Processing error:",
"lbl_open": "Open website",
"lbl_score": "Web Score:",
"lbl_fit": "Fit (Venegard Potential):",
"lbl_reason": "Analysis:"
}
}[ui_lang]
st.markdown("<br>", unsafe_allow_html=True)
st.markdown(
f"""
<h2 style="margin-bottom: 0.5rem;">{t['title']}</h2>
<p style="color:#9CA3AF; font-size:0.9rem; max-width:640px;">
{t['subtitle']}
</p>
""",
unsafe_allow_html=True,
)
st.markdown("<br>", unsafe_allow_html=True)
col_r1, col_r2 = st.columns([3, 1])
with col_r1:
target_niche = st.text_input(t['profile_lbl'], placeholder=t['profile_ph'], key="radar_niche")
with col_r2:
num_results = st.selectbox(t['count_lbl'], [5, 10, 15], index=0)
submit_radar = st.button(t['btn'], key="btn_radar")
if submit_radar and target_niche:
with st.status(t['stat_query'], expanded=True) as status:
serp_key = os.getenv("SERPAPI_KEY")
search_url = f"https://serpapi.com/search?q={target_niche}&api_key={serp_key}&engine=google&num=40"
try:
search_res = requests.get(search_url).json()
raw_results = search_res.get("organic_results", [])
# --- ANTI-KATALOGOVÝ FILTR ---
blocked_domains = [
'firmy.cz', 'najisto.cz', 'zlatestranky.cz', 'edb.cz', 'kurzy.cz',
'penize.cz', 'justice.cz', 'facebook.com', 'instagram.com',
'linkedin.com', 'twitter.com', 'youtube.com', 'yelp.',
'tripadvisor.', 'foursquare.com', 'katalog', 'rejstrik',
'seznam.cz', 'google.com', 'firmy-cesko.cz', 'detail.cz'
]
clean_results = []
for res in raw_results:
link = res.get("link", "").lower()
if not any(bad_domain in link for bad_domain in blocked_domains):
clean_results.append(res)
if len(clean_results) >= num_results:
break
results = clean_results
if not results:
st.error(t['err_no_res'])
st.stop()
status.update(label=t['stat_found'].format(len(results)), state="running")
pdf_context = extract_pdf_text(uploaded_file) if uploaded_file else ""
model = get_gemini_model()
# Sem si budeme ukládat analyzované firmy, abychom je mohli seřadit
analyzed_companies = []
for idx, res in enumerate(results):
company_link = res.get("link")
company_title = res.get("title")
status.update(label=t['stat_analyzing'].format(idx+1, len(results), company_title))
real_web_content = scrape_website(company_link)
lang_instruction = "Češtině" if ui_lang == "CZ" else "Angličtině (English)"
# PŘÍSNÝ PROMPT S VYNUCENOU DÉLKOU ANALÝZY
task = f'''Jsi B2B akviziční analytik pro digitální agenturu Venegard (stavíme prémiové weby a AI infrastrukturu).
Ohodnoť web tohoto potenciálního klienta podle těchto kritérií:
1. "score_celkove" (0-100): Objektivní stav jejich současného webu (100 = prémiový moderní design, 0 = zastaralá hrůza).
2. "score_fit" (0-100): Jak moc POTŘEBUJÍ naše služby? (100 = mají peníze/jsou velcí, ale mají tragický web = ideální cíl. 0 = už mají dokonalý web nebo je to konkurence). Buď přísný!
3. "duvod": Napiš PŘESNĚ 2 ROZVITÉ VĚTY (zhruba 30 až 40 slov celkem). Musí to vizuálně zabrat zhruba dva plné řádky textu. Co přesně je na webu špatně a proč jim můžeme pomoct. Žádná zbytečná omáčka.
Vrať POUZE validní JSON ve formátu (bez markdown bloku). Texty musí být v {lang_instruction}:
{{"score_celkove": 40, "score_fit": 95, "duvod": "Web působí velmi zastarale, chybí mu moderní konverzní prvky a nereprezentuje skutečnou velikost firmy. Vzhledem k jejich zjevnému kapitálu je to ideální klient pro komplexní redesign a nasazení AI automatizace, která je okamžitě odliší od konkurence."}}'''
prompt = build_prompt(
task_specific_prompt=task,
target_client_data=f"Reálný web klienta ({company_title}): {real_web_content}",
extra_context=pdf_context
)
try:
ai_res = model.generate_content(prompt, generation_config=genai.GenerationConfig(temperature=0.3))
clean_json = ai_res.text.replace("```json", "").replace("```", "").strip()
data = json.loads(clean_json)
except Exception as ai_err:
# Pokud AI selže na špatném webu, dáme tam fallback, ať to neshodí celý běh
data = {"score_celkove": 50, "score_fit": 50, "duvod": "Web se nepodařilo detailně zanalyzovat. Může se jednat o technickou chybu nebo blokaci na straně klienta."}
analyzed_companies.append({
"title": company_title,
"link": company_link,
"score_celkove": data.get("score_celkove", 50),
"score_fit": data.get("score_fit", 50),
"duvod": data.get("duvod", "")
})
# --- ŘAZENÍ OD NEJHORŠÍHO SKÓRE WEBU PO NEJLEPŠÍ ---
analyzed_companies.sort(key=lambda x: x["score_celkove"])
# --- VYKRESLENÍ SEŘAZENÝCH A ODDĚLENÝCH VÝSLEDKŮ ---
for idx, comp in enumerate(analyzed_companies):
with st.container():
# Čára pro vizuální oddělení (kromě prvního)
if idx > 0:
st.markdown("<hr style='margin: 2.5rem 0; border-color: #1F2937;'>", unsafe_allow_html=True)
st.markdown(f'<div class="result-box">', unsafe_allow_html=True)
st.markdown(f"<h4>{comp['title']}</h4>", unsafe_allow_html=True)
st.markdown(f"[{t['lbl_open']}]({comp['link']})")
rc1, rc2 = st.columns(2)
with rc1:
st.markdown(f"**{t['lbl_score']}** {comp['score_celkove']}/100")
st.progress(comp['score_celkove'] / 100)
with rc2:
st.markdown(f"**{t['lbl_fit']}** {comp['score_fit']}%")
st.progress(comp['score_fit'] / 100)
st.markdown(f"**{t['lbl_reason']}** {comp['duvod']}")
st.markdown('</div>', unsafe_allow_html=True)
# Ukládání do CRM
record = {
"Autor": st.session_state.get("username", "Jan"),
"Datum": datetime.now().strftime("%d.%m.%Y"),
"Nástroj": "Radar",
"Firma": comp['title'],
"URL": comp['link'],
"Analýza": comp['duvod']
}
st.session_state.crm_data.append(record)
db.add_record(record)
status.update(label=t['stat_done'], state="complete", expanded=False)
st.toast(t['toast'])
except Exception as e:
status.update(label=t['err_title'], state="error")
st.error(f"{t['err_desc']} {e}")