Améliorations recherche

This commit is contained in:
François Pelletier 2025-01-02 23:04:35 -05:00
parent 731d9bce6d
commit ad0c34fcff
10 changed files with 317 additions and 305 deletions

View file

@ -0,0 +1,40 @@
import streamlit as st
from import_data.utils.typesense_client import client
def rechercher_documents(cette_requete,
ces_filtres=None,
facette_par=None,
query_by="texte,embedding",
sort_by="_text_match:desc,creation_timestamp:desc",
nb_buckets=10,
prefix=False,
per_page=10,
page=1):
parametres_recherche = {
'q': cette_requete,
'query_by': query_by,
'sort_by': sort_by.replace(
"_text_match",
f"_text_match(buckets: {nb_buckets})"),
"exclude_fields": "embedding",
"prefix": str(prefix).lower(),
'per_page': per_page,
'page': page
}
if ces_filtres:
parametres_recherche['filter_by'] = ces_filtres
if facette_par:
parametres_recherche['facet_by'] = facette_par
st.write("Search parameters:", parametres_recherche)
all_results = []
try:
results = client.collections['social_media_posts'].documents.search(parametres_recherche)
all_results.extend(results['hits'])
return results
except Exception as e:
st.error(f"Error during search: {str(e)}") # Error handling

View file

@ -0,0 +1,19 @@
import streamlit as st
from import_data.utils.typesense_client import client
def recuperer_reseaux():
search_parameters = {
'q': '*',
'query_by': 'network',
'facet_by': 'network',
'per_page': 0
}
try:
results = client.collections['social_media_posts'].documents.search(search_parameters)
networks = [facet['value'] for facet in results['facet_counts'][0]['counts']]
return networks
except Exception as e:
st.error(f"Erreur lors de la récupération des réseaux : {str(e)}")
return ['Facebook', 'Instagram', 'Threads', 'LinkedIn', 'WordPress'] # Valeurs par défaut en cas d'erreur

View file

