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: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,15 @@ server/dist

# prompts
prompts/debug
# Virtual environment
venv/

# Environment variables
.env

# Python cache
__pycache__/
*.pyc

# DB
db.sqlite3
29 changes: 6 additions & 23 deletions backend/backend/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,15 @@

load_dotenv()

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
FRONTEND_URL = os.environ["FRONTEND_URL"]

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []

# Application definition

INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
Expand Down Expand Up @@ -81,18 +73,17 @@
WSGI_APPLICATION = "backend.wsgi.application"


# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases

DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
"ENGINE": "django.db.backends.postgresql",
"NAME": "fullstack_db",
"USER": "fullstack_user",
"PASSWORD": "fullstack123",
"HOST": "127.0.0.1",
"PORT": "5432",
}
}

# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
{
Expand All @@ -109,31 +100,23 @@
},
]

# Custom user model
AUTH_USER_MODEL = "authentication.CustomUser"

AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
]

# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/

LANGUAGE_CODE = "en-us"

TIME_ZONE = "Europe/Warsaw"
USE_TZ = True

USE_I18N = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/

STATIC_ROOT = BASE_DIR / "static"
STATIC_URL = "/static/"

# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

Expand Down
39 changes: 39 additions & 0 deletions backend/chat/management/commands/cleanup_conversations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from datetime import timedelta

from django.core.management.base import BaseCommand
from django.utils import timezone

from chat.models import Conversation


class Command(BaseCommand):
help = "Soft delete conversations older than given number of days"

def add_arguments(self, parser):
parser.add_argument(
"--days",
type=int,
default=30,
help="Delete conversations older than this many days",
)

def handle(self, *args, **options):
days = options["days"]
cutoff_date = timezone.now() - timedelta(days=days)

conversations = Conversation.objects.filter(
modified_at__lt=cutoff_date,
deleted_at__isnull=True,
)

count = conversations.count()

for convo in conversations:
convo.deleted_at = timezone.now()
convo.save(update_fields=["deleted_at"])

self.stdout.write(
self.style.SUCCESS(
f"Soft-deleted {count} conversation(s) older than {days} days"
)
)
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 6.0.1 on 2026-01-30 20:11

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, help_text='Auto-generated summary of the conversation', null=True),
),
]
36 changes: 33 additions & 3 deletions backend/chat/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import uuid

from django.db import models
from django.apps import apps

from authentication.models import CustomUser

Expand All @@ -15,6 +16,13 @@ def __str__(self):
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")

summary = models.TextField(
blank=True,
null=True,
help_text="Auto-generated summary of the conversation"
)

created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now=True)
active_version = models.ForeignKey(
Expand All @@ -31,6 +39,29 @@ def version_count(self):

version_count.short_description = "Number of versions"

def generate_summary(self):
"""
Generate a simple summary using the first few messages
"""
Message = apps.get_model("chat", "Message")

messages = (
Message.objects
.filter(version__conversation=self)
.order_by("created_at")
.values_list("content", flat=True)[:3]
)

if messages:
return " | ".join(messages)

return ""

def save(self, *args, **kwargs):
if not self.summary:
self.summary = self.generate_summary()
super().save(*args, **kwargs)


class Version(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
Expand All @@ -43,8 +74,7 @@ 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}` with no root message yet"


class Message(models.Model):
Expand All @@ -58,8 +88,8 @@ class Meta:
ordering = ["created_at"]

def save(self, *args, **kwargs):
self.version.conversation.save()
super().save(*args, **kwargs)
self.version.conversation.save()

def __str__(self):
return f"{self.role}: {self.content[:20]}..."
Loading