Skip to main content

An add-on for Plone

Project description

Shared infrastructure for the Omnia AI-assisted features suite in Plone 6.

This package provides settings management, a shared tabbed control panel, an “AI assistant” content menu, HTTP client services wrapping the Omnia APIs, extensibility interfaces, and branding assets. Other imio.omnia.* packages (imio.omnia.tinymce, imio.omnia.classification, etc.) build on top of it.

Installation

Add the egg to your buildout:

[buildout]

...

eggs =
    imio.omnia.core

Then run bin/buildout.

The package is auto-included in Plone via z3c.autoinclude.plugin, so no ZCML slug is needed.

Sub-packages that depend on imio.omnia.core should declare a GenericSetup dependency in their profiles/default/metadata.xml:

<?xml version="1.0"?>
<metadata>
  <version>1000</version>
  <dependencies>
    <dependency>profile-imio.omnia.core:default</dependency>
  </dependencies>
</metadata>

Configuration

Registry settings

Stored under the prefix imio.omnia.IOmniaCoreSettings:

Field

Purpose

core_api_url

Omnia Core API base URL

openai_api_url

Omnia OpenAI-compatible gateway base URL

openai_api_key

Optional Bearer token sent to the OpenAI-compatible API

openai_extra_headers

Additional HTTP headers for the OpenAI-compatible API (dict)

application_id

Application identifier (sent as x-imio-application header)

organization_id

Default organization / municipality ID (x-imio-municipality)

enable_proxy

Enable @@omnia-api proxy endpoint (default: False)

enable_openai_proxy

Enable @@omnia-openai-api streaming proxy (default: False)

These settings are editable via the Omnia control panel at @@omnia-ai-settings (Site Setup > Omnia).

Environment variables

Settings can also be driven by environment variables. They are synced to the Plone registry on Zope startup (requires SITE_ID to locate the Plone site):

Variable

Registry field

SITE_ID

Plone site ID in the ZODB (not stored in registry)

OMNIA_CORE_API_URL

core_api_url

OMNIA_OPENAI_API_URL

openai_api_url

OMNIA_OPENAI_API_KEY

openai_api_key

OMNIA_APPLICATION_ID

application_id

OMNIA_ORGANIZATION_ID

organization_id

Set them in buildout.cfg under [instance] environment-vars or export them in your shell before starting Plone.

Extending imio.omnia.core

Adding a control panel tab

Each Omnia sub-package can contribute a tab to the shared control panel. The tabbed layout is rendered by OmniaCoreControlPanelFormWrapper, which reads tabs from portal_actions in the omnia_controlpanel_tabs category.

Step 1 — Define a settings schema and form (browser/controlpanel.py):

from imio.omnia.core.browser.controlpanel import OmniaCoreControlPanelFormWrapper
from plone.app.registry.browser.controlpanel import RegistryEditForm
from plone.z3cform import layout
from zope import schema
from zope.interface import Interface

from my.package import _


class IMySettings(Interface):
    my_option = schema.TextLine(
        title=_("My option"),
        required=False,
    )


class MyControlPanelForm(RegistryEditForm):
    label = _("My add-on settings")
    schema = IMySettings


MyControlPanelView = layout.wrap_form(
    MyControlPanelForm, OmniaCoreControlPanelFormWrapper
)

Step 2 — Register the view (browser/configure.zcml):

<browser:page
  name="my-addon-settings"
  for="Products.CMFPlone.interfaces.IPloneSiteRoot"
  class=".controlpanel.MyControlPanelView"
  permission="cmf.ManagePortal"
  layer="my.package.interfaces.IMyBrowserLayer"
/>

Step 3 — Register the tab (profiles/default/actions.xml):

