AI网页内容总结

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

目前为 2025-03-14 提交的版本。查看 最新版本

// ==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();
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址