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
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 (
<h2>,<h3>,<h4>) and paragraphs - Not include
<h1>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
Ai. 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-keyHTTP header. The model is determined by the deployment name in the endpoint URL. Nomodelparameter is sent in the request body. - OpenAI / ChatGPT (
mode = openai) - Authenticates via
Authorization: Bearer <key>header. Themodelparameter (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 singlepromptstring. This mode exists for backwards compatibility.
Key files
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
-
Classes/Controller/ Ai Text Controller. php - Routes
-
/ok-ai-writer/generateand/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,modelfrom extension configuration - In devMode: client-sent credentials override server config
- Supports both Azure (
api-keyheader) and OpenAI (Bearertoken) - Supports both
messages[](conversation) andprompt(legacy) input - Uses
temperature: 0.3andmax_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
-
Classes/Middleware/ Add Language Labels. 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 Configuration/. Each uses
CKEDITOR.plugins.add() with editor.addCommand() and
editor.ui.addButton() for toolbar integration.
- AiText/plugin.js
- Registers the
AiTexttoolbar button. On click, creates a modal overlay with a chat-style dialog. Handles conversation state, API calls via XHR, token tracking, and content insertion viaeditor.insertHtml(). - AiTranslate/plugin.js
- Registers the
AiTranslatetoolbar 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
LoremIpsumtoolbar 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
<language-code>.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