luckay-knows-demo/konws-web/chatbox.html
liushuang 98d7406a99 init
2025-03-05 14:14:54 +08:00

297 lines
11 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>DeepSeek 32B Chat</title>
<script src="js/marked.min.js"></script>
<link rel="stylesheet" href="css/main.css" />
</head>
<body>
<div id="chatBox">
<div class="messages-container"></div>
<div id="inputArea">
<input type="text" id="userInput" placeholder="输入消息..." />
<button onclick="sendMessage()" id="sendBtn">
<svg class="icon" viewBox="0 0 1057 1024" xmlns="http://www.w3.org/2000/svg" width="20" height="20">
<path
d="M891.904 825.782857L462.482286 693.613714l429.421714-495.396571-561.517714 495.469714L0.073143 561.590857 1057.133714 0.073143 891.904 825.782857zM462.482286 1024v-231.058286l132.096 65.828572-132.096 165.156571z"
fill="#ffffff"
></path>
</svg>
</button>
</div>
</div>
<!-- 添加上传按钮 -->
<div class="upload-button-container">
<button onclick="showUploadDialog()" class="upload-btn">
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="20" height="20">
<path
d="M1024 736s-3.4048-10.24-10.24-20.5056l-150.1696-300.3392c-6.8352-10.24-20.48-20.5056-34.1248-20.5056H706.56c-13.6448 0-23.8848 10.24-23.8848 23.9104v40.96c0 13.6448 10.2144 23.9104 23.8848 23.9104h40.96c13.6448 0 27.2896 10.24 34.1248 20.48l105.8304 215.0656c6.8352 10.1888 0 20.4544-13.6704 20.4544H706.56c-13.6448 0-23.8848 10.24-23.8848 23.9104v122.9056c0 13.6448-10.24 23.9104-23.9104 23.9104H365.2352a23.3216 23.3216 0 0 1-23.8848-23.9104v-122.9056c0-13.6448-10.24-23.9104-23.9104-23.9104H146.7648c-13.6448 0-17.0752-10.24-13.6448-20.4544l109.2352-215.0656c6.8352-10.2144 20.48-20.48 34.1248-20.48h37.5552c13.6448 0 23.9104-10.24 23.9104-23.9104v-37.5552c0-13.6448-10.24-23.8848-23.9104-23.8848h-122.88c-13.6704 0-27.3152 10.2144-34.1248 20.48L10.24 715.4944c-6.8352 10.2656-10.24 20.5056-10.24 20.5056v235.4944c0 13.6448 10.24 23.9104 23.8848 23.9104h976.2048c13.6448 0 23.9104-10.24 23.9104-23.9104V736zM300.3648 292.2752h126.2848v358.4h170.6752v-358.4h133.12c13.6448 0 17.0752-6.8352 6.8352-17.0496l-211.6352-238.9504c-6.8352-10.2144-23.8848-10.2144-30.72 0l-204.8 238.9504c-6.8096 10.2144-3.4048 17.0496 10.24 17.0496z"
fill="#fff"
></path>
</svg>
</button>
</div>
<!-- 文件上传弹窗 -->
<div id="uploadDialog" class="upload-dialog">
<div class="upload-dialog-content">
<span class="close-btn" onclick="closeUploadDialog()">&times;</span>
<h2>上传文件</h2>
<div class="upload-area" id="dropZone">
<input type="file" id="fileInput" style="display: none" onchange="handleFileSelect(event)" />
<div class="upload-placeholder" onclick="document.getElementById('fileInput').click()">
<i class="fas fa-cloud-upload-alt"></i>
<p>点击或拖拽文件到此处上传</p>
<p class="supported-formats">支持的格式: PDF, DOC, DOCX</p>
</div>
</div>
<div id="uploadProgress" class="upload-progress" style="display: none">
<div class="progress-bar">
<div class="progress-fill"></div>
</div>
<span class="progress-text">0%</span>
</div>
<div id="uploadStatus" class="upload-status"></div>
</div>
</div>
<script>
const url = "http://localhost:8899";
let currentBotMessage = null;
// 添加消息到聊天框
function addMessage(content, isUser = false) {
const messagesContainer = document.querySelector(".messages-container");
const messageDiv = document.createElement("div");
messageDiv.className = `message ${isUser ? "user-message" : "bot-message"}`;
// 创建头像元素
const avatar = document.createElement("img");
avatar.className = "avatar";
avatar.src = isUser ? "./images/user-avatar.png" : "/images/bot-avatar.png";
avatar.alt = isUser ? "User Avatar" : "Bot Avatar";
// 创建消息内容容器
const messageContent = document.createElement("div");
messageContent.className = "message-content";
if (isUser) {
messageContent.textContent = content;
} else {
messageContent.innerHTML = marked.parse(content);
}
// 组装消息元素
messageDiv.appendChild(avatar);
messageDiv.appendChild(messageContent);
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
return messageDiv;
}
// 修改处理流式响应的部分
async function streamResponse(prompt) {
const btn = document.getElementById("sendBtn");
btn.disabled = true;
let accumulatedContent = "";
try {
const response = await fetch(url + "/knows/generate", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "text/event-stream"
},
body: JSON.stringify({
keyword: prompt
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
if (!currentBotMessage) {
// 创建完整的消息结构,包括头像
const messageDiv = document.createElement("div");
messageDiv.className = "message bot-message";
// 创建头像元素
const avatar = document.createElement("img");
avatar.className = "avatar";
avatar.src = "./images/bot-avatar.png";
avatar.alt = "Bot Avatar";
// 创建消息内容容器
const messageContent = document.createElement("div");
messageContent.className = "message-content";
// 组装消息元素
messageDiv.appendChild(avatar);
messageDiv.appendChild(messageContent);
document.querySelector(".messages-container").appendChild(messageDiv);
currentBotMessage = messageContent; // 更新 currentBotMessage 为消息内容容器
}
let thinkContent = true;
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split("\n").filter((line) => line.trim().startsWith("data: "));
for (const line of lines) {
try {
// 移除 "data: " 前缀并解析JSON
const jsonData = JSON.parse(line.substring(6));
if (jsonData.response) {
let content = jsonData.response;
// if (content.includes("\u003c/think\u003e")) {
// thinkContent = false;
// }
// if (!thinkContent) {
// accumulatedContent += content;
// currentBotMessage.innerHTML = marked.parse(accumulatedContent);
// }
accumulatedContent += content;
currentBotMessage.innerHTML = marked.parse(accumulatedContent);
}
if (jsonData.done) {
currentBotMessage = null;
}
} catch (error) {
console.error("解析数据失败:", error, "原始数据:", line);
continue;
}
}
document.querySelector(".messages-container").scrollTop = document.querySelector(".messages-container").scrollHeight;
}
} catch (error) {
console.error("请求失败:", error);
addMessage(`[错误] ${error.message}`, false);
} finally {
btn.disabled = false;
}
}
// 发送消息
async function sendMessage() {
const input = document.getElementById("userInput");
const userMessage = input.value.trim();
if (!userMessage) return;
addMessage(userMessage, true);
input.value = "";
await streamResponse(userMessage);
}
// 回车键发送
document.getElementById("userInput").addEventListener("keypress", (e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
function showUploadDialog() {
document.getElementById("uploadDialog").style.display = "block";
}
function closeUploadDialog() {
document.getElementById("uploadDialog").style.display = "none";
resetUploadDialog();
}
function resetUploadDialog() {
document.getElementById("fileInput").value = "";
document.getElementById("uploadProgress").style.display = "none";
document.getElementById("uploadStatus").innerHTML = "";
document.getElementById("uploadStatus").className = "upload-status";
}
function handleFileSelect(event) {
const file = event.target.files[0];
if (file) {
uploadFile(file);
}
}
function updateProgress(percent) {
const progressBar = document.querySelector(".progress-fill");
const progressText = document.querySelector(".progress-text");
progressBar.style.width = `${percent}%`;
progressText.textContent = `${percent}%`;
}
function uploadFile(file) {
const formData = new FormData();
formData.append("file", file);
const progressDiv = document.getElementById("uploadProgress");
const statusDiv = document.getElementById("uploadStatus");
progressDiv.style.display = "block";
statusDiv.innerHTML = "正在上传...";
statusDiv.className = "upload-status";
fetch(url + "/api/file/upload", {
method: "POST",
body: formData
})
.then((response) => response.json())
.then((data) => {
statusDiv.innerHTML = data.message;
statusDiv.className = "upload-status success";
updateProgress(100);
setTimeout(() => {
closeUploadDialog();
}, 2000);
})
.catch((error) => {
statusDiv.innerHTML = "上传失败: " + error.message;
statusDiv.className = "upload-status error";
updateProgress(0);
});
}
// 添加拖拽上传支持
const dropZone = document.getElementById("dropZone");
dropZone.addEventListener("dragover", (e) => {
e.preventDefault();
dropZone.style.borderColor = "#4CAF50";
});
dropZone.addEventListener("dragleave", (e) => {
e.preventDefault();
dropZone.style.borderColor = "#ccc";
});
dropZone.addEventListener("drop", (e) => {
e.preventDefault();
dropZone.style.borderColor = "#ccc";
const file = e.dataTransfer.files[0];
if (file) {
uploadFile(file);
}
});
</script>
</body>
</html>