from pathlib import Path import hashlib,json,shutil,re ROOTS=[Path('C:/wechat_rpa'),Path('C:/kefu/wechat_rpa')] HELPERS='''const loadingActionLocks = new Set(); const recentDbSelections = new Map(); function isLoadingActionPending(method){ return loadingActionLocks.has(method); } function callLoadingAction(method, action, label){ const serverBusy = method === "refreshWeChatDatabase" ? (STATE.wechatDb || {}).loading : (((STATE.automation || {}).protocol || {}).status === "checking"); if (serverBusy || loadingActionLocks.has(method)) return; loadingActionLocks.add(method); const selector = `[data-action="${action}"]`; const button = document.querySelector(selector); if (button){ button.disabled = true; button.innerHTML = icon("refresh") + label; } if (!callBridge(method)) { loadingActionLocks.delete(method); if (button) button.disabled = false; toast("界面尚未连接后台,请稍后重试。"); if (STATE.view) render(STATE.view); return; } // Qt owns long-running work. This short local lock covers the bridge round trip. setTimeout(()=>{ loadingActionLocks.delete(method); const current = document.querySelector(selector); const busy = method === "refreshWeChatDatabase" ? (STATE.wechatDb || {}).loading : (((STATE.automation || {}).protocol || {}).status === "checking"); if (current && !busy){ current.disabled = false; current.innerHTML = icon("refresh") + (method === "refreshWeChatDatabase" ? "刷新消息库" : "检测本机协议"); } }, 800); } function selectDbItem(method, key, selected){ const last = recentDbSelections.get(method); const recent = last && performance.now() - last.at < 800; if ((recent && last.key === key) || (!recent && String(selected || "") === key)) return; recentDbSelections.set(method, {key, at:performance.now()}); callBridge(method, key); } ''' def once(s,old,new): assert s.count(old)==1,(old[:100],s.count(old)) return s.replace(old,new,1) manifest=[] for root in ROOTS: backup=root/'backups/loading-performance-20260917/frontend' for rel in ['assets/ui/console-app.js','assets/ui/zhenyang-ai-console.html']: src=root/rel;dst=backup/rel assert not dst.exists(),dst dst.parent.mkdir(parents=True,exist_ok=True);shutil.copy2(src,dst) js=root/'assets/ui/console-app.js';s=js.read_text(encoding='utf-8') s=once(s,'const _queueList = queueList;',HELPERS+'const _queueList = queueList;') s=once(s,'"wechat-db-refresh": () => callBridge("refreshWeChatDatabase") || toast("正在刷新消息库"),','"wechat-db-refresh": () => callLoadingAction("refreshWeChatDatabase", "wechat-db-refresh", "正在读取…"),') s=once(s,'"wechat-db-account": () => callBridge("selectWeChatAccount", key),','"wechat-db-account": () => selectDbItem("selectWeChatAccount", key, (STATE.wechatDb || {}).selectedAccount),') s=once(s,'"wechat-db-conversation": () => callBridge("selectWeChatConversation", key),','"wechat-db-conversation": () => selectDbItem("selectWeChatConversation", key, (STATE.wechatDb || {}).selectedConversation),') s=once(s,'data-action="wechat-db-refresh" ${db.loading ? "disabled" : ""}','data-action="wechat-db-refresh" ${db.loading || isLoadingActionPending("refreshWeChatDatabase") ? "disabled" : ""}') s=once(s,'const patientControl = !currentConversation ? "" : !canSearchPatient','const patientControl = !currentConversation ? "" : patientContext.loading\n ? `${icon("refresh")} 患者关联加载中`\n : !canSearchPatient') if root==ROOTS[0]: s=once(s,'const checking = p.status === "checking";', 'const checking = p.status === "checking" || isLoadingActionPending("probeProtocol");') s=once(s,'"probe-protocol": () => callBridge("probeProtocol"),','"probe-protocol": () => callLoadingAction("probeProtocol", "probe-protocol", "检测中…"),') else: s=once(s,'let searchTimer = 0;', '''let searchTimer = 0; let dbSearchComposing = false; document.addEventListener("compositionstart", event=>{ if (event.target && event.target.id === "wechat-db-search") { dbSearchComposing = true; clearTimeout(searchTimer); } }); document.addEventListener("compositionend", event=>{ if (event.target && event.target.id === "wechat-db-search") { dbSearchComposing = false; event.target.dispatchEvent(new Event("input", {bubbles:true})); } });''') s=once(s,' const value = e.target.value || "";\n if (["ai-name"', ' const value = e.target.value || "";\n if (id === "wechat-db-search" && (e.isComposing || dbSearchComposing)) { clearTimeout(searchTimer); return; }\n if (["ai-name"') s=once(s,' const sameView = STATE.view === name;\n STATE.view = name;', ''' const sameView = STATE.view === name; const active = document.activeElement; const keepSearch = sameView && active && active.id === "wechat-db-search" ? {element:active, start:active.selectionStart, end:active.selectionEnd} : null; STATE.view = name;''') s=once(s,' main.innerHTML = html;\n normalizeReferenceView();', ''' main.innerHTML = html; if (keepSearch){ const placeholder=document.getElementById("wechat-db-search"); if (placeholder){ placeholder.replaceWith(keepSearch.element); keepSearch.element.focus({preventScroll:true}); if (keepSearch.start != null) keepSearch.element.setSelectionRange(keepSearch.start,keepSearch.end); } } normalizeReferenceView();''') s=once(s,' const prev = STATE.view;\n Object.assign(STATE, data);', ' const prev = STATE.view;\n Object.assign(STATE, data);') s=once(s,' if (typing && (STATE.view || prev) === prev && ["ai"', ' if (dbSearchComposing && prev === "wechatdb" && (STATE.view || prev) === prev) return;\n if (typing && (STATE.view || prev) === prev && ["ai"') js.write_text(s,encoding='utf-8',newline='\n') html=root/'assets/ui/zhenyang-ai-console.html';s=html.read_text(encoding='utf-8') s,n=re.subn(r'console-app\.js\?v=[^"\s]+','console-app.js?v=loading1',s);assert n==1 html.write_text(s,encoding='utf-8',newline='\n') manifest.append({'root':str(root),'files':[{'path':rel,'sha256':hashlib.sha256((root/rel).read_bytes()).hexdigest()} for rel in ['assets/ui/console-app.js','assets/ui/zhenyang-ai-console.html']]}) Path('C:/kefu/deploy/loading-performance-20260917/frontend-files.json').write_text(json.dumps(manifest,ensure_ascii=False,indent=2),encoding='utf-8') print('Saved JS-only loading improvements and HTML cache keys in both directories.')