assistant/docs/方案图解.html

347 lines
87 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="zh-CN" data-theme="light">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='7' fill='%2314695e'/%3E%3Cpath d='M8 24 16 8l8 16M11 18h10' fill='none' stroke='white' stroke-width='2'/%3E%3C/svg%3E">
<title>AI 获客助手 方案图解</title>
<style>
:root{--bg:#f5f6f7;--surface:#fff;--soft:#f0f2f3;--text:#1d252b;--muted:#5c6973;--line:#d9dfe3;--accent:#14695e;--tint:#e5f2ee;--feedback:#94601c;--feedback-bg:#faf0df;--arrow:#7f8b95;--focus:#14695e;--node:#fff;--group:#f5f7f8;--group-line:#dce2e6;--active:#14695e}
:root[data-theme="dark"]{--bg:#151a1f;--surface:#1d242b;--soft:#252e37;--text:#edf0f2;--muted:#a9b6c0;--line:#394550;--accent:#83cbbb;--tint:#253e39;--feedback:#e0b46e;--feedback-bg:#403421;--arrow:#98a7b2;--focus:#83cbbb;--node:#202931;--group:#1a2229;--group-line:#3a4650;--active:#83cbbb}
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font:14px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI","Microsoft YaHei",sans-serif}button,input,select{font:inherit}button,a,input,select{-webkit-tap-highlight-color:transparent}button{color:inherit;cursor:pointer}a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible{outline:3px solid var(--focus);outline-offset:3px}button:disabled{opacity:.4;cursor:default}
.shell{max-width:1600px;margin:auto;padding:30px 32px 22px}.mast{display:flex;align-items:flex-start;justify-content:space-between;gap:24px}.eyebrow{color:var(--muted);font-size:11px;font-weight:600;letter-spacing:.14em;text-transform:uppercase}.mast h1{font-size:27px;line-height:1.4;font-weight:650;letter-spacing:.02em;margin:8px 0}.intro{max-width:820px;color:var(--muted);margin:0;font-size:14px}.mast-actions{display:flex;gap:9px;align-items:center;flex-shrink:0;padding-top:4px}.pill{font-size:12px;padding:4px 10px;border:1px solid var(--line);border-radius:5px;white-space:nowrap}.btn{display:inline-flex;justify-content:center;align-items:center;gap:6px;min-height:35px;border:1px solid var(--line);border-radius:6px;background:var(--surface);padding:5px 11px;white-space:nowrap;font-size:12px}.btn:hover{border-color:var(--accent);background:var(--tint);text-decoration:none}.btn.primary{background:var(--accent);border-color:var(--accent);color:var(--surface)}[data-theme="dark"] .btn.primary{color:#15241f}.facts{display:flex;gap:22px;flex-wrap:wrap;font-size:12px;color:var(--muted);padding:18px 0}.facts b{color:var(--text);font-weight:600;margin-right:6px}.tabs{display:flex;gap:3px;border-bottom:1px solid var(--line);overflow-x:auto;scrollbar-width:thin}.tab{flex-shrink:0;display:flex;gap:9px;align-items:center;background:none;border:0;border-bottom:3px solid transparent;padding:13px 19px 12px;color:var(--muted);font-size:14px}.tab .index{font-size:10px;font-variant-numeric:tabular-nums;opacity:.8}.tab[aria-selected="true"]{color:var(--accent);border-bottom-color:var(--accent);font-weight:600}.tab:hover{background:var(--soft);color:var(--text)}
.workspace{display:grid;grid-template-columns:minmax(0,1fr) 292px;gap:20px;margin-top:20px;align-items:start}.diagram-panel{min-width:0;background:var(--surface);border:1px solid var(--line);border-radius:9px;overflow:hidden}.panel-head{padding:17px 20px 12px;display:flex;justify-content:space-between;align-items:start;gap:20px}.panel-head h2{font-size:17px;line-height:1.4;margin:0 0 5px;font-weight:650}.panel-head p{margin:0;color:var(--muted);font-size:12px;max-width:780px}.count{font-size:11px;color:var(--muted);white-space:nowrap;padding-top:2px}.toolbar{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:9px;padding:10px 16px;border-block:1px solid var(--line);background:var(--soft)}.tool-group{display:flex;gap:6px;align-items:center}.tool-group .btn{min-height:30px;padding:3px 9px}.zoom{display:inline-flex;justify-content:center;min-width:39px;font-size:11px;color:var(--muted);font-variant-numeric:tabular-nums}.focus-toggle{font-size:12px;color:var(--muted);display:flex;gap:5px;align-items:center;white-space:nowrap}.focus-toggle input{accent-color:var(--accent)}.viewport{overflow:auto;overscroll-behavior:contain;scrollbar-width:thin}.viewport svg{display:block;width:100%;min-width:820px;height:auto}.legend{display:flex;gap:22px;align-items:center;flex-wrap:wrap;padding:10px 19px 12px;border-top:1px solid var(--line);color:var(--muted);font-size:11px}.legend-item{display:flex;align-items:center;gap:7px}.sample{width:26px;border-top:1.5px solid var(--arrow)}.sample.optional{border-top-style:dashed}.sample.feedback{border-top-style:dashed;border-color:var(--feedback)}.guide{margin-left:auto}
.playback{display:flex;align-items:center;flex-wrap:wrap;gap:8px;padding:13px 18px;border-top:1px solid var(--line);font-size:12px}.playback select{max-width:230px;color:var(--text);background:var(--surface);border:1px solid var(--line);border-radius:5px;min-height:32px;padding:4px 6px}.play-label{color:var(--muted);margin-right:2px}.step-label{font-variant-numeric:tabular-nums;color:var(--muted);font-size:11px;flex:1;min-width:125px}.step-label b{color:var(--text);font-weight:500}.notes{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:18px}.note{padding-top:12px;border-top:1px solid var(--line)}.note h3{font-size:12px;margin:0 0 5px}.note p{font-size:12px;color:var(--muted);margin:0;line-height:1.8}
.inspector{min-width:0;position:sticky;top:18px}.search-box{position:relative;margin-bottom:15px}.search-box input{width:100%;height:38px;border:1px solid var(--line);border-radius:6px;background:var(--surface);padding:7px 12px;color:var(--text);font-size:12px}.results{position:absolute;z-index:5;top:44px;inset-inline:0;background:var(--surface);border:1px solid var(--line);border-radius:6px;max-height:360px;overflow:auto}.results:empty{display:none}.result{display:block;width:100%;padding:9px 12px;border:0;border-bottom:1px solid var(--line);background:none;text-align:left;font-size:12px}.result:last-child{border-bottom:0}.result:hover{background:var(--tint)}.result small{display:block;color:var(--muted);font-size:10px}.search-empty{padding:12px;color:var(--muted);font-size:12px}.detail{border:1px solid var(--line);border-radius:8px;background:var(--surface);padding:19px}.detail-kicker{font-size:10px;color:var(--muted);letter-spacing:.08em}.detail h3{margin:9px 0 8px;font-size:18px;line-height:1.5}.status-tag{display:inline-block;background:var(--soft);padding:3px 7px;border-radius:4px;font-size:10px;color:var(--muted);margin-bottom:13px}.detail-desc{font-size:12px;line-height:1.85;margin:0 0 15px}.detail dl{margin:0}.detail dt{font-size:10px;font-weight:600;letter-spacing:.06em;color:var(--muted);margin:15px 0 4px}.detail dd{font-size:12px;line-height:1.85;margin:0}.detail ul{padding-left:17px;margin:4px 0 0}.detail li{font-size:12px;line-height:1.8;margin:5px 0}.detail-source{border-top:1px solid var(--line);margin-top:17px;padding-top:12px;font-size:11px;color:var(--muted)}.side-foot{color:var(--muted);font-size:11px;margin:12px 3px;line-height:1.8}.footer{display:flex;gap:20px;justify-content:space-between;color:var(--muted);font-size:11px;padding-top:22px;margin-top:20px;border-top:1px solid var(--line)}.notice{position:fixed;bottom:22px;left:50%;transform:translateX(-50%);background:var(--text);color:var(--surface);padding:10px 18px;border-radius:7px;z-index:20;font-size:12px;max-width:90vw;pointer-events:none}.notice:empty{display:none}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
svg text{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Microsoft YaHei",sans-serif}svg .paper{fill:var(--surface)}svg .svg-heading{fill:var(--text);font-size:18px;font-weight:600}svg .svg-sub{fill:var(--muted);font-size:11px}svg .group-rect{fill:var(--group);stroke:var(--group-line);stroke-width:1}svg .group-title{fill:var(--muted);font-size:12px;font-weight:600}svg .node{cursor:pointer;outline:none}svg .node-box{fill:var(--node);stroke:var(--line);stroke-width:1.2}svg .node-title{fill:var(--text);font-size:15px;font-weight:550}svg .node-sub{fill:var(--muted);font-size:11px}svg .node-badge{fill:var(--muted);font-size:9px}svg .node:hover .node-box,svg .node:focus-visible .node-box{stroke:var(--accent);stroke-width:2}svg .node.selected .node-box{stroke:var(--accent);stroke-width:2;fill:var(--tint)}svg .node.route .node-box{stroke:var(--accent);stroke-width:1.8}svg .node.current .node-box{fill:var(--tint);stroke:var(--accent);stroke-width:2.3}svg .node.dim{opacity:.25}svg .edge{fill:none;stroke:var(--arrow);stroke-width:1.35;stroke-linejoin:round;stroke-linecap:round}svg .edge.optional{stroke-dasharray:6 5}svg .edge.feedback{stroke:var(--feedback);stroke-dasharray:5 4}svg .edge.highlight{stroke:var(--accent);stroke-width:2.2}svg .edge.dim{opacity:.2}svg .edge-label rect{fill:var(--surface)}svg .edge-label text{fill:var(--muted);font-size:10px}svg .edge-label.feedback text{fill:var(--feedback)}svg .edge-label.dim{opacity:.2}svg .arrow-tip{fill:var(--arrow)}svg .arrow-tip.active{fill:var(--accent)}svg .arrow-tip.feedback{fill:var(--feedback)}
@media(min-width:1700px){.shell{padding-top:40px}.workspace{grid-template-columns:minmax(0,1fr) 310px;gap:24px}}
@media(max-width:1120px){.workspace{grid-template-columns:minmax(0,1fr)}.inspector{position:static;display:grid;grid-template-columns:250px 1fr;gap:18px}.detail{grid-column:2;grid-row:1 / span 2}.search-box{margin:0}.side-foot{grid-column:1;align-self:start}.mast h1{font-size:24px}.guide{margin-left:0}}
@media(max-width:680px){.shell{padding:20px 14px}.mast{display:block}.mast-actions{padding-top:15px}.intro{font-size:12px}.facts{gap:8px 18px;padding:14px 0}.tab{padding:12px 13px;font-size:12px}.workspace{margin-top:14px;gap:14px}.panel-head{padding:15px}.panel-head h2{font-size:16px}.count{display:none}.toolbar{padding:8px 10px}.tool-group{gap:4px}.tool-group .btn{padding:3px 8px}.focus-toggle{font-size:11px}.playback{padding:11px}.playback select{max-width:185px}.step-label{flex-basis:100%;margin:1px 0 0}.legend{gap:10px 16px;padding:10px 12px}.guide{display:none}.notes{grid-template-columns:1fr;gap:12px}.inspector{display:block}.search-box{margin-bottom:12px}.detail{padding:16px}.footer{display:block}.footer span{display:block;margin:4px 0}}
@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important}}
@media print{body{background:white;color:black}.shell{padding:0}.mast-actions,.tabs,.toolbar,.playback,.inspector,.notes,.footer,.legend,.facts{display:none}.workspace{display:block;margin:10px 0}.diagram-panel{border:0}.viewport{overflow:visible}.viewport svg{width:100%!important;min-width:0!important}.panel-head{padding:0 0 10px}.notice{display:none}}
</style>
</head>
<body>
<main class="shell">
<header class="mast">
<div><div class="eyebrow">ASSISTANT / PRODUCT &amp; ENGINEERING</div><h1>AI 获客助手 方案图解</h1><p class="intro">从商家配置商品,到发现需求、形成线索与跟进;将平台操作学习为经过验证的能力,再由桌面端重复执行。</p></div>
<div class="mast-actions"><span class="pill">v0.2 · 设计阶段</span><button class="btn" id="theme" type="button">切换深色</button><a class="btn" href="整体方案-v0.2.md">文字方案 ↗</a></div>
</header>
<div class="facts"><span><b>当前</b>功能路径学习 + 轻量召回</span><span><b>桌面后续</b>Windows + macOS</span><span><b>模型</b>运营后台按用途配置</span><span><b>更新</b>2026.10.25</span></div>
<nav class="tabs" id="tabs" role="tablist" aria-label="方案图页"></nav>
<div class="workspace">
<section>
<div class="diagram-panel" id="diagram-panel">
<div class="panel-head"><div><h2 id="diagram-title"></h2><p id="diagram-description"></p></div><span class="count" id="diagram-count"></span></div>
<div class="toolbar">
<div class="tool-group"><button class="btn" id="zoom-out" aria-label="缩小" type="button">−</button><span class="zoom" id="zoom-value">100%</span><button class="btn" id="zoom-in" aria-label="放大" type="button">+</button><button class="btn" id="fit" type="button">适应宽度</button><label class="focus-toggle"><input id="focus" type="checkbox">聚焦关联</label></div>
<div class="tool-group"><button class="btn" id="export-svg" type="button">导出 SVG</button><button class="btn" id="export-png" type="button">导出 PNG</button></div>
</div>
<div class="viewport" id="viewport" role="region" aria-label="可滚动方案图" tabindex="0"></div>
<div class="legend"><span class="legend-item"><span class="sample"></span>固定职责或流程</span><span class="legend-item"><span class="sample optional"></span>可选或后续</span><span class="legend-item"><span class="sample feedback"></span>异常或修订</span><span class="guide">点击节点查看细节 · 窄屏可横向滚动</span></div>
<div class="playback"><span class="play-label">路径演示</span><select id="scenario" aria-label="选择演示路径"></select><button class="btn" id="prev" aria-label="上一步" type="button">上一步</button><button class="btn primary" id="play" type="button">播放</button><button class="btn" id="next" aria-label="下一步" type="button">下一步</button><span class="step-label" id="step-label"></span><button class="btn" id="reset" type="button">清除</button></div>
</div>
<div class="notes"><section class="note"><h3 id="note-title-a"></h3><p id="note-a"></p></section><section class="note"><h3 id="note-title-b"></h3><p id="note-b"></p></section></div>
</section>
<aside class="inspector" aria-label="节点详情">
<div class="search-box"><label class="sr-only" for="search">搜索所有图页的节点</label><input type="search" id="search" placeholder="搜索节点,例如 变参 / 额度 / 恢复" autocomplete="off" aria-controls="results" aria-expanded="false"><div class="results" id="results"></div></div>
<div class="detail" id="detail" aria-live="polite"></div>
<p class="side-foot">当前按功能模块逐层盘点网页入口,业务分类、完整 A2A 与桌面工程后置。图中包含未来整体设计;真实平台、召回质量与双端交付分别验收。路径播放仅演示设计关系。</p>
</aside>
</div>
<footer class="footer"><span>来源:AI 获客助手整体方案 v0.2 · 2026 年 10 月 25 日</span><span>独立离线文件 · 无外部资源依赖</span></footer>
</main>
<div class="notice" id="notice" role="status"></div>
<script>
"use strict";
// 同步来源 docs/整体方案-v0.2.md。图页数据与渲染器均内联,文件可离线打开。
const N=(id,x,y,w,title,sub,desc,input,output,checks,source,status="proposed",h=74)=>({id,x,y,w,h,title,sub,desc,input,output,checks,source,status});
const E=(a,b,label="",opt={})=>({a,b,label,...opt});
const G=(x,y,w,h,title)=>({x,y,w,h,title});
const diagrams=[
{id:"architecture",name:"系统架构",title:"三个环境 一套能力契约",description:"维护端生产能力,商家电脑执行任务,云端管理模型、权益与计量。模块表示职责,可先合并部署。",w:1220,h:842,
groups:[G(25,100,290,640,"维护者环境"),G(345,100,485,640,"商家电脑 · Windows / macOS"),G(860,100,335,640,"云端服务")],
nodes:[
N("observe",50,150,240,"学习与观察","目标范围 · 页面证据","我们在维护环境观察和调试平台链路,记录操作前后状态与未知分支。","平台入口、能力目标、步骤与模型预算","页面证据、操作知识、未覆盖项",["维护工具逐层盘点功能入口","学习成本与商家运行用量分开"],"5.1–5.4"),
N("candidate",50,265,240,"候选能力","参数实现 + 契约 + 用例","手写或由模型辅助产生候选,统一接受固定校验与浏览器试跑。","操作知识与证据","候选实现、输入输出契约、用例",["文字说明或录制结果不能直接发布","学习资料和商家执行包分开"],"5.2–5.3"),
N("verify",50,380,240,"校验与双端试跑","结构校验 · 变参 · 结果核对","先检查产物结构和规则,再验证不同参数、页面状态以及两种操作系统。","候选版本、样本、目标环境","校验记录、试跑结果、缺口",["校验通过与实际执行成功分别记录","Windows 与 macOS 分别通过"],"5.2、11"),
N("learning-db",50,495,240,"学习验证记录库","独立 PostgreSQL + 证据文件","维护者学习端单独记录校验、试跑、结果核对及双端验收,保留每次重跑和修订的历史。","能力版本与校验值、用例、参数、环境与实际结果","可按能力、版本、用例和系统回看的验证记录",["与商家业务数据库隔离","发布清单关联具体通过记录"],"5.3","confirmed"),
N("package",50,610,240,"不可变能力版本","版本号 · 校验值 · 兼容范围","发布满足覆盖范围和验收要求的能力版本,保留旧版本及验证依据。","已验证候选与兼容信息","可发布的能力包与清单",["修改产生新版本","任务绑定创建时的能力版本"],"5.3、8"),
N("desktop",375,150,425,"Electron 桌面界面","账号 · 商品 · 任务 · 线索 · 用量","商家配置业务条件、查看执行结果和接管浏览器。可从测试工程提取窗口、IPC、产品登录和状态展示基础,再适配获客页面。","商家商品条件、任务配置","受限 IPC 任务请求与结果展示",["Windows 与 macOS 首版同时适配,原打包只验证过 Windows","浏览器操作在独立执行进程中运行"],"2、4.1、4.4","confirmed"),
N("runner",375,265,425,"任务与链路执行器","Node.js / TypeScript 建议","编排已发布能力、检查点、动作账目和预算,按用途请求 AI 分析。可借用原进程生命周期接口,接入本项目执行进程。","任务快照、能力版本、账号会话","执行进度、线索、待核对动作",["首版账号内串行执行","原 Python、校园编排和本地 PostgreSQL 不随桌面壳整套引入"],"4、8"),
N("adapter",375,380,195,"平台适配器","平台链路与契约","将通用任务要求映射到某个平台的能力输入输出。","固定版本的能力及参数","平台操作与读取结果",["每个平台分别学习和验证","不把框架对象存为业务契约"],"4.2、5.7"),
N("sqlite",610,380,190,"商家业务 SQLite","任务 · 线索 · 账号目录映射","保存商品、任务、线索、检查点与动作核对结果,以及浏览器目录引用和最后核验时间。","执行进展、分析结果、来源","可恢复的业务记录",["与维护者学习验证库隔离","平台登录资料由本地浏览器管理"],"4.3、7–8"),
N("playwright",375,495,195,"Playwright","学习与桌面共用","学习与未来桌面共用固定执行协议;新小红书模式把有界动作与页面原生通信分别约束,实际合同随会话和验证留证。","当前页面、能力参数","页面数据与动作证据",["每个会话只有一个驱动持有控制权","不另建 Agent.run;双端仍须分别验收"],"4.2、6.3"),
N("midscene",610,495,190,"Midscene 可选","视觉与局部诊断","遇到明确的视觉或定位需求时再评估引入,相关模型调用接入 AI 网关。","需视觉辅助的局部步骤","定位或诊断结果",["首版不是必选依赖","缓存不等于整条链路零模型费用"],"5.6、6.3","pending"),
N("browser",375,610,195,"专用浏览器会话","按账号持久化 · 人工登录","主线使用独立可最大化浏览器;原 WebContentsView 宿主作为待验证候选。按账号复用固定目录或稳定分区,任务先核验身份。","专用账号目录或持久化分区、当前任务","已核验的会话、等待登录或等待处理状态",["原新会话随机分区要改为稳定账号映射","最小化及内嵌收起效果需双端验证;关闭则暂停保存进度"],"4.1–4.4"),
N("api",885,150,285,"产品账号与权益 API","产品登录 · 订阅 · 设备","可采用原 FastAPI 产品认证、权限和审计骨架,学校模型改为商家模型;订阅、订单和设备权益是本项目新增模块。","产品登录、设备及订单请求","登录状态、软件有效期与权益",["产品账号与第三方平台账号分别管理","原 deviceId 记录不等同于商业设备权益"],"4.3–4.4、9"),
N("gateway",885,265,285,"AI 网关","按用途路由 · 预算 · 请求标识","统一处理分析接口、结果格式、供应商调用与用量记录。","业务用途、必要内容、任务快照","结构化分析结果与实际计量",["供应商密钥保留服务端","相同请求标识复用已完成结果"],"4.3、6.4、9"),
N("models",885,380,285,"模型与运营配置","文本判断 · 复核 · 视觉 · 学习","我们在后台按四类用途配置模型与预算,不将厂商型号写死在客户端。","用途配置、真实样本和预算","已验证的模型配置版本",["文字模型与视觉模型分别校验","模型效果和成本按样本验证"],"6.4","confirmed"),
N("ledger",885,495,285,"订单与额度账本","预占 · 实际计量 · 结算","记录商家权益与额度,以及供应商实际消耗,分别对账。","请求标识、预占和实际消耗","额度余额、扣费与对账记录",["商家分析额度不等同供应商 Token","超时可能已发生费用,保留未决状态"],"9"),
N("release",885,610,285,"版本发布清单","能力包与双端客户端","发布版本、校验与兼容信息,客户端按任务固定版本执行。可借用原配置修订发布机制,替换为本项目能力契约。","经过验证的能力与客户端构建","可用版本及回滚信息",["Windows 打包模板可参考,macOS 分发链和升级回滚仍需建设","新能力版本用于新任务"],"4.1、4.4、5.5、8")
],
edges:[E("observe","candidate"),E("candidate","verify"),E("verify","learning-db","保存记录"),E("learning-db","package","关联通过记录"),E("desktop","runner"),E("runner","adapter","执行",{from:"bottom",fromAt:97.5/425,to:"top"}),E("runner","sqlite","保存",{from:"bottom",fromAt:.8,to:"top"}),E("adapter","playwright"),E("playwright","browser"),E("runner","midscene","按需调用",{kind:"optional",from:"right",fromAt:.78,to:"top",via:[[822,322.72],[822,470],[705,470]],lx:705,ly:470}),E("midscene","playwright","辅助",{kind:"optional",from:"left",to:"right"}),E("midscene","gateway","视觉调用",{kind:"optional",from:"right",to:"bottom",toAt:.95,via:[[840,532],[840,355],[1155.75,355]],lx:1065,ly:355}),E("desktop","api","权益",{from:"right",to:"left"}),E("runner","gateway","分析",{from:"right",to:"left"}),E("gateway","models"),E("gateway","ledger","计量",{from:"right",to:"right",via:[[1183,302],[1183,532]],lx:1150,ly:466}),E("api","ledger","账本",{from:"right",to:"right",via:[[1190,187],[1190,532]],lx:1150,ly:238}),E("package","release","版本化发布",{from:"bottom",to:"bottom",via:[[170,765],[1027,765]],lx:560,ly:765}),E("release","runner","拉取固定版本",{from:"left",to:"right",via:[[845,647],[845,302]],lx:842,ly:596}),E("sqlite","observe","诊断导出 → 维护端导入",{from:"right",to:"left",kind:"feedback",via:[[815,417],[815,720],[12,720],[12,187]],lx:290,ly:720})],
paths:[{name:"能力从学习到运行",ids:["observe","candidate","verify","learning-db","package","release","runner","adapter","playwright","browser"]},{name:"商家任务与 AI 分析",ids:["desktop","runner","gateway","models"]},{name:"可选视觉步骤接入网关",ids:["runner","midscene","gateway","models"]}],
notes:[["固定执行与原生通信","Playwright 学习/桌面复用同一 profile 与精确三字段合同。工具仅搜索输入/Enter、固定标准笔记锚点及普通根有界读取;15秒窗口内允许当前主框架第一方 HTTPS xhr/fetch POST 自然通信,包括安全通信。不直接接口或任意JS,不加指纹伪装;不是网络沙箱或绝无副作用承诺。legacy 四策略与旧记录独立保留。"],["采用适用的工程基础","测试工程的 Electron 壳、产品账号认证和管理后台列为候选提取范围。云端可沿用 FastAPI/PostgreSQL 骨架;本地保持 SQLite 和轻量执行进程,具体改造见文字方案 §4.4。"],["仍需独立建设与验证","原校园编排、密码代配和随机会话隔离不直接照搬。获客链路、AI 网关、商业账本与双端分发仍需实现;内嵌宿主与 Midscene 分别按需求验证。"]]
},
{id:"acquisition",name:"获客流程",title:"相关内容到可回看的客户线索",description:"同行商家视频的评论区与作者本人有采购需求是两条不同路径。相关性、角色和采购意图分别判断。",w:1220,h:862,
groups:[G(25,100,1170,710,"商家运行端 · 搜索入口先行,推荐流后续扩展")],
nodes:[
N("product",55,155,220,"商品与客户条件","商品 · 地区 · 排除条件","商家提供商品、使用场景、服务地区与目标客户条件。","商家配置","版本化商品条件",["未设置的信息不自行补齐","分析结果绑定商品条件版本"],"2、6.1"),
N("entry",350,155,220,"搜索或推荐流","首轮先验证关键词搜索","按任务入口发现本轮候选内容,记录来源及进度。","关键词或入口、范围与预算","候选视频和来源",["不承诺枚举无限列表全部内容","推荐流放在后续扩展阶段"],"3、8、10"),
N("video",655,155,220,"图文与视频相关性","文字优先 · 信息不足再补充","结合商品条件判断内容是否相关,必要时读取更多上下文。","标题、文本及必要补充内容","相关、不相关或待判断及依据",["相似度不能代替采购意图","无相关内容时继续发现或按预算结束"],"3、6.1–6.2"),
N("profile",945,155,220,"主页与作品读取","当前账号与作品来源","进入作者主页,读取简介及必要作品用于判断角色和需求。","相关视频及作者标识","账号信息、作品与来源",["对象身份与昵称分开","不能混用视频作者与评论者"],"3、7"),
N("role",945,300,220,"账号角色与需求","角色可多选","区分同行商家、采购方与信息不足,也允许同一账号兼具多种角色。","主页与内容上下文","角色标签与当前需求",["同行身份不排除采购需求","分支按本次商品与需求决定"],"3、6.1"),
N("comments",625,300,230,"同行视频评论与回复","保留父子关系与来源","读取与商品有关的视频评论及必要回复,查找需求信号。","视频标识、进度与读取范围","评论、回复、来源及下一状态",["动态列表每次定位当前对象","父评论和必要回复作为判断上下文"],"3、5.3、6.1"),
N("intent",315,300,230,"评论采购意图判断","原文依据 · 缺失信息","将需求信号与商家商品条件匹配,区分真实采购、闲聊和信息不足。","评论上下文与商品条件","采购意图与原文依据",["保留评论作者标识","新上下文变化后重新判断"],"6.1–6.2"),
N("buyer",945,445,220,"作者需求核对","采购条件与商品是否匹配","作者本人表达需求时,核对商品、场景和地区等必要条件。","作者需求与商家条件","可进入线索的匹配依据",["不能只因账号相关就判定客户","缺少条件时保留待判断"],"3、6.1"),
N("pending",625,445,230,"信息不足待判断","保留依据与缺失项","对当前信息无法支持判断的对象保留状态,不自动归为有效客户。","模糊内容或缺失条件","待判断对象与缺口",["模型自评分不作为真实准确率","不因缺少关键词直接排除所有模糊样本"],"3、6.1–6.3"),
N("lead",315,590,230,"线索去重与依据保存","对象标识 · 多条需求来源","归并可靠确认的同一对象,保留原始评论和内容来源。","两条业务路径的匹配结果","可回看的本地线索",["相同昵称不能直接认定同一人","内容、客户、动作分别去重"],"7"),
N("follow",625,590,230,"关注 私信 评论 回复","按阶段验证开放范围","按商品与任务配置准备跟进内容,记录目标与动作业务标识。","线索、跟进设置与模板版本","已准备或已执行的动作账目",["自动跟进保留为产品目标","实际首版范围依据功能验证结果确定"],"2、7、10","pending"),
N("readback",945,590,220,"结果核对与后续回复","确认 / 失败 / 结果未知","读取实际效果,记录关注、发送、发布评论与回复指定评论的状态。","动作标识与可核对页面证据","已确认结果或待核对记录",["未知结果先核对,不能直接重发","去重跨任务和模板变更生效"],"7、8")
],
edges:[E("product","entry"),E("entry","video"),E("video","profile","相关 / 补充"),E("profile","role"),E("role","comments","同行",{from:"left",to:"right"}),E("comments","intent", "评论需求",{from:"left",to:"right"}),E("role","buyer","作者有需求"),E("role","pending","信息不足",{from:"bottom",to:"right",via:[[1055,410],[880,410],[880,482]],lx:886,ly:410}),E("intent","lead","匹配",{from:"bottom",to:"top"}),E("buyer","lead","匹配",{from:"bottom",to:"top",via:[[1055,555],[430,555]],lx:760,ly:555}),E("lead","follow"),E("follow","readback"),E("video","entry","不相关则继续发现",{from:"top",to:"top",via:[[765,125],[460,125]],lx:600,ly:125}),E("intent","pending","需更多信息",{from:"bottom",to:"left",via:[[430,482]],lx:535,ly:482})],
paths:[{name:"从同行评论发现采购方",ids:["product","entry","video","profile","role","comments","intent","lead","follow","readback"]},{name:"作者本人表达采购需求",ids:["product","entry","video","profile","role","buyer","lead","follow","readback"]},{name:"信息不足保留待判断",ids:["product","entry","video","profile","role","pending"]}],
notes:[["商家配置的是业务条件","关键词帮助发现候选内容;商品与目标客户条件决定匹配结果。平台动作能够复用,同一对象对不同商家的匹配结论可能不同。"],["当前已接单篇正文与首层评论","有界搜索 → 装修规则 → 保留笔记 → 容器内定位 → 单篇阅读。初筛记录提供标题和编号;新八步合同在点击前按距离、次数和时间寻找目标,零滚动与实际位移分别记录,找不到就停止。正文、有限评论、部分结果和失败分别保存。新内容候选区分文字、媒体和混合评论,使用真实批次截图,媒体内容未解释;旧文字必填候选保留。逐步记录检查、执行与取证耗时。独立需求分析只取已保存的可用文字,区分明确、疑似、讨论和信息不足,逐条核对原文依据;媒体未解释、身份待核实。主备 Key 与型号由服务端配置,分析不重跑浏览器。历史点“查看分析”直接打开侧栏,优先展示分类、原文与依据,技术信息折叠;查看不新增模型调用,人工核对可显式追加保存,修正保留原文依据与修订链,原模型结果不变;统计仅按每源最新标注,不作真实准确率。实际验证及未完成项见进度。"]]
},
{id:"learning",name:"学习发布",title:"有界学习与能力发布闭环",description:"观察证据、操作知识、候选实现和执行验证分别留存。结构校验通过、真实试跑通过、已发布是不同状态。",w:1220,h:862,
groups:[G(25,100,1170,710,"维护者环境 · 按模块学习功能,保留版本和验证")],
nodes:[
N("goal",55,160,225,"限定目标与预算","能力 · 入口 · 角色 · 分支","学习开始先限定本次能力、覆盖范围和消耗边界。","平台能力目标与已知入口","学习范围、预算、待覆盖分支",["每条评论不是一条新学习任务","功能入口逐层探索;动态内容取样,多机调度后置"],"5.1、5.4"),
N("observe",350,160,225,"观察与证据","操作前后状态 · 对象 · 来源","记录操作、当前对象、定位候选、匹配数量、等待和页面依据。","真实页面与操作","观察记录、截图或快照引用",["证据有时间和所属对象","保存范围受学习预算限制"],"5.2"),
N("knowledge",645,160,225,"字段 状态 分支","值来源 · 前后条件 · 效果","将观察整理为可解释的操作知识,分清已理解与未覆盖情况。","观察证据","结构化操作知识及缺口",["字段类型、必填和取值来源明确","有结果、无结果、登录失效分别描述"],"5.1–5.2"),
N("gaps",940,160,225,"缺口与平台提醒","正常验证与中断分别记录","缺失分支保留待学习;出现平台提醒、登录失效或受限状态时保存依据并暂停。","未知条件、证据缺口或异常页面","覆盖说明或中断记录",["受限页面不能计为正常路径通过","人工处理后重新核对会话与目标"],"5.1–5.2、5.10"),
N("candidate",645,330,225,"候选能力与用例","TypeScript + JSON 可起步","从操作知识形成参数化实现、输入输出契约与用例,绑定证据。","操作知识、参数定义与证据","候选版本与验证用例",["手写和模型生成使用同一验收要求","Midscene 缓存不直接等于能力包"],"5.2–5.3"),
N("check",350,330,225,"固定规则校验","结构 · 类型 · 绑定 · 覆盖","程序检查契约、动作、等待、输出映射和证据引用,拒绝不合格候选。","候选能力、清单与用例","通过或拒绝及原因",["校验不是由模型自行宣布通过","固定校验不能代替真实浏览器验证"],"5.2"),
N("trial",55,330,225,"正式模式变参验证","完整任务 · 结果 · 平台状态","对冻结版本按拟交付方式运行完整任务,核对操作过程、对象、输出及当场反馈,再进入任务后观察。","精确版本、正常起始会话和变化参数","独立于探索记录的运行验证",["探索有所得不等于用户运行验收通过","出现提醒或状态未知时不开放版本"],"5.10、14.1"),
N("dual",55,520,225,"后续反馈与范围确认","官方消息 · 功能状态 · 复查","任务后复查官方通知和可观察功能,按预先定义的窗口记录延后反馈;当前只验证维护环境。","精确版本、执行记录和通知基线","待复查、限定范围通过或待复核",["当场无弹窗不代替后续观察","未来桌面及双端交付仍须各自验收"],"5.11、14.1"),
N("release",350,520,225,"发布不可变版本","版本号 · 内容校验 · 用例记录","将通过约定验收的候选纳入可用清单,保留原版本。","已验证候选和兼容信息","商家可用的能力版本",["变更生成新版本","不同能力版本使用关联新任务恢复进度"],"5.3、5.5、8"),
N("run",645,520,225,"商家固定执行","版本快照 · 参数 · 预算","商家任务重复调用已发布能力,AI 仍动态分析内容和采购意图。","已发布能力与商家条件","线索、动作结果与检查点",["不临时替换生产脚本","业务判断不冻结为某次学习结论"],"5.4、8"),
N("repair",940,520,225,"诊断 修订 回归","真实选步 · 新候选 · 再验证","对试跑失败或运行缺口分析原因,从本任务真实步骤选取并冻结修订候选,保留完整证据;新组合须独立校验和回放,不继承旧成功。","失败位置、版本、必要参数与证据","修订候选及回归结果",["运行异常通过诊断资料导出回流","结果未知先核对实际效果"],"4.3、5.5"),
N("records",350,695,520,"独立学习验证记录库","PostgreSQL · 校验 试跑 读回 双端验收留档","维护端独立保存每次验证的能力版本与校验值、用例、参数、系统与浏览器环境、前后状态、预期和实际结果、耗时、用量与证据引用。","每轮学习、校验和验证产物","可筛选回看的历史记录与发布依据",["重跑、修订追加记录,保留旧结论","与商家业务库隔离;大证据文件按引用保存"],"5.3","confirmed")
],
edges:[E("goal","observe"),E("observe","knowledge"),E("knowledge","gaps","缺口"),E("observe","gaps","平台提醒",{from:"top",to:"top",kind:"feedback",via:[[462.5,125],[1052.5,125]],lx:760,ly:125}),E("knowledge","candidate"),E("candidate","check","生成候选",{from:"left",to:"right"}),E("check","trial","校验通过",{from:"left",to:"right"}),E("trial","dual"),E("dual","release"),E("release","run"),E("run","repair","失败证据",{kind:"feedback"}),E("repair","candidate","修订后重验",{from:"top",to:"right",kind:"feedback",via:[[1052,450],[900,450],[900,367]],lx:980,ly:450}),E("trial","repair","试跑失败",{from:"bottom",to:"bottom",kind:"feedback",via:[[167,440],[300,440],[300,655],[1052,655]],lx:1010,ly:655}),E("check","candidate","拒绝后修订",{from:"top",to:"top",kind:"feedback",via:[[462,292],[757,292]],lx:605,ly:292})],
paths:[{name:"从观察到能力发布",ids:["goal","observe","knowledge","candidate","check","trial","dual","release","run"]},{name:"试跑失败后修订再验证",ids:["goal","observe","knowledge","candidate","check","trial","repair","candidate","check","trial","dual","release"]},{name:"候选校验拒绝后修订",ids:["candidate","check","candidate","check","trial","dual","release"]}],
notes:[["学习与召回共同推进","首条搜索/详情/评论组合已通过探索,当前新增平台路径图谱,33模板中18项有可核对观察;动态对象取样,模板不等于全站总量。发布者主页点击通过,评论者主页最终复验在官方安全验证处暂停,需人工处理后重新检测。既有固定版本继续变参复验,保留阶段证据和组合记录。组合仍为探索,不晋级发布;正式基线与官方消息等任务后反馈随后分别验收。观察未完成不能记为无异常。"],["当前执行合同与下一步","功能盘点另选playwright-xhs-navigation-ui-v1:固定已观察同站主页/通知锚点,明确opener的弹窗接续原策略及截止时间;主页只容许实际取证的两个入口上下文参数省略,其余对象/来源核对保留。菜单/标签/回复与消息窗口待补固定控件及实际状态断言;当前无导航候选或发布。旧搜索模式继续保留。已确定 Playwright 学习/桌面共用固定协议,原生搜索 profile 为 playwright-xhs-native-ui-v1,合同固定 engine=playwright、action_policy=xhs-bounded-readonly-v1、network_policy=xhs-page-native-v1。只允许已观察唯一搜索输入/Enter、固定同站标准笔记链接及普通根有界读取;保持目标、来源、次数和预算核验。最长15秒当前窗口内,主框架第一方HTTPS xhr/fetch POST自然通信(含安全通信)正常,不按逐接口名单认定业务副作用;非第一方、子框架、其他需策略授权的方法/资源类型、来源失配及过期仍停止,窗口不续签。这不是网络沙箱或防检测保证,不直接调用接口、任意JS或加指纹伪装。旧legacy四策略、候选及失败保留;不能将新通信范围回填旧记录。任务声明与实际会话、冻结清单与实际验证分开回显,缺字段显示未记录;schema10仅新增可空任务profile。新模式现已全新实录9条成功步骤,四步搜索和八步详情候选分别静态通过。首篇八步独立探索回放取得正文1条和完整首层评论2条,3批且范围截断,来源绑定及实际合同通过;worker14.297秒(含入口4.488秒)、服务端16.453秒。录制定位实际0→320,回放零滚动,不称分页已验收;等待前后18项不称零项等到结果。09阶段四步搜索未独立初筛回放,笔记仍取既有初筛;10阶段通用组合已对新词独立搜索,旧失败和部分结果保留,新七步取得正文1条/评论6条,仍为单样本探索。一次deepseek-flash分析完成,1822 Token、金额未知;维护者已确认两条联系方式短评论有咨询意向、可保留为待核实咨询线索,Codex按对话确认代录的两条人工纠错已保存;仅改提问表达与业务相关性,角色/自身需求/匹配仍未知或不足,正文尚未人工核对,原模型保留。以上为旧v2分析/五轴人工纠错,原记录不迁移。新v4提示/v3结果与六轴review-v2已加载,后续独立真实分析把两条短评论判为咨询yes并保留待核实,需求/身份/地区和match不升级;首轮输出合同失败保留且具体拒因未能重现,不作整体稳定性验收。服务端固定note无背景引用、comment仅引用同次阅读note,正文只发一次,仍限21源/16000字;咨询yes/no/unclear独立于自身需求及匹配,本源证据与linked-note背景分开,评论yes必须有笔记依据。拒绝其他评论、外部来源及未声明动作字段;纯媒体未知。程序只派生保留待核与有限建议,不升级需求、身份、地区或match,不建线索表。旧v2仍五轴review-v1可写,旧模型/核对不补值,新旧版本隔离复用,JSON字段无需schema变更。当前先验收行业无关的固化→召回→新参数执行,再补独立新搜索/新来源及正式基线/任务后反馈;正反例分类评测后置;本轮仅分析已有文字,没有重读平台。旧候选和4条旧阅读保留,不自动重读、扩第二篇或执行联系;探索通过不代表发布或桌面交付。范围等待、固定根/来源、逐批完整观察及PNG、15秒截止和总预算继续保留;框架、平台结果、账号后续复查和双端交付分别验收。"]]
},
{id:"recall",name:"任务与检索",title:"少量固定链路 服务信息查询与获客",description:"当前先在学习后台验证:商家文字 → 显式确认条件 → 已学搜索/阅读 → 自身需求与独立咨询 → 条件匹配和保留待核建议。普通信息问答、桌面与互动执行后置,实际业务质量仍待标注验证。",w:1220,h:810,
groups:[G(25,100,1170,635,"当前重点 · 搜索与读取先行,互动动作分别验收")],
nodes:[
N("input",55,160,225,"商家需求与条件确认","逐字段依据 · 追加版本","商家文字提取条件,维护者显式确认;未知地区不默认全国。","任务文字与原文依据","确认条件及不可变摘要",["当前只支持小红书装修找业主","人工补充与原文来源分开"],"14.2"),
N("route",350,160,225,"选用少量已学链路","平台 + 固定任务类型","只从正式运行及后续观察满足条件的版本选择链路,固定支持范围;实验候选留在学习端。","任务类型、平台和参数","可执行步骤或明确缺项",["少量能力先按任务类型路由","学习所得不直接开放用户运行"],"5.10、14.0"),
N("browser",645,160,225,"网页搜索与有限浏览","真实内容 · 当前会话","按正常页面路径搜索并读取图文、视频、主页或评论,等待状态就绪。","已学链路、当前账号和范围","内容、对象与来源或平台提醒",["单账号串行并限定读取范围","Playwright固定合同;动作与原生通信分开"],"5.10、14.1"),
N("analysis",940,160,225,"需求与咨询分开判断","本条原文 · 笔记背景","评论按引用理解笔记服务背景,自身需求、角色及地区仍只据本条。","已保存文字与固定背景引用","逐源六轴判断及分源依据",["正文只发一次;21源/16000字","纯媒体未知,不继承他人事实"],"6、14.2"),
N("result",940,355,225,"匹配与保留待核建议","咨询保留 · 匹配独立","程序派生保留待核、匹配及有限建议,咨询意向不升级为确认客户。","逐源分析与条件摘要","需核对的需求或咨询",["不补身份、地区或业务匹配","未建线索库;不执行联系"],"14.2"),
N("follow",645,355,225,"配置的跟进互动","关注 · 私信 · 评论与回复","绑定目标账号和评论层级,按当前已支持范围准备并执行跟进。","明确线索、对象与配置","已准备或待核对的互动",["当前只读执行器尚未支持写操作","各类互动单独学习与验证"],"5.10、14.3","pending"),
N("verify",350,355,225,"核对真实互动结果","确认 · 失败 · 未知","读取实际关注、发送或评论结果;不明确时保留未知并先核对。","动作记录与可观察结果","真实状态及来源依据",["未知结果不能自动重发","先确认评论对象和所属内容"],"5.10、7–8"),
N("record",55,355,225,"记录与后续复用","任务 · 线索 · 互动状态","保留本轮结果、已处理对象和动作记录,支持回看、去重与后续查询。","结果、来源与执行状态","可追溯记录与下一步条件",["模型调用和操作结果分别记录","历史资料先用简单条件查询"],"5.10、7"),
N("pause",55,560,225,"平台提醒或会话异常","保存依据 · 暂停 · 人工处理","探索出现提醒保留有效知识;运行中或任务后发现相关官方通知时,将对应版本置为待复核。","当前页面与异常事实","中断记录、待处理状态",["正式模式出现提醒时不晋级","有限验证不保证未来永不提醒"],"5.10、14.1","pending")
],
edges:[E("input","route"),E("route","browser"),E("browser","analysis"),E("analysis","result"),E("result","follow","获客跟进",{from:"left",to:"right"}),E("follow","verify","结果核对",{from:"left",to:"right"}),E("verify","record","保存",{from:"left",to:"right"}),E("browser","pause","出现提醒则中断",{from:"top",to:"left",kind:"feedback",via:[[757.5,125],[38,125],[38,597]],lx:740,ly:125})],
paths:[{name:"按问题查询真实内容",ids:["input","route","browser","analysis","result"]},{name:"发现需求并跟进",ids:["input","route","browser","analysis","result","follow","verify","record"]},{name:"平台提醒时暂停",ids:["input","route","browser","pause"]}],
notes:[["当前主线:先验证平台操作复用","学习端沉淀参数、定位、动作、输出和异常;轻量目录按平台/任务类型召回冻结版本,运行端再识别用户意图并按真实内容判断。先用新关键词验证搜索→详情→有限首层评论,不调用业务模型,不套装修初筛规则;咨询与人工核对资产保留、暂停扩展。桌面工程后置。"],["实验阅读与用户运行分别验收","初筛保留笔记可选旧七步或有界定位八步候选,绑定可信编号、当前标题和实际来源;重复、编号变化或预算耗尽即停。未就绪不自动登录。模型只分析已有文字,媒体未解释;服务方可有自身需求,同行内容仍可查看评论。本次新模式有界正文/评论阅读及一次文字分析通过,两条联系方式短评论已按对话确认保存人工纠错,可保留待核实咨询意向,但不确认自身需求、身份或地区,正文未核、原模型保留;新有界背景与独立咨询合同代码已实现并通过离线目标,v4提示/v3结果与六轴review-v2另存新事实;旧v2五轴核对仍可写且不迁移。新版本后续独立真实分析取得两条咨询yes/保留待核实,实际自身需求和匹配仍不足;首轮合同失败保留,不作为整体质量通过,未建立线索库;通用复用已对真实新词独立搜索并衔接正文/有限评论,更多范围及正式质量按第14.0节另验。结构与引用通过不等于业务准确率或账号身份通过,发布和双端交付待验收。"]]
},
{id:"ai",name:"AI 与额度",title:"一次分析请求如何计量与返回",description:"基础文本、复杂复核、视觉和维护学习分别配置。软件订阅、商家分析额度与供应商真实成本分别管理。",w:1220,h:842,
groups:[G(25,100,285,610,"商家电脑"),G(345,100,850,610,"云端网关与计量")],
nodes:[
N("content",50,155,235,"当前内容与商品条件","相关性 · 角色 · 采购意图","使用当前对象及必要上下文形成分析输入,三个判断分别记录。","标题、简介、评论和商品条件","待分析内容与用途",["文本优先,视觉按需","信息不足允许返回待判断"],"6.1–6.2"),
N("reuse",50,310,235,"去重与有效结果复用","上下文与版本参与复用键","先检查当前输入是否有仍有效的分析结果,再决定是否请求模型。","内容、商品、规则、模型与结构版本","已有结果或新分析请求",["上下文变化需重新判断","复用结果设置有效期"],"6.2–6.3"),
N("local",50,590,235,"保存分析与线索","结果依据 + 来源 + 用量","验证结果结构后写入本地记录,保留依据、模型和规则版本。","已完成分析结果或有效复用结果","本地分析记录、线索和客户端用量",["归属对象与原文依据对应","后续动作仍核对可靠身份与动作账目"],"6.1、7、9"),
N("gateway",375,310,235,"网关请求与幂等","业务用途 + 唯一请求标识","检查产品权益、请求和配置。已完成请求返回已有结果。","用途、必要内容、请求标识","可执行请求或已有结果",["重试复用原请求标识","不在客户端存供应商密钥"],"4.3、9"),
N("config",375,155,235,"运营模型配置","四类用途 独立预算","配置供应商、实际 model ID、协议、能力、价格和受限备用策略。","后台配置、质量和成本样本","经过验证的配置版本",["不要求使用最新或最强模型","任务保存配置快照"],"6.4","confirmed"),
N("reserve",670,310,235,"预占分析额度","校验权益与预算","在调用模型前预占本次请求额度,防止无预算开始调用。","请求标识、估算用量与余额","预占记录或等待额度",["余额不足不开始新的分析","预占与结算通过服务端账本管理"],"9"),
N("model",960,310,210,"模型调用","实际模型与用量标识","按配置调用已验证的模型,接收结果和可获得的实际用量。","结构化分析输入和用途预算","响应、请求标识与计量",["文字与视觉能力分别验证","备用模型回退也受预算限制"],"6.4、9"),
N("result",960,590,210,"结果与计量持久化","结构校验 · 响应保存","保存有效结果和实际供应商用量,再进行额度结算。","模型响应、实际计量","可复用结果与实际消耗记录",["结构不合格不进入有效线索","供应商成本与商家扣费分账记录"],"6.3、9"),
N("settle",670,590,235,"结算或释放预占","实际消耗与余额对账","依据结果和计量结算额度,释放多余预占,再返回已完成状态。","持久化结果、实际用量、预占记录","已结算请求与可返回结果",["已完成重试不再次扣费","客户端、服务端与供应商用量对账"],"9"),
N("return",375,590,235,"返回已完成结果","结构化判断与计量","将确认完成的结果交给商家端记录,异常保留明确状态。","结果、判断依据与计量","业务分析响应",["客户端按用途调用稳定接口","不硬编码供应商型号"],"4.3、6.4、9"),
N("uncertain",960,155,210,"超时 待核对","可能已经产生费用","调用超时可能已有供应商消耗,保留处理中或待核对状态。","未决请求与供应商记录","后续核对结果",["不立即按免费失败处理","不盲目发起另一笔调用"],"9")
],
edges:[E("content","reuse"),E("reuse","local","有效复用",{from:"bottom",to:"top"}),E("reuse","gateway","新分析"),E("config","gateway"),E("gateway","reserve"),E("reserve","model"),E("model","result","正常返回"),E("result","settle","保存后结算",{from:"left",to:"right"}),E("settle","return", "已完成",{from:"left",to:"right"}),E("return","local", "记录",{from:"left",to:"right"}),E("model","uncertain","超时",{from:"top",to:"bottom",kind:"feedback"}),E("uncertain","result","核对后补全",{from:"right",to:"right",kind:"feedback",via:[[1183,192],[1183,627]],lx:1150,ly:496}),E("gateway","return","已完成请求复用",{from:"bottom",to:"top",via:[[492,500]],lx:490,ly:484})],
paths:[{name:"新分析请求正常完成",ids:["content","reuse","gateway","reserve","model","result","settle","return","local"]},{name:"命中有效本地结果",ids:["content","reuse","local"]},{name:"供应商超时后核对",ids:["content","reuse","gateway","reserve","model","uncertain","result","settle","return","local"]},{name:"已完成请求重试",ids:["gateway","return","local"]}],
notes:[["三种账分开","软件有效期决定产品权益;商家分析额度决定可用分析量;供应商 Token 和视觉用量用于核算真实成本。套餐和价格还需实际样本验证。"],["学习成本与运行成本分开","维护者学习、探索和修复属于研发维护成本。商家任务中的文本分析、视觉分析和可选页面诊断属于运行用量。当前维护端文字分类使用可配置型号及两个去重 Key,最多一次受限切备;相同有效结果复用,保存已返回 Token,未知费用不编金额。商业额度结算仍未接入。"]]
},
{id:"recovery",name:"任务恢复",title:"任务状态与动作结果分别管理",description:"上方描述任务是否继续,下方描述单次跟进动作的结果。任务重启、崩溃或超时,不能代替实际动作核对。",w:1220,h:932,
groups:[G(25,100,1170,425,"任务生命周期"),G(25,555,1170,300,"关注或私信动作账目")],
nodes:[
N("queued",65,160,210,"待运行","绑定版本与条件快照","任务保存商品条件、能力版本、模型配置、账号与预算。","商家任务设置","可运行任务快照",["新版本不悄悄进入已有任务","一个账号同一时刻一个任务持有控制权"],"8"),
N("running",435,160,250,"运行中","按确认步骤保存检查点","串行调用能力,记录已完成对象、剩余队列、步骤与动作状态。","任务快照与账号会话","进度、线索、动作账目",["检查点不依赖列表第 N 条位置","休眠或退出后本地任务不会继续运行"],"4.2、8"),
N("complete",945,160,210,"完成","达到本轮任务范围","按实际发现的对象和任务预算完成本轮,不代表平台内容全部枚举。","已处理对象和任务条件","任务报告与已用额度",["保留结果来源和模型计量","待核对动作不能伪装成已确认发送"],"7–8"),
N("pausing",65,300,210,"暂停中","等待可确认的步骤边界","收到暂停请求后在可确认边界停止推进,保存必要状态。","暂停请求与当前步骤","检查点与暂停结果",["紧急停止仍可能留下未知动作","接管前移交浏览器控制权"],"4.2、8"),
N("login",365,300,220,"等待登录","官方页面人工登录 再核验","会话复用检查失败时停止推进,由商家在官方页面自行扫码或输入验证码,核验账号与页面后恢复。","失效会话或需登录页面","确认身份的会话",["不在产品界面索取平台手机号和验证码","重新确认当前账号、对象与页面"],"4.2、8"),
N("quota",655,300,220,"等待额度","补足额度后再检查","模型预算或额度不足时停止开启新分析及依赖它的后续动作。","余额不足或用途预算耗尽","明确等待状态与检查点",["未决调用先核对和结算","恢复前重新检查预算"],"8–9"),
N("handling",945,300,210,"等待处理","接管或核对后恢复","页面变化、必要人工接管或未决状态先等待处理,再核验恢复条件;升级兼容检查不满足时保持暂停等待处理。","异常证据、兼容问题或人工接管","处理记录与恢复条件",["页面失配可导出诊断资料","不在任务中生成并热换新脚本"],"5.5、8"),
N("paused",65,425,210,"已暂停","检查点已保存","恢复时核对页面、账号和对象,再回到固定版本的任务。","已确认检查点与恢复请求","可继续的任务上下文",["旧版本仍有效才继续","换能力版本时建立关联新任务"],"5.5、8"),
N("failed",435,425,220,"失败","无法按恢复规则继续","确认不可恢复时记录失败,保留依据与诊断信息;升级兼容问题先进入暂停或等待处理。","异常与恢复检查结果","失败记录及后续修订输入",["失败与动作结果未知分开","保留已完成对象和动作账目"],"5.5、8"),
N("cancelled",775,425,220,"取消","停止后续推进","任务取消后不开始后续处理,已经发生或结果未知的动作继续保留记录。","取消请求","终止的任务与未决动作记录",["取消不是撤回已发生的操作","重建任务仍沿用跨任务动作去重"],"7–8"),
N("prepared",65,610,210,"prepared 已准备","目标与业务标识已记录","动作执行前写入可靠目标、业务跟进轮次及内容版本。","线索与跟进设置","动作业务标识和准备记录",["关注与发送分开建模","模板变化不自动产生新的跟进轮次"],"7"),
N("executing",365,610,220,"executing 执行中","正在尝试平台动作","执行关注或发送后核对实际状态,崩溃与超时不直接判定失败。","已准备动作与当前页面","待核对或已确认的结果",["先记录意图再执行动作","程序成功返回不等同于业务效果确认"],"7"),
N("confirmed",945,610,210,"confirmed 已确认","实际效果可核对","依据实际平台状态或可读回证据确认动作结果。","动作标识与结果证据","已确认动作状态",["关联实际目标和内容","已确认动作跨任务去重"],"7"),
N("uncertain",655,750,220,"uncertain 结果未知","先核对实际效果","网络中断、程序崩溃或页面超时后,先恢复页面并核对动作是否发生。","未决动作和对应页面","确认发生、确认未发生或仍未知",["未知不能直接当失败重试","仅靠本地标识不能保证第三方动作恰好一次"],"7"),
N("action-failed",365,750,220,"failed 已确认失败","保留原因与跟进状态","依据核对结果记录失败;后续是否执行需重新满足业务与去重条件。","确认失败或未发生的证据","失败记录与后续处理条件",["不从未知状态直接自动重发","新的跟进轮次必须是明确业务设置"],"7")
],
edges:[E("queued","running"),E("running","complete","范围完成"),E("running","pausing","暂停请求",{from:"bottom",fromAt:.12,to:"top",via:[[465,265],[170,265]],lx:215,ly:265}),E("pausing","paused"),E("paused","running","恢复前核验",{from:"left",to:"left",via:[[40,462],[40,245],[415,245],[415,197]],lx:190,ly:245}),E("running","login","失效 / 核验恢复",{from:"bottom",fromAt:.3,to:"top",bi:true,lx:465,ly:280}),E("running","quota","额度不足 / 恢复",{from:"bottom",fromAt:.68,to:"top",bi:true,lx:735,ly:280}),E("running","handling","异常 / 处理恢复",{from:"right",fromAt:.8,to:"top",via:[[890,219.2],[890,265],[1050,265]],bi:true,lx:1045,ly:265}),E("running","failed","不可恢复",{from:"bottom",fromAt:.74,to:"top",via:[[620,395],[545,395]],lx:605,ly:397,kind:"feedback"}),E("running","cancelled","取消",{from:"right",fromAt:.9,to:"top",via:[[905,226.6],[905,400],[885,400]],lx:905,ly:392}),E("prepared","executing"),E("executing","confirmed","效果可核对"),E("executing","uncertain","崩溃 / 超时",{from:"bottom",to:"top",via:[[475,720],[765,720]],lx:645,ly:720,kind:"feedback"}),E("uncertain","confirmed","核验已发生",{from:"right",to:"left",via:[[915,787],[915,647]],lx:916,ly:733,kind:"feedback"}),E("uncertain","action-failed","核验失败或未发生",{from:"bottom",to:"bottom",via:[[765,842],[475,842]],kind:"feedback",lx:620,ly:842})],
paths:[{name:"任务正常完成",ids:["queued","running","complete"]},{name:"登录失效后恢复",ids:["queued","running","login","running","complete"]},{name:"暂停后核验恢复",ids:["queued","running","pausing","paused","running","complete"]},{name:"动作结果未知后确认发生",ids:["prepared","executing","uncertain","confirmed"]},{name:"动作核验失败或未发生",ids:["prepared","executing","uncertain","action-failed"]}],
notes:[["版本恢复的明确边界","旧任务继续使用原能力版本。若必须换版本,先检查进度兼容性,再建立关联的新任务,继承已完成对象和动作账目。"],["人工接管与机器执行互斥","暂停与接管通过明确控制权移交。恢复后重新核验账号、页面和目标对象;跨任务去重减少重复,但不能替代实际结果核对。"]]
},
{id:"session",name:"登录会话",title:"用户自行登录 下次复用同一浏览器资料",description:"平台官方页面承接扫码与验证码登录。固定账号资料目录保存可持久化状态,每次任务重新核验会话和账号。",w:1220,h:842,
groups:[G(25,100,1170,650,"独立窗口为主线 · 内嵌宿主待验证 · 同账号固定目录或分区")],
nodes:[
N("profile",55,155,250,"分配账号专用会话","固定目录 或稳定 persist 分区","独立浏览器分配固定资料目录;若采用内嵌宿主,则分配稳定 Electron 分区。SQLite 保存资料引用、状态和核验时间。","平台、本地账号标识","专用浏览器资料和账号映射",["按账号固定,不追加新会话随机 ID","重启和升级保留,同账号控制权互斥"],"4.2–4.4"),
N("human",480,155,250,"商家在官方页面登录","扫码 或 官方手机号验证码表单","主线打开独立可最大化浏览器,由商家自行完成登录;内嵌方案须先验证现成宿主与固定执行器接入。业务执行器暂停动作。","未登录或需重新验证的页面","平台完成的人工登录",["我们的产品界面不索取或代收验证码","旧密码本准入需要替换为人工登录与身份核验"],"2、4.1–4.4","confirmed"),
N("verified",905,155,250,"核验登录与账号身份","页面标志 + 可靠账号标识","等待登录跳转完成,读取必要的登录标志和账号标识,再绑定或核对任务账号。","完成登录的当前页面","身份明确且可用的会话",["二维码消失或已有 Cookie 不能单独判定成功","身份不明或与预期账号不同则等待处理"],"4.2"),
N("task",55,345,250,"下一次启动任务","读取原账号映射","软件再次启动或新建平台任务时,读取原账号及固定浏览器目录。","已保存账号记录与任务","要复用的会话或专用目录",["SQLite 上次已登录不证明当前仍有效","浏览器资料保留在当前设备"],"4.2–4.3"),
N("reuse",480,345,250,"复用或重开浏览器","同一账号目录或持久化分区","优先复用已有会话;关闭后在原实例释放控制权后重开同一账号资料。内嵌执行器连接宿主真实页面目标。","原会话或固定账号资料引用","待核验的浏览器会话",["不为每次任务创建随机登录分区","人工登录和业务执行使用同一会话"],"4.2、4.4"),
N("probe",905,345,250,"检查当前会话","先检测页面 再确认账号","打开后先检测可见登录、未登录或需验证信号;已登录无需重复扫码,身份仍须确认。未知不自动判为登录失效。","重开或现有浏览器会话","页面检测结果与人工确认入口",["已实现小红书可见页面状态检测","页面检测不代替账号身份确认","验证码、手机号和原始凭据不写入业务记录"],"4.2"),
N("closed",55,585,250,"关闭浏览器后暂停","保存检查点并释放目录","关闭整个浏览器会结束当前执行会话,运行端暂停;任务结束正常关闭时也保留账号目录。","浏览器关闭事件和当前任务","检查点、未决动作及保留的账号资料",["关闭浏览器不代表执行会继续","重新打开后再核验身份与页面"],"4.2、8"),
N("run",480,585,250,"同会话交给执行器","回到控制台 可最小化浏览器","登录核验完成后由执行器接管同一会话,提供显示浏览器和人工接管入口。","身份已核验的会话与任务","固定链路执行与任务进度",["最小化的运行效果在 Windows/macOS 分别验证","保留可见方式,不默认切换无头运行"],"4.1–4.2、8"),
N("expired",905,585,250,"会话失效 等待登录","到期 退出 清理 或 需重新验证","确认当前未登录或需要平台再次验证时,显示浏览器,由商家完成登录。","明确未登录、会话失效或需要重新验证的页面","明确等待状态与人工登录入口",["不自动操作验证码流程","重新登录成功后再核验账号并恢复"],"4.2、8")
],
edges:[E("profile","human"),E("human","verified","完成登录"),E("verified","run","同会话移交",{from:"bottom",to:"top",via:[[1030,270],[860,270],[860,530],[605,530]],lx:858,ly:494}),E("task","reuse","读取原目录"),E("reuse","probe","打开后核验"),E("probe","run","仍有效",{from:"bottom",to:"top",via:[[1030,500],[605,500]],lx:710,ly:500}),E("probe","expired","确认未登录或需验证"),E("expired","human","重新人工登录",{from:"right",to:"bottom",via:[[1180,622],[1180,285],[605,285]],kind:"feedback",lx:1100,ly:285}),E("run","closed","手动关闭则暂停",{from:"left",to:"right"}),E("closed","reuse","同目录重开",{from:"top",to:"left",via:[[180,475],[450,475],[450,382]],lx:305,ly:475})],
paths:[{name:"首次人工登录后运行",ids:["profile","human","verified","run"]},{name:"下次任务复用有效会话",ids:["task","reuse","probe","run"]},{name:"会话失效重新人工登录",ids:["task","reuse","probe","expired","human","verified","run"]},{name:"关闭浏览器后重开核验",ids:["run","closed","reuse","probe","run"]}],
notes:[["窗口形态与会话保存分别决定","主线仍为独立可最大化窗口,现有内嵌宿主列为首版待验证候选。两者都需保持登录与执行为同一会话,收起或最小化需双端验证;关闭执行浏览器则先暂停。"],["资料实现按宿主适配","独立浏览器使用固定目录,内嵌 Electron 使用稳定 persist 分区。SQLite 保存账号映射和核验状态;原工程的新会话随机标识与密码本准入需改造,不能直接提供跨任务扫码登录复用。"]]
},
{id:"delivery",name:"实施阶段",title:"按验证结果推进的六个阶段",description:"顺序表示依赖关系,没有预设交付日期或处理量承诺。双端基础验证与独立学习验证记录从第一阶段开始。",w:1220,h:832,
groups:[G(25,100,1170,660,"建议实施路线 · 已启动学习后台基建")],
nodes:[
N("foundation",65,155,475,"01 工程基础与单平台验证","学习后台迁入 · 独立存储 · 双端读取","迁入适用学习模块,接本项目 PostgreSQL 和 Playwright 固定执行。新模式首篇有界探索阅读已通过,两条咨询短评论人工纠错已保存;背景与独立咨询合同及离线验证已完成,后续独立真实分析取得两条咨询保留结果;首轮合同失败保留,业务质量扩展后置,通用复用及新词真实组合探索已通过,更多范围、正式基线与反馈待验。","适用源码、平台、测试账号、学习目标","独立基础骨架、可重复链路和学习验证记录",["独立应用目录、端口和进程归属,不依赖原校园服务","Windows 与 macOS 各自实测;首版尚未完成真实平台验收"],"5.8、10 阶段一","proposed",120),
N("discovery",680,155,475,"02 固化 召回 变参复用","固定版本 · 参数 · 实际来源","按平台和任务类型召回冻结候选,换新词执行搜索、详情与有限评论;不前置业务分类。","已学候选、证据与结构化任务","阶段验证、组合历史与明确缺口",["歧义显式选版本;不静默选最新","可用输出与失败取证分开"],"10 阶段二、14.0","confirmed",120),
N("assets",680,350,475,"03 运行时业务判断与恢复","用户意图 · 实时内容 · 中断恢复","操作复用闭环收口后,再接运行时业务判断、去重、质量评测和恢复。","通用操作能力及当前用户条件","业务判断与恢复验证",["已有咨询与人工核对保留、暂停扩展","内容判断不冻结为平台操作知识"],"10 阶段三、14.2","proposed",120),
N("followup",65,350,475,"04 跟进闭环","关注 · 私信 · 结果核对 · 回复","分别验证跟进动作、重复候选、发送中断及结果不明后核对,再确定首版开放范围。","可回看线索、测试对象与动作契约","跟进闭环与明确发布范围",["跟进验收不由只读流程通过替代","未知结果先核对,跨任务去重"],"10 阶段四","pending",120),
N("commercial",65,555,475,"05 可收费的双端安装版","安装升级 · 权益 · 后台模型 · 账本","完善管理界面、配置验证发布、设备权益和订单计量,两端分别验证安装生命周期。","已验证业务链路、客户端与服务端","可安装版本与用量对账结果",["模型配置逻辑在前期先以开发配置使用","最低系统版本和架构由依赖验证确定"],"10 阶段五","proposed",120),
N("expansion",680,555,475,"06 扩展覆盖","推荐流 · 多账号 · 新平台 · 手机查看","稳定单平台流程和商业用量后,依次评估多账号、新平台与可选同步。","稳定的单平台产品与需求反馈","新增适配及独立验证结果",["每增加一个平台都新增验证","手机查看与手机执行分别评估"],"10 阶段六","later",120)
],
edges:[E("foundation","discovery"),E("discovery","assets"),E("assets","followup","恢复能力支撑跟进",{from:"left",to:"right"}),E("followup","commercial"),E("commercial","expansion","基础流程与用量稳定",{kind:"optional"})],
paths:[{name:"从可行性验证到扩展",ids:["foundation","discovery","assets","followup","commercial","expansion"]}],
notes:[["基础模块提取与链路验证并行","桌面壳和后台基础按限定范围提取,云端可保留 FastAPI。学习证据与验证记录从原型开始保留;不以整套旧校园工程迁移完成为前提。"],["仍需验证的选择","首发平台、内嵌宿主是否采用、具体模型、跟进开放范围、套餐额度、并行规模和 Midscene 均待验证。Windows 原模板可参考,macOS 安装与发布仍需独立建设。"]]
}
];
const statusLabels={confirmed:"已确认方向 · 逐模块验收",proposed:"实施建议 · 逐模块验证",pending:"范围或效果待验证",later:"后续扩展 · 逐项验证"};
const $=id=>document.getElementById(id);
const esc=value=>String(value).replace(/[&<>"']/g,c=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[c]));
const state={tab:0,node:null,focus:false,zoom:1,scenario:0,step:-1,timer:null};
let toastTimer;
function validateDiagrams(){
const ids=new Set();
for(const d of diagrams){
if(ids.has(d.id))throw Error("重复图页 "+d.id);ids.add(d.id);
const nodes=new Map();
for(const n of d.nodes){if(nodes.has(n.id))throw Error("重复节点 "+n.id);nodes.set(n.id,n);if(n.x<0||n.y<0||n.x+n.w>d.w||n.y+n.h>d.h-40)throw Error("节点超出画布 "+d.id+"/"+n.id);if(!n.source||!n.desc)throw Error("缺失节点依据 "+n.id);}
for(const e of d.edges){if(!nodes.has(e.a)||!nodes.has(e.b))throw Error("关系引用无效 "+d.id);}
for(const p of d.paths){for(let i=0;i<p.ids.length;i++){if(!nodes.has(p.ids[i]))throw Error("路径节点无效");if(i&&!d.edges.some(e=>(e.a===p.ids[i-1]&&e.b===p.ids[i])||(e.bi&&e.b===p.ids[i-1]&&e.a===p.ids[i])))throw Error("路径关系缺失 "+d.id+" "+p.ids[i-1]+"→"+p.ids[i]);}}
for(let i=0;i<d.nodes.length;i++)for(let j=i+1;j<d.nodes.length;j++){const a=d.nodes[i],b=d.nodes[j];if(a.x<b.x+b.w&&a.x+a.w>b.x&&a.y<b.y+b.h&&a.y+a.h>b.y)throw Error("节点重叠 "+d.id);}
}
return true;
}
function endpoint(n,side,at=.5){if(side==="top")return[n.x+n.w*at,n.y];if(side==="bottom")return[n.x+n.w*at,n.y+n.h];if(side==="left")return[n.x,n.y+n.h*at];return[n.x+n.w,n.y+n.h*at];}
function edgePoints(e,map){
const a=map.get(e.a),b=map.get(e.b);
const vertical=Math.abs(a.x+a.w/2-b.x-b.w/2)<45;
const from=e.from||(vertical?(b.y>a.y?"bottom":"top"):(b.x>a.x?"right":"left"));
const to=e.to||(vertical?(b.y>a.y?"top":"bottom"):(b.x>a.x?"left":"right"));
const start=endpoint(a,from,e.fromAt??.5),end=endpoint(b,to,e.toAt??.5);
let middle=e.via||[];
if(!e.via&&start[0]!==end[0]&&start[1]!==end[1]){
if(from==="bottom"||from==="top"){const y=(start[1]+end[1])/2;middle=[[start[0],y],[end[0],y]];}
else{const x=(start[0]+end[0])/2;middle=[[x,start[1]],[x,end[1]]];}
}
return[start,...middle,end];
}
function labelPosition(points){let longest=0,pos=points[0];for(let i=1;i<points.length;i++){const a=points[i-1],b=points[i],len=Math.hypot(b[0]-a[0],b[1]-a[1]);if(len>longest){longest=len;pos=[(a[0]+b[0])/2,(a[1]+b[1])/2];}}return pos;}
function lines(text,maxUnits){const result=[];let part="",units=0;for(const c of text){const width=/[\x00-\x7F]/.test(c)?.55:1;if(units+width>maxUnits&&part){result.push(part);part="";units=0;}part+=c;units+=width;}if(part)result.push(part);return result;}
function nodeMarkup(n){
const titleLines=lines(n.title,(n.w-30)/15),subLines=lines(n.sub,(n.w-30)/11);
const titleY=n.h>90?34:29;
const title=titleLines.map((line,i)=>`<tspan x="16" y="${titleY+i*20}">${esc(line)}</tspan>`).join("");
const subtitle=subLines.map((line,i)=>`<tspan x="16" y="${titleY+titleLines.length*20+3+i*16}">${esc(line)}</tspan>`).join("");
return `<g class="node" data-node="${esc(n.id)}" role="button" tabindex="0" aria-label="${esc(n.title)} 查看详情" transform="translate(${n.x} ${n.y})"><title>${esc(n.title+":"+n.desc)}</title><rect class="node-box" width="${n.w}" height="${n.h}" rx="8"/><text class="node-title">${title}</text><text class="node-sub">${subtitle}</text></g>`;
}
function buildSVG(d){
const map=new Map(d.nodes.map(n=>[n.id,n]));
const groups=d.groups.map(g=>`<g><rect class="group-rect" x="${g.x}" y="${g.y}" width="${g.w}" height="${g.h}" rx="10"/><text class="group-title" x="${g.x+17}" y="${g.y+25}">${esc(g.title)}</text></g>`).join("");
const edges=d.edges.map((e,i)=>{
const pts=edgePoints(e,map),path=pts.map((p,j)=>(j?"L":"M")+p.join(" ")).join(" ");
const marker=e.kind==="feedback"?"feedback":"normal";
return `<path class="edge ${e.kind||""}" data-edge="${i}" d="${path}" marker-end="url(#arrow-${marker})" ${e.bi?`marker-start="url(#arrow-${marker}-start)"`:""}><title>${esc(map.get(e.a).title+" → "+map.get(e.b).title+(e.label?":"+e.label:""))}</title></path>`;
}).join("");
const labels=d.edges.map((e,i)=>{if(!e.label)return"";const p=labelPosition(edgePoints(e,map)),x=e.lx??p[0],y=e.ly??p[1],w=lines(e.label,200)[0].split("").reduce((a,c)=>a+(/[\x00-\x7F]/.test(c)?5.5:10),0)+10;return `<g class="edge-label ${e.kind||""}" data-label="${i}"><rect x="${x-w/2}" y="${y-11}" width="${w}" height="19" rx="3"/><text x="${x}" y="${y+2}" text-anchor="middle">${esc(e.label)}</text></g>`;}).join("");
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${d.w} ${d.h}" width="${d.w}" height="${d.h}" aria-labelledby="svg-title svg-description" role="img"><title id="svg-title">${esc(d.title)}</title><desc id="svg-description">${esc(d.description)} 所有节点可通过键盘聚焦并查看详情。</desc><defs>${["normal","active","feedback"].map(c=>`<marker id="arrow-${c}" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path class="arrow-tip ${c==="normal"?"":c}" d="M 1 1 L 9 5 L 1 9 Z"/></marker><marker id="arrow-${c}-start" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path class="arrow-tip ${c==="normal"?"":c}" d="M 1 1 L 9 5 L 1 9 Z"/></marker>`).join("")}</defs><rect class="paper" width="${d.w}" height="${d.h}"/><text class="svg-heading" x="28" y="38">${esc(d.title)}</text><text class="svg-sub" x="28" y="64">整体设计 · 实现状态见 README · 点击节点查看职责和来源</text>${groups}<g class="edges">${edges}</g><g class="labels">${labels}</g><g class="nodes">${d.nodes.map(nodeMarkup).join("")}</g><text class="svg-sub" x="28" y="${d.h-23}">来源:AI 获客助手整体方案 v0.2 · 2026-10-25</text><text class="svg-sub" x="${d.w-28}" y="${d.h-23}" text-anchor="end">${esc(d.name)} / ${d.nodes.length} 个节点</text></svg>`;
}
function renderTabs(){
$("tabs").innerHTML=diagrams.map((d,i)=>`<button type="button" class="tab" id="tab-${d.id}" role="tab" aria-selected="${i===state.tab}" aria-controls="viewport" tabindex="${i===state.tab?0:-1}" data-tab="${i}"><span class="index">${String(i+1).padStart(2,"0")}</span>${esc(d.name)}</button>`).join("");
}
function render(){
const d=diagrams[state.tab];renderTabs();
$("diagram-title").textContent=d.title;$("diagram-description").textContent=d.description;$("diagram-count").textContent=`${d.nodes.length} 节点 / ${d.edges.length} 关系`;
$("viewport").innerHTML=buildSVG(d);$("viewport").setAttribute("aria-labelledby","tab-"+d.id);
$("scenario").innerHTML=d.paths.map((p,i)=>`<option value="${i}">${esc(p.name)}</option>`).join("");$("scenario").value=String(state.scenario);
d.notes.forEach((n,i)=>{$("note-title-"+(i?"b":"a")).textContent=n[0];$("note-"+(i?"b":"a")).textContent=n[1];});
renderDetail();renderHighlights();renderZoom();
}
function renderDetail(){
const d=diagrams[state.tab],n=d.nodes.find(n=>n.id===state.node)||d.nodes[0];
$("detail").innerHTML=`<div class="detail-kicker">${esc(d.name)} / 节点职责</div><h3>${esc(n.title)}</h3><span class="status-tag">${esc(statusLabels[n.status])}</span><p class="detail-desc">${esc(n.desc)}</p><dl><dt>输入与前提</dt><dd>${esc(n.input)}</dd><dt>产出或状态</dt><dd>${esc(n.output)}</dd><dt>核对要点</dt><dd><ul>${n.checks.map(s=>`<li>${esc(s)}</li>`).join("")}</ul></dd></dl><div class="detail-source">来源:整体方案 §${esc(n.source)}<br><a href="整体方案-v0.2.md">查看完整文字方案 ↗</a></div>`;
}
function renderHighlights(){
const d=diagrams[state.tab],p=d.paths[state.scenario],route=state.step>=0?p.ids.slice(0,state.step+1):[],current=state.step>=0?p.ids[state.step]:null;
const neighbors=new Set([state.node]);for(const e of d.edges){if(e.a===state.node)neighbors.add(e.b);if(e.b===state.node)neighbors.add(e.a);}
$("viewport").querySelectorAll("[data-node]").forEach(el=>{const id=el.dataset.node;el.classList.toggle("selected",id===state.node);el.classList.toggle("route",route.includes(id));el.classList.toggle("current",id===current);el.classList.toggle("dim",state.focus&&Boolean(state.node)&&!neighbors.has(id));el.setAttribute("aria-pressed",String(id===state.node));});
d.edges.forEach((e,i)=>{
const highlighted=route.some((id,j)=>j&&((route[j-1]===e.a&&id===e.b)||(e.bi&&route[j-1]===e.b&&id===e.a)));
const dim=state.focus&&Boolean(state.node)&&e.a!==state.node&&e.b!==state.node;
const el=$("viewport").querySelector(`[data-edge="${i}"]`);el.classList.toggle("highlight",highlighted);el.classList.toggle("dim",dim);el.setAttribute("marker-end",`url(#arrow-${highlighted?"active":e.kind==="feedback"?"feedback":"normal"})`);if(e.bi)el.setAttribute("marker-start",`url(#arrow-${highlighted?"active":e.kind==="feedback"?"feedback":"normal"}-start)`);
$("viewport").querySelector(`[data-label="${i}"]`)?.classList.toggle("dim",dim);
});
$("step-label").innerHTML=state.step<0?"设计路径,尚未开始演示":`${state.step+1} / ${p.ids.length} · <b>${esc(d.nodes.find(n=>n.id===current).title)}</b>`;
$("prev").disabled=state.step<=0;$("next").disabled=state.step>=p.ids.length-1;$("play").textContent=state.timer?"暂停":"播放";
}
function renderZoom(){const svg=$("viewport").querySelector("svg");svg.style.width=state.zoom*100+"%";svg.style.minWidth=Math.round(820*state.zoom)+"px";$("zoom-value").textContent=Math.round(state.zoom*100)+"%";$("zoom-out").disabled=state.zoom<=.6;$("zoom-in").disabled=state.zoom>=2;}
function stop(){if(state.timer){clearInterval(state.timer);state.timer=null;}$("play").textContent="播放";}
function selectNode(id){state.node=id;renderDetail();renderHighlights();}
function setTab(index,updateHash=true){stop();state.tab=index;state.node=null;state.step=-1;state.scenario=0;state.zoom=1;render();$("viewport").scrollTo(0,0);if(updateHash){try{history.replaceState(null,"","#"+diagrams[index].id);}catch{location.hash=diagrams[index].id;}}}
function stepTo(index){const p=diagrams[state.tab].paths[state.scenario];state.step=Math.max(-1,Math.min(index,p.ids.length-1));if(state.step>=0)state.node=p.ids[state.step];renderDetail();renderHighlights();}
function toast(message){clearTimeout(toastTimer);$("notice").textContent=message;toastTimer=setTimeout(()=>{$("notice").textContent="";},3200);}
function download(blob,filename){const url=URL.createObjectURL(blob),a=document.createElement("a");a.href=url;a.download=filename;document.body.append(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),5000);}
function exportSVG(){
const original=$("viewport").querySelector("svg"),copy=original.cloneNode(true);
const from=[original,...original.querySelectorAll("*")],to=[copy,...copy.querySelectorAll("*")];
const properties=["fill","stroke","stroke-width","stroke-dasharray","stroke-linecap","stroke-linejoin","font-family","font-size","font-weight","text-anchor"];
from.forEach((el,i)=>{const css=getComputedStyle(el);properties.forEach(p=>{const value=css.getPropertyValue(p);if(value)to[i].style.setProperty(p,value);});to[i].style.opacity="1";to[i].removeAttribute("tabindex");to[i].removeAttribute("aria-pressed");to[i].removeAttribute("role");});
const d=diagrams[state.tab];copy.setAttribute("width",d.w);copy.setAttribute("height",d.h);copy.style.removeProperty("width");copy.style.removeProperty("min-width");copy.style.removeProperty("height");
const xml=new XMLSerializer().serializeToString(copy);return new Blob([xml],{type:"image/svg+xml;charset=utf-8"});
}
async function exportPNG(){
const blob=exportSVG(),url=URL.createObjectURL(blob),image=new Image(),d=diagrams[state.tab];
try{await new Promise((resolve,reject)=>{image.onload=resolve;image.onerror=()=>reject(Error("无法生成图片"));image.src=url;});const canvas=document.createElement("canvas"),ratio=2;canvas.width=d.w*ratio;canvas.height=d.h*ratio;const context=canvas.getContext("2d");if(!context)throw Error("浏览器不支持图片导出");context.scale(ratio,ratio);context.drawImage(image,0,0);const output=await new Promise(resolve=>canvas.toBlob(resolve,"image/png"));if(!output)throw Error("图片生成失败");download(output,"assistant-"+d.id+".png");toast("PNG 已导出,包含当前主题和整张图");}finally{URL.revokeObjectURL(url);}
}
$("tabs").addEventListener("click",event=>{const button=event.target.closest("[data-tab]");if(button)setTab(Number(button.dataset.tab));});
$("tabs").addEventListener("keydown",event=>{if(!["ArrowLeft","ArrowRight","Home","End"].includes(event.key))return;event.preventDefault();let next=state.tab;if(event.key==="Home")next=0;else if(event.key==="End")next=diagrams.length-1;else next=(next+(event.key==="ArrowRight"?1:-1)+diagrams.length)%diagrams.length;setTab(next);$("tab-"+diagrams[next].id).focus();});
$("viewport").addEventListener("click",event=>{const node=event.target.closest("[data-node]");if(node)selectNode(node.dataset.node);});
$("viewport").addEventListener("keydown",event=>{if((event.key==="Enter"||event.key===" ")&&event.target.matches("[data-node]")){event.preventDefault();selectNode(event.target.dataset.node);}});
$("focus").addEventListener("change",event=>{state.focus=event.target.checked;if(state.focus&&!state.node)state.node=diagrams[state.tab].nodes[0].id;renderDetail();renderHighlights();});
$("zoom-in").addEventListener("click",()=>{state.zoom=Math.min(2,Math.round((state.zoom+.2)*10)/10);renderZoom();});
$("zoom-out").addEventListener("click",()=>{state.zoom=Math.max(.6,Math.round((state.zoom-.2)*10)/10);renderZoom();});
$("fit").addEventListener("click",()=>{state.zoom=1;renderZoom();$("viewport").scrollTo(0,0);});
$("scenario").addEventListener("change",event=>{stop();state.scenario=Number(event.target.value);state.step=-1;state.node=null;renderDetail();renderHighlights();});
$("next").addEventListener("click",()=>{stop();stepTo(state.step+1);});$("prev").addEventListener("click",()=>{stop();stepTo(state.step-1);});
$("reset").addEventListener("click",()=>{stop();state.node=null;stepTo(-1);});
$("play").addEventListener("click",()=>{if(state.timer){stop();return;}const path=diagrams[state.tab].paths[state.scenario];if(state.step>=path.ids.length-1)state.step=-1;stepTo(state.step+1);state.timer=setInterval(()=>{if(state.step>=path.ids.length-1){stop();renderHighlights();}else stepTo(state.step+1);},1450);renderHighlights();});
$("export-svg").addEventListener("click",()=>{try{download(exportSVG(),"assistant-"+diagrams[state.tab].id+".svg");toast("SVG 已导出,可继续编辑和放大");}catch(error){toast("导出失败:"+error.message);}});
$("export-png").addEventListener("click",async()=>{const button=$("export-png");button.disabled=true;try{await exportPNG();}catch(error){toast("导出失败:"+error.message);}finally{button.disabled=false;}});
function setTheme(theme){document.documentElement.dataset.theme=theme;$("theme").textContent=theme==="dark"?"切换浅色":"切换深色";$("theme").setAttribute("aria-label",$("theme").textContent);try{localStorage.setItem("assistant-plan-theme",theme);}catch{}}
$("theme").addEventListener("click",()=>setTheme(document.documentElement.dataset.theme==="dark"?"light":"dark"));
$("search").addEventListener("input",()=>{const query=$("search").value.trim().toLocaleLowerCase();$("results").replaceChildren();if(!query){$("search").setAttribute("aria-expanded","false");return;}const found=[];diagrams.forEach((d,index)=>d.nodes.forEach(n=>{if((n.title+" "+n.sub+" "+n.desc).toLocaleLowerCase().includes(query))found.push({d,index,n});}));found.slice(0,9).forEach(({d,index,n})=>{const button=document.createElement("button");button.type="button";button.className="result";const small=document.createElement("small");small.textContent=d.name;button.append(small,document.createTextNode(n.title));button.addEventListener("click",()=>{setTab(index);selectNode(n.id);$("search").value="";$("results").replaceChildren();$("search").setAttribute("aria-expanded","false");$("viewport").querySelector(`[data-node="${n.id}"]`).focus({preventScroll:true});});$("results").append(button);});if(!found.length){const empty=document.createElement("div");empty.className="search-empty";empty.textContent="没有匹配节点,试试 证据、模型 或 版本";$("results").append(empty);}$("search").setAttribute("aria-expanded","true");});
document.addEventListener("click",event=>{if(!event.target.closest(".search-box")){$("results").replaceChildren();$("search").setAttribute("aria-expanded","false");}});
$("search").addEventListener("keydown",event=>{if(event.key==="Escape"){$("search").value="";$("results").replaceChildren();$("search").setAttribute("aria-expanded","false");}if(event.key==="ArrowDown"){$("results").querySelector("button")?.focus();event.preventDefault();}});
window.addEventListener("hashchange",()=>{const index=diagrams.findIndex(d=>"#"+d.id===location.hash);if(index>=0&&index!==state.tab)setTab(index,false);});
document.addEventListener("visibilitychange",()=>{if(document.hidden)stop();});
validateDiagrams();
try{const saved=localStorage.getItem("assistant-plan-theme");setTheme(saved||"light");}catch{setTheme("light");}
const initial=diagrams.findIndex(d=>"#"+d.id===location.hash);if(initial>=0)state.tab=initial;
render();
// 供本地回归检查使用,不调用任何平台或模型接口。
window.assistantPlan={diagrams,validate:validateDiagrams,exportSVG};
</script>
</body>
</html>