Skip to content

Conversation

@Prosper-ador
Copy link
Contributor

Summary

Adds a new Moodle local plugin local_gis_ai_assistant1 under plugins/local/gis_ai_assistant1/.
Provides OpenAI-compatible prompt sending, optional Rust bridge, and an admin analytics page.
Includes capability checks, error normalization, caching, events, and basic tests/docs.

…cture:

This commit establishes the foundational backend and Moodle plugin structure for the new AI Assistant. It introduces the core Rust binary and the essential PHP files for a `local` Moodle plugin.

The architecture is based on a process-isolated model where PHP manages a dedicated Rust process for AI tasks via `stdin`/`stdout`, ensuring Moodle's stability.

**Rust Processor (`packages/rust_processor`):**
-   Created a new binary crate, `moodle-ai-processor`.
-   Implemented a robust, asynchronous main event loop using Tokio, capable of handling `stdin` messages and graceful shutdown signals.
-   Defined the core `AIRequest` and `AIResponse` data structures for the JSON-based communication protocol between PHP and Rust.
-   Added a comprehensive unit test suite to validate serialization, deserialization, and error handling logic.

**Moodle Plugin (`plugins/local_aiassistant`):**
-   Initialized a new `local` plugin named `local_aiassistant`.
-   Created the `rust_process_manager` class, which is responsible for the lifecycle of the Rust child process using `proc_open`.
-   Implemented the initial plugin files: `version.php`, `db/access.php`, and `lang/en/local_aiassistant.php`.

This commit delivers a verifiable backend. The next steps will be to create the Docker infrastructure to build and run this system.

build(docker): Implement initial Docker setup with multi-stage Rust build:
This commit introduces the complete Docker and Docker Compose infrastructure required to build the Rust processor and run the Moodle environment.

The key component is a multi-stage `Dockerfile` for the Rust binary. This approach encapsulates the entire Rust toolchain, ensuring a reproducible build while producing a final, minimal "distroless" image (~20MB) for security and efficiency.

**Key Changes:**
-   **`packages/rust_processor/Dockerfile`:** A new multi-stage `Dockerfile` that compiles the Rust binary in a full toolchain image and copies the final artifact into a minimal `gcr.io/distroless/cc-debian12` image.
-   **`compose.yaml`:**
    -   Introduced a `rust-builder` service to build the Docker image from the Dockerfile.
    -   Introduced a `rust-copier` one-shot service to extract the compiled binary from the builder image into a shared Docker volume (`rust_binary`).
    -   Configured the `moodle` service to mount the `rust_binary` volume, making the executable available at `/app/moodle-ai-processor`.
-   **`.gitignore` & `.env`:** Added foundational config and ignore files for managing secrets and keeping the repository clean.

This setup automates the entire build-to-runtime pipeline, with the Moodle service explicitly depending on the successful completion of the `rust-copier`.

refactor(docker): Pivot build process to manual host-based compilation:
Based on architectural review and the goal of simplifying the developer workflow for direct iteration, this commit refactors the Docker build process. The fully automated `rust-builder`/`rust-copier` pipeline has been replaced with a manually-triggered, host-based build pattern.

This change gives the developer explicit control over the compilation step while still leveraging Docker for a consistent toolchain.

**Architectural Change:**
-   The `rust-builder` and `rust-copier` services have been **removed**.
-   A new one-shot service, `rust-build-runner`, has been introduced. This service is designed to be run manually via `docker compose run`.
-   An initializer service, `rust-build-init`, has been added to solve a critical permissions issue by `chown`-ing the Cargo cache volumes before the build runs.

**Developer Workflow Impact:**
-   To compile Rust code, the developer now runs: `docker compose run --rm rust-build-runner`.
-   The `moodle` service now mounts the compiled binary directly from the host filesystem path: `./packages/rust_processor/target/release/moodle-ai-processor`.

This approach provides a clearer separation between "compiling" and "running" and resolves complex Docker-in-Docker build context issues encountered previously.

feat(ui): Implement frontend chat interface and finalize Moodle integration:
This commit completes the initial implementation of the AI Assistant by adding the full user-facing interface and resolving key integration issues within Moodle.

