From 905067277adeb12ce835b266ebfc04031d0a7ce0 Mon Sep 17 00:00:00 2001 From: bruce Date: Sat, 30 May 2026 00:26:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20=E4=BC=98=E5=8C=96=E5=AF=B9?= =?UTF-8?q?=E8=AF=9D=E4=B8=8E=E7=AE=A1=E7=90=86=E9=A1=B5=E9=9D=A2=E5=B1=95?= =?UTF-8?q?=E7=A4=BA=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/chat/forms.py | 12 +- apps/chat/views.py | 4 + templates/audit/log_detail.html | 91 ++++--- templates/audit/log_list.html | 73 +++--- templates/base.html | 344 +++++++++++++++++++++++++ templates/chat/index.html | 216 ++++++++++++---- templates/documents/document_list.html | 99 +++---- templates/documents/upload.html | 65 +++-- templates/scenarios/index.html | 43 ++-- tests/test_chat.py | 47 ++++ 10 files changed, 776 insertions(+), 218 deletions(-) create mode 100644 templates/base.html diff --git a/apps/chat/forms.py b/apps/chat/forms.py index 90624ac..5f5d286 100644 --- a/apps/chat/forms.py +++ b/apps/chat/forms.py @@ -2,6 +2,8 @@ from django import forms class ChatForm(forms.Form): + # 该表单只负责收集用户问题和可选文档范围, + # 不承载任何 Agent 业务逻辑,便于在 View 层保持轻量。 message = forms.CharField( label="问题", max_length=4000, @@ -9,7 +11,12 @@ class ChatForm(forms.Form): "required": "请输入要咨询的问题。", "max_length": "问题过长,请控制在 4000 字以内。", }, - widget=forms.Textarea(attrs={"rows": 6}), + widget=forms.Textarea( + attrs={ + "rows": 8, + "placeholder": "例如:请结合已上传 SOP,分析当前异常的原因、风险等级和建议动作。", + } + ), ) document_ids = forms.MultipleChoiceField( label="文档范围", @@ -22,9 +29,12 @@ class ChatForm(forms.Form): def __init__(self, *args, documents=None, **kwargs): super().__init__(*args, **kwargs) documents = documents or [] + # 仅允许选择当前场景且已完成入库的文档, + # 避免前端把无效文件范围传入 Agent Core。 self.fields["document_ids"].choices = [ (str(document.id), document.original_name) for document in documents ] def clean_document_ids(self): + # View 与 Agent Core 都使用整型文档 ID,统一在表单层完成转换。 return [int(document_id) for document_id in self.cleaned_data.get("document_ids", [])] diff --git a/apps/chat/views.py b/apps/chat/views.py index 38eeb53..c7af5c2 100644 --- a/apps/chat/views.py +++ b/apps/chat/views.py @@ -10,6 +10,8 @@ from .forms import ChatForm def index(request, scenario_id: str): + # View 只负责请求编排、表单校验和模板渲染。 + # 具体 Agent 执行、审计写入和文档筛选规则分别交给独立模块处理。 try: scenario = get_scenario(scenario_id) except ScenarioNotFound: @@ -34,6 +36,7 @@ def index(request, scenario_id: str): if request.method == "POST" and form.is_valid(): message = form.cleaned_data["message"] try: + # 只把必要的运行选项传给 Agent Core,避免在 View 中散落模型细节。 result = run_agent( scenario, message, @@ -50,6 +53,7 @@ def index(request, scenario_id: str): "scenario": scenario, "form": form, "documents": documents, + "document_count": documents.count(), "result": result, "audit_log": audit_log, }, diff --git a/templates/audit/log_detail.html b/templates/audit/log_detail.html index 7f90f67..fc05fe8 100644 --- a/templates/audit/log_detail.html +++ b/templates/audit/log_detail.html @@ -1,37 +1,56 @@ - - - - - 审计日志详情 - - -

审计日志详情 #{{ log.id }}

- -
-

用户输入

-

{{ log.user_input }}

+{% extends "base.html" %} + +{% block title %}审计日志详情{% endblock %} + +{% block content %} + + +
+
+

