feat: 打通通知回链与历史节点回看
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
from django.shortcuts import get_object_or_404, render
|
from django.shortcuts import get_object_or_404, render
|
||||||
|
|
||||||
from .models import AgentAuditLog, NotificationRecord
|
from .models import AgentAuditLog, NotificationRecord
|
||||||
|
from apps.chat.models import Conversation
|
||||||
|
|
||||||
|
|
||||||
def log_list(request):
|
def log_list(request):
|
||||||
@@ -31,8 +32,9 @@ def log_detail(request, log_id: int):
|
|||||||
conversation_id=audit_log.conversation_id,
|
conversation_id=audit_log.conversation_id,
|
||||||
batch_id=audit_log.batch_id,
|
batch_id=audit_log.batch_id,
|
||||||
)
|
)
|
||||||
|
conversation = Conversation.objects.filter(conversation_id=audit_log.conversation_id).first()
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"audit/log_detail.html",
|
"audit/log_detail.html",
|
||||||
{"log": audit_log, "notifications": notifications},
|
{"log": audit_log, "notifications": notifications, "conversation": conversation},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
from agent_core.orchestrator import run_agent
|
from agent_core.orchestrator import run_agent
|
||||||
from agent_core.results import AgentResult
|
from agent_core.results import AgentResult
|
||||||
@@ -72,7 +73,10 @@ def detail(request, conversation_id: str):
|
|||||||
product_name=conversation.product_name,
|
product_name=conversation.product_name,
|
||||||
)
|
)
|
||||||
_apply_agent_result_to_conversation(conversation, result)
|
_apply_agent_result_to_conversation(conversation, result)
|
||||||
_persist_notification_records(result)
|
_persist_notification_records(
|
||||||
|
result,
|
||||||
|
web_detail_url=reverse("audit:detail", args=[audit_log.id]),
|
||||||
|
)
|
||||||
active_node = "risk"
|
active_node = "risk"
|
||||||
conversation.refresh_from_db()
|
conversation.refresh_from_db()
|
||||||
workspace_summary = _build_workspace_summary(conversation, batch)
|
workspace_summary = _build_workspace_summary(conversation, batch)
|
||||||
@@ -97,7 +101,7 @@ def detail(request, conversation_id: str):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _persist_notification_records(result: AgentResult) -> None:
|
def _persist_notification_records(result: AgentResult, *, web_detail_url: str = "") -> None:
|
||||||
payload = result.notification_payload or {}
|
payload = result.notification_payload or {}
|
||||||
owners = payload.get("owners") or []
|
owners = payload.get("owners") or []
|
||||||
if not owners:
|
if not owners:
|
||||||
@@ -112,7 +116,7 @@ def _persist_notification_records(result: AgentResult) -> None:
|
|||||||
owner_role=owner.get("owner_role", ""),
|
owner_role=owner.get("owner_role", ""),
|
||||||
feishu_user_id=owner.get("feishu_user_id", ""),
|
feishu_user_id=owner.get("feishu_user_id", ""),
|
||||||
message_status="sent" if result.status == "success" else "failed",
|
message_status="sent" if result.status == "success" else "failed",
|
||||||
web_detail_url="",
|
web_detail_url=web_detail_url,
|
||||||
receipt={"status": result.status},
|
receipt={"status": result.status},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,24 @@
|
|||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="panel">
|
||||||
|
<h2 class="section-title">会话节点结果</h2>
|
||||||
|
<ul class="detail-list">
|
||||||
|
{% if conversation and conversation.node_results %}
|
||||||
|
{% for node in conversation.node_results %}
|
||||||
|
<li class="detail-item">
|
||||||
|
<strong>{{ node.label }} / {{ node.status }}</strong>
|
||||||
|
{% if node.summary %}
|
||||||
|
<div>{{ node.summary }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<li class="detail-item">当前会话暂无节点结果。</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="panel">
|
<section class="panel">
|
||||||
<h2 class="section-title">通知留痕</h2>
|
<h2 class="section-title">通知留痕</h2>
|
||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from django.urls import reverse
|
|||||||
from agent_core.results import AgentResult
|
from agent_core.results import AgentResult
|
||||||
from apps.audit.models import AgentAuditLog, DemoBusinessRecord, NotificationRecord
|
from apps.audit.models import AgentAuditLog, DemoBusinessRecord, NotificationRecord
|
||||||
from apps.audit.services import create_audit_log, create_notification_record
|
from apps.audit.services import create_audit_log, create_notification_record
|
||||||
|
from apps.chat.models import Conversation
|
||||||
from agent_core.tools.builtin_tools import query_demo_records
|
from agent_core.tools.builtin_tools import query_demo_records
|
||||||
|
|
||||||
|
|
||||||
@@ -194,3 +195,35 @@ def test_audit_list_supports_batch_and_product_filters(client, db):
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert "产品A" in content
|
assert "产品A" in content
|
||||||
assert "产品B" not in content
|
assert "产品B" not in content
|
||||||
|
|
||||||
|
|
||||||
|
def test_audit_detail_page_shows_conversation_node_results(client, db):
|
||||||
|
Conversation.objects.create(
|
||||||
|
conversation_id="conv-001",
|
||||||
|
title="产品A",
|
||||||
|
product_name="产品A",
|
||||||
|
batch_id="SUB-20260604-001",
|
||||||
|
task_status="failed",
|
||||||
|
node_results=[
|
||||||
|
{"label": "资料包导入", "status": "已完成"},
|
||||||
|
{"label": "风险预警", "status": "已阻断"},
|
||||||
|
{"label": "飞书通知", "status": "失败"},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
log = create_audit_log(
|
||||||
|
"document_review",
|
||||||
|
"注册审核智能体",
|
||||||
|
"问题一",
|
||||||
|
AgentResult(answer="回答一", status="failed"),
|
||||||
|
batch_id="SUB-20260604-001",
|
||||||
|
conversation_id="conv-001",
|
||||||
|
product_name="产品A",
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.get(reverse("audit:detail", args=[log.id]))
|
||||||
|
|
||||||
|
content = response.content.decode("utf-8")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert "会话节点结果" in content
|
||||||
|
assert "风险预警 / 已阻断" in content
|
||||||
|
assert "飞书通知 / 失败" in content
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ def test_chat_execution_creates_notification_record_from_agent_result(client, db
|
|||||||
record = NotificationRecord.objects.get()
|
record = NotificationRecord.objects.get()
|
||||||
assert record.notify_reason == "task_completed"
|
assert record.notify_reason == "task_completed"
|
||||||
assert record.batch_id == batch.batch_id
|
assert record.batch_id == batch.batch_id
|
||||||
|
assert record.web_detail_url.endswith(f"/audit/{AgentAuditLog.objects.get().id}/")
|
||||||
|
|
||||||
|
|
||||||
def test_chat_execution_creates_failed_notification_record_and_updates_conversation(client, db, monkeypatch):
|
def test_chat_execution_creates_failed_notification_record_and_updates_conversation(client, db, monkeypatch):
|
||||||
@@ -220,6 +221,7 @@ def test_chat_execution_creates_failed_notification_record_and_updates_conversat
|
|||||||
conversation.refresh_from_db()
|
conversation.refresh_from_db()
|
||||||
assert record.notify_reason == "task_failed"
|
assert record.notify_reason == "task_failed"
|
||||||
assert record.message_status == "failed"
|
assert record.message_status == "failed"
|
||||||
|
assert record.web_detail_url.endswith(f"/audit/{AgentAuditLog.objects.get().id}/")
|
||||||
assert conversation.task_status == "failed"
|
assert conversation.task_status == "failed"
|
||||||
assert conversation.node_results[-1]["label"] == "飞书通知"
|
assert conversation.node_results[-1]["label"] == "飞书通知"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user