AI Nav

AI 对话导航、提示词库、问题答案导出、阅读主题与搜索定位增强脚本。

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name               AI Nav
// @version            5.0.2
// @license            GPL-3.0-or-later
// @author             凌致
// @description        AI 对话导航、提示词库、问题答案导出、阅读主题与搜索定位增强脚本。
// @match              https://chat.openai.com/**
// @match              https://chatgpt.com/**
// @match              https://gemini.google.com/*
// @match              https://gemini.google.com/app
// @match              https://gemini.google.com/app/*
// @icon               https://t1.gstatic.cn/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&size=32&url=https://chatgpt.com
// @namespace          ai-conversation-navigator
// @run-at             document-idle
// @grant              none
// ==/UserScript==

(function() {
    'use strict';

    // ============================================================
    // ==Config== 配置层 - 常量、选择器、开关
    // ============================================================

    // [START: 配置常量]
    const DOM_MARK = 'data-ai-conversation-navigator';
    const STORAGE_PREFIX = 'ai_navigator';
    const READER_CONFIG_KEY = `${STORAGE_PREFIX}_reader_config`;
    const READER_STYLE_ID = `${STORAGE_PREFIX}_reader_style`;
    const PROMPTS_STORAGE_KEY = `${STORAGE_PREFIX}_prompt_library`;
    const PROMPT_LIBRARY_SCHEMA_VERSION = 2;
    const CHATGPT_MESSAGE_SELECTOR = '[data-message-author-role]';
    const GEMINI_USER_SELECTOR = [
        'user-query-content',
        'user-query',
        '[data-test-id="user-query"]',
        '[class*="user-query"]',
        '[data-test-id*="user-query"]'
    ].join(',');
    const GEMINI_ASSISTANT_SELECTOR = [
        'model-response',
        'message-content.model-response-text',
        '.response-container-with-gpi',
        '[data-test-id="model-response"]',
        '[class*="model-response"]',
        '[data-test-id*="model-response"]'
    ].join(',');

    let conversationKey = null;
    let loaded = false;
    let activeIndex = null;
    let refreshScheduled = false;
    let geminiBootstrapStarted = false;
    // [END: 配置常量]
    const defaultReaderConfig = {
        theme: 'default',
        fontType: 'yahei',
        fontSize: 18,
        maxWidth: 900,
        hideFooter: true,
        cleanMode: false,
        publicStyle: false,
        publicColor: 'yellow',
        publicType: 'half'
    };

    const fontStacks = {
        yahei: '"Microsoft YaHei", "PingFang SC", sans-serif',
        songti: '"Songti SC", "STSong", "Noto Serif CJK SC", serif',
        heiti: '"SimHei", "Source Han Sans SC", sans-serif',
        kaiti: '"KaiTi", "STKaiti", "LXGW WenKai Screen Web", serif',
        fangsong: '"FangSong", "STFangsong", "Noto Serif CJK SC", serif',
        rounded: '"PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif',
        lishu: '"LiSu", "STLiti", "KaiTi", serif',
        youyuan: '"YouYuan", "Microsoft YaHei", sans-serif',
        mono: '"Cascadia Mono", "Consolas", "SFMono-Regular", monospace'
    };

    const themes = {
        default:{ bg: '', text: '', accentBg: '', accentText: '', inputBg: '', sidebarText: '' },
        white:  { bg: '#ffffff', text: '#333333', accentBg: '#f7f7f7', accentText: '#333333', inputBg: 'rgba(255,255,255,0.85)', sidebarText: '#000000' },
        yellow: { bg: '#f6f1e7', text: '#5b4636', accentBg: '#ffffff', accentText: '#4a3b2f', inputBg: 'rgba(255,255,255,0.7)', sidebarText: '#000000' },
        green:  { bg: '#cce8cf', text: '#222222', accentBg: '#ffffff', accentText: '#1f3322', inputBg: 'rgba(255,255,255,0.7)', sidebarText: '#000000' },
        sepia:  { bg: '#f2eadf', text: '#4d3e33', accentBg: '#fffaf4', accentText: '#4d3e33', inputBg: 'rgba(255,250,244,0.88)', sidebarText: '#3f352d' },
        gray:   { bg: '#eceff3', text: '#243041', accentBg: '#ffffff', accentText: '#243041', inputBg: 'rgba(255,255,255,0.88)', sidebarText: '#1f2937' },
        dark:   { bg: '#1a1a1a', text: '#d1d5db', accentBg: '#2d2d2d', accentText: '#f3f4f6', inputBg: 'rgba(42,42,42,0.8)', sidebarText: '#ffffff' }
    };

    const cssText = `
        :host {
            --brand-primary: #7fbf7b;
            --brand-primary-strong: #5f9f5c;
            --panel-bg: rgba(240, 248, 237, 0.98);
            --panel-border: rgba(95, 159, 92, 0.18);
            --panel-shadow: 0 18px 45px rgba(80, 112, 78, 0.18);
            --text-primary: #203126;
            --text-secondary: #5f7461;
            --surface-muted: rgba(230, 241, 227, 0.96);
            --line-strong: rgba(22, 33, 25, 0.16);
            --panel-width: 336px;
            --panel-max-height: min(72vh, 760px);
            display: block;
            position: fixed;
            top: 10vh;
            right: 16px;
            z-index: 2147483647;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            color: var(--text-primary);
            font-size: 13px;
            user-select: none;
        }
        .app-shell {
            position: relative;
        }
        .panel {
            position: relative;
            right: 0;
            width: var(--panel-width);
            max-height: var(--panel-max-height);
            display: flex;
            flex-direction: column;
            border-radius: 22px;
            background: linear-gradient(180deg, color-mix(in srgb, var(--panel-bg) 98%, white 2%), color-mix(in srgb, var(--panel-bg) 92%, transparent 8%));
            border: 1px solid var(--panel-border);
            box-shadow: 0 24px 64px rgba(15, 23, 42, 0.14), 0 2px 0 rgba(255, 255, 255, 0.35) inset;
            backdrop-filter: blur(22px) saturate(1.08);
            overflow: hidden;
        }
        .header {
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 12px 12px 10px;
            border-bottom: 1px solid var(--line-strong);
            cursor: move;
            background: linear-gradient(180deg, color-mix(in srgb, var(--panel-bg) 94%, white 6%), color-mix(in srgb, var(--panel-bg) 74%, transparent 26%));
        }
        .title-block {
            flex: 1;
            min-width: 0;
        }
        .header-actions {
            display: inline-flex;
            align-items: center;
            gap: 4px;
            padding: 3px;
            border-radius: 14px;
            background: color-mix(in srgb, var(--panel-bg) 80%, transparent 20%);
            border: 1px solid var(--line-strong);
            box-shadow: inset 0 1px 0 rgba(255,255,255,0.75);
        }
        .title {
            font-size: 14px;
            font-weight: 700;
            line-height: 1.15;
            letter-spacing: -0.01em;
        }
        .subtitle {
            margin-top: 2px;
            color: var(--text-secondary);
            font-size: 10px;
        }
        .icon-btn {
            width: 30px;
            height: 30px;
            border: 1px solid rgba(15, 23, 42, 0.07);
            border-radius: 10px;
            background: linear-gradient(180deg, rgba(255,255,255,0.96), rgba(248,250,252,0.92));
            color: var(--text-primary);
            cursor: pointer;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            font-size: 13px;
            flex-shrink: 0;
            box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
            transition: transform 0.14s ease, border-color 0.14s ease, background 0.14s ease, color 0.14s ease, box-shadow 0.14s ease;
        }
        .icon-btn[data-active="true"] {
            color: var(--brand-primary-strong);
            border-color: rgba(25, 195, 125, 0.28);
            background: linear-gradient(180deg, rgba(220,252,231,0.98), rgba(209,250,229,0.9));
            box-shadow: 0 8px 18px rgba(25, 195, 125, 0.12);
        }
        .icon-btn:hover {
            border-color: rgba(25, 195, 125, 0.35);
            color: var(--brand-primary-strong);
            background: #fff;
            box-shadow: 0 8px 18px rgba(15, 23, 42, 0.08);
            transform: translateY(-1px);
        }
        .content {
            display: flex;
            flex-direction: column;
            min-height: 0;
        }
        :host(:not([open])) .panel {
            width: auto;
            max-height: none;
            overflow: visible;
            border-radius: 18px;
        }
        :host(:not([open])) .header {
            padding: 8px;
            border-bottom: none;
            background: transparent;
            cursor: default;
        }
        :host(:not([open])) .title-block,
        :host(:not([open])) .content,
        :host(:not([open])) .header-actions > :not(.toggle-eye) {
            display: none !important;
        }
        :host(:not([open])) .header-actions {
            padding: 0;
            border: none;
            background: transparent;
            box-shadow: none;
        }
        .list-container {
            position: relative;
            padding: 8px;
            max-height: 44vh;
            overflow-y: auto;
            scrollbar-width: thin;
        }
        .list-container::-webkit-scrollbar {
            width: 6px;
        }
        .list-container::-webkit-scrollbar-thumb {
            background: rgba(107, 114, 128, 0.35);
            border-radius: 999px;
        }
        ul {
            list-style: none;
            margin: 0;
            padding: 0;
        }
        li {
            padding: 10px 11px;
            margin-bottom: 5px;
            border-radius: 12px;
            cursor: pointer;
            color: var(--text-secondary);
            background: linear-gradient(180deg, rgba(248,250,252,0.95), rgba(244,247,250,0.88));
            border: 1px solid rgba(15, 23, 42, 0.04);
            line-height: 1.35;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            transition: background 0.15s ease, color 0.15s ease, transform 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
        }
        .question-item {
            padding: 0;
            overflow: hidden;
        }
        .question-row {
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 10px 11px;
            position: sticky;
            top: 0;
            z-index: 1;
            background: inherit;
        }
        .question-text {
            flex: 1;
            min-width: 0;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        .expand-btn {
            width: 24px;
            height: 24px;
            border: 1px solid rgba(15, 23, 42, 0.08);
            border-radius: 8px;
            background: rgba(255,255,255,0.88);
            color: var(--text-secondary);
            cursor: pointer;
            flex-shrink: 0;
        }
        .expand-btn:hover {
            color: var(--brand-primary-strong);
            border-color: rgba(25, 195, 125, 0.24);
        }
        .question-item.active .question-row {
            color: var(--brand-primary-strong);
            font-weight: 600;
        }
        .answer-tree {
            display: flex;
            flex-direction: column;
            gap: 4px;
            padding: 0 8px 8px 8px;
        }
        .answer-node {
            padding: 7px 10px;
            border-radius: 10px;
            color: var(--text-secondary);
            background: rgba(255,255,255,0.7);
            border: 1px solid rgba(15, 23, 42, 0.04);
            font-size: 12px;
            cursor: pointer;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        .answer-node:hover {
            color: var(--brand-primary-strong);
            border-color: rgba(25, 195, 125, 0.14);
        }
        li:hover {
            color: var(--brand-primary-strong);
            background: rgba(220, 252, 231, 0.9);
            transform: translateX(-1px);
            border-color: rgba(25, 195, 125, 0.14);
        }
        li.active {
            color: var(--brand-primary-strong);
            background: linear-gradient(180deg, rgba(220,252,231,0.98), rgba(209,250,229,0.94));
            font-weight: 600;
            border-color: rgba(25, 195, 125, 0.2);
            box-shadow: inset 0 0 0 1px rgba(25, 195, 125, 0.08), 0 8px 16px rgba(25, 195, 125, 0.08);
        }
        .fade-bottom {
            position: sticky;
            bottom: 0;
            width: 100%;
            height: 16px;
            margin-top: -16px;
            background: linear-gradient(to top, rgba(255,255,255,0.94), rgba(255,255,255,0));
            pointer-events: none;
        }
        .settings-panel {
            display: none;
            padding: 12px 14px 14px;
            border-top: 1px solid rgba(15, 23, 42, 0.06);
            background: rgba(248, 250, 252, 0.72);
        }
        .settings-panel.show {
            display: block;
        }
        .settings-panel label {
            display: block;
            margin-bottom: 6px;
            color: var(--text-secondary);
            font-size: 12px;
        }
        .input-group {
            display: flex;
            gap: 8px;
            align-items: center;
        }
        .settings-panel input {
            width: 100%;
            box-sizing: border-box;
            padding: 8px 10px;
            border: 1px solid rgba(15, 23, 42, 0.12);
            border-radius: 10px;
            background: #fff;
            color: var(--text-primary);
        }
        .action-group {
            margin-top: 10px;
        }
        .settings-panel button {
            width: 100%;
            padding: 9px 12px;
            background: var(--brand-primary);
            color: #fff;
            border: none;
            border-radius: 10px;
            cursor: pointer;
            font-size: 12px;
            font-weight: 600;
        }
        .settings-panel button:hover {
            background: var(--brand-primary-strong);
        }
        .settings-overlay {
            position: fixed;
            inset: 0;
            background: rgba(15, 23, 42, 0.32);
            backdrop-filter: blur(3px);
            display: none;
            align-items: center;
            justify-content: center;
            padding: 20px;
            z-index: 10;
        }
        .settings-overlay.show {
            display: flex;
        }
        .settings-overlay.priority-overlay {
            z-index: 20;
        }
        .settings-modal {
            width: min(760px, calc(100vw - 28px));
            max-height: min(82vh, 860px);
            overflow-y: auto;
            background: linear-gradient(180deg, color-mix(in srgb, var(--panel-bg) 98%, white 2%), color-mix(in srgb, var(--panel-bg) 90%, var(--surface-muted) 10%));
            border: 1px solid color-mix(in srgb, var(--panel-border) 82%, rgba(255, 255, 255, 0.45) 18%);
            border-radius: 22px;
            box-shadow: 0 28px 72px rgba(15, 23, 42, 0.16);
            padding: 16px;
        }
        .prompt-modal {
            width: min(860px, calc(100vw - 28px));
            background: linear-gradient(180deg, color-mix(in srgb, var(--panel-bg) 98%, white 2%), color-mix(in srgb, var(--panel-bg) 88%, var(--surface-muted) 12%));
            border: 1px solid color-mix(in srgb, var(--line-strong) 72%, rgba(25, 195, 125, 0.18) 28%);
            box-shadow: 0 28px 72px rgba(15, 23, 42, 0.22);
        }
        .modal-head {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 10px;
            padding: 2px 2px 10px;
            border-bottom: 1px solid rgba(15, 23, 42, 0.05);
        }
        .modal-head-copy {
            display: flex;
            flex-direction: column;
            gap: 1px;
        }
        .modal-title {
            font-size: 16px;
            font-weight: 700;
            letter-spacing: -0.01em;
        }
        .modal-stack {
            display: flex;
            flex-direction: column;
            gap: 8px;
        }
        .prompt-workspace {
            display: grid;
            grid-template-columns: 168px minmax(0, 1fr);
            gap: 10px;
            min-height: min(76vh, 760px);
        }
        .prompt-pane-nav {
            display: flex;
            flex-direction: column;
            gap: 8px;
        }
        .prompt-pane-btn {
            display: flex;
            flex-direction: column;
            gap: 3px;
            width: 100%;
            padding: 12px;
            border: 1px solid rgba(15, 23, 42, 0.08);
            border-radius: 14px;
            background: rgba(255,255,255,0.92);
            color: var(--text-primary);
            text-align: left;
            cursor: pointer;
            transition: border-color 0.14s ease, background 0.14s ease, box-shadow 0.14s ease, transform 0.14s ease;
        }
        .prompt-pane-btn strong {
            font-size: 13px;
            line-height: 1.3;
        }
        .prompt-pane-btn span {
            font-size: 11px;
            color: var(--text-secondary);
            line-height: 1.4;
        }
        .prompt-pane-btn:hover {
            border-color: rgba(25, 195, 125, 0.28);
            transform: translateY(-1px);
        }
        .prompt-pane-btn.active {
            border-color: rgba(25, 195, 125, 0.34);
            background: linear-gradient(180deg, rgba(220,252,231,0.98), rgba(240,253,244,0.92));
            box-shadow: 0 12px 26px rgba(25, 195, 125, 0.12);
        }
        .prompt-pane-content {
            min-width: 0;
            min-height: 0;
        }
        .prompt-pane {
            display: none;
            flex-direction: column;
            gap: 8px;
            min-height: 0;
        }
        .prompt-pane.active {
            display: flex;
            height: 100%;
        }
        .prompt-browse-pane {
            min-height: 0;
        }
        .prompt-list-card {
            display: flex;
            flex-direction: column;
            flex: 1;
            min-height: 0;
        }
        .prompt-list-tools {
            flex-shrink: 0;
        }
        .modal-grid {
            display: grid;
            grid-template-columns: minmax(0, 1fr);
            gap: 12px;
            width: 100%;
        }
        .setting-card.span-2 {
            grid-column: 1 / -1;
        }
        .setting-card {
            background: linear-gradient(180deg, color-mix(in srgb, var(--panel-bg) 92%, white 8%), color-mix(in srgb, var(--surface-muted) 90%, white 10%));
            border: 1px solid color-mix(in srgb, var(--line-strong) 74%, rgba(255,255,255,0.22) 26%);
            border-radius: 14px;
            padding: 14px;
            box-shadow: 0 1px 2px rgba(15, 23, 42, 0.035);
            min-width: 0;
        }
        .setting-card label,
        .setting-title {
            display: block;
            margin-bottom: 7px;
            color: var(--text-primary);
            font-size: 11px;
            font-weight: 600;
            letter-spacing: 0.01em;
        }
        .field-block {
            display: flex;
            flex-direction: column;
            gap: 6px;
        }
        .field-row {
            display: grid;
            grid-template-columns: repeat(2, minmax(0, 1fr));
            gap: 8px;
        }
        .field-label {
            color: var(--text-primary);
            font-size: 12px;
            font-weight: 600;
        }
        .range-row,
        .toggle-row,
        .style-row {
            display: flex;
            gap: 6px;
            align-items: center;
            width: 100%;
        }
        .range-row.column {
            flex-direction: column;
            align-items: stretch;
            gap: 10px;
        }
        .range-row span {
            color: var(--text-secondary);
            font-size: 12px;
        }
        .number-input {
            width: 100% !important;
            min-width: 0;
            height: 42px;
            box-sizing: border-box;
            padding: 8px 12px;
            border: 1px solid rgba(15, 23, 42, 0.1);
            border-radius: 10px;
            background: color-mix(in srgb, var(--panel-bg) 88%, white 12%);
            color: var(--text-primary);
            box-shadow: inset 0 1px 1px rgba(15, 23, 42, 0.02);
            font-size: 14px;
        }
        .text-input,
        .text-area {
            width: 100%;
            box-sizing: border-box;
            border: 1px solid rgba(15, 23, 42, 0.1);
            border-radius: 12px;
            background: color-mix(in srgb, var(--panel-bg) 88%, white 12%);
            color: var(--text-primary);
            box-shadow: inset 0 1px 1px rgba(15, 23, 42, 0.02);
            font: inherit;
        }
        .text-input {
            min-height: 42px;
            padding: 10px 12px;
        }
        .text-area {
            min-height: 132px;
            padding: 12px;
            resize: vertical;
            line-height: 1.5;
        }
        .primary-btn {
            min-height: 40px;
            padding: 0 14px;
            border: 1px solid #6eaf6a;
            border-radius: 12px;
            background: linear-gradient(180deg, #8dc887, #72b46e);
            color: #fff;
            font-size: 14px;
            font-weight: 600;
            cursor: pointer;
            box-shadow: 0 8px 18px rgba(95, 159, 92, 0.18);
        }
        .primary-btn:hover {
            filter: brightness(1.02);
        }
        .prompt-list {
            display: flex;
            flex-direction: column;
            gap: 8px;
            flex: 1;
            min-height: 420px;
            max-height: none;
            overflow-y: auto;
            padding-right: 2px;
        }
        .prompt-group {
            display: flex;
            flex-direction: column;
            gap: 8px;
        }
        .prompt-tab-bar {
            display: flex;
            flex-wrap: wrap;
            gap: 8px;
        }
        .prompt-tab-btn {
            min-height: 36px;
            padding: 0 12px;
            border: 1px solid var(--line-strong);
            border-radius: 999px;
            background: color-mix(in srgb, var(--panel-bg) 88%, var(--surface-muted) 12%);
            color: var(--text-secondary);
            font-size: 12px;
            font-weight: 700;
            cursor: pointer;
        }
        .prompt-tab-btn.active {
            border-color: rgba(25, 195, 125, 0.28);
            background: linear-gradient(180deg, rgba(220,252,231,0.98), rgba(240,253,244,0.92));
            color: var(--brand-primary-strong);
            box-shadow: 0 8px 18px rgba(25, 195, 125, 0.12);
        }
        .prompt-item {
            padding: 9px 10px;
            border-radius: 12px;
            border: 1px solid color-mix(in srgb, var(--line-strong) 78%, rgba(25, 195, 125, 0.12) 22%);
            background: color-mix(in srgb, var(--panel-bg) 78%, var(--surface-muted) 22%);
            transition: border-color 0.14s ease, box-shadow 0.14s ease, transform 0.14s ease;
        }
        .prompt-item:hover {
            border-color: rgba(25, 195, 125, 0.22);
            box-shadow: 0 10px 18px rgba(15, 23, 42, 0.06);
            transform: translateY(-1px);
        }
        .prompt-item.selected {
            border-color: rgba(25, 195, 125, 0.35);
            box-shadow: 0 0 0 1px rgba(25, 195, 125, 0.12), 0 10px 18px rgba(15, 23, 42, 0.06);
            background: rgba(240, 253, 244, 0.9);
        }
        .prompt-item.expanded {
            box-shadow: 0 0 0 1px rgba(25, 195, 125, 0.1), 0 14px 26px rgba(15, 23, 42, 0.07);
        }
        .prompt-item-head {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 8px;
        }
        .prompt-head-main {
            display: flex;
            align-items: center;
            gap: 8px;
            min-width: 0;
            flex: 1;
        }
        .select-check {
            appearance: none;
            -webkit-appearance: none;
            width: 18px;
            height: 18px;
            margin: 0;
            border: 1.5px solid #000;
            border-radius: 5px;
            background: color-mix(in srgb, var(--panel-bg) 92%, white 8%);
            cursor: pointer;
            flex-shrink: 0;
            position: relative;
            box-shadow: inset 0 1px 1px rgba(255,255,255,0.6);
        }
        .select-check:checked {
            background: linear-gradient(180deg, #9fd49a, #7fbf7b);
            border-color: #000;
        }
        .select-check:checked::after {
            content: '';
            position: absolute;
            left: 5px;
            top: 1px;
            width: 4px;
            height: 9px;
            border: solid #14311c;
            border-width: 0 2px 2px 0;
            transform: rotate(45deg);
        }
        .prompt-item-head.sticky {
            position: sticky;
            top: 0;
            z-index: 1;
            padding-bottom: 8px;
            background: color-mix(in srgb, var(--panel-bg) 96%, transparent 4%);
        }
        .prompt-item-title {
            font-size: 13px;
            font-weight: 700;
            color: var(--text-primary);
            cursor: pointer;
            line-height: 1.4;
        }
        .prompt-item-meta {
            display: flex;
            align-items: center;
            gap: 6px;
            flex-wrap: wrap;
            margin-top: 6px;
        }
        .prompt-item-actions {
            display: inline-flex;
            align-items: center;
            gap: 6px;
            flex-wrap: wrap;
            justify-content: flex-end;
        }
        .mini-btn {
            min-width: 46px;
            height: 28px;
            padding: 0 10px;
            border: 1px solid rgba(15, 23, 42, 0.08);
            border-radius: 9px;
            background: rgba(255,255,255,0.96);
            color: var(--text-primary);
            font-size: 12px;
            cursor: pointer;
        }
        .mini-btn:hover {
            border-color: rgba(25, 195, 125, 0.28);
            color: var(--brand-primary-strong);
        }
        .mini-btn.danger:hover {
            border-color: rgba(239, 68, 68, 0.26);
            color: #dc2626;
        }
        .ghost-btn {
            min-height: 40px;
            padding: 0 14px;
            border: 1px solid rgba(15, 23, 42, 0.1);
            border-radius: 12px;
            background: rgba(255,255,255,0.96);
            color: var(--text-primary);
            font-size: 13px;
            font-weight: 600;
            cursor: pointer;
        }
        .ghost-btn:hover {
            border-color: rgba(25, 195, 125, 0.24);
            color: var(--brand-primary-strong);
        }
        .button-row {
            display: flex;
            gap: 8px;
            flex-wrap: wrap;
        }
        .button-row > * {
            flex: 1;
        }
        .prompt-item-content {
            margin-top: 5px;
            color: var(--text-secondary);
            font-size: 12px;
            line-height: 1.5;
            white-space: pre-wrap;
            word-break: break-word;
        }
        .prompt-item-body {
            display: none;
            margin-top: 8px;
            padding-top: 8px;
            border-top: 1px solid rgba(15, 23, 42, 0.06);
        }
        .prompt-item.expanded .prompt-item-body {
            display: block;
        }
        .tag-chip {
            display: inline-flex;
            align-items: center;
            width: fit-content;
            margin-top: 6px;
            padding: 3px 8px;
            border-radius: 999px;
            background: rgba(226, 232, 240, 0.75);
            color: var(--text-secondary);
            font-size: 11px;
            line-height: 1;
        }
        .status-line {
            margin-top: 6px;
            color: var(--text-secondary);
            font-size: 12px;
            line-height: 1.5;
        }
        .status-line[data-tone="success"] {
            color: #047857;
        }
        .status-line[data-tone="warning"] {
            color: #b45309;
        }
        .status-line[data-tone="danger"] {
            color: #b91c1c;
        }
        .editor-meta {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 8px;
            margin-top: 10px;
        }
        .editor-badge {
            display: inline-flex;
            align-items: center;
            min-height: 28px;
            padding: 0 10px;
            border-radius: 999px;
            background: rgba(226, 232, 240, 0.8);
            color: var(--text-secondary);
            font-size: 12px;
            font-weight: 600;
        }
        .toolbar-row {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 10px;
            margin-bottom: 8px;
        }
        .toolbar-row.stack {
            align-items: stretch;
            flex-direction: column;
            gap: 10px;
        }
        .toolbar-summary {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 8px;
        }
        .toolbar-head {
            display: flex;
            justify-content: flex-start;
            gap: 8px;
            align-items: center;
            flex-wrap: wrap;
        }
        .toolbar-actions {
            display: flex;
            align-items: center;
            gap: 6px;
            flex-wrap: wrap;
            justify-content: flex-end;
        }
        .master-check {
            display: inline-grid;
            grid-auto-flow: column;
            grid-template-columns: 18px auto;
            gap: 8px;
            align-items: center;
            justify-content: center;
            height: 38px;
            padding: 0 10px;
            border: 1px solid var(--line-strong);
            border-radius: 12px;
            background: color-mix(in srgb, var(--panel-bg) 88%, var(--surface-muted) 12%);
            color: var(--text-primary);
            font-size: 12px;
            font-weight: 700;
            box-sizing: border-box;
            vertical-align: middle;
        }
        .master-check .select-check {
            display: block;
            align-self: center;
            justify-self: center;
        }
        .master-check span {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            height: 100%;
            line-height: 1;
            white-space: nowrap;
        }
        .helper-text {
            margin-top: 6px;
            color: var(--text-secondary);
            font-size: 12px;
            line-height: 1.5;
        }
        .field-select {
            width: 100%;
            min-height: 42px;
            padding: 0 12px;
            border-radius: 12px;
            border: 1px solid var(--line-strong);
            background: color-mix(in srgb, var(--panel-bg) 86%, white 14%);
            color: var(--text-primary);
            font: inherit;
        }
        .field-row-2 {
            display: grid;
            grid-template-columns: repeat(2, minmax(0, 1fr));
            gap: 10px;
        }
        .inline-action-row {
            display: grid;
            grid-template-columns: minmax(0, 1fr) auto;
            gap: 8px;
            align-items: end;
        }
        .menu-wrap {
            position: relative;
        }
        .menu-panel {
            position: absolute;
            top: calc(100% + 8px);
            right: 0;
            width: 204px;
            padding: 8px;
            border-radius: 14px;
            border: 1px solid var(--line-strong);
            background: color-mix(in srgb, var(--panel-bg) 96%, transparent 4%);
            box-shadow: var(--panel-shadow);
            display: none;
            z-index: 20;
        }
        .menu-panel.show {
            display: block;
        }
        .menu-panel.disabled {
            display: none !important;
        }
        .menu-item {
            width: 100%;
            min-height: 36px;
            border: 1px solid transparent;
            border-radius: 10px;
            background: transparent;
            color: var(--text-primary);
            text-align: left;
            cursor: pointer;
            padding: 0 10px;
        }
        .menu-item:hover {
            background: var(--surface-muted);
            border-color: var(--line-strong);
        }
        .search-bar {
            display: grid;
            grid-template-columns: minmax(0, 1fr) auto auto;
            gap: 8px;
            margin-bottom: 0;
        }
        .search-hit {
            display: inline;
            padding: 0 1px;
            border-radius: 3px;
            background: var(--search-hit-bg, #fff59d) !important;
            color: var(--search-hit-text, #111827) !important;
            box-shadow: inset 0 0 0 1px var(--search-hit-outline, rgba(161, 98, 7, 0.18));
            box-decoration-break: clone;
            -webkit-box-decoration-break: clone;
        }
        .current-search-hit {
            background: var(--search-hit-current-bg, #f7c948) !important;
            color: var(--search-hit-current-text, #111827) !important;
            box-shadow: 0 0 0 2px var(--search-hit-shadow, rgba(245, 158, 11, 0.28));
        }
        .search-row {
            display: grid;
            grid-template-columns: minmax(0, 1fr) auto auto;
            gap: 8px;
            align-items: end;
        }
        .selection-row {
            display: flex;
            align-items: center;
            gap: 8px;
            flex-wrap: wrap;
        }
        .selection-row > * {
            margin: 0;
            align-self: center;
        }
        .selection-row .editor-badge,
        .selection-row .compact-btn,
        .selection-row .master-check {
            min-width: 108px;
            min-height: 38px;
            box-sizing: border-box;
        }
        .selection-row .editor-badge {
            margin-left: 0;
        }
        .selection-row .master-check {
            display: inline-flex;
            align-items: center;
            justify-content: flex-start;
            padding-left: 12px;
            padding-right: 12px;
            padding-top: 0;
            padding-bottom: 0;
        }
        .selection-row .master-check .select-check {
            position: relative;
            top: 2px;
            flex-shrink: 0;
        }
        .selection-row .master-check.checkbox-only {
            min-width: 38px;
            width: 38px;
            padding-left: 0;
            padding-right: 0;
            justify-content: center;
            gap: 0;
        }
        .selection-row .master-check.checkbox-only span {
            display: none;
        }
        .selection-row .master-check.checkbox-only .select-check {
            top: 1px;
        }
        .selection-row .master-check span {
            display: inline-flex;
            align-items: center;
            min-height: 0;
            height: auto;
        }
        .icon-mini-btn {
            width: 40px;
            height: 38px;
            border: 1px solid rgba(15, 23, 42, 0.1);
            border-radius: 12px;
            background: rgba(255,255,255,0.96);
            color: var(--text-primary);
            cursor: pointer;
            font-size: 15px;
        }
        .icon-mini-btn:hover {
            border-color: rgba(25, 195, 125, 0.24);
            color: var(--brand-primary-strong);
        }
        .compact-btn {
            min-width: 108px;
            min-height: 38px;
            padding: 0 12px;
            border: 1px solid var(--line-strong);
            border-radius: 12px;
            background: color-mix(in srgb, var(--panel-bg) 88%, var(--surface-muted) 12%);
            color: var(--text-primary);
            font-size: 12px;
            font-weight: 700;
            cursor: pointer;
        }
        .compact-btn.danger-btn:hover {
            border-color: rgba(239, 68, 68, 0.26);
            color: #dc2626;
        }
        .compact-btn:hover {
            border-color: rgba(25, 195, 125, 0.28);
            color: var(--brand-primary-strong);
        }
        .setting-stack {
            display: flex;
            flex-direction: column;
            gap: 12px;
            width: 100%;
        }
        .setting-row-2 {
            display: grid;
            grid-template-columns: repeat(2, minmax(0, 1fr));
            gap: 12px;
            width: 100%;
        }
        .setting-inline {
            display: grid;
            grid-template-columns: minmax(0, 1fr) auto;
            gap: 8px;
            align-items: center;
        }
        .setting-inline.equal {
            grid-template-columns: repeat(2, minmax(0, 1fr));
        }
        .setting-inline .number-input,
        .setting-inline .field-select,
        .setting-inline .ghost-btn,
        .setting-inline .primary-btn {
            width: 100%;
        }
        .setting-inline .primary-btn {
            min-width: 132px;
        }
        .setting-field {
            display: flex;
            flex-direction: column;
            gap: 6px;
        }
        .icon-action-btn {
            width: 32px;
            height: 32px;
            border: 1px solid var(--line-strong);
            border-radius: 10px;
            background: color-mix(in srgb, var(--panel-bg) 86%, white 14%);
            color: var(--text-primary);
            display: inline-flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            font-size: 14px;
            font-weight: 700;
        }
        .icon-action-btn:hover {
            border-color: rgba(25, 195, 125, 0.28);
            color: var(--brand-primary-strong);
        }
        .icon-action-btn.danger:hover {
            border-color: rgba(239, 68, 68, 0.26);
            color: #dc2626;
        }
        .drop-zone {
            display: flex;
            align-items: center;
            justify-content: center;
            min-height: 140px;
            padding: 16px;
            border-radius: 14px;
            border: 1px dashed rgba(15, 23, 42, 0.16);
            background: rgba(255,255,255,0.7);
            color: var(--text-secondary);
            text-align: center;
            line-height: 1.6;
        }
        .drop-zone.dragover {
            border-color: rgba(25, 195, 125, 0.4);
            background: rgba(240, 253, 244, 0.85);
            color: var(--brand-primary-strong);
        }
        .sr-file {
            display: none;
        }
        .prompt-empty {
            padding: 14px 12px;
            border-radius: 12px;
            border: 1px dashed rgba(15, 23, 42, 0.12);
            color: var(--text-secondary);
            text-align: center;
            font-size: 12px;
            background: rgba(255,255,255,0.58);
        }
        .choice-row {
            display: flex;
            gap: 6px;
            flex-wrap: wrap;
        }
        .choice-row.grid-2 {
            display: grid;
            grid-template-columns: repeat(2, minmax(0, 1fr));
        }
        .choice-btn,
        .style-dot,
        .type-btn {
            border: 1px solid rgba(15, 23, 42, 0.1);
            background: #fff;
            color: var(--text-primary);
            border-radius: 12px;
            cursor: pointer;
        }
        .choice-btn {
            padding: 6px 10px;
            font-size: 11px;
            min-height: 34px;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            gap: 6px;
            box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
            transition: transform 0.14s ease, border-color 0.14s ease, box-shadow 0.14s ease, background 0.14s ease, color 0.14s ease;
        }
        .choice-btn span:last-child {
            font-weight: 600;
            letter-spacing: 0.01em;
        }
        .choice-btn[data-value="default"] {
            background: linear-gradient(180deg, #ffffff, #f3f4f6);
            color: #111827;
        }
        .choice-btn.active,
        .type-btn.active {
            background: linear-gradient(180deg, #8dc887, #72b46e);
            color: #fff;
            border-color: #6eaf6a;
        }
        .choice-btn:hover,
        .style-dot:hover,
        .type-btn:hover {
            border-color: rgba(25, 195, 125, 0.45);
            transform: translateY(-1px);
        }
        .choice-btn.active {
            box-shadow: 0 8px 18px rgba(25, 195, 125, 0.2);
        }
        .toggle-row {
            justify-content: space-between;
        }
        .style-row {
            justify-content: space-between;
        }
        .style-dots {
            display: flex;
            gap: 8px;
        }
        .style-dot {
            width: 26px;
            height: 26px;
            border-radius: 999px;
        }
        .style-dot.active {
            outline: 3px solid rgba(25, 195, 125, 0.2);
            border-color: var(--brand-primary);
        }
        .type-switch {
            display: flex;
            gap: 8px;
            justify-content: flex-end;
        }
        .type-btn {
            padding: 6px 10px;
            font-size: 11px;
        }
        .range-row .number-input,
        .range-row button {
            height: 40px;
            border: 1px solid rgba(15, 23, 42, 0.1);
            border-radius: 10px;
            background: rgba(255,255,255,0.96);
            color: var(--text-primary);
        }
        .range-row button {
            width: 100%;
            padding: 0 14px;
            background: linear-gradient(180deg, #8dc887, #72b46e);
            color: #fff;
            border-color: #6eaf6a;
            white-space: nowrap;
            cursor: pointer;
            box-shadow: 0 8px 18px rgba(95, 159, 92, 0.18);
            font-size: 14px;
            font-weight: 600;
        }
        .inline-field {
            display: flex;
            align-items: center;
            gap: 6px;
        }
        .inline-field span {
            color: var(--text-secondary);
            font-size: 12px;
            white-space: nowrap;
        }
        .switch-pair {
            display: grid;
            grid-template-columns: repeat(2, minmax(0, 1fr));
            gap: 8px;
        }
        .switch-item {
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 8px;
            min-height: 34px;
            padding: 0 1px;
        }
        .theme-chip {
            width: 12px;
            height: 12px;
            border-radius: 999px;
            border: 1px solid rgba(15, 23, 42, 0.1);
            flex-shrink: 0;
            box-shadow: inset 0 1px 1px rgba(255,255,255,0.55);
        }
        @media (max-width: 640px) {
            :host {
                top: 12px;
                right: 12px;
            }
            .panel {
                width: min(320px, calc(100vw - 24px));
            }
            .settings-modal {
                width: min(100vw - 20px, 520px);
                padding: 10px;
            }
            .field-row,
            .modal-grid,
            .choice-row.grid-2,
            .switch-pair,
            .search-row,
            .prompt-workspace {
                grid-template-columns: 1fr;
            }
            .question-row {
                padding-right: 8px;
            }
        }
    `;
    // [END: CSS样式]

    // ============================================================
    // ==State== 状态层 - 全局状态管理
    // ============================================================

    // [START: 全局状态]
    // 注意: 使用原有的全局变量作为状态管理
    // conversationKey, loaded, activeIndex, refreshScheduled, geminiBootstrapStarted
    // [END: 全局状态]

    // ============================================================
    // ==Utils== 工具层 - 纯函数工具
    // ============================================================

    // [START: 工具函数]

    function detectSite() {
        if (location.hostname === 'gemini.google.com') return 'gemini';
        return 'chatgpt';
    }

    function generatePromptId() {
        return `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
    }

    function getPromptSignature(item) {
        return [
            sanitizeText(item?.title || '').toLowerCase(),
            sanitizeText(item?.group || '').toLowerCase(),
            sanitizeText(item?.content || '').toLowerCase()
        ].join('::');
    }

    function normalizePromptItem(item) {
        if (!item || typeof item.title !== 'string' || typeof item.content !== 'string') return null;
        const title = item.title.trim();
        const content = item.content.trim();
        if (!title || !content) return null;
        return {
            id: typeof item.id === 'string' && item.id.trim() ? item.id : generatePromptId(),
            title,
            content,
            group: typeof item.group === 'string' ? item.group.trim() : '',
            updatedAt: Number.isFinite(Number(item.updatedAt)) ? Number(item.updatedAt) : Date.now()
        };
    }

    function normalizePromptLibraryPayload(data) {
        const payload = Array.isArray(data)
            ? { version: 1, prompts: data }
            : (data && typeof data === 'object' ? data : { version: PROMPT_LIBRARY_SCHEMA_VERSION, prompts: [] });
        const prompts = Array.isArray(payload.prompts) ? payload.prompts : [];
        const normalized = prompts.map(normalizePromptItem).filter(Boolean);
        const deduped = [];
        const seen = new Set();

        normalized.forEach((item) => {
            const signature = getPromptSignature(item);
            if (seen.has(signature)) return;
            seen.add(signature);
            deduped.push(item);
        });

        return {
            version: Number.isFinite(Number(payload.version)) ? Number(payload.version) : 1,
            prompts: deduped
        };
    }

    function loadPromptLibraryPayload() {
        try {
            const raw = localStorage.getItem(PROMPTS_STORAGE_KEY);
            if (!raw) {
                return { version: PROMPT_LIBRARY_SCHEMA_VERSION, prompts: [], migrated: false };
            }
            const normalized = normalizePromptLibraryPayload(JSON.parse(raw));
            const migrated = !raw.includes(`"version":${PROMPT_LIBRARY_SCHEMA_VERSION}`) && !raw.includes(`"version": ${PROMPT_LIBRARY_SCHEMA_VERSION}`);
            if (migrated) {
                savePromptLibrary(normalized.prompts);
            }
            return {
                version: normalized.version,
                prompts: normalized.prompts,
                migrated
            };
        } catch {
            return { version: PROMPT_LIBRARY_SCHEMA_VERSION, prompts: [], migrated: false };
        }
    }

    function loadPromptLibrary() {
        return loadPromptLibraryPayload().prompts;
    }

    function savePromptLibrary(data) {
        const normalized = normalizePromptLibraryPayload({
            version: PROMPT_LIBRARY_SCHEMA_VERSION,
            prompts: Array.isArray(data) ? data : []
        });
        localStorage.setItem(PROMPTS_STORAGE_KEY, JSON.stringify({
            version: PROMPT_LIBRARY_SCHEMA_VERSION,
            prompts: normalized.prompts
        }));
    }

    function serializePromptLibraryToText(items) {
        return (items || []).map(item => [
            `### ${item.title}`,
            `group: ${item.group || '未标记'}`,
            '',
            item.content || ''
        ].join('\n')).join('\n\n---\n\n');
    }

    function parsePromptLibraryFromText(text) {
        return (text || '')
            .split(/\n\s*---\s*\n/g)
            .map(chunk => chunk.trim())
            .filter(Boolean)
            .map(chunk => {
                const lines = chunk.split('\n');
                const titleLine = lines.shift() || '';
                const title = titleLine.replace(/^###\s*/, '').trim();
                let group = '';
                if (lines[0] && /^group\s*:/i.test(lines[0])) {
                    group = lines.shift().replace(/^group\s*:/i, '').trim();
                }
                const content = lines.join('\n').trim();
                if (!title || !content) return null;
                return {
                    id: `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
                    title,
                    group: group === '未标记' ? '' : group,
                    content,
                    updatedAt: Date.now()
                };
            })
            .filter(Boolean);
    }

    function triggerTextDownload(filename, text, mimeType = 'text/plain;charset=utf-8') {
        const blob = new Blob([text], { type: mimeType });
        const url = URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = url;
        link.download = filename;
        link.rel = 'noopener';
        document.body.appendChild(link);
        link.click();
        link.remove();
        setTimeout(() => URL.revokeObjectURL(url), 1000);
    }

    async function copyText(text) {
        if (navigator.clipboard?.writeText) {
            await navigator.clipboard.writeText(text);
            return true;
        }
        const area = document.createElement('textarea');
        area.value = text;
        area.setAttribute('readonly', 'readonly');
        area.style.position = 'fixed';
        area.style.opacity = '0';
        document.body.appendChild(area);
        area.select();
        area.setSelectionRange(0, area.value.length);
        const success = document.execCommand('copy');
        area.remove();
        if (!success) throw new Error('copy failed');
        return true;
    }

    function scrollToNode(target, block = 'start') {
        if (!target) return;
        if (!isChatGPTSharePage()) target.style.scrollMarginTop = '56px';
        try {
            target.scrollIntoView({ behavior: 'smooth', block });
        } catch {
            target.scrollIntoView();
        }
    }

    function isChatGPTSharePage() {
        return detectSite() === 'chatgpt' && location.pathname.startsWith('/share/');
    }

    function getDepthStorageKey() {
        return `${STORAGE_PREFIX}_depth_${detectSite()}`;
    }

    function getPositionStorageKey() {
        return `${STORAGE_PREFIX}_position_${detectSite()}`;
    }

    function sanitizeText(text) {
        return (text || '').replace(/\s+/g, ' ').trim();
    }

    function getRootNodes(root = document) {
        const roots = [root];
        const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
        let current = walker.currentNode;
        while (current) {
            if (current.shadowRoot) {
                roots.push(current.shadowRoot);
            }
            current = walker.nextNode();
        }
        return roots;
    }

    function queryAllDeep(selector, root = document) {
        const results = [];
        getRootNodes(root).forEach(searchRoot => {
            results.push(...Array.from(searchRoot.querySelectorAll(selector)));
        });
        return Array.from(new Set(results));
    }

    function queryGeminiRoot() {
        return document.querySelector('#chat-history')
            || document.querySelector('[data-test-id="chat-history-container"]')
            || document.querySelector('chat-window-content')
            || document.querySelector('chat-window')
            || document.querySelector('main')
            || null;
    }

    function queryGeminiConversationContainers() {
        const root = queryGeminiRoot();
        if (!root) return [];
        return Array.from(root.querySelectorAll('.conversation-container'));
    }

    function getClosestMessageWrapper(element) {
        if (!element) return null;
        return element.closest('[data-testid^="conversation-turn-"]')
            || element.closest('[data-testid]')
            || element.closest('article')
            || element.closest('section')
            || element.parentElement;
    }

    function getSiteTitle() {
        return detectSite() === 'gemini' ? 'Gemini' : 'ChatGPT';
    }

    function getConversationKey() {
        return `${detectSite()}::${location.pathname}::${location.search}`;
    }

    function queryChatGPTContainer() {
        return document.querySelector('main')?.querySelector('.flex.flex-col.text-sm') || null;
    }

    function queryChatGPTQuestionElements() {
        const directUsers = queryAllDeep('[data-message-author-role="user"]');
        const wrapped = directUsers
            .map(getClosestMessageWrapper)
            .filter(Boolean);
        return Array.from(new Set(wrapped)).sort((a, b) => {
            if (a === b) return 0;
            const pos = a.compareDocumentPosition(b);
            if (pos & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
            if (pos & Node.DOCUMENT_POSITION_PRECEDING) return 1;
            return 0;
        });
    }

    function getGeminiMessageNodes() {
        const root = queryGeminiRoot();
        if (!root) return [];

        const nodes = Array.from(root.querySelectorAll(`${GEMINI_USER_SELECTOR}, ${GEMINI_ASSISTANT_SELECTOR}`));
        const filtered = nodes.filter(node => {
            if (!(node instanceof HTMLElement)) return false;
            const text = sanitizeText(node.innerText);
            if (!text) return false;
            const nestedSameType = node.parentElement?.closest(node.matches(GEMINI_USER_SELECTOR) ? GEMINI_USER_SELECTOR : GEMINI_ASSISTANT_SELECTOR);
            return !nestedSameType || nestedSameType === node;
        });

        const unique = [];
        filtered.forEach(node => {
            if (!unique.some(existing => existing.contains(node) || node.contains(existing))) {
                unique.push(node);
            }
        });

        return unique.sort((a, b) => {
            if (a === b) return 0;
            const pos = a.compareDocumentPosition(b);
            if (pos & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
            if (pos & Node.DOCUMENT_POSITION_PRECEDING) return 1;
            return 0;
        });
    }

    function queryGeminiQuestionElements() {
        const conversationContainers = queryGeminiConversationContainers();
        if (conversationContainers.length) {
            return conversationContainers.filter(container => container.querySelector('user-query-content'));
        }

        const root = queryGeminiRoot();
        const contentNodes = root ? Array.from(root.querySelectorAll('user-query-content')) : [];
        if (contentNodes.length) {
            return contentNodes.sort((a, b) => {
                if (a === b) return 0;
                const pos = a.compareDocumentPosition(b);
                if (pos & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
                if (pos & Node.DOCUMENT_POSITION_PRECEDING) return 1;
                return 0;
            });
        }
        const queryTextNodes = root ? Array.from(root.querySelectorAll('.query-text.gds-body-l, .query-text')) : [];
        if (queryTextNodes.length) {
            return queryTextNodes.sort((a, b) => {
                if (a === b) return 0;
                const pos = a.compareDocumentPosition(b);
                if (pos & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
                if (pos & Node.DOCUMENT_POSITION_PRECEDING) return 1;
                return 0;
            });
        }
        return getGeminiMessageNodes().filter(node => node.matches(GEMINI_USER_SELECTOR));
    }

    function queryQuestionElements() {
        return detectSite() === 'gemini'
            ? queryGeminiQuestionElements()
            : queryChatGPTQuestionElements();
    }

    function queryScrollContainer() {
        if (detectSite() === 'gemini') {
            return document.querySelector('#chat-history')
                || document.querySelector('[data-test-id="chat-history-container"]')
                || document.querySelector('infinite-scroller.chat-history')
                || document.querySelector('chat-window-content')
                || document.querySelector('chat-window')
                || document.querySelector('main')
                || document.scrollingElement
                || document.documentElement;
        }
        return queryChatGPTContainer()?.parentElement || document.scrollingElement || document.documentElement;
    }

    function loadReaderConfig() {
        try {
            const stored = JSON.parse(localStorage.getItem(READER_CONFIG_KEY) || '{}');
            return { ...defaultReaderConfig, ...stored };
        } catch {
            return { ...defaultReaderConfig };
        }
    }

    function saveReaderConfig(config) {
        localStorage.setItem(READER_CONFIG_KEY, JSON.stringify(config));
    }

    function applyReaderConfig(config) {
        const root = document.documentElement;
        const body = document.body;
        if (!root || !body) return;

        const site = detectSite();
        const theme = themes[config.theme] || themes.yellow;
        root.style.setProperty('--w-bg', theme.bg || '');
        root.style.setProperty('--w-text', theme.text || '');
        root.style.setProperty('--w-accent-bg', theme.accentBg || '');
        root.style.setProperty('--w-accent-text', theme.accentText || '');
        root.style.setProperty('--w-input-bg', theme.inputBg || '');
        root.style.setProperty('--w-sidebar-text', theme.sidebarText || '');
        root.style.setProperty('--w-font', fontStacks[config.fontType] || fontStacks.serif);
        root.style.setProperty('--w-footer-display', config.hideFooter ? 'none' : 'block');

        body.setAttribute('data-public-style', String(config.publicStyle));
        body.setAttribute('data-pub-color', config.publicColor);
        body.setAttribute('data-pub-type', config.publicType);
        body.setAttribute('data-theme', config.theme);
        body.setAttribute('data-clean-mode', String(config.cleanMode));

        let styleEl = document.getElementById(READER_STYLE_ID);
        if (!styleEl) {
            styleEl = document.createElement('style');
            styleEl.id = READER_STYLE_ID;
            document.head.appendChild(styleEl);
        }

        const geminiThemeCss = config.theme === 'default' ? '' : `
            :root, body, .theme-host, :where(.theme-host) {
                --bard-color-synthetic--chat-window-surface: var(--w-bg) !important;
                --gem-sys-color--surface: var(--w-bg) !important;
                --gem-sys-color--surface-variant: var(--w-bg) !important;
                --gem-sys-color--surface-container: var(--w-bg) !important;
                --gem-sys-color--surface-container-high: var(--w-bg) !important;
                --gem-sys-color--surface-container-low: var(--w-bg) !important;
                background-color: var(--w-bg) !important;
                color: var(--w-text) !important;
            }
            main, [role="main"] {
                background-color: var(--w-bg) !important;
                color: var(--w-text) !important;
            }
            gemini-app, main, infinite-scroller,
            .conversation-container, .response-container, .inner-container,
            .scroll-container, .input-area-container, .mat-drawer-container,
            mat-sidenav, .mat-drawer, .mat-drawer-inner-container,
            .chat-history, .explore-gems-container, conversations-list, bot-list,
            .overflow-container, mat-action-list, mat-nav-list,
            .conversation-items-container, side-nav-action-button,
            bard-sidenav, input-container,
            .input-area-container, .bottom-container, .composer-container, .input-wrapper {
                background: transparent !important;
                background-color: transparent !important;
                box-shadow: none !important;
                filter: none !important;
            }
            .input-gradient, input-container.input-gradient {
                background: transparent !important;
                pointer-events: auto !important;
            }
            .top-gradient-container, .scroll-container::after, .scroll-container::before {
                display: none !important;
            }
            .input-area {
                background-color: var(--w-input-bg) !important;
                border: 1px solid rgba(0,0,0,0.08) !important;
                box-shadow: 0 4px 12px rgba(0,0,0,0.03) !important;
            }
            .text-input-field, .ql-editor, .ql-container {
                background: transparent !important;
                border: none !important;
            }
            .user-query-bubble-with-background, .user-query-container .query-content {
                background-color: var(--w-accent-bg) !important;
                color: var(--w-accent-text) !important;
                border-radius: 16px !important;
                box-shadow: 0 2px 8px rgba(0,0,0,0.04) !important;
                border: 1px solid rgba(0,0,0,0.03) !important;
            }
            code, .code-container, pre {
                background-color: var(--w-accent-bg) !important;
                color: var(--w-text) !important;
                border-radius: 12px !important;
                border: 1px solid rgba(0,0,0,0.05) !important;
                box-shadow: 0 2px 6px rgba(0,0,0,0.03) !important;
            }
        `;

        const cleanModeCss = config.cleanMode
            ? (site === 'chatgpt'
                ? `
            nav, aside, header:has(a[href="/"]), [data-testid="left-sidebar"], [data-testid="nav"], [class*="sidebar"], [class*="SideBar"],
            [class*="top-bar"], [class*="TopBar"], [class*="composer-toolbar"], [class*="chat-history"], [class*="project-switcher"] {
                display: none !important;
            }
            main#main, #main, #thread, .composer-parent {
                margin-left: auto !important;
                margin-right: auto !important;
            }
                `
                : `
            bard-sidenav, side-navigation, .side-nav, .sidenav-container, .app-header, header, .top-nav, .gmat-mdc-dialog-surface + header {
                display: none !important;
            }
            main, [role="main"] {
                margin: 0 auto !important;
            }
                `)
            : '';

        styleEl.textContent = `
            body, p, li, h1, h2, h3, div, span, button, input, textarea {
                font-family: var(--w-font) !important;
            }
            ${site === 'gemini' ? geminiThemeCss : ''}
            ${cleanModeCss}
            main p, .model-response-text p, .markdown p, [data-message-author-role="assistant"] .markdown p, [data-message-author-role="user"] {
                font-size: ${config.fontSize}px !important;
                line-height: 1.8 !important;
                color: var(--w-text) !important;
            }
            ${site === 'gemini'
                ? `.conversation-container, .response-container, .inner-container, .input-area-container, main [data-message-author-role], main article, main section, main .markdown, main .prose {
                max-width: ${config.maxWidth}px !important;
            }`
                : ''}
            hallucination-disclaimer, .hallucination-disclaimer, .footer-container {
                display: var(--w-footer-display) !important;
            }
            body[data-public-style="true"] .model-response-text h1,
            body[data-public-style="true"] .model-response-text h2,
            body[data-public-style="true"] .markdown h1,
            body[data-public-style="true"] .markdown h2 {
                border-left: 5px solid var(--w-pub-accent, #fbc02d) !important;
                background: linear-gradient(to right, rgba(0,0,0,0.03), transparent) !important;
                padding: 10px 15px !important;
                border-radius: 0 8px 8px 0 !important;
            }
            body[data-public-style="true"] .model-response-text strong,
            body[data-public-style="true"] .model-response-text b,
            body[data-public-style="true"] .markdown strong,
            body[data-public-style="true"] .markdown b {
                padding: 0 3px !important;
                border-radius: 4px !important;
                ${config.publicType === 'full'
                    ? 'background-color: var(--w-pub-high, rgba(255,235,59,0.6)) !important;'
                    : 'background: linear-gradient(to bottom, transparent 55%, var(--w-pub-high, rgba(255,235,59,0.6)) 0) !important;'}
            }
        `;

        const searchUsesDarkStyle = config.theme === 'dark';
        root.style.setProperty('--search-hit-bg', searchUsesDarkStyle ? '#fde047' : '#fff59d');
        root.style.setProperty('--search-hit-text', '#111827');
        root.style.setProperty('--search-hit-current-bg', searchUsesDarkStyle ? '#f59e0b' : '#f7c948');
        root.style.setProperty('--search-hit-current-text', '#111827');
        root.style.setProperty('--search-hit-outline', searchUsesDarkStyle ? 'rgba(250, 204, 21, 0.42)' : 'rgba(161, 98, 7, 0.18)');
        root.style.setProperty('--search-hit-shadow', searchUsesDarkStyle ? 'rgba(245, 158, 11, 0.34)' : 'rgba(245, 158, 11, 0.24)');

        const pubColors = {
            yellow: ['rgba(255, 235, 59, 0.6)', '#fbc02d'],
            blue: ['rgba(144, 202, 249, 0.6)', '#1976d2'],
            pink: ['rgba(244, 143, 177, 0.6)', '#d81b60'],
            green: ['rgba(165, 214, 167, 0.6)', '#388e3c'],
            orange: ['rgba(251, 146, 60, 0.5)', '#ea580c'],
            purple: ['rgba(167, 139, 250, 0.5)', '#7c3aed'],
            red: ['rgba(252, 165, 165, 0.5)', '#dc2626'],
            cyan: ['rgba(103, 232, 249, 0.5)', '#0891b2'],
            lime: ['rgba(190, 242, 100, 0.55)', '#65a30d'],
            rose: ['rgba(253, 164, 175, 0.5)', '#e11d48']
        };
        const [high, accent] = pubColors[config.publicColor] || pubColors.yellow;
        root.style.setProperty('--w-pub-high', high);
        root.style.setProperty('--w-pub-accent', accent);
    }

    // [END: 工具函数]

    // ============================================================
    // ==Logic== 业务层 - 功能模块
    // ============================================================

    // [START: NavigatorApp 组件]
    class NavigatorApp extends HTMLElement {
        // AI对话导航器主组件 - 包含UI渲染、事件处理、数据管理等功能
        constructor() {
            super();
            this.attachShadow({ mode: 'open' });
            this._isOpen = true;
            this._suppressEyeClick = false;
            this._allQuestions = [];
            this._allElements = [];
            this._allDetails = [];
            this._allQuestionIds = [];
            this._questions = [];
            this._elements = [];
            this._details = [];
            this._questionIds = [];
            this._depth = 0;
            this._readerConfig = loadReaderConfig();
            this._expandedQuestions = new Set();
            this._selectedQuestionIds = new Set();
            this._selectedPromptIds = new Set();
            this._expandedPromptIds = new Set();
            this._stickyPromptIds = new Set();
            this._activePromptPane = 'create';
            this._activePromptTag = 'all';
            this._editingPromptId = null;
            this._promptFeedbackTimer = null;
            this._searchKeyword = '';
            this._searchResults = [];
            this._searchIndex = -1;
        }

        connectedCallback() {
            this.render();
            this.loadSettings();
            this.loadPosition();
            applyReaderConfig(this._readerConfig);
            this.updateOpenState();
            this.startTracking();
            this.initDraggable();
        }

        loadSettings() {
            const savedDepth = localStorage.getItem(getDepthStorageKey());
            this._depth = savedDepth ? parseInt(savedDepth, 10) : 0;
            const input = this.shadowRoot.querySelector('#depth-input');
            if (input) input.value = this._depth;
            if (this._allQuestions.length > 0) this.applyDepth();
        }

        saveSettings() {
            localStorage.setItem(getDepthStorageKey(), String(this._depth));
        }

        loadPosition() {
            try {
                const pos = JSON.parse(localStorage.getItem(getPositionStorageKey()) || '{}');
                if (pos.top) this.style.top = pos.top;
                if (pos.right) this.style.right = pos.right;
                if (pos.left) this.style.left = pos.left;
            } catch {}
        }

        savePosition() {
            localStorage.setItem(getPositionStorageKey(), JSON.stringify({
                top: this.style.top,
                right: this.style.right,
                left: this.style.left
            }));
        }

        anchorToRight(rect = this.getBoundingClientRect()) {
            const viewportWidth = window.innerWidth || document.documentElement.clientWidth || 0;
            const anchoredRight = Math.max(0, viewportWidth - rect.right);
            this.style.left = 'auto';
            this.style.right = `${anchoredRight}px`;
            this.style.top = `${rect.top}px`;
        }

        initDraggable() {
            const header = this.shadowRoot.querySelector('.header');
            if (!header) return;

            let startX = 0;
            let startY = 0;
            let startRight = 0;
            let startTop = 0;
            let startWidth = 0;
            let dragging = false;

            const onMove = (event) => {
                dragging = true;
                const dx = event.clientX - startX;
                const dy = event.clientY - startY;
                const nextLeft = (window.innerWidth || document.documentElement.clientWidth || 0) - startRight - startWidth + dx;
                const nextRight = Math.max(0, (window.innerWidth || document.documentElement.clientWidth || 0) - (nextLeft + startWidth));
                this.style.left = 'auto';
                this.style.right = `${nextRight}px`;
                this.style.top = `${startTop + dy}px`;
            };

            const onUp = () => {
                document.removeEventListener('mousemove', onMove);
                document.removeEventListener('mouseup', onUp);
                if (dragging) {
                    this._suppressEyeClick = true;
                    this.savePosition();
                    setTimeout(() => {
                        this._suppressEyeClick = false;
                    }, 0);
                }
            };

            header.addEventListener('mousedown', (event) => {
                const eyeButton = event.target.closest('.toggle-eye');
                if (event.target.closest('.icon-btn') && !eyeButton) return;
                if (!this._isOpen && !eyeButton) return;
                dragging = false;
                startX = event.clientX;
                startY = event.clientY;
                const rect = this.getBoundingClientRect();
                startRight = Math.max(0, (window.innerWidth || document.documentElement.clientWidth || 0) - rect.right);
                startTop = rect.top;
                startWidth = rect.width;
                this.style.left = 'auto';
                this.style.right = `${startRight}px`;
                this.style.top = `${rect.top}px`;
                document.addEventListener('mousemove', onMove);
                document.addEventListener('mouseup', onUp);
            });
        }

        saveReaderSettings() {
            saveReaderConfig(this._readerConfig);
            applyReaderConfig(this._readerConfig);
            this.updateReaderUI();
        }

        getVisibleQuestionIds() {
            return this._questionIds.filter(Boolean);
        }

        toggleQuestionSelection(id, forceState = null) {
            if (!id) return;
            const shouldSelect = forceState === null ? !this._selectedQuestionIds.has(id) : !!forceState;
            if (shouldSelect) this._selectedQuestionIds.add(id);
            else this._selectedQuestionIds.delete(id);
            this.updateList();
        }

        setVisibleQuestionSelection(checked) {
            this.getVisibleQuestionIds().forEach((id) => {
                if (checked) this._selectedQuestionIds.add(id);
                else this._selectedQuestionIds.delete(id);
            });
            this.updateList();
        }

        clearQuestionSelection() {
            this._selectedQuestionIds.clear();
            this.updateList();
        }

        clearSearchHighlights() {
            const normalizeTargets = new Set();
            this._searchResults.forEach((node) => {
                if (!(node instanceof HTMLElement) || !node.isConnected) return;
                const parent = node.parentNode;
                if (!parent) return;
                parent.replaceChild(document.createTextNode(node.textContent || ''), node);
                normalizeTargets.add(parent);
            });
            normalizeTargets.forEach((node) => {
                if (typeof node.normalize === 'function') node.normalize();
            });
            this._searchResults = [];
            this._searchIndex = -1;
        }

        updateSearchResultState() {
            this._searchResults.forEach((node, index) => {
                node.classList.toggle('current-search-hit', index === this._searchIndex);
                this.applySearchHighlightStyle(node, index === this._searchIndex);
            });
        }

        getSearchHighlightPalette() {
            const rootStyle = getComputedStyle(document.documentElement);
            return {
                bg: rootStyle.getPropertyValue('--search-hit-bg').trim() || '#fff59d',
                text: rootStyle.getPropertyValue('--search-hit-text').trim() || '#111827',
                currentBg: rootStyle.getPropertyValue('--search-hit-current-bg').trim() || '#f7c948',
                currentText: rootStyle.getPropertyValue('--search-hit-current-text').trim() || '#111827',
                outline: rootStyle.getPropertyValue('--search-hit-outline').trim() || 'rgba(161, 98, 7, 0.18)',
                shadow: rootStyle.getPropertyValue('--search-hit-shadow').trim() || 'rgba(245, 158, 11, 0.24)'
            };
        }

        applySearchHighlightStyle(node, isCurrent = false) {
            if (!(node instanceof HTMLElement)) return;
            const palette = this.getSearchHighlightPalette();
            node.style.display = 'inline';
            node.style.padding = '0 1px';
            node.style.borderRadius = '3px';
            node.style.background = isCurrent ? palette.currentBg : palette.bg;
            node.style.color = isCurrent ? palette.currentText : palette.text;
            node.style.boxShadow = isCurrent
                ? `0 0 0 2px ${palette.shadow}`
                : `inset 0 0 0 1px ${palette.outline}`;
            node.style.boxDecorationBreak = 'clone';
            node.style.webkitBoxDecorationBreak = 'clone';
        }

        runSearch(keyword) {
            this.clearSearchHighlights();
            this._searchKeyword = (keyword || '').trim().toLowerCase();
            if (!this._searchKeyword) {
                this.updateSearchStatus();
                return;
            }
            const roots = this._details.flatMap((detail) => {
                const nodes = [];
                if (detail?.answerRoot) nodes.push(detail.answerRoot);
                if (detail?.questionRoot) nodes.push(detail.questionRoot);
                return nodes;
            });
            const seenRoots = new Set();
            roots.forEach((root) => {
                if (!root || seenRoots.has(root)) return;
                seenRoots.add(root);
                const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
                    acceptNode: (node) => {
                        const text = node.nodeValue || '';
                        if (!text.trim()) return NodeFilter.FILTER_REJECT;
                        const parent = node.parentElement;
                        if (!parent) return NodeFilter.FILTER_REJECT;
                        if (parent.closest('.search-hit')) return NodeFilter.FILTER_REJECT;
                        if (['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT', 'OPTION'].includes(parent.tagName)) {
                            return NodeFilter.FILTER_REJECT;
                        }
                        return text.toLowerCase().includes(this._searchKeyword)
                            ? NodeFilter.FILTER_ACCEPT
                            : NodeFilter.FILTER_REJECT;
                    }
                });
                const textNodes = [];
                let currentNode = walker.nextNode();
                while (currentNode) {
                    textNodes.push(currentNode);
                    currentNode = walker.nextNode();
                }
                textNodes.forEach((textNode) => {
                    const rawText = textNode.nodeValue || '';
                    const lowerText = rawText.toLowerCase();
                    const matchLength = this._searchKeyword.length;
                    let startIndex = 0;
                    let matchIndex = lowerText.indexOf(this._searchKeyword, startIndex);
                    if (matchIndex === -1 || !textNode.parentNode) return;
                    const fragment = document.createDocumentFragment();
                    while (matchIndex !== -1) {
                        if (matchIndex > startIndex) {
                            fragment.appendChild(document.createTextNode(rawText.slice(startIndex, matchIndex)));
                        }
                        const hit = document.createElement('span');
                        hit.className = 'search-hit';
                        hit.textContent = rawText.slice(matchIndex, matchIndex + matchLength);
                        this.applySearchHighlightStyle(hit, false);
                        fragment.appendChild(hit);
                        this._searchResults.push(hit);
                        startIndex = matchIndex + matchLength;
                        matchIndex = lowerText.indexOf(this._searchKeyword, startIndex);
                    }
                    if (startIndex < rawText.length) {
                        fragment.appendChild(document.createTextNode(rawText.slice(startIndex)));
                    }
                    textNode.parentNode.replaceChild(fragment, textNode);
                });
            });
            if (this._searchResults.length) {
                this._searchIndex = 0;
                this.updateSearchResultState();
                scrollToNode(this._searchResults[0], 'center');
            }
            this.updateSearchStatus();
        }

        stepSearch(step) {
            if (!this._searchResults.length) return;
            this._searchIndex = (this._searchIndex + step + this._searchResults.length) % this._searchResults.length;
            this.updateSearchResultState();
            scrollToNode(this._searchResults[this._searchIndex], 'center');
            this.updateSearchStatus();
        }

        updateSearchStatus() {
            if (!this.searchStatusEl) return;
            if (!this._searchKeyword) {
                this.searchStatusEl.textContent = '输入关键词后定位内容';
                return;
            }
            if (!this._searchResults.length) {
                this.searchStatusEl.textContent = '未找到结果';
                return;
            }
            this.searchStatusEl.textContent = `${this._searchIndex + 1} / ${this._searchResults.length}`;
        }

        normalizeMarkdown(md) {
            return (md || '')
                .replace(/\r\n/g, '\n')
                .replace(/\u00a0/g, ' ')
                .replace(/\n{3,}/g, '\n\n')
                .replace(/[ \t]+\n/g, '\n')
                .trim();
        }

        escapeMarkdownText(text) {
            return (text || '').replace(/\\/g, '\\\\');
        }

        escapeCodeFence(text) {
            return (text || '').replace(/```/g, '``\\`');
        }

        getCodeLanguage(preEl) {
            const code = preEl.querySelector('code');
            if (!code) return '';
            const className = code.className || '';
            const langMatch = className.match(/language-([a-zA-Z0-9_-]+)/);
            return langMatch ? langMatch[1] : '';
        }

        getOrderedListPrefix(li, listEl) {
            const start = parseInt(listEl.getAttribute('start') || '1', 10);
            const items = Array.from(listEl.children).filter(child => child.tagName === 'LI');
            const index = items.indexOf(li);
            return `${start + Math.max(index, 0)}. `;
        }

        convertTableToMarkdown(tableEl) {
            const rows = Array.from(tableEl.querySelectorAll('tr'))
                .map(row => Array.from(row.children)
                    .filter(cell => ['TH', 'TD'].includes(cell.tagName))
                    .map(cell => this.nodeToMarkdown(cell).replace(/\n+/g, '<br>').trim()))
                .filter(row => row.length > 0);

            if (!rows.length) return '';

            const header = rows[0];
            const body = rows.slice(1);
            const separator = header.map(() => '---');
            const lines = [
                `| ${header.join(' | ')} |`,
                `| ${separator.join(' | ')} |`
            ];

            body.forEach(row => {
                const normalizedRow = header.map((_, index) => row[index] || '');
                lines.push(`| ${normalizedRow.join(' | ')} |`);
            });

            return `${lines.join('\n')}\n\n`;
        }

        nodeToMarkdown(node, context = {}) {
            if (!node) return '';
            if (node.nodeType === Node.TEXT_NODE) {
                return this.escapeMarkdownText(node.textContent || '');
            }
            if (node.nodeType !== Node.ELEMENT_NODE) return '';

            const tag = node.tagName.toLowerCase();
            if (['button', 'svg', 'path', 'noscript', 'style', 'script'].includes(tag)) return '';

            if (tag === 'pre') {
                const text = (node.innerText || '').trimEnd();
                const lang = this.getCodeLanguage(node);
                return text ? `\n\`\`\`${lang}\n${this.escapeCodeFence(text)}\n\`\`\`\n\n` : '';
            }

            if (tag === 'code') {
                if (node.closest('pre')) return '';
                const inlineCode = (node.textContent || '').replace(/`/g, '\\`');
                return inlineCode ? `\`${inlineCode}\`` : '';
            }

            if (tag === 'br') return '\n';
            if (tag === 'hr') return '\n---\n\n';
            if (tag === 'img') {
                const alt = node.getAttribute('alt') || 'image';
                const src = node.getAttribute('src') || '';
                return src ? `![${alt}](${src})` : alt;
            }
            if (tag === 'a') {
                const text = this.normalizeMarkdown(Array.from(node.childNodes).map(child => this.nodeToMarkdown(child, context)).join('')) || sanitizeText(node.textContent);
                const href = node.getAttribute('href') || '';
                return href ? `[${text}](${href})` : text;
            }
            if (/^h[1-6]$/.test(tag)) {
                const level = parseInt(tag[1], 10);
                const text = this.normalizeMarkdown(Array.from(node.childNodes).map(child => this.nodeToMarkdown(child, context)).join(''));
                return text ? `${'#'.repeat(level)} ${text}\n\n` : '';
            }
            if (tag === 'blockquote') {
                const text = this.normalizeMarkdown(Array.from(node.childNodes).map(child => this.nodeToMarkdown(child, context)).join(''));
                if (!text) return '';
                return text.split('\n').map(line => (line ? `> ${line}` : '>')).join('\n') + '\n\n';
            }
            if (tag === 'ul' || tag === 'ol') {
                const items = Array.from(node.children).filter(child => child.tagName === 'LI');
                const lines = items.map(li => {
                    const content = this.normalizeMarkdown(Array.from(li.childNodes).map(child => this.nodeToMarkdown(child, { inList: true })).join(''));
                    if (!content) return '';
                    const prefix = tag === 'ol' ? this.getOrderedListPrefix(li, node) : '- ';
                    return content.split('\n').map((line, index) => (index === 0 ? `${prefix}${line}` : `  ${line}`)).join('\n');
                }).filter(Boolean);
                return lines.length ? `${lines.join('\n')}\n\n` : '';
            }
            if (tag === 'table') {
                return this.convertTableToMarkdown(node);
            }

            const childrenContent = Array.from(node.childNodes).map(child => this.nodeToMarkdown(child, context)).join('');
            const normalized = this.normalizeMarkdown(childrenContent);

            if (['p', 'div', 'section', 'article', 'li'].includes(tag)) {
                if (!normalized) return '';
                return context.inList && tag === 'li' ? normalized : `${normalized}\n\n`;
            }

            return normalized;
        }

        collectChatGPTMessages() {
            const messageWrappers = Array.from(new Set(
                queryAllDeep(CHATGPT_MESSAGE_SELECTOR).map(getClosestMessageWrapper).filter(Boolean)
            )).sort((a, b) => {
                if (a === b) return 0;
                const pos = a.compareDocumentPosition(b);
                if (pos & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
                if (pos & Node.DOCUMENT_POSITION_PRECEDING) return 1;
                return 0;
            });

            return messageWrappers.map((child, index) => {
                const roleEl = child.querySelector(CHATGPT_MESSAGE_SELECTOR) || child;
                if (!roleEl) return null;

                const role = roleEl.getAttribute('data-message-author-role');
                const contentRoot = role === 'assistant'
                    ? (child.querySelector('.markdown, [data-message-id] .markdown, .prose') || roleEl)
                    : roleEl;
                const markdown = this.normalizeMarkdown(this.nodeToMarkdown(contentRoot));

                return markdown ? {
                    index: index + 1,
                    role: role === 'user' ? 'User' : 'ChatGPT',
                    markdown
                } : null;
            }).filter(Boolean);
        }

        collectGeminiMessages() {
            const conversationContainers = queryGeminiConversationContainers();
            if (conversationContainers.length) {
                const messages = [];

                conversationContainers.forEach(container => {
                    const userNode = container.querySelector('user-query-content');
                    const assistantNode = container.querySelector('model-response');

                    if (userNode) {
                        const userContent = userNode.querySelector('.query-text, .user-query-bubble-with-background, .query-content') || userNode;
                        const userMarkdown = this.normalizeMarkdown(this.nodeToMarkdown(userContent));
                        if (userMarkdown) {
                            messages.push({
                                index: messages.length + 1,
                                role: 'User',
                                markdown: userMarkdown
                            });
                        }
                    }

                    if (assistantNode) {
                        const assistantContent = assistantNode.querySelector('.markdown.markdown-main-panel, message-content .markdown, structured-content-container, .model-response-text, .response-content') || assistantNode;
                        const assistantMarkdown = this.normalizeMarkdown(this.nodeToMarkdown(assistantContent));
                        if (assistantMarkdown) {
                            messages.push({
                                index: messages.length + 1,
                                role: 'Gemini',
                                markdown: assistantMarkdown
                            });
                        }
                    }
                });

                return messages;
            }

            const messages = [];
            const root = queryGeminiRoot();
            const users = root ? Array.from(root.querySelectorAll('user-query-content')) : [];
            const assistants = root ? Array.from(root.querySelectorAll('model-response')) : [];
            const maxLength = Math.max(users.length, assistants.length);

            if (maxLength > 0) {
                for (let index = 0; index < maxLength; index += 1) {
                    const userNode = users[index];
                    const assistantNode = assistants[index];

                    if (userNode) {
                        const userMarkdown = this.normalizeMarkdown(this.nodeToMarkdown(userNode));
                        if (userMarkdown) {
                            messages.push({
                                index: messages.length + 1,
                                role: 'User',
                                markdown: userMarkdown
                            });
                        }
                    }

                    if (assistantNode) {
                        const assistantContent = assistantNode.querySelector('message-content.model-response-text, .markdown, .model-response-text, .response-container-with-gpi') || assistantNode;
                        const assistantMarkdown = this.normalizeMarkdown(this.nodeToMarkdown(assistantContent));
                        if (assistantMarkdown) {
                            messages.push({
                                index: messages.length + 1,
                                role: 'Gemini',
                                markdown: assistantMarkdown
                            });
                        }
                    }
                }

                return messages;
            }

            return getGeminiMessageNodes().map((node, index) => {
                const role = node.matches(GEMINI_USER_SELECTOR) ? 'User' : 'Gemini';
                const contentRoot = role === 'User'
                    ? (node.querySelector('.query-text, .user-query-bubble-with-background') || node)
                    : (node.querySelector('message-content.model-response-text, .markdown, .model-response-text, .response-container-with-gpi') || node);
                const markdown = this.normalizeMarkdown(this.nodeToMarkdown(contentRoot));
                return markdown ? { index: index + 1, role, markdown } : null;
            }).filter(Boolean);
        }

        collectConversationMessages() {
            return detectSite() === 'gemini'
                ? this.collectGeminiMessages()
                : this.collectChatGPTMessages();
        }

        buildSelectedExportMessages() {
            const activeIds = this._selectedQuestionIds.size
                ? new Set(this._selectedQuestionIds)
                : new Set(this._allQuestionIds);
            const messages = [];
            this._allQuestionIds.forEach((id, index) => {
                if (!activeIds.has(id)) return;
                const detail = this._allDetails[index] || {};
                const questionRoot = detail.questionRoot || this._allElements[index];
                const answerRoot = detail.answerRoot;
                const questionMarkdown = this.normalizeMarkdown(this.nodeToMarkdown(questionRoot));
                if (questionMarkdown) messages.push({ index: messages.length + 1, role: '问题', markdown: questionMarkdown });
                const answerMarkdown = this.normalizeMarkdown(this.nodeToMarkdown(answerRoot));
                if (answerMarkdown) messages.push({ index: messages.length + 1, role: '回答', markdown: answerMarkdown });
            });
            return messages;
        }

        getConversationExportMeta() {
            const rawTitle = document.title
                .replace(/ - ChatGPT$/i, '')
                .replace(/ - Gemini$/i, '')
                .replace(/[\\/:*?"<>|]/g, '_')
                .trim();
            return {
                title: rawTitle || `${getSiteTitle()} Conversation`,
                date: new Date().toLocaleString()
            };
        }

        exportToMarkdown() {
            const messages = this.buildSelectedExportMessages();
            if (messages.length === 0) return null;

            const { title, date } = this.getConversationExportMeta();
            let mdContent = `# ${title}\n\n*导出时间: ${date}*\n\n---\n\n`;

            messages.forEach(msg => {
                mdContent += `## ${msg.index}. ${msg.role}\n\n${msg.markdown}\n\n---\n\n`;
            });

            return { title, content: mdContent };
        }

        exportToTxt() {
            const messages = this.buildSelectedExportMessages();
            if (messages.length === 0) return null;

            const { title, date } = this.getConversationExportMeta();
            const content = [
                title,
                `导出时间: ${date}`,
                '========================',
                ...messages.flatMap(msg => [
                    `${msg.index}. ${msg.role}`,
                    msg.markdown
                        .replace(/^#+\s*/gm, '')
                        .replace(/\[(.*?)\]\((.*?)\)/g, '$1 ($2)')
                        .replace(/[*_`>#-]/g, '')
                        .trim(),
                    '------------------------'
                ])
            ].join('\n\n');
            return { title, content };
        }

        exportConversation(format = 'md') {
            const payload = format === 'txt' ? this.exportToTxt() : this.exportToMarkdown();
            if (!payload) {
                alert(`未提取到有效的 ${getSiteTitle()} 对话内容,请确认页面已完整加载。`);
                return;
            }
            triggerTextDownload(
                `${payload.title}.${format === 'txt' ? 'txt' : 'md'}`,
                payload.content,
                format === 'txt' ? 'text/plain;charset=utf-8' : 'text/markdown;charset=utf-8'
            );
            this.exportOverlayEl?.classList.remove('show');
        }

        set questions(data) {
            this._allQuestions = data.questions;
            this._allElements = data.elements;
            this._allDetails = data.details || data.questions.map(() => ({ headings: [] }));
            this._allQuestionIds = data.ids || data.questions.map((_, index) => `q_${index}`);
            this.applyDepth();

            // 清理已选问题ID,只保留在新数据中仍然存在的ID(修复导出选中数据Bug)
            const validIds = new Set(this._allQuestionIds);
            this._selectedQuestionIds.forEach(id => {
                if (!validIds.has(id)) {
                    this._selectedQuestionIds.delete(id);
                }
            });

        }

        applyDepth() {
            const depth = this._depth;
            if (depth > 0) {
                this._questions = this._allQuestions.slice(-depth);
                this._elements = this._allElements.slice(-depth);
                this._details = this._allDetails.slice(-depth);
                this._questionIds = this._allQuestionIds.slice(-depth);
            } else {
                this._questions = this._allQuestions;
                this._elements = this._allElements;
                this._details = this._allDetails;
                this._questionIds = this._allQuestionIds;
            }
            this.updateList();
        }

        updateOpenState() {
            if (this._isOpen) this.setAttribute('open', '');
            else this.removeAttribute('open');
            if (this.eyeBtnEl) {
                this.eyeBtnEl.dataset.active = this._isOpen ? 'true' : 'false';
                this.eyeBtnEl.title = this._isOpen ? '收起导航' : '展开导航';
                this.eyeBtnEl.textContent = this._isOpen ? '👁️' : '👁️';
            }
        }

        render() {
            const style = document.createElement('style');
            style.textContent = cssText;

            const container = document.createElement('div');
            const shell = document.createElement('div');
            shell.className = 'app-shell';

            const panel = document.createElement('div');
            panel.className = 'panel';

            const header = document.createElement('div');
            header.className = 'header';

            const titleBlock = document.createElement('div');
            titleBlock.className = 'title-block';

            const title = document.createElement('div');
            title.className = 'title';
            title.textContent = '问题导航';

            const subtitle = document.createElement('div');
            subtitle.className = 'subtitle';
            subtitle.textContent = `${getSiteTitle()} 会话目录`;

            const headerActions = document.createElement('div');
            headerActions.className = 'header-actions';

            const exportBtn = document.createElement('button');
            exportBtn.className = 'icon-btn toggle-export';
            exportBtn.type = 'button';
            exportBtn.title = '导出 Markdown';
            exportBtn.textContent = '↓';

            const promptBtn = document.createElement('button');
            promptBtn.className = 'icon-btn toggle-prompt';
            promptBtn.type = 'button';
            promptBtn.title = '提示词';
            promptBtn.textContent = '📝';

            const exportOverlay = document.createElement('div');
            exportOverlay.className = 'settings-overlay';

            const exportModal = document.createElement('div');
            exportModal.className = 'settings-modal';

            const exportModalHead = document.createElement('div');
            exportModalHead.className = 'modal-head';

            const exportModalHeadCopy = document.createElement('div');
            exportModalHeadCopy.className = 'modal-head-copy';

            const exportModalTitle = document.createElement('div');
            exportModalTitle.className = 'modal-title';
            exportModalTitle.textContent = '导出格式';

            const closeExportModalBtn = document.createElement('button');
            closeExportModalBtn.className = 'icon-btn';
            closeExportModalBtn.type = 'button';
            closeExportModalBtn.title = '关闭导出';
            closeExportModalBtn.textContent = '✕';

            const exportStack = document.createElement('div');
            exportStack.className = 'modal-stack';

            const exportCard = createCard('选择格式');
            const exportButtonRow = document.createElement('div');
            exportButtonRow.className = 'button-row';
            const exportMdBtn = document.createElement('button');
            exportMdBtn.type = 'button';
            exportMdBtn.className = 'primary-btn';
            exportMdBtn.textContent = '导出 Markdown';
            const exportTxtBtn = document.createElement('button');
            exportTxtBtn.type = 'button';
            exportTxtBtn.className = 'ghost-btn';
            exportTxtBtn.textContent = '导出 TXT';

            const settingsBtn = document.createElement('button');
            settingsBtn.className = 'icon-btn toggle-settings';
            settingsBtn.type = 'button';
            settingsBtn.title = '设置';
            settingsBtn.textContent = '⚙️';

            const eyeBtn = document.createElement('button');
            eyeBtn.className = 'icon-btn toggle-eye';
            eyeBtn.type = 'button';
            eyeBtn.title = '收起导航';
            eyeBtn.textContent = '👁️';
            eyeBtn.dataset.active = 'true';

            const content = document.createElement('div');
            content.className = 'content';

            const listContainer = document.createElement('div');
            listContainer.className = 'list-container';

            const listEl = document.createElement('ul');
            listEl.id = 'question-list';

            const searchBar = document.createElement('div');
            searchBar.className = 'search-bar';
            const searchInput = document.createElement('input');
            searchInput.type = 'text';
            searchInput.className = 'text-input';
            searchInput.placeholder = '搜索问题或回答';
            const searchPrevBtn = document.createElement('button');
            searchPrevBtn.type = 'button';
            searchPrevBtn.className = 'icon-mini-btn';
            searchPrevBtn.title = '上一个';
            searchPrevBtn.textContent = '↑';
            const searchNextBtn = document.createElement('button');
            searchNextBtn.type = 'button';
            searchNextBtn.className = 'icon-mini-btn';
            searchNextBtn.title = '下一个';
            searchNextBtn.textContent = '↓';
            searchBar.append(searchInput, searchPrevBtn, searchNextBtn);

            const questionToolbar = document.createElement('div');
            questionToolbar.className = 'toolbar-row stack';
            const questionToolbarSummary = document.createElement('div');
            questionToolbarSummary.className = 'toolbar-summary';
            const questionSelectionCount = document.createElement('div');
            questionSelectionCount.className = 'editor-badge';
            questionSelectionCount.textContent = '已选 0 题';
            const searchStatus = document.createElement('div');
            searchStatus.className = 'helper-text';
            searchStatus.textContent = '输入关键词后定位内容';
            const questionSelectAll = document.createElement('label');
            questionSelectAll.className = 'master-check';
            const questionSelectAllInput = document.createElement('input');
            questionSelectAllInput.type = 'checkbox';
            questionSelectAllInput.className = 'select-check';
            const questionSelectAllText = document.createElement('span');
            questionSelectAllText.textContent = '全选';
            questionSelectAll.append(questionSelectAllInput, questionSelectAllText);
            const clearQuestionSelectionBtn = document.createElement('button');
            clearQuestionSelectionBtn.type = 'button';
            clearQuestionSelectionBtn.className = 'compact-btn';
            clearQuestionSelectionBtn.textContent = '取消选择';
            const questionToolbarHead = document.createElement('div');
            questionToolbarHead.className = 'toolbar-head';
            questionToolbarHead.append(questionSelectAll, clearQuestionSelectionBtn);
            questionToolbarSummary.append(questionSelectionCount, searchStatus);
            questionToolbar.append(searchBar, questionToolbarHead, questionToolbarSummary);

            const fadeBottom = document.createElement('div');
            fadeBottom.className = 'fade-bottom';

            const settingsPanel = document.createElement('div');
            settingsPanel.className = 'settings-panel';

            const settingsLabel = document.createElement('label');
            settingsLabel.textContent = '显示最近对话轮数(0 为全部)';

            const inputGroup = document.createElement('div');
            inputGroup.className = 'input-group';

            const depthInput = document.createElement('input');
            depthInput.id = 'depth-input';
            depthInput.type = 'number';
            depthInput.min = '0';
            depthInput.step = '1';
            depthInput.className = 'number-input';

            const actionGroup = document.createElement('div');
            actionGroup.className = 'action-group';

            const saveBtn = document.createElement('button');
            saveBtn.id = 'save-refresh-btn';
            saveBtn.type = 'button';
            saveBtn.textContent = '保存并刷新';

            const overlay = document.createElement('div');
            overlay.className = 'settings-overlay';

            const promptOverlay = document.createElement('div');
            promptOverlay.className = 'settings-overlay';

            const modal = document.createElement('div');
            modal.className = 'settings-modal';

            const promptModal = document.createElement('div');
            promptModal.className = 'settings-modal prompt-modal';

            const modalHead = document.createElement('div');
            modalHead.className = 'modal-head';

            const promptModalHead = document.createElement('div');
            promptModalHead.className = 'modal-head';

            const modalHeadCopy = document.createElement('div');
            modalHeadCopy.className = 'modal-head-copy';

            const promptModalHeadCopy = document.createElement('div');
            promptModalHeadCopy.className = 'modal-head-copy';

            const modalTitle = document.createElement('div');
            modalTitle.className = 'modal-title';
            modalTitle.textContent = '阅读设置';

            const promptModalTitle = document.createElement('div');
            promptModalTitle.className = 'modal-title';
            promptModalTitle.textContent = '提示词';

            const closeModalBtn = document.createElement('button');
            closeModalBtn.className = 'icon-btn';
            closeModalBtn.type = 'button';
            closeModalBtn.title = '关闭设置';
            closeModalBtn.textContent = '✕';

            const closePromptModalBtn = document.createElement('button');
            closePromptModalBtn.className = 'icon-btn';
            closePromptModalBtn.type = 'button';
            closePromptModalBtn.title = '关闭提示词';
            closePromptModalBtn.textContent = '✕';

            const modalGrid = document.createElement('div');
            modalGrid.className = 'modal-grid';

            const promptWorkspace = document.createElement('div');
            promptWorkspace.className = 'prompt-workspace';

            const promptPaneNav = document.createElement('div');
            promptPaneNav.className = 'prompt-pane-nav';

            const promptCreatePaneBtn = document.createElement('button');
            promptCreatePaneBtn.type = 'button';
            promptCreatePaneBtn.className = 'prompt-pane-btn active';
            promptCreatePaneBtn.dataset.pane = 'create';
            const promptCreatePaneTitle = document.createElement('strong');
            promptCreatePaneTitle.textContent = '新增提示词';
            const promptCreatePaneDesc = document.createElement('span');
            promptCreatePaneDesc.textContent = '新建或覆盖保存提示词';
            promptCreatePaneBtn.append(promptCreatePaneTitle, promptCreatePaneDesc);

            const promptBrowsePaneBtn = document.createElement('button');
            promptBrowsePaneBtn.type = 'button';
            promptBrowsePaneBtn.className = 'prompt-pane-btn';
            promptBrowsePaneBtn.dataset.pane = 'browse';
            const promptBrowsePaneTitle = document.createElement('strong');
            promptBrowsePaneTitle.textContent = '已保存提示词';
            const promptBrowsePaneDesc = document.createElement('span');
            promptBrowsePaneDesc.textContent = '搜索、展开、复制和管理';
            promptBrowsePaneBtn.append(promptBrowsePaneTitle, promptBrowsePaneDesc);

            const promptPaneContent = document.createElement('div');
            promptPaneContent.className = 'prompt-pane-content';

            const promptCreatePane = document.createElement('div');
            promptCreatePane.className = 'prompt-pane active';
            promptCreatePane.dataset.pane = 'create';

            const promptBrowsePane = document.createElement('div');
            promptBrowsePane.className = 'prompt-pane prompt-browse-pane';
            promptBrowsePane.dataset.pane = 'browse';

            function createCard(labelText) {
                const card = document.createElement('div');
                card.className = 'setting-card';
                const label = document.createElement('div');
                label.className = 'setting-title';
                label.textContent = labelText;
                card.appendChild(label);
                return { card, label };
            }

            const promptFormCard = createCard('新增提示词');

            const promptTitleField = document.createElement('div');
            promptTitleField.className = 'field-block';
            const promptTitleLabel = document.createElement('label');
            promptTitleLabel.className = 'field-label';
            promptTitleLabel.textContent = '提示词标题';
            const promptTitleInput = document.createElement('input');
            promptTitleInput.type = 'text';
            promptTitleInput.className = 'text-input';
            promptTitleInput.placeholder = '例如:论文润色';
            promptTitleField.append(promptTitleLabel, promptTitleInput);

            const promptGroupField = document.createElement('div');
            promptGroupField.className = 'field-block';
            const promptGroupLabel = document.createElement('label');
            promptGroupLabel.className = 'field-label';
            promptGroupLabel.textContent = '提示词标签';
            const promptGroupInput = document.createElement('input');
            promptGroupInput.type = 'text';
            promptGroupInput.className = 'text-input';
            promptGroupInput.placeholder = '例如:写作';
            promptGroupInput.setAttribute('list', 'prompt-tag-options');
            const promptTagOptions = document.createElement('datalist');
            promptTagOptions.id = 'prompt-tag-options';
            promptGroupField.append(promptGroupLabel, promptGroupInput);

            const promptContentField = document.createElement('div');
            promptContentField.className = 'field-block';
            const promptContentLabel = document.createElement('label');
            promptContentLabel.className = 'field-label';
            promptContentLabel.textContent = '提示词内容';
            const promptContentInput = document.createElement('textarea');
            promptContentInput.className = 'text-area';
            promptContentInput.placeholder = '输入要保存的提示词内容';
            promptContentField.append(promptContentLabel, promptContentInput);

            const promptStatusLine = document.createElement('div');
            promptStatusLine.className = 'status-line';
            promptStatusLine.textContent = '支持新增、覆盖保存和导入自动去重。';

            const promptEditorMeta = document.createElement('div');
            promptEditorMeta.className = 'editor-meta';
            const promptEditorBadge = document.createElement('div');
            promptEditorBadge.className = 'editor-badge';
            promptEditorBadge.textContent = '当前模式:新增';
            const promptCancelEditBtn = document.createElement('button');
            promptCancelEditBtn.type = 'button';
            promptCancelEditBtn.className = 'ghost-btn';
            promptCancelEditBtn.textContent = '取消编辑';
            promptCancelEditBtn.style.display = 'none';

            const promptSaveBtn = document.createElement('button');
            promptSaveBtn.type = 'button';
            promptSaveBtn.className = 'primary-btn';
            promptSaveBtn.textContent = '新增并保存';
            const promptClearBtn = document.createElement('button');
            promptClearBtn.type = 'button';
            promptClearBtn.className = 'ghost-btn';
            promptClearBtn.textContent = '一键清空';
            const promptActionRow = document.createElement('div');
            promptActionRow.className = 'button-row';

            const promptTransferOverlay = document.createElement('div');
            promptTransferOverlay.className = 'settings-overlay';

            const promptTransferModal = document.createElement('div');
            promptTransferModal.className = 'settings-modal prompt-modal';

            const promptTransferHead = document.createElement('div');
            promptTransferHead.className = 'modal-head';

            const promptTransferHeadCopy = document.createElement('div');
            promptTransferHeadCopy.className = 'modal-head-copy';

            const promptTransferTitle = document.createElement('div');
            promptTransferTitle.className = 'modal-title';
            promptTransferTitle.textContent = '提示词导入导出';

            const closePromptTransferBtn = document.createElement('button');
            closePromptTransferBtn.className = 'icon-btn';
            closePromptTransferBtn.type = 'button';
            closePromptTransferBtn.title = '关闭导入导出';
            closePromptTransferBtn.textContent = '✕';

            const promptSearchWrap = document.createElement('div');
            promptSearchWrap.className = 'search-row';
            const promptSearchField = document.createElement('div');
            promptSearchField.className = 'field-block';
            const promptSearchLabel = document.createElement('label');
            promptSearchLabel.className = 'field-label';
            promptSearchLabel.textContent = '搜索提示词';
            const promptSearchInput = document.createElement('input');
            promptSearchInput.type = 'text';
            promptSearchInput.className = 'text-input';
            promptSearchInput.placeholder = '按标题、标签或内容搜索';
            promptSearchField.append(promptSearchLabel, promptSearchInput);

            const promptExportFileBtn = document.createElement('button');
            promptExportFileBtn.type = 'button';
            promptExportFileBtn.className = 'icon-mini-btn';
            promptExportFileBtn.title = '导出提示词(未选则导出全部)';
            promptExportFileBtn.textContent = '📥';
            const promptImportFileBtn = document.createElement('button');
            promptImportFileBtn.type = 'button';
            promptImportFileBtn.className = 'icon-mini-btn';
            promptImportFileBtn.title = '导入提示词文件';
            promptImportFileBtn.textContent = '📤';

            const promptFileInput = document.createElement('input');
            promptFileInput.type = 'file';
            promptFileInput.accept = '.json';
            promptFileInput.className = 'sr-file';

            const promptTransferField = document.createElement('div');
            promptTransferField.className = 'drop-zone';
            promptTransferField.textContent = '将提示词导出文件拖到这里导入,或点击下方按钮选择文件';

            const promptTransferActions = document.createElement('div');
            promptTransferActions.className = 'button-row';
            const promptTransferHint = document.createElement('div');
            promptTransferHint.className = 'helper-text';
            promptTransferHint.textContent = '导入 JSON 文件,支持旧版数组和 { version, prompts } 格式。';
            const promptChooseFileBtn = document.createElement('button');
            promptChooseFileBtn.type = 'button';
            promptChooseFileBtn.className = 'primary-btn';
            promptChooseFileBtn.textContent = '选择文件';

            const promptListCard = createCard('已保存提示词');
            promptListCard.card.classList.add('prompt-list-card');
            const promptListTools = document.createElement('div');
            promptListTools.className = 'modal-stack prompt-list-tools';
            const promptToolbar = document.createElement('div');
            promptToolbar.className = 'selection-row';
            const promptSelectionCount = document.createElement('div');
            promptSelectionCount.className = 'editor-badge';
            promptSelectionCount.textContent = '已选 0 条';
            const promptSelectAll = document.createElement('label');
            promptSelectAll.className = 'master-check checkbox-only';
            promptSelectAll.title = '全选';
            promptSelectAll.setAttribute('aria-label', '全选');
            const promptSelectAllInput = document.createElement('input');
            promptSelectAllInput.type = 'checkbox';
            promptSelectAllInput.className = 'select-check';
            const promptSelectAllText = document.createElement('span');
            promptSelectAllText.textContent = '全选';
            promptSelectAll.append(promptSelectAllInput, promptSelectAllText);
            const promptClearSelectionBtn = document.createElement('button');
            promptClearSelectionBtn.type = 'button';
            promptClearSelectionBtn.className = 'compact-btn';
            promptClearSelectionBtn.textContent = '取消选择';
            const promptBatchDeleteBtn = document.createElement('button');
            promptBatchDeleteBtn.type = 'button';
            promptBatchDeleteBtn.className = 'mini-btn danger';
            promptBatchDeleteBtn.textContent = '批量删除';
            const promptListHint = document.createElement('div');
            promptListHint.className = 'helper-text';
            promptListHint.textContent = '未选择时默认导出全部。';
            const promptTabBar = document.createElement('div');
            promptTabBar.className = 'prompt-tab-bar';
            const promptList = document.createElement('div');
            promptList.className = 'prompt-list';

            const cleanCard = createCard('净化阅读');
            const cleanButton = document.createElement('button');
            cleanButton.type = 'button';
            cleanButton.className = 'ghost-btn';

            const themeCard = createCard('背景主题');
            themeCard.card.classList.add('span-2');
            const themeChoices = document.createElement('div');
            themeChoices.className = 'setting-field';

            const fontSizeCard = createCard('字体大小');
            const fontSizeRow = document.createElement('div');
            fontSizeRow.className = 'setting-inline';
            const fontSizeUnit = document.createElement('span');
            fontSizeUnit.textContent = 'px';
            const fontSizeInput = document.createElement('input');
            fontSizeInput.type = 'number';
            fontSizeInput.className = 'number-input';

            const widthCard = createCard('阅读宽度');
            const widthRow = document.createElement('div');
            widthRow.className = 'setting-inline';
            const widthUnit = document.createElement('span');
            widthUnit.textContent = 'px';
            const widthInput = document.createElement('input');
            widthInput.type = 'number';
            widthInput.className = 'number-input';

            const fontCard = createCard('字体风格');
            const fontChoices = document.createElement('div');
            fontChoices.className = 'setting-field';

            const depthCard = createCard('显示对话轮数');
            const depthRow = document.createElement('div');
            depthRow.className = 'setting-inline';

            const switchCard = createCard('开关选项');
            switchCard.card.classList.add('span-2');
            const switchPair = document.createElement('div');
            switchPair.className = 'switch-pair';
            const footerRow = document.createElement('div');
            footerRow.className = 'switch-item';
            const footerTitle = document.createElement('span');
            footerTitle.textContent = '隐藏底部免责声明';
            const footerCheck = document.createElement('input');
            footerCheck.type = 'checkbox';

            const publicRow = document.createElement('div');
            publicRow.className = 'switch-item';
            const publicTitle = document.createElement('span');
            publicTitle.textContent = '公众号排版风格';
            const publicCheck = document.createElement('input');
            publicCheck.type = 'checkbox';

            const publicStyleCard = createCard('公众号高亮样式');
            publicStyleCard.card.classList.add('span-2');
            const publicStyleRow = document.createElement('div');
            publicStyleRow.className = 'style-row';
            const styleDots = document.createElement('div');
            styleDots.className = 'style-dots';
            const typeSwitch = document.createElement('div');
            typeSwitch.className = 'type-switch';

            const layoutStack = document.createElement('div');
            layoutStack.className = 'setting-stack';
            const rowFonts = document.createElement('div');
            rowFonts.className = 'setting-row-2';
            const rowWidthDepth = document.createElement('div');
            rowWidthDepth.className = 'setting-row-2';
            const rowModeClean = document.createElement('div');
            rowModeClean.className = 'setting-stack';

            titleBlock.append(title, subtitle);
            listContainer.append(questionToolbar, listEl, fadeBottom);
            inputGroup.appendChild(depthInput);
            actionGroup.appendChild(saveBtn);
            settingsPanel.append(settingsLabel, inputGroup, actionGroup);
            content.append(listContainer, settingsPanel);
            fontSizeRow.append(fontSizeInput, fontSizeUnit);
            widthRow.append(widthInput, widthUnit);
            depthRow.append(depthInput, saveBtn);
            footerRow.append(footerTitle, footerCheck);
            publicRow.append(publicTitle, publicCheck);
            switchPair.append(footerRow, publicRow);
            themeCard.card.appendChild(themeChoices);
            fontSizeCard.card.appendChild(fontSizeRow);
            widthCard.card.appendChild(widthRow);
            fontCard.card.appendChild(fontChoices);
            depthCard.card.appendChild(depthRow);
            switchCard.card.appendChild(switchPair);
            publicStyleCard.card.append(publicStyleRow);
            publicStyleRow.append(styleDots, typeSwitch);
            promptGroupField.appendChild(promptTagOptions);
            promptActionRow.append(promptSaveBtn, promptClearBtn);
            promptEditorMeta.append(promptEditorBadge, promptCancelEditBtn);
            promptFormCard.card.append(promptTitleField, promptGroupField, promptContentField, promptStatusLine, promptEditorMeta, promptActionRow);
            promptTransferActions.append(promptChooseFileBtn);
            promptBatchDeleteBtn.className = 'compact-btn danger-btn';
            promptToolbar.append(promptSelectAll, promptClearSelectionBtn, promptBatchDeleteBtn, promptSelectionCount);
            promptSearchWrap.append(promptSearchField, promptExportFileBtn, promptImportFileBtn);
            promptListTools.append(promptSearchWrap, promptTabBar, promptToolbar, promptListHint);
            promptListCard.card.append(promptListTools, promptList);
            promptCreatePane.append(promptFormCard.card);
            promptBrowsePane.append(promptListCard.card);
            promptPaneNav.append(promptCreatePaneBtn, promptBrowsePaneBtn);
            promptPaneContent.append(promptCreatePane, promptBrowsePane);
            promptWorkspace.append(promptPaneNav, promptPaneContent);
            headerActions.append(exportBtn, promptBtn, settingsBtn, eyeBtn);
            modalHeadCopy.append(modalTitle);
            modalHead.append(modalHeadCopy, closeModalBtn);
            promptModalHeadCopy.append(promptModalTitle);
            promptModalHead.append(promptModalHeadCopy, closePromptModalBtn);
            cleanCard.card.append(cleanButton);
            rowFonts.append(fontSizeCard.card, fontCard.card);
            rowWidthDepth.append(widthCard.card, depthCard.card);
            cleanCard.card.classList.add('span-2');
            rowModeClean.append(cleanCard.card);
            layoutStack.append(themeCard.card, rowFonts, rowWidthDepth, rowModeClean, switchCard.card, publicStyleCard.card);
            modalGrid.append(layoutStack);
            modal.append(modalHead, modalGrid);
            exportButtonRow.append(exportMdBtn, exportTxtBtn);
            exportCard.card.append(exportButtonRow);
            exportStack.append(exportCard.card);
            exportModalHeadCopy.append(exportModalTitle);
            exportModalHead.append(exportModalHeadCopy, closeExportModalBtn);
            exportModal.append(exportModalHead, exportStack);
            promptTransferHeadCopy.append(promptTransferTitle);
            promptTransferHead.append(promptTransferHeadCopy, closePromptTransferBtn);
            promptTransferModal.append(promptTransferHead, promptTransferField, promptTransferHint, promptTransferActions, promptFileInput);
            exportOverlay.appendChild(exportModal);
            promptModal.append(promptModalHead, promptWorkspace);
            overlay.appendChild(modal);
            promptOverlay.appendChild(promptModal);
            promptTransferOverlay.appendChild(promptTransferModal);
            header.append(titleBlock, headerActions);
            panel.append(header, content);
            shell.append(panel);
            container.appendChild(shell);
            container.appendChild(exportOverlay);
            container.appendChild(overlay);
            container.appendChild(promptOverlay);
            container.appendChild(promptTransferOverlay);

            this.exportOverlayEl = exportOverlay;
            this.overlayEl = overlay;
            this.promptOverlayEl = promptOverlay;
            this.promptTransferOverlayEl = promptTransferOverlay;
            this.depthInputEl = depthInput;
            this.fontSizeInputEl = fontSizeInput;
            this.widthInputEl = widthInput;
            this.footerCheckEl = footerCheck;
            this.publicCheckEl = publicCheck;
            this.themeChoicesEl = themeChoices;
            this.fontChoicesEl = fontChoices;
            this.styleDotsEl = styleDots;
            this.typeSwitchEl = typeSwitch;
            this.publicStyleCardEl = publicStyleCard.card;
            this.eyeBtnEl = eyeBtn;
            this.promptTitleInputEl = promptTitleInput;
            this.promptGroupInputEl = promptGroupInput;
            this.promptTagOptionsEl = promptTagOptions;
            this.promptContentInputEl = promptContentInput;
            this.promptStatusLineEl = promptStatusLine;
            this.promptEditorBadgeEl = promptEditorBadge;
            this.promptCancelEditBtnEl = promptCancelEditBtn;
            this.promptSearchInputEl = promptSearchInput;
            this.promptFileInputEl = promptFileInput;
            this.promptListEl = promptList;
            this.promptTabBarEl = promptTabBar;
            this.promptSelectionCountEl = promptSelectionCount;
            this.promptListHintEl = promptListHint;
            this.promptSelectAllInputEl = promptSelectAllInput;
            this.cleanButtonEl = cleanButton;
            this.promptSaveBtnEl = promptSaveBtn;
            this.promptCreatePaneBtnEl = promptCreatePaneBtn;
            this.promptBrowsePaneBtnEl = promptBrowsePaneBtn;
            this.promptCreatePaneEl = promptCreatePane;
            this.promptBrowsePaneEl = promptBrowsePane;
            this.searchInputEl = searchInput;
            this.searchStatusEl = searchStatus;
            this.questionSelectAllEl = questionSelectAllInput;
            this.questionSelectionCountEl = questionSelectionCount;

            eyeBtn.onclick = (event) => {
                if (this._suppressEyeClick) {
                    event.stopPropagation();
                    return;
                }
                if (this._isOpen) {
                    this.anchorToRight();
                }
                this._isOpen = !this._isOpen;
                if (!this._isOpen) {
                    settingsPanel.classList.remove('show');
                    exportOverlay.classList.remove('show');
                    overlay.classList.remove('show');
                    promptOverlay.classList.remove('show');
                    promptTransferOverlay.classList.remove('show');
                }
                this.updateOpenState();
                event.stopPropagation();
            };
            depthInput.oninput = (event) => {
                const val = parseInt(event.target.value, 10);
                if (!Number.isNaN(val) && val >= 0) {
                    this._depth = val;
                }
            };

            saveBtn.onclick = () => {
                this.saveSettings();
                location.reload();
            };

            exportBtn.onclick = (event) => {
                exportOverlay.classList.add('show');
                event.stopPropagation();
            };

            promptBtn.onclick = (event) => {
                promptOverlay.classList.add('show');
                this.renderPromptLibrary();
                event.stopPropagation();
            };

            settingsBtn.onclick = (event) => {
                overlay.classList.add('show');
                event.stopPropagation();
            };

            closeExportModalBtn.onclick = () => exportOverlay.classList.remove('show');
            closeModalBtn.onclick = () => overlay.classList.remove('show');
            closePromptModalBtn.onclick = () => promptOverlay.classList.remove('show');
            closePromptTransferBtn.onclick = () => promptTransferOverlay.classList.remove('show');
            exportOverlay.onclick = (event) => {
                if (event.target === exportOverlay) exportOverlay.classList.remove('show');
            };
            overlay.onclick = (event) => {
                if (event.target === overlay) overlay.classList.remove('show');
            };
            promptOverlay.onclick = (event) => {
                if (event.target === promptOverlay) promptOverlay.classList.remove('show');
            };
            promptTransferOverlay.onclick = (event) => {
                if (event.target === promptTransferOverlay) promptTransferOverlay.classList.remove('show');
            };
            promptCreatePaneBtn.onclick = () => this.switchPromptPane('create');
            promptBrowsePaneBtn.onclick = () => this.switchPromptPane('browse');

            exportMdBtn.onclick = () => this.exportConversation('md');
            exportTxtBtn.onclick = () => this.exportConversation('txt');
            promptSaveBtn.onclick = () => {
                this.savePromptEntry();
            };
            promptClearBtn.onclick = () => this.clearPromptEditor();
            promptCancelEditBtn.onclick = () => this.clearPromptEditor();
            promptSearchInput.oninput = () => this.renderPromptLibrary();
            promptExportFileBtn.onclick = () => this.exportPromptLibraryFile();
            promptImportFileBtn.onclick = () => promptTransferOverlay.classList.add('show');
            promptChooseFileBtn.onclick = () => promptFileInput.click();
            promptSelectAllInput.onchange = () => this.setVisiblePromptSelection(promptSelectAllInput.checked);
            promptClearSelectionBtn.onclick = () => this.clearPromptSelection();
            promptBatchDeleteBtn.onclick = () => this.removeSelectedPromptEntries();
            promptFileInput.onchange = (event) => this.importPromptLibraryFile(event.target.files?.[0]);
            promptTransferField.ondragover = (event) => {
                event.preventDefault();
                promptTransferField.classList.add('dragover');
            };
            promptTransferField.ondragleave = () => promptTransferField.classList.remove('dragover');
            promptTransferField.ondrop = (event) => {
                event.preventDefault();
                promptTransferField.classList.remove('dragover');
                this.importPromptLibraryFile(event.dataTransfer?.files?.[0]);
            };
            cleanButton.onclick = () => {
                this._readerConfig.cleanMode = !this._readerConfig.cleanMode;
                this.saveReaderSettings();
            };
            questionSelectAllInput.onchange = () => this.setVisibleQuestionSelection(questionSelectAllInput.checked);
            clearQuestionSelectionBtn.onclick = () => this.clearQuestionSelection();
            searchInput.oninput = () => this.runSearch(searchInput.value);
            searchPrevBtn.onclick = () => this.stepSearch(-1);
            searchNextBtn.onclick = () => this.stepSearch(1);

            listEl.onclick = (event) => {
                const li = event.target.closest('li');
                if (!li) return;
                const idx = Number(li.dataset.index);
                if (idx < 0) return;
                const targetEl = this._elements[idx];
                if (!targetEl) return;
                if (!isChatGPTSharePage()) targetEl.style.scrollMarginTop = '56px';
                targetEl.scrollIntoView({ behavior: 'smooth', block: 'start' });
            };

            this.shadowRoot.append(style, container);
            this.listEl = listEl;
            this.buildReaderControls();
            this.updateReaderUI();
            this.renderPromptLibrary();
            this.updateOpenState();
        }

        updateList() {
            if (!this.listEl) return;
            this.listEl.replaceChildren();
            const visibleIds = this.getVisibleQuestionIds();
            if (this.questionSelectAllEl) {
                this.questionSelectAllEl.checked = !!visibleIds.length && visibleIds.every((id) => this._selectedQuestionIds.has(id));
            }
            if (this.questionSelectionCountEl) {
                this.questionSelectionCountEl.textContent = `已选 ${this._selectedQuestionIds.size} 题`;
            }
            if (this._questions.length === 0) {
                const placeholder = detectSite() === 'gemini'
                    ? 'Gemini 对话加载中...'
                    : '暂未识别到问题';
                const li = document.createElement('li');
                li.dataset.index = '-1';
                li.title = placeholder;
                li.textContent = placeholder;
                this.listEl.appendChild(li);
                this.updateActiveItem();
                return;
            }
            this._questions.forEach((question, index) => {
                const li = document.createElement('li');
                li.className = 'question-item';
                li.dataset.index = String(index);

                const row = document.createElement('div');
                row.className = 'question-row';
                row.title = question;

                const checkbox = document.createElement('input');
                checkbox.type = 'checkbox';
                checkbox.className = 'select-check';
                checkbox.checked = this._selectedQuestionIds.has(this._questionIds[index]);
                checkbox.onclick = (event) => {
                    event.stopPropagation();
                    this.toggleQuestionSelection(this._questionIds[index], checkbox.checked);
                };

                const text = document.createElement('div');
                text.className = 'question-text';
                text.textContent = `问题:${question}`;

                const detail = this._details[index];
                const hasHeadings = !!detail?.headings?.length || !!detail?.answerRoot;
                const expandBtn = document.createElement('button');
                expandBtn.type = 'button';
                expandBtn.className = 'expand-btn';
                expandBtn.textContent = hasHeadings ? (this._expandedQuestions.has(index) ? '▼' : '▶') : '·';
                expandBtn.disabled = !hasHeadings;

                const answerTree = document.createElement('div');
                answerTree.className = 'answer-tree';
                answerTree.style.display = this._expandedQuestions.has(index) ? 'flex' : 'none';

                if (this._expandedQuestions.has(index) && hasHeadings) {
                    if (!detail.headings?.length && detail?.answerRoot) {
                        const child = document.createElement('div');
                        child.className = 'answer-node';
                        child.style.paddingLeft = '10px';
                        child.textContent = '回答';
                        child.onclick = (event) => {
                            event.stopPropagation();
                            scrollToNode(detail.answerRoot, 'start');
                        };
                        answerTree.appendChild(child);
                    }
                    detail.headings.forEach((item) => {
                        const child = document.createElement('div');
                        child.className = 'answer-node';
                        child.style.paddingLeft = `${10 + Math.max(item.level - 1, 0) * 14}px`;
                        child.textContent = `${item.level === 1 ? '回答:' : ''}${item.text}`;
                        child.onclick = (event) => {
                            event.stopPropagation();
                            scrollToNode(item.target, 'start');
                        };
                        answerTree.appendChild(child);
                    });
                }

                expandBtn.onclick = (event) => {
                    event.stopPropagation();
                    if (!hasHeadings) return;
                    if (this._expandedQuestions.has(index)) {
                        this._expandedQuestions.delete(index);
                    } else {
                        this._expandedQuestions.add(index);
                    }
                    this.updateList();
                };

                row.onclick = () => {
                    const targetEl = this._elements[index];
                    if (!targetEl) return;
                    scrollToNode(targetEl, 'start');
                };

                row.append(checkbox, text, expandBtn);
                li.append(row, answerTree);
                this.listEl.appendChild(li);
            });
            this.updateActiveItem();
        }

        buildReaderControls() {
            const themeOptions = [
                ['default', '默认 · 跟随页面'],
                ['white', '白纸 · 纯白背景'],
                ['yellow', '米黄 · 护眼暖色'],
                ['green', '青绿 · 柔和阅读'],
                ['sepia', '复古 · 偏暖纸感'],
                ['gray', '浅灰 · 低对比'],
                ['dark', '暗夜 · 深色阅读']
            ];
            const fontOptions = [
                ['yahei', '微软雅黑'],
                ['songti', '宋体'],
                ['heiti', '黑体'],
                ['kaiti', '楷体'],
                ['fangsong', '仿宋'],
                ['rounded', '圆体'],
                ['lishu', '隶书'],
                ['youyuan', '幼圆'],
                ['mono', '等宽']
            ];
            const styleColors = [
                ['yellow', '#fdd835'],
                ['blue', '#64b5f6'],
                ['pink', '#f06292'],
                ['green', '#81c784'],
                ['orange', '#fb923c'],
                ['purple', '#a78bfa'],
                ['red', '#f87171'],
                ['cyan', '#22d3ee'],
                ['lime', '#a3e635'],
                ['rose', '#fb7185']
            ];
            const pubTypes = [
                ['half', '半高亮'],
                ['full', '全高亮']
            ];

            this.themeChoicesEl.replaceChildren();
            this.fontChoicesEl.replaceChildren();
            this.styleDotsEl.replaceChildren();
            this.typeSwitchEl.replaceChildren();

            const themeSelect = document.createElement('select');
            themeSelect.className = 'field-select';
            themeOptions.forEach(([value, label]) => {
                const option = document.createElement('option');
                option.value = value;
                option.textContent = label;
                themeSelect.appendChild(option);
            });
            themeSelect.onchange = () => {
                this._readerConfig.theme = themeSelect.value;
                this.saveReaderSettings();
            };
            this.themeChoicesEl.appendChild(themeSelect);
            this.themeSelectEl = themeSelect;

            const fontSelect = document.createElement('select');
            fontSelect.className = 'field-select';
            fontOptions.forEach(([value, label]) => {
                const option = document.createElement('option');
                option.value = value;
                option.textContent = label;
                fontSelect.appendChild(option);
            });
            fontSelect.onchange = () => {
                this._readerConfig.fontType = fontSelect.value;
                this.saveReaderSettings();
            };
            this.fontChoicesEl.appendChild(fontSelect);
            this.fontSelectEl = fontSelect;

            styleColors.forEach(([value, color]) => {
                const dot = document.createElement('button');
                dot.type = 'button';
                dot.className = 'style-dot';
                dot.dataset.value = value;
                dot.style.backgroundColor = color;
                dot.title = value;
                dot.onclick = () => {
                    this._readerConfig.publicColor = value;
                    this.saveReaderSettings();
                };
                this.styleDotsEl.appendChild(dot);
            });

            pubTypes.forEach(([value, label]) => {
                const btn = document.createElement('button');
                btn.type = 'button';
                btn.className = 'type-btn';
                btn.dataset.value = value;
                btn.textContent = label;
                btn.onclick = () => {
                    this._readerConfig.publicType = value;
                    this.saveReaderSettings();
                };
                this.typeSwitchEl.appendChild(btn);
            });

            this.fontSizeInputEl.oninput = () => {
                const value = parseInt(this.fontSizeInputEl.value, 10);
                if (!Number.isNaN(value) && value > 0) {
                    this._readerConfig.fontSize = value;
                    this.saveReaderSettings();
                }
            };
            this.widthInputEl.oninput = () => {
                const value = parseInt(this.widthInputEl.value, 10);
                if (!Number.isNaN(value) && value > 0) {
                    this._readerConfig.maxWidth = value;
                    this.saveReaderSettings();
                }
            };
            this.depthInputEl.oninput = (event) => {
                const val = parseInt(event.target.value, 10);
                if (!Number.isNaN(val) && val >= 0) {
                    this._depth = val;
                    this.updateReaderUI();
                }
            };
            this.footerCheckEl.onchange = () => {
                this._readerConfig.hideFooter = this.footerCheckEl.checked;
                this.saveReaderSettings();
            };
            this.publicCheckEl.onchange = () => {
                this._readerConfig.publicStyle = this.publicCheckEl.checked;
                this.saveReaderSettings();
            };
            this.updateReaderUI();
        }

        updateReaderUI() {
            if (!this.themeChoicesEl) return;
            if (this.themeSelectEl) this.themeSelectEl.value = this._readerConfig.theme;
            if (this.fontSelectEl) this.fontSelectEl.value = this._readerConfig.fontType;
            this.styleDotsEl.querySelectorAll('.style-dot').forEach((btn) => btn.classList.toggle('active', btn.dataset.value === this._readerConfig.publicColor));
            this.typeSwitchEl.querySelectorAll('.type-btn').forEach((btn) => btn.classList.toggle('active', btn.dataset.value === this._readerConfig.publicType));
            this.fontSizeInputEl.value = String(this._readerConfig.fontSize);
            this.widthInputEl.value = String(this._readerConfig.maxWidth);
            this.depthInputEl.value = String(this._depth);
            this.footerCheckEl.checked = !!this._readerConfig.hideFooter;
            this.publicCheckEl.checked = !!this._readerConfig.publicStyle;
            this.publicStyleCardEl.style.display = this._readerConfig.publicStyle ? 'block' : 'none';
            if (this.cleanButtonEl) {
                this.cleanButtonEl.textContent = this._readerConfig.cleanMode ? '关闭净化阅读' : '开启净化阅读';
            }
        }
        updatePromptTagOptions() {
            if (!this.promptTagOptionsEl) return;
            this.promptTagOptionsEl.replaceChildren();
            const tags = Array.from(new Set(loadPromptLibrary().map(item => (item.group || '').trim()).filter(Boolean))).sort();
            tags.forEach((tag) => {
                const option = document.createElement('option');
                option.value = tag;
                this.promptTagOptionsEl.appendChild(option);
            });
        }

        getVisiblePromptEntries() {
            const prompts = loadPromptLibrary();
            const activeTag = this._activePromptTag || 'all';
            const keyword = (this.promptSearchInputEl?.value || '').trim().toLowerCase();
            const scopedPrompts = activeTag === 'all'
                ? prompts
                : prompts.filter((item) => (item.group || '未标记') === activeTag);
            return !keyword
                ? scopedPrompts
                : scopedPrompts.filter(item => [item.title, item.group, item.content].join('\n').toLowerCase().includes(keyword));
        }

        getPromptTabItems(prompts = loadPromptLibrary()) {
            const tags = Array.from(new Set(prompts.map(item => (item.group || '').trim() || '未标记'))).sort((a, b) => {
                if (a === '未标记') return -1;
                if (b === '未标记') return 1;
                return a.localeCompare(b, 'zh-Hans-CN');
            });
            return ['all', ...tags];
        }

        renderPromptTabs(prompts = loadPromptLibrary()) {
            if (!this.promptTabBarEl) return;
            const tabs = this.getPromptTabItems(prompts);
            if (!tabs.includes(this._activePromptTag)) {
                this._activePromptTag = 'all';
            }
            this.promptTabBarEl.replaceChildren();
            tabs.forEach((tabValue) => {
                const btn = document.createElement('button');
                btn.type = 'button';
                btn.className = 'prompt-tab-btn';
                btn.textContent = tabValue === 'all' ? '全部' : tabValue;
                btn.classList.toggle('active', tabValue === this._activePromptTag);
                btn.onclick = () => {
                    this._activePromptTag = tabValue;
                    this.renderPromptLibrary();
                };
                this.promptTabBarEl.appendChild(btn);
            });
        }

        prunePromptSelection(prompts = loadPromptLibrary()) {
            const validIds = new Set(prompts.map(item => item.id));
            Array.from(this._selectedPromptIds).forEach((id) => {
                if (!validIds.has(id)) this._selectedPromptIds.delete(id);
            });
        }

        setPromptFeedback(message, tone = 'default', persist = false) {
            if (!this.promptStatusLineEl) return;
            this.promptStatusLineEl.textContent = message;
            if (tone === 'default') this.promptStatusLineEl.removeAttribute('data-tone');
            else this.promptStatusLineEl.setAttribute('data-tone', tone);
            if (this._promptFeedbackTimer) {
                clearTimeout(this._promptFeedbackTimer);
                this._promptFeedbackTimer = null;
            }
            if (!persist) {
                this._promptFeedbackTimer = setTimeout(() => {
                    this.setPromptFeedback(this._editingPromptId ? '正在编辑提示词,可直接覆盖保存。' : '支持新增、覆盖保存和导入自动去重。', 'default', true);
                }, 2400);
            }
        }

        updatePromptEditorState() {
            if (this.promptEditorBadgeEl) {
                this.promptEditorBadgeEl.textContent = this._editingPromptId ? '当前模式:编辑' : '当前模式:新增';
            }
            if (this.promptSaveBtnEl) {
                this.promptSaveBtnEl.textContent = this._editingPromptId ? '覆盖保存' : '新增保存';
            }
            if (this.promptCancelEditBtnEl) {
                this.promptCancelEditBtnEl.style.display = this._editingPromptId ? '' : 'none';
            }
        }

        updatePromptSelectionUI(visiblePrompts = this.getVisiblePromptEntries()) {
            if (this.promptSelectionCountEl) {
                this.promptSelectionCountEl.textContent = `已选 ${this._selectedPromptIds.size} 条`;
            }
            if (this.promptSelectAllInputEl) {
                this.promptSelectAllInputEl.checked = !!visiblePrompts.length && visiblePrompts.every((item) => this._selectedPromptIds.has(item.id));
            }
            if (this.promptListHintEl) {
                this.promptListHintEl.textContent = visiblePrompts.length
                    ? `当前 ${visiblePrompts.length} 条,可全选。`
                    : '没有匹配结果。';
            }
        }

        updatePromptPaneUI() {
            const isCreate = this._activePromptPane === 'create';
            this.promptCreatePaneBtnEl?.classList.toggle('active', isCreate);
            this.promptBrowsePaneBtnEl?.classList.toggle('active', !isCreate);
            this.promptCreatePaneEl?.classList.toggle('active', isCreate);
            this.promptBrowsePaneEl?.classList.toggle('active', !isCreate);
        }

        switchPromptPane(pane) {
            this._activePromptPane = pane === 'browse' ? 'browse' : 'create';
            this.updatePromptPaneUI();
        }

        togglePromptExpanded(id) {
            if (!id) return;
            if (this._expandedPromptIds.has(id)) {
                this._expandedPromptIds.delete(id);
                this._stickyPromptIds.delete(id);
            } else {
                this._expandedPromptIds.add(id);
            }
            this.renderPromptLibrary();
        }

        togglePromptSticky(id) {
            if (!id) return;
            if (!this._expandedPromptIds.has(id)) {
                this._expandedPromptIds.add(id);
            }
            if (this._stickyPromptIds.has(id)) {
                this._stickyPromptIds.delete(id);
            } else {
                this._stickyPromptIds.add(id);
            }
            this.renderPromptLibrary();
        }

        renderPromptLibrary() {
            if (!this.promptListEl) return;
            this.updatePromptTagOptions();
            const prompts = loadPromptLibrary();
            this.renderPromptTabs(prompts);
            this.prunePromptSelection(prompts);
            const validIds = new Set(prompts.map(item => item.id));
            Array.from(this._expandedPromptIds).forEach((id) => {
                if (!validIds.has(id)) this._expandedPromptIds.delete(id);
            });
            Array.from(this._stickyPromptIds).forEach((id) => {
                if (!validIds.has(id)) this._stickyPromptIds.delete(id);
            });
            const visiblePrompts = this.getVisiblePromptEntries();
            this.promptListEl.replaceChildren();
            this.updatePromptEditorState();
            this.updatePromptSelectionUI(visiblePrompts);
            this.updatePromptPaneUI();
            if (!visiblePrompts.length) {
                const empty = document.createElement('div');
                empty.className = 'prompt-empty';
                empty.textContent = '没有可显示的提示词。';
                this.promptListEl.appendChild(empty);
                return;
            }
            visiblePrompts.slice().reverse().forEach((item) => {
                const card = document.createElement('div');
                card.className = 'prompt-item';
                card.dataset.id = item.id;

                const head = document.createElement('div');
                head.className = 'prompt-item-head';
                const expanded = this._expandedPromptIds.has(item.id);
                const sticky = this._stickyPromptIds.has(item.id);
                if (expanded) card.classList.add('expanded');
                if (sticky) head.classList.add('sticky');

                const title = document.createElement('div');
                title.className = 'prompt-item-title';
                title.textContent = item.title;
                title.onclick = (event) => {
                    event.stopPropagation();
                    this.togglePromptExpanded(item.id);
                };

                const headMain = document.createElement('div');
                headMain.className = 'prompt-head-main';
                const selectCheck = document.createElement('input');
                selectCheck.type = 'checkbox';
                selectCheck.className = 'select-check';
                selectCheck.checked = this._selectedPromptIds.has(item.id);
                selectCheck.onclick = (event) => {
                    event.stopPropagation();
                    this.togglePromptSelection(item.id);
                };
                headMain.append(selectCheck, title);

                const actions = document.createElement('div');
                actions.className = 'prompt-item-actions';

                const copyBtn = document.createElement('button');
                copyBtn.type = 'button';
                copyBtn.className = 'icon-action-btn';
                copyBtn.title = '复制';
                copyBtn.textContent = '📋';
                copyBtn.onclick = async (event) => {
                    event.stopPropagation();
                    await this.copyPromptEntry(item, copyBtn);
                };

                const deleteBtn = document.createElement('button');
                deleteBtn.type = 'button';
                deleteBtn.className = 'icon-action-btn danger';
                deleteBtn.title = '删除';
                deleteBtn.textContent = '🗑️';
                deleteBtn.onclick = (event) => {
                    event.stopPropagation();
                    this.removePromptEntry(item.id);
                };

                const editBtn = document.createElement('button');
                editBtn.type = 'button';
                editBtn.className = 'icon-action-btn';
                editBtn.title = '编辑';
                editBtn.textContent = '✏️';
                editBtn.onclick = (event) => {
                    event.stopPropagation();
                    this.fillPromptEntry(item);
                    this.switchPromptPane('create');
                };

                const tag = document.createElement('div');
                tag.className = 'tag-chip';
                tag.textContent = item.group || '未标记';

                const meta = document.createElement('div');
                meta.className = 'prompt-item-meta';

                const body = document.createElement('div');
                body.className = 'prompt-item-body';

                const content = document.createElement('div');
                content.className = 'prompt-item-content';
                content.textContent = item.content;

                const stickyBtn = document.createElement('button');
                stickyBtn.type = 'button';
                stickyBtn.className = 'mini-btn';
                stickyBtn.textContent = sticky ? '取消固定' : '固定标题';
                stickyBtn.onclick = (event) => {
                    event.stopPropagation();
                    this.togglePromptSticky(item.id);
                };

                if (this._selectedPromptIds.has(item.id)) {
                    card.classList.add('selected');
                }
                actions.append(editBtn, copyBtn, deleteBtn);
                head.append(headMain, actions);
                meta.append(tag);
                if (expanded) meta.append(stickyBtn);
                body.append(content);
                card.append(head, meta, body);
                this.promptListEl.appendChild(card);
            });
        }

        savePromptEntry() {
            const title = this.promptTitleInputEl?.value.trim();
            const group = this.promptGroupInputEl?.value.trim();
            const content = this.promptContentInputEl?.value.trim();
            if (!title || !content) {
                this.setPromptFeedback('标题和内容不能为空。', 'warning');
                return;
            }
            const prompts = loadPromptLibrary();
            const currentId = this._editingPromptId;
            const signature = getPromptSignature({ title, group, content });
            const duplicate = prompts.find(item => item.id !== currentId && getPromptSignature(item) === signature);
            if (duplicate) {
                this.setPromptFeedback(`已存在相同内容的提示词:${duplicate.title}`, 'warning');
                return;
            }
            if (currentId) {
                const target = prompts.find(item => item.id === currentId);
                if (!target) {
                    this._editingPromptId = null;
                    this.updatePromptEditorState();
                    this.setPromptFeedback('原提示词不存在,请重新选择后再编辑。', 'warning');
                    return;
                }
                target.title = title;
                target.group = group;
                target.content = content;
                target.updatedAt = Date.now();
                savePromptLibrary(prompts);
                this.clearPromptEditor(true);
                this.setPromptFeedback(`已覆盖保存:${title}`, 'success');
                this.renderPromptLibrary();
                return;
            }
            prompts.push({
                id: generatePromptId(),
                title,
                group,
                content,
                updatedAt: Date.now()
            });
            savePromptLibrary(prompts);
            this.clearPromptEditor(true);
            this.setPromptFeedback(`已新增提示词:${title}`, 'success');
            this.renderPromptLibrary();
        }

        clearPromptEditor(keepFeedback = false) {
            this.promptTitleInputEl.value = '';
            this.promptGroupInputEl.value = '';
            this.promptContentInputEl.value = '';
            this._editingPromptId = null;
            this.updatePromptEditorState();
            if (!keepFeedback) {
                this.setPromptFeedback('已清空编辑区。', 'default');
            }
        }

        fillPromptEntry(item) {
            if (!item) return;
            this._editingPromptId = item.id;
            this.promptTitleInputEl.value = item.title;
            this.promptGroupInputEl.value = item.group || '';
            this.promptContentInputEl.value = item.content;
            this.updatePromptEditorState();
            this.setPromptFeedback(`已载入编辑:${item.title}`, 'default', true);
            this.switchPromptPane('create');
        }

        removePromptEntry(id) {
            const current = loadPromptLibrary();
            const target = current.find(item => item.id === id);
            if (!target) return;
            savePromptLibrary(current.filter(item => item.id !== id));
            this._selectedPromptIds.delete(id);
            this._expandedPromptIds.delete(id);
            this._stickyPromptIds.delete(id);
            if (this._editingPromptId === id) this.clearPromptEditor(true);
            this.setPromptFeedback(`已删除提示词:${target.title}`, 'success');
            this.renderPromptLibrary();
        }

        togglePromptSelection(id) {
            if (this._selectedPromptIds.has(id)) this._selectedPromptIds.delete(id);
            else this._selectedPromptIds.add(id);
            this.renderPromptLibrary();
        }

        setVisiblePromptSelection(checked) {
            const visiblePrompts = this.getVisiblePromptEntries();
            if (!visiblePrompts.length) {
                this.setPromptFeedback('当前没有可选择的提示词。', 'warning');
                return;
            }
            visiblePrompts.forEach((item) => {
                if (checked) this._selectedPromptIds.add(item.id);
                else this._selectedPromptIds.delete(item.id);
            });
            this.setPromptFeedback(checked ? `已选中 ${visiblePrompts.length} 条当前结果。` : '已取消当前结果的选择。', 'success');
            this.renderPromptLibrary();
        }

        clearPromptSelection() {
            if (!this._selectedPromptIds.size) {
                this.setPromptFeedback('当前没有已选提示词。', 'warning');
                return;
            }
            this._selectedPromptIds.clear();
            this.setPromptFeedback('已清空所有选择。', 'success');
            this.renderPromptLibrary();
        }

        removeSelectedPromptEntries() {
            if (!this._selectedPromptIds.size) {
                this.setPromptFeedback('当前没有可选择的提示词。', 'warning');
                return;
            }
            const prompts = loadPromptLibrary();
            const removedCount = prompts.filter(item => this._selectedPromptIds.has(item.id)).length;
            savePromptLibrary(prompts.filter(item => !this._selectedPromptIds.has(item.id)));
            Array.from(this._selectedPromptIds).forEach((id) => {
                this._expandedPromptIds.delete(id);
                this._stickyPromptIds.delete(id);
            });
            this._selectedPromptIds.clear();
            if (this._editingPromptId) this.clearPromptEditor(true);
            this.setPromptFeedback(`已批量删除 ${removedCount} 条提示词。`, 'success');
            this.renderPromptLibrary();
        }

        async copyPromptEntry(item, buttonEl) {
            if (!item) return;
            const text = `${item.title}\n\n${item.content}`;
            try {
                await copyText(text);
                if (buttonEl) {
                    const old = buttonEl.textContent;
                    buttonEl.textContent = '已复制';
                    setTimeout(() => {
                        buttonEl.textContent = old;
                    }, 1200);
                }
                this.setPromptFeedback(`已复制提示词:${item.title}`, 'success');
            } catch {
                this.setPromptFeedback('复制失败,请检查浏览器剪贴板权限。', 'danger');
            }
        }

        exportPromptLibraryFile() {
            const all = loadPromptLibrary();
            const selected = this._selectedPromptIds.size
                ? all.filter(item => this._selectedPromptIds.has(item.id))
                : all;
            if (!selected.length) {
                this.setPromptFeedback('当前没有可导出的提示词。', 'warning');
                return;
            }
            const payload = {
                version: PROMPT_LIBRARY_SCHEMA_VERSION,
                exportedAt: Date.now(),
                prompts: selected
            };
            triggerTextDownload(`prompt-library-${new Date().toISOString().slice(0,10)}.json`, JSON.stringify(payload, null, 2), 'application/json;charset=utf-8');
            this.setPromptFeedback(`已导出 ${selected.length} 条提示词。`, 'success');
        }

        async importPromptLibraryFile(file) {
            if (!file) {
                this.setPromptFeedback('未选择导入文件。', 'warning');
                return;
            }
            if (!/\.json$/i.test(file.name)) {
                this.setPromptFeedback('导入失败:请选择 JSON 文件。', 'danger');
                if (this.promptFileInputEl) this.promptFileInputEl.value = '';
                return;
            }
            try {
                const text = await file.text();
                const payload = normalizePromptLibraryPayload(JSON.parse(text));
                if (!payload.prompts.length) {
                    this.setPromptFeedback('正在编辑提示词,可直接覆盖保存。', 'danger');
                    return;
                }
                if (payload.version > PROMPT_LIBRARY_SCHEMA_VERSION) {
                    this.setPromptFeedback(`导入文件版本 ${payload.version} 高于当前支持版本 ${PROMPT_LIBRARY_SCHEMA_VERSION}。`, 'warning');
                    return;
                }
                const existing = loadPromptLibrary();
                const seen = new Set(existing.map(getPromptSignature));
                const merged = existing.slice();
                let importedCount = 0;
                let duplicateCount = 0;

                payload.prompts.forEach((item) => {
                    const normalized = normalizePromptItem({ ...item, id: generatePromptId(), updatedAt: Date.now() });
                    if (!normalized) return;
                    const signature = getPromptSignature(normalized);
                    if (seen.has(signature)) {
                        duplicateCount += 1;
                        return;
                    }
                    seen.add(signature);
                    merged.push(normalized);
                    importedCount += 1;
                });

                if (!importedCount) {
                    this.setPromptFeedback(`导入完成,但 ${duplicateCount} 条均为重复内容,已自动跳过。`, 'warning');
                    return;
                }
                savePromptLibrary(merged);
                this.promptTransferOverlayEl?.classList.remove('show');
                this.setPromptFeedback(`导入成功:新增 ${importedCount} 条,跳过重复 ${duplicateCount} 条。`, 'success');
            } catch {
                this.setPromptFeedback('导入失败:文件内容不是有效的 JSON。', 'danger');
            }
            if (this.promptFileInputEl) this.promptFileInputEl.value = '';
            this.renderPromptLibrary();
        }

        updateActiveItem() {
            if (!this.listEl) return;
            const lis = this.listEl.querySelectorAll('.question-item');
            lis.forEach((li, index) => {
                if (index === activeIndex) li.classList.add('active');
                else li.classList.remove('active');
            });
        }

        startTracking() {
            const scrollTarget = queryScrollContainer();
            if (!scrollTarget) return;
            scrollTarget.addEventListener('scroll', () => {
                requestAnimationFrame(() => this.syncActiveIndex());
            }, { passive: true });
        }

        syncActiveIndex() {
            const newIndex = this._elements.findIndex(el => el.getBoundingClientRect().top >= 0);
            if (newIndex !== -1 && newIndex !== activeIndex) {
                activeIndex = newIndex;
                this.updateActiveItem();
            }
        }
    }

    customElements.define('ai-conversation-navigator', NavigatorApp);
    // [END: NavigatorApp 组件]

    // ============================================================
    // ==Main== 入口层 - 初始化与事件绑定
    // ============================================================

    // [START: 入口函数]
    function extractQuestionText(element) {
        if (!element) return '';
        if (detectSite() === 'gemini') {
            const content = element.matches('.conversation-container')
                ? (element.querySelector('user-query-content .query-text, user-query-content .user-query-bubble-with-background, user-query-content .query-content') || element.querySelector('user-query-content') || element)
                : element.matches('.query-text, .query-text.gds-body-l')
                ? element
                : element.matches('user-query-content')
                ? element
                : (element.querySelector('user-query-content, .query-text, .user-query-bubble-with-background') || element);
            return sanitizeText(content.innerText || content.textContent);
        }
        return sanitizeText(element.querySelector('[data-message-author-role="user"]')?.innerText || element.innerText);
    }

    function collectHeadingTargets(root) {
        if (!root) return [];
        const headingNodes = Array.from(root.querySelectorAll('h1, h2, h3, h4, h5, h6'))
            .filter(node => sanitizeText(node.textContent).length > 0);
        return headingNodes.map(node => ({
            text: sanitizeText(node.textContent),
            level: Number(node.tagName.slice(1)),
            target: node
        }));
    }

    function getChatGPTNavigatorEntries() {
        const wrappers = Array.from(new Set(
            queryAllDeep(CHATGPT_MESSAGE_SELECTOR).map(getClosestMessageWrapper).filter(Boolean)
        )).sort((a, b) => {
            if (a === b) return 0;
            const pos = a.compareDocumentPosition(b);
            if (pos & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
            if (pos & Node.DOCUMENT_POSITION_PRECEDING) return 1;
            return 0;
        });

        const entries = [];
        wrappers.forEach((wrapper) => {
            const roleEl = wrapper.querySelector(CHATGPT_MESSAGE_SELECTOR) || wrapper;
            const role = roleEl?.getAttribute('data-message-author-role');
            if (role !== 'user') return;

            entries.push({
                id: wrapper.dataset.messageId || wrapper.getAttribute('data-testid') || `chatgpt_${entries.length + 1}`,
                question: extractQuestionText(wrapper),
                element: wrapper,
                detail: { headings: [], questionRoot: wrapper, answerRoot: null }
            });
        });

        let entryIndex = -1;
        wrappers.forEach((wrapper) => {
            const roleEl = wrapper.querySelector(CHATGPT_MESSAGE_SELECTOR) || wrapper;
            const role = roleEl?.getAttribute('data-message-author-role');
            if (role === 'user') {
                entryIndex += 1;
                return;
            }
            if (role === 'assistant' && entries[entryIndex]) {
                const contentRoot = wrapper.querySelector('.markdown, [data-message-id] .markdown, .prose') || wrapper;
                entries[entryIndex].detail.headings = collectHeadingTargets(contentRoot);
                entries[entryIndex].detail.answerRoot = contentRoot;
            }
        });

        return entries.filter(item => item.question);
    }

    function getGeminiNavigatorEntries() {
        const containers = queryGeminiConversationContainers();
        if (containers.length) {
            return containers.map((container, index) => {
                const userNode = container.querySelector('user-query-content') || container;
                const assistantNode = container.querySelector('model-response');
                const assistantContent = assistantNode?.querySelector('.markdown.markdown-main-panel, message-content .markdown, structured-content-container, .model-response-text, .response-content') || assistantNode;
                return {
                    id: container.getAttribute('data-conversation-id') || `gemini_${index + 1}`,
                    question: extractQuestionText(userNode),
                    element: container,
                    detail: { headings: collectHeadingTargets(assistantContent), questionRoot: userNode, answerRoot: assistantContent }
                };
            }).filter(item => item.question);
        }

        const root = queryGeminiRoot();
        const users = root ? Array.from(root.querySelectorAll('user-query-content')) : [];
        const assistants = root ? Array.from(root.querySelectorAll('model-response')) : [];
        return users.map((userNode, index) => {
            const answerRoot = assistants[index]?.querySelector('message-content.model-response-text, .markdown, .model-response-text, .response-container-with-gpi') || assistants[index];
            return {
                id: userNode.getAttribute('data-message-id') || `gemini_${index + 1}`,
                question: extractQuestionText(userNode),
                element: userNode,
                detail: {
                    headings: collectHeadingTargets(answerRoot),
                    questionRoot: userNode,
                    answerRoot
                }
            };
        }).filter(item => item.question);
    }

    function collectNavigatorEntries() {
        return detectSite() === 'gemini'
            ? getGeminiNavigatorEntries()
            : getChatGPTNavigatorEntries();
    }

    function resetDisplay(elements) {
        elements.forEach(element => {
            if (element.style.display === 'none') {
                element.style.display = '';
            }
        });
    }

    function applyChatGPTFilter(depth) {
        const allChildren = Array.from(new Set(
            queryAllDeep(CHATGPT_MESSAGE_SELECTOR).map(getClosestMessageWrapper).filter(Boolean)
        ));
        if (!allChildren.length) return;
        resetDisplay(allChildren);

        if (depth <= 0) return;

        const questionEls = queryChatGPTQuestionElements();
        if (questionEls.length <= depth) return;

        const firstToKeep = questionEls[questionEls.length - depth];
        const firstToKeepIndex = allChildren.indexOf(firstToKeep);

        for (let index = 0; index < firstToKeepIndex; index += 1) {
            allChildren[index].style.display = 'none';
        }
    }

    function applyGeminiFilter() {
        // Gemini's application shell lazily mounts and reuses conversation nodes.
        // Hiding those nodes during initial render can leave the page stuck loading,
        // so Gemini keeps the full DOM visible and only trims the navigator list.
        const conversationContainers = queryGeminiConversationContainers();
        if (conversationContainers.length) {
            resetDisplay(conversationContainers);
            return;
        }

        const root = queryGeminiRoot();
        const userNodes = root ? Array.from(root.querySelectorAll('user-query-content')) : [];
        const assistantNodes = root ? Array.from(root.querySelectorAll('model-response')) : [];
        const pairedNodes = [];

        if (userNodes.length || assistantNodes.length) {
            const maxLength = Math.max(userNodes.length, assistantNodes.length);
            for (let index = 0; index < maxLength; index += 1) {
                if (userNodes[index]) pairedNodes.push(userNodes[index]);
                if (assistantNodes[index]) pairedNodes.push(assistantNodes[index]);
            }
        }

        const messageNodes = pairedNodes.length ? pairedNodes : getGeminiMessageNodes();
        resetDisplay(messageNodes);
    }

    function applyMainChatFilter() {
        if (detectSite() === 'gemini') {
            applyGeminiFilter();
        } else {
            const savedDepth = parseInt(localStorage.getItem(getDepthStorageKey()) || '0', 10);
            applyChatGPTFilter(savedDepth);
        }
    }

    function ensureNavigatorApp() {
        if (!document.body) return null;
        let app = document.getElementById(DOM_MARK);
        if (!app) {
            app = document.createElement('ai-conversation-navigator');
            app.id = DOM_MARK;
            document.body.appendChild(app);
        }
        return app;
    }

    function updateNavigator() {
        applyMainChatFilter();
        const entries = collectNavigatorEntries();
        const elements = entries.map(item => item.element).filter(Boolean);
        let app = document.getElementById(DOM_MARK);
        const hasGeminiRoot = detectSite() === 'gemini' && !!queryGeminiRoot();

        if (elements.length > 0) {
            app = app || ensureNavigatorApp();
            if (!app) return;

            const filteredEntries = entries.filter(item => item.question && item.element);
            app.questions = {
                ids: filteredEntries.map(item => item.id),
                questions: filteredEntries.map(item => item.question),
                elements: filteredEntries.map(item => item.element),
                details: filteredEntries.map(item => item.detail || { headings: [] })
            };
        } else if (detectSite() === 'gemini' || hasGeminiRoot) {
            app = app || ensureNavigatorApp();
            if (!app) return;
            app.questions = { ids: [], questions: [], elements: [], details: [] };
        } else if (app) {
            app.remove();
        }
    }

    const refreshNavigator = () => {
        const latestKey = getConversationKey();
        if (conversationKey !== latestKey) {
            conversationKey = latestKey;
            loaded = false;
            activeIndex = null;
            const app = document.getElementById(DOM_MARK);
            if (app) app.remove();
        }

        if (detectSite() === 'gemini') {
            ensureNavigatorApp();
        }

        const elements = queryQuestionElements();
        if (elements.length > 0) {
            applyMainChatFilter();
            updateNavigator();
            loaded = true;
        } else if (detectSite() === 'gemini') {
            updateNavigator();
            loaded = false;
        } else {
            const app = document.getElementById(DOM_MARK);
            if (app) app.remove();
            loaded = false;
        }
    };

    const scheduleRefreshNavigator = () => {
        if (refreshScheduled) return;
        refreshScheduled = true;
        requestAnimationFrame(() => {
            refreshScheduled = false;
            refreshNavigator();
        });
    };

    function startChatGPTBootstrap() {
        setInterval(scheduleRefreshNavigator, 800);

        const observer = new MutationObserver(() => {
            scheduleRefreshNavigator();
        });

        observer.observe(document.documentElement, {
            childList: true,
            subtree: true
        });
    }

    function startGeminiBootstrap() {
        if (geminiBootstrapStarted || detectSite() !== 'gemini') return;
        geminiBootstrapStarted = true;

        const bootstrap = () => {
            if (!document.body) return;
            ensureNavigatorApp();
            scheduleRefreshNavigator();
        };

        const startObserver = () => {
            const target = document.querySelector('main') || document.body || document.documentElement;
            if (!target) return false;

            const observer = new MutationObserver(() => {
                scheduleRefreshNavigator();
            });

            observer.observe(target, {
                childList: true,
                subtree: true,
                characterData: true
            });
            return true;
        };

        setTimeout(bootstrap, 300);
        setTimeout(bootstrap, 1500);
        setTimeout(bootstrap, 3000);
        setTimeout(() => {
            bootstrap();
            startObserver();
        }, 1500);

        window.addEventListener('load', bootstrap, { once: true });
        document.addEventListener('readystatechange', bootstrap, { passive: true });

        const probe = setInterval(() => {
            bootstrap();
            if (startObserver()) {
                clearInterval(probe);
            }
        }, 1000);

        setTimeout(() => clearInterval(probe), 10000);
    }

    if (detectSite() === 'gemini') {
        startGeminiBootstrap();
    } else {
        startChatGPTBootstrap();
    }
    // [END: 入口函数]

})();