您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
AI studio
// ==UserScript== // @name AI Giải Bài Tập // @namespace http://tampermonkey.net/ // @version 1.0 // @description AI studio // @author Tran Bao Ngoc // @match https://*/* // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @connect generativelanguage.googleapis.com // @require https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js // ==/UserScript== (async function() { 'use strict'; let GEMINI_API_KEY = GM_getValue('geminiApiKey', ""); // === UI Tối Giản Nhỏ Gọn === const ui = document.createElement('div'); ui.id = 'aiPanel'; ui.innerHTML = ` <div class="ai-header"> <h2>Trần Bảo Ngọc</h2> <div id="aiStatus">Ready</div> </div> <div id="apiKeySection"> <label>API Key Gemini</label> <input type="password" id="apiKeyInput" value="${GEMINI_API_KEY}" placeholder="Nhập API key của bạn..." /> </div> <button id="changeApiBtn" style="display:none; width: 100%; margin-bottom: 8px;">Thay đổi Key</button> <div class="ai-selects"> <select id="modelSelect"> <option value="gemini-flash-latest">⚡️ Flash</option> <option value="gemini-2.5-pro">✨ Pro 2.5</option> </select> <select id="lang"><option value="vi">VI</option><option value="en">EN</option></select> <select id="subject"> <option>Toán</option><option>Lý</option><option>Hóa</option><option>Sinh</option><option>Sử</option><option>Địa</option><option>Văn</option><option>Anh</option><option>GDCD</option><option>Tin học</option> </select> </div> <div class="ai-selects"> <select id="outputMode" style="width:100%"> <option value="answer">Chỉ đáp án</option> <option value="explain">Giải thích chi tiết</option> <option value="custom">Tùy chỉnh...</option> </select> </div> <div id="customPromptSection" style="display:none; margin-bottom: 8px;"> <label>Yêu cầu tùy chỉnh</label> <textarea id="customPromptInput" rows="3" placeholder="Ví dụ: Tóm tắt nội dung trong ảnh..."></textarea> </div> <div class="ai-actions"> <button id="btnShot" disabled>📸 Kéo vùng</button> <button id="btnFullPage" disabled>📄 Toàn trang</button> </div> <button id="btnToggleTextMode" class="text-mode-btn" disabled>📝 Nhập câu hỏi</button> <div id="textInputSection" style="display: none; margin-top: 8px;"> <label>Nhập câu hỏi của bạn vào đây</label> <textarea id="textQuestionInput" rows="4" placeholder="Ví dụ: Trình bày vai trò của quang hợp..."></textarea> <button id="btnSendTextQuestion" style="width:100%; margin-top: 4px;">Gửi câu hỏi</button> </div> <div class="ai-box"> <label>Ảnh</label> <div id="imgBox"></div> </div> <div class="ai-box"> <label>Đáp án</label> <div id="ansBox"></div> </div> `; document.body.appendChild(ui); // === Lấy các phần tử DOM === const apiKeyInput = document.getElementById('apiKeyInput'); const apiKeySection = document.getElementById('apiKeySection'); const changeApiBtn = document.getElementById('changeApiBtn'); const aiStatus = document.getElementById('aiStatus'); const btnShot = document.getElementById('btnShot'); const btnFullPage = document.getElementById('btnFullPage'); const btnToggleTextMode = document.getElementById('btnToggleTextMode'); const textInputSection = document.getElementById('textInputSection'); const textQuestionInput = document.getElementById('textQuestionInput'); const btnSendTextQuestion = document.getElementById('btnSendTextQuestion'); const outputModeSelect = document.getElementById('outputMode'); const customPromptSection = document.getElementById('customPromptSection'); const customPromptInput = document.getElementById('customPromptInput'); const allActionButtons = [btnShot, btnFullPage, btnToggleTextMode]; // === Hàm Gửi Yêu Cầu Đến Gemini (Đã cập nhật) === function sendToGemini(prompt, base64Image = null) { const model = document.getElementById('modelSelect').value; const ansBox = document.getElementById('ansBox'); const imgBox = document.getElementById('imgBox'); ansBox.innerHTML = "⏳ Đang gửi đến Gemini..."; ansBox.classList.add('loading'); let parts = [{ text: prompt }]; if (base64Image) { parts.push({ inlineData: { mimeType: "image/jpeg", data: base64Image } }); } GM_xmlhttpRequest({ method: "POST", url: `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${GEMINI_API_KEY}`, headers: { "Content-Type": "application/json" }, data: JSON.stringify({ contents: [{ parts: parts }], generationConfig: { "temperature": 0.2, "topP": 0.95, "topK": 40 } }), onload: r => { ansBox.classList.remove('loading'); try { const data = JSON.parse(r.responseText); if (data.error) throw new Error(data.error.message); const result = data?.candidates?.[0]?.content?.parts?.[0]?.text || "❌ Không nhận được phản hồi."; // *** THAY ĐỔI: Ẩn ảnh khi nhận được câu trả lời *** if (base64Image) { imgBox.innerHTML = ''; } typeEffect(ansBox, result.trim()); } catch (err) { ansBox.innerHTML = `<b style="color:red;">Lỗi API:</b> ${err.message || "Kiểm tra F12 > Console để biết thêm chi tiết"}`; console.error("Lỗi Gemini:", r.responseText); } }, onerror: err => { ansBox.classList.remove('loading'); ansBox.innerHTML = `<b style="color:red;">Lỗi request:</b> ${JSON.stringify(err)}`; } }); } // === Hàm tạo Prompt === function createPrompt(isImage = true) { const subj = document.getElementById('subject').value; const lang = document.getElementById('lang').value; const mode = document.getElementById('outputMode').value; const langStr = lang === 'vi' ? 'Tiếng Việt' : 'English'; const source = isImage ? 'trong ảnh' : 'được cung cấp'; if (mode === 'custom') { const customText = customPromptInput.value.trim(); if (!customText) { document.getElementById('ansBox').innerHTML = '<b style="color:red;">Lỗi:</b> Vui lòng nhập yêu cầu tùy chỉnh của bạn.'; return null; } return `${customText} (Trả lời bằng ${langStr})`; } else if (mode === 'answer') { return `Với bài tập môn ${subj} ${source}, chỉ đưa ra đáp án cuối cùng. Không giải thích. Không dùng markdown. Trả lời bằng ${langStr}.`; } else { // 'explain' return `Phân tích và giải chi tiết bài tập môn ${subj} ${source}. Suy nghĩ từng bước, đưa ra công thức và lời giải rõ ràng. Trả lời bằng ${langStr}.`; } } // === Hàm kiểm tra API Key === function checkApiKey(key) { if (!key) { aiStatus.textContent = 'Vui lòng nhập API Key'; aiStatus.style.color = '#e74c3c'; allActionButtons.forEach(b => b.disabled = true); apiKeySection.style.display = 'block'; changeApiBtn.style.display = 'none'; return; } aiStatus.textContent = '🔄 Đang kiểm tra key...'; aiStatus.style.color = '#f1c40f'; allActionButtons.forEach(b => b.disabled = true); GM_xmlhttpRequest({ method: "POST", url: `https://generativelanguage.googleapis.com/v1beta/models/gemini-flash-latest:generateContent?key=${key}`, headers: { "Content-Type": "application/json" }, data: JSON.stringify({ contents: [{ parts: [{ text: "hi" }] }] }), onload: function(response) { if (response.status === 200) { try { const data = JSON.parse(response.responseText); if (data.candidates) { aiStatus.textContent = '✅ Key hợp lệ'; aiStatus.style.color = '#00b894'; GEMINI_API_KEY = key; GM_setValue('geminiApiKey', key); apiKeySection.style.display = 'none'; changeApiBtn.style.display = 'block'; allActionButtons.forEach(b => b.disabled = false); } else { throw new Error("Invalid response"); } } catch (e) { aiStatus.textContent = '❌ Key không hợp lệ.'; aiStatus.style.color = '#e74c3c'; allActionButtons.forEach(b => b.disabled = true); apiKeySection.style.display = 'block'; changeApiBtn.style.display = 'none'; } } else { aiStatus.textContent = '❌ Key không hợp lệ/hết hạn'; aiStatus.style.color = '#e74c3c'; allActionButtons.forEach(b => b.disabled = true); apiKeySection.style.display = 'block'; changeApiBtn.style.display = 'none'; } }, onerror: function(error) { aiStatus.textContent = ' Lỗi mạng khi kiểm tra key'; aiStatus.style.color = '#e74c3c'; allActionButtons.forEach(b => b.disabled = true); } }); } // === Hàm xử lý chụp ảnh === async function handleScreenshot(options = {}) { const imgBox = document.getElementById('imgBox'); const ansBox = document.getElementById('ansBox'); imgBox.innerHTML = "🕐 Đang chụp ảnh..."; ansBox.innerHTML = ""; try { // Luôn cuộn lên đầu trang để đảm bảo chụp từ đầu khi chụp toàn trang if (!options.x && !options.y) { window.scrollTo(0, 0); } const canvas = await html2canvas(document.body, { ...options, scale: 1.5, useCORS: true, allowTaint: true }); const base64 = canvas.toDataURL('image/jpeg', 0.9).split(',')[1]; imgBox.innerHTML = `<img src="${canvas.toDataURL()}">`; const prompt = createPrompt(true); if (prompt) { sendToGemini(prompt, base64); } } catch (err) { imgBox.innerHTML = `<b style="color:red;">❌ Lỗi chụp ảnh:</b> ${err.message}`; ansBox.innerHTML = ''; } } // === Xử lý sự kiện === apiKeyInput.addEventListener('blur', () => checkApiKey(apiKeyInput.value.trim())); changeApiBtn.addEventListener('click', () => { apiKeySection.style.display = 'block'; changeApiBtn.style.display = 'none'; apiKeyInput.focus(); allActionButtons.forEach(b => b.disabled = true); aiStatus.textContent = "Nhập key mới và click ra ngoài"; aiStatus.style.color = "#f1c40f"; }); outputModeSelect.addEventListener('change', () => { customPromptSection.style.display = (outputModeSelect.value === 'custom') ? 'block' : 'none'; }); btnToggleTextMode.addEventListener('click', () => { const isVisible = textInputSection.style.display === 'block'; textInputSection.style.display = isVisible ? 'none' : 'block'; }); btnSendTextQuestion.addEventListener('click', () => { const question = textQuestionInput.value.trim(); if (!question) { document.getElementById('ansBox').innerHTML = '<b style="color:red;">Lỗi:</b> Vui lòng nhập câu hỏi.'; return; } const prompt = createPrompt(false); if (prompt) { const fullPrompt = `Câu hỏi: "${question}".\n\n${prompt}`; document.getElementById('imgBox').innerHTML = ""; sendToGemini(fullPrompt, null); } }); // === CSS === GM_addStyle(` #aiPanel { position: fixed; top: 30px; left: 30px; width: 280px; background: #1e1e1e; color: #fff; z-index: 999999; padding: 12px; border-radius: 8px; font-family: 'Segoe UI', sans-serif; box-shadow: 0 4px 12px rgba(0,0,0,0.3); display: none; cursor: move; font-size: 12px; border: 1px solid #333; } .ai-header { text-align: center; margin-bottom: 12px; border-bottom: 1px solid #333; padding-bottom: 8px; } .ai-header h2 { margin: 0 0 4px; font-size: 14px; color: #00b894; } #aiStatus { font-size: 10px; color: #aaa; min-height: 12px; } #apiKeySection, .ai-selects, .ai-box, #customPromptSection, .ai-actions { margin-bottom: 8px; } label { display: block; font-size: 11px; color: #ccc; margin-bottom: 4px; } #apiKeyInput, textarea { width: 100%; box-sizing: border-box; padding: 6px; border: 1px solid #444; border-radius: 4px; background: #2a2a2a; color: #fff; font-size: 11px; } #apiKeyInput:focus, textarea:focus { outline: none; border-color: #00b894; } textarea { resize: vertical; } .ai-selects, .ai-actions { display: flex; gap: 4px; } select, button { flex: 1; padding: 8px; border: 1px solid #444; border-radius: 4px; background: #2a2a2a; color: #fff; font-size: 11px; } select:focus, button:focus { outline: none; border-color: #00b894; } button { cursor: pointer; border: none; transition: background 0.2s ease; } button:disabled { background: #555 !important; cursor: not-allowed; opacity: 0.6; } .ai-actions button { background: #00b894; } .ai-actions button:hover:not(:disabled) { background: #009975; } #btnToggleTextMode { width: 100%; background: #e67e22; margin-bottom: 4px; } #btnToggleTextMode:hover:not(:disabled) { background: #d35400; } #btnSendTextQuestion { background: #3498db; } #btnSendTextQuestion:hover:not(:disabled) { background: #2980b9; } #changeApiBtn { background: #3498db; } #changeApiBtn:hover { background: #2980b9; } .ai-box div { min-height: 40px; background: #2a2a2a; padding: 8px; border-radius: 4px; font-size: 11px; white-space: pre-wrap; word-wrap: break-word; border: 1px solid #444; } #imgBox img { max-width: 100%; border-radius: 4px; } #ansBox.loading::after { content: '⏳'; display: inline-block; animation: spin 1s linear infinite; margin-left: 5px; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } #aiSnipOverlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); z-index: 2147483647; display: none; cursor: crosshair; } #aiSnipBox { position: absolute; border: 2px dashed #00b894; background: rgba(0,184,148,0.1); z-index: 2147483648; display: none; pointer-events: none; border-radius: 4px; } `); // === Kéo thả panel === let dragging = false, dragOffset = {x:0,y:0}; ui.addEventListener('mousedown', e => { if (!['INPUT', 'SELECT', 'BUTTON', 'TEXTAREA'].includes(e.target.tagName)) { dragging = true; dragOffset.x = e.clientX - ui.offsetLeft; dragOffset.y = e.clientY - ui.offsetTop; } }); document.addEventListener('mousemove', e => { if (dragging) { ui.style.left = (e.clientX - dragOffset.x) + 'px'; ui.style.top = (e.clientY - dragOffset.y) + 'px'; } }); document.addEventListener('mouseup', () => { dragging = false; }); // === Toggle bằng ShiftRight === document.addEventListener('keydown', e => { if (e.code === 'ShiftRight') { ui.style.display = ui.style.display === 'none' ? 'block' : 'none'; if(ui.style.display === 'block') { checkApiKey(GM_getValue('geminiApiKey', "")); } } }); // === Overlay và Chụp ảnh === const overlay = document.createElement('div'); overlay.id = 'aiSnipOverlay'; document.body.appendChild(overlay); const snipBox = document.createElement('div'); snipBox.id = 'aiSnipBox'; document.body.appendChild(snipBox); let selecting = false, startX, startY, endX, endY; btnShot.onclick = () => { selecting = true; overlay.style.display = 'block'; ui.style.display = 'none'; }; // --- CHỤP TOÀN BỘ TRANG --- btnFullPage.onclick = () => { ui.style.display = 'none'; // Ẩn panel trước khi chụp setTimeout(() => { handleScreenshot({}).finally(() => { ui.style.display = 'block'; // Hiện lại panel sau khi xong }); }, 150); }; overlay.addEventListener('mousedown', e => { if (!selecting) return; startX = e.clientX; startY = e.clientY; snipBox.style.left = startX + 'px'; snipBox.style.top = startY + 'px'; snipBox.style.width = '0px'; snipBox.style.height = '0px'; snipBox.style.display = 'block'; }); overlay.addEventListener('mousemove', e => { if (!selecting || startX === undefined) return; endX = e.clientX; endY = e.clientY; snipBox.style.left = Math.min(startX, endX) + 'px'; snipBox.style.top = Math.min(startY, endY) + 'px'; snipBox.style.width = Math.abs(endX - startX) + 'px'; snipBox.style.height = Math.abs(endY - startY) + 'px'; }); overlay.addEventListener('mouseup', async e => { if (!selecting || startX === undefined) return; const left = Math.min(startX, endX); const top = Math.min(startY, endY); const width = Math.abs(endX - startX); const height = Math.abs(endY - startY); selecting = false; overlay.style.display = 'none'; snipBox.style.display = 'none'; ui.style.display = 'block'; startX = startY = endX = endY = undefined; if (width < 10 || height < 10) return; handleScreenshot({ x: left, y: top, width: width, height: height }); }); // === Hiệu ứng gõ chữ === function typeEffect(el, text, speed = 10) { el.innerHTML = ""; let i = 0; function typing() { if (i < text.length) { el.innerHTML += text.charAt(i++); setTimeout(typing, speed); } }; typing(); } // === Khởi chạy lần đầu === checkApiKey(GEMINI_API_KEY); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址