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
Copied!

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

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 <key> 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 

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
Copied!

Component details 

AiTextController 

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

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 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 <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

Oliver Kroener <ok@oliver-kroener.de>