Issue #3586602: Add a field widget action base class and text plugin to run...

Support Automator Chain trigger with Field Widget Actions

Closes #3586602 (closed)

Problem / Motivation

Single-automator flows can hit context-window limits and can't express multi-turn workflows. Automator Chains solve this, but there was no way to trigger a chain from an entity edit form without a page reload. Field Widget Actions (FWA) already triggers single automators from a widget button; this MR adds the same capability for Automator Chains, as requested by the maintainer: "the work should be continued here in the same way it is done for automators already", with all changes in the AI module only.

A site builder can now attach a button to a text field widget that: takes values from configured source fields on the entity (any field type, e.g. a PDF file field per the issue screenshot), feeds them to the chain's required input fields, runs the chain, and writes the configured output field's generated value into the widget — via AJAX, no reload.

What changed

File Change
src/Plugin/FieldWidgetAction/AutomatorChainBaseAction.php New. Abstract base class holding all chain-action logic: chain-aware config form, availability filtering, chain execution and output handling.
src/Plugin/FieldWidgetAction/AutomatorChainText.php New. Concrete, attribute-only plugin (automator_chain_text) for text targets — same widget/field types as the existing automator_text action.
src/Plugin/FieldWidgetAction/AutomatorBaseAction.php Fix. The #states visibility selector for the settings section was built from getPluginId(), but FWA keys action subforms by action ID (a UUID for actions added through the UI), so the "Enabled" toggle never showed/hid the settings. Now uses $action_id ?: getPluginId(). Benefits all existing automator actions.
config/schema/ai_automators.schema.yml New schema. Shared field_widget_action_automator_chain_base type + a two-line entry for automator_chain_text.
tests/src/Kernel/Plugin/FieldWidgetAction/AutomatorChainActionTest.php New. 5 kernel test cases (see Testing below).

Architectural decisions

  1. Base class + one Text example, not 30 variants. Per maintainer feedback on the issue, the MVP ships an extensible abstract base and one concrete example so contributors can add whatever variant they need. A new variant is an attribute-only subclass (mirroring the matching single-automator plugin, e.g. Boolean), overriding transformFormInput()/setFormInput() only when the widget's user-input shape differs from storage shape, plus a two-line schema entry extending the shared schema type. The extension contract is documented in the base class docblock.
  2. AutomatorChainBaseAction extends AutomatorBaseAction. The chain action reuses the entire proven downstream half of the automator action unchanged — button wiring (actionButton()/runAutomatorSubmit()), the AJAX rebuild callback, user-input population (setFormInput()/transformFormInput()), and multi-value widget scaffolding (updateItemsCount()). Only the upstream half is overridden: configuration (chain + mappings instead of automator_id — the config form calls FieldWidgetActionBase::buildConfigurationForm() directly to avoid inheriting the required automator_id select) and value production (populateAutomatorValues() runs the chain via the existing ai_automator.automate service instead of entityModifier->saveEntity()). This also means future per-type overrides behave identically for both action families.
  3. #states-driven per-chain settings instead of "save and re-open". The issue's prototype required saving and re-opening the settings form to populate the input/output selects after picking a chain. Nested AJAX inside the (already AJAX-driven) form display settings subform is fragile, so instead one settings section per chain is rendered upfront and toggled visible by #states on the chain select — everything is configurable in one pass. This mirrors the existing prior art in AiAutomatorsCKEditor::buildConfigurationForm(). Trade-off: settings for previewed-but-unselected chains persist in config (harmless — runtime reads only the selected chain's entry; the schema is a keyed sequence so it validates). Pruning them would need a plugin-level submit hook in the standalone FWA module.
  4. Per-required-field input mapping (superset of the prototype). Instead of a single source→input pair, the form renders one source-field select per required chain field (via Automate::getRequiredFields()), so chains with multiple inputs work. For single-input chains this collapses to exactly the prototype's UI. Source options include all form-configurable fields on the host bundle regardless of type — deliberately unfiltered, since the reported use case feeds a PDF file field into the chain; the value is passed through as FieldItemList::getValue(), which Automate::run() accepts for any type-compatible pair.
  5. Output side is type-filtered; values are massaged. Output-field options come from Automate::getAutomatedFields($chain, $plugin_field_types) — filtered by the plugin definition's field_types, so the output is always a shape the inherited transformFormInput() understands. isAvailable() uses the same filter: the action is only offered when at least one chain has a compatible automated output. Because chain output and target field can be different-but-compatible types (e.g. text_longstring), massageOutputValues() strips item properties the target field's storage doesn't define (e.g. format) and respects the target's cardinality.
  6. Graceful degradation. A deleted chain type or de-automated output field logs a warning and shows a messenger error instead of a fatal — parity with the parent class's deleted-automator handling. Automate::run() failures (including provider exceptions) are caught the same way.

