ai-robot-channel/target/classes/static/chat-history.html

456 lines
16 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>聊天记录查询</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: #f5f5f5;
min-height: 100vh;
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.header {
background: #1890ff;
color: white;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
}
.header h1 {
font-size: 24px;
margin-bottom: 8px;
}
.header p {
opacity: 0.8;
font-size: 14px;
}
.filters {
background: white;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
display: flex;
gap: 20px;
flex-wrap: wrap;
align-items: center;
}
.filter-group {
display: flex;
align-items: center;
gap: 8px;
}
.filter-group label {
font-weight: 500;
color: #333;
}
select, input {
padding: 8px 12px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 14px;
min-width: 200px;
}
select:focus, input:focus {
outline: none;
border-color: #1890ff;
}
.btn {
padding: 8px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.3s;
}
.btn-primary {
background: #1890ff;
color: white;
}
.btn-primary:hover {
background: #40a9ff;
}
.btn-secondary {
background: #f0f0f0;
color: #333;
}
.btn-secondary:hover {
background: #d9d9d9;
}
.main-content {
display: grid;
grid-template-columns: 350px 1fr;
gap: 20px;
}
.session-list {
background: white;
border-radius: 8px;
overflow: hidden;
}
.session-list-header {
padding: 15px 20px;
background: #fafafa;
border-bottom: 1px solid #f0f0f0;
font-weight: 600;
}
.session-item {
padding: 15px 20px;
border-bottom: 1px solid #f0f0f0;
cursor: pointer;
transition: background 0.2s;
}
.session-item:hover {
background: #f5f5f5;
}
.session-item.active {
background: #e6f7ff;
border-left: 3px solid #1890ff;
}
.session-item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.session-customer-id {
font-weight: 500;
color: #333;
font-size: 14px;
}
.session-status {
font-size: 12px;
padding: 2px 8px;
border-radius: 10px;
}
.status-AI { background: #e6f7ff; color: #1890ff; }
.status-PENDING { background: #fff7e6; color: #fa8c16; }
.status-MANUAL { background: #f6ffed; color: #52c41a; }
.status-CLOSED { background: #f5f5f5; color: #999; }
.session-meta {
font-size: 12px;
color: #999;
}
.chat-panel {
background: white;
border-radius: 8px;
display: flex;
flex-direction: column;
min-height: 600px;
}
.chat-header {
padding: 15px 20px;
background: #fafafa;
border-bottom: 1px solid #f0f0f0;
display: flex;
justify-content: space-between;
align-items: center;
}
.chat-header-info {
font-size: 14px;
color: #666;
}
.chat-messages {
flex: 1;
padding: 20px;
overflow-y: auto;
background: #fafafa;
}
.message {
margin-bottom: 16px;
display: flex;
flex-direction: column;
}
.message.customer {
align-items: flex-start;
}
.message.ai, .message.manual {
align-items: flex-end;
}
.message-sender {
font-size: 12px;
color: #999;
margin-bottom: 4px;
}
.message-content {
max-width: 70%;
padding: 10px 15px;
border-radius: 8px;
font-size: 14px;
line-height: 1.5;
}
.message.customer .message-content {
background: white;
border: 1px solid #e8e8e8;
}
.message.ai .message-content {
background: #e6f7ff;
border: 1px solid #91d5ff;
}
.message.manual .message-content {
background: #f6ffed;
border: 1px solid #b7eb8f;
}
.message-time {
font-size: 11px;
color: #bbb;
margin-top: 4px;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: #999;
}
.empty-state svg {
width: 80px;
height: 80px;
margin-bottom: 16px;
opacity: 0.5;
}
.loading {
text-align: center;
padding: 20px;
color: #999;
}
.no-sessions {
text-align: center;
padding: 40px;
color: #999;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>聊天记录查询</h1>
<p>查看各客服账号的历史聊天记录</p>
</div>
<div class="filters">
<div class="filter-group">
<label>客服账号:</label>
<select id="kfAccountSelect">
<option value="">请选择客服账号</option>
</select>
</div>
<div class="filter-group">
<label>会话状态:</label>
<select id="statusSelect">
<option value="all">全部</option>
<option value="AI">AI接待中</option>
<option value="PENDING">待接入</option>
<option value="MANUAL">人工接待中</option>
<option value="CLOSED">已结束</option>
</select>
</div>
<button class="btn btn-primary" onclick="loadSessions()">查询会话</button>
<button class="btn btn-secondary" onclick="refreshKfAccounts()">刷新账号</button>
</div>
<div class="main-content">
<div class="session-list">
<div class="session-list-header">
会话列表 (<span id="sessionCount">0</span>)
</div>
<div id="sessionListContainer">
<div class="no-sessions">请选择客服账号并查询</div>
</div>
</div>
<div class="chat-panel">
<div class="chat-header">
<div class="chat-header-info" id="chatHeaderInfo">请选择会话查看聊天记录</div>
</div>
<div class="chat-messages" id="chatMessagesContainer">
<div class="empty-state">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path 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"/>
</svg>
<p>选择左侧会话查看聊天记录</p>
</div>
</div>
</div>
</div>
</div>
<script>
let currentSessionId = null;
async function refreshKfAccounts() {
try {
const response = await fetch('/chat-history/api/kf-accounts');
const result = await response.json();
if (result.code === 0) {
const select = document.getElementById('kfAccountSelect');
select.innerHTML = '<option value="">请选择客服账号</option>';
result.data.forEach(account => {
const option = document.createElement('option');
option.value = account.openKfId;
option.textContent = account.name || account.openKfId;
select.appendChild(option);
});
if (result.data.length === 0) {
alert('未获取到客服账号,请检查配置');
}
} else {
alert('获取客服账号失败: ' + result.message);
}
} catch (error) {
console.error('获取客服账号失败:', error);
alert('获取客服账号失败,请检查网络连接');
}
}
async function loadSessions() {
const kfId = document.getElementById('kfAccountSelect').value;
const status = document.getElementById('statusSelect').value;
if (!kfId) {
alert('请选择客服账号');
return;
}
const container = document.getElementById('sessionListContainer');
container.innerHTML = '<div class="loading">加载中...</div>';
try {
const response = await fetch(`/chat-history/api/sessions?openKfId=${encodeURIComponent(kfId)}&status=${status}`);
const result = await response.json();
if (result.code === 0) {
document.getElementById('sessionCount').textContent = result.data.length;
renderSessionList(result.data);
} else {
container.innerHTML = `<div class="no-sessions">查询失败: ${result.message}</div>`;
}
} catch (error) {
console.error('加载会话列表失败:', error);
container.innerHTML = '<div class="no-sessions">加载失败,请重试</div>';
}
}
function renderSessionList(sessions) {
const container = document.getElementById('sessionListContainer');
if (sessions.length === 0) {
container.innerHTML = '<div class="no-sessions">暂无会话记录</div>';
return;
}
container.innerHTML = sessions.map(session => `
<div class="session-item" data-session-id="${session.sessionId}" onclick="selectSession('${session.sessionId}')">
<div class="session-item-header">
<span class="session-customer-id">${session.customerId.substring(0, 15)}...</span>
<span class="session-status status-${session.status}">${getStatusText(session.status)}</span>
</div>
<div class="session-meta">
消息: ${session.messageCount} 条 | ${formatTime(session.updatedAt)}
</div>
</div>
`).join('');
}
function getStatusText(status) {
const map = {
'AI': 'AI接待',
'PENDING': '待接入',
'MANUAL': '人工接待',
'CLOSED': '已结束'
};
return map[status] || status;
}
function formatTime(timeStr) {
if (!timeStr) return '-';
const date = new Date(timeStr);
return `${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${String(date.getMinutes()).padStart(2, '0')}`;
}
async function selectSession(sessionId) {
currentSessionId = sessionId;
document.querySelectorAll('.session-item').forEach(item => {
item.classList.remove('active');
});
document.querySelector(`[data-session-id="${sessionId}"]`).classList.add('active');
const container = document.getElementById('chatMessagesContainer');
container.innerHTML = '<div class="loading">加载中...</div>';
try {
const response = await fetch(`/chat-history/api/messages?sessionId=${encodeURIComponent(sessionId)}`);
const result = await response.json();
if (result.code === 0) {
document.getElementById('chatHeaderInfo').textContent =
`会话ID: ${sessionId} | 消息数: ${result.data.length}`;
renderMessages(result.data);
} else {
container.innerHTML = `<div class="no-sessions">加载失败: ${result.message}</div>`;
}
} catch (error) {
console.error('加载消息失败:', error);
container.innerHTML = '<div class="no-sessions">加载失败,请重试</div>';
}
}
function renderMessages(messages) {
const container = document.getElementById('chatMessagesContainer');
if (messages.length === 0) {
container.innerHTML = '<div class="empty-state"><p>暂无消息记录</p></div>';
return;
}
container.innerHTML = messages.map(msg => {
const senderName = getSenderName(msg.senderType, msg.senderId);
return `
<div class="message ${msg.senderType}">
<div class="message-sender">${senderName}</div>
<div class="message-content">${escapeHtml(msg.content)}</div>
<div class="message-time">${formatTime(msg.createdAt)}</div>
</div>
`;
}).join('');
container.scrollTop = container.scrollHeight;
}
function getSenderName(senderType, senderId) {
switch (senderType) {
case 'customer': return '客户';
case 'ai': return 'AI助手';
case 'manual': return `客服(${senderId || '未知'})`;
default: return senderType;
}
}
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML.replace(/\n/g, '<br>');
}
document.addEventListener('DOMContentLoaded', function() {
refreshKfAccounts();
});
</script>
</body>
</html>