基础信息

+
    +
  • 场景:{{ log.scenario_name }}
  • +
  • 状态:{{ log.status }}
  • +
  • 模型:{{ log.model_name }}
  • +
  • 耗时:{{ log.latency_ms }} ms
  • +
+
+ +
+

用户输入

+
{{ log.user_input|linebreaksbr }}
+
+ +
+

最终回答

+
{{ log.final_answer|linebreaksbr }}
+
+ +
+

结构化输出

+
{{ log.structured_output }}
+
+ +
+

引用来源

+
{{ log.retrieved_chunks }}
+
+ +
+

工具调用

+
{{ log.tool_calls }}
+
+ + {% if log.error_message %} +
+

错误信息

+
{{ log.error_message }}
+
+ {% endif %}
-
-

最终回答

-

{{ log.final_answer }}

-
-
-

结构化输出

-
{{ log.structured_output }}
-
-
-

引用来源

-
{{ log.retrieved_chunks }}
-
-
-

工具调用

-
{{ log.tool_calls }}
-
- {% if log.error_message %} -
-

错误信息

-
{{ log.error_message }}
-
- {% endif %} - - +{% endblock %} diff --git a/templates/audit/log_list.html b/templates/audit/log_list.html index 54c0823..94f6ffc 100644 --- a/templates/audit/log_list.html +++ b/templates/audit/log_list.html @@ -1,37 +1,40 @@ - - - - - 审计日志 - - -

审计日志

- - - - - - - - - - - - - - {% for log in logs %} +{% extends "base.html" %} + +{% block title %}审计日志{% endblock %} + +{% block content %} + + +
+
ID场景状态模型耗时详情
+ - - - - - - + + + + + + - {% empty %} - - {% endfor %} - -
{{ log.id }}{{ log.scenario_name }}{{ log.status }}{{ log.model_name }}{{ log.latency_ms }} ms查看ID场景状态模型耗时详情
暂无审计日志。
- - + + + {% for log in logs %} + + {{ log.id }} + {{ log.scenario_name }} + {{ log.status }} + {{ log.model_name }} + {{ log.latency_ms }} ms + 查看详情 + + {% empty %} + 暂无审计日志,先去执行一次对话吧。 + {% endfor %} + + + +{% endblock %} diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..1545237 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,344 @@ + + + + + + {% block title %}Universal Agent Demo Framework{% endblock %} + + + +
+
+
+

Universal Agent Demo Framework

+

面向复试演示的可配置 AI Agent 单体系统

+
+ +
+ {% block content %}{% endblock %} +
+ + diff --git a/templates/chat/index.html b/templates/chat/index.html index d9431bf..663ba49 100644 --- a/templates/chat/index.html +++ b/templates/chat/index.html @@ -1,61 +1,169 @@ - - - - - Agent 对话 - - -

Agent 对话

- +{% extends "base.html" %} +{% block title %}{{ scenario.name|default:"Agent 对话" }}{% endblock %} + +{% block content %} {% if error %} -

{{ error }}

+
{{ error }}
{% endif %} {% if scenario %} -
-

{{ scenario.name }}

-

{{ scenario.description }}

-

角色:{{ scenario.agent.role }}

-

目标:{{ scenario.agent.goal }}

+ + +
+
+
+

提问面板

+

可以直接提问,也可以勾选部分已入库文档作为当前上下文范围。

+
+ {% csrf_token %} +
+ {{ form.message.label_tag }} + {{ form.message }} + {% if form.message.errors %} +

{{ form.message.errors|join:" " }}

+ {% endif %} +
+
+ {{ form.document_ids.label_tag }} +

不勾选时默认检索当前场景全部已入库文档。

+
+ {% for checkbox in form.document_ids %} + + {% empty %} +
当前场景还没有已入库文档,系统将仅依赖工具和模型能力生成结果。
+ {% endfor %} +
+ {% if form.document_ids.errors %} +

{{ form.document_ids.errors|join:" " }}

+ {% endif %} +
+
+ +
+
+
+ +
+

执行说明

