From 5747ee1996b366a91054f2e2ce44970e4ae9c1f8 Mon Sep 17 00:00:00 2001 From: mdshakib007 Date: Mon, 26 Jan 2026 21:35:35 +0600 Subject: [PATCH 1/3] Add AI Inspector UI and context menu integration - Implemented injectInspectorUI function to create a draggable AI Inspector button. - Added context menu option to trigger the AI Inspector toggle. - Updated manifest.json to include content script and permissions for the new functionality. --- Apps/Web/aiplugin/background.js | 24 +++- Apps/Web/aiplugin/content.js | 189 ++++++++++++++++++++++++++++++++ Apps/Web/aiplugin/manifest.json | 18 ++- 3 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 Apps/Web/aiplugin/content.js diff --git a/Apps/Web/aiplugin/background.js b/Apps/Web/aiplugin/background.js index e35af5915..def9567e4 100644 --- a/Apps/Web/aiplugin/background.js +++ b/Apps/Web/aiplugin/background.js @@ -135,6 +135,13 @@ if (navigator.userAgentData.platform.toLowerCase().includes('mac')) { } browserAppData.runtime.onMessage.addListener( function (request, sender, sendResponse) { + + if (request.action === 'toggle_from_content_script') { + // allows the floating button to trigger the toggle logic + toggle(sender.tab); + return; + } + if (request.apiName == 'ai_record_single_action') { var url = `${zeuz_url}/ai_record_single_action/` fetch(url, { @@ -181,4 +188,19 @@ browserAppData.runtime.onMessage.addListener( .then(text => { console.log(text); sendResponse(text); }) } } -); \ No newline at end of file +); + +// add AI Inspector to the right click menu +browserAppData.runtime.onInstalled.addListener(() => { + browserAppData.contextMenus.create({ + id: "toggle-ai-inspect", + title: "Inspect with AI", + contexts: ["all"] + }); +}); + +browserAppData.contextMenus.onClicked.addListener((info, tab) => { + if (info.menuItemId === "toggle-ai-inspect" && tab) { + toggle(tab); + } +}); \ No newline at end of file diff --git a/Apps/Web/aiplugin/content.js b/Apps/Web/aiplugin/content.js new file mode 100644 index 000000000..9f03b0187 --- /dev/null +++ b/Apps/Web/aiplugin/content.js @@ -0,0 +1,189 @@ +function injectInspectorUI() { + // headless check + if (/Headless/i.test(navigator.userAgent)) { + console.log("Headless mode detected. ZeuZ AI Inspector UI skipped."); + return; + } + + const host = document.createElement('div'); + host.id = 'zeuz-ai-inspector-host'; + + // initial position (fixed) + Object.assign(host.style, { + position: 'fixed', + bottom: '20px', + right: '20px', + zIndex: '2147483647', // maximum z-index + width: 'auto', + height: 'auto', + filter: 'drop-shadow(0 4px 6px rgba(0,0,0,0.15))' + }); + + document.body.appendChild(host); + + // shadow dom + const shadow = host.attachShadow({ mode: 'open' }); + + const style = document.createElement('style'); + style.textContent = ` + :host { + font-family: sans-serif; + } + .container { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + cursor: grab; /* Cursor indicates draggable */ + user-select: none; + } + .container:active { + cursor: grabbing; + } + + /* The Main Button */ + .ai-fab { + width: 56px; + height: 56px; + background: #1500ffff; + border-radius: 50%; + border: 2px solid #fff; + display: flex; + align-items: center; + justify-content: center; + font-size: 28px; + color: white; + transition: all 0.2s ease; + } + + /* Active State */ + .ai-fab.active { + background: #ff0000ff; + animation: pulse 2s infinite; + } + + /* Hover Effect */ + .container:hover .ai-fab { + transform: scale(1.05); + } + + /* Close Button */ + .close-btn { + position: absolute; + top: -8px; + right: -8px; + width: 20px; + height: 20px; + background: #4b5563; + color: #fff; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: bold; + cursor: pointer; + opacity: 0; /* Hidden by default */ + transition: opacity 0.2s; + border: 2px solid white; + } + + /* close button on hover */ + .container:hover .close-btn { + opacity: 1; + } + + @keyframes pulse { + 0% { box-shadow: 0 0 0 0 rgba(220, 38, 38, 0.7); } + 70% { box-shadow: 0 0 0 10px rgba(220, 38, 38, 0); } + 100% { box-shadow: 0 0 0 0 rgba(220, 38, 38, 0); } + } + `; + shadow.appendChild(style); + + const container = document.createElement('div'); + container.className = 'container'; + + // main button + const btn = document.createElement('div'); + btn.className = 'ai-fab'; + const btnImg = document.createElement('img'); + btnImg.src = 'zeuz.png' + btn.innerHTML = btnImg; + + // close btn + const closeBtn = document.createElement('div'); + closeBtn.className = 'close-btn'; + closeBtn.innerHTML = '✕'; + + closeBtn.addEventListener('click', (e) => { + e.stopPropagation(); + host.remove(); // remove the whole UI + }); + + container.appendChild(btn); + container.appendChild(closeBtn); + shadow.appendChild(container); + + // drag + let isDragging = false; + let hasMoved = false; + let startX, startY, initialRight, initialBottom; + + const onMouseDown = (e) => { + isDragging = true; + hasMoved = false; + + const rect = host.getBoundingClientRect(); + + host.style.right = 'auto'; + host.style.bottom = 'auto'; + host.style.left = rect.left + 'px'; + host.style.top = rect.top + 'px'; + + startX = e.clientX; + startY = e.clientY; + }; + + const onMouseMove = (e) => { + if (!isDragging) return; + + const dx = e.clientX - startX; + const dy = e.clientY - startY; + + if (Math.abs(dx) > 3 || Math.abs(dy) > 3) { + hasMoved = true; + } + + host.style.left = (host.offsetLeft + dx) + 'px'; + host.style.top = (host.offsetTop + dy) + 'px'; + + startX = e.clientX; + startY = e.clientY; + }; + + const onMouseUp = () => { + isDragging = false; + }; + + // drag listeners + container.addEventListener('mousedown', onMouseDown); + window.addEventListener('mousemove', onMouseMove); + window.addEventListener('mouseup', onMouseUp); + + btn.addEventListener('click', () => { + if (!hasMoved) { + chrome.runtime.sendMessage({ action: 'toggle_from_content_script' }); + } + }); + + chrome.runtime.onMessage.addListener((request) => { + if (request.action === 'activate') { + btn.classList.add('active'); + } else if (request.action === 'deactivate') { + btn.classList.remove('active'); + } + }); +} + +injectInspectorUI(); \ No newline at end of file diff --git a/Apps/Web/aiplugin/manifest.json b/Apps/Web/aiplugin/manifest.json index 0e4e310fe..4bf0a2308 100644 --- a/Apps/Web/aiplugin/manifest.json +++ b/Apps/Web/aiplugin/manifest.json @@ -29,6 +29,16 @@ "js": [ "inspect.js" ] + }, + { + "all_frames": false, + "matches": [ + "http://*/*", + "https://*/*" + ], + "js": [ + "content.js" + ] } ], "commands": { @@ -50,5 +60,11 @@ "host_permissions": [ "http://*/", "https://*/" - ] + ], + "web_accessible_resources": [ + { + "resources": ["zeuz.png", "zeuz-active.png"], + "matches": [""] + } +] } \ No newline at end of file From f2c0d1fb6fc9de974f310798d151fa9e59ef89bc Mon Sep 17 00:00:00 2001 From: shakib Date: Tue, 27 Jan 2026 09:09:10 +0600 Subject: [PATCH 2/3] Update AI Inspector UI styles and add right click context menu for deactivation --- Apps/Web/aiplugin/content.js | 38 +++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/Apps/Web/aiplugin/content.js b/Apps/Web/aiplugin/content.js index 9f03b0187..ab6d45195 100644 --- a/Apps/Web/aiplugin/content.js +++ b/Apps/Web/aiplugin/content.js @@ -45,7 +45,7 @@ function injectInspectorUI() { .ai-fab { width: 56px; height: 56px; - background: #1500ffff; + background: #d3a8ffff; border-radius: 50%; border: 2px solid #fff; display: flex; @@ -54,12 +54,18 @@ function injectInspectorUI() { font-size: 28px; color: white; transition: all 0.2s ease; + cursor: pointer; + } + + .ai-fab img { + pointer-events: none; } /* Active State */ .ai-fab.active { - background: #ff0000ff; + background: #ff8d8dff; animation: pulse 2s infinite; + cursor: default; } /* Hover Effect */ @@ -108,8 +114,10 @@ function injectInspectorUI() { const btn = document.createElement('div'); btn.className = 'ai-fab'; const btnImg = document.createElement('img'); - btnImg.src = 'zeuz.png' - btn.innerHTML = btnImg; + btnImg.src = chrome.runtime.getURL('zeuz.png'); + btnImg.style.width = '32px'; + btnImg.style.height = '32px'; + btn.appendChild(btnImg); // close btn const closeBtn = document.createElement('div'); @@ -128,9 +136,12 @@ function injectInspectorUI() { // drag let isDragging = false; let hasMoved = false; - let startX, startY, initialRight, initialBottom; + let startX, startY; const onMouseDown = (e) => { + // don't drag if inspector is active + if (btn.classList.contains('active')) return; + isDragging = true; hasMoved = false; @@ -143,6 +154,8 @@ function injectInspectorUI() { startX = e.clientX; startY = e.clientY; + + e.preventDefault(); }; const onMouseMove = (e) => { @@ -171,8 +184,17 @@ function injectInspectorUI() { window.addEventListener('mousemove', onMouseMove); window.addEventListener('mouseup', onMouseUp); - btn.addEventListener('click', () => { - if (!hasMoved) { + btn.addEventListener('click', (e) => { + if (!hasMoved && !btn.classList.contains('active')) { + chrome.runtime.sendMessage({ action: 'toggle_from_content_script' }); + } + hasMoved = false; // Reset after click + }); + + // right-click context menu for deactivation when inspector is active + btn.addEventListener('contextmenu', (e) => { + e.preventDefault(); + if (btn.classList.contains('active')) { chrome.runtime.sendMessage({ action: 'toggle_from_content_script' }); } }); @@ -180,8 +202,10 @@ function injectInspectorUI() { chrome.runtime.onMessage.addListener((request) => { if (request.action === 'activate') { btn.classList.add('active'); + btnImg.src = chrome.runtime.getURL('zeuz-active.png'); } else if (request.action === 'deactivate') { btn.classList.remove('active'); + btnImg.src = chrome.runtime.getURL('zeuz.png'); } }); } From 6f34353ac8a20ecbacdae3c14de96835fbd7aec3 Mon Sep 17 00:00:00 2001 From: shakib Date: Tue, 27 Jan 2026 09:23:15 +0600 Subject: [PATCH 3/3] Refactor Inspector class to improve modal text insertion and add success message display --- Apps/Web/aiplugin/inspect.js | 193 ++++++++++++++++++++++++----------- 1 file changed, 132 insertions(+), 61 deletions(-) diff --git a/Apps/Web/aiplugin/inspect.js b/Apps/Web/aiplugin/inspect.js index 8ce7ac08f..0cb3a1ab3 100644 --- a/Apps/Web/aiplugin/inspect.js +++ b/Apps/Web/aiplugin/inspect.js @@ -63,25 +63,23 @@ class Inspector { // message element const modalNode = document.getElementById(this.modalNode); - function insert_modal_text(response, modal_id) { + const insert_modal_text = (response, modal_id) => { console.log("insert_modal_text ..................") if (response["info"] == "success") { - // show message about element const modalText = 'Element data was recorded. Please Click "Add by AI"'; console.log(modalText); - if (modalNode) { - modalNode.innerText = modalText; - } else { - const modalHtml = document.createElement('div'); - modalHtml.innerText = modalText; - modalHtml.id = modal_id; - document.body.appendChild(modalHtml); + + if (this.successContainer) { + this.successContainer.textContent = modalText; + this.successContainer.classList.add('show'); + setTimeout(() => { + this.successContainer.classList.remove('show'); + }, 3000); } return true; } console.error(response["info"]); return false; - } async function send_data(server_url, api_key, data, modal_id, refinedHtml) { @@ -295,23 +293,6 @@ class Inspector { setOptions(options) { this.options = options; - let position = 'bottom:0;left:0'; - let positionParent = 'top:0;left:0'; - let positionModal = 'top:50%;left:40%'; - switch (options.position) { - case 'tl': - position = 'top:0;left:0'; - break; - case 'tr': - position = 'top:0;right:0'; - break; - case 'br': - position = 'bottom:0;right:0'; - break; - default: - break; - } - this.styles = `*{cursor:crosshair!important;}#xpath-content{${position};cursor:initial!important;padding:10px;background:gray;color:white;position:fixed;font-size:14px;z-index:10000001;}#xpath-parent-content{${positionParent};cursor:initial!important;padding:10px;background:gray;color:white;position:fixed;font-size:14px;z-index:10000001;}#${this.modalNode}{${position};cursor:initial!important;padding:10px;background:#F2F2F2;color:green;position:fixed;font-size:14px;z-index:10000001;}#${this.elementNode}{${positionParent};cursor:initial!important;padding:10px;background:gray;color:white;position:fixed;font-size:14px;z-index:10000001;}`; this.activate(); } @@ -354,6 +335,102 @@ class Inspector { overlayHtml && overlayHtml.remove(); } + createAttributeDisplay() { + const host = document.createElement('div'); + host.id = 'zeuz-attributes-host'; + Object.assign(host.style, { + position: 'fixed', + top: '10px', + left: '10px', + zIndex: '2147483647', + pointerEvents: 'none' + }); + document.body.appendChild(host); + + const shadow = host.attachShadow({ mode: 'open' }); + const style = document.createElement('style'); + style.textContent = ` + .attributes-container { + background: rgba(0, 0, 0, 0.8); + color: white; + padding: 6px 10px; + border-radius: 4px; + font-family: monospace; + font-size: 11px; + max-width: 300px; + word-break: break-all; + backdrop-filter: blur(4px); + border: 1px solid rgba(255, 255, 255, 0.2); + } + `; + shadow.appendChild(style); + + const container = document.createElement('div'); + container.className = 'attributes-container'; + shadow.appendChild(container); + + this.attributesHost = host; + this.attributesContainer = container; + } + + createSuccessMessage() { + const host = document.createElement('div'); + host.id = 'zeuz-success-host'; + Object.assign(host.style, { + position: 'fixed', + top: '20px', + left: '50%', + transform: 'translateX(-50%)', + zIndex: '2147483647', + pointerEvents: 'none' + }); + document.body.appendChild(host); + + const shadow = host.attachShadow({ mode: 'open' }); + const style = document.createElement('style'); + style.textContent = ` + .success-message { + background: linear-gradient(135deg, #4ade80, #22c55e); + color: white; + padding: 12px 20px; + border-radius: 8px; + font-family: sans-serif; + font-size: 14px; + font-weight: 500; + box-shadow: 0 4px 12px rgba(34, 197, 94, 0.3); + border: 1px solid rgba(255, 255, 255, 0.2); + opacity: 0; + transform: translateY(-10px); + transition: all 0.3s ease; + } + .success-message.show { + opacity: 1; + transform: translateY(0); + } + `; + shadow.appendChild(style); + + const container = document.createElement('div'); + container.className = 'success-message'; + shadow.appendChild(container); + + this.successHost = host; + this.successContainer = container; + } + + updateAttributePosition(mouseY) { + if (this.attributesHost) { + const isTopHalf = mouseY < window.innerHeight / 2; + if (isTopHalf) { + this.attributesHost.style.top = 'auto'; + this.attributesHost.style.bottom = '10px'; + } else { + this.attributesHost.style.top = '10px'; + this.attributesHost.style.bottom = 'auto'; + } + } + } + copyText(XPath) { const hdInp = document.createElement('textarea'); hdInp.textContent = XPath; @@ -387,35 +464,32 @@ class Inspector { this.doc.body.appendChild(this.container); + // attributes display if not exists + if (!this.attributesHost) { + this.createAttributeDisplay(); + } + + // position based on mouse location + this.updateAttributePosition(e.clientY); - // show element attributes - const elementNode = document.getElementById(this.elementNode); - var elementText = ""; + let elementText = ""; for (let name of e.target.getAttributeNames()) { let value = e.target.getAttribute(name); - var each = name + " = \"" + value + "\", "; - elementText += each; + elementText += `${name}="${value}" `; } - if (elementNode) { - elementNode.innerText = elementText; - } else { - const elementHtml = document.createElement('div'); - elementHtml.innerText = elementText; - elementHtml.id = this.elementNode; - document.body.appendChild(elementHtml); - } - + + this.attributesContainer.textContent = elementText.trim(); } activate() { this.createOverlayElements(); - // add styles - if (!document.getElementById(this.cssNode)) { - const styles = document.createElement('style'); - styles.innerText = this.styles; - styles.id = this.cssNode; - document.getElementsByTagName('head')[0].appendChild(styles); - } + this.createSuccessMessage(); + + const style = document.createElement('style'); + style.id = this.cssNode; + style.textContent = '*{cursor:crosshair!important;}'; + document.head.appendChild(style); + // add listeners document.addEventListener('click', this.getData, true); this.options.inspector && (document.addEventListener('mouseover', this.draw)); @@ -425,30 +499,27 @@ class Inspector { // remove overlay this.removeOverlay(); - this.cssNode = 'xpath-css'; - this.overlayElement = 'xpath-overlay'; - this.modalNode = 'zeuzMyModal'; - this.elementNode = 'zeuzMyElement'; - let Remove = [ this.cssNode, this.overlayElement, - this.modalNode, - this.elementNode, + 'zeuz-attributes-host', + 'zeuz-success-host' ] - for (let elem of Remove){ - elem = document.getElementById(elem); + for (let elemId of Remove){ + const elem = document.getElementById(elemId); elem && elem.remove(); } - // remove styles - const cssNode = document.getElementById(this.cssNode); - cssNode && cssNode.remove(); - // remove listeners document.removeEventListener('click', this.getData, true); this.options && this.options.inspector && (document.removeEventListener('mouseover', this.draw)); + + // reset + this.attributesHost = null; + this.attributesContainer = null; + this.successHost = null; + this.successContainer = null; } getXPath(el) {