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})
Current date: {now}

") for dataset in data.get("datasets", []): label = dataset.get("label", "Data") rows = "" for lbl, val in zip(data.get("labels", []), dataset.get("data", [])): rows += f"{lbl} ➤ {val}
" self.chat_area.append(f"{label}:
{rows}") print("[DEBUG] ✅ Hourly count displayed successfully.") else: req_status = data.get("status_code") print(f"[DEBUG] ❌ API returned status {req_status}") self.chat_area.append(f"Bot: ❌ API returned status {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
Current date: {current_date}

" for label, value in zip(labels, values): message += f"{label}: {value}
" self.chat_area.append(message) else: self.chat_area.append(f"Bot: ❌ No data found for this selection.") except Exception as e: self.chat_area.append(f"Bot: ❌ Error fetching report: {e}") def fetch_and_display_production_order_count(self): url = "https://pds.iotsignin.com/api/get/module-production-order/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 Order Count", "plant-name": self.selected_plant, "line-name": self.selected_line, "production-order": self.selected_order, "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 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})
Current date: {now}

") datasets = data.get("datasets", []) for dataset in datasets: label = dataset.get("label", "Data") self.chat_area.append(f"{label}:") if isinstance(dataset.get("data"), dict): for timeslot, count in dataset.get("data", {}).items(): self.chat_area.append(f"{timeslot}: {count}
") elif isinstance(dataset.get("data"), list): labels = data.get("labels", []) for idx, value in enumerate(dataset.get("data")): label_text = labels[idx] if idx < len(labels) else f"Data {idx + 1}" self.chat_area.append(f"{label_text}: {value}
") else: self.chat_area.append(f"Data: {dataset.get('data')}") print("[DEBUG] ✅ Production order count handled/displayed.") else: print(f"[DEBUG] ❌ API status_code not SUCCESS: {status}") self.chat_area.append(f"Bot: ❌ API returned status {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()