Skip to content

Instantly share code, notes, and snippets.

@eric15342335
Last active May 22, 2025 10:34
Show Gist options
  • Save eric15342335/d6cce128f946d81e989d84cce96906c8 to your computer and use it in GitHub Desktop.
Save eric15342335/d6cce128f946d81e989d84cce96906c8 to your computer and use it in GitHub Desktop.
ai-studio-easy-use.js
// ==UserScript==
// @name Google AI Studio easy use
// @namespace http://tampermonkey.net/
// @version 1.1.4-fork
// @description Automatically set Google AI Studio system prompt; Increase chat content font size; Toggle Grounding with Ctrl/Cmd + i. Dark theme support for script UI. 自动设置 Google AI Studio 的系统提示词;增大聊天内容字号;快捷键 Ctrl/Cmd + i 开关Grounding;脚本UI支持深色主题。
// @author Victor Cheng (Modified by User)
// @match https://aistudio.google.com/*
// @match https://ai.dev/*
// @grant none
// @license MIT
// @run-at document-end
// @downloadURL https://update.greasyfork.org/scripts/523344/Google%20AI%20Studio%20easy%20use.user.js
// @updateURL https://update.greasyfork.org/scripts/523344/Google%20AI%20Studio%20easy%20use.meta.js
// ==/UserScript==
(function() {
'use strict';
//=======================================
// 常量管理
//=======================================
const CONSTANTS = {
STORAGE_KEYS: {
SYSTEM_PROMPT: 'aiStudioSystemPrompt',
FONT_SIZE: 'aiStudioFontSize'
},
DEFAULTS: {
SYSTEM_PROMPT: '1. Answer in the same language as the question.\n2. If web search is necessary, always search in English.',
FONT_SIZE: 'medium'
},
SELECTORS: {
NAVIGATION: '[role="navigation"]',
SYSTEM_INSTRUCTIONS: '.toolbar-system-instructions',
SYSTEM_INSTRUCTIONS_BUTTON: 'button[aria-label="System instructions"]',
SYSTEM_TEXTAREA: '.toolbar-system-instructions textarea',
NEW_CHAT_LINK: 'a[href$="/prompts/new_chat"]',
SEARCH_TOGGLE: '.search-as-a-tool-toggle button',
CHAT_LINKS: '.nav-sub-items-wrapper a',
CHAT_CONTENT_PARAGRAPHS: 'ms-cmark-node p', // Selector for chat content paragraphs
},
FONT_SIZES: [
{ value: 'small', label: 'Small', size: '12px' },
{ value: 'medium', label: 'Medium', size: '14px' },
{ value: 'large', label: 'Large', size: '16px' },
{ value: 'x-large', label: 'X-large', size: '18px' },
{ value: 'xx-large', label: 'XX-large', size: '20px' }
],
SHORTCUTS: {
TOGGLE_GROUNDING: { key: 'i', requiresCmd: true },
NEW_CHAT: { key: 'j', requiresCmd: true },
SWITCH_CHAT: { key: '/', requiresCmd: true }
},
THEME_COLORS: {
LIGHT: {
BACKGROUND_PRIMARY: 'white',
BACKGROUND_SECONDARY: '#f8f9fa',
BACKGROUND_INPUT: 'white',
TEXT_PRIMARY: '#202124',
TEXT_SECONDARY: '#3c4043',
TEXT_MUTED: '#5f6368',
TEXT_INPUT: '#202124',
BORDER_PRIMARY: '#dadce0',
BORDER_INPUT: '#dadce0',
ACCENT_PRIMARY: '#076eff',
ACCENT_PRIMARY_TEXT: 'white',
ACCENT_SECONDARY_BACKGROUND: '#e8f0fe',
BUTTON_SECONDARY_BG: '#f8f9fa',
BUTTON_SECONDARY_TEXT: '#3c4043',
BUTTON_SECONDARY_BORDER: '#dadce0',
KBD_BG: '#f1f3f4',
KBD_TEXT: '#202124',
KBD_BORDER: '#d1d5da',
DIALOG_SHADOW: '0 2px 10px rgba(0,0,0,0.1)',
},
DARK: {
BACKGROUND_PRIMARY: '#2d2d2d',
BACKGROUND_SECONDARY: '#3c3c3c',
BACKGROUND_INPUT: '#383838',
TEXT_PRIMARY: '#e0e0e0',
TEXT_SECONDARY: '#b0b0b0',
TEXT_MUTED: '#888888',
TEXT_INPUT: '#e0e0e0',
BORDER_PRIMARY: '#4a4a4a',
BORDER_INPUT: '#555555',
ACCENT_PRIMARY: '#2979ff',
ACCENT_PRIMARY_TEXT: '#ffffff',
ACCENT_SECONDARY_BACKGROUND: '#00439e',
BUTTON_SECONDARY_BG: '#4a4a4a',
BUTTON_SECONDARY_TEXT: '#e0e0e0',
BUTTON_SECONDARY_BORDER: '#5f5f5f',
KBD_BG: '#4f4f4f',
KBD_TEXT: '#e0e0e0',
KBD_BORDER: '#666666',
DIALOG_SHADOW: '0 4px 15px rgba(0,0,0,0.3)',
}
}
};
//=======================================
// 工具类
//=======================================
class DOMUtils {
static createElement(tag, attributes = {}, styles = {}) {
const element = document.createElement(tag);
Object.entries(attributes).forEach(([key, value]) => {
if (key === 'textContent') {
element.textContent = value;
} else if (key === 'className') {
element.className = value;
} else {
element.setAttribute(key, value);
}
});
Object.assign(element.style, styles);
return element;
}
static querySelector(selector, parent = document) {
return parent.querySelector(selector);
}
static querySelectorAll(selector, parent = document) {
return parent.querySelectorAll(selector);
}
static isElementVisible(element) {
if (!element) return false;
const style = window.getComputedStyle(element);
return style.display !== 'none' && style.visibility !== 'hidden' && element.offsetParent !== null;
}
static async waitForElement(selector, timeout = 3000, parent = document) {
return new Promise((resolve) => {
const intervalTime = 100;
let elapsedTime = 0;
const interval = setInterval(() => {
const element = DOMUtils.querySelector(selector, parent);
if (element && DOMUtils.isElementVisible(element)) {
clearInterval(interval);
resolve(element);
} else if (elapsedTime >= timeout) {
clearInterval(interval);
resolve(null); // Resolve with null if timeout
}
elapsedTime += intervalTime;
}, intervalTime);
});
}
}
class StyleManager {
static createStyleSheet(id, css) {
let style = document.getElementById(id);
if (!style) {
style = DOMUtils.createElement('style', { id });
document.head.appendChild(style);
}
style.textContent = css;
return style;
}
static updateFontSize(size) {
const fontSize = CONSTANTS.FONT_SIZES.find(s => s.value === size)?.size || CONSTANTS.DEFAULTS.FONT_SIZE.size;
this.createStyleSheet('aiStudioCustomFontSizeStyle', `
${CONSTANTS.SELECTORS.CHAT_CONTENT_PARAGRAPHS} {
font-size: ${fontSize} !important;
}
`);
}
}
class SystemPromptManager {
static async isTAAccessible(textarea) {
return new Promise(resolve => {
if (!textarea || !DOMUtils.isElementVisible(textarea) || textarea.disabled || textarea.readOnly) {
resolve(false);
}
const isActiveElement = document.activeElement === textarea;
if (isActiveElement) {
resolve(true);
return;
}
const originalActiveElement = document.activeElement;
textarea.focus();
setTimeout(() => {
const focused = document.activeElement === textarea;
if (originalActiveElement && typeof originalActiveElement.focus === 'function') {
originalActiveElement.focus();
} else {
textarea.blur();
}
resolve(focused);
}, 50);
});
}
static async update(prompt) {
console.log("SystemPromptManager: Attempting to update system prompt.");
const systemInstructionsButton = await DOMUtils.waitForElement(CONSTANTS.SELECTORS.SYSTEM_INSTRUCTIONS_BUTTON, 2000);
if (!systemInstructionsButton) {
console.warn("SystemPromptManager: System instructions button not found or not visible. Cannot proceed with prompt update.");
return false;
}
console.log("SystemPromptManager: System instructions button found.");
let textarea = DOMUtils.querySelector(CONSTANTS.SELECTORS.SYSTEM_TEXTAREA);
let isTextareaCurrentlyAccessible = textarea ? await SystemPromptManager.isTAAccessible(textarea) : false;
// Case 1: Textarea is accessible AND prompt is already correct.
if (isTextareaCurrentlyAccessible && textarea.value === prompt) {
console.log("SystemPromptManager: Textarea accessible and prompt already matches. No update needed.");
return true;
}
// Case 2: Textarea is not accessible (panel likely closed), so we need to open it.
// Or, it is accessible, but the prompt is different (handled by falling through to Case 3).
if (!isTextareaCurrentlyAccessible) {
console.log("SystemPromptManager: Textarea not accessible. Clicking button to open panel.");
systemInstructionsButton.click();
const maxAttempts = 30; // 30 * 100ms = 3 seconds
let attempts = 0;
let panelOpenedAndTextareaReady = false;
while (attempts < maxAttempts) {
textarea = DOMUtils.querySelector(CONSTANTS.SELECTORS.SYSTEM_TEXTAREA); // Re-query inside loop
if (textarea && await SystemPromptManager.isTAAccessible(textarea)) {
isTextareaCurrentlyAccessible = true; // Mark as accessible now
panelOpenedAndTextareaReady = true;
console.log("SystemPromptManager: Textarea became accessible after click.");
break;
}
await new Promise(resolve => setTimeout(resolve, 100));
attempts++;
}
if (!panelOpenedAndTextareaReady) {
console.warn("SystemPromptManager: Failed to make textarea accessible after clicking button.");
return false; // Failed to open panel or find/access textarea
}
// After opening, it's possible the prompt is now correct (e.g., if opening triggered a default fill or a race condition resolved)
// or the textarea is now ready for update.
// We must use the 'textarea' variable that was confirmed inside the loop.
if (textarea && textarea.value === prompt) {
console.log("SystemPromptManager: Prompt matches after opening panel. No further update needed.");
return true;
}
}
// Case 3: Textarea is now accessible (either initially or after a successful click-to-open),
// and prompt needs to be set or updated.
if (textarea && isTextareaCurrentlyAccessible) { // 'isTextareaCurrentlyAccessible' should be true if we passed Case 2 successfully
console.log("SystemPromptManager: Textarea accessible. Setting/updating prompt value.");
textarea.focus(); // Ensure focus before changing value
textarea.value = prompt;
textarea.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
textarea.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));
console.log("SystemPromptManager: System prompt updated and events dispatched.");
// We will not automatically close the panel here. If the script opened it to set the prompt,
// and the prompt is now set, the main goal is achieved.
// Repeated calls should be caught by the "prompt already matches" check earlier.
return true;
} else {
// This state implies that even after attempting to open the panel, the textarea isn't usable.
console.warn("SystemPromptManager: Textarea not accessible when attempting to set value. This might indicate a persistent UI issue.");
return false;
}
}
}
//=======================================
// 功能类
//=======================================
class ShortcutManager {
constructor() {
this.currentChatIndex = 0;
this.bindGlobalShortcuts();
}
bindGlobalShortcuts() {
window.addEventListener('keydown', (e) => this.handleKeydown(e), {
capture: true,
passive: false
});
}
handleKeydown(e) {
const isCmdOrCtrl = e.metaKey || e.ctrlKey;
if (!isCmdOrCtrl) return;
const key = e.key.toLowerCase();
const shortcut = Object.entries(CONSTANTS.SHORTCUTS)
.find(([_, value]) => value.key === key && value.requiresCmd);
if (!shortcut) return;
e.preventDefault();
e.stopPropagation();
switch(shortcut[0]) {
case 'TOGGLE_GROUNDING':
this.toggleGrounding();
break;
case 'NEW_CHAT':
this.createNewChat();
break;
case 'SWITCH_CHAT':
this.switchToNextChat();
break;
}
}
toggleGrounding() {
const searchToggle = DOMUtils.querySelector(CONSTANTS.SELECTORS.SEARCH_TOGGLE);
searchToggle?.click();
}
createNewChat() {
const newChatLink = DOMUtils.querySelector(CONSTANTS.SELECTORS.NEW_CHAT_LINK);
if (newChatLink) {
newChatLink.click();
this.currentChatIndex = 0;
}
}
switchToNextChat() {
const chatLinks = DOMUtils.querySelectorAll(CONSTANTS.SELECTORS.CHAT_LINKS);
if (chatLinks.length > 0) {
this.currentChatIndex = (this.currentChatIndex + 1) % chatLinks.length;
chatLinks[this.currentChatIndex].click();
}
}
}
//=======================================
// UI相关类
//=======================================
class UIComponents {
static createSettingLink() {
return DOMUtils.createElement('a',
{ textContent: '⚙️', className: 'easy-use-settings' },
{
display: 'flex',
color: CONSTANTS.THEME_COLORS.LIGHT.ACCENT_PRIMARY,
textDecoration: 'none',
fontSize: '20px',
marginBottom: '20px',
cursor: 'pointer',
'align-self': 'center'
}
);
}
static createShortcutsSection(colors) {
const shortcutsSection = DOMUtils.createElement('div', {}, {
marginBottom: '24px',
padding: '12px',
background: colors.BACKGROUND_SECONDARY,
borderRadius: '4px'
});
const shortcutsTitle = DOMUtils.createElement('div',
{ textContent: 'Keyboard Shortcuts' },
{
fontWeight: '500',
marginBottom: '8px',
color: colors.TEXT_PRIMARY
}
);
const shortcutsList = DOMUtils.createElement('div', {}, {
fontSize: '14px',
color: colors.TEXT_MUTED
});
const shortcuts = [
{ key: 'Ctrl/Cmd + i', description: 'Toggle Grounding' },
{ key: 'Ctrl/Cmd + j', description: 'New Chat' },
{ key: 'Ctrl/Cmd + /', description: 'Switch Recent Chats' }
];
shortcuts.forEach(({ key, description }) => {
const shortcutItem = DOMUtils.createElement('div', {}, { marginBottom: '4px'});
shortcutItem.textContent = '• ';
const kbd = DOMUtils.createElement('kbd', { textContent: key }, {
padding: '2px 6px',
border: `1px solid ${colors.KBD_BORDER}`,
borderRadius: '3px',
background: colors.KBD_BG,
color: colors.KBD_TEXT,
fontFamily: 'monospace',
fontSize: '0.9em',
margin: '0 2px'
});
const text = document.createTextNode(`: ${description}`);
shortcutItem.appendChild(kbd);
shortcutItem.appendChild(text);
shortcutsList.appendChild(shortcutItem);
});
shortcutsSection.appendChild(shortcutsTitle);
shortcutsSection.appendChild(shortcutsList);
return shortcutsSection;
}
}
class DialogManager {
constructor(settingsManager) {
this.settingsManager = settingsManager;
this.dialog = null;
this.overlay = null;
this.currentColors = CONSTANTS.THEME_COLORS.LIGHT;
}
createOverlay() {
return DOMUtils.createElement('div', {}, {
position: 'fixed',
top: '0',
left: '0',
width: '100%',
height: '100%',
background: 'rgba(0,0,0,0.5)',
zIndex: '9999'
});
}
createDialog() {
const settings = this.settingsManager.getSettings();
const colors = this.currentColors;
const dialog = DOMUtils.createElement('div', {}, {
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
background: colors.BACKGROUND_PRIMARY,
padding: '30px',
borderRadius: '8px',
boxShadow: colors.DIALOG_SHADOW,
zIndex: '10000',
minWidth: '450px',
maxWidth: '700px',
width: '50vw'
});
const title = DOMUtils.createElement('h2',
{ textContent: '⚙️ Easy Use Settings' },
{
margin: '0 0 20px 0',
fontSize: '18px',
color: colors.TEXT_PRIMARY
}
);
dialog.appendChild(title);
const promptSection = this.createPromptSection(settings, colors);
dialog.appendChild(promptSection);
const fontSection = this.createFontSection(settings, colors);
dialog.appendChild(fontSection);
dialog.appendChild(UIComponents.createShortcutsSection(colors));
const buttonContainer = this.createButtonContainer(colors);
dialog.appendChild(buttonContainer);
return dialog;
}
createPromptSection(settings, colors) {
const section = DOMUtils.createElement('div', {}, {
marginBottom: '24px'
});
const label = DOMUtils.createElement('label',
{ textContent: 'Global System Prompt' },
{
display: 'block',
marginBottom: '8px',
fontWeight: '500',
color: colors.TEXT_PRIMARY
}
);
const textarea = document.createElement('textarea');
textarea.value = settings.systemPrompt;
Object.assign(textarea.style, {
width: '100%',
minHeight: '100px',
marginBottom: '8px',
padding: '8px',
border: `1px solid ${colors.BORDER_INPUT}`,
borderRadius: '4px',
fontFamily: 'inherit',
resize: 'vertical',
background: colors.BACKGROUND_INPUT,
color: colors.TEXT_INPUT
});
textarea.spellcheck = false;
const resetButton = DOMUtils.createElement('button',
{ textContent: 'Reset to Default' },
{
padding: '4px 8px',
backgroundColor: colors.BUTTON_SECONDARY_BG,
color: colors.BUTTON_SECONDARY_TEXT,
border: `1px solid ${colors.BUTTON_SECONDARY_BORDER}`,
borderRadius: '4px',
cursor: 'pointer',
fontSize: '12px',
marginBottom: '16px'
}
);
resetButton.addEventListener('click', () => {
textarea.value = CONSTANTS.DEFAULTS.SYSTEM_PROMPT;
});
section.appendChild(label);
section.appendChild(textarea);
section.appendChild(resetButton);
return section;
}
createFontSection(settings, colors) {
const section = DOMUtils.createElement('div', {}, {
marginBottom: '24px'
});
const label = DOMUtils.createElement('label',
{ textContent: 'Font Size' },
{
display: 'block',
marginBottom: '8px',
fontWeight: '500',
color: colors.TEXT_PRIMARY
}
);
const buttonGroup = DOMUtils.createElement('div',
{ className: 'font-button-group' },
{
display: 'flex',
gap: '8px',
width: '100%'
}
);
CONSTANTS.FONT_SIZES.forEach(size => {
const button = DOMUtils.createElement('button',
{
type: 'button',
value: size.value,
textContent: size.label,
title: `${size.label} (${size.size})`
},
{
...this.getFontButtonStyles(size.value === settings.fontSize, colors),
fontSize: size.size
}
);
if (size.value === settings.fontSize) {
button.setAttribute('data-selected', 'true');
}
button.addEventListener('click', () => this.handleFontButtonClick(button, buttonGroup, colors));
buttonGroup.appendChild(button);
});
section.appendChild(label);
section.appendChild(buttonGroup);
return section;
}
getFontButtonStyles(isSelected, colors) {
return {
flex: '1',
padding: '8px',
border: `1px solid ${isSelected ? colors.ACCENT_PRIMARY : colors.BORDER_PRIMARY}`,
borderRadius: '4px',
background: isSelected ? colors.ACCENT_SECONDARY_BACKGROUND : colors.BACKGROUND_INPUT,
color: isSelected ? colors.ACCENT_PRIMARY : colors.TEXT_SECONDARY,
cursor: 'pointer',
transition: 'all 0.2s',
fontFamily: 'inherit'
};
}
handleFontButtonClick(clickedButton, buttonGroup, colors) {
buttonGroup.querySelectorAll('button').forEach(btn => {
const isThisButton = btn === clickedButton;
Object.assign(btn.style, {
...this.getFontButtonStyles(isThisButton, colors),
fontSize: CONSTANTS.FONT_SIZES.find(s => s.value === btn.value)?.size
});
if (isThisButton) {
btn.setAttribute('data-selected', 'true');
} else {
btn.removeAttribute('data-selected');
}
});
}
createButtonContainer(colors) {
const container = DOMUtils.createElement('div', {
className: 'dialog-buttons'
}, {
display: 'flex',
gap: '10px',
justifyContent: 'flex-end'
});
const saveButton = DOMUtils.createElement('button', {
className: 'save-button',
textContent: 'Save'
}, {
padding: '8px 16px',
backgroundColor: colors.ACCENT_PRIMARY,
color: colors.ACCENT_PRIMARY_TEXT,
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontWeight: '500'
});
const cancelButton = DOMUtils.createElement('button', {
className: 'cancel-button',
textContent: 'Cancel'
}, {
padding: '8px 16px',
backgroundColor: colors.BUTTON_SECONDARY_BG,
color: colors.BUTTON_SECONDARY_TEXT,
border: `1px solid ${colors.BUTTON_SECONDARY_BORDER}`,
borderRadius: '4px',
cursor: 'pointer',
fontWeight: '500'
});
container.appendChild(cancelButton);
container.appendChild(saveButton);
return container;
}
show() {
const isSystemDark = document.body.classList.contains('dark-theme');
this.currentColors = isSystemDark ? CONSTANTS.THEME_COLORS.DARK : CONSTANTS.THEME_COLORS.LIGHT;
this.overlay = this.createOverlay();
this.dialog = this.createDialog();
document.body.appendChild(this.overlay);
document.body.appendChild(this.dialog);
this.bindEvents();
}
hide() {
if (this.dialog && this.overlay) {
this.dialog.ownerDocument.removeEventListener('keydown', this.handleEscKey);
document.body.removeChild(this.dialog);
document.body.removeChild(this.overlay);
this.dialog = null;
this.overlay = null;
}
}
bindEvents() {
const saveButton = this.dialog.querySelector('.save-button');
const cancelButton = this.dialog.querySelector('.cancel-button');
if (saveButton && cancelButton) {
saveButton.addEventListener('click', () => this.handleSave());
cancelButton.addEventListener('click', () => this.hide());
this.dialog.ownerDocument.addEventListener('keydown', this.handleEscKey);
}
if (this.overlay) {
this.overlay.addEventListener('click', () => this.hide());
}
}
handleEscKey = (event) => {
if (event.key === 'Escape') {
this.hide();
}
}
handleSave() {
const textarea = this.dialog.querySelector('textarea');
const selectedFontButton = this.dialog.querySelector('button[data-selected="true"]');
if (!textarea || !selectedFontButton) return;
const newSettings = {
systemPrompt: textarea.value.trim(),
fontSize: selectedFontButton.value
};
this.settingsManager.saveSettings(newSettings);
StyleManager.updateFontSize(newSettings.fontSize);
SystemPromptManager.update(newSettings.systemPrompt); // Re-apply/verify system prompt
this.hide();
}
}
//=======================================
// 核心管理器类
//=======================================
class SettingsManager {
constructor() {
this.settings = this.loadSettings();
}
loadSettings() {
return {
systemPrompt: localStorage.getItem(CONSTANTS.STORAGE_KEYS.SYSTEM_PROMPT) || CONSTANTS.DEFAULTS.SYSTEM_PROMPT,
fontSize: localStorage.getItem(CONSTANTS.STORAGE_KEYS.FONT_SIZE) || CONSTANTS.DEFAULTS.FONT_SIZE
};
}
saveSettings(settings) {
localStorage.setItem(CONSTANTS.STORAGE_KEYS.SYSTEM_PROMPT, settings.systemPrompt);
localStorage.setItem(CONSTANTS.STORAGE_KEYS.FONT_SIZE, settings.fontSize);
this.settings = settings;
}
getSettings() {
return { ...this.settings };
}
}
class AppManager {
constructor() {
this.settingsManager = new SettingsManager();
this.shortcutManager = new ShortcutManager();
this.dialogManager = new DialogManager(this.settingsManager);
}
init() {
this.initSettingsLink();
this.applyInitialSettings();
this.observeRouteChanges();
}
initSettingsLink() {
const link = UIComponents.createSettingLink();
link.addEventListener('click', (e) => {
e.preventDefault();
this.dialogManager.show();
});
this.observeNavigation(link);
}
observeNavigation(link) {
const observer = new MutationObserver((mutationsList, obs) => {
const nav = DOMUtils.querySelector(CONSTANTS.SELECTORS.NAVIGATION);
if (nav && !nav.querySelector('.easy-use-settings')) {
if (!link.parentNode) {
link.classList.add('easy-use-settings');
nav.insertBefore(link, nav.firstChild);
}
} else if (nav && nav.querySelector('.easy-use-settings') && link.parentNode !== nav) {
const oldLink = nav.querySelector('.easy-use-settings');
if (oldLink) oldLink.remove();
nav.insertBefore(link, nav.firstChild);
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
applyInitialSettings() {
const settings = this.settingsManager.getSettings();
StyleManager.updateFontSize(settings.fontSize);
this.initSystemPrompt(settings.systemPrompt);
}
async initSystemPrompt(prompt, maxRetries = 15, interval = 1000) {
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
console.log("Initializing system prompt...");
for (let i = 0; i < maxRetries; i++) {
if (document.readyState !== 'complete' && document.readyState !== 'interactive') {
console.log(`Attempt ${i + 1}: Document not ready. Waiting...`);
await wait(interval);
continue;
}
// Check for the button first, as SystemPromptManager.update relies on it.
// SystemPromptManager.update has its own waitForElement for the button,
// but this initial check ensures we don't even try if the button isn't there yet.
const systemInstructionsButton = await DOMUtils.waitForElement(CONSTANTS.SELECTORS.SYSTEM_INSTRUCTIONS_BUTTON, interval / 2);
if (!systemInstructionsButton) {
console.log(`Attempt ${i + 1}: System instructions button not found or not visible by AppManager. Retrying...`);
await wait(interval);
continue;
}
console.log(`Attempt ${i + 1}: System instructions button found by AppManager. Proceeding to SystemPromptManager.update.`);
const success = await SystemPromptManager.update(prompt);
if (success) {
console.log("System prompt initialization successful on attempt " + (i + 1));
return;
}
console.log(`Attempt ${i + 1} to set system prompt via SystemPromptManager.update failed or indicated no update needed but was part of init. Retrying...`);
await wait(interval);
}
console.error(`Failed to initialize system prompt after ${maxRetries} attempts.`);
}
observeRouteChanges() {
let lastUrl = location.href;
const observer = new MutationObserver(() => {
const url = location.href;
if (url !== lastUrl) {
lastUrl = url;
console.log('Route changed to:', url);
// When route changes, re-apply settings, especially the system prompt.
setTimeout(() => {
// applyInitialSettings calls initSystemPrompt, which now has robust checks.
this.applyInitialSettings();
}, 500); // Delay to allow new page elements to settle.
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
}
// Startup check
if (document.readyState === 'complete' || document.readyState === 'interactive') {
new AppManager().init();
} else {
window.addEventListener('DOMContentLoaded', () => {
new AppManager().init();
});
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment