${i
${esc(x[0])} ${esc(x[1])} `).join("");
const messages = (task.messages || []).map(message=>`
${esc(message.time || "")} ${esc(message.text || "")}
`).join("") || `
暂无来源消息
`;
const retryButton = taskRetryButton(task);
return `
任务详情 客户 ${esc(task.name || "暂无选中任务")}
会话 ID ${esc(task.sessionDisplay || task.sessionId || "--")} ${icon("copy")}
已等待 ${esc(task.wait || "--")}
重试 ${esc(task.retries || "0 次")}
来源消息
${messages}
${taskReviewHint(task)}AI 回复预览
${taskAnswer(task)}${icon("sparkles")} ${esc(task.action || "等待新任务")}
${process}
${taskApprovalButton(task)}${taskCancelButton(task)}${retryButton}${icon("users")}${taskHandoffLabel(task)}
`;
}
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;
const waiting = (STATE.metrics && STATE.metrics.waiting) || "0";
return `
${head("桌面自动化控制台","监听企业微信未读消息,并由 AI 自动生成与发送回复",connected(`
${icon("send")}自动发送 `,"danger"))}${liveRibbonAuto()}${reviewSummary()}
任务队列 ${esc(waiting)} ${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=>`
${name} `).join("");
return `
${head("任务队列","查看待审核、需人工处理及自动回复任务",connected(`
${icon("send")}自动发送 `,"danger"))}${liveRibbonQueue()}${reviewSummary()}
全部任务 ${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.time || "")} ${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 || "全部会话";
const dateValue = STATE.archiveDate || "";
return `
${head("会话归档","保存客户上下文、自动回复与回访线索",`
${esc(STATE.wecomLabel || "企业微信")} ${esc(STATE.aiLabel || "AI 服务")}${icon("download")}导出记录 `)}
会话记录 ${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"];
const tab = STATE.aiTab || "基础设置";
const tabs = ["基础设置","知识与工具","安全策略"].map(n=>`
${n} `).join("");
const lengthIndex = Number(ai.replyLengthIndex ?? 1);
const lengthLabel = ai.replyLength || ["短","中等","长"][lengthIndex] || "中等";
const tempVal = Number(ai.temperature || 0.35);
return `
${head("AI 设置","配置模型、客服人格、知识库与工具能力",`
配置已同步${icon("play")} 测试 AI ${icon("send")}保存并发布 `)}
${tabs}
客服人格 AI
${icon("sparkles")} 实时预览 您好!我是甄养堂${esc(ai.name || "贴心管家")}, 很高兴为您服务 😊
请问有什么可以帮您的吗?
${icon("brain")} 模型引擎 当前模型 ${icon("sparkles")} ${esc(ai.model || "云端模型")}
${icon("tray")} 企业知识库 ${esc(ai.knowledge || "0")}个 MCP 服务
● ${esc(ai.syncAt || "尚未同步")} ✓ ${Number(ai.knowledge||0)?"已连接":"未配置"} ${icon("refresh")}立即同步 ${icon("folder")}管理知识
${icon("link")} MCP 配置 服务数量 ${esc(ai.knowledge || "0")}
工具开关 ${toolsOn.filter(Boolean).length ? "已启用" : "未启用"}
${icon("settings")}编辑服务器 JSON
${icon("shield")} 安全边界 ${icon("shield")} 不做医疗诊断
${icon("pill")} 不修改用药方案
${icon("users")} 高风险转人工
转人工规则 ${icon("shield")} 诊断 ${icon("package")} 用药调整 ${icon("coins")} 投诉退款
${icon("settings")}规则管理
当前模式 ${icon("shield")}严格模式
配置状态 ${statusCells([["cloud", STATE.aiOk ? "云端模型正常" : "AI 服务异常", `最后同步:${esc(ai.syncAt || "尚未同步")}`],["folder","MCP 工具配置",`${esc(ai.knowledge || "0")} 个服务`],["folder",`${esc(ai.toolCount || 0)} 个工具可用`, toolsOn.filter(Boolean).length ? "已启用" : "未启用"],["shield","安全策略已生效","严格模式"]])}`;
};
// Keep the live WebChannel pages on the exact HTML reference structure. Data
// and actions are dynamic, but no extra cards or alternate tab layouts are
// introduced here.
archiveView = function(){
if (STATE.demo) return wireDemoArchive(_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.time || "")} ${esc(x.text || "").replace(/\n/g," ")}
`).join("")
: `
选择会话后显示聊天记录
`;
const tags = (STATE.memory && STATE.memory.tags || []).map((tag,index)=>`
${esc(tag)} `).join("") || `
暂无标签 `;
const settleTokens = ["▣","☎",icon("user")];
const settleStyles = ["","background:linear-gradient(145deg,#9d7cff,#6e5be5)","background:linear-gradient(145deg,#ffc65a,#ff941f)"];
const settle = (STATE.settle || []).map((item,index)=>`
${settleTokens[index] || icon("clipboard")}
${esc(item.title)} ${esc(item.hint)}
${esc(item.value)} `).join("");
const filter = STATE.archiveFilter || "全部会话";
const now = new Date();
const currentDate = `${now.getFullYear()}-${String(now.getMonth()+1).padStart(2,"0")}-${String(now.getDate()).padStart(2,"0")}`;
const dateLabel = STATE.archiveDate || currentDate;
const filterButtons = ["全部会话","今日","AI 已回复","已转人工"].map(name=>`
${name}${name==="全部会话"?"⌄":""} `).join("");
return `
${head("会话归档","保存客户上下文、自动回复与回访线索",`
${esc(STATE.wecomLabel || "企业微信")} ${esc(STATE.aiLabel || "AI 服务")}${icon("download")}导出记录 `)}
会话记录 ${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) || "选择会话后显示长期上下文。").replace(/\n/g," ")}
◔ 上下文 ${esc((STATE.memory && STATE.memory.rounds) || 0)} 轮
最近更新 ${activities("复制全部记录")}`;
};
aiView = function(){
if (STATE.demo) return wireDemoAi(_aiView());
const ai = STATE.ai || {};
const toolsOn = ai.tools || [true,true,true];
const names = ["挂号登记","客户查询","回访记录"];
const toolTokens = ["▣","⌕","▤"];
const tab = STATE.aiTab || "基础设置";
const tabs = ["基础设置","知识与工具","安全策略"].map(name=>`
${name} `).join("");
const contextValue = Math.max(1,Number(ai.context || 8));
const lengthIndex = Math.max(0,Math.min(2,Number(ai.replyLengthIndex ?? 1)));
const lengthLabel = ["短","中等","长"][lengthIndex];
const knowledgeCount = ai.knowledgeCount || ai.knowledge || "0";
const connectedKnowledge = ai.knowledgeConnected !== undefined ? Boolean(ai.knowledgeConnected) : Number(ai.knowledge || 0) > 0;
const syncLabel = ai.syncLabel || ai.syncAt || "尚未同步";
const toolCount = Number(ai.toolCount ?? toolsOn.filter(Boolean).length);
const configLabel = ai.synced === false ? "配置未同步" : "配置已同步";
const plan = ai.plan || {};
const knowledgeStatus = connectedKnowledge ? "知识库已同步" : "知识库未连接";
const toolStatus = toolCount === 3 ? "全部运行正常" : `${toolCount} 个已启用`;
const reviewRules = ai.reviewRules || [];
const reviewRulesHtml = reviewRules.length
? reviewRules.map(r=>`
${icon("shield")} ${esc(r.label)} ${r.enabled?"已启用":"已停用"}
${esc((r.keywords||[]).slice(0,6).join("、"))}${(r.keywords||[]).length>6?" 等":""}
`).join("")
: `
暂无关键词规则
`;
const blanketReview = (STATE.sendMode || "auto") === "review";
return `
${head("AI 设置","配置模型、客服人格、知识库与工具能力",`
${configLabel}▷ 测试 AI ${icon("send")}保存并发布 `)}
${tabs}
客服人格 AI
✧ 实时预览 您好!我是甄养堂${esc(ai.name || "贴心管家")}, 很高兴为您服务 😊
请问有什么可以帮您的吗?
✣ 模型编排 由后台决定,本机只执行 答题模型: ${esc(plan.answer || "后台未配置")}
裁判模型: ${esc(plan.judge || "未启用")}
裁判模式: ${esc(plan.mode || "—")}
编排版本: ${plan.version ? "v" + esc(plan.version) : "—"}
上下文轮数: ${esc(ai.context || 8)} 轮
网关:${esc(plan.gateway || "未下发——模型调用会失败,请去「系统设置 → 后台连接」")}
最后同步:${esc(plan.syncedAt || "尚未同步")}
模型接口、密钥、温度、最大 tokens 现在统一在后台的「AI 模型 → 模型清单 / 角色编排」 里配。 上下文轮数和 MCP 在「桌面端 → 客户端配置」 里配,随配置下发——在这台机器上改会被下一次同步覆盖。
▰ 企业知识库 ${esc(knowledgeCount)}个知识片段
● ${esc(syncLabel)} ✓ ${connectedKnowledge?"已连接":"未连接"} ${icon("refresh")}立即同步 ${icon("folder")}管理知识
◇ MCP 工具 单次最多调用 ${esc(ai.mcpRounds || 5)} 轮 · 由后台下发 工具清单和调用轮数在后台的「桌面端 → 客户端配置 → MCP 服务器」 里配,随配置下发。 在这台机器上改会被下一次同步覆盖,所以这里只展示不修改。
${icon("shield")} 选择性审核规则 由后台决定,本机只执行 ${reviewRulesHtml}
命中任意一条,这一条回复才会停下来等人工确认;其余照常自动发送。模型裁判判定为高风险时同样会转人工,那一条不在这份清单里、关不掉。
在后台的「桌面端 → 客户端配置 → 审核规则」 里配置,随配置下发。
发送模式 ${icon("shield")}${blanketReview?"全部审核后发送":"自动发送(命中规则单独审核)"}
在「自动化设置」页顶部切换;这个总开关和上面的选择性规则是 OR 的关系,任一个成立都会拦下。
配置状态 ${statusCells([["cloud",STATE.aiOk===false?"AI 服务异常":"云端模型正常","最后检查:刚刚"],["folder",knowledgeStatus,`${esc(knowledgeCount)} 个知识片段`],["folder",`${toolCount} 个工具可用`,toolStatus],["shield","安全策略已生效","严格模式"]])}`;
};
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);
const recipientId = String(recipient.id || "").trim();
const recipientName = String(recipient.displayName || "").trim() || String(recipient.name || "").trim();
const nameKnown = recipient.nameKnown !== false && Boolean(recipientName) && recipientName !== "微信联系人";
const recipientTitle = !recipientId ? "尚未选择助理私人微信" : nameKnown ? recipientName : "昵称未读取,请核对联系编号";
return `
医疗助理审核提醒 ${r.enabled ? "当前账号已开启" : "当前账号未开启"}
客户会话进入待审核时,由当前托管企业微信账号向指定助理的私人微信发送客户咨询、待发送回复和审核原因。
托管企微账号(最近核验) ${esc(r.accountName || "尚未核验")} ${r.accountId ? `${esc(r.accountId)} ` : ""}
${icon("user")}
${recipientId ? "已配置收件人" : "未配置收件人"} 助理私人微信
${esc(recipientTitle)}
${recipientId ? `
联系编号 ${esc(recipientId)}
` : ""}
${icon("refresh")}${busy ? "正在核验…" : "读取当前账号"}
${recipient.id ? "更换助理联系人" : "选择助理联系人"}
开启审核提醒
${r.reason ? `${esc(r.reason)}
` : ""}
${r.message ? `${esc(r.message)}
` : ""}
${r.notification && r.notification.message ? `最近提醒(读取账号时刷新):${esc(r.notification.message)}
` : ""}
设置按企微账号分别保存。选择或更换联系人后默认关闭,确认收件人后再开启;同名联系人按列表中的联系编号区分。
人工用同一企业微信账号直接回复客户后结束本次审核;后续自动回复仍逐条风控。此识别以账号发出的消息为准,不区分手机或电脑。在助理私人微信回复提醒,不会解除客户审核。
提醒内容示例(只读,不会发送) ${esc(r.preview || "请先读取当前账号")} `;
}
automationView = function(){
if (STATE.demo) return _automationView();
const a = STATE.automation || {};
const autoOn = (STATE.sendMode || "auto") === "auto";
return `
${head("自动化设置","配置企业微信监听、消息合并与自动发送规则",`
${esc(a.wecom || "企业微信未识别")} 规则已生效${icon("save")}保存设置 `)}
${icon("send")} 自动发送 ${icon("users")} 审核后发送
${reviewAssistantPanel(a)}
监听与发送策略 ${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")} 最小化恢复: ● 已开启
${icon("refresh")}重新检测
消息处理 ✓ 同一客户连续消息自动合并
✓ 保留最近 轮上下文 ${esc(a.context || 8)} 轮
✓ 一次只处理 1 个会话 1 个
异常与兜底 ${icon("clock")} AI 超时:${esc(a.timeout || 120)} 秒后重试 2 次
${icon("alert")} 发送失败:自动重试 已开启
${icon("message")} 固定回复:${esc(a.reply || "在的,请稍等")} 编辑
高风险转人工 ● 已开启 ${icon("shield")} 诊断 ${icon("package")} 用药调整 ${icon("coins")} 投诉退款
${icon("settings")}规则管理
启动前检查 ${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=>`
✓
${esc(x.time)} ● ${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:[]};
const chain = (STATE.chain && STATE.chain.length ? STATE.chain : [{icon:"activity",title:"扫描未读",time:""},{icon:"message",title:"读取消息",time:""},{icon:"brain",title:"请求 AI",time:""},{icon:"send",title:"回填发送",time:""}]);
return `
${head("运行日志","实时追踪连接、消息识别、AI 调用与自动发送事件",`
${icon("refresh")}实时刷新 ${icon("download")}导出日志 ${icon("tray")} 清空日志 ${esc(STATE.statusLabel || "监控中")}`)}${liveRibbonLogs()}
实时事件流 ${["全部","连接","识别","AI","发送","警告"].map(n=>`${n} `).join("")}${icon("search")}
${list}
系统健康 ${[["企业微信连接", STATE.wecomConnected ? "正常" : "待连接"],["AI 服务", STATE.aiOk ? "正常" : "异常"],["窗口识别", STATE.wecomConnected ? "正常" : "待监听"]].map(x=>`
${icon("check")}
${x[0]} ${x[1]} `).join("")}
${icon("clock")}运行时长 ${esc(STATE.runtime || "00:00:00")}
当前回复链路 ${chain.map((x,i)=>`
${icon(x.icon || "activity")}
${esc(x.title)} ${esc(x.time || "")} `).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("查看完整记录")}`;
};
wechatDbView = function(){
const db = STATE.wechatDb || {};
const accounts = db.accounts || [];
const conversations = db.conversations || [];
const messages = db.messages || [];
const selectedAccount = db.selectedAccount || "";
const selectedConversation = db.selectedConversation || "";
const currentAccount = accounts.find(item=>String(item.id)===String(selectedAccount)) || accounts[0] || null;
const currentConversation = conversations.find(item=>String(item.id)===String(selectedConversation)) || conversations[0] || null;
const patientContext = db.patientContext || {};
const patientBindings = patientContext.patient_bindings || [];
const patientBinding = patientBindings.find(item=>item.is_primary) || patientBindings[0] || null;
const patientPerson = patientContext.person || null;
const patientPermissions = patientContext.permissions || {};
const canSearchPatient = patientPermissions.can_search === true;
const canBindPatient = patientPermissions.can_bind === true;
const canUnbindPatient = patientPermissions.can_unbind === true;
const accountRows = accounts.length ? accounts.map(item=>`
${icon("database")} ${esc(item.name || item.id)} ${esc(item.databaseCount || 0)} 个库
${esc(item.id)} · ${esc(item.sizeLabel || "0 B")} ${esc(item.conversationCount || 0)} 个会话 ${esc(item.messageCount || 0)} 条消息 `).join("") : `
没有检测到可读取的账号数据库
`;
const files = (db.files || (currentAccount && currentAccount.files) || []);
const fileRows = files.length ? files.map(file=>`
${icon("database")} ${esc(file.name)} ● ${esc(file.status || "未知")} ${esc(file.sizeLabel || "0 B")} ${esc(file.rows || 0)} 行 ${esc(file.modified || "")}
`).join("") : `
当前账号没有数据库文件
`;
const conversationRows = conversations.length ? conversations.map(item=>`
${esc(item.name || item.id)} ${esc(item.lastTime || "")} ${esc(item.kind || "会话")} · ${esc(item.messageCount || 0)} 条 · ${esc(item.lastDirection || "")} ${esc(item.preview || "暂无消息内容")} `).join("") : `
没有符合条件的会话
`;
const messageRows = messages.length ? messages.map(item=>`
${esc(item.direction==="outbound"?"我":"客户")} ${esc(item.sender || "")} ${esc(item.time || "")} · ${esc(item.type || "消息")} ${esc(item.content || "")}
`).join("") : `
${currentConversation?"当前会话没有可显示的消息":"请先选择一个会话"}
`;
const cloudBackup = db.cloudBackup || {};
const cloudNotice = cloudBackup.message ? `
${icon(cloudBackup.status === "completed" ? "check" : "cloud")} ${esc(cloudBackup.message)}
` : "";
const notice = db.error ? `
${icon("alert")} ${esc(db.error)}
` : (db.loading ? `
${icon("refresh")} ${esc(db.loadingMessage || "正在读取最新数据库,请稍候…")}
` : (db.warning ? `
${icon("info")} ${esc(db.warning)}
` : ""));
const actionState = db.loading ? `
${icon("refresh")}读取中 ` : db.error ? `
${icon("alert")}未就绪 ` : `
${accounts.length ? ' ' : ''}${esc(accounts.length ? (db.warning ? "已有缓存" : "已读取") : "等待读取")} `;
const setup = !db.loading && db.manualSetupRequired ? `
${icon("folder")}选择数据目录 获取本机密钥 导入密钥 ${esc(db.sourceRoot || "自动处理未完成,可手动选择目录、重新获取密钥或导入密钥文件。")}
` : "";
const patientControl = !currentConversation ? "" : patientContext.loading
? `
${icon("refresh")} 患者关联加载中 `
: !canSearchPatient
? `
${icon("user")} ${esc(patientPermissions.reason || patientContext.error || patientContext.reason || "正在读取患者权限")} `
: patientBinding
? `
${icon("check")} 已绑定:${esc(patientBinding.patient_name || "未命名患者")} ${esc(patientBinding.phone_masked || "")} ${canBindPatient ? `
更换患者 ` : ""}${canUnbindPatient ? `
解除绑定 ` : ""}`
: `
${icon("user")} ${esc(patientPerson ? (patientPerson.display_name || "客户") + " · 未绑定患者" : (patientContext.reason || "未绑定患者"))} ${canBindPatient ? `
${esc(patientContext.requires_person_selection ? "选择联系人并绑定" : "绑定 ZYT 患者")} ` : ""}`;
return `
${head("微信消息库","查看本机企业微信数据库、会话与收发消息明细",`${actionState}
${icon("refresh")}刷新消息库 `)}${ribbon([["本机账号",esc(db.accountCount || 0),"users","green"],["数据库文件",esc(db.databaseCount || 0),"database"],["会话总数",esc(db.conversationCount || 0),"message"],["消息总数",esc(db.messageCount || 0),"layers","","blue"]])}${notice}${cloudNotice}${setup}
消息库账号 ${esc(currentAccount ? currentAccount.updated || "" : "")} ${accountRows}
数据库文件 ${esc(currentAccount ? currentAccount.sizeLabel || "" : "")} ${fileRows}
${esc(currentConversation ? currentConversation.name : "会话消息")} ${esc(messages.length)} 条 ${icon("user")} ${esc(currentConversation ? currentConversation.kind || "会话" : "未选择")} ${esc(currentConversation ? currentConversation.id : "")} ${patientControl}
${messageRows}
`;
};
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("refresh")}检查更新 ${icon("save")}保存设置 `)}
基础设置 ${icon("activity")}
启动后自动监听
${icon("log")}
关闭窗口时保持后台运行
${icon("globe")}界面语言 简体中文
${icon("monitor")}缩放比例 ${["自动","100%","125%","150%","200%"].map(n=>`${n} `).join("")}
数据与存储 ${esc(s.storage || "—")}已使用
● 会话归档 ${esc(s.archiveSize || "—")}
● 运行日志 ${esc(s.logSize || "—")}
● 缓存数据 ${esc(s.cacheSize || "—")}
数据保留 天
${icon("folder")}打开目录 ${icon("cloud")}立即备份 ${icon("wifi")} 清理缓存
通知提醒 ${icon("hourglass")}
模型超时提醒
${icon("bell")}
发送失败提醒 消息发送失败,已加入重试队列
${esc(s.noticeTime || "—")}●
后台连接 后台地址
${icon("cloud")}配置同步: ${esc(s.syncState || "未检测")}
${icon("wifi")}模型网关: ${esc(s.gatewayState || "未同步")}
${esc(s.gatewayUrl || "网关地址由后台下发,连接成功后自动填入")}
${icon("wifi")} 保存并连接 模型、密钥和角色编排都在后台配,本机只需要填这一个地址
维护与诊断 ${icon("refresh")}
检查更新 ${esc(s.updateLabel || "已是最新版本")}
${icon("clipboard")}
导出诊断 导出系统诊断报告
${icon("refresh")}
恢复默认设置 重置所有设置项
${icon("shield")}
查看许可信息 查看软件许可详情
系统状态 ${statusCells([["shield","配置已保存","所有设置已生效"],["cloud","最近备份", esc(s.lastBackup || "尚未备份")],["shield","安全策略正常","所有安全策略已启用"],["monitor","后台服务", STATE.listening ? "运行中" : "已停止"]])}`;
};
views = {auto:autoView,queue:queueView,archive:archiveView,ai:aiView,automation:automationView,logs:logsView,wechatdb:wechatDbView,system:systemView};
function syncAttentionBadges(){
const counts = STATE.attention || STATE.metrics || {};
const review = Math.max(0, Math.trunc(Number(counts.review) || 0));
const human = Math.max(0, Math.trunc(Number(counts.human) || 0));
const total = review + human;
const detail = total ? `待处理 ${total} 个会话(待审核 ${review},需人工 ${human})` : "暂无待审核或需人工处理的会话";
[["auto", "自动回复"], ["queue", "任务队列"]].forEach(([view, label]) => {
const button = document.querySelector(`.nav-btn[data-view="${view}"]`);
const badge = button && button.querySelector(".nav-attention-badge");
if (!badge) return;
badge.hidden = total === 0;
badge.textContent = total > 99 ? "99+" : String(total);
button.title = detail;
button.setAttribute("aria-label", `${label},${detail}`);
});
}
function syncOrb(){
syncAttentionBadges();
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 ? "监听中" : "已停止");
// 侧栏这颗球就是开始/暂停按钮,提示语要跟着状态走
const listener = document.getElementById("listener");
if (listener) listener.title = STATE.listening ? "点击暂停监听" : "点击开始监听";
}
function coreFingerprint(state){
const sys = state.system || {};
const ai = state.ai || {};
const auto = state.automation || {};
const metrics = state.metrics || {};
const view = state.view || "auto";
const common = {
view: state.view,
demo: state.demo,
listening: state.listening,
wecomConnected: state.wecomConnected,
aiOk: state.aiOk,
wecomLabel: state.wecomLabel,
aiLabel: state.aiLabel,
};
const people = (state.people || []).map(p => [
p.key, p.name, p.status, p.bubbles, p.avatar, p.tone, p.dot
]);
const task = state.task ? {
key: state.task.key,
name: state.task.name,
sessionId: state.task.sessionId,
sessionDisplay: state.task.sessionDisplay,
action: state.task.action,
stepIndex: state.task.stepIndex,
answer: state.task.answer,
awaitingReview: state.task.awaitingReview,
canApprove: state.task.canApprove,
canRetry: state.task.canRetry,
deliveryUncertain: state.task.deliveryUncertain,
needsHuman: state.task.needsHuman,
reviewReason: state.task.reviewReason,
isProtocol: state.task.isProtocol,
foreignDraft: state.task.foreignDraft,
messageCount: state.task.messageCount,
confidence: state.task.confidence,
messages: (state.task.messages || []).map(message => [message.time, message.text]),
steps: (state.task.steps || []).map(step => [step[0], step[1]]),
} : null;
const metricModes = [Number(metrics.failed) > 0, Number(metrics.warnings) > 0, Number(metrics.review) || 0, Number(metrics.human) || 0];
let page;
if (view === "auto") {
page = {
selectedKey: state.selectedKey,
queueFilter: state.queueFilter,
queueSort: state.queueSort,
people,
task,
journey: (state.journey || []).map(j => [j.title, j.status, j.mode, j.done, j.active]),
windowStats: state.windowStats,
metricModes,
};
} else if (view === "queue") {
page = {
selectedKey: state.selectedKey,
queueFilter: state.queueFilter,
queueSort: state.queueSort,
people,
task,
metricModes,
};
} else if (view === "archive") {
page = {
archiveFilter: state.archiveFilter,
archiveQuery: state.archiveQuery,
archiveDate: state.archiveDate,
selectedArchive: state.selectedArchive,
archives: (state.archives || []).map(a => [
a.key, a.badge, a.count, a.name, a.time, a.preview, a.avatar, a.tone
]),
chat: (state.chat || []).map(message => [message.role, message.time, message.text]),
memory: state.memory,
settle: state.settle,
};
} else if (view === "ai") {
page = {aiTab: state.aiTab, ai};
} else if (view === "automation") {
page = {sendMode: state.sendMode, dpi: state.dpi, auto, tray: sys.tray};
} else if (view === "logs") {
page = {
statusLabel: state.statusLabel,
eventFilter: state.eventFilter,
eventQuery: state.eventQuery,
exceptions: state.exceptions,
logDetail: state.logDetail,
chain: (state.chain || []).map(item => [item.icon, item.title]),
metricModes,
};
} else if (view === "wechatdb") {
const db = state.wechatDb || {};
page = {
wechatDb: {
loading: db.loading,
loadingMessage: db.loadingMessage,
manualSetupRequired: db.manualSetupRequired,
sourceRoot: db.sourceRoot,
cloudBackup: db.cloudBackup,
error: db.error,
warning: db.warning,
query: db.query,
refreshedAt: db.refreshedAt,
selectedAccount: db.selectedAccount,
selectedConversation: db.selectedConversation,
accountCount: db.accountCount,
databaseCount: db.databaseCount,
conversationCount: db.conversationCount,
messageCount: db.messageCount,
accounts: (db.accounts || []).map(item=>[item.id,item.name,item.databaseCount,item.messageCount,item.conversationCount,item.sizeLabel,item.updated]),
files: (db.files || []).map(item=>[item.name,item.sizeLabel,item.modified,item.rows,item.status]),
conversations: (db.conversations || []).map(item=>[item.id,item.name,item.kind,item.messageCount,item.lastTime,item.preview,item.lastDirection]),
messages: (db.messages || []).map(item=>[item.id,item.sender,item.direction,item.time,item.type,item.content]),
patientContext: db.patientContext || {},
},
};
} else {
const {
storage: _storage,
archiveSize: _archiveSize,
logSize: _logSize,
cacheSize: _cacheSize,
noticeTime: _noticeTime,
...systemStructure
} = sys;
page = {system: systemStructure};
}
return JSON.stringify({common, page});
}
function logsFingerprint(state){
return JSON.stringify((state.logs || []).map(x => [x.time, x.title, x.detail, x.state]));
}
function logRowsHtml(events){
events = (events || []).slice(-5);
if (!events.length){
return `
当前没有运行记录
`;
}
return events.map(x=>`
✓
${esc(x.time)} ● ${icon(x.icon || "activity")}
${esc(x.title)} ${esc(x.detail)}
${esc(x.state || "")} `).join("");
}
function wireDemoArchive(html){
let rowIndex = 0;
return html
.replace('data-action="toast">' + icon("download") + '导出记录', 'data-action="export-archive">' + icon("download") + '导出记录')
.replace('
', '
')
.replace('
全部会话⌄ ', '
全部会话⌄ ')
.replace('
今日 ', '
今日 ')
.replace('
AI 已回复 ', '
AI 已回复 ')
.replace('
已转人工 ', '
已转人工 ')
.replace('
2026-08-18 □ ', '
2026-08-18 □ ')
.replace(/
/g, (_match, classes) => `
`)
.replace('
' + icon("refresh") + ' ', '
' + icon("refresh") + ' ')
.replace('data-action="logs">复制全部记录', 'data-action="copy-archive">复制全部记录');
}
function wireDemoAi(html){
const activeTab = STATE.aiTab || "基础设置";
let toolIndex = 0;
return html
.replace('
配置已同步', '
配置已同步')
.replace('
▷ 测试 AI ', '
▷ 测试 AI ')
.replace('
' + icon("send") + '保存并发布 ', '
' + icon("send") + '保存并发布 ')
.replace('
基础设置 知识与工具 安全策略
', `
${["基础设置","知识与工具","安全策略"].map(name=>`${name} `).join("")}
`)
.replace('
', '
')
.replace('