import os
import sys
import requests
import base64
import platform
from PyQt6.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QLabel, QLineEdit, QPushButton,
QToolButton, QSizePolicy, QStackedWidget, QSpacerItem, QTextEdit, QScrollArea,
QMessageBox, QHBoxLayout, QTabWidget, QFileDialog, QComboBox, QDialog
)
from PyQt6.QtCore import Qt, QEvent, QTime, pyqtSlot, QTimer, QUrl, QObject, QDateTime
from PyQt6.QtGui import QPalette, QColor, QPixmap, QFont
from PyQt6.QtWebChannel import QWebChannel
from PyQt6.QtWebEngineWidgets import QWebEngineView
# Placeholder new screens to navigate on button click
class JSBridge(QObject):
@pyqtSlot(str, str, str)
def saveFile(self, base64_data, filename, source):
try:
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, "All Files (*)")
if path:
with open(path, 'wb') as f:
f.write(data)
QMessageBox.information(None, f"{source.title()} Download", f"File saved to:\n{path}")
if platform.system() == "Windows":
os.startfile(path)
elif platform.system() == "Darwin":
os.system(f"open '{path}'")
else:
os.system(f"xdg-open '{path}'")
except Exception as e:
QMessageBox.critical(None, "Save Failed", str(e))
class ProductionDisplayScreen(QWidget):
def __init__(self, stacked, url="https://pds.iotsignin.com/admin"):
super().__init__()
self.stacked = stacked
self.url = url
self.setObjectName("ProductionDisplayScreen")
self.init_ui()
def init_ui(self):
self.channel = QWebChannel()
self.js_bridge = JSBridge()
self.channel.registerObject("pyBridge", self.js_bridge)
# Header
header = QWidget()
hl = QHBoxLayout(header)
hl.setContentsMargins(8, 8, 8, 8)
header.setStyleSheet("background-color: #e6f2ff;")
# Logo
logo = QLabel()
pix = QPixmap(
r"C:\Users\Administrator\Downloads\sfg printing\backup from airline device\SRIMATHI\cri_logo.png.png")
logo.setPixmap(
pix.scaled(90, 70, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation))
hl.addWidget(logo)
hl.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
# Title
title = QLabel("CRI DIGITAL MANUFACTURING SOLUTIONS")
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
title.setStyleSheet("font-weight:bold; font-size:28px;")
hl.addWidget(title)
hl.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum))
# Back button
self.btn_back = QPushButton("Back")
self.btn_back.setStyleSheet("""
QPushButton {
background-color: #c2185b;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
}
QPushButton:hover {
background-color: #34495E;
}
QPushButton:pressed {
background-color: #1F2D3D;
}
""")
self.btn_back.clicked.connect(self.go_back)
hl.addWidget(self.btn_back)
# Navigation buttons
btn_style = """
QPushButton {
background-color: #c2185b;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
}
QPushButton:hover {
background-color: #34495E;
}
QPushButton:pressed {
background-color: #1F2D3D;
}
"""
self.btn_prev = QPushButton("Previous")
self.btn_refresh = QPushButton("Refresh")
self.btn_next = QPushButton("Next")
self.btn_full = QPushButton("Full Screen")
self.btn_dup = QPushButton("Duplicate")
self.btn_del = QPushButton("Delete")
self.tab_dropdown = QComboBox()
for b in [self.btn_prev, self.btn_refresh, self.btn_next, self.btn_full, self.btn_dup, self.btn_del]:
b.setStyleSheet(btn_style)
hl.addWidget(b)
hl.addWidget(self.tab_dropdown)
# Tabbed WebView
self.tabs = QTabWidget()
self.tabs.setTabsClosable(False)
self.add_tab(self.url)
self.tabs.setStyleSheet("""
QTabWidget::pane { background-color: #ffe6e6; }
QTabBar::tab {
background: #f8d7da;
padding: 6px;
border-radius: 4px;
}
QTabBar::tab:selected {
background: #e6e6fa;
font-weight: bold;
}
""")
# Connections
self.btn_prev.clicked.connect(lambda: self.notify("Going to previous page...", self.go_prev))
self.btn_refresh.clicked.connect(lambda: self.notify("Refreshing...", self.go_refresh))
self.btn_next.clicked.connect(lambda: self.notify("Going to next page...", self.go_next))
self.btn_full.clicked.connect(self.toggle_fullscreen)
self.btn_dup.clicked.connect(self.duplicate_tab)
self.btn_del.clicked.connect(self.delete_tab)
self.tab_dropdown.currentIndexChanged.connect(self.switch_tab)
# Layout
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(header)
layout.addWidget(self.tabs)
def add_tab(self, url):
webview = QWebEngineView()
webview.page().setWebChannel(self.channel)
index = self.tabs.addTab(webview, "Loading…")
self.tab_dropdown.addItem("Loading…")
self.tabs.setCurrentIndex(index)
self.tab_dropdown.setCurrentIndex(index)
webview.page().titleChanged.connect(lambda title, w=webview: self.update_tab_title(w, title))
webview.page().loadFinished.connect(lambda ok, w=webview: self.inject_js(w) if ok else None)
webview.load(QUrl(url))
def update_tab_title(self, webview, title):
i = self.tabs.indexOf(webview)
if i != -1:
self.tabs.setTabText(i, title or "New Tab")
self.tab_dropdown.setItemText(i, title or "New Tab")
def notify(self, message, callback):
dlg = QDialog(self)
dlg.setWindowFlags(Qt.WindowType.FramelessWindowHint)
dlg.setStyleSheet("""
QDialog { background-color: #34495E; border: 2px solid #2C3E50; border-radius: 10px; }
QLabel { color: white; padding: 15px; font-size: 14px; }
""")
lbl = QLabel(message)
lbl.setAlignment(Qt.AlignmentFlag.AlignCenter)
v = QVBoxLayout(dlg)
v.addWidget(lbl)
dlg.resize(250, 80)
dlg.move(self.geometry().center().x() - 125, self.geometry().center().y() - 40)
dlg.show()
QTimer.singleShot(1000, lambda: (dlg.close(), callback()))
def current_webview(self):
return self.tabs.currentWidget()
def go_prev(self):
view = self.current_webview()
if view and view.history().canGoBack():
view.back()
def go_refresh(self):
view = self.current_webview()
if view: view.reload()
def go_next(self):
view = self.current_webview()
if view and view.history().canGoForward():
view.forward()
def toggle_fullscreen(self):
self.setWindowState(self.windowState() ^ Qt.WindowState.WindowFullScreen)
def duplicate_tab(self):
view = self.current_webview()
if view:
self.add_tab(view.url().toString())
def delete_tab(self):
if self.tabs.count() > 1:
index = self.tabs.currentIndex()
self.tabs.removeTab(index)
self.tab_dropdown.removeItem(index)
def switch_tab(self, index):
self.tabs.setCurrentIndex(index)
def go_back(self):
for i in range(self.stacked.count()):
widget = self.stacked.widget(i)
if isinstance(widget, SelectorScreen):
self.stacked.setCurrentWidget(widget)
return
def inject_js(self, webview):
js_code = """
(function () {
if (window._fileDownloadInjected) return;
window._fileDownloadInjected = true;
let _downloadInProgress = false;
function getFileUrlAndType(el) {
let href = el.getAttribute("href") || el.getAttribute("data-url") || "";
if (!href) {
const inner = el.querySelector("a[href]");
if (inner) href = inner.getAttribute("href") || "";
}
let filename = "download.csv";
if (href) {
const parts = href.split('/');
filename = parts[parts.length - 1].split('?')[0] || filename;
}
const isExport = href.includes("/exports") || el.textContent.toLowerCase().includes("export");
const source = isExport ? "export" : "import";
return { href, filename, source };
}
function startDownload(e, href, filename, source) {
if (_downloadInProgress) return;
_downloadInProgress = true;
e.preventDefault();
fetch(href, { credentials: 'include' })
.then(res => {
if (!res.ok) throw new Error("Failed: " + res.status);
return res.blob();
})
.then(blob => {
const reader = new FileReader();
reader.onloadend = () => {
const base64 = reader.result;
if (window.pyBridge && window.pyBridge.saveFile) {
window.pyBridge.saveFile(base64, filename, source);
} else {
alert("❌ pyBridge not available");
}
_downloadInProgress = false;
};
reader.readAsDataURL(blob);
})
.catch(err => {
alert("❌ Download error: " + err.message);
_downloadInProgress = false;
});
}
document.addEventListener("click", function (e) {
const el = e.target.closest("a,button");
if (!el) return;
const { href, filename, source } = getFileUrlAndType(el);
if (href && (href.endsWith(".csv") || href.endsWith(".xlsx") || href.includes("/exports") || href.startsWith("blob:"))) {
startDownload(e, href, filename, source);
}
}, true);
const origCreateObjectURL = URL.createObjectURL;
URL.createObjectURL = function (blob) {
if (_downloadInProgress) return origCreateObjectURL(blob);
_downloadInProgress = true;
try {
const reader = new FileReader();
reader.onloadend = function () {
const base64 = reader.result;
if (window.pyBridge && window.pyBridge.saveFile) {
window.pyBridge.saveFile(base64, "blob_file.csv", "import");
}
_downloadInProgress = false;
};
reader.readAsDataURL(blob);
} catch (e) {
console.error("Blob hook failed", e);
_downloadInProgress = false;
}
return origCreateObjectURL(blob);
};
function waitForBridge() {
if (typeof QWebChannel === 'undefined') return setTimeout(waitForBridge, 100);
new QWebChannel(qt.webChannelTransport, function (channel) {
window.pyBridge = channel.objects.pyBridge;
});
}
waitForBridge();
})();
"""
webview.page().runJavaScript(f"""
var script = document.createElement('script');
script.src = 'qrc:///qtwebchannel/qwebchannel.js';
script.onload = function() {{
{js_code}
}};
document.head.appendChild(script);
""")
class BotScreen(QWidget):
def __init__(self, stacked, module_name=""):
super().__init__()
self.stacked = stacked
self.module_name = module_name
self.charts = []
self.plants = []
self.lines = []
self.filters = []
self.awaiting_chart_choice = False
self.awaiting_plant_choice = False
self.awaiting_line_choice = False
self.awaiting_order_input = False
self.awaiting_filter_choice = False
self.selected_chart = None
self.selected_plant = None
self.selected_line = None
self.selected_order = None
self.selected_filter = None
self.init_ui()
if self.module_name:
self.load_chart_data()
def set_module_name(self, name):
self.module_name = name
self.clear_chat()
self.load_chart_data()
def init_ui(self):
main_layout = QVBoxLayout()
top_bar = QHBoxLayout()
logo = QLabel()
logo.setPixmap(QPixmap(
r'C:\Users\Administrator\Downloads\sfg printing\backup from airline device\SRIMATHI\cri_logo.png.png'
).scaled(40, 40, Qt.AspectRatioMode.KeepAspectRatio))
top_bar.addWidget(logo)
title = QLabel("CRI DIGITAL MANUFACTURING SOLUTIONS \n CRI BOT ASSISTANT")
title.setStyleSheet("font-size: 16px; font-weight: bold; color: black; padding: 10px; border-radius: 8px;")
top_bar.addWidget(title)
top_bar.addStretch()
back_btn = QPushButton("Back")
back_btn.clicked.connect(self.go_back)
top_bar.addWidget(back_btn)
refresh_btn = QPushButton("Refresh")
refresh_btn.clicked.connect(self.load_chart_data)
top_bar.addWidget(refresh_btn)
main_layout.addLayout(top_bar)
self.chart_info = QLabel("Waiting for chart data...")
self.chart_info.setWordWrap(True)
self.chart_info.setStyleSheet(
"background: white; color: black; font-size: 15px; padding: 14px; border-radius: 10px")
main_layout.addWidget(self.chart_info)
self.chat_area = QTextEdit()
self.chat_area.setReadOnly(True)
self.chat_area.setStyleSheet("background-color: white; color: black; padding: 10px; font-size: 14px;")
main_layout.addWidget(self.chat_area)
input_layout = QHBoxLayout()
self.user_input = QLineEdit()
self.user_input.setPlaceholderText("Type a number to select...")
input_layout.addWidget(self.user_input)
send_btn = QPushButton("Send")
send_btn.setStyleSheet(
"padding: 8px; background: #2e7d32; color: white; border-radius: 8px;")
send_btn.clicked.connect(self.send_message)
input_layout.addWidget(send_btn)
main_layout.addLayout(input_layout)
self.setLayout(main_layout)
def load_chart_data(self):
self.chart_info.setText("Loading chart data...")
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": self.module_name
}
try:
response = requests.get(url, headers=headers, timeout=10)
data = response.json()
status = data.get("status_code", "UNKNOWN")
desc = data.get("status_description", [])
self.chart_info.setText(f"API Status: {status}")
self.chat_area.append(f"HTTP: {response.status_code}")
self.chat_area.append(f"Bot Module: {self.module_name}")
if isinstance(desc, list) and desc:
self.charts = desc
else:
self.charts = [
"Production Line Count",
"Production Hourly Count",
"Production Line Stop Count",
"Production Order Count"
]
self.awaiting_chart_choice = True
self.show_choice_menu("chart", self.charts)
except Exception as e:
self.chart_info.setText("Failed to load chart data.")
self.chat_area.append(f"Bot: Error: {e}")
def show_choice_menu(self, item, items_list):
label_map = {
"chart": "chart",
"plant": "plant",
"line": "line",
"order": "production order",
"filter": "date filter"
}
lines = [f"{i + 1}. {name}" for i, name in enumerate(items_list)]
menu_txt = f"Bot: Please select a {label_map[item]}:
" + "
".join(lines)
self.chat_area.append(menu_txt)
def send_message(self):
msg = self.user_input.text().strip()
self.user_input.clear()
if self.awaiting_chart_choice:
if msg.isdigit():
idx = int(msg) - 1
if 0 <= idx < len(self.charts):
self.selected_chart = self.charts[idx]
self.awaiting_chart_choice = False
self.chat_area.append(f"Bot: ✅ You selected: {self.selected_chart}")
self.fetch_and_display_plants()
else:
self.chat_area.append(f"Bot: ❌ Please select a number from 1 to {len(self.charts)}.")
else:
self.chat_area.append(f"Bot: ❌ Please enter a number (1-{len(self.charts)}).")
return
if self.awaiting_plant_choice:
if msg.isdigit():
idx = int(msg) - 1
if 0 <= idx < len(self.plants):
self.selected_plant = self.plants[idx]
self.awaiting_plant_choice = False
self.chat_area.append(f"Bot: ✅ You selected: {self.selected_plant}")
self.fetch_and_display_lines()
else:
self.chat_area.append(f"Bot: ❌ Please select a number from 1 to {len(self.plants)}.")
else:
self.chat_area.append(f"Bot: ❌ Please enter a number (1-{len(self.plants)}).")
return
if self.awaiting_line_choice:
if msg.isdigit():
idx = int(msg) - 1
if 0 <= idx < len(self.lines):
self.selected_line = self.lines[idx]
self.awaiting_line_choice = False
self.chat_area.append(f"Bot: ✅ You selected: {self.selected_line}")
chart_raw = self.selected_chart
chart_normalized = chart_raw.strip().lower() if chart_raw else ""
if chart_normalized == "production order count":
print(f"[DEBUG] self.selected_chart (raw): '{self.selected_chart}'")
self.awaiting_order_input = True
self.chat_area.append("Bot: Please enter the production order number:")
else:
self.fetch_and_display_filters()
else:
self.chat_area.append(f"Bot: ❌ Please select a number from 1 to {len(self.lines)}.")
else:
self.chat_area.append(f"Bot: ❌ Please enter a number (1-{len(self.lines)}).")
return
if hasattr(self, 'awaiting_order_input') and self.awaiting_order_input:
order = msg.strip()
if not order.isdigit():
self.chat_area.append("Bot: ❌ Please enter a valid production order number (numbers only).")
else:
self.selected_order = order
self.awaiting_order_input = False
self.chat_area.append(f"Bot: ✅ Production order set: {self.selected_order}")
self.fetch_and_display_filters()
return
if self.awaiting_filter_choice:
if msg.isdigit():
idx = int(msg) - 1
if 0 <= idx < len(self.filters):
self.selected_filter = self.filters[idx]
self.awaiting_filter_choice = False
self.chat_area.append(f"Bot: ✅ You selected filter: {self.selected_filter}")
now = QDateTime.currentDateTime()
fmt_date = now.toString("dddd, MMMM d, yyyy, hh:mm AP")
self.chat_area.append(f"Current date: {fmt_date}")
selected = self.selected_chart.strip().lower()
if selected == "production hourly count":
self.fetch_and_display_hourly_count()
elif selected == "production line stop count":
self.fetch_and_display_production_line_stop_count()
elif selected == "production order count":
self.fetch_and_display_production_order_count()
else:
self.fetch_and_display_production_count()
else:
self.chat_area.append(f"Bot: ❌ Please select a number from 1 to {len(self.filters)}.")
else:
self.chat_area.append(f"Bot: ❌ Please enter a valid number (1–{len(self.filters)}).")
return
def fetch_and_display_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)
data = response.json()
if data.get("status_code") == "SUCCESS":
plants = data.get("status_description", [])
if plants:
self.plants = plants
self.awaiting_plant_choice = True
self.show_choice_menu("plant", plants)
else:
self.chat_area.append("Bot: ❌ No plants found.")
else:
self.chat_area.append(f"Bot: ❌ Error: {data.get('status_description')}")
except Exception as e:
self.chat_area.append(f"Bot: ❌ Failed to load plants: {e}")
def fetch_and_display_lines(self):
url = 'https://pds.iotsignin.com/api/get/module-plantline-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": self.selected_plant,
"chart-name": self.selected_chart
}
try:
response = requests.get(url, headers=headers, timeout=10)
data = response.json()
if data.get("status_code") == "SUCCESS":
lines = data.get("status_description", [])
if lines:
self.lines = lines
self.awaiting_line_choice = True
self.show_choice_menu("line", lines)
else:
self.chat_area.append("Bot: ❌ No lines found for the selected plant.")
else:
self.chat_area.append(f"Bot: ❌ Error: {data.get('status_description')}")
except Exception as e:
self.chat_area.append(f"Bot: ❌ Failed to load lines: {e}")
def fetch_and_display_filters(self):
url = 'https://pds.iotsignin.com/api/get/module-line-filter-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=",
"line-name": self.selected_line
}
try:
response = requests.get(url, headers=headers, timeout=10)
data = response.json()
if data.get("status_code") == "SUCCESS":
filters = data.get("status_description", [])
if filters:
self.filters = filters
self.awaiting_filter_choice = True
self.show_choice_menu("filter", filters)
else:
self.chat_area.append("Bot: ❌ No date filters found for the selected line.")
else:
self.chat_area.append(f"Bot: ❌ Error: {data.get('status_description')}")
except Exception as e:
self.chat_area.append(f"Bot: ❌ Failed to load date filters: {e}")
def fetch_and_display_production_count(self):
url = 'https://pds.iotsignin.com/api/get/module-filter-value/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": self.module_name,
"chart-name": self.selected_chart,
"plant-name": self.selected_plant,
"line-name": self.selected_line,
"filter-name": self.selected_filter
}
try:
response = requests.get(url, headers=headers, timeout=10)
data = response.json()
if data.get("status_code") == "SUCCESS":
result = data.get("status_description")
if isinstance(result, dict):
self.chat_area.append(f"Bot: 📊 Production Counts (All Lines):")
for ln, count in result.items():
self.chat_area.append(f"{ln}: {count}")
else:
self.chat_area.append(f"Bot: 📈 Production Count: {result}")
else:
self.chat_area.append(
f"Bot: ❌ Error getting production count: {data.get('status_description', 'Unknown')}")
except Exception as e:
self.chat_area.append(f"Bot: ❌ Failed to load production count: {e}")
API_TOKEN = "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE="
def fetch_and_display_hourly_count(self):
url = "https://pds.iotsignin.com/api/get/module-filter-value/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=",
"Accept": "application/json",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
"module-name": "Production Dashboard",
"chart-name": "Production Hourly Count",
"plant-name": self.selected_plant,
"line-name": self.selected_line,
"filter-name": self.selected_filter
}
try:
response = requests.get(url, headers=headers, timeout=10)
print("📡 [DEBUG] HTTP Status:", response.status_code)
print("📡 [DEBUG] Raw API Response:\n", response.text)
print("-" * 50)
self.chat_area.append(f"Raw API response:
{response.text}")
if not response.text.strip():
print("[DEBUG] ❌ Empty response received.")
self.chat_area.append("Bot: ❌ API returned no data.")
return
try:
data = response.json()
except Exception as json_err:
print(f"[DEBUG] ❌ JSON Parse Error: {json_err}")
self.chat_area.append(f"Bot: ❌ Failed to parse data: {json_err}")
return
if data.get("status_code") == "SUCCESS":
now = QDateTime.currentDateTime().toString("dddd, MMMM d, yyyy, h:mm AP t")
self.chat_area.append(
f"📊 Production Hourly Count ({self.selected_filter}){req_status}")
except requests.exceptions.HTTPError as errh:
print("[DEBUG] ❌ HTTP Error:", errh)
self.chat_area.append(f"Bot: ❌ HTTP Error: {errh}")
except requests.exceptions.ConnectionError:
print("[DEBUG] ❌ Connection Error.")
self.chat_area.append("Bot: ❌ Connection error.")
except requests.exceptions.Timeout:
print("[DEBUG] ❌ Timeout Error.")
self.chat_area.append("Bot: ❌ Request timed out.")
except Exception as e:
print(f"[DEBUG] ❌ General Error: {e}")
self.chat_area.append(f"Bot: ❌ Unexpected Error: {e}")
def fetch_and_display_production_line_stop_count(self):
url = "https://pds.iotsignin.com/api/get/module-production-linestop/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",
"chart-name": "Production Line Stop",
"plant-name": self.selected_plant,
"line-name": self.selected_line,
"filter-name": self.selected_filter
}
try:
response = requests.get(url, headers=headers, timeout=10)
self.chat_area.append(f"Raw API response:{response.text}")
if response.status_code != 200:
self.chat_area.append(f"Bot: ❌ HTTP error {response.status_code}")
return
if not response.text.strip():
self.chat_area.append(f"Bot: ❌ No response from API.")
return
try:
data = response.json()
except Exception as e:
self.chat_area.append(f"Bot: ❌ Failed to parse API response: {e}")
return
current_date = QDateTime.currentDateTime().toString("dddd, MMMM d, yyyy, h:mm AP t")
if data.get("status_code") == "SUCCESS":
labels = data.get("labels", [])
values = data.get("data", [])
message = f"Production Line Stop Counts{response.text}")
if not response.text.strip():
print("[DEBUG] ❌ Empty API response received.")
self.chat_area.append("Bot: ❌ API returned no data.")
return
try:
data = response.json()
except Exception as json_err:
print(f"[DEBUG] ❌ JSON Parse Error: {json_err}")
self.chat_area.append(f"Bot: ❌ Failed to parse data: {json_err}")
return
status = data.get("status_code")
if status == "SUCCESS":
now = QDateTime.currentDateTime().toString("dddd, MMMM d, yyyy, h:mm AP t")
self.chat_area.append(
f"Production Order Count ({self.selected_filter}){status}")
except requests.exceptions.HTTPError as errh:
print("[DEBUG] ❌ HTTP Error:", errh)
self.chat_area.append(f"Bot: ❌ HTTP Error: {errh}")
except requests.exceptions.ConnectionError:
print("[DEBUG] ❌ Connection Error.")
self.chat_area.append("Bot: ❌ Connection error.")
except requests.exceptions.Timeout:
print("[DEBUG] ❌ Timeout Error.")
self.chat_area.append("Bot: ❌ Request timed out.")
except Exception as e:
print(f"[DEBUG] ❌ General Error: {e}")
self.chat_area.append(f"Bot: ❌ Unexpected Error: {e}")
def clear_chat(self):
self.chat_area.clear()
def go_back(self):
for i in range(self.stacked.count()):
widget = self.stacked.widget(i)
if isinstance(widget, SelectorScreen):
self.stacked.setCurrentWidget(widget)
return
class SelectorScreen(QWidget):
def __init__(self, stacked, username=None):
super().__init__()
self.stacked = stacked
self.username = username or "User"
self.init_ui()
def init_ui(self):
# Set light blue background
palette = self.palette()
palette.setColor(QPalette.ColorRole.Window, QColor("#ADD8E6"))
self.setPalette(palette)
self.setAutoFillBackground(True)
# Set Times New Roman font for entire screen
self.setFont(QFont("Times New Roman"))
self.layout = QVBoxLayout()
self.layout.setContentsMargins(40, 30, 40, 30)
self.layout.setSpacing(15)
self.layout.setAlignment(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignHCenter)
# CRI red logo at top
self.cri_logo = QLabel()
cri_logo_path = r'C:\Users\Administrator\Downloads\sfg printing\backup from airline device\SRIMATHI\cri_red_logo.png'
self.cri_logo.setPixmap(QPixmap(cri_logo_path).scaledToWidth(150, Qt.TransformationMode.SmoothTransformation))
self.cri_logo.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.layout.addWidget(self.cri_logo)
# Title
title_label = QLabel("SELECTOR SCREEN")
title_label.setFont(QFont("Times New Roman", 18, QFont.Weight.Bold))
title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.layout.addWidget(title_label)
# Welcome username label
welcome_label = QLabel(f"Welcome, {self.username}!")
welcome_label.setFont(QFont("Times New Roman", 14))
welcome_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.layout.addWidget(welcome_label)
# Instruction label
instruction_label = QLabel("Choose menu to use")
instruction_label.setFont(QFont("Times New Roman", 12))
instruction_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.layout.addWidget(instruction_label)
# Production Display Section
prod_display_title = QLabel("PRODUCTION DISPLAY WEBSITE")
prod_display_title.setFont(QFont("Times New Roman", 14, QFont.Weight.Bold))
prod_display_title.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.layout.addWidget(prod_display_title)
self.production_display_btn = QPushButton("Production Display Website")
self.production_display_btn.setFont(QFont("Times New Roman", 12))
self.production_display_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
self.production_display_btn.setStyleSheet(
"padding: 10px; background-color: #c2185b; color: white; border-radius: 8px;")
self.production_display_btn.clicked.connect(self.open_production_display)
self.layout.addWidget(self.production_display_btn)
# Dashboards Section
dashboards_title = QLabel("DASHBOARDS")
dashboards_title.setFont(QFont("Times New Roman", 14, QFont.Weight.Bold))
dashboards_title.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.layout.addWidget(dashboards_title)
self.production_dashboard_btn = QPushButton("Production Dashboard")
self.production_dashboard_btn.setFont(QFont("Times New Roman", 12))
self.production_dashboard_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
self.production_dashboard_btn.setStyleSheet(
"padding: 10px; background-color: #c2185b; color: white; border-radius: 8px;")
self.production_dashboard_btn.clicked.connect(lambda: self.open_dashboard("Production Dashboard"))
self.layout.addWidget(self.production_dashboard_btn)
self.invoice_dashboard_btn = QPushButton("Invoice Dashboard")
self.invoice_dashboard_btn.setFont(QFont("Times New Roman", 12))
self.invoice_dashboard_btn.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
self.invoice_dashboard_btn.setStyleSheet(
"padding: 10px; background-color: #c2185b; color: white; border-radius: 8px;")
self.invoice_dashboard_btn.clicked.connect(lambda: self.open_dashboard("Invoice Dashboard"))
self.layout.addWidget(self.invoice_dashboard_btn)
# Spacer to push the logo down
self.layout.addSpacerItem(QSpacerItem(20, 20, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding))
# CRI banner logo at bottom
self.cri_banner_logo = QLabel()
banner_logo_path = r'C:\Users\Administrator\Downloads\sfg printing\backup from airline device\SRIMATHI\cri_farm_banner.jpg'
self.cri_banner_logo.setPixmap(
QPixmap(banner_logo_path).scaledToWidth(350, Qt.TransformationMode.SmoothTransformation))
self.cri_banner_logo.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.layout.addWidget(self.cri_banner_logo)
self.setLayout(self.layout)
def open_production_display(self):
# Check if screen already exists
for i in range(self.stacked.count()):
widget = self.stacked.widget(i)
if isinstance(widget, ProductionDisplayScreen):
self.stacked.setCurrentWidget(widget)
return
# Otherwise, add new screen
display_screen = ProductionDisplayScreen(self.stacked)
self.stacked.addWidget(display_screen)
self.stacked.setCurrentWidget(display_screen)
def open_dashboard(self, dashboard_type):
# Check if screen already exists
for i in range(self.stacked.count()):
widget = self.stacked.widget(i)
if isinstance(widget, BotScreen) and widget.module_name == dashboard_type:
self.stacked.setCurrentWidget(widget)
return
# Otherwise, create new screen
bot_screen = BotScreen(self.stacked, dashboard_type)
self.stacked.addWidget(bot_screen)
self.stacked.setCurrentWidget(bot_screen)
def show_error(self, msg):
error = QLabel(f"❌ {msg}")
error.setStyleSheet("color: red;")
self.layout.addWidget(error)
class LoginScreen(QWidget):
def __init__(self, stacked):
super().__init__()
self.stacked = stacked
self.init_ui()
def init_ui(self):
self.showMaximized()
# Light blue background
palette = self.palette()
palette.setColor(QPalette.ColorRole.Window, QColor("#ADD8E6"))
self.setPalette(palette)
self.setAutoFillBackground(True)
layout = QVBoxLayout()
layout.setContentsMargins(40, 30, 40, 30)
layout.setSpacing(20)
layout.setAlignment(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignHCenter)
# Logo
logo = QLabel()
logo.setPixmap(QPixmap(
r'C:\Users\Administrator\Downloads\sfg printing\backup from airline device\SRIMATHI\cri_red_logo.png'
).scaled(150, 150, Qt.AspectRatioMode.KeepAspectRatio))
logo.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(logo)
# Title
title_label = QLabel("CRI DIGITAL MANUFACTURING \n SOLUTIONS")
title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
title_label.setStyleSheet("""
font-size: 18px;
font-weight: bold;
color: #002244;
margin-bottom: 10px;
""")
layout.addWidget(title_label)
# Username QLineEdit
self.username = QLineEdit()
self.username.setPlaceholderText("Username (max 25 chars)")
self.username.setMaxLength(25)
self.username.setStyleSheet(self.input_style())
layout.addWidget(self.username)
# Password QLineEdit
self.password = QLineEdit()
self.password.setPlaceholderText("Password (max 25 chars)")
self.password.setMaxLength(25)
self.password.setEchoMode(QLineEdit.EchoMode.Password)
self.password.setStyleSheet(self.input_style())
# Create eye toggle button with emoji inside password field
self.eye_button = QToolButton(self.password)
self.eye_button.setText("🙈") # Initially hidden password
self.eye_button.setCursor(Qt.CursorShape.PointingHandCursor)
self.eye_button.setStyleSheet("border: none; background: transparent; font-size: 18px;")
self.eye_button.setToolTip("Show/Hide password")
self.eye_button.clicked.connect(self.toggle_password_visibility)
# Adjust size and margins in QLineEdit for the eye button
frame_width = self.password.style().pixelMetric(self.password.style().PixelMetric.PM_DefaultFrameWidth)
button_size = 24
self.eye_button.setFixedSize(button_size, button_size)
self.password.setTextMargins(0, 0, button_size + frame_width, 0)
# Position the eye button correctly
self.position_eye_button()
# Reposition on resize or text change
self.password.textChanged.connect(self.position_eye_button)
self.password.installEventFilter(self)
layout.addWidget(self.password)
# Connect Enter key navigation
self.username.returnPressed.connect(self.password.setFocus)
self.password.returnPressed.connect(self.do_login)
# Login button smaller style
login_btn = QPushButton("Login")
login_btn.setStyleSheet("""
QPushButton {
background-color: #E91E63;
color: white;
font-weight: bold;
padding: 6px 15px;
border-radius: 12px;
font-size: 14px;
max-width: 120px;
}
QPushButton:hover {
background-color: #C2185B;
}
QPushButton:pressed {
background-color: #AD1457;
}
""")
login_btn.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
login_btn.clicked.connect(self.do_login)
layout.addWidget(login_btn, alignment=Qt.AlignmentFlag.AlignCenter)
# Secondary image/banner
logo2 = QLabel()
logo2.setPixmap(QPixmap(
r'C:\Users\Administrator\Downloads\sfg printing\backup from airline device\SRIMATHI\cri_farm_banner.jpg'
).scaled(350, 350, Qt.AspectRatioMode.KeepAspectRatio))
logo2.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(logo2)
self.setLayout(layout)
def position_eye_button(self):
frame_width = self.password.style().pixelMetric(self.password.style().PixelMetric.PM_DefaultFrameWidth)
button_size = self.eye_button.width()
self.eye_button.move(
self.password.rect().right() - button_size - frame_width,
(self.password.rect().bottom() - button_size + 1) // 2
)
def eventFilter(self, obj, event):
if obj == self.password and event.type() == QEvent.Type.Resize:
self.position_eye_button()
return super().eventFilter(obj, event)
def toggle_password_visibility(self):
if self.password.echoMode() == QLineEdit.EchoMode.Password:
self.password.setEchoMode(QLineEdit.EchoMode.Normal)
self.eye_button.setText("👁️")
else:
self.password.setEchoMode(QLineEdit.EchoMode.Password)
self.eye_button.setText("🙈")
def input_style(self):
return """
QLineEdit {
background-color: white;
border: 2px solid #ccc;
border-radius: 15px;
padding: 8px 12px;
color: black;
font-size: 14px;
min-width: 300px;
}
QLineEdit:focus {
border: 2px solid purple;
}
"""
def do_login(self):
username = self.username.text().strip()
password = self.password.text().strip()
if not username or not password:
QMessageBox.critical(self, "Input Error", "Please enter both username and password.")
return
url = "https://pds.iotsignin.com/api/testing/user/get-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=",
"User-Name": username,
"User-Pass": password
}
try:
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
data = response.json()
if "roles" in data:
roles = data["roles"]
roles_str = ', '.join(roles)
info_msg = f"Login successful!"
result = QMessageBox.information(self, "Success", info_msg, QMessageBox.StandardButton.Ok)
if result == QMessageBox.StandardButton.Ok:
selector_screen = SelectorScreen(self.stacked, username)
self.stacked.addWidget(selector_screen)
self.stacked.setCurrentWidget(selector_screen)
elif data.get("status_code") == "ERROR":
description = data.get("status_description", "Unknown error from server.")
QMessageBox.critical(self, "Login Error", description)
else:
QMessageBox.critical(self, "Login Error", "Unexpected response from server.")
except requests.exceptions.RequestException as e:
QMessageBox.critical(self, "Network Error", f"Failed to connect to server:\n{str(e)}")
def main():
app = QApplication(sys.argv)
stacked = QStackedWidget()
login_screen = LoginScreen(stacked)
stacked.addWidget(login_screen)
stacked.setFixedSize(1200, 800)
stacked.show()
sys.exit(app.exec())
if __name__ == '__main__':
main()