Config shape (widget third-party settings, per action UUID)

settings: automator_chain_type: product_page_generator chain_settings: product_page_generator: input_mapping: field_spec_pdf_input: field_product_spec # chain input => host field output_field: field_product_page_copy

Testing

Automated: vendor/bin/phpunit -c <config> modules/ai_automators/tests/src/Kernel/Plugin/FieldWidgetAction/AutomatorChainActionTest.php — covers config form options and UUID-keyed #states selectors; chain filtering by output type / isAvailable(); a full deterministic chain run through the ai_test echo provider asserting the output lands in $form_state->getUserInput() and the disposable automator_chain entity is cleaned up; output massaging (format stripping + cardinality); and graceful handling of a deleted chain. PHPCS (Drupal/DrupalPractice) and PHPStan (module config) are clean; the pre-existing FWA kernel suite passes unchanged.

Manual (UI):

  1. Enable ai_automators and field_widget_actions, configure a working chat provider, and clear caches.
  2. Under Configuration → AI → Automator Chain types, create a chain type (e.g. Product Page Generator). Via Manage fields add a required input field (e.g. Chain Input, formatted long text) and an output field (Chain Output, plain text — any of string, string_long, text, text_long, text_with_summary). Edit the output field, enable an AI Automator on it (e.g. LLM: Text), base field = Chain Input, prompt using {{ context }}.
  3. On a content type's Manage form display, open the widget settings (gear) of a plain-text target field → Field Widget Actions → add Automator Chain Text Suggestion.
  4. Check Enable Automator Chains — verify the Automator Chain section appears only when checked (this exercises the #states fix). Pick the chain — verify its settings section appears immediately, without saving and re-opening. Map the source field(s) and pick the output field. Update, save.
  5. Edit a node, fill the source field, click the button: the target field is filled via AJAX with the chain's output, no page reload, and no automator_chain entities are left behind.
  6. Edge cases: a chain whose only automated output is a non-text field must not appear in the chain select (and with no compatible chain the action is not rendered at all); adding a second required field to the chain shows a second source select; deleting the chain type and clicking the button shows a friendly error and logs a warning (no WSOD); with a text_long chain output and a plain-text target, the value lands cleanly with no format leakage; verify an existing Automator Text Suggestion action's settings section now also toggles correctly with its Enabled checkbox (parent fix).

Follow-ups / notes for maintainers

  • The ~30 existing automator FWA plugins ship no per-plugin settings schema (settings.automator_id falls through to the base type) — separate issue; a shared schema type would fix all at once.
  • Pruning stale per-chain settings on chain switch would require a plugin submit hook in the standalone field_widget_actions module.
  • Additional field-type variants (Boolean, List*, File/Image, …) are intended as follow-ups; if many land, consider a trait so chain variants can extend each type's concrete automator class and inherit its transformFormInput().
  • Automate::run() performs no access checks — identical exposure to the existing automator FWA buttons and the CKEditor chain integration.

Merge request reports

Loading