// 对话历史模块 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 = '

加载中...

'; } try { const response = await fetch('/api/conversations/tenants'); const tenants = await response.json(); if (!Array.isArray(tenants) || tenants.length === 0) { if (tenantListEl) { tenantListEl.innerHTML = '

暂无对话会话数据

'; } return; } const cardsHtml = tenants.map(t => { const lastActive = t.last_active_time ? new Date(t.last_active_time).toLocaleString() : '无'; return `
${t.tenant_id}
会话总数
${t.session_count}
消息总数
${t.message_count}
活跃会话
${t.active_session_count}
最近活跃: ${lastActive}
`; }).join(''); if (tenantListEl) { tenantListEl.innerHTML = cardsHtml; } } catch (error) { console.error('加载对话租户列表失败:', error); if (tenantListEl) { tenantListEl.innerHTML = '
加载失败
'; } 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 = '

加载中...

'; } 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 = '

暂无会话数据

'; } } } catch (error) { console.error('加载租户会话列表失败:', error); if (sessionListEl) { sessionListEl.innerHTML = '
加载失败
'; } 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 = '

暂无会话数据

'; return; } const statusBadge = (status) => { if (status === 'active') return '活跃'; if (status === 'ended') return '已结束'; return `${status || '未知'}`; }; const sourceBadge = (source) => { if (source === 'feishu') return '飞书'; if (source === 'websocket') return 'WebSocket'; if (source === 'api') return 'API'; return `${source || '未知'}`; }; const formatTime = (isoStr) => { if (!isoStr) return '-'; return new Date(isoStr).toLocaleString(); }; const rowsHtml = sessions.map(s => ` ${s.title || s.session_id || '-'} ${s.message_count || 0} ${statusBadge(s.status)} ${sourceBadge(s.source)} ${formatTime(s.created_at)} ${formatTime(s.updated_at)} `).join(''); sessionListEl.innerHTML = `
${rowsHtml}
会话标题 消息数 状态 来源 创建时间 更新时间 操作
`; }, // 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 => `
用户:
${msg.user_message || ''}
助手:
${msg.assistant_response || ''}
${msg.timestamp ? new Date(msg.timestamp).toLocaleString() : ''} ${msg.response_time ? ` | 响应: ${msg.response_time}s` : ''} ${msg.confidence_score ? ` | 置信度: ${Math.round(msg.confidence_score * 100)}%` : ''}
`).join(''); const modalHtml = ` `; // 移除已存在的模态框 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 = ` `; } else { // 消息详情视图: "对话历史 > {tenant_id} > {session_title}" breadcrumbEl.innerHTML = ` `; } 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 = `

暂无对话记录

`; 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 = '飞书'; } else { sourceBadge = 'Web'; } // 判断群聊/私聊 let typeBadge = ''; if (method.includes('group')) { typeBadge = '群聊'; } else if (method.includes('p2p')) { typeBadge = '私聊'; } return `
${sourceBadge} ${typeBadge} 用户: ${userId || '匿名'}
${new Date(conv.timestamp).toLocaleString()}

用户: ${conv.user_message?.substring(0, 100)}${conv.user_message?.length > 100 ? '...' : ''}

助手: ${conv.assistant_response?.substring(0, 100)}${conv.assistant_response?.length > 100 ? '...' : ''}

响应时间: ${conv.response_time || 0}ms ${conv.work_order_id ? `工单: ${conv.work_order_id}` : ''}
`; }).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 = ` `; // 移除已存在的模态框 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 = '

未找到匹配的会话

'; } 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'); } } });