Repository: mitodl/open-edx-plugins
Pull request #447: feat: add block usage key and transcript asset id
Author: login: asadali145
Labels: name: Needs Review
Created: 2025-03-03T15:14:13Z | Merged: 2025-03-06T08:18:20Z

### What are the relevant tickets?
<!--- If it fixes an open issue, please link to the issue here. -->
<!--- Closes # --->
<!--- Fixes # --->
<!--- N/A --->
Closes https://github.com/mitodl/hq/issues/6843

### Description (What does it do?)
<!--- Describe your changes in detail -->
Passes the block usage key and transcripts asset ID.

Here are the example block usage keys:
```
Video: block-v1:testx+test101+2024_T1+type@video+block@d6d49d1d72f942cd9d534a54f99d8a87
Dropdown: block-v1:testx+test101+2024_T1+type@problem+block@a5e043d9325e4ed9b8912d1131051321
MCQ: block-v1:testx+test101+2024_T1+type@problem+block@be69872810904ad195e8ec9241a59e5d
```
Here is an example Asset ID for the video block English transcript:
`asset-v1:testx+test101+2024_T1+type@asset+block@91204cd0-44cb-4b68-9e44-303ff8ffc2e3-en.srt`


### How can this be tested?
<!---
Please describe in detail how your changes have been tested.
Include details of your testing environment, any set-up required
(e.g. data entry required for validation) and the tests you ran to
see how your change affects other areas of the code, etc.
Please also include instructions for how your reviewer can validate your changes.
--->

- Generate package by checking out this branch and running `pants package :: .`
- Enable xBlock configurations in CMS <STUDIO_URL>/admin/xblock_config/studioconfig/
- Enable xBlock asides in LMS <LMS_URL>/admin/lms_xblock/xblockasidesconfig/
- Install the ol-openedx-chat in LMS and CMS, Reload them
  - make sure to install the latest version 0.2.2
- In frontend-app-learning:
  - Create env.config.jsx and add the below code:
```
import * as remoteAiChatDrawer from "./mitodl-smoot-design/dist/bundles/remoteAiChatDrawer.umd.js"

remoteAiChatDrawer.init({
  messageOrigin: "http://local.openedx.io:8000",
  transformBody: messages => ({ message: messages[messages.length - 1].content }),
})

const config = {
  ...process.env,
};

export default config;
```
- Now run the below in the shell inside the learning MFE folder:
- `npm pack @mitodl/smoot-design@3.4.0`
- `tar -xvzf mitodl-smoot-design-3.4.0.tgz`
- `mv package mitodl-smoot-design`
- Now start learning MFE by `npm run dev`
- Now enable the `ol_openedx_chat.ol_openedx_chat_enabled` waffle flag for a course at <LMS_URL>/admin/waffle_utils/waffleflagcourseoverridemodel/
- Verify that the chat options are only enabled for this course and are disabled for all others.
- Visit the CMS and enable the chat for a few problem and video blocks.
- For the video block:
  - Click on Edit button at the right top of the block in CMS
  <img width="1735" alt="Screenshot 2025-03-03 at 10 02 27 PM" src="https://github.com/user-attachments/assets/9300dee0-c8c8-49b1-8105-f3c7300fa44d" />
  - Click on `import youtube transcript`.
  <img width="1735" alt="Screenshot 2025-03-03 at 10 02 47 PM" src="https://github.com/user-attachments/assets/d99c1217-6542-48f7-9626-c0a0aa935206" />
  - When we fetch transcripts, it returns an empty list. We will have to manually upload transcripts.
  - Click on the `Download Transcript for Editing`
  <img width="1735" alt="Screenshot 2025-03-03 at 10 03 12 PM" src="https://github.com/user-attachments/assets/ae60ecad-228d-4962-88dc-179d8ada705e" />
  - Now upload the downloaded file
  <img width="1735" alt="Screenshot 2025-03-03 at 10 03 43 PM" src="https://github.com/user-attachments/assets/bf7bfd20-6bf9-47ed-a588-6ae2872172fc" />
  - Save the settings, enable the AI Assistant, and Publish the unit.
- Visit LMS and you should see the button for AI Chat drawer and clicking on the button should open a drawer.
- Block usage key and Asset ID are passed to the drawer. You can check by adding a console log in ai_chat.js at line 16
  - `console.log(event.data.requestOpts)`
- To communicate with the LEARN AI backend, you can add the API URL in LMS private.py: `LEARN_AI_API_URL = "http://ai.open.odl.local:8002/http/recommendation_agent/"`


