一、问题背景
1.1 遇到了什么痛点?
在日常使用 DeepSeek、ChatGPT、Claude 等 AI 工具撰写报告时,我反复遇到同一个问题:
- AI 输出的 Markdown 格式(表格、代码块、数学公式)在网页上排版完美
- 但复制到 Word 后格式全部丢失------表格变成纯文本、代码块失去高亮、LaTeX 公式变成乱码
- AI 的思考过程 和搜索引用编号 (如
-14-15、[5](url))混入正文,需要手动删除 - 切换不同 AI 平台时,每个平台的操作方式不同,无法统一处理
核心矛盾:AI 大幅提升了内容生成效率,但"从 AI 输出到可用文档"这最后一公里,全靠手动。
1.2 现有方案为什么不够?
| 方案 | 问题 |
|---|---|
| 直接复制粘贴 | 格式全丢,引用编号混入 |
| AI 平台自带的导出 | 仅少数平台有,且功能简陋 |
| 第三方 Markdown 编辑器 | 仍需手动搬运,无法自动清洗 |
| 截图转 Word | 无法编辑,图片模糊 |
1.3 本文要解决什么
构建一个浏览器扩展(Manifest V3) ,自动在 AI 对话页面注入捕获按钮,点击后将 AI 回复清洗、转换为标准 Markdown 并渲染预览,最终导出为 Word 文档。全过程一句话:点按钮 → 编辑 → 导出。
二、方案设计
2.1 整体架构
text
┌─────────────────────────────────────────────────────────┐
│ NQ-Assistant 架构 │
│ │
│ AI 网页(DeepSeek/ChatGPT/Claude/Kimi/豆包) │
│ │ │
│ ▼ content.js(注入按钮 + DOM 提取) │
│ │ │
│ ▼ background.js(消息中转 + chrome.storage 持久化) │
│ │ │
│ ▼ sidepanel.js(markdown-it 渲染 + 编辑 + 导出) │
│ │ │
│ ▼ Word 文档(HTML-DOC 格式,WPS/Word 原生兼容) │
└─────────────────────────────────────────────────────────┘
2.2 为什么选择这个架构?
| 决策点 | 选择 | 原因 |
|---|---|---|
| 扩展类型 | Manifest V3 Side Panel | 侧边栏不遮挡网页,可边对话边预览 |
| Markdown 渲染 | markdown-it(而非 marked/showdown) | 浏览器兼容性最好,表格/代码块支持完整 |
| Word 导出 | HTML-DOC(而非 docx.js) | css+html 嵌入,Word/WPS 原生打开,无需额外库 |
| 存储 | chrome.storage.local | Manifest V3 不支持 localStorage,且 storage 支持跨设备同步 |
| 依赖策略 | 全部本地打包 | 零网络请求,避免 CDN 被墙(国内用户友好) |
2.3 关键风险与应对
⚠️ 风险 1 :各 AI 平台的 DOM 结构不同,且随时可能更新
应对 :使用平台检测 + 可配置选择器,每个平台独立配置
message和markdown的选择器
⚠️ 风险 2 :element.innerHTML直接展示在侧栏会有 css 冲突应对:预览走 Markdown→HTML 渲染(保证样式一致性),导出保留原始 HTML(保证格式 100% 保真)
三、核心实现
3.1 多平台 DOM 提取
每个 AI 平台的网页结构不同。通过平台检测 + 选择器配置实现统一接口:
javascript
// content.js --- 平台自动检测与选择器配置
const PLATFORMS = {
deepseek: {
host: 'chat.deepseek.com',
message: '.ds-message', // 消息容器
markdown: '.ds-markdown', // Markdown 渲染容器
think: '.ds-think-content, [class*="think"]'
},
chatgpt: {
host: 'chatgpt.com',
message: '[data-message-author-role="assistant"]',
markdown: '.markdown, [class*="prose"]',
think: '[class*="think"], [class*="reasoning"]'
},
claude: { /* ... */ },
kimi: { /* ... */ },
doubao: { /* ... */ }
};
// 运行时自动检测
function detectPlatform() {
const host = window.location.hostname;
for (const [key, cfg] of Object.entries(PLATFORMS)) {
if (host.includes(cfg.host.replace('https://', '')))
return { key, ...cfg };
}
}
3.2 HTML 清洗管线
从 DOM 到可用的 Markdown 需要经过多层清洗。这是整个工具的核心逻辑:
javascript
// content.js --- 内容提取管线
function extractContent(el) {
const clone = el.cloneNode(true);
// 第1层:移除思考过程 DOM 节点
removeThinking(clone);
// → 去掉 .ds-think-content 等思考区块
// 第2层:移除 UI 元素(按钮、图标、工具栏)
removeUIElements(clone);
// → 去掉复制、下载按钮和代码块工具栏
// 第3层:移除引用链接(上标数字、citation 类元素)
removeCitationLinks(clone);
// → 去掉 <sup>1</sup>、[-5](url) 等引用
// 第4层:保存清洗后的 HTML(导出用,100% 保真)
const html = clone.innerHTML.trim();
// 第5层:HTML → Markdown(预览和编辑用)
const md = elementToMarkdown(clone);
// → <h3> → ###, <table> → |---|, <code> → ``````等
return { html, md };
}
为什么分两层(html + md)?
html:保留 AI 网页的原始渲染效果,导出时直接嵌入 Word,排版 100% 还原md:转换为 Markdown 后预览和编辑,格式统一且易于修改
3.3 HTML → Markdown 转换
这是最复杂的一环。AI 网页将 Markdown 渲染为 HTML,我们需要逆向还原:
javascript
// content.js --- DOM 元素到 Markdown 语法的转换
function elementToMarkdown(el) {
const children = Array.from(el.children);
if (children.length === 0) return getText(el);
let md = '';
for (const child of children) {
const tag = child.tagName.toLowerCase();
const text = getText(child);
switch (tag) {
case 'h1': md += '\n# ' + text + '\n'; break;
case 'h2': md += '\n## ' + text + '\n'; break;
case 'h3': md += '\n### '+ text + '\n'; break;
case 'p': md += '\n' + processInline(child) + '\n'; break;
case 'pre': md += processCode(child); break;
case 'ul': md += processList(child, false); break;
case 'ol': md += processList(child, true); break;
case 'table': md += processTable(child); break;
case 'blockquote': md += '\n> ' + text.replace(/\n/g, '\n> ') + '\n'; break;
// ... 更多标签处理
}
}
return md.replace(/\n{3,}/g, '\n\n').trim();
}
3.4 引用编号清理
DeepSeek 搜索模式会在文中插入引用编号,格式多变:
text
原始: "追溯到1912年-14-15,被评为国家级一流本科专业-15-29。"
清洗: "追溯到1912年,被评为国家级一流本科专业。"
需要处理 -14-15、-29、[-5]、[-5](url) 等多种格式,同时保护真正的年份范围(1912-1920):
javascript
function stripCitations(md) {
// 1. 移除 "-数字-数字"(如 -14-15)
md = md.replace(/[\------]\d{1,3}[\------]\d{1,3}/g, '');
// 2. 移除 "-数字"(保护年份 1912-1920)
md = md.replace(/([\------])\s*(\d{1,3})(?=[,。;\n]|$)/g,
(match, dash, num) => {
const ctx = /* 检查上下文是否为年份 */;
if (isYearRange(ctx)) return match; // 保留年份
return ''; // 移除引用
}
);
// 3. 移除 "[-5](url)" 和 "[5]" 格式
md = md.replace(/\[-?\d{1,3}([,\-]\d{1,3})*\]\([^)]*\)/g, '');
md = md.replace(/\s*\[-?\d{1,3}([,\-]\d{1,3})*\]/g, '');
return md;
}
3.5 数学公式处理
AI 网页使用 KaTeX 将 LaTeX 渲染为 HTML。提取时需要还原:
javascript
// content.js --- KaTeX HTML → $...$ / $$...$$
function convertKatexToLatex(root) {
root.querySelectorAll('.katex').forEach(katexEl => {
// 从 <annotation encoding="application/x-tex"> 提取原始 LaTeX
const annotation = katexEl.querySelector('annotation[encoding="application/x-tex"]');
if (annotation) {
const latex = annotation.textContent.trim();
const isDisplay = katexEl.closest('.katex-display') !== null;
const wrapper = isDisplay ? '\n$$\n' + latex + '\n$$\n' : '$' + latex + '$';
// 替换 KaTeX 元素为文本节点
katexEl.parentNode.replaceChild(document.createTextNode(wrapper), katexEl);
}
});
}
在侧栏渲染时,需要区分"真正的数学公式"和"解释性文字中的示例":
javascript
// sidepanel.js --- 智能数学公式检测
let p = md.replace(/\$\$([\s\S]+?)\$\$/g, (_, m) =>
/[\\\^_{}]/.test(m) // 含有 LaTeX 符号?
? '<div class="math-block">' + esc(m) + '</div>' // → 公式
: '<code>$$' + esc(m) + '$$</code>' // → 代码示例
);
3.6 导出管线
导出使用 msg.html(清洗后的原始 HTML),确保 Word 中排版与网页一致:
javascript
// sidepanel.js --- 导出
function getExportHTML(msgs, tpl) {
const bodyHtml = msgs.map((m, i) => {
const rendered = m.html || renderMarkdown(m.content); // html 优先
return rendered + (i < msgs.length - 1 ? '<div class="page-break"></div>' : '');
}).join('\n');
return `<!DOCTYPE html><html lang="zh-CN">
<head><meta charset="UTF-8"><title>NQ-Assistant</title>
<style>${getExportCSS(tpl)}</style></head>
<body>${bodyHtml}</body></html>`;
}
四、验证效果
4.1 测试环境
| 项目 | 配置 |
|---|---|
| 浏览器 | Edge 126 / Chrome 126 |
| 操作系统 | Windows 11 |
| 测试平台 | DeepSeek R1、ChatGPT 4o、Claude 3.5 Sonnet、Kimi、豆包 |
| 测试内容 | 含表格/代码块/LaTeX公式/多级标题的 AI 回复 |
4.2 测试结果
| 测试项 | DeepSeek | ChatGPT | Claude | Kimi | 豆包 |
|---|---|---|---|---|---|
| 按钮注入 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 表格保留 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 代码块保留 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 数学公式 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 思考过程过滤 | ✅ | N/A | N/A | ✅ | ✅ |
| 引用编号清理 | ✅ | ✅ | ✅ | ✅ | ✅ |
| Word 导出 | ✅ | ✅ | ✅ | ✅ | ✅ |
4.3 边界与局限
- DOM 结构变化风险 :各平台可能随时更新前端代码,导致选择器失效。可通过更新
PLATFORMS配置修复 - 复杂表格:跨行/跨列的合并单元格可能不完全保留
- 图片:AI 生成的图片引用可能因跨域无法导出
4.4 项目展示





