如何为 ONLYOFFICE AI 插件添加自定义功能
2025年12月04日作者:Krystal
ONLYOFFICE AI 插件现已采用全新的、对开发者更友好的架构。如果您想扩展插件功能,例如添加特定的 AI 功能,您不再需要编辑一个庞大且结构单一的文件。
取而代之的是,插件现在在 .dev 文件夹中提供了一个专用工作区。本指南将详细展示如何利用此工作区来添加新的自定义功能。
我们将以为文档编辑器构建描述图像功能作为示例。
开发工作流程
核心概念很简单:您在 .dev 环境中工作,脚本会生成生产代码。
- .dev/helpers/: 这是您的沙盒环境。您可以在这里创建新文件。
- helpers.js: 这是插件实际读取的生产文件。请勿直接编辑此文件。它是自动生成的。
步骤 1:选择编辑器范围
在 .dev/helpers/ 目录中,您会找到三个文件夹,分别对应 ONLYOFFICE 的编辑器:
- word/ (文本文档编辑器)
- cell/ (电子表格编辑器)
- slide/ (演示文稿编辑器)
由于我们的图像描述功能是针对文本文件的,因此我们将在 .dev/helpers/word/ 目录中工作。
步骤 2:创建自定义功能
在 .dev/helpers/word/ 目录中创建一个名为 describe-image.js 的新文件。
该插件使用名为 RegisteredFunction 的特定类来定义功能。此结构告诉 AI 该功能的功能以及如何调用它。
提示:确保语法正确(注意是否缺少右括号)。
代码:
(function () {
let func = new RegisteredFunction({
name: "describeImage",
description:
"Allows users to select an image and generate a meaningful title, description, caption, or alt text for it using AI.",
// Define parameters so the AI knows what to ask for
parameters: {
type: "object",
properties: {
prompt: {
type: "string",
description:
"instruction for the AI (e.g., 'Add a short title for this chart.')",
},
},
required: ["prompt"],
},
// Provide examples to train the AI on usage
examples: [
{
prompt: "Add a short title for this chart.",
arguments: { prompt: "Add a short title for this chart." },
},
{
prompt: "Write me a 1–2 sentence description of this photo.",
arguments: {
prompt: "Write me a 1–2 sentence description of this photo.",
},
},
{
prompt: "Generate a descriptive caption for this organizational chart.",
arguments: {
prompt:
"Generate a descriptive caption for this organizational chart.",
},
},
{
prompt: "Provide accessibility-friendly alt text for this infographic.",
arguments: {
prompt:
"Provide accessibility-friendly alt text for this infographic.",
},
},
],
});
// The actual logic executed inside the editor
func.call = async function (params) {
let prompt = params.prompt;
async function insertMessage(message) {
Asc.scope._message = String(message || "");
// 3. Insert the result into the document
await Asc.Editor.callCommand(function () {
const msg = Asc.scope._message || "";
const doc = Api.GetDocument();
const selected =
(doc.GetSelectedDrawings && doc.GetSelectedDrawings()) || [];
if (selected.length > 0) {
for (let i = 0; i < selected.length; i++) {
const drawing = selected[i];
const para = Api.CreateParagraph();
para.AddText(msg);
drawing.InsertParagraph(para, "after", true);
}
} else {
const para = Api.CreateParagraph();
para.AddText(msg);
let range = doc.GetCurrentParagraph();
range.InsertParagraph(para, "after", true);
}
Asc.scope._message = "";
}, true);
}
try {
// 1. Get the selected image
let imageData = await new Promise((resolve) => {
window.Asc.plugin.executeMethod(
"GetImageDataFromSelection",
[],
function (result) {
resolve(result);
}
);
});
if (!imageData || !imageData.src) {
await insertMessage("Please select a valid image first.");
return;
}
const whiteRectangleBase64 =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==";
if (imageData.src === whiteRectangleBase64) {
await insertMessage("Please select a valid image first.");
return;
}
let argPrompt = prompt + " (for the selected image)";
// 2. Send image + prompt to the AI engine
let requestEngine = AI.Request.create(AI.ActionType.Chat);
if (!requestEngine) {
await insertMessage("AI request engine not available.");
return;
}
const allowVision = /(vision|gemini|gpt-4o|gpt-4v|gpt-4-vision)/i;
if (!allowVision.test(requestEngine.modelUI.name)) {
await insertMessage(
"⚠ This model may not support images. Please choose a vision-capable model (e.g. GPT-4V, Gemini, etc.)."
);
return;
}
await Asc.Editor.callMethod("StartAction", [
"Block",
"AI (" + requestEngine.modelUI.name + ")",
]);
await Asc.Editor.callMethod("StartAction", ["GroupActions"]);
let messages = [
{
role: "user",
content: [
{ type: "text", text: argPrompt },
{
type: "image_url",
image_url: { url: imageData.src, detail: "high" },
},
],
},
];
let resultText = "";
await requestEngine.chatRequest(messages, false, async function (data) {
if (data) {
resultText += data;
}
});
await Asc.Editor.callMethod("EndAction", ["GroupActions"]);
await Asc.Editor.callMethod("EndAction", [
"Block",
"AI (" + requestEngine.modelUI.name + ")",
]);
Asc.scope.text = resultText || "";
if (!Asc.scope.text.trim()) {
await insertMessage(
"⚠ AI request failed (maybe the model cannot handle images)."
);
return;
}
await insertMessage(Asc.scope.text);
} catch (e) {
try {
await Asc.Editor.callMethod("EndAction", ["GroupActions"]);
await Asc.Editor.callMethod("EndAction", [
"Block",
"AI (describeImage)",
]);
} catch (ee) {
/* ignore */
}
console.error("describeImage error:", e);
await insertMessage(
"An unexpected error occurred while describing the image."
);
}
};
return func;
})();
步骤 3:编译更改
这是新工作流程中最重要的一步。插件目前还无法直接读取您的新文件。您必须运行辅助脚本,将新文件合并到主插件逻辑中。
1. 打开终端。
2. 导航到 helpers 目录:
cd .dev/helpers
3. 运行 Python 构建脚本:
python3 helpers.py
helpers.py 脚本会扫描 word/、cell/ 和 slide/ 文件夹,找到你新编写的 describe-image.js 文件,并将其合并到主 helpers.js 文件中。
步骤 4:测试功能
- 在 ONLYOFFICE 中重新加载插件。
- 在文档中选择一张图片。
- 询问 AI:“描述这张图片”或“为这张图片写一个标题”。
AI 现在会识别你新编写的自定义功能,并执行你刚刚编写的逻辑。
创建免费的 ONLYOFFICE 账户
在线查看并协作编辑文本文档、电子表格、幻灯片、表单和 PDF 文件。