Diff:
diff --git a/src/ol_openedx_chat/BUILD b/src/ol_openedx_chat/BUILD
index 7e473e6c3..8cdd54920 100644
--- a/src/ol_openedx_chat/BUILD
+++ b/src/ol_openedx_chat/BUILD
@@ -16,7 +16,7 @@ python_distribution(
     ],
     provides=setup_py(
         name="ol-openedx-chat",
-        version="0.2.1",
+        version="0.2.2",
         description="An Open edX plugin to add Open Learning AI chat aside to xBlocks",
         license="BSD-3-Clause",
         author="MIT Office of Digital Learning",
diff --git a/src/ol_openedx_chat/block.py b/src/ol_openedx_chat/block.py
index 507b1c8fa..ed5bb90f2 100644
--- a/src/ol_openedx_chat/block.py
+++ b/src/ol_openedx_chat/block.py
@@ -1,7 +1,11 @@
+import logging
+
 import pkg_resources
 from django.conf import settings
 from django.template import Context, Template
 from django.utils.translation import gettext_lazy as _
+from ol_openedx_chat.compat import get_ol_openedx_chat_enabled_flag
+from ol_openedx_chat.constants import ENGLISH_LANGUAGE_TRANSCRIPT, VIDEO_BLOCK_CATEGORY
 from ol_openedx_chat.utils import is_aside_applicable_to_block
 from rest_framework import status as api_status
 from web_fragments.fragment import Fragment
@@ -10,7 +14,7 @@
 from xblock.fields import Boolean, Scope
 from xmodule.x_module import AUTHOR_VIEW, STUDENT_VIEW
 
-from .compat import get_ol_openedx_chat_enabled_flag
+log = logging.getLogger(__name__)
 
 
 def get_resource_bytes(path):
@@ -54,6 +58,7 @@ def student_view_aside(self, block, context=None):
         """
         Renders the aside contents for the student view
         """  # noqa: D401
+        from xmodule.video_block.transcripts_utils import Transcript
 
         # This is a workaround for those blocks which do not have has_author_view=True
         # because when a block does not define has_author_view=True in it, the only view
@@ -66,23 +71,51 @@ def student_view_aside(self, block, context=None):
         if not self.ol_chat_enabled:
             return fragment
 
+        block_usage_key = self.scope_ids.usage_id.usage_key
+        block_id = block_usage_key.block_id
+        block_type = getattr(block, "category", None)
+
         fragment.add_content(
             render_template(
                 "static/html/student_view.html",
                 {
-                    "block_key": self.scope_ids.usage_id.usage_key.block_id,
-                    "block_type": getattr(block, "category", None),
+                    "block_id": block_id,
+                    "block_usage_key": block_usage_key,
+                    "block_type": block_type,
                 },
             )
         )
         fragment.add_css(get_resource_bytes("static/css/ai_chat.css"))
         fragment.add_javascript(get_resource_bytes("static/js/ai_chat.js"))
+
+        request_body = {
+            "edx_module_id": block_usage_key,
+        }
+
+        if block_type == VIDEO_BLOCK_CATEGORY:
+            try:
+                transcripts_info = block.get_transcripts_info()
+                if transcripts_info.get("transcripts", {}).get(
+                    ENGLISH_LANGUAGE_TRANSCRIPT
+                ):
+                    request_body["transcript_asset_id"] = Transcript.asset_location(
+                        block.location,
+                        transcripts_info["transcripts"][ENGLISH_LANGUAGE_TRANSCRIPT],
+                    )
+
+            except Exception:  # noqa: BLE001
+                log.info(
+                    "Error while fetching transcripts for block %s",
+                    block.location,
+                )
+
         extra_context = {
             "ask_tim_drawer_title": f"about {block.display_name}",
-            "block_usage_key": self.scope_ids.usage_id.usage_key.block_id,
             "user_id": self.runtime.user_id,
+            "block_id": block_id,
             "learn_ai_api_url": settings.LEARN_AI_API_URL,
             "learning_mfe_base_url": settings.LEARNING_MICROFRONTEND_URL,
+            "request_body": request_body,
         }
 
         fragment.initialize_js("AiChatAsideInit", json_args=extra_context)
diff --git a/src/ol_openedx_chat/constants.py b/src/ol_openedx_chat/constants.py
index 8e8670baf..3ff1a5e89 100644
--- a/src/ol_openedx_chat/constants.py
+++ b/src/ol_openedx_chat/constants.py
@@ -1,3 +1,7 @@
 # The dictionary should contain all the block types for which the chat should be
 # applicable if a block has sub-blocks or sub category, that should be added in the list
-CHAT_APPLICABLE_BLOCKS = ["problem", "video"]
+VIDEO_BLOCK_CATEGORY = "video"
+PROBLEM_BLOCK_CATEGORY = "problem"
+CHAT_APPLICABLE_BLOCKS = [PROBLEM_BLOCK_CATEGORY, VIDEO_BLOCK_CATEGORY]
+
+ENGLISH_LANGUAGE_TRANSCRIPT = "en"
diff --git a/src/ol_openedx_chat/static/html/student_view.html b/src/ol_openedx_chat/static/html/student_view.html
index 2bbd9232d..8e9c44fe6 100644
--- a/src/ol_openedx_chat/static/html/student_view.html
+++ b/src/ol_openedx_chat/static/html/student_view.html
@@ -1,5 +1,5 @@
 <div class="{{ block_type }}-block-chat-button-container">
-  <div class="ai-chat-button {{ block_type }}-block-chat-button" id="chat-button-{{ block_key }}" data-block-key="{{ block_key }}">
+  <div class="ai-chat-button {{ block_type }}-block-chat-button" id="chat-button-{{ block_id }}" data-block-id="{{ block_id }}">
     {% if block_type == "problem" %}
       <p>Help me with this problem</p>
     {% elif block_type == "video" %}
@@ -7,5 +7,5 @@
     {% endif %}
     <img src="/static/images/icon.svg" class="chat-icon">
   </div>
-  <div id="app-root-{{ block_key }}"></div>
+  <div id="app-root-{{ block_id }}"></div>
 </div>
diff --git a/src/ol_openedx_chat/static/js/ai_chat.js b/src/ol_openedx_chat/static/js/ai_chat.js
index 3bb26e4f3..856d0feea 100644
--- a/src/ol_openedx_chat/static/js/ai_chat.js
+++ b/src/ol_openedx_chat/static/js/ai_chat.js
@@ -8,20 +8,27 @@
           role: "assistant",
         },
       ];
