replace python django backend with nodejs backend

This commit is contained in:
wea_ondara
2024-05-27 18:59:58 +02:00
parent ebd0748894
commit 8b60d023e8
123 changed files with 15193 additions and 88 deletions

View File

3
backend_old/ai/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@@ -0,0 +1,51 @@
import datetime
import json
import os
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
class AiBase:
model_id_or_path: str = None
model = None
tokenizer = None
def __init__(self, model_id_or_path: str = None):
self.model_id_or_path = model_id_or_path
print('Loading ' + model_id_or_path)
self.model_id_or_path = model_id_or_path
self.model = AutoModelForCausalLM.from_pretrained(model_id_or_path, torch_dtype='auto', device_map='auto')
self.tokenizer = AutoTokenizer.from_pretrained(model_id_or_path)
# print(self.tokenizer.default_chat_template)
# print(type(self.model))
# print(type(self.tokenizer))
print('Loaded')
def generate(self, messages):
return []
def record_conversation(self, messages, response):
messages = messages + [response]
this_dir = os.path.dirname(os.path.abspath(__file__))
conversations_folder = this_dir + '/../../../conversations'
folder = conversations_folder + '/' + self.model_id_or_path.replace('/', '_')
self._mkdir(conversations_folder)
self._mkdir(folder)
timestamp = datetime.datetime.utcnow().strftime('%Y%m%d%H%M%S')
pickle_filename = folder + '/' + timestamp + '.json'
with open(pickle_filename, 'w') as file:
json.dump(messages, file)
def _mkdir(self, path):
if not os.path.isdir(path):
os.mkdir(path)
def __del__(self):
del self.model
del self.tokenizer
import gc
gc.collect()
torch.cuda.empty_cache()

View File

