Kako dodati prilagođene funkcije u ONLYOFFICE AI plugin

3 December 2025By Natalija Blagojevic

ONLYOFFICE AI plugin je uveo novu, za developere pogodniju arhitekturu. Ako želite da proširite mogućnosti plugin-a, na primer da dodate određenu AI funkciju, više ne morate da uređujete veliki, monolitni fajl.

Umesto toga, plugin sada ima posebno radno okruženje koje se nalazi u .dev folderu. Ovaj vodič će vam pokazati tačno kako da koristite ovo radno okruženje da biste dodali novu prilagođenu funkciju.

Kao primer ćemo izgraditi funkciju Opis slike za Document Editor.

How to add custom features to the ONLYOFFICE AI plugin

Proces razvoja

Osnovni koncept je jednostavan: radite u .dev, a skripta generiše produkcijski kod.

  • .dev/helpers/: Ovo je vaš sandbox. Ovde kreirate nove fajlove.
  • helpers.js: Ovo je produkcijski fajl koji dodatak zapravo koristi. Nemojte ga direktno uređivati. On se generiše automatski.

Korak 1: Izaberite opseg editora

Unutar .dev/helpers/ pronaći ćete tri foldera koji odgovaraju ONLYOFFICE editorima:

  • word/ (Document Editor)
  • cell/ (Spreadsheet Editor)
  • slide/ (Presentation Editor)

Pošto je naša funkcija opisivanja slike namenjena tekstualnim dokumentima, radićemo u .dev/helpers/word/.

Korak 2: Kreirajte svoju funkciju

Kreirajte novi fajl pod nazivom describe-image.js unutar .dev/helpers/word/.

Plugin koristi specifičnu klasu pod nazivom RegisteredFunction za definisanje mogućnosti. Ova struktura govori AI-ju šta funkcija radi i kako da je pozove.

Savet: Obratite pažnju da je sintaksa čista (pazite na nedostajuće zatvorene zagrade).

Kod:

(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;
})();

Korak 3: Kompajlirajte vaše izmene

Ovo je najvažniji korak u novom radnom procesu. Plugin još uvek ne može direktno da pročita vaš novi fajl. Morate pokrenuti helper skriptu da biste spojili vaš novi fajl sa glavnom logikom plugin-a.

  1. Otvorite terminal.
  2. Pređite u helpers direktorijum:
cd .dev/helpers

3. Pokrenite Python build skriptu:

python3 helpers.py

Skripta helpers.py će skenirati word/, cell/ i slide/ foldere, pronaći vaš novi describe-image.js i spojiti ga u glavni helpers.js fajl.

Korak 4: Testirajte vašu funkciju

  1. Ponovo učitajte plugin u ONLYOFFICE.
  2. Selektujte sliku u vašem dokumentu.
  3. Zatražite od AI-ja: “Opiši ovu sliku” ili “Napiši opis za ovu sliku.”

Zatražite od AI-ja: “Opiši ovu sliku” ili “Napiši opis za ovu sliku.”

AI će sada prepoznati vašu novu prilagođenu funkciju i izvršiti logiku koju ste upravo napisali.

Create your free ONLYOFFICE account

View, edit and collaborate on docs, sheets, slides, forms, and PDF files online.