From d0914d2c0eb717e35e96bb617361eda3557c9d43 Mon Sep 17 00:00:00 2001 From: jothi Date: Tue, 9 Sep 2025 17:28:23 +0530 Subject: [PATCH] load lines commented --- Production Display.py | 1182 --------------------------------------- bg_user_message.xml | 4 - bot.png | Bin 3338 -> 0 bytes cri_farm_banner.jpg | Bin 133744 -> 0 bytes download | 3 - eye.png | Bin 2115 -> 0 bytes login.qss.py | 68 --- pds.py | 1033 ---------------------------------- rppds.py | 484 ++++++++++++++++ sample.py | 0 sfgprintingnew.py | 1217 +++++++++++++++++++++++++++++++++++++++++ styles.py | 15 - test.py | 915 ------------------------------- web.png | Bin 5291 -> 0 bytes 14 files changed, 1701 insertions(+), 3220 deletions(-) delete mode 100644 Production Display.py delete mode 100644 bg_user_message.xml delete mode 100644 bot.png delete mode 100644 cri_farm_banner.jpg delete mode 100644 download delete mode 100644 eye.png delete mode 100644 login.qss.py delete mode 100644 pds.py create mode 100644 rppds.py delete mode 100644 sample.py create mode 100644 sfgprintingnew.py delete mode 100644 styles.py delete mode 100644 test.py delete mode 100644 web.png diff --git a/Production Display.py b/Production Display.py deleted file mode 100644 index 8879be5..0000000 --- a/Production Display.py +++ /dev/null @@ -1,1182 +0,0 @@ -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() \ No newline at end of file diff --git a/bg_user_message.xml b/bg_user_message.xml deleted file mode 100644 index b3a9cb3..0000000 --- a/bg_user_message.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/bot.png b/bot.png deleted file mode 100644 index 8c465739c9e03409a7d9101e485fb8bc55972c66..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3338 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

