""" VLM AI Insights Tab - ChatGPT-like interface for Vision Language Model interactions """ from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QTextEdit, QLineEdit, QPushButton, QScrollArea, QFrame, QLabel, QSplitter, QComboBox, QSlider, QGroupBox, QCheckBox, QSpinBox) from PySide6.QtCore import Qt, Signal, QTimer, QDateTime from PySide6.QtGui import QFont, QTextCharFormat, QColor, QPixmap class ChatMessage(QFrame): """Individual chat message widget""" def __init__(self, message, is_user=True, timestamp=None, parent=None): super().__init__(parent) self.message = message self.is_user = is_user self.timestamp = timestamp or QDateTime.currentDateTime() self._setup_ui() self._apply_style() def _setup_ui(self): """Setup message UI""" layout = QHBoxLayout(self) layout.setContentsMargins(10, 8, 10, 8) if self.is_user: layout.addStretch() # Message bubble bubble = QFrame() bubble.setMaximumWidth(400) bubble_layout = QVBoxLayout(bubble) bubble_layout.setContentsMargins(12, 8, 12, 8) # Message text message_label = QLabel(self.message) message_label.setWordWrap(True) message_label.setFont(QFont("Segoe UI", 9)) bubble_layout.addWidget(message_label) # Timestamp time_label = QLabel(self.timestamp.toString("hh:mm")) time_label.setFont(QFont("Segoe UI", 7)) time_label.setAlignment(Qt.AlignRight if self.is_user else Qt.AlignLeft) bubble_layout.addWidget(time_label) layout.addWidget(bubble) if not self.is_user: layout.addStretch() def _apply_style(self): """Apply message styling""" if self.is_user: # User message (blue, right-aligned) self.setStyleSheet(""" QFrame { background-color: #3498db; border-radius: 12px; margin-left: 50px; } QLabel { color: white; } """) else: # AI message (gray, left-aligned) self.setStyleSheet(""" QFrame { background-color: #ecf0f1; border-radius: 12px; margin-right: 50px; } QLabel { color: #2c3e50; } """) class VLMInsightsTab(QWidget): """ VLM AI Insights Tab with ChatGPT-like interface Features: - Chat-style interface for VLM interactions - Image context from traffic cameras - Predefined prompts for traffic analysis - Conversation history - AI insights and recommendations - Export conversation functionality """ # Signals insight_generated = Signal(str) vlm_query_sent = Signal(str, dict) conversation_exported = Signal(str) def __init__(self, parent=None): super().__init__(parent) self.conversation_history = [] self.current_image_context = None self._setup_ui() print("🤖 VLM AI Insights Tab initialized") def _setup_ui(self): """Setup the VLM insights UI""" # Main splitter main_splitter = QSplitter(Qt.Horizontal) layout = QVBoxLayout(self) layout.addWidget(main_splitter) # Left panel - Chat interface left_panel = self._create_chat_panel() main_splitter.addWidget(left_panel) # Right panel - Settings and context right_panel = self._create_settings_panel() main_splitter.addWidget(right_panel) # Set splitter proportions (70% chat, 30% settings) main_splitter.setSizes([700, 300]) def _create_chat_panel(self): """Create chat interface panel""" panel = QFrame() layout = QVBoxLayout(panel) # Chat header header = self._create_chat_header() layout.addWidget(header) # Conversation area self.conversation_scroll = QScrollArea() self.conversation_scroll.setWidgetResizable(True) self.conversation_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarNever) self.conversation_widget = QWidget() self.conversation_layout = QVBoxLayout(self.conversation_widget) self.conversation_layout.setAlignment(Qt.AlignTop) self.conversation_layout.setSpacing(5) self.conversation_scroll.setWidget(self.conversation_widget) layout.addWidget(self.conversation_scroll, 1) # Input area input_area = self._create_input_area() layout.addWidget(input_area) return panel def _create_chat_header(self): """Create chat header with title and controls""" header = QFrame() header.setFixedHeight(50) header.setStyleSheet("background-color: #34495e; border-radius: 8px; margin-bottom: 5px;") layout = QHBoxLayout(header) layout.setContentsMargins(15, 10, 15, 10) # Title and status title_layout = QVBoxLayout() title = QLabel("🤖 VLM AI Assistant") title.setFont(QFont("Segoe UI", 12, QFont.Bold)) title.setStyleSheet("color: white;") title_layout.addWidget(title) self.status_label = QLabel("Ready to analyze traffic scenes") self.status_label.setFont(QFont("Segoe UI", 8)) self.status_label.setStyleSheet("color: #bdc3c7;") title_layout.addWidget(self.status_label) layout.addLayout(title_layout) layout.addStretch() # Action buttons clear_btn = QPushButton("🗑️ Clear") clear_btn.setFixedSize(60, 30) clear_btn.setStyleSheet(""" QPushButton { background-color: #e74c3c; color: white; border: none; border-radius: 4px; font-size: 8pt; } QPushButton:hover { background-color: #c0392b; } """) clear_btn.clicked.connect(self._clear_conversation) layout.addWidget(clear_btn) export_btn = QPushButton("📤 Export") export_btn.setFixedSize(60, 30) export_btn.setStyleSheet(""" QPushButton { background-color: #3498db; color: white; border: none; border-radius: 4px; font-size: 8pt; } QPushButton:hover { background-color: #2980b9; } """) export_btn.clicked.connect(self._export_conversation) layout.addWidget(export_btn) return header def _create_input_area(self): """Create message input area""" input_frame = QFrame() input_frame.setFixedHeight(100) layout = QVBoxLayout(input_frame) # Quick prompts prompts_layout = QHBoxLayout() prompts = [ "Analyze current traffic", "Count vehicles", "Detect violations", "Safety assessment" ] for prompt in prompts: btn = QPushButton(prompt) btn.setMaximumHeight(25) btn.setStyleSheet(""" QPushButton { background-color: #ecf0f1; border: 1px solid #bdc3c7; border-radius: 12px; padding: 4px 8px; font-size: 8pt; } QPushButton:hover { background-color: #d5dbdb; } """) btn.clicked.connect(lambda checked, p=prompt: self._send_quick_prompt(p)) prompts_layout.addWidget(btn) prompts_layout.addStretch() layout.addLayout(prompts_layout) # Message input input_layout = QHBoxLayout() self.message_input = QLineEdit() self.message_input.setPlaceholderText("Ask the AI about traffic conditions, violations, or safety...") self.message_input.setFont(QFont("Segoe UI", 9)) self.message_input.setStyleSheet(""" QLineEdit { border: 2px solid #bdc3c7; border-radius: 20px; padding: 8px 15px; font-size: 9pt; } QLineEdit:focus { border-color: #3498db; } """) self.message_input.returnPressed.connect(self._send_message) input_layout.addWidget(self.message_input) self.send_btn = QPushButton("➤") self.send_btn.setFixedSize(40, 40) self.send_btn.setStyleSheet(""" QPushButton { background-color: #3498db; color: white; border: none; border-radius: 20px; font-size: 16pt; font-weight: bold; } QPushButton:hover { background-color: #2980b9; } QPushButton:pressed { background-color: #21618c; } """) self.send_btn.clicked.connect(self._send_message) input_layout.addWidget(self.send_btn) layout.addLayout(input_layout) return input_frame def _create_settings_panel(self): """Create settings and context panel""" panel = QFrame() layout = QVBoxLayout(panel) # Image context section context_section = self._create_context_section() layout.addWidget(context_section) # VLM settings settings_section = self._create_vlm_settings() layout.addWidget(settings_section) # Conversation stats stats_section = self._create_stats_section() layout.addWidget(stats_section) layout.addStretch() return panel def _create_context_section(self): """Create image context section""" section = QGroupBox("Image Context") layout = QVBoxLayout(section) # Current image preview self.image_preview = QLabel("No image selected") self.image_preview.setFixedSize(200, 150) self.image_preview.setAlignment(Qt.AlignCenter) self.image_preview.setStyleSheet(""" QLabel { border: 2px dashed #bdc3c7; border-radius: 8px; background-color: #ecf0f1; color: #7f8c8d; } """) layout.addWidget(self.image_preview) # Image source selection source_layout = QHBoxLayout() source_layout.addWidget(QLabel("Source:")) self.image_source_combo = QComboBox() self.image_source_combo.addItems([ "Live Camera 1", "Live Camera 2", "Current Frame", "Upload Image" ]) self.image_source_combo.currentTextChanged.connect(self._change_image_source) source_layout.addWidget(self.image_source_combo) layout.addLayout(source_layout) # Capture button capture_btn = QPushButton("📸 Capture Current") capture_btn.clicked.connect(self._capture_current_frame) layout.addWidget(capture_btn) return section def _create_vlm_settings(self): """Create VLM model settings""" section = QGroupBox("AI Settings") layout = QVBoxLayout(section) # Model selection model_layout = QHBoxLayout() model_layout.addWidget(QLabel("Model:")) self.model_combo = QComboBox() self.model_combo.addItems([ "LLaVA-Next-Video", "GPT-4 Vision", "Claude Vision" ]) model_layout.addWidget(self.model_combo) layout.addLayout(model_layout) # Temperature setting temp_layout = QHBoxLayout() temp_layout.addWidget(QLabel("Creativity:")) self.temperature_slider = QSlider(Qt.Horizontal) self.temperature_slider.setRange(1, 10) self.temperature_slider.setValue(5) temp_layout.addWidget(self.temperature_slider) self.temp_label = QLabel("0.5") temp_layout.addWidget(self.temp_label) layout.addLayout(temp_layout) # Max response length length_layout = QHBoxLayout() length_layout.addWidget(QLabel("Max Length:")) self.max_length_spin = QSpinBox() self.max_length_spin.setRange(50, 1000) self.max_length_spin.setValue(300) self.max_length_spin.setSuffix(" words") length_layout.addWidget(self.max_length_spin) layout.addLayout(length_layout) # Analysis options self.detailed_analysis_cb = QCheckBox("Detailed Analysis") self.detailed_analysis_cb.setChecked(True) layout.addWidget(self.detailed_analysis_cb) self.safety_focus_cb = QCheckBox("Safety Focus") self.safety_focus_cb.setChecked(True) layout.addWidget(self.safety_focus_cb) # Connect temperature slider to label self.temperature_slider.valueChanged.connect( lambda v: self.temp_label.setText(f"{v/10:.1f}") ) return section def _create_stats_section(self): """Create conversation statistics""" section = QGroupBox("Conversation Stats") layout = QVBoxLayout(section) # Message count self.message_count_label = QLabel("Messages: 0") layout.addWidget(self.message_count_label) # Insights generated self.insights_count_label = QLabel("Insights: 0") layout.addWidget(self.insights_count_label) # Session time self.session_time_label = QLabel("Session: 0 min") layout.addWidget(self.session_time_label) return section def _send_message(self): """Send user message""" message = self.message_input.text().strip() if not message: return # Add user message self._add_message(message, is_user=True) # Clear input self.message_input.clear() # Send to VLM (simulate response for now) self._simulate_vlm_response(message) # Emit signal context = { 'image': self.current_image_context, 'settings': self._get_current_settings() } self.vlm_query_sent.emit(message, context) def _send_quick_prompt(self, prompt): """Send a quick prompt""" self.message_input.setText(prompt) self._send_message() def _add_message(self, message, is_user=True): """Add message to conversation""" chat_message = ChatMessage(message, is_user) self.conversation_layout.addWidget(chat_message) # Scroll to bottom QTimer.singleShot(100, lambda: self.conversation_scroll.verticalScrollBar().setValue( self.conversation_scroll.verticalScrollBar().maximum() )) # Update stats self.conversation_history.append({ 'message': message, 'is_user': is_user, 'timestamp': QDateTime.currentDateTime() }) self._update_stats() def _simulate_vlm_response(self, user_message): """Simulate VLM response (replace with actual VLM integration)""" # Simulate processing delay self.status_label.setText("AI is analyzing...") QTimer.singleShot(2000, lambda: self._generate_response(user_message)) def _generate_response(self, user_message): """Generate AI response""" # Simple response simulation responses = { "analyze current traffic": "I can see moderate traffic flow with 8 vehicles currently in view. Traffic appears to be flowing smoothly with no apparent congestion. Most vehicles are maintaining safe following distances.", "count vehicles": "I count 5 cars, 2 trucks, and 1 motorcycle currently visible in the intersection. Traffic density appears normal for this time of day.", "detect violations": "I don't detect any obvious traffic violations at this moment. All vehicles appear to be following traffic signals and maintaining proper lanes.", "safety assessment": "Overall safety conditions look good. Visibility is clear, traffic signals are functioning properly, and vehicle speeds appear appropriate for the intersection." } # Find best matching response response = None for key, value in responses.items(): if key.lower() in user_message.lower(): response = value break if not response: response = f"I understand you're asking about '{user_message}'. Based on the current traffic scene, I can provide analysis of vehicle movements, count objects, assess safety conditions, and identify potential violations. Could you be more specific about what aspect you'd like me to focus on?" # Add AI response self._add_message(response, is_user=False) # Update status self.status_label.setText("Ready to analyze traffic scenes") # Emit insight signal self.insight_generated.emit(response) def _clear_conversation(self): """Clear conversation history""" # Remove all message widgets for i in reversed(range(self.conversation_layout.count())): child = self.conversation_layout.itemAt(i).widget() if child: child.setParent(None) # Clear history self.conversation_history.clear() # Update stats self._update_stats() print("🤖 Conversation cleared") def _export_conversation(self): """Export conversation history""" self.conversation_exported.emit("conversation_history") print("🤖 Conversation exported") def _change_image_source(self, source): """Change image context source""" self.image_preview.setText(f"Source: {source}") print(f"🤖 Image source changed to: {source}") def _capture_current_frame(self): """Capture current frame for analysis""" # Simulate frame capture self.image_preview.setText("Current frame\ncaptured") self.current_image_context = "current_frame" print("🤖 Current frame captured for analysis") def _get_current_settings(self): """Get current VLM settings""" return { 'model': self.model_combo.currentText(), 'temperature': self.temperature_slider.value() / 10.0, 'max_length': self.max_length_spin.value(), 'detailed_analysis': self.detailed_analysis_cb.isChecked(), 'safety_focus': self.safety_focus_cb.isChecked() } def _update_stats(self): """Update conversation statistics""" total_messages = len(self.conversation_history) ai_messages = sum(1 for msg in self.conversation_history if not msg['is_user']) self.message_count_label.setText(f"Messages: {total_messages}") self.insights_count_label.setText(f"Insights: {ai_messages}") def add_ai_insight(self, insight_text): """Add an AI insight to the conversation""" self._add_message(insight_text, is_user=False) def set_image_context(self, image_data): """Set image context for VLM analysis""" self.current_image_context = image_data # Update image preview if needed print("🤖 Image context updated for VLM analysis")