Files
kefu/deploy/ui-performance-20260916/patch_web.py
T
2026-09-21 10:34:06 +08:00

162 lines
7.3 KiB
Python

from pathlib import Path
folder=Path(__file__).resolve().parent;p=folder/'payload/assets/ui/console-app.js'
s=p.read_text(encoding='utf-8')
helper='''// Keep in-progress values separate from background snapshots. They are cleared
// only when the backend echoes the same value, never just because focus moved.
const fieldDrafts = new Map();
const pageScroll = new Map();
const pendingEdits = new Map();
const fieldSources = {
"ai-name":["ai","name"], "auto-poll":["automation","poll"],
"auto-batch":["automation","batch"], "auto-delay":["automation","delay"],
"auto-idle":["automation","idle"], "auto-context":["automation","context"],
"auto-draft-autosend":["automation","draftAutosend"],
"sys-language":["system","language"], "sys-scale":["system","scale"],
"sys-retention":["system","retention"], "sys-server-url":["system","serverUrl"],
"archive-search":[null,"archiveQuery"], "event-search":[null,"eventQuery"],
"wechat-db-search":["wechatDb","query"], "archive-date":[null,"archiveDate"]
};
function recordDraft(element){
if (!element || !fieldSources[element.id]) return;
fieldDrafts.set(element.id, {view:STATE.view,value:element.value});
}
function acknowledgeDrafts(data){
for (const [id,draft] of fieldDrafts){
const [section,key]=fieldSources[id];
const source=section ? data[section] : data;
if (source && source[key] != null && String(source[key]) === draft.value) fieldDrafts.delete(id);
}
}
function restoreDrafts(view){
for (const [id,draft] of fieldDrafts){
const element=document.getElementById(id);
if (draft.view===view && element && element.value!==draft.value) element.value=draft.value;
}
if (view==='ai' && fieldDrafts.has('ai-name')) markAiDirty();
}
function cancelEdit(key){
const edit=pendingEdits.get(key);
if (edit) clearTimeout(edit.timer);
pendingEdits.delete(key);
}
function sendEdit(key){
const edit=pendingEdits.get(key);
if (!edit) return;
cancelEdit(key);
callBridge(edit.method,edit.payload);
}
function scheduleEdit(key,method,payload,delay){
cancelEdit(key);
pendingEdits.set(key,{method,payload,timer:setTimeout(()=>sendEdit(key),delay)});
}
function flushEdits(){
for (const key of [...pendingEdits.keys()]) sendEdit(key);
}
let composing=false, pointerDown=false, deferredRefresh=0;
document.addEventListener('pointerdown',()=>{pointerDown=true;},true);
document.addEventListener('pointerup',()=>{pointerDown=false;},true);
document.addEventListener('pointercancel',()=>{pointerDown=false;},true);
document.addEventListener('compositionstart',()=>{composing=true;});
document.addEventListener('compositionend',event=>{
composing=false;
event.target.dispatchEvent(new Event('input',{bubbles:true}));
});
document.addEventListener('focusout',()=>{
clearTimeout(deferredRefresh);
deferredRefresh=setTimeout(()=>{
const active=document.activeElement;
if (!pointerDown && !composing && window.__deferredView===STATE.view
&& !(active && /^(INPUT|TEXTAREA|SELECT)$/.test(active.tagName))){
window.__deferredView=null;
render(STATE.view);
}
},100);
});
'''
assert s.count('const _render = render;')==1;s=s.replace('const _render = render;',helper+'const _render = render;')
s=s.replace(''' const sameView = STATE.view === name;
STATE.view = name;''',''' const sameView = STATE.view === name;
const scrollHost = document.getElementById("main");
if (scrollHost) pageScroll.set(STATE.view,scrollHost.scrollTop);
STATE.view = name;''',1)
s=s.replace(''' main.innerHTML = html;
normalizeReferenceView();''',''' main.innerHTML = html;
restoreDrafts(name);
normalizeReferenceView();
main.scrollTop = pageScroll.get(name) || 0;''',1)
point=''' const prev = STATE.view;
Object.assign(STATE, data);'''
new=''' if (window.__pendingNavigation && data.view && data.view!==window.__pendingNavigation) return;
if (data.view===window.__pendingNavigation) window.__pendingNavigation=null;
acknowledgeDrafts(data);
const prev = STATE.view;
const previousScale=(STATE.system || {}).scale;
Object.assign(STATE, data);
if (data.system && data.system.scale!==previousScale && typeof fit==='function') fit();'''
assert s.count(point)==1;s=s.replace(point,new)
s=s.replace(''' if (typing && (STATE.view || prev) === prev && ["ai","automation","system","archive","logs"].includes(prev)) {
return;
}''',''' if ((typing || composing) && (STATE.view || prev)===prev) {
window.__deferredView=prev;
return;
}''')
old=''' if (name !== STATE.view) render(name);
callBridge("navigate", name);'''
new=''' flushEdits();
window.__pendingNavigation=name;
if (name !== STATE.view) render(name);
const serial=window.__navigationSerial=(window.__navigationSerial || 0)+1;
requestAnimationFrame(()=>requestAnimationFrame(()=>{
if (serial===window.__navigationSerial) callBridge("navigate",name);
}));'''
assert s.count(old)==1;s=s.replace(old,new)
# Separate debounces, capture values now (before a navigation replaces the DOM).
s=s.replace('let searchTimer = 0;\n','')
s=s.replace(''' const id = e.target.id || "";
const value = e.target.value || "";''',''' const id = e.target.id || "";
const value = e.target.value || "";
recordDraft(e.target);
if (e.isComposing || composing) return;''',1)
old=''' clearTimeout(searchTimer);
searchTimer = setTimeout(() => {
if (id === "archive-search") callBridge("setArchiveQuery", value);
else if (id === "event-search") callBridge("setEventQuery", value);
else callBridge("setWeChatDatabaseQuery", value);
}, 280);'''
new=''' const method={"archive-search":"setArchiveQuery","event-search":"setEventQuery","wechat-db-search":"setWeChatDatabaseQuery"}[id];
scheduleEdit(id,method,value,280);'''
assert old in s;s=s.replace(old,new)
s=s.replace('id === "auto-context") {','id === "auto-context" || id === "auto-draft-autosend") {')
old=''' clearTimeout(searchTimer);
searchTimer = setTimeout(() => {
callBridge("saveAutomationSettings", collectAutomationPayload());
}, 400);'''
new=''' scheduleEdit("automation","saveAutomationSettings",collectAutomationPayload(),400);'''
assert old in s;s=s.replace(old,new)
old=''' clearTimeout(searchTimer);
searchTimer = setTimeout(() => {
callBridge("setSystemField", collectSystemPayload());
}, 280);'''
new=''' if (id === "sys-retention") scheduleEdit("system","setSystemField",collectSystemPayload(),280);'''
assert old in s;s=s.replace(old,new)
old=''' if (id === "sys-language" || id === "sys-scale") {
callBridge("setSystemField", collectSystemPayload());
}'''
new=''' if (id === "sys-language" || id === "sys-scale") {
recordDraft(e.target);
cancelEdit("system");
callBridge("setSystemField",collectSystemPayload());
}'''
assert old in s;s=s.replace(old,new)
# Cancel the pending autosave when clicking the explicit Save button.
point='''document.addEventListener("click", e => {
const nav ='''
new='''document.addEventListener("click", e => {
const saveAction=e.target.closest('[data-action="save-system"],[data-action="save-automation"]');
if (saveAction) cancelEdit(saveAction.dataset.action==='save-system'?'system':'automation');
const nav ='''
assert point in s;s=s.replace(point,new)
p.write_text(s,encoding='utf-8',newline='\n')
print('Prepared independent saves, draft protection and navigation coalescing')