Files
DEMO-AGENT/templates/home.html

367 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% 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="/" role="tab" aria-selected="false">首页</a>
<a class="tab active" href="/" 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="/?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="/" 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>
</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-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="上一个工作流">&lsaquo;</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="下一个工作流">&rsaquo;</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 %}