churnsight-ai / frontend.py
Hasitha16's picture
Update frontend.py
3a342ac verified
raw
history blame
14.1 kB
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("""
<style>
html, body, [class*="st-"] {
background-color: #121212;
color: #f5f5f5;
}
</style>
""", 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}")