add teaching feature

This commit is contained in:
2026-03-08 21:15:46 +08:00
parent 05d46295a0
commit 889380c58e
7 changed files with 281 additions and 1 deletions

View File

@@ -5,6 +5,7 @@ from ..views.oj import (
AIDetailDataAPI,
AIDurationDataAPI,
AIHeatmapDataAPI,
AIHintAPI,
AILoginSummaryAPI,
)
@@ -12,6 +13,7 @@ urlpatterns = [
path("ai/detail", AIDetailDataAPI.as_view()),
path("ai/duration", AIDurationDataAPI.as_view()),
path("ai/analysis", AIAnalysisAPI.as_view()),
path("ai/hint", AIHintAPI.as_view()),
path("ai/heatmap", AIHeatmapDataAPI.as_view()),
path("ai/login_summary", AILoginSummaryAPI.as_view()),
]

View File

@@ -715,6 +715,86 @@ class AIAnalysisAPI(APIView):
return response
class AIHintAPI(APIView):
@login_required
def post(self, request):
submission_id = request.data.get("submission_id")
if not submission_id:
return self.error("submission_id is required")
try:
submission = Submission.objects.get(id=submission_id, user_id=request.user.id)
except Submission.DoesNotExist:
return self.error("Submission not found")
problem = submission.problem
client = get_ai_client()
# 获取参考答案(同语言优先,否则取第一个)
answers = problem.answers or []
ref_answer = next(
(a["code"] for a in answers if a["language"] == submission.language),
answers[0]["code"] if answers else "",
)
system_prompt = (
"你是编程助教。你知道题目的参考答案,但【绝对禁止】把参考答案或其中任何代码"
"直接告诉学生,也不能以任何形式暗示完整解法。"
"你的任务是:对照参考答案,找出学生代码中的问题,"
"给出方向性提示(例如:指出哪类边界情况需要考虑、"
"哪个算法思路更合适、哪行代码逻辑可能有问题等)。"
"语气鼓励回复简洁3-5句话使用 Markdown 格式。"
)
user_prompt = (
f"题目:{problem.title}\n"
f"题目描述:{problem.description[:500]}\n"
f"参考答案(仅供你分析,不可透露给学生):\n```\n{ref_answer[:2000]}\n```\n"
f"学生提交语言:{submission.language}\n"
f"判题结果:{submission.result}\n"
f"错误信息:{submission.statistic_info.get('err_info', '')}\n"
f"学生代码:\n```\n{submission.code[:2000]}\n```"
)
def stream_generator():
try:
stream = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt},
],
stream=True,
)
except Exception as exc:
yield f"data: {json.dumps({'type': 'error', 'message': str(exc)})}\n\n"
yield "event: end\n\n"
return
yield "event: start\n\n"
try:
for chunk in stream:
if not chunk.choices:
continue
choice = chunk.choices[0]
if choice.finish_reason:
yield f"data: {json.dumps({'type': 'done'})}\n\n"
break
content = choice.delta.content
if content:
yield f"data: {json.dumps({'type': 'delta', 'content': content})}\n\n"
except Exception as exc:
yield f"data: {json.dumps({'type': 'error', 'message': str(exc)})}\n\n"
finally:
yield "event: end\n\n"
response = StreamingHttpResponse(
streaming_content=stream_generator(),
content_type="text/event-stream",
)
response["Cache-Control"] = "no-cache"
return response
class AIHeatmapDataAPI(APIView):
@login_required
def get(self, request):