408 lines
14 KiB
HTML
408 lines
14 KiB
HTML
|
<!DOCTYPE html>
|
|||
|
<html lang="zh-CN">
|
|||
|
<head>
|
|||
|
<meta charset="UTF-8">
|
|||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|||
|
<title>物体的浮沉条件是什么?</title>
|
|||
|
<style>
|
|||
|
* {
|
|||
|
margin: 0;
|
|||
|
padding: 0;
|
|||
|
box-sizing: border-box;
|
|||
|
font-family: 'Microsoft YaHei', Arial, sans-serif;
|
|||
|
}
|
|||
|
|
|||
|
body {
|
|||
|
background-color: #f5f5f5;
|
|||
|
height: 100vh;
|
|||
|
display: flex;
|
|||
|
flex-direction: column;
|
|||
|
}
|
|||
|
|
|||
|
.container {
|
|||
|
display: flex;
|
|||
|
flex: 1;
|
|||
|
overflow: hidden;
|
|||
|
}
|
|||
|
|
|||
|
.simulation-panel {
|
|||
|
flex: 1;
|
|||
|
border: 1px solid #ddd;
|
|||
|
overflow: hidden;
|
|||
|
background-color: white;
|
|||
|
}
|
|||
|
|
|||
|
.chat-panel {
|
|||
|
flex: 0 0 400px;
|
|||
|
display: flex;
|
|||
|
flex-direction: column;
|
|||
|
border: 1px solid #ddd;
|
|||
|
background-color: white;
|
|||
|
box-shadow: -2px 0 5px rgba(0,0,0,0.1);
|
|||
|
}
|
|||
|
|
|||
|
.chat-header {
|
|||
|
padding: 15px;
|
|||
|
background-color: #2196F3; /* 改为蓝色 */
|
|||
|
color: white;
|
|||
|
font-size: 18px;
|
|||
|
font-weight: bold;
|
|||
|
}
|
|||
|
|
|||
|
.chat-messages {
|
|||
|
flex: 1;
|
|||
|
padding: 15px;
|
|||
|
overflow-y: auto;
|
|||
|
}
|
|||
|
|
|||
|
.message {
|
|||
|
margin-bottom: 15px;
|
|||
|
max-width: 85%;
|
|||
|
padding: 10px 15px;
|
|||
|
border-radius: 18px;
|
|||
|
word-break: break-word;
|
|||
|
}
|
|||
|
|
|||
|
.user-message {
|
|||
|
background-color: #E3F2FD; /* 改为浅蓝色 */
|
|||
|
margin-left: auto;
|
|||
|
border-top-right-radius: 4px;
|
|||
|
}
|
|||
|
|
|||
|
.ai-message {
|
|||
|
background-color: #E5E5EA;
|
|||
|
border-top-left-radius: 4px;
|
|||
|
}
|
|||
|
|
|||
|
.message-content {
|
|||
|
line-height: 1.5;
|
|||
|
}
|
|||
|
|
|||
|
.chat-input {
|
|||
|
display: flex;
|
|||
|
padding: 10px;
|
|||
|
border-top: 1px solid #ddd;
|
|||
|
}
|
|||
|
|
|||
|
.chat-input input {
|
|||
|
flex: 1;
|
|||
|
padding: 10px 15px;
|
|||
|
border: 1px solid #ddd;
|
|||
|
border-radius: 20px;
|
|||
|
outline: none;
|
|||
|
}
|
|||
|
|
|||
|
.chat-input button {
|
|||
|
margin-left: 10px;
|
|||
|
padding: 0 15px;
|
|||
|
background-color: #2196F3; /* 改为蓝色 */
|
|||
|
color: white;
|
|||
|
border: none;
|
|||
|
border-radius: 20px;
|
|||
|
cursor: pointer;
|
|||
|
font-weight: bold;
|
|||
|
}
|
|||
|
|
|||
|
.chat-input button:hover {
|
|||
|
background-color: #1976D2; /* 改为深蓝色 */
|
|||
|
}
|
|||
|
|
|||
|
.loading-indicator {
|
|||
|
display: flex;
|
|||
|
align-items: center;
|
|||
|
margin: 10px 0;
|
|||
|
}
|
|||
|
|
|||
|
.loading-dot {
|
|||
|
width: 8px;
|
|||
|
height: 8px;
|
|||
|
border-radius: 50%;
|
|||
|
background-color: #666;
|
|||
|
margin: 0 4px;
|
|||
|
animation: pulse 1.4s infinite ease-in-out both;
|
|||
|
}
|
|||
|
|
|||
|
.loading-dot:nth-child(1) {
|
|||
|
animation-delay: -0.32s;
|
|||
|
}
|
|||
|
|
|||
|
.loading-dot:nth-child(2) {
|
|||
|
animation-delay: -0.16s;
|
|||
|
}
|
|||
|
|
|||
|
@keyframes pulse {
|
|||
|
0%, 80%, 100% {
|
|||
|
transform: scale(0);
|
|||
|
}
|
|||
|
40% {
|
|||
|
transform: scale(1);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
iframe {
|
|||
|
width: 100%;
|
|||
|
height: 100%;
|
|||
|
border: none;
|
|||
|
}
|
|||
|
|
|||
|
/* 响应式设计 */
|
|||
|
@media (max-width: 900px) {
|
|||
|
.container {
|
|||
|
flex-direction: column;
|
|||
|
}
|
|||
|
|
|||
|
.simulation-panel {
|
|||
|
height: 50vh;
|
|||
|
}
|
|||
|
|
|||
|
.chat-panel {
|
|||
|
flex: 1;
|
|||
|
width: 100%;
|
|||
|
}
|
|||
|
}
|
|||
|
</style>
|
|||
|
</head>
|
|||
|
<body>
|
|||
|
<div class="container">
|
|||
|
<div class="simulation-panel">
|
|||
|
<iframe src="deepseek_html_20250812_5fd0ec.html"></iframe>
|
|||
|
</div>
|
|||
|
<div class="chat-panel">
|
|||
|
<div class="chat-header">AI 助手</div>
|
|||
|
<div class="chat-messages" id="chat-messages">
|
|||
|
<div class="message ai-message">
|
|||
|
<div class="message-content">提示词:
|
|||
|
开发一个高中化学讲解的课件,要求如下:
|
|||
|
左侧控制面板:
|
|||
|
标题:岩盐结构
|
|||
|
描述:NaCl晶体模型交互演示
|
|||
|
离子半径设置:
|
|||
|
钠离子半径:0.3
|
|||
|
氯离子半径:0.45
|
|||
|
视图控制:
|
|||
|
显示/隐藏离子:一个复选框,用于控制是否显示离子。
|
|||
|
显示/隐藏晶胞:一个复选框,用于控制是否显示晶胞。
|
|||
|
显示八面体:一个按钮,用于显示八面体结构。
|
|||
|
仅显示八面体:一个按钮,用于仅显示八面体结构。
|
|||
|
切八分之一晶胞:一个按钮,用于显示晶胞的八分之一。
|
|||
|
重置视图:一个按钮,用于重置视图到默认状态。
|
|||
|
观察视角:
|
|||
|
X轴、Y轴、Z轴:三个按钮,用于调整观察的三维视角。
|
|||
|
右侧主展示区:
|
|||
|
标题:氯化钠晶胞结构学演示
|
|||
|
描述:高中化学交互式教学演示
|
|||
|
氯化钠晶胞结构图:
|
|||
|
展示了氯化钠的晶胞结构,其中绿色球代表氯离子,蓝色球代表钠离子。晶胞的边缘用红色线条连接,形成一个立方体结构。
|
|||
|
交互提示:
|
|||
|
提示用户可以使用鼠标拖拽旋转视角,滚轮缩放,平行投影等操作来观察晶胞结构。
|
|||
|
这个网页的主要功能是通过交互式的方式,让用户能够观察和学习氯化钠的晶胞结构,并通过调整离子半径和视角来获得更深入的理解。</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
<div class="chat-input">
|
|||
|
<input type="text" id="user-input" placeholder="输入你的问题..." value="氯化钠晶胞结构是什么样的?">
|
|||
|
<button id="send-btn">发送</button>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
<script>
|
|||
|
document.addEventListener('DOMContentLoaded', function() {
|
|||
|
const chatMessages = document.getElementById('chat-messages');
|
|||
|
const userInput = document.getElementById('user-input');
|
|||
|
const sendBtn = document.getElementById('send-btn');
|
|||
|
let sseConnection = null;
|
|||
|
let isLoading = false;
|
|||
|
|
|||
|
// 发送消息
|
|||
|
function sendMessage() {
|
|||
|
const message = userInput.value.trim();
|
|||
|
if (!message || isLoading) return;
|
|||
|
|
|||
|
// 添加用户消息
|
|||
|
addMessage(message, 'user');
|
|||
|
userInput.value = '';
|
|||
|
|
|||
|
// 显示加载中
|
|||
|
showLoading();
|
|||
|
|
|||
|
// 关闭当前可能存在的SSE连接
|
|||
|
if (sseConnection) {
|
|||
|
sseConnection.close();
|
|||
|
sseConnection = null;
|
|||
|
}
|
|||
|
|
|||
|
// 发送POST请求到/api/llm
|
|||
|
fetch('/api/llm', {
|
|||
|
method: 'POST',
|
|||
|
headers: {
|
|||
|
'Content-Type': 'application/json',
|
|||
|
},
|
|||
|
body: JSON.stringify({ question: message })
|
|||
|
})
|
|||
|
.then(response => {
|
|||
|
if (!response.ok) {
|
|||
|
throw new Error('HTTP error! status: ' + response.status);
|
|||
|
}
|
|||
|
|
|||
|
// 处理流式响应
|
|||
|
const reader = response.body.getReader();
|
|||
|
const decoder = new TextDecoder();
|
|||
|
let buffer = '';
|
|||
|
let aiMessageElement = null;
|
|||
|
let aiMessageContentElement = null;
|
|||
|
|
|||
|
function processChunk({ done, value }) {
|
|||
|
if (done) {
|
|||
|
// 处理最后一块数据
|
|||
|
if (buffer) {
|
|||
|
handleSSEMessage(buffer, aiMessageElement, aiMessageContentElement);
|
|||
|
}
|
|||
|
hideLoading();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// 解码接收到的数据
|
|||
|
const chunk = decoder.decode(value, { stream: true });
|
|||
|
buffer += chunk;
|
|||
|
|
|||
|
// 按行分割数据
|
|||
|
const lines = buffer.split('\n');
|
|||
|
buffer = lines.pop(); // 保存不完整的行
|
|||
|
|
|||
|
// 如果还没有创建AI消息元素,则创建
|
|||
|
if (!aiMessageElement) {
|
|||
|
aiMessageElement = document.createElement('div');
|
|||
|
aiMessageElement.className = 'message ai-message';
|
|||
|
aiMessageContentElement = document.createElement('div');
|
|||
|
aiMessageContentElement.className = 'message-content';
|
|||
|
aiMessageElement.appendChild(aiMessageContentElement);
|
|||
|
chatMessages.appendChild(aiMessageElement);
|
|||
|
scrollToBottom();
|
|||
|
}
|
|||
|
|
|||
|
// 处理每一行
|
|||
|
for (const line of lines) {
|
|||
|
if (line.trim()) {
|
|||
|
handleSSEMessage(line, aiMessageElement, aiMessageContentElement);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 继续读取
|
|||
|
return reader.read().then(processChunk);
|
|||
|
}
|
|||
|
|
|||
|
// 开始读取响应体
|
|||
|
return reader.read().then(processChunk);
|
|||
|
})
|
|||
|
.catch(error => {
|
|||
|
console.error('发送请求失败:', error);
|
|||
|
hideLoading();
|
|||
|
addMessage('抱歉,请求失败: ' + error.message, 'ai');
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
// 处理SSE消息
|
|||
|
function handleSSEMessage(line, aiMessageElement, aiMessageContentElement) {
|
|||
|
// 检查是否是SSE格式 (以'data: '开头)
|
|||
|
if (line.startsWith('data: ')) {
|
|||
|
const data = line.substring(6).trim();
|
|||
|
|
|||
|
// 检查是否是结束信号
|
|||
|
if (data === 'DONE') {
|
|||
|
hideLoading();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
try {
|
|||
|
// 解析JSON
|
|||
|
const jsonData = JSON.parse(data);
|
|||
|
// 提取content字段
|
|||
|
if (jsonData.content) {
|
|||
|
aiMessageContentElement.textContent += jsonData.content;
|
|||
|
scrollToBottom();
|
|||
|
}
|
|||
|
} catch (error) {
|
|||
|
console.error('解析JSON失败:', error, '数据:', data);
|
|||
|
// 如果解析失败,尝试直接添加文本
|
|||
|
aiMessageContentElement.textContent += data;
|
|||
|
scrollToBottom();
|
|||
|
}
|
|||
|
} else if (line) {
|
|||
|
// 如果不是标准SSE格式,尝试直接解析
|
|||
|
try {
|
|||
|
const jsonData = JSON.parse(line);
|
|||
|
if (jsonData.content) {
|
|||
|
aiMessageContentElement.textContent += jsonData.content;
|
|||
|
scrollToBottom();
|
|||
|
}
|
|||
|
} catch (error) {
|
|||
|
console.error('解析非标准SSE数据失败:', error, '数据:', line);
|
|||
|
// 如果解析失败,直接添加文本
|
|||
|
aiMessageContentElement.textContent += line;
|
|||
|
scrollToBottom();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 添加消息到聊天框
|
|||
|
function addMessage(message, sender) {
|
|||
|
const messageElement = document.createElement('div');
|
|||
|
messageElement.className = `message ${sender}-message`;
|
|||
|
|
|||
|
const contentElement = document.createElement('div');
|
|||
|
contentElement.className = 'message-content';
|
|||
|
contentElement.textContent = message;
|
|||
|
|
|||
|
messageElement.appendChild(contentElement);
|
|||
|
chatMessages.appendChild(messageElement);
|
|||
|
scrollToBottom();
|
|||
|
}
|
|||
|
|
|||
|
// 显示加载中
|
|||
|
function showLoading() {
|
|||
|
if (isLoading) return;
|
|||
|
isLoading = true;
|
|||
|
|
|||
|
const loadingElement = document.createElement('div');
|
|||
|
loadingElement.className = 'loading-indicator';
|
|||
|
loadingElement.id = 'loading-indicator';
|
|||
|
|
|||
|
for (let i = 0; i < 3; i++) {
|
|||
|
const dot = document.createElement('div');
|
|||
|
dot.className = 'loading-dot';
|
|||
|
loadingElement.appendChild(dot);
|
|||
|
}
|
|||
|
|
|||
|
chatMessages.appendChild(loadingElement);
|
|||
|
scrollToBottom();
|
|||
|
}
|
|||
|
|
|||
|
// 隐藏加载中
|
|||
|
function hideLoading() {
|
|||
|
if (!isLoading) return;
|
|||
|
isLoading = false;
|
|||
|
|
|||
|
const loadingElement = document.getElementById('loading-indicator');
|
|||
|
if (loadingElement) {
|
|||
|
loadingElement.remove();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 滚动到底部
|
|||
|
function scrollToBottom() {
|
|||
|
chatMessages.scrollTop = chatMessages.scrollHeight;
|
|||
|
}
|
|||
|
|
|||
|
// 发送按钮点击事件
|
|||
|
sendBtn.addEventListener('click', sendMessage);
|
|||
|
|
|||
|
// 输入框回车事件
|
|||
|
userInput.addEventListener('keypress', function(e) {
|
|||
|
if (e.key === 'Enter') {
|
|||
|
sendMessage();
|
|||
|
}
|
|||
|
});
|
|||
|
});
|
|||
|
</script>
|
|||
|
</body>
|
|||
|
</html>
|