**User Interface:**
-   **SCSS:** A new, modern stylesheet (`scss/ai_assistant.scss`) was created for the floating chat window, using BEM and Moodle's Bootstrap variables for a native look and feel.
-   **Mustache Template:** The HTML for the chat window is now in a Moodle template (`templates/chat_interface.mustache`) for clean separation of concerns.
-   **JavaScript:** The `js/aiassistant.js` AMD module handles all frontend logic: toggling the UI, rendering templates, handling user input, and making secure AJAX calls to the backend.

**Moodle Integration Fixes:**
-   **`lib.php` & `db/events.php`:** Refactored the asset loading logic to use the correct Moodle event observer pattern (`on_before_header`). This fixes a critical "Cannot require a CSS file after <head> has been printed" error.
-   **`settings.php`:** Made the settings page robust by adding an `isset($settings)` check, preventing a fatal `Call to a member function add() on null` error during Moodle's command-line installation.

The plugin is now feature-complete for its initial version, with a functional backend and a polished, working user interface.

Ref: #17
This commit resolves an issue where the settings page for the AI Assistant plugin was not accessible from the Moodle administration UI.

Although the settings were defined in `settings.php`, a link to this page was not being added to the administration navigation tree.

The fix involves creating a new `db/admin.php` file. This file uses the standard Moodle admin tree API (`$ADMIN->add()`) to register the plugin's settings page under the "Local plugins" category.

With this change, a clickable "AI Assistant" link now correctly appears on the local plugins page, allowing administrators to access and configure the plugin settings as intended.

Ref: #17
This commit resolves two final issues related to the Moodle plugin's integration and permissions.

First, it corrects a bug where the plugin's settings page was not accessible. The `db/admin.php` file was refactored to use the standard `admin_category` and `admin_externalpage` helpers. This ensures a clickable link to the "AI Assistant" settings appears correctly in the Site Administration -> Plugins navigation tree.

Second, the plugin's capabilities in `db/access.php` have been refined. Default permissions are now granted to all authenticated users (`user` archetype), and an `upgrade` array has been added to ensure the Manager role automatically receives new capabilities on future site upgrades. This aligns with Moodle security best practices.

With these changes, the plugin is now fully and correctly integrated into the Moodle administration interface.

Ref: #17
This commit resolves the issue where the AI Assistant's settings page was not accessible in the Moodle UI.

The previous `db/admin.php` implementation was based on an outdated API. This has been replaced with the modern Moodle 4/5 approach, which involves creating a new `admin_category` under the 'Plugins' tab and then adding an `admin_externalpage` link to it.

This ensures a dedicated, visible entry point for the "AI Assistant" settings in the main administration navigation tree, aligning with Moodle's standard plugin architecture and providing a clear, accessible path for administrators to configure the plugin.

Ref: #17
This commit resolves the issue where the AI Assistant settings page was not accessible from the "Local plugins" overview.

The previous implementation used a `db/admin.php` file with an incorrect class (`admin_externalpage`), which failed to create the navigation link.

Following Moodle best practices for local plugins, this has been refactored:
1.  The `db/admin.php` file has been removed as it is not necessary.
2.  The `settings.php` file has been updated to correctly create an `admin_settingpage` and then add itself to the main `$ADMIN` tree under the `localplugins` category.

This is the standard Moodle pattern which allows the system to automatically discover the settings page and make its name a clickable link on the `localplugins.php` overview page.

Ref: #17
…ble toggle

This commit significantly improves the administrative experience for the AI Assistant plugin by refactoring the settings page for better organization and robustness.

The `settings.php` file has been updated to incorporate several Moodle best practices:
1.  **Enable/Disable Toggle:** An `admin_setting_configcheckbox` has been added, allowing administrators to globally enable or disable the plugin's functionality without uninstalling it. The `lib.php` hooks have been updated to respect this setting.
2.  **Grouped Settings:** `admin_setting_heading` elements are now used to group related settings (General, API, Binary) into clear, collapsible sections, improving the usability and scalability of the configuration page.
3.  **Input Validation:** The Rust binary path setting now includes server-side validation using `set_validate_function` to ensure the path cannot be saved as an empty value, preventing runtime errors due to misconfiguration.

