Greasy Fork

Greasy Fork is available in English.

AI 对话助手(一键同步多模型)

拒绝复制粘贴!一键将你的问题分发给 ChatGPT、Claude、Gemini、豆包、Kimi 等所有 AI 模型。在任意 AI 网站提问,脚本会自动将问题同步到其他已打开的 AI 标签页。助你快速横向对比模型效果,效率提升 10 倍。

当前为 2025-11-19 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         AI 对话助手(一键同步多模型)
// @name:zh-CN   AI 对话助手(一键同步多模型)
// @name:en      AI Chat Assistant (One-click Sync Multi-Model)
// @namespace    https://github.com/YHangbin
// @version      1.0
// @description  拒绝复制粘贴!一键将你的问题分发给 ChatGPT、Claude、Gemini、豆包、Kimi 等所有 AI 模型。在任意 AI 网站提问,脚本会自动将问题同步到其他已打开的 AI 标签页。助你快速横向对比模型效果,效率提升 10 倍。
// @description:en  Do not copy and paste! Sync your questions to ChatGPT, Claude, Gemini, Doubao, Kimi and other AI models with one click.
// @author       Gemini 2.5 Pro & User
// @match        https://doubao.com/chat/*
// @match        https://www.doubao.com/chat/*
// @match        https://chat.qwen.ai/*
// @match        https://tongyi.com/*
// @match        https://www.tongyi.com/*
// @match        https://aistudio.google.com/*
// @match        https://gemini.google.com/*
// @match        https://chatgpt.com/*
// @match        https://yuanbao.tencent.com/*
// @match        https://chat.deepseek.com/*
// @match        https://kimi.com/*
// @match        https://www.kimi.com/*
// @match        https://claude.ai/*
// @match        https://grok.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_addValueChangeListener
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_addStyle
// @grant        unsafeWindow
// @run-at       document-start
// @license      MIT
// ==/UserScript==

/*
 * =================================================================================================
 * --- 功能简介与使用说明 ---
 *
 * 【AI 对话助手(一键同步多模型)】
 *
 * 核心目标:拒绝复制粘贴,实现“一处提问,多处同步”。
 *
 * 核心亮点:
 * 1. 光速同步: 在一个页面输入,所有模型即刻响应。
 * 2. 双向互通: 不分“主次”,任何一个聊天窗口都可以作为控制台。
 * 3. 原生体验: 非弹窗式设计,使用网站原生的输入框,保留所有富文本功能。
 * 4. 广泛支持: 适配 ChatGPT, Claude, Gemini, 豆包, Kimi, 通义千问, DeepSeek, Grok 等。
 *
 * 简易使用说明:
 * 1. 打开面板: 点击页面右下角的悬浮按钮。
 * 2. 选择目标:
 *    - 灰色 (点击启动): 标签页未打开,点击自动打开。
 *    - 蓝色边框 (待发送): 标签页已打开,点击即可选中。
 *    - 蓝色填充 (已选中): 问题将同步发送给这些模型。
 * 3. 发送问题: 在当前输入框正常提问,脚本自动分发。
 * 4. 管理模型: 点击齿轮图标,自定义显示哪些常用模型。
 * =================================================================================================
 */

