From 5fdcc31c7484ab7ca63628ba9d75cc608789269f Mon Sep 17 00:00:00 2001 From: bruce Date: Thu, 4 Jun 2026 02:54:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=E5=AE=A1=E6=A0=B8?= =?UTF-8?q?=E6=99=BA=E8=83=BD=E4=BD=93=E9=A1=B5=E9=A3=8E=E9=99=A9=E4=B8=8E?= =?UTF-8?q?=E9=80=9A=E7=9F=A5=E8=83=BD=E5=8A=9B=E5=8D=A1=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/chat/views.py | 53 +++++++++++ templates/chat/index.html | 195 +++++++++++++++++++++++++++----------- tests/test_chat.py | 56 +++++++++++ 3 files changed, 251 insertions(+), 53 deletions(-) diff --git a/apps/chat/views.py b/apps/chat/views.py index 9b0e29d..c9b5c02 100644 --- a/apps/chat/views.py +++ b/apps/chat/views.py @@ -86,6 +86,8 @@ def detail(request, conversation_id: str): conversation.refresh_from_db() workspace_summary = _build_workspace_summary(conversation, batch) export_card = _build_export_card(result, conversation) + risk_card = _build_risk_card(result, conversation) + notify_card = _build_notify_card(result, conversation) return render( request, @@ -105,6 +107,8 @@ def detail(request, conversation_id: str): "workspace_summary": workspace_summary, "upload_form": upload_form, "export_card": export_card, + "risk_card": risk_card, + "notify_card": notify_card, }, ) @@ -265,6 +269,55 @@ def _build_export_card(result: AgentResult | None, conversation: Conversation) - } +def _build_risk_card(result: AgentResult | None, conversation: Conversation) -> dict: + structured_output = {} + if result and result.structured_output: + structured_output = result.structured_output + else: + structured_output = (conversation.latest_summary or {}).get("structured_output") or {} + if structured_output.get("output_type") != "registration_risk_report": + return {} + return { + "summary": structured_output.get("summary", ""), + "highest_risk_level": structured_output.get("highest_risk_level", ""), + "pass_status": structured_output.get("pass_status", ""), + "manual_review_items": structured_output.get("manual_review_items") or [], + "risk_items": structured_output.get("risk_items") or [], + "owner_roles": structured_output.get("owner_roles") or [], + } + + +def _build_notify_card(result: AgentResult | None, conversation: Conversation) -> dict: + latest_summary = conversation.latest_summary or {} + structured_output = latest_summary.get("structured_output") or {} + notification_payload = latest_summary.get("notification_payload") or {} + + if result and result.structured_output: + structured_output = result.structured_output + if result and result.notification_payload: + notification_payload = result.notification_payload + + notify_reason = ( + structured_output.get("notify_reason") + or notification_payload.get("notify_reason") + or "" + ) + mentioned_users = structured_output.get("mentioned_users") or notification_payload.get("mentioned_users") or [] + message_status = structured_output.get("message_status") or notification_payload.get("message_status") or "" + web_detail_url = structured_output.get("web_detail_url") or notification_payload.get("web_detail_url") or "" + owners = structured_output.get("owner_roles") or notification_payload.get("owners") or [] + + if not any([notify_reason, mentioned_users, message_status, web_detail_url, owners]): + return {} + return { + "notify_reason": notify_reason, + "mentioned_users": mentioned_users, + "message_status": message_status, + "web_detail_url": web_detail_url, + "owners": owners, + } + + def _apply_agent_result_to_conversation(conversation: Conversation, result: AgentResult) -> None: conversation.task_status = result.status if result.node_results: diff --git a/templates/chat/index.html b/templates/chat/index.html index d898d43..6dbe968 100644 --- a/templates/chat/index.html +++ b/templates/chat/index.html @@ -88,65 +88,154 @@

节点式结果

- {% if export_card %} + {% if export_card or risk_card or notify_card %}
-
- Word 导出能力卡 -
模板:{{ export_card.template_name }} / {{ export_card.template_version|default:"-" }}
-
当前导出状态:{{ export_card.export_status|default:"-" }}
-
- -
-

回填字段表

-
- - - - - - - - - - - - - {% for item in export_card.filled_fields %} - - - - - - - - - {% empty %} - - {% endfor %} - -
占位符字段名字段值来源回填状态是否必填
{{ item.placeholder }}{{ item.field_name }}{{ item.field_value }}{{ item.source }}{{ item.fill_status }}{{ item.required|yesno:"是,否" }}
当前暂无回填字段。
+ {% if risk_card %} +
+ 风险预警能力卡 +
总风险等级:{{ risk_card.highest_risk_level|default:"-" }}
+
是否通过:{{ risk_card.pass_status|default:"-" }}
+ {% if risk_card.summary %} +
{{ risk_card.summary }}
+ {% endif %}
+ +
+

待复核与风险项

+
    + {% for item in risk_card.manual_review_items %} +
  • {{ item }}
  • + {% empty %} + {% for item in risk_card.risk_items %} +
  • + {{ item.title|default:item.issue }} +
    {{ item.risk_level|default:"-" }}
    +
  • + {% empty %} +
  • 当前无待复核项。
  • + {% endfor %} + {% endfor %} +
+
+ +
+

责任角色

