Greasy Fork is available in English.
解决填充消失,支持分类折叠、半透明自适应预览、原地编辑、追加模式。
当前为
// ==UserScript==
// @name AI 提示词大师 Pro 9.0 (稳定填充+分类折叠+预览优化)
// @namespace http://tampermonkey.net/
// @version 9.0
// @license AGPL-3.0
// @description 解决填充消失,支持分类折叠、半透明自适应预览、原地编辑、追加模式。
// @author WaterHuo
// @match https://gemini.google.com/*
// @match https://chatgpt.com/*
// @match https://claude.ai/*
// @match https://chat.deepseek.com/*
// @match https://www.doubao.com/*
// @match https://www.kimi.ai/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// ==/UserScript==
(function() {
'use strict';
// ======= 1. 深度样式表 =======
GM_addStyle(`
#pm-root { font-family: -apple-system, system-ui, sans-serif; }
.pm-panel { position: fixed; top: 80px; right: 20px; width: 220px; background: #fff; border-radius: 12px;
box-shadow: 0 8px 32px rgba(0,0,0,0.1); z-index: 2147483640; border: 1px solid #eee; display: flex; flex-direction: column; }
.pm-header { padding: 12px; background: #fcfcfc; border-bottom: 1px solid #f0f0f0; display: flex; justify-content: space-between; align-items: center; border-radius: 12px 12px 0 0; }
.pm-title { font-size: 13px; font-weight: 700; color: #1a73e8; display: flex; align-items: center; gap: 5px; }
.pm-body { padding: 8px; max-height: 75vh; overflow-y: auto; scrollbar-width: thin; }
/* 分类与折叠 */
.pm-cat-wrap { margin-bottom: 8px; border-radius: 8px; overflow: hidden; }
.pm-cat-header { display: flex; align-items: center; padding: 6px 4px; background: #f8f9fa; cursor: pointer; group; position: relative; }
.pm-cat-fold-icon { font-size: 10px; margin-right: 6px; transition: transform 0.2s; color: #70757a; }
.pm-cat-name { font-size: 11px; color: #5f6368; font-weight: 700; flex: 1; text-transform: uppercase; letter-spacing: 0.5px; }
.pm-cat-tools { display: none; gap: 6px; margin-right: 4px; }
.pm-cat-header:hover .pm-cat-tools { display: flex; }
/* 模板列表 */
.pm-tpl-list { padding: 4px 0; }
.pm-tpl-list.folded { display: none; }
.pm-item-wrap { position: relative; margin-bottom: 2px; }
.pm-btn { width: 100%; border: none; background: transparent; padding: 8px 10px; text-align: left; font-size: 12px;
border-radius: 6px; cursor: pointer; color: #3c4043; transition: 0.2s; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pm-btn:hover { background: #f1f3f4; color: #1a73e8; }
/* 预览浮窗 (需求2: 半透明、自适应) */
#pm-preview-float {
position: fixed; display: none; width: auto; max-width: 200px; background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(8px); border: 1px solid rgba(0,0,0,0.08); box-shadow: 0 4px 16px rgba(0,0,0,0.1);
border-radius: 8px; padding: 10px; font-size: 11px; line-height: 1.4; color: #444; z-index: 2147483647;
pointer-events: auto; word-break: break-all; transition: opacity 0.2s;
}
/* 原地编辑器 (需求3) */
.pm-inline-editor { background: #fff; border: 1px solid #1a73e8; border-radius: 8px; padding: 8px; margin: 4px 0; box-shadow: 0 4px 12px rgba(26,115,232,0.15); }
.pm-inline-editor textarea { width: 100%; height: 100px; border: 1px solid #dadce0; border-radius: 4px; padding: 6px; font-size: 12px; box-sizing: border-box; resize: vertical; margin: 5px 0; }
.pm-inline-editor input, .pm-inline-editor select { width: 100%; border: 1px solid #dadce0; border-radius: 4px; padding: 4px 6px; font-size: 12px; box-sizing: border-box; margin-bottom: 4px; }
.pm-ed-btns { display: flex; justify-content: flex-end; gap: 6px; }
.pm-ebtn { padding: 3px 8px; font-size: 11px; border-radius: 4px; cursor: pointer; border: none; }
.pm-save { background: #1a73e8; color: #fff; }
.pm-cancel { background: #f1f3f4; color: #5f6368; }
.pm-toast { position: fixed; bottom: 30px; left: 50%; transform: translateX(-50%); background: #323232; color: #fff;
padding: 6px 16px; border-radius: 16px; font-size: 12px; z-index: 2147483647; display: none; }
`);
// ======= 2. 数据与持久化 =======
const DATA_KEY = 'pm_data_v9';
const FOLD_KEY = 'pm_folded_cats';
let promptData = GM_getValue(DATA_KEY, {
"写作类": [{ name: "📝 深度润色", content: "请对以下文本进行优化:先梳理逻辑脉络(若原文条理混乱,可按 “总分 / 因果 / 递进” 等清晰结构重组),再优化语言表达 —— 做到简洁精准、流畅自然,剔除冗余重复内容,完整保留原文核心信息与核心意图,不添加额外修饰,仅提升文本的逻辑性与可读性。" }],
"代码类": [{ name: "💻 逻辑审查", content: "请检查这段代码是否存在潜在的逻辑错误..." }]
});
let foldedCats = GM_getValue(FOLD_KEY, []); // 存储折叠状态的分类名
let isEditMode = false;
let previewLock = false;
let appendMode = true; // 默认追加模式
// ======= 3. 稳定填充核心 (解决填充消失问题) =======
async function stableInject(text) {
const inputField = document.querySelector('div[role="textbox"], #prompt-textarea, textarea[placeholder*="输入"], #chat-input, [contenteditable="true"], textarea');
if (!inputField) return toast("未找到输入框");
inputField.focus();
const isRich = inputField.isContentEditable;
const oldVal = isRich ? inputField.innerText : inputField.value;
const newVal = (appendMode && oldVal.trim()) ? (oldVal + "\n" + text) : text;
try {
if (isRich) {
// 针对 Gemini/Claude/DeepSeek 富文本模式
const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents(inputField);
selection.removeAllRanges();
selection.addRange(range);
document.execCommand('delete', false);
document.execCommand('insertText', false, newVal);
} else {
// 针对 ChatGPT/豆包/Kimi 原生模式
const setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
setter.call(inputField, newVal);
inputField.dispatchEvent(new Event('input', { bubbles: true }));
}
toast("填充成功");
// 保持焦点并滚动到底部
inputField.focus();
setTimeout(() => { inputField.scrollTop = inputField.scrollHeight; }, 10);
} catch (e) {
toast("尝试填充失败");
}
}
// ======= 4. UI 渲染引擎 =======
function toast(msg) {
const t = document.getElementById('pm-toast') || (()=>{
const d = document.createElement('div'); d.id='pm-toast'; d.className='pm-toast'; document.body.appendChild(d); return d;
})();
t.innerText = msg; t.style.display = 'block';
setTimeout(() => t.style.display = 'none', 2000);
}
function renderUI() {
let root = document.getElementById('pm-root');
if (!root) {
root = document.createElement('div'); root.id = 'pm-root'; document.body.appendChild(root);
const pf = document.createElement('div'); pf.id = 'pm-preview-float'; document.body.appendChild(pf);
}
const previewFloat = document.getElementById('pm-preview-float');
root.innerHTML = `
<div class="pm-panel">
<div class="pm-header">
<span class="pm-title">🪄 提示词大师 <small style="font-weight:normal; font-size:10px; margin-left:5px; color:#999;">${appendMode?'追加':'替换'}</small></span>
<div style="display:flex; gap:8px;">
<span id="pm-toggle-mode" style="cursor:pointer; font-size:12px;" title="切换 追加/替换 填充">${appendMode?'➕':'🔄'}</span>
<span id="pm-config-btn" style="cursor:pointer; font-size:13px;">${isEditMode ? '✅' : '⚙️'}</span>
</div>
</div>
<div class="pm-body" id="pm-list-container"></div>
${isEditMode ? `<div style="padding:8px; border-top:1px solid #eee"><button id="pm-new-cat" style="width:100%; padding:6px; border:none; background:#e8f0fe; color:#1a73e8; border-radius:6px; font-size:11px; cursor:pointer;">+ 新建分类</button></div>` : ''}
</div>
`;
const container = document.getElementById('pm-list-container');
Object.keys(promptData).forEach(cat => {
const isFolded = foldedCats.includes(cat);
const catWrap = document.createElement('div');
catWrap.className = 'pm-cat-wrap';
// 分类头部
const header = document.createElement('div');
header.className = 'pm-cat-header';
header.innerHTML = `
<span class="pm-cat-fold-icon" style="transform: ${isFolded ? 'rotate(-90deg)' : 'rotate(0deg)'}">▼</span>
<span class="pm-cat-name">${cat}</span>
${isEditMode ? `<div class="pm-cat-tools">
<span class="pm-ed-cat" data-cat="${cat}">✏️</span>
<span class="pm-del-cat" data-cat="${cat}">×</span>
</div>` : ''}
`;
// 折叠点击事件
header.onclick = (e) => {
if (e.target.closest('.pm-cat-tools')) return;
if (isFolded) foldedCats = foldedCats.filter(c => c !== cat);
else foldedCats.push(cat);
GM_setValue(FOLD_KEY, foldedCats);
renderUI();
};
if (isEditMode) {
header.querySelector('.pm-ed-cat').onclick = () => editCatName(catWrap, cat);
header.querySelector('.pm-del-cat').onclick = () => deleteCat(cat);
}
catWrap.appendChild(header);
// 模板列表
const tplList = document.createElement('div');
tplList.className = `pm-tpl-list ${isFolded ? 'folded' : ''}`;
promptData[cat].forEach((item, idx) => {
const itemWrap = document.createElement('div');
itemWrap.className = 'pm-item-wrap';
const btn = document.createElement('button');
btn.className = 'pm-btn';
btn.innerText = item.name;
// 预览触发 (需求2)
btn.onmouseenter = (e) => {
if (isEditMode || previewLock) return;
const rect = btn.getBoundingClientRect();
const coreText = item.content.length > 70 ? item.content.substring(0, 70) + "..." : item.content;
previewFloat.innerHTML = `<div style="font-weight:bold;margin-bottom:4px;color:#1a73e8;">预览</div>${coreText.replace(/\n/g, '<br>')}<div style="font-size:9px;color:#aaa;margin-top:4px;">(点击填充,右侧支持选中复制)</div>`;
previewFloat.style.display = 'block';
// 自适应位置计算
let topPos = rect.top;
if (topPos + 150 > window.innerHeight) topPos = window.innerHeight - 160;
previewFloat.style.top = `${topPos}px`;
previewFloat.style.right = `${window.innerWidth - rect.left + 10}px`;
};
btn.onmouseleave = () => { if(!previewLock) previewFloat.style.display = 'none'; };
btn.onclick = () => {
if (isEditMode) {
editTpl(itemWrap, cat, idx);
} else {
stableInject(item.content);
previewLock = true;
setTimeout(() => { previewLock = false; previewFloat.style.display='none'; }, 1500);
}
};
itemWrap.appendChild(btn);
tplList.appendChild(itemWrap);
});
if (isEditMode) {
const addTplBtn = document.createElement('button');
addTplBtn.className = 'pm-btn'; addTplBtn.style.border = "1px dashed #ccc"; addTplBtn.style.color="#999";
addTplBtn.innerText = "+ 新模板";
addTplBtn.onclick = () => editTpl(tplList, cat, -1, addTplBtn);
tplList.appendChild(addTplBtn);
}
catWrap.appendChild(tplList);
container.appendChild(catWrap);
});
document.getElementById('pm-config-btn').onclick = () => { isEditMode = !isEditMode; renderUI(); };
document.getElementById('pm-toggle-mode').onclick = () => { appendMode = !appendMode; renderUI(); };
if (isEditMode) document.getElementById('pm-new-cat').onclick = () => {
const n = prompt("输入新分类名称:"); if(n) { promptData[n]=[]; saveData(); }
};
}
// ======= 5. 交互:原地编辑分类/模板 =======
function editCatName(wrap, oldName) {
const header = wrap.querySelector('.pm-cat-header');
header.style.display = 'none';
const editor = document.createElement('div');
editor.className = 'pm-inline-editor';
editor.innerHTML = `<input type="text" id="new-cat-inp" value="${oldName}"><div class="pm-ed-btns"><button class="pm-ebtn pm-cancel">取消</button><button class="pm-ebtn pm-save">保存</button></div>`;
wrap.prepend(editor);
editor.querySelector('.pm-cancel').onclick = () => renderUI();
editor.querySelector('.pm-save').onclick = () => {
const n = editor.querySelector('#new-cat-inp').value.trim();
if(n && n !== oldName) { promptData[n] = promptData[oldName]; delete promptData[oldName]; saveData(); }
else renderUI();
};
}
function deleteCat(catName) {
if(promptData[catName].length > 0) {
if(!confirm(`该分类下有模板,删除后将迁移至“通用”分类,确定吗?`)) return;
if(!promptData["通用"]) promptData["通用"] = [];
promptData["通用"] = promptData["通用"].concat(promptData[catName]);
} else if(!confirm(`确定删除空分类 [${catName}]?`)) return;
delete promptData[catName];
saveData();
}
function editTpl(container, cat, idx, addBtn = null) {
const isNew = idx === -1;
const item = isNew ? { name: "", content: "" } : promptData[cat][idx];
const editor = document.createElement('div');
editor.className = 'pm-inline-editor';
let cats = Object.keys(promptData).map(c => `<option value="${c}" ${c === cat ? 'selected' : ''}>${c}</option>`).join('');
editor.innerHTML = `
<input type="text" id="ed-name" placeholder="名称" value="${item.name}">
<select id="ed-cat">${cats}</select>
<div style="position:relative"><textarea id="ed-cont" placeholder="提示词内容...">${item.content}</textarea><span id="pm-clear-ed" style="position:absolute;right:5px;top:5px;cursor:pointer;color:#ccc;">×</span></div>
<div class="pm-ed-btns"><button class="pm-ebtn pm-cancel">取消</button><button class="pm-ebtn pm-save">保存</button></div>
`;
if(!isNew) container.querySelector('.pm-btn').style.display = 'none';
if(addBtn) addBtn.style.display = 'none';
container.appendChild(editor);
editor.querySelector('#pm-clear-ed').onclick = () => editor.querySelector('#ed-cont').value = "";
editor.querySelector('.pm-cancel').onclick = () => renderUI();
editor.querySelector('.pm-save').onclick = () => {
const n = editor.querySelector('#ed-name').value.trim();
const c = editor.querySelector('#ed-cont').value;
const tCat = editor.querySelector('#ed-cat').value;
if(!n || !c) return toast("请完整填写");
if(!isNew) promptData[cat].splice(idx, 1);
promptData[tCat].push({ name: n, content: c });
saveData(); toast("保存成功");
};
}
function saveData() { GM_setValue(DATA_KEY, promptData); renderUI(); }
// ======= 6. 自动检测与启动 =======
const init = () => { if (!document.getElementById('pm-root')) renderUI(); };
setInterval(init, 2000); // 应对单页应用重绘
setTimeout(init, 1000);
})();