Cómo añadir funciones personalizadas al plugin de IA de ONLYOFFICE

4 diciembre 2025Por Dea

El plugin de IA de ONLYOFFICE ha introducido una nueva arquitectura orientada al desarrollo. Si deseas ampliar las capacidades del plugin, por ejemplo, para añadir una función específica de IA, ya no necesitas editar un archivo monolítico y enorme.

En su lugar, el plugin ahora incluye un espacio de trabajo dedicado ubicado en la carpeta .dev. Esta guía te mostrará exactamente cómo usar este espacio para añadir una nueva función personalizada.

Usaremos como ejemplo la función Describir imagen para el Editor de documentos.

Cómo añadir funciones personalizadas al plugin de IA de ONLYOFFICE

Flujo de trabajo de desarrollo

El concepto central es sencillo: Trabajas dentro de .dev, y un script genera el código de producción.

  • .dev/helpers/: Este es tu entorno de pruebas. Aquí es donde creas nuevos archivos.
  • helpers.js: Este es el archivo de producción que el plugin realmente utiliza. No lo edites directamente; se genera automáticamente.

Paso 1: Elige el alcance del editor

Dentro de .dev/helpers/ encontrarás tres carpetas correspondientes a los editores ONLYOFFICE:

  • word/ (Editor de documentos)
  • cell/ (Editor de hojas de cálculo)
  • slide/ (Editor de presentaciones)

Como nuestra función para describir imágenes está diseñada para documentos de texto, trabajaremos en .dev/helpers/word/.

Paso 2: Crea tu propia función

Crea un nuevo archivo llamado describe-image.js dentro de .dev/helpers/word/.

El plugin utiliza una clase específica llamada RegisteredFunction para definir capacidades. Esta estructura le indica a la IA qué hace la función y cómo debe llamarla.

Consejo: Asegúrate de que tu sintaxis sea correcta (cuidado con llaves sin cerrar).

El código:

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

Paso 3: Compila tus cambios

Este es el paso más importante del nuevo flujo de trabajo. El plugin aún no puede leer tu nuevo archivo directamente. Debes ejecutar el script de ayuda para fusionar tu nuevo archivo con la lógica principal del plugin.

  1. Abre tu terminal.
  2. Navega al directorio helpers:
  3. cd .dev/helpers
  4. Ejecuta el script de compilación en Python.
  5. python3 helpers.py

El script helpers.py analizará las carpetas word/, cell/ y slide/, encontrará tu nuevo describe-image.js y lo fusionará en el archivo principal helpers.js.

 Paso 4: Prueba tu función

  1. Recarga el plugin en ONLYOFFICE.
  2. Selecciona una imagen en tu documento.
  3. Pídele a la IA: “Describe esta imagen” o “Escribe un pie de foto para esto.”

La IA ahora reconocerá tu nueva función personalizada y ejecutará la lógica que has escrito.

Crea tu cuenta gratuita de ONLYOFFICE

Visualiza, edita y colabora en documentos, hojas, diapositivas, formularios y archivos PDF en línea.