Skip to content
Open
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
12 changes: 0 additions & 12 deletions backend/.env.example

This file was deleted.

1 change: 1 addition & 0 deletions backend/authentication/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def login_view(request):
user = authenticate(request, email=email, password=password)
if user is not None:
login(request, user)
request.session.save()
response = JsonResponse({"data": "Login successful"})

# Set session cookie manually
Expand Down
14 changes: 8 additions & 6 deletions backend/backend/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
FRONTEND_URL = os.environ["FRONTEND_URL"]
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
Expand Down Expand Up @@ -138,14 +139,15 @@
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

CORS_ALLOWED_ORIGINS = [
FRONTEND_URL,
"http://localhost:3000",
]
CORS_ALLOW_CREDENTIALS = True

#CORS_ALLOW_ALL_ORIGINS = True
CSRF_TRUSTED_ORIGINS = [
FRONTEND_URL,
"http://localhost:3000",
]

SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_SAMESITE = "None"
SESSION_COOKIE_SAMESITE = "Lax"
SESSION_COOKIE_SECURE = False
CSRF_COOKIE_SECURE = False
CSRF_COOKIE_HTTPONLY = False
5 changes: 3 additions & 2 deletions backend/chat/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from nested_admin.nested import NestedModelAdmin, NestedStackedInline, NestedTabularInline

from chat.models import Conversation, Message, Role, Version

from .models import Conversation

class RoleAdmin(NestedModelAdmin):
list_display = ["id", "name"]
Expand Down Expand Up @@ -51,9 +51,10 @@ def queryset(self, request, queryset):
class ConversationAdmin(NestedModelAdmin):
actions = ["undelete_selected", "soft_delete_selected"]
inlines = [VersionInline]
list_display = ("title", "id", "created_at", "modified_at", "deleted_at", "version_count", "is_deleted", "user")
list_display = ("title", "summary", "id", "created_at", "modified_at", "deleted_at", "version_count", "is_deleted", "user")
list_filter = (DeletedListFilter,)
ordering = ("-modified_at",)
readonly_fields = ('summary',)

def undelete_selected(self, request, queryset):
queryset.update(deleted_at=None)
Expand Down
18 changes: 18 additions & 0 deletions backend/chat/migrations/0002_conversation_summary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.2 on 2025-04-26 06:16

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("chat", "0001_initial"),
]

operations = [
migrations.AddField(
model_name="conversation",
name="summary",
field=models.TextField(blank=True, null=True),
),
]
23 changes: 20 additions & 3 deletions backend/chat/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def __str__(self):


class Conversation(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) # <-- Added default=uuid.uuid4
title = models.CharField(max_length=100, blank=False, null=False, default="Mock title")
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now=True)
Expand All @@ -22,6 +22,7 @@ class Conversation(models.Model):
)
deleted_at = models.DateTimeField(null=True, blank=True)
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
summary = models.TextField(blank=True, null=True)

def __str__(self):
return self.title
Expand All @@ -31,6 +32,17 @@ def version_count(self):

version_count.short_description = "Number of versions"

def generate_summary(self):
messages = []
if self.active_version:
messages = self.active_version.messages.all()[:3] # ⬅️ Limit to first 3 directly in query
else:
messages = Message.objects.filter(version__conversation=self).order_by("created_at")[:3] # ⬅️ Better fallback

contents = [msg.content for msg in messages]
return " | ".join(contents) if contents else "No messages yet"



