Files
assist/src/web/static/js/modules/conversations.js
Jeason 58b3c615ef refactor: dashboard.js 模块化拆分 从7766行拆为13个独立模块
核心 dashboard.js 缩减至266行,只保留:
- TSPDashboard class 定义(constructor、init、bindEvents、switchTab)
- 分页组件、缓存、状态管理、自动刷新、WebSocket、i18n

功能模块拆分到 modules/ 目录:
- chat.js (207行)  智能对话
- agent.js (424行)  Agent管理
- alerts.js (348行)  预警管理
- knowledge.js (699行)  知识库管理
- workorders.js (约500行)  工单管理
- conversations.js (671行)  对话历史
- monitoring.js (455行)  Token/AI监控
- system.js (1388行)  系统优化+设置+数据分析图表
- tenants.js (165行)  租户管理
- utils.js (573行)  工具函数
- dashboard-home.js (286行)  仪表板首页
- feishu-sync.js (698行)  飞书同步管理器

拆分方式:Object.assign(TSPDashboard.prototype, {...})
this 引用完全不会断,所有模块方法互相调用正常
2026-04-02 15:10:23 +08:00

672 lines
31 KiB
JavaScript

// 对话历史模块
Object.assign(TSPDashboard.prototype, {
async loadConversationTenantList() {
this.conversationCurrentTenantId = null;
this.renderConversationBreadcrumb(null);
// 加载全局统计
this.loadConversationStats(null);
// 显示租户列表容器,隐藏详情容器
const tenantListEl = document.getElementById('conversation-tenant-list');
const tenantDetailEl = document.getElementById('conversation-tenant-detail');
if (tenantListEl) tenantListEl.style.display = '';
if (tenantDetailEl) tenantDetailEl.style.display = 'none';
// 显示加载中 spinner
if (tenantListEl) {
tenantListEl.innerHTML = '<div class="col-12 text-center py-4"><i class="fas fa-spinner fa-spin fa-2x"></i><p class="mt-2 text-muted">加载中...</p></div>';
}
try {
const response = await fetch('/api/conversations/tenants');
const tenants = await response.json();
if (!Array.isArray(tenants) || tenants.length === 0) {
if (tenantListEl) {
tenantListEl.innerHTML = '<div class="col-12 text-center py-4"><i class="fas fa-comments fa-2x text-muted"></i><p class="mt-2 text-muted">暂无对话会话数据</p></div>';
}
return;
}
const cardsHtml = tenants.map(t => {
const lastActive = t.last_active_time ? new Date(t.last_active_time).toLocaleString() : '无';
return `
<div class="col-md-4 col-sm-6 mb-3">
<div class="card h-100 shadow-sm" style="cursor:pointer" onclick="dashboard.loadConversationTenantDetail('${t.tenant_id}')">
<div class="card-body">
<h6 class="card-title"><i class="fas fa-building me-2"></i>${t.tenant_id}</h6>
<div class="d-flex justify-content-between mt-3">
<div>
<small class="text-muted">会话总数</small>
<h5 class="mb-0">${t.session_count}</h5>
</div>
<div>
<small class="text-muted">消息总数</small>
<h5 class="mb-0">${t.message_count}</h5>
</div>
<div>
<small class="text-muted">活跃会话</small>
<h5 class="mb-0 text-success">${t.active_session_count}</h5>
</div>
</div>
<div class="mt-3">
<small class="text-muted"><i class="fas fa-clock me-1"></i>最近活跃: ${lastActive}</small>
</div>
</div>
</div>
</div>
`;
}).join('');
if (tenantListEl) {
tenantListEl.innerHTML = cardsHtml;
}
} catch (error) {
console.error('加载对话租户列表失败:', error);
if (tenantListEl) {
tenantListEl.innerHTML = '<div class="col-12 text-center py-4 text-danger"><i class="fas fa-exclamation-triangle"></i> 加载失败</div>';
}
this.showNotification('加载对话租户列表失败', 'error');
}
},
// Task 7.1: 加载对话历史租户详情视图
async loadConversationTenantDetail(tenantId, page = 1) {
this.conversationCurrentTenantId = tenantId;
this.paginationConfig.currentConversationPage = page;
// 隐藏租户列表,显示详情容器
const tenantListEl = document.getElementById('conversation-tenant-list');
const tenantDetailEl = document.getElementById('conversation-tenant-detail');
if (tenantListEl) tenantListEl.style.display = 'none';
if (tenantDetailEl) tenantDetailEl.style.display = '';
// 渲染面包屑(如果 renderConversationBreadcrumb 已实现)
if (typeof this.renderConversationBreadcrumb === 'function') {
this.renderConversationBreadcrumb(tenantId);
}
// 加载租户级统计(如果 loadConversationStats 已实现)
if (typeof this.loadConversationStats === 'function') {
this.loadConversationStats(tenantId);
}
// 显示加载中 spinner
const sessionListEl = document.getElementById('conversation-session-list');
if (sessionListEl) {
sessionListEl.innerHTML = '<div class="text-center py-4"><i class="fas fa-spinner fa-spin fa-2x"></i><p class="mt-2 text-muted">加载中...</p></div>';
}
try {
const perPage = this.getPageSize('conversation-session-pagination');
const statusFilter = document.getElementById('conversation-status-filter')?.value || '';
const dateFilter = document.getElementById('conversation-detail-date-filter')?.value || '';
let url = `/api/conversations/sessions?tenant_id=${encodeURIComponent(tenantId)}&page=${page}&per_page=${perPage}`;
if (statusFilter) url += `&status=${encodeURIComponent(statusFilter)}`;
if (dateFilter) url += `&date_filter=${encodeURIComponent(dateFilter)}`;
const response = await fetch(url);
const data = await response.json();
if (data.sessions) {
this.renderConversationSessionTable(data.sessions, tenantId);
this.updateConversationTenantPagination(data);
} else {
if (sessionListEl) {
sessionListEl.innerHTML = '<div class="text-center py-4"><i class="fas fa-comments fa-2x text-muted"></i><p class="mt-2 text-muted">暂无会话数据</p></div>';
}
}
} catch (error) {
console.error('加载租户会话列表失败:', error);
if (sessionListEl) {
sessionListEl.innerHTML = '<div class="text-center py-4 text-danger"><i class="fas fa-exclamation-triangle"></i> 加载失败</div>';
}
this.showNotification('加载租户会话列表失败', 'error');
}
},
// Task 7.1: 渲染会话表格
renderConversationSessionTable(sessions, tenantId) {
const sessionListEl = document.getElementById('conversation-session-list');
if (!sessionListEl) return;
if (!sessions || sessions.length === 0) {
sessionListEl.innerHTML = '<div class="text-center py-4"><i class="fas fa-comments fa-2x text-muted"></i><p class="mt-2 text-muted">暂无会话数据</p></div>';
return;
}
const statusBadge = (status) => {
if (status === 'active') return '<span class="badge bg-success">活跃</span>';
if (status === 'ended') return '<span class="badge bg-secondary">已结束</span>';
return `<span class="badge bg-info">${status || '未知'}</span>`;
};
const sourceBadge = (source) => {
if (source === 'feishu') return '<span class="badge bg-purple">飞书</span>';
if (source === 'websocket') return '<span class="badge bg-blue">WebSocket</span>';
if (source === 'api') return '<span class="badge bg-indigo">API</span>';
return `<span class="badge bg-secondary">${source || '未知'}</span>`;
};
const formatTime = (isoStr) => {
if (!isoStr) return '-';
return new Date(isoStr).toLocaleString();
};
const rowsHtml = sessions.map(s => `
<tr style="cursor:pointer" onclick="dashboard.viewSessionMessages('${s.session_id}', '${(s.title || '').replace(/'/g, "\\'")}')">
<td>${s.title || s.session_id || '-'}</td>
<td class="text-center">${s.message_count || 0}</td>
<td class="text-center">${statusBadge(s.status)}</td>
<td class="text-center">${sourceBadge(s.source)}</td>
<td>${formatTime(s.created_at)}</td>
<td>${formatTime(s.updated_at)}</td>
<td class="text-center" onclick="event.stopPropagation()">
<button class="btn btn-outline-danger btn-sm" onclick="dashboard.deleteConversationSession('${s.session_id}')">
<i class="fas fa-trash"></i>
</button>
</td>
</tr>
`).join('');
sessionListEl.innerHTML = `
<div class="table-responsive">
<table class="table table-hover table-striped mb-0">
<thead>
<tr>
<th>会话标题</th>
<th class="text-center">消息数</th>
<th class="text-center">状态</th>
<th class="text-center">来源</th>
<th>创建时间</th>
<th>更新时间</th>
<th class="text-center">操作</th>
</tr>
</thead>
<tbody>
${rowsHtml}
</tbody>
</table>
</div>
`;
},
// Task 7.1: 查看会话消息详情
async viewSessionMessages(sessionId, sessionTitle) {
try {
const response = await fetch(`/api/conversations/sessions/${encodeURIComponent(sessionId)}`);
const data = await response.json();
if (data.success && data.messages) {
// 更新面包屑到第三层(如果已实现)
if (typeof this.renderConversationBreadcrumb === 'function') {
this.renderConversationBreadcrumb(this.conversationCurrentTenantId, sessionTitle || sessionId);
}
this.showSessionMessagesModal(data.session, data.messages);
} else {
throw new Error(data.error || '获取会话消息失败');
}
} catch (error) {
console.error('获取会话消息失败:', error);
this.showNotification('获取会话消息失败: ' + error.message, 'error');
}
},
// Task 7.1: 显示会话消息模态框
showSessionMessagesModal(session, messages) {
const messagesHtml = messages.map(msg => `
<div class="mb-3 p-3 border rounded">
<div class="mb-2">
<strong class="text-primary"><i class="fas fa-user me-1"></i>用户:</strong>
<div class="mt-1">${msg.user_message || ''}</div>
</div>
<div>
<strong class="text-success"><i class="fas fa-robot me-1"></i>助手:</strong>
<div class="mt-1">${msg.assistant_response || ''}</div>
</div>
<div class="mt-2 text-muted">
<small>
${msg.timestamp ? new Date(msg.timestamp).toLocaleString() : ''}
${msg.response_time ? ` | 响应: ${msg.response_time}s` : ''}
${msg.confidence_score ? ` | 置信度: ${Math.round(msg.confidence_score * 100)}%` : ''}
</small>
</div>
</div>
`).join('');
const modalHtml = `
<div class="modal fade" id="sessionMessagesModal" tabindex="-1">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-comments me-2"></i>${session?.title || session?.session_id || '会话详情'}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3 text-muted">
<small>
会话ID: ${session?.session_id || '-'} |
消息数: ${session?.message_count || 0} |
状态: ${session?.status || '-'} |
来源: ${session?.source || '-'}
</small>
</div>
${messages.length > 0 ? messagesHtml : '<div class="text-center text-muted py-3">暂无消息记录</div>'}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
`;
// 移除已存在的模态框
const existingModal = document.getElementById('sessionMessagesModal');
if (existingModal) existingModal.remove();
document.body.insertAdjacentHTML('beforeend', modalHtml);
const modal = new bootstrap.Modal(document.getElementById('sessionMessagesModal'));
modal.show();
// 模态框关闭后清理
document.getElementById('sessionMessagesModal').addEventListener('hidden.bs.modal', function () {
this.remove();
});
},
// Task 7.1: 对话租户详情分页
updateConversationTenantPagination(data) {
this.createPaginationComponent(data, 'conversation-session-pagination', 'loadConversationTenantDetailPage', '条会话');
},
// Task 7.2: 面包屑导航
renderConversationBreadcrumb(tenantId, sessionTitle) {
const breadcrumbEl = document.getElementById('conversation-breadcrumb');
if (!breadcrumbEl) return;
if (!tenantId) {
breadcrumbEl.innerHTML = '';
return;
}
if (!sessionTitle) {
// 租户详情视图: "对话历史 > {tenant_id}"
breadcrumbEl.innerHTML = `
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-0">
<li class="breadcrumb-item"><a href="#" onclick="dashboard.loadConversationTenantList(); return false;"><i class="fas fa-history me-1"></i>对话历史</a></li>
<li class="breadcrumb-item active" aria-current="page">${tenantId}</li>
</ol>
</nav>
`;
} else {
// 消息详情视图: "对话历史 > {tenant_id} > {session_title}"
breadcrumbEl.innerHTML = `
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-0">
<li class="breadcrumb-item"><a href="#" onclick="dashboard.loadConversationTenantList(); return false;"><i class="fas fa-history me-1"></i>对话历史</a></li>
<li class="breadcrumb-item"><a href="#" onclick="dashboard.loadConversationTenantDetail('${tenantId}'); return false;">${tenantId}</a></li>
<li class="breadcrumb-item active" aria-current="page">${sessionTitle}</li>
</ol>
</nav>
`;
}
this.conversationCurrentTenantId = tenantId;
},
// Task 7.1: 应用对话筛选条件
applyConversationFilters() {
if (this.conversationCurrentTenantId) {
this.loadConversationTenantDetail(this.conversationCurrentTenantId, 1);
}
},
// Task 7.3: 删除会话并处理空租户自动返回
async deleteConversationSession(sessionId) {
if (!confirm('确定要删除这个会话及其所有消息吗?')) return;
try {
const response = await fetch(`/api/conversations/sessions/${encodeURIComponent(sessionId)}`, { method: 'DELETE' });
const data = await response.json();
if (data.success) {
this.showNotification('会话已删除', 'success');
// 刷新当前租户详情视图并检查是否还有剩余会话
if (this.conversationCurrentTenantId) {
const tenantId = this.conversationCurrentTenantId;
const perPage = this.getPageSize('conversation-session-pagination');
const checkUrl = `/api/conversations/sessions?tenant_id=${encodeURIComponent(tenantId)}&page=1&per_page=${perPage}`;
const checkResp = await fetch(checkUrl);
const checkData = await checkResp.json();
if (!checkData.sessions || checkData.sessions.length === 0 || checkData.total === 0) {
// 该租户下已无会话,自动返回租户列表视图
this.loadConversationTenantList();
} else {
// 仍有会话,刷新当前详情视图(调整页码避免越界)
const maxPage = checkData.total_pages || 1;
const currentPage = Math.min(this.paginationConfig.currentConversationPage, maxPage);
await this.loadConversationTenantDetail(tenantId, currentPage);
}
}
} else {
throw new Error(data.error || '删除失败');
}
} catch (error) {
console.error('删除会话失败:', error);
this.showNotification('删除会话失败: ' + error.message, 'error');
}
},
// 对话历史管理
async loadConversationHistory(page = 1) {
try {
const pageSize = this.getPageSize('conversations-pagination');
const response = await fetch(`/api/conversations?page=${page}&per_page=${pageSize}`);
const data = await response.json();
if (data.conversations) {
this.renderConversationList(data.conversations || []);
this.updateConversationPagination(data);
this.updateConversationStats(data.stats || {});
} else {
throw new Error(data.error || '加载对话历史失败');
}
} catch (error) {
console.error('加载对话历史失败:', error);
this.showNotification('加载对话历史失败: ' + error.message, 'error');
}
},
renderConversationList(conversations) {
const container = document.getElementById('conversation-list');
if (!conversations || conversations.length === 0) {
container.innerHTML = `
<div class="empty-state">
<i class="fas fa-comments"></i>
<p>暂无对话记录</p>
</div>
`;
return;
}
const html = conversations.map(conv => {
// 识别来源和类型
let sourceBadge = '';
const method = conv.invocation_method || '';
const userId = conv.user_id || '';
// 判断是否来自飞书 (根据调用方式或ID格式)
if (method.includes('feishu') || userId.startsWith('ou_')) {
sourceBadge = '<span class="badge bg-purple me-1">飞书</span>';
} else {
sourceBadge = '<span class="badge bg-blue me-1">Web</span>';
}
// 判断群聊/私聊
let typeBadge = '';
if (method.includes('group')) {
typeBadge = '<span class="badge bg-azure me-1">群聊</span>';
} else if (method.includes('p2p')) {
typeBadge = '<span class="badge bg-indigo me-1">私聊</span>';
}
return `
<div class="card mb-3 conversation-item" data-conversation-id="${conv.id}">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-2">
<div>
<h6 class="mb-1" style="font-family: var(--font-family-primary); font-weight: 600;">
${sourceBadge}
${typeBadge}
用户: ${userId || '匿名'}
</h6>
<small class="text-muted">${new Date(conv.timestamp).toLocaleString()}</small>
</div>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-primary" onclick="dashboard.viewConversation(${conv.id})">
<i class="fas fa-eye"></i>
</button>
<button class="btn btn-outline-danger" onclick="dashboard.deleteConversation(${conv.id})">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
<div class="conversation-preview">
<p class="mb-1"><strong>用户:</strong> ${conv.user_message?.substring(0, 100)}${conv.user_message?.length > 100 ? '...' : ''}</p>
<p class="mb-0"><strong>助手:</strong> ${conv.assistant_response?.substring(0, 100)}${conv.assistant_response?.length > 100 ? '...' : ''}</p>
</div>
<div class="mt-2">
<span class="badge bg-secondary">响应时间: ${conv.response_time || 0}ms</span>
${conv.work_order_id ? `<span class="badge bg-info">工单: ${conv.work_order_id}</span>` : ''}
</div>
</div>
</div>
`;
}).join('');
container.innerHTML = html;
},
updateConversationPagination(data) {
this.createPaginationComponent(data, 'conversations-pagination', 'loadConversationHistory', '条对话');
},
updateConversationStats(stats) {
document.getElementById('conversation-total').textContent = stats.total || 0;
document.getElementById('conversation-today').textContent = stats.today || 0;
document.getElementById('conversation-avg-response').textContent = `${stats.avg_response_time || 0}ms`;
document.getElementById('conversation-active-users').textContent = stats.active_users || 0;
},
// Task 8.3: 加载对话统计(支持租户级别)
async loadConversationStats(tenantId) {
try {
let url = '/api/conversations/analytics';
if (tenantId) {
url += `?tenant_id=${encodeURIComponent(tenantId)}`;
}
const response = await fetch(url);
const data = await response.json();
const analytics = data.analytics || {};
const convStats = analytics.conversations || {};
const totalEl = document.getElementById('conversation-total');
const todayEl = document.getElementById('conversation-today');
const avgResponseEl = document.getElementById('conversation-avg-response');
const activeUsersEl = document.getElementById('conversation-active-users');
if (totalEl) totalEl.textContent = convStats.total || 0;
if (todayEl) todayEl.textContent = convStats.today || 0;
if (avgResponseEl) avgResponseEl.textContent = `${Math.round(convStats.avg_response_time || 0)}ms`;
if (activeUsersEl) activeUsersEl.textContent = convStats.active_users || 0;
} catch (error) {
console.error('加载对话统计失败:', error);
}
},
async refreshConversationHistory() {
// 先尝试触发一次合并迁移(幂等,重复调用也安全)
try {
await fetch('/api/conversations/migrate-merge', { method: 'POST' });
} catch (e) { /* 忽略迁移失败 */ }
// 根据当前视图状态刷新:租户详情视图或租户列表视图
if (this.conversationCurrentTenantId) {
await this.loadConversationTenantDetail(this.conversationCurrentTenantId, this.paginationConfig.currentConversationPage);
} else {
await this.loadConversationTenantList();
}
this.showNotification('对话历史已刷新', 'success');
},
async clearAllConversations() {
if (!confirm('确定要清空所有对话历史吗?此操作不可恢复!')) {
return;
}
try {
const response = await fetch('/api/conversations/clear', { method: 'DELETE' });
const data = await response.json();
if (data.success) {
this.showNotification('对话历史已清空', 'success');
await this.loadConversationHistory();
} else {
throw new Error(data.error || '清空对话历史失败');
}
} catch (error) {
console.error('清空对话历史失败:', error);
this.showNotification('清空对话历史失败: ' + error.message, 'error');
}
},
async deleteConversation(conversationId) {
if (!confirm('确定要删除这条对话记录吗?')) {
return;
}
try {
const response = await fetch(`/api/conversations/${conversationId}`, { method: 'DELETE' });
const data = await response.json();
if (data.success) {
this.showNotification('对话记录已删除', 'success');
await this.loadConversationHistory();
} else {
throw new Error(data.error || '删除对话记录失败');
}
} catch (error) {
console.error('删除对话记录失败:', error);
this.showNotification('删除对话记录失败: ' + error.message, 'error');
}
},
async viewConversation(conversationId) {
try {
const response = await fetch(`/api/conversations/${conversationId}`);
const data = await response.json();
if (data.success) {
data.user_id = data.user_id || '匿名';
this.showConversationModal(data);
} else {
throw new Error(data.error || '获取对话详情失败');
}
} catch (error) {
console.error('获取对话详情失败:', error);
this.showNotification('获取对话详情失败: ' + error.message, 'error');
}
},
showConversationModal(conversation) {
const modalHtml = `
<div class="modal fade" id="conversationModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">对话详情</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<strong>用户:</strong> ${conversation.user_id || '匿名'}<br>
<strong>时间:</strong> ${new Date(conversation.timestamp).toLocaleString()}<br>
<strong>响应时间:</strong> ${conversation.response_time || 0}ms
</div>
<div class="mb-3">
<h6>用户消息:</h6>
<div class="border p-3 rounded">${conversation.user_message || ''}</div>
</div>
<div class="mb-3">
<h6>助手回复:</h6>
<div class="border p-3 rounded">${conversation.assistant_response || ''}</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
`;
// 移除已存在的模态框
const existingModal = document.getElementById('conversationModal');
if (existingModal) {
existingModal.remove();
}
// 添加新模态框
document.body.insertAdjacentHTML('beforeend', modalHtml);
// 显示模态框
const modal = new bootstrap.Modal(document.getElementById('conversationModal'));
modal.show();
},
async filterConversations() {
const search = document.getElementById('conversation-search').value.trim();
const userFilter = document.getElementById('conversation-user-filter')?.value || '';
const dateFilter = document.getElementById('conversation-date-filter')?.value || '';
// Task 8.1: 在 Tenant_Detail_View 中搜索时自动附加 tenant_id 参数
if (this.conversationCurrentTenantId) {
if (!search) {
// 清空搜索时恢复当前租户的完整分页列表
this.loadConversationTenantDetail(this.conversationCurrentTenantId);
return;
}
try {
const perPage = this.getPageSize('conversation-session-pagination');
let url = `/api/conversations/sessions?search=${encodeURIComponent(search)}&tenant_id=${encodeURIComponent(this.conversationCurrentTenantId)}&page=1&per_page=${perPage}`;
if (dateFilter) url += `&date_filter=${encodeURIComponent(dateFilter)}`;
const response = await fetch(url);
const data = await response.json();
if (data.sessions) {
this.renderConversationSessionTable(data.sessions, this.conversationCurrentTenantId);
this.updateConversationTenantPagination(data);
} else {
const sessionListEl = document.getElementById('conversation-session-list');
if (sessionListEl) {
sessionListEl.innerHTML = '<div class="text-center py-4"><i class="fas fa-search fa-2x text-muted"></i><p class="mt-2 text-muted">未找到匹配的会话</p></div>';
}
const paginationEl = document.getElementById('conversation-session-pagination');
if (paginationEl) paginationEl.innerHTML = '';
}
} catch (error) {
console.error('搜索租户会话失败:', error);
this.showNotification('搜索会话失败: ' + error.message, 'error');
}
return;
}
// 非租户详情视图:保持原有行为
try {
const params = new URLSearchParams();
if (search) params.append('search', search);
if (userFilter) params.append('user_id', userFilter);
if (dateFilter) params.append('date_filter', dateFilter);
const response = await fetch(`/api/conversations?${params.toString()}`);
const data = await response.json();
if (data.success) {
this.renderConversationList(data.conversations || []);
this.renderConversationPagination(data.pagination || {});
} else {
throw new Error(data.error || '筛选对话失败');
}
} catch (error) {
console.error('筛选对话失败:', error);
this.showNotification('筛选对话失败: ' + error.message, 'error');
}
}
});