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

@@ -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}",
}