#27 消息级来源 title 空值串行:SourceRow 标题链空值 fallback(非空 title >paper_id>id>「(无标题)」,绝不渲染空文本或正文片段);bench store 新增 sourcesStale 失效标记,chat.js QA 流 onComplete 与检索模式完成终态置标记, SourcesPane 监听自动 force 重拉(终态一次,跨会话后台完成不打扰当前视图)。 后端 normalize title 回填由 Dev-B1 负责。 #32 KG 搜索「水稻」无结果无反馈:KgPane 成功空结果新增 kgNoResult 「未找到相关实体」提示(与 kgDown 服务离线、kgEmpty 初始空态三态文案区分、 触发条件互斥),搜索按钮空结果后保持可用,arginine 命中路径不回归。 #47b 关闭文档→返回→再关闭看不到文档:docgen store 状态机加固——open/ adoptArtifact 自含复位 viewMode='doc';close() 作废在途 content/html 拉取序号 (迟到响应不再复活已清空的 html/css/templateKey/letterhead);fetchHtml 追加 contentSeq 归属守卫。反复关闭/资料库往返/会话切换无残留。 #52a「总结论文」路由 QA:WRITE_VERB 补「总结/概括/归纳」;「总结」自身在 DOC_TYPE 中会误拉无附件普通总结句,规则 3 细化——纯总结类动词句须命中 其他文档类型词(如「论文」)才进 docgen,带附件走规则 4 兜底。后端 intent.py 词典同步由 Dev-B1 负责,请 TL 校验双端一致。 单测:tests/r3debug/r4fixes.test.js 新增 31 例(含 SourceRow fallback、 sourcesStale 链路、KgPane 三态、docgen 状态机迟到响应作废、意图路由), r3debug 全套 68/68 通过。
111 lines
6.7 KiB
JavaScript
111 lines
6.7 KiB
JavaScript
// intent.js —— 前端意图路由(P3/dev3 归属;纯函数,无 Vue 依赖,可直接 node 直测)
|
||
// 唯一事实源:docs/team/cases_intent.md(与后端 services/docgen/intent.py 同词典同规则同用例表)
|
||
// 判定顺序(优先级从高到低,命中即返回;漏判安全:不命中一律 qa):
|
||
// 1. export:hasActiveArtifact 且含 EXPORT_WORD 且含 FORMAT_WORD
|
||
// 2. edit: hasActiveArtifact 且含 EDIT_WORD
|
||
// 3. docgen:含 WRITE_VERB 且含 DOC_TYPE(排除词优先,防误判;
|
||
// 缺陷 #52a 细化:纯总结类动词句的「总结」自身不作 DOC_TYPE 证据,
|
||
// 须命中其他文档类型词如「论文」才进 docgen)
|
||
// 4. docgen:hasAttachments 且含 WRITE_VERB(附件兜底信号)
|
||
// 5. qa
|
||
|
||
// —— 导出意图词典 ——
|
||
const EXPORT_WORD = ['导出', '转成', '转为', '转换成', '下载成', '输出为', '存为', '转pdf', '转word'];
|
||
// FORMAT_WORD 英文不区分大小写;"doc" 是 "docx" 的子串,判定时逐一 contains 即可
|
||
const FORMAT_WORD = ['pdf', 'docx', 'doc', 'word'];
|
||
|
||
// —— 编辑意图词典(仅当存在活动文档时生效)——
|
||
const EDIT_WORD = ['改', '修改', '调整', '删', '删除', '加一段', '补充', '润色', '缩写', '扩写', '重写', '改写', '优化', '加个', '增加一段'];
|
||
|
||
// —— 写文档意图词典 ——
|
||
// 缺陷 #52a:补「总结/概括/归纳」——「帮我总结论文」+附件走 docgen(规则 4 附件兜底)。
|
||
// 纯总结类动词句(无其他写动词、无附件)须命中「论文」等其他文档类型词才进
|
||
// docgen(规则 3 细化,详见 WRITE_VERB_SUMMARIZE_EXCLUDE 注释)——
|
||
// 无附件「总结一下植物光合作用的过程」仍走 QA(漏判安全)。
|
||
// 后端同词典:sdk/app/services/docgen/intent.py 的 WRITE_VERB(由 Dev-B1 同步)。
|
||
const WRITE_VERB = ['写', '撰写', '拟', '起草', '编制', '生成', '编写', '拟定', '草拟', '制定', '总结', '概括', '归纳'];
|
||
const DOC_TYPE = [
|
||
'论文', '报告', '通知', '通告', '公告', '请示', '批复', '函', '申请书', '申请',
|
||
'申报书', '标书', '合同', '协议', '方案', '计划', '总结', '汇报', '讲话稿', '致辞',
|
||
'证明', '纪要', '简报', '材料', '说明书', '指南', '文档', '申报材料', '工作总结',
|
||
'调研报告', '可行性报告', '项目书', '策划书', '演讲稿', '新闻稿', '文案', '简历',
|
||
'制度', '规定', '办法', '细则', '章程', '声明', '邀请函', '感谢信', '慰问信',
|
||
'介绍信', '推荐信', '倡议书', '述职报告', '开题报告', '结题报告', '实验报告',
|
||
'分析报告', '会议纪要',
|
||
];
|
||
// 明确排除(不属于 DOC_TYPE,防误判):介绍、内容、代码、诗、作业、问题、建议、想法
|
||
const DOC_TYPE_EXCLUDE = ['介绍', '内容', '代码', '诗', '作业', '问题', '建议', '想法'];
|
||
// 缺陷 #52a:「总结/概括/归纳」入 WRITE_VERB 后,「总结」本身会命中 DOC_TYPE
|
||
// (如「总结一下植物光合作用的过程」无附件也被规则 3 拉进 docgen)。
|
||
// 防护语义(TL 裁决):句子的写动词仅由总结类新动词构成时,「总结」自身
|
||
// 不作 DOC_TYPE 证据——必须有其他文档类型词(如「总结这篇论文」的「论文」)
|
||
// 才进 docgen;带附件场景由规则 4 兜底,不受影响。
|
||
// 「写一篇总结」「写一份工作总结」等既有写类动词 + 总结类文档词路径不受影响。
|
||
const WRITE_VERB_SUMMARIZE_EXCLUDE = ['总结', '概括', '归纳'];
|
||
const WRITE_VERB_NON_SUMMARIZE = WRITE_VERB.filter((w) => !WRITE_VERB_SUMMARIZE_EXCLUDE.includes(w));
|
||
const DOC_TYPE_NON_SUMMARIZE = DOC_TYPE.filter((w) => !WRITE_VERB_SUMMARIZE_EXCLUDE.includes(w));
|
||
|
||
/** 文本是否含词典中任一词(子串判定;英文不区分大小写——中文不受影响,与后端一致) */
|
||
export function hasAnyWord(text, words) {
|
||
const s = String(text == null ? '' : text).toLowerCase();
|
||
for (const w of words) {
|
||
if (s.includes(String(w).toLowerCase())) return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/** 文本是否含任一排除词(排除词优先于 DOC_TYPE 命中——防误判) */
|
||
export function hasAnyExcludeWord(text) {
|
||
return hasAnyWord(text, DOC_TYPE_EXCLUDE);
|
||
}
|
||
|
||
/** 缺陷 #52a:文本含总结类新动词 → 规则 3 需额外校验 */
|
||
export function hasAnySummarizeVerb(text) {
|
||
return hasAnyWord(text, WRITE_VERB_SUMMARIZE_EXCLUDE);
|
||
}
|
||
|
||
/** 文本含除总结类新动词外的其他写类动词(写/撰写/生成/制定…) */
|
||
export function hasNonSummarizeWriteVerb(text) {
|
||
return hasAnyWord(text, WRITE_VERB_NON_SUMMARIZE);
|
||
}
|
||
|
||
/** 文本命中非总结类的文档类型词(「总结」自身不作证据) */
|
||
export function hasNonSummarizeDocType(text) {
|
||
return hasAnyWord(text, DOC_TYPE_NON_SUMMARIZE);
|
||
}
|
||
|
||
/** 导出文档类型词词典(任务书约定的导出类型词;判定走 EXPORT_WORD+FORMAT_WORD,此处导出供组件复用) */
|
||
export const EXPORT_DOC_TYPE_WORDS = [
|
||
'论文', '报告', '通知', '通告', '公告', '请示', '批复', '函', '申请书', '申报书',
|
||
'标书', '合同', '协议', '方案', '计划', '总结', '汇报', '讲话稿', '致辞',
|
||
'证明', '纪要', '简报', '材料', '说明书', '指南',
|
||
];
|
||
|
||
/**
|
||
* 意图路由(唯一事实源 docs/team/cases_intent.md 的 route 签名)
|
||
* @param {string} text 用户输入文本
|
||
* @param {boolean} hasAttachments 本轮是否带附件(资料 chips)
|
||
* @param {boolean} hasActiveArtifact 是否存在活动文档(文档 TAB 当前打开的 artifact)
|
||
* @returns {'qa'|'docgen'|'edit'|'export'}
|
||
*/
|
||
export function route(text, hasAttachments, hasActiveArtifact) {
|
||
const s = String(text == null ? '' : text);
|
||
// 1. 导出意图(需活动文档)
|
||
if (hasActiveArtifact && hasAnyWord(s, EXPORT_WORD) && hasAnyWord(s, FORMAT_WORD)) return 'export';
|
||
// 2. 编辑意图(需活动文档)
|
||
if (hasActiveArtifact && hasAnyWord(s, EDIT_WORD)) return 'edit';
|
||
// 3. 写文档意图:写类动词 + 文档类型词(排除词防误判优先)
|
||
// 缺陷 #52a:纯总结类动词句(无其他写动词)须命中非总结类文档类型词
|
||
// (如「论文」)才进 docgen——「总结一下植物光合作用的过程」仍走 QA
|
||
if (hasAnyWord(s, WRITE_VERB) && !hasAnyExcludeWord(s) && hasAnyWord(s, DOC_TYPE)) {
|
||
const summarizeOnly = hasAnySummarizeVerb(s) && !hasNonSummarizeWriteVerb(s);
|
||
if (!summarizeOnly || hasNonSummarizeDocType(s)) return 'docgen';
|
||
}
|
||
// 4. 附件兜底:拖入文件 + 写类动词,即使无文档类型词也视为生成意图
|
||
if (hasAttachments && hasAnyWord(s, WRITE_VERB)) return 'docgen';
|
||
// 5. 漏判安全:一律 qa
|
||
return 'qa';
|
||
}
|
||
|
||
export default route;
|