Onlyoffice集成与AI交互操作指引(Iframe版)
Onlyoffice集成与AI交互操作指引(Iframe版)
本文档系统介绍了软件系统集成OnlyOffice实现在线编辑与AI辅助功能的方案。主要内容包括:后端需提供文档配置信息并实现Callback接口以处理文档保存;前端通过Vue集成编辑器,利用连接器(connector)调用API实现文本操作、菜单定制及事件监听;AI交互采用基于postMessage的消息机制,实现从编辑器发送文本到AI处理并返回结果替换的完整异步流程。该方案实现了文档编辑与AI能力的深度结合。
OnlyOffice集成
后端对接
Onlyoffice只提供的是文档的在线编辑能力,文档的保存、权限、文档信息、配置信息等都需要通过后端服务进行返回。
获取config配置
-
描述
- 配置文件主要包含用户信息,文档信息,编辑器配置,监听事件,及token等配置,用于文件内容的读取和编辑器的渲染。
-
文档参考链接
- https://api.onlyoffice.com/zh-CN/docs/docs-api/usage-api/config/
-
获取配置及文档信息(根据文档链接和操作模式返回配置信息)
{"document": {"fileType": "docx","info": {"favorite": true,"folder": "Example Files","owner": "John Smith","sharingSettings": [{"permissions": "Full Access","user": "John Smith"},{"isLink": true,"permissions": "Read Only","user": "External link"}],"uploaded": "2010-07-07 3:46 PM"},"isForm": true,"key": "Khirz6zTPdfd7","permissions": {"chat": true,"comment": true,"commentGroups": [{"edit": ["Group2",""],"remove": [""],"view": ""}],"copy": true,"deleteCommentAuthorOnly": false,"download": true,"edit": true,"editCommentAuthorOnly": false,"fillForms": true,"modifyContentControl": true,"modifyFilter": true,"print": true,"protect": true,"review": true,"reviewGroups": ["Group1","Group2",""],"userInfoGroups": ["Group1",""]},"referenceData": {"fileKey": "BCFA2CED","instanceId": "https://example.com"},"title": "Example Document Title.docx","url": "https://example.com/url-to-example-document.docx"},"documentType": "word","editorConfig": {"actionLink": "ACTION_DATA","callbackUrl": "https://example.com/url-to-callback.ashx","coEditing": {"change": true,"mode": "fast"},"createUrl": "https://example.com/url-to-create-document/","customization": {"about": true,"anonymous": {"label": "Guest","request": true},"autosave": true,"close": {"text": "Close file","visible": true},"comments": true,"compactHeader": false,"compactToolbar": false,"compatibleFeatures": false,"customer": {"address": "My City, 123a-45","info": "Some additional information","logo": "https://example.com/logo-big.png","logoDark": "https://example.com/dark-logo-big.png","mail": "john@example.com","name": "John Smith and Co.","phone": "123456789","www": "example.com"},"features": {"featuresTips": true,"roles": true,"spellcheck": {"change": true,"mode": true},"tabBackground": {"change": true,"mode": "header"},"tabStyle": {"change": true,"mode": "fill"}},"feedback": {"url": "https://example.com","visible": true},"font": {"name": "Arial","size": "11px"},"forceWesternFontSize": false,"forcesave": false,"goback": {"blank": true,"text": "Open file location","url": "https://example.com"},"help": true,"hideNotes": false,"hideRightMenu": true,"hideRulers": false,"integrationMode": "embed","layout": {"header": {"editMode": true,"save": true,"user": true,"users": true},"leftMenu": {"mode": true,"navigation": true,"spellcheck": true},"rightMenu": {"mode": true},"statusBar": {"actionStatus": true,"docLang": true,"textLang": true},"toolbar": {"collaboration": {"mailmerge": true},"draw": true,"file": {"close": true,"info": true,"save": true,"settings": true},"home": {},"layout": true,"plugins": true,"protect": true,"references": true,"save": true,"view": {"navigation": true}}},"loaderLogo": "https://example.com/loader-logo.png","loaderName": "The document is loading, please wait...","logo": {"image": "https://example.com/logo.png","imageDark": "https://example.com/dark-logo.png","imageLight": "https://example.com/light-logo.png","url": "https://example.com","visible": true},"macros": true,"macrosMode": "warn","mentionShare": true,"mobile": {"forceView": true,"info": false,"standardView": false},"plugins": true,"pointerMode": "select","review": {"hideReviewDisplay": false,"hoverMode": false,"reviewDisplay": "original","showReviewChanges": false,"trackChanges": true},"showHorizontalScroll": true,"showVerticalScroll": true,"slidePlayerBackground": "#000000","submitForm": {"resultMessage": "text","visible": true},"toolbarHideFileName": false,"uiTheme": "theme-dark","unit": "cm","wordHeadingsColor": "#00ff00","zoom": 100},"embedded": {"embedUrl": "https://example.com/embedded?doc=exampledocument1.docx","fullscreenUrl": "https://example.com/embedded?doc=exampledocument1.docx#fullscreen","saveUrl": "https://example.com/download?doc=exampledocument1.docx","shareUrl": "https://example.com/view?doc=exampledocument1.docx","toolbarDocked": "top"},"lang": "en","mode": "edit","plugins": {"autostart": ["asc.{0616AE85-5DBE-4B6B-A0A9-455C4F1503AD}","asc.{FFE1F462-1EA2-4391-990D-4CC84940B754}"],"options": {"all": {"keyAll": "valueAll"},"asc.{38E022EA-AD92-45FC-B22B-49DF39746DB4}": {"keyYoutube": "valueYoutube"}},"pluginsData": ["https://example.com/plugin1/config.json","https://example.com/plugin2/config.json"]},"recent": [{"folder": "Example Files","title": "exampledocument1.docx","url": "https://example.com/exampledocument1.docx"},{"folder": "Example Files","title": "exampledocument2.docx","url": "https://example.com/exampledocument2.docx"}],"region": "en-US","templates": [{"image": "https://example.com/exampletemplate1.png","title": "exampletemplate1.docx","url": "https://example.com/url-to-create-template1"},{"image": "https://example.com/exampletemplate2.png","title": "exampletemplate2.docx","url": "https://example.com/url-to-create-template2"}],"user": {"group": "Group1,Group2","id": "78e1e841","image": "https://example.com/url-to-user-avatar.png","name": "John Smith"}},"events": {},"height": "100%","token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.LwimMJA3puF3ioGeS-tfczR3370GXBZMIL-bdpu4hOU","type": "desktop","width": "100%"
}
实现Callback接口
-
描述
- callback的链接在获取配置时已经返回,onlyoffice会根据配置的callback地址,进行回调,通知服务有关文档编辑的状态,用户需要根据相关状态实现文档的保存。
-
文档参考链接
- https://api.onlyoffice.com/zh-CN/docs/docs-api/usage-api/callback-handler/
- 注意事项
- 要对回调内容中token进行校验,校验合法后才允许文件的更新保存
页面集成
VUE集成
-
文档参考链接
- https://api.onlyoffice.com/zh-CN/docs/docs-api/get-started/frontend-frameworks/vue/
-
参考实现(测试验证时的实现)
该集成页面实现了编辑器集成,自定义菜单注册,API执行,AI交互,消息发送与监听
<template><div style="height: 100%;"><div>OnlyOffice + AI交互示例</div><!-- 控制按钮 --><button @click="getFullText">获取全文</button><button @click="replaceWithAIResult">替换文本</button><button @click="getFullHtml">获取全文(HTML)</button><button @click="showMessage">消息展示</button><button @click="addComment">添加注释</button><button @click="getSelection">获取选中文本</button><button @click="getSelectionType">获取选择类型</button><button @click="inputText">输入文本</button><button @click="pasteHtml">粘贴HTML</button><button @click="searchAndReplace">搜索替换</button><div id="container"><div id="onlyoffice"></div><!-- <iframe id="aiIframe"src="AIURL"></iframe> --></div></div>
</template><script>
export default {data() {return {docEditor: null,connector: null,aiBox: {width: 226,height: 284,bottom: 5,isDragging: false},aiResult: ""};},mounted() {this.initOnlyOffice();// ---------------- AI iframe 返回结果 ----------------window.addEventListener('message', (event) => {if (!event.data || event.data.type !== 'replaceText') return;const { content } = event.data.data;// 这里执行文档替换操作,比如调用 OnlyOffice connector 方法if (this.connector) {this.connector.executeMethod("PasteText", [content]);console.log("已替换选中文本", content);}});},methods: {// 初始化 OnlyOffice 编辑器async initOnlyOffice() {try {const res = await fetch("http://{yourbacekservice}/onlyoffice/config?id=123");const config = await res.json();config.editorConfig.events = {onAppReady: () => {console.log("OnlyOffice加载完成");},onDocumentReady: this.onDocumentReady};const script = document.createElement("script");script.src = "http://{youronlrofficehost}/web-apps/apps/api/documents/api.js";script.onload = () => {this.docEditor = new DocsAPI.DocEditor("onlyoffice", config.editorConfig);};document.head.appendChild(script);} catch (e) {console.error("OnlyOffice初始化失败", e);}},onDocumentReady() {this.connector = this.docEditor.createConnector();console.log("文档准备就绪", this.connector);this.connector.attachEvent("onContextMenuShow", () => {this.connector.executeMethod("GetSelectedText", [], selectedText => {const hasSelection = selectedText && selectedText.trim().length > 0;const childItems = [{ id: "analyzeText", text: "分析文本内容", onClick: () => this.sendToAI(selectedText, "analyzeText") }];if (hasSelection) {childItems.push({id: "optimizeText",text: "文案优化",onClick: () => this.sendToAI(selectedText, "optimizeText")},{ id: "correctText", text: "文本纠错", onClick: () => this.sendToAI(selectedText, "correctText") },{id: "translateText",text: "文本翻译",onClick: () => this.sendToAI(selectedText, "translateText")});}this.connector.addContextMenuItem([{ id: "hopeSeekAI", text: "HopeSeek(AI)", items: childItems }]);});});const iframe = document.getElementById('aiIframe');if (!iframe) return;iframe.contentWindow.postMessage({type: 'openChange',data: { isShow: true }}, '*');},sendToAI(content, action) {const iframe = document.getElementById("aiIframe");if (!iframe) return;iframe.contentWindow.postMessage({type: "aiRequest",data: { content, action, source: "onlyoffice" }}, "*");},getSelectionType() {this.connector.executeMethod("GetSelectionType", [], selectedType => {console.log("获取选择类型", selectedType);});},inputText() {this.connector.executeMethod("InputText", ["ONLYOFFICE Plugins", ""])},getSelection() {// this.connector.executeMethod("GetSelectedText", [], selectedText => {// console.log("已获取选中文本", selectedText);// });this.connector.executeMethod("GetSelectedText", [{ "Numbering": false, "Math": false, "TableCellSeparator": '\n', "ParaSeparator": '\n', "TabSymbol": String.fromCharCode(9) }], function (data) {const sText = data;// ExecTypograf (sText);console.log(sText);});},replaceWithAIResult() {this.connector.executeMethod("PasteText", ["要粘贴的内容"]);},pasteHtml() {this.connector.executeMethod("PasteHtml", ["<p><b>Plugin methods for OLE objects</b></p><ul><li>AddOleObject</li><li>EditOleObject</li></ul>"]);},searchAndReplace() {this.connector.executeMethod("SearchAndReplace", [{"searchString": "目标","replaceString": "R目标R","matchCase": true}]);},getFullText() {this.connector.callCommand(() => Api.GetDocument().GetText(), data => console.log(data));},getFullHtml() {this.connector.executeMethod("GetFileHTML", null, res => console.log(res));},showMessage() {this.docEditor.showMessage("12324")},// AI 浮窗拖拽操作startDrag(e) {e.preventDefault();this.aiBox.isDragging = true;this.aiBox.startY = e.clientY - this.aiBox.bottom;document.addEventListener('mousemove', this.onDrag);document.addEventListener('mouseup', this.stopDrag);},onDrag(e) {if (!this.aiBox.isDragging) return;let newY = window.innerHeight - e.clientY;if (newY > 5 && (window.innerHeight - newY) > 150) this.aiBox.bottom = newY;document.getElementById('aiBox').style.bottom = this.aiBox.bottom + 'px';},stopDrag() {this.aiBox.isDragging = false;document.removeEventListener('mousemove', this.onDrag);document.removeEventListener('mouseup', this.stopDrag);},addComment() {this.connector.executeMethod("AddComment", [{"UserName": "John Smith","QuoteText": "text","Text": "要填写的注释内容","Time": "1662737941471","Solved": false,}], function (comment) {console.log(comment)});}}};
</script><style scoped>
body,
html {margin: 0;height: 100%;overflow: hidden;
}#container {display: flex;height: 100%;
}#onlyoffice {flex: 2;
}#sidebar {width: 350px;border-left: 1px solid #ddd;
}#aiBox {position: fixed;right: 0;bottom: 5px;width: 600px;height: 100%;z-index: 2000;border: 1px solid #ccc;background: #fff;box-shadow: 0 0 8px rgba(0, 0, 0, 0.3);
}#aiIframe {width: 70%;height: 100%;border: none;
}
</style>
连接器(connector)介绍
简介
对于我们而言,很多情况下都是简单的操作一下文档,做一些和业务系统相关操作的功能,使用到:callCommand 、executeMethod、attachEvent、detachEvent这四个核心块api模块。
初始化
this.connector = this.docEditor.createConnector();
核心块说明
- callCommand()
- 基础api调用模块,用于组合并执行复杂api或者自定义代码。- 文档链接:[https://api.onlyoffice.com/docs/plugin-and-macros/interacting-with-editors/overview/how-to-call-commands/](https://api.onlyoffice.com/docs/plugin-and-macros/interacting-with-editors/overview/how-to-call-commands/)
- 示例
this.connector.callCommand(() => Api.GetDocument().GetText(), data => console.log(data));
-
executeMethod()。
-
直接执行某个api,它与callCommand的区别是:callCommand是自己写代码执行也就是执行function(){xxxxx}方法体,executeMethod执行的只是某一个方法。
-
文档链接:https://api.onlyoffice.com/docs/plugin-and-macros/interacting-with-editors/overview/how-to-call-methods/
-
- 示例
this.connector.executeMethod("PasteText", ["要粘贴的内容"]);
- attachEvent、detachEvent
- 绑定、解绑事件。
- 文档在这:[https://api.onlyoffice.com/docs/plugin-and-macros/interacting-with-editors/overview/how-to-attach-events/](https://api.onlyoffice.com/docs/plugin-and-macros/interacting-with-editors/overview/how-to-attach-events/)- 示例
/**
* 绑定事件
*/
connector.attachEvent("onAddComment", function(){console.log("event: onAddComment");
});/**
* 解绑事件
*/
connector.detachEvent("onAddComment");
常用API
添加上下文菜单-addContextMenuItem
-
文档链接
- https://api.onlyoffice.com/zh-CN/docs/docs-api/usage-api/automation-api/
- 代码示例
connector.attachEvent("onContextMenuShow", (options) => {connector.addContextMenuItem([{text: "mainItem",onClick: () => {console.log("[CONTEXTMENUCLICK] menuSubItem1");},}]);
});
添加工具栏菜单-addToolbarMenuItem
-
文档链接
- https://api.onlyoffice.com/zh-CN/docs/docs-api/usage-api/automation-api/#addtoolbarmenuitem
-
代码示例
connector.addToolbarMenuItem({tabs: [{text: "Connector",items: [{id: "toolConnector1",type: "button",text: "Meaning",hint: "Meaning",lockInViewMode: true,icons: "./icon.svg",items: [{id: "toolC1",text: "Text",data: "Hello",onClick: (data) => {console.log(`[TOOLBARMENUCLICK]: ${data}`);},},],},],},],
});
事件监听-attachEvent
-
文档链接
- https://api.onlyoffice.com/zh-CN/docs/docs-api/usage-api/automation-api/#attachevent
-
代码示例
connector.attachEvent("onChangeContentControl", (obj) => {console.log(`[EVENT] onChangeContentControl: ${JSON.stringify(obj)}`)
})
添加注释-AddComment
-
文档链接
- https://api.onlyoffice.com/zh-CN/docs/plugin-and-macros/interacting-with-editors/text-document-api/Methods/AddComment/
-
代码示例
addComment() {this.connector.executeMethod("AddComment", [{"UserName": "John Smith","QuoteText": "text","Text": "要填写的注释内容","Time": "1662737941471","Solved": false,}], function (comment) {console.log(comment)});}
获取选中内容-GetSelectedContent
-
文档链接
- https://api.onlyoffice.com/zh-CN/docs/plugin-and-macros/interacting-with-editors/text-document-api/Methods/GetSelectedContent/
-
代码示例
getSelection() {方法一:this.connector.executeMethod("GetSelectedText", [], selectedText => {console.log("已获取选中文本", selectedText);});方法二:this.connector.executeMethod("GetSelectedText", [{"Numbering": false, "Math": false, "TableCellSeparator": '\n', "ParaSeparator": '\n', "TabSymbol": String.fromCharCode(9)}], function (data) {const sText = data;// ExecTypograf (sText);console.log(sText);});
},
获取选择类型-GetSelectionType
-
文档链接
- https://api.onlyoffice.com/zh-CN/docs/plugin-and-macros/interacting-with-editors/text-document-api/Methods/GetSelectionType/
-
代码示例
getSelectionType() {this.connector.executeMethod("GetSelectionType", [], selectedType => {console.log("获取选择类型", selectedType);});
},
输入文本-InputText
-
文档链接
- https://api.onlyoffice.com/zh-CN/docs/plugin-and-macros/interacting-with-editors/text-document-api/Methods/InputText/
-
代码示例
inputText() {this.connector.executeMethod("InputText", ["ONLYOFFICE Plugins", ""])},
粘贴文本-PasteText(如果当期有选中内容的话,实际是删除并粘贴)
-
文档链接
- https://api.onlyoffice.com/zh-CN/docs/plugin-and-macros/interacting-with-editors/text-document-api/Methods/PasteText/
-
代码示例
this.connector.executeMethod("PasteText", ["要粘贴的内容"]);
查找并替换文本-SearchAndReplace
- 文档链接
- https://api.onlyoffice.com/zh-CN/docs/plugin-and-macros/interacting-with-editors/text-document-api/Methods/SearchAndReplace/
- 代码示例
searchAndReplace() {this.connector.executeMethod("SearchAndReplace", [{"searchString": "目标","replaceString": "R目标R","matchCase": true}]);},
AI交互
整体说明
ONLYOFFICE与AI助手的交互本质上是一个基于 “消息驱动” 的异步通信模型,其核心是 postMessage
API。整个过程可以清晰地划分为两个主要阶段:请求阶段和响应阶段。
整个交互流程可以概括为以下两个核心阶段:
第一阶段:从编辑器到AI助手(发送请求)
此阶段完成从用户操作到AI接收处理任务的闭环。
-
用户操作触发:
-
用户在ONLYOFFICE在线编辑器中选择一段文本内容。
-
用户点击编辑器菜单中集成的AI功能按钮(例如:“内容优化”、“续写”、“翻译”、“添加注释”等)。
-
-
集成页面捕获事件并组装消息:
-
集成页面(即嵌入编辑器的父页面)监听到来自编辑器的这个特定菜单点击事件。
-
集成页面通过编辑器提供的API(如
getSelectedText
)获取用户当前选中的内容。 -
集成页面将操作事件类型(如:
"analyzeText"
)和选中的内容组装成一个结构化的消息对象。例如:
-
{"type": "ai-request", // 固定消息类型,表明这是一条AI请求"source":"onlyoffice", "data": {"action": "analyzeText", // 具体的事件类型"selectedText": "这里是用户选中的文本内容...","otherParams": {} // 其他可能需要的参数}
}
-
发送消息至AI助手:
-
集成页面通过
postMessage
方法,将该消息对象发送到AI助手(通常是一个独立的、隐藏的或浮层的<iframe>
窗口)。 -
发送时指定AI助手窗口的源(origin),以确保安全。
-
const iframe = document.getElementById("aiIframe");if (!iframe) return;iframe.contentWindow.postMessage({"type": "ai-request", // 固定消息类型,表明这是一条AI请求"source":"onlyoffice", "data": {"action": "analyzeText", // 具体的事件类型"selectedText": "这里是用户选中的文本内容...","otherParams": {} // 其他可能需要的参数}}, "");
第二阶段:从AI助手回编辑器(执行操作)
此阶段完成从AI生成结果到编辑器内容更新的闭环。
-
AI助手监听并处理消息:
-
AI助手窗口通过
window.addEventListener('message', ...)
持续监听消息。 -
接收到消息后,首先验证消息来源的合法性,确保其来自集成的父页面。
-
解析消息内容,根据
action
字段判断需要执行的具体AI任务(例如:调用“内容优化”的API)。 -
AI助手调用相应的后端AI服务接口,获取生成的结果。
-
-
用户确认与指令发送:
-
AI助手将生成的结果展示给用户(在它的UI界面中)。
-
用户查看结果后,点击“替换”、“插入”或“取消”等按钮。
-
当用户点击“替换”时,AI助手会组装一条响应消息。例如:
-
{"type": "ai-response", // 固定消息类型,表明这是一条AI请求"source":"ai-plugin", "data": {"action": "analyzeText", // 具体的事件类型"content": "content","otherParams": {"sourceMessage":{源消息内容}} // 其他可能需要的参数}
}
-
集成页面接收并执行操作:
-
集成页面监听来自AI助手窗口的
message
事件。 -
接收到响应消息后,同样进行来源验证和解析。
-
根据解析出的
-
替换(Replace):用 响应的
content
替换当前选中的文本。 -
插入(Insert):在光标处或选定位置插入响应的
content
。 -
添加注释(Comment):为选定文本或指定位置添加以响应的
content
为内容的注释。
-
-
window.addEventListener('message', (event) => {//进行事件,消息源,源数据的校验
});
整体交互流程
整个交互过程可以概括为下图所示的闭环流程:
核心特点:
-
解耦设计:编辑器与AI功能模块分离,通过标准API通信,易于开发和维护。
-
安全通信:使用
postMessage
并严格验证 origin,保障跨域通信安全。 -
异步交互:所有操作均为非阻塞,保证用户体验流畅。
-
可扩展性:只需定义新的
type
和对应的处理逻辑,即可轻松添加更多AI功能。