44import pandas as pd
55import plotly .express as px
66from backend import ModalXSystem
7- from emotion_engine import EmotionAnalyzer
87
8+ # --- PAGE CONFIG ---
99st .set_page_config (
1010 page_title = "DIU Presentation Grader" ,
1111 page_icon = "🎓" ,
1212 layout = "wide" ,
1313 initial_sidebar_state = "expanded"
1414)
1515
16+ # --- CSS STYLING ---
1617st .markdown ("""
1718 <style>
1819 .stApp { background-color: #0E1117; color: #FAFAFA; }
4950 color: white;
5051 border: 1px solid #444;
5152 }
52- [data-testid="stSidebar"] {
53- background-color: #12151e;
54- border-right: 1px solid #333;
55- }
56-
5753 div.stDownloadButton > button {
5854 background: linear-gradient(90deg, #198754, #20c997);
5955 color: white !important;
6359 font-size: 16px;
6460 font-weight: bold;
6561 border-radius: 8px;
66- box-shadow: 0 4px 12px rgba(32, 201, 151, 0.4);
67- transition: transform 0.2s;
68- }
69- div.stDownloadButton > button:hover {
70- transform: scale(1.02);
71- }
72-
73- .stStatusWidget {
74- background-color: #1E2130;
75- border: 1px solid #444;
76- border-radius: 10px;
7762 }
7863 </style>
7964""" , unsafe_allow_html = True )
8065
8166st .markdown ('<div class="main-header"><h1>🎓 DIU Smart Faculty Grader</h1><p>AI-Powered Multi-Modal Presentation Assessment</p></div>' , unsafe_allow_html = True )
8267
83- @st .cache_resource
84- def load_emotion_engine ():
85- return EmotionAnalyzer ()
86-
68+ # --- SIDEBAR ---
8769with st .sidebar :
8870 st .header ("📋 Student Details" )
8971 s_name = st .text_input ("Student Name" , placeholder = "e.g. Muntasir Islam" )
@@ -115,22 +97,18 @@ def load_emotion_engine():
11597
11698 st .markdown ("---" )
11799 analyze_btn = st .button ("🚀 Start Analysis" , type = "primary" , use_container_width = True )
118- st .caption ("Powered by ModalX Engine v2.1 " )
100+ st .caption ("Powered by ModalX Engine v3.0 " )
119101
102+ # --- MAIN LOGIC ---
120103if analyze_btn :
121104 if not video_path or not s_name :
122105 st .error ("⚠️ Please enter Student Name and Provide a Video first." )
123106 else :
107+ # Initialize Backend
124108 if 'modalx' not in st .session_state :
125109 with st .spinner ("⚡ Booting AI Engine..." ):
126110 st .session_state .modalx = ModalXSystem ()
127111
128- try :
129- emotion_engine = load_emotion_engine ()
130- except Exception as e :
131- st .error (f"Could not load Emotion Model: { e } " )
132- st .stop ()
133-
134112 proc_col1 , proc_col2 = st .columns ([1 , 1 ])
135113
136114 with proc_col1 :
@@ -142,46 +120,34 @@ def load_emotion_engine():
142120 st .video (video_path )
143121
144122 results = None
145- emotion_results = None
146123
147124 with proc_col2 :
148125 with st .status ("🚀 ModalX Engine Running..." , expanded = True ) as status :
149126 st .write ("🔄 Initializing Neural Networks..." )
150127 time .sleep (1 )
151128
152- st .write ("🧠 Running General Assessment (Whisper + CV)..." )
129+ st .write ("🧠 Extracting Audio & Transcribing (Whisper)..." )
130+ st .write ("👁️ Scanning Facial Landmarks (MediaPipe)..." )
131+ st .write ("🎭 Analyzing Micro-Expressions & Tone (CNN)..." )
132+ st .write ("📝 Evaluating Content Impact & Vocabulary..." )
133+
153134 try :
135+ # The backend now handles EVERYTHING (Audio, Visual, Emotion, Content)
154136 results = st .session_state .modalx .analyze (video_path , s_name , s_id , is_url )
155- except Exception as e :
156- st .error (f"General Engine Error: { e } " )
157-
158- if video_path and os .path .exists (video_path ) and not is_url :
159- st .write ("🎭 Analyzing Emotional Tones (CNN-1D)..." )
160- try :
161- e_times , e_emotions , e_summary = emotion_engine .predict (video_path )
162- emotion_results = {
163- "times" : e_times ,
164- "emotions" : e_emotions ,
165- "summary" : e_summary
166- }
167- except Exception as e :
168- st .warning (f"Emotion Analysis skipped: { e } " )
169- elif is_url :
170- st .warning ("⚠️ Emotion Graph unavailable for external URLs (Download required)" )
171-
172- if results :
173137 status .update (label = "✅ Analysis Complete!" , state = "complete" , expanded = False )
174- else :
138+ except Exception as e :
139+ st .error (f"Engine Error: { e } " )
175140 status .update (label = "❌ Analysis Failed" , state = "error" )
176141
142+ # --- RESULTS DASHBOARD ---
177143 if results :
178144 st .divider ()
179145 st .balloons ()
180146
181147 score = results ['score' ]
182148
183- grade = "F"
184- grade_bg = "#dc3545"
149+ # Grade Logic
150+ grade = "F" ; grade_bg = "#dc3545"
185151 if score >= 80 : grade , grade_bg = "A+" , "#198754"
186152 elif score >= 75 : grade , grade_bg = "A" , "#20c997"
187153 elif score >= 70 : grade , grade_bg = "A-" , "#0dcaf0"
@@ -192,7 +158,7 @@ def load_emotion_engine():
192158 c1 , c2 , c3 = st .columns ([1.5 , 1.5 , 3 ])
193159
194160 with c1 :
195- st .metric ("Final Score" , f"{ score } /100" , delta = f"{ score - 70 } vs Avg" )
161+ st .metric ("Final Weighted Score" , f"{ score } /100" , delta = f"{ score - 70 } vs Avg" )
196162
197163 with c2 :
198164 st .markdown (f"""
@@ -205,115 +171,104 @@ def load_emotion_engine():
205171 with c3 :
206172 st .markdown (f"### 👤 { s_name } " )
207173 st .caption (f"Student ID: { s_id } " )
208- st .info ("Grading Logic: Weighted average of Speech Clarity (60%) and Visual Engagement (40%). " )
174+ st .info ("Grading Logic: Audio (30%), Visual (30%), Emotion (20%), Content (20%) " )
209175
210176 st .divider ()
211177
212- tab1 , tab2 , tab3 = st .tabs (["📊 Performance Metrics" , "🎭 Emotional Intelligence" , "📝 Transcript & Feedback" ])
178+ # --- TABS ---
179+ tab1 , tab2 , tab3 = st .tabs (["📊 Performance Metrics" , "🎭 Emotional Intelligence" , "📝 Content & Report" ])
213180
181+ # TAB 1: CORE METRICS
214182 with tab1 :
215183 col_a , col_b = st .columns (2 )
216184
217185 with col_a :
218186 st .subheader ("🗣️ Audio Intelligence" )
219187 audio = results ['metrics' ]['audio' ]
220-
221188 st .write (f"**Speaking Pace** ({ audio ['wpm' ]} WPM)" )
222189 st .progress (float (min (audio ['wpm' ]/ 160 , 1.0 )))
223-
224- st .write (f"**Tonal Variation** (Score: { audio ['physics' ]['pitch_variation' ]} )" )
190+ st .write (f"**Pitch Variation** ({ audio ['physics' ]['pitch_variation' ]} )" )
225191 st .progress (float (min (audio ['physics' ]['pitch_variation' ]/ 50 , 1.0 )))
226-
227- st .write (f"**Confidence (Volume)** (Score: { audio ['physics' ]['volume_score' ]} )" )
228- st .progress (float (min (audio ['physics' ]['volume_score' ]/ 100 , 1.0 )))
229-
230- if audio ['filler_count' ] > 3 :
231- st .warning (f"⚠️ High Filler Words Detected: { audio ['filler_count' ]} " )
232- else :
233- st .success (f"✅ Low Filler Words: { audio ['filler_count' ]} " )
192+ st .metric ("Pause Ratio" , f"{ audio ['physics' ]['pause_ratio' ]} %" )
234193
235194 with col_b :
236195 visual = results ['metrics' ]['visual' ]
237-
238196 if visual .get ('is_slide_mode' , False ):
239197 st .subheader ("🖼️ Slide Design AI" )
240- st .info ("Scanner Mode: Slide Presentation" )
198+ st .info ("Mode: Slide Presentation" )
241199 slides = results ['metrics' ]['slides' ]
242-
243200 st .metric ("Word Density" , f"{ slides ['avg_words_per_slide' ]} words/slide" )
244- st .write ("**Readability Score**" )
245- st .progress (float (min (slides ['readability_score' ]/ 100 , 1.0 )))
246- st .write (f"**Slide Transitions Detected:** { slides ['slide_changes' ]} " )
201+ st .metric ("Slide Readability" , f"{ int (slides ['readability_score' ])} /100" )
247202 else :
248203 st .subheader ("👁️ Behavioral AI" )
249- st .info ("Scanner Mode: Presenter Face" )
250-
251- st .write (f"**Eye Contact Consistency** ({ visual ['eye_contact_score' ]} %)" )
204+ st .info ("Mode: Face Presentation" )
205+ st .write (f"**Eye Contact** ({ visual ['eye_contact_score' ]} %)" )
252206 st .progress (float (visual ['eye_contact_score' ]/ 100 ))
253-
254207 st .write (f"**Posture Stability** ({ visual ['posture_score' ]} %)" )
255208 st .progress (float (visual ['posture_score' ]/ 100 ))
256209
210+ # TAB 2: EMOTION ANALYZER
257211 with tab2 :
258- if emotion_results and emotion_results ['times' ]:
212+ emo_data = results .get ('emotion_data' , {})
213+ if emo_data and emo_data .get ('times' ):
259214 st .subheader ("📈 Emotional Flow Over Time" )
260215
261- e_times = emotion_results ['times' ]
262- e_emotions = emotion_results ['emotions' ]
263- e_summary = emotion_results ['summary' ]
216+ e_times = emo_data ['times' ]
217+ e_emotions = emo_data ['emotions' ]
218+ e_summary = emo_data ['summary' ]
264219
220+ # Timeline Graph
265221 df = pd .DataFrame ({"Time (s)" : e_times , "Emotion" : e_emotions })
266222 emotion_order = sorted (list (set (e_emotions )))
267223
268224 fig = px .scatter (
269225 df , x = "Time (s)" , y = "Emotion" , color = "Emotion" ,
270226 size = [15 ]* len (df ), template = "plotly_dark" ,
271- category_orders = {"Emotion" : emotion_order },
272- title = "Speaker Emotion Timeline"
227+ category_orders = {"Emotion" : emotion_order }
273228 )
274229 fig .update_traces (mode = 'lines+markers' , line = dict (width = 1 , color = 'gray' ))
275- fig .update_layout (height = 400 , paper_bgcolor = "#0E1117" , plot_bgcolor = "#0E1117" )
230+ fig .update_layout (height = 350 , paper_bgcolor = "#0E1117" , plot_bgcolor = "#0E1117" )
276231 st .plotly_chart (fig , use_container_width = True )
277232
278- c_pie , c_dom = st .columns ([1 , 1 ])
233+ # Summary
234+ c_pie , c_stat = st .columns ([1 , 1 ])
279235 with c_pie :
280236 st .markdown ("##### Emotion Distribution" )
281- fig_pie = px .pie (
282- names = list (e_summary .keys ()),
283- values = list (e_summary .values ()),
284- hole = 0.4 , template = "plotly_dark"
285- )
237+ fig_pie = px .pie (names = list (e_summary .keys ()), values = list (e_summary .values ()), hole = 0.4 , template = "plotly_dark" )
286238 fig_pie .update_layout (paper_bgcolor = "#0E1117" )
287239 st .plotly_chart (fig_pie , use_container_width = True )
288-
289- with c_dom :
240+ with c_stat :
290241 dom_emotion = max (e_summary , key = e_summary .get )
291- st .markdown ("##### Analysis" )
292242 st .metric ("Dominant Tone" , dom_emotion .upper ())
293-
294- if dom_emotion in ['happy' , 'neutral' , 'surprise' , 'surprised' ]:
295- st .success ("The speaker maintains a **Positive/Confident** tone." )
296- elif dom_emotion in ['fear' , 'sad' ]:
297- st .warning ("The speaker seems **Nervous or Low Energy**. Needs more enthusiasm." )
298- elif dom_emotion in ['angry' , 'disgust' , 'anger' ]:
299- st .error ("The speaker sounds **Aggressive/Frustrated**. Needs a softer tone." )
243+ if dom_emotion in ['happy' , 'neutral' , 'surprise' ]:
244+ st .success ("Positive tone detected. Good confidence." )
245+ else :
246+ st .warning ("Negative/Nervous tone detected." )
300247 else :
301- st .info ("Emotion analysis is not available for this file type or URL ." )
248+ st .info ("Emotion analysis unavailable for this file." )
302249
250+ # TAB 3: CONTENT & REPORT
303251 with tab3 :
304- fb_col1 , fb_col2 = st .columns ([2 , 1 ])
252+ c_col1 , c_col2 = st .columns ([2 , 1 ])
305253
306- with fb_col1 :
254+ with c_col1 :
255+ st .subheader ("🧠 Content Intelligence" )
256+ audio = results ['metrics' ]['audio' ]
257+
258+ col_i1 , col_i2 = st .columns (2 )
259+ col_i1 .metric ("Content Score" , f"{ int (audio .get ('content_score' , 0 ))} /100" )
260+ col_i2 .metric ("Power Words Used" , audio .get ('impact_words' , 0 ))
261+
307262 st .markdown ("### 🤖 AI Recommendations" )
308263 for item in results ['feedback' ]:
309264 st .warning (f"👉 { item } " )
310-
311- st .markdown ( "### 📄 Speech Transcript" )
312- st .text_area ( "Full Transcript" , results [ 'metrics' ][ ' audio' ] ['transcript' ], height = 150 )
265+
266+ with st .expander ( "View Full Transcript" ):
267+ st .text ( audio ['transcript' ])
313268
314- with fb_col2 :
269+ with c_col2 :
315270 st .markdown ("### 📥 Official Report" )
316- st .write ("Download the verified PDF report for faculty submission ." )
271+ st .write ("Download the verified PDF report containing all graphs and scores ." )
317272
318273 if results ['report' ]:
319274 st .download_button (
@@ -323,5 +278,6 @@ def load_emotion_engine():
323278 mime = "application/pdf"
324279 )
325280
281+ # Cleanup
326282 if video_path and os .path .exists (video_path ) and not is_url :
327283 os .remove (video_path )
0 commit comments