From 7fd8118b1cae81bfde5ee7e71d104f206f9d934a Mon Sep 17 00:00:00 2001 From: kitt Date: Wed, 29 Nov 2023 21:07:50 -0500 Subject: [PATCH] create threads from battles and battle groups --- assistant/data.py | 69 ++++++++++++++++++- ..._content_type_thread_object_id_and_more.py | 29 ++++++++ assistant/models.py | 10 +++ assistant/runner.py | 10 ++- .../templates/assistant/create_thread.html | 2 +- assistant/urls.py | 1 + assistant/views.py | 26 ++++++- battles/templates/battles/groups/view.html | 5 ++ battles/templates/battles/view_battle.html | 20 ++++-- users/models.py | 4 ++ 10 files changed, 163 insertions(+), 13 deletions(-) create mode 100644 assistant/migrations/0005_thread_content_type_thread_object_id_and_more.py diff --git a/assistant/data.py b/assistant/data.py index 205015af..1e20c8e5 100644 --- a/assistant/data.py +++ b/assistant/data.py @@ -5,12 +5,12 @@ from django.db.models import Prefetch from openai import OpenAI -from battles.models import Player, Battle +from battles.models import Player, Battle, BattleGroup client = OpenAI(api_key=settings.OPENAI_API_KEY) -def upload_data_to_openai(user): +def upload_user_battles_to_openai(user): player_query = Player.objects.select_related( "title_adjective__string", "title_subject__string", @@ -59,3 +59,68 @@ def upload_data_to_openai(user): purpose='assistants' ) return openai_file + + +def upload_battle_to_openai(battle: Battle): + battle_array = [json.dumps(battle.to_gpt_dict()) + '\n'] + temp_file = StringIO("") + temp_file.writelines(battle_array) + temp_file.seek(0) + temp_file = BytesIO(temp_file.read().encode('utf-8')) + temp_file.seek(0) + openai_file = client.files.create( + file=temp_file, + purpose='assistants' + ) + return openai_file + + +def upload_battle_group_to_openai(battle_group: BattleGroup): + player_query = Player.objects.select_related( + "title_adjective__string", + "title_subject__string", + "nameplate_background", + "nameplate_badge_1__description", + "nameplate_badge_2__description", + "nameplate_badge_3__description", + "weapon__name", + "weapon__sub__name", + "weapon__special__name", + "head_gear__gear__name", + "head_gear__primary_ability__name", + "head_gear__secondary_ability_1__name", + "head_gear__secondary_ability_2__name", + "head_gear__secondary_ability_3__name", + "clothing_gear__gear__name", + "clothing_gear__primary_ability__name", + "clothing_gear__secondary_ability_1__name", + "clothing_gear__secondary_ability_2__name", + "clothing_gear__secondary_ability_3__name", + "shoes_gear__gear__name", + "shoes_gear__primary_ability__name", + "shoes_gear__secondary_ability_1__name", + "shoes_gear__secondary_ability_2__name", + "shoes_gear__secondary_ability_3__name", + ) + player_prefetch = Prefetch( + 'teams__players', + queryset=player_query, + ) + battles = battle_group.battles.select_related("vs_stage__name").prefetch_related("awards__name", + player_prefetch).order_by( + '-played_time') + battle_array = [] + battle: Battle + for battle in battles: + battle_data = battle.to_gpt_dict() + battle_array.append(json.dumps(battle_data) + '\n') + temp_file = StringIO("") + temp_file.writelines(battle_array) + temp_file.seek(0) + temp_file = BytesIO(temp_file.read().encode('utf-8')) + temp_file.seek(0) + openai_file = client.files.create( + file=temp_file, + purpose='assistants' + ) + return openai_file diff --git a/assistant/migrations/0005_thread_content_type_thread_object_id_and_more.py b/assistant/migrations/0005_thread_content_type_thread_object_id_and_more.py new file mode 100644 index 00000000..798c4b62 --- /dev/null +++ b/assistant/migrations/0005_thread_content_type_thread_object_id_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 4.2.7 on 2023-11-30 01:47 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('assistant', '0004_thread_runner_machine_id'), + ] + + operations = [ + migrations.AddField( + model_name='thread', + name='content_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, + to='contenttypes.contenttype'), + ), + migrations.AddField( + model_name='thread', + name='object_id', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddIndex( + model_name='thread', + index=models.Index(fields=['content_type', 'object_id'], name='assistant_t_content_9f2484_idx'), + ), + ] diff --git a/assistant/models.py b/assistant/models.py index b2c33ed5..64743242 100644 --- a/assistant/models.py +++ b/assistant/models.py @@ -1,3 +1,5 @@ +from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.models import ContentType from django.db import models @@ -15,3 +17,11 @@ class Status(models.TextChoices): initial_message = models.TextField(default='') status = models.CharField(blank=True, choices=Status.choices, max_length=25) runner_machine_id = models.CharField(blank=True, max_length=20) + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, blank=True, null=True) + object_id = models.PositiveIntegerField(blank=True, null=True) + content_object = GenericForeignKey("content_type", "object_id") + + class Meta: + indexes = [ + models.Index(fields=["content_type", "object_id"]), + ] diff --git a/assistant/runner.py b/assistant/runner.py index 3d629c7e..29913a38 100644 --- a/assistant/runner.py +++ b/assistant/runner.py @@ -11,7 +11,8 @@ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'splashcat.settings') django.setup() -from assistant.data import upload_data_to_openai +from battles.models import Battle, BattleGroup +from assistant.data import upload_user_battles_to_openai, upload_battle_to_openai, upload_battle_group_to_openai from assistant.models import Thread client = OpenAI(api_key=settings.OPENAI_API_KEY) @@ -26,7 +27,12 @@ initial_prompt = thread.initial_message -openai_file = upload_data_to_openai(thread.creator) +if thread.content_type.model_class() == Battle: + openai_file = upload_battle_to_openai(thread.content_object) +elif thread.content_type.model_class() == BattleGroup: + openai_file = upload_battle_group_to_openai(thread.content_object) +else: + openai_file = upload_user_battles_to_openai(thread.creator) thread.openai_file_id = openai_file.id thread.status = thread.Status.CREATED diff --git a/assistant/templates/assistant/create_thread.html b/assistant/templates/assistant/create_thread.html index 2fba8bcc..1bac4008 100644 --- a/assistant/templates/assistant/create_thread.html +++ b/assistant/templates/assistant/create_thread.html @@ -5,7 +5,7 @@

{% block title %}Create Thread{% endblock %}

-
+ {% csrf_token %} {{ form.as_div }} diff --git a/assistant/urls.py b/assistant/urls.py index 14618679..2dfb0f4d 100644 --- a/assistant/urls.py +++ b/assistant/urls.py @@ -9,4 +9,5 @@ path('threads//messages/', views.get_thread_messages, name='get_thread_messages'), path('threads//messages/send/', views.send_message_to_thread, name='send_message_to_thread'), path('threads/create/', views.create_thread, name='create_thread'), + path('threads/create////', views.create_thread, name='create_thread'), ] diff --git a/assistant/views.py b/assistant/views.py index afa292b3..399cc45f 100644 --- a/assistant/views.py +++ b/assistant/views.py @@ -3,8 +3,9 @@ import django_htmx.http from django.conf import settings from django.contrib.auth.decorators import login_required +from django.contrib.contenttypes.models import ContentType from django.db import transaction -from django.http import HttpResponseBadRequest +from django.http import HttpResponseBadRequest, HttpResponseNotFound from django.shortcuts import redirect, get_object_or_404, render from django.views.decorators.http import require_POST from openai import OpenAI @@ -12,6 +13,7 @@ from assistant import orchestrator from assistant.forms import CreateThreadForm from assistant.models import Thread +from battles.models import Battle, BattleGroup from users.models import User, SponsorshipTiers client = OpenAI(api_key=settings.OPENAI_API_KEY) @@ -40,7 +42,25 @@ def threads(request): @login_required @require_sponsor_tier @transaction.atomic -def create_thread(request): +def create_thread(request, app_label, model_name, object_id): + found_object: None = None + if app_label: + if not model_name and not object_id: + return HttpResponseNotFound() + content_type = ContentType.objects.get(app_label__iexact=app_label, model__iexact=model_name) + found_object = content_type.get_object_for_this_type(pk=object_id) + + if content_type.model_class() is Battle: + found_object: Battle + if found_object.uploader_id != request.user.id: + return HttpResponseBadRequest() + elif content_type.model_class() is BattleGroup: + found_object: BattleGroup + if found_object.creator_id != request.user.id: + return HttpResponseBadRequest() + else: + return HttpResponseBadRequest() + if request.method == "GET": form = CreateThreadForm() return render(request, "assistant/create_thread.html", { @@ -54,6 +74,8 @@ def create_thread(request): openai_thread = client.beta.threads.create() thread = Thread(creator=request.user, openai_thread_id=openai_thread.id, status=Thread.Status.PENDING, initial_message=form.cleaned_data['initial_message']) + if found_object: + thread.content_object = found_object thread.save() machine_id = orchestrator.schedule_machine(thread) diff --git a/battles/templates/battles/groups/view.html b/battles/templates/battles/groups/view.html index e043530a..333acc06 100644 --- a/battles/templates/battles/groups/view.html +++ b/battles/templates/battles/groups/view.html @@ -42,6 +42,11 @@

+ {% if battle_group.creator_id == user.id and user.has_splashcat_assistant %} + Create Assistant Thread + {% endif %} +

Creator

{% include "users/includes/user-link.html" with user=battle_group.creator show_splashtag=True %} diff --git a/battles/templates/battles/view_battle.html b/battles/templates/battles/view_battle.html index a4accf2c..4149f845 100644 --- a/battles/templates/battles/view_battle.html +++ b/battles/templates/battles/view_battle.html @@ -80,12 +80,20 @@

- {% if battle.gpt_description_generated %} -
- AI Battle Description - {{ battle.gpt_description }} -
- {% endif %} +
+ {% if battle.gpt_description_generated %} +
+ AI Battle Description + {{ battle.gpt_description }} +
+ {% endif %} + {% if battle.uploader_id == user.id and user.has_splashcat_assistant %} + + {% endif %} +
diff --git a/users/models.py b/users/models.py index 853632be..76f5afcb 100644 --- a/users/models.py +++ b/users/models.py @@ -99,6 +99,10 @@ def sponsor_tiers(self) -> dict[SponsorshipTiers, bool]: sponsorship_amount = 0 return {tier: sponsorship_amount >= amount for tier, amount in sponsorship_tier_costs.items()} + @property + def has_splashcat_assistant(self): + return self.sponsor_tiers[SponsorshipTiers.X_PONSOR] + def get_full_name(self): return self.display_name.strip()