+
    +
  • + 1. 场景配置 + 系统会先读取当前 YAML 场景配置,确定角色、目标、工具和输出结构。 +
  • +
  • + 2. RAG 与工具 + 如果场景启用了知识库检索,系统会根据你的问题召回相关片段,并执行声明式工具。 +
  • +
  • + 3. 结构化结果 + Agent Core 会优先解析 JSON 输出,解析失败时回退为稳定的展示结构。 +
  • +
+
+
+ +
+
+

回答总览

+ {% if result %} +
    +
  • 模型:{{ result.model_name }}
  • +
  • 状态:{{ result.status }}
  • +
  • 耗时:{{ result.latency_ms }} ms
  • +
+
+ 主回答 +
{{ result.answer|linebreaksbr }}
+
+ {% if audit_log %} +

+ 查看本次审计日志 +

+ {% endif %} + {% else %} +
提交问题后,这里会展示 Agent 的主回答、模型信息和执行状态。
+ {% endif %} +
+ + {% if result %} +
+

结构化结果

+ + + {% for key, value in result.structured_output.items %} + + + + + {% endfor %} + +
{{ key }} + {% if key == "answer" or key == "summary" or key == "reply" %} + {{ value|linebreaksbr }} + {% else %} +
{{ value }}
+ {% endif %} +
+
+ +
+

引用片段

+ {% if result.references %} +
    + {% for reference in result.references %} +
  • + {{ reference.source }} +
    {{ reference.content|default:"无正文内容"|linebreaksbr }}
    +
  • + {% endfor %} +
+ {% else %} +
当前回答没有引用知识库片段。
+ {% endif %} +
+ +
+

工具调用

+ {% if result.tool_calls %} +
    + {% for tool_call in result.tool_calls %} +
  • + {{ tool_call.tool_name }} +

    执行状态:{{ tool_call.success }}

    + {% if tool_call.error %} +

    {{ tool_call.error }}

    + {% endif %} +
    {{ tool_call.result }}
    +
  • + {% endfor %} +
+ {% else %} +
当前场景没有声明工具,或本次执行无需调用工具。
+ {% endif %} +
+ + {% if result.error %} +
+

错误信息

+
{{ result.error }}
+
+ {% endif %} + {% endif %} +
- -
- {% csrf_token %} - {{ form.as_p }} - -
- - {% if result %} -
-

回答

-

{{ result.answer }}

-

模型:{{ result.model_name }}

-

状态:{{ result.status }}

-

耗时:{{ result.latency_ms }} ms

-
-
-

结构化输出

-
{{ result.structured_output }}
-
-
-

引用来源

-
{{ result.references }}
-
-
-

工具调用

-
{{ result.tool_calls }}
-
- {% if audit_log %} -

查看审计日志

- {% endif %} - {% if result.error %} -
-

错误信息

-
{{ result.error }}
-
- {% endif %} - {% endif %} {% endif %} - - +{% endblock %} diff --git a/templates/documents/document_list.html b/templates/documents/document_list.html index 9903645..5e76cfb 100644 --- a/templates/documents/document_list.html +++ b/templates/documents/document_list.html @@ -1,50 +1,53 @@ - - - - - 文件列表 - - -

文件列表

- - - - - - - - - - - - - - {% for document in documents %} +{% extends "base.html" %} + +{% block title %}文档中心{% endblock %} + +{% block content %} + + +
+
文件名场景类型大小状态操作
+ - - - - - - + + + + + + - {% empty %} - - {% endfor %} - -
{{ document.original_name }}{{ document.scenario_id }}{{ document.file_type }}{{ document.size }}{{ document.status }} - {% if document.status != "indexed" %} -
- {% csrf_token %} - -
- {% endif %} - {% if document.error_message %} -
{{ document.error_message }}
- {% endif %} -
文件名场景类型大小状态操作
暂无文件。
- - + + + {% for document in documents %} + + {{ document.original_name }} + {{ document.scenario_id }} + {{ document.file_type }} + {{ document.size }} + {{ document.status }} + + {% if document.status != "indexed" %} +
+ {% csrf_token %} + +
+ {% else %} + 已可用于检索 + {% endif %} + {% if document.error_message %} +
{{ document.error_message }}
+ {% endif %} + + + {% empty %} + 暂无文件,请先上传题目材料。 + {% endfor %} + + + +{% endblock %} diff --git a/templates/documents/upload.html b/templates/documents/upload.html index 6285233..72499b7 100644 --- a/templates/documents/upload.html +++ b/templates/documents/upload.html @@ -1,26 +1,39 @@ - - - - - 上传文件 - - -

