From 1b44361b6a96d835771b6e8b7d87e967fac8149b Mon Sep 17 00:00:00 2001
From: Lucas <lbometon@hotmail.fr>
Date: Fri, 17 Jan 2025 09:08:55 +0100
Subject: [PATCH] Update metaSearchAgent.ts

---
 src/search/metaSearchAgent.ts | 352 +++++++++++++++-------------------
 1 file changed, 152 insertions(+), 200 deletions(-)

diff --git a/src/search/metaSearchAgent.ts b/src/search/metaSearchAgent.ts
index 5e4254a..da971bf 100644
--- a/src/search/metaSearchAgent.ts
+++ b/src/search/metaSearchAgent.ts
@@ -146,6 +146,7 @@ export class MetaSearchAgent implements MetaSearchAgentType {
 
         // Recherche web si activée
         if (this.config.searchWeb) {
+          console.log('🔍 Démarrage de la recherche web...');
           const res = await searchSearxng(question, {
             language: 'fr',
             engines: this.config.activeEngines,
@@ -159,60 +160,17 @@ export class MetaSearchAgent implements MetaSearchAgentType {
                   title: result.title,
                   url: result.url,
                   type: 'web',
+                  source: 'web',
+                  displayDomain: new URL(result.url).hostname.replace('www.', ''),
+                  favicon: `https://s2.googleusercontent.com/s2/favicons?domain_url=${result.url}`,
+                  linkText: 'Voir la page',
                   ...(result.img_src && { img_src: result.img_src }),
                 },
               }),
           );
+          console.log('🌐 Sources web trouvées:', documents.length);
         }
 
-        // Recherche d'experts si activée
-        if (this.config.searchDatabase) {
-          try {
-            console.log('🔍 Recherche d\'experts...');
-            const expertResults = await handleExpertSearch(
-              {
-                query: question,
-                chat_history: [],
-                messageId: 'search_' + Date.now(),
-                chatId: 'chat_' + Date.now()
-              },
-              llm
-            );
-
-            console.log('🔍 Experts trouvés:', expertResults.experts.length);
-            const expertDocs = expertResults.experts.map(expert =>
-              new Document({
-                pageContent: `Expert: ${expert.prenom} ${expert.nom}
-                Spécialité: ${expert.specialite}
-                Ville: ${expert.ville}
-                Tarif: ${expert.tarif}€
-                Expertises: ${expert.expertises}
-                Services: ${JSON.stringify(expert.services)}
-                ${expert.biographie}`,
-                metadata: {
-                  type: 'expert',
-                  expert: true,
-                  expertData: expert,
-                  title: `${expert.specialite} - ${expert.ville}`,
-                  url: `/expert/${expert.id_expert}`,
-                  image_url: expert.image_url
-                }
-              })
-            );
-
-            documents = [...expertDocs, ...documents];
-          } catch (error) {
-            console.error('Erreur lors de la recherche d\'experts:', error);
-          }
-        }
-
-        // Trier pour mettre les experts en premier
-        documents.sort((a, b) => {
-          if (a.metadata?.type === 'expert' && b.metadata?.type !== 'expert') return -1;
-          if (a.metadata?.type !== 'expert' && b.metadata?.type === 'expert') return 1;
-          return 0;
-        });
-
         return { query: question, docs: documents };
       }),
     ]);
@@ -347,9 +305,11 @@ export class MetaSearchAgent implements MetaSearchAgentType {
           }
 
           // 3. Enfin, compléter avec la recherche web si nécessaire et si peu de résultats
