mirror of
https://github.com/spbleadersofdigtal/backend.git
synced 2024-11-24 03:53:44 +03:00
update openai, added qr and more
This commit is contained in:
parent
bc33ac8bad
commit
367ef3be53
|
@ -16,10 +16,11 @@
|
||||||
"0.0.0.0",
|
"0.0.0.0",
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
"192.168.83.181",
|
"192.168.83.181",
|
||||||
|
"192.168.22.4",
|
||||||
"ed68-77-234-219-9.ngrok-free.app",
|
"ed68-77-234-219-9.ngrok-free.app",
|
||||||
]
|
]
|
||||||
CORS_ORIGIN_ALLOW_ALL = True
|
CORS_ORIGIN_ALLOW_ALL = True
|
||||||
CSRF_TRUSTED_ORIGINS = ["https://*.ngrok-free.app"]
|
CSRF_TRUSTED_ORIGINS = ["https://*.ngrok-free.app", "http://192.168.83.181:8000"]
|
||||||
|
|
||||||
# WhiteNoise
|
# WhiteNoise
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import datetime
|
import datetime
|
||||||
|
import re
|
||||||
from ast import literal_eval
|
from ast import literal_eval
|
||||||
|
|
||||||
import openai
|
import openai
|
||||||
|
@ -6,6 +7,8 @@
|
||||||
|
|
||||||
KEY = settings.OPENAI_KEY
|
KEY = settings.OPENAI_KEY
|
||||||
|
|
||||||
|
regex = r"({(\n.+)+\n})"
|
||||||
|
|
||||||
description = """🍀 Что такое Pitch-Deck? Pitch-Deck представляет собой презентацию-тизер проекта/компании для
|
description = """🍀 Что такое Pitch-Deck? Pitch-Deck представляет собой презентацию-тизер проекта/компании для
|
||||||
инвесторов, партнеров, журналистов и других заинтересованных лиц. Цель презентации - привлечение дополнительного
|
инвесторов, партнеров, журналистов и других заинтересованных лиц. Цель презентации - привлечение дополнительного
|
||||||
финансирования (инвестиций). Почему это проблема?
|
финансирования (инвестиций). Почему это проблема?
|
||||||
|
@ -29,7 +32,7 @@
|
||||||
создание Pitch-Deck."""
|
создание Pitch-Deck."""
|
||||||
|
|
||||||
names_prompt = """
|
names_prompt = """
|
||||||
По тексту ответь или предположи ответ на вопросы в следющем формате:
|
По тексту ответь или предположи ответ на вопросы в следующем формате:
|
||||||
{
|
{
|
||||||
"names": "Назови 5 имен проекта с данным описанием через запятую"
|
"names": "Назови 5 имен проекта с данным описанием через запятую"
|
||||||
}
|
}
|
||||||
|
@ -37,7 +40,7 @@
|
||||||
|
|
||||||
prompts = [
|
prompts = [
|
||||||
"""
|
"""
|
||||||
По тексту ответь или предположи ответ на вопросы в следющем формате:
|
По тексту ответь или предположи ответ на вопросы в следующем формате:
|
||||||
{
|
{
|
||||||
'users': 'Кто будет пользоваться продуктом',
|
'users': 'Кто будет пользоваться продуктом',
|
||||||
'problems': 'Какие проблемы решает продукт',
|
'problems': 'Какие проблемы решает продукт',
|
||||||
|
@ -47,24 +50,23 @@
|
||||||
}
|
}
|
||||||
""",
|
""",
|
||||||
"""
|
"""
|
||||||
По тексту ответь или предположи ответ на вопросы в следющем формате:
|
По тексту ответь или предположи ответ на вопросы в следующем формате:
|
||||||
{
|
{
|
||||||
'awards': 'Когда проблема будет решена, какова будет ценность для ваших пользователей',
|
'awards': 'Ценность продукта для пользователей',
|
||||||
'money': 'На чем проект зарабатывает? сколько и за что ему платят клиенты',
|
'money': 'На чем проект зарабатывает? сколько и за что ему платят клиенты',
|
||||||
'aims': Напиши 3 цели: на месяц, на полгода и год, формат: {'1': цель на месяц, '2': цель на полгода, '3': цель на год},
|
'aims': Напиши 3 цели: на месяц, на полгода и год, формат: {'1': цель на месяц, '2': цель на полгода, '3': цель на год},
|
||||||
'investments_sold': 'На что потратить инвестиции под проект',
|
'investments_sold': 'На что потратить инвестиции под проект',
|
||||||
'financial_indicators': 'Напиши финансовые показатели проекта'
|
'financial_indicators': 'Напиши финансовые показатели проекта'
|
||||||
}
|
}
|
||||||
""",
|
""",
|
||||||
"""
|
"""По тексту ответь или предположи ответ на вопросы в следующем формате: { 'achieve': 'Чего добьется команда после
|
||||||
По тексту ответь или предположи ответ на вопросы в следющем формате:
|
освоения инвестиций', 'competitors_strength': 'Сильные стороны конкурентов', 'competitors_low': 'Слабые стороны
|
||||||
{
|
конкурентов', 'advantages': 'Какие могут быть преимущества над конкурентами', 'category': "На каком рынке
|
||||||
|
находится этот проект? Выбери из вариантов: 'Business Software', 'IndustrialTech', 'E-commerce', 'Advertising &
|
||||||
'achieve': 'Чего добьется команда после освоения инвестиций',
|
Marketing', 'Hardware', 'RetailTech', 'ConstructionTech', 'Web3', 'EdTech', 'Business Intelligence',
|
||||||
'competitors_strength': 'Сильные стороны конкурентов',
|
'Cybersecurity', 'HrTech', 'Telecom & Communication', 'Media & Entertainment', 'FinTech', 'MedTech', 'Transport &
|
||||||
'competitors_low': 'Слабые стороны конкурентов',
|
Logistics', 'Gaming', 'FoodTech', 'AI', 'WorkTech', 'Consumer Goods & Services', 'Aero & SpaceTech',
|
||||||
'advantages': 'Какие могут быть преимущества над конкурентами'
|
'Legal & RegTech', 'Travel', 'PropTech', 'Energy', 'GreenTech'" } ,
|
||||||
}
|
|
||||||
""",
|
""",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -100,7 +102,12 @@ def create_hints(description: str, stage: int):
|
||||||
model="gpt-3.5-turbo",
|
model="gpt-3.5-turbo",
|
||||||
messages=[{"role": "user", "content": description + "\n" + prompts[stage]}],
|
messages=[{"role": "user", "content": description + "\n" + prompts[stage]}],
|
||||||
)
|
)
|
||||||
content = literal_eval(chat_completion.choices[0].message.content)
|
str_content = chat_completion.choices[0].message.content
|
||||||
|
print(str_content)
|
||||||
|
filtered_content = list(re.finditer(regex, str_content, re.MULTILINE))[-1].group()
|
||||||
|
if not len(filtered_content):
|
||||||
|
raise ValueError(f"answer doesnt pass validation, {filtered_content}")
|
||||||
|
content = literal_eval(filtered_content)
|
||||||
for assertion_statement in assertions[stage]:
|
for assertion_statement in assertions[stage]:
|
||||||
assert assertion_statement(content)
|
assert assertion_statement(content)
|
||||||
|
|
||||||
|
|
|
@ -203,6 +203,22 @@ def validate(self, data):
|
||||||
raise serializers.ValidationError("Incorrect type")
|
raise serializers.ValidationError("Incorrect type")
|
||||||
if any([type(x) is not str for x in answer.values()]):
|
if any([type(x) is not str for x in answer.values()]):
|
||||||
raise serializers.ValidationError("Incorrect type")
|
raise serializers.ValidationError("Incorrect type")
|
||||||
|
case "multiple_date_description":
|
||||||
|
if type(answer) is not dict:
|
||||||
|
raise serializers.ValidationError("Incorrect type")
|
||||||
|
if not (params["min"] <= len(answer) <= params["max"]):
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
"number of dates is too small or too large"
|
||||||
|
)
|
||||||
|
|
||||||
|
for date in answer.keys():
|
||||||
|
try:
|
||||||
|
parse(date)
|
||||||
|
except ValueError:
|
||||||
|
raise serializers.ValidationError("Incorrect date type")
|
||||||
|
for val in answer.values():
|
||||||
|
if type(val) is not str:
|
||||||
|
raise serializers.ValidationError("Incorrect type")
|
||||||
|
|
||||||
data["question_id"] = question.id
|
data["question_id"] = question.id
|
||||||
data["deck_id"] = deck.id
|
data["deck_id"] = deck.id
|
||||||
|
|
|
@ -32,6 +32,10 @@ class QuestionType(models.TextChoices):
|
||||||
select = "select"
|
select = "select"
|
||||||
link = "link"
|
link = "link"
|
||||||
date = "date"
|
date = "date"
|
||||||
|
multiple_date_description = (
|
||||||
|
"multiple_date_description",
|
||||||
|
"multiple date description",
|
||||||
|
)
|
||||||
photo = "photo"
|
photo = "photo"
|
||||||
multiple_photo = "multiple_photo", "multiple photo"
|
multiple_photo = "multiple_photo", "multiple photo"
|
||||||
photo_description = "photo_description", "photo description"
|
photo_description = "photo_description", "photo description"
|
||||||
|
|
|
@ -1,21 +1,40 @@
|
||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save, pre_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
|
||||||
from pitch_deck_generator.decks.models import PitchDeck, QuestionAnswer
|
from pitch_deck_generator.decks.models import PitchDeck, QuestionAnswer
|
||||||
from pitch_deck_generator.decks.tasks import (
|
from pitch_deck_generator.decks.tasks import (
|
||||||
generate_numeric_values,
|
generate_numeric_values,
|
||||||
|
qenerate_answer_qr,
|
||||||
run_pitch_deck_calculation,
|
run_pitch_deck_calculation,
|
||||||
|
save_answer_to_deck,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=PitchDeck)
|
@receiver(post_save, sender=PitchDeck)
|
||||||
def tag_create(sender, instance: PitchDeck, created, **kwargs):
|
def pitch_deck_create(sender, instance: PitchDeck, created, **kwargs):
|
||||||
if created:
|
if created:
|
||||||
run_pitch_deck_calculation.apply_async(kwargs={"pk": instance.pk})
|
run_pitch_deck_calculation.apply_async(kwargs={"pk": instance.pk}, delay=1)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=QuestionAnswer)
|
@receiver(post_save, sender=QuestionAnswer)
|
||||||
def question_numeric_run(sender, instance: QuestionAnswer, created, **kwargs):
|
def question_answer_create(sender, instance: QuestionAnswer, created, **kwargs):
|
||||||
if created:
|
if created:
|
||||||
if instance.question.inner_tag == "category":
|
if instance.question.inner_tag == "category":
|
||||||
generate_numeric_values.apply_async(kwargs={"pk": instance.deck.pk})
|
generate_numeric_values.apply_async(
|
||||||
|
kwargs={"pk": instance.deck.pk}, countdown=1
|
||||||
|
)
|
||||||
|
elif instance.question.inner_tag in ["finance_model"]:
|
||||||
|
qenerate_answer_qr.apply_async(kwargs={"pk": instance.pk}, countdown=1)
|
||||||
|
save_answer_to_deck.apply_async(kwargs={"pk": instance.pk}, countdown=5)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(pre_save, sender=QuestionAnswer)
|
||||||
|
def question_answer_update(sender, instance: QuestionAnswer, created, **kwargs):
|
||||||
|
if instance.id:
|
||||||
|
if instance.question.inner_tag == "category":
|
||||||
|
generate_numeric_values.apply_async(
|
||||||
|
kwargs={"pk": instance.deck.pk}, countdown=1
|
||||||
|
)
|
||||||
|
elif instance.question.inner_tag in ["finance_model"]:
|
||||||
|
qenerate_answer_qr.apply_async(kwargs={"pk": instance.pk}, countdown=1)
|
||||||
|
save_answer_to_deck.apply_async(kwargs={"pk": instance.pk}, countdown=5)
|
||||||
|
|
|
@ -1,14 +1,21 @@
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
import qrcode
|
||||||
import requests
|
import requests
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
from django.core.files import File
|
||||||
|
|
||||||
from ml.openai_handle import create_hints, create_name_hint
|
from ml.openai_handle import create_hints, create_name_hint
|
||||||
from pitch_deck_generator.decks.models import (
|
from pitch_deck_generator.decks.models import (
|
||||||
PitchDeck,
|
PitchDeck,
|
||||||
Question,
|
Question,
|
||||||
QuestionAnswer,
|
QuestionAnswer,
|
||||||
|
QuestionAnswerPhoto,
|
||||||
QuestionDeckHint,
|
QuestionDeckHint,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ML_HOST = "https://short-peaches-speak.loca.lt/"
|
||||||
|
|
||||||
data_types = {
|
data_types = {
|
||||||
"names": ("text", 1),
|
"names": ("text", 1),
|
||||||
"type": ("select", 13),
|
"type": ("select", 13),
|
||||||
|
@ -25,7 +32,7 @@
|
||||||
"money": ("text", 11),
|
"money": ("text", 11),
|
||||||
"financial_indicators": ("text", 33),
|
"financial_indicators": ("text", 33),
|
||||||
"users_metrics": ("multiple_range", 12),
|
"users_metrics": ("multiple_range", 12),
|
||||||
"aims": ("text", 15),
|
"aims": ("multiple_date_description", 15),
|
||||||
"money_recieved": ("number", 16),
|
"money_recieved": ("number", 16),
|
||||||
"past_investors": ("text", 17),
|
"past_investors": ("text", 17),
|
||||||
"how_much_investments": ("range", 18),
|
"how_much_investments": ("range", 18),
|
||||||
|
@ -51,7 +58,7 @@ def run_pitch_deck_calculation(pk: int):
|
||||||
generate_pitch_deck_name.apply_async(kwargs={"pk": pk})
|
generate_pitch_deck_name.apply_async(kwargs={"pk": pk})
|
||||||
generate_known_values.apply_async(kwargs={"pk": pk})
|
generate_known_values.apply_async(kwargs={"pk": pk})
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
generate_batch_hints.apply_async(kwargs={"pk": pk, "num": i})
|
generate_batch_hints.apply_async(kwargs={"pk": pk, "num": i}, delay=1)
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
|
@ -69,47 +76,8 @@ def generate_pitch_deck_name(pk: int):
|
||||||
@shared_task
|
@shared_task
|
||||||
def generate_known_values(pk: int):
|
def generate_known_values(pk: int):
|
||||||
pitch_deck = PitchDeck.objects.get(pk=pk)
|
pitch_deck = PitchDeck.objects.get(pk=pk)
|
||||||
_, question_id = data_types["category"]
|
|
||||||
QuestionDeckHint.objects.create(
|
|
||||||
question_id=question_id,
|
|
||||||
deck=pitch_deck,
|
|
||||||
hint={
|
|
||||||
"type": "select",
|
|
||||||
"value": [
|
|
||||||
"Business Software",
|
|
||||||
"IndustrialTech",
|
|
||||||
"E-commerce",
|
|
||||||
"Advertising & Marketing",
|
|
||||||
"Hardware",
|
|
||||||
"RetailTech",
|
|
||||||
"ConstructionTech",
|
|
||||||
"Web3",
|
|
||||||
"EdTech",
|
|
||||||
"Business Intelligence",
|
|
||||||
"Cybersecurity",
|
|
||||||
"HrTech",
|
|
||||||
"Telecom & Communication",
|
|
||||||
"Media & Entertainment",
|
|
||||||
"FinTech",
|
|
||||||
"MedTech",
|
|
||||||
"Transport & Logistics",
|
|
||||||
"Gaming",
|
|
||||||
"FoodTech",
|
|
||||||
"AI",
|
|
||||||
"WorkTech",
|
|
||||||
"Consumer Goods & Services",
|
|
||||||
"Aero & SpaceTech",
|
|
||||||
"Legal & RegTech",
|
|
||||||
"Travel",
|
|
||||||
"PropTech",
|
|
||||||
"Energy",
|
|
||||||
"GreenTech",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
req = requests.post(
|
req = requests.post(
|
||||||
"https://rare-needles-lead.loca.lt/search",
|
ML_HOST + "search",
|
||||||
json={"body": pitch_deck.description},
|
json={"body": pitch_deck.description},
|
||||||
)
|
)
|
||||||
data = req.json()
|
data = req.json()
|
||||||
|
@ -122,14 +90,28 @@ def generate_known_values(pk: int):
|
||||||
@shared_task
|
@shared_task
|
||||||
def generate_batch_hints(pk: int, num: int):
|
def generate_batch_hints(pk: int, num: int):
|
||||||
pitch_deck = PitchDeck.objects.get(pk=pk)
|
pitch_deck = PitchDeck.objects.get(pk=pk)
|
||||||
data = create_hints(pitch_deck.description, num)
|
try:
|
||||||
|
data = create_hints(pitch_deck.description, num)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
data = create_hints(pitch_deck.description, num)
|
||||||
for el in data:
|
for el in data:
|
||||||
question_type, question_id = data_types[el["type"]]
|
question_type, question_id = data_types[el["type"]]
|
||||||
QuestionDeckHint.objects.create(
|
if el["type"] == "aims":
|
||||||
question_id=question_id,
|
dates = {}
|
||||||
deck=pitch_deck,
|
for e in el["value"]:
|
||||||
hint={"type": question_type, "value": el["value"]},
|
dates[e["date"]] = e["aim"]
|
||||||
)
|
QuestionDeckHint.objects.create(
|
||||||
|
question_id=question_id,
|
||||||
|
deck=pitch_deck,
|
||||||
|
hint={"type": question_type, "value": dates},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
QuestionDeckHint.objects.create(
|
||||||
|
question_id=question_id,
|
||||||
|
deck=pitch_deck,
|
||||||
|
hint={"type": question_type, "value": el["value"]},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
|
@ -144,7 +126,7 @@ def generate_numeric_values(pk: int):
|
||||||
category = q.first().answer
|
category = q.first().answer
|
||||||
type = q2.first().answer
|
type = q2.first().answer
|
||||||
req = requests.post(
|
req = requests.post(
|
||||||
"https://rare-needles-lead.loca.lt/numeric",
|
ML_HOST + "numeric",
|
||||||
json={
|
json={
|
||||||
"description": pitch_deck.description,
|
"description": pitch_deck.description,
|
||||||
"category": category,
|
"category": category,
|
||||||
|
@ -159,3 +141,28 @@ def generate_numeric_values(pk: int):
|
||||||
deck=pitch_deck,
|
deck=pitch_deck,
|
||||||
hint={"type": question_type, "value": el["value"]},
|
hint={"type": question_type, "value": el["value"]},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def save_answer_to_deck(pk: int):
|
||||||
|
qa = QuestionAnswer.objects.get(pk=pk)
|
||||||
|
question = qa.question
|
||||||
|
deck = qa.deck
|
||||||
|
deck.questions[question.inner_tag] = {
|
||||||
|
"answer": qa.answer,
|
||||||
|
"photos": [x.file.url for x in qa.photos.all()],
|
||||||
|
}
|
||||||
|
deck.save()
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def qenerate_answer_qr(pk: int):
|
||||||
|
qa = QuestionAnswer.objects.get(pk=pk)
|
||||||
|
link = qa.answer
|
||||||
|
img = qrcode.make(link)
|
||||||
|
with tempfile.NamedTemporaryFile() as tmp:
|
||||||
|
img.save(tmp.name)
|
||||||
|
QuestionAnswerPhoto.objects.create(
|
||||||
|
answer=qa,
|
||||||
|
file=File(tmp, name="qr.png"),
|
||||||
|
)
|
||||||
|
|
38
poetry.lock
generated
38
poetry.lock
generated
|
@ -2281,6 +2281,18 @@ files = [
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
pylint = ">=1.7"
|
pylint = ">=1.7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pypng"
|
||||||
|
version = "0.20220715.0"
|
||||||
|
description = "Pure Python library for saving and loading PNG images"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "pypng-0.20220715.0-py3-none-any.whl", hash = "sha256:4a43e969b8f5aaafb2a415536c1a8ec7e341cd6a3f957fd5b5f32a4cfeed902c"},
|
||||||
|
{file = "pypng-0.20220715.0.tar.gz", hash = "sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "7.4.0"
|
version = "7.4.0"
|
||||||
|
@ -2455,6 +2467,30 @@ files = [
|
||||||
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
|
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "qrcode"
|
||||||
|
version = "7.4.2"
|
||||||
|
description = "QR Code image generator"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "qrcode-7.4.2-py3-none-any.whl", hash = "sha256:581dca7a029bcb2deef5d01068e39093e80ef00b4a61098a2182eac59d01643a"},
|
||||||
|
{file = "qrcode-7.4.2.tar.gz", hash = "sha256:9dd969454827e127dbd93696b20747239e6d540e082937c90f14ac95b30f5845"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||||
|
pypng = "*"
|
||||||
|
typing-extensions = "*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
all = ["pillow (>=9.1.0)", "pytest", "pytest-cov", "tox", "zest.releaser[recommended]"]
|
||||||
|
dev = ["pytest", "pytest-cov", "tox"]
|
||||||
|
maintainer = ["zest.releaser[recommended]"]
|
||||||
|
pil = ["pillow (>=9.1.0)"]
|
||||||
|
test = ["coverage", "pytest"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redis"
|
name = "redis"
|
||||||
version = "4.6.0"
|
version = "4.6.0"
|
||||||
|
@ -3298,4 +3334,4 @@ multidict = ">=4.0"
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.11"
|
python-versions = "^3.11"
|
||||||
content-hash = "31ce390d9b50a16455803f012bd6f7eca984fbec0d0e60404edaea07b5523792"
|
content-hash = "e22fcc25641133d1b08cd9bb014e157933077e7a6bd09ce8e4186c844777abf9"
|
||||||
|
|
|
@ -49,6 +49,7 @@ pytest-django = "^4.5.2"
|
||||||
sentry-sdk = "^1.12.0"
|
sentry-sdk = "^1.12.0"
|
||||||
openai = "^0.27.9"
|
openai = "^0.27.9"
|
||||||
isort = "5.11.5"
|
isort = "5.11.5"
|
||||||
|
qrcode = "^7.4.2"
|
||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user