// ==UserScript==
// @name AI助手選擇器
// @name:en AI Assistant Selector
// @namespace http://tampermonkey.net/
// @version 1.9
// @description 一個 Tampermonkey 腳本,提供浮動介面整合多款 AI 助手,支援右鍵呼叫、清空歷史訊息、可調整尺寸,並記錄 AI 選擇狀態。
// @description:en A Tampermonkey script that provides a floating interface integrating multiple AI assistants, with support for right-click activation, clearing chat history, resizable windows, and persistent AI selection state.
// @author Your name
// @match *://*/*
// @match *://grok.com/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @grant GM_openInTab
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// 樣式定義
const styles = `
.ai-selector-container {
position: fixed;
background: #2c2c2c;
padding: 15px;
border-radius: 10px;
z-index: 9999;
width: ${GM_getValue('containerWidth', '300px')};
height: ${GM_getValue('containerHeight', 'auto')};
top: ${GM_getValue('containerTop', '50px')};
left: ${GM_getValue('containerLeft', '50px')};
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
color: white;
font-family: Arial, sans-serif;
transition: all 0.3s;
resize: both;
overflow: auto;
}
.ai-selector-bubble {
position: fixed;
width: 40px;
height: 40px;
background: #666;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 16px;
font-weight: bold;
cursor: move;
z-index: 10000;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
.ai-selector-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.ai-selector-title {
font-size: 16px;
font-weight: bold;
}
.ai-selector-minimize {
cursor: pointer;
padding: 5px;
}
.ai-selector-content {
display: none;
flex-direction: column;
gap: 10px;
}
.ai-option {
display: flex;
align-items: center;
padding: 8px;
border-radius: 5px;
cursor: pointer;
transition: background 0.3s;
}
.ai-option:hover {
background: rgba(255,255,255,0.1);
}
.ai-option.selected {
background: #4285f4;
}
.ai-name {
margin-left: 10px;
}
.question-input {
width: 100%;
padding: 8px;
border-radius: 5px;
border: 1px solid #444;
background: #1c1c1c;
color: white;
margin-top: 10px;
box-sizing: border-box;
}
.send-button {
width: 100%;
padding: 8px;
border-radius: 5px;
border: none;
background: #4285f4;
color: white;
cursor: pointer;
margin-top: 10px;
}
.send-button:hover {
background: #5294ff;
}
.send-button:disabled {
background: #666;
cursor: not-allowed;
}
.clear-button {
width: 100%;
padding: 8px;
border-radius: 5px;
border: none;
background: #e74c3c;
color: white;
cursor: pointer;
margin-top: 10px;
}
.clear-button:hover {
background: #ff6655;
}
.grok-response {
margin-top: 10px;
padding: 10px;
background: #1c1c1c;
border-radius: 5px;
border: 1px solid #444;
color: white;
white-space: pre-wrap;
max-height: 200px;
overflow-y: auto;
}
.grok-response p {
margin: 5px 0;
padding: 5px;
background: #333;
border-radius: 3px;
}
`;
// AI助手配置
const AIs = [
{ id: 'gemini', name: 'Gemini', url: 'https://gemini.google.com/app', inputSelector: 'rich-textarea.text-input-field_textarea', color: '#8e44ad' },
{ id: 'grok', name: 'Grok', url: 'https://grok.com/', color: '#e74c3c' },
{ id: 'chatgpt', name: 'ChatGPT', url: 'https://chatgpt.com/', color: '#27ae60' },
{ id: 'perplexity', name: 'Perplexity', url: 'https://www.perplexity.ai/', color: '#3498db' }
];
// 添加樣式
GM_addStyle(styles);
// 創建UI
function createUI() {
const bubble = document.createElement('div');
bubble.className = 'ai-selector-bubble';
bubble.textContent = 'AI';
bubble.style.top = GM_getValue('positionTop', '20px');
bubble.style.left = GM_getValue('positionLeft', 'calc(100% - 60px)');
const container = document.createElement('div');
container.className = 'ai-selector-container';
container.style.display = 'none';
const header = document.createElement('div');
header.className = 'ai-selector-header';
const title = document.createElement('div');
title.className = 'ai-selector-title';
title.textContent = 'AI助手選擇器 / AI Assistant Selector';
const minimize = document.createElement('div');
minimize.className = 'ai-selector-minimize';
minimize.textContent = '×';
header.appendChild(title);
header.appendChild(minimize);
const content = document.createElement('div');
content.className = 'ai-selector-content';
const selectedAIs = GM_getValue('selectedAIs', AIs.map(ai => ai.id));
AIs.forEach(ai => {
const option = document.createElement('div');
option.className = 'ai-option';
option.dataset.aiId = ai.id;
option.style.border = `2px solid ${ai.color}`;
if (selectedAIs.includes(ai.id)) {
option.classList.add('selected');
}
const name = document.createElement('span');
name.className = 'ai-name';
name.textContent = ai.name;
option.appendChild(name);
content.appendChild(option);
});
const questionInput = document.createElement('textarea');
questionInput.className = 'question-input';
questionInput.placeholder = '輸入您的問題 / Enter your question';
const sendButton = document.createElement('button');
sendButton.className = 'send-button';
sendButton.textContent = '發送到選中的AI / Send to Selected AI';
const clearButton = document.createElement('button');
clearButton.className = 'clear-button';
clearButton.textContent = '清空歷史訊息 / Clear History';
const grokResponseDiv = document.createElement('div');
grokResponseDiv.className = 'grok-response';
grokResponseDiv.innerHTML = '<p>等待 Grok 回應... / Waiting for Grok response...</p>';
content.appendChild(questionInput);
content.appendChild(sendButton);
content.appendChild(clearButton);
content.appendChild(grokResponseDiv);
container.appendChild(header);
container.appendChild(content);
document.body.appendChild(bubble);
document.body.appendChild(container);
return { bubble, container, grokResponseDiv };
}
// 初始化事件監聽
function initializeEvents(bubble, container, grokResponseDiv) {
const aiOptions = container.querySelectorAll('.ai-option');
const questionInput = container.querySelector('.question-input');
const sendButton = container.querySelector('.send-button');
const clearButton = container.querySelector('.clear-button');
const minimizeButton = container.querySelector('.ai-selector-minimize');
const content = container.querySelector('.ai-selector-content');
aiOptions.forEach(option => {
option.addEventListener('click', () => {
option.classList.toggle('selected');
const selectedAIs = Array.from(aiOptions)
.filter(opt => opt.classList.contains('selected'))
.map(opt => opt.dataset.aiId);
GM_setValue('selectedAIs', selectedAIs);
});
});
minimizeButton.addEventListener('click', () => {
container.style.display = 'none';
bubble.style.display = 'flex';
});
sendButton.addEventListener('click', () => {
const selectedAIs = Array.from(aiOptions).filter(option => option.classList.contains('selected'));
const question = questionInput.value.trim();
if (selectedAIs.length > 0 && question) {
selectedAIs.forEach(aiOption => {
const aiId = aiOption.dataset.aiId;
const ai = AIs.find(a => a.id === aiId);
if (ai) openAIInNewTab(ai, question);
});
questionInput.value = '';
}
});
clearButton.addEventListener('click', () => {
grokResponseDiv.innerHTML = '<p>等待 Grok 回應... / Waiting for Grok response...</p>';
});
function makeDraggable(element, saveKeyPrefix, onClick) {
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
let startX = 0, startY = 0;
let moved = false;
element.onmousedown = dragMouseDown;
function dragMouseDown(e) {
e.preventDefault();
startX = e.clientX;
startY = e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
moved = false;
document.onmouseup = closeDragElement;
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e.preventDefault();
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
let newTop = element.offsetTop - pos2;
let newLeft = element.offsetLeft - pos1;
const elementWidth = element.offsetWidth;
const elementHeight = element.offsetHeight;
newTop = Math.max(0, Math.min(newTop, window.innerHeight - elementHeight));
newLeft = Math.max(0, Math.min(newLeft, window.innerWidth - elementWidth));
element.style.top = newTop + "px";
element.style.left = newLeft + "px";
moved = true;
}
function closeDragElement(e) {
document.onmouseup = null;
document.onmousemove = null;
if (saveKeyPrefix) {
GM_setValue(`${saveKeyPrefix}Top`, element.style.top);
GM_setValue(`${saveKeyPrefix}Left`, element.style.left);
}
if (!moved && Math.abs(e.clientX - startX) < 5 && Math.abs(e.clientY - startY) < 5 && onClick) {
onClick();
}
}
}
function showContainer() {
const bubbleRect = bubble.getBoundingClientRect();
const containerWidth = parseInt(container.style.width) || 300;
const containerHeight = parseInt(container.style.height) || container.offsetHeight;
let containerLeft = bubbleRect.left + bubbleRect.width / 2 - containerWidth / 2;
let containerTop = bubbleRect.bottom;
containerLeft = Math.max(0, Math.min(containerLeft, window.innerWidth - containerWidth));
containerTop = Math.min(containerTop, window.innerHeight - containerHeight);
container.style.left = GM_getValue('containerLeft', `${containerLeft}px`);
container.style.top = GM_getValue('containerTop', `${containerTop}px`);
bubble.style.display = 'none';
container.style.display = 'block';
content.style.display = 'flex';
}
makeDraggable(bubble, 'position', showContainer);
document.addEventListener('contextmenu', (e) => {
if (container.style.display === 'none') {
e.preventDefault();
showContainer();
}
});
container.addEventListener('resize', () => {
GM_setValue('containerWidth', container.style.width);
GM_setValue('containerHeight', container.style.height);
});
}
// 在新標籤頁中打開AI但不切換焦點
function openAIInNewTab(ai, question) {
const url = `${ai.url}${ai.id === 'gemini' ? '?q=' : '?q='}${encodeURIComponent(question)}`;
GM_openInTab(url, {
active: false, // 不激活新標籤頁
insert: true, // 在當前標籤旁插入
setParent: true // 設置父窗口關係
});
}
// 處理 Gemini 頁面
function handleGeminiPage() {
if (window.location.hostname === 'gemini.google.com' && window.location.search.includes('q=')) {
const query = new URLSearchParams(window.location.search).get('q');
if (query) {
console.log("开始执行Gemini自动填入脚本");
function setTextAndSendAfterDelay(string) {
const richTextarea = document.querySelector('rich-textarea.text-input-field_textarea');
if (!richTextarea) return false;
const firstDiv = richTextarea.querySelector('div');
if (!firstDiv) return false;
firstDiv.innerText = string;
const event = new Event('input', { bubbles: true });
firstDiv.dispatchEvent(event);
setTimeout(() => {
const sendButton = document.querySelector('.send-button');
if (sendButton) sendButton.click();
}, 1000);
return true;
}
const waitForElement = (selector, maxAttempts = 30) => {
return new Promise((resolve) => {
let attempts = 0;
const interval = setInterval(() => {
const element = document.querySelector(selector);
attempts++;
if (element || attempts >= maxAttempts) {
clearInterval(interval);
resolve(element);
}
}, 500);
});
};
const trySetQuestion = async () => {
await waitForElement('rich-textarea.text-input-field_textarea');
setTextAndSendAfterDelay(decodeURIComponent(query));
};
trySetQuestion();
window.history.replaceState({}, document.title, '/app');
}
}
}
// 處理 Grok 頁面,兩秒後傳資料
function handleGrokPage() {
if (window.location.hostname === 'grok.com') {
let lastContent = '';
let timeoutId = null;
setInterval(() => {
const breakWordsPs = document.querySelectorAll('p.break-words:not(.processed)');
let currentContent = '';
breakWordsPs.forEach(p => {
const content = p.textContent.trim();
if (content) {
currentContent += content + '\n';
p.classList.add('processed');
}
});
if (currentContent && currentContent !== lastContent) {
lastContent = currentContent;
if (timeoutId) clearTimeout(timeoutId); // 清除之前的計時器
timeoutId = setTimeout(() => {
GM_setValue('grokResponse', lastContent.trim());
console.log('Grok 新內容穩定兩秒後傳出:', lastContent.trim());
}, 2000); // 等待兩秒
}
}, 1000); // 每秒檢查一次
}
}
// 檢查並顯示 Grok 回應
function checkGrokResponse(container, grokResponseDiv) {
if (window.location.hostname !== 'grok.com') {
if (!grokResponseDiv) {
console.error('未找到 .grok-response 元素');
return;
}
grokResponseDiv.innerHTML = '<p>等待 Grok 回應... / Waiting for Grok response...</p>';
setInterval(() => {
const response = GM_getValue('grokResponse', '');
if (response) {
console.log('A 頁面收到 Grok 回應:', response);
const newResponse = document.createElement('p');
newResponse.textContent = response;
if (grokResponseDiv.firstChild) {
grokResponseDiv.insertBefore(newResponse, grokResponseDiv.firstChild);
} else {
grokResponseDiv.appendChild(newResponse);
}
GM_setValue('grokResponse', '');
}
}, 1000);
}
}
// 啟動腳本
function initialize() {
const { bubble, container, grokResponseDiv } = createUI();
initializeEvents(bubble, container, grokResponseDiv);
handleGeminiPage();
handleGrokPage();
checkGrokResponse(container, grokResponseDiv);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize);
} else {
initialize();
}
})();