-          if (this.config.searchWeb && docs.length < 3) {
+          if (this.config.searchWeb) {
             try {
+              console.log('🌐 Démarrage de la recherche web...');
               const webResults = await this.performWebSearch(input.query);
+              console.log(`🌐 ${webResults.length} résultats web trouvés`);
               docs = [...docs, ...webResults];
             } catch (error) {
               console.error('❌ Erreur lors de la recherche web:', error);
@@ -468,23 +428,30 @@ export class MetaSearchAgent implements MetaSearchAgentType {
           sources?.map(source => {
             const isUploadedDoc = source.metadata?.type === 'uploaded';
             const isExpert = source.metadata?.type === 'expert';
-            const pageNumber = source.metadata?.pageNumber || 1;
+            const isWeb = source.metadata?.type === 'web';
             const sourceId = source.metadata?.source;
 
             // Construire l'URL selon le type de source
             let url;
             if (isUploadedDoc && sourceId) {
-              url = `/api/uploads/${sourceId}/content?page=${pageNumber}`;
+              const page = source.metadata?.pageNumber || source.metadata?.page || 1;
+              console.log(`🔍 Construction URL pour source ${sourceId} - Page ${page}`, source.metadata);
+              url = `/api/uploads/${sourceId}/content?page=${page}`;
             } else if (isExpert) {
               url = source.metadata?.url;
-            } else if (source.metadata?.type === 'web') {
+            } else if (isWeb) {
               url = source.metadata?.url;
+              console.log('🌐 Source web trouvée:', {
+                title: source.metadata?.title,
+                url: url
+              });
             }
 
             // Construire un titre descriptif
             let title = source.metadata?.title || '';
             if (isUploadedDoc && title) {
-              title = `${title} - Page ${pageNumber}`;
+              const page = source.metadata?.pageNumber || source.metadata?.page || 1;
+              title = `${title} - Page ${page}`;
             } else if (isExpert) {
               title = source.metadata?.displayTitle || title;
             }
@@ -498,22 +465,28 @@ export class MetaSearchAgent implements MetaSearchAgentType {
                 title: title,
                 type: source.metadata?.type || 'web',
                 url: url,
-                source: sourceId,
-                pageNumber: pageNumber,
+                source: sourceId || (isWeb ? 'web' : undefined),
+                pageNumber: source.metadata?.pageNumber || source.metadata?.page || 1,
+                displayDomain: isUploadedDoc ? 'Document local' : 
+                             isWeb ? new URL(url).hostname.replace('www.', '') : undefined,
                 searchText:
                   source.metadata?.searchText?.substring(0, 200) ||
                   limitedContent.substring(0, 200),
                 expertData: source.metadata?.expertData,
                 illustrationImage: source.metadata?.illustrationImage,
                 imageTitle: source.metadata?.imageTitle,
-                favicon: source.metadata?.favicon,
-                linkText: source.metadata?.linkText,
-                expertName: source.metadata?.expertName
+                favicon: isWeb ? `https://s2.googleusercontent.com/s2/favicons?domain_url=${url}` : source.metadata?.favicon,
+                linkText: isWeb ? 'Voir la page' : 'Voir la source',
+                expertName: source.metadata?.expertName,
+                fileId: sourceId,
+                page: source.metadata?.pageNumber || source.metadata?.page || 1,
+                isFile: isUploadedDoc
               }
             };
           }) || [];
 
         console.log('🔍 Sources normalisées:', normalizedSources.length);
+        console.log('🔍 Types de sources:', normalizedSources.map(s => s.metadata.type));
 
         emitter.emit(
           'data',
@@ -545,10 +518,13 @@ export class MetaSearchAgent implements MetaSearchAgentType {
 
   private async handleStreamWithMemory(
     stream: IterableReadableStream<StreamEvent>,
-    emitter: eventEmitter
+    emitter: eventEmitter,
+    llm: BaseChatModel,
+    originalQuery: string
   ) {
     let fullAssistantResponse = '';
-
+    let hasEmittedSuggestions = false;
+  
     for await (const event of stream) {
       if (event.event === 'on_chain_stream') {
         if (event.name === 'FinalResponseGenerator') {
@@ -559,72 +535,102 @@ export class MetaSearchAgent implements MetaSearchAgentType {
           );
         }
       } else if (event.event === 'on_chain_end') {
-        if (event.name === 'FinalResponseGenerator') {
+        if (event.name === 'FinalResponseGenerator' && !hasEmittedSuggestions) {
+          try {
+            const suggestionsPrompt = `
+            Based on this conversation and response, suggest 3 relevant follow-up questions:
+            "${fullAssistantResponse}"
+            Return only the questions, one per line.`;
+            
+            const suggestionsResponse = await llm.invoke(suggestionsPrompt);
+            const suggestions = String(suggestionsResponse.content)
+              .split('\n')
+              .filter(s => s.trim())
+              .slice(0, 3);
+
+            // Émettre uniquement les suggestions
+            emitter.emit(
+              'data',
+              JSON.stringify({
+                type: 'suggestions',
+                data: {
+                  suggestions: suggestions,
+                  suggestedExperts: []
+                }
+              })
+            );
+
+            hasEmittedSuggestions = true;
+          } catch (error) {
+            console.error('❌ Erreur lors de la génération des suggestions:', error);
+          }
+
           this.updateMemory(new AIMessage(fullAssistantResponse.trim()));
           emitter.emit('end');
         }
         if (event.name === 'FinalSourceRetriever') {
           const sources = event.data.output;
-          const normalizedSources =
-            sources?.map(source => {
-              const isUploadedDoc = source.metadata?.type === 'uploaded';
-              const isExpert = source.metadata?.type === 'expert';
-              const pageNumber = source.metadata?.pageNumber || 1;
-              const sourceId = source.metadata?.source;
-
-              let url;
-              if (isUploadedDoc && sourceId) {
-                url = `/api/uploads/${sourceId}/content?page=${pageNumber}`;
-              } else if (isExpert) {
-                url = source.metadata?.url;
-              } else if (source.metadata?.type === 'web') {
-                url = source.metadata?.url;
+          const normalizedSources = sources?.map(source => {
+            const isUploadedDoc = source.metadata?.type === 'uploaded';
+            const isExpert = source.metadata?.type === 'expert';
+            const pageNumber = source.metadata?.pageNumber || source.metadata?.page || 1;
+            const sourceId = source.metadata?.source;
+            
+            let url;
+            if (isUploadedDoc && sourceId) {
+              url = `/api/uploads/${sourceId}/content?page=${pageNumber}`;
+            } else if (isExpert) {
+              url = source.metadata?.url;
+            } else if (source.metadata?.type === 'web') {
+              url = source.metadata?.url;
+            }
+            
+            let title = source.metadata?.title || '';
+            if (isUploadedDoc && title) {
+              title = `${title} - Page ${pageNumber}`;
+            } else if (isExpert) {
+              title = source.metadata?.displayTitle || title;
+            }
+            
+            const limitedContent = source.pageContent?.substring(0, 1000) || '';
+            
+            return {
+              pageContent: limitedContent,
+              metadata: {
+                title: title,
+                type: source.metadata?.type || 'web',
+                url: url,
+                source: sourceId,
+                pageNumber: pageNumber,
+                displayDomain: isUploadedDoc ? 'Document local' : undefined,
+                searchText: source.metadata?.searchText?.substring(0, 200) || limitedContent.substring(0, 200),
+                expertData: source.metadata?.expertData,
+                illustrationImage: source.metadata?.illustrationImage,
+                imageTitle: source.metadata?.imageTitle,
+                favicon: source.metadata?.favicon,
+                linkText: isUploadedDoc ? 'Voir le document' : 'Voir la source',
+                expertName: source.metadata?.expertName,
+                fileId: sourceId,
+                page: pageNumber,
+                isFile: isUploadedDoc
               }
-
-              let title = source.metadata?.title || '';
-              if (isUploadedDoc && title) {
-                title = `${title} - Page ${pageNumber}`;
-              } else if (isExpert) {
-                title = source.metadata?.displayTitle || title;
-              }
-
-              const limitedContent =
-                source.pageContent?.substring(0, 1000) || '';
-
-              return {
-                pageContent: limitedContent,
-                metadata: {
-                  title: title,
-                  type: source.metadata?.type || 'web',
-                  url: url,
-                  source: sourceId,
-                  pageNumber: pageNumber,
-                  searchText:
-                    source.metadata?.searchText?.substring(0, 200) ||
-                    limitedContent.substring(0, 200),
-                  expertData: source.metadata?.expertData,
-                  illustrationImage: source.metadata?.illustrationImage,
-                  imageTitle: source.metadata?.imageTitle,
-                  favicon: source.metadata?.favicon,
-                  linkText: source.metadata?.linkText,
-                  expertName: source.metadata?.expertName
-                }
-              };
-            }) || [];
+            };
+          }) || [];
 
           console.log('🔍 Sources normalisées:', normalizedSources.length);
-
+          
           emitter.emit(
             'data',
-            JSON.stringify({
-              type: 'sources',
+            JSON.stringify({ 
+              type: 'sources', 
               data: normalizedSources,
               illustrationImage: normalizedSources[0]?.metadata?.illustrationImage || null,
               imageTitle: normalizedSources[0]?.metadata?.imageTitle || null
             })
           );
         }
-      } else {
+        }
+      else {
         emitter.emit(event.event, event.data);
       }
     }
@@ -809,10 +815,8 @@ Prends en compte:
 
       if (uploadedDocs.length > 0) {
         try {
-          // Utiliser l'instance unique de RAGDocumentChain
           const ragChain = RAGDocumentChain.getInstance();
           
-          // Vérifier si déjà initialisé avec les mêmes documents
           if (!ragChain.isInitialized()) {
             console.log('🔄 Initialisation du vector store...');
             await ragChain.initializeVectorStoreFromDocuments(uploadedDocs, embeddings);
@@ -820,11 +824,9 @@ Prends en compte:
             console.log('✅ Vector store déjà initialisé');
           }
 
-          // Recherche sémantique
           const relevantDocs = await ragChain.searchSimilarDocuments(message, 5);
           console.log('📄 Documents pertinents trouvés:', relevantDocs.length);
 
-          // Extraction du contexte pour enrichir la recherche
           const documentContext = relevantDocs
             .map(doc => doc.pageContent)
             .join('\n')
@@ -833,44 +835,49 @@ Prends en compte:
           const documentTitle = uploadedDocs[0]?.metadata?.title || '';
           const enrichedQuery = `${message} ${documentTitle} ${documentContext}`;
 
-          // 2. Recherche d'experts si nécessaire
-          let expertResults = [];
-          if (analysis.requiresExpertSearch) {
-            expertResults = await this.searchExperts(message, embeddings, llm);
-          }
-
-          // 3. Recherche web si nécessaire
+          // Recherche web si nécessaire
           let webResults = [];
           if (analysis.requiresWebSearch) {
-            webResults = await this.searchWeb(enrichedQuery);
+            console.log('🔍 Démarrage de la recherche web...');
+            const res = await searchSearxng(enrichedQuery, {
+              language: 'fr',
+              engines: this.config.activeEngines,
+            });
+
+            webResults = res.results.map(result =>
+              new Document({
+                pageContent: result.content,
+                metadata: {
+                  title: result.title,
+                  url: result.url,
+                  type: 'web',
+                  source: 'web',
+                  ...(result.img_src && { img_src: result.img_src }),
+                },
+              })
+            );
+            console.log('🌐 Résultats web trouvés:', webResults.length);
           }
 
-          // Combinaison des résultats avec les scores appropriés
+          // Combinaison des résultats
           const combinedResults = [
             ...relevantDocs.map(doc => ({
               ...doc,
               metadata: {
                 ...doc.metadata,
-                score: 0.8 // Score élevé pour les documents uploadés
+                type: doc.metadata.type || 'uploaded'
               }
             })),
-            ...expertResults.map(expert => ({
-              ...expert,
-              metadata: {
-                ...expert.metadata,
-                score: 0.6 // Score moyen pour les experts
-              }
-            })),
-            ...webResults.map(web => ({
-              ...web,
-              metadata: {
-                ...web.metadata,
-                score: 0.4 // Score plus faible pour les résultats web
-              }
-            }))
+            ...webResults
           ];
 
-          // Tri et sélection des meilleurs résultats
+          console.log('🔄 Résultats combinés:', {
+            total: combinedResults.length,
+            uploaded: relevantDocs.length,
+            web: webResults.length,
+            types: combinedResults.map(doc => doc.metadata.type)
+          });
+
           const finalResults = await this.rerankDocs(
             message,
             combinedResults,
@@ -880,7 +887,6 @@ Prends en compte:
             llm
           );
 
-          // Création de la chaîne de réponse
           const answeringChain = await this.createAnsweringChain(
             llm,
             fileIds,
@@ -898,71 +904,17 @@ Prends en compte:
             }
           );
 
-          this.handleStreamWithMemory(stream, emitter);
+          this.handleStreamWithMemory(stream, emitter, llm, message);
         } catch (error) {
           console.error('❌ Erreur lors de la gestion des documents:', error);
-          // Fallback en mode standard
-          const answeringChain = await this.createAnsweringChain(
-            llm,
-            fileIds,
-            embeddings,
-            effectiveMode
-          );
-
-          const stream = answeringChain.streamEvents(
-            {
-              chat_history: this.conversationHistory,
-              query: message
-            },
-            {
-              version: 'v1'
-            }
-          );
-
-          this.handleStreamWithMemory(stream, emitter);
+          await this.handleFallback(llm, message, mergedHistory, emitter, fileIds, embeddings, effectiveMode);
         }
       } else {
-        // Fallback sans documents uploadés
-        const answeringChain = await this.createAnsweringChain(
-          llm,
-          fileIds,
-          embeddings,
-          effectiveMode
-        );
-
-        const stream = answeringChain.streamEvents(
-          {
-            chat_history: mergedHistory,
-            query: message
-          },
-          {
-            version: 'v1'
-          }
-        );
-
-        this.handleStreamWithMemory(stream, emitter);
+        await this.handleFallback(llm, message, mergedHistory, emitter, fileIds, embeddings, effectiveMode);
       }
     } catch (error) {
       console.error('❌ Erreur:', error);
-      // Fallback en mode standard
-      const answeringChain = await this.createAnsweringChain(
-        llm,
-        fileIds,
-        embeddings,
-        effectiveMode
-      );
-
-      const stream = answeringChain.streamEvents(
-        {
-          chat_history: this.conversationHistory,
-          query: message
-        },
-        {
-          version: 'v1'
-        }
-      );
-
-      this.handleStreamWithMemory(stream, emitter);
+      await this.handleFallback(llm, message, this.conversationHistory, emitter, fileIds, embeddings, effectiveMode);
     }
 
     return emitter;
@@ -994,7 +946,7 @@ Prends en compte:
       }
     );
 
-    this.handleStreamWithMemory(stream, emitter);
+    this.handleStreamWithMemory(stream, emitter, llm, message);
   }
 
   private async ensureVectorStoreInitialized(documents: Document[], embeddings: Embeddings): Promise<RAGDocumentChain> {
@@ -1209,7 +1161,7 @@ export const searchHandlers: Record<string, MetaSearchAgentType> = {
                   title: content.title || 'Document sans titre',
                   source: fileId,
                   type: 'uploaded',
-                  url: `/api/uploads/${fileId}/view?page=${pageNumber}`,
+                  url: `/viewer/${fileId}?page=${pageNumber}`, // URL vers le viewer Next.js
                   pageNumber: pageNumber,
                   chunkIndex: index,
                   totalChunks: chunks.length,