GlobalChatgpt Pro

多密钥支持(仅自定义)、拖动、主题切换、卡通背景、真实聊天的增强脚本

当前为 2025-07-06 提交的版本,查看 最新版本

// ==UserScript==
// @name         GlobalChatgpt Pro 
// @namespace    http://tampermonkey.net/
// @version      1.5
// @description  多密钥支持(仅自定义)、拖动、主题切换、卡通背景、真实聊天的增强脚本
// @author       maken
// @match        *://*/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_notification
// @grant        GM_xmlhttpRequest
// @connect      api.openai.com
// @license      MIT (https://opensource.org/license/mit/)
// ==/UserScript==

(function () {
    'use strict';

    // 只支持自定义密钥,无预设密钥
    const CONFIG = {
        customKey: GM_getValue('gcui_apiKey', ''),
        theme: GM_getValue('gcui_theme', 'pink'),
        expanded: true,
        posX: GM_getValue('gcui_posX', 100),
        posY: GM_getValue('gcui_posY', 100),
    };

    const state = {
        container: null,
        header: null,
        body: null,
        inputArea: null,
        input: null,
        sendBtn: null,
        themeBtn: null,
        keyBtn: null,
    };

    function getCurrentApiKey() {
        return CONFIG.customKey || '';
    }

    function savePosition(x, y) {
        GM_setValue('gcui_posX', x);
        GM_setValue('gcui_posY', y);
    }

    function showMessage(sender, text) {
        const msg = document.createElement('div');
        Object.assign(msg.style, {
            marginBottom: '8px',
            wordBreak: 'break-word',
            backgroundColor: sender === '我' ? 'rgba(255,255,255,0.7)' : 'rgba(255,255,255,0.3)',
            padding: '6px 10px',
            borderRadius: '6px',
            alignSelf: sender === '我' ? 'flex-end' : 'flex-start',
            maxWidth: '80%'
        });
        msg.innerHTML = `<strong style="color:${sender === '我' ? '#d6006e' : '#333'}">${sender}:</strong> ${text}`;
        state.body.appendChild(msg);
        state.body.scrollTop = state.body.scrollHeight;
    }

    async function sendMessage() {
        const text = state.input.value.trim();
        if (!text) return;

        const apiKey = getCurrentApiKey();
        if (!apiKey || !apiKey.startsWith('sk-')) {
            GM_notification({ text: '请设置有效的 API 密钥', timeout: 2000 });
            return;
        }

        showMessage('我', text);
        state.input.value = '';
        state.input.focus();

        const payload = {
            model: 'gpt-3.5-turbo',
            messages: [{ role: 'user', content: text }],
            temperature: 0.7
        };

        GM_xmlhttpRequest({
            method: 'POST',
            url: 'https://api.openai.com/v1/chat/completions',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${apiKey}`
            },
            data: JSON.stringify(payload),
            responseType: 'json',
            onload: function (res) {
                const data = res.response;
                console.log('[返回数据]', data);

                if (!data || data.error) {
                    const msg = data?.error?.message || '请求失败';
                    showMessage('系统', `错误:${msg}`);
                    return;
                }

                const reply = data.choices?.[0]?.message?.content?.trim();
                if (reply) {
                    showMessage('AI', reply);
                } else {
                    showMessage('系统', '无有效回复,检查模型或密钥');
                }
            },
            onerror: function (err) {
                console.error('请求失败:', err);
                showMessage('系统', '请求失败,请检查网络或密钥');
            }
        });
    }

    function updateTheme() {
        const t = CONFIG.theme;
        const { container, body, inputArea, sendBtn, themeBtn } = state;
        container.style.backgroundColor = t === 'pink' ? '#ffc0d9' : '#2c2c2c';
        state.header.style.backgroundColor = t === 'pink' ? '#ff66b2' : '#444';
        state.header.style.color = t === 'pink' ? '#fff' : '#ddd';
        body.style.color = t === 'pink' ? '#333' : '#ddd';

        if (t === 'pink') {
            body.style.backgroundImage = 'url("https://img.redocn.com/sheji/20240607/keaikatongmaosucaituAItu_13339783.jpg")';
            body.style.backgroundColor = '#fff0f7';
            themeBtn.textContent = '🌙';
        } else {
            body.style.backgroundImage = 'none';
            body.style.backgroundColor = '#222';
            themeBtn.textContent = '☀️';
        }

        inputArea.style.backgroundColor = t === 'pink' ? '#ffd6e8' : '#333';
        sendBtn.style.backgroundColor = t === 'pink' ? '#ff66b2' : '#666';
    }

    async function buildUI() {
        const container = document.createElement('div');
        Object.assign(container.style, {
            position: 'fixed',
            top: CONFIG.posY + 'px',
            left: CONFIG.posX + 'px',
            width: '360px',
            height: '500px',
            borderRadius: '10px',
            boxShadow: '0 0 10px rgba(0,0,0,0.3)',
            zIndex: '999999',
            display: 'flex',
            flexDirection: 'column',
            userSelect: 'none'
        });

        // Header
        const header = document.createElement('div');
        Object.assign(header.style, {
            padding: '10px',
            fontSize: '18px',
            fontWeight: 'bold',
            display: 'flex',
            justifyContent: 'space-between',
            alignItems: 'center',
            cursor: 'grab'
        });
        const title = document.createElement('span');
        title.textContent = 'GlobalChatUI Pro';
        header.appendChild(title);

        const controls = document.createElement('div');
        controls.style.display = 'flex';
        controls.style.gap = '6px';
        controls.style.alignItems = 'center';

        // Theme button
        const themeBtn = document.createElement('button');
        themeBtn.style.cssText = 'font-size:16px;background:#fff;border:none;border-radius:4px;padding:3px 6px;cursor:pointer;color:#333;';
        controls.appendChild(themeBtn);
        state.themeBtn = themeBtn;

        // 自定义密钥按钮(钥匙图标)
        const keyBtn = document.createElement('button');
        keyBtn.title = '点击设置自定义密钥';
        keyBtn.textContent = '🔑';
        keyBtn.style.cssText = 'font-size:18px;background:#fff;border:none;border-radius:4px;padding:3px 6px;cursor:pointer;color:#333;';
        controls.appendChild(keyBtn);
        state.keyBtn = keyBtn;

        keyBtn.addEventListener('click', async () => {
            const newKey = prompt('请输入自定义密钥(以 sk- 开头)', CONFIG.customKey);
            if (newKey !== null) {
                CONFIG.customKey = newKey.trim();
                await GM_setValue('gcui_apiKey', CONFIG.customKey);
                GM_notification({ text: CONFIG.customKey ? '自定义密钥已保存' : '已清除密钥', timeout: 2000 });
            }
        });

        // Toggle Expand button
        const toggleBtn = document.createElement('button');
        toggleBtn.textContent = CONFIG.expanded ? '−' : '+';
        toggleBtn.style.cssText = 'font-size:20px;background:none;border:none;cursor:pointer;';
        controls.appendChild(toggleBtn);

        header.appendChild(controls);
        container.appendChild(header);
        state.header = header;

        // Body
        const body = document.createElement('div');
        Object.assign(body.style, {
            flex: '1',
            overflowY: 'auto',
            padding: '10px',
            fontSize: '14px',
            display: CONFIG.expanded ? 'flex' : 'none',
            flexDirection: 'column'
        });
        container.appendChild(body);
        state.body = body;

        // Input Area
        const inputArea = document.createElement('div');
        Object.assign(inputArea.style, {
            display: CONFIG.expanded ? 'flex' : 'none',
            padding: '10px',
            borderTop: '1px solid #ccc',
            alignItems: 'center',
            gap: '5px'
        });

        const input = document.createElement('textarea');
        input.rows = 2;
        input.placeholder = '请输入消息...';
        Object.assign(input.style, {
            flex: '1',
            resize: 'none',
            borderRadius: '5px',
            border: '1px solid #ccc',
            padding: '5px',
            fontSize: '14px',
            fontFamily: 'inherit'
        });

        const sendBtn = document.createElement('button');
        sendBtn.textContent = '发送';
        sendBtn.style.cssText = 'padding:6px 15px;border:none;border-radius:5px;color:#fff;cursor:pointer;';
        inputArea.appendChild(input);
        inputArea.appendChild(sendBtn);
        container.appendChild(inputArea);

        state.container = container;
        state.input = input;
        state.sendBtn = sendBtn;
        state.inputArea = inputArea;

        // Toggle expand event
        toggleBtn.addEventListener('click', () => {
            CONFIG.expanded = !CONFIG.expanded;
            container.style.height = CONFIG.expanded ? '500px' : '40px';
            body.style.display = CONFIG.expanded ? 'flex' : 'none';
            inputArea.style.display = CONFIG.expanded ? 'flex' : 'none';
            toggleBtn.textContent = CONFIG.expanded ? '−' : '+';
        });

        // Drag to move
        let dragging = false, offsetX = 0, offsetY = 0;
        header.addEventListener('mousedown', e => {
            dragging = true;
            offsetX = e.clientX - container.offsetLeft;
            offsetY = e.clientY - container.offsetTop;
            header.style.cursor = 'grabbing';
        });
        document.addEventListener('mouseup', () => {
            if (dragging) {
                dragging = false;
                header.style.cursor = 'grab';
                savePosition(container.offsetLeft, container.offsetTop);
            }
        });
        document.addEventListener('mousemove', e => {
            if (!dragging) return;
            let x = e.clientX - offsetX, y = e.clientY - offsetY;
            x = Math.max(0, Math.min(x, window.innerWidth - container.offsetWidth));
            y = Math.max(0, Math.min(y, window.innerHeight - container.offsetHeight));
            container.style.left = x + 'px';
            container.style.top = y + 'px';
        });

        // Send message events
        sendBtn.addEventListener('click', sendMessage);
        input.addEventListener('keydown', e => {
            if (e.key === 'Enter' && !e.shiftKey) {
                e.preventDefault();
                sendBtn.click();
            }
        });

        // Theme switch event
        themeBtn.addEventListener('click', async () => {
            CONFIG.theme = CONFIG.theme === 'pink' ? 'dark' : 'pink';
            await GM_setValue('gcui_theme', CONFIG.theme);
            updateTheme();
        });

        document.body.appendChild(container);
        updateTheme();
    }

    buildUI();
})();



// ==UserScript==
// @name         GlobalChatUI Pro 第二部分 修正版
// @match        *://*/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_notification
// ==/UserScript==

(async function() {
  'use strict';

  // 等待DOM准备
  await new Promise(resolve => {
    if (document.readyState === 'complete' || document.readyState === 'interactive') resolve();
    else window.addEventListener('DOMContentLoaded', resolve);
  });

  // 获取DOM元素,确认ID是第1部分中对应的
  const container = document.getElementById('gcui-container');
  const header = document.getElementById('gcui-header');
  const body = document.getElementById('gcui-body');
  const inputArea = document.getElementById('gcui-input-area');
  const sendBtn = document.getElementById('gcui-send-btn');

  if (!container || !header || !body || !inputArea || !sendBtn) {
    console.error('缺少必要DOM元素,请确认前置代码生成');
    return;
  }

  // 异步读取配置
  let theme = 'pink';
  let posX = 100;
  let posY = 100;
  try {
    theme = await GM_getValue('gcui_theme', 'pink');
    posX = await GM_getValue('gcui_posX', 100);
    posY = await GM_getValue('gcui_posY', 100);
  } catch(e) {
    console.warn('读取配置失败,使用默认值', e);
  }

  // 初始化位置样式
  container.style.position = 'fixed';
  container.style.left = posX + 'px';
  container.style.top = posY + 'px';
  container.style.zIndex = 99999;
  container.style.userSelect = 'none';

  // 拖拽相关变量
  let dragging = false;
  let offsetX = 0;
  let offsetY = 0;

  header.style.cursor = 'grab';
  header.style.userSelect = 'none';

  header.addEventListener('mousedown', e => {
    dragging = true;
    offsetX = e.clientX - container.offsetLeft;
    offsetY = e.clientY - container.offsetTop;
    header.style.cursor = 'grabbing';
    e.preventDefault();
  });

  document.addEventListener('mouseup', async () => {
    if (dragging) {
      dragging = false;
      header.style.cursor = 'grab';
      try {
        await GM_setValue('gcui_posX', container.offsetLeft);
        await GM_setValue('gcui_posY', container.offsetTop);
      } catch(e) {
        console.warn('保存位置失败', e);
      }
    }
  });

  document.addEventListener('mousemove', e => {
    if (!dragging) return;
    let x = e.clientX - offsetX;
    let y = e.clientY - offsetY;

    x = Math.min(Math.max(0, x), window.innerWidth - container.offsetWidth);
    y = Math.min(Math.max(0, y), window.innerHeight - container.offsetHeight);

    container.style.left = x + 'px';
    container.style.top = y + 'px';
  });

  // 主题应用函数
  function applyTheme(currentTheme) {
    if (currentTheme === 'pink') {
      container.style.backgroundColor = '#ffc0d9';
      header.style.backgroundColor = '#ff66b2';
      header.style.color = '#fff';
      body.style.backgroundColor = '#fff0f7';
      body.style.color = '#333';
      inputArea.style.backgroundColor = '#ffd6e8';
      sendBtn.style.backgroundColor = '#ff66b2';
      sendBtn.style.color = '#fff';
    } else {
      container.style.backgroundColor = '#2c2c2c';
      header.style.backgroundColor = '#444';
      header.style.color = '#ddd';
      body.style.backgroundColor = '#222';
      body.style.color = '#ddd';
      inputArea.style.backgroundColor = '#333';
      sendBtn.style.backgroundColor = '#666';
      sendBtn.style.color = '#fff';
    }
  }

  // 创建并插入主题切换按钮
  const themeToggleBtn = document.createElement('button');
  themeToggleBtn.textContent = theme === 'pink' ? '切换暗黑' : '切换粉色';
  themeToggleBtn.style.marginLeft = '10px';
  themeToggleBtn.style.padding = '4px 10px';
  themeToggleBtn.style.border = 'none';
  themeToggleBtn.style.borderRadius = '4px';
  themeToggleBtn.style.cursor = 'pointer';
  themeToggleBtn.style.backgroundColor = '#fff';
  themeToggleBtn.style.color = '#333';
  themeToggleBtn.style.userSelect = 'none';

  header.appendChild(themeToggleBtn);

  themeToggleBtn.addEventListener('click', async () => {
    theme = theme === 'pink' ? 'dark' : 'pink';
    try {
      await GM_setValue('gcui_theme', theme);
    } catch(e) {
      console.warn('保存主题失败', e);
    }
    applyTheme(theme);
    themeToggleBtn.textContent = theme === 'pink' ? '切换暗黑' : '切换粉色';
  });

  applyTheme(theme);

  // 提示函数
  function showToast(msg) {
    if (typeof GM_notification === 'function') {
      GM_notification({ text: msg, timeout: 2000, silent: true });
    } else {
      alert(msg);
    }
  }

  showToast('GlobalChatUI Pro 第二部分已加载');

})();