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

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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      2.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 3 Pro & User
// @match        https://doubao.com/chat/*
// @match        https://www.doubao.com/chat/*
// @match        https://chat.qwen.ai/*
// @match        https://qianwen.com/*
// @match        https://www.qianwen.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==

/*
 * =================================================================================================
 * --- v2.0 功能简介与使用说明 (基于 v1.2.16 稳定版内核) ---
 *
 * 【AI 对话助手(一键同步多模型)】
 *
 * 核心目标:拒绝复制粘贴,实现“一处提问,多处同步”。
 *
 * 核心亮点:
 * 1. 光速同步: 在一个页面输入,所有选中的模型即刻响应。
 * 2. 智能双向同步: 不分“主次”,任何聊天窗口都可作为控制台。开启“同步选择状态”后体验更佳。
 * 3. 原生体验: 使用网站原生输入框,保留文件上传等所有富文本功能。
 * 4. 高度可定制: 提供设置面板,可自定义常用模型、动画效果和同步逻辑。
 * 5. 精美 UI: 悬浮球拥有谷歌 AI 同款动态彩虹光环,视觉反馈生动有趣。
 *
 * 简易使用说明:
 * 1. 打开面板: 点击页面右下角的【悬浮球】图标。
 * 2. 选择目标:
 *    - 在面板中点击模型名称(如“Kimi”)即可选中/取消。
 *    - 点击【全选】图标可一键选中/取消所有可见模型。
 * 3. 高级设置: 点击面板标题旁的【齿轮】图标,可进行个性化设置。
 * 4. 发送问题: 在当前页面的输入框正常提问并发送,脚本将自动分发给所有“已选中”的目标。
 *
 * 开发者提示:
 * - 想要添加对新 AI 网站的支持吗?过程很简单,只需在代码的 `config.SITES` 对象中,
 *   仿照现有格式添加一个新的网站配置即可。欢迎大家贡献代码!
 *
 * 维护说明:
 * - 本脚本为个人兴趣项目,随缘更新,主要看心情和灵感。感谢理解!
 * =================================================================================================
 */

