From 4efd801c540e11d1f20b8ed2e63ff5c0beefbe09 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Wed, 24 Sep 2025 21:01:01 +0800 Subject: [PATCH] add model --- ai/migrations/0001_initial.py | 34 +++++++++++++ ai/models.py | 17 ++++++- ai/views/oj.py | 95 ++++++++++++++++++++++++++++------- oj/dev_settings.py | 6 +-- oj/settings.py | 1 + 5 files changed, 132 insertions(+), 21 deletions(-) create mode 100644 ai/migrations/0001_initial.py diff --git a/ai/migrations/0001_initial.py b/ai/migrations/0001_initial.py new file mode 100644 index 0000000..f823c1e --- /dev/null +++ b/ai/migrations/0001_initial.py @@ -0,0 +1,34 @@ +# Generated by Django 5.2.3 on 2025-09-24 12:59 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='AIAnalysis', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('provider', models.TextField(default='deepseek')), + ('data', models.JSONField()), + ('system_prompt', models.TextField()), + ('user_prompt', models.TextField()), + ('analysis', models.TextField()), + ('create_time', models.DateTimeField(auto_now_add=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'db_table': 'ai_analysis', + 'ordering': ['-create_time'], + }, + ), + ] diff --git a/ai/models.py b/ai/models.py index 71a8362..4b170ca 100644 --- a/ai/models.py +++ b/ai/models.py @@ -1,3 +1,18 @@ from django.db import models -# Create your models here. +from account.models import User + + +class AIAnalysis(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE) + provider = models.TextField(default="deepseek") + model = models.TextField(default="deepseek-chat") + data = models.JSONField() + system_prompt = models.TextField() + user_prompt = models.TextField() + analysis = models.TextField() + create_time = models.DateTimeField(auto_now_add=True) + + class Meta: + db_table = "ai_analysis" + ordering = ["-create_time"] diff --git a/ai/views/oj.py b/ai/views/oj.py index 5535517..a3b32b0 100644 --- a/ai/views/oj.py +++ b/ai/views/oj.py @@ -17,6 +17,8 @@ from account.models import User from problem.models import Problem from submission.models import Submission, JudgeStatus from account.decorators import login_required +from ai.models import AIAnalysis +from textwrap import dedent # 常量定义 @@ -414,30 +416,58 @@ class AIWeeklyDataAPI(APIView): class AIAnalysisAPI(APIView): + @login_required def post(self, request): + user = request.user details = request.data.get("details") weekly = request.data.get("weekly") - API_KEY = get_env("AI_KEY") + api_key = get_env("AI_KEY") - if not API_KEY: + if not api_key: return self.error("API_KEY is not set") - client = OpenAI(api_key=API_KEY, base_url="https://api.deepseek.com") + client = OpenAI(api_key=api_key, base_url="https://api.deepseek.com") - system_prompt = """ - 你是一个风趣的编程老师,学生使用判题狗平台进行编程练习。 - 请根据学生提供的详细数据和每周数据,给出用户的学习建议。 - 请使用 markdown 格式输出,不要在代码块中输出。 - 最后不要忘记写一句祝福语。 - """ + system_prompt =""" + 你是一个风趣的编程老师,学生使用判题狗平台进行编程练习。 + 请根据学生提供的详细数据和每周数据,给出用户的学习建议。 + 请使用 markdown 格式输出,不要在代码块中输出。 + 最后不要忘记写一句祝福语。 + """ user_prompt = f""" - 这段时间内的详细数据: {details} - 每周或每月的数据: {weekly} - """ + 这段时间内的详细数据: {details} + 每周或每月的数据: {weekly} + """ + + analysis_chunks = [] + saved = False + save_error = None + store_error_sent = False + + def try_save(): + nonlocal saved, save_error + if saved: + return + if not analysis_chunks: + saved = True + return + try: + AIAnalysis.objects.create( + user=user, + data={"details": details, "weekly": weekly}, + system_prompt=dedent(system_prompt).strip(), + user_prompt="这段时间内的详细数据, 每周或每月的数据", + analysis="".join(analysis_chunks).strip(), + ) + except Exception as exc: + save_error = str(exc) + finally: + saved = True def stream_generator(): + nonlocal store_error_sent try: stream = client.chat.completions.create( model="deepseek-chat", @@ -470,13 +500,36 @@ class AIAnalysisAPI(APIView): finish_reason = getattr(choice, "finish_reason", None) delta = getattr(choice, "delta", None) - if delta and getattr(delta, "content", None): - payload = json.dumps( - {"type": "delta", "content": delta.content} - ) - yield f"data: {payload}\n\n" + raw_content = getattr(delta, "content", None) + if raw_content: + if isinstance(raw_content, list): + text_content = "".join( + ( + item.get("text") + if isinstance(item, dict) + else getattr(item, "text", None) or "" + ) + for item in raw_content + ) + else: + text_content = str(raw_content) + + if text_content: + analysis_chunks.append(text_content) + payload = json.dumps( + {"type": "delta", "content": text_content} + ) + yield f"data: {payload}\n\n" if finish_reason: + try_save() + if save_error and not store_error_sent: + error_payload = json.dumps( + {"type": "store_error", "message": save_error} + ) + yield f"data: {error_payload}\n\n" + store_error_sent = True + payload = json.dumps({"type": "done"}) yield f"data: {payload}\n\n" break @@ -484,6 +537,14 @@ class AIAnalysisAPI(APIView): payload = json.dumps({"type": "error", "message": str(exc)}) yield f"data: {payload}\n\n" finally: + try_save() + if save_error and not store_error_sent: + error_payload = json.dumps( + {"type": "store_error", "message": save_error} + ) + yield f"data: {error_payload}\n\n" + store_error_sent = True + yield "event: end\n\n" response = StreamingHttpResponse( diff --git a/oj/dev_settings.py b/oj/dev_settings.py index 067efe7..db70d4e 100644 --- a/oj/dev_settings.py +++ b/oj/dev_settings.py @@ -6,8 +6,8 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", - "HOST": "10.13.114.114", - "PORT": "5433", + "HOST": "150.158.29.156", + "PORT": "5432", "NAME": "onlinejudge", "USER": "onlinejudge", "PASSWORD": "onlinejudge", @@ -15,7 +15,7 @@ DATABASES = { } REDIS_CONF = { - "host": "10.13.114.114", + "host": "150.158.29.156", "port": 6379, } diff --git a/oj/settings.py b/oj/settings.py index 48b120b..27fdf8b 100644 --- a/oj/settings.py +++ b/oj/settings.py @@ -55,6 +55,7 @@ LOCAL_APPS = [ "message", "comment", "tutorial", + "ai", ] INSTALLED_APPS = VENDOR_APPS + LOCAL_APPS