上传文件

- -
- {% csrf_token %} -

- - -

-

{{ form.file.label_tag }} {{ form.file }}

- {{ form.errors }} -

支持 .txt、.md、.pdf 和 .docx 文件。

- -
- - +{% extends "base.html" %} + +{% block title %}上传文件{% endblock %} + +{% block content %} + + +
+
+ {% csrf_token %} +
+ + +
+
+ {{ form.file.label_tag }} + {{ form.file }} + {% if form.file.errors %} +

{{ form.file.errors|join:" " }}

+ {% endif %} +
+ {% if form.errors %} +
{{ form.errors }}
+ {% endif %} +
+ + 返回文件列表 +
+
+
+{% endblock %} diff --git a/templates/scenarios/index.html b/templates/scenarios/index.html index c848d37..83528cc 100644 --- a/templates/scenarios/index.html +++ b/templates/scenarios/index.html @@ -1,23 +1,30 @@ - - - - - Universal Agent Demo Framework - - -

Universal Agent Demo Framework

-

用于复试展示的通用 AI Agent Demo 框架。

-
+{% extends "base.html" %} + +{% block title %}场景首页{% endblock %} + +{% block content %} + + +
{% for scenario in scenarios %} -
+

{{ scenario.name }}

{{ scenario.description }}

-

场景 ID:{{ scenario.id }}

-

进入对话

-
+
    +
  • 场景 ID:{{ scenario.id }}
  • +
  • 输出:{{ scenario.output.type }}
  • +
  • 工具数:{{ scenario.tools|length }}
  • +
+

+ 进入对话 +

+ {% empty %} -

暂无可用场景,请检查 configs 目录。

+
暂无可用场景,请检查 `configs/` 目录和 YAML 配置内容。
{% endfor %} -
- - +
+{% endblock %} diff --git a/tests/test_chat.py b/tests/test_chat.py index 7cbe541..7bae4cc 100644 --- a/tests/test_chat.py +++ b/tests/test_chat.py @@ -1,5 +1,6 @@ from django.urls import reverse +from agent_core.results import AgentResult from apps.audit.models import AgentAuditLog from apps.documents.models import UploadedDocument @@ -58,3 +59,49 @@ def test_chat_passes_selected_document_ids_to_agent_core(client, db, monkeypatch assert response.status_code == 200 assert captured["options"]["document_ids"] == [selected.id] assert other.id not in captured["options"]["document_ids"] + + +def test_chat_renders_structured_output_references_and_tool_calls(client, db, monkeypatch): + def fake_run_agent(scenario_config, user_input, options=None): + return AgentResult( + answer="建议先隔离现场。", + structured_output={ + "output_type": "quality_report", + "summary": "发现异常批次需要立即处置。", + "risk_level": "high", + "suggested_actions": ["隔离现场", "通知负责人"], + }, + references=[ + { + "source": "sop.md", + "content": "异常处理 SOP:先隔离现场,再通知负责人。", + } + ], + tool_calls=[ + { + "tool_name": "query_demo_records", + "success": True, + "result": {"records": [{"title": "A线缺陷"}]}, + "error": "", + } + ], + model_name="mock-model", + status="success", + ) + + monkeypatch.setattr("apps.chat.views.run_agent", fake_run_agent) + + response = client.post( + reverse("chat:index", args=["quality_analysis"]), + {"message": "分析 A 线异常"}, + ) + + content = response.content.decode("utf-8") + assert response.status_code == 200 + assert "结构化结果" in content + assert "发现异常批次需要立即处置" in content + assert "引用片段" in content + assert "sop.md" in content + assert "工具调用" in content + assert "query_demo_records" in content + assert "查看本次审计日志" in content