Skip to content

Commit

Permalink
Allow direct-link to tutor chat w/ a context; update context link UI.
Browse files Browse the repository at this point in the history
  • Loading branch information
liffiton committed Jul 23, 2024
1 parent 9a57351 commit 7fee59b
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 23 deletions.
71 changes: 51 additions & 20 deletions src/codehelp/templates/context_config.html
Original file line number Diff line number Diff line change
Expand Up @@ -163,42 +163,73 @@ <h3 class="title is-4">Schedule '<span x-text="ctx.name"></span>'</h3>
<td class="has-text-centered">
<form method="post">
<span x-data="{
link_URL: '{{ url_for('helper.help_form', class_id=auth['class_id'], ctx_name='__replace__', _external=True) }}'.replace('__replace__', encodeURIComponent(ctx.name)),
copied: false,
showLinkModal: false,
showModal() {
this.copied = false;
this.showLinkModal = true;
},
copy_url() {
navigator.clipboard.writeText(this.link_URL);
this.copied = true;
},
}">
{% macro link_display_alpinejs(route) -%}
{# unholy combination of Jinja templating (route available in python) and Javascript string replacement (context available in JS)... #}
{
link_URL: '{{ url_for(route, class_id=auth['class_id'], ctx_name='__replace__', _external=True) }}'.replace('__replace__', encodeURIComponent(ctx.name)),
copied: false,
copy_url() {
navigator.clipboard.writeText(this.link_URL);
this.copied = true;
setTimeout(() => {this.copied = false;}, 2000);
},
}
{%- endmacro %}
<button x-bind:title="`link to '${ctx.name}'`" class="button is-white is-small has-text-grey" type="button" @click="showModal">
<svg aria-hidden="true" class="icon is-small"><use href="#svg_link" /></svg>
</button>
<div class="modal" x-bind:class="{'is-active': showLinkModal}" @keydown.escape.window="showLinkModal = false;">
<div class="modal-background" @click="showLinkModal = false;"></div>
<div class="modal-content">
<div class="box has-text-left">
<h3 class="title is-4">Link to '<span x-text="ctx.name"></span>'</h3>
<p>This link will take your students directly to the request page with the '<span x-text="ctx.name"></span>' context pre-selected.</p>
<div style="display: flex; flex-wrap: wrap; gap: 1em;">
<div class="control">
<span class="input is-size-5" style="max-width: 100%; overflow-x: auto;" x-text="link_URL"></span>
<h3 class="title is-4">
<svg aria-hidden="true" class="icon"><use href="#svg_link" /></svg>
Link to '<span x-text="ctx.name"></span>'
</h3>
<p>Take your students directly to CodeHelp with the '<span x-text="ctx.name"></span>' context pre-selected:</p>
<div class="field has-addons is-horizontal" x-data="{{ link_display_alpinejs('helper.help_form') }}">
<div class="field-label label is-normal">Help Form:</div>
<div class="field-body" style="flex-grow: 5;">
<div class="control">
<span class="input" x-text="link_URL"></span>
</div>
<div class="control">
<button type="button" class="button icon-text" x-bind:class="copied ? 'is-success' : 'is-link'" @click="copy_url">
<svg aria-hidden="true" class="icon is-right"><use href="#svg_copy" /></svg>
<span x-text="copied ? 'copied' : 'copy'"></span>
</button>
</div>
</div>
<div class="control">
<button type="button" class="button icon-text is-size-5" x-bind:class="copied ? 'is-success' : 'is-link'" @click="copy_url">
<svg aria-hidden="true" class="icon is-right"><use href="#svg_copy" /></svg>
<span x-text="copied ? 'copied' : 'copy'"></span>
</button>
</div>
{% if "chats_experiment" in auth['class_experiments'] %}
<div class="field has-addons is-horizontal" x-data="{{ link_display_alpinejs('tutor.tutor_form') }}">
<div class="field-label label is-normal">Tutor Chat:</div>
<div class="field-body" style="flex-grow: 5;">
<div class="control">
<span class="input" x-text="link_URL"></span>
</div>
<div class="control">
<button type="button" class="button icon-text" x-bind:class="copied ? 'is-success' : 'is-link'" @click="copy_url">
<svg aria-hidden="true" class="icon is-right"><use href="#svg_copy" /></svg>
<span x-text="copied ? 'copied' : 'copy'"></span>
</button>
</div>
</div>
</div>
<div class="mt-4">
<p>The link will let students access this context even if it is currently hidden.</p>
<p class="has-text-danger-dark">Students must have joined this class before they use the link.</p>
<p class="has-text-danger-dark">If the class connects using LTI, students must log in via LTI before they use the link.</p>
{% endif %}
<div class="notification content is-warning is-light" style="font-size: 85%;">
<p>Context links will let students access the context even if it is currently hidden.</p>
<p>Context links will <b>not</b> register a student in the class:</p>
<ul>
<li>Students must have already joined this class before they use a context link.</li>
<li>If the class connects using LTI, students must log in via LTI from your LMS before they use a context link.</li>
</ul>
</div>
</div>
</div>
Expand Down
28 changes: 25 additions & 3 deletions src/codehelp/tutor.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from gened.admin import bp as bp_admin
from gened.admin import register_admin_link
from gened.auth import get_auth, login_required
from gened.classes import switch_class
from gened.db import get_db
from gened.experiments import experiment_required
from gened.openai import LLMDict, get_completion, with_llm
Expand Down Expand Up @@ -55,12 +56,33 @@ def before_request() -> None:


@bp.route("/")
def tutor_form() -> str:
contexts_list = get_available_contexts()
@bp.route("/ctx/<int:class_id>/<string:ctx_name>")
def tutor_form(class_id: int | None = None, ctx_name: str | None = None) -> str | Response:

if class_id is not None:
success = switch_class(class_id)
if not success:
# Can't access the specified context
flash("Cannot access class and context. Make sure you are logged in correctly before using this link.", "danger")
return make_response(render_template("error.html"), 400)

# we may select a context from a given ctx_name, from a given query_id, or from the user's most recently-used context
selected_context_name = None
if ctx_name is not None:
# see if the given context is part of the current class (whether available or not)
context = get_context_by_name(ctx_name)
if context is None:
flash(f"Context not found: {ctx_name}", "danger")
return make_response(render_template("error.html"), 404)
contexts_list = [context] # this will be the only context in this page -- no other options
selected_context_name = ctx_name
else:
contexts_list = get_available_contexts()

# turn into format we can pass to js via JSON
contexts = {ctx.name: ctx.desc_html() for ctx in contexts_list}

selected_context_name = None
# regardless, if there is only one context, select it
if len(contexts) == 1:
selected_context_name = next(iter(contexts.keys()))

Expand Down

0 comments on commit 7fee59b

Please sign in to comment.