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
2 changes: 1 addition & 1 deletion backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ DJANGO_SECRET_KEY=...
OPENAI_API_TYPE=...
OPENAI_API_BASE=...
OPENAI_API_VERSION=...
OPENAI_API_KEY=...
OPENAI_API_KEY=...
2 changes: 1 addition & 1 deletion backend/authentication/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def csrf_token(request):
return JsonResponse({"data": token})


@api_view(["POST"])
@api_view(["POST","GET"])
def login_view(request):
email = request.data.get("email")
password = request.data.get("password")
Expand Down
42 changes: 36 additions & 6 deletions backend/backend/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,38 @@
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

dotenv_path=os.path.join(BASE_DIR,".env")
# 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"]
FRONTEND_URL = "http://localhost:3000"

OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4")

# SECRET_KEY = os.getenv("DJANGO_SECRET_KEY", "_*htzwer^io(v06$y(t*9lk-oj-zyzi4)o^o$$=*76+2pbi#+i")

# FRONTEND_URL = os.getenv("FRONTEND_URL", "http://localhost:3000")

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

ALLOWED_HOSTS = []

LOGIN_URL = "/auth/login/"
# Application definition

INSTALLED_APPS = [
"corsheaders",
'django_crontab',
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"corsheaders",
"rest_framework",
"nested_admin",
"authentication",
Expand All @@ -50,14 +60,14 @@
]

MIDDLEWARE = [
"corsheaders.middleware.CorsMiddleware",
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"corsheaders.middleware.CorsMiddleware",
]

ROOT_URLCONF = "backend.urls"
Expand Down Expand Up @@ -146,6 +156,26 @@
FRONTEND_URL,
]

SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_SAMESITE = "None"
SESSION_COOKIE_SECURE = False
CSRF_COOKIE_SECURE = False
CSRF_COOKIE_HTTPONLY = False
CSRF_COOKIE_SAMESITE = "Lax"

CORS_ALLOW_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
CORS_ALLOW_HEADERS = [
"authorization",
"content-type",
"x-csrftoken",
"x-requested-with",
]

REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.BasicAuthentication",
],
}

CRONJOBS = [
('0 0 * * *', 'django.core.management.call_command', ['cleanup_old_conversations']),
]
1 change: 1 addition & 0 deletions backend/backend/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ def root_view(request):
path("chat/", include("chat.urls")),
path("gpt/", include("gpt.urls")),
path("auth/", include("authentication.urls")),
path("message/", include("chat.urls")),
path("", root_view),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
7 changes: 6 additions & 1 deletion backend/chat/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from django.utils import timezone
from nested_admin.nested import NestedModelAdmin, NestedStackedInline, NestedTabularInline

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


class RoleAdmin(NestedModelAdmin):
Expand Down Expand Up @@ -85,8 +85,13 @@ class VersionAdmin(NestedModelAdmin):
inlines = [MessageInline]
list_display = ("id", "conversation", "parent_version", "root_message")

class HistoryAdmin(admin.ModelAdmin):
list_display = ("id", "conversation_id", "version_id", "role", "timestamp")
search_fields = ("role", "content")
list_filter = ("role", "timestamp")

admin.site.register(Role, RoleAdmin)
admin.site.register(Message, MessageAdmin)
admin.site.register(Conversation, ConversationAdmin)
admin.site.register(Version, VersionAdmin)
admin.site.register(History, HistoryAdmin)
19 changes: 19 additions & 0 deletions backend/chat/management/commands/cleanup_old_conversations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import datetime
from django.core.management.base import BaseCommand
from django.utils import timezone
from chat.models import History

class Command(BaseCommand):

#Deletes conversations older than 30 days from SQLite.

def handle(self, *args, **options):
threshold = timezone.now() - datetime.timedelta(days=30)

old_conversations = History.objects.filter(timestamp=threshold)
count = old_conversations.count()

# Delete the old conversations
old_conversations.delete()

self.stdout.write(self.style.SUCCESS(f"Deleted {count} conversations older than 30 days."))
24 changes: 24 additions & 0 deletions backend/chat/migrations/0002_history.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Generated by Django 5.1.7 on 2025-03-13 06:14

import uuid
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("chat", "0001_initial"),
]

operations = [
migrations.CreateModel(
name="History",
fields=[
("id", models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
("conversation_id", models.UUIDField()),
("version_id", models.UUIDField()),
("role", models.CharField(max_length=20)),
("content", models.TextField()),
("timestamp", models.DateTimeField(auto_now_add=True)),
],
),
]
17 changes: 17 additions & 0 deletions backend/chat/migrations/0003_history_question.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 5.1.7 on 2025-03-13 06:56

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("chat", "0002_history"),
]

