|
|
1 ay önce | |
|---|---|---|
| build | 1 ay önce | |
| harness | 1 ay önce | |
| public | 1 ay önce | |
| src | 1 ay önce | |
| .editorconfig | 1 ay önce | |
| .env.development | 1 ay önce | |
| .env.production | 1 ay önce | |
| .eslintignore | 1 ay önce | |
| .eslintrc.js | 1 ay önce | |
| .gitignore | 1 ay önce | |
| .prettierrc.cjs | 1 ay önce | |
| CLAUDE.md | 1 ay önce | |
| README.md | 1 ay önce | |
| agents.md | 1 ay önce | |
| index.html | 1 ay önce | |
| jest.config.js | 1 ay önce | |
| package-lock.json | 1 ay önce | |
| package.json | 1 ay önce | |
| redirect.html | 1 ay önce | |
| tsconfig.json | 1 ay önce | |
| tsconfig.node.json | 1 ay önce | |
| vite.config.ts | 1 ay önce |
本文件当前只记录按日期的改动。项目概述、技术栈、目录结构、接口说明等内容, 等项目结束时再统一总结。
POST /api/chat(SSE)
src/components/api-chat-coordinator.ts,把 SSE 事件翻译成既有渲染组件
可识别的内容标记,渲染层无需感知新协议{thread_id, question};answer 与 result.response 只展示一次;
未收到 done/error 即断流时按连接中断处理,不自动重试src/components/stream-message-coordinator.ts 保留未用VITE_CHAT_API,新增 dev 代理 /chat-api → http://192.168.2.23:8000thread_busy → 该问题还在处理中)<scope>、<!-- POLICY_TABLE -->、
<ref_links> 等原始标记一起复制出来priority.rank 排序.gitignore 约定不入库)public/merchant-agent/policies_public.v1.json
(262 条申报事项 / 38 个政策),本轮匹配到的惠企政策置顶is_policy_library,缺失时回退到按标题在库中匹配
(已做归一化,容忍《》全半角括号等差异)status 字段判定形态(found / not_found / failed / 无 status),
替掉原先靠"候选数是否为 0"和 input_help 文案正则的猜测式判断
failed(查询服务失败)此前会落进 not_found 分支,文案被填成"未查询到匹配的公司",
等于把失败说成查无公司,已改为独立分支并说明失败原因status(初次询问是否需要公司信息)此前给"跳过公司查询",改为"不需要公司信息"/skip 改为发送选项文本「跳过公司查询」content.trim()
(<scope> 进度块算作有内容),渲染层用 stripScopeBlocks()
(进度块不算内容)。同一段内容,一处认为「有」、一处认为「没有」done 才写入,处理期间内容只有进度块,把这个缝隙
从偶发放大成「一切会话就出现」:归一化认为有内容 → 不替换文案,却把状态翻成
Finish;渲染层认为没内容 → 显示「请求已取消」,而请求其实还在跑hasVisibleMessageContent()(utils/interrupted-message.ts),
归一化与渲染层都改用它harness/tools/verify-empty-content-agreement.mjs,
断言的不变量就是「两处必须一致」,30 项断言ScopeContent 按卡片宽度切样式 —— 没撑满容器时标题 nowrap + 省略号,
撑满时切成 pre-wrap。而阶段文案长度不一(新协议的文案比原来的"正在思考中..."长得多),
切换时表现为「突然换行」且卡片高度跳动nowrap + 省略号data.市级政策id
拼出 https://zwdt.sh.gov.cn/qykj/shell_oc_policy_zq/policy/policy-detail?id=<id>
apply_link 字段 262 条全为空;资源申请备注 里的 URL 是按申报事项
零散对应、且混有内网地址(25 条指向 http://10.235.238.34:7202/imanage/login),
不能作为来源。市级政策id 有 261/262 条,可靠public/merchant-agent/policies_public.v1.json:262 条,data.市级政策id 有 261 条VITE_DOWNLOAD_URL:327 条,该字段一条都没有(它有的 policy_id 是 40 位、
与条目 id 相同,与 24 位的 市级政策id 是两套 id 体系)index.html 原本是远端优先(远端带 CORS 头,浏览器里确实会赢),
于是取不到 id → 拼不出详情地址 → 「政策名称」是纯文本。
此前怀疑的「HMR 状态陈旧」「走了兜底路径」均排除VITE_POLICY_LOCAL_FIRST:仅开发环境打开(本地优先、远端兜底),
生产仍远端优先——生产该问题属后端数据缺失,需远端那份补上 data.市级政策id,
前端无法修harness/tools/check-policy-sources.mjs:对比两个来源的条数与字段覆盖率,
并在「远端也带上 id 了」时提醒可以移除该开关c_session_id upsert:没有就新增、有就更新)与问答记录(先写问题);
回答完成补写回答;停止/断流也补写已生成的内容,避免中断的问答只有问题没有回答c_record_id 定位那一行)访客_<访客id> 归属(访客 id 复用埋点的那个)session.id(它恒等于聊天协议的 thread_id);问答记录标识用
AI 消息 id(同时是反馈组件用的 record.id,因此从 DMS 读回的历史消息点赞仍能命中原始行).env.development.local,按约定不入库),
浏览器产物里没有 token;DMS 不可用时静默降级,聊天与本地历史不受影响delContentById(各种传法都返回参数错误),
而是 POST /content/updateAudit 把状态改成 4(销毁);addContent 会返回记录 uuid?access_token= 或 ?credit_code=harness/)结构对齐
Learn Harness Engineering 的 OpenAI 高级包:
docs/plans/ → docs/exec-plans/(completed/ + tech-debt-tracker.md),
并新增结构校验脚本 harness/tools/validate-harness.mjs(机械约束优先于口头约定:
可检出假 passing、多个 in_progress、游离的计划文件等)harness/、CLAUDE.md、agents.md 纳入版本控制(此前排除)
.env.*.local、.claude/settings.local.json),
以及 esbuild 生成的 harness/tools/_*.mjs(构建产物,入库会与源码漂移);
产物重新生成的命令写在 harness/tools/README.mdtitle 与
declaration_item 都取自 card.name.text(只有政策名),而后端不下发
id/policy_id(实测恒为 undefined)——这些卡片在字段上完全无法区分,
原来的反查只要任一条件命中就返回,于是点第二张也跳到第一张resolvePolicyDetailIndex(),对象同一性优先,
退化的字段比对只在唯一命中时采信,多命中时交给「直接展示被点条目」的兜底
—— 宁可走兜底,也不张冠李戴harness/tools/verify-policy-detail-index.mjs(10 项断言,
含「点第 2 张 → 下标 1」这条针对本 bug 的断言)declaration_item 改用后端每条唯一的 data.title,不再与卡片标题同值;
卡片区标题仍是政策名,观感不变(含一处连带的 panelData.title 取值顺序修正——
否则改详情标题会连带改掉卡片标题)harness/tools/verify-policy-card-fields.mjs(8 项断言:同政策两条必须可区分、
卡片标题不变、无后缀时退化为政策名)cardList,与旧列表共用同一套模板与样式类,未新增 CSS;
旧列表逻辑靠既有守卫自然短路,openPolicyList 一行未动harness/tools/verify-policy-card-list.mjs(24 项断言:坏 JSON/空输入不抛错、
首见优先去重、降序+同分稳定、六面关键词过滤)
—— 断言数由 31 降为 24:随「收回会话级聚合」删掉了那 7 项聚合断言c_record_id,
问题与回答各存一半)。曾一度改成「一个会话一行、整段对话 JSON 塞进 c_answer」,
现按需求改回——更贴近库表原本语义(chat_record 即一行一轮问答),也便于按轮次检索统计。
反馈随之改为写在该轮的 c_feedback_* 列上;会话删除连带清理其名下所有问答行harness/docs/exec-plans/completed/ 开始启用,
已实现的两个计划(DMS 落地、政策列表侧边栏)移入并更新状态;
结构校验脚本新增两条检查(active/ 里状态写着已完成、completed/ 里状态仍写进行中)isGenerating,于是「同一时间只能一个会话在生成」,
并衍生出两个 bug:生成中切到别的会话,发送按钮仍显示「停止」(想发送却停了生成);
停止时目标会话解析错误(同步事件把它提前清空 → 回落到「当前会话」),
会把停止打在看的那条会话上、并凭空造出一条空的「请求已取消」消息tasks Map + 每轮独立协调器实例 + 监听器闭包捕获会话 id),
各会话并行、互不干扰;停止只停自己那一轮;新建会话不再打断正在生成的会话verify-chat-task-utils.mjs(33 项,含把「响应式容器破坏对象同一性」
这个陷阱钉死的断言)、verify-coordinator-multi-instance.mjs
(11 项,验证多实例事件隔离——并行化的地基)VITE_STREAM_SERVER / VITE_STREAM_TOKEN /
VITE_HUMAN_IDLE_MP4_URL / VITE_PLUGIN_URL / VITE_PLUGIN_ASR)——数字人与插件那条
从未接入本项目前端的链路的遗留;其中两个还是入库的凭据.env.development 里重复定义的 VITE_ASR / VITE_XF_ASR(后定义的才生效)/stream/ 代理(/asr/ 保留,是切讯飞 ASR 的退路)public/images、public/pencil_exports 与构建分析产物 stats.htmlquestion 文本」的方案(用户拍板,不需要后端配合)sendMessage 拆成两个变量——发给后端的 questionForBackend(含附件地址)与
本地用的原文 text(气泡、会话标题、DMS 记录都不显示 URL)VITE_UPLOAD_SIGN_URL(四份 env 均写,值仍为厂商地址),
将来换自建签名服务只改配置//qingpu-data-api.metamaker.cn/common/qp_signed_url 无需鉴权即可取签名;
文件实际存放在厂商的 OSS bucket heijing-products(杭州),
外链为 https://prod.heijingai.com/qingpu/<uuid>.<ext>harness/tools/verify-attachment-question.mjs(10 项断言)BusinessAssistant.vue 的页面浏览、BusinessRecord.vue 的反馈统计)
与整个 src/network/api/assistant-statistics.ts,以及已无人使用的
VITE_ASSISTANT_STATISTICS_BASE_URLgetOrCreateAssistantVisitorId(DMS 的 访客_<id> 归属在用),搬进 DMS 模块,
localStorage key 保持不变(改了会让已存在的访客换新 id)src/network/api/card/index.js 整文件删除(三个函数均无调用方)
及其未使用的 importPolicyListUpdatedMatch.vue 有意保留——老会话历史里可能仍有
<policy-list-updated> 块,删了那些卡片列表会渲染不出来title = 问题文本);删除会话仍清理其名下的 1889 记录[已停用],同时在 verify-dms-payload-fields.mjs
里加了源码级断言(不得出现未注释的 upsertDmsSession 调用),防止被顺手改回title 与运营分析字段:
title = 会话标题 —— DMS 的系统字段 title
原本只在创建时初始化,改 c_title 不会带动它,所以前端改名后 DMS 里看到的标题不变title 用问题文本(原先库里是字符串 "null",
在 DMS 列表里看不出是哪一轮)title(用户需求);1887 另有四个 must=true 的运营字段
(summary / qpyszx / qyzt / rzqpyx)——不带就 214 数据错误,四个必须全带,
但只传空值/默认值(取值属业务口径,前端不猜;用户拍板「先传空值」)。
⚠️ 只在新增时带、更新时一律不带——否则会把后端将来回填的值冲成空harness/tools/verify-dms-payload-fields.mjs(9 项断言):打桩 fetch 抓真实请求体,
断言字段集合「不多不少」且 title 的值正确 —— 这条不需要 DMS token 就能跑VITE_CHAT_TARGET(聊天后端真实地址)——此前它硬编码在 vite.config.ts 的
代理配置里;现由 vite.config.ts 从 env 读取(保留同值兜底).home-fixed-input 仍是 position: fixed 悬浮,
给它预留的是写死的 padding-bottom: 180px(与输入框实际高度无关);
另有 height: 100dvh 无 100vh 兜底、页面无 text-size-adjust: 100% ——
见技术债第 7 条,换台手机仍可能变样你好,有什么我可以帮您的?)clamp(17px,5.7vw,22px),≥386px 的屏恒为 22px,
更窄的屏按比例收缩以免标题被顶出屏幕)。过程:一度整体 −4px(PC 32→28、移动端 20→16)
→ 用户反馈移动端「太小了」→ 全部还原 → 用户要求移动端调到 22pxcompany_info → 分类接口 → DMS 企业两栏目):
result 事件里的 company_info 非空时,回答完成后自动同步到 DMS 两栏目:
1888 企业基础信息 ← 分类接口(POST /api/company/classify)的六个角度标签,
一行一家企业(幂等键 c_credit_code,字段级 diff,无差异零写入);
1886 企业荣誉信息 ← company_info.honors.records(逐条真实荣誉),一条荣誉一行honors.complete === true 才删;荣誉名写 c_honor 与系统 title。
—— 数据源曾用分类接口的「资质荣誉」标签,实测宇树科技只给 2 个标签而真实荣誉有 31 条,
按用户要求改为以 honors.records 为准(分类标签仍写进 1888 的 c_tag_honor)company_info(原先 result 分支是空的、数据被丢弃)verify-company-classify-sync.mjs(53 项纯逻辑)、
verify-company-info-passthrough.mjs(10 项,假 SSE 流验证适配层把 company_info 原样带出)、
verify-company-classify-dms.mjs(23 项打真实 DMS)、
verify-company-classify-e2e.mjs(16 项端到端:真实分类接口 + 真实 DMS)api-chat.md 补上 company_info 的契约章节c_tag_* 分类标签字段);
两栏目都已有数据(DMS 侧 2026-09-17 灌入),所以同步多为「更新已存在的行」,
只动身份三字段与标签字段;1886 靠 c_source 标记所有权,绝不碰灌入的荣誉行@media (max-width: 360px) 里那条 .heading { font-size: 28px }:
它最初与基础值同值(空操作),09-18 基础值改小后变成窄屏把标题放大回 28px 的覆盖,
而标题是 white-space: nowrap —— 360px 屏下标题宽 356px、可用仅 272px,会溢出屏幕 84px;
窄屏收缩已由 .heading 自己的 clamp 负责(已注释保留原文并写明原因)company_info 里 DMS 有列的字段现在全部同步
honors.records 逐条荣誉对齐(一条一行,c_honor 存荣誉名)harness/tools/audit-company-info-coverage.mjs:1888 写 43/47 列、
1886 写 12/13 列;未覆盖的 4 列(c_scope_brief/c_phone_number/c_email/c_industry)
后端接口本就没返回max-width: 462px + 部门列写死 110px,整行只有约 520px 宽、右侧空一大块,
部门写全称时还被挤成三行;改为名称列占满剩余空间、部门列可伸展(下限仍 110px)result.data.cipa 给出布尔值,
true(问的是 CIPA / 海外服务平台 / 出海服务)时在参考资料之前追加一张二维码图片
(src/assets/qiyeweixin.png,点击可放大)
<image-scope> 内容标记,渲染层零改动(该标记本来就有对应行类型)<image-scope>,避免把图片地址复制进剪贴板false/缺字段/非布尔值不显示、跨轮不串、补问轮不展示)answer_start / answer_delta / answer_end /
answer_abort,契约见 harness/docs/reference/token-streaming.md)
src/components/answer-stream.ts):
正文块用的规整函数是插入空行的,若不做增量规整,answer_end 的"整块替换"会与已流文本
分歧 → 整段重建 → 思考中卡片每轮闪现重播。规整器保证「已上屏内容恒等于最终全文的逐步前缀」,
正常路径因此退化为纯尾缀追加(零抖动)concat(deltas) === answer_end.text 成立;
answer 事件是正文开头的压缩版,流式轮跳过不重复渲染(仅当与正文公共前缀 < 90% 才保留)verify-answer-stream.mjs(78 项:纯逻辑 + 协调器级假流)、probe-answer-stream.mjs(真后端探针)source/item
才开始 answer_start,而前端此前一直等 done 才显示卡片 —— 只能靠整块重写把卡片插到正文之上,
界面上表现为「正文先渲一遍、又从头再渲一遍」。现在开流前就把卡片上屏,收尾只做纯追加answer_end.text(1610 字)比增量拼接(1309 字)
多 301 字,导致应用侧记录的"流式正文"不再是上屏内容的后缀 → 重排落兜底分支 → 正文上屏两份。
修法:answer_end 后同步"流式正文",并加固兜底(对不上时绝不重复插入正文)done 要等 result(375~390KB 的完整快照),
比正文定稿晚 3~10 秒,而参考资料的数据(source 事件)在正文开流前就发完了 —— 等于白等。
现在正文一定稿就发;二维码(来自 result.cipa、只能等)走定向重排,保住「在参考资料之前」的版式.dms-token,代理每个请求现读(换 token 不用重启):
npm run dms:token(从剪贴板读)、npm run dms:login(账号密码自动登录)、
npm run dev 前自动续期。顺带发现原来 .env.development.local 里的 token 写在了下一行,
dotenv 读出来是空串 —— 等于一直没生效company_info 值与库里不同就更新;
但已被登录态认领过的行(c_is_login="1")不许被后来的访客对话改写 —— 否则会把这行改到别的公司名下c_title 不写(标题由运营侧生成);DMS 系统字段 title 只在建行那一刻带上c_ip,访客身份为 访客_<IP>;
取不到 IP 时退回浏览器随机 id,不影响任何写入###、**、反引号、[文字](链接) 的括号语法、无序列表的 -
都不再复制出去;有序列表序号保留(顺序是内容)、链接地址保留(写成「文字(地址)」)addContent 会忽略该列照写,
而 updateContent 会静默丢弃整个更新(返回仍是 {"code":200,"message":"成功"})——
新增列时必须先确认 DMS 侧已加pictures / files 两个请求字段(后端扩展了 /api/chat 的请求体)
pictures:图片 URL 列表;files:非图片文件 URL 列表。两者都是字符串数组422 invalid_request 拒掉;
带上 pictures 问「这张图片里是什么」,模型确实描述了图的内容(不是收下就忽略);
空数组与不传都接受POST {DMS}/file/uploadFile(multipart,五个字段全必填:file / type / columnId / contentId / paraName),
返回相对路径(如 /static/photo/1889-c_question-<uuid>.png),它随请求体发给后端;
原先直传厂商 OSS 的实现整段停用保留
type 决定目录(0 图 → /static/photo/,其余 → /static/file/);
少任何一个字段都返回 {"code":-1,"message":"缺少必传字段"}window.alert;
拖拽路径还会把不支持的文件静默丢弃,用户不知道文件为什么没进去)file.type.startsWith('image/'),
等于任何 image/* 都放行 —— SVG / BMP / HEIC 全被当图片收下;现改为按扩展名判<Toast> 是全屏黑遮罩 + 居中白框(本质是模态框的观感,还会挡住整页)@/utils/app-message(Ant Design Vue 4.x 的 message,本项目已有该依赖):
顶部弹出、3 秒自动消失、错误为粉底红感叹号 —— 就是设计稿那种小条showToast(text));顺带发现 BusinessRecord.vue 里那份
<Toast> 与 showToast 没有任何调用点、也没有子组件 inject(死代码),一并停用window.alert 一并换成轻提示(用户 2026-09-23:「另外那几处语音的
window.alert 一同更换」):PC 1 处(初始化失败)+ Mobile 3 处(麦克风权限 ×2、初始化失败 ×1)
showToast(...) 后与全站提示同一条链路(顶部弹出、自动消失),
不再有阻塞式弹窗;文案与收尾动作一字未动(仍会释放语音实例、复位状态)openMicroPhone 返回 false 时仍是静默 return(无任何提示)——
与 Mobile 不一致,属已报告的待定项,未擅自补文案window.confirm("可能会清空之前的文字…") 不换 ——
它是要用户做决定的模态确认,不是轻提示APP_MESSAGE_DURATION 一处即可)/static/photo/…),
而消息附件仍把相对路径当展示地址用 —— <img src="/static/photo/…"> 经 dev server
取回的是 index.html(200 但 text/html),图解不出来。
实测对照:相对路径 → text/html;/dms-api + 相对路径 → image/png ✓url(可显示地址,气泡里 <img> 用)与
fileUrl(相对路径,随请求体 pictures/files 发给后端),
toChatAttachmentUrls 改取 fileUrl(发给后端的地址与从前一致,不受影响)c_image / c_file)(用户 2026-09-23:在「助手问答记录」里
新建了这两个字段,要求把对应的路径存进去):
/static/photo/…)—— 与请求体 pictures/files 里发给后端的是
同一个值,库里记的和后端收到的能对上账;不存 /dms-api 展示地址(那是环境相关的拼接结果)updateContent 遇到模型里没有的列会
静默丢弃整个 patch(返回仍是 200「成功」),所以更新路径只在「真有附件值」时才捎上这两列,
最坏也只影响带附件的轮次,不会连累补 answerverify-dms-chat-storage.mjs 65 → 78/78(打真实 DMS 的写入-读回-补 answer 全链路)c_ip(真实 IP)、c_is_login="0"、
时间戳、系统 title,以及四个必填运营位(summary 空串 + 三个布尔 false,只做空值兜底、
不猜业务值);c_credit_code 留空(用户拍板:这一列只放真实信用代码,不写访客标签)totalResponse)时写一次 —— 中断/报错的轮次不建行c_title、c_description、c_focus_points、c_core_needs、
c_contact_clues、c_follow_up_departments、c_settlement_intent_level、jygjbm 等)
前端一律不碰,否则会把分析系统的值冲掉c_is_login="0" 的访客行,且只在登录后拉取verify-dms-chat-storage.mjs 78 → 89/89(打真实 DMS 新增 11 项,含"后续轮次带回
企业信息时这个纯访客行能正常被填上");变异测试注入旧行为 → 断言大面积失败CreateToken,24h 过期),而 secret 不能进前端产物 ——
所以走 dev 端点 GET /nls-token/(vite.config.ts 的中间件,apply:'serve'),
由 vite 在 node 侧签名并把 {token, expireAt, appkey} 交给前端;凭据只写在
.env.development.local(gitignored,值不进仓库任何文件)。
harness/tools/nls-token.mjs(唯一一份),命令行工具
harness/tools/get-nls-token.mjs 与 dev 端点共用;带 5 分钟提前刷新 + 并发去重status:20000000 SUCCESS);
npm run build 后产物里搜三个凭据值 0 命中index.html 的 initToken() / initCardInfo() / else 分支:两个环境都走不到
(isKnowledgeApi 恒 true),且它们的产物(globalThis.token / cardInfo)全仓库无读者;
那段本身还是坏的(调用从未挂到 globalThis 的 changeLoadingText/showReloadView,走到会抛)。
一并删掉随之孤儿的 randomString / changeLoadingText / showReloadView 与
VITE_APP_KEY(产物核对:app_key / openid_to_token 0 命中)src/three-libs/asr/ → src/asr/,three-libs/ 目录删除(名字有误导性,且只剩 asr 一个子目录);
import 同步为 @/asr/nls,验证脚本新增断言钉住新位置.env.qingpu / .env.test 删除前,先把它俩独有的配置合并进 .env.production:
VITE_DOWNLOAD_URL="/api/download/" 与 VITE_REDIRECT_MAP(后者 production 原本没有,
直接删会让 redirect.html 的产物变成 JS 语法错误 —— 顺带修掉了:以前 npm run build
打出来的生产包里那个页面就是坏的)
build:qingpu / build:test,保留 zip,新增 npm run deploy:qingpu(= build + zip)index.html 里两处本来就是死的 qingpu 分支一并删掉(isQingpuEnv 写的是字面量 "MODE"、
永远 false 且无人读;isLanMode 赋值后从未使用)initToken()(openid_to_token + VITE_APP_KEY)现在两个
环境都走不到(唯一为 false 的 test 环境已删),删它前要先理清与 initCardInfo() 的关系harness/docs/reference/current-api-call-sites.md):
VITE_UPLOAD_SIGN_URL)、旧厂商 ASR 全套
(three-libs/asr/index.ts + VITE_ASR / VITE_XF_ASR + vite /asr/ 代理 +
index.html 的 globalThis.isXF)、stream-message-coordinator.ts 整个类(~1100 行)utils/scope-stream-boundary.ts、updateSessionInfo、
getModelServerUrl / isKnowledgeApi、三个空目录、两处停用注释块,
以及因此变成孤儿的 npm 依赖 qs / @microsoft/fetch-event-sourceMessageState 枚举是活的,已从被删的协调器搬到 src/types/record.ts(取值未动)components/utils.ts 因此被误删,构建报错才发现,已恢复)src/asr/nls-protocol.ts(纯函数:帧构建 / 事件折叠 / 16k PCM 编码)src/three-libs/asr/nls.ts,对外契约与旧 SDK 一致 → 两个壳组件只改了 import
与一处构造参数(去掉硬编码 appKey),其余一行没动StartTranscription→TranscriptionStarted→二进制音频帧→
TranscriptionResultChanged(中间结果,会被覆盖)→SentenceEnd(整句定稿)→TranscriptionCompleted;
两条硬约束——每条指令都要带 appkey、task_id 不能是空串(各踩过一次,已写成断言)SentenceEnd;中间结果只用于实时显示harness/tools/verify-nls-realtime.mjs 39/39,含真实识别(Windows TTS 造语音 →
推流给真实网关 → 识别结果与所说话一致)与切线的源码级断言audioContext.sampleRate 算(旧实现写死 44100 的隐患不再沿用)harness/docs/reference/asr-nls.md(旧的 asr-realtime.md 标注为"换之前那套")/nls-token/ 端点(已登记技术债 #10);
浏览器实测由用户确认(harness 不能替真人说话)harness/docs/reference/asr-realtime.md ——
SDK 在仓库里(src/three-libs/asr/index.ts),文档逐条对源码核对:构造参数与方法、
transform/error 事件契约、底层 WebSocket 协议(/common/asr_hub?project=qingpu&engine=aliyun_dashscope…、
首帧、16k/16bit 单声道 PCM 的二进制帧、结束帧、服务端 start/stop → mid/fin/breath 的映射)、
两端的生命周期与调用点、以及6 条已知坑(采样率换算写死 44100、"权限被拒绝"文案不可达、
openMicroPhone 只会 throw 或返回 true、非讯飞分支不主动关 socket、硬编码的十年期 JWT、
clear() 会清空所有监听)。只是文档,没改任何代码。.env.production / .env.qingpu / .env.test
都没有配 VITE_DMS_API —— 这些环境打出来的包里附件传不上去(直接抛「未配置 DMS 接口地址」)、
DMS 会话落库也静默跳过。接生产要先定:DMS 从部署环境是否可达、token 由谁注入
(开发环境是 vite 代理加的,生产得 nginx 做同样的事)