feat: 完善 Web UI 细节
新增页面:
- session_detail.html - 会话详情独立页面
- channel_edit.html - 频道配置编辑独立页面
- 错误页面 (401/403/404/500)
功能优化:
- 添加错误处理器支持 HTML 响应
- 更新编辑按钮跳转独立页面
- 完善暗黑主题支持
作者: 小黑 🐶
This commit is contained in:
187
app/templates/monitor/session_detail.html
Normal file
187
app/templates/monitor/session_detail.html
Normal file
@@ -0,0 +1,187 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}会话详情 - 智队中枢{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
{# 页面头部 #}
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
<a href="{{ url_for('web.sessions') }}" class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
|
||||
</svg>
|
||||
</a>
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold dark:text-white">会话详情</h1>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">ID: {{ session.id }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
{% if session.status == 'active' %}
|
||||
<button onclick="closeSession()" class="btn btn-danger flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
关闭会话
|
||||
</button>
|
||||
{% endif %}
|
||||
<button onclick="exportSession()" class="btn btn-secondary flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path>
|
||||
</svg>
|
||||
导出
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# 会话信息卡片 #}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div class="card p-4">
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mb-1">用户</div>
|
||||
<div class="font-medium dark:text-white flex items-center gap-2">
|
||||
<div class="w-8 h-8 bg-primary-100 dark:bg-primary-900/30 rounded-full flex items-center justify-center">
|
||||
<span class="text-primary-600 dark:text-primary-400 text-sm font-medium">
|
||||
{{ session.user.username[0] if session.user else '?' }}
|
||||
</span>
|
||||
</div>
|
||||
{{ session.user.username if session.user else '匿名用户' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card p-4">
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mb-1">主 Agent</div>
|
||||
<div class="font-medium dark:text-white">
|
||||
{{ session.primary_agent.name if session.primary_agent else '未分配' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card p-4">
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mb-1">状态</div>
|
||||
<div>
|
||||
{% if session.status == 'active' %}
|
||||
<span class="badge badge-success flex items-center gap-1 w-fit">
|
||||
<span class="w-2 h-2 bg-green-500 rounded-full animate-pulse"></span>
|
||||
活跃
|
||||
</span>
|
||||
{% elif session.status == 'paused' %}
|
||||
<span class="badge badge-warning w-fit">暂停</span>
|
||||
{% else %}
|
||||
<span class="badge badge-danger w-fit">已关闭</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card p-4">
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mb-1">消息数</div>
|
||||
<div class="text-2xl font-bold dark:text-white">{{ session.message_count }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# 时间线 #}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2 class="text-lg font-semibold dark:text-white">时间线</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="flex items-center gap-8 text-sm">
|
||||
<div>
|
||||
<div class="text-gray-500 dark:text-gray-400">创建时间</div>
|
||||
<div class="font-medium dark:text-white">{{ session.created_at.strftime('%Y-%m-%d %H:%M:%S') if session.created_at else '-' }}</div>
|
||||
</div>
|
||||
<div class="w-px h-8 bg-gray-200 dark:bg-gray-700"></div>
|
||||
<div>
|
||||
<div class="text-gray-500 dark:text-gray-400">最后活跃</div>
|
||||
<div class="font-medium dark:text-white">{{ session.last_active_at.strftime('%Y-%m-%d %H:%M:%S') if session.last_active_at else '-' }}</div>
|
||||
</div>
|
||||
<div class="w-px h-8 bg-gray-200 dark:bg-gray-700"></div>
|
||||
<div>
|
||||
<div class="text-gray-500 dark:text-gray-400">更新时间</div>
|
||||
<div class="font-medium dark:text-white">{{ session.updated_at.strftime('%Y-%m-%d %H:%M:%S') if session.updated_at else '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# 消息记录 #}
|
||||
<div class="card">
|
||||
<div class="card-header flex items-center justify-between">
|
||||
<h2 class="text-lg font-semibold dark:text-white">消息记录</h2>
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">共 {{ messages|length }} 条</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if messages %}
|
||||
<div class="space-y-4 max-h-[600px] overflow-auto pr-2" id="messages-container">
|
||||
{% for msg in messages %}
|
||||
<div class="flex {{ 'justify-end' if msg.sender_type == 'agent' else 'justify-start' }}">
|
||||
<div class="max-w-[80%] {{ 'bg-primary-100 dark:bg-primary-900/30 text-gray-900 dark:text-gray-100' if msg.sender_type == 'agent' else 'bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-gray-100' }} rounded-2xl px-4 py-3">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="text-xs font-medium {{ 'text-primary-700 dark:text-primary-400' if msg.sender_type == 'agent' else 'text-gray-600 dark:text-gray-400' }}">
|
||||
{{ 'Agent' if msg.sender_type == 'agent' else ('用户' if msg.sender_type == 'user' else '系统') }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-400">{{ msg.created_at.strftime('%H:%M:%S') if msg.created_at else '' }}</span>
|
||||
{% if msg.status == 'sent' %}
|
||||
<svg class="w-3 h-3 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
{% elif msg.status == 'delivered' %}
|
||||
<svg class="w-3 h-3 text-primary-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7m0 0l-4-4m4 4l-4-4"></path>
|
||||
</svg>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="text-sm whitespace-pre-wrap">{{ msg.content }}</div>
|
||||
{% if msg.message_type == 'media' %}
|
||||
<div class="mt-2">
|
||||
<span class="inline-flex items-center gap-1 text-xs text-gray-500 bg-gray-200 dark:bg-gray-600 px-2 py-1 rounded">
|
||||
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
媒体文件
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-12">
|
||||
<div class="w-16 h-16 bg-gray-100 dark:bg-gray-700 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<svg class="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<p class="text-gray-500 dark:text-gray-400">暂无消息</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function closeSession() {
|
||||
if (!confirm('确定要关闭此会话吗?此操作不可撤销。')) return;
|
||||
|
||||
fetch(`/api/web/sessions/{{ session.id }}/close`, { method: 'PUT' })
|
||||
.then(res => {
|
||||
if (res.ok) {
|
||||
showToast('会话已关闭', 'success');
|
||||
setTimeout(() => location.reload(), 1000);
|
||||
} else {
|
||||
showToast('关闭失败', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function exportSession() {
|
||||
window.open(`/api/sessions/{{ session.id }}/messages?format=json`, '_blank');
|
||||
showToast('开始导出...', 'info');
|
||||
}
|
||||
|
||||
// 自动滚动到底部
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const container = document.getElementById('messages-container');
|
||||
if (container) {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user