Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name xAI Console Tools
- // @namespace http://tampermonkey.net/
- // @version 1.0
- // @description Safe retry/edit/reset helpers, optional 2K forcing, JPEG download
- // @match https://console.x.ai/playground/imagine*
- // @match https://console.x.ai/team/*/imagine*
- // @grant none
- // @run-at document-start
- // ==/UserScript==
- (function () {
- 'use strict';
- if (window.__XAI_TOOLS_SINGLETON__) {
- console.warn('[xAI Tools] duplicate instance blocked');
- return;
- }
- window.__XAI_TOOLS_SINGLETON__ = '1.0';
- const TAG = '[xAI Tools]';
- const PANEL_ID = 'xai-tools-panel';
- const BTN_2K_ID = 'xai-2k-btn';
- const BTN_COUNT_ID = 'xai-count-btn';
- const BTN_RETRY_ID = 'xai-retry-btn';
- const BTN_RESET_ID = 'xai-reset-btn';
- const BTN_LOG_ID = 'xai-log-btn';
- const GAP_PX = 8;
- const JPEG_QUALITY = 0.95;
- const FORCE_2K_KEY = 'gc_force2k_v1';
- const FORCE_COUNT_KEY = 'gc_force_image_count_v1';
- const PANEL_IMAGE_COUNT_VALUES = [1, 2, 4];
- const DEFAULT_IMAGE_COUNT = 4;
- const IMAGE_COUNT_MIN = 1;
- const IMAGE_COUNT_MAX = 10;
- const AUTO_REHYDRATE_KEY = 'gc_auto_rehydrate_v1';
- const DEBUG_LOG_KEY = '__xt_debug_log_v4';
- const SHOW_LOG_BTN_KEY = 'gc_show_log_btn_v1';
- const RESTORE_STATE_KEY = '__xt_restore_state_v4';
- const RESTORE_DB_NAME = '__xt_restore_db_v4';
- const RESTORE_DB_STORE = 'kv';
- const RESTORE_IMAGE_KEY = 'restore-image';
- const BYPASS_ATTR = 'data-xai-jpeg-bypass';
- const DEFAULT_UI_SYNC_KEY = '__xt_default_ui_sync_v1';
- let postStartRetryInFlight = false;
- let postStartRetryJobId = 0;
- let countUiSyncInFlight = null;
- let countUiStartupSyncDone = false;
- let countUiSyncQueuedReason = '';
- let startupDefaultSyncTimer = 0;
- let lastBannerSignature = '';
- let lastBannerSignatureAt = 0;
- let pendingManualCountPick = 0;
- let pendingManualCountPickedAt = 0;
- let highDemandRecoveryInFlight = 0;
- let highDemandWatchRaf = 0;
- let preferredSubmitCountUntil = 0;
- let preferredSubmitCountValue = 0;
- let glitchSubmitProxyBusy = false;
- let glitchProxyButton = null;
- let glitchProxyState = null;
- function pad2(n) {
- return String(n).padStart(2, '0');
- }
- function timeStamp() {
- const d = new Date();
- return `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`;
- }
- function fileStamp() {
- const d = new Date();
- return `${pad2(d.getDate())}.${pad2(d.getMonth() + 1)}.${d.getFullYear()} ${pad2(d.getHours())}-${pad2(d.getMinutes())}-${pad2(d.getSeconds())}`;
- }
- function appendPersistentLog(line) {
- try {
- const prev = localStorage.getItem(DEBUG_LOG_KEY) || '';
- const next = `${prev}${prev ? '\n' : ''}${line}`;
- localStorage.setItem(DEBUG_LOG_KEY, next.slice(-50000));
- } catch {}
- }
- function getPersistentLog() {
- try {
- return localStorage.getItem(DEBUG_LOG_KEY) || '';
- } catch {
- return '';
- }
- }
- function flushPersistentLogToConsole() {
- const text = getPersistentLog();
- if (!text) return;
- console.groupCollapsed(`${TAG} persisted debug log`);
- console.log(text);
- console.groupEnd();
- }
- function log(msg, type = 'log') {
- const line = `${TAG} ${timeStamp()} ${msg}`;
- const fn = type === 'error' ? console.error : type === 'warn' ? console.warn : console.log;
- fn(line);
- appendPersistentLog(line);
- }
- function sleep(ms) {
- return new Promise(resolve => setTimeout(resolve, ms));
- }
- function isVisible(el) {
- if (!(el instanceof Element)) return false;
- const style = getComputedStyle(el);
- if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
- const rect = el.getBoundingClientRect();
- return rect.width > 0 && rect.height > 0;
- }
- function getText(el) {
- if (!(el instanceof Element)) return '';
- return [el.textContent || '', el.getAttribute('aria-label') || '', el.getAttribute('title') || '']
- .join(' ')
- .replace(/\s+/g, ' ')
- .trim()
- .toLowerCase();
- }
- function isForce2kEnabled() {
- try {
- return localStorage.getItem(FORCE_2K_KEY) === '1';
- } catch {
- return false;
- }
- }
- function setForce2kEnabled(enabled) {
- try {
- localStorage.setItem(FORCE_2K_KEY, enabled ? '1' : '0');
- } catch {}
- }
- function sanitizeImageCount(value, fallback = 1) {
- const num = Math.round(Number(value));
- if (!Number.isFinite(num)) return fallback;
- if (num < IMAGE_COUNT_MIN || num > IMAGE_COUNT_MAX) return fallback;
- return num;
- }
- function getForcedImageCount() {
- try {
- const raw = Number(localStorage.getItem(FORCE_COUNT_KEY) || String(DEFAULT_IMAGE_COUNT));
- return sanitizeImageCount(raw, DEFAULT_IMAGE_COUNT);
- } catch {
- return DEFAULT_IMAGE_COUNT;
- }
- }
- function setForcedImageCount(value) {
- const next = sanitizeImageCount(value, DEFAULT_IMAGE_COUNT);
- try {
- localStorage.setItem(FORCE_COUNT_KEY, String(next));
- } catch {}
- return next;
- }
- function syncStoredImageCount(value, reason = 'ui') {
- const next = sanitizeImageCount(value, 0);
- if (!next) return false;
- const prev = getForcedImageCount();
- if (prev !== next) {
- setForcedImageCount(next);
- log(`image count observed from ${reason} -> x${next}`, 'warn');
- }
- updateCountButton();
- positionPanel();
- return true;
- }
- function cycleForcedImageCount() {
- const current = getForcedImageCount();
- const index = PANEL_IMAGE_COUNT_VALUES.indexOf(current);
- const next = PANEL_IMAGE_COUNT_VALUES[(index >= 0 ? index + 1 : 0) % PANEL_IMAGE_COUNT_VALUES.length];
- setForcedImageCount(next);
- return next;
- }
- function preferStoredImageCount(value, ttlMs = 12000, reason = 'panel') {
- preferredSubmitCountValue = sanitizeImageCount(value, getForcedImageCount());
- preferredSubmitCountUntil = Date.now() + Math.max(1000, Math.round(Number(ttlMs) || 0));
- log(`count preference locked to x${preferredSubmitCountValue} (${reason})`, 'warn');
- }
- function hasPreferredStoredImageCount() {
- return Date.now() < preferredSubmitCountUntil && preferredSubmitCountValue === getForcedImageCount();
- }
- function clearPreferredStoredImageCount() {
- preferredSubmitCountUntil = 0;
- preferredSubmitCountValue = 0;
- }
- function normText(value) {
- return String(value || '').replace(/\s+/g, ' ').trim();
- }
- function exactCountFromText(value) {
- const text = normText(value);
- if (!/^(10|[1-9])$/.test(text)) return null;
- const num = Number(text);
- return Number.isFinite(num) ? num : null;
- }
- function rememberManualCountPick(value, reason = 'ui-manual') {
- const next = sanitizeImageCount(value, 0);
- if (!next) return false;
- pendingManualCountPick = next;
- pendingManualCountPickedAt = Date.now();
- clearPreferredStoredImageCount();
- syncStoredImageCount(next, reason);
- return true;
- }
- async function waitForValue(factory, timeoutMs = 1400, intervalMs = 80) {
- const started = Date.now();
- while (Date.now() - started < timeoutMs) {
- const value = factory();
- if (value) return value;
- await sleep(intervalMs);
- }
- return null;
- }
- function clickElement(el) {
- if (!(el instanceof HTMLElement)) return false;
- try { if (typeof el.focus === 'function') el.focus(); } catch {}
- try { el.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, cancelable: true, pointerType: 'mouse', isPrimary: true, button: 0, buttons: 1 })); } catch {}
- try { el.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true, button: 0, buttons: 1, view: window })); } catch {}
- try { el.dispatchEvent(new PointerEvent('pointerup', { bubbles: true, cancelable: true, pointerType: 'mouse', isPrimary: true, button: 0, buttons: 0 })); } catch {}
- try { el.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true, button: 0, buttons: 0, view: window })); } catch {}
- try { el.click(); return true; } catch { return false; }
- }
- function sendOpenKeys(el) {
- if (!(el instanceof HTMLElement)) return false;
- try { if (typeof el.focus === 'function') el.focus(); } catch {}
- const events = [
- new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', bubbles: true, cancelable: true }),
- new KeyboardEvent('keyup', { key: 'Enter', code: 'Enter', bubbles: true, cancelable: true }),
- new KeyboardEvent('keydown', { key: ' ', code: 'Space', bubbles: true, cancelable: true }),
- new KeyboardEvent('keyup', { key: ' ', code: 'Space', bubbles: true, cancelable: true })
- ];
- let fired = false;
- for (const evt of events) {
- try { el.dispatchEvent(evt); fired = true; } catch {}
- }
- return fired;
- }
- async function tryOpenImageSettingsMenu(imageBtn, reason = 'manual') {
- if (!(imageBtn instanceof HTMLElement)) return null;
- let menu = findOpenImageSettingsMenu(imageBtn);
- if (menu) return menu;
- for (let pass = 1; pass <= 4; pass++) {
- if (pass > 1) await closeImageMenus(imageBtn).catch(() => {});
- clickElement(imageBtn);
- menu = await waitForValue(() => findOpenImageSettingsMenu(imageBtn), 700, 70);
- if (menu) return menu;
- sendOpenKeys(imageBtn);
- menu = await waitForValue(() => findOpenImageSettingsMenu(imageBtn), 700, 70);
- if (menu) return menu;
- try { imageBtn.click(); } catch {}
- menu = await waitForValue(() => findOpenImageSettingsMenu(imageBtn), 700, 70);
- if (menu) return menu;
- }
- log(`count UI sync failed (${reason}): Image menu did not open`, 'error');
- return null;
- }
- function getPopupForTrigger(trigger, roles = []) {
- if (!(trigger instanceof HTMLElement)) return null;
- const popupId = trigger.getAttribute('aria-controls') || '';
- if (popupId) {
- const popup = document.getElementById(popupId);
- if (popup instanceof HTMLElement && isVisible(popup)) {
- if (!roles.length || roles.includes((popup.getAttribute('role') || '').toLowerCase())) return popup;
- }
- }
- return null;
- }
- function clickOutsideForMenuDismiss() {
- const textarea = getPromptTextarea();
- const target = textarea || document.body;
- if (!(target instanceof HTMLElement)) return false;
- try { if (typeof target.focus === 'function') target.focus(); } catch {}
- try { target.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, cancelable: true, pointerType: 'mouse', isPrimary: true, button: 0, buttons: 1 })); } catch {}
- try { target.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true, button: 0, buttons: 1, view: window })); } catch {}
- try { target.dispatchEvent(new PointerEvent('pointerup', { bubbles: true, cancelable: true, pointerType: 'mouse', isPrimary: true, button: 0, buttons: 0 })); } catch {}
- try { target.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true, button: 0, buttons: 0, view: window })); } catch {}
- try { target.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, button: 0, buttons: 0, view: window })); } catch {}
- return true;
- }
- function findOpenImageSettingsMenu(imageBtn = null) {
- const byTrigger = getPopupForTrigger(imageBtn, ['menu']);
- if (byTrigger) return byTrigger;
- const menus = Array.from(document.querySelectorAll('[role="menu"], [data-radix-menu-content]')).filter(el => {
- if (!(el instanceof HTMLElement)) return false;
- if (!isVisible(el)) return false;
- const text = normText(el.textContent);
- return /number of images/i.test(text) && (/(image resolution|aspect ratio|model)/i.test(text));
- });
- return menus[menus.length - 1] || null;
- }
- function findImageCountCombobox(menu) {
- if (!(menu instanceof Element)) return null;
- const buttons = Array.from(menu.querySelectorAll('button[role="combobox"], button'));
- return buttons.find(el => /number of images/i.test(normText(el.textContent))) || null;
- }
- function getDisplayedUiCountFromCombobox(btn) {
- if (!(btn instanceof Element)) return null;
- const matches = normText(btn.textContent).match(/\b(10|[1-9])\b/g);
- if (!matches || !matches.length) return null;
- const value = Number(matches[matches.length - 1]);
- return Number.isFinite(value) ? value : null;
- }
- function findOpenCountListboxGeneric() {
- const listboxes = Array.from(document.querySelectorAll('[role="listbox"]')).filter(el => {
- if (!(el instanceof HTMLElement)) return false;
- if (!isVisible(el)) return false;
- const numericValues = new Set();
- for (const node of Array.from(el.querySelectorAll('*'))) {
- if (!(node instanceof HTMLElement)) continue;
- const value = exactCountFromText(node.textContent);
- if (value) numericValues.add(value);
- }
- return numericValues.size >= 3;
- });
- return listboxes[listboxes.length - 1] || null;
- }
- function collectCountOptionCandidates(listbox, value) {
- if (!(listbox instanceof Element)) return [];
- const targetText = String(value);
- const nodes = Array.from(listbox.querySelectorAll('*')).filter(el => {
- if (!(el instanceof HTMLElement)) return false;
- if (!isVisible(el)) return false;
- return normText(el.textContent) === targetText;
- });
- const unique = [];
- const seen = new Set();
- const push = (el) => {
- if (!(el instanceof HTMLElement)) return;
- if (el === listbox) return;
- if (!listbox.contains(el)) return;
- if (!isVisible(el)) return;
- if (seen.has(el)) return;
- seen.add(el);
- unique.push(el);
- };
- for (const node of nodes) {
- push(node.closest('[role="option"], [role="menuitemradio"], [data-radix-collection-item]'));
- let cur = node;
- while (cur && cur !== listbox) {
- if (cur instanceof HTMLElement && normText(cur.textContent) === targetText) push(cur);
- cur = cur.parentElement;
- }
- }
- unique.sort((a, b) => {
- const score = (el) => {
- const rect = el.getBoundingClientRect();
- let s = (rect.width || 0) * (rect.height || 0);
- if (el.getAttribute('role') === 'option') s += 1000000;
- if (el.hasAttribute('data-radix-collection-item')) s += 500000;
- return s;
- };
- return score(b) - score(a);
- });
- return unique;
- }
- async function chooseCountOptionFromListbox(listbox, value, countBtn) {
- const candidates = collectCountOptionCandidates(listbox, value);
- if (!candidates.length) {
- log(`count UI sync: option x${value} not found in listbox`, 'error');
- return false;
- }
- for (const candidate of candidates) {
- const label = candidate.getAttribute('role') || candidate.tagName.toLowerCase();
- clickElement(candidate);
- await sleep(140);
- const menuNow = findOpenImageSettingsMenu();
- const countBtnNow = findImageCountCombobox(menuNow) || countBtn;
- const uiValue = getDisplayedUiCountFromCombobox(countBtnNow);
- const listboxStillOpen = document.contains(listbox) && isVisible(listbox);
- if (uiValue === value) {
- log(`count UI sync: option x${value} chosen via ${label}`, 'warn');
- return true;
- }
- if (!listboxStillOpen) {
- log(`count UI sync: listbox closed after clicking ${label}`, 'warn');
- return true;
- }
- }
- return false;
- }
- function sendEscape(target = document.activeElement || document.body) {
- const host = target instanceof EventTarget ? target : document;
- try { host.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', code: 'Escape', bubbles: true })); } catch {}
- try { host.dispatchEvent(new KeyboardEvent('keyup', { key: 'Escape', code: 'Escape', bubbles: true })); } catch {}
- }
- async function closeImageMenus(imageBtn) {
- const btn = imageBtn || findToolbarAnchor();
- for (let pass = 1; pass <= 6; pass++) {
- const menuOpen = !!findOpenImageSettingsMenu(btn);
- const listboxOpen = !!findOpenCountListboxGeneric();
- if (!menuOpen && !listboxOpen) return true;
- if (listboxOpen) {
- sendEscape(document.activeElement || document.body);
- await sleep(80);
- }
- if (btn instanceof HTMLElement && btn.getAttribute('aria-expanded') === 'true') {
- clickElement(btn);
- await sleep(100);
- }
- if (findOpenImageSettingsMenu(btn) || findOpenCountListboxGeneric()) {
- clickOutsideForMenuDismiss();
- await sleep(90);
- sendEscape(document.activeElement || document.body);
- await sleep(90);
- }
- }
- return !findOpenImageSettingsMenu(btn) && !findOpenCountListboxGeneric();
- }
- async function applyForcedImageCountToUi(wantedCount, reason = 'manual') {
- const targetCount = sanitizeImageCount(wantedCount, getForcedImageCount());
- const imageBtn = findToolbarAnchor();
- if (!imageBtn) {
- log(`count UI sync skipped (${reason}): Image button not found`, 'warn');
- return false;
- }
- let menu = await tryOpenImageSettingsMenu(imageBtn, reason);
- if (!menu) return false;
- const countBtn = findImageCountCombobox(menu);
- if (!countBtn) {
- log(`count UI sync failed (${reason}): Number of images control not found`, 'error');
- await closeImageMenus(imageBtn);
- return false;
- }
- const currentUiValue = getDisplayedUiCountFromCombobox(countBtn);
- if (currentUiValue === targetCount) {
- await closeImageMenus(imageBtn);
- syncStoredImageCount(targetCount, `ui-confirmed:${reason}`);
- log(`count UI sync (${reason}): already x${targetCount}`, 'warn');
- return true;
- }
- const popupId = countBtn.getAttribute('aria-controls') || '';
- clickElement(countBtn);
- const listbox = await waitForValue(() => {
- const byId = popupId ? document.getElementById(popupId) : null;
- if (byId instanceof HTMLElement && isVisible(byId)) return byId;
- return findOpenCountListboxGeneric();
- }, 1800, 90);
- if (!listbox) {
- log(`count UI sync failed (${reason}): listbox did not open`, 'error');
- await closeImageMenus(imageBtn);
- return false;
- }
- const picked = await chooseCountOptionFromListbox(listbox, targetCount, countBtn);
- await sleep(140);
- const menuAfter = findOpenImageSettingsMenu(imageBtn) || menu;
- const countBtnAfter = findImageCountCombobox(menuAfter) || countBtn;
- const finalUiValue = getDisplayedUiCountFromCombobox(countBtnAfter);
- const success = picked && (finalUiValue === targetCount || finalUiValue === null);
- const closed = await closeImageMenus(imageBtn);
- if (success) syncStoredImageCount(finalUiValue || targetCount, `ui-apply:${reason}`);
- log(
- success
- ? `count UI sync applied (${reason}): x${targetCount}${closed ? '' : ' (menu-close-uncertain)'}`
- : `count UI sync uncertain (${reason}): wanted x${targetCount}, ui=${finalUiValue ?? 'unknown'}`,
- success ? 'warn' : 'error'
- );
- return success;
- }
- async function syncForcedImageCountUi(reason = 'manual') {
- if (countUiSyncInFlight) {
- countUiSyncQueuedReason = reason || countUiSyncQueuedReason || 'manual';
- return countUiSyncInFlight;
- }
- countUiSyncInFlight = (async () => {
- let loop = 0;
- let ok = false;
- let nextReason = reason || 'manual';
- do {
- countUiSyncQueuedReason = '';
- const wanted = getForcedImageCount();
- ok = await applyForcedImageCountToUi(wanted, `${nextReason}#${++loop}`);
- await sleep(80);
- nextReason = countUiSyncQueuedReason || nextReason;
- } while (countUiSyncQueuedReason && loop < 6);
- return ok;
- })().finally(() => {
- countUiSyncInFlight = null;
- });
- return countUiSyncInFlight;
- }
- function getVisibleUiImageCount() {
- const menu = findOpenImageSettingsMenu();
- const countBtn = findImageCountCombobox(menu);
- return getDisplayedUiCountFromCombobox(countBtn);
- }
- function extractCountOptionValueFromTarget(target, event = null) {
- const listbox = findOpenCountListboxGeneric();
- const candidates = [];
- const pushCandidate = (node) => {
- if (!(node instanceof HTMLElement)) return;
- if (candidates.includes(node)) return;
- candidates.push(node);
- };
- if (event && typeof event.composedPath === 'function') {
- for (const node of event.composedPath()) pushCandidate(node);
- }
- if (target instanceof HTMLElement) {
- let cur = target;
- while (cur) {
- pushCandidate(cur);
- if (cur === listbox) break;
- cur = cur.parentElement;
- }
- }
- for (const el of candidates) {
- const value = exactCountFromText(el.textContent);
- if (!value) continue;
- if (listbox instanceof HTMLElement && listbox.contains(el)) return value;
- const parentListbox = el.closest('[role="listbox"]');
- if (parentListbox instanceof HTMLElement && isVisible(parentListbox)) return value;
- }
- return null;
- }
- function installCountUiObserver() {
- const handleMaybeCountOptionPick = (event) => {
- if (countUiSyncInFlight) return;
- const clickedValue = extractCountOptionValueFromTarget(event.target, event);
- if (!clickedValue) return;
- rememberManualCountPick(clickedValue, 'ui-option-pick');
- setTimeout(() => {
- if (countUiSyncInFlight) return;
- const uiValue = getVisibleUiImageCount() || pendingManualCountPick || clickedValue;
- syncStoredImageCount(uiValue, 'ui-option-confirm');
- }, 180);
- };
- document.addEventListener('pointerdown', handleMaybeCountOptionPick, true);
- document.addEventListener('mousedown', handleMaybeCountOptionPick, true);
- document.addEventListener('click', handleMaybeCountOptionPick, true);
- }
- function scheduleCountUiStartupSync() {
- if (countUiStartupSyncDone) return;
- countUiStartupSyncDone = true;
- const rawRestore = (() => { try { return sessionStorage.getItem(RESTORE_STATE_KEY); } catch { return null; } })();
- const needsDefaultSync = !rawRestore;
- if (!needsDefaultSync) return;
- clearTimeout(startupDefaultSyncTimer);
- startupDefaultSyncTimer = setTimeout(async () => {
- try {
- await syncForcedImageCountUi('startup-default');
- } catch (e) {
- log(`startup default count sync failed: ${e.message}`, 'error');
- } finally {
- try { sessionStorage.removeItem(DEFAULT_UI_SYNC_KEY); } catch {}
- focusPromptTextarea();
- }
- }, 1200);
- }
- function shouldPatchImagePayload() {
- return true;
- }
- function isAutoRehydrateEnabled() {
- try {
- return localStorage.getItem(AUTO_REHYDRATE_KEY) !== '0';
- } catch {
- return true;
- }
- }
- function setAutoRehydrateEnabled(enabled) {
- try {
- localStorage.setItem(AUTO_REHYDRATE_KEY, enabled ? '1' : '0');
- } catch {}
- return !!enabled;
- }
- function modifyPayload(originalBody) {
- try {
- const body = JSON.parse(originalBody);
- let changed = false;
- if (isForce2kEnabled() && body.resolution !== '2k') {
- body.resolution = '2k';
- changed = true;
- log('resolution -> 2k', 'warn');
- }
- const storedCount = getForcedImageCount();
- const bodyCount = sanitizeImageCount(body.n, 0);
- const recentManualPick = pendingManualCountPick && (Date.now() - pendingManualCountPickedAt) < 5000;
- if (recentManualPick && bodyCount && bodyCount === pendingManualCountPick) {
- syncStoredImageCount(bodyCount, 'payload-manual-confirm');
- clearPreferredStoredImageCount();
- } else if (bodyCount !== storedCount) {
- body.n = storedCount;
- changed = true;
- log(`n -> ${storedCount}`, 'warn');
- }
- return changed ? JSON.stringify(body) : originalBody;
- } catch (e) {
- log(`modify payload error: ${e.message}`, 'error');
- return originalBody;
- }
- }
- function isImageApiUrl(url) {
- try {
- const u = new URL(url, location.href);
- return /^\/v1\/images(?:\/|$)/.test(u.pathname);
- } catch {
- return typeof url === 'string' && url.includes('/v1/images');
- }
- }
- const nativeFetch = window.fetch.bind(window);
- window.fetch = async function (input, init) {
- let nextInput = input;
- let nextInit = init;
- try {
- if (!shouldPatchImagePayload()) {
- return nativeFetch(input, init);
- }
- const isReq = input instanceof Request;
- const url = isReq ? input.url : String(input || '');
- const method = ((init && init.method) || (isReq ? input.method : 'GET') || 'GET').toUpperCase();
- if (method === 'POST' && isImageApiUrl(url)) {
- if (typeof init?.body === 'string') {
- nextInit = { ...init, body: modifyPayload(init.body) };
- } else if (isReq && !init) {
- const cloned = input.clone();
- const textBody = await cloned.text();
- const patchedBody = modifyPayload(textBody);
- if (patchedBody !== textBody) {
- nextInput = new Request(input, { body: patchedBody });
- }
- }
- noteImageSubmitDispatched('fetch');
- }
- } catch (e) {
- log(`fetch interception error: ${e.message}`, 'error');
- }
- return nativeFetch(nextInput, nextInit);
- };
- function openRestoreDb() {
- return new Promise((resolve, reject) => {
- const req = indexedDB.open(RESTORE_DB_NAME, 1);
- req.onupgradeneeded = () => {
- const db = req.result;
- if (!db.objectStoreNames.contains(RESTORE_DB_STORE)) db.createObjectStore(RESTORE_DB_STORE);
- };
- req.onsuccess = () => resolve(req.result);
- req.onerror = () => reject(req.error || new Error('indexedDB open failed'));
- });
- }
- async function idbSet(key, value) {
- const db = await openRestoreDb();
- return new Promise((resolve, reject) => {
- const tx = db.transaction(RESTORE_DB_STORE, 'readwrite');
- tx.objectStore(RESTORE_DB_STORE).put(value, key);
- tx.oncomplete = () => { db.close(); resolve(); };
- tx.onerror = () => { db.close(); reject(tx.error || new Error('indexedDB put failed')); };
- });
- }
- async function idbGet(key) {
- const db = await openRestoreDb();
- return new Promise((resolve, reject) => {
- const tx = db.transaction(RESTORE_DB_STORE, 'readonly');
- const req = tx.objectStore(RESTORE_DB_STORE).get(key);
- req.onsuccess = () => { db.close(); resolve(req.result); };
- req.onerror = () => { db.close(); reject(req.error || new Error('indexedDB get failed')); };
- });
- }
- async function idbDelete(key) {
- const db = await openRestoreDb();
- return new Promise((resolve, reject) => {
- const tx = db.transaction(RESTORE_DB_STORE, 'readwrite');
- tx.objectStore(RESTORE_DB_STORE).delete(key);
- tx.oncomplete = () => { db.close(); resolve(); };
- tx.onerror = () => { db.close(); reject(tx.error || new Error('indexedDB delete failed')); };
- });
- }
- function getPromptTextarea() {
- const textareas = Array.from(document.querySelectorAll('textarea')).filter(isVisible);
- return textareas.find(el => /what would you like to edit\?|what image would you like to generate\?/i.test(el.getAttribute('placeholder') || '')) || textareas[0] || null;
- }
- function getComposerRoot() {
- const textarea = getPromptTextarea();
- if (!textarea) return null;
- return textarea.closest('form') || textarea.parentElement || textarea;
- }
- function getComposerImagePreviews() {
- const root = getComposerRoot();
- if (!root) return [];
- const imgs = Array.from(root.querySelectorAll('img')).filter(img => {
- if (!(img instanceof HTMLImageElement)) return false;
- if (!isVisible(img)) return false;
- const alt = (img.alt || '').toLowerCase();
- const src = img.currentSrc || img.src || '';
- return alt.includes('image to edit') && /^(data:image\/|blob:|https?:)/i.test(src);
- });
- const unique = [];
- const seen = new Set();
- for (const img of imgs) {
- const key = `${img.currentSrc || img.src || ''}|${img.alt || ''}`;
- if (!key || seen.has(key)) continue;
- seen.add(key);
- unique.push(img);
- }
- return unique;
- }
- function getComposerImagePreview() {
- return getComposerImagePreviews()[0] || null;
- }
- function findNativeRetryButton() {
- const buttons = Array.from(document.querySelectorAll('button, [role="button"]'));
- return buttons.find(el => {
- if (!(el instanceof Element)) return false;
- if (!isVisible(el)) return false;
- if (el.closest(`#${PANEL_ID}`)) return false;
- return /\bretry\b/.test(getText(el));
- }) || null;
- }
- function focusPromptTextarea() {
- const textarea = getPromptTextarea();
- if (!(textarea instanceof HTMLTextAreaElement)) return false;
- try {
- textarea.focus();
- const len = textarea.value.length;
- textarea.setSelectionRange(len, len);
- return true;
- } catch {
- return false;
- }
- }
- function findStartButton() {
- const textarea = getPromptTextarea();
- const form = textarea?.closest('form') || null;
- const root = getComposerRoot();
- const candidates = Array.from(document.querySelectorAll('button[type="submit"]')).filter(el => {
- if (!(el instanceof HTMLButtonElement)) return false;
- if (!isVisible(el)) return false;
- if (el.closest(`#${PANEL_ID}`)) return false;
- return !!el.querySelector('svg.lucide-arrow-up') || el.type === 'submit';
- });
- if (!candidates.length) return null;
- const ranked = candidates.map((btn, index) => {
- let score = 0;
- if (form && btn.closest('form') === form) score += 1000;
- if (root && root.contains(btn)) score += 700;
- if (!btn.disabled && btn.getAttribute('aria-disabled') !== 'true') score += 300;
- score += index;
- return { btn, score };
- }).sort((a, b) => b.score - a.score);
- return ranked[0]?.btn || null;
- }
- function getAlertBannerLines(card) {
- if (!(card instanceof Element)) return [];
- const seen = new Set();
- const lines = [];
- for (const el of Array.from(card.querySelectorAll('p, h1, h2, h3, span'))) {
- if (!(el instanceof HTMLElement)) continue;
- if (!isVisible(el)) continue;
- if (el.closest('button, a[href]')) continue;
- const text = normText(el.textContent);
- if (!text || seen.has(text)) continue;
- seen.add(text);
- lines.push(text);
- }
- return lines;
- }
- function reportBannerMatchOnce(kind, lines) {
- const signature = `${kind}|${lines.join(' | ')}`;
- const now = Date.now();
- if (signature === lastBannerSignature && (now - lastBannerSignatureAt) < 2500) return;
- lastBannerSignature = signature;
- lastBannerSignatureAt = now;
- log(`${kind} banner matched: ${lines.join(' / ') || 'no-text'}`, 'warn');
- }
- function analyzeAlertBanner(card) {
- if (!(card instanceof HTMLElement)) return null;
- if (!isVisible(card)) return null;
- if (card.closest(`#${PANEL_ID}`)) return null;
- const hasAlert = !!card.querySelector('svg.lucide-circle-alert');
- if (!hasAlert) return null;
- const rect = card.getBoundingClientRect();
- if (rect.width < 220 || rect.height < 60) return null;
- if (card.querySelector('textarea, input[type="text"], input[type="file"]')) return null;
- const actionEls = Array.from(card.querySelectorAll('a[href], button, [role="button"]')).filter(isVisible);
- const hasSignupHref = actionEls.some(el => /\/sign-up/i.test(el.getAttribute?.('href') || ''));
- const actionTexts = actionEls.map(el => getText(el));
- const hasAction = hasSignupHref || actionTexts.some(t => t.includes('help') || t.includes('sign up') || t.includes('try again'));
- if (!hasAction) return null;
- const lines = getAlertBannerLines(card);
- const combined = lines.join(' | ');
- const isHighDemand = /\bhigh demand\b/i.test(combined) || /please try again soon/i.test(combined);
- const kind = isHighDemand ? 'high-demand' : 'glitch';
- return { card, kind, lines, combined, actionTexts };
- }
- function findBannerInfo(kind = '') {
- const cards = Array.from(document.querySelectorAll('div.rounded-3xl.border, div.border.rounded-3xl'));
- for (const card of cards) {
- const info = analyzeAlertBanner(card);
- if (!info) continue;
- if (kind && info.kind !== kind) continue;
- reportBannerMatchOnce(info.kind, info.lines);
- return info;
- }
- return null;
- }
- function findGlitchBanner() {
- return findBannerInfo('glitch')?.card || null;
- }
- function findHighDemandBanner() {
- return findBannerInfo('high-demand')?.card || null;
- }
- function hasGlitchBanner() {
- return !!findGlitchBanner();
- }
- function hasHighDemandBanner() {
- return !!findHighDemandBanner();
- }
- async function clickNativeRetryAlways() {
- const retryBtn = findNativeRetryButton();
- if (!retryBtn) {
- log('native Retry button not found', 'warn');
- return false;
- }
- log('clicking native Retry', 'warn');
- try {
- retryBtn.click();
- } catch (e) {
- log(`native Retry click failed: ${e.message}`, 'error');
- return false;
- }
- const started = Date.now();
- while (Date.now() - started < 5000) {
- await sleep(120);
- const promptNow = (getPromptTextarea()?.value || '').trim();
- const imageNow = !!getComposerImagePreview();
- if (promptNow || imageNow) {
- log(`native Retry hydrated composer: prompt=${promptNow ? 'yes' : 'no'}, image=${imageNow ? 'yes' : 'no'}`, 'warn');
- await sleep(220);
- return true;
- }
- }
- log('native Retry timeout: composer did not hydrate', 'warn');
- return false;
- }
- async function waitForStartReadyOrGlitch(timeoutMs = 12000) {
- const started = Date.now();
- let sawDisabledSubmit = false;
- while (Date.now() - started < timeoutMs) {
- if (hasGlitchBanner()) return { kind: 'glitch', button: null };
- const btn = findStartButton();
- if (btn) {
- const disabled = btn.disabled || btn.getAttribute('aria-disabled') === 'true';
- if (!disabled) return { kind: 'start', button: btn };
- sawDisabledSubmit = true;
- }
- await sleep(120);
- }
- if (hasGlitchBanner()) return { kind: 'glitch', button: null };
- if (sawDisabledSubmit) log('start button stayed disabled until timeout', 'warn');
- return { kind: 'timeout', button: null };
- }
- async function triggerStartButton(btn, options = {}) {
- if (!btn) return false;
- const shouldSyncCountUi = options.syncCountUi === true;
- try {
- const promptBeforeSync = getPromptTextarea()?.value || '';
- const previewCountBeforeSync = getComposerImagePreviews().length;
- if (shouldSyncCountUi) {
- try {
- await syncForcedImageCountUi('before-submit');
- } catch (e) {
- log(`pre-submit count UI sync failed: ${e.message}`, 'error');
- }
- }
- if (promptBeforeSync) {
- const textareaNow = getPromptTextarea();
- if (textareaNow instanceof HTMLTextAreaElement) {
- const shouldRefreshPrompt = !previewCountBeforeSync;
- if (textareaNow.value !== promptBeforeSync || shouldRefreshPrompt) {
- const restored = restorePromptIntoTextarea(textareaNow, promptBeforeSync, shouldRefreshPrompt);
- log(`pre-submit prompt restore after count sync = ${restored ? 'yes' : 'no'}`, restored ? 'warn' : 'error');
- await sleep(90);
- }
- }
- }
- const form = btn.closest('form');
- if (form && typeof form.requestSubmit === 'function') {
- form.requestSubmit(btn);
- log('start button requestSubmit sent', 'warn');
- } else {
- btn.click();
- log('start button clicked', 'warn');
- }
- return true;
- } catch (e) {
- log(`start button trigger failed: ${e.message}`, 'error');
- return false;
- }
- }
- async function waitForEnabledStartButton(timeoutMs = 7000) {
- const started = Date.now();
- while (Date.now() - started < timeoutMs) {
- const btn = findStartButton();
- if (btn && !btn.disabled && btn.getAttribute('aria-disabled') !== 'true') return btn;
- await sleep(120);
- }
- return null;
- }
- async function waitForPostSubmitOutcome(timeoutMs = 3200) {
- const started = Date.now();
- while (Date.now() - started < timeoutMs) {
- if (hasGlitchBanner()) return { kind: 'glitch' };
- if (hasHighDemandBanner()) return { kind: 'high-demand' };
- await sleep(120);
- }
- return { kind: 'submitted' };
- }
- function getHighDemandRetryDelay(attempt = 0) {
- return 1000 + Math.floor(Math.random() * 1400) + (attempt * 260);
- }
- async function submitWithHighDemandRetry(initialBtn, reason = 'submit', options = {}) {
- const opts = typeof options === 'number' ? { maxExtraRetries: options } : (options || {});
- const maxExtraRetries = Math.max(0, Math.floor(Number(opts.maxExtraRetries ?? 7)));
- const syncCountUiFirstAttempt = opts.syncCountUiFirstAttempt === true;
- let btn = initialBtn || findStartButton();
- if (!btn) {
- log(`submit skipped (${reason}): start button not found`, 'warn');
- return { kind: 'no-start' };
- }
- highDemandRecoveryInFlight++;
- try {
- for (let attempt = 0; attempt <= maxExtraRetries; attempt++) {
- const clicked = await triggerStartButton(btn, { syncCountUi: syncCountUiFirstAttempt && attempt === 0 });
- if (!clicked) return { kind: 'submit-failed' };
- const outcome = await waitForPostSubmitOutcome(3800);
- if (outcome.kind === 'high-demand' && attempt < maxExtraRetries) {
- const delay = getHighDemandRetryDelay(attempt);
- log(`high-demand after submit (${reason}) -> retry #${attempt + 1} in ${delay}ms`, 'warn');
- await sleep(delay);
- btn = await waitForEnabledStartButton(9000);
- if (!btn) {
- log(`high-demand retry stopped (${reason}): start button did not recover`, 'warn');
- return { kind: 'high-demand-timeout' };
- }
- continue;
- }
- if (outcome.kind === 'high-demand') {
- log(`high-demand persisted after ${attempt + 1} submit attempts (${reason})`, 'warn');
- }
- return outcome;
- }
- return { kind: 'submitted' };
- } finally {
- highDemandRecoveryInFlight = Math.max(0, highDemandRecoveryInFlight - 1);
- }
- }
- async function recoverHighDemandGlobally(reason = 'observer') {
- if (highDemandRecoveryInFlight > 0) return false;
- if (!hasHighDemandBanner()) return false;
- highDemandRecoveryInFlight++;
- try {
- for (let attempt = 0; attempt < 8; attempt++) {
- if (!hasHighDemandBanner()) return true;
- const delay = getHighDemandRetryDelay(attempt);
- log(`high-demand watcher (${reason}) -> retry #${attempt + 1} in ${delay}ms`, 'warn');
- await sleep(delay);
- if (!hasHighDemandBanner()) return true;
- const btn = await waitForEnabledStartButton(9000);
- if (!btn) {
- log(`high-demand watcher stopped (${reason}): start button did not recover`, 'warn');
- return false;
- }
- const clicked = await triggerStartButton(btn, { syncCountUi: false });
- if (!clicked) return false;
- const outcome = await waitForPostSubmitOutcome(3800);
- if (outcome.kind === 'glitch') {
- log(`high-demand watcher saw glitch banner (${reason}), stopping`, 'warn');
- return false;
- }
- if (outcome.kind !== 'high-demand') {
- log(`high-demand watcher recovered (${reason}) after ${attempt + 1} retries`, 'warn');
- return true;
- }
- }
- log(`high-demand watcher gave up after 8 retries (${reason})`, 'warn');
- return !hasHighDemandBanner();
- } finally {
- highDemandRecoveryInFlight = Math.max(0, highDemandRecoveryInFlight - 1);
- }
- }
- function scheduleHighDemandWatch(reason = 'observer', delayMs = 140) {
- if (highDemandRecoveryInFlight > 0) return;
- cancelAnimationFrame(highDemandWatchRaf);
- highDemandWatchRaf = requestAnimationFrame(() => {
- setTimeout(() => {
- if (highDemandRecoveryInFlight > 0) return;
- if (!hasHighDemandBanner()) return;
- recoverHighDemandGlobally(reason).catch(e => log(`high-demand watcher failed: ${e.message}`, 'error'));
- }, delayMs);
- });
- }
- function dataUrlToBlob(dataUrl) {
- const match = dataUrl.match(/^data:([^;,]+)?(;base64)?,(.*)$/s);
- if (!match) throw new Error('invalid data URL');
- const mime = match[1] || 'application/octet-stream';
- const isBase64 = !!match[2];
- const data = match[3] || '';
- if (isBase64) {
- const binary = atob(data);
- const bytes = new Uint8Array(binary.length);
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
- return new Blob([bytes], { type: mime });
- }
- return new Blob([decodeURIComponent(data)], { type: mime });
- }
- async function srcToBlob(src) {
- if (!src) throw new Error('empty image src');
- if (src.startsWith('data:')) return dataUrlToBlob(src);
- const fetchOpts = src.startsWith('blob:') ? {} : { credentials: 'include' };
- const res = await nativeFetch(src, fetchOpts);
- if (!res.ok) throw new Error(`image fetch failed: HTTP ${res.status}`);
- return await res.blob();
- }
- function normalizeRestoreOptions(optionsOrAutoSubmitAfterRestore = false, focusPromptAfterRestore = false) {
- if (typeof optionsOrAutoSubmitAfterRestore === 'object' && optionsOrAutoSubmitAfterRestore) {
- return {
- autoSubmitAfterRestore: !!optionsOrAutoSubmitAfterRestore.autoSubmitAfterRestore,
- focusPromptAfterRestore: !!optionsOrAutoSubmitAfterRestore.focusPromptAfterRestore,
- syncCountUiAfterRestore: !!optionsOrAutoSubmitAfterRestore.syncCountUiAfterRestore
- };
- }
- return {
- autoSubmitAfterRestore: !!optionsOrAutoSubmitAfterRestore,
- focusPromptAfterRestore: !!focusPromptAfterRestore,
- syncCountUiAfterRestore: false
- };
- }
- async function captureComposerSnapshot(options = {}) {
- const opts = options && typeof options === 'object' ? options : {};
- const waitForContentMs = Math.max(0, Math.round(Number(opts.waitForContentMs) || 0));
- const deadline = Date.now() + waitForContentMs;
- let textarea = null;
- let prompt = '';
- let previews = [];
- do {
- textarea = getPromptTextarea();
- prompt = textarea?.value || '';
- previews = getComposerImagePreviews();
- if (prompt.trim() || previews.length || !waitForContentMs) break;
- await sleep(120);
- } while (Date.now() < deadline);
- const imageBlobs = [];
- const imageTypes = [];
- let blobSize = 0;
- for (const [index, img] of previews.entries()) {
- try {
- const src = img.currentSrc || img.src || '';
- if (!src) continue;
- const srcKind = src.startsWith('data:') ? 'data' : src.startsWith('blob:') ? 'blob' : src.startsWith('http') ? 'http' : 'other';
- log(`save image #${index + 1}: src kind = ${srcKind}`, 'warn');
- const blob = await srcToBlob(src);
- if (!(blob && blob.size > 0)) continue;
- imageBlobs.push(blob);
- imageTypes.push(blob.type || 'image/png');
- blobSize += blob.size;
- } catch (e) {
- log(`save image #${index + 1} for restore failed: ${e.message}`, 'error');
- }
- }
- return {
- prompt,
- imageBlobs,
- imageTypes,
- blobSize,
- imageCount: imageBlobs.length,
- hasImage: imageBlobs.length > 0
- };
- }
- async function persistRestoreState(snapshot, optionsOrAutoSubmitAfterRestore = false, focusPromptAfterRestore = false, sourceLabel = 'composer') {
- const restoreOptions = normalizeRestoreOptions(optionsOrAutoSubmitAfterRestore, focusPromptAfterRestore);
- const prompt = snapshot?.prompt || '';
- const hasImage = !!snapshot?.hasImage;
- const imageTypes = Array.isArray(snapshot?.imageTypes) ? snapshot.imageTypes : [];
- const blobSize = snapshot?.blobSize || 0;
- const imageBlobs = Array.isArray(snapshot?.imageBlobs)
- ? snapshot.imageBlobs.filter(blob => blob instanceof Blob && blob.size > 0)
- : (snapshot?.imageBlob instanceof Blob && snapshot.imageBlob.size > 0 ? [snapshot.imageBlob] : []);
- const imageCount = snapshot?.imageCount || imageBlobs.length || (hasImage ? 1 : 0);
- if (imageBlobs.length) {
- await idbSet(RESTORE_IMAGE_KEY, imageBlobs);
- } else {
- try { await idbDelete(RESTORE_IMAGE_KEY); } catch {}
- }
- const state = {
- createdAt: Date.now(),
- prompt,
- hasImage: imageBlobs.length > 0,
- imageType: imageTypes[0] || 'image/png',
- imageTypes,
- imageCount,
- autoSubmitAfterRestore: !!restoreOptions.autoSubmitAfterRestore,
- focusPromptAfterRestore: !!restoreOptions.focusPromptAfterRestore,
- syncCountUiAfterRestore: !!restoreOptions.syncCountUiAfterRestore
- };
- sessionStorage.setItem(RESTORE_STATE_KEY, JSON.stringify(state));
- log(`restore state saved [${sourceLabel}]: prompt=${prompt.trim() ? 'yes' : 'no'}, images=${imageBlobs.length}, bytes=${blobSize}, auto=${state.autoSubmitAfterRestore ? 'yes' : 'no'}, focus=${state.focusPromptAfterRestore ? 'yes' : 'no'}`, 'warn');
- }
- async function saveRestoreStateFromPreviousTask(optionsOrAutoSubmitAfterRestore = false, focusPromptAfterRestore = false) {
- await clickNativeRetryAlways();
- const snapshot = await captureComposerSnapshot({ waitForContentMs: 3200 });
- await persistRestoreState(snapshot, optionsOrAutoSubmitAfterRestore, focusPromptAfterRestore, 'previous-task');
- }
- async function saveRestoreStateFromCurrentComposer(optionsOrAutoSubmitAfterRestore = false, focusPromptAfterRestore = false) {
- const snapshot = await captureComposerSnapshot({ waitForContentMs: 1200 });
- await persistRestoreState(snapshot, optionsOrAutoSubmitAfterRestore, focusPromptAfterRestore, 'current-composer');
- }
- async function saveEmptyRestoreState(optionsOrAutoSubmitAfterRestore = false, focusPromptAfterRestore = false) {
- await persistRestoreState({ prompt: '', imageBlobs: [], imageTypes: [], blobSize: 0, imageCount: 0, hasImage: false }, optionsOrAutoSubmitAfterRestore, focusPromptAfterRestore, 'empty');
- }
- function setNativeTextareaValue(textarea, value) {
- if (!(textarea instanceof HTMLTextAreaElement)) return;
- const nextValue = String(value ?? '');
- const previous = textarea.value;
- try { textarea.focus(); } catch {}
- const proto = HTMLTextAreaElement.prototype;
- const desc = Object.getOwnPropertyDescriptor(proto, 'value');
- if (desc && desc.set) desc.set.call(textarea, nextValue);
- else textarea.value = nextValue;
- try {
- const tracker = textarea._valueTracker;
- if (tracker && typeof tracker.setValue === 'function') tracker.setValue(previous);
- } catch {}
- try {
- textarea.dispatchEvent(new InputEvent('beforeinput', {
- bubbles: true,
- cancelable: true,
- inputType: 'insertText',
- data: nextValue
- }));
- } catch {}
- try {
- textarea.dispatchEvent(new InputEvent('input', {
- bubbles: true,
- cancelable: false,
- inputType: 'insertText',
- data: nextValue
- }));
- } catch {
- try { textarea.dispatchEvent(new Event('input', { bubbles: true })); } catch {}
- }
- try { textarea.dispatchEvent(new Event('change', { bubbles: true })); } catch {}
- try { textarea.dispatchEvent(new KeyboardEvent('keyup', { key: 'End', code: 'End', bubbles: true })); } catch {}
- try {
- const len = textarea.value.length;
- textarea.setSelectionRange(len, len);
- } catch {}
- }
- function createTextClipboardData(text) {
- const dt = new DataTransfer();
- try { dt.setData('text/plain', String(text ?? '')); } catch {}
- return dt;
- }
- function createTextPasteEvent(text) {
- const dt = createTextClipboardData(text);
- let evt;
- try { evt = new ClipboardEvent('paste', { bubbles: true, cancelable: true }); }
- catch { evt = new Event('paste', { bubbles: true, cancelable: true }); }
- try { Object.defineProperty(evt, 'clipboardData', { value: dt }); } catch {}
- return evt;
- }
- function restorePromptIntoTextarea(textarea, prompt, refresh = false) {
- if (!(textarea instanceof HTMLTextAreaElement)) return false;
- const nextValue = String(prompt ?? '');
- const proto = HTMLTextAreaElement.prototype;
- const desc = Object.getOwnPropertyDescriptor(proto, 'value');
- const setter = desc && desc.set ? desc.set.bind(textarea) : null;
- const tracker = (() => {
- try { return textarea._valueTracker; } catch { return null; }
- })();
- const writeValue = (value, inputType, data) => {
- const prev = textarea.value;
- try { textarea.focus(); } catch {}
- try { textarea.setSelectionRange(0, textarea.value.length); } catch {}
- if (setter) setter(value);
- else textarea.value = value;
- try { textarea.defaultValue = value; } catch {}
- try { textarea.textContent = value; } catch {}
- try {
- if (tracker && typeof tracker.setValue === 'function') tracker.setValue(prev);
- } catch {}
- try {
- textarea.dispatchEvent(new InputEvent('beforeinput', {
- bubbles: true,
- cancelable: true,
- inputType,
- data
- }));
- } catch {}
- try {
- textarea.dispatchEvent(new InputEvent('input', {
- bubbles: true,
- cancelable: false,
- inputType,
- data
- }));
- } catch {
- try { textarea.dispatchEvent(new Event('input', { bubbles: true })); } catch {}
- }
- try { textarea.dispatchEvent(new Event('change', { bubbles: true })); } catch {}
- };
- if (refresh || textarea.value === nextValue) {
- writeValue('', 'deleteContentBackward', null);
- }
- writeValue(nextValue, 'insertText', nextValue);
- try {
- textarea.dispatchEvent(createTextPasteEvent(nextValue));
- } catch {}
- try { textarea.dispatchEvent(new KeyboardEvent('keydown', { key: 'End', code: 'End', bubbles: true })); } catch {}
- try { textarea.dispatchEvent(new KeyboardEvent('keyup', { key: 'End', code: 'End', bubbles: true })); } catch {}
- try {
- const len = textarea.value.length;
- textarea.setSelectionRange(len, len);
- } catch {}
- return textarea.value === nextValue;
- }
- function createClipboardDataWithFile(file) {
- const dt = new DataTransfer();
- dt.items.add(file);
- return dt;
- }
- function createPasteEvent(file) {
- const dt = createClipboardDataWithFile(file);
- let evt;
- try { evt = new ClipboardEvent('paste', { bubbles: true, cancelable: true }); }
- catch { evt = new Event('paste', { bubbles: true, cancelable: true }); }
- try { Object.defineProperty(evt, 'clipboardData', { value: dt }); } catch {}
- try { Object.defineProperty(evt, 'dataTransfer', { value: dt }); } catch {}
- return evt;
- }
- function createDragEvent(type, file) {
- const dt = createClipboardDataWithFile(file);
- let evt;
- try { evt = new DragEvent(type, { bubbles: true, cancelable: true }); }
- catch { evt = new Event(type, { bubbles: true, cancelable: true }); }
- try { Object.defineProperty(evt, 'dataTransfer', { value: dt }); } catch {}
- return evt;
- }
- function getBestFileInput() {
- const textarea = getPromptTextarea();
- const root = getComposerRoot();
- const inputs = Array.from(document.querySelectorAll('input[type="file"]')).filter(el => el instanceof HTMLInputElement);
- if (!inputs.length) return null;
- const ranked = inputs.map((input, index) => {
- let score = 0;
- if (root && root.contains(input)) score += 1000;
- if (textarea && input.closest('form') && textarea.closest('form') && input.closest('form') === textarea.closest('form')) score += 300;
- if (isVisible(input)) score += 100;
- score += index;
- return { input, score };
- }).sort((a, b) => b.score - a.score);
- return ranked[0]?.input || null;
- }
- function tryRestoreViaFileInputFiles(files) {
- const input = getBestFileInput();
- log(`restore image: chosen file input = ${input ? 'yes' : 'no'}`, 'warn');
- if (!input) return false;
- try {
- const dt = new DataTransfer();
- for (const file of files) dt.items.add(file);
- input.files = dt.files;
- input.dispatchEvent(new Event('input', { bubbles: true }));
- input.dispatchEvent(new Event('change', { bubbles: true }));
- log(`restore image: file-input path attempted (${files.length} file${files.length === 1 ? '' : 's'})`, 'warn');
- return true;
- } catch (e) {
- log(`restore image: file-input failed: ${e.message}`, 'error');
- return false;
- }
- }
- function tryRestoreViaPasteSingle(file, textarea) {
- const root = getComposerRoot();
- const target = textarea || root || document.body;
- if (!target) return false;
- try {
- if (target instanceof HTMLElement && typeof target.focus === 'function') target.focus();
- target.dispatchEvent(createPasteEvent(file));
- log('restore image: single paste path attempted', 'warn');
- return true;
- } catch (e) {
- log(`restore image: single paste failed: ${e.message}`, 'error');
- return false;
- }
- }
- function tryRestoreViaDropSingle(file, textarea) {
- const root = getComposerRoot();
- const target = root || textarea || document.body;
- if (!target || !(target instanceof EventTarget)) return false;
- try {
- target.dispatchEvent(createDragEvent('dragenter', file));
- target.dispatchEvent(createDragEvent('dragover', file));
- target.dispatchEvent(createDragEvent('drop', file));
- log('restore image: single drop path attempted', 'warn');
- return true;
- } catch (e) {
- log(`restore image: single drop failed: ${e.message}`, 'error');
- return false;
- }
- }
- async function tryRestoreFilesPath(files, textarea) {
- if (!Array.isArray(files) || !files.length) return '';
- if (tryRestoreViaFileInputFiles(files)) return 'file';
- let pasted = 0;
- for (const file of files) {
- if (tryRestoreViaPasteSingle(file, textarea)) {
- pasted++;
- await sleep(120);
- continue;
- }
- pasted = 0;
- break;
- }
- if (pasted === files.length) return 'paste';
- let dropped = 0;
- for (const file of files) {
- if (tryRestoreViaDropSingle(file, textarea)) {
- dropped++;
- await sleep(120);
- continue;
- }
- dropped = 0;
- break;
- }
- if (dropped === files.length) return 'drop';
- return '';
- }
- async function clearRestoreState() {
- try { sessionStorage.removeItem(RESTORE_STATE_KEY); } catch {}
- try { await idbDelete(RESTORE_IMAGE_KEY); } catch {}
- }
- async function restoreStateAfterReset() {
- const raw = sessionStorage.getItem(RESTORE_STATE_KEY);
- if (!raw) return;
- const state = JSON.parse(raw);
- let imageBlobs = [];
- if (state.hasImage) {
- try {
- const stored = await idbGet(RESTORE_IMAGE_KEY);
- imageBlobs = Array.isArray(stored)
- ? stored.filter(blob => blob instanceof Blob && blob.size > 0)
- : (stored instanceof Blob && stored.size > 0 ? [stored] : []);
- log(`restore image blobs loaded: ${imageBlobs.length} item(s), ${imageBlobs.reduce((sum, blob) => sum + (blob?.size || 0), 0)} bytes`, 'warn');
- } catch (e) {
- log(`restore image load failed: ${e.message}`, 'error');
- }
- }
- const wantedImageCount = Math.max(Number(state.imageCount) || 0, imageBlobs.length);
- let attempts = 0;
- let imageInjectDone = false;
- let autoSubmitted = false;
- const tick = async () => {
- attempts++;
- const textarea = getPromptTextarea();
- const startBtnNow = findStartButton();
- const startReadyNow = !!(startBtnNow && !startBtnNow.disabled && startBtnNow.getAttribute('aria-disabled') !== 'true');
- if (state.prompt && textarea) {
- const needsPromptWrite = textarea.value !== state.prompt;
- const needsPromptRefresh = !state.hasImage && !startReadyNow && attempts <= 12 && (attempts === 1 || attempts % 2 === 0);
- if (needsPromptWrite || needsPromptRefresh) {
- const restored = restorePromptIntoTextarea(textarea, state.prompt, needsPromptRefresh);
- log(`restore prompt attempt #${attempts}: ${restored ? 'applied' : 'failed'}`, restored ? 'warn' : 'error');
- await sleep(90);
- }
- }
- const promptOk = !state.prompt || (textarea && textarea.value === state.prompt);
- const previews = getComposerImagePreviews();
- let imageOk = !state.hasImage || previews.length >= wantedImageCount;
- if (state.hasImage) {
- if (!imageOk && imageBlobs.length && !imageInjectDone && attempts >= 2) {
- const files = imageBlobs.map((blob, index) => {
- const mime = blob.type || (state.imageTypes && state.imageTypes[index]) || state.imageType || 'image/png';
- const ext = mime.includes('jpeg') ? 'jpg' : mime.includes('webp') ? 'webp' : 'png';
- return new File([blob], `restore-${index + 1}.${ext}`, { type: mime, lastModified: Date.now() + index });
- });
- log(`restore attempt #${attempts}: trying image reinsert (${files.length} file${files.length === 1 ? '' : 's'})`, 'warn');
- const method = await tryRestoreFilesPath(files, textarea);
- imageInjectDone = true;
- log(`restore attempt #${attempts}: chosen method = ${method || 'none'}`, method ? 'warn' : 'error');
- }
- }
- if (promptOk && imageOk && state.autoSubmitAfterRestore && !autoSubmitted) {
- const startState = await waitForStartReadyOrGlitch(12000);
- if (startState.kind === 'start') {
- autoSubmitted = true;
- preferStoredImageCount(getForcedImageCount(), 12000, 'post-reset-submit');
- const submitOutcome = await submitWithHighDemandRetry(startState.button, 'post-reset', { syncCountUiFirstAttempt: true, maxExtraRetries: 9 });
- log(`post-reset auto submit result: ${submitOutcome.kind}`, submitOutcome.kind === 'glitch' ? 'error' : 'warn');
- } else {
- log(`post-reset auto submit skipped: ${startState.kind}`, 'warn');
- autoSubmitted = true;
- }
- }
- if (promptOk && imageOk && (!state.autoSubmitAfterRestore || autoSubmitted)) {
- if (state.syncCountUiAfterRestore) {
- try {
- await syncForcedImageCountUi('post-reset-default');
- } catch (e) {
- log(`post-reset default count sync failed: ${e.message}`, 'error');
- }
- }
- if (state.focusPromptAfterRestore && !state.autoSubmitAfterRestore) {
- const focused = focusPromptTextarea();
- log(`post-reset focus applied = ${focused ? 'yes' : 'no'}`, focused ? 'warn' : 'error');
- }
- await clearRestoreState();
- log('restore completed', 'warn');
- return;
- }
- if (attempts >= 24) {
- log(`restore timed out: prompt=${promptOk ? 'ok' : 'no'}, images=${imageOk ? 'ok' : `${previews.length}/${wantedImageCount}`}`, 'warn');
- return;
- }
- await sleep(700);
- return tick();
- };
- await tick();
- }
- function scheduleHighDemandChecks(reason = 'submit') {
- [900, 2200, 4200, 6800, 9500].forEach(delay => {
- setTimeout(() => {
- if (highDemandRecoveryInFlight > 0) return;
- if (!hasHighDemandBanner()) return;
- recoverHighDemandGlobally(`${reason}@${delay}`).catch(e => log(`high-demand watcher failed: ${e.message}`, 'error'));
- }, delay);
- });
- }
- function noteImageSubmitDispatched(reason = 'submit') {
- scheduleHighDemandChecks(reason);
- if (isAutoRehydrateEnabled()) schedulePostStartRehydrate(reason);
- }
- function schedulePostStartRehydrate(reason = 'submit') {
- if (!isAutoRehydrateEnabled()) return;
- const jobId = ++postStartRetryJobId;
- const delays = [2200, 3400, 5200];
- (async () => {
- if (postStartRetryInFlight && jobId !== postStartRetryJobId) return;
- postStartRetryInFlight = true;
- try {
- for (const delay of delays) {
- await sleep(delay);
- if (jobId !== postStartRetryJobId) return;
- if (hasGlitchBanner() || hasHighDemandBanner()) continue;
- const promptNow = (getPromptTextarea()?.value || '').trim();
- const imageNow = !!getComposerImagePreview();
- if (promptNow || imageNow) {
- log(`post-submit rehydrate skipped: composer already filled (${reason})`, 'warn');
- return;
- }
- const retryBtn = findNativeRetryButton();
- if (!retryBtn) continue;
- const ok = await clickNativeRetryAlways();
- if (jobId !== postStartRetryJobId) return;
- if (ok) {
- focusPromptTextarea();
- log(`post-submit rehydrate applied (${reason})`, 'warn');
- return;
- }
- }
- log(`post-submit rehydrate skipped/timed out (${reason})`, 'warn');
- } catch (e) {
- log(`post-submit rehydrate failed (${reason}): ${e.message}`, 'error');
- } finally {
- if (jobId === postStartRetryJobId) postStartRetryInFlight = false;
- }
- })();
- }
- function performCacheReset() {
- log('starting cache reset', 'warn');
- const lsPatterns = [
- /flushAfter/i, /imagine/i, /generation/i, /grok-imagine/i,
- /mixpanel|mp_/i, /distinct_id|device_id/i, /_rst|_r/i,
- /event|payload/i, /quota|limit|usage|count|reset/i,
- /video|vid|vidgen|grok-imagine-video/i, /request_type/i,
- /credit|credits|demand|highdemand|purchase|balance|high demand/i
- ];
- for (let i = localStorage.length - 1; i >= 0; i--) {
- const key = localStorage.key(i);
- if (key && ![DEBUG_LOG_KEY, FORCE_2K_KEY, FORCE_COUNT_KEY, AUTO_REHYDRATE_KEY].includes(key) && lsPatterns.some(p => p.test(key))) {
- localStorage.removeItem(key);
- }
- }
- for (let i = sessionStorage.length - 1; i >= 0; i--) {
- const key = sessionStorage.key(i);
- if (key && ![RESTORE_STATE_KEY, DEFAULT_UI_SYNC_KEY].includes(key) && lsPatterns.some(p => p.test(key))) {
- sessionStorage.removeItem(key);
- }
- }
- const cookiePatterns = ['mp_', 'mixpanel', 'distinct_id', 'device_id', 'xai_', 'imagine', 'console', 'grok', '_rst', '_r', 'video', 'vid', 'credit', 'demand', 'high', 'purchase', 'balance'];
- document.cookie.split(';').forEach(c => {
- const name = c.split('=')[0]?.trim();
- if (!name) return;
- if (cookiePatterns.some(p => name.toLowerCase().includes(p.toLowerCase()))) {
- ['.x.ai', 'console.x.ai', '.console.x.ai', ''].forEach(d => {
- ['/', '/playground', '/playground/imagine', '/team', ''].forEach(p => {
- document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=${p}${d ? ';domain=' + d : ''}`;
- });
- });
- }
- });
- const u = new URL(location.href);
- const now = Date.now();
- u.searchParams.set('_r', now);
- u.searchParams.set('_rst', now);
- u.searchParams.set('_rstv', now);
- u.searchParams.set('_vidrst', now);
- location.replace(u.toString());
- }
- async function repeatReset(autoSubmitAfterRestore = false, focusPromptAfterRestore = false) {
- await closeImageMenus(findToolbarAnchor()).catch(() => {});
- await saveRestoreStateFromPreviousTask({ autoSubmitAfterRestore, focusPromptAfterRestore });
- performCacheReset();
- }
- async function submitCurrentComposerThroughReset() {
- await closeImageMenus(findToolbarAnchor()).catch(() => {});
- await saveRestoreStateFromCurrentComposer({ autoSubmitAfterRestore: true, focusPromptAfterRestore: false });
- performCacheReset();
- }
- async function newComposerAction() {
- setForce2kEnabled(false);
- setForcedImageCount(DEFAULT_IMAGE_COUNT);
- pendingManualCountPick = 0;
- pendingManualCountPickedAt = 0;
- clearPreferredStoredImageCount();
- update2kButton();
- updateCountButton();
- await closeImageMenus(findToolbarAnchor()).catch(() => {});
- await saveEmptyRestoreState({ autoSubmitAfterRestore: false, focusPromptAfterRestore: true, syncCountUiAfterRestore: true });
- performCacheReset();
- }
- async function smartRetryAction() {
- log('smart Retry action started', 'warn');
- if (hasGlitchBanner()) {
- log('glitch banner visible before Retry -> switching to reset flow', 'warn');
- await repeatReset(true);
- return;
- }
- await clickNativeRetryAlways();
- const state = await waitForStartReadyOrGlitch(12000);
- if (state.kind === 'glitch') {
- log('glitch banner detected before start -> switching to reset flow', 'warn');
- await repeatReset(true);
- return;
- }
- if (state.kind === 'timeout') {
- log('Retry flow stopped: start button was not ready in time', 'warn');
- return;
- }
- const submitOutcome = await submitWithHighDemandRetry(state.button, 'retry', { syncCountUiFirstAttempt: false, maxExtraRetries: 7 });
- if (submitOutcome.kind === 'glitch') {
- log('glitch banner detected after submit -> switching to reset flow', 'warn');
- await repeatReset(true);
- return;
- }
- log(`Retry flow finished: ${submitOutcome.kind}`, 'warn');
- }
- function restoreGlitchProxyButton() {
- if (!(glitchProxyButton instanceof HTMLElement) || !glitchProxyState) {
- glitchProxyButton = null;
- glitchProxyState = null;
- return;
- }
- try {
- if (glitchProxyButton instanceof HTMLButtonElement) glitchProxyButton.disabled = !!glitchProxyState.disabled;
- if (glitchProxyState.hadDisabledAttr) glitchProxyButton.setAttribute('disabled', '');
- else glitchProxyButton.removeAttribute('disabled');
- if (glitchProxyState.ariaDisabled == null) glitchProxyButton.removeAttribute('aria-disabled');
- else glitchProxyButton.setAttribute('aria-disabled', glitchProxyState.ariaDisabled);
- glitchProxyButton.style.pointerEvents = glitchProxyState.pointerEvents;
- glitchProxyButton.style.opacity = glitchProxyState.opacity;
- glitchProxyButton.style.cursor = glitchProxyState.cursor;
- if (glitchProxyState.title == null) glitchProxyButton.removeAttribute('title');
- else glitchProxyButton.setAttribute('title', glitchProxyState.title);
- glitchProxyButton.removeAttribute('data-xai-glitch-proxy');
- } catch {}
- glitchProxyButton = null;
- glitchProxyState = null;
- }
- function unlockGlitchProxyButton() {
- const btn = findStartButton();
- if (!(btn instanceof HTMLElement)) {
- restoreGlitchProxyButton();
- return;
- }
- if (glitchProxyButton && glitchProxyButton !== btn) restoreGlitchProxyButton();
- if (!glitchProxyState || glitchProxyButton !== btn) {
- glitchProxyButton = btn;
- glitchProxyState = {
- disabled: btn instanceof HTMLButtonElement ? btn.disabled : btn.hasAttribute('disabled'),
- hadDisabledAttr: btn.hasAttribute('disabled'),
- ariaDisabled: btn.getAttribute('aria-disabled'),
- pointerEvents: btn.style.pointerEvents || '',
- opacity: btn.style.opacity || '',
- cursor: btn.style.cursor || '',
- title: btn.getAttribute('title')
- };
- }
- try {
- if (btn instanceof HTMLButtonElement) btn.disabled = false;
- btn.removeAttribute('disabled');
- btn.setAttribute('aria-disabled', 'false');
- btn.style.pointerEvents = 'auto';
- btn.style.opacity = '1';
- btn.style.cursor = 'pointer';
- btn.setAttribute('title', 'xAI Tools: текущий composer будет сохранён, затем reset и submit');
- btn.setAttribute('data-xai-glitch-proxy', '1');
- } catch {}
- }
- function updateGlitchSubmitProxyState() {
- if (hasGlitchBanner()) {
- unlockGlitchProxyButton();
- } else {
- restoreGlitchProxyButton();
- }
- }
- function isStartButtonTrigger(target) {
- if (!(target instanceof Element)) return false;
- const trigger = target.closest('button, [role="button"]');
- const startBtn = findStartButton();
- return !!(trigger && startBtn && trigger === startBtn);
- }
- async function runGlitchSubmitProxy(reason = 'submit') {
- if (glitchSubmitProxyBusy) return;
- glitchSubmitProxyBusy = true;
- try {
- log(`glitch submit proxy started (${reason})`, 'warn');
- await submitCurrentComposerThroughReset();
- } finally {
- glitchSubmitProxyBusy = false;
- }
- }
- function installGlitchSubmitProxy() {
- document.addEventListener('click', (event) => {
- if (!hasGlitchBanner()) return;
- if (!isStartButtonTrigger(event.target)) return;
- event.preventDefault();
- event.stopPropagation();
- if (typeof event.stopImmediatePropagation === 'function') event.stopImmediatePropagation();
- runGlitchSubmitProxy('click').catch(e => log(`glitch submit proxy failed: ${e.message}`, 'error'));
- }, true);
- document.addEventListener('submit', (event) => {
- if (!hasGlitchBanner()) return;
- const form = event.target instanceof HTMLFormElement ? event.target : null;
- const startBtn = findStartButton();
- if (!form || !startBtn || startBtn.closest('form') !== form) return;
- event.preventDefault();
- event.stopPropagation();
- if (typeof event.stopImmediatePropagation === 'function') event.stopImmediatePropagation();
- runGlitchSubmitProxy('form-submit').catch(e => log(`glitch submit proxy failed: ${e.message}`, 'error'));
- }, true);
- }
- function makeBaseButton() {
- const btn = document.createElement('button');
- btn.type = 'button';
- btn.className =
- 'inline-flex items-center justify-center gap-x-2 rounded-full ' +
- 'text-sm font-medium transition-colors focus-visible:outline-none ' +
- 'focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none ' +
- 'disabled:opacity-50 border border-input text-primary ' +
- 'hover:border-primary/15 shadow-sm hover:bg-overlay-hover ' +
- 'h-9 px-3 py-2 bg-surface-l1 pointer-events-auto';
- btn.style.margin = '0';
- btn.style.pointerEvents = 'auto';
- btn.style.transition = 'opacity 0.15s ease, transform 0.12s ease, background-color 0.15s ease, box-shadow 0.15s ease';
- return btn;
- }
- function update2kButton() {
- const btn = document.getElementById(BTN_2K_ID);
- if (!btn) return;
- const enabled = isForce2kEnabled();
- btn.textContent = enabled ? '2K' : '1K';
- btn.title = enabled ? 'Сейчас 2K' : 'Сейчас 1K';
- btn.style.background = '';
- btn.style.boxShadow = '';
- }
- function updateCountButton() {
- const btn = document.getElementById(BTN_COUNT_ID);
- if (!btn) return;
- const count = getForcedImageCount();
- btn.textContent = `x${count}`;
- btn.title = `Сейчас x${count}`;
- btn.style.background = '';
- btn.style.boxShadow = '';
- }
- function downloadTextFile(text, filename) {
- const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = filename;
- a.style.display = 'none';
- document.body.appendChild(a);
- a.click();
- a.remove();
- setTimeout(() => URL.revokeObjectURL(url), 30000);
- }
- async function downloadPersistentLog() {
- const text = getPersistentLog() || `${TAG} log is empty`;
- const filename = `xai-tools-log ${fileStamp()}.txt`;
- downloadTextFile(text, filename);
- try {
- if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
- await navigator.clipboard.writeText(text);
- log('persistent log downloaded and copied to clipboard', 'warn');
- } else {
- log('persistent log downloaded', 'warn');
- }
- } catch (e) {
- log(`persistent log downloaded, clipboard copy failed: ${e.message}`, 'error');
- }
- }
- function isLogButtonVisible() {
- try {
- return localStorage.getItem(SHOW_LOG_BTN_KEY) === '1';
- } catch {
- return false;
- }
- }
- function setLogButtonVisible(visible) {
- try {
- localStorage.setItem(SHOW_LOG_BTN_KEY, visible ? '1' : '0');
- } catch {}
- return !!visible;
- }
- function updateLogButtonVisibility() {
- const btn = document.getElementById(BTN_LOG_ID);
- if (!btn) return;
- const visible = isLogButtonVisible();
- btn.style.display = visible ? '' : 'none';
- positionPanel();
- }
- function createPanel() {
- if (!document.body || document.getElementById(PANEL_ID)) return;
- const panel = document.createElement('div');
- panel.id = PANEL_ID;
- panel.style.position = 'fixed';
- panel.style.display = 'flex';
- panel.style.gap = `${GAP_PX}px`;
- panel.style.alignItems = 'center';
- panel.style.zIndex = '9999';
- panel.style.opacity = '0';
- panel.style.pointerEvents = 'auto';
- panel.style.transition = 'opacity 0.15s ease';
- panel.style.left = '16px';
- panel.style.top = '16px';
- const btn2k = makeBaseButton();
- btn2k.id = BTN_2K_ID;
- btn2k.style.minWidth = '48px';
- btn2k.addEventListener('click', () => {
- const next = !isForce2kEnabled();
- setForce2kEnabled(next);
- update2kButton();
- positionPanel();
- log(`2K forcing ${next ? 'enabled' : 'disabled'}`, 'warn');
- });
- const btnCount = makeBaseButton();
- btnCount.id = BTN_COUNT_ID;
- btnCount.style.minWidth = '48px';
- btnCount.addEventListener('click', () => {
- const next = cycleForcedImageCount();
- pendingManualCountPick = 0;
- pendingManualCountPickedAt = 0;
- preferStoredImageCount(next, 15000, 'panel-button');
- updateCountButton();
- positionPanel();
- log(`forced image count -> x${next}`, 'warn');
- syncForcedImageCountUi('panel-button').catch(e => log(`count UI sync failed: ${e.message}`, 'error'));
- });
- const btnRetry = makeBaseButton();
- btnRetry.id = BTN_RETRY_ID;
- btnRetry.textContent = 'Retry';
- btnRetry.style.minWidth = '62px';
- btnRetry.title = 'Вернуть прошлый вариант и сразу запустить';
- btnRetry.addEventListener('click', () => smartRetryAction().catch(e => log(`smart Retry failed: ${e.message}`, 'error')));
- const btnReset = makeBaseButton();
- btnReset.id = BTN_RESET_ID;
- btnReset.textContent = 'New';
- btnReset.style.minWidth = '62px';
- btnReset.title = 'Чистый composer после reset';
- btnReset.addEventListener('click', () => newComposerAction().catch(e => log(`New action failed: ${e.message}`, 'error')));
- const btnLog = makeBaseButton();
- btnLog.id = BTN_LOG_ID;
- btnLog.textContent = 'Log';
- btnLog.style.minWidth = '54px';
- btnLog.title = 'Download debug log';
- btnLog.addEventListener('click', () => downloadPersistentLog().catch(e => log(`log download failed: ${e.message}`, 'error')));
- panel.appendChild(btn2k);
- panel.appendChild(btnCount);
- panel.appendChild(btnRetry);
- panel.appendChild(btnReset);
- panel.appendChild(btnLog);
- document.body.appendChild(panel);
- update2kButton();
- updateCountButton();
- updateLogButtonVisibility();
- }
- function findToolbarAnchor() {
- const textarea = getPromptTextarea();
- const buttons = Array.from(document.querySelectorAll('button[aria-haspopup="menu"]')).filter(el => {
- if (!(el instanceof HTMLElement)) return false;
- if (!isVisible(el)) return false;
- if (el.closest(`#${PANEL_ID}`)) return false;
- const span = el.querySelector('span');
- const chevron = el.querySelector('svg.lucide-chevrons-up-down');
- return span?.textContent?.trim() === 'Image' && chevron;
- });
- if (!buttons.length) return null;
- if (!textarea) return buttons[0] || null;
- const textareaRect = textarea.getBoundingClientRect();
- return buttons.map((btn, index) => {
- const rect = btn.getBoundingClientRect();
- const dx = Math.abs(rect.left - textareaRect.right);
- const dy = Math.abs((rect.top + rect.height / 2) - (textareaRect.top + textareaRect.height / 2));
- const score = dx + (dy * 2) + index;
- return { btn, score };
- }).sort((a, b) => a.score - b.score)[0]?.btn || buttons[0] || null;
- }
- function isViewerOpen() {
- const controls = Array.from(document.querySelectorAll('button, a, [role="button"]')).filter(el => {
- if (!isVisible(el)) return false;
- if (el.closest(`#${PANEL_ID}`)) return false;
- return true;
- });
- const hasDownload = controls.some(el => /\bdownload\b/.test(getText(el)));
- const hasEdit = controls.some(el => /\bedit\b/.test(getText(el)));
- return hasDownload && hasEdit;
- }
- function positionPanel() {
- const panel = document.getElementById(PANEL_ID);
- if (!panel) return;
- if (isViewerOpen()) {
- panel.style.opacity = '0';
- panel.style.pointerEvents = 'none';
- return;
- }
- const target = findToolbarAnchor();
- if (!target) {
- panel.style.opacity = '0';
- panel.style.pointerEvents = 'none';
- return;
- }
- const rect = target.getBoundingClientRect();
- const panelRect = panel.getBoundingClientRect();
- let left = rect.left - panelRect.width - GAP_PX;
- let top = rect.top + ((rect.height - panelRect.height) / 2);
- if (left < 16) left = 16;
- if (top < 12) top = 12;
- panel.style.left = `${left}px`;
- panel.style.top = `${top}px`;
- panel.style.opacity = '1';
- panel.style.pointerEvents = 'auto';
- }
- function scoreImage(img) {
- const rect = img.getBoundingClientRect();
- return (img.naturalWidth || rect.width || 0) * (img.naturalHeight || rect.height || 0);
- }
- function isVariantImage(img) {
- if (!(img instanceof HTMLImageElement)) return false;
- if (!isVisible(img)) return false;
- const src = img.currentSrc || img.src || '';
- const alt = (img.alt || '').toLowerCase();
- if (!src) return false;
- const looksRelevant = src.startsWith('data:image/') || src.startsWith('blob:') || src.startsWith('http') || alt.includes('variant');
- if (!looksRelevant) return false;
- const w = img.naturalWidth || img.width || 0;
- const h = img.naturalHeight || img.height || 0;
- return w >= 256 && h >= 256;
- }
- function findImageNearTrigger(trigger) {
- let node = trigger.closest('button, a, [role="button"]');
- for (let i = 0; i < 10 && node; i++, node = node.parentElement) {
- const imgs = Array.from(node.querySelectorAll('img')).filter(isVariantImage);
- if (imgs.length) {
- imgs.sort((a, b) => scoreImage(b) - scoreImage(a));
- return imgs[0];
- }
- }
- const all = Array.from(document.querySelectorAll('img')).filter(isVariantImage);
- all.sort((a, b) => scoreImage(b) - scoreImage(a));
- return all[0] || null;
- }
- function loadImage(src) {
- return new Promise((resolve, reject) => {
- const img = new Image();
- img.decoding = 'async';
- img.onload = () => resolve(img);
- img.onerror = () => reject(new Error('image load failed'));
- img.src = src;
- });
- }
- function canvasToBlob(canvas, type, quality) {
- return new Promise((resolve, reject) => {
- canvas.toBlob(blob => blob ? resolve(blob) : reject(new Error('canvas.toBlob failed')), type, quality);
- });
- }
- async function imageSrcToJpegBlob(src) {
- const img = await loadImage(src);
- const width = img.naturalWidth || img.width;
- const height = img.naturalHeight || img.height;
- if (!width || !height) throw new Error('invalid image dimensions');
- const canvas = document.createElement('canvas');
- canvas.width = width;
- canvas.height = height;
- const ctx = canvas.getContext('2d', { alpha: false });
- ctx.fillStyle = '#ffffff';
- ctx.fillRect(0, 0, width, height);
- ctx.drawImage(img, 0, 0);
- return await canvasToBlob(canvas, 'image/jpeg', JPEG_QUALITY);
- }
- function makeTimestampRu() {
- const d = new Date();
- return `${pad2(d.getDate())}.${pad2(d.getMonth() + 1)}.${d.getFullYear()} ${pad2(d.getHours())}-${pad2(d.getMinutes())}-${pad2(d.getSeconds())}`;
- }
- function makeFilename() {
- return `Grok API - ${makeTimestampRu()}.jpg`;
- }
- function downloadBlob(blob, filename) {
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = filename;
- a.style.display = 'none';
- document.body.appendChild(a);
- a.click();
- a.remove();
- setTimeout(() => URL.revokeObjectURL(url), 30000);
- }
- function markBypass(el) {
- if (!(el instanceof Element)) return;
- el.setAttribute(BYPASS_ATTR, '1');
- setTimeout(() => {
- try { el.removeAttribute(BYPASS_ATTR); } catch {}
- }, 300);
- }
- function invokeOriginalDownload(trigger) {
- if (!(trigger instanceof HTMLElement)) return;
- markBypass(trigger);
- trigger.click();
- }
- function isDownloadTrigger(el) {
- if (!(el instanceof Element)) return false;
- const trigger = el.closest('button, a, [role="button"]');
- if (!trigger) return false;
- if (trigger.getAttribute(BYPASS_ATTR) === '1') return false;
- if ([BTN_2K_ID, BTN_COUNT_ID, BTN_RETRY_ID, BTN_RESET_ID, BTN_LOG_ID].includes(trigger.id)) return false;
- if (trigger.closest(`#${PANEL_ID}`)) return false;
- return /\bdownload\b|\bsave\b/.test(getText(trigger));
- }
- function installDownloadInterceptor() {
- document.addEventListener('click', async (event) => {
- const target = event.target;
- if (!(target instanceof Element)) return;
- if (!isDownloadTrigger(target)) return;
- const trigger = target.closest('button, a, [role="button"]');
- if (!trigger) return;
- const img = findImageNearTrigger(trigger);
- if (!img) return;
- const src = img.currentSrc || img.src || '';
- if (!src) return;
- event.preventDefault();
- event.stopPropagation();
- if (typeof event.stopImmediatePropagation === 'function') event.stopImmediatePropagation();
- try {
- const jpegBlob = await imageSrcToJpegBlob(src);
- const filename = makeFilename();
- downloadBlob(jpegBlob, filename);
- log(`saved as jpeg: ${filename}`);
- } catch (e) {
- log(`jpeg conversion failed, fallback to native: ${e.message}`, 'error');
- invokeOriginalDownload(trigger);
- }
- }, true);
- }
- function installHighDemandWatcher() {
- const observer = new MutationObserver(() => {
- updateGlitchSubmitProxyState();
- if (highDemandRecoveryInFlight > 0) return;
- if (!hasHighDemandBanner()) return;
- scheduleHighDemandWatch('mutation', 180);
- });
- observer.observe(document.documentElement, {
- childList: true,
- subtree: true
- });
- }
- function installPanelHotkeys() {
- document.addEventListener('keydown', (event) => {
- if (!(event.ctrlKey && event.key === 'F12')) return;
- if (event.altKey || event.shiftKey || event.metaKey) return;
- event.preventDefault();
- const next = !isLogButtonVisible();
- setLogButtonVisible(next);
- updateLogButtonVisibility();
- log(`log button ${next ? 'shown' : 'hidden'} (Ctrl+F12)`, 'warn');
- }, true);
- }
- function initUi() {
- const rawRestore = (() => { try { return sessionStorage.getItem(RESTORE_STATE_KEY); } catch { return null; } })();
- if (!rawRestore) {
- setForce2kEnabled(false);
- setForcedImageCount(DEFAULT_IMAGE_COUNT);
- pendingManualCountPick = 0;
- pendingManualCountPickedAt = 0;
- clearPreferredStoredImageCount();
- }
- createPanel();
- positionPanel();
- installCountUiObserver();
- installGlitchSubmitProxy();
- installHighDemandWatcher();
- installPanelHotkeys();
- updateGlitchSubmitProxyState();
- const mo = new MutationObserver(() => {
- createPanel();
- positionPanel();
- updateGlitchSubmitProxyState();
- });
- mo.observe(document.documentElement, {
- childList: true,
- subtree: true
- });
- const throttled = (() => {
- let raf = 0;
- return () => {
- cancelAnimationFrame(raf);
- raf = requestAnimationFrame(() => {
- positionPanel();
- });
- };
- })();
- window.addEventListener('resize', throttled);
- window.addEventListener('scroll', throttled, { passive: true });
- setTimeout(() => { positionPanel(); updateGlitchSubmitProxyState(); }, 150);
- setTimeout(() => { positionPanel(); updateGlitchSubmitProxyState(); }, 500);
- setTimeout(() => { positionPanel(); updateGlitchSubmitProxyState(); }, 1200);
- scheduleCountUiStartupSync();
- }
- installDownloadInterceptor();
- if (document.readyState !== 'loading') {
- flushPersistentLogToConsole();
- initUi();
- restoreStateAfterReset().catch(e => log(`restore init failed: ${e.message}`, 'error'));
- } else {
- document.addEventListener('DOMContentLoaded', () => {
- flushPersistentLogToConsole();
- initUi();
- restoreStateAfterReset().catch(e => log(`restore init failed: ${e.message}`, 'error'));
- }, { once: true });
- }
- log(`ready; 2K forcing is ${isForce2kEnabled() ? 'ON' : 'OFF'}, image count is x${getForcedImageCount()}`, 'warn');
- })();
Add Comment
Please, Sign In to add comment