Onlyoffice集成与AI交互操作指引(Iframe版)
本文档系统介绍了软件系统集成OnlyOffice实现在线编辑与AI辅助功能的方案。主要内容包括:后端需提供文档配置信息并实现Callback接口以处理文档保存;前端通过Vue集成编辑器,利用连接器(connector)调用API实现文本操作、菜单定制及事件监听;AI交互采用基于postMessage的消息机制,实现从编辑器发送文本到AI处理并返回结果替换的完整异步流程。该方案实现了文档编辑与AI能力的深度结合。
OnlyOffice集成
后端对接
Onlyoffice只提供的是文档的在线编辑能力,文档的保存、权限、文档信息、配置信息等都需要通过后端服务进行返回。
获取config配置
描述
- 配置文件主要包含用户信息,文档信息,编辑器配置,监听事件,及token等配置,用于文件内容的读取和编辑器的渲染。
文档参考链接
获取配置及文档信息(根据文档链接和操作模式返回配置信息)
{
"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地址,进行回调,通知服务有关文档编辑的状态,用户需要根据相关状态实现文档的保存。
文档参考链接
- 注意事项
- 要对回调内容中token进行校验,校验合法后才允许文件的更新保存
页面集成
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执行的只是某一个方法。
- 示例
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
- 代码示例
connector.attachEvent("onContextMenuShow", (options) => {
connector.addContextMenuItem([{
text: "mainItem",
onClick: () => {
console.log("[CONTEXTMENUCLICK] menuSubItem1");
},
}]);
});
添加工具栏菜单-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
connector.attachEvent("onChangeContentControl", (obj) => {
console.log(`[EVENT] onChangeContentControl: ${JSON.stringify(obj)}`)
})
添加注释-AddComment
addComment() {
this.connector.executeMethod("AddComment", [
{
"UserName": "John Smith",
"QuoteText": "text",
"Text": "要填写的注释内容",
"Time": "1662737941471",
"Solved": false,
}
], function (comment) {
console.log(comment)
});
}
获取选中内容-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
getSelectionType() {
this.connector.executeMethod("GetSelectionType", [], selectedType => {
console.log("获取选择类型", selectedType);
});
},
输入文本-InputText
inputText() {
this.connector.executeMethod("InputText", ["ONLYOFFICE Plugins", ""])
},
粘贴文本-PasteText(如果当期有选中内容的话,实际是删除并粘贴)
this.connector.executeMethod("PasteText", ["要粘贴的内容"]);
查找并替换文本-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功能。