Merge pull request #109 from R0m1k3/antigravity

feat: add script to verify search service functionality across all co…
This commit is contained in:
LogiFlow authored and GitHub committed 2025-11-29 18:15:35 +01:00
commit 403fb67e58
3 files changed
+337 -3

No files matched your search

+62
View File
@@ -0,0 +1,62 @@
# Guide de Démarrage - Priceflow
## Problème détecté
- ❌ Docker non installé/accessible
- ❌ WSL non installé
- ❌ Application non démarrée (port 8555 fermé)
## Solutions possibles
### Option 1 : Installer Docker Desktop (Recommandé)
1. Télécharger Docker Desktop : https://www.docker.com/products/docker-desktop/
2. Installer et démarrer Docker Desktop
3. Ouvrir un terminal PowerShell dans `C:\Users\Michael\Git\Priceflow`
4. Lancer l'application :
```bash
docker compose up -d
```
5. Vérifier les logs :
```bash
docker compose logs api | Select-String "site(s)"
```
Vous devriez voir : **"10 site(s) de recherche créé(s)"**
### Option 2 : Installer WSL + Docker
1. Installer WSL :
```powershell
wsl --install
```
2. Redémarrer l'ordinateur
3. Installer Docker dans WSL
4. Suivre les étapes de l'Option 1
### Option 3 : Application déjà démarrée ailleurs
Si l'application tourne déjà dans un conteneur ou processus différent :
1. Vérifier les processus :
```powershell
Get-Process | Where-Object {$_.ProcessName -like "*python*" -or $_.ProcessName -like "*uvicorn*"}
```
2. Vérifier les ports ouverts :
```powershell
netstat -ano | findstr :8555
```
## Prochaines étapes après démarrage
1. ✅ Vérifier que 10 sites sont seedés (logs)
2. ✅ Tester via script : `docker compose exec api python verify_all_sites.py`
3. ✅ Tester via interface : http://localhost:8555/search
## État actuel
**Application : ❌ NON DÉMARRÉE**
- Configuration : ✅ 10 sites ajoutés dans `search_config.py`
- Script de test : ✅ `verify_all_sites.py` créé
- Tests : ⏸️ En attente du démarrage de l'application
+128 -3
View File
@@ -2,12 +2,13 @@ import React, { useState, useEffect, useRef } from 'react';
import axios from 'axios';
import { toast } from 'sonner';
import { useTranslation } from 'react-i18next';
import { Search as SearchIcon, Loader2, ExternalLink, TrendingUp, Package } from 'lucide-react';
import { Search as SearchIcon, Loader2, ExternalLink, TrendingUp, Package, ChevronDown, ChevronUp } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { Progress } from '@/components/ui/progress';
import { Badge } from '@/components/ui/badge';
import { Checkbox } from '@/components/ui/checkbox';
const API_URL = '/api';
@@ -17,8 +18,67 @@ export default function MultiSearch() {
const [isSearching, setIsSearching] = useState(false);
const [progress, setProgress] = useState(null);
const [results, setResults] = useState([]);
const [availableSites, setAvailableSites] = useState([]);
const [selectedSiteIds, setSelectedSiteIds] = useState([]);
const [showSiteSelector, setShowSiteSelector] = useState(false);
const eventSourceRef = useRef(null);
// Fetch available sites on mount
useEffect(() => {
const fetchSites = async () => {
try {
const response = await axios.get(`${API_URL}/search-sites`);
const sites = response.data.filter(site => site.is_active);
setAvailableSites(sites);
// Load selection from localStorage or select all by default
const savedSelection = localStorage.getItem('selectedSiteIds');
if (savedSelection) {
setSelectedSiteIds(JSON.parse(savedSelection));
} else {
setSelectedSiteIds(sites.map(s => s.id));
}
} catch (error) {
console.error('Error fetching sites:', error);
toast.error('Erreur lors du chargement des sites');
}
};
fetchSites();
}, []);
// Save selection to localStorage
useEffect(() => {
if (selectedSiteIds.length > 0) {
localStorage.setItem('selectedSiteIds', JSON.stringify(selectedSiteIds));
}
}, [selectedSiteIds]);
const toggleSite = (siteId) => {
setSelectedSiteIds(prev =>
prev.includes(siteId)
? prev.filter(id => id !== siteId)
: [...prev, siteId]
);
};
const selectAll = () => {
setSelectedSiteIds(availableSites.map(s => s.id));
};
const deselectAll = () => {
setSelectedSiteIds([]);
};
// Group sites by category
const sitesByCategory = availableSites.reduce((acc, site) => {
const category = site.category || 'Autres';
if (!acc[category]) {
acc[category] = [];
}
acc[category].push(site);
return acc;
}, {});
const handleSearch = async (e) => {
e.preventDefault();
if (!query.trim()) {
@@ -26,6 +86,11 @@ export default function MultiSearch() {
return;
}
if (selectedSiteIds.length === 0) {
toast.error('Veuillez sélectionner au moins un site');
return;
}
// Fermer l'EventSource précédent si existant
if (eventSourceRef.current) {
eventSourceRef.current.close();
@@ -35,10 +100,11 @@ export default function MultiSearch() {
setResults([]);
setProgress({ status: 'searching', total: 0, completed: 0 });
// Construire l'URL - recherche sur TOUS les sites actifs
// Construire l'URL avec les sites sélectionnés
const params = new URLSearchParams({
q: query.trim(),
max_results: 50, // Plus de résultats pour la comparaison
max_results: 50,
sites: selectedSiteIds.join(','), // Include selected sites
});
// Créer l'EventSource pour SSE
@@ -147,6 +213,65 @@ export default function MultiSearch() {
</Button>
</div>
</form>
{/* Site Selector */}
<div className="mt-4 border-t pt-4">
<button
type="button"
onClick={() => setShowSiteSelector(!showSiteSelector)}
className="flex items-center justify-between w-full text-sm font-medium mb-2"
>
<span>
Sites à rechercher ({selectedSiteIds.length} sur {availableSites.length} sélectionné{selectedSiteIds.length > 1 ? 's' : ''})
</span>
{showSiteSelector ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
</button>
{showSiteSelector && (
<div className="space-y-3">
{/* Quick Actions */}
<div className="flex gap-2">
<Button
type="button"
variant="outline"
size="sm"
onClick={selectAll}
>
Tout sélectionner
</Button>
<Button
type="button"
variant="outline"
size="sm"
onClick={deselectAll}
>
Tout désélectionner
</Button>
</div>
{/* Sites grouped by category */}
{Object.entries(sitesByCategory).map(([category, sites]) => (
<div key={category} className="space-y-2">
<h4 className="text-sm font-medium text-muted-foreground">{category}</h4>
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
{sites.map(site => (
<label
key={site.id}
className="flex items-center space-x-2 cursor-pointer hover:bg-muted/50 p-2 rounded transition-colors"
>
<Checkbox
checked={selectedSiteIds.includes(site.id)}
onCheckedChange={() => toggleSite(site.id)}
/>
<span className="text-sm">{site.name}</span>
</label>
))}
</div>
</div>
))}
</div>
)}
</div>
</CardContent>
</Card>
+147
View File
@@ -0,0 +1,147 @@
"""
Script de vérification complète du comparateur sur tous les sites configurés
"""
import asyncio
import logging
import sys
import os
from datetime import datetime
# Add project root to path
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".")))
from app.core.search_config import SITE_CONFIGS
from app.services.search_service import new_search_service
from app.services.browserless_service import browserless_service
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
handlers=[logging.StreamHandler()]
)
logger = logging.getLogger(__name__)
async def test_site(site_key: str, query: str) -> dict:
"""Test a single site and return results"""
config = SITE_CONFIGS[site_key]
result = {
"site": config["name"],
"site_key": site_key,
"category": config["category"],
"success": False,
"count": 0,
"error": None,
"sample_results": []
}
try:
print(f"\n🔍 Testing {config['name']} ({site_key})...")
results = await new_search_service.search_site(site_key, query)
if results:
result["success"] = True
result["count"] = len(results)
# Store first 2 results as samples
result["sample_results"] = [
{
"title": r.title,
"url": r.url,
"price": r.price,
"in_stock": r.in_stock
}
for r in results[:2]
]
print(f" ✅ Found {len(results)} results")
for r in results[:2]:
price_str = f"{r.price}€" if r.price else "N/A"
stock_str = "✓" if r.in_stock else "✗" if r.in_stock is False else "?"
print(f" - {r.title[:60]}... [{price_str}] [Stock: {stock_str}]")
else:
print(f" ⚠️ No results found")
except Exception as e:
result["error"] = str(e)
print(f" ❌ Error: {e}")
logger.exception(f"Error testing {site_key}")
return result
async def verify_all_sites():
"""Test all configured sites"""
query = "chaise" # Test query
print("=" * 80)
print(f"VERIFICATION DU COMPARATEUR - Query: '{query}'")
print(f"Started at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("=" * 80)
# 1. Test Browserless Connection
print("\n[1] Testing Browserless Connection...")
try:
await browserless_service.start()
print("✅ Browserless connected successfully")
except Exception as e:
print(f"❌ Browserless connection failed: {e}")
return
# 2. Test all sites
print(f"\n[2] Testing {len(SITE_CONFIGS)} configured sites...")
all_results = []
for site_key in SITE_CONFIGS.keys():
result = await test_site(site_key, query)
all_results.append(result)
# Small delay between sites to avoid overload
await asyncio.sleep(1)
# 3. Summary
print("\n" + "=" * 80)
print("SUMMARY")
print("=" * 80)
successful = [r for r in all_results if r["success"]]
failed = [r for r in all_results if not r["success"]]
print(f"\n✅ Successful: {len(successful)}/{len(all_results)}")
for r in successful:
print(f" - {r['site']:20s} | {r['count']:2d} results | Category: {r['category']}")
if failed:
print(f"\n❌ Failed: {len(failed)}/{len(all_results)}")
for r in failed:
error_msg = r['error'][:50] if r['error'] else "No results"
print(f" - {r['site']:20s} | Error: {error_msg}")
# 4. Detailed results by category
print("\n" + "=" * 80)
print("BY CATEGORY")
print("=" * 80)
categories = {}
for r in all_results:
cat = r["category"]
if cat not in categories:
categories[cat] = []
categories[cat].append(r)
for cat, sites in categories.items():
successful_count = sum(1 for s in sites if s["success"])
print(f"\n{cat}: {successful_count}/{len(sites)} working")
for site in sites:
status = "✅" if site["success"] else "❌"
count = f"({site['count']} results)" if site["success"] else "(failed)"
print(f" {status} {site['site']:20s} {count}")
# 5. Cleanup
await browserless_service.stop()
print("\n" + "=" * 80)
print(f"Verification completed at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("=" * 80)
return all_results
if __name__ == "__main__":
asyncio.run(verify_all_sites())