.. include:: ../../Includes.txt
.. _ai-label-integration:
=====================
AI Label integration
=====================
Integrate your TYPO3 extension with AI Foundation **AI Label** so AI-generated
content is recorded, reviewable in the backend module, and labelled on the
frontend when rules require it.
Prerequisites
-------------
* AI Foundation (``ns_t3af``) installed
* Your extension persists content on applicable tables (``pages``,
``tt_content``, ``sys_file_metadata``, or tables registered in AI Label
settings)
* AI requests go through AI Foundation (``AiServiceInterface``) so capture
correlation ids are available
Overview
--------
Three integration points:
1. **Capture** (automatic) — AI Foundation stores each generation in
``tx_nst3af_ailabel_generation``.
2. **Bind** (your code) — when you save a record, link the generation to that
row.
3. **Optional direct origin** — report involvement without a prior capture.
Applicable tables
-----------------
Built-in:
* ``pages``
* ``tt_content``
* ``sys_file_metadata``
Additional tables: add via **AI Label → Settings → Applicable tables**,
``$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['ns_t3af']['ailabelApplicableTables']``,
or listen to ``CollectApplicableTablesEvent``:
.. code-block:: php
use NITSAN\NsT3AF\AiLabel\Event\CollectApplicableTablesEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
#[AsEventListener]
final class RegisterNewsTableForAiLabel
{
public function __invoke(CollectApplicableTablesEvent $event): void
{
$event->addTable('tx_news_domain_model_news');
}
}
Schema columns for extra tables are added by ``ApplicableTableSchemaListener``
as ``CREATE TABLE`` fragments. After changing extra tables, run
**Maintenance → Analyze Database Structure** so the columns exist.
Bind after save (recommended)
-----------------------------
Use ``AiLabelBindHelper`` at the point where your extension knows the final
record uid (after DataHandler or repository save):
.. code-block:: php
use NITSAN\NsT3AF\AiLabel\Service\AiLabelBindHelper;
// After DataHandler / repository save — pass your extension key as $source
AiLabelBindHelper::bindContentRecord($uid, 'my_extension');
AiLabelBindHelper::bindPageRecord($uid, 'my_extension');
AiLabelBindHelper::bindFileMetadata($metaUid, 'my_extension');
AiLabelBindHelper::bindRecord('tx_news_domain_model_news', $uid, 'my_extension');
Every child bind stores involvement ``ai_generated``. Editors may change that
later in the AI Label module. Visitor badges still require confirmation.
Parameters:
* ``$uid`` — live record uid (file metadata uid for ``bindFileMetadata``)
* ``$source`` — short extension identifier stored as recording source (use your
extension key, for example ``my_extension``)
* ``$altTextOnly`` (``bindFileMetadata`` only) — skip bind when only file
**metadata text** changed (alt / title / description). Do **not** use this
flag when the binary media itself was AI-generated (images, TTS audio).
ns_t3aa metadata writes always pass ``altTextOnly: true`` so photos are not
stamped as AI media when only accessibility text was generated.
If no capture correlation id is in the current request (for example a follow-up
HTTP request after async file processing), the helper writes origin with
``recordOrigin()`` instead of ``bindGeneration()``.
Direct origin reporting
-------------------------
When capture correlation is unavailable:
.. code-block:: php
use NITSAN\NsT3AF\AiLabel\Domain\Involvement;
use NITSAN\NsT3AF\Api\AiLabelRecorderInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
$recorder = GeneralUtility::makeInstance(AiLabelRecorderInterface::class);
$recorder->recordOrigin(
'tt_content',
$uid,
Involvement::AiGenerated,
'my_extension',
aiSystem: 'gpt-4',
aiVendor: 'openai',
);
Public API interface: ``Classes/Api/AiLabelRecorderInterface.php``.
Convenience methods (also reset confirmation):
.. code-block:: php
$recorder->markGenerated('tt_content', $uid, 'my_extension');
$recorder->markModified('tt_content', $uid, 'my_extension');
$recorder->clearInvolvement('tt_content', $uid, 'my_extension');
These throw ``\InvalidArgumentException`` when ``$table`` is not an applicable
table.
Involvement values
------------------
.. list-table::
:header-rows: 1
:widths: 30 70
* - Value
- Use when
* - ``not_reviewed``
- Default; no editor decision
* - ``no_ai``
- Editor asserts no AI involvement
* - ``ai_generated``
- Content substantially created by AI
* - ``ai_modified``
- AI changed existing content
* - ``origin_unknown``
- Role of AI unclear
* - ``suggestion``
- System detected/suggested; awaiting person
Frontend rendering
------------------
Include the TypoScript that hooks into ``fluid_styled_content`` (visitor badges
on content elements). Listing a Site Set as a dependency does **not** load its
TypoScript automatically — import the file as well.
**Site Set (TYPO3 v13.4+):** add ``nitsan/ns-t3af-label`` to your sitepackage
set's ``dependencies``, **and** import the setup:
.. code-block:: yaml
dependencies:
- nitsan/ns-t3af-label
.. code-block:: typoscript
@import 'EXT:ns_t3af/Configuration/TypoScript/setup.typoscript'
**Classic TypoScript templates:** include static template
“AI Foundation labels”, or ``@import`` the same file.
Fluid badge (namespace ``ail`` is registered by AI Foundation):
.. code-block:: html
Fluid Styled Content ships an image partial override that already calls
```` on each rendered file.
Assign state without rendering the badge:
.. code-block:: html
…
DataProcessor alias ``nst3af-label`` (variable ``labelState`` by default):
.. code-block:: typoscript
tt_content {
dataProcessing {
1550 = nst3af-label
1550 {
as = labelState
}
}
}
Or inject ``FrontendLabelRenderer`` / ``FrontendLabelStateFactory`` in PHP.
Auto-confirm
------------
Settings tab drives auto-confirm via ``AutoConfirmSettingsService``:
* **Confirm when OUR extension recorded it** — ``ns_t3ai``, ``ns_t3aa``,
``ns_t3af`` for media, text, or both.
* **Confirm when DETECTED on upload** — recording source ``detected_upload``
(IPTC Digital Source Type already signals AI).
* **Hold** — public-interest text stays manual (also EXTCONF
``ailabelHoldList``).
EXTCONF ``ailabelAutoConfirmSources`` remains an extra allow-list (used even
when the Settings own-toggle is off). Auto-confirm never fills the responsible
person field.
Check ``AutoConfirmSettingsServiceTest`` and ``AiLabelRuleMatrixTest`` before
relying on auto-confirm in production.
Module settings consumed on the frontend
----------------------------------------
``AiLabelSettingsService`` drives visitor badge appearance:
* ``labelSize``, ``labelWording`` — passed to ``FrontendLabelRenderer``
(``show_site_language`` = text only; ``icon_only`` = icon only)
* ``markImageFile === overlay`` — enables image partial wrapper and overlay badge
* ``markImageFile === written_in`` — stamps processed image copies (ImageMagick)
* ``labelPosition`` — overlay CSS class and processed-file stamp corner
* ``machineReadable`` — ``iptc`` / ``iptc_jsonld`` / ``off``
* ``labelUnknownOrigin`` — visitor badge for confirmed unknown-origin media
* ``secondInfoLayer`` — expandable ```` on the badge with
human-readable involvement wording only (machine reason codes stay in
evidence export / backend lists)
* When ``markImageFile === overlay``, media CTypes (``image``, ``textmedia``,
``textpic``) skip the content-element After/All drop-in badge so the image
overlay is the single visitor mark
Deep links
----------
File list module: ``ProcessFileListActionsListener`` adds **AI Label** link to
the media tab with folder pre-selected.
Maintainer references
---------------------
* Agent summary: ``context/features/ai-label.md``
* Deep spec: ``context/specs/FEATURE_AiLabel.md``
* Unit tests: ``Tests/Unit/AiLabel/``
Run tests:
.. code-block:: bash
cd packages/ns_t3af
composer test -- --filter AiLabel