from pathlib import Path import os, shutil, difflib from qt_assistant_parts import PICKER, BRIDGE, METHODS ARTIFACT=Path(__file__).parent PANEL='''function reviewAssistantPanel(a){ const r = a.reviewAssistant || {}; const recipient = r.recipient || {}; const busy = Boolean(r.busy); const canChoose = r.ready && !busy; const canToggle = canChoose && (recipient.id || r.enabled); return `

医疗助理审核提醒

${r.enabled ? "当前账号已开启" : "当前账号未开启"}

客户会话进入待审核时,由当前托管企业微信账号向指定助理的私人微信发送客户咨询、待发送回复和审核原因。

托管企微账号(最近核验)${esc(r.accountName || "尚未核验")}${r.accountId ? `${esc(r.accountId)}` : ""}
助理私人微信
${esc(recipient.name || "尚未选择")}${recipient.label ? `
${esc(recipient.label)}
` : ""}
开启审核提醒
${r.reason ? `

${esc(r.reason)}

` : ""} ${r.message ? `

${esc(r.message)}

` : ""}

设置按企微账号分别保存。选择或更换联系人后默认关闭,确认收件人后再开启;同名联系人按列表中的联系编号区分。

人工用同一企业微信账号直接回复客户后结束本次审核;后续自动回复仍逐条风控。此识别以账号发出的消息为准,不区分手机或电脑。在助理私人微信回复提醒,不会解除客户审核。

提醒内容示例(只读,不会发送)
${esc(r.preview || "请先读取当前账号")}
`; } ''' for root in (Path('C:/wechat_rpa'),Path('C:/kefu/wechat_rpa')): for relative in ('wechat_gui_qt.py','assets/ui/console-app.js'): p=root/relative;backup=root/'backups/review-assistant-20260917'/relative backup.parent.mkdir(parents=True,exist_ok=True) if not backup.exists():shutil.copyfile(p,backup) shutil.copyfile(ARTIFACT/'review_assistant_contacts.py',root/'review_assistant_contacts.py') p=root/'wechat_gui_qt.py';old=p.read_text(encoding='utf-8');s=old assert 'class ReviewAssistantPicker' not in s s=s.replace(' QListWidget,\n',' QListWidget,\n QListWidgetItem,\n',1) s=s.replace('class MainWindow(QMainWindow):',PICKER+'class MainWindow(QMainWindow):',1) s=s.replace(' wechatDatabaseLoaded = Signal(object)',' reviewAssistantUpdated = Signal(object)\n wechatDatabaseLoaded = Signal(object)',1) # Python helpers use native protocol capability in either visual or protocol mode. methods=METHODS.replace(' if self.runtime_settings.get("reply_transport", "visual") != "protocol":\n return False, "请切换到协议模式后配置医疗助理审核提醒"\n','') s=s.replace(' def _console_automation_state(self, wecom) -> dict:',methods+' def _console_automation_state(self, wecom) -> dict:',1) pos=s.index(' def _console_automation_state(');end=s.index(' def _console_system_state(',pos) section=s[pos:end].replace(' return {\n',' return {\n "reviewAssistant": self._review_assistant_console_state(),\n',1) s=s[:pos]+section+s[end:] s=s.replace(' @Slot(str, result=str)\n def saveAutomationSettings',BRIDGE+' @Slot(str, result=str)\n def saveAutomationSettings',1) s=s.replace(' self.wechatDatabaseLoaded.connect(self._wechat_database_loaded)', ' self.reviewAssistantUpdated.connect(self._review_assistant_updated)\n self.wechatDatabaseLoaded.connect(self._wechat_database_loaded)',1) s=s.replace(' "notify_manual": True,',' "notify_manual": True,\n "review_assistant_accounts": {},',1) if root==Path('C:/kefu/wechat_rpa'): start=s.index(' def save_runtime_settings(self, settings: dict | None = None)') end=s.index(' def _broadcast_save_result(',start) section=s[start:end] section=section.replace(' -> None:', ' -> bool:',1) section=section.replace(' self.runtime_settings = merged',' previous_settings = getattr(self, "runtime_settings", {})\n self.runtime_settings = merged',1) section=section.replace(' self.append_log(f"运行参数保存失败:{exc}", "err")',' self.append_log(f"运行参数保存失败:{exc}", "err")\n self.runtime_settings = previous_settings\n return False',1) section=section.replace(' self._broadcast_save_result(True, "设置已保存并生效")',' self._broadcast_save_result(True, "设置已保存并生效")\n return True',1) s=s[:start]+section+s[end:] compile(s,str(p),'exec');t=p.with_suffix('.assistant.tmp');t.write_text(s,encoding='utf-8');os.replace(t,p) label='production' if root==Path('C:/wechat_rpa') else 'source' (ARTIFACT/f'{label}-qt.diff').write_text(''.join(difflib.unified_diff(old.splitlines(True),s.splitlines(True),fromfile='before',tofile=str(p))),encoding='utf-8') p=root/'assets/ui/console-app.js';old=p.read_text(encoding='utf-8');s=old assert 'function reviewAssistantPanel' not in s s=s.replace('automationView = function(){',PANEL+'automationView = function(){',1) start=s.index('automationView = function(){');end=s.index('\nlogsView = function',start) section=s[start:end] section=section.replace('
','
${reviewAssistantPanel(a)}
',1) if root==Path('C:/wechat_rpa'): section=section.replace('

${ready?', ' ${reviewAssistantPanel(a)}

${ready?',1) section=section.replace('发送结果不明确时暂停监听并保留记录,防止重复发送。','发送结果不明确时保留该会话记录待核对,其他会话继续监听,防止重复发送。') assert '${reviewAssistantPanel(a)}' in section s=s[:start]+section+s[end:] actions=''' "review-assistant-refresh": () => callBridge("refreshReviewAssistant"), "review-assistant-choose": () => callBridge("chooseReviewAssistant"), "review-assistant-toggle": () => { const r = (STATE.automation || {}).reviewAssistant || {}; if (!r.ready || r.busy) return; callBridgeWithFeedback("setReviewAssistantEnabled", [JSON.stringify({expected_account: r.accountId, enabled: !r.enabled})], { pending: "正在核对当前账号与助理联系人…", }); }, ''' s=s.replace(' "save-automation": () => callBridgeWithFeedback(',actions+' "save-automation": () => callBridgeWithFeedback(',1) t=p.with_suffix('.assistant.tmp');t.write_text(s,encoding='utf-8');os.replace(t,p) (ARTIFACT/f'{label}-js.diff').write_text(''.join(difflib.unified_diff(old.splitlines(True),s.splitlines(True),fromfile='before',tofile=str(p))),encoding='utf-8') print(label, 'UI installed')