operations = [
migrations.AddField(
model_name="history",
name="question",
field=models.TextField(blank=True, null=True),
),
]
17 changes: 17 additions & 0 deletions backend/chat/migrations/0004_history_summary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 5.1.7 on 2025-03-13 08:15

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("chat", "0003_history_question"),
]

operations = [
migrations.AddField(
model_name="history",
name="summary",
field=models.TextField(blank=True, null=True),
),
]
14 changes: 14 additions & 0 deletions backend/chat/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,17 @@ def save(self, *args, **kwargs):

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


class History(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
conversation_id = models.UUIDField()
version_id = models.UUIDField()
role = models.CharField(max_length=20)
question=models.TextField(null=True, blank=True)
content = models.TextField()
summary = models.TextField(blank=True, null=True)
timestamp = models.DateTimeField(auto_now_add=True)

def __str__(self):
return f"{self.role}: {self.content[:50]}..."
1 change: 1 addition & 0 deletions backend/chat/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@
),
path("conversations/<uuid:pk>/delete/", views.conversation_soft_delete, name="conversation_delete"),
path("versions/<uuid:pk>/add_message/", views.version_add_message, name="version_add_message"),
path("save_message/", views.save_message, name="save_message")
]
45 changes: 43 additions & 2 deletions backend/chat/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response

from chat.models import Conversation, Message, Version
from django.http import JsonResponse
import uuid
from chat.models import Conversation, Message, Version, History
from chat.serializers import ConversationSerializer, MessageSerializer, TitleSerializer, VersionSerializer
from chat.utils.branching import make_branched_conversation

import openai
import os

openai.api_key = os.getenv("OPENAI_API_KEY")

@api_view(["GET"])
def chat_root_view(request):
Expand Down Expand Up @@ -230,3 +235,39 @@ def version_add_message(request, pk):
status=status.HTTP_201_CREATED,
)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@api_view(["POST"])
def save_message(request):
conversation_id = request.POST.get("conversation_id")
version_id = request.POST.get("version_id")
role = request.POST.get("role")
content = request.POST.get("content")
question=request.POST.get("question")

message = History.objects.create(
conversation_id=uuid.UUID(conversation_id),
version_id=uuid.UUID(version_id),
role=role,
question=question,
content=content
)

summary = generate_summary(conversation_id)
History.objects.filter(conversation_id=conversation_id).update(summary=summary)

return JsonResponse({"message": "Message saved", "id": str(message.id)})

def generate_summary(conversation_id):
messages = History.objects.filter(conversation_id=conversation_id).order_by("timestamp")
conversation_text = "\n".join([msg.content for msg in messages])

response = openai.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": "Summarize the following conversation in a concise manner."},
{"role": "user", "content": conversation_text}
]
)

summary = response.choices[0].message.content.strip()
return summary
2 changes: 1 addition & 1 deletion backend/gpt/urls.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from django.urls import path

from gpt import views

urlpatterns = [
path("", views.gpt_root_view),
path("title/", views.get_title),
path("question/", views.get_answer),
path("conversation/", views.get_conversation),
path("chatgpt/", views.chat_with_gpt, name="chat_with_gpt"),
]
24 changes: 22 additions & 2 deletions backend/gpt/views.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse, StreamingHttpResponse
from rest_framework.decorators import api_view

import openai
import os
from src.utils.gpt import get_conversation_answer, get_gpt_title, get_simple_answer
from rest_framework.response import Response

client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY"),base_url=os.getenv("OPENAI_BASE"))

@api_view(["POST"])
def chat_with_gpt(request):
prompt = request.data.get("prompt", "Hello, ChatGPT!") # Default prompt

try:
response = client.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": prompt}
],
max_tokens=100
)
return Response({"response": response.choices[0].message.content})
except Exception as e:
return Response({"error": str(e)}, status=500)

@api_view(["GET"])
def gpt_root_view(request):
return JsonResponse({"message": "GPT endpoint works!"})
Expand All @@ -25,7 +45,7 @@ def get_answer(request):
return StreamingHttpResponse(get_simple_answer(data["user_question"], stream=True), content_type="text/html")


@login_required
#@login_required
@api_view(["POST"])
def get_conversation(request):
data = request.data
Expand Down
6 changes: 2 additions & 4 deletions backend/src/libs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,5 @@
__all__ = ["openai"]
load_dotenv()

openai.api_type = os.getenv("OPENAI_API_TYPE")
openai.api_base = os.getenv("OPENAI_API_BASE")
openai.api_version = os.getenv("OPENAI_API_VERSION")
openai.api_key = os.getenv("OPENAI_API_KEY")
# TODO: The 'openai.api_base' option isn't read in the client API. You will need to pass it when you instantiate the client, e.g. 'OpenAI(base_url=os.getenv("OPENAI_API_BASE"))'
# openai.api_base = os.getenv("OPENAI_API_BASE")
Loading