Files
PlantAI-rag-new/ui/src/intent.js
T
admin 91f6a1c242 feat: 新增论文撰写功能 paper-writer(独立 MCP 服务 + BFF 大脑 + 前端三卡)
- paper_writer_service(8097):FastAPI + MCP streamable-http 单端点,9 工具
  (propose_paper_outline/write_paper_section/get_paper_progress/search_refs/
  revise_paper_section/revise_paper/get_paper/complete_paper/cancel_paper),
  SQLite 三表(papers/paper_sections/citations,WAL),GB/T 7714 引用协议
- BFF:services/paper/{mcp_client,agent}.py(裸 JSON-RPC 客户端 + 规则状态机)、
  routers/paper.py、schemas.py 4 个 Paper*Block + ArtifactBlock 加 paper_id/citation_count、
  config.py 开关、main.py 条件注册、docgen.py 抽 insert_artifact 公共 helper
- 前端:intent.js paperIntent 纯函数、Composer docgen 分支 sub-route、sendPaper/轮询、
  PaperBriefCard/PaperOutlineCard/PaperProgressCard、DocPane cite-chip
- deploy/paper_restart.sh + toggle_paper.sh;compile_sdk.sh 白名单追加 paper 文件
- tests/paper 26 条(工具逻辑 + MCP 协议 + BFF 状态机 + 块 schema + 前端 intent 单测)
2026-09-30 22:29:22 +08:00

122 lines
7.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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';
}
// —— 论文撰写意图(paper-writer):在 docgen 分支内做 sub-route ——
// 「论文/综述」主题词 + 写类动词 → 论文撰写(结构化引用体系),
// 其余 docgen(报告/通知等)仍走原 sendDocgen。纯函数,node 直测。
const PAPER_TYPE_WORDS = ['论文', '综述'];
export function paperIntent(text) {
const s = String(text == null ? '' : text).trim();
if (!s) return false;
return PAPER_TYPE_WORDS.some((w) => s.includes(w)) && hasAnyWord(s, WRITE_VERB);
}
export default route;