|
|
| Line 1: |
Line 1: |
| <html lang="zh-CN"> | | <html> |
| <head>
| | <iframe src="https://mns.uestc.cn/resource/talk2zhiwei.html" width="100%" frameborder="0"></iframe> |
| <meta charset="UTF-8">
| |
| <meta name="viewport" content="width=device-width, initial-scale=1.0">
| |
| <title>赵老师的数字分身</title>
| |
| <style>
| |
| /* 仅保留核心样式,移除背景色 */
| |
| .dt-container {
| |
| max-width: 800px;
| |
| margin: 0 auto;
| |
| background: white;
| |
| border-radius: 15px;
| |
| box-shadow: 0 10px 30px rgba(0,0,0,0.2);
| |
| overflow: hidden;
| |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
| |
| }
| |
| | |
| .dt-header {
| |
| background: linear-gradient(135deg, #4a90e2 0%, #667eea 100%);
| |
| color: white;
| |
| padding: 20px;
| |
| text-align: center;
| |
| }
| |
| | |
| .dt-header h1 {
| |
| font-size: 24px;
| |
| margin-bottom: 5px;
| |
| }
| |
| | |
| .dt-header p {
| |
| font-size: 14px;
| |
| opacity: 0.9;
| |
| }
| |
| | |
| .dt-chat-area {
| |
| height: 400px;
| |
| overflow-y: auto;
| |
| padding: 20px;
| |
| background: #f8f9fa;
| |
| }
| |
| | |
| .dt-message {
| |
| margin-bottom: 15px;
| |
| padding: 12px 16px;
| |
| border-radius: 12px;
| |
| max-width: 80%;
| |
| word-wrap: break-word;
| |
| line-height: 1.5;
| |
| }
| |
| | |
| .dt-user-message {
| |
| background: linear-gradient(135deg, #4a90e2 0%, #667eea 100%);
| |
| color: white;
| |
| margin-left: auto;
| |
| text-align: right;
| |
| }
| |
| | |
| .dt-bot-message {
| |
| background: #e9ecef;
| |
| color: #333;
| |
| margin-right: auto;
| |
| }
| |
| | |
| .dt-input-area {
| |
| padding: 20px;
| |
| background: white;
| |
| border-top: 1px solid #eee;
| |
| display: flex;
| |
| gap: 10px;
| |
| }
| |
| | |
| .dt-message-input {
| |
| flex: 1;
| |
| padding: 12px 15px;
| |
| border: 2px solid #e1e5e9;
| |
| border-radius: 25px;
| |
| resize: none;
| |
| height: 50px;
| |
| font-size: 14px;
| |
| transition: border-color 0.3s;
| |
| }
| |
| | |
| .dt-message-input:focus {
| |
| outline: none;
| |
| border-color: #4a90e2;
| |
| box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.1);
| |
| }
| |
| | |
| .dt-button-group {
| |
| display: flex;
| |
| gap: 10px;
| |
| }
| |
| | |
| .dt-send-button, .dt-save-button {
| |
| padding: 12px 20px;
| |
| border: none;
| |
| border-radius: 25px;
| |
| cursor: pointer;
| |
| font-size: 14px;
| |
| font-weight: 500;
| |
| transition: transform 0.2s, box-shadow 0.2s;
| |
| }
| |
| | |
| .dt-send-button {
| |
| background: linear-gradient(135deg, #4a90e2 0%, #667eea 100%);
| |
| color: white;
| |
| }
| |
| | |
| .dt-save-button {
| |
| background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
| |
| color: white;
| |
| }
| |
| | |
| .dt-send-button:hover, .dt-save-button:hover {
| |
| transform: translateY(-2px);
| |
| box-shadow: 0 4px 12px rgba(0,0,0,0.15);
| |
| }
| |
| | |
| .dt-send-button:active, .dt-save-button:active {
| |
| transform: translateY(0);
| |
| }
| |
| | |
| .dt-typing-indicator {
| |
| display: inline-block;
| |
| padding: 12px 16px;
| |
| background: #e9ecef;
| |
| border-radius: 12px;
| |
| font-style: italic;
| |
| color: #666;
| |
| }
| |
| | |
| .dt-status-bar {
| |
| background: #f8f9fa;
| |
| padding: 10px 20px;
| |
| border-top: 1px solid #eee;
| |
| font-size: 12px;
| |
| color: #666;
| |
| text-align: center;
| |
| }
| |
| | |
| .dt-welcome-message {
| |
| text-align: center;
| |
| color: #666;
| |
| font-style: italic;
| |
| padding: 20px;
| |
| }
| |
| | |
| /* Markdown 样式 */
| |
| .markdown-content {
| |
| line-height: 1.6;
| |
| }
| |
| | |
| .markdown-content h1,
| |
| .markdown-content h2,
| |
| .markdown-content h3 {
| |
| margin: 10px 0 5px 0;
| |
| font-weight: bold;
| |
| color: #333;
| |
| }
| |
| | |
| .markdown-content h1 {
| |
| font-size: 1.5em;
| |
| border-bottom: 1px solid #eee;
| |
| padding-bottom: 5px;
| |
| }
| |
| | |
| .markdown-content h2 {
| |
| font-size: 1.3em;
| |
| }
| |
| | |
| .markdown-content h3 {
| |
| font-size: 1.1em;
| |
| }
| |
| | |
| .markdown-content p {
| |
| margin: 8px 0;
| |
| }
| |
| | |
| .markdown-content ul,
| |
| .markdown-content ol {
| |
| margin: 8px 0;
| |
| padding-left: 20px;
| |
| }
| |
| | |
| .markdown-content li {
| |
| margin: 4px 0;
| |
| }
| |
| | |
| .markdown-content strong {
| |
| font-weight: bold;
| |
| }
| |
| | |
| .markdown-content em {
| |
| font-style: italic;
| |
| }
| |
| | |
| .markdown-content code {
| |
| background: #f1f1f1;
| |
| padding: 2px 4px;
| |
| border-radius: 3px;
| |
| font-family: monospace;
| |
| font-size: 0.9em;
| |
| }
| |
| | |
| .markdown-content pre {
| |
| background: #f8f8f8;
| |
| padding: 10px;
| |
| border-radius: 5px;
| |
| overflow-x: auto;
| |
| margin: 10px 0;
| |
| }
| |
| | |
| .markdown-content pre code {
| |
| background: none;
| |
| padding: 0;
| |
| }
| |
| | |
| .markdown-content blockquote {
| |
| border-left: 3px solid #ccc;
| |
| padding-left: 10px;
| |
| margin: 10px 0;
| |
| color: #666;
| |
| font-style: italic;
| |
| }
| |
| | |
| .markdown-content a {
| |
| color: #4a90e2;
| |
| text-decoration: none;
| |
| }
| |
| | |
| .markdown-content a:hover {
| |
| text-decoration: underline;
| |
| }
| |
| | |
| @media (max-width: 600px) {
| |
| .dt-container {
| |
| margin: 10px;
| |
| border-radius: 10px;
| |
| }
| |
|
| |
| .dt-chat-area {
| |
| height: 300px;
| |
| }
| |
|
| |
| .dt-message {
| |
| max-width: 90%;
| |
| }
| |
|
| |
| .dt-input-area {
| |
| flex-direction: column;
| |
| }
| |
|
| |
| .dt-button-group {
| |
| flex-direction: row;
| |
| }
| |
| }
| |
| </style>
| |
| </head>
| |
| <body>
| |
| <!-- 这个容器可以嵌入到其他网页中 -->
| |
| <div class="dt-container">
| |
| <div class="dt-header">
| |
| <h1>🤖 赵老师的数字分身</h1>
| |
| <p>由于学校网络管理,偶遇重大节日或特殊日期时可能无法回答,请谅解~</p>
| |
| </div>
| |
|
| |
| <div class="dt-chat-area" id="dt-chat-messages">
| |
| <div class="dt-welcome-message">
| |
| 正在连接服务器...
| |
| </div>
| |
| </div>
| |
|
| |
| <div class="dt-input-area">
| |
| <textarea class="dt-message-input" id="dt-message-input" placeholder="请输入您的问题..."></textarea>
| |
| <div class="dt-button-group">
| |
| <button class="dt-send-button" id="dt-send-button">发送</button>
| |
| <button class="dt-save-button" id="dt-save-button">保存对话</button>
| |
| </div>
| |
| </div>
| |
|
| |
| <div class="dt-status-bar" id="dt-status-bar">
| |
| <span id="dt-connection-status">正在初始化...</span>
| |
| </div>
| |
| </div>
| |
| | |
| <script>
| |
| // 获取配置信息,允许通过属性传入
| |
| const container = document.querySelector('.dt-container');
| |
| const backendUrl = container.dataset.backendUrl || 'https://mns.uestc.cn/vs';
| |
| const knowledgeBase = container.dataset.knowledgeBase || 'default'; // 可指定知识库
| |
| | |
| document.addEventListener('DOMContentLoaded', function() {
| |
| const chatMessages = document.getElementById('dt-chat-messages');
| |
| const messageInput = document.getElementById('dt-message-input');
| |
| const sendButton = document.getElementById('dt-send-button');
| |
| const saveButton = document.getElementById('dt-save-button');
| |
| const connectionStatus = document.getElementById('dt-connection-status');
| |
|
| |
| // 生成唯一的会话ID
| |
| const sessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
| |
|
| |
| // 简单的Markdown解析函数
| |
| function renderMarkdown(text) {
| |
| // 替换标题
| |
| text = text.replace(/^### (.*$)/gim, '<h3>$1</h3>');
| |
| text = text.replace(/^## (.*$)/gim, '<h2>$1</h2>');
| |
| text = text.replace(/^# (.*$)/gim, '<h1>$1</h1>');
| |
|
| |
| // 替换粗体和斜体
| |
| text = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
| |
| text = text.replace(/\*(.*?)\*/g, '<em>$1</em>');
| |
|
| |
| // 替换行内代码
| |
| text = text.replace(/`(.*?)`/g, '<code>$1</code>');
| |
|
| |
| // 替换链接
| |
| text = text.replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2" target="_blank">$1</a>');
| |
|
| |
| // 替换引用
| |
| text = text.replace(/^> (.*$)/gim, '<blockquote>$1</blockquote>');
| |
|
| |
| // 替换列表
| |
| text = text.replace(/^\- (.*$)/gim, '<li>$1</li>');
| |
| text = text.replace(/^\* (.*$)/gim, '<li>$1</li>');
| |
|
| |
| // 将连续的<li>包装到<ul>中
| |
| text = text.replace(/(<li>.*<\/li>)/gs, '<ul>$1</ul>');
| |
|
| |
| // 替换换行符为<p>标签
| |
| text = text.replace(/\n\s*\n/g, '</p><p>');
| |
|
| |
| // 包装剩余文本到<p>标签中
| |
| text = '<p>' + text + '</p>';
| |
|
| |
| // 清理多余的<p>标签
| |
| text = text.replace(/<p>\s*<\/p>/g, '');
| |
|
| |
| return text;
| |
| }
| |
|
| |
| // 首次连接:发送"欢迎接入"获取欢迎信息
| |
| async function initializeConnection() {
| |
| try {
| |
| connectionStatus.textContent = '正在初始化...';
| |
| connectionStatus.style.color = '#ffc107';
| |
|
| |
| const response = await fetch(`${backendUrl}/query`, {
| |
| method: 'POST',
| |
| headers: {
| |
| 'Content-Type': 'application/json'
| |
| },
| |
| body: JSON.stringify({
| |
| query: "欢迎接入",
| |
| session_id: sessionId,
| |
| knowledge_base: knowledgeBase
| |
| })
| |
| });
| |
|
| |
| if (response.ok) {
| |
| const data = await response.json();
| |
| if (data.response) {
| |
| // 成功连接,显示欢迎信息
| |
| connectionStatus.textContent = '连接正常';
| |
| connectionStatus.style.color = '#28a745';
| |
|
| |
| // 清空欢迎消息并显示欢迎信息
| |
| chatMessages.innerHTML = '';
| |
| addMessage(data.response, 'bot');
| |
| return true;
| |
| }
| |
| }
| |
|
| |
| connectionStatus.textContent = '连接异常';
| |
| connectionStatus.style.color = '#dc3545';
| |
| return false;
| |
| } catch (error) {
| |
| console.error('初始化连接失败:', error);
| |
| connectionStatus.textContent = '连接失败';
| |
| connectionStatus.style.color = '#dc3545';
| |
| return false;
| |
| }
| |
| }
| |
|
| |
| // 定期连接测试(不返回消息,只检查连接状态)
| |
| async function testConnection() {
| |
| try {
| |
| const response = await fetch(`${backendUrl}/query`, {
| |
| method: 'POST',
| |
| headers: {
| |
| 'Content-Type': 'application/json'
| |
| },
| |
| body: JSON.stringify({
| |
| query: "连接测试",
| |
| session_id: sessionId,
| |
| knowledge_base: knowledgeBase
| |
| })
| |
| });
| |
|
| |
| if (response.ok) {
| |
| const data = await response.json();
| |
| // 连接测试只检查状态,不显示消息
| |
| if (data.response === "pong") {
| |
| connectionStatus.textContent = '连接正常';
| |
| connectionStatus.style.color = '#28a745';
| |
| return true;
| |
| }
| |
| }
| |
|
| |
| connectionStatus.textContent = '连接异常';
| |
| connectionStatus.style.color = '#dc3545';
| |
| return false;
| |
| } catch (error) {
| |
| console.error('连接测试失败:', error);
| |
| connectionStatus.textContent = '连接失败';
| |
| connectionStatus.style.color = '#dc3545';
| |
| return false;
| |
| }
| |
| }
| |
|
| |
| // 添加消息到聊天记录
| |
| function addMessage(text, sender) {
| |
| const messageDiv = document.createElement('div');
| |
| messageDiv.className = `dt-message dt-${sender}-message`;
| |
|
| |
| // 如果是机器人消息,应用Markdown渲染
| |
| if (sender === 'bot') {
| |
| messageDiv.innerHTML = `<div class="markdown-content">${renderMarkdown(text)}</div>`;
| |
| } else {
| |
| // 用户消息直接显示
| |
| messageDiv.textContent = text;
| |
| }
| |
|
| |
| chatMessages.appendChild(messageDiv);
| |
|
| |
| // 自动滚动到底部
| |
| chatMessages.scrollTop = chatMessages.scrollHeight;
| |
| }
| |
|
| |
| // 发送消息
| |
| async function sendMessage() {
| |
| const message = messageInput.value.trim();
| |
| if (!message) return;
| |
|
| |
| // 添加用户消息
| |
| addMessage(message, 'user');
| |
| messageInput.value = '';
| |
|
| |
| // 显示打字指示器
| |
| const typingIndicator = document.createElement('div');
| |
| typingIndicator.className = 'dt-message dt-bot-message';
| |
| typingIndicator.innerHTML = '<div class="dt-typing-indicator">正在思考...</div>';
| |
| chatMessages.appendChild(typingIndicator);
| |
| chatMessages.scrollTop = chatMessages.scrollHeight;
| |
|
| |
| try {
| |
| const response = await fetch(`${backendUrl}/query`, {
| |
| method: 'POST',
| |
| headers: {
| |
| 'Content-Type': 'application/json'
| |
| },
| |
| body: JSON.stringify({
| |
| query: message,
| |
| session_id: sessionId,
| |
| knowledge_base: knowledgeBase
| |
| })
| |
| });
| |
|
| |
| const data = await response.json();
| |
|
| |
| // 移除打字指示器
| |
| chatMessages.removeChild(typingIndicator);
| |
|
| |
| if (data.response) {
| |
| addMessage(data.response, 'bot');
| |
| } else {
| |
| addMessage('抱歉,服务器返回了错误信息。', 'bot');
| |
| }
| |
| } catch (error) {
| |
| console.error('Error:', error);
| |
| chatMessages.removeChild(typingIndicator);
| |
| addMessage('抱歉,连接服务器失败: ' + error.message, 'bot');
| |
| }
| |
| }
| |
|
| |
| // 保存对话历史
| |
| function saveChatHistory() {
| |
| const messages = chatMessages.querySelectorAll('.dt-message');
| |
| let historyText = `数字分身对话历史\n${new Date().toLocaleString()}\n知识库: ${knowledgeBase}\n会话ID: ${sessionId}\n\n`;
| |
|
| |
| messages.forEach(msg => {
| |
| if (msg.classList.contains('dt-user-message')) {
| |
| historyText += `用户: ${msg.textContent}\n`;
| |
| } else if (msg.classList.contains('dt-bot-message') && !msg.querySelector('.dt-typing-indicator')) {
| |
| historyText += `数字分身: ${msg.textContent}\n`;
| |
| }
| |
| });
| |
|
| |
| // 创建下载链接
| |
| const blob = new Blob([historyText], { type: 'text/plain;charset=utf-8' });
| |
| const url = URL.createObjectURL(blob);
| |
| const a = document.createElement('a');
| |
| a.href = url;
| |
| a.download = `数字分身对话_${knowledgeBase}_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.txt`;
| |
| document.body.appendChild(a);
| |
| a.click();
| |
| document.body.removeChild(a);
| |
| URL.revokeObjectURL(url);
| |
| }
| |
|
| |
| // 事件监听器
| |
| sendButton.addEventListener('click', sendMessage);
| |
| saveButton.addEventListener('click', saveChatHistory);
| |
|
| |
| messageInput.addEventListener('keypress', function(e) {
| |
| if (e.key === 'Enter' && !e.shiftKey) {
| |
| e.preventDefault();
| |
| sendMessage();
| |
| }
| |
| });
| |
|
| |
| // 页面加载时初始化连接并获取欢迎信息
| |
| initializeConnection();
| |
|
| |
| // 每30秒自动测试连接状态(不显示消息)
| |
| const connectionInterval = setInterval(async () => {
| |
| await testConnection();
| |
| }, 30000);
| |
| });
| |
| </script>
| |
| </body> | |
| </html> | | </html> |