|
|
1 miesiąc temu | |
|---|---|---|
| build | 1 miesiąc temu | |
| harness | 1 miesiąc temu | |
| public | 1 miesiąc temu | |
| src | 1 miesiąc temu | |
| .editorconfig | 1 miesiąc temu | |
| .env.development | 1 miesiąc temu | |
| .env.production | 1 miesiąc temu | |
| .eslintignore | 1 miesiąc temu | |
| .eslintrc.js | 1 miesiąc temu | |
| .gitignore | 1 miesiąc temu | |
| .prettierrc.cjs | 1 miesiąc temu | |
| CLAUDE.md | 1 miesiąc temu | |
| README.md | 1 miesiąc temu | |
| agents.md | 1 miesiąc temu | |
| index.html | 1 miesiąc temu | |
| jest.config.js | 1 miesiąc temu | |
| package-lock.json | 1 miesiąc temu | |
| package.json | 1 miesiąc temu | |
| tsconfig.json | 1 miesiąc temu | |
| tsconfig.node.json | 1 miesiąc temu | |
| vite.config.ts | 1 miesiąc temu |
本文件当前只记录按日期的改动。项目概述、技术栈、目录结构、接口说明等内容, 等项目结束时再统一总结。
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 命中src/asr/nls-protocol.ts(纯函数:帧构建 / 事件折叠 / 16k PCM 编码)src/asr/nls.ts(2026-09-24 前在 src/three-libs/asr/),对外契约与旧 SDK 一致
→ 两个壳组件只改了 import 与一处构造参数(去掉硬编码 appKey),其余一行没动StartTranscription→TranscriptionStarted→二进制音频帧→
TranscriptionResultChanged(中间结果,会被覆盖)→SentenceEnd(整句定稿)→TranscriptionCompleted;
两条硬约束——每条指令都要带 appkey、task_id 不能是空串(各踩过一次,已写成断言)SentenceEnd;中间结果只用于实时显示harness/tools/verify-nls-realtime.mjs(39/39,2026-09-24 已长到 44/44),
含真实识别(Windows TTS 造语音 → 推流给真实网关 → 识别结果与所说话一致)与切线的源码级断言audioContext.sampleRate 算(旧实现写死 44100 的隐患不再沿用)harness/docs/reference/asr-nls.md(旧的 asr-realtime.md 标注为"换之前那套")/nls-token/ 端点 —— 2026-09-24 起改为直接调
后端网关的 getALYToken(见本页 ## 20260924 第一条),技术债 #10 已大半还清;
浏览器实测由用户确认(harness 不能替真人说话)harness/docs/reference/asr-realtime.md ——
当时 SDK 就在仓库里(src/three-libs/asr/index.ts,该文件已于 2026-09-24 删除,
文档现在是历史记录),文档逐条对源码核对:构造参数与方法、
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 没有配 VITE_DMS_API
—— 该环境打出来的包里附件传不上去(直接抛「未配置 DMS 接口地址」)、
DMS 会话落库也静默跳过。接生产要先定:DMS 从部署环境是否可达、token 由谁注入
(开发环境是 vite 代理加的,生产得 nginx 做同样的事)今天两条主线:语音识别换阿里云 NLS 收尾(token 改由后端网关签发)+ 一次大清理 (死代码、多余环境、走不到的引导链)。清理过程中出过一次回归,已修并补上守护脚本。
DEFAULT_DEPTS),改为从惠企政策库的 政策所属部门
现算(去重 + 中文排序,前面固定「全部部门」)globalThis.policiesPublic
是异步加载、没有响应式依赖,computed 会把首次的空列表缓存住verify-policy-library 29 → 35/35(新增第 7 节:打印库里 10 个部门、
断言不再有硬编码列表、选项由数据派生、现算时机、失效回退、匹配口径)QuestionCard 本来就有只读分支,
但判定是「内容里带 answers」,而此前没有任何代码把答案写回内容 → 永远走不到。
新增 src/utils/question-cards-answers.ts(纯函数),提交时把答案写回最后一条带
<question-cards> 的 AI 消息 → 卡片立刻变只读,刷新后也保留isFreeformSelected() 对常驻输入框恒返回 true
→ 那一行永远带选中边框。改成「填了内容、且单选时没选别的选项」才算选中verify-question-cards.mjs 18/18(含边界、幂等、多轮只替换最后一个、
源码级接线断言),变异测试两处都能红;其余回归全绿http://121.43.55.7:10011/proxy/verify/getALYToken):
{code:200, content:"<token>"},拿它 + 新 appkey 真调通一次识别才改代码/aly-api/…;端点只有 HTTP、页面是 HTTPS,必须走同源路径,
与 /ip-api/ 同一个坑),前端侧不再需要 AccessKeySecret → vite 里那段"自己签 token"的
/nls-token/ 中间件连同 AK/SK 通路一起删除VITE_NLS_TOKEN_API 进 .env.development;appkey 放 .env.development.local(gitignored)/aly-api/ + 注入 VITE_NLS_APPKEY(技术债 #10)harness/docs/reference/current-api-call-sites.md §0VITE_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-source(净删 2257 行)MessageState 枚举是活的,已从被删的协调器搬到 src/types/record.ts(取值未动)components/utils.ts 因此被误删,构建报错才发现,已恢复).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 赋值后从未使用)index.html 的 initToken() / initCardInfo() / else 分支两个环境都走不到
(isKnowledgeApi 恒 true),产物(globalThis.token / cardInfo)全仓库无读者,
且那段本身是坏的(调用从未挂到 globalThis 的 changeLoadingText/showReloadView,走到会抛)。
一并删掉随之孤儿的 randomString / changeLoadingText / showReloadView 与 VITE_APP_KEY。
另:src/three-libs/asr/ → src/asr/,three-libs/ 目录删除(名字有误导性,且只剩 asr 一个子目录)initToken 时在 index.html 的内联脚本里留了个不配对的 } → 脚本语法错误 →
hasSequence 没置位、sequenceLoaded 没派发 → 加载遮罩永不隐藏。
构建没抓到,因为 Vite 不解析 index.html 里的普通 <script>(原样拷贝);
已修,并新增 harness/tools/verify-inline-scripts.mjs(编译 dist/*.html 的内联脚本 +
报出未替换的 %VITE_*% 占位符,变异测试确认会红),同时写进 sops/verification.md:
改了 index.html / redirect.html 必须跑它current-api-call-sites.md §0 A 表;
相比清点时新增一条漏记的(正文里 <policy-list-updated> 给的动态 link)progress.md 续 50,本文不重复。stats.html 及相关配置(用户:「这个不需要,把它和相关代码删掉」):
vite.config.ts 的 rollup-plugin-visualizer 插件块 + 该 devDependency
(package.json / package-lock.json)+ .gitignore 那两行 + 仓库根目录 1.78 MB 的报告本体。
dist/:插件 v5 的选项名是 filename(不是 file),
file: "./dist/stats.html" 被忽略 → 回落到默认名、按 cwd 解析 → 仓库根目录Failed to scan for dependencies)+ 加护栏:
.vue 也当 HTML,扫描前先 replace(/<!--.*?-->/gs, "<!---->")
(可跨行)—— .vue 的 <script> 里一个不成对的 HTML 注释起始符会和后面任意一个结束符
(哪怕隔着几十行)配成注释整段吃掉。BusinessRecord.vue 的注释与 9 行后的正则就这么配上了对,
于是正则那行被并进注释、下一行以 .replace( 开头 → Unexpected "."--force),npm run build 抓不到.vue 挪到 stream-xml-filter.ts
(POLICY_TABLE_START / POLICY_TABLE_END,顺带消掉重复);新增护栏脚本
verify-vue-script-html-comments.mjs,
并写进验证 SOP 与架构「关键约束」npx vite --force 冷扫描 0 错误;产物 hash 变了但只差 70 字节(字面量去重),语义等价index.html 里那段第三方脚本(hm.baidu.com/hm.js?d6307cb…)整块移除
dm:["demo.heijingai.com"] ⇒ 数据进的是厂商账号,
本项目从未用过它的数据(git log -S '_hmt.push' 全历史为空,代码里也无人读 _hmt)HMACCOUNT/Hm_lvt_*/Hm_lpvt_* cookie 并上报 hm.gif;
传的是访问元信息(URL / 来源 / 标题 / 屏幕 / 语言 + utm 渠道参数),不含任何业务数据VITE_DOWNLOAD_URL(用户确认):它只定义在 .env.production、全仓库没有任何读者
(index.html 的本地兜底早已写死 /merchant-agent/policies_public.v2.json)⇒
生产侧 nginx 也不再需要转发 /api/download/
verify-policy-library.mjs 新增 §10(含"政策库远端/本地兜底都还在"两条防误伤断言),
变异测试(把配置放回去)确认会红VITE_ALY_TARGET)VITE_ALY_TARGET:vite.config.ts 的 /aly-api/ 代理只认它、缺了就落到硬编码兜底,
与另外三个 target 不对称;填入值同原兜底 ⇒ 零行为变化runtime-config.ts 的 getApiBaseUrl():无人调用,且它读的 globalThis.VITE_API
从来没有被赋值过(index.html 只注入 CHAT / DMS / IP)⇒ 纯死代码architecture.md 的环境变量表按代码重写(原表还列着已删的 VITE_MODEL_SERVER /
VITE_ASR / VITE_STREAM_SERVER,并缺一半现役变量)/aly-api 经新 target 取到真实 token · /ip-api 取到真实 IP ·
产物 hash 未变(main-D0U2AkA8).env.development.local 里的 ALIYUN_NLS_* 不是残留 ——
是 harness 的 NLS 测试脚本在读runtime-config.ts 的 NLS_APPKEY_BUILTIN ⇒ 生产不再需要部署侧注入
(覆盖链保留:globalThis 注入 → env → 内置值,临时换值不必改代码).env.development.localasr/nls.ts 注释里的 /nls-token/、asr-nls.md 的「旧实现保留 / VITE_ASR·VITE_XF_ASR 仍在」
(两者早在 2026-09-24 就删了)、NLS 校验脚本里的断言标签/aly-api/verify-nls-realtime 44/44(含真实网关连接).env.development.local 里
不再保存任何凭据(现在只剩一段说明)—— ALIYUN_NLS_AK/SK 是"网关取不到 token 时自签"的兜底,
ALIYUN_NLS_APPKEY 已内置在代码里
readBuiltinAppkey()),启用条件放宽为"只要有 appkey"
⇒ 删掉凭据后真实网关段仍然会跑:verify-nls-realtime 44/44、verify-nls-browser-path 4/4,
两者都是真实联网识别dms/client.ts 一条指错位置的提示(DMS token 在本机 .dms-token,不在 .env.development.local)loadEnv / readDotEnvFile 都容忍文件缺失,
.gitignore 的 .env.*.local 模式仍在)⇒ 仓库只剩 .env.development / .env.production 两份;
同时修掉 4 处"凭据在那个文件里"的旧注释(vite.config.ts 的 DMS token 来源、两个 NLS 脚本头部、.env.development 头部)get-nls-token --check 仍读得到内置 appkey · verify-nls-realtime 44/44(真实网关)·
dev 冒烟页面 200 且 /dms-api 返回真实数据 · 构建 + validate-harness 通过harness/tools/dms-login.mjs 新增 DEFAULT_ACCOUNT;取值顺序变为
参数 → 环境变量 → 代码内置默认 → 本机 .dms-account(兜底)src/」user_yszs_web(登录前是 user_gtx)· 直连 DMS code=200 取到数据 ·
dev 经 /dms-api 代理读写正常user_yszs_web.dms-account(用户:「没用就删掉」):它已被代码内置的默认账号覆盖
(参数 → env → 内置 → 文件),本机那份是永远轮不到的旧账号;
.dms-token 保留 —— 它是 dev 代理每个请求现读的当前 token(自动生成物,删了会自愈但没意义)<policy-list-updated> 整条链(用户:「老对话仅测试,后续会全部清空」):
PolicyListUpdatedMatch.vue 整文件 · BusinessRecord 的挂载点/import/
hasPolicyTableRow 分支 · StreamXMLFilter 的枚举成员与标签规则 · 相关注释与文档。<policy-list-updated>{"link":…}</policy-list-updated> 会当普通文本显示。<form> / <link-url> / <file-docx> /
<ui-form> / <followup> / <silence> / <qingpu_enterprise_notificaion>),
结论写成 legacy-content-markers.md:
前三个「只有旧历史能触发」,后四个已经是死链(消费端空实现或无人注册)——
本清单没有据此改任何代码。/dms-api(要补 /dms 前缀)+
/ip-api + /aly-api(后两条打的还是同一台 121.43.55.7:10011)。现在四条:| 前缀 | 目标 | 是否改写 |
|---|---|---|
| /oauth/** | 121.43.55.7:10086(DMS 登录,实测 POST /oauth/api/user/login) | 直通(真实路径就带 /oauth) |
| /dms/** | 121.43.55.7:10081(DMS 数据 + 政策库 json),代理侧注入 token 头 | 直通(真实路径就带 /dms) |
| /api/** | 121.43.55.7:10011(语音 token + 取本机 IP 共用) | 剥掉 /api(实测直连 /api/proxy/… 是 404) |
| /chat-api/** | 聊天后端(保留) | 剥前缀 |
VITE_DMS_API /dms-api→/dms、VITE_POLICY_REMOTE_URL /dms/static/…、
VITE_IP_API→/api、VITE_NLS_TOKEN_API→/api/proxy/verify/getALYTokenvite.config.ts 里的字面量,该文件不再 loadEnv;
原先的 VITE_CHAT_TARGET / VITE_DMS_TARGET / VITE_OAUTH_TARGET / VITE_API_TARGET
四个变量已删除(.env.development 里那条"target 区"改成说明).env.production 首次配上 VITE_DMS_API="/dms"(原来生产根本没有 ⇒ 技术债 #9 的
"未配置"那半已还,只剩 nginx 转发 + 注入 token)/dms/content/selectContentList 取到真实数据 · /dms/static/…json 393 KB ·
/api/proxy/verify/getALYToken 取到 token · /api/proxy/verify/getIPByBrowser 取到真实 IP ·
/oauth/api/user/login 用默认账号拿到 JWTverify-policy-library 54/54 · verify-attachment-question 57/57 · verify-dms-payload-fields 19/19 ·
verify-question-cards 36/36 · validate-harness 通过 · npm run build 通过(hash 变,因 env 值变)/dms 真实数据 · /api token 与真实 IP · /oauth 到达业务层)全通过src/network/api/dms/service-token.ts:用内置服务账号经 POST /oauth/api/user/login
换 JWT(message 字段),缓存在内存、过期前 5 分钟刷新、并发共用同一个在途请求client.ts 随请求带 token 头;遇 201/212(token 失效)丢掉重换一次再试vite.config.ts 的 /dms/ 代理改成只在请求没带 token 时注入(命令行走代理的排查方式不变)VITE_OAUTH_LOGIN_URL(两份 env);生产需 nginx 直通 /oauth/ → 121.43.55.7:10086harness/tools/verify-dms-service-token.mjs 5/5 —— 含关键断言
「带假 token 被 DMS 拒(212)⇒ 代理没有覆盖前端带的 token」index.html 删掉 initMessageChannel()(监听父页面消息 + 回发 {type:"ready"})与调用
—— ⚠️ 保留紧随其后的遮罩收尾三行(hasSequence 等,verify-inline-scripts 守着)WeixinJSBridgeReady 监听(回调里只剩 console.log + 注释掉的视频播放)VITE_PARENT_ORIGIN;文档(architecture env 表 / call-sites #12 / 技术债 #11)同步verify-inline-scripts 8/8 · validate-harness 通过<form>/<link-url>/<file-docx> 也删):
stream-xml-filter.ts 删掉 7 条规则 + 7 个枚举成员(含两个随之失去调用方的私有方法与
"silence 透明容器"特判)⇒ 规则表只剩 4 条(scope / image-scope / ref_links / question-cards)FormContent / LinkURLContent / OfficeURLContent)及其挂载、import、
inject("formOptionsControl")、customProcessor、jsonrepair;两个壳组件里已无人 inject 的同名 provide 也删isPossibleOpenPrefix() 没检查"是否真的以规则名开头",
导致任意文本只要在第 N 个字符处是空格/> 就被当成"可能的前缀"而永久挂起
(实测 <form id="a">… 撞 image-scope、</link-url> 撞 ref_links)verify-stream-filter-rules 32/32 · verify-question-cards 36/36 · 构建通过 · dev 冒烟页面 200src/ 全部 + vite.config.ts / index.html / 两份 .env(harness/tools/*.mjs 不动 —— 头部注释就是"怎么跑")token、202 是空结果、请求体只两字段、/api 剥前缀、遮罩收尾四行删不得…);
删除:日期/"用户要求"/踩坑史/演变叙述/[已删] 记录/代价论证.ts/.vue 的注释行 2561 → 1638(−923,−36%);.env 的变量行与 HEAD 逐字一致minify 后逐字节比对 ⇒ 31 个 .ts 里 30 个完全一致
(唯一不同者是上一条里我自己的代码改动);.vue 逐行审计也全是注释verify-inline-scripts 8/8 · verify-answer-stream 165/165 · 其余 8 个校验脚本全过 · dev 冒烟页面 200BusinessAssistantMobileInputShell.vue,PC/Mobile 共用)⇒
加开关 VOICE_INPUT_VISIBLE = false + v-if;ASR 逻辑保留,改回 window.isSecureContext 即 HTTPS 自动恢复。
产物核对:麦克风 SVG 已被常量折叠,生产包 0 命中navigator.clipboard 在非安全上下文不存在,原写法
navigator.clipboard.writeText(x).catch(…execCommand 兜底…) 会同步抛错、兜底永不执行 ⇒
全站「复制」失效(主复制按钮就在 FeedbackWidget 里)。改成显式判断 + try/catch + 兜底;
新增 verify-clipboard-fallback.mjs 9/9(HTTP / HTTPS / 权限被拒 三组桩)crypto.randomUUID 有守卫+兜底 · mediaDevices/subtle/serviceWorker 无虞或不可达