|
|
13 hours ago | |
|---|---|---|
| build | 4 days ago | |
| harness | 13 hours ago | |
| public | 4 days ago | |
| src | 13 hours ago | |
| .editorconfig | 4 days ago | |
| .env.development | 2 days ago | |
| .env.production | 2 days ago | |
| .env.qingpu | 2 days ago | |
| .env.test | 2 days ago | |
| .eslintignore | 4 days ago | |
| .eslintrc.js | 4 days ago | |
| .gitignore | 3 days ago | |
| .prettierrc.cjs | 4 days ago | |
| CLAUDE.md | 2 days ago | |
| README.md | 14 hours ago | |
| agents.md | 3 days ago | |
| index.html | 3 days ago | |
| jest.config.js | 4 days ago | |
| package-lock.json | 4 days ago | |
| package.json | 4 days ago | |
| redirect.html | 4 days ago | |
| tsconfig.json | 4 days ago | |
| tsconfig.node.json | 4 days ago | |
| vite.config.ts | 2 days ago |
本文件当前只记录按日期的改动。项目概述、技术栈、目录结构、接口说明等内容, 等项目结束时再统一总结。
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)