Spaces:
Sleeping
Sleeping
| <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Scrap-Dji - Exemple Frontend</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| padding: 20px; | |
| } | |
| .container { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| background: white; | |
| border-radius: 20px; | |
| padding: 40px; | |
| box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); | |
| } | |
| h1 { | |
| color: #333; | |
| margin-bottom: 10px; | |
| font-size: 2.5em; | |
| } | |
| .subtitle { | |
| color: #666; | |
| margin-bottom: 30px; | |
| font-size: 1.1em; | |
| } | |
| .search-box { | |
| display: flex; | |
| gap: 10px; | |
| margin-bottom: 20px; | |
| } | |
| input[type="text"] { | |
| flex: 1; | |
| padding: 15px 20px; | |
| border: 2px solid #ddd; | |
| border-radius: 10px; | |
| font-size: 16px; | |
| transition: border-color 0.3s; | |
| } | |
| input[type="text"]:focus { | |
| outline: none; | |
| border-color: #667eea; | |
| } | |
| button { | |
| padding: 15px 30px; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| border: none; | |
| border-radius: 10px; | |
| font-size: 16px; | |
| font-weight: bold; | |
| cursor: pointer; | |
| transition: transform 0.2s; | |
| } | |
| button:hover { | |
| transform: translateY(-2px); | |
| } | |
| button:active { | |
| transform: translateY(0); | |
| } | |
| .filters { | |
| display: flex; | |
| gap: 15px; | |
| margin-bottom: 30px; | |
| flex-wrap: wrap; | |
| } | |
| select, | |
| .checkbox-label { | |
| padding: 10px 15px; | |
| border: 2px solid #ddd; | |
| border-radius: 8px; | |
| font-size: 14px; | |
| } | |
| .checkbox-label { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| cursor: pointer; | |
| } | |
| .stats { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
| gap: 20px; | |
| margin-bottom: 30px; | |
| } | |
| .stat-card { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| padding: 20px; | |
| border-radius: 10px; | |
| text-align: center; | |
| } | |
| .stat-value { | |
| font-size: 2em; | |
| font-weight: bold; | |
| margin-bottom: 5px; | |
| } | |
| .stat-label { | |
| font-size: 0.9em; | |
| opacity: 0.9; | |
| } | |
| .results { | |
| margin-top: 30px; | |
| } | |
| .result-item { | |
| background: #f8f9fa; | |
| padding: 20px; | |
| border-radius: 10px; | |
| margin-bottom: 15px; | |
| border-left: 4px solid #667eea; | |
| transition: transform 0.2s; | |
| } | |
| .result-item:hover { | |
| transform: translateX(5px); | |
| } | |
| .result-title { | |
| font-size: 1.3em; | |
| color: #333; | |
| margin-bottom: 10px; | |
| font-weight: bold; | |
| } | |
| .result-meta { | |
| color: #666; | |
| font-size: 0.9em; | |
| margin-bottom: 10px; | |
| } | |
| .result-excerpt { | |
| color: #444; | |
| line-height: 1.6; | |
| margin-bottom: 10px; | |
| } | |
| .result-link { | |
| color: #667eea; | |
| text-decoration: none; | |
| font-weight: bold; | |
| } | |
| .result-link:hover { | |
| text-decoration: underline; | |
| } | |
| .loading { | |
| text-align: center; | |
| padding: 40px; | |
| color: #666; | |
| } | |
| .error { | |
| background: #fee; | |
| color: #c33; | |
| padding: 15px; | |
| border-radius: 8px; | |
| margin-bottom: 20px; | |
| } | |
| .badge { | |
| display: inline-block; | |
| padding: 4px 10px; | |
| background: #667eea; | |
| color: white; | |
| border-radius: 12px; | |
| font-size: 0.8em; | |
| margin-right: 5px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>🌍 Scrap-Dji</h1> | |
| <p class="subtitle">Base de Connaissance Panafricaine - Recherche Intelligente</p> | |
| <!-- Statistiques --> | |
| <div class="stats" id="stats"> | |
| <div class="stat-card"> | |
| <div class="stat-value" id="total-docs">-</div> | |
| <div class="stat-label">Documents</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-value" id="total-pays">-</div> | |
| <div class="stat-label">Pays</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-value" id="total-sources">-</div> | |
| <div class="stat-label">Sources</div> | |
| </div> | |
| </div> | |
| <!-- Recherche --> | |
| <div class="search-box"> | |
| <input type="text" id="search-input" | |
| placeholder="Recherchez des articles (ex: économie togo, politique bénin...)" | |
| onkeypress="if(event.key==='Enter') search()"> | |
| <button onclick="search()">🔍 Rechercher</button> | |
| </div> | |
| <!-- Filtres --> | |
| <div class="filters"> | |
| <select id="pays-filter"> | |
| <option value="">Tous les pays</option> | |
| <option value="Togo">Togo</option> | |
| <option value="Bénin">Bénin</option> | |
| <option value="Afrique">Afrique</option> | |
| </select> | |
| <select id="langue-filter"> | |
| <option value="">Toutes les langues</option> | |
| <option value="fr">Français</option> | |
| <option value="en">English</option> | |
| </select> | |
| <label class="checkbox-label"> | |
| <input type="checkbox" id="fuzzy-checkbox" checked> | |
| Recherche permissive (tolérance aux fautes) | |
| </label> | |
| </div> | |
| <!-- Résultats --> | |
| <div class="results" id="results"></div> | |
| </div> | |
| <script> | |
| // ⚠️ REMPLACEZ PAR L'URL DE VOTRE SPACE HUGGING FACE | |
| const API_URL = 'http://localhost:7860'; // Pour test local | |
| // const API_URL = 'https://VOTRE_USERNAME-scrap-dji.hf.space'; // Pour production | |
| // Charger les statistiques au démarrage | |
| loadStats(); | |
| async function loadStats() { | |
| try { | |
| const response = await fetch(`${API_URL}/api/stats`); | |
| const data = await response.json(); | |
| document.getElementById('total-docs').textContent = data.total_documents; | |
| document.getElementById('total-pays').textContent = Object.keys(data.pays).length; | |
| document.getElementById('total-sources').textContent = Object.keys(data.sources).length; | |
| } catch (error) { | |
| console.error('Erreur chargement stats:', error); | |
| } | |
| } | |
| async function search() { | |
| const query = document.getElementById('search-input').value.trim(); | |
| if (!query) { | |
| alert('Veuillez entrer une requête de recherche'); | |
| return; | |
| } | |
| const pays = document.getElementById('pays-filter').value; | |
| const langue = document.getElementById('langue-filter').value; | |
| const fuzzy = document.getElementById('fuzzy-checkbox').checked; | |
| const resultsDiv = document.getElementById('results'); | |
| resultsDiv.innerHTML = '<div class="loading">🔍 Recherche en cours...</div>'; | |
| try { | |
| const response = await fetch(`${API_URL}/api/search`, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| query: query, | |
| pays: pays || null, | |
| langue: langue || null, | |
| limit: 20, | |
| fuzzy: fuzzy | |
| }) | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`Erreur HTTP: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| displayResults(data); | |
| } catch (error) { | |
| resultsDiv.innerHTML = ` | |
| <div class="error"> | |
| ❌ Erreur lors de la recherche: ${error.message} | |
| <br><br> | |
| Vérifiez que l'API est accessible à l'adresse: ${API_URL} | |
| </div> | |
| `; | |
| } | |
| } | |
| function displayResults(data) { | |
| const resultsDiv = document.getElementById('results'); | |
| if (data.total === 0) { | |
| resultsDiv.innerHTML = ` | |
| <div class="loading"> | |
| ❌ Aucun résultat trouvé pour "${data.query}" | |
| <br><br> | |
| Essayez avec d'autres mots-clés ou activez la recherche permissive | |
| </div> | |
| `; | |
| return; | |
| } | |
| let html = ` | |
| <h2>📊 ${data.total} résultat(s) trouvé(s) en ${data.execution_time_ms}ms</h2> | |
| <p style="color: #666; margin-bottom: 20px;">Requête: "${data.query}"</p> | |
| `; | |
| data.results.forEach((result, index) => { | |
| const titre = result.titre || 'Sans titre'; | |
| const texte = result.texte || ''; | |
| const excerpt = texte.substring(0, 200) + (texte.length > 200 ? '...' : ''); | |
| const pays = result.pays || 'Inconnu'; | |
| const langue = result.langue || 'fr'; | |
| const source = result.source_url || '#'; | |
| const score = result._score || 0; | |
| html += ` | |
| <div class="result-item"> | |
| <div class="result-title">${index + 1}. ${titre}</div> | |
| <div class="result-meta"> | |
| <span class="badge">${pays}</span> | |
| <span class="badge">${langue.toUpperCase()}</span> | |
| <span class="badge">Score: ${score.toFixed(1)}</span> | |
| </div> | |
| <div class="result-excerpt">${excerpt}</div> | |
| <a href="${source}" target="_blank" class="result-link">🔗 Lire l'article complet</a> | |
| </div> | |
| `; | |
| }); | |
| resultsDiv.innerHTML = html; | |
| } | |
| </script> | |
| </body> | |
| </html> |