-      $(`#chat-button-${init_args.block_usage_key}`).on("click", { askTimTitle: init_args.ask_tim_drawer_title }, function (event) {
-        const blockKey = $(this).data("block-key");
+
+      $(`#chat-button-${init_args.block_id}`).on("click", {
+        askTimTitle: init_args.ask_tim_drawer_title,
+        blockID: init_args.block_id,
+        requestBody: init_args.request_body,
+      }, function (event) {
+
         window.parent.postMessage(
           {
             type: "smoot-design::chat-open",
             payload: {
-              chatId: blockKey,
+              chatId: event.data.blockID,
               askTimTitle: event.data.askTimTitle,
               apiUrl: init_args.learn_ai_api_url,
               initialMessages: INITIAL_MESSAGES,
+              requestBody: event.data.requestBody,
             },
           },
           init_args.learning_mfe_base_url, // Ensure correct parent origin
         );
+
       });
     });
   }


Reviews:
user: login: gemini-code-assist[bot]
body: Hello @asadali145, I'm Gemini Code Assist[^1]! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

## Summary of Changes
This pull request addresses issue #6843 by passing the block usage key and transcript asset ID to the frontend. The changes modify the `student_view_aside` method in `src/ol_openedx_chat/block.py` to include the block usage key and transcript asset ID in the context passed to the template. The javascript file `src/ol_openedx_chat/static/js/ai_chat.js` is updated to pass these values when posting a message to the parent window. The HTML template `src/ol_openedx_chat/static/html/student_view.html` is also updated to use `block_id` instead of `block_key`.

### Highlights
* **Feature**: Passes the block usage key and transcript asset ID to the frontend.
* **Block Context**: Modifies the `student_view_aside` method to include `block_usage_key` and `transcript_asset_id` in the template context.
* **Javascript Update**: Updates `ai_chat.js` to include `blockUsageKey` and `transcriptAssetID` in the message payload.
* **Template Update**: Updates `student_view.html` to use `block_id` instead of `block_key`.

### Changelog
<details>
<summary>Click here to see the changelog</summary>

* **src/ol_openedx_chat/block.py**
  * Added logging import.
  * Added `log` instance for logging.
  * Imported `Transcript` from `xmodule.video_block.transcripts_utils`.
  * Modified `student_view_aside` to include `block_usage_key` and `transcript_asset_id` in the template context.
  * Added logic to fetch the transcript asset ID for video blocks.
  * Handled exceptions when fetching transcripts.
  * Replaced `block_usage_key.block_id` with `block_id` in the extra context.
