# -*- coding: utf-8 -*-
"""Build console-app.js from the original mock script plus live-data hooks."""
from pathlib import Path
root = Path(__file__).resolve().parent
script = (root / "_orig_script.js").read_text(encoding="utf-8")
for old, new in [
("const people=", "var people="),
("let listening=true", "var listening=true"),
("const head=", "var head="),
("const connected=", "var connected="),
("const ribbon=", "var ribbon="),
("const queueList=", "var queueList="),
("const activities=", "var activities="),
("const journey=", "var journey="),
("const statusCells=", "var statusCells="),
("const views=", "var views="),
("const glyphIcons=", "var glyphIcons="),
]:
script = script.replace(old, new, 1)
script = script.replace(
"function fit(){const s=Math.min(innerWidth/1630,innerHeight/965);document.getElementById(\"stage\").style.setProperty(\"--scale\",String(s))}addEventListener(\"resize\",fit);fit();render((location.hash||\"#auto\").slice(1));",
"function fit(){const s=Math.min(innerWidth/1630,innerHeight/965);document.getElementById(\"stage\").style.setProperty(\"--scale\",String(s))}",
1,
)
header = r'''
const icon=n=>``;
window.STATE = window.STATE || { view: "auto", demo: true, ready: false, listening: true };
function esc(value){
return String(value ?? "").replace(/[&<>"']/g, ch => ({
"&":"&","<":"<",">":">",'"':""","'":"'"
}[ch]));
}
function bridge(){ return window.qtBridge || null; }
function callBridge(name, ...args){
const api = bridge();
if (!api || typeof api[name] !== "function") return false;
api[name](...args);
return true;
}
'''
# Original script starts with icon=...; drop that duplicate.
if script.lstrip().startswith("const icon="):
script = script.lstrip()
script = script[script.index(";") + 1 :]
footer = r'''
const _queueList = queueList;
const _activities = activities;
const _connected = connected;
const _journey = journey;
connected = function(extra="", primary=""){
const wecom = STATE.wecomLabel || (STATE.wecomConnected ? "企业微信已连接" : "企业微信未连接");
const ai = STATE.aiLabel || (STATE.aiOk === false ? "AI 服务异常" : "AI 服务正常");
const listenLabel = STATE.listening ? `${icon("pause")}暂停监听` : `${icon("play")}恢复监听`;
return `${esc(wecom)}${esc(ai)}${extra}`;
};
queueList = function(archive=false){
if (STATE.demo) return _queueList(archive);
const rows = archive ? (STATE.archives || []) : (STATE.people || []);
if (!rows.length){
const title = archive ? "暂无会话档案" : "当前没有待处理任务";
const hint = archive ? "新的客户会话会自动出现在这里" : "所有客户消息均已完成";
return `
${i
${esc(x[0])}${esc(x[1])}`).join("");
const message = (task.messages && task.messages[0]) || {time:"", text:"队列为空时,选择任务后会在这里显示来源消息。"};
return `
任务详情
客户${esc(task.name || "暂无选中任务")}
会话 ID${esc(task.sessionId || "--")}
已等待${esc(task.wait || "--")}
重试${esc(task.retries || "0 次")}
来源消息
${esc(message.text || "")}
${icon("sparkles")} ${esc(task.action || "等待新任务")}
${process}
`;
}
function schedulePanel(){
const s = (STATE.task && STATE.task.schedule) || {priority:"—", eta:"—", protect:"—", retry:"—"};
return `
调度信息
${icon("flag")}
优先级${esc(s.priority)}
${icon("clock")}
预计完成${esc(s.eta)}
${icon("shield")}
人工保护${esc(s.protect)}
${icon("refresh")}
下次重试${esc(s.retry)}
`;
}
autoView = function(){
if (STATE.demo) return _autoView();
const count = (STATE.people || []).length;
return `
${head("桌面自动化控制台","监听企业微信未读消息,并由 AI 自动生成与发送回复",connected(`
`,"danger"))}${liveRibbonAuto()}
任务队列
${count}${icon("filter")}${icon("sort")}
${queueList()}${currentTaskCard()}
自动化执行状态
${esc(STATE.journeyTotal || "")}${journey()}${windowStatusCard()}
`;
};
queueView = function(){
if (STATE.demo) return _queueView();
const count = (STATE.people || []).length;
const filter = STATE.queueFilter || "全部";
const pills = ["全部","处理中","等待中","待重试"].map(name=>`
`).join("");
return `
${head("任务队列","查看排队、处理中与待重试的自动回复任务",connected(`
`,"danger"))}${liveRibbonQueue()}
全部任务
${pills}
${queueList()}${queueDetailPanel()}${schedulePanel()}
`;
};
archiveView = function(){
if (STATE.demo) return _archiveView();
const rows = STATE.archives || [];
const chat = STATE.chat || [];
const selected = rows.find(x=>x.key===STATE.selectedArchive) || rows[0] || {};
const chatHtml = chat.length ? chat.map(x=>`
${icon(x.role==="assistant"?"bot":"user")}${esc(x.text || "")}
`).join("") : `
选择会话后显示聊天记录
`;
const tags = (STATE.memory && STATE.memory.tags || []).map((t,i)=>`
${esc(t)}`).join("") || `
暂无标签`;
const settle = (STATE.settle || []).map(x=>`
${x.iconHtml || icon("clipboard")}
${esc(x.title)}${esc(x.hint)}
${esc(x.value)}`).join("");
const filter = STATE.archiveFilter || "全部会话";
return `
${head("会话归档","保存客户上下文、自动回复与回访线索",`
${esc(STATE.wecomLabel || "企业微信")}${esc(STATE.aiLabel || "AI 服务")}`)}
会话记录
${icon("user")}${esc(selected.name || "未选择")}${esc(selected.count || "0")} 条消息${esc(selected.badge || "")}${icon("clock")}${esc(selected.time || "")}
${chatHtml}
客户记忆
${tags}
${esc((STATE.memory && STATE.memory.text) || "选择会话后显示长期上下文。")}
${icon("clock")} 上下文 ${esc((STATE.memory && STATE.memory.rounds) || 0)} 轮最近更新
${activities("复制全部记录")}`;
};
aiView = function(){
if (STATE.demo) return _aiView();
const ai = STATE.ai || {};
const toolsOn = ai.tools || [true, true, true];
const names = ["挂号登记","客户查询","回访记录"];
const toolIcons = ["clipboard","search","clipboard"];
return `
${head("AI 设置","配置模型、客服人格、知识库与工具能力",`
配置已同步`)}
客服人格
AI
${icon("sparkles")} 实时预览您好!我是甄养堂${esc(ai.name || "贴心管家")},
很高兴为您服务 😊
请问有什么可以帮您的吗?
${icon("brain")} 模型引擎
${icon("tray")} 企业知识库
${esc(ai.knowledge || "0")}个知识片段
● 已同步✓ 已连接
${icon("package")} MCP 工具
单次最多调用 ${esc(ai.mcpRounds || 5)} 个工具 ⓘ${icon("shield")} 安全边界
${icon("shield")} 不做医疗诊断
${icon("pill")} 不修改用药方案
${icon("users")} 高风险转人工
当前模式:${icon("shield")}严格模式
配置状态
${statusCells([["cloud","云端模型正常","最后检查:刚刚"],["folder","知识库已同步",`${ai.knowledge || 0} 个知识片段`],["folder","3 个工具可用","全部运行正常"],["shield","安全策略已生效","严格模式"]])}`;
};
automationView = function(){
if (STATE.demo) return _automationView();
const a = STATE.automation || {};
const autoOn = (STATE.sendMode || "auto") === "auto";
return `
${head("自动化设置","配置企业微信监听、消息合并与自动发送规则",`
${esc(a.wecom || "企业微信未识别")}规则已生效`)}
监听与发送策略
${icon("activity")} ● 规则运行中${[["activity","扫描未读"],["log","提取消息"],["folder","合并消息"],["brain","请求 AI"],["send","回填发送"]].map(x=>`
`).join("")}
企业微信窗口
${icon("clipboard")} 主窗口:● ${esc(a.wecom || "未识别")}
${icon("user")} 当前账号:${esc(a.account || "—")}
${icon("globe")} DPI:${esc(STATE.dpi || "—")}
${icon("monitor")} 最小化恢复:● 已开启
人机共存
鼠标静止 ${esc(a.idle || "5")} 秒 后恢复
${icon("bot")}
AI 运行中${icon("cursor")}
${icon("pause")}
检测到人工操作
AI 暂停 消息处理
✓ 同一客户连续消息自动合并
✓ 保留最近 ${esc(a.context || 8)} 轮上下文${esc(a.context || 8)} 轮
✓ 一次只处理 1 个会话1 个
异常与兜底
${icon("clock")}AI 超时:重试 2 次2 次
${icon("alert")}发送失败:自动重试已开启
${icon("message")}固定回复:${esc(a.reply || "在的,请稍等")}编辑
高风险转人工
● 已开启${icon("shield")} 诊断${icon("package")} 用药调整${icon("coins")} 投诉退款
启动前检查
${statusCells([["check","企业微信已登录", STATE.wecomConnected ? "检查完成" : "等待连接"],["cloud","AI 服务正常","检查完成"],["activity","窗口坐标已校准", STATE.wecomConnected ? "检查完成" : "待校准"],["shield","规则配置有效","检查完成"]])}`;
};
logsView = function(){
if (STATE.demo) return _logsView();
const events = STATE.logs || [];
const list = events.length ? events.map(x=>`
✓
${icon(x.icon || "activity")}
${esc(x.title)}${esc(x.detail)}
${esc(x.state || "")}`).join("") : `
当前没有运行记录
`;
const ex = STATE.exceptions || {};
const detail = STATE.logDetail || {title:"等待事件", text:"开始监听后,这里会显示最近一条运行事件。", tags:[]};
return `
${head("运行日志","实时追踪连接、消息识别、AI 调用与自动发送事件",`
${esc(STATE.statusLabel || "监控中")}`)}${liveRibbonLogs()}
实时事件流
${["全部","连接","识别","AI","发送","警告"].map(n=>``).join("")}
${list}
系统健康
${[["企业微信连接", STATE.wecomConnected ? "正常" : "待连接"],["AI 服务","正常"],["窗口识别", STATE.wecomConnected ? "正常" : "待监听"]].map(x=>`
${icon("check")}
${x[0]}${x[1]}`).join("")}
${icon("clock")}运行时长 ${esc(STATE.runtime || "00:00:00")}
当前回复链路
${[["activity","扫描未读",""],["message","读取消息",""],["brain","请求 AI",""],["send","回填发送",""]].map(x=>`
${icon(x[0])}
${x[1]}${x[2]}`).join("")}
事件详情
${icon("user")}
${esc(detail.title)}${esc(detail.text)}
${(detail.tags||[]).map(t=>`${esc(t)}`).join("")}
异常追踪
${icon("alert")}
发送失败${esc(ex.sendFail || 0)}
${icon("clock")}
模型超时${esc(ex.timeout || 0)}
${icon("monitor")}
窗口丢失${esc(ex.windowLost || 0)}
最近活动
${activities("查看完整记录")}`;
};
systemView = function(){
if (STATE.demo) return _systemView();
const s = STATE.system || {};
const on = k => s[k] ? "on" : "";
return `
${head("系统设置","管理软件启动、数据存储、通知与系统维护",`
当前版本 v${esc(s.version || "2.0")} ${icon("info")}`)}
基础设置
${icon("activity")}
启动后自动监听${icon("log")}
关闭窗口时保持后台运行${icon("globe")}界面语言: 简体中文${icon("chevron")}
${icon("monitor")}缩放比例: 自动${icon("chevron")}
数据与存储
${esc(s.storage || "—")}已使用
● 会话归档${esc(s.archiveSize || "—")}
● 运行日志${esc(s.logSize || "—")}
● 缓存数据${esc(s.cacheSize || "—")}
数据保留${esc(s.retention || 90)} 天
通知提醒
${icon("hourglass")}
模型超时提醒${icon("bell")}
发送失败提醒消息发送失败,已加入重试队列
安全与隐私
${icon("shield")}敏感信息脱敏:已开启 ●
${icon("lock")}本地数据加密:已开启 ●
${icon("lock")}日志隐藏聊天正文:已开启 ●
${icon("log")}退出时清理剪贴板:已开启 ●
网络与服务
${icon("cloud")}云端 AI:已连接 ●
${icon("folder")}企业知识库:已连接 ●
${icon("wifi")}网络延迟:— ●
维护与诊断
${icon("refresh")}
检查更新已是最新版本
${icon("clipboard")}
导出诊断导出系统诊断报告
${icon("refresh")}
恢复默认设置重置所有设置项
${icon("shield")}
查看许可信息查看软件许可详情
系统状态
${statusCells([["shield","配置已保存","所有设置已生效"],["cloud","数据目录正常","本地数据可写"],["shield","安全策略正常","所有安全策略已启用"],["monitor","后台服务", STATE.listening ? "运行中" : "已停止"]])}`;
};
views = {auto:autoView,queue:queueView,archive:archiveView,ai:aiView,automation:automationView,logs:logsView,system:systemView};
const _render = render;
render = function(name){
if(!views[name]) name = STATE.view || "auto";
STATE.view = name;
listening = !!STATE.listening;
_render(name);
const orb = document.getElementById("orb");
const label = document.getElementById("orb-label");
if (orb) orb.classList.toggle("paused", !STATE.listening);
if (label) label.textContent = STATE.statusLabel || (STATE.listening ? "监听中" : "已停止");
};
window.applyState = function(payload){
const data = typeof payload === "string" ? JSON.parse(payload) : (payload || {});
const prev = STATE.view;
Object.assign(STATE, data);
STATE.ready = true;
if (typeof data.listening === "boolean") listening = data.listening;
const active = document.activeElement;
const typing = active && /^(INPUT|TEXTAREA|SELECT)$/.test(active.tagName);
if (typing && (STATE.view || prev) === prev && ["ai","automation","system","archive","logs"].includes(prev)) {
return;
}
render(STATE.view || prev || "auto");
};
window.showConsoleView = function(name){ render(name); };
document.addEventListener("click", e => {
const nav = e.target.closest("[data-view]");
if (nav){
const name = nav.dataset.view;
render(name);
callBridge("navigate", name);
e.stopImmediatePropagation();
return;
}
const action = e.target.closest("[data-action]");
if (!action) return;
const a = action.dataset.action;
const key = action.dataset.key || "";
if (a === "toggle-listen" || a === "start-listen"){
if (!callBridge("toggleListen")) {
listening = !listening;
STATE.listening = listening;
render(STATE.view);
}
e.stopImmediatePropagation();
return;
}
const map = {
logs: () => callBridge("navigate", "logs") || render("logs"),
refresh: () => callBridge("refresh") || toast("已刷新"),
"clear-logs": () => callBridge("clearLogs") || toast("日志已清空"),
"cancel-task": () => callBridge("cancelTask", key),
"retry-task": () => callBridge("retryTask", key),
"handoff-task": () => callBridge("handoffTask", key),
"select-task": () => callBridge("selectTask", key),
"select-archive": () => callBridge("selectArchive", key),
"queue-filter": () => callBridge("setQueueFilter", key),
"archive-filter": () => callBridge("setArchiveFilter", key),
"event-filter": () => callBridge("setEventFilter", key),
"copy-session": () => callBridge("copyText", key),
"copy-archive": () => callBridge("copyArchive"),
"export-archive": () => callBridge("exportArchive"),
"export-logs": () => callBridge("exportLogs"),
"full-log": () => callBridge("showFullLog"),
"save-ai": () => callBridge("saveAi", JSON.stringify({name: (document.getElementById("ai-name")||{}).value || "", temperature: Number((document.getElementById("ai-temp")||{}).value || 35)/100})),
"test-ai": () => callBridge("testAi"),
"pick-model": () => callBridge("pickModel"),
"sync-knowledge": () => callBridge("syncKnowledge"),
"open-data": () => callBridge("openDataDir"),
"save-automation": () => callBridge("saveAutomation"),
"send-mode": () => callBridge("setSendMode", key),
"toggle-idle": () => callBridge("toggleIdle"),
"redetect": () => callBridge("redetectWecom"),
"security-rules": () => callBridge("showSecurityRules"),
"save-system": () => callBridge("saveSystem"),
"sys-toggle": () => callBridge("toggleSetting", key),
"check-update": () => callBridge("checkUpdate"),
"backup": () => callBridge("backupData"),
"clear-cache": () => callBridge("clearCache"),
"reset-settings": () => callBridge("resetSettings"),
license: () => callBridge("showLicense"),
"toggle-tool": () => callBridge("toggleTool", key),
};
if (map[a]){
map[a]();
e.stopImmediatePropagation();
}
}, true);
document.addEventListener("change", e => {
if (e.target && e.target.id === "archive-search") callBridge("setArchiveQuery", e.target.value || "");
if (e.target && e.target.id === "event-search") callBridge("setEventQuery", e.target.value || "");
});
const _fit = fit;
fit = function(){
const embedded = document.documentElement.classList.contains("embedded");
const w = embedded ? 1630 : 1630;
const h = embedded ? 920 : 965;
const s = Math.min(innerWidth / w, innerHeight / h);
document.getElementById("stage").style.setProperty("--scale", String(s));
};
function connectQt(){
if (typeof qt === "undefined" || !qt.webChannelTransport || typeof QWebChannel === "undefined") return;
new QWebChannel(qt.webChannelTransport, channel => {
window.qtBridge = channel.objects.bridge;
if (window.qtBridge.stateChanged) window.qtBridge.stateChanged.connect(window.applyState);
if (window.qtBridge.requestState) window.qtBridge.requestState();
});
}
connectQt();
addEventListener("resize", fit);
fit();
render((STATE.view || (location.hash || "#auto").slice(1) || "auto"));
'''
(root / "console-app.js").write_text(header + script + footer, encoding="utf-8")
print("wrote", root / "console-app.js", (root / "console-app.js").stat().st_size)