diff --git a/app/services/improved_search_service.py b/app/services/improved_search_service.py index 8bb4f0d..5fe4bb3 100644 --- a/app/services/improved_search_service.py +++ b/app/services/improved_search_service.py @@ -1,6 +1,21 @@ """ Improved Search Service - Using Persistent Browser Connection -from app.services.parsers import ParserFactory, ProductResult +Based on ScraperService pattern for better session management and reliability +""" + +import asyncio +import json +import logging +import re +from typing import AsyncGenerator, Optional +from urllib.parse import quote_plus, urljoin, urlparse + +from bs4 import BeautifulSoup +from playwright.async_api import Browser, BrowserContext, Page, async_playwright + +from app.core.config import settings +from app.core.search_config import SITE_CONFIGS, BROWSERLESS_URL +from app.services.ai_price_extractor import AIPriceExtractor logger = logging.getLogger(__name__) diff --git a/frontend/src/pages/Search.jsx b/frontend/src/pages/Search.jsx index 68c4e26..64e5129 100644 --- a/frontend/src/pages/Search.jsx +++ b/frontend/src/pages/Search.jsx @@ -51,236 +51,224 @@ export default function Search() { // Charger les sites au démarrage useEffect(() => { - loadSites(); - }, []); + } catch (error) { + console.error('Error loading sites:', error); + toast.error('Erreur lors du chargement des sites'); + } +}; - const loadSites = async () => { +const handleSearch = async (e) => { + e.preventDefault(); + if (!query.trim() || !selectedSiteId) { + toast.error('Veuillez entrer une recherche et sélectionner un site'); + return; + } + + // Clear previous storage when starting new search + sessionStorage.removeItem(STORAGE_KEY); + + // Fermer l'EventSource précédent si existant + if (eventSourceRef.current) { + eventSourceRef.current.close(); + } + + setIsSearching(true); + setResults([]); + setProgress({ status: 'searching', total: 1, completed: 0 }); + + // Construire l'URL avec les paramètres + const params = new URLSearchParams({ + q: query.trim(), + sites: selectedSiteId, + max_results: 20, + }); + + // Créer l'EventSource pour SSE + const eventSource = new EventSource(`${API_URL}/search?${params}`); + eventSourceRef.current = eventSource; + + eventSource.addEventListener('progress', (event) => { try { - const response = await axios.get(`${API_URL}/search-sites`); - const activeSites = response.data.filter(site => site.is_active); - setSites(activeSites); - // Sélectionner le premier site actif par défaut si aucun site n'est sélectionné - if (activeSites.length > 0 && !selectedSiteId) { - setSelectedSiteId(activeSites[0].id.toString()); - } - } catch (error) { - console.error('Error loading sites:', error); - toast.error('Erreur lors du chargement des sites'); - } - }; + const data = JSON.parse(event.data); + setProgress(data); + setResults(data.results || []); - const handleSearch = async (e) => { - e.preventDefault(); - if (!query.trim() || !selectedSiteId) { - toast.error('Veuillez entrer une recherche et sélectionner un site'); - return; - } + if (data.status === 'completed' || data.status === 'error') { + setIsSearching(false); + eventSource.close(); - // Clear previous storage when starting new search - sessionStorage.removeItem(STORAGE_KEY); - - // Fermer l'EventSource précédent si existant - if (eventSourceRef.current) { - eventSourceRef.current.close(); - } - - setIsSearching(true); - setResults([]); - setProgress({ status: 'searching', total: 1, completed: 0 }); - - // Construire l'URL avec les paramètres - const params = new URLSearchParams({ - q: query.trim(), - sites: selectedSiteId, - max_results: 20, - }); - - // Créer l'EventSource pour SSE - const eventSource = new EventSource(`${API_URL}/search?${params}`); - eventSourceRef.current = eventSource; - - eventSource.addEventListener('progress', (event) => { - try { - const data = JSON.parse(event.data); - setProgress(data); - setResults(data.results || []); - - if (data.status === 'completed' || data.status === 'error') { - setIsSearching(false); - eventSource.close(); - - if (data.status === 'completed') { - toast.success(`${data.results?.length || 0} produits trouvés`); - } else if (data.message) { - toast.error(data.message); - } + if (data.status === 'completed') { + toast.success(`${data.results?.length || 0} produits trouvés`); + } else if (data.message) { + toast.error(data.message); } - } catch (error) { - console.error('Error parsing SSE data:', error); } - }); - - eventSource.onerror = () => { - setIsSearching(false); - eventSource.close(); - toast.error('Erreur de connexion au serveur'); - }; - }; - - const handleAddToMonitoring = async (result) => { - try { - await axios.post(`${API_URL}/items`, { - url: result.url, - name: result.title, - target_price: null, - check_interval_minutes: 60, - }); - toast.success(`"${result.title}" ajouté au monitoring`); } catch (error) { - console.error('Error adding item:', error); - toast.error("Erreur lors de l'ajout au monitoring"); + console.error('Error parsing SSE data:', error); } + }); + + eventSource.onerror = () => { + setIsSearching(false); + eventSource.close(); + toast.error('Erreur de connexion au serveur'); }; +}; - const progressPercent = progress - ? Math.round((progress.completed / Math.max(progress.total, 1)) * 100) - : 0; +const handleAddToMonitoring = async (result) => { + try { + await axios.post(`${API_URL}/items`, { + url: result.url, + name: result.title, + target_price: null, + check_interval_minutes: 60, + }); + toast.success(`"${result.title}" ajouté au monitoring`); + } catch (error) { + console.error('Error adding item:', error); + toast.error("Erreur lors de l'ajout au monitoring"); + } +}; - const selectedSite = sites.find(s => s.id.toString() === selectedSiteId); +const progressPercent = progress + ? Math.round((progress.completed / Math.max(progress.total, 1)) * 100) + : 0; - return ( -
- Recherchez des produits sur un site et ajoutez-les au monitoring -
-+ Recherchez des produits sur un site et ajoutez-les au monitoring +