Skip to content

Introduce prompt management - #949

Closed
chrfritsch wants to merge 30 commits into
8.4.xfrom
prompt-management
Closed

Introduce prompt management#949
chrfritsch wants to merge 30 commits into
8.4.xfrom
prompt-management

Conversation

@chrfritsch

Copy link
Copy Markdown
Member

Make sure these boxes are checked before submitting your pull request - thank you!

  • All coding styles are fulfilled. (How to check for cs issues?)
  • All tests are running locally. (How to run the test?)
  • Necessary update hooks are provided.
  • User roles have correct access for new introduced permission.
  • Every thunder module has a README.md in its root. Follow this guidelines, but we don't need every topic.
  • Code is covered with well-balanced amount of inline comments.
  • New features or changes are documented.

If you are really awesome, then your feature is covered by additional tests. Well done!

Christian Fritsch and others added 30 commits August 12, 2026 13:16
* Add prompt testing

* fix

* added save button and model selector

* add suggestion field widget

* add dependencies

* Fix PHPStan errors in thunder_ai_prompt_management

Resolves the 19 static analysis errors that failed the deprecation
testing CI job (missing return types, undefined methods/properties on
generic entity interfaces, an unhandled match branch, and a return
type mismatch).

* Fix coding style violations in thunder_ai_prompt_management

Resolves the errors from the drupal-coder coding style check: a
same-namespace use statement, missing match() spacing, an over-long
line, undocumented parameters, and an unused import.

* Require drupal/field_widget_actions ^1.4 explicitly

field_widget_actions was only a transitive dependency, so a fresh
composer resolve (this distribution ships no composer.lock) could
land on 1.3.0, which lacks the #validate/clearErrorsForAction wiring
that AiTaskSuggestion relies on to suppress unrelated required-field
errors on AJAX suggestion requests. That is what broke the "kernel"
CI job: AiTaskSuggestionTest asserted on a #validate key that 1.3.0
never sets.

---------

Co-authored-by: Christian Fritsch <christian.fritsch@burda.com>
Optional chaining (?.) isn't supported by the project's pinned
eslint/parser, causing a parse error that hid the further issues
below it. Replaced it with plain && checks, named the wrapping IIFE,
and renamed the summary callback's parameter to stop it shadowing
the outer attach(context) parameter.
CI's prettier defaults to arrowParens: always (my local
eslint-config-drupal bundles an older prettier defaulting to
"avoid"), so the single-param arrow needs parens.
- Fix a real int/string type-mismatch bug in
  AIPromptAccessControlHandler::checkAccess() (entity->getOwnerId()
  is int, account->id() is string, so the strict === always failed
  for real users) - caught after fixing the test below.
- Fix AIPromptAccessControlHandlerTest to not let its "own" prompt
  owner become uid 1, which bypasses access checks as the superuser
  and made the update/delete own-vs-any assertions vacuous.
- Guard AIPromptTestForm against a stale entity_context referencing
  an uninstalled/renamed entity type (getDefinition() no longer
  throws).
- Replace leftover "example" scaffold text in AiTaskForm messages and
  AiTask's property docblocks with actual AI task wording.
- Switch AIPromptForm to AutowireTrait + promoted properties instead
  of a manual constructor/create().
- Extract the "type.bundle" string decoding shared by
  EntityContextWidget and AIPromptTestForm into one static helper.
- Memoize AiTaskSuggestion::loadPrompts() per task/type/bundle to
  avoid re-querying once per delta on multi-value fields.
- Switch form.js to an arrow-function IIFE per JS conventions.
- Condense multi-line rationale comments down to one line per the
  project's comment convention.
The drupal-coder check fails on any phpcs finding, warnings included,
so the condensed one-line comments from the previous commit still
needed trimming to stay under 80 characters.
…957)

* Add ThunderPromptSource: ai_prompt_content-backed prompt source seam

Implements ai_chatbot_assistant_ui's PromptSourceInterface against this
module's ai_prompt_content entity, grouped by ai_task and filtered by the
entity_context field (type.bundle / type.* values). Swapped in via
ThunderAiPromptManagementServiceProvider::alter(), guarded by
hasDefinition() so this module carries no hard dependency on the chatbot
module.

getSlashGroups()/getSuggestions() take a CacheableMetadata argument and add
their own tags via addCacheableDependency() as they build the response,
matching the interface signature after the seam's cacheability rework -
the block and controllers own route/session.exists/user.permissions
themselves, since those apply regardless of which provider is active.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Fix CI failures on ThunderPromptSource

- phpcs: capitalize the ThunderPromptSourceTest doc comment that starts
  with a method name.
