DeepSeek
⛶
🧰
工具箱
DeepSeek API Key
设置Key
记忆条数
设置记忆
桌面背景颜色
用户气泡颜色
AI气泡颜色
字体颜色
应用颜色
清空聊天窗口
聊天记录
发送
<script>
/************************************************************
* 全局变量 & DOM 获取
************************************************************/
let apiKey = "";
let memoryLimit = 10; // 默认记忆条数
const KEY_STORAGE = "DEEPSEEK_CHAT_HISTORY";
const COLOR_STORAGE = "DEEPSEEK_COLOR_CONFIG";
const toolboxToggle = document.getElementById("toolboxToggle");
const toolboxContainer = document.getElementById("toolboxContainer");
const apiKeyInput = document.getElementById("apiKeyInput");
const setKeyBtn = document.getElementById("setKeyBtn");
const memoryLimitInput = document.getElementById("memoryLimit");
const setMemoryBtn = document.getElementById("setMemoryBtn");
const clearChatBtn = document.getElementById("clearChatBtn");
const chatContainer = document.getElementById("chatContainer");
const userInput = document.getElementById("userInput");
const sendBtn = document.getElementById("sendBtn");
const historyContainer = document.getElementById("historyContainer");
// 自定义颜色相关
const desktopColorInput = document.getElementById("desktopColor");
const bubbleColorUserInput = document.getElementById("bubbleColorUser");
const bubbleColorAIInput = document.getElementById("bubbleColorAI");
const fontColorInput = document.getElementById("fontColor");
const applyColorBtn = document.getElementById("applyColorBtn");
// 全屏按钮
const fullscreenToggle = document.getElementById("fullscreenToggle");
// 聊天记录数组:[{role: "user"|"ai", content: "xxx", time: 167xxx }]
let chatHistory = [];
// 颜色配置(在本地缓存/读取)
let colorConfig = {
desktopColor: "#FFFFFF",
bubbleColorUser: "#FFF0F0",
bubbleColorAI: "#F0FFF0",
fontColor: "#333333"
};
/************************************************************
* 页面初始化
************************************************************/
window.addEventListener("DOMContentLoaded", () => {
// 从 localStorage 加载聊天记录
const saved = localStorage.getItem(KEY_STORAGE);
if (saved) {
try {
chatHistory = JSON.parse(saved);
} catch (e) {
console.warn("解析本地聊天记录失败:", e);
}
}
// 从 localStorage 加载颜色配置
const savedColor = localStorage.getItem(COLOR_STORAGE);
if (savedColor) {
try {
colorConfig = JSON.parse(savedColor);
} catch (e) {
console.warn("解析本地颜色配置失败:", e);
}
}
// 渲染历史 & 更新界面颜色
renderChatFromHistory();
renderHistoryList();
applyColorConfig(); // 应用颜色设置到界面
// 同步颜色选择器
desktopColorInput.value = colorConfig.desktopColor;
bubbleColorUserInput.value = colorConfig.bubbleColorUser;
bubbleColorAIInput.value = colorConfig.bubbleColorAI;
fontColorInput.value = colorConfig.fontColor;
});
/************************************************************
* 顶栏相关按钮
************************************************************/
// 全屏
fullscreenToggle.addEventListener("click", () => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
} else if (document.exitFullscreen) {
document.exitFullscreen();
}
});
// 显示 / 隐藏工具箱
toolboxToggle.addEventListener("click", () => {
toolboxContainer.classList.toggle("show");
});
/************************************************************
* 工具箱交互
************************************************************/
// 设置 Key
setKeyBtn.addEventListener("click", () => {
const keyVal = apiKeyInput.value.trim();
if (!keyVal) {
alert("请输入有效的 DeepSeek Key");
return;
}
apiKey = keyVal;
alert("Key 设置成功!");
});
// 设置记忆条数
setMemoryBtn.addEventListener("click", () => {
const val = parseInt(memoryLimitInput.value, 10);
if (isNaN(val) || val < 1) {
alert("请输入正确的记忆条数");
return;
}
memoryLimit = val;
alert(`记忆条数已设置为:${memoryLimit}`);
});
// 应用颜色
applyColorBtn.addEventListener("click", () => {
colorConfig.desktopColor = desktopColorInput.value;
colorConfig.bubbleColorUser = bubbleColorUserInput.value;
colorConfig.bubbleColorAI = bubbleColorAIInput.value;
colorConfig.fontColor = fontColorInput.value;
// 应用到页面
applyColorConfig();
// 保存到 localStorage
localStorage.setItem(COLOR_STORAGE, JSON.stringify(colorConfig));
});
// 清空聊天窗口(不删除历史)
clearChatBtn.addEventListener("click", () => {
chatContainer.innerHTML = "";
});
/************************************************************
* 发送消息
************************************************************/
sendBtn.addEventListener("click", async () => {
const content = userInput.value.trim();
if (!content) return;
if (!apiKey) {
alert("请先在工具箱中设置 DeepSeek Key!");
return;
}
// 先渲染用户消息
addMessage(content, "user");
userInput.value = "";
// 构建带有“如果回答中涉及数学公式,请使用 $...$ 或 $$...$$ 标记”的 system 提示
const systemPrompt = "You are a helpful assistant. If your answer involves math formulas, please use $...$ or $$...$$ to mark the formula.";
// 将最近 memoryLimit 条对话一起发送给 DeepSeek
const messagesToSend = buildChatMessages(systemPrompt, content);
// 向 DeepSeek 发送请求
try {
const response = await fetch("https://api.deepseek.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${apiKey}`
},
body: JSON.stringify({
model: "deepseek-chat",
messages: messagesToSend
})
});
if (!response.ok) {
throw new Error(`请求失败,状态码: ${response.status}`);
}
const result = await response.json();
let aiReply = result?.choices?.[0]?.message?.content || "AI 未返回有效的消息。";
// 去除多余符号:★☆*# 等
aiReply = aiReply.replace(/[★☆*#]/g, "");
// 逐字打字
addMessage(aiReply, "ai", true);
} catch (err) {
console.error(err);
addMessage("请求失败,请检查 Key、网络或模型。", "ai");
}
});
/************************************************************
* 构建聊天上下文
************************************************************/
function buildChatMessages(systemPrompt, lastUserContent) {
// 1. 先放一个系统提示
const messages = [
{ role: "system", content: systemPrompt }
];
// 2. 获取最近 memoryLimit 条非 system 消息(含 user + ai)
const relevantMsgs = [];
for (let i = chatHistory.length - 1; i >= 0 && relevantMsgs.length < memoryLimit; i--) {
const msg = chatHistory[i];
if (msg.role === "user") {
relevantMsgs.push({ role: "user", content: msg.content });
} else if (msg.role === "ai") {
relevantMsgs.push({ role: "assistant", content: msg.content });
}
}
// 取完后要反转一下顺序(因为我们是从后往前取)
relevantMsgs.reverse();
// 3. 把这些消息推到 messages 数组中
messages.push(...relevantMsgs);
// 4. 最后再加上本次用户输入
messages.push({ role: "user", content: lastUserContent });
return messages;
}
/************************************************************
* 添加消息到屏幕 & 保存记录
************************************************************/
function addMessage(text, role, typed = false) {
// 存到 chatHistory
const messageData = { role, content: text, time: Date.now() };
chatHistory.push(messageData);
// 渲染当前消息到屏幕
renderSingleMessage(role, text, typed);
// 保存到 localStorage
localStorage.setItem(KEY_STORAGE, JSON.stringify(chatHistory));
// 重新渲染聊天记录列表
renderHistoryList();
}
/************************************************************
* 在屏幕渲染单条消息(可选 逐字打字 + MathJax 渲染)
************************************************************/
function renderSingleMessage(role, text, typed) {
const wrapper = document.createElement("div");
wrapper.classList.add("message-wrapper", role === "user" ? "wrapper-user" : "wrapper-ai");
const msgDiv = document.createElement("div");
msgDiv.classList.add("chat-message");
// 文字部分
const textSpan = document.createElement("div");
textSpan.classList.add("message-text");
// 处理消息内容,识别代码块
const contentResult = processMessageText(text);
textSpan.innerHTML = contentResult.html;
// 图标按钮区域
const actionsDiv = document.createElement("div");
actionsDiv.classList.add("message-actions");
// 复制按钮
const copyBtn = document.createElement("button");
copyBtn.classList.add("icon-btn");
copyBtn.innerHTML = "📋"; // 剪贴板图标
copyBtn.title = "复制";
copyBtn.onclick = () => {
navigator.clipboard.writeText(text)
.then(() => alert("复制成功!"))
.catch(() => alert("复制失败,请手动复制。"));
};
// 删除按钮
const delBtn = document.createElement("button");
delBtn.classList.add("icon-btn");
delBtn.innerHTML = "🗑️"; // 垃圾桶图标
delBtn.title = "删除";
delBtn.onclick = () => {
// 找到当前消息在 chatHistory 中的索引
const index = chatHistory.findIndex(msg => msg.content === text && msg.role === role);
if (index !== -1) {
chatHistory.splice(index, 1);
localStorage.setItem(KEY_STORAGE, JSON.stringify(chatHistory));
renderHistoryList();
renderChatFromHistory();
}
};
actionsDiv.appendChild(copyBtn);
actionsDiv.appendChild(delBtn);
msgDiv.appendChild(textSpan);
msgDiv.appendChild(actionsDiv);
wrapper.appendChild(msgDiv);
chatContainer.appendChild(wrapper);
// 绑定运行代码按钮的事件
contentResult.codeBlocks.forEach(block => {
const btn = msgDiv.querySelector(`.run-code-btn[data-codeblockid="${block.id}"]`);
if (btn) {
btn.addEventListener('click', () => {
runCode(block.language, block.code);
});
}
});
scrollToBottom();
// 公式渲染
if (window.MathJax) {
MathJax.typesetPromise([textSpan]).catch(err => console.log(err));
}
}
/************************************************************
* 处理消息文本,识别代码块并生成对应的 HTML
************************************************************/
function processMessageText(text) {
const codeBlockRegex = /```(\w*)\n([\s\S]*?)```/g;
let html = '';
let lastIndex = 0;
let match;
let codeBlocks = [];
while ((match = codeBlockRegex.exec(text)) !== null) {
// Append text before code block
html += escapeHtml(text.slice(lastIndex, match.index)).replace(/\n/g, '<br/>');
// Code block
const language = match[1] || 'plaintext';
const code = match[2];
// Unique ID for code block
const codeBlockId = 'codeblock_' + codeBlocks.length;
// Replace code block with formatted code and Run Code button
html += `
<pre><code>${escapeHtml(code)}</code></pre>
<button class="run-code-btn" data-codeblockid="${codeBlockId}">运行代码</button>
`;
// Store code block
codeBlocks.push({ id: codeBlockId, language, code });
lastIndex = match.index + match[0].length;
}
// Append remaining text after last code block
html += escapeHtml(text.slice(lastIndex)).replace(/\n/g, '<br/>');
return { html, codeBlocks };
}
/************************************************************
* 转义 HTML,防止 XSS
************************************************************/
function escapeHtml(text) {
return text.replace(/[&<>"']/g, function (m) {
switch (m) {
case '&':
return '&';
case '<':
return '<';
case '>':
return '>';
case '"':
return '"';
case "'":
return ''';
default:
return m;
}
});
}
/************************************************************
* 运行代码并在模态框中显示结果
************************************************************/
function runCode(language, code) {
if (language.toLowerCase() === 'html') {
// 创建模态框
const modal = document.createElement('div');
modal.classList.add('code-modal');
modal.innerHTML = `
<div class="code-modal-content">
<span class="code-modal-close">×</span>
<div class="code-modal-header">代码运行结果:</div>
<div class="code-output-container"></div>
</div>
`;
document.body.appendChild(modal);
const closeModal = modal.querySelector('.code-modal-close');
closeModal.addEventListener('click', () => {
document.body.removeChild(modal);
});
// 在 iframe 中运行代码
const outputContainer = modal.querySelector('.code-output-container');
const iframe = document.createElement('iframe');
iframe.sandbox = 'allow-scripts allow-same-origin';
iframe.style.width = '100%';
iframe.style.height = '100%';
outputContainer.appendChild(iframe);
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
iframeDoc.open();
iframeDoc.write(code);
iframeDoc.close();
} else {
alert('目前仅支持运行 HTML 代码。');
}
}
/************************************************************
* 从历史记录渲染到屏幕
************************************************************/
function renderChatFromHistory() {
chatContainer.innerHTML = "";
chatHistory.forEach(msg => {
// 不渲染 system 的消息
if (msg.role === "system") return;
renderSingleMessage(msg.role, msg.content, false);
});
}
/************************************************************
* 聊天记录:渲染到“工具箱”
************************************************************/
function renderHistoryList() {
historyContainer.innerHTML = "";
chatHistory.forEach((item, idx) => {
if (item.role === "system") return;
const row = document.createElement("div");
row.classList.add("history-item");
const t = new Date(item.time);
const timeStr = t.toLocaleTimeString("zh-CN", { hour12: false })
+ " " + t.toLocaleDateString("zh-CN");
const shortContent = item.content.length > 30
? item.content.slice(0, 30) + "..."
: item.content;
row.innerHTML = `
<div style="flex:1;">
[${item.role === "user" ? "用户" : "AI"}] ${escapeHtml(shortContent)}
<div style="font-size:12px;color:#999;">${timeStr}</div>
</div>
`;
const delBtn = document.createElement("button");
delBtn.textContent = "删除";
delBtn.onclick = () => {
chatHistory.splice(idx, 1);
localStorage.setItem(KEY_STORAGE, JSON.stringify(chatHistory));
renderHistoryList();
renderChatFromHistory();
};
row.appendChild(delBtn);
historyContainer.appendChild(row);
});
}
/************************************************************
* 滚动到底部
************************************************************/
function scrollToBottom() {
chatContainer.scrollTop = chatContainer.scrollHeight;
}
/************************************************************
* 应用颜色配置
************************************************************/
function applyColorConfig() {
// 桌面背景
chatContainer.style.backgroundColor = colorConfig.desktopColor;
// 所有聊天气泡
const allMessages = document.querySelectorAll(".chat-message");
allMessages.forEach(msgDiv => {
// 判断是 user 还是 ai
const wrapper = msgDiv.parentElement;
if (wrapper.classList.contains("wrapper-user")) {
msgDiv.style.backgroundColor = colorConfig.bubbleColorUser;
} else {
msgDiv.style.backgroundColor = colorConfig.bubbleColorAI;
}
msgDiv.style.color = colorConfig.fontColor;
});
}