feat(dashboard): 增加首页工作台并调整聊天入口

This commit is contained in:
2026-06-08 22:25:16 +08:00
parent ef0a9ee13e
commit ccfa43645e
11 changed files with 632 additions and 25 deletions

View File

@@ -2,10 +2,11 @@ from django.contrib import admin
from django.contrib.auth.views import LoginView, LogoutView, PasswordChangeView
from django.urls import include, path
from review_agent.views import attachment_manager, knowledge_base_manager, stream_chat, workspace
from review_agent.views import attachment_manager, home_dashboard, knowledge_base_manager, stream_chat, workspace
urlpatterns = [
path("", workspace, name="home"),
path("", home_dashboard, name="home"),
path("chat/", workspace, name="chat"),
path("knowledge-base/", knowledge_base_manager, name="knowledge_base_manager"),
path("attachments/", attachment_manager, name="attachment_manager"),
path("", include("review_agent.urls")),

View File

@@ -1,9 +1,10 @@
from django.contrib.auth.decorators import login_required
from django.db.models import Count, Q
from django.db.models import Count, Q, Sum
import json
from django.http import HttpRequest, HttpResponse, JsonResponse, StreamingHttpResponse
from django.shortcuts import redirect, render
from django.utils.http import urlencode
from django.views.decorators.http import require_http_methods
from .services import (
@@ -28,6 +29,29 @@ from .models import KnowledgeBaseDocument
from .regulatory_review.services.info_extract import ensure_regulatory_condition_candidates
@login_required
@require_http_methods(["GET"])
def home_dashboard(request: HttpRequest) -> HttpResponse:
"""Renders the data-first home dashboard for the current user."""
if request.GET.get("conversation"):
query = {"conversation": request.GET["conversation"]}
search = (request.GET.get("q") or "").strip()
if search:
query["q"] = search
return redirect(f"/chat/?{urlencode(query)}")
context = build_home_dashboard_context(request.user)
return render(
request,
"workbench.html",
{
"page_title": "首页",
"dashboard": context,
},
)
@login_required
@require_http_methods(["GET", "POST"])
def workspace(request: HttpRequest) -> HttpResponse:
@@ -39,7 +63,7 @@ def workspace(request: HttpRequest) -> HttpResponse:
if action == "new_conversation":
conversation = create_conversation(request.user)
return redirect(f"/?conversation={conversation.pk}")
return redirect(f"/chat/?conversation={conversation.pk}")
if action == "send_message":
content = (request.POST.get("prompt") or "").strip()
@@ -47,7 +71,7 @@ def workspace(request: HttpRequest) -> HttpResponse:
conversation = create_conversation(request.user)
if content:
send_message(conversation, content)
return redirect(f"/?conversation={conversation.pk}")
return redirect(f"/chat/?conversation={conversation.pk}")
search = (request.GET.get("q") or "").strip()
conversations = list_conversations(request.user, search)
@@ -325,3 +349,139 @@ def _format_form_fill_label(batch: ApplicationFormFillBatch) -> str:
if batch.risk_notes:
parts.append(f"提示 {len(batch.risk_notes)}")
return " · ".join(parts)
def build_home_dashboard_context(user) -> dict[str, object]:
conversations = Conversation.objects.filter(user=user)
active_attachments = FileAttachment.objects.filter(user=user).exclude(
upload_status=FileAttachment.UploadStatus.DELETED
)
active_knowledge_documents = KnowledgeBaseDocument.objects.filter(user=user).exclude(
status=KnowledgeBaseDocument.Status.DELETED
)
knowledge_context = build_knowledge_base_context_for_user(user)
builtin_source_count = int(knowledge_context.get("source_count") or 0)
collection_chunk_count = int((knowledge_context.get("collection") or {}).get("count") or 0)
managed_document_count = active_knowledge_documents.count()
file_batches = FileSummaryBatch.objects.filter(user=user).select_related("conversation")
regulatory_batches = RegulatoryReviewBatch.objects.filter(user=user).select_related("conversation")
form_fill_batches = ApplicationFormFillBatch.objects.filter(user=user, is_deleted=False).select_related("conversation")
batch_status_counts = _build_batch_status_counts(file_batches, regulatory_batches, form_fill_batches)
total_batches = file_batches.count() + regulatory_batches.count() + form_fill_batches.count()
successful_batches = batch_status_counts["success"]
handled_batches = successful_batches + batch_status_counts["failed"]
recent_records = _build_recent_dashboard_records(
conversations.order_by("-updated_at", "-id")[:8],
file_batches.order_by("-created_at", "-id")[:8],
regulatory_batches.order_by("-created_at", "-id")[:8],
form_fill_batches.order_by("-created_at", "-id")[:8],
)
return {
"metrics": {
"conversation_count": conversations.count(),
"recent_conversation_count": conversations.filter(messages__isnull=False).distinct().count(),
"attachment_count": active_attachments.count(),
"active_attachment_count": active_attachments.filter(is_active=True).count(),
"knowledge_document_count": managed_document_count + builtin_source_count,
"running_batch_count": batch_status_counts["running"],
"handled_batch_count": handled_batches,
"success_batch_count": successful_batches,
"waiting_batch_count": batch_status_counts["waiting"],
"failed_batch_count": batch_status_counts["failed"],
"total_batch_count": total_batches,
},
"knowledge": {
"document_count": managed_document_count,
"builtin_source_count": builtin_source_count,
"total_material_count": managed_document_count + builtin_source_count,
"active_document_count": active_knowledge_documents.filter(is_active=True).count(),
"indexed_document_count": active_knowledge_documents.filter(indexed_chunk_count__gt=0).count(),
"managed_chunk_count": active_knowledge_documents.aggregate(total=Sum("indexed_chunk_count"))["total"] or 0,
"chunk_count": collection_chunk_count,
},
"attachments": {
"attachment_count": active_attachments.count(),
"active_attachment_count": active_attachments.filter(is_active=True).count(),
"recent_attachment_count": active_attachments.order_by("-created_at", "-id")[:5].count(),
"conversation_count": active_attachments.values("conversation_id").distinct().count(),
},
"workflow": {
"file_summary_count": file_batches.count(),
"regulatory_review_count": regulatory_batches.count(),
"application_form_fill_count": form_fill_batches.count(),
**batch_status_counts,
},
"recent_records": recent_records,
}
def _build_batch_status_counts(file_batches, regulatory_batches, form_fill_batches) -> dict[str, int]:
running_statuses = {
FileSummaryBatch.Status.PENDING,
FileSummaryBatch.Status.RUNNING,
ApplicationFormFillBatch.Status.PENDING,
ApplicationFormFillBatch.Status.RUNNING,
RegulatoryReviewBatch.Status.PENDING,
RegulatoryReviewBatch.Status.RUNNING,
}
waiting_statuses = {
ApplicationFormFillBatch.Status.WAITING_USER,
RegulatoryReviewBatch.Status.WAITING_USER,
}
success_statuses = {
FileSummaryBatch.Status.SUCCESS,
RegulatoryReviewBatch.Status.SUCCESS,
ApplicationFormFillBatch.Status.SUCCESS,
ApplicationFormFillBatch.Status.PARTIAL_SUCCESS,
}
failed_statuses = {
FileSummaryBatch.Status.FAILED,
RegulatoryReviewBatch.Status.FAILED,
ApplicationFormFillBatch.Status.FAILED,
}
statuses = [
*file_batches.values_list("status", flat=True),
*regulatory_batches.values_list("status", flat=True),
*form_fill_batches.values_list("status", flat=True),
]
return {
"running": sum(1 for status in statuses if status in running_statuses),
"waiting": sum(1 for status in statuses if status in waiting_statuses),
"success": sum(1 for status in statuses if status in success_statuses),
"failed": sum(1 for status in statuses if status in failed_statuses),
}
def _build_recent_dashboard_records(conversations, file_batches, regulatory_batches, form_fill_batches) -> list[dict[str, object]]:
records = []
for conversation in conversations:
records.append(
{
"type": "对话",
"title": conversation.title or "新对话",
"status": "已更新",
"updated_at": conversation.updated_at,
"url": f"/chat/?conversation={conversation.pk}",
}
)
for batch in file_batches:
records.append(_batch_record(batch, "文件汇总"))
for batch in regulatory_batches:
status = batch.status
risk_label = _format_risk_label(batch.risk_summary or {})
records.append(_batch_record(batch, "法规核查", status_label=risk_label or status))
for batch in form_fill_batches:
records.append(_batch_record(batch, "申报填表"))
return sorted(records, key=lambda item: item["updated_at"], reverse=True)[:8]
def _batch_record(batch, record_type: str, status_label: str | None = None) -> dict[str, object]:
return {
"type": record_type,
"title": batch.batch_no,
"status": status_label or batch.status,
"updated_at": batch.created_at,
"url": f"/chat/?conversation={batch.conversation_id}",
}

View File

@@ -1478,6 +1478,116 @@ input:focus {
background: #eaf2ff;
}
.dashboard-page {
display: grid;
align-content: start;
gap: 12px;
height: calc(100vh - 60px);
overflow-y: auto;
padding: 16px 24px 20px;
background: var(--bg);
}
.dashboard-hero,
.metric-grid,
.dashboard-split,
.dashboard-panel {
width: min(1440px, 100%);
margin: 0 auto;
}
.dashboard-hero {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 0;
}
.dashboard-hero h1 {
margin: 2px 0;
font-size: 22px;
}
.dashboard-hero p {
margin: 0;
color: var(--muted);
font-size: 13px;
}
.dashboard-primary-action {
background: #ffffff;
}
.metric-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
}
.metric-card {
display: grid;
gap: 8px;
min-height: 104px;
padding: 14px;
border: 1px solid var(--line);
border-radius: 8px;
background: #ffffff;
}
.metric-card span,
.metric-card em {
color: var(--muted);
font-size: 12px;
font-style: normal;
font-weight: 700;
}
.metric-card strong {
color: var(--text);
font-size: 30px;
line-height: 1;
}
.dashboard-split {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.dashboard-stat-list {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 10px;
margin: 0;
}
.dashboard-stat-list div {
display: grid;
gap: 6px;
padding: 12px;
border: 1px solid var(--line);
border-radius: 8px;
background: #f8fafc;
}
.dashboard-stat-list dt {
color: var(--muted);
font-size: 12px;
font-weight: 700;
}
.dashboard-stat-list dd {
margin: 0;
color: var(--text);
font-size: 22px;
font-weight: 800;
}
.recent-activity-table td {
height: 44px;
}
.table-empty,
.attachment-manager-empty {
color: var(--muted);
@@ -2293,6 +2403,23 @@ input:focus {
grid-template-columns: 1fr;
gap: 4px;
}
.dashboard-page {
height: auto;
min-height: calc(100vh - 60px);
padding: 12px;
}
.dashboard-hero {
align-items: stretch;
flex-direction: column;
}
.metric-grid,
.dashboard-split,
.dashboard-stat-list {
grid-template-columns: 1fr;
}
}
@keyframes pulse-caret {

View File

@@ -9,8 +9,8 @@
<header class="topbar">
<div class="topbar-left">
<div class="tabbar" role="tablist" aria-label="页面切换">
<a class="tab" href="/" role="tab" aria-selected="false">首页</a>
<a class="tab" href="/" role="tab" aria-selected="false">审核智能体</a>
<a class="tab" href="{% url 'home' %}" role="tab" aria-selected="false">首页</a>
<a class="tab" href="{% url 'chat' %}" role="tab" aria-selected="false">审核智能体</a>
<a class="tab" href="{% url 'knowledge_base_manager' %}" role="tab" aria-selected="false">知识库管理</a>
<a class="tab active" href="{% url 'attachment_manager' %}" role="tab" aria-selected="true">附件管理</a>
</div>
@@ -52,7 +52,7 @@
{% endfor %}
</select>
{% if selected_conversation %}
<a class="return-chat-link" href="{% url 'home' %}?conversation={{ selected_conversation.pk }}">返回对话</a>
<a class="return-chat-link" href="{% url 'chat' %}?conversation={{ selected_conversation.pk }}">返回对话</a>
{% endif %}
</div>
</header>

View File

@@ -9,8 +9,8 @@
<header class="topbar">
<div class="topbar-left">
<div class="tabbar" role="tablist" aria-label="页面切换">
<a class="tab" href="/" role="tab" aria-selected="false">首页</a>
<a class="tab active" href="/" role="tab" aria-selected="true">审核智能体</a>
<a class="tab" href="{% url 'home' %}" role="tab" aria-selected="false">首页</a>
<a class="tab active" href="{% url 'chat' %}" role="tab" aria-selected="true">审核智能体</a>
<a class="tab" href="{% url 'knowledge_base_manager' %}" role="tab" aria-selected="false">知识库管理</a>
<a class="tab" href="{% url 'attachment_manager' %}" role="tab" aria-selected="false">附件管理</a>
</div>
@@ -79,7 +79,7 @@
>
<a
class="history-link"
href="/?conversation={{ conversation.pk }}{% if search_query %}&q={{ search_query|urlencode }}{% endif %}"
href="{% url 'chat' %}?conversation={{ conversation.pk }}{% if search_query %}&q={{ search_query|urlencode }}{% endif %}"
>
<span class="history-title">{{ conversation.title|default:"新对话" }}</span>
<span class="history-meta">{{ conversation.updated_at|date:"m月d日 H:i" }}</span>
@@ -202,7 +202,7 @@
</div>
<div class="composer-wrap">
<form class="composer" action="/" method="post" id="chatComposer">
<form class="composer" action="{% url 'chat' %}" method="post" id="chatComposer">
{% csrf_token %}
<input type="hidden" name="action" value="send_message">
<input type="hidden" name="conversation_id" id="conversationIdInput" value="{% if current_conversation %}{{ current_conversation.pk }}{% endif %}">

View File

@@ -9,8 +9,8 @@
<header class="topbar">
<div class="topbar-left">
<div class="tabbar" role="tablist" aria-label="页面切换">
<a class="tab" href="/" role="tab" aria-selected="false">首页</a>
<a class="tab" href="/" role="tab" aria-selected="false">审核智能体</a>
<a class="tab" href="{% url 'home' %}" role="tab" aria-selected="false">首页</a>
<a class="tab" href="{% url 'chat' %}" role="tab" aria-selected="false">审核智能体</a>
<a class="tab active" href="{% url 'knowledge_base_manager' %}" role="tab" aria-selected="true">知识库管理</a>
<a class="tab" href="{% url 'attachment_manager' %}" role="tab" aria-selected="false">附件管理</a>
</div>
@@ -41,7 +41,7 @@
</div>
<div class="knowledge-hero-actions">
<span class="knowledge-status status-{{ knowledge_base.status.code }}">{{ knowledge_base.status.label }}</span>
<a class="return-chat-link" href="{% url 'home' %}">返回对话</a>
<a class="return-chat-link" href="{% url 'chat' %}">返回对话</a>
</div>
</header>

173
templates/workbench.html Normal file
View File

@@ -0,0 +1,173 @@
{% extends "base.html" %}
{% load static %}
{% block title %}首页 - DEMO-AGENT V2{% endblock %}
{% block body_class %}app-body{% endblock %}
{% block content %}
<main class="app-shell">
<header class="topbar">
<div class="topbar-left">
<div class="tabbar" role="tablist" aria-label="页面切换">
<a class="tab active" href="{% url 'home' %}" role="tab" aria-selected="true">首页</a>
<a class="tab" href="{% url 'chat' %}" role="tab" aria-selected="false">审核智能体</a>
<a class="tab" href="{% url 'knowledge_base_manager' %}" role="tab" aria-selected="false">知识库管理</a>
<a class="tab" href="{% url 'attachment_manager' %}" role="tab" aria-selected="false">附件管理</a>
</div>
</div>
<div class="topbar-right">
<div class="user-menu">
<button class="user-menu-trigger" type="button">
<span class="avatar large">{{ request.user.username|slice:":1"|upper }}</span>
<div class="user-copy">
<strong>{{ request.user.username }}</strong>
<span>当前登录用户</span>
</div>
</button>
</div>
</div>
</header>
<section class="dashboard-page">
<header class="dashboard-hero attachment-manager-toolbar">
<div>
<p class="eyebrow">首页</p>
<h1>注册资料审核工作台</h1>
<p>当前账号资料、知识库、附件与审核处理数据总览。</p>
</div>
<a class="return-chat-link dashboard-primary-action" href="{% url 'chat' %}">进入审核智能体</a>
</header>
<section class="metric-grid" aria-label="首页关键指标">
<article class="metric-card">
<span>对话总数</span>
<strong>{{ dashboard.metrics.conversation_count }}</strong>
<em>已处理 {{ dashboard.metrics.recent_conversation_count }}</em>
</article>
<article class="metric-card">
<span>附件总数</span>
<strong>{{ dashboard.metrics.attachment_count }}</strong>
<em>启用 {{ dashboard.metrics.active_attachment_count }}</em>
</article>
<article class="metric-card">
<span>知识库材料</span>
<strong>{{ dashboard.metrics.knowledge_document_count }}</strong>
<em>管理 {{ dashboard.knowledge.document_count }} · 内置 {{ dashboard.knowledge.builtin_source_count }}</em>
</article>
<article class="metric-card">
<span>执行中批次</span>
<strong>{{ dashboard.metrics.running_batch_count }}</strong>
<em>总批次 {{ dashboard.metrics.total_batch_count }}</em>
</article>
<article class="metric-card">
<span>已处理批次</span>
<strong>{{ dashboard.metrics.handled_batch_count }}</strong>
<em>成功 {{ dashboard.metrics.success_batch_count }}</em>
</article>
<article class="metric-card">
<span>等待确认</span>
<strong>{{ dashboard.metrics.waiting_batch_count }}</strong>
<em>需人工处理</em>
</article>
<article class="metric-card">
<span>失败批次</span>
<strong>{{ dashboard.metrics.failed_batch_count }}</strong>
<em>需排查</em>
</article>
<article class="metric-card">
<span>申报填表</span>
<strong>{{ dashboard.workflow.application_form_fill_count }}</strong>
<em>自动填表批次</em>
</article>
</section>
<div class="dashboard-split">
<section class="attachment-manager-panel dashboard-panel">
<div class="summary-subheading">
<h3>知识库概览</h3>
</div>
<dl class="dashboard-stat-list">
<div>
<dt>管理文档</dt>
<dd>{{ dashboard.knowledge.document_count }}</dd>
</div>
<div>
<dt>内置材料</dt>
<dd>{{ dashboard.knowledge.builtin_source_count }}</dd>
</div>
<div>
<dt>已索引</dt>
<dd>{{ dashboard.knowledge.indexed_document_count }}</dd>
</div>
<div>
<dt>向量片段</dt>
<dd>{{ dashboard.knowledge.chunk_count }}</dd>
</div>
</dl>
</section>
<section class="attachment-manager-panel dashboard-panel">
<div class="summary-subheading">
<h3>附件与文档概览</h3>
</div>
<dl class="dashboard-stat-list">
<div>
<dt>附件总数</dt>
<dd>{{ dashboard.attachments.attachment_count }}</dd>
</div>
<div>
<dt>启用附件</dt>
<dd>{{ dashboard.attachments.active_attachment_count }}</dd>
</div>
<div>
<dt>最近上传</dt>
<dd>{{ dashboard.attachments.recent_attachment_count }}</dd>
</div>
<div>
<dt>关联对话</dt>
<dd>{{ dashboard.attachments.conversation_count }}</dd>
</div>
</dl>
</section>
</div>
<section class="attachment-manager-panel dashboard-panel">
<div class="summary-subheading">
<h3>最近处理记录</h3>
<span>最近 8 条</span>
</div>
<div class="attachment-table-wrap">
<table class="attachment-table recent-activity-table">
<thead>
<tr>
<th>类型</th>
<th>名称或批次号</th>
<th>状态</th>
<th>更新时间</th>
<th>入口</th>
</tr>
</thead>
<tbody>
{% for record in dashboard.recent_records %}
<tr>
<td>{{ record.type }}</td>
<td class="attachment-name">{{ record.title }}</td>
<td>{{ record.status }}</td>
<td>{{ record.updated_at|date:"Y-m-d H:i" }}</td>
<td class="attachment-actions">
<a href="{{ record.url }}">查看</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="5" class="table-empty">暂无处理记录</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
</section>
</main>
{% endblock %}

View File

@@ -31,7 +31,7 @@ def test_workspace_renders_application_form_fill_workflow_card(client, django_us
)
client.force_login(user)
response = client.get(f"{reverse('home')}?conversation={conversation.pk}")
response = client.get(f"{reverse('chat')}?conversation={conversation.pk}")
content = response.content.decode("utf-8")
assert "AFF-CARD" in content

View File

@@ -17,7 +17,7 @@ def test_workspace_renders_summary_panel(client, django_user_model):
)
client.force_login(user)
response = client.get(f"{reverse('home')}?conversation={conversation.pk}")
response = client.get(f"{reverse('chat')}?conversation={conversation.pk}")
assert response.status_code == 200
content = response.content.decode("utf-8")
@@ -37,7 +37,7 @@ def test_workspace_links_to_attachment_manager(client, django_user_model):
conversation = Conversation.objects.create(user=user, title="会话")
client.force_login(user)
response = client.get(f"{reverse('home')}?conversation={conversation.pk}")
response = client.get(f"{reverse('chat')}?conversation={conversation.pk}")
assert response.status_code == 200
content = response.content.decode("utf-8")
@@ -85,7 +85,7 @@ def test_attachment_manager_selects_conversation_and_lists_attachments(client, d
assert "编辑" in content
assert "删除" in content
assert "attachment-manager-split" in content
assert reverse("home") + f"?conversation={conversation.pk}" in content
assert reverse("chat") + f"?conversation={conversation.pk}" in content
def test_attachment_manager_uses_compact_admin_layout(client, django_user_model):
@@ -142,7 +142,7 @@ def test_workspace_renders_workflow_history_as_batch_carousel(client, django_use
)
client.force_login(user)
response = client.get(f"{reverse('home')}?conversation={conversation.pk}")
response = client.get(f"{reverse('chat')}?conversation={conversation.pk}")
assert response.status_code == 200
content = response.content.decode("utf-8")
@@ -265,7 +265,7 @@ def test_workspace_tool_buttons_fill_default_prompts(client, django_user_model):
conversation = Conversation.objects.create(user=user, title="会话")
client.force_login(user)
response = client.get(f"{reverse('home')}?conversation={conversation.pk}")
response = client.get(f"{reverse('chat')}?conversation={conversation.pk}")
content = response.content.decode("utf-8")
script = open("static/js/app.js", encoding="utf-8").read()

View File

@@ -0,0 +1,146 @@
import pytest
from django.urls import reverse
from review_agent.models import (
ApplicationFormFillBatch,
Conversation,
FileAttachment,
FileSummaryBatch,
KnowledgeBaseDocument,
RegulatoryReviewBatch,
)
pytestmark = pytest.mark.django_db
def test_home_dashboard_renders_current_user_metrics(client, django_user_model):
user = django_user_model.objects.create_user(username="owner", password="pass")
other = django_user_model.objects.create_user(username="other", password="pass")
conversation = Conversation.objects.create(user=user, title="注册资料会话")
other_conversation = Conversation.objects.create(user=other, title="其他用户会话")
FileAttachment.objects.create(
conversation=conversation,
user=user,
original_name="active.docx",
storage_path="x/active.docx",
file_size=128,
is_active=True,
)
FileAttachment.objects.create(
conversation=conversation,
user=user,
original_name="deleted.docx",
storage_path="x/deleted.docx",
file_size=128,
is_active=False,
upload_status=FileAttachment.UploadStatus.DELETED,
)
FileAttachment.objects.create(
conversation=other_conversation,
user=other,
original_name="other.docx",
storage_path="x/other.docx",
file_size=128,
)
KnowledgeBaseDocument.objects.create(
user=user,
display_name="法规资料",
original_name="rule.md",
storage_path="kb/rule.md",
file_size=64,
is_active=True,
indexed_chunk_count=3,
)
KnowledgeBaseDocument.objects.create(
user=user,
display_name="删除资料",
original_name="deleted.md",
storage_path="kb/deleted.md",
file_size=64,
status=KnowledgeBaseDocument.Status.DELETED,
is_active=False,
indexed_chunk_count=5,
)
KnowledgeBaseDocument.objects.create(
user=other,
display_name="其他资料",
original_name="other.md",
storage_path="kb/other.md",
file_size=64,
indexed_chunk_count=9,
)
summary = FileSummaryBatch.objects.create(
conversation=conversation,
user=user,
batch_no="FS-RUN",
status=FileSummaryBatch.Status.RUNNING,
)
RegulatoryReviewBatch.objects.create(
conversation=conversation,
user=user,
source_summary_batch=summary,
batch_no="RR-WAIT",
status=RegulatoryReviewBatch.Status.WAITING_USER,
risk_summary={"high": 2},
)
ApplicationFormFillBatch.objects.create(
conversation=conversation,
user=user,
source_summary_batch=summary,
batch_no="AFF-OK",
status=ApplicationFormFillBatch.Status.SUCCESS,
)
FileSummaryBatch.objects.create(
conversation=other_conversation,
user=other,
batch_no="FS-OTHER",
status=FileSummaryBatch.Status.FAILED,
)
client.force_login(user)
response = client.get(reverse("home"))
assert response.status_code == 200
content = response.content.decode("utf-8")
assert "注册资料审核工作台" in content
assert "当前账号资料、知识库、附件与审核处理数据总览" in content
assert "工作流流程" not in content
assert "对话总数" in content
assert "附件总数" in content
assert "知识库材料" in content
assert "内置材料" in content
assert f"管理 {1} · 内置" in content
assert "向量片段" in content
assert "FS-RUN" in content
assert "RR-WAIT" in content
assert "AFF-OK" in content
assert "FS-OTHER" not in content
assert "其他用户会话" not in content
assert f'href="{reverse("chat")}?conversation={conversation.pk}"' in content
def test_chat_route_renders_review_agent_workspace(client, django_user_model):
user = django_user_model.objects.create_user(username="owner", password="pass")
conversation = Conversation.objects.create(user=user, title="审核会话")
client.force_login(user)
response = client.get(f"{reverse('chat')}?conversation={conversation.pk}")
assert response.status_code == 200
content = response.content.decode("utf-8")
assert "审核智能体" in content
assert 'id="summaryPanel"' in content
assert f'action="{reverse("chat")}"' in content
assert f'href="{reverse("chat")}?conversation={conversation.pk}"' in content
def test_legacy_home_conversation_redirects_to_chat(client, django_user_model):
user = django_user_model.objects.create_user(username="owner", password="pass")
conversation = Conversation.objects.create(user=user, title="旧入口会话")
client.force_login(user)
response = client.get(f"{reverse('home')}?conversation={conversation.pk}")
assert response.status_code == 302
assert response["Location"] == f"{reverse('chat')}?conversation={conversation.pk}"

View File

@@ -44,7 +44,7 @@ def test_workspace_renders_regulatory_workflow_card(client, django_user_model):
)
client.force_login(user)
response = client.get(f"{reverse('home')}?conversation={conversation.pk}")
response = client.get(f"{reverse('chat')}?conversation={conversation.pk}")
content = response.content.decode("utf-8")
assert "RR-CARD" in content
@@ -97,7 +97,7 @@ def test_workspace_renders_condition_confirmation_form(client, django_user_model
)
client.force_login(user)
response = client.get(f"{reverse('home')}?conversation={conversation.pk}")
response = client.get(f"{reverse('chat')}?conversation={conversation.pk}")
content = response.content.decode("utf-8")
assert "适用条件确认" in content
@@ -152,7 +152,7 @@ def test_workspace_refreshes_incomplete_condition_confirmation_candidates(client
)
client.force_login(user)
response = client.get(f"{reverse('home')}?conversation={conversation.pk}")
response = client.get(f"{reverse('chat')}?conversation={conversation.pk}")
content = response.content.decode("utf-8")
assert "体外诊断试剂" in content
@@ -193,7 +193,7 @@ def test_workspace_renders_rectification_actions_and_summaries(client, tmp_path,
)
client.force_login(user)
response = client.get(f"{reverse('home')}?conversation={conversation.pk}")
response = client.get(f"{reverse('chat')}?conversation={conversation.pk}")
content = response.content.decode("utf-8")
assert "data-rectification-action=\"full-review\"" in content