feat(chat): refine workspace chat layout
This commit is contained in:
@@ -376,7 +376,7 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="page">
|
||||
<main class="page {% block page_class %}{% endblock %}">
|
||||
{% if messages %}
|
||||
<div class="stack">
|
||||
{% for message in messages %}
|
||||
|
||||
@@ -1,16 +1,359 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}审核智能体{% endblock %}
|
||||
{% block page_class %}chat-page{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="page-header">
|
||||
<style>
|
||||
.chat-page {
|
||||
width: min(100vw - 32px, 1920px);
|
||||
margin-top: 12px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.chat-hero {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
min-height: 74px;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
.chat-hero .page-title {
|
||||
font-size: 1.45rem;
|
||||
}
|
||||
|
||||
.chat-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 280px minmax(680px, 1fr) 340px;
|
||||
gap: 12px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.history-toggle {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.chat-layout:has(.history-toggle:checked) {
|
||||
grid-template-columns: 54px minmax(760px, 1fr) 340px;
|
||||
}
|
||||
|
||||
.chat-layout:has(.history-toggle:checked) .history-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chat-layout:has(.history-toggle:checked) .history-panel {
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
.chat-layout:has(.history-toggle:checked) .history-collapse {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.chat-layout:has(.history-toggle:checked) .history-collapse span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.history-panel,
|
||||
.right-rail {
|
||||
position: sticky;
|
||||
top: 84px;
|
||||
}
|
||||
|
||||
.history-head,
|
||||
.right-rail-head,
|
||||
.chat-head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.history-collapse {
|
||||
flex: none;
|
||||
min-height: 34px;
|
||||
border-radius: 8px;
|
||||
font-size: 0.86rem;
|
||||
background: var(--surface-soft);
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.history-list {
|
||||
max-height: calc(100vh - 230px);
|
||||
overflow: auto;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
||||
.history-card {
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.chat-shell {
|
||||
min-height: calc(100vh - 190px);
|
||||
display: grid;
|
||||
grid-template-rows: auto minmax(360px, 1fr) auto;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chat-head {
|
||||
padding: 16px 18px 12px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.node-strip {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 2px;
|
||||
max-width: 52vw;
|
||||
}
|
||||
|
||||
.chat-thread {
|
||||
display: grid;
|
||||
align-content: start;
|
||||
gap: 18px;
|
||||
padding: 22px min(7vw, 72px);
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.chat-message {
|
||||
display: grid;
|
||||
grid-template-columns: 34px minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
max-width: 980px;
|
||||
}
|
||||
|
||||
.chat-message.user {
|
||||
justify-self: end;
|
||||
grid-template-columns: minmax(0, 1fr) 34px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 10px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 700;
|
||||
font-size: 0.82rem;
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.chat-message.user .avatar {
|
||||
grid-column: 2;
|
||||
background: #111827;
|
||||
}
|
||||
|
||||
.bubble {
|
||||
padding: 13px 15px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
line-height: 1.75;
|
||||
box-shadow: 0 4px 14px rgba(31, 45, 61, 0.04);
|
||||
}
|
||||
|
||||
.chat-message.user .bubble {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
background: #eef4ff;
|
||||
border-color: #d8e5ff;
|
||||
}
|
||||
|
||||
.bubble-meta {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
color: var(--muted);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.prompt-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.composer {
|
||||
padding: 14px min(7vw, 72px) 18px;
|
||||
border-top: 1px solid var(--border);
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
}
|
||||
|
||||
.composer-box {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
max-width: 1040px;
|
||||
margin: 0 auto;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 16px;
|
||||
background: #fff;
|
||||
padding: 10px;
|
||||
box-shadow: 0 10px 28px rgba(31, 45, 61, 0.08);
|
||||
}
|
||||
|
||||
.composer-box label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.composer-box textarea {
|
||||
height: 86px;
|
||||
min-height: 78px;
|
||||
max-height: 180px;
|
||||
border: 0;
|
||||
padding: 8px 10px;
|
||||
resize: vertical;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.composer-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
border-top: 1px solid #edf1f5;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.scope-summary {
|
||||
color: var(--muted);
|
||||
font-size: 0.86rem;
|
||||
}
|
||||
|
||||
.document-scope {
|
||||
display: none;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
background: var(--surface-soft);
|
||||
}
|
||||
|
||||
.composer-box:focus-within .document-scope {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.send-button {
|
||||
border-radius: 999px;
|
||||
min-width: 92px;
|
||||
}
|
||||
|
||||
.result-panel {
|
||||
padding: 14px 18px;
|
||||
}
|
||||
|
||||
.upload-card {
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
|
||||
}
|
||||
|
||||
.upload-dropzone {
|
||||
position: relative;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
min-height: 164px;
|
||||
padding: 18px;
|
||||
border: 1.5px dashed #9fb7e8;
|
||||
border-radius: 12px;
|
||||
background: #f4f8ff;
|
||||
text-align: center;
|
||||
color: var(--text);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.upload-dropzone input[type="file"] {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.upload-icon {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 12px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 10px;
|
||||
background: #ffffff;
|
||||
color: var(--primary);
|
||||
box-shadow: 0 6px 18px rgba(47, 111, 236, 0.14);
|
||||
font-size: 1.3rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.upload-dropzone strong {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.rail-card {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.status-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.status-tile {
|
||||
min-height: 74px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 10px;
|
||||
background: var(--surface-soft);
|
||||
}
|
||||
|
||||
.status-tile strong {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.chat-layout,
|
||||
.chat-layout:has(.history-toggle:checked) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.history-panel,
|
||||
.right-rail {
|
||||
position: static;
|
||||
}
|
||||
.node-strip {
|
||||
max-width: 100%;
|
||||
}
|
||||
.chat-thread,
|
||||
.composer {
|
||||
padding-left: 18px;
|
||||
padding-right: 18px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<section class="page-header chat-hero">
|
||||
<div>
|
||||
<span class="eyebrow">Agent Workspace</span>
|
||||
<h1 class="page-title">审核智能体</h1>
|
||||
<p class="page-lead">以会话为中心组织资料包上传、节点式审核结果和动态任务信息卡。</p>
|
||||
<p class="page-lead">围绕会话完成知识库问答、资料包上传、节点审核和导出留痕。</p>
|
||||
</div>
|
||||
{% if conversation %}
|
||||
<div class="badge-row">
|
||||
<span class="pill pill-accent">批次:{{ conversation.batch_id }}</span>
|
||||
<span class="pill">产品:{{ conversation.product_name|default:"未识别产品名称" }}</span>
|
||||
<span class="pill pill-accent">批次:{{ conversation.batch_id|default:"未绑定" }}</span>
|
||||
<span class="pill">产品:{{ conversation.product_name|default:"知识库问答" }}</span>
|
||||
<span class="pill">阶段:{{ conversation_context.task_status }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -43,14 +386,20 @@
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<section class="workspace-grid" style="grid-template-columns: 320px minmax(0, 1fr) 360px;">
|
||||
<div class="stack">
|
||||
<article class="panel">
|
||||
<section class="chat-layout">
|
||||
<input class="history-toggle" id="history-collapsed" type="checkbox">
|
||||
<aside class="panel history-panel">
|
||||
<div class="history-head">
|
||||
<div class="history-content">
|
||||
<h2 class="section-title">会话历史</h2>
|
||||
<p class="section-copy">左侧保留历史会话,标题默认使用解析后的产品名称。</p>
|
||||
<ul class="detail-list">
|
||||
<p class="section-copy">按会话快速切换资料包和知识库问答。</p>
|
||||
</div>
|
||||
<label class="button history-collapse" for="history-collapsed" title="收起或展开会话历史">‹ <span>收起</span></label>
|
||||
</div>
|
||||
<div class="history-content">
|
||||
<ul class="detail-list history-list">
|
||||
{% for item in conversation_history %}
|
||||
<li class="detail-item">
|
||||
<li class="detail-item history-card">
|
||||
<strong><a href="{% url 'chat:detail' item.conversation_id %}">{{ item.title }}</a></strong>
|
||||
<div class="muted">产品:{{ item.product_name|default:"未识别" }}</div>
|
||||
<div class="muted">批次:{{ item.batch_id|default:"未绑定" }}</div>
|
||||
@@ -67,34 +416,78 @@
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="stack">
|
||||
<article class="panel">
|
||||
<div class="section-heading">
|
||||
<article class="panel chat-shell">
|
||||
<div class="chat-head">
|
||||
<div>
|
||||
<h2 class="section-title">对话区与节点导航</h2>
|
||||
<p class="section-copy">中间区域承接用户问题、Agent 回答和节点式结果摘要。</p>
|
||||
</div>
|
||||
<p class="section-copy">像聊天一样提问,Agent 会结合知识库和所选文档回答。</p>
|
||||
</div>
|
||||
{% if node_results %}
|
||||
<div class="badge-row" style="margin-bottom: 14px;">
|
||||
<div class="node-strip">
|
||||
{% for node in node_results %}
|
||||
<span class="pill {% if node.status == '已完成' %}pill-success{% else %}pill-signal{% endif %}">{{ node.label }} / {{ node.status }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<form method="post" class="stack" action="{% if conversation %}{% url 'chat:detail' conversation.conversation_id %}{% else %}{% url 'chat:index' %}{% endif %}">
|
||||
</div>
|
||||
|
||||
<div class="chat-thread">
|
||||
{% if conversation %}
|
||||
<div class="chat-message assistant">
|
||||
<div class="avatar">RA</div>
|
||||
<div class="bubble">
|
||||
<span class="bubble-meta">审核智能体</span>
|
||||
当前会话已就绪。可以询问资料目录、法规依据、字段抽取、一致性问题或风险整改建议。
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="chat-message assistant">
|
||||
<div class="avatar">RA</div>
|
||||
<div class="bubble">
|
||||
<span class="bubble-meta">审核智能体</span>
|
||||
可以先不上传资料,直接询问注册法规、资料清单、模板字段或历史知识库内容。
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if form.message.value %}
|
||||
<div class="chat-message user">
|
||||
<div class="avatar">你</div>
|
||||
<div class="bubble">
|
||||
<span class="bubble-meta">用户问题</span>
|
||||
{{ form.message.value|linebreaksbr }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if result %}
|
||||
<div class="chat-message assistant">
|
||||
<div class="avatar">RA</div>
|
||||
<div class="bubble">
|
||||
<span class="bubble-meta">Agent 回答</span>
|
||||
{{ result.answer|linebreaksbr }}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="prompt-chips">
|
||||
{% for item in prompt_templates %}
|
||||
<span class="pill pill-accent">{{ item }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<form method="post" class="composer" action="{% if conversation %}{% url 'chat:detail' conversation.conversation_id %}{% else %}{% url 'chat:index' %}{% endif %}">
|
||||
{% csrf_token %}
|
||||
<div>
|
||||
<div class="composer-box">
|
||||
{{ form.message.label_tag }}
|
||||
{{ form.message }}
|
||||
{% if form.message.errors %}
|
||||
<p class="notice notice-error">{{ form.message.errors|join:" " }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<div class="document-scope">
|
||||
{{ form.document_ids.label_tag }}
|
||||
<div class="checkbox-list">
|
||||
{% for checkbox in form.document_ids %}
|
||||
@@ -107,21 +500,15 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<button type="submit">发送问题</button>
|
||||
<div class="composer-actions">
|
||||
<span class="scope-summary">默认检索知识库;聚焦输入框可选择文档范围。</span>
|
||||
<button class="send-button" type="submit">发送问题</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% if result %}
|
||||
<div class="detail-item" style="margin-top: 16px;">
|
||||
<strong>Agent 回答</strong>
|
||||
<div>{{ result.answer|linebreaksbr }}</div>
|
||||
</div>
|
||||
{% elif not conversation %}
|
||||
<div class="notice" style="margin-top: 16px;">可以先不上传资料,直接询问注册法规、资料清单、模板字段或历史知识库内容。</div>
|
||||
{% endif %}
|
||||
</article>
|
||||
|
||||
<article class="panel">
|
||||
<article class="panel result-panel">
|
||||
<h2 class="section-title">节点式结果</h2>
|
||||
{% if analysis_card or export_card or risk_card or notify_card %}
|
||||
<div class="stack">
|
||||
@@ -391,13 +778,17 @@
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="stack">
|
||||
<article class="panel">
|
||||
<aside class="stack right-rail">
|
||||
<article class="panel upload-card">
|
||||
<div class="right-rail-head">
|
||||
<div>
|
||||
<h2 class="section-title">上传区</h2>
|
||||
<p class="section-copy">右侧保留资料包上传入口和当前会话的资料上下文。</p>
|
||||
<p class="section-copy">拖拽或点击导入资料包。</p>
|
||||
</div>
|
||||
</div>
|
||||
{% if batch %}
|
||||
<ul class="detail-list">
|
||||
<li class="detail-item">
|
||||
<ul class="detail-list" style="margin-bottom: 14px;">
|
||||
<li class="detail-item rail-card">
|
||||
<strong>当前资料包</strong>
|
||||
<div>批次:{{ batch.batch_id }}</div>
|
||||
<div>文件数:{{ batch.file_count }}</div>
|
||||
@@ -407,10 +798,14 @@
|
||||
</ul>
|
||||
<form method="post" action="{% url 'chat:upload-documents' conversation.conversation_id %}" enctype="multipart/form-data" class="stack" style="margin-top: 16px;">
|
||||
{% csrf_token %}
|
||||
<div>
|
||||
{{ upload_form.files.label_tag }}
|
||||
<label class="upload-dropzone">
|
||||
{{ upload_form.files }}
|
||||
</div>
|
||||
<span>
|
||||
<span class="upload-icon">↑</span>
|
||||
<strong>拖拽补充资料到这里</strong>
|
||||
<span class="muted">或点击选择文件,支持多选和压缩包。</span>
|
||||
</span>
|
||||
</label>
|
||||
<div class="button-row">
|
||||
<button type="submit">继续上传资料</button>
|
||||
<a class="button" href="{% url 'documents:list' %}">返回资料包</a>
|
||||
@@ -430,11 +825,14 @@
|
||||
<form method="post" action="{% url 'documents:upload' %}" enctype="multipart/form-data" class="stack" style="margin-top: 16px;">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="scenario_id" value="document_review">
|
||||
<div>
|
||||
{{ upload_form.files.label_tag }}
|
||||
<label class="upload-dropzone">
|
||||
{{ upload_form.files }}
|
||||
<p class="help-text">支持 PDF、DOCX、MD、TXT、ZIP、7Z 与 RAR。上传后会自动形成资料包并绑定新的审核会话。</p>
|
||||
</div>
|
||||
<span>
|
||||
<span class="upload-icon">↑</span>
|
||||
<strong>拖拽资料包到这里</strong>
|
||||
<span class="muted">PDF、DOCX、MD、TXT、ZIP、7Z、RAR 均可。</span>
|
||||
</span>
|
||||
</label>
|
||||
<div class="button-row">
|
||||
<button type="submit">导入资料包</button>
|
||||
<a class="button" href="{% url 'documents:upload' %}">打开导入向导</a>
|
||||
@@ -445,24 +843,26 @@
|
||||
|
||||
<article class="panel">
|
||||
<h2 class="section-title">动态信息卡</h2>
|
||||
<ul class="detail-list">
|
||||
<li class="detail-item">
|
||||
<div class="status-grid">
|
||||
<div class="status-tile">
|
||||
<strong>最高风险等级</strong>
|
||||
<div>{{ workspace_summary.highest_risk_level }}</div>
|
||||
</li>
|
||||
<li class="detail-item">
|
||||
</div>
|
||||
<div class="status-tile">
|
||||
<strong>是否允许正式导出</strong>
|
||||
<div>{{ workspace_summary.export_allowed }}</div>
|
||||
</li>
|
||||
<li class="detail-item">
|
||||
</div>
|
||||
<div class="status-tile">
|
||||
<strong>通知状态</strong>
|
||||
<div>{{ workspace_summary.notify_status }}</div>
|
||||
</li>
|
||||
<li class="detail-item">
|
||||
</div>
|
||||
<div class="status-tile">
|
||||
<strong>导出状态</strong>
|
||||
<div>{{ workspace_summary.export_status }}</div>
|
||||
</li>
|
||||
<li class="detail-item">
|
||||
</div>
|
||||
</div>
|
||||
<ul class="detail-list" style="margin-top: 10px;">
|
||||
<li class="detail-item rail-card">
|
||||
<strong>导出下载地址</strong>
|
||||
<div>
|
||||
{% if workspace_summary.download_url %}
|
||||
@@ -478,6 +878,6 @@
|
||||
{% endif %}
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user