-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 Z>&*VyhlocZR}BBno87Q9CK= 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 -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) - - layout.setAlignment(Qt.AlignmentFlag.AlignRight if self.is_user else 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) - 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) - # No voice filtering - use system default voice - - def tts_worker(self): - while True: - text = self.tts_queue.get() - if text is None: - break - try: - self.tts_engine.say(text) - self.tts_engine.runAndWait() - except Exception: - pass - self.tts_queue.task_done() - - def speak(self, text): - self.tts_queue.put(text) - - def closeEvent(self, event): - self.tts_queue.put(None) - self.tts_thread.join(timeout=2) - super().closeEvent(event) - - def _setup_ui(self): - layout = QVBoxLayout(self) - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(0) - - header = QWidget() - header.setFixedHeight(70) - header.setStyleSheet(""" - background: qlineargradient(x1:0,y1:0,x2:1,y2:0, - stop:0 #B0E0E6, stop:1 #F3C2C2); - color: #333; - font-size: 22px; - """) - hlayout = QHBoxLayout(header) - hlayout.setContentsMargins(10, 5, 10, 5) - - logo = QLabel() - pix = QPixmap("cri_logo.png") - if pix and not pix.isNull(): - logo.setPixmap(pix.scaled(60, 60, Qt.AspectRatioMode.KeepAspectRatio)) - hlayout.addWidget(logo) - - title = QLabel("PRODUCTION BOT") - title.setStyleSheet("font-size: 24px; font-weight: bold; color: #333;") - hlayout.addWidget(title) - - hlayout.addStretch(1) - - self.back_btn = QPushButton("Back") - self.back_btn.setStyleSheet(""" - QPushButton { - background: #C71585; - color: white; - border-radius: 8px; - padding: 8px 14px; - font-weight: bold; - } - QPushButton:hover { - background: #b95975; - } - """) - if self.back_action: - self.back_btn.clicked.connect(self.back_action) - hlayout.addWidget(self.back_btn) - - self.refresh_btn = QPushButton("Refresh") - self.refresh_btn.setStyleSheet(""" - QPushButton { - background: #C71585; - color: white; - border-radius: 8px; - padding: 8px 14px; - font-weight: bold; - } - QPushButton:hover { - background: #b95975; - } - """) - hlayout.addWidget(self.refresh_btn) - - layout.addWidget(header) - - self.chat_area = ChatScrollArea() - self.chat_area.setStyleSheet(""" - QScrollArea { - background: qlineargradient(x1:0,y1:0,x2:1,y2:1, - stop:0 #b0e6e6, stop:1 #f3c2c2); - } - """) - layout.addWidget(self.chat_area, stretch=1) - - input_area = QWidget() - input_layout = QHBoxLayout(input_area) - input_layout.setContentsMargins(10, 10, 10, 10) - - self.user_input = QLineEdit() - self.user_input.setPlaceholderText("Type your message...") - self.user_input.setStyleSheet(""" - QLineEdit { - font-size: 16px; - padding: 8px; - border: 2px solid #db7093; - border-radius: 4px; - } - QLineEdit:focus { - border-color: #f3c2c2; - background: #fff0f0; - } - """) - self.send_btn = QPushButton("Send") - self.send_btn.setStyleSheet(""" - QPushButton { - background: #006400; - color: white; - font-weight: bold; - padding: 8px 20px; - border-radius: 8px; - } - QPushButton:hover { - background: #a2136a; - } - """) - input_layout.addWidget(self.user_input) - input_layout.addWidget(self.send_btn) - - layout.addWidget(input_area) - - self.send_btn.clicked.connect(self._on_send) - - def _show_greeting(self): - hour = QTime.currentTime().hour() - greeting = "Good night" - if 5 <= hour < 12: - greeting = "Good morning" - elif 12 <= hour < 17: - greeting = "Good afternoon" - elif 17 <= hour < 21: - greeting = "Good evening" - self.chat_area.add_bubble(f"{greeting}, User", False) - self.speak(f"{greeting}, User") - - def _on_send(self): - msg = self.user_input.text().strip() - if not msg: - return - self.chat_area.add_bubble(msg, True) - self.user_input.clear() - - if msg.isdigit(): - idx = int(msg) - if self.awaiting_plants: - plant = self.plant_map.get(idx) - if plant: - self.plant_selected(plant) - else: - self.chat_area.add_bubble("Invalid plant number. Please try again.", False) - self.speak("Invalid plant number. Please try again.") - else: - chart = self.chart_map.get(idx) - if chart: - self.chart_selected(chart) - else: - self.chat_area.add_bubble("Invalid chart number. Please try again.", False) - self.speak("Invalid chart number. Please try again.") - else: - self.chat_area.add_bubble("Please enter a valid number.", False) - self.speak("Please enter a valid number.") - - def load_charts(self): - url = "https://pds.iotsignin.com/api/get/modulechart-name/data" - headers = { - "Authorization": "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE=", - "module-name": "Production DashBoard" - } - try: - response = requests.get(url, headers=headers, timeout=10) - response.raise_for_status() - data = response.json() - if data.get("status_code") == "SUCCESS": - charts = data.get("status_description", []) - self.chart_map = {i + 1: c for i, c in enumerate(charts)} - numbered = "\n".join(f"{num}. {name}" for num, name in self.chart_map.items()) - self.chat_area.add_bubble("Please select a chart:\n\n" + numbered, False) - self.speak("Please select a chart.") - else: - self.chat_area.add_bubble("Failed to load charts.", False) - self.speak("Failed to load charts.") - except Exception as e: - self.chat_area.add_bubble(f"Error loading charts: {str(e)}", False) - self.speak("Error loading charts.") - - def chart_selected(self, chart_name): - self.chat_area.add_bubble(f"You selected: {chart_name}", False) - self.speak(f"You selected {chart_name}") - self.awaiting_plants = True - QTimer.singleShot(500, self.load_plants) - - # def load_plants(self): - # url = "https://pds.iotsignin.com/api/get/moduleplant-name/data" - # headers = { - # "Authorization": "Bearer sb-eba140ab-74bb-44a4-8d92-70a636940def!b1182|it-rt-dev-cri-stjllphr!b68:616d8991-307b-4ab1-be37-7894a8c6db9d$0p0fE2I7w1Ve23-lVSKQF0ka3mKrTVcKPJYELr-i4nE=", - # "plant-name": "Plant List" - # } - try: - response = requests.get(url, headers=headers, timeout=10) - response.raise_for_status() - data = response.json() - if data.get("status_code") == "SUCCESS": - plants = data.get("status_description", []) - self.plant_map = {i + 1: p for i, p in enumerate(plants)} - numbered = "\n".join(f"{num}. {name}" for num, name in self.plant_map.items()) - self.chat_area.add_bubble("Please select a plant:\n\n" + numbered, False) - self.speak("Please select a plant.") - else: - self.chat_area.add_bubble("Failed to load plants.", False) - self.speak("Failed to load plants.") - except Exception as e: - self.chat_area.add_bubble(f"Error loading plants: {str(e)}", False) - self.speak("Error loading plants.") - - def plant_selected(self, plant_name): - self.chat_area.add_bubble(f"You selected plant: {plant_name}", False) - self.speak(f"You selected plant {plant_name}") - self.awaiting_plants = False - - -# InvoiceBotScreen is unchanged based on your last code snippet -class InvoiceBotScreen(QWidget): - 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") - if pix and 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 _show_greeting(self): - hour = QTime.currentTime().hour() - greeting = "Good night" - if 5 <= hour < 12: - greeting = "Good morning" - elif 12 <= hour < 17: - greeting = "Good afternoon" - elif 17 <= hour < 21: - greeting = "Good evening" - self.chat_area.add_bubble(f"{greeting}, User", False) - - def _on_send(self): - msg = self.user_input.text().strip() - if msg: - self.chat_area.add_bubble(msg, True) - self.user_input.clear() - self.chat_area.add_bubble("Processing your invoice request...", False) - -# Bot selector screen to choose Production or Invoice bots -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()) diff --git a/rppds.py b/rppds.py new file mode 100644 index 0000000..1fa2eed --- /dev/null +++ b/rppds.py @@ -0,0 +1,484 @@ +#!/usr/bin/env python3 +# main.py +import sys +import os +import json +import threading +import queue +import subprocess +from pathlib import Path + +from PyQt5.QtWidgets import ( + QApplication, QWidget, QStackedWidget, QVBoxLayout, QPushButton, QLabel, + QLineEdit, QTextEdit, QHBoxLayout, QFileDialog, QMessageBox +) +from PyQt5.QtCore import Qt, pyqtSignal, QObject, QThread +from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile, QWebEngineDownloadItem + +# VOSK imports (make sure vosk and sounddevice installed) +try: + from vosk import Model, KaldiRecognizer + import sounddevice as sd +except Exception as e: + Model = None + KaldiRecognizer = None + sd = None + print("Vosk/sounddevice not available:", e) + +# ---------- Helpers & Workers ---------- + +class WorkerSignals(QObject): + finished = pyqtSignal() + error = pyqtSignal(str) + result = pyqtSignal(object) + progress = pyqtSignal(int) + +class GenericWorker(QThread): + """Simple generic worker that runs a function in a QThread.""" + result_ready = pyqtSignal(object) + error = pyqtSignal(str) + + def __init__(self, fn, *args, **kwargs): + super().__init__() + self.fn = fn + self.args = args + self.kwargs = kwargs + + def run(self): + try: + res = self.fn(*self.args, **self.kwargs) + self.result_ready.emit(res) + except Exception as e: + self.error.emit(str(e)) + +# ---------- Vosk Voice Worker ---------- +class VoskWorker(QThread): + text_ready = pyqtSignal(str) + error = pyqtSignal(str) + + def __init__(self, model_path, device=None, samplerate=16000, parent=None): + super().__init__(parent) + self.model_path = model_path + self.device = device + self.samplerate = samplerate + self._running = True + + def stop(self): + self._running = False + + def run(self): + if Model is None or sd is None: + self.error.emit("Vosk or sounddevice not installed") + return + if not Path(self.model_path).exists(): + self.error.emit(f"Vosk model not found at {self.model_path}") + return + + model = Model(self.model_path) + rec = KaldiRecognizer(model, self.samplerate) + try: + with sd.RawInputStream(samplerate=self.samplerate, blocksize=8000, dtype='int16', + channels=1, device=self.device) as stream: + while self._running: + data = stream.read(4000)[0] + if rec.AcceptWaveform(data): + text = rec.Result() + parsed = json.loads(text).get("text", "") + if parsed: + self.text_ready.emit(parsed) + else: + # partial = rec.PartialResult() + pass + except Exception as e: + self.error.emit(str(e)) + +# ---------- TTS helper ---------- +def speak_text(text): + # Try pyttsx3 first (if installed); else fallback to espeak + try: + import pyttsx3 + engine = pyttsx3.init() + engine.say(text) + engine.runAndWait() + except Exception: + # fallback: use espeak command-line + try: + subprocess.run(['espeak', text], check=False) + except Exception as e: + print("TTS failed:", e) + +# ---------- Screens ---------- +class LoginWindow(GradientWidget): + API_URL = "https://pds1.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) + +class SelectorScreen(QWidget): + select_bot = pyqtSignal(str) + + def __init__(self): + super().__init__() + self._build_ui() + + def _build_ui(self): + layout = QVBoxLayout() + layout.addWidget(QLabel("Selector Screen - choose action")) + btn_web = QPushButton("Open WebAssistant") + btn_prod = QPushButton("Production Bot") + btn_invoice = QPushButton("Invoice Bot") + btn_web.clicked.connect(lambda: self.select_bot.emit("webassistant")) + btn_prod.clicked.connect(lambda: self.select_bot.emit("production")) + btn_invoice.clicked.connect(lambda: self.select_bot.emit("invoice")) + layout.addWidget(btn_web) + layout.addWidget(btn_prod) + layout.addWidget(btn_invoice) + self.setLayout(layout) + +class WebAssistantScreen(QWidget): + # navigation signals + go_back = pyqtSignal() + + def __init__(self, download_folder=None, model_path=None): + super().__init__() + self.download_folder = download_folder or str(Path.home() / "Downloads") + self.model_path = model_path + self.vosk_worker = None + self._build_ui() + + def _build_ui(self): + layout = QVBoxLayout() + header = QHBoxLayout() + back_btn = QPushButton("Back") + back_btn.clicked.connect(lambda: self.go_back.emit()) + self.url_edit = QLineEdit("https://www.example.com") + go_btn = QPushButton("Go") + go_btn.clicked.connect(self._load_url) + header.addWidget(back_btn) + header.addWidget(self.url_edit) + header.addWidget(go_btn) + layout.addLayout(header) + + # Web view + self.webview = QWebEngineView() + profile = QWebEngineProfile.defaultProfile() + profile.downloadRequested.connect(self._on_download_requested) + self.webview.setUrl(self.url_edit.text()) + layout.addWidget(self.webview) + + # Voice area + vbox = QHBoxLayout() + self.voice_out = QPushButton("Speak Selected Text") + self.voice_out.clicked.connect(self._speak_selected) + self.voice_in = QPushButton("Start Voice Input") + self.voice_in.setCheckable(True) + self.voice_in.clicked.connect(self._toggle_voice) + vbox.addWidget(self.voice_in) + vbox.addWidget(self.voice_out) + layout.addLayout(vbox) + + # log + self.log = QTextEdit() + self.log.setReadOnly(True) + layout.addWidget(self.log) + + self.setLayout(layout) + + def _load_url(self): + url = self.url_edit.text().strip() + if not url.startswith("http"): + url = "http://" + url + self.webview.setUrl(url) + + def _on_download_requested(self, download_item: QWebEngineDownloadItem): + # This handles downloads initiated by the web page + suggested = download_item.downloadFileName() + dest, _ = QFileDialog.getSaveFileName(self, "Save File", str(Path(self.download_folder) / suggested)) + if not dest: + download_item.cancel() + return + download_item.setPath(dest) + download_item.accept() + download_item.finished.connect(lambda: self.log.append(f"Downloaded: {dest}")) + + def _speak_selected(self): + # get selected text from webview (async) + def got_selection(text): + if not text: + self.log.append("No text selected") + return + self.log.append("TTS: " + text) + threading.Thread(target=speak_text, args=(text,), daemon=True).start() + # run JS to get selection + self.webview.page().runJavaScript("window.getSelection().toString();", got_selection) + + def _toggle_voice(self, checked): + if checked: + self.voice_in.setText("Stop Voice Input") + self.log.append("Starting voice input...") + self.vosk_worker = VoskWorker(self.model_path or "models/vosk-model-small-en-us-0.15") + self.vosk_worker.text_ready.connect(self._on_voice_text) + self.vosk_worker.error.connect(self._on_voice_error) + self.vosk_worker.start() + else: + self.voice_in.setText("Start Voice Input") + if self.vosk_worker: + self.vosk_worker.stop() + self.vosk_worker = None + self.log.append("Stopped voice input") + + def _on_voice_text(self, text): + self.log.append(f"Voice: {text}") + # You can send the recognized text to the web page or your bot API + # Example: inject into active text field + js = f""" + (function() {{ + var el = document.activeElement; + if (el && ('value' in el)) {{ + el.value = el.value + " {text}"; + }} else {{ + console.log("No active element"); + }} + }})(); + """ + self.webview.page().runJavaScript(js) + + def _on_voice_error(self, err): + self.log.append("Voice error: " + str(err)) + +class BotBaseScreen(QWidget): + go_back = pyqtSignal() + + def __init__(self, bot_name="bot"): + super().__init__() + self.bot_name = bot_name + self._build_ui() + + def _build_ui(self): + layout = QVBoxLayout() + header = QHBoxLayout() + back_btn = QPushButton("Back") + back_btn.clicked.connect(lambda: self.go_back.emit()) + header.addWidget(QLabel(f"{self.bot_name.capitalize()}")) + header.addWidget(back_btn) + layout.addLayout(header) + + self.log = QTextEdit() + self.log.setReadOnly(True) + layout.addWidget(self.log) + + # Example controls for sending API calls + self.send_btn = QPushButton("Run Bot Task (API call)") + self.send_btn.clicked.connect(self._run_task) + layout.addWidget(self.send_btn) + + self.setLayout(layout) + + def _run_task(self): + self.log.append(f"Starting {self.bot_name} job...") + # placeholder for heavy work - use GenericWorker to avoid blocking UI + def fake_job(): + import time + time.sleep(2) + return {"status": "ok", "bot": self.bot_name} + worker = GenericWorker(fake_job) + worker.result_ready.connect(lambda r: self.log.append(str(r))) + worker.error.connect(lambda e: self.log.append("Error: " + e)) + worker.start() + +class ProductionBotScreen(BotBaseScreen): + def __init__(self): + super().__init__(bot_name="production") + +class InvoiceBotScreen(BotBaseScreen): + def __init__(self): + super().__init__(bot_name="invoice") + +# ---------- Application Controller ---------- +class AppController(QWidget): + def __init__(self): + super().__init__() + self.setWindowTitle("Pi Assistant App") + self.resize(1024, 720) + layout = QVBoxLayout() + self.stack = QStackedWidget() + layout.addWidget(self.stack) + self.setLayout(layout) + + # screens + self.login = LoginScreen() + self.selector = SelectorScreen() + self.webassistant = WebAssistantScreen(model_path="models/vosk-model-small-en-us-0.15") + self.production = ProductionBotScreen() + self.invoice = InvoiceBotScreen() + + # add to stack + self.stack.addWidget(self.login) # idx 0 + self.stack.addWidget(self.selector) # idx 1 + self.stack.addWidget(self.webassistant) # idx 2 + self.stack.addWidget(self.production) # idx 3 + self.stack.addWidget(self.invoice) # idx 4 + + # wire signals + self.login.login_success.connect(self.on_login) + self.selector.select_bot.connect(self.on_select) + self.webassistant.go_back.connect(self.show_selector) + self.production.go_back.connect(self.show_selector) + self.invoice.go_back.connect(self.show_selector) + + self.show_login() + + def show_login(self): + self.stack.setCurrentWidget(self.login) + + def show_selector(self): + self.stack.setCurrentWidget(self.selector) + + def show_webassistant(self): + self.stack.setCurrentWidget(self.webassistant) + + def show_production(self): + self.stack.setCurrentWidget(self.production) + + def show_invoice(self): + self.stack.setCurrentWidget(self.invoice) + + def on_login(self, user_info): + # Called after login: you can check roles and direct user + print("Logged in:", user_info) + self.show_selector() + + def on_select(self, name): + if name == "webassistant": + self.show_webassistant() + elif name == "production": + self.show_production() + elif name == "invoice": + self.show_invoice() + +# ---------- main ---------- +def main(): + app = QApplication(sys.argv) + # ensure WebEngine resources are loaded properly + controller = AppController() + controller.show() + sys.exit(app.exec_()) + +if __name__ == "__main__": + main() diff --git a/sample.py b/sample.py deleted file mode 100644 index e69de29..0000000 diff --git a/sfgprintingnew.py b/sfgprintingnew.py new file mode 100644 index 0000000..2185c6a --- /dev/null +++ b/sfgprintingnew.py @@ -0,0 +1,1217 @@ +import sys +import sqlite3 +import os +from PyQt5.QtWidgets import ( + QApplication, QWidget, QLabel, QLineEdit, QPushButton, + QVBoxLayout, QHBoxLayout, QMessageBox, QStackedWidget, QTableWidget, QHeaderView, QTableWidgetItem, QTextEdit, + QAbstractScrollArea, QScrollArea, QSizePolicy,QFormLayout +) +from PyQt5.QtGui import QFont, QColor, QPalette, QPixmap, QBrush +from PyQt5.QtCore import Qt +from PyQt5.QtCore import QEvent +from db_setup import init_db, get_connection + +# Initialize DB once (creates tables if missing) +init_db() + +# Global references +global_stacked_widget = None +global_main_screen = None +keyboard_window = None + +def insert_text(widget, text): + if isinstance(widget, (QLineEdit, QTextEdit)): + widget.insert(text) + +def backspace_text(widget): + if isinstance(widget, QLineEdit): + current_text = widget.text() + widget.setText(current_text[:-1]) + elif isinstance(widget, QTextEdit): + cursor = widget.textCursor() + cursor.deletePreviousChar() + widget.setTextCursor(cursor) + +def clear_text(widget): + if isinstance(widget, QLineEdit): + widget.clear() + elif isinstance(widget, QTextEdit): + widget.clear() + +def show_keyboard(): + global keyboard_window + + # Close any existing keyboard window + if keyboard_window is not None: + keyboard_window.close() + + keyboard_window = QWidget() + keyboard_window.setWindowTitle("Numeric Keyboard") + keyboard_window.setFixedSize(270, 360) + layout = QVBoxLayout() + + # Button layout + buttons = [ + ["1", "2", "3"], + ["4", "5", "6"], + ["7", "8", "9"], + ["0"], + ["Back", "Clear"] + ] + + for row in buttons: + row_layout = QHBoxLayout() + for key in row: + btn = QPushButton(key) + btn.setFixedSize(80, 60) + btn.setFont(QFont("Arial", 14)) + + # Connect button actions + if key == "Back": + btn.clicked.connect(lambda _, w=QApplication.focusWidget(): backspace_text(w)) + elif key == "Clear": + btn.clicked.connect(lambda _, w=QApplication.focusWidget(): clear_text(w)) + else: + btn.clicked.connect(lambda _, k=key, w=QApplication.focusWidget(): insert_text(w, k)) + + row_layout.addWidget(btn) + layout.addLayout(row_layout) + + keyboard_window.setLayout(layout) + keyboard_window.setWindowModality(Qt.NonModal) + keyboard_window.setWindowFlag(Qt.WindowStaysOnTopHint) + keyboard_window.show() + + +# Navigation functions +def go_home(): + if global_stacked_widget and global_stacked_widget.currentIndex() != 1: + global_stacked_widget.setCurrentIndex(1) + + +def refresh_screen(): + if global_stacked_widget: + current_index = global_stacked_widget.currentIndex() + current_widget = global_stacked_widget.currentWidget() + current_class = type(current_widget) + + new_widget = current_class() # Recreate the same screen + global_stacked_widget.removeWidget(current_widget) + global_stacked_widget.insertWidget(current_index, new_widget) + global_stacked_widget.setCurrentIndex(current_index) + + # Optional: delete the old widget to free memory + current_widget.deleteLater() + +def go_login(): + if global_stacked_widget: + global_stacked_widget.setCurrentIndex(0) + +def open_operator_identity(): + global global_stacked_widget + operator_screen = OperatorIdentityScreen() + global_stacked_widget.addWidget(operator_screen) + global_stacked_widget.setCurrentWidget(operator_screen) + +def open_checklist_screen(self): + global global_stacked_widget + checklist_screen = ChecklistScreen() + global_stacked_widget.addWidget(checklist_screen) + global_stacked_widget.setCurrentWidget(checklist_screen) + +def go_first_off(): + global global_stacked_widget + first_off_screen = FirstOff() + global_stacked_widget.addWidget(first_off_screen) + global_stacked_widget.setCurrentWidget(first_off_screen) + + +def go_final_validation(): + global global_stacked_widget + final_validation_screen = FinalValidation() + global_stacked_widget.addWidget(final_validation_screen) + global_stacked_widget.setCurrentWidget(final_validation_screen) + +# ---------------- Login Screen ---------------- # +class LoginScreen(QWidget): + def __init__(self, stacked_widget): + super().__init__() + self.stacked_widget = stacked_widget + + self.init_ui() + def init_ui(self): + self.setWindowTitle("Login Screen") + palette = QPalette() + palette.setColor(QPalette.Window, QColor("#E6F7FF")) + self.setAutoFillBackground(True) + self.setPalette(palette) + + title = QLabel("WELCOME TO CRI PUMPS") + title.setFont(QFont("Times New Roman", 28, QFont.Bold)) + title.setStyleSheet("color: darkblue;") + title.setAlignment(Qt.AlignCenter) + + logo = QLabel() + logo.setAlignment(Qt.AlignCenter) + logo_path = "/home/cri/myproject/venv/cri_logo_red.png" + if os.path.exists(logo_path): + pixmap = QPixmap(logo_path) + if not pixmap.isNull(): + logo.setPixmap(pixmap.scaledToWidth(150, Qt.SmoothTransformation)) + else: + logo.setText("Failed to load image.") + else: + logo.setText("Logo not found.") + + self.username_label = QLabel("Username:") + self.username_label.setFont(QFont("Times New Roman", 14, QFont.Bold)) + self.username_edit = QLineEdit() + self.username_edit.setFont(QFont("Times New Roman", 12)) + self.username_edit.setFixedHeight(40) + self.username_edit.setFixedWidth(300) + self.username_edit.setPlaceholderText("Enter username") + self.username_edit.returnPressed.connect(self.focus_password) + + user_row = QHBoxLayout() + user_row.addStretch() + user_row.addWidget(self.username_label) + user_row.addWidget(self.username_edit) + user_row.addStretch() + + self.password_label = QLabel("Password:") + self.password_label.setFont(QFont("Times New Roman", 14, QFont.Bold)) + self.password_edit = QLineEdit() + self.password_edit.setFont(QFont("Times New Roman", 12)) + self.password_edit.setFixedHeight(40) + self.password_edit.setFixedWidth(300) + self.password_edit.setEchoMode(QLineEdit.Password) + self.password_edit.setPlaceholderText("Enter password") + self.password_edit.returnPressed.connect(self.login) + + self.toggle_button = QPushButton("👁") + self.toggle_button.setFixedWidth(40) + self.toggle_button.setCheckable(True) + self.toggle_button.toggled.connect(self.toggle_password) + + pass_row = QHBoxLayout() + pass_row.addStretch() + pass_row.addWidget(self.password_label) + pass_layout = QHBoxLayout() + pass_layout.addWidget(self.password_edit) + pass_layout.addWidget(self.toggle_button) + pass_row.addLayout(pass_layout) + pass_row.addStretch() + + self.login_button = QPushButton("LOGIN") + self.login_button.setFont(QFont("Times New Roman", 12, QFont.Bold)) + self.login_button.setFixedWidth(200) + self.login_button.setStyleSheet(""" + QPushButton { + background-color: darkblue; + color: white; + padding: 10px; + border-radius: 6px; + } +""") + self.login_button.clicked.connect(self.login) + + login_btn_row = QHBoxLayout() + login_btn_row.addStretch() + login_btn_row.addWidget(self.login_button) + login_btn_row.addStretch() + +# Second logo below login button + footer_logo = QLabel() + footer_logo.setAlignment(Qt.AlignCenter) + footer_logo_path = "/home/cri/myproject/venv/cri_farm_banner.jpg" + if os.path.exists(footer_logo_path): + pixmap2 = QPixmap(footer_logo_path) + if not pixmap2.isNull(): + footer_logo.setPixmap(pixmap2.scaledToWidth(500, Qt.SmoothTransformation)) + else: + footer_logo.setText("Failed to load image.") + else: + footer_logo.setText("Footer logo not found.") + + # Main layout + layout = QVBoxLayout() + layout.addSpacing(50) + layout.addWidget(title) + layout.addSpacing(30) + layout.addWidget(logo) # Top logo + layout.addSpacing(40) + layout.addLayout(user_row) + layout.addSpacing(20) + layout.addLayout(pass_row) + layout.addSpacing(40) + layout.addLayout(login_btn_row) + layout.addSpacing(30) + layout.addWidget(footer_logo) # New footer logo + layout.addStretch() + + self.setLayout(layout) + + def toggle_password(self, checked): + self.password_edit.setEchoMode(QLineEdit.Normal if checked else QLineEdit.Password) + + def focus_password(self): + self.password_edit.setFocus() + + def login(self): + username = self.username_edit.text() + password = self.password_edit.text() + + if not username or not password: + QMessageBox.warning(self, "Error", "Enter both username and password.") + return + + # ✅ Use the same connection from db_setup.py + conn = get_connection() + cursor = conn.cursor() + cursor.execute( + "SELECT * FROM user_details WHERE user_name=? AND user_pass=?", + (username, password) + ) + result = cursor.fetchone() + conn.close() + + if result: + self.stacked_widget.setCurrentIndex(1) + else: + QMessageBox.warning(self, "Error", "Invalid credentials.") + +# ---------------- Main Screen ---------------- # + +class MainScreen(QWidget): + def __init__(self): + super().__init__() + self.setWindowTitle("Production Display") + self.showFullScreen() + + # Set entire background to light blue + palette = self.palette() + palette.setColor(QPalette.Window, QColor("#ADD8E6")) + self.setAutoFillBackground(True) + self.setPalette(palette) + + # Initialize widgets + self.table = QTableWidget(5, 3) # Example table + self.operator_identity_btn = QPushButton("Operator Identity") + self.btn_serial_print = QPushButton("Serial Print") + self.btn_part_validation = QPushButton("Part Validation") + self.btn_checklist = QPushButton("Checklist") + self.btn_couth_marking = QPushButton("Couth Marking") + self.prod_order_input = QLineEdit() + self.serial_input = QLineEdit() + + self.init_ui() + # Helper for focusing next input + def make_focus_function(self, index): + def focus_next(): + if index < len(self.input_fields): + self.input_fields[index].setFocus() + return focus_next + + def init_ui(self): + palette = self.palette() + palette.setColor(QPalette.Window, QColor("#E6F7FF")) + self.setPalette(palette) + + # Title label + self.title_label = QLabel("WORK ORDER VALIDATION") + self.title_label.setFont(QFont("Times New Roman", 20, QFont.Bold)) + self.title_label.setStyleSheet("color: white;") + self.title_label.setAlignment(Qt.AlignCenter) + + # Logo + logo_label = QLabel() + pixmap = QPixmap("/home/cri/myproject/cri_logo_red.png") + pixmap = pixmap.scaled(60, 60, Qt.KeepAspectRatio, Qt.SmoothTransformation) + logo_label.setPixmap(pixmap) + logo_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + + # Title layout (logo + title text) + title_layout = QHBoxLayout() + title_layout.setContentsMargins(10, 5, 10, 5) + title_layout.addWidget(logo_label) + title_layout.addSpacing(20) + title_layout.addWidget(self.title_label, stretch=1) + title_layout.addStretch() + + # Header container with background color + header_widget = QWidget() + header_widget.setLayout(title_layout) + header_widget.setStyleSheet("background-color: #003366;") # Dark blue background + + + # Disable widgets that should start inactive + self.table.setDisabled(True) + self.operator_identity_btn.setDisabled(True) + self.other_buttons = [ + self.btn_serial_print, self.btn_part_validation, + self.btn_checklist, self.btn_couth_marking + ] + for btn in self.other_buttons: + btn.setDisabled(True) + + self.prod_order_input.setEnabled(True) + self.serial_input.setEnabled(True) + + self.prod_order_input.editingFinished.connect(lambda: print("Work order details fetch")) + self.serial_input.editingFinished.connect(lambda: print("Serial details fetch")) + + # Input Fields + label_names = [ + "Production Order Number", "Item Description", "Current Serial Number", + "Operator Number", "Completed Quantity", "Sub Assembly Serial Number", + "Sub Assembly Item Description", "Previous Serial Number", + "Total Quantity", "Remaining Quantity" + ] + font_label = QFont("Times New Roman", 14, QFont.Bold) + font_input = QFont("Times New Roman", 12) + #field_width, field_height = 350, 50 + + self.input_fields = [] + left_layout, right_layout = QVBoxLayout(), QVBoxLayout() + + for i, name in enumerate(label_names): + sub_layout = left_layout if i < 5 else right_layout + field_box = QVBoxLayout() + + label = QLabel(name) + label.setFont(font_label) + + input_box = QLineEdit() + input_box.setFont(font_input) + input_box.setMinimumHeight(45) + input_box.setStyleSheet(""" + QLineEdit { + background-color: #ADD8E6; + padding: 6px; + border-radius: 6px; + } + """) + # ✅ Auto expand horizontally with screen + input_box.setSizePolicy(QPushButton().sizePolicy().Expanding, + QPushButton().sizePolicy().Fixed) + + if i == 0: + toggle_button = QPushButton("Serial OFF") + toggle_button.setCheckable(True) + toggle_button.setFont(QFont("Times New Roman", 10, QFont.Bold)) + toggle_button.setStyleSheet(""" + QPushButton { + background-color: #00008B; + color: white; + padding: 6px; + border-radius: 6px; + } + QPushButton:checked { + background-color: #FF6347; + color: white; + } + """) + toggle_button.clicked.connect(self.handle_serial_toggle) + self.serial_toggle_button = toggle_button + + toggle_layout = QHBoxLayout() + toggle_layout.addWidget(input_box, stretch=3) + toggle_layout.addWidget(toggle_button, stretch=1) + field_box.addWidget(label) + field_box.addLayout(toggle_layout) + else: + if i < 9: + input_box.returnPressed.connect(self.make_focus_function(i + 1)) + field_box.addWidget(label) + field_box.addWidget(input_box) + + sub_layout.addLayout(field_box) + # ✅ Add spacing between each field block + sub_layout.addSpacing(15) + self.input_fields.append(input_box) + + input_layout = QHBoxLayout() + input_layout.addStretch() + input_layout.addLayout(left_layout, stretch=1) + input_layout.addSpacing(60) + input_layout.addLayout(right_layout, stretch=1) + input_layout.addStretch() + + # Right navigation buttons + right_buttons = QVBoxLayout() + button_actions = { + "Operator Mapping": open_operator_identity, + "Product Check List": open_checklist_screen, + "Process Check List": go_first_off, + "Part Validation": go_final_validation + } + + for name, handler in button_actions.items(): + btn = QPushButton(name) + btn.setFont(QFont("Times New Roman", 11, QFont.Bold)) + btn.setFixedSize(180, 45) + btn.setStyleSheet(""" + QPushButton { + background-color: #00008B; + color: white; + padding: 6px; + border-radius: 6px; + } + QPushButton:hover { + background-color: #0000CD; + } + QPushButton:pressed { + background-color: #000066; + } + """) + btn.clicked.connect(handler) + right_buttons.addWidget(btn) + right_buttons.addSpacing(15) + right_buttons.addStretch() + # ✅ Extra OK / NOT OK buttons below right buttons + ok_btn = QPushButton("OK") + ok_btn.setStyleSheet(""" + QPushButton { + background-color: darkgreen; + color: white; + border-radius: 8px; + padding: 5px 10px; + } + QPushButton:pressed { + background-color: green; + } + """) + ok_btn.setFont(QFont("Times New Roman", 9)) + ok_btn.setFixedWidth(100) + + not_ok_btn = QPushButton("NOT OK") + not_ok_btn.setStyleSheet(""" + QPushButton { + background-color: darkred; + color: white; + border-radius: 8px; + padding: 5px 10px; + } + QPushButton:pressed { + background-color: red; + } + """) + not_ok_btn.setFont(QFont("Times New Roman", 9)) + not_ok_btn.setFixedWidth(100) + + # Place them in a horizontal layout + ok_notok_row = QHBoxLayout() + ok_notok_row.addStretch() + ok_notok_row.addWidget(ok_btn) + ok_notok_row.addSpacing(10) + ok_notok_row.addWidget(not_ok_btn) + ok_notok_row.addStretch() + + # Add to right side buttons layout + right_buttons.addSpacing(20) + right_buttons.addLayout(ok_notok_row) + + # Bottom navigation + bottom_buttons = QHBoxLayout() + btn_data = [ + ("REFRESH", refresh_screen), + ("LOGIN", go_login) + ] + for txt, handler in btn_data: + btn = QPushButton(txt) + btn.setFont(QFont("Times New Roman", 11, QFont.Bold)) + btn.setFixedSize(150, 45) + btn.setStyleSheet(""" + QPushButton { + background-color: #00008B; + color: white; + font-weight: bold; + border-radius: 6px; + } + QPushButton:pressed { + background-color: #000066; + } + """) + btn.clicked.connect(handler) + bottom_buttons.addStretch() + bottom_buttons.addWidget(btn) + bottom_buttons.addStretch() + + # Final layout + content = QHBoxLayout() + content.addLayout(input_layout, stretch=3) + content.addLayout(right_buttons, stretch=1) + + main_layout = QVBoxLayout() + main_layout.addWidget(header_widget) + main_layout.addSpacing(30) + main_layout.addLayout(content) + main_layout.addStretch() + main_layout.addLayout(bottom_buttons) + + self.setLayout(main_layout) + + def handle_serial_toggle(self): + if self.serial_toggle_button.isChecked(): + self.serial_toggle_button.setText("Serial ON") + self.title_label.setText("SERIAL NUMBER VALIDATION") + else: + self.serial_toggle_button.setText("Serial OFF") + self.title_label.setText("WORK ORDER VALIDATION") + + +# ------------------------------ Operator Identity Screen ------------------# + +class OperatorIdentityScreen(QWidget): + def __init__(self): + super().__init__() + self.setWindowTitle("Operator Identity") + self.showFullScreen() + self.inputs = [] + # Set entire background to light blue + palette = self.palette() + palette.setColor(QPalette.Window, QColor("#ADD8E6")) # Light Blue + self.setAutoFillBackground(True) + self.setPalette(palette) + self.init_ui() + def init_ui(self): + palette = self.palette() + palette.setColor(QPalette.Window, QColor("#E6F7FF")) + self.setPalette(palette) + + # Header: Logo + Title + logo_label = QLabel() + pixmap = QPixmap("/home/cri/myproject/cri_logo_red.png") + pixmap = pixmap.scaled(60, 60, Qt.KeepAspectRatio, Qt.SmoothTransformation) + logo_label.setPixmap(pixmap) + logo_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + + title_label = QLabel("OPERATOR IDENTITY") + title_label.setFont(QFont("Times New Roman", 20, QFont.Bold)) + title_label.setStyleSheet("color: white;") + title_label.setAlignment(Qt.AlignCenter) # ✅ Centered title + + header_layout = QHBoxLayout() + header_layout.setContentsMargins(10, 10, 10, 10) + header_layout.addWidget(logo_label) + header_layout.addStretch() + header_layout.addWidget(title_label) + header_layout.addStretch() + + header_widget = QWidget() + header_widget.setLayout(header_layout) + header_widget.setStyleSheet("background-color: #003366;") # Dark Blue background + + # Input Fields + left_layout = QVBoxLayout() + right_layout = QVBoxLayout() + + for i in range(10): + label = QLabel(f"Operator ID {i + 1}") + label.setFont(QFont("Times New Roman", 12, QFont.Bold)) + label.setStyleSheet("color: black;") + + line_edit = QLineEdit() + line_edit.setFont(QFont("Times New Roman", 11)) + line_edit.setFixedHeight(50) + line_edit.setFixedWidth(800) + line_edit.setStyleSheet("background-color: #ADD8E6;") + line_edit.textChanged.connect(self.check_fields) + + if i < 9: + line_edit.returnPressed.connect(self.make_focus_function(i + 1)) + + self.inputs.append(line_edit) + + clear_btn = QPushButton("CLEAR") + clear_btn.setFixedSize(70, 25) + clear_btn.setStyleSheet(""" + QPushButton { + background-color: red; + color: white; + border: 1px solid black; + } + QPushButton:pressed { + background-color: darkred; + } +""") + clear_btn.setAutoFillBackground(True) + clear_btn.clicked.connect(line_edit.clear) + + entry_layout = QVBoxLayout() + entry_layout.addWidget(label) + entry_layout.addWidget(line_edit) + entry_layout.addWidget(clear_btn) + + # Add space below each operator entry + entry_layout.addSpacing(20) # 👈 Equal space between entries + if i < 5: + left_layout.addLayout(entry_layout) + else: + right_layout.addLayout(entry_layout) + + input_area = QHBoxLayout() + input_area.addLayout(left_layout) + input_area.addSpacing(40) + input_area.addLayout(right_layout) + + # Submit Button + self.submit_btn = QPushButton("SUBMIT") + self.submit_btn.setFont(QFont("Times New Roman", 10, QFont.Bold)) + self.submit_btn.setStyleSheet(""" + QPushButton { + background-color: #008000; /* Dark green hex code */ + color: white; + border: 1px solid black; + font-weight: bold; + padding: 8px; + } + QPushButton:pressed { + background-color: #006400; /* Darker green on press */ + } +""") + self.submit_btn.setAutoFillBackground(True) + self.submit_btn.setEnabled(False) + self.submit_btn.clicked.connect(self.submit_operator_ids) + + # Bottom Navigation Buttons + bottom_buttons = QHBoxLayout() + button_data = [ + ("HOME", "lightblue", go_home), + ("REFRESH", "lightblue", refresh_screen) + ] + for text, color, handler in button_data: + btn = QPushButton(text) + btn.setFont(QFont("Times New Roman", 12, QFont.Bold)) + btn.setFixedSize(150, 50) + btn.setStyleSheet(""" + QPushButton { + background-color: #00008B; /* Dark Blue hex code */ + color: white; + border: 1px solid black; + font-weight: bold; + } + QPushButton:pressed { + background-color: #000066; /* Darker blue on press */ + } +""") + btn.setAutoFillBackground(True) + btn.clicked.connect(handler) + bottom_buttons.addWidget(btn) + self.submit_btn.setFixedSize(150, 50) + bottom_buttons.addWidget(self.submit_btn) + + layout = QVBoxLayout() + layout.addWidget(header_widget) + layout.addSpacing(20) + layout.addLayout(input_area) + layout.addStretch() + layout.addSpacing(20) + layout.addLayout(bottom_buttons) + self.setLayout(layout) + + def make_focus_function(self, index): + def focus_next(): + if index < len(self.inputs): + self.inputs[index].setFocus() + + return focus_next + + def check_fields(self): + """Enable submit button if any field has text""" + self.submit_btn.setEnabled(any(inp.text().strip() for inp in self.inputs)) + + def submit_operator_ids(self): + operator_ids = [inp.text().strip() if inp.text().strip() else None for inp in self.inputs] + if not any(operator_ids): + QMessageBox.warning(self, "Input Error", "Please fill at least one Operator ID before submitting.") + return + + try: + conn = sqlite3.connect("sfg printing.db") + cursor = conn.cursor() + cursor.execute(''' + INSERT INTO operator_details ( + operator1_id, operator2_id, operator3_id, operator4_id, operator5_id, + operator6_id, operator7_id, operator8_id, operator9_id, operator10_id + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''', operator_ids) + conn.commit() + conn.close() + QMessageBox.information(self, "Success", "Operator IDs saved successfully.") + for inp in self.inputs: + inp.clear() + self.submit_btn.setEnabled(False) + except Exception as e: + QMessageBox.critical(self, "Database Error", f"Failed to insert data:\n{e}") + + +# -----------------------------------Check List Screen---------------------------# + +class ChecklistScreen(QWidget): + def __init__(self): + super().__init__() + self.setWindowTitle("Checklist Screen") + self.resize(1000, 700) + + # Set background + palette = self.palette() + palette.setColor(QPalette.Window, QColor("#ADD8E6")) + self.setAutoFillBackground(True) + self.setPalette(palette) + + layout = QVBoxLayout(self) + + # ---------------- Header ---------------- + header_widget = QWidget() + header_widget.setStyleSheet("background-color: #003366;") + header_layout = QHBoxLayout(header_widget) + header_layout.setContentsMargins(10, 10, 10, 10) + + logo_label = QLabel() + pixmap = QPixmap("/home/cri/myproject/cri_logo_red.png") + pixmap = pixmap.scaled(60, 60, Qt.KeepAspectRatio, Qt.SmoothTransformation) + logo_label.setPixmap(pixmap) + logo_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + + title_label = QLabel("PRODUCT CHECK LIST") + title_label.setFont(QFont("Times New Roman", 24, QFont.Bold)) + title_label.setStyleSheet("color: white;") + title_label.setAlignment(Qt.AlignCenter) + + header_layout.addWidget(logo_label) + header_layout.addSpacing(20) + header_layout.addWidget(title_label, stretch=1) + header_layout.addStretch() + layout.addWidget(header_widget) + + # ---------------- Inputs ---------------- + input_layout = QHBoxLayout() + self.work_order = self.create_labeled_input("Workorder Number") + self.serial_number = self.create_labeled_input("Serial Number") + self.operator_plant = self.create_labeled_input("Operator Number") + + input_layout.addLayout(self.work_order['layout']) + input_layout.addLayout(self.serial_number['layout']) + input_layout.addLayout(self.operator_plant['layout']) + layout.addLayout(input_layout) + + # ---------------- Table ---------------- + self.table = QTableWidget(1, 3) + self.table.setHorizontalHeaderLabels(["Dimension Name", "Dimension Value", "Entered Value"]) + self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + self.table.verticalHeader().setVisible(False) + self.table.installEventFilter(self) + + # Header style + self.table.setStyleSheet(""" + QHeaderView::section { + background-color: #003366; + color: white; + font-weight: bold; + padding: 6px; + border: 1px solid #CCCCCC; + } + """) + self.apply_row_colors() + layout.addWidget(self.table) + + # ---------------- Buttons ---------------- + button_layout = QHBoxLayout() + button_layout.setSpacing(40) + button_layout.addStretch() + + button_style = """ + QPushButton { + background-color: #00008B; /* Dark Blue */ + color: white; /* White text */ + font-weight: bold; + border: 1px solid black; + } + QPushButton:pressed { + background-color: #000066; /* Darker blue on press */ + } +""" + + submit_button = QPushButton("SUBMIT") + submit_button.setFont(QFont("Times New Roman", 12, QFont.Bold)) + submit_button.setFixedSize(150, 50) + submit_button.setStyleSheet(button_style) + submit_button.clicked.connect(self.submit_data) + button_layout.addWidget(submit_button) + + back_button = QPushButton("BACK") + back_button.setFont(QFont("Times New Roman", 12, QFont.Bold)) + back_button.setFixedSize(150, 50) + back_button.setStyleSheet(button_style) + back_button.clicked.connect(go_home) + button_layout.addWidget(back_button) + + button_layout.addStretch() + layout.addLayout(button_layout) + + # ---------------- Helpers ---------------- + def create_labeled_input(self, label_text): + layout = QVBoxLayout() + label = QLabel(label_text) + label.setFont(QFont("Times New Roman", 12)) + line_edit = QLineEdit() + line_edit.setFixedWidth(200) + layout.addWidget(label) + layout.addWidget(line_edit) + return {"layout": layout, "edit": line_edit} + + def add_row(self): + row = self.table.rowCount() + self.table.insertRow(row) + for col in range(self.table.columnCount()): + item = QTableWidgetItem("") + item.setForeground(QBrush(QColor("black"))) + color = QColor("#d8f3dc") if row % 2 == 0 else QColor("#d0f0ff") + item.setBackground(QBrush(color)) + self.table.setItem(row, col, item) + + def apply_row_colors(self): + for row in range(self.table.rowCount()): + for col in range(self.table.columnCount()): + item = self.table.item(row, col) + if item: + color = QColor("#d8f3dc") if row % 2 == 0 else QColor("#d0f0ff") + item.setBackground(QBrush(color)) + + def eventFilter(self, source, event): + if source == self.table and event.type() == QEvent.KeyPress: + row = self.table.currentRow() + col = self.table.currentColumn() + if event.key() in (Qt.Key_Return, Qt.Key_Enter): + # Move to next column or add new row + if col < self.table.columnCount() - 1: + self.table.setCurrentCell(row, col + 1) + else: + self.add_row() + self.table.setCurrentCell(self.table.rowCount() - 1, 0) + return True + elif event.key() == Qt.Key_Right: + # Move right in same row + if col < self.table.columnCount() - 1: + self.table.setCurrentCell(row, col + 1) + return True + return super().eventFilter(source, event) + + def submit_data(self): + data_rows = [] + for row in range(self.table.rowCount()): + row_data = [] + for col in range(self.table.columnCount()): + item = self.table.item(row, col) + row_data.append(item.text().strip() if item else "") + if any(row_data): + data_rows.append(row_data) + + if not data_rows: + QMessageBox.warning(self, "Empty Table", "Please enter at least one row of dimension data.") + return + + try: + with sqlite3.connect("/home/cri/myproject/sfgprinting.db") as conn: + cursor = conn.cursor() + for row in data_rows: + name = row[0] if len(row) > 0 else "" + value = row[1] if len(row) > 1 else "" + entervalue = row[2] if len(row) > 2 else "" + cursor.execute(""" + INSERT INTO check_list_details ( + Dimension_Name, Dimension_Value, Entered_Value + ) VALUES (?, ?, ?) + """, (name, value, entervalue)) + conn.commit() + + QMessageBox.information(self, "Success", "Checklist data saved successfully.") + self.clear_fields() + + except sqlite3.Error as e: + QMessageBox.critical(self, "Database Error", f"SQLite error occurred:\n{e}") + except Exception as e: + QMessageBox.critical(self, "Error", f"Unexpected error occurred:\n{e}") + + def clear_fields(self): + self.work_order['edit'].clear() + self.serial_number['edit'].clear() + self.operator_plant['edit'].clear() + self.table.setRowCount(0) # Clear all rows + def go_back(self): + self.stacked_widget.setCurrentIndex(3) + +# -------------------------first off screen---------------------------------------# +class FirstOff(QWidget): + def __init__(self): + super().__init__() + self.setWindowTitle("First Off") + self.resize(1400, 750) + palette = self.palette() + palette.setColor(QPalette.Window, QColor("#ADD8E6")) + self.setAutoFillBackground(True) + self.setPalette(palette) + main_layout = QVBoxLayout(self) + + # Header + header_widget = QWidget() + header_widget.setStyleSheet("background-color: #003366;") + header_layout = QHBoxLayout(header_widget) + header_layout.setContentsMargins(10, 10, 10, 10) + + logo_label = QLabel() + pixmap = QPixmap("/home/cri/myproject/cri_logo_red.png") + pixmap = pixmap.scaled(60, 60, Qt.KeepAspectRatio, Qt.SmoothTransformation) + logo_label.setPixmap(pixmap) + logo_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + + title_label = QLabel("PROCESS CHECK LIST") + title_label.setFont(QFont("Times New Roman", 24, QFont.Bold)) + title_label.setStyleSheet("color: white;") + title_label.setAlignment(Qt.AlignCenter) + + header_layout.addWidget(logo_label) + header_layout.addSpacing(20) + header_layout.addWidget(title_label, stretch=1) + header_layout.addStretch() + + main_layout.addWidget(header_widget) + + # Table + self.table = QTableWidget(0, 3) + self.table.setHorizontalHeaderLabels(["Dimension Name", "Dimension Value", "Enter Value"]) + self.table.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents) + self.table.setStyleSheet(""" + QTableWidget { + background-color: white; + gridline-color: black; + } + QHeaderView::section { + background-color: #00008B; + color: white; + font-weight: bold; + padding: 4px; + border: 1px solid black; + } + """) + + header = self.table.horizontalHeader() + header.setSectionResizeMode(QHeaderView.Stretch) + header.setHighlightSections(False) + + main_layout.addWidget(self.table, stretch=1) + + # Add first row + self.add_row() + + # Install event filter to capture Enter key + self.table.installEventFilter(self) + + # Back Button + back_btn = QPushButton("BACK") + back_btn.setFont(QFont("Times New Roman", 12, QFont.Bold)) + back_btn.setStyleSheet(""" + QPushButton { + background-color: #00008B; /* Dark Blue */ + color: white; + border: 1px solid black; + padding: 10px 20px; + font-weight: bold; + } + QPushButton:pressed { + background-color: #000066; /* Darker blue on press */ + } + """) + back_btn.clicked.connect(go_home) + main_layout.addWidget(back_btn, alignment=Qt.AlignCenter) + + def add_row(self): + row = self.table.rowCount() + self.table.insertRow(row) + for col in range(self.table.columnCount()): + item = QTableWidgetItem() + bg_color = "white" if row % 2 == 0 else "#E6F0FF" + item.setBackground(QBrush(QColor(bg_color))) + self.table.setItem(row, col, item) + + def eventFilter(self, obj, event): + if obj == self.table and event.type() == QEvent.KeyPress: + if event.key() in (Qt.Key_Return, Qt.Key_Enter): + row = self.table.currentRow() + col = self.table.currentColumn() + + # Move to next column + if col < self.table.columnCount() - 1: + self.table.setCurrentCell(row, col + 1) + else: + # Last column, move to next row + if row == self.table.rowCount() - 1: + self.add_row() + self.table.setCurrentCell(row + 1, 0) + return True + return super().eventFilter(obj, event) + + def go_back(self): + self.stacked_widget.setCurrentIndex(4) +# -------------------------final validation screen----------------------------------# +class FinalValidation(QWidget): + def __init__(self): + super().__init__() + self.setWindowTitle("Final Validation") + self.resize(1400, 750) + + # Background + palette = self.palette() + palette.setColor(QPalette.Window, QColor("#ADD8E6")) + self.setAutoFillBackground(True) + self.setPalette(palette) + main_layout = QVBoxLayout(self) + + # ================= Header ================= + header_widget = QWidget() + header_widget.setStyleSheet("background-color: #003366;") + header_layout = QHBoxLayout(header_widget) + header_layout.setContentsMargins(10, 10, 10, 10) + + logo_label = QLabel() + pixmap = QPixmap("/home/cri/myproject/cri_logo_red.png") + pixmap = pixmap.scaled(60, 60, Qt.KeepAspectRatio, Qt.SmoothTransformation) + logo_label.setPixmap(pixmap) + logo_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + + title_label = QLabel("PART VALIDATION") + title_label.setFont(QFont("Times New Roman", 24, QFont.Bold)) + title_label.setStyleSheet("color: white;") + title_label.setAlignment(Qt.AlignCenter) + + header_layout.addWidget(logo_label) + header_layout.addSpacing(20) + header_layout.addWidget(title_label, stretch=1) + header_layout.addStretch() + main_layout.addWidget(header_widget) + + # Top Info Layout + info_layout = QHBoxLayout() + + self.work_order_input = QLineEdit() + self.serial_input = QLineEdit() + self.operator_input = QLineEdit() + + light_blue_style = "background-color: #E6F0FF;" + self.work_order_input.setStyleSheet(light_blue_style) + self.serial_input.setStyleSheet(light_blue_style) + self.operator_input.setStyleSheet(light_blue_style) + + info_layout.addWidget(self._labeled_field("Work Order Number:", self.work_order_input)) + info_layout.addWidget(self._labeled_field("Serial Number:", self.serial_input)) + info_layout.addWidget(self._labeled_field("Operator Plant:", self.operator_input)) + main_layout.addLayout(info_layout) + + # ================= Table ================= + self.table = QTableWidget(0, 3) + self.table.setHorizontalHeaderLabels(["Dimension Name", "Dimension Value", "Enter Value"]) + self.table.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents) + self.table.setStyleSheet(""" + QTableWidget { + background-color: white; + gridline-color: black; + } + QHeaderView::section { + background-color: #00008B; + color: white; + font-weight: bold; + padding: 4px; + border: 1px solid black; + } + """) + + header = self.table.horizontalHeader() + header.setSectionResizeMode(QHeaderView.Stretch) + header.setHighlightSections(False) + + main_layout.addWidget(self.table, stretch=1) + + # Add first row + self.add_row() + + # Install event filter for Enter key + self.table.installEventFilter(self) + + # ================= Back Button ================= + back_btn = QPushButton("BACK") + back_btn.setFont(QFont("Times New Roman", 12, QFont.Bold)) + back_btn.setStyleSheet(""" + QPushButton { + background-color: #00008B; + color: white; + border: 1px solid black; + padding: 10px 20px; + font-weight: bold; + } + QPushButton:pressed { + background-color: #000066; + } + """) + back_btn.clicked.connect(go_home) + main_layout.addWidget(back_btn, alignment=Qt.AlignCenter) + + def _labeled_field(self, label_text, line_edit): + container = QWidget() + layout = QHBoxLayout(container) + layout.setContentsMargins(0, 0, 0, 0) + + label = QLabel(label_text) + label.setFont(QFont("Times New Roman", 11)) + label.setFixedWidth(160) + + line_edit.setFixedWidth(300) + line_edit.setFixedHeight(30) + line_edit.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + + layout.addWidget(label) + layout.addWidget(line_edit) + return container + + def add_row(self): + row = self.table.rowCount() + self.table.insertRow(row) + for col in range(self.table.columnCount()): + item = QTableWidgetItem() + bg_color = "white" if row % 2 == 0 else "#E6F0FF" + item.setBackground(QBrush(QColor(bg_color))) + self.table.setItem(row, col, item) + + def eventFilter(self, obj, event): + if obj == self.table and event.type() == QEvent.KeyPress: + if event.key() in (Qt.Key_Return, Qt.Key_Enter): + row = self.table.currentRow() + col = self.table.currentColumn() + + # Move to next column + if col < self.table.columnCount() - 1: + self.table.setCurrentCell(row, col + 1) + else: + # Last column → move to next row + if row == self.table.rowCount() - 1: + self.add_row() + self.table.setCurrentCell(row + 1, 0) + return True + return super().eventFilter(obj, event) + + def go_back(self): + self.stacked_widget.setCurrentIndex(5) +# ---------------- Application Entry ---------------- # +if __name__ == "__main__": + app = QApplication(sys.argv) + stacked_widget = QStackedWidget() + login_screen = LoginScreen(stacked_widget) + main_screen = MainScreen() + stacked_widget.addWidget(login_screen) + stacked_widget.addWidget(main_screen) + stacked_widget.setCurrentIndex(0) + global_stacked_widget = stacked_widget + global_main_screen = main_screen + stacked_widget.show() + sys.exit(app.exec_()) + diff --git a/styles.py b/styles.py deleted file mode 100644 index 5b3d6e4..0000000 --- a/styles.py +++ /dev/null @@ -1,15 +0,0 @@ -import os - -def load_login_qss(): - """ - Load and return the contents of 'login.qss', - located in the same directory as this script. - """ - base = os.path.dirname(os.path.abspath(__file__)) - path = os.path.join(base, "login.qss") - try: - with open(path, encoding="utf-8") as f: - return f.read() - except OSError as e: - print(f"Error loading login.qss: {e}") - return "" diff --git a/test.py b/test.py deleted file mode 100644 index 3a1daf9..0000000 --- a/test.py +++ /dev/null @@ -1,915 +0,0 @@ - -import sys -import requests - -from PyQt6.QtWidgets import ( - QApplication, QWidget, QVBoxLayout, QLabel, QLineEdit, QPushButton, - QHBoxLayout, QStackedWidget, QCheckBox, QTextEdit -) -from PyQt6.QtGui import QPixmap, QFont -from PyQt6.QtCore import Qt, QDateTime, QDate - -# --- Login Screen --- -class LoginScreen(QWidget): - def __init__(self, stacked): - super().__init__() - self.stacked = stacked - self.init_ui() - - def init_ui(self): - layout = QVBoxLayout() - logo = QLabel() - logo.setPixmap(QPixmap( - r'C:\Users\Administrator\Downloads\sfg printing\backup from airline device\SRIMATHI\cri_logo.png.png' - ).scaled(100, 100, Qt.AspectRatioMode.KeepAspectRatio)) - logo.setAlignment(Qt.AlignmentFlag.AlignCenter) - layout.addWidget(logo) - - title_label = QLabel("Login Screen") - title_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - title_label.setStyleSheet("font-size: 18px; font-weight: bold; color: #002244; margin-bottom: 10px;") - layout.addWidget(title_label) - - self.username = QLineEdit() - self.username.setPlaceholderText("Username") - layout.addWidget(self.username) - pw_layout = QHBoxLayout() - - self.password = QLineEdit() - self.password.setPlaceholderText("Password") - self.password.setEchoMode(QLineEdit.EchoMode.Password) - pw_layout.addWidget(self.password) - - self.eye_toggle = QCheckBox("Show") - self.eye_toggle.toggled.connect(lambda checked: self.password.setEchoMode( - QLineEdit.EchoMode.Normal if checked else QLineEdit.EchoMode.Password)) - pw_layout.addWidget(self.eye_toggle) - layout.addLayout(pw_layout) - - self.username.returnPressed.connect(self.password.setFocus) - self.password.returnPressed.connect(self.do_login) - login_btn = QPushButton("Login") - login_btn.clicked.connect(self.do_login) - layout.addWidget(login_btn) - - logo2 = QLabel() - logo2.setPixmap(QPixmap( - r'C:\Users\Administrator\Downloads\sfg printing\backup from airline device\SRIMATHI\cri_farm_banner.jpg' - ).scaled(200, 200, Qt.AspectRatioMode.KeepAspectRatio)) - logo2.setAlignment(Qt.AlignmentFlag.AlignCenter) - layout.addWidget(logo2) - self.setLayout(layout) - - def do_login(self): - if self.username.text() and self.password.text(): - self.stacked.setCurrentIndex(1) - -# --- Module Selector --- -class SelectorScreen(QWidget): - - def __init__(self, stacked, bot_screen): - super().__init__() - self.stacked = stacked - self.bot_screen = bot_screen - self.init_ui() - self.load_module_list() - - def init_ui(self): - self.layout = QVBoxLayout() - title_label = QLabel("Selector Screen") - title_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - title_label.setStyleSheet("font-size: 18px; font-weight: bold; color: #002244;") - self.layout.addWidget(title_label) - - date_label = QLabel(QDate.currentDate().toString("dddd, MMMM d, yyyy")) - date_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - date_label.setFont(QFont("Arial", 14, QFont.Weight.Bold)) - self.layout.addWidget(date_label) - - self.button_container = QVBoxLayout() - self.layout.addLayout(self.button_container) - self.setLayout(self.layout) - - def load_module_list(self): - url = 'https://pds.iotsignin.com/api/get/module-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": "Module List" - } - try: - response = requests.get(url, headers=headers, timeout=10) - if response.status_code == 200: - data = response.json() - for module in data.get("status_description", []): - self.add_module_button(module) - else: - self.show_error(f"Failed to load modules: {response.status_code}") - except Exception as e: - self.show_error(str(e)) - - def add_module_button(self, module_name): - btn = QPushButton(module_name) - btn.setStyleSheet( - "padding: 10px; background-color: #c2185b; color: white; font-size: 14px; border-radius: 8px;") - btn.clicked.connect(lambda _, name=module_name: self.goto_bot_screen(name)) - self.button_container.addWidget(btn) - - def show_error(self, msg): - error = QLabel(f"❌ {msg}") - error.setStyleSheet("color: red;") - self.layout.addWidget(error) - - def goto_bot_screen(self, module_name): - self.bot_screen.set_module_name(module_name) - self.stacked.setCurrentIndex(2) - -# --- BotScreen: full chat-driven selection --- -class BotScreen(QWidget): - - def __init__(self, stacked): - super().__init__() - self.stacked = stacked - self.module_name = "" - self.charts = [] - self.plants = [] - self.lines = [] - self.filters = [] - self.invoice_types = [] - - 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.awaiting_invoice_choice = False - - self.selected_chart = None - self.selected_plant = None - self.selected_line = None - self.selected_order = None - self.selected_filter = None - self.selected_invoice_type = None - self.init_ui() - - 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", - "invoice": "invoice type", - } - # Ensure name extraction works for both simple str and dict/list cases. - lines = [ - f"{i + 1}. {item_name['name'] if isinstance(item_name, dict) and 'name' in item_name else str(item_name)}" - for i, item_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() - # --- Chart selection --- - 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 - - # --- Plant selection --- - 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}") - - # Normalize chart name and check for invoice / invoice quantity charts explicitly - selected_chart_name = (self.selected_chart or "").strip().lower() - - if selected_chart_name == "invoice": - # Fetch invoice types for regular invoice dashboard - self.fetch_and_display_invoice_types() - elif selected_chart_name == "invoice quantity": - # For invoice quantity, also fetch invoice types first (if needed) - self.fetch_and_display_invoice_types() - # Alternatively: if invoice type is fixed or selection is not needed, you can - # proceed to date filter directly by calling - # self.fetch_and_display_invoice_filters() here if your flow allows - else: - # For other charts, fetch lines normally - 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 - - # --- Line selection --- - 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 "" - - # 🚩 Only for Chart 4 (Production Order Count) - if chart_normalized == "production order count": - print(f"[DEBUG] self.selected_chart (raw): '{self.selected_chart}'") - self.awaiting_order_input = True # Set state for next prompt - 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 - - # --- Production order entry (Chart 4 only) --- - 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 MODULE 2, chart 2 then this part will execute here--------- - - if self.awaiting_invoice_choice: - if msg.isdigit(): - idx = int(msg) - 1 - if 0 <= idx < len(self.invoice_types): - self.selected_invoice_type = self.invoice_types[idx] - self.awaiting_invoice_choice = False - self.chat_area.append( - f"Bot: ✅ You selected invoice type: {self.selected_invoice_type}" - ) - - # Normalize selected chart to distinguish between Invoice and Invoice Quantity dashboards - selected_chart_name = (self.selected_chart or "").strip().lower() - - if selected_chart_name == "invoice": - # For "Invoice" chart, fetch invoice filters (date filters) - self.fetch_and_display_invoice_filters() - elif selected_chart_name == "invoice quantity": - # For "Invoice Quantity" chart, fetch invoice filters as well - self.fetch_and_display_invoice_filters() - else: - # If future charts, handle accordingly or fallback - self.chat_area.append( - "Bot: ❌ Unrecognized invoice chart selected." - ) - else: - self.chat_area.append( - f"Bot: ❌ Please select a number from 1 to {len(self.invoice_types)}." - ) - else: - self.chat_area.append( - f"Bot: ❌ Please enter a valid number (1–{len(self.invoice_types)})." - ) - return - - # --- Date filter selection --- - 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}") - # Show current date/time - 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 or "").strip().lower() - if "invoice" in selected: - # NEW: Handle invoice dashboard: no line name needed - self.fetch_and_display_invoice_production_count() - elif 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() - elif selected == "invoice": - self.fetch_and_display_invoice_production_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 - - # handle user input - 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): - selected_chart_lower = (self.selected_chart or "").strip().lower() - - # URL remains the same unless invoice filters come from a different path - url = 'https://pds.iotsignin.com/api/get/module-line-filter-name/data' - - # Base headers - headers = { - "Authorization": self.API_TOKEN - } - - # Check if it’s an invoice chart by partial match - if "invoice" in selected_chart_lower: - # INVOICE FLOW: Skip line name, use invoice-type - url = 'https://pds.iotsignin.com/api/get/module-invoice-filter-name/data' - headers.update({ - "module-name": self.module_name, - "chart-name": self.selected_chart, - "plant-name": self.selected_plant, - "invoice-type": self.selected_invoice_type - }) - else: - # PRODUCTION FLOW: Use line name as before - headers.update({ - "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) - self.fetch_and_display_invoice_production_count() - else: - self.chat_area.append("Bot: ❌ No date filters found for your selection.") - else: - self.chat_area.append(f"Bot: ❌ API Error: {data.get('status_description', 'Unknown error')}") - 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=" - -#CHART -2 PRODUCTION HOURLY COUNT - def fetch_and_display_hourly_count(self): - url = "https://pds.iotsignin.com/api/get/module-filter-value/data" - - # 👇 Pass everything only via HEADERS, not params - 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", # ✅ Chart name as header - "plant-name": self.selected_plant, # ✅ From previous input - "line-name": self.selected_line, # ✅ From previous input - "filter-name": self.selected_filter # ✅ e.g., "Today", "Yesterday" - } - - try: - response = requests.get(url, headers=headers, timeout=10) - - # ✅ Print raw HTTP response to console - print("📡 [DEBUG] HTTP Status:", response.status_code) - print("📡 [DEBUG] Raw API Response:\n", response.text) - print("-" * 50) # Divider in logs - - # ✅ Display raw response in chat area - self.chat_area.append(f"Raw API response:

