347 lines
87 KiB
HTML
347 lines
87 KiB
HTML
<!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 & 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=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[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>
|