File size: 6,855 Bytes
48cf750
 
071f116
48cf750
 
071f116
301305e
 
48cf750
 
557bf9a
 
 
48cf750
 
557bf9a
48cf750
 
 
 
 
 
 
 
 
301305e
 
48cf750
 
301305e
 
 
 
 
 
 
 
 
 
 
 
48cf750
 
 
 
 
f8b03d9
557bf9a
48cf750
 
557bf9a
 
 
301305e
557bf9a
301305e
557bf9a
301305e
557bf9a
301305e
 
48cf750
557bf9a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301305e
557bf9a
 
 
071f116
 
48cf750
071f116
 
48cf750
301305e
48cf750
071f116
 
557bf9a
 
 
 
301305e
 
 
071f116
301305e
 
 
 
 
 
 
 
 
071f116
48cf750
 
 
301305e
48cf750
 
301305e
557bf9a
071f116
 
301305e
071f116
 
 
 
301305e
 
 
 
071f116
 
 
301305e
071f116
301305e
071f116
301305e
 
 
071f116
 
 
301305e
071f116
 
 
301305e
557bf9a
 
071f116
 
301305e
557bf9a
48cf750
071f116
557bf9a
071f116
557bf9a
48cf750
 
557bf9a
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
import streamlit as st
import requests
import plotly.express as px
import pandas as pd
import numpy as np
import os
import json
from audio_recorder_streamlit import audio_recorder

# --- CONFIG ---
# Use environment variables for deployment, fallback to localhost
API_URL = os.getenv("API_URL", "http://localhost:8000/predict")
HEALTH_URL = os.getenv("HEALTH_URL", "http://localhost:8000/health")

st.set_page_config(
    page_title="VigilAudio Dashboard",
    layout="wide"
)

# --- CUSTOM CSS ---
st.markdown("""
    <style>
    .main { background-color: #0e1117; }
    .stMetric { background-color: #161b22; border-radius: 10px; padding: 10px; border: 1px solid #30363d; }
    .emotion-badge { 
        padding: 10px 20px; 
        border-radius: 10px; 
        font-weight: bold; 
        text-transform: uppercase;
        font-size: 28px;
        text-align: center;
    }
    .alert-banner {
        background-color: rgba(255, 75, 75, 0.15);
        border: 2px solid #ff4b4b;
        color: #ff4b4b;
        padding: 15px;
        border-radius: 10px;
        font-weight: bold;
        margin-bottom: 20px;
        text-align: center;
    }
    </style>
    """, unsafe_allow_html=True)

# --- UI HEADER ---
st.title("VigilAudio")
st.caption("ML-Powered Content Safety Engine (Microservices Architecture)")
st.markdown("---")

# --- SYSTEM STATUS SIDEBAR ---
with st.sidebar:
    st.header("System Health")
    try:
        health_resp = requests.get(HEALTH_URL, timeout=2)
        health = health_resp.json()
        st.success(f"Backend: {health['status'].upper()}")
        st.info(f"Engine: {health['engine']}")
        st.caption(f"Limit: {health['max_duration_limit']}s")
    except:
        st.error("Backend Server Offline")

# --- INPUT TABS ---
tab_upload, tab_mic = st.tabs(["Upload File", "Live Microphone"])

audio_source_bytes = None
audio_source_name = "recording.wav"

with tab_upload:
    uploaded_file = st.file_uploader("Upload Audio Content", type=["wav", "mp3", "m4a"], key="api_uploader")
    if uploaded_file:
        audio_source_bytes = uploaded_file.getvalue()
        audio_source_name = uploaded_file.name

with tab_mic:
    st.write("Capture Live Audio")
    recorded_audio_bytes = audio_recorder(text="Start Recording", icon_size="2x", key="api_recorder")
    if recorded_audio_bytes:
        audio_source_bytes = recorded_audio_bytes
        audio_source_name = "mic_recording.wav"

# --- MAIN CONTENT ---
if audio_source_bytes is not None:
    st.audio(audio_source_bytes)
    
    if st.button("Analyze Content", type="primary", use_container_width=True, key="api_analyze_btn"):
        with st.spinner("Communicating with VigilAudio API..."):
            files = {"file": (audio_source_name, audio_source_bytes)}
            try:
                response = requests.post(API_URL, files=files)
                
                if response.status_code == 200:
                    data = response.json()
                    
                    # Process results
                    dominant = data['dominant_emotion']
                    timeline = data['timeline']
                    
                    # Truncation Warning
                    if data.get('is_truncated'):
                        st.warning(f"Note: Audio was truncated to 60s for analysis (Original: {data['original_duration']}s)")

                    # Moderation Logic: Flag high-confidence negative emotions
                    flagged_emotions = ['angry', 'fearful', 'disgusted']
                    is_flagged = any(seg['emotion'] in flagged_emotions and seg['confidence'] > 0.85 for seg in timeline)
                    
                    if is_flagged:
                        st.markdown("""
                            <div class="alert-banner">
                                MODERATION ALERT: High-intensity negative sentiment detected. Human review recommended.
                            </div>
                        """, unsafe_allow_html=True)

                    # Layout for charts
                    res_col1, res_col2 = st.columns([1, 2])
                    
                    color_map = {
                        "angry": "#f48771", "happy": "#89d185", "sad": "#4fc1ff",
                        "fearful": "#c586c0", "disgusted": "#ce9178", "neutral": "#808080",
                        "suprised": "#dcdcaa"
                    }
                    
                    with res_col1:
                        st.subheader("Dominant Tone")
                        color = color_map.get(dominant, "#ffffff")
                        st.markdown(f"""
                            <div style="background-color: {color}22; border: 2px solid {color}; color: {color};" class="emotion-badge">
                                {dominant}
                            </div>
                        """, unsafe_allow_html=True)
                        
                        # Distribution Pie
                        emotions_list = [seg["emotion"] for seg in timeline]
                        dist_counts = pd.Series(emotions_list).value_counts().reset_index()
                        dist_counts.columns = ['emotion', 'count']
                        fig_dist = px.pie(
                            dist_counts, values='count', names='emotion',
                            color='emotion', color_discrete_map=color_map,
                            hole=0.4, height=250
                        )
                        fig_dist.update_layout(showlegend=False, margin=dict(l=0, r=0, t=0, b=0), paper_bgcolor='rgba(0,0,0,0)')
                        st.plotly_chart(fig_dist, use_container_width=True)
                    
                    with res_col2:
                        st.subheader("Confidence Timeline")
                        timeline_df = pd.DataFrame(timeline)
                        fig_timeline = px.bar(
                            timeline_df, x="start_sec", y="confidence", color="emotion",
                            color_discrete_map=color_map,
                            labels={"start_sec": "Time (s)", "confidence": "Confidence"}
                        )
                        fig_timeline.update_layout(
                            template="plotly_dark", plot_bgcolor='rgba(0,0,0,0)', 
                            paper_bgcolor='rgba(0,0,0,0)', margin=dict(l=0, r=0, t=0, b=0),
                            height=300
                        )
                        st.plotly_chart(fig_timeline, use_container_width=True)

                    with st.expander("Detailed Audit Log"):
                        st.json(data)
                else:
                    st.error(f"API Error: {response.text}")
            except Exception as e:
                st.error(f"Connection Error: {e}")

else:
    st.info("System standby. Please provide audio content via upload or microphone.")