@@ -0,0 +1,38 @@
import torch
from .AiBase import AiBase
class QwenAi(AiBase):
default_device = 'cuda' # the device to load the model onto
default_model_id = 'Qwen/Qwen1.5-1.8B-Chat'
default_instruction = {'role': 'system',
'name': 'system',
'content': 'Your name is "Laura". You are an AI created by Alice.'}
def __init__(self, model_id_or_path=default_model_id):
super().__init__(model_id_or_path)
def generate(self, messages):
try:
# prepare
messages = [m for m in messages if m['role'] != 'system']
input_messages = [self.default_instruction] + messages
# generate
text = self.tokenizer.apply_chat_template(input_messages, tokenize=False, add_generation_prompt=True)
model_inputs = self.tokenizer([text], return_tensors='pt').to(self.default_device)
generated_ids = self.model.generate(model_inputs.input_ids, max_new_tokens=300)
generated_ids = [
output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
response = self.tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
# add response and save conversation
response_entry = {'role': 'assistant', 'name': 'assistant', 'content': response}
messages.append(response_entry)
self.record_conversation(input_messages, response_entry)
return messages
finally:
torch.cuda.empty_cache() # clear cache or the gpu mem will be used a lot

View File

11
backend_old/ai/apps.py Normal file
View File

@@ -0,0 +1,11 @@
from django.apps import AppConfig
class AisConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'ai'
def ready(self):
# noinspection PyUnresolvedReferences
from . import events # register events
pass

View File

@@ -0,0 +1,46 @@
from injector import Injector
from .eventsold import ChatTextEvent
from .websocket import DashboardConnectionManager
class EventListener:
def handle(self, *args, **kwargs):
raise NotImplementedError
class ChatTextListener(EventListener):
def handle(self, event):
print('ChatTextListener')
get_manager().broadcast({'type': 'chat_text', 'message': event.message, 'ai_instance': event.ai_name})
def get_manager():
from .injector import InjectorModule
inj = Injector(InjectorModule)
return inj.get(DashboardConnectionManager)
ChatTextEvent.register(ChatTextListener())
# @injector.inject
# def on_chat_text(manager: DashboardConnectionManager, ai_instance: AiInstance, message):
# manager.broadcast({'type': 'chat_text', 'message': message, 'ai_instance': ai_instance.configuration.name})
#
#
# @injector.inject
# def on_discord_online(manager: DashboardConnectionManager, ai_instance: AiInstance, status: bool):
# manager.broadcast({'type': 'discord_online', 'status': status, 'ai_instance': ai_instance.configuration.name})
#
#
# @injector.inject
# def on_discord_react_to_chat(manager: DashboardConnectionManager, ai_instance: AiInstance, status: bool):
# manager.broadcast({'type': 'discord_react_to_chat',
# 'status': status,
# 'ai_instance': ai_instance.configuration.name})
#
# def register_events():
# Dispatcher.RegisterHandler('event::chat_text', on_chat_text)
# Dispatcher.RegisterHandler('event::discord::online', on_discord_online)
# Dispatcher.RegisterHandler('event::discord::react_to_chat', on_discord_react_to_chat)

30
backend_old/ai/events.py Normal file
View File

@@ -0,0 +1,30 @@
from django.dispatch import receiver, Signal
from injector import Injector
from .websocket import DashboardConnectionManager
chat_text_signal = Signal()
discord_online_signal = Signal()
discord_react_to_chat_signal = Signal()
@receiver(chat_text_signal)
def handle_chat_text(sender, **kwargs):
get_manager().broadcast({'type': 'chat_text', 'message': kwargs['message'], 'ai_instance': kwargs['ai_name']})
@receiver(discord_online_signal)
def handle_discord_online(sender, **kwargs):
get_manager().broadcast({'type': 'discord_online', 'status': kwargs['status'], 'ai_instance': kwargs['ai_name']})
@receiver(discord_react_to_chat_signal)
def handle_discord_react_to_chat(sender, **kwargs):
get_manager().broadcast(
{'type': 'discord_react_to_chat', 'status': kwargs['status'], 'ai_instance': kwargs['ai_name']})
def get_manager():
from .injector import InjectorModule
inj = Injector(InjectorModule)
return inj.get(DashboardConnectionManager)

View File

@@ -0,0 +1,11 @@
from injector import Binder, Module, singleton
from .services.AiService import AiService
from .websocket import DashboardConnectionManager
class InjectorModule(Module):
def configure(self, binder: Binder) -> None:
self.__class__.instance = self
binder.bind(AiService, scope=singleton)
binder.bind(DashboardConnectionManager, scope=singleton)

View File

@@ -0,0 +1,22 @@
# Generated by Django 5.0.4 on 2024-04-22 15:06
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='AiConfig',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('model_id_or_path', models.CharField(max_length=255)),
],
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 5.0.4 on 2024-04-23 17:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ai', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='aiconfig',
name='discord_token',
field=models.CharField(default='', max_length=255),
preserve_default=False,
),
]

View File

11
backend_old/ai/models.py Normal file
View File

@@ -0,0 +1,11 @@
from django.db import models
# Create your models here.
class AiConfig(models.Model):
name = models.CharField(max_length=255)
model_id_or_path = models.CharField(max_length=255)
discord_token = models.CharField(max_length=255)
def __str__(self):
return self.name

View File

@@ -0,0 +1,19 @@
from rest_framework import serializers
from .models import AiConfig
class AiConfigSerializer(serializers.ModelSerializer):
class Meta:
model = AiConfig
fields = ['name', 'model_id_or_path']
class DiscordStatusSerializer(serializers.Serializer):
online = serializers.BooleanField()
react_to_chat = serializers.BooleanField()
class AiInstanceSerializer(serializers.Serializer):
configuration = AiConfigSerializer()
messages = serializers.ListField()
discord = DiscordStatusSerializer()

View File