* **src/ol_openedx_chat/static/html/student_view.html**
  * Replaced `block_key` with `block_id` in the HTML template.
* **src/ol_openedx_chat/static/js/ai_chat.js**
  * Modified the click handler to include `blockUsageKey`, `blockID`, and `transcriptAssetID` in the message payload.
  * Used `block_id` instead of `block_usage_key` when selecting the chat button.

</details>


<details>

<summary><b>Using Gemini Code Assist</b></summary>
<br>

The full guide for Gemini Code Assist can be found on our [documentation page](https://developers.google.com/gemini-code-assist/docs/review-github-code), here are some quick tips.

<b>Invoking Gemini</b>

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either `/gemini <command>` or `@gemini-code-assist <command>`. Below is a summary of the supported commands.

Feature | Command | Description
--- | --- | ---
Code Review | `/gemini review` | Performs a code review for the current pull request in its current state.
Pull Request Summary | `/gemini summary` | Provides a summary of the current pull request in its current state.
Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments.
Help | `/gemini help` | Displays a list of available commands.

<b>Customization</b>

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a `.gemini/` folder in the base of the repository. Detailed instructions can be found [here](https://developers.google.com/gemini-code-assist/docs/review-github-code).

<b>Limitations & Feedback</b>

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with :thumbsup: and :thumbsdown: on @gemini-code-assist comments to provide feedback.

</details>

<b>You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the [Gemini Code Assist IDE Extension](https://cloud.google.com/products/gemini/code-assist).</b>


[^1]: Review the [Privacy Notices](https://policies.google.com/privacy), [Generative AI Prohibited Use Policy](https://policies.google.com/terms/generative-ai/use-policy), [Terms of Service](https://policies.google.com/terms), and learn how to configure Gemini Code Assist in GitHub [here](https://developers.google.com/gemini-code-assist/docs/review-github-code). Gemini can make mistakes, so double check it and [use code with caution](https://support.google.com/legal/answer/13505487).

---

**Did you know?**

The first version of JavaScript was created in just 10 days by Brendan Eich at Netscape Communications Corporation in 1995.

state: COMMENTED
author_association: NONE
submitted_at: 2025-03-03T15:14:25Z
user: login: gemini-code-assist[bot]
body: ## Code Review
The pull request introduces the block usage key and transcript asset ID to the OL Chat aside, enhancing the chat functionality within the Open Learning platform. The changes involve modifying the block's student and author views to include these new parameters. Overall, the code is well-structured and addresses the intended functionality. However, there are a few areas that could be improved for clarity and maintainability.

### Merge Readiness
The pull request is almost ready for merging. The changes are well-structured and address the intended functionality. However, I recommend addressing the comments before merging. I am unable to directly approve this pull request, and recommend that others review and approve this code before merging.
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-03T15:14:34Z
user: login: pdpinch
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-03T21:37:45Z
user: login: ChristopherChudzicki
body: This worked well for me. I have one small rename request if you agree with it.

Additionally: we should verify with whoever is building the Video chatbot (@arslanashraf7 , I think?) that `{ block_id, block_usage_key, transcript_asset_id }` are good names for the API to use. Seems OK to me.

state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T22:01:55Z
user: login: asadali145
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T08:24:35Z
user: login: asadali145
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T08:29:15Z
user: login: arslanashraf7
body: Looks good. Just a few nitpicks.

_You don't need to do the following in this PR, but, I would also recommend creating two separate tickets:_

1. A ticket to update the readme for this package since the setup steps for this can become complex for anyone easily.
2. A ticket to add tests. Unit tests and ideally integration test with open edX
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T12:04:16Z
user: login: arslanashraf7
body: LGTM 👍
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T12:38:10Z
user: login: ChristopherChudzicki
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T14:43:44Z
user: login: arslanashraf7
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T15:07:23Z
user: login: asadali145
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T15:10:10Z
user: login: ChristopherChudzicki
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T12:35:31Z
user: login: ChristopherChudzicki
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T12:36:56Z

Review comments:
diff_hunk: @@ -66,23 +72,51 @@ def student_view_aside(self, block, context=None):
         if not self.ol_chat_enabled:
             return fragment
 
+        block_id = self.scope_ids.usage_id.usage_key.block_id
+        block_usage_key = self.scope_ids.usage_id.usage_key
path: src/ol_openedx_chat/block.py
user: login: ChristopherChudzicki
body: Naive of me to think this was called [`module_id`](https://edx.readthedocs.io/projects/devdata/en/latest/internal_data_formats/sql_schema.html#module-id) and not three different things. I guess module_id, usage_key, and location are all the same thing?
created_at: 2025-03-03T19:56:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -66,23 +72,51 @@ def student_view_aside(self, block, context=None):
         if not self.ol_chat_enabled:
             return fragment
 
+        block_id = self.scope_ids.usage_id.usage_key.block_id
+        block_usage_key = self.scope_ids.usage_id.usage_key
+        block_type = getattr(block, "category", None)
+
         fragment.add_content(
             render_template(
                 "static/html/student_view.html",
                 {
-                    "block_key": self.scope_ids.usage_id.usage_key.block_id,
-                    "block_type": getattr(block, "category", None),
+                    "block_id": block_id,
+                    "block_usage_key": block_usage_key,
+                    "block_type": block_type,
                 },
             )
         )
         fragment.add_css(get_resource_bytes("static/css/ai_chat.css"))
         fragment.add_javascript(get_resource_bytes("static/js/ai_chat.js"))
+
+        request_opts = {
+            "block_id": block_id,
+            "block_usage_key": block_usage_key,
+        }
+
+        if block_type == VIDEO_BLOCK_CATEGORY:
+            try:
+                transcripts_info = block.get_transcripts_info()
+                if transcripts_info.get("transcripts") and transcripts_info[
+                    "transcripts"
+                ].get("en"):
+                    request_opts["transcript_asset_id"] = Transcript.asset_location(
+                        block.location,
+                        transcripts_info["transcripts"][ENGLISH_LANGUAGE_TRANSCRIPT],
+                    )
+
+            except Exception:
+                log.exception(
+                    "Error while fetching transcripts for block %s",
+                    block.location,
+                )
+
         extra_context = {
             "ask_tim_drawer_title": f"about {block.display_name}",
-            "block_usage_key": self.scope_ids.usage_id.usage_key.block_id,
             "user_id": self.runtime.user_id,
             "learn_ai_api_url": settings.LEARN_AI_API_URL,
             "learning_mfe_base_url": settings.LEARNING_MICROFRONTEND_URL,
+            "request_opts": request_opts,
path: src/ol_openedx_chat/block.py
user: login: ChristopherChudzicki
body: **Rename Request:** I think we should call this `request_body`. My understanding from our short conversation earlier today is that this would be things append to the request body (with the whole body being this + message). 

(as opposed to other requestOpts, like headers)
created_at: 2025-03-03T19:58:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -66,23 +72,51 @@ def student_view_aside(self, block, context=None):
         if not self.ol_chat_enabled:
             return fragment
 
+        block_id = self.scope_ids.usage_id.usage_key.block_id
+        block_usage_key = self.scope_ids.usage_id.usage_key
+        block_type = getattr(block, "category", None)
+
         fragment.add_content(
             render_template(
                 "static/html/student_view.html",
                 {
-                    "block_key": self.scope_ids.usage_id.usage_key.block_id,
-                    "block_type": getattr(block, "category", None),
+                    "block_id": block_id,
+                    "block_usage_key": block_usage_key,
+                    "block_type": block_type,
                 },
             )
         )
         fragment.add_css(get_resource_bytes("static/css/ai_chat.css"))
         fragment.add_javascript(get_resource_bytes("static/js/ai_chat.js"))
+
+        request_opts = {
+            "block_id": block_id,
+            "block_usage_key": block_usage_key,
+        }
+
+        if block_type == VIDEO_BLOCK_CATEGORY:
+            try:
+                transcripts_info = block.get_transcripts_info()
+                if transcripts_info.get("transcripts") and transcripts_info[
+                    "transcripts"
+                ].get("en"):
+                    request_opts["transcript_asset_id"] = Transcript.asset_location(
+                        block.location,
+                        transcripts_info["transcripts"][ENGLISH_LANGUAGE_TRANSCRIPT],
+                    )
+
+            except Exception:
path: src/ol_openedx_chat/block.py
user: login: pdpinch
body: It's possible that the video might not have any transcripts, or no English language transcript. For example, it might be a demonstration video without audio. 

Warning on missing transcripts should be OK, but I don't think we want to raise an exception. 
created_at: 2025-03-03T21:37:44Z
author_association: MEMBER
diff_hunk: @@ -66,23 +72,51 @@ def student_view_aside(self, block, context=None):
         if not self.ol_chat_enabled:
             return fragment
 
+        block_id = self.scope_ids.usage_id.usage_key.block_id
+        block_usage_key = self.scope_ids.usage_id.usage_key
+        block_type = getattr(block, "category", None)
+
         fragment.add_content(
             render_template(
                 "static/html/student_view.html",
                 {
-                    "block_key": self.scope_ids.usage_id.usage_key.block_id,
-                    "block_type": getattr(block, "category", None),
+                    "block_id": block_id,
+                    "block_usage_key": block_usage_key,
+                    "block_type": block_type,
                 },
             )
         )
         fragment.add_css(get_resource_bytes("static/css/ai_chat.css"))
         fragment.add_javascript(get_resource_bytes("static/js/ai_chat.js"))
+
+        request_opts = {
+            "block_id": block_id,
+            "block_usage_key": block_usage_key,
+        }
+
+        if block_type == VIDEO_BLOCK_CATEGORY:
+            try:
+                transcripts_info = block.get_transcripts_info()
+                if transcripts_info.get("transcripts") and transcripts_info[
+                    "transcripts"
+                ].get("en"):
+                    request_opts["transcript_asset_id"] = Transcript.asset_location(
+                        block.location,
+                        transcripts_info["transcripts"][ENGLISH_LANGUAGE_TRANSCRIPT],
+                    )
+
+            except Exception:
+                log.exception(
+                    "Error while fetching transcripts for block %s",
+                    block.location,
+                )
+
         extra_context = {
             "ask_tim_drawer_title": f"about {block.display_name}",
-            "block_usage_key": self.scope_ids.usage_id.usage_key.block_id,
             "user_id": self.runtime.user_id,
             "learn_ai_api_url": settings.LEARN_AI_API_URL,
             "learning_mfe_base_url": settings.LEARNING_MICROFRONTEND_URL,
+            "request_opts": request_opts,
path: src/ol_openedx_chat/block.py
user: login: asadali145
body: Sounds good! I can rename it.
created_at: 2025-03-04T08:24:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -66,23 +72,51 @@ def student_view_aside(self, block, context=None):
         if not self.ol_chat_enabled:
             return fragment
 
+        block_id = self.scope_ids.usage_id.usage_key.block_id
+        block_usage_key = self.scope_ids.usage_id.usage_key
path: src/ol_openedx_chat/block.py
user: login: asadali145
body: Yes, it's all the same. The class is named [BlockUsageLocator](https://github.com/openedx/opaque-keys/blob/master/opaque_keys/edx/locator.py#L621), so we generally use variable names like usage_key.
created_at: 2025-03-04T08:29:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -66,23 +72,51 @@ def student_view_aside(self, block, context=None):
         if not self.ol_chat_enabled:
             return fragment
 
+        block_id = self.scope_ids.usage_id.usage_key.block_id
+        block_usage_key = self.scope_ids.usage_id.usage_key
path: src/ol_openedx_chat/block.py
user: login: arslanashraf7
body: nit:

If we really need to define two different variables for this then, maybe we should define them in a way that clearly shows their relation. A more readable approach would be to explicitly show that the `block_id` is if the `block_usage_key` is at hand.

```
block_usage_key = self.scope_ids.usage_id.usage_key
block_id = block_usage_key.block_id

```
        
created_at: 2025-03-04T11:42:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -66,23 +72,51 @@ def student_view_aside(self, block, context=None):
         if not self.ol_chat_enabled:
             return fragment
 
+        block_id = self.scope_ids.usage_id.usage_key.block_id
+        block_usage_key = self.scope_ids.usage_id.usage_key
+        block_type = getattr(block, "category", None)
+
         fragment.add_content(
             render_template(
                 "static/html/student_view.html",
                 {
-                    "block_key": self.scope_ids.usage_id.usage_key.block_id,
-                    "block_type": getattr(block, "category", None),
+                    "block_id": block_id,
+                    "block_usage_key": block_usage_key,
+                    "block_type": block_type,
                 },
             )
         )
         fragment.add_css(get_resource_bytes("static/css/ai_chat.css"))
         fragment.add_javascript(get_resource_bytes("static/js/ai_chat.js"))
+
+        request_body = {
+            "block_usage_key": block_usage_key,
+        }
+
+        if block_type == VIDEO_BLOCK_CATEGORY:
+            try:
+                transcripts_info = block.get_transcripts_info()
+                if transcripts_info.get("transcripts") and transcripts_info[
+                    "transcripts"
+                ].get(ENGLISH_LANGUAGE_TRANSCRIPT):
path: src/ol_openedx_chat/block.py
user: login: arslanashraf7
body: Dictionary's get chaining might be more readable here.

```suggestion
                if transcripts_info.get("transcripts", {}).get(ENGLISH_LANGUAGE_TRANSCRIPT):
```
created_at: 2025-03-04T11:52:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,6 +13,9 @@
 from xmodule.x_module import AUTHOR_VIEW, STUDENT_VIEW
 
 from .compat import get_ol_openedx_chat_enabled_flag
+from .constants import ENGLISH_LANGUAGE_TRANSCRIPT, VIDEO_BLOCK_CATEGORY
path: src/ol_openedx_chat/block.py
user: login: arslanashraf7
body: nit:

We don't usually follow relative imports in edX plugins because the plugins could be importing things from edX and some from itself and it can get confusing sometimes, Absolute imports are more prominent and readable.
created_at: 2025-03-04T11:56:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -66,23 +71,51 @@ def student_view_aside(self, block, context=None):
         if not self.ol_chat_enabled:
             return fragment
 
+        block_usage_key = self.scope_ids.usage_id.usage_key
+        block_id = block_usage_key.block_id
+        block_type = getattr(block, "category", None)
+
         fragment.add_content(
             render_template(
                 "static/html/student_view.html",
                 {
-                    "block_key": self.scope_ids.usage_id.usage_key.block_id,
-                    "block_type": getattr(block, "category", None),
+                    "block_id": block_id,
+                    "block_usage_key": block_usage_key,
+                    "block_type": block_type,
                 },
             )
         )
         fragment.add_css(get_resource_bytes("static/css/ai_chat.css"))
         fragment.add_javascript(get_resource_bytes("static/js/ai_chat.js"))
+
+        request_body = {
+            "edx_module_id": block_usage_key,
+        }
path: src/ol_openedx_chat/block.py
user: login: ChristopherChudzicki
body: I agree with @arslanashraf7 's suggestion about using naming to align with learn so I think `edx_module_id` is good.

But we do want to send the transcript module_id also.

Maybe send `video_edx_module_id` and `transcript_edx_module_id`

?
created_at: 2025-03-04T14:42:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -66,23 +71,51 @@ def student_view_aside(self, block, context=None):
         if not self.ol_chat_enabled:
             return fragment
 
+        block_usage_key = self.scope_ids.usage_id.usage_key
+        block_id = block_usage_key.block_id
+        block_type = getattr(block, "category", None)
+
         fragment.add_content(
             render_template(
                 "static/html/student_view.html",
                 {
-                    "block_key": self.scope_ids.usage_id.usage_key.block_id,
-                    "block_type": getattr(block, "category", None),
+                    "block_id": block_id,
+                    "block_usage_key": block_usage_key,
+                    "block_type": block_type,
                 },
             )
         )
         fragment.add_css(get_resource_bytes("static/css/ai_chat.css"))
         fragment.add_javascript(get_resource_bytes("static/js/ai_chat.js"))
+
+        request_body = {
+            "edx_module_id": block_usage_key,
+        }
path: src/ol_openedx_chat/block.py
user: login: arslanashraf7
body: >But we do want to send the transcript module_id also.

>Maybe send video_edx_module_id and transcript_edx_module_id

>?

Yes, I'm ok with `transcript_asset_id`, `transcript_edx_module_id` since there is not separate field for transcript Ids in Learn at least now.

created_at: 2025-03-04T15:07:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -66,23 +71,51 @@ def student_view_aside(self, block, context=None):
         if not self.ol_chat_enabled:
             return fragment
 
+        block_usage_key = self.scope_ids.usage_id.usage_key
+        block_id = block_usage_key.block_id
+        block_type = getattr(block, "category", None)
+
         fragment.add_content(
             render_template(
                 "static/html/student_view.html",
                 {
-                    "block_key": self.scope_ids.usage_id.usage_key.block_id,
-                    "block_type": getattr(block, "category", None),
+                    "block_id": block_id,
+                    "block_usage_key": block_usage_key,
+                    "block_type": block_type,
                 },
             )
         )
         fragment.add_css(get_resource_bytes("static/css/ai_chat.css"))
         fragment.add_javascript(get_resource_bytes("static/js/ai_chat.js"))
+
+        request_body = {
+            "edx_module_id": block_usage_key,
+        }
path: src/ol_openedx_chat/block.py
user: login: asadali145
body: > Maybe send video_edx_module_id and transcript_edx_module_id

edx_module_id is generic for videos and problems. I think `transcript_asset_id ` is good for now.
created_at: 2025-03-04T15:10:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -66,23 +71,51 @@ def student_view_aside(self, block, context=None):
         if not self.ol_chat_enabled:
             return fragment
 
+        block_usage_key = self.scope_ids.usage_id.usage_key
+        block_id = block_usage_key.block_id
+        block_type = getattr(block, "category", None)
+
         fragment.add_content(
             render_template(
                 "static/html/student_view.html",
                 {
-                    "block_key": self.scope_ids.usage_id.usage_key.block_id,
-                    "block_type": getattr(block, "category", None),
+                    "block_id": block_id,
+                    "block_usage_key": block_usage_key,
+                    "block_type": block_type,
                 },
             )
         )
         fragment.add_css(get_resource_bytes("static/css/ai_chat.css"))
         fragment.add_javascript(get_resource_bytes("static/js/ai_chat.js"))
+
+        request_body = {
+            "edx_module_id": block_usage_key,
+        }
path: src/ol_openedx_chat/block.py
user: login: ChristopherChudzicki
body: sorry—this was a mistaken comment on my part. I forgot/missed during my re-review that we add the transcript id below (of course, since it depends on block type). 


created_at: 2025-03-05T12:36:50Z
author_association: CONTRIBUTOR

PR comments:
user: login: asadali145
created_at: 2025-03-04T08:49:53Z
body: > Additionally: we should verify with whoever is building the Video chatbot (@arslanashraf7, I think?) that { block_id, block_usage_key, transcript_asset_id } are good names for the API to use. Seems OK to me.

I have removed the `block_id` from the `requestBody`, Now, we will have `block_usage_key` and `transcript_asset_id`.
author_association: CONTRIBUTOR
user: login: arslanashraf7
created_at: 2025-03-04T12:11:04Z
body: > This worked well for me. I have one small rename request if you agree with it.
> 
> Additionally: we should verify with whoever is building the Video chatbot (@arslanashraf7 , I think?) that `{ block_id, block_usage_key, transcript_asset_id }` are good names for the API to use. Seems OK to me.

I am fine with these two. But, MIT learn system treats the `block_usage_key` with name `edx_block_id`. Also, Keeping in mind @abeglova 's work in https://github.com/mitodl/hq/issues/6849, I think we should name `block_usage_key` to be `edx_module_id` to keep things consistent in MIT Learn.


author_association: CONTRIBUTOR
user: login: asadali145
created_at: 2025-03-04T12:17:35Z
body: @arslanashraf7 thanks, i can create the issues you mentioned. The PR should be good now as per your suggestions.
author_association: CONTRIBUTOR
user: login: arslanashraf7
created_at: 2025-03-04T14:04:38Z
body: @asadali145 Just one more thing, What URL would this chat widget from the video blocks hit? The video GPT when deployed would be accessible somewhere at `<LEARN_AI_BASE_URL>/http/video_gpt_agent/`.

On another thought @pdpinch @sovsey :

If the video doesn't have transcripts, Should the chat widget be visible? I ask this because if there is no transcript the video GPT would not be able to answer questions.
author_association: CONTRIBUTOR
user: login: asadali145
created_at: 2025-03-04T14:13:02Z
body: > @asadali145 Just one more thing, What URL would this chat widget from the video blocks hit? The video GPT when deployed would be accessible somewhere at <LEARN_AI_BASE_URL>/http/video_gpt_agent/.

For now, it is set to `/http/recommendation_agent/` through settings, We will have to make changes if videos have a different URL.
author_association: CONTRIBUTOR
user: login: sovsey
created_at: 2025-03-04T17:01:40Z
body: > If the video doesn't have transcripts, Should the chat widget be visible? I ask this because if there is no transcript the video GPT would not be able to answer questions.

If there aren't transcripts, can we hide the VideoGPT button? Thoughts @pdpinch ?
author_association: NONE
user: login: pdpinch
created_at: 2025-03-05T22:20:15Z
body: > If the video doesn't have transcripts, Should the chat widget be visible? I ask this because if there is no transcript the video GPT would not be able to answer questions.

I think this is too much business logic, which can lead to user confusion or unitended outcomes. We already have a checkbox in Studio for course authors to turn the chatbot on or off. 

Videos without transcripts should be rare (but not forbidden), so if this a serious concern we could add a warning to the Studio UI that it won't work without a transcript being set. 
author_association: MEMBER