File size: 14,099 Bytes
fc94552
 
 
82211db
fc94552
 
5224787
 
fc94552
05dad7c
5224787
 
05dad7c
8e27b3a
fc94552
 
 
 
 
 
 
 
 
 
 
b6cdcfd
 
 
 
fc94552
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
05dad7c
fc94552
9aa56c6
fc94552
 
9da624c
90267c3
1a945f1
 
fc94552
05dad7c
fc94552
 
b6cdcfd
 
fc94552
5094d9c
9aa56c6
fc94552
05dad7c
996d2c2
fc94552
8e27b3a
 
4ce1c62
9aa56c6
3a342ac
 
 
fc94552
 
82b3c12
fc94552
4ce1c62
3a342ac
4ce1c62
 
 
 
 
 
 
 
 
 
 
 
 
05dad7c
82b3c12
9aa56c6
fc94552
c88afb5
fc94552
 
4ce1c62
fc94552
 
 
b6cdcfd
82b3c12
fc94552
4ce1c62
fc94552
05dad7c
fc94552
 
1959778
 
 
 
 
fc94552
 
 
 
 
9aa56c6
fc94552
9aa56c6
fc94552
571f6f2
fc94552
33c45f1
 
 
 
b6cdcfd
 
 
 
 
 
5224787
 
 
 
 
 
63a3c84
05dad7c
 
5224787
 
9aa56c6
 
68804d2
 
 
996d2c2
68804d2
996d2c2
 
 
 
 
 
05dad7c
996d2c2
 
 
 
 
 
68804d2
996d2c2
 
 
 
 
 
c88afb5
05dad7c
 
c88afb5
05dad7c
c88afb5
 
 
 
 
 
571f6f2
c88afb5
 
 
 
1959778
 
 
 
 
c88afb5
 
05dad7c
 
 
 
 
 
c88afb5
05dad7c
727b706
05dad7c
 
 
 
 
 
 
 
996d2c2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
05dad7c
996d2c2
0f55ff6
05dad7c
 
 
b6cdcfd
58ae022
996d2c2
 
05dad7c
 
 
 
 
 
 
996d2c2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
05dad7c
 
1959778
 
 
 
 
05dad7c
1959778
25a23d8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
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}")