PICKER = '''class ReviewAssistantPicker(QDialog): recipientsLoaded = Signal(object) recipientValidated = Signal(object) def __init__(self, account, parent=None): super().__init__(parent) self.account = str(account) self.recipient = None self._serial = 0 self._offset = 0 self._closed = False self.setWindowTitle("选择医疗助理私人微信") self.resize(660, 500) layout = QVBoxLayout(self) note = QLabel("仅列出当前企微账号已有的私人微信单聊。请核对联系人及编号尾号,同名联系人不会自动合并。") note.setWordWrap(True) layout.addWidget(note) self.query = QLineEdit() self.query.setPlaceholderText("搜索联系人昵称") self.query.setMaxLength(100) layout.addWidget(self.query) self.contacts = QListWidget() layout.addWidget(self.contacts, 1) self.status = QLabel("正在只读加载联系人…") self.status.setWordWrap(True) layout.addWidget(self.status) buttons = QHBoxLayout() self.previous = QPushButton("上一页") self.next = QPushButton("下一页") self.choose = QPushButton("选择此联系人") self.choose.setEnabled(False) cancel = QPushButton("取消") for widget in (self.previous, self.next, self.choose, cancel): buttons.addWidget(widget) layout.addLayout(buttons) self._search_timer = QTimer(self) self._search_timer.setSingleShot(True) self._search_timer.setInterval(250) self._search_timer.timeout.connect(lambda: self._load(reset=True)) self.query.textChanged.connect(lambda _text: self._search_timer.start()) self.contacts.currentItemChanged.connect(lambda item, _old: self.choose.setEnabled(item is not None)) self.previous.clicked.connect(lambda: self._page(-50)) self.next.clicked.connect(lambda: self._page(50)) self.choose.clicked.connect(self._choose) cancel.clicked.connect(self.reject) self.finished.connect(self._finished) self.recipientsLoaded.connect(self._loaded) self.recipientValidated.connect(self._validated) QTimer.singleShot(0, self, self._load) def _finished(self, _result): self._closed = True self._search_timer.stop() def _page(self, delta): self._offset = max(0, self._offset + delta) self._load() def _load(self, *, reset=False): if self._closed: return if reset: self._offset = 0 self._serial += 1 serial, query, offset = self._serial, self.query.text(), self._offset self.choose.setEnabled(False) self.previous.setEnabled(False) self.next.setEnabled(False) self.contacts.clear() self.status.setText("正在只读加载联系人…") def work(): try: from review_assistant_contacts import list_recipients result = list_recipients(self.account, query, 50, offset) except Exception as exc: result = {"ready": False, "reason": str(exc), "items": []} result["_serial"] = serial try: self.recipientsLoaded.emit(result) except RuntimeError: pass threading.Thread(target=work, name="review-assistant-contacts", daemon=True).start() def _loaded(self, result): if self._closed or result.get("_serial") != self._serial: return if not result.get("ready") or str(result.get("account_id")) != self.account: self.status.setText(str(result.get("reason") or "当前企微账号已变化,请重新选择")) return for row in result.get("items", []): item = QListWidgetItem(f"{row['name']} · {row['identityLabel']}\\n最近联系:{row.get('lastTime') or '—'}") item.setData(Qt.ItemDataRole.UserRole, dict(row)) self.contacts.addItem(item) self.previous.setEnabled(self._offset > 0) self.next.setEnabled(bool(result.get("has_more"))) total = int(result.get("total") or 0) self.status.setText(f"共 {total} 个匹配的私人微信单聊;请选择并核对收件人。" if total else "没有匹配的已有私人微信单聊。请先与助理建立会话并刷新消息库。") def _choose(self): item = self.contacts.currentItem() if item is None or self._closed: return row = item.data(Qt.ItemDataRole.UserRole) self.choose.setEnabled(False) self.query.setEnabled(False) self.contacts.setEnabled(False) self.previous.setEnabled(False) self.next.setEnabled(False) self.status.setText("正在复核当前企微账号和所选联系人…") self._search_timer.stop() self._serial += 1 def work(): try: from review_assistant_contacts import validate_recipient result = validate_recipient(self.account, str(row.get("id") or "")) except Exception as exc: result = {"ok": False, "reason": str(exc)} try: self.recipientValidated.emit(result) except RuntimeError: pass threading.Thread(target=work, name="review-assistant-recipient-check", daemon=True).start() def _validated(self, result): if self._closed: return if result.get("ok") and result.get("account_id") == self.account: self.recipient = result["recipient"] self.accept() else: self.status.setText(str(result.get("reason") or "联系人核验失败,请重新读取当前账号")) self.query.setEnabled(True) self.contacts.setEnabled(True) ''' BRIDGE = ''' @Slot() def refreshReviewAssistant(self) -> None: self.host._refresh_review_assistant() @Slot() def chooseReviewAssistant(self) -> None: self.host._choose_review_assistant() @Slot(str, result=str) def setReviewAssistantEnabled(self, payload: str) -> str: try: value = json.loads(payload) if not isinstance(value, dict) or type(value.get("enabled")) is not bool: raise ValueError("提醒开关格式无效") result = self.host._save_review_assistant( str(value.get("expected_account") or ""), enabled=value["enabled"]) except (ValueError, TypeError) as exc: result = {"ok": False, "message": str(exc)} return json.dumps(result, ensure_ascii=False) ''' METHODS = ''' def _review_assistant_available(self) -> tuple[bool, str]: if "PROTOCOL" not in globals(): return False, "当前版本不支持协议审核提醒" if self.runtime_settings.get("reply_transport", "visual") != "protocol": return False, "请切换到协议模式后配置医疗助理审核提醒" return True, "" def _review_assistant_console_state(self) -> dict: from review_assistant_contacts import notification_preview available, reason = self._review_assistant_available() state = dict(getattr(self, "_review_assistant_state", {}) or {}) account = str(state.get("account_id") or "") if available else "" settings = self.runtime_settings.get("review_assistant_accounts") or {} config = settings.get(account, {}) if isinstance(settings, dict) else {} if not isinstance(config, dict): config = {} recipient_id = str(config.get("recipient_conv_id") or "") return {"supported": available, "accountId": account, "accountName": str(state.get("account_name") or account), "ready": available and bool(state.get("ready")), "busy": bool(getattr(self, "_review_assistant_busy", False)), "reason": reason or str(state.get("reason") or ("点击读取当前账号后选择助理私人微信" if not account else "")), "message": str(state.get("message") or ""), "enabled": config.get("enabled") is True, "recipient": {"id": recipient_id, "name": str(config.get("recipient_name") or ""), "label": f"私人微信 · 编号尾号 {recipient_id[-8:]}" if recipient_id else ""}, "preview": notification_preview(state.get("account_name") or account or "当前企微账号")} def _refresh_review_assistant(self) -> None: available, reason = self._review_assistant_available() if not available: self._review_assistant_state = {"ready": False, "reason": reason} self._push_console_state(force=True) return if getattr(self, "_review_assistant_busy", False): return self._review_assistant_busy = True self._review_assistant_serial = getattr(self, "_review_assistant_serial", 0) + 1 serial = self._review_assistant_serial self._schedule_console_push(urgent=True) def work(): try: from review_assistant_contacts import active_context, list_recipients result = active_context() if result.get("ready"): result = list_recipients(result["account_id"], limit=1) except Exception as exc: result = {"ready": False, "reason": str(exc)} result.update(_kind="refresh", _serial=serial) try: self.reviewAssistantUpdated.emit(result) except RuntimeError: pass threading.Thread(target=work, name="review-assistant-account", daemon=True).start() def _choose_review_assistant(self) -> None: state = self._review_assistant_console_state() if not state["ready"] or state["busy"]: self._refresh_review_assistant() return expected = state["accountId"] dialog = ReviewAssistantPicker(expected, self) try: if dialog.exec() == QDialog.DialogCode.Accepted and dialog.recipient: self._save_review_assistant(expected, recipient=dialog.recipient) finally: dialog.deleteLater() def _save_review_assistant(self, expected_account: str, *, enabled=None, recipient=None) -> dict: state = self._review_assistant_console_state() if not state["ready"] or state["busy"] or not expected_account or state["accountId"] != expected_account: return {"ok": False, "message": "当前账号未核验或正在处理,请重新读取当前账号"} accounts = self.runtime_settings.get("review_assistant_accounts") or {} previous = dict(accounts.get(expected_account) or {}) if isinstance(accounts, dict) else {} target = str((recipient or {}).get("id") or previous.get("recipient_conv_id") or "") if (enabled is True or recipient is not None) and not target: return {"ok": False, "message": "请先选择助理私人微信联系人"} self._review_assistant_busy = True self._review_assistant_serial = getattr(self, "_review_assistant_serial", 0) + 1 serial = self._review_assistant_serial self._schedule_console_push(urgent=True) def work(): try: from review_assistant_contacts import active_context, validate_recipient if enabled is False and recipient is None: context = active_context() result = {"ok": context.get("ready") and context.get("account_id") == expected_account, "reason": context.get("reason") or "当前企业微信账号已变化,请重新读取", "recipient": None} else: result = validate_recipient(expected_account, target) except Exception as exc: result = {"ok": False, "reason": str(exc)} result.update(_kind="save", _serial=serial, expected_account=expected_account, requested_enabled=enabled, replace_recipient=recipient is not None) try: self.reviewAssistantUpdated.emit(result) except RuntimeError: pass threading.Thread(target=work, name="review-assistant-save", daemon=True).start() return {"ok": True, "message": "正在核对当前企微账号与收件人,保存结果将显示在提醒设置中"} @Slot(object) def _review_assistant_updated(self, result) -> None: if result.get("_serial") != getattr(self, "_review_assistant_serial", 0): return self._review_assistant_busy = False if getattr(self, "_exit_requested", False): return available, reason = self._review_assistant_available() if not available: self._review_assistant_state = {"ready": False, "reason": reason} elif result.get("_kind") == "refresh": self._review_assistant_state = {key: result.get(key) for key in ("account_id", "account_name", "ready", "reason")} elif not result.get("ok"): self._review_assistant_state = {"ready": False, "reason": result.get("reason") or "联系人核验失败,请重新读取账号"} self.append_log("[审核提醒] " + self._review_assistant_state["reason"], "warn") else: expected = str(result.get("expected_account") or "") if expected != str(getattr(self, "_review_assistant_state", {}).get("account_id") or ""): return settings = self.runtime_settings.get("review_assistant_accounts") or {} accounts = dict(settings) if isinstance(settings, dict) else {} config = dict(accounts.get(expected) or {}) recipient = result.get("recipient") changed = bool(recipient and recipient["id"] != config.get("recipient_conv_id")) if recipient: config.update(recipient_conv_id=recipient["id"], recipient_name=recipient["name"], recipient_kind="微信客户") requested = result.get("requested_enabled") config["enabled"] = bool(requested) if requested is not None else (False if changed else config.get("enabled") is True) accounts[expected] = config if self.save_runtime_settings({"review_assistant_accounts": accounts}) is False: self._review_assistant_state["message"] = "保存失败,原有提醒配置保持不变,请重试" else: self._review_assistant_state["message"] = ("已选择助理私人微信;开启提醒后生效" if changed else ("当前账号的医疗助理审核提醒已开启" if config["enabled"] else "当前账号的医疗助理审核提醒已关闭")) self.append_log("[审核提醒] " + self._review_assistant_state["message"], "ok") self._push_console_state(force=True) '''