File size: 7,291 Bytes
f9f1893
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# dashboard/app.py
# SupportMind Streamlit Dashboard
# Asmitha · 2026

import streamlit as st
import sys, os, json
import numpy as np

sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))

st.set_page_config(page_title="SupportMind", page_icon="🧠", layout="wide")

# Custom CSS
st.markdown("""
<style>
    .stApp { background-color: #0a0a0f; }
    .metric-card { background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.08);
        border-radius: 12px; padding: 20px; text-align: center; }
    .action-route { color: #22c55e; font-size: 28px; font-weight: 800; }
    .action-clarify { color: #eab308; font-size: 28px; font-weight: 800; }
    .action-escalate { color: #ef4444; font-size: 28px; font-weight: 800; }
</style>
""", unsafe_allow_html=True)

st.title("🧠 SupportMind")
st.caption("Confidence-Gated Support Intelligence for B2B SaaS")

# Sidebar
st.sidebar.header("⚙️ Configuration")
mc_passes = st.sidebar.slider("MC Dropout Passes", 5, 50, 20)
route_thresh = st.sidebar.slider("Route Threshold", 0.5, 0.95, 0.80, 0.05)
clarify_thresh = st.sidebar.slider("Clarify Threshold", 0.3, 0.8, 0.55, 0.05)
entropy_max = st.sidebar.slider("Max Entropy (Route)", 0.1, 1.0, 0.35, 0.05)

# Hero metrics
col1, col2, col3, col4 = st.columns(4)
col1.metric("Routing Accuracy", "89.1%", "+16.8pp")
col2.metric("Ambiguous Gain", "+32.3%", "vs baseline")
col3.metric("Annual Savings", "$756K", "per 10K tickets/mo")
col4.metric("Pipeline Latency", "45ms", "20-pass MC Dropout")

st.divider()

# Ticket Input
st.header("🎯 Route a Ticket")

presets = {
    "Billing Issue": "My invoice from last month shows $299 but my plan is $199. Please fix this billing error immediately.",
    "Technical Bug": "The API endpoint /v2/export returns a 500 error when batch size exceeds 1000 records.",
    "Ambiguous Ticket": "Hey, we have issues with the export function since last Tuesday. Also our invoice looks incorrect. We are considering upgrading but want this sorted first.",
    "Churn Risk": "This is the third time I'm reporting this. Still not fixed. We're looking at switching to a competitor.",
    "Onboarding": "We just signed up yesterday and need help setting up SSO for our team of 50 users.",
}

preset = st.selectbox("Quick presets:", ["Custom"] + list(presets.keys()))
if preset != "Custom":
    ticket_text = st.text_area("Ticket Text", value=presets[preset], height=100)
else:
    ticket_text = st.text_area("Ticket Text", placeholder="Enter support ticket text...", height=100)

if st.button("⚡ Route Ticket", type="primary", use_container_width=True):
    if ticket_text.strip():
        with st.spinner("Running MC Dropout inference..."):
            try:
                from ensemble_router import EnsembleRouter
                import ensemble_router as er
                er.ROUTE_THRESHOLD = route_thresh
                er.CLARIFY_THRESHOLD = clarify_thresh
                er.ENTROPY_MAX = entropy_max

                router = EnsembleRouter(device='cpu')
                result = router.route(ticket_text, n_passes=mc_passes)

                # Display action
                action = result['action']
                action_colors = {'route': '🟢', 'clarify': '🟡', 'escalate': '🔴'}
                st.subheader(f"{action_colors.get(action, '')} Decision: {action.upper()}")
                st.caption(result['reason'])

                # Metrics
                c1, c2, c3 = st.columns(3)
                c1.metric("Confidence", f"{result['confidence']:.4f}")
                c2.metric("Entropy", f"{result['entropy']:.4f}")
                c3.metric("Top Category", result['top_category'].replace('_', ' ').title())

                # Probability distribution
                st.subheader("📊 Category Probabilities")
                import plotly.graph_objects as go
                cats = list(result['all_probs'].keys())
                probs_vals = list(result['all_probs'].values())
                fig = go.Figure(go.Bar(
                    x=probs_vals, y=[c.replace('_', ' ').title() for c in cats],
                    orientation='h',
                    marker_color=['#6366f1' if p == max(probs_vals) else '#334155' for p in probs_vals]
                ))
                fig.update_layout(
                    plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)',
                    font_color='#94a3b8', height=300, margin=dict(l=0,r=0,t=10,b=0),
                    xaxis_title="Probability"
                )
                st.plotly_chart(fig, use_container_width=True)

                # Clarification
                if action == 'clarify':
                    st.subheader("💡 Suggested Clarification")
                    try:
                        from clarification_engine import ClarificationEngine
                        bank_path = os.path.join(os.path.dirname(__file__), '..', 'data', 'clarification_bank.json')
                        clar = ClarificationEngine(bank_path)
                        probs_arr = np.array(list(result['all_probs'].values()))
                        q = clar.select_question(probs_arr, result['top_two_classes'])
                        st.info(f"**{q['question_text']}**")
                        for opt in q.get('options', []):
                            st.button(opt, disabled=True)
                        st.caption(f"Expected information gain: {q['expected_gain']:.4f}")
                    except Exception as e:
                        st.warning(f"Could not load clarification: {e}")

                # SLA
                st.subheader("🚨 SLA Breach Prediction")
                try:
                    from sla_predictor import SLABreachPredictor
                    from feature_extraction import FeatureExtractor
                    feat_ext = FeatureExtractor()
                    features = feat_ext.extract(ticket_text)
                    sla_path = os.path.join(os.path.dirname(__file__), '..', 'models', 'sla_predictor', 'sla_xgb.json')
                    sla = SLABreachPredictor(sla_path)
                    sla_features = {
                        'text_complexity_score': features['text_complexity_score'],
                        'agent_queue_depth': 15, 'customer_tier': 3,
                        'hour_of_day': 14, 'day_of_week': 2,
                        'similar_ticket_avg_hrs': 4.5,
                        'sentiment_score': features['sentiment_score'],
                        'repeat_issue': 0, 'escalated_before': 0,
                    }
                    sla_result = sla.explain(sla_features)
                    sc1, sc2 = st.columns(2)
                    sc1.metric("Breach Probability", f"{sla_result['breach_probability']:.1%}")
                    sc2.metric("Risk Level", sla_result['risk_level'].upper())
                    if sla_result['contributing_factors']:
                        st.caption("Factors: " + ", ".join(sla_result['contributing_factors']))
                except Exception as e:
                    st.warning(f"SLA prediction unavailable: {e}")

            except Exception as e:
                st.error(f"Error: {e}")
                import traceback
                st.code(traceback.format_exc())
    else:
        st.warning("Please enter ticket text.")