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 initial architecture for AI chat aside #393

Merged
merged 4 commits into from
Nov 18, 2024
Merged
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
41 changes: 41 additions & 0 deletions src/ol_openedx_chat/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
python_sources(
name="ol_openedx_chat",
dependencies=[
"src/ol_openedx_chat/settings:ol_chat_settings",
],
)

python_distribution(
name="ol_openedx_chat_package",
dependencies=[
":ol_openedx_chat",
"src/ol_openedx_chat/static/js:ol_chat_js",
"src/ol_openedx_chat/static/css:ol_chat_css",
"src/ol_openedx_chat/static/html:ol_chat_html",
],
provides=setup_py(
name="ol-openedx-chat",
version="0.1.0",
description="An Open edX plugin to add Open Learning AI chat aside to xBlocks",
license="BSD-3-Clause",
author="MIT Office of Digital Learning",
include_package_data=True,
zip_safe=False,
keywords="Python edx",
entry_points={
'xblock_asides.v1': [
'ol_openedx_chat = ol_openedx_chat.block:OLChatAside',
],
"lms.djangoapp": [
"ol_openedx_chat = ol_openedx_chat.apps:OLOpenedxChatConfig"
],
"cms.djangoapp": [
"ol_openedx_chat = ol_openedx_chat.apps:OLOpenedxChatConfig"
],
},
),
)

python_tests(
name="tests",
)
11 changes: 11 additions & 0 deletions src/ol_openedx_chat/CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Change Log
----------

..
All enhancements and patches to ol_openedx_chat will be documented
in this file. It adheres to the structure of https://keepachangelog.com/ ,
but in reStructuredText instead of Markdown (for ease of incorporation into
Sphinx documentation and the PyPI description).

