Files
Rpa_Skills/rpa-dashboard-no-ai/templates/bridge.py
T
VasanthandClaude Opus 5.5 162edd41df No-AI desktop path: PyQt bot wired to the page
- templates/bot_runner.py (new, no Qt): runs the bot as a child process
  (unbuffered, UTF-8), tails its output, keeps raw lines in bot_raw.log,
  and turns lines into step/log/status events. Step markers and friendly
  rules use specific substrings, first match wins; unmatched error lines
  are always shown, other unmatched lines hidden. Dry run:
  python dashboard/bot_runner.py prints the events the page would get.
- bridge.py: delegates to bot_runner, one run at a time, restores the
  saved dark mode on every page load (fixes sample-data mode in PyQt),
  adds load_latest_report / download_report slots the page calls.
- SKILL.md: skip the stack question for a PyQt project; desktop wiring
  steps from the bot's real output; verify steps for the host.
- tests: assert-based self-check for the line logic and run loop; Seam 1
  run replay (status dot, Run button, steps, log) and wired-page mode;
  fixture b-pyqt-bot and its Seam 2 eval (all 14 checks pass).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 14:28:51 +05:30

73 lines
2.6 KiB
Python

"""Minimal PyQt6 host for dashboard.html (desktop mode). Pattern from Tebra main_window.py.
The bot runs as a child process and this window shows its progress. The bot wiring
(command, step markers, friendly rules) lives in bot_runner.py, next to this file.
Run it with: python dashboard/bridge.py
"""
import json, sys, threading
from pathlib import Path
from PyQt6.QtCore import QObject, pyqtSlot, pyqtSignal, QUrl, QSettings
from PyQt6.QtWidgets import QApplication, QMainWindow
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWebChannel import QWebChannel
import bot_runner
HTML_PATH = Path(__file__).with_name("dashboard.html")
def settings():
return QSettings("{{ORG}}", "{{APP_NAME}}")
class Bridge(QObject):
_emit = pyqtSignal(str) # thread-safe hop to the GUI thread
def __init__(self, view):
super().__init__()
self.view, self._one_run = view, threading.Lock()
self._emit.connect(lambda js: self.view.page().runJavaScript(f"window.__pyMsg({js})"))
# Restore the saved dark-mode choice on every page load, sample-data mode included.
view.loadFinished.connect(lambda _ok: self.send_to_js("apply_dark", {"on": settings().value("dark", False, bool)}))
def send_to_js(self, event, data):
self._emit.emit(json.dumps(json.dumps({"event": event, "data": data})))
@pyqtSlot()
def load_dashboard(self):
self.send_to_js("dashboard_data", {"batches": []}) # TODO: read from DB on a worker thread
@pyqtSlot()
def load_latest_report(self):
self.send_to_js("report_data", {"rows": []}) # TODO: read the latest report rows
@pyqtSlot()
def download_report(self):
pass # TODO: export the report rows to .xlsx
@pyqtSlot(bool)
def set_dark(self, on):
settings().setValue("dark", on)
@pyqtSlot()
def start_automation(self):
if self._one_run.acquire(blocking=False): # a second click can't start a second bot
threading.Thread(target=self._run, daemon=True).start()
def _run(self):
try:
bot_runner.run(self.send_to_js)
finally:
self._one_run.release()
if __name__ == "__main__":
app = QApplication(sys.argv)
win, view = QMainWindow(), QWebEngineView()
bridge, channel = Bridge(view), QWebChannel()
channel.registerObject("pyBridge", bridge)
view.page().setWebChannel(channel)
view.setUrl(QUrl.fromLocalFile(str(HTML_PATH)))
win.setCentralWidget(view); win.resize(1280, 820); win.setWindowTitle("{{APP_NAME}}"); win.show()
sys.exit(app.exec())