+
+ + + + + + + + + + + + {% for item in risk_card.owner_roles %} + + + + + + + + {% empty %} + + {% endfor %} + +
owner_roleowner_namedepartmentchapter_scoperisk_scope
{{ item.owner_role }}{{ item.owner_name }}{{ item.department }}{{ item.chapter_scope }}{{ item.risk_scope }}
当前无责任角色。
+
+
+ {% endif %} + +
+ {% if export_card %} + Word 导出能力卡 +
模板:{{ export_card.template_name }} / {{ export_card.template_version|default:"-" }}
+
当前导出状态:{{ export_card.export_status|default:"-" }}
+ {% elif notify_card %} + 飞书通知能力卡 +
通知原因:{{ notify_card.notify_reason|default:"-" }}
+
消息状态:{{ notify_card.message_status|default:"-" }}
+ {% endif %}
-
-

拦截项区

-
    - {% for item in export_card.blocked_fields %} -
  • - {{ item.field_name }} -
    拦截原因:{{ item.block_reason }}
    -
    来源:{{ item.risk_source }}
    -
  • - {% empty %} -
  • 当前无拦截项。
  • - {% endfor %} -
-
+ {% if export_card %} +
+

回填字段表

+
+ + + + + + + + + + + + + {% for item in export_card.filled_fields %} + + + + + + + + + {% empty %} + + {% endfor %} + +
占位符字段名字段值来源回填状态是否必填
{{ item.placeholder }}{{ item.field_name }}{{ item.field_value }}{{ item.source }}{{ item.fill_status }}{{ item.required|yesno:"是,否" }}
当前暂无回填字段。
+
+
+ {% endif %} + + {% if export_card %} +
+

拦截项区

+
    + {% for item in export_card.blocked_fields %} +
  • + {{ item.field_name }} +
    拦截原因:{{ item.block_reason }}
    +
    来源:{{ item.risk_source }}
    +
  • + {% empty %} +
  • 当前无拦截项。
  • + {% endfor %} +
+
+ {% endif %} + + {% if notify_card %} +
+

飞书通知能力卡

+
    +
  • 通知原因
    {{ notify_card.notify_reason|default:"-" }}
  • +
  • 消息状态
    {{ notify_card.message_status|default:"-" }}
  • +
  • 被 @ 处理人
    {{ notify_card.mentioned_users|join:" / "|default:"-" }}
  • +
  • Web 详情链接
    {{ notify_card.web_detail_url|default:"-" }}
  • +
+
+ {% endif %}
- 维护 Word 模板 - 维护字段映射 - 查看导出记录 + {% if export_card %} + 维护 Word 模板 + 维护字段映射 + 查看导出记录 + {% endif %} + {% if risk_card %} + 查看责任人映射 + {% endif %} + {% if notify_card %} + 查看飞书配置 + {% endif %}
{% elif result and result.structured_output %} diff --git a/tests/test_chat.py b/tests/test_chat.py index 61888c9..ca072c9 100644 --- a/tests/test_chat.py +++ b/tests/test_chat.py @@ -487,6 +487,62 @@ def test_chat_page_shows_word_export_field_table_and_governance_entries(client, assert "维护字段映射" in content +def test_chat_page_shows_risk_and_notification_cards_from_conversation_summary(client, db): + batch, conversation = _create_conversation_with_batch() + conversation.node_results = [ + {"label": "资料包导入", "status": "已完成"}, + {"label": "目录汇总", "status": "已完成"}, + {"label": "法规完整性检查", "status": "已完成"}, + {"label": "字段抽取", "status": "已完成"}, + {"label": "一致性核查", "status": "待复核"}, + {"label": "风险预警", "status": "已阻断"}, + {"label": "Word 回填导出", "status": "待复核"}, + {"label": "飞书通知", "status": "已完成"}, + ] + conversation.latest_summary = { + "structured_output": { + "output_type": "registration_risk_report", + "summary": "存在高风险项,需人工复核。", + "highest_risk_level": "high", + "pass_status": "blocked", + "manual_review_items": ["CH1.11.5 沟通记录缺失"], + "risk_items": [{"title": "产品名称跨文档不一致", "risk_level": "high"}], + "owner_roles": [ + { + "owner_role": "注册资料负责人", + "owner_name": "张三", + "department": "注册事务部", + "chapter_scope": "CH1", + "risk_scope": "字段冲突", + "feishu_user_id": "ou_demo_1", + "feishu_open_id": "on_demo_1", + "feishu_name": "张三", + "notify_enabled": True, + } + ], + "notify_reason": "task_completed", + "mentioned_users": ["ou_demo_1"], + "message_status": "sent", + "web_detail_url": "https://example.com/audit/1", + } + } + conversation.save(update_fields=["node_results", "latest_summary", "updated_at"]) + + response = client.get(reverse("chat:detail", args=[conversation.conversation_id])) + + content = response.content.decode("utf-8") + assert response.status_code == 200 + assert "风险预警能力卡" in content + assert "总风险等级" in content + assert "high" in content + assert "注册资料负责人" in content + assert "CH1.11.5 沟通记录缺失" in content + assert "飞书通知能力卡" in content + assert "task_completed" in content + assert "sent" in content + assert "ou_demo_1" in content + + def test_chat_upload_keeps_existing_conversation_binding_and_adds_documents(client, db): batch, conversation = _create_conversation_with_batch() existing_document = UploadedDocument.objects.create(