| import streamlit as st |
| import requests |
| import json |
| import os |
| from datetime import datetime |
| import google.generativeai as genai |
|
|
| |
| 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.""" |
| |
| |
| ui_lang = st.session_state.get("ui_lang", "CZ") |
| |
| |
| 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", []) |
| |
| |
| 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() |
| |
| |
| 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)" |
| |
| |
| 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: |
| |
| 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", "") |
| }) |
| |
| |
| analyzed_companies.sort(key=lambda x: x["score_celkove"]) |
|
|
| |
| for idx, comp in enumerate(analyzed_companies): |
| with st.container(): |
| |
| 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) |
| |
| |
| 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}") |