import streamlit as st import requests import pandas as pd 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, "use_aspects": False, "use_explain_bulk": False } 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"]) st.session_state.use_aspects = st.checkbox("๐Ÿ” Detect Pain Points", value=st.session_state.get("use_aspects", False)) st.session_state.use_explain_bulk = st.checkbox("๐Ÿง  Generate PM Insight (Bulk)", value=st.session_state.get("use_explain_bulk", False)) verbosity = st.radio("๐Ÿ—ฃ๏ธ Response Style", ["Brief", "Detailed"]) 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) if review and (len(review.split()) < 20 or len(review.split()) > 50): st.warning("โš ๏ธ For best results, keep the review between 20 to 50 words.") st.session_state.review = review analyze = False col1, col2, col3 = st.columns(3) with col1: analyze = st.button("๐Ÿ” Analyze", disabled=not (20 <= len(review.split()) <= 50)) 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": st.session_state.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: try: err_detail = res.json().get("detail", "No detail provided.") except Exception: err_detail = res.text st.error(f"โŒ Backend Error ({res.status_code}): {err_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 st.session_state.use_aspects: if data.get("pain_points"): st.error("๐Ÿ” Pain Points: " + ", ".join(data["pain_points"])) else: st.info("โœ… No specific pain points were detected.") 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}") 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?", "What benefits did they mention?", "What made their experience smooth?" ] elif churn == "High Risk": suggestions = [ "What made the user upset?", "Is this user likely to churn?", "What were the major complaints?", "What could improve their experience?" ] else: suggestions = [ "What are the key takeaways?", "Is there any concern raised?", "Did the user express dissatisfaction?", "Is this feedback actionable?" ] 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: try: err_detail = res.json().get("detail", "No detail provided.") except Exception: err_detail = res.text st.error(f"โŒ Follow-up API Error ({res.status_code}): {err_detail}") 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") st.markdown("#### ๐Ÿ“ฅ Upload CSV or Paste Reviews") uploaded_file = st.file_uploader("Upload a CSV with a 'review' column", type=["csv"]) bulk_input = st.text_area("Or paste multiple reviews (one per line)", height=180) reviews = [] if uploaded_file is not None: try: df_csv = pd.read_csv(uploaded_file) if "review" in df_csv.columns: reviews = df_csv["review"].dropna().astype(str).tolist() else: st.warning("CSV must contain a 'review' column.") except Exception as e: st.error(f"CSV error: {e}") elif bulk_input.strip(): reviews = [line.strip() for line in bulk_input.split("\\n") if line.strip()] st.markdown("#### ๐Ÿง  Bulk Analysis Configuration") explain_bulk = st.checkbox("๐Ÿง  Generate Explanations", value=st.session_state.get("use_explain_bulk", False)) enable_followups = st.checkbox("๐Ÿ’ฌ Generate Follow-Up Q&A", value=True) if st.button("๐Ÿš€ Analyze Bulk") and reviews: payload = { "reviews": reviews, "model": "distilbert-base-uncased-finetuned-sst-2-english" if sentiment_model == "Auto-detect" else sentiment_model, "industry": None, "product_category": None, "device": None, "aspects": st.session_state.use_aspects, "intelligence": st.session_state.intelligence_mode, "explain_bulk": explain_bulk, "follow_up": [["What is the issue here?", "What could be improved?"]] * len(reviews) if enable_followups else None } 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) if any("follow_up" in r for r in results): st.markdown("### ๐Ÿ’ฌ Follow-Up Answers") for r in results: st.markdown(f"**Review:** {r['review']}") if isinstance(r.get("follow_up"), list): for ans in r["follow_up"]: st.info(ans) elif "follow_up" in r: st.info(r["follow_up"]) if "churn_risk" in df.columns: st.markdown("### ๐Ÿ“ˆ Churn Risk Chart") churn_summary = df["churn_risk"].value_counts().reset_index() churn_summary.columns = ["Churn Risk", "Count"] fig = px.pie(churn_summary, names="Churn Risk", values="Count", title="Churn Risk Distribution") st.plotly_chart(fig, use_container_width=True) st.download_button("โฌ‡๏ธ Export Results CSV", df.to_csv(index=False), "bulk_results.csv") else: try: err_detail = res.json().get("detail", "No detail provided.") except Exception: err_detail = res.text st.error(f"โŒ Bulk API Error ({res.status_code}): {err_detail}") except Exception as e: st.error(f"Bulk analysis failed: {e}") # === ROOT CAUSE & FIX (AI Product Triage) === tab3 = st.container() with tab3: st.title("๐Ÿ› ๏ธ Root Cause & Fix Suggestion") st.markdown("Get AI-generated issue triage from user feedback.") triage_input = st.text_area("๐Ÿ“ Paste a customer review or complaint here") if st.button("๐Ÿค– Analyze Root Cause"): if len(triage_input.strip().split()) < 5: st.warning("Please enter at least one complete issue or sentence.") else: with st.spinner("Analyzing..."): try: res = requests.post(f"{backend_url}/rootcause/", json={"text": triage_input}, headers={"x-api-key": api_token}) if res.ok: triage = res.json() st.success("โœ… Analysis complete") st.markdown("### ๐Ÿงฉ Detected Problem") st.info(triage.get("problem", "โ€”")) st.markdown("### ๐Ÿ› ๏ธ Inferred Root Cause") st.warning(triage.get("cause", "โ€”")) st.markdown("### ๐Ÿ’ก Suggested Fix or Team") st.success(triage.get("suggestion", "โ€”")) else: st.error(f"API Error: {res.status_code}") except Exception as e: st.error(f"Root cause analysis failed: {e}")