Merge pull request 'pds-bot' (#1) from pds-bot into master

Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
2025-08-06 08:33:10 +00:00

498
pds.py
View File

@@ -216,9 +216,6 @@ class WebAssistantScreen(GradientWidget):
# Chat bubble widget for chat interface # Chat bubble widget for chat interface
import queue
import threading
class ChatBubble(QWidget): class ChatBubble(QWidget):
def __init__(self, text, is_user=False): def __init__(self, text, is_user=False):
super().__init__() super().__init__()
@@ -242,11 +239,7 @@ class ChatBubble(QWidget):
bubble_label.setMaximumWidth(250) bubble_label.setMaximumWidth(250)
bubble_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) bubble_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
if self.is_user: layout.setAlignment(Qt.AlignmentFlag.AlignRight if self.is_user else Qt.AlignmentFlag.AlignLeft)
layout.setAlignment(Qt.AlignmentFlag.AlignRight)
else:
layout.setAlignment(Qt.AlignmentFlag.AlignLeft)
layout.addWidget(bubble_label) layout.addWidget(bubble_label)
@@ -263,229 +256,260 @@ class ChatScrollArea(QScrollArea):
def add_bubble(self, text, is_user=False): def add_bubble(self, text, is_user=False):
bubble = ChatBubble(text, is_user=is_user) bubble = ChatBubble(text, is_user=is_user)
self.chat_layout.addWidget(bubble) self.chat_layout.addWidget(bubble)
# Scroll to bottom reliably
self.verticalScrollBar().setValue(self.verticalScrollBar().maximum()) self.verticalScrollBar().setValue(self.verticalScrollBar().maximum())
# class ProductionBotScreen(QWidget): class ProductionBotScreen(QWidget):
# def __init__(self, back_action=None): def __init__(self, back_action=None):
# super().__init__() super().__init__()
# self.back_action = back_action 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
# 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): class InvoiceBotScreen(QWidget):
def __init__(self, back_action=None): def __init__(self, back_action=None):
super().__init__() super().__init__()
@@ -509,8 +533,8 @@ class InvoiceBotScreen(QWidget):
hlayout.setContentsMargins(10, 5, 10, 5) hlayout.setContentsMargins(10, 5, 10, 5)
logo = QLabel() logo = QLabel()
pix = QPixmap("cri_logo.png.png") pix = QPixmap("cri_logo.png")
if not pix.isNull(): if pix and not pix.isNull():
logo.setPixmap(pix.scaled(60, 60, Qt.AspectRatioMode.KeepAspectRatio)) logo.setPixmap(pix.scaled(60, 60, Qt.AspectRatioMode.KeepAspectRatio))
hlayout.addWidget(logo) hlayout.addWidget(logo)
@@ -595,17 +619,11 @@ class InvoiceBotScreen(QWidget):
layout.addWidget(input_widget) layout.addWidget(input_widget)
self.send_btn.clicked.connect(self._on_send) self.send_btn.clicked.connect(self._on_send)
self._show_greeting() self._show_greeting()
self.chat_area.add_bubble("Invoice bot activated. What invoice do you need?", is_user=False) 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): def _show_greeting(self):
hour = QTime.currentTime().hour() hour = QTime.currentTime().hour()
greeting = "Good night" greeting = "Good night"
@@ -615,8 +633,14 @@ class InvoiceBotScreen(QWidget):
greeting = "Good afternoon" greeting = "Good afternoon"
elif 17 <= hour < 21: elif 17 <= hour < 21:
greeting = "Good evening" 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 # Bot selector screen to choose Production or Invoice bots
class BotSelectorScreen(QWidget): class BotSelectorScreen(QWidget):