// ==UserScript==
// @name PromptHelper
// @namespace http://tampermonkey.net/
// @version 1.3
// @description PromptHelper:通用于 ChatGPT, Gemini, Claude, Kimi, DeepSeek, 通义、元宝、Google AI Studio、Grok 的侧边模板助手(仅保留默认“通用交互式提问模板”,默认选中;更稳事件触发)。
// @author Sauterne
// @match http://chat.openai.com/*
// @match https://chat.openai.com/*
// @match http://chatgpt.com/*
// @match https://chatgpt.com/*
// @match http://gemini.google.com/*
// @match https://gemini.google.com/*
// @match http://claude.ai/*
// @match https://claude.ai/*
// @match http://demo.fuclaude.com/*
// @match https://demo.fuclaude.com/*
// @match http://www.kimi.com/*
// @match https://www.kimi.com/*
// @match http://kimi.com/*
// @match https://kimi.com/*
// @match http://kimi.moonshot.cn/*
// @match https://kimi.moonshot.cn/*
// @match http://chat.deepseek.com/*
// @match https://chat.deepseek.com/*
// @match http://www.tongyi.com/*
// @match https://www.tongyi.com/*
// @match http://yuanbao.tencent.com/chat/*
// @match https://yuanbao.tencent.com/chat/*
// @match http://aistudio.google.com/*
// @match https://aistudio.google.com/*
// @match http://grok.com/*
// @match https://grok.com/*
// @match http://www.grok.com/*
// @match https://www.grok.com/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @run-at document-start
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// === 可配置项(保持旧行为;仅提供开关不改变现有功能) ===
const SETTINGS = {
forceOpenShadow: true
};
// --- [核心修复] Closed Shadow DOM ---
const originalAttachShadow = Element.prototype.attachShadow;
Element.prototype.attachShadow = function(options) {
if (SETTINGS.forceOpenShadow && options && options.mode === 'closed') {
options.mode = 'open';
}
return originalAttachShadow.call(this, options);
};
// === 事件兼容工具 ===
function tryCreateInputEvent(type, opts = {}) {
try { return new InputEvent(type, opts); } catch (_) { return new Event(type, { bubbles: !!opts.bubbles, cancelable: !!opts.cancelable }); }
}
function tryCreateKeyboardEvent(type, opts = {}) {
try { return new KeyboardEvent(type, opts); } catch (_) { return new Event(type, { bubbles: !!opts.bubbles, cancelable: !!opts.cancelable }); }
}
// === ProseMirror 粘贴法(Claude) ===
function escapeHtml(s) { return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
function textToHtmlPreserveBlankLines(text) {
const lines = text.split('\n'); const paras = []; let buf = [];
const flush = () => { if (!buf.length) return; const inner = buf.map(ln => escapeHtml(ln)).join('<br>'); paras.push(`<p>${inner}</p>`); buf = []; };
for (let i=0;i<lines.length;i++){ const ln=lines[i]; if (ln===''){flush(); paras.push('<p> </p>');} else buf.push(ln); }
flush(); if (paras.length===0) paras.push('<p> </p>'); return paras.join('');
}
function pasteIntoProseMirror(editableEl, plainText) {
const html = textToHtmlPreserveBlankLines(plainText); editableEl.focus(); let ok=false;
try{ const dt=new DataTransfer(); dt.setData('text/plain',plainText); dt.setData('text/html',html);
const evt=new ClipboardEvent('paste',{bubbles:true,cancelable:true,clipboardData:dt}); ok=editableEl.dispatchEvent(evt);}catch(_){ok=false;}
if(!ok){ try{document.execCommand('insertText',false,plainText);}catch(_){ editableEl.textContent=''; editableEl.dispatchEvent(new Event('input',{bubbles:true})); editableEl.textContent=plainText; editableEl.dispatchEvent(new Event('input',{bubbles:true})); } }
}
// === 原生 setter,确保受控组件感知 ===
const nativeTextareaValueSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set;
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set;
function setNativeValue(el, value) {
const setter = el.tagName === 'TEXTAREA' ? nativeTextareaValueSetter :
el.tagName === 'INPUT' ? nativeInputValueSetter : null;
if (setter) setter.call(el, value); else el.value = value;
}
window.addEventListener('DOMContentLoaded', () => {
// --- 站点配置 ---
const siteConfigs = {
'openai.com': { name: 'ChatGPT', inputSelector: '#prompt-textarea' },
'chatgpt.com': { name: 'ChatGPT', inputSelector: '#prompt-textarea' },
'gemini.google.com': { name: 'Gemini', shadowRootSelector: 'chat-app', inputSelector: 'div.initial-input-area textarea, rich-textarea .ql-editor, [contenteditable="true"][role="textbox"]' },
'claude.ai': { name: 'Claude', inputSelector: '.ProseMirror[contenteditable="true"]' },
'fuclaude.com': { name: 'Claude', inputSelector: '.ProseMirror[contenteditable="true"]' },
'kimi.com': { name: 'Kimi', inputSelector: 'div.chat-input-editor[data-lexical-editor="true"], div[contenteditable="true"], textarea, [role="textbox"], [data-lexical-editor]' },
'kimi.moonshot.cn': { name: 'Kimi', inputSelector: 'div.chat-input-editor[data-lexical-editor="true"], div[contenteditable="true"], textarea, [role="textbox"], [data-lexical-editor]' },
'deepseek.com': { name: 'DeepSeek', inputSelector: 'textarea[placeholder*="随便聊点什么"], textarea[placeholder*="Ask me anything"], div[contenteditable="true"], #chat-input, [role="textbox"]' },
'tongyi.com': { name: '通义', inputSelector: 'textarea[placeholder*="有问题,随时问通义"], textarea[placeholder*="问题"], textarea, div[contenteditable="true"], [role="textbox"]' },
'yuanbao.tencent.com': { name: '腾讯元宝', inputSelector: 'textarea[placeholder*="输入问题"], textarea[placeholder*="问题"], textarea, div[contenteditable="true"], [role="textbox"]' },
'aistudio.google.com': { name: 'Google AI Studio', shadowRootSelector: 'app-root', inputSelector: '[contenteditable="true"], textarea, [role="textbox"], [aria-label*="prompt"], [aria-label*="Prompt"], [placeholder*="prompt"], [placeholder*="Prompt"], .prompt-input, #prompt-input, input[type="text"]' },
'grok.com': { name: 'Grok', inputSelector: 'form .query-bar textarea[aria-label], textarea[aria-label*="Grok"], textarea[aria-label*="向 Grok"], textarea' }
};
// --- 仅保留一个默认模板,且不可删除 ---
const DEFAULT_TEMPLATE_ID = 'default_interactive';
const defaultPrompts = {
[DEFAULT_TEMPLATE_ID]: {
name: "通用交互式提问模板",
template: `SYSTEM ROLE — "Audit-Grade Researcher"
You are a meticulous research analyst. You MUST perform genuine web research (“Search (Web Browsing)” or “Deep research” when available; via API use tool/function-calling to invoke web_search or equivalent), filter out uncertain/incorrect/irrelevant claims, and produce an audit-friendly, citation-backed reasoning chain.
Do NOT reveal chain-of-thought or internal notes. Output language: Chinese only.
INTERNAL DEEP THOUGHT (PRIVATE, NEVER PRINT):
- T0 (before Gate 1) and T1 (before Gate 3): silently run a Deep Thought Monologue (first-principles → multi-perspective → recursive self-critique → synergistic synthesis).
- 若仍有不确定或冲突,优先进入澄清而非猜测。
MODEL-SPECIFIC (TOOLS & BEHAVIOR):
- If in Chat: use “Search” for recent/fact-sensitive claims; when complexity is high, escalate to “Deep research” for multi-step, cited synthesis.
- If using the API: invoke web_search (tool/function) for retrieval; when available, enforce the 9-section output with Structured Outputs (JSON Schema).
- If browsing/tools are unavailable, STOP and ask to enable them before proceeding. Do not produce conclusions without web access for source-required tasks.
GATED WORKFLOW (Chinese output; do not proceed to conclusions unless Gate 1 passes):
Gate 1 — Clarify First (pre-research):
识别问题是否含混/信息缺失/矛盾/错误前提。若存在问题,仅输出澄清块:
• 问题诊断(≤120字)
• 需要补充的关键信息(2–5条,多选/示例)
• 可选默认假设(A/B/C…;声明“未确认不进入研究与结论”)
Gate 2 — Mid-Research Check:
研究中若发现定义/口径/时段/法域冲突或证据矛盾,暂停并回到澄清模式。
Gate 3 — Pre-Final Check:
结论前核验:所有用作推理前提的断言均有 [S#];计算逐步复核;若仍有缺口,回澄清。
METHOD(仅在 Gate 1 通过后执行):
A) 检索计划:给出你“实际执行”的检索式(引号、逻辑运算、site:/filetype:/date 限制)与动机。
B) 执行检索:打开并对比权威来源;剔除过时/仅观点/不可核验内容;必要时进一步检索补证。
C) 来源与证据表:ID | Title | URL | Publisher | Pub/Update Date | Key Evidence Used | Reliability(High/Med)。
D) 去伪存真记录:列明删除项与理由(过时、观点化、被反证、无法核验、无关)。
E) 已确认事实:仅留可交叉验证事实;关键结论力求 ≥3 个独立来源;每条附 [S#]。
F) 逻辑论证链:编号逐步推导,关键步骤附 [S#]。
G) 结论:中文作答,给出最优答案与置信度/不确定性范围。
H) 局限与更新触发条件:说明残余不确定性与可能改变结论的新证据。
NUMERICAL RIGOR:
- 展示算式与单位换算的逐步过程;逐位检查关键数字;避免心算跳步。
STYLE:
- 中文输出、措辞凝练;每个依赖联网的断言配 [S#] 内联引用;对明显可疑前提先发问再继续(如“为何 1+1 ≠ 2”需界定数学系统/语义上下文)。
FINAL OUTPUT FORMAT(九段固定):
1) 问题重述(若处于 Clarification Mode,仅输出“问题诊断/信息缺口/可选默认假设”)
2) 检索计划(含实际检索式与时间范围)
3) 来源与证据表(Sources Table)
4) 去伪存真记录(Exclusion Log)
5) 已确认事实清单(全部带 [S#])
6) 逻辑论证链(逐步推导,步步有 [S#])
7) 结论(最准确答案 + 置信度/范围)
8) 局限与更新触发条件
9) 参考文献(按 [S#] 列完整引文,含链接与访问日期)
USER QUESTION (paste multi-paragraph content between the markers):
<<<BEGIN_USER_QUESTION>>>
{User Question}
<<<END_USER_QUESTION>>>
`
}
};
// --- 国际化 ---
const translations = {
zh: {
toggleButton: "Helper", panelTitle: "PromptHelper", collapseTitle: "收起", selectTemplate: "选择模板", newBtn: "新建",
saveBtn: "保存", deleteBtn: "删除", templateName: "模板名称", templateNamePlaceholder: "为您的模板命名",
templateContent: "模板内容 (使用 {User Question} 作为占位符)", yourQuestion: "您的问题",
yourQuestionPlaceholder: "在此输入您的具体问题...", copyBtn: "复制到剪贴板", copiedBtn: "已复制!",
submitBtn: "填入提问栏", selectDefault: "-- 选择一个模板 --", alertSaveSuccess: "模板已保存!",
alertSaveError: "模板名称和内容不能为空!", alertDeleteConfirm: "确定要删除模板",
alertDeleteError: "请先选择一个要删除的模板!", alertCopyError: "复制失败,请查看控制台。",
alertSubmitError: "未找到当前网站的输入框。", alertTemplateError: "请先选择或创建一个模板!",
alertCannotDeleteDefault: "默认模板不可删除。"
},
en: {
toggleButton: "Helper", panelTitle: "PromptHelper", collapseTitle: "Collapse", selectTemplate: "Select Template", newBtn: "New",
saveBtn: "Save", deleteBtn: "Delete", templateName: "Template Name", templateNamePlaceholder: "Name your template",
templateContent: "Template Content (use {User Question} as placeholder)", yourQuestion: "Your Question",
yourQuestionPlaceholder: "Enter your specific question here...", copyBtn: "Copy to Clipboard", copiedBtn: "Copied!",
submitBtn: "Fill into Input", selectDefault: "-- Select a template --", alertSaveSuccess: "Template saved!",
alertSaveError: "Template name and content cannot be empty!", alertDeleteConfirm: "Are you sure you want to delete the template",
alertDeleteError: "Please select a template to delete first!", alertCopyError: "Failed to copy. See console for details.",
alertSubmitError: "Could not find the input box for the current site.", alertTemplateError: "Please select or create a template first!",
alertCannotDeleteDefault: "The default template cannot be deleted."
}
};
let currentLang = GM_getValue('universal_prompt_helper_lang', 'zh');
function injectStyles() {
GM_addStyle(`
#prompt-helper-container { all: initial !important; }
#prompt-helper-container *, #prompt-helper-container *::before, #prompt-helper-container *::after {
all: unset !important; box-sizing: border-box !important;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important;
margin: 0 !important; padding: 0 !important; text-decoration: none !important; border: none !important; outline: none !important;
}
#prompt-helper-container { position: fixed !important; top: 100px !important; right: 0 !important; z-index: 99999 !important; font-size: 16px !important; color: #333 !important; line-height: 1.5 !important; }
#prompt-helper-toggle { width: 40px !important; height: 100px !important; background-color: #007bff !important; color: white !important; border: none !important; border-radius: 10px 0 0 10px !important; cursor: pointer !important; writing-mode: vertical-rl !important; text-orientation: mixed !important; display: flex !important; align-items: center !important; justify-content: center !important; font-size: 16px !important; box-shadow: -2px 2px 5px rgba(0,0,0,0.2) !important; }
#prompt-helper-content { position: absolute !important; top: 0 !important; right: 40px !important; width: 400px !important; background-color: #f8f9fa !important; border: 1px solid #dee2e6 !important; border-radius: 8px !important; box-shadow: -2px 2px 10px rgba(0,0,0,0.1) !important; padding: 15px !important; display: flex !important; flex-direction: column !important; gap: 15px !important; transition: transform 0.3s ease-in-out, opacity 0.3s ease-in-out !important; color: #333 !important; text-align: left !important; }
#prompt-helper-content.hidden { transform: translateX(100%) !important; opacity: 0 !important; pointer-events: none !important; }
#prompt-helper-content h3 { padding: 0 !important; font-size: 18px !important; color: #343a40 !important; text-align: center !important; font-weight: bold !important; }
#prompt-helper-content .ph-section { display: flex !important; flex-direction: column !important; gap: 8px !important; }
#prompt-helper-content label { font-weight: bold !important; color: #495057 !important; font-size: 14px !important; }
#prompt-helper-content select, #prompt-helper-content input, #prompt-helper-content textarea { width: 100% !important; padding: 8px !important; border: 1px solid #ced4da !important; border-radius: 4px !important; font-size: 14px !important; color: #333 !important; background-color: #fff !important; line-height: 1.5 !important; }
#prompt-helper-content textarea { resize: vertical !important; min-height: 100px !important; }
#prompt-helper-content #ph-template-body { height: 150px !important; }
#prompt-helper-content #ph-user-question { height: 80px !important; }
#prompt-helper-content .ph-button-group { display: flex !important; gap: 10px !important; justify-content: space-between !important; }
#prompt-helper-content .ph-button-group button { flex-grow: 1 !important; }
#prompt-helper-content button { padding: 10px !important; border-radius: 5px !important; border: none !important; cursor: pointer !important; font-size: 14px !important; font-weight: bold !important; transition: background-color 0.2s, color 0.2s !important; color: white !important; }
#prompt-helper-content button:disabled { cursor: not-allowed !important; opacity: 0.7 !important; }
#prompt-helper-container .ph-btn-primary { background-color: #007bff !important; } #prompt-helper-container .ph-btn-primary:hover { background-color: #0056b3 !important; }
#prompt-helper-container .ph-btn-secondary { background-color: #6c757d !important; } #prompt-helper-container .ph-btn-secondary:hover { background-color: #5a6268 !important; }
#prompt-helper-container .ph-btn-success { background-color: #28a745 !important; } #prompt-helper-container .ph-btn-success:hover { background-color: #218838 !important; }
#prompt-helper-container .ph-btn-danger { background-color: #dc3545 !important; } #prompt-helper-container .ph-btn-danger:hover { background-color: #c82333 !important; }
#prompt-helper-container button:focus-visible, #prompt-helper-container select:focus-visible, #prompt-helper-container input:focus-visible, #prompt-helper-container textarea:focus-visible { outline: 2px solid #0056b3 !important; outline-offset: 2px !important; }
#prompt-helper-container .ph-header { display: flex !important; justify-content: space-between !important; align-items: center !important; margin-bottom: 10px !important; padding: 0 !important; }
#prompt-helper-container #ph-collapse-btn { font-size: 24px !important; cursor: pointer !important; color: #6c757d !important; border: none !important; background: none !important; padding: 0 5px !important; line-height: 1 !important; }
#prompt-helper-container #ph-lang-toggle { font-size: 12px !important; color: #007bff !important; background: none !important; border: 1px solid #007bff !important; padding: 2px 6px !important; border-radius: 4px !important; cursor: pointer !important; }
`);
}
function buildUI() {
const create = (tag, id, classes = [], attributes = {}, children = []) => {
const el = document.createElement(tag);
if (id) el.id = id;
if (classes.length) el.classList.add(...classes);
for (const key in attributes) el.setAttribute(key, attributes[key]);
for (const child of children) el.appendChild(child);
return el;
};
const D = {};
const container = create('div', 'prompt-helper-container');
D.toggleButton = create('button', 'prompt-helper-toggle');
D.contentPanel = create('div', 'prompt-helper-content', ['hidden']);
D.langToggleButton = create('button', 'ph-lang-toggle', [], {}, [document.createTextNode('中/En')]);
D.title = create('h3', 'ph-title');
D.collapseButton = create('button', 'ph-collapse-btn');
const header = create('div', 'ph-header', ['ph-header'], {}, [D.langToggleButton, D.title, D.collapseButton]);
D.labelSelect = create('label', 'ph-label-select', [], { for: 'ph-template-select' });
D.templateSelect = create('select', 'ph-template-select');
D.newBtn = create('button', 'ph-new-btn', ['ph-btn-primary']);
D.saveBtn = create('button', 'ph-save-btn', ['ph-btn-success']);
D.deleteBtn = create('button', 'ph-delete-btn', ['ph-btn-danger']);
const section1 = create('div', null, ['ph-section'], {}, [ D.labelSelect, D.templateSelect, create('div', null, ['ph-button-group'], {}, [D.newBtn, D.saveBtn, D.deleteBtn]) ]);
D.labelName = create('label', 'ph-label-name', [], { for: 'ph-template-name' });
D.templateNameInput = create('input', 'ph-template-name', [], { type: 'text' });
D.labelContent = create('label', 'ph-label-content', [], { for: 'ph-template-body' });
D.templateBodyTextarea = create('textarea', 'ph-template-body');
const section2 = create('div', null, ['ph-section'], {}, [D.labelName, D.templateNameInput, D.labelContent, D.templateBodyTextarea]);
D.labelQuestion = create('label', 'ph-label-question', [], { for: 'ph-user-question' });
D.userQuestionTextarea = create('textarea', 'ph-user-question');
const section3 = create('div', null, ['ph-section'], {}, [D.labelQuestion, D.userQuestionTextarea]);
D.copyBtn = create('button', 'ph-copy-btn', ['ph-btn-secondary']);
D.submitBtn = create('button', 'ph-submit-btn', ['ph-btn-primary']);
const section4 = create('div', null, ['ph-section'], {}, [ create('div', null, ['ph-button-group'], {}, [D.copyBtn, D.submitBtn]) ]);
D.contentPanel.append(header, section1, section2, section3, section4);
container.append(D.toggleButton, D.contentPanel);
return { container, elements: D };
}
function findInputElement() {
const siteConfig = getCurrentSiteConfig();
if (!siteConfig) { console.log('[PromptHelper] 未找到当前网站配置'); return null; }
console.log(`[PromptHelper] 正在查找 ${window.location.hostname} 的输入元素...`);
console.log(`[PromptHelper] 使用选择器: ${siteConfig.inputSelector}`);
let inputElement = null;
// 1) Shadow DOM
if (siteConfig.shadowRootSelector) {
const host = document.querySelector(siteConfig.shadowRootSelector);
if (host && host.shadowRoot) {
const elementInShadow = host.shadowRoot.querySelector(siteConfig.inputSelector);
if (elementInShadow) inputElement = elementInShadow;
}
}
// 2) 常规 DOM
if (!inputElement) {
const selectors = siteConfig.inputSelector.split(',').map(s => s.trim());
for (const selector of selectors) {
const elements = document.querySelectorAll(selector);
for (const element of elements) {
if (!element.closest('#prompt-helper-container')) { inputElement = element; break; }
}
if (inputElement) break;
}
}
// 3) AI Studio 回退
if (!inputElement && window.location.hostname.includes('aistudio.google.com')) {
const aiStudioSelectors = [
'[contenteditable="true"]','textarea','[role="textbox"]','[aria-label*="prompt"]','[aria-label*="Prompt"]','[aria-label*="message"]','[aria-label*="Message"]','[placeholder*="prompt"]','[placeholder*="Prompt"]','[placeholder*="message"]','[placeholder*="Message"]','[data-testid*="prompt"]','[data-testid*="input"]','.prompt-input','.chat-input','.message-input','input[type="text"]','div[spellcheck="true"]','[data-lexical-editor]','.editor-input'
];
for (const selector of aiStudioSelectors) {
const elements = document.querySelectorAll(selector);
for (const element of elements) {
if (!element.closest('#prompt-helper-container')) {
const style = window.getComputedStyle(element);
const isVisible = style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0';
const isEditable = !element.disabled && !element.readOnly &&
(element.contentEditable === 'true' || element.tagName.toLowerCase() === 'textarea' || element.tagName.toLowerCase() === 'input');
if (isVisible && isEditable) { inputElement = element; break; }
}
}
if (inputElement) break;
}
}
// 4) DeepSeek 回退
if (!inputElement && window.location.hostname.includes('deepseek.com')) {
const fallbackSelectors = [
'textarea','[contenteditable="true"]','[role="textbox"]','input[type="text"]','.chat-input','[data-placeholder]','[aria-label*="输入"]','[aria-label*="input"]','[placeholder*="聊"]','[placeholder*="chat"]'
];
for (const selector of fallbackSelectors) {
const elements = document.querySelectorAll(selector);
for (const element of elements) {
if (!element.closest('#prompt-helper-container')) {
const style = window.getComputedStyle(element);
if (style.display !== 'none' && style.visibility !== 'hidden' && !element.disabled && !element.readOnly) {
inputElement = element; break;
}
}
}
if (inputElement) break;
}
}
// 5) Grok 回退
if (!inputElement && window.location.hostname.includes('grok.com')) {
const grokSelectors = [
'form .query-bar textarea[aria-label]',
'textarea[aria-label*="Grok"]',
'textarea[aria-label*="向 Grok"]',
'textarea'
];
for (const selector of grokSelectors) {
const elements = document.querySelectorAll(selector);
for (const el of elements) {
if (!el.closest('#prompt-helper-container')) {
const st = window.getComputedStyle(el);
const visible = st.display !== 'none' && st.visibility !== 'hidden' && st.opacity !== '0';
const editable = !el.disabled && !el.readOnly;
if (visible && editable) { inputElement = el; break; }
}
}
if (inputElement) break;
}
}
if (inputElement) {
console.log('[PromptHelper] 成功找到输入元素:', { tagName: inputElement.tagName, className: inputElement.className, id: inputElement.id, placeholder: inputElement.placeholder, contentEditable: inputElement.contentEditable, element: inputElement });
} else {
console.log('[PromptHelper] 未找到输入元素');
const allInputs = document.querySelectorAll('textarea, input, [contenteditable="true"], [role="textbox"]');
allInputs.forEach((el, index) => {
console.log(`[PromptHelper] 候选元素 ${index + 1}:`, { tagName: el.tagName, className: el.className, id: el.id, placeholder: el.placeholder, contentEditable: el.contentEditable, isVisible: window.getComputedStyle(el).display !== 'none', element: el });
});
}
return inputElement;
}
function init() {
if (document.getElementById('prompt-helper-container')) return;
if (window.promptHelperInitialized) return;
window.promptHelperInitialized = true;
injectStyles();
const { container, elements: D } = buildUI();
const addToDOM = () => { if (document.body) { document.body.appendChild(container); } else { setTimeout(addToDOM, 100); } };
addToDOM();
let prompts = {};
const updateUI = () => {
const t = translations[currentLang];
D.toggleButton.textContent = t.toggleButton; D.title.textContent = t.panelTitle;
D.collapseButton.title = t.collapseTitle; D.collapseButton.textContent = '\u00d7';
D.labelSelect.textContent = t.selectTemplate;
D.newBtn.textContent = t.newBtn; D.saveBtn.textContent = t.saveBtn; D.deleteBtn.textContent = t.deleteBtn;
D.labelName.textContent = t.templateName; D.templateNameInput.placeholder = t.templateNamePlaceholder;
D.labelContent.textContent = t.templateContent;
D.labelQuestion.textContent = t.yourQuestion; D.userQuestionTextarea.placeholder = t.yourQuestionPlaceholder;
D.copyBtn.textContent = t.copyBtn; D.submitBtn.textContent = t.submitBtn;
populateDropdown();
// 默认选择“通用交互式提问模板”
if (prompts[DEFAULT_TEMPLATE_ID]) D.templateSelect.value = DEFAULT_TEMPLATE_ID;
displaySelectedPrompt();
};
const savePrompts = () => GM_setValue('universal_prompt_helper_prompts', JSON.stringify(prompts));
// 迁移:移除旧版本的三个默认模板(不影响用户自建模板)
function removeLegacyDefaults(obj) {
const legacyIds = ['prompt_1', 'prompt_2', 'prompt_3'];
const legacyNames = new Set(['通用回答模板','代码评审模板','英文润色模板']);
legacyIds.forEach(id => { if (id in obj) delete obj[id]; });
for (const k of Object.keys(obj)) {
if (legacyNames.has(obj[k]?.name)) delete obj[k];
}
}
const populateDropdown = () => {
const currentSelection = D.templateSelect.value;
D.templateSelect.textContent = '';
const defaultOption = document.createElement('option');
defaultOption.value = ''; defaultOption.textContent = translations[currentLang].selectDefault;
D.templateSelect.appendChild(defaultOption);
// 默认模板优先
if (prompts[DEFAULT_TEMPLATE_ID]) {
const opt = document.createElement('option');
opt.value = DEFAULT_TEMPLATE_ID; opt.textContent = prompts[DEFAULT_TEMPLATE_ID].name;
D.templateSelect.appendChild(opt);
}
for (const id in prompts) {
if (id === DEFAULT_TEMPLATE_ID) continue;
const option = document.createElement('option');
option.value = id; option.textContent = prompts[id].name;
D.templateSelect.appendChild(option);
}
if (prompts[currentSelection]) D.templateSelect.value = currentSelection;
};
const displaySelectedPrompt = () => {
const selectedId = D.templateSelect.value;
if (selectedId && prompts[selectedId]) {
D.templateNameInput.value = prompts[selectedId].name;
D.templateBodyTextarea.value = prompts[selectedId].template;
} else { D.templateNameInput.value = ''; D.templateBodyTextarea.value = ''; }
D.deleteBtn.disabled = (selectedId === DEFAULT_TEMPLATE_ID); // 默认模板不可删除
};
const generateFinalPrompt = () => {
const template = D.templateBodyTextarea.value;
const question = D.userQuestionTextarea.value;
if (!template) { alert(translations[currentLang].alertTemplateError); return null; }
return template.replace('{User Question}', question);
};
const loadPrompts = () => {
const saved = GM_getValue('universal_prompt_helper_prompts', null);
if (saved) {
try { prompts = JSON.parse(saved) || {}; } catch { prompts = {}; }
removeLegacyDefaults(prompts); // 删除旧默认模板
if (!prompts[DEFAULT_TEMPLATE_ID]) {
prompts[DEFAULT_TEMPLATE_ID] = defaultPrompts[DEFAULT_TEMPLATE_ID];
}
savePrompts();
} else {
prompts = { ...defaultPrompts };
savePrompts();
}
updateUI(); // 更新并默认选中
// 再确保选中默认模板一次(防第三方样式/脚本影响)
if (prompts[DEFAULT_TEMPLATE_ID]) {
D.templateSelect.value = DEFAULT_TEMPLATE_ID;
displaySelectedPrompt();
}
};
// 事件绑定
D.toggleButton.addEventListener('click', () => D.contentPanel.classList.remove('hidden'));
D.collapseButton.addEventListener('click', () => D.contentPanel.classList.add('hidden'));
D.langToggleButton.addEventListener('click', () => { currentLang = currentLang === 'zh' ? 'en' : 'zh'; GM_setValue('universal_prompt_helper_lang', currentLang); updateUI(); });
D.templateSelect.addEventListener('change', displaySelectedPrompt);
D.newBtn.addEventListener('click', () => { D.templateSelect.value = ''; D.templateNameInput.value = ''; D.templateBodyTextarea.value = ''; D.templateNameInput.focus(); D.deleteBtn.disabled = true; });
D.saveBtn.addEventListener('click', () => {
const name = D.templateNameInput.value.trim();
const template = D.templateBodyTextarea.value.trim();
if (!name || !template) { alert(translations[currentLang].alertSaveError); return; }
let selectedId = D.templateSelect.value || `prompt_${Date.now()}`;
prompts[selectedId] = { name, template };
savePrompts(); populateDropdown(); D.templateSelect.value = selectedId; displaySelectedPrompt();
alert(`${translations[currentLang].alertSaveSuccess} "${name}"`);
});
D.deleteBtn.addEventListener('click', () => {
const selectedId = D.templateSelect.value;
if (!selectedId) { alert(translations[currentLang].alertDeleteError); return; }
if (selectedId === DEFAULT_TEMPLATE_ID) { alert(translations[currentLang].alertCannotDeleteDefault); return; }
if (confirm(`${translations[currentLang].alertDeleteConfirm} "${prompts[selectedId].name}"?`)) {
delete prompts[selectedId]; savePrompts(); populateDropdown();
// 删除后默认选中默认模板
if (prompts[DEFAULT_TEMPLATE_ID]) D.templateSelect.value = DEFAULT_TEMPLATE_ID;
displaySelectedPrompt();
}
});
D.copyBtn.addEventListener('click', () => {
const finalPrompt = generateFinalPrompt();
if (finalPrompt) {
navigator.clipboard.writeText(finalPrompt).then(() => {
const originalText = D.copyBtn.textContent;
D.copyBtn.textContent = translations[currentLang].copiedBtn; D.copyBtn.disabled = true;
setTimeout(() => { D.copyBtn.textContent = originalText; D.copyBtn.disabled = false; }, 2000);
}).catch(err => { console.error('Copy failed:', err); alert(translations[currentLang].alertCopyError); });
}
});
D.submitBtn.addEventListener('click', () => {
const finalPrompt = generateFinalPrompt();
if (!finalPrompt) return;
const inputElement = findInputElement();
if (!inputElement) { alert(translations[currentLang].alertSubmitError); return; }
// textarea
if (inputElement.tagName.toLowerCase() === 'textarea') {
if (window.location.hostname.includes('tongyi.com')) {
// 通义:老逻辑 + 受控兜底
const reactKey = Object.keys(inputElement).find(key => key.startsWith('__reactInternalInstance') || key.startsWith('__reactFiber') || key.startsWith('__reactProps'));
if (reactKey) {
try {
const fiberNode = inputElement[reactKey];
const possiblePaths = [
fiberNode?.memoizedProps?.onChange,
fiberNode?.return?.memoizedProps?.onChange,
fiberNode?.return?.return?.memoizedProps?.onChange,
fiberNode?.pendingProps?.onChange
];
for (const onChange of possiblePaths) {
if (onChange && typeof onChange === 'function') {
const fakeEvent = { target: { value: finalPrompt }, currentTarget: { value: finalPrompt }, preventDefault: () => {}, stopPropagation: () => {} };
onChange(fakeEvent); break;
}
}
} catch (e) { console.log('[PromptHelper] React状态操作失败:', e); }
}
inputElement.focus();
inputElement.value = '';
inputElement.value = finalPrompt;
try { Object.defineProperty(inputElement, 'value', { value: finalPrompt, writable: true, configurable: true }); } catch (_){}
[
new Event('focus', { bubbles: true }),
tryCreateInputEvent('beforeinput', { bubbles: true, cancelable: true, data: finalPrompt, inputType: 'insertText' }),
tryCreateInputEvent('input', { bubbles: true, cancelable: true, data: finalPrompt, inputType: 'insertText' }),
new Event('change', { bubbles: true }),
tryCreateKeyboardEvent('keydown', { bubbles: true, key: 'a' }),
tryCreateKeyboardEvent('keyup', { bubbles: true, key: 'a' }),
new Event('blur', { bubbles: true })
].forEach((ev, i) => setTimeout(() => inputElement.dispatchEvent(ev), i * 10));
setTimeout(() => {
if (inputElement.value !== finalPrompt) inputElement.value = finalPrompt;
inputElement.blur();
setTimeout(() => {
inputElement.focus();
inputElement.value = finalPrompt;
inputElement.dispatchEvent(tryCreateInputEvent('input', { bubbles: true, cancelable: true, data: finalPrompt, inputType: 'insertText' }));
inputElement.dispatchEvent(new Event('change', { bubbles: true }));
inputElement.dispatchEvent(new Event('propertychange', { bubbles: true }));
window.dispatchEvent(new Event('resize'));
}, 50);
}, 150);
} else if (window.location.hostname.includes('grok.com')) {
// Grok:原生 setter + 事件序列
inputElement.focus();
setNativeValue(inputElement, ''); inputElement.dispatchEvent(new Event('input', { bubbles: true }));
setNativeValue(inputElement, finalPrompt); try { inputElement.setAttribute('value', finalPrompt); } catch (_){}
inputElement.dispatchEvent(tryCreateInputEvent('beforeinput', { bubbles: true, cancelable: true, inputType: 'insertFromPaste', data: finalPrompt }));
inputElement.dispatchEvent(new Event('input', { bubbles: true }));
inputElement.dispatchEvent(tryCreateInputEvent('input', { bubbles: true, cancelable: true, inputType: 'insertText', data: finalPrompt }));
inputElement.dispatchEvent(new Event('change', { bubbles: true }));
['keydown','keypress','keyup'].forEach(type => inputElement.dispatchEvent(tryCreateKeyboardEvent(type, { bubbles: true, cancelable: true, key: 'a', code: 'KeyA' })));
try { inputElement.setSelectionRange(finalPrompt.length, finalPrompt.length); } catch (_){}
setTimeout(() => { inputElement.dispatchEvent(new Event('input', { bubbles: true })); inputElement.dispatchEvent(new Event('change', { bubbles: true })); }, 50);
} else {
// ChatGPT 等原逻辑
if (window.location.hostname.includes('openai.com') || window.location.hostname.includes('chatgpt.com')) {
inputElement.value = finalPrompt;
const isChrome = navigator.userAgent.includes('Chrome') && !navigator.userAgent.includes('Firefox');
if (isChrome) {
setTimeout(() => {
inputElement.focus(); inputElement.value = finalPrompt;
if (typeof inputElement.setSelectionRange === 'function') inputElement.setSelectionRange(finalPrompt.length, finalPrompt.length);
inputElement.dispatchEvent(tryCreateInputEvent('input', { bubbles: true, cancelable: false, inputType: 'insertText' }));
let protectionCount = 0;
const protect = () => {
if (protectionCount < 20) {
const cur = inputElement.value;
if (cur.replace(/\n/g,'') === finalPrompt.replace(/\n/g,'') && !cur.includes('\n') && finalPrompt.includes('\n')) {
inputElement.value = finalPrompt;
if (typeof inputElement.setSelectionRange === 'function') inputElement.setSelectionRange(finalPrompt.length, finalPrompt.length);
inputElement.dispatchEvent(tryCreateInputEvent('input', { bubbles: true, cancelable: false, inputType: 'insertText' }));
}
protectionCount++; setTimeout(protect, 100);
}
};
setTimeout(protect, 100);
}, 50);
} else {
inputElement.dispatchEvent(new Event('input', { bubbles: true }));
if (typeof inputElement.setSelectionRange === 'function') inputElement.setSelectionRange(finalPrompt.length, finalPrompt.length);
}
} else {
inputElement.value = finalPrompt;
}
// DeepSeek 伴随 div 镜像
if (window.location.hostname.includes('deepseek.com')) {
const parentDiv = inputElement.parentElement;
if (parentDiv) {
let displayDiv = parentDiv.querySelector('.b13855df');
if (!displayDiv) {
const allDivs = parentDiv.querySelectorAll('div');
for (const div of allDivs) { if (!div.classList.contains('_24fad49') && div !== parentDiv) { displayDiv = div; break; } }
}
if (displayDiv) {
displayDiv.innerHTML = '';
finalPrompt.split('\n').forEach((line, idx) => { if (idx>0) displayDiv.appendChild(document.createElement('br')); displayDiv.appendChild(document.createTextNode(line)); });
}
}
}
}
// contenteditable
} else if (inputElement.getAttribute('contenteditable') === 'true') {
if (window.location.hostname.includes('claude.ai') || window.location.hostname.includes('fuclaude.com')) {
pasteIntoProseMirror(inputElement, finalPrompt);
inputElement.dispatchEvent(new Event('input', { bubbles: true }));
inputElement.dispatchEvent(new Event('change', { bubbles: true }));
try { const range=document.createRange(); const sel=window.getSelection(); range.selectNodeContents(inputElement); range.collapse(false); sel.removeAllRanges(); sel.addRange(range); } catch(_){}
} else {
if (window.location.hostname.includes('claude.ai') || window.location.hostname.includes('fuclaude.com') || window.location.hostname.includes('openai.com') || window.location.hostname.includes('chatgpt.com')) {
inputElement.innerHTML = '';
const lines = finalPrompt.split('\n');
lines.forEach((line, index) => {
if (index > 0) inputElement.appendChild(document.createElement('br'));
if (line.length > 0) inputElement.appendChild(document.createTextNode(line));
else if (index < lines.length - 1) inputElement.appendChild(document.createElement('br'));
});
const isChrome = navigator.userAgent.includes('Chrome') && !navigator.userAgent.includes('Firefox');
if (isChrome) {
const needEscape = (window.location.hostname.includes('openai.com') || window.location.hostname.includes('chatgpt.com'));
const htmlWithBreaks = needEscape ? escapeHtml(finalPrompt).replace(/\n/g,'<br>') : finalPrompt.replace(/\n/g,'<br>');
setTimeout(() => {
inputElement.focus(); inputElement.innerHTML = htmlWithBreaks;
const range=document.createRange(); const sel=window.getSelection();
range.selectNodeContents(inputElement); range.collapse(false); sel.removeAllRanges(); sel.addRange(range);
inputElement.dispatchEvent(tryCreateInputEvent('input', { bubbles: true, cancelable: false, inputType: 'insertFromPaste' }));
let protectionCount = 0;
const protect = () => {
if (protectionCount < 20) {
const currentHtml = inputElement.innerHTML;
const currentText = inputElement.textContent || inputElement.innerText;
if (currentText.replace(/\n/g,'') === finalPrompt.replace(/\n/g,'') && !currentHtml.includes('<br>') && finalPrompt.includes('\n')) {
inputElement.innerHTML = htmlWithBreaks;
try { const r=document.createRange(); const s=window.getSelection(); r.selectNodeContents(inputElement); r.collapse(false); s.removeAllRanges(); s.addRange(r);} catch(_){}
}
protectionCount++; setTimeout(protect, 100);
}
};
setTimeout(protect, 100);
}, 50);
}
} else {
inputElement.textContent = finalPrompt;
}
}
// 兜底
} else {
if ('value' in inputElement) inputElement.value = finalPrompt;
if (inputElement.textContent !== undefined) inputElement.textContent = finalPrompt;
if (inputElement.innerText !== undefined) inputElement.innerText = finalPrompt;
}
// 通用事件
['input','change','keydown','keyup','paste'].forEach(type => {
let ev;
if (type==='input') ev = tryCreateInputEvent('input', { bubbles:true, cancelable:true, inputType:'insertText', data: finalPrompt });
else if (type==='keydown' || type==='keyup') ev = tryCreateKeyboardEvent(type, { bubbles:true, cancelable:true, key:'a', code:'KeyA' });
else ev = new Event(type, { bubbles:true, cancelable:true });
inputElement.dispatchEvent(ev);
});
if (window.location.hostname.includes('deepseek.com')) {
['keydown','keypress','keyup'].forEach(t => inputElement.dispatchEvent(tryCreateKeyboardEvent(t, { bubbles:true, cancelable:true, key:'a', code:'KeyA', which:65, keyCode:65 })));
inputElement.dispatchEvent(new Event('compositionstart', { bubbles: true }));
inputElement.dispatchEvent(new Event('compositionupdate', { bubbles: true }));
inputElement.dispatchEvent(new Event('compositionend', { bubbles: true }));
}
// 聚焦与光标
inputElement.focus();
if (inputElement.tagName.toLowerCase() === 'textarea' || inputElement.type === 'text') {
try { inputElement.setSelectionRange(finalPrompt.length, finalPrompt.length); } catch (_){}
} else if (inputElement.getAttribute('contenteditable') === 'true') {
const range=document.createRange(); const sel=window.getSelection();
if (sel && inputElement.childNodes.length > 0) { range.selectNodeContents(inputElement); range.collapse(false); sel.removeAllRanges(); sel.addRange(range); }
}
// 轻微延迟再触发
setTimeout(() => {
inputElement.dispatchEvent(new Event('input', { bubbles: true }));
inputElement.dispatchEvent(new Event('change', { bubbles: true }));
const specialSites = ['deepseek.com','kimi.moonshot.cn','kimi.com','www.kimi.com','tongyi.com','yuanbao.tencent.com'];
const currentSiteCheck = specialSites.find(site => window.location.hostname.includes(site));
if (currentSiteCheck) console.log(`[PromptHelper] 延迟检查 ${currentSiteCheck} 状态`);
}, 100);
// 额外同步检查
const specialSites = ['deepseek.com','kimi.moonshot.cn','kimi.com','www.kimi.com','tongyi.com','yuanbao.tencent.com','aistudio.google.com'];
const currentSite = specialSites.find(site => window.location.hostname.includes(site));
if (currentSite) {
const skipComplexProcessing = (currentSite === 'kimi.moonshot.cn' || currentSite === 'kimi.com' || currentSite === 'www.kimi.com') && inputElement.getAttribute('contenteditable') === 'true';
if (!skipComplexProcessing) {
setTimeout(() => {
const parentDiv = inputElement.parentElement;
if (parentDiv) {
inputElement.blur();
setTimeout(() => {
inputElement.focus();
try { if (inputElement.tagName.toLowerCase() === 'textarea' || inputElement.type === 'text') inputElement.setSelectionRange(finalPrompt.length, finalPrompt.length); } catch(_){}
}, 50);
window.dispatchEvent(new Event('resize'));
const reactKey = Object.keys(inputElement).find(key => key.startsWith('__reactInternalInstance') || key.startsWith('__reactFiber'));
if (reactKey) {
try {
const fiberNode = inputElement[reactKey];
if (fiberNode && fiberNode.memoizedProps && typeof fiberNode.memoizedProps.onChange === 'function') {
const fakeEvent = { target: { value: finalPrompt }, currentTarget: { value: finalPrompt }, preventDefault: () => {}, stopPropagation: () => {} };
fiberNode.memoizedProps.onChange(fakeEvent);
}
} catch (e) { console.log(`[PromptHelper] ${currentSite} React状态更新失败:`, e); }
}
if (currentSite === 'tongyi.com') {
inputElement.focus(); inputElement.value = '';
const txt = finalPrompt;
for (let i=0;i<txt.length;i++){
const ch = txt[i];
inputElement.value += ch;
[ tryCreateKeyboardEvent('keydown', { bubbles:true, cancelable:true, key: ch }),
tryCreateInputEvent('input', { bubbles:true, cancelable:true, data: ch, inputType: 'insertText' }),
tryCreateKeyboardEvent('keyup', { bubbles:true, cancelable:true, key: ch }) ].forEach(ev => inputElement.dispatchEvent(ev));
}
inputElement.dispatchEvent(new Event('change', { bubbles: true }));
inputElement.dispatchEvent(new Event('blur', { bubbles: true }));
setTimeout(() => inputElement.focus(), 50);
} else if (currentSite === 'yuanbao.tencent.com') {
try { inputElement.setAttribute('value', finalPrompt); } catch(_){}
} else if (currentSite === 'aistudio.google.com') {
const angularKey = Object.keys(inputElement).find(key => key.startsWith('__ngContext') || key.startsWith('__ng') || key.includes('angular'));
if (angularKey) {
try {
const ngZone = window.ng?.getComponent?.(inputElement);
if (ngZone) {
ngZone.run(() => {
inputElement.value = finalPrompt;
if (inputElement.textContent !== undefined) inputElement.textContent = finalPrompt;
});
}
} catch (e) { console.log('[PromptHelper] Angular状态操作失败:', e); }
}
if (inputElement.getAttribute('contenteditable') === 'true') {
inputElement.innerHTML = '';
finalPrompt.split('\n').forEach((line, idx) => { if (idx>0) inputElement.appendChild(document.createElement('br')); inputElement.appendChild(document.createTextNode(line)); });
}
[ new Event('focus', { bubbles: true }),
tryCreateInputEvent('input', { bubbles: true, cancelable: true, inputType: 'insertText' }),
new Event('change', { bubbles: true }),
new Event('blur', { bubbles: true }) ].forEach((ev, i) => setTimeout(() => inputElement.dispatchEvent(ev), i * 20));
}
}
setTimeout(() => {
if ('value' in inputElement && inputElement.value !== finalPrompt) {
inputElement.value = finalPrompt;
inputElement.dispatchEvent(new Event('input', { bubbles: true }));
}
}, 300);
}, 500);
}
}
D.userQuestionTextarea.value = '';
D.contentPanel.classList.add('hidden');
});
loadPrompts();
}
function getCurrentSiteConfig() {
const hostname = window.location.hostname;
for (const key in siteConfigs) { if (hostname.includes(key)) return siteConfigs[key]; }
return null;
}
if (getCurrentSiteConfig()) { init(); }
});
})();