Greasy Fork

Greasy Fork is available in English.

AI网页内容总结

自动调用AI总结网页内容并流式显示

当前为 2025-03-14 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         AI网页内容总结
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  自动调用AI总结网页内容并流式显示
// @author       AiCoder
// @match        *://*/*
// @connect *
// @license MIT
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/turndown.min.js
// ==/UserScript==

(function() {
    'use strict';

    // 配置参数
    const CONFIG = {
        // 替换为你的API密钥和端点
        apiKey: 'YOUR_API_KEY_HERE',
        apiEndpoint: 'https://api.openai.com/v1/chat/completions',
        model: 'gpt-3.5-turbo',
        maxTokens: 1000,
        temperature: 0.7,
        // UI配置
        uiPosition: 'bottom-right', // 可选: top-left, top-right, bottom-left, bottom-right
        theme: 'light', // 可选: light, dark
        // 自动触发设置
        autoSummarize: true, // 是否自动总结
        delay: 500, // 页面加载后延迟多少毫秒开始总结
        // 自动总结域名列表
        autoSummarizeDomains: ['juejin.cn', 'zhihu.com', 'csdn.net', 'jianshu.com'],
        // 域名黑名单,支持通配符 *
        blacklistDomains: ['mail.google.com', '*.facebook.com', 'twitter.com', '*.twitter.com']
    };

    // 保存用户配置
    const savedConfig = GM_getValue('aiSummaryConfig');
    if (savedConfig) {
        Object.assign(CONFIG, JSON.parse(savedConfig));
    }

    // 添加样式
    GM_addStyle(`
        #ai-summary-container {
            position: fixed;
            ${CONFIG.uiPosition.includes('bottom') ? 'bottom: 20px;' : 'top: 20px;'}
            ${CONFIG.uiPosition.includes('right') ? 'right: 20px;' : 'left: 20px;'}
            width: 350px;
            max-height: 500px;
            background-color: ${CONFIG.theme === 'light' ? '#ffffff' : '#2d2d2d'};
            color: ${CONFIG.theme === 'light' ? '#333333' : '#f0f0f0'};
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            z-index: 9999;
            overflow: hidden;
            font-family: Arial, sans-serif;
            transition: all 0.3s ease;
            opacity: 0.95;
        }
        #ai-summary-container:hover {
            opacity: 1;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
        }
        #ai-summary-header {
            padding: 10px 15px;
            background-color: ${CONFIG.theme === 'light' ? '#f0f0f0' : '#444444'};
            border-bottom: 1px solid ${CONFIG.theme === 'light' ? '#e0e0e0' : '#555555'};
            display: flex;
            justify-content: space-between;
            align-items: center;
            cursor: move;
        }
        #ai-summary-title {
            font-weight: bold;
            font-size: 14px;
            margin: 0;
        }
        #ai-summary-controls {
            display: flex;
            gap: 5px;
        }
        #ai-summary-controls button {
            background: none;
            border: none;
            cursor: pointer;
            font-size: 16px;
            color: ${CONFIG.theme === 'light' ? '#555' : '#ccc'};
            padding: 0;
            width: 24px;
            height: 24px;
            display: flex;
            align-items: center;
            justify-content: center;
            border-radius: 4px;
        }
        #ai-summary-controls button:hover {
            background-color: ${CONFIG.theme === 'light' ? '#e0e0e0' : '#555555'};
        }
        #ai-summary-content {
            padding: 15px;
            overflow-y: auto;
            max-height: 400px;
            font-size: 14px;
            line-height: 1.5;
        }
        #ai-summary-content.loading {
            opacity: 0.7;
        }
        #ai-summary-content p {
            margin: 0 0 10px 0;
        }
        #ai-summary-footer {
            padding: 8px 15px;
            border-top: 1px solid ${CONFIG.theme === 'light' ? '#e0e0e0' : '#555555'};
            display: flex;
            justify-content: space-between;
            font-size: 12px;
            color: ${CONFIG.theme === 'light' ? '#888' : '#aaa'};
        }
        #ai-summary-settings {
            position: absolute;
            top: 40px;
            right: 0;
            background-color: ${CONFIG.theme === 'light' ? '#ffffff' : '#2d2d2d'};
            border: 1px solid ${CONFIG.theme === 'light' ? '#e0e0e0' : '#555555'};
            border-radius: 4px;
            padding: 10px;
            width: 250px;
            max-height: 400px;
            overflow-y: auto;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            z-index: 10000;
            display: none;
        }
        #ai-summary-settings.visible {
            display: block;
        }
        .settings-group {
            margin-bottom: 10px;
        }
        .settings-group label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
            font-size: 12px;
        }
        .settings-group input, .settings-group select {
            width: 100%;
            padding: 5px;
            border: 1px solid ${CONFIG.theme === 'light' ? '#e0e0e0' : '#555555'};
            border-radius: 4px;
            background-color: ${CONFIG.theme === 'light' ? '#ffffff' : '#333333'};
            color: ${CONFIG.theme === 'light' ? '#333333' : '#f0f0f0'};
        }
        .settings-group input[type="checkbox"] {
            width: auto;
        }
        .settings-actions {
            display: flex;
            justify-content: flex-end;
            gap: 5px;
            margin-top: 10px;
            position: sticky;
            bottom: 0;
            background-color: ${CONFIG.theme === 'light' ? '#ffffff' : '#2d2d2d'};
            padding: 5px 0;
        }
        .settings-actions button {
            padding: 5px 10px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 12px;
        }
        #save-settings {
            background-color: #4CAF50;
            color: white;
        }
        #cancel-settings {
            background-color: #f0f0f0;
            color: #333;
        }
        .cursor-pointer {
            cursor: pointer;
        }
        .typing-effect {
            border-right: 2px solid ${CONFIG.theme === 'light' ? '#333' : '#f0f0f0'};
            white-space: nowrap;
            overflow: hidden;
            animation: typing 3.5s steps(40, end), blink-caret 0.75s step-end infinite;
        }
        @keyframes typing {
            from { width: 0 }
            to { width: 100% }
        }
        @keyframes blink-caret {
            from, to { border-color: transparent }
            50% { border-color: ${CONFIG.theme === 'light' ? '#333' : '#f0f0f0'} }
        }
    `);

    // 检查域名是否匹配通配符规则
    function domainMatchesPattern(domain, pattern) {
        // 转换通配符为正则表达式
        const regexPattern = pattern.replace(/\./g, '\\.').replace(/\*/g, '.*');
        const regex = new RegExp(`^${regexPattern}$`);
        return regex.test(domain);
    }

    // 检查当前域名是否在黑名单中
    function isCurrentDomainBlacklisted() {
        const currentDomain = window.location.hostname;
        
        for (const pattern of CONFIG.blacklistDomains) {
            if (domainMatchesPattern(currentDomain, pattern)) {
                console.log(`当前域名 ${currentDomain} 匹配黑名单规则 ${pattern},不创建UI`);
                return true;
            }
        }
        
        return false;
    }

    // 创建UI
    function createUI() {
        const container = document.createElement('div');
        container.id = 'ai-summary-container';
        container.innerHTML = `
            <div id="ai-summary-header">
                <h3 id="ai-summary-title">AI网页内容总结</h3>
                <div id="ai-summary-controls">
                    <button id="ai-summary-refresh" title="刷新总结">🔄</button>
                    <button id="ai-summary-settings-btn" title="设置">⚙️</button>
                    <button id="ai-summary-minimize" title="最小化">_</button>
                    <button id="ai-summary-close" title="关闭">✕</button>
                </div>
            </div>
            <div id="ai-summary-content">
                <p>点击刷新按钮开始总结当前网页内容...</p>
            </div>
            <div id="ai-summary-footer">
                <span>由AI提供支持</span>
                <span id="ai-summary-toggle" class="cursor-pointer">自动总结: ${CONFIG.autoSummarize ? '开启' : '关闭'}</span>
            </div>
            <div id="ai-summary-settings">
                <div class="settings-group">
                    <label for="api-key">API密钥</label>
                    <input type="password" id="api-key" value="${CONFIG.apiKey}" placeholder="输入你的API密钥">
                </div>
                <div class="settings-group">
                    <label for="api-endpoint">API端点</label>
                    <input type="text" id="api-endpoint" value="${CONFIG.apiEndpoint}" placeholder="API端点URL">
                </div>
                <div class="settings-group">
                    <label for="model">模型</label>
                    <input type="text" id="model" value="${CONFIG.model}" placeholder="AI模型名称">
                </div>
                <div class="settings-group">
                    <label for="position">位置</label>
                    <select id="position">
                        <option value="top-left" ${CONFIG.uiPosition === 'top-left' ? 'selected' : ''}>左上角</option>
                        <option value="top-right" ${CONFIG.uiPosition === 'top-right' ? 'selected' : ''}>右上角</option>
                        <option value="bottom-left" ${CONFIG.uiPosition === 'bottom-left' ? 'selected' : ''}>左下角</option>
                        <option value="bottom-right" ${CONFIG.uiPosition === 'bottom-right' ? 'selected' : ''}>右下角</option>
                    </select>
                </div>
                <div class="settings-group">
                    <label for="theme">主题</label>
                    <select id="theme">
                        <option value="light" ${CONFIG.theme === 'light' ? 'selected' : ''}>浅色</option>
                        <option value="dark" ${CONFIG.theme === 'dark' ? 'selected' : ''}>深色</option>
                    </select>
                </div>
                <div class="settings-group">
                    <label>
                        <input type="checkbox" id="auto-summarize" ${CONFIG.autoSummarize ? 'checked' : ''}>
                        自动总结
                    </label>
                </div>
                <div class="settings-group">
                    <label for="auto-domains">自动总结域名列表</label>
                    <input type="text" id="auto-domains" value="${CONFIG.autoSummarizeDomains.join(', ')}" placeholder="输入域名,用逗号分隔">
                    <small style="display: block; margin-top: 5px; font-size: 11px; color: ${CONFIG.theme === 'light' ? '#666' : '#aaa'};">输入域名,用逗号分隔,例如: juejin.cn, zhihu.com</small>
                </div>
                <div class="settings-group">
                    <label for="blacklist-domains">域名黑名单</label>
                    <input type="text" id="blacklist-domains" value="${CONFIG.blacklistDomains.join(', ')}" placeholder="输入黑名单域名,用逗号分隔">
                    <small style="display: block; margin-top: 5px; font-size: 11px; color: ${CONFIG.theme === 'light' ? '#666' : '#aaa'};">支持通配符*,例如: *.facebook.com, twitter.* (匹配黑名单域名时不会创建UI)</small>
                </div>
                <div class="settings-actions">
                    <button id="cancel-settings">取消</button>
                    <button id="save-settings">保存</button>
                </div>
            </div>
        `;

        document.body.appendChild(container);
        makeElementDraggable(container);
        addEventListeners();

        // 检查当前域名是否在自动总结列表中
        if (CONFIG.autoSummarize) {
            const currentDomain = window.location.hostname;
            const shouldAutoSummarize = CONFIG.autoSummarizeDomains.some(domain => currentDomain.includes(domain));
            
            if (shouldAutoSummarize) {
                console.log(`当前域名 ${currentDomain} 在自动总结列表中,将自动开始总结`);
                // 修改内容提示为"正在总结中..."
                document.getElementById('ai-summary-content').innerHTML = '<p>正在总结中...</p>';
                setTimeout(() => {
                    summarizeContent(true); // 传递true表示是自动总结
                }, CONFIG.delay);
            } else {
                console.log(`当前域名 ${currentDomain} 不在自动总结列表中,不会自动总结`);
            }
        }
    }

    // 添加事件监听器
    function addEventListeners() {
        // 刷新按钮
        document.getElementById('ai-summary-refresh').addEventListener('click', () => {
            summarizeContent();
        });

        // 设置按钮
        document.getElementById('ai-summary-settings-btn').addEventListener('click', () => {
            const settingsPanel = document.getElementById('ai-summary-settings');
            settingsPanel.classList.toggle('visible');
        });

        // 最小化按钮
        document.getElementById('ai-summary-minimize').addEventListener('click', () => {
            const container = document.getElementById('ai-summary-container');
            const content = document.getElementById('ai-summary-content');
            const footer = document.getElementById('ai-summary-footer');

            if (content.style.display === 'none') {
                content.style.display = 'block';
                footer.style.display = 'flex';
                document.getElementById('ai-summary-minimize').textContent = '_';
            } else {
                content.style.display = 'none';
                footer.style.display = 'none';
                document.getElementById('ai-summary-minimize').textContent = '□';
            }
        });

        // 关闭按钮
        document.getElementById('ai-summary-close').addEventListener('click', () => {
            const container = document.getElementById('ai-summary-container');
            container.style.display = 'none';
        });

        // 自动总结开关
        document.getElementById('ai-summary-toggle').addEventListener('click', () => {
            CONFIG.autoSummarize = !CONFIG.autoSummarize;
            document.getElementById('ai-summary-toggle').textContent = `自动总结: ${CONFIG.autoSummarize ? '开启' : '关闭'}`;
            document.getElementById('auto-summarize').checked = CONFIG.autoSummarize;
            saveConfig();
        });

        // 取消设置
        document.getElementById('cancel-settings').addEventListener('click', () => {
            document.getElementById('ai-summary-settings').classList.remove('visible');
        });
        
        // 保存设置
        document.getElementById('save-settings').addEventListener('click', () => {
            // 获取用户输入的配置
            CONFIG.apiKey = document.getElementById('api-key').value;
            CONFIG.apiEndpoint = document.getElementById('api-endpoint').value;
            CONFIG.model = document.getElementById('model').value;
            CONFIG.uiPosition = document.getElementById('position').value;
            CONFIG.theme = document.getElementById('theme').value;
            CONFIG.autoSummarize = document.getElementById('auto-summarize').checked;
            
            // 获取并处理自动总结域名列表
            const domainsInput = document.getElementById('auto-domains').value;
            CONFIG.autoSummarizeDomains = domainsInput.split(',').map(domain => domain.trim()).filter(domain => domain);
            
            // 获取并处理黑名单域名列表
            const blacklistInput = document.getElementById('blacklist-domains').value;
            CONFIG.blacklistDomains = blacklistInput.split(',').map(domain => domain.trim()).filter(domain => domain);
            
            // 保存配置
            saveConfig();
            
            // 更新UI
            updateUIWithConfig();
            
            // 隐藏设置面板
            document.getElementById('ai-summary-settings').classList.remove('visible');
            
            // 显示保存成功提示
            const contentElement = document.getElementById('ai-summary-content');
            contentElement.innerHTML = '<p>设置已保存</p>';
            setTimeout(() => {
                contentElement.innerHTML = '<p>点击刷新按钮开始总结当前网页内容...</p>';
            }, 2000);
        });
    }

    // 保存配置到本地存储
    function saveConfig() {
        try {
            GM_setValue('aiSummaryConfig', JSON.stringify(CONFIG));
            console.log('配置已保存');
        } catch (error) {
            console.error('保存配置时出错:', error);
            alert('保存配置失败,请查看控制台获取详细信息');
        }
    }

    // 根据配置更新UI
    function updateUIWithConfig() {
        const container = document.getElementById('ai-summary-container');
        
        // 更新位置
        container.style.top = CONFIG.uiPosition.includes('top') ? '20px' : 'auto';
        container.style.bottom = CONFIG.uiPosition.includes('bottom') ? '20px' : 'auto';
        container.style.left = CONFIG.uiPosition.includes('left') ? '20px' : 'auto';
        container.style.right = CONFIG.uiPosition.includes('right') ? '20px' : 'auto';
        
        // 更新主题
        if (CONFIG.theme === 'light') {
            container.style.backgroundColor = '#ffffff';
            container.style.color = '#333333';
        } else {
            container.style.backgroundColor = '#2d2d2d';
            container.style.color = '#f0f0f0';
        }
        
        // 更新自动总结开关文本
        document.getElementById('ai-summary-toggle').textContent = `自动总结: ${CONFIG.autoSummarize ? '开启' : '关闭'}`;
    }

    // 使元素可拖拽
    function makeElementDraggable(element) {
        let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
        const header = document.getElementById('ai-summary-header');
        
        if (header) {
            header.onmousedown = dragMouseDown;
        } else {
            element.onmousedown = dragMouseDown;
        }

        function dragMouseDown(e) {
            e = e || window.event;
            e.preventDefault();
            // 获取鼠标位置
            pos3 = e.clientX;
            pos4 = e.clientY;
            document.onmouseup = closeDragElement;
            // 鼠标移动时调用elementDrag
            document.onmousemove = elementDrag;
        }

        function elementDrag(e) {
            e = e || window.event;
            e.preventDefault();
            // 计算新位置
            pos1 = pos3 - e.clientX;
            pos2 = pos4 - e.clientY;
            pos3 = e.clientX;
            pos4 = e.clientY;
            // 设置元素的新位置
            element.style.top = (element.offsetTop - pos2) + 'px';
            element.style.left = (element.offsetLeft - pos1) + 'px';
            // 重置位置配置,因为用户手动拖动了
            CONFIG.uiPosition = 'custom';
        }

        function closeDragElement() {
            // 停止移动
            document.onmouseup = null;
            document.onmousemove = null;
        }
    }

    // 提取网页内容
    function extractPageContent() {
        // 获取页面标题
        const title = document.title;
        
        // 使用Turndown将HTML转换为Markdown
        const turndownService = new TurndownService({
            headingStyle: 'atx',
            codeBlockStyle: 'fenced',
            emDelimiter: '_',
            hr: '---',
            bulletListMarker: '-',
        });
        
        // 自定义规则以更好地处理内容
        turndownService.addRule('removeAds', {
            filter: function(node) {
                // 过滤掉可能的广告元素
                return node.className && (
                    node.className.includes('ad') ||
                    node.className.includes('banner') ||
                    node.className.includes('sidebar') ||
                    node.id && (node.id.includes('ad') || node.id.includes('banner'))
                );
            },
            replacement: function() {
                return '';
            }
        });

        // 添加自定义规则,忽略一些不需要的元素
        turndownService.addRule('ignoreNavAndFooter', {
            filter: function(node) {
                return (
                    node.nodeName.toLowerCase() === 'nav' ||
                    node.nodeName.toLowerCase() === 'footer' ||
                    node.classList.contains('nav') ||
                    node.classList.contains('footer') ||
                    node.classList.contains('menu') ||
                    node.id === 'footer' ||
                    node.id === 'nav' ||
                    node.id === 'menu'
                );
            },
            replacement: function() {
                return '';
            }
        });
        
        // 尝试获取文章内容
        let content = '';
        let htmlContent = '';
        
        // 尝试获取文章内容
        const articleElements = document.querySelectorAll('article, .article, .post, .content, main, .main-content, [role="main"]');
        if (articleElements.length > 0) {
            // 使用第一个找到的文章元素
            htmlContent = articleElements[0].innerHTML;
        } else {
            // 如果没有找到文章元素,尝试获取所有段落
            const paragraphs = document.querySelectorAll('p');
            if (paragraphs.length > 0) {
                // 创建一个临时容器来存放所有段落
                const tempContainer = document.createElement('div');
                paragraphs.forEach(p => {
                    // 只添加有实际内容的段落
                    if (p.textContent.trim().length > 0) {
                        tempContainer.appendChild(p.cloneNode(true));
                    }
                });
                htmlContent = tempContainer.innerHTML;
            } else {
                // 如果没有找到段落,获取body的内容
                // 但排除一些常见的非内容区域
                const body = document.body.cloneNode(true);
                const elementsToRemove = body.querySelectorAll('header, footer, nav, aside, script, style, .sidebar, .ad, .advertisement, .banner, .navigation, .related, .recommended');
                elementsToRemove.forEach(el => el.remove());
                htmlContent = body.innerHTML;
            }
        }
        
        // 将HTML转换为Markdown
        content = turndownService.turndown(htmlContent);
        
        // 清理内容(删除多余空白行)
        content = content.replace(/\n{3,}/g, '\n\n').trim();
        
        // 如果内容太长,截取前10000个字符
        if (content.length > 10000) {
            content = content.substring(0, 10000) + '...';
        }
        
        return { title, content };
    }

    // 调用AI API进行总结
    function summarizeContent(isAuto = false) {
        // 显示加载状态
        const contentElement = document.getElementById('ai-summary-content');
        contentElement.classList.add('loading');
        contentElement.innerHTML = isAuto 
            ? '<p>正在自动总结内容,请稍候...</p>'
            : '<p>正在总结内容,请稍候...</p>';
        
        // 提取页面内容
        const { title, content } = extractPageContent();
        
        // 如果API密钥未设置,显示提示
        if (CONFIG.apiKey === 'YOUR_API_KEY_HERE') {
            contentElement.classList.remove('loading');
            contentElement.innerHTML = '<p>请先在设置中配置你的API密钥</p>';
            return;
        }
        
        // 准备请求数据
        const requestData = {
            model: CONFIG.model,
            messages: [
                {
                    role: 'system',
                    content: '你是一个专业的内容总结助手。请简洁明了地总结以下网页内容的要点,包含主要观点、关键信息和重要细节。通俗易懂,突出重点。'
                },
                {
                    role: 'user',
                    content: `网页标题: ${title}\n\n网页内容: ${content}\n\n请总结这个网页的主要内容,突出关键信息。`
                }
            ],
            max_tokens: CONFIG.maxTokens,
            temperature: CONFIG.temperature,
            stream: true
        };
        
        // 发送API请求
        let summaryText = '';
        let lastResponseLength = 0; // 添加此变量来跟踪响应长度
        contentElement.innerHTML = '';
        
        GM_xmlhttpRequest({
            method: 'POST',
            url: CONFIG.apiEndpoint,
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${CONFIG.apiKey}`
            },
            data: JSON.stringify(requestData),
            timeout: 30000, // 设置30秒超时
            onloadstart: function() {
                // 创建一个段落用于显示流式响应
                const paragraph = document.createElement('p');
                contentElement.appendChild(paragraph);
                console.log('开始接收流式响应...');
            },
            onreadystatechange: function(response) {
                try {
                    // 处理流式响应
                    const responseText = response.responseText || '';
                    
                    // 只处理新数据
                    if (responseText.length <= lastResponseLength) {
                        return;
                    }
                    
                    // 计算新数据
                    const newResponseText = responseText.substring(lastResponseLength);
                    lastResponseLength = responseText.length;
                    
                    console.log(`接收到新数据,长度: ${newResponseText.length}, 总长度: ${responseText.length}`);
                    
                    // 将新响应拆分为各个数据行
                    const lines = newResponseText.split('\n');
                    let newContent = '';
                    
                    for (const line of lines) {
                        if (line.startsWith('data: ') && line !== 'data: [DONE]') {
                            try {
                                const jsonStr = line.substring(6);
                                if (jsonStr.trim() === '') continue;
                                
                                const data = JSON.parse(jsonStr);
                                if (data.choices && data.choices[0].delta && data.choices[0].delta.content) {
                                    newContent += data.choices[0].delta.content;
                                }
                            } catch (e) {
                                // 可能是不完整的JSON,忽略错误
                                console.log('解析单行数据时出错 (可能是不完整的JSON):', e.message);
                            }
                        }
                    }
                    
                    // 只要有新内容就立即更新UI
                    if (newContent) {
                        summaryText += newContent;
                        const paragraph = contentElement.querySelector('p');
                        if (paragraph) {
                            paragraph.innerHTML = renderMarkdown(summaryText);
                            contentElement.scrollTop = contentElement.scrollHeight; // 滚动到底部
                        }
                    }
                } catch (error) {
                    console.error('处理流式响应时出错:', error);
                }
            },
            onload: function(response) {
                contentElement.classList.remove('loading');
                console.log('响应接收完成,状态码:', response.status);
                
                if (response.status !== 200) {
                    contentElement.innerHTML = `<p>API请求失败: ${response.status} ${response.statusText}</p>`;
                    console.error('API请求失败:', response.status, response.statusText, response.responseText);
                    return;
                }
                
                // 确保我们有完整的内容
                if (summaryText.trim() === '') {
                    console.log('尝试从完整响应中提取内容...');
                    // 提取完整响应内容的逻辑...
                    // ... existing code for handling complete response ...
                } else {
                    console.log('流式响应已完成,总内容长度:', summaryText.length);
                }
            },
            onerror: function(error) {
                contentElement.classList.remove('loading');
                contentElement.innerHTML = `<p>请求出错: ${error}</p>`;
                console.error('API请求出错:', error);
            },
            ontimeout: function() {
                contentElement.classList.remove('loading');
                contentElement.innerHTML = '<p>请求超时,请检查网络连接或API端点是否正确</p>';
                console.error('API请求超时');
            }
        });
    }

    // 在页面加载完成后初始化
    // 渲染Markdown文本为HTML
    function renderMarkdown(text) {
        if (!text) return '';
        
        // 基本Markdown语法转换
        let html = text
            // 标题
            .replace(/^### (.+)$/gm, '<h3>$1</h3>')
            .replace(/^## (.+)$/gm, '<h2>$1</h2>')
            .replace(/^# (.+)$/gm, '<h1>$1</h1>')
            // 粗体
            .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
            // 斜体
            .replace(/\*(.+?)\*/g, '<em>$1</em>')
            // 代码块
            .replace(/```([\s\S]+?)```/g, '<pre><code>$1</code></pre>')
            // 行内代码
            .replace(/`(.+?)`/g, '<code>$1</code>')
            // 链接
            .replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2" target="_blank">$1</a>')
            // 无序列表
            .replace(/^- (.+)$/gm, '<li>$1</li>')
            // 有序列表
            .replace(/^\d+\. (.+)$/gm, '<li>$1</li>')
            // 段落
            .replace(/\n\n/g, '</p><p>');
        
        // 包装在段落标签中
        html = '<p>' + html + '</p>';
        
        // 修复列表
        html = html.replace(/<p><li>/g, '<ul><li>').replace(/<\/li><\/p>/g, '</li></ul>');
        
        return html;
    }
    
    // 使用事件监听器确保DOM已加载
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initializeScript);
    } else {
        initializeScript();
    }

    // 初始化脚本
    function initializeScript() {
        // 检查当前域名是否在黑名单中
        if (isCurrentDomainBlacklisted()) {
            console.log('当前域名在黑名单中,不创建UI');
            return;
        }
        
        createUI();
    }
})();