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