Files
poa-pds/pds.py
2025-08-06 13:42:30 +05:30

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())