|
<!DOCTYPE html> |
|
<html lang="zh-CN"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>AI学习助手</title> |
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"> |
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css"> |
|
|
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css"> |
|
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/github.min.css"> |
|
<style> |
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); |
|
|
|
:root { |
|
|
|
--primary-color: #0f2d49; |
|
--primary-light: #234a70; |
|
--secondary-color: #4a6cfd; |
|
--secondary-light: #7b91ff; |
|
--tertiary-color: #f7f9fe; |
|
|
|
|
|
--success-color: #10b981; |
|
--success-light: #d1fae5; |
|
--warning-color: #f59e0b; |
|
--warning-light: #fef3c7; |
|
--danger-color: #ef4444; |
|
--danger-light: #fee2e2; |
|
--info-color: #3b82f6; |
|
--info-light: #dbeafe; |
|
|
|
|
|
--neutral-50: #f9fafb; |
|
--neutral-100: #f3f4f6; |
|
--neutral-200: #e5e7eb; |
|
--neutral-300: #d1d5db; |
|
--neutral-400: #9ca3af; |
|
--neutral-500: #6b7280; |
|
--neutral-600: #4b5563; |
|
--neutral-700: #374151; |
|
--neutral-800: #1f2937; |
|
--neutral-900: #111827; |
|
|
|
|
|
--border-radius-sm: 0.25rem; |
|
--border-radius: 0.375rem; |
|
--border-radius-lg: 0.5rem; |
|
--border-radius-xl: 0.75rem; |
|
--border-radius-xxl: 1rem; |
|
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); |
|
--shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06); |
|
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); |
|
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); |
|
--shadow-inner: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06); |
|
--transition-base: all 0.2s ease-in-out; |
|
--transition-smooth: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
|
--font-family: 'Inter', 'PingFang SC', 'Microsoft YaHei', sans-serif; |
|
} |
|
|
|
body { |
|
font-family: var(--font-family); |
|
margin: 0; |
|
padding: 0; |
|
height: 100vh; |
|
background-color: var(--neutral-50); |
|
color: var(--neutral-800); |
|
display: flex; |
|
flex-direction: column; |
|
-webkit-font-smoothing: antialiased; |
|
-moz-osx-font-smoothing: grayscale; |
|
} |
|
|
|
|
|
.header { |
|
background: linear-gradient(135deg, var(--primary-color), var(--primary-light)); |
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1); |
|
color: white; |
|
padding: 1rem 1.5rem; |
|
box-shadow: var(--shadow-md); |
|
position: relative; |
|
z-index: 10; |
|
} |
|
|
|
.header-content { |
|
max-width: 1600px; |
|
margin: 0 auto; |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
} |
|
|
|
.header h1 { |
|
margin: 0; |
|
font-size: 1.5rem; |
|
font-weight: 600; |
|
color: white; |
|
display: flex; |
|
align-items: center; |
|
} |
|
|
|
.header h1 i { |
|
margin-right: 0.75rem; |
|
color: rgba(255, 255, 255, 0.8); |
|
} |
|
|
|
.header .badge { |
|
background-color: rgba(255, 255, 255, 0.15); |
|
color: white; |
|
font-weight: 500; |
|
padding: 0.35em 0.75em; |
|
font-size: 0.85rem; |
|
border-radius: 9999px; |
|
} |
|
|
|
|
|
.main-container { |
|
flex: 1; |
|
display: flex; |
|
max-width: 1600px; |
|
margin: 0 auto; |
|
padding: 1.5rem; |
|
width: 100%; |
|
box-sizing: border-box; |
|
height: calc(100vh - 70px); |
|
overflow: hidden; |
|
} |
|
|
|
.chat-container { |
|
flex: 1; |
|
display: flex; |
|
flex-direction: column; |
|
background-color: white; |
|
border-radius: var(--border-radius-xl); |
|
box-shadow: var(--shadow); |
|
overflow: hidden; |
|
height: 100%; |
|
max-width: 800px; |
|
margin: 0 auto; |
|
transition: var(--transition-smooth); |
|
position: relative; |
|
} |
|
|
|
|
|
.chat-messages { |
|
flex: 1; |
|
overflow-y: auto; |
|
padding: 1.5rem; |
|
display: flex; |
|
flex-direction: column; |
|
background-color: var(--neutral-50); |
|
gap: 1rem; |
|
scroll-behavior: smooth; |
|
} |
|
|
|
|
|
.chat-messages::-webkit-scrollbar { |
|
width: 6px; |
|
height: 6px; |
|
} |
|
|
|
.chat-messages::-webkit-scrollbar-track { |
|
background: var(--neutral-100); |
|
border-radius: 10px; |
|
} |
|
|
|
.chat-messages::-webkit-scrollbar-thumb { |
|
background: var(--neutral-300); |
|
border-radius: 10px; |
|
} |
|
|
|
.chat-messages::-webkit-scrollbar-thumb:hover { |
|
background: var(--neutral-400); |
|
} |
|
|
|
|
|
.message { |
|
max-width: 85%; |
|
border-radius: var(--border-radius-lg); |
|
padding: 1rem; |
|
position: relative; |
|
line-height: 1.5; |
|
box-shadow: var(--shadow-sm); |
|
overflow-wrap: break-word; |
|
word-wrap: break-word; |
|
hyphens: auto; |
|
} |
|
|
|
.message .message-content { |
|
padding: 0 10px; |
|
margin: 0; |
|
} |
|
|
|
.user-message { |
|
background: linear-gradient(135deg, #e9f5ff, #c2e4ff); |
|
align-self: flex-end; |
|
color: var(--primary-color); |
|
border-bottom-right-radius: 0.2rem; |
|
border-left: 1px solid rgba(74, 108, 253, 0.1); |
|
border-top: 1px solid rgba(255, 255, 255, 0.5); |
|
} |
|
|
|
.bot-message { |
|
background-color: white; |
|
align-self: flex-start; |
|
color: var(--neutral-800); |
|
border-bottom-left-radius: 0.2rem; |
|
border-left: 3px solid var(--secondary-color); |
|
box-shadow: var(--shadow); |
|
} |
|
|
|
|
|
.input-container { |
|
padding: 1rem 1.5rem; |
|
border-top: 1px solid var(--neutral-200); |
|
background-color: white; |
|
position: relative; |
|
} |
|
|
|
.input-row { |
|
display: flex; |
|
gap: 0.75rem; |
|
position: relative; |
|
} |
|
|
|
.input-field { |
|
flex: 1; |
|
padding: 0.85rem 1rem; |
|
border: 1px solid var(--neutral-200); |
|
border-radius: var(--border-radius-lg); |
|
resize: none; |
|
font-size: 0.95rem; |
|
box-shadow: var(--shadow-inner); |
|
transition: var(--transition-base); |
|
} |
|
|
|
.input-field:focus { |
|
outline: none; |
|
border-color: var(--secondary-color); |
|
box-shadow: 0 0 0 3px rgba(74, 108, 253, 0.15); |
|
} |
|
|
|
.send-button { |
|
padding: 0 1.25rem; |
|
background: linear-gradient(135deg, var(--secondary-color), var(--secondary-light)); |
|
color: white; |
|
border: none; |
|
border-radius: var(--border-radius-lg); |
|
cursor: pointer; |
|
font-weight: 500; |
|
transition: var(--transition-base); |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
} |
|
|
|
.send-button:hover { |
|
box-shadow: 0 4px 10px rgba(74, 108, 253, 0.25); |
|
transform: translateY(-1px); |
|
} |
|
|
|
.send-button:disabled { |
|
background: var(--neutral-300); |
|
cursor: not-allowed; |
|
transform: none; |
|
box-shadow: none; |
|
} |
|
|
|
|
|
.plugin-container { |
|
display: none; |
|
flex-direction: column; |
|
background-color: white; |
|
border-radius: var(--border-radius-xl); |
|
box-shadow: var(--shadow); |
|
overflow: hidden; |
|
height: 100%; |
|
flex: 2; |
|
margin-left: 1.5rem; |
|
transition: var(--transition-smooth); |
|
} |
|
|
|
.plugin-header { |
|
padding: 0.85rem 1.25rem; |
|
border-bottom: 1px solid var(--neutral-200); |
|
background-color: var(--neutral-50); |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
} |
|
|
|
.plugin-title { |
|
margin: 0; |
|
font-size: 1rem; |
|
font-weight: 600; |
|
color: var(--primary-color); |
|
display: flex; |
|
align-items: center; |
|
} |
|
|
|
.plugin-title i { |
|
margin-right: 0.5rem; |
|
color: var(--secondary-color); |
|
} |
|
|
|
.plugin-close { |
|
width: 32px; |
|
height: 32px; |
|
background: var(--neutral-100); |
|
border: none; |
|
border-radius: 50%; |
|
cursor: pointer; |
|
font-size: 0.85rem; |
|
color: var(--neutral-700); |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
transition: var(--transition-base); |
|
} |
|
|
|
.plugin-close:hover { |
|
background-color: var(--neutral-200); |
|
color: var(--neutral-900); |
|
} |
|
|
|
.plugin-content { |
|
flex: 1; |
|
overflow-y: auto; |
|
padding: 0; |
|
background-color: var(--neutral-50); |
|
} |
|
|
|
|
|
.main-container.with-plugin .chat-container { |
|
max-width: 380px; |
|
margin: 0; |
|
} |
|
|
|
|
|
pre { |
|
margin: 1rem 0; |
|
padding: 1rem; |
|
background-color: var(--neutral-800); |
|
border-radius: var(--border-radius); |
|
overflow-x: auto; |
|
position: relative; |
|
} |
|
|
|
pre code { |
|
font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace; |
|
font-size: 0.9rem; |
|
color: #e5e7eb; |
|
padding: 0; |
|
background: none; |
|
} |
|
|
|
.copy-button { |
|
position: absolute; |
|
top: 0.5rem; |
|
right: 0.5rem; |
|
padding: 0.25rem 0.5rem; |
|
background-color: rgba(255, 255, 255, 0.1); |
|
color: rgba(255, 255, 255, 0.6); |
|
border: none; |
|
border-radius: var(--border-radius-sm); |
|
font-size: 0.75rem; |
|
cursor: pointer; |
|
transition: var(--transition-base); |
|
display: flex; |
|
align-items: center; |
|
gap: 0.25rem; |
|
} |
|
|
|
.copy-button:hover { |
|
background-color: rgba(255, 255, 255, 0.2); |
|
color: rgba(255, 255, 255, 0.9); |
|
} |
|
|
|
|
|
.code-result { |
|
margin-top: 0.5rem; |
|
padding: 0.75rem; |
|
background-color: var(--neutral-900); |
|
border-radius: var(--border-radius); |
|
color: white; |
|
font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace; |
|
font-size: 0.85rem; |
|
white-space: pre-wrap; |
|
} |
|
|
|
|
|
code:not(pre code) { |
|
font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace; |
|
font-size: 0.9em; |
|
color: var(--primary-color); |
|
background-color: var(--neutral-100); |
|
padding: 0.2em 0.4em; |
|
border-radius: 3px; |
|
} |
|
|
|
|
|
.reference-container { |
|
margin-top: 1rem; |
|
border-top: 1px dashed var(--neutral-300); |
|
padding-top: 0.75rem; |
|
} |
|
|
|
.reference-toggle { |
|
color: var(--neutral-600); |
|
font-size: 0.85rem; |
|
font-weight: 500; |
|
cursor: pointer; |
|
display: flex; |
|
align-items: center; |
|
transition: var(--transition-base); |
|
} |
|
|
|
.reference-toggle:hover { |
|
color: var(--secondary-color); |
|
} |
|
|
|
.reference-toggle i { |
|
margin-right: 0.35rem; |
|
font-size: 0.9rem; |
|
} |
|
|
|
.reference-content { |
|
display: none; |
|
margin-top: 0.75rem; |
|
padding: 0.75rem; |
|
background-color: var(--neutral-100); |
|
border-radius: var(--border-radius); |
|
font-size: 0.85rem; |
|
} |
|
|
|
.reference-item { |
|
margin-bottom: 0.75rem; |
|
padding-bottom: 0.75rem; |
|
border-bottom: 1px solid var(--neutral-200); |
|
} |
|
|
|
.reference-item:last-child { |
|
margin-bottom: 0; |
|
padding-bottom: 0; |
|
border-bottom: none; |
|
} |
|
|
|
.reference-item-source { |
|
color: var(--neutral-600); |
|
font-size: 0.8rem; |
|
margin-top: 0.25rem; |
|
} |
|
|
|
|
|
.welcome-message { |
|
display: flex; |
|
flex-direction: column; |
|
align-items: center; |
|
text-align: center; |
|
margin-bottom: 1.5rem; |
|
} |
|
|
|
.welcome-icon { |
|
width: 64px; |
|
height: 64px; |
|
background: linear-gradient(135deg, var(--secondary-color), var(--secondary-light)); |
|
border-radius: 50%; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
margin-bottom: 1rem; |
|
color: white; |
|
font-size: 2rem; |
|
box-shadow: 0 4px 12px rgba(74, 108, 253, 0.25); |
|
} |
|
|
|
.welcome-title { |
|
font-size: 1.5rem; |
|
font-weight: 600; |
|
margin-bottom: 0.5rem; |
|
color: var(--primary-color); |
|
} |
|
|
|
.welcome-description { |
|
color: var(--neutral-700); |
|
max-width: 400px; |
|
margin-bottom: 0; |
|
} |
|
|
|
|
|
.visualization-result, .mindmap-result { |
|
padding: 1.5rem; |
|
text-align: center; |
|
} |
|
|
|
.visualization-image, .mindmap-image { |
|
max-width: 100%; |
|
border-radius: var(--border-radius); |
|
box-shadow: var(--shadow); |
|
} |
|
|
|
|
|
@media (max-width: 1200px) { |
|
.main-container.with-plugin .chat-container { |
|
max-width: 320px; |
|
} |
|
} |
|
|
|
@media (max-width: 992px) { |
|
.main-container { |
|
flex-direction: column; |
|
padding: 1rem; |
|
} |
|
|
|
.main-container.with-plugin .chat-container { |
|
max-width: 100%; |
|
margin-bottom: 1rem; |
|
height: 40vh; |
|
} |
|
|
|
.plugin-container { |
|
margin-left: 0; |
|
height: calc(60vh - 32px); |
|
} |
|
} |
|
|
|
@media (max-width: 768px) { |
|
.main-container { |
|
padding: 0.75rem; |
|
} |
|
|
|
.message { |
|
max-width: 95%; |
|
} |
|
} |
|
|
|
|
|
.katex { |
|
font-size: 1.1em; |
|
} |
|
|
|
.message h1, .message h2, .message h3, |
|
.message h4, .message h5, .message h6 { |
|
margin-top: 1em; |
|
margin-bottom: 0.5em; |
|
line-height: 1.3; |
|
} |
|
|
|
.message h1 { |
|
font-size: 1.6em; |
|
border-bottom: 1px solid var(--neutral-200); |
|
padding-bottom: 0.3em; |
|
} |
|
|
|
.message h2 { |
|
font-size: 1.4em; |
|
border-bottom: 1px solid var(--neutral-200); |
|
padding-bottom: 0.3em; |
|
} |
|
|
|
.message h3 { |
|
font-size: 1.2em; |
|
} |
|
|
|
.message h4 { |
|
font-size: 1.1em; |
|
} |
|
|
|
.message h5, .message h6 { |
|
font-size: 1em; |
|
} |
|
|
|
.message p { |
|
margin: 0.5em 0; |
|
} |
|
|
|
.message ul, .message ol { |
|
margin: 0.5em 0; |
|
padding-left: 1.5em; |
|
} |
|
|
|
.message li { |
|
margin: 0.25em 0; |
|
} |
|
|
|
.message blockquote { |
|
margin: 0.5em 0; |
|
padding-left: 1em; |
|
border-left: 4px solid var(--neutral-300); |
|
color: var(--neutral-700); |
|
} |
|
|
|
.message img { |
|
max-width: 100%; |
|
border-radius: var(--border-radius); |
|
} |
|
|
|
.message table { |
|
border-collapse: collapse; |
|
margin: 1em 0; |
|
width: 100%; |
|
} |
|
|
|
.message table th, |
|
.message table td { |
|
border: 1px solid var(--neutral-300); |
|
padding: 0.5em; |
|
} |
|
|
|
.message table th { |
|
background-color: var(--neutral-100); |
|
font-weight: 600; |
|
} |
|
|
|
.message table tr:nth-child(even) { |
|
background-color: var(--neutral-50); |
|
} |
|
|
|
|
|
@keyframes fadeIn { |
|
from { opacity: 0; transform: translateY(10px); } |
|
to { opacity: 1; transform: translateY(0); } |
|
} |
|
|
|
.message { |
|
animation: fadeIn 0.3s ease forwards; |
|
} |
|
|
|
|
|
.input-suggestions { |
|
position: absolute; |
|
bottom: 100%; |
|
left: 0; |
|
right: 0; |
|
background-color: white; |
|
border-top-left-radius: var(--border-radius-lg); |
|
border-top-right-radius: var(--border-radius-lg); |
|
box-shadow: var(--shadow-md); |
|
padding: 0.75rem; |
|
display: none; |
|
border: 1px solid var(--neutral-200); |
|
border-bottom: none; |
|
} |
|
|
|
.suggestion-title { |
|
font-size: 0.85rem; |
|
font-weight: 600; |
|
color: var(--neutral-700); |
|
margin-bottom: 0.5rem; |
|
} |
|
|
|
.suggestion-buttons { |
|
display: flex; |
|
flex-wrap: wrap; |
|
gap: 0.5rem; |
|
} |
|
|
|
.suggestion-button { |
|
padding: 0.5rem 0.75rem; |
|
background-color: var(--neutral-100); |
|
border: 1px solid var(--neutral-200); |
|
border-radius: var(--border-radius); |
|
font-size: 0.85rem; |
|
color: var(--neutral-800); |
|
cursor: pointer; |
|
transition: var(--transition-base); |
|
} |
|
|
|
.suggestion-button:hover { |
|
background-color: var(--secondary-light); |
|
color: white; |
|
border-color: var(--secondary-light); |
|
} |
|
|
|
|
|
.typing-indicator { |
|
display: inline-block; |
|
margin-left: 5px; |
|
} |
|
|
|
.typing-dot { |
|
display: inline-block; |
|
width: 6px; |
|
height: 6px; |
|
border-radius: 50%; |
|
background-color: var(--neutral-500); |
|
margin-right: 3px; |
|
animation: typingDot 1.4s infinite ease-in-out; |
|
} |
|
|
|
.typing-dot:nth-child(1) { animation-delay: 0s; } |
|
.typing-dot:nth-child(2) { animation-delay: 0.2s; } |
|
.typing-dot:nth-child(3) { animation-delay: 0.4s; } |
|
|
|
@keyframes typingDot { |
|
0%, 60%, 100% { transform: translateY(0); } |
|
30% { transform: translateY(-5px); } |
|
} |
|
|
|
|
|
.typewriter-controls { |
|
display: flex; |
|
gap: 8px; |
|
margin-top: 12px; |
|
align-items: center; |
|
justify-content: flex-end; |
|
} |
|
|
|
.typewriter-btn { |
|
width: 32px; |
|
height: 32px; |
|
border-radius: 50%; |
|
border: none; |
|
background-color: var(--neutral-100); |
|
color: var(--neutral-600); |
|
cursor: pointer; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
transition: var(--transition-base); |
|
} |
|
|
|
.typewriter-btn:hover { |
|
background-color: var(--neutral-200); |
|
color: var(--neutral-900); |
|
} |
|
|
|
.typewriter-btn.pause-btn { |
|
color: var(--danger-color); |
|
} |
|
|
|
.typewriter-btn.continue-btn { |
|
color: var(--success-color); |
|
} |
|
|
|
.typewriter-btn.speed-btn { |
|
color: var(--warning-color); |
|
} |
|
|
|
|
|
.typing-cursor { |
|
display: inline-block; |
|
width: 2px; |
|
height: 1em; |
|
background-color: var(--neutral-700); |
|
margin-left: 2px; |
|
vertical-align: middle; |
|
animation: blink 1s infinite; |
|
} |
|
|
|
@keyframes blink { |
|
0%, 100% { opacity: 1; } |
|
50% { opacity: 0; } |
|
} |
|
|
|
|
|
pre { |
|
position: relative; |
|
white-space: pre; |
|
word-wrap: normal; |
|
overflow-x: auto; |
|
} |
|
|
|
|
|
.message-content p { |
|
word-break: break-word; |
|
} |
|
|
|
|
|
.copy-button { |
|
opacity: 0.8; |
|
z-index: 10; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<header class="header"> |
|
<div class="header-content"> |
|
<h1><i class="bi bi-mortarboard"></i> {{ agent_name }}</h1> |
|
<div> |
|
<span class="badge">AI学习助手</span> |
|
</div> |
|
</div> |
|
</header> |
|
|
|
<div class="main-container" id="main-container"> |
|
<div class="chat-container"> |
|
<div class="chat-messages" id="chat-messages"> |
|
<div class="welcome-message"> |
|
<div class="welcome-icon"> |
|
<i class="bi bi-robot"></i> |
|
</div> |
|
<h2 class="welcome-title">欢迎使用 {{ agent_name }}</h2> |
|
{% if agent_description %} |
|
<p class="welcome-description">{{ agent_description }}</p> |
|
{% else %} |
|
<p class="welcome-description">我是您的AI学习助手,有任何问题都可以随时向我提问</p> |
|
{% endif %} |
|
</div> |
|
|
|
<div class="message bot-message"> |
|
<div class="message-content"> |
|
<p>您好!我是{{ agent_name }},很高兴能够帮助您学习。请问有什么我可以协助您的问题吗?</p> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="input-container"> |
|
<div class="input-suggestions" id="input-suggestions"> |
|
<div class="suggestion-title">推荐问题:</div> |
|
<div class="suggestion-buttons"> |
|
<button class="suggestion-button">介绍一下这门课程的主要内容</button> |
|
<button class="suggestion-button">这门课程有哪些重点知识?</button> |
|
<button class="suggestion-button">请给我一些学习建议</button> |
|
</div> |
|
</div> |
|
<div class="input-row"> |
|
<textarea class="input-field" id="user-input" placeholder="输入您的问题..." rows="2"></textarea> |
|
<button class="send-button" id="send-button"> |
|
<i class="bi bi-send"></i> |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="plugin-container code-plugin" id="code-plugin"> |
|
<div class="plugin-header"> |
|
<h3 class="plugin-title"><i class="bi bi-code-square"></i> Python代码执行</h3> |
|
<button class="plugin-close" id="close-code-plugin"> |
|
<i class="bi bi-x-lg"></i> |
|
</button> |
|
</div> |
|
<div class="plugin-content"> |
|
<iframe id="code-execution-frame" src="" style="width: 100%; height: 100%; border: none;"></iframe> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="plugin-container visualization-plugin" id="visualization-plugin"> |
|
<div class="plugin-header"> |
|
<h3 class="plugin-title"><i class="bi bi-graph-up"></i> 3D可视化</h3> |
|
<button class="plugin-close" id="close-visualization-plugin"> |
|
<i class="bi bi-x-lg"></i> |
|
</button> |
|
</div> |
|
<div class="plugin-content"> |
|
<div class="visualization-result" id="visualization-result"> |
|
<div class="text-center py-4"> |
|
<div class="spinner-border text-primary" role="status"> |
|
<span class="visually-hidden">加载中...</span> |
|
</div> |
|
<p class="mt-3">正在准备3D可视化...</p> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="plugin-container mindmap-plugin" id="mindmap-plugin"> |
|
<div class="plugin-header"> |
|
<h3 class="plugin-title"><i class="bi bi-diagram-3"></i> 思维导图</h3> |
|
<button class="plugin-close" id="close-mindmap-plugin"> |
|
<i class="bi bi-x-lg"></i> |
|
</button> |
|
</div> |
|
<div class="plugin-content"> |
|
<div class="mindmap-result" id="mindmap-result"> |
|
<div class="text-center py-4"> |
|
<div class="spinner-border text-primary" role="status"> |
|
<span class="visually-hidden">加载中...</span> |
|
</div> |
|
<p class="mt-3">正在生成思维导图...</p> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/contrib/auto-render.min.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/markdown-it@13.0.1/dist/markdown-it.min.js"></script> |
|
<script> |
|
|
|
const agentId = "{{ agent_id }}"; |
|
const token = "{{ token }}"; |
|
let executionContext = null; |
|
const mainContainer = document.getElementById('main-container'); |
|
let isTyping = false; |
|
let typewriterInterval = null; |
|
let typewriterPaused = false; |
|
let currentMessageQueue = []; |
|
let typewriterSpeed = 20; |
|
let currentMessageElement = null; |
|
|
|
|
|
const chatMessages = document.getElementById('chat-messages'); |
|
const userInput = document.getElementById('user-input'); |
|
const sendButton = document.getElementById('send-button'); |
|
|
|
|
|
const codePlugin = document.getElementById('code-plugin'); |
|
const closeCodePlugin = document.getElementById('close-code-plugin'); |
|
const codeExecutionFrame = document.getElementById('code-execution-frame'); |
|
|
|
|
|
const visualizationPlugin = document.getElementById('visualization-plugin'); |
|
const closeVisualizationPlugin = document.getElementById('close-visualization-plugin'); |
|
const visualizationResult = document.getElementById('visualization-result'); |
|
|
|
|
|
const mindmapPlugin = document.getElementById('mindmap-plugin'); |
|
const closeMindmapPlugin = document.getElementById('close-mindmap-plugin'); |
|
const mindmapResult = document.getElementById('mindmap-result'); |
|
|
|
|
|
const inputSuggestions = document.getElementById('input-suggestions'); |
|
|
|
|
|
const md = window.markdownit({ |
|
html: false, |
|
linkify: true, |
|
typographer: true, |
|
breaks: true, |
|
highlight: function (str, lang) { |
|
if (lang && hljs.getLanguage(lang)) { |
|
try { |
|
const highlighted = hljs.highlight(str, { language: lang }).value; |
|
return `<pre><code class="hljs language-${lang}">${highlighted}</code><button class="copy-button" onclick="copyToClipboard(this)"><i class="bi bi-clipboard"></i> 复制</button></pre>`; |
|
} catch (__) {} |
|
} |
|
return `<pre><code class="hljs">${md.utils.escapeHtml(str)}</code><button class="copy-button" onclick="copyToClipboard(this)"><i class="bi bi-clipboard"></i> 复制</button></pre>`; |
|
} |
|
}); |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
|
sendButton.addEventListener('click', sendMessage); |
|
|
|
|
|
userInput.addEventListener('keydown', function(e) { |
|
if (e.key === 'Enter' && !e.shiftKey) { |
|
e.preventDefault(); |
|
sendMessage(); |
|
} |
|
}); |
|
|
|
|
|
document.querySelectorAll('.suggestion-button').forEach(button => { |
|
button.addEventListener('click', function() { |
|
userInput.value = this.textContent; |
|
sendMessage(); |
|
}); |
|
}); |
|
|
|
|
|
closeCodePlugin.addEventListener('click', () => { |
|
codePlugin.style.display = 'none'; |
|
|
|
codeExecutionFrame.src = ''; |
|
updateMainContainerLayout(); |
|
}); |
|
|
|
closeVisualizationPlugin.addEventListener('click', () => { |
|
visualizationPlugin.style.display = 'none'; |
|
updateMainContainerLayout(); |
|
}); |
|
|
|
closeMindmapPlugin.addEventListener('click', () => { |
|
mindmapPlugin.style.display = 'none'; |
|
updateMainContainerLayout(); |
|
}); |
|
|
|
|
|
userInput.addEventListener('focus', function() { |
|
|
|
}); |
|
|
|
userInput.addEventListener('blur', function(e) { |
|
|
|
setTimeout(() => { |
|
|
|
}, 100); |
|
}); |
|
}); |
|
|
|
|
|
function updateMainContainerLayout() { |
|
|
|
const isAnyPluginVisible = |
|
codePlugin.style.display === 'flex' || |
|
visualizationPlugin.style.display === 'flex' || |
|
mindmapPlugin.style.display === 'flex'; |
|
|
|
|
|
if (isAnyPluginVisible) { |
|
mainContainer.classList.add('with-plugin'); |
|
} else { |
|
mainContainer.classList.remove('with-plugin'); |
|
} |
|
} |
|
|
|
|
|
async function sendMessage() { |
|
const message = userInput.value.trim(); |
|
if (!message || isTyping) return; |
|
|
|
|
|
addMessage(message, true); |
|
|
|
|
|
userInput.value = ''; |
|
|
|
|
|
sendButton.disabled = true; |
|
isTyping = true; |
|
|
|
|
|
const typingIndicator = addTypingIndicator(); |
|
|
|
try { |
|
|
|
const response = await fetch(`/api/student/chat/${agentId}`, { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json' |
|
}, |
|
body: JSON.stringify({ |
|
message: message, |
|
token: token |
|
}) |
|
}); |
|
|
|
const data = await response.json(); |
|
|
|
|
|
if (typingIndicator) { |
|
typingIndicator.remove(); |
|
} |
|
|
|
if (data.success) { |
|
|
|
const processedContent = processResponseContent(data.message); |
|
|
|
|
|
const messageElement = addMessage(processedContent, false); |
|
|
|
|
|
if (data.references && data.references.length > 0) { |
|
|
|
const checkTypewriterComplete = setInterval(() => { |
|
if (!typewriterInterval) { |
|
clearInterval(checkTypewriterComplete); |
|
addReferences(messageElement, data.references); |
|
} |
|
}, 100); |
|
} |
|
|
|
|
|
if (data.tools && data.tools.length > 0) { |
|
|
|
hideAllPlugins(); |
|
|
|
|
|
activatePlugins(data.message, data.tools); |
|
} |
|
} else { |
|
|
|
addMessage(`错误: ${data.message}`, false); |
|
} |
|
} catch (error) { |
|
console.error('发送请求出错:', error); |
|
|
|
if (typingIndicator) { |
|
typingIndicator.remove(); |
|
} |
|
addMessage('发送请求时出错,请重试', false); |
|
} finally { |
|
|
|
sendButton.disabled = false; |
|
isTyping = false; |
|
} |
|
} |
|
|
|
|
|
function addTypingIndicator() { |
|
const botMessage = document.createElement('div'); |
|
botMessage.className = 'message bot-message'; |
|
|
|
const content = document.createElement('div'); |
|
content.className = 'message-content'; |
|
content.innerHTML = ` |
|
<p> |
|
<span class="typing-indicator"> |
|
<span class="typing-dot"></span> |
|
<span class="typing-dot"></span> |
|
<span class="typing-dot"></span> |
|
</span> |
|
</p> |
|
`; |
|
|
|
botMessage.appendChild(content); |
|
chatMessages.appendChild(botMessage); |
|
|
|
|
|
chatMessages.scrollTop = chatMessages.scrollHeight; |
|
|
|
return botMessage; |
|
} |
|
|
|
|
|
function processResponseContent(content) { |
|
|
|
return content.replace(/===参考来源开始===[\s\S]*?===参考来源结束===/, ''); |
|
} |
|
|
|
|
|
function addReferences(messageElement, references) { |
|
|
|
const referenceContainer = document.createElement('div'); |
|
referenceContainer.className = 'reference-container'; |
|
|
|
|
|
const toggleButton = document.createElement('div'); |
|
toggleButton.className = 'reference-toggle'; |
|
toggleButton.innerHTML = '<i class="bi bi-journal-text"></i> 参考来源'; |
|
|
|
|
|
const referenceContent = document.createElement('div'); |
|
referenceContent.className = 'reference-content'; |
|
|
|
|
|
references.forEach(ref => { |
|
const refItem = document.createElement('div'); |
|
refItem.className = 'reference-item'; |
|
|
|
refItem.innerHTML = ` |
|
<div><strong>[${ref.index}]</strong> ${ref.summary}</div> |
|
<div class="reference-item-source">来源: ${ref.file_name}</div> |
|
`; |
|
|
|
referenceContent.appendChild(refItem); |
|
}); |
|
|
|
|
|
toggleButton.addEventListener('click', () => { |
|
if (referenceContent.style.display === 'block') { |
|
referenceContent.style.display = 'none'; |
|
toggleButton.innerHTML = '<i class="bi bi-journal-text"></i> 参考来源'; |
|
} else { |
|
referenceContent.style.display = 'block'; |
|
toggleButton.innerHTML = '<i class="bi bi-journal-arrow-up"></i> 收起参考来源'; |
|
} |
|
}); |
|
|
|
|
|
referenceContainer.appendChild(toggleButton); |
|
referenceContainer.appendChild(referenceContent); |
|
messageElement.appendChild(referenceContainer); |
|
} |
|
|
|
|
|
function addMessage(content, isUser) { |
|
const messageDiv = document.createElement('div'); |
|
messageDiv.className = isUser ? 'message user-message' : 'message bot-message'; |
|
|
|
const messageContent = document.createElement('div'); |
|
messageContent.className = 'message-content'; |
|
|
|
if (isUser) { |
|
|
|
messageContent.innerHTML = `<p>${content.replace(/\n/g, '<br>')}</p>`; |
|
} else { |
|
|
|
messageContent.innerHTML = '<p></p>'; |
|
|
|
const controlsDiv = document.createElement('div'); |
|
controlsDiv.className = 'typewriter-controls'; |
|
controlsDiv.innerHTML = ` |
|
<button class="typewriter-btn pause-btn" title="暂停"><i class="bi bi-pause-fill"></i></button> |
|
<button class="typewriter-btn continue-btn" style="display:none;" title="继续"><i class="bi bi-play-fill"></i></button> |
|
<button class="typewriter-btn speed-btn" title="加速"><i class="bi bi-lightning-fill"></i></button> |
|
`; |
|
messageContent.appendChild(controlsDiv); |
|
|
|
|
|
const pauseBtn = controlsDiv.querySelector('.pause-btn'); |
|
const continueBtn = controlsDiv.querySelector('.continue-btn'); |
|
const speedBtn = controlsDiv.querySelector('.speed-btn'); |
|
|
|
pauseBtn.addEventListener('click', () => { |
|
pauseTypewriter(); |
|
pauseBtn.style.display = 'none'; |
|
continueBtn.style.display = 'inline-block'; |
|
}); |
|
|
|
continueBtn.addEventListener('click', () => { |
|
continueTypewriter(); |
|
continueBtn.style.display = 'none'; |
|
pauseBtn.style.display = 'inline-block'; |
|
}); |
|
|
|
speedBtn.addEventListener('click', () => { |
|
toggleTypewriterSpeed(); |
|
}); |
|
|
|
|
|
startTypewriter(content, messageContent.querySelector('p')); |
|
} |
|
|
|
messageDiv.appendChild(messageContent); |
|
chatMessages.appendChild(messageDiv); |
|
|
|
|
|
chatMessages.scrollTop = chatMessages.scrollHeight; |
|
|
|
return messageDiv; |
|
} |
|
|
|
|
|
function startTypewriter(text, element) { |
|
|
|
if (typewriterInterval) { |
|
clearInterval(typewriterInterval); |
|
} |
|
|
|
|
|
currentMessageElement = element; |
|
|
|
|
|
typewriterPaused = false; |
|
let mdContent = md.render(text); |
|
|
|
|
|
parseHTMLToQueue(mdContent); |
|
|
|
|
|
typewriterInterval = setInterval(typeNextChar, typewriterSpeed); |
|
} |
|
|
|
|
|
function parseHTMLToQueue(html) { |
|
|
|
currentMessageQueue = []; |
|
|
|
|
|
const tempDiv = document.createElement('div'); |
|
tempDiv.innerHTML = html; |
|
|
|
|
|
processNode(tempDiv); |
|
} |
|
|
|
|
|
function processNode(node) { |
|
|
|
for (let i = 0; i < node.childNodes.length; i++) { |
|
const child = node.childNodes[i]; |
|
|
|
if (child.nodeType === Node.TEXT_NODE) { |
|
|
|
for (let j = 0; j < child.textContent.length; j++) { |
|
currentMessageQueue.push({ |
|
type: 'text', |
|
content: child.textContent[j] |
|
}); |
|
} |
|
} else if (child.nodeType === Node.ELEMENT_NODE) { |
|
|
|
currentMessageQueue.push({ |
|
type: 'element-start', |
|
tagName: child.tagName.toLowerCase(), |
|
attributes: Array.from(child.attributes) |
|
}); |
|
|
|
|
|
processNode(child); |
|
|
|
|
|
currentMessageQueue.push({ |
|
type: 'element-end', |
|
tagName: child.tagName.toLowerCase() |
|
}); |
|
} |
|
} |
|
} |
|
|
|
|
|
function typeNextChar() { |
|
if (typewriterPaused || currentMessageQueue.length === 0 || !currentMessageElement) { |
|
return; |
|
} |
|
|
|
let item = currentMessageQueue.shift(); |
|
|
|
if (item.type === 'text') { |
|
|
|
currentMessageElement.innerHTML += item.content; |
|
} else if (item.type === 'element-start') { |
|
|
|
const el = document.createElement(item.tagName); |
|
if (item.attributes) { |
|
item.attributes.forEach(attr => { |
|
el.setAttribute(attr.name, attr.value); |
|
}); |
|
} |
|
currentMessageElement.appendChild(el); |
|
|
|
currentMessageElement = el; |
|
} else if (item.type === 'element-end') { |
|
|
|
|
|
if (currentMessageElement.parentElement) { |
|
currentMessageElement = currentMessageElement.parentElement; |
|
} |
|
} |
|
|
|
|
|
chatMessages.scrollTop = chatMessages.scrollHeight; |
|
|
|
|
|
if (currentMessageQueue.length === 0) { |
|
clearInterval(typewriterInterval); |
|
typewriterInterval = null; |
|
|
|
|
|
const botMessage = chatMessages.querySelector('.bot-message:last-child'); |
|
if (botMessage) { |
|
try { |
|
renderMathInElement(botMessage.querySelector('.message-content'), { |
|
delimiters: [ |
|
{left: '$$', right: '$$', display: true}, |
|
{left: '$', right: '$', display: false}, |
|
{left: '\\(', right: '\\)', display: false}, |
|
{left: '\\[', right: '\\]', display: true} |
|
], |
|
throwOnError: false |
|
}); |
|
} catch (e) { |
|
console.error('渲染LaTeX出错:', e); |
|
} |
|
|
|
|
|
botMessage.querySelectorAll('pre').forEach(pre => { |
|
if (!pre.querySelector('.copy-button')) { |
|
const copyButton = document.createElement('button'); |
|
copyButton.className = 'copy-button'; |
|
copyButton.innerHTML = '<i class="bi bi-clipboard"></i> 复制'; |
|
copyButton.onclick = function() { copyToClipboard(this); }; |
|
pre.appendChild(copyButton); |
|
} |
|
}); |
|
} |
|
} |
|
} |
|
|
|
|
|
function pauseTypewriter() { |
|
typewriterPaused = true; |
|
} |
|
|
|
|
|
function continueTypewriter() { |
|
typewriterPaused = false; |
|
|
|
|
|
if (!typewriterInterval && currentMessageQueue.length > 0) { |
|
typewriterInterval = setInterval(typeNextChar, typewriterSpeed); |
|
} |
|
} |
|
|
|
|
|
function toggleTypewriterSpeed() { |
|
if (typewriterSpeed === 20) { |
|
typewriterSpeed = 5; |
|
} else { |
|
typewriterSpeed = 20; |
|
} |
|
|
|
|
|
if (typewriterInterval) { |
|
clearInterval(typewriterInterval); |
|
typewriterInterval = setInterval(typeNextChar, typewriterSpeed); |
|
} |
|
} |
|
|
|
|
|
function copyToClipboard(button) { |
|
const codeBlock = button.parentNode.querySelector('code'); |
|
const text = codeBlock.textContent; |
|
|
|
navigator.clipboard.writeText(text).then(() => { |
|
button.innerHTML = '<i class="bi bi-check"></i> 已复制'; |
|
|
|
setTimeout(() => { |
|
button.innerHTML = '<i class="bi bi-clipboard"></i> 复制'; |
|
}, 2000); |
|
}).catch(err => { |
|
console.error('复制失败:', err); |
|
button.innerHTML = '<i class="bi bi-exclamation-triangle"></i> 失败'; |
|
|
|
setTimeout(() => { |
|
button.innerHTML = '<i class="bi bi-clipboard"></i> 复制'; |
|
}, 2000); |
|
}); |
|
} |
|
|
|
|
|
function hideAllPlugins() { |
|
codePlugin.style.display = 'none'; |
|
visualizationPlugin.style.display = 'none'; |
|
mindmapPlugin.style.display = 'none'; |
|
updateMainContainerLayout(); |
|
} |
|
|
|
|
|
function extractCodeBlocks(message) { |
|
const codeBlocks = []; |
|
const codeRegex = /```python\n([\s\S]*?)\n```/g; |
|
|
|
let match; |
|
while ((match = codeRegex.exec(message)) !== null) { |
|
codeBlocks.push(match[1]); |
|
} |
|
|
|
return codeBlocks; |
|
} |
|
|
|
|
|
function extract3DVisualizationCode(message) { |
|
|
|
const codeRegex = /```python\s*(import[\s\S]*?def create_3d_plot\(\):[\s\S]*?return[\s\S]*?})\s*```/i; |
|
const match = codeRegex.exec(message); |
|
|
|
if (match) { |
|
return match[1].trim(); |
|
} |
|
|
|
return null; |
|
} |
|
|
|
|
|
function extractMindmapContent(message) { |
|
|
|
const mindmapRegex = /@startmindmap\n([\s\S]*?)@endmindmap/; |
|
const match = mindmapRegex.exec(message); |
|
|
|
if (match) { |
|
return `@startmindmap\n${match[1]}\n@endmindmap`; |
|
} |
|
|
|
return null; |
|
} |
|
|
|
|
|
function activateCodePlugin(message) { |
|
|
|
const codeBlocks = extractCodeBlocks(message); |
|
|
|
if (codeBlocks.length > 0) { |
|
const isAlreadyVisible = codePlugin.style.display === 'flex'; |
|
|
|
|
|
codePlugin.style.display = 'flex'; |
|
updateMainContainerLayout(); |
|
|
|
|
|
const iframe = document.getElementById('code-execution-frame'); |
|
|
|
|
|
if (isAlreadyVisible && iframe.contentWindow) { |
|
|
|
iframe.contentWindow.postMessage({ |
|
type: 'setCode', |
|
code: codeBlocks[0] |
|
}, '*'); |
|
} else { |
|
|
|
let src = '/code_execution.html'; |
|
if (codeBlocks.length > 0) { |
|
src += `?code=${encodeURIComponent(codeBlocks[0])}`; |
|
} |
|
|
|
iframe.src = src; |
|
|
|
|
|
iframe.onload = function() { |
|
if (codeBlocks.length > 0) { |
|
iframe.contentWindow.postMessage({ |
|
type: 'setCode', |
|
code: codeBlocks[0] |
|
}, '*'); |
|
} |
|
}; |
|
} |
|
} |
|
} |
|
|
|
|
|
function activate3DVisualization(message) { |
|
const code = extract3DVisualizationCode(message); |
|
|
|
if (code) { |
|
|
|
visualizationPlugin.style.display = 'flex'; |
|
updateMainContainerLayout(); |
|
|
|
|
|
visualizationResult.innerHTML = ` |
|
<div class="text-center py-4"> |
|
<div class="spinner-border" style="color: var(--secondary-color);" role="status"> |
|
<span class="visually-hidden">生成中...</span> |
|
</div> |
|
<p class="mt-3">正在生成3D图形,请稍候...</p> |
|
</div> |
|
`; |
|
|
|
|
|
fetch('/api/visualization/3d-surface', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json' |
|
}, |
|
body: JSON.stringify({ code: code }) |
|
}) |
|
.then(response => response.json()) |
|
.then(data => { |
|
if (data.success) { |
|
|
|
visualizationResult.innerHTML = ` |
|
<div class="visualization-iframe-container" style="width:100%; height:500px;"> |
|
<iframe src="${data.html_url}" style="width:100%; height:100%; border:none;"></iframe> |
|
</div> |
|
<div class="text-center mt-3"> |
|
<p>3D图形生成成功</p> |
|
<a href="${data.html_url}" class="btn btn-sm btn-outline-primary" target="_blank"> |
|
<i class="bi bi-arrows-fullscreen"></i> 全屏查看 |
|
</a> |
|
</div> |
|
`; |
|
} else { |
|
visualizationResult.innerHTML = ` |
|
<div class="alert alert-danger"> |
|
<i class="bi bi-exclamation-triangle-fill me-2"></i> |
|
生成失败: ${data.message} |
|
</div> |
|
`; |
|
} |
|
}) |
|
.catch(error => { |
|
console.error('生成3D图形出错:', error); |
|
visualizationResult.innerHTML = ` |
|
<div class="alert alert-danger"> |
|
<i class="bi bi-exclamation-triangle-fill me-2"></i> |
|
生成3D图形时发生错误,请重试 |
|
</div> |
|
`; |
|
}); |
|
} |
|
} |
|
|
|
|
|
function activateMindmap(message) { |
|
const content = extractMindmapContent(message); |
|
|
|
if (content) { |
|
|
|
mindmapPlugin.style.display = 'flex'; |
|
updateMainContainerLayout(); |
|
|
|
|
|
mindmapResult.innerHTML = ` |
|
<div class="text-center py-4"> |
|
<div class="spinner-border" style="color: var(--secondary-color);" role="status"> |
|
<span class="visually-hidden">生成中...</span> |
|
</div> |
|
<p class="mt-3">正在生成思维导图,请稍候...</p> |
|
</div> |
|
`; |
|
|
|
|
|
fetch('/api/visualization/mindmap', { |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json' |
|
}, |
|
body: JSON.stringify({ content: content }) |
|
}) |
|
.then(response => response.json()) |
|
.then(data => { |
|
if (data.success) { |
|
mindmapResult.innerHTML = ` |
|
<div class="text-center"> |
|
<img src="${data.url}" class="mindmap-image" alt="思维导图"> |
|
<p class="mt-3">生成成功!</p> |
|
<a href="${data.url}" class="btn btn-sm btn-outline-primary mt-2" target="_blank"> |
|
<i class="bi bi-download"></i> 下载图片 |
|
</a> |
|
</div> |
|
`; |
|
} else { |
|
mindmapResult.innerHTML = ` |
|
<div class="alert alert-danger"> |
|
<i class="bi bi-exclamation-triangle-fill me-2"></i> |
|
生成失败: ${data.message} |
|
</div> |
|
`; |
|
} |
|
}) |
|
.catch(error => { |
|
console.error('生成思维导图出错:', error); |
|
mindmapResult.innerHTML = ` |
|
<div class="alert alert-danger"> |
|
<i class="bi bi-exclamation-triangle-fill me-2"></i> |
|
生成思维导图时发生错误,请重试 |
|
</div> |
|
`; |
|
}); |
|
} |
|
} |
|
|
|
|
|
function activatePlugins(message, tools) { |
|
|
|
if (tools.includes('code') && message.includes('```python')) { |
|
activateCodePlugin(message); |
|
} |
|
|
|
|
|
if (tools.includes('visualization') && |
|
(message.includes('def create_3d_plot') || |
|
message.includes('3D') || message.includes('可视化'))) { |
|
activate3DVisualization(message); |
|
} |
|
|
|
|
|
if (tools.includes('mindmap') && |
|
(message.includes('@startmindmap') || |
|
message.includes('思维导图'))) { |
|
activateMindmap(message); |
|
} |
|
} |
|
</script> |
|
|
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/languages/python.min.js"></script> |
|
</body> |
|
</html> |