mirror of
https://github.com/R0m1k3/Priceflow.git
synced 2026-10-11 17:29:14 +02:00
Merge pull request #109 from R0m1k3/antigravity
feat: add script to verify search service functionality across all co…
This commit is contained in:
3 files changed
+337
-3
No files matched your search
@@ -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
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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())
|
||||
Reference in new issue
Block a user