'commit'
This commit is contained in:
384
dsLightRag/static/xnfzsy_ohm.html
Normal file
384
dsLightRag/static/xnfzsy_ohm.html
Normal file
@@ -0,0 +1,384 @@
|
||||
<!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>
|
||||
<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: #4CAF50;
|
||||
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: #DCF8C6;
|
||||
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: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.chat-input button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.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="https://phet.colorado.edu/sims/html/circuit-construction-kit-ac/latest/circuit-construction-kit-ac_all.html?locale=zh_CN"></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">你好!我是你的AI助手,可以帮助你理解欧姆定律相关问题。请随时提问。</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>
|
Reference in New Issue
Block a user