Spaces:
Running
Running
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.")
|