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功能。