-
-
Save LawfulLeah/e22a425dbb3e28c23ba12777ab03342f to your computer and use it in GitHub Desktop.
AI Studio patch to actually get the features users want (remade and with new additions via the help of Gemini 2.0 pro, Gemini Flash 2.0, Gemini Flash 2.0 Thinking, and Gemini Experimental 1206), along with some performance improvements.. Instructions: Paste in **in the devtool's console** after opening a prompt, and wait 5 seconds (for the scrip…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This script modifies Google's MakerSuite to add sliders for controlling | |
* Top K, Frequency Penalty, Presence Penalty, and Seed during text generation. | |
* It intercepts the serialization process to inject these parameters into | |
* the AI model's request payload. | |
* The Seed slider also gets a secondary button for generating a random seed. | |
* It also provides a checkbox to disable the frequency and presence penalties. | |
*/ | |
/** | |
* Regular expression to identify the null checker function within MakerSuite's code. | |
* @type {RegExp} | |
*/ | |
const NULL_CHECKER_REGEX = /function\(\){var a=new _\...,b=new ..;return _\.(..)\(a,..,1,b\)}/gm; | |
/** | |
* Default values for the control parameters. | |
* @type {object} | |
*/ | |
const DEFAULT_VALUES = { | |
topK: 40, | |
frequencyPenalty: 0.2, | |
presencePenalty: 0.2, | |
seed: 0, | |
penaltiesEnabled: true, // Default penalties to enabled. | |
seedEnabled: true, // Default seed to enabled | |
}; | |
/** | |
* Minimum and maximum values for the sliders. | |
* @type {object} | |
*/ | |
const SLIDER_BOUNDS = { | |
topK: {min: 1, max: 200}, | |
frequencyPenalty: {min: -2, max: 2}, | |
presencePenalty: {min: -2, max: 2}, | |
seed: {min: 0, max: 10000000000}, | |
}; | |
/** | |
* Name of the variable in the `window` object that will be used to store the control parameter values. | |
* @type {object} | |
*/ | |
const WINDOW_VARIABLE_NAMES = { | |
topK: "top_k", | |
frequencyPenalty: "frequency_penalty", | |
presencePenalty: "presence_penalty", | |
seed: "random_seed", | |
penaltiesEnabled: "penalties_enabled", | |
seedEnabled: "seed_enabled", // New variable to control the state of the seed. | |
}; | |
// --- Trusted Types Policy --- | |
let ttPolicy; | |
// Use a default policy that allows all HTML if trustedTypes is not supported | |
const defaultPolicy = { | |
createHTML: (string) => string, | |
}; | |
try { | |
if (typeof trustedTypes !== 'undefined') { | |
ttPolicy = trustedTypes.createPolicy('allow-all', { | |
createHTML: (string) => string, | |
}); | |
} else { | |
// Fallback: If trustedTypes is not supported, use the default policy. | |
ttPolicy = defaultPolicy; | |
console.warn("Trusted Types are not supported. Using a fallback policy."); | |
} | |
} catch (error){ | |
console.error("Could not create a trustedTypes policy. Most likely, it has already been created", error) | |
ttPolicy = defaultPolicy; // Ensure ttPolicy is *always* defined, use fallback | |
} | |
/** | |
* Extracts the name of the null checker function from MakerSuite's code. | |
* @returns {string|null} The name of the null checker function, or null if not found. | |
*/ | |
function findNullCheckerName() { | |
let nullCheckerName = null; | |
Object.keys(default_MakerSuite).find((key) => { | |
const value = default_MakerSuite[key]; | |
if (typeof value !== 'function') return false; | |
try { | |
const match = NULL_CHECKER_REGEX.exec(value.toString()); | |
if (match) { | |
nullCheckerName = match[1]; | |
return true; | |
} | |
} catch { | |
return false; | |
} | |
}); | |
return nullCheckerName; | |
} | |
/** | |
* Overrides the original null checker function to inject control parameters | |
* into the payload sent to the AI model during text generation. | |
* @param {string} nullCheckerName - The name of the null checker function. | |
*/ | |
function overrideNullChecker(nullCheckerName) { | |
// Check if default_MakerSuite exists and has the necessary property before proceeding | |
if (typeof default_MakerSuite === 'undefined' || !default_MakerSuite.hasOwnProperty(nullCheckerName)) { | |
console.error('default_MakerSuite or nullCheckerName is not defined. Nullchecker override aborted.'); | |
return; | |
} | |
const originalNullChecker = default_MakerSuite[nullCheckerName]; | |
// Check that original Nullchecker is, indeed, a function | |
if (typeof originalNullChecker !== 'function'){ | |
console.error("The Nullchecker is not a function! Nullchecker override aborted") | |
} | |
default_MakerSuite[nullCheckerName] = (a, b, c, d) => { | |
try { | |
const firstKey = Object.keys(a)[0]; | |
// Check if the payload is likely a text generation request | |
if ( | |
a[firstKey] !== undefined && | |
a[firstKey].length > 10 && | |
c === 16 && | |
a[firstKey].includes('text/plain') | |
) { | |
const modifiedPayload = originalNullChecker(a, b, c, d); | |
// Inject parameters | |
modifiedPayload[firstKey][6] = window[WINDOW_VARIABLE_NAMES.topK]; | |
// Conditionally inject seed | |
if (window[WINDOW_VARIABLE_NAMES.seedEnabled]) { | |
modifiedPayload[firstKey][2] = window[WINDOW_VARIABLE_NAMES.seed]; | |
} | |
// Conditionally inject penalties | |
if (window[WINDOW_VARIABLE_NAMES.penaltiesEnabled]) { | |
modifiedPayload[firstKey][10] = window[WINDOW_VARIABLE_NAMES.frequencyPenalty]; | |
modifiedPayload[firstKey][9] = window[WINDOW_VARIABLE_NAMES.presencePenalty]; | |
} | |
return modifiedPayload; | |
} | |
} catch (error) { | |
console.error("Error in the null checker override: ", error); | |
} | |
return originalNullChecker(a, b, c, d); | |
}; | |
} | |
/** | |
* Normalizes a value to a 0-1 range. | |
* @param {number} val - The value to normalize. | |
* @param {number} max - The maximum value of the range. | |
* @param {number} min - The minimum value of the range. | |
* @returns {number} The normalized value. | |
*/ | |
function normalize(val, max, min) { | |
return (val - min) / (max - min); | |
} | |
/** | |
* Creates an event handler for slider input events. | |
* Updates the slider's visual representation and the corresponding window variable. | |
* @param {HTMLElement} containerElement - The slider's container element. | |
* @param {object} bounds - The minimum and maximum values for the slider. | |
* @param {string} windowKey - The name of the window variable to update. | |
* @returns {(e: Event) => void} The event handler function. | |
*/ | |
function createSliderEventHandler(containerElement, bounds, windowKey) { | |
return (event) => { | |
let realValue = parseFloat(containerElement.querySelector('input').value); | |
const normalizedValue = normalize(realValue, bounds.max, bounds.min); | |
const sliderThumb = containerElement.querySelector('mat-slider-visual-thumb'); | |
const sliderTrack = containerElement.querySelector('.mdc-slider__track--active_fill'); | |
const numberInput = containerElement.querySelector('input[type="number"]'); | |
if (sliderThumb) { | |
const sliderWidth = containerElement.querySelector('mat-slider').getClientRects()[0].width; | |
const thumbPosition = sliderWidth * normalizedValue; | |
sliderThumb.style.transform = `translateX(${thumbPosition}px)`; | |
} | |
if (sliderTrack) { | |
sliderTrack.style.transform = `scaleX(${normalizedValue})`; | |
} | |
if (numberInput) { | |
numberInput.value = realValue; | |
} | |
window[windowKey] = realValue; | |
}; | |
} | |
/** | |
* Adds a new slider to the UI. | |
* @param {HTMLElement} originalSliderContainer - The container of the original slider to clone. | |
* @param {string} title - The title of the new slider. | |
* @param {object} bounds - The minimum and maximum values for the slider. | |
* @param {string} windowKey - The name of the window variable to update. | |
*/ | |
function addSlider(originalSliderContainer, title, bounds, windowKey) { | |
const newSliderContainer = originalSliderContainer.cloneNode(true); | |
newSliderContainer.querySelector('h3').innerText = title; | |
const inputElement = newSliderContainer.querySelector('input'); | |
inputElement.min = bounds.min; | |
inputElement.max = bounds.max; | |
inputElement.value = | |
DEFAULT_VALUES[ | |
Object.keys(WINDOW_VARIABLE_NAMES).find( | |
(key) => WINDOW_VARIABLE_NAMES[key] === windowKey | |
) | |
] || 0; | |
// Ensure integer values for Top K and Seed | |
if (windowKey === WINDOW_VARIABLE_NAMES.topK || windowKey === WINDOW_VARIABLE_NAMES.seed){ | |
inputElement.step = 1; | |
} else { | |
inputElement.step = 0.01; | |
} | |
const sliderEventHandler = createSliderEventHandler( | |
newSliderContainer, | |
bounds, | |
windowKey | |
); | |
inputElement.addEventListener('input', sliderEventHandler); | |
// Add an event listener to the number input box for manual changes | |
const numberInput = newSliderContainer.querySelector('input[type="number"]'); | |
if (numberInput) { | |
numberInput.addEventListener('change', (event) => { | |
const newValue = parseFloat(event.target.value); | |
inputElement.value = newValue; | |
sliderEventHandler(event); | |
}); | |
} | |
// Add a button for random seed generation below the Seed slider | |
if (windowKey === WINDOW_VARIABLE_NAMES.seed) { | |
const randomSeedButton = document.createElement('button'); | |
randomSeedButton.innerText = 'Randomize'; | |
randomSeedButton.style.display = 'block'; | |
randomSeedButton.style.marginTop = '10px'; | |
randomSeedButton.style.width = '95%'; | |
randomSeedButton.addEventListener('click', () => { | |
const randomSeed = | |
Math.floor(Math.random() * (bounds.max - bounds.min + 1)) + bounds.min; | |
inputElement.value = randomSeed; | |
if (numberInput){ | |
numberInput.value = randomSeed; | |
} | |
sliderEventHandler(); | |
}); | |
newSliderContainer.appendChild(randomSeedButton); | |
} | |
originalSliderContainer.parentElement.appendChild(newSliderContainer); | |
} | |
/** | |
* Adds a checkbox to enable/disable penalties. | |
* @param {HTMLElement} originalSliderContainer - The container of the original slider to use as a template | |
*/ | |
function addPenaltyCheckbox(originalSliderContainer){ | |
const newCheckboxContainer = originalSliderContainer.cloneNode(true); | |
newCheckboxContainer.querySelector('h3').innerText = "Enable" | |
const checkbox = document.createElement('input'); | |
checkbox.type = 'checkbox'; | |
checkbox.checked = DEFAULT_VALUES.penaltiesEnabled; // Set the checkbox's default state | |
checkbox.style.marginLeft = "10px" | |
const label = document.createElement('label'); | |
label.style.marginLeft = "5px" | |
label.appendChild(document.createTextNode(" use of Penalties?")); | |
const numberInput = newCheckboxContainer.querySelector('input[type="number"]'); | |
if (numberInput){ | |
numberInput.remove(); | |
} | |
const matSlider = newCheckboxContainer.querySelector('mat-slider') | |
if (matSlider){ | |
matSlider.remove() | |
} | |
const mdcSlider = newCheckboxContainer.querySelector('.mdc-slider') | |
if (mdcSlider){ | |
mdcSlider.remove(); | |
} | |
const h3Element = newCheckboxContainer.querySelector('h3') | |
h3Element.appendChild(checkbox) | |
h3Element.appendChild(label); | |
checkbox.addEventListener('change', () => { | |
window[WINDOW_VARIABLE_NAMES.penaltiesEnabled] = checkbox.checked; | |
}) | |
originalSliderContainer.parentElement.appendChild(newCheckboxContainer); | |
} | |
/** | |
* Adds a checkbox to enable/disable seed. | |
* @param {HTMLElement} originalSliderContainer - The container of the original slider to use as a template | |
*/ | |
function addSeedCheckbox(originalSliderContainer){ | |
const newCheckboxContainer = originalSliderContainer.cloneNode(true); | |
newCheckboxContainer.querySelector('h3').innerText = "Enable" | |
const checkbox = document.createElement('input'); | |
checkbox.type = 'checkbox'; | |
checkbox.checked = DEFAULT_VALUES.seedEnabled; // Set the checkbox's default state | |
checkbox.style.marginLeft = "10px" | |
const label = document.createElement('label'); | |
label.style.marginLeft = "5px" | |
label.appendChild(document.createTextNode("Manual Seed?")); | |
const numberInput = newCheckboxContainer.querySelector('input[type="number"]'); | |
if (numberInput){ | |
numberInput.remove(); | |
} | |
const matSlider = newCheckboxContainer.querySelector('mat-slider') | |
if (matSlider){ | |
matSlider.remove() | |
} | |
const mdcSlider = newCheckboxContainer.querySelector('.mdc-slider') | |
if (mdcSlider){ | |
mdcSlider.remove(); | |
} | |
const h3Element = newCheckboxContainer.querySelector('h3') | |
h3Element.appendChild(checkbox) | |
h3Element.appendChild(label); | |
checkbox.addEventListener('change', () => { | |
window[WINDOW_VARIABLE_NAMES.seedEnabled] = checkbox.checked; | |
}) | |
originalSliderContainer.parentElement.appendChild(newCheckboxContainer); | |
} | |
/** | |
* Initializes the control parameters in the window object with default values. | |
*/ | |
function initializeWindowVariables() { | |
for (const key in WINDOW_VARIABLE_NAMES) { | |
window[WINDOW_VARIABLE_NAMES[key]] = DEFAULT_VALUES[key]; | |
} | |
} | |
// Debounce function to limit the rate of function calls. *RE-ADDED, BUT MODIFIED* | |
function debounce(func, delay) { | |
let timeoutId; | |
return function (...args) { | |
clearTimeout(timeoutId); | |
timeoutId = setTimeout(() => { | |
func.apply(this, args); | |
}, delay); | |
}; | |
} | |
/** | |
* Implements *targeted* virtualization using IntersectionObserver and Trusted Types. | |
* This version ONLY virtualizes model response turns, not user input turns. | |
*/ | |
function virtualizeChatHistory() { | |
const chatContainer = document.querySelector('ms-autoscroll-container'); | |
if (!chatContainer) { | |
console.error('ms-autoscroll-container not found. Virtualization aborted.'); | |
return; | |
} | |
// Debounce the scroll event handling. This is the key change. | |
const debouncedScrollHandler = debounce(() => { | |
const chatTurns = document.querySelectorAll('ms-chat-turn.model.render'); | |
chatTurns.forEach(chatTurn => { | |
const contentDiv = chatTurn.querySelector('.turn-content'); | |
if (!contentDiv) return; // Skip if no content div | |
const rect = chatTurn.getBoundingClientRect(); | |
const isVisible = (rect.top >= 0 && rect.bottom <= window.innerHeight) || | |
(rect.top < window.innerHeight && rect.bottom >= 0); | |
if (isVisible) { | |
// Restore content if it was cached | |
if (chatTurn.dataset.cachedContent) { | |
contentDiv.innerHTML = ttPolicy.createHTML(chatTurn.dataset.cachedContent); | |
delete chatTurn.dataset.cachedContent; | |
} | |
} else { | |
// Cache and remove content | |
chatTurn.dataset.cachedContent = contentDiv.innerHTML; | |
contentDiv.innerHTML = ttPolicy.createHTML(''); // Empty, but trusted. | |
} | |
}); | |
}, 150); // 150ms debounce delay. Adjust as needed. | |
// Observe existing model turns. | |
const chatTurns = document.querySelectorAll('ms-chat-turn.model.render'); // Select only model turns | |
// Observe newly added model turns dynamically. | |
const chatSession = document.querySelector('ms-chat-session'); | |
if (chatSession) { | |
const mutationObserver = new MutationObserver(mutations => { | |
mutations.forEach(mutation => { | |
if (mutation.type === 'childList') { | |
mutation.addedNodes.forEach(node => { | |
// *** TARGET ONLY MODEL TURNS *** | |
if (node.nodeType === Node.ELEMENT_NODE && node.matches('ms-chat-turn.model.render')) { | |
// When a new chat turn is added, set up the debounced scroll handler. | |
chatContainer.addEventListener('scroll', debouncedScrollHandler); | |
} | |
}); | |
} | |
}); | |
}); | |
mutationObserver.observe(chatSession, { childList: true, subtree: true }); | |
} | |
// Initial application of the debounced scroll handler | |
chatContainer.addEventListener('scroll', debouncedScrollHandler); | |
} | |
/** | |
* Main function - now with a delay! | |
*/ | |
function main() { | |
// Add a delay before running the main logic. 5000ms = 5 seconds | |
setTimeout(() => { | |
initializeWindowVariables(); | |
const nullCheckerName = findNullCheckerName(); | |
if (!nullCheckerName) { | |
console.error('Null checker function not found. Aborting.'); | |
return; | |
} | |
overrideNullChecker(nullCheckerName); | |
const originalSlider = document.querySelector( | |
'[title="Top P set of tokens to consider during generation."]' | |
); | |
if (!originalSlider) { | |
console.error('Original slider not found. Aborting.'); | |
return; | |
} | |
const originalSliderContainer = originalSlider.parentElement.parentElement; | |
addSlider( | |
originalSliderContainer, | |
'Top K', | |
SLIDER_BOUNDS.topK, | |
WINDOW_VARIABLE_NAMES.topK | |
); | |
addSlider( | |
originalSliderContainer, | |
'Frequency Penalty', | |
SLIDER_BOUNDS.frequencyPenalty, | |
WINDOW_VARIABLE_NAMES.frequencyPenalty | |
); | |
addSlider( | |
originalSliderContainer, | |
'Presence Penalty', | |
SLIDER_BOUNDS.presencePenalty, | |
WINDOW_VARIABLE_NAMES.presencePenalty | |
); | |
addSlider( | |
originalSliderContainer, | |
'Seed', | |
SLIDER_BOUNDS.seed, | |
WINDOW_VARIABLE_NAMES.seed | |
); | |
addPenaltyCheckbox(originalSliderContainer) | |
addSeedCheckbox(originalSliderContainer) | |
// --- Performance Optimizations --- | |
// Debouncing input is not ideal here. We'll rely on virtualization. | |
// setupInputEventHandlers(); // NO EVENT HANDLING FOR NOW | |
// Virtualize the chat history (TARGETED approach) | |
virtualizeChatHistory(); | |
}, 5000); // 5000 milliseconds = 5 seconds delay | |
} | |
// Execute the main function | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Description:
AI Studio patch to actually get the features users want (remade and with new additions via the help of Gemini 2.0 pro, Gemini Flash 2.0, Gemini Flash 2.0 Thinking, and Gemini Experimental 1206), along with some performance improvements.
Instructions:
Paste in in the devtool's console after opening a prompt, and wait 5 seconds (for the script to initialize) after that, then, well, have fun!
If some models are showing instant/quick internal error responses with this script, try disabling the penalties. This is because some models cannot support penalties, so you'll have to do that to prevent the errors.
(AI studio without script)

(AI Studio with script)
