import json import logging from django.db import transaction from rest_framework import status from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from .llm import analyze_requirements from .models import Requirement, Submission logger = logging.getLogger(__name__) def _parse_llm_override(request) -> dict: raw = request.data.get("llm") if not raw: return {} if isinstance(raw, str): try: raw = json.loads(raw) except json.JSONDecodeError: return {} return raw if isinstance(raw, dict) else {} def _validate_llm_override(override: dict) -> str | None: if override.get("mode") != "custom": return None if not (override.get("base_url") or "").strip() or not (override.get("model") or "").strip(): return "custom 模式必须提供 llm.base_url 和 llm.model" return None @api_view(["POST"]) @permission_classes([IsAuthenticated]) @transaction.atomic def create_requirement(request): title = (request.data.get("title") or "").strip() content = (request.data.get("content") or "").strip() if not title or not content: return Response({"detail": "title 和 content 不能为空"}, status=status.HTTP_400_BAD_REQUEST) override = _parse_llm_override(request) err = _validate_llm_override(override) if err: return Response({"detail": err}, status=status.HTTP_400_BAD_REQUEST) try: spec = analyze_requirements(content, override) except Exception: logger.exception("AI 分析作业要求失败, 使用默认检测计划") from .llm import _default_spec spec = _default_spec() requirement = Requirement.objects.create( title=title, content=content, spec=spec, llm_config=override, ) from .serializers import RequirementSerializer return Response(RequirementSerializer(requirement).data, status=status.HTTP_201_CREATED) @api_view(["GET"]) @permission_classes([IsAuthenticated]) def list_requirements(request): qs = Requirement.objects.all() q = request.query_params.get("q") if q: qs = qs.filter(title__icontains=q) | qs.filter(content__icontains=q) from .serializers import RequirementSerializer page = int(request.query_params.get("page", 1)) size = int(request.query_params.get("size", 20)) items = qs[(page - 1) * size: page * size] return Response({ "count": qs.count(), "page": page, "items": RequirementSerializer(items, many=True).data, }) @api_view(["GET"]) @permission_classes([IsAuthenticated]) def get_requirement(request, pk): try: requirement = Requirement.objects.get(pk=pk) except Requirement.DoesNotExist: return Response({"detail": "不存在"}, status=status.HTTP_404_NOT_FOUND) from .serializers import RequirementSerializer return Response(RequirementSerializer(requirement).data) @api_view(["POST"]) @permission_classes([IsAuthenticated]) @transaction.atomic def create_submission(request, pk): try: requirement = Requirement.objects.get(pk=pk) except Requirement.DoesNotExist: return Response({"detail": "作业要求不存在"}, status=status.HTTP_404_NOT_FOUND) name = (request.data.get("name") or "").strip() file = request.FILES.get("file") if not file: return Response({"detail": "缺少上传文件 file"}, status=status.HTTP_400_BAD_REQUEST) submission = Submission.objects.create( requirement=requirement, name=name or file.name, file=file, submitted_by=request.user, ) from .tasks import run_submission try: run_submission(submission.id) except Exception: logger.exception("检测任务启动失败") submission.status = "failed" submission.save(update_fields=["status"]) from .serializers import SubmissionSerializer return Response(SubmissionSerializer(submission).data, status=status.HTTP_201_CREATED) @api_view(["GET"]) @permission_classes([IsAuthenticated]) def list_submissions(request): qs = Submission.objects.all() req_id = request.query_params.get("requirement") if req_id: qs = qs.filter(requirement_id=req_id) from .serializers import SubmissionSerializer return Response({"items": SubmissionSerializer(qs, many=True).data}) @api_view(["GET"]) @permission_classes([IsAuthenticated]) def get_submission(request, pk): try: submission = Submission.objects.get(pk=pk) except Submission.DoesNotExist: return Response({"detail": "不存在"}, status=status.HTTP_404_NOT_FOUND) from .serializers import SubmissionSerializer return Response(SubmissionSerializer(submission).data) @api_view(["GET"]) @permission_classes([IsAuthenticated]) def get_report(request, pk): try: submission = Submission.objects.get(pk=pk) except Submission.DoesNotExist: return Response({"detail": "不存在"}, status=status.HTTP_404_NOT_FOUND) if not submission.report: return Response({"detail": "报告尚未生成", "status": submission.status}, status=status.HTTP_202_ACCEPTED) return Response(submission.report)