跳绳鸭
67.76M · 2026-02-04
全职创业2年零1个月,接下来和大家分享一下我们最近做了AI协同产品 JitWord 的研发历程。
说实话,启动这次迭代前,团队内部是有分歧的。
一方面,AI公文助手 是很多政企客户反复提的需求——他们想要 Word 那种严谨的公文排版,又想要AI的生成能力,还要能在线协同;另一方面,国产化适配是信创大背景下的必选项,涉及国产操作系统、国产浏览器、甚至国产芯片的兼容性问题。
这两个需求,任何一个单独做都要扒层皮。但市场不等人,我们决定在1个月内"双线作战"。
这篇文章记录了我们如何从0搭建AI公文助手模块,以及在国产化适配过程中遇到的那些让人头秃的坑。希望能给同样面临信创改造或富文本技术选型的同学一些参考。
简单给新朋友介绍一下。JitWord 是我们团队开发的协同AI文档引擎,定位是"让Web文档拥有桌面级体验",打造“云端Office”办公体验。
核心能力包括:
.docx格式,导出后还能在Office里二次编辑| 项目 | 描述 |
|---|---|
| 产品名称 | JitWord 协同AI文档 |
| 技术栈 | Vue3 + NestJS + CRDT + WebSocket |
| 核心功能 | 实时协同、AI写作、公文处理、Word导出 |
| 适用场景 | 企业文档中台、科研协作、政务办公 |
| 版本状态 | V2.1(AI公文助手 + 国产化适配版) |
最近我们也开源了一版sdk,大家可以轻松本地使用和集成:
github地址:github.com/MrXujiang/j…
做传统富文本编辑器的朋友可能不知道,公文排版是中文排版的地狱模式。
我们调研了市面上几乎所有的Web Office方案,发现要么是简单的表单模板(灵活性不够),要么是把PDF转图片(无法二次编辑)。所以决定自己实现一套结构化公文引擎。
我们采用了 模板引擎 + AI生成 + 人工调整 的三段式架构:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 公文模板库 │────▶│ AI解析层 │────▶│ 编辑渲染层 │
│ (.docx解析) │ │ (LLM+规则) │ │ (结构化编辑)│
└─────────────┘ └─────────────┘ └─────────────┘
│ │
│ ┌─────────────┐ │
│◄───│ 导出引擎 │◄────────────────────┘
│ │(Word/PDF) │
│ └─────────────┘
关键技术决策:
这是我们定义的公文结构规范(节选):
// 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时重新计算行高。
最初我们直接把"写一篇关于XX的通知"扔给GPT-5,结果生成的内容总是太口语化,而且格式经常出错。
优化后的流程是:
// 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内核浏览器上稳定运行。
现象: 在麒麟V10系统上,协同编辑总是过几分钟就断开,提示"网络异常",但用户明明能正常刷网页。
排查过程:
proxy_read_timeout太短,改成3600秒,无效。解决方案:
// 心跳机制加强版
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);
}
}
这是让我最想骂街的坑。
现象: 在统信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);
});
现象: 同样的"仿宋",在Windows上叫"仿宋",在国产系统上可能叫"FangSong"、"Fangsong"、或者"Source Han Serif CN"。公文要求必须用仿宋_GB2312,但这个字体在某些国产系统上没有预装。
解决方案三部曲:
font-family: 'FangSong_GB2312', 'Source Han Serif CN', 'Noto Serif CJK SC', serif;// 导出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生成大段文本时,不能直接一次性插入编辑器(会导致卡顿)。我们改成了逐句插入 + 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:政府机关的请示报告
场景2:国企的发文通知
我们在以下环境完成了完整测试:
这1个月的"双线作战",最大的收获不是功能本身,而是对信创环境下的Web开发有了更深理解:
不要相信浏览器的UserAgent:国产浏览器都伪装成Chrome,但行为可能完全不同。必须做特性检测(feature detection)而非浏览器嗅探。
富文本编辑器要"防御性编程":输入法、选区、滚动这些在标准浏览器上稳定的功能,在特殊环境下可能有各种奇奇怪怪的表现。代码要更保守,try-catch要更密集。
AI生成必须后接规则校验:大模型有幻觉,公文又是极其严谨的体裁。AI负责"快",规则引擎负责"准",两者结合才能实用。
字体和排版是信创隐形大坑:中西文混排、行高计算、字体回退,这些细节决定了产品看起来是"业余demo"还是"正式产品"。
JitWord 目前主要面向企业级用户和开发者集成。
如果你也是正在做信创改造的技术负责人,或者需要公文处理能力的产品经理,欢迎评论区交流踩坑经验。国产化这条路,大家互相搀扶才能走得快一点。
我们也开源了一版sdk,大家可以轻松本地使用和集成:
github地址:github.com/MrXujiang/j…
这1个月的攻坚只是开始,接下来的 roadmap 包括:
技术栈彩蛋
如果你在关注相关技术方向,这是我们用的核心栈,也是目前市面上比较热门的技术方向:
觉得有用的话,点个赞或者收藏吧。信创适配这条路很长,希望这篇文章能帮你少走些弯路。有任何技术问题,评论区留言,我看到都会回复。