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