import streamlit as st import requests import pandas as pd import os from datetime import datetime import json import time import threading from database_manager import DatabaseManager from contact_scraper import ContactScraper # Import dla Hugging Face Hub (opcjonalne) try: from huggingface_hub import HfApi HF_AVAILABLE = True except ImportError: HF_AVAILABLE = False # Hugging Face Token - czytaj z pliku def load_hf_token(): """Wczytaj token Hugging Face z pliku""" try: if os.path.exists('hf_token.txt'): with open('hf_token.txt', 'r', encoding='utf-8') as f: token = f.read().strip() if token and token.startswith('hf_'): return token return None except Exception as e: print(f"Błąd wczytywania tokenu: {e}") return None HF_TOKEN = None # Load environment variables from dotenv import load_dotenv load_dotenv() # Poland regions with detailed city structure for intelligent searching POLAND_REGIONS = { "Mazowieckie": { "major_cities": ["Warszawa"], # 1.8M+ - multiple queries needed "large_cities": ["Radom", "Płock", "Siedlce", "Ostrołęka"], # 50K+ - single query "medium_cities": ["Pruszków", "Legionowo", "Otwock", "Piaseczno", "Wołomin"], # 20K+ - single query "small_cities": ["Grodzisk Mazowiecki", "Marki", "Nowy Dwór Mazowiecki", "Wyszków", "Kobyłka"] # 5K+ - single query }, "Pomorskie": { "major_cities": ["Gdańsk", "Gdynia"], # 500K+ - multiple queries "large_cities": ["Słupsk", "Tczew", "Wejherowo", "Rumia"], # 50K+ - single query "medium_cities": ["Starogard Gdański", "Chojnice", "Kwidzyn", "Malbork", "Lębork"], # 20K+ - single query "small_cities": ["Reda", "Puck", "Kartuzy", "Bytów", "Człuchów"] # 5K+ - single query }, "Śląskie": { "major_cities": ["Katowice", "Częstochowa"], # 300K+ - multiple queries "large_cities": ["Sosnowiec", "Gliwice", "Zabrze", "Bytom", "Ruda Śląska", "Rybnik"], # 50K+ - single query "medium_cities": ["Tychy", "Dąbrowa Górnicza", "Chorzów", "Jaworzno", "Jastrzębie-Zdrój"], # 20K+ - single query "small_cities": ["Mikołów", "Piekary Śląskie", "Racibórz", "Wodzisław Śląski", "Będzin"] # 5K+ - single query }, "Wielkopolskie": { "major_cities": ["Poznań"], # 500K+ - multiple queries "large_cities": ["Kalisz", "Konin", "Piła", "Ostrów Wielkopolski"], # 50K+ - single query "medium_cities": ["Gniezno", "Leszno", "Śrem", "Września", "Krotoszyn"], # 20K+ - single query "small_cities": ["Rawicz", "Jarocin", "Wągrowiec", "Środa Wielkopolska", "Gostyń"] # 5K+ - single query }, "Małopolskie": { "major_cities": ["Kraków"], # 800K+ - multiple queries "large_cities": ["Tarnów", "Nowy Sącz", "Oświęcim", "Chrzanów"], # 50K+ - single query "medium_cities": ["Wieliczka", "Bochnia", "Zakopane", "Skawina", "Brzesko"], # 20K+ - single query "small_cities": ["Andrychów", "Kęty", "Trzebinia", "Olkusz", "Myślenice"] # 5K+ - single query }, "Dolnośląskie": { "major_cities": ["Wrocław"], # 600K+ - multiple queries "large_cities": ["Wałbrzych", "Legnica", "Jelenia Góra", "Lubin"], # 50K+ - single query "medium_cities": ["Głogów", "Świdnica", "Bolesławiec", "Oława", "Dzierżoniów"], # 20K+ - single query "small_cities": ["Zgorzelec", "Oleśnica", "Trzebnica", "Bielawa", "Kłodzko"] # 5K+ - single query }, "Łódzkie": { "major_cities": ["Łódź"], # 700K+ - multiple queries "large_cities": ["Piotrków Trybunalski", "Pabianice", "Tomaszów Mazowiecki", "Bełchatów"], # 50K+ - single query "medium_cities": ["Zgierz", "Skierniewice", "Kutno", "Sieradz", "Łowicz"], # 20K+ - single query "small_cities": ["Radomsko", "Wieluń", "Opoczno", "Rawa Mazowiecka", "Brzeziny"] # 5K+ - single query }, "Lubelskie": { "major_cities": ["Lublin"], # 350K+ - multiple queries "large_cities": ["Chełm", "Zamość", "Puławy", "Świdnik"], # 50K+ - single query "medium_cities": ["Kraśnik", "Lubartów", "Łuków", "Biłgoraj", "Dęblin"], # 20K+ - single query "small_cities": ["Tomaszów Lubelski", "Hrubieszów", "Janów Lubelski", "Krasnystaw", "Łęczna"] # 5K+ - single query }, "Podlaskie": { "major_cities": ["Białystok"], # 300K+ - multiple queries "large_cities": ["Suwałki", "Łomża"], # 50K+ - single query "medium_cities": ["Augustów", "Hajnówka", "Sokółka", "Grajewo", "Kolno"], # 20K+ - single query "small_cities": ["Zambrów", "Wysokie Mazowieckie", "Bielsk Podlaski", "Sejny", "Ciechanowiec"] # 5K+ - single query }, "Podkarpackie": { "major_cities": ["Rzeszów"], # 200K+ - multiple queries "large_cities": ["Przemyśl", "Stalowa Wola", "Mielec", "Tarnobrzeg"], # 50K+ - single query "medium_cities": ["Jarosław", "Krosno", "Dębica", "Nisko", "Sanok"], # 20K+ - single query "small_cities": ["Przeworsk", "Ropczyce", "Łańcut", "Leżajsk", "Kolbuszowa"] # 5K+ - single query }, "Kujawsko-Pomorskie": { "major_cities": ["Bydgoszcz", "Toruń"], # 350K+ - multiple queries "large_cities": ["Włocławek", "Grudziądz", "Inowrocław"], # 50K+ - single query "medium_cities": ["Brodnica", "Świecie", "Chełmno", "Nakło nad Notecią", "Rypin"], # 20K+ - single query "small_cities": ["Golub-Dobrzyń", "Lipno", "Radziejów", "Żnin", "Mogilno"] # 5K+ - single query }, "Warmińsko-Mazurskie": { "major_cities": ["Olsztyn"], # 200K+ - multiple queries "large_cities": ["Elbląg", "Ełk", "Ostróda", "Iława"], # 50K+ - single query "medium_cities": ["Giżycko", "Bartoszyce", "Kętrzyn", "Szczytno", "Mrągowo"], # 20K+ - single query "small_cities": ["Pisz", "Braniewo", "Lidzbark Warmiński", "Działdowo", "Nowe Miasto Lubawskie"] # 5K+ - single query }, "Zachodniopomorskie": { "major_cities": ["Szczecin"], # 400K+ - multiple queries "large_cities": ["Koszalin", "Stargard", "Kołobrzeg", "Świnoujście"], # 50K+ - single query "medium_cities": ["Szczecinek", "Police", "Wałcz", "Białogard", "Goleniów"], # 20K+ - single query "small_cities": ["Choszczno", "Drawsko Pomorskie", "Myślibórz", "Chojna", "Pyrzyce"] # 5K+ - single query }, "Lubuskie": { "major_cities": ["Zielona Góra", "Gorzów Wielkopolski"], # 100K+ - single query each "large_cities": ["Żary", "Żagań", "Sulechów"], # 50K+ - single query "medium_cities": ["Nowa Sól", "Kostrzyn nad Odrą", "Świebodzin", "Międzyrzecz", "Słubice"], # 20K+ - single query "small_cities": ["Wschowa", "Lubsko", "Krosno Odrzańskie", "Gubin", "Cybinka"] # 5K+ - single query }, "Opolskie": { "major_cities": ["Opole"], # 120K+ - single query "large_cities": ["Kędzierzyn-Koźle", "Nysa", "Brzeg", "Kluczbork"], # 50K+ - single query "medium_cities": ["Strzelce Opolskie", "Krapkowice", "Głubczyce", "Głuchołazy", "Paczków"], # 20K+ - single query "small_cities": ["Zdzieszowice", "Gogolin", "Leśnica", "Prudnik", "Głuchołazy"] # 5K+ - single query }, "Świętokrzyskie": { "major_cities": ["Kielce"], # 200K+ - multiple queries "large_cities": ["Ostrowiec Świętokrzyski", "Starachowice", "Skarżysko-Kamienna", "Sandomierz"], # 50K+ - single query "medium_cities": ["Końskie", "Busko-Zdrój", "Jędrzejów", "Włoszczowa", "Pinczów"], # 20K+ - single query "small_cities": ["Staszów", "Opatów", "Sędziszów", "Wąchock", "Ćmielów"] # 5K+ - single query } } # Query strategy based on city size and business type QUERY_STRATEGY = { "major_cities": 3, # 3 queries for major cities (different areas/districts) "large_cities": 1, # 1 query for large cities "medium_cities": 1, # 1 query for medium cities "small_cities": 1 # 1 query for small cities } # Rural business strategy - includes surrounding areas RURAL_BUSINESS_STRATEGY = { "major_cities": 4, # 4 queries: city + surrounding areas "large_cities": 2, # 2 queries: city + surrounding areas "medium_cities": 2, # 2 queries: city + surrounding areas "small_cities": 2 # 2 queries: city + surrounding areas } # Business types that benefit from rural search RURAL_BUSINESS_TYPES = [ "hurtownia nawozów", "hurtownia chemiczna", "sklep rolniczy", "maszyny rolnicze", "pasze", "nasiona", "nawozy", "pestycydy", "sprzęt rolniczy", "traktory", "kombajny", "opryskiwacze", "agrochemikalia", "środki ochrony roślin", "nawozy sztuczne" ] # Cost per query (SerpAPI pricing) COST_PER_QUERY = 0.0005 # $0.0005 per query st.set_page_config( page_title="SerpAPI Business Finder", page_icon="🏢", layout="wide" ) # Initialize database and scraper @st.cache_resource def get_database_manager(): return DatabaseManager() @st.cache_resource def get_contact_scraper(): return ContactScraper() # Initialize session state if 'db_manager' not in st.session_state: st.session_state.db_manager = get_database_manager() if 'contact_scraper' not in st.session_state: st.session_state.contact_scraper = get_contact_scraper() if 'scraping_in_progress' not in st.session_state: st.session_state.scraping_in_progress = False if 'scraping_result' not in st.session_state: st.session_state.scraping_result = "" def is_rural_business(query): """Check if the business type benefits from rural search strategy""" query_lower = query.lower() return any(rural_type in query_lower for rural_type in RURAL_BUSINESS_TYPES) def get_query_strategy(query): """Get appropriate query strategy based on business type""" if is_rural_business(query): return RURAL_BUSINESS_STRATEGY return QUERY_STRATEGY def run_contact_scraping(dataset_name, search_type, progress_bar, status_text): """Run contact scraping in background thread""" try: # Initialize database manager and scraper in thread db_manager = DatabaseManager() contact_scraper = ContactScraper() # Get companies that need contact collection companies = db_manager.get_companies_for_contact_collection(dataset_name, search_type) if not companies: st.session_state.scraping_result = "Brak firm do zbierania kontaktów" return # Scrape contacts results = contact_scraper.batch_scrape_contacts(companies) # Update database with results updated_count = 0 emails_found = 0 phones_found = 0 for result in results: if result['status'] == 'success': company_id = result['company_id'] company_name = result.get('company_name', 'Unknown') updates = {} print(f"Processing company {company_id}: {company_name}") print(f"Found emails: {result.get('emails', [])}") print(f"Found phones: {result.get('phones', [])}") if result['emails']: updates['email'] = '; '.join(result['emails']) emails_found += len(result['emails']) print(f"Adding email: {updates['email']}") if result['phones']: # Get existing phone from the company data company_data = next((c for c in companies if c['id'] == company_id), None) existing_phone = company_data.get('phone', '') if company_data else '' new_phones = '; '.join(result['phones']) if existing_phone: updates['phone'] = f"{existing_phone}; {new_phones}" else: updates['phone'] = new_phones phones_found += len(result['phones']) print(f"Adding phone: {updates['phone']}") if updates: print(f"Updating company {company_id} with: {updates}") success = db_manager.update_company(company_id, updates) if success: print(f"Successfully updated company {company_id}") db_manager.mark_contact_collected( company_id, bool(result['emails']), bool(result['phones']) ) updated_count += 1 else: print(f"Failed to update company {company_id}") else: print(f"No updates needed for company {company_id}") # Store result in session state for display result_text = f"Zakończono! Zaktualizowano {updated_count} firm" if emails_found > 0: result_text += f", znaleziono {emails_found} emaili" if phones_found > 0: result_text += f", znaleziono {phones_found} telefonów" st.session_state.scraping_result = result_text # Clear cache to show updated data cache_key = f"companies_{dataset_name}" if cache_key in st.session_state: del st.session_state[cache_key] cache_key_stats = f"dataset_stats_{dataset_name}" if cache_key_stats in st.session_state: del st.session_state[cache_key_stats] # Clear contact collection cache for key in list(st.session_state.keys()): if key.startswith(f"companies_needing_contacts_{dataset_name}"): del st.session_state[key] # Trigger page refresh to show updated data st.session_state.should_refresh = True except Exception as e: st.session_state.scraping_result = f"Błąd podczas zbierania kontaktów: {str(e)}" finally: st.session_state.scraping_in_progress = False def upload_to_huggingface(): """Upload plików projektu do Hugging Face Hub""" if not HF_AVAILABLE: st.error("❌ Hugging Face Hub nie jest zainstalowany!") st.info("Zainstaluj: `pip install huggingface_hub`") return False try: # Użyj tokenu z pliku if HF_TOKEN: api = HfApi(token=HF_TOKEN) try: user_info = api.whoami() st.info(f"👤 Zalogowany jako: {user_info['name']}") except Exception as e: st.error(f"❌ Nieprawidłowy token Hugging Face! Błąd: {e}") st.info("🔑 Sprawdź token w pliku hf_token.txt") return False else: st.error("❌ Nie znaleziono tokenu Hugging Face!") st.info("🔑 Utwórz plik hf_token.txt z twoim tokenem") st.info("📝 Format pliku: jedna linia z tokenem zaczynającym się od 'hf_'") return False # Konfiguracja repozytorium repo_id = st.text_input( "Nazwa repozytorium (np. username/leadfinder-app):", value="ZygmuntL/Leadfinder1", help="Format: nazwa-uzytkownika/nazwa-repozytorium" ) if not repo_id or "/" not in repo_id: st.error("❌ Nieprawidłowa nazwa repozytorium!") return False # Pliki do uploadowania files_to_upload = [ "database_manager.py", "contact_scraper.py", "requirements.txt" ] # Opcjonalnie - upload głównego pliku jako app.py if st.checkbox("📤 Upload głównego pliku (app.py)", value=False): files_to_upload.append(("simple_serpapi_app.py", "app.py")) if st.button("🚀 Upload do Hugging Face"): with st.spinner("Uploadowanie plików..."): success_count = 0 for file_item in files_to_upload: # Obsługa tuple (lokalna_nazwa, zdalna_nazwa) lub string if isinstance(file_item, tuple): file_path, remote_path = file_item else: file_path = file_item remote_path = file_item if os.path.exists(file_path): try: # Sprawdź czy repozytorium istnieje, jeśli nie - utwórz try: api.repo_info(repo_id=repo_id, repo_type="model") except: st.info(f"🆕 Tworzenie nowego repozytorium: {repo_id}") api.create_repo(repo_id=repo_id, repo_type="model", exist_ok=True) # Upload pliku api.upload_file( path_or_fileobj=file_path, path_in_repo=remote_path, repo_id=repo_id, commit_message=f"Update {remote_path} - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" ) success_count += 1 st.success(f"✅ {file_path} uploaded as {remote_path}!") except Exception as e: st.error(f"❌ Błąd uploadowania {file_path}: {e}") st.info(f"🔍 Szczegóły błędu: {str(e)}") else: st.warning(f"⚠️ Plik {file_path} nie istnieje") if success_count > 0: st.success(f"🎉 Uploadowano {success_count} plików!") st.info(f"🔗 Link: https://huggingface.co/{repo_id}") else: st.error("❌ Nie udało się uploadować żadnych plików!") return True except Exception as e: st.error(f"❌ Błąd: {e}") return False def calculate_search_cost(search_strategy, selected_regions=None, selected_cities=None, query=""): """Calculate estimated cost for search strategy""" total_queries = 0 if search_strategy == "Inteligentne - Cała Polska": # Calculate based on city sizes for region_data in POLAND_REGIONS.values(): for city_type, cities in region_data.items(): queries_per_city = QUERY_STRATEGY[city_type] total_queries += len(cities) * queries_per_city elif search_strategy == "Inteligentne - Województwo": if selected_regions: for region in selected_regions: if region in POLAND_REGIONS: region_data = POLAND_REGIONS[region] for city_type, cities in region_data.items(): queries_per_city = QUERY_STRATEGY[city_type] total_queries += len(cities) * queries_per_city elif search_strategy == "Inteligentne - Wybrane miasta": if selected_cities: # Find city type for each selected city for city in selected_cities: for region_data in POLAND_REGIONS.values(): for city_type, cities in region_data.items(): if city in cities: total_queries += QUERY_STRATEGY[city_type] break elif search_strategy == "Tradycyjne - Województwa": total_queries = len(POLAND_REGIONS) elif search_strategy == "Tradycyjne - Główne miasta": total_queries = sum(len(cities) for region_data in POLAND_REGIONS.values() for cities in region_data.values()) elif search_strategy == "Pojedyncze miasto": total_queries = 1 estimated_cost = total_queries * COST_PER_QUERY return total_queries, estimated_cost def search_businesses_optimized(query, search_strategy, selected_regions, selected_cities, api_key, max_results_per_query=20): """Intelligent search with adaptive strategy based on city size and business type""" all_results = [] total_queries = 0 total_cost = 0 # Get appropriate query strategy based on business type query_strategy = get_query_strategy(query) is_rural = is_rural_business(query) # Determine search locations based on strategy search_locations = [] if search_strategy == "Inteligentne - Cała Polska": # Search all cities with appropriate query count per city type for region_name, region_data in POLAND_REGIONS.items(): for city_type, cities in region_data.items(): queries_per_city = query_strategy[city_type] for city in cities: for i in range(queries_per_city): if queries_per_city > 1: if is_rural: # For rural businesses, search city + surrounding areas if i == 0: search_locations.append(city) else: # Add surrounding area modifiers for rural search rural_modifiers = ["okolice", "gmina", "powiat", "rejon"] modifier = rural_modifiers[(i-1) % len(rural_modifiers)] search_locations.append(f"{modifier} {city}") else: # For urban businesses, search different city areas area_modifiers = ["centrum", "północ", "południe", "wschód", "zachód"] modifier = area_modifiers[i % len(area_modifiers)] search_locations.append(f"{city} {modifier}") else: search_locations.append(city) elif search_strategy == "Inteligentne - Województwo": for region in selected_regions: if region in POLAND_REGIONS: region_data = POLAND_REGIONS[region] for city_type, cities in region_data.items(): queries_per_city = query_strategy[city_type] for city in cities: for i in range(queries_per_city): if queries_per_city > 1: if is_rural: if i == 0: search_locations.append(city) else: rural_modifiers = ["okolice", "gmina", "powiat", "rejon"] modifier = rural_modifiers[(i-1) % len(rural_modifiers)] search_locations.append(f"{modifier} {city}") else: area_modifiers = ["centrum", "północ", "południe", "wschód", "zachód"] modifier = area_modifiers[i % len(area_modifiers)] search_locations.append(f"{city} {modifier}") else: search_locations.append(city) elif search_strategy == "Inteligentne - Wybrane miasta": for city in selected_cities: # Find city type and apply appropriate query count for region_data in POLAND_REGIONS.values(): for city_type, cities in region_data.items(): if city in cities: queries_per_city = query_strategy[city_type] for i in range(queries_per_city): if queries_per_city > 1: if is_rural: if i == 0: search_locations.append(city) else: rural_modifiers = ["okolice", "gmina", "powiat", "rejon"] modifier = rural_modifiers[(i-1) % len(rural_modifiers)] search_locations.append(f"{modifier} {city}") else: area_modifiers = ["centrum", "północ", "południe", "wschód", "zachód"] modifier = area_modifiers[i % len(area_modifiers)] search_locations.append(f"{city} {modifier}") else: search_locations.append(city) break elif search_strategy == "Tradycyjne - Województwa": search_locations = list(POLAND_REGIONS.keys()) elif search_strategy == "Tradycyjne - Główne miasta": for region_data in POLAND_REGIONS.values(): for cities in region_data.values(): search_locations.extend(cities) elif search_strategy == "Pojedyncze miasto": search_locations = [selected_cities[0]] if selected_cities else [] # Progress bar progress_bar = st.progress(0) status_text = st.empty() for i, location in enumerate(search_locations): status_text.text(f"Wyszukiwanie w: {location} ({i+1}/{len(search_locations)})") try: results = search_businesses(query, location, api_key, max_results_per_query) # Check if we hit rate limit if not results and total_queries > 0: st.warning("⚠️ **Wykryto limit zapytań - przerywam wyszukiwanie**") break all_results.extend(results) total_queries += 1 total_cost += COST_PER_QUERY # Update progress progress_bar.progress((i + 1) / len(search_locations)) # Longer delay to avoid rate limiting time.sleep(1.0) except Exception as e: st.error(f"Błąd wyszukiwania w {location}: {str(e)}") # If we get an error, stop searching to avoid more API calls break # Remove duplicates based on name and address unique_results = [] seen = set() for result in all_results: key = (result['name'], result['address']) if key not in seen: seen.add(key) unique_results.append(result) status_text.text(f"Zakończono! Znaleziono {len(unique_results)} unikalnych firm z {total_queries} zapytań") progress_bar.empty() status_text.empty() return unique_results, total_queries, total_cost def search_businesses(query, location, api_key, num_results=20): """Search for businesses using SerpAPI""" results = [] # SerpAPI returns up to 20 results per request for Google Maps params = { 'q': f"{query} {location}", 'api_key': api_key, 'engine': 'google_maps' } try: response = requests.get('https://serpapi.com/search', params=params) data = response.json() # Handle different response status codes if response.status_code == 429: st.error("🚫 **Limit zapytań SerpAPI przekroczony!**") st.warning(""" **Co to oznacza:** - Przekroczyłeś miesięczny limit zapytań do SerpAPI - Sprawdź swój plan w panelu SerpAPI - Możesz zwiększyć limit lub poczekać do następnego miesiąca **Rozwiązania:** 1. Sprawdź swój plan w https://serpapi.com/dashboard 2. Rozważ upgrade do wyższego planu 3. Użyj mniejszej liczby zapytań (wybierz "Pojedyncze miasto") """) return results elif response.status_code != 200: st.error(f"❌ **Błąd API: {response.status_code}**") st.write(f"Response: {data}") return results # Debug info (only show if there's an issue) if 'error' in data: st.error(f"🚫 **API Error: {data['error']}**") # Provide specific guidance for common errors if "quota" in data['error'].lower() or "limit" in data['error'].lower(): st.warning(""" **Limit zapytań przekroczony!** - Sprawdź swój plan w https://serpapi.com/dashboard - Rozważ upgrade lub poczekaj do następnego miesiąca """) elif "invalid" in data['error'].lower() and "key" in data['error'].lower(): st.error("**Nieprawidłowy klucz API!** Sprawdź klucz w pliku .env") return results if 'local_results' in data and data['local_results']: for business in data['local_results']: result = { 'name': business.get('title', ''), 'address': business.get('address', ''), 'phone': business.get('phone', ''), 'website': business.get('website', ''), 'rating': str(business.get('rating', '')), 'reviews': str(business.get('reviews', '')), 'category': business.get('type', ''), 'hours': business.get('hours', ''), 'price': business.get('price', ''), 'description': business.get('snippet', '') } results.append(result) else: st.warning("No local_results found in API response") # Show what we got instead st.write("Available data:", data) except Exception as e: st.error(f"Error fetching data: {str(e)}") return results[:num_results] def main(): st.title("🏢 SerpAPI Business Finder - Optymalizacja Kosztów") st.markdown("Inteligentne wyszukiwanie firm z optymalizacją obszarów i kosztów") # Database status st.info("🗄️ **Baza danych SQLite jest aktywna** - wszystkie wyniki są automatycznie zapisywane") # API limits warning with st.expander("⚠️ **Informacje o limitach SerpAPI**"): st.markdown(""" **🚫 Błąd 429 - "Too Many Requests":** **Co to oznacza:** - Przekroczyłeś miesięczny limit zapytań do SerpAPI - Twój plan ma ograniczoną liczbę zapytań miesięcznie **Jak sprawdzić swój limit:** 1. Idź na https://serpapi.com/dashboard 2. Sprawdź sekcję "Usage" lub "Billing" 3. Zobacz ile zapytań zostało Ci w tym miesiącu **Rozwiązania:** - **Natychmiastowe**: Użyj "Pojedyncze miasto" (1 zapytanie = $0.0005) - **Krótkoterminowe**: Upgrade do wyższego planu SerpAPI - **Długoterminowe**: Poczekaj do następnego miesiąca (limity resetują się) **💡 Wskazówki:** - Zawsze testuj z "Pojedyncze miasto" przed większymi wyszukiwaniami - Monitoruj swoje użycie w dashboard SerpAPI - Rozważ plan z większym limitem jeśli często używasz aplikacji """) # Tips section with st.expander("💡 Inteligentne strategie wyszukiwania"): st.markdown(""" **🧠 Inteligentne strategie (REKOMENDOWANE):** 🎯 **Pojedyncze miasto** - $0.0005 (1 zapytanie) - Najtańsza opcja, idealna do testów 🏙️ **Inteligentne - Wybrane miasta** - Dostosowuje zapytania do wielkości miasta - Warszawa: 3 zapytania (centrum, północ, południe) = $0.0015 - Gdańsk: 3 zapytania = $0.0015 - Małe miasto (5K): 1 zapytanie = $0.0005 - **Przykład**: 5 miast różnej wielkości = ~$0.005 🗺️ **Inteligentne - Województwo** - Optymalizuje całe województwo - Pomorskie: ~25 zapytań = $0.0125 - Mazowieckie: ~35 zapytań = $0.0175 - **Zaleta**: Znajdzie firmy w małych miastach (5K+ mieszkańców) 🇵🇱 **Inteligentne - Cała Polska** - ~400 zapytań = $0.20 - Pokrywa wszystkie miasta 5K+ mieszkańców - Warszawa: 3 zapytania, małe miasto: 1 zapytanie - **Rezultat**: ~8000+ firm z całej Polski **📊 Tradycyjne strategie (dla porównania):** 🗺️ **Tradycyjne - Województwa** - $0.008 (16 zapytań) - Tylko nazwy województw, może przegapić małe miasta 🏢 **Tradycyjne - Główne miasta** - $0.08 (160 zapytań) - Tylko duże miasta, pomija małe miejscowości **💡 Rekomendacje dla różnych biznesów:** **🏙️ Biznesy miejskie** (myjnia samochodowa, restauracja, sklep): - **Test**: "Pojedyncze miasto" (Warszawa) - $0.0005 - **Region**: "Inteligentne - Województwo" (Pomorskie) - $0.0125 - **Cała Polska**: "Inteligentne - Cała Polska" - $0.20 **🌾 Biznesy wiejskie** (hurtownia nawozów, maszyny rolnicze, pasze): - **Automatycznie wykrywane** - aplikacja dostosowuje strategię - **Więcej zapytań** - miasto + okolice + gmina + powiat - **Znajdzie firmy** w małych miejscowościach i na wsi - **Przykład**: "hurtownia nawozów" w Pomorskim - ~50 zapytań = $0.025 **🔍 Jak działa wyszukiwanie:** - **SerpAPI** wyszukuje w **promieniu** wokół podanej lokalizacji - **"Pomorskie"** = całe województwo + okolice - **"Gdańsk"** = miasto + okoliczne gminy - **"okolice Gdańsk"** = wsie i małe miejscowości wokół Gdańska """) # Sidebar with st.sidebar: st.header("⚙️ Konfiguracja") # API Key input api_key = st.text_input("SerpAPI Key", value=os.getenv('SERPAPI_KEY', ''), type="password") if api_key and api_key != 'your_serpapi_key_here': st.success("✅ SerpAPI Key skonfigurowany") else: st.error("❌ SerpAPI Key nie jest skonfigurowany") st.info("Dodaj SERPAPI_KEY do pliku .env") st.markdown("---") # Initialize query variable query = st.text_input("Wyszukiwana fraza", value="myjnia samochodowa", help="np. myjnia samochodowa, hurtownia chemiczna") # Dataset management st.header("📊 Zarządzanie datasetami") # Get existing datasets (with caching) if 'datasets_cache' not in st.session_state: st.session_state.datasets_cache = st.session_state.db_manager.get_datasets() datasets = st.session_state.datasets_cache dataset_names = [d['name'] for d in datasets] # Dataset selection dataset_choice = st.selectbox( "Wybierz dataset", ["(nowy)"] + dataset_names, help="Wybierz istniejący dataset lub utwórz nowy" ) if dataset_choice == "(nowy)": dataset_name = st.text_input("Nazwa nowego datasetu", value=f"dataset_{datetime.now().strftime('%Y%m%d_%H%M')}") if st.button("Utwórz dataset"): if dataset_name: # Use empty string for business_type when creating dataset success = st.session_state.db_manager.create_dataset(dataset_name, "", "") if success: # Clear datasets cache when creating new dataset if 'datasets_cache' in st.session_state: del st.session_state.datasets_cache st.success(f"Dataset '{dataset_name}' utworzony!") st.rerun() else: st.error("Dataset o tej nazwie już istnieje!") else: dataset_name = dataset_choice # Show dataset stats (with caching) cache_key_stats = f"dataset_stats_{dataset_name}" if cache_key_stats not in st.session_state: st.session_state[cache_key_stats] = st.session_state.db_manager.get_dataset_stats(dataset_name) stats = st.session_state[cache_key_stats] col1, col2, col3, col4, col5 = st.columns(5) with col1: st.metric("Firmy", stats['total_companies']) with col2: st.metric("Z emailami", stats['with_email']) with col3: st.metric("Z telefonami", stats['with_phone']) with col4: st.metric("Ze stronami WWW", stats['with_website']) with col5: st.metric("Potrzebują kontaktów", stats.get('need_contact_collection', 0)) st.markdown("---") # Search strategy st.header("🎯 Strategia wyszukiwania") search_strategy = st.selectbox( "Wybierz strategię", [ "Pojedyncze miasto", "Inteligentne - Wybrane miasta", "Inteligentne - Województwo", "Inteligentne - Cała Polska", "Tradycyjne - Województwa", "Tradycyjne - Główne miasta" ], help="Inteligentne strategie dostosowują liczbę zapytań do wielkości miasta" ) st.markdown("---") # Search parameters st.header("🔍 Parametry wyszukiwania") max_results_per_query = st.slider("Wyników na zapytanie", 10, 20, 20, help="Maksymalnie 20 na zapytanie (limit SerpAPI)") # Location selection based on strategy selected_regions = [] selected_cities = [] if search_strategy == "Pojedyncze miasto": all_cities = [] for region_data in POLAND_REGIONS.values(): for cities in region_data.values(): all_cities.extend(cities) selected_cities = [st.selectbox("Wybierz miasto", all_cities)] elif search_strategy == "Inteligentne - Wybrane miasta": all_cities = [] for region_data in POLAND_REGIONS.values(): for cities in region_data.values(): all_cities.extend(cities) selected_cities = st.multiselect("Wybierz miasta", all_cities, default=["Warszawa", "Kraków", "Gdańsk"]) elif search_strategy == "Inteligentne - Województwo": selected_regions = st.multiselect("Wybierz województwa", list(POLAND_REGIONS.keys()), default=["Pomorskie"]) # Show cost for selected strategy if search_strategy in ["Inteligentne - Cała Polska", "Tradycyjne - Województwa", "Tradycyjne - Główne miasta"]: queries, cost = calculate_search_cost(search_strategy, query=query) st.info(f"💰 Szacowany koszt: {queries} zapytań = ${cost:.2f}") potential_results = queries * max_results_per_query st.info(f"📊 Potencjalne wyniki: {queries} zapytań × {max_results_per_query} = {potential_results} firm (przed usunięciem duplikatów)") elif selected_regions or selected_cities: queries, cost = calculate_search_cost(search_strategy, selected_regions, selected_cities, query) if queries > 0: st.info(f"💰 Szacowany koszt: {queries} zapytań = ${cost:.2f}") potential_results = queries * max_results_per_query st.info(f"📊 Potencjalne wyniki: {queries} zapytań × {max_results_per_query} = {potential_results} firm (przed usunięciem duplikatów)") # Show business type detection if is_rural_business(query): st.success("🌾 Wykryto biznes wiejski - zastosowano strategię rozszerzoną (miasto + okolice)") else: st.info("🏙️ Biznes miejski - standardowa strategia wyszukiwania") # Main content if not api_key or api_key == 'your_serpapi_key_here': st.warning("Proszę skonfigurować klucz SerpAPI w panelu bocznym") return # Validation if search_strategy in ["Inteligentne - Wybrane miasta", "Pojedyncze miasto"] and not selected_cities: st.warning("Proszę wybrać miasta do wyszukiwania") return elif search_strategy == "Inteligentne - Województwo" and not selected_regions: st.warning("Proszę wybrać województwa do wyszukiwania") return if st.button("🔍 Wyszukaj firmy", type="primary"): if dataset_choice == "(nowy)": st.error("Proszę najpierw utworzyć dataset!") return # Show estimated cost and warn about limits total_queries_est, estimated_cost = calculate_search_cost(search_strategy, selected_regions, selected_cities, query) st.info(f"💰 **Szacowany koszt**: ${estimated_cost:.4f} ({total_queries_est} zapytań)") if estimated_cost > 0.01: # More than 1 cent st.warning("⚠️ **Wysoki koszt wyszukiwania!** Rozważ użycie 'Pojedyncze miasto' do testów.") with st.spinner("Wyszukiwanie firm..."): results, total_queries, total_cost = search_businesses_optimized( query, search_strategy, selected_regions, selected_cities, api_key, max_results_per_query ) if results: # Add location info to results for result in results: result['location'] = f"{query} {search_strategy}" # Save to database added_count = st.session_state.db_manager.add_companies(results, dataset_name, query) # Clear cache when adding new companies cache_key = f"companies_{dataset_name}" if cache_key in st.session_state: del st.session_state[cache_key] cache_key_stats = f"dataset_stats_{dataset_name}" if cache_key_stats in st.session_state: del st.session_state[cache_key_stats] # Clear contact collection cache for key in list(st.session_state.keys()): if key.startswith(f"companies_needing_contacts_{dataset_name}"): del st.session_state[key] # Calculate duplicates removed potential_results = total_queries * max_results_per_query duplicates_removed = potential_results - len(results) duplicate_rate = (duplicates_removed / potential_results * 100) if potential_results > 0 else 0 st.success(f"Znaleziono {len(results)} unikalnych firm z {total_queries} zapytań") st.info(f"Zapisano {added_count} nowych firm do datasetu '{dataset_name}'") # Cost summary col1, col2, col3, col4 = st.columns(4) with col1: st.metric("Znalezione firmy", len(results)) with col2: st.metric("Zapytania API", total_queries) with col3: st.metric("Koszt", f"${total_cost:.2f}") with col4: st.metric("Duplikaty usunięte", f"{duplicates_removed} ({duplicate_rate:.1f}%)") # Display results df = pd.DataFrame(results) st.dataframe(df, width='stretch') # Download buttons timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") strategy_name = search_strategy.replace(" ", "_").replace("-", "_") csv_filename = f"{query.replace(' ', '_')}_{strategy_name}_{timestamp}.csv" csv_data = df.to_csv(index=False) st.download_button( label="📥 Pobierz CSV", data=csv_data, file_name=csv_filename, mime="text/csv" ) # Display raw data with st.expander("📋 Szczegóły danych"): st.json(results) else: st.warning("Nie znaleziono żadnych firm") # Data management section - always visible st.markdown("---") st.header("📊 Zarządzanie danymi") if dataset_choice != "(nowy)": # Show current dataset data if st.button("🔄 Odśwież dane"): # Clear cache when refreshing cache_key = f"companies_{dataset_name}" if cache_key in st.session_state: del st.session_state[cache_key] # Clear contact collection cache for key in list(st.session_state.keys()): if key.startswith(f"companies_needing_contacts_{dataset_name}"): del st.session_state[key] st.rerun() # Get companies from selected dataset (with caching) cache_key = f"companies_{dataset_name}" if cache_key not in st.session_state: st.session_state[cache_key] = st.session_state.db_manager.get_companies(dataset_name) companies_df = st.session_state[cache_key] if not companies_df.empty: st.subheader(f"Firmy w datasecie '{dataset_name}'") # Filter options col1, col2 = st.columns(2) with col1: filter_email = st.checkbox("Tylko z emailami", value=False) with col2: filter_phone = st.checkbox("Tylko z telefonami", value=False) # Apply filters filtered_df = companies_df.copy() if filter_email: filtered_df = filtered_df[filtered_df['email'].notna() & (filtered_df['email'] != '')] if filter_phone: filtered_df = filtered_df[filtered_df['phone'].notna() & (filtered_df['phone'] != '')] st.dataframe(filtered_df, width='stretch') # Contact collection section st.subheader("📧 Zbieranie kontaktów") # Contact collection options search_type = st.selectbox( "Co zbierać:", ["both", "emails_only", "phones_only"], format_func=lambda x: { "both": "📧📞 Emails i telefony (wszystko brakujące)", "emails_only": "📧 Tylko emaile (jeśli już masz telefony)", "phones_only": "📞 Tylko telefony (jeśli już masz emaile)" }[x], help="Wybierz co chcesz zbierać ze stron WWW" ) # Show how many companies need contact collection (with caching) cache_key_contacts = f"companies_needing_contacts_{dataset_name}_{search_type}" if cache_key_contacts not in st.session_state: st.session_state[cache_key_contacts] = st.session_state.db_manager.get_companies_for_contact_collection(dataset_name, search_type) companies_needing_contacts = st.session_state[cache_key_contacts] if companies_needing_contacts: search_type_text = { "both": "kontaktów (emaili lub telefonów)", "emails_only": "emaili", "phones_only": "telefonów" }[search_type] st.info(f"🔍 Znaleziono {len(companies_needing_contacts)} firm, które potrzebują zbierania {search_type_text}") else: st.success("✅ Wszystkie firmy mają już kompletne dane kontaktowe!") col1, col2 = st.columns(2) with col1: button_text = { "both": "🔍 Zbierz emaile i telefony", "emails_only": "📧 Zbierz tylko emaile", "phones_only": "📞 Zbierz tylko telefony" }[search_type] if st.button(button_text, disabled=st.session_state.scraping_in_progress or not companies_needing_contacts): if not st.session_state.scraping_in_progress and companies_needing_contacts: # Start scraping in background st.session_state.scraping_in_progress = True st.session_state.scraping_result = f"Rozpoczynam zbieranie {search_type_text} dla {len(companies_needing_contacts)} firm..." # Run in thread to avoid blocking thread = threading.Thread( target=run_contact_scraping, args=(dataset_name, search_type, None, None) ) thread.start() st.rerun() # Show scraping status if st.session_state.scraping_in_progress: st.info("🔄 Zbieranie kontaktów w toku...") elif hasattr(st.session_state, 'scraping_result'): st.success(st.session_state.scraping_result) # Check if we should refresh the page if hasattr(st.session_state, 'should_refresh') and st.session_state.should_refresh: del st.session_state.should_refresh st.rerun() with col2: if st.button("🗑️ Usuń dataset", type="secondary"): if st.session_state.db_manager.delete_dataset(dataset_name): # Clear all cache when deleting dataset if 'datasets_cache' in st.session_state: del st.session_state.datasets_cache cache_key = f"companies_{dataset_name}" if cache_key in st.session_state: del st.session_state[cache_key] cache_key_stats = f"dataset_stats_{dataset_name}" if cache_key_stats in st.session_state: del st.session_state[cache_key_stats] # Clear contact collection cache for key in list(st.session_state.keys()): if key.startswith(f"companies_needing_contacts_{dataset_name}"): del st.session_state[key] st.success(f"Dataset '{dataset_name}' usunięty!") st.rerun() else: st.error("Błąd podczas usuwania datasetu") # Data management tabs tab1, tab2, tab3, tab4 = st.tabs(["✏️ Edycja firm", "➕ Dodawanie firm", "☁️ Upload do HF", "🔄 Aktualizacja Space"]) with tab1: st.subheader("✏️ Ręczne uzupełnianie danych") # Select company to edit company_options = [(f"{row['name']} - {row['address']}", row['id']) for _, row in companies_df.iterrows()] selected_company = st.selectbox("Wybierz firmę do edycji", company_options) if selected_company: company_id = selected_company[1] company_data = companies_df[companies_df['id'] == company_id].iloc[0] col1, col2 = st.columns(2) with col1: new_email = st.text_input("Email", value=company_data.get('email', '')) new_phone = st.text_input("Telefon", value=company_data.get('phone', '')) with col2: new_website = st.text_input("Strona WWW", value=company_data.get('website', '')) new_address = st.text_input("Adres", value=company_data.get('address', '')) if st.button("💾 Zapisz zmiany"): updates = {} if new_email != company_data.get('email', ''): updates['email'] = new_email if new_phone != company_data.get('phone', ''): updates['phone'] = new_phone if new_website != company_data.get('website', ''): updates['website'] = new_website if new_address != company_data.get('address', ''): updates['address'] = new_address if updates: if st.session_state.db_manager.update_company(company_id, updates): st.success("Dane zaktualizowane!") st.rerun() else: st.error("Błąd podczas aktualizacji") with tab2: st.subheader("➕ Dodawanie nowej firmy") with st.form("add_company"): col1, col2 = st.columns(2) with col1: new_name = st.text_input("Nazwa firmy:", placeholder="np. Myjnia ABC") new_email = st.text_input("Email:", placeholder="kontakt@firma.pl") with col2: new_phone = st.text_input("Telefon:", placeholder="+48 123 456 789") new_website = st.text_input("Strona WWW:", placeholder="https://www.firma.pl") new_address = st.text_input("Adres:", placeholder="ul. Przykładowa 1, 00-000 Warszawa") new_business_type = st.text_input("Wyszukiwana fraza:", value="myjnia samochodowa") if st.form_submit_button("Dodaj firmę"): if new_name: # Create company data company_data = { 'name': new_name, 'email': new_email, 'phone': new_phone, 'website': new_website, 'address': new_address, 'business_type': new_business_type, 'dataset_name': dataset_name } success = st.session_state.db_manager.add_companies([company_data]) if success: # Clear cache when adding new company cache_key = f"companies_{dataset_name}" if cache_key in st.session_state: del st.session_state[cache_key] cache_key_stats = f"dataset_stats_{dataset_name}" if cache_key_stats in st.session_state: del st.session_state[cache_key_stats] # Clear contact collection cache for key in list(st.session_state.keys()): if key.startswith(f"companies_needing_contacts_{dataset_name}"): del st.session_state[key] st.success("Firma dodana!") st.rerun() else: st.error("Błąd podczas dodawania firmy!") else: st.error("Nazwa firmy jest wymagana!") with tab3: st.subheader("☁️ Upload do Hugging Face Hub") st.markdown(""" **📤 Automatyczne uploadowanie plików projektu:** - ✅ `simple_serpapi_app.py` - główna aplikacja - ✅ `database_manager.py` - zarządzanie bazą danych - ✅ `contact_scraper.py` - zbieranie kontaktów - ✅ `requirements.txt` - zależności **🔧 Wymagania:** 1. Zainstaluj: `pip install huggingface_hub` 2. Zaloguj się: `huggingface-cli login` 3. Utwórz repozytorium na https://huggingface.co/new """) upload_to_huggingface() with tab4: st.subheader("🔄 Jak zaktualizować Space na Hugging Face") st.markdown(""" **📋 Krok po kroku aktualizacja Space:** ### 🎯 **Metoda 1: Przez aplikację (najłatwiejsza)** 1. **W tej aplikacji** → zakładka "☁️ Upload do HF" 2. **Wpisz nazwę repozytorium** (np. `ZygmuntL/leadfinder`) 3. **Kliknij "🚀 Upload do Hugging Face"** 4. **Space automatycznie się zaktualizuje!** ### 🎯 **Metoda 2: Ręcznie przez Git** 1. **Otwórz terminal** w folderze projektu 2. **Wykonaj komendy:** ```bash git add . git commit -m "Update application" git push ``` 3. **Space automatycznie się zaktualizuje!** ### 🎯 **Metoda 3: Upload przez stronę** 1. **Idź na** https://huggingface.co/spaces/ZygmuntL/leadfinder 2. **Kliknij "Files"** → "Add file" → "Upload files" 3. **Przeciągnij pliki** (`app.py`, `database_manager.py`, etc.) 4. **Kliknij "Commit changes"** ### ⚡ **Automatyczne aktualizacje:** - **Każda zmiana** w kodzie → Space się aktualizuje - **Nowe funkcje** → dostępne od razu - **Poprawki błędów** → działają natychmiast ### 🔧 **Ważne pliki do aktualizacji:** - ✅ `app.py` (główna aplikacja) - ✅ `database_manager.py` (baza danych) - ✅ `contact_scraper.py` (zbieranie kontaktów) - ✅ `requirements.txt` (zależności) - ✅ `README.md` (dokumentacja) """) st.info("💡 **Wskazówka:** Używaj Metody 1 - najłatwiejsza i najszybsza!") # Table editing st.subheader("📝 Edycja tabeli") # Editable table st.write("**Kliknij na komórkę aby edytować, zaznacz wiersze aby przenieść:**") # Add checkbox column for selection companies_df_with_checkbox = companies_df.copy() companies_df_with_checkbox['Zaznacz'] = False # Create editable dataframe with selection edited_df = st.data_editor( companies_df_with_checkbox[['Zaznacz', 'id', 'name', 'email', 'phone', 'website', 'address']], num_rows="dynamic", width='stretch', key=f"editor_{dataset_name}", column_config={ "Zaznacz": st.column_config.CheckboxColumn("Zaznacz", help="Zaznacz firmy do przeniesienia"), "id": st.column_config.NumberColumn("ID", disabled=True), "name": st.column_config.TextColumn("Nazwa firmy"), "email": st.column_config.TextColumn("Email"), "phone": st.column_config.TextColumn("Telefon"), "website": st.column_config.TextColumn("Strona WWW"), "address": st.column_config.TextColumn("Adres") } ) # Dataset transfer section st.write("**Przenoszenie zaznaczonych firm:**") # Get all datasets all_datasets = st.session_state.db_manager.get_datasets() dataset_names = [d['name'] for d in all_datasets if d['name'] != dataset_name] if dataset_names: col1, col2 = st.columns(2) with col1: target_dataset = st.selectbox("Przenieś do datasetu:", dataset_names) with col2: if st.button("📦 Przenieś zaznaczone firmy"): # Get selected rows from checkboxes selected_companies = edited_df[edited_df['Zaznacz'] == True] if len(selected_companies) > 0: success_count = 0 duplicate_count = 0 for _, row in selected_companies.iterrows(): company_id = row['id'] company_data = companies_df[companies_df['id'] == company_id].iloc[0] # Check for duplicates in target dataset existing_companies = st.session_state.db_manager.get_companies(target_dataset) is_duplicate = False for _, existing in existing_companies.iterrows(): if (existing['name'] == company_data['name'] and existing['address'] == company_data['address']): is_duplicate = True break if is_duplicate: duplicate_count += 1 st.warning(f"Firma '{company_data['name']}' już istnieje w '{target_dataset}' - pominięto") else: success = st.session_state.db_manager.update_company( company_id, {'dataset_name': target_dataset} ) if success: success_count += 1 if success_count > 0: # Clear cache when moving companies cache_key = f"companies_{dataset_name}" if cache_key in st.session_state: del st.session_state[cache_key] cache_key_stats = f"dataset_stats_{dataset_name}" if cache_key_stats in st.session_state: del st.session_state[cache_key_stats] # Clear target dataset cache target_cache_key = f"companies_{target_dataset}" if target_cache_key in st.session_state: del st.session_state[target_cache_key] target_cache_key_stats = f"dataset_stats_{target_dataset}" if target_cache_key_stats in st.session_state: del st.session_state[target_cache_key_stats] # Clear contact collection cache for both datasets for key in list(st.session_state.keys()): if key.startswith(f"companies_needing_contacts_{dataset_name}") or key.startswith(f"companies_needing_contacts_{target_dataset}"): del st.session_state[key] st.success(f"Przeniesiono {success_count} firm do '{target_dataset}'!") if duplicate_count > 0: st.info(f"Pominięto {duplicate_count} duplikatów") st.rerun() else: st.error("Błąd podczas przenoszenia lub wszystkie firmy to duplikaty!") else: st.warning("Zaznacz firmy checkboxami aby je przenieść") else: st.info("Brak innych datasetów do przeniesienia.") # Save changes button if st.button("💾 Zapisz zmiany w tabeli"): changes_made = False # Check if rows were deleted if len(edited_df) < len(companies_df): # Some rows were deleted original_ids = set(companies_df['id'].tolist()) current_ids = set(edited_df['id'].tolist()) deleted_ids = original_ids - current_ids if deleted_ids: for company_id in deleted_ids: # Delete company from database if st.session_state.db_manager.delete_company(company_id): changes_made = True st.info(f"Usunięto firmę o ID: {company_id}") # Check for edited rows for idx, row in edited_df.iterrows(): # Find corresponding original row by ID original_row = companies_df[companies_df['id'] == row['id']] if not original_row.empty: original_row = original_row.iloc[0] # Check for changes (ignore checkbox column) updates = {} if row['name'] != original_row['name']: updates['name'] = row['name'] if row['email'] != original_row['email']: updates['email'] = row['email'] if row['phone'] != original_row['phone']: updates['phone'] = row['phone'] if row['website'] != original_row['website']: updates['website'] = row['website'] if row['address'] != original_row['address']: updates['address'] = row['address'] if updates: company_id = original_row['id'] if st.session_state.db_manager.update_company(company_id, updates): changes_made = True if changes_made: # Clear cache when saving changes cache_key = f"companies_{dataset_name}" if cache_key in st.session_state: del st.session_state[cache_key] cache_key_stats = f"dataset_stats_{dataset_name}" if cache_key_stats in st.session_state: del st.session_state[cache_key_stats] # Clear contact collection cache for key in list(st.session_state.keys()): if key.startswith(f"companies_needing_contacts_{dataset_name}"): del st.session_state[key] st.success("Zmiany zapisane!") st.rerun() else: st.info("Brak zmian do zapisania") # Download options st.subheader("📥 Pobieranie danych") if not companies_df.empty: col1, col2, col3 = st.columns(3) with col1: if st.button("📊 Pobierz wszystkie dane"): csv_data = companies_df.to_csv(index=False) st.download_button( label="💾 Pobierz CSV", data=csv_data, file_name=f"{dataset_name}_all_data.csv", mime="text/csv" ) with col2: if st.button("📧 Pobierz z emailami"): email_df = companies_df[companies_df['email'].notna() & (companies_df['email'] != '')] if not email_df.empty: csv_data = email_df.to_csv(index=False) st.download_button( label="💾 Pobierz CSV", data=csv_data, file_name=f"{dataset_name}_with_emails.csv", mime="text/csv" ) else: st.warning("Brak firm z emailami") with col3: if st.button("📞 Pobierz z telefonami"): phone_df = companies_df[companies_df['phone'].notna() & (companies_df['phone'] != '')] if not phone_df.empty: csv_data = phone_df.to_csv(index=False) st.download_button( label="💾 Pobierz CSV", data=csv_data, file_name=f"{dataset_name}_with_phones.csv", mime="text/csv" ) else: st.warning("Brak firm z telefonami") else: st.info("Dataset jest pusty. Wykonaj wyszukiwanie, aby dodać firmy.") else: st.info("💡 Wybierz dataset z listy powyżej, aby zobaczyć dane i zarządzać nimi") st.markdown(""" **Dostępne funkcje po wybraniu datasetu:** - 📊 Przeglądanie firm w datasecie - 🔍 Automatyczne zbieranie emaili i telefonów ze stron WWW - ✏️ Ręczne uzupełnianie danych firm - 📥 Pobieranie danych w różnych formatach CSV - 🗑️ Usuwanie datasetów """) if __name__ == "__main__": main()