<?xml version="1.0"?>
<object name="portal_actions" meta_type="Plone Actions Tool"
        xmlns:i18n="http://xml.zope.org/namespaces/i18n">
  <object name="omnia_controlpanel_tabs" meta_type="CMF Action Category">
    <object name="my.package" meta_type="CMF Action">
      <property name="title" i18n:translate="">My add-on</property>
      <property name="url_expr">string:${portal_url}/@@my-addon-settings</property>
      <property name="icon_expr">string:gear</property>
    </object>
  </object>
</object>

Step 4 — Register the settings in the Plone registry (profiles/default/registry/main.xml):

<?xml version="1.0"?>
<registry>
  <records interface="my.package.browser.controlpanel.IMySettings"
           prefix="my.package.IMySettings" />
</registry>

Adding menu actions (AI assistant menu)

The “AI assistant” icon in the Plone toolbar opens a submenu populated from two sources:

  1. Static portal actions in the omnia_actions category.

  2. Dynamic IOmniaActionsProvider utilities.

Approach A — Static actions via actions.xml

Register a CMF Action in the omnia_actions category (profiles/default/actions.xml):

<?xml version="1.0"?>
<object name="portal_actions" meta_type="Plone Actions Tool"
        xmlns:i18n="http://xml.zope.org/namespaces/i18n">
  <object name="omnia_actions" meta_type="CMF Action Category">
    <object name="my_action" meta_type="CMF Action" i18n:domain="my.package">
      <property name="title" i18n:translate="">My AI action</property>
      <property name="description" i18n:translate="">Run my custom AI action.</property>
      <property name="url_expr">string:$object_url/@@my-ai-action-view</property>
      <property name="icon_expr">string:cpu</property>
      <property name="available_expr">object/@@my-ai-action-view/available|nothing</property>
      <property name="visible">True</property>
    </object>
  </object>
</object>

Approach B — Dynamic actions via IOmniaActionsProvider

Implement a utility that returns actions based on runtime conditions:

from imio.omnia.core.interfaces import IOmniaActionsProvider
from plone.protect.utils import addTokenToUrl
from zope.interface import implementer


@implementer(IOmniaActionsProvider)
class MyActionsProvider:

    def __call__(self, context, request):
        # Return an empty list to hide the actions, or a list of dicts.
        return [
            {
                "title": "My dynamic action",
                "description": "Does something smart.",
                "action": addTokenToUrl(
                    f"{context.absolute_url()}/@@my-action-view", request
                ),
                "selected": False,
                "icon": "cpu",
                "extra": {
                    "id": "plone-contentmenu-actions-my-action",
                    "separator": None,
                    "class": "actionicon-object_buttons-my-action",
                    "modal": "",  # empty string to open in a modal
                },
                "submenu": None,
            },
        ]

Register the utility in configure.zcml:

<utility
  provides="imio.omnia.core.interfaces.IOmniaActionsProvider"
  factory=".action.MyActionsProvider"
  name="my-actions"
/>

Using the API services

Two HTTP client services wrap the upstream Omnia APIs. Both are multi-adapters on (context, request) and automatically send x-imio-application and x-imio-municipality headers resolved from the registry and the IOrganizationIDProvider adapter.

IOmniaCoreAPIService

Wraps the Omnia Core AI agents API (/imio/omnia/core/v1/agents/).

Available methods:

  • expand_text(input, expansion_target=50)

  • improve_text(input)

  • reduce_text(input, reduction_target=30)

  • correct_text(input)

  • make_accessible(input)

  • translate_text(input, target_language)

  • suggest_titles(input)

  • convert_meeting_notes_to_minutes(meeting_name, meeting_notes)

  • categorize_content(input, vocabulary, unique=False)

  • deduce_metadata(input=None, image_url=None, image_file=None)

  • send(method, path, **kwargs) / post_json(path, payload) for raw calls

Usage:

from zope.component import getMultiAdapter
from imio.omnia.core.interfaces import IOmniaCoreAPIService

service = getMultiAdapter((context, request), IOmniaCoreAPIService)
result = service.improve_text("Le projet va bien.")
# result is a parsed JSON dict from the upstream API