(function () {
    'use strict';

    const AITabSync = {
        // --- 1. State Management ---
        state: {
            thisSite: null,
            visibleTargets: [],
            selectedTargets: new Set(),
            isLoggingEnabled: false,
            isSubmitting: false,
            isProcessingTask: false,
            menuCommandId: null,
            tooltipTimeoutId: null,
            animationStyle: 'spin',
            isSelectionSynced: true,
        },

        // --- 2. Configuration ---
        config: {
            SCRIPT_VERSION: '2.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',
                ANIMATION_STYLE: 'multi_sync_animation_style_v1.0',
                SELECTION_SYNC_ENABLED: 'multi_sync_selection_sync_v1.0',
                SHARED_SELECTION: 'multi_sync_shared_selection_v1.0',
            },
            TIMINGS: {
                HEARTBEAT_INTERVAL: 5000,
                STALE_THRESHOLD: 15000,
                CLEANUP_INTERVAL: 10000,
                SUBMIT_TIMEOUT: 20000,
                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 first = body.indexOf('{'),
                                last = body.lastIndexOf('}');
                            if (first === -1 || last < first) return '';
                            return JSON.parse(body.substring(first, last + 1))?.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 p = new URLSearchParams(body);
                            const f = p.get('f.req');
                            if (!f) return '';
                            return JSON.parse(JSON.parse(f)?.[1])?.[0]?.[0] || '';
                        } 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: ['/chat/completion', '/samantha/chat/completion'],
                    inputSelectors: ['textarea[data-testid="chat_input_input"]', 'textarea'],
                    queryExtractor: (body) => {
                        try {
                            const json = JSON.parse(body);
                            const msgs = json.messages;
                            if (!msgs || msgs.length === 0) return '';
                            const lastMsg = msgs[msgs.length - 1];
                            if (lastMsg.content_block && Array.isArray(lastMsg.content_block)) {
                                for (const block of lastMsg.content_block) {
                                    if (block.content && block.content.text_block && block.content.text_block.text) {
                                        return block.content.text_block.text;
                                    }
                                }
                            }
                            if (lastMsg.content) {
                                if (typeof lastMsg.content === 'string') {
                                    try {
                                        const inner = JSON.parse(lastMsg.content);
                                        if (inner.text) return inner.text;
                                    } catch (e) {}
                                    return lastMsg.content;
                                }
                            }
                            return '';
                        } 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: 'qianwen.com',
                    url: 'https://www.qianwen.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 j = JSON.parse(body);
                            const m = j?.[1];
                            if (Array.isArray(m)) {
                                for (let i = m.length - 1; i >= 0; i--) {
                                    if (Array.isArray(m[i]) && m[i][1] === 'user') return m[i][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 {
                            return JSON.parse(body)?.messages?.slice(-1)?.[0]?.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) {}
                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;
                if (siteId === 'GROK') {
                    const dt = new DataTransfer();
                    dt.setData('text/plain', value);
                    element.dispatchEvent(new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true }));
                } else if (siteId === 'KIMI') {
                    element.dispatchEvent(new InputEvent('beforeinput', { bubbles: true, cancelable: true, composed: true, inputType: 'insertText', data: value }));
                } else if (element.isContentEditable || element.contentEditable === 'true') {
                    if (siteId === 'CLAUDE') {
                        element.innerHTML = '';
                        const p = document.createElement('p');
                        p.textContent = value;
                        element.appendChild(p);
                    } else {
                        element.textContent = value;
                    }
                    element.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
                } else if (element.tagName === 'TEXTAREA') {
                    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 (CSP Safe) ---
        ui: {
            injectStyle() {
                GM_addStyle(`
                    :root {
                        --ai-g-blue: #1a73e8;
                        --ai-g-red: #ea4335;
                        --ai-g-yellow: #fbbc04;
                        --ai-g-green: #34a853;
                    }
                    @keyframes ae-fg {
                        0% { opacity: 0; }
                        30% { opacity: 1; }
                        50% { opacity: 1; }
                        100% { opacity: 0; }
                    }
                    @keyframes ae-rg {
                        from { transform: translate(-50%, -50%) rotate(0deg); }
                        to { transform: translate(-50%, -50%) rotate(180deg); }
                    }
                    @keyframes ai-breathe {
                        0%, 100% { opacity: 0.35; }
                        50% { opacity: 0.6; }
                    }
                    @keyframes ai-spin-sending {
                        from { transform: translate(-50%, -50%) rotate(0deg); }
                        to { transform: translate(-50%, -50%) rotate(360deg); }
                    }
                    @property --ai-border-angle {
                        syntax: '<angle>';
                        initial-value: 0deg;
                        inherits: false;
                    }
                    @keyframes ai-border-rotate-once {
                        0% { --ai-border-angle: 0deg; opacity: 1; }
                        85% { opacity: 1; }
                        100% { --ai-border-angle: 360deg; opacity: 0; }
                    }
                    @keyframes ae-zoom-in {
                        from { transform: scale(0.92); opacity: 0; }
                        to { transform: scale(1); opacity: 1; }
                    }
                    @keyframes ai-spin-slow-infinite {
                        from { transform: translate(-50%, -50%) rotate(0deg); }
                        to { transform: translate(-50%, -50%) rotate(360deg); }
                    }
                    #ai-sync-container {
                        position: fixed;
                        bottom: 20px;
                        right: 20px;
                        z-index: 99998;
                        display: flex;
                        align-items: flex-end;
                        gap: 12px;
                        pointer-events: none;
                        font-family: sans-serif;
                    }
                    #ai-sync-container.expanded {
                        pointer-events: auto;
                    }
                    #ai-sync-toggle-fab {
                        position: relative;
                        width: 44px;
                        height: 44px;
                        border-radius: 50%;
                        background: transparent;
                        border: none;
                        cursor: pointer;
                        padding: 0;
                        pointer-events: auto;
                        box-shadow: 0 2px 6px rgba(0,0,0,0.15);
                        transition: transform 0.2s, box-shadow 0.2s;
                        overflow: visible;
                        --mouse-angle: 180deg;
                    }
                    #ai-sync-toggle-fab:hover {
                        box-shadow: 0 4px 12px rgba(0,0,0,0.25);
                        transform: scale(1.08);
                    }
                    .ai-visual-clipper {
                        position: absolute;
                        inset: 0;
                        border-radius: 50%;
                        overflow: hidden;
                        z-index: 0;
                    }
                    .ai-gradient-layer {
                        position: absolute;
                        top: 50%;
                        left: 50%;
                        width: 200%;
                        height: 200%;
                        background: conic-gradient(#3186ff00 0deg, #34a853 43deg, #ffd314 65deg, #ff4641 105deg, #3186ff 144deg, #3186ff 180deg, #3186ff00 324deg);
                        transform: translate(-50%, -50%) rotate(var(--mouse-angle));
                        opacity: 0;
                        pointer-events: none;
                        transition: opacity 400ms linear, transform 0.1s linear;
                    }
                    .ai-layer-blur {
                        filter: blur(2px);
                        opacity: 0;
                    }
                    .ai-layer-sharp {
                        filter: blur(0px);
                        opacity: 0;
                    }
                    #ai-sync-toggle-fab.intro-playing.animation-aurora .ai-gradient-layer {
                        animation: ae-fg 2000ms linear backwards, ae-rg 2000ms cubic-bezier(0.20, 0.00, 0.00, 1.00) backwards;
                    }
                    #ai-sync-toggle-fab.intro-playing.animation-spin .ai-gradient-layer {
                        opacity: 0.5;
                        animation: ai-spin-slow-infinite 8s linear infinite;
                    }
                    #ai-sync-toggle-fab:not(.intro-playing):hover .ai-layer-blur {
                        opacity: 0.35;
                        animation: ai-breathe 3s infinite alternate;
                    }
                    #ai-sync-toggle-fab:not(.intro-playing):hover .ai-layer-sharp {
                        opacity: 1;
                    }
                    #ai-sync-toggle-fab.sending .ai-gradient-layer,
                    #ai-sync-toggle-fab.sending:hover .ai-gradient-layer {
                        opacity: 1 !important;
                        animation: ai-spin-sending 1s linear infinite !important;
                        transition: opacity 0.2s;
                    }
                    .ai-inner-mask {
                        position: absolute;
                        inset: 2px;
                        background: #fff;
                        border-radius: 50%;
                        z-index: 1;
                        transition: filter 1s cubic-bezier(0,0,0,1);
                    }
                    .ai-inner-mask::after {
                        content: '';
                        position: absolute;
                        inset: -1px;
                        border-radius: 50%;
                        border: 1px solid #dadce0;
                        transition: opacity 0.2s;
                    }
                    #ai-sync-toggle-fab:hover .ai-inner-mask::after,
                    #ai-sync-toggle-fab.sending .ai-inner-mask::after {
                        opacity: 0;
                    }
                    .ai-icon-wrapper {
                        position: absolute;
                        top: 50%;
                        left: 50%;
                        transform: translate(-50%, -50%);
                        z-index: 2;
                        display: flex;
                        align-items: center;
                        justify-content: center;
                        width: 24px;
                        height: 24px;
                    }
                    .ai-icon-wrapper svg {
                        width: 100%;
                        height: 100%;
                        color: #5f6368;
                        transition: color 0.3s;
                    }
                    #ai-sync-toggle-fab:hover svg,
                    #ai-sync-toggle-fab.sending svg {
                        color: var(--ai-g-blue);
                    }
                    .ai-sync-fab-badge {
                        position: absolute;
                        top: -4px;
                        right: -4px;
                        background: linear-gradient(135deg, #00c6ff, #0072ff);
                        color: white;
                        border-radius: 10px;
                        padding: 0 5px;
                        font-size: 10px;
                        font-weight: bold;
                        min-width: 16px;
                        line-height: 16px;
                        text-align: center;
                        z-index: 10;
                        border: 2px solid #fff;
                        box-shadow: 0 2px 4px rgba(0,0,0,0.25);
                    }
                    #ai-sync-content-panel {
                        display: inline-block;
                        background: rgba(255,255,255,0.98);
                        backdrop-filter: blur(12px);
                        border: 1px solid #dadce0;
                        border-radius: 16px;
                        padding: 12px 16px;
                        box-shadow: 0 8px 24px rgba(0,0,0,0.12);
                        opacity: 0;
                        transform: translateX(15px);
                        visibility: hidden;
                        margin-bottom: 4px;
                        transition: all 0.2s;
                        position: relative;
                        z-index: 1;
                    }
                    #ai-sync-container.expanded #ai-sync-content-panel {
                        opacity: 1;
                        transform: translateX(0);
                        visibility: visible;
                        border-color: transparent;
                    }
                    #ai-sync-container.expanded #ai-sync-content-panel::before {
                        content: "";
                        position: absolute;
                        inset: 0;
                        border-radius: 16px;
                        padding: 2px;
                        background: conic-gradient(from var(--ai-border-angle), transparent 0%, transparent 60%, var(--ai-g-blue) 80%, var(--ai-g-red) 86%, var(--ai-g-yellow) 92%, var(--ai-g-green) 98%, transparent 100%);
                        -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
                        -webkit-mask-composite: xor;
                        mask-composite: exclude;
                        animation: ai-border-rotate-once 0.8s linear forwards;
                        pointer-events: none;
                        z-index: 10;
                    }
                    #ai-sync-panel-title-wrapper {
                        display: flex;
                        justify-content: space-between;
                        align-items: center;
                        margin-bottom: 12px;
                        padding-bottom: 8px;
                        border-bottom: 1px solid #f1f3f4;
                    }
                    #ai-sync-panel-title {
                        font-weight: 600;
                        font-size: 15px;
                        color: #202124;
                        flex-grow: 1;
                    }
                    #ai-sync-select-all-btn,
                    #ai-sync-settings-btn {
                        all: unset;
                        cursor: pointer;
                        color: #5f6368;
                        padding: 4px;
                        border-radius: 50%;
                        transition: background 0.2s, color 0.2s;
                        display: flex;
                        flex-shrink: 0;
                    }
                    #ai-sync-select-all-btn:hover,
                    #ai-sync-settings-btn:hover {
                        color: #202124;
                        background-color: #f1f3f4;
                    }
                    #ai-sync-chips-container {
                        display: flex;
                        flex-wrap: wrap;
                        gap: 8px;
                        max-width: 260px;
                    }
                    #ai-sync-chips-container > i {
                        flex-grow: 1;
                    }
                    .ai-sync-chip {
                        all: unset;
                        box-sizing: border-box;
                        cursor: pointer;
                        padding: 6px 12px;
                        border-radius: 8px;
                        font-size: 13px;
                        font-weight: 500;
                        border: 1px solid #dadce0;
                        color: #5f6368;
                        background-color: #fff;
                        transition: all 0.2s ease;
                        flex-grow: 1;
                        text-align: center;
                    }
                    .ai-sync-chip:hover {
                        background-color: #f8f9fa;
                        border-color: #dadce0;
                        color: #202124;
                    }
                    .ai-sync-chip.online {
                        border-color: var(--ai-g-blue);
                        color: var(--ai-g-blue);
                        background: #f1f8ff;
                    }
                    .ai-sync-chip.selected {
                        background-color: var(--ai-g-blue);
                        border-color: var(--ai-g-blue);
                        color: white;
                        box-shadow: 0 1px 2px rgba(26,115,232,0.3);
                    }
                    #ai-sync-settings-overlay {
                        display: none;
                        position: fixed;
                        top: 0;
                        left: 0;
                        width: 100vw;
                        height: 100vh;
                        background: rgba(0,0,0,0.5);
                        backdrop-filter: blur(4px);
                        z-index: 99999;
                        justify-content: center;
                        align-items: center;
                    }
                    #ai-sync-settings-panel {
                        background: #fff;
                        border-radius: 16px;
                        width: 340px;
                        display: flex;
                        flex-direction: column;
                        box-shadow: 0 12px 40px rgba(0,0,0,0.2);
                        overflow: hidden;
                        animation: ae-zoom-in 0.2s ease-out;
                    }
                    .ai-sync-settings-header {
                        padding: 16px 24px;
                        border-bottom: 1px solid #f1f3f4;
                        background: #fff;
                    }
                    .ai-sync-settings-title {
                        margin: 0;
                        font-size: 18px;
                        font-weight: 600;
                        color: #202124;
                    }
                    .ai-sync-settings-list {
                        display: grid;
                        grid-template-columns: 1fr 1fr;
                        gap: 10px;
                        padding: 20px;
                        background: #fff;
                        max-height: 400px;
                        overflow-y: auto;
                    }
                    .ai-sync-settings-item label {
                        display: flex;
                        align-items: center;
                        cursor: pointer;
                        font-size: 14px;
                        color: #3c4043;
                        padding: 10px 12px;
                        border-radius: 8px;
                        background-color: #f8f9fa;
                        transition: background 0.2s;
                        user-select: none;
                    }
                    .ai-sync-settings-item label:hover {
                        background-color: #e8f0fe;
                        color: #1967d2;
                    }
                    .ai-sync-settings-item input[type="checkbox"] {
                        appearance: none;
                        -webkit-appearance: none;
                        width: 20px;
                        height: 20px;
                        border: 2px solid #5f6368;
                        border-radius: 4px;
                        margin-right: 12px;
                        position: relative;
                        flex-shrink: 0;
                        transition: all 0.2s;
                        background: #fff;
                        cursor: pointer;
                    }
                    .ai-sync-settings-item input[type="checkbox"]:checked {
                        background-color: var(--ai-g-blue);
                        border-color: var(--ai-g-blue);
                    }
                    .ai-sync-settings-item input[type="checkbox"]:checked::after {
                        content: '';
                        position: absolute;
                        left: 6px;
                        top: 2px;
                        width: 5px;
                        height: 10px;
                        border: solid white;
                        border-width: 0 2px 2px 0;
                        transform: rotate(45deg);
                        display: block;
                    }
                    .ai-sync-settings-divider {
                        border: none;
                        height: 1px;
                        background-color: #f1f3f4;
                        margin: 0 20px;
                    }
                    .ai-sync-settings-uigroup {
                        padding: 10px 20px;
                        background: #fff;
                    }
                    .ai-sync-settings-toggle {
                        display: flex;
                        justify-content: space-between;
                        align-items: center;
                    }
                    .ai-sync-settings-toggle > span {
                        font-size: 14px;
                        color: #3c4043;
                        cursor: default;
                    }
                    .toggle-switch {
                        position: relative;
                        display: inline-block;
                        width: 38px;
                        height: 22px;
                    }
                    .toggle-switch input {
                        opacity: 0;
                        width: 0;
                        height: 0;
                    }
                    .toggle-switch-track {
                        position: absolute;
                        cursor: pointer;
                        top: 0;
                        left: 0;
                        right: 0;
                        bottom: 0;
                        background-color: #ccc;
                        transition: .4s;
                        border-radius: 22px;
                    }
                    .toggle-switch-thumb {
                        position: absolute;
                        content: '';
                        height: 18px;
                        width: 18px;
                        left: 2px;
                        bottom: 2px;
                        background-color: white;
                        transition: .4s;
                        border-radius: 50%;
                    }
                    input:checked + .toggle-switch-track {
                        background-color: var(--ai-g-blue);
                    }
                    input:checked + .toggle-switch-track .toggle-switch-thumb {
                        transform: translateX(16px);
                    }
                    #ai-sync-custom-tooltip {
                        display: none;
                        position: fixed;
                        background: rgba(32,33,36,0.9);
                        color: #fff;
                        padding: 6px 10px;
                        border-radius: 4px;
                        font-size: 12px;
                        font-weight: 500;
                        z-index: 100000;
                        pointer-events: none;
                        transform: translate(-50%, -100%);
                        margin-top: -8px;
                        white-space: nowrap;
                        box-shadow: 0 2px 6px rgba(0,0,0,0.15);
                    }
                `);
            },
            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 selectAllBtn = document.createElement('button');
                selectAllBtn.id = 'ai-sync-select-all-btn';
                selectAllBtn.title = '全选';
                const saSvg = document.createElementNS(svgNS, 'svg');
                saSvg.setAttribute('width', '20');
                saSvg.setAttribute('height', '20');
                saSvg.setAttribute('viewBox', '0 0 24 24');
                saSvg.setAttribute('fill', 'currentColor');
                const saPath = document.createElementNS(svgNS, 'path');
                saPath.setAttribute('d', 'M3 14h4v-4H3v4zm0 5h4v-4H3v4zM3 9h4V5H3v4zm5-4v4h13V5H8zm0 5h13v-4H8v4zm0 5h13v-4H8v4z');
                saSvg.appendChild(saPath);
                selectAllBtn.appendChild(saSvg);
                titleWrapper.appendChild(selectAllBtn);
                const settingsBtn = document.createElement('button');
                settingsBtn.id = 'ai-sync-settings-btn';
                settingsBtn.title = '自定义常用模型';
                const setSvg = document.createElementNS(svgNS, 'svg');
                setSvg.setAttribute('width', '20');
                setSvg.setAttribute('height', '20');
                setSvg.setAttribute('viewBox', '0 0 24 24');
                setSvg.setAttribute('fill', 'currentColor');
                const setPath = document.createElementNS(svgNS, 'path');
                setPath.setAttribute('d', 'M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 0 0 .12-.61l-1.92-3.32a.488.488 0 0 0-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 0 0-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58a.49.49 0 0 0-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.58 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z');
                setSvg.appendChild(setPath);
                settingsBtn.appendChild(setSvg);
                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.classList.add('intro-playing');
                const clipper = document.createElement('div');
                clipper.className = 'ai-visual-clipper';
                const blurLayer = document.createElement('div');
                blurLayer.className = 'ai-gradient-layer ai-layer-blur';
                const sharpLayer = document.createElement('div');
                sharpLayer.className = 'ai-gradient-layer ai-layer-sharp';
                const innerMask = document.createElement('div');
                innerMask.className = 'ai-inner-mask';
                const iconWrapper = document.createElement('div');
                iconWrapper.className = 'ai-icon-wrapper';
                const fabSvg = document.createElementNS(svgNS, 'svg');
                fabSvg.setAttribute('viewBox', '0 0 24 24');
                fabSvg.setAttribute('fill', 'none');
                fabSvg.setAttribute('stroke', 'currentColor');
                fabSvg.setAttribute('stroke-width', '1.5');
                fabSvg.setAttribute('stroke-linecap', 'round');
                fabSvg.setAttribute('stroke-linejoin', 'round');
                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);
                iconWrapper.appendChild(fabSvg);
                clipper.appendChild(blurLayer);
                clipper.appendChild(sharpLayer);
                clipper.appendChild(innerMask);
                clipper.appendChild(iconWrapper);
                elements.fab.appendChild(clipper);
                const updateAngle = (e) => {
                    const rect = elements.fab.getBoundingClientRect();
                    const angle = Math.atan2(e.clientY - (rect.top + rect.height / 2), e.clientX - (rect.left + rect.width / 2)) * (180 / Math.PI) + 180;
                    elements.fab.style.setProperty('--mouse-angle', `${angle}deg`);
                };
                elements.fab.addEventListener('mousemove', updateAngle);
                elements.fab.addEventListener('mouseenter', (e) => {
                    elements.fab.classList.remove('intro-playing');
                    updateAngle(e);
                });
                elements.fab.addEventListener('mouseleave', () => {
                    if (AITabSync.state.animationStyle === 'spin') {
                        AITabSync.elements.fab.classList.add('intro-playing');
                    }
                });
                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, state } = AITabSync;
                const overlay = document.createElement('div');
                overlay.id = 'ai-sync-settings-overlay';
                const panel = document.createElement('div');
                panel.id = 'ai-sync-settings-panel';
                const header = document.createElement('div');
                header.className = 'ai-sync-settings-header';
                const title = document.createElement('h2');
                title.className = 'ai-sync-settings-title';
                title.textContent = '自定义常用模型';
                header.appendChild(title);
                panel.appendChild(header);
                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 = state.visibleTargets.includes(siteId);
                    label.appendChild(checkbox);
                    label.appendChild(document.createTextNode(site.name));
                    item.appendChild(label);
                    list.appendChild(item);
                });
                panel.appendChild(list);
                const divider = document.createElement('hr');
                divider.className = 'ai-sync-settings-divider';
                panel.appendChild(divider);
                const uiGroup = document.createElement('div');
                uiGroup.className = 'ai-sync-settings-uigroup';
                const toggleContainer = document.createElement('div');
                toggleContainer.className = 'ai-sync-settings-toggle';
                const animLabel = document.createElement('span');
                animLabel.textContent = '启用持续旋转动画';
                toggleContainer.appendChild(animLabel);
                const switchLabel = document.createElement('label');
                switchLabel.className = 'toggle-switch';
                const switchInput = document.createElement('input');
                switchInput.type = 'checkbox';
                switchInput.id = 'ai-sync-animation-toggle';
                switchInput.checked = state.animationStyle === 'spin';
                const switchTrack = document.createElement('div');
                switchTrack.className = 'toggle-switch-track';
                const switchThumb = document.createElement('div');
                switchThumb.className = 'toggle-switch-thumb';
                switchTrack.appendChild(switchThumb);
                switchLabel.appendChild(switchInput);
                switchLabel.appendChild(switchTrack);
                toggleContainer.appendChild(switchLabel);
                uiGroup.appendChild(toggleContainer);
                panel.appendChild(uiGroup);
                const uiGroupSync = document.createElement('div');
                uiGroupSync.className = 'ai-sync-settings-uigroup';
                const toggleContainerSync = document.createElement('div');
                toggleContainerSync.className = 'ai-sync-settings-toggle';
                const syncLabel = document.createElement('span');
                syncLabel.textContent = '同步选择状态';
                toggleContainerSync.appendChild(syncLabel);
                const switchLabelSync = document.createElement('label');
                switchLabelSync.className = 'toggle-switch';
                const switchInputSync = document.createElement('input');
                switchInputSync.type = 'checkbox';
                switchInputSync.id = 'ai-sync-selection-sync-toggle';
                switchInputSync.checked = state.isSelectionSynced;
                const switchTrackSync = document.createElement('div');
                switchTrackSync.className = 'toggle-switch-track';
                const switchThumbSync = document.createElement('div');
                switchThumbSync.className = 'toggle-switch-thumb';
                switchTrackSync.appendChild(switchThumbSync);
                switchLabelSync.appendChild(switchInputSync);
                switchLabelSync.appendChild(switchTrackSync);
                toggleContainerSync.appendChild(switchLabelSync);
                uiGroupSync.appendChild(toggleContainerSync);
                panel.appendChild(uiGroupSync);
                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';
                config.DISPLAY_ORDER.filter((id) => state.visibleTargets.includes(id) && id !== state.thisSite.id).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();
                    this.updateSelectAllButtonState();
                }
            },
            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 targetsWithoutSelf = Array.from(AITabSync.state.selectedTargets).filter(id => id !== AITabSync.state.thisSite.id);
                const count = targetsWithoutSelf.length;
                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();
                }
            },
            updateSelectAllButtonState() {
                const { state, config } = AITabSync;
                const btn = document.getElementById('ai-sync-select-all-btn');
                if (!btn) return;
                const visibleTargets = config.DISPLAY_ORDER.filter(id => state.visibleTargets.includes(id) && id !== state.thisSite.id);
                const allSelected = visibleTargets.length > 0 && visibleTargets.every(id => state.selectedTargets.has(id));
                btn.title = allSelected ? '全部取消' : '全选';
                btn.style.color = allSelected ? 'var(--ai-g-blue)' : '#5f6368';
            },
            togglePanelVisibility() {
                const { container } = AITabSync.elements;
                if (!container) return;
                container.classList.toggle('expanded');
                if (container.classList.contains('expanded')) {
                    this.updatePanelState();
                    this.updateSelectAllButtonState();
                    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-select-all-btn').addEventListener('click', this.onSelectAllClick);
                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.settingsModal.querySelector('#ai-sync-animation-toggle').addEventListener('change', this.onAnimationToggleChange);
                elements.settingsModal.querySelector('#ai-sync-selection-sync-toggle').addEventListener('change', this.onSelectionSyncToggleChange);
                elements.container.addEventListener('mouseover', this.onChipMouseOver, true);
                elements.container.addEventListener('mouseout', this.onChipMouseOut, true);
            },
            async onChipClick(event) {
                if (event.target.matches('.ai-sync-chip')) {
                    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);
                        if (state.isSelectionSynced && state.selectedTargets.size === 1 && state.selectedTargets.has(state.thisSite.id)) {
                            state.selectedTargets.clear();
                        }
                    } else {
                        state.selectedTargets.add(siteId);
                        if (state.isSelectionSynced) {
                            state.selectedTargets.add(state.thisSite.id);
                        }
                        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();
                    ui.updateSelectAllButtonState();
                    if (state.isSelectionSynced) {
                        await GM_setValue(config.KEYS.SHARED_SELECTION, JSON.stringify(Array.from(state.selectedTargets)));
                    }
                } else if (event.target === AITabSync.elements.container) {
                    AITabSync.ui.togglePanelVisibility();
                }
            },
            async onSelectAllClick() {
                const { config, state, ui, utils } = AITabSync;
                const visibleTargets = config.DISPLAY_ORDER.filter(id => state.visibleTargets.includes(id) && id !== state.thisSite.id);
                const allSelected = visibleTargets.length > 0 && visibleTargets.every(id => state.selectedTargets.has(id));
                if (allSelected) {
                    state.selectedTargets.clear();
                } else {
                    const activeTabs = JSON.parse(await GM_getValue(config.KEYS.ACTIVE_TABS, '{}'));
                    visibleTargets.forEach(siteId => {
                        state.selectedTargets.add(siteId);
                        const siteInfo = config.SITES[siteId];
                        if (!activeTabs[siteId] && siteInfo) {
                            utils.log(`(全选) 打开新标签页: ${siteId}`);
                            window.open(siteInfo.url, `ai_sync_window_for_${siteId}`);
                        }
                    });
                    if (state.isSelectionSynced) {
                        state.selectedTargets.add(state.thisSite.id);
                    }
                }
                ui.updatePanelState();
                ui.updateSelectAllButtonState();
                if (state.isSelectionSynced) {
                    await GM_setValue(config.KEYS.SHARED_SELECTION, JSON.stringify(Array.from(state.selectedTargets)));
                }
            },
            async onSettingsChange(event) {
                if (event.target.type !== 'checkbox') return;
                const { config, state, ui } = 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);
                state.visibleTargets = newVisibleTargets;
                const oldTargets = config.DISPLAY_ORDER;
                oldTargets.forEach((id) => {
                    if (!newVisibleTargets.includes(id) && state.selectedTargets.has(id)) state.selectedTargets.delete(id);
                });
                ui.rebuildChipsUI();
            },
            async onAnimationToggleChange(event) {
                const { state, config, elements, utils } = AITabSync;
                const isSpinEnabled = event.target.checked;
                const newStyle = isSpinEnabled ? 'spin' : 'aurora';
                if (state.animationStyle === newStyle) return;
                await GM_setValue(config.KEYS.ANIMATION_STYLE, newStyle);
                state.animationStyle = newStyle;
                utils.log(`动画样式已切换为: ${newStyle}`);
                elements.fab.classList.remove('animation-aurora', 'animation-spin', 'intro-playing');
                elements.fab.classList.add(isSpinEnabled ? 'animation-spin' : 'animation-aurora');
                if (isSpinEnabled) {
                    elements.fab.classList.add('intro-playing');
                }
            },
            async onSelectionSyncToggleChange(event) {
                const { state, config, utils } = AITabSync;
                const isEnabled = event.target.checked;
                state.isSelectionSynced = isEnabled;
                await GM_setValue(config.KEYS.SELECTION_SYNC_ENABLED, isEnabled);
                utils.log(`选择状态同步已 ${isEnabled ? '开启' : '关闭'}.`);
                if (!isEnabled) {
                    GM_deleteValue(config.KEYS.SHARED_SELECTION);
                }
            },
            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 = state.selectedTargets.has(siteId) ? '已选中 (点击取消)' : (chip.classList.contains('online') ? '待发送 (点击选中)' : '点击启动');
                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}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;
                try {
                    const key = AITabSync.config.KEYS.ACTIVE_TABS;
                    const activeTabs = JSON.parse(await GM_getValue(key, '{}'));
                    if (activeTabs[thisSite.id]) {
                        delete activeTabs[thisSite.id];
                        await GM_setValue(key, JSON.stringify(activeTabs));
                    }
                } catch (e) {}
            },
            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];
                            if (typeof tabInfo !== 'object' || tabInfo === null || now - tabInfo.timestamp > AITabSync.config.TIMINGS.STALE_THRESHOLD) {
                                delete activeTabs[siteId];
                                hasChanged = true;
                            }
                        }
                    }
                    if (hasChanged) await GM_setValue(AITabSync.config.KEYS.ACTIVE_TABS, JSON.stringify(activeTabs));
                } catch (e) {}
            },
        },

        // --- 8. Communication Module ---
        comms: {
            deployNetworkInterceptor() {
                const { thisSite } = AITabSync.state;
                if (!thisSite?.queryExtractor) 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 {
                                if (config.body && typeof config.body === 'string') {
                                    const query = site.queryExtractor(config.body);
                                    if (query) AITabSync.comms.handleQueryFound(query, site);
                                }
                            } catch (e) {}
                        }
                        return fetch.apply(this, args);
                    };
                    unsafeWindow.fetch._isHooked = true;
                }
            },
            async handleQueryFound(query, sourceSite) {
                const { utils, state, config, elements } = AITabSync;
                const targets = Array.from(state.selectedTargets);
                if (targets.length === 0) return;
                if (elements.fab) {
                    elements.fab.classList.add('sending');
                    setTimeout(() => elements.fab?.classList.remove('sending'), 2000);
                }
                await GM_setValue(config.KEYS.SHARED_QUERY, JSON.stringify({
                    query,
                    timestamp: Date.now(),
                    sourceId: sourceSite.id,
                    targetIds: targets
                }));
            },
            async processSharedQuery(value) {
                const { state, utils, config } = AITabSync;
                if (state.isProcessingTask || !value) return;
                state.isProcessingTask = true;
                try {
                    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) {
                    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';
                    inputArea.dispatchEvent(new KeyboardEvent(eventType, {
                        key: 'Enter',
                        code: 'Enter',
                        keyCode: 13,
                        which: 13,
                        bubbles: true,
                        cancelable: true,
                        ctrlKey: useModifierKey && !isMac,
                        metaKey: useModifierKey && isMac
                    }));
                    setTimeout(() => (state.isSubmitting = false), 2000);
                } catch (error) {
                    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) {}
                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) {}
                    }
                });
            },
        },

        // --- 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);
                }
                state.animationStyle = await GM_getValue(config.KEYS.ANIMATION_STYLE, 'spin');
                state.isSelectionSynced = await GM_getValue(config.KEYS.SELECTION_SYNC_ENABLED, true);
                if (state.isSelectionSynced) {
                    const sharedSelection = await GM_getValue(config.KEYS.SHARED_SELECTION, '[]');
                    state.selectedTargets = new Set(JSON.parse(sharedSelection));
                }
            },
            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 newTargets = JSON.parse(nv || '[]');
                    state.visibleTargets = newTargets;
                    const oldTargets = config.DISPLAY_ORDER;
                    oldTargets.forEach((id) => {
                        if (!newTargets.includes(id) && state.selectedTargets.has(id)) state.selectedTargets.delete(id);
                    });
                    ui.rebuildChipsUI();
                });
                GM_addValueChangeListener(config.KEYS.SHARED_SELECTION, (name, ov, nv, remote) => {
                    if (remote && state.isSelectionSynced) {
                        utils.log('接收到同步的选择状态:', nv);
                        state.selectedTargets = new Set(JSON.parse(nv || '[]'));
                        ui.updatePanelState();
                        ui.updateSelectAllButtonState();
                    }
                });
            },
            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, elements } = 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();
                    elements.fab.classList.add(state.animationStyle === 'spin' ? 'animation-spin' : 'animation-aurora');
                    if (state.animationStyle === 'spin') {
                        elements.fab.classList.add('intro-playing');
                    }
                    AITabSync.events.register();
                    this.registerGMListeners();
                    this.startBackgroundTasks();
                    lifecycle.ensureWindowName();
                    lifecycle.deployHistoryInterceptor();
                    comms.initReceiver();
                    document.addEventListener('visibilitychange', () => {
                        if (document.visibilityState === 'visible') lifecycle.registerTabAsActive();
                    });
                    window.addEventListener('beforeunload', lifecycle.unregisterTabAsInactive);
                    if (window.self === window.top) ui.updateMenuCommand();
                } catch (error) {
                    utils.log('初始化错误', error);
                }
            },
        },
    };

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