"""PyQt6 host for the AI desktop dashboard: the no-AI Bridge plus one slot for the AI contract. Copy next to the no-AI bridge.py and bot_runner.py, with server_stub.py and bands.py. Run it with: python dashboard/ai_bridge.py The page sends each AI request (templates/contract.md: runs, review rows, the approve gate) as ai_request(id, method, path, body) and gets one `ai_response` envelope back. The review backend answers it in Python, so the approve gate holds whatever the page does. """ import json, sys, threading from PyQt6.QtCore import QUrl, pyqtSlot from PyQt6.QtWidgets import QApplication, QMainWindow from PyQt6.QtWebEngineWidgets import QWebEngineView from PyQt6.QtWebChannel import QWebChannel import bridge import server_stub as review # sample data. Once the project has its own review backend, import that (same dispatch()). class AiBridge(bridge.Bridge): @pyqtSlot(int, str, str, str) def ai_request(self, req_id, method, path, body): # ponytail: answered on the GUI thread; move to a worker if the backend gets slow. status, payload = review.dispatch(method, path, json.loads(body or "{}")) self.send_to_js("ai_response", {"id": req_id, "status": status, "body": payload}) if __name__ == "__main__": threading.Thread(target=review.simulate_extraction, daemon=True).start() # sample data only: delete with server_stub app = QApplication(sys.argv) win, view = QMainWindow(), QWebEngineView() host, channel = AiBridge(view), QWebChannel() channel.registerObject("pyBridge", host) view.page().setWebChannel(channel) view.setUrl(QUrl.fromLocalFile(str(bridge.HTML_PATH))) win.setCentralWidget(view); win.resize(1280, 820); win.setWindowTitle("{{APP_NAME}}"); win.show() sys.exit(app.exec())