feat: #3631526 Generate into the site's own Image and SVG Image media types, keeping the AI tabs

Issue: #3631526

AI Media no longer needs AI media types of its own. Each generator saves into one of the site's media types, and the Media Library keeps its AI tabs: Image, Generate image with AI, SVG Image, Generate SVG with AI.

What changed

  • One media type each. Generated images are saved as the site's Image media, generated SVGs as its SVG Image media (core Image source, svg field), generated videos as its Video media (core Video file source). The ai_generated_image and ai_generated_video media sources, AiGeneratedSourceInterface, AiGeneratedSourceTrait and their schema are removed. Generators declare core sources in #[AiMediaGenerator(media_source: ...)].
  • Mapping on the settings page. New setting generators.<id>.media_type (defaults: image, svg_image, video). The Generators table has a Media type column listing only compatible types (by source, and by the field's extensions overlapping the generator's); None means no generator. A missing, non-existent or incompatible mapping simply means no tab and no generator. One media type holds one generator (validated). Older settings read the defaults through the existing settings-defaults override; no update hooks. AiMediaGeneration::generatorOf(MediaTypeInterface) returns the offered generator mapped to a type, or NULL.
  • Media Library. Core's image and video_file sources (any source a generator names, while it is on core's form) use AiMediaGenerateForm, which now extends core's FileUploadForm: without ai_media=1 it is core's upload form; with it, and a mapped generator, the input is the generator exactly as before (description, examples, options, disclosure, cost line, Generate with core HTMX, review with name and alt, Generate again, Remove, Save). The mode is kept in form state across AJAX and HTMX rebuilds. ai_media is an extra query parameter, outside MediaLibraryState's hash, which fromRequest() keeps. Because a module may own the upload form instead (DropzoneJS replaces it for image and video_file on Varbase), the Media Library UI builder is swapped (service provider, core's arguments kept) to build AI Media's form in AI mode for a type with a generator, and the type's own form otherwise. Both FileUploadForm and MediaLibraryUiBuilder are @internal; the phpstan notices are ignored inline.
  • AI tabs. In preprocess_links__media_library_menu, each AI tab is inserted right after its media type's tab, linking to that tab's URL plus ai_media=1, and is the active tab (with "(selected)") in AI mode. Same rules as before: tabs setting, generator offered and its tab setting, permission, configured (admins see the setup message), New badge; tab_position: end puts them last in the generators' order.
  • Core's Add media page (/media/add/image, /media/add/svg_image, /media/add/video) keeps core's file widget and adds the generator below it, "✦ Or generate with AI". Generating fills the source field, name and alt; core's Save keeps it, and a plain upload still saves without a description. Types without a generator are untouched.
  • Generate media with AI and /media/add?ai=1 list only types with a mapped, offered generator the user can create.
  • SVG rules (selectable SVG thumbnails, image style fallback) apply to the SVG generator's mapped type: the thumbnail rule is now a per-bundle definition, not a change to every media type.
  • Config actions and recipe. New setAiMediaTypes action (image: image, svg: svg_image); unknown or incompatible types fail. recipes/ai_media_svg_example now creates (or keeps) svg_image with field_media_svg_image, maps SVG to it, and its page field is field_animated_svg.
  • Docs updated, including a new Add media screenshot.

Upgrading from 1.0.0-alpha1

There is no migration while the module is pre-release. Sites on alpha1 must delete their two AI media types (those using "Image generated with AI" and "Video generated with AI"), and their media, before updating, then check the Media type of each generator on the settings page.

Tested

  • New Kernel tests: MediaTypeMappingTest (defaults, older settings, None/missing/incompatible/off, lighter generator wins, compatible types, config action) and MediaLibraryAiTabsTest (tab insertion and order, active state in AI mode, end position and hiding rules, upload vs AI mode and mode kept on rebuild, Add media stacked form and permission, and the AI tab opening the generator when another module owns the upload form). SvgMediaTest covers SVG thumbnails on the mapped type only. MediaTypeOrderTest removed. 80 Kernel tests pass locally on Drupal 11.4.11.
  • phpcs (Drupal, DrupalPractice) clean on PHP; phpstan level 0 clean apart from the new static() notices the template ignores; cspell adds no new words; eslint on the step definitions clean.
  • varbase-e2e free lane (Echo provider) on a fresh Drupal 11.4 standard site with the updated fixtures recipe: 42 scenarios, 784 steps pass.
  • Browser at 1440 px: the four tabs in order; an image and an SVG generated from the AI tabs and saved as Image and SVG Image, with the AI tab still selected after Save; the Image tab still uploads; /media/add/image shows the upload with "Or generate with AI" below, generates, and saves; the settings page lists only compatible types.

Follow-up for drupal/varbase_ai_media_base

Drop its image_ai_gen / svg_ai_gen media types, set the mapping (setAiMediaTypes: { image: image, svg: svg_image }), keep svg_image allowed in Full HTML Insert Media, and grant create image media / create svg_image media with generate media with ai.

AI-Generated: Yes. Drafted and tested with Claude Code.

Checkpoints:

  • File an issue
  • Addition/Change/Update/Fix
  • Testing to ensure no regression
  • Automated unit testing coverage
  • Automated functional testing coverage
  • UX/UI designer responsibilities
  • Readability
  • Accessibility
  • Performance
  • Security
  • Documentation
  • Reviewed by a human
  • Code review by maintainers
  • Full testing and approval
  • Credit contributors
  • Review with the product owner
  • Release notes snippet
  • Release

🤖 Generated with Claude Code

Edited by Rajab Natshah

Merge request reports

Loading
Loading