Skip to content

Instantly share code, notes, and snippets.

@kiranwayne
Last active December 25, 2025 15:33
Show Gist options
  • Select an option

  • Save kiranwayne/8aa5f99af288298c7c069d6504efc109 to your computer and use it in GitHub Desktop.

Select an option

Save kiranwayne/8aa5f99af288298c7c069d6504efc109 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Assistant.sh Enhanced
// @namespace http://tampermonkey.net/
// @version 1.1
// @description Customize max-width (slider/manual input) and toggle text justification for web.assistant.sh
// @author kiranwayne
// @match https://web.assistant.sh/*
// @updateURL https://gist.github.com/kiranwayne/8aa5f99af288298c7c069d6504efc109/raw/assistant.sh_enhanced.js
// @downloadURL https://gist.github.com/kiranwayne/8aa5f99af288298c7c069d6504efc109/raw/assistant.sh_enhanced.js
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @run-at document-end
// ==/UserScript==
(async () => {
'use strict';
// --- Configuration & Constants ---
const SCRIPT_NAME = 'Assistant.sh Enhanced';
const SCRIPT_VERSION = '1.1';
// --- TARGET SELECTORS ---
// Target both the <main> chat area AND the floating input bar container
const WIDTH_TARGET_SELECTORS = 'main[class*="max-w-"], .fixed.bottom-0 > div[class*="max-w-"]';
// Target typography container
const TEXT_SELECTOR = '.prose';
const CONFIG_PREFIX = 'assistantSh_enhanced_v2_';
const MAX_WIDTH_PX_KEY = CONFIG_PREFIX + 'maxWidthPx';
const USE_DEFAULT_WIDTH_KEY = CONFIG_PREFIX + 'useDefaultWidth';
const JUSTIFY_KEY = CONFIG_PREFIX + 'justifyEnabled';
const UI_VISIBLE_KEY = CONFIG_PREFIX + 'uiVisible';
const WIDTH_STYLE_ID = 'vm-as-width-style';
const JUSTIFY_STYLE_ID = 'vm-as-justify-style';
const GLOBAL_STYLE_ID = 'vm-as-global-style';
const SETTINGS_PANEL_ID = 'as-userscript-settings-panel';
// Slider pixel config
const SCRIPT_DEFAULT_WIDTH_PX = 900;
const MIN_WIDTH_PX = 600;
const MAX_WIDTH_PX = 2000;
const STEP_WIDTH_PX = 10;
// --- State Variables ---
let config = {
maxWidthPx: SCRIPT_DEFAULT_WIDTH_PX,
useDefaultWidth: false,
justifyEnabled: false,
uiVisible: false
};
// UI and style references
let settingsPanel = null;
let widthSlider = null;
let widthLabel = null;
let widthInput = null;
let defaultWidthCheckbox = null;
let justifyCheckbox = null;
let menuCommandId_ToggleUI = null;
// --- Helper Functions ---
async function loadSettings() {
config.maxWidthPx = await GM_getValue(MAX_WIDTH_PX_KEY, SCRIPT_DEFAULT_WIDTH_PX);
config.maxWidthPx = Math.max(MIN_WIDTH_PX, Math.min(MAX_WIDTH_PX, config.maxWidthPx));
config.useDefaultWidth = await GM_getValue(USE_DEFAULT_WIDTH_KEY, false);
config.justifyEnabled = await GM_getValue(JUSTIFY_KEY, false);
config.uiVisible = await GM_getValue(UI_VISIBLE_KEY, false);
}
async function saveSetting(key, value) {
if (key === MAX_WIDTH_PX_KEY) {
const numValue = parseInt(value, 10);
if (!isNaN(numValue)) {
const clampedValue = Math.max(MIN_WIDTH_PX, Math.min(MAX_WIDTH_PX, numValue));
await GM_setValue(key, clampedValue);
config.maxWidthPx = clampedValue;
}
} else {
await GM_setValue(key, value);
if (key === USE_DEFAULT_WIDTH_KEY) { config.useDefaultWidth = value; }
else if (key === JUSTIFY_KEY) { config.justifyEnabled = value; }
else if (key === UI_VISIBLE_KEY) { config.uiVisible = value; }
}
}
// --- CSS Generation ---
function getWidthCss() {
if (config.useDefaultWidth) return '';
return `
${WIDTH_TARGET_SELECTORS} {
max-width: ${config.maxWidthPx}px !important;
}
`;
}
function getJustifyCss() {
if (!config.justifyEnabled) return '';
return `
${TEXT_SELECTOR}, ${TEXT_SELECTOR} p {
text-align: justify !important;
-webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto;
}
`;
}
function getGlobalSpinnerCss() {
return `
#${SETTINGS_PANEL_ID} input[type=number] { -moz-appearance: textfield !important; }
#${SETTINGS_PANEL_ID} input[type=number]::-webkit-inner-spin-button,
#${SETTINGS_PANEL_ID} input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: inner-spin-button !important; opacity: 1 !important; cursor: pointer;
}
`;
}
// --- Style Injection ---
function injectOrUpdateStyle(styleId, cssContent) {
let style = document.getElementById(styleId);
if (cssContent) {
if (!style) {
style = document.createElement('style');
style.id = styleId;
style.textContent = cssContent;
document.head.appendChild(style);
} else if (style.textContent !== cssContent) {
style.textContent = cssContent;
}
} else {
if (style) style.remove();
}
}
function applyGlobalHeadStyles() {
injectOrUpdateStyle(GLOBAL_STYLE_ID, getGlobalSpinnerCss());
}
function applyWidthStyle() {
injectOrUpdateStyle(WIDTH_STYLE_ID, getWidthCss());
}
function applyJustificationStyle() {
injectOrUpdateStyle(JUSTIFY_STYLE_ID, getJustifyCss());
}
// --- UI State Update ---
function updateUIState() {
if (!settingsPanel) return;
defaultWidthCheckbox.checked = config.useDefaultWidth;
const isCustomWidthEnabled = !config.useDefaultWidth;
widthSlider.disabled = !isCustomWidthEnabled;
widthInput.disabled = !isCustomWidthEnabled;
const opacity = isCustomWidthEnabled ? 1 : 0.5;
widthLabel.style.opacity = opacity;
widthSlider.style.opacity = opacity;
widthInput.style.opacity = opacity;
widthSlider.value = config.maxWidthPx;
widthInput.value = config.maxWidthPx;
widthLabel.textContent = `${config.maxWidthPx}px`;
justifyCheckbox.checked = config.justifyEnabled;
}
// --- Click Outside Handler ---
async function handleClickOutside(event) {
if (settingsPanel && document.body.contains(settingsPanel) && !settingsPanel.contains(event.target)) {
await saveSetting(UI_VISIBLE_KEY, false);
removeSettingsUI();
updateTampermonkeyMenu();
}
}
// --- UI Creation/Removal ---
function removeSettingsUI() {
document.removeEventListener('click', handleClickOutside, true);
settingsPanel = document.getElementById(SETTINGS_PANEL_ID);
if (settingsPanel) {
settingsPanel.remove();
settingsPanel = null;
}
}
function createSettingsUI() {
if (document.getElementById(SETTINGS_PANEL_ID) || !config.uiVisible) return;
settingsPanel = document.createElement('div');
settingsPanel.id = SETTINGS_PANEL_ID;
// Dark theme matching the site's aesthetic
Object.assign(settingsPanel.style, {
position: 'fixed',
top: '20px',
right: '20px',
zIndex: '99999',
display: 'block',
background: 'rgba(23, 23, 23, 0.95)',
color: '#ECECF1',
border: '1px solid #333',
borderRadius: '12px',
padding: '16px',
boxShadow: '0 10px 25px rgba(0,0,0,0.5)',
backdropFilter: 'blur(10px)',
minWidth: '300px',
fontFamily: 'sans-serif',
fontSize: '14px'
});
// Header
const headerDiv = document.createElement('div');
headerDiv.style.marginBottom = '12px';
headerDiv.style.borderBottom = '1px solid #333';
headerDiv.style.paddingBottom = '8px';
const titleElement = document.createElement('h4');
titleElement.textContent = SCRIPT_NAME;
Object.assign(titleElement.style, { margin: '0', fontSize: '16px', fontWeight: '600', color: '#fff' });
const subElement = document.createElement('div');
subElement.textContent = `v${SCRIPT_VERSION}`;
Object.assign(subElement.style, { fontSize: '11px', color: '#888', marginTop: '2px' });
headerDiv.appendChild(titleElement);
headerDiv.appendChild(subElement);
settingsPanel.appendChild(headerDiv);
// --- Width Section ---
const widthSection = document.createElement('div');
// Toggle Default Checkbox
const defaultWidthDiv = document.createElement('div');
defaultWidthDiv.style.marginBottom = '12px';
defaultWidthDiv.style.display = 'flex';
defaultWidthDiv.style.alignItems = 'center';
defaultWidthCheckbox = document.createElement('input');
defaultWidthCheckbox.type = 'checkbox';
defaultWidthCheckbox.id = 'as-userscript-defaultwidth-toggle';
defaultWidthCheckbox.style.marginRight = '8px';
const defaultWidthLabel = document.createElement('label');
defaultWidthLabel.htmlFor = 'as-userscript-defaultwidth-toggle';
defaultWidthLabel.textContent = 'Use Default Site Width';
defaultWidthLabel.style.cursor = 'pointer';
defaultWidthDiv.appendChild(defaultWidthCheckbox);
defaultWidthDiv.appendChild(defaultWidthLabel);
// Controls Container
const customWidthControlsDiv = document.createElement('div');
customWidthControlsDiv.style.display = 'flex';
customWidthControlsDiv.style.alignItems = 'center';
customWidthControlsDiv.style.gap = '8px';
customWidthControlsDiv.style.marginBottom = '8px';
widthLabel = document.createElement('span');
widthLabel.style.minWidth = '45px';
widthLabel.style.fontFamily = 'monospace';
widthLabel.style.color = '#aaa';
widthLabel.style.fontSize = '12px';
widthSlider = document.createElement('input');
widthSlider.type = 'range';
widthSlider.min = MIN_WIDTH_PX;
widthSlider.max = MAX_WIDTH_PX;
widthSlider.step = STEP_WIDTH_PX;
widthSlider.style.flexGrow = '1';
widthSlider.style.cursor = 'pointer';
widthInput = document.createElement('input');
widthInput.type = 'number';
// Note: we don't strictly enforce min/max on the HTML attribute alone to allow typing,
// but we handle validation in JS.
widthInput.min = MIN_WIDTH_PX;
widthInput.max = MAX_WIDTH_PX;
widthInput.step = STEP_WIDTH_PX;
Object.assign(widthInput.style, {
width: '60px',
padding: '4px',
background: '#222',
color: '#fff',
border: '1px solid #444',
borderRadius: '4px',
fontSize: '12px'
});
customWidthControlsDiv.appendChild(widthLabel);
customWidthControlsDiv.appendChild(widthSlider);
customWidthControlsDiv.appendChild(widthInput);
widthSection.appendChild(defaultWidthDiv);
widthSection.appendChild(customWidthControlsDiv);
// --- Justify Section ---
const justifySection = document.createElement('div');
justifySection.style.borderTop = '1px solid #333';
justifySection.style.paddingTop = '12px';
justifySection.style.marginTop = '12px';
justifySection.style.display = 'flex';
justifySection.style.alignItems = 'center';
justifyCheckbox = document.createElement('input');
justifyCheckbox.type = 'checkbox';
justifyCheckbox.id = 'as-userscript-justify-toggle';
justifyCheckbox.style.marginRight = '8px';
const justifyLabel = document.createElement('label');
justifyLabel.htmlFor = 'as-userscript-justify-toggle';
justifyLabel.textContent = 'Justify Text Content';
justifyLabel.style.cursor = 'pointer';
justifySection.appendChild(justifyCheckbox);
justifySection.appendChild(justifyLabel);
// Append Sections
settingsPanel.appendChild(widthSection);
settingsPanel.appendChild(justifySection);
document.body.appendChild(settingsPanel);
// --- Event Listeners ---
defaultWidthCheckbox.addEventListener('change', async (e) => {
await saveSetting(USE_DEFAULT_WIDTH_KEY, e.target.checked);
applyWidthStyle();
updateUIState();
});
// SLIDER Logic (Instant Update)
widthSlider.addEventListener('input', (e) => {
const val = parseInt(e.target.value, 10);
config.maxWidthPx = val;
if (widthLabel) widthLabel.textContent = `${val}px`;
if (widthInput) widthInput.value = val;
if (!config.useDefaultWidth) applyWidthStyle();
});
widthSlider.addEventListener('change', async (e) => {
const val = parseInt(e.target.value, 10);
if (!config.useDefaultWidth) await saveSetting(MAX_WIDTH_PX_KEY, val);
});
// TEXT INPUT Logic (Debounced/Deferred validation)
widthInput.addEventListener('input', (e) => {
const val = parseInt(e.target.value, 10);
if (isNaN(val)) return;
// Only update visuals if the number is currently valid.
// If user types "1", we don't update style or slider yet (too small).
// If user types "1234", we update.
if (val >= MIN_WIDTH_PX && val <= MAX_WIDTH_PX) {
config.maxWidthPx = val;
if (widthLabel) widthLabel.textContent = `${val}px`;
if (widthSlider) widthSlider.value = val;
if (!config.useDefaultWidth) applyWidthStyle();
}
});
widthInput.addEventListener('change', async (e) => {
// On blur/enter, strict clamp and save
let val = parseInt(e.target.value, 10);
if (isNaN(val)) val = config.maxWidthPx;
const clamped = Math.max(MIN_WIDTH_PX, Math.min(MAX_WIDTH_PX, val));
// Update state
config.maxWidthPx = clamped;
e.target.value = clamped; // Force input to match clamp
if (widthSlider) widthSlider.value = clamped;
if (widthLabel) widthLabel.textContent = `${clamped}px`;
if (!config.useDefaultWidth) {
applyWidthStyle();
await saveSetting(MAX_WIDTH_PX_KEY, clamped);
}
});
justifyCheckbox.addEventListener('change', async (e) => {
await saveSetting(JUSTIFY_KEY, e.target.checked);
applyJustificationStyle();
});
// Initialize UI values
updateUIState();
// Close when clicking outside
setTimeout(() => {
document.addEventListener('click', handleClickOutside, true);
}, 100);
}
// --- Tampermonkey Menu ---
function updateTampermonkeyMenu() {
if (menuCommandId_ToggleUI !== null) {
GM_unregisterMenuCommand(menuCommandId_ToggleUI);
}
const label = config.uiVisible ? 'Hide Settings Panel' : 'Show Settings Panel';
menuCommandId_ToggleUI = GM_registerMenuCommand(label, async () => {
const newState = !config.uiVisible;
await saveSetting(UI_VISIBLE_KEY, newState);
if (newState) {
createSettingsUI();
} else {
removeSettingsUI();
}
updateTampermonkeyMenu();
});
}
// --- Initialization ---
await loadSettings();
applyGlobalHeadStyles();
applyWidthStyle();
applyJustificationStyle();
if (config.uiVisible) {
createSettingsUI();
}
updateTampermonkeyMenu();
console.log(`[${SCRIPT_NAME}] Loaded.`);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment