.. include:: /Includes.rst.txt
.. _architecture:
============
Architecture
============
This section describes the internal architecture of the AI Writer extension
for developers who want to understand, extend, or debug the extension.
Request flow
============
.. code-block:: text
Browser (CKEditor Plugin)
│
│ User types prompt in the AI dialog
│
▼
TYPO3 AJAX Route
POST /typo3/ajax/ok-ai-writer/generate
Body: { messages[] } (production mode)
Body: { endpoint, apikey, mode, (dev mode — optional overrides)
model, messages[] }
│
▼
AiTextController::generateAction()
│ Reads extension configuration (mode, apiUrl, apiKey, model)
│ In devMode: client values override server config
│ Prepends SEO system prompt
│
├── mode=azure ──▶ Azure OpenAI API (api-key header)
│
└── mode=openai ──▶ OpenAI API (Bearer token + model in body)
│
│ Returns: choices[].message.content (HTML)
│ usage.prompt_tokens / completion_tokens
│
▼
Response flows back to CKEditor
│ Displayed in preview area
│ Added to conversation history
▼
Editor clicks "Insert into Editor"
Content inserted at cursor position
.. note::
The TYPO3 backend acts as a **proxy** between the browser and the AI
API. This avoids CORS restrictions. In production mode (``devMode``
disabled), API credentials never leave the server.
System prompt
=============
The controller prepends a system message (identical for both providers) that
instructs the AI to:
- Generate well-structured, SEO-optimized HTML content
- Use semantic headings (``
``, ````, ````) and paragraphs
- **Not** include ```` tags (the page already has one)
- **Not** include markdown formatting, code fences, or explanations
- Return only clean HTML
.. important::
The system prompt is defined in
:php:`AiTextController::generateAction()`. If you need to customize
the AI's behavior (e.g. different tone, language, or formatting rules),
modify the ``$systemMessage`` array in that method.
Provider modes
==============
The controller supports two AI providers, selected via the ``mode``
extension configuration:
**Azure OpenAI** (``mode = azure``)
Authenticates via ``api-key`` HTTP header. The model is determined by
the deployment name in the endpoint URL. No ``model`` parameter is sent
in the request body.
**OpenAI / ChatGPT** (``mode = openai``)
Authenticates via ``Authorization: Bearer `` header. The ``model``
parameter (e.g. ``gpt-4o``) is included in the JSON request body.
Conversation mode
=================
The plugin supports two message modes:
**Conversation mode** (default)
The browser sends the full ``messages[]`` array (user + assistant turns).
The controller prepends the system prompt and forwards the entire history
to the AI API. This enables iterative refinement.
**Legacy single-prompt mode**
If no ``messages[]`` array is sent, the controller falls back to using
a single ``prompt`` string. This mode exists for backwards compatibility.
Key files
=========
.. code-block:: text
Classes/
├── Controller/
│ └── AiTextController.php AJAX endpoint — proxies to AI API
└── Middleware/
└── AddLanguageLabels.php Injects XLIFF labels + config into backend JS
Configuration/
├── Backend/
│ └── AjaxRoutes.php Registers /ok-ai-writer/generate + /translate
├── RequestMiddlewares.php Registers AddLanguageLabels middleware
├── RTE/
│ └── Default.yaml CKEditor 4 external plugin registration
└── Services.yaml Symfony DI autowiring
Resources/
├── Private/Language/
│ ├── locallang.xlf English labels
│ └── de.locallang.xlf German labels
└── Public/CKEditor/Plugins/
├── AiText/plugin.js CKEditor 4 AI Text plugin
├── AiTranslate/plugin.js CKEditor 4 AI Translate plugin
└── LoremIpsum/plugin.js CKEditor 4 Lorem Ipsum plugin
Component details
=================
AiTextController
----------------
:File: :file:`Classes/Controller/AiTextController.php`
:Routes: ``/ok-ai-writer/generate`` and ``/ok-ai-writer/translate`` (AJAX, POST)
Receives the conversation messages from the browser. Reads API credentials
from extension configuration (or from the request in devMode). Prepends the
SEO system prompt (for generate) or translation system prompt (for translate),
forwards to the configured AI provider via Guzzle HTTP client, and returns
the JSON response.
- Reads ``devMode``, ``mode``, ``apiUrl``, ``apiKey``, ``model`` from
extension configuration
- In devMode: client-sent credentials override server config
- Supports both Azure (``api-key`` header) and OpenAI (``Bearer`` token)
- Supports both ``messages[]`` (conversation) and ``prompt`` (legacy) input
- Uses ``temperature: 0.3`` and ``max_tokens: 2000`` (4000 for translate)
- Timeout: 120 seconds
- Returns API errors as HTTP 502 with the first 500 characters of the
error response
AddLanguageLabels middleware
----------------------------
:File: :file:`Classes/Middleware/AddLanguageLabels.php`
:Stack: Backend middleware
Injects the extension's XLIFF translation labels into the TYPO3 backend
page renderer so they are available via ``TYPO3.lang`` in JavaScript.
Also injects extension configuration as inline settings
(``TYPO3.settings.ok_ai_writer``) with blinded credential values.
CKEditor plugins
-----------------
All three plugins are self-contained CKEditor 4 IIFEs registered via
``externalPlugins`` in :file:`Configuration/RTE/Default.yaml`. Each uses
``CKEDITOR.plugins.add()`` with ``editor.addCommand()`` and
``editor.ui.addButton()`` for toolbar integration.
**AiText/plugin.js**
Registers the ``AiText`` toolbar button. On click, creates a modal overlay
with a chat-style dialog. Handles conversation state, API calls via XHR,
token tracking, and content insertion via ``editor.insertHtml()``.
**AiTranslate/plugin.js**
Registers the ``AiTranslate`` toolbar button. On click, opens a language
selector dialog. Sends the full editor content to the translate endpoint
and replaces it with the translated HTML.
**LoremIpsum/plugin.js**
Registers the ``LoremIpsum`` toolbar button. On click, opens a dialog
to choose the number of paragraphs (1–20) and inserts Lorem Ipsum text
at the cursor position.
Localization
============
The extension ships with two language files:
================================================= ===========
File Language
================================================= ===========
``Resources/Private/Language/locallang.xlf`` English
``Resources/Private/Language/de.locallang.xlf`` German
================================================= ===========
Labels are loaded into the backend via the ``AddLanguageLabels`` middleware
and accessed in JavaScript through ``TYPO3.lang['label.key']``.
To add a new translation, create a file named
``.locallang.xlf`` (e.g. ``fr.locallang.xlf``) following
the XLIFF 1.2 format.
License
=======
This extension is licensed under the
`GNU General Public License v2.0 or later `__.
:Author: `Oliver Kroener `__