Skip to content

Instantly share code, notes, and snippets.

@linyanm
Last active August 22, 2026 12:17
Show Gist options
  • Select an option

  • Save linyanm/e7d2fcd0208957cf4a3f1a6ef90d4ffb to your computer and use it in GitHub Desktop.

Select an option

Save linyanm/e7d2fcd0208957cf4a3f1a6ef90d4ffb to your computer and use it in GitHub Desktop.
DeepSeek 提示词注入:在 chat.deepseek.com 静默注入系统提示词(Safari Userscripts / Tampermonkey)
// ==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