@@ -0,0 +1,83 @@
import threading
from typing import AnyStr
from .DcClient import DcClient
from ..ais.AiBase import AiBase
from ..ais.QwenAi import QwenAi
from ..events import chat_text_signal, discord_online_signal, discord_react_to_chat_signal
from ..models import AiConfig
class DiscordStatus:
def __init__(self, instance):
self._instance = instance
self._client = DcClient(instance)
self.online = False
self.react_to_chat = False
def set_online(self, online: bool):
if online and not self.online:
self._client.connect()
elif not online and self.online:
self._client.disconnect()
else:
return
self.online = online
discord_online_signal.send(sender=self.__class__, ai_name=self._instance.configuration.name, status=self.online)
def set_react_to_chat(self, react_to_chat: bool):
self.react_to_chat = react_to_chat
discord_react_to_chat_signal.send(sender=self.__class__, ai_name=self._instance.configuration.name,
status=self.react_to_chat)
def __del__(self):
self.set_online(False)
class AiInstance:
def __init__(self, configuration: AiConfig):
print('Initializing AiInstance ' + configuration.name)
self._bot: AiBase | None = None
self._botLock = threading.Lock()
self.configuration = configuration
self.discord = DiscordStatus(self)
self.messages = []
self._messagesLock = threading.Lock()
def endure_loaded(self):
self._botLock.acquire()
try:
if self._bot is None:
self._bot = QwenAi(self.configuration.model_id_or_path)
finally:
self._botLock.release()
def endure_unloaded(self):
self._botLock.acquire()
try:
if self._bot is not None:
del self._bot
self._bot = None
finally:
self._botLock.release()
def chat_text(self, user: AnyStr, text: AnyStr):
self.endure_loaded()
self._messagesLock.acquire()
try:
self.messages.append({'role': 'user', 'name': user, 'content': text})
chat_text_signal.send(sender=self.__class__, ai_name=self.configuration.name, message=self.messages[-1])
self.messages = self._bot.generate(self.messages)
chat_text_signal.send(sender=self.__class__, ai_name=self.configuration.name, message=self.messages[-1])
return self.messages[-1]
finally:
self._messagesLock.release()
def chat_conversation(self, conversation):
self.endure_loaded()
return self._bot.generate(conversation)
def __del__(self):
print('Destructing AiInstance ' + self.configuration.name)
del self._bot
del self.discord

View File

@@ -0,0 +1,34 @@
from .AiInstance import AiInstance
from ..models import AiConfig
class AiService:
# Singleton
# def __new__(cls):
# if not hasattr(cls, 'instance'):
# cls.instance = super(AiService, cls).__new__(cls)
# return cls.instance
def __init__(self):
self.instances: [AiInstance] = []
print('Initializing AiService')
def get_configurations(self) -> [AiConfig]:
return AiConfig.objects.filter()
def get_instances(self) -> [AiInstance]:
self.__ensure_instances_inited__()
return self.instances
def get_instance(self, name: str) -> AiInstance:
return [i for i in self.get_instances() if i.configuration.name == name][0]
def __ensure_instances_inited__(self):
for config in self.get_configurations():
if len([i for i in self.instances if i.configuration.name == config.name]) == 0:
self.instances.append(AiInstance(config))
def __del__(self):
print('Destructing AiService')
for instance in self.instances:
del instance

View File

@@ -0,0 +1,44 @@
from threading import Thread
from discord import Intents, Client
from . import AiInstance
class DcClient:
def __init__(self, _ai_instance: AiInstance):
self._ai_instance = _ai_instance
self._intents = Intents.default()
self._intents.message_content = True
self._discord_client = Client(intents=self._intents)
@self._discord_client.event
async def on_ready():
print(f'We have logged in as {self._discord_client.user}')
@self._discord_client.event
async def on_message(message):
if not self._ai_instance.discord.react_to_chat:
return None
if message.author == self._discord_client.user:
return
if message.content.isspace():
await message.channel.send('### Empty message')
return
response = self._ai_instance.chat_text(message.author.name, message.content)
if response is not None:
await message.channel.send(response['content'])
def connect(self):
def start():
self._discord_client.run(self._ai_instance.configuration.discord_token)
thread = Thread(target=start)
thread.start()
def disconnect(self):
self._discord_client.close()

3
backend_old/ai/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

18
backend_old/ai/urls.py Normal file
View File

