From ffda90ce160da52cdb869b427c83ebb5d59a346f Mon Sep 17 00:00:00 2001 From: elijah <146715005+schizoposter@users.noreply.github.com> Date: Thu, 24 Oct 2024 15:20:04 +0200 Subject: [PATCH] v1.2 --- README.md | 22 +++ src/background.js | 8 ++ src/content.js | 326 ++++++++++++++++++++++++++++++++++++++++++ src/icons/icon128.png | Bin 0 -> 10421 bytes src/icons/icon16.png | Bin 0 -> 373 bytes src/icons/icon32.png | Bin 0 -> 1047 bytes src/icons/icon48.png | Bin 0 -> 1989 bytes src/manifest.json | 49 +++++++ src/popup.html | 268 ++++++++++++++++++++++++++++++++++ src/popup.js | 80 +++++++++++ 10 files changed, 753 insertions(+) create mode 100644 README.md create mode 100644 src/background.js create mode 100644 src/content.js create mode 100644 src/icons/icon128.png create mode 100644 src/icons/icon16.png create mode 100644 src/icons/icon32.png create mode 100644 src/icons/icon48.png create mode 100644 src/manifest.json create mode 100644 src/popup.html create mode 100644 src/popup.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..8e49507 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# CAS + +A simple, versatile language processing interface for websites. + +## Features +- Configurable + - Use own API + - Adjust opacity of responses +- Hidden + - Only visible to those who know where to look + +## Usage +After installing the extensions, selecting text and pressing `Ctrl+Space` will display the answer above the selected text in the configured opacity. To hide the answer, press `Ctrl+Space` again without selecting any text. +Additionally, pressing `Ctrl+Shift+Space` will write the answer into the nearest text field. + +## Configuration +The extension can be configured by clicking on the extension icon in the toolbar. +You will need to add your own OpenAI Endpoint (for example `https://api.elia.network`), and an API key. +There is an default URL and Key which is restricted to llama3.2-90b and 10 Requests per minute. +The opacity can be adjusted from 0 to 1, and is set to 0.1 per default. Adjust based on your screen's brightness and if you want to risk it being spotted. + +## Installation diff --git a/src/background.js b/src/background.js new file mode 100644 index 0000000..b51288f --- /dev/null +++ b/src/background.js @@ -0,0 +1,8 @@ +// background.js +browser.commands.onCommand.addListener((command) => { + if (command === "print-selection" || command === "fill-input") { + browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => { + browser.tabs.sendMessage(tabs[0].id, { action: command }); + }); + } +}); diff --git a/src/content.js b/src/content.js new file mode 100644 index 0000000..c3003d2 --- /dev/null +++ b/src/content.js @@ -0,0 +1,326 @@ +// Constants +const DEFAULT_SETTINGS = { + apiUrl: "https://api.elia.network", + apiKey: "sk-TvFhtxHAPXEcmRtyva-ctA", + textOpacity: 10, + model: "llama3.2-90b", +}; + +const MAX_CONTEXT_LENGTH = 6000; + +// State +let overlay = null; +let isVisible = false; +let lastKnownSelection = null; +let lastKnownRange = null; +let scrollTimeout = null; +let resizeRAF = null; + +// Utility Functions +const getPageContext = () => { + const bodyText = document.body.innerText; + return bodyText.length > MAX_CONTEXT_LENGTH + ? `${bodyText.substring(0, MAX_CONTEXT_LENGTH)}...` + : bodyText; +}; + +const loadSettings = async () => { + try { + const data = await browser.storage.sync.get("settings"); + return data.settings || DEFAULT_SETTINGS; + } catch (error) { + console.error("Error loading settings:", error); + return DEFAULT_SETTINGS; + } +}; + +// API Functions +const queryLLM = async (text) => { + const settings = await loadSettings(); + const pageContext = getPageContext(); + const response = await fetch(`${settings.apiUrl}/v1/chat/completions`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${settings.apiKey}`, + }, + body: JSON.stringify({ + model: settings.model, + messages: [ + { + role: "user", + content: `You are tasked to solve an exam. Exam's page context: ${pageContext}\n\Answer this question of said exam in very short but accurate manner (Utilize context to figure out the exam's topic. ONLY REPLY WITH ANSWER TO THE QUESTION): ${text}`, + }, + ], + temperature: 0.1, + }), + }); + if (!response.ok) { + const errorData = await response.json().catch(() => null); + throw new Error( + `HTTP error! status: ${response.status}${errorData ? `, message: ${JSON.stringify(errorData)}` : ""}`, + ); + } + + const data = await response.json(); + return data.choices[0].message.content; +}; + +// UI Functions +const createOverlay = (text) => { + if (overlay) overlay.remove(); + + overlay = document.createElement("div"); + overlay.textContent = text; + overlay.className = "llm-overlay"; + + loadSettings().then((settings) => { + overlay.style.cssText = ` + position: absolute; + padding: 10px; + z-index: 2147483647; + color: rgba(0, 0, 0, ${settings.textOpacity / 100}); + font-size: 14px; + max-width: 600px; + white-space: pre-wrap; + pointer-events: none; + `; + + document.body.appendChild(overlay); + requestAnimationFrame(positionOverlay); + }); + + return overlay; +}; + +const positionOverlay = () => { + if (!overlay || !isVisible) return; + + try { + const selection = window.getSelection(); + if (!selection.rangeCount) return; + + const range = selection.getRangeAt(0); + const rect = range.getBoundingClientRect(); + + // Get scroll positions + const scrollX = window.pageXOffset || document.documentElement.scrollLeft; + const scrollY = window.pageYOffset || document.documentElement.scrollTop; + + // Get viewport dimensions + const viewportWidth = + window.innerWidth || document.documentElement.clientWidth; + const viewportHeight = + window.innerHeight || document.documentElement.clientHeight; + + // Calculate overlay dimensions + const overlayWidth = overlay.offsetWidth; + const overlayHeight = overlay.offsetHeight; + + // Calculate positions + let top = rect.top + scrollY; + let left = rect.left + scrollX + rect.width / 2 - overlayWidth / 2; + + // Position above selection by default + top -= overlayHeight + 5; + + // If overlay would go above viewport, position it below selection + if (top - scrollY < 0) { + top = rect.bottom + scrollY + 5; + } + + // Keep overlay within horizontal bounds + left = Math.max( + scrollX + 5, + Math.min(left, scrollX + viewportWidth - overlayWidth - 10), + ); + + // Apply positions + overlay.style.top = `${top}px`; + overlay.style.left = `${left}px`; + } catch (error) { + console.error("Error positioning overlay:", error); + } +}; + +// Event Handlers +const handleScroll = () => { + if (!isVisible) return; + + if (scrollTimeout) { + cancelAnimationFrame(scrollTimeout); + } + + scrollTimeout = requestAnimationFrame(positionOverlay); +}; + +const handleResize = () => { + if (!isVisible) return; + + if (resizeRAF) { + cancelAnimationFrame(resizeRAF); + } + + resizeRAF = requestAnimationFrame(positionOverlay); +}; + +// Initialize Event Listeners +window.addEventListener("resize", handleResize, { passive: true }); + +const attachScrollListeners = () => { + // Listen to window scroll + window.addEventListener("scroll", handleScroll, { passive: true }); + + // Listen to scroll events on all scrollable elements + document.addEventListener("scroll", handleScroll, { + capture: true, + passive: true, + }); + + // Find and attach listeners to all scrollable containers + const scrollableElements = document.querySelectorAll( + [ + '*[style*="overflow: auto"]', + '*[style*="overflow:auto"]', + '*[style*="overflow-y: auto"]', + '*[style*="overflow-y:auto"]', + '*[style*="overflow: scroll"]', + '*[style*="overflow:scroll"]', + '*[style*="overflow-y: scroll"]', + '*[style*="overflow-y:scroll"]', + ].join(","), + ); + + scrollableElements.forEach((element) => { + element.addEventListener("scroll", handleScroll, { passive: true }); + }); + + // Handle dynamic content changes + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.addedNodes.length) { + const newScrollableElements = document.querySelectorAll( + [ + '*[style*="overflow: auto"]', + '*[style*="overflow:auto"]', + '*[style*="overflow-y: auto"]', + '*[style*="overflow-y:auto"]', + '*[style*="overflow: scroll"]', + '*[style*="overflow:scroll"]', + '*[style*="overflow-y: scroll"]', + '*[style*="overflow-y:scroll"]', + ].join(","), + ); + + newScrollableElements.forEach((element) => { + if (!element.hasScrollListener) { + element.addEventListener("scroll", handleScroll, { passive: true }); + element.hasScrollListener = true; + } + }); + } + }); + }); + + observer.observe(document.body, { + childList: true, + subtree: true, + }); +}; + +// Initialize scroll listeners +attachScrollListeners(); + +// Message Handler +browser.runtime.onMessage.addListener((message) => { + if (message.action === "print-selection") { + toggleOverlay(); + } else if (message.action === "fill-input") { + modifyNearestInput(); + } +}); + +// Main Functions +const toggleOverlay = async () => { + const selection = window.getSelection(); + const selectedText = selection.toString().trim(); + + if (!selectedText) { + if (overlay) { + overlay.remove(); + overlay = null; + } + isVisible = false; + return; + } + + isVisible = true; + createOverlay("Processing..."); + + try { + const llmResponse = await queryLLM(selectedText); + createOverlay(llmResponse); + } catch (error) { + console.error("Error:", error); + createOverlay("Error processing request"); + } +}; + +function findNearestInput(selectionNode) { + const inputs = Array.from( + document.querySelectorAll( + 'input[type="text"], input:not([type]), textarea', + ), + ); + + if (!inputs.length) return null; + + const selection = window.getSelection(); + const range = selection.getRangeAt(0); + const rect = range.getBoundingClientRect(); + const selectionX = rect.left + rect.width / 2; + const selectionY = rect.top + rect.height / 2; + + let nearestInput = null; + let shortestDistance = Infinity; + + inputs.forEach((input) => { + const inputRect = input.getBoundingClientRect(); + const inputX = inputRect.left + inputRect.width / 2; + const inputY = inputRect.top + inputRect.height / 2; + + const distance = Math.sqrt( + Math.pow(selectionX - inputX, 2) + Math.pow(selectionY - inputY, 2), + ); + + if (distance < shortestDistance) { + shortestDistance = distance; + nearestInput = input; + } + }); + + return nearestInput; +} + +const modifyNearestInput = async () => { + const selection = window.getSelection(); + const selectedText = selection.toString().trim(); + if (!selectedText) return; + + const nearestInput = findNearestInput(selection.anchorNode); + if (!nearestInput) { + alert("No input field found nearby"); + return; + } + + try { + const oldPlaceholder = nearestInput.placeholder; + nearestInput.placeholder = "Processing..."; + const llmResponse = await queryLLM(selectedText); + nearestInput.value = llmResponse; + nearestInput.placeholder = oldPlaceholder; + } catch (error) { + console.error("Error:", error); + nearestInput.value = "Error processing request"; + } +}; diff --git a/src/icons/icon128.png b/src/icons/icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..50105d562c8991d006d2ec8f0b2ade1e8bc60f55 GIT binary patch literal 10421 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 T5^&)N00000NkvXXu0mjf9onP} literal 0 HcmV?d00001 diff --git a/src/icons/icon32.png b/src/icons/icon32.png new file mode 100644 index 0000000000000000000000000000000000000000..077834856359a4c46ef2abc17ccc56b81cef477a GIT binary patch literal 1047 zcmV+y1nB#TP)^L(G4zziQ^hV^j?;Q!^o+kO`8K7i|(elrJzm35OGM1 z3KQ+UCsVGU@&P-$s4Mc3r4*tvj`=DNHB0Y9-RB1}s)LS(I_1UI$Qoev+{Kh94eSXX zPp-A5a4~s}0zBngjy3Ij(G!cI`@H{xI#~fcSbEQ+&f$p*W2L(84BGl=xth!k~(SZ(;ks7D!WJYjr*UWbyFn z=_$dIYf4G7(AVdF01=0t&kDDfJs=EQNU;X&kx232%AX&HF!mH!xKi z(sE$o)wh$*q57`x#N=A6f0wHif&@m*7+!AM2~s9%3jSxa2aDHmr{|#dc;HLcfB&CKk zgPn}PYL;_kwG;FSX5)Q**OFRLR65zT#59Jw`fw+rxwp{WF zO^37yN+`V2*338AY)hiPx{l!_oO5PXoerLO&(0K032 z+3E)DXlsSWb#K^Ej|nwK{gJ>$XMfs*XzrY1`eWc{2%WW+gsb2dyuE)HmqB97+FHE9 z0FCRP;pS85jh;#AXl4LIr;_#`I{X=gbR^5b$wOn+W*9=lUu?{u(XO<`QfamgCDbQYlz%f56n);KOO=N&LerLno8SmN_K*x~|-|EUu9b4}<h^{ znSf9hR;N9RJ+p7)Z#*C_qh|+3dfaw%o$hnfVq?0Zwn~uAqUpSQ$$=Sn;BRBIe=vc7 R%jW<9002ovPDHLkV1o9T{+j>* literal 0 HcmV?d00001 diff --git a/src/icons/icon48.png b/src/icons/icon48.png new file mode 100644 index 0000000000000000000000000000000000000000..ce39acbe6fc1209c3be4369d369c2bbbb323ecd6 GIT binary patch literal 1989 zcmV;$2RitPP)NF|ixRoq@BAK(~b41~4~mjI^YUu#~VZpiMWiuKMe! z0~z&=Zh@=)GgGrJkFEsbeS|iB@61`Tm~Gxsxa*Y1;@%4Qq#Iqk);2Uiwc+%f#Mb06#YGiWpbK z8{MY%m;F;yGNma5h#xjVzg@e#&->uv1y!(#>hRf!5i*~AG zN&0YvOb@12mBGW59W&eO8d~=xcZT6NA@HYxBc)eC*9^S3Ya@&}9b^NoDFD4+ymmv< zDAuus^$p)>UzUBnWJdPX=~DMVurCx39)-p@fq6Sh`2-DnS~{0f%n6K58YX?x0E3OC zpRPqxIs-^afZ9d~$R2+6a(N^?MKv>)1PYcG0;YxhX?Me57TTGq%MBm4Q(PLQx9{-w zPBF%mK-L9Aq>oY8NTAP!eeuqwe-pPvkfA-a>@)03^kBivhv@i}6D`wk1rZ>Nh07Pi z&;;h>+(V{1VQUCcej%&PY&cTw=@JQLBwp0gQ}j#_x3!?E|_YaJ0P@m4~Y+0d;jeTKyb*!4)iGDw;BReCdlU1$JY8 z7R;W9CkhuilF6GqldJ5;B)H#S5izcaH=d3}?!?^Zwtn(WfzSz*MY`?hZIlgT&k9yL zSIsN4??`AK!sXu!UoS*}_T-^dX6MYp;#I{gTrcjO^fx*dCsdo>@2_%&$<^jlNsPKA z>jVGKZ8z$wAUP6O{8Z5qaRr`XRHMR`$s>*v(J=Sv`H1hI=%l3NrPnZR)?JRw?w@)u zI|XMX+G=gfnKl3GKt@GiaKDQJx=pX6+t~6Lb{?v9^yT{bm5EHMMR!PLOORo7mF z?3`N&o?|rkNj5IsEAZfLc?eT)CMi@$sQH)>NIQS16pXxJb5hZxLKt~UhWH&4%J+Gx zF``Wi;<;tT5S(v%Ab1^HzbeD(6>sC&qCz~JTL9BE>GPD$(Ms1CP9@^{ z=Cq_sTJ3ft-e#w)ZmKx0aluno7=i_UF?#4eu7we358%<6r;Wnp>{z^t}ar%N?$b~ukk*U{;x>o#oSj3e>)DRdXE zKSKg>!i!mxXTae6ud-8n)VL_&iShDwW5qB?l&`RO|zR6KZp{YE%7 z`l~CSvyCF4E;aYmww#?^#)Lp}!VsxRlMRRkYkN>ci?oh+U#5m@z>=b3l$U+adEbUm zotXWiCI)tS)&w?vye;GQTW&4iy=gQq8@C2QJB8n>c~JQ3)2Khth-Vfoa^7LHBMW}h zU~87sz^2?eG3}|s`TJw3`%|A4aSaSixR~;hsNa}{s&^W2_Z#2i$76k*_&X=YlNEq~ zYz;LXsfqioKu1MjQ12l!Z2R%~>~sS5;`+kf{FaRxR|TNXx1R9P`lj8zx&m}m1d@8D zNYmRBK7LJf2Bb;Vp*S?D2}B#YSl!p1fmlT{+UImc0O1 X?#&inQJDwK00000NkvXXu0mjf8&=3< literal 0 HcmV?d00001 diff --git a/src/manifest.json b/src/manifest.json new file mode 100644 index 0000000..ea56ca6 --- /dev/null +++ b/src/manifest.json @@ -0,0 +1,49 @@ +{ + "manifest_version": 2, + "name": "CAS", + "version": "1.2", + "description": "Language processing interface", + "background": { + "scripts": ["background.js"] + }, + "browser_specific_settings": { + "gecko": { + "id": "cas@elia.network", + "strict_min_version": "42.0" + } + }, + "content_scripts": [ + { + "matches": [""], + "js": ["content.js"] + } + ], + "permissions": ["activeTab", "storage", "*://api.elia.network/*"], + "commands": { + "print-selection": { + "suggested_key": { + "default": "Ctrl+Space" + }, + "description": "Process question" + }, + "fill-input": { + "suggested_key": { + "default": "Ctrl+Shift+Space" + }, + "description": "Process question and fill into nearest input field" + } + }, + "browser_action": { + "default_popup": "popup.html", + "default_icon": { + "16": "icons/icon16.png", + "48": "icons/icon48.png", + "128": "icons/icon128.png" + } + }, + "icons": { + "16": "icons/icon16.png", + "48": "icons/icon48.png", + "128": "icons/icon128.png" + } +} diff --git a/src/popup.html b/src/popup.html new file mode 100644 index 0000000..004edbe --- /dev/null +++ b/src/popup.html @@ -0,0 +1,268 @@ + + + + CAS + + + +
+

