import json from django.contrib.auth.decorators import login_required from django.conf import settings from django.http import Http404, JsonResponse from django.views.decorators.http import require_http_methods from review_agent.models import ExportedSummaryFile, RegulatoryInfoPackageBatch, WorkflowNodeRun from review_agent.regulatory_info_package.constants import WORKFLOW_TYPE from review_agent.regulatory_info_package.services.input_select import select_instruction_input from review_agent.regulatory_info_package.workflow import ( create_regulatory_info_package_batch, start_regulatory_info_package_workflow, ) @require_http_methods(["GET"]) def health(request): return JsonResponse({"workflow_type": WORKFLOW_TYPE, "status": "available"}) @login_required @require_http_methods(["POST"]) def start(request): try: payload = json.loads(request.body.decode("utf-8") or "{}") except json.JSONDecodeError: return JsonResponse({"error": "JSON 格式错误。"}, status=400) from review_agent.models import Conversation conversation = Conversation.objects.filter(pk=payload.get("conversation_id"), user=request.user).first() if not conversation: raise Http404("对话不存在。") selection = select_instruction_input(conversation, str(payload.get("message") or "")) if selection.status != "selected": return JsonResponse( {"status": selection.status, "message": selection.message, "candidates": selection.candidates}, status=400, ) batch = create_regulatory_info_package_batch( conversation=conversation, user=request.user, source_attachment=selection.attachment, source_summary_batch=selection.source_summary_batch, source_summary_item_id=selection.source_summary_item_id, source_file_name=selection.file_name, source_storage_path=selection.storage_path, ) start_regulatory_info_package_workflow(batch, async_run=getattr(settings, "REGULATORY_INFO_PACKAGE_ASYNC", True)) return JsonResponse({"batch_id": batch.pk, "workflow_type": WORKFLOW_TYPE, "status": batch.status}) @login_required @require_http_methods(["GET"]) def batch_status(request, batch_id: int): batch = RegulatoryInfoPackageBatch.objects.filter( pk=batch_id, conversation__user=request.user, is_deleted=False, ).first() if not batch: raise Http404("材料包批次不存在。") exports = ExportedSummaryFile.objects.filter( workflow_type=WORKFLOW_TYPE, workflow_batch_id=batch.pk, ).order_by("-export_type", "id") sorted_exports = sorted(exports, key=lambda item: 0 if item.export_type == ExportedSummaryFile.ExportType.ZIP else 1) return JsonResponse( { "batch": { "id": batch.pk, "workflow_type": WORKFLOW_TYPE, "batch_no": batch.batch_no, "status": batch.status, "product_name": batch.product_name, "risk_summary_text": _risk_summary_text(batch), "error_message": batch.error_message, }, "nodes": [ { "node_code": node.node_code, "node_name": node.node_name, "status": node.status, "progress": node.progress, "message": node.message, } for node in WorkflowNodeRun.objects.filter( workflow_type=WORKFLOW_TYPE, workflow_batch_id=batch.pk, ).order_by("id") ], "exports": [ { "id": export.pk, "export_type": export.export_type, "export_category": export.export_category, "file_name": export.file_name, "download_url": f"/api/review-agent/file-summary/exports/{export.pk}/download/", } for export in sorted_exports ], "failed_files": [item for item in batch.generated_files if item.get("status") == "failed"], "notifications": [ { "id": item.pk, "channel": item.channel, "send_status": item.send_status, "status_label": "通知已记录" if item.send_status == "success" else item.send_status, "error_message": item.error_message, } for item in batch.notifications.filter(is_deleted=False).order_by("-created_at", "-id") ], } ) def _risk_summary_text(batch: RegulatoryInfoPackageBatch) -> str: parts = [] if batch.missing_fields: parts.append(f"缺失字段 {len(batch.missing_fields)}") if batch.llm_only_fields: parts.append(f"LLM-only {len(batch.llm_only_fields)}") if batch.conflict_fields: parts.append(f"冲突字段 {len(batch.conflict_fields)}") if batch.risk_notes: parts.append(f"提示 {len(batch.risk_notes)}") return " · ".join(parts)