资讯动态

WPS JSAPI插件开发实录:用DeepSeek API实现文档AI摘要与回填

发布时间:2026/9/18 7:49:58 来源:尧图企业网站定制
简介面向想借助DeepSeek API提高办公自动化水平的开发者和进阶用户这份PDF文档以真实集成案例为主线讲清了在WPS中构建智能办公插件的完整路径。资源只有1个PDF文件大小2.16MB共33页目录分章涵盖背景与目标、开发环境搭建、需求调研与功能定义、总体架构设计、DeepSeek API接入、文本生成/翻译/格式调整/信息检索等核心模块开发、与WPS菜单及文档内容的深度交互、测试规划与执行、部署发布及后续维护等环节内容组织清晰且覆盖量产级项目全生命周期。当前已有106人学习。读者既能获得插件开发的整体架构思路和分模块实现方法也能学到请求参数构造、响应结果解析、错误处理与重试机制、兼容性与安全性测试等实战落地技巧很适合希望在办公场景中快速落地AI能力的开发人员作为从入门到进阶的参考手册。1. WPS深度集成不是“写个宏”是把WPS当成AI的外设打开WPS后把选中文字交给DeepSeekAPI几秒后生成摘要、翻译、周报甚至续写方案再自动填回文档——这类“深度集成”不是往功能区塞一个按钮那么简单而是要让WPS文档对象模型和云端模型之间的数据流转形成闭环。标题里“完整开发实录”的实际含义是在WPS的加载项JSAPI插件体系里用JavaScript调用DeepSeekAPI再把返回结果以Range/Selection的方式写回文档。整套链路的核心是“文档上下文提取→HTTP请求构造→流式响应处理→选区回填”每一步都藏在WPS封装好的接口后面不踩一遍很难摸清。这个方案最大的价值是绕开VBA的两道坎一是WPS个人版默认装不了VBA模块即便装了vba7.1Word/Excel几个对象模型的调用限制也很多二是VBA做网络请求MSXML2.XMLHTTP在WPS里兼容性很差而JSAPI插件跑在类似浏览器的运行时里标准fetch、Promise、async/await都能用DeepSeekAPI的OpenAI兼容接口几乎不用封装就能直接调。适合的人群是写过前端、懂一点REST API的后台开发或IT运维不需要精通Office对象模型但要愿意啃WPS加载项的XML清单和事件绑定。下面按一条能半小时跑通主干的路径展开先解决技术选型问题再搭出插件骨架最后把文本提取、参数调优、调试排错这些真正决定“能不能用”的细节补齐。2. 为什么是JSAPI加载项而不是VBA选型背后的兼容性账2.1 WPS的三种扩展通道分别对应什么场景WPS对外扩展通常有三条路COM加载项、VBA宏、JSAPI插件。COM加载项能力最强可以直接操作WPS进程内对象但开发要用C或.NET编译出来的DLL要在注册表注册——网上热词里“软件装好之后不能直接剪切粘贴移动安装目录”“office、wps这类软件都依赖注册”说的就是这类组件的痛点一旦WPS目录变动DLL加载直接失败。VBA宏适合个人脚本不跨机器且WPS个人版仅安装后不带VBA组件需要单独装vba7.1装了以后依然存在ActiveX控件调用受限制的问题发网络请求时尤其麻烦。JSAPI插件是WPS近年主推的轻量扩展方式本质上是一个内嵌浏览器容器加载Web页面页面里的JavaScript通过WPS提供的wpsjsapi对象访问文档内容。开发语言是标准JavaScript调试可以用浏览器DevTools思路发布产物是一个文件夹通过WPS的“开发者工具→加载项”入口加载不碰注册表。对于要调DeepSeekAPI这种有异步网络交互的场景JSAPI是唯一能顺畅处理Promise和流式读取的方案。2.2 能进“深度集成”的接口只有JSAPI这一层所谓深度集成不是把一段文本粘贴到网页对话框里再复制回来而是让模型可以直接读选区、写选区、遍历段落、读表格。JSAPI里的wps.WpsApplication().ActiveDocument对象能拿到当前文档选区用Selection.Range.Text读取写入用Selection.Text ...表格遍历用Document.Tables.Item(i)。这些接口在VBA里也有对应版本但JSAPI的调用是异步的await wps.WpsApplication().ActiveDocument更贴合API请求的异步模型。另一个关键点是JSAPI插件页面可以自己做UI任务窗格里的输入框、按钮、下拉选项都由Web前端控制模型返回的Markdown可以先用marked之类的库渲染成HTML再做回填这在VBA里几乎不可行。综合下来标题里“深度集成”的技术底座在实际开发中几乎都默认指向JSAPI加载项。3. 搭项目骨架让WPS加载项发出第一次DeepSeekAPI请求3.1 初始化工程的命令和目录结构WPS官方提供了一套基于Node.js的命令行工具常见做法是全局安装脚手架后生成一个RPA类型的加载项工程。命令行如下npm install -g wpsjsrpa wpsjsrpa create my-deepseek-plugin cd my-deepseek-plugin wpsjsrpa dev生成后的目录结构核心是这三块my-deepseek-plugin/ ├── package.json ├── wpsjs.config.json ├── index.html # 任务窗格页面 ├── index.js # 页面逻辑 └── plugin/ ├── manifest.xml # 加载项声明含按钮、权限 └── index.htmlmanifest.xml里有两个字段决定插件在WPS里的入口形态。一个是Type写TaskPane则加载项出现在右侧任务窗格写Button则出现在功能区另一个是Script里的URL指向实际HTML页面。wpsjsrpa dev会启动本地开发服务器把页面代理到WPS加载项容器里修改代码后刷新任务窗格即可生效不需要重启WPS。注意这一步有几个热词里高频出现的坑“wps自定义功能区没有宏”“wps vba”常见的原因是WPS个人版没有加载宏的安全入口而JSAPI加载项的入口在开发工具→加载项里需要先在WPS的选项面板里勾选“显示开发工具选项卡”否则菜单都找不到。另外wpsjsrpa dev的端口默认是127.0.0.1:8000如果本机8000被占用改wpsjs.config.json里的devUrl即可WPS 64位和32位的插件配置目录不同64位系统装64位WPS的话加载项调试面板里会显示“64bit”字样确认自己的版本和工具链一致再往下走。3.2 第一个能用的网络请求从任务窗格发到DeepSeekAPI初始化工程后把index.js里的逻辑替换为下面这段最小实现该代码能实现“用户点击按钮把固定提示词发给DeepSeek返回文本显示在任务窗格里”const button document.getElementById(sendBtn); const output document.getElementById(output); button.addEventListener(click, async () { const apiKey sk-你的key; const url https://api.deepseek.com/chat/completions; const body { model: deepseek-chat, messages: [ { role: system, content: 你是一个严谨的技术助手回答尽量简洁。 }, { role: user, content: 用一句话解释什么是WPS加载项。 } ], temperature: 0.3, stream: false }; try { const resp await fetch(url, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${apiKey} }, body: JSON.stringify(body) }); if (!resp.ok) { throw new Error(HTTP ${resp.status}: ${await resp.text()}); } const data await resp.json(); output.innerText data.choices[0].message.content; } catch (err) { output.innerText 请求失败 err.message; } });model字段填deepseek-chat是DeepSeekAPI对话补全模型的通用名对应V3系列能力知识截止时间、推理特点都由服务端决定客户端不需要额外指定。temperature: 0.3是深度集成场景里极实用的参数做摘要、翻译、周报这类偏向抽取和规整的任务时低温让输出更稳、更少编造如果后续做文案润色或头脑风暴可以调高到0.8。stream: false表示一次性返回完整结果调试时更容易看清JSON结构。Authorization用的是Bearer令牌格式直接拼接字符串即可DeepSeekAPI兼容OpenAI的鉴权头设计。响应体里data.choices[0].message.content就是最终文本多轮对话时把每次的assistant消息再塞回messages数组就行。提示fetch在WPS加载项容器里受同源策略管控如果请求报CORS错误先在wpsjs.config.json里给插件配置domain白名单把api.deepseek.com加进去。4. 做第一个有实际价值的插件功能选中文本的AI摘要生成与回填4.1 从文档里取选中内容的正确姿势任务窗格里的按钮直接操作文档对象模型核心是拿到当前活动文档的选中区域。WPS的JSAPI把VBA的Selection对象搬了过来但改成了异步属性访问。下面这段代码将读取用户选中的文字并显示在任务窗格里async function getSelectedText() { const app await wps.WpsApplication(); const doc await app.ActiveDocument; const sel await doc.Selection; const text await sel.Text; return text; }逐行说明wps.WpsApplication()返回Application对象等价于VBA里的ApplicationActiveDocument是当前打开的那个文档对象Selection.Text属性在JSAPI里是异步获取的必须await。注意await sel.Text拿到的字符串末尾会带一个换行符或段落标记做字数统计或截断时先trim()掉。这里有个细节WPS打开多个标签页时ActiveDocument永远指向当前可见的那个所以从任务窗格触发操作的用户心智是“我在看哪个文档插件就操作哪个文档”。如果文档没保存过sel.Text也能正常读取但回填前要注意文档是否处于编辑锁定状态——被密码保护或协作权限限制的文档写入会抛Permission Denied。4.2 生成摘要后回填的三种方式拿到选中文本后拼进messages数组发给DeepSeek然后把拿到结果的文本写回文档。常见有三种回填方式覆盖原选区、插入到选区末尾、弹窗确认后写入。下面是“插入到选区末尾并追加分隔线”的实现async function insertAfterSelection(text) { const app await wps.WpsApplication(); const doc await app.ActiveDocument; const sel await doc.Selection; const range await sel.Range; await range.InsertAfter(\n\n[AI摘要]\n text); await range.Collapse(0); // wdCollapseEnd }sel.Range返回一个Range对象InsertAfter是在Range结束位置后面插入字符串插入后原选区不会被替换符合“保留原文、追加摘要”的预览语义。Collapse(0)把Range折叠到末尾参数0对应Word里的wdCollapseEnd这样光标会停在摘要后面方便用户继续打字如果填1则折叠到开头光标回到选中前的位置。还有一个更常用的变体——把摘要插入到文首作为“AI导语”const docContent await doc.Content; await docContent.InsertBefore([AI摘要]\n text \n\n);doc.Content代表整篇文档的RangeInsertBefore在文档最前面插入内容。适合自动生成文章导语或日报摘要的场景。到底用Selection还是Content判断标准是操作范围是否跟随用户光标。如果功能是“针对选区做任务”绝对不要绕过Selection否则用户会困惑“为什么插件改的是别的地方”。4.3 把按钮和流程串起来的完整实现把上面几块组合起来就是一个“选中文字→生成摘要→一键插入”的最小可用插件。document.getElementById(summarizeBtn).addEventListener(click, async () { const text await getSelectedText().catch(() ); if (!text.trim()) { alert(请先在文档中选中文字); return; } const summary await askDeepSeek([ { role: system, content: 你是文档摘要助手输出50字以内的简洁摘要。 }, { role: user, content: text.slice(0, 6000) } ]); await insertAfterSelection(summary); }); async function askDeepSeek(messages) { const resp await fetch(https://api.deepseek.com/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer localStorage.getItem(ds_key) }, body: JSON.stringify({ model: deepseek-chat, messages, temperature: 0.2, stream: false }) }); const data await resp.json(); return data.choices[0].message.content.trim(); }注意这段代码的两个关键设计一是text.slice(0, 6000)DeepSeekAPI的上下文窗口足够容纳几万字但加载项插件跑在WPS容器里一次性传输过长的Selection.Text会造成UI卡顿截断到6000字符是兼顾效果与性能的通用做法二是API key存在localStorage里这个细节决定了插件能不能发给别人用。提示把DeepSeekAPI key直接写死在插件代码里一旦插件包发给别人key就泄露了。常见做法是插件只负责界面和文档交互真实调用通过自己的后端转发前端拿到的只是一个临时token。个人自用可以先用localStorage存key但不要把key发到公开仓库。5. 从能用到好用上下文管理、流式响应与文档语义提取5.1 多轮对话状态怎么维护摘要功能只用了一次请求但“智能办公插件”的核心场景是连续对话比如先问“这段合同的核心风险是什么”再问“那风险点按严重程度排序”第二次提问必须带上第一次的上下文。OpenAI兼容接口本身不保存状态所有上下文要靠客户端拼接。我在实际项目里的做法是维护一个全局messages数组let history []; async function chat(role, content) { history.push({ role, content }); // 控制上下文长度如果超出最近4000字符丢弃最早的消息 while (JSON.stringify(history).length 4000) { history.shift(); } const resp await fetch(https://api.deepseek.com/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${localStorage.getItem(ds_key)} }, body: JSON.stringify({ model: deepseek-chat, messages: history, temperature: 0.3, stream: false }) }); const data await resp.json(); const reply data.choices[0].message.content; history.push({ role: assistant, content: reply }); return reply; }字符串长度控制是滑动窗口的简化版本每次请求前把history序列化成JSON看总长度超过阈值就shift()掉最早的消息。这么做避免了“调用次数多了以后token超限报错”的经典问题——InvalidArgumentError: this models maximum context length is 8192 tokens这类错误十有八九是上下文没做裁剪。5.2 流式输出让长文档生成不再像“卡死”生成摘要时stream: false没问题但如果让DeepSeek写一篇方案或润色一大段文案等完整返回可能要几十秒任务窗格一直空白用户会以为插件崩溃。流式输出能解决这个问题DeepSeekAPI的流式响应和OpenAI一样设置stream: true后返回text/event-stream格式。下面是流式读取的实现用ReadableStream逐块解析async function streamChat(messages, onChunk) { const resp await fetch(https://api.deepseek.com/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${localStorage.getItem(ds_key)} }, body: JSON.stringify({ model: deepseek-chat, messages, stream: true, temperature: 0.3 }) }); const reader resp.body.getReader(); const decoder new TextDecoder(utf-8); let buffer ; while (true) { const { done, value } await reader.read(); if (done) break; buffer decoder.decode(value, { stream: true }); // 按行分割 SSE 数据 const lines buffer.split(\n); buffer lines.pop(); for (const line of lines) { const trimmed line.trim(); if (!trimmed.startsWith(data:)) continue; const dataStr trimmed.slice(5).trim(); if (dataStr [DONE]) continue; try { const json JSON.parse(dataStr); const delta json.choices[0]?.delta?.content; if (delta) onChunk(delta); } catch (e) { console.error(解析流式数据失败, e); } } } }这段代码的难点不在API调用而在SSE的拆包。reader.read()拿到的value是按网络包切割的一个完整的data:行可能被拆到两次读取里所以用buffer暂存未处理完的片段每次拼接后再按行拆分。lines.pop()是精华最后一行可能是不完整的半行留到下一轮循环再处理。choices[0].delta.content是增量文本累积起来就是完整回答。拿到增量后怎么展示我的做法是先把内容累积到一个变量实时更新任务窗格的textarea或pre元素全部结束后再把完整文本交给InsertAfter。这样用户能看到“打字机效果”心理上觉得插件还活着。5.3 文档级语义提取从选区走向全文档“深度集成”如果只处理选中文本价值有限。实际办公场景里用得最多的是把整篇文档的结构提取出来喂给模型比如生成全文摘要、自动提取关键词、按段落重写。WPS的JSAPI提供了段落和表格集合的遍历能力async function extractFullText() { const app await wps.WpsApplication(); const doc await app.ActiveDocument; const paragraphs await doc.Paragraphs; const count await paragraphs.Count; let fullText ; for (let i 1; i count; i) { const para await paragraphs.Item(i); const text await para.Range.Text; fullText text \n; // 超过8000字符就停止避免任务窗格卡死 if (fullText.length 8000) break; } return fullText; }Paragraphs集合的下标从1开始而不是JavaScript习惯的0这是从VBA继承下来的习惯写循环时特别容易踩坑。Item(i)拿到单个段落Range.Text拿到该段落文本。限制8000字符是因为整篇文档可能有几十万字全量发给模型既慢又贵本质上是让模型“读重点”——取前N段或按标题分段抽样更聪明。这个函数可以延伸出很实用的功能自动生成周报。选中本周写的文档提取标题和正文前几行扔给DeepSeek让它“把下列文档内容整理成周报包含完成事项和下一步计划”输出后通过InsertBefore插到文档开头。整个过程用户只需要点一次按钮。6. 调试终极技巧让WPS加载项开发者工具替你抓出所有藏着的错6.1wpsjsrpa debugger比alert好用得多JSAPI加载项开发最常见的痛苦是“任务窗格显示空白但不知道哪一步错了”。WPS的加载项容器虽然基于Chromium但不会自动弹出DevTools。这时候要用wpsjsrpa提供的调试命令wpsjsrpa debugger执行后加载项页面会被强制打开一个调试窗口能看到console.log的输出、Network面板里的DeepSeekAPI请求详情、以及Sources面板里的断点。最常见的报错集中在两类跨域请求失败看Network里CORS error字样和wps.WpsApplication()拿不到对象通常是因为文档没打开或者插件权限没开。6.2 关闭加载项后WPS后台进程残留的处理热词里“如何解决WPS界面关闭后后台还有一堆子程序运行”“wps office 消息筛选器显示应用程序正在使用中”这些高频问题在加载项开发中体会更深wpsjsrpa dev启动后WPS主进程退出但加载项容器进程经常驻留后台。再次wpsjsrpa dev时会报端口占用这是开发中最容易被误判为代码问题的故障。处理方式不是去任务管理器硬杀而是结束WPS后台常驻的加载项进程在任务管理器里找到镜像名为wpscloudsvr.exe或wpscenter.exe的进程全部结束然后重新进入文档触发加载项加载。WPS设计成后台保留进程是为了云同步和开机加速但如果开发插件时发现改动不生效先检查是不是旧进程缓存了旧代码。6.3 参数调优的最后一块拼图temperature与top_p的配合一个容易忽略的细节OpenAI兼容接口里temperature和top_p不能同时大改。DeepSeekAPI的文档也遵循这个约束官方建议是“修改temperature时保持top_p为默认值1或修改top_p时保持temperature为1”。在代码里的落地是const payload { model: deepseek-chat, messages, temperature: 0.3 // top_p 保持默认不传 };做摘要、翻译、规则抽取这种对确定性要求高的任务固定用temperature: 0.2~0.3让回答更稳定做创意文案或头脑风暴时调到0.8~1.0。不要同时调两个参数输出会变得不可控。这个细节在深度集成场景里体现得特别明显——插件面对的是非技术用户他们不会理解“为什么同一段文字两次生成结果不一样”所以温度默认值直接决定插件口碑。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价