// ==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 第二部分已加载');
})();