您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
The definitive power-user script for Google AI Studio. Performs an instant, flicker-free check on page load & between chats (SPA-aware), so the UI only appears when needed. Replaces all browser dialogs with a professional, themed UI and non-blocking toast notifications. Features a 'one-shot' workflow to inject, close, & hide with a single click. Offers total control: closable button (hide session/forever) & full Tampermonkey menu integration (edit, inject, re-show).
// ==UserScript== // @name AI Studio System Instructions Injector // @version 15.0 // @description The definitive power-user script for Google AI Studio. Performs an instant, flicker-free check on page load & between chats (SPA-aware), so the UI only appears when needed. Replaces all browser dialogs with a professional, themed UI and non-blocking toast notifications. Features a 'one-shot' workflow to inject, close, & hide with a single click. Offers total control: closable button (hide session/forever) & full Tampermonkey menu integration (edit, inject, re-show). // @author Sayf // @homepageURL https://github.com/zSayf/Tampermonkey.AI-Studio-System-Instructions-Injector // @source https://github.com/zSayf/Tampermonkey.AI-Studio-System-Instructions-Injector // @supportURL https://github.com/zSayf/Tampermonkey.AI-Studio-System-Instructions-Injector/issues // @license MIT // @match https://aistudio.google.com/*prompts/* // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @namespace https://github.com/zSayf // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM4ZTRhYWQiIHN0cm9rZS13aWR0aD0iMi41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNSA0VjJtMCAxOHYtMm0tNy41LTEyLjVMNCA0bTE2IDE2bC0zLjUtMy41TTIgMTJoMm0xNiAwaDJNNy41IDcuNUw0IDRtMTIuNSAwbDMuNSAzLjUiLz48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIyIi8+PC9zdmc+ // ==/UserScript== (async function() { 'use strict'; // --- Configuration & Constants --- const SCRIPT_NAME = 'AI Studio Injector'; const STORAGE_KEY_INSTRUCTIONS = 'custom_system_instructions'; const STORAGE_KEY_BUTTON_HIDDEN = 'injector_button_hidden_forever'; const DEFAULT_INSTRUCTIONS = ''; const SELECTORS = { systemInstructionsButton: '[data-test-system-instructions-card]', systemInstructionsSubtitle: '[data-test-system-instructions-card] .subtitle', systemInstructionsTextArea: 'textarea[aria-label="System instructions"]', overlayBackdrop: '.cdk-overlay-backdrop' }; let systemInstructions; // --- UI Modals --- function showCustomPrompt({ title, message, defaultValue }) { return new Promise((resolve) => { const overlay = document.createElement('div'); overlay.id = 'injector-overlay'; const modal = document.createElement('div'); modal.id = 'injector-modal'; modal.innerHTML = ` <h2>${title}</h2> <p>${message}</p> <textarea id="injector-textarea" placeholder="Paste your system instructions here...">${defaultValue}</textarea> <div class="injector-buttons"> <button id="injector-cancel">Cancel</button> <button id="injector-save">Save</button> </div> `; document.body.appendChild(overlay); document.body.appendChild(modal); const textarea = document.getElementById('injector-textarea'); textarea.focus(); textarea.select(); const closeModal = () => { overlay.remove(); modal.remove(); }; document.getElementById('injector-save').addEventListener('click', () => { resolve(textarea.value); closeModal(); }); document.getElementById('injector-cancel').addEventListener('click', () => { resolve(null); closeModal(); }); overlay.addEventListener('click', () => { resolve(null); closeModal(); }); }); } function showConfirmationModal({ title, message }) { return new Promise((resolve) => { const overlay = document.createElement('div'); overlay.id = 'injector-overlay'; const modal = document.createElement('div'); modal.id = 'injector-modal'; modal.innerHTML = ` <h2>${title}</h2> <p>${message}</p> <div class="injector-buttons confirmation"> <button data-choice="cancel" class="cancel">Cancel</button> <button data-choice="overwrite">Overwrite</button> </div> `; document.body.appendChild(overlay); document.body.appendChild(modal); const closeModal = () => { overlay.remove(); modal.remove(); }; modal.querySelector('button[data-choice="overwrite"]').addEventListener('click', () => { resolve(true); closeModal(); }); modal.querySelector('button[data-choice="cancel"]').addEventListener('click', () => { resolve(false); closeModal(); }); overlay.addEventListener('click', () => { resolve(false); closeModal(); }); }); } function showHideConfirmationModal() { return new Promise((resolve) => { const modal = document.createElement('div'); modal.id = 'injector-modal'; modal.innerHTML = ` <h2>Hide Button</h2> <p>How would you like to hide the injector button?</p> <div class="injector-buttons confirmation"> <button data-choice="session">For Session</button> <button data-choice="forever">Hide Forever</button> <button data-choice="cancel" class="cancel">Cancel</button> </div> `; const overlay = document.createElement('div'); overlay.id = 'injector-overlay'; document.body.appendChild(overlay); document.body.appendChild(modal); const closeModal = () => { overlay.remove(); modal.remove(); }; modal.querySelectorAll('button').forEach(button => { button.addEventListener('click', () => { resolve(button.dataset.choice); closeModal(); }); }); overlay.addEventListener('click', () => { resolve('cancel'); closeModal(); }); }); } function showToast(message, type = 'info') { let toastContainer = document.getElementById('injector-toast-container'); if (!toastContainer) { toastContainer = document.createElement('div'); toastContainer.id = 'injector-toast-container'; document.body.appendChild(toastContainer); } const toast = document.createElement('div'); toast.className = `injector-toast ${type}`; const icons = { success: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>`, info: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>`, error: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>` }; toast.innerHTML = `<div class="toast-icon">${icons[type]}</div><div class="toast-message">${message}</div>`; toastContainer.appendChild(toast); setTimeout(() => { toast.classList.add('fade-out'); toast.addEventListener('animationend', () => toast.remove()); }, 3500); } // --- Styling --- GM_addStyle(` #injector-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.7); z-index: 9998; backdrop-filter: blur(4px); } #injector-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #2d2d2d; color: #e0e0e0; border-radius: 12px; padding: 24px; width: 90%; max-width: 600px; z-index: 9999; box-shadow: 0 10px 30px rgba(0,0,0,0.5); border: 1px solid #444; } #injector-modal h2 { margin-top: 0; font-size: 1.4em; color: #fff; } #injector-modal p { margin-bottom: 16px; line-height: 1.5; color: #ccc; } #injector-textarea { width: 100%; min-height: 200px; background: #1e1e1e; color: #e0e0e0; border: 1px solid #555; border-radius: 8px; padding: 12px; font-family: monospace; font-size: 14px; resize: vertical; box-sizing: border-box; } .injector-buttons { text-align: right; margin-top: 20px; } .injector-buttons button { padding: 10px 20px; border: none; border-radius: 6px; cursor: pointer; font-weight: bold; transition: all 0.2s ease; } #injector-cancel, .injector-buttons.confirmation button.cancel { background: #444; color: #fff; } #injector-cancel:hover, .injector-buttons.confirmation button.cancel:hover { background: #555; } #injector-save, .injector-buttons.confirmation button[data-choice="overwrite"] { background: #c0392b; color: #fff; } #injector-save:hover, .injector-buttons.confirmation button[data-choice="overwrite"]:hover { background: #e74c3c; } .injector-buttons.confirmation { display: flex; justify-content: flex-end; gap: 10px; } .injector-buttons.confirmation button { background: #4a4a4a; } .injector-buttons.confirmation button:hover { background: #5a5a5a; } #ai-studio-injector-trigger { position: fixed; bottom: 0; left: 50%; transform: translateX(-50%); z-index: 9990; background-color: #6a1b9a; color: white; border: none; border-radius: 12px 12px 0 0; padding: 8px 20px; display: flex; align-items: center; justify-content: center; gap: 10px; cursor: pointer; box-shadow: 0 -4px 15px rgba(0, 0, 0, 0.3); transition: all 0.25s ease; } #ai-studio-injector-trigger:hover { background-color: #8e24aa; transform: translateX(-50%) translateY(-5px); box-shadow: 0 -6px 20px rgba(0, 0, 0, 0.4); } .injector-trigger-text { font-size: 14px; font-weight: bold; } .injector-close-btn { position: absolute; top: -5px; right: -5px; width: 20px; height: 20px; background: #c0392b; color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: bold; cursor: pointer; transition: transform 0.2s, opacity 0.2s; opacity: 0.5; line-height: 20px; } #ai-studio-injector-trigger:hover .injector-close-btn { opacity: 1; } .injector-close-btn:hover { transform: scale(1.2); } #injector-toast-container { position: fixed; top: 20px; right: 20px; z-index: 10000; display: flex; flex-direction: column; gap: 10px; } .injector-toast { display: flex; align-items: center; padding: 12px 16px; border-radius: 8px; background: #333; color: #fff; border-left: 5px solid #555; box-shadow: 0 5px 15px rgba(0,0,0,0.4); animation: toast-fade-in 0.3s ease; } .injector-toast.fade-out { animation: toast-fade-out 0.4s ease forwards; } .injector-toast .toast-icon { margin-right: 12px; display: flex; align-items: center; } .injector-toast .toast-icon svg { width: 20px; height: 20px; } .injector-toast.success { border-left-color: #2ecc71; } .injector-toast.success .toast-icon { color: #2ecc71; } .injector-toast.info { border-left-color: #3498db; } .injector-toast.info .toast-icon { color: #3498db; } .injector-toast.error { border-left-color: #e74c3c; } .injector-toast.error .toast-icon { color: #e74c3c; } @keyframes toast-fade-in { from { opacity: 0; transform: translateX(100%); } to { opacity: 1; transform: translateX(0); } } @keyframes toast-fade-out { from { opacity: 1; transform: translateX(0); } to { opacity: 0; transform: translateX(100%); } } `); // --- Core Logic --- async function editInstructions() { const currentInstructions = await GM_getValue(STORAGE_KEY_INSTRUCTIONS, DEFAULT_INSTRUCTIONS); const userInput = await showCustomPrompt({ title: "Edit System Instructions", message: "You can modify your saved system instructions below.", defaultValue: currentInstructions }); if (userInput !== null && userInput.trim() !== '') { systemInstructions = userInput.trim(); await GM_setValue(STORAGE_KEY_INSTRUCTIONS, systemInstructions); showToast('Instructions saved successfully!', 'success'); } else if (userInput !== null) { showToast('Instructions cannot be empty.', 'error'); } } async function loadOrPromptForInstructions() { let savedInstructions = await GM_getValue(STORAGE_KEY_INSTRUCTIONS, null); if (savedInstructions === null) { let userInput = null; while (userInput === null || userInput.trim() === '') { userInput = await showCustomPrompt({ title: `Welcome to ${SCRIPT_NAME}!`, message: "This is your first time. Please set your custom system instructions below.", defaultValue: DEFAULT_INSTRUCTIONS }); if (userInput === null) { const dummyInstruction = "---INJECTOR_DISABLED---"; await GM_setValue(STORAGE_KEY_INSTRUCTIONS, dummyInstruction); return dummyInstruction; } if (userInput.trim() === '') { showToast("Instructions cannot be empty. Please provide a prompt or cancel.", "error"); } } savedInstructions = userInput.trim(); await GM_setValue(STORAGE_KEY_INSTRUCTIONS, savedInstructions); } return savedInstructions; } function waitForElement(selector, callback) { const existingElement = document.querySelector(selector); if (existingElement) return callback(existingElement); const observer = new MutationObserver(() => { const element = document.querySelector(selector); if (element) { observer.disconnect(); callback(element); } }); observer.observe(document.body, { childList: true, subtree: true }); } function injectText(textArea, text) { textArea.value = text; textArea.dispatchEvent(new Event('input', { bubbles: true })); textArea.dispatchEvent(new Event('blur', { bubbles: true })); console.log(`[${SCRIPT_NAME}]: SUCCESS: Text injected.`); } function hideButton() { const btn = document.getElementById('ai-studio-injector-trigger'); if (btn) btn.style.display = 'none'; } function closePanel() { return new Promise((resolve, reject) => { let attempts = 0; const maxAttempts = 25; const interval = setInterval(() => { if (!document.querySelector(SELECTORS.systemInstructionsTextArea)) { clearInterval(interval); resolve(); return; } if (attempts >= maxAttempts) { clearInterval(interval); reject(new Error("Failed to auto-close the panel.")); return; } const backdrop = document.querySelector(SELECTORS.overlayBackdrop); if (backdrop) backdrop.click(); attempts++; }, 100); }); } async function runFullSequence(textArea) { injectText(textArea, systemInstructions); try { await closePanel(); hideButton(); console.log(`[${SCRIPT_NAME}]: Sequence complete. Button hidden.`); } catch (error) { console.error(`[${SCRIPT_NAME}]: ${error.message}`); showToast(error.message, 'error'); } } function waitForContent(textArea) { return new Promise(resolve => { let attempts = 0; const maxAttempts = 50; const interval = setInterval(() => { if (textArea.value.trim() !== '' || attempts >= maxAttempts) { clearInterval(interval); resolve(); } attempts++; }, 10); }); } async function processPanel() { waitForElement(SELECTORS.systemInstructionsTextArea, async (textArea) => { await waitForContent(textArea); const pageText = textArea.value.trim(); const myText = systemInstructions.trim(); if (pageText === '') { await runFullSequence(textArea); } else if (pageText === myText) { showToast('Instructions already match!', 'success'); try { await closePanel(); hideButton(); } catch (e) { hideButton(); } } else { const shouldOverwrite = await showConfirmationModal({ title: 'Conflict Detected', message: 'The instructions field already has text. Do you want to overwrite it?' }); if (shouldOverwrite) { await runFullSequence(textArea); } else { try { await closePanel(); hideButton(); } catch(e) { hideButton(); } } } }); } function triggerInjectionSequence() { const panel = document.querySelector(SELECTORS.systemInstructionsTextArea); const button = document.querySelector(SELECTORS.systemInstructionsButton); if (panel) { processPanel(); } else if (button) { button.click(); processPanel(); } else { console.error(`[${SCRIPT_NAME}]: Could not find the system instructions button.`); showToast('Could not find the instructions button.', 'error'); } } function createTriggerButton() { const svgIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M15 4V2m0 18v-2m-7.5-12.5L4 4m16 16l-3.5-3.5M2 12h2m16 0h2M7.5 7.5L4 4m12.5 0l3.5 3.5"/><circle cx="12" cy="12" r="2"/></svg>`; const btn = document.createElement('button'); btn.id = 'ai-studio-injector-trigger'; btn.innerHTML = ` <div class="injector-close-btn">×</div> ${svgIcon} <span class="injector-trigger-text">INJECTOR</span> `; btn.title = 'Inject System Instructions (One-Shot)\nRight-click to edit your saved instructions.'; btn.addEventListener('click', triggerInjectionSequence); btn.addEventListener('contextmenu', e => { e.preventDefault(); editInstructions(); }); btn.querySelector('.injector-close-btn').addEventListener('click', async (e) => { e.stopPropagation(); const choice = await showHideConfirmationModal(); switch (choice) { case 'session': hideButton(); break; case 'forever': await GM_setValue(STORAGE_KEY_BUTTON_HIDDEN, true); hideButton(); break; case 'cancel': default: break; } }); document.body.appendChild(btn); } async function performInitialCheck() { console.log(`[${SCRIPT_NAME}]: Performing instant check...`); try { const subtitleElement = await new Promise((resolve, reject) => { waitForElement(SELECTORS.systemInstructionsSubtitle, resolve); setTimeout(() => reject(new Error('Subtitle element timeout')), 5000); }); const pageText = subtitleElement.textContent.trim(); const myText = systemInstructions.trim(); const isMatched = pageText === myText; console.log(`[${SCRIPT_NAME}]: Instant check result: ${isMatched ? 'Matched' : 'No Match'}`); return isMatched; } catch (error) { console.warn(`[${SCRIPT_NAME}]: Instant check failed: ${error.message}. Assuming button is needed.`); return false; } } async function runChatCheck() { const oldButton = document.getElementById('ai-studio-injector-trigger'); if (oldButton) oldButton.remove(); const isPermanentlyHidden = await GM_getValue(STORAGE_KEY_BUTTON_HIDDEN, false); if (isPermanentlyHidden) { console.log(`[${SCRIPT_NAME}]: Button is permanently hidden by user setting.`); return; } const alreadyInjected = await performInitialCheck(); if (!alreadyInjected) { createTriggerButton(); console.log(`[${SCRIPT_NAME}]: Ready for this chat. Button created.`); } else { console.log(`[${SCRIPT_NAME}]: Ready for this chat. Button not needed.`); } } function startNavigationObserver() { let lastUrl = location.href; const observer = new MutationObserver(() => { if (location.href !== lastUrl) { lastUrl = location.href; console.log(`[${SCRIPT_NAME}]: URL changed, re-running chat check.`); runChatCheck(); } }); observer.observe(document.body, { childList: true, subtree: true }); } // --- Script Initialization --- async function main() { systemInstructions = await loadOrPromptForInstructions(); if (systemInstructions === "---INJECTOR_DISABLED---") { console.log(`[${SCRIPT_NAME}]: Initial setup cancelled. Script is inactive.`); showToast("Injector is inactive until instructions are set (via Tampermonkey menu).", "info"); GM_registerMenuCommand("Set System Instructions", editInstructions); return; } GM_registerMenuCommand("Edit System Instructions", editInstructions); GM_registerMenuCommand("Inject Instructions (from Menu)", triggerInjectionSequence); GM_registerMenuCommand("► (Re)Show Button", async () => { await GM_setValue(STORAGE_KEY_BUTTON_HIDDEN, false); showToast('Button will be shown on next refresh.', 'info'); }); runChatCheck(); startNavigationObserver(); } main(); })();