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> |