lexlepty/functioncall
0
1<!DOCTYPE html>2<html lang="zh-CN">3<head>4 <meta charset="UTF-8">5 <meta name="viewport" content="width=device-width, initial-scale=1.0">6 <title>AI 对话系统</title>7 <link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">8 <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" rel="stylesheet">9 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/atom-one-dark.min.css">10 <style>11 :root {12 --primary-color: #3498db;13 --secondary-color: #2ecc71;14 --background-color: #f5f5f5;15 --chat-background: #ffffff;16 --text-color: #333333;17 --chat-bubble-user: #e8f5fe;18 --chat-bubble-ai: #f0f0f0;19 --sidebar-background: #ffffff;20 --input-background: #ffffff;21 --send-button-color: #4CAF50;22 --console-background: #f8f9fa;23 --console-text: #495057;24 --fold-background: #f8f9fa;25 --fold-text: #495057;26 --border-color: transparent;27 --search-result-background: #f8f9fa;28 }29 .dark-mode {30 --primary-color: #3498db;31 --secondary-color: #2ecc71;32 --background-color: #1e2124;33 --chat-background: #36393f;34 --text-color: #dcddde;35 --chat-bubble-user: #4e5d94;36 --chat-bubble-ai: #40444b;37 --sidebar-background: #2f3136;38 --input-background: #40444b;39 --send-button-color: #7289da;40 --console-background: #2f3136;41 --console-text: #dcddde;42 --fold-background: #2f3136;43 --fold-text: #dcddde;44 --border-color: #ffffff;45 --search-result-background: #2f3136;46 }47 body {48 background-color: var(--background-color);49 color: var(--text-color);50 transition: all 0.3s ease;51 }52 .chat-container {53 background-color: var(--chat-background);54 transition: all 0.3s ease;55 border-radius: 8px;56 box-shadow: 0 2px 10px rgba(0,0,0,0.1);57 }58 .chat-bubble {59 max-width: 80%;60 padding: 12px;61 border-radius: 12px;62 margin-bottom: 12px;63 box-shadow: 0 1px 2px rgba(0,0,0,0.1);64 transition: all 0.3s ease;65 }66 .user-bubble {67 background-color: var(--chat-bubble-user);68 color: var(--text-color);69 margin-left: auto;70 border-bottom-right-radius: 4px;71 }72 .ai-bubble {73 background-color: var(--chat-bubble-ai);74 color: var(--text-color);75 margin-right: auto;76 border-bottom-left-radius: 4px;77 }78 .dark-mode .chat-bubble {79 border: 1px solid var(--border-color);80 }81 .status-indicator {82 width: 12px;83 height: 12px;84 border-radius: 50%;85 display: inline-block;86 margin-right: 8px;87 transition: all 0.3s ease;88 }89 .status-active {90 background-color: var(--secondary-color);91 }92 .status-inactive {93 background-color: #95a5a6;94 }95 .sidebar {96 background-color: var(--sidebar-background);97 transition: all 0.3s ease;98 border-radius: 8px;99 box-shadow: 0 2px 10px rgba(0,0,0,0.1);100 }101 #user-input {102 background-color: var(--input-background);103 color: var(--text-color);104 transition: all 0.3s ease;105 border-radius: 8px;106 padding-right: 40px;107 }108 #send-btn {109 position: absolute;110 right: 10px;111 top: 50%;112 transform: translateY(-50%);113 background: none;114 border: none;115 color: var(--send-button-color);116 font-size: 20px;117 cursor: pointer;118 transition: all 0.3s ease;119 }120 #send-btn:hover {121 opacity: 0.8;122 }123 .console {124 background-color: var(--console-background);125 color: var(--console-text);126 transition: all 0.3s ease;127 border-radius: 8px;128 box-shadow: 0 2px 10px rgba(0,0,0,0.1);129 }130 #status-log {131 background-color: var(--console-background);132 color: var(--console-text);133 transition: all 0.3s ease;134 border: 1px solid var(--border-color);135 }136 /* 滚动条样式 */137 ::-webkit-scrollbar {138 width: 8px;139 }140 ::-webkit-scrollbar-track {141 background: var(--chat-background);142 }143 ::-webkit-scrollbar-thumb {144 background: var(--primary-color);145 border-radius: 4px;146 }147 ::-webkit-scrollbar-thumb:hover {148 background: var(--secondary-color);149 }150 /* 代码块样式 */151 .hljs {152 background: var(--fold-background) !important;153 color: var(--fold-text) !important;154 }155 .search-results {156 background-color: var(--search-result-background);157 color: var(--text-color);158 border: 1px solid var(--border-color);159 }160 .search-results summary {161 cursor: pointer;162 font-weight: bold;163 }164 .search-results ul {165 list-style-type: none;166 padding-left: 0;167 }168 .search-results li {169 background-color: var(--chat-bubble-ai);170 border: 1px solid var(--border-color);171 }172 /* 控制台消息条目样式 */173 #status-log div {174 background-color: var(--console-background);175 color: var(--console-text);176 border: 1px solid var(--border-color);177 }178 @media (max-width: 768px) {179 .sidebar {180 position: fixed;181 right: -300px;182 top: 0;183 bottom: 0;184 width: 300px;185 z-index: 1000;186 transition: right 0.3s ease-in-out;187 }188 .sidebar.open {189 right: 0;190 }191 .sidebar-overlay {192 display: none;193 position: fixed;194 top: 0;195 left: 0;196 right: 0;197 bottom: 0;198 background-color: rgba(0, 0, 0, 0.5);199 z-index: 999;200 }201 .sidebar-overlay.show {202 display: block;203 }204 }205</style>206<body class="h-screen flex flex-col md:flex-row p-4">207 <!-- 主聊天窗口 -->208 <div class="chat-container flex-grow md:w-3/4 p-4 flex flex-col h-screen mr-4">209 <div id="chat-window" class="flex-grow overflow-y-auto mb-4 p-4 rounded-lg shadow-inner"></div>210 <div class="relative">211 <input id="user-input" type="text" class="w-full border p-2" placeholder="输入您的问题...">212 <button id="send-btn" class="text-2xl">213 <i class="fas fa-paper-plane"></i>214 </button>215 </div>216 </div>217 218 <!-- 右侧控制台 -->219 <div class="sidebar console md:w-1/4 p-4 h-screen overflow-y-auto">220 <h2 class="text-xl font-bold mb-4">运行状态</h2>221 <div class="mb-4">222 <div class="model-status">223 <span class="status-indicator" id="main-model-status"></span>224 <span class="model-name">主模型</span>225 <span class="model-action" id="main-model-action"></span>226 </div>227 <div class="model-status">228 <span class="status-indicator" id="sub-model-1-status"></span>229 <span class="model-name">次级模型1</span>230 <span class="model-action" id="sub-model-1-action"></span>231 </div>232 <div class="model-status">233 <span class="status-indicator" id="sub-model-2-status"></span>234 <span class="model-name">次级模型2</span>235 <span class="model-action" id="sub-model-2-action"></span>236 </div>237 <div class="model-status">238 <span class="status-indicator" id="arbitration-model-status"></span>239 <span class="model-name">裁决模型</span>240 <span class="model-action" id="arbitration-model-action"></span>241 </div>242 <div class="model-status">243 <span class="status-indicator" id="search-status"></span>244 <span class="model-name">搜索</span>245 <span class="model-action" id="search-action"></span>246 </div>247 <div class="model-status">248 <span class="status-indicator" id="email-status"></span>249 <span class="model-name">邮件发送</span>250 <span class="model-action" id="email-action"></span>251 </div>252 </div>253 <div id="status-log" class="p-2 rounded h-64 overflow-y-auto mb-4"></div>254 <button id="settings-btn" class="w-full bg-gray-300 dark:bg-gray-600 text-gray-800 dark:text-white px-4 py-2 rounded hover:bg-gray-400 dark:hover:bg-gray-500 transition duration-200">255 <i class="fas fa-cog"></i> 设置256 </button>257 </div>258 259 <!-- 移动端侧边栏切换按钮 -->260 <button id="sidebar-toggle" class="md:hidden fixed top-4 right-4 bg-gray-300 dark:bg-gray-600 text-gray-800 dark:text-white p-2 rounded-full shadow-lg hover:bg-gray-400 dark:hover:bg-gray-500 transition duration-200 z-50">261 <i class="fas fa-bars"></i>262 </button>263 264 <!-- 移动端侧边栏遮罩 -->265 <div id="sidebar-overlay" class="sidebar-overlay"></div>266 267 <!-- 暗色模式切换按钮 -->268 <button id="dark-mode-toggle" class="fixed top-4 left-4 bg-gray-300 dark:bg-gray-600 text-gray-800 dark:text-white p-2 rounded-full shadow-lg hover:bg-gray-400 dark:hover:bg-gray-500 transition duration-200">269 <i class="fas fa-moon"></i>270 </button>271 272 <!-- 设置对话框 -->273 <div id="settings-modal" class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden flex items-center justify-center z-50">274 <div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-xl">275 <h2 class="text-xl font-bold mb-4">设置</h2>276 <div class="mb-4">277 <label for="max-history" class="block mb-2">保留对话轮数:</label>278 <input type="number" id="max-history" class="border rounded p-2 w-full dark:bg-gray-700 dark:text-white" min="1" max="50" value="10">279 </div>280 <div class="flex justify-end">281 <button id="save-settings" class="bg-blue-500 text-white px-4 py-2 rounded mr-2 hover:bg-blue-600 transition duration-200">保存</button>282 <button id="cancel-settings" class="bg-gray-300 dark:bg-gray-600 text-gray-800 dark:text-white px-4 py-2 rounded hover:bg-gray-400 dark:hover:bg-gray-500 transition duration-200">取消</button>283 </div>284 </div>285 </div>286 287 <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.0.2/marked.min.js"></script>288 <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>289 <script>290 const chatWindow = document.getElementById('chat-window');291 const userInput = document.getElementById('user-input');292 const sendBtn = document.getElementById('send-btn');293 const statusLog = document.getElementById('status-log');294 const settingsBtn = document.getElementById('settings-btn');295 const settingsModal = document.getElementById('settings-modal');296 const saveSettingsBtn = document.getElementById('save-settings');297 const cancelSettingsBtn = document.getElementById('cancel-settings');298 const maxHistoryInput = document.getElementById('max-history');299 const darkModeToggle = document.getElementById('dark-mode-toggle');300 const sidebarToggle = document.getElementById('sidebar-toggle');301 const sidebar = document.querySelector('.sidebar');302 const sidebarOverlay = document.getElementById('sidebar-overlay');303 let conversationHistory = [];304 let maxHistory = 10;305 function addMessage(content, isUser) {306 const bubble = document.createElement('div');307 bubble.className = `chat-bubble ${isUser ? 'user-bubble' : 'ai-bubble'}`;308 bubble.innerHTML = isUser ? content : marked.parse(content);309 chatWindow.appendChild(bubble);310 chatWindow.scrollTop = chatWindow.scrollHeight;311 312 bubble.querySelectorAll('pre code').forEach((block) => {313 hljs.highlightBlock(block);314 });315 316 conversationHistory.push({317 role: isUser ? 'user' : 'assistant',318 content: content319 });320 321 if (conversationHistory.length > maxHistory * 2) {322 conversationHistory = conversationHistory.slice(-maxHistory * 2);323 }324 }325 function updateStatus(main, sub1, sub2, arbitration, search, email) {326 updateModelStatus('main-model', main);327 updateModelStatus('sub-model-1', sub1);328 updateModelStatus('sub-model-2', sub2);329 updateModelStatus('arbitration-model', arbitration);330 updateModelStatus('search', search);331 updateModelStatus('email', email);332 }333 function updateModelStatus(modelId, isActive, action = '') {334 const statusElement = document.getElementById(`${modelId}-status`);335 const actionElement = document.getElementById(`${modelId}-action`);336 statusElement.className = `status-indicator ${isActive ? 'status-active' : 'status-inactive'}`;337 actionElement.textContent = action;338 }339 function addStatusLog(message) {340 const logEntry = document.createElement('div');341 logEntry.className = 'mb-2 p-2 bg-white dark:bg-gray-700 rounded shadow';342 logEntry.textContent = message;343 statusLog.appendChild(logEntry);344 statusLog.scrollTop = statusLog.scrollHeight;345 }346 function displaySearchResults(results, type) {347 const searchResults = document.createElement('details');348 searchResults.className = 'search-results mb-2 p-2 rounded';349 const summary = document.createElement('summary');350 summary.textContent = type === 'papers' ? '论文搜索结果' : '搜索结果';351 searchResults.appendChild(summary);352 const resultsList = document.createElement('ul');353 resultsList.className = 'mt-2';354 results.forEach(result => {355 const li = document.createElement('li');356 li.className = 'mb-2 p-2 rounded';357 if (type === 'papers') {358 li.innerHTML = `359 <strong>标题:</strong> ${result.标题}<br>360 <strong>作者:</strong> ${result.作者}<br>361 <strong>DOI:</strong> ${result.DOI}<br>362 <strong>ISBN:</strong> ${result.ISBN}<br>363 <strong>摘要:</strong> ${result.摘要}364 `;365 } else {366 li.innerHTML = `<strong>${result.title}</strong><br>${result.body}`;367 }368 resultsList.appendChild(li);369 });370 searchResults.appendChild(resultsList);371 chatWindow.appendChild(searchResults);372 }373 function updateStatusFromLog(log) {374 if (log.includes("主模型:")) {375 updateModelStatus('main-model', true, log.split("主模型:")[1]);376 } else if (log.includes("次级模型1:")) {377 updateModelStatus('sub-model-1', true, log.split("次级模型1:")[1]);378 } else if (log.includes("次级模型2:")) {379 updateModelStatus('sub-model-2', true, log.split("次级模型2:")[1]);380 } else if (log.includes("裁决模型:")) {381 updateModelStatus('arbitration-model', true, log.split("裁决模型:")[1]);382 } else if (log.includes("搜索:")) {383 updateModelStatus('search', true, log.split("搜索:")[1]);384 } else if (log.includes("邮件:")) {385 updateModelStatus('email', true, log.split("邮件:")[1]);386 }387 }388 async function sendMessage() {389 const question = userInput.value.trim();390 if (!question) return;391 addMessage(question, true);392 userInput.value = '';393 updateStatus(false, false, false, false);394 statusLog.innerHTML = '';395 addStatusLog('开始处理问题...');396 try {397 const response = await fetch('/chat', {398 method: 'POST',399 headers: {400 'Content-Type': 'application/json'401 },402 body: JSON.stringify({ 403 question,404 history: conversationHistory.slice(-maxHistory * 2 + 1)405 })406 });407 const data = await response.json();408 data.status_log.forEach(log => addStatusLog(log));409 updateStatus(410 data.status_log.includes("主模型:回答生成完成"),411 data.status_log.some(log => log.includes("次级模型")),412 data.search_used,413 data.email_sent414 );415 if (data.search_results) {416 if (Array.isArray(data.search_results) && data.search_results.length > 0 && 'DOI' in data.search_results[0]) {417 displaySearchResults(data.search_results, 'papers');418 } else {419 displaySearchResults(data.search_results, 'web');420 }421 }422 addMessage(data.response, false);423 addStatusLog('回答完成');424 } catch (error) {425 console.error('Error:', error);426 addMessage('发生错误,请稍后再试。', false);427 updateStatus(false, false, false, false);428 addStatusLog('发生错误');429 }430 }431 sendBtn.addEventListener('click', sendMessage);432 433 userInput.addEventListener('keypress', (e) => {434 if (e.key === 'Enter') {435 sendMessage();436 }437 });438 settingsBtn.addEventListener('click', () => {439 settingsModal.classList.remove('hidden');440 maxHistoryInput.value = maxHistory;441 });442 saveSettingsBtn.addEventListener('click', async () => {443 const newMaxHistory = parseInt(maxHistoryInput.value);444 if (newMaxHistory >= 1 && newMaxHistory <= 50) {445 maxHistory = newMaxHistory;446 try {447 const response = await fetch('/settings', {448 method: 'POST',449 headers: {450 'Content-Type': 'application/json'451 },452 body: JSON.stringify({ max_history: maxHistory })453 });454 const data = await response.json();455 if (data.status === 'success') {456 alert('设置已保存');457 } else {458 alert('保存设置时出错');459 }460 } catch (error) {461 console.error('Error:', error);462 alert('保存设置时出错');463 }464 settingsModal.classList.add('hidden');465 } else {466 alert('请输入1到50之间的数字');467 }468 });469 cancelSettingsBtn.addEventListener('click', () => {470 settingsModal.classList.add('hidden');471 });472 darkModeToggle.addEventListener('click', () => {473 document.body.classList.toggle('dark-mode');474 updateDarkModeIcon();475 });476 function updateDarkModeIcon() {477 const icon = darkModeToggle.querySelector('i');478 if (document.body.classList.contains('dark-mode')) {479 icon.classList.remove('fa-moon');480 icon.classList.add('fa-sun');481 } else {482 icon.classList.remove('fa-sun');483 icon.classList.add('fa-moon');484 }485 }486 function initDarkMode() {487 if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {488 document.body.classList.add('dark-mode');489 }490 updateDarkModeIcon();491 }492 window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {493 if (e.matches) {494 document.body.classList.add('dark-mode');495 } else {496 document.body.classList.remove('dark-mode');497 }498 updateDarkModeIcon();499 });500 sidebarToggle.addEventListener('click', () => {501 sidebar.classList.toggle('open');502 sidebarOverlay.classList.toggle('show');503 });504 sidebarOverlay.addEventListener('click', () => {505 sidebar.classList.remove('open');506 sidebarOverlay.classList.remove('show');507 });508 window.addEventListener('load', () => {509 initDarkMode();510 });511 window.addEventListener('resize', () => {512 if (window.innerWidth >= 768) {513 sidebar.classList.remove('open');514 sidebarOverlay.classList.remove('show');515 }516 });517 // 可以在这里添加任何其他需要的 JavaScript 功能518 </script>519</body>520</html>