import streamlit as st import requests import pandas as pd import azure.cognitiveservices.speech as speechsdk import tempfile from gtts import gTTS import base64 from io import BytesIO import os import plotly.express as px from datetime import datetime import uuid # Simulated in-memory storage for churn log if "churn_log" not in st.session_state: st.session_state.churn_log = [] st.set_page_config(page_title="ChurnSight AI", page_icon="๐Ÿง ", layout="wide") if os.path.exists("logo.png"): st.image("logo.png", width=180) # Session state setup defaults = { "review": "", "dark_mode": False, "intelligence_mode": True, "trigger_example_analysis": False, "last_response": None, "followup_answer": None } for k, v in defaults.items(): if k not in st.session_state: st.session_state[k] = v # Dark mode styling if st.session_state.dark_mode: st.markdown(""" """, unsafe_allow_html=True) # Sidebar config with st.sidebar: st.header("โš™๏ธ PM Config") st.session_state.dark_mode = st.toggle("๐ŸŒ™ Dark Mode", value=st.session_state.dark_mode) st.session_state.intelligence_mode = st.toggle("๐Ÿง  Intelligence Mode", value=st.session_state.intelligence_mode) api_token = st.text_input("๐Ÿ” API Token", value="my-secret-key", type="password") if api_token.strip() == "my-secret-key": st.warning("๐Ÿงช Demo Mode โ€” Not all features are active. Add your API token to unlock full features.") backend_url = st.text_input("๐ŸŒ Backend URL", value="http://localhost:8000") sentiment_model = st.selectbox("๐Ÿ“Š Sentiment Model", ["Auto-detect", "distilbert-base-uncased-finetuned-sst-2-english"]) industry = st.selectbox("๐Ÿญ Industry", ["Auto-detect", "Generic", "E-commerce", "Healthcare", "Education"]) product_category = st.selectbox("๐Ÿงฉ Product Category", ["Auto-detect", "General", "Mobile Devices", "Laptops"]) use_aspects = st.checkbox("๐Ÿ” Detect Pain Points") use_explain_bulk = st.checkbox("๐Ÿง  Generate PM Insight (Bulk)") verbosity = st.radio("๐Ÿ—ฃ๏ธ Response Style", ["Brief", "Detailed"]) voice_lang = st.selectbox("๐Ÿ”ˆ Voice Language", ["en", "fr", "es", "de", "hi", "zh"]) # Text-to-Speech # Setup usage tracking if "tts_usage_count" not in st.session_state: st.session_state.tts_usage_count = 0 if "enable_audio" not in st.session_state: st.session_state.enable_audio = False # Azure TTS function def azure_speak(text, lang='en-US'): try: speech_config = speechsdk.SpeechConfig( subscription=st.secrets["AZURE_SPEECH_KEY"], region=st.secrets["AZURE_REGION"] ) speech_config.speech_synthesis_language = lang with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmpfile: audio_config = speechsdk.audio.AudioOutputConfig(filename=tmpfile.name) synthesizer = speechsdk.SpeechSynthesizer(speech_config, audio_config) result = synthesizer.speak_text_async(text).get() if result.reason == speechsdk.ResultReason.SynthesizingAudioCompleted: st.session_state.tts_usage_count += 1 return tmpfile.name else: st.error("Speech synthesis failed.") return None except Exception as e: st.error(f"Azure TTS error: {e}") return None tab1, tab2 = st.tabs(["๐Ÿง  Analyze Review", "๐Ÿ“š Bulk Reviews"]) # === SINGLE REVIEW ANALYSIS === with tab1: st.title("๐Ÿ“Š ChurnSight AI โ€” Product Feedback Assistant") st.markdown("Analyze feedback to detect churn risk, extract pain points, and support product decisions.") review = st.text_area("๐Ÿ“ Enter Customer Feedback", value=st.session_state.review, height=180) st.session_state.review = review analyze = False col1, col2, col3 = st.columns(3) with col1: analyze = st.button("๐Ÿ” Analyze") with col2: if st.button("๐ŸŽฒ Example"): st.session_state.review = ( "The app crashes every time I try to checkout. It's so slow and unresponsive. " "Customer support never replied. I'm switching to another brand." ) st.session_state.trigger_example_analysis = True st.rerun() with col3: if st.button("๐Ÿงน Clear"): for key in ["review", "last_response", "followup_answer"]: st.session_state[key] = "" st.rerun() if st.session_state.review and (analyze or st.session_state.get("trigger_example_analysis")): with st.spinner("Analyzing feedback..."): try: model_used = None if sentiment_model == "Auto-detect" else sentiment_model payload = { "text": st.session_state.review, "model": model_used or "distilbert-base-uncased-finetuned-sst-2-english", "industry": industry, "product_category": product_category, "verbosity": verbosity, "aspects": use_aspects, "intelligence": st.session_state.get("intelligence_mode", False) } headers = {"x-api-key": st.session_state.get("api_token", "my-secret-key")} res = requests.post(f"{backend_url}/analyze/", json=payload, headers=headers) if res.ok: st.session_state.last_response = res.json() else: st.error(f"Error: {res.status_code} - {res.json().get('detail')}") except Exception as e: st.error(f"๐Ÿšซ Exception: {e}") data = st.session_state.last_response if data: st.subheader("๐Ÿ“Œ PM Insight Summary") st.info(data["summary"]) st.markdown(f"**Industry:** `{data['industry']}` | **Category:** `{data['product_category']}` | **Device:** Web") st.metric("๐Ÿ“Š Sentiment", data["sentiment"]["label"], delta=f"{data['sentiment']['score']:.2%}") st.info(f"๐Ÿ’ข Emotion: {data['emotion']}") if "churn_risk" in data: risk = data["churn_risk"] color = "๐Ÿ”ด" if risk == "High Risk" else "๐ŸŸข" st.metric("๐Ÿšจ Churn Risk", f"{color} {risk}") if data.get("pain_points"): st.error("๐Ÿ” Pain Points: " + ", ".join(data["pain_points"])) # Add to churn log try: st.session_state.churn_log.append({ "timestamp": datetime.now(), "product": data.get("product_category", "General"), "churn_risk": data.get("churn_risk", "Unknown"), "session_id": str(uuid.uuid4()) }) if len(st.session_state.churn_log) > 1000: st.session_state.churn_log = st.session_state.churn_log[-1000:] except Exception as e: st.warning(f"๐Ÿงช Logging failed: {e}") # Only show toggle and button for audio st.subheader("๐Ÿ”Š Audio Summary") st.session_state.enable_audio = st.toggle("๐ŸŽง Generate Audio Summary") if st.session_state.enable_audio: if st.session_state.tts_usage_count > 20: st.warning("๐Ÿ”‡ Azure TTS usage limit reached for this session.") else: if st.button("โ–ถ๏ธ Generate & Play Audio"): audio_path = azure_speak(data["summary"], lang=f"{voice_lang}-US") if audio_path: audio_bytes = open(audio_path, "rb").read() st.audio(audio_bytes, format="audio/mp3") st.download_button("โฌ‡๏ธ Download Audio", audio_bytes, "summary.mp3") st.markdown("### ๐Ÿ” Ask a Follow-Up") sentiment = data["sentiment"]["label"].lower() churn = data.get("churn_risk", "") pain = data.get("pain_points", []) if sentiment == "positive" and churn == "Low Risk": suggestions = ["What features impressed the user?", "Would they recommend the product?"] elif churn == "High Risk": suggestions = ["What made the user upset?", "Is this user likely to churn?"] else: suggestions = ["What are the key takeaways?", "Is there any concern raised?"] selected_q = st.selectbox("๐Ÿ’ก Suggested Questions", ["Type your own..."] + suggestions) q_input = st.text_input("๐Ÿ” Your Question") if selected_q == "Type your own..." else selected_q if q_input: try: follow_payload = { "text": st.session_state.review, "question": q_input, "verbosity": verbosity } headers = {"x-api-key": st.session_state.get("api_token", "my-secret-key")} # โœ… FIX here res = requests.post(f"{backend_url}/followup/", json=follow_payload, headers=headers) if res.ok: st.success(res.json().get("answer")) else: st.error("Failed to get follow-up answer.") except Exception as e: st.error(f"โš ๏ธ Follow-up error: {e}") if st.checkbox("๐Ÿ“Š Show Churn Risk Trends"): try: df = pd.DataFrame(st.session_state.churn_log) df["date"] = pd.to_datetime(df["timestamp"]).dt.date trend = df.groupby(["date", "churn_risk"]).size().unstack(fill_value=0).reset_index() y_columns = [col for col in trend.columns if col != "date"] st.markdown("#### ๐Ÿ“… Daily Churn Trend") fig = px.bar(trend, x="date", y=y_columns, barmode="group") st.plotly_chart(fig, use_container_width=True) st.download_button("โฌ‡๏ธ Export Trend CSV", trend.to_csv(index=False), "churn_trend.csv") except Exception as e: st.error(f"Trend error: {e}") # === BULK REVIEW ANALYSIS === with tab2: st.title("๐Ÿ“š Bulk Feedback Analysis") bulk_input = st.text_area("๐Ÿ“ฅ Paste multiple reviews (one per line)", height=250) if st.button("๐Ÿš€ Analyze Bulk"): lines = [l.strip() for l in bulk_input.strip().splitlines() if l.strip()] payload = { "reviews": lines, "model": "distilbert-base-uncased-finetuned-sst-2-english" if sentiment_model == "Auto-detect" else sentiment_model, "industry": None, "product_category": None, "device": None, "aspects": use_aspects, "intelligence": st.session_state.intelligence_mode } try: res = requests.post(f"{backend_url}/bulk/?token={api_token}", json=payload) if res.ok: results = res.json().get("results", []) df = pd.DataFrame(results) st.dataframe(df) st.download_button("โฌ‡๏ธ Export Results CSV", df.to_csv(index=False), "bulk_results.csv") else: st.error(f"API Error: {res.status_code}") except Exception as e: st.error(f"Bulk analysis failed: {e}")