|
@@ -1,84 +1,138 @@
|
|
|
import streamlit as st
|
|
import streamlit as st
|
|
|
import os
|
|
import os
|
|
|
-from workflow_Agent import app # Ton graphe compilé
|
|
|
|
|
import glob
|
|
import glob
|
|
|
|
|
+import shutil
|
|
|
|
|
+from workflow_Agent import app # Ton graphe compilé
|
|
|
|
|
+from langchain_core.messages import HumanMessage
|
|
|
|
|
|
|
|
|
|
+# --- CONFIGURATION ET NETTOYAGE AU DÉMARRAGE ---
|
|
|
st.set_page_config(page_title="Dataltist AI Assistant", layout="wide")
|
|
st.set_page_config(page_title="Dataltist AI Assistant", layout="wide")
|
|
|
st.title("📊 Dataltist BI Chat")
|
|
st.title("📊 Dataltist BI Chat")
|
|
|
|
|
|
|
|
-# --- 1. Gestion du Fichier ---
|
|
|
|
|
|
|
+from langfuse.langchain import CallbackHandler
|
|
|
|
|
+
|
|
|
|
|
+# Initialize Langfuse CallbackHandler for Langchain (tracing)
|
|
|
|
|
+langfuse_handler = CallbackHandler()
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+# Initialisation du dossier de travail
|
|
|
|
|
+if "app_initialized" not in st.session_state:
|
|
|
|
|
+ if os.path.exists("outputs"):
|
|
|
|
|
+ shutil.rmtree("outputs")
|
|
|
|
|
+ os.makedirs("outputs", exist_ok=True)
|
|
|
|
|
+ if not os.path.exists("data"):
|
|
|
|
|
+ os.makedirs("data", exist_ok=True)
|
|
|
|
|
+ st.session_state.app_initialized = True
|
|
|
|
|
+ st.session_state.query_count = 0
|
|
|
|
|
+ st.session_state.messages = []
|
|
|
|
|
+
|
|
|
|
|
+# --- 1. SIDEBAR : GESTION DES DONNÉES ---
|
|
|
|
|
+file_path = None
|
|
|
with st.sidebar:
|
|
with st.sidebar:
|
|
|
- st.header("Données")
|
|
|
|
|
|
|
+ st.header("📁 Données")
|
|
|
uploaded_file = st.file_uploader("Charge ton fichier (CSV/Excel)", type=["csv", "xlsx"])
|
|
uploaded_file = st.file_uploader("Charge ton fichier (CSV/Excel)", type=["csv", "xlsx"])
|
|
|
|
|
|
|
|
if uploaded_file:
|
|
if uploaded_file:
|
|
|
- # On sauvegarde le fichier dans le dossier 'data'
|
|
|
|
|
file_path = os.path.join("data", uploaded_file.name)
|
|
file_path = os.path.join("data", uploaded_file.name)
|
|
|
with open(file_path, "wb") as f:
|
|
with open(file_path, "wb") as f:
|
|
|
f.write(uploaded_file.getbuffer())
|
|
f.write(uploaded_file.getbuffer())
|
|
|
- st.success(f"Fichier {uploaded_file.name} chargé !")
|
|
|
|
|
|
|
+ st.success(f"Fichier '{uploaded_file.name}' prêt !")
|
|
|
|
|
|
|
|
-# --- 2. Interface de Chat ---
|
|
|
|
|
-if "messages" not in st.session_state:
|
|
|
|
|
- st.session_state.messages = []
|
|
|
|
|
|
|
+ st.write("---")
|
|
|
|
|
+ if st.button("🗑️ Effacer la discussion & fichiers", use_container_width=True):
|
|
|
|
|
+ st.session_state.messages = []
|
|
|
|
|
+ st.session_state.query_count = 0
|
|
|
|
|
+ if os.path.exists("outputs"):
|
|
|
|
|
+ shutil.rmtree("outputs")
|
|
|
|
|
+ os.makedirs("outputs", exist_ok=True)
|
|
|
|
|
+ st.rerun()
|
|
|
|
|
|
|
|
-if st.sidebar.button("🗑️ Effacer la discussion"):
|
|
|
|
|
- st.session_state.messages = []
|
|
|
|
|
- st.rerun()
|
|
|
|
|
-
|
|
|
|
|
-# Affichage de l'historique
|
|
|
|
|
|
|
+# --- 2. AFFICHAGE DE L'HISTORIQUE ---
|
|
|
for message in st.session_state.messages:
|
|
for message in st.session_state.messages:
|
|
|
with st.chat_message(message["role"]):
|
|
with st.chat_message(message["role"]):
|
|
|
st.markdown(message["content"])
|
|
st.markdown(message["content"])
|
|
|
|
|
+ # Réaffichage des graphiques archivés pour ce message
|
|
|
|
|
+ if "charts" in message and message["charts"]:
|
|
|
|
|
+ cols = st.columns(min(len(message["charts"]), 2))
|
|
|
|
|
+ for idx, path in enumerate(message["charts"]):
|
|
|
|
|
+ if os.path.exists(path):
|
|
|
|
|
+ cols[idx % 2].image(path, use_container_width=True)
|
|
|
|
|
|
|
|
-# --- 3. Interaction avec les Agents ---
|
|
|
|
|
|
|
+# --- 3. INTERACTION AVEC LES AGENTS ---
|
|
|
if prompt := st.chat_input("Pose ta question sur tes données..."):
|
|
if prompt := st.chat_input("Pose ta question sur tes données..."):
|
|
|
- # 1. NETTOYAGE PRÉALABLE : On vide les anciens graphiques avant de commencer
|
|
|
|
|
- # Cela garantit que list_of_plots ne contiendra que les nouveaux PNG
|
|
|
|
|
- if not os.path.exists("outputs"):
|
|
|
|
|
- os.makedirs("outputs")
|
|
|
|
|
- for f in glob.glob("outputs/*.png"):
|
|
|
|
|
- os.remove(f)
|
|
|
|
|
|
|
+ if not file_path:
|
|
|
|
|
+ st.error("Veuillez d'abord charger un fichier dans la barre latérale.")
|
|
|
|
|
+ st.stop()
|
|
|
|
|
+
|
|
|
|
|
+ # Archivage : Création du dossier spécifique pour cette requête
|
|
|
|
|
+ current_query_id = st.session_state.query_count
|
|
|
|
|
+ query_folder = os.path.join("outputs", f"query_{current_query_id}")
|
|
|
|
|
+ os.makedirs(query_folder, exist_ok=True)
|
|
|
|
|
|
|
|
- # 2. Affichage du message utilisateur
|
|
|
|
|
|
|
+ # Affichage message utilisateur
|
|
|
st.session_state.messages.append({"role": "user", "content": prompt})
|
|
st.session_state.messages.append({"role": "user", "content": prompt})
|
|
|
with st.chat_message("user"):
|
|
with st.chat_message("user"):
|
|
|
st.markdown(prompt)
|
|
st.markdown(prompt)
|
|
|
|
|
|
|
|
- # 3. Appel des agents
|
|
|
|
|
|
|
+ # Appel des agents
|
|
|
with st.chat_message("assistant"):
|
|
with st.chat_message("assistant"):
|
|
|
- # On utilise expanded=True pour voir le raisonnement des agents (Analyseur, Exécuteur...)
|
|
|
|
|
with st.status("Analyse Dataltist en cours...", expanded=True) as status:
|
|
with st.status("Analyse Dataltist en cours...", expanded=True) as status:
|
|
|
|
|
|
|
|
inputs = {
|
|
inputs = {
|
|
|
- "messages": [("user", prompt)],
|
|
|
|
|
|
|
+ "messages": [HumanMessage(content=prompt)],
|
|
|
"current_df_path": file_path
|
|
"current_df_path": file_path
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- # On lance le workflow (invoke est bloquant, donc l'analyse se termine avant la suite)
|
|
|
|
|
- final_state = app.invoke(inputs)
|
|
|
|
|
|
|
+ # Exécution du graphe
|
|
|
|
|
+ final_state = app.invoke(
|
|
|
|
|
+ inputs,
|
|
|
|
|
+ config={"callbacks": [langfuse_handler], "run_name": f"Query_{st.session_state.query_count}"}
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
- # Récupération du message final du Reporter
|
|
|
|
|
|
|
+ # Récupération de la réponse finale (Reporter)
|
|
|
response_text = final_state["messages"][-1].content
|
|
response_text = final_state["messages"][-1].content
|
|
|
st.markdown(response_text)
|
|
st.markdown(response_text)
|
|
|
|
|
|
|
|
- # --- AFFICHAGE DES GRAPHIQUES ---
|
|
|
|
|
- # On cherche les fichiers APRES l'exécution
|
|
|
|
|
- list_of_plots = glob.glob("outputs/*.png")
|
|
|
|
|
|
|
+ # --- GESTION DES FICHIERS GÉNÉRÉS ---
|
|
|
|
|
+ new_files_paths = []
|
|
|
|
|
+ # On scanne la racine de 'outputs/' pour trouver ce que l'Exécuteur a créé
|
|
|
|
|
+ raw_files = [f for f in glob.glob("outputs/*") if os.path.isfile(f)]
|
|
|
|
|
|
|
|
- if list_of_plots:
|
|
|
|
|
- st.write("---") # Séparateur visuel
|
|
|
|
|
- st.subheader("📊 Visualisations générées")
|
|
|
|
|
|
|
+ if raw_files:
|
|
|
|
|
+ st.write("---")
|
|
|
|
|
+ st.subheader("📊 Résultats de l'analyse")
|
|
|
|
|
+
|
|
|
|
|
+ # On déplace les fichiers vers le dossier de la query pour l'historique
|
|
|
|
|
+ for f in raw_files:
|
|
|
|
|
+ dest_path = os.path.join(query_folder, os.path.basename(f))
|
|
|
|
|
+ shutil.move(f, dest_path)
|
|
|
|
|
+ new_files_paths.append(dest_path)
|
|
|
|
|
+
|
|
|
|
|
+ # Affichage des images et fichiers Excel
|
|
|
|
|
+ images = [p for p in new_files_paths if p.lower().endswith(('.png', '.jpg'))]
|
|
|
|
|
+ docs = [p for p in new_files_paths if p.lower().endswith(('.xlsx', '.csv'))]
|
|
|
|
|
+
|
|
|
|
|
+ if images:
|
|
|
|
|
+ cols = st.columns(min(len(images), 2))
|
|
|
|
|
+ for idx, img_path in enumerate(images):
|
|
|
|
|
+ cols[idx % 2].image(img_path, use_container_width=True, caption=f"Visuel {idx+1}")
|
|
|
|
|
|
|
|
- # Organisation en colonnes pour éviter de longs défilements verticaux
|
|
|
|
|
- # On limite à 2 colonnes par ligne pour une meilleure lisibilité
|
|
|
|
|
- cols = st.columns(2)
|
|
|
|
|
- for idx, plot_path in enumerate(list_of_plots):
|
|
|
|
|
- col_idx = idx % 2
|
|
|
|
|
- with cols[col_idx]:
|
|
|
|
|
- st.image(plot_path, use_container_width=True, caption=f"Analyse {idx+1}")
|
|
|
|
|
|
|
+ if docs:
|
|
|
|
|
+ for doc_path in docs:
|
|
|
|
|
+ with open(doc_path, "rb") as f:
|
|
|
|
|
+ st.download_button(
|
|
|
|
|
+ label=f"📥 Télécharger {os.path.basename(doc_path)}",
|
|
|
|
|
+ data=f,
|
|
|
|
|
+ file_name=os.path.basename(doc_path),
|
|
|
|
|
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
status.update(label="Analyse terminée !", state="complete")
|
|
status.update(label="Analyse terminée !", state="complete")
|
|
|
|
|
|
|
|
- # 4. Sauvegarde dans l'historique pour le chat
|
|
|
|
|
- st.session_state.messages.append({"role": "assistant", "content": response_text})
|
|
|
|
|
|
|
+ # Mise à jour de la session et du compteur
|
|
|
|
|
+ st.session_state.messages.append({
|
|
|
|
|
+ "role": "assistant",
|
|
|
|
|
+ "content": response_text,
|
|
|
|
|
+ "charts": new_files_paths
|
|
|
|
|
+ })
|
|
|
|
|
+ st.session_state.query_count += 1
|