- phpstan: read entity_context values via getValue()['value'], matching
  EntityContextWidget's existing pattern, instead of the untyped ->value
  property access that FieldItemInterface doesn't declare.
- phpstan: baseline the "unknown interface" finding for
  PromptSourceInterface - ai_chatbot_assistant_ui is an optional
  integration with no hard composer dependency, same as the existing
  entity_browser baseline entries.

* Require ai_chatbot_assistant_ui as a dev dependency for phpstan

interface.notFound is non-ignorable in phpstan, so the earlier baseline
entry for PromptSourceInterface never actually worked. Add the module
to require-dev (1.0.x-dev, same pattern as thunder_testing_demo - it
has no tagged release yet) so the interface is resolvable, and drop
the baseline entry now that the error is gone at the source.

This module stays a dev-only/soft dependency: it's absent from
require and from thunder_ai_prompt_management's .info.yml, so
production installs are unaffected; only test/static-analysis
tooling needs it on disk.

* Enable entity_blueprint in ThunderPromptSourceTest

thunder_ai_prompt_management's entity_context_prompt_builder service
autowires entity_blueprint's BlueprintSchemaBuilderInterface. Enabling
thunder_ai_prompt_management without entity_blueprint (and its own
field/text/filter/entity_test dependencies - the same set
EntityContextPromptBuilderTest already enables) leaves that interface
unbound, so the container fails to compile for every test method.

---------

Co-authored-by: Christian Fritsch <christian.fritsch@burda.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The "type.bundle"/"type.*" encoding, decoding, grouping, and matching
rules were reimplemented independently in EntityContextWidget,
AIPromptTestForm, ThunderPromptSource, and AiTaskSuggestion, already
drifting into three different styles. Move the shared logic into one
dependency-free EntityContext class and delegate to it from all four
call sites; no behavior change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Consolidate the entity_context field-value extraction loop, still
  duplicated in three call sites, into EntityContext::valuesFromField().
- Fill in the info.yml description/package placeholders.
- Add declare(strict_types=1) to the .module file, matching the rest
  of the module.
- Use match() in AIPromptForm::save(), matching AiTaskForm's pattern.
- Mark ThunderAiPromptManagementServiceProvider final, matching every
  other single-implementation class in the module.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Matches the PHP attribute already used elsewhere in the module
(AiTaskSuggestion) instead of the older docblock annotation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
EntityContextWidget::currentSelections(), ThunderPromptSource::
matchesContext(), and AIPromptTestForm::allowedContexts() each added
no logic of their own beyond a single delegating call into
EntityContext. Inline them at their call sites.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* Ai prompt list filters.

* Ai prompt list filters test fix.

* Ai prompt list filters test fix.

* Ai prompt list filters test fix.
Rebuilds the AI Prompts admin page as a proper View (matching the
Content view): sortable columns, native bulk operations wired to the
existing delete/save actions, and Gin's status marker styling for
free. Adds filters for label, prompt type, and model (the entity
reference and model filters need small custom Views plugins since the
base fields aren't attached Field API fields).

AIPromptListBuilder now only supplies row operations (Edit/Delete/Test
prompt), since Views owns the listing itself; the custom
AIPromptListFilterForm is fully superseded and removed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- AiTask entity's own label/label_collection read "AiTask"/"AiTasks"
  (no space) while everywhere else referring to it said "AI Task" -
  fix the entity labels, its route/menu titles, and the config schema
  label to match.
- Capitalize "ai" to "AI" throughout: permission titles, action link
  titles, route titles, entity label_singular/plural/label_count,
  status/log messages, field descriptions, and class doc comments.
  Also fixes AIPrompt's label_count, which reused the plural string
  for the singular form.
- Rename the "Test prompt" row operation to "Test", matching the
  terse "Edit"/"Delete" style already used in the same dropdown.
- Rename the module itself from "Thunder ai prompt management" to
  "Thunder AI Prompt Management", matching sibling module names.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Full-width prompt/model fields and a bordered, scrollable response
  block, matching the styling already used on the main edit form.
- Run test/Save prompt now submit via Drupal's Form API AJAX instead
  of a full page reload, with a throbber during the request.
- Gin's sticky-header button clones sit outside the real #actions
  container (which Gin squeezes to a 1x1px fixed box once sticky
  actions take over), so the built-in AJAX throbber never reached the
  visible buttons. js/form.js mirrors the real button's disabled
  state onto the sticky clone via a real sibling spinner element
  (::after never paints on <input>, which the sticky buttons are).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Wraps the once() call, the sticky-button selector template literal, the
optional-chaining replacement, and the MutationObserver options object to
stay under the project's 80-char print width.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@chrfritsch chrfritsch closed this Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants