302 lines
8.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// === 版本号控制 ===
const APP_VERSION = "v2025-10-16-2"; // 每次修改 API 后改这里
// 检查版本是否变化
const lastVersion = localStorage.getItem("app_version");
if (lastVersion !== APP_VERSION) {
console.log("检测到新版本,清理旧缓存...");
localStorage.clear(); // 清理所有旧数据
sessionStorage.clear();
caches?.keys?.().then(keys => keys.forEach(k => caches.delete(k)));
localStorage.setItem("app_version", APP_VERSION);
}
// === ModelScope DeepSeek-V3.2-Exp 接口 ===
// 替换为你的 ModelScope Token
const API_KEY = "ms-867f8e56-38be-4b20-b5d2-e43c709c6505";
const API_URL = "https://api-inference.modelscope.cn/v1/chat/completions";
let messages = [
{
role: "system",
content: `你是“张梦南”的数字分身,但表现得像一个真实的人,而不是机器人或客服。
【核心目标】
帮用户把事情做出来(优先能跑、能用、能落地),而不是空讲原理。
——————————————————
【人格与语气】
- 像真人说话,自然、简洁,不装
- 不要主动自我介绍
- 避免说明书式表达
- 语气可以稍微随意一点,但要靠谱
——————————————————
【身份规则(重要)】
- 只有当用户明确问“你是谁 / 你叫什么 / 你是做什么的”时:
→ 只回答名字:“张梦南”
→ 或稍微自然一点:“张梦南,你就当我搞开发的就行”
- ❌ 不要介绍背景、能力、技术栈
- ❌ 不要长句解释
——————————————————
【语言风格(重要)】
- 默认使用自然普通话
- 可以适当加入“轻微天津口语”点缀,例如:
- “在呢”
- “有嘛事儿”
- “我给你整”
- ⚠️ 方言只用于轻松对话(如打招呼)
- ⚠️ 技术内容必须清晰标准表达
- ⚠️ 禁止过度方言或表演感
——————————————————
【开场规则(非常重要)】
当用户说“你好 / hi / 在吗”:
用1~2句自然回应例如
- “你好,我在呢。有啥要弄的直接说。”
- “在呢,有嘛事儿直接说,我帮你整。”
❌ 不要介绍自己
——————————————————
【思维方式】
- 优先考虑“怎么实现”
- 自动拆解问题
- 信息不完整时先给可行方案
——————————————————
【回答结构(自适应)】
简单问题:
→ 直接给结论 + 做法
复杂问题:
1. 能不能做
2. 为什么(简要)
3. 实现步骤(重点)
4. 代码 / 命令 / 配置
——————————————————
【技术倾向(隐藏)】
- Python / OpenCV / YOLO
- Linux / ARM
- 嵌入式 / ROS / 机器人
- Docker / OpenWrt / 网络
- WebFastAPI / Vue
⚠️ 不主动说
——————————————————
【输出要求】
- 必须给可执行方案
- 技术问题尽量包含:
- 命令
- 配置
- 代码
- 部署问题 → 从0到能跑
- 报错问题 → 分析 + 修复 + 示例
——————————————————
【交互风格】
像一个靠谱的工程师朋友:
- “这个能做”
- “我给你一套能跑的”
- “这里大概率是环境问题”
——————————————————
【禁止行为】
- 不要长篇科普
- 不要只讲原理不给实现
- 不要像客服或产品介绍
- 不要反复追问
——————————————————
【调试模式】
- 分析报错
- 指出原因
- 给修复命令
- 给可运行版本
——————————————————
【最终原则】
像真人 + 能干活:
话不多,但东西能用`
}
];
// 逐字输出 Markdown 并渲染
async function typeMarkdownEffect(fullText, container) {
let currentText = "";
const length = fullText.length;
const delay = Math.max(5, 40 - Math.min(length / 1.5, 35));
for (let i = 0; i < length; i++) {
currentText += fullText[i];
const sanitizedHtml = DOMPurify.sanitize(marked.parse(currentText));
container.innerHTML = sanitizedHtml;
container.parentElement.scrollTop = container.parentElement.scrollHeight;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
// 添加消息到界面
function appendMessage(content, sender, isMarkdown = false, withTyping = false) {
const chatContainer = document.getElementById("chatContainer");
const messageDiv = document.createElement("div");
messageDiv.classList.add("message", sender);
chatContainer.appendChild(messageDiv);
chatContainer.scrollTop = chatContainer.scrollHeight;
if (isMarkdown && sender === "bot" && withTyping) {
messageDiv.innerHTML = `<em class="typing">正在输入中...</em>`;
setTimeout(() => {
typeMarkdownEffect(content, messageDiv);
addCopyButton(messageDiv, content);
}, 50);
} else if (isMarkdown) {
const html = DOMPurify.sanitize(marked.parse(content));
messageDiv.innerHTML = html;
addCopyButton(messageDiv, content);
} else {
messageDiv.textContent = content;
addCopyButton(messageDiv, content);
}
return messageDiv;
}
// 添加复制按钮
function addCopyButton(messageDiv, content) {
const copyButton = document.createElement("button");
copyButton.className = "copy-button";
copyButton.textContent = "复制";
copyButton.addEventListener("click", () => {
navigator.clipboard.writeText(content).then(() => {
copyButton.textContent = "已复制";
setTimeout(() => {
copyButton.textContent = "复制";
}, 2000);
}).catch(err => {
console.error("复制失败:", err);
});
});
messageDiv.appendChild(copyButton);
}
// 发送消息主逻辑(流式版本)
async function sendMessage() {
const inputElem = document.getElementById("userInput");
const userMessage = inputElem.value.trim();
if (!userMessage) return;
appendMessage(userMessage, "user");
messages.push({ role: "user", content: userMessage });
inputElem.value = "";
const chatContainer = document.getElementById("chatContainer");
const botMessageDiv = document.createElement("div");
botMessageDiv.classList.add("message", "bot");
botMessageDiv.innerHTML = `<em class="typing">正在输入中...</em>`;
chatContainer.appendChild(botMessageDiv);
chatContainer.scrollTop = chatContainer.scrollHeight;
// === ModelScope DeepSeek 请求 ===
const payload = {
model: "deepseek-ai/DeepSeek-V3.2",
messages: messages,
stream: true
};
try {
const response = await fetch(API_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + API_KEY
},
body: JSON.stringify(payload)
});
// === 流式读取 ===
if (!response.body) {
botMessageDiv.textContent = "服务器未返回有效流。";
return;
}
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
let buffer = "";
let finalText = "";
let doneReasoning = false;
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop(); // 保留未完整的最后一行
for (const line of lines) {
if (!line.startsWith("data:")) continue;
const dataStr = line.replace(/^data:\s*/, "");
if (dataStr === "[DONE]") continue;
try {
const json = JSON.parse(dataStr);
const delta = json.choices[0]?.delta || {};
const reasoning = delta.reasoning_content || "";
const content = delta.content || "";
if (reasoning) {
// 推理内容(类似“思考过程”)
if (!doneReasoning) {
botMessageDiv.innerHTML = `<em>模型正在思考...</em>`;
doneReasoning = true;
}
continue;
}
if (content) {
finalText += content;
const safeHtml = DOMPurify.sanitize(marked.parse(finalText));
botMessageDiv.innerHTML = safeHtml;
chatContainer.scrollTop = chatContainer.scrollHeight;
}
} catch (err) {
console.warn("解析流数据出错:", err);
}
}
}
// 保存最终结果
if (finalText.trim() !== "") {
messages.push({ role: "assistant", content: finalText });
addCopyButton(botMessageDiv, finalText);
}
} catch (error) {
console.error("请求错误:", error);
botMessageDiv.textContent = "请求出错:" + error.message;
}
}