Skip to content

feat(assistant): openwebui_config field + create form with JSON upload#101

Open
martinydeAI wants to merge 4 commits into
developfrom
feature/issue-14-openwebui-config-upload
Open

feat(assistant): openwebui_config field + create form with JSON upload#101
martinydeAI wants to merge 4 commits into
developfrom
feature/issue-14-openwebui-config-upload

Conversation

@martinydeAI

@martinydeAI martinydeAI commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

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_config JSON column on Assistant.

Highlights:

  • Entity / persistence: nullable openwebui_config JSON column
    with getter / setter. Constructor signature unchanged so existing
    fixtures and tests stay valid. Migration adds the column
    nullable so historical rows backfill cleanly.
  • Shared validator: App\Validator\OpenWebUiConfigValidator
    hosts the validation pipeline. validateSyntax() checks JSON
    parseability; exampleDelay() is a deliberately slow scaffold
    (sleep(2)) so the upload UI can be exercised with the progress
    bar visible — it will be removed when a real long-running
    validation lands. validate() runs every registered check and
    aggregates errors, so the AJAX endpoint and the form submit
    share one pipeline.
  • Controller: GET|POST /assistant/new plus
    POST /assistant/new/validate-config (AJAX,
    JsonResponse). CSRF-protected (intent assistant-create).
    Returns 422 on invalid submit, 403 on bad CSRF, redirects to the
    detail page on success.
  • UI: raw HTML form (project convention) using the existing
    Form/Label, Form/TextInput, Form/Button, and Eyebrow
    components. A Stimulus controller
    (assistant_config_upload_controller.js) reads the file with
    FileReader, POSTs its contents to the validate endpoint,
    animates the <progress> on each validation method pass, and copies the JSON into a
    hidden form field on success. The file itself never reaches
    the server
    — only its parsed JSON ends up in the database.

Screenshot of the result

Skærmbillede 2026-06-23 kl  09 00 00

Checklist

  • My code is covered by test cases.
  • My code passes our test (all our tests).
  • My code passes our static analysis suite.
  • My code passes our continuous integration process.

Verified locally:

  • task coding-standards-check — green (PHP-CS-Fixer, Twig CS
    Fixer, Prettier YAML/CSS/JS, markdownlint, composer
    validate/normalize).
  • task test-coverage87 tests, 313 assertions, 100 %
    coverage
    .

Additional comments or questions

The exampleDelay() scaffold makes every submit and AJAX call
sleep 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 JSON column has the practical limit imposed
by max_allowed_packet. For now we lean on those defaults; a
size 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/new will pick up that
gating along with everything else.


Details - AI specificities

  • Controller stays thin (no business logic; reads + delegates to
    the validator and EntityManager).
  • All validate* methods carry the PHPDoc shape from CLAUDE.md
    (summary, description, @param, @return, @throws).
  • Test docblocks open with // Tests … / // Ensures … /
    // Verifies … per the project convention.
  • New translation keys live under assistant.new.* in the
    existing messages domain.
  • Plan file at docs/plans/assistant-openwebui-config.md.

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 martinyde added this to the Base setup milestone Jun 22, 2026
Comment thread assets/controllers/assistant_config_upload_controller.js Outdated
Comment thread src/Controller/AssistantCreateController.php
Comment thread src/Controller/AssistantCreateController.php
Comment thread src/Controller/AssistantCreateController.php
Comment thread src/Validator/OpenWebUiConfigValidator.php
Comment thread tests/Integration/Controller/AssistantCreateControllerTest.php Outdated
martinydeAI and others added 2 commits June 23, 2026 08:24
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>
@martinyde martinyde requested a review from tuj June 23, 2026 07:06
* 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

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