Spaces:
Running
Running
<html lang="zh-CN"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>AI 对话系统</title> | |
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet"> | |
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" rel="stylesheet"> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css"> | |
<style> | |
.chat-bubble { | |
max-width: 80%; | |
padding: 10px; | |
border-radius: 10px; | |
margin-bottom: 10px; | |
} | |
.user-bubble { | |
background-color: #DCF8C6; | |
margin-left: auto; | |
} | |
.ai-bubble { | |
background-color: #E5E5EA; | |
margin-right: auto; | |
} | |
.status-indicator { | |
width: 10px; | |
height: 10px; | |
border-radius: 50%; | |
display: inline-block; | |
margin-right: 5px; | |
} | |
.status-active { | |
background-color: #4CAF50; | |
} | |
.status-inactive { | |
background-color: #9E9E9E; | |
} | |
pre code { | |
white-space: pre-wrap; | |
word-wrap: break-word; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-100"> | |
<div class="container mx-auto p-4 flex h-screen"> | |
<!-- 左侧对话窗口 --> | |
<div class="w-2/3 bg-white rounded-lg shadow-md p-4 mr-4 flex flex-col"> | |
<div id="chat-window" class="flex-grow overflow-y-auto mb-4"></div> | |
<div class="flex"> | |
<input id="user-input" type="text" class="flex-grow border rounded-l-lg p-2" placeholder="输入您的问题..."> | |
<button id="send-btn" class="bg-blue-500 text-white px-4 py-2 rounded-r-lg"> | |
<i class="fas fa-paper-plane"></i> 发送 | |
</button> | |
</div> | |
</div> | |
<!-- 右侧状态窗口 --> | |
<div class="w-1/3 bg-white rounded-lg shadow-md p-4 flex flex-col"> | |
<h2 class="text-xl font-bold mb-4">运行状态</h2> | |
<div class="mb-4"> | |
<div class="mb-2"> | |
<span class="status-indicator" id="main-model-status"></span> | |
主模型 | |
</div> | |
<div class="mb-2"> | |
<span class="status-indicator" id="sub-model-status"></span> | |
次模型 | |
</div> | |
<div class="mb-2"> | |
<span class="status-indicator" id="search-status"></span> | |
搜索 | |
</div> | |
<div> | |
<span class="status-indicator" id="email-status"></span> | |
邮件发送 | |
</div> | |
</div> | |
<div id="status-log" class="bg-gray-100 p-2 rounded flex-grow overflow-y-auto"></div> | |
<button id="settings-btn" class="mt-4 bg-gray-300 text-gray-800 px-4 py-2 rounded"> | |
<i class="fas fa-cog"></i> 设置 | |
</button> | |
</div> | |
</div> | |
<!-- 设置对话框 --> | |
<div id="settings-modal" class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden flex items-center justify-center"> | |
<div class="bg-white p-4 rounded-lg"> | |
<h2 class="text-xl font-bold mb-4">设置</h2> | |
<div class="mb-4"> | |
<label for="max-history" class="block mb-2">保留对话轮数:</label> | |
<input type="number" id="max-history" class="border rounded p-2" min="1" max="50" value="10"> | |
</div> | |
<div class="flex justify-end"> | |
<button id="save-settings" class="bg-blue-500 text-white px-4 py-2 rounded mr-2">保存</button> | |
<button id="cancel-settings" class="bg-gray-300 text-gray-800 px-4 py-2 rounded">取消</button> | |
</div> | |
</div> | |
</div> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.0.2/marked.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script> | |
<script> | |
const chatWindow = document.getElementById('chat-window'); | |
const userInput = document.getElementById('user-input'); | |
const sendBtn = document.getElementById('send-btn'); | |
const mainModelStatus = document.getElementById('main-model-status'); | |
const subModelStatus = document.getElementById('sub-model-status'); | |
const searchStatus = document.getElementById('search-status'); | |
const emailStatus = document.getElementById('email-status'); | |
const statusLog = document.getElementById('status-log'); | |
const settingsBtn = document.getElementById('settings-btn'); | |
const settingsModal = document.getElementById('settings-modal'); | |
const saveSettingsBtn = document.getElementById('save-settings'); | |
const cancelSettingsBtn = document.getElementById('cancel-settings'); | |
const maxHistoryInput = document.getElementById('max-history'); | |
let conversationHistory = []; | |
let maxHistory = 10; | |
function addMessage(content, isUser) { | |
const bubble = document.createElement('div'); | |
bubble.className = `chat-bubble ${isUser ? 'user-bubble' : 'ai-bubble'}`; | |
bubble.innerHTML = isUser ? content : marked.parse(content); | |
chatWindow.appendChild(bubble); | |
chatWindow.scrollTop = chatWindow.scrollHeight; | |
// 高亮代码块 | |
bubble.querySelectorAll('pre code').forEach((block) => { | |
hljs.highlightBlock(block); | |
}); | |
// 更新对话历史 | |
conversationHistory.push({ | |
role: isUser ? 'user' : 'assistant', | |
content: content | |
}); | |
// 限制历史记录长度 | |
if (conversationHistory.length > maxHistory * 2) { | |
conversationHistory = conversationHistory.slice(-maxHistory * 2); | |
} | |
} | |
function updateStatus(mainActive, subActive, searchActive, emailActive) { | |
mainModelStatus.className = `status-indicator ${mainActive ? 'status-active' : 'status-inactive'}`; | |
subModelStatus.className = `status-indicator ${subActive ? 'status-active' : 'status-inactive'}`; | |
searchStatus.className = `status-indicator ${searchActive ? 'status-active' : 'status-inactive'}`; | |
emailStatus.className = `status-indicator ${emailActive ? 'status-active' : 'status-inactive'}`; | |
} | |
function addStatusLog(message) { | |
const logEntry = document.createElement('div'); | |
logEntry.className = 'mb-2 p-2 bg-white rounded shadow'; | |
logEntry.textContent = message; | |
statusLog.appendChild(logEntry); | |
statusLog.scrollTop = statusLog.scrollHeight; | |
} | |
function displaySearchResults(results, type) { | |
const searchResults = document.createElement('details'); | |
searchResults.className = 'mb-2 p-2 bg-gray-100 rounded'; | |
const summary = document.createElement('summary'); | |
summary.textContent = type === 'papers' ? '论文搜索结果' : '搜索结果'; | |
searchResults.appendChild(summary); | |
const resultsList = document.createElement('ul'); | |
resultsList.className = 'mt-2'; | |
results.forEach(result => { | |
const li = document.createElement('li'); | |
li.className = 'mb-2'; | |
if (type === 'papers') { | |
li.innerHTML = ` | |
<strong>标题:</strong> ${result.标题}<br> | |
<strong>作者:</strong> ${result.作者}<br> | |
<strong>DOI:</strong> ${result.DOI}<br> | |
<strong>ISBN:</strong> ${result.ISBN}<br> | |
<strong>摘要:</strong> ${result.摘要} | |
`; | |
} else { | |
li.innerHTML = `<strong>${result.title}</strong><br>${result.body}`; | |
} | |
resultsList.appendChild(li); | |
}); | |
searchResults.appendChild(resultsList); | |
chatWindow.appendChild(searchResults); | |
} | |
async function sendMessage() { | |
const question = userInput.value.trim(); | |
if (!question) return; | |
addMessage(question, true); | |
userInput.value = ''; | |
updateStatus(false, false, false, false); | |
statusLog.innerHTML = ''; | |
addStatusLog('开始处理问题...'); | |
try { | |
const response = await fetch('/chat', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify({ | |
question, | |
history: conversationHistory.slice(-maxHistory * 2 + 1) // 发送历史消息,但不包括刚刚添加的用户消息 | |
}) | |
}); | |
const data = await response.json(); | |
data.status_log.forEach(log => addStatusLog(log)); | |
updateStatus(true, true, data.search_used, data.email_sent); | |
if (data.search_used) { | |
if (Array.isArray(data.search_results) && data.search_results.length > 0 && 'DOI' in data.search_results[0]) { | |
displaySearchResults(data.search_results, 'papers'); | |
} else { | |
displaySearchResults(data.search_results, 'web'); | |
} | |
} | |
addMessage(data.response, false); | |
addStatusLog('回答完成'); | |
} catch (error) { | |
console.error('Error:', error); | |
addMessage('发生错误,请稍后再试。', false); | |
updateStatus(false, false, false, false); | |
addStatusLog('发生错误'); | |
} | |
} | |
sendBtn.addEventListener('click', sendMessage); | |
userInput.addEventListener('keypress', (e) => { | |
if (e.key === 'Enter') { | |
sendMessage(); | |
} | |
}); | |
// 设置相关功能 | |
settingsBtn.addEventListener('click', () => { | |
settingsModal.classList.remove('hidden'); | |
maxHistoryInput.value = maxHistory; | |
}); | |
saveSettingsBtn.addEventListener('click', async () => { | |
const newMaxHistory = parseInt(maxHistoryInput.value); | |
if (newMaxHistory >= 1 && newMaxHistory <= 50) { | |
maxHistory = newMaxHistory; | |
try { | |
const response = await fetch('/settings', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify({ max_history: maxHistory }) | |
}); | |
const data = await response.json(); | |
if (data.status === 'success') { | |
alert('设置已保存'); | |
} else { | |
alert('保存设置时出错'); | |
} | |
} catch (error) { | |
console.error('Error:', error); | |
alert('保存设置时出错'); | |
} | |
settingsModal.classList.add('hidden'); | |
} else { | |
alert('请输入1到50之间的数字'); | |
} | |
}); | |
cancelSettingsBtn.addEventListener('click', () => { | |
settingsModal.classList.add('hidden'); | |
}); | |
</script> | |
</body> | |
</html> |