Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import requests | |
| import tempfile | |
| import uuid | |
| import os | |
| from dotenv import load_dotenv | |
| from groq import Groq | |
| from langsmith import Client as LangSmithClient | |
| import time | |
| import pandas as pd | |
| import plotly.express as px | |
| import plotly.graph_objects as go | |
| load_dotenv() | |
| HF_TOKEN = f"Bearer {os.getenv('HF_TOKEN_SECRET')}" | |
| GROQ_API_KEY = os.getenv("GROQ_API_KEY") | |
| LANGSMITH_API_KEY = os.getenv("LANGSMITH_API_KEY") | |
| groq_client = Groq(api_key=GROQ_API_KEY) | |
| langsmith_client = LangSmithClient(api_key=LANGSMITH_API_KEY) | |
| st.set_page_config( | |
| page_title="🧠 NLP Magique", | |
| page_icon="✨", | |
| layout="wide", | |
| initial_sidebar_state="collapsed" | |
| ) | |
| st.markdown(""" | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); | |
| @import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css'); | |
| .stApp { | |
| background: linear-gradient(135deg, #f5f7ff, #e4e9ff); | |
| font-family: 'Inter', sans-serif; | |
| color: #1f2937; | |
| } | |
| .main-title { | |
| text-align: center; | |
| font-size: 3.5rem; | |
| font-weight: 700; | |
| color: #4f46e5; | |
| margin: 2rem 0; | |
| animation: fadeInDown 1s ease-out; | |
| } | |
| .navbar { | |
| background: linear-gradient(90deg, #ffffff, #f9fafb); | |
| padding: 1rem 2rem; | |
| border-bottom: 1px solid #e5e7eb; | |
| display: flex; | |
| align-items: center; | |
| gap: 2rem; | |
| margin-bottom: 2rem; | |
| overflow-x: auto; | |
| white-space: nowrap; | |
| box-shadow: 0 2px 4px rgba(0,0,0,0.05); | |
| border-radius: 8px; | |
| } | |
| .navbar-brand { | |
| font-size: 1.25rem; | |
| font-weight: 700; | |
| color: #4f46e5; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .navbar-brand i { | |
| font-size: 1.5rem; | |
| } | |
| .navbar button { | |
| background: none; | |
| border: none; | |
| font-size: 1rem; | |
| font-weight: 500; | |
| color: #4b5563; | |
| padding: 0.75rem 1.25rem; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| border-radius: 6px; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .navbar button:hover { | |
| color: #4f46e5; | |
| background: #e0e7ff; | |
| transform: scale(1.05); | |
| } | |
| .navbar button.active { | |
| color: #4f46e5; | |
| background: #e0e7ff; | |
| font-weight: 600; | |
| border-bottom: 3px solid #4f46e5; | |
| } | |
| .navbar button i { | |
| font-size: 1rem; | |
| } | |
| .card { | |
| background: linear-gradient(145deg, #ffffff, #f9fafb); | |
| border-radius: 16px; | |
| padding: 2rem; | |
| margin: 1.5rem 0; | |
| box-shadow: 0 4px 20px rgba(0,0,0,0.08); | |
| transition: all 0.3s ease; | |
| border: 1px solid #e5e7eb; | |
| } | |
| .card:hover { | |
| transform: translateY(-4px); | |
| box-shadow: 0 6px 24px rgba(0,0,0,0.12); | |
| } | |
| .stButton>button { | |
| background: linear-gradient(90deg, #4f46e5, #7c3aed); | |
| color: white; | |
| font-weight: 600; | |
| border-radius: 8px; | |
| padding: 0.75rem 1.5rem; | |
| border: none; | |
| transition: all 0.3s ease; | |
| width: 100%; | |
| } | |
| .stButton>button:hover { | |
| background: linear-gradient(90deg, #4338ca, #6d28d9); | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 12px rgba(79,70,229,0.3); | |
| } | |
| .stTextArea textarea, .stSlider>div, .stTextInput input { | |
| background: #f9fafb; | |
| border-radius: 8px; | |
| padding: 1rem; | |
| border: 1px solid #d1d5db; | |
| transition: all 0.3s ease; | |
| } | |
| .stTextArea textarea:focus, .stTextInput input:focus { | |
| border-color: #4f46e5; | |
| box-shadow: 0 0 0 3px rgba(79,70,229,0.1); | |
| } | |
| .stFileUploader { | |
| background: #f9fafb; | |
| border-radius: 8px; | |
| padding: 1rem; | |
| border: 1px solid #d1d5db; | |
| } | |
| .output-card { | |
| background: #f9fafb; | |
| border-radius: 8px; | |
| padding: 1.5rem; | |
| margin-top: 1rem; | |
| border: 1px solid #e5e7eb; | |
| position: relative; | |
| font-size: 1rem; | |
| line-height: 1.6; | |
| } | |
| .copy-button { | |
| position: absolute; | |
| top: 0.5rem; | |
| right: 0.5rem; | |
| background: #4f46e5; | |
| color: white; | |
| border: none; | |
| border-radius: 6px; | |
| padding: 0.5rem 1rem; | |
| font-size: 0.875rem; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| } | |
| .copy-button:hover { | |
| background: #4338ca; | |
| } | |
| .chat-window { | |
| width: 100%; | |
| max-width: 600px; | |
| height: 500px; | |
| background: #ffffff; | |
| border-radius: 12px; | |
| box-shadow: 0 6px 20px rgba(0,0,0,0.15); | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| margin: 0 auto; | |
| border: 1px solid #e5e7eb; | |
| } | |
| .chat-header { | |
| background: #f9fafb; | |
| color: #1f2937; | |
| padding: 0.75rem 1rem; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| font-weight: 500; | |
| font-size: 0.875rem; | |
| border-bottom: 1px solid #e5e7eb; | |
| } | |
| .chat-body { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 0.75rem; | |
| background: #f9fafb; | |
| } | |
| .chat-message { | |
| margin: 0.5rem 0; | |
| padding: 0.5rem 0.75rem; | |
| border-radius: 8px; | |
| max-width: 85%; | |
| font-size: 0.875rem; | |
| line-height: 1.4; | |
| } | |
| .user-message { | |
| background: #e0e7ff; | |
| margin-left: auto; | |
| border: 1px solid #c7d2fe; | |
| } | |
| .bot-message { | |
| background: #f3e8ff; | |
| margin-right: auto; | |
| border: 1px solid #e9d5ff; | |
| } | |
| .chat-input { | |
| display: flex; | |
| align-items: center; | |
| padding: 0.5rem; | |
| background: #ffffff; | |
| border-top: 1px solid #e5e7eb; | |
| gap: 0.5rem; | |
| } | |
| .chat-input button { | |
| background: #4f46e5; | |
| color: white; | |
| border: none; | |
| border-radius: 6px; | |
| padding: 0.5rem 1rem; | |
| font-size: 0.875rem; | |
| cursor: pointer; | |
| transition: background 0.2s ease; | |
| } | |
| .chat-input button:hover { | |
| background: #4338ca; | |
| } | |
| .langsmith-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| margin-top: 1rem; | |
| } | |
| .langsmith-table th, .langsmith-table td { | |
| border: 1px solid #e5e7eb; | |
| padding: 0.75rem; | |
| text-align: left; | |
| font-size: 0.875rem; | |
| } | |
| .langsmith-table th { | |
| background: #f9fafb; | |
| font-weight: 600; | |
| } | |
| .langsmith-table tr:nth-child(even) { | |
| background: #f9fafb; | |
| } | |
| .footer { | |
| text-align: center; | |
| padding: 2rem; | |
| margin-top: 3rem; | |
| background: #ffffff; | |
| border-radius: 12px; | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.08); | |
| font-size: 1rem; | |
| color: #4b5563; | |
| } | |
| .footer a { | |
| color: #4f46e5; | |
| font-weight: 600; | |
| text-decoration: none; | |
| } | |
| .footer a:hover { | |
| color: #4338ca; | |
| text-decoration: underline; | |
| } | |
| .social-icons img { | |
| width: 24px; | |
| margin: 0 0.5rem; | |
| transition: transform 0.3s ease; | |
| } | |
| .social-icons img:hover { | |
| transform: scale(1.2); | |
| } | |
| @keyframes fadeInDown { | |
| from { opacity: 0; transform: translateY(-20px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| @media (max-width: 768px) { | |
| .main-title { font-size: 2.5rem; } | |
| .card { padding: 1.5rem; } | |
| .chat-window { width: 90vw; } | |
| .navbar { gap: 1rem; padding: 0.75rem 1rem; } | |
| .navbar-brand { font-size: 1rem; } | |
| .navbar button { padding: 0.5rem 0.75rem; font-size: 0.875rem; } | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| st.markdown(""" | |
| <script> | |
| function copyToClipboard(text) { | |
| navigator.clipboard.writeText(text).then(() => { | |
| alert('Texte copié !'); | |
| }); | |
| } | |
| </script> | |
| """, unsafe_allow_html=True) | |
| def generate_text(prompt): | |
| url = "https://api-inference.huggingface.co/models/gpt2" | |
| headers = {"Authorization": HF_TOKEN} | |
| payload = {"inputs": f"{prompt}"} | |
| response = requests.post(url, headers=headers, json=payload) | |
| if response.ok: | |
| return response.json()[0]["generated_text"] | |
| else: | |
| st.error(f"Erreur: {response.status_code} - {response.text}") | |
| return "Erreur lors de la génération." | |
| def summarize_text(text): | |
| url = "https://api-inference.huggingface.co/models/facebook/bart-large-cnn" | |
| headers = {"Authorization": HF_TOKEN} | |
| payload = {"inputs": text} | |
| response = requests.post(url, headers=headers, json=payload) | |
| if response.ok: | |
| return response.json()[0]["summary_text"] | |
| else: | |
| st.error(f"Erreur: {response.status_code} - {response.text}") | |
| return "Erreur lors du résumé." | |
| def transcribe_audio(path): | |
| url = "https://api-inference.huggingface.co/models/openai/whisper-large-v2" | |
| headers = { | |
| "Authorization": HF_TOKEN, | |
| "Content-Type": "application/octet-stream" | |
| } | |
| with open(path, "rb") as f: | |
| response = requests.post(url, headers=headers, data=f) | |
| if response.ok: | |
| return response.json()["text"] | |
| else: | |
| st.error(f"Erreur: {response.status_code} - {response.text}") | |
| return "Erreur lors de la transcription." | |
| def chat_with_grok(user_input, conversation_id): | |
| try: | |
| run = langsmith_client.create_run( | |
| name="Grok_Chat", | |
| run_type="chain", | |
| inputs={"user_input": user_input, "conversation_id": conversation_id} | |
| ) | |
| response = groq_client.chat.completions.create( | |
| messages=[ | |
| {"role": "system", | |
| "content": "Vous êtes Grok, un assistant IA créé par xAI. Répondez en français avec précision et clarté."}, | |
| {"role": "user", "content": user_input} | |
| ], | |
| model="llama-3.3-70b-versatile", | |
| temperature=0.7, | |
| max_tokens=1000 | |
| ) | |
| response_text = response.choices[0].message.content | |
| if run: | |
| run.update({"outputs": response_text, "end_time": time.time()}) | |
| return response_text | |
| except Exception as e: | |
| if 'run' in locals() and run: | |
| run.update({"outputs": {"error": str(e)}, "end_time": time.time(), "status": "error"}) | |
| st.error(f"Erreur: {str(e)}") | |
| return "Une erreur s'est produite. Réessayez." | |
| # Navbar | |
| st.markdown('<div class="navbar">', unsafe_allow_html=True) | |
| st.markdown('<span class="navbar-brand"><i class="fas fa-brain"></i> NLP Magique</span>', unsafe_allow_html=True) | |
| tabs = [ | |
| {"name": "Résumé", "icon": "fas fa-compress"}, | |
| {"name": "Génération", "icon": "fas fa-pen-fancy"}, | |
| {"name": "Transcription", "icon": "fas fa-microphone"}, | |
| {"name": "Chatbot", "icon": "fas fa-comment-dots"}, | |
| {"name": "LangSmith Monitoring", "icon": "fas fa-chart-line"} | |
| ] | |
| selected_tab = st.session_state.get('selected_tab', tabs[0]["name"]) | |
| for tab in tabs: | |
| active = 'active' if selected_tab == tab["name"] else '' | |
| if st.button(f'<i class="{tab["icon"]}"></i> {tab["name"]}', key=f"nav_{tab["name"]}", help=tab["name"], | |
| unsafe_allow_html=True): | |
| st.session_state.selected_tab = tab["name"] | |
| st.rerun() | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| # Initialize session state | |
| if 'chat_history' not in st.session_state: | |
| st.session_state.chat_history = [] | |
| if 'conversation_id' not in st.session_state: | |
| st.session_state.conversation_id = str(uuid.uuid4()) | |
| if 'langsmith_runs' not in st.session_state: | |
| st.session_state.langsmith_runs = [] | |
| st.markdown('<h1 class="main-title">✨ NLP Magique</h1>', unsafe_allow_html=True) | |
| if selected_tab == "Génération": | |
| with st.container(): | |
| st.markdown("<div class='card'>", unsafe_allow_html=True) | |
| st.subheader("Génération de texte") | |
| prompt = st.text_area("Entrez votre idée :", "La médecine moderne", height=150) | |
| temp = st.slider("Créativité", 0.1, 1.0, 0.7, step=0.1) | |
| if st.button("Générer"): | |
| with st.spinner("Génération..."): | |
| output = generate_text(prompt) | |
| st.markdown( | |
| f'<div class="output-card">{output}<button class="copy-button" onclick="copyToClipboard(\'{output}\')">Copier</button></div>', | |
| unsafe_allow_html=True) | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| elif selected_tab == "Résumé": | |
| with st.container(): | |
| st.markdown("<div class='card'>", unsafe_allow_html=True) | |
| st.subheader("Résumé de texte") | |
| texte = st.text_area("Collez votre texte :", height=200) | |
| if st.button("Résumer"): | |
| with st.spinner("Résumé..."): | |
| summary = summarize_text(texte) | |
| st.markdown( | |
| f'<div class="output-card">{summary}<button class="copy-button" onclick="copyToClipboard(\'{summary}\')">Copier</button></div>', | |
| unsafe_allow_html=True) | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| elif selected_tab == "Transcription": | |
| with st.container(): | |
| st.markdown("<div class='card'>", unsafe_allow_html=True) | |
| st.subheader("Transcription audio") | |
| audio_file = st.file_uploader("Chargez un audio (max 30s)", type=["wav", "mp3", "m4a"]) | |
| if audio_file: | |
| with tempfile.NamedTemporaryFile(delete=False, suffix='.wav') as tmp_file: | |
| tmp_file.write(audio_file.read()) | |
| audio_path = tmp_file.name | |
| st.audio(audio_path) | |
| if st.button("Transcrire"): | |
| with st.spinner("Transcription..."): | |
| transcript = transcribe_audio(audio_path) | |
| st.markdown( | |
| f'<div class="output-card">{transcript}<button class="copy-button" onclick="copyToClipboard(\'{transcript}\')">Copier</button></div>', | |
| unsafe_allow_html=True) | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| elif selected_tab == "Chatbot": | |
| with st.container(): | |
| st.markdown('<div class="chat-window">', unsafe_allow_html=True) | |
| st.markdown('<div class="chat-header">Grok AI</div>', unsafe_allow_html=True) | |
| st.markdown('<div class="chat-body">', unsafe_allow_html=True) | |
| for message in st.session_state.chat_history: | |
| if message['role'] == 'user': | |
| st.markdown(f'<div class="chat-message user-message">{message["content"]}</div>', | |
| unsafe_allow_html=True) | |
| else: | |
| st.markdown(f'<div class="chat-message bot-message">{message["content"]}</div>', unsafe_allow_html=True) | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| st.markdown('<div class="chat-input">', unsafe_allow_html=True) | |
| user_input = st.text_input("Votre message :", key="chat_input", placeholder="Tapez ici...") | |
| col1, col2 = st.columns([3, 1]) | |
| with col1: | |
| if st.button("Envoyer", key="chat_send"): | |
| if user_input: | |
| with st.spinner("Grok répond..."): | |
| st.session_state.chat_history.append({"role": "user", "content": user_input}) | |
| response = chat_with_grok(user_input, st.session_state.conversation_id) | |
| st.session_state.chat_history.append({"role": "assistant", "content": response}) | |
| st.session_state.langsmith_runs.append({ | |
| "input": user_input, | |
| "output": response, | |
| "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"), | |
| "input_length": len(user_input) | |
| }) | |
| st.rerun() | |
| with col2: | |
| if st.button("Effacer", key="chat_clear"): | |
| st.session_state.chat_history = [] | |
| st.session_state.conversation_id = str(uuid.uuid4()) | |
| st.session_state.langsmith_runs = [] | |
| st.rerun() | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| st.markdown('</div>', unsafe_allow_html=True) | |
| elif selected_tab == "LangSmith Monitoring": | |
| with st.container(): | |
| st.markdown("<div class='card'>", unsafe_allow_html=True) | |
| st.subheader("LangSmith Monitoring") | |
| if st.session_state.langsmith_runs: | |
| df = pd.DataFrame(st.session_state.langsmith_runs) | |
| df['timestamp'] = pd.to_datetime(df['timestamp']) | |
| df['hour'] = df['timestamp'].dt.strftime('%Y-%m-%d %H:00') | |
| # Chart 1: Interactions per Hour | |
| st.markdown("<h3>Interactions par heure</h3>", unsafe_allow_html=True) | |
| chart_type = st.selectbox("Type de graphique", ["Bar", "Line", "Area"], key="chart_type_interactions") | |
| interaction_counts = df.groupby('hour').size().reset_index(name='count') | |
| if chart_type == "Bar": | |
| fig = px.bar( | |
| interaction_counts, | |
| x='hour', | |
| y='count', | |
| color_discrete_sequence=['#4f46e5'], | |
| title="Nombre d'interactions par heure", | |
| labels={'hour': 'Heure', 'count': 'Nombre d\'interactions'} | |
| ) | |
| elif chart_type == "Line": | |
| fig = px.line( | |
| interaction_counts, | |
| x='hour', | |
| y='count', | |
| color_discrete_sequence=['#4f46e5'], | |
| title="Nombre d'interactions par heure", | |
| labels={'hour': 'Heure', 'count': 'Nombre d\'interactions'}, | |
| markers=True | |
| ) | |
| else: # Area | |
| fig = px.area( | |
| interaction_counts, | |
| x='hour', | |
| y='count', | |
| color_discrete_sequence=['#4f46e5'], | |
| title="Nombre d'interactions par heure", | |
| labels={'hour': 'Heure', 'count': 'Nombre d\'interactions'} | |
| ) | |
| fig.update_layout( | |
| plot_bgcolor='rgba(0,0,0,0)', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| font=dict(family="Inter, sans-serif", size=12, color="#1f2937"), | |
| xaxis_tickangle=45, | |
| showlegend=False, | |
| margin=dict(l=40, r=40, t=80, b=40) | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # Chart 2: Input Length per Interaction | |
| st.markdown("<h3>Longueur des entrées par interaction</h3>", unsafe_allow_html=True) | |
| input_length_chart_type = st.selectbox("Type de graphique", ["Bar", "Line", "Area"], | |
| key="chart_type_input_length") | |
| input_lengths = df.groupby('hour')['input_length'].mean().reset_index(name='avg_length') | |
| if input_length_chart_type == "Bar": | |
| fig2 = px.bar( | |
| input_lengths, | |
| x='hour', | |
| y='avg_length', | |
| color_discrete_sequence=['#7c3aed'], | |
| title="Longueur moyenne des entrées par heure", | |
| labels={'hour': 'Heure', 'avg_length': 'Longueur moyenne (caractères)'} | |
| ) | |
| elif input_length_chart_type == "Line": | |
| fig2 = px.line( | |
| input_lengths, | |
| x='hour', | |
| y='avg_length', | |
| color_discrete_sequence=['#7c3aed'], | |
| title="Longueur moyenne des entrées par heure", | |
| labels={'hour': 'Heure', 'avg_length': 'Longueur moyenne (caractères)'}, | |
| markers=True | |
| ) | |
| else: # Area | |
| fig2 = px.area( | |
| input_lengths, | |
| x='hour', | |
| y='avg_length', | |
| color_discrete_sequence=['#7c3aed'], | |
| title="Longueur moyenne des entrées par heure", | |
| labels={'hour': 'Heure', 'avg_length': 'Longueur moyenne (caractères)'} | |
| ) | |
| fig2.update_layout( | |
| plot_bgcolor='rgba(0,0,0,0)', | |
| paper_bgcolor='rgba(0,0,0,0)', | |
| font=dict(family="Inter, sans-serif", size=12, color="#1f2937"), | |
| xaxis_tickangle=45, | |
| showlegend=False, | |
| margin=dict(l=40, r=40, t=80, b=40) | |
| ) | |
| st.plotly_chart(fig2, use_container_width=True) | |
| # Table: Run Details | |
| st.markdown("<h3>Détails des interactions</h3>", unsafe_allow_html=True) | |
| st.markdown('<table class="langsmith-table">', unsafe_allow_html=True) | |
| st.markdown('<tr><th>Timestamp</th><th>Input</th><th>Output</th><th>Longueur entrée</th></tr>', | |
| unsafe_allow_html=True) | |
| for run in st.session_state.langsmith_runs: | |
| st.markdown( | |
| f'<tr><td>{run["timestamp"]}</td><td>{run["input"]}</td><td>{run["output"]}</td><td>{run["input_length"]}</td></tr>', | |
| unsafe_allow_html=True | |
| ) | |
| st.markdown('</table>', unsafe_allow_html=True) | |
| else: | |
| st.write("Aucune donnée de monitoring disponible.") | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| st.markdown(""" | |
| <div class="footer"> | |
| <p>© 2025 NTONGA<br> | |
| Propulsé par <a href='https://www.huggingface.co/' target='_blank'>Hugging Face</a>, <a href='https://streamlit.io/' target='_blank'>Streamlit</a>, et <a href='https://x.ai/' target='_blank'>xAI</a></p> | |
| <div class='social-icons'> | |
| <a href="https://twitter.com" target="_blank"><img src='https://img.icons8.com/color/48/twitter--v1.png'></a> | |
| <a href="https://www.linkedin.com" target="_blank"><img src='https://img.icons8.com/color/48/linkedin.png'></a> | |
| <a href="https://github.com" target="_blank"><img src='https://img.icons8.com/color/48/github--v1.png'></a> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) |