1010 lines
36 KiB
Python
1010 lines
36 KiB
Python
import sys
|
|
import os
|
|
import base64
|
|
import requests
|
|
import pyttsx3
|
|
import queue
|
|
import threading
|
|
from PyQt6.QtCore import Qt, QUrl, QObject, pyqtSlot, QTimer, QTime
|
|
from PyQt6.QtGui import QIcon, QPixmap, QAction, QFont, QColor, QLinearGradient, QBrush, QPainter, QPalette
|
|
from PyQt6.QtWidgets import (
|
|
QApplication, QWidget, QLabel, QLineEdit, QPushButton, QMessageBox,
|
|
QVBoxLayout, QHBoxLayout, QStackedWidget, QComboBox, QFileDialog,
|
|
QTabWidget, QFrame, QScrollArea, QSizePolicy
|
|
)
|
|
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
|
from PyQt6.QtWebChannel import QWebChannel
|
|
from threading import Thread
|
|
|
|
|
|
# Gradient widget base class for backgrounds
|
|
class GradientWidget(QWidget):
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.setAutoFillBackground(True)
|
|
|
|
def paintEvent(self, event):
|
|
painter = QPainter(self)
|
|
gradient = QLinearGradient(0, 0, 0, self.height())
|
|
gradient.setColorAt(0, QColor("#B0E0E6"))
|
|
gradient.setColorAt(1, QColor("#F3C2C2"))
|
|
painter.fillRect(self.rect(), QBrush(gradient))
|
|
|
|
|
|
# JSBridge for interacting with web content (file save)
|
|
class JSBridge(QObject):
|
|
@pyqtSlot(str, str, str)
|
|
def saveFile(self, base64_data, filename, source):
|
|
if base64_data.startswith("data:"):
|
|
base64_data = base64_data.split(",", 1)[1]
|
|
data = base64.b64decode(base64_data)
|
|
path, _ = QFileDialog.getSaveFileName(None, f"Save {source.title()} File", filename)
|
|
if path:
|
|
with open(path, "wb") as f:
|
|
f.write(data)
|
|
QMessageBox.information(None, f"{source.title()} Download", f"Saved to:\n{path}")
|
|
if sys.platform.startswith("win"):
|
|
os.startfile(path)
|
|
elif sys.platform.startswith("darwin"):
|
|
os.system(f"open '{path}'")
|
|
else:
|
|
os.system(f"xdg-open '{path}'")
|
|
|
|
|
|
# WebAssistantScreen with tabbed browser and credential injection
|
|
class WebAssistantScreen(GradientWidget):
|
|
def __init__(self, email, password, go_back):
|
|
super().__init__()
|
|
self.email = email
|
|
self.password = password
|
|
self.go_back = go_back
|
|
self._inject_attempts = 0
|
|
self._max_inject_attempts = 5
|
|
self._setup_ui()
|
|
|
|
def _setup_ui(self):
|
|
layout = QVBoxLayout(self)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.setSpacing(0)
|
|
|
|
toolbar = QWidget(self)
|
|
toolbar.setFixedHeight(60)
|
|
toolbar.setStyleSheet("""
|
|
background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
|
|
stop:0 #B0E0E6, stop:1 #F3C2C2);
|
|
""")
|
|
hl = QHBoxLayout(toolbar)
|
|
hl.setContentsMargins(10, 5, 10, 5)
|
|
hl.setSpacing(10)
|
|
|
|
logo = QLabel()
|
|
pix = QPixmap("cri_logo.png.png")
|
|
if not pix.isNull():
|
|
logo.setPixmap(pix.scaled(60, 60, Qt.AspectRatioMode.KeepAspectRatio))
|
|
hl.addWidget(logo, alignment=Qt.AlignmentFlag.AlignVCenter)
|
|
|
|
title = QLabel(" CRI DIGITAL MANUFACTURING SOLUTIONS - PRODUCTION DISPLAY")
|
|
title.setStyleSheet("font-size: 24px; font-weight: bold; color: #333;")
|
|
hl.addWidget(title, alignment=Qt.AlignmentFlag.AlignVCenter)
|
|
hl.addStretch(1)
|
|
|
|
self.btn_back = QPushButton("Back")
|
|
self.btn_prev = QPushButton("Previous")
|
|
self.btn_refresh = QPushButton("Refresh")
|
|
self.btn_next = QPushButton("Next")
|
|
self.btn_dup = QPushButton("Duplicate")
|
|
self.btn_del = QPushButton("Delete")
|
|
self.spinner = QComboBox()
|
|
|
|
button_style = """
|
|
QPushButton {
|
|
background: #C71585;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 8px;
|
|
padding: 10px 20px;
|
|
font-size: 16px;
|
|
font-weight: bold;
|
|
}
|
|
QPushButton:hover {
|
|
background: #b95470;
|
|
}
|
|
"""
|
|
for w in (self.btn_back, self.btn_prev, self.btn_refresh,
|
|
self.btn_next, self.btn_dup, self.btn_del):
|
|
w.setStyleSheet(button_style)
|
|
hl.addWidget(w, alignment=Qt.AlignmentFlag.AlignVCenter)
|
|
|
|
self.spinner.setStyleSheet("""
|
|
QComboBox {
|
|
padding: 6px;
|
|
border: 1px solid #aaa;
|
|
border-radius: 6px;
|
|
background-color: #ffffff;
|
|
}
|
|
""")
|
|
hl.addWidget(self.spinner, alignment=Qt.AlignmentFlag.AlignVCenter)
|
|
|
|
layout.addWidget(toolbar)
|
|
|
|
self.tabs = QTabWidget(self)
|
|
self.tabs.setTabsClosable(False)
|
|
layout.addWidget(self.tabs, stretch=1)
|
|
|
|
self.channel = QWebChannel()
|
|
self.channel.registerObject("pyBridge", JSBridge())
|
|
|
|
self.add_tab("https://pds.iotsignin.com/admin")
|
|
|
|
self.btn_back.clicked.connect(self.go_back)
|
|
self.btn_prev.clicked.connect(lambda: self.tabs.currentWidget().back())
|
|
self.btn_refresh.clicked.connect(lambda: self.tabs.currentWidget().reload())
|
|
self.btn_next.clicked.connect(lambda: self.tabs.currentWidget().forward())
|
|
self.btn_dup.clicked.connect(self.duplicate_tab)
|
|
self.btn_del.clicked.connect(self.delete_tab)
|
|
self.spinner.currentIndexChanged.connect(self.switch_tab)
|
|
|
|
def add_tab(self, url):
|
|
view = QWebEngineView()
|
|
view.page().setWebChannel(self.channel)
|
|
view.load(QUrl(url))
|
|
index = self.tabs.addTab(view, "Loading…")
|
|
self.spinner.addItem(f"Tab {index + 1}")
|
|
view.titleChanged.connect(lambda t, v=view: self.tabs.setTabText(self.tabs.indexOf(v), t))
|
|
view.loadFinished.connect(self.on_page_loaded)
|
|
|
|
def on_page_loaded(self, ok):
|
|
if ok:
|
|
self._inject_attempts = 0
|
|
self.inject_credentials()
|
|
QTimer.singleShot(1000, self.inject_credentials)
|
|
QTimer.singleShot(3000, self.inject_credentials)
|
|
|
|
def inject_credentials(self):
|
|
if self._inject_attempts >= self._max_inject_attempts:
|
|
return
|
|
self._inject_attempts += 1
|
|
js_code = f"""
|
|
(function() {{
|
|
function setField(field, value) {{
|
|
if (!field) return false;
|
|
field.focus();
|
|
field.value = value;
|
|
field.dispatchEvent(new Event('input', {{ bubbles: true }}));
|
|
field.dispatchEvent(new Event('change', {{ bubbles: true }}));
|
|
return true;
|
|
}}
|
|
var emailField = document.querySelector('input[type="email"], input#email, input[name="email"], input[data-testid="email"]');
|
|
var pwdField = document.querySelector('input[type="password"], input#password, input[name="password"], input[data-testid="password"]');
|
|
var emailSet = setField(emailField, "{self.email}");
|
|
var passwordSet = setField(pwdField, "{self.password}");
|
|
return {{
|
|
emailSet: emailSet,
|
|
passwordSet: passwordSet
|
|
}};
|
|
}})();
|
|
"""
|
|
current_index = self.tabs.currentIndex()
|
|
if current_index != -1:
|
|
view = self.tabs.widget(current_index)
|
|
view.page().runJavaScript(js_code, self.handle_injection_result)
|
|
|
|
def handle_injection_result(self, result):
|
|
if result and (not result['emailSet'] or not result['passwordSet']):
|
|
if self._inject_attempts < self._max_inject_attempts:
|
|
QTimer.singleShot(1000, self.inject_credentials)
|
|
|
|
def duplicate_tab(self):
|
|
current = self.current_tab()
|
|
if current:
|
|
url = current.url().toString()
|
|
self.add_tab(url)
|
|
self.tabs.setCurrentIndex(self.tabs.count() - 1)
|
|
|
|
def delete_tab(self):
|
|
if self.tabs.count() > 1:
|
|
idx = self.tabs.currentIndex()
|
|
self.tabs.removeTab(idx)
|
|
self.spinner.removeItem(idx)
|
|
|
|
def switch_tab(self, i):
|
|
if 0 <= i < self.tabs.count():
|
|
self.tabs.setCurrentIndex(i)
|
|
|
|
def current_tab(self):
|
|
return self.tabs.currentWidget()
|
|
|
|
|
|
# Chat bubble widget for chat interface
|
|
import queue
|
|
import threading
|
|
|
|
class ChatBubble(QWidget):
|
|
def __init__(self, text, is_user=False):
|
|
super().__init__()
|
|
self.text = text
|
|
self.is_user = is_user
|
|
self.init_ui()
|
|
|
|
def init_ui(self):
|
|
layout = QVBoxLayout(self)
|
|
layout.setContentsMargins(10, 0, 10, 0)
|
|
|
|
bubble_label = QLabel(self.text)
|
|
bubble_label.setWordWrap(True)
|
|
bubble_label.setFont(QFont("Segoe UI", 12))
|
|
bubble_label.setStyleSheet(f"""
|
|
background-color: {"#B4DBFA" if self.is_user else "#2E3A59"};
|
|
color: {"black" if self.is_user else "white"};
|
|
padding: 10px 14px;
|
|
border-radius: 16px;
|
|
""")
|
|
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.addWidget(bubble_label)
|
|
|
|
|
|
class ChatScrollArea(QScrollArea):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.setWidgetResizable(True)
|
|
self.chat_content = QWidget()
|
|
self.chat_layout = QVBoxLayout(self.chat_content)
|
|
self.chat_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
|
self.chat_layout.setSpacing(8)
|
|
self.setWidget(self.chat_content)
|
|
|
|
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
|
|
|
|
# InvoiceBotScreen, independent UI and logic
|
|
class InvoiceBotScreen(QWidget):
|
|
def __init__(self, back_action=None):
|
|
super().__init__()
|
|
self.back_action = back_action
|
|
self._setup_ui()
|
|
|
|
def _setup_ui(self):
|
|
layout = QVBoxLayout(self)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.setSpacing(0)
|
|
|
|
self.header = QWidget()
|
|
self.header.setFixedHeight(70)
|
|
self.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(self.header)
|
|
hlayout.setContentsMargins(10, 5, 10, 5)
|
|
|
|
logo = QLabel()
|
|
pix = QPixmap("cri_logo.png.png")
|
|
if not pix.isNull():
|
|
logo.setPixmap(pix.scaled(60, 60, Qt.AspectRatioMode.KeepAspectRatio))
|
|
hlayout.addWidget(logo)
|
|
|
|
title = QLabel("INVOICE BOT")
|
|
title.setStyleSheet("font-size: 24px; font-weight: bold; color: #333;")
|
|
hlayout.addWidget(title)
|
|
|
|
hlayout.addStretch(1)
|
|
|
|
self.back_btn = QPushButton("Back")
|
|
self.refresh_btn = QPushButton("Refresh")
|
|
for btn in [self.back_btn, self.refresh_btn]:
|
|
btn.setStyleSheet("""
|
|
QPushButton {
|
|
background: #C71585;
|
|
color: white;
|
|
border-radius: 8px;
|
|
padding: 8px 14px;
|
|
font-weight: bold;
|
|
}
|
|
QPushButton:hover {
|
|
background: #f2f2f2;
|
|
}
|
|
""")
|
|
if self.back_action:
|
|
self.back_btn.clicked.connect(self.back_action)
|
|
|
|
hlayout.addWidget(self.back_btn)
|
|
hlayout.addWidget(self.refresh_btn)
|
|
|
|
layout.addWidget(self.header)
|
|
|
|
self.chat_area = ChatScrollArea()
|
|
self.chat_area.setStyleSheet("""
|
|
QScrollArea {
|
|
background: qlineargradient(
|
|
x1: 0, y1: 0,
|
|
x2: 1, y2: 1,
|
|
stop: 0 #b3e0e6,
|
|
stop: 1 #f3c2c2
|
|
);
|
|
}
|
|
""")
|
|
layout.addWidget(self.chat_area, stretch=1)
|
|
|
|
input_widget = QWidget()
|
|
input_layout = QHBoxLayout(input_widget)
|
|
input_layout.setContentsMargins(10, 10, 10, 10)
|
|
|
|
self.user_input = QLineEdit()
|
|
self.user_input.setPlaceholderText("Type your message...")
|
|
self.send_btn = QPushButton("Send")
|
|
|
|
self.user_input.setStyleSheet("""
|
|
QLineEdit {
|
|
font-size: 16px;
|
|
padding: 8px;
|
|
border: 2px solid #ccc;
|
|
border-radius: 4px;
|
|
border-color: #DB7093;
|
|
}
|
|
QLineEdit:focus {
|
|
border: 2px solid #f3c2c2;
|
|
background-color: #fff0f0;
|
|
}
|
|
""")
|
|
self.send_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #006400;
|
|
color: white;
|
|
font-weight: bold;
|
|
padding: 8px 20px;
|
|
border-radius: 8px;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #a2136a;
|
|
}
|
|
""")
|
|
|
|
input_layout.addWidget(self.user_input)
|
|
input_layout.addWidget(self.send_btn)
|
|
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"
|
|
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", is_user=False)
|
|
|
|
|
|
# Bot selector screen to choose Production or Invoice bots
|
|
class BotSelectorScreen(QWidget):
|
|
def __init__(self, navigate_to_dashboard):
|
|
super().__init__()
|
|
self.navigate_to_dashboard = navigate_to_dashboard
|
|
self._init_ui()
|
|
self.engine = pyttsx3.init()
|
|
QTimer.singleShot(500, self._speak_welcome_message)
|
|
|
|
def _init_ui(self):
|
|
# Gradient background for widget
|
|
gradient = QLinearGradient(0, 0, 0, self.height() or 400)
|
|
gradient.setColorAt(0, QColor("#B0E0E6"))
|
|
gradient.setColorAt(1, QColor("#F3C2C2"))
|
|
palette = QPalette()
|
|
palette.setBrush(QPalette.ColorRole.Window, QBrush(gradient))
|
|
self.setPalette(palette)
|
|
self.setAutoFillBackground(True)
|
|
|
|
layout = QVBoxLayout(self)
|
|
layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
layout.setSpacing(20)
|
|
|
|
logo = QLabel(alignment=Qt.AlignmentFlag.AlignCenter)
|
|
pix = QPixmap("cri_logo.png.png")
|
|
if not pix.isNull():
|
|
pix = pix.scaled(200, 200, Qt.AspectRatioMode.KeepAspectRatio)
|
|
logo.setPixmap(pix)
|
|
else:
|
|
logo.setText("CRI Logo")
|
|
logo.setStyleSheet("font-size: 36px; font-weight: bold; color: #333;")
|
|
layout.addWidget(logo)
|
|
|
|
welcome_label = QLabel("Welcome to CRI PUMPS", alignment=Qt.AlignmentFlag.AlignCenter)
|
|
welcome_label.setStyleSheet("font-size: 36px; font-weight: bold; color: #333;")
|
|
layout.addWidget(welcome_label)
|
|
|
|
menu_label = QLabel("Choose menu to use", alignment=Qt.AlignmentFlag.AlignCenter)
|
|
menu_label.setStyleSheet("font-size: 32px; color: #333;")
|
|
layout.addWidget(menu_label)
|
|
|
|
button_container = QFrame()
|
|
button_layout = QVBoxLayout(button_container)
|
|
button_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
button_layout.setSpacing(40)
|
|
|
|
button_style = """
|
|
QPushButton {
|
|
background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
|
|
stop:0 #88CFF1, stop:1 #F3C2C2);
|
|
border: 2px solid #D36C84;
|
|
border-radius: 15px;
|
|
padding: 25px;
|
|
font-size: 24px;
|
|
font-weight: bold;
|
|
color: #333;
|
|
min-width: 400px;
|
|
min-height: 100px;
|
|
}
|
|
QPushButton:hover {
|
|
background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
|
|
stop:0 #AEDFF7, stop:1 #F5D3D3);
|
|
border: 3px solid #b95470;
|
|
}
|
|
"""
|
|
|
|
prod_btn = QPushButton("Production Dashboard")
|
|
prod_btn.setStyleSheet(button_style)
|
|
prod_btn.clicked.connect(lambda: self.navigate_to_dashboard("Production"))
|
|
button_layout.addWidget(prod_btn, alignment=Qt.AlignmentFlag.AlignCenter)
|
|
|
|
invoice_btn = QPushButton("Invoice Dashboard")
|
|
invoice_btn.setStyleSheet(button_style)
|
|
invoice_btn.clicked.connect(lambda: self.navigate_to_dashboard("Invoice"))
|
|
button_layout.addWidget(invoice_btn, alignment=Qt.AlignmentFlag.AlignCenter)
|
|
|
|
layout.addWidget(button_container, alignment=Qt.AlignmentFlag.AlignCenter)
|
|
layout.addStretch(1)
|
|
|
|
def _speak_welcome_message(self):
|
|
self.engine.say("Welcome to C R I Pumps. Click the button to see the production count.")
|
|
self.engine.runAndWait()
|
|
|
|
|
|
# SelectorScreen (your unchanged code)
|
|
class SelectorScreen(GradientWidget):
|
|
def __init__(self, switch_to_login, switch_to_web, switch_to_bot):
|
|
super().__init__()
|
|
self.switch_to_login = switch_to_login
|
|
self.switch_to_web = switch_to_web
|
|
self.switch_to_bot = switch_to_bot
|
|
self.email = ""
|
|
self.password = ""
|
|
self._init_ui()
|
|
|
|
def _init_ui(self):
|
|
layout = QVBoxLayout(self)
|
|
layout.setContentsMargins(20, 20, 20, 20)
|
|
layout.setSpacing(0)
|
|
|
|
logo_top = QLabel(alignment=Qt.AlignmentFlag.AlignCenter)
|
|
pix = QPixmap("cri_logo.png.png")
|
|
if not pix.isNull():
|
|
pix = pix.scaled(200, 200, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)
|
|
logo_top.setPixmap(pix)
|
|
else:
|
|
logo_top.setText("CRI Logo")
|
|
logo_top.setStyleSheet("font-size: 24px; font-weight: bold; color: #333;")
|
|
layout.addWidget(logo_top)
|
|
|
|
layout.addSpacing(40)
|
|
|
|
title = QLabel("Welcome to CRI PUMPS", alignment=Qt.AlignmentFlag.AlignCenter)
|
|
title.setStyleSheet("""
|
|
font-size: 36px;
|
|
font-weight: bold;
|
|
color: #333;
|
|
text-shadow: 1px 1px 2px rgba(255,255,255,0.8);
|
|
""")
|
|
layout.addWidget(title)
|
|
|
|
layout.addSpacing(30)
|
|
|
|
subtitle = QLabel("PRODUCTION DISPLAY SOFTWARE", alignment=Qt.AlignmentFlag.AlignCenter)
|
|
subtitle.setStyleSheet("""
|
|
font-size: 28px;
|
|
font-weight: bold;
|
|
color: #333;
|
|
font-style: italic;
|
|
text-shadow: 1px 1px 2px rgba(255,255,255,0.8);
|
|
""")
|
|
layout.addWidget(subtitle)
|
|
|
|
layout.addSpacing(30)
|
|
|
|
subtitle2 = QLabel("Choose menu to use", alignment=Qt.AlignmentFlag.AlignCenter)
|
|
subtitle2.setStyleSheet("""
|
|
font-size: 28px;
|
|
color: #333;
|
|
text-shadow: 1px 1px 2px rgba(255,255,255,0.8);
|
|
""")
|
|
layout.addWidget(subtitle2)
|
|
|
|
layout.addSpacing(40)
|
|
|
|
logo_mid = QLabel(alignment=Qt.AlignmentFlag.AlignCenter)
|
|
pix2 = QPixmap("cri_farm_banner.jpg")
|
|
if not pix2.isNull():
|
|
pix2 = pix2.scaled(500, 300, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)
|
|
logo_mid.setPixmap(pix2)
|
|
else:
|
|
logo_mid.setText("CRI Banner")
|
|
logo_mid.setStyleSheet("font-size: 24px; color: #333;")
|
|
layout.addWidget(logo_mid)
|
|
|
|
layout.addStretch(1)
|
|
|
|
bottom = QWidget(self)
|
|
bottom.setStyleSheet("background: transparent;")
|
|
hb = QHBoxLayout(bottom)
|
|
hb.setContentsMargins(0, 20, 0, 20)
|
|
hb.setSpacing(80)
|
|
|
|
button_style = """
|
|
QPushButton {
|
|
background: #F3C2C2;
|
|
border: none;
|
|
font-size: 20px;
|
|
border-radius: 10px;
|
|
padding: 10px;
|
|
min-width: 120px;
|
|
min-height: 60px;
|
|
}
|
|
QPushButton:hover {
|
|
background: #E8B1B1;
|
|
}
|
|
"""
|
|
|
|
btn_logout = QPushButton("Logout")
|
|
btn_logout.setStyleSheet(button_style + "font-size: 24px;")
|
|
btn_logout.clicked.connect(self.switch_to_login)
|
|
hb.addWidget(btn_logout, alignment=Qt.AlignmentFlag.AlignCenter)
|
|
|
|
btn_web = QPushButton()
|
|
pix_web = QPixmap("web.png")
|
|
if not pix_web.isNull():
|
|
pix_web = pix_web.scaled(60, 60, Qt.AspectRatioMode.KeepAspectRatio)
|
|
btn_web.setIcon(QIcon(pix_web))
|
|
btn_web.setIconSize(pix_web.size())
|
|
else:
|
|
btn_web.setText("Web")
|
|
btn_web.setStyleSheet(button_style)
|
|
btn_web.clicked.connect(self.switch_to_web)
|
|
hb.addWidget(btn_web, alignment=Qt.AlignmentFlag.AlignCenter)
|
|
|
|
btn_bot = QPushButton()
|
|
pix_bot = QPixmap("bot.png")
|
|
if not pix_bot.isNull():
|
|
pix_bot = pix_bot.scaled(60, 60, Qt.AspectRatioMode.KeepAspectRatio)
|
|
btn_bot.setIcon(QIcon(pix_bot))
|
|
btn_bot.setIconSize(pix_bot.size())
|
|
else:
|
|
btn_bot.setText("Bot")
|
|
btn_bot.setStyleSheet(button_style)
|
|
btn_bot.clicked.connect(self.switch_to_bot)
|
|
hb.addWidget(btn_bot, alignment=Qt.AlignmentFlag.AlignCenter)
|
|
|
|
layout.addWidget(bottom)
|
|
|
|
|
|
# LoginWindow with full UI & login logic
|
|
class LoginWindow(GradientWidget):
|
|
API_URL = "https://pds.iotsignin.com/api/testing/user/get-data"
|
|
AUTH_TOKEN = "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE="
|
|
|
|
def __init__(self, stack):
|
|
super().__init__()
|
|
self.stack = stack
|
|
self._init_ui()
|
|
|
|
def _init_ui(self):
|
|
layout = QVBoxLayout(self)
|
|
layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
layout.setSpacing(40)
|
|
|
|
logo = QLabel()
|
|
pix = QPixmap("cri_logo.png.png")
|
|
if not pix.isNull():
|
|
pix = pix.scaled(150, 150, Qt.AspectRatioMode.KeepAspectRatio)
|
|
logo.setPixmap(pix)
|
|
else:
|
|
logo.setText("CRI Logo")
|
|
logo.setStyleSheet("font-size: 24px; font-weight: bold; color: #333;")
|
|
layout.addWidget(logo, alignment=Qt.AlignmentFlag.AlignCenter)
|
|
|
|
self.email_input = QLineEdit()
|
|
self.email_input.setPlaceholderText("USER NAME")
|
|
self.email_input.setFixedWidth(400)
|
|
self.email_input.setStyleSheet("""
|
|
background: rgba(255,255,255,0.7);
|
|
border: 2px solid #F3C2C2;
|
|
border-radius: 10px;
|
|
padding: 12px;
|
|
font-size: 20px;
|
|
color: #333;
|
|
""")
|
|
layout.addWidget(self.email_input, alignment=Qt.AlignmentFlag.AlignCenter)
|
|
|
|
self.password_input = QLineEdit()
|
|
self.password_input.setPlaceholderText("PASSWORD")
|
|
self.password_input.setEchoMode(QLineEdit.EchoMode.Password)
|
|
self.password_input.setFixedWidth(400)
|
|
self.password_input.setStyleSheet("""
|
|
background: rgba(255,255,255,0.7);
|
|
border: 2px solid #F3C2C2;
|
|
border-radius: 10px;
|
|
padding: 12px;
|
|
font-size: 20px;
|
|
color: #333;
|
|
""")
|
|
layout.addWidget(self.password_input, alignment=Qt.AlignmentFlag.AlignCenter)
|
|
|
|
eye_action = QAction(QIcon("eye_closed.png"), "", self.password_input)
|
|
eye_action.setCheckable(True)
|
|
self.password_input.addAction(eye_action, QLineEdit.ActionPosition.TrailingPosition)
|
|
|
|
def toggle_password_visibility(checked):
|
|
self.password_input.setEchoMode(
|
|
QLineEdit.EchoMode.Normal if checked else QLineEdit.EchoMode.Password
|
|
)
|
|
eye_action.setIcon(QIcon("eye.png") if checked else QIcon("eye_closed.png"))
|
|
|
|
eye_action.toggled.connect(toggle_password_visibility)
|
|
|
|
self.login_btn = QPushButton("LOGIN")
|
|
self.login_btn.setFixedWidth(300)
|
|
self.login_btn.setStyleSheet("""
|
|
QPushButton {
|
|
font-size: 22px;
|
|
font-weight: bold;
|
|
color: white;
|
|
background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
|
|
stop:0 #C71585, stop:1 #DB7093);
|
|
border: none;
|
|
border-radius: 14px;
|
|
padding: 16px;
|
|
}
|
|
QPushButton:hover {
|
|
background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
|
|
stop:0 #D87093, stop:1 #FF69B4);
|
|
}
|
|
""")
|
|
layout.addWidget(self.login_btn, alignment=Qt.AlignmentFlag.AlignCenter)
|
|
|
|
self.email_input.returnPressed.connect(self.password_input.setFocus)
|
|
self.password_input.returnPressed.connect(self.login_btn.setFocus)
|
|
self.login_btn.clicked.connect(self.perform_login)
|
|
|
|
def perform_login(self):
|
|
email_input = self.email_input.text().strip()
|
|
password = self.password_input.text().strip()
|
|
if not email_input or not password:
|
|
QMessageBox.warning(self, "Error", "Please enter email and password")
|
|
return
|
|
|
|
headers = {
|
|
"Authorization": self.AUTH_TOKEN,
|
|
"User-Name": email_input,
|
|
"User-Pass": password
|
|
}
|
|
|
|
try:
|
|
resp = requests.get(self.API_URL, headers=headers, timeout=10)
|
|
resp.raise_for_status()
|
|
data = resp.json()
|
|
except requests.exceptions.RequestException as e:
|
|
QMessageBox.critical(self, "Network Error", f"Failed to connect:\n{e}")
|
|
return
|
|
except ValueError as e:
|
|
QMessageBox.critical(self, "Response Error", f"Invalid response:\n{e}")
|
|
return
|
|
|
|
if data.get("status_code") == "ERROR":
|
|
desc = data.get("status_description", "Login failed")
|
|
QMessageBox.warning(self, "Login Failed", desc)
|
|
else:
|
|
user_email = data.get("email", email_input)
|
|
QMessageBox.information(self, "Success", "Login successful!")
|
|
selector = self.stack.widget(1)
|
|
selector.email = user_email
|
|
selector.password = password
|
|
self.stack.setCurrentIndex(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app = QApplication(sys.argv)
|
|
stack = QStackedWidget()
|
|
|
|
# Navigation helper - remove extra widgets beyond base screens
|
|
def remove_extras_and_go(index):
|
|
while stack.count() > 2:
|
|
widget = stack.widget(2)
|
|
stack.removeWidget(widget)
|
|
widget.deleteLater()
|
|
stack.setCurrentIndex(index)
|
|
|
|
def show_login():
|
|
remove_extras_and_go(0)
|
|
|
|
def show_selector():
|
|
remove_extras_and_go(1)
|
|
|
|
def show_web():
|
|
selector = stack.widget(1)
|
|
if hasattr(selector, "email") and selector.email:
|
|
remove_extras_and_go(1)
|
|
web_screen = WebAssistantScreen(
|
|
email=selector.email, password=selector.password, go_back=show_selector
|
|
)
|
|
stack.addWidget(web_screen)
|
|
stack.setCurrentIndex(stack.count() - 1)
|
|
|
|
def show_bot_selector():
|
|
remove_extras_and_go(1)
|
|
bot_selector = BotSelectorScreen(navigate_to_dashboard)
|
|
stack.addWidget(bot_selector)
|
|
stack.setCurrentIndex(stack.count() - 1)
|
|
|
|
def navigate_to_dashboard(dashboard_type):
|
|
remove_extras_and_go(1)
|
|
if dashboard_type == "Production":
|
|
screen = ProductionBotScreen(back_action=show_bot_selector)
|
|
elif dashboard_type == "Invoice":
|
|
screen = InvoiceBotScreen(back_action=show_bot_selector)
|
|
else:
|
|
return
|
|
stack.addWidget(screen)
|
|
stack.setCurrentIndex(stack.count() - 1)
|
|
|
|
login_screen = LoginWindow(stack)
|
|
selector_screen = SelectorScreen(show_login, show_web, show_bot_selector)
|
|
|
|
stack.addWidget(login_screen) # Index 0
|
|
stack.addWidget(selector_screen) # Index 1
|
|
|
|
stack.setWindowTitle("Production Display")
|
|
stack.setCurrentIndex(0)
|
|
stack.showMaximized()
|
|
sys.exit(app.exec())
|