/** * 流式状态显示组件 * 用于显示RAG检索过程的实时状态 */ class StreamStatusDisplay { constructor(container) { this.container = container; this.statusElements = {}; this.retrievalHistory = []; // 记录检索历史 this.statusHistory = []; // 记录所有状态历史 this.maxDisplayItems = 3; // 最多显示3条状态 this.eventQueue = []; // 事件队列 this.isProcessing = false; // 是否正在处理队列 this.minDisplayTime = 500; // 每个状态最少显示500ms this.init(); } init() { // 创建状态显示区域 this.statusArea = document.createElement('div'); this.statusArea.className = 'stream-status-area'; this.statusArea.style.cssText = ` padding: 12px 16px; background: linear-gradient(135deg, #f6f9fc 0%, #f0f4f8 100%); border-radius: 12px; margin-bottom: 16px; font-size: 14px; color: #475569; min-height: 48px; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); display: flex; align-items: center; gap: 12px; position: relative; overflow: hidden; `; // 添加动画背景 const animBg = document.createElement('div'); animBg.style.cssText = ` position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(99, 102, 241, 0.1), transparent); animation: shimmer 2s infinite; `; this.statusArea.appendChild(animBg); // 添加CSS动画 if (!document.getElementById('stream-status-styles')) { const style = document.createElement('style'); style.id = 'stream-status-styles'; style.textContent = ` @keyframes shimmer { 0% { left: -100%; } 100% { left: 100%; } } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } @keyframes rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .status-icon { font-size: 20px; animation: pulse 1.5s ease-in-out infinite; } .status-spinner { animation: rotate 1s linear infinite; } `; document.head.appendChild(style); } // 状态内容容器 this.contentArea = document.createElement('div'); this.contentArea.style.cssText = ` position: relative; z-index: 1; flex: 1; display: flex; align-items: center; gap: 12px; `; this.statusArea.appendChild(this.contentArea); this.container.appendChild(this.statusArea); } updateStatus(event) { // 清除淡出定时器 if (this.fadeTimeout) { clearTimeout(this.fadeTimeout); } switch (event.type) { case 'cache_hit': case 'cache': if (event.cached) { this.showCacheHit(); } else { this.showCacheMiss(); } break; case 'cache_miss': this.showCacheMiss(); break; case 'starting': this.showStatus('🔍 开始分析查询...', '#6366f1'); break; case 'complexity_check': const complexity = event.data; if (complexity) { if (complexity.is_complex) { this.showStatus( `📊 复杂查询 (${complexity.level || '高'}) - 置信度: ${(complexity.confidence * 100).toFixed(1)}%`, '#f59e0b' ); } else { this.showStatus( `📊 简单查询 - 置信度: ${(complexity.confidence * 100).toFixed(1)}%`, '#10b981' ); } } break; case 'documents': const docs = event.data; if (docs) { // 记录检索历史 this.retrievalHistory.push({ count: docs.count, new_docs: docs.new_docs || 0, is_incremental: docs.is_incremental || false, retrieval_type: docs.retrieval_type || '检索', retrieval_reason: docs.retrieval_reason || '' }); // 根据是否是增量检索显示不同信息 let message = ''; if (docs.is_incremental) { // 增量检索(第二次及以后) const reason = docs.retrieval_reason || '继续检索'; message = `📚 ${reason}:新增 ${docs.new_docs || 0} 篇文档(总计 ${docs.count} 篇)`; this.showStatus(message, '#8b5cf6'); // 紫色表示增量 } else { // 初始检索 message = `📚 已检索 ${docs.count} 篇文档`; if (docs.retrieval_type) { message = `📚 ${docs.retrieval_type}:${docs.count} 篇文档`; } this.showStatus(message, '#6366f1'); // 蓝色表示初始 } // 显示检索历史汇总 if (this.retrievalHistory.length > 1) { this.showRetrievalSummary(); } // 显示来源文档 if (docs.sources && docs.sources.length > 0) { this.addSources(docs.sources); } } break; case 'sufficiency_check': const sufficient = event.data; if (sufficient) { // 置信度可能是数字或未定义 const confidence = sufficient.confidence !== undefined ? sufficient.confidence : 0.5; if (sufficient.is_sufficient) { this.showStatus( `✅ 信息充分 (置信度: ${(confidence * 100).toFixed(1)}%)`, '#10b981' ); } else { this.showStatus( `⚠️ 信息不足,继续检索... (置信度: ${(confidence * 100).toFixed(1)}%)`, '#f59e0b' ); } } break; case 'sub_queries': const queries = event.data; if (queries && queries.length > 0) { this.showStatus(`🔄 生成了 ${queries.length} 个子查询`, '#8b5cf6'); this.showSubQueries(queries); } break; case 'iteration': const iter = event.data; if (iter) { // 只显示当前轮次,不显示最大轮次 this.showStatus(`🔄 第 ${iter.current} 轮迭代`, '#6366f1'); } break; case 'generating': // 不要在这里调用scheduleFadeOut this.showStatus('✨ 正在生成答案...', '#f59e0b'); // 使用黄色表示进行中 break; case 'cached': this.showStatus('💾 结果已缓存', '#10b981'); this.scheduleFadeOut(); break; case 'complete': this.showStatus('✅ 答案生成完成', '#10b981'); // 答案生成完成后,2秒后自动消失 setTimeout(() => { this.fadeOutAndHide(); }, 2000); break; case 'cancelled': // 清除之前的所有状态 this.statusHistory = []; // 显示取消状态 this.showStatus('⚠️ 任务已停止', '#ef4444'); // 1.5秒后淡出 setTimeout(() => { this.fadeOutAndHide(); }, 1500); break; default: if (event.message) { this.showStatus(event.message, '#6366f1'); } } } showStatus(message, color = '#6366f1') { // 添加到历史记录 this.statusHistory.push({ message: message, color: color, timestamp: Date.now() }); // 使用独立的渲染方法 this.renderStatuses(); } showCacheHit() { this.statusArea.style.background = 'linear-gradient(135deg, #dcfce7 0%, #d1fae5 100%)'; this.statusArea.style.borderLeft = '3px solid #10b981'; this.contentArea.innerHTML = `
📦
使用缓存结果(伪流式输出)
`; } showCacheMiss() { this.statusArea.style.background = 'linear-gradient(135deg, #fef3c7 0%, #fde68a 100%)'; this.statusArea.style.borderLeft = '3px solid #f59e0b'; this.contentArea.innerHTML = `
🔍
开始实时检索...
`; } showSubQueries(queries) { const maxDisplay = 3; const displayQueries = queries.slice(0, maxDisplay); const remaining = queries.length - maxDisplay; // 构建子查询列表HTML const queriesList = displayQueries.map((q, i) => `${i + 1}. ${q.length > 30 ? q.substring(0, 30) + '...' : q}` ).join('
'); const moreText = remaining > 0 ? `
... 还有 ${remaining} 个` : ''; // 更新最后一个状态的消息,添加子查询列表 if (this.statusHistory.length > 0) { const lastStatus = this.statusHistory[this.statusHistory.length - 1]; // 如果消息中还没有子查询列表,就添加 if (!lastStatus.message.includes('
')) { lastStatus.message += `
${queriesList}${moreText}`; // 重新渲染 this.renderStatuses(); } } } showRetrievalSummary() { // 显示检索历史汇总 const totalRounds = this.retrievalHistory.length; const totalDocs = this.retrievalHistory[this.retrievalHistory.length - 1].count; // 创建一个小的汇总显示 const summaryHtml = ` 共 ${totalRounds} 轮检索 `; // 添加到当前状态显示中 if (!this.contentArea.querySelector('.retrieval-summary')) { const summarySpan = document.createElement('span'); summarySpan.className = 'retrieval-summary'; summarySpan.innerHTML = summaryHtml; this.contentArea.appendChild(summarySpan); } else { this.contentArea.querySelector('.retrieval-summary').innerHTML = summaryHtml; } } addSources(sources) { // 不单独添加,而是将来源信息合并到最新的状态消息中 const maxDisplay = 3; const displaySources = sources.slice(0, maxDisplay); const sourceText = displaySources.join(', '); const moreText = sources.length > maxDisplay ? ` 等${sources.length}个` : ''; // 更新最后一个状态的消息,添加来源信息 if (this.statusHistory.length > 0) { const lastStatus = this.statusHistory[this.statusHistory.length - 1]; // 如果消息中还没有来源信息,就添加 if (!lastStatus.message.includes('来源:')) { lastStatus.message += `
来源: ${sourceText}${moreText}`; // 重新渲染 this.renderStatuses(); } } } renderStatuses() { // 抽取渲染逻辑为独立方法 const recentStatuses = this.statusHistory.slice(-this.maxDisplayItems); this.contentArea.innerHTML = `
${recentStatuses.map((status, index) => { const isLatest = index === recentStatuses.length - 1; const opacity = isLatest ? 1 : 0.6; return `
${status.message}
`; }).join('')}
`; // 更新背景色 if (recentStatuses.length > 0) { const latestStatus = recentStatuses[recentStatuses.length - 1]; this.statusArea.style.background = `linear-gradient(135deg, ${this.hexToRgba(latestStatus.color, 0.05)} 0%, ${this.hexToRgba(latestStatus.color, 0.1)} 100%)`; this.statusArea.style.borderLeft = `3px solid ${latestStatus.color}`; } } scheduleFadeOut() { // 清除之前的定时器 if (this.fadeTimeout) { clearTimeout(this.fadeTimeout); } // 5秒后淡出 this.fadeTimeout = setTimeout(() => { this.fadeOut(); }, 5000); } fadeOut() { this.statusArea.style.opacity = '0.3'; this.statusArea.style.transform = 'scale(0.98)'; // 不完全隐藏,保留淡化状态 // setTimeout(() => { // this.statusArea.style.display = 'none'; // }, 300); } fadeOutAndHide() { // 添加过渡动画 this.statusArea.style.transition = 'all 0.5s ease-out'; this.statusArea.style.opacity = '0'; this.statusArea.style.transform = 'scale(0.95)'; this.statusArea.style.maxHeight = this.statusArea.offsetHeight + 'px'; // 动画结束后完全隐藏 setTimeout(() => { this.statusArea.style.maxHeight = '0'; this.statusArea.style.padding = '0'; this.statusArea.style.margin = '0'; this.statusArea.style.overflow = 'hidden'; // 再过渡一段时间后完全移除显示 setTimeout(() => { this.statusArea.style.display = 'none'; }, 500); }, 500); } clear() { if (this.fadeTimeout) { clearTimeout(this.fadeTimeout); } this.contentArea.innerHTML = ''; this.statusArea.style.opacity = '1'; this.statusArea.style.transform = 'scale(1)'; this.statusArea.style.display = 'flex'; this.statusArea.style.background = 'linear-gradient(135deg, #f6f9fc 0%, #f0f4f8 100%)'; this.statusArea.style.borderLeft = 'none'; // 重置历史记录 this.retrievalHistory = []; this.statusHistory = []; } destroy() { if (this.fadeTimeout) { clearTimeout(this.fadeTimeout); } if (this.statusArea && this.statusArea.parentNode) { this.statusArea.parentNode.removeChild(this.statusArea); } } hexToRgba(hex, alpha) { const r = parseInt(hex.slice(1, 3), 16); const g = parseInt(hex.slice(3, 5), 16); const b = parseInt(hex.slice(5, 7), 16); return `rgba(${r}, ${g}, ${b}, ${alpha})`; } } // 导出 window.StreamStatusDisplay = StreamStatusDisplay; console.log('[StreamStatusDisplay] 组件已加载');