Last active
August 22, 2026 12:17
-
-
Save linyanm/e7d2fcd0208957cf4a3f1a6ef90d4ffb to your computer and use it in GitHub Desktop.
DeepSeek 提示词注入:在 chat.deepseek.com 静默注入系统提示词(Safari Userscripts / Tampermonkey)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name DeepSeek 提示词注入 | |
| // @namespace https://hermes.local/ | |
| // @version 0.0.1 | |
| // @description 在 chat.deepseek.com 静默注入系统提示词:多模板管理、输入栏胶囊切换、可选仅首条注入 / 动态时间 / 专家模式 | |
| // @match https://chat.deepseek.com/* | |
| // @icon https://www.google.com/s2/favicons?sz=256&domain=https://chat.deepseek.com/ | |
| // @downloadURL https://gist.githubusercontent.com/linyanm/e7d2fcd0208957cf4a3f1a6ef90d4ffb/raw/DeepSeek%20%E6%8F%90%E7%A4%BA%E8%AF%8D%E6%B3%A8%E5%85%A5.user.js | |
| // @updateURL https://gist.githubusercontent.com/linyanm/e7d2fcd0208957cf4a3f1a6ef90d4ffb/raw/DeepSeek%20%E6%8F%90%E7%A4%BA%E8%AF%8D%E6%B3%A8%E5%85%A5.user.js | |
| // @grant none | |
| // @inject-into page | |
| // @sandbox raw | |
| // @run-at document-start | |
| // @noframes | |
| // ==/UserScript== | |
| (function () { | |
| 'use strict'; | |
| if (window.__dspPromptHooked) return; | |
| window.__dspPromptHooked = true; | |
| const LS_PROMPTS = 'dsp_prompts_list'; | |
| const LS_ACTIVE = 'dsp_active_prompt_id'; | |
| const LS_AUTO_EXPERT = 'dsp_auto_expert_enabled'; | |
| const LS_FIRST_ONLY = 'dsp_first_only_enabled'; | |
| const State = { | |
| prompts: [], | |
| activeId: '', | |
| autoExpert: false, | |
| firstOnly: false, | |
| load() { | |
| try { | |
| this.prompts = JSON.parse(localStorage.getItem(LS_PROMPTS) || '[]'); | |
| if (!Array.isArray(this.prompts)) this.prompts = []; | |
| } catch { | |
| this.prompts = []; | |
| } | |
| this.activeId = localStorage.getItem(LS_ACTIVE) || ''; | |
| this.autoExpert = localStorage.getItem(LS_AUTO_EXPERT) === 'true'; | |
| this.firstOnly = localStorage.getItem(LS_FIRST_ONLY) === 'true'; | |
| }, | |
| save() { | |
| localStorage.setItem(LS_PROMPTS, JSON.stringify(this.prompts)); | |
| if (this.activeId) localStorage.setItem(LS_ACTIVE, this.activeId); | |
| else localStorage.removeItem(LS_ACTIVE); | |
| localStorage.setItem(LS_AUTO_EXPERT, String(this.autoExpert)); | |
| localStorage.setItem(LS_FIRST_ONLY, String(this.firstOnly)); | |
| }, | |
| active() { | |
| if (!this.activeId) return null; | |
| return this.prompts.find((p) => p.id === this.activeId) || null; | |
| }, | |
| }; | |
| State.load(); | |
| // ----- 网络拦截:发送前把当前提示词写进请求体 ----- | |
| function buildPromptText(item) { | |
| let text = (item.content || '').trim(); | |
| if (!text) return ''; | |
| if (item.includeTime) { | |
| const timeStr = new Date().toLocaleString('zh-CN', { hour12: false }); | |
| text += `\n\n[系统附加信息:当前实时系统时间为 ${timeStr}]`; | |
| } | |
| return text; | |
| } | |
| function injectIntoParsed(parsed, customPrompt, baseContent) { | |
| if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return false; | |
| let modified = false; | |
| if (Array.isArray(parsed.messages) && parsed.messages.length) { | |
| const first = parsed.messages[0]; | |
| if (first && first.role === 'system' && typeof first.content === 'string') { | |
| if (!first.content.startsWith(baseContent)) { | |
| first.content = customPrompt + '\n\n' + first.content; | |
| modified = true; | |
| } | |
| } else { | |
| const already = parsed.messages.some( | |
| (m) => m && m.role === 'system' && typeof m.content === 'string' && m.content.startsWith(baseContent), | |
| ); | |
| if (!already) { | |
| parsed.messages.unshift({ role: 'system', content: customPrompt }); | |
| modified = true; | |
| } | |
| } | |
| return modified; | |
| } | |
| for (const key of ['prompt', 'message']) { | |
| if (typeof parsed[key] === 'string' && parsed[key] && !parsed[key].startsWith(baseContent)) { | |
| parsed[key] = customPrompt + '\n\n' + parsed[key]; | |
| modified = true; | |
| } | |
| } | |
| return modified; | |
| } | |
| function modifyRequest(bodyStr) { | |
| const item = State.active(); | |
| if (!item) return bodyStr; | |
| const baseContent = (item.content || '').trim(); | |
| if (!baseContent) return bodyStr; | |
| const customPrompt = buildPromptText(item); | |
| if (!customPrompt) return bodyStr; | |
| try { | |
| const parsed = JSON.parse(bodyStr); | |
| if (State.firstOnly && !isFirstTurn(parsed)) return bodyStr; | |
| if (injectIntoParsed(parsed, customPrompt, baseContent)) { | |
| return JSON.stringify(parsed); | |
| } | |
| } catch { | |
| /* 非 JSON 请求原样放行 */ | |
| } | |
| return bodyStr; | |
| } | |
| function isFirstTurn(parsed) { | |
| if (!parsed || typeof parsed !== 'object') return true; | |
| if (parsed.parent_message_id !== null && parsed.parent_message_id !== undefined) return false; | |
| if (Array.isArray(parsed.messages)) { | |
| return !parsed.messages.some((m) => m && m.role === 'assistant'); | |
| } | |
| return true; | |
| } | |
| function isCompletionUrl(url) { | |
| try { | |
| const u = new URL(String(url || ''), location.href); | |
| return /\/chat\/completions?\/?$/.test(u.pathname); | |
| } catch { | |
| return /\/chat\/completion/i.test(String(url || '')); | |
| } | |
| } | |
| async function rewriteBody(body) { | |
| if (!body) return { body, changed: false }; | |
| if (typeof body === 'string') { | |
| const next = modifyRequest(body); | |
| return { body: next, changed: next !== body }; | |
| } | |
| if (typeof Blob !== 'undefined' && body instanceof Blob) { | |
| const text = await body.text(); | |
| const next = modifyRequest(text); | |
| if (next !== text) { | |
| return { body: new Blob([next], { type: body.type || 'application/json' }), changed: true }; | |
| } | |
| return { body, changed: false }; | |
| } | |
| if (body instanceof Uint8Array || body instanceof ArrayBuffer) { | |
| try { | |
| const text = new TextDecoder('utf-8').decode(body); | |
| const next = modifyRequest(text); | |
| if (next !== text) { | |
| return { body: new TextEncoder().encode(next), changed: true }; | |
| } | |
| } catch { | |
| /* ignore */ | |
| } | |
| } | |
| return { body, changed: false }; | |
| } | |
| const xhrMeta = new WeakMap(); | |
| const xhrOpen = XMLHttpRequest.prototype.open; | |
| const xhrSend = XMLHttpRequest.prototype.send; | |
| XMLHttpRequest.prototype.open = function (method, url, ...rest) { | |
| xhrMeta.set(this, { url: String(url) }); | |
| return xhrOpen.call(this, method, url, ...rest); | |
| }; | |
| XMLHttpRequest.prototype.send = function (body) { | |
| try { | |
| const meta = xhrMeta.get(this); | |
| if (meta && isCompletionUrl(meta.url) && typeof body === 'string') { | |
| body = modifyRequest(body); | |
| } | |
| } catch (err) { | |
| console.error('[DS Prompt] XHR hook error', err); | |
| } | |
| return xhrSend.call(this, body); | |
| }; | |
| const origFetch = window.fetch; | |
| window.fetch = async function (...args) { | |
| try { | |
| let input = args[0]; | |
| let init = args[1]; | |
| let url = ''; | |
| let body = null; | |
| const isRequest = typeof Request !== 'undefined' && input instanceof Request; | |
| if (isRequest) { | |
| url = input.url; | |
| body = init && init.body !== undefined ? init.body : await input.clone().text().catch(() => null); | |
| } else { | |
| url = typeof input === 'string' ? input : (input && input.url) || ''; | |
| body = init && init.body; | |
| } | |
| if (isCompletionUrl(url) && body) { | |
| const rewritten = await rewriteBody(body); | |
| if (rewritten.changed) { | |
| if (isRequest && (!init || init.body === undefined)) { | |
| args[0] = new Request(input, { body: rewritten.body }); | |
| } else { | |
| args[1] = Object.assign({}, init || {}, { body: rewritten.body }); | |
| } | |
| } | |
| } | |
| } catch (err) { | |
| console.error('[DS Prompt] fetch hook error', err); | |
| } | |
| return origFetch.apply(this, args); | |
| }; | |
| // ----- UI ----- | |
| function esc(text) { | |
| const d = document.createElement('div'); | |
| d.textContent = text == null ? '' : String(text); | |
| return d.innerHTML; | |
| } | |
| function toast(msg) { | |
| let el = document.getElementById('dsp-toast'); | |
| if (!el) { | |
| el = document.createElement('div'); | |
| el.id = 'dsp-toast'; | |
| document.body.appendChild(el); | |
| } | |
| el.textContent = msg; | |
| el.classList.add('show'); | |
| clearTimeout(toast._t); | |
| toast._t = setTimeout(() => el.classList.remove('show'), 2200); | |
| } | |
| const CSS = ` | |
| #dsp-toast { | |
| position: fixed; top: 20px; left: 50%; z-index: 1000001; | |
| transform: translateX(-50%) translateY(-12px); | |
| background: var(--dsw-alias-bg-base, #fff); | |
| color: var(--dsw-alias-label-primary, #1a1a1a); | |
| border: 1px solid var(--dsw-alias-border-l2, rgba(0,0,0,.08)); | |
| box-shadow: 0 8px 28px rgba(0,0,0,.12); | |
| padding: 10px 18px; border-radius: 999px; | |
| font-size: 13px; font-weight: 500; opacity: 0; | |
| pointer-events: none; transition: .25s ease; | |
| } | |
| #dsp-toast.show { opacity: 1; transform: translateX(-50%) translateY(0); } | |
| #dsp-overlay { | |
| position: fixed; inset: 0; z-index: 999997; | |
| background: rgba(0,0,0,.22); | |
| backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); | |
| opacity: 0; pointer-events: none; transition: opacity .2s ease; | |
| } | |
| #dsp-overlay.open { opacity: 1; pointer-events: auto; } | |
| #dsp-panel { | |
| position: fixed; z-index: 999998; top: 50%; left: 50%; | |
| width: min(480px, calc(100vw - 32px)); max-height: 85vh; | |
| transform: translate(-50%, -46%) scale(.96); | |
| background: var(--dsw-alias-bg-layer-1, #fff); | |
| color: var(--dsw-alias-label-primary, #1a1a1a); | |
| border: 1px solid var(--dsw-alias-border-l1, rgba(0,0,0,.06)); | |
| border-radius: 20px; | |
| box-shadow: 0 24px 64px rgba(0,0,0,.14); | |
| display: flex; flex-direction: column; overflow: hidden; | |
| opacity: 0; pointer-events: none; transition: .22s ease; | |
| font-family: inherit; | |
| } | |
| #dsp-panel.open { opacity: 1; pointer-events: auto; transform: translate(-50%, -50%) scale(1); } | |
| #dsp-panel .dsp-hd { | |
| padding: 18px 20px 14px; display: flex; align-items: center; justify-content: space-between; | |
| border-bottom: 1px solid var(--dsw-alias-border-l1, rgba(0,0,0,.06)); | |
| } | |
| #dsp-panel .dsp-hd h3 { margin: 0; font-size: 16px; font-weight: 600; } | |
| #dsp-panel .dsp-close { | |
| width: 30px; height: 30px; border: 0; border-radius: 50%; cursor: pointer; | |
| background: var(--dsw-alias-interactive-bg-hover, rgba(0,0,0,.05)); | |
| color: var(--dsw-alias-label-secondary, #666); font-size: 18px; line-height: 1; | |
| } | |
| #dsp-panel .dsp-bd { flex: 1; overflow: auto; padding: 16px 20px 20px; } | |
| #dsp-panel .dsp-bd::-webkit-scrollbar { width: 5px; } | |
| #dsp-panel .dsp-bd::-webkit-scrollbar-thumb { background: rgba(0,0,0,.12); border-radius: 8px; } | |
| .dsp-card { | |
| display: flex; align-items: center; justify-content: space-between; gap: 12px; | |
| padding: 14px 16px; margin-bottom: 18px; border-radius: 14px; | |
| background: var(--dsw-alias-bg-layer-2, #f7f7f8); | |
| border: 1px solid var(--dsw-alias-border-l1, rgba(0,0,0,.06)); | |
| } | |
| .dsp-card b { display: block; font-size: 14px; } | |
| .dsp-card-desc { display: block; margin: 4px 0 0; font-size: 12px; color: var(--dsw-alias-label-tertiary, #888); line-height: 1.4; } | |
| .dsp-switch { | |
| display: inline-block; position: relative; | |
| width: 42px; height: 24px; flex: 0 0 42px; | |
| box-sizing: border-box; vertical-align: middle; | |
| } | |
| .dsp-switch input { | |
| appearance: none; -webkit-appearance: none; | |
| position: absolute; inset: 0; margin: 0; padding: 0; border: 0; | |
| width: 100%; height: 100%; opacity: 0; cursor: pointer; z-index: 1; | |
| } | |
| .dsp-slider { | |
| display: block; position: absolute; left: 0; top: 0; | |
| width: 42px; height: 24px; margin: 0; padding: 0; border: 0; | |
| box-sizing: border-box; line-height: 0; font-size: 0; | |
| border-radius: 99px; pointer-events: none; | |
| background: var(--dsw-alias-border-l3, #ccc); transition: background .2s; | |
| } | |
| .dsp-slider:before { | |
| content: ""; display: block; position: absolute; | |
| width: 18px; height: 18px; left: 3px; top: 3px; margin: 0; padding: 0; border: 0; | |
| box-sizing: border-box; background: #fff; border-radius: 50%; | |
| box-shadow: 0 1px 4px rgba(0,0,0,.2); transition: transform .2s; | |
| } | |
| .dsp-switch input:checked + .dsp-slider { background: var(--dsw-alias-brand-primary, #4d6bfe); } | |
| .dsp-switch input:checked + .dsp-slider:before { transform: translateX(18px); } | |
| .dsp-list-hd { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; } | |
| .dsp-list-hd b { font-size: 14px; } | |
| .dsp-btn-new, .dsp-btn-pri, .dsp-btn-sec { | |
| border: 0; cursor: pointer; font-family: inherit; font-weight: 500; | |
| } | |
| .dsp-btn-new { | |
| background: var(--dsw-alias-brand-primary, #4d6bfe); color: #fff; | |
| border-radius: 99px; padding: 6px 14px; font-size: 13px; | |
| } | |
| .dsp-btn-pri { | |
| background: var(--dsw-alias-brand-primary, #4d6bfe); color: #fff; | |
| border-radius: 10px; padding: 8px 16px; font-size: 13px; | |
| } | |
| .dsp-btn-sec { | |
| background: transparent; color: var(--dsw-alias-label-secondary, #666); | |
| border-radius: 10px; padding: 8px 14px; font-size: 13px; | |
| } | |
| #dsp-prompt-list { display: flex; flex-direction: column; gap: 10px; } | |
| .dsp-empty { font-size: 13px; color: var(--dsw-alias-label-tertiary, #888); padding: 18px 8px; text-align: center; } | |
| .dsp-item { | |
| padding: 14px; border-radius: 14px; | |
| background: var(--dsw-alias-bg-base, #fff); | |
| border: 1px solid var(--dsw-alias-border-l2, rgba(0,0,0,.08)); | |
| } | |
| .dsp-item-hd { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 6px; } | |
| .dsp-item-title { font-size: 14px; font-weight: 600; } | |
| .dsp-item-actions { display: flex; gap: 8px; } | |
| .dsp-item-actions button { | |
| background: none; border: 0; cursor: pointer; font-size: 12px; font-weight: 500; padding: 2px 4px; | |
| color: var(--dsw-alias-brand-text, #4d6bfe); | |
| } | |
| .dsp-item-actions .del { color: #ef4444; } | |
| .dsp-item-preview { | |
| font-size: 13px; color: var(--dsw-alias-label-secondary, #666); | |
| display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.45; | |
| } | |
| .dsp-badge { | |
| display: inline-block; margin-left: 8px; font-size: 11px; font-weight: 600; | |
| padding: 1px 7px; border-radius: 99px; | |
| color: var(--dsw-alias-brand-text, #4d6bfe); | |
| background: var(--dsw-alias-button-ghost-active-fill, #f0f4ff); | |
| } | |
| #dsp-edit { display: none; margin-top: 14px; padding: 16px; border-radius: 14px; | |
| background: var(--dsw-alias-bg-layer-2, #f7f7f8); | |
| border: 1px solid var(--dsw-alias-border-l2, rgba(0,0,0,.08)); | |
| } | |
| .dsp-field { margin-bottom: 10px; } | |
| .dsp-input, .dsp-textarea { | |
| width: 100%; box-sizing: border-box; border-radius: 10px; | |
| border: 1px solid var(--dsw-alias-border-l2, rgba(0,0,0,.1)); | |
| background: var(--dsw-alias-bg-base, #fff); | |
| color: inherit; font: inherit; font-size: 13px; padding: 10px 12px; outline: none; | |
| } | |
| .dsp-textarea { min-height: 140px; resize: vertical; line-height: 1.55; } | |
| .dsp-edit-ft { display: flex; justify-content: flex-end; gap: 8px; margin-top: 8px; } | |
| .dsp-time-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 8px 0 12px; font-size: 13px; } | |
| #dsp-pill-wrap { | |
| display: inline-flex; align-items: center; height: 34px; margin-left: 8px; flex-shrink: 0; | |
| } | |
| .dsp-pill { | |
| display: flex; align-items: center; height: 100%; | |
| border: 1px solid var(--dsw-alias-border-l2, rgba(130,130,150,.25)); | |
| border-radius: 18px; padding-right: 4px; color: var(--dsw-alias-label-primary, #333); | |
| font-size: 13px; font-weight: 500; background: transparent; | |
| } | |
| .dsp-pill.active { | |
| color: var(--dsw-alias-brand-text, #4d6bfe); | |
| background: var(--dsw-alias-button-ghost-active-fill, #f0f4ff); | |
| border-color: var(--dsw-alias-button-ghost-active-border, rgba(77,107,254,.3)); | |
| } | |
| .dsp-pill-trigger { | |
| display: flex; align-items: center; height: 100%; padding: 0 8px 0 12px; cursor: pointer; user-select: none; | |
| } | |
| #dsp-pill-text { max-width: 96px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } | |
| .dsp-pill-div { width: 1px; height: 14px; background: var(--dsw-alias-border-l2, rgba(0,0,0,.12)); margin: 0 2px; } | |
| #dsp-gear { | |
| width: 24px; height: 24px; border: 0; border-radius: 50%; cursor: pointer; | |
| background: transparent; color: inherit; display: flex; align-items: center; justify-content: center; | |
| } | |
| #dsp-menu { | |
| position: fixed; z-index: 9999999; display: none; flex-direction: column; | |
| min-width: 180px; max-height: 280px; overflow: auto; padding: 6px; | |
| background: var(--dsw-alias-bg-layer-3, #fff); | |
| border: 1px solid var(--dsw-alias-border-l2, rgba(0,0,0,.08)); | |
| border-radius: 12px; box-shadow: 0 12px 32px rgba(0,0,0,.12); | |
| } | |
| #dsp-menu.open { display: flex; } | |
| .dsp-menu-item { | |
| padding: 10px 12px; border-radius: 8px; cursor: pointer; font-size: 13px; font-weight: 500; | |
| color: var(--dsw-alias-label-primary, #333); | |
| } | |
| .dsp-menu-item:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(0,0,0,.04)); } | |
| .dsp-menu-item.active { | |
| color: var(--dsw-alias-brand-text, #4d6bfe); | |
| background: var(--dsw-alias-button-ghost-active-fill, #f0f4ff); | |
| } | |
| #dsp-fallback { | |
| position: fixed; right: 18px; bottom: 88px; z-index: 999990; | |
| height: 36px; padding: 0 12px; border-radius: 18px; cursor: pointer; | |
| border: 1px solid var(--dsw-alias-border-l2, rgba(0,0,0,.1)); | |
| background: var(--dsw-alias-bg-base, #fff); | |
| color: var(--dsw-alias-label-primary, #333); | |
| box-shadow: 0 6px 20px rgba(0,0,0,.12); | |
| font-size: 13px; font-weight: 500; | |
| } | |
| `; | |
| let overlay, panel, menu, editIdInput, editTitle, editContent, editTime, expertCb, firstOnlyCb; | |
| function ensureStyle() { | |
| if (document.getElementById('dsp-style')) return; | |
| const style = document.createElement('style'); | |
| style.id = 'dsp-style'; | |
| style.textContent = CSS; | |
| (document.head || document.documentElement).appendChild(style); | |
| } | |
| function buildChrome() { | |
| if (document.getElementById('dsp-panel')) return; | |
| overlay = document.createElement('div'); | |
| overlay.id = 'dsp-overlay'; | |
| overlay.addEventListener('click', () => togglePanel(false)); | |
| panel = document.createElement('div'); | |
| panel.id = 'dsp-panel'; | |
| panel.innerHTML = ` | |
| <div class="dsp-hd"> | |
| <h3>提示词与偏好</h3> | |
| <button type="button" class="dsp-close" aria-label="关闭">×</button> | |
| </div> | |
| <div class="dsp-bd"> | |
| <div class="dsp-card"> | |
| <div> | |
| <b>仅首条注入</b> | |
| <p class="dsp-card-desc">只在新对话第一条写入系统提示词,后续轮次不再发送。换指令需新开对话。</p> | |
| </div> | |
| <label class="dsp-switch"> | |
| <input type="checkbox" id="dsp-first-only"> | |
| <span class="dsp-slider"></span> | |
| </label> | |
| </div> | |
| <div class="dsp-card"> | |
| <div> | |
| <b>专家模式自动激活</b> | |
| <p class="dsp-card-desc">新建对话时自动点亮「专家」模式。</p> | |
| </div> | |
| <label class="dsp-switch"> | |
| <input type="checkbox" id="dsp-auto-expert"> | |
| <span class="dsp-slider"></span> | |
| </label> | |
| </div> | |
| <div class="dsp-list-hd"> | |
| <b>指令库</b> | |
| <button type="button" class="dsp-btn-new" id="dsp-new">新建</button> | |
| </div> | |
| <div id="dsp-prompt-list"></div> | |
| <div id="dsp-edit"> | |
| <input type="hidden" id="dsp-edit-id"> | |
| <div class="dsp-field"> | |
| <input class="dsp-input" id="dsp-edit-title" placeholder="标题,例如:代码审查"> | |
| </div> | |
| <div class="dsp-field"> | |
| <textarea class="dsp-textarea" id="dsp-edit-content" placeholder="系统提示词内容,发送时会静默注入"></textarea> | |
| </div> | |
| <div class="dsp-time-row"> | |
| <span>动态时间注入</span> | |
| <label class="dsp-switch"> | |
| <input type="checkbox" id="dsp-edit-time"> | |
| <span class="dsp-slider"></span> | |
| </label> | |
| </div> | |
| <div class="dsp-edit-ft"> | |
| <button type="button" class="dsp-btn-sec" id="dsp-edit-cancel">取消</button> | |
| <button type="button" class="dsp-btn-pri" id="dsp-edit-save">保存</button> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| menu = document.createElement('div'); | |
| menu.id = 'dsp-menu'; | |
| document.body.appendChild(overlay); | |
| document.body.appendChild(panel); | |
| document.body.appendChild(menu); | |
| panel.querySelector('.dsp-close').addEventListener('click', () => togglePanel(false)); | |
| expertCb = panel.querySelector('#dsp-auto-expert'); | |
| expertCb.addEventListener('change', () => { | |
| State.autoExpert = expertCb.checked; | |
| State.save(); | |
| toast(State.autoExpert ? '已开启自动专家模式' : '已关闭自动专家模式'); | |
| }); | |
| firstOnlyCb = panel.querySelector('#dsp-first-only'); | |
| firstOnlyCb.addEventListener('change', () => { | |
| State.firstOnly = firstOnlyCb.checked; | |
| State.save(); | |
| toast(State.firstOnly ? '已开启仅首条注入' : '已关闭仅首条注入,每轮都会发送'); | |
| }); | |
| editIdInput = panel.querySelector('#dsp-edit-id'); | |
| editTitle = panel.querySelector('#dsp-edit-title'); | |
| editContent = panel.querySelector('#dsp-edit-content'); | |
| editTime = panel.querySelector('#dsp-edit-time'); | |
| panel.querySelector('#dsp-new').addEventListener('click', () => openEditor(null)); | |
| panel.querySelector('#dsp-edit-cancel').addEventListener('click', () => { | |
| panel.querySelector('#dsp-edit').style.display = 'none'; | |
| }); | |
| panel.querySelector('#dsp-edit-save').addEventListener('click', saveEditor); | |
| } | |
| function togglePanel(show) { | |
| buildChrome(); | |
| expertCb.checked = State.autoExpert; | |
| firstOnlyCb.checked = State.firstOnly; | |
| if (show) { | |
| renderList(); | |
| panel.classList.add('open'); | |
| overlay.classList.add('open'); | |
| menu.classList.remove('open'); | |
| } else { | |
| panel.classList.remove('open'); | |
| overlay.classList.remove('open'); | |
| } | |
| } | |
| function selectPrompt(id) { | |
| State.activeId = id; | |
| State.save(); | |
| renderAll(); | |
| menu.classList.remove('open'); | |
| toast(id ? '指令已生效' : '已关闭提示词注入'); | |
| } | |
| function renderList() { | |
| const listEl = panel.querySelector('#dsp-prompt-list'); | |
| listEl.innerHTML = ''; | |
| if (!State.prompts.length) { | |
| listEl.innerHTML = '<div class="dsp-empty">还没有指令,点右上角新建一条</div>'; | |
| return; | |
| } | |
| State.prompts.forEach((p) => { | |
| const row = document.createElement('div'); | |
| row.className = 'dsp-item'; | |
| row.innerHTML = ` | |
| <div class="dsp-item-hd"> | |
| <div class="dsp-item-title">${esc(p.title)}${p.includeTime ? '<span class="dsp-badge">动态时间</span>' : ''}</div> | |
| <div class="dsp-item-actions"> | |
| <button type="button" class="edit">编辑</button> | |
| <button type="button" class="del">删除</button> | |
| </div> | |
| </div> | |
| <div class="dsp-item-preview">${esc(p.content)}</div> | |
| `; | |
| row.querySelector('.edit').addEventListener('click', () => openEditor(p)); | |
| row.querySelector('.del').addEventListener('click', () => deletePrompt(p.id)); | |
| listEl.appendChild(row); | |
| }); | |
| } | |
| function renderMenu() { | |
| if (!menu) return; | |
| menu.innerHTML = ''; | |
| const none = document.createElement('div'); | |
| none.className = 'dsp-menu-item' + (!State.activeId ? ' active' : ''); | |
| none.textContent = '无 (纯净对话)'; | |
| none.addEventListener('click', () => selectPrompt('')); | |
| menu.appendChild(none); | |
| State.prompts.forEach((p) => { | |
| const item = document.createElement('div'); | |
| item.className = 'dsp-menu-item' + (p.id === State.activeId ? ' active' : ''); | |
| item.textContent = p.title; | |
| item.addEventListener('click', () => selectPrompt(p.id)); | |
| menu.appendChild(item); | |
| }); | |
| const text = document.getElementById('dsp-pill-text'); | |
| const pill = document.querySelector('.dsp-pill'); | |
| const active = State.active(); | |
| if (text) text.textContent = active ? active.title : '无 (纯净对话)'; | |
| if (pill) pill.classList.toggle('active', !!active); | |
| const fallback = document.getElementById('dsp-fallback'); | |
| if (fallback) fallback.textContent = active ? `提示词 · ${active.title}` : '提示词'; | |
| } | |
| function renderAll() { | |
| if (panel) renderList(); | |
| renderMenu(); | |
| } | |
| function openEditor(item) { | |
| const box = panel.querySelector('#dsp-edit'); | |
| box.style.display = 'block'; | |
| if (item) { | |
| editIdInput.value = item.id; | |
| editTitle.value = item.title; | |
| editContent.value = item.content; | |
| editTime.checked = !!item.includeTime; | |
| } else { | |
| editIdInput.value = ''; | |
| editTitle.value = ''; | |
| editContent.value = ''; | |
| editTime.checked = false; | |
| } | |
| setTimeout(() => { | |
| box.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); | |
| editTitle.focus(); | |
| }, 30); | |
| } | |
| function saveEditor() { | |
| const title = editTitle.value.trim(); | |
| const content = editContent.value.trim(); | |
| if (!title || !content) { | |
| toast('标题和内容都不能为空'); | |
| return; | |
| } | |
| const id = editIdInput.value; | |
| if (id) { | |
| const item = State.prompts.find((p) => p.id === id); | |
| if (item) { | |
| item.title = title; | |
| item.content = content; | |
| item.includeTime = editTime.checked; | |
| } | |
| } else { | |
| const newId = 'pr_' + Date.now(); | |
| State.prompts.push({ | |
| id: newId, | |
| title, | |
| content, | |
| includeTime: editTime.checked, | |
| }); | |
| if (!State.activeId) State.activeId = newId; | |
| } | |
| State.save(); | |
| panel.querySelector('#dsp-edit').style.display = 'none'; | |
| renderAll(); | |
| toast('已保存'); | |
| } | |
| function deletePrompt(id) { | |
| if (!confirm('确定删除这条指令?')) return; | |
| State.prompts = State.prompts.filter((p) => p.id !== id); | |
| if (State.activeId === id) State.activeId = ''; | |
| State.save(); | |
| renderAll(); | |
| toast('已删除'); | |
| } | |
| function placeMenu(anchor) { | |
| const rect = anchor.getBoundingClientRect(); | |
| const spaceBelow = window.innerHeight - rect.bottom; | |
| menu.style.left = Math.max(8, rect.left) + 'px'; | |
| if (spaceBelow < 240) { | |
| menu.style.top = 'auto'; | |
| menu.style.bottom = window.innerHeight - rect.top + 8 + 'px'; | |
| } else { | |
| menu.style.top = rect.bottom + 8 + 'px'; | |
| menu.style.bottom = 'auto'; | |
| } | |
| menu.classList.add('open'); | |
| } | |
| let pillWrap = null; | |
| function isShown(el) { | |
| if (!el || !el.isConnected) return false; | |
| const rect = el.getBoundingClientRect(); | |
| if (rect.width < 2 || rect.height < 2) return false; | |
| if (rect.bottom < 0 || rect.top > window.innerHeight) return false; | |
| const style = getComputedStyle(el); | |
| if (style.display === 'none' || style.visibility === 'hidden') return false; | |
| return true; | |
| } | |
| function createPill() { | |
| if (pillWrap) return pillWrap; | |
| const wrap = document.createElement('div'); | |
| wrap.id = 'dsp-pill-wrap'; | |
| wrap.innerHTML = ` | |
| <div class="dsp-pill" id="dsp-pill"> | |
| <div class="dsp-pill-trigger" id="dsp-pill-trigger"> | |
| <span id="dsp-pill-text">无 (纯净对话)</span> | |
| </div> | |
| <div class="dsp-pill-div"></div> | |
| <button type="button" id="dsp-gear" aria-label="管理提示词"> | |
| <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| <circle cx="12" cy="12" r="3"/> | |
| <path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9c.3.7 1 1.1 1.5 1.1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/> | |
| </svg> | |
| </button> | |
| </div> | |
| `; | |
| wrap.querySelector('#dsp-pill-trigger').addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| if (menu.classList.contains('open')) { | |
| menu.classList.remove('open'); | |
| return; | |
| } | |
| renderMenu(); | |
| placeMenu(e.currentTarget); | |
| }); | |
| wrap.querySelector('#dsp-gear').addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| togglePanel(true); | |
| }); | |
| pillWrap = wrap; | |
| return wrap; | |
| } | |
| function lastToolbarChild(container) { | |
| return Array.from(container.children).reverse().find((c) => c !== pillWrap && c.id !== 'dsp-pill-wrap') || null; | |
| } | |
| function placePill(container, targetNode) { | |
| const wrap = createPill(); | |
| if (!container) return; | |
| if (targetNode === wrap) targetNode = wrap.previousSibling; | |
| if (!targetNode || !container.contains(targetNode)) targetNode = lastToolbarChild(container); | |
| if (wrap.parentNode === container && wrap.previousSibling === targetNode) return; | |
| if (targetNode && targetNode.nextSibling && targetNode.nextSibling !== wrap) { | |
| container.insertBefore(wrap, targetNode.nextSibling); | |
| } else { | |
| container.appendChild(wrap); | |
| } | |
| renderMenu(); | |
| } | |
| function injectFallback() { | |
| if (document.getElementById('dsp-fallback')) return; | |
| const btn = document.createElement('button'); | |
| btn.type = 'button'; | |
| btn.id = 'dsp-fallback'; | |
| btn.textContent = '提示词'; | |
| btn.addEventListener('click', () => togglePanel(true)); | |
| document.body.appendChild(btn); | |
| renderMenu(); | |
| } | |
| function toolbarFromButton(btn) { | |
| if (!btn || !btn.parentElement) return null; | |
| let target = btn; | |
| let container = btn.parentElement; | |
| try { | |
| if (getComputedStyle(container).display !== 'flex' && container.parentElement | |
| && getComputedStyle(container.parentElement).display === 'flex') { | |
| target = container; | |
| container = container.parentElement; | |
| } | |
| } catch { | |
| /* ignore */ | |
| } | |
| return { container, target }; | |
| } | |
| function findVisibleComposer() { | |
| const fields = document.querySelectorAll('textarea, [contenteditable="true"]'); | |
| for (const field of fields) { | |
| if (field.closest('#dsp-panel, #dsp-edit, #dsp-menu')) continue; | |
| if (isShown(field)) return field; | |
| } | |
| return null; | |
| } | |
| function findKeywordButton(scope) { | |
| const keywords = ['联网搜索', '深度思考', '智能搜索', 'DeepThink', 'Web Search']; | |
| const nodes = scope.querySelectorAll( | |
| 'div[role="switch"], div[role="button"], div[role="checkbox"], button, [tabindex]', | |
| ); | |
| let target = null; | |
| for (const btn of nodes) { | |
| if (btn.closest('#dsp-panel, #dsp-menu, #dsp-pill-wrap, #dsp-fallback')) continue; | |
| if (!isShown(btn)) continue; | |
| const txt = (btn.textContent || '').replace(/\s+/g, ' ').trim(); | |
| if (!txt || txt.length > 20) continue; | |
| if (keywords.some((k) => txt.includes(k))) target = btn; | |
| } | |
| return target; | |
| } | |
| function findToolbarNear(composer) { | |
| let scope = composer.parentElement; | |
| for (let i = 0; i < 10 && scope && scope !== document.body; i++, scope = scope.parentElement) { | |
| const keywordBtn = findKeywordButton(scope); | |
| if (keywordBtn) return toolbarFromButton(keywordBtn); | |
| const rows = Array.from(scope.children).filter((child) => { | |
| if (child === composer || child.contains(composer) || child.id === 'dsp-pill-wrap') return false; | |
| if (!child.querySelector('button, [role="button"], [role="switch"]')) return false; | |
| try { | |
| return getComputedStyle(child).display.includes('flex'); | |
| } catch { | |
| return false; | |
| } | |
| }); | |
| if (rows.length) { | |
| const row = rows[rows.length - 1]; | |
| return { container: row, target: lastToolbarChild(row) }; | |
| } | |
| } | |
| return null; | |
| } | |
| function findToolbar() { | |
| const composer = findVisibleComposer(); | |
| if (!composer) return null; | |
| return findToolbarNear(composer); | |
| } | |
| function ensurePillMounted() { | |
| const found = findToolbar(); | |
| if (found) { | |
| placePill(found.container, found.target); | |
| const fallback = document.getElementById('dsp-fallback'); | |
| if (fallback) fallback.remove(); | |
| return; | |
| } | |
| if (!pillWrap || !isShown(pillWrap)) injectFallback(); | |
| } | |
| let routeTimers = []; | |
| function mountAfterRoute() { | |
| routeTimers.forEach(clearTimeout); | |
| routeTimers = []; | |
| ensurePillMounted(); | |
| [50, 150, 400, 800, 1600].forEach((ms) => { | |
| routeTimers.push(setTimeout(ensurePillMounted, ms)); | |
| }); | |
| } | |
| function hookHistory() { | |
| const fire = () => mountAfterRoute(); | |
| const wrap = (type) => { | |
| const orig = history[type]; | |
| if (typeof orig !== 'function') return; | |
| history[type] = function (...args) { | |
| const ret = orig.apply(this, args); | |
| fire(); | |
| return ret; | |
| }; | |
| }; | |
| wrap('pushState'); | |
| wrap('replaceState'); | |
| window.addEventListener('popstate', fire); | |
| } | |
| // ----- 专家模式 ----- | |
| let manualOverride = false; | |
| let lastHref = location.href; | |
| function tryExpertMode() { | |
| if (!State.autoExpert) return; | |
| if (lastHref !== location.href) { | |
| lastHref = location.href; | |
| manualOverride = false; | |
| } | |
| if (manualOverride) return; | |
| const expert = document.querySelector('div[data-model-type="expert"][role="radio"]'); | |
| const quick = document.querySelector('div[data-model-type="default"][role="radio"]'); | |
| if (expert && quick && quick.getAttribute('aria-checked') === 'true') { | |
| expert.click(); | |
| } | |
| } | |
| function bootUI() { | |
| ensureStyle(); | |
| buildChrome(); | |
| renderAll(); | |
| let mountScheduled = false; | |
| const observer = new MutationObserver(() => { | |
| if (mountScheduled) return; | |
| mountScheduled = true; | |
| requestAnimationFrame(() => { | |
| mountScheduled = false; | |
| ensurePillMounted(); | |
| }); | |
| }); | |
| observer.observe(document.body, { childList: true, subtree: true }); | |
| hookHistory(); | |
| mountAfterRoute(); | |
| setInterval(() => { | |
| if (lastHref !== location.href) { | |
| lastHref = location.href; | |
| manualOverride = false; | |
| mountAfterRoute(); | |
| } | |
| ensurePillMounted(); | |
| tryExpertMode(); | |
| }, 800); | |
| document.addEventListener('click', (e) => { | |
| if (menu && menu.classList.contains('open')) { | |
| const trigger = document.getElementById('dsp-pill-trigger'); | |
| if ((!trigger || !trigger.contains(e.target)) && !menu.contains(e.target)) { | |
| menu.classList.remove('open'); | |
| } | |
| } | |
| if (e.target.closest && e.target.closest('div[data-model-type]')) { | |
| manualOverride = true; | |
| } | |
| const btn = e.target.closest && e.target.closest('div[role="button"], button'); | |
| if (btn) { | |
| const t = (btn.textContent || '').trim(); | |
| if (t.includes('新对话') || t === 'New' || t.includes('New chat')) { | |
| manualOverride = false; | |
| } | |
| } | |
| }, true); | |
| window.addEventListener('scroll', () => menu && menu.classList.remove('open'), true); | |
| window.addEventListener('resize', () => menu && menu.classList.remove('open')); | |
| } | |
| function whenBody(fn) { | |
| if (document.body) fn(); | |
| else document.addEventListener('DOMContentLoaded', fn, { once: true }); | |
| } | |
| whenBody(bootUI); | |
| console.log('[DS Prompt] loaded'); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment