ONLYOFFICEのAIプラグインにカスタム機能を追加する方法

2025年12月03日著者:Denis

ONLYOFFICEのAIプラグインは、開発者に優しい新アーキテクチャを導入しました。プラグインの機能を拡張したい場合、たとえば特定のAI機能を追加する場合、もはや巨大なモノリシックファイルを編集する必要はありません。

代わりに、プラグインには.devフォルダーに配置された専用ワークスペースが用意されています。このガイドでは、このワークスペースを使用して新しいカスタム機能を追加する方法を正確に示します。

例として、ドキュメントエディター用の画像を説明機能を構築します。

How to add custom features to the ONLYOFFICE AI plugin

開発ワークフロー

基本的な概念はシンプルです:.devで作業し、スクリプトが本番コードをビルドします。

  • .dev/helpers/:これはサンドボックスです。ここに新しいファイルを作成します。
  • helpers.js:これはプラグインが実際に読み取る本番ファイルです。直接編集しないでください。自動生成されます。

ステップ1:エディタースコープを選択

.dev/helpers/内には、ONLYOFFICEエディターに対応する3つのフォルダーがあります:

  • 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.",
    // AIが何を尋ねるべきかを知るためのパラメータを定義
    parameters: {
      type: "object",
      properties: {
        prompt: {
          type: "string",
          description:
            "instruction for the AI (e.g., 'Add a short title for this chart.')",
        },
      },
      required: ["prompt"],
    },
    // 使用方法についてAIをトレーニングするための例を提供
    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.",
        },
      },
    ],
  });

   // エディター内で実行される実際のロジック
  func.call = async function (params) {
    let prompt = params.prompt;

    async function insertMessage(message) {
      Asc.scope._message = String(message || "");
      // 3. 結果をドキュメントに挿入
      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. 選択した画像を取得
      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. 画像+プロンプトをAIエンジンに送信
      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:機能をテスト

  1. ONLYOFFICEでプラグインをリロードします。
  2. ドキュメント内の画像を選択します。
  3. AIに尋ねます:「この画像を説明して」または「これのキャプションを書いて」

これで、AIは新しいカスタム機能を認識し、作成したロジックを実行します。

ONLYOFFICEの無料アカウントを登録する

オンラインでドキュメント、スプレッドシート、スライド、フォーム、PDFファイルの閲覧、編集、共同作業