import streamlit as st import requests import pandas as pd import azure.cognitiveservices.speech as speechsdk import tempfile 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'): import os if "HF_SPACE_ID" in os.environ: st.warning("Azure TTS is not supported on Hugging Face Spaces. Using fallback TTS.") return None if st.session_state.tts_usage_count > 20: st.warning("๐Ÿ”‡ TTS usage limit reached.") return None try: import azure.cognitiveservices.speech as speechsdk 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("โŒ Azure TTS 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.progress(data["sentiment"]["score"]) 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": api_token} 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}")