merge: 合并V2到master
# Conflicts: # .gitignore # README.md # config/asgi.py # config/settings.py # config/urls.py # config/wsgi.py # manage.py # requirements.txt # templates/base.html # tests/conftest.py
This commit is contained in:
139
templates/attachment_manager.html
Normal file
139
templates/attachment_manager.html
Normal file
@@ -0,0 +1,139 @@
|
||||
{% 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" 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>
|
||||
</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="attachment-manager-page"
|
||||
data-selected-conversation="{% if selected_conversation %}{{ selected_conversation.pk }}{% endif %}"
|
||||
>
|
||||
<header class="attachment-manager-hero attachment-manager-toolbar">
|
||||
<div>
|
||||
<p class="eyebrow">附件管理</p>
|
||||
<h1>附件管理</h1>
|
||||
<p>管理各对话下上传的审核资料、版本、状态和下载。</p>
|
||||
</div>
|
||||
<div class="attachment-manager-selectbar">
|
||||
<label for="attachmentConversationSelect">对话</label>
|
||||
<select class="attachment-manager-select-control" id="attachmentConversationSelect">
|
||||
<option value="">请选择对话</option>
|
||||
{% for conversation in conversations %}
|
||||
<option
|
||||
value="{{ conversation.pk }}"
|
||||
{% if selected_conversation and selected_conversation.pk == conversation.pk %}selected{% endif %}
|
||||
>
|
||||
{{ conversation.title|default:"新对话" }} · {{ conversation.updated_at|date:"m月d日 H:i" }} · {{ conversation.attachment_count }} 个附件
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% if selected_conversation %}
|
||||
<a class="return-chat-link" href="{% url 'chat' %}?conversation={{ selected_conversation.pk }}">返回对话</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{% if selected_conversation %}
|
||||
<div class="attachment-manager-content attachment-manager-split">
|
||||
<section class="attachment-manager-panel upload-manager-panel">
|
||||
<div class="summary-subheading">
|
||||
<h3>上传附件</h3>
|
||||
<span>{{ selected_conversation.title|default:"新对话" }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="upload-dropzone manager-upload-dropzone"
|
||||
id="managerUploadDropzone"
|
||||
data-upload-url="{% url 'file_summary_attachment_upload' selected_conversation.pk %}"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
>
|
||||
<input id="managerAttachmentInput" type="file" multiple hidden>
|
||||
<strong>拖拽文件到这里</strong>
|
||||
<span>支持 doc、docx、xls、xlsx、ppt、pptx、pdf、zip、7z、rar</span>
|
||||
</div>
|
||||
<p class="upload-status" id="managerUploadStatus">上传后会归属到当前选择的对话。</p>
|
||||
</section>
|
||||
|
||||
<section class="attachment-manager-panel">
|
||||
<div class="summary-subheading">
|
||||
<h3>附件列表</h3>
|
||||
<input class="attachment-search" id="attachmentSearch" type="search" placeholder="搜索文件名">
|
||||
</div>
|
||||
<div class="attachment-table-wrap">
|
||||
<table class="attachment-table" id="attachmentManagerTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>状态</th>
|
||||
<th>文件名</th>
|
||||
<th>版本</th>
|
||||
<th>大小</th>
|
||||
<th>上传时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for attachment in attachments %}
|
||||
<tr
|
||||
data-attachment-id="{{ attachment.pk }}"
|
||||
data-update-url="{% url 'file_summary_attachment_detail' selected_conversation.pk attachment.pk %}"
|
||||
data-download-url="{% url 'file_summary_attachment_download' selected_conversation.pk attachment.pk %}"
|
||||
>
|
||||
<td>{% if attachment.is_active %}启用{% else %}禁用{% endif %}</td>
|
||||
<td class="attachment-name">{{ attachment.original_name }}</td>
|
||||
<td>v{{ attachment.version_no }}</td>
|
||||
<td>{{ attachment.file_size }} bytes</td>
|
||||
<td>{{ attachment.created_at|date:"Y-m-d H:i" }}</td>
|
||||
<td class="attachment-actions">
|
||||
<a href="{% url 'file_summary_attachment_download' selected_conversation.pk attachment.pk %}">下载</a>
|
||||
<button type="button" data-attachment-action="edit">编辑</button>
|
||||
<button type="button" data-attachment-action="toggle">{% if attachment.is_active %}禁用{% else %}启用{% endif %}</button>
|
||||
<button type="button" data-attachment-action="delete">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="6" class="table-empty">当前对话暂无附件</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{% else %}
|
||||
<section class="attachment-manager-panel attachment-manager-empty attachment-manager-content">
|
||||
<h2>请选择一个对话查看附件</h2>
|
||||
<p>通过上方下拉框选择对话后,可上传、下载、编辑、启用禁用或删除附件。</p>
|
||||
</section>
|
||||
{% endif %}
|
||||
</section>
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{% static 'js/attachment_manager.js' %}"></script>
|
||||
{% endblock %}
|
||||
@@ -1,351 +1,14 @@
|
||||
{% load static %}
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block title %}Universal Agent Demo Framework{% endblock %}</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #f4f1ea;
|
||||
--surface: #fffdf8;
|
||||
--surface-strong: #fff7ea;
|
||||
--border: #d7c9b0;
|
||||
--text: #2f261d;
|
||||
--muted: #73614f;
|
||||
--accent: #a54c2b;
|
||||
--accent-soft: #f1d2b8;
|
||||
--success: #2b6a4d;
|
||||
--warning: #9a5a00;
|
||||
--danger: #8d2f2f;
|
||||
--shadow: 0 18px 40px rgba(91, 63, 36, 0.10);
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
|
||||
color: var(--text);
|
||||
background:
|
||||
radial-gradient(circle at top left, #f7e2c8 0, transparent 30%),
|
||||
linear-gradient(180deg, #f6efe2 0%, #f4f1ea 45%, #efe7da 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
a { color: var(--accent); text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
|
||||
.shell {
|
||||
width: min(1180px, calc(100vw - 32px));
|
||||
margin: 0 auto;
|
||||
padding: 24px 0 40px;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
padding: 18px 20px;
|
||||
border: 1px solid rgba(215, 201, 176, 0.7);
|
||||
background: rgba(255, 253, 248, 0.88);
|
||||
backdrop-filter: blur(12px);
|
||||
border-radius: 22px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.brand-title {
|
||||
margin: 0;
|
||||
font-size: 1.35rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.brand-note {
|
||||
margin: 6px 0 0;
|
||||
color: var(--muted);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.nav-link,
|
||||
.button,
|
||||
button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
padding: 10px 16px;
|
||||
font-size: 0.95rem;
|
||||
cursor: pointer;
|
||||
transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease;
|
||||
}
|
||||
|
||||
.button-primary,
|
||||
button {
|
||||
border-color: var(--accent);
|
||||
background: linear-gradient(135deg, #b9562f, #94452a);
|
||||
color: #fffaf4;
|
||||
box-shadow: 0 10px 22px rgba(165, 76, 43, 0.20);
|
||||
}
|
||||
|
||||
.nav-link:hover,
|
||||
.button:hover,
|
||||
button:hover {
|
||||
text-decoration: none;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 12px 24px rgba(91, 63, 36, 0.12);
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
display: inline-block;
|
||||
margin-bottom: 10px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(241, 210, 184, 0.8);
|
||||
color: var(--accent);
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
margin: 0 0 10px;
|
||||
font-size: clamp(2rem, 3vw, 3rem);
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.page-lead {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 1rem;
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
.card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.panel,
|
||||
.card {
|
||||
background: rgba(255, 253, 248, 0.94);
|
||||
border: 1px solid rgba(215, 201, 176, 0.85);
|
||||
border-radius: 24px;
|
||||
padding: 20px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.card h2,
|
||||
.panel h2,
|
||||
.panel h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.meta-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin: 12px 0 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
color: var(--muted);
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
.meta-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 999px;
|
||||
background: var(--surface-strong);
|
||||
border: 1px solid rgba(215, 201, 176, 0.75);
|
||||
}
|
||||
|
||||
.layout-two-columns {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(320px, 420px) minmax(0, 1fr);
|
||||
gap: 20px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.stack {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
textarea,
|
||||
select,
|
||||
input[type="text"],
|
||||
input[type="file"] {
|
||||
width: 100%;
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--border);
|
||||
background: #fff;
|
||||
padding: 12px 14px;
|
||||
font: inherit;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
textarea { min-height: 150px; resize: vertical; }
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.help-text,
|
||||
.muted {
|
||||
color: var(--muted);
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
.checkbox-list {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.checkbox-item {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: flex-start;
|
||||
padding: 10px 12px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(215, 201, 176, 0.85);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 6px 12px;
|
||||
border-radius: 999px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
background: var(--surface-strong);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.status-success { color: var(--success); }
|
||||
.status-failed { color: var(--danger); }
|
||||
|
||||
.notice {
|
||||
padding: 14px 16px;
|
||||
border-radius: 18px;
|
||||
border: 1px solid rgba(215, 201, 176, 0.85);
|
||||
background: rgba(255, 247, 234, 0.92);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.notice-error {
|
||||
border-color: rgba(141, 47, 47, 0.25);
|
||||
background: rgba(255, 238, 238, 0.95);
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.kv-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.kv-table th,
|
||||
.kv-table td {
|
||||
padding: 12px 10px;
|
||||
border-bottom: 1px solid rgba(215, 201, 176, 0.6);
|
||||
vertical-align: top;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.kv-table th {
|
||||
width: 150px;
|
||||
color: var(--muted);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.detail-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
padding: 14px 16px;
|
||||
border-radius: 18px;
|
||||
background: #fff;
|
||||
border: 1px solid rgba(215, 201, 176, 0.75);
|
||||
}
|
||||
|
||||
.detail-item strong {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
margin: 0;
|
||||
padding: 14px;
|
||||
border-radius: 18px;
|
||||
background: #2f261d;
|
||||
color: #fdf8f1;
|
||||
overflow: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
font-size: 0.92rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin: 0 0 12px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.layout-two-columns { grid-template-columns: 1fr; }
|
||||
.topbar { flex-direction: column; align-items: flex-start; }
|
||||
.nav-links { justify-content: flex-start; }
|
||||
}
|
||||
</style>
|
||||
<title>{% block title %}DEMO-AGENT V2{% endblock %}</title>
|
||||
<link rel="stylesheet" href="{% static 'css/login.css' %}?v=20260608-chat-delete1">
|
||||
</head>
|
||||
<body>
|
||||
<div class="shell">
|
||||
<header class="topbar">
|
||||
<div>
|
||||
<p class="brand-title">Universal Agent Demo Framework</p>
|
||||
<p class="brand-note">面向复试演示的可配置 AI Agent 单体系统</p>
|
||||
</div>
|
||||
<nav class="nav-links">
|
||||
<a class="nav-link" href="{% url 'scenarios:index' %}">场景首页</a>
|
||||
<a class="nav-link" href="{% url 'documents:list' %}">文档中心</a>
|
||||
<a class="nav-link" href="{% url 'audit:list' %}">审计日志</a>
|
||||
</nav>
|
||||
</header>
|
||||
{% if messages %}
|
||||
<section class="stack" style="margin-bottom: 18px;">
|
||||
{% for message in messages %}
|
||||
<div class="notice{% if message.tags == 'error' %} notice-error{% endif %}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
</section>
|
||||
{% endif %}
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
<body class="{% block body_class %}{% endblock %}">
|
||||
{% block content %}{% endblock %}
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
372
templates/home.html
Normal file
372
templates/home.html
Normal file
@@ -0,0 +1,372 @@
|
||||
{% 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" 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>
|
||||
</div>
|
||||
|
||||
<div class="topbar-right">
|
||||
<div class="user-menu" id="userMenu">
|
||||
<button class="user-menu-trigger" id="userMenuTrigger" type="button" aria-haspopup="menu" aria-expanded="false">
|
||||
<span class="avatar large">{{ request.user.username|slice:":1"|upper }}</span>
|
||||
<div class="user-copy">
|
||||
<strong>{{ request.user.username }}</strong>
|
||||
<span>当前登录用户</span>
|
||||
</div>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<div class="user-dropdown" id="userDropdown" role="menu">
|
||||
<div class="user-dropdown-section" role="none">
|
||||
<p class="user-dropdown-label">用户信息</p>
|
||||
<strong class="user-dropdown-name">{{ request.user.username }}</strong>
|
||||
</div>
|
||||
<a class="user-dropdown-link" href="{% url 'password_change' %}" role="menuitem">修改密码</a>
|
||||
<form action="{% url 'logout' %}" method="post" class="user-dropdown-form" role="none">
|
||||
{% csrf_token %}
|
||||
<button class="user-dropdown-link danger-link" type="submit" role="menuitem">退出登录</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="workspace" data-sidebar-state="open">
|
||||
<aside class="sidebar" id="sidebar">
|
||||
<div class="sidebar-top">
|
||||
<div class="sidebar-header">
|
||||
<button class="icon-button sidebar-toggle" type="button" id="sidebarToggle" aria-label="折叠侧边栏">
|
||||
<span></span>
|
||||
<span></span>
|
||||
</button>
|
||||
<div class="brand">
|
||||
<span class="brand-mark">审</span>
|
||||
<div class="brand-copy">
|
||||
<strong class="brand-text">审核智能体</strong>
|
||||
<span class="brand-subtitle">临床注册文件审核工作台</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="new_conversation">
|
||||
<button class="new-chat" type="submit">+ 新对话</button>
|
||||
</form>
|
||||
<form class="search-form" method="get">
|
||||
<label class="sr-only" for="conversationSearch">搜索会话</label>
|
||||
<input id="conversationSearch" type="text" name="q" value="{{ search_query }}" placeholder="搜索会话...">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-group">
|
||||
<p class="sidebar-label">对话记录</p>
|
||||
<nav class="history-list" aria-label="对话历史">
|
||||
{% for conversation in conversations %}
|
||||
<div
|
||||
class="history-item{% if current_conversation and current_conversation.pk == conversation.pk %} active{% endif %}"
|
||||
data-conversation-id="{{ conversation.pk }}"
|
||||
data-delete-url="{% url 'review_agent_conversation_detail' conversation.pk %}"
|
||||
>
|
||||
<a
|
||||
class="history-link"
|
||||
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>
|
||||
</a>
|
||||
<button
|
||||
class="history-delete"
|
||||
type="button"
|
||||
data-conversation-delete
|
||||
aria-label="删除对话 {{ conversation.title|default:'新对话' }}"
|
||||
title="删除对话"
|
||||
>×</button>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="history-empty">
|
||||
<p>暂无会话记录</p>
|
||||
<span>点击上方“新对话”开始审核。</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
</aside>
|
||||
|
||||
<section class="chat-shell">
|
||||
<section class="chat-stage" data-stream-url="{% url 'chat_stream' %}">
|
||||
<div class="chat-scroll-wrap">
|
||||
<div class="chat-scroll" id="chatScroll">
|
||||
{% if current_conversation %}
|
||||
<div class="conversation-header" id="conversation-top" data-node-label="会话开始">
|
||||
<div>
|
||||
<p class="eyebrow">审核智能体</p>
|
||||
<h1>{{ current_conversation.title|default:"新对话" }}</h1>
|
||||
</div>
|
||||
<span class="conversation-meta">最后更新 {{ current_conversation.updated_at|date:"Y-m-d H:i" }}</span>
|
||||
</div>
|
||||
|
||||
{% for message in messages %}
|
||||
<article
|
||||
class="message {{ message.role }}"
|
||||
id="message-{{ message.pk }}"
|
||||
data-message-id="{{ message.pk }}"
|
||||
data-node-label="{% if message.role == 'assistant' %}AI{% else %}用户{% endif %} {{ forloop.counter }}"
|
||||
>
|
||||
<div class="message-avatar{% if message.role == 'user' %} user-mark{% endif %}">
|
||||
{% if message.role == "assistant" %}AI{% else %}{{ request.user.username|slice:":1"|upper }}{% endif %}
|
||||
</div>
|
||||
<div class="message-bubble">
|
||||
{% if message.role == "assistant" %}
|
||||
<div class="message-content markdown-content"></div>
|
||||
<template class="message-raw">{{ message.content }}</template>
|
||||
{% else %}
|
||||
<p>{{ message.content|linebreaksbr }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
{% if condition_confirmation %}
|
||||
<article
|
||||
class="message assistant"
|
||||
id="condition-confirmation-{{ condition_confirmation.id }}"
|
||||
data-condition-confirmation-card
|
||||
data-node-label="AI 适用条件确认"
|
||||
>
|
||||
<div class="message-avatar">AI</div>
|
||||
<div class="message-bubble">
|
||||
<form
|
||||
class="condition-confirm-form"
|
||||
data-condition-confirm-form
|
||||
data-batch-id="{{ condition_confirmation.id }}"
|
||||
data-confirm-url="/api/review-agent/regulatory-review/{{ condition_confirmation.id }}/conditions/"
|
||||
>
|
||||
{% csrf_token %}
|
||||
<strong>适用条件确认</strong>
|
||||
<p>请确认 {{ condition_confirmation.batch_no }} 的产品类别、注册类型和临床评价路径,确认后我会继续法规核查。</p>
|
||||
{% for field, config in condition_confirmation.candidates.items %}
|
||||
<label>
|
||||
<span>{{ config.label }}</span>
|
||||
{% if config.input_type == "select" %}
|
||||
<select name="{{ field }}">
|
||||
{% for option in config.options %}
|
||||
<option value="{{ option }}"{% if option == config.suggested %} selected{% endif %}>{{ option }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% else %}
|
||||
<input type="text" name="{{ field }}" value="{{ config.suggested|default:'' }}">
|
||||
{% endif %}
|
||||
</label>
|
||||
{% endfor %}
|
||||
<button type="submit">确认并继续</button>
|
||||
<p class="condition-confirm-status" data-condition-confirm-status></p>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<p class="eyebrow">审核智能体</p>
|
||||
<h1>开始新的审核对话</h1>
|
||||
<p class="muted">输入资料疑点、法规条款、说明书问题或风险项,系统会为你保留真实会话记录。</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<nav class="node-rail{% if not current_conversation %} hidden{% endif %}" id="nodeRail" aria-label="对话节点导航">
|
||||
<div class="node-rail-line"></div>
|
||||
{% if current_conversation %}
|
||||
{% for message in messages %}
|
||||
{% if message.role == "user" %}
|
||||
<a
|
||||
class="node-anchor{% if forloop.last %} latest{% endif %}"
|
||||
href="#message-{{ message.pk }}"
|
||||
data-target="message-{{ message.pk }}"
|
||||
title="用户 {{ forloop.counter }}"
|
||||
>
|
||||
<span class="node-dot"></span>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="composer-wrap">
|
||||
<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 %}">
|
||||
<label class="sr-only" for="prompt">输入消息</label>
|
||||
<textarea id="prompt" name="prompt" rows="1" placeholder="输入审核问题、法规条款、说明书疑点或上传需求"></textarea>
|
||||
<div class="composer-actions">
|
||||
<div class="composer-tools">
|
||||
<button
|
||||
class="tool-chip"
|
||||
type="button"
|
||||
data-prompt-template="请对当前对话已上传的文件或压缩包自动汇总文件目录、文件类型和页数,并生成可下载的汇总报告。"
|
||||
>目录自动汇总</button>
|
||||
<button
|
||||
class="tool-chip"
|
||||
type="button"
|
||||
data-prompt-template="请对当前对话最近成功汇总的注册资料发起 NMPA 法规核查与风险预警,检查完整性、章节结构、一致性、高风险问题、阻断项、证据来源和整改建议。"
|
||||
>法规核查与风险预警</button>
|
||||
<button
|
||||
class="tool-chip"
|
||||
type="button"
|
||||
data-prompt-template="请基于当前对话最近成功汇总的产品资料,自动提取产品关键信息并填入申报文件模板"
|
||||
>申报文件填表</button>
|
||||
<button
|
||||
class="tool-chip"
|
||||
type="button"
|
||||
data-prompt-template="根据说明书生成第1章监管信息"
|
||||
>第1章监管信息</button>
|
||||
</div>
|
||||
<button class="send-button" type="submit" id="sendButton">发送</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<aside
|
||||
class="summary-panel"
|
||||
id="summaryPanel"
|
||||
data-attachment-url-template="/api/review-agent/conversations/__conversation_id__/attachments/"
|
||||
data-message-url-template="/api/review-agent/conversations/__conversation_id__/messages/"
|
||||
data-status-url-template="/api/review-agent/file-summary/__batch_id__/status/"
|
||||
data-regulatory-status-url-template="/api/review-agent/regulatory-review/__batch_id__/status/"
|
||||
data-application-form-fill-status-url-template="/api/review-agent/application-form-fill/__batch_id__/status/"
|
||||
data-regulatory-info-package-status-url-template="/api/review-agent/regulatory-info-package/__batch_id__/status/"
|
||||
data-events-url-template="/api/review-agent/file-summary/__batch_id__/events/"
|
||||
>
|
||||
<section class="summary-section upload-section">
|
||||
<div class="summary-heading">
|
||||
<h2>文件汇总</h2>
|
||||
<span>当前对话</span>
|
||||
</div>
|
||||
<div class="upload-dropzone" id="uploadDropzone" tabindex="0" role="button">
|
||||
<input id="attachmentInput" type="file" multiple hidden>
|
||||
<strong>拖拽文件到这里</strong>
|
||||
<span>支持多文件、zip、7z、rar</span>
|
||||
</div>
|
||||
<p class="upload-status" id="uploadStatus">上传后发送“自动汇总文件目录与页数”启动工作流。</p>
|
||||
</section>
|
||||
|
||||
<section class="summary-section attachment-section">
|
||||
<div class="summary-subheading">
|
||||
<h3>附件</h3>
|
||||
<a
|
||||
class="attachment-manager-link"
|
||||
href="{% url 'attachment_manager' %}{% if current_conversation %}?conversation={{ current_conversation.pk }}{% endif %}"
|
||||
aria-label="打开附件管理页面"
|
||||
>↗</a>
|
||||
</div>
|
||||
<div class="attachment-list" id="attachmentList">
|
||||
{% for attachment in attachments %}
|
||||
<div class="attachment-item" data-attachment-id="{{ attachment.pk }}">
|
||||
<div>
|
||||
<strong>{{ attachment.original_name }}</strong>
|
||||
<span>v{{ attachment.version_no }} · {{ attachment.file_size }} bytes · {{ attachment.upload_status }}</span>
|
||||
</div>
|
||||
{% if attachment.is_active %}<em>active</em>{% endif %}
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="panel-empty">暂无附件</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="summary-section workflow-section">
|
||||
<div class="summary-subheading">
|
||||
<h3>工作流</h3>
|
||||
</div>
|
||||
<div class="workflow-card-list workflow-batch-carousel" id="workflowCardList" data-active-index="0">
|
||||
{% for batch in workflow_cards %}
|
||||
<article
|
||||
class="workflow-card{% if forloop.first %} active{% endif %}"
|
||||
data-batch-id="{{ batch.id }}"
|
||||
data-workflow-type="{{ batch.workflow_type }}"
|
||||
data-workflow-index="{{ forloop.counter0 }}"
|
||||
aria-hidden="{% if forloop.first %}false{% else %}true{% endif %}"
|
||||
>
|
||||
<header>
|
||||
<strong>{{ batch.batch_no }}</strong>
|
||||
<span class="workflow-status status-{{ batch.status }}">{{ batch.status }}</span>
|
||||
</header>
|
||||
{% if batch.risk_label %}
|
||||
<p class="workflow-risk-summary">{{ batch.risk_label }}</p>
|
||||
{% endif %}
|
||||
{% if batch.workflow_type == "regulatory_review" %}
|
||||
<div class="workflow-card-actions">
|
||||
<button
|
||||
type="button"
|
||||
data-rectification-action="full-review"
|
||||
data-batch-no="{{ batch.batch_no }}"
|
||||
>整包复核</button>
|
||||
<button
|
||||
type="button"
|
||||
data-rectification-action="issue-review"
|
||||
data-batch-no="{{ batch.batch_no }}"
|
||||
>缺失项复核</button>
|
||||
</div>
|
||||
<p class="workflow-record-summary">
|
||||
通知 {{ batch.notification_count|default:0 }} · 复核记录 {{ batch.review_record_count|default:0 }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if batch.error_message %}
|
||||
<p class="workflow-error">{{ batch.error_message }}</p>
|
||||
{% endif %}
|
||||
<ol>
|
||||
{% for node in batch.nodes %}
|
||||
<li class="node-status status-{{ node.status }}" data-node-code="{{ node.node_code }}">
|
||||
<div>
|
||||
<span>{{ node.node_name }}</span>
|
||||
{% if node.message %}<small>{{ node.message }}</small>{% endif %}
|
||||
</div>
|
||||
<em>{{ node.progress }}%</em>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</article>
|
||||
{% empty %}
|
||||
<div class="panel-empty">暂无工作流</div>
|
||||
{% endfor %}
|
||||
{% if workflow_cards %}
|
||||
<div class="workflow-batch-controls">
|
||||
<button type="button" class="workflow-batch-btn" data-workflow-action="prev" aria-label="上一个工作流">‹</button>
|
||||
<div class="workflow-batch-dots" aria-label="工作流批次">
|
||||
{% for batch in workflow_cards %}
|
||||
<button
|
||||
type="button"
|
||||
class="workflow-batch-dot{% if forloop.first %} active{% endif %}"
|
||||
data-workflow-index-dot="{{ forloop.counter0 }}"
|
||||
aria-label="查看{{ batch.batch_no }}状态"
|
||||
aria-current="{% if forloop.first %}true{% else %}false{% endif %}"
|
||||
></button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<button type="button" class="workflow-batch-btn" data-workflow-action="next" aria-label="下一个工作流">›</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</section>
|
||||
</aside>
|
||||
</section>
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.2.6/dist/purify.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked@15.0.12/marked.min.js"></script>
|
||||
<script src="{% static 'js/app.js' %}?v=20260608-chat-delete1"></script>
|
||||
{% endblock %}
|
||||
219
templates/knowledge_base.html
Normal file
219
templates/knowledge_base.html
Normal file
@@ -0,0 +1,219 @@
|
||||
{% 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" 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>
|
||||
</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="knowledge-page"
|
||||
data-document-url="{% url 'knowledge_base_document_list' %}"
|
||||
data-search-url="{% url 'knowledge_base_search' %}"
|
||||
data-rebuild-url="{% url 'knowledge_base_rebuild_index' %}"
|
||||
>
|
||||
<header class="attachment-manager-hero attachment-manager-toolbar">
|
||||
<div>
|
||||
<p class="eyebrow">知识库管理</p>
|
||||
<h1>知识库管理</h1>
|
||||
<p>管理当前账号所有对话可调用的法规、制度、模板和审查依据。</p>
|
||||
</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 'chat' %}">返回对话</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="attachment-manager-content attachment-manager-split knowledge-workbench">
|
||||
<aside class="knowledge-left-rail">
|
||||
<section class="attachment-manager-panel knowledge-panel knowledge-upload-panel">
|
||||
<div class="summary-subheading">
|
||||
<h3>上传知识</h3>
|
||||
<span>所有对话可调用</span>
|
||||
</div>
|
||||
<form class="knowledge-document-form" id="knowledgeDocumentForm">
|
||||
{% csrf_token %}
|
||||
<div
|
||||
class="upload-dropzone manager-upload-dropzone knowledge-upload-dropzone"
|
||||
id="knowledgeUploadDropzone"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
aria-controls="knowledgeDocumentFile"
|
||||
>
|
||||
<input id="knowledgeDocumentFile" name="file" type="file" required hidden>
|
||||
<strong>点击选择文件,或拖拽到这里</strong>
|
||||
<span>支持 doc、docx、xls、xlsx、ppt、pptx、pdf、txt、md</span>
|
||||
</div>
|
||||
<div class="knowledge-inline-actions">
|
||||
<label class="knowledge-checkbox">
|
||||
<input name="is_active" type="checkbox" checked>
|
||||
<span>上传后启用</span>
|
||||
</label>
|
||||
<button type="submit">上传并解析</button>
|
||||
</div>
|
||||
<p class="upload-status" id="knowledgeDocumentStatus">上传后会进入当前账号的全局知识库。</p>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="attachment-manager-panel knowledge-panel knowledge-parse-panel">
|
||||
<div class="summary-subheading">
|
||||
<h3>解析与索引</h3>
|
||||
<span class="knowledge-status status-{{ knowledge_base.status.code }}">{{ knowledge_base.status.label }}</span>
|
||||
</div>
|
||||
<dl class="knowledge-compact-stats">
|
||||
<div>
|
||||
<dt>向量片段</dt>
|
||||
<dd>{{ knowledge_base.collection.count }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>用户材料</dt>
|
||||
<dd>{{ knowledge_base.managed_document_count|default:0 }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>内置法规</dt>
|
||||
<dd>{{ knowledge_base.source_count }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<p class="knowledge-panel-note">{{ knowledge_base.status.message }}</p>
|
||||
<p class="upload-status" id="knowledgeRebuildStatus"></p>
|
||||
<div class="knowledge-form-actions">
|
||||
<button type="button" onclick="window.location.reload()">刷新状态</button>
|
||||
<button type="button" id="knowledgeRebuildIndexButton">重建索引</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="attachment-manager-panel knowledge-panel knowledge-search-panel">
|
||||
<div class="summary-subheading">
|
||||
<h3>RAG 检索测试</h3>
|
||||
<span>Top 3</span>
|
||||
</div>
|
||||
<form class="knowledge-search-form" id="knowledgeSearchForm">
|
||||
{% csrf_token %}
|
||||
<label class="sr-only" for="knowledgeSearchQuery">检索问题</label>
|
||||
<input id="knowledgeSearchQuery" name="query" type="search" placeholder="输入审查问题或关键词">
|
||||
<button type="submit">测试检索</button>
|
||||
</form>
|
||||
<div class="knowledge-search-results" id="knowledgeSearchResults">
|
||||
<p class="panel-empty">输入问题后查看命中材料、依据片段和相似度。</p>
|
||||
</div>
|
||||
</section>
|
||||
</aside>
|
||||
|
||||
<section class="knowledge-right-display">
|
||||
<section class="attachment-manager-panel knowledge-panel knowledge-document-list-panel">
|
||||
<div class="summary-subheading">
|
||||
<h3>知识库材料列表</h3>
|
||||
<input class="attachment-search" id="knowledgeDocumentSearch" type="search" placeholder="搜索文件名">
|
||||
</div>
|
||||
<div class="attachment-table-wrap">
|
||||
<table class="attachment-table knowledge-document-table" id="knowledgeDocumentTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>状态</th>
|
||||
<th>材料名称</th>
|
||||
<th>文件名</th>
|
||||
<th>大小</th>
|
||||
<th>入库状态</th>
|
||||
<th>更新时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for document in knowledge_base.managed_documents %}
|
||||
<tr
|
||||
data-document-id="{{ document.id }}"
|
||||
data-detail-url="/api/review-agent/knowledge-base/documents/{{ document.id }}/"
|
||||
data-index-url="/api/review-agent/knowledge-base/documents/{{ document.id }}/index/"
|
||||
>
|
||||
<td>{% if document.is_active %}启用{% else %}停用{% endif %}</td>
|
||||
<td class="attachment-name">{{ document.display_name }}</td>
|
||||
<td>{{ document.original_name }}</td>
|
||||
<td>{{ document.file_size }} bytes</td>
|
||||
<td>{{ document.indexed_label }}</td>
|
||||
<td>{{ document.updated_at|slice:":19" }}</td>
|
||||
<td class="attachment-actions">
|
||||
<button type="button" data-kb-action="index">解析入库</button>
|
||||
<button type="button" data-kb-action="edit">编辑</button>
|
||||
<button type="button" data-kb-action="toggle">{% if document.is_active %}停用{% else %}启用{% endif %}</button>
|
||||
<button type="button" data-kb-action="delete">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="7" class="table-empty">当前知识库暂无材料</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="attachment-manager-panel knowledge-panel knowledge-source-panel">
|
||||
<div class="summary-subheading">
|
||||
<h3>内置法规材料</h3>
|
||||
<input class="attachment-search" id="knowledgeSourceSearch" type="search" placeholder="搜索内置材料">
|
||||
</div>
|
||||
<div class="attachment-table-wrap">
|
||||
<table class="attachment-table knowledge-source-table" id="knowledgeSourceTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>状态</th>
|
||||
<th>文件</th>
|
||||
<th>类型</th>
|
||||
<th>大小</th>
|
||||
<th>索引</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for source in knowledge_base.sources %}
|
||||
<tr data-source-name="{{ source.name }}">
|
||||
<td>{% if source.supported %}可解析{% else %}暂不支持{% endif %}</td>
|
||||
<td class="attachment-name">{{ source.relative_path }}</td>
|
||||
<td>{{ source.suffix }}</td>
|
||||
<td>{{ source.size }} bytes</td>
|
||||
<td>{{ source.indexed_label }}</td>
|
||||
<td class="attachment-actions">
|
||||
<button type="button" data-source-action="index">手动入库</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="6" class="table-empty">暂无法规材料</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{% static 'js/knowledge_base.js' %}?v=20260608-kb6"></script>
|
||||
{% endblock %}
|
||||
30
templates/registration/login.html
Normal file
30
templates/registration/login.html
Normal file
@@ -0,0 +1,30 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}登录 - DEMO-AGENT V2{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main class="login-page">
|
||||
<section class="login-card" aria-labelledby="login-title">
|
||||
<p class="eyebrow">DEMO-AGENT V2</p>
|
||||
<h1 id="login-title">登录系统</h1>
|
||||
<p class="muted">请输入账号和密码进入 Django 基础后台。</p>
|
||||
|
||||
{% if form.errors %}
|
||||
<div class="alert" role="alert">用户名或密码不正确,请重新输入。</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" novalidate>
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
|
||||
<label for="{{ form.username.id_for_label }}">用户名</label>
|
||||
{{ form.username }}
|
||||
|
||||
<label for="{{ form.password.id_for_label }}">密码</label>
|
||||
{{ form.password }}
|
||||
|
||||
<button class="button" type="submit">登录</button>
|
||||
</form>
|
||||
</section>
|
||||
</main>
|
||||
{% endblock %}
|
||||
31
templates/registration/password_change.html
Normal file
31
templates/registration/password_change.html
Normal file
@@ -0,0 +1,31 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}修改密码 - DEMO-AGENT V2{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main class="login-page">
|
||||
<section class="login-card">
|
||||
<p class="eyebrow">审核智能体</p>
|
||||
<h1>修改密码</h1>
|
||||
<p class="muted">输入当前密码,并设置新的登录密码。</p>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert" role="alert">{{ form.non_field_errors }}</div>
|
||||
{% endif %}
|
||||
|
||||
{% for field in form %}
|
||||
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
|
||||
{{ field }}
|
||||
{% if field.errors %}
|
||||
<div class="alert" role="alert">{{ field.errors }}</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<button class="button" type="submit">保存密码</button>
|
||||
</form>
|
||||
</section>
|
||||
</main>
|
||||
{% endblock %}
|
||||
173
templates/workbench.html
Normal file
173
templates/workbench.html
Normal 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 %}
|
||||
Reference in New Issue
Block a user