CAS

+ v1.2 +
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + 50% +
+
+ +
+
+ Solve Question +
+ + + U +
+
+
+ Fill Answer +
+ + + 7 +
+
+
+ + +
+ +
+ + + + diff --git a/src/popup.js b/src/popup.js new file mode 100644 index 0000000..514f62f --- /dev/null +++ b/src/popup.js @@ -0,0 +1,80 @@ +const DEFAULT_SETTINGS = { + apiUrl: "https://api.elia.network", + apiKey: "sk-TvFhtxHAPXEcmRtyva-ctA", + textOpacity: 10, + model: "llama3.2-90b", +}; + +// Load settings when the popup opens +document.addEventListener("DOMContentLoaded", async () => { + try { + const settings = await loadSettings(); + + // Populate form fields + document.getElementById("apiUrl").value = settings.apiUrl; + document.getElementById("model").value = settings.model; + document.getElementById("apiKey").value = settings.apiKey; + document.getElementById("textOpacity").value = settings.textOpacity; + document.getElementById("opacityValue").textContent = + `${settings.textOpacity}%`; + } catch (error) { + console.error("Error loading settings:", error); + showStatus("Error loading settings", "error"); + } +}); + +// Handle opacity slider changes +document.getElementById("textOpacity").addEventListener("input", (e) => { + document.getElementById("opacityValue").textContent = `${e.target.value}%`; +}); + +// Handle form submission +document + .getElementById("settingsForm") + .addEventListener("submit", async (e) => { + e.preventDefault(); + + const settings = { + apiUrl: document.getElementById("apiUrl").value, + apiKey: document.getElementById("apiKey").value, + model: document.getElementById("model").value, + textOpacity: parseInt(document.getElementById("textOpacity").value), + }; + + try { + await browser.storage.sync.set({ settings }); + showStatus("Settings saved successfully!", "success"); + } catch (error) { + console.error("Error saving settings:", error); + showStatus("Error saving settings", "error"); + } + }); + +// Load settings from storage or use defaults +async function loadSettings() { + try { + const data = await browser.storage.sync.get("settings"); + return { ...DEFAULT_SETTINGS, ...data.settings }; + } catch (error) { + console.error("Error loading settings:", error); + return DEFAULT_SETTINGS; + } +} + +// Show status message +function showStatus(message, type) { + const status = document.getElementById("status"); + status.textContent = message; + status.className = type; + status.style.display = "block"; + + setTimeout(() => { + status.style.display = "none"; + }, 2000); +} + +// Handle errors +window.addEventListener("error", (event) => { + console.error("Error:", event.error); + showStatus("An error occurred", "error"); +});