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
20 changes: 14 additions & 6 deletions backend/chat/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def display_desc(self, obj):

class MessageInline(NestedTabularInline):
model = Message
extra = 2 # number of extra forms to display
extra = 2


class VersionInline(NestedStackedInline):
Expand Down Expand Up @@ -51,7 +51,17 @@ 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",
"id",
"created_at",
"modified_at",
"deleted_at",
"version_count",
"is_deleted",
"user",
"summary", # 🔥 Show summary in admin
)
list_filter = (DeletedListFilter,)
ordering = ("-modified_at",)

Expand All @@ -68,10 +78,8 @@ def soft_delete_selected(self, request, queryset):
def get_action_choices(self, request, **kwargs):
choices = super().get_action_choices(request)
for idx, choice in enumerate(choices):
fn_name = choice[0]
if fn_name == "delete_selected":
new_choice = (fn_name, "Hard delete selected conversations")
choices[idx] = new_choice
if choice[0] == "delete_selected":
choices[idx] = (choice[0], "Hard delete selected conversations")
return choices

def is_deleted(self, obj):
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.4 on 2025-07-28 06:26

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),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Generated by Django 5.2.4 on 2025-07-28 07:21

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('chat', '0002_conversation_summary'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.RemoveField(
model_name='conversation',
name='active_version',
),
migrations.AlterField(
model_name='conversation',
name='title',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AlterField(
model_name='conversation',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='conversations', to=settings.AUTH_USER_MODEL),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Generated by Django 5.2.4 on 2025-07-28 09:03

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('chat', '0003_remove_conversation_active_version_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.AddField(
model_name='conversation',
name='active_version',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='current_version_conversations', to='chat.version'),
),
migrations.AlterField(
model_name='conversation',
name='title',
field=models.CharField(default='Mock title', max_length=100),
),
migrations.AlterField(
model_name='conversation',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
]
25 changes: 15 additions & 10 deletions backend/chat/models.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import uuid

from django.db import models

from authentication.models import CustomUser
from chat.utils.summarizer import generate_summary # Your own T5-based summarizer


class Role(models.Model):
name = models.CharField(max_length=20, blank=False, null=False, default="user")
name = models.CharField(max_length=20, default="user")

def __str__(self):
return self.name


class Conversation(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=100, blank=False, null=False, default="Mock title")
title = models.CharField(max_length=100, default="Mock title")
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now=True)
active_version = models.ForeignKey(
"Version", null=True, blank=True, on_delete=models.CASCADE, related_name="current_version_conversations"
)
deleted_at = models.DateTimeField(null=True, blank=True)
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
summary = models.TextField(blank=True, null=True) # ✅ Summary field

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

version_count.short_description = "Number of versions"

def update_summary(self):
from .models import Message # Avoid circular import
messages = Message.objects.filter(version__conversation=self).order_by("created_at")
combined_text = " ".join(msg.content for msg in messages)
if combined_text.strip():
self.summary = generate_summary(combined_text)
self.save()


class Version(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
Expand All @@ -41,15 +49,12 @@ class Version(models.Model):
)

def __str__(self):
if self.root_message:
return f"Version of `{self.conversation.title}` created at `{self.root_message.created_at}`"
else:
return f"Version of `{self.conversation.title}` with no root message yet"
return f"Version of `{self.conversation.title}`"


class Message(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
content = models.TextField(blank=False, null=False)
content = models.TextField()
role = models.ForeignKey(Role, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
version = models.ForeignKey("Version", related_name="messages", on_delete=models.CASCADE)
Expand All @@ -58,8 +63,8 @@ class Meta:
ordering = ["created_at"]

def save(self, *args, **kwargs):
self.version.conversation.save()
super().save(*args, **kwargs)
self.version.conversation.update_summary() # ✅ Auto-trigger on message save

def __str__(self):
return f"{self.role}: {self.content[:20]}..."
Empty file added backend/chat/utils/summa
Empty file.
15 changes: 15 additions & 0 deletions backend/chat/utils/summarizer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from transformers import pipeline

# Load once and reuse
summarizer = pipeline("summarization", model="t5-small", tokenizer="t5-small")

def generate_summary(text: str) -> str:
if not text.strip():
return "No content available for summary."
try:
# Truncate if too long for small model
text = text[:1000]
summary = summarizer(text, max_length=60, min_length=20, do_sample=False)
return summary[0]['summary_text']
except Exception as e:
return f"Summary error: {str(e)}"