Greasy Fork

来自缓存

Greasy Fork is available in English.

Wikipedia AI Summary (维基百科AI总结)

维基百科 AI 摘要脚本,兼容适配OpenAI API格式的所有LLM。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Wikipedia AI Summary (维基百科AI总结)
// @namespace    http://yhgzs-111.github.io/
// @version      1.0 
// @description  维基百科 AI 摘要脚本,兼容适配OpenAI API格式的所有LLM。
// @author       Deepseek, ChatGPT and NNNH 
// @match        https://*.wikipedia.org/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=wikipedia.org
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @connect      *
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 默认配置
    const defaultConfig = {
        apiKey: '',
        apiURL: '',
        systemPrompt: '请用中文总结以下内容,保持关键信息的完整性:',
        model: ''
    };

    // 初始化配置
    let config = {
        apiKey: GM_getValue('apiKey', defaultConfig.apiKey),
        apiURL: GM_getValue('apiURL', defaultConfig.apiURL),
        systemPrompt: GM_getValue('systemPrompt', defaultConfig.systemPrompt),
        model: GM_getValue('model', defaultConfig.model)
    };

    // 注册设置菜单
    GM_registerMenuCommand("AI Summary Settings", showSettings);

    // 创建浮动按钮
    let floatingBtn = null;
    document.addEventListener('mouseup', handleTextSelection);

    function handleTextSelection(e) {
        const selection = window.getSelection().toString().trim();
        if (!selection) {
            if (floatingBtn) {
                floatingBtn.remove();
                floatingBtn = null;
            }
            return;
        }

        if (!floatingBtn) {
            createFloatingButton(e);
        } else {
            positionButton(e);
        }
    }

    function createFloatingButton(e) {
        floatingBtn = document.createElement('button');
        floatingBtn.textContent = 'AI Summary';
        Object.assign(floatingBtn.style, {
            position: 'absolute',
            zIndex: 9999,
            background: '#4CAF50',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            padding: '8px 16px',
            cursor: 'pointer',
            boxShadow: '0 2px 5px rgba(0,0,0,0.2)'
        });
        positionButton(e);
        floatingBtn.addEventListener('click', handleSummary);
        document.body.appendChild(floatingBtn);
    }

    function positionButton(e) {
        // 直接使用 e.pageX 与 e.pageY(包含滚动偏移),无需额外添加滚动量
        floatingBtn.style.left = `${e.pageX + 15}px`;
        floatingBtn.style.top = `${e.pageY - 50}px`;
    }

    async function handleSummary() {
        const selection = window.getSelection().toString().trim();
        if (!selection) return;

        floatingBtn.textContent = '处理中...';
        try {
            const summary = await getAISummary(selection);
            showSummaryPopup(summary);
        } catch (error) {
            alert(`错误: ${error.message}`);
        } finally {
            if (floatingBtn) {
                floatingBtn.remove();
                floatingBtn = null;
            }
        }
    }

    // 修改后的 getAISummary 函数,兼容 /v1/chat/completions 和 /v1/completions 两种接口格式
    async function getAISummary(text) {
        return new Promise((resolve, reject) => {
            // 判断是否为 chat 接口(URL 中包含 "chat/completions")
            const isChatEndpoint = config.apiURL.includes("chat/completions");
            // 判断模型是否为 OpenAI 官方支持的LLM模型
            const isChatModel = ["gpt-3.5-turbo", "gpt-4", "gpt-4o"].includes(config.model);
            let payload;
            if (isChatEndpoint && isChatModel) {
                // 使用对话接口格式
                payload = {
                    model: config.model,
                    messages: [
                        { role: "system", content: config.systemPrompt },
                        { role: "user", content: text }
                    ],
                    temperature: 0.7
                };
            } else {
                // 使用传统 completions 接口格式:将 systemPrompt 与用户输入拼接成 prompt
                payload = {
                    model: config.model,
                    prompt: config.systemPrompt + "\n" + text,
                    temperature: 0.7,
                    max_tokens: 150 // 可根据需要调整
                };
            }
            GM_xmlhttpRequest({
                method: "POST",
                url: config.apiURL,
                headers: {
                    "Content-Type": "application/json",
                    "Authorization": `Bearer ${config.apiKey}`
                },
                data: JSON.stringify(payload),
                onload: function(response) {
                    try {
                        const data = JSON.parse(response.responseText);
                        if (response.status === 200) {
                            let summary;
                            if (isChatEndpoint && isChatModel) {
                                summary = data.choices[0].message.content;
                            } else {
                                summary = data.choices[0].text;
                            }
                            resolve(summary);
                        } else {
                            reject(new Error(data.error?.message || 'Unknown error'));
                        }
                    } catch(e) {
                        reject(new Error("Response parse error: " + e.message));
                    }
                },
                onerror: function(error) {
                    reject(error);
                }
            });
        });
    }

    function showSummaryPopup(content) {
        const popup = document.createElement('div');
        Object.assign(popup.style, {
            position: 'fixed',
            top: '50%',
            left: '50%',
            transform: 'translate(-50%, -50%)',
            background: 'white',
            padding: '20px',
            borderRadius: '8px',
            boxShadow: '0 4px 12px rgba(0,0,0,0.2)',
            maxWidth: '600px',
            width: '90%',
            maxHeight: '80vh',
            overflow: 'auto',
            zIndex: 10000
        });

        const closeBtn = document.createElement('button');
        closeBtn.textContent = '×';
        Object.assign(closeBtn.style, {
            position: 'absolute',
            top: '10px',
            right: '10px',
            background: 'none',
            border: 'none',
            fontSize: '20px',
            cursor: 'pointer'
        });
        closeBtn.onclick = () => popup.remove();

        const contentDiv = document.createElement('div');
        contentDiv.innerHTML = content.replace(/\n/g, '<br>');

        popup.appendChild(closeBtn);
        popup.appendChild(contentDiv);
        document.body.appendChild(popup);
    }

    function showSettings() {
        const settings = `
            <div style="padding:20px;font-family:sans-serif">
                <h2>AI Summary Settings</h2>
                <form id="settingsForm">
                    <label>API Key:<br>
                        <input type="password" id="apiKey" value="${config.apiKey}" style="width:300px"></label><br><br>
                    <label>API URL:<br>
                        <input type="text" id="apiURL" value="${config.apiURL}" style="width:300px"></label><br><br>
                    <label>System Prompt:<br>
                        <textarea id="systemPrompt" rows="4" style="width:300px">${config.systemPrompt}</textarea></label><br><br>
                    <label>Model (手动输入):<br>
                        <input type="text" id="model" value="${config.model}" style="width:300px"></label><br><br>
                    <button type="submit">Save</button>
                </form>
            </div>
        `;

        const win = window.open('', 'AI Summary Settings', 'width=400,height=500');
        win.document.write(settings);
        win.document.getElementById('settingsForm').onsubmit = function(e) {
            e.preventDefault();
            config = {
                apiKey: win.document.getElementById('apiKey').value,
                apiURL: win.document.getElementById('apiURL').value,
                systemPrompt: win.document.getElementById('systemPrompt').value,
                model: win.document.getElementById('model').value
            };
            GM_setValue('apiKey', config.apiKey);
            GM_setValue('apiURL', config.apiURL);
            GM_setValue('systemPrompt', config.systemPrompt);
            GM_setValue('model', config.model);
            win.close();
        };
    }
})();