微博 AI 概括工具

使用 LLM 对微博博文进行 AI 概括总结

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         微博 AI 概括工具
// @namespace    https://github.com/sixiaolong1117/Tampermonkey
// @version      0.2
// @description  使用 LLM 对微博博文进行 AI 概括总结
// @license      MIT
// @icon         https://weibo.com/favicon.ico
// @author       SI Xiaolong
// @match        https://weibo.com/*
// @match        https://*.weibo.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_notification
// @connect      localhost
// @connect      127.0.0.1
// @connect      cloud.infini-ai.com
// ==/UserScript==

(function() {
    'use strict';

    // 默认配置
    const DEFAULT_CONFIG = {
        aiProvider: 'ollama', // 'ollama' 或 'infini'
        
        // Ollama 配置
        ollamaUrl: 'http://localhost:11434',
        ollamaModel: 'llama3.2',
        
        // Infini-AI 配置
        infiniApiKey: '',
        infiniModel: 'deepseek-v3.2-exp',
        
        // 通用配置
        maxLength: 1000,
        prompt: '请用简洁的中文总结以下微博内容,提取核心要点,限制在100字以内:'
    };

    // 工具函数:检测深色模式
    const isDarkMode = () => {
        return document.documentElement.classList.contains('theme-dark') ||
               document.body.classList.contains('dark') ||
               window.matchMedia('(prefers-color-scheme: dark)').matches;
    };

    // 工具函数:获取主题颜色
    const getColors = () => {
        const dark = isDarkMode();
        return {
            panelBg: dark ? '#1e1e1e' : '#ffffff',
            panelBorder: dark ? '#444444' : '#1976D2',
            btnContainerBg: dark ? '#2a2a2a' : '#f5f5f5',
            textPrimary: dark ? '#e0e0e0' : '#333333',
            textSecondary: dark ? '#b0b0b0' : '#666666',
            primaryBtn: dark ? '#1565C0' : '#1976D2',
            successBtn: dark ? '#2E7D32' : '#4caf50',
            infoBtn: dark ? '#0277BD' : '#00ACC1',
            warningBtn: dark ? '#E65100' : '#FF6F00',
            errorBg: dark ? '#3d1f1f' : '#ffebee',
            errorBorder: dark ? '#8B0000' : '#ef5350',
            errorText: dark ? '#ff6b6b' : '#c62828',
            shadow: dark ? '0 4px 12px rgba(0,0,0,0.5)' : '0 4px 12px rgba(0,0,0,0.15)',
            inputBg: dark ? '#2a2a2a' : '#ffffff',
            inputBorder: dark ? '#555555' : '#cccccc'
        };
    };

    class OllamaSummarizer {
        constructor() {
            this.config = this.loadConfig();
            this.initMenu();
            this.observeFeed();
        }

        loadConfig() {
            const saved = GM_getValue('ollama_config');
            return { ...DEFAULT_CONFIG, ...saved };
        }

        saveConfig(config) {
            this.config = { ...this.config, ...config };
            GM_setValue('ollama_config', this.config);
        }

        initMenu() {
            GM_registerMenuCommand('⚙️ AI 设置', () => this.showSettingsPanel());
        }

        // ============ 设置面板相关方法 ============
        
        showSettingsPanel() {
            // 避免重复创建
            if (document.getElementById('ollama-settings-panel')) {
                document.getElementById('ollama-settings-panel').style.display = 'flex';
                return;
            }

            const overlay = this.createOverlay();
            const panel = this.createPanel();
            
            overlay.appendChild(panel);
            document.body.appendChild(overlay);
            
            this.bindSettingsEvents(overlay, panel);
        }

        createOverlay() {
            const overlay = document.createElement('div');
            overlay.id = 'ollama-settings-panel';
            overlay.style.cssText = `
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background: rgba(0, 0, 0, 0.5);
                display: flex;
                justify-content: center;
                align-items: center;
                z-index: 99999;
            `;
            return overlay;
        }

        createPanel() {
            const colors = getColors();
            const panel = document.createElement('div');
            panel.style.cssText = `
                background: ${colors.panelBg};
                border-radius: 12px;
                box-shadow: ${colors.shadow};
                width: 500px;
                max-width: 90%;
                max-height: 80vh;
                overflow-y: auto;
            `;

            panel.innerHTML = `
                ${this.createPanelHeader(colors)}
                ${this.createPanelBody(colors)}
            `;

            return panel;
        }

        createPanelHeader(colors) {
            return `
                <div style="
                    padding: 20px;
                    border-bottom: 2px solid ${colors.panelBorder};
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                ">
                    <h2 style="margin: 0; color: ${colors.textPrimary}; font-size: 20px;">⚙️ AI 设置</h2>
                    <button id="close-settings" style="
                        background: transparent;
                        border: none;
                        font-size: 24px;
                        cursor: pointer;
                        color: ${colors.textSecondary};
                        padding: 0;
                        width: 30px;
                        height: 30px;
                        line-height: 30px;
                    ">×</button>
                </div>
            `;
        }

        createPanelBody(colors) {
            return `
                <div style="padding: 20px;">
                    ${this.createProviderSelector(colors)}
                    ${this.createOllamaSettings(colors)}
                    ${this.createInfiniSettings(colors)}
                    ${this.createCommonSettings(colors)}
                    ${this.createActionButtons(colors)}
                </div>
            `;
        }

        createProviderSelector(colors) {
            return `
                <div style="margin-bottom: 20px;">
                    <label style="
                        display: block;
                        margin-bottom: 8px;
                        color: ${colors.textPrimary};
                        font-weight: 500;
                    ">🤖 选择 AI 平台</label>
                    <select id="ai-provider-select" style="
                        width: 100%;
                        padding: 10px;
                        border: 1px solid ${colors.inputBorder};
                        border-radius: 6px;
                        background: ${colors.inputBg};
                        color: ${colors.textPrimary};
                        font-size: 14px;
                        box-sizing: border-box;
                    ">
                        <option value="ollama" ${this.config.aiProvider === 'ollama' ? 'selected' : ''}>Ollama (本地模型)</option>
                        <option value="infini" ${this.config.aiProvider === 'infini' ? 'selected' : ''}>Infini-AI (云端)</option>
                    </select>
                </div>
            `;
        }

        createOllamaSettings(colors) {
            const display = this.config.aiProvider === 'ollama' ? 'block' : 'none';
            return `
                <div id="ollama-settings" style="display: ${display};">
                    <div style="margin-bottom: 20px;">
                        <label style="
                            display: block;
                            margin-bottom: 8px;
                            color: ${colors.textPrimary};
                            font-weight: 500;
                        ">🌐 Ollama 地址</label>
                        <input
                            type="text"
                            id="ollama-url-input"
                            value="${this.config.ollamaUrl}"
                            placeholder="http://localhost:11434"
                            style="
                                width: 100%;
                                padding: 10px;
                                border: 1px solid ${colors.inputBorder};
                                border-radius: 6px;
                                background: ${colors.inputBg};
                                color: ${colors.textPrimary};
                                font-size: 14px;
                                box-sizing: border-box;
                            "
                        />
                        <small style="color: ${colors.textSecondary}; display: block; margin-top: 5px;">
                            默认: http://localhost:11434
                        </small>
                    </div>

                    <div style="margin-bottom: 20px;">
                        <label style="
                            display: block;
                            margin-bottom: 8px;
                            color: ${colors.textPrimary};
                            font-weight: 500;
                        ">🧠 模型名称</label>
                        <input
                            type="text"
                            id="ollama-model-input"
                            value="${this.config.ollamaModel}"
                            placeholder="llama3.2"
                            style="
                                width: 100%;
                                padding: 10px;
                                border: 1px solid ${colors.inputBorder};
                                border-radius: 6px;
                                background: ${colors.inputBg};
                                color: ${colors.textPrimary};
                                font-size: 14px;
                                box-sizing: border-box;
                            "
                        />
                        <small style="color: ${colors.textSecondary}; display: block; margin-top: 5px;">
                            推荐: llama3.2, qwen2.5:7b, mistral:7b
                        </small>
                    </div>
                </div>
            `;
        }

        createInfiniSettings(colors) {
            const display = this.config.aiProvider === 'infini' ? 'block' : 'none';
            return `
                <div id="infini-settings" style="display: ${display};">
                    <div style="margin-bottom: 20px;">
                        <label style="
                            display: block;
                            margin-bottom: 8px;
                            color: ${colors.textPrimary};
                            font-weight: 500;
                        ">🔑 Infini API Key</label>
                        <input
                            type="password"
                            id="infini-api-key-input"
                            value="${this.config.infiniApiKey}"
                            placeholder="输入 API Key"
                            style="
                                width: 100%;
                                padding: 10px;
                                border: 1px solid ${colors.inputBorder};
                                border-radius: 6px;
                                background: ${colors.inputBg};
                                color: ${colors.textPrimary};
                                font-size: 14px;
                                box-sizing: border-box;
                            "
                        />
                        <small style="color: ${colors.textSecondary}; display: block; margin-top: 5px;">
                            访问 <a href="https://cloud.infini-ai.com" target="_blank" style="color: ${colors.infoBtn};">Infini-AI</a> 获取 API Key
                        </small>
                    </div>

                    <div style="margin-bottom: 20px;">
                        <label style="
                            display: block;
                            margin-bottom: 8px;
                            color: ${colors.textPrimary};
                            font-weight: 500;
                        ">🧠 模型名称</label>
                        <input
                            type="text"
                            id="infini-model-input"
                            value="${this.config.infiniModel}"
                            placeholder="deepseek-v3.2-exp"
                            style="
                                width: 100%;
                                padding: 10px;
                                border: 1px solid ${colors.inputBorder};
                                border-radius: 6px;
                                background: ${colors.inputBg};
                                color: ${colors.textPrimary};
                                font-size: 14px;
                                box-sizing: border-box;
                            "
                        />
                        <small style="color: ${colors.textSecondary}; display: block; margin-top: 5px;">
                            推荐: deepseek-v3.2-exp, qwen2.5-72b-instruct
                        </small>
                    </div>
                </div>
            `;
        }

        createCommonSettings(colors) {
            return `
                <div style="margin-bottom: 20px;">
                    <label style="
                        display: block;
                        margin-bottom: 8px;
                        color: ${colors.textPrimary};
                        font-weight: 500;
                    ">📏 最大文本长度(字数)</label>
                    <input
                        type="number"
                        id="max-length-input"
                        value="${this.config.maxLength}"
                        min="500"
                        max="5000"
                        step="100"
                        style="
                            width: 100%;
                            padding: 10px;
                            border: 1px solid ${colors.inputBorder};
                            border-radius: 6px;
                            background: ${colors.inputBg};
                            color: ${colors.textPrimary};
                            font-size: 14px;
                            box-sizing: border-box;
                        "
                    />
                    <small style="color: ${colors.textSecondary}; display: block; margin-top: 5px;">
                        建议: 1000-2000 字(越大越准确,但速度越慢)
                    </small>
                </div>

                <div style="margin-bottom: 20px;">
                    <label style="
                        display: block;
                        margin-bottom: 8px;
                        color: ${colors.textPrimary};
                        font-weight: 500;
                    ">💬 提示词</label>
                    <textarea
                        id="ollama-prompt-input"
                        placeholder="请输入概括提示词"
                        style="
                            width: 100%;
                            padding: 10px;
                            border: 1px solid ${colors.inputBorder};
                            border-radius: 6px;
                            background: ${colors.inputBg};
                            color: ${colors.textPrimary};
                            font-size: 14px;
                            box-sizing: border-box;
                            resize: vertical;
                            min-height: 80px;
                        "
                    >${this.config.prompt}</textarea>
                    <small style="color: ${colors.textSecondary}; display: block; margin-top: 5px;">
                        用于指导AI如何概括微博内容
                    </small>
                </div>
            `;
        }

        createActionButtons(colors) {
            return `
                <button id="test-connection" style="
                    width: 100%;
                    padding: 12px;
                    background: ${colors.infoBtn};
                    color: white;
                    border: none;
                    border-radius: 6px;
                    cursor: pointer;
                    font-size: 14px;
                    font-weight: 500;
                    margin-bottom: 15px;
                ">🔌 测试连接</button>

                <div id="test-result" style="
                    padding: 10px;
                    border-radius: 6px;
                    font-size: 13px;
                    display: none;
                    margin-bottom: 15px;
                "></div>

                <div style="display: flex; gap: 10px;">
                    <button id="save-settings" style="
                        flex: 1;
                        padding: 12px;
                        background: ${colors.successBtn};
                        color: white;
                        border: none;
                        border-radius: 6px;
                        cursor: pointer;
                        font-size: 14px;
                        font-weight: 500;
                    ">💾 保存设置</button>

                    <button id="reset-settings" style="
                        flex: 1;
                        padding: 12px;
                        background: ${colors.warningBtn};
                        color: white;
                        border: none;
                        border-radius: 6px;
                        cursor: pointer;
                        font-size: 14px;
                        font-weight: 500;
                    ">🔄 恢复默认</button>
                </div>
            `;
        }

        bindSettingsEvents(overlay, panel) {
            const closePanel = () => {
                overlay.style.display = 'none';
            };

            // 关闭按钮
            panel.querySelector('#close-settings').onclick = closePanel;
            overlay.onclick = (e) => {
                if (e.target === overlay) closePanel();
            };

            // AI 平台切换
            panel.querySelector('#ai-provider-select').addEventListener('change', (e) => {
                const isOllama = e.target.value === 'ollama';
                panel.querySelector('#ollama-settings').style.display = isOllama ? 'block' : 'none';
                panel.querySelector('#infini-settings').style.display = isOllama ? 'none' : 'block';
            });

            // 测试连接
            panel.querySelector('#test-connection').onclick = () => this.testConnection(panel);

            // 保存设置
            panel.querySelector('#save-settings').onclick = () => this.saveSettings(panel, closePanel);

            // 恢复默认
            panel.querySelector('#reset-settings').onclick = () => this.resetSettings(panel);
        }

        async testConnection(panel) {
            const testBtn = panel.querySelector('#test-connection');
            const testResult = panel.querySelector('#test-result');
            const colors = getColors();
            
            const provider = panel.querySelector('#ai-provider-select').value;

            testBtn.disabled = true;
            testBtn.textContent = '🔄 测试中...';
            testResult.style.display = 'block';
            testResult.style.background = colors.inputBg;
            testResult.style.color = colors.textSecondary;
            testResult.textContent = '正在连接...';

            try {
                if (provider === 'ollama') {
                    await this.testOllamaConnection(panel, testResult, colors);
                } else {
                    await this.testInfiniConnection(panel, testResult, colors);
                }
            } catch (error) {
                testResult.style.background = colors.errorBg;
                testResult.style.color = colors.errorText;
                testResult.textContent = `❌ 连接失败: ${error.message}`;
            }

            testBtn.disabled = false;
            testBtn.textContent = '🔌 测试连接';
        }

        testOllamaConnection(panel, testResult, colors) {
            const url = panel.querySelector('#ollama-url-input').value;

            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: 'GET',
                    url: `${url}/api/tags`,
                    timeout: 5000,
                    onload: (response) => {
                        if (response.status === 200) {
                            const data = JSON.parse(response.responseText);
                            const models = data.models || [];
                            testResult.style.background = '#e8f5e9';
                            testResult.style.color = '#2e7d32';
                            testResult.innerHTML = `
                                ✅ 连接成功!<br>
                                发现 ${models.length} 个模型: ${models.map(m => m.name).join(', ') || '无'}
                            `;
                            resolve();
                        } else {
                            reject(new Error(`HTTP ${response.status}`));
                        }
                    },
                    onerror: reject,
                    ontimeout: () => reject(new Error('连接超时'))
                });
            });
        }

        testInfiniConnection(panel, testResult, colors) {
            const apiKey = panel.querySelector('#infini-api-key-input').value.trim();
            const model = panel.querySelector('#infini-model-input').value.trim();

            if (!apiKey) {
                testResult.style.background = colors.errorBg;
                testResult.style.color = colors.errorText;
                testResult.textContent = '❌ 请先输入 API Key';
                return Promise.reject(new Error('未输入 API Key'));
            }

            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: 'POST',
                    url: 'https://cloud.infini-ai.com/maas/v1/chat/completions',
                    headers: {
                        'Authorization': `Bearer ${apiKey}`,
                        'Content-Type': 'application/json'
                    },
                    data: JSON.stringify({
                        model: model,
                        messages: [{ role: 'user', content: 'test' }],
                        max_tokens: 10
                    }),
                    timeout: 10000,
                    onload: (response) => {
                        if (response.status === 200) {
                            testResult.style.background = '#e8f5e9';
                            testResult.style.color = '#2e7d32';
                            testResult.innerHTML = `✅ 连接成功!<br>模型: ${model}`;
                            resolve();
                        } else {
                            reject(new Error(`API 错误: ${response.status}`));
                        }
                    },
                    onerror: reject,
                    ontimeout: () => reject(new Error('连接超时'))
                });
            });
        }

        saveSettings(panel, closePanel) {
            const provider = panel.querySelector('#ai-provider-select').value;
            const maxLength = parseInt(panel.querySelector('#max-length-input').value);
            const prompt = panel.querySelector('#ollama-prompt-input').value.trim();

            // 验证通用配置
            if (maxLength < 500 || maxLength > 5000) {
                alert('❌ 最大文本长度必须在 500-5000 之间!');
                return;
            }

            const newConfig = {
                aiProvider: provider,
                maxLength: maxLength,
                prompt: prompt
            };

            // 验证并保存 Ollama 配置
            if (provider === 'ollama') {
                const url = panel.querySelector('#ollama-url-input').value.trim();
                const model = panel.querySelector('#ollama-model-input').value.trim();

                if (!url || !model) {
                    alert('❌ 请填写完整的 Ollama 配置!');
                    return;
                }

                newConfig.ollamaUrl = url;
                newConfig.ollamaModel = model;
            }
            // 验证并保存 Infini 配置
            else if (provider === 'infini') {
                const apiKey = panel.querySelector('#infini-api-key-input').value.trim();
                const model = panel.querySelector('#infini-model-input').value.trim();

                if (!apiKey || !model) {
                    alert('❌ 请填写完整的 Infini-AI 配置!');
                    return;
                }

                newConfig.infiniApiKey = apiKey;
                newConfig.infiniModel = model;
            }

            this.saveConfig(newConfig);
            alert('✅ 设置已保存!');
            closePanel();
        }

        resetSettings(panel) {
            if (confirm('确定要恢复默认设置吗?')) {
                panel.querySelector('#ai-provider-select').value = DEFAULT_CONFIG.aiProvider;
                panel.querySelector('#ollama-url-input').value = DEFAULT_CONFIG.ollamaUrl;
                panel.querySelector('#ollama-model-input').value = DEFAULT_CONFIG.ollamaModel;
                panel.querySelector('#infini-api-key-input').value = DEFAULT_CONFIG.infiniApiKey;
                panel.querySelector('#infini-model-input').value = DEFAULT_CONFIG.infiniModel;
                panel.querySelector('#max-length-input').value = DEFAULT_CONFIG.maxLength;
                panel.querySelector('#ollama-prompt-input').value = DEFAULT_CONFIG.prompt;
                
                // 触发平台切换事件
                const event = new Event('change');
                panel.querySelector('#ai-provider-select').dispatchEvent(event);
            }
        }

        // ============ AI 调用相关方法 ============

        async callAI(content) {
            const fullPrompt = this.config.prompt + '\n\n' + content;
            
            if (this.config.aiProvider === 'infini') {
                return await this.callInfiniAI(fullPrompt);
            } else {
                return await this.callOllamaAPI(fullPrompt);
            }
        }

        callOllamaAPI(prompt) {
            return new Promise((resolve, reject) => {
                const payload = {
                    model: this.config.ollamaModel,
                    prompt: prompt,
                    stream: false
                };

                GM_xmlhttpRequest({
                    method: 'POST',
                    url: `${this.config.ollamaUrl}/api/generate`,
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    data: JSON.stringify(payload),
                    responseType: 'json',
                    timeout: 60000,
                    onload: (response) => {
                        if (response.status === 200) {
                            const data = response.response;
                            resolve(data.response || '无返回内容');
                        } else {
                            reject(new Error(`Ollama API 错误: ${response.status}`));
                        }
                    },
                    onerror: (error) => {
                        reject(new Error(`网络错误: ${error.statusText || 'Unknown'}`));
                    },
                    ontimeout: () => {
                        reject(new Error('请求超时'));
                    }
                });
            });
        }

        callInfiniAI(prompt) {
            return new Promise((resolve, reject) => {
                if (!this.config.infiniApiKey) {
                    reject(new Error('未配置 Infini API Key'));
                    return;
                }

                GM_xmlhttpRequest({
                    method: 'POST',
                    url: 'https://cloud.infini-ai.com/maas/v1/chat/completions',
                    headers: {
                        'Authorization': `Bearer ${this.config.infiniApiKey}`,
                        'Content-Type': 'application/json'
                    },
                    data: JSON.stringify({
                        model: this.config.infiniModel,
                        messages: [
                            { role: 'user', content: prompt }
                        ]
                    }),
                    timeout: 60000,
                    onload: (response) => {
                        if (response.status === 200) {
                            try {
                                const data = JSON.parse(response.responseText);
                                const result = data.choices?.[0]?.message?.content || '无返回内容';
                                resolve(result);
                            } catch (e) {
                                reject(new Error('解析响应失败'));
                            }
                        } else {
                            reject(new Error(`Infini API 错误: ${response.status}`));
                        }
                    },
                    onerror: (error) => {
                        reject(new Error(`网络错误: ${error.statusText || 'Unknown'}`));
                    },
                    ontimeout: () => {
                        reject(new Error('请求超时'));
                    }
                });
            });
        }

        // ============ 微博相关方法 ============

        observeFeed() {
            const observer = new MutationObserver((mutations) => {
                mutations.forEach((mutation) => {
                    mutation.addedNodes.forEach((node) => {
                        if (node.nodeType === 1) {
                            this.processFeedItems(node);
                        }
                    });
                });
            });

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

            // 初始处理
            this.processFeedItems(document.body);
        }

        processFeedItems(container) {
            const feedItems = container.querySelectorAll?.('.Feed_body_3R0rO') || [];

            feedItems.forEach((feedItem) => {
                if (feedItem.querySelector('.weibo-ai-summary-btn')) return;

                const header = feedItem.querySelector('.woo-box-flex');
                const content = feedItem.querySelector('.detail_wbtext_4CRf9');

                if (header && content) {
                    this.addSummaryButton(header, content);
                }
            });
        }

        addSummaryButton(header, contentElement) {
            // 尝试多个可能的位置插入按钮,避免和用户名交互冲突
            const possibleParents = [
                header.querySelector('.head_main_4K3n4'), // 头部主容器
                header.querySelector('.woo-box-flex'),    // flex 容器
                header                                     // 最后兜底:直接插入 header
            ];

            let targetParent = null;
            for (const parent of possibleParents) {
                if (parent && !parent.classList.contains('head_name_24eEB')) {
                    targetParent = parent;
                    break;
                }
            }

            if (!targetParent) return;

            // 创建按钮容器,独立于用户名
            const buttonContainer = document.createElement('div');
            buttonContainer.className = 'weibo-ai-button-container';
            buttonContainer.style.cssText = `
                display: inline-flex;
                align-items: center;
                margin-left: 10px;
                flex-shrink: 0;
            `;

            const button = document.createElement('button');
            button.className = 'weibo-ai-summary-btn';
            button.textContent = 'AI 概括';
            button.style.cssText = `
                padding: 2px 8px;
                border: 1px solid #d0d0d0;
                border-radius: 3px;
                background: transparent;
                color: #8590a6;
                font-size: 12px;
                cursor: pointer;
                transition: all 0.2s;
                flex-shrink: 0;
            `;

            // 悬停效果
            button.addEventListener('mouseenter', () => {
                button.style.borderColor = '#667eea';
                button.style.color = '#667eea';
                button.style.background = 'rgba(102, 126, 234, 0.05)';
            });

            button.addEventListener('mouseleave', () => {
                button.style.borderColor = '#d0d0d0';
                button.style.color = '#8590a6';
                button.style.background = 'transparent';
            });

            // 阻止事件冒泡,避免触发父元素的点击事件
            button.addEventListener('click', (e) => {
                e.preventDefault();
                e.stopPropagation();
                e.stopImmediatePropagation();
                this.summarizeContent(contentElement, button);
            });

            // 阻止容器的点击事件冒泡
            buttonContainer.addEventListener('click', (e) => {
                e.stopPropagation();
            });

            buttonContainer.appendChild(button);

            // 插入到合适的位置
            if (targetParent === header) {
                // 如果是直接插入 header,添加到第一个子元素后面
                const firstChild = header.firstElementChild;
                if (firstChild && firstChild.nextSibling) {
                    header.insertBefore(buttonContainer, firstChild.nextSibling);
                } else {
                    header.appendChild(buttonContainer);
                }
            } else {
                targetParent.appendChild(buttonContainer);
            }
        }

        async summarizeContent(contentElement, button) {
            const originalText = button.textContent;
            button.textContent = '概括中...';
            button.disabled = true;

            try {
                const content = this.extractTextContent(contentElement);
                const summary = await this.callAI(content);
                this.showSummaryPopup(summary, contentElement);
            } catch (error) {
                console.error('概括失败:', error);
                GM_notification({
                    text: `概括失败: ${error.message}`,
                    title: 'AI 概括错误',
                    timeout: 3000
                });
            } finally {
                button.textContent = originalText;
                button.disabled = false;
            }
        }

        extractTextContent(element) {
            let text = element.textContent || element.innerText || '';
            // 清理文本,移除多余空格和换行
            text = text.replace(/\s+/g, ' ').trim();
            // 限制长度
            return text.substring(0, this.config.maxLength);
        }

        showSummaryPopup(summary, contentElement) {
            // 移除已存在的弹窗
            const existingPopup = document.querySelector('.weibo-summary-popup');
            if (existingPopup) {
                existingPopup.remove();
            }

            const colors = getColors();

            const popup = document.createElement('div');
            popup.className = 'weibo-summary-popup';
            popup.style.cssText = `
                position: absolute;
                background: ${colors.panelBg};
                border: 1px solid ${colors.panelBorder};
                border-radius: 8px;
                padding: 15px;
                max-width: 400px;
                box-shadow: ${colors.shadow};
                z-index: 1000;
                font-family: system-ui, -apple-system, sans-serif;
                font-size: 14px;
                line-height: 1.5;
                color: ${colors.textPrimary};
            `;

            const providerName = this.config.aiProvider === 'infini' 
                ? `Infini-AI (${this.config.infiniModel})`
                : `Ollama (${this.config.ollamaModel})`;

            popup.innerHTML = `
                <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
                    <strong style="color: #667eea;">AI 概括</strong>
                    <button class="close-popup" style="background: none; border: none; font-size: 16px; cursor: pointer; color: ${colors.textSecondary};">×</button>
                </div>
                <div class="summary-content" style="white-space: pre-wrap; margin-bottom: 10px;">${summary}</div>
                <div style="font-size: 12px; color: ${colors.textSecondary}; text-align: right;">
                    ${providerName}
                </div>
            `;

            // 定位弹窗
            const rect = contentElement.getBoundingClientRect();
            popup.style.top = `${rect.bottom + window.scrollY + 10}px`;
            popup.style.left = `${rect.left + window.scrollX}px`;

            document.body.appendChild(popup);

            // 关闭按钮事件
            popup.querySelector('.close-popup').addEventListener('click', () => {
                popup.remove();
            });

            // 点击外部关闭
            setTimeout(() => {
                const closeHandler = (e) => {
                    if (!popup.contains(e.target)) {
                        popup.remove();
                        document.removeEventListener('click', closeHandler);
                    }
                };
                document.addEventListener('click', closeHandler);
            }, 100);
        }
    }

    // 初始化
    let initialized = false;
    function init() {
        if (!initialized && document.querySelector('.Feed_body_3R0rO')) {
            initialized = true;
            new OllamaSummarizer();
        }
    }

    // 页面加载时初始化
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    // 处理动态加载的内容
    setInterval(init, 1000);
})();