IOmniaOpenAIService

Wraps the Omnia OpenAI-compatible gateway (/imio/omnia/openai/v1/).

Available methods:

  • list_models()

  • chat_completions(model, messages, stream=False, temperature=None, max_tokens=None, tools=None, tool_choice=None)

Usage:

from zope.component import getMultiAdapter
from imio.omnia.core.interfaces import IOmniaOpenAIService

service = getMultiAdapter((context, request), IOmniaOpenAIService)
result = service.chat_completions(
    model="Mistral Large",
    messages=[{"role": "user", "content": "Hello"}],
)

Streaming:

for chunk in service.chat_completions(
    model="Mistral Large",
    messages=[{"role": "user", "content": "Hello"}],
    stream=True,
):
    # Each chunk is a parsed JSON dict (SSE data frame)
    print(chunk)

Overriding organization ID resolution

The IOrganizationIDProvider adapter resolves which organization / municipality ID is sent with every API request. The default implementation reads from the registry (the organization_id setting).

To override the resolution for specific content types, register a more specific adapter:

from zope.component import adapter
from zope.interface import implementer
from imio.omnia.core.interfaces import IOrganizationIDProvider
from my.package.interfaces import IMyContentType


@adapter(IMyContentType)
@implementer(IOrganizationIDProvider)
class MyOrganizationIDProvider:

    def __init__(self, context):
        self.context = context

    def __call__(self):
        # Resolve the org ID from the content hierarchy
        return self.context.municipality_code

Register in configure.zcml:

<adapter factory=".adapters.MyOrganizationIDProvider" />

Zope’s adapter specificity ensures your adapter is used for IMyContentType objects while the default adapter handles everything else.

Using the proxy view (@@omnia-api)

The @@omnia-api view forwards browser JavaScript requests to the Omnia Core API, adding authentication headers server-side. This avoids exposing API credentials to the browser.

Requirements:

  • The enable_proxy setting must be True (disabled by default).

  • The caller must have the imio.omnia.core: Access Omnia API proxy permission (granted to Authenticated by default).

  • Requests must be POST with a JSON body.

URL pattern: <context_url>/@@omnia-api/<path> where <path> maps to the upstream API path (e.g. /v1/agents/improve-text).

JavaScript example:

const response = await fetch(
  `${portalUrl}/@@omnia-api/v1/agents/improve-text`,
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ input: selectedText }),
  }
);
const data = await response.json();

Using the streaming proxy (@@omnia-openai-api)

The @@omnia-openai-api view proxies browser requests to the OpenAI-compatible gateway and streams the response back as Server-Sent Events (SSE). This lets browser-side chat widgets stream completions without exposing API credentials or the upstream URL to the client.

Requirements:

  • The enable_openai_proxy setting must be True (disabled by default).

  • The caller must have the imio.omnia.core: Access Omnia OpenAI proxy permission (granted to Authenticated by default).

  • Requests must carry an HMAC Bearer token generated by imio.omnia.core.tokens.generate_token().

Projects that need anonymous access to @@omnia-openai-api can override the default role mapping in their own GenericSetup rolemap.xml by granting the imio.omnia.core: Access Omnia OpenAI proxy permission to Anonymous.

Token generation (server-side, e.g. in a viewlet):

from imio.omnia.core.tokens import generate_token

token = generate_token(context.portal_url())
# Pass this token to the browser as a JS variable

Tokens are HMAC-SHA256 signed using the Plone site keyring and expire after 2 hours. The proxy validates them on every request.

URL pattern: <context_url>/@@omnia-openai-api/<path> where <path> maps to the upstream API path (e.g. chat/completions).

JavaScript example:

