diff --git a/review_agent/regulatory_review/views.py b/review_agent/regulatory_review/views.py index 86c8dcd..b244421 100644 --- a/review_agent/regulatory_review/views.py +++ b/review_agent/regulatory_review/views.py @@ -23,8 +23,7 @@ def batch_status(request, batch_id: int): workflow_type="regulatory_review", workflow_batch_id=batch.pk, ).order_by("id") - return JsonResponse( - { + payload = { "batch": { "id": batch.pk, "workflow_type": "regulatory_review", @@ -46,7 +45,14 @@ def batch_status(request, batch_id: int): for node in nodes ], } - ) + if batch.status == RegulatoryReviewBatch.Status.WAITING_USER and (batch.condition_json or {}).get("candidates"): + payload["condition_confirmation"] = { + "batch_id": batch.pk, + "batch_no": batch.batch_no, + "confirm_url": f"/api/review-agent/regulatory-review/{batch.pk}/conditions/", + "candidates": batch.condition_json["candidates"], + } + return JsonResponse(payload) @require_http_methods(["POST"]) diff --git a/static/js/app.js b/static/js/app.js index 0717538..67a1478 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -644,6 +644,74 @@ selectWorkflowBatchIndex(activeIndex); } + function ensureConditionConfirmationCard(confirmation) { + if (!chatScroll || !confirmation || !confirmation.candidates) { + return; + } + var cardId = "condition-confirmation-" + confirmation.batch_id; + if (document.getElementById(cardId)) { + return; + } + var article = document.createElement("article"); + article.className = "message assistant"; + article.id = cardId; + article.setAttribute("data-node-label", "AI 适用条件确认"); + + var avatar = document.createElement("div"); + avatar.className = "message-avatar"; + avatar.textContent = "AI"; + + var bubble = document.createElement("div"); + bubble.className = "message-bubble"; + var form = document.createElement("form"); + form.className = "condition-confirm-form"; + form.setAttribute("data-condition-confirm-form", ""); + form.setAttribute("data-batch-id", confirmation.batch_id); + form.setAttribute("data-confirm-url", confirmation.confirm_url); + form.innerHTML = + '' + + "适用条件确认" + + "

请确认 " + + escapeHtml(confirmation.batch_no || "") + + " 的产品类别、注册类型和临床评价路径,确认后我会继续法规核查。

" + + renderConditionFields(confirmation.candidates) + + '' + + '

'; + bubble.appendChild(form); + article.appendChild(avatar); + article.appendChild(bubble); + chatScroll.appendChild(article); + bindConditionConfirmForms(); + scrollChatToBottom(); + } + + function renderConditionFields(candidates) { + var html = ""; + Object.keys(candidates || {}).forEach(function (field) { + var config = candidates[field] || {}; + html += ""; + }); + return html; + } + async function refreshWorkflowCard(batchId, workflow_type) { if (!summaryPanel || !batchId) { return ""; @@ -662,6 +730,9 @@ return ""; } var payload = await response.json(); + if (payload.condition_confirmation) { + ensureConditionConfirmationCard(payload.condition_confirmation); + } var card = ensureWorkflowCard({ batch_id: payload.batch.id, batch_no: payload.batch.batch_no, diff --git a/tests/test_regulatory_frontend.py b/tests/test_regulatory_frontend.py index 188dc34..c786a6e 100644 --- a/tests/test_regulatory_frontend.py +++ b/tests/test_regulatory_frontend.py @@ -159,5 +159,7 @@ def test_frontend_selects_status_url_by_workflow_type(): assert "statusUrlForWorkflow" in script assert "bindConditionConfirmForms" in script assert "data-condition-confirm-form" in script + assert "ensureConditionConfirmationCard" in script + assert "condition_confirmation" in script assert "bindRectificationActionButtons" in script assert "data-rectification-action" in script diff --git a/tests/test_regulatory_views.py b/tests/test_regulatory_views.py index 198f9a6..3636f39 100644 --- a/tests/test_regulatory_views.py +++ b/tests/test_regulatory_views.py @@ -43,3 +43,40 @@ def test_regulatory_batch_status_requires_owner(client, django_user_model): assert payload["batch"]["workflow_type"] == "regulatory_review" assert payload["batch"]["batch_no"] == "RR-STATUS" assert payload["nodes"][0]["node_code"] == "prepare" + + +def test_regulatory_batch_status_exposes_condition_confirmation(client, django_user_model): + owner = django_user_model.objects.create_user(username="owner", password="pass") + conversation = Conversation.objects.create(user=owner, title="会话") + summary = FileSummaryBatch.objects.create( + conversation=conversation, + user=owner, + batch_no="FS-OK", + status=FileSummaryBatch.Status.SUCCESS, + ) + batch = RegulatoryReviewBatch.objects.create( + conversation=conversation, + user=owner, + source_summary_batch=summary, + batch_no="RR-WAIT", + status=RegulatoryReviewBatch.Status.WAITING_USER, + condition_json={ + "confirmed": False, + "candidates": { + "product_category": { + "label": "产品类别", + "input_type": "select", + "options": ["体外诊断试剂", "医疗器械", "其他"], + "suggested": "体外诊断试剂", + } + }, + }, + ) + client.force_login(owner) + + response = client.get(reverse("regulatory_review_batch_status", args=[batch.pk])) + + payload = response.json() + assert payload["batch"]["status"] == RegulatoryReviewBatch.Status.WAITING_USER + assert payload["condition_confirmation"]["batch_id"] == batch.pk + assert payload["condition_confirmation"]["candidates"]["product_category"]["suggested"] == "体外诊断试剂"