feat(assistant): openwebui_config field + create form with JSON upload#101
Open
martinydeAI wants to merge 4 commits into
Open
feat(assistant): openwebui_config field + create form with JSON upload#101martinydeAI wants to merge 4 commits into
martinydeAI wants to merge 4 commits into
Conversation
Adds the first concrete piece of the assistant data model from issue #14: store the OpenWebUI export JSON verbatim on the Assistant entity, and let users upload it via a create form with AJAX-validated JSON, a progress bar, and re-validation on submit. Entity / persistence: - New nullable `openwebui_config` JSON column on `assistant` (`Assistant::$openwebuiConfig`, getter / setter). Constructor signature unchanged so existing fixtures and tests stay valid. - Migration adds the column nullable so historical rows backfill cleanly. Validation service: - `App\Validator\OpenWebUiConfigValidator` hosts the shared pipeline: `validateSyntax()` checks JSON parseability, `exampleDelay()` is a temporary scaffold that sleeps 2 seconds to exercise the slow-validation UI, `validate()` runs every registered check and aggregates errors. - `App\Validator\ValidationResult` is the immutable result value object (`isValid()`, `getErrors()`). Controller: - `App\Controller\AssistantCreateController`: - `GET|POST /assistant/new` renders the form and handles submit. CSRF-protected with the `assistant-create` intent. On valid submit decodes the JSON into the entity's column and redirects to the detail page; on invalid returns 422 with errors; invalid CSRF returns 403. - `POST /assistant/new/validate-config` is the AJAX endpoint; returns `{valid, errors}` via JsonResponse. UI: - `templates/assistant/new.html.twig` is a raw HTML form (project convention) using the existing `Form/Label`, `Form/TextInput`, `Form/Button`, and `Eyebrow` components, plus a native file input and `<progress>` element. - `assets/controllers/assistant_config_upload_controller.js` reads the file with FileReader, POSTs its contents to the validate endpoint, animates the progress bar toward 90 % during the wait and snaps to 100 % on response, and updates the hidden form field on success. The file itself is never uploaded to the server. Tests (87 total, 100 % coverage): - Unit: `ValidationResult`, `OpenWebUiConfigValidator` (every validation method + the composite), Assistant entity coverage for the new field. - Integration: form render, AJAX endpoint (valid + invalid), submit happy path (asserts persisted config), submit invalid path (422 + no persistence), CSRF rejection. Refs #14. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
martinyde
requested changes
Jun 23, 2026
Addresses every review comment on PR #101. JS / progress bar (@martinyde, line 89 + line 71): - The progress bar is now a counter, not a timer. The validator exposes an ordered list of check identifiers via `OpenWebUiConfigValidator::getChecks()`; the Stimulus controller reads that list from the form's `data-...-checks-value` attribute and POSTs one validation request per check, advancing the `<progress>` element one notch on each success. The total is `checks.length`, the value is `passed`. - The AJAX endpoint now accepts a `check` field and runs only that check via `OpenWebUiConfigValidator::runCheck()`. Unknown identifiers return 400. Thin controller (@martinyde, line 25, 88, 103): - New `App\Assistant\AssistantCreator` service owns validation + persistence on the submit path. The controller no longer injects `EntityManagerInterface` directly. - New `App\Assistant\InvalidAssistantInputException` carries the validator's per-check error list back to the controller. - The `emptySubmitted()` / `readSubmitted()` private helpers stay in the controller: they translate HTTP request shape to the creator's call signature, which is the controller's job. The controller body is now just CSRF check, parse, call service, render response. Test docblock (@martinyde, line 16): - `AssistantCreateControllerTest`'s class docblock drops the "submit-path tests sleep 2 s" callout - that was tied to the old `validate()`-on-submit model; the AJAX path is now per-check. New tests for the new shapes: - Validator: `getChecks()` ordering, `runCheck()` dispatch for both registered identifiers, `runCheck()` rejecting unknown ones. - Controller: AJAX endpoint with `check=syntax`, malformed JSON for syntax, unknown-check 400. - `AssistantCreator`: happy path persists with decoded config; invalid JSON raises `InvalidAssistantInputException` carrying the validator errors and persists nothing. - `InvalidAssistantInputException`: round-trip of the error list. Verified locally: - `task coding-standards-check` - green. - `task test-coverage` - 95 tests, 332 assertions, 100% coverage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
tuj
requested changes
Jun 23, 2026
| * 4. If every check passes: populate the hidden form field with | ||
| * the JSON content and mark the status as valid. | ||
| * | ||
| * The file itself is never submitted to the server — only its |
Contributor
There was a problem hiding this comment.
Instead of landing in a hidden field it should be added to a textarea where the content can be edited.
This would open up for more ways of entering json in the system.
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Links to issues
Refs #14.
Description
Issue #14 explicitly leaves the choice between "file reference to
a .json file or store json in database" open. This PR picks the
"store json in database" branch and ships the first concrete
create flow: an OpenWebUI export JSON can be uploaded through a
new form, validated client-side via AJAX (with a progress bar),
re-validated server-side, and persisted verbatim into a new
openwebui_configJSON column onAssistant.Highlights:
openwebui_configJSON columnwith getter / setter. Constructor signature unchanged so existing
fixtures and tests stay valid. Migration adds the column
nullable so historical rows backfill cleanly.
App\Validator\OpenWebUiConfigValidatorhosts the validation pipeline.
validateSyntax()checks JSONparseability;
exampleDelay()is a deliberately slow scaffold(
sleep(2)) so the upload UI can be exercised with the progressbar visible — it will be removed when a real long-running
validation lands.
validate()runs every registered check andaggregates errors, so the AJAX endpoint and the form submit
share one pipeline.
GET|POST /assistant/newplusPOST /assistant/new/validate-config(AJAX,JsonResponse). CSRF-protected (intentassistant-create).Returns 422 on invalid submit, 403 on bad CSRF, redirects to the
detail page on success.
Form/Label,Form/TextInput,Form/Button, andEyebrowcomponents. A Stimulus controller
(
assistant_config_upload_controller.js) reads the file withFileReader, POSTs its contents to the validate endpoint,animates the
<progress>on each validation method pass, and copies the JSON into ahidden form field on success. The file itself never reaches
the server — only its parsed JSON ends up in the database.
Screenshot of the result
Checklist
Verified locally:
task coding-standards-check— green (PHP-CS-Fixer, Twig CSFixer, Prettier YAML/CSS/JS, markdownlint, composer
validate/normalize).
task test-coverage— 87 tests, 313 assertions, 100 %coverage.
Additional comments or questions
The
exampleDelay()scaffold makes every submit and AJAX callsleep 2 seconds, which adds ~6 s to the test suite. That cost is
acknowledged in the validator docblock and in the integration test
file's class-level docblock — both call out that the method is
temporary and will be deleted in the same commit that introduces a
real slow validation. Suite time is back to baseline as soon as
that happens.
The hidden form field carrying the JSON has no length limit on the
client; the MariaDB
JSONcolumn has the practical limit imposedby
max_allowed_packet. For now we lean on those defaults; asize check is a natural next validation to add to the pipeline.
The route does not require authentication — develop has no
gated routes today. Issue #97 will introduce a default-deny +
allow-list across the app and
/assistant/newwill pick up thatgating along with everything else.
Details - AI specificities
the validator and EntityManager).
validate*methods carry the PHPDoc shape from CLAUDE.md(summary, description,
@param,@return,@throws).// Tests …/// Ensures …/// Verifies …per the project convention.assistant.new.*in theexisting
messagesdomain.docs/plans/assistant-openwebui-config.md.