@ -1,134 +1,27 @@
import streamlit as st
import typesense
from datetime import datetime, time
import pandas as pd
import plotly.express as px
from dotenv import load_dotenv
import os
from import_data.utils.typesense_client import client
from search_app_ui.rechercher_documents import rechercher_documents
from search_app_ui.recuperer_reseaux import recuperer_reseaux
# Configurer la page en mode large
st.set_page_config(layout="wide")
# Forcer le thème sombre
st.markdown("""
<style>
/* Fond principal */
.stApp {
background-color: #0e1117;
color: #fafafa;
}
/* Barre latérale */
.css-1d391kg {
background-color: #262730;
}
/* Boutons */
.stButton>button {
color: #fafafa;
background-color: #262730;
border-color: #fafafa;
}
/* Champs de texte */
.stTextInput>div>div>input {
color: #fafafa;
background-color: #262730;
}
/* Boîte de sélection */
.stSelectbox>div>div>select {
color: #fafafa;
background-color: #262730;
}
/* Sélection multiple */
.stMultiSelect>div>div>select {
color: #fafafa;
background-color: #262730;
}
/* Saisie de date */
.stDateInput>div>div>input {
color: #fafafa;
background-color: #262730;
}
</style>
""", unsafe_allow_html=True)
# Load and apply the CSS
def load_css(file_name):
with open(file_name) as f:
st.markdown(f'<style>{f.read()}</style>', unsafe_allow_html=True)
# Ajouter ce CSS pour créer une zone de résultats défilable
st.markdown("""
<style>
.scrollable-results {
height: 400px;
overflow-y: scroll;
border: 1px solid #ccc;
padding: 10px;
border-radius: 5px;
}
</style>
""", unsafe_allow_html=True)
# Load the CSS file
load_css('style.css') # If the CSS file is in a subdirectory, adjust the path accordingly
# Charger les variables d'environnement
load_dotenv()
# Initialiser le client Typesense
client = typesense.Client({
'nodes': [{
'host': 'localhost',
'port': '8108',
'protocol': 'http'
}],
'api_key': os.getenv('TYPESENSE_API_KEY'),
'connection_timeout_seconds': 2
})
def rechercher_documents(cette_requete, ces_filtres=None, facette_par=None):
parametres_recherche = {
'q': cette_requete,
'query_by': 'texte,embedding',
'sort_by': '_text_match(buckets: 10):desc,creation_timestamp:desc',
"exclude_fields": "embedding",
"prefix": "false",
'per_page': 10,
'page': 1
}
if ces_filtres:
parametres_recherche['filter_by'] = ces_filtres
if facette_par:
parametres_recherche['facet_by'] = facette_par
st.write("Search parameters:", parametres_recherche)
all_results = []
try:
while True:
results = client.collections['social_media_posts'].documents.search(parametres_recherche)
all_results.extend(results['hits'])
if len(all_results) >= results['found']:
break
parametres_recherche['page'] += 1
results['hits'] = all_results
return results
except Exception as e:
st.error(f"Error during search: {str(e)}") # Error handling
# Récupérer dynamiquement les réseaux depuis Typesense
def get_networks():
search_parameters = {
'q': '*',
'query_by': 'network',
'facet_by': 'network',
'per_page': 0
}
try:
results = client.collections['social_media_posts'].documents.search(search_parameters)
networks = [facet['value'] for facet in results['facet_counts'][0]['counts']]
return networks
except Exception as e:
st.error(f"Erreur lors de la récupération des réseaux : {str(e)}")
return ['Facebook', 'Instagram', 'Threads', 'LinkedIn', 'WordPress'] # Valeurs par défaut en cas d'erreur
# Interface utilisateur Streamlit
st.title('Recherche dans tes contenus publiés sur le web')
@ -147,7 +40,7 @@ date_fin = col2.date_input('Date de fin', value=datetime.now())
# Filtre de réseau social et de langue
col3, col4 = st.columns(2)
reseaux = get_networks()
reseaux = recuperer_reseaux()
reseaux_selectionnes = col3.multiselect('Sélectionnez les réseaux sociaux', reseaux,
default=reseaux[0] if reseaux else None)
langues = [('fr', 'Français'), ('en', 'English')]
@ -156,105 +49,187 @@ langue_selectionnees = col4.multiselect('Sélectionnez la langue',
format_func=lambda x: x,
default='Français')
# Filtre sur le nombre de mots
nombre_mots = st.slider('Nombre de mots minimum', min_value=0, max_value=1000, value=100, step=10)
# Convertir les étiquettes en codes de langage
selected_lang_codes = [code for code, label in langues if label in langue_selectionnees]
# Filtre sur le nombre de mots
nombre_mots = st.slider('Nombre de mots minimum', min_value=0, max_value=1000, value=100, step=10)
# Nouvelle section pour les options de recherche avancées
st.sidebar.header("Options de recherche avancées")
# Option pour activer/désactiver les options avancées
show_advanced_options = st.sidebar.checkbox("Activer les options avancées")
if show_advanced_options:
# Champs de recherche
query_by = st.sidebar.multiselect(
"Champs de recherche",
["texte", "embedding"],
default=["texte", "embedding"]
)
# Tri
sort_options = [
"_text_match:desc", "creation_timestamp:desc",
"_text_match:asc", "creation_timestamp:asc",
]
nb_buckets = st.sidebar.number_input("Nombre de buckets pour le tri", min_value=1, value=10)
sort_by = st.sidebar.multiselect(
"Trier par",
sort_options,
default=["_text_match:desc", "creation_timestamp:desc"]
)
# Préfixe
prefix = st.sidebar.checkbox("Activer la recherche par préfixe", value=False)
# Pagination
per_page = st.sidebar.slider("Résultats par page", min_value=1, max_value=100, value=10)
page = st.sidebar.number_input("Page", min_value=1, value=1)
else:
# Valeurs par défaut si les options avancées ne sont pas affichées
query_by = ["texte", "embedding"]
sort_by = ["_text_match:desc", "creation_timestamp:desc"]
prefix = False
nb_buckets = 10
per_page = 10
page = 1
if st.button('Rechercher'):
# Préparer les filtres
debut_datetime = datetime.combine(date_debut, time.min)
fin_datetime = datetime.combine(date_fin, time.max)
filtre_date = f"creation_timestamp:[{int(debut_datetime.timestamp())}..{int(fin_datetime.timestamp())}]"
filtre_reseau = f"network:[{' '.join(reseaux_selectionnes)}]" if reseaux_selectionnes else None
filtre_langue = f"langue:[{' '.join(selected_lang_codes)}]" if selected_lang_codes else None
filtre_reseau = f"network:=[{', '.join(reseaux_selectionnes)}]" if reseaux_selectionnes else None
filtre_langue = f"langue:=[{', '.join(selected_lang_codes)}]" if selected_lang_codes else None
filtre_mots = f"nombre_de_mots:[{nombre_mots}..10000]" if nombre_mots > 0 else None
filtres = ' && '.join(filter(None, [filtre_date, filtre_reseau, filtre_langue, filtre_mots]))
liste_filtres = []
if filtre_date:
liste_filtres.append(filtre_date)
if filtre_reseau:
liste_filtres.append(filtre_reseau)
if filtre_langue:
liste_filtres.append(filtre_langue)
if filtre_mots:
liste_filtres.append(filtre_mots)
filtres = ' && '.join(liste_filtres) if liste_filtres else None
# Effectuer la recherche pour tous les résultats
tous_resultats = rechercher_documents(requete, ces_filtres=filtres, facette_par='network')
nombre_total_resultats = tous_resultats['found']
tous_resultats = rechercher_documents(
requete,
ces_filtres=filtres,
facette_par='network',
query_by=','.join(query_by),
sort_by=','.join(sort_by),
nb_buckets=nb_buckets,
prefix=prefix,
per_page=per_page,
page=page
)
if tous_resultats:
nombre_total_resultats = tous_resultats['found']
# Afficher le nombre total de résultats
st.subheader(f"Trouvé {nombre_total_resultats} résultats parmi {total_documents } documents indexés")
else:
nombre_total_resultats = 0
st.subheader("Aucun résultat trouvé")
# Afficher le nombre total de résultats
st.subheader(f"Trouvé {nombre_total_resultats} résultats parmi {total_documents } documents indexés")
# Affichage des résultats (100 maximum)
st.subheader("Résultats de la recherche")
for hit in tous_resultats['hits'][:100]: # Limite à 100 résultats
col1, col2 = st.columns([1, 4])
with col1:
st.markdown(f"**{hit['document']['network']}**")
st.markdown(
f"**{datetime.fromtimestamp(hit['document']['creation_timestamp']).strftime('%Y-%m-%d %H:%M:%S')}**")
st.markdown(f"**{hit['document']['nombre_de_mots']} mots**")
# Score
st.markdown(f"**Score: {hit["hybrid_search_info"]['rank_fusion_score']}**")
# Étiquettes de couleur pour les facettes
st.markdown(f"""
<span style="background-color: #007bff; color: white; padding: 2px 6px; border-radius: 10px;">
{hit['document']['langue']}
</span>
""", unsafe_allow_html=True)
with col2:
# Boîte de texte pour le contenu
st.text_area("Contenu", hit['document']['texte'], height=150)
# URI en dessous
if 'uri' in hit['document']:
st.markdown(f"[Lien vers le post original]({hit['document']['uri']})")
st.markdown("---")
# Afficher les facettes
if 'facet_counts' in tous_resultats:
facettes_reseau = {facette['value']: facette['count'] for facette in
tous_resultats['facet_counts'][0]['counts']}
st.subheader("Résultats par Réseau")
# Graphique en camembert pour montrer la distribution des résultats par réseau social
fig = px.pie(values=list(facettes_reseau.values()), names=list(facettes_reseau.keys()),
title="Distribution par Réseau")
# Ce graphique montre la proportion de résultats pour chaque réseau social
st.plotly_chart(fig)
# Distribution temporelle par réseau et par mois
if nombre_total_resultats > 0:
st.subheader("Résultats au fil du temps par réseau (agrégation mensuelle)")
# Affichage des résultats (100 maximum)
st.subheader("Résultats de la recherche")
df_temporel = pd.DataFrame({
'date': [datetime.fromtimestamp(hit['document']['creation_timestamp']) for hit in tous_resultats['hits']],
'network': [hit['document']['network'] for hit in tous_resultats['hits']]
})
for hit in tous_resultats['hits'][:100]: # Limite à 100 résultats
col1, col2 = st.columns([1, 4])
df_temporel['mois'] = df_temporel['date'].dt.to_period('M')
df_temporel = df_temporel.groupby(['mois', 'network']).size().reset_index(name='count')
df_temporel['mois'] = df_temporel['mois'].dt.to_timestamp()
with col1:
st.markdown(f"**{hit['document']['network']}**")
st.markdown(
f"**{datetime.fromtimestamp(hit['document']['creation_timestamp']).strftime('%Y-%m-%d %H:%M:%S')}**")
st.markdown(f"**{hit['document']['nombre_de_mots']} mots**")
# Score
if "texte" in query_by and "embedding" in query_by:
score = hit["hybrid_search_info"]['rank_fusion_score']
score_type = "Hybrid"
elif "texte" in query_by:
score = hit["text_match_info"]['score']
score_type = "Text"
elif "embedding" in query_by:
score = hit["vector_distance"]
score_type = "Vector"
else:
score = "N/A"
score_type = "Unknown"
st.markdown(f"**{score_type} Score: {score}**")
# Étiquettes de couleur pour les facettes
st.markdown(f"""
<span style="background-color: #007bff; color: white; padding: 2px 6px; border-radius: 10px;">
{hit['document']['langue']}
</span>
""", unsafe_allow_html=True)
# Graphique linéaire pour montrer l'évolution du nombre de posts par réseau au fil du temps
fig = px.line(df_temporel, x='mois', y='count', color='network',
title="Distribution temporelle par réseau (agrégation mensuelle)")
fig.update_layout(xaxis_title="Mois", yaxis_title="Nombre de posts")
fig.update_xaxes(tickformat="%B %Y")
# Ce graphique permet de visualiser les tendances de publication pour chaque réseau social au fil des mois
st.plotly_chart(fig)
with col2:
# Boîte de texte pour le contenu
st.text_area("Contenu", hit['document']['texte'], height=150)
# Graphique à barres empilées pour montrer la répartition des posts par réseau pour chaque mois
fig_bar = px.bar(df_temporel, x='mois', y='count', color='network',
title="Distribution temporelle par réseau (barres empilées, agrégation mensuelle)")
fig_bar.update_layout(xaxis_title="Mois", yaxis_title="Nombre de posts")
fig_bar.update_xaxes(tickformat="%B %Y")
# Ce graphique permet de comparer facilement le volume de posts entre les différents réseaux sociaux pour chaque mois
st.plotly_chart(fig_bar)
# URI en dessous
if 'uri' in hit['document']:
st.markdown(f"[Lien vers le post original]({hit['document']['uri']})")
# Affichage du hit brut
st.subheader("Données brutes")
st.json(hit, expanded=False)
st.subheader("Tableau récapitulatif mensuel")
df_pivot = df_temporel.pivot(index='mois', columns='network', values='count').fillna(0)
df_pivot['Total'] = df_pivot.sum(axis=1)
df_pivot = df_pivot.reset_index()
df_pivot['mois'] = df_pivot['mois'].dt.strftime('%B %Y')
# Ce tableau fournit un résumé détaillé du nombre de posts par réseau social pour chaque mois
st.dataframe(df_pivot)
st.markdown("---")
# Afficher les facettes
if 'facet_counts' in tous_resultats:
facettes_reseau = {facette['value']: facette['count'] for facette in
tous_resultats['facet_counts'][0]['counts']}
st.subheader("Résultats par Réseau")
# Graphique en camembert pour montrer la distribution des résultats par réseau social
fig = px.pie(values=list(facettes_reseau.values()), names=list(facettes_reseau.keys()),
title="Distribution par Réseau")
# Ce graphique montre la proportion de résultats pour chaque réseau social
st.plotly_chart(fig)
# Distribution temporelle par réseau et par mois
if nombre_total_resultats > 0:
st.subheader("Résultats au fil du temps par réseau (agrégation mensuelle)")
df_temporel = pd.DataFrame({
'date': [datetime.fromtimestamp(hit['document']['creation_timestamp']) for hit in tous_resultats['hits']],
'network': [hit['document']['network'] for hit in tous_resultats['hits']]
})
df_temporel['mois'] = df_temporel['date'].dt.to_period('M')
df_temporel = df_temporel.groupby(['mois', 'network']).size().reset_index(name='count')
df_temporel['mois'] = df_temporel['mois'].dt.to_timestamp()
# Graphique linéaire pour montrer l'évolution du nombre de posts par réseau au fil du temps
fig = px.line(df_temporel, x='mois', y='count', color='network',
title="Distribution temporelle par réseau (agrégation mensuelle)")
fig.update_layout(xaxis_title="Mois", yaxis_title="Nombre de posts")
fig.update_xaxes(tickformat="%B %Y")
# Ce graphique permet de visualiser les tendances de publication pour chaque réseau social au fil des mois
st.plotly_chart(fig)
# Graphique à barres empilées pour montrer la répartition des posts par réseau pour chaque mois
fig_bar = px.bar(df_temporel, x='mois', y='count', color='network',
title="Distribution temporelle par réseau (barres empilées, agrégation mensuelle)")
fig_bar.update_layout(xaxis_title="Mois", yaxis_title="Nombre de posts")
fig_bar.update_xaxes(tickformat="%B %Y")
# Ce graphique permet de comparer facilement le volume de posts entre les différents réseaux sociaux pour chaque mois
st.plotly_chart(fig_bar)
st.subheader("Tableau récapitulatif mensuel")
df_pivot = df_temporel.pivot(index='mois', columns='network', values='count').fillna(0)
df_pivot['Total'] = df_pivot.sum(axis=1)
df_pivot = df_pivot.reset_index()
df_pivot['mois'] = df_pivot['mois'].dt.strftime('%B %Y')
# Ce tableau fournit un résumé détaillé du nombre de posts par réseau social pour chaque mois
st.dataframe(df_pivot)

41
search_app_ui/style.css Normal file
View file

@ -0,0 +1,41 @@
/* Fond principal */
.stApp {
background-color: #0e1117;
color: #fafafa;
}
/* Barre latérale */
.css-1d391kg {
background-color: #262730;
}
/* Boutons */
.stButton>button {
color: #fafafa;
background-color: #262730;
border-color: #fafafa;
}
/* Champs de texte */
.stTextInput>div>div>input {
color: #fafafa;
background-color: #262730;
}
/* Boîte de sélection */
.stSelectbox>div>div>select {
color: #fafafa;
background-color: #262730;
}
/* Sélection multiple */
.stMultiSelect>div>div>select {
color: #fafafa;
background-color: #262730;
}
/* Saisie de date */
.stDateInput>div>div>input {
color: #fafafa;
background-color: #262730;
}