Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add basic lms chat #398

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions src/ol_openedx_chat/block.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import pkg_resources
from django.template import Context, Template
from web_fragments.fragment import Fragment
from xblock.core import XBlockAside
from xblock.core import XBlockAside, XBlock
from webob.response import Response

from xmodule.video_block.transcripts_utils import get_transcript_from_contentstore
import openai

openai.api_key = "<API_KEY"

BLOCK_PROBLEM_CATEGORY = "problem"
MULTIPLE_CHOICE_TYPE = "multiplechoiceresponse"
Expand Down Expand Up @@ -42,9 +48,38 @@ def student_view_aside(self, block, context=None): # noqa: ARG002
Renders the aside contents for the student view
""" # noqa: D401
fragment = Fragment("")
fragment.add_content(render_template("static/html/student_view.html"))
if getattr(block, "category", None) == "video":
content, filename, mimetype = get_transcript_from_contentstore(block, 'en', 'txt', block.get_transcripts_info())
prompt = (
"Summarize the following video transcript into a concise, informative summary:\n\n"
f"{content}"
)
response = openai.ChatCompletion.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "You are an expert at summarizing content."},
{"role": "user", "content": prompt},
],
max_tokens=200,
temperature=0.5,
)
print("\n\n\n", response["choices"][0]["message"]["content"], "\n\n\n")
return fragment

fragment.add_content(render_template("static/html/student_view.html", {"block_key": self.scope_ids.usage_id.usage_key.block_id}))
fragment.add_css(get_resource_bytes("static/css/ai_chat.css"))
fragment.add_javascript(get_resource_bytes("static/js/ai_chat.js"))
fragment.initialize_js("AiChatAsideView", json_args={"test_arg": "test_value"})
return fragment

@XBlock.handler
def mock_handler(self, request=None, suffix=None):
print("\n\n\n")
print(request.POST)
print(suffix)
print("\n\n\n")
return Response(json_body={"message": "Hello, This your MIT Teaching Assistant. (A Server message)"})

@XBlockAside.aside_for("author_view")
def author_view_aside(self, block, context=None): # noqa: ARG002
"""
Expand All @@ -64,6 +99,9 @@ def should_apply_to_block(cls, block):
instances, the problem type of the given block needs to be retrieved in
different ways.
""" # noqa: D401
if getattr(block, "category", None) == 'video':
return True

if getattr(block, "category", None) != BLOCK_PROBLEM_CATEGORY:
return False
block_problem_types = None
Expand Down
140 changes: 140 additions & 0 deletions src/ol_openedx_chat/static/css/ai_chat.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/* General Reset */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: Arial, sans-serif;
}

/* Chat Button */
.chat-button {
position: relative;
bottom: 70px;
left: 300px;
margin-left: auto;
width: fit-content;
background-color: #A31F34;
color: white;
padding: 10px 15px;
border-radius: 50px;
cursor: pointer;
font-size: 16px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
}

/* Chat Window */
.chat-window {
position: relative;
bottom: 450px;
left: 300px;
margin-left: auto;
width: 300px;
height: 400px;
background-color: white;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
z-index: 1001;
display: none; /* Initially hidden */
flex-direction: column;
overflow: hidden;
}

/* Chat Header */
.chat-header {
background-color: #A31F34;
color: white;
padding: 10px;
font-size: 18px;
font-weight: bold;
display: flex;
justify-content: space-between;
align-items: center;
}

/* Close Chat Button */
.close-chat {
cursor: pointer;
font-size: 20px;
font-weight: bold;
}

/* Chat Body */
.chat-body {
flex: 1; /* Ensures it takes up available space */
padding: 10px;
overflow-y: auto; /* Enables vertical scrolling */
background-color: #f9f9f9;
max-height: 300px; /* Limit the height of the chat body */
scrollbar-width: thin; /* Custom scrollbar for modern browsers */
scrollbar-color: #A31F34 #f9f9f9;
}

/* Optional: Styling for scrollbars in webkit browsers (Chrome, Safari) */
.chat-body::-webkit-scrollbar {
width: 8px; /* Width of the scrollbar */
}

.chat-body::-webkit-scrollbar-thumb {
background-color: #A31F34; /* Color of the scrollbar thumb */
border-radius: 4px; /* Roundness of the scrollbar thumb */
}

.chat-body::-webkit-scrollbar-track {
background-color: #f9f9f9; /* Color of the scrollbar track */
}

.bot-message {
background-color: #e0e0e0;
color: #333;
padding: 10px;
border-radius: 10px;
margin: 5px 0;
max-width: 80%;
}

.user-message {
background-color: #A31F34;
color: white;
padding: 10px;
border-radius: 10px;
margin: 5px 0;
max-width: 80%;
align-self: flex-end;
}

/* Chat Footer */
.chat-footer {
display: flex;
border-top: 1px solid #ddd;
position: absolute;
bottom: 0;
width: inherit;
}

.chat-input {
flex: 1;
border: none;
padding: 10px;
font-size: 14px;
}

.chat-input:focus {
outline: none;
}

.send-button {
background-color: #A31F34;
color: white;
border: none;
padding: 10px 15px;
cursor: pointer;
font-size: 14px;
}

.send-button:hover {
background-color: #A31F34;
}
26 changes: 25 additions & 1 deletion src/ol_openedx_chat/static/html/student_view.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
<div>
<button type="button" onclick="alert('Hello World!')">Hello World!</button>
<!-- Chat Button -->
<div class="chat-button" id="chat-button-{{ block_key }}" data-block-key="{{ block_key }}">
Help me with this problem
</div>

<!-- Chat Window -->
<div class="chat-window" id="chat-window-{{ block_key }}" data-block-key="{{ block_key }}">
<div class="chat-header">
<span>MIT Teaching Assistant</span>
<span class="close-chat" id="close-chat-{{ block_key }}" data-block-key="{{ block_key }}">&times;</span>
</div>
<div class="chat-body">
<p class="bot-message">Hi! How can I assist you today?</p>
</div>
<div class="chat-footer">
<input type="text" class="chat-input" placeholder="Type your message here...">
<button class="send-button">Send</button>
</div>
</div>
</div>
<script
defer
id="ze-snippet"
type='application/javascript'
src="https://static.zdassets.com/ekr/snippet.js?key=8ef9ef96-3317-40a9-8ef6-de0737503caa"
></script>
2 changes: 1 addition & 1 deletion src/ol_openedx_chat/static/js/BUILD
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
resources(
name="ol_chat_js",
sources=["src_js/*.js","lib/*.js"],
sources=["*.js"],
)
76 changes: 76 additions & 0 deletions src/ol_openedx_chat/static/js/ai_chat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
function AiChatAsideView(runtime, element, block_element, init_args) {
$('.chat-button').on('click', function () {
const blockKey = $(this).data("block-key")
const chatWindowSelector = '#chat-window-' + blockKey
const chatButtonSelector = '#chat-button-' + blockKey
$(chatWindowSelector).fadeIn();
$(chatButtonSelector).hide();
});

// Close chat window
$('.close-chat').on('click', function () {
const blockKey = $(this).data("block-key")
const chatWindowSelector = '#chat-window-' + blockKey
const chatButtonSelector = '#chat-button-' + blockKey
$(chatWindowSelector).fadeOut();
$(chatButtonSelector).fadeIn();
});

// Send message
function sendMessage() {
const message = $('.chat-input').val().trim();
if (message !== '') {
// Display user message
const userMessage = $('<p>')
.addClass('user-message')
.text(message);
$('.chat-body').append(userMessage);

// Clear input field
$('.chat-input').val('');

// Simulate bot response
// setTimeout(() => {
// const botMessage = $('<p>')
// .addClass('bot-message')
// .text('Thanks for reaching out! We will get back to you shortly.');
// $('.chat-body').append(botMessage);
//
// // Scroll to the bottom of the chat body
// $('.chat-body').scrollTop($('.chat-body')[0].scrollHeight);
// }, 1000);

// Scroll to the bottom after user message
$('.chat-body').scrollTop($('.chat-body')[0].scrollHeight);

$.ajax({
type: "POST",
url: runtime.handlerUrl(element, 'mock_handler'),

Choose a reason for hiding this comment

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

Question: Does tthis call the problem-specific OLChatAside mock_handler method? So the actual URL might be like problem/uuid-1234-xyz/?

And is this way, the chat API knows the context—which problem is it helping with?

Am I understanding that roughly correctly?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, You are spot on.

data: JSON.stringify({"message": message}),
success: function(resp) {
console.log(resp.message)
setTimeout(() => {
const botMessage = $('<p>')
.addClass('bot-message')
.text(resp.message);
$('.chat-body').append(botMessage);

$('.chat-body').scrollTop($('.chat-body')[0].scrollHeight);
}, 1000
);
}
});
}
}

// Send message on button click
$('.send-button').on('click', sendMessage);

// Send message on pressing Enter
$('.chat-input').on('keypress', function (event) {
if (event.which === 13) {
sendMessage();
}
});
console.log(init_args)
}
Loading