Gemini Editor

Unlocks editing for previous user messages in Gemini chats.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Gemini Editor
// @namespace    https://github.com/ushan0v/gemini-editor
// @version      1.0.0
// @description  Unlocks editing for previous user messages in Gemini chats.
// @author       ushan0v
// @license      MIT
// @match        https://gemini.google.com/*
// @run-at       document-start
// @grant        none
// @icon         https://www.google.com/s2/favicons?sz=64&domain=gemini.google.com
// @homepageURL  https://github.com/ushan0v/gemini-editor
// @supportURL   https://github.com/ushan0v/gemini-editor/issues
// ==/UserScript==

(function () {
    'use strict';

    const DEBUG = false;
    const LOG_PREFIX = '[Gemini Editor]';
    const STYLE_ID = 'gemini-editor-userscript-style';
    const TOOLTIP_ID = 'gemini-editor-tooltip';
    const XHR_URL = Symbol('geminiEditorUrl');
    const XHR_CAPTURE_ATTACHED = Symbol('geminiEditorCaptureAttached');
    const XHR_STREAM_CAPTURE_LENGTH = Symbol('geminiEditorStreamCaptureLength');
    const ATTRS = {
        processed: 'data-gemini-editor-processed',
        wrapper: 'data-gemini-editor-wrapper',
        customButton: 'data-gemini-editor-button',
        optimistic: 'data-gemini-editor-optimistic',
        optimisticAttachments: 'data-gemini-editor-optimistic-attachments',
        attachmentOwned: 'data-gemini-editor-owned',
        attachmentKey: 'data-gemini-editor-attachment-key',
        tooltip: 'data-gemini-editor-tooltip',
    };
    const SELECTORS = {
        appMain: 'main',
        conversationContainer: '.conversation-container',
        userQuery: 'user-query',
        editor: '.ql-editor.textarea',
        textInputField: '.text-input-field',
        queryText: '.query-text',
        queryTextLine: '.query-text-line',
        hiddenText: '.cdk-visually-hidden, .screen-reader-user-query-label, [aria-hidden="true"], [hidden]',
        responseNodes: 'model-response, pending-response, dual-model-response, generative-ui-response',
        inputAreaContainer: '.input-area-container',
        attachmentPreviewWrapper: '.attachment-preview-wrapper',
        nativeAttachmentPreviewWrapper: '.attachment-preview-wrapper:not([data-gemini-editor-owned="true"])',
        ownedAttachmentPreviewWrapper: '.attachment-preview-wrapper[data-gemini-editor-owned="true"]',
        attachmentPreviewContainer: 'uploader-file-preview-container',
        ownedAttachmentPreviewContainer: 'uploader-file-preview-container[data-gemini-editor-owned="true"]',
        userQueryFileButton: '[data-test-id="uploaded-file"] button[aria-label], button.new-file-preview-file[aria-label]',
        userQueryImagePreview: 'img[data-test-id="uploaded-img"]',
        userQueryVideoPreview: 'img[data-test-id="video-thumbnail"]',
        editModeBar: '.gemini-edit-mode-bar',
        copyIcon: 'mat-icon[fonticon="content_copy"]',
        nativeEditIcon: 'mat-icon[fonticon="edit"]',
        nativeEditButton: 'button[data-test-id="prompt-edit-button"]',
        jslog: '[jslog]',
        draftNode: '[data-test-draft-id]',
    };
    const UI_STRINGS = {
        editLabel: 'Edit',
        editTooltip: 'Edit prompt',
        editMode: 'Editing mode',
        cancel: 'Cancel',
        removeFile: 'Remove file',
        imagePreview: 'Image preview',
        videoPreview: 'Video preview',
        openImagePreview: 'Open uploaded image preview',
        openVideoPreview: 'Open uploaded video preview',
        unknownType: 'Unknown',
    };

    const state = {
        editTargetContainer: null,
        editContextPath: null,
        pendingOverride: null,
        optimisticContainer: null,
        attachmentCache: new Map(),
        attachmentCarryover: null,
        cacheScopeConversationId: null,
        observer: null,
        scanQueued: false,
        uiStarted: false,
        tooltipTarget: null,
    };

    const CODE_FILE_EXTENSIONS = new Set([
        'astro', 'bash', 'bat', 'c', 'cc', 'cfg', 'conf', 'cpp', 'cs',
        'css', 'cts', 'cxx', 'go', 'graphql', 'h', 'hpp', 'htm', 'html',
        'ini', 'java', 'js', 'json', 'jsx', 'kt', 'kts', 'less', 'lua',
        'mjs', 'php', 'plist', 'properties', 'ps1',
        'py', 'rb', 'rs', 'sass', 'scss', 'sh', 'sql', 'svelte', 'svg',
        'swift', 'toml', 'ts', 'tsx', 'txt', 'vue', 'xml', 'yaml', 'yml',
        'zsh',
    ]);
    const PLAIN_TEXT_FILE_EXTENSIONS = new Set([
        'csv', 'log', 'md', 'markdown', 'rst', 'text', 'tsv', 'txt',
    ]);
    const ARCHIVE_FILE_EXTENSIONS = new Set([
        '7z', 'bz2', 'gz', 'rar', 'tar', 'tgz', 'xz', 'zip',
    ]);

    function logDebug() {
        if (DEBUG) {
            console.debug(LOG_PREFIX, '[debug]', ...arguments);
        }
    }

    function getUiStrings() {
        return UI_STRINGS;
    }

    function getNativeIconTemplate(fonticon) {
        return document.querySelector(`mat-icon[fonticon="${fonticon}"]`)
            || document.querySelector('mat-icon.google-symbols');
    }

    function getScopeAttributeName(node, prefix) {
        if (!node?.attributes) {
            return null;
        }

        for (const attribute of Array.from(node.attributes)) {
            if (attribute.name.startsWith(prefix)) {
                return attribute.name;
            }
        }

        return null;
    }

    function applyScopeAttribute(node, attributeName) {
        if (node && attributeName) {
            node.setAttribute(attributeName, '');
        }

        return node;
    }

    function getComposerScopeAttributes(textInputField) {
        const field = textInputField || getTextInputField();
        if (!field) {
            return {
                inputContentAttr: null,
                previewContainerHostAttr: null,
                previewChipContentAttr: null,
                previewChipHostAttr: null,
                previewInnerContentAttr: null,
            };
        }

        const inputContentAttr = getScopeAttributeName(field, '_ngcontent-')
            || Array.from(field.children)
                .map((child) => getScopeAttributeName(child, '_ngcontent-'))
                .find(Boolean)
            || null;
        const nativeContainer = field.querySelector(`${SELECTORS.nativeAttachmentPreviewWrapper} ${SELECTORS.attachmentPreviewContainer}`);
        const nativeChip = nativeContainer?.querySelector('uploader-file-preview') || null;
        const nativeInner = nativeChip?.querySelector('.file-preview-container, .file-preview, .image-preview') || null;

        return {
            inputContentAttr,
            previewContainerHostAttr: getScopeAttributeName(nativeContainer, '_nghost-'),
            previewChipContentAttr: getScopeAttributeName(nativeChip, '_ngcontent-'),
            previewChipHostAttr: getScopeAttributeName(nativeChip, '_nghost-'),
            previewInnerContentAttr: getScopeAttributeName(nativeInner, '_ngcontent-'),
        };
    }

    function getOwnedTooltipElement() {
        let tooltip = document.getElementById(TOOLTIP_ID);
        if (tooltip) {
            return tooltip;
        }

        tooltip = document.createElement('div');
        tooltip.id = TOOLTIP_ID;
        tooltip.className = 'gemini-editor-tooltip';
        tooltip.setAttribute('role', 'tooltip');
        tooltip.setAttribute('aria-hidden', 'true');
        tooltip.hidden = true;

        const target = document.body || document.documentElement;
        target?.appendChild(tooltip);
        return tooltip;
    }

    function positionOwnedTooltip(target) {
        const tooltip = document.getElementById(TOOLTIP_ID);
        if (!tooltip || !target?.isConnected) {
            return;
        }

        const rect = target.getBoundingClientRect();
        if (!rect.width && !rect.height) {
            return;
        }

        const tooltipRect = tooltip.getBoundingClientRect();
        const viewportWidth = window.innerWidth || document.documentElement.clientWidth || 0;
        const preferredTop = rect.top - tooltipRect.height - 8;
        const fallbackTop = rect.bottom + 8;
        const top = preferredTop >= 8
            ? preferredTop
            : Math.min(fallbackTop, Math.max(8, window.innerHeight - tooltipRect.height - 8));
        const left = Math.min(
            Math.max(8, rect.left + (rect.width / 2) - (tooltipRect.width / 2)),
            Math.max(8, viewportWidth - tooltipRect.width - 8),
        );

        tooltip.style.left = `${Math.round(left)}px`;
        tooltip.style.top = `${Math.round(top)}px`;
    }

    function showOwnedTooltip(target) {
        const tooltipText = target?.getAttribute?.(ATTRS.tooltip);
        if (!tooltipText) {
            return;
        }

        const tooltip = getOwnedTooltipElement();
        state.tooltipTarget = target;
        tooltip.textContent = tooltipText;
        tooltip.hidden = false;
        tooltip.setAttribute('aria-hidden', 'false');
        tooltip.classList.add('visible');
        window.requestAnimationFrame(() => {
            if (state.tooltipTarget === target) {
                positionOwnedTooltip(target);
            }
        });
    }

    function hideOwnedTooltip() {
        state.tooltipTarget = null;
        const tooltip = document.getElementById(TOOLTIP_ID);
        if (!tooltip) {
            return;
        }

        tooltip.classList.remove('visible');
        tooltip.setAttribute('aria-hidden', 'true');
        window.setTimeout(() => {
            if (!tooltip.classList.contains('visible')) {
                tooltip.hidden = true;
            }
        }, 120);
    }

    function syncOwnedTooltipPosition() {
        if (state.tooltipTarget?.isConnected) {
            positionOwnedTooltip(state.tooltipTarget);
            return;
        }

        hideOwnedTooltip();
    }

    function initTooltipController() {
        if (document.documentElement?.dataset?.geminiEditorTooltipReady === 'true') {
            return;
        }

        document.documentElement.dataset.geminiEditorTooltipReady = 'true';

        document.addEventListener('pointerover', (event) => {
            const target = event.target?.closest?.(`[${ATTRS.tooltip}]`);
            if (!target || target === state.tooltipTarget) {
                return;
            }

            showOwnedTooltip(target);
        }, true);

        document.addEventListener('pointerout', (event) => {
            if (!state.tooltipTarget) {
                return;
            }

            const currentTarget = event.target?.closest?.(`[${ATTRS.tooltip}]`);
            const relatedTarget = event.relatedTarget?.closest?.(`[${ATTRS.tooltip}]`) || null;
            if (currentTarget === state.tooltipTarget && relatedTarget !== state.tooltipTarget) {
                hideOwnedTooltip();
            }
        }, true);

        document.addEventListener('focusin', (event) => {
            const target = event.target?.closest?.(`[${ATTRS.tooltip}]`);
            if (target) {
                showOwnedTooltip(target);
            }
        }, true);

        document.addEventListener('focusout', (event) => {
            if (event.target?.closest?.(`[${ATTRS.tooltip}]`) === state.tooltipTarget) {
                hideOwnedTooltip();
            }
        }, true);

        window.addEventListener('scroll', syncOwnedTooltipPosition, true);
        window.addEventListener('resize', syncOwnedTooltipPosition);
    }

    function ensureButtonRippleSpan(button) {
        if (!button || button.querySelector(':scope > .mat-ripple.mat-mdc-button-ripple')) {
            return;
        }

        const ripple = document.createElement('span');
        ripple.className = 'mat-ripple mat-mdc-button-ripple';
        button.appendChild(ripple);
    }

    function ensureComposerButtonRipples(root = document) {
        root.querySelectorAll([
            'button[data-test-id="bard-mode-menu-button"]',
            'button.speech_dictation_mic_button',
            'button.send-button.submit',
            `button[${ATTRS.customButton}="true"]`,
        ].join(', ')).forEach(ensureButtonRippleSpan);
    }

    function logDebugIssue() {
        if (DEBUG) {
            console.debug(LOG_PREFIX, '[debug:issue]', ...arguments);
        }
    }

    function decodeHtmlAttributeValue(rawValue) {
        if (typeof rawValue !== 'string') {
            return '';
        }

        return rawValue
            .replace(/"/g, '"')
            .replace(/"/g, '"')
            .replace(/&/g, '&');
    }

    function extractBalancedBracketSegment(source, marker) {
        if (typeof source !== 'string' || typeof marker !== 'string') {
            return null;
        }

        const markerIndex = source.indexOf(marker);
        if (markerIndex === -1) {
            return null;
        }

        const startIndex = source.indexOf('[', markerIndex + marker.length);
        if (startIndex === -1) {
            return null;
        }

        let depth = 0;
        let inString = false;
        let escaped = false;

        for (let index = startIndex; index < source.length; index += 1) {
            const char = source[index];

            if (escaped) {
                escaped = false;
                continue;
            }

            if (char === '\\') {
                escaped = true;
                continue;
            }

            if (char === '"') {
                inString = !inString;
                continue;
            }

            if (inString) {
                continue;
            }

            if (char === '[') {
                depth += 1;
                continue;
            }

            if (char === ']') {
                depth -= 1;
                if (depth === 0) {
                    return source.slice(startIndex, index + 1);
                }
            }
        }

        return null;
    }

    function normalizeJslogMetadata(parsedMetadata) {
        if (!parsedMetadata) {
            return null;
        }

        const data = Array.isArray(parsedMetadata[0]) ? parsedMetadata[0] : parsedMetadata;
        if (!Array.isArray(data)) {
            return null;
        }

        const result = {
            r: typeof data[0] === 'string' && data[0].startsWith('r_') ? data[0] : null,
            c: typeof data[1] === 'string' && data[1].startsWith('c_') ? data[1] : null,
            rc: typeof data[3] === 'string' && data[3].startsWith('rc_') ? data[3] : null,
        };

        return result.r || result.c || result.rc ? result : null;
    }

    function extractDataFromJslog(rawJslog) {
        if (!rawJslog) {
            return null;
        }

        const decoded = decodeHtmlAttributeValue(rawJslog);
        const metadataJson = extractBalancedBracketSegment(decoded, 'BardVeMetadataKey:');
        if (!metadataJson) {
            return null;
        }

        try {
            return normalizeJslogMetadata(JSON.parse(metadataJson));
        } catch (error) {
            logDebugIssue('Failed to parse jslog metadata.', error);
            return null;
        }
    }

    function getJslogDataScore(data) {
        if (!data) {
            return -1;
        }

        return (data.r ? 4 : 0) + (data.c ? 2 : 0) + (data.rc ? 1 : 0);
    }

    function mergeJslogData(base, next) {
        if (!base) {
            return next || null;
        }

        if (!next) {
            return base;
        }

        return {
            r: base.r || next.r || null,
            c: base.c || next.c || null,
            rc: base.rc || next.rc || null,
        };
    }

    function getBestJslogData(root) {
        if (!root) {
            return null;
        }

        const nodes = [];
        if (root instanceof Element && root.hasAttribute('jslog')) {
            nodes.push(root);
        }

        if (root.querySelectorAll) {
            nodes.push(...root.querySelectorAll(SELECTORS.jslog));
        }

        let best = null;
        let bestScore = -1;

        nodes.forEach((node) => {
            const next = extractDataFromJslog(node.getAttribute('jslog'));
            if (!next) {
                return;
            }

            const merged = mergeJslogData(best, next);
            const score = getJslogDataScore(merged);
            if (score > bestScore) {
                best = merged;
                bestScore = score;
            }
        });

        return best;
    }

    function getAttachmentCacheKey(conversationId, messageId) {
        if (!conversationId || !messageId) {
            return null;
        }

        return `${conversationId}::${messageId}`;
    }

    function parseBatchExecuteEntries(rawText) {
        if (typeof rawText !== 'string' || !rawText.length) {
            return [];
        }

        const normalized = rawText.replace(/^\)\]\}'\r?\n+/, '');
        const lines = normalized.split('\n');
        const entries = [];

        for (let index = 0; index < lines.length; index += 1) {
            const line = lines[index];
            if (!line) {
                continue;
            }

            const candidate = /^\d+$/.test(line) ? lines[index + 1] : line;
            if (candidate === undefined) {
                continue;
            }

            try {
                const parsed = JSON.parse(candidate);
                if (Array.isArray(parsed) && parsed.length === 1 && Array.isArray(parsed[0])) {
                    entries.push(parsed[0]);
                } else {
                    entries.push(parsed);
                }

                if (/^\d+$/.test(line)) {
                    index += 1;
                }
            } catch {
                continue;
            }
        }

        return entries;
    }

    function getBatchExecutePayload(rawText, rpcid) {
        const entry = parseBatchExecuteEntries(rawText).find((item) => {
            return Array.isArray(item) && item[0] === 'wrb.fr' && item[1] === rpcid && typeof item[2] === 'string';
        });

        if (!entry) {
            return null;
        }

        try {
            return JSON.parse(entry[2]);
        } catch (error) {
            logDebugIssue('Failed to parse batchexecute payload.', error);
            return null;
        }
    }

    function getStreamGeneratePayloads(rawText) {
        return parseBatchExecuteEntries(rawText)
            .filter((item) => {
                return Array.isArray(item) && item[0] === 'wrb.fr' && typeof item[2] === 'string';
            })
            .map((item) => {
                try {
                    return JSON.parse(item[2]);
                } catch (error) {
                    logDebugIssue('Failed to parse StreamGenerate payload.', error);
                    return null;
                }
            })
            .filter(Boolean);
    }

    function isConversationId(value) {
        return typeof value === 'string' && value.startsWith('c_');
    }

    function isMessageId(value) {
        return typeof value === 'string' && value.startsWith('r_');
    }

    function getStreamPayloadIds(payload) {
        const ids = Array.isArray(payload?.[1]) ? payload[1] : [];
        const conversationId = isConversationId(ids[0])
            ? ids[0]
            : getConversationIdFromLocation();
        const messageId = isMessageId(ids[1])
            ? ids[1]
            : (isMessageId(ids[0]) ? ids[0] : null);

        return { conversationId, messageId };
    }

    function isRawAttachmentEntry(value) {
        return Array.isArray(value)
            && typeof value[2] === 'string'
            && typeof value[5] === 'string'
            && typeof value[11] === 'string';
    }

    function collectTurnLikeNodes(root, out = []) {
        if (!Array.isArray(root)) {
            return out;
        }

        const turnKey = root[0];
        if (Array.isArray(turnKey) && isConversationId(turnKey[0]) && isMessageId(turnKey[1])) {
            out.push(root);
        }

        root.forEach((item) => {
            if (Array.isArray(item)) {
                collectTurnLikeNodes(item, out);
            }
        });

        return out;
    }

    function collectAttachmentArrays(root, out = []) {
        if (Array.isArray(root)) {
            if (root.length > 0 && root.every(isRawAttachmentEntry)) {
                out.push(root);
                return out;
            }

            root.forEach((item) => {
                collectAttachmentArrays(item, out);
            });
            return out;
        }

        if (root && typeof root === 'object') {
            Object.values(root).forEach((item) => {
                collectAttachmentArrays(item, out);
            });
            return out;
        }

        return out;
    }

    function dedupeAttachmentsByToken(attachments) {
        const seen = new Set();
        return attachments.filter((attachment) => {
            if (!attachment?.token || seen.has(attachment.token)) {
                return false;
            }

            seen.add(attachment.token);
            return true;
        });
    }

    function isAttachmentArray(value) {
        return Array.isArray(value) && value.length > 0 && value.every(isRawAttachmentEntry);
    }

    function extractRawAttachmentsFromUserMessage(userMessage) {
        if (!Array.isArray(userMessage)) {
            return [];
        }

        const directCandidates = [
            userMessage?.[4]?.[0]?.[3],
            userMessage?.[4]?.[1],
            userMessage?.[5]?.[0]?.[3],
        ];

        for (const candidate of directCandidates) {
            if (isAttachmentArray(candidate)) {
                return candidate;
            }
        }

        const attachmentArrays = collectAttachmentArrays(userMessage[4] ?? [], []);
        return attachmentArrays.sort((left, right) => right.length - left.length)[0] || [];
    }

    function extractRawAttachmentsFromTurn(turn) {
        if (!Array.isArray(turn)) {
            return [];
        }

        const directUserMessage = Array.isArray(turn?.[2]?.[0])
            ? turn[2][0]
            : null;
        const directAttachments = extractRawAttachmentsFromUserMessage(directUserMessage);
        if (directAttachments.length) {
            return directAttachments;
        }

        const nestedUserMessage = Array.isArray(turn?.[2])
            ? turn[2].find((item) => {
                return Array.isArray(item) && extractRawAttachmentsFromUserMessage(item).length > 0;
            })
            : null;

        return extractRawAttachmentsFromUserMessage(nestedUserMessage);
    }

    function setAttachmentCarryover(conversationId, attachments, meta = {}) {
        const nextAttachments = Array.isArray(attachments)
            ? attachments.map(cloneAttachmentRecord).filter(Boolean)
            : [];

        state.attachmentCarryover = nextAttachments.length
            ? {
                conversationId,
                attachments: nextAttachments,
                createdAt: Date.now(),
                submittedText: normalizePromptText(meta.submittedText ?? ''),
                targetIndex: Number.isInteger(meta.targetIndex) ? meta.targetIndex : null,
            }
            : null;
    }

    function getAttachmentCarryover() {
        if (!state.attachmentCarryover) {
            return null;
        }

        if ((Date.now() - state.attachmentCarryover.createdAt) > 120000) {
            state.attachmentCarryover = null;
            return null;
        }

        return state.attachmentCarryover;
    }

    function promoteAttachmentCarryoverToContainer(container, index) {
        const carryover = getAttachmentCarryover();
        if (!carryover || !container) {
            return false;
        }

        const userQuery = container.querySelector(SELECTORS.userQuery);
        const currentData = mergeJslogData(
            getBestJslogData(userQuery),
            getBestJslogData(container),
        );
        const conversationId = currentData?.c || getConversationIdFromLocation();
        const cacheKey = getAttachmentCacheKey(conversationId, currentData?.r);
        if (!cacheKey || !currentData?.r || (carryover.conversationId && carryover.conversationId !== conversationId)) {
            return false;
        }

        if (Number.isInteger(carryover.targetIndex) && Number.isInteger(index) && index < carryover.targetIndex) {
            return false;
        }

        if (carryover.submittedText) {
            const queryText = getPlainTextFromElement(userQuery?.querySelector(SELECTORS.queryText));
            if (normalizePromptText(queryText) !== carryover.submittedText) {
                return false;
            }
        }

        const existing = state.attachmentCache.get(cacheKey);
        if (Array.isArray(existing) && existing.length) {
            state.attachmentCarryover = null;
            return false;
        }

        state.attachmentCache.set(cacheKey, carryover.attachments.map(cloneAttachmentRecord).filter(Boolean));
        state.attachmentCarryover = null;
        logDebug('Promoted carryover attachments to refreshed message.', {
            conversationId,
            messageId: currentData.r,
            count: state.attachmentCache.get(cacheKey)?.length ?? 0,
        });
        return true;
    }

    function getAttachmentCarryoverForContainer(container, index) {
        const carryover = getAttachmentCarryover();
        if (!carryover || !container) {
            return [];
        }

        const userQuery = container.querySelector(SELECTORS.userQuery);
        const currentData = mergeJslogData(
            getBestJslogData(userQuery),
            getBestJslogData(container),
        );
        const conversationId = currentData?.c || getConversationIdFromLocation();
        if (carryover.conversationId && conversationId && carryover.conversationId !== conversationId) {
            return [];
        }

        if (Number.isInteger(carryover.targetIndex) && Number.isInteger(index) && index < carryover.targetIndex) {
            return [];
        }

        if (carryover.submittedText) {
            const queryText = getPlainTextFromElement(userQuery?.querySelector(SELECTORS.queryText));
            if (normalizePromptText(queryText) !== carryover.submittedText) {
                return [];
            }
        }

        return carryover.attachments.map(cloneAttachmentRecord).filter(Boolean);
    }

    function cloneAttachmentRecord(attachment) {
        if (!attachment) {
            return null;
        }

        return {
            key: attachment.key,
            kind: attachment.kind,
            typeCode: attachment.typeCode,
            filename: attachment.filename,
            displayName: attachment.displayName,
            typeLabel: attachment.typeLabel,
            mime: attachment.mime,
            token: attachment.token,
            previewUrl: attachment.previewUrl,
            downloadUrl: attachment.downloadUrl,
            viewUrl: attachment.viewUrl,
            width: attachment.width,
            height: attachment.height,
            durationSeconds: attachment.durationSeconds,
            payloadRecord: cloneAttachmentPayloadRecord(attachment.payloadRecord),
        };
    }

    function getAttachmentFileExtension(filename) {
        if (typeof filename !== 'string') {
            return '';
        }

        const match = filename.match(/\.([^.]+)$/);
        return match ? match[1].toLowerCase() : '';
    }

    function stripAttachmentExtension(filename) {
        if (typeof filename !== 'string') {
            return '';
        }

        return filename.replace(/\.[^.]+$/, '');
    }

    function truncateMiddle(value, maxLength) {
        if (typeof value !== 'string' || value.length <= maxLength) {
            return value ?? '';
        }

        const edgeLength = Math.max(4, Math.floor((maxLength - 3) / 2));
        return `${value.slice(0, edgeLength)}...${value.slice(-edgeLength)}`;
    }

    function getAttachmentMimeSubtype(mime) {
        if (typeof mime !== 'string' || !mime.includes('/')) {
            return '';
        }

        return mime.split('/')[1]?.split(';')[0]?.trim().toLowerCase() || '';
    }

    function isPlainTextAttachment(attachment) {
        const extension = getAttachmentFileExtension(attachment?.filename);
        const mime = typeof attachment?.mime === 'string' ? attachment.mime.toLowerCase() : '';
        return mime === 'text/plain' || PLAIN_TEXT_FILE_EXTENSIONS.has(extension);
    }

    function isArchiveAttachment(attachment) {
        const extension = getAttachmentFileExtension(attachment?.filename);
        const mime = typeof attachment?.mime === 'string' ? attachment.mime.toLowerCase() : '';
        return ARCHIVE_FILE_EXTENSIONS.has(extension)
            || mime === 'application/zip'
            || mime === 'application/x-zip-compressed'
            || mime === 'application/x-7z-compressed'
            || mime === 'application/x-rar-compressed'
            || mime === 'application/gzip'
            || mime === 'application/x-tar';
    }

    function formatAttachmentTypeLabel(attachment) {
        const strings = getUiStrings();
        const extension = getAttachmentFileExtension(attachment?.filename);

        if (attachment?.kind === 'image') {
            return extension ? extension.toUpperCase() : 'IMG';
        }

        if (attachment?.kind === 'video') {
            return extension ? extension.toUpperCase() : 'VIDEO';
        }

        if (isCodeLikeAttachment(attachment) || isPlainTextAttachment(attachment) || isArchiveAttachment(attachment)) {
            return extension ? extension.toUpperCase() : (getAttachmentMimeSubtype(attachment?.mime) || strings.unknownType).toUpperCase();
        }

        if (attachment?.mime === 'application/octet-stream') {
            return strings.unknownType;
        }

        if (extension && extension.length <= 8) {
            return extension.toUpperCase();
        }

        const mimeSubtype = getAttachmentMimeSubtype(attachment?.mime);
        if (mimeSubtype && mimeSubtype.length <= 8) {
            return mimeSubtype.toUpperCase();
        }

        return strings.unknownType;
    }

    function formatAttachmentDisplayName(attachment) {
        if (!attachment?.filename) {
            return '';
        }

        if (attachment.kind === 'image' || attachment.kind === 'video') {
            return attachment.filename;
        }

        const isUnknownBinary = attachment.mime === 'application/octet-stream'
            && !isCodeLikeAttachment(attachment)
            && !isPlainTextAttachment(attachment)
            && !isArchiveAttachment(attachment);
        const baseName = isUnknownBinary
            ? attachment.filename
            : (stripAttachmentExtension(attachment.filename) || attachment.filename);

        return truncateMiddle(baseName, isUnknownBinary ? 23 : 22);
    }

    function getAttachmentKind(rawAttachment) {
        const mime = rawAttachment?.[11] ?? '';
        const typeCode = Number(rawAttachment?.[1]);

        if (typeCode === 1 || mime.startsWith('image/')) {
            return 'image';
        }

        if (typeCode === 2 || mime.startsWith('video/')) {
            return 'video';
        }

        return 'file';
    }

    function getAttachmentPreviewUrl(rawAttachment, kind) {
        if (kind === 'image' && typeof rawAttachment?.[3] === 'string' && rawAttachment[3]) {
            return rawAttachment[3];
        }

        const urlGroup = Array.isArray(rawAttachment?.[7]) ? rawAttachment[7] : [];
        return typeof urlGroup[0] === 'string' && urlGroup[0] ? urlGroup[0] : null;
    }

    function getAttachmentViewUrl(rawAttachment, kind) {
        const urlGroup = Array.isArray(rawAttachment?.[7]) ? rawAttachment[7] : [];

        if (kind === 'video' && typeof urlGroup[2] === 'string' && urlGroup[2]) {
            return urlGroup[2];
        }

        if (typeof rawAttachment?.[3] === 'string' && rawAttachment[3]) {
            return rawAttachment[3];
        }

        return typeof urlGroup[0] === 'string' && urlGroup[0] ? urlGroup[0] : null;
    }

    function getAttachmentDimensions(rawAttachment, kind) {
        if (kind === 'video' && Array.isArray(rawAttachment?.[16])) {
            return {
                width: Number(rawAttachment[16][2]) || null,
                height: Number(rawAttachment[16][1]) || null,
            };
        }

        if (Array.isArray(rawAttachment?.[15])) {
            return {
                width: Number(rawAttachment[15][0]) || null,
                height: Number(rawAttachment[15][1]) || null,
            };
        }

        return { width: null, height: null };
    }

    function getAttachmentDurationSeconds(rawAttachment) {
        const durationParts = rawAttachment?.[16]?.[0];
        if (!Array.isArray(durationParts)) {
            return null;
        }

        const seconds = Number(durationParts[0]);
        const nanos = Number(durationParts[1]);

        if (!Number.isFinite(seconds)) {
            return null;
        }

        if (!Number.isFinite(nanos)) {
            return seconds;
        }

        return Math.max(0, Math.round(seconds + (nanos / 1000000000)));
    }

    function normalizeConversationAttachment(rawAttachment) {
        if (!Array.isArray(rawAttachment)) {
            return null;
        }

        const kind = getAttachmentKind(rawAttachment);
        const filename = typeof rawAttachment[2] === 'string' ? rawAttachment[2] : '';
        const mime = typeof rawAttachment[11] === 'string' ? rawAttachment[11] : '';
        const token = typeof rawAttachment[5] === 'string' ? rawAttachment[5] : '';

        if (!filename || !mime || !token) {
            return null;
        }

        const { width, height } = getAttachmentDimensions(rawAttachment, kind);
        const attachment = {
            key: token || `${filename}:${mime}`,
            kind,
            typeCode: Number(rawAttachment[1]) || 0,
            filename,
            mime,
            token,
            previewUrl: getAttachmentPreviewUrl(rawAttachment, kind),
            downloadUrl: typeof rawAttachment?.[7]?.[1] === 'string' && rawAttachment[7][1] ? rawAttachment[7][1] : null,
            viewUrl: getAttachmentViewUrl(rawAttachment, kind),
            width,
            height,
            durationSeconds: getAttachmentDurationSeconds(rawAttachment),
        };

        attachment.typeLabel = formatAttachmentTypeLabel(attachment);
        attachment.displayName = formatAttachmentDisplayName(attachment);

        return attachment;
    }

    function cloneAttachmentPayloadRecord(payloadRecord) {
        if (!Array.isArray(payloadRecord)) {
            return null;
        }

        return payloadRecord.map((item) => {
            return Array.isArray(item) ? cloneAttachmentPayloadRecord(item) : item;
        });
    }

    function normalizePayloadAttachmentRecord(payloadRecord, uiAttachment = {}) {
        if (!Array.isArray(payloadRecord) || !Array.isArray(payloadRecord[0])) {
            return null;
        }

        const typeCode = Number(payloadRecord[0][1]) || 0;
        const filename = typeof payloadRecord[1] === 'string' ? payloadRecord[1] : '';
        const mime = typeof payloadRecord[0][3] === 'string' ? payloadRecord[0][3] : '';
        const token = typeof payloadRecord[2] === 'string' ? payloadRecord[2] : '';
        if (!filename || !mime) {
            return null;
        }

        const kind = typeCode === 1 || mime.startsWith('image/')
            ? 'image'
            : (typeCode === 2 || mime.startsWith('video/') ? 'video' : 'file');
        const attachment = {
            key: token || `${filename}:${mime}:${payloadRecord[0][0] || ''}`,
            kind,
            typeCode,
            filename,
            mime,
            token,
            previewUrl: uiAttachment.previewUrl || null,
            downloadUrl: null,
            viewUrl: uiAttachment.viewUrl || uiAttachment.previewUrl || null,
            width: null,
            height: null,
            durationSeconds: uiAttachment.durationSeconds ?? null,
            payloadRecord: cloneAttachmentPayloadRecord(payloadRecord),
        };

        attachment.typeLabel = formatAttachmentTypeLabel(attachment);
        attachment.displayName = formatAttachmentDisplayName(attachment);

        return attachment;
    }

    function buildAttachmentPayloadRecord(attachment) {
        const payloadRecord = cloneAttachmentPayloadRecord(attachment?.payloadRecord);
        if (payloadRecord) {
            return payloadRecord;
        }

        if (!attachment?.filename || !attachment?.mime || !attachment?.token) {
            return null;
        }

        return [
            [null, attachment.typeCode, 1, attachment.mime],
            attachment.filename,
            attachment.token,
        ];
    }

    function getCachedAttachmentsForMessage(conversationId, messageId) {
        const cacheKey = getAttachmentCacheKey(conversationId, messageId);
        const attachments = cacheKey ? state.attachmentCache.get(cacheKey) : null;
        return Array.isArray(attachments)
            ? attachments.map(cloneAttachmentRecord).filter(Boolean)
            : [];
    }

    function storeConversationLoadPayload(payload) {
        const turns = collectTurnLikeNodes(payload);
        const activeConversationId = getConversationIdFromLocation();

        turns.forEach((turn) => {
            const turnKey = Array.isArray(turn?.[0]) ? turn[0] : null;
            const conversationId = turnKey?.[0] ?? null;
            const messageId = turnKey?.[1] ?? null;
            if (activeConversationId && conversationId && conversationId !== activeConversationId) {
                return;
            }

            const cacheKey = getAttachmentCacheKey(conversationId, messageId);
            if (!cacheKey) {
                return;
            }

            const rawAttachmentArray = extractRawAttachmentsFromTurn(turn);
            const attachments = dedupeAttachmentsByToken(
                rawAttachmentArray
                    .map(normalizeConversationAttachment)
                    .filter(Boolean),
            );

            state.attachmentCache.set(cacheKey, attachments);
            logDebug('Stored attachments from conversation-load.', {
                conversationId,
                messageId,
                count: attachments.length,
            });
        });
    }

    function getAttachmentsFromPayload(payload) {
        const rawAttachments = [];
        collectAttachmentArrays(payload).forEach((attachmentArray) => {
            rawAttachments.push(...attachmentArray);
        });

        return dedupeAttachmentsByToken(
            rawAttachments
                .map(normalizeConversationAttachment)
                .filter(Boolean),
        );
    }

    function haveSameAttachmentTokens(left, right) {
        if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
            return false;
        }

        return left.every((attachment, index) => {
            return attachment?.token && attachment.token === right[index]?.token;
        });
    }

    function storeStreamGeneratePayload(payload) {
        const { conversationId, messageId } = getStreamPayloadIds(payload);
        const cacheKey = getAttachmentCacheKey(conversationId, messageId);
        if (!cacheKey) {
            return false;
        }

        const attachments = getAttachmentsFromPayload(payload);
        if (!attachments.length) {
            return false;
        }

        const existing = state.attachmentCache.get(cacheKey);
        if (haveSameAttachmentTokens(existing, attachments)) {
            return false;
        }

        state.attachmentCache.set(cacheKey, attachments);
        logDebug('Stored attachments from StreamGenerate.', {
            conversationId,
            messageId,
            count: attachments.length,
        });
        refreshPendingOverrideAttachments(conversationId, messageId, attachments);
        return true;
    }

    function refreshPendingOverrideAttachments(conversationId, messageId, attachments) {
        if (!state.pendingOverride?.attachments?.length || !state.editTargetContainer || !messageId) {
            return;
        }

        const userQuery = state.editTargetContainer.querySelector(SELECTORS.userQuery);
        const currentData = mergeJslogData(
            getBestJslogData(userQuery),
            getBestJslogData(state.editTargetContainer),
        );
        const targetConversationId = currentData?.c || getConversationIdFromLocation();
        if (currentData?.r !== messageId || (conversationId && targetConversationId && conversationId !== targetConversationId)) {
            return;
        }

        const usedAttachmentIndexes = new Set();
        const nextAttachments = state.pendingOverride.attachments.map((pendingAttachment) => {
            const replacementIndex = attachments.findIndex((attachment, index) => {
                return !usedAttachmentIndexes.has(index)
                    && attachment?.filename === pendingAttachment?.filename
                    && attachment?.kind === pendingAttachment?.kind;
            });
            if (replacementIndex === -1) {
                return pendingAttachment;
            }

            usedAttachmentIndexes.add(replacementIndex);
            return attachments[replacementIndex];
        });

        const upgraded = nextAttachments.some((attachment, index) => {
            return attachment?.token && attachment.token !== state.pendingOverride.attachments[index]?.token;
        });
        if (!upgraded) {
            return;
        }

        state.pendingOverride.attachments = nextAttachments.map(cloneAttachmentRecord).filter(Boolean);
        syncEditComposerAttachmentUi();
        logDebug('Refreshed pending edit attachments from StreamGenerate tokens.', {
            conversationId,
            messageId,
            count: state.pendingOverride.attachments.length,
        });
    }

    function handleConversationLoadResponse(rawText) {
        const payload = getBatchExecutePayload(rawText, 'hNvQHb');
        if (!payload) {
            return false;
        }

        storeConversationLoadPayload(payload);
        return true;
    }

    function handleStreamGenerateResponse(rawText) {
        return getStreamGeneratePayloads(rawText).reduce((storedAny, payload) => {
            return storeStreamGeneratePayload(payload) || storedAny;
        }, false);
    }

    function getAppMain() {
        return document.querySelector(SELECTORS.appMain) || document.body;
    }

    function getEditor() {
        return document.querySelector(SELECTORS.editor);
    }

    function getConversationContainers() {
        return Array.from(document.querySelectorAll(SELECTORS.conversationContainer));
    }

    function normalizeRenderedLineBreaks(value) {
        return typeof value === 'string' ? value.replace(/\r\n/g, '\n').replace(/\n$/, '') : '';
    }

    function normalizePromptText(text) {
        return typeof text === 'string' ? text.replace(/\r\n/g, '\n') : '';
    }

    function getNormalizedPromptLines(text) {
        const lines = normalizePromptText(text ?? '').split('\n');
        return lines.length ? lines : [''];
    }

    function populatePromptLineNode(lineNode, text) {
        lineNode.replaceChildren();

        if (text.length) {
            lineNode.textContent = text;
        } else {
            lineNode.appendChild(document.createElement('br'));
        }

        return lineNode;
    }

    function buildPromptLineNodes(text, createLineNode) {
        return getNormalizedPromptLines(text).map((line) => {
            return populatePromptLineNode(createLineNode(), line);
        });
    }

    function getPromptTextFromQueryElement(element) {
        if (!element) {
            return '';
        }

        const lineNodes = Array.from(element.querySelectorAll(SELECTORS.queryTextLine));
        if (!lineNodes.length) {
            return '';
        }

        return lineNodes
            .map((lineNode) => normalizeRenderedLineBreaks(lineNode.innerText))
            .join('\n');
    }

    function getPromptTextFromEditor(editor) {
        if (!editor) {
            return '';
        }

        const blocks = Array.from(editor.children);
        if (!blocks.length) {
            return normalizeRenderedLineBreaks(editor.innerText);
        }

        return blocks
            .map((block) => normalizeRenderedLineBreaks(block.innerText))
            .join('\n');
    }

    function getPlainTextFromElement(element) {
        if (!element) {
            return '';
        }

        if (element.matches?.(SELECTORS.queryText)) {
            return getPromptTextFromQueryElement(element);
        }

        if (element.matches?.(SELECTORS.editor)) {
            return getPromptTextFromEditor(element);
        }

        const blockTags = new Set(['P', 'DIV', 'LI', 'UL', 'OL', 'PRE', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6']);
        const parts = [];

        function walk(node) {
            node.childNodes.forEach((child) => {
                if (child.nodeType === Node.TEXT_NODE) {
                    parts.push(child.nodeValue ?? '');
                    return;
                }

                if (child.nodeType !== Node.ELEMENT_NODE) {
                    return;
                }

                if (child.matches(SELECTORS.hiddenText)) {
                    return;
                }

                if (child.tagName === 'BR') {
                    parts.push('\n');
                }

                walk(child);

                if (blockTags.has(child.tagName)) {
                    parts.push('\n');
                }
            });
        }

        walk(element);

        let normalized = parts
            .join('')
            .replace(/\r\n/g, '\n');

        if (normalized.endsWith('\n')) {
            normalized = normalized.slice(0, -1);
        }

        return normalized;
    }

    function buildQuillParagraphs(text) {
        return buildPromptLineNodes(text, () => document.createElement('p'));
    }

    function setEditorContent(editor, text) {
        const fragment = document.createDocumentFragment();
        buildQuillParagraphs(text).forEach((node) => fragment.appendChild(node));
        editor.replaceChildren(fragment);
    }

    function getCurrentChatLocationKey() {
        return `${window.location.pathname}${window.location.search}`;
    }

    function getConversationIdFromLocation() {
        const match = window.location.pathname.match(/\/app\/([^/?#]+)/);
        if (!match || !match[1]) {
            return null;
        }

        return match[1].startsWith('c_') ? match[1] : `c_${match[1]}`;
    }

    function syncAttachmentCacheScope() {
        const conversationId = getConversationIdFromLocation();
        if (state.cacheScopeConversationId === conversationId) {
            return;
        }

        if (state.cacheScopeConversationId === null) {
            state.cacheScopeConversationId = conversationId;
            return;
        }

        state.cacheScopeConversationId = conversationId;
        state.attachmentCarryover = null;
        logDebug('Updated active chat scope.', {
            conversationId,
        });
    }

    function getEditorText() {
        const editor = getEditor();
        return editor ? getPlainTextFromElement(editor) : '';
    }

    function getTextInputField() {
        return document.querySelector(SELECTORS.textInputField);
    }

    function isCodeLikeAttachment(attachment) {
        const extension = getAttachmentFileExtension(attachment?.filename);
        if (attachment?.typeCode === 16 || CODE_FILE_EXTENSIONS.has(extension)) {
            return true;
        }

        const mime = typeof attachment?.mime === 'string' ? attachment.mime.toLowerCase() : '';
        return (mime.startsWith('text/')
            && mime !== 'text/plain')
            || mime === 'application/json'
            || mime === 'application/javascript'
            || mime === 'application/x-javascript'
            || mime === 'text/javascript'
            || mime === 'application/xml'
            || mime === 'text/xml'
            || mime === 'application/x-yaml';
    }

    function getAttachmentIconType(attachment) {
        if (isCodeLikeAttachment(attachment)) {
            return 'text/code';
        }

        if (isPlainTextAttachment(attachment)) {
            return 'text/plain';
        }

        if (isArchiveAttachment(attachment)) {
            return getAttachmentFileExtension(attachment?.filename) === 'zip'
                ? 'application/zip'
                : 'application/octet-stream';
        }

        const mime = typeof attachment?.mime === 'string' ? attachment.mime.toLowerCase() : '';
        return mime || 'application/octet-stream';
    }

    function getAttachmentIconUrl(attachment) {
        return `https://drive-thirdparty.googleusercontent.com/32/type/${getAttachmentIconType(attachment)}`;
    }

    function getAttachmentIconAltText(attachment) {
        const strings = getUiStrings();
        return `${attachment?.typeLabel || strings.unknownType} file icon`;
    }

    function formatAttachmentDuration(durationSeconds) {
        if (!Number.isFinite(durationSeconds) || durationSeconds < 0) {
            return '';
        }

        const hours = Math.floor(durationSeconds / 3600);
        const minutes = Math.floor((durationSeconds % 3600) / 60);
        const seconds = Math.floor(durationSeconds % 60);

        if (hours > 0) {
            return `${hours}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
        }

        return `${minutes}:${String(seconds).padStart(2, '0')}`;
    }

    function createMaterialIcon(name) {
        const template = getNativeIconTemplate(name);
        if (template) {
            const icon = template.cloneNode(true);
            icon.removeAttribute('id');
            icon.removeAttribute('aria-hidden');
            icon.setAttribute('aria-hidden', 'true');
            icon.setAttribute('fonticon', name);
            icon.setAttribute('data-mat-icon-name', name);
            if (name === 'play_arrow') {
                icon.classList.add('icon-filled');
            } else {
                icon.classList.remove('icon-filled');
            }
            return icon;
        }

        const icon = document.createElement('mat-icon');
        icon.setAttribute('role', 'img');
        icon.setAttribute('fonticon', name);
        icon.setAttribute('aria-hidden', 'true');
        icon.setAttribute('data-mat-icon-type', 'font');
        icon.setAttribute('data-mat-icon-name', name);
        icon.className = 'mat-icon notranslate google-symbols mat-ligature-font mat-icon-no-color';
        if (name === 'play_arrow') {
            icon.classList.add('icon-filled');
        }
        icon.textContent = name;
        return icon;
    }

    function getComposerNativeRemoveButtons(textInputField) {
        const field = textInputField || getTextInputField();
        if (!field) {
            return [];
        }

        return Array.from(field.querySelectorAll(`uploader-file-preview:not([${ATTRS.attachmentOwned}="true"]) button[data-test-id="cancel-button"]`));
    }

    function clearNativeComposerAttachments(textInputField) {
        const removeButtons = getComposerNativeRemoveButtons(textInputField);
        removeButtons.forEach((button) => {
            button.click();
        });
    }

    function extractFilenameFromRemoveAriaLabel(label) {
        if (typeof label !== 'string' || !label.trim()) {
            return '';
        }

        const strings = getUiStrings();
        const prefixes = [
            strings.removeFile,
        ].filter(Boolean);

        for (const prefix of prefixes) {
            if (label.startsWith(`${prefix} `)) {
                return label.slice(prefix.length + 1).trim();
            }
        }

        return '';
    }

    function getVisibleNativeComposerAttachmentFilenames(textInputField) {
        const field = textInputField || getTextInputField();
        if (!field) {
            return [];
        }

        const filenames = [];
        const nativeChips = Array.from(field.querySelectorAll(`uploader-file-preview:not([${ATTRS.attachmentOwned}="true"])`));
        nativeChips.forEach((chip) => {
            const cancelLabel = chip.querySelector('button[data-test-id="cancel-button"]')?.getAttribute('aria-label');
            const fileNameFromLabel = extractFilenameFromRemoveAriaLabel(cancelLabel);
            if (fileNameFromLabel) {
                filenames.push(fileNameFromLabel);
                return;
            }

            const titledNode = chip.querySelector('[title]');
            const titledValue = titledNode?.getAttribute('title');
            if (titledValue) {
                filenames.push(titledValue);
            }
        });

        return filenames;
    }

    function parseAttachmentDurationLabel(value) {
        if (typeof value !== 'string') {
            return null;
        }

        const parts = value.trim().split(':').map((part) => Number(part));
        if (!parts.length || parts.some((part) => !Number.isFinite(part) || part < 0)) {
            return null;
        }

        return parts.reduce((total, part) => (total * 60) + part, 0);
    }

    function getVisibleNativeComposerAttachmentDetails(textInputField) {
        const field = textInputField || getTextInputField();
        if (!field) {
            return [];
        }

        return Array.from(field.querySelectorAll(`uploader-file-preview:not([${ATTRS.attachmentOwned}="true"])`))
            .map((chip) => {
                const cancelLabel = chip.querySelector('button[data-test-id="cancel-button"]')?.getAttribute('aria-label');
                const filename = extractFilenameFromRemoveAriaLabel(cancelLabel)
                    || chip.querySelector('[title]')?.getAttribute('title')
                    || '';
                const imagePreview = chip.querySelector('img[data-test-id="image-preview"]');
                const videoPreview = chip.querySelector('img[data-test-id="video-preview"]');
                const previewUrl = normalizeAttachmentMatchUrl(
                    imagePreview?.getAttribute('src')
                    || videoPreview?.getAttribute('src')
                    || '',
                );
                const durationSeconds = parseAttachmentDurationLabel(
                    chip.querySelector('[data-test-id="video-timecode"]')?.textContent || '',
                );

                return {
                    filename,
                    previewUrl,
                    viewUrl: previewUrl,
                    durationSeconds,
                };
            })
            .filter((attachment) => attachment.filename || attachment.previewUrl);
    }

    function filterNativePayloadAttachmentsByComposerUi(nativeAttachments, textInputField) {
        if (!Array.isArray(nativeAttachments) || !nativeAttachments.length) {
            return [];
        }

        const visibleFilenames = getVisibleNativeComposerAttachmentFilenames(textInputField);
        if (!visibleFilenames.length) {
            return [];
        }

        const counts = new Map();
        visibleFilenames.forEach((filename) => {
            counts.set(filename, (counts.get(filename) || 0) + 1);
        });

        return nativeAttachments.filter((attachmentRecord) => {
            const filename = typeof attachmentRecord?.[1] === 'string'
                ? attachmentRecord[1]
                : '';
            const remaining = counts.get(filename) || 0;
            if (!filename || remaining < 1) {
                return false;
            }

            counts.set(filename, remaining - 1);
            return true;
        });
    }

    function normalizeNativePayloadAttachments(nativeAttachments, textInputField) {
        if (!Array.isArray(nativeAttachments) || !nativeAttachments.length) {
            return [];
        }

        const uiAttachments = getVisibleNativeComposerAttachmentDetails(textInputField);
        return nativeAttachments
            .map((payloadRecord, index) => {
                const filename = typeof payloadRecord?.[1] === 'string' ? payloadRecord[1] : '';
                const matchingUiAttachment = uiAttachments.find((attachment) => {
                    return attachment.filename && attachment.filename === filename;
                }) || uiAttachments[index] || {};

                return normalizePayloadAttachmentRecord(payloadRecord, matchingUiAttachment);
            })
            .filter(Boolean);
    }

    function normalizeAttachmentMatchUrl(url) {
        return typeof url === 'string' ? url.trim() : '';
    }

    function getVisibleUserQueryAttachmentDescriptors(userQuery) {
        if (!userQuery) {
            return [];
        }

        const previewNodes = Array.from(userQuery.querySelectorAll('user-query-file-preview'));
        const descriptorNodes = previewNodes.length ? previewNodes : [userQuery];
        const descriptors = [];
        descriptorNodes.forEach((node) => {
            const fileButton = node.querySelector(SELECTORS.userQueryFileButton);
            if (fileButton) {
                descriptors.push({
                    kind: 'file',
                    filename: fileButton.getAttribute('aria-label')?.trim() || '',
                    previewUrl: '',
                });
                return;
            }

            const imagePreview = node.querySelector(SELECTORS.userQueryImagePreview);
            if (imagePreview) {
                descriptors.push({
                    kind: 'image',
                    filename: '',
                    previewUrl: normalizeAttachmentMatchUrl(imagePreview.getAttribute('src')),
                });
                return;
            }

            const videoPreview = node.querySelector(SELECTORS.userQueryVideoPreview);
            if (videoPreview) {
                descriptors.push({
                    kind: 'video',
                    filename: '',
                    previewUrl: normalizeAttachmentMatchUrl(videoPreview.getAttribute('src')),
                });
            }
        });

        if (descriptors.length) {
            return descriptors;
        }

        userQuery.querySelectorAll(SELECTORS.userQueryFileButton).forEach((button) => {
            descriptors.push({
                kind: 'file',
                filename: button.getAttribute('aria-label')?.trim() || '',
                previewUrl: '',
            });
        });
        userQuery.querySelectorAll(SELECTORS.userQueryImagePreview).forEach((image) => {
            descriptors.push({
                kind: 'image',
                filename: '',
                previewUrl: normalizeAttachmentMatchUrl(image.getAttribute('src')),
            });
        });
        userQuery.querySelectorAll(SELECTORS.userQueryVideoPreview).forEach((image) => {
            descriptors.push({
                kind: 'video',
                filename: '',
                previewUrl: normalizeAttachmentMatchUrl(image.getAttribute('src')),
            });
        });

        return descriptors.filter((descriptor) => descriptor.filename || descriptor.previewUrl || descriptor.kind);
    }

    function filterCachedAttachmentsByUserQueryUi(attachments, userQuery) {
        if (!Array.isArray(attachments) || !attachments.length) {
            return [];
        }

        const descriptors = getVisibleUserQueryAttachmentDescriptors(userQuery);
        if (!descriptors.length) {
            return [];
        }

        const usedAttachmentIndexes = new Set();
        const findAttachmentIndex = (descriptor) => {
            const normalizedPreviewUrl = normalizeAttachmentMatchUrl(descriptor.previewUrl);

            if (descriptor.filename) {
                const filenameIndex = attachments.findIndex((attachment, index) => {
                    return !usedAttachmentIndexes.has(index) && attachment?.filename === descriptor.filename;
                });
                if (filenameIndex !== -1) {
                    return filenameIndex;
                }
            }

            if (normalizedPreviewUrl) {
                const previewIndex = attachments.findIndex((attachment, index) => {
                    return !usedAttachmentIndexes.has(index) && [
                        attachment?.previewUrl,
                        attachment?.viewUrl,
                    ]
                        .map(normalizeAttachmentMatchUrl)
                        .includes(normalizedPreviewUrl);
                });
                if (previewIndex !== -1) {
                    return previewIndex;
                }
            }

            return attachments.findIndex((attachment, index) => {
                return !usedAttachmentIndexes.has(index) && attachment?.kind === descriptor.kind;
            });
        };

        return descriptors
            .map((descriptor) => {
                const index = findAttachmentIndex(descriptor);
                if (index === -1) {
                    return null;
                }

                usedAttachmentIndexes.add(index);
                return attachments[index];
            })
            .filter(Boolean);
    }

    function removePendingAttachment(attachmentKey) {
        if (!state.pendingOverride?.attachments?.length) {
            return;
        }

        state.pendingOverride.attachments = state.pendingOverride.attachments.filter((attachment) => {
            return attachment?.key !== attachmentKey;
        });

        logDebug('Removed pending attachment from edit state.', {
            attachmentKey,
            remaining: state.pendingOverride.attachments.length,
        });
        syncEditComposerAttachmentUi();
    }

    function createAttachmentRemoveButton(attachment, contentScopeAttr) {
        const strings = getUiStrings();
        const button = document.createElement('button');
        button.type = 'button';
        button.className = 'cancel-button ng-star-inserted';
        button.setAttribute('data-test-id', 'cancel-button');
        button.setAttribute('aria-label', `${strings.removeFile} ${attachment.filename}`);
        button.setAttribute(ATTRS.attachmentOwned, 'true');

        const icon = createMaterialIcon('close');
        applyScopeAttribute(button, contentScopeAttr);
        applyScopeAttribute(icon, contentScopeAttr);
        button.appendChild(icon);
        button.addEventListener('click', (event) => {
            event.preventDefault();
            event.stopPropagation();
            removePendingAttachment(attachment.key);
        });
        return button;
    }

    function createOwnedAttachmentShell(textInputField, attachment) {
        const scope = getComposerScopeAttributes(textInputField);
        const chip = document.createElement('uploader-file-preview');
        chip.className = 'file-preview-chip ng-star-inserted';
        chip.setAttribute(ATTRS.attachmentOwned, 'true');
        chip.setAttribute(ATTRS.attachmentKey, attachment.key);
        applyScopeAttribute(chip, scope.previewChipContentAttr);
        applyScopeAttribute(chip, scope.previewChipHostAttr);

        const container = document.createElement('div');
        container.className = 'mat-mdc-tooltip-trigger file-preview-container';
        container.setAttribute(ATTRS.attachmentOwned, 'true');
        container.setAttribute(ATTRS.tooltip, attachment.filename);
        applyScopeAttribute(container, scope.previewInnerContentAttr);

        chip.appendChild(container);
        return { chip, container, scope };
    }

    function createOwnedFileAttachmentChip(textInputField, attachment) {
        const { chip, container, scope } = createOwnedAttachmentShell(textInputField, attachment);
        const preview = document.createElement('div');
        preview.className = 'file-preview discovery-feed-theme ng-star-inserted';
        preview.setAttribute('data-test-id', 'file-preview');
        preview.setAttribute(ATTRS.attachmentOwned, 'true');
        applyScopeAttribute(preview, scope.previewInnerContentAttr);

        const icon = document.createElement('img');
        icon.className = 'file-icon';
        icon.setAttribute('data-test-id', 'file-icon-img');
        icon.src = getAttachmentIconUrl(attachment);
        icon.alt = getAttachmentIconAltText(attachment);
        applyScopeAttribute(icon, scope.previewInnerContentAttr);

        const name = document.createElement('div');
        name.className = 'file-name';
        name.setAttribute('data-test-id', 'file-name');
        name.title = attachment.filename;
        name.textContent = ` ${attachment.displayName} `;
        applyScopeAttribute(name, scope.previewInnerContentAttr);

        const type = document.createElement('div');
        type.className = 'file-type';
        type.textContent = attachment.typeLabel;
        applyScopeAttribute(type, scope.previewInnerContentAttr);

        preview.appendChild(icon);
        preview.appendChild(name);
        preview.appendChild(type);
        preview.appendChild(createAttachmentRemoveButton(attachment, scope.previewInnerContentAttr));

        container.appendChild(preview);
        return chip;
    }

    function createOwnedImageAttachmentChip(textInputField, attachment) {
        const { chip, container, scope } = createOwnedAttachmentShell(textInputField, attachment);
        const previewButton = document.createElement('button');
        previewButton.type = 'button';
        previewButton.className = 'image-preview clickable ng-star-inserted';
        previewButton.setAttribute(ATTRS.attachmentOwned, 'true');
        applyScopeAttribute(previewButton, scope.previewInnerContentAttr);

        const image = document.createElement('img');
        image.setAttribute('data-test-id', 'image-preview');
        image.setAttribute('aria-label', getUiStrings().imagePreview);
        image.src = attachment.previewUrl || attachment.viewUrl || '';
        applyScopeAttribute(image, scope.previewInnerContentAttr);

        previewButton.appendChild(image);
        if (attachment.viewUrl) {
            previewButton.addEventListener('click', () => {
                window.open(attachment.viewUrl, '_blank', 'noopener,noreferrer');
            });
        }

        container.appendChild(previewButton);
        container.appendChild(createAttachmentRemoveButton(attachment, scope.previewInnerContentAttr));
        return chip;
    }

    function createOwnedVideoAttachmentChip(textInputField, attachment) {
        const strings = getUiStrings();
        const { chip, container, scope } = createOwnedAttachmentShell(textInputField, attachment);
        const preview = document.createElement('div');
        preview.className = 'image-preview ng-star-inserted';
        preview.setAttribute(ATTRS.attachmentOwned, 'true');
        applyScopeAttribute(preview, scope.previewInnerContentAttr);

        const imageContainer = document.createElement('div');
        imageContainer.className = 'video-preview-img-container';
        applyScopeAttribute(imageContainer, scope.previewInnerContentAttr);

        const image = document.createElement('img');
        image.setAttribute('data-test-id', 'video-preview');
        image.setAttribute('aria-label', strings.videoPreview);
        image.src = attachment.previewUrl || attachment.viewUrl || '';
        applyScopeAttribute(image, scope.previewInnerContentAttr);
        imageContainer.appendChild(image);

        const timecodeWrapper = document.createElement('div');
        timecodeWrapper.className = 'timecode-wrapper ng-star-inserted';
        applyScopeAttribute(timecodeWrapper, scope.previewInnerContentAttr);
        const timecode = document.createElement('span');
        timecode.setAttribute('data-test-id', 'video-timecode');
        timecode.className = 'video-timecode gds-label-m';
        timecode.textContent = ` ${formatAttachmentDuration(attachment.durationSeconds)} `;
        applyScopeAttribute(timecode, scope.previewInnerContentAttr);
        timecodeWrapper.appendChild(timecode);

        preview.appendChild(imageContainer);
        preview.appendChild(timecodeWrapper);
        preview.appendChild(createAttachmentRemoveButton(attachment, scope.previewInnerContentAttr));

        container.appendChild(preview);
        return chip;
    }

    function createOwnedAttachmentChip(textInputField, attachment) {
        if (attachment.kind === 'image' && (attachment.previewUrl || attachment.viewUrl)) {
            return createOwnedImageAttachmentChip(textInputField, attachment);
        }

        if (attachment.kind === 'video' && (attachment.previewUrl || attachment.viewUrl)) {
            return createOwnedVideoAttachmentChip(textInputField, attachment);
        }

        return createOwnedFileAttachmentChip(textInputField, attachment);
    }

    function getOwnedComposerAttachmentNodes(container) {
        if (!container) {
            return [];
        }

        return Array.from(container.children).filter((node) => {
            return node.nodeType === Node.ELEMENT_NODE && node.getAttribute(ATTRS.attachmentOwned) === 'true';
        });
    }

    function removeOwnedAttachmentUi(textInputField) {
        const field = textInputField || getTextInputField();
        if (!field) {
            return;
        }

        if (state.tooltipTarget?.closest?.(`[${ATTRS.attachmentOwned}="true"]`)) {
            hideOwnedTooltip();
        }

        field.querySelectorAll(`uploader-file-preview[${ATTRS.attachmentOwned}="true"]`).forEach((node) => {
            node.remove();
        });

        const ownedWrapper = field.querySelector(SELECTORS.ownedAttachmentPreviewWrapper);
        if (ownedWrapper) {
            ownedWrapper.remove();
        }

        if (!field.querySelector(SELECTORS.attachmentPreviewWrapper)) {
            field.classList.remove('with-file-preview');
        }
    }

    function ensureOwnedAttachmentContainer(textInputField) {
        const nativeWrapper = textInputField.querySelector(SELECTORS.nativeAttachmentPreviewWrapper);
        if (nativeWrapper) {
            textInputField.querySelector(SELECTORS.ownedAttachmentPreviewWrapper)?.remove();
            return nativeWrapper.querySelector(SELECTORS.attachmentPreviewContainer);
        }

        const scope = getComposerScopeAttributes(textInputField);
        let ownedWrapper = textInputField.querySelector(SELECTORS.ownedAttachmentPreviewWrapper);
        if (!ownedWrapper) {
            ownedWrapper = document.createElement('div');
            ownedWrapper.className = 'attachment-preview-wrapper ng-star-inserted';
            ownedWrapper.setAttribute(ATTRS.attachmentOwned, 'true');
            applyScopeAttribute(ownedWrapper, scope.inputContentAttr);
            textInputField.insertBefore(ownedWrapper, textInputField.firstChild);
        }

        let ownedContainer = ownedWrapper.querySelector(SELECTORS.ownedAttachmentPreviewContainer);
        if (!ownedContainer) {
            ownedContainer = document.createElement('uploader-file-preview-container');
            ownedContainer.className = 'uploader-file-preview-container ng-star-inserted';
            ownedContainer.setAttribute(ATTRS.attachmentOwned, 'true');
            applyScopeAttribute(ownedContainer, scope.inputContentAttr);
            applyScopeAttribute(ownedContainer, scope.previewContainerHostAttr);
            ownedWrapper.appendChild(ownedContainer);
        }

        return ownedContainer;
    }

    function syncEditComposerAttachmentUi() {
        const textInputField = getTextInputField();
        const attachments = Array.isArray(state.pendingOverride?.attachments)
            ? state.pendingOverride.attachments
            : [];

        if (!textInputField) {
            return;
        }

        if (!attachments.length) {
            removeOwnedAttachmentUi(textInputField);
            return;
        }

        textInputField.classList.add('with-file-preview');
        const ownedContainer = ensureOwnedAttachmentContainer(textInputField);
        if (!ownedContainer) {
            return;
        }

        const nextKeys = attachments.map((attachment) => attachment.key);
        const currentKeys = getOwnedComposerAttachmentNodes(ownedContainer).map((node) => {
            return node.getAttribute(ATTRS.attachmentKey);
        });

        const needsRender = nextKeys.length !== currentKeys.length
            || nextKeys.some((key, index) => key !== currentKeys[index]);

        if (!needsRender) {
            return;
        }

        const fragment = document.createDocumentFragment();
        attachments.forEach((attachment) => {
            fragment.appendChild(createOwnedAttachmentChip(textInputField, attachment));
        });

        const nativeWrapper = textInputField.querySelector(SELECTORS.nativeAttachmentPreviewWrapper);
        if (nativeWrapper) {
            const firstNativeNode = Array.from(ownedContainer.children).find((node) => {
                return node.nodeType === Node.ELEMENT_NODE && node.getAttribute(ATTRS.attachmentOwned) !== 'true';
            }) || null;

            getOwnedComposerAttachmentNodes(ownedContainer).forEach((node) => {
                node.remove();
            });
            ownedContainer.insertBefore(fragment, firstNativeNode);
            return;
        }

        ownedContainer.replaceChildren(fragment);
    }

    function parsePixelValue(value) {
        if (typeof value !== 'string') {
            return null;
        }

        const match = value.trim().match(/^(-?\d+(?:\.\d+)?)px$/);
        if (!match) {
            return null;
        }

        const pixels = Number(match[1]);
        return Number.isFinite(pixels) && pixels > 0 ? pixels : null;
    }

    function getPendingConversationMinHeight(targetContainer) {
        const containers = getConversationContainers();
        const lastContainer = containers[containers.length - 1] ?? null;
        const candidates = [];

        if (lastContainer) {
            candidates.push(lastContainer);
        }

        if (targetContainer && targetContainer !== lastContainer) {
            candidates.push(targetContainer);
        }

        for (const container of candidates) {
            const inlineMinHeight = parsePixelValue(container.style?.minHeight || '');
            if (inlineMinHeight) {
                return `${Math.round(inlineMinHeight)}px`;
            }

            const computedMinHeight = parsePixelValue(window.getComputedStyle(container).minHeight);
            if (computedMinHeight) {
                return `${Math.round(computedMinHeight)}px`;
            }
        }

        return null;
    }

    function getOptimisticConversationMinHeight(targetContainer) {
        const pendingMinHeight = parsePixelValue(getPendingConversationMinHeight(targetContainer) || '');
        const targetRect = targetContainer?.getBoundingClientRect?.();
        const remainingViewportHeight = targetRect
            ? Math.max(0, Math.round(window.innerHeight - targetRect.top))
            : 0;
        const nextMinHeight = Math.max(pendingMinHeight || 0, remainingViewportHeight || 0);

        return nextMinHeight > 0 ? `${nextMinHeight}px` : null;
    }

    function copyAngularScopeAttributes(source, target) {
        if (!source?.attributes || !target) {
            return;
        }

        Array.from(source.attributes).forEach((attribute) => {
            if (attribute.name.startsWith('_ngcontent-') || attribute.name.startsWith('_nghost-')) {
                target.setAttribute(attribute.name, attribute.value);
            }
        });
    }

    function cloneResponseShellNode(source, fallbackTag, fallbackClassName) {
        const node = source ? source.cloneNode(false) : document.createElement(fallbackTag);
        if (!source && fallbackClassName) {
            node.className = fallbackClassName;
        }

        node.removeAttribute('id');
        node.removeAttribute('jslog');
        node.removeAttribute('aria-describedby');
        node.removeAttribute('cdk-describedby-host');
        return node;
    }

    function stripClonedResponseRuntimeAttributes(root) {
        root.removeAttribute('id');
        root.removeAttribute('jslog');
        root.removeAttribute('aria-describedby');
        root.removeAttribute('cdk-describedby-host');
        root.querySelectorAll('[jslog], [aria-describedby], [cdk-describedby-host]').forEach((node) => {
            node.removeAttribute('jslog');
            node.removeAttribute('aria-describedby');
            node.removeAttribute('cdk-describedby-host');
        });
        root.querySelectorAll('[id]').forEach((node) => {
            if (typeof SVGElement === 'undefined' || !(node instanceof SVGElement)) {
                node.removeAttribute('id');
            }
        });
    }

    function uniquifyClonedSvgIds(root) {
        root.querySelectorAll('svg').forEach((svg, svgIndex) => {
            const idNodes = Array.from(svg.querySelectorAll('[id]'));
            if (!idNodes.length) {
                return;
            }

            const suffix = `gemini-editor-${Date.now().toString(36)}-${svgIndex}-${Math.random().toString(36).slice(2)}`;
            const idMap = new Map();
            idNodes.forEach((node) => {
                const oldId = node.getAttribute('id');
                if (!oldId) {
                    return;
                }

                const newId = `${oldId}-${suffix}`;
                idMap.set(oldId, newId);
                node.setAttribute('id', newId);
            });

            if (!idMap.size) {
                return;
            }

            const updateReferenceValue = (value) => {
                let nextValue = value.replace(/url\(#([^)]+)\)/g, (match, id) => {
                    return idMap.has(id) ? `url(#${idMap.get(id)})` : match;
                });

                if (nextValue.startsWith('#') && idMap.has(nextValue.slice(1))) {
                    nextValue = `#${idMap.get(nextValue.slice(1))}`;
                }

                return nextValue;
            };

            [svg, ...Array.from(svg.querySelectorAll('*'))].forEach((node) => {
                Array.from(node.attributes || []).forEach((attribute) => {
                    if (attribute.value.includes('#')) {
                        node.setAttribute(attribute.name, updateReferenceValue(attribute.value));
                    }
                });
            });
        });
    }

    function createFallbackPendingAvatar(sourceResponseNode) {
        const avatar = document.createElement('div');
        avatar.className = 'avatar avatar_primary ng-star-inserted';
        copyAngularScopeAttributes(sourceResponseNode, avatar);

        const model = document.createElement('div');
        model.className = 'avatar_primary_model is-gpi-avatar';
        copyAngularScopeAttributes(sourceResponseNode, model);

        const icon = document.createElement('mat-icon');
        icon.className = 'google-symbols notranslate';
        icon.setAttribute('fonticon', 'auto_awesome');
        icon.textContent = 'auto_awesome';

        model.appendChild(icon);
        avatar.appendChild(model);
        return avatar;
    }

    function createOptimisticResponseSlot(sourceResponseNode) {
        if (!sourceResponseNode) {
            return null;
        }

        const sourceResponseContainer = sourceResponseNode.querySelector('response-container');
        const sourceInnerContainer = sourceResponseContainer?.querySelector('.response-container') || null;
        const sourceHeader = sourceResponseNode.querySelector('.response-container-header');
        const sourceControls = sourceHeader?.querySelector('.response-container-header-controls') || null;
        const sourceAvatarWrapper = sourceHeader?.querySelector('.response-container-header-avatar') || null;
        const sourceAvatar = sourceAvatarWrapper?.querySelector('.avatar')
            || sourceResponseNode.querySelector('.avatar.avatar_primary, .avatar');

        const placeholder = document.createElement('pending-response');
        placeholder.className = sourceResponseNode.className || 'ng-star-inserted';
        placeholder.setAttribute('data-gemini-editor-pending-response', 'true');
        copyAngularScopeAttributes(sourceResponseNode, placeholder);

        const responseContainer = cloneResponseShellNode(
            sourceResponseContainer,
            'response-container',
            'ng-star-inserted',
        );
        const innerContainer = cloneResponseShellNode(
            sourceInnerContainer,
            'div',
            'response-container response-container-with-gpi is-mobile',
        );
        if (!innerContainer.classList.contains('response-container')) {
            innerContainer.classList.add('response-container');
        }

        const header = cloneResponseShellNode(
            sourceHeader,
            'div',
            'response-container-header ng-star-inserted',
        );
        const controls = cloneResponseShellNode(
            sourceControls,
            'div',
            'response-container-header-controls',
        );
        const avatarWrapper = cloneResponseShellNode(
            sourceAvatarWrapper,
            'div',
            'response-container-header-avatar ng-star-inserted',
        );

        avatarWrapper.appendChild(
            sourceAvatar ? sourceAvatar.cloneNode(true) : createFallbackPendingAvatar(sourceResponseNode),
        );
        header.appendChild(controls);
        header.appendChild(avatarWrapper);
        innerContainer.appendChild(header);
        responseContainer.appendChild(innerContainer);
        placeholder.appendChild(responseContainer);

        stripClonedResponseRuntimeAttributes(placeholder);
        uniquifyClonedSvgIds(placeholder);

        placeholder.style.display = 'block';
        placeholder.style.width = '100%';
        placeholder.style.flex = '1 1 auto';
        placeholder.style.minHeight = '48px';
        return placeholder;
    }

    function setOptimisticQueryText(queryTextElement, text) {
        if (!queryTextElement) {
            return;
        }

        const lineTemplate = queryTextElement.querySelector(SELECTORS.queryTextLine);
        const lineNodes = buildPromptLineNodes(text, () => {
            const paragraph = lineTemplate
                ? lineTemplate.cloneNode(false)
                : document.createElement('p');

            if (!lineTemplate) {
                paragraph.className = 'query-text-line';
            }

            return paragraph;
        });
        const childNodes = Array.from(queryTextElement.childNodes);
        const fragment = document.createDocumentFragment();
        let lineNodesInserted = false;

        childNodes.forEach((node) => {
            const isLineNode = node.nodeType === Node.ELEMENT_NODE
                && node instanceof Element
                && node.matches(SELECTORS.queryTextLine);

            if (isLineNode) {
                if (!lineNodesInserted) {
                    lineNodes.forEach((lineNode) => fragment.appendChild(lineNode));
                    lineNodesInserted = true;
                }
                return;
            }

            fragment.appendChild(node.cloneNode(true));
        });

        if (!lineNodesInserted) {
            lineNodes.forEach((lineNode) => fragment.appendChild(lineNode));
        }

        queryTextElement.replaceChildren(fragment);
    }

    function getAttachmentPreviewDescriptor(node) {
        const fileButton = node?.querySelector?.(SELECTORS.userQueryFileButton);
        if (fileButton) {
            return {
                kind: 'file',
                filename: fileButton.getAttribute('aria-label')?.trim() || '',
                previewUrl: '',
            };
        }

        const imagePreview = node?.querySelector?.(SELECTORS.userQueryImagePreview);
        if (imagePreview) {
            return {
                kind: 'image',
                filename: '',
                previewUrl: normalizeAttachmentMatchUrl(imagePreview.getAttribute('src')),
            };
        }

        const videoPreview = node?.querySelector?.(SELECTORS.userQueryVideoPreview);
        if (videoPreview) {
            return {
                kind: 'video',
                filename: '',
                previewUrl: normalizeAttachmentMatchUrl(videoPreview.getAttribute('src')),
            };
        }

        return {
            kind: '',
            filename: '',
            previewUrl: '',
        };
    }

    function getAttachmentPreviewItemNode(previewNode) {
        const parent = previewNode?.parentElement;
        if (parent?.parentElement?.classList?.contains('scrollable-area')) {
            return parent;
        }

        return previewNode;
    }

    function getUserQueryAttachmentPreviewItems(userQuery) {
        return Array.from(userQuery?.querySelectorAll?.('user-query-file-preview') || [])
            .map((previewNode, index) => {
                return {
                    index,
                    previewNode,
                    itemNode: getAttachmentPreviewItemNode(previewNode),
                    descriptor: getAttachmentPreviewDescriptor(previewNode),
                };
            });
    }

    function selectAttachmentPreviewIndexes(userQuery, attachments) {
        if (!Array.isArray(attachments) || !attachments.length) {
            return new Set();
        }

        const items = getUserQueryAttachmentPreviewItems(userQuery);
        const usedIndexes = new Set();
        const findMatchIndex = (attachment) => {
            const normalizedPreviewUrls = [
                attachment?.previewUrl,
                attachment?.viewUrl,
            ].map(normalizeAttachmentMatchUrl).filter(Boolean);

            if (attachment?.filename) {
                const filenameIndex = items.findIndex((item) => {
                    return !usedIndexes.has(item.index)
                        && item.descriptor.filename
                        && item.descriptor.filename === attachment.filename;
                });
                if (filenameIndex !== -1) {
                    return items[filenameIndex].index;
                }
            }

            if (normalizedPreviewUrls.length) {
                const previewIndex = items.findIndex((item) => {
                    return !usedIndexes.has(item.index)
                        && item.descriptor.previewUrl
                        && normalizedPreviewUrls.includes(item.descriptor.previewUrl);
                });
                if (previewIndex !== -1) {
                    return items[previewIndex].index;
                }
            }

            const kindIndex = items.findIndex((item) => {
                return !usedIndexes.has(item.index)
                    && attachment?.kind
                    && item.descriptor.kind === attachment.kind;
            });
            return kindIndex === -1 ? -1 : items[kindIndex].index;
        };

        attachments.forEach((attachment) => {
            const index = findMatchIndex(attachment);
            if (index !== -1) {
                usedIndexes.add(index);
            }
        });

        return usedIndexes;
    }

    function removeAttachmentPreviewItem(previewNode) {
        const itemNode = getAttachmentPreviewItemNode(previewNode);
        if (itemNode?.parentNode) {
            itemNode.remove();
        } else {
            previewNode.remove();
        }
    }

    function cloneFilteredUserQueryAttachmentContainers(sourceUserQuery, attachments) {
        const selectedIndexes = selectAttachmentPreviewIndexes(sourceUserQuery, attachments);
        if (!selectedIndexes.size) {
            return [];
        }

        let previewIndex = 0;
        return getTopLevelUserQueryAttachmentContainers(sourceUserQuery)
            .map((sourceAttachmentContainer) => {
                const clone = sourceAttachmentContainer.cloneNode(true);
                Array.from(clone.querySelectorAll('user-query-file-preview')).forEach((previewNode) => {
                    if (!selectedIndexes.has(previewIndex)) {
                        removeAttachmentPreviewItem(previewNode);
                    }
                    previewIndex += 1;
                });

                if (!clone.querySelector('user-query-file-preview')) {
                    return null;
                }

                clone.setAttribute(ATTRS.optimisticAttachments, 'true');
                stripClonedAttachmentRuntimeAttributes(clone);
                return clone;
            })
            .filter(Boolean);
    }

    function getUserQueryAttachmentInsertBeforeNode(userQuery) {
        const root = getUserQueryRoot(userQuery);
        if (!root) {
            return { root: null, beforeNode: null };
        }

        const queryContent = Array.from(root.children).find((node) => {
            return node.nodeType === Node.ELEMENT_NODE
                && node instanceof Element
                && node.classList.contains('query-content');
        }) || null;

        return {
            root,
            beforeNode: queryContent || root.firstChild,
        };
    }

    function replaceOptimisticUserQueryAttachmentsFromRecords(targetUserQuery, sourceUserQuery, attachments) {
        if (!targetUserQuery || !sourceUserQuery) {
            return false;
        }

        getTopLevelUserQueryAttachmentContainers(targetUserQuery).forEach((node) => node.remove());

        const clonedContainers = cloneFilteredUserQueryAttachmentContainers(sourceUserQuery, attachments);
        if (!clonedContainers.length) {
            return false;
        }

        const { root, beforeNode } = getUserQueryAttachmentInsertBeforeNode(targetUserQuery);
        if (!root) {
            return false;
        }

        clonedContainers.forEach((container) => {
            root.insertBefore(container, beforeNode);
        });
        return true;
    }

    function appendOptimisticUserQueryAttachmentsFromSource(sourceContainer, targetContainer) {
        const sourceUserQuery = sourceContainer?.querySelector?.(SELECTORS.userQuery);
        const targetUserQuery = targetContainer?.querySelector?.(SELECTORS.userQuery);
        if (!sourceUserQuery || !targetUserQuery) {
            return false;
        }

        const sourceAttachmentContainers = getTopLevelUserQueryAttachmentContainers(sourceUserQuery)
            .filter((node) => {
                return Boolean(node.querySelector([
                    SELECTORS.userQueryFileButton,
                    SELECTORS.userQueryImagePreview,
                    SELECTORS.userQueryVideoPreview,
                ].join(', ')));
            });
        if (!sourceAttachmentContainers.length) {
            return false;
        }

        const { root, beforeNode } = getUserQueryAttachmentInsertBeforeNode(targetUserQuery);
        if (!root) {
            return false;
        }

        sourceAttachmentContainers.forEach((sourceAttachmentContainer) => {
            const clone = sourceAttachmentContainer.cloneNode(true);
            clone.setAttribute(ATTRS.optimisticAttachments, 'true');
            stripClonedAttachmentRuntimeAttributes(clone);
            root.insertBefore(clone, beforeNode);
        });
        return true;
    }

    function createOptimisticConversationContainer(sourceContainer, text, pendingMinHeight, attachments = []) {
        if (!sourceContainer) {
            return null;
        }

        const optimisticContainer = sourceContainer.cloneNode(true);
        optimisticContainer.setAttribute(ATTRS.optimistic, 'true');
        optimisticContainer.removeAttribute('id');
        optimisticContainer.querySelectorAll('[id]').forEach((node) => {
            if (typeof SVGElement === 'undefined' || !(node instanceof SVGElement)) {
                node.removeAttribute('id');
            }
        });
        uniquifyClonedSvgIds(optimisticContainer);

        if (pendingMinHeight) {
            optimisticContainer.style.minHeight = pendingMinHeight;
        }

        const responseNode = optimisticContainer.querySelector(SELECTORS.responseNodes);
        if (responseNode) {
            const responseSlot = createOptimisticResponseSlot(responseNode);
            if (responseSlot) {
                responseNode.replaceWith(responseSlot);
            } else {
                responseNode.remove();
            }
        }

        const queryTextElement = optimisticContainer.querySelector(SELECTORS.queryText);
        setOptimisticQueryText(queryTextElement, text);
        replaceOptimisticUserQueryAttachmentsFromRecords(
            optimisticContainer.querySelector(SELECTORS.userQuery),
            sourceContainer.querySelector(SELECTORS.userQuery),
            attachments,
        );

        return optimisticContainer;
    }

    function moveCaretToEnd(editor) {
        editor.focus();

        const selection = window.getSelection();
        if (!selection) {
            return;
        }

        const range = document.createRange();
        const walker = document.createTreeWalker(editor, NodeFilter.SHOW_TEXT);
        let lastTextNode = null;

        while (walker.nextNode()) {
            lastTextNode = walker.currentNode;
        }

        if (lastTextNode) {
            range.setStart(lastTextNode, lastTextNode.textContent?.length ?? 0);
        } else if (editor.lastElementChild) {
            range.selectNodeContents(editor.lastElementChild);
        } else {
            range.selectNodeContents(editor);
        }

        range.collapse(false);

        selection.removeAllRanges();
        selection.addRange(range);
    }

    function setDraftText(text) {
        const editor = getEditor();
        if (!editor) {
            return;
        }

        setEditorContent(editor, text ?? '');
        editor.dispatchEvent(new Event('input', { bubbles: true }));
        window.setTimeout(() => moveCaretToEnd(editor), 0);
    }

    function getConversationContainersFrom(container) {
        const containers = getConversationContainers();
        const startIndex = containers.indexOf(container);
        if (startIndex === -1) {
            return [];
        }

        return containers.slice(startIndex);
    }

    function getUserQueryRoot(userQuery) {
        const roots = Array.from(userQuery?.querySelectorAll?.('.user-query-container') || []);
        return roots.find((root) => {
            return Array.from(root.children).some((child) => {
                return child.nodeType === Node.ELEMENT_NODE
                    && child instanceof Element
                    && (
                        child.classList.contains('query-content')
                        || child.classList.contains('file-preview-container')
                    );
            });
        }) || roots[roots.length - 1] || userQuery || null;
    }

    function getTopLevelUserQueryAttachmentContainers(userQuery) {
        const root = getUserQueryRoot(userQuery);
        if (!root) {
            return [];
        }

        return Array.from(root.children).filter((node) => {
            return node.nodeType === Node.ELEMENT_NODE
                && node instanceof Element
                && node.classList.contains('file-preview-container');
        });
    }

    function hasNativeUserQueryAttachmentContainer(userQuery) {
        return getTopLevelUserQueryAttachmentContainers(userQuery).some((node) => {
            return node.getAttribute(ATTRS.optimisticAttachments) !== 'true'
                && Boolean(node.querySelector([
                    SELECTORS.userQueryFileButton,
                    SELECTORS.userQueryImagePreview,
                    SELECTORS.userQueryVideoPreview,
                ].join(', ')));
        });
    }

    function removeOptimisticUserQueryAttachmentContainers(userQuery) {
        getTopLevelUserQueryAttachmentContainers(userQuery)
            .filter((node) => node.getAttribute(ATTRS.optimisticAttachments) === 'true')
            .forEach((node) => node.remove());
    }

    function cleanupOptimisticUserQueryAttachments(userQuery) {
        if (hasNativeUserQueryAttachmentContainer(userQuery)) {
            removeOptimisticUserQueryAttachmentContainers(userQuery);
        }
    }

    function cleanupOptimisticUserQueryAttachmentsInDocument() {
        document.querySelectorAll(SELECTORS.userQuery).forEach((userQuery) => {
            cleanupOptimisticUserQueryAttachments(userQuery);
        });
    }

    function stripClonedAttachmentRuntimeAttributes(root) {
        root.removeAttribute('id');
        root.removeAttribute('jslog');
        root.removeAttribute('aria-describedby');
        root.removeAttribute('cdk-describedby-host');
        root.querySelectorAll('[id], [jslog], [aria-describedby], [cdk-describedby-host]').forEach((node) => {
            node.removeAttribute('id');
            node.removeAttribute('jslog');
            node.removeAttribute('aria-describedby');
            node.removeAttribute('cdk-describedby-host');
        });
    }

    function copyOptimisticUserQueryAttachments(sourceContainer, targetContainer) {
        const sourceUserQuery = sourceContainer?.querySelector?.(SELECTORS.userQuery);
        const targetUserQuery = targetContainer?.querySelector?.(SELECTORS.userQuery);
        if (!sourceUserQuery || !targetUserQuery || hasNativeUserQueryAttachmentContainer(targetUserQuery)) {
            return false;
        }

        const sourceAttachmentContainers = getTopLevelUserQueryAttachmentContainers(sourceUserQuery)
            .filter((node) => {
                return Boolean(node.querySelector([
                    SELECTORS.userQueryFileButton,
                    SELECTORS.userQueryImagePreview,
                    SELECTORS.userQueryVideoPreview,
                ].join(', ')));
            });
        if (!sourceAttachmentContainers.length) {
            return false;
        }

        removeOptimisticUserQueryAttachmentContainers(targetUserQuery);

        const targetRoot = getUserQueryRoot(targetUserQuery);
        if (!targetRoot) {
            return false;
        }

        const queryContent = Array.from(targetRoot.children).find((node) => {
            return node.nodeType === Node.ELEMENT_NODE
                && node instanceof Element
                && node.classList.contains('query-content');
        }) || null;

        sourceAttachmentContainers.forEach((sourceAttachmentContainer) => {
            const clonedAttachmentContainer = sourceAttachmentContainer.cloneNode(true);
            clonedAttachmentContainer.setAttribute(ATTRS.optimisticAttachments, 'true');
            stripClonedAttachmentRuntimeAttributes(clonedAttachmentContainer);
            targetRoot.insertBefore(clonedAttachmentContainer, queryContent || targetRoot.firstChild);
        });
        logDebug('Copied optimistic attachment UI to refreshed message.');
        return true;
    }

    function syncOptimisticConversation() {
        if (!state.optimisticContainer) {
            return;
        }

        if (!state.optimisticContainer.isConnected) {
            state.optimisticContainer = null;
            return;
        }

        let node = state.optimisticContainer.nextElementSibling;
        while (node) {
            if (node.matches?.(SELECTORS.conversationContainer)) {
                if (node.matches?.('pending-request')) {
                    appendOptimisticUserQueryAttachmentsFromSource(node, state.optimisticContainer);
                    node.remove();
                    return;
                }

                promoteAttachmentCarryoverToContainer(node, getConversationContainers().indexOf(node));
                copyOptimisticUserQueryAttachments(state.optimisticContainer, node);
                state.optimisticContainer.remove();
                state.optimisticContainer = null;
                return;
            }

            node = node.nextElementSibling;
        }
    }

    function injectStyles() {
        let style = document.getElementById(STYLE_ID);
        const shouldAppendStyle = !style;
        if (!style) {
            style = document.createElement('style');
            style.id = STYLE_ID;
        }
        style.textContent = `
            .gemini-edit-mode-bar {
                display: flex;
                align-items: center;
                justify-content: space-between;
                width: 100%;
                box-sizing: border-box;
                padding: 10px 24px;
                margin-bottom: 0;
                border-radius: 28px 28px 0 0;
                background-color: var(--gem-sys-color--surface-container-high);
                border: none;
                font-family: "Google Sans", "Helvetica Neue", sans-serif;
                animation: geminiEditSlideIn 0.2s ease;
                z-index: 1;
                position: relative;
                top: 0;
            }

            @keyframes geminiEditSlideIn {
                from {
                    transform: translateY(10px);
                    opacity: 0;
                }
                to {
                    transform: translateY(0);
                    opacity: 1;
                }
            }

            .gemini-input-active-edit input-area-v2 {
                border-top-left-radius: 0 !important;
                border-top-right-radius: 0 !important;
                border-top: none !important;
            }

            .gemini-edit-left {
                display: flex;
                align-items: center;
                gap: 12px;
            }

            .gemini-edit-icon {
                display: flex;
                align-items: center;
                justify-content: center;
                color: var(--gem-sys-color--primary, #a8c7fa);
            }

            .gemini-edit-label {
                font-size: 14px;
                font-weight: 500;
                color: var(--gem-sys-color--on-surface, #e3e3e3);
                letter-spacing: 0.1px;
            }

            .gemini-edit-cancel {
                background: transparent;
                border: 1px solid var(--gem-sys-color--outline, #8e918f);
                color: var(--gem-sys-color--primary, #a8c7fa);
                cursor: pointer;
                font-family: "Google Sans", sans-serif;
                font-weight: 500;
                font-size: 13px;
                padding: 6px 16px;
                border-radius: 100px;
                transition: all 0.2s;
            }

            .gemini-edit-cancel:hover {
                background-color: rgba(var(--gem-sys-color--primary-rgb, 168, 199, 250), 0.08);
                border-color: var(--gem-sys-color--primary, #a8c7fa);
            }

            .gemini-editor-tooltip {
                position: fixed;
                z-index: 2147483647;
                max-width: min(320px, calc(100vw - 16px));
                padding: 6px 8px;
                border-radius: 4px;
                background: rgba(32, 33, 36, 0.96);
                color: #fff;
                font-family: "Google Sans", "Helvetica Neue", sans-serif;
                font-size: 12px;
                font-weight: 500;
                line-height: 16px;
                letter-spacing: 0.1px;
                pointer-events: none;
                opacity: 0;
                transform: translateY(4px);
                transition: opacity 0.12s linear, transform 0.12s ease;
                white-space: nowrap;
            }

            .gemini-editor-tooltip.visible {
                opacity: 1;
                transform: translateY(0);
            }

            .input-area-container [${ATTRS.wrapper}="true"],
            .input-area-container [${ATTRS.customButton}="true"],
            .text-input-field [${ATTRS.wrapper}="true"],
            .text-input-field [${ATTRS.customButton}="true"] {
                display: none !important;
            }

            user-query-content.edit-mode [${ATTRS.wrapper}="true"],
            user-query-content.edit-mode [${ATTRS.customButton}="true"] {
                display: none !important;
            }

            user-query[${ATTRS.processed}="true"] div:has(> ${SELECTORS.nativeEditButton}:not([${ATTRS.customButton}="true"])) {
                display: none !important;
            }

            [data-gemini-editor-pending-response="true"] .avatar_primary_animation {
                transform-origin: center;
                animation: geminiEditorPendingAvatarPulse 1.35s ease-in-out infinite;
            }

            [data-gemini-editor-pending-response="true"] .avatar_primary_animation svg {
                transform-origin: center;
                animation: geminiEditorPendingAvatarTurn 1.8s cubic-bezier(0.4, 0, 0.2, 1) infinite;
            }

            [data-gemini-editor-pending-response="true"] .avatar_primary_animation svg g[mask] {
                transform-origin: center;
                animation: geminiEditorPendingAvatarSweep 1.35s ease-in-out infinite;
            }

            @keyframes geminiEditorPendingAvatarPulse {
                0%, 100% {
                    opacity: 0.72;
                    filter: saturate(0.95);
                }
                45% {
                    opacity: 1;
                    filter: saturate(1.25);
                }
            }

            @keyframes geminiEditorPendingAvatarTurn {
                0% {
                    transform: rotate(0deg) scale(0.92);
                }
                45% {
                    transform: rotate(90deg) scale(1.04);
                }
                100% {
                    transform: rotate(180deg) scale(0.92);
                }
            }

            @keyframes geminiEditorPendingAvatarSweep {
                0%, 100% {
                    opacity: 0.68;
                }
                50% {
                    opacity: 1;
                }
            }

            .text-input-field .attachment-preview-wrapper[${ATTRS.attachmentOwned}="true"] {
                grid-area: file-preview;
                display: flex;
                gap: var(--gem-sys-spacing--s);
                flex-wrap: nowrap;
                overflow-x: auto;
                max-height: 168px;
                margin-inline: 0;
                width: 100%;
                align-self: stretch;
            }

            .text-input-field:has(.attachment-preview-wrapper[${ATTRS.attachmentOwned}="true"]) {
                row-gap: var(--gem-sys-spacing--s);
            }

            .attachment-preview-wrapper[${ATTRS.attachmentOwned}="true"] > uploader-file-preview-container[${ATTRS.attachmentOwned}="true"] {
                display: contents;
            }

            uploader-file-preview-container[${ATTRS.attachmentOwned}="true"] {
                display: inline-flex;
                flex-flow: nowrap;
                overflow: auto;
                width: auto;
            }

            uploader-file-preview[${ATTRS.attachmentOwned}="true"] {
                flex-shrink: 0;
            }

            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .file-preview-container {
                padding: 0;
                position: relative;
            }

            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .file-preview,
            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .image-preview {
                position: relative;
                border-radius: var(--gem-sys-shape--corner-large);
            }

            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .file-preview.discovery-feed-theme {
                background: var(--gem-sys-color--surface-container-high);
            }

            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .file-preview {
                width: 208px;
                height: unset;
                padding: var(--gem-sys-spacing--l) 44px var(--gem-sys-spacing--l) var(--gem-sys-spacing--l);
                display: grid;
                grid-template:
                    "name name" 1fr
                    "icon type" var(--gem-sys-spacing--xl)
                    / var(--gem-sys-spacing--xl) 1fr;
                gap: var(--gem-sys-spacing--s) var(--gem-sys-spacing--xs);
                align-items: center;
                box-sizing: border-box;
                text-align: start;
                border-radius: var(--gem-sys-shape--corner-large-increased);
            }

            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .file-icon,
            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .file-name,
            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .file-type {
                margin: 0;
            }

            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .file-name {
                grid-area: name;
                color: var(--gem-sys-color--on-surface-variant);
                font-family: "Google Sans Flex", "Google Sans", "Helvetica Neue", sans-serif;
                font-size: var(--gem-sys-typography-type-scale--title-s-font-size);
                font-weight: var(--gem-sys-typography-type-scale--title-s-font-weight);
                letter-spacing: var(--gem-sys-typography-type-scale--title-s-font-tracking);
                line-height: var(--gem-sys-typography-type-scale--title-s-line-height);
                white-space: nowrap;
                overflow: hidden;
                text-overflow: ellipsis;
            }

            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .file-icon {
                grid-area: icon;
                place-self: center;
                width: var(--gem-sys-spacing--l);
                height: var(--gem-sys-spacing--l);
            }

            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .file-type {
                grid-area: type;
                color: var(--gem-sys-color--on-surface-variant);
                font-family: "Google Sans Flex", "Google Sans", "Helvetica Neue", sans-serif;
                font-size: var(--gem-sys-typography-type-scale--label-m-font-size);
                font-weight: var(--gem-sys-typography-type-scale--label-m-font-weight);
                letter-spacing: var(--gem-sys-typography-type-scale--label-m-font-tracking);
                line-height: var(--gem-sys-typography-type-scale--label-m-line-height);
            }

            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .image-preview {
                width: 80px;
                height: 80px;
                overflow: hidden;
                display: flex;
                justify-content: center;
                align-items: center;
                background: var(--gem-sys-color--surface-container-highest);
            }

            uploader-file-preview[${ATTRS.attachmentOwned}="true"] button.image-preview {
                background: none;
                border: none;
                margin: 0;
                padding: 0;
                text-decoration: underline;
                cursor: pointer;
                color: var(--gem-sys-color--primary);
            }

            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .image-preview > img {
                width: 100%;
                height: 100%;
                object-fit: cover;
                border-radius: 0;
            }

            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .video-preview-img-container {
                position: relative;
                display: inline-block;
                width: 100%;
                height: 100%;
            }

            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .video-preview-img-container::before {
                content: "";
                position: absolute;
                inset: 0;
                background: linear-gradient(180deg, transparent, rgba(0, 0, 0, 0.6));
            }

            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .video-preview-img-container img {
                width: 100%;
                height: 100%;
                object-fit: cover;
            }

            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .timecode-wrapper {
                padding: var(--gem-sys-spacing--s);
                position: absolute;
                inset-inline-start: 0;
                bottom: 0;
                display: flex;
                height: var(--gem-sys-spacing--l);
            }

            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .timecode-wrapper .video-timecode {
                color: #fff;
            }

            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .cancel-button {
                width: unset;
                height: unset;
                padding: var(--gem-sys-spacing--s);
                box-sizing: border-box;
                position: absolute;
                top: 0;
                right: 0;
                background: transparent;
                border: none;
                cursor: pointer;
            }

            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .cancel-button > .mat-icon {
                display: none;
                position: relative;
                align-items: center;
                justify-content: center;
                font-size: var(--gem-sys-spacing--xxl);
                width: var(--gem-sys-spacing--xxl);
                height: var(--gem-sys-spacing--xxl);
                padding: var(--gem-sys-spacing--xs);
                color: var(--gem-sys-color--on-surface-variant);
                background-color: var(--gem-sys-color--surface);
                border-radius: 50%;
                opacity: 1;
            }

            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .cancel-button > .mat-icon::after {
                content: "";
                position: absolute;
                inset: 0;
                border-radius: 50%;
                background-color: var(--gem-sys-color--on-surface-variant);
                opacity: 0;
            }

            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .cancel-button:hover > .mat-icon::after {
                opacity: 0.08;
            }

            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .file-preview:hover .cancel-button > .mat-icon,
            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .image-preview:hover .cancel-button > .mat-icon,
            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .file-preview-container:hover > .image-preview + .cancel-button > .mat-icon,
            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .file-preview-container:hover > button.image-preview + .cancel-button > .mat-icon,
            uploader-file-preview[${ATTRS.attachmentOwned}="true"] .file-preview-container:focus-within > button.image-preview + .cancel-button > .mat-icon {
                display: flex;
                background-color: var(--gem-sys-color--surface);
            }
        `;

        const target = document.head || document.documentElement;
        if (target && shouldAppendStyle) {
            target.appendChild(style);
        }
    }

    function createEditButtonElement(copyButton) {
        const strings = getUiStrings();
        const container = document.createElement('div');
        const baseWrapperClass = copyButton?.parentElement?.className || 'ng-star-inserted';
        container.className = baseWrapperClass;
        container.setAttribute(ATTRS.wrapper, 'true');

        const button = copyButton ? copyButton.cloneNode(true) : document.createElement('button');

        button.setAttribute('type', 'button');
        button.setAttribute('aria-label', strings.editLabel);
        button.setAttribute('data-gemini-editor-role', 'edit-button');
        button.setAttribute(ATTRS.customButton, 'true');
        button.setAttribute(ATTRS.tooltip, strings.editTooltip);
        button.disabled = false;
        button.removeAttribute('disabled');
        button.removeAttribute('aria-disabled');
        button.removeAttribute('aria-describedby');
        button.removeAttribute('cdk-describedby-host');
        button.removeAttribute('data-test-id');
        button.removeAttribute('jslog');
        button.removeAttribute('mattooltip');

        const icon = button.querySelector('mat-icon');
        if (icon) {
            icon.setAttribute('fonticon', 'edit');
            icon.setAttribute('data-mat-icon-name', 'edit');
            icon.textContent = '';
        }

        ensureButtonRippleSpan(button);

        container.appendChild(button);
        return { container, button };
    }

    function getResponseJslogNode(container) {
        const selectors = [
            'model-response [jslog]',
            'pending-response [jslog]',
            'dual-model-response [jslog]',
            'generative-ui-response [jslog]',
        ];

        for (const selector of selectors) {
            const node = container.querySelector(selector);
            if (node) {
                return node;
            }
        }

        return null;
    }

    function getParentData(currentContainer, index, allContainers) {
        const parentData = { r: null, c: null, rc: null };

        if (index > 0) {
            const previousContainer = allContainers[index - 1];
            const previousUserQuery = previousContainer.querySelector(SELECTORS.userQuery);
            const previousModelNode = getResponseJslogNode(previousContainer);

            const userData = getBestJslogData(previousUserQuery);
            const modelData = getBestJslogData(previousModelNode);

            let rc = modelData?.rc ?? null;
            if (!rc) {
                const draftNode = previousContainer.querySelector(SELECTORS.draftNode);
                rc = draftNode?.getAttribute('data-test-draft-id') ?? null;
            }

            parentData.r = userData?.r || modelData?.r || null;
            parentData.c = userData?.c || modelData?.c || null;
            parentData.rc = rc;
            return parentData;
        }

        const currentUserQuery = currentContainer.querySelector(SELECTORS.userQuery);
        const currentData = getBestJslogData(currentUserQuery);
        parentData.c = currentData?.c || getConversationIdFromLocation();
        return parentData;
    }

    function ensureEditModeBanner() {
        const strings = getUiStrings();
        const inputAreaContainer = document.querySelector(SELECTORS.inputAreaContainer);
        if (!inputAreaContainer) {
            return;
        }

        inputAreaContainer.classList.add('gemini-input-active-edit');

        if (document.querySelector(SELECTORS.editModeBar)) {
            return;
        }

        const banner = document.createElement('div');
        banner.className = 'gemini-edit-mode-bar';

        const left = document.createElement('div');
        left.className = 'gemini-edit-left';

        const iconWrapper = document.createElement('div');
        iconWrapper.className = 'gemini-edit-icon';

        const svgNs = 'http://www.w3.org/2000/svg';
        const svg = document.createElementNS(svgNs, 'svg');
        svg.setAttribute('width', '20');
        svg.setAttribute('height', '20');
        svg.setAttribute('viewBox', '0 -960 960 960');
        svg.setAttribute('fill', 'currentColor');

        const path = document.createElementNS(svgNs, 'path');
        path.setAttribute('d', 'M200-200h57l391-391-57-57-391 391v57Zm-80 80v-170l528-527q12-11 26.5-17t30.5-6q16 0 31 6t26 17l55 56q12 11 17.5 26t5.5 30q0 16-5.5 30.5T817-647L290-120H120Zm640-584-56-56 56 56Zm-141 85-28-29 57 57-29-28Z');
        svg.appendChild(path);
        iconWrapper.appendChild(svg);

        const label = document.createElement('span');
        label.className = 'gemini-edit-label';
        label.textContent = strings.editMode;

        const cancelButton = document.createElement('button');
        cancelButton.className = 'gemini-edit-cancel';
        cancelButton.type = 'button';
        cancelButton.textContent = strings.cancel;
        cancelButton.addEventListener('click', disableEditMode);

        left.appendChild(iconWrapper);
        left.appendChild(label);
        banner.appendChild(left);
        banner.appendChild(cancelButton);

        inputAreaContainer.prepend(banner);
    }

    function enableEditMode(text, parentData, targetContainer, attachments) {
        state.editTargetContainer = targetContainer;
        state.editContextPath = getCurrentChatLocationKey();
        state.pendingOverride = {
            ...parentData,
            attachments: Array.isArray(attachments)
                ? attachments.map(cloneAttachmentRecord).filter(Boolean)
                : [],
        };
        logDebug('Edit mode enabled.', {
            text,
            parentData,
            attachmentCount: state.pendingOverride.attachments.length,
            path: state.editContextPath,
        });

        setDraftText(text);
        clearNativeComposerAttachments();
        ensureEditModeBanner();
        syncEditComposerAttachmentUi();
    }

    function disableEditMode() {
        logDebug('Edit mode disabled.');
        state.editTargetContainer = null;
        state.editContextPath = null;
        state.pendingOverride = null;

        const banner = document.querySelector(SELECTORS.editModeBar);
        if (banner) {
            banner.remove();
        }

        const inputAreaContainer = document.querySelector(SELECTORS.inputAreaContainer);
        if (inputAreaContainer) {
            inputAreaContainer.classList.remove('gemini-input-active-edit');
        }

        removeOwnedAttachmentUi();
        clearNativeComposerAttachments();
        setDraftText('');
    }

    function syncEditModeWithCurrentChat() {
        if (!state.editTargetContainer) {
            return;
        }

        if (state.editContextPath !== getCurrentChatLocationKey() || !state.editTargetContainer.isConnected) {
            disableEditMode();
        }
    }

    function clearHistoryFrom(container) {
        getConversationContainersFrom(container).forEach((node) => node.remove());
    }

    function getPendingOverrideAttachmentRecords() {
        return Array.isArray(state.pendingOverride?.attachments)
            ? state.pendingOverride.attachments.map(cloneAttachmentRecord).filter(Boolean)
            : [];
    }

    function beginOptimisticEditUi(targetContainer, submittedText, attachments = getPendingOverrideAttachmentRecords()) {
        if (state.optimisticContainer?.isConnected) {
            return true;
        }

        if (!targetContainer?.parentElement) {
            return false;
        }

        const pendingMinHeight = getOptimisticConversationMinHeight(targetContainer);
        const optimisticContainer = createOptimisticConversationContainer(
            targetContainer,
            submittedText,
            pendingMinHeight,
            attachments,
        );
        if (!optimisticContainer) {
            return false;
        }

        targetContainer.parentElement.insertBefore(optimisticContainer, targetContainer);
        state.optimisticContainer = optimisticContainer;
        clearHistoryFrom(targetContainer);
        return true;
    }

    function handleOverrideSuccess(targetContainer, submittedText, attachments = getPendingOverrideAttachmentRecords()) {
        logDebug('Applying optimistic edit UI.', {
            submittedText,
            hasTarget: Boolean(targetContainer),
        });
        disableEditMode();

        if (state.optimisticContainer?.isConnected) {
            return;
        }

        state.optimisticContainer = null;
        beginOptimisticEditUi(targetContainer, submittedText, attachments);
    }

    function handlePendingEditSubmitIntent() {
        if (!state.pendingOverride || !state.editTargetContainer) {
            return;
        }

        beginOptimisticEditUi(state.editTargetContainer, getEditorText(), getPendingOverrideAttachmentRecords());
    }

    function getPendingRequestText(pendingRequest) {
        return getPlainTextFromElement(pendingRequest?.querySelector(SELECTORS.queryText))
            || getEditorText();
    }

    function syncPendingEditRequest() {
        if (
            !state.pendingOverride
            || !state.editTargetContainer
            || state.optimisticContainer?.isConnected
        ) {
            return false;
        }

        const pendingRequest = document.querySelector('pending-request');
        if (!pendingRequest) {
            return false;
        }

        beginOptimisticEditUi(state.editTargetContainer, getPendingRequestText(pendingRequest), getPendingOverrideAttachmentRecords());
        if (pendingRequest.isConnected) {
            pendingRequest.remove();
        }

        return true;
    }

    function handleEditClick(userQuery) {
        const currentContainer = userQuery.closest(SELECTORS.conversationContainer);
        if (!currentContainer) {
            logDebug('Edit click ignored: no conversation container.');
            return;
        }

        const allContainers = getConversationContainers();
        const index = allContainers.indexOf(currentContainer);
        if (index === -1) {
            return;
        }

        const textElement = userQuery.querySelector(SELECTORS.queryText);
        const text = getPlainTextFromElement(textElement);
        const parentData = getParentData(currentContainer, index, allContainers);
        const currentData = mergeJslogData(
            getBestJslogData(userQuery),
            getBestJslogData(currentContainer),
        );
        let cachedAttachments = getCachedAttachmentsForMessage(
            currentData?.c || getConversationIdFromLocation(),
            currentData?.r,
        );
        if (!cachedAttachments.length && promoteAttachmentCarryoverToContainer(currentContainer, index)) {
            cachedAttachments = getCachedAttachmentsForMessage(
                currentData?.c || getConversationIdFromLocation(),
                currentData?.r,
            );
        }

        const carryoverAttachments = !cachedAttachments.length
            ? getAttachmentCarryoverForContainer(currentContainer, index)
            : [];
        const sourceAttachments = cachedAttachments.length ? cachedAttachments : carryoverAttachments;
        const attachments = filterCachedAttachmentsByUserQueryUi(sourceAttachments, userQuery);
        logDebug('Opening edit mode.', {
            index,
            text,
            parentData,
            messageId: currentData?.r ?? null,
            cachedAttachmentCount: cachedAttachments.length,
            carryoverAttachmentCount: carryoverAttachments.length,
            attachmentCount: attachments.length,
        });

        enableEditMode(text, parentData, currentContainer, attachments);
    }

    function isLastConversationContainer(container) {
        const containers = getConversationContainers();
        return Boolean(container && containers[containers.length - 1] === container);
    }

    function getNativeEditButton(userQuery) {
        const nativeButton = Array.from(userQuery.querySelectorAll(SELECTORS.nativeEditButton))
            .find((button) => button.getAttribute(ATTRS.customButton) !== 'true');
        if (nativeButton) {
            return nativeButton;
        }

        return Array.from(userQuery.querySelectorAll(SELECTORS.nativeEditIcon))
            .map((icon) => icon.closest('button'))
            .find((button) => {
                return button && button.getAttribute(ATTRS.customButton) !== 'true';
            }) || null;
    }

    function hideNativeEditButton(nativeEditButton) {
        const wrapper = nativeEditButton?.closest?.('div');
        if (wrapper && wrapper.getAttribute(ATTRS.wrapper) !== 'true') {
            wrapper.style.display = 'none';
        }
    }

    function isNativeEditModeContainer(container) {
        return Boolean(container?.querySelector?.('.edit-button-area button.cancel-button, .edit-button-area button.update-button'));
    }

    function removeCustomEditButtons(root) {
        if (!root?.querySelectorAll) {
            return;
        }

        root.querySelectorAll(`[${ATTRS.wrapper}="true"]`).forEach((node) => {
            node.remove();
        });

        root.querySelectorAll(`[${ATTRS.customButton}="true"]`).forEach((button) => {
            const wrapper = button.closest(`[${ATTRS.wrapper}="true"]`);
            if (wrapper) {
                wrapper.remove();
            } else {
                button.remove();
            }
        });
    }

    function cleanupMisplacedCustomEditButtons() {
        document.querySelectorAll([
            SELECTORS.inputAreaContainer,
            SELECTORS.textInputField,
        ].join(', ')).forEach(removeCustomEditButtons);

        getConversationContainers().forEach((container) => {
            if (isNativeEditModeContainer(container)) {
                removeCustomEditButtons(container);
            }
        });
    }

    function getCustomEditButtonWrappers(userQuery) {
        const wrappers = Array.from(userQuery.querySelectorAll(`[${ATTRS.wrapper}="true"]`));
        const orphanButtons = Array.from(userQuery.querySelectorAll(`[${ATTRS.customButton}="true"]`))
            .filter((button) => !button.closest(`[${ATTRS.wrapper}="true"]`));

        return {
            wrappers,
            orphanButtons,
            count: wrappers.length + orphanButtons.length,
        };
    }

    function hasStableCustomEditButton(userQuery, buttonsContainer) {
        const custom = getCustomEditButtonWrappers(userQuery);
        return custom.count === 1
            && custom.wrappers.length === 1
            && custom.wrappers[0].parentElement === buttonsContainer
            && Boolean(custom.wrappers[0].querySelector(`[${ATTRS.customButton}="true"]`));
    }

    function triggerNativeEditMode(userQuery, nativeEditButton, customButtonContainer) {
        if (!nativeEditButton) {
            return false;
        }

        const currentContainer = userQuery.closest(SELECTORS.conversationContainer);
        const wrapper = nativeEditButton.closest('div');
        const previousDisplay = wrapper?.style?.display;
        customButtonContainer?.remove();
        removeCustomEditButtons(currentContainer);
        if (wrapper) {
            wrapper.style.display = '';
        }

        const hadScriptEditMode = Boolean(state.pendingOverride || state.editTargetContainer);
        nativeEditButton.click();

        if (wrapper) {
            wrapper.style.display = previousDisplay || 'none';
        }

        cleanupMisplacedCustomEditButtons();
        window.requestAnimationFrame(cleanupMisplacedCustomEditButtons);
        window.setTimeout(cleanupMisplacedCustomEditButtons, 100);
        window.setTimeout(cleanupMisplacedCustomEditButtons, 300);
        window.setTimeout(() => {
            cleanupMisplacedCustomEditButtons();
            if (!isNativeEditModeContainer(currentContainer)) {
                processUserQuery(userQuery);
            }
        }, 1500);

        if (hadScriptEditMode) {
            disableEditMode();
        }
        return true;
    }

    function canUseNativeEditMode(currentContainer, nativeEditButton) {
        return Boolean(nativeEditButton && isLastConversationContainer(currentContainer));
    }

    function processUserQuery(userQuery) {
        if (!userQuery) {
            return;
        }

        const currentContainer = userQuery.closest(SELECTORS.conversationContainer);
        if (isNativeEditModeContainer(currentContainer)) {
            removeCustomEditButtons(currentContainer);
            return;
        }

        const nativeEditButton = getNativeEditButton(userQuery);

        const copyIcon = userQuery.querySelector(SELECTORS.copyIcon);
        if (!copyIcon) {
            return;
        }

        const copyButton = copyIcon.closest('button');
        const copyWrapper = copyButton?.parentElement;
        const buttonsContainer = copyWrapper?.parentElement;
        if (!copyButton || !copyWrapper || !buttonsContainer) {
            return;
        }

        if (hasStableCustomEditButton(userQuery, buttonsContainer)) {
            userQuery.setAttribute(ATTRS.processed, 'true');
            hideNativeEditButton(nativeEditButton);
            return;
        }

        removeCustomEditButtons(userQuery);

        const { container, button } = createEditButtonElement(copyButton);

        if (nativeEditButton) {
            const nativeWrapper = nativeEditButton.closest('div');
            if (nativeWrapper) {
                nativeWrapper.style.display = 'none';
                buttonsContainer.insertBefore(container, nativeWrapper);
            } else if (copyWrapper.nextSibling) {
                buttonsContainer.insertBefore(container, copyWrapper.nextSibling);
            } else {
                buttonsContainer.appendChild(container);
            }
        } else if (copyWrapper.nextSibling) {
            buttonsContainer.insertBefore(container, copyWrapper.nextSibling);
        } else {
            buttonsContainer.appendChild(container);
        }

        userQuery.setAttribute(ATTRS.processed, 'true');
        button.addEventListener('click', (event) => {
            event.preventDefault();
            event.stopPropagation();

            if (event.ctrlKey && canUseNativeEditMode(currentContainer, nativeEditButton)) {
                triggerNativeEditMode(userQuery, nativeEditButton, container);
                return;
            }

            logDebug('Custom edit button clicked.', {
                text: userQuery.querySelector(SELECTORS.queryText)?.innerText ?? null,
            });
            handleEditClick(userQuery);
        });
    }

    function scanConversationContainers() {
        injectStyles();
        ensureComposerButtonRipples();
        cleanupMisplacedCustomEditButtons();
        syncAttachmentCacheScope();
        syncEditModeWithCurrentChat();
        syncOptimisticConversation();

        const containers = getConversationContainers();
        containers.forEach((container) => {
            const userQuery = container.querySelector(SELECTORS.userQuery);
            if (userQuery) {
                cleanupOptimisticUserQueryAttachments(userQuery);
                processUserQuery(userQuery);
            }
        });

        if (getAttachmentCarryover() && !state.optimisticContainer) {
            for (const [index, container] of containers.entries()) {
                if (promoteAttachmentCarryoverToContainer(container, index)) {
                    break;
                }
            }
        }

        if (state.pendingOverride) {
            ensureEditModeBanner();
        }

        syncEditComposerAttachmentUi();
    }

    function scheduleScan() {
        if (state.scanQueued) {
            return;
        }

        state.scanQueued = true;
        const schedule = window.requestAnimationFrame
            ? window.requestAnimationFrame.bind(window)
            : (callback) => window.setTimeout(callback, 16);

        schedule(() => {
            state.scanQueued = false;
            scanConversationContainers();
        });
    }

    function attachObserver() {
        if (state.observer) {
            return;
        }

        const observerTarget = getAppMain();
        if (!observerTarget) {
            return;
        }

        state.observer = new MutationObserver(() => {
            syncPendingEditRequest();
            syncOptimisticConversation();
            cleanupOptimisticUserQueryAttachmentsInDocument();
            scheduleScan();
        });

        state.observer.observe(observerTarget, {
            childList: true,
            subtree: true,
        });
    }

    function isEnabledSendButton(button) {
        return button
            && !button.disabled
            && button.getAttribute('aria-disabled') !== 'true';
    }

    function handleSubmitIntentCapture(event) {
        if (!state.pendingOverride) {
            return;
        }

        const sendButton = event.target?.closest?.('button.send-button.submit');
        if (isEnabledSendButton(sendButton)) {
            handlePendingEditSubmitIntent();
        }
    }

    function handleEditorSubmitKeyCapture(event) {
        if (
            !state.pendingOverride
            || event.defaultPrevented
            || event.isComposing
            || event.key !== 'Enter'
            || event.shiftKey
            || event.altKey
            || event.ctrlKey
            || event.metaKey
            || !event.target?.closest?.(SELECTORS.editor)
        ) {
            return;
        }

        handlePendingEditSubmitIntent();
    }

    function patchHistoryNavigation() {
        if (window.history.pushState.__geminiEditorPatched || window.history.replaceState.__geminiEditorPatched) {
            return;
        }

        const originalPushState = window.history.pushState;
        const originalReplaceState = window.history.replaceState;

        window.history.pushState = function pushState() {
            const result = originalPushState.apply(this, arguments);
            window.setTimeout(scheduleScan, 0);
            return result;
        };

        window.history.replaceState = function replaceState() {
            const result = originalReplaceState.apply(this, arguments);
            window.setTimeout(scheduleScan, 0);
            return result;
        };

        window.history.pushState.__geminiEditorPatched = true;
        window.history.replaceState.__geminiEditorPatched = true;
    }

    function startUiController() {
        if (state.uiStarted) {
            return;
        }

        state.uiStarted = true;
        injectStyles();
        initTooltipController();
        scheduleScan();
        attachObserver();
        patchHistoryNavigation();

        window.addEventListener('pageshow', scheduleScan);
        window.addEventListener('popstate', scheduleScan);
        document.addEventListener('DOMContentLoaded', () => {
            attachObserver();
            scheduleScan();
        }, { once: true });
        document.addEventListener('click', handleSubmitIntentCapture, true);
        document.addEventListener('keydown', handleEditorSubmitKeyCapture, true);
    }

    function isGeminiGenerateRequest(url) {
        return typeof url === 'string' && url.includes('StreamGenerate');
    }

    function isBatchExecuteRequest(url) {
        return typeof url === 'string' && url.includes('/_/BardChatUi/data/batchexecute');
    }

    function maybeCaptureConversationLoad(rawText) {
        const captured = handleConversationLoadResponse(rawText);
        if (captured) {
            logDebug('Captured conversation-load payload.', {
                cachedMessages: state.attachmentCache.size,
            });
        }

        return captured;
    }

    function maybeCaptureStreamGenerate(rawText) {
        const captured = handleStreamGenerateResponse(rawText);
        if (captured) {
            logDebug('Captured StreamGenerate attachments.', {
                cachedMessages: state.attachmentCache.size,
            });
            window.setTimeout(scheduleScan, 0);
        }

        return captured;
    }

    function getRequestBodyText(body) {
        if (typeof body === 'string') {
            return body;
        }

        if (body instanceof URLSearchParams) {
            return body.toString();
        }

        return '';
    }

    function parseStreamGenerateRequestBody(body) {
        const bodyText = getRequestBodyText(body);
        if (!bodyText) {
            return null;
        }

        try {
            const params = new URLSearchParams(bodyText);
            const requestPayload = params.get('f.req');
            if (!requestPayload) {
                return null;
            }

            const outerPayload = JSON.parse(requestPayload);
            if (!Array.isArray(outerPayload) || typeof outerPayload[1] !== 'string') {
                return null;
            }

            const innerPayload = JSON.parse(outerPayload[1]);
            return Array.isArray(innerPayload) ? innerPayload : null;
        } catch (error) {
            logDebugIssue('Failed to parse StreamGenerate request body.', error);
            return null;
        }
    }

    function getTextFromStreamGenerateRequestPayload(innerPayload) {
        return typeof innerPayload?.[0]?.[0] === 'string' ? innerPayload[0][0] : '';
    }

    function getConversationIdFromStreamGenerateRequestPayload(innerPayload) {
        const conversationId = innerPayload?.[2]?.[0];
        return isConversationId(conversationId) ? conversationId : getConversationIdFromLocation();
    }

    function maybeCaptureOutgoingStreamGenerate(body, meta = {}) {
        const innerPayload = parseStreamGenerateRequestBody(body);
        const nativeAttachments = Array.isArray(innerPayload?.[0]?.[3]) ? innerPayload[0][3] : [];
        if (!nativeAttachments.length) {
            return false;
        }

        const attachments = normalizeNativePayloadAttachments(nativeAttachments, getTextInputField());
        if (!attachments.length) {
            return false;
        }

        const submittedText = meta.submittedText || getTextFromStreamGenerateRequestPayload(innerPayload);
        setAttachmentCarryover(getConversationIdFromStreamGenerateRequestPayload(innerPayload), attachments, {
            submittedText,
            targetIndex: Number.isInteger(meta.targetIndex) ? meta.targetIndex : null,
        });
        window.setTimeout(scheduleScan, 0);
        logDebug('Captured outgoing StreamGenerate attachments.', {
            attachmentCount: attachments.length,
            submittedText,
        });
        return true;
    }

    function maybeCaptureXhrStreamGenerateResponse(xhr) {
        const rawText = typeof xhr?.responseText === 'string' ? xhr.responseText : '';
        if (!rawText || rawText.length === xhr[XHR_STREAM_CAPTURE_LENGTH]) {
            return;
        }

        xhr[XHR_STREAM_CAPTURE_LENGTH] = rawText.length;
        maybeCaptureStreamGenerate(rawText);
    }

    function applyPendingOverride(body) {
        if (!state.pendingOverride) {
            return { applied: false, body };
        }

        if (typeof body !== 'string') {
            if (body instanceof URLSearchParams) {
                body = body.toString();
            } else {
                return { applied: false, body };
            }
        }

        try {
            const params = new URLSearchParams(body);
            const requestPayload = params.get('f.req');
            if (!requestPayload) {
                return { applied: false, body };
            }

            const outerPayload = JSON.parse(requestPayload);
            if (!Array.isArray(outerPayload) || typeof outerPayload[1] !== 'string') {
                return { applied: false, body };
            }

            const innerPayload = JSON.parse(outerPayload[1]);
            if (!Array.isArray(innerPayload) || !Array.isArray(innerPayload[2]) || !Array.isArray(innerPayload[0])) {
                return { applied: false, body };
            }

            if (state.pendingOverride.c) {
                innerPayload[2][0] = state.pendingOverride.c;
            }

            innerPayload[2][1] = state.pendingOverride.r;
            innerPayload[2][2] = state.pendingOverride.rc;
            const preservedAttachmentRecords = Array.isArray(state.pendingOverride.attachments)
                ? state.pendingOverride.attachments.map(cloneAttachmentRecord).filter(Boolean)
                : [];
            const preservedAttachments = preservedAttachmentRecords
                .map(buildAttachmentPayloadRecord)
                .filter(Boolean);
            const nativeAttachments = filterNativePayloadAttachmentsByComposerUi(
                Array.isArray(innerPayload[0][3]) ? innerPayload[0][3] : [],
                getTextInputField(),
            );
            const nativeAttachmentRecords = normalizeNativePayloadAttachments(nativeAttachments, getTextInputField());
            innerPayload[0][3] = [...preservedAttachments, ...nativeAttachments];
            logDebug('Patched StreamGenerate payload.', {
                c: innerPayload[2][0],
                r: innerPayload[2][1],
                rc: innerPayload[2][2],
                attachmentCount: innerPayload[0][3].length,
                preservedAttachmentCount: preservedAttachments.length,
                nativeAttachmentCount: nativeAttachments.length,
            });

            outerPayload[1] = JSON.stringify(innerPayload);
            params.set('f.req', JSON.stringify(outerPayload));

            return {
                applied: true,
                conversationId: innerPayload[2][0] || state.pendingOverride.c || getConversationIdFromLocation(),
                attachments: [...preservedAttachmentRecords, ...nativeAttachmentRecords],
                body: params.toString(),
            };
        } catch (error) {
            logDebugIssue('Failed to override StreamGenerate payload.', error);
            return { applied: false, body };
        }
    }

    function patchFetch() {
        if (typeof window.fetch !== 'function' || window.fetch.__geminiEditorPatched) {
            return;
        }

        const originalFetch = window.fetch;

        window.fetch = function geminiEditorFetch(input, init) {
            const url = typeof input === 'string'
                ? input
                : (input && typeof input.url === 'string' ? input.url : '');

            if (isGeminiGenerateRequest(url)) {
                const submittedText = getEditorText();
                maybeCaptureOutgoingStreamGenerate(init?.body, {
                    submittedText,
                    targetIndex: getConversationContainers().length,
                });
            }

            return originalFetch.call(this, input, init).then((response) => {
                if (isBatchExecuteRequest(url)) {
                    response.clone().text().then((rawText) => {
                        maybeCaptureConversationLoad(rawText);
                    }).catch((error) => {
                        logDebugIssue('Failed to capture batchexecute fetch response.', error);
                    });
                }

                if (isGeminiGenerateRequest(url)) {
                    response.clone().text().then((rawText) => {
                        maybeCaptureStreamGenerate(rawText);
                    }).catch((error) => {
                        logDebugIssue('Failed to capture StreamGenerate fetch response.', error);
                    });
                }

                return response;
            });
        };

        window.fetch.__geminiEditorPatched = true;
    }

    function patchXmlHttpRequest() {
        const xhrPrototype = XMLHttpRequest.prototype;
        if (
            xhrPrototype.open.__geminiEditorPatched
            || xhrPrototype.send.__geminiEditorPatched
        ) {
            return;
        }

        const originalOpen = xhrPrototype.open;
        const originalSend = xhrPrototype.send;

        xhrPrototype.open = function open(method, url) {
            this[XHR_URL] = typeof url === 'string' ? url : String(url);
            return originalOpen.apply(this, arguments);
        };

        xhrPrototype.send = function send(body) {
            let nextBody = body;

            if (!this[XHR_CAPTURE_ATTACHED] && isBatchExecuteRequest(this[XHR_URL])) {
                this.addEventListener('load', () => {
                    try {
                        maybeCaptureConversationLoad(this.responseText);
                    } catch (error) {
                        logDebugIssue('Failed to capture batchexecute XHR response.', error);
                    }
                });
                this[XHR_CAPTURE_ATTACHED] = true;
            }

            if (!this[XHR_CAPTURE_ATTACHED] && isGeminiGenerateRequest(this[XHR_URL])) {
                this.addEventListener('progress', () => {
                    try {
                        maybeCaptureXhrStreamGenerateResponse(this);
                    } catch (error) {
                        logDebugIssue('Failed to capture progressive StreamGenerate XHR response.', error);
                    }
                });
                this.addEventListener('load', () => {
                    try {
                        maybeCaptureXhrStreamGenerateResponse(this);
                    } catch (error) {
                        logDebugIssue('Failed to capture StreamGenerate XHR response.', error);
                    }
                });
                this[XHR_CAPTURE_ATTACHED] = true;
            }

            if (state.pendingOverride && isGeminiGenerateRequest(this[XHR_URL])) {
                const targetContainer = state.editTargetContainer;
                const submittedText = getEditorText();
                const pendingOverride = {
                    ...state.pendingOverride,
                    attachments: Array.isArray(state.pendingOverride.attachments)
                        ? state.pendingOverride.attachments.map(cloneAttachmentRecord).filter(Boolean)
                        : [],
                };
                const targetIndex = targetContainer
                    ? getConversationContainers().indexOf(targetContainer)
                    : null;
                logDebug('Intercepted candidate StreamGenerate request.', {
                    submittedText,
                    pendingOverride: state.pendingOverride,
                });
                const result = applyPendingOverride(body);

                if (result.applied) {
                    nextBody = result.body;
                    const submittedAttachments = Array.isArray(result.attachments)
                        ? result.attachments.map(cloneAttachmentRecord).filter(Boolean)
                        : pendingOverride.attachments;
                    setAttachmentCarryover(result.conversationId, submittedAttachments, {
                        submittedText,
                        targetIndex,
                    });
                    state.pendingOverride = null;
                    handleOverrideSuccess(targetContainer, submittedText, submittedAttachments);
                }
            } else if (isGeminiGenerateRequest(this[XHR_URL])) {
                maybeCaptureOutgoingStreamGenerate(nextBody, {
                    submittedText: getEditorText(),
                    targetIndex: getConversationContainers().length,
                });
            }

            return originalSend.call(this, nextBody);
        };

        xhrPrototype.open.__geminiEditorPatched = true;
        xhrPrototype.send.__geminiEditorPatched = true;
    }

    function bootstrap() {
        console.log(LOG_PREFIX, 'Initializing userscript.');
        patchXmlHttpRequest();
        patchFetch();
        startUiController();
        document.addEventListener('DOMContentLoaded', scheduleScan, { once: true });
    }

    bootstrap();
})();