Files
kefu/deploy/review-assistant-20260917/qt_assistant_parts.py
T
2026-09-21 10:34:06 +08:00

299 lines
15 KiB
Python

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)
'''