# 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(""" """, 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.")