五、安装与使用
5.1 获取代码
bash
git clone https://github.com/NQLOVELSJ/NQ_Assitant.git
5.2 加载到浏览器
- 打开
edge://extensions(或chrome://extensions) - 开启「开发人员模式」
- 点击「加载解压缩的扩展」→ 选择项目文件夹
- 工具栏出现图标 ✅
5.3 使用
- 打开任意支持的 AI 平台,正常对话
- AI 回复下方出现 📋 预览 按钮
- 点击 → 侧边栏捕获
- 编辑、排序、选模板 → 📄 导出所选 Word
- 快捷键
Ctrl+E快速导出
六、总结
本文从实际痛点出发,完整介绍了如何构建一个跨平台 AI 回复导出工具。核心思路是:DOM 清洗 → 多层过滤 → 双轨输出(Markdown 预览 + HTML 导出)。
这个方案的适用边界:
- ✅ 适用于需要从 AI 对话中提取格式化内容的场景
- ✅ 核心清洗逻辑可复用到其他浏览器扩展
- ⚠️ 依赖各平台 DOM 结构的稳定性,需定期维护选择器
📦 GitHub:https://github.com/NQLOVELSJ/NQ_Assitant
📄 License:MIT
本文所有代码均为 NQ-Assistant 项目实际代码,可直接运行验证。