const response = await fetch(
  `${portalUrl}/@@omnia-openai-api/chat/completions`,
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${token}`,
    },
    body: JSON.stringify({
      model: "Mistral Large",
      messages: [{ role: "user", content: "Hello" }],
      stream: true,
    }),
  }
);

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  for (const line of decoder.decode(value).split("\n")) {
    if (!line.startsWith("data: ")) continue;
    const data = line.slice(6);
    if (data === "[DONE]") break;
    const chunk = JSON.parse(data);
    process.stdout.write(chunk.choices?.[0]?.delta?.content ?? "");
  }
}

Available icons

The following icon names are registered in the Plone icon registry and can be used in icon_expr properties of portal actions:

Icon name

Description

omnia.ia.dark

Omnia IA (dark)

omnia.ia.light

Omnia IA (light)

omnia.monochrome.dark

Monochrome (dark)

omnia.monochrome.light

Monochrome (light)

omnia.picto.dark

Pictogram (dark)

omnia.picto.light

Pictogram (light)

omnia.logotype.dark

Logotype (dark)

omnia.logotype.light

Logotype (light)

omnia.imio.logotype.dark

iMio logotype (dark)

omnia.imio.logotype.light

iMio logotype (light)

The SVG files are served from ++plone++imio.omnia.core/.

Authors

License

The project is licensed under the GPLv2.

Contributors

Changelog

1.0a1 (2026-04-03)

  • Initial release. [duchenean]

  • Added OmniaCoreAPIService multi-adapter wrapping the Omnia Core API (/imio/omnia/core/v1/agents/): expand, improve, reduce, correct, translate, make accessible, suggest titles, convert meeting notes, categorize content, and extract metadata. [duchenean]

  • Added OmniaOpenAIService multi-adapter wrapping the OpenAI-compatible Omnia gateway (/imio/omnia/openai/v1/): model listing and chat completions with streaming SSE support. [duchenean]

  • Added shared Omnia control panel (@@omnia-ai-settings) with tabbed navigation extensible via omnia_controlpanel_tabs portal actions. [duchenean]

  • Added “AI assistant” content menu with dynamic action collection via IOmniaActionsProvider utilities. [duchenean]

  • Added IOrganizationIDProvider adapter interface for context-aware organization ID resolution. [duchenean]

  • Added environment variable to registry sync on Zope startup (OMNIA_CORE_API_URL, OMNIA_OPENAI_API_URL, OMNIA_APPLICATION_ID, OMNIA_ORGANIZATION_ID). [duchenean]

  • Added HMAC-signed token generation for securing browser-to-proxy communication. [duchenean]

  • Added IImioOmniaControlPanelFieldProvider interface for extending the control panel schema from downstream packages. [duchenean]

  • Added Omnia SVG branding icons. [duchenean]

  • Added i18n support (en, fr). [duchenean]

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

imio_omnia_core-1.0a1.tar.gz (135.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

imio_omnia_core-1.0a1-py3-none-any.whl (147.0 kB view details)

Uploaded Python 3

File details

Details for the file imio_omnia_core-1.0a1.tar.gz.

File metadata

  • Download URL: imio_omnia_core-1.0a1.tar.gz
  • Upload date:
  • Size: 135.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for imio_omnia_core-1.0a1.tar.gz
Algorithm Hash digest
SHA256 35a22d4e22bf585b001c510b4ce6161e1bd410c99217066c3e586fe1f6f7533b
MD5 106ca064c6f3ebabc5fb389bf9eb3f69
BLAKE2b-256 237078af1a18d28a5df97c3c71c9795e56aacedd48fe6b61a70b03d04978f96a

See more details on using hashes here.

File details

Details for the file imio_omnia_core-1.0a1-py3-none-any.whl.

File metadata

File hashes

Hashes for imio_omnia_core-1.0a1-py3-none-any.whl
Algorithm Hash digest
SHA256 8dd6f69a36a3068e08836e10f15751f0c336c6423fc95af8fd1a07a876ee8f46
MD5 0eefaf2d249c679213c713b3403e8d72
BLAKE2b-256 314844e6b1b2987618b6bec0d1d899d52b5506d6a331cbdb08929b755a6fcf6b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page