class Version(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
Expand Down Expand Up @@ -58,8 +70,13 @@ class Meta:
ordering = ["created_at"]

def save(self, *args, **kwargs):
self.version.conversation.save()
super().save(*args, **kwargs)
is_new = self._state.adding # check if this is a new message
super().save(*args, **kwargs) # first, save the message normally

if is_new:
conversation = self.version.conversation
conversation.summary = conversation.generate_summary()
conversation.save()

def __str__(self):
return f"{self.role}: {self.content[:20]}..."
82 changes: 43 additions & 39 deletions backend/chat/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@


def should_serialize(validated_data, field_name) -> bool:
if validated_data.get(field_name) is not None:
return True
return validated_data.get(field_name) is not None


class TitleSerializer(serializers.Serializer):
Expand All @@ -24,21 +23,14 @@ class MessageSerializer(serializers.ModelSerializer):

class Meta:
model = Message
fields = [
"id", # DB
"content",
"role", # required
"created_at", # DB, read-only
]
fields = ["id", "content", "role", "created_at"]
read_only_fields = ["id", "created_at", "version"]

def create(self, validated_data):
message = Message.objects.create(**validated_data)
return message
return Message.objects.create(**validated_data)

def to_representation(self, instance):
representation = super().to_representation(instance)
representation["versions"] = [] # add versions field
return representation


Expand All @@ -52,12 +44,12 @@ class Meta:
model = Version
fields = [
"id",
"conversation_id", # DB
"conversation_id",
"root_message",
"messages",
"active",
"created_at", # DB, read-only
"parent_version", # optional
"created_at",
"parent_version",
]
read_only_fields = ["id", "conversation"]

Expand All @@ -72,17 +64,13 @@ def get_created_at(obj):
return timezone.localtime(obj.root_message.created_at)

def create(self, validated_data):
messages_data = validated_data.pop("messages")
messages_data = validated_data.pop("messages", [])
version = Version.objects.create(**validated_data)
for message_data in messages_data:
Message.objects.create(version=version, **message_data)

return version

def update(self, instance, validated_data):
instance.conversation = validated_data.get("conversation", instance.conversation)
instance.parent_version = validated_data.get("parent_version", instance.parent_version)
instance.root_message = validated_data.get("root_message", instance.root_message)
if not any(
[
should_serialize(validated_data, "conversation"),
Expand All @@ -93,15 +81,22 @@ def update(self, instance, validated_data):
raise ValidationError(
"At least one of the following fields must be provided: conversation, parent_version, root_message"
)

instance.conversation = validated_data.get("conversation", instance.conversation)
instance.parent_version = validated_data.get("parent_version", instance.parent_version)
instance.root_message = validated_data.get("root_message", instance.root_message)
instance.save()

messages_data = validated_data.pop("messages", [])
for message_data in messages_data:
if "id" in message_data:
message = Message.objects.get(id=message_data["id"], version=instance)
message.content = message_data.get("content", message.content)
message.role = message_data.get("role", message.role)
message.save()
try:
message = Message.objects.get(id=message_data["id"], version=instance)
message.content = message_data.get("content", message.content)
message.role = message_data.get("role", message.role)
message.save()
except Message.DoesNotExist:
raise ValidationError(f"Message with ID {message_data['id']} not found for this version.")
else:
Message.objects.create(version=instance, **message_data)

Expand All @@ -114,39 +109,48 @@ class ConversationSerializer(serializers.ModelSerializer):
class Meta:
model = Conversation
fields = [
"id", # DB
"title", # required
"id",
"title",
"active_version",
"versions", # optional
"modified_at", # DB, read-only
"versions",
"modified_at",
"summary",
]

def create(self, validated_data):
versions_data = validated_data.pop("versions", [])
conversation = Conversation.objects.create(**validated_data)
for version_data in versions_data:
version_serializer = VersionSerializer(data=version_data)
if version_serializer.is_valid():
version_serializer.save(conversation=conversation)

version_serializer.is_valid(raise_exception=True)
version_serializer.save(conversation=conversation)
return conversation

def update(self, instance, validated_data):
instance.title = validated_data.get("title", instance.title)
active_version_id = validated_data.get("active_version", instance.active_version)
if active_version_id is not None:
active_version = Version.objects.get(id=active_version_id)
instance.active_version = active_version

active_version_id = validated_data.get("active_version")
if active_version_id:
try:
active_version = Version.objects.get(id=active_version_id)
instance.active_version = active_version
except Version.DoesNotExist:
raise ValidationError(f"Active version with ID {active_version_id} not found.")

instance.save()

versions_data = validated_data.pop("versions", [])
for version_data in versions_data:
if "id" in version_data:
version = Version.objects.get(id=version_data["id"], conversation=instance)
version_serializer = VersionSerializer(version, data=version_data)
try:
version = Version.objects.get(id=version_data["id"], conversation=instance)
version_serializer = VersionSerializer(version, data=version_data)
except Version.DoesNotExist:
raise ValidationError(f"Version with ID {version_data['id']} not found for this conversation.")
else:
version_serializer = VersionSerializer(data=version_data)
if version_serializer.is_valid():
version_serializer.save(conversation=instance)

return instance
version_serializer.is_valid(raise_exception=True)
version_serializer.save(conversation=instance)

return instance
Loading