// ==UserScript==
// @name Google AI Studio - Auto Settings
// @namespace https://github.com/ai-studio-tools
// @version 7.1
// @description Automatically configures model parameters (Temperature, Top-P, Media Resolution) in Google AI Studio with a clean, modern interface
// @author AI Studio Tools
// @match https://aistudio.google.com/prompts/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=aistudio.google.com
// @grant none
// @run-at document-end
// @license MIT
// @homepageURL https://github.com/Stranmor/google-ai-studio-auto-settings
// @supportURL https://github.com/Stranmor/google-ai-studio-auto-settings/issues
// ==/UserScript==
(function () {
"use strict";
// ==================== CONFIGURATION ====================
const CONFIG = {
settings: {
temperature: 0.7,
topP: 0.0,
mediaResolution: "Low",
},
execution: {
debug: true,
maxAttempts: 30,
retryDelay: 2000,
pageLoadTimeout: 60000,
},
selectors: {
temperature: {
container: '[data-test-id="temperatureSliderContainer"]',
title: "Temperature",
},
topP: {
titles: ["Top P", "Top-P"],
},
mediaResolution: {
container: '[data-test-id="mediaResolution"]',
title: "Media resolution",
},
promptInput: "ms-autosize-textarea textarea.textarea",
},
storage: {
positionKey: "as-panel-position",
},
};
// ==================== UTILITIES ====================
const Logger = {
log: (message, ...args) =>
CONFIG.execution.debug && console.log(`[AS] ${message}`, ...args),
warn: (message, ...args) =>
CONFIG.execution.debug && console.warn(`[AS] ${message}`, ...args),
error: (message, ...args) =>
CONFIG.execution.debug && console.error(`[AS] ${message}`, ...args),
};
const TimeUtils = {
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
};
const StorageUtils = {
savePosition: (x, y) => {
try {
localStorage.setItem(
CONFIG.storage.positionKey,
JSON.stringify({ x, y }),
);
Logger.log(`Position saved: x=${x}, y=${y}`);
} catch (e) {
Logger.error("Failed to save position:", e);
}
},
loadPosition: () => {
try {
const saved = localStorage.getItem(CONFIG.storage.positionKey);
if (saved) {
const pos = JSON.parse(saved);
Logger.log(`Position loaded: x=${pos.x}, y=${pos.y}`);
return pos;
}
} catch (e) {
Logger.error("Failed to load position:", e);
}
return { x: 20, y: 20 }; // default bottom-left
},
};
// ==================== DOM INTERACTION ====================
class DOMInteractor {
static findElementByText(selector, text) {
const elements = document.querySelectorAll(selector);
for (const el of elements) {
if (el.textContent.trim() === text) {
return el.closest(".settings-item-column, .settings-item");
}
}
return null;
}
static click(element) {
if (!element) return false;
const eventOptions = { bubbles: true, cancelable: true, view: window };
element.dispatchEvent(new MouseEvent("mousedown", eventOptions));
element.focus?.();
element.dispatchEvent(new MouseEvent("mouseup", eventOptions));
element.dispatchEvent(new MouseEvent("click", eventOptions));
return true;
}
static setValue(element, value) {
if (!element) return false;
element.focus();
const descriptor = Object.getOwnPropertyDescriptor(
HTMLInputElement.prototype,
"value",
);
descriptor?.set?.call(element, value);
["input", "change"].forEach((eventType) =>
element.dispatchEvent(new Event(eventType, { bubbles: true })),
);
element.blur();
return true;
}
static isPageLoaded() {
return (
document.querySelector(CONFIG.selectors.promptInput) !== null &&
document.querySelector("h3") !== null
);
}
static restorePromptFocus() {
const promptInput = document.querySelector(CONFIG.selectors.promptInput);
if (promptInput) {
setTimeout(() => {
promptInput.focus();
Logger.log("Focus restored to prompt input");
}, 100);
return true;
}
Logger.warn("Prompt input not found for focus restore");
return false;
}
}
// ==================== SETTINGS MANAGERS ====================
class BaseSettingManager {
constructor(name) {
this.name = name;
this.isApplied = false;
}
async check() {
throw new Error("Method check() must be implemented");
}
async apply() {
throw new Error("Method apply() must be implemented");
}
reset() {
this.isApplied = false;
}
log(message, level = "log") {
Logger[level](`[${this.name}] ${message}`);
}
}
class TemperatureManager extends BaseSettingManager {
constructor() {
super("Temperature");
}
async check(targetValue) {
const container = this._findContainer();
if (!container) return false;
const input = container.querySelector('input[type="number"]');
if (!input) return false;
const isMatch = Math.abs(parseFloat(input.value) - targetValue) < 0.001;
if (isMatch) {
this.isApplied = true;
this.log(`Already set to ${targetValue}`);
}
return isMatch;
}
async apply(value) {
if (this.isApplied) return true;
const container = this._findContainer();
if (!container) {
this.log("Container not found", "warn");
return false;
}
const numInput = container.querySelector('input[type="number"]');
const rangeInput = container.querySelector('input[type="range"]');
if (!numInput || !rangeInput) {
this.log("Input elements not found", "warn");
return false;
}
DOMInteractor.click(numInput);
DOMInteractor.setValue(numInput, value);
DOMInteractor.setValue(rangeInput, value);
this.isApplied = await this.check(value);
this.log(
this.isApplied
? `Successfully set to ${value}`
: `Failed to set to ${value}`,
this.isApplied ? "log" : "error",
);
return this.isApplied;
}
_findContainer() {
return (
DOMInteractor.findElementByText(
"h3",
CONFIG.selectors.temperature.title,
) || document.querySelector(CONFIG.selectors.temperature.container)
);
}
}
class TopPManager extends BaseSettingManager {
constructor() {
super("TopP");
}
async check(targetValue) {
const container = this._findContainer();
if (!container) return false;
const input = container.querySelector('input[type="number"]');
if (!input) return false;
const isMatch = Math.abs(parseFloat(input.value) - targetValue) < 0.001;
if (isMatch) {
this.isApplied = true;
this.log(`Already set to ${targetValue}`);
}
return isMatch;
}
async apply(value) {
if (this.isApplied) return true;
const container = this._findContainer();
if (!container) {
this.log("Container not found", "warn");
return false;
}
const numInput = container.querySelector('input[type="number"]');
const rangeInput = container.querySelector('input[type="range"]');
if (!numInput || !rangeInput) {
this.log("Input elements not found", "warn");
return false;
}
if (value === 0) {
this._removeMinConstraint(rangeInput);
this._removeMinConstraint(numInput);
}
DOMInteractor.click(numInput);
DOMInteractor.setValue(numInput, value);
DOMInteractor.setValue(rangeInput, value);
this.isApplied = await this.check(value);
this.log(
this.isApplied
? `Successfully set to ${value}`
: `Failed to set to ${value}`,
this.isApplied ? "log" : "error",
);
return this.isApplied;
}
_findContainer() {
for (const title of CONFIG.selectors.topP.titles) {
const container = DOMInteractor.findElementByText("h3", title);
if (container) return container;
}
return null;
}
_removeMinConstraint(input) {
if (input) {
input.removeAttribute("min");
input.min = "0";
}
}
}
class MediaResolutionManager extends BaseSettingManager {
constructor() {
super("MediaResolution");
}
async check(targetValue) {
const container = this._findContainer();
if (!container) return false;
const select = container.querySelector("mat-select");
if (!select) return false;
const currentValue = select
.querySelector(".mat-mdc-select-value-text span")
?.textContent?.trim();
const isMatch = currentValue === targetValue;
if (isMatch) {
this.isApplied = true;
this.log(`Already set to ${targetValue}`);
}
return isMatch;
}
async apply(value) {
if (this.isApplied) return true;
const container = this._findContainer();
if (!container) {
this.log("Container not found", "warn");
return false;
}
const select = container.querySelector("mat-select");
if (!select) {
this.log("Select element not found", "warn");
return false;
}
DOMInteractor.click(select);
await TimeUtils.sleep(100);
const option = Array.from(document.querySelectorAll("mat-option")).find(
(opt) =>
opt
.querySelector(".mdc-list-item__primary-text")
?.textContent?.trim() === value,
);
if (!option) {
this.log(`Option "${value}" not found`, "error");
DOMInteractor.click(document.body);
return false;
}
DOMInteractor.click(option);
await TimeUtils.sleep(100);
this.isApplied = await this.check(value);
this.log(
this.isApplied
? `Successfully set to ${value}`
: `Failed to set to ${value}`,
this.isApplied ? "log" : "error",
);
return this.isApplied;
}
_findContainer() {
return (
DOMInteractor.findElementByText(
"h3",
CONFIG.selectors.mediaResolution.title,
) || document.querySelector(CONFIG.selectors.mediaResolution.container)
);
}
}
// ==================== ORCHESTRATOR ====================
class SettingsOrchestrator {
constructor() {
this.managers = [
new TemperatureManager(),
new TopPManager(),
new MediaResolutionManager(),
];
}
async waitForPageLoad() {
const startTime = Date.now();
Logger.log("Waiting for page to load...");
while (Date.now() - startTime < CONFIG.execution.pageLoadTimeout) {
if (DOMInteractor.isPageLoaded()) {
Logger.log("Page loaded successfully");
return true;
}
await TimeUtils.sleep(500);
}
Logger.warn("Page load timeout, but continuing anyway...");
return false;
}
async applyAll() {
await this.waitForPageLoad();
const { temperature, topP, mediaResolution } = CONFIG.settings;
Logger.log("Starting settings application");
for (
let attempt = 1;
attempt <= CONFIG.execution.maxAttempts;
attempt++
) {
Logger.log(`Attempt ${attempt}/${CONFIG.execution.maxAttempts}`);
if (!this.managers[0].isApplied)
await this.managers[0].apply(temperature);
if (!this.managers[1].isApplied) await this.managers[1].apply(topP);
if (!this.managers[2].isApplied)
await this.managers[2].apply(mediaResolution);
if (this.isComplete()) {
Logger.log("All settings applied successfully");
DOMInteractor.restorePromptFocus();
return true;
}
if (attempt < CONFIG.execution.maxAttempts) {
await TimeUtils.sleep(CONFIG.execution.retryDelay);
}
}
Logger.warn("Failed to apply all settings after maximum attempts");
DOMInteractor.restorePromptFocus();
return false;
}
isComplete() {
return this.managers.every((m) => m.isApplied);
}
reset() {
this.managers.forEach((m) => m.reset());
}
getStatus() {
return {
total: this.managers.length,
applied: this.managers.filter((m) => m.isApplied).length,
pending: this.managers.filter((m) => !m.isApplied).map((m) => m.name),
};
}
}
// ==================== UI COMPONENT ====================
class UIComponent {
constructor(orchestrator) {
this.orchestrator = orchestrator;
this.panel = null;
this.isDragging = false;
this.dragStartX = 0;
this.dragStartY = 0;
this.panelStartX = 0;
this.panelStartY = 0;
}
render() {
this._injectStyles();
const panel = document.createElement("div");
panel.id = "as-panel";
panel.className = "as-panel--loading";
const savedPos = StorageUtils.loadPosition();
panel.style.left = `${savedPos.x}px`;
panel.style.bottom = `${savedPos.y}px`;
panel.innerHTML = `
<div id="as-status">⏳</div>
<div id="as-tooltip">Loading...</div>
`;
document.body.appendChild(panel);
this.panel = panel;
this._attachDragListeners();
Logger.log("UI rendered successfully");
}
_attachDragListeners() {
const status = this.panel.querySelector("#as-status");
status.addEventListener("mousedown", (e) => this._onDragStart(e));
document.addEventListener("mousemove", (e) => this._onDragMove(e));
document.addEventListener("mouseup", (e) => this._onDragEnd(e));
// Touch support
status.addEventListener("touchstart", (e) => this._onDragStart(e));
document.addEventListener("touchmove", (e) => this._onDragMove(e));
document.addEventListener("touchend", (e) => this._onDragEnd(e));
}
_onDragStart(e) {
e.preventDefault();
this.isDragging = true;
const clientX = e.clientX || e.touches?.[0]?.clientX || 0;
const clientY = e.clientY || e.touches?.[0]?.clientY || 0;
this.dragStartX = clientX;
this.dragStartY = clientY;
const rect = this.panel.getBoundingClientRect();
this.panelStartX = rect.left;
this.panelStartY = window.innerHeight - rect.bottom;
this.panel.style.transition = "none";
this.panel.style.cursor = "grabbing";
Logger.log("Drag started");
}
_onDragMove(e) {
if (!this.isDragging) return;
const clientX = e.clientX || e.touches?.[0]?.clientX || 0;
const clientY = e.clientY || e.touches?.[0]?.clientY || 0;
const deltaX = clientX - this.dragStartX;
const deltaY = -(clientY - this.dragStartY);
let newX = this.panelStartX + deltaX;
let newY = this.panelStartY + deltaY;
// Boundaries
const maxX = window.innerWidth - this.panel.offsetWidth;
const maxY = window.innerHeight - this.panel.offsetHeight;
newX = Math.max(0, Math.min(newX, maxX));
newY = Math.max(0, Math.min(newY, maxY));
this.panel.style.left = `${newX}px`;
this.panel.style.bottom = `${newY}px`;
}
_onDragEnd(e) {
if (!this.isDragging) return;
this.isDragging = false;
this.panel.style.transition = "";
this.panel.style.cursor = "pointer";
const rect = this.panel.getBoundingClientRect();
const finalX = rect.left;
const finalY = window.innerHeight - rect.bottom;
StorageUtils.savePosition(finalX, finalY);
Logger.log("Drag ended");
// Check if it was just a click (minimal movement)
const clientX = e.clientX || e.changedTouches?.[0]?.clientX || 0;
const clientY = e.clientY || e.changedTouches?.[0]?.clientY || 0;
const moveDistance = Math.sqrt(
Math.pow(clientX - this.dragStartX, 2) +
Math.pow(clientY - this.dragStartY, 2),
);
if (moveDistance < 5) {
this._handleClick();
}
}
async _handleClick() {
Logger.log("Panel clicked - reapplying settings");
this.orchestrator.reset();
await this.runApplication();
}
_injectStyles() {
const style = document.createElement("style");
style.id = "as-styles";
style.textContent = `
#as-panel {
position: fixed !important;
width: 28px !important;
height: 28px !important;
background: #ffffff !important;
border: 1px solid #d1d5db !important;
border-radius: 6px !important;
box-shadow: 0 1px 3px rgba(0,0,0,0.1) !important;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
z-index: 999999 !important;
cursor: pointer !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
transition: none !important;
user-select: none !important;
opacity: 0.85 !important;
}
#as-panel:hover {
opacity: 1 !important;
border-color: #9ca3af !important;
}
#as-panel.as-panel--success {
background-color: #ecfdf5 !important;
border-color: #10b981 !important;
}
#as-panel.as-panel--success #as-status {
color: #10b981 !important;
}
#as-panel.as-panel--error {
background-color: #fef2f2 !important;
border-color: #ef4444 !important;
}
#as-panel.as-panel--error #as-status {
color: #ef4444 !important;
}
#as-panel.as-panel--loading {
background-color: #eff6ff !important;
border-color: #3b82f6 !important;
}
#as-panel.as-panel--loading #as-status {
color: #3b82f6 !important;
}
#as-panel:hover #as-tooltip {
opacity: 1 !important;
visibility: visible !important;
transform: translateY(-50%) scale(1) !important;
}
#as-status {
font-size: 22px !important;
line-height: 1 !important;
transition: color 0.3s !important;
cursor: grab !important;
pointer-events: all !important;
}
#as-status:active {
cursor: grabbing !important;
}
#as-tooltip {
position: absolute !important;
left: 58px !important;
top: 50% !important;
transform: translateY(-50%) scale(0.95) !important;
background: #1f2937 !important;
color: #fff !important;
padding: 8px 14px !important;
border-radius: 8px !important;
font-size: 13px !important;
font-weight: 500 !important;
white-space: nowrap !important;
opacity: 0 !important;
visibility: hidden !important;
transition: all 0.2s ease-out !important;
pointer-events: none !important;
box-shadow: 0 4px 12px rgba(0,0,0,0.3) !important;
}
#as-tooltip::before {
content: '' !important;
position: absolute !important;
right: 100% !important;
top: 50% !important;
transform: translateY(-50%) !important;
border: 6px solid transparent !important;
border-right-color: #1f2937 !important;
}
`;
document.head.appendChild(style);
Logger.log("Styles injected");
}
async runApplication() {
this.updateStatus("loading");
const success = await this.orchestrator.applyAll();
this.updateStatus(success ? "success" : "error");
}
updateStatus(state) {
if (!this.panel) return;
const statusEl = this.panel.querySelector("#as-status");
const tooltipEl = this.panel.querySelector("#as-tooltip");
const status = this.orchestrator.getStatus();
this.panel.className = `as-panel--${state}`;
switch (state) {
case "loading":
statusEl.textContent = "⏳";
tooltipEl.textContent = `Applying... (${status.applied}/${status.total})`;
break;
case "success":
statusEl.textContent = "✓";
tooltipEl.textContent = "All settings applied!";
break;
case "error":
statusEl.textContent = "✗";
tooltipEl.textContent = `Failed! Pending: ${status.pending.join(", ")}`;
break;
}
Logger.log(`Status updated: ${state}`);
}
}
// ==================== APPLICATION ====================
class Application {
constructor() {
this.orchestrator = new SettingsOrchestrator();
this.ui = new UIComponent(this.orchestrator);
}
async initialize() {
try {
Logger.log("Initializing Auto Settings v7.1");
await TimeUtils.sleep(1000);
this.ui.render();
await this.ui.runApplication();
Logger.log("Initialization complete");
} catch (error) {
Logger.error("Initialization failed:", error);
}
}
}
// ==================== ENTRY POINT ====================
function init() {
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
new Application().initialize();
});
} else {
new Application().initialize();
}
}
init();
})();