您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
在任意网站上添加 DeepSeek 聊天窗口:窗口默认隐藏,仅通过快捷键 Ctrl+Alt+D 显示;通过官方API 输入,支持聊天、历史记录管理、新对话、模型切换与记忆功能。API 输出支持 Markdown 渲染。
当前为
// ==UserScript== // @name DeepSeek API聊天 // @namespace http://tampermonkey.net/ // @version 1.2 // @description 在任意网站上添加 DeepSeek 聊天窗口:窗口默认隐藏,仅通过快捷键 Ctrl+Alt+D 显示;通过官方API 输入,支持聊天、历史记录管理、新对话、模型切换与记忆功能。API 输出支持 Markdown 渲染。 // @author AMT // @match *://*/* // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @grant GM_setClipboard // @require https://cdn.jsdelivr.net/npm/[email protected]/marked.min.js // @connect *.deepseek.com // @connect deepseek.com // @license MIT // ==/UserScript== (function() { 'use strict'; // 检查跨域请求权限 if (typeof GM_xmlhttpRequest === 'undefined') { alert('请启用Tampermonkey的"允许访问跨域URL"权限'); return; } /***************** 创建Shadow DOM容器,确保UI样式隔离 *****************/ const host = document.createElement('div'); host.id = 'deepseek-chat-host'; host.style.all = 'initial'; // 重置所有样式 host.style.position = 'fixed'; host.style.right = '0'; host.style.top = '50%'; host.style.transform = 'translateY(-50%)'; host.style.zIndex = '9999'; // 默认隐藏整个界面 host.style.display = 'none'; document.body.appendChild(host); const shadow = host.attachShadow({ mode: 'open' }); // 添加全局样式到 shadow root(仅影响本UI内) const style = document.createElement('style'); style.textContent = ` /* 全局字体重置为系统默认 */ * { font-family: inherit !important; font-size: 18px; line-height: 1.5; } /* 全局按钮字体设置 */ button { font-size: 0.8em !important; font-family: inherit !important; font-weight: normal !important; line-height: normal !important; } /* 统一滚动条样式 */ ::-webkit-scrollbar { width: 12px; height: 12px; } /* 输出框滚动条 */ .conversation-div-style::-webkit-scrollbar-track { background: transparent; margin: 10px 0; } /* 输入框滚动条 */ textarea::-webkit-scrollbar-track { background: transparent !important; margin: 10px 0; } textarea { font-size: 1em !important; font-family: inherit !important; line-height: normal !important; } ::-webkit-scrollbar-corner { background: #333; } ::-webkit-scrollbar-thumb { background-color: #555; border-radius: 10px; border: 2px solid transparent; background-clip: content-box; } textarea:focus, input:focus { outline: none !important; border: 1px solid #4682B4 !important; } p { margin: 0.3em 0 !important; } pre { white-space: pre !important; background-color: #222 !important; color: #eee !important; padding: 0.8em !important; border-radius: 8px !important; overflow-x: auto !important; font-size: 0.9em !important; margin: 0.5em 0 !important; } code { background-color: transparent !important; padding: 0 !important; font-family: monospace !important; } .code-block-wrapper:hover button { opacity: 1 !important; } .code-block-wrapper button:hover { background-color: #666 !important; } `; shadow.appendChild(style); /***************** 全局变量与存储 *****************/ let apiKey = GM_getValue('deepseek_api', ''); let currentModel = "deepseek-chat"; let isStreaming = false; let streamCancel = false; let autoScrollEnabled = true; const modelDisplay = { "deepseek-chat": "Chat", "deepseek-reasoner": "Reasoner" }; let memoryEnabled = false; let chatHistory = JSON.parse(GM_getValue('deepseek_history', '[]')); let currentSession = []; let currentSessionId = Date.now(); let isSending = false; /***************** 工具函数 *****************/ function safeCopyToClipboard(text, button) { try { if (typeof GM_setClipboard !== 'undefined') { GM_setClipboard(text); } else if (navigator.clipboard?.writeText) { navigator.clipboard.writeText(text); } else { const textarea = document.createElement('textarea'); textarea.value = text; textarea.style.position = 'fixed'; textarea.style.top = '-9999px'; document.body.appendChild(textarea); textarea.select(); document.execCommand('copy'); document.body.removeChild(textarea); } if (button) { button.textContent = '✅'; button.style.backgroundColor = '#4CAF50'; // ✅ 强制刷新 Shadow DOM 以更新图标 requestAnimationFrame(() => { setTimeout(() => { button.textContent = '📋'; button.style.backgroundColor = '#444'; }, 1000); }); } } catch (e) { alert('复制失败,请手动复制'); console.error('复制失败:', e); } } function renderMarkdown(text) { const parsed = marked.parse(text.trim()); const tempDiv = document.createElement('div'); tempDiv.innerHTML = parsed; const codeBlocks = tempDiv.querySelectorAll('pre code'); codeBlocks.forEach(code => { const wrapper = document.createElement('div'); wrapper.className = 'code-block-wrapper'; wrapper.style.position = 'relative'; const pre = code.parentElement; if (pre && pre.tagName === 'PRE') { pre.style.margin = '0'; pre.parentElement.replaceChild(wrapper, pre); wrapper.appendChild(pre); const copyBtn = document.createElement('button'); copyBtn.textContent = '📋'; copyBtn.title = '复制代码'; copyBtn.style.cssText = ` position: absolute; top: 6px; right: 6px; font-size: 0.8em; background: #444; color: white; border: none; border-radius: 5px; padding: 2px 6px; cursor: pointer; opacity: 0.7; z-index: 10; transition: background 0.3s, opacity 0.3s; `; copyBtn.addEventListener('click', (e) => { e.stopPropagation(); safeCopyToClipboard(code.textContent, copyBtn); }); wrapper.appendChild(copyBtn); } }); return tempDiv; } function saveCurrentSession() { if (currentSession.length > 0) { let idx = chatHistory.findIndex(s => s.id === currentSessionId); const sessionRecord = { id: currentSessionId, messages: currentSession, timestamp: new Date().toLocaleString() }; if (idx === -1) { chatHistory.push(sessionRecord); } else { chatHistory[idx] = sessionRecord; } GM_setValue('deepseek_history', JSON.stringify(chatHistory)); } } function loadSession(sessionRecord) { currentSession = sessionRecord.messages; currentSessionId = sessionRecord.id; renderConversation(); } /***************** 跨域请求函数 *****************/ function gmFetch(url, options) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: options.method, url: url, headers: options.headers, data: options.body, onload: function(response) { if (response.status < 200 || response.status >= 300) { const error = new Error(`HTTP Error ${response.status}`); error.response = response; return reject(error); } response.json = function() { try { return JSON.parse(response.responseText); } catch (e) { console.error('JSON解析失败:', e); return null; } }; resolve(response); }, onerror: function(error) { error.message = `网络错误: ${error.error || error.statusText}`; reject(error); } }); }); } /***************** 主窗口与UI *****************/ const chatContainer = document.createElement('div'); chatContainer.id = 'deepseek-chat-ui'; chatContainer.style.all = 'initial'; chatContainer.style.fontFamily = 'Arial, sans-serif'; chatContainer.style.fontSize = '14px'; chatContainer.style.isolation = 'isolate'; chatContainer.style.position = 'fixed'; chatContainer.style.right = '0'; chatContainer.style.top = '50%'; chatContainer.style.transform = 'translateY(-50%)'; chatContainer.style.width = apiKey ? '35vw' : '15vw'; chatContainer.style.height = apiKey ? '75vh' : '16.67vh'; chatContainer.style.backgroundColor = '#333'; chatContainer.style.borderRadius = '10px'; chatContainer.style.boxShadow = '0 2px 8px rgba(0,0,0,0.2)'; chatContainer.style.transition = 'opacity 0.3s, transform 0.3s'; // 不再使用 opacity 控制显示,统一由 host.style.display 控制 shadow.appendChild(chatContainer); // 阻止内部点击影响外部,同时若点击不在历史记录面板内,则关闭面板 chatContainer.addEventListener('click', (e) => { e.stopPropagation(); if(historyPanel && !historyPanel.contains(e.target)) { hideHistoryPanel(); } }); const contentDiv = document.createElement('div'); contentDiv.style.width = '100%'; contentDiv.style.height = '100%'; contentDiv.style.display = 'flex'; contentDiv.style.flexDirection = 'column'; contentDiv.style.boxSizing = 'border-box'; contentDiv.style.color = 'white'; contentDiv.style.padding = '1em'; chatContainer.appendChild(contentDiv); /***************** 历史记录面板 *****************/ let historyPanel; function showHistoryPanel() { if(historyPanel) return; historyPanel = document.createElement('div'); historyPanel.id = 'history-panel'; historyPanel.style.zIndex = '10000'; historyPanel.style.position = 'absolute'; historyPanel.style.left = '0'; historyPanel.style.top = '0'; historyPanel.style.height = '100%'; historyPanel.style.width = '40%'; historyPanel.style.backgroundColor = '#222'; historyPanel.style.borderTopLeftRadius = '10px'; historyPanel.style.borderBottomLeftRadius = '10px'; historyPanel.style.overflowY = 'auto'; historyPanel.style.padding = '0.5em'; historyPanel.style.boxSizing = 'border-box'; const header = document.createElement('div'); header.style.display = 'flex'; header.style.justifyContent = 'space-between'; header.style.alignItems = 'center'; header.style.marginBottom = '0.5em'; const backBtn = document.createElement('button'); backBtn.innerText = '返回'; backBtn.style.fontSize = '1em'; backBtn.style.padding = '0.2em 0.5em'; backBtn.style.border = '1px solid white'; backBtn.style.borderRadius = '10px'; backBtn.style.backgroundColor = '#444'; backBtn.style.color = 'white'; backBtn.style.cursor = 'pointer'; backBtn.addEventListener('click', () => { hideHistoryPanel(); }); header.appendChild(backBtn); const title = document.createElement('span'); title.style.color = 'white'; title.innerText = '聊天历史'; header.appendChild(title); const clearBtn = document.createElement('button'); clearBtn.innerText = '清空所有'; clearBtn.style.fontSize = '1em'; clearBtn.style.padding = '0.2em 0.5em'; clearBtn.style.border = '1px solid white'; clearBtn.style.borderRadius = '10px'; clearBtn.style.backgroundColor = '#444'; clearBtn.style.color = 'white'; clearBtn.style.cursor = 'pointer'; clearBtn.addEventListener('click', () => { if(confirm("确定清空所有聊天记录吗?")) { chatHistory = []; GM_setValue('deepseek_history', JSON.stringify(chatHistory)); renderHistoryPanel(); currentSession = []; currentSessionId = Date.now(); if(conversationDiv) { conversationDiv.innerHTML = ''; } } }); header.appendChild(clearBtn); historyPanel.appendChild(header); renderHistoryPanel(); chatContainer.appendChild(historyPanel); } function hideHistoryPanel() { if(historyPanel && historyPanel.parentNode) { chatContainer.removeChild(historyPanel); historyPanel = null; } } function renderHistoryPanel() { if(!historyPanel) return; while(historyPanel.childNodes.length > 1) { historyPanel.removeChild(historyPanel.lastChild); } chatHistory.forEach(session => { const item = document.createElement('div'); let summary = session.timestamp; if(session.messages.length > 0) { const firstMsg = session.messages.find(m => m.role === 'user'); if(firstMsg) { summary += " - " + firstMsg.content.substring(0, 20) + "..."; } } item.innerText = summary; item.style.color = 'white'; item.style.padding = '0.3em'; item.style.borderBottom = '1px solid #666'; item.style.cursor = 'pointer'; item.addEventListener('click', () => { loadSession(session); renderConversation(); hideHistoryPanel(); }); item.addEventListener('contextmenu', (e) => { e.preventDefault(); if(confirm("删除该聊天记录?")) { if(session.id === currentSessionId) { currentSession = []; currentSessionId = Date.now(); if(conversationDiv) { conversationDiv.innerHTML = ''; } } chatHistory = chatHistory.filter(s => s.id !== session.id); GM_setValue('deepseek_history', JSON.stringify(chatHistory)); renderHistoryPanel(); } }); historyPanel.appendChild(item); }); } /***************** 对话区与输入区 *****************/ let conversationDiv; let messageInput; let sendBtn; // 全局声明 // 自动滚动监听:当滚动接近底部时自动恢复 function setupAutoScroll() { conversationDiv.addEventListener('scroll', () => { if (conversationDiv.scrollTop + conversationDiv.clientHeight >= conversationDiv.scrollHeight - 10) { autoScrollEnabled = true; } else { autoScrollEnabled = false; } }); } function renderConversation() { if(!conversationDiv) return; conversationDiv.innerHTML = ''; currentSession.forEach(msgObj => { // 使用自定义Markdown渲染(不高亮) addChatBubble(msgObj.role === 'user', msgObj.content, false); }); } // 流式输出结束后调用,恢复输入框和发送按钮 function finishStreaming() { isStreaming = false; streamCancel = false; messageInput.disabled = false; sendBtn.disabled = false; } function addChatBubble(isUser, text, isStream = false) { // 保留原始换行和空格,避免破坏代码缩进 const cleanedText = text.trim(); if (isUser) { const bubble = document.createElement('div'); bubble.style.cssText = ` padding: 0.5em; margin: 0.5em 0; border-radius: 12px; max-width: 80%; word-wrap: break-word; white-space: pre-wrap; background-color: #6699CC; color: white; align-self: flex-end; box-shadow: 0 2px 4px rgba(0,0,0,0.1); `; bubble.textContent = cleanedText; conversationDiv.appendChild(bubble); } else { const streamContainer = document.createElement('div'); streamContainer.style.cssText = ` padding: 0.5em; color: #EEEEEE; font-size: 0.95em; line-height: 1.6; white-space: pre-wrap; word-break: break-word; `; if (isStream) { messageInput.disabled = true; sendBtn.disabled = true; let rawContent = ''; let index = 0; const charArray = cleanedText.split(''); function streamOutput() { if (index < charArray.length && !streamCancel) { rawContent += charArray[index++]; streamContainer.textContent = rawContent; // 逐字符显示,纯文本 if (autoScrollEnabled) { conversationDiv.scrollTop = conversationDiv.scrollHeight; } setTimeout(streamOutput, 1000 / 60); } else { const renderedNode = renderMarkdown(rawContent); streamContainer.innerHTML = ''; // 清空旧内容 streamContainer.appendChild(renderedNode); finishStreaming(); } } isStreaming = true; streamOutput(); } else { const renderedNode = renderMarkdown(cleanedText); streamContainer.appendChild(renderedNode); } conversationDiv.appendChild(streamContainer); } if (autoScrollEnabled) { conversationDiv.scrollTop = conversationDiv.scrollHeight; } } /***************** 渲染整个界面 *****************/ function renderUI() { contentDiv.innerHTML = ''; if (!apiKey) { const promptText = document.createElement('div'); promptText.innerText = 'DeepSeek'; promptText.style.textAlign = 'center'; promptText.style.fontSize = '2em'; promptText.style.marginBottom = '0.5em'; contentDiv.appendChild(promptText); const apiInput = document.createElement('input'); apiInput.type = 'text'; apiInput.placeholder = '请输入api key'; apiInput.style.width = '100%'; apiInput.style.fontSize = '1em'; apiInput.style.padding = '0.5em'; apiInput.style.boxSizing = 'border-box'; apiInput.style.borderRadius = '10px'; apiInput.style.border = '1px solid white'; apiInput.style.backgroundColor = '#444'; apiInput.style.color = 'white'; apiInput.style.marginBottom = '0.5em'; contentDiv.appendChild(apiInput); const confirmBtn = document.createElement('button'); confirmBtn.innerText = '确认'; confirmBtn.style.width = '100%'; confirmBtn.style.fontSize = '1em'; confirmBtn.style.padding = '0.5em'; confirmBtn.style.borderRadius = '10px'; confirmBtn.style.border = '1px solid white'; confirmBtn.style.backgroundColor = '#444'; confirmBtn.style.color = 'white'; confirmBtn.style.cursor = 'pointer'; contentDiv.appendChild(confirmBtn); confirmBtn.addEventListener('click', (e) => { e.stopPropagation(); const value = apiInput.value.trim(); if (value) { apiKey = value; GM_setValue('deepseek_api', apiKey); chatContainer.style.height = '75vh'; chatContainer.style.width = '35vw'; currentSession = []; currentSessionId = Date.now(); renderUI(); } }); } else { const headerDiv = document.createElement('div'); headerDiv.style.display = 'flex'; headerDiv.style.justifyContent = 'space-between'; headerDiv.style.alignItems = 'center'; headerDiv.style.marginBottom = '0.5em'; contentDiv.appendChild(headerDiv); const leftHeader = document.createElement('div'); leftHeader.style.display = 'flex'; leftHeader.style.gap = '0.5em'; headerDiv.appendChild(leftHeader); const historyBtn = document.createElement('button'); historyBtn.innerText = '历史记录'; historyBtn.style.fontSize = '1em'; historyBtn.style.padding = '0.3em'; historyBtn.style.borderRadius = '10px'; historyBtn.style.border = '1px solid white'; historyBtn.style.backgroundColor = '#444'; historyBtn.style.color = 'white'; historyBtn.style.cursor = 'pointer'; historyBtn.addEventListener('click', (e) => { e.stopPropagation(); if(historyPanel) { hideHistoryPanel(); } else { showHistoryPanel(); } }); leftHeader.appendChild(historyBtn); const newConvBtn = document.createElement('button'); newConvBtn.innerText = '开启新对话'; newConvBtn.style.fontSize = '1em'; newConvBtn.style.padding = '0.3em'; newConvBtn.style.borderRadius = '10px'; newConvBtn.style.border = '1px solid white'; newConvBtn.style.backgroundColor = '#444'; newConvBtn.style.color = 'white'; newConvBtn.style.cursor = 'pointer'; newConvBtn.addEventListener('click', (e) => { e.stopPropagation(); saveCurrentSession(); currentSession = []; currentSessionId = Date.now(); if(conversationDiv) { conversationDiv.innerHTML = ''; } }); leftHeader.appendChild(newConvBtn); const reenterBtn = document.createElement('button'); reenterBtn.innerText = '重新输入api'; reenterBtn.style.fontSize = '1em'; reenterBtn.style.padding = '0.3em'; reenterBtn.style.borderRadius = '10px'; reenterBtn.style.border = '1px solid white'; reenterBtn.style.backgroundColor = '#444'; reenterBtn.style.color = 'white'; reenterBtn.style.cursor = 'pointer'; reenterBtn.addEventListener('click', (e) => { e.stopPropagation(); hideHistoryPanel(); saveCurrentSession(); apiKey = ''; GM_setValue('deepseek_api', ''); chatContainer.style.height = '16.67vh'; chatContainer.style.width = '15vw'; renderUI(); }); headerDiv.appendChild(reenterBtn); conversationDiv = document.createElement('div'); conversationDiv.style.flex = '1'; conversationDiv.style.overflowY = 'auto'; conversationDiv.style.marginBottom = '0.5em'; conversationDiv.style.padding = '0.5em'; conversationDiv.style.boxSizing = 'border-box'; conversationDiv.style.backgroundColor = '#333'; conversationDiv.style.display = 'flex'; conversationDiv.style.flexDirection = 'column'; conversationDiv.style.gap = '0.5em'; contentDiv.appendChild(conversationDiv); setupAutoScroll(); renderConversation(); const inputContainer = document.createElement('div'); inputContainer.style.position = 'relative'; inputContainer.style.width = '100%'; inputContainer.style.boxSizing = 'border-box'; inputContainer.style.height = '10vh'; contentDiv.appendChild(inputContainer); messageInput = document.createElement('textarea'); messageInput.placeholder = '给deepseek发送消息'; messageInput.style.position = 'absolute'; messageInput.style.left = '0'; messageInput.style.right = '0'; messageInput.style.bottom = '0'; messageInput.style.height = '10vh'; messageInput.style.padding = '0.5em 0.5em 3em 0.5em'; messageInput.style.fontSize = '1.2em'; messageInput.style.boxSizing = 'border-box'; messageInput.style.borderRadius = '10px'; messageInput.style.border = '1px solid white'; messageInput.style.backgroundColor = '#444'; messageInput.style.color = 'white'; messageInput.style.overflowY = 'hidden'; messageInput.style.resize = 'none'; inputContainer.appendChild(messageInput); const inputOverlay = document.createElement('div'); inputOverlay.style.position = 'absolute'; inputOverlay.style.left = '0.5em'; inputOverlay.style.right = '0.8em'; inputOverlay.style.bottom = '0.07em'; inputOverlay.style.height = '2.5em'; inputOverlay.style.backgroundColor = '#444'; inputOverlay.style.pointerEvents = 'none'; inputContainer.appendChild(inputOverlay); // 创建模型、记忆和发送按钮 const modelBtn = document.createElement('button'); modelBtn.innerText = "深度思考R1"; modelBtn.style.position = 'absolute'; modelBtn.style.left = '0.5em'; modelBtn.style.bottom = '0.5em'; modelBtn.style.width = '8em'; modelBtn.style.height = '2em'; modelBtn.style.fontSize = '1em'; modelBtn.style.lineHeight = '2em'; modelBtn.style.textAlign = 'center'; modelBtn.style.borderRadius = '10px'; modelBtn.style.border = '1px solid white'; modelBtn.style.backgroundColor = '#444'; modelBtn.style.color = 'white'; modelBtn.style.cursor = 'pointer'; modelBtn.style.zIndex = '10'; modelBtn.addEventListener('click', () => { currentModel = (currentModel === 'deepseek-chat' ? 'deepseek-reasoner' : 'deepseek-chat'); modelBtn.style.backgroundColor = currentModel === 'deepseek-reasoner' ? '#87CEFA' : '#444'; }); inputContainer.appendChild(modelBtn); const memoryBtn = document.createElement('button'); memoryBtn.innerText = '记忆'; memoryBtn.style.position = 'absolute'; memoryBtn.style.left = '9em'; memoryBtn.style.bottom = '0.5em'; memoryBtn.style.width = '3em'; memoryBtn.style.height = '2em'; memoryBtn.style.fontSize = '1em'; memoryBtn.style.lineHeight = '2em'; memoryBtn.style.textAlign = 'center'; memoryBtn.style.borderRadius = '10px'; memoryBtn.style.border = '1px solid white'; memoryBtn.style.backgroundColor = memoryEnabled ? '#87CEFA' : '#444'; memoryBtn.style.color = 'white'; memoryBtn.style.cursor = 'pointer'; memoryBtn.style.zIndex = '10'; memoryBtn.addEventListener('click', () => { memoryEnabled = !memoryEnabled; memoryBtn.style.backgroundColor = memoryEnabled ? '#87CEFA' : '#444'; }); inputContainer.appendChild(memoryBtn); sendBtn = document.createElement('button'); sendBtn.innerText = '发送'; sendBtn.style.position = 'absolute'; sendBtn.style.right = '1.2em'; sendBtn.style.bottom = '0.5em'; sendBtn.style.width = '3em'; sendBtn.style.height = '2em'; sendBtn.style.fontSize = '1em'; sendBtn.style.lineHeight = '2em'; sendBtn.style.textAlign = 'center'; sendBtn.style.borderRadius = '10px'; sendBtn.style.border = '1px solid white'; sendBtn.style.backgroundColor = '#444'; sendBtn.style.color = 'white'; sendBtn.style.cursor = 'pointer'; sendBtn.style.zIndex = '10'; inputContainer.appendChild(sendBtn); // 自动调整输入框高度 function autoResize() { const initialHeight = window.innerHeight * 0.10; messageInput.style.height = 'auto'; let newHeight = messageInput.scrollHeight; if (newHeight < initialHeight) newHeight = initialHeight; const maxHeight = window.innerHeight * 0.25; if (newHeight > maxHeight) { inputContainer.style.height = messageInput.style.height = maxHeight + 'px'; messageInput.style.overflowY = 'auto'; } else { inputContainer.style.height = messageInput.style.height = newHeight + 'px'; messageInput.style.overflowY = 'hidden'; } sendBtn.disabled = (messageInput.value.trim() === ''); } messageInput.addEventListener('input', autoResize); autoResize(); // 阻止流式输出期间的输入(包括回车发送) messageInput.addEventListener('keydown', (e) => { if (isStreaming) { e.preventDefault(); return; } if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); if(messageInput.value.trim() === '') return; if (!isSending) sendMessage(); } }); messageInput.addEventListener('focus', () => { if(isStreaming) { messageInput.blur(); } }); // 发送消息函数 async function sendMessage() { if (isSending || isStreaming) { if (isStreaming) { streamCancel = true; } return; } isSending = true; messageInput.disabled = true; sendBtn.disabled = true; const msg = messageInput.value.trim(); if (!msg) { isSending = false; messageInput.disabled = false; return; } addChatBubble(true, msg); currentSession.push({ role: "user", content: msg }); saveCurrentSession(); messageInput.value = ''; autoResize(); const waitingIndicator = document.createElement('div'); waitingIndicator.textContent = '思考中...'; waitingIndicator.style.cssText = ` color: #888; font-size: 0.9em; font-style: italic; padding: 0.3em 0; align-self: flex-start; animation: blink 1s infinite; `; conversationDiv.appendChild(waitingIndicator); conversationDiv.scrollTop = conversationDiv.scrollHeight; try { let messagesPayload = [ { role: "system", content: "You are a helpful assistant." } ]; if(memoryEnabled && currentSession.length > 0) { messagesPayload = messagesPayload.concat(currentSession); } if(!memoryEnabled) { messagesPayload.push({ role: "user", content: msg }); } const response = await gmFetch('https://api.deepseek.com/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + apiKey }, body: JSON.stringify({ messages: messagesPayload, model: currentModel }) }); const data = response.json(); if (waitingIndicator.parentNode) { conversationDiv.removeChild(waitingIndicator); } if (data.choices?.[0]?.message?.content) { const reply = data.choices[0].message.content.trim(); addChatBubble(false, reply, true); // 流式输出 currentSession.push({ role: "assistant", content: reply }); saveCurrentSession(); } } catch (err) { let errorMsg = '请求失败: '; if (err.response) { errorMsg += `${err.response.status} - ${err.response.statusText}`; try { const errData = JSON.parse(err.response.responseText); errorMsg += ` (${errData.error?.message || '未知错误'})`; } catch(e) {} } else { errorMsg += err.message || err.toString(); } addChatBubble(false, errorMsg); } finally { isSending = false; if (waitingIndicator && waitingIndicator.parentNode) { conversationDiv.removeChild(waitingIndicator); } if (!isStreaming) { messageInput.disabled = false; sendBtn.disabled = false; } conversationDiv.scrollTop = conversationDiv.scrollHeight; } } sendBtn.addEventListener('click', sendMessage); } } renderUI(); /***************** 显示与隐藏窗口 *****************/ // 使用 host.style.display 控制整个聊天界面的显示/隐藏 let visible = false; document.addEventListener('keydown', (e) => { if (e.ctrlKey && e.altKey && e.key.toLowerCase() === 'd') { visible = !visible; host.style.display = visible ? 'block' : 'none'; if (visible) { setTimeout(() => { const input = shadow.querySelector('textarea'); input?.focus(); }, 100); } e.preventDefault(); e.stopPropagation(); } }, true); document.addEventListener('click', (e) => { if (!e.composedPath().includes(host)) { visible = false; host.style.display = 'none'; hideHistoryPanel(); } }); })();