1个月双线作战:AI公文助手从0到1开发,与国产化适配的"踩坑"全记录
全职创业2年零1个月,接下来和大家分享一下我们最近做了AI协同产品 JitWord 的研发历程。
为什么要在1个月内攻坚两个硬骨头?
说实话,启动这次迭代前,团队内部是有分歧的。
一方面,AI公文助手 是很多政企客户反复提的需求——他们想要 Word 那种严谨的公文排版,又想要AI的生成能力,还要能在线协同;另一方面,国产化适配是信创大背景下的必选项,涉及国产操作系统、国产浏览器、甚至国产芯片的兼容性问题。
这两个需求,任何一个单独做都要扒层皮。但市场不等人,我们决定在1个月内"双线作战"。
这篇文章记录了我们如何从0搭建AI公文助手模块,以及在国产化适配过程中遇到的那些让人头秃的坑。希望能给同样面临信创改造或富文本技术选型的同学一些参考。
JitWord 是什么?(如果你第一次听说)
简单给新朋友介绍一下。JitWord 是我们团队开发的协同AI文档引擎,定位是"让Web文档拥有桌面级体验",打造“云端Office”办公体验。
核心能力包括:
- 多人实时协同:基于CRDT算法,支持Word级别的冲突解决
- AI辅助创作:内置AI续写、润色、总结,支持自定义Prompt
- 数学公式渲染:自研公式引擎,支持LaTeX到Word的无损转换(之前文章有详细讲过)
-
一键导出Word:不只是PDF,是真正的
.docx格式,导出后还能在Office里二次编辑
| 项目 | 描述 |
|---|---|
| 产品名称 | JitWord 协同AI文档 |
| 技术栈 | Vue3 + NestJS + CRDT + WebSocket |
| 核心功能 | 实时协同、AI写作、公文处理、Word导出 |
| 适用场景 | 企业文档中台、科研协作、政务办公 |
| 版本状态 | V2.1(AI公文助手 + 国产化适配版) |
最近我们也开源了一版sdk,大家可以轻松本地使用和集成:
github地址:github.com/MrXujiang/j…
第一部分:AI公文助手从0到1
1.1 需求拆解:公文场景的残酷现实
做传统富文本编辑器的朋友可能不知道,公文排版是中文排版的地狱模式。
- 红头文件:要严格遵循 GB/T 9704-2012 国家标准,版头、发文字号、签发人都有固定位置
- 多层嵌套结构:一、(一)、1.(1)、①,这五种层级格式不能乱
- 表格与附件:公文里的表格必须能跨页重复表头,附件说明有特定格式
- 严格的页面设置:A4纸张、上白边37mm±1mm、下白边35mm±1mm...
我们调研了市面上几乎所有的Web Office方案,发现要么是简单的表单模板(灵活性不够),要么是把PDF转图片(无法二次编辑)。所以决定自己实现一套结构化公文引擎。
1.2 技术架构:如何把AI塞进公文流程?
我们采用了 模板引擎 + AI生成 + 人工调整 的三段式架构:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 公文模板库 │────▶│ AI解析层 │────▶│ 编辑渲染层 │
│ (.docx解析) │ │ (LLM+规则) │ │ (结构化编辑)│
└─────────────┘ └─────────────┘ └─────────────┘
│ │
│ ┌─────────────┐ │
│◄───│ 导出引擎 │◄────────────────────┘
│ │(Word/PDF) │
│ └─────────────┘
关键技术决策:
- 模板解析:目前了复用 mammoth.js,但是它的样式映射太粗粒度,后面规划重写。
- AI提示词工程:公文写作不是 Creative Writing,而是 Constraint Writing。我们让AI先分析模板结构,再填充内容,最后做格式合规性检查(比如检查发文字号是否符合"国发〔2024〕1号"这种格式)。
- 编辑器选型:基础还是ProseMirror,但重写了NodeSpec来支持"公文块"(OfficialBlock)概念。每个公文块是一个不可随意拆分的逻辑单元,比如"主送机关"是一个块,"正文"是一个块。
1.3 核心代码:公文模板的JSON Schema设计
这是我们定义的公文结构规范(节选):
// types/document.ts
export interface OfficialDocument {
version: 'GB/T-9704-2012';
header: {
issuingBody: string; // 发文机关
documentNumber: string; // 发文字号
urgencyLevel: '特急' | '加急' | '平急';
};
body: OfficialBlock[]; // 正文,由多个公文块组成
attachments?: Attachment[];
}
export interface OfficialBlock {
type: 'redHeader' | 'recipient' | 'text' | 'level1' | 'level2' | 'table';
content: string | TableContent;
style: {
fontFamily: '仿宋_GB2312' | '黑体' | '楷体_GB2312'; // 信创字体
fontSize: number; // 三号=16pt,小三=15pt...
lineHeight: number; // 28-30磅固定值
};
}
遇到的坑: 仿宋_GB2312 这个字体在Mac和Linux上表现差异巨大。Windows上看着好好的文档,在国产系统(基于Linux)上打开后行高会乱掉。解决方案是用CSS的line-height: fixed + 字体fallback栈,并且在导出Word时重新计算行高。
1.4 AI生成流程的优化
最初我们直接把"写一篇关于XX的通知"扔给GPT-5,结果生成的内容总是太口语化,而且格式经常出错。
优化后的流程是:
- 模板匹配:先根据用户选择的公文类型(通知、通报、请示、报告等),加载对应的Prompt模板
- 结构化生成:要求AI输出JSON格式,而不是Markdown
- 规则校验层:用正则表达式校验公文要素是否齐全(比如通知必须有"特此通知"结尾,请示必须有"妥否,请批复")
// ai/officialWriter.ts
async function generateOfficialDoc(params: GenerationParams) {
const template = loadTemplate(params.type);
const structuredPrompt = `
你是一个严谨的公文写作助手。请根据以下信息,生成符合GB/T 9704-2012的公文内容。
必须输出为JSON格式,字段定义如下:${JSON_SCHEMA}
用户输入:${params.topic}
要求:${params.requirements}
`;
const raw = await llm.generate(structuredPrompt);
const doc = JSON.parse(raw);
// 规则校验
if (!validateOfficialFormat(doc)) {
throw new Error('生成内容不符合公文规范,请重试');
}
return doc;
}
效果: 生成一份标准通知的时间从人工30分钟缩短到AI 10秒 + 人工审核2分钟,效率提升90%。
第二部分:国产化适配的"踩坑"全记录
如果说AI公文助手是"从0到1的创造",那国产化适配就是"从能用到好用的磨砺"。
我们的目标是让 JitWord 能在统信UOS、麒麟OS等国产操作系统,以及360安全浏览器、奇安信可信浏览器等国产Chromium内核浏览器上稳定运行。
2.1 踩坑一:WebSocket连接的诡异断开
现象: 在麒麟V10系统上,协同编辑总是过几分钟就断开,提示"网络异常",但用户明明能正常刷网页。
排查过程:
- 首先排查Nginx配置,以为是
proxy_read_timeout太短,改成3600秒,无效。 - 检查浏览器Network面板,发现国产浏览器的某些安全策略会主动断开静默的WebSocket连接。
- 最后发现是奇安信可信浏览器内置了"长连接保护"策略,超过5分钟没有数据交互就会自动断开。
解决方案:
// 心跳机制加强版
export class ReliableWebSocket {
private ws: WebSocket;
private heartbeatInterval: NodeJS.Timer;
// 国产浏览器的心跳间隔要更短
private heartbeatDelay = isDomesticBrowser() ? 10000 : 30000;
connect() {
this.ws = new WebSocket(url);
this.heartbeatInterval = setInterval(() => {
// 发送空操作或ping帧,保持连接活性
this.send({ type: 'heartbeat', timestamp: Date.now() });
}, this.heartbeatDelay);
}
}
2.2 踩坑二:富文本编辑器的输入法冲突
这是让我最想骂街的坑。
现象: 在统信UOS + 搜狗输入法(国产版)下,输入中文时,编辑器光标会乱跳,甚至吞字。
根因分析:
国产操作系统的输入法架构和Windows差异很大。我们用的ProseMirror在处理beforeinput事件时,和一些国产输入法的Composition事件冲突。具体表现为:输入法开始合成(compositionstart)时,ProseMirror尝试更新选区,导致输入法丢失了上下文。
解决方案: 不得不patch了ProseMirror的view模块,在合成输入期间暂停所有远程协同更新:
// patches/prosemirror-view.ts
let isComposing = false;
editorView.dom.addEventListener('compositionstart', () => {
isComposing = true;
// 暂停接收远程操作,避免光标跳动
collaboration.pauseSync();
});
editorView.dom.addEventListener('compositionend', (e) => {
isComposing = false;
const finalData = e.data;
// 延迟恢复同步,等待输入法插入完成
setTimeout(() => {
collaboration.resumeSync();
}, 100);
});
2.3 踩坑三:字体渲染与导出
现象: 同样的"仿宋",在Windows上叫"仿宋",在国产系统上可能叫"FangSong"、"Fangsong"、或者"Source Han Serif CN"。公文要求必须用仿宋_GB2312,但这个字体在某些国产系统上没有预装。
解决方案三部曲:
-
前端降级方案:CSS设置
font-family: 'FangSong_GB2312', 'Source Han Serif CN', 'Noto Serif CJK SC', serif; - 后端字体嵌入:导出Word时,如果检测目标系统缺少字体,用Java操作POI把字体文件嵌入到生成的docx中
- Web字体预加载:在编辑器初始化时,异步加载WOFF2格式的仿宋字体文件,确保所见即所得
// 导出Word时的字体嵌入逻辑(Java实现)
public void embedFonts(XWPFDocument doc, String[] requiredFonts) {
for (String fontName : requiredFonts) {
if (!systemHasFont(fontName)) {
InputStream fontStream = getClass().getResourceAsStream("/fonts/" + fontName + ".ttf");
doc.embedFont(fontName, fontStream);
}
}
}
性能优化:让国产硬件也能流畅运行
说实话,很多国产终端的硬件配置(特别是信创笔记本)不如主流Windows本。我们在1个月内做了以下针对性优化:
虚拟滚动 + 分层渲染
公文通常很长(几十页很正常),我们在ProseMirror基础上实现了虚拟滚动,只渲染可视区域内的DOM节点。同时把静态内容(已经定稿的段落)标记为contenteditable: false,减少MutationObserver的开销。
AI生成的防抖处理
当AI生成大段文本时,不能直接一次性插入编辑器(会导致卡顿)。我们改成了逐句插入 + requestAnimationFrame:
async function insertAIGeneratedContent(content: string) {
const sentences = content.split(/([。!?])/); // 按句分割
for (let i = 0; i < sentences.length; i += 2) {
const sentence = sentences[i] + (sentences[i+1] || '');
await new Promise(resolve => {
requestAnimationFrame(() => {
editor.insertText(sentence);
resolve(null);
});
});
// 每5句暂停一下,让UI线程喘息
if (i % 5 === 0) await sleep(10);
}
}
最终效果与场景展示
公文助手实际应用场景
场景1:政府机关的请示报告
- 输入:"关于申请信息化建设经费的请示"
- AI生成:自动匹配"请示"模板,生成红头、发文字号、正文、结尾语
- 人工调整:只需填写具体金额和项目明细
- 导出:直接生成符合省级办公厅格式要求的Word文件
场景2:国企的发文通知
- 协同:办公室主任起草,分管领导在线批注修改,法务审核合规性
- 留痕:所有修改记录保存,满足公文归档的审计要求
- 套红:一键生成带红色抬头的正式公文版式
国产化适配验证环境
我们在以下环境完成了完整测试:
- 操作系统:统信UOS 1060、麒麟V10 SP1、中科方德
- CPU架构:x86_64、ARM64(鲲鹏920、飞腾2000)
- 浏览器:360安全浏览器v13、奇安信可信浏览器、火狐中国浏览器
技术总结与反思
这1个月的"双线作战",最大的收获不是功能本身,而是对信创环境下的Web开发有了更深理解:
-
不要相信浏览器的UserAgent:国产浏览器都伪装成Chrome,但行为可能完全不同。必须做特性检测(feature detection)而非浏览器嗅探。
-
富文本编辑器要"防御性编程":输入法、选区、滚动这些在标准浏览器上稳定的功能,在特殊环境下可能有各种奇奇怪怪的表现。代码要更保守,try-catch要更密集。
-
AI生成必须后接规则校验:大模型有幻觉,公文又是极其严谨的体裁。AI负责"快",规则引擎负责"准",两者结合才能实用。
-
字体和排版是信创隐形大坑:中西文混排、行高计算、字体回退,这些细节决定了产品看起来是"业余demo"还是"正式产品"。
如何集成和体验?
JitWord 目前主要面向企业级用户和开发者集成。
- 在线演示:如果你想看看AI公文助手的实际效果,可以访问我们的演示环境(文中不放链接了,掘金私信我或评论获取)
- 私有化部署:支持国产服务器私有化部署,适配信创环境
- SDK集成:提供JavaScript SDK,可以Embed到你的业务系统中
如果你也是正在做信创改造的技术负责人,或者需要公文处理能力的产品经理,欢迎评论区交流踩坑经验。国产化这条路,大家互相搀扶才能走得快一点。
我们也开源了一版sdk,大家可以轻松本地使用和集成:
github地址:github.com/MrXujiang/j…
未来规划
这1个月的攻坚只是开始,接下来的 roadmap 包括:
- 智能校对:接入NLP模型,自动检查公文中的政治术语准确性、数字逻辑一致性(比如"2024年"不能写成"2024年度"在某些语境下)
- 手写签批:对接国产手写板和签章系统,实现移动端批公文
- 更多公文类型:从现在的通知、请示、报告,扩展到会议纪要、函、议案等15种法定公文
技术栈彩蛋 🎯
如果你在关注相关技术方向,这是我们用的核心栈,也是目前市面上比较热门的技术方向:
- Vue3 + Vite + TypeScript(前端)
- NestJS + TypeORM(后端,支持国产数据库适配)🚀
- ProseMirror(编辑器内核,深度定制)
- Yjs(CRDT协同算法)🧩
- Docker + K8s(部署)
觉得有用的话,点个赞或者收藏吧。信创适配这条路很长,希望这篇文章能帮你少走些弯路。有任何技术问题,评论区留言,我看到都会回复。