Bedrock Learning Gemini Solver

Takes a screenshot of Bedrock Learning, sends it to Gemini, and displays the answer in a beautifully styled new tab.

当前为 2025-02-16 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Bedrock Learning Gemini Solver
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.2
  5. // @description Takes a screenshot of Bedrock Learning, sends it to Gemini, and displays the answer in a beautifully styled new tab.
  6. // @author You
  7. // @match https://app.bedrocklearning.org/*
  8. // @grant GM_openInTab
  9. // @grant GM_xmlhttpRequest
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. (function () {
  16. 'use strict';
  17.  
  18. const GEMINI_API_KEY_KEY = 'geminiApiKey'; // Key for storing the API key in Tampermonkey storage
  19. let geminiApiKey = GM_getValue(GEMINI_API_KEY_KEY, null); // Retrieve the API key from storage
  20. const GEMINI_API_URL = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro-latest:generateContent?key=';
  21. const DEFAULT_PROMPT = "Analyze the image and identify the MAIN question. Answer to that question with a clear and concise answer. Do not show your working.";
  22. const ADDITIONAL_PROMPT_MESSAGE = "Enter any additional instructions or questions to send with the image (or leave blank for default prompt):";
  23.  
  24. async function checkApiKey() {
  25. if (!geminiApiKey) {
  26. geminiApiKey = prompt("Enter your Google AI Studio API Key:");
  27. if (geminiApiKey) {
  28. GM_setValue(GEMINI_API_KEY_KEY, geminiApiKey); // Save the API key to Tampermonkey storage
  29. alert("API key saved. Press Ctrl+X again to process the question.");
  30. } else {
  31. alert("API key required for the script to function.");
  32. }
  33. return false;
  34. }
  35. return true;
  36. }
  37.  
  38. function captureScreenshot() {
  39. return new Promise((resolve, reject) => {
  40. // Use native browser APIs or a custom implementation for screenshot capture
  41. // This is a placeholder for the actual screenshot logic
  42. try {
  43. const canvas = document.createElement('canvas');
  44. const context = canvas.getContext('2d');
  45. canvas.width = window.innerWidth;
  46. canvas.height = window.innerHeight;
  47. context.drawImage(document.documentElement, 0, 0);
  48. resolve(canvas);
  49. } catch (error) {
  50. reject(error);
  51. }
  52. });
  53. }
  54.  
  55. function convertCanvasToBlob(canvas) {
  56. return new Promise((resolve, reject) => {
  57. canvas.toBlob(blob => {
  58. blob ? resolve(blob) : reject(new Error('Failed to convert canvas to blob.'));
  59. }, 'image/png');
  60. });
  61. }
  62.  
  63. async function sendImageToGemini(imageBlob, additionalPrompt = "") {
  64. if (!await checkApiKey()) return;
  65.  
  66. const reader = new FileReader();
  67. reader.readAsDataURL(imageBlob);
  68.  
  69. return new Promise((resolve, reject) => {
  70. reader.onloadend = () => {
  71. const base64Image = reader.result.split(',')[1];
  72. const promptText = additionalPrompt.trim() !== "" ? additionalPrompt : DEFAULT_PROMPT;
  73.  
  74. const payload = {
  75. contents: [
  76. {
  77. parts: [
  78. { text: promptText },
  79. {
  80. inline_data: {
  81. mime_type: "image/png",
  82. data: base64Image
  83. }
  84. }
  85. ]
  86. }
  87. ]
  88. };
  89.  
  90. GM_xmlhttpRequest({
  91. method: "POST",
  92. url: GEMINI_API_URL + geminiApiKey,
  93. headers: { "Content-Type": "application/json" },
  94. data: JSON.stringify(payload),
  95. onload: function (response) {
  96. if (response.status >= 200 && response.status < 300) {
  97. try {
  98. const jsonResponse = JSON.parse(response.responseText);
  99. const answer = jsonResponse?.candidates?.[0]?.content?.parts?.[0]?.text || "No answer found.";
  100. resolve(answer);
  101. } catch (error) {
  102. reject("Error parsing response: " + error.message);
  103. }
  104. } else {
  105. reject(`API Error: ${response.status} - ${response.responseText}`);
  106. }
  107. },
  108. onerror: function (error) {
  109. reject("Request error: " + error);
  110. }
  111. });
  112. };
  113. reader.onerror = () => reject(new Error('Failed to read image.'));
  114. });
  115. }
  116.  
  117. function displayAnswerInNewTab(answer) {
  118. const newTabContent = `
  119. <!DOCTYPE html>
  120. <html lang="en">
  121. <head>
  122. <meta charset="UTF-8">
  123. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  124. <title>Gemini Answer</title>
  125. <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
  126. <style>
  127. @keyframes hueShift {
  128. 0% { background-color: #1E90FF; } /* Blue */
  129. 33% { background-color: #32CD32; } /* Green */
  130. 66% { background-color: #FF69B4; } /* Pink */
  131. 100% { background-color: #1E90FF; } /* Blue */
  132. }
  133.  
  134. body {
  135. font-family: 'Poppins', sans-serif;
  136. animation: hueShift 10s infinite;
  137. color: #FFF;
  138. text-align: center;
  139. display: flex;
  140. justify-content: center;
  141. align-items: center;
  142. height: 100vh;
  143. margin: 0;
  144. overflow: hidden;
  145. }
  146. .container {
  147. background: rgba(255, 255, 255, 0.1);
  148. padding: 20px;
  149. border-radius: 12px;
  150. max-width: 600px;
  151. box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
  152. backdrop-filter: blur(10px);
  153. }
  154. h1 {
  155. font-size: 22px;
  156. font-weight: 600;
  157. margin-bottom: 10px;
  158. }
  159. #answer {
  160. font-family: 'Poppins', sans-serif;
  161. white-space: pre-wrap;
  162. word-wrap: break-word;
  163. font-size: 14px;
  164. background: rgba(255, 255, 255, 0.1);
  165. padding: 10px;
  166. border-radius: 8px;
  167. text-align: left;
  168. max-height: 300px;
  169. overflow-y: auto;
  170. }
  171. button {
  172. margin-top: 10px;
  173. padding: 10px 20px;
  174. font-size: 14px;
  175. border: none;
  176. border-radius: 8px;
  177. background: #FFC857;
  178. color: #222;
  179. cursor: pointer;
  180. transition: 0.3s;
  181. }
  182. button:hover {
  183. background: #FFA500;
  184. }
  185. </style>
  186. </head>
  187. <body>
  188. <div class="container">
  189. <h1>Gemini Answer</h1>
  190. <pre id="answer">${answer}</pre>
  191. <button onclick="copyToClipboard()">📋 Copy</button>
  192. </div>
  193.  
  194. <script>
  195. function copyToClipboard() {
  196. const answerText = document.getElementById("answer").textContent;
  197. navigator.clipboard.writeText(answerText).then(() => {
  198. alert("Copied to clipboard!");
  199. }).catch(err => console.error("Copy failed:", err));
  200. }
  201. </script>
  202. </body>
  203. </html>
  204. `;
  205.  
  206. GM_openInTab(`data:text/html;charset=utf-8,${encodeURIComponent(newTabContent)}`, { active: true });
  207. }
  208.  
  209. document.addEventListener('keydown', async function (event) {
  210. if (event.ctrlKey && event.key === 'x') {
  211. event.preventDefault();
  212. try {
  213. const canvas = await captureScreenshot();
  214. const imageBlob = await convertCanvasToBlob(canvas);
  215. const additionalPrompt = prompt(ADDITIONAL_PROMPT_MESSAGE);
  216. const answer = await sendImageToGemini(imageBlob, additionalPrompt);
  217. displayAnswerInNewTab(answer);
  218. } catch (error) {
  219. alert("Error: " + error);
  220. }
  221. }
  222. });
  223.  
  224. console.log("Bedrock Learning Gemini Solver script loaded.");
  225. })();