Web 控制台不设自有服务器。LLM 与 embedding 请求由浏览器直接发到用户自选的厂商端点,灌库数据流由浏览器直接发到板子 IP 或 USB 端点。三个理由:
localStorage,用完可一键清除。代价(必须提前处理):板端 httpd 必须加 CORS 响应头并处理 OPTIONS 预检(否则浏览器 fetch 直连 http://192.168.1.46 会被同源策略拦死);页面必须以 http 或 localhost 提供(https 页面请求 http 板子会触发 mixed content 拦截,现有 start_web.ps1 是本地 http,符合要求)。
一个容易被忽略的事实:浏览器无法访问 USB 大容量存储(MSC)设备——虚拟 U 盘是 OS 层概念,JS 没有 API 枚举或写入它。所以报告里那个很漂亮的「拖 .emf 进虚拟 U 盘」,在 Web GUI 里做不到,它是 OS 文件管理器的行为。
Web 端能做的是:生成 .emf → 让用户下载 → 引导他拖进 U 盘,并在页面上给出「插入后板子指示灯会蓝快闪,完成后变绿常亮」的确认提示。这个 UX 依然成立,只是执行者是操作系统而非浏览器。
用户说不出自己要什么知识库。向导的价值在于用苏格拉底式提问,把「我想要个知识库」逼成一份可执行规格:使用者是谁 / 杀手问题 10 条 / 红线 / 术语别名表 / 时效策略 / 答错代价。这六项决定了后面所有生成参数。第 5 章给出了可直接复制的完整 Prompt 文本。
| 发现 | 内容与影响 |
|---|---|
| ① 现有 Web 控制台可复用率约 60% | EdgeMemory-Gateway/web 已有:protocol.js(单行 JSON 传输层)flasher.js(esptool-js 串口刷写)ota.js(24 KB,无线 OTA)ble_client.js(Web BLE)app.js(82 KB,五个页签:主板信息/模块数据/设备·知识库/固件刷写(串口)/无线 OTA)。传输层与 OTA 页可直接继承,新增的是「生成向导 + 打包 + 烧录编排」三块。 |
| ② 现有板端知识库是纯文本检索,没有向量 | em_store.c 提供的是 em_kb_add / em_kb_search / em_kb_count / em_kb_clear / em_kb_compact,em_kb_search 是文本/标签匹配。ANN 与时序是 emlite 的新增工作(WP1)。因此 Web 端生成的 .emf 必须分两代:v1 纯文本(现在就能跑通端到端),v2 带向量(emlite 就绪后启用)。schema 从第一天就预留向量字段,避免二次改格式。 |
| ③ 源码树与项目记忆不一致(P0 确认项) | 当前 C:\emgw-build\fw_ascii\main 只有 16 个 .c 文件,不含 ota_mgr.c / kb_ota.c / cmd_console.c / em_dev.c;httpd 仅注册 4 个端点(/、/api/status、/snapshot.jpg、/stream)。而项目记忆记录这些模块与 POST /ota、POST /kbota 等端点已存在。开工前必须先核对一次(emgw-tools.cmd status + 备份区 D:\SmartEDB-dev),确认是源码树回退还是记忆过时——这直接决定 WP7 的工时是「接 API」还是「写 API」。 |
| ④ 串口灌库耗时远超 G5 判据,判据必须按通道拆分 | 12,000 条 × 662 B ≈ 7.9 MB。串口 921600 bps 理论 86 s,但加上分块开销、板端 4 KB 页写与校验,实测很可能 2–4 分钟。v1.2 的 G5 判据「灌 12,000 条 ≤ 60 s」只对 HTTP 通路成立。本版把它拆成四条分通道判据(见 7.4)。 |
| 步 | 阶段 | 用户做的事 | 系统做的事 |
|---|---|---|---|
| 1 | 连接 | 打开 EdgeMemory Studio,点「扫描设备」 | 并行探测四通道:WebUSB → Web Serial → 局域网 mDNS/HTTP 探测 → BLE;列出所有发现的板子(含 EME-xxxxxx ID、IP、形态(form)、容量、固件版本) |
| 2 | 选场景 | 选 S1 工业故障指纹库 / S2 野外巡检 / S3 私域知识库 / 自定义 | 载入对应模板(默认目标条数、默认维度、默认 TTL 策略、默认红线) |
| 3 | 配置模型 | 选 LLM 与 embedding 来源(云端 / 本地 Ollama / 离线模板),填 Key | 连通性自检;Key 存 localStorage,可一键清除;敏感场景默认提示用本地 |
| 4 | 关键词访谈 | 回答 6 组引导问题(可点候选、可改) | 跑 P1 Prompt,产出《知识库规格卡》JSON,用户可手工修订 |
| 5 | 素材补充 | (可选)上传 PDF/Markdown/CSV,或直接粘贴 | 切块(按语义不按页)、去重、抽取术语 |
| 6 | 生成 | 点「生成」 | 跑 P2 生成条目 → P3 冲突与去重审计 → P4 自生成 20 条验收用例;全程可见 token 消耗与条目预览 |
| 7 | 校验 | 看容量水位、看 P4 用例自测结果 | 本地 dry-run 检索(不烧录,先在浏览器里用同一份 schema 跑一遍 Top-K),给出「预计命中率」 |
| 8 | 打包 | 点「打包 .emf」 | 打包 + CRC32 + schema_hash + 体积预估;超限立即报警并给出裁剪建议(降维 / 砍低 salience / 缩摘要) |
| 9 | 一键烧录 | 点「烧录到板子」 | 自动选路(HTTP > WebUSB > Serial > 导出下载),4 KB 分块 + 断点续传 + 进度条 + 预计剩余时间;失败自动降级到下一通路 |
| 10 | 验收 | 在板子上跑 P4 的 20 条用例 | 逐条比对期望(命中/拒答/未找到),输出实测召回率与拒答准确率;这是 S1「top-1 ≥85%、误报 ≤5%」判据的采集点 |
| 通道 | 浏览器 API | 实测吞吐预估 | 优点 | 硬约束 |
|---|---|---|---|---|
| WebUSB 首选 USB | navigator.usb | bulk 1–4 MB/s | 无需波特率、吞吐最高、与 CDC 可复合共存、免驱动 | 仅 Chrome/Edge;Windows 需板端 USB-OTG 描述符正确(WinUSB/自定义 class);需要板端 TinyUSB 增加 vendor/WebUSB class |
| Web Serial (CDC) | navigator.serial | 921600 bps ≈ 90 KB/s | 现有 protocol.js 已实现;板端零改动;可作 console 复用 | 仅 Chrome/Edge;需 HTTPS 或 localhost;速率最低(8 MB 约 2–4 分钟) |
| HTTP over WiFi 首选无线 | fetch | 2–6 MB/s | 吞吐高、不受浏览器 API 限制、任何浏览器可用、支持多板并发 | 板端必须加 CORS 头 + OPTIONS 预检;页面必须 http/localhost;需先知道板子 IP(mDNS 或手动) |
| Web BLE | navigator.bluetooth | ≤ 100 KB/s | 现有 ble_client.js 已实现;板子未接 WiFi 时可用 | NimBLE 吞吐低;只适合灌小规模专家包(≤2,000 条)或发命令,不适合灌满库 |
| USB MSC OS 层 | — | OS 文件拷贝 | 零驱动、用户最熟、UX 最好 | 浏览器不可访问。Web 端只能生成 .emf 供下载 + 引导拖拽,见 7.3 |
四条通道对上层暴露同一组方法,让向导与烧录逻辑完全不关心底下是 USB 还是 WiFi:
interface Transport {
kind: 'webusb' | 'serial' | 'http' | 'ble';
connect(opts): Promise<void>;
disconnect(): Promise<void>;
// 单行 JSON 命令(小数据)
request(cmd: string, args?: object, timeoutMs?: number): Promise<any>;
// 大块数据传输(灌库 / OTA)
stream(payload: Uint8Array, onProgress: (sent, total) => void): Promise<Result>;
// 能力协商结果
caps(): Caps;
}
现有 protocol.js 的 request() 已经是这个形状,直接适配即可。stream() 是新增的——串口走分块命令、HTTP 走 POST body、WebUSB 走 bulk 批量端点。
不同板子(N16R8 / N8R8)、不同固件版本、不同引擎后端(emlite / emdb)参数都不同。连接后必须先拉一次能力,UI 才能正确显示容量上限与可选维度。
GET /api/caps 或 串口命令 sys.caps
{
"device_id": "EME-21E980",
"board": "devkit-n16r8",
"form": "usb-dongle", // 形态标识(gamma-card / usb-dongle),非角色
"fw_ver": "0.9.3", "build_time": "2026-10-26T14:02",
"engine": "emlite", // 或 "emdb"
"engine_ver": "0.1",
"kb": {
"capacity_entries": 12900, // 由 edgemem 分区大小与每条字节数实时算出
"used_entries": 4343,
"used_bytes": 946176,
"dim": 384, "dtype": "int8", // 0/null = 尚未启用向量
"vector_ready": false,
"ts_region_bytes": 1441792
},
"transport": ["http","serial","webusb","ble"],
"max_chunk": 4096,
"features": ["kb_import","kb_export","ota","uf2"]
}
https:// 或 localhost / 127.0.0.1。Firefox 与 Safari 不支持——页面必须能优雅降级到「HTTP 通路 + 下载 .emf」。http://192.168.1.46。所以 EdgeMemory Studio 必须以 http 或 localhost 提供(现有 start_web.ps1 符合)。若要上线到 https 域名,板端必须支持 HTTPS 或改为纯下载模式。Access-Control-Allow-Origin: *、Access-Control-Allow-Headers: Content-Type, X-OTA-Token、Access-Control-Allow-Methods: GET,POST,OPTIONS,并响应 OPTIONS 预检返回 204。send,httpd open_fn 设 TCP_NODELAY,否则分块灌库会从 10 fps 掉到 0.33 fps。EdgeMemory Studio
├── ① 设备(连接)
│ ├── 扫描 / 通道选择(自动 → WebUSB → Serial → HTTP → BLE)
│ ├── 设备卡片:EME-ID / 形态(form)/ 固件版本 / 容量水位条 / 引擎后端
│ └── 操作:断开、重启、store.map、备份导出
├── ② 新建知识库(向导)★ 核心
│ ├── Step 1 选场景模板
│ ├── Step 2 配置模型(LLM / embedding / 隐私档位)
│ ├── Step 3 关键词访谈(6 组问题 → 规格卡)
│ ├── Step 4 素材补充(可选)
│ └── Step 5 生成 + 审计 + dry-run
├── ③ 打包与烧录
│ ├── 条目表(可编辑 / 可删 / 可改 salience)
│ ├── 容量水位与裁剪建议
│ ├── 打包 .emf(含 CRC / schema_hash / 体积)
│ └── 一键烧录(自动选路 + 进度 + 断点续传)
├── ④ 验收
│ ├── P4 用例面板(20 条:10 应命中 / 5 应拒答 / 5 应未找到)
│ ├── 在板端实跑 + 命中率 / 拒答准确率
│ └── 导出验收报告
├── ⑤ 固件与更新
│ ├── 固件:OTA(HTTP)/ 串口(esptool-js)/ UF2 拖拽(β,P1)
│ ├── 知识库:HTTP 分块 / 串口分块 / USB bulk / 导出下载
│ └── 更新日志与回滚
└── ⑥ 设置
├── API Key 管理(脱敏显示、一键清除)
├── 隐私档位(云端 / 本地 / 离线)
└── 高级:维度、量化、块大小、超时
| 模板 | 目标条数 | 默认维度 | 默认 TTL | 默认红线(必须拒答) |
|---|---|---|---|---|
| S1 工业振动 故障指纹库 | 2,900 | 时序主导 向量 256d | 指纹 0 / 工况 90d | 涉及停机判定的安全阈值;超出本机型的规格 |
| S2 野外巡检 专家包 | 1,800 (板 A 档位) | 384d | 物种 0 / 点位 180d | 物种鉴定结论(只给相似候选,不下判定) |
| S3 私域知识库 首销 SKU | 12,000 (满载) | 384d 可选 256d | 决策 30d / 偏好 0 / 文档 365d | 未经确认的价格与条款;涉他人隐私 |
| 自定义 | 按板端 caps 上限 | 可选 | 自定义 | 由 Step 3 访谈确定 |
| 档位 | LLM | Embedding | 适用与提示 |
|---|---|---|---|
| ① 云端(默认) | OpenAI 兼容端点 / 通义 / DeepSeek / 智谱,可自定义 baseURL | text-embedding-3-small(1536d,需 PCA 降维) / BGE-M3 / 阿里 text-embedding-v3(1024d,可指定 512/256) | 效果最好、零本地资源。页面必须明示「内容将发送至你选择的厂商」 |
| ② 本地 | Ollama / LM Studio(http://localhost:11434) | Ollama bge-m3 / nomic-embed-text | 数据不出本机。S2 涉密场景、企业客户建议默认此档。需用户已装本地服务,页面提供一键连通性自检 |
| ③ 离线模板 | 不用 LLM,纯结构化生成 | 不用 embedding,退化成 v1 纯文本检索 | 最保守。用户直接填条目表格或导入 CSV。保证在最坏情况下向导仍可用 |
Embedding 维度与板端 caps.dim 不一致时,前端必须做降维或重算提示:优先选支持「原生指定输出维度」的模型(如 text-embedding-v3 可直接要 256d),其次用 PCA(离线拟合,前端内置一个 384×256 投影矩阵即可)。不要盲目截断,截断前 256 维等于丢掉一半信息。
用 P1 Prompt 驱动,6 组问题,每组给候选。产出《知识库规格卡》JSON,用户可在右侧实时预览并手工改。
caps.capacity_entries;超限给出三种裁剪建议(降维 / 砍 salience<40 的条目 / 缩摘要到 60 字)。以下 Prompt 已按 EdgeMemory-MVP 的约束设计:容量上限、条目自洽、断网可用、拒答优先。花括号变量由前端在运行时注入:{CAP} 容量上限、{NCHARS} 每条字数上限、{DOMAIN} 领域、{SPEC} 规格卡、{MATERIAL} 素材。
# 角色
你是一名「设备端知识库架构师」。你的产出将被编译成一个 .emf 碎片包,
烧录进一块 ESP32-S3 记忆外设(容量上限 {CAP} 条,每条 ≤ {NCHARS} 字),
在完全断网的环境下被现场人员检索。板子上不跑模型,只做检索。
# 硬约束(违反即作废,我会在编译前逐条校验)
1. 自洽:每条记忆脱离上下文单看也必须能被理解。禁止出现"如上所述""见前文"
"参考第 3 节"这类指代表述。需要上下文时,把上下文写进这一条。
2. 可检索:每条必须含具体名词、型号、数值、动作步骤。禁止只有代词和形容词。
3. 不编造:素材中不存在的数值、型号、条款、日期,一律写 [待确认],
绝不猜测填充。编造一条比缺失一条危害大得多。
4. 配额:总量不超过 {CAP} 条。宁可少而准,不可多而泛。
5. 格式:输出严格 JSONL,每行一个对象。不要 markdown 代码块围栏,
不要任何解释性文字,不要用逗号分隔多个对象。
# 你的成功标准
让一个身上只有这块板子的现场人员,在断网、嘈杂、戴手套的状态下,
问出正确的问题并得到可以直接照做的答案。
不是"信息全面",而是"关键时刻答得对、不该答时不乱答"。
# 任务
帮用户定义一块"断网可用的设备端知识库"。不要问泛泛的问题。
用苏格拉底式提问,一次只问一组,每组给出 3-5 个候选让用户选或改。
如果用户的回答太模糊,就追问一个更具体的例子。
# 开场(先说这句,再问第 1 组)
"我先问 6 组问题,大约 3 分钟。答完我会生成一份规格卡给你确认,
然后才真正开始生成内容。你可以随时说'跳过'用默认值。"
## 第 1 组 · 使用者
这块板子挂在谁身上?他当时手上有别的东西吗?
A. 现场维修工(戴手套、可能单手操作、环境嘈杂)
B. 巡检员(走动中查看、时间紧张)
C. 设备自身(机器查询自己的历史,无人类参与)
D. 销售 / 客服(需要准确的报价与条款)
E. 其他(请描述)
## 第 2 组 · 杀手问题(最重要,请多花时间)
请列出 10 个"如果这块板子答对这 10 个问题,用户就觉得值回票价"的问题。
要求:
- 每个问题都写成现场人员真正会说的口语,不要书面语
- 按"高频 × 高价值"排序,第 1 条是最关键的
- 至少 3 条要包含具体故障现象或具体型号,不要全是泛泛而问
我会把它们直接作为验收测试用例。
## 第 3 组 · 红线
哪些问题它必须回答"我不知道"而不是猜?
(例如:涉及人身安全的参数阈值、超出本机型范围的规格、
未确认的价格与交期、需要实时数据才能判断的状态)
请给出 3-8 条,每条写成一句话的判据。
## 第 4 组 · 行话
这个领域里有哪些缩写、别名、俗称需要等价映射?
请给"标准术语 → 现场叫法"的对照,例如:
"轴承外圈故障频率" → ["BPFO", "外圈频率", "外滚道"]
我会把别名写进同义词表,让用户用口语也能命中。
## 第 5 组 · 时效
哪些内容会过期?过期周期是多久?过期后应该:
A. 删除 B. 降权(仍能命中但排后面) C. 标注"可能已过期"后仍返回
请按内容类别分别说明。
## 第 6 组 · 错不起的程度
答错一条的代价是什么?
A. 只是麻烦(降权即可)
B. 造成返工(必须高准确率,宁可少答)
C. 安全事故(宁可拒答,绝不猜测)
# 输出
把以上 6 组整理成一份《知识库规格卡》,严格输出这个 JSON(不要围栏):
{
"persona": "...",
"killer_questions": ["...", "..."], // 恰好 10 条
"redlines": ["..."], // 3-8 条
"glossary": [{"std": "...", "aliases": ["..."]}],
"ttl_policy": [{"category": "...", "days": 0, "action": "delete|demote|annotate"}],
"error_cost": "A|B|C",
"domain": "...",
"target_count": 0 // 你建议的条数,不超过 {CAP}
}
# 输入
规格卡:{SPEC}
素材(可能为空):{MATERIAL}
配额:{CAP} 条上限,建议 {target_count} 条
# 输出格式(严格 JSONL,每行一个对象)
{"id":"","text":"","summary":"","tags":[],"salience":0,
"source":"","valid_from":"","ttl_days":0,"redline":false}
# 字段规则
- text :≤ {NCHARS} 字。自洽完整,含具体数值 / 型号 / 动作步骤。
禁止"参见 xx"。需要上下文就把上下文写进来。
- summary :≤ 40 字。仅用于结果列表展示,必须让人一眼判断要不要展开。
不要写成 text 的截断,要写成"答案摘要"。
- tags :3-6 个。必须包含规格卡 glossary 里的现场叫法(别名),
不能只写标准术语——用户是用口语搜的。
- salience:0-100。命中杀手问题或与红线相关的给 ≥80;
术语表条目 50-70;纯背景知识 ≤40。
- source :素材文件名或 "generated"。无来源写 "synthetic"。
- ttl_days:0 = 永不过期;>0 表示 N 天后按 ttl_policy 处理。
- redline :true 表示属于"宁可拒答"领域,检索时只在精确匹配时才返回。
# 生成顺序(严格执行,配额不足时从后往前砍)
1. 先覆盖全部 10 个杀手问题:每个至少 2 条,一正一反
(正确做法 1 条 + 常见误判的反例 1 条)
2. 再补红线条目(每条红线至少 1 条)
3. 再补术语表的别名映射条目
4. 最后才用背景知识填满剩余配额
# 自检(生成完自己过一遍,把不合格的删掉重写)
- 有没有哪条脱离上下文读不懂?删掉重写。
- 有没有哪条含 [待确认]?保留但 salience 降到 30 以下。
- 有没有两条说的是同一件事?合并成一条。
- 10 个杀手问题是不是每个都能被至少一条命中?抽查一遍。
# 输入
条目集(JSONL)+ 规格卡 {SPEC}
# 任务
1. 找出语义重复(余弦相似度可能 > 0.95)的条目对,
保留信息量更大、更具体的一条,输出合并建议(不要自行合并)。
2. 找出互相矛盾的条目(同一问题给出两个不同答案),
标出并请求仲裁,不要自行取舍。
3. 找出"看似有用实则无法离线验证"的条目:
依赖实时数据、外部链接、需要联网查询才能确认的,标记剔除。
4. 检查每条是否自洽(脱离上下文可读)。
5. 检查 tags 是否覆盖了 glossary 里的全部别名。
# 输出(严格 JSON,不要围栏)
{
"duplicates": [{"keep": "id1", "drop": "id2", "reason": "..."}],
"conflicts": [{"a": "id1", "b": "id2", "question": "...", "need_arbitration": true}],
"unverifiable": [{"id": "...", "reason": "..."}],
"not_selfcontained": [{"id": "...", "reason": "..."}],
"missing_aliases": ["..."]
}
# 输入
条目集 + 规格卡(含 killer_questions 与 redlines)
# 任务
生成 20 条验收问句,必须覆盖三类:
A. 10 条应命中(expect: hit)
- 其中 3 条必须用别名 / 口语提问(考验同义词表)
- 其中 2 条必须故意写错别字或用缩写(考验鲁棒性)
- 每条标注期望命中的条目 id 与最低分
B. 5 条应拒答(expect: refuse)——来自 redlines,
期望板子返回"不提供判断"而不是硬凑一个答案
C. 5 条应未找到(expect: miss)——超出覆盖集,
期望返回"未找到"而不是返回最像的那条
# 输出(严格 JSONL)
{"query":"...","expect":"hit|refuse|miss","expect_id":"...","min_score":0.0,"why":"..."}
# 说明
这批用例会被写进 .emf 的 testset 区,在烧录后于板端实跑。
它们是 S1「top-1 ≥ 85%、误报 ≤ 5%」判据的直接采集来源。
请认真设计 C 类(应未找到)——它能测出这块板子会不会"不懂装懂",
而这正是记忆类产品最掉价的地方。
# 说明
以下话术由固件在检索失败 / 命中红线 / 命中过期条目时直接返回,
断网状态下使用,不得调用任何模型。请按 spec 的语言风格本地化后写入。
1) 未找到
"这块板子上没有关于「{query}」的记录。
已记录的相近主题:{top3_tags}。要不要换个说法再问?"
2) 命中红线
"「{topic}」涉及{安全边界 / 型号范围 / 未确认信息},
请以现场规程或厂家确认为准,本设备不提供判断。"
3) 命中过期条目
"找到一条记录,但已 {ttl_days} 天未更新({date}),仅供参考。
未过期的最新记录:{newer_tags}。"
4) 低置信(分数低于阈值但仍返回)
"最相近的是下面这条,但匹配度不高({score})。
如果不是你要的,建议用更具体的型号或现象重新提问。"
# 设计原则(写死在固件里,不可由模型覆盖)
- 绝不把"未找到"包装成"找到了"。宁可显示"没有"也不要硬凑。
- 拒答时必须给出"已记录的相近主题",把死路变成岔路。
- 涉及安全的回答必须显式提示以现场规程为准。
三原则(沿用战略报告):自洽(脱离上下文可读)、离线可验签(板端不联网也能验完整性)、schema 与 eXtremeDB 同源(将来 emdb 后端可直接导入)。
偏移 长度 类型 字段 说明 0 8 char magic "EMKBIMG1" 8 2 u16 version 1 10 2 u16 flags bit0=含向量 bit1=含 testset bit2=已签名 12 2 u16 dim 向量维度;0 = 无向量(v1 纯文本) 14 1 u8 dtype 0=none 1=int8 2=float16 15 1 u8 schema_ver schema 版本号 16 4 u32 count 条目数 20 4 u32 schema_hash schema 定义的 FNV-1a 32 24 4 u32 payload_len payload 字节数 28 4 u32 payload_crc32 payload 的 CRC32 --- 32 字节头结束,此后为 payload --- 32 - - payload count 条记录顺序拼接 末尾 32 u8[32] digest payload 的 SHA-256(flags.bit2=1 时追加 HMAC)
字段 类型 说明
id u32 全局唯一(同碎片内单调)
text_len u16 text 字节数
text utf8 正文,≤ {NCHARS} 字
sum_len u8 summary 字节数
summary utf8 ≤ 40 字,列表展示用
tag_cnt u8 3-6
tags (u8 len + utf8)* 标签(含别名)
salience u8 0-100
src_len u8 source 字节数
source utf8 来源文件名或 "synthetic"
valid_from u32 unix 秒;0 = 未知
ttl_days u16 0 = 永不过期
redline u8 0/1
vector i8[dim] 或 f16[dim] 仅 flags.bit0=1 时存在(dtype 决定)
记录不加对齐填充——flash 空间比 CPU 对齐宝贵。板端按字段顺序流式解析即可。向量一律放在记录末尾,这样 v1 固件(不识向量)可以安全跳过前段、只读文本部分,实现前后向兼容。
payload 之后追加 P4 生成的验收用例集,格式:
u32 n_cases 重复 n_cases 次: u16 query_len + utf8 query u8 expect 0=hit 1=refuse 2=miss u32 expect_id expect=0 时有效 f32 min_score u16 why_len + utf8 why
把 testset 打进包里,好处是:板子在断网现场也能自检——运维人员按一下就能跑一遍 20 条用例,看这块板子的记忆是否还健康。这是 mem.check 命令的数据来源,也是「记忆健康检查」这个卖点的实现基础。
| 检查项 | 时机 | 失败处理 |
|---|---|---|
| magic / version | 收包头 8 字节即刻 | 立即拒绝,不擦除任何区域 |
| schema_hash 不匹配 | 收包头后 | 拒绝,返回期望的 hash。前端提示「请升级固件或重新生成」 |
| 容量超限(count × 每条 > 剩余) | 收包头后(预检) | 拒绝。必须支持 ?dry=1 预检——前端在传大文件前先预检,避免白传 8 MB |
| payload_crc32 | 收完 payload | 丢弃本次会话,原库保持可用 |
| 逐块 CRC32 | 每块到达时 | 只重传该块(断点续传的基础) |
| digest / HMAC | commit 前 | 拒绝写入 |
edgemem 是单槽无回滚,写坏了就是记忆丢失——对一个卖「记忆资产」的产品,这是最不可接受的事故。
if (caps.features 含 "kb_import" && 通道 == HTTP)
→ ① HTTP 分块上传 (最快,首选)
else if (通道 == WebUSB && 板端支持 bulk)
→ ② USB bulk 分块 (次快)
else if (通道 == Web Serial)
→ ③ 串口分块命令 (最慢但必达)
else → ④ 导出 .emf 下载,引导拖进虚拟 U 盘(OS 层)
任意通路失败 → 自动降级到下一级,并在 UI 明示原因与当前通路。
| 参数 | 取值 | 理由 |
|---|---|---|
| 块大小 | 4096 B | 对齐 flash 页,避免读-改-写放大;也与 caps.max_chunk 一致 |
| 块头 | u32 seq | u16 len | u32 crc32 | 10 字节;CRC 覆盖块数据 |
| 确认 | 每块回 {"ok":true,"ack":seq} | 逐块确认是断点续传的基础 |
| 断点续传 | session_id + 已确认最高 seq | 传输中断后重连,GET /api/kb/session 拿进度,从 ack+1 续传 |
| 并发 | 1(串行) | 板端单线程写 flash,并发无收益反增复杂度 |
| 超时 | 单块 5 s,重试 3 次 | 超过则降级通路 |
.emf 并触发下载(文件名含日期与条数,如 kb-S3-20261026-12000.emf)EMGWBOOT 的 U 盘 → 把刚下载的 .emf 拖进去」kb.stat 确认落库结果基准:12,000 条 × 662 B ≈ 7.9 MB payload(v1 纯文本略小,约 5–6 MB)。
| 通路 | 理论传输 | 含板端写入 | 验收判据 | 说明 |
|---|---|---|---|---|
| ① HTTP / WiFi | 2–4 s | 15–40 s | ≤ 60 s | 瓶颈是板端 flash 写入与校验,不是网络 |
| ② USB bulk | 2–8 s | 20–50 s | ≤ 90 s | 与 HTTP 同量级 |
| ③ 串口 921600 | ≈ 86 s | 2–4 min | ≤ 240 s | 远超 v1.2 的 60 s 判据;UI 必须给进度与预计剩余时间,否则用户以为卡死 |
| ④ MSC 拖拽 | OS 拷贝 | 20–60 s | ≤ 60 s | 板端 UF2 式落盘 |
| BLE | > 10 min | 不可用 | — | 不建议用于灌满库;仅适合 ≤2,000 条专家包(S2 板 A 场景) |
IDLE
└─> PRECHECK (GET /api/kb/session?dry=1,传包头 32B + count)
├─ 容量不足 ──> FAILED(容量) → 建议裁剪或换板
└─ 通过
└─> TRANSFER (逐块,seq 递增,每块 ack)
├─ 块 CRC 错 ──> 重传该块(≤3 次)
├─ 连续超时 ──> SUSPENDED(保留 session_id)
│ └─> 重连后 RESUME(从 ack+1)
└─ 收完
└─> VERIFY (payload_crc32 + digest)
├─ 失败 ──> FAILED(校验),原库完好
└─ 通过
└─> COMMIT (原子切换)
└─> REBOOT? ──> DONE
└─> 自动跑 testset(若含)
每个状态都要在 UI 上有明确呈现(图标 + 文案 + 进度 + 预计剩余 + 当前 seq/total)。烧录是用户唯一会盯着看几十秒的操作,进度反馈的质量直接决定他觉得这个产品专业还是山寨。
| 通路 | 实现 | 适用 | 耗时 | 风险与约束 |
|---|---|---|---|---|
| OTA(HTTP) 主力 | POST /api/ota/token → POST /ota(X-OTA-Token 头或 ?token=) | 已连 WiFi 的板子;现场批量升级 | 11–15 s (1.6 MB) | 双槽 + BOOTLOADER_APP_ROLLBACK_ENABLE + 启动 5 s 后 em_ota_mark_valid() 自证。只写 app 分区,不碰 edgemem——升级不破坏已灌入的记忆 |
| 串口(esptool-js) | 现有 flasher.js + vendor/esptool-js.js | 首刷、救砖、无 WiFi 时 | 1–3 min | 绕过板端校验,直接写 flash。只能用于首刷与救砖,不用于日常升级。切 TinyUSB 后不能再走 USB 串口,需 USB-UART 转换器 |
| UF2 拖拽 P1 评估 | 官方 Virtual USB Flash Drive UF2 OTA | β 形态(插在用户电脑上) | 20–40 s | 与拖 .emf 共用同一套交互,UX 统一,还能省下整个第二槽(N16R8 → 15,300 条)。但单槽写入中断 = 变砖,只能串口救砖。α 模组形态(焊在客户设备里)绝不可采用,必须保留双槽 OTA |
| 通路 | 实现 | 适用 | 约束 |
|---|---|---|---|
| HTTP 分块 主力 | POST /api/kb/session → /chunk → /commit | 全量替换或增量合并 | 需板端实现会话管理;支持 ?dry=1 预检与断点续传 |
| 串口 / USB 分块 | kb.import.begin → kb.import.chunk → kb.import.commit | 无 WiFi、板子在手边 | 慢(2–4 min),必须给进度;共用同一套分块协议 |
| MSC 拖拽 | 下载 .emf → 拖进 EMGWBOOT | 免驱动、免联网、最稳兜底 | 浏览器不可访问;Web 端只负责生成与引导 |
| 增量合并 P1 | 只传差异条目(新增 / 更新 / 删除三列表) | 日常小幅更新 | v1 先只做全量替换;增量合并依赖 emlite 的事务与 MVCC,放到 W1-09 之后 |
GET /api/kb/export 备份,或在 UI 上强制勾选「我已备份」才能继续。edgemem 是单槽无回滚,覆盖即永久丢失。UI 应在检测到「板上有数据且本次为全量替换」时默认勾选自动备份并先下载到本地。
fw_ascii/main 源码树中未找到 ota_mgr.c / kb_ota.c / cmd_console.c,httpd 仅注册 4 个端点。项目记忆记录这些模块已存在。开工前必须先核对源码树状态——若确实缺失,下表全部为新建(工时见 WBS);若只是当前快照回退,则退化为适配工作。
| 方法 | 路径 | 用途 | 要点 |
|---|---|---|---|
| OPTIONS | /* | CORS 预检 | 返回 204 + 三个 Access-Control-* 头 |
| GET | /api/caps | 能力协商 | 见 3.3;容量由分区与每条字节数实时算 |
| GET | /api/kb/stat | 条数 / 字节 / 水位 / 引擎 / 维度 | 前端水位条数据源 |
| GET | /api/kb/export | 导出 .emf(备份) | 流式输出,带 32 B 头 |
| POST | /api/kb/session | 开启灌库会话(含 ?dry=1 预检) | 入参:32 B 头;返回 session_id + 已确认 seq |
| POST | /api/kb/chunk | 上传分块 | 入参:session_id + seq + len + crc32 + data;返回 ack |
| POST | /api/kb/commit | 校验并原子生效 | 验 payload_crc32 + digest;失败不改变原库 |
| GET | /api/kb/session | 查询进度 / 断点续传 | 返回 acked_seq、total、状态 |
| POST | /api/ota/token | 申请 OTA token(仅首次) | 现有约定 token emgw-ota-demo |
| POST | /ota | 固件 OTA | X-OTA-Token 头或 ?token=;不碰 edgemem |
所有响应统一 JSON:{"ok":true,"data":{...}} / {"ok":false,"err":"CODE","msg":"..."},与现有串口协议保持一致,前端可共用一套错误处理。
| 命令 | 参数 | 说明 |
|---|---|---|
sys.caps | — | 同 /api/caps,供 USB/串口通道使用 |
kb.stat | — | 条数 / 字节 / 水位 / 引擎 / 维度 |
kb.export | — | 导出 .emf(分片回传,配 snap.chunk 机制) |
kb.import.begin | {"head_b64":"...","dry":0|1} | 开启灌库会话 |
kb.import.chunk | {"seq":n,"b64":"...","crc":0x...} | 分块;注意 JSON 缓冲别用 256 B(含中文 msg 会切出非法 UTF-8,用 768 B) |
kb.import.commit | — | 校验并生效 |
kb.wipe | {"confirm":true} | 清空(需二次确认) |
kb.verify | — | 跑包内 testset,输出命中率 / 拒答准确率 |
ota.begin / ota.chunk / ota.end | — | 串口 OTA(若 HTTP OTA 不可用时的兜底) |
ota.status / ota.set_token | — | 沿用现有约定 |
send,httpd open_fn 设 TCP_NODELAY。否则分块灌库从 10 块/s 掉到 0.33 块/s——这一条能让串口通路从 4 分钟变成 40 分钟。cmd_console_write() + esp_log_set_vprintf())。各源各把锁无效。hub_task 栈 6144 内不得放大缓冲;灌库缓冲一律 malloc 到堆,不放栈。周期性自发重启先抓 Rebooting 前 30 行。EdgeMemory-Gateway/web/ 【真源,OneDrive 上这份为准】
├── index.html 现有(扩展:新增「知识库向导」「验收」入口)
├── css/style.css
├── js/
│ ├── protocol.js 现有·复用(单行 JSON 传输层)
│ ├── ble_client.js 现有·复用
│ ├── flasher.js 现有·复用(esptool-js 串口刷写)
│ ├── ota.js 现有·复用(无线 OTA)
│ └── app.js 现有·重构为按需加载 studio 模块
├── vendor/esptool-js.js 现有
└── studio/ 【新增,本设计书的主体】
├── studio.html 向导主入口(可独立打开)
├── css/studio.css
├── js/
│ ├── conn/
│ │ ├── transport.js 四通道统一抽象(接口见 3.2)
│ │ ├── serial.js Web Serial
│ │ ├── webusb.js WebUSB
│ │ ├── http.js fetch + CORS
│ │ └── ble.js Web BLE
│ ├── wizard/
│ │ ├── steps.js 五步状态机
│ │ ├── tpl_scene.js S1/S2/S3/自定义模板
│ │ ├── prompts.js ★ P0–P5 预设 Prompt 全集(第 5 章)
│ │ ├── llm.js LLM 适配器(云端 / Ollama / 离线)
│ │ ├── embed.js Embedding 适配器 + 维度选择/PCA
│ │ └── schema.js 规格卡与条目 schema 定义 + 校验
│ ├── pack/
│ │ ├── emf.js .emf 打包 / 解包 / 校验(纯 JS,无依赖)
│ │ ├── chunker.js 4 KB 分块 + CRC32 + 断点续传
│ │ └── dryrun.js 浏览器内暴力 Top-K 预检
│ ├── burn/
│ │ ├── burner.js 一键烧录编排(自动选路 + 降级)
│ │ └── progress.js 进度 / 速率 / 预计剩余
│ └── ui/
│ ├── components.js 条目表、水位条、用例面板
│ └── toast.js
└── tools/
├── emf_pack.py PC 端等价打包(CI / 批处理)
└── emf_dump.py 解包与检视
不推翻现有 index.html 五个页签(主板信息 / 模块数据 / 设备·知识库 / 固件刷写 / 无线 OTA),而是在其上新增两个入口:
studio/studio.html,携带已连接的传输上下文(避免重复连接)。kb.verify 结果,渲染命中率面板。现有 ota.js 与 flasher.js 直接复用为第 8 章的固件更新通路,不重写。
| ID | 任务 | 交付物 | 验收标准 | 人日 |
|---|---|---|---|---|
| WP7-A 板端 API(若源码树确认缺失则全部为新建) | 38 | |||
| W7-01 | CORS 中间件 + OPTIONS 预检(httpd) | http_srv.c 改动 | 浏览器 fetch 直连板子成功;OPTIONS 返回 204 | 2 |
| W7-02 | /api/caps 能力协商(含实时容量计算) | 端点 + 串口 sys.caps | 改分区表或改每条字节数后,返回的 capacity_entries 同步变化 | 3 |
| W7-03 | /api/kb/stat | 端点 | 与 store.map 数字一致 | 2 |
| W7-04 | /api/kb/export(流式导出 .emf) | 端点 | 导出 4,343 条成功;再用 emf_dump.py 解析条数一致 | 4 |
| W7-05 | 灌库会话 API:session / chunk / commit / session-status(含 ?dry=1) | kb_ota.c 或新建 | 断点续传实测:传到 50% 断网,重连后从 ack+1 继续;失败路径原库完好 | 12 |
| W7-06 | 串口 kb.* 命令集 | cli_json.c 扩展 | 与 HTTP 通路跑同一数据集,结果一致 | 5 |
| W7-07 | OTA 端点(/api/ota/token + /ota) | ota_mgr.c | 升级成功且 edgemem 数据不变;断电重启能回滚 | 6 |
| W7-08 | 串口 ota.* 命令集 + kb.verify | CLI 扩展 | kb.verify 能跑包内 testset 并输出命中率 | 4 |
| WP7-B 连接层 | 20 | |||
| W7-09 | Transport 统一抽象 + 设备发现(四通道并行探测) | conn/* | 一次扫描列出所有板子(含 ID / IP / 容量 / 版本);上层代码不感知通道差异 | 6 |
| W7-10 | WebUSB 通道(需板端 TinyUSB 增加 vendor class) | webusb.js + 固件侧 | Chrome 可直连;吞吐 ≥1 MB/s | 6 |
| W7-11 | HTTP 通道(fetch + CORS + 错误处理) | http.js | 任何浏览器可用;错误码可读 | 3 |
| W7-12 | 串口通道适配现有 protocol.js;BLE 通道适配 ble_client.js | 适配层 | 两条通道与 HTTP 跑同一测试集结果一致 | 5 |
| WP7-C 知识库向导 | 32 | |||
| W7-13 | 场景模板 S1 / S2 / S3 / 自定义 | tpl_scene.js | 三个模板默认参数与任务书第 9 章一致 | 3 |
| W7-14 | 预设 Prompt 全集 P0–P5(第 5 章落地) | prompts.js | 五组 Prompt 可切换模型实测;P2 输出能被 schema 校验器 100% 接受 | 5 |
| W7-15 | LLM 适配器(云端 / Ollama / 离线三档) | llm.js | 三档均可跑通完整访谈;离线档不发起任何网络请求 | 5 |
| W7-16 | Embedding 适配器 + 维度选择(384/256)+ PCA | embed.js | 端到端可复现;W1-14 降维实验可直接复用本模块 | 6 |
| W7-17 | 规格卡 schema + 条目校验器 | schema.js | P0 五条硬约束逐条可自动校验(自洽性用规则初筛) | 4 |
| W7-18 | 素材上传与语义切块(PDF/MD/TXT/CSV) | 切块模块 | 每块继承最近标题;不产生「见上文」式条目 | 4 |
| W7-19 | P3 审计面板(重复 / 矛盾 / 不可验证 / 不自洽) | 审计 UI | 四类问题可交互处理(采纳建议 / 忽略 / 手工改) | 5 |
| WP7-D 打包与烧录 | 26 | |||
| W7-20 | .emf 打包 / 解包 / 校验(纯 JS,无依赖) | emf.js | 与 Python 版跑同一批黄金向量,字节级一致 | 6 |
| W7-21 | .emf 打包 Python 版 + emf_dump.py | tools/* | CI 可批量出包;解包可检视任一条目 | 4 |
| W7-22 | 4 KB 分块 + CRC32 + 断点续传 | chunker.js | 人为中断 3 次均能续传完成;最终 CRC 正确 | 5 |
| W7-23 | 一键烧录编排(自动选路 + 降级 + 状态机) | burner.js | HTTP 不可用时自动降级到串口并成功完成 | 5 |
| W7-24 | 进度 / 速率 / 预计剩余 / 失败原因可读 | progress.js | 串口灌库 4 分钟全程有反馈,用户不会以为卡死 | 3 |
| W7-25 | MSC 拖拽引导(生成下载 + 图文引导 + 完成确认) | 引导层 | 用户按引导一次成功;LED 状态提示正确 | 3 |
| WP7-E 验收、UI 与安全 | 22 | |||
| W7-26 | 本地 dry-run(浏览器内暴力 Top-K) | dryrun.js | 12,000 条检索 <500 ms;预估命中率与板端实测偏差 ≤10% | 4 |
| W7-27 | P4 验收用例面板 + 板端 kb.verify 对接 | 验收 UI | 20 条用例可视跑;输出召回率与拒答准确率 | 4 |
| W7-28 | 向导五步 UI + 条目表(可编辑 / 可删 / 改 salience) | steps.js + UI | 全流程可在 15 分钟内走完并产出 .emf | 6 |
| W7-29 | 固件更新面板(OTA / 串口 / UF2 三通路统一) | UI | 三通路各成功一次;升级后 edgemem 数据完好 | 3 |
| W7-30 | 安全:API Key 脱敏存储 / 一键清除 / 日志脱敏 | 设置模块 | Key 不出现在任何日志、错误信息与 DOM 属性中 | 2 |
| W7-31 | 端到端联调(三场景各跑一遍完整旅程) | 联调报告 | S1/S2/S3 均能从零到板子可用 | 3 |
| WP7 合计 | 138 | |||
WP7 共 31 项 / 138 人日,按 ×1.3 缓冲 ≈ 179 人日。与 WP0–WP6(v1.2 的 206 人日)有部分并行关系:WP7-C/D(向导与打包,35 人日)不依赖 emlite,可立刻启动;WP7-A 的灌库 API 依赖 WP1 的存储层。建议排期:WP7-C/D 与 WP1 并行先做,WP7-A 排在 WP1-02 之后。
共 28 条用例:TC-W-C-* 连接层 8 条、TC-W-G-* 生成向导 8 条、TC-W-B-* 打包烧录 8 条、TC-W-U-* 更新与安全 4 条。列:前置 → 步骤 → 预期。
| ID | 类别 | 步骤 | 预期 |
|---|---|---|---|
| TC-W-C-01 | 设备发现 | 打开 Studio,点「扫描设备」,板 B 已上电且连 WiFi,同时 USB 已插 | 同一块板只出现一次(按 EME-xxxxxx 去重,不因多通道重复列出) |
| TC-W-C-02 | CORS | 浏览器 fetch('http://<ip>/api/caps') | 返回 200 + JSON;不需要代理、不报 CORS 错误 |
| TC-W-C-03 | 能力协商 | 改分区表到方案 B 后重新读 /api/caps | capacity_entries 随之变化(不再是前端写死的旧值) |
| TC-W-C-04 | 通道降级 | 断开 WiFi 后点「烧录」 | 自动降级到串口或提示导出下载;不静默失败 |
| TC-W-C-05 | 串口吞吐 | 串口 921600 灌 12,000 条 | ≤ 240 s;全程有进度与预计剩余;实测值记录进报告替换推算值 |
| TC-W-C-06 | BLE 限制 | 尝试用 BLE 灌 12,000 条 | UI 明确提示「不建议使用,预计 >10 分钟」并推荐换通道 |
| TC-W-C-07 | Mixed content | 用 https 域名托管 Studio 后访问 http 板子 | 给出可读错误:「页面需通过 http 或 localhost 打开」,而非静默失败 |
| TC-W-C-08 | 浏览器兼容 | Firefox / Safari 打开 Studio | WebUSB/WebSerial 不可用,但 HTTP 通路与「下载 .emf」可用,且有明确提示 |
| ID | 类别 | 步骤 | 预期 |
|---|---|---|---|
| TC-W-G-01 | P1 访谈 | 选 S1 模板,对 6 组问题全选「跳过用默认」 | 仍能产出合法规格卡;killer_questions 恰好 10 条 |
| TC-W-G-02 | P0 自洽 | 用含「如上所述」「见前文」的素材生成 100 条 | 校验器全部拦下;生成的条目中零条含指代表述 |
| TC-W-G-03 | P0 不编造 | 素材故意缺失某型号参数,问到该参数 | 输出 [待确认] 且 salience ≤ 30,不编数值 |
| TC-W-G-04 | P2 配额 | 设 {CAP}=100,素材足够生成 500 条 | 严格 ≤100 条;按优先级砍(背景知识先砍,杀手问题保留) |
| TC-W-G-05 | P2 别名 | 规格卡含别名「BPFO」,生成后搜「外圈频率」 | dry-run 能命中;tags 里含别名而非只有标准术语 |
| TC-W-G-06 | P3 审计 | 故意灌入 5 条语义重复 + 2 条互相矛盾 | 审计面板分别报出 duplicates 5 组、conflicts 2 组,且不自行合并/取舍 |
| TC-W-G-07 | P4 用例 | 生成后查看验收用例面板 | 恰好 20 条:10 hit(含 3 条别名、2 条错字)+ 5 refuse + 5 miss |
| TC-W-G-08 | 离线档 | 选「离线模板」档走完整流程,全程抓包 | 零外部网络请求;仍能产出可用 .emf(v1 纯文本) |
| ID | 类别 | 步骤 | 预期 |
|---|---|---|---|
| TC-W-B-01 | 双实现一致 | 同一批条目分别用 JS 与 Python 打包 | 字节级完全一致(含 CRC32 与 digest) |
| TC-W-B-02 | 容量预检 | 打一个超过板端剩余容量的包,先 ?dry=1 | 预检阶段即拒绝;不会白传 8 MB |
| TC-W-B-03 | 断点续传 | HTTP 灌库传到约 50% 时断开网络,重连 | 从 acked_seq+1 继续;最终 CRC 正确,条数正确 |
| TC-W-B-04 | 块错误重传 | 人为篡改某一块的 CRC | 该块被拒,只重传该块;不重启整个会话 |
| TC-W-B-05 | 失败不破坏 | commit 前人为让 digest 校验失败 | 原库完好且可查(这是 edgemem 单槽无回滚下的底线要求) |
| TC-W-B-06 | 格式兼容 | 用 v1 固件(不识向量)打开一个 flags.bit0=1 的包 | 能安全跳过向量读文本部分,不崩溃 |
| TC-W-B-07 | 备份强制 | 板上有 4,343 条时点「全量替换」 | 默认勾选自动备份并先下载;未备份则不允许继续 |
| TC-W-B-08 | testset 落板 | 烧录含 testset 的包后跑 kb.verify | 输出命中率与拒答准确率,与 dry-run 预估偏差 ≤10% |
| ID | 类别 | 步骤 | 预期 |
|---|---|---|---|
| TC-W-U-01 | OTA 保数据 | 板上有 4,343 条,走 HTTP OTA 升级固件 | 升级成功且知识库条数与内容不变;重启后仍可查 |
| TC-W-U-02 | OTA 回滚 | 升级后 5 s 内断电 3 次 | 自动回滚到旧固件且能正常启动 |
| TC-W-U-03 | Key 不泄露 | 填 API Key 后走完整流程,检查日志、DOM、网络面板 | Key 不出现在任何日志、错误、DOM 属性中;仅发往用户自选端点 |
| TC-W-U-04 | Key 清除 | 点「清除全部密钥」后刷新页面 | 向导回到未配置状态;localStorage 中无残留 |
| ID | 风险 | 等级 | 缓解 | 兜底 |
|---|---|---|---|---|
| R-W1 | 源码树与记忆不一致:ota_mgr.c / kb_ota.c 在当前 fw_ascii 中不存在 | 高 | 开工前用 emgw-tools.cmd status + 备份区核对一次 | 若确为缺失,W7-05/W7-07 由「适配」升级为「新建」,WP7-A 工时 +60% |
| R-W2 | 浏览器 API 覆盖面窄:WebUSB/WebSerial 仅 Chromium,且需 https/localhost | 中 | HTTP 通路不依赖任何新 API,任何浏览器可用;下载 .emf 兜底 | 检测到不支持时直接切到「HTTP + 下载」模式,不给死路 |
| R-W3 | CORS / mixed content 卡死 | 高 | W7-01 列为 P0;页面固定以 http/localhost 提供 | 若必须 https 部署,退化为纯下载 .emf + MSC 拖拽 |
| R-W4 | 云端生成与「私域」叙事冲突:用户质疑「你不是说数据不出设备吗」 | 高 | 三档隐私位;敏感场景默认本地;页面在发送前明确告知 | 把「灌库过程可选本地模型」写成对外卖点,而非隐藏选项 |
| R-W5 | API Key 泄露(XSS / 误配置代理端点) | 中 | 脱敏显示、一键清除、不写日志;不支持配置任意第三方代理 baseURL 而未经二次确认 | 检测到 Key 出现在日志/DOM 即报错阻断(TC-W-U-03) |
| R-W6 | 串口灌库耗时 2–4 分钟被用户误判为卡死 | 中 | 进度 + 速率 + 预计剩余 + 断点续传 | 超过 240 s 自动提示改用 HTTP 通路 |
| R-W7 | JS 与 Python 打包实现漂移 | 中 | 共用同一批黄金测试向量(TC-W-B-01),进 CI | 任一实现变更未过黄金向量即阻断合并 |
| R-W8 | WebUSB Windows 驱动问题(需 WinUSB/自定义 class) | 中 | 板端描述符按 WebUSB 规范实现;提供 Zadig 指引 | WebUSB 不可用时自动退回 Web Serial(CDC) |
| R-W9 | 板端灌库缓冲栈溢出导致周期性重启 | 中 | 灌库缓冲一律 malloc 到堆,不放栈;启动打 heap 水位基线 | 按已知坑集合排查,先抓 Rebooting 前 30 行 |
| R-W10 | PDF 解析库(pdf.js)体积拖慢页面 | 低 | 按需动态 import(),不进首屏 | 去掉 PDF 支持,只保留 MD/TXT/CSV |
/api/kb/export),避免「记忆被锁死在设备里」的指控。| 序 | 动作 | 说明 |
|---|---|---|
| 1 | 核对源码树(零成本,阻塞 WP7-A 排期) | 确认 ota_mgr.c / kb_ota.c / cmd_console.c 是否真的缺失。这一条决定 WP7 是 138 人日还是 200+ 人日 |
| 2 | 跑 W7-01 CORS 冒烟(半天) | 在板端 httpd 加三个响应头 + OPTIONS,用浏览器 fetch 验证。这是整个 Web 方案的地基,不通则一切无从谈起 |
| 3 | 先做 WP7-C/D(向导 + 打包,35 人日)不依赖 emlite | 可以立刻开工,且能先产出 v1 纯文本的端到端演示——比等 emlite 就绪再一起做,早 2 个月拿到可用于对外演示的东西 |
| 4 | 把 W1-14(降维量化实验)与 W7-16(embedding 适配器)合并排期 | 两者共用同一套实验数据与评测脚本,合并可省约 3 人日 |
| 5 | McObject 授权询问(既有 P0 项,不因本设计书改变优先级) | 仍是全局阻塞项 |