{response.text}
") - - # 🔒 Handle empty API response - if not response.text.strip(): - print("[DEBUG] ❌ Empty response received.") - self.chat_area.append("Bot: ❌ API returned no data.") - return - - # 🔒 Try parsing JSON - 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}") - - # 🔒 Specific error handling - 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}") - - - # PRODUCTION LINE STOP DATA - def fetch_and_display_production_line_stop_count(self): - # You must have these set by prior steps: - # self.selected_plant, self.selected_line, self.selected_filter - 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 # Example: "Today" - } - 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}") - - # CHART-4 PRODUCTION ORDER COUNT - 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 raw API response in Python console for debugging - print("📡 [DEBUG] HTTP Status:", response.status_code) - print("📡 [DEBUG] Raw API Response:\n", response.text) - print("-" * 50) - - # Display raw API response in chat for traceability - 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}:") - # Handle 'Today' and 'Yesterday' (dict) - if isinstance(dataset.get("data"), dict): - for timeslot, count in dataset.get("data", {}).items(): - self.chat_area.append(f"{timeslot}: {count}
") - # Handle 'This Week', 'This Month' (list) - 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): - self.stacked.setCurrentIndex(1) - - - # MODULE -2 INVOICE DASHBOARD - #CHART-1 INVOICE - - #to fetch the invoice types - - def fetch_and_display_invoice_types(self): - url = "https://pds.iotsignin.com/api/get/module-invoice-type/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=", - "invoice-type-list": "Invoice Type List" - } - try: - response = requests.get(url, headers=headers, timeout=10) - - if response.status_code == 200: - data = response.json() - if data.get("status_code") == "SUCCESS": - self.invoice_types = data.get("status_description", []) - if self.invoice_types: - self.awaiting_invoice_choice = True - self.show_choice_menu("invoice", self.invoice_types) - else: - self.chat_area.append("Bot: ❌ No invoice types found.") - else: - error_msg = data.get("status_description", "Unknown error") - self.chat_area.append(f"Bot: ❌ Failed to get invoice types: {error_msg}") - else: - self.chat_area.append(f"Bot: ❌ HTTP Error {response.status_code} while fetching invoice types.") - - except Exception as e: - self.chat_area.append(f"Bot: ❌ Error getting invoice types: {str(e)}") - -# To fetch date filter - - def fetch_and_display_invoice_filters(self): - url = "http://pds.iotsignin.com/api/get/module-invoice-filter/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=", - "filter-name": "Filter List" - } - - try: - response = requests.get(url, headers=headers, timeout=10) - if response.status_code != 200: - self.chat_area.append(f"Bot: ❌ HTTP Error {response.status_code} when fetching filters.") - return - - 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 invoice filters found.") - else: - self.chat_area.append(f"Bot: ❌ API Error: {data.get('status_description', 'Unknown error')}") - - except Exception as e: - self.chat_area.append(f"Bot: ❌ Exception when fetching invoice filters: {str(e)}") - - # to fetch production count - def fetch_and_display_invoice_production_count(self): - url = "https://pds.iotsignin.com/api/get/module-invoice-count/data" - headers = { - "Authorization": self.API_TOKEN, - "plant-name": self.selected_plant, - "invoice-name": self.selected_invoice_type, - "filter-name": self.selected_filter - } - - try: - response = requests.get(url, headers=headers, timeout=10) - - # Show raw API response for debugging - 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} fetching invoice count.") - return - - data = response.json() - status = data.get("status_code") - if status != "SUCCESS": - error_msg = data.get("status_description", "Unknown error") - self.chat_area.append(f"Bot: ❌ API Error: {error_msg}") - return - - labels = data.get("labels", []) - datasets = data.get("datasets", []) - - # Show current date/time - now = QDateTime.currentDateTime() - fmt_date = now.toString("dddd, MMMM d, yyyy, h:mm AP") - self.chat_area.append(f"Current date: {fmt_date}
") - - # Multiple datasets means 'All Invoice' selection - if len(datasets) > 1: - for dataset in datasets: - label = dataset.get("label", "Dataset") - values = dataset.get("data", []) - self.chat_area.append(f"{label}:
") - for lbl, val in zip(labels, values): - self.chat_area.append(f"{lbl}: {val}
") - self.chat_area.append("
") # spacing between datasets - # Single dataset for specific invoice type - elif len(datasets) == 1: - dataset = datasets[0] - label = dataset.get("label", "Invoices") - values = dataset.get("data", []) - self.chat_area.append(f"{label}:
") - for lbl, val in zip(labels, values): - self.chat_area.append(f"{lbl}: {val}
") - else: - self.chat_area.append("Bot: ❌ No data found in API response.") - - except Exception as e: - self.chat_area.append(f"Bot: ❌ Exception fetching invoice count: {str(e)}") - - - def fetch_and_display_invoice_quantity(self): - url = "https://pds.iotsignin.com/api/get/module-invoice-quantity/data" - headers = { - "Authorization": self.API_TOKEN, - "plant-name": self.selected_plant, - "invoice-name": self.selected_invoice_type, - "filter-name": self.selected_filter - } - try: - response = requests.get(url, headers=headers, timeout=10) - - # Show raw response for debugging - 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} fetching invoice quantity.") - return - - data = response.json() - if data.get("status_code") != "SUCCESS": - error_msg = data.get("status_description", "Unknown error") - self.chat_area.append(f"Bot: ❌ API Error: {error_msg}") - return - - labels = data.get("labels", []) - datasets = data.get("datasets", []) - - # Display current date/time in desired format - now = QDateTime.currentDateTime() - fmt_date = now.toString("dddd, MMMM d, yyyy, h:mm AP") - self.chat_area.append(f"Current date: {fmt_date}
") - - if len(datasets) > 1: - # Multiple datasets (e.g. All Invoice) - for dataset in datasets: - label = dataset.get("label", "Data") - values = dataset.get("data", []) - self.chat_area.append(f"{label}:
") - for lbl, val in zip(labels, values): - self.chat_area.append(f"{lbl}: {val}
") - self.chat_area.append("
") - elif len(datasets) == 1: - # Single dataset (single invoice type) - dataset = datasets[0] - label = dataset.get("label", "Invoice Quantity") - values = dataset.get("data", []) - self.chat_area.append(f"{label}:
") - for lbl, val in zip(labels, values): - self.chat_area.append(f"{lbl}: {val}
") - else: - self.chat_area.append("Bot: ❌ No invoice quantity data found.") - - except Exception as e: - self.chat_area.append(f"Bot: ❌ Exception fetching invoice quantity: {str(e)}") - - -def main(): - app = QApplication(sys.argv) - stacked = QStackedWidget() - bot_screen = BotScreen(stacked) - selector_screen = SelectorScreen(stacked, bot_screen) - stacked.addWidget(LoginScreen(stacked)) - stacked.addWidget(selector_screen) - stacked.addWidget(bot_screen) - stacked.setFixedSize(420, 640) - stacked.show() , - - - sys.exit(app.exec()) - -if __name__ == '__main__': - main() diff --git a/web.png b/web.png deleted file mode 100644 index 270c2969463c3f0488a3ef052642d5612c646231..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5291 zcmcImWl$VSmmPGlfgvFbHaHP9K#%|f4|jKhhTsgronT?mAxQAx?(P!800RVf3osBQ zkl;ZAfiJJV&3o_Ls{OH5ySJ-ORiEyMMXt@@7^5PZbNsL4wi`fB0Kfn+I9SX8Y;r6da;)240Nt-+ zVgYdegx>+g$HgPS!p8ZvstN{R;b3Fq0`czxadH0KfCa$D!6nB7;@@H7y~`}AY2gw~ z0O8Y$PoQ9VA|NHL{oK;k%{?)geleA1|RWkz5;PyBj0kiLle+p+KvA5MJxhx)3*q3{PdR6`T`JGv;2! zH|zr>FZk3u4>0kIor>x{|T@1<&Ds}o<;k`IWyYb{_rKo&d z`kdQ#iTx{&8!_dVMI!v9Yzn(RvauJd@QOH4ER^oGYlswqEUD?V?qpLvX;KoXYkqL8 zC|2&kf|RWdWzkjB!idBK`g5oq#oU*~lP5TYl@*8@p?cpQRZe*cyB@lXF`u4^rz*2>M2f}I$}5|6(8p=G)6k~s7$l{lsfwEB z3u+#$i@KrpR-7q2CEYjJi}O2GvHWX=Vhq1cEWsD$=7J~sDK}TzRa!>0Jtx=IaiQ_k z45jr92gfII^~@KbxoKRPKV)S71GK&_Pn{@%KrBCDBd6%lt0G}1VEjJ4Wb<1t&#W(^ z&O?xCM?QL1)uTdZ!LI-aJtAp)86tZ`qNA=!?5#CtDi6aH9*gfY6@2AhB*YDFU|h7a zHYP+ll&5*0_ucKx?olAC-J)wB9XUH|00sN>q^R%?v-LbOVfka8rPRU=K0KG%bWv3| zXW_FZq^psQBneu*pvUW24@oMb8qdv1FFCLj&d-vE>XFWY^A>o6X@+HI+)PG_PkYqs zO5!rLeC4Ts`j*G1`vj{;YD?oW#gT%$G|qOjI2`*A7{9rQ5*|v;e%^$G1Znz3s<58C zaZeBmGk0d)tr5QKhY-kkx*w8C2@!?8$k=4OLQzh#&u6aPJ$fs_#8c1AXYexJqm+!!3qO8d#h2qk9VWIX0^jb_$*12vO8^P z#LcJ#lw7k_Feuh{Iw^-*wFUSMEs+MQVQoem&0Z9Yr4uK?$trWL3L$*G6Lpe9 zTUf2vO`ChG*GDRc5yn~*Fm&ATS&hd+dhewadd`|&AflrW=iSm3k-=!=8>Uxtp397r zR`}VJt*IWCXY`zRg!jebTCxHy1EYs40oB|aM)x8nwTXsO8>zN~oDN?&yz_jDX>J?| zJMfFE^wBOvljqO8UCATnQ$BE0SXW-e0v1;7NVfd9rjm-ToD@^h(j3zYC=-&XKxB|k znTJ3!yr=e#G!@CHYg(kbNzrQOfRp-F!%Uj+%xCWMSZ~PkqLQ+@P0;=EOQ!o%-wj+J z7Aaxwm){fMJL)8zdh}pHm1}X**K=UM29gU76p5f_QD(I?p6U?BCNtn*i`+~m7ylvG z?}OovzBG`2Rak3AM8<7rN=QOgzw<~rTBf#l@|bWg?lClnILOSYueN-UsP?LD!0c%L zEFk@3-ozjFr)kxq(&Z0QvZGL($gk^-zG93g9j#nZIJFH`yYZ_r4&{>|K(FniPnsJ2 z1nEYEj=@XDYqLkb1E!e>5z)v6*c-}MXcFFR?lE-e?w&O_=@AJNI3q9KXhw?E~EMi^BtEG7AprHg{8s7Gz~ySJV%+t6**>Uvi*di&lT*#Yb-93%37S( zHXI*T1;urBKMo!5ILJ>*F!x@FwK1$n3{Kt0hs-yr!9z8F{t@;?fPCzlLeQ4ONKj#l z~@XM8Q@ZWddDEO(iw#HIyYd>*K^Z;h^SsW}7}3&JMXpD*IU~Wit3z>nD5!Ni%WeL6w>Iv5;-ZS&@D@QIB)OFrD_lR4n-GXBE4&$U1mkev{U247?@0B!Wst zuOP48Hdu^cdJ74STU*!NIp40bVbZ@|jH7j30oq7Y7;x!a9sYx7x_F_s=t*jE?g$T|cZ(|A)BbB9 z&;foPhL_0mfX{+L?f&-o1kUU%{tdBI0nOcXssMVZx~dSU`mTUUITZa`q4qs5ar?o? zFn@`OR|OQZk)tB?wD|9idkr7%TJ|>|r*MvH?|c-r;SI zLKzM-H4A<6W4!hfpIad+a}D%jVTEedG=GcU(`_W#4@j6vQa#GGICc-+1wz6bW!D<#cUgq#7LbjSD<|q}jX-6Q2Nm}GrkJ9ZT3KtWop~lYz zEGzUMht`ei>>ppeY@7D^dL4}wH@Ul>TfCq(AS#{bwWgm3R&D=?>DtrWu0Q6SeQwoA z2@O}&CEnt3*Z{yrxav0@K;5>V*_Uh57WV%AWrX-7r2`eCv{kvJHy!dbH0+<|*dMBR zjvIM?>Vy0J&c)61Z?|y^kWni7MQZNu?P zx8nqvZZ=TrGZlVdb-(O1-=V}29AGBXb)gAGXzPAN#O|(`4Vvl}0I&AB1;lsBn*E2z zz?-)7ytgw%6y;>d?EDgq{Z-tVxLGO$FWvx+6Cs|ot)nAf1I@~~_sPL+H%)9Nz|?iv zm`{a+`c#IN<~%Aw4O3a^L%n@B(D$&q1)mFQ#i`3n+awn{?e(;{04%vHInNUoU?Rv- z=pFE=QT7FIdmo?Bvtim-1z;r5rEpSCzD2OiTV1yZ`QEhbG~@(7>G^nIrFc{P=F#EN z@Ko8ee(U@!&Nb3GffZe;(lM&={SsC$bW+8Sbh?nXzpt}aYa6L}Xv++BgRNR(?>wpJ zw|Ak@*HXsiw0t+^xwmKXhK%+Td?OA2;PDr%(v#<}(Ra5u!67riVO6uD zhKt22y^(x7OyjV`0;bjZeo@0%Kg(^s<~_wUtv9cZV@9b7ejh z`ZDf6>WL9X-l=myku}B2FIuD=7kP7?VM{L0$)1{~vGc$4#dW$?N=m^s&EuVEvVOA* zg=|a4dzebZB$n?)8mMjg>agTwaS-gWR_zfEip-IzXHfz9aSFX`*Bj_3+pkN8^#*1& z^g0OfGDyPR`q$75bWXsO>Hs@n<_QB&Mwm27?eL}aiox+#1K^v9wSiC^;=PY($h?(j zBu1dc-EB}|ZiqPTe*Frj3QY^u+9Ph(oo%jr79i2x$$%JB1JX4cNxY5OYaWtqi`0sV zLSGOyPbfGp23K@@8rgnmfw2Ysq>?lYl{_r+7WNQwBzZ6-_0jr#cr6BfI;&X%&?5KpWAEZFN1uV`?X zcs%u)>Di)EXiwV}O8Ji%pB(yT3MylLR{M0a+}*sCl>xMzf4(sTqv3|^8}lV2b|y<7 z18)VcQ6qtLpaJn@lwVrw>mhuR^t2B%YS8Ck`PT;l!z)wu-pZ%Z{2b*aNY);~aPW)x z6gxlKW{qZBT1Q17yb@lPk!W<^_KT{{NW%;OOt3g0H!I|{?$0tiF;GjbnNwAns;~Pb zmFjpa+|NB|IdQbxGs~5$ZTAA3tmH4J@!NBBk$Zdx%hq#n7*FnY2#Pi=58}v844DvY z(zRu&S@n#I%_pP^W`k#wnfmT59@i~#9(Arcy$?HT<^D)sv;bF(NaP4$&bhJ#F!ir2 zj#PEyq1e1RZv+Q$1NZ9$mzwU2{6orfE!7Y+cH= z?6JgqTp-?T2-6{KFXW#1xeNKrkh#Mvg#36H+o?~4CZ=NxdcV9zdQ&E2Q=PTYbD-#z zm4m+k_8X&!=|k`yz_B%{F>V zM5K|@(yDAK`}I^puC3@_UU5`4j)pM$dd3sbHWE1PkFL8g3_jHgj47g+F2%nWzwP^n>uJaib-|-W>}vI?jh1V1?$~ z9njC)E{P)e_}S0W5`Rt@(O8M|HZzDv!u)9{Jw$QGUp&J|9%}a>dqI3T{_zE)BcQ$D z8=JFbC=L2E>FHobK&)5cRDDTsxyv}+XIiM}2*ySdLGQd9PW&u8#O|h;G{-Bpn1^rH zemkn|@T^;{#vu~(-~oJDZG372TAXM-(=^^uQk3szUB;Ye(SXf$m-MPJyhr?Uc?qEN zjPY8#dwzbFH@DIjs}j2#s|EmMzWJwh{oPQ1a-kSv9^~d3L{GV}(81KJOSFI8XV9v8 z$)i-}j*2|X+Wph-ll*xKON=*KJ#9XGQWf3;HCZhytG(svjA8Yx?4v&h