This project adheres to Semantic Versioning (https://semver.org/).
.. There should always be an "Unreleased" section for changes pending release.
28 changes: 28 additions & 0 deletions src/ol_openedx_chat/LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Copyright (C) 2022 MIT Open Learning

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 changes: 31 additions & 0 deletions src/ol_openedx_chat/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
ol-openedx-chat
###############

An xBlock aside to add MIT Open Learning chat into xBlocks


Purpose
*******

MIT's AI chatbot for Open edX

Getting Started with Development
********************************


Deploying
*********

Getting Help
************

Documentation
=============

License
*******

The code in this repository is licensed under the AGPL 3.0 unless
otherwise noted.

Please see `LICENSE.txt <LICENSE.txt>`_ for details.
7 changes: 7 additions & 0 deletions src/ol_openedx_chat/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""
MIT's AI chatbot for Open edX
"""

__version__ = "0.1.0"

default_app_config = "ol_openedx_chat.apps.OLOpenedxChatConfig"
28 changes: 28 additions & 0 deletions src/ol_openedx_chat/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""
ol_openedx_chat Django application initialization.
"""

from django.apps import AppConfig
from edx_django_utils.plugins import PluginSettings
from openedx.core.djangoapps.plugins.constants import ProjectType, SettingsType


class OLOpenedxChatConfig(AppConfig):
"""
Configuration for the ol_openedx_chat Django application.
"""

name = "ol_openedx_chat"

plugin_app = {
PluginSettings.CONFIG: {
ProjectType.LMS: {
SettingsType.COMMON: {PluginSettings.RELATIVE_PATH: "settings.common"},
},
ProjectType.CMS: {
SettingsType.COMMON: {
PluginSettings.RELATIVE_PATH: "settings.cms_settings"
},
},
},
}
79 changes: 79 additions & 0 deletions src/ol_openedx_chat/block.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import pkg_resources
from django.template import Context, Template
from web_fragments.fragment import Fragment
from xblock.core import XBlockAside

BLOCK_PROBLEM_CATEGORY = "problem"
MULTIPLE_CHOICE_TYPE = "multiplechoiceresponse"


def get_resource_bytes(path):
"""
Helper method to get the unicode contents of a resource in this repo.

Args:
path (str): The path of the resource

Returns:
unicode: The unicode contents of the resource at the given path
""" # noqa: D401
resource_contents = pkg_resources.resource_string(__name__, path)
return resource_contents.decode("utf-8")


def render_template(template_path, context=None):
"""
Evaluate a template by resource path, applying the provided context.
"""
context = context or {}
template_str = get_resource_bytes(template_path)
template = Template(template_str)
return template.render(Context(context))


class OLChatAside(XBlockAside):
"""
XBlock aside that enables OL AI Chat functionality for an XBlock
"""

@XBlockAside.aside_for("student_view")
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"))
return fragment

@XBlockAside.aside_for("author_view")
def author_view_aside(self, block, context=None): # noqa: ARG002
"""
Renders the aside contents for the author view
""" # noqa: D401
fragment = Fragment("")
fragment.add_content(render_template("static/html/studio_view.html"))
return fragment

@classmethod
def should_apply_to_block(cls, block):
"""
Overrides base XBlockAside implementation. Indicates whether or not this aside
should apply to a given block.

Due to the different ways that the Studio and LMS runtimes construct XBlock
instances, the problem type of the given block needs to be retrieved in
different ways.
""" # noqa: D401
if getattr(block, "category", None) != BLOCK_PROBLEM_CATEGORY:
return False
block_problem_types = None
# LMS passes in the block instance with `problem_types` as a property of
# `descriptor`
if hasattr(block, "descriptor"):
block_problem_types = getattr(block.descriptor, "problem_types", None)
# Studio passes in the block instance with `problem_types` as a top-level property # noqa: E501
elif hasattr(block, "problem_types"):
block_problem_types = block.problem_types
# We only want this aside to apply to the block if the problem is multiple
# choice AND there are not multiple problem types.
return block_problem_types == {MULTIPLE_CHOICE_TYPE}
1 change: 1 addition & 0 deletions src/ol_openedx_chat/settings/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
python_sources(name="ol_chat_settings")
11 changes: 11 additions & 0 deletions src/ol_openedx_chat/settings/cms_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# noqa: INP001
"""Settings to provide to edX"""


def plugin_settings(settings): # noqa: ARG001
"""
Populate CMS settings
"""


DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
11 changes: 11 additions & 0 deletions src/ol_openedx_chat/settings/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# noqa: INP001
"""Settings to provide to edX"""


def plugin_settings(settings): # noqa: ARG001
"""
Populate settings
"""


DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
10 changes: 10 additions & 0 deletions src/ol_openedx_chat/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[isort]
include_trailing_comma = True
indent = ' '
line_length = 120
multi_line_output = 3
skip=
migrations

[wheel]
universal = 1
4 changes: 4 additions & 0 deletions src/ol_openedx_chat/static/css/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
resources(
name="ol_chat_css",
sources=["*.css"],
)
4 changes: 4 additions & 0 deletions src/ol_openedx_chat/static/html/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
resources(
name="ol_chat_html",
sources=["*.html"],
)
3 changes: 3 additions & 0 deletions src/ol_openedx_chat/static/html/student_view.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div>
<button type="button" onclick="alert('Hello World!')">Hello World!</button>
</div>
25 changes: 25 additions & 0 deletions src/ol_openedx_chat/static/html/studio_view.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<div>

<div class="textbox-container">
<label for="gpt_version">Add a GPT model</label>
<input
type="text"
id="gpt_version"
name="gpt_verson"
placeholder="Enter GPT model version"
pattern="[A-Za-z0-9]"
title="Please add a GPT model name">
</div>

<div class="textbox-container">
<label for="add_prompt">Add GPT prompt</label>
<input
type="text"
id="add_prompt"
name="add_prompt"
placeholder="Pleae add prompt context"
pattern="[A-Za-z0-9]"
title="Please add a GPT Prompt">
</div>

</div>
4 changes: 4 additions & 0 deletions src/ol_openedx_chat/static/js/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
resources(
name="ol_chat_js",
sources=["src_js/*.js","lib/*.js"],
)
Empty file.
41 changes: 41 additions & 0 deletions src/ol_openedx_chat/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Pytest config"""

import json
import logging
from pathlib import Path

import pytest

BASE_DIR = Path(__file__).parent.absolute()


def pytest_addoption(parser):
"""Pytest hook that adds command line options"""
parser.addoption(
"--disable-logging",
action="store_true",
default=False,
help="Disable all logging during test run",
)
parser.addoption(
"--error-log-only",
action="store_true",
default=False,
help="Disable all logging output below 'error' level during test run",
)


def pytest_configure(config):
"""Pytest hook that runs after command line options have been parsed"""
if config.getoption("--disable-logging"):
logging.disable(logging.CRITICAL)
elif config.getoption("--error-log-only"):
logging.disable(logging.WARNING)


@pytest.fixture()
def example_event(request): # noqa: PT004
"""An example real event captured previously""" # noqa: D401
with Path.open(BASE_DIR / ".." / "test_data" / "example_event.json") as f:
request.cls.example_event = json.load(f)
yield
8 changes: 8 additions & 0 deletions src/ol_openedx_chat/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""
URLs for ol_openedx_chat.
"""

urlpatterns = [
# Fill in URL patterns and views here.
# re_path(r'', TemplateView.as_view(template_name="ol_openedx_chat/base.html")), # noqa: ERA001, E501
]
Loading