Intégration JavaScript du moteur de recherche

Cette documentation explique comment intégrer le moteur de recherche hybride de Cléa-API dans une application JavaScript/TypeScript.

Configuration du client

import axios from 'axios';

const API_URL = 'http://localhost:8080'; // Ou votre URL de déploiement

const cleaClient = axios.create({
  baseURL: API_URL,
  headers: {
    'Content-Type': 'application/json',
  },
});

Effectuer une recherche simple

async function searchDocuments(query, topK = 10) {
  try {
    const response = await cleaClient.post('/search/hybrid_search', {
      query,
      topK,
    });
    return response.data;
  } catch (error) {
    console.error('Erreur lors de la recherche:', error);
    throw error;
  }
}

// Utilisation
searchDocuments('analyse des risques')
  .then(results => {
    console.log(`${results.totalResults} résultats trouvés`);
    results.results.forEach(chunk => {
      console.log(`[${chunk.score.toFixed(2)}] ${chunk.title}: ${chunk.content.slice(0, 100)}...`);
    });
  });

Recherche avancée avec confiance et normalisation

async function advancedSearch(query, options = {}) {
  const defaultOptions = {
    topK: 10,
    theme: null,
    documentType: null,
    startDate: null,
    endDate: null,
    corpusId: null,
    hierarchical: false,
    filterByRelevance: true,
    normalizeScores: true,
  };

  const searchParams = {
    query,
    ...defaultOptions,
    ...options,
  };

  try {
    const response = await cleaClient.post('/search/hybrid_search', searchParams);
    return response.data;
  } catch (error) {
    console.error('Erreur lors de la recherche avancée:', error);
    throw error;
  }
}

// Utilisation avec évaluation de la confiance
advancedSearch('bonnes pratiques développement durable', {
  theme: 'RSE',
  filterByRelevance: true,
  normalizeScores: true,
})
.then(results => {
  // Analyser le niveau de confiance
  const { confidence } = results;
  console.log(`Confiance: ${confidence.level.toFixed(2)} - ${confidence.message}`);

  // Afficher les statistiques
  console.log(`Stats: min=${confidence.stats.min.toFixed(2)}, max=${confidence.stats.max.toFixed(2)}`);

  // Gérer le cas où la requête est hors domaine
  if (confidence.level < 0.3) {
    console.log('⚠️ La requête semble être hors du domaine de connaissance');
    // Afficher un message à l'utilisateur
  }

  // Afficher les résultats s'il y en a
  if (results.results.length > 0) {
    results.results.forEach(chunk => {
      console.log(`[${chunk.score.toFixed(2)}] ${chunk.title}`);
    });
  } else {
    console.log('Aucun résultat pertinent trouvé');
  }
});

Composant React d'exemple

import React, { useState, useEffect } from 'react';
import axios from 'axios';

const API_URL = 'http://localhost:8080';

function SearchComponent() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [confidence, setConfidence] = useState(null);
  const [isSearching, setIsSearching] = useState(false);
  const [error, setError] = useState(null);

  const handleSearch = async () => {
    if (!query.trim()) return;

    setIsSearching(true);
    setError(null);

    try {
      const response = await axios.post(`${API_URL}/search/hybrid_search`, {
        query: query.trim(),
        topK: 10,
        filterByRelevance: true,
        normalizeScores: true
      });

      setResults(response.data.results);
      setConfidence(response.data.confidence);
    } catch (err) {
      setError('Erreur lors de la recherche');
      console.error(err);
    } finally {
      setIsSearching(false);
    }
  };

  return (
    <div className="search-container">
      <div className="search-bar">
        <input
          type="text"
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          placeholder="Rechercher..."
        />
        <button onClick={handleSearch} disabled={isSearching}>
          {isSearching ? 'Recherche...' : 'Rechercher'}
        </button>
      </div>

      {confidence && (
        <div className={`confidence-meter confidence-${Math.floor(confidence.level * 10)}`}>
          <div className="confidence-label">
            Pertinence: {(confidence.level * 100).toFixed(0)}%
          </div>
          <div className="confidence-message">{confidence.message}</div>
        </div>
      )}

      {error && <div className="error-message">{error}</div>}

      <div className="results-list">
        {results.length === 0 && !isSearching && query && (
          <p>Aucun résultat pertinent trouvé.</p>
        )}

        {results.map((result) => (
          <div key={result.chunkId} className="result-item">
            <div className="result-header">
              <h3>{result.title}</h3>
              <span className="score">{(result.score * 100).toFixed(0)}% pertinent</span>
            </div>
            <div className="result-content">{result.content}</div>
            <div className="result-meta">
              Thème: {result.theme} | Type: {result.documentType} | 
              Date: {new Date(result.publishDate).toLocaleDateString()}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

export default SearchComponent;