diff --git a/README.md b/README.md
index 966165f..decefd8 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,40 @@
-# chat_backend
+# Chat Bot Backend
-Backend django project for the chat site
\ No newline at end of file
+## Setup
+
+Clone the repo
+```console
+git clone http://10.0.0.160:3000/AI_ML_Operations_LLC/Chat_Bot_Backend.git
+```
+
+Go into the repo
+```console
+cd Chat_Bot_Backend
+```
+
+Create the virtual environment
+```console
+virtualenv --python=python3.9 venv
+```
+
+Activate the Virtual Environment
+```console
+. venv/bin/activate
+```
+
+Install the requirments
+```console
+python -m pip install -r requirements.txt
+```
+
+Pre-populate the database with test data
+
+Run the dev server
+```console
+python manage.py
+```
+
+## TODO
+
+ - [ ] Create inital database with temp data
+ - [ ] Do a lot of stuff.....
diff --git a/llm_be/.idea/.gitignore b/llm_be/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/llm_be/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/llm_be/.idea/inspectionProfiles/Project_Default.xml b/llm_be/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..03d9549
--- /dev/null
+++ b/llm_be/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/llm_be/.idea/inspectionProfiles/profiles_settings.xml b/llm_be/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/llm_be/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/llm_be/.idea/llm_be.iml b/llm_be/.idea/llm_be.iml
new file mode 100644
index 0000000..6a7db59
--- /dev/null
+++ b/llm_be/.idea/llm_be.iml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/llm_be/.idea/misc.xml b/llm_be/.idea/misc.xml
new file mode 100644
index 0000000..b808fd5
--- /dev/null
+++ b/llm_be/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/llm_be/.idea/modules.xml b/llm_be/.idea/modules.xml
new file mode 100644
index 0000000..2b64d3d
--- /dev/null
+++ b/llm_be/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/llm_be/.idea/vcs.xml b/llm_be/.idea/vcs.xml
new file mode 100644
index 0000000..6c0b863
--- /dev/null
+++ b/llm_be/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/llm_be/chat_backend/__init__.py b/llm_be/chat_backend/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/llm_be/chat_backend/admin.py b/llm_be/chat_backend/admin.py
new file mode 100644
index 0000000..b985b0b
--- /dev/null
+++ b/llm_be/chat_backend/admin.py
@@ -0,0 +1,71 @@
+from django.contrib import admin
+from .models import CustomUser, Announcement, Company, LLMModels, Conversation, Prompt, Feedback, PromptMetric
+
+# Register your models here.
+
+
+class AnnouncmentAdmin(admin.ModelAdmin):
+ model = Announcement
+
+
+class CompanyAdmin(admin.ModelAdmin):
+ model = Company
+
+
+class CustomUserAdmin(admin.ModelAdmin):
+ model = CustomUser
+ list_display = (
+ "email",
+ "username",
+ "is_company_manager",
+ "first_name",
+ "last_name",
+ "is_active",
+ "is_staff",
+ "has_usable_password",
+ "deleted",
+ "has_signed_tos",
+ "last_login",
+ "slug",
+ "get_set_password_url"
+ )
+ search_fields = ("fields", "username", "first_name", "last_name", "slug")
+
+class FeedbackAdmin(admin.ModelAdmin):
+ model = Feedback
+ search_fields = ("status", "text", "get_user_email")
+ list_display= (
+ "status", "get_user_email", "title", "category"
+ )
+
+class LLMModelsAdmin(admin.ModelAdmin):
+ model = LLMModels
+ list_display = ("name", "port", "description")
+ search_fields = ("name", "port", "description")
+
+
+class ConversationAdmin(admin.ModelAdmin):
+ model = Conversation
+ list_display = ("title", "get_user_email","deleted")
+ search_fields = ("title",)
+
+
+class PromptAdmin(admin.ModelAdmin):
+ model = Prompt
+ list_display = ("message", "user_created", "get_conversation_title")
+ search_fields = ("message",)
+
+class PromptMetricAdmin(admin.ModelAdmin):
+ model = PromptMetric
+ list_display = ("event", "model_name", "prompt_length","reponse_length",'has_file','file_type', "get_duration")
+
+
+admin.site.register(Announcement, AnnouncmentAdmin)
+admin.site.register(Company, CompanyAdmin)
+admin.site.register(CustomUser, CustomUserAdmin)
+
+admin.site.register(LLMModels, LLMModelsAdmin)
+admin.site.register(Conversation, ConversationAdmin)
+admin.site.register(Prompt, PromptAdmin)
+admin.site.register(PromptMetric, PromptMetricAdmin)
+admin.site.register(Feedback, FeedbackAdmin)
diff --git a/llm_be/chat_backend/apps.py b/llm_be/chat_backend/apps.py
new file mode 100644
index 0000000..24f2d5c
--- /dev/null
+++ b/llm_be/chat_backend/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class ChatBackendConfig(AppConfig):
+ default_auto_field = "django.db.models.BigAutoField"
+ name = "chat_backend"
diff --git a/llm_be/chat_backend/client.py b/llm_be/chat_backend/client.py
new file mode 100644
index 0000000..d550dbb
--- /dev/null
+++ b/llm_be/chat_backend/client.py
@@ -0,0 +1,30 @@
+"""
+llama client - Abstract this in the future
+"""
+import ollama
+from typing import List, Dict
+
+class LlamaClient(object):
+ def __init__(self, model: str='llama3'):
+ self.client = ollama.Client(host="http://127.0.0.1:11434")
+ self.model = model
+
+ def check_if_model_exists(self) -> bool:
+ raise NotImplementedError
+
+ def generate_conversation_title(self, message:str):
+ response = self.generate_single_message("Summarise the phrase in one to for words\"%s\"" % message)
+
+ raw_response = response['response'].replace("\"","")
+ return " ".join(raw_response.split()[:4])
+
+ def generate_single_message(self, message: str):
+ return ollama.generate(model=self.model, prompt=message)
+
+ def get_chat_response(self, messages: List[str]):
+ return self.client.chat(model = self.model, messages=messages, stream=False)
+
+
+ def get_streamed_chat_response(self, messages: List[str]):
+ return self.client.chat(model = self.model, messages=messages, stream=True)
+
diff --git a/llm_be/chat_backend/migrations/0001_initial.py b/llm_be/chat_backend/migrations/0001_initial.py
new file mode 100644
index 0000000..1662b14
--- /dev/null
+++ b/llm_be/chat_backend/migrations/0001_initial.py
@@ -0,0 +1,158 @@
+# Generated by Django 3.2.18 on 2024-06-20 20:41
+
+import django.contrib.auth.models
+import django.contrib.auth.validators
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ("auth", "0012_alter_user_first_name_max_length"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="Company",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("name", models.TextField(max_length=256)),
+ ("state", models.TextField(max_length=2)),
+ ("zipcode", models.TextField(max_length=5)),
+ ("address", models.TextField(max_length=256)),
+ ],
+ ),
+ migrations.CreateModel(
+ name="CustomUser",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("password", models.CharField(max_length=128, verbose_name="password")),
+ (
+ "last_login",
+ models.DateTimeField(
+ blank=True, null=True, verbose_name="last login"
+ ),
+ ),
+ (
+ "is_superuser",
+ models.BooleanField(
+ default=False,
+ help_text="Designates that this user has all permissions without explicitly assigning them.",
+ verbose_name="superuser status",
+ ),
+ ),
+ (
+ "username",
+ models.CharField(
+ error_messages={
+ "unique": "A user with that username already exists."
+ },
+ help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
+ max_length=150,
+ unique=True,
+ validators=[
+ django.contrib.auth.validators.UnicodeUsernameValidator()
+ ],
+ verbose_name="username",
+ ),
+ ),
+ (
+ "first_name",
+ models.CharField(
+ blank=True, max_length=150, verbose_name="first name"
+ ),
+ ),
+ (
+ "last_name",
+ models.CharField(
+ blank=True, max_length=150, verbose_name="last name"
+ ),
+ ),
+ (
+ "email",
+ models.EmailField(
+ blank=True, max_length=254, verbose_name="email address"
+ ),
+ ),
+ (
+ "is_staff",
+ models.BooleanField(
+ default=False,
+ help_text="Designates whether the user can log into this admin site.",
+ verbose_name="staff status",
+ ),
+ ),
+ (
+ "is_active",
+ models.BooleanField(
+ default=True,
+ help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
+ verbose_name="active",
+ ),
+ ),
+ (
+ "date_joined",
+ models.DateTimeField(
+ default=django.utils.timezone.now, verbose_name="date joined"
+ ),
+ ),
+ (
+ "company",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to="chat_backend.company",
+ ),
+ ),
+ (
+ "groups",
+ models.ManyToManyField(
+ blank=True,
+ help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
+ related_name="user_set",
+ related_query_name="user",
+ to="auth.Group",
+ verbose_name="groups",
+ ),
+ ),
+ (
+ "user_permissions",
+ models.ManyToManyField(
+ blank=True,
+ help_text="Specific permissions for this user.",
+ related_name="user_set",
+ related_query_name="user",
+ to="auth.Permission",
+ verbose_name="user permissions",
+ ),
+ ),
+ ],
+ options={
+ "verbose_name": "user",
+ "verbose_name_plural": "users",
+ "abstract": False,
+ },
+ managers=[
+ ("objects", django.contrib.auth.models.UserManager()),
+ ],
+ ),
+ ]
diff --git a/llm_be/chat_backend/migrations/0002_accouncement.py b/llm_be/chat_backend/migrations/0002_accouncement.py
new file mode 100644
index 0000000..3a99827
--- /dev/null
+++ b/llm_be/chat_backend/migrations/0002_accouncement.py
@@ -0,0 +1,43 @@
+# Generated by Django 3.2.18 on 2024-06-21 19:27
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("chat_backend", "0001_initial"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="Accouncement",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "status",
+ models.CharField(
+ choices=[
+ ("DEFAULT", "default"),
+ ("WARNING", "warning"),
+ ("INFO", "info"),
+ ("DANGER", "danger"),
+ ],
+ default="DEFAULT",
+ max_length=7,
+ ),
+ ),
+ ("message", models.TextField(max_length=256)),
+ ("start_date_time", models.DateTimeField(auto_now=True)),
+ ("end_date_time", models.DateTimeField(auto_now=True)),
+ ],
+ ),
+ ]
diff --git a/llm_be/chat_backend/migrations/0003_alter_customuser_company.py b/llm_be/chat_backend/migrations/0003_alter_customuser_company.py
new file mode 100644
index 0000000..0dc7f16
--- /dev/null
+++ b/llm_be/chat_backend/migrations/0003_alter_customuser_company.py
@@ -0,0 +1,24 @@
+# Generated by Django 3.2.18 on 2024-06-21 19:30
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("chat_backend", "0002_accouncement"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="customuser",
+ name="company",
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ to="chat_backend.company",
+ ),
+ ),
+ ]
diff --git a/llm_be/chat_backend/migrations/0004_rename_accouncement_announcement.py b/llm_be/chat_backend/migrations/0004_rename_accouncement_announcement.py
new file mode 100644
index 0000000..afaa21b
--- /dev/null
+++ b/llm_be/chat_backend/migrations/0004_rename_accouncement_announcement.py
@@ -0,0 +1,17 @@
+# Generated by Django 3.2.18 on 2024-06-21 19:32
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("chat_backend", "0003_alter_customuser_company"),
+ ]
+
+ operations = [
+ migrations.RenameModel(
+ old_name="Accouncement",
+ new_name="Announcement",
+ ),
+ ]
diff --git a/llm_be/chat_backend/migrations/0005_conversation_llmmodels_announcement_created_and_more.py b/llm_be/chat_backend/migrations/0005_conversation_llmmodels_announcement_created_and_more.py
new file mode 100644
index 0000000..e1057c7
--- /dev/null
+++ b/llm_be/chat_backend/migrations/0005_conversation_llmmodels_announcement_created_and_more.py
@@ -0,0 +1,162 @@
+# Generated by Django 4.2.13 on 2024-07-02 15:15
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("chat_backend", "0004_rename_accouncement_announcement"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="Conversation",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("created", models.DateTimeField(default=django.utils.timezone.now)),
+ (
+ "last_modified",
+ models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ (
+ "title",
+ models.CharField(
+ default="",
+ help_text="The title for the conversation",
+ max_length=64,
+ ),
+ ),
+ (
+ "user",
+ models.OneToOneField(
+ on_delete=django.db.models.deletion.CASCADE,
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ ],
+ options={
+ "abstract": False,
+ },
+ ),
+ migrations.CreateModel(
+ name="LLMModels",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("created", models.DateTimeField(default=django.utils.timezone.now)),
+ (
+ "last_modified",
+ models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ ("name", models.CharField(default="", max_length=254)),
+ (
+ "port",
+ models.IntegerField(
+ help_text="This specifies the port that the LLM runs on"
+ ),
+ ),
+ (
+ "description",
+ models.CharField(
+ default="",
+ help_text="A description for the LLM. Limit is 512 characters",
+ max_length=512,
+ ),
+ ),
+ ],
+ options={
+ "abstract": False,
+ },
+ ),
+ migrations.AddField(
+ model_name="announcement",
+ name="created",
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ migrations.AddField(
+ model_name="announcement",
+ name="last_modified",
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ migrations.AddField(
+ model_name="company",
+ name="created",
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ migrations.AddField(
+ model_name="company",
+ name="last_modified",
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ migrations.CreateModel(
+ name="Prompt",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("created", models.DateTimeField(default=django.utils.timezone.now)),
+ (
+ "last_modified",
+ models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ (
+ "message",
+ models.CharField(
+ help_text="The text for a prompt", max_length=10240
+ ),
+ ),
+ (
+ "user_created",
+ models.BooleanField(
+ help_text="True if was created by the user. False if it was generate by the LLM"
+ ),
+ ),
+ (
+ "conversation",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to="chat_backend.conversation",
+ ),
+ ),
+ ],
+ options={
+ "abstract": False,
+ },
+ ),
+ migrations.AddField(
+ model_name="company",
+ name="available_llms",
+ field=models.ForeignKey(
+ blank=True,
+ help_text="A list of LLMs that company can use",
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ to="chat_backend.llmmodels",
+ ),
+ ),
+ ]
diff --git a/llm_be/chat_backend/migrations/0006_customuser_is_company_manager.py b/llm_be/chat_backend/migrations/0006_customuser_is_company_manager.py
new file mode 100644
index 0000000..605eba9
--- /dev/null
+++ b/llm_be/chat_backend/migrations/0006_customuser_is_company_manager.py
@@ -0,0 +1,21 @@
+# Generated by Django 4.2.13 on 2024-07-03 13:26
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("chat_backend", "0005_conversation_llmmodels_announcement_created_and_more"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="customuser",
+ name="is_company_manager",
+ field=models.BooleanField(
+ default=False,
+ help_text="Allows the edit/add/remove of users for a company",
+ ),
+ ),
+ ]
diff --git a/llm_be/chat_backend/migrations/0007_alter_prompt_conversation.py b/llm_be/chat_backend/migrations/0007_alter_prompt_conversation.py
new file mode 100644
index 0000000..3ba498d
--- /dev/null
+++ b/llm_be/chat_backend/migrations/0007_alter_prompt_conversation.py
@@ -0,0 +1,24 @@
+# Generated by Django 4.2.13 on 2024-07-03 18:33
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("chat_backend", "0006_customuser_is_company_manager"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="prompt",
+ name="conversation",
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ to="chat_backend.conversation",
+ ),
+ ),
+ ]
diff --git a/llm_be/chat_backend/migrations/0008_alter_conversation_user.py b/llm_be/chat_backend/migrations/0008_alter_conversation_user.py
new file mode 100644
index 0000000..44b174a
--- /dev/null
+++ b/llm_be/chat_backend/migrations/0008_alter_conversation_user.py
@@ -0,0 +1,25 @@
+# Generated by Django 4.2.13 on 2024-07-03 18:46
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("chat_backend", "0007_alter_prompt_conversation"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="conversation",
+ name="user",
+ field=models.OneToOneField(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ ]
diff --git a/llm_be/chat_backend/migrations/0009_alter_conversation_user.py b/llm_be/chat_backend/migrations/0009_alter_conversation_user.py
new file mode 100644
index 0000000..7ba5177
--- /dev/null
+++ b/llm_be/chat_backend/migrations/0009_alter_conversation_user.py
@@ -0,0 +1,25 @@
+# Generated by Django 4.2.13 on 2024-07-03 18:48
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("chat_backend", "0008_alter_conversation_user"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="conversation",
+ name="user",
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ ]
diff --git a/llm_be/chat_backend/migrations/0010_conversation_deleted_customuser_deleted.py b/llm_be/chat_backend/migrations/0010_conversation_deleted_customuser_deleted.py
new file mode 100644
index 0000000..d8b98f1
--- /dev/null
+++ b/llm_be/chat_backend/migrations/0010_conversation_deleted_customuser_deleted.py
@@ -0,0 +1,27 @@
+# Generated by Django 4.2.13 on 2024-07-10 14:16
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("chat_backend", "0009_alter_conversation_user"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="conversation",
+ name="deleted",
+ field=models.BooleanField(
+ default=False, help_text="This is to hid accounts"
+ ),
+ ),
+ migrations.AddField(
+ model_name="customuser",
+ name="deleted",
+ field=models.BooleanField(
+ default=False, help_text="This is to hid accounts"
+ ),
+ ),
+ ]
diff --git a/llm_be/chat_backend/migrations/0011_customuser_has_signed_tos.py b/llm_be/chat_backend/migrations/0011_customuser_has_signed_tos.py
new file mode 100644
index 0000000..c853d37
--- /dev/null
+++ b/llm_be/chat_backend/migrations/0011_customuser_has_signed_tos.py
@@ -0,0 +1,20 @@
+# Generated by Django 4.2.13 on 2024-09-11 13:22
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("chat_backend", "0010_conversation_deleted_customuser_deleted"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="customuser",
+ name="has_signed_tos",
+ field=models.BooleanField(
+ default=False, help_text="If the user has signed the TOS"
+ ),
+ ),
+ ]
diff --git a/llm_be/chat_backend/migrations/0012_customuser_slug.py b/llm_be/chat_backend/migrations/0012_customuser_slug.py
new file mode 100644
index 0000000..3050eb1
--- /dev/null
+++ b/llm_be/chat_backend/migrations/0012_customuser_slug.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.13 on 2024-09-11 18:21
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("chat_backend", "0011_customuser_has_signed_tos"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="customuser",
+ name="slug",
+ field=models.SlugField(default="luygzxpbkv"),
+ ),
+ ]
diff --git a/llm_be/chat_backend/migrations/0013_alter_customuser_slug.py b/llm_be/chat_backend/migrations/0013_alter_customuser_slug.py
new file mode 100644
index 0000000..21b9936
--- /dev/null
+++ b/llm_be/chat_backend/migrations/0013_alter_customuser_slug.py
@@ -0,0 +1,19 @@
+# Generated by Django 4.2.13 on 2024-09-11 18:28
+
+import autoslug.fields
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("chat_backend", "0012_customuser_slug"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="customuser",
+ name="slug",
+ field=autoslug.fields.AutoSlugField(editable=False, populate_from="email"),
+ ),
+ ]
diff --git a/llm_be/chat_backend/migrations/0014_feedback.py b/llm_be/chat_backend/migrations/0014_feedback.py
new file mode 100644
index 0000000..5076e4f
--- /dev/null
+++ b/llm_be/chat_backend/migrations/0014_feedback.py
@@ -0,0 +1,52 @@
+# Generated by Django 4.2.13 on 2024-09-14 18:29
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("chat_backend", "0013_alter_customuser_slug"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="Feedback",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("text", models.TextField(max_length=512)),
+ (
+ "status",
+ models.CharField(
+ choices=[
+ ("SUBMITTED", "Submitted"),
+ ("RESOLVED", "Resolved"),
+ ("DEFFERED", "Deffered"),
+ ("CLOSED", "Closed"),
+ ],
+ default="SUBMITTED",
+ max_length=24,
+ ),
+ ),
+ (
+ "user",
+ models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ ],
+ ),
+ ]
diff --git a/llm_be/chat_backend/migrations/0015_feedback_category_feedback_created_and_more.py b/llm_be/chat_backend/migrations/0015_feedback_category_feedback_created_and_more.py
new file mode 100644
index 0000000..372c8b9
--- /dev/null
+++ b/llm_be/chat_backend/migrations/0015_feedback_category_feedback_created_and_more.py
@@ -0,0 +1,42 @@
+# Generated by Django 4.2.13 on 2024-09-15 01:39
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("chat_backend", "0014_feedback"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="feedback",
+ name="category",
+ field=models.CharField(
+ choices=[
+ ("NOT_DEFINED", "Not defined"),
+ ("BUG", "Bug"),
+ ("ENCHANCEMENT", "Enhancment"),
+ ],
+ default="NOT_DEFINED",
+ max_length=24,
+ ),
+ ),
+ migrations.AddField(
+ model_name="feedback",
+ name="created",
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ migrations.AddField(
+ model_name="feedback",
+ name="last_modified",
+ field=models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ migrations.AddField(
+ model_name="feedback",
+ name="title",
+ field=models.TextField(default="", max_length=64),
+ ),
+ ]
diff --git a/llm_be/chat_backend/migrations/0016_promptmetric_alter_feedback_category.py b/llm_be/chat_backend/migrations/0016_promptmetric_alter_feedback_category.py
new file mode 100644
index 0000000..e41acfa
--- /dev/null
+++ b/llm_be/chat_backend/migrations/0016_promptmetric_alter_feedback_category.py
@@ -0,0 +1,90 @@
+# Generated by Django 4.2.13 on 2024-12-30 15:55
+
+from django.db import migrations, models
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("chat_backend", "0015_feedback_category_feedback_created_and_more"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="PromptMetric",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("created", models.DateTimeField(default=django.utils.timezone.now)),
+ (
+ "last_modified",
+ models.DateTimeField(default=django.utils.timezone.now),
+ ),
+ (
+ "prompt_id",
+ models.IntegerField(
+ help_text="The id of the prompt this matches to"
+ ),
+ ),
+ (
+ "event",
+ models.CharField(
+ choices=[
+ ("CREATED", "Created"),
+ ("SUBMITTED", "Submitted"),
+ ("PROCESSED", "Processed"),
+ ("FINISHED", "Finished"),
+ ("MAX_PROMPT_METRIC_CHOICES", "Max Prompt Metric Choices"),
+ ],
+ default="CREATED",
+ max_length=26,
+ ),
+ ),
+ ("start_time", models.DateTimeField()),
+ ("end_time", models.DateTimeField()),
+ (
+ "prompt_length",
+ models.IntegerField(
+ help_text="How many characters are in the prompt"
+ ),
+ ),
+ (
+ "reponse_legnth",
+ models.IntegerField(
+ help_text="How many characters are in the response"
+ ),
+ ),
+ ("has_file", models.BooleanField(help_text="Is there a file")),
+ (
+ "file_type",
+ models.CharField(help_text="The file type, if any", max_length=16),
+ ),
+ ],
+ options={
+ "abstract": False,
+ },
+ ),
+ migrations.AlterField(
+ model_name="feedback",
+ name="category",
+ field=models.CharField(
+ choices=[
+ ("NOT_DEFINED", "Not defined"),
+ ("BUG", "Bug"),
+ ("ENHANCEMENT", "Enhancement"),
+ ("OTHER", "Other"),
+ ("MAX_CATEGORIES", "Max Categories"),
+ ],
+ default="NOT_DEFINED",
+ max_length=24,
+ ),
+ ),
+ ]
diff --git a/llm_be/chat_backend/migrations/0017_remove_promptmetric_reponse_legnth_and_more.py b/llm_be/chat_backend/migrations/0017_remove_promptmetric_reponse_legnth_and_more.py
new file mode 100644
index 0000000..61535a1
--- /dev/null
+++ b/llm_be/chat_backend/migrations/0017_remove_promptmetric_reponse_legnth_and_more.py
@@ -0,0 +1,54 @@
+# Generated by Django 4.2.13 on 2024-12-30 20:55
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("chat_backend", "0016_promptmetric_alter_feedback_category"),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name="promptmetric",
+ name="reponse_legnth",
+ ),
+ migrations.AddField(
+ model_name="promptmetric",
+ name="conversation_id",
+ field=models.IntegerField(
+ default=0, help_text="The id of the conversation this matches to"
+ ),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name="promptmetric",
+ name="model_name",
+ field=models.CharField(
+ default="temp", help_text="The name of the model", max_length=215
+ ),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name="promptmetric",
+ name="reponse_length",
+ field=models.IntegerField(
+ blank=True,
+ help_text="How many characters are in the response",
+ null=True,
+ ),
+ ),
+ migrations.AlterField(
+ model_name="promptmetric",
+ name="end_time",
+ field=models.DateTimeField(blank=True, null=True),
+ ),
+ migrations.AlterField(
+ model_name="promptmetric",
+ name="file_type",
+ field=models.CharField(
+ blank=True, help_text="The file type, if any", max_length=16, null=True
+ ),
+ ),
+ ]
diff --git a/llm_be/chat_backend/migrations/0018_prompt_file_prompt_file_type.py b/llm_be/chat_backend/migrations/0018_prompt_file_prompt_file_type.py
new file mode 100644
index 0000000..54aec2a
--- /dev/null
+++ b/llm_be/chat_backend/migrations/0018_prompt_file_prompt_file_type.py
@@ -0,0 +1,36 @@
+# Generated by Django 4.2.17 on 2024-12-31 16:01
+
+import django.core.files.storage
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("chat_backend", "0017_remove_promptmetric_reponse_legnth_and_more"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="prompt",
+ name="file",
+ field=models.FileField(
+ blank=True,
+ help_text="file for the prompt",
+ null=True,
+ upload_to=django.core.files.storage.FileSystemStorage(
+ location="prompt_files"
+ ),
+ ),
+ ),
+ migrations.AddField(
+ model_name="prompt",
+ name="file_type",
+ field=models.CharField(
+ blank=True,
+ help_text="file type of the file for the prompt",
+ max_length=16,
+ null=True,
+ ),
+ ),
+ ]
diff --git a/llm_be/chat_backend/migrations/0019_customuser_conversation_order_and_more.py b/llm_be/chat_backend/migrations/0019_customuser_conversation_order_and_more.py
new file mode 100644
index 0000000..628e4ee
--- /dev/null
+++ b/llm_be/chat_backend/migrations/0019_customuser_conversation_order_and_more.py
@@ -0,0 +1,27 @@
+# Generated by Django 4.2.17 on 2025-02-17 15:24
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("chat_backend", "0018_prompt_file_prompt_file_type"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="customuser",
+ name="conversation_order",
+ field=models.BooleanField(
+ default=True, help_text="How the conversations should display"
+ ),
+ ),
+ migrations.AlterField(
+ model_name="conversation",
+ name="deleted",
+ field=models.BooleanField(
+ default=False, help_text="This is to hide conversations"
+ ),
+ ),
+ ]
diff --git a/llm_be/chat_backend/migrations/__init__.py b/llm_be/chat_backend/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/llm_be/chat_backend/models.py b/llm_be/chat_backend/models.py
new file mode 100644
index 0000000..7505470
--- /dev/null
+++ b/llm_be/chat_backend/models.py
@@ -0,0 +1,193 @@
+from django.db import models
+from django.contrib.auth.models import AbstractUser
+from django.utils import timezone
+from autoslug import AutoSlugField
+from django.core.files.storage import FileSystemStorage
+# Create your models here.
+
+FILE_STORAGE = FileSystemStorage(location='prompt_files')
+
+class TimeInfoBase(models.Model):
+
+ created = models.DateTimeField(default=timezone.now)
+ last_modified = models.DateTimeField(default=timezone.now)
+
+ class Meta:
+ abstract = True
+
+ def save(self, *args, **kwargs):
+ if not kwargs.pop("skip_last_modified", False) and not hasattr(
+ self, "skip_last_modified"
+ ):
+ self.last_modified = timezone.now()
+ if kwargs.get("update_fields") is not None:
+ kwargs["update_fields"] = list(
+ {*kwargs["update_fields"], "last_modified"}
+ )
+
+ super().save(*args, **kwargs)
+
+
+class LLMModels(TimeInfoBase):
+ name = models.CharField(max_length=254, default="")
+ port = models.IntegerField(help_text="This specifies the port that the LLM runs on")
+ description = models.CharField(
+ max_length=512,
+ default="",
+ help_text="A description for the LLM. Limit is 512 characters",
+ )
+
+
+class Company(TimeInfoBase):
+ name = models.TextField(max_length=256)
+ state = models.TextField(max_length=2)
+ zipcode = models.TextField(max_length=5)
+ address = models.TextField(max_length=256)
+ available_llms = models.ForeignKey(
+ "LLMModels",
+ on_delete=models.CASCADE,
+ blank=True,
+ null=True,
+ help_text="A list of LLMs that company can use",
+ )
+
+
+class CustomUser(AbstractUser):
+ company = models.ForeignKey(
+ Company, on_delete=models.CASCADE, blank=True, null=True
+ )
+ is_company_manager = models.BooleanField(
+ help_text="Allows the edit/add/remove of users for a company", default=False
+ )
+ deleted = models.BooleanField(help_text="This is to hid accounts", default=False)
+ has_signed_tos = models.BooleanField(default=False, help_text="If the user has signed the TOS")
+ slug = AutoSlugField(populate_from='email')
+ conversation_order = models.BooleanField(default=True, help_text='How the conversations should display')
+ def get_set_password_url(self):
+ return f"https://www.chat.aimloperations.com/set_password?slug={self.slug}"
+
+FEEDBACK_CHOICE = (
+ ("SUBMITTED", "Submitted"),
+ ("RESOLVED", "Resolved"),
+ ("DEFFERED", "Deffered"),
+ ("CLOSED", "Closed"),
+)
+
+FEEDBACK_CATEGORIES = (
+ ('NOT_DEFINED', 'Not defined'),
+ ('BUG', 'Bug'),
+ ('ENHANCEMENT', 'Enhancement'),
+ ('OTHER', 'Other'),
+ ('MAX_CATEGORIES', 'Max Categories'),
+)
+
+class Feedback(TimeInfoBase):
+ title = models.TextField(max_length=64, default='')
+ user = models.ForeignKey(
+ CustomUser, on_delete=models.CASCADE, blank=True, null=True
+ )
+ text = models.TextField(max_length=512)
+ status = models.CharField(max_length=24, choices=FEEDBACK_CHOICE, default="SUBMITTED")
+ category = models.CharField(max_length=24, choices=FEEDBACK_CATEGORIES, default="NOT_DEFINED")
+
+ def get_user_email(self):
+ if self.user:
+ return self.user.email
+ else:
+ return ""
+
+
+MONTH_CHOICES = (
+ ("JANUARY", "January"),
+ ("FEBRUARY", "February"),
+ ("MARCH", "March"),
+ # ....
+ ("DECEMBER", "December"),
+)
+
+month = models.CharField(max_length=9,
+ choices=MONTH_CHOICES,
+ default="JANUARY")
+
+class Announcement(TimeInfoBase):
+ class Status(models.TextChoices):
+ default = "DEFAULT", "default"
+ warning = "WARNING", "warning"
+ info = "INFO", "info"
+ danger = "DANGER", "danger"
+
+ status = models.CharField(
+ max_length=7, choices=Status.choices, default=Status.default
+ )
+ message = models.TextField(max_length=256)
+ start_date_time = models.DateTimeField(auto_now=True)
+ end_date_time = models.DateTimeField(auto_now=True)
+
+
+class Conversation(TimeInfoBase):
+ user = models.ForeignKey(
+ CustomUser, on_delete=models.CASCADE, blank=True, null=True
+ )
+ title = models.CharField(
+ max_length=64, help_text="The title for the conversation", default=""
+ )
+ deleted = models.BooleanField(help_text="This is to hide conversations", default=False)
+
+ def get_user_email(self):
+ if self.user:
+ return self.user.email
+ else:
+ return ""
+
+ def __str__(self):
+ return self.title
+
+
+class Prompt(TimeInfoBase):
+ message = models.CharField(max_length=10 * 1024, help_text="The text for a prompt")
+ user_created = models.BooleanField(
+ help_text="True if was created by the user. False if it was generate by the LLM"
+ )
+ conversation = models.ForeignKey(
+ "Conversation", on_delete=models.CASCADE, blank=True, null=True
+ )
+ file =models.FileField(upload_to=FILE_STORAGE, blank=True, null=True, help_text="file for the prompt")
+ file_type=models.CharField(max_length=16, blank=True, null=True, help_text='file type of the file for the prompt')
+
+ def get_conversation_title(self):
+ if self.conversation:
+ return self.conversation.title
+ else:
+ return ""
+
+ def file_exists(self):
+ return self.file != None and self.file.storage.exists(self.file.name)
+
+
+
+class PromptMetric(TimeInfoBase):
+ PROMPT_METRIC_CHOICES = (
+ ("CREATED", "Created"),
+ ("SUBMITTED", "Submitted"),
+ ("PROCESSED", "Processed"),
+ ("FINISHED", "Finished"),
+ ("MAX_PROMPT_METRIC_CHOICES", "Max Prompt Metric Choices"),
+ )
+ prompt_id = models.IntegerField(help_text="The id of the prompt this matches to")
+ conversation_id = models.IntegerField(help_text="The id of the conversation this matches to")
+ event = models.CharField(
+ max_length=26, choices=PROMPT_METRIC_CHOICES, default='CREATED'
+ )
+ model_name = models.CharField(max_length=215, help_text="The name of the model")
+ start_time = models.DateTimeField()
+ end_time = models.DateTimeField(blank=True, null=True)
+ prompt_length = models.IntegerField( help_text="How many characters are in the prompt")
+ reponse_length = models.IntegerField(blank=True, null=True, help_text="How many characters are in the response")
+ has_file = models.BooleanField(help_text="Is there a file")
+ file_type = models.CharField(max_length=16, help_text='The file type, if any', blank=True, null=True)
+
+ def get_duration(self):
+ if(self.start_time and self.end_time):
+ difference =self.end_time - self.start_time
+ return difference.seconds
+ return 0
diff --git a/llm_be/chat_backend/renderers.py b/llm_be/chat_backend/renderers.py
new file mode 100644
index 0000000..f611bb0
--- /dev/null
+++ b/llm_be/chat_backend/renderers.py
@@ -0,0 +1,8 @@
+from rest_framework.renderers import BaseRenderer
+
+class ServerSentEventRenderer(BaseRenderer):
+ media_type = 'text/event-stream'
+ format = 'txt'
+
+ def render(self, data, accepted_media_type=None, renderer_context=None):
+ return data
\ No newline at end of file
diff --git a/llm_be/chat_backend/routing.py b/llm_be/chat_backend/routing.py
new file mode 100644
index 0000000..70829b5
--- /dev/null
+++ b/llm_be/chat_backend/routing.py
@@ -0,0 +1,7 @@
+from django.urls import re_path
+from .views import ChatConsumerAgain
+
+websocket_urlpatterns = [
+ re_path(r'ws/chat_again/$', ChatConsumerAgain.as_asgi()),
+
+]
\ No newline at end of file
diff --git a/llm_be/chat_backend/serializers.py b/llm_be/chat_backend/serializers.py
new file mode 100644
index 0000000..07c0dcc
--- /dev/null
+++ b/llm_be/chat_backend/serializers.py
@@ -0,0 +1,69 @@
+from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
+from rest_framework import serializers
+from .models import CustomUser, Announcement, Company, Conversation, Prompt, Feedback, FEEDBACK_CATEGORIES
+
+
+class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
+ @classmethod
+ def get_token(cls, user):
+ token = super(MyTokenObtainPairSerializer, cls).get_token(user)
+
+ # add custom claim
+ token["company"] = "something here"
+
+ return token
+
+
+class CompanySerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Company
+ fields = "__all__"
+
+
+class AnnouncmentSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Announcement
+ fields = "__all__"
+
+class FeedbackSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Feedback
+ fields = "__all__"
+
+class CustomUserSerializer(serializers.ModelSerializer):
+ email = serializers.EmailField(required=True)
+ username = serializers.CharField()
+ password = serializers.CharField(min_length=8, write_only=True)
+ company = CompanySerializer()
+ has_usable_password = serializers.BooleanField()
+
+ class Meta:
+ model = CustomUser
+ fields = "__all__"
+ extra_kwargs = {"password": {"write_only": True}}
+
+ # def create(self, validated_data):
+ # password = validated_data.pop('password',None)
+ # instance = self.Meta.model(**validated_data)
+ # if password is not None:
+ # instance.set_password(password)
+ # instance.save()
+ # return instance
+
+
+class ConversationSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Conversation
+ fields = ("title", "created", "last_modified", "id")
+
+
+class PromptSerializer(serializers.ModelSerializer):
+
+ class Meta:
+ model = Prompt
+ fields = ("message", "user_created", "created", "id", )
+
+class BasicUserSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = CustomUser
+ fields = ("email", "first_name", "last_name", "is_active","has_usable_password","is_company_manager",'has_signed_tos')
\ No newline at end of file
diff --git a/llm_be/chat_backend/templates/emails/feedback_email.html b/llm_be/chat_backend/templates/emails/feedback_email.html
new file mode 100644
index 0000000..3dd5f4a
--- /dev/null
+++ b/llm_be/chat_backend/templates/emails/feedback_email.html
@@ -0,0 +1,105 @@
+
+
+
+
+
+ New Feedback Submission
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Hello,
+
A new feedback item has been submitted. Here are the details:
+
+
+
+ Title: {{ title }}
+
+
+
+
+ Feedback:
+ {{ feedback_text }}
+
+
+
Thank you for your attention.
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/llm_be/chat_backend/templates/emails/feedback_email.txt b/llm_be/chat_backend/templates/emails/feedback_email.txt
new file mode 100644
index 0000000..1c5c6bb
--- /dev/null
+++ b/llm_be/chat_backend/templates/emails/feedback_email.txt
@@ -0,0 +1,3 @@
+New feedback for Chat by AI ML Operations, LLC
+
+"New Feedback. {{ title }}. {{ feedback_text }}"
\ No newline at end of file
diff --git a/llm_be/chat_backend/templates/emails/invite_email.html b/llm_be/chat_backend/templates/emails/invite_email.html
new file mode 100644
index 0000000..b358c97
--- /dev/null
+++ b/llm_be/chat_backend/templates/emails/invite_email.html
@@ -0,0 +1,97 @@
+
+
+
+
+
+ Invitation to Chat by AI ML Operations, LLC
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Hello,
+
You have been invited to use Chat by AI ML Operations, LLC.
+
+
Please click link to set your password.
+
Once you have set your password go here to get started.
+
+
Thank you.
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/llm_be/chat_backend/templates/emails/invite_email.txt b/llm_be/chat_backend/templates/emails/invite_email.txt
new file mode 100644
index 0000000..0ba347b
--- /dev/null
+++ b/llm_be/chat_backend/templates/emails/invite_email.txt
@@ -0,0 +1,3 @@
+Welcome to AI ML Operations, LLC Chat Services
+
+"Welcome to chat.aimloperations.com. Please use {{ url }} to set your password"
\ No newline at end of file
diff --git a/llm_be/chat_backend/tests.py b/llm_be/chat_backend/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/llm_be/chat_backend/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/llm_be/chat_backend/urls.py b/llm_be/chat_backend/urls.py
new file mode 100644
index 0000000..d32c9c5
--- /dev/null
+++ b/llm_be/chat_backend/urls.py
@@ -0,0 +1,52 @@
+from django.urls import path
+from rest_framework_simplejwt import views as jwt_views
+from .views import (
+ AcknowledgeTermsOfService,
+ CustomObtainTokenView,
+ CustomUserCreate,
+ CustomUserInvite,
+ LogoutAndBlacklistRefreshTokenForUserView,
+ CustomUserGet,
+ is_authenticated,
+ AnnouncmentView,
+ FeedbackView,
+ ConversationsView,
+ ConversationDetailView,
+ CompanyUsersView,
+ SetUserPassword,
+ ConversationPreferences,
+ UserPromptAnalytics,
+ UserConversationAnalytics,
+ CompanyUsageAnalytics,
+ AdminAnalytics
+)
+
+urlpatterns = [
+ path("token/obtain/", CustomObtainTokenView.as_view(), name="token_create"),
+ path("token/refresh/", jwt_views.TokenRefreshView.as_view(), name="token_refresh"),
+ path("user/create/", CustomUserCreate.as_view(), name="create_user"),
+ path("user/invite/", CustomUserInvite.as_view(), name="invite_user"),
+ path("user/set_password//", SetUserPassword.as_view(), name="set_password"),
+ path(
+ "blacklist/",
+ LogoutAndBlacklistRefreshTokenForUserView.as_view(),
+ name="blacklist",
+ ),
+ path("user/get/", CustomUserGet.as_view(), name="get_user"),
+ path("user/acknowledge_tos/", AcknowledgeTermsOfService.as_view(), name="acknowledge_tos"),
+ path("company_users",CompanyUsersView.as_view(), name="company_users"),
+ path("user/is_authenticated/", is_authenticated, name="is_authenticated"),
+ path("announcment/get/", AnnouncmentView.as_view(), name="get_announcments"),
+ path("conversations", ConversationsView.as_view(), name="conversations"),
+ path("feedbacks/", FeedbackView.as_view(), name="feedbacks"),
+ path(
+ "conversation_details",
+ ConversationDetailView.as_view(),
+ name="conversation_details",
+ ),
+ path("conversation_preferences", ConversationPreferences.as_view(), name="conversation_preferences"),
+ path("analytics/user_prompts/", UserPromptAnalytics.as_view(), name="analytics_user_prompts"),
+ path("analytics/user_conversations/", UserConversationAnalytics.as_view(), name="analytics_user_conversations"),
+ path("analytics/company_usage/", CompanyUsageAnalytics.as_view(), name="analytics_company_usage"),
+ path("analytics/admin/", AdminAnalytics.as_view(), name="analytics_admin"),
+]
diff --git a/llm_be/chat_backend/utils.py b/llm_be/chat_backend/utils.py
new file mode 100644
index 0000000..ba6a9f6
--- /dev/null
+++ b/llm_be/chat_backend/utils.py
@@ -0,0 +1,7 @@
+import datetime
+
+def last_day_of_month(any_day):
+ # The day 28 exists in every month. 4 days later, it's always next month
+ next_month = any_day.replace(day=28) + datetime.timedelta(days=4)
+ # subtracting the number of the current day brings us back one month
+ return next_month - datetime.timedelta(days=next_month.day)
\ No newline at end of file
diff --git a/llm_be/chat_backend/views.py b/llm_be/chat_backend/views.py
new file mode 100644
index 0000000..7a87583
--- /dev/null
+++ b/llm_be/chat_backend/views.py
@@ -0,0 +1,714 @@
+from channels.layers import get_channel_layer
+from channels.db import database_sync_to_async
+from rest_framework_simplejwt.views import TokenObtainPairView
+from rest_framework_simplejwt.tokens import RefreshToken
+from rest_framework import permissions, status
+from .serializers import (
+ MyTokenObtainPairSerializer,
+ CustomUserSerializer,
+ BasicUserSerializer,
+ AnnouncmentSerializer,
+ CompanySerializer,
+ ConversationSerializer,
+ PromptSerializer,
+ FeedbackSerializer
+)
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from .models import CustomUser, Announcement, Conversation, Prompt, Feedback,PromptMetric
+from django.views.decorators.cache import never_cache
+from django.http import JsonResponse
+from datetime import datetime
+from .client import LlamaClient
+from asgiref.sync import sync_to_async, async_to_sync
+from channels.generic.websocket import AsyncWebsocketConsumer
+from langchain_ollama.llms import OllamaLLM
+from langchain_core.prompts import ChatPromptTemplate
+from langchain_core.messages import HumanMessage, SystemMessage
+import re
+from django.conf import settings
+import json
+import base64
+import pandas as pd
+
+# For email support
+from django.core.mail import EmailMultiAlternatives
+from django.template.loader import render_to_string
+from django.utils.html import strip_tags
+from django.template.loader import get_template
+from django.template import Context
+from django.utils import timezone
+from django.core.files import File
+from django.core.files.base import ContentFile
+import math
+import datetime
+import pytz
+
+from dateutil.relativedelta import relativedelta
+
+from .utils import last_day_of_month
+
+
+CHANNEL_NAME: str = 'llm_messages'
+MODEL_NAME: str = "llama3"
+
+# Create your views here.
+class CustomObtainTokenView(TokenObtainPairView):
+ permission_classes = (permissions.AllowAny,)
+ serializer_class = MyTokenObtainPairSerializer
+
+
+class CustomUserCreate(APIView):
+ permission_classes = (permissions.AllowAny,)
+ authentication_classes = ()
+
+ def post(self, request, format="json"):
+ serializer = CustomUserSerializer(data=request.data)
+ if serializer.is_valid():
+ user = serializer.save()
+ if user:
+ json = serializer.data
+ return Response(json, status=status.HTTP_201_CREATED)
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+def send_invite_email(slug, email_to_invite):
+ print(f"url : https://www.chat.aimloperations.com/set_password?slug={slug}")
+ url = f"https://www.chat.aimloperations.com/set_password?slug={slug}"
+ subject = "Welcome to AI ML Operations, LLC Chat Services"
+ from_email = "ryan@aimloperations.com"
+ to=email_to_invite
+ d = {"url": url}
+ html_content = get_template(r'emails/invite_email.html').render(d)
+ text_content = get_template(r'emails/invite_email.txt').render(d)
+
+ msg = EmailMultiAlternatives(subject, text_content, from_email, [to])
+ msg.attach_alternative(html_content, "text/html")
+ msg.send(fail_silently=True)
+
+def send_feedback_email(feedback_obj):
+ subject = "New Feedback for Chat by AI ML Operations, LLC"
+ from_email = "ryan@aimloperations.com"
+ to="ryan@aimloperations.com"
+ d = {"title": feedback_obj.title, "feedback_text": feedback_obj.text}
+ html_content = get_template(r'emails/feedback_email.html').render(d)
+ text_content = get_template(r'emails/feedback_email.txt').render(d)
+
+ msg = EmailMultiAlternatives(subject, text_content, from_email, [to])
+ msg.attach_alternative(html_content, "text/html")
+ msg.send(fail_silently=True)
+
+class CustomUserInvite(APIView):
+ http_method_names = ['post']
+ def post(self, request, format="json"):
+ def valid_email(email_string):
+ regex = r'^[a-z0-9]+[\._]?[a-z0-9]+[@]\w+[.]\w+$'
+ if re.match(regex,email_string):
+ return True
+ else:
+ return False
+
+ email_to_invite = request.data['email']
+
+ if len(email_to_invite) == 0 or not valid_email(email_to_invite) or not request.user.is_company_manager:
+ return Response(status=status.HTTP_400_BAD_REQUEST)
+ # make sure there isn't a user with this email already
+ existing_users = CustomUser.objects.filter(email=email_to_invite)
+ if len(existing_users) > 0:
+ return Response(status=status.HTTP_400_BAD_REQUEST)
+ # create the object and send the email
+ user = CustomUser.objects.create(email=email_to_invite, username=email_to_invite, company=request.user.company)
+
+ # send an email
+ send_invite_email(user.slug, email_to_invite)
+
+
+
+ return Response(status=status.HTTP_201_CREATED)
+
+class SetUserPassword(APIView):
+ http_method_names = ['post','get']
+ permission_classes = (permissions.AllowAny,)
+ authentication_classes = ()
+ def get(self, request, slug):
+ user = CustomUser.objects.get(slug=slug)
+ if user.last_login:
+ return Response(status=status.HTTP_401_UNAUTHORIZED)
+ else:
+ return Response(status=status.HTTP_200_OK)
+ def post(self, request, slug, format="json"):
+ user = CustomUser.objects.get(slug=slug)
+ user.set_password(request.data['password'])
+ user.save()
+ return Response(status=status.HTTP_200_OK)
+
+
+
+class CustomUserGet(APIView):
+ http_method_names = ['get', 'head', 'post']
+ def get(self, request, format="json"):
+
+ email = request.user.email
+ username = request.user.username
+ user = CustomUser.objects.get(email=email)
+ serializer = CustomUserSerializer(user)
+
+ return Response(serializer.data, status=status.HTTP_200_OK)
+
+class FeedbackView(APIView):
+ http_method_names = ['post','get']
+ def post(self, request, format="json"):
+ serializer = FeedbackSerializer(data=request.data)
+ print(request.data)
+ if serializer.is_valid():
+
+ feedback_obj = serializer.save()
+ feedback_obj.user = request.user
+
+ feedback_obj.save()
+ send_feedback_email(feedback_obj)
+ return Response(serializer.data, status=status.HTTP_201_CREATED)
+ else:
+ print(serializer.errors)
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+ def get(self, request, format="json"):
+ feedback_objs = Feedback.objects.filter(user=request.user)
+ serializer = FeedbackSerializer(feedback_objs, many=True)
+ return Response(serializer.data, status=status.HTTP_200_OK)
+
+
+
+class AcknowledgeTermsOfService(APIView):
+ http_method_names = ['post']
+ def post(self, request, format="json"):
+ request.user.has_signed_tos = True
+ request.user.save()
+ return Response(status=status.HTTP_200_OK)
+
+class CompanyUsersView(APIView):
+ def get(self, request, format="json"):
+ # TODO: make sure you are a manager of that company
+ if request.user.is_company_manager:
+ users = CustomUser.objects.filter(company_id=request.user.company.id)
+ serializer = BasicUserSerializer(users, many=True)
+ return Response(serializer.data, status=status.HTTP_200_OK)
+ else:
+ return Response(status=status.HTTP_401_UNAUTHORIZED)
+
+
+ def post(self, request, format="json"):
+ if request.user.is_company_manager:
+ user = CustomUser.objects.get(email=request.data.get("email"))
+ if request.user.company_id == user.company_id:
+ field = request.data.get("field")
+ data = {}
+ if field == "is_active":
+ data.update({"is_active": not user.is_active})
+ elif field == "company_manager":
+ data.update({"is_company_manager": not user.is_company_manager})
+ elif field == "has_password":
+ if user.has_usable_password():
+ user.set_unusable_password()
+ serializer = CustomUserSerializer(user, data, partial=True)
+ if serializer.is_valid():
+ serializer.save()
+ return Response(status=status.HTTP_200_OK)
+ return Response(status=status.HTTP_400_BAD_REQUEST)
+ return Response(status=status.HTTP_401_UNAUTHORIZED)
+
+ def delete(self, request, format="json"):
+ if request.user.is_company_manager:
+ user = CustomUser.objects.get(email=request.data.get("email"))
+ if request.user.company_id == user.company_id:
+ user.delete()
+ return Response(status=status.HTTP_200_OK)
+ return Response(status=status.HTTP_401_UNAUTHORIZED)
+
+class AnnouncmentView(APIView):
+ permission_classes = (permissions.AllowAny,)
+ serializer_class = AnnouncmentSerializer
+
+ def get(self, request, format="json"):
+ announcements = Announcement.objects.all()
+ serializer = AnnouncmentSerializer(announcements, many=True)
+
+ return Response(serializer.data, status=status.HTTP_200_OK)
+
+
+class LogoutAndBlacklistRefreshTokenForUserView(APIView):
+ permission_classes = (permissions.AllowAny,)
+ authentication_classes = ()
+
+ def post(self, request):
+ try:
+ refresh_token = request.data["refresh_token"]
+ token = RefreshToken(refresh_token)
+ token.blacklist()
+ return Response(status=status.HTTP_205_RESET_CONTENT)
+ except Exception as e:
+ return Response(status=status.HTTP_400_BAD_REQUEST)
+
+
+@never_cache
+def is_authenticated(request):
+ if request.user.is_authenticated:
+ return JsonResponse({}, status=status.HTTP_200_OK)
+ return JsonResponse({}, status=status.HTTP_401_UNAUTHORIZED)
+
+
+class ConversationsView(APIView):
+ def get(self, request, format="json"):
+ order = "created" if request.user.conversation_order else "-created"
+ conversations = Conversation.objects.filter(user=request.user, deleted=False).order_by(order)
+ serializer = ConversationSerializer(conversations, many=True)
+ return Response(serializer.data, status=status.HTTP_200_OK)
+
+ def post(self, request, format="json"):
+ """
+ Create a blank conversation and return the title and id number
+ """
+ title = request.data.get("name")
+ conversation = Conversation.objects.create(title=title)
+ conversation.save()
+ conversation.user_id = request.user.id
+ conversation.save()
+
+ # TODO: when we are smart enough to create a conversation when a prompt is sent
+ # client = LlamaClient()
+ # inital_message = request.data.get("prompt")
+ # title = client.generate_conversation_title(inital_message)
+ # title = title if title else "New Conversation"
+ # conversation = Conversation.objects.create(title=title)
+ # conversation.save()
+ # conversation.user_id = request.user.id
+ # conversation.save()
+
+ return Response({"title": title, "id": conversation.id}, status=status.HTTP_201_CREATED)
+
+
+class ConversationPreferences(APIView):
+ def get(self, request, format="json"):
+ user = request.user
+ return Response({"order": user.conversation_order}, status=status.HTTP_200_OK)
+
+ def post(self, request, format="json"):
+ user = request.user
+ user.conversation_order = not user.conversation_order
+ user.save()
+ return Response({"order": user.conversation_order}, status=status.HTTP_200_OK)
+
+
+
+class ConversationDetailView(APIView):
+ def get(self, request, format="json"):
+ conversation_id = request.query_params.get("conversation_id")
+ prompts = Prompt.objects.filter(conversation__id=conversation_id)
+ serailzer = PromptSerializer(prompts, many=True)
+ return Response(serailzer.data, status=status.HTTP_200_OK)
+
+
+ def post(self, request, format="json"):
+ print('In the post')
+ # Add the prompt to the database
+ # make sure there is a conversation for it
+ # if there is not a conversation create a title for it
+
+ # make sure that our model exists and it is running
+ prompt = request.data.get("prompt")
+
+ conversation_id = request.data.get("conversation_id")
+ is_user = bool(request.data.get("is_user"))
+
+ try:
+ conversation = Conversation.objects.get(id=conversation_id)
+
+ # add the prompt to the conversation
+ serializer = PromptSerializer(
+ data={
+ "message": prompt,
+ "user_created": is_user,
+ "created": datetime.now(),
+ }
+ )
+ if serializer.is_valid():
+ prompt_instance = serializer.save()
+ prompt_instance.conversation_id = conversation.id
+ prompt_instance = serializer.save()
+
+ # set up the streaming response if it is from the user
+ print(f'Do we have a valid user? {is_user}')
+ if is_user:
+ messages = []
+ for prompt_obj in Prompt.objects.filter(conversation__id=conversation_id):
+ messages.append({
+ 'content':prompt_obj.message,
+ 'role': 'user' if prompt_obj.user_created else 'assistant'
+ })
+
+ channel_layer = get_channel_layer()
+ print(f'Sending to the channel: {CHANNEL_NAME}')
+ async_to_sync(channel_layer.group_send)(
+ CHANNEL_NAME, {
+ 'type':'receive',
+ 'content': messages
+ }
+ )
+ except:
+ print(f"Error trying to submit to conversation_id: {conversation_id} with request.data: {request.data}")
+ pass
+
+
+ return Response(status=status.HTTP_200_OK)
+
+ def delete(self, request, format="json"):
+ conversation_id = request.data.get("conversation_id")
+ conversation = Conversation.objects.get(id=conversation_id, user=request.user)
+ conversation.deleted = True
+ conversation.save()
+ return Response(status=status.HTTP_202_ACCEPTED)
+
+class UserPromptAnalytics(APIView):
+ def get(self, request, format="json"):
+ now = timezone.now()
+ result = []
+
+ number_of_months = 3
+ company_user_ids = CustomUser.objects.filter(company=request.user.company).values_list('id', flat=True)
+ for i in range(number_of_months):
+ next_year = now.year
+ next_month = now.month - i
+ while next_month < 1:
+ next_year -= 1
+ next_month += 12
+
+ start_date = datetime.datetime(next_year, next_month, 1)
+ end_date = last_day_of_month(start_date)
+ total_conversations = Conversation.objects.filter(created__gte=start_date, created__lte=end_date)
+ total_prompts = Prompt.objects.filter(conversation__id__in=total_conversations, created__gte=start_date, created__lte=end_date)
+ total_users = len(CustomUser.objects.all())
+ my_conversations = Conversation.objects.filter(user=request.user)
+ my_prompts = Prompt.objects.filter(conversation__in=my_conversations, created__gte=start_date, created__lte=end_date)
+ company_conversations = Conversation.objects.filter(user__id__in=company_user_ids)
+ company_prompts = Prompt.objects.filter(conversation__in=company_conversations, created__gte=start_date, created__lte=end_date)
+
+ result.append({
+ "month":start_date.strftime("%B"),
+ "you": len(my_prompts),
+ "others": len(company_prompts)/len(company_user_ids),
+ "all":len(total_prompts)/total_users
+ })
+
+ return Response(result[::-1], status=status.HTTP_200_OK)
+
+class UserConversationAnalytics(APIView):
+ def get(self, request, format="json"):
+ now = timezone.now()
+ result = []
+
+ number_of_months = 3
+ company_user_ids = CustomUser.objects.filter(company=request.user.company).values_list('id', flat=True)
+ for i in range(number_of_months):
+ next_year = now.year
+ next_month = now.month - i
+ while next_month < 1:
+ next_year -= 1
+ next_month += 12
+
+ start_date = datetime.datetime(next_year, next_month, 1)
+ end_date = last_day_of_month(start_date)
+ total_conversations = len(Conversation.objects.filter(created__gte=start_date, created__lte=end_date))
+ total_users = len(CustomUser.objects.all())
+ company_conversations = len(Conversation.objects.filter(user__id__in=company_user_ids, created__gte=start_date, created__lte=end_date))
+
+ result.append({
+ "month":start_date.strftime("%B"),
+ "you": len(Conversation.objects.filter(user=request.user, created__gte=start_date, created__lte=end_date)),
+ "others": company_conversations/len(company_user_ids),
+ "all":total_conversations/total_users
+ })
+
+
+ return Response(result[::-1], status=status.HTTP_200_OK)
+
+class CompanyUsageAnalytics(APIView):
+ def get(self, request, format="json"):
+ now = timezone.now()
+ result = []
+
+ number_of_months = 3
+ company_user_ids = CustomUser.objects.filter(company=request.user.company).values_list('id', flat=True)
+
+ for i in range(number_of_months):
+ next_year = now.year
+ next_month = now.month - i
+ while next_month < 1:
+ next_year -= 1
+ next_month += 12
+
+ start_date = datetime.datetime(next_year, next_month, 1)
+ end_date = last_day_of_month(start_date)
+ conversations = Conversation.objects.filter(user__id__in=company_user_ids, created__gte=start_date, created__lte=end_date)
+
+ conversation_user_ids = conversations.values_list("user__id", flat=True).distinct()
+ result.append({
+ "month":start_date.strftime("%B"),
+ "used":len(conversation_user_ids),
+ "not_used":len(company_user_ids) - len(conversation_user_ids)
+ })
+ return Response(result[::-1], status=status.HTTP_200_OK)
+
+class AdminAnalytics(APIView):
+ def get(self, request, format="json"):
+ number_of_months = 3
+ result = []
+ now = timezone.now()
+
+ for i in range(number_of_months):
+ next_year = now.year
+ next_month = now.month - i
+ while next_month < 1:
+ next_year -= 1
+ next_month += 12
+
+ start_date = datetime.datetime(next_year, next_month, 1)
+ end_date = last_day_of_month(start_date)
+ durations = [item.get_duration() for item in PromptMetric.objects.filter(created__gte=start_date, created__lte=end_date)]
+ if len(durations) == 0:
+ result.append({
+ "month":start_date.strftime("%B"),
+ "range":[0,0],
+ "avg": 0,
+ "median":0,
+ })
+ continue
+
+ average = sum(durations)/len(durations)
+ min_value = min(durations)
+ max_value = max(durations)
+ durations.sort()
+ median = durations[len(durations)//2]
+ result.append({
+ "month":start_date.strftime("%B"),
+ "range":[min_value,max_value],
+ "avg": average,
+ "median":median,
+ })
+
+
+
+
+ return Response(result[::-1], status=status.HTTP_200_OK)
+
+prompt = ChatPromptTemplate.from_messages([
+ ("system", "You are a helpful assistant."),
+ ("user", "{input}")
+])
+
+llm = OllamaLLM(model=MODEL_NAME)
+
+# output_parser = StrOutputParser()
+# # Chain
+# chain = prompt | llm.with_config({"run_name": "model"}) | output_parser.with_config({"run_name": "Assistant"})
+
+@database_sync_to_async
+def create_conversation(prompt, email):
+ # return the conversation id
+
+
+ response = llm.invoke("Summarise the phrase in one to for words\"%s\"" % prompt)
+ print(f"Response: {response}")
+ print(dir(response))
+ title = response.replace("\"","")
+ title = " ".join(title.split(" ")[:4])
+
+
+ conversation = Conversation.objects.create(title = title)
+ conversation.save()
+
+ user = CustomUser.objects.get(email=email)
+ conversation.user_id = user.id
+ conversation.save()
+ return conversation.id
+
+@database_sync_to_async
+def get_messages(conversation_id, prompt, file_string: str = None, file_type: str = ""):
+ messages = []
+
+ conversation = Conversation.objects.get(id=conversation_id)
+ print(file_string)
+
+ # add the prompt to the conversation
+ serializer = PromptSerializer(
+ data={
+ "message": prompt,
+ "user_created": True,
+ "created": datetime.now(),
+ }
+ )
+ if serializer.is_valid(raise_exception=True):
+ prompt_instance = serializer.save()
+ prompt_instance.conversation_id = conversation.id
+ prompt_instance.save()
+ if file_string:
+ file_name = f"prompt_{prompt_instance.id}_data.{file_type}"
+ f = ContentFile(file_string, name=file_name)
+ prompt_instance.file.save(file_name, f)
+ prompt_instance.file_type = file_type
+ prompt_instance.save()
+
+
+
+ for prompt_obj in Prompt.objects.filter(conversation__id=conversation_id):
+ messages.append({
+ 'content': prompt_obj.message,
+ 'role': 'user' if prompt_obj.user_created else 'assistant',
+ 'has_file': prompt_obj.file_exists(),
+ 'file': prompt_obj.file if prompt_obj.file_exists() else None,
+ 'file_type': prompt_obj.file_type if prompt_obj.file_exists() else None,
+ })
+
+ # now transform the messages
+ transformed_messages = []
+ for message in messages:
+
+ if message['has_file'] and message['file_type'] != None:
+ if 'csv' in message['file_type']:
+ file_type = 'csv'
+ altered_message = f"{message['content']}\n The file type is csv and the file contents are: {message['file'].read()}"
+ elif 'xlsx' in message['file_type']:
+ file_type = 'xlsx'
+ df = pd.read_excel(message['file'].read())
+ altered_message = f"{message['content']}\n The file type is xlsx and the file contents are: {df}"
+ elif 'txt' in message['file_type']:
+ file_type = 'txt'
+ altered_message = f"{message['content']}\n The file type is csv and the file contents are: {message['file'].read()}"
+ else:
+ altered_message = message['content']
+
+ transformed_message = SystemMessage(content=altered_message) if message['role'] == 'assistant' else HumanMessage(content=altered_message)
+ transformed_messages.append(transformed_message)
+
+ return transformed_messages, prompt_instance
+
+
+@database_sync_to_async
+def save_generated_message(conversation_id, message):
+ conversation = Conversation.objects.get(id=conversation_id)
+
+ # add the prompt to the conversation
+ serializer = PromptSerializer(
+ data={
+ "message": message,
+ "user_created": False,
+ "created": datetime.now(),
+ }
+ )
+ if serializer.is_valid():
+ prompt_instance = serializer.save()
+ prompt_instance.conversation_id = conversation.id
+ prompt_instance = serializer.save()
+
+@database_sync_to_async
+def create_prompt_metric(prompt_id, prompt, has_file, file_type, model_name, conversation_id):
+ prompt_metric = PromptMetric.objects.create(
+ prompt_id=prompt_id,
+ start_time = timezone.now(),
+ prompt_length = len(prompt),
+ has_file = has_file,
+ file_type = file_type,
+ model_name=model_name,
+ conversation_id=conversation_id,
+ )
+ prompt_metric.save()
+ return prompt_metric
+
+@database_sync_to_async
+def update_prompt_metric(prompt_metric, status):
+ prompt_metric.event = status
+ prompt_metric.save()
+
+@database_sync_to_async
+def finish_prompt_metric(prompt_metric, response_length):
+ print(f'finish_prompt_metric: {response_length}')
+ prompt_metric.end_time = timezone.now()
+ prompt_metric.reponse_length = response_length
+ prompt_metric.event = 'FINISHED'
+ prompt_metric.save(update_fields=["end_time", "reponse_length","event"])
+ print("finish_prompt_metric saved")
+
+class ChatConsumerAgain(AsyncWebsocketConsumer):
+ async def connect(self):
+ await self.accept()
+
+ async def disconnect(self, close_code):
+ await self.close()
+
+ async def receive(self, text_data=None, bytes_data=None):
+ print(f"Text Data: {text_data}")
+ print(f"Bytes Data: {bytes_data}")
+ if text_data:
+ data = json.loads(text_data)
+ message = data.get('message',None)
+ conversation_id = data.get('conversation_id',None)
+ email = data.get("email", None)
+ file = data.get("file", None)
+ file_type = data.get("fileType", "")
+
+ if not conversation_id:
+ # we need to create a new conversation
+ # we will generate a name for it too
+ conversation_id = await create_conversation(message, email)
+
+ if conversation_id:
+ decoded_file = None
+
+ if file:
+ decoded_file = base64.b64decode(file)
+ print(decoded_file)
+ if 'csv' in file_type:
+ file_type = 'csv'
+ altered_message = f"{message}\n The file type is csv and the file contents are: {decoded_file}"
+ elif 'xmlformats-officedocument' in file_type:
+ file_type = 'xlsx'
+ df = pd.read_excel(decoded_file)
+ altered_message = f"{message}\n The file type is xlsx and the file contents are: {df}"
+ elif 'text' in file_type:
+ file_type = 'txt'
+ altered_message = f"{message}\n The file type is txt and the file contents are: {decoded_file}"
+ else:
+ file_type = 'Not Sure'
+
+
+
+
+ print(f'received: "{message}" for conversation {conversation_id}')
+ # TODO: add the message to the database
+ # get the new conversation
+ # TODO: get the messages here
+
+ messages, prompt = await get_messages(conversation_id, message, decoded_file, file_type)
+
+ prompt_metric = await create_prompt_metric(prompt.id, prompt.message, True if file else False, file_type, MODEL_NAME, conversation_id)
+ if file:
+ # udpate with the altered_message
+ messages = messages[:-1] + [HumanMessage(content=altered_message)]
+ print(messages)
+ # send it to the LLM
+
+ # stream the response back
+ response = ""
+ # start of the message
+ await self.send('CONVERSATION_ID')
+ await self.send(str(conversation_id))
+ await self.send('START_OF_THE_STREAM_ENDER_GAME_42')
+ async for chunk in llm.astream(messages):
+ print(f"chunk: {chunk}")
+ response += chunk
+ await self.send(chunk)
+ await self.send('END_OF_THE_STREAM_ENDER_GAME_42')
+ await save_generated_message(conversation_id, response)
+
+ await finish_prompt_metric(prompt_metric, len(response))
+
+ if bytes_data:
+ print("we have byte data")
diff --git a/llm_be/llm_be/__init__.py b/llm_be/llm_be/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/llm_be/llm_be/asgi.py b/llm_be/llm_be/asgi.py
new file mode 100644
index 0000000..f8be107
--- /dev/null
+++ b/llm_be/llm_be/asgi.py
@@ -0,0 +1,25 @@
+"""
+ASGI config for llm_be project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+from channels.routing import ProtocolTypeRouter, URLRouter
+from channels.auth import AuthMiddlewareStack
+import chat_backend.routing
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'llm_be.settings')
+
+application = ProtocolTypeRouter({
+ "http": get_asgi_application(),
+ "websocket": AuthMiddlewareStack(
+ URLRouter(
+ chat_backend.routing.websocket_urlpatterns
+ )
+ ),
+ })
diff --git a/llm_be/llm_be/settings.py b/llm_be/llm_be/settings.py
new file mode 100644
index 0000000..ab0118b
--- /dev/null
+++ b/llm_be/llm_be/settings.py
@@ -0,0 +1,205 @@
+"""
+Django settings for llm_be project.
+
+Generated by 'django-admin startproject' using Django 3.2.18.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/3.2/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/3.2/ref/settings/
+"""
+
+from pathlib import Path
+from datetime import timedelta
+import os
+
+# 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/3.2/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'django-insecure-6suk6fj5q2)1tj%)f(wgw1smnliv5-#&@zvgvj1wp#(#@h#31x'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = ['*.aimloperations.com','localhost','127.0.0.1','chat.aimloperations.com','chatbackend.aimloperations.com']
+CORS_ORIGIN_ALLOW_ALL = True
+
+
+# Application definition
+
+INSTALLED_APPS = [
+ 'daphne',
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ 'chat_backend',
+ 'rest_framework',
+ 'corsheaders',
+ 'rest_framework_simplejwt.token_blacklist',
+
+]
+
+MIDDLEWARE = [
+ '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",
+ "django.middleware.common.CommonMiddleware",
+]
+
+ROOT_URLCONF = 'llm_be.urls'
+
+# SETTINGS_PATH = os.path.dirname(os.path.dirname(__file__))
+# TEMPLATE_DIRS = (
+# os.path.join(SETTINGS_PATH, 'templates'),
+# )
+
+print(os.path.join(BASE_DIR, 'templates'))
+
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [os.path.join(BASE_DIR, 'templates')],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.debug',
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
+]
+
+WSGI_APPLICATION = 'llm_be.wsgi.application'
+ASGI_APPLICATION = 'llm_be.asgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': BASE_DIR / 'db.sqlite3',
+ }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+ {
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ },
+]
+
+
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/3.2/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'UTC'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/3.2/howto/static-files/
+
+STATIC_URL = '/static/'
+
+# Default primary key field type
+# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
+
+DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
+
+# custom user model
+AUTH_USER_MODEL = 'chat_backend.CustomUser'
+
+# rest framework jwt stuff
+REST_FRAMEWORK = {
+ 'DEFAULT_PERMISSION_CLASSES': (
+ 'rest_framework.permissions.IsAuthenticated',
+ ),
+ 'DEFAULT_AUTHENTICATION_CLASSES': (
+'rest_framework_simplejwt.authentication.JWTAuthentication',
+ ), #
+}
+
+SIMPLE_JWT = {
+ 'ACCESS_TOKEN_LIFETIME':timedelta(hours=5),
+ 'REFRESH_TOKEN_LIFETIME':timedelta(days=14),
+ 'ROTATE_REFRESH_TOKENS':True,
+ 'BLACKLIST_AFTER_ROTATION':True,
+ 'ALGORITHM':"HS256",
+ "SIGNING_KEY":SECRET_KEY,
+ 'VERIFYING_KEY':None,
+ "AUTH_HEADER_TYPES":('JWT',),
+ 'USER_ID_FIELD':'id',
+ 'USER_ID_CLAIM':'user_id',
+ 'AUTH_TOKEN_CLASSES':('rest_framework_simplejwt.tokens.AccessToken',),
+ 'TOKEN_TYPE_CLAIM':'token_type',
+}
+
+# CORS settings
+CORS_ALLOWED_ORIGINS = [
+ "http://localhost:3000",
+ "http://127.0.0.1:3000",
+]
+
+# channel settings
+CHANNEL_LAYERS = {
+ 'default': {
+ 'BACKEND': 'channels.layers.InMemoryChannelLayer',
+ },
+}
+
+# # Office 365 settings
+# EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
+# EMAIL_HOST = os.getenv("APP_EMAIL_HOST", "smtp.office365.com")
+# EMAIL_PORT = os.getenv("APP_EMAIL_PORT", 587)
+# EMAIL_HOST_USER = "ryan@aimloperations.com"#os.getenv("APP_EMAIL_HOST_USER")
+# SERVER_EMAIL = EMAIL_HOST_USER
+# DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
+# EMAIL_HOST_PASSWORD = "!HopeThisW0rkz"#os.getenv("APP_EMAIL_HOST_PASSWORD")
+# EMAIL_USE_TLS = os.getenv("APP_EMAIL_USE_TLS", True)
+# EMAIL_TIMEOUT = os.getenv("APP_EMAIL_TIMEOUT", 60)
+
+# SMTP2GO
+EMAIL_HOST = 'mail.smtp2go.com'
+EMAIL_HOST_USER = 'info.aimloperations.com'
+EMAIL_HOST_PASSWORD = 'ZDErIII2sipNNVMz'
+EMAIL_PORT = 2525
+EMAIL_USE_TLS = True
\ No newline at end of file
diff --git a/llm_be/llm_be/urls.py b/llm_be/llm_be/urls.py
new file mode 100644
index 0000000..16d76aa
--- /dev/null
+++ b/llm_be/llm_be/urls.py
@@ -0,0 +1,24 @@
+"""llm_be URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+ https://docs.djangoproject.com/en/3.2/topics/http/urls/
+Examples:
+Function views
+ 1. Add an import: from my_app import views
+ 2. Add a URL to urlpatterns: path('', views.home, name='home')
+Class-based views
+ 1. Add an import: from other_app.views import Home
+ 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
+Including another URLconf
+ 1. Import the include() function: from django.urls import include, path
+ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
+"""
+from django.contrib import admin
+from django.urls import path, include
+from django.conf import settings
+from django.conf.urls.static import static
+
+urlpatterns = [
+ path('admin/', admin.site.urls),
+ path('api/', include('chat_backend.urls')),
+] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
diff --git a/llm_be/llm_be/wsgi.py b/llm_be/llm_be/wsgi.py
new file mode 100644
index 0000000..1edc130
--- /dev/null
+++ b/llm_be/llm_be/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for llm_be project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'llm_be.settings')
+
+application = get_wsgi_application()
diff --git a/llm_be/manage.py b/llm_be/manage.py
new file mode 100755
index 0000000..96e59ea
--- /dev/null
+++ b/llm_be/manage.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+def main():
+ """Run administrative tasks."""
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'llm_be.settings')
+ try:
+ from django.core.management import execute_from_command_line
+ except ImportError as exc:
+ raise ImportError(
+ "Couldn't import Django. Are you sure it's installed and "
+ "available on your PYTHONPATH environment variable? Did you "
+ "forget to activate a virtual environment?"
+ ) from exc
+ execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/llm_be/scp_command.txt b/llm_be/scp_command.txt
new file mode 100644
index 0000000..5ed525b
--- /dev/null
+++ b/llm_be/scp_command.txt
@@ -0,0 +1 @@
+scp -r ./* westfarn@10.0.0.103:/home/westfarn/chat_temp/
diff --git a/requirements.dev b/requirements.dev
new file mode 100644
index 0000000..60d210d
--- /dev/null
+++ b/requirements.dev
@@ -0,0 +1,133 @@
+aiohappyeyeballs==2.4.4
+aiohttp==3.11.11
+aiosignal==1.3.2
+annotated-types==0.7.0
+anyio==4.7.0
+asgiref==3.8.1
+astor==0.8.1
+async-timeout==4.0.3
+attrs==24.3.0
+autobahn==24.4.2
+Automat==24.8.1
+black==24.10.0
+certifi==2024.12.14
+cffi==1.17.1
+channels==4.2.0
+charset-normalizer==3.4.1
+click==8.1.8
+constantly==23.10.4
+contourpy==1.3.0
+cryptography==44.0.0
+cycler==0.12.1
+daphne==4.1.2
+dataclasses-json==0.6.7
+distro==1.9.0
+Django==4.2.17
+django-autoslug==1.9.9
+django-cors-headers==4.6.0
+djangorestframework==3.15.2
+djangorestframework-simplejwt==5.3.1
+duckdb==1.1.3
+et_xmlfile==2.0.0
+exceptiongroup==1.2.2
+Faker==33.1.0
+filelock==3.16.1
+fonttools==4.55.3
+frozenlist==1.5.0
+fsspec==2024.12.0
+greenlet==3.1.1
+h11==0.14.0
+httpcore==1.0.7
+httpx==0.27.2
+httpx-sse==0.4.0
+hyperlink==21.0.0
+idna==3.10
+importlib_resources==6.4.5
+incremental==24.7.2
+Jinja2==3.1.5
+jiter==0.8.2
+jsonpatch==1.33
+jsonpointer==3.0.0
+kiwisolver==1.4.7
+langchain==0.3.13
+langchain-community==0.3.13
+langchain-core==0.3.28
+langchain-ollama==0.2.2
+langchain-openai==0.2.14
+langchain-text-splitters==0.3.4
+langsmith==0.2.7
+lxml==5.3.0
+MarkupSafe==3.0.2
+marshmallow==3.23.2
+matplotlib==3.9.4
+mpmath==1.3.0
+multidict==6.1.0
+mypy-extensions==1.0.0
+networkx==3.2.1
+numpy==2.0.2
+nvidia-cublas-cu12==12.4.5.8
+nvidia-cuda-cupti-cu12==12.4.127
+nvidia-cuda-nvrtc-cu12==12.4.127
+nvidia-cuda-runtime-cu12==12.4.127
+nvidia-cudnn-cu12==9.1.0.70
+nvidia-cufft-cu12==11.2.1.3
+nvidia-curand-cu12==10.3.5.147
+nvidia-cusolver-cu12==11.6.1.9
+nvidia-cusparse-cu12==12.3.1.170
+nvidia-nccl-cu12==2.21.5
+nvidia-nvjitlink-cu12==12.4.127
+nvidia-nvtx-cu12==12.4.127
+ollama==0.4.5
+ollama-python==0.1.2
+openai==1.58.1
+openpyxl==3.1.5
+orjson==3.10.13
+packaging==24.2
+pandas==2.2.3
+pandasai==2.4.1
+pathspec==0.12.1
+pillow==11.0.0
+platformdirs==4.3.6
+propcache==0.2.1
+pyasn1==0.6.1
+pyasn1_modules==0.4.1
+pycparser==2.22
+pydantic==2.10.4
+pydantic-settings==2.7.1
+pydantic_core==2.27.2
+PyJWT==2.10.1
+pyOpenSSL==24.3.0
+pyparsing==3.2.0
+python-dateutil==2.9.0.post0
+python-docx==1.1.2
+python-dotenv==1.0.1
+pytz==2024.2
+PyYAML==6.0.2
+regex==2024.11.6
+requests==2.32.3
+requests-toolbelt==1.0.0
+responses==0.25.3
+scipy==1.13.1
+service-identity==24.2.0
+six==1.17.0
+sniffio==1.3.1
+SQLAlchemy==2.0.36
+sqlglot==26.0.1
+sqlglotrs==0.3.0
+sqlparse==0.5.3
+sympy==1.13.1
+tenacity==9.0.0
+tiktoken==0.8.0
+tomli==2.2.1
+torch==2.5.1
+tqdm==4.67.1
+triton==3.1.0
+Twisted==24.11.0
+txaio==23.1.1
+typing-inspect==0.9.0
+typing_extensions==4.12.2
+tzdata==2024.2
+urllib3==2.3.0
+yarl==1.18.3
+zipp==3.21.0
+zope.interface==7.2
diff --git a/upgrade_all_package.sh b/upgrade_all_package.sh
new file mode 100755
index 0000000..c428a0b
--- /dev/null
+++ b/upgrade_all_package.sh
@@ -0,0 +1 @@
+python -m pip --disable-pip-version-check list --outdated --format=json | python -c "import json, sys; print('\n'.join([x['name'] for x in json.load(sys.stdin)]))" | xargs -n1 pip install -U