(function () {
    'use strict';

    /**
     * @class AITabSync
     * @description Core application object for the AI Tab Sync userscript.
     * Encapsulates all state, configuration, and logic.
     */
    const AITabSync = {
        // ===================================================================================
        // --- 1. State Management ---
        // ===================================================================================
        state: {
            thisSite: null,
            visibleTargets: [],
            selectedTargets: new Set(),
            isLoggingEnabled: false,
            isSubmitting: false,
            isProcessingTask: false,
            menuCommandId: null,
            tooltipTimeoutId: null,
        },

        // ===================================================================================
        // --- 2. Configuration ---
        // ===================================================================================
        config: {
            SCRIPT_VERSION: '1.0',
            KEYS: {
                SHARED_QUERY: 'multi_sync_query_v1.0',
                ACTIVE_TABS: 'multi_sync_active_tabs_v1.0',
                LOGGING_ENABLED: 'multi_sync_logging_v1.0',
                VISIBLE_TARGETS: 'multi_sync_visible_targets_v1.0',
            },
            TIMINGS: {
                HEARTBEAT_INTERVAL: 5000,
                STALE_THRESHOLD: 15000,
                CLEANUP_INTERVAL: 10000,
                SUBMIT_TIMEOUT: 20000, // Increased timeout for complex SPAs
                HUMAN_LIKE_DELAY: 500,
                FRESHNESS_THRESHOLD: 5000,
                TOOLTIP_DELAY: 300,
            },
            DISPLAY_ORDER: ['AI_STUDIO', 'GEMINI', 'TONGYI', 'QWEN', 'YUANBAO', 'CHATGPT', 'CLAUDE', 'DOUBAO', 'DEEPSEEK', 'KIMI', 'GROK'],
            SITES: {
                GROK: {
                    id: 'GROK',
                    name: 'Grok',
                    host: 'grok.com',
                    url: 'https://grok.com/',
                    apiPaths: ['/rest/app-chat/conversations/'],
                    inputSelectors: ['div.tiptap.ProseMirror'],
                    queryExtractor: (body) => {
                        try {
                            return JSON.parse(body)?.message || '';
                        } catch (e) {
                            return '';
                        }
                    },
                },
                CLAUDE: {
                    id: 'CLAUDE',
                    name: 'Claude',
                    host: 'claude.ai',
                    url: 'https://claude.ai/new',
                    apiPaths: ['/api/organizations/', '/completion'],
                    inputSelectors: ['div[contenteditable="true"][role="textbox"]'],
                    queryExtractor: (body) => {
                        try {
                            return JSON.parse(body)?.prompt || '';
                        } catch (e) {
                            return '';
                        }
                    },
                },
                KIMI: {
                    id: 'KIMI',
                    name: 'Kimi',
                    host: 'kimi.com',
                    url: 'https://www.kimi.com/',
                    apiPaths: ['/apiv2/kimi.gateway.chat.v1.ChatService/Chat'],
                    inputSelectors: ['[data-lexical-editor="true"]'],
                    queryExtractor: (body) => {
                        try {
                            const firstBraceIndex = body.indexOf('{');
                            const lastBraceIndex = body.lastIndexOf('}');
                            if (firstBraceIndex === -1 || lastBraceIndex < firstBraceIndex) return '';
                            const jsonString = body.substring(firstBraceIndex, lastBraceIndex + 1);
                            return JSON.parse(jsonString)?.message?.blocks?.[0]?.text?.content || '';
                        } catch (e) {
                            return '';
                        }
                    },
                },
                GEMINI: {
                    id: 'GEMINI',
                    name: 'Gemini',
                    host: 'gemini.google.com',
                    url: 'https://gemini.google.com/app',
                    apiPaths: ['/StreamGenerate'],
                    inputSelectors: ['div.ql-editor[contenteditable="true"]'],
                    queryExtractor: (body) => {
                        try {
                            const params = new URLSearchParams(body);
                            const f_req = params.get('f.req');
                            if (!f_req) return '';
                            const outerArray = JSON.parse(f_req);
                            const innerJsonString = outerArray?.[1];
                            if (!innerJsonString) return '';
                            const innerArray = JSON.parse(innerJsonString);
                            const query = innerArray?.[0]?.[0];
                            return typeof query === 'string' ? query : '';
                        } catch (e) {
                            return '';
                        }
                    },
                },
                YUANBAO: {
                    id: 'YUANBAO',
                    name: '元宝',
                    host: 'yuanbao.tencent.com',
                    url: 'https://yuanbao.tencent.com/',
                    apiPaths: ['/api/chat/'],
                    inputSelectors: ['.ql-editor[contenteditable="true"]'],
                    queryExtractor: (body) => {
                        try {
                            return JSON.parse(body)?.prompt || '';
                        } catch (e) {
                            return '';
                        }
                    },
                },
                DEEPSEEK: {
                    id: 'DEEPSEEK',
                    name: 'DeepSeek',
                    host: 'chat.deepseek.com',
                    url: 'https://chat.deepseek.com/',
                    apiPaths: ['/api/v0/chat/completion'],
                    inputSelectors: ['textarea[placeholder="给 DeepSeek 发送消息 "]'],
                    queryExtractor: (body) => {
                        try {
                            return JSON.parse(body)?.prompt || '';
                        } catch (e) {
                            return '';
                        }
                    },
                },
                DOUBAO: {
                    id: 'DOUBAO',
                    name: '豆包',
                    host: 'doubao.com',
                    url: 'https://www.doubao.com/chat/',
                    apiPaths: ['/samantha/chat/completion'],
                    inputSelectors: ['textarea[data-testid="chat_input_input"]'],
                    queryExtractor: (body) => {
                        try {
                            const outerJson = JSON.parse(body);
                            const innerJsonString = outerJson?.messages?.[0]?.content;
                            if (!innerJsonString) return '';
                            const innerJson = JSON.parse(innerJsonString);
                            return innerJson?.text || '';
                        } catch (e) {
                            return '';
                        }
                    },
                },
                QWEN: {
                    id: 'QWEN',
                    name: 'Qwen',
                    host: 'chat.qwen.ai',
                    url: 'https://chat.qwen.ai/',
                    apiPaths: ['/api/v2/chat/completions'],
                    inputSelectors: ['textarea#chat-input', 'textarea[data-testid="yuntu-textarea"]'],
                    queryExtractor: (body) => {
                        try {
                            return JSON.parse(body)?.messages?.slice(-1)?.[0]?.content || '';
                        } catch (e) {
                            return '';
                        }
                    },
                },
                TONGYI: {
                    id: 'TONGYI',
                    name: '通义',
                    host: 'tongyi.com',
                    url: 'https://www.tongyi.com/',
                    apiPaths: ['/dialog/conversation'],
                    inputSelectors: ['div[class*="textareaWrap"] textarea', 'textarea[class*="ant-input"]'],
                    queryExtractor: (body) => {
                        try {
                            return JSON.parse(body)?.contents?.[0]?.content || '';
                        } catch (e) {
                            return '';
                        }
                    },
                },
                AI_STUDIO: {
                    id: 'AI_STUDIO',
                    name: 'AI Studio',
                    host: 'aistudio.google.com',
                    url: 'https://aistudio.google.com/prompts/new_chat',
                    apiPaths: ['/GenerateContent'],
                    inputSelectors: ['ms-autosize-textarea textarea'],
                    queryExtractor: (body) => {
                        try {
                            const json = JSON.parse(body);
                            const messages = json?.[1];
                            if (Array.isArray(messages)) {
                                for (let i = messages.length - 1; i >= 0; i--) {
                                    const msgBlock = messages[i];
                                    if (Array.isArray(msgBlock) && msgBlock[1] === 'user') {
                                        return msgBlock[0]?.[0]?.[1] || '';
                                    }
                                }
                            }
                            return '';
                        } catch (e) {
                            return '';
                        }
                    },
                },
                CHATGPT: {
                    id: 'CHATGPT',
                    name: 'ChatGPT',
                    host: 'chatgpt.com',
                    url: 'https://chatgpt.com/',
                    apiPaths: ['/backend-api/conversation', '/backend-api/f/conversation'],
                    inputSelectors: ['#prompt-textarea'],
                    queryExtractor: (body) => {
                        try {
                            const json = JSON.parse(body);
                            const lastMessage = json?.messages?.slice(-1)?.[0];
                            return lastMessage?.content?.parts?.[0] || '';
                        } catch (e) {
                            return '';
                        }
                    },
                },
            },
        },

        // ===================================================================================
        // --- 3. Cached Elements ---
        // ===================================================================================
        elements: {
            container: null,
            fab: null,
            chipsContainer: null,
            settingsModal: null,
            tooltip: null,
        },

        // ===================================================================================
        // --- 4. Utility Methods ---
        // ===================================================================================
        utils: {
            log(message, ...optionalParams) {
                if (!AITabSync.state.isLoggingEnabled || typeof console === 'undefined') return;
                console.log(`%c[AI Sync v${AITabSync.config.SCRIPT_VERSION}] ${message}`, 'color: #1976D2; font-weight: bold;', ...optionalParams);
            },
            waitFor(conditionFn, timeout, description) {
                return new Promise((resolve, reject) => {
                    let result = conditionFn();
                    if (result) return resolve(result);
                    let timeoutId = null;
                    const observer = new MutationObserver(() => {
                        result = conditionFn();
                        if (result) {
                            if (timeoutId) clearTimeout(timeoutId);
                            observer.disconnect();
                            resolve(result);
                        }
                    });
                    observer.observe(document.documentElement, {
                        childList: true,
                        subtree: true,
                        attributes: true,
                    });
                    timeoutId = setTimeout(() => {
                        observer.disconnect();
                        const lastResult = conditionFn();
                        lastResult ? resolve(lastResult) : reject(new Error(`waitFor timed out after ${timeout}ms for: ${description}`));
                    }, timeout);
                });
            },
            deepQuerySelector(selector, root = document) {
                try {
                    const el = root.querySelector(selector);
                    if (el) return el;
                } catch (e) {
                    /* ignore */
                }
                for (const host of root.querySelectorAll('*')) {
                    if (host.shadowRoot) {
                        const found = AITabSync.utils.deepQuerySelector(selector, host.shadowRoot);
                        if (found) return found;
                    }
                }
                return null;
            },
            getCurrentSiteInfo() {
                const { SITES } = AITabSync.config;
                const currentHost = window.location.hostname;
                if (currentHost.includes('chatgpt.com')) return SITES.CHATGPT;
                for (const siteKey in SITES) {
                    if (Object.prototype.hasOwnProperty.call(SITES, siteKey) && currentHost.includes(SITES[siteKey].host)) {
                        return SITES[siteKey];
                    }
                }
                return null;
            },
            simulateInput(element, value) {
                element.focus();
                const siteId = AITabSync.state.thisSite?.id;

                // Handle complex rich-text editors with specific event-based methods first.
                if (siteId === 'GROK') {
                    // Grok (Tiptap/ProseMirror) responds well to simulated paste events.
                    const dataTransfer = new DataTransfer();
                    dataTransfer.setData('text/plain', value);
                    const pasteEvent = new ClipboardEvent('paste', {
                        clipboardData: dataTransfer,
                        bubbles: true,
                        cancelable: true,
                    });
                    element.dispatchEvent(pasteEvent);
                } else if (siteId === 'KIMI') {
                    // Kimi (Lexical) responds to 'beforeinput' events.
                    const beforeInputEvent = new InputEvent('beforeinput', {
                        bubbles: true,
                        cancelable: true,
                        composed: true,
                        inputType: 'insertText',
                        data: value,
                    });
                    element.dispatchEvent(beforeInputEvent);
                } else if (element.isContentEditable || element.contentEditable === 'true') {
                    // Generic handler for other contentEditable elements (like Gemini, Claude).
                    if (siteId === 'CLAUDE') {
                        element.innerHTML = `<p>${value}</p>`; // Claude expects a paragraph.
                    } else {
                        element.textContent = value;
                    }
                    element.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
                } else if (element.tagName === 'TEXTAREA') {
                    // Standard handler for <textarea> elements.
                    const valueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
                    valueSetter.call(element, value);
                    element.dispatchEvent(new Event('input', { bubbles: true }));
                    element.dispatchEvent(new Event('change', { bubbles: true }));
                }
            },
        },

        // ===================================================================================
        // --- 5. UI Module ---
        // ===================================================================================
        ui: {
            injectStyle() {
                GM_addStyle(`
                    @keyframes ai-sync-halo-spin {
                        from { transform: rotate(0deg); }
                        to { transform: rotate(360deg); }
                    }
                    :root {
                        --ai-sync-active-color: #0d6efd;
                        --ai-sync-theme-color: #0d6efd;
                        --ai-sync-offline-color: #6c757d;
                    }
                    #ai-sync-container {
                        position: fixed;
                        bottom: 20px;
                        right: 20px;
                        z-index: 99998;
                        display: flex;
                        align-items: flex-end;
                        gap: 12px;
                        pointer-events: none;
                    }
                    #ai-sync-container.expanded {
                        pointer-events: auto;
                    }
                    #ai-sync-toggle-fab {
                        position: relative;
                        width: 36px;
                        height: 36px;
                        background-color: #fff;
                        border: 1px solid #e0e0e0;
                        border-radius: 50%;
                        box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
                        display: flex;
                        align-items: center;
                        justify-content: center;
                        cursor: pointer;
                        padding: 0;
                        color: #212121;
                        pointer-events: auto;
                        transition: all 0.2s;
                    }
                    #ai-sync-toggle-fab:hover {
                        border-color: #c0c0c0;
                        transform: scale(1.08);
                        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
                    }
                    #ai-sync-toggle-fab:focus-visible {
                        outline: 2px solid var(--ai-sync-theme-color);
                        outline-offset: 2px;
                    }
                    #ai-sync-toggle-fab::before {
                        content: '';
                        position: absolute;
                        top: -2px;
                        left: -2px;
                        width: calc(100% + 4px);
                        height: calc(100% + 4px);
                        border-radius: 50%;
                        background: conic-gradient(from 180deg, transparent 0%, transparent 70%, var(--ai-sync-theme-color) 100%);
                        z-index: -1;
                        opacity: 0;
                        transition: opacity 0.3s ease;
                    }
                    #ai-sync-toggle-fab.sending::before {
                        opacity: 1;
                        animation: ai-sync-halo-spin 1.2s linear infinite;
                    }
                    .ai-sync-fab-badge {
                        position: absolute;
                        top: -2px;
                        right: -4px;
                        background-color: var(--ai-sync-theme-color);
                        color: white;
                        border-radius: 8px;
                        padding: 0 5px;
                        font-size: 11px;
                        font-weight: 600;
                        line-height: 16px;
                        min-width: 16px;
                        text-align: center;
                        border: 1px solid white;
                    }
                    #ai-sync-content-panel {
                        display: inline-block;
                        background-color: rgba(255, 255, 255, 0.98);
                        backdrop-filter: blur(8px);
                        border: 1px solid #e0e0e0;
                        border-radius: 12px;
                        padding: 12px 16px;
                        box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
                        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
                        opacity: 0;
                        transform: translateX(15px);
                        transition: none;
                        visibility: hidden;
                    }
                    #ai-sync-container.expanded #ai-sync-content-panel {
                        opacity: 1;
                        transform: translateX(0);
                        visibility: visible;
                    }
                    #ai-sync-panel-title-wrapper {
                        display: flex;
                        justify-content: space-between;
                        align-items: center;
                        margin-bottom: 12px;
                    }
                    #ai-sync-panel-title {
                        font-weight: 600;
                        font-size: 15px;
                        color: #333;
                    }
                    #ai-sync-settings-btn {
                        all: unset;
                        cursor: pointer;
                        color: #757575;
                        padding: 2px;
                        border-radius: 4px;
                        line-height: 1;
                    }
                    #ai-sync-settings-btn:hover {
                        color: #212121;
                        background-color: #f0f0f0;
                    }
                    #ai-sync-chips-container {
                        display: flex;
                        flex-wrap: wrap;
                        gap: 8px;
                        max-width: 240px;
                    }
                    #ai-sync-chips-container > i {
                        flex-grow: 1;
                    }
                    .ai-sync-chip {
                        all: unset;
                        box-sizing: border-box;
                        cursor: pointer;
                        padding: 5px 10px;
                        border-radius: 16px;
                        font-size: 13px;
                        line-height: 1.4;
                        border: 1.5px solid var(--ai-sync-offline-color);
                        color: var(--ai-sync-offline-color);
                        background-color: #fff;
                        transition: all 0.2s ease;
                        user-select: none;
                        text-align: center;
                        flex-grow: 1;
                    }
                    .ai-sync-chip:not(.selected):hover {
                        background-color: #f5f5f5;
                    }
                    .ai-sync-chip:active {
                        transform: scale(0.96);
                    }
                    .ai-sync-chip.online {
                        border-color: var(--ai-sync-active-color);
                        color: var(--ai-sync-active-color);
                    }
                    .ai-sync-chip.selected {
                        background-color: var(--ai-sync-active-color);
                        border-color: var(--ai-sync-active-color);
                        color: white;
                    }
                    #ai-sync-settings-overlay {
                        display: none;
                        position: fixed;
                        top: 0;
                        left: 0;
                        width: 100vw;
                        height: 100vh;
                        background-color: rgba(0, 0, 0, 0.4);
                        z-index: 99999;
                        justify-content: center;
                        align-items: center;
                        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
                    }
                    #ai-sync-settings-panel {
                        background-color: #fff;
                        border-radius: 12px;
                        box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15);
                        width: 250px;
                        padding: 20px 24px;
                        display: flex;
                        flex-direction: column;
                        gap: 16px;
                    }
                    .ai-sync-settings-title {
                        margin: 0;
                        font-size: 18px;
                        font-weight: 600;
                        color: #212121;
                    }
                    .ai-sync-settings-list {
                        display: grid;
                        grid-template-columns: auto auto;
                        justify-content: space-between;
                        gap: 12px;
                    }
                    .ai-sync-settings-item label {
                        display: flex;
                        align-items: center;
                        cursor: pointer;
                        font-size: 14px;
                        color: #424242;
                    }
                    .ai-sync-settings-item input[type="checkbox"] {
                        margin-right: 8px;
                        accent-color: var(--ai-sync-theme-color);
                    }
                    #ai-sync-custom-tooltip {
                        display: none;
                        position: fixed;
                        background-color: rgba(249, 249, 249, 0.98);
                        backdrop-filter: blur(8px);
                        color: #212121;
                        border: 1px solid #e0e0e0;
                        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
                        padding: 4px 8px;
                        border-radius: 6px;
                        font-size: 12px;
                        z-index: 100000;
                        pointer-events: none;
                        transform: translate(-50%, -100%);
                        white-space: nowrap;
                        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
                    }
                `);
            },
            createMainPanel() {
                if (document.getElementById('ai-sync-container')) return;

                const { elements } = AITabSync;
                const svgNS = 'http://www.w3.org/2000/svg';

                elements.container = document.createElement('div');
                elements.container.id = 'ai-sync-container';

                const panel = document.createElement('div');
                panel.id = 'ai-sync-content-panel';

                const titleWrapper = document.createElement('div');
                titleWrapper.id = 'ai-sync-panel-title-wrapper';
                const title = document.createElement('span');
                title.id = 'ai-sync-panel-title';
                title.textContent = '发送给:';
                titleWrapper.appendChild(title);

                const settingsBtn = document.createElement('button');
                settingsBtn.id = 'ai-sync-settings-btn';
                settingsBtn.title = '自定义常用模型';
                const settingsSvg = document.createElementNS(svgNS, 'svg');
                settingsSvg.setAttribute('width', '16');
                settingsSvg.setAttribute('height', '16');
                settingsSvg.setAttribute('fill', 'currentColor');
                settingsSvg.setAttribute('viewBox', '0 0 16 16');
                const settingsPath = document.createElementNS(svgNS, 'path');
                settingsPath.setAttribute(
                    'd',
                    'M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311a1.464 1.464 0 0 1-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.169.311c-.698 1.283.705 2.686 1.987 1.987l.31.17a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.169-.311c.698-1.283-.705-2.686-1.987-1.987l-.31-.17a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z',
                );
                settingsSvg.appendChild(settingsPath);
                settingsBtn.appendChild(settingsSvg);
                titleWrapper.appendChild(settingsBtn);
                panel.appendChild(titleWrapper);

                elements.chipsContainer = this.buildChipsContainer();
                panel.appendChild(elements.chipsContainer);

                elements.fab = document.createElement('button');
                elements.fab.id = 'ai-sync-toggle-fab';
                elements.fab.title = 'AI 对话助手';
                elements.fab.setAttribute('aria-label', 'AI 对话助手');
                const fabSvg = document.createElementNS(svgNS, 'svg');
                fabSvg.setAttribute('width', '20');
                fabSvg.setAttribute('height', '20');
                fabSvg.setAttribute('viewBox', '0 0 24 24');
                fabSvg.setAttribute('fill', 'none');
                fabSvg.setAttribute('stroke', 'currentColor');
                fabSvg.setAttribute('stroke-width', '1.2');
                fabSvg.setAttribute('stroke-linecap', 'round');
                fabSvg.setAttribute('stroke-linejoin', 'round');
                fabSvg.setAttribute('aria-hidden', 'true');
                const fabRect = document.createElementNS(svgNS, 'rect');
                fabRect.setAttribute('x', '9');
                fabRect.setAttribute('y', '9');
                fabRect.setAttribute('width', '13');
                fabRect.setAttribute('height', '13');
                fabRect.setAttribute('rx', '2');
                fabRect.setAttribute('ry', '2');
                const fabPath = document.createElementNS(svgNS, 'path');
                fabPath.setAttribute('d', 'M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1');
                fabSvg.appendChild(fabRect);
                fabSvg.appendChild(fabPath);
                elements.fab.appendChild(fabSvg);

                elements.container.appendChild(panel);
                elements.container.appendChild(elements.fab);
                document.body.appendChild(elements.container);
            },
            createSettingsModal() {
                if (document.getElementById('ai-sync-settings-overlay')) return;
                const { config } = AITabSync;

                const overlay = document.createElement('div');
                overlay.id = 'ai-sync-settings-overlay';
                const panel = document.createElement('div');
                panel.id = 'ai-sync-settings-panel';

                const title = document.createElement('h2');
                title.className = 'ai-sync-settings-title';
                title.textContent = '自定义常用模型';
                panel.appendChild(title);

                const list = document.createElement('div');
                list.className = 'ai-sync-settings-list';

                config.DISPLAY_ORDER.forEach((siteId) => {
                    const site = config.SITES[siteId];
                    if (!site) return;

                    const item = document.createElement('div');
                    item.className = 'ai-sync-settings-item';
                    const label = document.createElement('label');
                    const checkbox = document.createElement('input');
                    checkbox.type = 'checkbox';
                    checkbox.value = siteId;
                    checkbox.checked = AITabSync.state.visibleTargets.includes(siteId);
                    label.appendChild(checkbox);
                    label.appendChild(document.createTextNode(` ${site.name}`));
                    item.appendChild(label);
                    list.appendChild(item);
                });

                panel.appendChild(list);
                overlay.appendChild(panel);
                document.body.appendChild(overlay);
                AITabSync.elements.settingsModal = overlay;
            },
            createTooltip() {
                if (document.getElementById('ai-sync-custom-tooltip')) return;
                AITabSync.elements.tooltip = document.createElement('div');
                AITabSync.elements.tooltip.id = 'ai-sync-custom-tooltip';
                document.body.appendChild(AITabSync.elements.tooltip);
            },
            buildChipsContainer() {
                const { config, state } = AITabSync;
                const container = document.createElement('div');
                container.id = 'ai-sync-chips-container';

                const targetsToDisplay = config.DISPLAY_ORDER.filter((id) => state.visibleTargets.includes(id) && id !== state.thisSite.id);

                targetsToDisplay.forEach((siteId) => {
                    const site = config.SITES[siteId];
                    if (!site) return;
                    const chip = document.createElement('button');
                    chip.className = 'ai-sync-chip';
                    chip.dataset.siteId = site.id;
                    chip.textContent = site.name;
                    container.appendChild(chip);
                });
                container.appendChild(document.createElement('i'));
                container.appendChild(document.createElement('i'));
                return container;
            },
            async rebuildChipsUI() {
                const { elements } = AITabSync;
                const oldContainer = elements.chipsContainer || document.getElementById('ai-sync-chips-container');
                if (oldContainer && oldContainer.parentElement) {
                    const newContainer = this.buildChipsContainer();
                    oldContainer.parentElement.replaceChild(newContainer, oldContainer);
                    elements.chipsContainer = newContainer;
                    await this.updatePanelState();
                }
            },
            async updatePanelState() {
                const activeTabs = JSON.parse(await GM_getValue(AITabSync.config.KEYS.ACTIVE_TABS, '{}'));
                document.querySelectorAll('.ai-sync-chip').forEach((chip) => {
                    const siteId = chip.dataset.siteId;
                    chip.classList.toggle('online', !!activeTabs[siteId]);
                    chip.classList.toggle('selected', AITabSync.state.selectedTargets.has(siteId));
                });
                this.updateFabBadge();
            },
            updateFabBadge() {
                const { fab } = AITabSync.elements;
                if (!fab) return;
                const count = AITabSync.state.selectedTargets.size;
                let badge = fab.querySelector('.ai-sync-fab-badge');
                if (count > 0) {
                    if (!badge) {
                        badge = document.createElement('span');
                        badge.className = 'ai-sync-fab-badge';
                        fab.appendChild(badge);
                    }
                    badge.textContent = count;
                } else {
                    badge?.remove();
                }
            },
            togglePanelVisibility() {
                const { container } = AITabSync.elements;
                if (!container) return;
                container.classList.toggle('expanded');
                if (container.classList.contains('expanded')) {
                    this.updatePanelState();
                    document.addEventListener('click', AITabSync.events.onClickOutside, true);
                } else {
                    document.removeEventListener('click', AITabSync.events.onClickOutside, true);
                }
            },
            updateMenuCommand() {
                const { state } = AITabSync;
                if (state.menuCommandId) GM_unregisterMenuCommand(state.menuCommandId);
                const label = state.isLoggingEnabled ? '停用调试日志' : '启用调试日志';
                state.menuCommandId = GM_registerMenuCommand(label, AITabSync.events.onToggleLogging);
            },
        },

        // ===================================================================================
        // --- 6. Event Handlers ---
        // ===================================================================================
        events: {
            register() {
                const { elements, ui } = AITabSync;
                elements.fab.addEventListener('click', (e) => {
                    e.stopPropagation();
                    ui.togglePanelVisibility();
                });
                elements.container.addEventListener('click', this.onChipClick);
                elements.container.querySelector('#ai-sync-settings-btn').addEventListener('click', () => {
                    if (elements.settingsModal) elements.settingsModal.style.display = 'flex';
                });
                elements.settingsModal.addEventListener('click', (e) => {
                    if (e.target === elements.settingsModal) elements.settingsModal.style.display = 'none';
                });
                elements.settingsModal.querySelector('.ai-sync-settings-list').addEventListener('change', this.onSettingsChange);

                elements.container.addEventListener('mouseover', this.onChipMouseOver, true);
                elements.container.addEventListener('mouseout', this.onChipMouseOut, true);
            },
            async onChipClick(event) {
                if (!event.target.matches('.ai-sync-chip')) return;
                const { config, state, ui, utils } = AITabSync;
                const chip = event.target;
                const siteId = chip.dataset.siteId;
                const siteInfo = config.SITES[siteId];
                if (!siteInfo) return;

                if (state.selectedTargets.has(siteId)) {
                    state.selectedTargets.delete(siteId);
                } else {
                    state.selectedTargets.add(siteId);
                    const activeTabs = JSON.parse(await GM_getValue(config.KEYS.ACTIVE_TABS, '{}'));
                    if (!activeTabs[siteId]) {
                        utils.log(`选择了离线目标 ${siteId},正在打开新标签页...`);
                        window.open(siteInfo.url, `ai_sync_window_for_${siteId}`);
                    }
                }
                ui.updatePanelState();
            },
            async onSettingsChange(event) {
                if (event.target.type !== 'checkbox') return;
                const { config } = AITabSync;
                const list = event.currentTarget;
                const checkboxes = list.querySelectorAll('input[type="checkbox"]:checked');
                const newVisibleTargets = Array.from(checkboxes).map((cb) => cb.value);
                await GM_setValue(config.KEYS.VISIBLE_TARGETS, newVisibleTargets);
            },
            onClickOutside(event) {
                const { container } = AITabSync.elements;
                if (container && !container.contains(event.target) && container.classList.contains('expanded')) {
                    AITabSync.ui.togglePanelVisibility();
                }
            },
            onChipMouseOver(event) {
                if (!event.target.matches('.ai-sync-chip')) return;
                const { state, config, elements } = AITabSync;
                const chip = event.target;
                const siteId = chip.dataset.siteId;

                let tooltipText = '';
                if (state.selectedTargets.has(siteId)) tooltipText = '已选中 (点击取消)';
                else if (chip.classList.contains('online')) tooltipText = '待发送 (点击选中)';
                else tooltipText = '点击启动';

                state.tooltipTimeoutId = setTimeout(() => {
                    elements.tooltip.textContent = tooltipText;
                    const chipRect = chip.getBoundingClientRect();
                    elements.tooltip.style.left = `${chipRect.left + chipRect.width / 2}px`;
                    elements.tooltip.style.top = `${chipRect.top - 10}px`;
                    elements.tooltip.style.display = 'block';
                }, config.TIMINGS.TOOLTIP_DELAY);
            },
            onChipMouseOut(event) {
                if (!event.target.matches('.ai-sync-chip')) return;
                clearTimeout(AITabSync.state.tooltipTimeoutId);
                AITabSync.elements.tooltip.style.display = 'none';
            },
            async onToggleLogging() {
                const { state, ui } = AITabSync;
                state.isLoggingEnabled = !state.isLoggingEnabled;
                await GM_setValue(AITabSync.config.KEYS.LOGGING_ENABLED, state.isLoggingEnabled);
                alert(`[AI Sync] 调试日志 ${state.isLoggingEnabled ? '已开启' : '已关闭'}。`);
                ui.updateMenuCommand();
            },
        },

        // ===================================================================================
        // --- 7. Lifecycle & Background Tasks ---
        // ===================================================================================
        lifecycle: {
            ensureWindowName() {
                const { thisSite } = AITabSync.state;
                if (!thisSite) return;
                const expectedName = `ai_sync_window_for_${thisSite.id}`;
                if (window.name !== expectedName) window.name = expectedName;
            },
            deployHistoryInterceptor() {
                const { thisSite } = AITabSync.state;
                if (!thisSite) return;
                const originalPushState = history.pushState;
                const originalReplaceState = history.replaceState;
                let lastUrl = location.href;
                const handleUrlChange = () => {
                    setTimeout(() => {
                        if (location.href !== lastUrl) {
                            lastUrl = location.href;
                            this.ensureWindowName();
                            this.registerTabAsActive();
                        }
                    }, 100);
                };
                history.pushState = function (...args) {
                    originalPushState.apply(this, args);
                    handleUrlChange();
                };
                history.replaceState = function (...args) {
                    originalReplaceState.apply(this, args);
                    handleUrlChange();
                };
                window.addEventListener('popstate', handleUrlChange);
            },
            async registerTabAsActive() {
                const { thisSite } = AITabSync.state;
                if (!thisSite) return;
                try {
                    const activeTabs = JSON.parse(await GM_getValue(AITabSync.config.KEYS.ACTIVE_TABS, '{}'));
                    activeTabs[thisSite.id] = { url: window.location.href, timestamp: Date.now() };
                    await GM_setValue(AITabSync.config.KEYS.ACTIVE_TABS, JSON.stringify(activeTabs));
                } catch (e) {
                    AITabSync.utils.log('心跳注册失败:', e);
                }
            },
            async unregisterTabAsInactive() {
                const { thisSite } = AITabSync.state;
                if (!thisSite) return;
                // This is a fire-and-forget call, not guaranteed to complete on all browsers.
                try {
                    const key = AITabSync.config.KEYS.ACTIVE_TABS;
                    const json = await GM_getValue(key, '{}');
                    const activeTabs = JSON.parse(json);
                    if (activeTabs[thisSite.id]) {
                        delete activeTabs[thisSite.id];
                        await GM_setValue(key, JSON.stringify(activeTabs));
                        AITabSync.utils.log(`Tab un-registered due to unload.`);
                    }
                } catch (e) {
                    // This might happen if the page is torn down too quickly.
                }
            },
            async cleanupStaleTabs() {
                try {
                    const activeTabs = JSON.parse(await GM_getValue(AITabSync.config.KEYS.ACTIVE_TABS, '{}'));
                    const now = Date.now();
                    let hasChanged = false;
                    for (const siteId in activeTabs) {
                        if (Object.prototype.hasOwnProperty.call(activeTabs, siteId)) {
                            const tabInfo = activeTabs[siteId];
                            const isStale =
                                typeof tabInfo !== 'object' || tabInfo === null || typeof tabInfo.timestamp !== 'number' || now - tabInfo.timestamp > AITabSync.config.TIMINGS.STALE_THRESHOLD;
                            if (isStale) {
                                delete activeTabs[siteId];
                                hasChanged = true;
                            }
                        }
                    }
                    if (hasChanged) await GM_setValue(AITabSync.config.KEYS.ACTIVE_TABS, JSON.stringify(activeTabs));
                } catch (e) {
                    AITabSync.utils.log('清理陈旧标签页时出错:', e);
                }
            },
        },

        // ===================================================================================
        // --- 8. Communication Module ---
        // ===================================================================================
        comms: {
            deployNetworkInterceptor() {
                const { thisSite } = AITabSync.state;
                if (!thisSite?.queryExtractor) {
                    AITabSync.utils.log(`[${thisSite.name}] 不支持作为发送方,跳过网络拦截器部署。`);
                    return;
                }

                const { send } = unsafeWindow.XMLHttpRequest.prototype;
                if (!send._isHooked) {
                    const { open } = unsafeWindow.XMLHttpRequest.prototype;
                    unsafeWindow.XMLHttpRequest.prototype.open = function (method, url, ...args) {
                        this._url = url;
                        return open.apply(this, [method, url, ...args]);
                    };
                    unsafeWindow.XMLHttpRequest.prototype.send = function (body) {
                        const site = AITabSync.utils.getCurrentSiteInfo();
                        if (site?.apiPaths.some((p) => this._url?.includes(p)) && body && typeof body === 'string' && !AITabSync.state.isSubmitting) {
                            const query = site.queryExtractor(body);
                            if (query) AITabSync.comms.handleQueryFound(query, site);
                        }
                        return send.apply(this, arguments);
                    };
                    unsafeWindow.XMLHttpRequest.prototype.send._isHooked = true;
                }

                const { fetch } = unsafeWindow;
                if (!fetch._isHooked) {
                    unsafeWindow.fetch = async function (...args) {
                        const site = AITabSync.utils.getCurrentSiteInfo();
                        const url = args[0] instanceof Request ? args[0].url : args[0];
                        const config = args[1] || {};
                        if (site?.apiPaths.some((p) => url.includes(p)) && (config.method || 'GET').toUpperCase() === 'POST' && !AITabSync.state.isSubmitting) {
                            try {
                                // Ensure body is not FormData or Blob before decoding to avoid errors
                                if (config.body && typeof config.body !== 'string' && !(config.body instanceof FormData) && !(config.body instanceof Blob)) {
                                    const body = config.body instanceof Uint8Array ? new TextDecoder().decode(config.body) : config.body;
                                    if (typeof body === 'string') {
                                        const query = site.queryExtractor(body);
                                        if (query) AITabSync.comms.handleQueryFound(query, site);
                                    }
                                } else if (typeof config.body === 'string') {
                                    const query = site.queryExtractor(config.body);
                                    if (query) AITabSync.comms.handleQueryFound(query, site);
                                }
                            } catch (e) {
                                AITabSync.utils.log(`[${site.name}] 处理 Fetch Body 出错:`, e);
                            }
                        }
                        return fetch.apply(this, args);
                    };
                    unsafeWindow.fetch._isHooked = true;
                }
            },
            async handleQueryFound(query, sourceSite) {
                const { utils, state, config, elements } = AITabSync;
                utils.log(`成功拦截到问题: "${query}"`);
                const targets = Array.from(state.selectedTargets);
                if (targets.length === 0) {
                    utils.log('未选择任何同步目标,操作取消。');
                    return;
                }
                utils.log(`准备同步到: ${targets.join(', ')}`);

                if (elements.fab) {
                    elements.fab.classList.add('sending');
                    setTimeout(() => elements.fab.classList.remove('sending'), 2000);
                }

                const message = { query, timestamp: Date.now(), sourceId: sourceSite.id, targetIds: targets };
                await GM_setValue(config.KEYS.SHARED_QUERY, JSON.stringify(message));
                utils.log('广播完成。');
            },
            async processSharedQuery(value) {
                const { state, utils, config } = AITabSync;
                if (state.isProcessingTask) return;
                state.isProcessingTask = true;
                try {
                    if (!value) return;
                    const data = JSON.parse(value);
                    if (!data.targetIds?.includes(state.thisSite.id) || Date.now() - data.timestamp >= config.TIMINGS.FRESHNESS_THRESHOLD) return;

                    const remainingTargets = data.targetIds.filter((id) => id !== state.thisSite.id);
                    if (remainingTargets.length > 0) {
                        await GM_setValue(config.KEYS.SHARED_QUERY, JSON.stringify({ ...data, targetIds: remainingTargets }));
                    } else {
                        await GM_deleteValue(config.KEYS.SHARED_QUERY);
                    }
                    await this.processSubmission(state.thisSite, data.query);
                } catch (e) {
                    utils.log('处理共享任务失败:', e);
                    await GM_deleteValue(config.KEYS.SHARED_QUERY);
                } finally {
                    state.isProcessingTask = false;
                }
            },
            async processSubmission(site, query) {
                const { utils, config, state } = AITabSync;
                const inputArea = await utils.waitFor(
                    () => site.inputSelectors.map((s) => utils.deepQuerySelector(s)).find(Boolean),
                    config.TIMINGS.SUBMIT_TIMEOUT,
                    '输入框',
                );

                utils.simulateInput(inputArea, query);
                await new Promise((resolve) => setTimeout(resolve, config.TIMINGS.HUMAN_LIKE_DELAY));

                try {
                    state.isSubmitting = true;

                    const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
                    const eventType = site.id === 'QWEN' ? 'keypress' : 'keydown';
                    const useModifierKey = site.id === 'AI_STUDIO'; // Check if the site requires a modifier key

                    const enterEvent = new KeyboardEvent(eventType, {
                        key: 'Enter',
                        code: 'Enter',
                        keyCode: 13,
                        which: 13,
                        bubbles: true,
                        cancelable: true,
                        ctrlKey: useModifierKey && !isMac, // Use Ctrl if it's the target site and not a Mac
                        metaKey: useModifierKey && isMac, // Use Cmd if it's the target site and is a Mac
                        shiftKey: false,
                    });
                    inputArea.dispatchEvent(enterEvent);
                    utils.log(`[${site.name}] 成功分派 ${eventType} 事件。`);
                    setTimeout(() => (state.isSubmitting = false), 2000);
                } catch (error) {
                    utils.log(`提交过程中发生错误 (Enter键模拟): ${error.message}`);
                    state.isSubmitting = false;
                }
            },
            async initReceiver() {
                const { utils, config } = AITabSync;
                try {
                    await utils.waitFor(
                        () => AITabSync.state.thisSite.inputSelectors.map((s) => utils.deepQuerySelector(s)).find(Boolean),
                        config.TIMINGS.SUBMIT_TIMEOUT,
                        'UI就绪 (输入框出现)',
                    );
                    const value = await GM_getValue(config.KEYS.SHARED_QUERY);
                    if (value) this.processSharedQuery(value);
                } catch (error) {
                    utils.log('等待UI就绪超时,可能无法处理初始同步任务。');
                }

                GM_addValueChangeListener(config.KEYS.SHARED_QUERY, (name, old_value, new_value, remote) => {
                    if (remote && new_value) {
                        try {
                            if (JSON.parse(new_value).sourceId !== AITabSync.state.thisSite.id) {
                                this.processSharedQuery(new_value);
                            }
                        } catch (e) {
                            utils.log('解析收到的广播任务时出错:', e);
                        }
                    }
                });
            },
        },

        // ===================================================================================
        // --- 9. Main Application Logic ---
        // ===================================================================================
        main: {
            async loadInitialState() {
                const { state, config } = AITabSync;
                state.isLoggingEnabled = await GM_getValue(config.KEYS.LOGGING_ENABLED, false);
                state.visibleTargets = await GM_getValue(config.KEYS.VISIBLE_TARGETS, null);
                if (state.visibleTargets === null) {
                    state.visibleTargets = [...config.DISPLAY_ORDER];
                    await GM_setValue(config.KEYS.VISIBLE_TARGETS, state.visibleTargets);
                }
            },
            registerGMListeners() {
                const { config, ui, state, utils } = AITabSync;
                GM_addValueChangeListener(config.KEYS.LOGGING_ENABLED, (name, ov, nv) => {
                    state.isLoggingEnabled = nv;
                    ui.updateMenuCommand();
                });
                GM_addValueChangeListener(config.KEYS.ACTIVE_TABS, (name, ov, nv, remote) => {
                    if (remote) ui.updatePanelState();
                });
                GM_addValueChangeListener(config.KEYS.VISIBLE_TARGETS, (name, ov, nv) => {
                    const oldTargets = ov || config.DISPLAY_ORDER;
                    const newTargets = nv || [];
                    state.visibleTargets = newTargets;

                    const hiddenTargets = oldTargets.filter((id) => !newTargets.includes(id));
                    hiddenTargets.forEach((id) => {
                        if (state.selectedTargets.has(id)) {
                            state.selectedTargets.delete(id);
                            utils.log(`模型 "${config.SITES[id]?.name || id}" 已从菜单隐藏,同步选择已取消。`);
                        }
                    });
                    ui.rebuildChipsUI();
                });
            },
            startBackgroundTasks() {
                const { lifecycle, config, ui } = AITabSync;
                lifecycle.registerTabAsActive();
                lifecycle.cleanupStaleTabs();
                setInterval(lifecycle.registerTabAsActive, config.TIMINGS.HEARTBEAT_INTERVAL);
                setInterval(lifecycle.cleanupStaleTabs, config.TIMINGS.CLEANUP_INTERVAL);
                setInterval(() => {
                    if (document.body && !document.getElementById('ai-sync-container')) {
                        ui.createMainPanel();
                    }
                }, 2000);
            },
            initEarly() {
                AITabSync.state.thisSite = AITabSync.utils.getCurrentSiteInfo();
                if (!AITabSync.state.thisSite) return false;
                AITabSync.comms.deployNetworkInterceptor();
                return true;
            },
            async initDOMReady() {
                const { state, ui, utils, lifecycle, comms } = AITabSync;
                if (!state.thisSite) return;

                try {
                    await utils.waitFor(() => document.body, 10000, 'document.body to be ready');

                    await this.loadInitialState();
                    utils.log(`脚本 v${AITabSync.config.SCRIPT_VERSION} 在 ${state.thisSite.name} 启动。`);

                    ui.injectStyle();
                    ui.createMainPanel();
                    ui.createSettingsModal();
                    ui.createTooltip();
                    AITabSync.events.register();
                    this.registerGMListeners();
                    this.startBackgroundTasks();

                    lifecycle.ensureWindowName();
                    lifecycle.deployHistoryInterceptor();
                    comms.initReceiver();

                    // Immediately send a heartbeat when the tab becomes visible.
                    // This counters browser throttling of setInterval in background tabs.
                    document.addEventListener('visibilitychange', () => {
                        if (document.visibilityState === 'visible') {
                            AITabSync.utils.log('Tab became visible, sending immediate heartbeat.');
                            lifecycle.registerTabAsActive();
                        }
                    });

                    // Attempt to unregister the tab when the user closes it.
                    // This is a "best-effort" feature and may not always succeed.
                    window.addEventListener('beforeunload', lifecycle.unregisterTabAsInactive);

                    if (window.self === window.top) ui.updateMenuCommand();
                } catch (error) {
                    utils.log('初始化过程中发生严重错误:', error);
                }
            },
        },
    };

    // --- Script Execution ---
    if (AITabSync.main.initEarly()) {
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => AITabSync.main.initDOMReady());
        } else {
            AITabSync.main.initDOMReady();
        }
    }
})();