-{% endblock %}
\ No newline at end of file
+{% endif %}
+{% endblock %}
diff --git a/src/accounts/templates/accounts/register.html b/src/accounts/templates/accounts/register.html
index b42a550..428af0b 100644
--- a/src/accounts/templates/accounts/register.html
+++ b/src/accounts/templates/accounts/register.html
@@ -3,6 +3,121 @@
{% block title %}Register{% endblock %}
{% block content %}
+{% if not user.is_authenticated %}
+
@@ -13,7 +128,7 @@
Create an Account
+{% endif %}
{% endblock %}
diff --git a/src/conversations/forms.py b/src/conversations/forms.py
new file mode 100644
index 0000000..1095f9d
--- /dev/null
+++ b/src/conversations/forms.py
@@ -0,0 +1,32 @@
+"""Forms for the conversations app."""
+
+from django import forms
+
+from .models import TeacherFeedback
+
+
+class TeacherFeedbackForm(forms.ModelForm):
+ """Form for teachers to submit/update feedback on a student conversation."""
+
+ class Meta:
+ model = TeacherFeedback
+ fields = ["feedback_type", "feedback"]
+ widgets = {
+ "feedback": forms.Textarea(
+ attrs={
+ "rows": 5,
+ "class": "form-control",
+ "placeholder": (
+ "Write feedback for the student — what is strong, "
+ "what needs revision, and any next steps."
+ ),
+ }
+ ),
+ "feedback_type": forms.Select(attrs={"class": "form-select"}),
+ }
+
+ def clean_feedback(self):
+ value = (self.cleaned_data.get("feedback") or "").strip()
+ if not value:
+ raise forms.ValidationError("Feedback cannot be empty.")
+ return value
diff --git a/src/conversations/migrations/0007_teacherfeedback.py b/src/conversations/migrations/0007_teacherfeedback.py
new file mode 100644
index 0000000..0c51658
--- /dev/null
+++ b/src/conversations/migrations/0007_teacherfeedback.py
@@ -0,0 +1,111 @@
+# Generated by Django 5.2.13 on 2026-05-23 00:24
+
+import django.core.validators
+import django.db.models.deletion
+import uuid
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ (
+ "conversations",
+ "0006_alter_homeworkprogresswidgetresponse_post_value_and_more",
+ ),
+ ("homeworks", "0008_homeworkprogresswidget"),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="TeacherFeedback",
+ fields=[
+ (
+ "id",
+ models.UUIDField(
+ default=uuid.uuid4,
+ editable=False,
+ primary_key=True,
+ serialize=False,
+ ),
+ ),
+ (
+ "feedback",
+ models.TextField(
+ validators=[django.core.validators.MinLengthValidator(1)]
+ ),
+ ),
+ (
+ "feedback_type",
+ models.CharField(
+ choices=[
+ ("general", "General"),
+ ("needs_revision", "Needs revision"),
+ ("good_work", "Good work"),
+ ("clarify_reasoning", "Clarify reasoning"),
+ ],
+ default="general",
+ max_length=32,
+ ),
+ ),
+ ("created_at", models.DateTimeField(auto_now_add=True)),
+ ("updated_at", models.DateTimeField(auto_now=True)),
+ (
+ "conversation",
+ models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="teacher_feedback",
+ to="conversations.conversation",
+ ),
+ ),
+ (
+ "section",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="teacher_feedback",
+ to="homeworks.section",
+ ),
+ ),
+ (
+ "student",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="received_feedback",
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ (
+ "submission",
+ models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name="teacher_feedback",
+ to="conversations.submission",
+ ),
+ ),
+ (
+ "teacher",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="authored_feedback",
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ ],
+ options={
+ "db_table": "conversations_teacher_feedback",
+ "ordering": ["-updated_at"],
+ "constraints": [
+ models.UniqueConstraint(
+ condition=models.Q(("conversation__isnull", False)),
+ fields=("teacher", "conversation"),
+ name="uniq_teacher_feedback_per_conversation",
+ )
+ ],
+ },
+ ),
+ ]
diff --git a/src/conversations/models.py b/src/conversations/models.py
index 17fb524..5961002 100644
--- a/src/conversations/models.py
+++ b/src/conversations/models.py
@@ -280,3 +280,80 @@ def save(self, *args, **kwargs):
if self.post_value is not None and self.post_submitted_at is None:
self.post_submitted_at = timezone.now()
super().save(*args, **kwargs)
+
+
+class TeacherFeedback(models.Model):
+ """Teacher-authored feedback on a student's conversation/section.
+
+ Visible to the student who owns the conversation and to teachers/TAs of
+ the same course context. One feedback per (teacher, conversation) pair;
+ teachers can update their own feedback.
+ """
+
+ FEEDBACK_TYPE_GENERAL = "general"
+ FEEDBACK_TYPE_NEEDS_REVISION = "needs_revision"
+ FEEDBACK_TYPE_GOOD_WORK = "good_work"
+ FEEDBACK_TYPE_CLARIFY_REASONING = "clarify_reasoning"
+
+ FEEDBACK_TYPE_CHOICES = [
+ (FEEDBACK_TYPE_GENERAL, "General"),
+ (FEEDBACK_TYPE_NEEDS_REVISION, "Needs revision"),
+ (FEEDBACK_TYPE_GOOD_WORK, "Good work"),
+ (FEEDBACK_TYPE_CLARIFY_REASONING, "Clarify reasoning"),
+ ]
+
+ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
+ teacher = models.ForeignKey(
+ "accounts.User",
+ on_delete=models.CASCADE,
+ related_name="authored_feedback",
+ )
+ student = models.ForeignKey(
+ "accounts.User",
+ on_delete=models.CASCADE,
+ related_name="received_feedback",
+ )
+ section = models.ForeignKey(
+ "homeworks.Section",
+ on_delete=models.CASCADE,
+ related_name="teacher_feedback",
+ )
+ conversation = models.ForeignKey(
+ Conversation,
+ on_delete=models.CASCADE,
+ related_name="teacher_feedback",
+ null=True,
+ blank=True,
+ )
+ submission = models.ForeignKey(
+ Submission,
+ on_delete=models.SET_NULL,
+ related_name="teacher_feedback",
+ null=True,
+ blank=True,
+ )
+ feedback = models.TextField(validators=[MinLengthValidator(1)])
+ feedback_type = models.CharField(
+ max_length=32,
+ choices=FEEDBACK_TYPE_CHOICES,
+ default=FEEDBACK_TYPE_GENERAL,
+ )
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+ class Meta:
+ db_table = "conversations_teacher_feedback"
+ ordering = ["-updated_at"]
+ constraints = [
+ models.UniqueConstraint(
+ fields=["teacher", "conversation"],
+ condition=models.Q(conversation__isnull=False),
+ name="uniq_teacher_feedback_per_conversation",
+ ),
+ ]
+
+ def __str__(self):
+ return (
+ f"Feedback by {self.teacher.username} for "
+ f"{self.student.username} on section {self.section_id}"
+ )
diff --git a/src/conversations/templates/conversations/detail.html b/src/conversations/templates/conversations/detail.html
index dce160b..b6eecd8 100644
--- a/src/conversations/templates/conversations/detail.html
+++ b/src/conversations/templates/conversations/detail.html
@@ -9,6 +9,478 @@
{% endblock %}
{% block content %}
+{% if request.user.student_profile and not request.user.teacher_profile and not conversation_data.is_teacher_test %}
+{% comment %}STUDENT branch — wraps existing chat surface in shell. JS hooks preserved.{% endcomment %}
+
+ {% include "student/_sidebar.html" with active="homework" %}
+
+
+
+
+
+
+
+
AI Tutor conversation
+
{{ conversation_data.section_title }}
+
Work through your reasoning with the AI tutor before submitting.
+
+ {% if user.id == conversation_data.user_id %}
+
+
+ {% if conversation_data.can_submit %}
+
+ {% endif %}
+
+ {% endif %}
+
+
+
+
+ {# ====== EXISTING CHAT SURFACE — JS hooks must stay intact ====== #}
+
+ {% if timeline %}
+ {% for item in timeline %}
+ {% if item.type == 'message' %}
+ {% with message=item.data %}
+
+
+
+ {% if message.message_type == 'code' %}
+
+
+
{{ message.content }}
+
+
+ {% elif message.message_type == 'code_execution' %}
+
{{ message.content }}
+ {% else %}
+
+
+
+
+
+ {% endif %}
+
+
+ {% endwith %}
+ {% endif %}
+ {% endfor %}
+ {% else %}
+
+ Send your first message to start the conversation.
+
+ {% endif %}
+
+
+
+ {# Message input form — students only #}
+ {% if user.id == conversation_data.user_id %}
+
+ {% endif %}
+
+
+ {% if teacher_feedback_list %}
+
+ Teacher Feedback
+ From your instructor
+
+ {% for fb in teacher_feedback_list %}
+
+
+ {{ fb.teacher.username }}
+
+ {{ fb.get_feedback_type_display }}
+
+ {{ fb.updated_at|date:"M d, Y H:i" }}
+
+
{{ fb.feedback }}
+
+ {% endfor %}
+
+
+ {% endif %}
+
+ {% if conversation_data.homework_id %}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+
+{% elif request.user.teacher_profile %}
+{% comment %}TEACHER branch — dark modern conversation workspace. JS hooks preserved.{% endcomment %}
+
+ {% include "teacher/_sidebar.html" with active="homework" %}
+
+
+
+
+
+
+
+
+ {% if conversation_data.is_teacher_test %}Instructor test conversation{% else %}Student conversation{% endif %}
+
+
{{ conversation_data.section_title }}
+
+ {% if conversation_data.is_teacher_test %}
+ Test conversation under your account. Use it to verify prompts and AI responses.
+ {% else %}
+ Read-only view of a student's AI tutor conversation for this section.
+ {% endif %}
+
+
+ {% if not conversation_data.is_teacher_test and user.id == conversation_data.user_id %}
+
+
+ {% csrf_token %}
+
+ Delete & Start New
+
+
+ {% if conversation_data.can_submit %}
+
+ {% csrf_token %}
+
+ Submit Conversation
+
+
+ {% endif %}
+
+ {% endif %}
+
+
+ {% if conversation_data.is_teacher_test %}
+
+ You're viewing this conversation as a teacher.
+
+ {% endif %}
+
+ {% if conversation_data.paste_events and user.id != conversation_data.user_id %}
+
+ {{ conversation_data.paste_events|length }} paste event{{ conversation_data.paste_events|length|pluralize }} detected
+ in this conversation (shown inline below).
+
+ {% endif %}
+
+
+
+ {# ====== EXISTING CHAT SURFACE — JS hooks must stay intact ====== #}
+
+ {% if timeline %}
+ {% for item in timeline %}
+ {% if item.type == 'message' %}
+ {% with message=item.data %}
+
+
+
+ {% if message.message_type == 'code' %}
+
+
+
{{ message.content }}
+
+
+ {% elif message.message_type == 'code_execution' %}
+
{{ message.content }}
+ {% else %}
+
+
+
+
+
+ {% endif %}
+
+
+ {% endwith %}
+ {% elif item.type == 'paste_event' %}
+ {% with paste_event=item.data %}
+
+
+
+
+ Click to view pasted content
+ {{ paste_event.pasted_content }}
+
+
+
+ {% endwith %}
+ {% elif item.type == 'rapid_text_growth_event' %}
+ {% with rapid_text_growth_event=item.data %}
+
+
+
+
+ Click to view added text
+ {{ rapid_text_growth_event.added_text }}
+
+
+
+ {% endwith %}
+ {% endif %}
+ {% endfor %}
+ {% else %}
+
No messages in this conversation yet.
+ {% endif %}
+
+
+
+ {# Message composer — only if this is the teacher's own conversation #}
+ {% if user.id == conversation_data.user_id %}
+
+
+ {% csrf_token %}
+
+ Your message
+
+
+
+
+
+ {% elif conversation_data.is_teacher_test %}
+
+ You are viewing this conversation as a teacher and cannot send messages.
+
+ {% else %}
+
+ Conversation review is read-only.
+
+ {% endif %}
+
+
+ {# Teacher feedback — saved entries and submission form #}
+ {% if teacher_feedback_list or can_submit_feedback %}
+
+ Teacher Feedback
+ Feedback on this conversation
+
+ {% if teacher_feedback_list %}
+
+ {% for fb in teacher_feedback_list %}
+
+
+ {{ fb.teacher.username }}
+
+ {{ fb.get_feedback_type_display }}
+
+ {{ fb.updated_at|date:"M d, Y H:i" }}
+
+
{{ fb.feedback }}
+
+ {% endfor %}
+
+ {% endif %}
+
+ {% if can_submit_feedback %}
+
+ {% csrf_token %}
+
+
+ Feedback type
+ {{ feedback_form.feedback_type }}
+
+
+
Feedback
+ {{ feedback_form.feedback }}
+ {% if feedback_form.feedback.errors %}
+
{{ feedback_form.feedback.errors.0 }}
+ {% endif %}
+
+
+
+
+ {% if my_feedback %}Update Feedback{% else %}Save Feedback{% endif %}
+
+
+
+ {% endif %}
+
+ {% endif %}
+
+ {% if conversation_data.homework_id %}
+
+ {% else %}
+
+ {% endif %}
+
+
+
+
+{% else %}
+{% comment %}TA / fallback branch — original markup{% endcomment %}
@@ -61,7 +533,7 @@
Conversation for {{ conversation_data.section_title }}
{{ conversation_data.paste_events|length }} paste event{{ conversation_data.paste_events|length|pluralize }} detected in this conversation (shown inline below).
{% endif %}
-
+
{% if timeline %}
@@ -164,7 +636,7 @@
Conversation for {{ conversation_data.section_title }}
{% endif %}
-
+
{% if user.id == conversation_data.user_id %}
@@ -184,7 +656,7 @@
Conversation for {{ conversation_data.section_title }}
Chat Message
-
+
R Code
@@ -201,13 +673,13 @@ Conversation for {{ conversation_data.section_title }}
{% elif conversation_data.is_teacher_test %}
-
+
You are viewing this conversation as a teacher and cannot send messages.
{% endif %}
-
+
{% if conversation_data.homework_id %}