// === 版本号控制 === 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 / 网络 - Web(FastAPI / 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 = `正在输入中...`; 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 = `正在输入中...`; 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 = `模型正在思考...`; 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; } }