Greasy Fork

来自缓存

Greasy Fork is available in English.

AI 网页总结助手 Pro

一键总结网页,侧栏显示,快捷输出,多语言,Q&A问答,导出,深色浅色模式

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         AI 网页总结助手 Pro
// @namespace    https://github.com/cyx0118/ai_summary_helper-pro
// @version      2.2.0
// @description  一键总结网页,侧栏显示,快捷输出,多语言,Q&A问答,导出,深色浅色模式
// @author       超级小忍者
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @connect      api.openai.com
// @connect      api.deepseek.com
// @connect      *
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    const DEFAULT_CONFIG = {
        shortcutKey: 'q', useCtrl: true, useShift: false, useAlt: false,
        apiKey: '', apiEndpoint: 'https://api.deepseek.com/v1/chat/completions',
        modelName: 'deepseek-chat', maxContentLength: 8000,
        summaryLanguage: 'zh-CN', summaryLength: 'medium', summaryTone: 'neutral',
        sidebarWidth: 440, enableStreaming: true, theme: 'system',
        systemPrompt: '你是一个网页内容分析助手。直接输出总结内容,不要添加任何客套话、问候语或引言。直接用 Markdown 格式输出结构化总结。',
    };

    const LANG_OPTIONS = [
        { value: 'zh-CN', label: '中文' }, { value: 'en', label: 'English' },
        { value: 'ja', label: '日本語' }, { value: 'ko', label: '한국어' },
        { value: 'fr', label: 'Français' }, { value: 'de', label: 'Deutsch' },
        { value: 'es', label: 'Español' }, { value: 'ru', label: 'Русский' },
        { value: 'auto', label: '与原文相同' },
    ];
    const LENGTH_OPTIONS = [
        { value: 'brief', label: '简洁', tokens: 300 },
        { value: 'medium', label: '中等', tokens: 800 },
        { value: 'detailed', label: '详细', tokens: 2000 },
    ];
    const TONE_OPTIONS = [
        { value: 'neutral', label: '中性客观' }, { value: 'professional', label: '专业严谨' },
        { value: 'casual', label: '轻松通俗' }, { value: 'academic', label: '学术风格' },
        { value: 'child', label: '小学生能懂' }, { value: 'humorous', label: '幽默风格' },
        { value: 'bullet', label: '要点列举' },
    ];

    function getConfig() {
        const s = GM_getValue('ai_summary_config', null);
        if (!s) return { ...DEFAULT_CONFIG };
        try { return { ...DEFAULT_CONFIG, ...JSON.parse(s) }; } catch { return { ...DEFAULT_CONFIG }; }
    }
    function saveConfig(c) { GM_setValue('ai_summary_config', JSON.stringify(c)); }
    function getLangLabel(c) { const f = LANG_OPTIONS.find(l => l.value === c); return f ? f.label : c; }
    function getLengthInfo(v) { return LENGTH_OPTIONS.find(l => l.value === v) || LENGTH_OPTIONS[1]; }
    function getToneLabel(v) { const f = TONE_OPTIONS.find(t => t.value === v); return f ? f.label : '中性客观'; }

    // ===================== 主题管理 =====================
    function getEffectiveTheme() {
        const cfg = getConfig();
        if (cfg.theme === 'system') {
            return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
        }
        return cfg.theme;
    }

    function applyTheme() {
        const t = getEffectiveTheme();
        document.documentElement.setAttribute('ai-theme', t);
    }

    // 监听系统主题变化
    window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
        if (getConfig().theme === 'system') applyTheme();
    });

    // ===================== 样式 =====================
    GM_addStyle(`
        /* ===== 主题变量 ===== */
        :root, [ai-theme="light"] {
            --ai-bg: rgba(255,255,255,.72);
            --ai-bg-solid: #fff;
            --ai-bg-card: rgba(248,249,250,.85);
            --ai-bg-input: rgba(255,255,255,.9);
            --ai-bg-header: rgba(250,251,252,.6);
            --ai-bg-hover: rgba(241,243,244,.9);
            --ai-bg-btn: rgba(241,243,244,.8);
            --ai-border: rgba(232,234,237,.8);
            --ai-border-light: rgba(255,255,255,.5);
            --ai-border-card: rgba(255,255,255,.4);
            --ai-border-input: rgba(218,220,224,.8);
            --ai-text: #202124;
            --ai-text-heading: #1a1a2e;
            --ai-text-secondary: #5f6368;
            --ai-text-muted: #80868b;
            --ai-text-label: #3c4043;
            --ai-overlay: rgba(0,0,0,.3);
            --ai-shadow: 0 20px 60px rgba(0,0,0,.25);
            --ai-shadow-sidebar: -4px 0 24px rgba(0,0,0,.15);
            --ai-error-bg: rgba(252,232,230,.9);
            --ai-error-border: rgba(245,198,203,.8);
            --ai-error-text: #c62828;
            --ai-privacy-bg: rgba(255,248,225,.8);
            --ai-privacy-border: rgba(255,224,130,.5);
            --ai-toast-bg: rgba(50,50,50,.9);
            --ai-chat-user-bg: rgba(102,126,234,.12);
            --ai-chat-user-border: rgba(102,126,234,.2);
            --ai-chat-ai-bg: rgba(248,249,250,.85);
            --ai-chat-ai-border: rgba(255,255,255,.4);
            --ai-scroll-thumb: rgba(0,0,0,.15);
            --ai-scroll-track: transparent;
        }
        [ai-theme="dark"] {
            --ai-bg: rgba(30,30,35,.82);
            --ai-bg-solid: #1e1e23;
            --ai-bg-card: rgba(40,42,50,.85);
            --ai-bg-input: rgba(45,47,55,.9);
            --ai-bg-header: rgba(35,37,45,.7);
            --ai-bg-hover: rgba(55,57,68,.9);
            --ai-bg-btn: rgba(50,52,62,.8);
            --ai-border: rgba(60,62,72,.8);
            --ai-border-light: rgba(50,52,60,.5);
            --ai-border-card: rgba(60,62,72,.6);
            --ai-border-input: rgba(70,72,82,.8);
            --ai-text: #e0e0e0;
            --ai-text-heading: #f0f0f0;
            --ai-text-secondary: #aaa;
            --ai-text-muted: #888;
            --ai-text-label: #ccc;
            --ai-overlay: rgba(0,0,0,.55);
            --ai-shadow: 0 20px 60px rgba(0,0,0,.5);
            --ai-shadow-sidebar: -4px 0 24px rgba(0,0,0,.4);
            --ai-error-bg: rgba(60,30,30,.85);
            --ai-error-border: rgba(100,50,50,.6);
            --ai-error-text: #ff8a80;
            --ai-privacy-bg: rgba(50,45,30,.7);
            --ai-privacy-border: rgba(100,80,40,.5);
            --ai-toast-bg: rgba(40,40,45,.95);
            --ai-chat-user-bg: rgba(102,126,234,.18);
            --ai-chat-user-border: rgba(102,126,234,.3);
            --ai-chat-ai-bg: rgba(40,42,50,.85);
            --ai-chat-ai-border: rgba(60,62,72,.5);
            --ai-scroll-thumb: rgba(255,255,255,.15);
            --ai-scroll-track: transparent;
        }

        /* 滚动条 */
        #ai-summary-sidebar ::-webkit-scrollbar { width: 6px; }
        #ai-summary-sidebar ::-webkit-scrollbar-thumb { background: var(--ai-scroll-thumb); border-radius: 3px; }
        #ai-summary-sidebar ::-webkit-scrollbar-track { background: var(--ai-scroll-track); }

        /* ===== 浮动按钮 ===== */
        #ai-summary-settings-btn{position:fixed;bottom:20px;right:20px;width:48px;height:48px;border-radius:50%;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);color:#fff;border:none;cursor:pointer;font-size:22px;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 15px rgba(102,126,234,.4);z-index:999998;transition:transform .2s,box-shadow .2s,right .35s cubic-bezier(.4,0,.2,1);user-select:none}
        #ai-summary-settings-btn:hover{transform:scale(1.1);box-shadow:0 6px 20px rgba(102,126,234,.6)}
        #ai-summary-settings-btn.sidebar-open{right:460px}

        /* ===== 侧栏 ===== */
        #ai-summary-sidebar{position:fixed;top:0;right:0;height:100vh;z-index:999999;display:flex;flex-direction:column;background:var(--ai-bg);backdrop-filter:blur(24px) saturate(180%);-webkit-backdrop-filter:blur(24px) saturate(180%);border-left:1px solid var(--ai-border-light);box-shadow:var(--ai-shadow-sidebar);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;transform:translateX(100%);transition:transform .35s cubic-bezier(.4,0,.2,1);color:var(--ai-text)}
        #ai-summary-sidebar.open{transform:translateX(0)}
        #ai-summary-sidebar-resize{position:absolute;top:0;left:-4px;width:8px;height:100%;cursor:col-resize;z-index:10}
        #ai-summary-sidebar-resize:hover,#ai-summary-sidebar-resize.dragging{background:rgba(102,126,234,.3)}

        .ai-sb-header{display:flex;align-items:center;justify-content:space-between;padding:14px 20px;border-bottom:1px solid var(--ai-border);flex-shrink:0;background:var(--ai-bg-header)}
        .ai-sb-header h2{margin:0;font-size:16px;font-weight:600;color:var(--ai-text-heading);display:flex;align-items:center;gap:8px}
        .ai-sb-header-actions{display:flex;align-items:center;gap:4px}
        .ai-sb-btn{width:30px;height:30px;border-radius:50%;border:none;background:var(--ai-bg-btn);cursor:pointer;font-size:14px;display:flex;align-items:center;justify-content:center;transition:background .2s;color:var(--ai-text-secondary)}
        .ai-sb-btn:hover{background:var(--ai-bg-hover)}

        .ai-sb-info{padding:10px 20px;border-bottom:1px solid var(--ai-border);flex-shrink:0}
        .ai-sb-info .pt{font-size:13px;font-weight:600;color:var(--ai-text-label);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
        .ai-sb-info .pu{font-size:10px;color:var(--ai-text-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-top:2px}

        .ai-sb-tabs{display:flex;border-bottom:1px solid var(--ai-border);flex-shrink:0}
        .ai-sb-tab{flex:1;padding:10px 0;text-align:center;font-size:13px;font-weight:500;color:var(--ai-text-secondary);cursor:pointer;border-bottom:2px solid transparent;transition:all .2s;background:none;border-top:none;border-left:none;border-right:none}
        .ai-sb-tab:hover{color:var(--ai-text-heading);background:rgba(102,126,234,.04)}
        .ai-sb-tab.active{color:#667eea;border-bottom-color:#667eea;font-weight:600}

        .ai-sb-body{padding:16px 20px;overflow-y:auto;flex:1}
        .ai-sb-footer{padding:10px 20px;border-top:1px solid var(--ai-border);display:flex;gap:8px;justify-content:flex-end;flex-shrink:0;background:var(--ai-bg-header)}

        .ai-sb-content{background:var(--ai-bg-card);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border:1px solid var(--ai-border-card);border-radius:10px;padding:16px;font-size:14px;line-height:1.8;color:var(--ai-text);word-break:break-word}
        .ai-sb-content strong,.ai-sb-content b{color:var(--ai-text-heading)}
        .ai-sb-content h1,.ai-sb-content h2,.ai-sb-content h3{margin:14px 0 6px 0;color:var(--ai-text-heading)}
        .ai-sb-content h1{font-size:18px}.ai-sb-content h2{font-size:16px}.ai-sb-content h3{font-size:15px}
        .ai-sb-content ul,.ai-sb-content ol{margin:6px 0;padding-left:18px}
        .ai-sb-content li{margin:3px 0}
        .ai-sb-content code{background:rgba(100,100,120,.2);padding:1px 5px;border-radius:4px;font-size:13px}
        .ai-sb-content blockquote{border-left:3px solid #667eea;margin:8px 0;padding:4px 14px;color:var(--ai-text-secondary)}

        .ai-stream-cursor::after{content:'\u2588';animation:ai-blink 1s step-end infinite;color:#667eea}
        @keyframes ai-blink{50%{opacity:0}}

        .ai-sb-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:80px 20px;gap:16px}
        .ai-sb-spinner{width:36px;height:36px;border:3px solid var(--ai-border);border-top-color:#667eea;border-radius:50%;animation:ai-spin .8s linear infinite}
        @keyframes ai-spin{to{transform:rotate(360deg)}}
        .ai-sb-loading-text{font-size:13px;color:var(--ai-text-secondary)}

        .ai-sb-error{background:var(--ai-error-bg);backdrop-filter:blur(8px);border:1px solid var(--ai-error-border);border-radius:10px;padding:14px 16px;color:var(--ai-error-text);font-size:13px;line-height:1.6}

        .ai-btn{padding:7px 14px;border-radius:6px;border:none;font-size:12px;font-weight:600;cursor:pointer;transition:all .2s}
        .ai-btn-primary{background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;box-shadow:0 2px 8px rgba(102,126,234,.3)}
        .ai-btn-primary:hover{box-shadow:0 4px 12px rgba(102,126,234,.5);transform:translateY(-1px)}
        .ai-btn-secondary{background:var(--ai-bg-btn);color:var(--ai-text-secondary)}
        .ai-btn-ghost{background:var(--ai-bg);color:var(--ai-text-secondary);border:1px solid var(--ai-border-input)}
        .ai-btn-ghost:hover{background:var(--ai-bg-hover)}

        .ai-chat-msgs{display:flex;flex-direction:column;gap:12px}
        .ai-chat-msg{padding:12px 14px;border-radius:10px;font-size:13px;line-height:1.7;word-break:break-word}
        .ai-chat-msg.user{background:var(--ai-chat-user-bg);border:1px solid var(--ai-chat-user-border);align-self:flex-end;max-width:85%;color:var(--ai-text)}
        .ai-chat-msg.assistant{background:var(--ai-chat-ai-bg);border:1px solid var(--ai-chat-ai-border);align-self:flex-start;max-width:95%;color:var(--ai-text)}
        .ai-chat-input-area{display:flex;gap:8px;padding:10px 0 0;border-top:1px solid var(--ai-border);margin-top:12px}
        .ai-chat-input{flex:1;padding:8px 12px;border:1px solid var(--ai-border-input);border-radius:8px;font-size:13px;outline:none;background:var(--ai-bg-input);color:var(--ai-text);font-family:inherit;resize:none;min-height:36px;max-height:100px}
        .ai-chat-input:focus{border-color:#667eea;box-shadow:0 0 0 2px rgba(102,126,234,.1)}
        .ai-chat-send{padding:8px 14px;border-radius:8px;border:none;background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;font-size:13px;cursor:pointer;font-weight:600;align-self:flex-end}

        /* ===== 设置面板 ===== */
        .ai-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:var(--ai-overlay);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);z-index:1000000;display:flex;align-items:center;justify-content:center;animation:ai-fi .2s ease}
        @keyframes ai-fi{from{opacity:0}to{opacity:1}}
        .ai-panel{background:var(--ai-bg);backdrop-filter:blur(24px) saturate(180%);-webkit-backdrop-filter:blur(24px) saturate(180%);border:1px solid var(--ai-border-light);border-radius:16px;box-shadow:var(--ai-shadow);max-width:600px;width:92vw;max-height:88vh;display:flex;flex-direction:column;animation:ai-su .3s ease;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif;color:var(--ai-text)}
        @keyframes ai-su{from{transform:translateY(30px);opacity:0}to{transform:translateY(0);opacity:1}}
        .ai-panel-header{display:flex;align-items:center;justify-content:space-between;padding:18px 24px;border-bottom:1px solid var(--ai-border)}
        .ai-panel-header h2{margin:0;font-size:18px;font-weight:600;color:var(--ai-text-heading);display:flex;align-items:center;gap:8px}
        .ai-panel-close{width:34px;height:34px;border-radius:50%;border:none;background:var(--ai-bg-btn);cursor:pointer;font-size:18px;display:flex;align-items:center;justify-content:center;color:var(--ai-text-secondary)}
        .ai-panel-close:hover{background:var(--ai-bg-hover)}
        .ai-panel-body{padding:22px 24px;overflow-y:auto;flex:1}

        .ai-fg{margin-bottom:16px}
        .ai-fg label{display:block;font-size:13px;font-weight:600;color:var(--ai-text-label);margin-bottom:5px}
        .ai-fg .desc{font-size:11px;color:var(--ai-text-muted);margin-bottom:6px}
        .ai-fg input[type="text"],.ai-fg input[type="password"],.ai-fg select,.ai-fg textarea{width:100%;padding:9px 12px;border:1px solid var(--ai-border-input);border-radius:8px;font-size:13px;color:var(--ai-text);outline:none;transition:border-color .2s,box-shadow .2s;box-sizing:border-box;background:var(--ai-bg-input);font-family:inherit}
        .ai-fg input:focus,.ai-fg select:focus,.ai-fg textarea:focus{border-color:#667eea;box-shadow:0 0 0 3px rgba(102,126,234,.15)}
        .ai-fg textarea{resize:vertical;min-height:70px;line-height:1.5}
        .ai-fg .shortcut-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
        .ai-fg .mod-check{display:flex;align-items:center;gap:4px;font-size:12px;color:var(--ai-text-secondary);cursor:pointer}
        .ai-fg .mod-check input{width:15px;height:15px;accent-color:#667eea}
        .ai-fg .key-input{width:70px!important;text-align:center;text-transform:uppercase;font-weight:600;font-size:15px;letter-spacing:2px}
        .ai-fg .shortcut-preview{font-size:12px;color:#667eea;font-weight:500;padding:3px 8px;background:rgba(102,126,234,.12);border-radius:6px}
        .ai-btn-group{display:flex;gap:10px;justify-content:flex-end;padding-top:6px}
        .ai-divider{height:1px;background:var(--ai-border);margin:14px 0}
        .ai-privacy{background:var(--ai-privacy-bg);border:1px solid var(--ai-privacy-border);border-radius:8px;padding:10px 14px;font-size:11px;color:var(--ai-text-secondary);line-height:1.5;margin-bottom:14px}
        .ai-privacy strong{color:#e65100}

        .ai-toast{position:fixed;top:20px;left:50%;transform:translateX(-50%);background:var(--ai-toast-bg);backdrop-filter:blur(12px);color:#fff;padding:10px 24px;border-radius:8px;font-size:13px;z-index:1000001;animation:ai-toast-in .3s ease;box-shadow:0 4px 12px rgba(0,0,0,.3)}
        @keyframes ai-toast-in{from{transform:translateX(-50%) translateY(-20px);opacity:0}to{transform:translateX(-50%) translateY(0);opacity:1}}

        /* 主题切换按钮 */
        .ai-theme-btn{font-size:16px;line-height:1}
        .ai-theme-btn[data-theme="light"]::after{content:'\u2600\uFE0F'}
        .ai-theme-btn[data-theme="dark"]::after{content:'\uD83C\uDF19'}
        .ai-theme-btn[data-theme="system"]::after{content:'\uD83D\uDCA1'}
    `);

    function showToast(m,d){const e=document.querySelector('.ai-toast');if(e)e.remove();const t=document.createElement('div');t.className='ai-toast';t.textContent=m;document.body.appendChild(t);setTimeout(()=>t.remove(),d||2000)}
    function getShortcutText(c){const p=[];if(c.useCtrl)p.push('Ctrl');if(c.useShift)p.push('Shift');if(c.useAlt)p.push('Alt');p.push(c.shortcutKey.toUpperCase());return p.join(' + ')}
    function esc(s){return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"')}
    function md(text){let h=text.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');h=h.replace(/^### (.+)$/gm,'<h3>$1</h3>').replace(/^## (.+)$/gm,'<h2>$1</h2>').replace(/^# (.+)$/gm,'<h1>$1</h1>');h=h.replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>').replace(/\*(.+?)\*/g,'<em>$1</em>');h=h.replace(/`([^`]+)`/g,'<code>$1</code>').replace(/^> (.+)$/gm,'<blockquote>$1</blockquote>');h=h.replace(/^\- (.+)$/gm,'<li>$1</li>').replace(/^\* (.+)$/gm,'<li>$1</li>');h=h.replace(/(<li>.*<\/li>\n?)+/g,'<ul>$&</ul>').replace(/^\d+\. (.+)$/gm,'<li>$1</li>');h=h.replace(/\n\n/g,'<br><br>').replace(/\n/g,'<br>');return h}

    // ===================== 内容提取 =====================
    function extractContent(){const sels=['article','[itemprop="articleBody"]','[data-testid="article-body"]','main','[role="main"]','.post-content','.article-content','.entry-content','.article-body','.post-body','.story-body','.markdown-body','.content-body','.page-content','.text-content','#content','#main-content','#article-content','.post','.article','.story'];let el=null;for(const s of sels){for(const c of document.querySelectorAll(s)){if(c.innerText.trim().length>200){el=c;break}}if(el)break}if(!el)el=document.body;const cl=el.cloneNode(true);['script','style','nav','header','footer','iframe','noscript','.ad','.ads','.advertisement','.ad-container','[class*="ad-"]','.sidebar','.side-bar','.widget','.widget-area','.comment','.comments','.comment-section','#comments','.social-share','.share-buttons','.related-posts','.related-articles','.navigation','.nav','.menu','.breadcrumb','.breadcrumbs','.cookie-banner','.cookie-notice','.popup','.modal','[role="banner"]','[role="navigation"]','[role="complementary"]','[aria-hidden="true"]','.sr-only','.visually-hidden','.newsletter','.subscribe','.signup','.paywall'].forEach(s=>{try{cl.querySelectorAll(s).forEach(e=>e.remove())}catch(x){}});let t=cl.innerText||cl.textContent||'';return t.replace(/\s+/g,' ').replace(/\n\s*\n/g,'\n').trim()}

    // ===================== API =====================
    function buildSummaryPrompt(content,config){const url=location.href,title=document.title;const tc=content.substring(0,config.maxContentLength);const lang=config.summaryLanguage==='auto'?'':`请使用${getLangLabel(config.summaryLanguage)}回答。`;const tone=getToneLabel(config.summaryTone);const lenMap={brief:'用3句话高度概括核心内容。',medium:'用结构化格式总结,包含主题概述、3-5个关键要点、核心观点、简要评价。',detailed:'进行全面深入的总结分析,包含:主题概述、详细要点分析、核心观点与建议、数据/案例提取、优缺点评价。'};return{system:config.systemPrompt,user:`${lang}请以"${tone}"的语气,${lenMap[config.summaryLength]||lenMap.medium}\n\n网页标题:${title}\n网页地址:${url}\n\n---\n${tc}`,maxTokens:getLengthInfo(config.summaryLength).tokens||800}}
    function buildQAPrompt(content,config,ch){const tc=content.substring(0,config.maxContentLength);const lang=config.summaryLanguage==='auto'?'':`请使用${getLangLabel(config.summaryLanguage)}回答。`;const msgs=[{role:'system',content:`你是一个网页内容问答助手。基于以下网页内容回答用户问题。如果问题与网页内容无关,请说明。${lang}\n\n【网页内容】\n${tc}`}];ch.forEach(m=>msgs.push(m));return{messages:msgs,maxTokens:1500}}

    function simulateStream(text,onChunk,onDone,speed){const chars=text.split('');let idx=0;const interval=speed||12;function tick(){idx=Math.min(idx+Math.ceil(chars.length/60),chars.length);onChunk(chars.slice(0,idx).join(''));if(idx>=chars.length){onDone(text);return}setTimeout(tick,interval)}tick()}

    function callAPI(requestBody,config,onChunk,onDone,onErr){delete requestBody.stream;GM_xmlhttpRequest({method:'POST',url:config.apiEndpoint,headers:{'Content-Type':'application/json','Authorization':'Bearer '+config.apiKey},data:JSON.stringify(requestBody),timeout:120000,onload:function(resp){let result='';try{const text=resp.responseText||'';if(text.indexOf('data:')!==-1&&text.indexOf('"choices"')!==-1){const lines=text.split('\n');for(const line of lines){const tr=line.trim();if(!tr||!tr.startsWith('data:'))continue;const ds=tr.substring(5).trim();if(ds==='[DONE]')continue;try{const d=JSON.parse(ds);const delta=d.choices&&d.choices[0]&&d.choices[0].delta;if(delta&&delta.content)result+=delta.content}catch(e){}}if(result){if(config.enableStreaming)simulateStream(result,onChunk,onDone,10);else onDone(result);return}}const d=JSON.parse(text);if(d.choices&&d.choices[0]){result=d.choices[0].message?d.choices[0].message.content:(d.choices[0].text||'');if(config.enableStreaming&&result.length>20)simulateStream(result,onChunk,onDone,10);else onDone(result)}else if(d.error)onErr(new Error(d.error.message||JSON.stringify(d.error)));else onErr(new Error('API 返回格式异常'))}catch(e){if(result)onDone(result);else onErr(new Error('解析响应失败'))}},onerror:function(){onErr(new Error('网络请求失败'))},ontimeout:function(){onErr(new Error('请求超时'))}})}

    // ===================== 侧栏 =====================
    let sidebar=null,summaryCache=null,chatHistory=[];

    function closeSidebar(){if(sidebar){sidebar.classList.remove('open');const b=document.getElementById('ai-summary-settings-btn');if(b)b.classList.remove('sidebar-open')}}
    function openSidebar(){if(sidebar){sidebar.classList.add('open');const b=document.getElementById('ai-summary-settings-btn');if(b)b.classList.add('sidebar-open');return}createSidebar()}

    function cycleTheme(){
        const cfg=getConfig();
        const order=['light','dark','system'];
        const idx=order.indexOf(cfg.theme);
        cfg.theme=order[(idx+1)%3];
        saveConfig(cfg);
        applyTheme();
        // 更新按钮
        const btn=document.getElementById('ai-theme-toggle');
        if(btn)btn.setAttribute('data-theme',cfg.theme);
        const map={light:'浅色',dark:'深色',system:'跟随系统'};
        showToast(`🎨 主题:${map[cfg.theme]}`);
    }

    function createSidebar(){const config=getConfig();applyTheme();const el=document.createElement('div');el.id='ai-summary-sidebar';el.style.width=config.sidebarWidth+'px';el.innerHTML=`
        <div id="ai-summary-sidebar-resize"></div>
        <div class="ai-sb-header">
            <h2>🤖 AI 助手</h2>
            <div class="ai-sb-header-actions">
                <button class="ai-sb-btn ai-theme-btn" id="ai-theme-toggle" data-theme="${config.theme}" title="切换主题"></button>
                <button class="ai-sb-btn" id="ai-sb-refresh" title="重新总结">🔄</button>
                <button class="ai-sb-btn" id="ai-sb-close" title="关闭">✕</button>
            </div>
        </div>
        <div class="ai-sb-info"><div class="pt">${esc(document.title)}</div><div class="pu">${esc(location.href)}</div></div>
        <div class="ai-sb-tabs"><button class="ai-sb-tab active" data-mode="summary">📝 总结</button><button class="ai-sb-tab" data-mode="qa">💬 问答</button></div>
        <div class="ai-sb-body" id="ai-sb-content"></div>
        <div class="ai-sb-footer" id="ai-sb-footer" style="display:none"><button class="ai-btn ai-btn-ghost" id="ai-fb-md">📄 MD</button><button class="ai-btn ai-btn-ghost" id="ai-fb-pdf">🖨 PDF</button><button class="ai-btn ai-btn-ghost" id="ai-fb-copy">📋 复制</button></div>
    `;document.body.appendChild(el);sidebar=el;requestAnimationFrame(()=>{el.classList.add('open');const b=document.getElementById('ai-summary-settings-btn');if(b)b.classList.add('sidebar-open')});document.getElementById('ai-sb-close').onclick=closeSidebar;document.getElementById('ai-sb-refresh').onclick=()=>{summaryCache=null;runSummary()};document.getElementById('ai-fb-copy').onclick=()=>{copyResult()};document.getElementById('ai-fb-md').onclick=()=>{exportMD()};document.getElementById('ai-fb-pdf').onclick=()=>{exportPDF()};document.getElementById('ai-theme-toggle').onclick=cycleTheme;el.querySelectorAll('.ai-sb-tab').forEach(tab=>{tab.onclick=()=>{el.querySelectorAll('.ai-sb-tab').forEach(t=>t.classList.remove('active'));tab.classList.add('active');if(tab.dataset.mode==='summary')runSummary();else showQA()}});setupResize(el);runSummary()}

    function runSummary(){const area=document.getElementById('ai-sb-content'),footer=document.getElementById('ai-sb-footer');if(!area)return;const config=getConfig();const ck=`${location.href}_${config.summaryLength}_${config.summaryLanguage}_${config.summaryTone}`;if(summaryCache&&summaryCache.ck===ck){displayResult(summaryCache.result,area,footer);return}area.innerHTML='<div class="ai-sb-loading"><div class="ai-sb-spinner"></div><div class="ai-sb-loading-text">正在分析网页内容...</div></div>';if(footer)footer.style.display='none';const content=extractContent();if(!content||content.length<50){area.innerHTML='<div class="ai-sb-error">⚠️ 无法提取到足够的网页内容</div>';return}const prompt=buildSummaryPrompt(content,config);const reqBody={model:config.modelName,messages:[{role:'system',content:prompt.system},{role:'user',content:prompt.user}],temperature:0.3,max_tokens:prompt.maxTokens};if(config.enableStreaming){area.innerHTML='<div class="ai-sb-content"><span class="ai-stream-cursor" id="ai-stream-el"></span></div>';callAPI(reqBody,config,(text)=>{const s=document.getElementById('ai-stream-el');if(s)s.innerHTML=md(text)},(result)=>{summaryCache={ck,result};displayResult(result,area,footer)},(err)=>{area.innerHTML=`<div class="ai-sb-error">❌ ${esc(err.message)}</div>`})}else{callAPI(reqBody,config,()=>{},(result)=>{summaryCache={ck,result};displayResult(result,area,footer)},(err)=>{area.innerHTML=`<div class="ai-sb-error">❌ ${esc(err.message)}</div>`})}}

    function displayResult(result,area,footer){area.innerHTML=`<div class="ai-sb-content">${md(result)}</div>`;if(footer)footer.style.display='flex';sidebar._result=result}

    function showQA(){const area=document.getElementById('ai-sb-content'),footer=document.getElementById('ai-sb-footer');if(!area)return;if(footer)footer.style.display='none';chatHistory=[];area.innerHTML=`<div class="ai-chat-msgs" id="ai-chat-msgs"><div class="ai-chat-msg assistant">👋 基于当前网页内容向我提问吧!</div></div><div class="ai-chat-input-area"><textarea class="ai-chat-input" id="ai-chat-input" placeholder="输入问题,Enter 发送..." rows="1"></textarea><button class="ai-chat-send" id="ai-chat-send">发送</button></div>`;const input=document.getElementById('ai-chat-input');const doSend=()=>{const q=input.value.trim();if(!q)return;input.value='';input.style.height='auto';sendQA(q)};document.getElementById('ai-chat-send').onclick=doSend;input.onkeydown=(e)=>{if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();doSend()}};input.oninput=()=>{input.style.height='auto';input.style.height=Math.min(input.scrollHeight,100)+'px'}}

    function sendQA(question){const msgsEl=document.getElementById('ai-chat-msgs');if(!msgsEl)return;const config=getConfig();msgsEl.innerHTML+=`<div class="ai-chat-msg user">${esc(question)}</div>`;chatHistory.push({role:'user',content:question});const aDiv=document.createElement('div');aDiv.className='ai-chat-msg assistant';aDiv.textContent='思考中...';msgsEl.appendChild(aDiv);msgsEl.scrollTop=msgsEl.scrollHeight;const content=extractContent();const prompt=buildQAPrompt(content,config,chatHistory);const reqBody={model:config.modelName,messages:prompt.messages,temperature:0.3,max_tokens:prompt.maxTokens};callAPI(reqBody,config,(text)=>{aDiv.innerHTML=md(text);msgsEl.scrollTop=msgsEl.scrollHeight},(result)=>{aDiv.innerHTML=md(result);chatHistory.push({role:'assistant',content:result});msgsEl.scrollTop=msgsEl.scrollHeight},(err)=>{aDiv.textContent='❌'+err.message})}

    function copyResult(){const t=sidebar._result||'';if(!t){showToast('没有可复制的内容');return}navigator.clipboard.writeText(t).then(()=>showToast('✅ 已复制')).catch(()=>{const ta=document.createElement('textarea');ta.value=t;document.body.appendChild(ta);ta.select();document.execCommand('copy');ta.remove();showToast('✅ 已复制')})}
    function exportMD(){const t=sidebar._result||'';if(!t){showToast('没有可导出的内容');return}const b=new Blob([t],{type:'text/markdown;charset=utf-8'});const u=URL.createObjectURL(b);const a=document.createElement('a');a.href=u;a.download=`summary-${document.title.replace(/[^\w\u4e00-\u9fff]/g,'_').substring(0,50)}.md`;a.click();URL.revokeObjectURL(u);showToast('✅ Markdown 已下载')}
    function exportPDF(){const t=sidebar._result||'';if(!t){showToast('没有可导出的内容');return}const h=`<!DOCTYPE html><html><head><meta charset="utf-8"><title>总结</title><style>body{font-family:-apple-system,sans-serif;max-width:700px;margin:40px auto;padding:0 20px;line-height:1.8;color:#202124}h1,h2,h3{color:#1a1a2e}blockquote{border-left:3px solid #667eea;padding:4px 14px;color:#5f6368;margin:8px 0}code{background:#f1f3f4;padding:1px 5px;border-radius:4px;font-size:13px}.meta{color:#80868b;font-size:12px;margin-bottom:20px}</style></head><body><h1>📄 网页总结</h1><div class="meta">来源:${esc(document.title)}<br>URL:${esc(location.href)}<br>时间:${new Date().toLocaleString()}</div><hr>${md(t)}</body></html>`;const w=window.open('','_blank');w.document.write(h);w.document.close();setTimeout(()=>w.print(),500);showToast('✅ 已打开打印窗口')}

    function setupResize(el){const h=document.getElementById('ai-summary-sidebar-resize');if(!h)return;h.onmousedown=(e)=>{e.preventDefault();const sx=e.clientX,sw=el.offsetWidth;h.classList.add('dragging');const mv=(e)=>{el.style.width=Math.min(Math.max(sx-e.clientX+sw,320),800)+'px'};const up=()=>{h.classList.remove('dragging');document.removeEventListener('mousemove',mv);document.removeEventListener('mouseup',up);const c=getConfig();c.sidebarWidth=el.offsetWidth;saveConfig(c)};document.addEventListener('mousemove',mv);document.addEventListener('mouseup',up)}}

    // ===================== 设置面板 =====================
    function createSettings(){const config=getConfig();document.querySelectorAll('.ai-overlay').forEach(e=>e.remove());const ov=document.createElement('div');ov.className='ai-overlay';ov.innerHTML=`
        <div class="ai-panel">
            <div class="ai-panel-header"><h2>⚙️ AI 助手设置</h2><button class="ai-panel-close" id="ai-set-close">✕</button></div>
            <div class="ai-panel-body">
                <div class="ai-privacy"><strong>🔒 隐私声明:</strong>网页内容将发送至您配置的 AI API 服务器处理。插件不收集/存储数据,配置保存在本地浏览器。</div>
                <div class="ai-fg"><label>🔑 API Key</label><div class="desc">支持 OpenAI / DeepSeek / Claude 等兼容接口</div><input type="password" id="s-key" value="${esc(config.apiKey)}" placeholder="sk-xxxxxxxx" /></div>
                <div class="ai-fg"><label>🌐 API 端点</label><input type="text" id="s-endpoint" value="${esc(config.apiEndpoint)}" /></div>
                <div class="ai-fg"><label>🤖 模型</label><select id="s-model">${['deepseek-chat','deepseek-v4-flash','deepseek-reasoner','gpt-4o','gpt-4o-mini','gpt-4-turbo','gpt-3.5-turbo','claude-3-5-sonnet-20241022'].map(m=>`<option value="${m}" ${config.modelName===m?'selected':''}>${m}</option>`).join('')}<option value="custom" ${!['deepseek-chat','deepseek-v4-flash','deepseek-reasoner','gpt-4o','gpt-4o-mini','gpt-4-turbo','gpt-3.5-turbo','claude-3-5-sonnet-20241022'].includes(config.modelName)?'selected':''}>自定义</option></select></div>
                <div class="ai-fg" id="s-custom-grp" style="display:none"><label>自定义模型名</label><input type="text" id="s-custom" value="${esc(config.modelName)}" /></div>
                <div class="ai-fg"><label>📏 最大内容长度</label><input type="text" id="s-maxlen" value="${config.maxContentLength}" /></div>
                <div class="ai-fg"><label>⚡ 流式输出</label><div class="desc">模拟逐字显示,减少等待感</div><label class="mod-check"><input type="checkbox" id="s-stream" ${config.enableStreaming?'checked':''} /> 启用</label></div>
                <div class="ai-divider"></div>
                <div class="ai-fg"><label>🎨 主题外观</label><div class="desc">切换深色/浅色模式,或跟随系统</div><select id="s-theme"><option value="light" ${config.theme==='light'?'selected':''}>☀️ 浅色</option><option value="dark" ${config.theme==='dark'?'selected':''}>🌙 深色</option><option value="system" ${config.theme==='system'?'selected':''}>💡 跟随系统</option></select></div>
                <div class="ai-fg"><label>🌍 总结语言</label><select id="s-lang">${LANG_OPTIONS.map(l=>`<option value="${l.value}" ${config.summaryLanguage===l.value?'selected':''}>${l.label}</option>`).join('')}</select></div>
                <div class="ai-fg"><label>📏 总结长度</label><select id="s-length">${LENGTH_OPTIONS.map(l=>`<option value="${l.value}" ${config.summaryLength===l.value?'selected':''}>${l.label}</option>`).join('')}</select></div>
                <div class="ai-fg"><label>🎨 语气风格</label><select id="s-tone">${TONE_OPTIONS.map(t=>`<option value="${t.value}" ${config.summaryTone===t.value?'selected':''}>${t.label}</option>`).join('')}</select></div>
                <div class="ai-divider"></div>
                <div class="ai-fg"><label>📝 系统提示词</label><div class="desc">自定义 AI 角色和行为</div><textarea id="s-prompt" rows="3">${esc(config.systemPrompt)}</textarea><div style="margin-top:4px"><button class="ai-btn ai-btn-secondary" id="s-reset-prompt" style="font-size:11px;padding:3px 10px">恢复默认</button></div></div>
                <div class="ai-divider"></div>
                <div class="ai-fg"><label>⌨️ 快捷键</label><div class="shortcut-row"><label class="mod-check"><input type="checkbox" id="s-ctrl" ${config.useCtrl?'checked':''} /> Ctrl</label><label class="mod-check"><input type="checkbox" id="s-shift" ${config.useShift?'checked':''} /> Shift</label><label class="mod-check"><input type="checkbox" id="s-alt" ${config.useAlt?'checked':''} /> Alt</label><span style="color:var(--ai-text-secondary)">+</span><input type="text" class="key-input" id="s-key-input" value="${config.shortcutKey.toUpperCase()}" maxlength="1" /></div><div style="margin-top:6px"><span class="shortcut-preview" id="s-preview">${getShortcutText(config)}</span></div></div>
                <div class="ai-btn-group"><button class="ai-btn ai-btn-secondary" id="s-reset">恢复默认</button><button class="ai-btn ai-btn-primary" id="s-save">💾 保存</button></div>
            </div>
        </div>
    `;document.body.appendChild(ov);document.getElementById('ai-set-close').onclick=()=>ov.remove();ov.onclick=(e)=>{if(e.target===ov)ov.remove()};const ms=document.getElementById('s-model');const cg=document.getElementById('s-custom-grp');if(ms.value==='custom')cg.style.display='block';ms.onchange=()=>{cg.style.display=ms.value==='custom'?'block':'none'};document.getElementById('s-reset-prompt').onclick=()=>{document.getElementById('s-prompt').value=DEFAULT_CONFIG.systemPrompt;showToast('✅ 已恢复默认提示词')};const up=()=>{const c={useCtrl:document.getElementById('s-ctrl').checked,useShift:document.getElementById('s-shift').checked,useAlt:document.getElementById('s-alt').checked,shortcutKey:document.getElementById('s-key-input').value||'Q'};document.getElementById('s-preview').textContent=getShortcutText(c)};['s-ctrl','s-shift','s-alt','s-key-input'].forEach(id=>{document.getElementById(id).addEventListener('input',up);document.getElementById(id).addEventListener('change',up)});document.getElementById('s-key-input').addEventListener('input',(e)=>{e.target.value=e.target.value.replace(/[^a-zA-Z]/g,'').substring(0,1)});document.getElementById('s-reset').onclick=()=>{if(confirm('确定恢复所有设置为默认值吗?')){saveConfig(DEFAULT_CONFIG);ov.remove();applyTheme();showToast('✅ 已恢复默认');setTimeout(()=>createSettings(),300)}};document.getElementById('s-save').onclick=()=>{const nc={apiKey:document.getElementById('s-key').value.trim(),apiEndpoint:document.getElementById('s-endpoint').value.trim(),modelName:ms.value==='custom'?document.getElementById('s-custom').value.trim():ms.value,maxContentLength:parseInt(document.getElementById('s-maxlen').value)||8000,enableStreaming:document.getElementById('s-stream').checked,theme:document.getElementById('s-theme').value,summaryLanguage:document.getElementById('s-lang').value,summaryLength:document.getElementById('s-length').value,summaryTone:document.getElementById('s-tone').value,systemPrompt:document.getElementById('s-prompt').value.trim()||DEFAULT_CONFIG.systemPrompt,shortcutKey:(document.getElementById('s-key-input').value||'q').toLowerCase(),useCtrl:document.getElementById('s-ctrl').checked,useShift:document.getElementById('s-shift').checked,useAlt:document.getElementById('s-alt').checked,sidebarWidth:config.sidebarWidth};if(!nc.apiKey){showToast('⚠️ 请输入 API Key');return}if(!nc.apiEndpoint){showToast('⚠️ 请输入 API 端点');return}saveConfig(nc);ov.remove();summaryCache=null;applyTheme();showToast('✅ 已保存,快捷键:'+getShortcutText(nc))}}

    // ===================== 初始化 =====================
    function createBtn(){if(document.getElementById('ai-summary-settings-btn'))return;const b=document.createElement('button');b.id='ai-summary-settings-btn';b.title='AI 助手设置';b.textContent='⚙️';document.body.appendChild(b);b.onclick=()=>createSettings()}

    function regShortcut(){document.addEventListener('keydown',(e)=>{const c=getConfig();const km=e.key.toLowerCase()===c.shortcutKey.toLowerCase();const cm=c.useCtrl?(e.ctrlKey||e.metaKey):(!e.ctrlKey&&!e.metaKey);const sm=c.useShift?e.shiftKey:!e.shiftKey;const am=c.useAlt?e.altKey:!e.altKey;if(km&&cm&&sm&&am){const tag=e.target.tagName.toLowerCase();if(tag==='input'||tag==='textarea'||tag==='select'||e.target.isContentEditable)return;e.preventDefault();e.stopPropagation();if(sidebar&&sidebar.classList.contains('open'))closeSidebar();else{if(!c.apiKey){showToast('⚠️ 请先设置 API Key');createSettings();return}openSidebar()}}if(e.key==='Escape')closeSidebar()})}

    GM_registerMenuCommand('⚙️ 设置',()=>createSettings());
    GM_registerMenuCommand('🤖 总结当前页面',()=>{const c=getConfig();if(!c.apiKey){showToast('⚠️ 请先设置 API Key');createSettings();return}openSidebar()});

    function init(){applyTheme();createBtn();regShortcut();console.log('[AI 助手] 已加载,快捷键:'+getShortcutText(getConfig()))}
    if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',init);else init();
})();