Corresponding language strings have been added to `lang/en/local_aiassistant.php` to support these new UI elements. This change makes the plugin more professional, user-friendly, and resilient.

Ref: #17
…ter rendering.

Clarifies permission check in AI assistant initialization

Refines the comment describing the capability check to improve clarity and align terminology with the AI assistant's purpose. Ensures consistent language without altering functionality.
This commit addresses an issue where the AI Assistant's floating action button was not appearing in the UI.

The root cause was identified as the plugin's "Enable" setting not being active by default after installation. The core logic in `lib.php`, which correctly checks this setting and the user's capabilities before rendering any output, was functioning as intended.

The fix involves ensuring the plugin is enabled via the admin settings UI. This commit also includes a final verification of all UI integration files (`lib.php`, `db/events.php`, `db/access.php`) to confirm they follow Moodle best practices for asset loading and permission checks.

Ref: #17
…ture and integrate build/runtime

- Create plugin at plugins/local/gis_ai_assistant1/ with full modular tree:
  README, CONTRIBUTING, LICENSE, version.php (Moodle 4.5+), lib.php, settings.php
  db/{access,install,upgrade,events}
  lang/en/local_ai_rust_chat.php (per spec) [+ local_gis_ai_assistant1.php for plugin-specific keys]
  classes/{api,analytics,forms,output(+templates),helpers,external,interfaces,observers,privacy(+metadata.xml),task}
  amd/src/{chat_widget,api_service,streaming_handler,analytics_dashboard,utils} (+build)
  analytics/{index.php, classes/{report_table,exporter}, js/{analytics_chart,filters}}
  pix/{icon.png, ai.svg, rust.svg}, cli/{check_env,sync_analytics,migrate_data}
  tests/{generator.php, phpunit/*, behat/*}
  rust/{Cargo.toml, build.rs, src modules, .cargo/config.toml}
  .gitignore, composer.json, package.json, .github/{workflows, ISSUE_TEMPLATE}

- compose.yaml: mount plugins/local/gis_ai_assistant1 and ensure permissions
- Gruntfile.js: add RequireJS target for local/gis_ai_assistant1 AMD
- Rust: fix system message .content(...) String conversion to satisfy type bound
…plugin-level skeleton

- Move Rust code from packages/rust_processor → packages/ai_bridge for clearer naming and monorepo consistency
- Remove plugin-level rust/ skeleton under plugins/local/gis_ai_assistant1 (single canonical Rust package now lives in packages/)
- Update workspace members in Cargo.toml to reference packages/ai_bridge
- Update compose.yaml:
  - rust-build-runner source mount: ./packages/ai_bridge:/app/src
  - moodle binary mount: ./packages/ai_bridge/target/release/moodle-ai-processor:/app/moodle-ai-processor:ro
- Preserve binary name moodle-ai-processor (RUST_BINARY_PATH=/app/moodle-ai-processor remains valid)

Why:
- Centralize Rust components under packages/ for better ownership and CI
- Prepare for multi-crate expansion (e.g., analytics, microservice mode)

DevOps/Build Notes:
- Rebuild the binary after the move:
  docker compose run --rm rust-build-runner
- No PHP changes required; ENV-driven binary path unchanged

Files touched:
- Cargo.toml (workspace members)
- compose.yaml (mount paths)
- Removed: plugins/local/gis_ai_assistant1/rust/*
- Renamed dir: packages/rust_processor → packages/ai_bridge
Add declare(strict_types=1); set requires=2025040100; keep release and maturity; upgrade plugin version.
Add strict_types; README hint; show green/red presence for key ENV vars.
Add docblock; add extend_navigation() with TYPE_SETTING; add pluginfile() returning not found.
Add env_present, env_missing, envstatus, enabled; keep existing strings.
…apshot and secret masking

Introduces and refines the environment variable loading helper to provide safe, type-aware access and comprehensive debugging features.

The key features are:
- **Typed Access:** Provides robust `get()`, `get_bool()`, and `get_int()` methods with required type hinting and optional fallback values.
- **Validation:** Includes `validate_required()` to ensure critical environment variables are set, throwing a `moodle_exception('envmissing', ...)` when missing.
- **Secret Masking:** Adds a configurable `MASK_SECRETS` toggle (default: true). The `mask_secret()` function now uses improved logic (`looks_like_secret()`) to hide sensitive values in logs.
- **Snapshot Feature:** Implements `snapshot()` for debugging, which respects the `MASK_SECRETS` setting to safely log the current environment configuration.
- **Defaults:** Adds `AI_RUST_LIB_PATH=/usr/local/lib/libai_rust.so` as a default environment setting.
- **Quality:** Enforces `declare(strict_types=1)` and provides complete PHPDoc documentation.
…d stack traces

Introduces a comprehensive logging facade for Moodle that centralizes log calls, respects configuration settings, and enhances debugging output.

Key enhancements:
- **Configurable Output:** Core log() uses Moodle's debugging() function only when the environment variable AI_DEBUG is true.
- **File Logging:** Supports optional file logging via the AI_LOG_FILE environment variable, writing structured log entries to the specified file.
- **Structured Context:** The main log($message, $level, array $context) method includes the context array, which is JSON-encoded for easier analysis in file logs.
- **Helper Methods:** Adds convenience methods like info(), error(), and debug().
- **Exception Handling:** The exception() helper automatically includes a full stack trace in the log output when AI_DEBUG is true.
- **Quality:** Enforces `declare(strict_types=1)` and provides complete PHPDoc documentation.
…on and stricter validation

Expands sanitization helpers to ensure prompt integrity and strict email validation, including new moderation capabilities.

Key features:
- **Prompt Sanitization:** `sanitize_prompt()` now strips tags, normalizes whitespace, and safely truncates input.
- **Prompt Moderation:** Adds optional bad-words filtering to `sanitize_prompt()` by checking the BAD_WORDS_LIST environment variable for a list of terms to filter.
- **Stricter Email Validation:** `sanitize_email()` uses both Moodle's internal PARAM_EMAIL validation and PHP's `filter_var(FILTER_VALIDATE_EMAIL)` for robust checks.
- **Validation Exceptions:** Throws a `moodle_exception('invalidemail', ...)` upon detecting an invalid email address.
- **Quality:** Enforces `declare(strict_types=1)` and provides complete PHPDoc documentation.
…bust errors

- Env-driven mode via AI_RUST_MODE (ffi|api)
- FFI: FFI::cdef with ai_send_prompt/ai_free_string contract; free Rust string
- API: POST to AI_RUST_ENDPOINT/send_prompt with x-user-email and timeout
- Log and raise on non-2xx or invalid JSON; fallback from FFI→API
…support

- Builds payload with model/input, sends Authorization and x-user-email
- Timeouts from AI_TIMEOUT; optional streaming via CURLOPT_WRITEFUNCTION
- Logs and raises on HTTP/cURL errors or invalid JSON
… chat completions

- Extract content from output[*].content[0].text, choices[0].message.content, text, etc.
- Return structure: {content, raw, tokens} (tokens from usage.* if present)
- Throw localized errors on API errors or empty responses
Adds necessary English language strings to support exceptions thrown by the new helpers and to report errors from API and bridge interactions.

The added strings are:
- **Helper Exceptions:**
    - `envmissing`: 'Required environment variable missing: {$a}' (Used by env_loader)
    - `invalidemail`: 'Invalid email address provided' (Used by sanitizer)
- **API/Bridge Exceptions:**
    - `apiresponseerror`
    - `emptyresponse`
    - `invalidmode`

These additions ensure that user-facing errors related to configuration, input validation, and external service communication are clearly translated.
…vice

- Read AI_RUST_API_KEY and send Authorization: Bearer header when present
- Preserve x-user-email header and timeouts
- Cache resolved env/default values per-request
- Add get_array() for comma-separated ENV (trim, dedupe, filter empties)
- No change to exception semantics for required keys
- Non-streaming: switch to \curl() for proxy/CA trust
- Streaming: keep raw cURL with write callback; caller should set SSE headers
@Prosper-ador Prosper-ador force-pushed the feature/48-openai-compatible-plugin branch from ca6dc0a to 02b719f Compare October 21, 2025 03:13
@Prosper-ador Prosper-ador changed the title Add local_gis_ai_assistant1 plugin (OpenAI-compatible, Rust bridge, analytics) [WIP]: Add local_gis_ai_assistant1 plugin (OpenAI-compatible, Rust bridge, analytics) Oct 21, 2025
@Prosper-ador Prosper-ador marked this pull request as draft October 21, 2025 08:25
…ials are env-only

- helpers/sanitizer: read BAD_WORDS_LIST via env_loader; safer regex quoting with preg_quote delimiter
- helpers/logger: read AI_LOG_FILE and AI_DEBUG via env_loader
- abstract_processor: read OPENAI_BASE_URL, OPENAI_MODEL via env_loader; AI_TIMEOUT with legacy OPENAI_TIMEOUT fallback via env_loader; get_apikey is ENV-only
- api/http_client: use env_loader for base URL, model, timeout; Authorization header only when OPENAI_API_KEY is set; cfg() now reads plugin config only for non-credential settings
- api/rust_bridge: use env_loader for AI_RUST_* and AI_TIMEOUT; remove internal cfg() helper; AI_RUST_API_KEY is env-only
- provider::is_provider_configured(): env-only OPENAI_API_KEY check
Files:
- plugins/ai/provider/gis_ai/classes/helpers/sanitizer.php
- plugins/ai/provider/gis_ai/classes/helpers/logger.php
- plugins/ai/provider/gis_ai/classes/abstract_processor.php
- plugins/ai/provider/gis_ai/classes/api/http_client.php
- plugins/ai/provider/gis_ai/classes/api/rust_bridge.php
- plugins/ai/provider/gis_ai/classes/provider.php
- Add 'loginrequired' => true to aiplacement_gis_ai_chat_send in db/services.php
- Add global $CFG before require_once in classes/external/send.php and classes/forms/feedback_form.php
Files:
- plugins/ai/placement/gis_ai_chat/db/services.php
- plugins/ai/placement/gis_ai_chat/classes/external/send.php
- plugins/ai/placement/gis_ai_chat/classes/forms/feedback_form.php
- Use 'gis_ai_chat:generate_text' as the language string key to match Moodle conventions
File:
- plugins/ai/placement/gis_ai_chat/lang/en/aiplacement_gis_ai_chat.php
- Add classes/process_generate_text.php to handle \core_ai\aiactions\generate_text
- Sanitize prompt via helpers\sanitizer, choose backend via api\rust_bridge or api\http_client
- Normalize responses with api\response_normalizer and log analytics via analytics\usage_tracker
- Update classes/provider.php to include generate_text only when processor class exists
Files:
- plugins/ai/provider/gis_ai/classes/process_generate_text.php
- plugins/ai/provider/gis_ai/classes/provider.php
- Implement provider::healthcheck() to verify FFI library path or API endpoint /health
- Uses env_loader for AI_RUST_MODE, AI_RUST_LIB_PATH, AI_RUST_ENDPOINT and Moodle \curl for HTTP
File:
- plugins/ai/provider/gis_ai/classes/provider.php
… (env-only)

- Fall back to \admin_settingpage when core_ai\admin\admin_settingspage_provider is not available
- Remove UI setting for API key; credentials must be provided via env
- Update language to reflect env-only credentials
Files:
- plugins/ai/provider/gis_ai/settings.php
- plugins/ai/provider/gis_ai/lang/en/aiprovider_gis_ai.php
- sanitizer_test: verifies BAD_WORDS_LIST masking behavior
- response_normalizer_test: verifies content/tokens extraction for common response shapes
Files:
- plugins/ai/provider/gis_ai/tests/sanitizer_test.php
- plugins/ai/provider/gis_ai/tests/response_normalizer_test.php
- Verify capability label and id appear on Define roles page
File:
- plugins/ai/placement/gis_ai_chat/tests/behat/placement_capability.feature
This commit increments the plugin version to 2025102101 to reflect the latest changes.
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.

2 participants