2121
2222# ROS2 Messages
2323from std_msgs .msg import String , Bool
24- from coffee_voice_agent_msgs .msg import AgentStatus , ToolEvent
24+ from coffee_voice_agent_msgs .msg import AgentStatus , ToolEvent , VipDetection , ExtensionEvent
2525
2626# Import custom widgets
2727from .widgets .agent_status_widget import AgentStatusWidget
3030from .widgets .tool_monitor_widget import ToolMonitorWidget
3131from .widgets .analytics_widget import AnalyticsWidget
3232from .widgets .virtual_request_widget import VirtualRequestWidget
33+ from .widgets .admin_override_widget import AdminOverrideWidget
3334
3435
3536class VoiceAgentMonitorNode (Node ):
3637 """ROS2 Node for handling voice agent monitoring subscriptions"""
3738
3839 # PyQt signals for thread-safe UI updates
3940 agent_status_received = pyqtSignal (AgentStatus )
40- tool_event_received = pyqtSignal (ToolEvent )
41+ tool_event_received = pyqtSignal (ToolEvent )
4142 user_speech_received = pyqtSignal (str )
4243 connection_status_received = pyqtSignal (bool )
44+ vip_detection_received = pyqtSignal (VipDetection )
45+ extension_event_received = pyqtSignal (ExtensionEvent )
4346
4447 def __init__ (self ):
4548 super ().__init__ ('voice_agent_monitor_node' )
@@ -73,6 +76,20 @@ def __init__(self):
7376 10
7477 )
7578
79+ self .vip_detection_sub = self .create_subscription (
80+ VipDetection ,
81+ 'voice_agent/vip_detections' ,
82+ self .vip_detection_callback ,
83+ 10
84+ )
85+
86+ self .extension_event_sub = self .create_subscription (
87+ ExtensionEvent ,
88+ 'voice_agent/extension_events' ,
89+ self .extension_event_callback ,
90+ 10
91+ )
92+
7693 # Publisher for sending virtual requests
7794 self .virtual_request_pub = self .create_publisher (
7895 String ,
@@ -97,6 +114,14 @@ def speech_callback(self, msg):
97114 def connection_callback (self , msg ):
98115 """Handle connection status messages"""
99116 self .connection_status_received .emit (msg .data )
117+
118+ def vip_detection_callback (self , msg ):
119+ """Handle VipDetection messages"""
120+ self .vip_detection_received .emit (msg )
121+
122+ def extension_event_callback (self , msg ):
123+ """Handle ExtensionEvent messages"""
124+ self .extension_event_received .emit (msg )
100125
101126
102127class VoiceAgentMonitor (Plugin ):
@@ -136,20 +161,30 @@ def _setup_ui(self):
136161 self .tool_monitor_widget = ToolMonitorWidget ()
137162 self .analytics_widget = AnalyticsWidget ()
138163 self .virtual_request_widget = VirtualRequestWidget ()
164+ self .admin_override_widget = AdminOverrideWidget ()
139165
140- # Arrange widgets in dashboard layout
141- # Row 0: Agent Status | Conversation Flow | Analytics
142- main_layout .addWidget (self .agent_status_widget , 0 , 0 )
166+ # Create left column container with vertical layout
167+ left_column_widget = QWidget ()
168+ left_column_layout = QVBoxLayout ()
169+ left_column_widget .setLayout (left_column_layout )
170+
171+ # Add widgets to left column container
172+ left_column_layout .addWidget (self .agent_status_widget )
173+ left_column_layout .addWidget (self .emotion_widget )
174+ left_column_layout .addWidget (self .admin_override_widget )
175+
176+ # Arrange widgets in dashboard layout (back to 2-row layout)
177+ # Row 0: Left Column Container (spans 2 rows) | Conversation Flow | Analytics
178+ main_layout .addWidget (left_column_widget , 0 , 0 , 2 , 1 ) # span 2 rows, 1 column
143179 main_layout .addWidget (self .conversation_widget , 0 , 1 )
144180 main_layout .addWidget (self .analytics_widget , 0 , 2 )
145181
146- # Row 1: Emotion Display | Tool Monitor | Controls
147- main_layout .addWidget (self .emotion_widget , 1 , 0 )
182+ # Row 1: (Left Column continues) | Tool Monitor | Virtual Requests
148183 main_layout .addWidget (self .tool_monitor_widget , 1 , 1 )
149184 main_layout .addWidget (self .virtual_request_widget , 1 , 2 )
150185
151186 # Set column stretch to make conversation widget wider
152- main_layout .setColumnStretch (0 , 1 ) # Status/Emotion column
187+ main_layout .setColumnStretch (0 , 1 ) # Left column container
153188 main_layout .setColumnStretch (1 , 2 ) # Conversation/Tools column (wider)
154189 main_layout .setColumnStretch (2 , 1 ) # Analytics/Controls column
155190
@@ -170,6 +205,8 @@ def _init_ros(self):
170205 self .ros_node .tool_event_received .connect (self ._update_tool_event )
171206 self .ros_node .user_speech_received .connect (self ._update_user_speech )
172207 self .ros_node .connection_status_received .connect (self ._update_connection_status )
208+ self .ros_node .vip_detection_received .connect (self ._update_vip_detection )
209+ self .ros_node .extension_event_received .connect (self ._update_extension_event )
173210
174211 # Connect virtual request widget signals to publishers
175212 self .virtual_request_widget .virtual_request_signal .connect (self ._send_virtual_request )
@@ -288,6 +325,10 @@ def _update_agent_status(self, status):
288325 self .agent_status_widget .update_status (status )
289326 self .emotion_widget .update_emotion (status .emotion , status .previous_emotion )
290327 self .conversation_widget .update_agent_state (status )
328+
329+ # Reset admin override widget when conversation ends
330+ if status .behavioral_mode == "dormant" :
331+ self .admin_override_widget .reset_vip_status ()
291332
292333 @pyqtSlot (ToolEvent )
293334 def _update_tool_event (self , event ):
@@ -305,6 +346,26 @@ def _update_connection_status(self, connected):
305346 """Update UI with connection status"""
306347 self .agent_status_widget .update_connection (connected )
307348
349+ @pyqtSlot (VipDetection )
350+ def _update_vip_detection (self , detection ):
351+ """Update UI with new VIP detection"""
352+ self .admin_override_widget .update_vip_detection (
353+ detection .user_identifier ,
354+ list (detection .matched_keywords ),
355+ detection .importance_level ,
356+ detection .recommended_extension_minutes
357+ )
358+
359+ @pyqtSlot (ExtensionEvent )
360+ def _update_extension_event (self , event ):
361+ """Update UI with new extension event"""
362+ self .admin_override_widget .update_extension_event (
363+ event .action ,
364+ event .extension_minutes ,
365+ event .reason ,
366+ event .granted_by
367+ )
368+
308369 @pyqtSlot (str )
309370 def _send_virtual_request (self , request_json ):
310371 """Send virtual request through ROS2"""
0 commit comments