@@ -0,0 +1,18 @@
from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import path
from .views import AiConfigApiView, AiApiView, AiDiscordApiView, AiChatTextApiView
from .websocket import DashboardConsumer
urlpatterns = [
path('api/v1/ai/<name>/chat/text', AiChatTextApiView.as_view()),
path('api/v1/ai/<name>/discord', AiDiscordApiView.as_view()),
path('api/v1/ai/config', AiConfigApiView.as_view()),
path('api/v1/ai', AiApiView.as_view()),
]
application = ProtocolTypeRouter({
'websocket': URLRouter([
path('ws/dashboard/', DashboardConsumer.as_asgi()),
])
})

87
backend_old/ai/views.py Normal file
View File

@@ -0,0 +1,87 @@
from injector import inject
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from .serializers import AiConfigSerializer, AiInstanceSerializer
from .services.AiService import AiService
class AiApiView(APIView):
# add permission to check if user is authenticated
# permission_classes = [permissions.IsAuthenticated]
ai_service: AiService = None
@inject
def setup(self, request, ai_service: AiService, **kwargs):
super().setup(request)
self.ai_service = ai_service
def get(self, request, *args, **kwargs):
instances = self.ai_service.get_instances()
serializer = AiInstanceSerializer(instances, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
class AiConfigApiView(APIView):
# add permission to check if user is authenticated
# permission_classes = [permissions.IsAuthenticated]
ai_service: AiService = None
@inject
def setup(self, request, ai_service: AiService, **kwargs):
super().setup(request)
self.ai_service = ai_service
def get(self, request, *args, **kwargs):
configurations = self.ai_service.get_configurations()
serializer = AiConfigSerializer(configurations, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
class AiChatTextApiView(APIView):
# add permission to check if user is authenticated
# permission_classes = [permissions.IsAuthenticated]
ai_service: AiService = None
@inject
def setup(self, request, ai_service: AiService, **kwargs):
super().setup(request)
self.ai_service = ai_service
def post(self, request, *args, **kwargs):
name = self.kwargs.get('name')
print("name", name)
ai_instance = self.ai_service.get_instance(name)
if ai_instance is None:
return Response(status=status.HTTP_404_NOT_FOUND)
print(request.data)
response = ai_instance.chat_text(request.data['name'], request.data['content'])
return Response(response, status=status.HTTP_200_OK)
class AiDiscordApiView(APIView):
# add permission to check if user is authenticated
# permission_classes = [permissions.IsAuthenticated]
ai_service: AiService = None
@inject
def setup(self, request, ai_service: AiService, **kwargs):
super().setup(request)
self.ai_service = ai_service
def patch(self, request, *args, **kwargs):
name = self.kwargs.get('name')
ai_instance = self.ai_service.get_instance(name)
if ai_instance is None:
return Response(status=status.HTTP_404_NOT_FOUND)
if 'online' in request.data:
ai_instance.discord.set_online(request.data['online'])
if 'react_to_chat' in request.data:
ai_instance.discord.set_react_to_chat(request.data['react_to_chat'])
return Response(status=status.HTTP_200_OK)

View File

@@ -0,0 +1,44 @@
import injector
import json
from channels.generic.websocket import WebsocketConsumer
class DashboardConnectionManager:
def __init__(self):
self._clients = []
def register(self, client: WebsocketConsumer):
self._clients.append(client)
pass
def unregister(self, client: WebsocketConsumer):
self._clients.remove(client)
def broadcast(self, message: any):
for client in self._clients:
client.push(message)
class DashboardConsumer(WebsocketConsumer):
@injector.inject
def __init__(self, manager: DashboardConnectionManager, *args, **kwargs):
super().__init__(*args, **kwargs)
self._manager = manager
def connect(self):
self.accept()
self._manager.register(self)
def disconnect(self, close_code):
self._manager.unregister(self)
def receive(self, text_data=None, bytes_data=None):
# text_data_json = json.loads(text_data)
# message = text_data_json['message']
# self.send(text_data=json.dumps({
# 'message': message
# }))
pass # do nothing currently
def push(self, message):
self.send(text_data=json.dumps(message))