| <!DOCTYPE html> |
| <html lang="zh-CN"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"> |
| <meta http-equiv="Pragma" content="no-cache"> |
| <meta http-equiv="Expires" content="0"> |
| <title>HR智能对话助手</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: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| min-height: 100vh; |
| padding: 20px; |
| } |
| |
| .container { |
| max-width: 1200px; |
| margin: 0 auto; |
| background: white; |
| border-radius: 16px; |
| box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); |
| overflow: hidden; |
| } |
| |
| .header { |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| color: white; |
| padding: 24px; |
| text-align: center; |
| } |
| |
| .header h1 { |
| font-size: 28px; |
| margin-bottom: 8px; |
| } |
| |
| .header p { |
| opacity: 0.9; |
| font-size: 14px; |
| } |
| |
| .main-content { |
| display: flex; |
| height: calc(100vh - 200px); |
| min-height: 500px; |
| } |
| |
| .chat-section { |
| flex: 1; |
| display: flex; |
| flex-direction: column; |
| border-right: 1px solid #e5e7eb; |
| } |
| |
| .chat-messages { |
| flex: 1; |
| overflow-y: auto; |
| padding: 20px; |
| background: #f9fafb; |
| } |
| |
| .message { |
| margin-bottom: 16px; |
| display: flex; |
| align-items: flex-start; |
| gap: 12px; |
| } |
| |
| .message.user { |
| flex-direction: row-reverse; |
| } |
| |
| .message-avatar { |
| width: 36px; |
| height: 36px; |
| border-radius: 50%; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-weight: bold; |
| color: white; |
| flex-shrink: 0; |
| } |
| |
| .message.user .message-avatar { |
| background: #667eea; |
| } |
| |
| .message.assistant .message-avatar { |
| background: #10b981; |
| } |
| |
| .message-content { |
| max-width: 70%; |
| padding: 12px 16px; |
| border-radius: 12px; |
| line-height: 1.5; |
| } |
| |
| .message.user .message-content { |
| background: #667eea; |
| color: white; |
| border-bottom-right-radius: 4px; |
| } |
| |
| .message.assistant .message-content { |
| background: white; |
| border: 1px solid #e5e7eb; |
| border-bottom-left-radius: 4px; |
| } |
| |
| .chat-input { |
| padding: 20px; |
| background: white; |
| border-top: 1px solid #e5e7eb; |
| display: flex; |
| gap: 12px; |
| } |
| |
| .chat-input input { |
| flex: 1; |
| padding: 12px 16px; |
| border: 2px solid #e5e7eb; |
| border-radius: 8px; |
| font-size: 14px; |
| transition: border-color 0.2s; |
| } |
| |
| .chat-input input:focus { |
| outline: none; |
| border-color: #667eea; |
| } |
| |
| .chat-input button { |
| padding: 12px 24px; |
| background: #667eea; |
| color: white; |
| border: none; |
| border-radius: 8px; |
| font-weight: 600; |
| cursor: pointer; |
| transition: background 0.2s; |
| } |
| |
| .chat-input button:hover { |
| background: #5568d3; |
| } |
| |
| .chat-input button:disabled { |
| background: #d1d5db; |
| cursor: not-allowed; |
| } |
| |
| .reasoning-panel { |
| width: 400px; |
| overflow-y: auto; |
| padding: 20px; |
| background: #f9fafb; |
| } |
| |
| .panel-title { |
| font-size: 18px; |
| font-weight: 600; |
| margin-bottom: 16px; |
| color: #111827; |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| .panel-section { |
| background: white; |
| border-radius: 8px; |
| padding: 16px; |
| margin-bottom: 12px; |
| border: 1px solid #e5e7eb; |
| } |
| |
| .panel-section-title { |
| font-weight: 600; |
| margin-bottom: 8px; |
| color: #374151; |
| font-size: 14px; |
| } |
| |
| .panel-section-content { |
| font-size: 13px; |
| color: #6b7280; |
| line-height: 1.6; |
| } |
| |
| .badge { |
| display: inline-block; |
| padding: 4px 8px; |
| border-radius: 4px; |
| font-size: 12px; |
| font-weight: 500; |
| margin-right: 6px; |
| margin-bottom: 4px; |
| } |
| |
| .badge.intent { |
| background: #dbeafe; |
| color: #1e40af; |
| } |
| |
| .badge.good { |
| background: #d1fae5; |
| color: #065f46; |
| } |
| |
| .badge.warning { |
| background: #fef3c7; |
| color: #92400e; |
| } |
| |
| .badge.error { |
| background: #fee2e2; |
| color: #991b1b; |
| } |
| |
| .score-display { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| margin-top: 8px; |
| } |
| |
| .score-bar { |
| flex: 1; |
| height: 8px; |
| background: #e5e7eb; |
| border-radius: 4px; |
| overflow: hidden; |
| } |
| |
| .score-fill { |
| height: 100%; |
| border-radius: 4px; |
| transition: width 0.3s; |
| } |
| |
| .score-fill.good { |
| background: #10b981; |
| } |
| |
| .score-fill.warning { |
| background: #f59e0b; |
| } |
| |
| .score-fill.error { |
| background: #ef4444; |
| } |
| |
| .knowledge-item { |
| padding: 8px; |
| background: #f3f4f6; |
| border-radius: 6px; |
| margin-bottom: 8px; |
| } |
| |
| .knowledge-question { |
| font-weight: 500; |
| color: #374151; |
| margin-bottom: 4px; |
| } |
| |
| .knowledge-similarity { |
| font-size: 12px; |
| color: #10b981; |
| } |
| |
| .loading { |
| text-align: center; |
| padding: 20px; |
| color: #6b7280; |
| } |
| |
| .spinner { |
| border: 3px solid #e5e7eb; |
| border-top: 3px solid #667eea; |
| border-radius: 50%; |
| width: 24px; |
| height: 24px; |
| animation: spin 1s linear infinite; |
| margin: 0 auto 8px; |
| } |
| |
| @keyframes spin { |
| 0% { transform: rotate(0deg); } |
| 100% { transform: rotate(360deg); } |
| } |
| |
| .examples { |
| padding: 16px; |
| background: #f0fdf4; |
| border: 1px solid #bbf7d0; |
| border-radius: 8px; |
| margin: 20px; |
| } |
| |
| .examples-title { |
| font-weight: 600; |
| margin-bottom: 8px; |
| color: #166534; |
| } |
| |
| .example-button { |
| display: inline-block; |
| padding: 6px 12px; |
| background: white; |
| border: 1px solid #86efac; |
| border-radius: 6px; |
| margin: 4px; |
| font-size: 13px; |
| cursor: pointer; |
| transition: all 0.2s; |
| } |
| |
| .example-button:hover { |
| background: #f0fdf4; |
| border-color: #22c55e; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <div class="header"> |
| <h1>HR智能对话助手</h1> |
| <p>基于RAG的检索增强生成 · 自动展示模型判断依据</p> |
| </div> |
|
|
| <div class="main-content"> |
| <div class="chat-section"> |
| <div class="chat-messages" id="chatMessages"> |
| <div class="examples"> |
| <div class="examples-title">试试这些问题:</div> |
| <button class="example-button" onclick="askQuestion('我想申请机器学习培训')">我想申请机器学习培训</button> |
| <button class="example-button" onclick="askQuestion('社保怎么缴纳?')">社保怎么缴纳?</button> |
| <button class="example-button" onclick="askQuestion('年假有多少天?')">年假有多少天?</button> |
| <button class="example-button" onclick="askQuestion('我想申请离职')">我想申请离职</button> |
| </div> |
| </div> |
|
|
| <div class="chat-input"> |
| <input type="text" id="userInput" placeholder="请输入您的问题..." onkeypress="handleKeyPress(event)"> |
| <button id="sendButton" onclick="sendMessage()">发送</button> |
| </div> |
| </div> |
|
|
| <div class="reasoning-panel" id="reasoningPanel"> |
| <div class="panel-title"> |
| <span>模型判断依据</span> |
| </div> |
| <div class="panel-section"> |
| <div class="panel-section-content" style="text-align: center; color: #9ca3af; padding: 40px 0;"> |
| 发送消息后,这里将展示AI的完整思考过程 |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| // Use relative path to support any port |
| const API_URL = '/api/v1/chat'; |
| let conversationHistory = []; |
| let sessionId = 'web_' + Date.now(); |
| |
| function askQuestion(question) { |
| document.getElementById('userInput').value = question; |
| sendMessage(); |
| } |
| |
| function handleKeyPress(event) { |
| if (event.key === 'Enter') { |
| sendMessage(); |
| } |
| } |
| |
| async function sendMessage() { |
| const input = document.getElementById('userInput'); |
| const message = input.value.trim(); |
| |
| if (!message) return; |
| |
| // 添加用户消息 |
| addMessage('user', message); |
| input.value = ''; |
| |
| // 禁用发送按钮 |
| document.getElementById('sendButton').disabled = true; |
| |
| // 显示加载中 |
| addLoadingIndicator(); |
| |
| try { |
| const response = await fetch(API_URL, { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json', |
| }, |
| body: JSON.stringify({ |
| question: message, |
| conversation_history: conversationHistory, |
| show_reasoning: true, |
| session_id: sessionId |
| }) |
| }); |
| |
| const data = await response.json(); |
| |
| // 移除加载中 |
| removeLoadingIndicator(); |
| |
| // 添加助手消息(使用打字机效果) |
| const answer = data.answer || '抱歉,无法获取回复'; |
| addMessage('assistant', answer, true); |
| |
| // 更新推理面板 |
| updateReasoningPanel(data.reasoning); |
| |
| // 更新对话历史 |
| conversationHistory = data.conversation_history || []; |
| |
| } catch (error) { |
| removeLoadingIndicator(); |
| addMessage('assistant', '抱歉,发生了错误:' + error.message, true); |
| console.error('Error:', error); |
| } |
| |
| // 启用发送按钮 |
| document.getElementById('sendButton').disabled = false; |
| } |
| |
| function addMessage(role, content, animate = false) { |
| const messagesDiv = document.getElementById('chatMessages'); |
| const messageDiv = document.createElement('div'); |
| messageDiv.className = `message ${role}`; |
| |
| const avatar = role === 'user' ? '员' : 'HR'; |
| const avatarClass = role === 'user' ? 'user' : 'assistant'; |
| |
| messageDiv.innerHTML = ` |
| <div class="message-avatar ${avatarClass}">${avatar}</div> |
| <div class="message-content"></div> |
| `; |
| |
| messagesDiv.appendChild(messageDiv); |
| |
| const contentDiv = messageDiv.querySelector('.message-content'); |
| |
| // 助手消息使用打字机效果 |
| if (role === 'assistant' && animate) { |
| typeWriter(content, contentDiv, messagesDiv); |
| } else { |
| contentDiv.innerHTML = content; |
| messagesDiv.scrollTop = messagesDiv.scrollHeight; |
| } |
| } |
| |
| // 打字机效果 |
| function typeWriter(text, element, container) { |
| let index = 0; |
| const speed = 15; // 打字速度(毫秒) |
| |
| function type() { |
| if (index < text.length) { |
| element.innerHTML += text.charAt(index); |
| index++; |
| container.scrollTop = container.scrollHeight; |
| setTimeout(type, speed); |
| } |
| } |
| |
| type(); |
| } |
| |
| function addLoadingIndicator() { |
| const messagesDiv = document.getElementById('chatMessages'); |
| const loadingDiv = document.createElement('div'); |
| loadingDiv.className = 'message assistant'; |
| loadingDiv.id = 'loadingIndicator'; |
| loadingDiv.innerHTML = ` |
| <div class="message-avatar assistant">HR</div> |
| <div class="message-content"> |
| <div class="spinner"></div> |
| <div style="text-align: center; font-size: 12px;">正在思考...</div> |
| </div> |
| `; |
| messagesDiv.appendChild(loadingDiv); |
| messagesDiv.scrollTop = messagesDiv.scrollHeight; |
| } |
| |
| function removeLoadingIndicator() { |
| const loading = document.getElementById('loadingIndicator'); |
| if (loading) loading.remove(); |
| } |
| |
| function updateReasoningPanel(reasoning) { |
| const panel = document.getElementById('reasoningPanel'); |
| |
| if (!reasoning) { |
| panel.innerHTML = ` |
| <div class="panel-title"> |
| <span>模型判断依据</span> |
| </div> |
| <div class="panel-section"> |
| <div class="panel-section-content">无判断依据数据</div> |
| </div> |
| `; |
| return; |
| } |
| |
| // 新数据结构解析 |
| const layer1 = reasoning.layer1_intelligence_analysis || {}; |
| const layer2 = reasoning.layer2_execution || {}; |
| const replyInstruction = reasoning.reply_instruction || {}; |
| |
| // 意图理解 |
| const intentUnderstanding = layer1.intent_understanding || {}; |
| // 场景识别 |
| const scenarioRecognition = layer1.scenario_recognition || {}; |
| // 信息提取 |
| const informationExtraction = layer1.information_extraction || {}; |
| // 情绪分析 |
| const emotionAnalysis = layer1.emotion_analysis || {}; |
| // 风险评估 |
| const riskAssessment = layer1.risk_assessment || {}; |
| // 正确性检查 |
| const correctnessCheck = layer2.correctness_check || {}; |
| // 合规性检查 |
| const complianceCheck = layer2.compliance_check || {}; |
| // 质量分数 |
| const qualityScore = layer2.quality_score || 0; |
| |
| panel.innerHTML = ` |
| <div class="panel-title"> |
| <span>模型判断依据</span> |
| <span style="float: right; font-size: 14px; font-weight: normal; color: #6b7280;"> |
| 质量分数: <strong style="color: ${qualityScore >= 80 ? '#10b981' : qualityScore >= 60 ? '#f59e0b' : '#ef4444'}">${qualityScore}</strong> |
| </span> |
| </div> |
| |
| |
| <div class="panel-section"> |
| <div class="panel-section-title"> |
| <span class="badge intent">意图理解</span> |
| </div> |
| <div class="panel-section-content"> |
| <strong>识别意图:</strong> ${intentUnderstanding.detected_intent || 'unknown'}<br> |
| <strong>置信度:</strong> ${(intentUnderstanding.confidence * 100 || 0).toFixed(1)}% |
| </div> |
| </div> |
| |
| |
| <div class="panel-section"> |
| <div class="panel-section-title"> |
| <span class="badge intent">场景识别</span> |
| </div> |
| <div class="panel-section-content"> |
| <strong>场景:</strong> ${scenarioRecognition.identified_scenario || 'unknown'}<br> |
| <strong>置信度:</strong> ${(scenarioRecognition.confidence * 100 || 0).toFixed(1)}%<br> |
| <strong>描述:</strong> ${scenarioRecognition.description || 'N/A'} |
| </div> |
| </div> |
| |
| |
| ${Object.keys(informationExtraction).length > 0 ? ` |
| <div class="panel-section"> |
| <div class="panel-section-title"> |
| <span class="badge intent">信息提取</span> |
| </div> |
| <div class="panel-section-content"> |
| ${informationExtraction.extracted_data ? Object.entries(informationExtraction.extracted_data).map(([key, value]) => ` |
| <div style="padding: 4px 0; border-bottom: 1px solid #f3f4f6;"> |
| <strong>${key}:</strong> ${value} |
| </div> |
| `).join('') : ''} |
| <div style="margin-top: 8px; font-size: 12px;"> |
| 提取字段: ${(informationExtraction.extracted_fields || []).join(', ') || 'N/A'} |
| </div> |
| </div> |
| </div> |
| ` : ''} |
| |
| |
| <div class="panel-section"> |
| <div class="panel-section-title"> |
| <span class="badge ${emotionAnalysis.emotion === 'positive' ? 'good' : emotionAnalysis.emotion === 'negative' ? 'error' : 'warning'}"> |
| 情绪分析 |
| </span> |
| </div> |
| <div class="panel-section-content"> |
| <strong>情绪:</strong> ${emotionAnalysis.emotion || 'neutral'}<br> |
| <strong>强度:</strong> ${(emotionAnalysis.intensity * 100 || 0).toFixed(0)}% |
| </div> |
| </div> |
| |
| |
| <div class="panel-section"> |
| <div class="panel-section-title"> |
| <span class="badge ${correctnessCheck.is_correct ? 'good' : 'warning'}"> |
| 正确性检查 |
| </span> |
| </div> |
| <div class="panel-section-content"> |
| ${correctnessCheck.is_question ? ` |
| <strong>类型:</strong> 追问<br> |
| <strong>状态:</strong> <span class="badge good">合理</span> |
| ` : ` |
| <strong>相似度分数:</strong> ${((correctnessCheck.similarity_score || 0) * 100).toFixed(1)}%<br> |
| <strong>等级:</strong> ${correctnessCheck.level || 'unknown'} |
| `} |
| </div> |
| </div> |
| |
| |
| <div class="panel-section"> |
| <div class="panel-section-title"> |
| <span class="badge ${complianceCheck.is_compliant ? 'good' : 'error'}"> |
| 合规性检查 |
| </span> |
| </div> |
| <div class="panel-section-content"> |
| <strong>状态:</strong> ${complianceCheck.is_compliant ? '<span class="badge good">合规</span>' : '<span class="badge error">违规</span>'} |
| ${complianceCheck.violations && complianceCheck.violations.length > 0 ? ` |
| <div style="margin-top: 8px; padding: 8px; background: #fee2e2; border-radius: 4px;"> |
| <strong style="color: #991b1b;">检测到违规:</strong> |
| <ul style="margin: 4px 0 0 20px; color: #991b1b;"> |
| ${complianceCheck.violations.map(v => `<li>${v.type || v.category || '违规'}: ${v.text || v.word || 'N/A'}</li>`).join('')} |
| </ul> |
| </div> |
| ` : ''} |
| </div> |
| </div> |
| `; |
| } |
| </script> |
| </body> |
| </html> |
|
|