diff --git a/pds.py b/pds.py index b223ca3..083dad5 100644 --- a/pds.py +++ b/pds.py @@ -216,9 +216,6 @@ class WebAssistantScreen(GradientWidget): # Chat bubble widget for chat interface -import queue -import threading - class ChatBubble(QWidget): def __init__(self, text, is_user=False): super().__init__() @@ -242,11 +239,7 @@ class ChatBubble(QWidget): bubble_label.setMaximumWidth(250) bubble_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) - if self.is_user: - layout.setAlignment(Qt.AlignmentFlag.AlignRight) - else: - layout.setAlignment(Qt.AlignmentFlag.AlignLeft) - + layout.setAlignment(Qt.AlignmentFlag.AlignRight if self.is_user else Qt.AlignmentFlag.AlignLeft) layout.addWidget(bubble_label) @@ -263,229 +256,260 @@ class ChatScrollArea(QScrollArea): def add_bubble(self, text, is_user=False): bubble = ChatBubble(text, is_user=is_user) self.chat_layout.addWidget(bubble) - # Scroll to bottom reliably self.verticalScrollBar().setValue(self.verticalScrollBar().maximum()) -# class ProductionBotScreen(QWidget): -# def __init__(self, back_action=None): -# super().__init__() -# self.back_action = back_action -# -# self.chart_map = {} -# self.plant_map = {} -# self.awaiting_plants = False -# -# self.init_tts() -# self._setup_ui() -# -# QTimer.singleShot(500, self._show_greeting) -# QTimer.singleShot(1500, self.load_charts) -# -# self.tts_queue = queue.Queue() -# self.tts_thread = threading.Thread(target=self.tts_worker, daemon=True) -# self.tts_thread.start() -# -# def init_tts(self): -# self.tts_engine = pyttsx3.init() -# self.tts_engine.setProperty('rate', 170) -# -# def _setup_ui(self): -# layout = QVBoxLayout(self) -# layout.setContentsMargins(0, 0, 0, 0) -# layout.setSpacing(0) -# -# header = QWidget() -# header.setFixedHeight(70) -# # Fix indentation here: -# header.setStyleSheet(""" -# background: qlineargradient(x1:0,y1:0,x2:1,y2:0, -# stop:0 #B0E0E6, stop:1 #F3C2C2); -# color: #333; -# font-size: 22px; -# """) -# hlayout = QHBoxLayout(header) -# hlayout.setContentsMargins(10, 5, 10, 5) -# -# logo = QLabel() -# pix = QPixmap("cri_logo.png") # Corrected filename -# if pix and not pix.isNull(): -# logo.setPixmap(pix.scaled(60, 60, Qt.AspectRatioMode.KeepAspectRatio)) -# hlayout.addWidget(logo) -# -# title = QLabel("PRODUCTION BOT") -# title.setStyleSheet("font-size: 24px; font-weight: bold; color: #333;") -# hlayout.addWidget(title) -# hlayout.addStretch(1) -# -# self.back_btn = QPushButton("Back") -# self.back_btn.setStyleSheet(""" -# QPushButton { -# background: #C71585; -# color: white; -# border-radius: 8px; -# padding: 8px 14px; -# font-weight: bold; -# } -# QPushButton:hover { -# background: #b95975; -# } -# """) -# if self.back_action: -# self.back_btn.clicked.connect(self.back_action) -# hlayout.addWidget(self.back_btn) -# -# self.refresh_btn = QPushButton("Refresh") -# self.refresh_btn.setStyleSheet(""" -# QPushButton { -# background: #C71585; -# color: white; -# border-radius: 8px; -# padding: 8px 14px; -# font-weight: bold; -# } -# QPushButton:hover { -# background: #b95975; -# } -# """) -# hlayout.addWidget(self.refresh_btn) -# -# layout.addWidget(header) -# -# self.chat_area = ChatScrollArea() -# self.chat_area.setStyleSheet(""" -# QScrollArea { -# background: qlineargradient(x1:0,y1:0,x2:1,y2:1, -# stop:0 #b0e6e6, stop:1 #f3c2c2); -# } -# """) -# layout.addWidget(self.chat_area, stretch=1) -# -# input_area = QWidget() -# input_layout = QHBoxLayout(input_area) -# input_layout.setContentsMargins(10, 10, 10, 10) -# -# self.user_input = QLineEdit() -# self.user_input.setPlaceholderText("Type your message...") -# self.user_input.setStyleSheet(""" -# QLineEdit { -# font-size: 16px; -# padding: 8px; -# border: 2px solid #db7093; -# border-radius: 4px; -# } -# QLineEdit:focus { -# border-color: #f3c2c2; -# background: #fff0f0; -# } -# """) -# self.send_btn = QPushButton("Send") -# self.send_btn.setStyleSheet(""" -# QPushButton { -# background: #006400; -# color: white; -# font-weight: bold; -# padding: 8px 20px; -# border-radius: 8px; -# } -# QPushButton:hover { -# background: #a2136a; -# } -# """) -# input_layout.addWidget(self.user_input) -# input_layout.addWidget(self.send_btn) -# -# layout.addWidget(input_area) -# -# self.send_btn.clicked.connect(self._on_send) -# -# def _on_send(self): -# msg = self.user_input.text().strip() -# if not msg: -# return -# self.chat_area.add_bubble(msg, True) -# self.user_input.clear() -# -# if msg.isdigit(): -# idx = int(msg) -# if self.awaiting_plants: -# plant = self.plant_map.get(idx) -# if plant: -# self.plant_selected(plant) -# else: -# self.chat_area.add_bubble("Invalid plant number. Please try again.", False) -# self.speak("Invalid plant number. Please try again.") -# else: -# chart = self.chart_map.get(idx) -# if chart: -# self.chart_selected(chart) -# else: -# self.chat_area.add_bubble("Invalid chart number. Please try again.", False) -# self.speak("Invalid chart number. Please try again.") -# else: -# self.chat_area.add_bubble("Please enter a valid number.", False) -# self.speak("Please enter a valid number.") -# -# def load_charts(self): -# url = "https://pds.iotsignin.com/api/get/modulechart-name/data" -# headers = { -# "Authorization": "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE=", -# "module-name": "Production DashBoard" -# } -# -# try: -# response = requests.get(url, headers=headers, timeout=10) -# response.raise_for_status() -# data = response.json() -# if data.get("status_code") == "SUCCESS": -# charts = data.get("status_description", []) -# self.chart_map = {i + 1: c for i, c in enumerate(charts)} -# numbered = "\n".join(f"{num}. {name}" for num, name in self.chart_map.items()) -# self.chat_area.add_bubble("Please select a chart:\n\n" + numbered, False) -# self.speak("Please select a chart.") -# else: -# self.chat_area.add_bubble("Failed to load charts.", False) -# self.speak("Failed to load charts.") -# except Exception as e: -# self.chat_area.add_bubble(f"Error loading charts: {str(e)}", False) -# self.speak("Error loading charts.") -# -# def chart_selected(self, chart_name): -# self.chat_area.add_bubble(f"You selected: {chart_name}", False) -# self.speak(f"You selected {chart_name}") -# self.awaiting_plants = True -# QTimer.singleShot(500, self.load_plants) -# -# def load_plants(self): -# url = "https://pds.iotsignin.com/api/get/moduleplant-name/data" # Correct API URL for plants -# headers = { -# "Authorization": "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE=", -# "plant-name": "Plant List" -# } -# -# try: -# response = requests.get(url, headers=headers, timeout=10) -# response.raise_for_status() -# data = response.json() -# if data.get("status_code") == "SUCCESS": -# plants = data.get("status_description", []) -# self.plant_map = {i + 1: p for i, p in enumerate(plants)} -# numbered = "\n".join(f"{num}. {name}" for num, name in self.plant_map.items()) -# self.chat_area.add_bubble("Please select a plant:\n\n" + numbered, False) -# self.speak("Please select a plant.") -# else: -# self.chat_area.add_bubble("Failed to load plants.", False) -# self.speak("Failed to load plants.") -# except Exception as e: -# self.chat_area.add_bubble(f"Error loading plants: {str(e)}", False) -# self.speak("Error loading plants.") -# -# def plant_selected(self, plant_name): -# self.chat_area.add_bubble(f"You selected plant: {plant_name}", False) -# self.speak(f"You selected plant {plant_name}") -# self.awaiting_plants = False +class ProductionBotScreen(QWidget): + def __init__(self, back_action=None): + super().__init__() + self.back_action = back_action -# InvoiceBotScreen, independent UI and logic + self.chart_map = {} + self.plant_map = {} + self.awaiting_plants = False + + self.init_tts() + self._setup_ui() + + QTimer.singleShot(500, self._show_greeting) + QTimer.singleShot(1500, self.load_charts) + + self.tts_queue = queue.Queue() + self.tts_thread = threading.Thread(target=self.tts_worker, daemon=True) + self.tts_thread.start() + + def init_tts(self): + self.tts_engine = pyttsx3.init() + self.tts_engine.setProperty('rate', 170) + # No voice filtering - use system default voice + + def tts_worker(self): + while True: + text = self.tts_queue.get() + if text is None: + break + try: + self.tts_engine.say(text) + self.tts_engine.runAndWait() + except Exception: + pass + self.tts_queue.task_done() + + def speak(self, text): + self.tts_queue.put(text) + + def closeEvent(self, event): + self.tts_queue.put(None) + self.tts_thread.join(timeout=2) + super().closeEvent(event) + + def _setup_ui(self): + layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + header = QWidget() + header.setFixedHeight(70) + header.setStyleSheet(""" + background: qlineargradient(x1:0,y1:0,x2:1,y2:0, + stop:0 #B0E0E6, stop:1 #F3C2C2); + color: #333; + font-size: 22px; + """) + hlayout = QHBoxLayout(header) + hlayout.setContentsMargins(10, 5, 10, 5) + + logo = QLabel() + pix = QPixmap("cri_logo.png") + if pix and not pix.isNull(): + logo.setPixmap(pix.scaled(60, 60, Qt.AspectRatioMode.KeepAspectRatio)) + hlayout.addWidget(logo) + + title = QLabel("PRODUCTION BOT") + title.setStyleSheet("font-size: 24px; font-weight: bold; color: #333;") + hlayout.addWidget(title) + + hlayout.addStretch(1) + + self.back_btn = QPushButton("Back") + self.back_btn.setStyleSheet(""" + QPushButton { + background: #C71585; + color: white; + border-radius: 8px; + padding: 8px 14px; + font-weight: bold; + } + QPushButton:hover { + background: #b95975; + } + """) + if self.back_action: + self.back_btn.clicked.connect(self.back_action) + hlayout.addWidget(self.back_btn) + + self.refresh_btn = QPushButton("Refresh") + self.refresh_btn.setStyleSheet(""" + QPushButton { + background: #C71585; + color: white; + border-radius: 8px; + padding: 8px 14px; + font-weight: bold; + } + QPushButton:hover { + background: #b95975; + } + """) + hlayout.addWidget(self.refresh_btn) + + layout.addWidget(header) + + self.chat_area = ChatScrollArea() + self.chat_area.setStyleSheet(""" + QScrollArea { + background: qlineargradient(x1:0,y1:0,x2:1,y2:1, + stop:0 #b0e6e6, stop:1 #f3c2c2); + } + """) + layout.addWidget(self.chat_area, stretch=1) + + input_area = QWidget() + input_layout = QHBoxLayout(input_area) + input_layout.setContentsMargins(10, 10, 10, 10) + + self.user_input = QLineEdit() + self.user_input.setPlaceholderText("Type your message...") + self.user_input.setStyleSheet(""" + QLineEdit { + font-size: 16px; + padding: 8px; + border: 2px solid #db7093; + border-radius: 4px; + } + QLineEdit:focus { + border-color: #f3c2c2; + background: #fff0f0; + } + """) + self.send_btn = QPushButton("Send") + self.send_btn.setStyleSheet(""" + QPushButton { + background: #006400; + color: white; + font-weight: bold; + padding: 8px 20px; + border-radius: 8px; + } + QPushButton:hover { + background: #a2136a; + } + """) + input_layout.addWidget(self.user_input) + input_layout.addWidget(self.send_btn) + + layout.addWidget(input_area) + + self.send_btn.clicked.connect(self._on_send) + + def _show_greeting(self): + hour = QTime.currentTime().hour() + greeting = "Good night" + if 5 <= hour < 12: + greeting = "Good morning" + elif 12 <= hour < 17: + greeting = "Good afternoon" + elif 17 <= hour < 21: + greeting = "Good evening" + self.chat_area.add_bubble(f"{greeting}, User", False) + self.speak(f"{greeting}, User") + + def _on_send(self): + msg = self.user_input.text().strip() + if not msg: + return + self.chat_area.add_bubble(msg, True) + self.user_input.clear() + + if msg.isdigit(): + idx = int(msg) + if self.awaiting_plants: + plant = self.plant_map.get(idx) + if plant: + self.plant_selected(plant) + else: + self.chat_area.add_bubble("Invalid plant number. Please try again.", False) + self.speak("Invalid plant number. Please try again.") + else: + chart = self.chart_map.get(idx) + if chart: + self.chart_selected(chart) + else: + self.chat_area.add_bubble("Invalid chart number. Please try again.", False) + self.speak("Invalid chart number. Please try again.") + else: + self.chat_area.add_bubble("Please enter a valid number.", False) + self.speak("Please enter a valid number.") + + def load_charts(self): + url = "https://pds.iotsignin.com/api/get/modulechart-name/data" + headers = { + "Authorization": "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE=", + "module-name": "Production DashBoard" + } + try: + response = requests.get(url, headers=headers, timeout=10) + response.raise_for_status() + data = response.json() + if data.get("status_code") == "SUCCESS": + charts = data.get("status_description", []) + self.chart_map = {i + 1: c for i, c in enumerate(charts)} + numbered = "\n".join(f"{num}. {name}" for num, name in self.chart_map.items()) + self.chat_area.add_bubble("Please select a chart:\n\n" + numbered, False) + self.speak("Please select a chart.") + else: + self.chat_area.add_bubble("Failed to load charts.", False) + self.speak("Failed to load charts.") + except Exception as e: + self.chat_area.add_bubble(f"Error loading charts: {str(e)}", False) + self.speak("Error loading charts.") + + def chart_selected(self, chart_name): + self.chat_area.add_bubble(f"You selected: {chart_name}", False) + self.speak(f"You selected {chart_name}") + self.awaiting_plants = True + QTimer.singleShot(500, self.load_plants) + + # def load_plants(self): + # url = "https://pds.iotsignin.com/api/get/moduleplant-name/data" + # headers = { + # "Authorization": "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE=", + # "plant-name": "Plant List" + # } + try: + response = requests.get(url, headers=headers, timeout=10) + response.raise_for_status() + data = response.json() + if data.get("status_code") == "SUCCESS": + plants = data.get("status_description", []) + self.plant_map = {i + 1: p for i, p in enumerate(plants)} + numbered = "\n".join(f"{num}. {name}" for num, name in self.plant_map.items()) + self.chat_area.add_bubble("Please select a plant:\n\n" + numbered, False) + self.speak("Please select a plant.") + else: + self.chat_area.add_bubble("Failed to load plants.", False) + self.speak("Failed to load plants.") + except Exception as e: + self.chat_area.add_bubble(f"Error loading plants: {str(e)}", False) + self.speak("Error loading plants.") + + def plant_selected(self, plant_name): + self.chat_area.add_bubble(f"You selected plant: {plant_name}", False) + self.speak(f"You selected plant {plant_name}") + self.awaiting_plants = False + + +# InvoiceBotScreen is unchanged based on your last code snippet class InvoiceBotScreen(QWidget): def __init__(self, back_action=None): super().__init__() @@ -509,8 +533,8 @@ class InvoiceBotScreen(QWidget): hlayout.setContentsMargins(10, 5, 10, 5) logo = QLabel() - pix = QPixmap("cri_logo.png.png") - if not pix.isNull(): + pix = QPixmap("cri_logo.png") + if pix and not pix.isNull(): logo.setPixmap(pix.scaled(60, 60, Qt.AspectRatioMode.KeepAspectRatio)) hlayout.addWidget(logo) @@ -595,17 +619,11 @@ class InvoiceBotScreen(QWidget): layout.addWidget(input_widget) self.send_btn.clicked.connect(self._on_send) + self._show_greeting() self.chat_area.add_bubble("Invoice bot activated. What invoice do you need?", is_user=False) - def _on_send(self): - msg = self.user_input.text().strip() - if msg: - self.chat_area.add_bubble(msg, is_user=True) - self.user_input.clear() - self.chat_area.add_bubble("Processing your invoice request...", is_user=False) - def _show_greeting(self): hour = QTime.currentTime().hour() greeting = "Good night" @@ -615,8 +633,14 @@ class InvoiceBotScreen(QWidget): greeting = "Good afternoon" elif 17 <= hour < 21: greeting = "Good evening" - self.chat_area.add_bubble(f"{greeting}, User", is_user=False) + self.chat_area.add_bubble(f"{greeting}, User", False) + def _on_send(self): + msg = self.user_input.text().strip() + if msg: + self.chat_area.add_bubble(msg, True) + self.user_input.clear() + self.chat_area.add_bubble("Processing your invoice request...", False) # Bot selector screen to choose Production or Invoice bots class BotSelectorScreen(QWidget):