diff --git a/docs/_static/screenshots/scope_create.png b/docs/_static/screenshots/scope_create.png index d96a30c5..fa7f372e 100644 Binary files a/docs/_static/screenshots/scope_create.png and b/docs/_static/screenshots/scope_create.png differ diff --git a/docs/concepts/framework_concepts.rst b/docs/concepts/framework_concepts.rst index f6c2aaf6..8608b5ba 100644 --- a/docs/concepts/framework_concepts.rst +++ b/docs/concepts/framework_concepts.rst @@ -15,10 +15,10 @@ The plugin uses three main configuration concepts: .. code-block:: yaml - AI_EXTENSIONS: - provider: - API_KEY: "sk-proj-your-api-key" - MODEL: "provider/your-model" + AI_EXTENSIONS: + provider: + API_KEY: "sk-proj-your-api-key" + MODEL: "provider/your-model" **Profile** Defines the **what** - what the AI will be instructed to do and which information it will have access to. @@ -29,9 +29,15 @@ The plugin uses three main configuration concepts: :alt: Profile configuration view showing base template and effective configuration **Scope** - Defines the **where** - the context in which an AI workflow will be visible and usable (LMS/CMS, specific course, location). + Defines the **where** - the context in which an AI workflow will be visible and usable (LMS/CMS, specific course, location, and frontend widget). - Example usage in Django Admin, where a *scope* is defined for a location (blank for all locations, or a :ref:`specific unit `) and resource (blank for all courses, or a :ref:`specific course key `). + A scope has three matching dimensions: + + - **UI Slot Selector ID**: The ``id`` of the frontend widget that should render the workflow (see :ref:`ui-slot-selector-id`). + - **Course ID**: Optionally limits the scope to a specific course. + - **Location regex**: Optionally limits the scope to specific units within a course. + + Example usage in Django Admin, where a *scope* is defined with a widget ID, and optionally for a :ref:`specific course ` or :ref:`specific unit `. .. image:: /_static/screenshots/scope_create.png :alt: Create new scope interface diff --git a/docs/quickstarts/extension_guide.rst b/docs/quickstarts/extension_guide.rst index 9d291a66..d4a003b3 100644 --- a/docs/quickstarts/extension_guide.rst +++ b/docs/quickstarts/extension_guide.rst @@ -243,7 +243,7 @@ In your ``frontend/src/index.tsx`` (or your plugin's entry point): .. code-block:: tsx - import { registerComponents } from '@openedx/openedx-ai-extensions-ui'; + import { registerComponents, REGISTRY_NAMES } from '@openedx/openedx-ai-extensions-ui'; import MyRequestComponent from './components/MyRequestComponent'; import MyIndependentTabComponent from './components/MyIndependentTabComponent'; @@ -253,7 +253,7 @@ In your ``frontend/src/index.tsx`` (or your plugin's entry point): }); // Register a specialized entry (for example, a new tab in AI Settings). - registerComponents('settings', { + registerComponents(REGISTRY_NAMES.SETTINGS, { id: 'my-plugin-id', label: 'My AI Plugin', component: MyIndependentTabComponent, diff --git a/docs/quickstarts/usage_guide.rst b/docs/quickstarts/usage_guide.rst index 11cad726..020db375 100644 --- a/docs/quickstarts/usage_guide.rst +++ b/docs/quickstarts/usage_guide.rst @@ -82,6 +82,9 @@ Creating the Scope - **Course ID**: Leave empty (applies to all courses), or :ref:`target specific courses ` - **Location regex**: Leave empty (applies to all units), or :ref:`target specific units ` - **Profile**: Select the profile you just created using the name you chose in the **Slug** field + - **UI slot selector ID**: Enter the ``id`` of the frontend widget that should render this workflow. + See :ref:`ui-slot-selector-id` for details. + - **Enabled**: Leave checked to activate the scope immediately 3. Click :guilabel:`Save` @@ -134,8 +137,11 @@ Creating the Scope - **Service variant**: Select ``CMS - Studio`` - **Course ID**: Leave empty (applies to all courses in Studio), or :ref:`target specific courses ` - - **Location regex**: Leave empty - there is no targeting locations in the Studio context + - **Location regex**: Leave empty — location targeting is not used in the Studio context - **Profile**: Select the profile you just created + - **UI slot selector ID**: Enter the ``id`` of the frontend widget that should render this workflow. + See :ref:`ui-slot-selector-id` for details. + - **Enabled**: Leave checked to activate the scope immediately 3. Click :guilabel:`Save` @@ -150,6 +156,75 @@ Navigate to a course in Studio. You should see the AI assistant interface availa Advanced Configuration ********************** +.. _ui-slot-selector-id: + +Using the UI Slot Selector ID +============================== + +The **UI Slot Selector ID** field controls which frontend widget renders a given scope. +It does **not** refer to the Open edX UI slot (e.g. ``openedx.learning.unit.header.slot.v1``). +It refers to the ``id`` property of the widget that is inserted into that slot via the +plugin operations system: + +.. code-block:: javascript + + op: PLUGIN_OPERATIONS.Insert, + widget: { + id: 'ai-assist-button', // ← this is the UI Slot Selector ID + priority: 10, + type: DIRECT_PLUGIN, + RenderWidget: ConfigurableAIAssistance, + } + +Every widget sends its own ``id`` with each backend request. The backend only returns a +scope whose **UI Slot Selector ID** exactly matches that value. This guarantees that each +widget on the page receives its own independently configured workflow. + +Common widget IDs +----------------- + +The most commonly used widget IDs are: + +.. code-block:: text + + ai-assist-button ← LMS unit-level AI assistant + ai-assist-button-course-outline-sidebar ← Studio course outline sidebar + +A less common one is ``ai-extensions-settings-card``, which appears as a new card in the Pages & Resources view in Studio. Additional IDs may be in use +depending on your installation. The Django admin field auto-suggests all values it finds +in existing scope records, so the available options grow as you configure more scopes. + +Wildcards (empty value) +----------------------- + +If you leave **UI Slot Selector ID** empty, the scope will match *any* widget that does +not find a more specific scope for its widget ID. This is a convenient shortcut for +simple deployments, but it means any new widget you add later may unexpectedly receive +the same workflow. + +For production deployments, always set an explicit **UI Slot Selector ID** so scope +resolution is predictable. + +Scope resolution and specificity +--------------------------------- + +When multiple scopes could match a request, the backend selects the most *specific* one +using a weighted score: + ++--------------------+--------+ +| Field | Weight | ++====================+========+ +| Location regex | +4 | ++--------------------+--------+ +| Course ID | +2 | ++--------------------+--------+ +| UI Slot Selector | +1 | ++--------------------+--------+ + +A scope with all three fields set outranks one that only sets two, and so on. The +**Specificity index** column in the Django admin list view shows the computed score for +each scope (read-only; updated automatically on save). + .. _target-specific-courses: Targeting Specific Courses @@ -213,6 +288,9 @@ To target multiple specific units, use the OR operator (``|``): This matches any unit with one of the three specified block IDs. +.. note:: + **Course ID is required** when using Location regex. Saving a scope with Location regex set but Course ID empty will fail with a validation error. + .. warning:: Location regex is a powerful but technical feature. Test your regex patterns carefully to ensure they match the intended units.